diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index c32e0daa6..e1a377973 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -107,7 +107,16 @@ jobs: KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} GRADLE_OPTS: -Xmx4g -XX:+UseG1GC + PR_NUMBER_RAW: ${{ github.event.pull_request.number }} + HEAD_SHA_RAW: ${{ github.event.pull_request.head.sha }} run: | + # A pull request build carries the pull request it came from and the commit the release + # notes name, so the update check can tell it from a later build of the same pull request. + # The checkout is the merge commit, so the head commit has to be passed in. + if [ -n "$PR_NUMBER_RAW" ]; then + export PR_NUMBER="$PR_NUMBER_RAW" + export BUILD_ID="$(printf '%s' "$HEAD_SHA_RAW" | cut -c1-8)" + fi export KEYSTORE_FILE="${{ github.workspace }}/release.jks" if [ -n "$KEYSTORE_BASE64" ]; then echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE_FILE" diff --git a/.github/workflows/pr-release-publish.yml b/.github/workflows/pr-release-publish.yml index 2825147c0..ed569c241 100644 --- a/.github/workflows/pr-release-publish.yml +++ b/.github/workflows/pr-release-publish.yml @@ -108,6 +108,34 @@ jobs: wait "$pid" done + # PRs come from forks, which get no secrets, so every PR build is signed with a fresh throwaway + # key and cannot update over the previous one. This run holds secrets without running PR code, + # so the APKs are re-signed here with one key kept for PR builds only. + - name: Re-sign APKs with the PR key + if: ${{ steps.scope.outputs.allowed == 'true' }} + env: + PR_KEYSTORE_BASE64: ${{ secrets.PR_KEYSTORE_BASE64 }} + PR_KEYSTORE_PASSWORD: ${{ secrets.PR_KEYSTORE_PASSWORD }} + PR_KEY_ALIAS: ${{ secrets.PR_KEY_ALIAS }} + PR_KEY_PASSWORD: ${{ secrets.PR_KEY_PASSWORD }} + run: | + set -euo pipefail + if [ -z "$PR_KEYSTORE_BASE64" ]; then + echo "::warning::PR_KEYSTORE_BASE64 is not set; PR builds keep their one-off key and will not update over each other" + exit 0 + fi + build_tools=$(find "$ANDROID_SDK_ROOT/build-tools" -maxdepth 1 -mindepth 1 -type d | sort -V | tail -1) + keystore="$RUNNER_TEMP/pr.jks" + trap 'rm -f "$keystore"' EXIT + printf '%s' "$PR_KEYSTORE_BASE64" | base64 -d > "$keystore" + for apk in artifacts/*/*.apk; do + "$build_tools/apksigner" sign \ + --ks "$keystore" --ks-pass env:PR_KEYSTORE_PASSWORD \ + --ks-key-alias "$PR_KEY_ALIAS" --key-pass env:PR_KEY_PASSWORD \ + "$apk" + "$build_tools/apksigner" verify --print-certs "$apk" | grep 'SHA-256 digest' + done + - name: Build release notes from PR metadata if: ${{ steps.scope.outputs.allowed == 'true' }} id: pr diff --git a/CREDITS.md b/CREDITS.md index dfa4edb83..cb33ddff9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -24,6 +24,12 @@ corresponding source for every copyleft component is available from the projects - **Mesa/Turnip** contributions by the [Mesa3D](https://www.mesa3d.org/) team - **Goldberg Steam Emulator** by [Mr. Goldberg](https://gitlab.com/Mr_Goldberg/goldberg_emulator), maintained by [Detanup01](https://github.com/Detanup01/gbe_fork) +- **Wayland display server** โ€” the embedded Wayland compositor (`app/src/main/cpp/waylandcomp`), + its Wayland runtime design, clipboard and IME bridges, and the winewayland launch path by + **Banner ([The412Banner](https://github.com/The412Banner))** from + [Bannerlator](https://github.com/The412Banner/Bannerlator), ported to WinNative; ships the + vendored `libwayland-server` 1.26 and `libffi` from the same project. See + [docs/WAYLAND-DISPLAY.md](docs/WAYLAND-DISPLAY.md). ## Retro console emulation diff --git a/README.md b/README.md index bebc5ea58..f0790694f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ alongside them. | ๐Ÿ“ฆ **Install** | [Releases](https://github.com/WinNative-Emu/WinNative/releases) | | ๐ŸŽฎ **Retro consoles** | [docs/RETRO-CONSOLES.md](docs/RETRO-CONSOLES.md) โ€” NES through PlayStation 2 | | ๐ŸŽž๏ธ **Frame generation** | [docs/FRAME-GENERATION.md](docs/FRAME-GENERATION.md) โ€” LSFG and DIS | +| ๐Ÿ–ฅ๏ธ **Wayland display server** | [docs/WAYLAND-DISPLAY.md](docs/WAYLAND-DISPLAY.md) โ€” winewayland sessions on the embedded compositor | | ๐Ÿ”จ **Build from source** | [docs/BUILDING.md](docs/BUILDING.md) | | ๐Ÿ™ **Credits & licenses** | [CREDITS.md](CREDITS.md) ยท [EMULATOR_CREDITS.md](EMULATOR_CREDITS.md) | | ๐Ÿ’ฌ **Chat** | [Discord](https://discord.gg/uhTkvGfakU) | diff --git a/app/build.gradle b/app/build.gradle index 72d564989..bf9fbe83d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,6 +164,13 @@ def appBuildId = providers.gradleProperty("BUILD_ID") .map { it.trim() } .getOrElse(gitBuildId) +// The pull request this build came from, so an install made from CI knows which one to follow. +// Digits only: it is written into generated Java as a literal. +def appPullRequest = providers.gradleProperty("PR_NUMBER") + .orElse(providers.environmentVariable("PR_NUMBER")) + .map { it.trim().replaceAll("[^0-9]", "") } + .getOrElse("") + def playUnreachableVersionCodeBase = 2100000000 def brandedVersionCode = playUnreachableVersionCodeBase + appVersionCode if (brandedVersionCode < playUnreachableVersionCodeBase || brandedVersionCode > 2147483647) { @@ -203,6 +210,7 @@ android { buildConfigField("String", "COLD_CLIENT_VERSION", "\"${coldClientVersion}\"") buildConfigField "int", "TRUE_VERSION_CODE", "${appVersionCode}" buildConfigField "String", "BUILD_ID", "\"${appBuildId}\"" + buildConfigField "String", "PR_NUMBER", "\"${appPullRequest}\"" buildConfigField "String", "RESHADE_CATALOG_URL", "\"https://raw.githubusercontent.com/nicholasx417/WinNative-Components/main/Reshade.json\"" } diff --git a/app/src/androidTest/assets/update/ci-pr-769.json b/app/src/androidTest/assets/update/ci-pr-769.json new file mode 100644 index 000000000..0c4c664f6 --- /dev/null +++ b/app/src/androidTest/assets/update/ci-pr-769.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/392625586","assets_url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/392625586/assets","upload_url":"https://uploads.github.com/repos/WinNative-Emu/WinNative-CI/releases/392625586/assets{?name,label}","html_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/tag/pr-769","id":392625586,"author":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"node_id":"RE_kwDOSJWgoM4XZv2y","tag_name":"pr-769","target_commitish":"main","name":"PR #769: Feature Wayland, GameScope, Steam","draft":false,"immutable":false,"prerelease":true,"created_at":"2026-04-22T08:00:42Z","updated_at":"2026-09-22T15:09:42Z","published_at":"2026-09-21T00:29:14Z","assets":[{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/581646616","id":581646616,"node_id":"RA_kwDOSJWgoM4iqzkY","name":"WinNative-Debug-Antutu-PR.769-59423.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":608213326,"digest":"sha256:ed36bd77d984de1a247be53270eb96d1a3bc15c16de53f9a76b90540d08490ec","download_count":1,"created_at":"2026-09-22T15:09:29Z","updated_at":"2026-09-22T15:09:42Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-769/WinNative-Debug-Antutu-PR.769-59423.apk"},{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/581645657","id":581645657,"node_id":"RA_kwDOSJWgoM4iqzVZ","name":"WinNative-Debug-Ludashi-PR.769-59423.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":608216634,"digest":"sha256:c9192b832cb20de332a72c656dc07276c893e03e43a7843d988cbd7f93d5bd59","download_count":2,"created_at":"2026-09-22T15:08:57Z","updated_at":"2026-09-22T15:09:14Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-769/WinNative-Debug-Ludashi-PR.769-59423.apk"},{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/581646216","id":581646216,"node_id":"RA_kwDOSJWgoM4iqzeI","name":"WinNative-Debug-Pubg-PR.769-59423.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":608222642,"digest":"sha256:7189027dad603dfb09293428cea20bb2fac19f9aab331f37109f9862865920f7","download_count":1,"created_at":"2026-09-22T15:09:15Z","updated_at":"2026-09-22T15:09:28Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-769/WinNative-Debug-Pubg-PR.769-59423.apk"},{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/581645076","id":581645076,"node_id":"RA_kwDOSJWgoM4iqzMU","name":"WinNative-Debug-Standard-PR.769-59423.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":608207858,"digest":"sha256:a43e21b7352580373d567681bc61a03b59bd6ebb72059c9f6d6f811975962983","download_count":1,"created_at":"2026-09-22T15:08:39Z","updated_at":"2026-09-22T15:08:56Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-769/WinNative-Debug-Standard-PR.769-59423.apk"}],"tarball_url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/tarball/pr-769","zipball_url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/zipball/pr-769","body":"# PR #769\n\nTitle: Feature Wayland, GameScope, Steam\n\nAuthor: @maxjivi05\n\nSource PR: https://github.com/WinNative-Emu/WinNative/pull/769\nSource branch: feature/wayland-gamescope\nSource commit: `594231dd8e5eebfd83a20634852584c696eba924`\nBuild workflow run: https://github.com/WinNative-Emu/WinNative/actions/runs/35744179796\n\n## What This PR Is About\n\nNo PR description was provided.\n\n## Attached Artifacts\n\n- WinNative-Debug-Standard-PR.769-59423.apk\n- WinNative-Debug-Ludashi-PR.769-59423.apk\n- WinNative-Debug-Pubg-PR.769-59423.apk\n- WinNative-Debug-Antutu-PR.769-59423.apk\n\nBuilt from source commit `59423`.\n\n\n","mentions_count":1} \ No newline at end of file diff --git a/app/src/androidTest/assets/update/ci-pr-770.json b/app/src/androidTest/assets/update/ci-pr-770.json new file mode 100644 index 000000000..8fddcdfda --- /dev/null +++ b/app/src/androidTest/assets/update/ci-pr-770.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/393308884","assets_url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/393308884/assets","upload_url":"https://uploads.github.com/repos/WinNative-Emu/WinNative-CI/releases/393308884/assets{?name,label}","html_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/tag/pr-770","id":393308884,"author":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"node_id":"RE_kwDOSJWgoM4XcWrU","tag_name":"pr-770","target_commitish":"main","name":"PR #770: DIS: improve smoothness, eliminate NaN on huge frames difference.","draft":false,"immutable":false,"prerelease":true,"created_at":"2026-04-22T08:00:42Z","updated_at":"2026-09-21T21:28:02Z","published_at":"2026-09-21T21:26:30Z","assets":[{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/579900633","id":579900633,"node_id":"RA_kwDOSJWgoM4ikJTZ","name":"WinNative-Debug-Antutu-PR.770-091b4.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":603382192,"digest":"sha256:6029cb67b22e2dedb97707a6146a47ef31b43f27945a8af636bd0598e502b365","download_count":7,"created_at":"2026-09-21T21:27:41Z","updated_at":"2026-09-21T21:28:01Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-770/WinNative-Debug-Antutu-PR.770-091b4.apk"},{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/579899604","id":579899604,"node_id":"RA_kwDOSJWgoM4ikJDU","name":"WinNative-Debug-Ludashi-PR.770-091b4.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":603385208,"digest":"sha256:04b3d23d77550fe437d0c075ed120ebb2242fd91430c88c09944bf20360b8e13","download_count":3,"created_at":"2026-09-21T21:26:59Z","updated_at":"2026-09-21T21:27:20Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-770/WinNative-Debug-Ludashi-PR.770-091b4.apk"},{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/579900103","id":579900103,"node_id":"RA_kwDOSJWgoM4ikJLH","name":"WinNative-Debug-Pubg-PR.770-091b4.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":603391036,"digest":"sha256:fe61663ce1530530ab6ccf6020d97c2a8c0473bdfc34991cf6df25e3cc60e804","download_count":10,"created_at":"2026-09-21T21:27:21Z","updated_at":"2026-09-21T21:27:40Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-770/WinNative-Debug-Pubg-PR.770-091b4.apk"},{"url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/assets/579899035","id":579899035,"node_id":"RA_kwDOSJWgoM4ikI6b","name":"WinNative-Debug-Standard-PR.770-091b4.apk","label":"","uploader":{"login":"maxjivi05","id":6025299,"node_id":"MDQ6VXNlcjYwMjUyOTk=","avatar_url":"https://avatars.githubusercontent.com/u/6025299?v=4","gravatar_id":"","url":"https://api.github.com/users/maxjivi05","html_url":"https://github.com/maxjivi05","followers_url":"https://api.github.com/users/maxjivi05/followers","following_url":"https://api.github.com/users/maxjivi05/following{/other_user}","gists_url":"https://api.github.com/users/maxjivi05/gists{/gist_id}","starred_url":"https://api.github.com/users/maxjivi05/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maxjivi05/subscriptions","organizations_url":"https://api.github.com/users/maxjivi05/orgs","repos_url":"https://api.github.com/users/maxjivi05/repos","events_url":"https://api.github.com/users/maxjivi05/events{/privacy}","received_events_url":"https://api.github.com/users/maxjivi05/received_events","type":"User","user_view_type":"public","site_admin":false},"content_type":"application/vnd.android.package-archive","state":"uploaded","size":603375980,"digest":"sha256:38fa9d1a28f27da396cffbde36be2a7b368a62d9b5d3a52bded5e14b02ef3492","download_count":26,"created_at":"2026-09-21T21:26:39Z","updated_at":"2026-09-21T21:26:58Z","browser_download_url":"https://github.com/WinNative-Emu/WinNative-CI/releases/download/pr-770/WinNative-Debug-Standard-PR.770-091b4.apk"}],"tarball_url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/tarball/pr-770","zipball_url":"https://api.github.com/repos/WinNative-Emu/WinNative-CI/zipball/pr-770","body":"# PR #770\n\nTitle: DIS: improve smoothness, eliminate NaN on huge frames difference.\n\nAuthor: @qwertypower\n\nSource PR: https://github.com/WinNative-Emu/WinNative/pull/770\nSource branch: main\nSource commit: `091b4e71fb62af77354980366a46104a3b680705`\nBuild workflow run: https://github.com/WinNative-Emu/WinNative/actions/runs/35656232903\n\n## What This PR Is About\n\nNo PR description was provided.\n\n## Attached Artifacts\n\n- WinNative-Debug-Standard-PR.770-091b4.apk\n- WinNative-Debug-Ludashi-PR.770-091b4.apk\n- WinNative-Debug-Pubg-PR.770-091b4.apk\n- WinNative-Debug-Antutu-PR.770-091b4.apk\n\nBuilt from source commit `091b4`.\n\n\n","mentions_count":1} \ No newline at end of file diff --git a/app/src/androidTest/assets/update/official.json b/app/src/androidTest/assets/update/official.json new file mode 100644 index 000000000..e6b8797b9 --- /dev/null +++ b/app/src/androidTest/assets/update/official.json @@ -0,0 +1 @@ +[{"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/387735054", "assets_url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/387735054/assets", "upload_url": "https://uploads.github.com/repos/WinNative-Emu/WinNative/releases/387735054/assets{?name,label}", "html_url": "https://github.com/WinNative-Emu/WinNative/releases/tag/v0.6.2-beta", "id": 387735054, "author": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "node_id": "RE_kwDORgdwrM4XHF4O", "tag_name": "v0.6.2-beta", "target_commitish": "a19f05b562fb5b160db88a3168b52892ddea0864", "name": "v0.6.2-beta", "draft": false, "immutable": false, "prerelease": false, "created_at": "2026-09-12T23:17:59Z", "updated_at": "2026-09-12T23:33:06Z", "published_at": "2026-09-12T23:33:06Z", "assets": [{"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/560135084", "id": 560135084, "node_id": "RA_kwDORgdwrM4hYvus", "name": "WinNative-v0.6.2-beta-Antutu-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600794259, "digest": "sha256:648059ad78a0e3550b1a3114038a10d45cfc730ad3a21eec1a58106c02a88cd8", "download_count": 4547, "created_at": "2026-09-12T23:30:07Z", "updated_at": "2026-09-12T23:30:56Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.2-beta/WinNative-v0.6.2-beta-Antutu-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/560135082", "id": 560135082, "node_id": "RA_kwDORgdwrM4hYvuq", "name": "WinNative-v0.6.2-beta-Ludashi-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600797563, "digest": "sha256:756d80ee99643bab29611ce123b1bcd8eb175a452be45cbfc0ed4a0b420c3a2f", "download_count": 2538, "created_at": "2026-09-12T23:30:07Z", "updated_at": "2026-09-12T23:30:54Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.2-beta/WinNative-v0.6.2-beta-Ludashi-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/560135076", "id": 560135076, "node_id": "RA_kwDORgdwrM4hYvuk", "name": "WinNative-v0.6.2-beta-Pubg-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600803267, "digest": "sha256:9144b58102b7278cc28d9c54371ed15efc9282073cb6688ea27d7d02d02ed527", "download_count": 632, "created_at": "2026-09-12T23:30:07Z", "updated_at": "2026-09-12T23:30:56Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.2-beta/WinNative-v0.6.2-beta-Pubg-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/560135080", "id": 560135080, "node_id": "RA_kwDORgdwrM4hYvuo", "name": "WinNative-v0.6.2-beta-Standard-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600788371, "digest": "sha256:39e77e1e005ab6b5d3af2cda1a5e0c6bbd9c551706126fe1f3d8b86f0380fb20", "download_count": 5085, "created_at": "2026-09-12T23:30:07Z", "updated_at": "2026-09-12T23:30:52Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.2-beta/WinNative-v0.6.2-beta-Standard-signed.apk"}], "tarball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/tarball/v0.6.2-beta", "zipball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/zipball/v0.6.2-beta", "body": "## What's Changed\r\n* Fix Multiple Crashes & Freezing Issues\r\n* DIS: run the variational refinement on the whole pyramid by @qwertypower in https://github.com/WinNative-Emu/WinNative/pull/748\r\n* Feature/device profiles by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/751\r\n\r\n\r\n**Changelog**: https://github.com/WinNative-Emu/WinNative/compare/v0.6.1-beta...v0.6.2-beta", "reactions": {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/387735054/reactions", "total_count": 29, "+1": 8, "-1": 0, "laugh": 3, "hooray": 5, "confused": 0, "heart": 6, "rocket": 3, "eyes": 4}, "mentions_count": 2}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/387586788", "assets_url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/387586788/assets", "upload_url": "https://uploads.github.com/repos/WinNative-Emu/WinNative/releases/387586788/assets{?name,label}", "html_url": "https://github.com/WinNative-Emu/WinNative/releases/tag/v0.6.1-beta", "id": 387586788, "author": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "node_id": "RE_kwDORgdwrM4XGhrk", "tag_name": "v0.6.1-beta", "target_commitish": "7e8903791edf031cfb75a15c75e71bf2883ffead", "name": "v0.6.1-beta", "draft": false, "immutable": false, "prerelease": false, "created_at": "2026-09-12T12:48:23Z", "updated_at": "2026-09-12T14:33:01Z", "published_at": "2026-09-12T14:33:01Z", "assets": [{"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/559346174", "id": 559346174, "node_id": "RA_kwDORgdwrM4hVvH-", "name": "WinNative-v0.6.1-beta-Antutu-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600687629, "digest": "sha256:8e6a3cf002c7b43a61d1871fbb0cb67eb69721a06d51dc0c30d043ef18af5cf8", "download_count": 337, "created_at": "2026-09-12T13:53:21Z", "updated_at": "2026-09-12T13:53:46Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.1-beta/WinNative-v0.6.1-beta-Antutu-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/559346170", "id": 559346170, "node_id": "RA_kwDORgdwrM4hVvH6", "name": "WinNative-v0.6.1-beta-Ludashi-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600690753, "digest": "sha256:efacada27f029b810d831cad90325b3a723da4fbe7d9ff0fdd5b9567fead71cc", "download_count": 316, "created_at": "2026-09-12T13:53:21Z", "updated_at": "2026-09-12T13:53:44Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.1-beta/WinNative-v0.6.1-beta-Ludashi-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/559346173", "id": 559346173, "node_id": "RA_kwDORgdwrM4hVvH9", "name": "WinNative-v0.6.1-beta-Pubg-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600696693, "digest": "sha256:35f2f9053d7d1c82c6107492b3fee71525d55359ea7f20d7d672afaad1061a3f", "download_count": 65, "created_at": "2026-09-12T13:53:21Z", "updated_at": "2026-09-12T13:53:46Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.1-beta/WinNative-v0.6.1-beta-Pubg-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/559346171", "id": 559346171, "node_id": "RA_kwDORgdwrM4hVvH7", "name": "WinNative-v0.6.1-beta-Standard-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600681481, "digest": "sha256:41deb130722aeeeeb84ef0553a1da4f2674e91608047103292ce2e80023c3fd7", "download_count": 477, "created_at": "2026-09-12T13:53:21Z", "updated_at": "2026-09-12T13:53:45Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.1-beta/WinNative-v0.6.1-beta-Standard-signed.apk"}], "tarball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/tarball/v0.6.1-beta", "zipball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/zipball/v0.6.1-beta", "body": "## What's Changed\r\n* fix: Steam Launcher cloud saves, offline mode and disable-cloud toggle by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/732\r\n* Add ASTC texture transcoding setting for the Gamenative wrapper by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/734\r\n* Show proper FPS with RedMagic Frame Gen in HUD by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/736\r\n* FG screen border fix by @qwertypower in https://github.com/WinNative-Emu/WinNative/pull/737\r\n* Fix frame pacing by @qwertypower in https://github.com/WinNative-Emu/WinNative/pull/740\r\n* Fix/steam offline fully offline and fixed by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/738\r\n* Replace the frame-generation switches with one Off/LSFG/DIS selector,\u2026 by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/742\r\n* dxwrapper (dd7to9) updated to v1.8.8600.25 by @ajay9634 in https://github.com/WinNative-Emu/WinNative/pull/741\r\n* Refactor: Refactored background persistence behavior (SessionKeepAliveService), NotificationHelper, and LogManager. by @Juan-Antonio-Doe in https://github.com/WinNative-Emu/WinNative/pull/609\r\n* Fix/performance ddda and other 32 Bit Games by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/743\r\n* Change store that owns game by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/735\r\n\r\n\r\n**Full Changelog**: https://github.com/WinNative-Emu/WinNative/compare/v0.6.0-beta...v0.6.1-beta\r\n", "reactions": {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/387586788/reactions", "total_count": 13, "+1": 2, "-1": 0, "laugh": 2, "hooray": 3, "confused": 0, "heart": 2, "rocket": 2, "eyes": 2}, "mentions_count": 4}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/386712349", "assets_url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/386712349/assets", "upload_url": "https://uploads.github.com/repos/WinNative-Emu/WinNative/releases/386712349/assets{?name,label}", "html_url": "https://github.com/WinNative-Emu/WinNative/releases/tag/v0.6.0-beta", "id": 386712349, "author": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "node_id": "RE_kwDORgdwrM4XDMMd", "tag_name": "v0.6.0-beta", "target_commitish": "54f6b57e7de6b8e1928c42ae375da3da98607717", "name": "v0.6.0-beta", "draft": false, "immutable": false, "prerelease": false, "created_at": "2026-09-11T00:29:29Z", "updated_at": "2026-09-11T01:07:54Z", "published_at": "2026-09-11T01:07:54Z", "assets": [{"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/556148276", "id": 556148276, "node_id": "RA_kwDORgdwrM4hJiY0", "name": "WinNative-v0.6.0-beta-Antutu-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600272549, "digest": "sha256:ae4fea927f71589fc97ad40b543aa65fe5ee6e551f0869848f4ba95b5d5e68e7", "download_count": 849, "created_at": "2026-09-11T00:40:36Z", "updated_at": "2026-09-11T00:41:02Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.0-beta/WinNative-v0.6.0-beta-Antutu-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/556148274", "id": 556148274, "node_id": "RA_kwDORgdwrM4hJiYy", "name": "WinNative-v0.6.0-beta-Ludashi-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600275645, "digest": "sha256:9d3d1d8a8a77350ded88c4f199d7d1815e9864df2c7144633673fc7567fda078", "download_count": 781, "created_at": "2026-09-11T00:40:36Z", "updated_at": "2026-09-11T00:41:05Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.0-beta/WinNative-v0.6.0-beta-Ludashi-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/556148277", "id": 556148277, "node_id": "RA_kwDORgdwrM4hJiY1", "name": "WinNative-v0.6.0-beta-Pubg-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600281677, "digest": "sha256:55f0657ccfcd827875b96855932027acf126bb301bfd1104f53d007c024652da", "download_count": 163, "created_at": "2026-09-11T00:40:36Z", "updated_at": "2026-09-11T00:41:02Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.0-beta/WinNative-v0.6.0-beta-Pubg-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/556148275", "id": 556148275, "node_id": "RA_kwDORgdwrM4hJiYz", "name": "WinNative-v0.6.0-beta-Standard-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 600266185, "digest": "sha256:865b25b6a4d7b02bfc0c45cf9a09a149304454a210f13733583d61b0b4408446", "download_count": 1113, "created_at": "2026-09-11T00:40:36Z", "updated_at": "2026-09-11T00:41:04Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.6.0-beta/WinNative-v0.6.0-beta-Standard-signed.apk"}], "tarball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/tarball/v0.6.0-beta", "zipball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/zipball/v0.6.0-beta", "body": "## What's Changed\r\n* Credit Camille LaVey for the LSFG frame generation port by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/709\r\n* Feature Update Checker Implementation by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/707\r\n* Feature steam branch downloads by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/714\r\n* Stop repeating per-launch work that cannot change its own result by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/710\r\n* docs: update Dolphin core packaging by @Jovinull in https://github.com/WinNative-Emu/WinNative/pull/717\r\n* docs: fix canonical repository clone URL by @Jovinull in https://github.com/WinNative-Emu/WinNative/pull/716\r\n* Feature/itch.io by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/718\r\n* Fix Steam Launcher for Multiplayer/Networking/Cloud Saves by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/720\r\n* Add the DirectAudio driver, and with it microphone support by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/719\r\n* Fix/steam cloud sync by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/722\r\n* Support page added by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/725\r\n* Frame Gen for Retro by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/726\r\n* Add a release picker to the retro console bundle updater by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/724\r\n* fix misc bugs, performance, stability by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/723\r\n* misc fixes & device resolutions by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/728\r\n* fix: Steam Launcher toggle as per-shortcut setting by @Juan-Antonio-Doe in https://github.com/WinNative-Emu/WinNative/pull/729\r\n* Add DIS frame generation release by @qwertypower in https://github.com/WinNative-Emu/WinNative/pull/731\r\n* Fix RedMagic LSFG Pacing by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/730\r\n\r\n## New Contributors\r\n* @Jovinull made their first contribution in https://github.com/WinNative-Emu/WinNative/pull/717\r\n* @Juan-Antonio-Doe made their first contribution in https://github.com/WinNative-Emu/WinNative/pull/729\r\n* @qwertypower made their first contribution in https://github.com/WinNative-Emu/WinNative/pull/731\r\n\r\n**Full Changelog**: https://github.com/WinNative-Emu/WinNative/compare/v0.5.0-beta...v0.6.0-beta\r\n", "reactions": {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/386712349/reactions", "total_count": 14, "+1": 5, "-1": 0, "laugh": 1, "hooray": 4, "confused": 0, "heart": 2, "rocket": 1, "eyes": 1}, "mentions_count": 4}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/378068579", "assets_url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/378068579/assets", "upload_url": "https://uploads.github.com/repos/WinNative-Emu/WinNative/releases/378068579/assets{?name,label}", "html_url": "https://github.com/WinNative-Emu/WinNative/releases/tag/v0.5.0-beta", "id": 378068579, "author": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "node_id": "RE_kwDORgdwrM4WiN5j", "tag_name": "v0.5.0-beta", "target_commitish": "2bcd0a31c6bfd47b81a9cbdb9751a28046ac861d", "name": "v0.5.0-beta", "draft": false, "immutable": false, "prerelease": false, "created_at": "2026-08-27T19:09:45Z", "updated_at": "2026-08-27T19:59:43Z", "published_at": "2026-08-27T19:59:43Z", "assets": [{"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/532764306", "id": 532764306, "node_id": "RA_kwDORgdwrM4fwVaS", "name": "WinNative-v0.5.0-beta-Antutu-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 598917605, "digest": "sha256:fc31157368d76e408dbdabe4562c3136e32f63f099748f762788204262aa15bb", "download_count": 4740, "created_at": "2026-08-27T19:21:41Z", "updated_at": "2026-08-27T19:22:25Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.5.0-beta/WinNative-v0.5.0-beta-Antutu-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/532764307", "id": 532764307, "node_id": "RA_kwDORgdwrM4fwVaT", "name": "WinNative-v0.5.0-beta-Ludashi-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 598920809, "digest": "sha256:e65a7397a2e17f6b441ff1a52a0a57dc235120b35923f48d5fb61ee148aa6856", "download_count": 2815, "created_at": "2026-08-27T19:21:41Z", "updated_at": "2026-08-27T19:22:24Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.5.0-beta/WinNative-v0.5.0-beta-Ludashi-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/532764302", "id": 532764302, "node_id": "RA_kwDORgdwrM4fwVaO", "name": "WinNative-v0.5.0-beta-Pubg-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 598926777, "digest": "sha256:b40a24466abfb9319459f66a41ddcd1982b86da4fde535f219c081d835a37251", "download_count": 700, "created_at": "2026-08-27T19:21:40Z", "updated_at": "2026-08-27T19:22:26Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.5.0-beta/WinNative-v0.5.0-beta-Pubg-signed.apk"}, {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/assets/532764304", "id": 532764304, "node_id": "RA_kwDORgdwrM4fwVaQ", "name": "WinNative-v0.5.0-beta-Standard-signed.apk", "label": "", "uploader": {"login": "github-actions[bot]", "id": 41898282, "node_id": "MDM6Qm90NDE4OTgyODI=", "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", "url": "https://api.github.com/users/github-actions%5Bbot%5D", "html_url": "https://github.com/apps/github-actions", "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", "type": "Bot", "user_view_type": "public", "site_admin": false}, "content_type": "application/vnd.android.package-archive", "state": "uploaded", "size": 598911673, "digest": "sha256:4366a5f7d16a16d282bee63f00e3012d3dbbb8b1bf1f56f3416fff85e3186998", "download_count": 5870, "created_at": "2026-08-27T19:21:41Z", "updated_at": "2026-08-27T19:22:26Z", "browser_download_url": "https://github.com/WinNative-Emu/WinNative/releases/download/v0.5.0-beta/WinNative-v0.5.0-beta-Standard-signed.apk"}], "tarball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/tarball/v0.5.0-beta", "zipball_url": "https://api.github.com/repos/WinNative-Emu/WinNative/zipball/v0.5.0-beta", "body": "## Features\r\n* Frame Generation implemented by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/697 A special thank you to the Eden Development Team, whose original work on the Eden Emulator formed the foundation of this feature!\r\n\r\n## Bug Fixes\r\n* Fix non-ASCII game paths and ANSI code pages for shortcut launches by @kingokksa in https://github.com/WinNative-Emu/WinNative/pull/694\r\n* Center the Steam sign-in error inside the password field by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/702\r\n* Fix the Java formatting violations spotless reports by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/703\r\n* PS2 Portrait Issues by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/704\r\n* Show the frame generation input and output rate on the Mango HUD by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/705\r\n* Locate the Lossless DLL with WinNative's file picker by @maxjivi05 in https://github.com/WinNative-Emu/WinNative/pull/706\r\n\r\n## New Contributors\r\n* @kingokksa made their first contribution in https://github.com/WinNative-Emu/WinNative/pull/694\r\n\r\n**Full Changelog**: https://github.com/WinNative-Emu/WinNative/compare/v0.4.1...v0.5.0-beta", "reactions": {"url": "https://api.github.com/repos/WinNative-Emu/WinNative/releases/378068579/reactions", "total_count": 41, "+1": 15, "-1": 0, "laugh": 7, "hooray": 8, "confused": 0, "heart": 4, "rocket": 4, "eyes": 3}, "mentions_count": 2}] \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/app/update/UpdateSourceTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/app/update/UpdateSourceTest.kt new file mode 100644 index 000000000..3575804fe --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/app/update/UpdateSourceTest.kt @@ -0,0 +1,125 @@ +package com.winlator.cmod.app.update + +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.BuildConfig +import org.junit.Assert.* +import org.junit.Test + +/** + * Against what GitHub actually answers: the payloads under androidTest/assets/update are the + * bodies of the real CI and official releases, saved as the API returned them. + */ +class UpdateSourceTest { + private fun payload(name: String): String = + InstrumentationRegistry + .getInstrumentation() + .context.assets + .open("update/$name") + .bufferedReader() + .use { it.readText() } + + private val ci769 get() = payload("ci-pr-769.json") + private val ci770 get() = payload("ci-pr-770.json") + + @Test + fun aPullRequestOffersOnlyItsOwnBuild() { + assertNotNull(UpdateRelease.pickPullRequest(ci769, 769)) + assertNotNull(UpdateRelease.pickPullRequest(ci770, 770)) + // The release for one pull request can never be served to another, however it is reached. + assertNull(UpdateRelease.pickPullRequest(ci770, 769)) + assertNull(UpdateRelease.pickPullRequest(ci769, 770)) + } + + @Test + fun theTagAsksForThePullRequestThatWasInstalled() { + assertEquals("pr-769", UpdateRelease.ciTag(769)) + assertTrue(UpdateRelease.ciReleaseUrl(770).endsWith("/WinNative-CI/releases/tags/pr-770")) + } + + @Test + fun theBuildIsTheCommitTheNotesName() { + val release = UpdateRelease.pickPullRequest(ci769, 769)!! + assertEquals("594231dd8e5eebfd83a20634852584c696eba924", release.buildRef) + assertEquals(UpdateChannel.PULL_REQUEST, release.channel) + // The tag never moves, so what is ignored has to be this build of it and not the tag. + assertEquals("pr-769@594231dd8e5eebfd83a20634852584c696eba924", release.key) + } + + /** The app carries a shortened commit; the notes carry the whole one. */ + @Test + fun aShortenedCommitStillNamesTheSameBuild() { + assertTrue(UpdateRelease.isSameBuild("594231dd", "594231dd8e5eebfd83a20634852584c696eba924")) + assertTrue(UpdateRelease.isSameBuild("594231DD", "594231dd8e5eebfd83a20634852584c696eba924")) + assertFalse(UpdateRelease.isSameBuild("091b4e71", "594231dd8e5eebfd83a20634852584c696eba924")) + assertFalse(UpdateRelease.isSameBuild("", "594231dd8e5eebfd83a20634852584c696eba924")) + assertFalse(UpdateRelease.isSameBuild("594231dd", "")) + // Too short to mean anything on its own, so it only answers to itself. + assertFalse(UpdateRelease.isSameBuild("5942", "594231dd8e5eebfd83a20634852584c696eba924")) + } + + @Test + fun theApkChosenIsThisBuildsOwnFlavour() { + val release = UpdateRelease.pickPullRequest(ci769, 769)!! + val flavour = BuildConfig.FLAVOR.lowercase() + assertTrue(release.apkName, release.apkName.lowercase().contains(flavour)) + assertTrue(release.apkName, release.apkName.endsWith(".apk")) + assertTrue(release.apkUrl, release.apkUrl.startsWith("https://")) + assertTrue(release.apkSize > 0) + // Every flavour that CI publishes is reachable, each getting its own file. + val names = listOf( + "WinNative-Debug-Standard-PR.769-59423.apk", + "WinNative-Debug-Ludashi-PR.769-59423.apk", + "WinNative-Debug-Pubg-PR.769-59423.apk", + "WinNative-Debug-Antutu-PR.769-59423.apk", + ) + for (wanted in UpdateRelease.KNOWN_VARIANTS) { + val picked = UpdateRelease.matchApkAsset(names, wanted) + assertNotNull(wanted, picked) + assertTrue(wanted, picked!!.lowercase().contains(wanted)) + } + } + + /** The official path has to keep working exactly as it did. */ + @Test + fun officialTakesTheNewestReleaseAndKeepsTheTagAsItsKey() { + val release = UpdateRelease.pick(payload("official.json"))!! + assertEquals("v0.6.2-beta", release.tag) + assertEquals(UpdateChannel.OFFICIAL, release.channel) + assertEquals("", release.buildRef) + assertEquals("v0.6.2-beta", release.key) + assertTrue(release.apkName, release.apkName.lowercase().contains(BuildConfig.FLAVOR.lowercase())) + assertTrue(release.apkName, release.apkName.contains("signed")) + } + + @Test + fun aReleaseWithoutACommitIsNotOffered() { + val body = ci769.replace("Source commit:", "Built from:") + assertNull(UpdateRelease.pickPullRequest(body, 769)) + assertEquals("", UpdateRelease.sourceCommit("no commit here")) + assertEquals("", UpdateRelease.sourceCommit(null)) + assertEquals("abc1234", UpdateRelease.sourceCommit("Source commit: `abc1234`\r\n")) + assertEquals("abc1234", UpdateRelease.sourceCommit("Source commit: abc1234")) + } + + /** + * The stamp the build carries is what decides, so this holds for a CI build and for one made + * anywhere else. A build made outside CI follows the official releases, as it always did. + */ + @Test + fun theSourceFollowsTheStampTheBuildCarries() { + val pullRequest = UpdateRelease.installedPullRequest() + assertEquals(BuildConfig.PR_NUMBER.trim().toIntOrNull() ?: 0, pullRequest) + if (pullRequest > 0) { + assertTrue(UpdateRelease.isPullRequestBuild()) + assertEquals(UpdateChannel.PULL_REQUEST, UpdateService.channel()) + assertEquals("PR #$pullRequest", UpdateService.updateSourceName()) + assertTrue(UpdateService.installedVersionName().startsWith("PR #$pullRequest")) + assertTrue(UpdateRelease.installedBuildRef().isNotEmpty()) + } else { + assertFalse(UpdateRelease.isPullRequestBuild()) + assertEquals(UpdateChannel.OFFICIAL, UpdateService.channel()) + assertEquals("", UpdateService.updateSourceName()) + assertEquals(BuildConfig.VERSION_NAME, UpdateService.installedVersionName()) + } + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/feature/settings/ComponentsScreenTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/feature/settings/ComponentsScreenTest.kt new file mode 100644 index 000000000..0ca2a81f0 --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/feature/settings/ComponentsScreenTest.kt @@ -0,0 +1,127 @@ +package com.winlator.cmod.feature.settings + +import android.content.pm.ActivityInfo +import androidx.activity.ComponentActivity +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.R +import com.winlator.cmod.runtime.content.ContentProfile +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File + +@RunWith(AndroidJUnit4::class) +class ComponentsScreenTest { + @get:Rule val compose = createAndroidComposeRule() + private val context = InstrumentationRegistry.getInstrumentation().targetContext + + @Test + fun platformTabsExposeLinuxProtonsFromComponents() { + runComponentsTest("components-linux-tab.png") + } + + @Test + fun platformTabsExposeLinuxProtonsFromComponentsInLandscape() { + compose.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + compose.waitUntil(5_000) { + compose.activity.resources.configuration.screenWidthDp > + compose.activity.resources.configuration.screenHeightDp + } + runComponentsTest("components-linux-tab-landscape.png") + } + + private fun runComponentsTest(screenshotName: String) { + val state = + mutableStateOf( + ComponentsState( + installed = + listOf( + ComponentItem( + key = "proton", + type = ContentProfile.ContentType.CONTENT_TYPE_PROTON, + verName = "Proton-11.0", + isInstalled = true, + hasRemote = false, + ), + ), + linuxAvailable = + listOf( + LinuxComponentItem( + key = "GE-Proton11-7-aarch64", + type = ContentProfile.ContentType.CONTENT_TYPE_PROTON, + verName = "GE-Proton 11-7 (ARM64)", + isInstalled = false, + hasRemote = true, + sizeBytes = 645_786_140, + ), + LinuxComponentItem( + key = "proton-cachyos-11.0-20260703-slr-arm64", + type = ContentProfile.ContentType.CONTENT_TYPE_PROTON, + verName = "CachyOS Proton 11.0-20260703", + isInstalled = false, + hasRemote = true, + sizeBytes = 339_912_088, + ), + ), + ), + ) + val bridge = SettingsNavBridge().apply { + zone = SettingsFocusZone.CONTENT + contentControllerActive = true + } + var downloadedLinuxProton: String? = null + compose.setContent { + ComponentsScreen( + bridge = bridge, + state = state.value, + onPlatformSelected = { platform -> state.value = state.value.copy(platform = platform) }, + onTypeSelected = {}, + onInstallFromFile = {}, + onDownloadItem = {}, + onRemoveItem = {}, + onDownloadLinuxItem = { downloadedLinuxProton = it.key }, + onRemoveLinuxItem = {}, + onCancelLinuxItem = {}, + onDismissConflict = {}, + onToggleAutoCreateContainer = {}, + onRefresh = {}, + ) + } + + compose.onNodeWithText("Android ${context.getString(R.string.settings_content_components)}").assertIsDisplayed() + compose.onNodeWithText("Linux ${context.getString(R.string.settings_content_components)}").assertIsDisplayed() + compose.onNodeWithText("Proton-11.0").assertIsDisplayed() + compose.onNodeWithText("Android ${context.getString(R.string.settings_content_components)}").performClick() + compose.runOnIdle { bridge.contentNavRight() } + compose.waitForIdle() + compose.runOnIdle { bridge.contentActivate() } + compose.waitForIdle() + + compose.runOnIdle { assertEquals(ComponentsPlatform.LINUX, state.value.platform) } + compose.onNodeWithText("Proton").assertIsDisplayed() + compose.onNodeWithText("GE-Proton 11-7 (ARM64)").assertIsDisplayed() + compose.onNodeWithText("CachyOS Proton 11.0-20260703").assertIsDisplayed() + compose.onAllNodesWithText(context.getString(R.string.common_ui_open)).assertCountEquals(0) + screenshot(screenshotName) + compose.onAllNodesWithText(context.getString(R.string.common_ui_download))[0].performClick() + compose.runOnIdle { assertTrue(downloadedLinuxProton in state.value.linuxAvailable.map { it.key }) } + } + + private fun screenshot(name: String) { + val instrumentation = InstrumentationRegistry.getInstrumentation() + android.os.SystemClock.sleep(350) + val bitmap = instrumentation.uiAutomation.takeScreenshot() + File(context.getExternalFilesDir(null), name).outputStream().use { bitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, it) } + bitmap.recycle() + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/feature/settings/DriversScreenTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/feature/settings/DriversScreenTest.kt new file mode 100644 index 000000000..b171cc00e --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/feature/settings/DriversScreenTest.kt @@ -0,0 +1,66 @@ +package com.winlator.cmod.feature.settings + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.R +import com.winlator.cmod.runtime.content.DriverPackages +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import java.io.File + +class DriversScreenTest { + @get:Rule val compose = createComposeRule() + private val context = InstrumentationRegistry.getInstrumentation().targetContext + + @Test + fun platformTabsAndInstallDestinationKeepPaneNavigation() { + val state = mutableStateOf(DriversState(installedDrivers = listOf(InstalledDriverItem(id = "android", name = "WN Android", version = "1.16")))) + val bridge = SettingsNavBridge().apply { + zone = SettingsFocusZone.CONTENT + contentControllerActive = true + } + var destination: DriverPackages.Platform? = null + compose.setContent { + DriversScreen( + state = state.value, + onInstallFromFile = { destination = it }, + onPlatformSelected = { + state.value = state.value.copy(platform = it, installedDrivers = listOf(InstalledDriverItem(id = DriverPackages.BUNDLED, name = "Bundled Mesa", version = "26.2.2", selected = true, removable = false), InstalledDriverItem(id = "linux-b", name = "WN Linux Turnip 0.1.0-b", version = "0.1.0-b"), InstalledDriverItem(id = "linux-p", name = "WN Linux Turnip 0.1.0-p", version = "0.1.0-p"))) + }, + onSelectDriver = {}, onSourceTapped = {}, onReleaseTapped = {}, onDownloadAsset = {}, onRemoveDriver = {}, + onRepoAdded = { _, _ -> }, onRepoUpdated = { _, _, _ -> }, onRepoDeleted = {}, onRestoreDefaultRepos = {}, bridge = bridge, + ) + } + compose.onNodeWithText("Android").assertIsDisplayed() + compose.onNodeWithText("Linux").assertIsDisplayed() + compose.onNodeWithText("Android").performClick() + compose.runOnIdle { bridge.contentNavRight() } + compose.waitForIdle() + compose.runOnIdle { bridge.contentActivate() } + compose.waitForIdle() + assertEquals(DriverPackages.Platform.LINUX, state.value.platform) + compose.onNodeWithText("Bundled Mesa").assertIsDisplayed() + compose.onNodeWithText("WN Linux Turnip 0.1.0-b").assertIsDisplayed() + compose.onNodeWithText("WN Linux Turnip 0.1.0-p").assertIsDisplayed() + screenshot("linux-drivers.png") + compose.onNodeWithText(context.getString(R.string.settings_drivers_install)).performClick() + compose.onNodeWithText(context.getString(R.string.settings_drivers_platform_hint)).assertIsDisplayed() + screenshot("driver-destination.png") + compose.onAllNodesWithText("Android")[1].performClick() + compose.runOnIdle { assertEquals(DriverPackages.Platform.ANDROID, destination) } + } + + private fun screenshot(name: String) { + val instrumentation = InstrumentationRegistry.getInstrumentation() + android.os.SystemClock.sleep(350) + val bitmap = instrumentation.uiAutomation.takeScreenshot() + File(context.getExternalFilesDir(null), name).outputStream().use { bitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, it) } + bitmap.recycle() + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/runtime/content/DriverPackagesTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/content/DriverPackagesTest.kt new file mode 100644 index 000000000..b968b88a0 --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/content/DriverPackagesTest.kt @@ -0,0 +1,95 @@ +package com.winlator.cmod.runtime.content + +import android.net.Uri +import androidx.preference.PreferenceManager +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.runtime.linux.LinuxRuntime +import org.json.JSONObject +import org.junit.After +import org.junit.Assert.* +import org.junit.Test +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +class DriverPackagesTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val fixtures = File(context.filesDir, "driver-tests") + private val installed = mutableListOf() + + private fun install(file: File, required: DriverPackages.Platform? = null): DriverPackages.Installed = + DriverPackages.install(context, Uri.fromFile(file), file.name, required).also { installed += it } + + @After + fun clean() { + installed.forEach { + if (it.platform == DriverPackages.Platform.LINUX) DriverPackages.removeLinux(context, it.id) + else File(context.filesDir, "contents/adrenotools/${it.id}").deleteRecursively() + } + PreferenceManager.getDefaultSharedPreferences(context).edit().remove("linux_driver_selected").commit() + } + + @Test + fun realPackagesInstallIntoSeparateStoresAndLinuxSelectionControlsRuntime() { + val android = install(File(fixtures, "android.zip")) + val balanced = install(File(fixtures, "linux-b.zip")) + val performance = install(File(fixtures, "linux-p.zip")) + assertEquals(DriverPackages.Platform.ANDROID, android.platform) + assertEquals(DriverPackages.Platform.LINUX, balanced.platform) + assertEquals(DriverPackages.Platform.LINUX, performance.platform) + assertTrue(AdrenotoolsManager(context).enumarateInstalledDrivers().contains(android.id)) + assertFalse(AdrenotoolsManager(context).enumarateInstalledDrivers().contains(balanced.id)) + assertEquals(performance.id, DriverPackages.selectedLinux(context)) + DriverPackages.selectLinux(context, balanced.id) + assertEquals(balanced.id, LinuxRuntime.driverName(context)) + val icd = LinuxRuntime.vulkanIcd(context)!! + val library = JSONObject(icd.readText()).getJSONObject("ICD").getString("library_path") + assertTrue(File(library).isFile) + assertTrue(library.startsWith(DriverPackages.linuxDirectory(context).path)) + assertTrue(runCatching { install(File(fixtures, "linux-b.zip")) }.isFailure) + DriverPackages.removeLinux(context, balanced.id) + assertEquals(DriverPackages.BUNDLED, DriverPackages.selectedLinux(context)) + assertEquals(LinuxRuntime.TURNIP_VERSION, LinuxRuntime.driverName(context)) + assertTrue(File(context.filesDir, "contents/adrenotools/${android.id}/meta.json").isFile) + } + + @Test + fun androidOnlyEntryPointRejectsLinuxAndWrongAbiMetadata() { + assertEquals("", AdrenotoolsManager(context).installDriver(Uri.fromFile(File(fixtures, "linux-b.zip")))) + assertTrue(runCatching { install(File(fixtures, "android.zip"), DriverPackages.Platform.LINUX) }.isFailure) + val invalid = rewrite("linux-b.zip") { name, bytes -> + if (name == "meta.json") JSONObject(String(bytes)).put("platform", "android").toString().toByteArray() else bytes + } + assertTrue(runCatching { install(invalid) }.isFailure) + } + + @Test + fun unsafePathsAndInvalidElfNeverInstallOrEscapeStaging() { + val invalid = rewrite("linux-b.zip") { name, bytes -> if (name.endsWith(".so")) byteArrayOf(1, 2, 3) else bytes } + assertTrue(runCatching { install(invalid) }.isFailure) + val archive = File(context.cacheDir, "traversal.zip") + ZipOutputStream(archive.outputStream()).use { + it.putNextEntry(ZipEntry("../escaped-driver")) + it.write(byteArrayOf(1)) + it.closeEntry() + } + assertTrue(runCatching { install(archive) }.isFailure) + assertFalse(File(context.cacheDir, "escaped-driver").exists()) + assertTrue(context.cacheDir.listFiles().orEmpty().none { it.isDirectory && it.name.startsWith("driver-") }) + } + + private fun rewrite(source: String, change: (String, ByteArray) -> ByteArray): File { + val output = File(context.cacheDir, "invalid-driver.zip") + ZipFile(File(fixtures, source)).use { input -> + ZipOutputStream(output.outputStream()).use { zip -> + input.entries().asSequence().forEach { entry -> + zip.putNextEntry(ZipEntry(entry.name)) + zip.write(change(entry.name, input.getInputStream(entry).use { it.readBytes() })) + zip.closeEntry() + } + } + } + return output + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxCompositorDriverTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxCompositorDriverTest.kt new file mode 100644 index 000000000..8fdb8668b --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxCompositorDriverTest.kt @@ -0,0 +1,35 @@ +package com.winlator.cmod.runtime.linux + +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.runtime.content.AdrenotoolsManager +import java.io.File +import org.json.JSONObject +import org.junit.Assert.* +import org.junit.Test + +class LinuxCompositorDriverTest { + @Test + fun requiresInstalledTurnipInsteadOfAnyAndroidDriver() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val id = "compositor-driver-test" + val directory = File(context.filesDir, "contents/adrenotools/$id") + assertFalse(directory.exists()) + directory.mkdirs() + val drivers = AdrenotoolsManager(context) + val library = File(directory, "vulkan.adreno.so") + val metadata = File(directory, "meta.json") + try { + library.writeBytes(byteArrayOf(1)) + metadata.writeText(JSONObject().put("name", "Qualcomm driver").put("libraryName", library.name).toString()) + assertFalse(drivers.isTurnipDriver(id)) + metadata.writeText(JSONObject().put("name", "WN Turnip 1.16-p").put("libraryName", library.name).toString()) + assertTrue(drivers.isTurnipDriver(id)) + library.delete() + assertFalse(drivers.isTurnipDriver(id)) + assertFalse(drivers.isTurnipDriver("26.2.2")) + assertFalse(drivers.isTurnipDriver("System")) + } finally { + directory.deleteRecursively() + } + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxLauncherTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxLauncherTest.kt new file mode 100644 index 000000000..9e15bd24f --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxLauncherTest.kt @@ -0,0 +1,83 @@ +package com.winlator.cmod.runtime.linux + +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.runtime.display.environment.components.LinuxProgramLauncherComponent +import com.winlator.cmod.runtime.wine.EnvVars +import java.io.File +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import org.junit.Assert.* +import org.junit.Test + +class LinuxLauncherTest { + @Test + fun preservesArgumentsAndCapturesEarlyStderr() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val log = File(context.cacheDir, "linux-launcher-test.log").apply { delete() } + val done = CountDownLatch(1) + val exit = AtomicInteger(-1) + val launcher = LinuxProgramLauncherComponent( + listOf("/system/bin/sh", "-c", "printf '<%s>\\n' \"\$@\"; echo loader-error >&2; exit 23", "test", "a b", "", "quote\"value", "apostrophe'value"), + EnvVars(), context.cacheDir, log + ) { status -> exit.set(status); done.countDown() } + try { + launcher.start() + assertTrue(done.await(10, TimeUnit.SECONDS)) + assertEquals(23, exit.get()) + val text = log.readText() + assertTrue(text.contains("\n<>\n\n")) + assertTrue(text.contains("loader-error")) + assertTrue(text.contains("status 23")) + } finally { + launcher.stop() + log.delete() + } + } + + /** The guest command carries the container's environment, where a user may have typed anything. */ + @Test + fun theSessionLogSaysWhatWasAskedAndHowItEnded() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val log = File(context.cacheDir, "linux-launcher-record.log").apply { delete() } + val done = CountDownLatch(1) + val launcher = LinuxProgramLauncherComponent( + listOf("/system/bin/sh", "-c", "kill -9 \$\$", "--kill-on-exit", "-r", "/root", + "/usr/bin/env", "STEAM_TOKEN=must-not-be-logged"), + EnvVars(), context.cacheDir, log + ) { done.countDown() } + try { + launcher.start() + assertTrue(done.await(10, TimeUnit.SECONDS)) + val text = log.readText() + assertTrue(text, text.contains("WinNative " + com.winlator.cmod.BuildConfig.VERSION_NAME)) + assertTrue(text, text.contains("--kill-on-exit -r /root")) + assertFalse(text, text.contains("must-not-be-logged")) + assertTrue(text, text.contains("status 137 (SIGKILL")) + } finally { + launcher.stop() + log.delete() + } + } + + @Test + fun reportsFailureWhenProotCannotStart() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val log = File(context.cacheDir, "linux-launcher-missing.log").apply { delete() } + val done = CountDownLatch(1) + val exit = AtomicInteger(-1) + val launcher = LinuxProgramLauncherComponent( + listOf(File(context.cacheDir, "missing-proot-executable").path), + EnvVars(), context.cacheDir, log + ) { status -> exit.set(status); done.countDown() } + try { + launcher.start() + assertTrue(done.await(10, TimeUnit.SECONDS)) + assertEquals(127, exit.get()) + assertTrue(log.readText().contains("could not start")) + } finally { + launcher.stop() + log.delete() + } + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxProtonsTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxProtonsTest.kt new file mode 100644 index 000000000..10f2f0603 --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxProtonsTest.kt @@ -0,0 +1,61 @@ +package com.winlator.cmod.runtime.linux + +import androidx.test.platform.app.InstrumentationRegistry +import java.io.File +import kotlinx.coroutines.runBlocking +import org.json.JSONObject +import org.junit.Assert.* +import org.junit.Test +import org.apache.commons.compress.archivers.tar.TarArchiveEntry +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream +import java.util.zip.GZIPOutputStream + +class LinuxProtonsTest { + @Test + fun rejectsTraversalAndX86BuildsWithoutLeavingPartialInstallation() = runBlocking { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val archive = File(context.cacheDir, "invalid-proton.tar.gz") + val build = LinuxProtons.Build("invalid-proton", "Invalid", "https://example.invalid/proton.tar.gz", "", 4096) + val x86 = ByteArray(20).apply { + this[0] = 127; this[1] = 69; this[2] = 76; this[3] = 70; this[4] = 2; this[18] = 62 + } + val cases = listOf(mapOf("../escaped-proton" to byteArrayOf(1)), mapOf("tree/proton" to byteArrayOf(1), "tree/files/bin-arm64/wine" to x86)) + for (entries in cases) { + TarArchiveOutputStream(GZIPOutputStream(archive.outputStream())).use { tar -> + entries.forEach { (name, bytes) -> + tar.putArchiveEntry(TarArchiveEntry(name).apply { size = bytes.size.toLong() }) + tar.write(bytes) + tar.closeArchiveEntry() + } + } + assertTrue(runCatching { LinuxProtons.installArchive(context, build, archive) }.isFailure) + assertFalse(File(LinuxProtons.directory(context), "escaped-proton").exists()) + assertFalse(File(LinuxProtons.directory(context), build.id).exists()) + assertFalse(File(LinuxProtons.directory(context), ".${build.id}.staging").exists()) + } + archive.delete() + } + + @Test + fun installsRealArmProtonArchiveAndRemovesOnlyThatBuild() = runBlocking { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val fixture = File(context.filesDir, "proton-test") + val json = JSONObject(File(fixture, "build.json").readText()) + val build = LinuxProtons.Build(json.getString("id"), json.getString("name"), json.getString("url"), json.getString("sha256"), json.getLong("size")) + val target = File(LinuxProtons.directory(context), build.id) + assertFalse(target.exists()) + try { + LinuxProtons.installArchive(context, build, File(fixture, "archive")) + assertTrue(File(target, "proton").canExecute()) + assertTrue(File(target, "files/bin-arm64/wine").canExecute()) + assertEquals(build.id, JSONObject(File(target, "winnative-proton.json").readText()).getString("id")) + assertTrue(File(target, "toolmanifest.vdf").readText().contains("/winnative-proton-wrap %verb%")) + assertTrue(File(target, "winnative-proton-wrap").readText().contains("winnative-proton-launch")) + assertFalse(LinuxProtons.directory(context).listFiles().orEmpty().any { it.name.endsWith(".staging") }) + LinuxProtons.remove(context, build)?.join() + assertFalse(target.exists()) + } finally { + target.deleteRecursively() + } + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxRuntimeTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxRuntimeTest.kt new file mode 100644 index 000000000..d9c353323 --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/linux/LinuxRuntimeTest.kt @@ -0,0 +1,97 @@ +package com.winlator.cmod.runtime.linux + +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.shared.ui.widget.EnvVarsView +import com.winlator.cmod.runtime.content.DriverPackages +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.wine.EnvVars +import java.io.File +import org.json.JSONObject +import org.junit.Assert.* +import org.junit.Test + +class LinuxRuntimeTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext + + /** + * The options are put to the bundled proot rather than to a list of the ones it is believed to + * take: an option it does not know is fatal at startup, and a list kept by hand drifts from the + * binary that is actually shipped. The guest command is replaced so nothing starts a session. + */ + @Test + fun passesOnlyOptionsThisProotAccepts() { + val guest = listOf("/usr/bin/env", "-i", "HOME=/root", "/usr/bin/id") + val command = LinuxRuntime.command(context, ImageFs.find(context), context.cacheDir, + null, null, emptyList(), guest) + assertEquals(guest, command.takeLast(guest.size)) + val asked = command.dropLast(guest.size) + "/bin/true" + val proot = ProcessBuilder(asked) + .redirectErrorStream(true) + .also { it.environment()["PROOT_LOADER"] = LinuxRuntime.prootLoader(context).path } + .also { it.environment()["PROOT_TMP_DIR"] = context.cacheDir.path } + .start() + val said = try { + proot.inputStream.bufferedReader().use { it.readText() } + } finally { + if (!proot.waitFor(10, java.util.concurrent.TimeUnit.SECONDS)) proot.destroyForcibly() + } + assertFalse(said, said.contains("unknown option")) + assertFalse(said, said.contains("missing value for option")) + assertFalse(said, said.contains("expects no value")) + } + + @Test + fun aLinuxSessionReadsTheContainerWithoutWhatOnlyWineAndAndroidRead() { + val kept = EnvVars(EnvVarsView.forGamescope(Container.DEFAULT_ENV_VARS)) + assertFalse(kept.has("WINE_FAST_YIELD")) + assertFalse(kept.has("WRAPPER_MAX_IMAGE_COUNT")) + assertEquals("lazy", kept.get("ZINK_DESCRIPTORS")) + // Measured on device: sysmem is not a frame cost here, so the driver's flags are left alone. + assertEquals("noconform,sysmem", kept.get("TU_DEBUG")) + } + + @Test + fun seccompFallbackIsHostOnlyAndOptIn() { + val guest = EnvVars() + for (disabled in listOf("", "0", "false", "off")) { + guest.put("PROOT_NO_SECCOMP", disabled) + assertFalse(LinuxRuntime.hostEnvironment(context, guest).has("PROOT_NO_SECCOMP")) + } + for (enabled in listOf("1", "true", "on")) { + guest.put("PROOT_NO_SECCOMP", enabled) + val host = LinuxRuntime.hostEnvironment(context, guest) + assertEquals("1", host.get("PROOT_NO_SECCOMP")) + assertEquals(LinuxRuntime.prootLoader(context).path, host.get("PROOT_LOADER")) + } + guest.put("PROTON_USE_XALIA", "0") + assertFalse(LinuxRuntime.hostEnvironment(context, guest).has("PROTON_USE_XALIA")) + } + + @Test + fun missingImportedLibraryFallsBackWithoutChangingSavedChoice() { + val id = "linux-runtime-test-driver" + val directory = File(DriverPackages.linuxDirectory(context), id) + assertFalse(directory.exists()) + directory.mkdirs() + val library = File(directory, "test.so") + val manifest = File(directory, LinuxRuntime.DRIVER_ICD) + try { + File(directory, "meta.json").writeText(JSONObject().put("name", id).toString()) + library.writeBytes(byteArrayOf(1)) + manifest.writeText(JSONObject().put("ICD", JSONObject().put("library_path", library.path)).toString()) + assertEquals(manifest, DriverPackages.selectedLinuxIcd(context, id)) + library.delete() + assertNull(DriverPackages.selectedLinuxIcd(context, id)) + assertTrue(manifest.isFile) + library.writeBytes(byteArrayOf(1)) + manifest.writeText(JSONObject().put("ICD", JSONObject().put("library_path", library.name)).toString()) + assertEquals(manifest, DriverPackages.selectedLinuxIcd(context, id)) + manifest.writeText("invalid json") + assertNull(DriverPackages.selectedLinuxIcd(context, id)) + assertNull(DriverPackages.selectedLinuxIcd(context, "removed-driver")) + } finally { + directory.deleteRecursively() + } + } +} diff --git a/app/src/androidTest/kotlin/com/winlator/cmod/runtime/system/LogShareTest.kt b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/system/LogShareTest.kt new file mode 100644 index 000000000..1f4f1a53e --- /dev/null +++ b/app/src/androidTest/kotlin/com/winlator/cmod/runtime/system/LogShareTest.kt @@ -0,0 +1,51 @@ +package com.winlator.cmod.runtime.system + +import androidx.test.platform.app.InstrumentationRegistry +import com.winlator.cmod.runtime.linux.LinuxRuntime +import java.io.ByteArrayOutputStream +import java.io.File +import org.junit.Assert.* +import org.junit.Test + +class LogShareTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext + + /** A token of the shape Steam's connection log prints, with nothing real in it. */ + private val token = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0Iiwic3ViIjoiMCJ9.bm90YXJlYWxzaWduYXR1cmU" + + @Test + fun theSteamClientsSessionTokenIsNotWhatGetsShared() { + val logs = File(LinuxRuntime.rootDir(context), "root/.local/share/Steam/logs") + val made = !logs.isDirectory + assertTrue(logs.isDirectory || logs.mkdirs()) + val log = File(logs, "connection_log.txt") + try { + log.writeText("Logon successful\nUsing JWT: $token\nConnected\n") + val shared = ByteArrayOutputStream() + LogManager.copyShareable(context, log, shared) + val text = shared.toString("UTF-8") + assertFalse(text.contains(token)) + assertFalse(text.contains("eyJ0eXAi")) + assertTrue(text.contains("Logon successful")) + assertTrue(text.contains("Connected")) + } finally { + log.delete() + if (made) logs.deleteRecursively() + } + } + + @Test + fun theAppsOwnLogsGoOutAsTheyAre() { + val log = File(LogManager.getLogsDir(context, false), "log-share-test.txt") + try { + log.parentFile?.mkdirs() + log.writeText("kept $token\n") + val shared = ByteArrayOutputStream() + LogManager.copyShareable(context, log, shared) + assertEquals("kept $token\n", shared.toString("UTF-8")) + } finally { + log.delete() + } + } +} diff --git a/app/src/main/app/shell/LibraryGameLaunchScreen.kt b/app/src/main/app/shell/LibraryGameLaunchScreen.kt index 62807d21d..68721386f 100644 --- a/app/src/main/app/shell/LibraryGameLaunchScreen.kt +++ b/app/src/main/app/shell/LibraryGameLaunchScreen.kt @@ -55,6 +55,7 @@ import androidx.compose.material.icons.outlined.SaveAlt import androidx.compose.material.icons.outlined.Construction import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.DesktopWindows +import androidx.compose.material.icons.outlined.DriveFileMove import androidx.compose.material.icons.outlined.EmojiEvents import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.Refresh @@ -113,6 +114,7 @@ import coil.compose.AsyncImage import coil.request.CachePolicy import coil.request.ImageRequest import com.winlator.cmod.R +import com.winlator.cmod.feature.library.LibraryStorageMove import com.winlator.cmod.shared.ui.layout.isPortraitLayout import com.winlator.cmod.shared.ui.layout.screenWidthDp import androidx.compose.runtime.CompositionLocalProvider @@ -128,7 +130,7 @@ import java.util.Locale private val LaunchBlack = Color.Black private val LaunchCard = Color(0xFF12121B) -private val LaunchAccent = Color(0xFF1A9FFF) +internal val LaunchAccent = Color(0xFF1A9FFF) private val LaunchAccentGlow = Color(0xFF58A6FF) private val LaunchTextPrimary = Color(0xFFF0F4FF) private val LaunchTextSecondary = Color(0xFF93A6BC) @@ -186,6 +188,12 @@ internal fun LibraryGameLaunchScreen( onVerifyFiles: () -> Unit = {}, onCheckForUpdate: () -> Unit = {}, onWorkshop: () -> Unit = {}, + /** + * Where this game would go if moved, or null when there is nowhere to move it. Drives the one + * move entry, which only ever offers the other location. + */ + moveTarget: LibraryStorageMove.Target? = null, + onMoveGame: () -> Unit = {}, branches: List = emptyList(), selectedBranchId: String = "", isBranchSelectionEnabled: Boolean = true, @@ -363,6 +371,8 @@ internal fun LibraryGameLaunchScreen( showCheats = onCheats != null, cheatsEnabled = cheatsEnabled, areSteamActionsEnabled = areSteamActionsEnabled, + moveTarget = moveTarget, + onMoveGame = onMoveGame, onVerifyFiles = onVerifyFiles, onCheckForUpdate = onCheckForUpdate, onWorkshop = onWorkshop, @@ -955,6 +965,8 @@ private fun SourceTag( showCheats: Boolean = false, cheatsEnabled: Boolean = true, areSteamActionsEnabled: Boolean = true, + moveTarget: LibraryStorageMove.Target? = null, + onMoveGame: () -> Unit = {}, storeOptions: List = emptyList(), selectedStoreId: String = "", onSelectStore: (String) -> Unit = {}, @@ -1014,6 +1026,20 @@ private fun SourceTag( icon = Icons.Outlined.Storefront, label = stringResource(R.string.library_games_store_change), ) { menuOpen = false; storeMenuOpen = true } + if (moveTarget != null) { + LaunchSourceMenuItem( + icon = Icons.Outlined.DriveFileMove, + label = + stringResource( + if (moveTarget == LibraryStorageMove.Target.APP_STORAGE) { + R.string.library_games_move_to_app_storage_title + } else { + R.string.library_games_move_to_download_folder_title + }, + ), + enabled = areSteamActionsEnabled, + ) { menuOpen = false; onMoveGame() } + } if (menuEnabled || showAchievements || showCheats) { Box( Modifier diff --git a/app/src/main/app/shell/UnifiedActivity.kt b/app/src/main/app/shell/UnifiedActivity.kt index 886136cbe..ab4636f84 100644 --- a/app/src/main/app/shell/UnifiedActivity.kt +++ b/app/src/main/app/shell/UnifiedActivity.kt @@ -1101,6 +1101,9 @@ class UnifiedActivity : supportFragmentManager.registerFragmentLifecycleCallbacks(inputControlsFragmentTracker, true) com.winlator.cmod.runtime.display.GlassesManager.init(this) + lifecycleScope.launch(Dispatchers.IO) { + com.winlator.cmod.feature.library.LibraryCache.preload(applicationContext) + } bootstrapStartupState() maybeAutoSignInGoogleOnLaunch() processPendingRetroCloudBackup() diff --git a/app/src/main/app/shell/UnifiedActivityDrawer.kt b/app/src/main/app/shell/UnifiedActivityDrawer.kt index a1fcb7d28..4d4703032 100644 --- a/app/src/main/app/shell/UnifiedActivityDrawer.kt +++ b/app/src/main/app/shell/UnifiedActivityDrawer.kt @@ -163,6 +163,9 @@ import com.winlator.cmod.feature.settings.SettingsHost import com.winlator.cmod.feature.settings.SettingsNavBridge import com.winlator.cmod.feature.settings.SettingsNavItem import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.library.LibraryItemType +import com.winlator.cmod.feature.library.LinuxApps +import com.winlator.cmod.runtime.linux.LinuxClientInstaller import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork import com.winlator.cmod.feature.artwork.SteamArtworkScraper @@ -782,13 +785,18 @@ internal fun UnifiedActivity.DrawerSwitchCard( } @Composable -internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { +internal fun UnifiedActivity.AddCustomGameDialog( + onDismiss: () -> Unit, + onInstallLinuxClient: () -> Unit, +) { val context = LocalContext.current val scope = rememberCoroutineScope() var selectedExePath by remember { mutableStateOf(null) } var gameName by remember { mutableStateOf("") } var gameFolder by remember { mutableStateOf(null) } var retroSystem by remember { mutableStateOf(null) } + var linuxApp by remember { mutableStateOf(false) } + var itemType by remember { mutableStateOf(LibraryItemType.GAME) } var isAdding by remember { mutableStateOf(false) } var nameEditing by remember { mutableStateOf(false) } val nameFocus = remember { FocusRequester() } @@ -800,34 +808,70 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { } } val registry = remember { PaneNavRegistry() } + var steamRestorable by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + steamRestorable = withContext(Dispatchers.IO) { LinuxApps.isSteamShortcutMissing(context) } + } + val restoreSteam: () -> Unit = { + isAdding = true + scope.launch(Dispatchers.IO) { + val container = LinuxApps.gamescopeContainer(ContainerManager(context)) + if (container == null) { + // The entry lives in the GameScope container, which the Linux Client install creates. + withContext(Dispatchers.Main) { + isAdding = false + onDismiss() + LinuxClientInstaller.start(context) + onInstallLinuxClient() + } + return@launch + } + val added = runCatching { LinuxApps.ensureSteamShortcut(context, container) }.isSuccess + withContext(Dispatchers.Main) { + isAdding = false + val name = LinuxApps.STEAM_SHORTCUT_NAME + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + context.getString(if (added) R.string.library_games_added else R.string.library_games_add_failed, name), + android.widget.Toast.LENGTH_SHORT, + ) + if (added) onDismiss() + } + } + } val addEnabled = selectedExePath != null && gameName.isNotBlank() && !isAdding && - (retroSystem != null || gameFolder != null) + (retroSystem != null || linuxApp || gameFolder != null) val doAdd: () -> Unit = { isAdding = true val chosenRetro = retroSystem + val chosenType = itemType + val name = gameName.trim() scope.launch(Dispatchers.IO) { val added = - if (chosenRetro != null) { - com.winlator.cmod.feature.retro.RetroShortcuts - .create(context, gameName.trim(), selectedExePath!!, chosenRetro) - } else { - addCustomGame(context, gameName.trim(), selectedExePath!!, gameFolder!!) - true + when { + chosenRetro != null -> + com.winlator.cmod.feature.retro.RetroShortcuts + .create(context, name, selectedExePath!!, chosenRetro) + linuxApp -> LinuxApps.create(context, name, selectedExePath!!, chosenType) + else -> { + addCustomGame(context, name, selectedExePath!!, gameFolder!!, type = chosenType) + true + } } withContext(Dispatchers.Main) { isAdding = false if (added) { com.winlator.cmod.shared.ui.toast.WinToast.show( context, - "$gameName added!", + context.getString(R.string.library_games_added, name), android.widget.Toast.LENGTH_SHORT, ) onDismiss() } else { com.winlator.cmod.shared.ui.toast.WinToast.show( context, - "Could not add game", + context.getString(R.string.library_games_add_failed, name), android.widget.Toast.LENGTH_SHORT, ) } @@ -839,10 +883,11 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { val detectedRetro = com.winlator.cmod.feature.retro.RetroSystems.detectForFile(path) val file = java.io.File(path) val launchable = file.extension.lowercase() in DirectoryPickerDialog.ExecutableExtensions - if (!file.isFile || (!launchable && detectedRetro == null)) { + val linux = detectedRetro == null && !launchable && LinuxApps.isLinuxExecutable(file) + if (!file.isFile || (!launchable && !linux && detectedRetro == null)) { com.winlator.cmod.shared.ui.toast.WinToast.show( context, - R.string.common_ui_select_valid_exe_file, + R.string.common_ui_select_valid_executable, android.widget.Toast.LENGTH_SHORT, ) return @@ -850,9 +895,10 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { selectedExePath = path retroSystem = detectedRetro + linuxApp = linux gameFolder = - if (detectedRetro != null) { - java.io.File(path).parent + if (detectedRetro != null || linux) { + file.parent } else { LibraryShortcutUtils.detectCustomGameFolder(path) } @@ -892,7 +938,7 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { Column(Modifier.padding(horizontal = 16.dp, vertical = 14.dp)) { // Title Text( - stringResource(R.string.library_games_add_custom_game), + stringResource(R.string.library_games_add_title), color = TextPrimary, fontWeight = FontWeight.SemiBold, fontSize = 15.sp, @@ -927,8 +973,9 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { .getExternalStoragePublicDirectory( android.os.Environment.DIRECTORY_DOWNLOADS, ).absolutePath, - title = getString(R.string.common_ui_select_exe), + title = getString(R.string.common_ui_select_executable), allowedExtensions = DirectoryPickerDialog.ExecutableExtensions + + LinuxApps.Extensions + com.winlator.cmod.feature.retro.RetroSystems.allExtensions, dimAmount = 0.5f, preserveBackdropBlur = true, @@ -942,7 +989,7 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { Icon(Icons.Outlined.FolderOpen, contentDescription = null, tint = Accent, modifier = Modifier.size(16.dp)) Spacer(Modifier.width(8.dp)) Text( - selectedExePath ?: "Select Executable or Console ROM", + selectedExePath ?: stringResource(R.string.library_games_select_executable_or_rom), color = if (selectedExePath == null) TextSecondary else TextPrimary, maxLines = if (selectedExePath == null) 1 else Int.MAX_VALUE, overflow = if (selectedExePath == null) TextOverflow.Ellipsis else TextOverflow.Visible, @@ -951,6 +998,32 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { ) } + if (selectedExePath == null && steamRestorable) { + Spacer(Modifier.height(8.dp)) + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(Color.White.copy(alpha = 0.05f)) + .paneNavItem( + cornerRadius = 12.dp, + tapToSelect = true, + onActivate = { if (!isAdding) restoreSteam() }, + ).padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon(Icons.Outlined.SportsEsports, contentDescription = null, tint = Accent, modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(8.dp)) + Text( + stringResource(R.string.library_games_add_linux_steam), + color = TextPrimary, + fontSize = 12.sp, + modifier = Modifier.weight(1f), + ) + } + } + if (selectedExePath != null) { Spacer(Modifier.height(8.dp)) @@ -990,6 +1063,36 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { Spacer(Modifier.height(8.dp)) + if (retroSystem == null) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + stringResource(R.string.library_games_type), + color = TextSecondary, + fontSize = 11.sp, + modifier = Modifier.weight(1f), + ) + LibraryItemType.entries.forEach { candidate -> + Spacer(Modifier.width(6.dp)) + DrawerFilterButton( + label = + stringResource( + when (candidate) { + LibraryItemType.GAME -> R.string.library_games_type_game + LibraryItemType.APPLICATION -> R.string.library_games_type_app + }, + ), + checked = itemType == candidate, + fontSize = 11.sp, + ) { itemType = candidate } + } + } + + Spacer(Modifier.height(8.dp)) + } + if (retroSystem != null) { val activeRetroSystem = retroSystem var consoleMenuOpen by remember { mutableStateOf(false) } @@ -1055,7 +1158,7 @@ internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { } } } - } else { + } else if (!linuxApp) { // Game folder โ€” single compact row Row( modifier = @@ -1262,6 +1365,7 @@ internal fun addCustomGame( exePath: String, gameFolderPath: String, coverArt: java.io.File? = null, + type: LibraryItemType = LibraryItemType.GAME, ) { val containerManager = ContainerManager(context) val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) @@ -1310,6 +1414,7 @@ internal fun addCustomGame( content.append("custom_name=$name\n") content.append("custom_exe=$exePath\n") content.append("custom_game_folder=$gameFolderPath\n") + content.append("${LibraryItemType.EXTRA_KEY}=${type.key}\n") content.append("uuid=$shortcutUuid\n") extractedArtworkPath?.let { content.append("customCoverArtPath=$it\n") } content.append("container_id=${container.id}\n") diff --git a/app/src/main/app/shell/UnifiedActivityGameDialogs.kt b/app/src/main/app/shell/UnifiedActivityGameDialogs.kt index 65cf07fb4..f74460e40 100644 --- a/app/src/main/app/shell/UnifiedActivityGameDialogs.kt +++ b/app/src/main/app/shell/UnifiedActivityGameDialogs.kt @@ -158,6 +158,7 @@ import com.winlator.cmod.app.db.PluviaDatabase import com.winlator.cmod.app.service.DownloadService import com.winlator.cmod.app.service.download.DownloadCoordinator import com.winlator.cmod.app.update.UpdateService +import com.winlator.cmod.feature.library.LibraryStorageMove import com.winlator.cmod.feature.library.LibraryStoreOption import com.winlator.cmod.feature.settings.InputControlsFragment import com.winlator.cmod.feature.settings.SettingsFocusZone @@ -228,6 +229,7 @@ import com.winlator.cmod.shared.android.RefreshRateUtils import com.winlator.cmod.shared.io.StorageUtils import com.winlator.cmod.shared.io.FileUtils import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.ContainerProgressPopup import com.winlator.cmod.shared.ui.dialog.PopupDialog import com.winlator.cmod.shared.ui.dialog.PopupTextAction import androidx.compose.foundation.focusGroup @@ -1766,6 +1768,20 @@ internal fun UnifiedActivity.LibraryGameDetailDialog( ?.takeIf { it.isNotBlank() } } + // Only a Steam install can be relocated here; the other stores own their own install paths. + val canMoveStorage = !isCustom && !isEpic && !isGog + var moveRefreshKey by remember(app.id) { mutableStateOf(0) } + var movePlan by remember(app.id) { mutableStateOf(null) } + var moveConfirmVisible by remember(app.id) { mutableStateOf(false) } + LaunchedEffect(app.id, canMoveStorage, moveRefreshKey) { + movePlan = + if (!canMoveStorage) { + null + } else { + withContext(Dispatchers.IO) { LibraryStorageMove.plan(context, app.id) } + } + } + var steamBranches by remember(app.id) { mutableStateOf>(emptyList()) } var selectedSteamBranch by remember(app.id) { mutableStateOf(STEAM_DEFAULT_BRANCH) } var steamBranchRefreshKey by remember(app.id) { mutableStateOf(0) } @@ -2785,6 +2801,8 @@ internal fun UnifiedActivity.LibraryGameDetailDialog( isGog -> !hasBlockingGogDownloadForLibrary else -> !hasBlockingSteamDownloadForLibrary }, + moveTarget = movePlan?.target, + onMoveGame = { moveConfirmVisible = movePlan != null }, onVerifyFiles = { context.runIfOnlineOrToast { scope.launch { @@ -3312,6 +3330,74 @@ internal fun UnifiedActivity.LibraryGameDetailDialog( onDismissRequest = { showWorkshopDialog = false }, ) } + + movePlan?.let { plan -> + val toAppStorage = plan.target == LibraryStorageMove.Target.APP_STORAGE + LaunchDangerConfirmDialog( + visible = moveConfirmVisible, + title = + stringResource( + if (toAppStorage) { + R.string.library_games_move_to_app_storage_title + } else { + R.string.library_games_move_to_download_folder_title + }, + ), + message = + stringResource( + if (toAppStorage) { + R.string.library_games_move_to_app_storage_confirm + } else { + R.string.library_games_move_to_download_folder_confirm + }, + app.name, + ), + confirmLabel = stringResource(R.string.common_ui_move), + icon = Icons.Outlined.DriveFileMove, + accentColor = LaunchAccent, + onDismissRequest = { moveConfirmVisible = false }, + onConfirm = { + moveConfirmVisible = false + startLibraryStorageMove(plan, app.name) { moveRefreshKey++ } + }, + ) + } + } + } +} + +/** + * Runs a [LibraryStorageMove] behind a progress popup. The popup is the only thing holding the + * activity, so it is closed on every path out; [onMoved] refreshes the entry's move offer so the + * menu immediately names the other direction. + */ +private fun UnifiedActivity.startLibraryStorageMove( + plan: LibraryStorageMove.Plan, + appName: String, + onMoved: () -> Unit, +) { + val popup = ContainerProgressPopup(this, R.string.library_games_moving, indeterminate = false) + popup.show() + lifecycleScope.launch { + val result = + LibraryStorageMove.move(plan) { copied, total -> + if (total > 0L) popup.setProgress(((copied * 100L) / total).toInt()) + } + popup.close() + if (result.isSuccess) { + onMoved() + PluviaApp.events.emit(AndroidEvent.LibraryInstallStatusChanged(plan.appId)) + com.winlator.cmod.shared.ui.toast.WinToast.show( + this@startLibraryStorageMove, + getString(R.string.library_games_move_done, appName), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this@startLibraryStorageMove, + getString(R.string.library_games_move_failed, appName), + android.widget.Toast.LENGTH_LONG, + ) } } } diff --git a/app/src/main/app/shell/UnifiedActivityHub.kt b/app/src/main/app/shell/UnifiedActivityHub.kt index 328f2d77f..5f34da8f1 100644 --- a/app/src/main/app/shell/UnifiedActivityHub.kt +++ b/app/src/main/app/shell/UnifiedActivityHub.kt @@ -158,10 +158,17 @@ import com.winlator.cmod.app.db.PluviaDatabase import com.winlator.cmod.app.service.DownloadService import com.winlator.cmod.app.service.download.DownloadCoordinator import com.winlator.cmod.app.update.UpdateService +import com.winlator.cmod.feature.library.LibraryCache +import com.winlator.cmod.feature.library.LibraryContentFilters +import com.winlator.cmod.feature.library.LibraryItemType import com.winlator.cmod.feature.library.LibraryStoreLinks import com.winlator.cmod.feature.library.LibraryStoreOption import com.winlator.cmod.feature.library.LibraryStoreTransfer +import com.winlator.cmod.feature.library.LinuxApps import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.stores.steam.enums.AppType +import com.winlator.cmod.feature.settings.LinuxClientDialog +import com.winlator.cmod.runtime.linux.LinuxClientInstaller import com.winlator.cmod.feature.settings.SettingsFocusZone import com.winlator.cmod.feature.settings.SettingsHost import com.winlator.cmod.feature.settings.SettingsNavBridge @@ -338,8 +345,25 @@ internal fun UnifiedActivity.UnifiedHub() { val chatServiceEnabled by SteamService.chatServiceEnabledFlow.collectAsState() val isEpicLoggedIn by EpicAuthManager.isLoggedInFlow.collectAsState() val isGogLoggedIn by GOGAuthManager.isLoggedInFlow.collectAsState() - val steamApps by db.steamAppDao().getAllOwnedApps().collectAsState(initial = emptyList()) + // Null until Room has answered, which is how the library tells "nothing owned" from "not read yet". + val steamAppsOrNull by db.steamAppDao().getAllOwnedApps().collectAsState(initial = null) + val steamApps = steamAppsOrNull ?: emptyList() val context = LocalContext.current + var showLinuxClient by rememberSaveable { mutableStateOf(false) } + val linuxClient by LinuxClientInstaller.state.collectAsState() + // An install finishing adds the Steam entry, which the Library has to read again to show. + LaunchedEffect(linuxClient) { + val found = linuxClient + if (found is LinuxClientInstaller.State.Installed) localLibraryRefreshKey++ + // A newer runtime is put in front of the user once; after that it waits in Stores. + if (found is LinuxClientInstaller.State.UpdateAvailable && found.isNews) { + LinuxClientInstaller.dismissUpdate(context, found) + showLinuxClient = true + } + } + LaunchedEffect(showLinuxClient) { + if (!LinuxClientInstaller.isWorking) LinuxClientInstaller.refresh(context) + } val persona by SteamService.instance?.localPersona?.collectAsState() ?: remember { mutableStateOf(null) } val scope = rememberCoroutineScope() @@ -385,8 +409,11 @@ internal fun UnifiedActivity.UnifiedHub() { } } - val epicApps by db.epicGameDao().getAll().collectAsState(initial = emptyList()) - val gogApps by db.gogGameDao().getAll().collectAsState(initial = emptyList()) + val epicAppsOrNull by db.epicGameDao().getAll().collectAsState(initial = null) + val gogAppsOrNull by db.gogGameDao().getAll().collectAsState(initial = null) + val epicApps = epicAppsOrNull ?: emptyList() + val gogApps = gogAppsOrNull ?: emptyList() + val storeListsReady = steamAppsOrNull != null && epicAppsOrNull != null && gogAppsOrNull != null val controllerState = rememberControllerConnectionState() val isControllerConnected = controllerState.isConnected @@ -504,17 +531,7 @@ internal fun UnifiedActivity.UnifiedHub() { val filteredSteamApps = remember(steamApps, contentFilters.toMap()) { - steamApps.filter { app -> - when (app.type) { - com.winlator.cmod.feature.stores.steam.enums.AppType.game -> contentFilters["games"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.demo -> contentFilters["games"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.dlc -> contentFilters["dlc"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.application -> contentFilters["applications"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.tool -> contentFilters["tools"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.config -> contentFilters["tools"] == true - else -> contentFilters["games"] == true - } - } + steamApps.filter { app -> LibraryContentFilters.allows(app.type, contentFilters) } } var globalSettingsApp by remember { mutableStateOf(null) } @@ -1013,6 +1030,8 @@ internal fun UnifiedActivity.UnifiedHub() { steamApps = filteredSteamApps, epicApps = epicApps, gogApps = gogApps, + storeListsReady = storeListsReady, + contentFilters = contentFilters.toMap(), layoutMode = libraryLayoutMode, libraryRefreshKey = libraryRefreshKey, shortcutRefreshKey = shortcutRefreshKey, @@ -1211,10 +1230,22 @@ internal fun UnifiedActivity.UnifiedHub() { } if (showAddCustomGame) { - AddCustomGameDialog(onDismiss = { - showAddCustomGame = false - localLibraryRefreshKey++ - }) + AddCustomGameDialog( + onDismiss = { + showAddCustomGame = false + localLibraryRefreshKey++ + }, + onInstallLinuxClient = { showLinuxClient = true }, + ) + } + + if (showLinuxClient) { + LinuxClientDialog( + state = linuxClient, + onStart = { LinuxClientInstaller.start(context) }, + onCancelInstall = { LinuxClientInstaller.cancel() }, + onDismiss = { showLinuxClient = false }, + ) } chatFriend?.let { cf -> @@ -2040,6 +2071,8 @@ internal fun UnifiedActivity.LibraryCarousel( steamApps: List, epicApps: List, gogApps: List, + storeListsReady: Boolean, + contentFilters: Map, layoutMode: LibraryLayoutMode, libraryRefreshKey: Int = 0, shortcutRefreshKey: Int = 0, @@ -2051,9 +2084,14 @@ internal fun UnifiedActivity.LibraryCarousel( val context = LocalContext.current val libraryScope = rememberCoroutineScope() - var cachedShortcuts by remember { mutableStateOf>(emptyList()) } - var customApps by remember { mutableStateOf>(emptyList()) } - var customStoragePathByAppId by remember { mutableStateOf>(emptyMap()) } + // Where this screen left off; see LibraryCache.session. + val resumed = remember { LibraryCache.session } + var cachedShortcuts by remember { mutableStateOf(resumed?.shortcuts ?: emptyList()) } + var customApps by remember { mutableStateOf(resumed?.customApps ?: emptyList()) } + var customStoragePathByAppId by remember { mutableStateOf(resumed?.customStoragePaths ?: emptyMap()) } + // A scan that ran before the shortcuts or a store's rows were read would publish a library with + // those games missing, and the grid would drop them only to bring them back a moment later. + var shortcutsReady by remember { mutableStateOf(resumed != null) } val externalStorageState by com.winlator.cmod.feature.storage.ExternalStorage.state.collectAsState() var localLibraryRefreshKey by remember { mutableIntStateOf(0) } var shortcutsLoaded by remember { mutableStateOf(false) } @@ -2127,6 +2165,14 @@ internal fun UnifiedActivity.LibraryCarousel( SteamApp( id = customId, name = displayName, + // The Steam client is how its games are reached, so it shows with + // them rather than behind the Applications filter, which starts off. + type = + if (LinuxApps.isSteamClientShortcut(shortcut)) { + AppType.game + } else { + LibraryItemType.of(shortcut).appType + }, developer = "Custom", gameDir = gameDir, ) @@ -2144,31 +2190,42 @@ internal fun UnifiedActivity.LibraryCarousel( } shortcutsLoaded = true + shortcutsReady = true } // Move library filtering and file checks off the main thread. - var mergedInstalledApps by remember { mutableStateOf>(emptyList()) } - var installedApps by remember { mutableStateOf>(emptyList()) } - var stableInstalledApps by remember { mutableStateOf>(emptyList()) } - var gogByPseudoId by remember { mutableStateOf>(emptyMap()) } + var mergedInstalledApps by remember { mutableStateOf(resumed?.merged ?: emptyList()) } + var installedApps by remember { mutableStateOf(resumed?.apps ?: emptyList()) } + // What the library drew last time, shown until this launch's own scan has an answer. + val savedPicture = remember { resumed?.apps ?: LibraryCache.preloaded } + var stableInstalledApps by remember { mutableStateOf(savedPicture ?: emptyList()) } + var libraryCacheLoaded by remember { mutableStateOf(savedPicture != null) } + LaunchedEffect(Unit) { + if (libraryCacheLoaded) return@LaunchedEffect + val cached = withContext(Dispatchers.IO) { LibraryCache.load(context) } + if (stableInstalledApps.isEmpty()) stableInstalledApps = cached + libraryCacheLoaded = true + } + var gogByPseudoId by remember { mutableStateOf(resumed?.gogByPseudoId ?: emptyMap()) } var libraryStoreLinks by remember { mutableStateOf(LibraryStoreLinkResult()) } - var epicByPseudoId by remember { mutableStateOf>(emptyMap()) } - var stableGogByPseudoId by remember { mutableStateOf>(emptyMap()) } - var stableEpicByPseudoId by remember { mutableStateOf>(emptyMap()) } - var customListArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customHeroArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customCarouselArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customIconArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customIconPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomIconArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomIconPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomHeroPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomCarouselPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomListPathByAppId by remember { mutableStateOf>(emptyMap()) } + var epicByPseudoId by remember { mutableStateOf(resumed?.epicByPseudoId ?: emptyMap()) } + var stableGogByPseudoId by remember { mutableStateOf(resumed?.gogByPseudoId ?: emptyMap()) } + var stableEpicByPseudoId by remember { mutableStateOf(resumed?.epicByPseudoId ?: emptyMap()) } + val resumedArtwork = resumed?.artwork ?: LibraryCache.Artwork() + var customListArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.list) } + var customHeroArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.hero) } + var customCarouselArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.carousel) } + var customArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.cover) } + var customIconArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.iconArtwork) } + var customIconPathByAppId by remember { mutableStateOf(resumedArtwork.icon) } + var stableCustomArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.cover) } + var stableCustomIconArtworkPathByAppId by remember { mutableStateOf(resumedArtwork.iconArtwork) } + var stableCustomIconPathByAppId by remember { mutableStateOf(resumedArtwork.icon) } + var stableCustomHeroPathByAppId by remember { mutableStateOf(resumedArtwork.hero) } + var stableCustomCarouselPathByAppId by remember { mutableStateOf(resumedArtwork.carousel) } + var stableCustomListPathByAppId by remember { mutableStateOf(resumedArtwork.list) } var artworkCacheRefreshKey by remember { mutableIntStateOf(0) } - var libraryLoaded by remember { mutableStateOf(false) } + var libraryLoaded by remember { mutableStateOf(resumed != null) } // Suppress transient empty states before background recomputation starts. val scanInputToken = remember( @@ -2176,14 +2233,18 @@ internal fun UnifiedActivity.LibraryCarousel( epicApps, gogApps, customApps, + contentFilters, customStoragePathByAppId, externalStorageState.connectivityKey, libraryRefreshKey, localLibraryRefreshKey, + storeListsReady, + shortcutsReady, ) { Any() } var processedScanToken by remember { mutableStateOf(null) } LaunchedEffect(scanInputToken) { + if (!storeListsReady || !shortcutsReady) return@LaunchedEffect withContext(Dispatchers.IO) { val steamInstalled = steamApps.filter { SteamService.isAppInstalled(it.id) } @@ -2329,7 +2390,11 @@ internal fun UnifiedActivity.LibraryCarousel( val storeInstallPaths = installedRows.associate { row -> row.libraryId to row.installPath } val merged = - (steamInstalled + customApps + mappedEpic + mappedGog) + ( + steamInstalled + + customApps.filter { LibraryContentFilters.allows(it.type, contentFilters) } + + mappedEpic + mappedGog + ) .distinctBy { it.id } .filterNot { it.id in storeLinks.hiddenLibraryIds } .filterNot { app -> @@ -2350,6 +2415,10 @@ internal fun UnifiedActivity.LibraryCarousel( (allPlaytime["${searchKey}_last_played"] as? Long) ?: 0L } + // Every store's rows and the shortcuts were read before this ran, so what it found is + // the library, empty or not. None of it needs the network. + LibraryCache.save(context, sorted) + withContext(Dispatchers.Main) { gogByPseudoId = gogMap epicByPseudoId = epicMap @@ -2496,6 +2565,44 @@ internal fun UnifiedActivity.LibraryCarousel( } } + LaunchedEffect( + libraryLoaded, + installedApps, + mergedInstalledApps, + gogByPseudoId, + epicByPseudoId, + cachedShortcuts, + customApps, + customStoragePathByAppId, + customArtworkPathByAppId, + customIconArtworkPathByAppId, + customIconPathByAppId, + customHeroArtworkPathByAppId, + customCarouselArtworkPathByAppId, + customListArtworkPathByAppId, + ) { + if (!libraryLoaded) return@LaunchedEffect + LibraryCache.session = + LibraryCache.Session( + apps = installedApps, + merged = mergedInstalledApps, + gogByPseudoId = gogByPseudoId, + epicByPseudoId = epicByPseudoId, + shortcuts = cachedShortcuts, + customApps = customApps, + customStoragePaths = customStoragePathByAppId, + artwork = + LibraryCache.Artwork( + cover = customArtworkPathByAppId, + iconArtwork = customIconArtworkPathByAppId, + icon = customIconPathByAppId, + hero = customHeroArtworkPathByAppId, + carousel = customCarouselArtworkPathByAppId, + list = customListArtworkPathByAppId, + ), + ) + } + LaunchedEffect(mergedInstalledApps, playtimeRefreshKey) { if (mergedInstalledApps.isEmpty()) { installedApps = emptyList() @@ -2525,9 +2632,24 @@ internal fun UnifiedActivity.LibraryCarousel( installedApps.isEmpty() && stableInstalledApps.isNotEmpty() && (processedScanToken !== scanInputToken || awaitingShortcutScan) - val visibleInstalledApps = if (keepPreviousLibraryVisible) stableInstalledApps else installedApps - val visibleGogByPseudoId = if (keepPreviousLibraryVisible) stableGogByPseudoId else gogByPseudoId - val visibleEpicByPseudoId = if (keepPreviousLibraryVisible) stableEpicByPseudoId else epicByPseudoId + // The saved picture holds a card's id and name and nothing of its artwork. The stores' rows are + // read long before the scan has checked every install, so the cards are drawn from those. + val savedPictureApps = + remember(stableInstalledApps, steamApps) { + val owned = steamApps.associateBy { it.id } + stableInstalledApps.map { owned[it.id] ?: it } + } + val savedPictureGog = + remember(stableGogByPseudoId, gogApps) { + stableGogByPseudoId.ifEmpty { gogApps.filter { it.isInstalled }.associateBy { gogPseudoId(it.id) } } + } + val savedPictureEpic = + remember(stableEpicByPseudoId, epicApps) { + stableEpicByPseudoId.ifEmpty { epicApps.filter { it.isInstalled }.associateBy { 2000000000 + it.id } } + } + val visibleInstalledApps = if (keepPreviousLibraryVisible) savedPictureApps else installedApps + val visibleGogByPseudoId = if (keepPreviousLibraryVisible) savedPictureGog else gogByPseudoId + val visibleEpicByPseudoId = if (keepPreviousLibraryVisible) savedPictureEpic else epicByPseudoId val visibleCustomArtworkPathByAppId = if (keepPreviousLibraryVisible) stableCustomArtworkPathByAppId else customArtworkPathByAppId val visibleCustomIconArtworkPathByAppId = @@ -2609,7 +2731,7 @@ internal fun UnifiedActivity.LibraryCarousel( // DB (steamApps/epicApps/gogApps become non-empty) or if other sources // (custom apps, other stores) already have installed games. val awaitingStoreSync = - installedApps.isEmpty() && ( + visibleInstalledApps.isEmpty() && ( (isLoggedIn && steamApps.isEmpty()) || (epicApps.isEmpty() && EpicService.hasStoredCredentials(context)) || (gogApps.isEmpty() && GOGAuthManager.isLoggedIn(context)) @@ -2617,9 +2739,10 @@ internal fun UnifiedActivity.LibraryCarousel( // Only block the surface while the first library result is unresolved. // After that, keep the current content/empty state visible during // background refreshes so the UI does not flicker back to a spinner. - val initialLibraryLoadPending = !libraryLoaded + val initialLibraryLoadPending = !libraryLoaded && (!libraryCacheLoaded || visibleInstalledApps.isEmpty()) val waitingForFirstEmptyStateResolution = - installedApps.isEmpty() && (processedScanToken !== scanInputToken || awaitingStoreSync || awaitingShortcutScan) + visibleInstalledApps.isEmpty() && + (processedScanToken !== scanInputToken || awaitingStoreSync || awaitingShortcutScan) val showLoading = initialLibraryLoadPending || waitingForFirstEmptyStateResolution if (showLoading) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { diff --git a/app/src/main/app/shell/UnifiedActivityLaunch.kt b/app/src/main/app/shell/UnifiedActivityLaunch.kt index 01a5e1b78..fc4de73f4 100644 --- a/app/src/main/app/shell/UnifiedActivityLaunch.kt +++ b/app/src/main/app/shell/UnifiedActivityLaunch.kt @@ -277,7 +277,10 @@ internal fun UnifiedActivity.launchSteamGame( val detectedLaunchExecutable = SteamService.getInstalledExe(app.id) if (shortcut != null) { - if (!SetupWizardActivity.isContainerUsable(context, shortcut.container)) { + // An entry in the GameScope container hands the title to the native client, which + // needs no Wine and keeps its own Exec. + val throughClient = shortcut.container.isGamescopeRuntime + if (!throughClient && !SetupWizardActivity.isContainerUsable(context, shortcut.container)) { withContext(Dispatchers.Main) { SetupWizardActivity.promptToInstallWineOrCreateContainer( context, @@ -286,32 +289,34 @@ internal fun UnifiedActivity.launchSteamGame( } return@launch } - normalizeContainerDrives(shortcut.container) + if (!throughClient) normalizeContainerDrives(shortcut.container) shortcut.putExtra("game_source", "STEAM") shortcut.putExtra("game_install_path", gameInstallPath) val existingLaunchExecutable = shortcut.getExtra("launch_exe_path") if (existingLaunchExecutable.isNullOrBlank() && detectedLaunchExecutable.isNotBlank()) { shortcut.putExtra("launch_exe_path", detectedLaunchExecutable) } - val loaderExec = "wine \"C:\\\\Program Files (x86)\\\\Steam\\\\steamclient_loader_x64.exe\"" - val lines = - com.winlator.cmod.shared.io.FileUtils - .readLines(shortcut.file) - val rewritten = StringBuilder() - var execUpdated = false - for (line in lines) { - if (line.startsWith("Exec=")) { + if (!throughClient) { + val loaderExec = "wine \"C:\\\\Program Files (x86)\\\\Steam\\\\steamclient_loader_x64.exe\"" + val lines = + com.winlator.cmod.shared.io.FileUtils + .readLines(shortcut.file) + val rewritten = StringBuilder() + var execUpdated = false + for (line in lines) { + if (line.startsWith("Exec=")) { + rewritten.append("Exec=").append(loaderExec).append("\n") + execUpdated = true + } else { + rewritten.append(line).append("\n") + } + } + if (!execUpdated) { rewritten.append("Exec=").append(loaderExec).append("\n") - execUpdated = true - } else { - rewritten.append(line).append("\n") } + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcut.file, rewritten.toString()) } - if (!execUpdated) { - rewritten.append("Exec=").append(loaderExec).append("\n") - } - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcut.file, rewritten.toString()) shortcut.saveData() val intent = Intent(context, XServerDisplayActivity::class.java) intent.putExtra("container_id", shortcut.container.id) @@ -923,6 +928,13 @@ internal fun UnifiedActivity.launchCustomGame( return@launch } + if (com.winlator.cmod.feature.library.LinuxApps.isLinuxShortcut(shortcut)) { + withContext(Dispatchers.Main) { + com.winlator.cmod.feature.library.LinuxApps.launch(context, shortcut) + } + return@launch + } + if (com.winlator.cmod.feature.retro.RetroShortcuts.isRetroShortcut(shortcut)) { // Asks RetroShortcuts which launcher this shortcut needs rather // than reasoning about it here. Listing the embedded paths in two diff --git a/app/src/main/app/shell/UnifiedActivityStartup.kt b/app/src/main/app/shell/UnifiedActivityStartup.kt index 34ed75ada..8c63aa5d9 100644 --- a/app/src/main/app/shell/UnifiedActivityStartup.kt +++ b/app/src/main/app/shell/UnifiedActivityStartup.kt @@ -201,6 +201,9 @@ import com.winlator.cmod.feature.sync.CloudSyncHelper import com.winlator.cmod.feature.sync.google.CloudSyncManager import com.winlator.cmod.feature.sync.google.GameSaveBackupManager import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.feature.library.LinuxApps +import com.winlator.cmod.feature.library.LinuxSteamLibrary +import com.winlator.cmod.runtime.linux.LinuxRuntime import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.container.Shortcut import com.winlator.cmod.runtime.display.XServerDisplayActivity @@ -407,6 +410,15 @@ internal fun UnifiedActivity.bootstrapStartupState() { lifecycleScope.launch(Dispatchers.IO) { val appContext = applicationContext + // The Steam entry's artwork is redrawn here as well as at container creation, so an + // install made by an earlier build picks up the current one. + runCatching { + val manager = ContainerManager(appContext) + LinuxApps.gamescopeContainer(manager)?.let { LinuxApps.ensureSteamShortcut(appContext, it) } + if (LinuxRuntime.isInstalled(appContext)) { + LinuxSteamLibrary.adoptClientInstalls(appContext, LinuxRuntime.rootDir(appContext)) + } + }.onFailure { Log.w("UnifiedActivity", "Could not refresh the Steam entries", it) } val resolvedLayoutMode = runCatching { PrefManager.init(appContext) diff --git a/app/src/main/app/shell/UnifiedActivityStores.kt b/app/src/main/app/shell/UnifiedActivityStores.kt index 6438453f1..ad9a983a0 100644 --- a/app/src/main/app/shell/UnifiedActivityStores.kt +++ b/app/src/main/app/shell/UnifiedActivityStores.kt @@ -437,13 +437,9 @@ internal fun UnifiedActivity.GameCapsule( } } else { val preferWide = !useLibraryCapsule && !listMode && DeviceProfileSettings.preferWideArtwork(context) + val primaryRef = StoreArtworkCache.primaryRef(app, gogGame, epicGame, useLibraryCapsule, listMode, preferWide) val imageModel = - remember(app.id, gogGame, epicGame, useLibraryCapsule, listMode, preferWide, artworkCacheRefreshKey) { - StoreArtworkCache.imageModel( - context, - StoreArtworkCache.primaryRef(app, gogGame, epicGame, useLibraryCapsule, listMode, preferWide), - ) - } + remember(primaryRef, artworkCacheRefreshKey) { StoreArtworkCache.imageModel(context, primaryRef) } AsyncImage( model = ImageRequest diff --git a/app/src/main/app/update/UpdateDialog.kt b/app/src/main/app/update/UpdateDialog.kt index c4561a804..338985a01 100644 --- a/app/src/main/app/update/UpdateDialog.kt +++ b/app/src/main/app/update/UpdateDialog.kt @@ -171,7 +171,7 @@ private fun UpdateDialogHeader(release: UpdateRelease) { fontSize = 11.sp, ) } - UpdateChannelBadge(release.preRelease) + UpdateChannelBadge(release) } val meta = buildMetaLine(release) @@ -182,7 +182,7 @@ private fun UpdateDialogHeader(release: UpdateRelease) { Spacer(Modifier.height(9.dp)) Text( - text = "${UpdateService.installedVersionName()} โ†’ ${release.tag}", + text = "${UpdateService.installedVersionName()} โ†’ ${release.displayVersion}", color = WinNativeAccent, fontSize = 12.sp, fontWeight = FontWeight.Medium, @@ -191,9 +191,9 @@ private fun UpdateDialogHeader(release: UpdateRelease) { } @Composable -private fun UpdateChannelBadge(preRelease: Boolean) { - val isDev = preRelease - val tint = if (isDev) Color(0xFFFFB74D) else WinNativeAccent +private fun UpdateChannelBadge(release: UpdateRelease) { + val pullRequest = release.channel == UpdateChannel.PULL_REQUEST + val tint = if (pullRequest) Color(0xFFFFB74D) else WinNativeAccent Box( modifier = Modifier @@ -203,7 +203,7 @@ private fun UpdateChannelBadge(preRelease: Boolean) { .padding(horizontal = 8.dp, vertical = 3.dp), ) { Text( - text = if (isDev) "DEV" else "OFFICIAL", + text = if (pullRequest) release.tag.uppercase() else "OFFICIAL", color = tint, fontSize = 9.sp, fontWeight = FontWeight.Bold, diff --git a/app/src/main/app/update/UpdateDownloader.kt b/app/src/main/app/update/UpdateDownloader.kt index 4aa156b78..c580effe7 100644 --- a/app/src/main/app/update/UpdateDownloader.kt +++ b/app/src/main/app/update/UpdateDownloader.kt @@ -30,6 +30,23 @@ object UpdateDownloader { } } + /** + * As [fetchText], except that a release which is not there answers null instead of raising. + * A pull request's release goes away when it is merged or closed, and that is an answer, not + * a fault to report. + */ + fun fetchTextOrNull(url: String): String? { + val connection = open(url) + try { + val code = connection.responseCode + if (code == HttpURLConnection.HTTP_NOT_FOUND || code == HttpURLConnection.HTTP_GONE) return null + if (code !in 200..299) throw IllegalStateException("HTTP $code for $url") + return connection.inputStream.bufferedReader().use { it.readText() } + } finally { + connection.disconnect() + } + } + fun downloadApk( context: Context, release: UpdateRelease, diff --git a/app/src/main/app/update/UpdateRelease.kt b/app/src/main/app/update/UpdateRelease.kt index b959f42df..8838a6482 100644 --- a/app/src/main/app/update/UpdateRelease.kt +++ b/app/src/main/app/update/UpdateRelease.kt @@ -6,16 +6,14 @@ import org.json.JSONObject import timber.log.Timber import java.util.Locale -enum class UpdateChannel( - val id: String, -) { - OFFICIAL("official"), - DEVELOPMENT("development"), - ; - - companion object { - fun fromId(id: String?): UpdateChannel = entries.firstOrNull { it.id == id } ?: OFFICIAL - } +/** + * Where a build takes its updates from. It is not a preference: an official install can only be + * replaced by an official release and a pull request build only by a later build of that same + * pull request, because anything else is signed with another key and would be refused on install. + */ +enum class UpdateChannel { + OFFICIAL, + PULL_REQUEST, } data class UpdateNoteEntry( @@ -40,15 +38,57 @@ data class UpdateRelease( val apkUrl: String, val apkSize: Long, val sections: List, + val channel: UpdateChannel = UpdateChannel.OFFICIAL, + /** + * Which build of this tag it is. A pull request keeps one tag for its whole life and replaces + * the assets under it, so the commit is the only thing that tells two builds apart. Empty for + * an official release, where the tag already is the identity. + */ + val buildRef: String = "", + val assetUpdatedAt: String = "", ) { - val key: String get() = tag + val key: String get() = if (buildRef.isEmpty()) tag else "$tag@$buildRef" + + val displayDate: String get() = (assetUpdatedAt.takeIf { it.isNotBlank() } ?: publishedAt).substringBefore('T') - val displayDate: String get() = publishedAt.substringBefore('T') + /** What the build is called where a version number would go. */ + val displayVersion: String get() = if (buildRef.isEmpty()) tag else "$tag ยท ${shortRef(buildRef)}" companion object { const val RELEASES_URL = "https://api.github.com/repos/WinNative-Emu/WinNative/releases?per_page=30" const val RELEASES_PAGE = "https://github.com/WinNative-Emu/WinNative/releases" + fun ciReleaseUrl(pullRequest: Int): String = + "https://api.github.com/repos/WinNative-Emu/WinNative-CI/releases/tags/${ciTag(pullRequest)}" + + fun ciTag(pullRequest: Int): String = "pr-$pullRequest" + + fun shortRef(ref: String): String = if (ref.length > 8) ref.substring(0, 8) else ref + + /** The pull request this build was made for, or 0 when it was not made for one. */ + fun installedPullRequest(): Int = BuildConfig.PR_NUMBER.trim().toIntOrNull()?.takeIf { it > 0 } ?: 0 + + fun isPullRequestBuild(): Boolean = installedPullRequest() > 0 + + fun installedBuildRef(): String = BuildConfig.BUILD_ID.trim() + + /** + * Whether two commits name the same build. What the app carries is shortened and what the + * release notes carry is not, so they are compared over the length they share, and a ref + * too short to be meaningful only matches itself. + */ + fun isSameBuild( + installed: String, + remote: String, + ): Boolean { + val mine = installed.trim().lowercase(Locale.US) + val theirs = remote.trim().lowercase(Locale.US) + if (mine.isEmpty() || theirs.isEmpty()) return false + val shared = minOf(mine.length, theirs.length) + if (shared < 7) return mine == theirs + return mine.regionMatches(0, theirs, 0, shared) + } + val KNOWN_VARIANTS: List = BuildConfig.KNOWN_FLAVORS .split(',') @@ -88,26 +128,71 @@ data class UpdateRelease( fun isReleaseBuild(): Boolean = installedVersion() != null - fun pick( - json: String, - channel: UpdateChannel, - ): UpdateRelease? { + /** The official releases, newest version wins. Pre-releases are never offered here. */ + fun pick(json: String): UpdateRelease? { val array = runCatching { JSONArray(json) }.getOrNull() ?: return null var best: UpdateRelease? = null for (i in 0 until array.length()) { val candidate = parse(array.optJSONObject(i) ?: continue) ?: continue - if (channel == UpdateChannel.OFFICIAL && candidate.preRelease) continue + if (candidate.preRelease) continue if (best == null || candidate.version > best.version) best = candidate } return best } - private fun parse(o: JSONObject): UpdateRelease? { + /** + * The one CI release for a pull request. Its tag is fixed, so the answer is whichever + * build is under it now; whether that is newer than the installed one is decided by the + * caller against the commit. + */ + fun pickPullRequest( + json: String, + pullRequest: Int, + ): UpdateRelease? { + val o = runCatching { JSONObject(json) }.getOrNull() ?: return null if (o.optBoolean("draft", false)) return null val tag = o.optString("tag_name").takeIf { it.isNotBlank() } ?: return null - val version = AppVersion.parse(tag) ?: return null - + if (!tag.equals(ciTag(pullRequest), ignoreCase = true)) { + Timber.w("CI release %s is not the pull request that was asked for (%d)", tag, pullRequest) + return null + } val assets = o.optJSONArray("assets") ?: return null + val apk = pickAsset(assets, tag) ?: return null + val url = apk.optString("browser_download_url").takeIf { it.startsWith("https://") } ?: return null + val commit = sourceCommit(o.optString("body")) + if (commit.isEmpty()) { + Timber.w("CI release %s does not name a source commit; not offering it", tag) + return null + } + return UpdateRelease( + tag = tag, + name = o.optString("name").takeIf { it.isNotBlank() } ?: tag, + version = AppVersion(listOf(0), ""), + preRelease = true, + publishedAt = o.optString("published_at"), + htmlUrl = o.optString("html_url"), + apkName = apk.optString("name"), + apkUrl = url, + apkSize = apk.optLong("size", 0L), + sections = parseNotes(o.optString("body")), + channel = UpdateChannel.PULL_REQUEST, + buildRef = commit, + assetUpdatedAt = apk.optString("updated_at"), + ) + } + + /** The commit the CI notes name, which is the head of the pull request, not the merge. */ + private val SOURCE_COMMIT = Regex("(?im)^[ \\t]*Source commit:[ \\t]*`?([0-9a-fA-F]{7,40})`?[ \\t\\r]*$") + + fun sourceCommit(body: String?): String { + if (body.isNullOrBlank()) return "" + return SOURCE_COMMIT.find(body)?.groupValues?.get(1)?.lowercase(Locale.US).orEmpty() + } + + private fun pickAsset( + assets: JSONArray, + tag: String, + ): JSONObject? { val byName = LinkedHashMap() for (i in 0 until assets.length()) { val asset = assets.optJSONObject(i) ?: continue @@ -124,7 +209,16 @@ data class UpdateRelease( ) return null } - val apk = byName.getValue(chosen) + return byName.getValue(chosen) + } + + private fun parse(o: JSONObject): UpdateRelease? { + if (o.optBoolean("draft", false)) return null + val tag = o.optString("tag_name").takeIf { it.isNotBlank() } ?: return null + val version = AppVersion.parse(tag) ?: return null + + val assets = o.optJSONArray("assets") ?: return null + val apk = pickAsset(assets, tag) ?: return null val url = apk.optString("browser_download_url").takeIf { it.startsWith("https://") } ?: return null diff --git a/app/src/main/app/update/UpdateService.kt b/app/src/main/app/update/UpdateService.kt index e64346697..e04459e47 100644 --- a/app/src/main/app/update/UpdateService.kt +++ b/app/src/main/app/update/UpdateService.kt @@ -63,26 +63,34 @@ object UpdateService { private var hourlyRunnable: Runnable? = null private var postGameRunnable: Runnable? = null - fun isSupported(context: Context): Boolean = - UpdateRelease.isReleaseBuild() && UpdateApkInstaller.isOfficialInstall(context) + /** + * A pull request build follows its own pull request and an official install follows the + * official releases. It is the build that decides, not a setting: the two are signed with + * different keys, so an update from the other one could not be installed anyway. + */ + fun channel(): UpdateChannel = + if (UpdateRelease.isPullRequestBuild()) UpdateChannel.PULL_REQUEST else UpdateChannel.OFFICIAL - fun installedVersionName(): String = com.winlator.cmod.BuildConfig.VERSION_NAME + fun isSupported(context: Context): Boolean = + when (channel()) { + // A CI build is signed with whatever key CI had, so the official certificate is not + // asked for; what it is replaced with still has to match it, which install checks. + UpdateChannel.PULL_REQUEST -> UpdateRelease.installedBuildRef().isNotEmpty() + UpdateChannel.OFFICIAL -> UpdateRelease.isReleaseBuild() && UpdateApkInstaller.isOfficialInstall(context) + } - fun isEnabled(context: Context): Boolean = isSupported(context) && UpdateStore.isEnabled(context) + fun installedVersionName(): String = + if (UpdateRelease.isPullRequestBuild()) { + "PR #${UpdateRelease.installedPullRequest()} ยท ${UpdateRelease.shortRef(UpdateRelease.installedBuildRef())}" + } else { + com.winlator.cmod.BuildConfig.VERSION_NAME + } - fun channel(context: Context): UpdateChannel = UpdateStore.channel(context) + /** What this install follows, for the settings screen to show. */ + fun updateSourceName(): String = + if (UpdateRelease.isPullRequestBuild()) "PR #${UpdateRelease.installedPullRequest()}" else "" - fun setChannel( - context: Context, - channel: UpdateChannel, - ) { - if (UpdateStore.channel(context) == channel) return - UpdateStore.setChannel(context, channel) - available = null - dialogVisible = false - UpdateStore.resetCheckTimer(context) - checkNow(context, manual = true) - } + fun isEnabled(context: Context): Boolean = isSupported(context) && UpdateStore.isEnabled(context) fun onAppStarted(context: Context) { val appContext = context.applicationContext @@ -184,14 +192,35 @@ object UpdateService { return true } - private fun fetch(context: Context): UpdateRelease? { + private fun fetch(context: Context): UpdateRelease? = + when (channel()) { + UpdateChannel.PULL_REQUEST -> fetchPullRequest() + UpdateChannel.OFFICIAL -> fetchOfficial() + } + + private fun fetchOfficial(): UpdateRelease? { val installed = UpdateRelease.installedVersion() ?: return null val json = UpdateDownloader.fetchText(UpdateRelease.RELEASES_URL) - val release = UpdateRelease.pick(json, UpdateStore.channel(context)) ?: return null + val release = UpdateRelease.pick(json) ?: return null if (release.version <= installed) return null return release } + /** + * A pull request keeps one release for its whole life and replaces the assets under it, so + * there is no version to compare: the build under the tag is newer when its commit is not the + * one installed. A pull request that has been merged or closed takes its release with it, + * which reads as nothing to install rather than as a failure. + */ + private fun fetchPullRequest(): UpdateRelease? { + val pullRequest = UpdateRelease.installedPullRequest() + if (pullRequest <= 0) return null + val json = UpdateDownloader.fetchTextOrNull(UpdateRelease.ciReleaseUrl(pullRequest)) ?: return null + val release = UpdateRelease.pickPullRequest(json, pullRequest) ?: return null + if (UpdateRelease.isSameBuild(UpdateRelease.installedBuildRef(), release.buildRef)) return null + return release + } + private fun inGame(): Boolean = PluviaApp.currentForegroundActivity is XServerDisplayActivity fun dismissDialog( diff --git a/app/src/main/app/update/UpdateStore.kt b/app/src/main/app/update/UpdateStore.kt index 348fed8e8..9040fa4e1 100644 --- a/app/src/main/app/update/UpdateStore.kt +++ b/app/src/main/app/update/UpdateStore.kt @@ -5,7 +5,6 @@ import androidx.preference.PreferenceManager object UpdateStore { const val PREF_ENABLED = "check_for_updates" - private const val PREF_CHANNEL = "update_channel" private const val PREF_IGNORED_KEY = "update_ignored_key" private const val PREF_LAST_CHECK = "update_last_check" @@ -13,15 +12,6 @@ object UpdateStore { fun isEnabled(context: Context): Boolean = prefs(context).getBoolean(PREF_ENABLED, true) - fun channel(context: Context): UpdateChannel = UpdateChannel.fromId(prefs(context).getString(PREF_CHANNEL, null)) - - fun setChannel( - context: Context, - channel: UpdateChannel, - ) { - prefs(context).edit().putString(PREF_CHANNEL, channel.id).remove(PREF_IGNORED_KEY).apply() - } - fun ignoredKey(context: Context): String? = prefs(context).getString(PREF_IGNORED_KEY, null) fun ignore( @@ -36,8 +26,4 @@ object UpdateStore { fun markChecked(context: Context) { prefs(context).edit().putLong(PREF_LAST_CHECK, System.currentTimeMillis()).apply() } - - fun resetCheckTimer(context: Context) { - prefs(context).edit().putLong(PREF_LAST_CHECK, 0L).apply() - } } diff --git a/app/src/main/assets/directaudio/PROVENANCE.md b/app/src/main/assets/directaudio/PROVENANCE.md index d4a01288f..3c72d17a6 100644 --- a/app/src/main/assets/directaudio/PROVENANCE.md +++ b/app/src/main/assets/directaudio/PROVENANCE.md @@ -86,3 +86,17 @@ gh release download directaudio-v1.3.2 --repo The412Banner/directaudio \ --pattern 'directaudio-wine1*-arm64ec-sdk*.zip' \ --dir app/src/main/assets/directaudio ``` + +## The GameScope build (`linux/winedirectaudio.so`) + +Built by WinNative from the same release (`directaudio-v1.3.2`, `directaudio.c` unmodified) for +aarch64 glibc, against the headers of `ValveSoftware/wine` at +`c1928082ac141b7f03982eacce980a4fe4840d0b`, and linked with WinNative's own +`tools/linuxfs/directaudio/wn_aaudio_client.c` in place of Android's `libaaudio`. The complete +corresponding source and the script that reproduces the binary are in this repository under +`tools/linuxfs/directaudio/` (LGPL-2.1 ยง6); the driver's source is fetched by that script from the +upstream tag. + +| File | SHA-256 | +| --- | --- | +| `linux/winedirectaudio.so` | `089b83f2d099c4e7d20c1e556676174ff2a5a883a58e60093f09b07516059fd5` | diff --git a/app/src/main/assets/directaudio/linux/winedirectaudio.so b/app/src/main/assets/directaudio/linux/winedirectaudio.so new file mode 100755 index 000000000..13a754a23 Binary files /dev/null and b/app/src/main/assets/directaudio/linux/winedirectaudio.so differ diff --git a/app/src/main/assets/inputcontrols/icons/40.png b/app/src/main/assets/inputcontrols/icons/40.png new file mode 100644 index 000000000..6c37022d5 Binary files /dev/null and b/app/src/main/assets/inputcontrols/icons/40.png differ diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp index 1b2734eff..766efcd27 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -242,6 +242,22 @@ "toggleSwitch": false, "text": "", "iconId": 15 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_GUIDE", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.8, + "x": 0.5, + "y": 0.78, + "toggleSwitch": false, + "text": "", + "iconId": 40 } ] } \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles/controls-3.icp b/app/src/main/assets/inputcontrols/profiles/controls-3.icp index e119faa8e..3a65cba20 100644 --- a/app/src/main/assets/inputcontrols/profiles/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles/controls-3.icp @@ -1 +1 @@ -{"id":3,"name":"Virtual Gamepad","cursorSpeed":1,"elements":[{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1.15,"x":0.12745098769664764,"y":0.1111111119389534,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1.15,"x":0.8714597225189209,"y":0.1111111119389534,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1.05,"x":0.12745098769664764,"y":0.21355397999286652,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1.05,"x":0.8714597225189209,"y":0.21355397999286652,"toggleSwitch":false,"text":"","iconId":0},{"type":"D_PAD","shape":"CIRCLE","bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.92,"x":0.27450981736183167,"y":0.8085106611251831,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1.08,"x":0.12745098769664764,"y":0.616233229637146,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1.08,"x":0.8725489974021912,"y":0.6170212626457214,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.656862735748291,"y":0.6808510422706604,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.7058823704719543,"y":0.5744680762290955,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.7058823704719543,"y":0.7872340679168701,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.7549019455909729,"y":0.6808510422706604,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.9,"x":0.4535221457481384,"y":0.8936170339584351,"toggleSwitch":false,"text":"","iconId":16},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.9,"x":0.5453885197639465,"y":0.8936170339584351,"toggleSwitch":false,"text":"","iconId":15},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.78,"x":0.1666666716337204,"y":0.8510638475418091,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.78,"x":0.8333333134651184,"y":0.8510638475418091,"toggleSwitch":false,"text":"","iconId":0}]} +{"id":3,"name":"Virtual Gamepad","cursorSpeed":1,"elements":[{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1.15,"x":0.12745098769664764,"y":0.1111111119389534,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1.15,"x":0.8714597225189209,"y":0.1111111119389534,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1.05,"x":0.12745098769664764,"y":0.21355397999286652,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1.05,"x":0.8714597225189209,"y":0.21355397999286652,"toggleSwitch":false,"text":"","iconId":0},{"type":"D_PAD","shape":"CIRCLE","bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.92,"x":0.27450981736183167,"y":0.8085106611251831,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1.08,"x":0.12745098769664764,"y":0.616233229637146,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1.08,"x":0.8725489974021912,"y":0.6170212626457214,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.656862735748291,"y":0.6808510422706604,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.7058823704719543,"y":0.5744680762290955,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.7058823704719543,"y":0.7872340679168701,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.7549019455909729,"y":0.6808510422706604,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.9,"x":0.4535221457481384,"y":0.8936170339584351,"toggleSwitch":false,"text":"","iconId":16},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.9,"x":0.5453885197639465,"y":0.8936170339584351,"toggleSwitch":false,"text":"","iconId":15},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.78,"x":0.1666666716337204,"y":0.8510638475418091,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.78,"x":0.8333333134651184,"y":0.8510638475418091,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_GUIDE","NONE","NONE","NONE"],"scale":0.8,"x":0.5,"y":0.775,"toggleSwitch":false,"text":"","iconId":40}]} \ No newline at end of file diff --git a/app/src/main/assets/linuxfs/usr/lib/libvulkan_freedreno.so b/app/src/main/assets/linuxfs/usr/lib/libvulkan_freedreno.so new file mode 100644 index 000000000..acfbe6a2f Binary files /dev/null and b/app/src/main/assets/linuxfs/usr/lib/libvulkan_freedreno.so differ diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-directaudio b/app/src/main/assets/linuxfs/usr/local/bin/winnative-directaudio new file mode 100644 index 000000000..1697ecc62 --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-directaudio @@ -0,0 +1,186 @@ +#!/usr/bin/python3 +# Points the Windows games the Steam client runs at DirectAudio, or back at Wine's own choice: +# winnative-directaudio on|off +# +# The app stages the driver under /usr/local/share/winnative/directaudio. It goes into every +# Proton whose mmdevapi walks the same unixlib call table - a driver built for another table +# would crash the game at its first sound - and a prefix is only told to use it when the Proton +# that runs it took it: mmdevapi has no fallback once the registry names a driver, and a title +# that opens audio while loading then never starts. +import glob +import os +import shutil +import struct +import sys +import time + +STAGE = "/usr/local/share/winnative/directaudio" +UNIXLIB = "aarch64-unix/winedirectaudio.so" +HALVES = {"aarch64-windows/winedirectaudio.drv": "system32", "i386-windows/winedirectaudio.drv": "syswow64"} +SECTION = b"[Software\\\\Wine\\\\Drivers]" +VALUE = b'"Audio"="directaudio"' + + +def call_table_entries(path): + """The length of an ELF64 unixlib's __wine_unix_call_funcs, or 0 when it cannot be read.""" + try: + with open(path, "rb") as f: + data = f.read() + if data[:5] != b"\x7fELF\x02": + return 0 + shoff, = struct.unpack_from(" on|off | --prepare on|off") diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-epic-launch b/app/src/main/assets/linuxfs/usr/local/bin/winnative-epic-launch new file mode 100644 index 000000000..919a369f3 --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-epic-launch @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +# Runs an Epic game the native Steam client started, signed in as the app's Epic account: +# winnative-epic-launch [argument ...] +# +# The shortcut the app writes names the game in WN_EPIC. Epic signs a game in with an exchange +# code that lasts five minutes and is spent the first time it is used, so the command line cannot +# be written into the shortcut ahead of time - it is asked for here, as the game starts. +# +# A game whose sign-in cannot be had is started anyway: one that keeps its own session, or that +# plays offline, still runs, and a title that insists on Epic says so itself. Refusing to start +# would leave the user with a library entry that does nothing. +import os +import socket +import sys + +ENDPOINT = "/run/winnative-epic/endpoint" +TIMEOUT = 60 + + +def sign_in(app_name): + """The game's Epic command line, or [] when the app cannot supply one.""" + try: + with open(ENDPOINT, encoding="utf-8") as f: + port, secret = f.read().split() + except (OSError, ValueError): + print("== epic: the app is not signed in to Epic; starting %s without it" % app_name, file=sys.stderr) + return [] + try: + with socket.create_connection(("127.0.0.1", int(port)), TIMEOUT) as s: + s.settimeout(TIMEOUT) + s.sendall(("%s %s\n" % (secret, app_name)).encode("utf-8")) + reply = b"" + while True: + part = s.recv(4096) + if not part: + break + reply += part + except (OSError, ValueError) as e: + print("== epic: could not reach the app to sign %s in: %s" % (app_name, e), file=sys.stderr) + return [] + lines = reply.decode("utf-8", "replace").splitlines() + if not lines or lines[0] != "ok": + print("== epic: %s was not signed in: %s" % (app_name, lines[0] if lines else "no answer"), file=sys.stderr) + return [] + print("== epic: %s signed in" % app_name, file=sys.stderr) + return lines[1:] + + +def main(): + proton, args = sys.argv[1], sys.argv[2:] + app_name = os.environ.get("WN_EPIC", "") + # The variable is the app's own, and Proton passes the environment on to the game. + os.environ.pop("WN_EPIC", None) + os.execv(proton, [proton] + args + (sign_in(app_name) if app_name else [])) + + +if __name__ == "__main__": + if len(sys.argv) < 3: + sys.exit("usage: winnative-epic-launch [argument ...]") + main() diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-netmanager b/app/src/main/assets/linuxfs/usr/local/bin/winnative-netmanager new file mode 100644 index 000000000..3f9f94051 --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-netmanager @@ -0,0 +1,243 @@ +#!/usr/bin/python3 +# Answers for NetworkManager on the runtime's system bus, for the Steam client: +# winnative-netmanager +# +# The client's network page and its connection indicator are built on NetworkManager. It asks for +# one over the system bus as it starts, and without an answer it leaves out half of what its +# interface then calls: Settings > Internet stops with "StartScanningForNetworks is not a function" +# and the header shows no connection while downloads run. +# +# The network here is Android's: the app has no say in Wi-Fi, and nothing in this runtime could +# scan or join one. So this reports what is true and no more - one connection that is up while +# Android has a route out, under the address the app was given - and refuses what it cannot do. +import socket +import sys + +import gi + +gi.require_version("Gio", "2.0") +from gi.repository import Gio, GLib # noqa: E402 + +NAME = "org.freedesktop.NetworkManager" +ROOT = "/org/freedesktop/NetworkManager" +SETTINGS = ROOT + "/Settings" +DEVICE = ROOT + "/Devices/1" +ACTIVE = ROOT + "/ActiveConnection/1" +IP4 = ROOT + "/IP4Config/1" +PROFILE = SETTINGS + "/1" +UUID = "b1d0c8e4-57f0-4c0e-9d60-77696e6e6174" +CHECK_SECONDS = 10 + +# NetworkManager's own numbers. +NM_STATE_DISCONNECTED, NM_STATE_CONNECTED_GLOBAL = 20, 70 +NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_FULL = 1, 4 +NM_DEVICE_TYPE_ETHERNET = 1 +NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_ACTIVATED = 30, 100 +NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2 + +INTROSPECTION = """ + + + + + + + + + + + + + + + + + + + + +""" + + +def address(): + """The address Android routes out by, or None without a route. Nothing is sent.""" + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("192.0.2.1", 9)) + return s.getsockname()[0] + except OSError: + return None + + +def v(signature, value): + return GLib.Variant(signature, value) + + +def objects(ip): + """Every object NetworkManager would export, as its ObjectManager describes them.""" + up = ip is not None + found = { + ROOT: {NAME: { + "Devices": v("ao", [DEVICE]), + "AllDevices": v("ao", [DEVICE]), + "ActiveConnections": v("ao", [ACTIVE] if up else []), + "PrimaryConnection": v("o", ACTIVE if up else "/"), + "PrimaryConnectionType": v("s", "802-3-ethernet" if up else ""), + "ActivatingConnection": v("o", "/"), + "NetworkingEnabled": v("b", True), + "WirelessEnabled": v("b", False), + "WirelessHardwareEnabled": v("b", False), + "WwanEnabled": v("b", False), + "WwanHardwareEnabled": v("b", False), + "Startup": v("b", False), + "Version": v("s", "1.46.0"), + "State": v("u", NM_STATE_CONNECTED_GLOBAL if up else NM_STATE_DISCONNECTED), + "Connectivity": v("u", NM_CONNECTIVITY_FULL if up else NM_CONNECTIVITY_NONE), + "ConnectivityCheckAvailable": v("b", False), + "ConnectivityCheckEnabled": v("b", False), + "Metered": v("u", 0), + }}, + SETTINGS: {NAME + ".Settings": { + "Connections": v("ao", [PROFILE]), + "Hostname": v("s", socket.gethostname()), + "CanModify": v("b", False), + }}, + PROFILE: {NAME + ".Settings.Connection": { + "Unsaved": v("b", False), + "Flags": v("u", 0), + "Filename": v("s", ""), + }}, + DEVICE: { + NAME + ".Device": { + "Interface": v("s", "android"), + "IpInterface": v("s", "android" if up else ""), + "Driver": v("s", "android"), + "DeviceType": v("u", NM_DEVICE_TYPE_ETHERNET), + "State": v("u", NM_DEVICE_STATE_ACTIVATED if up else NM_DEVICE_STATE_DISCONNECTED), + "StateReason": v("(uu)", (NM_DEVICE_STATE_ACTIVATED if up else NM_DEVICE_STATE_DISCONNECTED, 0)), + "ActiveConnection": v("o", ACTIVE if up else "/"), + "Ip4Config": v("o", IP4 if up else "/"), + "Ip6Config": v("o", "/"), + "Dhcp4Config": v("o", "/"), + "Dhcp6Config": v("o", "/"), + "AvailableConnections": v("ao", [PROFILE]), + "Managed": v("b", True), + "Autoconnect": v("b", True), + "Real": v("b", True), + "Capabilities": v("u", 1), + "Mtu": v("u", 1500), + "HwAddress": v("s", ""), + }, + NAME + ".Device.Wired": { + "HwAddress": v("s", ""), + "PermHwAddress": v("s", ""), + "Speed": v("u", 0), + "Carrier": v("b", up), + }, + }, + } + if up: + found[ACTIVE] = {NAME + ".Connection.Active": { + "Connection": v("o", PROFILE), + "SpecificObject": v("o", "/"), + "Id": v("s", "Android"), + "Uuid": v("s", UUID), + "Type": v("s", "802-3-ethernet"), + "Devices": v("ao", [DEVICE]), + "State": v("u", NM_ACTIVE_CONNECTION_STATE_ACTIVATED), + "StateFlags": v("u", 0), + "Default": v("b", True), + "Ip4Config": v("o", IP4), + "Dhcp4Config": v("o", "/"), + "Default6": v("b", False), + "Ip6Config": v("o", "/"), + "Dhcp6Config": v("o", "/"), + "Vpn": v("b", False), + "Master": v("o", "/"), + }} + found[IP4] = {NAME + ".IP4Config": { + # The one address the app can see; the prefix says only that much is known. + "AddressData": v("aa{sv}", [{"address": v("s", ip), "prefix": v("u", 32)}]), + "Addresses": v("aau", []), + "Gateway": v("s", ""), + "RouteData": v("aa{sv}", []), + "Routes": v("aau", []), + "NameserverData": v("aa{sv}", []), + "Nameservers": v("au", []), + "Domains": v("as", []), + "Searches": v("as", []), + "DnsOptions": v("as", []), + "DnsPriority": v("i", 0), + "WinsServerData": v("as", []), + "WinsServers": v("au", []), + }} + return found + + +class NetworkManager: + def __init__(self, bus): + self.bus = bus + self.ip = address() + node = Gio.DBusNodeInfo.new_for_xml(INTROSPECTION) + manager, root, profile, device = node.interfaces + for path, interface in (("/org/freedesktop", manager), (ROOT, root), (PROFILE, profile), (DEVICE, device)): + bus.register_object_with_closures2(path, interface, self.call, None, None) + GLib.timeout_add_seconds(CHECK_SECONDS, self.check) + + def call(self, bus, sender, path, interface, method, args, invocation): + if method == "GetManagedObjects": + invocation.return_value(v("(a{oa{sa{sv}}})", (objects(self.ip),))) + elif method == "GetPermissions": + invocation.return_value(v("(a{ss})", ({},))) + elif method == "CheckConnectivity": + self.check() + invocation.return_value(v("(u)", (NM_CONNECTIVITY_FULL if self.ip else NM_CONNECTIVITY_NONE,))) + elif method == "GetSettings": + # The name the client shows the connection under. + invocation.return_value(v("(a{sa{sv}})", ({"connection": { + "id": v("s", "Android"), "uuid": v("s", UUID), "type": v("s", "802-3-ethernet")}},))) + else: + invocation.return_value(v("(ao)", ([DEVICE],))) + + def check(self): + """Tells the bus when Android gains or loses its route, as NetworkManager would.""" + ip = address() + if (ip is None) != (self.ip is None) or (ip and ip != self.ip): + before, after = objects(self.ip), objects(ip) + self.ip = ip + for path in before.keys() - after.keys(): + self.announce("InterfacesRemoved", path, v("(oas)", (path, list(before[path])))) + for path in after.keys() - before.keys(): + self.announce("InterfacesAdded", path, v("(oa{sa{sv}})", (path, after[path]))) + for path in after.keys() & before.keys(): + for interface, values in after[path].items(): + self.bus.emit_signal(None, path, "org.freedesktop.DBus.Properties", "PropertiesChanged", + v("(sa{sv}as)", (interface, values, []))) + state = after[ROOT][NAME]["State"].unpack() + device = after[DEVICE][NAME + ".Device"]["State"].unpack() + was = before[DEVICE][NAME + ".Device"]["State"].unpack() + self.bus.emit_signal(None, ROOT, NAME, "StateChanged", v("(u)", (state,))) + self.bus.emit_signal(None, DEVICE, NAME + ".Device", "StateChanged", v("(uuu)", (device, was, 0))) + return True + + def announce(self, signal, path, body): + self.bus.emit_signal(None, "/org/freedesktop", "org.freedesktop.DBus.ObjectManager", signal, body) + + +def main(): + loop = GLib.MainLoop() + state = {} + + def acquired(bus, name): + state["manager"] = NetworkManager(bus) + + def lost(bus, name): + print("== netmanager: the system bus is gone", file=sys.stderr) + loop.quit() + + Gio.bus_own_name(Gio.BusType.SYSTEM, NAME, Gio.BusNameOwnerFlags.NONE, acquired, None, lost) + loop.run() + + +if __name__ == "__main__": + main() diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-proton-launch b/app/src/main/assets/linuxfs/usr/local/bin/winnative-proton-launch new file mode 100644 index 000000000..14c6dfd7f --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-proton-launch @@ -0,0 +1,75 @@ +#!/bin/sh +proton=$1 +shift +# The Proton the app's settings chose for this game. Its line keeps the tool Steam mapped when it +# was written: while Steam still starts that one the app's choice is newer, otherwise the user +# changed it in Steam and Steam's stands. `*` is the container's, for games Steam leaves on ours. +choices=/etc/winnative/proton-choices +app=${STEAM_COMPAT_APP_ID:-${STEAM_COMPAT_DATA_PATH##*/}} +if [ -n "$app" ] && [ -r "$choices" ]; then + case $proton in + */compatibilitytools.d/*/proton) started=${proton%/proton}; started=${started##*/} ;; + *) started=winnative-proton ;; + esac + tool= + fallback= + while read -r key name mapped; do + if [ "$key" = "$app" ]; then + [ "$mapped" = "$started" ] && tool=$name + fallback= + break + fi + [ "$key" = "*" ] && [ "$started" = winnative-proton ] && fallback=$name + done < "$choices" + tool=${tool:-$fallback} + case $tool in + "" | */* | .*) chosen= ;; + winnative-proton) chosen=/opt/winnative-proton/proton ;; + *) chosen=${STEAM_COMPAT_CLIENT_INSTALL_PATH:-$HOME/.local/share/Steam}/compatibilitytools.d/$tool/proton ;; + esac + if [ -n "$chosen" ] && [ -x "$chosen" ] && [ -x "$(dirname "$chosen")/files/bin-arm64/wine" ]; then + proton=$chosen + fi +fi +# The Vulkan driver the app's settings chose for this game, else the container's `*`. The session +# draws with the driver of whichever entry started it, and the game would otherwise inherit that. +drivers=/etc/winnative/driver-choices +if [ -n "$app" ] && [ -r "$drivers" ]; then + icd= + while read -r key path; do + if [ "$key" = "$app" ]; then + icd=$path + break + fi + [ "$key" = "*" ] && icd=$path + done < "$drivers" + [ -n "$icd" ] && [ -r "$icd" ] && export VK_ICD_FILENAMES="$icd" +fi +base=$(dirname "$proton") +if [ ! -x "$proton" ] || [ ! -x "$base/files/bin-arm64/wine" ]; then + echo "winnative-proton: incomplete ARM64 tool: $base" >&2 + exit 126 +fi +export PROTON_USE_ARM64=1 +# CachyOS Proton defaults to its PipeWire audio driver, and with no PipeWire server in the session +# the game hung at audio init behind Steam's spinner. Off, it takes the PulseAudio path we do run. +export PROTON_USE_PIPEWIRE=${PROTON_USE_PIPEWIRE:-0} +fake=/usr/local/lib/libwninput.so +if [ -f "$fake" ]; then + case ":${LD_PRELOAD:-}:" in + *":$fake:"*) ;; + *) LD_PRELOAD="$fake${LD_PRELOAD:+:$LD_PRELOAD}"; export LD_PRELOAD ;; + esac +fi +if [ -n "${WN_LOG:-}" ]; then + export PROTON_LOG=${PROTON_LOG:-1} + export PROTON_LOG_DIR=${PROTON_LOG_DIR:-$(dirname "$WN_LOG")} +fi +audio=off +[ "${WN_DIRECTAUDIO:-0}" = 1 ] && audio=on +winnative-directaudio --prepare "$base/files" "${STEAM_COMPAT_DATA_PATH:-}" "$audio" || exit $? +echo "winnative-proton: tool=$base verb=${1:-missing} app=${SteamAppId:-unknown} icd=${VK_ICD_FILENAMES:-none}" >&2 +if [ -n "${WN_EPIC:-}" ] && [ -x /usr/local/bin/winnative-epic-launch ]; then + exec /usr/local/bin/winnative-epic-launch "$proton" "$@" +fi +exec "$proton" "$@" diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-seed-redists b/app/src/main/assets/linuxfs/usr/local/bin/winnative-seed-redists new file mode 100644 index 000000000..3662faf7d --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-seed-redists @@ -0,0 +1,93 @@ +#!/bin/bash +# Marks Steam's shared redistributables as already run in every Proton prefix, and in the +# template Proton makes new prefixes from. +# +# A game whose depot lists _CommonRedist makes the client run the real installer inside the +# prefix on first launch. Those are x86 bundles, so they run under FEX, and the 2019 one leaves +# a task behind every time: its main thread exits while a helper stays blocked reading a pipe it +# also holds the write end of, so the process never reaps and the client keeps waiting on it. +# +# The client skips a "Run Process" block whose HasRunKey is already set, which is the same escape +# WineUtils.seedVcRedistRegistryIfDllsPresent() takes for the app's own containers. The names and +# versions come out of the install scripts rather than a list kept here, because Valve revises +# them; a prefix already carrying the same set is left alone. +set -u +steam_root=${1:?steam root} + +redist="$steam_root/steamapps/common/Steamworks Shared/_CommonRedist" + +# Every process an install script would run, as the registry key and value the client writes once +# it has run it. Names sit one level inside "Run Process", the key they share a level further in. +pairs=$(find "$redist" -name installscript.vdf -print0 2>/dev/null | sort -z | xargs -0 -r awk ' + FNR == 1 { depth = 0; name = ""; key = "" } + /^[[:space:]]*\{/ { depth++; next } + /^[[:space:]]*\}/ { + if (depth == 3 && name != "" && key != "") + printf "%s\t%s\n", key, name + depth-- + next + } + { + n = split($0, part, "\"") + if (n >= 5 && tolower(part[2]) == "hasrunkey") { key = part[4]; next } + if (n >= 3 && depth == 2) { name = part[2]; key = "" } + } +') +# The client downloads the scripts with the first game that wants them, which is too late for that +# game; until then the set they held when this was written stands in. +[ -n "$pairs" ] || pairs=$(sed 's/^\([^ ]*\) /HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\\1\t/' <<'KNOWN' +DirectX\\Jun2010 dxsetup +.NET\\4.8 4.8 +vcredist\\2008 x86 SP1 +vcredist\\2008 x64 SP1 +vcredist\\2010 x86 +vcredist\\2010 x64 +vcredist\\2013 x86 12.0.30501 +vcredist\\2013 x64 12.0.30501 +vcredist\\2015 x86 Update 3 14.0.24215.0 +vcredist\\2015 x64 Update 3 14.0.24215.0 +vcredist\\2017 x86 14.10.25008.0 +vcredist\\2017 x64 14.10.25008.0 +vcredist\\2019 x86 14.28.29334.0 +vcredist\\2019 x64 14.28.29334.0 +vcredist\\2022 x86 14.51.36247.0 +vcredist\\2022 x64 14.51.36247.0 +KNOWN +) +stamp=$(printf '%s' "$pairs" | cksum) + +block=$(printf '%s\n' "$pairs" | awk -F'\t' -v now="$(date +%s)" ' + { + path = $1 + sub(/^HKEY_LOCAL_MACHINE\\\\/, "", path) + if (path == $1) + next + printf "\n[%s] %d\n\"%s\"=dword:00000001\n", path, now, $2 + # The client writes these from 32-bit steam.exe, so its own copy lands under Wow6432Node; + # both spellings are seeded because the reader picks by its own word size. + sub(/^Software\\\\/, "Software\\\\\\\\Wow6432Node\\\\\\\\", path) + printf "\n[%s] %d\n\"%s\"=dword:00000001\n", path, now, $2 + } +') +[ -n "$block" ] || exit 0 + +# Proton builds a new prefix from its template, and the client runs the install scripts in that +# prefix moments later, so the template carries the markers too. Its date is kept: Proton reads a +# changed one as a new template and upgrades every prefix made from the old. +for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ + "$steam_root"/compatibilitytools.d/*/files/share/default_pfx*/system.reg \ + /opt/winnative-proton/files/share/default_pfx*/system.reg \ + "$steam_root"/steamapps/compatdata/*/pfx/system.reg \ + /mnt/winnative/steamapps/compatdata/*/pfx/system.reg; do + [ -f "$reg" ] || continue + seeded=$(dirname "$reg")/.winnative-redists + [ "$(cat "$seeded" 2>/dev/null)" = "$stamp" ] && continue + dated=$(mktemp) || continue + touch -r "$reg" "$dated" + if printf '%s\n' "$block" >>"$reg"; then + printf '%s\n' "$stamp" >"$seeded" + echo "== seeded redist markers into $reg" + fi + touch -r "$dated" "$reg" + rm -f "$dated" +done diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-session b/app/src/main/assets/linuxfs/usr/local/bin/winnative-session new file mode 100644 index 000000000..60972860a --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-session @@ -0,0 +1,221 @@ +#!/bin/bash +# The Linux runtime's session. WinNative execs this under proot with the output size and mode; +# the first pass starts gamescope, which execs this script again inside it for the program: +# winnative-session desktop the XFCE desktop +# winnative-session run a Linux program (AppImage, script or ELF) +# winnative-session steam [steam-url] the native Steam client +# WN_WIDTH / WN_HEIGHT size the output, WN_FPS limits it, WN_LOG collects everything said here. +set -u +export HOME=${HOME:-/root} +mode=${1:-desktop} +shift || true +if [ -n "${WN_LOG:-}" ] && [ -z "${WN_INSIDE:-}" ]; then + mkdir -p "$(dirname "$WN_LOG")" 2>/dev/null + # The client alone writes megabytes an hour; one earlier log is kept, not every session's. + if [ "$(stat -c %s "$WN_LOG" 2>/dev/null || echo 0)" -gt 8388608 ]; then + mv -f "$WN_LOG" "$WN_LOG.1" + fi + exec >>"$WN_LOG" 2>&1 + echo "== winnative-session $(date) mode=$mode $*" + echo "== uid=$(id -u) size=${WN_WIDTH:-?}x${WN_HEIGHT:-?} fps=${WN_FPS:-0} icd=${VK_ICD_FILENAMES:-none}" + echo "== gamescope: $(gamescope --version 2>&1 | head -1)" +fi +export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} +export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} +export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} +mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" + +if [ -z "${WN_INSIDE:-}" ]; then + # The compositor may still be starting, and a socket left by an earlier session is not + # a compositor: probe until a connection succeeds. + for _ in $(seq 1 100); do + wayland-info >/dev/null 2>&1 && break + sleep 0.1 + done + # The desktop is XFCE on labwc, a compositor of its own that draws into one window of the + # app's: gamescope shows a single program at a time and cannot hold a panel and its windows. + # A runtime installed before the desktop was part of it still gets the file manager. + if [ "$mode" = desktop ] && command -v labwc >/dev/null && command -v xfce4-session >/dev/null; then + export WN_INSIDE=1 XDG_SESSION_TYPE=wayland XDG_CURRENT_DESKTOP=XFCE + export WLR_BACKENDS=wayland WLR_RENDERER=${WN_DESKTOP_RENDERER:-pixman} + exec dbus-run-session -- labwc -S xfce4-session + fi + width=${WN_WIDTH:-1280} + height=${WN_HEIGHT:-720} + args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$width" -h "$height") + # -r is the refresh rate gamescope advertises, which a game reads as the display's; without it + # gamescope says 60. A frame limit is a deliberate cap, so it wins over the panel's rate. + if [ "${WN_FPS:-0}" -gt 0 ]; then + args+=(-r "$WN_FPS") + elif [ "${WN_REFRESH:-0}" -gt 0 ]; then + args+=(-r "$WN_REFRESH") + fi + [ "$mode" = steam ] && args+=(-e) + export WN_INSIDE=1 + exec gamescope "${args[@]}" -- "$0" "$mode" "$@" +fi + +# gamescope manages X windows: toolkits go through Xwayland, not gamescope's own Wayland socket. +export GDK_BACKEND=x11 QT_QPA_PLATFORM=xcb SDL_VIDEODRIVER=x11 + +# gamescope's reaper waits for every process left under it before the compositor comes down. A +# process a game left behind - a Wine service blocked on a server that is already gone - is +# reparented there when the client exits, and the screen stays black on it for good. Nothing of +# the session outlives the program it was started for. +reap_orphans() { + local round p stat pid left + for round in 1 2 3 4 5; do + left= + for p in /proc/[0-9]*; do + pid=${p#/proc/} + [ "$pid" = "$$" ] && continue + stat=$(cat "$p/stat" 2>/dev/null) || continue + # The parent follows the bracketed command name, which may itself contain spaces. + stat=${stat##*) } + set -- $stat + [ "${2:-}" = "$PPID" ] && left="$left $pid" + done + [ -n "$left" ] || return 0 + echo "== leaving behind:$left" + kill -9 $left 2>/dev/null + sleep 0.2 + done +} + +case $mode in + desktop) + exec pcmanfm + ;; + run) + target=${1:?program path} + shift + case ${target,,} in + *.appimage) + export APPIMAGE_EXTRACT_AND_RUN=1 + chmod +x "$target" 2>/dev/null + exec "$target" "$@" + ;; + *.sh) + exec bash "$target" "$@" + ;; + *) + chmod +x "$target" 2>/dev/null + cd "$(dirname "$target")" && exec "$target" "$@" + ;; + esac + ;; + steam) + winnative-steam-install || exit $? + steam_root=$XDG_DATA_HOME/Steam + # A non-Steam game has nothing to update or sync, so as an argument it is launched within a + # second of sign-in - before the client's interface listens for the launch's prompts, which + # the launch then waits on for good. Its URL is held back for the client's pipe instead. + deferred= + case ${1:-} in + steam://rungameid/?????????????????*) deferred=$1; shift ;; + esac + # Register the games the app downloaded (bound at /mnt/winnative) with the client, and the + # ARM64 Proton that lets it launch the Windows ones. Neither is worth losing the session over. + winnative-steam-library "$steam_root" || true + winnative-steam-compat "$steam_root" || true + # Steam runs a game's shared redistributables inside its prefix on first launch; marking + # them as already run keeps those x86 installers from starting under emulation at all. + winnative-seed-redists "$steam_root" || true + # With DirectAudio chosen the games' prefixes name it as their audio driver; otherwise they + # are handed back to Wine's own choice, which finds the PulseAudio server. + winnative-directaudio "$steam_root" "$([ -n "${WN_DIRECTAUDIO:-}" ] && echo on || echo off)" || true + ( + beat=0 + for _ in $(seq 1 720); do + sleep 5 + winnative-seed-redists "$steam_root" >/dev/null 2>&1 || true + beat=$((beat + 1)) + if [ "$((beat % 3))" -eq 0 ]; then + winnative-steam-compat "$steam_root" --refresh >/dev/null 2>&1 || true + fi + done + ) & + heartbeat=$! + export STEAM_RUNTIME=1 + export LD_LIBRARY_PATH=$steam_root/steamrtarm64:$steam_root/steamrtarm64/panorama${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} + export SDL_VIDEO_X11_DGAMOUSE=0 + [ -d /usr/share/guestos/fex-mesa ] && export FEX_ROOTFS=/usr/share/guestos/fex-mesa + # The compatibility tool runs Valve's arm64 Proton. The app installs a copy with the runtime; + # a runtime from before that has the client fetch the depot instead. + if [ ! -f /opt/winnative-proton/proton ] && [ ! -f "$steam_root/steamapps/appmanifest_4427310.acf" ]; then + set -- "$@" steam://install/4427310 + fi + # An app id left in ~/.wn-autolaunch is handed to the client as a URL, so a launch can be + # reproduced without touching the screen while the runtime is being brought up. + if [ -s "$HOME/.wn-autolaunch" ]; then + _wn_app=$(cat "$HOME/.wn-autolaunch") + # A bare app id runs the game; a full steam:// URL is passed through, which is how a + # validate or an install is driven without touching the screen. + case $_wn_app in + steam://*) _wn_url=$_wn_app ;; + *) _wn_url=steam://rungameid/$_wn_app ;; + esac + echo "== autolaunch $_wn_url" + set -- "$@" "$_wn_url" + fi + # The client asks gamescope to pass touches through as touches, and gamescope then drops a + # nested pointer's position: a click lands wherever the cursor was last left. The app sends + # a tap as a pointer, so the mode is held at "hover", where the position moves the cursor. + xprop -root -spy STEAM_TOUCH_CLICK_MODE 2>/dev/null | while read -r _ _ touch_mode; do + [ "$touch_mode" = 0 ] || xprop -root -f STEAM_TOUCH_CLICK_MODE 32c -set STEAM_TOUCH_CLICK_MODE 0 + done & + # The client builds its network page and its connection indicator on NetworkManager, which + # it looks for on the system bus as it starts. The network is Android's, so the runtime has + # neither; without them Settings > Internet fails and the header shows no connection. + mkdir -p /run/dbus + rm -f /run/dbus/system_bus_socket + dbus-daemon --config-file=/usr/local/share/winnative/system-bus.conf --nofork --nopidfile & + system_bus=$! + for _ in $(seq 1 50); do + [ -S /run/dbus/system_bus_socket ] && break + sleep 0.1 + done + winnative-netmanager & + netmanager=$! + waiter= + if [ -n "$deferred" ]; then + ( + ui_log=$steam_root/logs/webhelper_js.txt + loaded="SteamApp Init - After Login total time" + before=$(grep -ac "$loaded" "$ui_log" 2>/dev/null) + for _ in $(seq 1 180); do + sleep 1 + [ "$(grep -ac "$loaded" "$ui_log" 2>/dev/null)" = "$before" ] || break + done + sleep 2 + echo "== launching $deferred" + # A pipe nobody reads would hold this open for good; the client reads it while it lives. + timeout 10 bash -c 'echo "$0" > "$1"' "$deferred" "$HOME/.steam/steam.pipe" + ) & + waiter=$! + fi + # The client exits 42 when it has updated itself and wants a fresh start (steam.sh does this). + while :; do + "$steam_root/steamrtarm64/steam" -gamepadui -clientbeta publicbeta \ + -no-cef-sandbox -cef-force-gpu -cef-ozone-platform=x11 \ + -cef-use-gl=angle -cef-use-angle=vulkan "$@" + rc=$? + echo "== steam exited rc=$rc" + [ "$rc" -eq 42 ] || break + done + pkill -x xprop + # Every job started here is ended here and waited for, before the sweep. One left running is + # adopted by gamescope's reaper on the way out, which waits for it and so holds the + # compositor and the proot above it open on a black screen; and the sweep is no second + # chance, since a job this shell still owns is not an orphan for it to find. + kill "$netmanager" "$system_bus" "$heartbeat" 2>/dev/null + [ -z "$waiter" ] || kill "$waiter" 2>/dev/null + wait "$netmanager" "$system_bus" "$heartbeat" ${waiter:+"$waiter"} 2>/dev/null + reap_orphans + exit "$rc" + ;; + *) + echo "winnative-session: unknown mode '$mode'" >&2 + exit 64 + ;; +esac diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-compat b/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-compat new file mode 100755 index 000000000..384660cc4 --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-compat @@ -0,0 +1,339 @@ +#!/usr/bin/python3 +import os +import re +import shutil +import sys + +TOOL = "winnative-proton" +LAUNCHER = "winnative-launch" +EXTRA_WRAPPER = "winnative-proton-wrap" +SOURCES = ("Proton Experimental (ARM64)", "Proton 11.0 (ARM64)") +BUNDLED = "/opt/winnative-proton" +LIBRARY = "/mnt/winnative" +TOKEN = re.compile(r'\s*(?:"((?:[^"\\]|\\.)*)"|([{}])|//[^\n]*)') +NOT_GAMES = frozenset(( + "228980", "1070560", "1391110", "1493710", "3127680", "4183110", + "4185400", "4427310", "4628740", +)) +ANTICHEAT_RUNTIMES = ("1161040", "1826330") + +TOOLMANIFEST = '''"manifest" +{ + "version" "2" + "commandline" "/%s %%verb%%" + "use_sessions" "1" + "compatmanager_layer_name" "proton" +} +''' % LAUNCHER + +EXTRA_TOOLMANIFEST = '''"manifest" +{ + "version" "2" + "commandline" "/%s %%verb%%" + "use_sessions" "1" + "compatmanager_layer_name" "proton" +} +''' % EXTRA_WRAPPER + +COMPAT_TOOL_VDF = '''"compatibilitytools" +{ + "compat_tools" + { + "%s" + { + "install_path" "." + "display_name" "WN Proton Experimental" + "from_oslist" "windows" + "to_oslist" "linux" + } + } +} +''' % TOOL + +EXTRA_COMPAT_TOOL_VDF = '''"compatibilitytools" +{ + "compat_tools" + { + "%s" + { + "install_path" "." + "display_name" "%s (WinNative)" + "from_oslist" "windows" + "to_oslist" "linux" + } + } +} +''' + +LAUNCHER_SH = '''#!/bin/sh +verb=$1 +shift || true +steam_root=${STEAM_COMPAT_CLIENT_INSTALL_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/Steam} +run_proton() { + exec /usr/local/bin/winnative-proton-launch "$@" +} +if [ -d "%s/files/bin-arm64" ] && [ -x "%s/proton" ]; then + run_proton "%s/proton" "$verb" "$@" +fi +for name in %s; do + depot="$steam_root/steamapps/common/$name" + if [ -d "$depot/files/bin-arm64" ] && [ -x "$depot/proton" ]; then + run_proton "$depot/proton" "$verb" "$@" + fi +done +echo "winnative-proton: no ARM64 Proton depot is installed yet" >&2 +exit 1 +''' % (BUNDLED, BUNDLED, BUNDLED, " ".join('"%s"' % name for name in SOURCES)) + +EXTRA_WRAPPER_SH = '''#!/bin/sh +here=$(dirname "$0") +exec /usr/local/bin/winnative-proton-launch "$here/%s" "$@" +''' + + +def write_changed(path, body, mode=0o644): + try: + with open(path, encoding="utf-8") as source: + if source.read() == body: + os.chmod(path, mode) + return + except OSError: + pass + staged = path + ".winnative-staged" + with open(staged, "w", encoding="utf-8") as output: + output.write(body) + os.chmod(staged, mode) + os.replace(staged, path) + + +def tokenize(text): + tokens = [] + pos = 0 + while pos < len(text): + m = TOKEN.match(text, pos) + if not m or m.end() == pos: + break + pos = m.end() + if m.group(2): + tokens.append(m.group(2)) + elif m.group(1) is not None: + tokens.append('"' + m.group(1) + '"') + return tokens + + +def find_block(tokens, path): + depth, want, i = 0, 0, 0 + while i < len(tokens): + token = tokens[i] + if token == "{": + depth += 1 + elif token == "}": + depth -= 1 + if depth < want: + return None + elif depth == want and i + 1 < len(tokens) and tokens[i + 1] == "{": + if token.lower() == '"%s"' % path[want].lower(): + if want == len(path) - 1: + return i + 1 + want += 1 + i += 1 + return None + + +def block_end(tokens, start): + depth = 0 + for i in range(start, len(tokens)): + if tokens[i] == "{": + depth += 1 + elif tokens[i] == "}": + depth -= 1 + if depth == 0: + return i + return len(tokens) + + +def serialize(tokens): + out, depth, i = [], 0, 0 + while i < len(tokens): + token = tokens[i] + if token == "}": + depth -= 1 + out.append("\t" * depth + "}") + i += 1 + elif i + 1 < len(tokens) and tokens[i + 1] == "{": + out.append("\t" * depth + token) + out.append("\t" * depth + "{") + depth += 1 + i += 2 + else: + out.append("\t" * depth + token + "\t\t" + tokens[i + 1]) + i += 2 + return "\n".join(out) + "\n" + + +def first_key(path, section): + try: + with open(path, encoding="utf-8", errors="replace") as source: + tokens = tokenize(source.read()) + except OSError: + return None + block = find_block(tokens, section) + if block is None: + return None + for i in range(block + 1, block_end(tokens, block)): + if i + 1 < len(tokens) and tokens[i + 1] == "{": + return tokens[i].strip('"') + return None + + +def manifest_entry_point(path): + try: + with open(path, encoding="utf-8", errors="replace") as source: + tokens = tokenize(source.read()) + except OSError: + return None + for i in range(len(tokens) - 1): + if tokens[i].lower() == '"commandline"': + parts = tokens[i + 1].strip('"').split() + if parts: + return parts[0].lstrip("/") + return None + + +def mapped_apps(steam_root): + apps = set() + for library in (steam_root, LIBRARY): + steamapps = os.path.join(library, "steamapps") + try: + names = os.listdir(steamapps) + except OSError: + continue + for name in names: + match = re.fullmatch(r"appmanifest_(\d+)\.acf", name) + if match and match.group(1) not in NOT_GAMES: + apps.add(match.group(1)) + return sorted(apps.union(ANTICHEAT_RUNTIMES), key=int) + + +def build_tool(tools): + tool_dir = os.path.join(tools, TOOL) + if os.path.islink(tool_dir): + os.unlink(tool_dir) + os.makedirs(tool_dir, exist_ok=True) + files = { + "toolmanifest.vdf": TOOLMANIFEST, + "compatibilitytool.vdf": COMPAT_TOOL_VDF, + LAUNCHER: LAUNCHER_SH, + } + for name, body in files.items(): + write_changed(os.path.join(tool_dir, name), body, 0o755 if name == LAUNCHER else 0o644) + + +def adopt_extras(tools): + adopted = set() + try: + entries = sorted(os.listdir(tools)) + except OSError: + return adopted + for entry in entries: + if entry == TOOL or entry.startswith("."): + continue + tool_dir = os.path.join(tools, entry) + manifest = os.path.join(tool_dir, "toolmanifest.vdf") + compat = os.path.join(tool_dir, "compatibilitytool.vdf") + if not os.path.isdir(tool_dir) or not os.path.isfile(manifest): + continue + original = manifest + ".winnative-original" + try: + if not os.path.isfile(original): + shutil.copy2(manifest, original) + entry_point = manifest_entry_point(original) + if not entry_point or entry_point in (EXTRA_WRAPPER, LAUNCHER): + entry_point = "proton" + if not os.path.exists(os.path.join(tool_dir, entry_point)): + continue + name = first_key(compat, ["compatibilitytools", "compat_tools"]) or entry + write_changed(os.path.join(tool_dir, EXTRA_WRAPPER), EXTRA_WRAPPER_SH % entry_point, 0o755) + write_changed(manifest, EXTRA_TOOLMANIFEST) + if os.path.isfile(compat): + write_changed(compat, EXTRA_COMPAT_TOOL_VDF % (name, entry)) + adopted.add(name) + except (OSError, ValueError, IndexError) as error: + print("winnative-steam-compat: could not adopt %s: %s" % (entry, error), file=sys.stderr) + return adopted + + +def register_default(config_path, apps=(), protect=()): + with open(config_path, encoding="utf-8", errors="replace") as source: + tokens = tokenize(source.read()) + steam = find_block(tokens, ["InstallConfigStore", "Software", "Valve", "Steam"]) + if steam is None: + return + protect = {name.lower() for name in protect} + mapping = find_block(tokens[steam + 1:], ["CompatToolMapping"]) + entry = ['"0"', "{", '"name"', '"%s"' % TOOL, '"config"', '""', '"priority"', '"75"', "}"] + changed = False + if mapping is None: + end = block_end(tokens, steam) + tokens[end:end] = ['"CompatToolMapping"', "{"] + entry + ["}"] + changed = True + mapping = find_block(tokens[steam + 1:], ["CompatToolMapping"]) + mapping += steam + 1 + end = block_end(tokens, mapping) + for i in range(mapping, end): + if tokens[i] != '"name"' or i + 1 >= end: + continue + name = tokens[i + 1].strip('"') + lowered = name.lower() + if lowered.startswith("proton") and lowered != TOOL.lower() and lowered not in protect: + tokens[i + 1] = '"%s"' % TOOL + changed = True + if find_block(tokens[mapping + 1:end], ["0"]) is None: + tokens[mapping + 1:mapping + 1] = entry + end += len(entry) + changed = True + for app in apps: + if find_block(tokens[mapping + 1:end], [app]) is not None: + continue + item = ['"%s"' % app, "{", '"name"', '"%s"' % TOOL, '"config"', '""', '"priority"', '"250"', "}"] + tokens[mapping + 1:mapping + 1] = item + end += len(item) + changed = True + if not changed: + return + tmp = config_path + ".winnative" + with open(tmp, "w", encoding="utf-8") as output: + output.write(serialize(tokens)) + os.replace(tmp, config_path) + + +def find_depot(steam_root): + if os.path.isdir(os.path.join(BUNDLED, "files", "bin-arm64")) and os.path.isfile(os.path.join(BUNDLED, "proton")): + return BUNDLED + for name in SOURCES: + depot = os.path.join(steam_root, "steamapps", "common", name) + if os.path.isdir(os.path.join(depot, "files", "bin-arm64")) and os.path.isfile(os.path.join(depot, "proton")): + return depot + return None + + +def main(): + if len(sys.argv) < 2: + sys.exit(2) + steam_root = sys.argv[1] + tools = os.path.join(steam_root, "compatibilitytools.d") + os.makedirs(tools, exist_ok=True) + build_tool(tools) + extras = adopt_extras(tools) + depot = find_depot(steam_root) + state = os.path.basename(depot) if depot else "waiting for ARM64 Proton depot" + print("winnative-steam-compat: registered %s (%s)" % (TOOL, state)) + config = os.path.join(steam_root, "config", "config.vdf") + if "--refresh" not in sys.argv[2:] and os.path.isfile(config): + apps = mapped_apps(steam_root) + register_default(config, apps, extras) + print("winnative-steam-compat: default and %d installed title(s) set to %s" % (len(apps), TOOL)) + + +if __name__ == "__main__": + main() diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-install b/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-install new file mode 100644 index 000000000..52fd07ed2 --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-install @@ -0,0 +1,59 @@ +#!/bin/bash +# Fetches the native arm64 Steam client from Valve's client-update CDN into ~/.local/share/Steam. +# The client is not redistributable, so the runtime ships this script instead of the client. +set -eu +cdn=https://client-update.fastly.steamstatic.com +manifest=steam_client_publicbeta_linuxarm64 +steam_root=${XDG_DATA_HOME:-$HOME/.local/share}/Steam +stamp=$steam_root/package/winnative-installed +[ -x "$steam_root/steamrtarm64/steam" ] && [ -f "$stamp" ] && exit 0 + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +curl -fsSL "$cdn/$manifest" -o "$work/manifest" +version=$(awk -F'"' '$2 == "version" { print $4; exit }' "$work/manifest") +# Every component block: its name, then the plain zip's file name and sha256. +awk -F'"' ' + /^\t"/ && NF >= 3 && $4 == "" { block = $2 } + $2 == "file" { file[block] = $4 } + $2 == "sha2" { sha[block] = $4 } + END { for (b in file) if (b ~ /_all$/ || b ~ /_linuxarm64_linuxarm64$/) print b, file[b], sha[b] } +' "$work/manifest" | sort > "$work/parts" +[ -s "$work/parts" ] || { echo "winnative-steam-install: no client components in $manifest" >&2; exit 1; } + +mkdir -p "$steam_root/package" +while read -r block file sum; do + echo "steam: $block" + curl -fsSL "$cdn/$file" -o "$work/$file" + echo "$sum $work/$file" | sha256sum -c --quiet + unzip -qo "$work/$file" -d "$steam_root" + rm -f "$work/$file" +done < "$work/parts" +# Some entries are zipped with Windows separators; unzip keeps the backslash in the file name. +find "$steam_root" -depth -name '*\\*' | while read -r odd; do + fixed=${odd//\\//} + mkdir -p "$(dirname "$fixed")" + if [ -d "$odd" ]; then + cp -a "$odd/." "$fixed/" && rm -rf "$odd" + else + mv -f "$odd" "$fixed" + fi +done +echo publicbeta > "$steam_root/package/beta" +echo "$version" > "$stamp" +ln -sfn "$steam_root/steamrtarm64" "$steam_root/steamrtarm32" +chmod +x "$steam_root/steamrtarm64/steam" 2>/dev/null || true +# What Valve's steam.sh sets up before it runs the client (it has no arm64 branch itself). +mkdir -p "$HOME/.steam" +ln -sfn "$steam_root" "$HOME/.steam/root" +ln -sfn "$steam_root" "$HOME/.steam/steam" +ln -sfn "$steam_root/steamrtarm64" "$HOME/.steam/bin64" +ln -sfn "$steam_root/steamrtarm64" "$HOME/.steam/binarm64" +# The Steamworks SDK paths a game's steamclient stub is dlopened from, one per architecture. +# Proton's lsteamclient runs emulated under FEX and loads the x86_64 (sdk64) or x86 (sdk32) +# stub, which is only an IPC shim to the arm64 client over ~/.steam/steam.pipe; native arm64 +# games take sdkarm64. Pointing sdk64 at the arm64 tree hands the emulated loader a library it +# cannot map and Steamworks aborts. +ln -sfn "$steam_root/linux64" "$HOME/.steam/sdk64" +ln -sfn "$steam_root/linux32" "$HOME/.steam/sdk32" +ln -sfn "$steam_root/linuxarm64" "$HOME/.steam/sdkarm64" diff --git a/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-library b/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-library new file mode 100644 index 000000000..82d081231 --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/bin/winnative-steam-library @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +# Registers the games WinNative downloaded (bound at /mnt/winnative) as a library folder of the +# native Steam client, so they show as installed there. +# +# winnative-steam-library +import os +import re +import sys + +LIBRARY = "/mnt/winnative" + + +def main(): + steam_root = sys.argv[1] + steamapps = os.path.join(LIBRARY, "steamapps") + if not os.path.isdir(steamapps): + return + apps = sorted( + int(m.group(1)) + for m in (re.fullmatch(r"appmanifest_(\d+)\.acf", n) for n in os.listdir(steamapps)) + if m + ) + marker = os.path.join(LIBRARY, "libraryfolder.vdf") + if not os.path.isfile(marker): + with open(marker, "w", encoding="utf-8") as f: + f.write('"libraryfolder"\n{\n\t"contentid"\t\t"%d"\n\t"label"\t\t"WinNative"\n}\n' + % int.from_bytes(os.urandom(8), "little")) + # The client reads steamapps/libraryfolders.vdf at startup and rewrites config/ from it. + steamapps_dir = os.path.join(steam_root, "steamapps") + path = os.path.join(steamapps_dir, "libraryfolders.vdf") + text = "" + if os.path.isfile(path): + with open(path, encoding="utf-8", errors="replace") as f: + text = f.read() + if re.search(r'"path"\s+"%s"' % re.escape(LIBRARY), text): + return + indexes = [int(i) for i in re.findall(r'^\t"(\d+)"\s*$', text, re.M)] + if not text.strip(): + text = '"libraryfolders"\n{\n\t"0"\n\t{\n\t\t"path"\t\t"%s"\n\t\t"label"\t\t""\n\t}\n}\n' % steam_root + indexes = [0] + entry = ['\t"%d"' % (max(indexes) + 1), "\t{", '\t\t"path"\t\t"%s"' % LIBRARY, + '\t\t"label"\t\t"WinNative"', '\t\t"apps"', "\t\t{"] + entry += ['\t\t\t"%d"\t\t"0"' % app for app in apps] + entry += ["\t\t}", "\t}"] + closing = text.rstrip().rfind("}") + if closing < 0: + return + text = text[:closing] + "\n".join(entry) + "\n" + text[closing:] + os.makedirs(steamapps_dir, exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + f.write(text) + + +if __name__ == "__main__": + main() diff --git a/app/src/main/assets/linuxfs/usr/local/lib/libwninput.so b/app/src/main/assets/linuxfs/usr/local/lib/libwninput.so new file mode 100755 index 000000000..ce29ca778 Binary files /dev/null and b/app/src/main/assets/linuxfs/usr/local/lib/libwninput.so differ diff --git a/app/src/main/assets/linuxfs/usr/local/lib/libwnsession.so b/app/src/main/assets/linuxfs/usr/local/lib/libwnsession.so new file mode 100755 index 000000000..a82e1bf24 Binary files /dev/null and b/app/src/main/assets/linuxfs/usr/local/lib/libwnsession.so differ diff --git a/app/src/main/assets/linuxfs/usr/local/share/winnative/system-bus.conf b/app/src/main/assets/linuxfs/usr/local/share/winnative/system-bus.conf new file mode 100644 index 000000000..cbf364c9b --- /dev/null +++ b/app/src/main/assets/linuxfs/usr/local/share/winnative/system-bus.conf @@ -0,0 +1,15 @@ + + + + system + unix:path=/run/dbus/system_bus_socket + EXTERNAL + + + + + + + diff --git a/app/src/main/assets/wayland/keymap.xkb b/app/src/main/assets/wayland/keymap.xkb new file mode 100644 index 000000000..d1689e661 --- /dev/null +++ b/app/src/main/assets/wayland/keymap.xkb @@ -0,0 +1,2039 @@ +xkb_keymap { +xkb_keycodes "evdev_aliases(qwerty)" { + minimum = 8; + maximum = 709; + = 9; + = 10; + = 11; + = 12; + = 13; + = 14; + = 15; + = 16; + = 17; + = 18; + = 19; + = 20; + = 21; + = 22; + = 23; + = 24; + = 25; + = 26; + = 27; + = 28; + = 29; + = 30; + = 31; + = 32; + = 33; + = 34; + = 35; + = 36; + = 37; + = 38; + = 39; + = 40; + = 41; + = 42; + = 43; + = 44; + = 45; + = 46; + = 47; + = 48; + = 49; + = 50; + = 51; + = 52; + = 53; + = 54; + = 55; + = 56; + = 57; + = 58; + = 59; + = 60; + = 61; + = 62; + = 63; + = 64; + = 65; + = 66; + = 67; + = 68; + = 69; + = 70; + = 71; + = 72; + = 73; + = 74; + = 75; + = 76; + = 77; + = 78; + = 79; + = 80; + = 81; + = 82; + = 83; + = 84; + = 85; + = 86; + = 87; + = 88; + = 89; + = 90; + = 91; + = 92; + = 93; + = 94; + = 95; + = 96; + = 97; + = 98; + = 99; + = 100; + = 101; + = 102; + = 103; + = 104; + = 105; + = 106; + = 107; + = 108; + = 109; + = 110; + = 111; + = 112; + = 113; + = 114; + = 115; + = 116; + = 117; + = 118; + = 119; + = 120; + = 121; + = 122; + = 123; + = 124; + = 125; + = 126; + = 127; + = 128; + = 129; + = 130; + = 131; + = 132; + = 133; + = 134; + = 135; + = 136; + = 137; + = 138; + = 139; + = 140; + = 141; + = 142; + = 143; + = 144; + = 145; + = 146; + = 147; + = 148; + = 149; + = 150; + = 151; + = 152; + = 153; + = 154; + = 155; + = 156; + = 157; + = 158; + = 159; + = 160; + = 161; + = 162; + = 163; + = 164; + = 165; + = 166; + = 167; + = 168; + = 169; + = 170; + = 171; + = 172; + = 173; + = 174; + = 175; + = 176; + = 177; + = 178; + = 179; + = 180; + = 181; + = 182; + = 183; + = 184; + = 185; + = 186; + = 187; + = 188; + = 189; + = 190; + = 191; + = 192; + = 193; + = 194; + = 195; + = 196; + = 197; + = 198; + = 199; + = 200; + = 201; + = 202; + = 203; + = 204; + = 205; + = 206; + = 207; + = 208; + = 209; + = 210; + = 211; + = 212; + = 213; + = 214; + = 215; + = 216; + = 217; + = 218; + = 219; + = 220; + = 221; + = 222; + = 223; + = 224; + = 225; + = 226; + = 227; + = 228; + = 229; + = 230; + = 231; + = 232; + = 233; + = 234; + = 235; + = 236; + = 237; + = 238; + = 239; + = 240; + = 241; + = 242; + = 243; + = 244; + = 245; + = 246; + = 247; + = 248; + = 249; + = 250; + = 251; + = 252; + = 253; + = 254; + = 255; + = 256; + = 360; + = 361; + = 362; + = 363; + = 364; + = 365; + = 366; + = 367; + = 368; + = 369; + = 370; + = 371; + = 372; + = 373; + = 374; + = 375; + = 376; + = 377; + = 378; + = 379; + = 380; + = 381; + = 382; + = 383; + = 384; + = 385; + = 386; + = 387; + = 388; + = 389; + = 390; + = 391; + = 392; + = 393; + = 394; + = 395; + = 396; + = 397; + = 398; + = 399; + = 400; + = 401; + = 402; + = 403; + = 404; + = 405; + = 406; + = 407; + = 408; + = 409; + = 410; + = 411; + = 412; + = 413; + = 414; + = 415; + = 416; + = 417; + = 418; + = 419; + = 420; + = 421; + = 422; + = 423; + = 424; + = 425; + = 426; + = 427; + = 428; + = 429; + = 430; + = 431; + = 432; + = 433; + = 434; + = 435; + = 436; + = 437; + = 438; + = 439; + = 440; + = 441; + = 442; + = 443; + = 444; + = 445; + = 446; + = 447; + = 448; + = 449; + = 450; + = 452; + = 453; + = 454; + = 455; + = 456; + = 457; + = 458; + = 459; + = 472; + = 473; + = 474; + = 475; + = 476; + = 477; + = 478; + = 479; + = 480; + = 481; + = 482; + = 483; + = 484; + = 485; + = 486; + = 487; + = 488; + = 489; + = 490; + = 491; + = 492; + = 493; + = 505; + = 506; + = 507; + = 508; + = 509; + = 510; + = 511; + = 512; + = 513; + = 514; + = 520; + = 521; + = 522; + = 523; + = 524; + = 525; + = 526; + = 527; + = 528; + = 529; + = 530; + = 531; + = 532; + = 533; + = 534; + = 535; + = 536; + = 537; + = 538; + = 539; + = 540; + = 541; + = 542; + = 543; + = 544; + = 545; + = 546; + = 547; + = 548; + = 549; + = 550; + = 568; + = 569; + = 570; + = 584; + = 585; + = 586; + = 587; + = 588; + = 589; + = 590; + = 591; + = 592; + = 593; + = 594; + = 595; + = 596; + = 597; + = 598; + = 599; + = 600; + = 601; + = 616; + = 617; + = 618; + = 619; + = 620; + = 621; + = 622; + = 623; + = 624; + = 625; + = 626; + = 627; + = 628; + = 629; + = 630; + = 631; + = 632; + = 633; + = 634; + = 635; + = 636; + = 637; + = 638; + = 639; + = 640; + = 641; + = 642; + = 643; + = 644; + = 645; + = 646; + = 647; + = 648; + = 649; + = 650; + = 651; + = 652; + = 653; + = 654; + = 655; + = 656; + = 657; + = 664; + = 665; + = 666; + = 667; + = 668; + = 669; + = 670; + = 671; + = 672; + = 673; + = 674; + = 675; + = 676; + = 677; + = 678; + = 679; + = 680; + = 681; + = 682; + = 683; + = 684; + = 685; + = 686; + = 687; + = 688; + = 689; + = 690; + = 691; + = 692; + = 693; + = 696; + = 697; + = 698; + = 699; + = 700; + = 701; + = 704; + = 705; + = 706; + = 707; + = 708; + = 709; + indicator 1 = "Caps Lock"; + indicator 2 = "Num Lock"; + indicator 3 = "Scroll Lock"; + indicator 4 = "Compose"; + indicator 5 = "Kana"; + indicator 6 = "Sleep"; + indicator 7 = "Suspend"; + indicator 8 = "Mute"; + indicator 9 = "Misc"; + indicator 10 = "Mail"; + indicator 11 = "Charging"; + indicator 12 = "Shift Lock"; + indicator 13 = "Group 2"; + indicator 14 = "Mouse Keys"; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; + alias = ; +}; + +xkb_types "complete" { + virtual_modifiers NumLock,Alt,LevelThree,Super,LevelFive,Meta,Hyper=0x4000,ScrollLock=0x8000; + + type "ONE_LEVEL" { + modifiers= none; + level_name[1]= "Any"; + }; + type "TWO_LEVEL" { + modifiers= Shift; + map[Shift]= 2; + level_name[1]= "Base"; + level_name[2]= "Shift"; + }; + type "ALPHABETIC" { + modifiers= Shift+Lock; + map[Shift]= 2; + map[Lock]= 2; + level_name[1]= "Base"; + level_name[2]= "Caps"; + }; + type "PC_ALT_LEVEL2" { + modifiers= Alt; + map[Alt]= 2; + level_name[1]= "Base"; + level_name[2]= "Alt"; + }; + type "PC_SUPER_LEVEL2" { + modifiers= Super; + map[Super]= 2; + level_name[1]= "Base"; + level_name[2]= "Super"; + }; + type "PC_SHIFT_SUPER_LEVEL2" { + modifiers= Shift+Super; + map[Shift+Super]= 2; + level_name[1]= "Base"; + level_name[2]= "Super"; + }; + type "PC_CONTROL_LEVEL2" { + modifiers= Control; + map[Control]= 2; + level_name[1]= "Base"; + level_name[2]= "Control"; + }; + type "PC_CONTROL_SUPER_LEVEL2" { + modifiers= Control+Super; + map[Control+Super]= 2; + map[Control]= 1; + preserve[Control]= Control; + map[Super]= 1; + preserve[Super]= Super; + level_name[1]= "Base"; + level_name[2]= "Control + Super"; + }; + type "CTRL+ALT" { + modifiers= Shift+Control+Alt+LevelThree; + map[Shift]= 2; + preserve[Shift]= Shift; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + preserve[Shift+LevelThree]= Shift; + map[Control]= 1; + preserve[Control]= Control; + map[Shift+Control]= 2; + preserve[Shift+Control]= Shift+Control; + map[Control+LevelThree]= 3; + preserve[Control+LevelThree]= Control; + map[Shift+Control+LevelThree]= 4; + preserve[Shift+Control+LevelThree]= Shift+Control; + map[Alt]= 1; + preserve[Alt]= Alt; + map[Shift+Alt]= 2; + preserve[Shift+Alt]= Shift+Alt; + map[Alt+LevelThree]= 3; + preserve[Alt+LevelThree]= Alt; + map[Shift+Alt+LevelThree]= 4; + preserve[Shift+Alt+LevelThree]= Shift+Alt; + map[Control+Alt]= 5; + map[Shift+Control+Alt]= 2; + preserve[Shift+Control+Alt]= Shift+Control+Alt; + map[Control+Alt+LevelThree]= 3; + preserve[Control+Alt+LevelThree]= Control+Alt; + map[Shift+Control+Alt+LevelThree]= 4; + preserve[Shift+Control+Alt+LevelThree]= Shift+Control+Alt; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "Ctrl+Alt"; + }; + type "THREE_LEVEL" { + modifiers= Shift+LevelThree; + map[Shift]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 3; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "Level3"; + }; + type "FOUR_LEVEL" { + modifiers= Shift+LevelThree; + map[Shift]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + }; + type "FOUR_LEVEL_ALPHABETIC" { + modifiers= Shift+Lock+LevelThree; + map[Shift]= 2; + map[Lock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock+LevelThree]= 4; + map[Shift+Lock+LevelThree]= 3; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + }; + type "FOUR_LEVEL_SEMIALPHABETIC" { + modifiers= Shift+Lock+LevelThree; + map[Shift]= 2; + map[Lock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock+LevelThree]= 3; + preserve[Lock+LevelThree]= Lock; + map[Shift+Lock+LevelThree]= 4; + preserve[Shift+Lock+LevelThree]= Lock; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + }; + type "FOUR_LEVEL_KEYPAD" { + modifiers= Shift+NumLock+LevelThree; + map[Shift]= 2; + map[NumLock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[NumLock+LevelThree]= 4; + map[Shift+NumLock+LevelThree]= 3; + level_name[1]= "Base"; + level_name[2]= "Shift/Numlock"; + level_name[3]= "AltGr"; + level_name[4]= "Shift/Numlock AltGr"; + }; + type "FOUR_LEVEL_MIXED_KEYPAD" { + modifiers= Shift+NumLock+LevelThree; + map[Shift]= 2; + map[NumLock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[NumLock+LevelThree]= 3; + map[Shift+NumLock+LevelThree]= 4; + level_name[1]= "Base"; + level_name[2]= "Number"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + }; + type "FOUR_LEVEL_LOCKABLE_LEVEL2" { + modifiers= Shift+LevelThree+LevelFive; + map[Shift]= 2; + map[LevelFive]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[LevelThree+LevelFive]= 3; + map[Shift+LevelThree+LevelFive]= 4; + level_name[1]= "Base"; + level_name[2]= "Digit"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + }; + type "FOUR_LEVEL_X" { + modifiers= Shift+Control+Alt+LevelThree; + map[LevelThree]= 2; + map[Shift+LevelThree]= 3; + map[Control+Alt]= 4; + level_name[1]= "Base"; + level_name[2]= "AltGr"; + level_name[3]= "Shift AltGr"; + level_name[4]= "Ctrl+Alt"; + }; + type "SEPARATE_CAPS_AND_SHIFT_ALPHABETIC" { + modifiers= Shift+Lock+LevelThree; + map[Shift]= 2; + map[Lock]= 4; + preserve[Lock]= Lock; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock+LevelThree]= 3; + preserve[Lock+LevelThree]= Lock; + map[Shift+Lock+LevelThree]= 3; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Caps / Shift AltGr"; + }; + type "FOUR_LEVEL_PLUS_LOCK" { + modifiers= Shift+Lock+LevelThree; + map[Shift]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock]= 5; + map[Shift+Lock]= 2; + map[Lock+LevelThree]= 3; + map[Shift+Lock+LevelThree]= 4; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "Lock"; + }; + type "EIGHT_LEVEL" { + modifiers= Shift+LevelThree+LevelFive; + map[Shift]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[LevelFive]= 5; + map[Shift+LevelFive]= 6; + map[LevelThree+LevelFive]= 7; + map[Shift+LevelThree+LevelFive]= 8; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "X"; + level_name[6]= "X Shift"; + level_name[7]= "X AltGr"; + level_name[8]= "X Shift AltGr"; + }; + type "EIGHT_LEVEL_ALPHABETIC" { + modifiers= Shift+Lock+LevelThree+LevelFive; + map[Shift]= 2; + map[Lock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock+LevelThree]= 4; + map[Shift+Lock+LevelThree]= 3; + map[LevelFive]= 5; + map[Shift+LevelFive]= 6; + map[Lock+LevelFive]= 6; + map[Shift+Lock+LevelFive]= 5; + map[LevelThree+LevelFive]= 7; + map[Shift+LevelThree+LevelFive]= 8; + map[Lock+LevelThree+LevelFive]= 8; + map[Shift+Lock+LevelThree+LevelFive]= 7; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "X"; + level_name[6]= "X Shift"; + level_name[7]= "X AltGr"; + level_name[8]= "X Shift AltGr"; + }; + type "EIGHT_LEVEL_SEMIALPHABETIC" { + modifiers= Shift+Lock+LevelThree+LevelFive; + map[Shift]= 2; + map[Lock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock+LevelThree]= 3; + preserve[Lock+LevelThree]= Lock; + map[Shift+Lock+LevelThree]= 4; + preserve[Shift+Lock+LevelThree]= Lock; + map[LevelFive]= 5; + map[Shift+LevelFive]= 6; + map[Lock+LevelFive]= 6; + map[Shift+Lock+LevelFive]= 5; + map[LevelThree+LevelFive]= 7; + map[Shift+LevelThree+LevelFive]= 8; + map[Lock+LevelThree+LevelFive]= 7; + preserve[Lock+LevelThree+LevelFive]= Lock; + map[Shift+Lock+LevelThree+LevelFive]= 8; + preserve[Shift+Lock+LevelThree+LevelFive]= Lock; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "X"; + level_name[6]= "X Shift"; + level_name[7]= "X AltGr"; + level_name[8]= "X Shift AltGr"; + }; + type "EIGHT_LEVEL_WITH_LEVEL5_LOCK" { + modifiers= Shift+NumLock+LevelThree+LevelFive; + map[Shift]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[LevelFive]= 5; + map[Shift+LevelFive]= 6; + preserve[Shift+LevelFive]= Shift; + map[LevelThree+LevelFive]= 7; + map[Shift+LevelThree+LevelFive]= 8; + map[NumLock]= 5; + map[Shift+NumLock]= 6; + preserve[Shift+NumLock]= Shift; + map[NumLock+LevelThree]= 7; + map[Shift+NumLock+LevelThree]= 8; + map[Shift+NumLock+LevelFive]= 2; + map[NumLock+LevelThree+LevelFive]= 3; + map[Shift+NumLock+LevelThree+LevelFive]= 4; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "X"; + level_name[6]= "X Shift"; + level_name[7]= "X AltGr"; + level_name[8]= "X Shift AltGr"; + }; + type "EIGHT_LEVEL_ALPHABETIC_WITH_LEVEL5_LOCK" { + modifiers= Shift+Lock+NumLock+LevelThree+LevelFive; + map[Shift]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[LevelFive]= 5; + map[Shift+LevelFive]= 6; + preserve[Shift+LevelFive]= Shift; + map[LevelThree+LevelFive]= 7; + map[Shift+LevelThree+LevelFive]= 8; + map[NumLock]= 5; + map[Shift+NumLock]= 6; + preserve[Shift+NumLock]= Shift; + map[NumLock+LevelThree]= 7; + map[Shift+NumLock+LevelThree]= 8; + map[Shift+NumLock+LevelFive]= 2; + map[NumLock+LevelThree+LevelFive]= 3; + map[Shift+NumLock+LevelThree+LevelFive]= 4; + map[Lock]= 2; + map[Lock+LevelThree]= 3; + map[Shift+Lock+LevelThree]= 4; + map[Lock+LevelFive]= 5; + map[Shift+Lock+LevelFive]= 6; + map[Lock+LevelThree+LevelFive]= 7; + map[Shift+Lock+LevelThree+LevelFive]= 8; + map[Lock+NumLock]= 5; + map[Shift+Lock+NumLock]= 6; + map[Lock+NumLock+LevelThree]= 7; + map[Shift+Lock+NumLock+LevelThree]= 8; + map[Lock+NumLock+LevelFive]= 2; + map[Lock+NumLock+LevelThree+LevelFive]= 3; + map[Shift+Lock+NumLock+LevelThree+LevelFive]= 4; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "AltGr"; + level_name[4]= "Shift AltGr"; + level_name[5]= "X"; + level_name[6]= "X Shift"; + level_name[7]= "X AltGr"; + level_name[8]= "X Shift AltGr"; + }; + type "EIGHT_LEVEL_BY_CTRL" { + modifiers= Shift+Lock+Control+LevelThree; + map[Shift]= 2; + map[Lock]= 2; + map[LevelThree]= 3; + map[Shift+LevelThree]= 4; + map[Lock+LevelThree]= 4; + map[Shift+Lock+LevelThree]= 3; + map[Control]= 5; + map[Shift+Control]= 6; + map[Lock+Control]= 6; + map[Shift+Lock+Control]= 5; + map[Control+LevelThree]= 7; + map[Shift+Control+LevelThree]= 8; + map[Lock+Control+LevelThree]= 8; + map[Shift+Lock+Control+LevelThree]= 7; + level_name[1]= "Base"; + level_name[2]= "Shift"; + level_name[3]= "Level3"; + level_name[4]= "Shift Level3"; + level_name[5]= "Ctrl"; + level_name[6]= "Shift Ctrl"; + level_name[7]= "Level3 Ctrl"; + level_name[8]= "Shift Level3 Ctrl"; + }; + type "KEYPAD" { + modifiers= Shift+NumLock; + map[NumLock]= 2; + level_name[1]= "Base"; + level_name[2]= "Number"; + }; + type "SHIFT+ALT" { + modifiers= Shift+Alt; + map[Shift+Alt]= 2; + level_name[1]= "Base"; + level_name[2]= "Shift+Alt"; + }; +}; + +xkb_compatibility "complete" { + virtual_modifiers NumLock,Alt,LevelThree,Super,LevelFive,Meta,Hyper=0x4000,ScrollLock=0x8000; + + interpret.useModMapMods= AnyLevel; + interpret.repeat= False; + interpret ISO_Level2_Latch+Exactly(Shift) { + useModMapMods=level1; + action= LatchMods(modifiers=Shift,clearLocks,latchToLock); + }; + interpret Shift_Lock+AnyOf(Shift+Lock) { + action= LockMods(modifiers=Shift); + }; + interpret Num_Lock+AnyOf(all) { + virtualModifier= NumLock; + action= LockMods(modifiers=NumLock); + }; + interpret ISO_Level3_Shift+AnyOf(all) { + virtualModifier= LevelThree; + useModMapMods=level1; + action= SetMods(modifiers=LevelThree,clearLocks); + }; + interpret ISO_Level3_Latch+AnyOf(all) { + virtualModifier= LevelThree; + useModMapMods=level1; + action= LatchMods(modifiers=LevelThree,clearLocks,latchToLock); + }; + interpret ISO_Level3_Lock+AnyOf(all) { + virtualModifier= LevelThree; + useModMapMods=level1; + action= LockMods(modifiers=LevelThree); + }; + interpret Alt_L+AnyOf(all) { + virtualModifier= Alt; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Alt_R+AnyOf(all) { + virtualModifier= Alt; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Meta_L+AnyOf(all) { + virtualModifier= Meta; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Meta_R+AnyOf(all) { + virtualModifier= Meta; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Super_L+AnyOf(all) { + virtualModifier= Super; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Super_R+AnyOf(all) { + virtualModifier= Super; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Hyper_L+AnyOf(all) { + virtualModifier= Hyper; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Hyper_R+AnyOf(all) { + virtualModifier= Hyper; + action= SetMods(modifiers=modMapMods,clearLocks); + }; + interpret Scroll_Lock+AnyOf(all) { + virtualModifier= ScrollLock; + action= LockMods(modifiers=modMapMods); + }; + interpret ISO_Level5_Shift+AnyOf(all) { + virtualModifier= LevelFive; + useModMapMods=level1; + action= SetMods(modifiers=LevelFive,clearLocks); + }; + interpret ISO_Level5_Latch+AnyOf(all) { + virtualModifier= LevelFive; + useModMapMods=level1; + action= LatchMods(modifiers=LevelFive,clearLocks,latchToLock); + }; + interpret ISO_Level5_Lock+AnyOf(all) { + virtualModifier= LevelFive; + useModMapMods=level1; + action= LockMods(modifiers=LevelFive); + }; + interpret Mode_switch+AnyOfOrNone(all) { + action= SetGroup(group=+1); + }; + interpret ISO_Level3_Shift+AnyOfOrNone(all) { + action= SetMods(modifiers=LevelThree,clearLocks); + }; + interpret ISO_Level3_Latch+AnyOfOrNone(all) { + action= LatchMods(modifiers=LevelThree,clearLocks,latchToLock); + }; + interpret ISO_Level3_Lock+AnyOfOrNone(all) { + action= LockMods(modifiers=LevelThree); + }; + interpret ISO_Group_Latch+AnyOfOrNone(all) { + action= LatchGroup(group=2); + }; + interpret ISO_Next_Group+AnyOfOrNone(all) { + action= LockGroup(group=+1); + }; + interpret ISO_Prev_Group+AnyOfOrNone(all) { + action= LockGroup(group=-1); + }; + interpret ISO_First_Group+AnyOfOrNone(all) { + action= LockGroup(group=1); + }; + interpret ISO_Last_Group+AnyOfOrNone(all) { + action= LockGroup(group=2); + }; + interpret KP_1+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=+1); + }; + interpret KP_End+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=+1); + }; + interpret KP_2+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+0,y=+1); + }; + interpret KP_Down+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+0,y=+1); + }; + interpret KP_3+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=+1); + }; + interpret KP_Next+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=+1); + }; + interpret KP_4+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=+0); + }; + interpret KP_Left+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=+0); + }; + interpret KP_6+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=+0); + }; + interpret KP_Right+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=+0); + }; + interpret KP_7+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=-1); + }; + interpret KP_Home+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=-1); + }; + interpret KP_8+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+0,y=-1); + }; + interpret KP_Up+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+0,y=-1); + }; + interpret KP_9+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=-1); + }; + interpret KP_Prior+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=-1); + }; + interpret KP_5+AnyOfOrNone(all) { + repeat= True; + action= PtrBtn(button=default); + }; + interpret KP_Begin+AnyOfOrNone(all) { + repeat= True; + action= PtrBtn(button=default); + }; + interpret KP_F2+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=1); + }; + interpret KP_Divide+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=1); + }; + interpret KP_F3+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=2); + }; + interpret KP_Multiply+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=2); + }; + interpret KP_F4+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=3); + }; + interpret KP_Subtract+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=3); + }; + interpret KP_Separator+AnyOfOrNone(all) { + repeat= True; + action= PtrBtn(button=default,count=2); + }; + interpret KP_Add+AnyOfOrNone(all) { + repeat= True; + action= PtrBtn(button=default,count=2); + }; + interpret KP_0+AnyOfOrNone(all) { + repeat= True; + action= LockPtrBtn(button=default,affect=lock); + }; + interpret KP_Insert+AnyOfOrNone(all) { + repeat= True; + action= LockPtrBtn(button=default,affect=lock); + }; + interpret KP_Decimal+AnyOfOrNone(all) { + repeat= True; + action= LockPtrBtn(button=default,affect=unlock); + }; + interpret KP_Delete+AnyOfOrNone(all) { + repeat= True; + action= LockPtrBtn(button=default,affect=unlock); + }; + interpret F25+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=1); + }; + interpret F26+AnyOfOrNone(all) { + repeat= True; + action= SetPtrDflt(affect=button,button=2); + }; + interpret F27+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=-1); + }; + interpret F29+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=-1); + }; + interpret F31+AnyOfOrNone(all) { + repeat= True; + action= PtrBtn(button=default); + }; + interpret F33+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=-1,y=+1); + }; + interpret F35+AnyOfOrNone(all) { + repeat= True; + action= MovePtr(x=+1,y=+1); + }; + interpret Pointer_Button_Dflt+AnyOfOrNone(all) { + action= PtrBtn(button=default); + }; + interpret Pointer_Button1+AnyOfOrNone(all) { + action= PtrBtn(button=1); + }; + interpret Pointer_Button2+AnyOfOrNone(all) { + action= PtrBtn(button=2); + }; + interpret Pointer_Button3+AnyOfOrNone(all) { + action= PtrBtn(button=3); + }; + interpret Pointer_DblClick_Dflt+AnyOfOrNone(all) { + action= PtrBtn(button=default,count=2); + }; + interpret Pointer_DblClick1+AnyOfOrNone(all) { + action= PtrBtn(button=1,count=2); + }; + interpret Pointer_DblClick2+AnyOfOrNone(all) { + action= PtrBtn(button=2,count=2); + }; + interpret Pointer_DblClick3+AnyOfOrNone(all) { + action= PtrBtn(button=3,count=2); + }; + interpret Pointer_Drag_Dflt+AnyOfOrNone(all) { + action= LockPtrBtn(button=default,affect=both); + }; + interpret Pointer_Drag1+AnyOfOrNone(all) { + action= LockPtrBtn(button=1,affect=both); + }; + interpret Pointer_Drag2+AnyOfOrNone(all) { + action= LockPtrBtn(button=2,affect=both); + }; + interpret Pointer_Drag3+AnyOfOrNone(all) { + action= LockPtrBtn(button=3,affect=both); + }; + interpret Pointer_EnableKeys+AnyOfOrNone(all) { + action= LockControls(controls=MouseKeys); + }; + interpret Pointer_Accelerate+AnyOfOrNone(all) { + action= LockControls(controls=MouseKeysAccel); + }; + interpret Pointer_DfltBtnNext+AnyOfOrNone(all) { + action= SetPtrDflt(affect=button,button=+1); + }; + interpret Pointer_DfltBtnPrev+AnyOfOrNone(all) { + action= SetPtrDflt(affect=button,button=-1); + }; + interpret AccessX_Enable+AnyOfOrNone(all) { + action= LockControls(controls=AccessXKeys); + }; + interpret AccessX_Feedback_Enable+AnyOfOrNone(all) { + action= LockControls(controls=AccessXFeedback); + }; + interpret RepeatKeys_Enable+AnyOfOrNone(all) { + action= LockControls(controls=RepeatKeys); + }; + interpret SlowKeys_Enable+AnyOfOrNone(all) { + action= LockControls(controls=SlowKeys); + }; + interpret BounceKeys_Enable+AnyOfOrNone(all) { + action= LockControls(controls=BounceKeys); + }; + interpret StickyKeys_Enable+AnyOfOrNone(all) { + action= LockControls(controls=StickyKeys); + }; + interpret MouseKeys_Enable+AnyOfOrNone(all) { + action= LockControls(controls=MouseKeys); + }; + interpret MouseKeys_Accel_Enable+AnyOfOrNone(all) { + action= LockControls(controls=MouseKeysAccel); + }; + interpret Overlay1_Enable+AnyOfOrNone(all) { + action= LockControls(controls=none); + }; + interpret Overlay2_Enable+AnyOfOrNone(all) { + action= LockControls(controls=none); + }; + interpret AudibleBell_Enable+AnyOfOrNone(all) { + action= LockControls(controls=AudibleBell); + }; + interpret Terminate_Server+AnyOfOrNone(all) { + action= Terminate(); + }; + interpret Alt_L+AnyOfOrNone(all) { + action= SetMods(modifiers=Alt,clearLocks); + }; + interpret Alt_R+AnyOfOrNone(all) { + action= SetMods(modifiers=Alt,clearLocks); + }; + interpret Meta_L+AnyOfOrNone(all) { + action= SetMods(modifiers=Meta,clearLocks); + }; + interpret Meta_R+AnyOfOrNone(all) { + action= SetMods(modifiers=Meta,clearLocks); + }; + interpret Super_L+AnyOfOrNone(all) { + action= SetMods(modifiers=Super,clearLocks); + }; + interpret Super_R+AnyOfOrNone(all) { + action= SetMods(modifiers=Super,clearLocks); + }; + interpret Hyper_L+AnyOfOrNone(all) { + action= SetMods(modifiers=Hyper,clearLocks); + }; + interpret Hyper_R+AnyOfOrNone(all) { + action= SetMods(modifiers=Hyper,clearLocks); + }; + interpret Shift_L+AnyOfOrNone(all) { + action= SetMods(modifiers=Shift,clearLocks); + }; + interpret Shift_R+AnyOfOrNone(all) { + action= SetMods(modifiers=Shift,clearLocks); + }; + interpret XF86Switch_VT_1+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=1,!same); + }; + interpret XF86Switch_VT_2+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=2,!same); + }; + interpret XF86Switch_VT_3+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=3,!same); + }; + interpret XF86Switch_VT_4+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=4,!same); + }; + interpret XF86Switch_VT_5+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=5,!same); + }; + interpret XF86Switch_VT_6+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=6,!same); + }; + interpret XF86Switch_VT_7+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=7,!same); + }; + interpret XF86Switch_VT_8+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=8,!same); + }; + interpret XF86Switch_VT_9+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=9,!same); + }; + interpret XF86Switch_VT_10+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=10,!same); + }; + interpret XF86Switch_VT_11+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=11,!same); + }; + interpret XF86Switch_VT_12+AnyOfOrNone(all) { + repeat= True; + action= SwitchScreen(screen=12,!same); + }; + interpret XF86LogGrabInfo+AnyOfOrNone(all) { + repeat= True; + action= Private(type=0x86,data[0]=0x50,data[1]=0x72,data[2]=0x47,data[3]=0x72,data[4]=0x62,data[5]=0x73,data[6]=0x00); + }; + interpret XF86LogWindowTree+AnyOfOrNone(all) { + repeat= True; + action= Private(type=0x86,data[0]=0x50,data[1]=0x72,data[2]=0x57,data[3]=0x69,data[4]=0x6e,data[5]=0x73,data[6]=0x00); + }; + interpret XF86Next_VMode+AnyOfOrNone(all) { + repeat= True; + action= Private(type=0x86,data[0]=0x2b,data[1]=0x56,data[2]=0x4d,data[3]=0x6f,data[4]=0x64,data[5]=0x65,data[6]=0x00); + }; + interpret XF86Prev_VMode+AnyOfOrNone(all) { + repeat= True; + action= Private(type=0x86,data[0]=0x2d,data[1]=0x56,data[2]=0x4d,data[3]=0x6f,data[4]=0x64,data[5]=0x65,data[6]=0x00); + }; + interpret ISO_Level5_Shift+AnyOfOrNone(all) { + action= SetMods(modifiers=LevelFive,clearLocks); + }; + interpret ISO_Level5_Latch+AnyOfOrNone(all) { + action= LatchMods(modifiers=LevelFive,clearLocks,latchToLock); + }; + interpret ISO_Level5_Lock+AnyOfOrNone(all) { + action= LockMods(modifiers=LevelFive); + }; + interpret Caps_Lock+AnyOfOrNone(all) { + action= LockMods(modifiers=Lock); + }; + interpret Any+Exactly(Lock) { + action= LockMods(modifiers=Lock); + }; + interpret Any+AnyOf(all) { + action= SetMods(modifiers=modMapMods,clearLocks); + }; + indicator "Caps Lock" { + whichModState= locked; + modifiers= Lock; + }; + indicator "Num Lock" { + whichModState= locked; + modifiers= NumLock; + }; + indicator "Scroll Lock" { + whichModState= locked; + modifiers= ScrollLock; + }; + indicator "Shift Lock" { + whichModState= locked; + modifiers= Shift; + }; + indicator "Group 2" { + groups= 0xfffffffe; + }; + indicator "Mouse Keys" { + controls= MouseKeys; + }; +}; + +xkb_symbols "pc_us_inet(evdev)" { + name[1]="English (US)"; + + key { [ Escape ] }; + key { [ 1, exclam ] }; + key { [ 2, at ] }; + key { [ 3, numbersign ] }; + key { [ 4, dollar ] }; + key { [ 5, percent ] }; + key { [ 6, asciicircum ] }; + key { [ 7, ampersand ] }; + key { [ 8, asterisk ] }; + key { [ 9, parenleft ] }; + key { [ 0, parenright ] }; + key { [ minus, underscore ] }; + key { [ equal, plus ] }; + key { [ BackSpace, BackSpace ] }; + key { [ Tab, ISO_Left_Tab ] }; + key { [ q, Q ] }; + key { [ w, W ] }; + key { [ e, E ] }; + key { [ r, R ] }; + key { [ t, T ] }; + key { [ y, Y ] }; + key { [ u, U ] }; + key { [ i, I ] }; + key { [ o, O ] }; + key { [ p, P ] }; + key { [ bracketleft, braceleft ] }; + key { [ bracketright, braceright ] }; + key { [ Return ] }; + key { [ Control_L ] }; + key { [ a, A ] }; + key { [ s, S ] }; + key { [ d, D ] }; + key { [ f, F ] }; + key { [ g, G ] }; + key { [ h, H ] }; + key { [ j, J ] }; + key { [ k, K ] }; + key { [ l, L ] }; + key { [ semicolon, colon ] }; + key { [ apostrophe, quotedbl ] }; + key { [ grave, asciitilde ] }; + key { [ Shift_L ] }; + key { [ backslash, bar ] }; + key { [ z, Z ] }; + key { [ x, X ] }; + key { [ c, C ] }; + key { [ v, V ] }; + key { [ b, B ] }; + key { [ n, N ] }; + key { [ m, M ] }; + key { [ comma, less ] }; + key { [ period, greater ] }; + key { [ slash, question ] }; + key { [ Shift_R ] }; + key { + type= "CTRL+ALT", + symbols[1]= [ KP_Multiply, KP_Multiply, KP_Multiply, KP_Multiply, XF86ClearGrab ] + }; + key { [ Alt_L ] }; + key { [ space ] }; + key { [ Caps_Lock ] }; + key { + type= "CTRL+ALT", + symbols[1]= [ F1, F1, F1, F1, XF86Switch_VT_1 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F2, F2, F2, F2, XF86Switch_VT_2 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F3, F3, F3, F3, XF86Switch_VT_3 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F4, F4, F4, F4, XF86Switch_VT_4 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F5, F5, F5, F5, XF86Switch_VT_5 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F6, F6, F6, F6, XF86Switch_VT_6 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F7, F7, F7, F7, XF86Switch_VT_7 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F8, F8, F8, F8, XF86Switch_VT_8 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F9, F9, F9, F9, XF86Switch_VT_9 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F10, F10, F10, F10, XF86Switch_VT_10 ] + }; + key { [ Num_Lock ] }; + key { [ Scroll_Lock ] }; + key { [ KP_Home, KP_7 ] }; + key { [ KP_Up, KP_8 ] }; + key { [ KP_Prior, KP_9 ] }; + key { + type= "CTRL+ALT", + symbols[1]= [ KP_Subtract, KP_Subtract, KP_Subtract, KP_Subtract, XF86Prev_VMode ] + }; + key { [ KP_Left, KP_4 ] }; + key { [ KP_Begin, KP_5 ] }; + key { [ KP_Right, KP_6 ] }; + key { + type= "CTRL+ALT", + symbols[1]= [ KP_Add, KP_Add, KP_Add, KP_Add, XF86Next_VMode ] + }; + key { [ KP_End, KP_1 ] }; + key { [ KP_Down, KP_2 ] }; + key { [ KP_Next, KP_3 ] }; + key { [ KP_Insert, KP_0 ] }; + key { [ KP_Delete, KP_Decimal ] }; + key { [ ISO_Level3_Shift ] }; + key { + type= "PC_CONTROL_SUPER_LEVEL2", + symbols[1]= [ F24, XF86TouchpadToggle ] + }; + key { [ less, greater, bar, brokenbar ] }; + key { + type= "CTRL+ALT", + symbols[1]= [ F11, F11, F11, F11, XF86Switch_VT_11 ] + }; + key { + type= "CTRL+ALT", + symbols[1]= [ F12, F12, F12, F12, XF86Switch_VT_12 ] + }; + key { [ Katakana ] }; + key { [ Hiragana ] }; + key { [ Henkan_Mode ] }; + key { [ Hiragana_Katakana ] }; + key { [ Muhenkan ] }; + key { [ KP_Enter ] }; + key { [ Control_R ] }; + key { + type= "CTRL+ALT", + symbols[1]= [ KP_Divide, KP_Divide, KP_Divide, KP_Divide, XF86Ungrab ] + }; + key { + type= "PC_ALT_LEVEL2", + symbols[1]= [ Print, Sys_Req ] + }; + key { [ Alt_R ] }; + key { [ Linefeed ] }; + key { [ Home ] }; + key { [ Up ] }; + key { [ Prior ] }; + key { [ Left ] }; + key { [ Right ] }; + key { [ End ] }; + key { [ Down ] }; + key { [ Next ] }; + key { [ Insert ] }; + key { [ Delete ] }; + key { [ XF86AudioMute ] }; + key { [ XF86AudioLowerVolume ] }; + key { [ XF86AudioRaiseVolume ] }; + key { [ XF86PowerOff ] }; + key { [ KP_Equal ] }; + key { [ plusminus ] }; + key { + type= "PC_CONTROL_LEVEL2", + symbols[1]= [ Pause, Break ] + }; + key { [ XF86LaunchA ] }; + key { [ KP_Decimal, KP_Decimal ] }; + key { [ Hangul ] }; + key { [ Hangul_Hanja ] }; + key { [ Super_L ] }; + key { [ Super_R ] }; + key { [ Menu ] }; + key { [ Cancel ] }; + key { [ Redo ] }; + key { [ SunProps ] }; + key { [ Undo ] }; + key { [ SunFront ] }; + key { [ XF86Copy ] }; + key { [ XF86Open ] }; + key { [ XF86Paste ] }; + key { [ Find ] }; + key { [ XF86Cut ] }; + key { [ Help ] }; + key { [ XF86MenuKB ] }; + key { [ XF86Calculator ] }; + key { [ XF86Sleep ] }; + key { [ XF86WakeUp ] }; + key { [ XF86Explorer ] }; + key { [ XF86Send ] }; + key { [ XF86Xfer ] }; + key { [ XF86Launch1 ] }; + key { [ XF86Launch2 ] }; + key { [ XF86WWW ] }; + key { [ XF86DOS ] }; + key { [ XF86ScreenSaver ] }; + key { [ XF86RotateWindows ] }; + key { [ XF86TaskPane ] }; + key { [ XF86Mail ] }; + key { [ XF86Favorites ] }; + key { [ XF86MyComputer ] }; + key { [ XF86Back ] }; + key { [ XF86Forward ] }; + key { [ XF86Eject ] }; + key { [ XF86Eject ] }; + key { [ XF86AudioNext ] }; + key { [ XF86AudioPlay, XF86AudioPause ] }; + key { [ XF86AudioPrev ] }; + key { [ XF86AudioStop, XF86Eject ] }; + key { [ XF86AudioRecord ] }; + key { [ XF86AudioRewind ] }; + key { [ XF86Phone ] }; + key { [ XF86Tools ] }; + key { [ XF86HomePage ] }; + key { [ XF86Reload ] }; + key { [ XF86Close ] }; + key { [ XF86ScrollUp ] }; + key { [ XF86ScrollDown ] }; + key { [ parenleft ] }; + key { [ parenright ] }; + key { [ XF86New ] }; + key { [ Redo ] }; + key { [ XF86Tools ] }; + key { [ XF86Launch5 ] }; + key { [ XF86Launch6 ] }; + key { [ XF86Launch7 ] }; + key { [ XF86Launch8 ] }; + key { [ XF86Launch9 ] }; + key { [ F19 ] }; + key { [ XF86AudioMicMute ] }; + key { [ XF86TouchpadToggle ] }; + key { [ XF86TouchpadOn ] }; + key { + type= "PC_SHIFT_SUPER_LEVEL2", + symbols[1]= [ F23, XF86Assistant ] + }; + key { + type= "PC_CONTROL_SUPER_LEVEL2", + symbols[1]= [ F24, XF86TouchpadToggle ] + }; + key { [ ISO_Level5_Shift ] }; + key { [ NoSymbol, Alt_L ] }; + key { [ NoSymbol, Meta_L ] }; + key { [ NoSymbol, Super_L ] }; + key { [ NoSymbol, Hyper_L ] }; + key { [ XF86AudioPlay ] }; + key { [ XF86AudioPause ] }; + key { [ XF86Launch3 ] }; + key { [ XF86Launch4 ] }; + key { [ XF86LaunchB ] }; + key { [ XF86Suspend ] }; + key { [ XF86Close ] }; + key { [ XF86AudioPlay ] }; + key { [ XF86AudioForward ] }; + key { [ XF86AudioBassBoost ] }; + key { [ Print ] }; + key { [ XF86WebCam ] }; + key { [ XF86AudioPreset ] }; + key { [ XF86Mail ] }; + key { [ XF86Messenger ] }; + key { [ XF86Search ] }; + key { [ XF86Go ] }; + key { [ XF86Finance ] }; + key { [ XF86Game ] }; + key { [ XF86Shop ] }; + key { [ Cancel ] }; + key { [ XF86MonBrightnessDown ] }; + key { [ XF86MonBrightnessUp ] }; + key { [ XF86AudioMedia ] }; + key { [ XF86Display ] }; + key { [ XF86KbdLightOnOff ] }; + key { [ XF86KbdBrightnessDown ] }; + key { [ XF86KbdBrightnessUp ] }; + key { [ XF86Send ] }; + key { [ XF86Reply ] }; + key { [ XF86MailForward ] }; + key { [ XF86Save ] }; + key { [ XF86Documents ] }; + key { [ XF86Battery ] }; + key { [ XF86Bluetooth ] }; + key { [ XF86WLAN ] }; + key { [ XF86UWB ] }; + key { [ XF86Next_VMode ] }; + key { [ XF86Prev_VMode ] }; + key { [ XF86MonBrightnessCycle ] }; + key { [ XF86BrightnessAuto ] }; + key { [ XF86DisplayOff ] }; + key { [ XF86WWAN ] }; + key { [ XF86RFKill ] }; + key { [ XF86AudioMicMute ] }; + key { [ XF86OK ] }; + key { [ XF86Select ] }; + key { [ XF86GoTo ] }; + key { [ XF86Clear ] }; + key { [ XF86Option ] }; + key { [ XF86Info ] }; + key { [ XF86Time ] }; + key { [ XF86VendorLogo ] }; + key { [ XF86MediaSelectProgramGuide ] }; + key { [ XF86NextFavorite ] }; + key { [ XF86Favorites ] }; + key { [ XF86MediaSelectProgramGuide ] }; + key { [ XF86MediaSelectHome ] }; + key { [ XF86MediaLanguageMenu ] }; + key { [ XF86MediaTitleMenu ] }; + key { [ XF86Subtitle ] }; + key { [ XF86CycleAngle ] }; + key { [ XF86FullScreen ] }; + key { [ XF86AudioChannelMode ] }; + key { [ XF86Keyboard ] }; + key { [ XF86AspectRatio ] }; + key { [ XF86MediaSelectPC ] }; + key { [ XF86MediaSelectTV ] }; + key { [ XF86MediaSelectCable ] }; + key { [ XF86MediaSelectVCR ] }; + key { [ XF86MediaSelectVCRPlus ] }; + key { [ XF86MediaSelectSatellite ] }; + key { [ XF86CD ] }; + key { [ XF86MediaSelectTape ] }; + key { [ XF86MediaSelectRadio ] }; + key { [ XF86MediaSelectTuner ] }; + key { [ XF86MediaPlayer ] }; + key { [ XF86MediaSelectTeletext ] }; + key { [ XF86DVD ] }; + key { [ XF86MediaSelectAuxiliary ] }; + key { [ XF86Audio ] }; + key { [ XF86Video ] }; + key { [ XF86Memo ] }; + key { [ XF86Calendar ] }; + key { [ XF86Red ] }; + key { [ XF86Green ] }; + key { [ XF86Yellow ] }; + key { [ XF86Blue ] }; + key { [ XF86ChannelUp ] }; + key { [ XF86ChannelDown ] }; + key { [ XF86MediaPlaySlow ] }; + key { [ XF86AudioRandomPlay ] }; + key { [ XF86Break ] }; + key { [ XF86NumberEntryMode ] }; + key { [ XF86VideoPhone ] }; + key { [ XF86Game ] }; + key { [ XF86ZoomIn ] }; + key { [ XF86ZoomOut ] }; + key { [ XF86ZoomReset ] }; + key { [ XF86Word ] }; + key { [ XF86Editor ] }; + key { [ XF86Excel ] }; + key { [ XF86GraphicsEditor ] }; + key { [ XF86Presentation ] }; + key { [ XF86Database ] }; + key { [ XF86News ] }; + key { [ XF86Voicemail ] }; + key { [ XF86Addressbook ] }; + key { [ XF86Messenger ] }; + key { [ XF86DisplayToggle ] }; + key { [ XF86SpellCheck ] }; + key { [ XF86LogOff ] }; + key { [ dollar ] }; + key { [ EuroSign ] }; + key { [ XF86FrameBack ] }; + key { [ XF86FrameForward ] }; + key { [ XF86ContextMenu ] }; + key { [ XF86MediaRepeat ] }; + key { [ XF8610ChannelsUp ] }; + key { [ XF8610ChannelsDown ] }; + key { [ XF86Images ] }; + key { [ XF86NotificationCenter ] }; + key { [ XF86PickupPhone ] }; + key { [ XF86HangupPhone ] }; + key { [ XF86LinkPhone ] }; + key { [ XF86Fn ] }; + key { [ XF86Fn_Esc ] }; + key { [ XF86Fn_F1 ] }; + key { [ XF86Fn_F2 ] }; + key { [ XF86Fn_F3 ] }; + key { [ XF86Fn_F4 ] }; + key { [ XF86Fn_F5 ] }; + key { [ XF86Fn_F6 ] }; + key { [ XF86Fn_F7 ] }; + key { [ XF86Fn_F8 ] }; + key { [ XF86Fn_F9 ] }; + key { [ XF86Fn_F10 ] }; + key { [ XF86Fn_F11 ] }; + key { [ XF86Fn_F12 ] }; + key { [ XF86Fn_1 ] }; + key { [ XF86Fn_2 ] }; + key { [ XF86Fn_D ] }; + key { [ XF86Fn_E ] }; + key { [ XF86Fn_F ] }; + key { [ XF86Fn_S ] }; + key { [ XF86Fn_B ] }; + key { [ XF86FnRightShift ] }; + key { [ braille_dot_1 ] }; + key { [ braille_dot_2 ] }; + key { [ braille_dot_3 ] }; + key { [ braille_dot_4 ] }; + key { [ braille_dot_5 ] }; + key { [ braille_dot_6 ] }; + key { [ braille_dot_7 ] }; + key { [ braille_dot_8 ] }; + key { [ braille_dot_9 ] }; + key { [ braille_dot_1 ] }; + key { [ XF86Numeric0 ] }; + key { [ XF86Numeric1 ] }; + key { [ XF86Numeric2 ] }; + key { [ XF86Numeric3 ] }; + key { [ XF86Numeric4 ] }; + key { [ XF86Numeric5 ] }; + key { [ XF86Numeric6 ] }; + key { [ XF86Numeric7 ] }; + key { [ XF86Numeric8 ] }; + key { [ XF86Numeric9 ] }; + key { [ XF86NumericStar ] }; + key { [ XF86NumericPound ] }; + key { [ XF86NumericA ] }; + key { [ XF86NumericB ] }; + key { [ XF86NumericC ] }; + key { [ XF86NumericD ] }; + key { [ XF86CameraFocus ] }; + key { [ XF86WPSButton ] }; + key { [ XF86TouchpadToggle ] }; + key { [ XF86TouchpadOn ] }; + key { [ XF86TouchpadOff ] }; + key { [ XF86CameraZoomIn ] }; + key { [ XF86CameraZoomOut ] }; + key { [ XF86CameraUp ] }; + key { [ XF86CameraDown ] }; + key { [ XF86CameraLeft ] }; + key { [ XF86CameraRight ] }; + key { [ XF86AttendantOn ] }; + key { [ XF86AttendantOff ] }; + key { [ XF86AttendantToggle ] }; + key { [ XF86LightsToggle ] }; + key { [ XF86ALSToggle ] }; + key { [ XF86RotationLockToggle ] }; + key { [ XF86RefreshRateToggle ] }; + key { [ XF86Buttonconfig ] }; + key { [ XF86Taskmanager ] }; + key { [ XF86Journal ] }; + key { [ XF86ControlPanel ] }; + key { [ XF86AppSelect ] }; + key { [ XF86Screensaver ] }; + key { [ XF86VoiceCommand ] }; + key { [ XF86Assistant ] }; + key { [ ISO_Next_Group ] }; + key { [ XF86EmojiPicker ] }; + key { [ XF86Dictate ] }; + key { [ XF86CameraAccessEnable ] }; + key { [ XF86CameraAccessDisable ] }; + key { [ XF86CameraAccessToggle ] }; + key { [ XF86Accessibility ] }; + key { [ XF86DoNotDisturb ] }; + key { [ XF86BrightnessMin ] }; + key { [ XF86BrightnessMax ] }; + key { [ XF86KbdInputAssistPrev ] }; + key { [ XF86KbdInputAssistNext ] }; + key { [ XF86KbdInputAssistPrevgroup ] }; + key { [ XF86KbdInputAssistNextgroup ] }; + key { [ XF86KbdInputAssistAccept ] }; + key { [ XF86KbdInputAssistCancel ] }; + key { [ XF86RightUp ] }; + key { [ XF86RightDown ] }; + key { [ XF86LeftUp ] }; + key { [ XF86LeftDown ] }; + key { [ XF86RootMenu ] }; + key { [ XF86MediaTopMenu ] }; + key { [ XF86Numeric11 ] }; + key { [ XF86Numeric12 ] }; + key { [ XF86AudioDesc ] }; + key { [ XF863DMode ] }; + key { [ XF86NextFavorite ] }; + key { [ XF86StopRecord ] }; + key { [ XF86PauseRecord ] }; + key { [ XF86VOD ] }; + key { [ XF86Unmute ] }; + key { [ XF86FastReverse ] }; + key { [ XF86SlowReverse ] }; + key { [ XF86Data ] }; + key { [ XF86OnScreenKeyboard ] }; + key { [ XF86PrivacyScreenToggle ] }; + key { [ XF86SelectiveScreenshot ] }; + key { [ XF86NextElement ] }; + key { [ XF86PreviousElement ] }; + key { [ XF86AutopilotEngageToggle ] }; + key { [ XF86MarkWaypoint ] }; + key { [ XF86Sos ] }; + key { [ XF86NavChart ] }; + key { [ XF86FishingChart ] }; + key { [ XF86SingleRangeRadar ] }; + key { [ XF86DualRangeRadar ] }; + key { [ XF86RadarOverlay ] }; + key { [ XF86TraditionalSonar ] }; + key { [ XF86ClearvuSonar ] }; + key { [ XF86SidevuSonar ] }; + key { [ XF86NavInfo ] }; + key { [ XF86BrightnessAdjust ] }; + key { [ XF86Macro1 ] }; + key { [ XF86Macro2 ] }; + key { [ XF86Macro3 ] }; + key { [ XF86Macro4 ] }; + key { [ XF86Macro5 ] }; + key { [ XF86Macro6 ] }; + key { [ XF86Macro7 ] }; + key { [ XF86Macro8 ] }; + key { [ XF86Macro9 ] }; + key { [ XF86Macro10 ] }; + key { [ XF86Macro11 ] }; + key { [ XF86Macro12 ] }; + key { [ XF86Macro13 ] }; + key { [ XF86Macro14 ] }; + key { [ XF86Macro15 ] }; + key { [ XF86Macro16 ] }; + key { [ XF86Macro17 ] }; + key { [ XF86Macro18 ] }; + key { [ XF86Macro19 ] }; + key { [ XF86Macro20 ] }; + key { [ XF86Macro21 ] }; + key { [ XF86Macro22 ] }; + key { [ XF86Macro23 ] }; + key { [ XF86Macro24 ] }; + key { [ XF86Macro25 ] }; + key { [ XF86Macro26 ] }; + key { [ XF86Macro27 ] }; + key { [ XF86Macro28 ] }; + key { [ XF86Macro29 ] }; + key { [ XF86Macro30 ] }; + key { [ XF86MacroRecordStart ] }; + key { [ XF86MacroRecordStop ] }; + key { [ XF86MacroPresetCycle ] }; + key { [ XF86MacroPreset1 ] }; + key { [ XF86MacroPreset2 ] }; + key { [ XF86MacroPreset3 ] }; + key { [ XF86KbdLcdMenu1 ] }; + key { [ XF86KbdLcdMenu2 ] }; + key { [ XF86KbdLcdMenu3 ] }; + key { [ XF86KbdLcdMenu4 ] }; + key { [ XF86KbdLcdMenu5 ] }; + key { [ XF86PerformanceMode ] }; + modifier_map Shift { , }; + modifier_map Lock { }; + modifier_map Control { , }; + modifier_map Mod1 { , , , }; + modifier_map Mod2 { }; + modifier_map Mod3 { }; + modifier_map Mod4 { , , }; + modifier_map Mod5 { }; +}; + +}; diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 9fbb004ff..c2c6c404b 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -77,6 +77,7 @@ endif() set(SHADER_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/winlator/vk/shaders") set(SHADER_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders") +set(WINLATOR_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}") file(MAKE_DIRECTORY "${SHADER_OUT_DIR}") set(BIN2C_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/winlator/vk/bin2c.cmake") @@ -230,6 +231,13 @@ add_library(waudio SHARED target_compile_options(waudio PRIVATE -Wall -Wextra -fvisibility=hidden) target_link_libraries(waudio log dl) +# The app's end of DirectAudio in a Linux session, see wnaudiohook/wn_aaudio_protocol.h. +add_library(wnaudiohost SHARED + wnaudiohook/wn_aaudio_host.c +) +target_compile_options(wnaudiohost PRIVATE -Wall -Wextra -fvisibility=hidden) +target_link_libraries(wnaudiohost aaudio android log) + find_package(SDL3 REQUIRED CONFIG) add_library(steamctrl SHARED @@ -237,3 +245,7 @@ add_library(steamctrl SHARED ) target_compile_options(steamctrl PRIVATE -Wall -Wextra -fvisibility=hidden) target_link_libraries(steamctrl SDL3::SDL3 log) + +# Embedded Wayland compositor (winewayland.drv sessions); see waylandcomp/CMakeLists.txt. +add_subdirectory(waylandcomp) +add_subdirectory(proot) diff --git a/app/src/main/cpp/proot/CMakeLists.txt b/app/src/main/cpp/proot/CMakeLists.txt index a0ce450b8..b5c8a7aa9 100644 --- a/app/src/main/cpp/proot/CMakeLists.txt +++ b/app/src/main/cpp/proot/CMakeLists.txt @@ -44,5 +44,10 @@ add_executable(libproot.so target_link_libraries(libproot.so talloc) -add_library(proot-loader SHARED - src/loader/loader.c) \ No newline at end of file +# The loader is a freestanding flat binary proot execs at LOADER_ADDRESS in place of every +# program; it is named like a library so the installer places it beside libproot.so. +add_executable(libproot-loader.so + src/loader/loader.c) +target_compile_options(libproot-loader.so PRIVATE -ffreestanding -fno-stack-protector -fno-pic -fno-pie) +target_link_options(libproot-loader.so PRIVATE -static -nostdlib -no-pie -Wl,-e,_start + -Wl,--image-base=0x2000000000 -Wl,-z,noexecstack) \ No newline at end of file diff --git a/app/src/main/cpp/proot/src/compat.h b/app/src/main/cpp/proot/src/compat.h index 9a99a5a88..3828a62d4 100644 --- a/app/src/main/cpp/proot/src/compat.h +++ b/app/src/main/cpp/proot/src/compat.h @@ -132,6 +132,11 @@ #ifndef PTRACE_O_TRACEEXIT #define PTRACE_O_TRACEEXIT 0x00000040 #endif +/* A tracee that outlives the tracer keeps the seccomp filter with nobody to answer + * it: every filtered syscall fails with ENOSYS and the process spins for good. */ +#ifndef PTRACE_O_EXITKILL +#define PTRACE_O_EXITKILL 0x00100000 +#endif #ifndef PTRACE_O_TRACESECCOMP #define PTRACE_O_TRACESECCOMP 0x00000080 #endif diff --git a/app/src/main/cpp/proot/src/execve/enter.c b/app/src/main/cpp/proot/src/execve/enter.c index 06d178901..6d1217528 100644 --- a/app/src/main/cpp/proot/src/execve/enter.c +++ b/app/src/main/cpp/proot/src/execve/enter.c @@ -22,6 +22,7 @@ #include /* assert(3), */ #include /* E*, */ +#include /* open(2), */ #include /* fwrite(3), */ #include /* getenv(3), */ #include /* strlen(3), strcpy(3), */ @@ -40,7 +41,13 @@ #include "path/temp.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" + +#ifndef AT_EMPTY_PATH +#define AT_EMPTY_PATH 0x1000 +#endif #include "tracee/tracee.h" +#include "tracee/mem.h" +#include "tracee/reg.h" #define P(a) PROGRAM_FIELD(load_info->elf_header, *program_header, a) @@ -125,6 +132,27 @@ static int add_mapping(const Tracee *tracee UNUSED, LoadInfo *load_info, * executable and is a regular file. This function returns -errno if * an error occured, 0 otherwise. */ +/** + * A tracee executing "/proc/self/fd/N" expects the kernel to open the + * descriptor before the exec closes it; the loader runs after, so hand + * it the descriptor's target instead. Returns -errno on error. + */ +static int resolve_proc_fd(pid_t pid, char host_path[PATH_MAX]) { + char prefix[32]; + char *end; + long fd; + int length; + + length = snprintf(prefix, sizeof(prefix), "/proc/%d/fd/", pid); + if (length < 0 || strncmp(host_path, prefix, length) != 0) + return 0; + errno = 0; + fd = strtol(host_path + length, &end, 10); + if (errno != 0 || end == host_path + length || *end != '\0') + return 0; + return readlink_proc_pid_fd(pid, fd, host_path); +} + int translate_and_check_exec(Tracee *tracee, char host_path[PATH_MAX], const char *user_path) { struct stat statl; @@ -137,6 +165,10 @@ int translate_and_check_exec(Tracee *tracee, char host_path[PATH_MAX], if (status < 0) return status; + status = resolve_proc_fd(tracee->pid, host_path); + if (status < 0) + return status; + status = access(host_path, F_OK); if (status < 0) return -ENOENT; @@ -348,17 +380,168 @@ static inline const char *get_loader_path(const Tracee *tracee) { return loader_path; } +#define SHEBANG_MAX 256 + +/** + * Read the "#!" line of @host_path into @interp and @argument (the rest of the + * line, one argument as the kernel passes it). Returns 1 for a script, 0 for + * anything else, -errno on error. + */ +static int extract_shebang(const char *host_path, char interp[PATH_MAX], + char argument[SHEBANG_MAX]) { + char line[SHEBANG_MAX]; + char *start, *end, *tail; + ssize_t size; + int fd; + + fd = open(host_path, O_RDONLY); + if (fd < 0) + return -errno; + size = read(fd, line, sizeof(line) - 1); + close(fd); + if (size < 2 || line[0] != '#' || line[1] != '!') + return 0; + line[size] = '\0'; + end = strchr(line, '\n'); + if (end != NULL) + *end = '\0'; + + start = line + 2; + while (*start == ' ' || *start == '\t') + start++; + end = start; + while (*end != '\0' && *end != ' ' && *end != '\t') + end++; + if (end == start || end - start >= PATH_MAX) + return -ENOEXEC; + memcpy(interp, start, end - start); + interp[end - start] = '\0'; + + while (*end == ' ' || *end == '\t') + end++; + tail = end + strlen(end); + while (tail > end && (tail[-1] == ' ' || tail[-1] == '\t' || tail[-1] == '\r')) + tail--; + *tail = '\0'; + strcpy(argument, end); + return 1; +} + +static word_t push_string(Tracee *tracee, const char *string) { + size_t size = strlen(string) + 1; + word_t address = alloc_mem(tracee, size); + if (address == 0 || write_data(tracee, address, string, size) < 0) + return 0; + return address; +} + +/** + * Replace argv[0] of the current execve with "@interp [@argument] @file", as the + * kernel does for a script. Returns -errno on error, 0 otherwise. + */ +static int prepend_shebang_argv(Tracee *tracee, const char *interp, + const char *argument, const char *file) { + const size_t max_args = 1 << 16; + word_t old_argv = peek_reg(tracee, CURRENT, SYSARG_2); + word_t *argv; + size_t count = 0, head = 0, i; + word_t address; + + argv = talloc_array(tracee->ctx, word_t, 4); + if (argv == NULL) + return -ENOMEM; + argv[head++] = push_string(tracee, interp); + if (argument[0] != '\0') + argv[head++] = push_string(tracee, argument); + argv[head++] = push_string(tracee, file); + for (i = 0; i < head; i++) + if (argv[i] == 0) + return -EFAULT; + count = head; + + /* The caller's argv[0] names the script and is dropped; the rest follows. */ + if (old_argv != 0) { + for (i = 1;; i++) { + word_t item; + if (i > max_args) + return -E2BIG; + if (read_data(tracee, &item, old_argv + i * sizeof(word_t), sizeof(word_t)) < 0) + return -EFAULT; + if (item == 0) + break; + argv = talloc_realloc(tracee->ctx, argv, word_t, count + 2); + if (argv == NULL) + return -ENOMEM; + argv[count++] = item; + } + } + argv = talloc_realloc(tracee->ctx, argv, word_t, count + 1); + if (argv == NULL) + return -ENOMEM; + argv[count] = 0; + + address = alloc_mem(tracee, (count + 1) * sizeof(word_t)); + if (address == 0) + return -EFAULT; + if (write_data(tracee, address, argv, (count + 1) * sizeof(word_t)) < 0) + return -EFAULT; + poke_reg(tracee, SYSARG_2, address); + return 0; +} + /** * Extract all the information that will be required by * translate_load_*(). This function returns -errno if an error * occured, otherwise 0. */ +/** + * Rewrite an execveat(2) into the execve(2) the rest of this file understands: + * the target becomes SYSARG_1 (named through /proc/self/fd for an fd-relative + * call, which resolve_proc_fd() then turns into the real path), and argv/envp + * shift down from SYSARG_3/4 to SYSARG_2/3. Returns -errno on error. + */ +static int normalize_execveat_enter(Tracee *tracee) { + char path[PATH_MAX]; + char rewritten[PATH_MAX]; + word_t flags; + int dirfd; + int status; + + dirfd = (int)peek_reg(tracee, CURRENT, SYSARG_1); + flags = peek_reg(tracee, CURRENT, SYSARG_5); + + status = get_sysarg_path(tracee, path, SYSARG_2); + if (status < 0) + return status; + + if (path[0] == '/') { + status = snprintf(rewritten, sizeof(rewritten), "%s", path); + } else if (path[0] == '\0' && (flags & AT_EMPTY_PATH) != 0) { + status = snprintf(rewritten, sizeof(rewritten), "/proc/self/fd/%d", dirfd); + } else if (dirfd == AT_FDCWD) { + status = snprintf(rewritten, sizeof(rewritten), "%s", path); + } else { + status = snprintf(rewritten, sizeof(rewritten), "/proc/self/fd/%d/%s", dirfd, path); + } + if (status < 0 || (size_t)status >= sizeof(rewritten)) + return -ENAMETOOLONG; + + status = set_sysarg_path(tracee, rewritten, SYSARG_1); + if (status < 0) + return status; + poke_reg(tracee, SYSARG_2, peek_reg(tracee, CURRENT, SYSARG_3)); + poke_reg(tracee, SYSARG_3, peek_reg(tracee, CURRENT, SYSARG_4)); + set_sysnum(tracee, PR_execve); + return 0; +} + int translate_execve_enter(Tracee *tracee) { char user_path[PATH_MAX]; char host_path[PATH_MAX]; char new_exe[PATH_MAX]; const char *loader_path; int status; + int depth; if (IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee)) { /* Syscalls can now be reported to its ptracer. */ @@ -370,6 +553,12 @@ int translate_execve_enter(Tracee *tracee) { return 0; } + if (get_sysnum(tracee, ORIGINAL) == PR_execveat) { + status = normalize_execveat_enter(tracee); + if (status < 0) + return status; + } + status = get_sysarg_path(tracee, user_path, SYSARG_1); if (status < 0) return status; @@ -379,6 +568,29 @@ int translate_execve_enter(Tracee *tracee) { if (status < 0) return status; + /* A script runs its interpreter; the kernel allows four levels of them. */ + for (depth = 0;; depth++) { + char interp[PATH_MAX]; + char argument[SHEBANG_MAX]; + + status = extract_shebang(host_path, interp, argument); + if (status < 0) + return status; + if (status == 0) + break; + if (depth == 4) + return -ELOOP; + + status = prepend_shebang_argv(tracee, interp, argument, user_path); + if (status < 0) + return status; + + strcpy(user_path, interp); + status = translate_and_check_exec(tracee, host_path, user_path); + if (status < 0) + return status; + } + strcpy(new_exe, host_path); status = detranslate_path(tracee, new_exe, NULL); if (status >= 0) { diff --git a/app/src/main/cpp/proot/src/syscall/enter.c b/app/src/main/cpp/proot/src/syscall/enter.c index 0fee07587..c527c3119 100644 --- a/app/src/main/cpp/proot/src/syscall/enter.c +++ b/app/src/main/cpp/proot/src/syscall/enter.c @@ -28,7 +28,8 @@ #include /* PR_SET_DUMPABLE */ #include /* struct sockaddr_un, */ #include /* talloc_*, */ -#include /* TCSETS, TCSANOW */ +#include /* TCGETS*, TCSETS*, */ +#include /* struct termios2, */ #include "arch.h" #include "execve/execve.h" @@ -109,6 +110,7 @@ int translate_syscall_enter(Tracee *tracee) { syscall_number = get_sysnum(tracee, ORIGINAL); switch (syscall_number) { case PR_execve: + case PR_execveat: status = translate_execve_enter(tracee); break; @@ -126,6 +128,30 @@ int translate_syscall_enter(Tracee *tracee) { status = 0; break; + case PR_ioctl: + /* The termios2 requests become the ones Android allows on a + * pty, see add_trace_tty_ioctl(). The kernel reads and writes + * the part the two structures share; exit.c fills in the + * speeds that termios2 adds. */ + switch ((uint32_t)peek_reg(tracee, CURRENT, SYSARG_2)) { + case TCGETS2: + poke_reg(tracee, SYSARG_2, TCGETS); + break; + case TCSETS2: + poke_reg(tracee, SYSARG_2, TCSETS); + break; + case TCSETSW2: + poke_reg(tracee, SYSARG_2, TCSETSW); + break; + case TCSETSF2: + poke_reg(tracee, SYSARG_2, TCSETSF); + break; + default: + break; + } + status = 0; + break; + case PR_getcwd: set_sysnum(tracee, PR_void); status = 0; @@ -299,6 +325,7 @@ int translate_syscall_enter(Tracee *tracee) { case PR_fchownat: case PR_fstatat64: case PR_newfstatat: + case PR_statx: case PR_utimensat: case PR_name_to_handle_at: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); @@ -310,6 +337,8 @@ int translate_syscall_enter(Tracee *tracee) { flags = (syscall_number == PR_fchownat || syscall_number == PR_name_to_handle_at) ? peek_reg(tracee, CURRENT, SYSARG_5) + : syscall_number == PR_statx + ? peek_reg(tracee, CURRENT, SYSARG_3) : peek_reg(tracee, CURRENT, SYSARG_4); if ((flags & AT_SYMLINK_NOFOLLOW) != 0) diff --git a/app/src/main/cpp/proot/src/syscall/exit.c b/app/src/main/cpp/proot/src/syscall/exit.c index 25319287f..bfda3be4b 100644 --- a/app/src/main/cpp/proot/src/syscall/exit.c +++ b/app/src/main/cpp/proot/src/syscall/exit.c @@ -20,6 +20,8 @@ * 02110-1301 USA. */ +#include /* TCGETS2, */ +#include /* struct termios2, CBAUD, */ #include /* errno(3), E* */ #include /* SYS_*, */ #include /* strlen(3), */ @@ -79,6 +81,33 @@ void translate_syscall_exit(Tracee *tracee) { translate_brk_exit(tracee); return; + case PR_ioctl: { + /* TCGETS answered a TCGETS2, see enter.c: the speeds the kernel + * would have reported are those its c_cflag encodes. */ + static const uint32_t speeds[] = { + 0, 50, 75, 110, 134, 150, 200, 300, + 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, + 0, 57600, 115200, 230400, 460800, 500000, 576000, 921600, + 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000}; + struct termios2 settings; + word_t address; + uint32_t baud; + + if ((int)syscall_result < 0 || + (uint32_t)peek_reg(tracee, ORIGINAL, SYSARG_2) != TCGETS2) + return; + + address = peek_reg(tracee, ORIGINAL, SYSARG_3); + if (read_data(tracee, &settings, address, sizeof(settings)) < 0) + return; + + baud = settings.c_cflag & CBAUD; + settings.c_ispeed = settings.c_ospeed = + speeds[(baud & ~CBAUDEX) | (baud & CBAUDEX ? 16 : 0)]; + (void)write_data(tracee, address, &settings, sizeof(settings)); + return; + } + case PR_getcwd: { char path[PATH_MAX]; size_t new_size; @@ -341,6 +370,7 @@ void translate_syscall_exit(Tracee *tracee) { } case PR_execve: + case PR_execveat: translate_execve_exit(tracee); return; diff --git a/app/src/main/cpp/proot/src/syscall/seccomp.c b/app/src/main/cpp/proot/src/syscall/seccomp.c index a9bb7f0f0..6addb4fe7 100644 --- a/app/src/main/cpp/proot/src/syscall/seccomp.c +++ b/app/src/main/cpp/proot/src/syscall/seccomp.c @@ -22,6 +22,8 @@ #include "arch.h" +#include /* TCGETS2, TCSETS*2, */ +#include /* struct termios2, */ #include /* assert(3), */ #include /* E*, */ #include /* AUDIT_, */ @@ -116,13 +118,50 @@ static int add_trace_syscall(struct sock_fprog *program, word_t syscall, } /** - * Append to @program->filter the statements that allow anything (if - * unfiltered). Note that @nb_traced_syscalls is used to make a - * sanity check. This function returns -errno if an error occurred, + * glibc 2.42 and later read and write terminal settings with the + * termios2 requests, which Android's SELinux policy denies an app on + * a pty: it lists the older requests only, so nothing in the guest + * finds a terminal. Append to @program->filter the statements that + * trace these requests of @syscall (ioctl), for enter.c to turn into + * the older ones, and allow any other request at once: nothing else + * handles ioctl, and the GPU driver's are spared the rest of the + * list. This function returns -errno if an error occurred, * otherwise 0. */ +static int add_trace_tty_ioctl(struct sock_fprog *program, word_t syscall) { + const size_t request_offset = offsetof(struct seccomp_data, args[1]); + +#define LENGTH_TRACE_TTY_IOCTL 8 + struct sock_filter statements[LENGTH_TRACE_TTY_IOCTL] = { + /* Not this syscall: skip the block, the accumulator + * still holds the syscall number. */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, syscall, 0, 7), + + /* Load the request's low word into the accumulator. */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, request_offset), + + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, TCGETS2, 3, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, TCSETS2, 2, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, TCSETSW2, 1, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, TCSETSF2, 0, 1), + + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE + FILTER_SYSEXIT), + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)}; + + DEBUG_FILTER("FILTER: trace if syscall == %ld and a termios2 request\n", + syscall); + + return add_statements(program, LENGTH_TRACE_TTY_IOCTL, statements); +} + +/** + * Append to @program->filter the statements that allow anything (if + * unfiltered). Note that @nb_traced_syscalls and @nb_tty_ioctls are + * used to make a sanity check. This function returns -errno if an + * error occurred, otherwise 0. + */ static int end_arch_section(struct sock_fprog *program, - size_t nb_traced_syscalls) { + size_t nb_traced_syscalls, size_t nb_tty_ioctls) { int status; #define LENGTH_END_SECTION 1 @@ -137,7 +176,8 @@ static int end_arch_section(struct sock_fprog *program, /* Sanity check, see start_arch_section(). */ if (talloc_array_length(program->filter) - program->len != - LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL) + LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL + + nb_tty_ioctls * LENGTH_TRACE_TTY_IOCTL) return -ERANGE; return 0; @@ -145,16 +185,17 @@ static int end_arch_section(struct sock_fprog *program, /** * Append to @program->filter the statements that check the current - * @architecture. Note that @nb_traced_syscalls is used to make a - * sanity check. This function returns -errno if an error occurred, - * otherwise 0. + * @architecture. Note that @nb_traced_syscalls and @nb_tty_ioctls + * give the length of the section. This function returns -errno if an + * error occurred, otherwise 0. */ static int start_arch_section(struct sock_fprog *program, uint32_t arch, - size_t nb_traced_syscalls) { + size_t nb_traced_syscalls, size_t nb_tty_ioctls) { const size_t arch_offset = offsetof(struct seccomp_data, arch); const size_t syscall_offset = offsetof(struct seccomp_data, nr); const size_t section_length = - LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL; + LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL + + nb_tty_ioctls * LENGTH_TRACE_TTY_IOCTL; int status; /* Sanity checks. */ @@ -244,6 +285,7 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) { struct sock_fprog program = {.len = 0, .filter = NULL}; size_t nb_traced_syscalls; + size_t nb_tty_ioctls; size_t i, j, k; int status; @@ -256,9 +298,13 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) { word_t syscall; nb_traced_syscalls = 0; + nb_tty_ioctls = 0; /* Pre-compute the number of traced syscalls for this architecture. */ for (j = 0; j < seccomp_archs[i].nb_abis; j++) { + if (detranslate_sysnum(seccomp_archs[i].abis[j], PR_ioctl) != + SYSCALL_AVOIDER) + nb_tty_ioctls++; for (k = 0; sysnums[k].value != PR_void; k++) { syscall = detranslate_sysnum(seccomp_archs[i].abis[j], sysnums[k].value); @@ -269,10 +315,21 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) { /* Filter: if handled architecture */ status = start_arch_section(&program, seccomp_archs[i].value, - nb_traced_syscalls); + nb_traced_syscalls, nb_tty_ioctls); if (status < 0) goto end; + /* Filter: ioctl first, it is by far the most frequent syscall */ + for (j = 0; j < seccomp_archs[i].nb_abis; j++) { + syscall = detranslate_sysnum(seccomp_archs[i].abis[j], PR_ioctl); + if (syscall == SYSCALL_AVOIDER) + continue; + + status = add_trace_tty_ioctl(&program, syscall); + if (status < 0) + goto end; + } + for (j = 0; j < seccomp_archs[i].nb_abis; j++) { for (k = 0; sysnums[k].value != PR_void; k++) { /* Get the architecture specific syscall number. */ @@ -289,7 +346,7 @@ static int set_seccomp_filters(const FilteredSysnum *sysnums) { } /* Filter: allow untraced syscalls for this architecture */ - status = end_arch_section(&program, nb_traced_syscalls); + status = end_arch_section(&program, nb_traced_syscalls, nb_tty_ioctls); if (status < 0) goto end; } @@ -330,6 +387,7 @@ static FilteredSysnum proot_sysnums[] = { {PR_chown32, 0}, {PR_connect, 0}, {PR_execve, FILTER_SYSEXIT}, + {PR_execveat, FILTER_SYSEXIT}, {PR_faccessat, 0}, {PR_faccessat2, 0}, {PR_fchdir, FILTER_SYSEXIT}, @@ -359,6 +417,7 @@ static FilteredSysnum proot_sysnums[] = { {PR_mknodat, 0}, {PR_name_to_handle_at, 0}, {PR_newfstatat, 0}, + {PR_statx, 0}, {PR_open, 0}, {PR_openat, 0}, {PR_prctl, 0}, diff --git a/app/src/main/cpp/proot/src/tracee/event.c b/app/src/main/cpp/proot/src/tracee/event.c index 4a16e4848..d5041c3ce 100644 --- a/app/src/main/cpp/proot/src/tracee/event.c +++ b/app/src/main/cpp/proot/src/tracee/event.c @@ -89,7 +89,8 @@ int launch_process(Tracee *tracee, char *const argv[]) { kill(getpid(), SIGSTOP); /* Improve performance by using seccomp mode 2 */ - enable_syscall_filtering(tracee); + if (getenv("PROOT_NO_SECCOMP") == NULL) + enable_syscall_filtering(tracee); /* Now process is ptraced, so the current rootfs is already the * guest rootfs. Note: Valgrind can't handle execve(2) on @@ -368,7 +369,7 @@ int handle_tracee_event(Tracee *tracee, int tracee_status) { const unsigned long default_ptrace_options = (PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACECLONE | - PTRACE_O_TRACEEXIT); + PTRACE_O_TRACEEXIT | PTRACE_O_EXITKILL); /* Distinguish some events from others and * automatically trace each new process with diff --git a/app/src/main/cpp/proot/src/tracee/mem.c b/app/src/main/cpp/proot/src/tracee/mem.c index bf1922932..ddf849995 100644 --- a/app/src/main/cpp/proot/src/tracee/mem.c +++ b/app/src/main/cpp/proot/src/tracee/mem.c @@ -20,6 +20,7 @@ * 02110-1301 USA. */ +#define _GNU_SOURCE /* process_vm_*, */ #include /* assert(3), */ #include /* errno, */ #include /* offsetof(), */ @@ -40,6 +41,23 @@ #include "tracee/abi.h" #include "tracee/mem.h" +/** + * The @address without the tag an arm64 pointer may carry in its top byte. + * + * Android's allocator tags heap pointers, and the first program is started by + * PRoot's own child, from its heap: execve(2) arrives with 0xb4... addresses + * for the path and the environment. The kernel takes those from the tracee, + * but on some kernels not from its tracer - PEEKDATA answers EIO - and nothing + * then starts. Guest addresses are never tagged, so this costs them nothing. + */ +static inline word_t untagged(word_t address) { +#if defined(__aarch64__) + return address & 0x00FFFFFFFFFFFFFFULL; +#else + return address; +#endif +} + /** * Load the word at the given @address, potentially *not* aligned. */ @@ -72,6 +90,30 @@ static inline void store_word(void *address, word_t value) { #endif } +/* Divides every page size, so no piece of a string crosses a page. */ +#define STRING_PIECE 1024 + +/** + * Copy @size bytes between this process and the @tracee's @address in one system + * call, @to the tracee or from it. A word at a time through ptrace(2) costs a + * system call for every eight bytes, and every path PRoot translates is read + * and written back this way. False when the kernel did not move all of it - + * a page that is not mapped, or not writable as ptrace(2) can force it to be - + * and the caller then goes the slow way, which also reports the error. + */ +static bool copy_at_once(const Tracee *tracee, void *local_address, word_t address, + word_t size, bool to) { + struct iovec local = {.iov_base = local_address, .iov_len = size}; + struct iovec remote = {.iov_base = (void *)address, .iov_len = size}; + ssize_t moved; + + if (size == 0) + return true; + moved = to ? process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0) + : process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); + return moved >= 0 && (word_t)moved == size; +} + /** * Copy @size bytes from the buffer @src_tracer to the address * @dest_tracee within the memory space of the @tracee process. It @@ -80,7 +122,7 @@ static inline void store_word(void *address, word_t value) { int write_data(Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size) { word_t *src = (word_t *)src_tracer; - word_t *dest = (word_t *)dest_tracee; + word_t *dest = (word_t *)untagged(dest_tracee); long status; word_t word, i, j; @@ -90,6 +132,9 @@ int write_data(Tracee *tracee, word_t dest_tracee, const void *src_tracer, uint8_t *last_dest_word; uint8_t *last_src_word; + if (copy_at_once(tracee, src, (word_t)dest, size, true)) + return 0; + nb_trailing_bytes = size % sizeof(word_t); nb_full_words = (size - nb_trailing_bytes) / sizeof(word_t); @@ -167,7 +212,7 @@ int writev_data(Tracee *tracee, word_t dest_tracee, */ int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, word_t size) { - word_t *src = (word_t *)src_tracee; + word_t *src = (word_t *)untagged(src_tracee); word_t *dest = (word_t *)dest_tracer; word_t nb_trailing_bytes; @@ -177,6 +222,9 @@ int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, uint8_t *last_src_word; uint8_t *last_dest_word; + if (copy_at_once(tracee, dest, (word_t)src, size, false)) + return 0; + nb_trailing_bytes = size % sizeof(word_t); nb_full_words = (size - nb_trailing_bytes) / sizeof(word_t); @@ -223,7 +271,7 @@ int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, */ int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t max_size) { - word_t *src = (word_t *)src_tracee; + word_t *src = (word_t *)untagged(src_tracee); word_t *dest = (word_t *)dest_tracer; word_t nb_trailing_bytes; @@ -233,6 +281,25 @@ int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, uint8_t *src_word; uint8_t *dest_word; + /* A transfer stops at the first page that is not mapped and moves nothing of + * the piece that reaches it, and a string's length is not known, so it is + * read in pieces that each end where a page might. */ + word_t offset = 0; + while (offset < max_size) { + word_t at = (word_t)src + offset; + word_t piece = STRING_PIECE - at % STRING_PIECE; + char *end; + + if (piece > max_size - offset) + piece = max_size - offset; + if (!copy_at_once(tracee, dest_tracer + offset, at, piece, false)) + break; + end = memchr(dest_tracer + offset, '\0', piece); + if (end != NULL) + return end - dest_tracer + 1; + offset += piece; + } + nb_trailing_bytes = max_size % sizeof(word_t); nb_full_words = (max_size - nb_trailing_bytes) / sizeof(word_t); @@ -281,6 +348,8 @@ int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t peek_word(const Tracee *tracee, word_t address) { word_t result = 0; + address = untagged(address); + errno = 0; result = (word_t)ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL); @@ -306,6 +375,8 @@ word_t peek_word(const Tracee *tracee, word_t address) { void poke_word(const Tracee *tracee, word_t address, word_t value) { word_t tmp; + address = untagged(address); + /* Don't overwrite the 32 MSB when running a 32-bit process on * a 64-bit kernel. */ if (is_32on64_mode(tracee)) { diff --git a/app/src/main/cpp/proot/src/tracee/seccomp.c b/app/src/main/cpp/proot/src/tracee/seccomp.c index 69de206c7..34bbdcfad 100644 --- a/app/src/main/cpp/proot/src/tracee/seccomp.c +++ b/app/src/main/cpp/proot/src/tracee/seccomp.c @@ -2,6 +2,7 @@ #include /* E*, */ #include /* SYS_SENDMMSG */ #include /* SIGSYS, */ +#include /* bool, */ #include /* memset */ #include /* statfs64 */ #include /* time(2), */ @@ -472,6 +473,50 @@ static int handle_seccomp_event_common(Tracee *tracee) { break; } + case PR_setuid: + case PR_setuid32: + case PR_setgid: + case PR_setgid32: + case PR_setreuid: + case PR_setreuid32: + case PR_setregid: + case PR_setregid32: { + /* Android's app policy traps these. Without privileges a process may only + * take an id it already holds, so the answer is known without the kernel. */ + bool is_uid = (sysnum == PR_setuid || sysnum == PR_setuid32 || + sysnum == PR_setreuid || sysnum == PR_setreuid32); + bool is_re = (sysnum == PR_setreuid || sysnum == PR_setreuid32 || + sysnum == PR_setregid || sysnum == PR_setregid32); + gid_t rxid, exid, sxid; + word_t first = peek_reg(tracee, CURRENT, SYSARG_1); + word_t second = is_re ? peek_reg(tracee, CURRENT, SYSARG_2) : (word_t)-1; + if (is_uid) + ret = getresuid(&rxid, &exid, &sxid); + else + ret = getresgid(&rxid, &exid, &sxid); + if (ret) { + set_result_after_seccomp(tracee, -EPERM); + break; + } + ret = 0; + if ((gid_t)first != (gid_t)-1 && (gid_t)first != rxid && (gid_t)first != exid && (gid_t)first != sxid) + ret = -EPERM; + if ((gid_t)second != (gid_t)-1 && (gid_t)second != rxid && (gid_t)second != exid && (gid_t)second != sxid) + ret = -EPERM; + set_result_after_seccomp(tracee, ret); + break; + } + + case PR_setfsuid: + case PR_setfsuid32: + set_result_after_seccomp(tracee, geteuid()); + break; + + case PR_setfsgid: + case PR_setfsgid32: + set_result_after_seccomp(tracee, getegid()); + break; + case PR_set_robust_list: default: /* Set errno to -ENOSYS */ diff --git a/app/src/main/cpp/waylandcomp/CMakeLists.txt b/app/src/main/cpp/waylandcomp/CMakeLists.txt new file mode 100644 index 000000000..c15792839 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/CMakeLists.txt @@ -0,0 +1,90 @@ +# Embedded Wayland compositor: runs a libwayland-server compositor in-process so games launched +# through Wine's winewayland.drv present into it. See docs/WAYLAND-DISPLAY.md for the design and +# CREDITS.md for attribution. Protocol glue is pre-generated in generated/ so no host +# wayland-scanner is needed at build time. +add_library(wnwayland SHARED + src/compositor.c + src/vk_present.c + src/effects_chain.c + src/vk_loader.c + src/sc_layer.c + src/ahb_swapchain.c + src/waylandcomp_jni.c + src/desktop_ext.c + src/wl_clipboard.c + src/wl_text_input.c + src/wl_toplevel_icon.c + src/wl_color_mgmt.c + src/hdr_compose.c + src/blend_pass.c + src/framegen_bridge.c + src/framegen_jni.c + generated/color-management-v1-protocol.c + generated/xdg-shell-protocol.c + generated/linux-dmabuf-v1-protocol.c + generated/viewporter-protocol.c + generated/desktop-v1-protocol.c + generated/presentation-time-protocol.c + generated/pointer-constraints-unstable-v1-protocol.c + generated/relative-pointer-unstable-v1-protocol.c + generated/wlr-data-control-unstable-v1-protocol.c + generated/text-input-unstable-v3-protocol.c + generated/xdg-toplevel-icon-v1-protocol.c + generated/ahb-v1-protocol.c +) + +target_include_directories(wnwayland PRIVATE + generated + prebuilt/include + src + # Committed SPIR-V of the screen-effect chain (src/effects_chain.c, src/hdr_compose.c). + shaders + ${CMAKE_CURRENT_SOURCE_DIR}/../adrenotools/include +) + +# The X11 renderer's frame-generation engines run here too, on the compositor's own Turnip +# device. Their sources are compiled in rather than linked from libwinlator: both libraries keep +# a private VkDispatch, so the visibility below must stay for the two copies not to merge. +set(FRAMEGEN_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../winlator/vk) +if (TARGET dxbc AND EXISTS ${FRAMEGEN_DIR}/dis/vkr_dis.c) + target_sources(wnwayland PRIVATE + src/framegen_engine.c + ${FRAMEGEN_DIR}/vk_dispatch.c + ${FRAMEGEN_DIR}/dis/vkr_dis.c + ${FRAMEGEN_DIR}/lsfg/lsfg_dll.c + ${FRAMEGEN_DIR}/lsfg/lsfg_dxbc.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_common.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_shaders.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_mipmaps.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_generate.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_alpha.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_beta.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_gamma.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_delta.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_chain.cpp + ${FRAMEGEN_DIR}/lsfg/lsfg_pacer.cpp + ${FRAMEGEN_DIR}/lsfg/vkr_lsfg.cpp + ) + target_include_directories(wnwayland PRIVATE ${FRAMEGEN_DIR} ${WINLATOR_GENERATED_DIR}) + target_compile_definitions(wnwayland PRIVATE VK_USE_PLATFORM_ANDROID_KHR) + target_compile_options(wnwayland PRIVATE -fvisibility=hidden) + target_compile_features(wnwayland PRIVATE cxx_std_17) + target_link_libraries(wnwayland dxbc) + add_dependencies(wnwayland winlator_shaders) +else() + target_sources(wnwayland PRIVATE src/framegen_engine_stub.c) +endif() + +add_library(wayland-server SHARED IMPORTED) +set_target_properties(wayland-server PROPERTIES IMPORTED_LOCATION + ${CMAKE_CURRENT_SOURCE_DIR}/prebuilt/lib/libwayland-server.so) + +# Vulkan is loaded at runtime through adrenotools (Turnip), never linked: the compositor needs the +# driver's dma-buf import path, which the system Adreno driver lacks. +target_link_libraries(wnwayland + wayland-server + adrenotools + dl + log + android +) diff --git a/app/src/main/cpp/waylandcomp/generated/ahb-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/ahb-v1-protocol.c new file mode 100644 index 000000000..08e5d76e0 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/ahb-v1-protocol.c @@ -0,0 +1,69 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright 2026 The412Banner + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_buffer_interface; + +static const struct wl_interface *banner_ahb_v1_types[] = { + NULL, + &wl_buffer_interface, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static const struct wl_message banner_ahb_v1_requests[] = { + { "destroy", "", banner_ahb_v1_types + 0 }, + { "attach", "ohuuuuuu", banner_ahb_v1_types + 1 }, +}; + +static const struct wl_message banner_ahb_v1_events[] = { + { "mode", "2u", banner_ahb_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface banner_ahb_v1_interface = { + "banner_ahb_v1", 2, + 2, banner_ahb_v1_requests, + 1, banner_ahb_v1_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/ahb-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/ahb-v1-server-protocol.h new file mode 100644 index 000000000..3ee1439f4 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/ahb-v1-server-protocol.h @@ -0,0 +1,214 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef WNC_AHB_V1_SERVER_PROTOCOL_H +#define WNC_AHB_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_banner_ahb_v1 The banner_ahb_v1 protocol + * @section page_ifaces_banner_ahb_v1 Interfaces + * - @subpage page_iface_banner_ahb_v1 - Android gralloc buffers behind dma-buf wl_buffers + * @section page_copyright_banner_ahb_v1 Copyright + *
+ *
+ * Copyright 2026 The412Banner
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct banner_ahb_v1; +struct wl_buffer; + +#ifndef WNC_AHB_V1_INTERFACE +#define WNC_AHB_V1_INTERFACE +/** + * @page page_iface_banner_ahb_v1 banner_ahb_v1 + * @section page_iface_banner_ahb_v1_desc Description + * + * Zero-copy window layers on Android. A Vulkan swapchain image that the + * client allocated as an Android AHardwareBuffer (gralloc) is still shared + * as a dma-buf through zwp_linux_dmabuf_v1, so the compositor can import + * and draw it the usual way. This interface additionally hands the + * compositor the AHardwareBuffer itself, once per wl_buffer, so the + * compositor can put the buffer straight on an Android SurfaceControl + * layer (SurfaceFlinger / the display hardware composite it) without + * copying it. + * + * The AHardwareBuffer travels over a Unix socket: the client creates a + * socketpair, writes the buffer's handle into one end with + * AHardwareBuffer_sendHandleToUnixSocket, then sends the other end in the + * attach request. The compositor reads it with + * AHardwareBuffer_recvHandleFromUnixSocket and closes the socket. Its + * reference to the AHardwareBuffer lives as long as the wl_buffer (plus + * whatever the display still holds). + * + * The compositor advertises this global whenever it can put buffers on a + * display layer at all; whether it WANTS gralloc buffers right now is the + * mode event (version 2), sent on bind and every time the user switches + * zero-copy on or off during the session. A client decides per swapchain, + * when it creates the swapchain, from the last mode it received, and a + * client whose live swapchain was built for the other mode retires it + * (VK_ERROR_OUT_OF_DATE_KHR on its next acquire or present) so the program + * rebuilds it for the new mode. Buffers of the old swapchain stay fully + * presentable until then: an attached AHardwareBuffer keeps its association + * for the wl_buffer's lifetime whatever the mode is now. A version 1 + * client (no mode event) keeps deciding from its own environment. A + * client that does not see the global (or does not want it) allocates + * swapchain images the standard way; nothing else changes. + * + * Synchronisation stays implicit: the dma-buf carries the client's render + * fence (DMA_BUF_IOCTL_IMPORT_SYNC_FILE by the client's driver), the + * compositor exports it as the layer's acquire fence, and the compositor + * imports the display's release fence into the dma-buf before it sends + * wl_buffer.release, so the client's next acquire waits on it. + * @section page_iface_banner_ahb_v1_api API + * See @ref iface_banner_ahb_v1. + */ +/** + * @defgroup iface_banner_ahb_v1 The banner_ahb_v1 interface + * + * Zero-copy window layers on Android. A Vulkan swapchain image that the + * client allocated as an Android AHardwareBuffer (gralloc) is still shared + * as a dma-buf through zwp_linux_dmabuf_v1, so the compositor can import + * and draw it the usual way. This interface additionally hands the + * compositor the AHardwareBuffer itself, once per wl_buffer, so the + * compositor can put the buffer straight on an Android SurfaceControl + * layer (SurfaceFlinger / the display hardware composite it) without + * copying it. + * + * The AHardwareBuffer travels over a Unix socket: the client creates a + * socketpair, writes the buffer's handle into one end with + * AHardwareBuffer_sendHandleToUnixSocket, then sends the other end in the + * attach request. The compositor reads it with + * AHardwareBuffer_recvHandleFromUnixSocket and closes the socket. Its + * reference to the AHardwareBuffer lives as long as the wl_buffer (plus + * whatever the display still holds). + * + * The compositor advertises this global whenever it can put buffers on a + * display layer at all; whether it WANTS gralloc buffers right now is the + * mode event (version 2), sent on bind and every time the user switches + * zero-copy on or off during the session. A client decides per swapchain, + * when it creates the swapchain, from the last mode it received, and a + * client whose live swapchain was built for the other mode retires it + * (VK_ERROR_OUT_OF_DATE_KHR on its next acquire or present) so the program + * rebuilds it for the new mode. Buffers of the old swapchain stay fully + * presentable until then: an attached AHardwareBuffer keeps its association + * for the wl_buffer's lifetime whatever the mode is now. A version 1 + * client (no mode event) keeps deciding from its own environment. A + * client that does not see the global (or does not want it) allocates + * swapchain images the standard way; nothing else changes. + * + * Synchronisation stays implicit: the dma-buf carries the client's render + * fence (DMA_BUF_IOCTL_IMPORT_SYNC_FILE by the client's driver), the + * compositor exports it as the layer's acquire fence, and the compositor + * imports the display's release fence into the dma-buf before it sends + * wl_buffer.release, so the client's next acquire waits on it. + */ +extern const struct wl_interface banner_ahb_v1_interface; +#endif + +/** + * @ingroup iface_banner_ahb_v1 + * @struct banner_ahb_v1_interface + */ +struct banner_ahb_v1_interface { + /** + * destroy the object + * + * Buffers already attached keep their AHardwareBuffer + * association. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * hand the compositor a buffer's AHardwareBuffer + * + * Associates the AHardwareBuffer readable from socket with the + * (already created) dma-buf wl_buffer. width, height and stride + * (pixels) describe the gralloc allocation; modifier_hi/lo is the + * DRM format modifier the dma-buf was shared with (the same value + * passed to zwp_linux_buffer_params_v1.add); image_count is the + * number of images in the swapchain this buffer belongs to (for + * the compositor's log). Sent once per wl_buffer, before the + * buffer is first attached to a surface. + * @param buffer the dma-buf wl_buffer + * @param socket socket the AHardwareBuffer handle was written to + * @param width buffer width in pixels + * @param height buffer height in pixels + * @param stride gralloc row stride in pixels + * @param modifier_hi high 32 bits of the DRM format modifier + * @param modifier_lo low 32 bits of the DRM format modifier + * @param image_count images in the client's swapchain + */ + void (*attach)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer, + int32_t socket, + uint32_t width, + uint32_t height, + uint32_t stride, + uint32_t modifier_hi, + uint32_t modifier_lo, + uint32_t image_count); +}; + +#define WNC_AHB_V1_MODE 0 + +/** + * @ingroup iface_banner_ahb_v1 + */ +#define WNC_AHB_V1_MODE_SINCE_VERSION 2 + +/** + * @ingroup iface_banner_ahb_v1 + */ +#define WNC_AHB_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_banner_ahb_v1 + */ +#define WNC_AHB_V1_ATTACH_SINCE_VERSION 1 + +/** + * @ingroup iface_banner_ahb_v1 + * Sends an mode event to the client owning the resource. + * @param resource_ The client's resource + * @param enabled 1 = gralloc swapchain images wanted, 0 = not + */ +static inline void +banner_ahb_v1_send_mode(struct wl_resource *resource_, uint32_t enabled) +{ + wl_resource_post_event(resource_, WNC_AHB_V1_MODE, enabled); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/color-management-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/color-management-v1-protocol.c new file mode 100644 index 000000000..ff31f8a41 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/color-management-v1-protocol.c @@ -0,0 +1,216 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright 2019 Sebastian Wick + * Copyright 2019 Erwin Burema + * Copyright 2020 AMD + * Copyright 2020-2024 Collabora, Ltd. + * Copyright 2024 Xaver Hugl + * Copyright 2022-2025 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface wp_color_management_output_v1_interface; +extern const struct wl_interface wp_color_management_surface_feedback_v1_interface; +extern const struct wl_interface wp_color_management_surface_v1_interface; +extern const struct wl_interface wp_image_description_creator_icc_v1_interface; +extern const struct wl_interface wp_image_description_creator_params_v1_interface; +extern const struct wl_interface wp_image_description_info_v1_interface; +extern const struct wl_interface wp_image_description_v1_interface; + +static const struct wl_interface *color_management_v1_types[] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + &wp_color_management_output_v1_interface, + &wl_output_interface, + &wp_color_management_surface_v1_interface, + &wl_surface_interface, + &wp_color_management_surface_feedback_v1_interface, + &wl_surface_interface, + &wp_image_description_creator_icc_v1_interface, + &wp_image_description_creator_params_v1_interface, + &wp_image_description_v1_interface, + &wp_image_description_v1_interface, + &wp_image_description_v1_interface, + NULL, + &wp_image_description_v1_interface, + &wp_image_description_v1_interface, + &wp_image_description_v1_interface, + &wp_image_description_v1_interface, + &wp_image_description_info_v1_interface, +}; + +static const struct wl_message wp_color_manager_v1_requests[] = { + { "destroy", "", color_management_v1_types + 0 }, + { "get_output", "no", color_management_v1_types + 8 }, + { "get_surface", "no", color_management_v1_types + 10 }, + { "get_surface_feedback", "no", color_management_v1_types + 12 }, + { "create_icc_creator", "n", color_management_v1_types + 14 }, + { "create_parametric_creator", "n", color_management_v1_types + 15 }, + { "create_windows_scrgb", "n", color_management_v1_types + 16 }, +}; + +static const struct wl_message wp_color_manager_v1_events[] = { + { "supported_intent", "u", color_management_v1_types + 0 }, + { "supported_feature", "u", color_management_v1_types + 0 }, + { "supported_tf_named", "u", color_management_v1_types + 0 }, + { "supported_primaries_named", "u", color_management_v1_types + 0 }, + { "done", "", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_color_manager_v1_interface = { + "wp_color_manager_v1", 1, + 7, wp_color_manager_v1_requests, + 5, wp_color_manager_v1_events, +}; + +static const struct wl_message wp_color_management_output_v1_requests[] = { + { "destroy", "", color_management_v1_types + 0 }, + { "get_image_description", "n", color_management_v1_types + 17 }, +}; + +static const struct wl_message wp_color_management_output_v1_events[] = { + { "image_description_changed", "", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_color_management_output_v1_interface = { + "wp_color_management_output_v1", 1, + 2, wp_color_management_output_v1_requests, + 1, wp_color_management_output_v1_events, +}; + +static const struct wl_message wp_color_management_surface_v1_requests[] = { + { "destroy", "", color_management_v1_types + 0 }, + { "set_image_description", "ou", color_management_v1_types + 18 }, + { "unset_image_description", "", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_color_management_surface_v1_interface = { + "wp_color_management_surface_v1", 1, + 3, wp_color_management_surface_v1_requests, + 0, NULL, +}; + +static const struct wl_message wp_color_management_surface_feedback_v1_requests[] = { + { "destroy", "", color_management_v1_types + 0 }, + { "get_preferred", "n", color_management_v1_types + 20 }, + { "get_preferred_parametric", "n", color_management_v1_types + 21 }, +}; + +static const struct wl_message wp_color_management_surface_feedback_v1_events[] = { + { "preferred_changed", "u", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_color_management_surface_feedback_v1_interface = { + "wp_color_management_surface_feedback_v1", 1, + 3, wp_color_management_surface_feedback_v1_requests, + 1, wp_color_management_surface_feedback_v1_events, +}; + +static const struct wl_message wp_image_description_creator_icc_v1_requests[] = { + { "create", "n", color_management_v1_types + 22 }, + { "set_icc_file", "huu", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_image_description_creator_icc_v1_interface = { + "wp_image_description_creator_icc_v1", 1, + 2, wp_image_description_creator_icc_v1_requests, + 0, NULL, +}; + +static const struct wl_message wp_image_description_creator_params_v1_requests[] = { + { "create", "n", color_management_v1_types + 23 }, + { "set_tf_named", "u", color_management_v1_types + 0 }, + { "set_tf_power", "u", color_management_v1_types + 0 }, + { "set_primaries_named", "u", color_management_v1_types + 0 }, + { "set_primaries", "iiiiiiii", color_management_v1_types + 0 }, + { "set_luminances", "uuu", color_management_v1_types + 0 }, + { "set_mastering_display_primaries", "iiiiiiii", color_management_v1_types + 0 }, + { "set_mastering_luminance", "uu", color_management_v1_types + 0 }, + { "set_max_cll", "u", color_management_v1_types + 0 }, + { "set_max_fall", "u", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_image_description_creator_params_v1_interface = { + "wp_image_description_creator_params_v1", 1, + 10, wp_image_description_creator_params_v1_requests, + 0, NULL, +}; + +static const struct wl_message wp_image_description_v1_requests[] = { + { "destroy", "", color_management_v1_types + 0 }, + { "get_information", "n", color_management_v1_types + 24 }, +}; + +static const struct wl_message wp_image_description_v1_events[] = { + { "failed", "us", color_management_v1_types + 0 }, + { "ready", "u", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_image_description_v1_interface = { + "wp_image_description_v1", 1, + 2, wp_image_description_v1_requests, + 2, wp_image_description_v1_events, +}; + +static const struct wl_message wp_image_description_info_v1_events[] = { + { "done", "", color_management_v1_types + 0 }, + { "icc_file", "hu", color_management_v1_types + 0 }, + { "primaries", "iiiiiiii", color_management_v1_types + 0 }, + { "primaries_named", "u", color_management_v1_types + 0 }, + { "tf_power", "u", color_management_v1_types + 0 }, + { "tf_named", "u", color_management_v1_types + 0 }, + { "luminances", "uuu", color_management_v1_types + 0 }, + { "target_primaries", "iiiiiiii", color_management_v1_types + 0 }, + { "target_luminance", "uu", color_management_v1_types + 0 }, + { "target_max_cll", "u", color_management_v1_types + 0 }, + { "target_max_fall", "u", color_management_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_image_description_info_v1_interface = { + "wp_image_description_info_v1", 1, + 0, NULL, + 11, wp_image_description_info_v1_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/color-management-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/color-management-v1-server-protocol.h new file mode 100644 index 000000000..08008e8f8 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/color-management-v1-server-protocol.h @@ -0,0 +1,2607 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef COLOR_MANAGEMENT_V1_SERVER_PROTOCOL_H +#define COLOR_MANAGEMENT_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_color_management_v1 The color_management_v1 protocol + * color management protocol + * + * @section page_desc_color_management_v1 Description + * + * The aim of the color management extension is to allow clients to know + * the color properties of outputs, and to tell the compositor about the color + * properties of their content on surfaces. Doing this enables a compositor + * to perform automatic color management of content for different outputs + * according to how content is intended to look like. + * + * The color properties are represented as an image description object which + * is immutable after it has been created. A wl_output always has an + * associated image description that clients can observe. A wl_surface + * always has an associated preferred image description as a hint chosen by + * the compositor that clients can also observe. Clients can set an image + * description on a wl_surface to denote the color characteristics of the + * surface contents. + * + * An image description includes SDR and HDR colorimetry and encoding, HDR + * metadata, and viewing environment parameters. An image description does + * not include the properties set through color-representation extension. + * It is expected that the color-representation extension is used in + * conjunction with the color management extension when necessary, + * particularly with the YUV family of pixel formats. + * + * Recommendation ITU-T H.273 + * "Coding-independent code points for video signal type identification" + * shall be referred to as simply H.273 here. + * + * The color-and-hdr repository + * (https://gitlab.freedesktop.org/pq/color-and-hdr) contains + * background information on the protocol design and legacy color management. + * It also contains a glossary, learning resources for digital color, tools, + * samples and more. + * + * The terminology used in this protocol is based on common color science and + * color encoding terminology where possible. The glossary in the color-and-hdr + * repository shall be the authority on the definition of terms in this + * protocol. + * + * Warning! The protocol described in this file is currently in the testing + * phase. Backward compatible changes may be added together with the + * corresponding interface version bump. Backward incompatible changes can + * only be done by creating a new major version of the extension. + * + * @section page_ifaces_color_management_v1 Interfaces + * - @subpage page_iface_wp_color_manager_v1 - color manager singleton + * - @subpage page_iface_wp_color_management_output_v1 - output color properties + * - @subpage page_iface_wp_color_management_surface_v1 - color management extension to a surface + * - @subpage page_iface_wp_color_management_surface_feedback_v1 - color management extension to a surface + * - @subpage page_iface_wp_image_description_creator_icc_v1 - holder of image description ICC information + * - @subpage page_iface_wp_image_description_creator_params_v1 - holder of image description parameters + * - @subpage page_iface_wp_image_description_v1 - Colorimetric image description + * - @subpage page_iface_wp_image_description_info_v1 - Colorimetric image description information + * @section page_copyright_color_management_v1 Copyright + *
+ *
+ * Copyright 2019 Sebastian Wick
+ * Copyright 2019 Erwin Burema
+ * Copyright 2020 AMD
+ * Copyright 2020-2024 Collabora, Ltd.
+ * Copyright 2024 Xaver Hugl
+ * Copyright 2022-2025 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_surface; +struct wp_color_management_output_v1; +struct wp_color_management_surface_feedback_v1; +struct wp_color_management_surface_v1; +struct wp_color_manager_v1; +struct wp_image_description_creator_icc_v1; +struct wp_image_description_creator_params_v1; +struct wp_image_description_info_v1; +struct wp_image_description_v1; + +#ifndef WP_COLOR_MANAGER_V1_INTERFACE +#define WP_COLOR_MANAGER_V1_INTERFACE +/** + * @page page_iface_wp_color_manager_v1 wp_color_manager_v1 + * @section page_iface_wp_color_manager_v1_desc Description + * + * A singleton global interface used for getting color management extensions + * for wl_surface and wl_output objects, and for creating client defined + * image description objects. The extension interfaces allow + * getting the image description of outputs and setting the image + * description of surfaces. + * + * Compositors should never remove this global. + * @section page_iface_wp_color_manager_v1_api API + * See @ref iface_wp_color_manager_v1. + */ +/** + * @defgroup iface_wp_color_manager_v1 The wp_color_manager_v1 interface + * + * A singleton global interface used for getting color management extensions + * for wl_surface and wl_output objects, and for creating client defined + * image description objects. The extension interfaces allow + * getting the image description of outputs and setting the image + * description of surfaces. + * + * Compositors should never remove this global. + */ +extern const struct wl_interface wp_color_manager_v1_interface; +#endif +#ifndef WP_COLOR_MANAGEMENT_OUTPUT_V1_INTERFACE +#define WP_COLOR_MANAGEMENT_OUTPUT_V1_INTERFACE +/** + * @page page_iface_wp_color_management_output_v1 wp_color_management_output_v1 + * @section page_iface_wp_color_management_output_v1_desc Description + * + * A wp_color_management_output_v1 describes the color properties of an + * output. + * + * The wp_color_management_output_v1 is associated with the wl_output global + * underlying the wl_output object. Therefore the client destroying the + * wl_output object has no impact, but the compositor removing the output + * global makes the wp_color_management_output_v1 object inert. + * @section page_iface_wp_color_management_output_v1_api API + * See @ref iface_wp_color_management_output_v1. + */ +/** + * @defgroup iface_wp_color_management_output_v1 The wp_color_management_output_v1 interface + * + * A wp_color_management_output_v1 describes the color properties of an + * output. + * + * The wp_color_management_output_v1 is associated with the wl_output global + * underlying the wl_output object. Therefore the client destroying the + * wl_output object has no impact, but the compositor removing the output + * global makes the wp_color_management_output_v1 object inert. + */ +extern const struct wl_interface wp_color_management_output_v1_interface; +#endif +#ifndef WP_COLOR_MANAGEMENT_SURFACE_V1_INTERFACE +#define WP_COLOR_MANAGEMENT_SURFACE_V1_INTERFACE +/** + * @page page_iface_wp_color_management_surface_v1 wp_color_management_surface_v1 + * @section page_iface_wp_color_management_surface_v1_desc Description + * + * A wp_color_management_surface_v1 allows the client to set the color + * space and HDR properties of a surface. + * + * If the wl_surface associated with the wp_color_management_surface_v1 is + * destroyed, the wp_color_management_surface_v1 object becomes inert. + * @section page_iface_wp_color_management_surface_v1_api API + * See @ref iface_wp_color_management_surface_v1. + */ +/** + * @defgroup iface_wp_color_management_surface_v1 The wp_color_management_surface_v1 interface + * + * A wp_color_management_surface_v1 allows the client to set the color + * space and HDR properties of a surface. + * + * If the wl_surface associated with the wp_color_management_surface_v1 is + * destroyed, the wp_color_management_surface_v1 object becomes inert. + */ +extern const struct wl_interface wp_color_management_surface_v1_interface; +#endif +#ifndef WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_INTERFACE +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_INTERFACE +/** + * @page page_iface_wp_color_management_surface_feedback_v1 wp_color_management_surface_feedback_v1 + * @section page_iface_wp_color_management_surface_feedback_v1_desc Description + * + * A wp_color_management_surface_feedback_v1 allows the client to get the + * preferred image description of a surface. + * + * If the wl_surface associated with this object is destroyed, the + * wp_color_management_surface_feedback_v1 object becomes inert. + * @section page_iface_wp_color_management_surface_feedback_v1_api API + * See @ref iface_wp_color_management_surface_feedback_v1. + */ +/** + * @defgroup iface_wp_color_management_surface_feedback_v1 The wp_color_management_surface_feedback_v1 interface + * + * A wp_color_management_surface_feedback_v1 allows the client to get the + * preferred image description of a surface. + * + * If the wl_surface associated with this object is destroyed, the + * wp_color_management_surface_feedback_v1 object becomes inert. + */ +extern const struct wl_interface wp_color_management_surface_feedback_v1_interface; +#endif +#ifndef WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_INTERFACE +#define WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_INTERFACE +/** + * @page page_iface_wp_image_description_creator_icc_v1 wp_image_description_creator_icc_v1 + * @section page_iface_wp_image_description_creator_icc_v1_desc Description + * + * This type of object is used for collecting all the information required + * to create a wp_image_description_v1 object from an ICC file. A complete + * set of required parameters consists of these properties: + * - ICC file + * + * Each required property must be set exactly once if the client is to create + * an image description. The set requests verify that a property was not + * already set. The create request verifies that all required properties are + * set. There may be several alternative requests for setting each property, + * and in that case the client must choose one of them. + * + * Once all properties have been set, the create request must be used to + * create the image description object, destroying the creator in the + * process. + * @section page_iface_wp_image_description_creator_icc_v1_api API + * See @ref iface_wp_image_description_creator_icc_v1. + */ +/** + * @defgroup iface_wp_image_description_creator_icc_v1 The wp_image_description_creator_icc_v1 interface + * + * This type of object is used for collecting all the information required + * to create a wp_image_description_v1 object from an ICC file. A complete + * set of required parameters consists of these properties: + * - ICC file + * + * Each required property must be set exactly once if the client is to create + * an image description. The set requests verify that a property was not + * already set. The create request verifies that all required properties are + * set. There may be several alternative requests for setting each property, + * and in that case the client must choose one of them. + * + * Once all properties have been set, the create request must be used to + * create the image description object, destroying the creator in the + * process. + */ +extern const struct wl_interface wp_image_description_creator_icc_v1_interface; +#endif +#ifndef WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_INTERFACE +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_INTERFACE +/** + * @page page_iface_wp_image_description_creator_params_v1 wp_image_description_creator_params_v1 + * @section page_iface_wp_image_description_creator_params_v1_desc Description + * + * This type of object is used for collecting all the parameters required + * to create a wp_image_description_v1 object. A complete set of required + * parameters consists of these properties: + * - transfer characteristic function (tf) + * - chromaticities of primaries and white point (primary color volume) + * + * The following properties are optional and have a well-defined default + * if not explicitly set: + * - primary color volume luminance range + * - reference white luminance level + * - mastering display primaries and white point (target color volume) + * - mastering luminance range + * + * The following properties are optional and will be ignored + * if not explicitly set: + * - maximum content light level + * - maximum frame-average light level + * + * Each required property must be set exactly once if the client is to create + * an image description. The set requests verify that a property was not + * already set. The create request verifies that all required properties are + * set. There may be several alternative requests for setting each property, + * and in that case the client must choose one of them. + * + * Once all properties have been set, the create request must be used to + * create the image description object, destroying the creator in the + * process. + * @section page_iface_wp_image_description_creator_params_v1_api API + * See @ref iface_wp_image_description_creator_params_v1. + */ +/** + * @defgroup iface_wp_image_description_creator_params_v1 The wp_image_description_creator_params_v1 interface + * + * This type of object is used for collecting all the parameters required + * to create a wp_image_description_v1 object. A complete set of required + * parameters consists of these properties: + * - transfer characteristic function (tf) + * - chromaticities of primaries and white point (primary color volume) + * + * The following properties are optional and have a well-defined default + * if not explicitly set: + * - primary color volume luminance range + * - reference white luminance level + * - mastering display primaries and white point (target color volume) + * - mastering luminance range + * + * The following properties are optional and will be ignored + * if not explicitly set: + * - maximum content light level + * - maximum frame-average light level + * + * Each required property must be set exactly once if the client is to create + * an image description. The set requests verify that a property was not + * already set. The create request verifies that all required properties are + * set. There may be several alternative requests for setting each property, + * and in that case the client must choose one of them. + * + * Once all properties have been set, the create request must be used to + * create the image description object, destroying the creator in the + * process. + */ +extern const struct wl_interface wp_image_description_creator_params_v1_interface; +#endif +#ifndef WP_IMAGE_DESCRIPTION_V1_INTERFACE +#define WP_IMAGE_DESCRIPTION_V1_INTERFACE +/** + * @page page_iface_wp_image_description_v1 wp_image_description_v1 + * @section page_iface_wp_image_description_v1_desc Description + * + * An image description carries information about the color encoding used on + * a surface when attached to a wl_surface via + * wp_color_management_surface_v1.set_image_description. A compositor can use + * this information to decode pixel values into colorimetrically meaningful + * quantities. + * + * Note, that the wp_image_description_v1 object is not ready to be used + * immediately after creation. The object eventually delivers either the + * 'ready' or the 'failed' event, specified in all requests creating it. The + * object is deemed "ready" after receiving the 'ready' event. + * + * An object which is not ready is illegal to use, it can only be destroyed. + * Any other request in this interface shall result in the 'not_ready' + * protocol error. Attempts to use an object which is not ready through other + * interfaces shall raise protocol errors defined there. + * + * Once created and regardless of how it was created, a + * wp_image_description_v1 object always refers to one fixed image + * description. It cannot change after creation. + * @section page_iface_wp_image_description_v1_api API + * See @ref iface_wp_image_description_v1. + */ +/** + * @defgroup iface_wp_image_description_v1 The wp_image_description_v1 interface + * + * An image description carries information about the color encoding used on + * a surface when attached to a wl_surface via + * wp_color_management_surface_v1.set_image_description. A compositor can use + * this information to decode pixel values into colorimetrically meaningful + * quantities. + * + * Note, that the wp_image_description_v1 object is not ready to be used + * immediately after creation. The object eventually delivers either the + * 'ready' or the 'failed' event, specified in all requests creating it. The + * object is deemed "ready" after receiving the 'ready' event. + * + * An object which is not ready is illegal to use, it can only be destroyed. + * Any other request in this interface shall result in the 'not_ready' + * protocol error. Attempts to use an object which is not ready through other + * interfaces shall raise protocol errors defined there. + * + * Once created and regardless of how it was created, a + * wp_image_description_v1 object always refers to one fixed image + * description. It cannot change after creation. + */ +extern const struct wl_interface wp_image_description_v1_interface; +#endif +#ifndef WP_IMAGE_DESCRIPTION_INFO_V1_INTERFACE +#define WP_IMAGE_DESCRIPTION_INFO_V1_INTERFACE +/** + * @page page_iface_wp_image_description_info_v1 wp_image_description_info_v1 + * @section page_iface_wp_image_description_info_v1_desc Description + * + * Sends all matching events describing an image description object exactly + * once and finally sends the 'done' event. + * + * This means + * - if the image description is parametric, it must send + * - primaries + * - named_primaries, if applicable + * - at least one of tf_power and tf_named, as applicable + * - luminances + * - target_primaries + * - target_luminance + * - if the image description is parametric, it may send, if applicable, + * - target_max_cll + * - target_max_fall + * - if the image description contains an ICC profile, it must send the + * icc_file event + * + * Once a wp_image_description_info_v1 object has delivered a 'done' event it + * is automatically destroyed. + * + * Every wp_image_description_info_v1 created from the same + * wp_image_description_v1 shall always return the exact same data. + * @section page_iface_wp_image_description_info_v1_api API + * See @ref iface_wp_image_description_info_v1. + */ +/** + * @defgroup iface_wp_image_description_info_v1 The wp_image_description_info_v1 interface + * + * Sends all matching events describing an image description object exactly + * once and finally sends the 'done' event. + * + * This means + * - if the image description is parametric, it must send + * - primaries + * - named_primaries, if applicable + * - at least one of tf_power and tf_named, as applicable + * - luminances + * - target_primaries + * - target_luminance + * - if the image description is parametric, it may send, if applicable, + * - target_max_cll + * - target_max_fall + * - if the image description contains an ICC profile, it must send the + * icc_file event + * + * Once a wp_image_description_info_v1 object has delivered a 'done' event it + * is automatically destroyed. + * + * Every wp_image_description_info_v1 created from the same + * wp_image_description_v1 shall always return the exact same data. + */ +extern const struct wl_interface wp_image_description_info_v1_interface; +#endif + +#ifndef WP_COLOR_MANAGER_V1_ERROR_ENUM +#define WP_COLOR_MANAGER_V1_ERROR_ENUM +enum wp_color_manager_v1_error { + /** + * request not supported + */ + WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE = 0, + /** + * color management surface exists already + */ + WP_COLOR_MANAGER_V1_ERROR_SURFACE_EXISTS = 1, +}; +#endif /* WP_COLOR_MANAGER_V1_ERROR_ENUM */ + +#ifndef WP_COLOR_MANAGER_V1_ERROR_ENUM_IS_VALID +#define WP_COLOR_MANAGER_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_manager_v1 + * Validate a wp_color_manager_v1 error value. + * + * @return true on success, false on error. + * @ref wp_color_manager_v1_error + */ +static inline bool +wp_color_manager_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE: + return version >= 1; + case WP_COLOR_MANAGER_V1_ERROR_SURFACE_EXISTS: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGER_V1_ERROR_ENUM_IS_VALID */ + +#ifndef WP_COLOR_MANAGER_V1_RENDER_INTENT_ENUM +#define WP_COLOR_MANAGER_V1_RENDER_INTENT_ENUM +/** + * @ingroup iface_wp_color_manager_v1 + * rendering intents + * + * See the ICC.1:2022 specification from the International Color Consortium + * for more details about rendering intents. + * + * The principles of ICC defined rendering intents apply with all types of + * image descriptions, not only those with ICC file profiles. + * + * Compositors must support the perceptual rendering intent. Other + * rendering intents are optional. + */ +enum wp_color_manager_v1_render_intent { + /** + * perceptual + */ + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL = 0, + /** + * media-relative colorimetric + */ + WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE = 1, + /** + * saturation + */ + WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION = 2, + /** + * ICC-absolute colorimetric + */ + WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE = 3, + /** + * media-relative colorimetric + black point compensation + */ + WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC = 4, +}; +#endif /* WP_COLOR_MANAGER_V1_RENDER_INTENT_ENUM */ + +#ifndef WP_COLOR_MANAGER_V1_RENDER_INTENT_ENUM_IS_VALID +#define WP_COLOR_MANAGER_V1_RENDER_INTENT_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_manager_v1 + * Validate a wp_color_manager_v1 render_intent value. + * + * @return true on success, false on error. + * @ref wp_color_manager_v1_render_intent + */ +static inline bool +wp_color_manager_v1_render_intent_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL: + return version >= 1; + case WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE: + return version >= 1; + case WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION: + return version >= 1; + case WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE: + return version >= 1; + case WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGER_V1_RENDER_INTENT_ENUM_IS_VALID */ + +#ifndef WP_COLOR_MANAGER_V1_FEATURE_ENUM +#define WP_COLOR_MANAGER_V1_FEATURE_ENUM +/** + * @ingroup iface_wp_color_manager_v1 + * compositor supported features + */ +enum wp_color_manager_v1_feature { + /** + * create_icc_creator request + */ + WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4 = 0, + /** + * create_parametric_creator request + */ + WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC = 1, + /** + * parametric set_primaries request + */ + WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES = 2, + /** + * parametric set_tf_power request + */ + WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER = 3, + /** + * parametric set_luminances request + */ + WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES = 4, + /** + * parametric set_mastering_display_primaries request + * + * The compositor supports set_mastering_display_primaries + * request with a target color volume fully contained inside the + * primary color volume. + */ + WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES = 5, + /** + * parametric target exceeds primary color volume + * + * The compositor additionally supports target color volumes that + * extend outside of the primary color volume. + * + * This can only be advertised if feature + * set_mastering_display_primaries is supported as well. + */ + WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME = 6, + /** + * get_windows_scrgb request + */ + WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB = 7, +}; +#endif /* WP_COLOR_MANAGER_V1_FEATURE_ENUM */ + +#ifndef WP_COLOR_MANAGER_V1_FEATURE_ENUM_IS_VALID +#define WP_COLOR_MANAGER_V1_FEATURE_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_manager_v1 + * Validate a wp_color_manager_v1 feature value. + * + * @return true on success, false on error. + * @ref wp_color_manager_v1_feature + */ +static inline bool +wp_color_manager_v1_feature_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME: + return version >= 1; + case WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGER_V1_FEATURE_ENUM_IS_VALID */ + +#ifndef WP_COLOR_MANAGER_V1_PRIMARIES_ENUM +#define WP_COLOR_MANAGER_V1_PRIMARIES_ENUM +/** + * @ingroup iface_wp_color_manager_v1 + * named color primaries + * + * Named color primaries used to encode well-known sets of primaries. H.273 + * is the authority, when it comes to the exact values of primaries and + * authoritative specifications, where an equivalent code point exists. + * + * A value of 0 is invalid and will never be present in the list of enums. + * + * Descriptions do list the specifications for convenience. + */ +enum wp_color_manager_v1_primaries { + /** + * Color primaries for the sRGB color space as defined by the BT.709 standard + * + * Color primaries as defined by - Rec. ITU-R BT.709-6 - Rec. + * ITU-R BT.1361-0 conventional colour gamut system and extended + * colour gamut system (historical) - IEC 61966-2-1 sRGB or sYCC - + * IEC 61966-2-4 - Society of Motion Picture and Television + * Engineers (SMPTE) RP 177 (1993) Annex B Equivalent to H.273 + * ColourPrimaries code point 1. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_SRGB = 1, + /** + * Color primaries for PAL-M as defined by the BT.470 standard + * + * Color primaries as defined by - Rec. ITU-R BT.470-6 System M + * (historical) - United States National Television System + * Committee 1953 Recommendation for transmission standards for + * color television - United States Federal Communications + * Commission (2003) Title 47 Code of Federal Regulations 73.682 + * (a)(20) Equivalent to H.273 ColourPrimaries code point 4. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M = 2, + /** + * Color primaries for PAL as defined by the BT.601 standard + * + * Color primaries as defined by - Rec. ITU-R BT.470-6 System B, + * G (historical) - Rec. ITU-R BT.601-7 625 - Rec. ITU-R BT.1358-0 + * 625 (historical) - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + * Equivalent to H.273 ColourPrimaries code point 5. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_PAL = 3, + /** + * Color primaries for NTSC as defined by the BT.601 standard + * + * Color primaries as defined by - Rec. ITU-R BT.601-7 525 - Rec. + * ITU-R BT.1358-1 525 or 625 (historical) - Rec. ITU-R BT.1700-0 + * NTSC - SMPTE 170M (2004) - SMPTE 240M (1999) (historical) + * Equivalent to H.273 ColourPrimaries code point 6 and 7. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_NTSC = 4, + /** + * Generic film with colour filters using Illuminant C + * + * Color primaries as defined by H.273 for generic film. + * Equivalent to H.273 ColourPrimaries code point 8. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM = 5, + /** + * Color primaries as defined by the BT.2020 and BT.2100 standard + * + * Color primaries as defined by - Rec. ITU-R BT.2020-2 - Rec. + * ITU-R BT.2100-0 Equivalent to H.273 ColourPrimaries code point + * 9. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 = 6, + /** + * Color primaries of the full CIE 1931 XYZ color space + * + * Color primaries as defined as the maximum of the CIE 1931 XYZ + * color space by - SMPTE ST 428-1 - (CIE 1931 XYZ as in ISO + * 11664-1) Equivalent to H.273 ColourPrimaries code point 10. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ = 7, + /** + * Color primaries of the DCI P3 color space as defined by the SMPTE RP 431 standard + * + * Color primaries as defined by Digital Cinema System and + * published in SMPTE RP 431-2 (2011). Equivalent to H.273 + * ColourPrimaries code point 11. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3 = 8, + /** + * Color primaries of Display P3 variant of the DCI-P3 color space as defined by the SMPTE EG 432 standard + * + * Color primaries as defined by Digital Cinema System and + * published in SMPTE EG 432-1 (2010). Equivalent to H.273 + * ColourPrimaries code point 12. + */ + WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3 = 9, + /** + * Color primaries of the Adobe RGB color space as defined by the ISO 12640 standard + * + * Color primaries as defined by Adobe as "Adobe RGB" and later + * published by ISO 12640-4 (2011). + */ + WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB = 10, +}; +#endif /* WP_COLOR_MANAGER_V1_PRIMARIES_ENUM */ + +#ifndef WP_COLOR_MANAGER_V1_PRIMARIES_ENUM_IS_VALID +#define WP_COLOR_MANAGER_V1_PRIMARIES_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_manager_v1 + * Validate a wp_color_manager_v1 primaries value. + * + * @return true on success, false on error. + * @ref wp_color_manager_v1_primaries + */ +static inline bool +wp_color_manager_v1_primaries_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_PAL: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_NTSC: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3: + return version >= 1; + case WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGER_V1_PRIMARIES_ENUM_IS_VALID */ + +#ifndef WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ENUM +#define WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ENUM +/** + * @ingroup iface_wp_color_manager_v1 + * named transfer functions + * + * Named transfer functions used to represent well-known transfer + * characteristics. H.273 is the authority, when it comes to the exact + * formulas and authoritative specifications, where an equivalent code + * point exists. + * + * A value of 0 is invalid and will never be present in the list of enums. + * + * Descriptions do list the specifications for convenience. + */ +enum wp_color_manager_v1_transfer_function { + /** + * BT.1886 display transfer characteristic + * + * Rec. ITU-R BT.1886 is the display transfer characteristic + * assumed by - Rec. ITU-R BT.601-7 525 and 625 - Rec. ITU-R + * BT.709-6 - Rec. ITU-R BT.2020-2 These recommendations are + * referred to by H.273 TransferCharacteristics code points 1, 6, + * 14, and 15, which are all equivalent. + * + * This TF implies these default luminances from Rec. ITU-R + * BT.2035: - primary color volume minimum: 0.01 cd/mยฒ - primary + * color volume maximum: 100 cd/mยฒ - reference white: 100 cd/mยฒ + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886 = 1, + /** + * Assumed display gamma 2.2 transfer function + * + * Transfer characteristics as defined by - Rec. ITU-R BT.470-6 + * System M (historical) - United States National Television System + * Committee 1953 Recommendation for transmission standards for + * color television - United States Federal Communications + * Commission (2003) Title 47 Code of Federal Regulations 73.682 + * (a) (20) - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM Equivalent + * to H.273 TransferCharacteristics code point 4. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22 = 2, + /** + * Assumed display gamma 2.8 transfer function + * + * Transfer characteristics as defined by - Rec. ITU-R BT.470-6 + * System B, G (historical) Equivalent to H.273 + * TransferCharacteristics code point 5. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28 = 3, + /** + * SMPTE ST 240 transfer function + * + * Transfer characteristics as defined by - SMPTE ST 240 (1999) + * Equivalent to H.273 TransferCharacteristics code point 7. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240 = 4, + /** + * extended linear transfer function + * + * Linear transfer function defined over all real numbers. + * Normalised electrical values are equal the normalised optical + * values. + * + * The differences to H.273 TransferCharacteristics code point 8 + * are the definition over all real numbers. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR = 5, + /** + * logarithmic 100:1 transfer function + * + * Logarithmic transfer characteristic (100:1 range). Equivalent + * to H.273 TransferCharacteristics code point 9. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100 = 6, + /** + * logarithmic (100*Sqrt(10) : 1) transfer function + * + * Logarithmic transfer characteristic (100 * Sqrt(10) : 1 + * range). Equivalent to H.273 TransferCharacteristics code point + * 10. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316 = 7, + /** + * IEC 61966-2-4 transfer function + * + * Transfer characteristics as defined by - IEC 61966-2-4 + * Equivalent to H.273 TransferCharacteristics code point 11. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC = 8, + /** + * sRGB piece-wise transfer function + * + * Transfer characteristics as defined by - IEC 61966-2-1 sRGB + * Equivalent to H.273 TransferCharacteristics code point 13 with + * MatrixCoefficients set to 0. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB = 9, + /** + * Extended sRGB piece-wise transfer function + * + * Transfer characteristics as defined by - IEC 61966-2-1 sYCC + * Equivalent to H.273 TransferCharacteristics code point 13 with + * MatrixCoefficients set to anything but 0. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB = 10, + /** + * perceptual quantizer transfer function + * + * Transfer characteristics as defined by - SMPTE ST 2084 (2014) + * for 10-, 12-, 14- and 16-bit systems - Rec. ITU-R BT.2100-2 + * perceptual quantization (PQ) system Equivalent to H.273 + * TransferCharacteristics code point 16. + * + * This TF implies these default luminances - primary color volume + * minimum: 0.005 cd/mยฒ - primary color volume maximum: 10000 + * cd/mยฒ - reference white: 203 cd/mยฒ + * + * The difference between the primary color volume minimum and + * maximum must be approximately 10000 cd/mยฒ as that is the swing + * of the EOTF defined by ST 2084 and BT.2100. The default value + * for the reference white is a protocol addition: it is suggested + * by Report ITU-R BT.2408-7 and is not part of ST 2084 or BT.2100. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ = 11, + /** + * SMPTE ST 428 transfer function + * + * Transfer characteristics as defined by - SMPTE ST 428-1 (2019) + * Equivalent to H.273 TransferCharacteristics code point 17. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428 = 12, + /** + * hybrid log-gamma transfer function + * + * Transfer characteristics as defined by - ARIB STD-B67 (2015) - + * Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system Equivalent to + * H.273 TransferCharacteristics code point 18. + * + * This TF implies these default luminances - primary color volume + * minimum: 0.005 cd/mยฒ - primary color volume maximum: 1000 + * cd/mยฒ - reference white: 203 cd/mยฒ + * + * HLG is a relative display-referred signal with a specified + * non-linear mapping to the display peak luminance (the HLG OOTF). + * All absolute luminance values used here for HLG assume a 1000 + * cd/mยฒ peak display. + * + * The default value for the reference white is a protocol + * addition: it is suggested by Report ITU-R BT.2408-7 and is not + * part of ARIB STD-B67 or BT.2100. + */ + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG = 13, +}; +#endif /* WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ENUM */ + +#ifndef WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ENUM_IS_VALID +#define WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_manager_v1 + * Validate a wp_color_manager_v1 transfer_function value. + * + * @return true on success, false on error. + * @ref wp_color_manager_v1_transfer_function + */ +static inline bool +wp_color_manager_v1_transfer_function_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428: + return version >= 1; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_color_manager_v1 + * @struct wp_color_manager_v1_interface + */ +struct wp_color_manager_v1_interface { + /** + * destroy the color manager + * + * Destroy the wp_color_manager_v1 object. This does not affect + * any other objects in any way. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * create a color management interface for a wl_output + * + * This creates a new wp_color_management_output_v1 object for + * the given wl_output. + * + * See the wp_color_management_output_v1 interface for more + * details. + */ + void (*get_output)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *output); + /** + * create a color management interface for a wl_surface + * + * If a wp_color_management_surface_v1 object already exists for + * the given wl_surface, the protocol error surface_exists is + * raised. + * + * This creates a new color wp_color_management_surface_v1 object + * for the given wl_surface. + * + * See the wp_color_management_surface_v1 interface for more + * details. + */ + void (*get_surface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); + /** + * create a color management feedback interface + * + * This creates a new color + * wp_color_management_surface_feedback_v1 object for the given + * wl_surface. + * + * See the wp_color_management_surface_feedback_v1 interface for + * more details. + */ + void (*get_surface_feedback)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); + /** + * make a new ICC-based image description creator object + * + * Makes a new ICC-based image description creator object with + * all properties initially unset. The client can then use the + * object's interface to define all the required properties for an + * image description and finally create a wp_image_description_v1 + * object. + * + * This request can be used when the compositor advertises + * wp_color_manager_v1.feature.icc_v2_v4. Otherwise this request + * raises the protocol error unsupported_feature. + * @param obj the new creator object + */ + void (*create_icc_creator)(struct wl_client *client, + struct wl_resource *resource, + uint32_t obj); + /** + * make a new parametric image description creator object + * + * Makes a new parametric image description creator object with + * all properties initially unset. The client can then use the + * object's interface to define all the required properties for an + * image description and finally create a wp_image_description_v1 + * object. + * + * This request can be used when the compositor advertises + * wp_color_manager_v1.feature.parametric. Otherwise this request + * raises the protocol error unsupported_feature. + * @param obj the new creator object + */ + void (*create_parametric_creator)(struct wl_client *client, + struct wl_resource *resource, + uint32_t obj); + /** + * create Windows-scRGB image description object + * + * This creates a pre-defined image description for the so-called + * Windows-scRGB stimulus encoding. This comes from the Windows 10 + * handling of its own definition of an scRGB color space for an + * HDR screen driven in BT.2100/PQ signalling mode. + * + * Windows-scRGB uses sRGB (BT.709) color primaries and white + * point. The transfer characteristic is extended linear. + * + * The nominal color channel value range is extended, meaning it + * includes negative and greater than 1.0 values. Negative values + * are used to escape the sRGB color gamut boundaries. To make use + * of the extended range, the client needs to use a pixel format + * that can represent those values, e.g. floating-point 16 bits per + * channel. + * + * Nominal color value R=G=B=0.0 corresponds to BT.2100/PQ system 0 + * cd/mยฒ, and R=G=B=1.0 corresponds to BT.2100/PQ system 80 + * cd/mยฒ. The maximum is R=G=B=125.0 corresponding to 10k cd/mยฒ. + * + * Windows-scRGB is displayed by Windows 10 by converting it to + * BT.2100/PQ, maintaining the CIE 1931 chromaticity and mapping + * the luminance as above. No adjustment is made to the signal to + * account for the viewing conditions. + * + * The reference white level of Windows-scRGB is unknown. If a + * reference white level must be assumed for compositor processing, + * it should be R=G=B=2.5375 corresponding to 203 cd/mยฒ of Report + * ITU-R BT.2408-7. + * + * The target color volume of Windows-scRGB is unknown. The color + * gamut may be anything between sRGB and BT.2100. + * + * Note: EGL_EXT_gl_colorspace_scrgb_linear definition differs from + * Windows-scRGB by using R=G=B=1.0 as the reference white level, + * while Windows-scRGB reference white level is unknown or varies. + * However, it seems probable that Windows implements both + * EGL_EXT_gl_colorspace_scrgb_linear and Vulkan + * VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT as Windows-scRGB. + * + * This request can be used when the compositor advertises + * wp_color_manager_v1.feature.windows_scrgb. Otherwise this + * request raises the protocol error unsupported_feature. + * + * The resulting image description object does not allow + * get_information request. The wp_image_description_v1.ready event + * shall be sent. + */ + void (*create_windows_scrgb)(struct wl_client *client, + struct wl_resource *resource, + uint32_t image_description); +}; + +#define WP_COLOR_MANAGER_V1_SUPPORTED_INTENT 0 +#define WP_COLOR_MANAGER_V1_SUPPORTED_FEATURE 1 +#define WP_COLOR_MANAGER_V1_SUPPORTED_TF_NAMED 2 +#define WP_COLOR_MANAGER_V1_SUPPORTED_PRIMARIES_NAMED 3 +#define WP_COLOR_MANAGER_V1_DONE 4 + +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_SUPPORTED_INTENT_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_SUPPORTED_FEATURE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_SUPPORTED_TF_NAMED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_SUPPORTED_PRIMARIES_NAMED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_DONE_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_GET_OUTPUT_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_GET_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_CREATE_ICC_CREATOR_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_CREATE_PARAMETRIC_CREATOR_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_manager_v1 + */ +#define WP_COLOR_MANAGER_V1_CREATE_WINDOWS_SCRGB_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_color_manager_v1 + * Sends an supported_intent event to the client owning the resource. + * @param resource_ The client's resource + * @param render_intent rendering intent + */ +static inline void +wp_color_manager_v1_send_supported_intent(struct wl_resource *resource_, uint32_t render_intent) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGER_V1_SUPPORTED_INTENT, render_intent); +} + +/** + * @ingroup iface_wp_color_manager_v1 + * Sends an supported_feature event to the client owning the resource. + * @param resource_ The client's resource + * @param feature supported feature + */ +static inline void +wp_color_manager_v1_send_supported_feature(struct wl_resource *resource_, uint32_t feature) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGER_V1_SUPPORTED_FEATURE, feature); +} + +/** + * @ingroup iface_wp_color_manager_v1 + * Sends an supported_tf_named event to the client owning the resource. + * @param resource_ The client's resource + * @param tf Named transfer function + */ +static inline void +wp_color_manager_v1_send_supported_tf_named(struct wl_resource *resource_, uint32_t tf) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGER_V1_SUPPORTED_TF_NAMED, tf); +} + +/** + * @ingroup iface_wp_color_manager_v1 + * Sends an supported_primaries_named event to the client owning the resource. + * @param resource_ The client's resource + * @param primaries Named color primaries + */ +static inline void +wp_color_manager_v1_send_supported_primaries_named(struct wl_resource *resource_, uint32_t primaries) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGER_V1_SUPPORTED_PRIMARIES_NAMED, primaries); +} + +/** + * @ingroup iface_wp_color_manager_v1 + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wp_color_manager_v1_send_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGER_V1_DONE); +} + +/** + * @ingroup iface_wp_color_management_output_v1 + * @struct wp_color_management_output_v1_interface + */ +struct wp_color_management_output_v1_interface { + /** + * destroy the color management output + * + * Destroy the color wp_color_management_output_v1 object. This + * does not affect any remaining protocol objects. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * get the image description of the output + * + * This creates a new wp_image_description_v1 object for the + * current image description of the output. There always is exactly + * one image description active for an output so the client should + * destroy the image description created by earlier invocations of + * this request. This request is usually sent as a reaction to the + * image_description_changed event or when creating a + * wp_color_management_output_v1 object. + * + * The image description of an output represents the color encoding + * the output expects. There might be performance and power + * advantages, as well as improved color reproduction, if a content + * update matches the image description of the output it is being + * shown on. If a content update is shown on any other output than + * the one it matches the image description of, then the color + * reproduction on those outputs might be considerably worse. + * + * The created wp_image_description_v1 object preserves the image + * description of the output from the time the object was created. + * + * The resulting image description object allows get_information + * request. + * + * If this protocol object is inert, the resulting image + * description object shall immediately deliver the + * wp_image_description_v1.failed event with the no_output cause. + * + * If the interface version is inadequate for the output's image + * description, meaning that the client does not support all the + * events needed to deliver the crucial information, the resulting + * image description object shall immediately deliver the + * wp_image_description_v1.failed event with the low_version cause. + * + * Otherwise the object shall immediately deliver the ready event. + */ + void (*get_image_description)(struct wl_client *client, + struct wl_resource *resource, + uint32_t image_description); +}; + +#define WP_COLOR_MANAGEMENT_OUTPUT_V1_IMAGE_DESCRIPTION_CHANGED 0 + +/** + * @ingroup iface_wp_color_management_output_v1 + */ +#define WP_COLOR_MANAGEMENT_OUTPUT_V1_IMAGE_DESCRIPTION_CHANGED_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_color_management_output_v1 + */ +#define WP_COLOR_MANAGEMENT_OUTPUT_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_management_output_v1 + */ +#define WP_COLOR_MANAGEMENT_OUTPUT_V1_GET_IMAGE_DESCRIPTION_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_color_management_output_v1 + * Sends an image_description_changed event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wp_color_management_output_v1_send_image_description_changed(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGEMENT_OUTPUT_V1_IMAGE_DESCRIPTION_CHANGED); +} + +#ifndef WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_ENUM +#define WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_ENUM +/** + * @ingroup iface_wp_color_management_surface_v1 + * protocol errors + */ +enum wp_color_management_surface_v1_error { + /** + * unsupported rendering intent + */ + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_RENDER_INTENT = 0, + /** + * invalid image description + */ + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION = 1, + /** + * forbidden request on inert object + */ + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_INERT = 2, +}; +#endif /* WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_ENUM */ + +#ifndef WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_ENUM_IS_VALID +#define WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_management_surface_v1 + * Validate a wp_color_management_surface_v1 error value. + * + * @return true on success, false on error. + * @ref wp_color_management_surface_v1_error + */ +static inline bool +wp_color_management_surface_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_RENDER_INTENT: + return version >= 1; + case WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION: + return version >= 1; + case WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_INERT: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_color_management_surface_v1 + * @struct wp_color_management_surface_v1_interface + */ +struct wp_color_management_surface_v1_interface { + /** + * destroy the color management interface for a surface + * + * Destroy the wp_color_management_surface_v1 object and do the + * same as unset_image_description. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the surface image description + * + * If this protocol object is inert, the protocol error inert is + * raised. + * + * Set the image description of the underlying surface. The image + * description and rendering intent are double-buffered state, see + * wl_surface.commit. + * + * It is the client's responsibility to understand the image + * description it sets on a surface, and to provide content that + * matches that image description. Compositors might convert images + * to match their own or any other image descriptions. + * + * Image descriptions which are not ready (see + * wp_image_description_v1) are forbidden in this request, and in + * such case the protocol error image_description is raised. + * + * All image descriptions which are ready (see + * wp_image_description_v1) are allowed and must always be accepted + * by the compositor. + * + * A rendering intent provides the client's preference on how + * content colors should be mapped to each output. The + * render_intent value must be one advertised by the compositor + * with wp_color_manager_v1.render_intent event, otherwise the + * protocol error render_intent is raised. + * + * When an image description is set on a surface, the Transfer + * Characteristics of the image description defines the valid range + * of the nominal (real-valued) color channel values. The + * processing of out-of-range color channel values is undefined, + * but compositors are recommended to clamp the values to the valid + * range when possible. + * + * By default, a surface does not have an associated image + * description nor a rendering intent. The handling of color on + * such surfaces is compositor implementation defined. Compositors + * should handle such surfaces as sRGB, but may handle them + * differently if they have specific requirements. + * + * Setting the image description has copy semantics; after this + * request, the image description can be immediately destroyed + * without affecting the pending state of the surface. + * @param render_intent rendering intent + */ + void (*set_image_description)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *image_description, + uint32_t render_intent); + /** + * remove the surface image description + * + * If this protocol object is inert, the protocol error inert is + * raised. + * + * This request removes any image description from the surface. See + * set_image_description for how a compositor handles a surface + * without an image description. This is double-buffered state, see + * wl_surface.commit. + */ + void (*unset_image_description)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_wp_color_management_surface_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_management_surface_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_V1_SET_IMAGE_DESCRIPTION_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_management_surface_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_V1_UNSET_IMAGE_DESCRIPTION_SINCE_VERSION 1 + +#ifndef WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_ENUM +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_ENUM +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + * protocol errors + */ +enum wp_color_management_surface_feedback_v1_error { + /** + * forbidden request on inert object + */ + WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT = 0, + /** + * attempted to use an unsupported feature + */ + WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_UNSUPPORTED_FEATURE = 1, +}; +#endif /* WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_ENUM */ + +#ifndef WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_ENUM_IS_VALID +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + * Validate a wp_color_management_surface_feedback_v1 error value. + * + * @return true on success, false on error. + * @ref wp_color_management_surface_feedback_v1_error + */ +static inline bool +wp_color_management_surface_feedback_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT: + return version >= 1; + case WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_UNSUPPORTED_FEATURE: + return version >= 1; + default: + return false; + } +} +#endif /* WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + * @struct wp_color_management_surface_feedback_v1_interface + */ +struct wp_color_management_surface_feedback_v1_interface { + /** + * destroy the color management interface for a surface + * + * Destroy the wp_color_management_surface_feedback_v1 object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * get the preferred image description + * + * If this protocol object is inert, the protocol error inert is + * raised. + * + * The preferred image description represents the compositor's + * preferred color encoding for this wl_surface at the current + * time. There might be performance and power advantages, as well + * as improved color reproduction, if the image description of a + * content update matches the preferred image description. + * + * This creates a new wp_image_description_v1 object for the + * currently preferred image description for the wl_surface. The + * client should stop using and destroy the image descriptions + * created by earlier invocations of this request for the + * associated wl_surface. This request is usually sent as a + * reaction to the preferred_changed event or when creating a + * wp_color_management_surface_feedback_v1 object if the client is + * capable of adapting to image descriptions. + * + * The created wp_image_description_v1 object preserves the + * preferred image description of the wl_surface from the time the + * object was created. + * + * The resulting image description object allows get_information + * request. + * + * If the image description is parametric, the client should set it + * on its wl_surface only if the image description is an exact + * match with the client content. Particularly if everything else + * matches, but the target color volume is greater than what the + * client needs, the client should create its own parameric image + * description with its exact parameters. + * + * If the interface version is inadequate for the preferred image + * description, meaning that the client does not support all the + * events needed to deliver the crucial information, the resulting + * image description object shall immediately deliver the + * wp_image_description_v1.failed event with the low_version cause, + * otherwise the object shall immediately deliver the ready event. + */ + void (*get_preferred)(struct wl_client *client, + struct wl_resource *resource, + uint32_t image_description); + /** + * get the preferred image description + * + * The same description as for get_preferred applies, except the + * returned image description is guaranteed to be parametric. This + * is meant for clients that can only deal with parametric image + * descriptions. + * + * If the compositor doesn't support parametric image descriptions, + * the unsupported_feature error is emitted. + */ + void (*get_preferred_parametric)(struct wl_client *client, + struct wl_resource *resource, + uint32_t image_description); +}; + +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_PREFERRED_CHANGED 0 + +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_PREFERRED_CHANGED_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_GET_PREFERRED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + */ +#define WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_GET_PREFERRED_PARAMETRIC_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_color_management_surface_feedback_v1 + * Sends an preferred_changed event to the client owning the resource. + * @param resource_ The client's resource + * @param identity image description id number + */ +static inline void +wp_color_management_surface_feedback_v1_send_preferred_changed(struct wl_resource *resource_, uint32_t identity) +{ + wl_resource_post_event(resource_, WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_PREFERRED_CHANGED, identity); +} + +#ifndef WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ENUM +#define WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ENUM +/** + * @ingroup iface_wp_image_description_creator_icc_v1 + * protocol errors + */ +enum wp_image_description_creator_icc_v1_error { + /** + * incomplete parameter set + */ + WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_INCOMPLETE_SET = 0, + /** + * property already set + */ + WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ALREADY_SET = 1, + /** + * fd not seekable and readable + */ + WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_BAD_FD = 2, + /** + * no or too much data + */ + WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_BAD_SIZE = 3, + /** + * offset + length exceeds file size + */ + WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_OUT_OF_FILE = 4, +}; +#endif /* WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ENUM */ + +#ifndef WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ENUM_IS_VALID +#define WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_image_description_creator_icc_v1 + * Validate a wp_image_description_creator_icc_v1 error value. + * + * @return true on success, false on error. + * @ref wp_image_description_creator_icc_v1_error + */ +static inline bool +wp_image_description_creator_icc_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_INCOMPLETE_SET: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ALREADY_SET: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_BAD_FD: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_BAD_SIZE: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_OUT_OF_FILE: + return version >= 1; + default: + return false; + } +} +#endif /* WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_image_description_creator_icc_v1 + * @struct wp_image_description_creator_icc_v1_interface + */ +struct wp_image_description_creator_icc_v1_interface { + /** + * Create the image description object from ICC data + * + * Create an image description object based on the ICC + * information previously set on this object. A compositor must + * parse the ICC data in some undefined but finite amount of time. + * + * The completeness of the parameter set is verified. If the set is + * not complete, the protocol error incomplete_set is raised. For + * the definition of a complete set, see the description of this + * interface. + * + * If the particular combination of the information is not + * supported by the compositor, the resulting image description + * object shall immediately deliver the + * wp_image_description_v1.failed event with the 'unsupported' + * cause. If a valid image description was created from the + * information, the wp_image_description_v1.ready event will + * eventually be sent instead. + * + * This request destroys the wp_image_description_creator_icc_v1 + * object. + * + * The resulting image description object does not allow + * get_information request. + */ + void (*create)(struct wl_client *client, + struct wl_resource *resource, + uint32_t image_description); + /** + * set the ICC profile file + * + * Sets the ICC profile file to be used as the basis of the image + * description. + * + * The data shall be found through the given fd at the given + * offset, having the given length. The fd must be seekable and + * readable. Violating these requirements raises the bad_fd + * protocol error. + * + * If reading the data fails due to an error independent of the + * client, the compositor shall send the + * wp_image_description_v1.failed event on the created + * wp_image_description_v1 with the 'operating_system' cause. + * + * The maximum size of the ICC profile is 32 MB. If length is + * greater than that or zero, the protocol error bad_size is + * raised. If offset + length exceeds the file size, the protocol + * error out_of_file is raised. + * + * A compositor may read the file at any time starting from this + * request and only until whichever happens first: - If create + * request was issued, the wp_image_description_v1 object delivers + * either failed or ready event; or - if create request was not + * issued, this wp_image_description_creator_icc_v1 object is + * destroyed. + * + * A compositor shall not modify the contents of the file, and the + * fd may be sealed for writes and size changes. The client must + * ensure to its best ability that the data does not change while + * the compositor is reading it. + * + * The data must represent a valid ICC profile. The ICC profile + * version must be 2 or 4, it must be a 3 channel profile and the + * class must be Display or ColorSpace. Violating these + * requirements will not result in a protocol error, but will + * eventually send the wp_image_description_v1.failed event on the + * created wp_image_description_v1 with the 'unsupported' cause. + * + * See the International Color Consortium specification ICC.1:2022 + * for more details about ICC profiles. + * + * If ICC file has already been set on this object, the protocol + * error already_set is raised. + * @param icc_profile ICC profile + * @param offset byte offset in fd to start of ICC data + * @param length length of ICC data in bytes + */ + void (*set_icc_file)(struct wl_client *client, + struct wl_resource *resource, + int32_t icc_profile, + uint32_t offset, + uint32_t length); +}; + + +/** + * @ingroup iface_wp_image_description_creator_icc_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_CREATE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_icc_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_ICC_V1_SET_ICC_FILE_SINCE_VERSION 1 + +#ifndef WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ENUM +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ENUM +/** + * @ingroup iface_wp_image_description_creator_params_v1 + * protocol errors + */ +enum wp_image_description_creator_params_v1_error { + /** + * incomplete parameter set + */ + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET = 0, + /** + * property already set + */ + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET = 1, + /** + * request not supported + */ + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE = 2, + /** + * invalid transfer characteristic + */ + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF = 3, + /** + * invalid primaries named + */ + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED = 4, + /** + * invalid luminance value or range + */ + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE = 5, +}; +#endif /* WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ENUM */ + +#ifndef WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ENUM_IS_VALID +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_image_description_creator_params_v1 + * Validate a wp_image_description_creator_params_v1 error value. + * + * @return true on success, false on error. + * @ref wp_image_description_creator_params_v1_error + */ +static inline bool +wp_image_description_creator_params_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED: + return version >= 1; + case WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE: + return version >= 1; + default: + return false; + } +} +#endif /* WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_image_description_creator_params_v1 + * @struct wp_image_description_creator_params_v1_interface + */ +struct wp_image_description_creator_params_v1_interface { + /** + * Create the image description object using params + * + * Create an image description object based on the parameters + * previously set on this object. + * + * The completeness of the parameter set is verified. If the set is + * not complete, the protocol error incomplete_set is raised. For + * the definition of a complete set, see the description of this + * interface. + * + * The protocol error invalid_luminance is raised if any of the + * following requirements is not met: - When max_cll is set, it + * must be greater than min L and less or equal to max L of the + * mastering luminance range. - When max_fall is set, it must be + * greater than min L and less or equal to max L of the mastering + * luminance range. - When both max_cll and max_fall are set, + * max_fall must be less or equal to max_cll. + * + * If the particular combination of the parameter set is not + * supported by the compositor, the resulting image description + * object shall immediately deliver the + * wp_image_description_v1.failed event with the 'unsupported' + * cause. If a valid image description was created from the + * parameter set, the wp_image_description_v1.ready event will + * eventually be sent instead. + * + * This request destroys the wp_image_description_creator_params_v1 + * object. + * + * The resulting image description object does not allow + * get_information request. + */ + void (*create)(struct wl_client *client, + struct wl_resource *resource, + uint32_t image_description); + /** + * named transfer characteristic + * + * Sets the transfer characteristic using explicitly enumerated + * named functions. + * + * When the resulting image description is attached to an image, + * the content should be encoded and decoded according to the + * industry standard practices for the transfer characteristic. + * + * Only names advertised with wp_color_manager_v1 event + * supported_tf_named are allowed. Other values shall raise the + * protocol error invalid_tf. + * + * If transfer characteristic has already been set on this object, + * the protocol error already_set is raised. + * @param tf named transfer function + */ + void (*set_tf_named)(struct wl_client *client, + struct wl_resource *resource, + uint32_t tf); + /** + * transfer characteristic as a power curve + * + * Sets the color component transfer characteristic to a power + * curve with the given exponent. Negative values are handled by + * mirroring the positive half of the curve through the origin. The + * valid domain and range of the curve are all finite real numbers. + * This curve represents the conversion from electrical to optical + * color channel values. + * + * When the resulting image description is attached to an image, + * the content should be encoded with the inverse of the power + * curve. + * + * The curve exponent shall be multiplied by 10000 to get the + * argument eexp value to carry the precision of 4 decimals. + * + * The curve exponent must be at least 1.0 and at most 10.0. + * Otherwise the protocol error invalid_tf is raised. + * + * If transfer characteristic has already been set on this object, + * the protocol error already_set is raised. + * + * This request can be used when the compositor advertises + * wp_color_manager_v1.feature.set_tf_power. Otherwise this request + * raises the protocol error unsupported_feature. + * @param eexp the exponent * 10000 + */ + void (*set_tf_power)(struct wl_client *client, + struct wl_resource *resource, + uint32_t eexp); + /** + * named primaries + * + * Sets the color primaries and white point using explicitly + * named sets. This describes the primary color volume which is the + * basis for color value encoding. + * + * Only names advertised with wp_color_manager_v1 event + * supported_primaries_named are allowed. Other values shall raise + * the protocol error invalid_primaries_named. + * + * If primaries have already been set on this object, the protocol + * error already_set is raised. + * @param primaries named primaries + */ + void (*set_primaries_named)(struct wl_client *client, + struct wl_resource *resource, + uint32_t primaries); + /** + * primaries as chromaticity coordinates + * + * Sets the color primaries and white point using CIE 1931 xy + * chromaticity coordinates. This describes the primary color + * volume which is the basis for color value encoding. + * + * Each coordinate value is multiplied by 1 million to get the + * argument value to carry precision of 6 decimals. + * + * If primaries have already been set on this object, the protocol + * error already_set is raised. + * + * This request can be used if the compositor advertises + * wp_color_manager_v1.feature.set_primaries. Otherwise this + * request raises the protocol error unsupported_feature. + * @param r_x Red x * 1M + * @param r_y Red y * 1M + * @param g_x Green x * 1M + * @param g_y Green y * 1M + * @param b_x Blue x * 1M + * @param b_y Blue y * 1M + * @param w_x White x * 1M + * @param w_y White y * 1M + */ + void (*set_primaries)(struct wl_client *client, + struct wl_resource *resource, + int32_t r_x, + int32_t r_y, + int32_t g_x, + int32_t g_y, + int32_t b_x, + int32_t b_y, + int32_t w_x, + int32_t w_y); + /** + * primary color volume luminance range and reference white + * + * Sets the primary color volume luminance range and the + * reference white luminance level. These values include the + * minimum display emission and ambient flare luminances, assumed + * to be optically additive and have the chromaticity of the + * primary color volume white point. + * + * The default luminances from + * https://www.color.org/chardata/rgb/srgb.xalter are - primary + * color volume minimum: 0.2 cd/mยฒ - primary color volume maximum: + * 80 cd/mยฒ - reference white: 80 cd/mยฒ + * + * Setting a named transfer characteristic can imply other default + * luminances. + * + * The default luminances get overwritten when this request is + * used. With transfer_function.st2084_pq the given 'max_lum' value + * is ignored, and 'max_lum' is taken as 'min_lum' + 10000 cd/mยฒ. + * + * 'min_lum' and 'max_lum' specify the minimum and maximum + * luminances of the primary color volume as reproduced by the + * targeted display. + * + * 'reference_lum' specifies the luminance of the reference white + * as reproduced by the targeted display, and reflects the targeted + * viewing environment. + * + * Compositors should make sure that all content is anchored, + * meaning that an input signal level of 'reference_lum' on one + * image description and another input signal level of + * 'reference_lum' on another image description should produce the + * same output level, even though the 'reference_lum' on both image + * representations can be different. + * + * 'reference_lum' may be higher than 'max_lum'. In that case + * reaching the reference white output level in image content + * requires the 'extended_target_volume' feature support. + * + * If 'max_lum' or 'reference_lum' are less than or equal to + * 'min_lum', the protocol error invalid_luminance is raised. + * + * The minimum luminance is multiplied by 10000 to get the argument + * 'min_lum' value and carries precision of 4 decimals. The maximum + * luminance and reference white luminance values are unscaled. + * + * If the primary color volume luminance range and the reference + * white luminance level have already been set on this object, the + * protocol error already_set is raised. + * + * This request can be used if the compositor advertises + * wp_color_manager_v1.feature.set_luminances. Otherwise this + * request raises the protocol error unsupported_feature. + * @param min_lum minimum luminance (cd/mยฒ) * 10000 + * @param max_lum maximum luminance (cd/mยฒ) + * @param reference_lum reference white luminance (cd/mยฒ) + */ + void (*set_luminances)(struct wl_client *client, + struct wl_resource *resource, + uint32_t min_lum, + uint32_t max_lum, + uint32_t reference_lum); + /** + * mastering display primaries as chromaticity coordinates + * + * Provides the color primaries and white point of the mastering + * display using CIE 1931 xy chromaticity coordinates. This is + * compatible with the SMPTE ST 2086 definition of HDR static + * metadata. + * + * The mastering display primaries and mastering display luminances + * define the target color volume. + * + * If mastering display primaries are not explicitly set, the + * target color volume is assumed to have the same primaries as the + * primary color volume. + * + * The target color volume is defined by all tristimulus values + * between 0.0 and 1.0 (inclusive) of the color space defined by + * the given mastering display primaries and white point. The + * colorimetry is identical between the container color space and + * the mastering display color space, including that no chromatic + * adaptation is applied even if the white points differ. + * + * The target color volume can exceed the primary color volume to + * allow for a greater color volume with an existing color space + * definition (for example scRGB). It can be smaller than the + * primary color volume to minimize gamut and tone mapping + * distances for big color spaces (HDR metadata). + * + * To make use of the entire target color volume a suitable pixel + * format has to be chosen (e.g. floating point to exceed the + * primary color volume, or abusing limited quantization range as + * with xvYCC). + * + * Each coordinate value is multiplied by 1 million to get the + * argument value to carry precision of 6 decimals. + * + * If mastering display primaries have already been set on this + * object, the protocol error already_set is raised. + * + * This request can be used if the compositor advertises + * wp_color_manager_v1.feature.set_mastering_display_primaries. + * Otherwise this request raises the protocol error + * unsupported_feature. The advertisement implies support only for + * target color volumes fully contained within the primary color + * volume. + * + * If a compositor additionally supports target color volume + * exceeding the primary color volume, it must advertise + * wp_color_manager_v1.feature.extended_target_volume. If a client + * uses target color volume exceeding the primary color volume and + * the compositor does not support it, the result is implementation + * defined. Compositors are recommended to detect this case and + * fail the image description gracefully, but it may as well result + * in color artifacts. + * @param r_x Red x * 1M + * @param r_y Red y * 1M + * @param g_x Green x * 1M + * @param g_y Green y * 1M + * @param b_x Blue x * 1M + * @param b_y Blue y * 1M + * @param w_x White x * 1M + * @param w_y White y * 1M + */ + void (*set_mastering_display_primaries)(struct wl_client *client, + struct wl_resource *resource, + int32_t r_x, + int32_t r_y, + int32_t g_x, + int32_t g_y, + int32_t b_x, + int32_t b_y, + int32_t w_x, + int32_t w_y); + /** + * display mastering luminance range + * + * Sets the luminance range that was used during the content + * mastering process as the minimum and maximum absolute luminance + * L. These values include the minimum display emission and ambient + * flare luminances, assumed to be optically additive and have the + * chromaticity of the primary color volume white point. This + * should be compatible with the SMPTE ST 2086 definition of HDR + * static metadata. + * + * The mastering display primaries and mastering display luminances + * define the target color volume. + * + * If mastering luminances are not explicitly set, the target color + * volume is assumed to have the same min and max luminances as the + * primary color volume. + * + * If max L is less than or equal to min L, the protocol error + * invalid_luminance is raised. + * + * Min L value is multiplied by 10000 to get the argument min_lum + * value and carry precision of 4 decimals. Max L value is unscaled + * for max_lum. + * + * This request can be used if the compositor advertises + * wp_color_manager_v1.feature.set_mastering_display_primaries. + * Otherwise this request raises the protocol error + * unsupported_feature. The advertisement implies support only for + * target color volumes fully contained within the primary color + * volume. + * + * If a compositor additionally supports target color volume + * exceeding the primary color volume, it must advertise + * wp_color_manager_v1.feature.extended_target_volume. If a client + * uses target color volume exceeding the primary color volume and + * the compositor does not support it, the result is implementation + * defined. Compositors are recommended to detect this case and + * fail the image description gracefully, but it may as well result + * in color artifacts. + * @param min_lum min L (cd/mยฒ) * 10000 + * @param max_lum max L (cd/mยฒ) + */ + void (*set_mastering_luminance)(struct wl_client *client, + struct wl_resource *resource, + uint32_t min_lum, + uint32_t max_lum); + /** + * maximum content light level + * + * Sets the maximum content light level (max_cll) as defined by + * CTA-861-H. + * + * max_cll is undefined by default. + * @param max_cll Maximum content light level (cd/mยฒ) + */ + void (*set_max_cll)(struct wl_client *client, + struct wl_resource *resource, + uint32_t max_cll); + /** + * maximum frame-average light level + * + * Sets the maximum frame-average light level (max_fall) as + * defined by CTA-861-H. + * + * max_fall is undefined by default. + * @param max_fall Maximum frame-average light level (cd/mยฒ) + */ + void (*set_max_fall)(struct wl_client *client, + struct wl_resource *resource, + uint32_t max_fall); +}; + + +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_CREATE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_TF_NAMED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_TF_POWER_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_PRIMARIES_NAMED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_PRIMARIES_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_LUMINANCES_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_MASTERING_DISPLAY_PRIMARIES_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_MASTERING_LUMINANCE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_MAX_CLL_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_creator_params_v1 + */ +#define WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_SET_MAX_FALL_SINCE_VERSION 1 + +#ifndef WP_IMAGE_DESCRIPTION_V1_ERROR_ENUM +#define WP_IMAGE_DESCRIPTION_V1_ERROR_ENUM +/** + * @ingroup iface_wp_image_description_v1 + * protocol errors + */ +enum wp_image_description_v1_error { + /** + * attempted to use an object which is not ready + */ + WP_IMAGE_DESCRIPTION_V1_ERROR_NOT_READY = 0, + /** + * get_information not allowed + */ + WP_IMAGE_DESCRIPTION_V1_ERROR_NO_INFORMATION = 1, +}; +#endif /* WP_IMAGE_DESCRIPTION_V1_ERROR_ENUM */ + +#ifndef WP_IMAGE_DESCRIPTION_V1_ERROR_ENUM_IS_VALID +#define WP_IMAGE_DESCRIPTION_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_image_description_v1 + * Validate a wp_image_description_v1 error value. + * + * @return true on success, false on error. + * @ref wp_image_description_v1_error + */ +static inline bool +wp_image_description_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_IMAGE_DESCRIPTION_V1_ERROR_NOT_READY: + return version >= 1; + case WP_IMAGE_DESCRIPTION_V1_ERROR_NO_INFORMATION: + return version >= 1; + default: + return false; + } +} +#endif /* WP_IMAGE_DESCRIPTION_V1_ERROR_ENUM_IS_VALID */ + +#ifndef WP_IMAGE_DESCRIPTION_V1_CAUSE_ENUM +#define WP_IMAGE_DESCRIPTION_V1_CAUSE_ENUM +/** + * @ingroup iface_wp_image_description_v1 + * generic reason for failure + */ +enum wp_image_description_v1_cause { + /** + * interface version too low + */ + WP_IMAGE_DESCRIPTION_V1_CAUSE_LOW_VERSION = 0, + /** + * unsupported image description data + */ + WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED = 1, + /** + * error independent of the client + */ + WP_IMAGE_DESCRIPTION_V1_CAUSE_OPERATING_SYSTEM = 2, + /** + * the relevant output no longer exists + */ + WP_IMAGE_DESCRIPTION_V1_CAUSE_NO_OUTPUT = 3, +}; +#endif /* WP_IMAGE_DESCRIPTION_V1_CAUSE_ENUM */ + +#ifndef WP_IMAGE_DESCRIPTION_V1_CAUSE_ENUM_IS_VALID +#define WP_IMAGE_DESCRIPTION_V1_CAUSE_ENUM_IS_VALID +/** + * @ingroup iface_wp_image_description_v1 + * Validate a wp_image_description_v1 cause value. + * + * @return true on success, false on error. + * @ref wp_image_description_v1_cause + */ +static inline bool +wp_image_description_v1_cause_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_IMAGE_DESCRIPTION_V1_CAUSE_LOW_VERSION: + return version >= 1; + case WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED: + return version >= 1; + case WP_IMAGE_DESCRIPTION_V1_CAUSE_OPERATING_SYSTEM: + return version >= 1; + case WP_IMAGE_DESCRIPTION_V1_CAUSE_NO_OUTPUT: + return version >= 1; + default: + return false; + } +} +#endif /* WP_IMAGE_DESCRIPTION_V1_CAUSE_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_image_description_v1 + * @struct wp_image_description_v1_interface + */ +struct wp_image_description_v1_interface { + /** + * destroy the image description + * + * Destroy this object. It is safe to destroy an object which is + * not ready. + * + * Destroying a wp_image_description_v1 object has no side-effects, + * not even if a + * wp_color_management_surface_v1.set_image_description has not yet + * been followed by a wl_surface.commit. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * get information about the image description + * + * Creates a wp_image_description_info_v1 object which delivers + * the information that makes up the image description. + * + * Not all image description protocol objects allow get_information + * request. Whether it is allowed or not is defined by the request + * that created the object. If get_information is not allowed, the + * protocol error no_information is raised. + */ + void (*get_information)(struct wl_client *client, + struct wl_resource *resource, + uint32_t information); +}; + +#define WP_IMAGE_DESCRIPTION_V1_FAILED 0 +#define WP_IMAGE_DESCRIPTION_V1_READY 1 + +/** + * @ingroup iface_wp_image_description_v1 + */ +#define WP_IMAGE_DESCRIPTION_V1_FAILED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_v1 + */ +#define WP_IMAGE_DESCRIPTION_V1_READY_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_image_description_v1 + */ +#define WP_IMAGE_DESCRIPTION_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_v1 + */ +#define WP_IMAGE_DESCRIPTION_V1_GET_INFORMATION_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_image_description_v1 + * Sends an failed event to the client owning the resource. + * @param resource_ The client's resource + * @param cause generic reason + * @param msg ad hoc human-readable explanation + */ +static inline void +wp_image_description_v1_send_failed(struct wl_resource *resource_, uint32_t cause, const char *msg) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_V1_FAILED, cause, msg); +} + +/** + * @ingroup iface_wp_image_description_v1 + * Sends an ready event to the client owning the resource. + * @param resource_ The client's resource + * @param identity image description id number + */ +static inline void +wp_image_description_v1_send_ready(struct wl_resource *resource_, uint32_t identity) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_V1_READY, identity); +} + +#define WP_IMAGE_DESCRIPTION_INFO_V1_DONE 0 +#define WP_IMAGE_DESCRIPTION_INFO_V1_ICC_FILE 1 +#define WP_IMAGE_DESCRIPTION_INFO_V1_PRIMARIES 2 +#define WP_IMAGE_DESCRIPTION_INFO_V1_PRIMARIES_NAMED 3 +#define WP_IMAGE_DESCRIPTION_INFO_V1_TF_POWER 4 +#define WP_IMAGE_DESCRIPTION_INFO_V1_TF_NAMED 5 +#define WP_IMAGE_DESCRIPTION_INFO_V1_LUMINANCES 6 +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_PRIMARIES 7 +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_LUMINANCE 8 +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_MAX_CLL 9 +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_MAX_FALL 10 + +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_ICC_FILE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_PRIMARIES_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_PRIMARIES_NAMED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_TF_POWER_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_TF_NAMED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_LUMINANCES_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_PRIMARIES_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_LUMINANCE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_MAX_CLL_SINCE_VERSION 1 +/** + * @ingroup iface_wp_image_description_info_v1 + */ +#define WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_MAX_FALL_SINCE_VERSION 1 + + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wp_image_description_info_v1_send_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_DONE); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an icc_file event to the client owning the resource. + * @param resource_ The client's resource + * @param icc ICC profile file descriptor + * @param icc_size ICC profile size, in bytes + */ +static inline void +wp_image_description_info_v1_send_icc_file(struct wl_resource *resource_, int32_t icc, uint32_t icc_size) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_ICC_FILE, icc, icc_size); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an primaries event to the client owning the resource. + * @param resource_ The client's resource + * @param r_x Red x * 1M + * @param r_y Red y * 1M + * @param g_x Green x * 1M + * @param g_y Green y * 1M + * @param b_x Blue x * 1M + * @param b_y Blue y * 1M + * @param w_x White x * 1M + * @param w_y White y * 1M + */ +static inline void +wp_image_description_info_v1_send_primaries(struct wl_resource *resource_, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_PRIMARIES, r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an primaries_named event to the client owning the resource. + * @param resource_ The client's resource + * @param primaries named primaries + */ +static inline void +wp_image_description_info_v1_send_primaries_named(struct wl_resource *resource_, uint32_t primaries) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_PRIMARIES_NAMED, primaries); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an tf_power event to the client owning the resource. + * @param resource_ The client's resource + * @param eexp the exponent * 10000 + */ +static inline void +wp_image_description_info_v1_send_tf_power(struct wl_resource *resource_, uint32_t eexp) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_TF_POWER, eexp); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an tf_named event to the client owning the resource. + * @param resource_ The client's resource + * @param tf named transfer function + */ +static inline void +wp_image_description_info_v1_send_tf_named(struct wl_resource *resource_, uint32_t tf) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_TF_NAMED, tf); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an luminances event to the client owning the resource. + * @param resource_ The client's resource + * @param min_lum minimum luminance (cd/mยฒ) * 10000 + * @param max_lum maximum luminance (cd/mยฒ) + * @param reference_lum reference white luminance (cd/mยฒ) + */ +static inline void +wp_image_description_info_v1_send_luminances(struct wl_resource *resource_, uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_LUMINANCES, min_lum, max_lum, reference_lum); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an target_primaries event to the client owning the resource. + * @param resource_ The client's resource + * @param r_x Red x * 1M + * @param r_y Red y * 1M + * @param g_x Green x * 1M + * @param g_y Green y * 1M + * @param b_x Blue x * 1M + * @param b_y Blue y * 1M + * @param w_x White x * 1M + * @param w_y White y * 1M + */ +static inline void +wp_image_description_info_v1_send_target_primaries(struct wl_resource *resource_, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_PRIMARIES, r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an target_luminance event to the client owning the resource. + * @param resource_ The client's resource + * @param min_lum min L (cd/mยฒ) * 10000 + * @param max_lum max L (cd/mยฒ) + */ +static inline void +wp_image_description_info_v1_send_target_luminance(struct wl_resource *resource_, uint32_t min_lum, uint32_t max_lum) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_LUMINANCE, min_lum, max_lum); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an target_max_cll event to the client owning the resource. + * @param resource_ The client's resource + * @param max_cll Maximum content light-level (cd/mยฒ) + */ +static inline void +wp_image_description_info_v1_send_target_max_cll(struct wl_resource *resource_, uint32_t max_cll) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_MAX_CLL, max_cll); +} + +/** + * @ingroup iface_wp_image_description_info_v1 + * Sends an target_max_fall event to the client owning the resource. + * @param resource_ The client's resource + * @param max_fall Maximum frame-average light level (cd/mยฒ) + */ +static inline void +wp_image_description_info_v1_send_target_max_fall(struct wl_resource *resource_, uint32_t max_fall) +{ + wl_resource_post_event(resource_, WP_IMAGE_DESCRIPTION_INFO_V1_TARGET_MAX_FALL, max_fall); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/desktop-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/desktop-v1-protocol.c new file mode 100644 index 000000000..9ed8996dd --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/desktop-v1-protocol.c @@ -0,0 +1,64 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright 2026 The412Banner + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_surface_interface; + +static const struct wl_interface *banner_desktop_v1_types[] = { + NULL, + &wl_surface_interface, + &wl_surface_interface, + NULL, + NULL, + NULL, +}; + +static const struct wl_message banner_desktop_v1_requests[] = { + { "destroy", "", banner_desktop_v1_types + 0 }, + { "set_desktop", "o", banner_desktop_v1_types + 1 }, + { "set_window", "ouii", banner_desktop_v1_types + 2 }, + { "set_zorder", "a", banner_desktop_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface banner_desktop_v1_interface = { + "banner_desktop_v1", 1, + 4, banner_desktop_v1_requests, + 0, NULL, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/desktop-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/desktop-v1-server-protocol.h new file mode 100644 index 000000000..e0e9e7b97 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/desktop-v1-server-protocol.h @@ -0,0 +1,142 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef WNC_DESKTOP_V1_SERVER_PROTOCOL_H +#define WNC_DESKTOP_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_banner_desktop_v1 The banner_desktop_v1 protocol + * @section page_ifaces_banner_desktop_v1 Interfaces + * - @subpage page_iface_banner_desktop_v1 - Windows virtual desktop layout + * @section page_copyright_banner_desktop_v1 Copyright + *
+ *
+ * Copyright 2026 The412Banner
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct banner_desktop_v1; +struct wl_surface; + +#ifndef WNC_DESKTOP_V1_INTERFACE +#define WNC_DESKTOP_V1_INTERFACE +/** + * @page page_iface_banner_desktop_v1 banner_desktop_v1 + * @section page_iface_banner_desktop_v1_desc Description + * + * Each Wine process is a separate Wayland client, so a Windows virtual + * desktop can't be built from subsurfaces. Instead the process that owns + * the desktop window marks one surface as the desktop, and every process + * reports where its top-level windows sit on that desktop and the global + * window stacking order. The compositor draws the desktop at the bottom, + * the windows at the reported positions, and sends pointer and keyboard + * input to the desktop surface; Wine routes it to the right window. + * @section page_iface_banner_desktop_v1_api API + * See @ref iface_banner_desktop_v1. + */ +/** + * @defgroup iface_banner_desktop_v1 The banner_desktop_v1 interface + * + * Each Wine process is a separate Wayland client, so a Windows virtual + * desktop can't be built from subsurfaces. Instead the process that owns + * the desktop window marks one surface as the desktop, and every process + * reports where its top-level windows sit on that desktop and the global + * window stacking order. The compositor draws the desktop at the bottom, + * the windows at the reported positions, and sends pointer and keyboard + * input to the desktop surface; Wine routes it to the right window. + */ +extern const struct wl_interface banner_desktop_v1_interface; +#endif + +/** + * @ingroup iface_banner_desktop_v1 + * @struct banner_desktop_v1_interface + */ +struct banner_desktop_v1_interface { + /** + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * mark a surface as the virtual desktop + * + * The surface is drawn at the origin, below every window, and + * sets the size of the desktop. + */ + void (*set_desktop)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface); + /** + * place a top-level window on the desktop + * + * Position of the top-left corner of the surface in desktop + * coordinates, and the window handle used by set_zorder. + */ + void (*set_window)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t hwnd, + int32_t x, + int32_t y); + /** + * report the desktop's window stacking order + * + * Visible top-level window handles as 32-bit values, topmost + * first. + */ + void (*set_zorder)(struct wl_client *client, + struct wl_resource *resource, + struct wl_array *hwnds); +}; + + +/** + * @ingroup iface_banner_desktop_v1 + */ +#define WNC_DESKTOP_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_banner_desktop_v1 + */ +#define WNC_DESKTOP_V1_SET_DESKTOP_SINCE_VERSION 1 +/** + * @ingroup iface_banner_desktop_v1 + */ +#define WNC_DESKTOP_V1_SET_WINDOW_SINCE_VERSION 1 +/** + * @ingroup iface_banner_desktop_v1 + */ +#define WNC_DESKTOP_V1_SET_ZORDER_SINCE_VERSION 1 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-client-protocol.h b/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-client-protocol.h new file mode 100644 index 000000000..e7c4665f3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-client-protocol.h @@ -0,0 +1,1153 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef LINUX_DMABUF_V1_CLIENT_PROTOCOL_H +#define LINUX_DMABUF_V1_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_linux_dmabuf_v1 The linux_dmabuf_v1 protocol + * @section page_ifaces_linux_dmabuf_v1 Interfaces + * - @subpage page_iface_zwp_linux_dmabuf_v1 - factory for creating dmabuf-based wl_buffers + * - @subpage page_iface_zwp_linux_buffer_params_v1 - parameters for creating a dmabuf-based wl_buffer + * - @subpage page_iface_zwp_linux_dmabuf_feedback_v1 - dmabuf feedback + * @section page_copyright_linux_dmabuf_v1 Copyright + *
+ *
+ * Copyright ยฉ 2014, 2015 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_buffer; +struct wl_surface; +struct zwp_linux_buffer_params_v1; +struct zwp_linux_dmabuf_feedback_v1; +struct zwp_linux_dmabuf_v1; + +#ifndef ZWP_LINUX_DMABUF_V1_INTERFACE +#define ZWP_LINUX_DMABUF_V1_INTERFACE +/** + * @page page_iface_zwp_linux_dmabuf_v1 zwp_linux_dmabuf_v1 + * @section page_iface_zwp_linux_dmabuf_v1_desc Description + * + * This interface offers ways to create generic dmabuf-based wl_buffers. + * + * For more information about dmabuf, see: + * https://www.kernel.org/doc/html/next/userspace-api/dma-buf-alloc-exchange.html + * + * Clients can use the get_surface_feedback request to get dmabuf feedback + * for a particular surface. If the client wants to retrieve feedback not + * tied to a surface, they can use the get_default_feedback request. + * + * The following are required from clients: + * + * - Clients must ensure that either all data in the dma-buf is + * coherent for all subsequent read access or that coherency is + * correctly handled by the underlying kernel-side dma-buf + * implementation. + * + * - Don't make any more attachments after sending the buffer to the + * compositor. Making more attachments later increases the risk of + * the compositor not being able to use (re-import) an existing + * dmabuf-based wl_buffer. + * + * The underlying graphics stack must ensure the following: + * + * - The dmabuf file descriptors relayed to the server will stay valid + * for the whole lifetime of the wl_buffer. This means the server may + * at any time use those fds to import the dmabuf into any kernel + * sub-system that might accept it. + * + * However, when the underlying graphics stack fails to deliver the + * promise, because of e.g. a device hot-unplug which raises internal + * errors, after the wl_buffer has been successfully created the + * compositor must not raise protocol errors to the client when dmabuf + * import later fails. + * + * To create a wl_buffer from one or more dmabufs, a client creates a + * zwp_linux_buffer_params_v1 object with a zwp_linux_dmabuf_v1.create_params + * request. All planes required by the intended format are added with + * the 'add' request. Finally, a 'create' or 'create_immed' request is + * issued, which has the following outcome depending on the import success. + * + * The 'create' request, + * - on success, triggers a 'created' event which provides the final + * wl_buffer to the client. + * - on failure, triggers a 'failed' event to convey that the server + * cannot use the dmabufs received from the client. + * + * For the 'create_immed' request, + * - on success, the server immediately imports the added dmabufs to + * create a wl_buffer. No event is sent from the server in this case. + * - on failure, the server can choose to either: + * - terminate the client by raising a fatal error. + * - mark the wl_buffer as failed, and send a 'failed' event to the + * client. If the client uses a failed wl_buffer as an argument to any + * request, the behaviour is compositor implementation-defined. + * + * For all DRM formats and unless specified in another protocol extension, + * pre-multiplied alpha is used for pixel values. + * + * Unless specified otherwise in another protocol extension, implicit + * synchronization is used. In other words, compositors and clients must + * wait and signal fences implicitly passed via the DMA-BUF's reservation + * mechanism. + * @section page_iface_zwp_linux_dmabuf_v1_api API + * See @ref iface_zwp_linux_dmabuf_v1. + */ +/** + * @defgroup iface_zwp_linux_dmabuf_v1 The zwp_linux_dmabuf_v1 interface + * + * This interface offers ways to create generic dmabuf-based wl_buffers. + * + * For more information about dmabuf, see: + * https://www.kernel.org/doc/html/next/userspace-api/dma-buf-alloc-exchange.html + * + * Clients can use the get_surface_feedback request to get dmabuf feedback + * for a particular surface. If the client wants to retrieve feedback not + * tied to a surface, they can use the get_default_feedback request. + * + * The following are required from clients: + * + * - Clients must ensure that either all data in the dma-buf is + * coherent for all subsequent read access or that coherency is + * correctly handled by the underlying kernel-side dma-buf + * implementation. + * + * - Don't make any more attachments after sending the buffer to the + * compositor. Making more attachments later increases the risk of + * the compositor not being able to use (re-import) an existing + * dmabuf-based wl_buffer. + * + * The underlying graphics stack must ensure the following: + * + * - The dmabuf file descriptors relayed to the server will stay valid + * for the whole lifetime of the wl_buffer. This means the server may + * at any time use those fds to import the dmabuf into any kernel + * sub-system that might accept it. + * + * However, when the underlying graphics stack fails to deliver the + * promise, because of e.g. a device hot-unplug which raises internal + * errors, after the wl_buffer has been successfully created the + * compositor must not raise protocol errors to the client when dmabuf + * import later fails. + * + * To create a wl_buffer from one or more dmabufs, a client creates a + * zwp_linux_buffer_params_v1 object with a zwp_linux_dmabuf_v1.create_params + * request. All planes required by the intended format are added with + * the 'add' request. Finally, a 'create' or 'create_immed' request is + * issued, which has the following outcome depending on the import success. + * + * The 'create' request, + * - on success, triggers a 'created' event which provides the final + * wl_buffer to the client. + * - on failure, triggers a 'failed' event to convey that the server + * cannot use the dmabufs received from the client. + * + * For the 'create_immed' request, + * - on success, the server immediately imports the added dmabufs to + * create a wl_buffer. No event is sent from the server in this case. + * - on failure, the server can choose to either: + * - terminate the client by raising a fatal error. + * - mark the wl_buffer as failed, and send a 'failed' event to the + * client. If the client uses a failed wl_buffer as an argument to any + * request, the behaviour is compositor implementation-defined. + * + * For all DRM formats and unless specified in another protocol extension, + * pre-multiplied alpha is used for pixel values. + * + * Unless specified otherwise in another protocol extension, implicit + * synchronization is used. In other words, compositors and clients must + * wait and signal fences implicitly passed via the DMA-BUF's reservation + * mechanism. + */ +extern const struct wl_interface zwp_linux_dmabuf_v1_interface; +#endif +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_INTERFACE +#define ZWP_LINUX_BUFFER_PARAMS_V1_INTERFACE +/** + * @page page_iface_zwp_linux_buffer_params_v1 zwp_linux_buffer_params_v1 + * @section page_iface_zwp_linux_buffer_params_v1_desc Description + * + * This temporary object is a collection of dmabufs and other + * parameters that together form a single logical buffer. The temporary + * object may eventually create one wl_buffer unless cancelled by + * destroying it before requesting 'create'. + * + * Single-planar formats only require one dmabuf, however + * multi-planar formats may require more than one dmabuf. For all + * formats, an 'add' request must be called once per plane (even if the + * underlying dmabuf fd is identical). + * + * You must use consecutive plane indices ('plane_idx' argument for 'add') + * from zero to the number of planes used by the drm_fourcc format code. + * All planes required by the format must be given exactly once, but can + * be given in any order. Each plane index can only be set once; subsequent + * calls with a plane index which has already been set will result in a + * plane_set error being generated. + * @section page_iface_zwp_linux_buffer_params_v1_api API + * See @ref iface_zwp_linux_buffer_params_v1. + */ +/** + * @defgroup iface_zwp_linux_buffer_params_v1 The zwp_linux_buffer_params_v1 interface + * + * This temporary object is a collection of dmabufs and other + * parameters that together form a single logical buffer. The temporary + * object may eventually create one wl_buffer unless cancelled by + * destroying it before requesting 'create'. + * + * Single-planar formats only require one dmabuf, however + * multi-planar formats may require more than one dmabuf. For all + * formats, an 'add' request must be called once per plane (even if the + * underlying dmabuf fd is identical). + * + * You must use consecutive plane indices ('plane_idx' argument for 'add') + * from zero to the number of planes used by the drm_fourcc format code. + * All planes required by the format must be given exactly once, but can + * be given in any order. Each plane index can only be set once; subsequent + * calls with a plane index which has already been set will result in a + * plane_set error being generated. + */ +extern const struct wl_interface zwp_linux_buffer_params_v1_interface; +#endif +#ifndef ZWP_LINUX_DMABUF_FEEDBACK_V1_INTERFACE +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_INTERFACE +/** + * @page page_iface_zwp_linux_dmabuf_feedback_v1 zwp_linux_dmabuf_feedback_v1 + * @section page_iface_zwp_linux_dmabuf_feedback_v1_desc Description + * + * This object advertises dmabuf parameters feedback. This includes the + * preferred devices and the supported formats/modifiers. + * + * The parameters are sent once when this object is created and whenever they + * change. The done event is always sent once after all parameters have been + * sent. When a single parameter changes, all parameters are re-sent by the + * compositor. + * + * Compositors can re-send the parameters when the current client buffer + * allocations are sub-optimal. Compositors should not re-send the + * parameters if re-allocating the buffers would not result in a more optimal + * configuration. In particular, compositors should avoid sending the exact + * same parameters multiple times in a row. + * + * The tranche_target_device and tranche_formats events are grouped by + * tranches of preference. For each tranche, a tranche_target_device, one + * tranche_flags and one or more tranche_formats events are sent, followed + * by a tranche_done event finishing the list. The tranches are sent in + * descending order of preference. All formats and modifiers in the same + * tranche have the same preference. + * + * To send parameters, the compositor sends one main_device event (unless + * the client bound version 6 or above), tranches (each consisting of one + * tranche_target_device event, one tranche_flags event, tranche_formats + * events and then a tranche_done event), then one done event. + * + * With version 6 and above, the compositor must always advertise at least + * one tranche with the sampling flag set. + * + * The compositor sends a format_table event at least once for each dmabuf + * parameters feedback object, before any tranche events. Unless the format + * table contents change later, the compositor is free not to send another + * format_table event for the same dmabuf parameters feedback object. + * @section page_iface_zwp_linux_dmabuf_feedback_v1_api API + * See @ref iface_zwp_linux_dmabuf_feedback_v1. + */ +/** + * @defgroup iface_zwp_linux_dmabuf_feedback_v1 The zwp_linux_dmabuf_feedback_v1 interface + * + * This object advertises dmabuf parameters feedback. This includes the + * preferred devices and the supported formats/modifiers. + * + * The parameters are sent once when this object is created and whenever they + * change. The done event is always sent once after all parameters have been + * sent. When a single parameter changes, all parameters are re-sent by the + * compositor. + * + * Compositors can re-send the parameters when the current client buffer + * allocations are sub-optimal. Compositors should not re-send the + * parameters if re-allocating the buffers would not result in a more optimal + * configuration. In particular, compositors should avoid sending the exact + * same parameters multiple times in a row. + * + * The tranche_target_device and tranche_formats events are grouped by + * tranches of preference. For each tranche, a tranche_target_device, one + * tranche_flags and one or more tranche_formats events are sent, followed + * by a tranche_done event finishing the list. The tranches are sent in + * descending order of preference. All formats and modifiers in the same + * tranche have the same preference. + * + * To send parameters, the compositor sends one main_device event (unless + * the client bound version 6 or above), tranches (each consisting of one + * tranche_target_device event, one tranche_flags event, tranche_formats + * events and then a tranche_done event), then one done event. + * + * With version 6 and above, the compositor must always advertise at least + * one tranche with the sampling flag set. + * + * The compositor sends a format_table event at least once for each dmabuf + * parameters feedback object, before any tranche events. Unless the format + * table contents change later, the compositor is free not to send another + * format_table event for the same dmabuf parameters feedback object. + */ +extern const struct wl_interface zwp_linux_dmabuf_feedback_v1_interface; +#endif + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * @struct zwp_linux_dmabuf_v1_listener + */ +struct zwp_linux_dmabuf_v1_listener { + /** + * supported buffer format + * + * This event advertises one buffer format that the server + * supports. All the supported formats are advertised once when the + * client binds to this interface. A roundtrip after binding + * guarantees that the client has received all supported formats. + * + * For the definition of the format codes, see the + * zwp_linux_buffer_params_v1::create request. + * + * Starting version 4, the format event is deprecated and must not + * be sent by compositors. Instead, use get_default_feedback or + * get_surface_feedback. + * @param format DRM_FORMAT code + * @deprecated Deprecated since version 4 + */ + void (*format)(void *data, + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, + uint32_t format); + /** + * supported buffer format modifier + * + * This event advertises the formats that the server supports, + * along with the modifiers supported for each format. All the + * supported modifiers for all the supported formats are advertised + * once when the client binds to this interface. A roundtrip after + * binding guarantees that the client has received all supported + * format-modifier pairs. + * + * For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi + * == 0x00ffffff and modifier_lo == 0xffffffff) is allowed in this + * event. It indicates that the server can support the format with + * an implicit modifier. When a plane has DRM_FORMAT_MOD_INVALID as + * its modifier, it is as if no explicit modifier is specified. The + * effective modifier will be derived from the dmabuf. + * + * A compositor that sends valid modifiers and + * DRM_FORMAT_MOD_INVALID for a given format supports both explicit + * modifiers and implicit modifiers. + * + * For the definition of the format and modifier codes, see the + * zwp_linux_buffer_params_v1::create and + * zwp_linux_buffer_params_v1::add requests. + * + * Starting version 4, the modifier event is deprecated and must + * not be sent by compositors. Instead, use get_default_feedback or + * get_surface_feedback. + * @param format DRM_FORMAT code + * @param modifier_hi high 32 bits of layout modifier + * @param modifier_lo low 32 bits of layout modifier + * @since 3 + * @deprecated Deprecated since version 4 + */ + void (*modifier)(void *data, + struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, + uint32_t format, + uint32_t modifier_hi, + uint32_t modifier_lo); +}; + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +static inline int +zwp_linux_dmabuf_v1_add_listener(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, + const struct zwp_linux_dmabuf_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwp_linux_dmabuf_v1, + (void (**)(void)) listener, data); +} + +#define ZWP_LINUX_DMABUF_V1_DESTROY 0 +#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS 1 +#define ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK 2 +#define ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK 3 + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_FORMAT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION 3 + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION 4 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION 4 + +/** @ingroup iface_zwp_linux_dmabuf_v1 */ +static inline void +zwp_linux_dmabuf_v1_set_user_data(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwp_linux_dmabuf_v1, user_data); +} + +/** @ingroup iface_zwp_linux_dmabuf_v1 */ +static inline void * +zwp_linux_dmabuf_v1_get_user_data(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwp_linux_dmabuf_v1); +} + +static inline uint32_t +zwp_linux_dmabuf_v1_get_version(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_v1); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * + * Objects created through this interface, especially wl_buffers, will + * remain valid. + */ +static inline void +zwp_linux_dmabuf_v1_destroy(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_dmabuf_v1, + ZWP_LINUX_DMABUF_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_v1), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * + * This temporary object is used to collect multiple dmabuf handles into + * a single batch to create a wl_buffer. It can only be used once and + * should be destroyed after a 'created' or 'failed' event has been + * received. + */ +static inline struct zwp_linux_buffer_params_v1 * +zwp_linux_dmabuf_v1_create_params(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1) +{ + struct wl_proxy *params_id; + + params_id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_dmabuf_v1, + ZWP_LINUX_DMABUF_V1_CREATE_PARAMS, &zwp_linux_buffer_params_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_v1), 0, NULL); + + return (struct zwp_linux_buffer_params_v1 *) params_id; +} + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * + * This request creates a new zwp_linux_dmabuf_feedback_v1 object not bound + * to a particular surface. This object will deliver feedback about dmabuf + * parameters to use if the client doesn't support per-surface feedback + * (see get_surface_feedback). + */ +static inline struct zwp_linux_dmabuf_feedback_v1 * +zwp_linux_dmabuf_v1_get_default_feedback(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_dmabuf_v1, + ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK, &zwp_linux_dmabuf_feedback_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_v1), 0, NULL); + + return (struct zwp_linux_dmabuf_feedback_v1 *) id; +} + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * + * This request creates a new zwp_linux_dmabuf_feedback_v1 object for the + * specified wl_surface. This object will deliver feedback about dmabuf + * parameters to use for buffers attached to this surface. + * + * If the surface is destroyed before the zwp_linux_dmabuf_feedback_v1 object, + * the feedback object becomes inert. + */ +static inline struct zwp_linux_dmabuf_feedback_v1 * +zwp_linux_dmabuf_v1_get_surface_feedback(struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, struct wl_surface *surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_dmabuf_v1, + ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK, &zwp_linux_dmabuf_feedback_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_v1), 0, NULL, surface); + + return (struct zwp_linux_dmabuf_feedback_v1 *) id; +} + +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM +#define ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM +enum zwp_linux_buffer_params_v1_error { + /** + * the zwp_linux_buffer_params_v1 object has already been used to create a wl_buffer + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED = 0, + /** + * plane index out of bounds + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX = 1, + /** + * the plane index was already set + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET = 2, + /** + * missing or too many planes to create a buffer + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE = 3, + /** + * format not supported + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT = 4, + /** + * invalid width or height + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS = 5, + /** + * offset + stride * height goes out of dmabuf bounds + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS = 6, + /** + * invalid wl_buffer resulted from importing dmabufs via the create_immed request on given buffer_params + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER = 7, + /** + * an array with mismatching size for a dev_t was used + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DEV_T_SIZE = 8, +}; +#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM */ + +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM +#define ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM +enum zwp_linux_buffer_params_v1_flags { + /** + * contents are y-inverted + */ + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT = 1, + /** + * content is interlaced + */ + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED = 2, + /** + * bottom field first + */ + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST = 4, +}; +#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM */ + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * @struct zwp_linux_buffer_params_v1_listener + */ +struct zwp_linux_buffer_params_v1_listener { + /** + * buffer creation succeeded + * + * This event indicates that the attempted buffer creation was + * successful. It provides the new wl_buffer referencing the + * dmabuf(s). + * + * Upon receiving this event, the client should destroy the + * zwp_linux_buffer_params_v1 object. + * @param buffer id for the the newly created wl_buffer + */ + void (*created)(void *data, + struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, + struct wl_buffer *buffer); + /** + * buffer creation failed + * + * This event indicates that the attempted buffer creation has + * failed. It usually means that one of the dmabuf constraints has + * not been fulfilled. + * + * Upon receiving this event, the client should destroy the + * zwp_linux_buffer_params_v1 object. + */ + void (*failed)(void *data, + struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1); +}; + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +static inline int +zwp_linux_buffer_params_v1_add_listener(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, + const struct zwp_linux_buffer_params_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwp_linux_buffer_params_v1, + (void (**)(void)) listener, data); +} + +#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY 0 +#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD 1 +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE 2 +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED 3 +#define ZWP_LINUX_BUFFER_PARAMS_V1_SET_SAMPLING_DEVICE 4 + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATED_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_FAILED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_SET_SAMPLING_DEVICE_SINCE_VERSION 6 + +/** @ingroup iface_zwp_linux_buffer_params_v1 */ +static inline void +zwp_linux_buffer_params_v1_set_user_data(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwp_linux_buffer_params_v1, user_data); +} + +/** @ingroup iface_zwp_linux_buffer_params_v1 */ +static inline void * +zwp_linux_buffer_params_v1_get_user_data(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwp_linux_buffer_params_v1); +} + +static inline uint32_t +zwp_linux_buffer_params_v1_get_version(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwp_linux_buffer_params_v1); +} + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * + * Cleans up the temporary data sent to the server for dmabuf-based + * wl_buffer creation. + */ +static inline void +zwp_linux_buffer_params_v1_destroy(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_buffer_params_v1, + ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_linux_buffer_params_v1), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * + * This request adds one dmabuf to the set in this + * zwp_linux_buffer_params_v1. + * + * The 64-bit unsigned value combined from modifier_hi and modifier_lo + * is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the + * fb modifier, which is defined in drm_mode.h of Linux UAPI. + * This is an opaque token. Drivers use this token to express tiling, + * compression, etc. driver-specific modifications to the base format + * defined by the DRM fourcc code. + * + * Starting from version 4, the invalid_format protocol error is sent if + * the format + modifier pair was not advertised as supported. + * + * Starting from version 5, the invalid_format protocol error is sent if + * all planes don't use the same modifier. + * + * This request raises the PLANE_IDX error if plane_idx is too large. + * The error PLANE_SET is raised if attempting to set a plane that + * was already set. + */ +static inline void +zwp_linux_buffer_params_v1_add(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_buffer_params_v1, + ZWP_LINUX_BUFFER_PARAMS_V1_ADD, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_linux_buffer_params_v1), 0, fd, plane_idx, offset, stride, modifier_hi, modifier_lo); +} + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * + * This asks for creation of a wl_buffer from the added dmabuf + * buffers. The wl_buffer is not created immediately but returned via + * the 'created' event if the dmabuf sharing succeeds. The sharing + * may fail at runtime for reasons a client cannot predict, in + * which case the 'failed' event is triggered. + * + * The 'format' argument is a DRM_FORMAT code, as defined by the + * libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + * authoritative source on how the format codes should work. + * + * The 'flags' is a bitfield of the flags defined in enum "flags". + * 'y_invert' means that the image needs to be y-flipped. + * + * Flag 'interlaced' means that the frame in the buffer is not + * progressive as usual, but interlaced. An interlaced buffer as + * supported here must always contain both top and bottom fields. + * The top field always begins on the first pixel row. The temporal + * ordering between the two fields is top field first, unless + * 'bottom_first' is specified. It is undefined whether 'bottom_first' + * is ignored if 'interlaced' is not set. + * + * This protocol does not convey any information about field rate, + * duration, or timing, other than the relative ordering between the + * two fields in one buffer. A compositor may have to estimate the + * intended field rate from the incoming buffer rate. It is undefined + * whether the time of receiving wl_surface.commit with a new buffer + * attached, applying the wl_surface state, wl_surface.frame callback + * trigger, presentation, or any other point in the compositor cycle + * is used to measure the frame or field times. There is no support + * for detecting missed or late frames/fields/buffers either, and + * there is no support whatsoever for cooperating with interlaced + * compositor output. + * + * The composited image quality resulting from the use of interlaced + * buffers is explicitly undefined. A compositor may use elaborate + * hardware features or software to deinterlace and create progressive + * output frames from a sequence of interlaced input buffers, or it + * may produce substandard image quality. However, compositors that + * cannot guarantee reasonable image quality in all cases are recommended + * to just reject all interlaced buffers. + * + * Any argument errors, including non-positive width or height, + * mismatch between the number of planes and the format, bad + * format, bad offset or stride, may be indicated by fatal protocol + * errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + * OUT_OF_BOUNDS. + * + * Dmabuf import errors in the server that are not obvious client + * bugs are returned via the 'failed' event as non-fatal. This + * allows attempting dmabuf sharing and falling back in the client + * if it fails. + * + * This request can be sent only once in the object's lifetime, after + * which the only legal request is destroy. This object should be + * destroyed after issuing a 'create' request. Attempting to use this + * object after issuing 'create' raises the ALREADY_USED protocol error. + * + * It is not mandatory to issue 'create'. If a client wants to + * cancel the buffer creation, it can just destroy this object. + */ +static inline void +zwp_linux_buffer_params_v1_create(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, int32_t width, int32_t height, uint32_t format, uint32_t flags) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_buffer_params_v1, + ZWP_LINUX_BUFFER_PARAMS_V1_CREATE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_linux_buffer_params_v1), 0, width, height, format, flags); +} + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * + * This asks for immediate creation of a wl_buffer by importing the + * added dmabufs. + * + * In case of import success, no event is sent from the server, and the + * wl_buffer is ready to be used by the client. + * + * Upon import failure, either of the following may happen, as seen fit + * by the implementation: + * - the client is terminated with one of the following fatal protocol + * errors: + * - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, + * in case of argument errors such as mismatch between the number + * of planes and the format, bad format, non-positive width or + * height, or bad offset or stride. + * - INVALID_WL_BUFFER, in case the cause for failure is unknown or + * platform specific. + * - the server creates an invalid wl_buffer, marks it as failed and + * sends a 'failed' event to the client. The result of using this + * invalid wl_buffer as an argument in any request by the client is + * defined by the compositor implementation. + * + * This takes the same arguments as a 'create' request, and obeys the + * same restrictions. + */ +static inline struct wl_buffer * +zwp_linux_buffer_params_v1_create_immed(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, int32_t width, int32_t height, uint32_t format, uint32_t flags) +{ + struct wl_proxy *buffer_id; + + buffer_id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_buffer_params_v1, + ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED, &wl_buffer_interface, wl_proxy_get_version((struct wl_proxy *) zwp_linux_buffer_params_v1), 0, NULL, width, height, format, flags); + + return (struct wl_buffer *) buffer_id; +} + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * + * Set the device the compositor should import the dmabufs to for sampling + * in the next create or create_immed request. + * + * To avoid race conditions when the compositor removes a device from the + * tranches, it is not a protocol error if the device hasn't been advertised + * by the compositor in a tranche with the sampling flag, but the import is + * likely to fail in that case. + * + * If the client doesn't know a suitable target device, it shouldn't set one, + * and the compositor should attempt import on all devices it supports. + * + * If the array is too small to contain a dev_t or larger than required, the + * invalid_dev_t_size error will be emitted. + */ +static inline void +zwp_linux_buffer_params_v1_set_sampling_device(struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, struct wl_array *device) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_buffer_params_v1, + ZWP_LINUX_BUFFER_PARAMS_V1_SET_SAMPLING_DEVICE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_linux_buffer_params_v1), 0, device); +} + +#ifndef ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM +enum zwp_linux_dmabuf_feedback_v1_tranche_flags { + /** + * direct scan-out tranche + * + * The scanout flag is a hint that direct scan-out may be + * attempted by the compositor on the target device if the client + * appropriately allocates a buffer. How to allocate a buffer that + * can be scanned out on the target device is + * implementation-defined. + * @since 4 + */ + ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT = 1, + /** + * sampling tranche + * + * The sampling flag describes that the compositor is able to + * efficiently sample from buffers imported to the target device if + * the client appropriately allocates a buffer. How to allocate a + * buffer that can be efficiently sampled on the target device is + * implementation defined. + * @since 6 + */ + ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SAMPLING = 2, +}; +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT_SINCE_VERSION 4 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SAMPLING_SINCE_VERSION 6 +#endif /* ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM */ + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * @struct zwp_linux_dmabuf_feedback_v1_listener + */ +struct zwp_linux_dmabuf_feedback_v1_listener { + /** + * all feedback has been sent + * + * This event is sent after all parameters of a + * zwp_linux_dmabuf_feedback_v1 object have been sent. + * + * This allows changes to the zwp_linux_dmabuf_feedback_v1 + * parameters to be seen as atomic, even if they happen via + * multiple events. + */ + void (*done)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1); + /** + * format and modifier table + * + * This event provides a file descriptor which can be + * memory-mapped to access the format and modifier table. + * + * The table contains a tightly packed array of consecutive format + * + modifier pairs. Each pair is 16 bytes wide. It contains a + * format as a 32-bit unsigned integer, followed by 4 bytes of + * unused padding, and a modifier as a 64-bit unsigned integer. The + * native endianness is used. + * + * The client must map the file descriptor in read-only private + * mode. + * + * Compositors are not allowed to mutate the table file contents + * once this event has been sent. Instead, compositors must create + * a new, separate table file and re-send feedback parameters. + * Compositors are allowed to store duplicate format + modifier + * pairs in the table. + * @param fd table file descriptor + * @param size table size, in bytes + */ + void (*format_table)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + int32_t fd, + uint32_t size); + /** + * preferred main device + * + * This event advertises the main device that the server prefers + * to use when direct scan-out to the target device isn't possible. + * The advertised main device may be different for each + * zwp_linux_dmabuf_feedback_v1 object, and may change over time. + * + * There is exactly one main device. The compositor must send at + * least one preference tranche with tranche_target_device equal to + * main_device. + * + * Clients need to create buffers that the main device can import + * and read from, otherwise creating the dmabuf wl_buffer will fail + * (see the zwp_linux_buffer_params_v1.create and create_immed + * requests for details). The main device will also likely be kept + * active by the compositor, so clients can use it instead of + * waking up another device for power savings. + * + * In general the device is a DRM node. The DRM node type (primary + * vs. render) is unspecified. Clients must not rely on the + * compositor sending a particular node type. Clients cannot check + * two devices for equality by comparing the dev_t value. + * + * If explicit modifiers are not supported and the client performs + * buffer allocations on a different device than the main device, + * then the client must force the buffer to have a linear layout. + * + * With version 6 and above, this event is no longer sent. Clients + * should use a device with the sampling flag in the tranches + * instead. + * @param device device dev_t value + * @deprecated Deprecated since version 6 + */ + void (*main_device)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + struct wl_array *device); + /** + * a preference tranche has been sent + * + * This event splits tranche_target_device and tranche_formats + * events into preference tranches. It is sent after a set of + * tranche_target_device and tranche_formats events; it represents + * the end of a tranche. The next tranche will have a lower + * preference. + */ + void (*tranche_done)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1); + /** + * target device + * + * This event advertises the target device that the server + * prefers to use for a buffer created given this tranche. The + * advertised target device may be different for each preference + * tranche, and may change over time. + * + * There is exactly one target device per tranche. + * + * The target device may be a scan-out device, for example if the + * compositor prefers to directly scan-out a buffer created given + * this tranche. The target device may be a rendering device, for + * example if the compositor prefers to texture from said buffer. + * + * The client can use this hint to allocate the buffer in a way + * that makes it accessible from the target device, ideally + * directly. The buffer must still be accessible from a device with + * the sampling flag, either through direct import or a potentially + * more expensive fallback path. If the buffer can't be directly + * imported for sampling, then clients must be prepared for the + * compositor changing the tranche priority or making wl_buffer + * creation fail (see the zwp_linux_buffer_params_v1.create and + * create_immed requests for details). + * + * If the device is a DRM node, the DRM node type (primary vs. + * render) is unspecified. Clients must not rely on the compositor + * sending a particular node type. Clients cannot check two devices + * for equality by comparing the dev_t value. + * + * This event is tied to a preference tranche, see the tranche_done + * event. + * @param device device dev_t value + */ + void (*tranche_target_device)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + struct wl_array *device); + /** + * supported buffer format modifiers + * + * This event advertises the format + modifier combinations that + * the compositor supports. + * + * It carries an array of indices, each referring to a format + + * modifier pair in the last received format table (see the + * format_table event). Each index is a 16-bit unsigned integer in + * native endianness. + * + * For legacy support, DRM_FORMAT_MOD_INVALID is an allowed + * modifier. It indicates that the server can support the format + * with an implicit modifier. When a buffer has + * DRM_FORMAT_MOD_INVALID as its modifier, it is as if no explicit + * modifier is specified. The effective modifier will be derived + * from the dmabuf. + * + * A compositor that sends valid modifiers and + * DRM_FORMAT_MOD_INVALID for a given format supports both explicit + * modifiers and implicit modifiers. + * + * Compositors must not send duplicate format + modifier pairs + * within the same tranche or across two different tranches with + * the same target device and flags. + * + * This event is tied to a preference tranche, see the tranche_done + * event. + * + * For the definition of the format and modifier codes, see the + * zwp_linux_buffer_params_v1.create request. + * @param indices array of 16-bit indexes + */ + void (*tranche_formats)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + struct wl_array *indices); + /** + * tranche flags + * + * This event sets tranche-specific flags. This event is tied to + * a preference tranche, see the tranche_done event. With version 6 + * and above, the compositor must set at least one flag in each + * tranche. + * @param flags tranche flags + */ + void (*tranche_flags)(void *data, + struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + uint32_t flags); +}; + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +static inline int +zwp_linux_dmabuf_feedback_v1_add_listener(struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, + const struct zwp_linux_dmabuf_feedback_v1_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) zwp_linux_dmabuf_feedback_v1, + (void (**)(void)) listener, data); +} + +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_DESTROY 0 + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_FORMAT_TABLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_MAIN_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_TARGET_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FORMATS_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_DESTROY_SINCE_VERSION 1 + +/** @ingroup iface_zwp_linux_dmabuf_feedback_v1 */ +static inline void +zwp_linux_dmabuf_feedback_v1_set_user_data(struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) zwp_linux_dmabuf_feedback_v1, user_data); +} + +/** @ingroup iface_zwp_linux_dmabuf_feedback_v1 */ +static inline void * +zwp_linux_dmabuf_feedback_v1_get_user_data(struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) +{ + return wl_proxy_get_user_data((struct wl_proxy *) zwp_linux_dmabuf_feedback_v1); +} + +static inline uint32_t +zwp_linux_dmabuf_feedback_v1_get_version(struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) +{ + return wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_feedback_v1); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * + * Using this request a client can tell the server that it is not going to + * use the zwp_linux_dmabuf_feedback_v1 object anymore. + */ +static inline void +zwp_linux_dmabuf_feedback_v1_destroy(struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) +{ + wl_proxy_marshal_flags((struct wl_proxy *) zwp_linux_dmabuf_feedback_v1, + ZWP_LINUX_DMABUF_FEEDBACK_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_linux_dmabuf_feedback_v1), WL_MARSHAL_FLAG_DESTROY); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-protocol.c new file mode 100644 index 000000000..7d2c38984 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-protocol.c @@ -0,0 +1,121 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2014, 2015 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_buffer_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface zwp_linux_buffer_params_v1_interface; +extern const struct wl_interface zwp_linux_dmabuf_feedback_v1_interface; + +static const struct wl_interface *linux_dmabuf_v1_types[] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + &zwp_linux_buffer_params_v1_interface, + &zwp_linux_dmabuf_feedback_v1_interface, + &zwp_linux_dmabuf_feedback_v1_interface, + &wl_surface_interface, + &wl_buffer_interface, + NULL, + NULL, + NULL, + NULL, + &wl_buffer_interface, +}; + +static const struct wl_message zwp_linux_dmabuf_v1_requests[] = { + { "destroy", "", linux_dmabuf_v1_types + 0 }, + { "create_params", "n", linux_dmabuf_v1_types + 6 }, + { "get_default_feedback", "4n", linux_dmabuf_v1_types + 7 }, + { "get_surface_feedback", "4no", linux_dmabuf_v1_types + 8 }, +}; + +static const struct wl_message zwp_linux_dmabuf_v1_events[] = { + { "format", "u", linux_dmabuf_v1_types + 0 }, + { "modifier", "3uuu", linux_dmabuf_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_linux_dmabuf_v1_interface = { + "zwp_linux_dmabuf_v1", 6, + 4, zwp_linux_dmabuf_v1_requests, + 2, zwp_linux_dmabuf_v1_events, +}; + +static const struct wl_message zwp_linux_buffer_params_v1_requests[] = { + { "destroy", "", linux_dmabuf_v1_types + 0 }, + { "add", "huuuuu", linux_dmabuf_v1_types + 0 }, + { "create", "iiuu", linux_dmabuf_v1_types + 0 }, + { "create_immed", "2niiuu", linux_dmabuf_v1_types + 10 }, + { "set_sampling_device", "6a", linux_dmabuf_v1_types + 0 }, +}; + +static const struct wl_message zwp_linux_buffer_params_v1_events[] = { + { "created", "n", linux_dmabuf_v1_types + 15 }, + { "failed", "", linux_dmabuf_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_linux_buffer_params_v1_interface = { + "zwp_linux_buffer_params_v1", 6, + 5, zwp_linux_buffer_params_v1_requests, + 2, zwp_linux_buffer_params_v1_events, +}; + +static const struct wl_message zwp_linux_dmabuf_feedback_v1_requests[] = { + { "destroy", "", linux_dmabuf_v1_types + 0 }, +}; + +static const struct wl_message zwp_linux_dmabuf_feedback_v1_events[] = { + { "done", "", linux_dmabuf_v1_types + 0 }, + { "format_table", "hu", linux_dmabuf_v1_types + 0 }, + { "main_device", "a", linux_dmabuf_v1_types + 0 }, + { "tranche_done", "", linux_dmabuf_v1_types + 0 }, + { "tranche_target_device", "a", linux_dmabuf_v1_types + 0 }, + { "tranche_formats", "a", linux_dmabuf_v1_types + 0 }, + { "tranche_flags", "u", linux_dmabuf_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_linux_dmabuf_feedback_v1_interface = { + "zwp_linux_dmabuf_feedback_v1", 6, + 1, zwp_linux_dmabuf_feedback_v1_requests, + 7, zwp_linux_dmabuf_feedback_v1_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-server-protocol.h new file mode 100644 index 000000000..7d87d90ac --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/linux-dmabuf-v1-server-protocol.h @@ -0,0 +1,996 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef LINUX_DMABUF_V1_SERVER_PROTOCOL_H +#define LINUX_DMABUF_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_linux_dmabuf_v1 The linux_dmabuf_v1 protocol + * @section page_ifaces_linux_dmabuf_v1 Interfaces + * - @subpage page_iface_zwp_linux_dmabuf_v1 - factory for creating dmabuf-based wl_buffers + * - @subpage page_iface_zwp_linux_buffer_params_v1 - parameters for creating a dmabuf-based wl_buffer + * - @subpage page_iface_zwp_linux_dmabuf_feedback_v1 - dmabuf feedback + * @section page_copyright_linux_dmabuf_v1 Copyright + *
+ *
+ * Copyright ยฉ 2014, 2015 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_buffer; +struct wl_surface; +struct zwp_linux_buffer_params_v1; +struct zwp_linux_dmabuf_feedback_v1; +struct zwp_linux_dmabuf_v1; + +#ifndef ZWP_LINUX_DMABUF_V1_INTERFACE +#define ZWP_LINUX_DMABUF_V1_INTERFACE +/** + * @page page_iface_zwp_linux_dmabuf_v1 zwp_linux_dmabuf_v1 + * @section page_iface_zwp_linux_dmabuf_v1_desc Description + * + * This interface offers ways to create generic dmabuf-based wl_buffers. + * + * For more information about dmabuf, see: + * https://www.kernel.org/doc/html/next/userspace-api/dma-buf-alloc-exchange.html + * + * Clients can use the get_surface_feedback request to get dmabuf feedback + * for a particular surface. If the client wants to retrieve feedback not + * tied to a surface, they can use the get_default_feedback request. + * + * The following are required from clients: + * + * - Clients must ensure that either all data in the dma-buf is + * coherent for all subsequent read access or that coherency is + * correctly handled by the underlying kernel-side dma-buf + * implementation. + * + * - Don't make any more attachments after sending the buffer to the + * compositor. Making more attachments later increases the risk of + * the compositor not being able to use (re-import) an existing + * dmabuf-based wl_buffer. + * + * The underlying graphics stack must ensure the following: + * + * - The dmabuf file descriptors relayed to the server will stay valid + * for the whole lifetime of the wl_buffer. This means the server may + * at any time use those fds to import the dmabuf into any kernel + * sub-system that might accept it. + * + * However, when the underlying graphics stack fails to deliver the + * promise, because of e.g. a device hot-unplug which raises internal + * errors, after the wl_buffer has been successfully created the + * compositor must not raise protocol errors to the client when dmabuf + * import later fails. + * + * To create a wl_buffer from one or more dmabufs, a client creates a + * zwp_linux_buffer_params_v1 object with a zwp_linux_dmabuf_v1.create_params + * request. All planes required by the intended format are added with + * the 'add' request. Finally, a 'create' or 'create_immed' request is + * issued, which has the following outcome depending on the import success. + * + * The 'create' request, + * - on success, triggers a 'created' event which provides the final + * wl_buffer to the client. + * - on failure, triggers a 'failed' event to convey that the server + * cannot use the dmabufs received from the client. + * + * For the 'create_immed' request, + * - on success, the server immediately imports the added dmabufs to + * create a wl_buffer. No event is sent from the server in this case. + * - on failure, the server can choose to either: + * - terminate the client by raising a fatal error. + * - mark the wl_buffer as failed, and send a 'failed' event to the + * client. If the client uses a failed wl_buffer as an argument to any + * request, the behaviour is compositor implementation-defined. + * + * For all DRM formats and unless specified in another protocol extension, + * pre-multiplied alpha is used for pixel values. + * + * Unless specified otherwise in another protocol extension, implicit + * synchronization is used. In other words, compositors and clients must + * wait and signal fences implicitly passed via the DMA-BUF's reservation + * mechanism. + * @section page_iface_zwp_linux_dmabuf_v1_api API + * See @ref iface_zwp_linux_dmabuf_v1. + */ +/** + * @defgroup iface_zwp_linux_dmabuf_v1 The zwp_linux_dmabuf_v1 interface + * + * This interface offers ways to create generic dmabuf-based wl_buffers. + * + * For more information about dmabuf, see: + * https://www.kernel.org/doc/html/next/userspace-api/dma-buf-alloc-exchange.html + * + * Clients can use the get_surface_feedback request to get dmabuf feedback + * for a particular surface. If the client wants to retrieve feedback not + * tied to a surface, they can use the get_default_feedback request. + * + * The following are required from clients: + * + * - Clients must ensure that either all data in the dma-buf is + * coherent for all subsequent read access or that coherency is + * correctly handled by the underlying kernel-side dma-buf + * implementation. + * + * - Don't make any more attachments after sending the buffer to the + * compositor. Making more attachments later increases the risk of + * the compositor not being able to use (re-import) an existing + * dmabuf-based wl_buffer. + * + * The underlying graphics stack must ensure the following: + * + * - The dmabuf file descriptors relayed to the server will stay valid + * for the whole lifetime of the wl_buffer. This means the server may + * at any time use those fds to import the dmabuf into any kernel + * sub-system that might accept it. + * + * However, when the underlying graphics stack fails to deliver the + * promise, because of e.g. a device hot-unplug which raises internal + * errors, after the wl_buffer has been successfully created the + * compositor must not raise protocol errors to the client when dmabuf + * import later fails. + * + * To create a wl_buffer from one or more dmabufs, a client creates a + * zwp_linux_buffer_params_v1 object with a zwp_linux_dmabuf_v1.create_params + * request. All planes required by the intended format are added with + * the 'add' request. Finally, a 'create' or 'create_immed' request is + * issued, which has the following outcome depending on the import success. + * + * The 'create' request, + * - on success, triggers a 'created' event which provides the final + * wl_buffer to the client. + * - on failure, triggers a 'failed' event to convey that the server + * cannot use the dmabufs received from the client. + * + * For the 'create_immed' request, + * - on success, the server immediately imports the added dmabufs to + * create a wl_buffer. No event is sent from the server in this case. + * - on failure, the server can choose to either: + * - terminate the client by raising a fatal error. + * - mark the wl_buffer as failed, and send a 'failed' event to the + * client. If the client uses a failed wl_buffer as an argument to any + * request, the behaviour is compositor implementation-defined. + * + * For all DRM formats and unless specified in another protocol extension, + * pre-multiplied alpha is used for pixel values. + * + * Unless specified otherwise in another protocol extension, implicit + * synchronization is used. In other words, compositors and clients must + * wait and signal fences implicitly passed via the DMA-BUF's reservation + * mechanism. + */ +extern const struct wl_interface zwp_linux_dmabuf_v1_interface; +#endif +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_INTERFACE +#define ZWP_LINUX_BUFFER_PARAMS_V1_INTERFACE +/** + * @page page_iface_zwp_linux_buffer_params_v1 zwp_linux_buffer_params_v1 + * @section page_iface_zwp_linux_buffer_params_v1_desc Description + * + * This temporary object is a collection of dmabufs and other + * parameters that together form a single logical buffer. The temporary + * object may eventually create one wl_buffer unless cancelled by + * destroying it before requesting 'create'. + * + * Single-planar formats only require one dmabuf, however + * multi-planar formats may require more than one dmabuf. For all + * formats, an 'add' request must be called once per plane (even if the + * underlying dmabuf fd is identical). + * + * You must use consecutive plane indices ('plane_idx' argument for 'add') + * from zero to the number of planes used by the drm_fourcc format code. + * All planes required by the format must be given exactly once, but can + * be given in any order. Each plane index can only be set once; subsequent + * calls with a plane index which has already been set will result in a + * plane_set error being generated. + * @section page_iface_zwp_linux_buffer_params_v1_api API + * See @ref iface_zwp_linux_buffer_params_v1. + */ +/** + * @defgroup iface_zwp_linux_buffer_params_v1 The zwp_linux_buffer_params_v1 interface + * + * This temporary object is a collection of dmabufs and other + * parameters that together form a single logical buffer. The temporary + * object may eventually create one wl_buffer unless cancelled by + * destroying it before requesting 'create'. + * + * Single-planar formats only require one dmabuf, however + * multi-planar formats may require more than one dmabuf. For all + * formats, an 'add' request must be called once per plane (even if the + * underlying dmabuf fd is identical). + * + * You must use consecutive plane indices ('plane_idx' argument for 'add') + * from zero to the number of planes used by the drm_fourcc format code. + * All planes required by the format must be given exactly once, but can + * be given in any order. Each plane index can only be set once; subsequent + * calls with a plane index which has already been set will result in a + * plane_set error being generated. + */ +extern const struct wl_interface zwp_linux_buffer_params_v1_interface; +#endif +#ifndef ZWP_LINUX_DMABUF_FEEDBACK_V1_INTERFACE +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_INTERFACE +/** + * @page page_iface_zwp_linux_dmabuf_feedback_v1 zwp_linux_dmabuf_feedback_v1 + * @section page_iface_zwp_linux_dmabuf_feedback_v1_desc Description + * + * This object advertises dmabuf parameters feedback. This includes the + * preferred devices and the supported formats/modifiers. + * + * The parameters are sent once when this object is created and whenever they + * change. The done event is always sent once after all parameters have been + * sent. When a single parameter changes, all parameters are re-sent by the + * compositor. + * + * Compositors can re-send the parameters when the current client buffer + * allocations are sub-optimal. Compositors should not re-send the + * parameters if re-allocating the buffers would not result in a more optimal + * configuration. In particular, compositors should avoid sending the exact + * same parameters multiple times in a row. + * + * The tranche_target_device and tranche_formats events are grouped by + * tranches of preference. For each tranche, a tranche_target_device, one + * tranche_flags and one or more tranche_formats events are sent, followed + * by a tranche_done event finishing the list. The tranches are sent in + * descending order of preference. All formats and modifiers in the same + * tranche have the same preference. + * + * To send parameters, the compositor sends one main_device event (unless + * the client bound version 6 or above), tranches (each consisting of one + * tranche_target_device event, one tranche_flags event, tranche_formats + * events and then a tranche_done event), then one done event. + * + * With version 6 and above, the compositor must always advertise at least + * one tranche with the sampling flag set. + * + * The compositor sends a format_table event at least once for each dmabuf + * parameters feedback object, before any tranche events. Unless the format + * table contents change later, the compositor is free not to send another + * format_table event for the same dmabuf parameters feedback object. + * @section page_iface_zwp_linux_dmabuf_feedback_v1_api API + * See @ref iface_zwp_linux_dmabuf_feedback_v1. + */ +/** + * @defgroup iface_zwp_linux_dmabuf_feedback_v1 The zwp_linux_dmabuf_feedback_v1 interface + * + * This object advertises dmabuf parameters feedback. This includes the + * preferred devices and the supported formats/modifiers. + * + * The parameters are sent once when this object is created and whenever they + * change. The done event is always sent once after all parameters have been + * sent. When a single parameter changes, all parameters are re-sent by the + * compositor. + * + * Compositors can re-send the parameters when the current client buffer + * allocations are sub-optimal. Compositors should not re-send the + * parameters if re-allocating the buffers would not result in a more optimal + * configuration. In particular, compositors should avoid sending the exact + * same parameters multiple times in a row. + * + * The tranche_target_device and tranche_formats events are grouped by + * tranches of preference. For each tranche, a tranche_target_device, one + * tranche_flags and one or more tranche_formats events are sent, followed + * by a tranche_done event finishing the list. The tranches are sent in + * descending order of preference. All formats and modifiers in the same + * tranche have the same preference. + * + * To send parameters, the compositor sends one main_device event (unless + * the client bound version 6 or above), tranches (each consisting of one + * tranche_target_device event, one tranche_flags event, tranche_formats + * events and then a tranche_done event), then one done event. + * + * With version 6 and above, the compositor must always advertise at least + * one tranche with the sampling flag set. + * + * The compositor sends a format_table event at least once for each dmabuf + * parameters feedback object, before any tranche events. Unless the format + * table contents change later, the compositor is free not to send another + * format_table event for the same dmabuf parameters feedback object. + */ +extern const struct wl_interface zwp_linux_dmabuf_feedback_v1_interface; +#endif + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * @struct zwp_linux_dmabuf_v1_interface + */ +struct zwp_linux_dmabuf_v1_interface { + /** + * unbind the factory + * + * Objects created through this interface, especially wl_buffers, + * will remain valid. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * create a temporary object for buffer parameters + * + * This temporary object is used to collect multiple dmabuf + * handles into a single batch to create a wl_buffer. It can only + * be used once and should be destroyed after a 'created' or + * 'failed' event has been received. + * @param params_id id for the newly created zwp_linux_buffer_params_v1 + */ + void (*create_params)(struct wl_client *client, + struct wl_resource *resource, + uint32_t params_id); + /** + * get default feedback + * + * This request creates a new zwp_linux_dmabuf_feedback_v1 object + * not bound to a particular surface. This object will deliver + * feedback about dmabuf parameters to use if the client doesn't + * support per-surface feedback (see get_surface_feedback). + * @since 4 + */ + void (*get_default_feedback)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * get feedback for a surface + * + * This request creates a new zwp_linux_dmabuf_feedback_v1 object + * for the specified wl_surface. This object will deliver feedback + * about dmabuf parameters to use for buffers attached to this + * surface. + * + * If the surface is destroyed before the + * zwp_linux_dmabuf_feedback_v1 object, the feedback object becomes + * inert. + * @since 4 + */ + void (*get_surface_feedback)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); +}; + +#define ZWP_LINUX_DMABUF_V1_FORMAT 0 +#define ZWP_LINUX_DMABUF_V1_MODIFIER 1 + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_FORMAT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION 3 + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION 4 +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + */ +#define ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION 4 + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * Sends an format event to the client owning the resource. + * @param resource_ The client's resource + * @param format DRM_FORMAT code + */ +static inline void +zwp_linux_dmabuf_v1_send_format(struct wl_resource *resource_, uint32_t format) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_V1_FORMAT, format); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_v1 + * Sends an modifier event to the client owning the resource. + * @param resource_ The client's resource + * @param format DRM_FORMAT code + * @param modifier_hi high 32 bits of layout modifier + * @param modifier_lo low 32 bits of layout modifier + */ +static inline void +zwp_linux_dmabuf_v1_send_modifier(struct wl_resource *resource_, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_V1_MODIFIER, format, modifier_hi, modifier_lo); +} + +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM +#define ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM +enum zwp_linux_buffer_params_v1_error { + /** + * the zwp_linux_buffer_params_v1 object has already been used to create a wl_buffer + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED = 0, + /** + * plane index out of bounds + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX = 1, + /** + * the plane index was already set + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET = 2, + /** + * missing or too many planes to create a buffer + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE = 3, + /** + * format not supported + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT = 4, + /** + * invalid width or height + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS = 5, + /** + * offset + stride * height goes out of dmabuf bounds + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS = 6, + /** + * invalid wl_buffer resulted from importing dmabufs via the create_immed request on given buffer_params + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER = 7, + /** + * an array with mismatching size for a dev_t was used + */ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DEV_T_SIZE = 8, +}; +#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM */ + +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM_IS_VALID +#define ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * Validate a zwp_linux_buffer_params_v1 error value. + * + * @return true on success, false on error. + * @ref zwp_linux_buffer_params_v1_error + */ +static inline bool +zwp_linux_buffer_params_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER: + return version >= 1; + case ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DEV_T_SIZE: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM_IS_VALID */ + +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM +#define ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM +enum zwp_linux_buffer_params_v1_flags { + /** + * contents are y-inverted + */ + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT = 1, + /** + * content is interlaced + */ + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED = 2, + /** + * bottom field first + */ + ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST = 4, +}; +#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM */ + +#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM_IS_VALID +#define ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM_IS_VALID +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * Validate a zwp_linux_buffer_params_v1 flags value. + * + * @return true on success, false on error. + * @ref zwp_linux_buffer_params_v1_flags + */ +static inline bool +zwp_linux_buffer_params_v1_flags_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + if (version >= 1) + valid |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED; + if (version >= 1) + valid |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST; + return (value & ~valid) == 0; +} +#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM_IS_VALID */ + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * @struct zwp_linux_buffer_params_v1_interface + */ +struct zwp_linux_buffer_params_v1_interface { + /** + * delete this object, used or not + * + * Cleans up the temporary data sent to the server for + * dmabuf-based wl_buffer creation. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * add a dmabuf to the temporary set + * + * This request adds one dmabuf to the set in this + * zwp_linux_buffer_params_v1. + * + * The 64-bit unsigned value combined from modifier_hi and + * modifier_lo is the dmabuf layout modifier. DRM AddFB2 ioctl + * calls this the fb modifier, which is defined in drm_mode.h of + * Linux UAPI. This is an opaque token. Drivers use this token to + * express tiling, compression, etc. driver-specific modifications + * to the base format defined by the DRM fourcc code. + * + * Starting from version 4, the invalid_format protocol error is + * sent if the format + modifier pair was not advertised as + * supported. + * + * Starting from version 5, the invalid_format protocol error is + * sent if all planes don't use the same modifier. + * + * This request raises the PLANE_IDX error if plane_idx is too + * large. The error PLANE_SET is raised if attempting to set a + * plane that was already set. + * @param fd dmabuf fd + * @param plane_idx plane index + * @param offset offset in bytes + * @param stride stride in bytes + * @param modifier_hi high 32 bits of layout modifier + * @param modifier_lo low 32 bits of layout modifier + */ + void (*add)(struct wl_client *client, + struct wl_resource *resource, + int32_t fd, + uint32_t plane_idx, + uint32_t offset, + uint32_t stride, + uint32_t modifier_hi, + uint32_t modifier_lo); + /** + * create a wl_buffer from the given dmabufs + * + * This asks for creation of a wl_buffer from the added dmabuf + * buffers. The wl_buffer is not created immediately but returned + * via the 'created' event if the dmabuf sharing succeeds. The + * sharing may fail at runtime for reasons a client cannot predict, + * in which case the 'failed' event is triggered. + * + * The 'format' argument is a DRM_FORMAT code, as defined by the + * libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + * authoritative source on how the format codes should work. + * + * The 'flags' is a bitfield of the flags defined in enum "flags". + * 'y_invert' means that the image needs to be y-flipped. + * + * Flag 'interlaced' means that the frame in the buffer is not + * progressive as usual, but interlaced. An interlaced buffer as + * supported here must always contain both top and bottom fields. + * The top field always begins on the first pixel row. The temporal + * ordering between the two fields is top field first, unless + * 'bottom_first' is specified. It is undefined whether + * 'bottom_first' is ignored if 'interlaced' is not set. + * + * This protocol does not convey any information about field rate, + * duration, or timing, other than the relative ordering between + * the two fields in one buffer. A compositor may have to estimate + * the intended field rate from the incoming buffer rate. It is + * undefined whether the time of receiving wl_surface.commit with a + * new buffer attached, applying the wl_surface state, + * wl_surface.frame callback trigger, presentation, or any other + * point in the compositor cycle is used to measure the frame or + * field times. There is no support for detecting missed or late + * frames/fields/buffers either, and there is no support whatsoever + * for cooperating with interlaced compositor output. + * + * The composited image quality resulting from the use of + * interlaced buffers is explicitly undefined. A compositor may use + * elaborate hardware features or software to deinterlace and + * create progressive output frames from a sequence of interlaced + * input buffers, or it may produce substandard image quality. + * However, compositors that cannot guarantee reasonable image + * quality in all cases are recommended to just reject all + * interlaced buffers. + * + * Any argument errors, including non-positive width or height, + * mismatch between the number of planes and the format, bad + * format, bad offset or stride, may be indicated by fatal protocol + * errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + * OUT_OF_BOUNDS. + * + * Dmabuf import errors in the server that are not obvious client + * bugs are returned via the 'failed' event as non-fatal. This + * allows attempting dmabuf sharing and falling back in the client + * if it fails. + * + * This request can be sent only once in the object's lifetime, + * after which the only legal request is destroy. This object + * should be destroyed after issuing a 'create' request. Attempting + * to use this object after issuing 'create' raises the + * ALREADY_USED protocol error. + * + * It is not mandatory to issue 'create'. If a client wants to + * cancel the buffer creation, it can just destroy this object. + * @param width base plane width in pixels + * @param height base plane height in pixels + * @param format DRM_FORMAT code + * @param flags see enum flags + */ + void (*create)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags); + /** + * immediately create a wl_buffer from the given dmabufs + * + * This asks for immediate creation of a wl_buffer by importing + * the added dmabufs. + * + * In case of import success, no event is sent from the server, and + * the wl_buffer is ready to be used by the client. + * + * Upon import failure, either of the following may happen, as seen + * fit by the implementation: - the client is terminated with one + * of the following fatal protocol errors: - INCOMPLETE, + * INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, in case of + * argument errors such as mismatch between the number of planes + * and the format, bad format, non-positive width or height, or bad + * offset or stride. - INVALID_WL_BUFFER, in case the cause for + * failure is unknown or platform specific. - the server creates an + * invalid wl_buffer, marks it as failed and sends a 'failed' event + * to the client. The result of using this invalid wl_buffer as an + * argument in any request by the client is defined by the + * compositor implementation. + * + * This takes the same arguments as a 'create' request, and obeys + * the same restrictions. + * @param buffer_id id for the newly created wl_buffer + * @param width base plane width in pixels + * @param height base plane height in pixels + * @param format DRM_FORMAT code + * @param flags see enum flags + * @since 2 + */ + void (*create_immed)(struct wl_client *client, + struct wl_resource *resource, + uint32_t buffer_id, + int32_t width, + int32_t height, + uint32_t format, + uint32_t flags); + /** + * set the target device of the wl_buffer + * + * Set the device the compositor should import the dmabufs to for + * sampling in the next create or create_immed request. + * + * To avoid race conditions when the compositor removes a device + * from the tranches, it is not a protocol error if the device + * hasn't been advertised by the compositor in a tranche with the + * sampling flag, but the import is likely to fail in that case. + * + * If the client doesn't know a suitable target device, it + * shouldn't set one, and the compositor should attempt import on + * all devices it supports. + * + * If the array is too small to contain a dev_t or larger than + * required, the invalid_dev_t_size error will be emitted. + * @param device device dev_t value + * @since 6 + */ + void (*set_sampling_device)(struct wl_client *client, + struct wl_resource *resource, + struct wl_array *device); +}; + +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATED 0 +#define ZWP_LINUX_BUFFER_PARAMS_V1_FAILED 1 + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATED_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_FAILED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + */ +#define ZWP_LINUX_BUFFER_PARAMS_V1_SET_SAMPLING_DEVICE_SINCE_VERSION 6 + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * Sends an created event to the client owning the resource. + * @param resource_ The client's resource + * @param buffer id for the the newly created wl_buffer + */ +static inline void +zwp_linux_buffer_params_v1_send_created(struct wl_resource *resource_, struct wl_resource *buffer) +{ + wl_resource_post_event(resource_, ZWP_LINUX_BUFFER_PARAMS_V1_CREATED, buffer); +} + +/** + * @ingroup iface_zwp_linux_buffer_params_v1 + * Sends an failed event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_linux_buffer_params_v1_send_failed(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_LINUX_BUFFER_PARAMS_V1_FAILED); +} + +#ifndef ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM +enum zwp_linux_dmabuf_feedback_v1_tranche_flags { + /** + * direct scan-out tranche + * + * The scanout flag is a hint that direct scan-out may be + * attempted by the compositor on the target device if the client + * appropriately allocates a buffer. How to allocate a buffer that + * can be scanned out on the target device is + * implementation-defined. + * @since 4 + */ + ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT = 1, + /** + * sampling tranche + * + * The sampling flag describes that the compositor is able to + * efficiently sample from buffers imported to the target device if + * the client appropriately allocates a buffer. How to allocate a + * buffer that can be efficiently sampled on the target device is + * implementation defined. + * @since 6 + */ + ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SAMPLING = 2, +}; +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT_SINCE_VERSION 4 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SAMPLING_SINCE_VERSION 6 +#endif /* ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM */ + +#ifndef ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM_IS_VALID +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM_IS_VALID +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Validate a zwp_linux_dmabuf_feedback_v1 tranche_flags value. + * + * @return true on success, false on error. + * @ref zwp_linux_dmabuf_feedback_v1_tranche_flags + */ +static inline bool +zwp_linux_dmabuf_feedback_v1_tranche_flags_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 4) + valid |= ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT; + if (version >= 6) + valid |= ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SAMPLING; + return (value & ~valid) == 0; +} +#endif /* ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_ENUM_IS_VALID */ + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * @struct zwp_linux_dmabuf_feedback_v1_interface + */ +struct zwp_linux_dmabuf_feedback_v1_interface { + /** + * destroy the feedback object + * + * Using this request a client can tell the server that it is not + * going to use the zwp_linux_dmabuf_feedback_v1 object anymore. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_DONE 0 +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_FORMAT_TABLE 1 +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_MAIN_DEVICE 2 +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_DONE 3 +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_TARGET_DEVICE 4 +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FORMATS 5 +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS 6 + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_FORMAT_TABLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_MAIN_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_TARGET_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FORMATS_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + */ +#define ZWP_LINUX_DMABUF_FEEDBACK_V1_DESTROY_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_DONE); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an format_table event to the client owning the resource. + * @param resource_ The client's resource + * @param fd table file descriptor + * @param size table size, in bytes + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_format_table(struct wl_resource *resource_, int32_t fd, uint32_t size) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_FORMAT_TABLE, fd, size); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an main_device event to the client owning the resource. + * @param resource_ The client's resource + * @param device device dev_t value + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_main_device(struct wl_resource *resource_, struct wl_array *device) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_MAIN_DEVICE, device); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an tranche_done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_tranche_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_DONE); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an tranche_target_device event to the client owning the resource. + * @param resource_ The client's resource + * @param device device dev_t value + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(struct wl_resource *resource_, struct wl_array *device) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_TARGET_DEVICE, device); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an tranche_formats event to the client owning the resource. + * @param resource_ The client's resource + * @param indices array of 16-bit indexes + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_tranche_formats(struct wl_resource *resource_, struct wl_array *indices) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FORMATS, indices); +} + +/** + * @ingroup iface_zwp_linux_dmabuf_feedback_v1 + * Sends an tranche_flags event to the client owning the resource. + * @param resource_ The client's resource + * @param flags tranche flags + */ +static inline void +zwp_linux_dmabuf_feedback_v1_send_tranche_flags(struct wl_resource *resource_, uint32_t flags) +{ + wl_resource_post_event(resource_, ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS, flags); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/pointer-constraints-unstable-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/pointer-constraints-unstable-v1-protocol.c new file mode 100644 index 000000000..89ea811ba --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/pointer-constraints-unstable-v1-protocol.c @@ -0,0 +1,109 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2014 Jonas ร…dahl + * Copyright ยฉ 2015 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_pointer_interface; +extern const struct wl_interface wl_region_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface zwp_confined_pointer_v1_interface; +extern const struct wl_interface zwp_locked_pointer_v1_interface; + +static const struct wl_interface *pointer_constraints_unstable_v1_types[] = { + NULL, + NULL, + &zwp_locked_pointer_v1_interface, + &wl_surface_interface, + &wl_pointer_interface, + &wl_region_interface, + NULL, + &zwp_confined_pointer_v1_interface, + &wl_surface_interface, + &wl_pointer_interface, + &wl_region_interface, + NULL, + &wl_region_interface, + &wl_region_interface, +}; + +static const struct wl_message zwp_pointer_constraints_v1_requests[] = { + { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, + { "lock_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 2 }, + { "confine_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 7 }, +}; + +WL_PRIVATE const struct wl_interface zwp_pointer_constraints_v1_interface = { + "zwp_pointer_constraints_v1", 1, + 3, zwp_pointer_constraints_v1_requests, + 0, NULL, +}; + +static const struct wl_message zwp_locked_pointer_v1_requests[] = { + { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, + { "set_cursor_position_hint", "ff", pointer_constraints_unstable_v1_types + 0 }, + { "set_region", "?o", pointer_constraints_unstable_v1_types + 12 }, +}; + +static const struct wl_message zwp_locked_pointer_v1_events[] = { + { "locked", "", pointer_constraints_unstable_v1_types + 0 }, + { "unlocked", "", pointer_constraints_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_locked_pointer_v1_interface = { + "zwp_locked_pointer_v1", 1, + 3, zwp_locked_pointer_v1_requests, + 2, zwp_locked_pointer_v1_events, +}; + +static const struct wl_message zwp_confined_pointer_v1_requests[] = { + { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, + { "set_region", "?o", pointer_constraints_unstable_v1_types + 13 }, +}; + +static const struct wl_message zwp_confined_pointer_v1_events[] = { + { "confined", "", pointer_constraints_unstable_v1_types + 0 }, + { "unconfined", "", pointer_constraints_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_confined_pointer_v1_interface = { + "zwp_confined_pointer_v1", 1, + 2, zwp_confined_pointer_v1_requests, + 2, zwp_confined_pointer_v1_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/pointer-constraints-unstable-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/pointer-constraints-unstable-v1-server-protocol.h new file mode 100644 index 000000000..8aeb92141 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/pointer-constraints-unstable-v1-server-protocol.h @@ -0,0 +1,619 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef POINTER_CONSTRAINTS_UNSTABLE_V1_SERVER_PROTOCOL_H +#define POINTER_CONSTRAINTS_UNSTABLE_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_pointer_constraints_unstable_v1 The pointer_constraints_unstable_v1 protocol + * protocol for constraining pointer motions + * + * @section page_desc_pointer_constraints_unstable_v1 Description + * + * This protocol specifies a set of interfaces used for adding constraints to + * the motion of a pointer. Possible constraints include confining pointer + * motions to a given region, or locking it to its current position. + * + * In order to constrain the pointer, a client must first bind the global + * interface "wp_pointer_constraints" which, if a compositor supports pointer + * constraints, is exposed by the registry. Using the bound global object, the + * client uses the request that corresponds to the type of constraint it wants + * to make. See wp_pointer_constraints for more details. + * + * Warning! The protocol described in this file is experimental and backward + * incompatible changes may be made. Backward compatible changes may be added + * together with the corresponding interface version bump. Backward + * incompatible changes are done by bumping the version number in the protocol + * and interface names and resetting the interface version. Once the protocol + * is to be declared stable, the 'z' prefix and the version number in the + * protocol and interface names are removed and the interface version number is + * reset. + * + * @section page_ifaces_pointer_constraints_unstable_v1 Interfaces + * - @subpage page_iface_zwp_pointer_constraints_v1 - constrain the movement of a pointer + * - @subpage page_iface_zwp_locked_pointer_v1 - receive relative pointer motion events + * - @subpage page_iface_zwp_confined_pointer_v1 - confined pointer object + * @section page_copyright_pointer_constraints_unstable_v1 Copyright + *
+ *
+ * Copyright ยฉ 2014      Jonas ร…dahl
+ * Copyright ยฉ 2015      Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_pointer; +struct wl_region; +struct wl_surface; +struct zwp_confined_pointer_v1; +struct zwp_locked_pointer_v1; +struct zwp_pointer_constraints_v1; + +#ifndef ZWP_POINTER_CONSTRAINTS_V1_INTERFACE +#define ZWP_POINTER_CONSTRAINTS_V1_INTERFACE +/** + * @page page_iface_zwp_pointer_constraints_v1 zwp_pointer_constraints_v1 + * @section page_iface_zwp_pointer_constraints_v1_desc Description + * + * The global interface exposing pointer constraining functionality. It + * exposes two requests: lock_pointer for locking the pointer to its + * position, and confine_pointer for locking the pointer to a region. + * + * The lock_pointer and confine_pointer requests create the objects + * wp_locked_pointer and wp_confined_pointer respectively, and the client can + * use these objects to interact with the lock. + * + * For any surface, only one lock or confinement may be active across all + * wl_pointer objects of the same seat. If a lock or confinement is requested + * when another lock or confinement is active or requested on the same surface + * and with any of the wl_pointer objects of the same seat, an + * 'already_constrained' error will be raised. + * @section page_iface_zwp_pointer_constraints_v1_api API + * See @ref iface_zwp_pointer_constraints_v1. + */ +/** + * @defgroup iface_zwp_pointer_constraints_v1 The zwp_pointer_constraints_v1 interface + * + * The global interface exposing pointer constraining functionality. It + * exposes two requests: lock_pointer for locking the pointer to its + * position, and confine_pointer for locking the pointer to a region. + * + * The lock_pointer and confine_pointer requests create the objects + * wp_locked_pointer and wp_confined_pointer respectively, and the client can + * use these objects to interact with the lock. + * + * For any surface, only one lock or confinement may be active across all + * wl_pointer objects of the same seat. If a lock or confinement is requested + * when another lock or confinement is active or requested on the same surface + * and with any of the wl_pointer objects of the same seat, an + * 'already_constrained' error will be raised. + */ +extern const struct wl_interface zwp_pointer_constraints_v1_interface; +#endif +#ifndef ZWP_LOCKED_POINTER_V1_INTERFACE +#define ZWP_LOCKED_POINTER_V1_INTERFACE +/** + * @page page_iface_zwp_locked_pointer_v1 zwp_locked_pointer_v1 + * @section page_iface_zwp_locked_pointer_v1_desc Description + * + * The wp_locked_pointer interface represents a locked pointer state. + * + * While the lock of this object is active, the wl_pointer objects of the + * associated seat will not emit any wl_pointer.motion events. + * + * This object will send the event 'locked' when the lock is activated. + * Whenever the lock is activated, it is guaranteed that the locked surface + * will already have received pointer focus and that the pointer will be + * within the region passed to the request creating this object. + * + * To unlock the pointer, send the destroy request. This will also destroy + * the wp_locked_pointer object. + * + * If the compositor decides to unlock the pointer the unlocked event is + * sent. See wp_locked_pointer.unlock for details. + * + * When unlocking, the compositor may warp the cursor position to the set + * cursor position hint. If it does, it will not result in any relative + * motion events emitted via wp_relative_pointer. + * + * If the surface the lock was requested on is destroyed and the lock is not + * yet activated, the wp_locked_pointer object is now defunct and must be + * destroyed. + * @section page_iface_zwp_locked_pointer_v1_api API + * See @ref iface_zwp_locked_pointer_v1. + */ +/** + * @defgroup iface_zwp_locked_pointer_v1 The zwp_locked_pointer_v1 interface + * + * The wp_locked_pointer interface represents a locked pointer state. + * + * While the lock of this object is active, the wl_pointer objects of the + * associated seat will not emit any wl_pointer.motion events. + * + * This object will send the event 'locked' when the lock is activated. + * Whenever the lock is activated, it is guaranteed that the locked surface + * will already have received pointer focus and that the pointer will be + * within the region passed to the request creating this object. + * + * To unlock the pointer, send the destroy request. This will also destroy + * the wp_locked_pointer object. + * + * If the compositor decides to unlock the pointer the unlocked event is + * sent. See wp_locked_pointer.unlock for details. + * + * When unlocking, the compositor may warp the cursor position to the set + * cursor position hint. If it does, it will not result in any relative + * motion events emitted via wp_relative_pointer. + * + * If the surface the lock was requested on is destroyed and the lock is not + * yet activated, the wp_locked_pointer object is now defunct and must be + * destroyed. + */ +extern const struct wl_interface zwp_locked_pointer_v1_interface; +#endif +#ifndef ZWP_CONFINED_POINTER_V1_INTERFACE +#define ZWP_CONFINED_POINTER_V1_INTERFACE +/** + * @page page_iface_zwp_confined_pointer_v1 zwp_confined_pointer_v1 + * @section page_iface_zwp_confined_pointer_v1_desc Description + * + * The wp_confined_pointer interface represents a confined pointer state. + * + * This object will send the event 'confined' when the confinement is + * activated. Whenever the confinement is activated, it is guaranteed that + * the surface the pointer is confined to will already have received pointer + * focus and that the pointer will be within the region passed to the request + * creating this object. It is up to the compositor to decide whether this + * requires some user interaction and if the pointer will warp to within the + * passed region if outside. + * + * To unconfine the pointer, send the destroy request. This will also destroy + * the wp_confined_pointer object. + * + * If the compositor decides to unconfine the pointer the unconfined event is + * sent. The wp_confined_pointer object is at this point defunct and should + * be destroyed. + * @section page_iface_zwp_confined_pointer_v1_api API + * See @ref iface_zwp_confined_pointer_v1. + */ +/** + * @defgroup iface_zwp_confined_pointer_v1 The zwp_confined_pointer_v1 interface + * + * The wp_confined_pointer interface represents a confined pointer state. + * + * This object will send the event 'confined' when the confinement is + * activated. Whenever the confinement is activated, it is guaranteed that + * the surface the pointer is confined to will already have received pointer + * focus and that the pointer will be within the region passed to the request + * creating this object. It is up to the compositor to decide whether this + * requires some user interaction and if the pointer will warp to within the + * passed region if outside. + * + * To unconfine the pointer, send the destroy request. This will also destroy + * the wp_confined_pointer object. + * + * If the compositor decides to unconfine the pointer the unconfined event is + * sent. The wp_confined_pointer object is at this point defunct and should + * be destroyed. + */ +extern const struct wl_interface zwp_confined_pointer_v1_interface; +#endif + +#ifndef ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM +#define ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM +/** + * @ingroup iface_zwp_pointer_constraints_v1 + * wp_pointer_constraints error values + * + * These errors can be emitted in response to wp_pointer_constraints + * requests. + */ +enum zwp_pointer_constraints_v1_error { + /** + * pointer constraint already requested on that surface + */ + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED = 1, +}; +#endif /* ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM */ + +#ifndef ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM_IS_VALID +#define ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_zwp_pointer_constraints_v1 + * Validate a zwp_pointer_constraints_v1 error value. + * + * @return true on success, false on error. + * @ref zwp_pointer_constraints_v1_error + */ +static inline bool +zwp_pointer_constraints_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM_IS_VALID */ + +#ifndef ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM +#define ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM +/** + * @ingroup iface_zwp_pointer_constraints_v1 + * constraint lifetime + * + * These values represent different lifetime semantics. They are passed + * as arguments to the factory requests to specify how the constraint + * lifetimes should be managed. + */ +enum zwp_pointer_constraints_v1_lifetime { + /** + * the pointer constraint is defunct once deactivated + * + * A oneshot pointer constraint will never reactivate once it has + * been deactivated. See the corresponding deactivation event + * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) + * for details. + */ + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT = 1, + /** + * the pointer constraint may reactivate + * + * A persistent pointer constraint may again reactivate once it + * has been deactivated. See the corresponding deactivation event + * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) + * for details. + */ + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT = 2, +}; +#endif /* ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM */ + +#ifndef ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM_IS_VALID +#define ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM_IS_VALID +/** + * @ingroup iface_zwp_pointer_constraints_v1 + * Validate a zwp_pointer_constraints_v1 lifetime value. + * + * @return true on success, false on error. + * @ref zwp_pointer_constraints_v1_lifetime + */ +static inline bool +zwp_pointer_constraints_v1_lifetime_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT: + return version >= 1; + case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM_IS_VALID */ + +/** + * @ingroup iface_zwp_pointer_constraints_v1 + * @struct zwp_pointer_constraints_v1_interface + */ +struct zwp_pointer_constraints_v1_interface { + /** + * destroy the pointer constraints manager object + * + * Used by the client to notify the server that it will no longer + * use this pointer constraints object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * lock pointer to a position + * + * The lock_pointer request lets the client request to disable + * movements of the virtual pointer (i.e. the cursor), effectively + * locking the pointer to a position. This request may not take + * effect immediately; in the future, when the compositor deems + * implementation-specific constraints are satisfied, the pointer + * lock will be activated and the compositor sends a locked event. + * + * The protocol provides no guarantee that the constraints are ever + * satisfied, and does not require the compositor to send an error + * if the constraints cannot ever be satisfied. It is thus possible + * to request a lock that will never activate. + * + * There may not be another pointer constraint of any kind + * requested or active on the surface for any of the wl_pointer + * objects of the seat of the passed pointer when requesting a + * lock. If there is, an error will be raised. See general pointer + * lock documentation for more details. + * + * The intersection of the region passed with this request and the + * input region of the surface is used to determine where the + * pointer must be in order for the lock to activate. It is up to + * the compositor whether to warp the pointer or require some kind + * of user interaction for the lock to activate. If the region is + * null the surface input region is used. + * + * A surface may receive pointer focus without the lock being + * activated. + * + * The request creates a new object wp_locked_pointer which is used + * to interact with the lock as well as receive updates about its + * state. See the the description of wp_locked_pointer for further + * information. + * + * Note that while a pointer is locked, the wl_pointer objects of + * the corresponding seat will not emit any wl_pointer.motion + * events, but relative motion events will still be emitted via + * wp_relative_pointer objects of the same seat. wl_pointer.axis + * and wl_pointer.button events are unaffected. + * @param surface surface to lock pointer to + * @param pointer the pointer that should be locked + * @param region region of surface + * @param lifetime lock lifetime + */ + void (*lock_pointer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface, + struct wl_resource *pointer, + struct wl_resource *region, + uint32_t lifetime); + /** + * confine pointer to a region + * + * The confine_pointer request lets the client request to confine + * the pointer cursor to a given region. This request may not take + * effect immediately; in the future, when the compositor deems + * implementation- specific constraints are satisfied, the pointer + * confinement will be activated and the compositor sends a + * confined event. + * + * The intersection of the region passed with this request and the + * input region of the surface is used to determine where the + * pointer must be in order for the confinement to activate. It is + * up to the compositor whether to warp the pointer or require some + * kind of user interaction for the confinement to activate. If the + * region is null the surface input region is used. + * + * The request will create a new object wp_confined_pointer which + * is used to interact with the confinement as well as receive + * updates about its state. See the the description of + * wp_confined_pointer for further information. + * @param surface surface to lock pointer to + * @param pointer the pointer that should be confined + * @param region region of surface + * @param lifetime confinement lifetime + */ + void (*confine_pointer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface, + struct wl_resource *pointer, + struct wl_resource *region, + uint32_t lifetime); +}; + + +/** + * @ingroup iface_zwp_pointer_constraints_v1 + */ +#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_pointer_constraints_v1 + */ +#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_pointer_constraints_v1 + */ +#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_locked_pointer_v1 + * @struct zwp_locked_pointer_v1_interface + */ +struct zwp_locked_pointer_v1_interface { + /** + * destroy the locked pointer object + * + * Destroy the locked pointer object. If applicable, the + * compositor will unlock the pointer. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the pointer cursor position hint + * + * Set the cursor position hint relative to the top left corner + * of the surface. + * + * If the client is drawing its own cursor, it should update the + * position hint to the position of its own cursor. A compositor + * may use this information to warp the pointer upon unlock in + * order to avoid pointer jumps. + * + * The cursor position hint is double buffered. The new hint will + * only take effect when the associated surface gets it pending + * state applied. See wl_surface.commit for details. + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + */ + void (*set_cursor_position_hint)(struct wl_client *client, + struct wl_resource *resource, + wl_fixed_t surface_x, + wl_fixed_t surface_y); + /** + * set a new lock region + * + * Set a new region used to lock the pointer. + * + * The new lock region is double-buffered. The new lock region will + * only take effect when the associated surface gets its pending + * state applied. See wl_surface.commit for details. + * + * For details about the lock region, see wp_locked_pointer. + * @param region region of surface + */ + void (*set_region)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region); +}; + +#define ZWP_LOCKED_POINTER_V1_LOCKED 0 +#define ZWP_LOCKED_POINTER_V1_UNLOCKED 1 + +/** + * @ingroup iface_zwp_locked_pointer_v1 + */ +#define ZWP_LOCKED_POINTER_V1_LOCKED_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_locked_pointer_v1 + */ +#define ZWP_LOCKED_POINTER_V1_UNLOCKED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_locked_pointer_v1 + */ +#define ZWP_LOCKED_POINTER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_locked_pointer_v1 + */ +#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_locked_pointer_v1 + */ +#define ZWP_LOCKED_POINTER_V1_SET_REGION_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_locked_pointer_v1 + * Sends an locked event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_locked_pointer_v1_send_locked(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_LOCKED_POINTER_V1_LOCKED); +} + +/** + * @ingroup iface_zwp_locked_pointer_v1 + * Sends an unlocked event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_locked_pointer_v1_send_unlocked(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_LOCKED_POINTER_V1_UNLOCKED); +} + +/** + * @ingroup iface_zwp_confined_pointer_v1 + * @struct zwp_confined_pointer_v1_interface + */ +struct zwp_confined_pointer_v1_interface { + /** + * destroy the confined pointer object + * + * Destroy the confined pointer object. If applicable, the + * compositor will unconfine the pointer. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set a new confine region + * + * Set a new region used to confine the pointer. + * + * The new confine region is double-buffered. The new confine + * region will only take effect when the associated surface gets + * its pending state applied. See wl_surface.commit for details. + * + * If the confinement is active when the new confinement region is + * applied and the pointer ends up outside of newly applied region, + * the pointer may warped to a position within the new confinement + * region. If warped, a wl_pointer.motion event will be emitted, + * but no wp_relative_pointer.relative_motion event. + * + * The compositor may also, instead of using the new region, + * unconfine the pointer. + * + * For details about the confine region, see wp_confined_pointer. + * @param region region of surface + */ + void (*set_region)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region); +}; + +#define ZWP_CONFINED_POINTER_V1_CONFINED 0 +#define ZWP_CONFINED_POINTER_V1_UNCONFINED 1 + +/** + * @ingroup iface_zwp_confined_pointer_v1 + */ +#define ZWP_CONFINED_POINTER_V1_CONFINED_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_confined_pointer_v1 + */ +#define ZWP_CONFINED_POINTER_V1_UNCONFINED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_confined_pointer_v1 + */ +#define ZWP_CONFINED_POINTER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_confined_pointer_v1 + */ +#define ZWP_CONFINED_POINTER_V1_SET_REGION_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_confined_pointer_v1 + * Sends an confined event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_confined_pointer_v1_send_confined(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_CONFINED_POINTER_V1_CONFINED); +} + +/** + * @ingroup iface_zwp_confined_pointer_v1 + * Sends an unconfined event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_confined_pointer_v1_send_unconfined(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWP_CONFINED_POINTER_V1_UNCONFINED); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/presentation-time-protocol.c b/app/src/main/cpp/waylandcomp/generated/presentation-time-protocol.c new file mode 100644 index 000000000..63eea910d --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/presentation-time-protocol.c @@ -0,0 +1,84 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2013-2014 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface wp_presentation_feedback_interface; + +static const struct wl_interface *presentation_time_types[] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + &wl_surface_interface, + &wp_presentation_feedback_interface, + &wl_output_interface, +}; + +static const struct wl_message wp_presentation_requests[] = { + { "destroy", "", presentation_time_types + 0 }, + { "feedback", "on", presentation_time_types + 7 }, +}; + +static const struct wl_message wp_presentation_events[] = { + { "clock_id", "u", presentation_time_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_presentation_interface = { + "wp_presentation", 2, + 2, wp_presentation_requests, + 1, wp_presentation_events, +}; + +static const struct wl_message wp_presentation_feedback_events[] = { + { "sync_output", "o", presentation_time_types + 9 }, + { "presented", "uuuuuuu", presentation_time_types + 0 }, + { "discarded", "", presentation_time_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_presentation_feedback_interface = { + "wp_presentation_feedback", 2, + 0, NULL, + 3, wp_presentation_feedback_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/presentation-time-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/presentation-time-server-protocol.h new file mode 100644 index 000000000..1e1a39c07 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/presentation-time-server-protocol.h @@ -0,0 +1,395 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef PRESENTATION_TIME_SERVER_PROTOCOL_H +#define PRESENTATION_TIME_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_presentation_time The presentation_time protocol + * @section page_ifaces_presentation_time Interfaces + * - @subpage page_iface_wp_presentation - timed presentation related wl_surface requests + * - @subpage page_iface_wp_presentation_feedback - presentation time feedback event + * @section page_copyright_presentation_time Copyright + *
+ *
+ * Copyright ยฉ 2013-2014 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_surface; +struct wp_presentation; +struct wp_presentation_feedback; + +#ifndef WP_PRESENTATION_INTERFACE +#define WP_PRESENTATION_INTERFACE +/** + * @page page_iface_wp_presentation wp_presentation + * @section page_iface_wp_presentation_desc Description + * + * + * + * + * The main feature of this interface is accurate presentation + * timing feedback to ensure smooth video playback while maintaining + * audio/video synchronization. Some features use the concept of a + * presentation clock, which is defined in the + * presentation.clock_id event. + * + * A content update for a wl_surface is submitted by a + * wl_surface.commit request. Request 'feedback' associates with + * the wl_surface.commit and provides feedback on the content + * update, particularly the final realized presentation time. + * + * + * + * When the final realized presentation time is available, e.g. + * after a framebuffer flip completes, the requested + * presentation_feedback.presented events are sent. The final + * presentation time can differ from the compositor's predicted + * display update time and the update's target time, especially + * when the compositor misses its target vertical blanking period. + * @section page_iface_wp_presentation_api API + * See @ref iface_wp_presentation. + */ +/** + * @defgroup iface_wp_presentation The wp_presentation interface + * + * + * + * + * The main feature of this interface is accurate presentation + * timing feedback to ensure smooth video playback while maintaining + * audio/video synchronization. Some features use the concept of a + * presentation clock, which is defined in the + * presentation.clock_id event. + * + * A content update for a wl_surface is submitted by a + * wl_surface.commit request. Request 'feedback' associates with + * the wl_surface.commit and provides feedback on the content + * update, particularly the final realized presentation time. + * + * + * + * When the final realized presentation time is available, e.g. + * after a framebuffer flip completes, the requested + * presentation_feedback.presented events are sent. The final + * presentation time can differ from the compositor's predicted + * display update time and the update's target time, especially + * when the compositor misses its target vertical blanking period. + */ +extern const struct wl_interface wp_presentation_interface; +#endif +#ifndef WP_PRESENTATION_FEEDBACK_INTERFACE +#define WP_PRESENTATION_FEEDBACK_INTERFACE +/** + * @page page_iface_wp_presentation_feedback wp_presentation_feedback + * @section page_iface_wp_presentation_feedback_desc Description + * + * A presentation_feedback object returns an indication that a + * wl_surface content update has become visible to the user. + * One object corresponds to one content update submission + * (wl_surface.commit). There are two possible outcomes: the + * content update is presented to the user, and a presentation + * timestamp delivered; or, the user did not see the content + * update because it was superseded or its surface destroyed, + * and the content update is discarded. + * + * Once a presentation_feedback object has delivered a 'presented' + * or 'discarded' event it is automatically destroyed. + * @section page_iface_wp_presentation_feedback_api API + * See @ref iface_wp_presentation_feedback. + */ +/** + * @defgroup iface_wp_presentation_feedback The wp_presentation_feedback interface + * + * A presentation_feedback object returns an indication that a + * wl_surface content update has become visible to the user. + * One object corresponds to one content update submission + * (wl_surface.commit). There are two possible outcomes: the + * content update is presented to the user, and a presentation + * timestamp delivered; or, the user did not see the content + * update because it was superseded or its surface destroyed, + * and the content update is discarded. + * + * Once a presentation_feedback object has delivered a 'presented' + * or 'discarded' event it is automatically destroyed. + */ +extern const struct wl_interface wp_presentation_feedback_interface; +#endif + +#ifndef WP_PRESENTATION_ERROR_ENUM +#define WP_PRESENTATION_ERROR_ENUM +/** + * @ingroup iface_wp_presentation + * fatal presentation errors + * + * These fatal protocol errors may be emitted in response to + * illegal presentation requests. + */ +enum wp_presentation_error { + /** + * invalid value in tv_nsec + */ + WP_PRESENTATION_ERROR_INVALID_TIMESTAMP = 0, + /** + * invalid flag + */ + WP_PRESENTATION_ERROR_INVALID_FLAG = 1, +}; +#endif /* WP_PRESENTATION_ERROR_ENUM */ + +#ifndef WP_PRESENTATION_ERROR_ENUM_IS_VALID +#define WP_PRESENTATION_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_presentation + * Validate a wp_presentation error value. + * + * @return true on success, false on error. + * @ref wp_presentation_error + */ +static inline bool +wp_presentation_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_PRESENTATION_ERROR_INVALID_TIMESTAMP: + return version >= 1; + case WP_PRESENTATION_ERROR_INVALID_FLAG: + return version >= 1; + default: + return false; + } +} +#endif /* WP_PRESENTATION_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_presentation + * @struct wp_presentation_interface + */ +struct wp_presentation_interface { + /** + * unbind from the presentation interface + * + * Informs the server that the client will no longer be using + * this protocol object. Existing objects created by this object + * are not affected. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * request presentation feedback information + * + * Request presentation feedback for the current content + * submission on the given surface. This creates a new + * presentation_feedback object, which will deliver the feedback + * information once. If multiple presentation_feedback objects are + * created for the same submission, they will all deliver the same + * information. + * + * For details on what information is returned, see the + * presentation_feedback interface. + * @param surface target surface + * @param callback new feedback object + */ + void (*feedback)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface, + uint32_t callback); +}; + +#define WP_PRESENTATION_CLOCK_ID 0 + +/** + * @ingroup iface_wp_presentation + */ +#define WP_PRESENTATION_CLOCK_ID_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_presentation + */ +#define WP_PRESENTATION_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_presentation + */ +#define WP_PRESENTATION_FEEDBACK_SINCE_VERSION 1 + +/** + * @ingroup iface_wp_presentation + * Sends an clock_id event to the client owning the resource. + * @param resource_ The client's resource + * @param clk_id platform clock identifier + */ +static inline void +wp_presentation_send_clock_id(struct wl_resource *resource_, uint32_t clk_id) +{ + wl_resource_post_event(resource_, WP_PRESENTATION_CLOCK_ID, clk_id); +} + +#ifndef WP_PRESENTATION_FEEDBACK_KIND_ENUM +#define WP_PRESENTATION_FEEDBACK_KIND_ENUM +/** + * @ingroup iface_wp_presentation_feedback + * bitmask of flags in presented event + * + * These flags provide information about how the presentation of + * the related content update was done. The intent is to help + * clients assess the reliability of the feedback and the visual + * quality with respect to possible tearing and timings. + */ +enum wp_presentation_feedback_kind { + /** + * presentation was vsync'd + * + * The presentation was synchronized to the "vertical retrace" by + * the display hardware such that tearing does not happen. Relying + * on software scheduling is not acceptable for this flag. If + * presentation is done by a copy to the active frontbuffer, then + * it must guarantee that tearing cannot happen. + */ + WP_PRESENTATION_FEEDBACK_KIND_VSYNC = 0x1, + /** + * hardware provided the presentation timestamp + * + * The display hardware provided measurements that the hardware + * driver converted into a presentation timestamp. Sampling a clock + * in software is not acceptable for this flag. + */ + WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK = 0x2, + /** + * hardware signalled the start of the presentation + * + * The display hardware signalled that it started using the new + * image content. The opposite of this is e.g. a timer being used + * to guess when the display hardware has switched to the new image + * content. + */ + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION = 0x4, + /** + * presentation was done zero-copy + * + * The presentation of this update was done zero-copy. This means + * the buffer from the client was given to display hardware as is, + * without copying it. Compositing with OpenGL counts as copying, + * even if textured directly from the client buffer. Possible + * zero-copy cases include direct scanout of a fullscreen surface + * and a surface on a hardware overlay. + */ + WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY = 0x8, +}; +#endif /* WP_PRESENTATION_FEEDBACK_KIND_ENUM */ + +#ifndef WP_PRESENTATION_FEEDBACK_KIND_ENUM_IS_VALID +#define WP_PRESENTATION_FEEDBACK_KIND_ENUM_IS_VALID +/** + * @ingroup iface_wp_presentation_feedback + * Validate a wp_presentation_feedback kind value. + * + * @return true on success, false on error. + * @ref wp_presentation_feedback_kind + */ +static inline bool +wp_presentation_feedback_kind_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; + if (version >= 1) + valid |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + if (version >= 1) + valid |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; + if (version >= 1) + valid |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + return (value & ~valid) == 0; +} +#endif /* WP_PRESENTATION_FEEDBACK_KIND_ENUM_IS_VALID */ + +#define WP_PRESENTATION_FEEDBACK_SYNC_OUTPUT 0 +#define WP_PRESENTATION_FEEDBACK_PRESENTED 1 +#define WP_PRESENTATION_FEEDBACK_DISCARDED 2 + +/** + * @ingroup iface_wp_presentation_feedback + */ +#define WP_PRESENTATION_FEEDBACK_SYNC_OUTPUT_SINCE_VERSION 1 +/** + * @ingroup iface_wp_presentation_feedback + */ +#define WP_PRESENTATION_FEEDBACK_PRESENTED_SINCE_VERSION 1 +/** + * @ingroup iface_wp_presentation_feedback + */ +#define WP_PRESENTATION_FEEDBACK_DISCARDED_SINCE_VERSION 1 + + +/** + * @ingroup iface_wp_presentation_feedback + * Sends an sync_output event to the client owning the resource. + * @param resource_ The client's resource + * @param output presentation output + */ +static inline void +wp_presentation_feedback_send_sync_output(struct wl_resource *resource_, struct wl_resource *output) +{ + wl_resource_post_event(resource_, WP_PRESENTATION_FEEDBACK_SYNC_OUTPUT, output); +} + +/** + * @ingroup iface_wp_presentation_feedback + * Sends an presented event to the client owning the resource. + * @param resource_ The client's resource + * @param tv_sec_hi high 32 bits of the seconds part of the presentation timestamp + * @param tv_sec_lo low 32 bits of the seconds part of the presentation timestamp + * @param tv_nsec nanoseconds part of the presentation timestamp + * @param refresh nanoseconds till next refresh + * @param seq_hi high 32 bits of refresh counter + * @param seq_lo low 32 bits of refresh counter + * @param flags combination of 'kind' values + */ +static inline void +wp_presentation_feedback_send_presented(struct wl_resource *resource_, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) +{ + wl_resource_post_event(resource_, WP_PRESENTATION_FEEDBACK_PRESENTED, tv_sec_hi, tv_sec_lo, tv_nsec, refresh, seq_hi, seq_lo, flags); +} + +/** + * @ingroup iface_wp_presentation_feedback + * Sends an discarded event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wp_presentation_feedback_send_discarded(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WP_PRESENTATION_FEEDBACK_DISCARDED); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/relative-pointer-unstable-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/relative-pointer-unstable-v1-protocol.c new file mode 100644 index 000000000..454f95556 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/relative-pointer-unstable-v1-protocol.c @@ -0,0 +1,80 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2014 Jonas ร…dahl + * Copyright ยฉ 2015 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_pointer_interface; +extern const struct wl_interface zwp_relative_pointer_v1_interface; + +static const struct wl_interface *relative_pointer_unstable_v1_types[] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + &zwp_relative_pointer_v1_interface, + &wl_pointer_interface, +}; + +static const struct wl_message zwp_relative_pointer_manager_v1_requests[] = { + { "destroy", "", relative_pointer_unstable_v1_types + 0 }, + { "get_relative_pointer", "no", relative_pointer_unstable_v1_types + 6 }, +}; + +WL_PRIVATE const struct wl_interface zwp_relative_pointer_manager_v1_interface = { + "zwp_relative_pointer_manager_v1", 1, + 2, zwp_relative_pointer_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message zwp_relative_pointer_v1_requests[] = { + { "destroy", "", relative_pointer_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwp_relative_pointer_v1_events[] = { + { "relative_motion", "uuffff", relative_pointer_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_relative_pointer_v1_interface = { + "zwp_relative_pointer_v1", 1, + 1, zwp_relative_pointer_v1_requests, + 1, zwp_relative_pointer_v1_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/relative-pointer-unstable-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/relative-pointer-unstable-v1-server-protocol.h new file mode 100644 index 000000000..f40c97498 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/relative-pointer-unstable-v1-server-protocol.h @@ -0,0 +1,204 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef RELATIVE_POINTER_UNSTABLE_V1_SERVER_PROTOCOL_H +#define RELATIVE_POINTER_UNSTABLE_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_relative_pointer_unstable_v1 The relative_pointer_unstable_v1 protocol + * protocol for relative pointer motion events + * + * @section page_desc_relative_pointer_unstable_v1 Description + * + * This protocol specifies a set of interfaces used for making clients able to + * receive relative pointer events not obstructed by barriers (such as the + * monitor edge or other pointer barriers). + * + * To start receiving relative pointer events, a client must first bind the + * global interface "wp_relative_pointer_manager" which, if a compositor + * supports relative pointer motion events, is exposed by the registry. After + * having created the relative pointer manager proxy object, the client uses + * it to create the actual relative pointer object using the + * "get_relative_pointer" request given a wl_pointer. The relative pointer + * motion events will then, when applicable, be transmitted via the proxy of + * the newly created relative pointer object. See the documentation of the + * relative pointer interface for more details. + * + * Warning! The protocol described in this file is experimental and backward + * incompatible changes may be made. Backward compatible changes may be added + * together with the corresponding interface version bump. Backward + * incompatible changes are done by bumping the version number in the protocol + * and interface names and resetting the interface version. Once the protocol + * is to be declared stable, the 'z' prefix and the version number in the + * protocol and interface names are removed and the interface version number is + * reset. + * + * @section page_ifaces_relative_pointer_unstable_v1 Interfaces + * - @subpage page_iface_zwp_relative_pointer_manager_v1 - get relative pointer objects + * - @subpage page_iface_zwp_relative_pointer_v1 - relative pointer object + * @section page_copyright_relative_pointer_unstable_v1 Copyright + *
+ *
+ * Copyright ยฉ 2014      Jonas ร…dahl
+ * Copyright ยฉ 2015      Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_pointer; +struct zwp_relative_pointer_manager_v1; +struct zwp_relative_pointer_v1; + +#ifndef ZWP_RELATIVE_POINTER_MANAGER_V1_INTERFACE +#define ZWP_RELATIVE_POINTER_MANAGER_V1_INTERFACE +/** + * @page page_iface_zwp_relative_pointer_manager_v1 zwp_relative_pointer_manager_v1 + * @section page_iface_zwp_relative_pointer_manager_v1_desc Description + * + * A global interface used for getting the relative pointer object for a + * given pointer. + * @section page_iface_zwp_relative_pointer_manager_v1_api API + * See @ref iface_zwp_relative_pointer_manager_v1. + */ +/** + * @defgroup iface_zwp_relative_pointer_manager_v1 The zwp_relative_pointer_manager_v1 interface + * + * A global interface used for getting the relative pointer object for a + * given pointer. + */ +extern const struct wl_interface zwp_relative_pointer_manager_v1_interface; +#endif +#ifndef ZWP_RELATIVE_POINTER_V1_INTERFACE +#define ZWP_RELATIVE_POINTER_V1_INTERFACE +/** + * @page page_iface_zwp_relative_pointer_v1 zwp_relative_pointer_v1 + * @section page_iface_zwp_relative_pointer_v1_desc Description + * + * A wp_relative_pointer object is an extension to the wl_pointer interface + * used for emitting relative pointer events. It shares the same focus as + * wl_pointer objects of the same seat and will only emit events when it has + * focus. + * @section page_iface_zwp_relative_pointer_v1_api API + * See @ref iface_zwp_relative_pointer_v1. + */ +/** + * @defgroup iface_zwp_relative_pointer_v1 The zwp_relative_pointer_v1 interface + * + * A wp_relative_pointer object is an extension to the wl_pointer interface + * used for emitting relative pointer events. It shares the same focus as + * wl_pointer objects of the same seat and will only emit events when it has + * focus. + */ +extern const struct wl_interface zwp_relative_pointer_v1_interface; +#endif + +/** + * @ingroup iface_zwp_relative_pointer_manager_v1 + * @struct zwp_relative_pointer_manager_v1_interface + */ +struct zwp_relative_pointer_manager_v1_interface { + /** + * destroy the relative pointer manager object + * + * Used by the client to notify the server that it will no longer + * use this relative pointer manager object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * get a relative pointer object + * + * Create a relative pointer interface given a wl_pointer object. + * See the wp_relative_pointer interface for more details. + */ + void (*get_relative_pointer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *pointer); +}; + + +/** + * @ingroup iface_zwp_relative_pointer_manager_v1 + */ +#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_relative_pointer_manager_v1 + */ +#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_relative_pointer_v1 + * @struct zwp_relative_pointer_v1_interface + */ +struct zwp_relative_pointer_v1_interface { + /** + * release the relative pointer object + * + * + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define ZWP_RELATIVE_POINTER_V1_RELATIVE_MOTION 0 + +/** + * @ingroup iface_zwp_relative_pointer_v1 + */ +#define ZWP_RELATIVE_POINTER_V1_RELATIVE_MOTION_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_relative_pointer_v1 + */ +#define ZWP_RELATIVE_POINTER_V1_DESTROY_SINCE_VERSION 1 + +/** + * @ingroup iface_zwp_relative_pointer_v1 + * Sends an relative_motion event to the client owning the resource. + * @param resource_ The client's resource + * @param utime_hi high 32 bits of a 64 bit timestamp with microsecond granularity + * @param utime_lo low 32 bits of a 64 bit timestamp with microsecond granularity + * @param dx the x component of the motion vector + * @param dy the y component of the motion vector + * @param dx_unaccel the x component of the unaccelerated motion vector + * @param dy_unaccel the y component of the unaccelerated motion vector + */ +static inline void +zwp_relative_pointer_v1_send_relative_motion(struct wl_resource *resource_, uint32_t utime_hi, uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) +{ + wl_resource_post_event(resource_, ZWP_RELATIVE_POINTER_V1_RELATIVE_MOTION, utime_hi, utime_lo, dx, dy, dx_unaccel, dy_unaccel); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/text-input-unstable-v3-protocol.c b/app/src/main/cpp/waylandcomp/generated/text-input-unstable-v3-protocol.c new file mode 100644 index 000000000..a6030f8df --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/text-input-unstable-v3-protocol.c @@ -0,0 +1,103 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2012, 2013 Intel Corporation + * Copyright ยฉ 2015, 2016 Jan Arne Petersen + * Copyright ยฉ 2017, 2018 Red Hat, Inc. + * Copyright ยฉ 2018 Purism SPC + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface zwp_text_input_v3_interface; + +static const struct wl_interface *text_input_unstable_v3_types[] = { + NULL, + NULL, + NULL, + NULL, + &wl_surface_interface, + &wl_surface_interface, + &zwp_text_input_v3_interface, + &wl_seat_interface, +}; + +static const struct wl_message zwp_text_input_v3_requests[] = { + { "destroy", "", text_input_unstable_v3_types + 0 }, + { "enable", "", text_input_unstable_v3_types + 0 }, + { "disable", "", text_input_unstable_v3_types + 0 }, + { "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 }, + { "set_text_change_cause", "u", text_input_unstable_v3_types + 0 }, + { "set_content_type", "uu", text_input_unstable_v3_types + 0 }, + { "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 }, + { "commit", "", text_input_unstable_v3_types + 0 }, + { "set_available_actions", "2a", text_input_unstable_v3_types + 0 }, + { "show_input_panel", "2", text_input_unstable_v3_types + 0 }, + { "hide_input_panel", "2", text_input_unstable_v3_types + 0 }, +}; + +static const struct wl_message zwp_text_input_v3_events[] = { + { "enter", "o", text_input_unstable_v3_types + 4 }, + { "leave", "o", text_input_unstable_v3_types + 5 }, + { "preedit_string", "?sii", text_input_unstable_v3_types + 0 }, + { "commit_string", "?s", text_input_unstable_v3_types + 0 }, + { "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 }, + { "done", "u", text_input_unstable_v3_types + 0 }, + { "action", "2uu", text_input_unstable_v3_types + 0 }, + { "language", "2s", text_input_unstable_v3_types + 0 }, + { "preedit_hint", "2uuu", text_input_unstable_v3_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = { + "zwp_text_input_v3", 2, + 11, zwp_text_input_v3_requests, + 9, zwp_text_input_v3_events, +}; + +static const struct wl_message zwp_text_input_manager_v3_requests[] = { + { "destroy", "", text_input_unstable_v3_types + 0 }, + { "get_text_input", "no", text_input_unstable_v3_types + 6 }, +}; + +WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = { + "zwp_text_input_manager_v3", 2, + 2, zwp_text_input_manager_v3_requests, + 0, NULL, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/text-input-unstable-v3-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/text-input-unstable-v3-server-protocol.h new file mode 100644 index 000000000..c8473dd6a --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/text-input-unstable-v3-server-protocol.h @@ -0,0 +1,1098 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef TEXT_INPUT_UNSTABLE_V3_SERVER_PROTOCOL_H +#define TEXT_INPUT_UNSTABLE_V3_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol + * Protocol for composing text + * + * @section page_desc_text_input_unstable_v3 Description + * + * This protocol allows compositors to act as input methods and to send text + * to applications. A text input object is used to manage state of what are + * typically text entry fields in the application. + * + * This document adheres to the RFC 2119 when using words like "must", + * "should", "may", etc. + * + * Warning! The protocol described in this file is experimental and + * backward incompatible changes may be made. Backward compatible changes + * may be added together with the corresponding interface version bump. + * Backward incompatible changes are done by bumping the version number in + * the protocol and interface names and resetting the interface version. + * Once the protocol is to be declared stable, the 'z' prefix and the + * version number in the protocol and interface names are removed and the + * interface version number is reset. + * + * @section page_ifaces_text_input_unstable_v3 Interfaces + * - @subpage page_iface_zwp_text_input_v3 - text input + * - @subpage page_iface_zwp_text_input_manager_v3 - text input manager + * @section page_copyright_text_input_unstable_v3 Copyright + *
+ *
+ * Copyright ยฉ 2012, 2013 Intel Corporation
+ * Copyright ยฉ 2015, 2016 Jan Arne Petersen
+ * Copyright ยฉ 2017, 2018 Red Hat, Inc.
+ * Copyright ยฉ 2018       Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * 
+ */ +struct wl_seat; +struct wl_surface; +struct zwp_text_input_manager_v3; +struct zwp_text_input_v3; + +#ifndef ZWP_TEXT_INPUT_V3_INTERFACE +#define ZWP_TEXT_INPUT_V3_INTERFACE +/** + * @page page_iface_zwp_text_input_v3 zwp_text_input_v3 + * @section page_iface_zwp_text_input_v3_desc Description + * + * The zwp_text_input_v3 interface represents text input and input methods + * associated with a seat. It provides enter/leave events to follow the + * text input focus for a seat. + * + * Requests are used to enable/disable the text-input object and set + * state information like surrounding and selected text or the content type. + * The information about the entered text is sent to the text-input object + * via the preedit_string and commit_string events. + * + * Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + * must not point to middle bytes inside a code point: they must either + * point to the first byte of a code point or to the end of the buffer. + * Lengths must be measured between two valid indices. + * + * Focus moving throughout surfaces will result in the emission of + * zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + * surface must commit zwp_text_input_v3.enable and + * zwp_text_input_v3.disable requests as the keyboard focus moves across + * editable and non-editable elements of the UI. Those two requests are not + * expected to be paired with each other, the compositor must be able to + * handle consecutive series of the same request. + * + * State is sent by the state requests (set_surrounding_text, + * set_content_type and set_cursor_rectangle) and a commit request. After an + * enter event or disable request all state information is invalidated and + * needs to be resent by the client. + * @section page_iface_zwp_text_input_v3_api API + * See @ref iface_zwp_text_input_v3. + */ +/** + * @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface + * + * The zwp_text_input_v3 interface represents text input and input methods + * associated with a seat. It provides enter/leave events to follow the + * text input focus for a seat. + * + * Requests are used to enable/disable the text-input object and set + * state information like surrounding and selected text or the content type. + * The information about the entered text is sent to the text-input object + * via the preedit_string and commit_string events. + * + * Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + * must not point to middle bytes inside a code point: they must either + * point to the first byte of a code point or to the end of the buffer. + * Lengths must be measured between two valid indices. + * + * Focus moving throughout surfaces will result in the emission of + * zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + * surface must commit zwp_text_input_v3.enable and + * zwp_text_input_v3.disable requests as the keyboard focus moves across + * editable and non-editable elements of the UI. Those two requests are not + * expected to be paired with each other, the compositor must be able to + * handle consecutive series of the same request. + * + * State is sent by the state requests (set_surrounding_text, + * set_content_type and set_cursor_rectangle) and a commit request. After an + * enter event or disable request all state information is invalidated and + * needs to be resent by the client. + */ +extern const struct wl_interface zwp_text_input_v3_interface; +#endif +#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE +#define ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE +/** + * @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3 + * @section page_iface_zwp_text_input_manager_v3_desc Description + * + * A factory for text-input objects. This object is a global singleton. + * @section page_iface_zwp_text_input_manager_v3_api API + * See @ref iface_zwp_text_input_manager_v3. + */ +/** + * @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface + * + * A factory for text-input objects. This object is a global singleton. + */ +extern const struct wl_interface zwp_text_input_manager_v3_interface; +#endif + +#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM +#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * text change reason + * + * Reason for the change of surrounding text or cursor posision. + */ +enum zwp_text_input_v3_change_cause { + /** + * input method caused the change + */ + ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0, + /** + * something else than the input method caused the change + */ + ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1, +}; +#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM_IS_VALID +#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM_IS_VALID +/** + * @ingroup iface_zwp_text_input_v3 + * Validate a zwp_text_input_v3 change_cause value. + * + * @return true on success, false on error. + * @ref zwp_text_input_v3_change_cause + */ +static inline bool +zwp_text_input_v3_change_cause_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM_IS_VALID */ + +#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * content hint + * + * Content hint is a bitmask to allow to modify the behavior of the text + * input. + */ +enum zwp_text_input_v3_content_hint { + /** + * no special behavior + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0, + /** + * suggest word completions + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1, + /** + * suggest word corrections + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2, + /** + * switch to uppercase letters at the start of a sentence + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4, + /** + * prefer lowercase letters + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8, + /** + * prefer uppercase letters + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10, + /** + * prefer casing for titles and headings (can be language dependent) + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20, + /** + * characters should be hidden + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40, + /** + * typed text should not be stored + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80, + /** + * just Latin characters should be entered + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100, + /** + * the text input is multiline + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200, + /** + * an on-screen way to fill in the input is already provided by the client + * @since 2 + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_ON_SCREEN_INPUT_PROVIDED = 0x400, + /** + * prefer not offering emoji support + * @since 2 + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_NO_EMOJI = 0x800, + /** + * the text input will display preedit text in place + * @since 2 + */ + ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN = 0x1000, +}; +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ON_SCREEN_INPUT_PROVIDED_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_NO_EMOJI_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN_SINCE_VERSION 2 +#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM_IS_VALID +#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM_IS_VALID +/** + * @ingroup iface_zwp_text_input_v3 + * Validate a zwp_text_input_v3 content_hint value. + * + * @return true on success, false on error. + * @ref zwp_text_input_v3_content_hint + */ +static inline bool +zwp_text_input_v3_content_hint_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN; + if (version >= 1) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE; + if (version >= 2) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_ON_SCREEN_INPUT_PROVIDED; + if (version >= 2) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_NO_EMOJI; + if (version >= 2) + valid |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_PREEDIT_SHOWN; + return (value & ~valid) == 0; +} +#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM_IS_VALID */ + +#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM +#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * content purpose + * + * The content purpose allows to specify the primary purpose of a text + * input. + * + * This allows an input method to show special purpose input panels with + * extra characters or to disallow some characters. + */ +enum zwp_text_input_v3_content_purpose { + /** + * default input, allowing all characters + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0, + /** + * allow only alphabetic characters + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1, + /** + * allow only digits + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2, + /** + * input a number (including decimal separator and sign) + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3, + /** + * input a phone number + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4, + /** + * input an URL + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5, + /** + * input an email address + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6, + /** + * input a name of a person + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7, + /** + * input a password (combine with sensitive_data hint) + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8, + /** + * input is a numeric password (combine with sensitive_data hint) + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9, + /** + * input a date + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10, + /** + * input a time + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11, + /** + * input a date and time + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12, + /** + * input for a terminal + */ + ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13, +}; +#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM_IS_VALID +#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM_IS_VALID +/** + * @ingroup iface_zwp_text_input_v3 + * Validate a zwp_text_input_v3 content_purpose value. + * + * @return true on success, false on error. + * @ref zwp_text_input_v3_content_purpose + */ +static inline bool +zwp_text_input_v3_content_purpose_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME: + return version >= 1; + case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM_IS_VALID */ + +#ifndef ZWP_TEXT_INPUT_V3_ERROR_ENUM +#define ZWP_TEXT_INPUT_V3_ERROR_ENUM +enum zwp_text_input_v3_error { + /** + * an invalid or duplicate action was specified + */ + ZWP_TEXT_INPUT_V3_ERROR_INVALID_ACTION = 0, +}; +#endif /* ZWP_TEXT_INPUT_V3_ERROR_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_ERROR_ENUM_IS_VALID +#define ZWP_TEXT_INPUT_V3_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_zwp_text_input_v3 + * Validate a zwp_text_input_v3 error value. + * + * @return true on success, false on error. + * @ref zwp_text_input_v3_error + */ +static inline bool +zwp_text_input_v3_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_TEXT_INPUT_V3_ERROR_INVALID_ACTION: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_TEXT_INPUT_V3_ERROR_ENUM_IS_VALID */ + +#ifndef ZWP_TEXT_INPUT_V3_ACTION_ENUM +#define ZWP_TEXT_INPUT_V3_ACTION_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * action + * + * A possible action to perform on a text input. + * + * The submit action is intended for input entries that expect some sort of + * activation after user interaction, e.g. the URL entry in a browser. + */ +enum zwp_text_input_v3_action { + /** + * no action + */ + ZWP_TEXT_INPUT_V3_ACTION_NONE = 0, + /** + * the action is submitted + */ + ZWP_TEXT_INPUT_V3_ACTION_SUBMIT = 1, +}; +#endif /* ZWP_TEXT_INPUT_V3_ACTION_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_ACTION_ENUM_IS_VALID +#define ZWP_TEXT_INPUT_V3_ACTION_ENUM_IS_VALID +/** + * @ingroup iface_zwp_text_input_v3 + * Validate a zwp_text_input_v3 action value. + * + * @return true on success, false on error. + * @ref zwp_text_input_v3_action + */ +static inline bool +zwp_text_input_v3_action_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_TEXT_INPUT_V3_ACTION_NONE: + return version >= 1; + case ZWP_TEXT_INPUT_V3_ACTION_SUBMIT: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_TEXT_INPUT_V3_ACTION_ENUM_IS_VALID */ + +#ifndef ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM +#define ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM +/** + * @ingroup iface_zwp_text_input_v3 + * preedit style hint + * + * Style hints for the preedit string. + */ +enum zwp_text_input_v3_preedit_hint { + /** + * simple pre-edit text style, typically underlined + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_WHOLE = 1, + /** + * hint for a selected piece of text, e.g. per-character navigation and composition + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SELECTION = 2, + /** + * predicted text, not typed by the user + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_PREDICTION = 3, + /** + * prefixed text not being currently edited, e.g. prior to a 'selection' section + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_PREFIX = 4, + /** + * suffixed text not being currently edited, e.g. after a 'selection' section + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SUFFIX = 5, + /** + * spelling error + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SPELLING_ERROR = 6, + /** + * wrong composition, e.g. user input that can not be transliterated + */ + ZWP_TEXT_INPUT_V3_PREEDIT_HINT_COMPOSE_ERROR = 7, +}; +#endif /* ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM */ + +#ifndef ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM_IS_VALID +#define ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM_IS_VALID +/** + * @ingroup iface_zwp_text_input_v3 + * Validate a zwp_text_input_v3 preedit_hint value. + * + * @return true on success, false on error. + * @ref zwp_text_input_v3_preedit_hint + */ +static inline bool +zwp_text_input_v3_preedit_hint_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_WHOLE: + return version >= 1; + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SELECTION: + return version >= 1; + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_PREDICTION: + return version >= 1; + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_PREFIX: + return version >= 1; + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SUFFIX: + return version >= 1; + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SPELLING_ERROR: + return version >= 1; + case ZWP_TEXT_INPUT_V3_PREEDIT_HINT_COMPOSE_ERROR: + return version >= 1; + default: + return false; + } +} +#endif /* ZWP_TEXT_INPUT_V3_PREEDIT_HINT_ENUM_IS_VALID */ + +/** + * @ingroup iface_zwp_text_input_v3 + * @struct zwp_text_input_v3_interface + */ +struct zwp_text_input_v3_interface { + /** + * Destroy the wp_text_input + * + * Destroy the wp_text_input object. Also disables all surfaces + * enabled through this wp_text_input object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * Request text input to be enabled + * + * Requests text input on the surface previously obtained from + * the enter event. + * + * This request must be issued every time the focused text input + * changes to a new one, including within the current surface. Use + * zwp_text_input_v3.disable when there is no longer any input + * focus on the current surface. + * + * Clients must not enable more than one text input on the single + * seat and should disable the current text input before enabling + * the new one. Requests to enable a text input when another text + * input is enabled on the same seat must be ignored by compositor. + * + * This request resets all state associated with previous enable, + * disable, set_surrounding_text, set_text_change_cause, + * set_content_type, and set_cursor_rectangle requests, as well as + * the state associated with preedit_string, commit_string, and + * delete_surrounding_text events. + * + * The set_surrounding_text, set_content_type and + * set_cursor_rectangle requests must follow if the text input + * supports the necessary functionality. + * + * State set with this request is double-buffered. It will get + * applied on the next zwp_text_input_v3.commit request, and stay + * valid until the next committed enable or disable request. + * + * The changes must be applied by the compositor after issuing a + * zwp_text_input_v3.commit request. + */ + void (*enable)(struct wl_client *client, + struct wl_resource *resource); + /** + * Disable text input on a surface + * + * Explicitly disable text input on the current surface + * (typically when there is no focus on any text entry inside the + * surface). + * + * State set with this request is double-buffered. It will get + * applied on the next zwp_text_input_v3.commit request. + */ + void (*disable)(struct wl_client *client, + struct wl_resource *resource); + /** + * sets the surrounding text + * + * Sets the surrounding plain text around the input, excluding + * the preedit text. + * + * The client should notify the compositor of any changes in any of + * the values carried with this request, including changes caused + * by handling incoming text-input events as well as changes caused + * by other mechanisms like keyboard typing. + * + * If the client is unaware of the text around the cursor, it + * should not issue this request, to signify lack of support to the + * compositor. + * + * Text is UTF-8 encoded, and should include the cursor position, + * the complete selection and additional characters before and + * after them. There is a maximum length of wayland messages, so + * text can not be longer than 4000 bytes. + * + * Cursor is the byte offset of the cursor within text buffer. + * + * Anchor is the byte offset of the selection anchor within text + * buffer. If there is no selected text, anchor is the same as + * cursor. + * + * If any preedit text is present, it is replaced with a cursor for + * the purpose of this event. + * + * Values set with this request are double-buffered. They will get + * applied on the next zwp_text_input_v3.commit request, and stay + * valid until the next committed enable or disable request. + * + * The initial state for affected fields is empty, meaning that the + * text input does not support sending surrounding text. If the + * empty values get applied, subsequent attempts to change them may + * have no effect. + */ + void (*set_surrounding_text)(struct wl_client *client, + struct wl_resource *resource, + const char *text, + int32_t cursor, + int32_t anchor); + /** + * indicates the cause of surrounding text change + * + * Tells the compositor why the text surrounding the cursor + * changed. + * + * Whenever the client detects an external change in text, cursor, + * or anchor posision, it must issue this request to the + * compositor. This request is intended to give the input method a + * chance to update the preedit text in an appropriate way, e.g. by + * removing it when the user starts typing with a keyboard. + * + * cause describes the source of the change. + * + * The value set with this request is double-buffered. It must be + * applied and reset to initial at the next + * zwp_text_input_v3.commit request. + * + * The initial value of cause is input_method. + */ + void (*set_text_change_cause)(struct wl_client *client, + struct wl_resource *resource, + uint32_t cause); + /** + * set content purpose and hint + * + * Sets the content purpose and content hint. While the purpose + * is the basic purpose of an input field, the hint flags allow to + * modify some of the behavior. + * + * Values set with this request are double-buffered. They will get + * applied on the next zwp_text_input_v3.commit request. Subsequent + * attempts to update them may have no effect. The values remain + * valid until the next committed enable or disable request. + * + * The initial value for hint is none, and the initial value for + * purpose is normal. + */ + void (*set_content_type)(struct wl_client *client, + struct wl_resource *resource, + uint32_t hint, + uint32_t purpose); + /** + * set cursor position + * + * Marks an area around the cursor as a x, y, width, height + * rectangle in surface local coordinates. + * + * Allows the compositor to put a window with word suggestions near + * the cursor, without obstructing the text being input. + * + * If the client is unaware of the position of edited text, it + * should not issue this request, to signify lack of support to the + * compositor. + * + * Values set with this request are double-buffered. They will get + * applied on the next zwp_text_input_v3.commit request, and stay + * valid until the next committed enable or disable request. + * + * The initial values describing a cursor rectangle are empty. That + * means the text input does not support describing the cursor + * area. If the empty values get applied, subsequent attempts to + * change them may have no effect. + * + * As of version 2, the zwp_text_input_v3.commit request does not + * apply values sent with this request. Instead, it stores them in + * a separate "committed" area. The committed values, if still + * valid, get applied on the next wl_surface.commit request on the + * surface with text-input focus. Both committed and applied values + * get invalidated on: + * + * - the next committed enable or disable request, or - a change of + * the focused surface of the text-input (leave or enter events). + * + * This double stage application allows the compositor to position + * the input method popup in the same frame as the contents of the + * text on the surface are updated. + */ + void (*set_cursor_rectangle)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * commit state + * + * Atomically applies state changes recently sent to the + * compositor. + * + * The commit request establishes and updates the state of the + * client, and must be issued after any changes to apply them. + * + * Text input state (enabled status, content purpose, content hint, + * surrounding text and change cause, cursor rectangle) is + * conceptually double-buffered within the context of a text input, + * i.e. between a committed enable request and the following + * committed enable or disable request. + * + * Protocol requests modify the pending state, as opposed to the + * current state in use by the input method. A commit request + * atomically applies all pending state, replacing the current + * state. After commit, the new pending state is as documented for + * each related request. + * + * Requests are applied in the order of arrival. + * + * Neither current nor pending state are modified unless noted + * otherwise. + * + * The compositor must count the number of commit requests coming + * from each zwp_text_input_v3 object and use the count as the + * serial in done events. + */ + void (*commit)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the available actions + * + * Set the actions available for this text input. + * + * Values set with this request are double-buffered. They will get + * applied on the next zwp_text_input_v3.commit request. + * + * If the available_actions array contains the none action, or + * contains the same action multiple times, the compositor must + * raise the invalid_action protocol error. + * + * Initially, no actions are available. + * @param available_actions available actions + * @since 2 + */ + void (*set_available_actions)(struct wl_client *client, + struct wl_resource *resource, + struct wl_array *available_actions); + /** + * show input panel + * + * Requests an input panel to be shown (e.g. a on-screen + * keyboard). + * + * This request only hints the desired interaction pattern from the + * client side, and its effect may be ignored by compositors given + * other environmental factors. Repeated calls will be ignored. + * @since 2 + */ + void (*show_input_panel)(struct wl_client *client, + struct wl_resource *resource); + /** + * hide input panel + * + * Requests an input panel to be hidden. + * + * This request only hints the desired interaction pattern from the + * client side, and its effect may be ignored by compositors given + * other environmental factors. Repeated calls will be ignored. + * @since 2 + */ + void (*hide_input_panel)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define ZWP_TEXT_INPUT_V3_ENTER 0 +#define ZWP_TEXT_INPUT_V3_LEAVE 1 +#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING 2 +#define ZWP_TEXT_INPUT_V3_COMMIT_STRING 3 +#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT 4 +#define ZWP_TEXT_INPUT_V3_DONE 5 +#define ZWP_TEXT_INPUT_V3_ACTION 6 +#define ZWP_TEXT_INPUT_V3_LANGUAGE 7 +#define ZWP_TEXT_INPUT_V3_PREEDIT_HINT 8 + +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_ACTION_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_LANGUAGE_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_PREEDIT_HINT_SINCE_VERSION 2 + +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SET_AVAILABLE_ACTIONS_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_SHOW_INPUT_PANEL_SINCE_VERSION 2 +/** + * @ingroup iface_zwp_text_input_v3 + */ +#define ZWP_TEXT_INPUT_V3_HIDE_INPUT_PANEL_SINCE_VERSION 2 + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an enter event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_text_input_v3_send_enter(struct wl_resource *resource_, struct wl_resource *surface) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_ENTER, surface); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an leave event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_text_input_v3_send_leave(struct wl_resource *resource_, struct wl_resource *surface) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_LEAVE, surface); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an preedit_string event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_text_input_v3_send_preedit_string(struct wl_resource *resource_, const char *text, int32_t cursor_begin, int32_t cursor_end) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_PREEDIT_STRING, text, cursor_begin, cursor_end); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an commit_string event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_text_input_v3_send_commit_string(struct wl_resource *resource_, const char *text) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_COMMIT_STRING, text); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an delete_surrounding_text event to the client owning the resource. + * @param resource_ The client's resource + * @param before_length length of text before current cursor position + * @param after_length length of text after current cursor position + */ +static inline void +zwp_text_input_v3_send_delete_surrounding_text(struct wl_resource *resource_, uint32_t before_length, uint32_t after_length) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT, before_length, after_length); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwp_text_input_v3_send_done(struct wl_resource *resource_, uint32_t serial) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_DONE, serial); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an action event to the client owning the resource. + * @param resource_ The client's resource + * @param action action performed + * @param serial serial number of the action event + */ +static inline void +zwp_text_input_v3_send_action(struct wl_resource *resource_, uint32_t action, uint32_t serial) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_ACTION, action, serial); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an language event to the client owning the resource. + * @param resource_ The client's resource + * @param language new language set by IME + */ +static inline void +zwp_text_input_v3_send_language(struct wl_resource *resource_, const char *language) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_LANGUAGE, language); +} + +/** + * @ingroup iface_zwp_text_input_v3 + * Sends an preedit_hint event to the client owning the resource. + * @param resource_ The client's resource + * @param start starting point of the affected substring + * @param end end point of the affected substring + * @param hint hint to apply + */ +static inline void +zwp_text_input_v3_send_preedit_hint(struct wl_resource *resource_, uint32_t start, uint32_t end, uint32_t hint) +{ + wl_resource_post_event(resource_, ZWP_TEXT_INPUT_V3_PREEDIT_HINT, start, end, hint); +} + +/** + * @ingroup iface_zwp_text_input_manager_v3 + * @struct zwp_text_input_manager_v3_interface + */ +struct zwp_text_input_manager_v3_interface { + /** + * Destroy the wp_text_input_manager + * + * Destroy the wp_text_input_manager object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * create a new text input object + * + * Creates a new text-input object for a given seat. + */ + void (*get_text_input)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *seat); +}; + + +/** + * @ingroup iface_zwp_text_input_manager_v3 + */ +#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwp_text_input_manager_v3 + */ +#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/viewporter-protocol.c b/app/src/main/cpp/waylandcomp/generated/viewporter-protocol.c new file mode 100644 index 000000000..d6aaa71e7 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/viewporter-protocol.c @@ -0,0 +1,75 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2013-2016 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface wp_viewport_interface; + +static const struct wl_interface *viewporter_types[] = { + NULL, + NULL, + NULL, + NULL, + &wp_viewport_interface, + &wl_surface_interface, +}; + +static const struct wl_message wp_viewporter_requests[] = { + { "destroy", "", viewporter_types + 0 }, + { "get_viewport", "no", viewporter_types + 4 }, +}; + +WL_PRIVATE const struct wl_interface wp_viewporter_interface = { + "wp_viewporter", 1, + 2, wp_viewporter_requests, + 0, NULL, +}; + +static const struct wl_message wp_viewport_requests[] = { + { "destroy", "", viewporter_types + 0 }, + { "set_source", "ffff", viewporter_types + 0 }, + { "set_destination", "ii", viewporter_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface wp_viewport_interface = { + "wp_viewport", 1, + 3, wp_viewport_requests, + 0, NULL, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/viewporter-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/viewporter-server-protocol.h new file mode 100644 index 000000000..51957565c --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/viewporter-server-protocol.h @@ -0,0 +1,401 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef VIEWPORTER_SERVER_PROTOCOL_H +#define VIEWPORTER_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_viewporter The viewporter protocol + * @section page_ifaces_viewporter Interfaces + * - @subpage page_iface_wp_viewporter - surface cropping and scaling + * - @subpage page_iface_wp_viewport - crop and scale interface to a wl_surface + * @section page_copyright_viewporter Copyright + *
+ *
+ * Copyright ยฉ 2013-2016 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_surface; +struct wp_viewport; +struct wp_viewporter; + +#ifndef WP_VIEWPORTER_INTERFACE +#define WP_VIEWPORTER_INTERFACE +/** + * @page page_iface_wp_viewporter wp_viewporter + * @section page_iface_wp_viewporter_desc Description + * + * The global interface exposing surface cropping and scaling + * capabilities is used to instantiate an interface extension for a + * wl_surface object. This extended interface will then allow + * cropping and scaling the surface contents, effectively + * disconnecting the direct relationship between the buffer and the + * surface size. + * @section page_iface_wp_viewporter_api API + * See @ref iface_wp_viewporter. + */ +/** + * @defgroup iface_wp_viewporter The wp_viewporter interface + * + * The global interface exposing surface cropping and scaling + * capabilities is used to instantiate an interface extension for a + * wl_surface object. This extended interface will then allow + * cropping and scaling the surface contents, effectively + * disconnecting the direct relationship between the buffer and the + * surface size. + */ +extern const struct wl_interface wp_viewporter_interface; +#endif +#ifndef WP_VIEWPORT_INTERFACE +#define WP_VIEWPORT_INTERFACE +/** + * @page page_iface_wp_viewport wp_viewport + * @section page_iface_wp_viewport_desc Description + * + * An additional interface to a wl_surface object, which allows the + * client to specify the cropping and scaling of the surface + * contents. + * + * This interface works with two concepts: the source rectangle (src_x, + * src_y, src_width, src_height), and the destination size (dst_width, + * dst_height). The contents of the source rectangle are scaled to the + * destination size, and content outside the source rectangle is ignored. + * This state is double-buffered, and is applied on the next + * wl_surface.commit. + * + * The two parts of crop and scale state are independent: the source + * rectangle, and the destination size. Initially both are unset, that + * is, no scaling is applied. The whole of the current wl_buffer is + * used as the source, and the surface size is as defined in + * wl_surface.attach. + * + * If the destination size is set, it causes the surface size to become + * dst_width, dst_height. The source (rectangle) is scaled to exactly + * this size. This overrides whatever the attached wl_buffer size is, + * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + * has no content and therefore no size. Otherwise, the size is always + * at least 1x1 in surface local coordinates. + * + * If the source rectangle is set, it defines what area of the wl_buffer is + * taken as the source. If the source rectangle is set and the destination + * size is not set, then src_width and src_height must be integers, and the + * surface size becomes the source rectangle size. This results in cropping + * without scaling. If src_width or src_height are not integers and + * destination size is not set, the bad_size protocol error is raised when + * the surface state is applied. + * + * The coordinate transformations from buffer pixel coordinates up to + * the surface-local coordinates happen in the following order: + * 1. buffer_transform (wl_surface.set_buffer_transform) + * 2. buffer_scale (wl_surface.set_buffer_scale) + * 3. crop and scale (wp_viewport.set*) + * This means, that the source rectangle coordinates of crop and scale + * are given in the coordinates after the buffer transform and scale, + * i.e. in the coordinates that would be the surface-local coordinates + * if the crop and scale was not applied. + * + * If src_x or src_y are negative, the bad_value protocol error is raised. + * Otherwise, if the source rectangle is partially or completely outside of + * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + * when the surface state is applied. A NULL wl_buffer does not raise the + * out_of_buffer error. + * + * If the wl_surface associated with the wp_viewport is destroyed, + * all wp_viewport requests except 'destroy' raise the protocol error + * no_surface. + * + * If the wp_viewport object is destroyed, the crop and scale + * state is removed from the wl_surface. The change will be applied + * on the next wl_surface.commit. + * @section page_iface_wp_viewport_api API + * See @ref iface_wp_viewport. + */ +/** + * @defgroup iface_wp_viewport The wp_viewport interface + * + * An additional interface to a wl_surface object, which allows the + * client to specify the cropping and scaling of the surface + * contents. + * + * This interface works with two concepts: the source rectangle (src_x, + * src_y, src_width, src_height), and the destination size (dst_width, + * dst_height). The contents of the source rectangle are scaled to the + * destination size, and content outside the source rectangle is ignored. + * This state is double-buffered, and is applied on the next + * wl_surface.commit. + * + * The two parts of crop and scale state are independent: the source + * rectangle, and the destination size. Initially both are unset, that + * is, no scaling is applied. The whole of the current wl_buffer is + * used as the source, and the surface size is as defined in + * wl_surface.attach. + * + * If the destination size is set, it causes the surface size to become + * dst_width, dst_height. The source (rectangle) is scaled to exactly + * this size. This overrides whatever the attached wl_buffer size is, + * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + * has no content and therefore no size. Otherwise, the size is always + * at least 1x1 in surface local coordinates. + * + * If the source rectangle is set, it defines what area of the wl_buffer is + * taken as the source. If the source rectangle is set and the destination + * size is not set, then src_width and src_height must be integers, and the + * surface size becomes the source rectangle size. This results in cropping + * without scaling. If src_width or src_height are not integers and + * destination size is not set, the bad_size protocol error is raised when + * the surface state is applied. + * + * The coordinate transformations from buffer pixel coordinates up to + * the surface-local coordinates happen in the following order: + * 1. buffer_transform (wl_surface.set_buffer_transform) + * 2. buffer_scale (wl_surface.set_buffer_scale) + * 3. crop and scale (wp_viewport.set*) + * This means, that the source rectangle coordinates of crop and scale + * are given in the coordinates after the buffer transform and scale, + * i.e. in the coordinates that would be the surface-local coordinates + * if the crop and scale was not applied. + * + * If src_x or src_y are negative, the bad_value protocol error is raised. + * Otherwise, if the source rectangle is partially or completely outside of + * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + * when the surface state is applied. A NULL wl_buffer does not raise the + * out_of_buffer error. + * + * If the wl_surface associated with the wp_viewport is destroyed, + * all wp_viewport requests except 'destroy' raise the protocol error + * no_surface. + * + * If the wp_viewport object is destroyed, the crop and scale + * state is removed from the wl_surface. The change will be applied + * on the next wl_surface.commit. + */ +extern const struct wl_interface wp_viewport_interface; +#endif + +#ifndef WP_VIEWPORTER_ERROR_ENUM +#define WP_VIEWPORTER_ERROR_ENUM +enum wp_viewporter_error { + /** + * the surface already has a viewport object associated + */ + WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS = 0, +}; +#endif /* WP_VIEWPORTER_ERROR_ENUM */ + +#ifndef WP_VIEWPORTER_ERROR_ENUM_IS_VALID +#define WP_VIEWPORTER_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_viewporter + * Validate a wp_viewporter error value. + * + * @return true on success, false on error. + * @ref wp_viewporter_error + */ +static inline bool +wp_viewporter_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS: + return version >= 1; + default: + return false; + } +} +#endif /* WP_VIEWPORTER_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_viewporter + * @struct wp_viewporter_interface + */ +struct wp_viewporter_interface { + /** + * unbind from the cropping and scaling interface + * + * Informs the server that the client will not be using this + * protocol object anymore. This does not affect any other objects, + * wp_viewport objects included. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * extend surface interface for crop and scale + * + * Instantiate an interface extension for the given wl_surface to + * crop and scale its content. If the given wl_surface already has + * a wp_viewport object associated, the viewport_exists protocol + * error is raised. + * @param id the new viewport interface id + * @param surface the surface + */ + void (*get_viewport)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); +}; + + +/** + * @ingroup iface_wp_viewporter + */ +#define WP_VIEWPORTER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_viewporter + */ +#define WP_VIEWPORTER_GET_VIEWPORT_SINCE_VERSION 1 + +#ifndef WP_VIEWPORT_ERROR_ENUM +#define WP_VIEWPORT_ERROR_ENUM +enum wp_viewport_error { + /** + * negative or zero values in width or height + */ + WP_VIEWPORT_ERROR_BAD_VALUE = 0, + /** + * destination size is not integer + */ + WP_VIEWPORT_ERROR_BAD_SIZE = 1, + /** + * source rectangle extends outside of the content area + */ + WP_VIEWPORT_ERROR_OUT_OF_BUFFER = 2, + /** + * the wl_surface was destroyed + */ + WP_VIEWPORT_ERROR_NO_SURFACE = 3, +}; +#endif /* WP_VIEWPORT_ERROR_ENUM */ + +#ifndef WP_VIEWPORT_ERROR_ENUM_IS_VALID +#define WP_VIEWPORT_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wp_viewport + * Validate a wp_viewport error value. + * + * @return true on success, false on error. + * @ref wp_viewport_error + */ +static inline bool +wp_viewport_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WP_VIEWPORT_ERROR_BAD_VALUE: + return version >= 1; + case WP_VIEWPORT_ERROR_BAD_SIZE: + return version >= 1; + case WP_VIEWPORT_ERROR_OUT_OF_BUFFER: + return version >= 1; + case WP_VIEWPORT_ERROR_NO_SURFACE: + return version >= 1; + default: + return false; + } +} +#endif /* WP_VIEWPORT_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wp_viewport + * @struct wp_viewport_interface + */ +struct wp_viewport_interface { + /** + * remove scaling and cropping from the surface + * + * The associated wl_surface's crop and scale state is removed. + * The change is applied on the next wl_surface.commit. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the source rectangle for cropping + * + * Set the source rectangle of the associated wl_surface. See + * wp_viewport for the description, and relation to the wl_buffer + * size. + * + * If all of x, y, width and height are -1.0, the source rectangle + * is unset instead. Any other set of values where width or height + * are zero or negative, or x or y are negative, raise the + * bad_value protocol error. + * + * The crop and scale state is double-buffered state, and will be + * applied on the next wl_surface.commit. + * @param x source rectangle x + * @param y source rectangle y + * @param width source rectangle width + * @param height source rectangle height + */ + void (*set_source)(struct wl_client *client, + struct wl_resource *resource, + wl_fixed_t x, + wl_fixed_t y, + wl_fixed_t width, + wl_fixed_t height); + /** + * set the surface size for scaling + * + * Set the destination size of the associated wl_surface. See + * wp_viewport for the description, and relation to the wl_buffer + * size. + * + * If width is -1 and height is -1, the destination size is unset + * instead. Any other pair of values for width and height that + * contains zero or negative values raises the bad_value protocol + * error. + * + * The crop and scale state is double-buffered state, and will be + * applied on the next wl_surface.commit. + * @param width surface width + * @param height surface height + */ + void (*set_destination)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); +}; + + +/** + * @ingroup iface_wp_viewport + */ +#define WP_VIEWPORT_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wp_viewport + */ +#define WP_VIEWPORT_SET_SOURCE_SINCE_VERSION 1 +/** + * @ingroup iface_wp_viewport + */ +#define WP_VIEWPORT_SET_DESTINATION_SINCE_VERSION 1 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/wlr-data-control-unstable-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/wlr-data-control-unstable-v1-protocol.c new file mode 100644 index 000000000..4bb2945af --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/wlr-data-control-unstable-v1-protocol.c @@ -0,0 +1,123 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2018 Simon Ser + * Copyright ยฉ 2019 Ivan Molodetskikh + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface zwlr_data_control_device_v1_interface; +extern const struct wl_interface zwlr_data_control_offer_v1_interface; +extern const struct wl_interface zwlr_data_control_source_v1_interface; + +static const struct wl_interface *wlr_data_control_unstable_v1_types[] = { + NULL, + NULL, + &zwlr_data_control_source_v1_interface, + &zwlr_data_control_device_v1_interface, + &wl_seat_interface, + &zwlr_data_control_source_v1_interface, + &zwlr_data_control_source_v1_interface, + &zwlr_data_control_offer_v1_interface, + &zwlr_data_control_offer_v1_interface, + &zwlr_data_control_offer_v1_interface, +}; + +static const struct wl_message zwlr_data_control_manager_v1_requests[] = { + { "create_data_source", "n", wlr_data_control_unstable_v1_types + 2 }, + { "get_data_device", "no", wlr_data_control_unstable_v1_types + 3 }, + { "destroy", "", wlr_data_control_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_data_control_manager_v1_interface = { + "zwlr_data_control_manager_v1", 2, + 3, zwlr_data_control_manager_v1_requests, + 0, NULL, +}; + +static const struct wl_message zwlr_data_control_device_v1_requests[] = { + { "set_selection", "?o", wlr_data_control_unstable_v1_types + 5 }, + { "destroy", "", wlr_data_control_unstable_v1_types + 0 }, + { "set_primary_selection", "2?o", wlr_data_control_unstable_v1_types + 6 }, +}; + +static const struct wl_message zwlr_data_control_device_v1_events[] = { + { "data_offer", "n", wlr_data_control_unstable_v1_types + 7 }, + { "selection", "?o", wlr_data_control_unstable_v1_types + 8 }, + { "finished", "", wlr_data_control_unstable_v1_types + 0 }, + { "primary_selection", "2?o", wlr_data_control_unstable_v1_types + 9 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_data_control_device_v1_interface = { + "zwlr_data_control_device_v1", 2, + 3, zwlr_data_control_device_v1_requests, + 4, zwlr_data_control_device_v1_events, +}; + +static const struct wl_message zwlr_data_control_source_v1_requests[] = { + { "offer", "s", wlr_data_control_unstable_v1_types + 0 }, + { "destroy", "", wlr_data_control_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_data_control_source_v1_events[] = { + { "send", "sh", wlr_data_control_unstable_v1_types + 0 }, + { "cancelled", "", wlr_data_control_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_data_control_source_v1_interface = { + "zwlr_data_control_source_v1", 1, + 2, zwlr_data_control_source_v1_requests, + 2, zwlr_data_control_source_v1_events, +}; + +static const struct wl_message zwlr_data_control_offer_v1_requests[] = { + { "receive", "sh", wlr_data_control_unstable_v1_types + 0 }, + { "destroy", "", wlr_data_control_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_data_control_offer_v1_events[] = { + { "offer", "s", wlr_data_control_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_data_control_offer_v1_interface = { + "zwlr_data_control_offer_v1", 1, + 2, zwlr_data_control_offer_v1_requests, + 1, zwlr_data_control_offer_v1_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/wlr-data-control-unstable-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/wlr-data-control-unstable-v1-server-protocol.h new file mode 100644 index 000000000..a48487584 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/wlr-data-control-unstable-v1-server-protocol.h @@ -0,0 +1,541 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef WLR_DATA_CONTROL_UNSTABLE_V1_SERVER_PROTOCOL_H +#define WLR_DATA_CONTROL_UNSTABLE_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_wlr_data_control_unstable_v1 The wlr_data_control_unstable_v1 protocol + * control data devices + * + * @section page_desc_wlr_data_control_unstable_v1 Description + * + * This protocol allows a privileged client to control data devices. In + * particular, the client will be able to manage the current selection and take + * the role of a clipboard manager. + * + * Warning! The protocol described in this file is experimental and + * backward incompatible changes may be made. Backward compatible changes + * may be added together with the corresponding interface version bump. + * Backward incompatible changes are done by bumping the version number in + * the protocol and interface names and resetting the interface version. + * Once the protocol is to be declared stable, the 'z' prefix and the + * version number in the protocol and interface names are removed and the + * interface version number is reset. + * + * Note! This protocol is deprecated and not intended for production use. + * For clipboard management, use the ext-data-control-v1 protocol. + * + * @section page_ifaces_wlr_data_control_unstable_v1 Interfaces + * - @subpage page_iface_zwlr_data_control_manager_v1 - manager to control data devices + * - @subpage page_iface_zwlr_data_control_device_v1 - manage a data device for a seat + * - @subpage page_iface_zwlr_data_control_source_v1 - offer to transfer data + * - @subpage page_iface_zwlr_data_control_offer_v1 - offer to transfer data + * @section page_copyright_wlr_data_control_unstable_v1 Copyright + *
+ *
+ * Copyright ยฉ 2018 Simon Ser
+ * Copyright ยฉ 2019 Ivan Molodetskikh
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * 
+ */ +struct wl_seat; +struct zwlr_data_control_device_v1; +struct zwlr_data_control_manager_v1; +struct zwlr_data_control_offer_v1; +struct zwlr_data_control_source_v1; + +#ifndef ZWLR_DATA_CONTROL_MANAGER_V1_INTERFACE +#define ZWLR_DATA_CONTROL_MANAGER_V1_INTERFACE +/** + * @page page_iface_zwlr_data_control_manager_v1 zwlr_data_control_manager_v1 + * @section page_iface_zwlr_data_control_manager_v1_desc Description + * + * This interface is a manager that allows creating per-seat data device + * controls. + * @section page_iface_zwlr_data_control_manager_v1_api API + * See @ref iface_zwlr_data_control_manager_v1. + */ +/** + * @defgroup iface_zwlr_data_control_manager_v1 The zwlr_data_control_manager_v1 interface + * + * This interface is a manager that allows creating per-seat data device + * controls. + */ +extern const struct wl_interface zwlr_data_control_manager_v1_interface; +#endif +#ifndef ZWLR_DATA_CONTROL_DEVICE_V1_INTERFACE +#define ZWLR_DATA_CONTROL_DEVICE_V1_INTERFACE +/** + * @page page_iface_zwlr_data_control_device_v1 zwlr_data_control_device_v1 + * @section page_iface_zwlr_data_control_device_v1_desc Description + * + * This interface allows a client to manage a seat's selection. + * + * When the seat is destroyed, this object becomes inert. + * @section page_iface_zwlr_data_control_device_v1_api API + * See @ref iface_zwlr_data_control_device_v1. + */ +/** + * @defgroup iface_zwlr_data_control_device_v1 The zwlr_data_control_device_v1 interface + * + * This interface allows a client to manage a seat's selection. + * + * When the seat is destroyed, this object becomes inert. + */ +extern const struct wl_interface zwlr_data_control_device_v1_interface; +#endif +#ifndef ZWLR_DATA_CONTROL_SOURCE_V1_INTERFACE +#define ZWLR_DATA_CONTROL_SOURCE_V1_INTERFACE +/** + * @page page_iface_zwlr_data_control_source_v1 zwlr_data_control_source_v1 + * @section page_iface_zwlr_data_control_source_v1_desc Description + * + * The wlr_data_control_source object is the source side of a + * wlr_data_control_offer. It is created by the source client in a data + * transfer and provides a way to describe the offered data and a way to + * respond to requests to transfer the data. + * @section page_iface_zwlr_data_control_source_v1_api API + * See @ref iface_zwlr_data_control_source_v1. + */ +/** + * @defgroup iface_zwlr_data_control_source_v1 The zwlr_data_control_source_v1 interface + * + * The wlr_data_control_source object is the source side of a + * wlr_data_control_offer. It is created by the source client in a data + * transfer and provides a way to describe the offered data and a way to + * respond to requests to transfer the data. + */ +extern const struct wl_interface zwlr_data_control_source_v1_interface; +#endif +#ifndef ZWLR_DATA_CONTROL_OFFER_V1_INTERFACE +#define ZWLR_DATA_CONTROL_OFFER_V1_INTERFACE +/** + * @page page_iface_zwlr_data_control_offer_v1 zwlr_data_control_offer_v1 + * @section page_iface_zwlr_data_control_offer_v1_desc Description + * + * A wlr_data_control_offer represents a piece of data offered for transfer + * by another client (the source client). The offer describes the different + * MIME types that the data can be converted to and provides the mechanism + * for transferring the data directly from the source client. + * @section page_iface_zwlr_data_control_offer_v1_api API + * See @ref iface_zwlr_data_control_offer_v1. + */ +/** + * @defgroup iface_zwlr_data_control_offer_v1 The zwlr_data_control_offer_v1 interface + * + * A wlr_data_control_offer represents a piece of data offered for transfer + * by another client (the source client). The offer describes the different + * MIME types that the data can be converted to and provides the mechanism + * for transferring the data directly from the source client. + */ +extern const struct wl_interface zwlr_data_control_offer_v1_interface; +#endif + +/** + * @ingroup iface_zwlr_data_control_manager_v1 + * @struct zwlr_data_control_manager_v1_interface + */ +struct zwlr_data_control_manager_v1_interface { + /** + * create a new data source + * + * Create a new data source. + * @param id data source to create + */ + void (*create_data_source)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * get a data device for a seat + * + * Create a data device that can be used to manage a seat's + * selection. + */ + void (*get_data_device)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *seat); + /** + * destroy the manager + * + * All objects created by the manager will still remain valid, + * until their appropriate destroy request has been called. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_zwlr_data_control_manager_v1 + */ +#define ZWLR_DATA_CONTROL_MANAGER_V1_CREATE_DATA_SOURCE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_manager_v1 + */ +#define ZWLR_DATA_CONTROL_MANAGER_V1_GET_DATA_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_manager_v1 + */ +#define ZWLR_DATA_CONTROL_MANAGER_V1_DESTROY_SINCE_VERSION 1 + +#ifndef ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_ENUM +#define ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_ENUM +enum zwlr_data_control_device_v1_error { + /** + * source given to set_selection or set_primary_selection was already used before + */ + ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE = 1, +}; +#endif /* ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_ENUM */ + +#ifndef ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_ENUM_IS_VALID +#define ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_zwlr_data_control_device_v1 + * Validate a zwlr_data_control_device_v1 error value. + * + * @return true on success, false on error. + * @ref zwlr_data_control_device_v1_error + */ +static inline bool +zwlr_data_control_device_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE: + return version >= 1; + default: + return false; + } +} +#endif /* ZWLR_DATA_CONTROL_DEVICE_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_zwlr_data_control_device_v1 + * @struct zwlr_data_control_device_v1_interface + */ +struct zwlr_data_control_device_v1_interface { + /** + * copy data to the selection + * + * This request asks the compositor to set the selection to the + * data from the source on behalf of the client. + * + * The given source may not be used in any further set_selection or + * set_primary_selection requests. Attempting to use a previously + * used source is a protocol error. + * + * To unset the selection, set the source to NULL. + */ + void (*set_selection)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *source); + /** + * destroy this data device + * + * Destroys the data device object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * copy data to the primary selection + * + * This request asks the compositor to set the primary selection + * to the data from the source on behalf of the client. + * + * The given source may not be used in any further set_selection or + * set_primary_selection requests. Attempting to use a previously + * used source is a protocol error. + * + * To unset the primary selection, set the source to NULL. + * + * The compositor will ignore this request if it does not support + * primary selection. + * @since 2 + */ + void (*set_primary_selection)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *source); +}; + +#define ZWLR_DATA_CONTROL_DEVICE_V1_DATA_OFFER 0 +#define ZWLR_DATA_CONTROL_DEVICE_V1_SELECTION 1 +#define ZWLR_DATA_CONTROL_DEVICE_V1_FINISHED 2 +#define ZWLR_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION 3 + +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_DATA_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_SELECTION_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_FINISHED_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION_SINCE_VERSION 2 + +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_SET_SELECTION_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_device_v1 + */ +#define ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION 2 + +/** + * @ingroup iface_zwlr_data_control_device_v1 + * Sends an data_offer event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_data_control_device_v1_send_data_offer(struct wl_resource *resource_, struct wl_resource *id) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_DEVICE_V1_DATA_OFFER, id); +} + +/** + * @ingroup iface_zwlr_data_control_device_v1 + * Sends an selection event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_data_control_device_v1_send_selection(struct wl_resource *resource_, struct wl_resource *id) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_DEVICE_V1_SELECTION, id); +} + +/** + * @ingroup iface_zwlr_data_control_device_v1 + * Sends an finished event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_data_control_device_v1_send_finished(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_DEVICE_V1_FINISHED); +} + +/** + * @ingroup iface_zwlr_data_control_device_v1 + * Sends an primary_selection event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_data_control_device_v1_send_primary_selection(struct wl_resource *resource_, struct wl_resource *id) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION, id); +} + +#ifndef ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_ENUM +#define ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_ENUM +enum zwlr_data_control_source_v1_error { + /** + * offer sent after wlr_data_control_device.set_selection + */ + ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_INVALID_OFFER = 1, +}; +#endif /* ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_ENUM */ + +#ifndef ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_ENUM_IS_VALID +#define ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_zwlr_data_control_source_v1 + * Validate a zwlr_data_control_source_v1 error value. + * + * @return true on success, false on error. + * @ref zwlr_data_control_source_v1_error + */ +static inline bool +zwlr_data_control_source_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_INVALID_OFFER: + return version >= 1; + default: + return false; + } +} +#endif /* ZWLR_DATA_CONTROL_SOURCE_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_zwlr_data_control_source_v1 + * @struct zwlr_data_control_source_v1_interface + */ +struct zwlr_data_control_source_v1_interface { + /** + * add an offered MIME type + * + * This request adds a MIME type to the set of MIME types + * advertised to targets. Can be called several times to offer + * multiple types. + * + * Calling this after wlr_data_control_device.set_selection is a + * protocol error. + * @param mime_type MIME type offered by the data source + */ + void (*offer)(struct wl_client *client, + struct wl_resource *resource, + const char *mime_type); + /** + * destroy this source + * + * Destroys the data source object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define ZWLR_DATA_CONTROL_SOURCE_V1_SEND 0 +#define ZWLR_DATA_CONTROL_SOURCE_V1_CANCELLED 1 + +/** + * @ingroup iface_zwlr_data_control_source_v1 + */ +#define ZWLR_DATA_CONTROL_SOURCE_V1_SEND_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_source_v1 + */ +#define ZWLR_DATA_CONTROL_SOURCE_V1_CANCELLED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_data_control_source_v1 + */ +#define ZWLR_DATA_CONTROL_SOURCE_V1_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_source_v1 + */ +#define ZWLR_DATA_CONTROL_SOURCE_V1_DESTROY_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_data_control_source_v1 + * Sends an send event to the client owning the resource. + * @param resource_ The client's resource + * @param mime_type MIME type for the data + * @param fd file descriptor for the data + */ +static inline void +zwlr_data_control_source_v1_send_send(struct wl_resource *resource_, const char *mime_type, int32_t fd) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_SOURCE_V1_SEND, mime_type, fd); +} + +/** + * @ingroup iface_zwlr_data_control_source_v1 + * Sends an cancelled event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_data_control_source_v1_send_cancelled(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_SOURCE_V1_CANCELLED); +} + +/** + * @ingroup iface_zwlr_data_control_offer_v1 + * @struct zwlr_data_control_offer_v1_interface + */ +struct zwlr_data_control_offer_v1_interface { + /** + * request that the data is transferred + * + * To transfer the offered data, the client issues this request + * and indicates the MIME type it wants to receive. The transfer + * happens through the passed file descriptor (typically created + * with the pipe system call). The source client writes the data in + * the MIME type representation requested and then closes the file + * descriptor. + * + * The receiving client reads from the read end of the pipe until + * EOF and then closes its end, at which point the transfer is + * complete. + * + * This request may happen multiple times for different MIME types. + * @param mime_type MIME type desired by receiver + * @param fd file descriptor for data transfer + */ + void (*receive)(struct wl_client *client, + struct wl_resource *resource, + const char *mime_type, + int32_t fd); + /** + * destroy this offer + * + * Destroys the data offer object. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define ZWLR_DATA_CONTROL_OFFER_V1_OFFER 0 + +/** + * @ingroup iface_zwlr_data_control_offer_v1 + */ +#define ZWLR_DATA_CONTROL_OFFER_V1_OFFER_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_data_control_offer_v1 + */ +#define ZWLR_DATA_CONTROL_OFFER_V1_RECEIVE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_data_control_offer_v1 + */ +#define ZWLR_DATA_CONTROL_OFFER_V1_DESTROY_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_data_control_offer_v1 + * Sends an offer event to the client owning the resource. + * @param resource_ The client's resource + * @param mime_type offered MIME type + */ +static inline void +zwlr_data_control_offer_v1_send_offer(struct wl_resource *resource_, const char *mime_type) +{ + wl_resource_post_event(resource_, ZWLR_DATA_CONTROL_OFFER_V1_OFFER, mime_type); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/xdg-shell-client-protocol.h b/app/src/main/cpp/waylandcomp/generated/xdg-shell-client-protocol.h new file mode 100644 index 000000000..5280ba38b --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/xdg-shell-client-protocol.h @@ -0,0 +1,2280 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef XDG_SHELL_CLIENT_PROTOCOL_H +#define XDG_SHELL_CLIENT_PROTOCOL_H + +#include +#include +#include "wayland-client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page page_xdg_shell The xdg_shell protocol + * @section page_ifaces_xdg_shell Interfaces + * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces + * - @subpage page_iface_xdg_positioner - child surface positioner + * - @subpage page_iface_xdg_surface - desktop user interface surface base interface + * - @subpage page_iface_xdg_toplevel - toplevel surface + * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus + * @section page_copyright_xdg_shell Copyright + *
+ *
+ * Copyright ยฉ 2008-2013 Kristian Hรธgsberg
+ * Copyright ยฉ 2013      Rafael Antognolli
+ * Copyright ยฉ 2013      Jasper St. Pierre
+ * Copyright ยฉ 2010-2013 Intel Corporation
+ * Copyright ยฉ 2015-2017 Samsung Electronics Co., Ltd
+ * Copyright ยฉ 2015-2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_seat; +struct wl_surface; +struct xdg_popup; +struct xdg_positioner; +struct xdg_surface; +struct xdg_toplevel; +struct xdg_wm_base; + +#ifndef XDG_WM_BASE_INTERFACE +#define XDG_WM_BASE_INTERFACE +/** + * @page page_iface_xdg_wm_base xdg_wm_base + * @section page_iface_xdg_wm_base_desc Description + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + * @section page_iface_xdg_wm_base_api API + * See @ref iface_xdg_wm_base. + */ +/** + * @defgroup iface_xdg_wm_base The xdg_wm_base interface + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + */ +extern const struct wl_interface xdg_wm_base_interface; +#endif +#ifndef XDG_POSITIONER_INTERFACE +#define XDG_POSITIONER_INTERFACE +/** + * @page page_iface_xdg_positioner xdg_positioner + * @section page_iface_xdg_positioner_desc Description + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an invalid_positioner error. + * @section page_iface_xdg_positioner_api API + * See @ref iface_xdg_positioner. + */ +/** + * @defgroup iface_xdg_positioner The xdg_positioner interface + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an invalid_positioner error. + */ +extern const struct wl_interface xdg_positioner_interface; +#endif +#ifndef XDG_SURFACE_INTERFACE +#define XDG_SURFACE_INTERFACE +/** + * @page page_iface_xdg_surface xdg_surface + * @section page_iface_xdg_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed, i.e. the client must perform the initial commit + * again before attaching a buffer. + * @section page_iface_xdg_surface_api API + * See @ref iface_xdg_surface. + */ +/** + * @defgroup iface_xdg_surface The xdg_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed, i.e. the client must perform the initial commit + * again before attaching a buffer. + */ +extern const struct wl_interface xdg_surface_interface; +#endif +#ifndef XDG_TOPLEVEL_INTERFACE +#define XDG_TOPLEVEL_INTERFACE +/** + * @page page_iface_xdg_toplevel xdg_toplevel + * @section page_iface_xdg_toplevel_desc Description + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by performing a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + * @section page_iface_xdg_toplevel_api API + * See @ref iface_xdg_toplevel. + */ +/** + * @defgroup iface_xdg_toplevel The xdg_toplevel interface + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by performing a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + */ +extern const struct wl_interface xdg_toplevel_interface; +#endif +#ifndef XDG_POPUP_INTERFACE +#define XDG_POPUP_INTERFACE +/** + * @page page_iface_xdg_popup xdg_popup + * @section page_iface_xdg_popup_desc Description + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + * @section page_iface_xdg_popup_api API + * See @ref iface_xdg_popup. + */ +/** + * @defgroup iface_xdg_popup The xdg_popup interface + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + */ +extern const struct wl_interface xdg_popup_interface; +#endif + +#ifndef XDG_WM_BASE_ERROR_ENUM +#define XDG_WM_BASE_ERROR_ENUM +enum xdg_wm_base_error { + /** + * given wl_surface has another role + */ + XDG_WM_BASE_ERROR_ROLE = 0, + /** + * xdg_wm_base was destroyed before children + */ + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, + /** + * the client tried to map or destroy a non-topmost popup + */ + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, + /** + * the client specified an invalid popup parent surface + */ + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, + /** + * the client provided an invalid surface state + */ + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, + /** + * the client provided an invalid positioner + */ + XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, + /** + * the client didnโ€™t respond to a ping event in time + */ + XDG_WM_BASE_ERROR_UNRESPONSIVE = 6, +}; +#endif /* XDG_WM_BASE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_wm_base + * @struct xdg_wm_base_listener + */ +struct xdg_wm_base_listener { + /** + * check if the client is alive + * + * The ping event asks the client if it's still alive. Pass the + * serial specified in the event back to the compositor by sending + * a "pong" request back with the specified serial. See + * xdg_wm_base.pong. + * + * Compositors can use this to determine if the client is still + * alive. It's unspecified what will happen if the client doesn't + * respond to the ping request, or in what timeframe. Clients + * should try to respond in a reasonable amount of time. The + * โ€œunresponsiveโ€ error is provided for compositors that wish + * to disconnect unresponsive clients. + * + * A compositor is free to ping in any way it wants, but a client + * must always respond to any xdg_wm_base object it created. + * @param serial pass this to the pong request + */ + void (*ping)(void *data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial); +}; + +/** + * @ingroup iface_xdg_wm_base + */ +static inline int +xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, + const struct xdg_wm_base_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, + (void (**)(void)) listener, data); +} + +#define XDG_WM_BASE_DESTROY 0 +#define XDG_WM_BASE_CREATE_POSITIONER 1 +#define XDG_WM_BASE_GET_XDG_SURFACE 2 +#define XDG_WM_BASE_PONG 3 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PING_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PONG_SINCE_VERSION 1 + +/** @ingroup iface_xdg_wm_base */ +static inline void +xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); +} + +/** @ingroup iface_xdg_wm_base */ +static inline void * +xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); +} + +static inline uint32_t +xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); +} + +/** + * @ingroup iface_xdg_wm_base + * + * Destroy this xdg_wm_base object. + * + * Destroying a bound xdg_wm_base object while there are surfaces + * still alive created by this xdg_wm_base object instance is illegal + * and will result in a defunct_surfaces error. + */ +static inline void +xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_wm_base + * + * Create a positioner object. A positioner object is used to position + * surfaces relative to some parent surface. See the interface description + * and xdg_surface.get_popup for details. + */ +static inline struct xdg_positioner * +xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL); + + return (struct xdg_positioner *) id; +} + +/** + * @ingroup iface_xdg_wm_base + * + * This creates an xdg_surface for the given surface. While xdg_surface + * itself is not a role, the corresponding surface may only be assigned + * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + * illegal to create an xdg_surface for a wl_surface which already has an + * assigned role and this will result in a role error. + * + * This creates an xdg_surface for the given surface. An xdg_surface is + * used as basis to define a role to a given surface, such as xdg_toplevel + * or xdg_popup. It also manages functionality shared between xdg_surface + * based surface roles. + * + * See the documentation of xdg_surface for more details about what an + * xdg_surface is and how it is used. + */ +static inline struct xdg_surface * +xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL, surface); + + return (struct xdg_surface *) id; +} + +/** + * @ingroup iface_xdg_wm_base + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. See xdg_wm_base.ping + * and xdg_wm_base.error.unresponsive. + */ +static inline void +xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, + XDG_WM_BASE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, serial); +} + +#ifndef XDG_POSITIONER_ERROR_ENUM +#define XDG_POSITIONER_ERROR_ENUM +enum xdg_positioner_error { + /** + * invalid input provided + */ + XDG_POSITIONER_ERROR_INVALID_INPUT = 0, +}; +#endif /* XDG_POSITIONER_ERROR_ENUM */ + +#ifndef XDG_POSITIONER_ANCHOR_ENUM +#define XDG_POSITIONER_ANCHOR_ENUM +enum xdg_positioner_anchor { + XDG_POSITIONER_ANCHOR_NONE = 0, + XDG_POSITIONER_ANCHOR_TOP = 1, + XDG_POSITIONER_ANCHOR_BOTTOM = 2, + XDG_POSITIONER_ANCHOR_LEFT = 3, + XDG_POSITIONER_ANCHOR_RIGHT = 4, + XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, + XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, + XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_ANCHOR_ENUM */ + +#ifndef XDG_POSITIONER_GRAVITY_ENUM +#define XDG_POSITIONER_GRAVITY_ENUM +enum xdg_positioner_gravity { + XDG_POSITIONER_GRAVITY_NONE = 0, + XDG_POSITIONER_GRAVITY_TOP = 1, + XDG_POSITIONER_GRAVITY_BOTTOM = 2, + XDG_POSITIONER_GRAVITY_LEFT = 3, + XDG_POSITIONER_GRAVITY_RIGHT = 4, + XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, + XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, + XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_GRAVITY_ENUM */ + +#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +/** + * @ingroup iface_xdg_positioner + * constraint adjustments + * + * The constraint adjustment value define ways the compositor will adjust + * the position of the surface, if the unadjusted position would result + * in the surface being partly constrained. + * + * Whether a surface is considered 'constrained' is left to the compositor + * to determine. For example, the surface may be partly outside the + * compositor's defined 'work area', thus necessitating the child surface's + * position be adjusted until it is entirely inside the work area. + * + * The adjustments can be combined, according to a defined precedence: 1) + * Flip, 2) Slide, 3) Resize. + */ +enum xdg_positioner_constraint_adjustment { + /** + * don't move the child surface when constrained + * + * Don't alter the surface position even if it is constrained on + * some axis, for example partially outside the edge of an output. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, + /** + * move along the x axis until unconstrained + * + * Slide the surface along the x axis until it is no longer + * constrained. + * + * First try to slide towards the direction of the gravity on the x + * axis until either the edge in the opposite direction of the + * gravity is unconstrained or the edge in the direction of the + * gravity is constrained. + * + * Then try to slide towards the opposite direction of the gravity + * on the x axis until either the edge in the direction of the + * gravity is unconstrained or the edge in the opposite direction + * of the gravity is constrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, + /** + * move along the y axis until unconstrained + * + * Slide the surface along the y axis until it is no longer + * constrained. + * + * First try to slide towards the direction of the gravity on the y + * axis until either the edge in the opposite direction of the + * gravity is unconstrained or the edge in the direction of the + * gravity is constrained. + * + * Then try to slide towards the opposite direction of the gravity + * on the y axis until either the edge in the direction of the + * gravity is unconstrained or the edge in the opposite direction + * of the gravity is constrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, + /** + * invert the anchor and gravity on the x axis + * + * Invert the anchor and gravity on the x axis if the surface is + * constrained on the x axis. For example, if the left edge of the + * surface is constrained, the gravity is 'left' and the anchor is + * 'left', change the gravity to 'right' and the anchor to 'right'. + * + * If the adjusted position also ends up being constrained, the + * resulting position of the flip_x adjustment will be the one + * before the adjustment. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, + /** + * invert the anchor and gravity on the y axis + * + * Invert the anchor and gravity on the y axis if the surface is + * constrained on the y axis. For example, if the bottom edge of + * the surface is constrained, the gravity is 'bottom' and the + * anchor is 'bottom', change the gravity to 'top' and the anchor + * to 'top'. + * + * The adjusted position is calculated given the original anchor + * rectangle and offset, but with the new flipped anchor and + * gravity values. + * + * If the adjusted position also ends up being constrained, the + * resulting position of the flip_y adjustment will be the one + * before the adjustment. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, + /** + * horizontally resize the surface + * + * Resize the surface horizontally so that it is completely + * unconstrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, + /** + * vertically resize the surface + * + * Resize the surface vertically so that it is completely + * unconstrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, +}; +#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ + +#define XDG_POSITIONER_DESTROY 0 +#define XDG_POSITIONER_SET_SIZE 1 +#define XDG_POSITIONER_SET_ANCHOR_RECT 2 +#define XDG_POSITIONER_SET_ANCHOR 3 +#define XDG_POSITIONER_SET_GRAVITY 4 +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 +#define XDG_POSITIONER_SET_OFFSET 6 +#define XDG_POSITIONER_SET_REACTIVE 7 +#define XDG_POSITIONER_SET_PARENT_SIZE 8 +#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9 + + +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 + +/** @ingroup iface_xdg_positioner */ +static inline void +xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); +} + +/** @ingroup iface_xdg_positioner */ +static inline void * +xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); +} + +static inline uint32_t +xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); +} + +/** + * @ingroup iface_xdg_positioner + * + * Notify the compositor that the xdg_positioner will no longer be used. + */ +static inline void +xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the size of the surface that is to be positioned with the positioner + * object. The size is in surface-local coordinates and corresponds to the + * window geometry. See xdg_surface.set_window_geometry. + * + * If a zero or negative size is set the invalid_input error is raised. + */ +static inline void +xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, width, height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify the anchor rectangle within the parent surface that the child + * surface will be placed relative to. The rectangle is relative to the + * window geometry as defined by xdg_surface.set_window_geometry of the + * parent surface. + * + * When the xdg_positioner object is used to position a child surface, the + * anchor rectangle may not extend outside the window geometry of the + * positioned child's parent surface. + * + * If a negative size is set the invalid_input error is raised. + */ +static inline void +xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_ANCHOR_RECT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y, width, height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Defines the anchor point for the anchor rectangle. The specified anchor + * is used derive an anchor point that the child surface will be + * positioned relative to. If a corner anchor is set (e.g. 'top_left' or + * 'bottom_right'), the anchor point will be at the specified corner; + * otherwise, the derived anchor point will be centered on the specified + * edge, or in the center of the anchor rectangle if no edge is specified. + */ +static inline void +xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_ANCHOR, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, anchor); +} + +/** + * @ingroup iface_xdg_positioner + * + * Defines in what direction a surface should be positioned, relative to + * the anchor point of the parent surface. If a corner gravity is + * specified (e.g. 'bottom_right' or 'top_left'), then the child surface + * will be placed towards the specified gravity; otherwise, the child + * surface will be centered over the anchor point on any axis that had no + * gravity specified. If the gravity is not in the โ€˜gravityโ€™ enum, an + * invalid_input error is raised. + */ +static inline void +xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_GRAVITY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, gravity); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify how the window should be positioned if the originally intended + * position caused the surface to be constrained, meaning at least + * partially outside positioning boundaries set by the compositor. The + * adjustment is set by constructing a bitmask describing the adjustment to + * be made when the surface is constrained on that axis. + * + * If no bit for one axis is set, the compositor will assume that the child + * surface should not change its position on that axis when constrained. + * + * If more than one bit for one axis is set, the order of how adjustments + * are applied is specified in the corresponding adjustment descriptions. + * + * The default adjustment is none. + */ +static inline void +xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, constraint_adjustment); +} + +/** + * @ingroup iface_xdg_positioner + * + * Specify the surface position offset relative to the position of the + * anchor on the anchor rectangle and the anchor on the surface. For + * example if the anchor of the anchor rectangle is at (x, y), the surface + * has the gravity bottom|right, and the offset is (ox, oy), the calculated + * surface position will be (x + ox, y + oy). The offset position of the + * surface is the one used for constraint testing. See + * set_constraint_adjustment. + * + * An example use case is placing a popup menu on top of a user interface + * element, while aligning the user interface element of the parent surface + * with some user interface element placed somewhere in the popup surface. + */ +static inline void +xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y); +} + +/** + * @ingroup iface_xdg_positioner + * + * When set reactive, the surface is reconstrained if the conditions used + * for constraining changed, e.g. the parent window moved. + * + * If the conditions changed and the popup was reconstrained, an + * xdg_popup.configure event is sent with updated geometry, followed by an + * xdg_surface.configure event. + */ +static inline void +xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_REACTIVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the parent window geometry the compositor should use when + * positioning the popup. The compositor may use this information to + * determine the future state the popup should be constrained using. If + * this doesn't match the dimension of the parent the popup is eventually + * positioned against, the behavior is undefined. + * + * The arguments are given in the surface-local coordinate space. + */ +static inline void +xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_PARENT_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, parent_width, parent_height); +} + +/** + * @ingroup iface_xdg_positioner + * + * Set the serial of an xdg_surface.configure event this positioner will be + * used in response to. The compositor may use this information together + * with set_parent_size to determine what future state the popup should be + * constrained using. + */ +static inline void +xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, + XDG_POSITIONER_SET_PARENT_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, serial); +} + +#ifndef XDG_SURFACE_ERROR_ENUM +#define XDG_SURFACE_ERROR_ENUM +enum xdg_surface_error { + /** + * Surface was not fully constructed + */ + XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, + /** + * Surface was already constructed + */ + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, + /** + * Attaching a buffer to an unconfigured surface + */ + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, + /** + * Invalid serial number when acking a configure event + */ + XDG_SURFACE_ERROR_INVALID_SERIAL = 4, + /** + * Width or height was zero or negative + */ + XDG_SURFACE_ERROR_INVALID_SIZE = 5, + /** + * Surface was destroyed before its role object + */ + XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 6, +}; +#endif /* XDG_SURFACE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_surface + * @struct xdg_surface_listener + */ +struct xdg_surface_listener { + /** + * suggest a surface change + * + * The configure event marks the end of a configure sequence. A + * configure sequence is a set of one or more events configuring + * the state of the xdg_surface, including the final + * xdg_surface.configure event. + * + * Where applicable, xdg_surface surface roles will during a + * configure sequence extend this event as a latched state sent as + * events before the xdg_surface.configure event. Such events + * should be considered to make up a set of atomically applied + * configuration states, where the xdg_surface.configure commits + * the accumulated state. + * + * Clients should arrange their surface for the new states, and + * then send an ack_configure request with the serial sent in this + * configure event at some point before committing the new surface. + * + * If the client receives multiple configure events before it can + * respond to one, it is free to discard all but the last event it + * received. + * @param serial serial of the configure event + */ + void (*configure)(void *data, + struct xdg_surface *xdg_surface, + uint32_t serial); +}; + +/** + * @ingroup iface_xdg_surface + */ +static inline int +xdg_surface_add_listener(struct xdg_surface *xdg_surface, + const struct xdg_surface_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, + (void (**)(void)) listener, data); +} + +#define XDG_SURFACE_DESTROY 0 +#define XDG_SURFACE_GET_TOPLEVEL 1 +#define XDG_SURFACE_GET_POPUP 2 +#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 +#define XDG_SURFACE_ACK_CONFIGURE 4 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 + +/** @ingroup iface_xdg_surface */ +static inline void +xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); +} + +/** @ingroup iface_xdg_surface */ +static inline void * +xdg_surface_get_user_data(struct xdg_surface *xdg_surface) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); +} + +static inline uint32_t +xdg_surface_get_version(struct xdg_surface *xdg_surface) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_surface); +} + +/** + * @ingroup iface_xdg_surface + * + * Destroy the xdg_surface object. An xdg_surface must only be destroyed + * after its role object has been destroyed, otherwise + * a defunct_role_object error is raised. + */ +static inline void +xdg_surface_destroy(struct xdg_surface *xdg_surface) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_surface + * + * This creates an xdg_toplevel object for the given xdg_surface and gives + * the associated wl_surface the xdg_toplevel role. + * + * See the documentation of xdg_toplevel for more details about what an + * xdg_toplevel is and how it is used. + */ +static inline struct xdg_toplevel * +xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL); + + return (struct xdg_toplevel *) id; +} + +/** + * @ingroup iface_xdg_surface + * + * This creates an xdg_popup object for the given xdg_surface and gives + * the associated wl_surface the xdg_popup role. + * + * If null is passed as a parent, a parent surface must be specified using + * some other protocol, before committing the initial state. + * + * See the documentation of xdg_popup for more details about what an + * xdg_popup is and how it is used. + */ +static inline struct xdg_popup * +xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) +{ + struct wl_proxy *id; + + id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_GET_POPUP, &xdg_popup_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL, parent, positioner); + + return (struct xdg_popup *) id; +} + +/** + * @ingroup iface_xdg_surface + * + * The window geometry of a surface is its "visible bounds" from the + * user's perspective. Client-side decorations often have invisible + * portions like drop-shadows which should be ignored for the + * purposes of aligning, placing and constraining windows. + * + * The window geometry is double buffered, and will be applied at the + * time wl_surface.commit of the corresponding wl_surface is called. + * + * When maintaining a position, the compositor should treat the (x, y) + * coordinate of the window geometry as the top left corner of the window. + * A client changing the (x, y) window geometry coordinate should in + * general not alter the position of the window. + * + * Once the window geometry of the surface is set, it is not possible to + * unset it, and it will remain the same until set_window_geometry is + * called again, even if a new subsurface or buffer is attached. + * + * If never set, the value is the full bounds of the surface, + * including any subsurfaces. This updates dynamically on every + * commit. This unset is meant for extremely simple clients. + * + * The arguments are given in the surface-local coordinate space of + * the wl_surface associated with this xdg_surface. + * + * The width and height must be greater than zero. Setting an invalid size + * will raise an invalid_size error. When applied, the effective window + * geometry will be the set window geometry clamped to the bounding + * rectangle of the combined geometry of the surface of the xdg_surface and + * the associated subsurfaces. + */ +static inline void +xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_SET_WINDOW_GEOMETRY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, x, y, width, height); +} + +/** + * @ingroup iface_xdg_surface + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client + * must make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * For instance, for toplevel surfaces the compositor might use this + * information to move a surface to the top left only when the client has + * drawn itself for the maximized or fullscreen state. + * + * If the client receives multiple configure events before it + * can respond to one, it only has to ack the last configure event. + * Acking a configure event that was never sent raises an invalid_serial + * error. + * + * A client is not required to commit immediately after sending + * an ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before committing, but + * only the last request sent before a commit indicates which configure + * event the client really is responding to. + * + * Sending an ack_configure request consumes the serial number sent with + * the request, as well as serial numbers sent by all configure events + * sent on this xdg_surface prior to the configure event referenced by + * the committed serial. + * + * It is an error to issue multiple ack_configure requests referencing a + * serial from the same configure event, or to issue an ack_configure + * request referencing a serial from a configure event issued before the + * event identified by the last ack_configure request for the same + * xdg_surface. Doing so will raise an invalid_serial error. + */ +static inline void +xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, + XDG_SURFACE_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, serial); +} + +#ifndef XDG_TOPLEVEL_ERROR_ENUM +#define XDG_TOPLEVEL_ERROR_ENUM +enum xdg_toplevel_error { + /** + * provided value is not a valid variant of the resize_edge enum + */ + XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE = 0, + /** + * invalid parent toplevel + */ + XDG_TOPLEVEL_ERROR_INVALID_PARENT = 1, + /** + * client provided an invalid min or max size + */ + XDG_TOPLEVEL_ERROR_INVALID_SIZE = 2, +}; +#endif /* XDG_TOPLEVEL_ERROR_ENUM */ + +#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM +#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM +/** + * @ingroup iface_xdg_toplevel + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. + */ +enum xdg_toplevel_resize_edge { + XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, + XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, + XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, + XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, +}; +#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ + +#ifndef XDG_TOPLEVEL_STATE_ENUM +#define XDG_TOPLEVEL_STATE_ENUM +/** + * @ingroup iface_xdg_toplevel + * types of state on the surface + * + * The different state values used on the surface. This is designed for + * state values like maximized, fullscreen. It is paired with the + * configure event to ensure that both the client and the compositor + * setting the state can be synchronized. + * + * States set in this way are double-buffered. They will get applied on + * the next commit. + */ +enum xdg_toplevel_state { + /** + * the surface is maximized + * the surface is maximized + * + * The surface is maximized. The window geometry specified in the + * configure event must be obeyed by the client. + * + * The client should draw without shadow or other decoration + * outside of the window geometry. + */ + XDG_TOPLEVEL_STATE_MAXIMIZED = 1, + /** + * the surface is fullscreen + * the surface is fullscreen + * + * The surface is fullscreen. The window geometry specified in + * the configure event is a maximum; the client cannot resize + * beyond it. For a surface to cover the whole fullscreened area, + * the geometry dimensions must be obeyed by the client. For more + * details, see xdg_toplevel.set_fullscreen. + */ + XDG_TOPLEVEL_STATE_FULLSCREEN = 2, + /** + * the surface is being resized + * the surface is being resized + * + * The surface is being resized. The window geometry specified in + * the configure event is a maximum; the client cannot resize + * beyond it. Clients that have aspect ratio or cell sizing + * configuration can use a smaller size, however. + */ + XDG_TOPLEVEL_STATE_RESIZING = 3, + /** + * the surface is now activated + * the surface is now activated + * + * Client window decorations should be painted as if the window + * is active. Do not assume this means that the window actually has + * keyboard or pointer focus. + */ + XDG_TOPLEVEL_STATE_ACTIVATED = 4, + /** + * the surfaceโ€™s left edge is tiled + * + * The window is currently in a tiled layout and the left edge is + * considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_LEFT = 5, + /** + * the surfaceโ€™s right edge is tiled + * + * The window is currently in a tiled layout and the right edge + * is considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, + /** + * the surfaceโ€™s top edge is tiled + * + * The window is currently in a tiled layout and the top edge is + * considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_TOP = 7, + /** + * the surfaceโ€™s bottom edge is tiled + * + * The window is currently in a tiled layout and the bottom edge + * is considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, +}; +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 +#endif /* XDG_TOPLEVEL_STATE_ENUM */ + +#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM +#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM +enum xdg_toplevel_wm_capabilities { + /** + * show_window_menu is available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1, + /** + * set_maximized and unset_maximized are available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 2, + /** + * set_fullscreen and unset_fullscreen are available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 3, + /** + * set_minimized is available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 4, +}; +#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM */ + +/** + * @ingroup iface_xdg_toplevel + * @struct xdg_toplevel_listener + */ +struct xdg_toplevel_listener { + /** + * suggest a surface change + * + * This configure event asks the client to resize its toplevel + * surface or to change its state. The configured state should not + * be applied immediately. See xdg_surface.configure for details. + * + * The width and height arguments specify a hint to the window + * about how its surface should be resized in window geometry + * coordinates. See set_window_geometry. + * + * If the width or height arguments are zero, it means the client + * should decide its own window dimension. This may happen when the + * compositor needs to configure the state of the surface but + * doesn't have any information about any previous or expected + * dimension. + * + * The states listed in the event specify how the width/height + * arguments should be interpreted, and possibly how it should be + * drawn. + * + * Clients must send an ack_configure in response to this event. + * See xdg_surface.configure and xdg_surface.ack_configure for + * details. + */ + void (*configure)(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states); + /** + * surface wants to be closed + * + * The close event is sent by the compositor when the user wants + * the surface to be closed. This should be equivalent to the user + * clicking the close button in client-side decorations, if your + * application has any. + * + * This is only a request that the user intends to close the + * window. The client may choose to ignore this request, or show a + * dialog to ask the user to save their data, etc. + */ + void (*close)(void *data, + struct xdg_toplevel *xdg_toplevel); + /** + * recommended window geometry bounds + * + * The configure_bounds event may be sent prior to a + * xdg_toplevel.configure event to communicate the bounds a window + * geometry size is recommended to constrain to. + * + * The passed width and height are in surface coordinate space. If + * width and height are 0, it means bounds is unknown and + * equivalent to as if no configure_bounds event was ever sent for + * this surface. + * + * The bounds can for example correspond to the size of a monitor + * excluding any panels or other shell components, so that a + * surface isn't created in a way that it cannot fit. + * + * The bounds may change at any point, and in such a case, a new + * xdg_toplevel.configure_bounds will be sent, followed by + * xdg_toplevel.configure and xdg_surface.configure. + * @since 4 + */ + void (*configure_bounds)(void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height); + /** + * compositor capabilities + * + * This event advertises the capabilities supported by the + * compositor. If a capability isn't supported, clients should hide + * or disable the UI elements that expose this functionality. For + * instance, if the compositor doesn't advertise support for + * minimized toplevels, a button triggering the set_minimized + * request should not be displayed. + * + * The compositor will ignore requests it doesn't support. For + * instance, a compositor which doesn't advertise support for + * minimized will ignore set_minimized requests. + * + * Compositors must send this event once before the first + * xdg_surface.configure event. When the capabilities change, + * compositors must send this event again and then send an + * xdg_surface.configure event. + * + * The configured state should not be applied immediately. See + * xdg_surface.configure for details. + * + * The capabilities are sent as an array of 32-bit unsigned + * integers in native endianness. + * @param capabilities array of 32-bit capabilities + * @since 5 + */ + void (*wm_capabilities)(void *data, + struct xdg_toplevel *xdg_toplevel, + struct wl_array *capabilities); +}; + +/** + * @ingroup iface_xdg_toplevel + */ +static inline int +xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, + const struct xdg_toplevel_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, + (void (**)(void)) listener, data); +} + +#define XDG_TOPLEVEL_DESTROY 0 +#define XDG_TOPLEVEL_SET_PARENT 1 +#define XDG_TOPLEVEL_SET_TITLE 2 +#define XDG_TOPLEVEL_SET_APP_ID 3 +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 +#define XDG_TOPLEVEL_MOVE 5 +#define XDG_TOPLEVEL_RESIZE 6 +#define XDG_TOPLEVEL_SET_MAX_SIZE 7 +#define XDG_TOPLEVEL_SET_MIN_SIZE 8 +#define XDG_TOPLEVEL_SET_MAXIMIZED 9 +#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 +#define XDG_TOPLEVEL_SET_FULLSCREEN 11 +#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 +#define XDG_TOPLEVEL_SET_MINIMIZED 13 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION 4 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION 5 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 + +/** @ingroup iface_xdg_toplevel */ +static inline void +xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); +} + +/** @ingroup iface_xdg_toplevel */ +static inline void * +xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); +} + +static inline uint32_t +xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); +} + +/** + * @ingroup iface_xdg_toplevel + * + * This request destroys the role surface and unmaps the surface; + * see "Unmapping" behavior in interface section for details. + */ +static inline void +xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set the "parent" of this surface. This surface should be stacked + * above the parent surface and all other ancestor surfaces. + * + * Parent surfaces should be set on dialogs, toolboxes, or other + * "auxiliary" surfaces, so that the parent is raised when the dialog + * is raised. + * + * Setting a null parent for a child surface unsets its parent. Setting + * a null parent for a surface which currently has no parent is a no-op. + * + * Only mapped surfaces can have child surfaces. Setting a parent which + * is not mapped is equivalent to setting a null parent. If a surface + * becomes unmapped, its children's parent is set to the parent of + * the now-unmapped surface. If the now-unmapped surface has no parent, + * its children's parent is unset. If the now-unmapped surface becomes + * mapped again, its parent-child relationship is not restored. + * + * The parent toplevel must not be one of the child toplevel's + * descendants, and the parent must be different from the child toplevel, + * otherwise the invalid_parent protocol error is raised. + */ +static inline void +xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_PARENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, parent); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + */ +static inline void +xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, title); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set an application identifier for the surface. + * + * The app ID identifies the general class of applications to which + * the surface belongs. The compositor can use this to group multiple + * surfaces together, or to determine how to launch a new application. + * + * For D-Bus activatable applications, the app ID is used as the D-Bus + * service name. + * + * The compositor shell will try to group application surfaces together + * by their app ID. As a best practice, it is suggested to select app + * ID's that match the basename of the application's .desktop file. + * For example, "org.freedesktop.FooViewer" where the .desktop file is + * "org.freedesktop.FooViewer.desktop". + * + * Like other properties, a set_app_id request can be sent after the + * xdg_toplevel has been mapped to update the property. + * + * See the desktop-entry specification [0] for more details on + * application identifiers and how they relate to well-known D-Bus + * names and .desktop files. + * + * [0] https://standards.freedesktop.org/desktop-entry-spec/ + */ +static inline void +xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, app_id); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Clients implementing client-side decorations might want to show + * a context menu when right-clicking on the decorations, giving the + * user a menu that they can use to maximize or minimize the window. + * + * This request asks the compositor to pop up such a window menu at + * the given position, relative to the local surface coordinates of + * the parent surface. There are no guarantees as to what menu items + * the window menu contains, or even if a window menu will be drawn + * at all. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. + */ +static inline void +xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SHOW_WINDOW_MENU, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, x, y); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Start an interactive, user-driven move of the surface. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. The passed + * serial is used to determine the type of interactive move (touch, + * pointer, etc). + * + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized), or if the passed serial + * is no longer valid. + * + * If triggered, the surface will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the move. It is up to the + * compositor to visually indicate that the move is taking place, such as + * updating a pointer cursor, during the move. There is no guarantee + * that the device focus will return when the move is completed. + */ +static inline void +xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Start a user-driven, interactive resize of the surface. + * + * This request must be used in response to some sort of user action + * like a button press, key press, or touch down event. The passed + * serial is used to determine the type of interactive resize (touch, + * pointer, etc). + * + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * + * If triggered, the client will receive configure events with the + * "resize" state enum value and the expected sizes. See the "resize" + * enum value for more details about what is required. The client + * must also acknowledge configure events using "ack_configure". After + * the resize is completed, the client will receive another "configure" + * event without the resize state. + * + * If triggered, the surface also will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the resize. It is up to the + * compositor to visually indicate that the resize is taking place, + * such as updating a pointer cursor, during the resize. There is no + * guarantee that the device focus will return when the resize is + * completed. + * + * The edges parameter specifies how the surface should be resized, and + * is one of the values of the resize_edge enum. Values not matching + * a variant of the enum will cause a protocol error. The compositor + * may use this information to update the surface position for example + * when dragging the top left corner. The compositor may also use + * this information to adapt its behavior, e.g. choose an appropriate + * cursor image. + */ +static inline void +xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, edges); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a maximum size for the window. + * + * The client can specify a maximum size so that the compositor does + * not try to configure the window beyond this size. + * + * The width and height arguments are in window geometry coordinates. + * See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get applied + * on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the maximum + * size. The compositor may decide to ignore the values set by the + * client and request a larger size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected maximum size in the given dimension. + * As a result, a client wishing to reset the maximum size + * to an unspecified state can use zero for width and height in the + * request. + * + * Requesting a maximum size to be smaller than the minimum size of + * a surface is illegal and will result in an invalid_size error. + * + * The width and height must be greater than or equal to zero. Using + * strictly negative values for width or height will result in a + * invalid_size error. + */ +static inline void +xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MAX_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Set a minimum size for the window. + * + * The client can specify a minimum size so that the compositor does + * not try to configure the window below this size. + * + * The width and height arguments are in window geometry coordinates. + * See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get applied + * on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the minimum + * size. The compositor may decide to ignore the values set by the + * client and request a smaller size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected minimum size in the given dimension. + * As a result, a client wishing to reset the minimum size + * to an unspecified state can use zero for width and height in the + * request. + * + * Requesting a minimum size to be larger than the maximum size of + * a surface is illegal and will result in an invalid_size error. + * + * The width and height must be greater than or equal to zero. Using + * strictly negative values for width and height will result in a + * invalid_size error. + */ +static inline void +xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MIN_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Maximize the surface. + * + * After requesting that the surface should be maximized, the compositor + * will respond by emitting a configure event. Whether this configure + * actually sets the window maximized is subject to compositor policies. + * The client must then update its content, drawing in the configured + * state. The client must also acknowledge the configure when committing + * the new content (see ack_configure). + * + * It is up to the compositor to decide how and where to maximize the + * surface, for example which output and what region of the screen should + * be used. + * + * If the surface was already maximized, the compositor will still emit + * a configure event with the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no direct + * effect. It may alter the state the surface is returned to when + * unmaximized unless overridden by the compositor. + */ +static inline void +xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Unmaximize the surface. + * + * After requesting that the surface should be unmaximized, the compositor + * will respond by emitting a configure event. Whether this actually + * un-maximizes the window is subject to compositor policies. + * If available and applicable, the compositor will include the window + * geometry dimensions the window had prior to being maximized in the + * configure event. The client must then update its content, drawing it in + * the configured state. The client must also acknowledge the configure + * when committing the new content (see ack_configure). + * + * It is up to the compositor to position the surface after it was + * unmaximized; usually the position the surface had before maximizing, if + * applicable. + * + * If the surface was already not maximized, the compositor will still + * emit a configure event without the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no direct + * effect. It may alter the state the surface is returned to when + * unmaximized unless overridden by the compositor. + */ +static inline void +xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_UNSET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Make the surface fullscreen. + * + * After requesting that the surface should be fullscreened, the + * compositor will respond by emitting a configure event. Whether the + * client is actually put into a fullscreen state is subject to compositor + * policies. The client must also acknowledge the configure when + * committing the new content (see ack_configure). + * + * The output passed by the request indicates the client's preference as + * to which display it should be set fullscreen on. If this value is NULL, + * it's up to the compositor to choose which display will be used to map + * this surface. + * + * If the surface doesn't cover the whole output, the compositor will + * position the surface in the center of the output and compensate with + * with border fill covering the rest of the output. The content of the + * border fill is undefined, but should be assumed to be in some way that + * attempts to blend into the surrounding area (e.g. solid black). + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + */ +static inline void +xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, output); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Make the surface no longer fullscreen. + * + * After requesting that the surface should be unfullscreened, the + * compositor will respond by emitting a configure event. + * Whether this actually removes the fullscreen state of the client is + * subject to compositor policies. + * + * Making a surface unfullscreen sets states for the surface based on the following: + * * the state(s) it may have had before becoming fullscreen + * * any state(s) decided by the compositor + * * any state(s) requested by the client while the surface was fullscreen + * + * The compositor may include the previous window geometry dimensions in + * the configure event, if applicable. + * + * The client must also acknowledge the configure when committing the new + * content (see ack_configure). + */ +static inline void +xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_UNSET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +/** + * @ingroup iface_xdg_toplevel + * + * Request that the compositor minimize your surface. There is no + * way to know if the surface is currently minimized, nor is there + * any way to unset minimization on this surface. + * + * If you are looking to throttle redrawing when minimized, please + * instead use the wl_surface.frame event for this, as this will + * also work with live previews on windows in Alt-Tab, Expose or + * similar compositor features. + */ +static inline void +xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, + XDG_TOPLEVEL_SET_MINIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); +} + +#ifndef XDG_POPUP_ERROR_ENUM +#define XDG_POPUP_ERROR_ENUM +enum xdg_popup_error { + /** + * tried to grab after being mapped + */ + XDG_POPUP_ERROR_INVALID_GRAB = 0, +}; +#endif /* XDG_POPUP_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_popup + * @struct xdg_popup_listener + */ +struct xdg_popup_listener { + /** + * configure the popup surface + * + * This event asks the popup surface to configure itself given + * the configuration. The configured state should not be applied + * immediately. See xdg_surface.configure for details. + * + * The x and y arguments represent the position the popup was + * placed at given the xdg_positioner rule, relative to the upper + * left corner of the window geometry of the parent surface. + * + * For version 2 or older, the configure event for an xdg_popup is + * only ever sent once for the initial configuration. Starting with + * version 3, it may be sent again if the popup is setup with an + * xdg_positioner with set_reactive requested, or in response to + * xdg_popup.reposition requests. + * @param x x position relative to parent surface window geometry + * @param y y position relative to parent surface window geometry + * @param width window geometry width + * @param height window geometry height + */ + void (*configure)(void *data, + struct xdg_popup *xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * popup interaction is done + * + * The popup_done event is sent out when a popup is dismissed by + * the compositor. The client should destroy the xdg_popup object + * at this point. + */ + void (*popup_done)(void *data, + struct xdg_popup *xdg_popup); + /** + * signal the completion of a repositioned request + * + * The repositioned event is sent as part of a popup + * configuration sequence, together with xdg_popup.configure and + * lastly xdg_surface.configure to notify the completion of a + * reposition request. + * + * The repositioned event is to notify about the completion of a + * xdg_popup.reposition request. The token argument is the token + * passed in the xdg_popup.reposition request. + * + * Immediately after this event is emitted, xdg_popup.configure and + * xdg_surface.configure will be sent with the updated size and + * position, as well as a new configure serial. + * + * The client should optionally update the content of the popup, + * but must acknowledge the new popup configuration for the new + * position to take effect. See xdg_surface.ack_configure for + * details. + * @param token reposition request token + * @since 3 + */ + void (*repositioned)(void *data, + struct xdg_popup *xdg_popup, + uint32_t token); +}; + +/** + * @ingroup iface_xdg_popup + */ +static inline int +xdg_popup_add_listener(struct xdg_popup *xdg_popup, + const struct xdg_popup_listener *listener, void *data) +{ + return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, + (void (**)(void)) listener, data); +} + +#define XDG_POPUP_DESTROY 0 +#define XDG_POPUP_GRAB 1 +#define XDG_POPUP_REPOSITION 2 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_GRAB_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 + +/** @ingroup iface_xdg_popup */ +static inline void +xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) +{ + wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); +} + +/** @ingroup iface_xdg_popup */ +static inline void * +xdg_popup_get_user_data(struct xdg_popup *xdg_popup) +{ + return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); +} + +static inline uint32_t +xdg_popup_get_version(struct xdg_popup *xdg_popup) +{ + return wl_proxy_get_version((struct wl_proxy *) xdg_popup); +} + +/** + * @ingroup iface_xdg_popup + * + * This destroys the popup. Explicitly destroying the xdg_popup + * object will also dismiss the popup, and unmap the surface. + * + * If this xdg_popup is not the "topmost" popup, a protocol error + * will be sent. + */ +static inline void +xdg_popup_destroy(struct xdg_popup *xdg_popup) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, + XDG_POPUP_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), WL_MARSHAL_FLAG_DESTROY); +} + +/** + * @ingroup iface_xdg_popup + * + * This request makes the created popup take an explicit grab. An explicit + * grab will be dismissed when the user dismisses the popup, or when the + * client destroys the xdg_popup. This can be done by the user clicking + * outside the surface, using the keyboard, or even locking the screen + * through closing the lid or a timeout. + * + * If the compositor denies the grab, the popup will be immediately + * dismissed. + * + * This request must be used in response to some sort of user action like a + * button press, key press, or touch down event. The serial number of the + * event should be passed as 'serial'. + * + * The parent of a grabbing popup must either be an xdg_toplevel surface or + * another xdg_popup with an explicit grab. If the parent is another + * xdg_popup it means that the popups are nested, with this popup now being + * the topmost popup. + * + * Nested popups must be destroyed in the reverse order they were created + * in, e.g. the only popup you are allowed to destroy at all times is the + * topmost one. + * + * When compositors choose to dismiss a popup, they may dismiss every + * nested grabbing popup as well. When a compositor dismisses popups, it + * will follow the same dismissing order as required from the client. + * + * If the topmost grabbing popup is destroyed, the grab will be returned to + * the parent of the popup, if that parent previously had an explicit grab. + * + * If the parent is a grabbing popup which has already been dismissed, this + * popup will be immediately dismissed. If the parent is a popup that did + * not take an explicit grab, an error will be raised. + * + * During a popup grab, the client owning the grab will receive pointer + * and touch events for all their surfaces as normal (similar to an + * "owner-events" grab in X11 parlance), while the top most grabbing popup + * will always have keyboard focus. + */ +static inline void +xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, + XDG_POPUP_GRAB, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, seat, serial); +} + +/** + * @ingroup iface_xdg_popup + * + * Reposition an already-mapped popup. The popup will be placed given the + * details in the passed xdg_positioner object, and a + * xdg_popup.repositioned followed by xdg_popup.configure and + * xdg_surface.configure will be emitted in response. Any parameters set + * by the previous positioner will be discarded. + * + * The passed token will be sent in the corresponding + * xdg_popup.repositioned event. The new popup position will not take + * effect until the corresponding configure event is acknowledged by the + * client. See xdg_popup.repositioned for details. The token itself is + * opaque, and has no other special meaning. + * + * If multiple reposition requests are sent, the compositor may skip all + * but the last one. + * + * If the popup is repositioned in response to a configure event for its + * parent, the client should send an xdg_positioner.set_parent_configure + * and possibly an xdg_positioner.set_parent_size request to allow the + * compositor to properly constrain the popup. + * + * If the popup is repositioned together with a parent that is being + * resized, but not in response to a configure event, the client should + * send an xdg_positioner.set_parent_size request. + */ +static inline void +xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token) +{ + wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, + XDG_POPUP_REPOSITION, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, positioner, token); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c b/app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c new file mode 100644 index 000000000..72b394a2b --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c @@ -0,0 +1,184 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2008-2013 Kristian Hรธgsberg + * Copyright ยฉ 2013 Rafael Antognolli + * Copyright ยฉ 2013 Jasper St. Pierre + * Copyright ยฉ 2010-2013 Intel Corporation + * Copyright ยฉ 2015-2017 Samsung Electronics Co., Ltd + * Copyright ยฉ 2015-2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface xdg_positioner_interface; +extern const struct wl_interface xdg_surface_interface; +extern const struct wl_interface xdg_toplevel_interface; + +static const struct wl_interface *xdg_shell_types[] = { + NULL, + NULL, + NULL, + NULL, + &xdg_positioner_interface, + &xdg_surface_interface, + &wl_surface_interface, + &xdg_toplevel_interface, + &xdg_popup_interface, + &xdg_surface_interface, + &xdg_positioner_interface, + &xdg_toplevel_interface, + &wl_seat_interface, + NULL, + NULL, + NULL, + &wl_seat_interface, + NULL, + &wl_seat_interface, + NULL, + NULL, + &wl_output_interface, + &wl_seat_interface, + NULL, + &xdg_positioner_interface, + NULL, +}; + +static const struct wl_message xdg_wm_base_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "create_positioner", "n", xdg_shell_types + 4 }, + { "get_xdg_surface", "no", xdg_shell_types + 5 }, + { "pong", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_wm_base_events[] = { + { "ping", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { + "xdg_wm_base", 5, + 4, xdg_wm_base_requests, + 1, xdg_wm_base_events, +}; + +static const struct wl_message xdg_positioner_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_size", "ii", xdg_shell_types + 0 }, + { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, + { "set_anchor", "u", xdg_shell_types + 0 }, + { "set_gravity", "u", xdg_shell_types + 0 }, + { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, + { "set_offset", "ii", xdg_shell_types + 0 }, + { "set_reactive", "3", xdg_shell_types + 0 }, + { "set_parent_size", "3ii", xdg_shell_types + 0 }, + { "set_parent_configure", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_positioner_interface = { + "xdg_positioner", 5, + 10, xdg_positioner_requests, + 0, NULL, +}; + +static const struct wl_message xdg_surface_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "get_toplevel", "n", xdg_shell_types + 7 }, + { "get_popup", "n?oo", xdg_shell_types + 8 }, + { "set_window_geometry", "iiii", xdg_shell_types + 0 }, + { "ack_configure", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_surface_events[] = { + { "configure", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_surface_interface = { + "xdg_surface", 5, + 5, xdg_surface_requests, + 1, xdg_surface_events, +}; + +static const struct wl_message xdg_toplevel_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_parent", "?o", xdg_shell_types + 11 }, + { "set_title", "s", xdg_shell_types + 0 }, + { "set_app_id", "s", xdg_shell_types + 0 }, + { "show_window_menu", "ouii", xdg_shell_types + 12 }, + { "move", "ou", xdg_shell_types + 16 }, + { "resize", "ouu", xdg_shell_types + 18 }, + { "set_max_size", "ii", xdg_shell_types + 0 }, + { "set_min_size", "ii", xdg_shell_types + 0 }, + { "set_maximized", "", xdg_shell_types + 0 }, + { "unset_maximized", "", xdg_shell_types + 0 }, + { "set_fullscreen", "?o", xdg_shell_types + 21 }, + { "unset_fullscreen", "", xdg_shell_types + 0 }, + { "set_minimized", "", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_toplevel_events[] = { + { "configure", "iia", xdg_shell_types + 0 }, + { "close", "", xdg_shell_types + 0 }, + { "configure_bounds", "4ii", xdg_shell_types + 0 }, + { "wm_capabilities", "5a", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { + "xdg_toplevel", 5, + 14, xdg_toplevel_requests, + 4, xdg_toplevel_events, +}; + +static const struct wl_message xdg_popup_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "grab", "ou", xdg_shell_types + 22 }, + { "reposition", "3ou", xdg_shell_types + 24 }, +}; + +static const struct wl_message xdg_popup_events[] = { + { "configure", "iiii", xdg_shell_types + 0 }, + { "popup_done", "", xdg_shell_types + 0 }, + { "repositioned", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_popup_interface = { + "xdg_popup", 5, + 3, xdg_popup_requests, + 3, xdg_popup_events, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/xdg-shell-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/xdg-shell-server-protocol.h new file mode 100644 index 000000000..2683694ce --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/xdg-shell-server-protocol.h @@ -0,0 +1,2257 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef XDG_SHELL_SERVER_PROTOCOL_H +#define XDG_SHELL_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_xdg_shell The xdg_shell protocol + * @section page_ifaces_xdg_shell Interfaces + * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces + * - @subpage page_iface_xdg_positioner - child surface positioner + * - @subpage page_iface_xdg_surface - desktop user interface surface base interface + * - @subpage page_iface_xdg_toplevel - toplevel surface + * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus + * @section page_copyright_xdg_shell Copyright + *
+ *
+ * Copyright ยฉ 2008-2013 Kristian Hรธgsberg
+ * Copyright ยฉ 2013      Rafael Antognolli
+ * Copyright ยฉ 2013      Jasper St. Pierre
+ * Copyright ยฉ 2010-2013 Intel Corporation
+ * Copyright ยฉ 2015-2017 Samsung Electronics Co., Ltd
+ * Copyright ยฉ 2015-2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_seat; +struct wl_surface; +struct xdg_popup; +struct xdg_positioner; +struct xdg_surface; +struct xdg_toplevel; +struct xdg_wm_base; + +#ifndef XDG_WM_BASE_INTERFACE +#define XDG_WM_BASE_INTERFACE +/** + * @page page_iface_xdg_wm_base xdg_wm_base + * @section page_iface_xdg_wm_base_desc Description + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + * @section page_iface_xdg_wm_base_api API + * See @ref iface_xdg_wm_base. + */ +/** + * @defgroup iface_xdg_wm_base The xdg_wm_base interface + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + */ +extern const struct wl_interface xdg_wm_base_interface; +#endif +#ifndef XDG_POSITIONER_INTERFACE +#define XDG_POSITIONER_INTERFACE +/** + * @page page_iface_xdg_positioner xdg_positioner + * @section page_iface_xdg_positioner_desc Description + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an invalid_positioner error. + * @section page_iface_xdg_positioner_api API + * See @ref iface_xdg_positioner. + */ +/** + * @defgroup iface_xdg_positioner The xdg_positioner interface + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an invalid_positioner error. + */ +extern const struct wl_interface xdg_positioner_interface; +#endif +#ifndef XDG_SURFACE_INTERFACE +#define XDG_SURFACE_INTERFACE +/** + * @page page_iface_xdg_surface xdg_surface + * @section page_iface_xdg_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed, i.e. the client must perform the initial commit + * again before attaching a buffer. + * @section page_iface_xdg_surface_api API + * See @ref iface_xdg_surface. + */ +/** + * @defgroup iface_xdg_surface The xdg_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed, i.e. the client must perform the initial commit + * again before attaching a buffer. + */ +extern const struct wl_interface xdg_surface_interface; +#endif +#ifndef XDG_TOPLEVEL_INTERFACE +#define XDG_TOPLEVEL_INTERFACE +/** + * @page page_iface_xdg_toplevel xdg_toplevel + * @section page_iface_xdg_toplevel_desc Description + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by performing a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + * @section page_iface_xdg_toplevel_api API + * See @ref iface_xdg_toplevel. + */ +/** + * @defgroup iface_xdg_toplevel The xdg_toplevel interface + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by performing a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + */ +extern const struct wl_interface xdg_toplevel_interface; +#endif +#ifndef XDG_POPUP_INTERFACE +#define XDG_POPUP_INTERFACE +/** + * @page page_iface_xdg_popup xdg_popup + * @section page_iface_xdg_popup_desc Description + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + * @section page_iface_xdg_popup_api API + * See @ref iface_xdg_popup. + */ +/** + * @defgroup iface_xdg_popup The xdg_popup interface + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + */ +extern const struct wl_interface xdg_popup_interface; +#endif + +#ifndef XDG_WM_BASE_ERROR_ENUM +#define XDG_WM_BASE_ERROR_ENUM +enum xdg_wm_base_error { + /** + * given wl_surface has another role + */ + XDG_WM_BASE_ERROR_ROLE = 0, + /** + * xdg_wm_base was destroyed before children + */ + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, + /** + * the client tried to map or destroy a non-topmost popup + */ + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, + /** + * the client specified an invalid popup parent surface + */ + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, + /** + * the client provided an invalid surface state + */ + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, + /** + * the client provided an invalid positioner + */ + XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, + /** + * the client didnโ€™t respond to a ping event in time + */ + XDG_WM_BASE_ERROR_UNRESPONSIVE = 6, +}; +#endif /* XDG_WM_BASE_ERROR_ENUM */ + +#ifndef XDG_WM_BASE_ERROR_ENUM_IS_VALID +#define XDG_WM_BASE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_wm_base + * Validate a xdg_wm_base error value. + * + * @return true on success, false on error. + * @ref xdg_wm_base_error + */ +static inline bool +xdg_wm_base_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_WM_BASE_ERROR_ROLE: + return version >= 1; + case XDG_WM_BASE_ERROR_DEFUNCT_SURFACES: + return version >= 1; + case XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP: + return version >= 1; + case XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT: + return version >= 1; + case XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE: + return version >= 1; + case XDG_WM_BASE_ERROR_INVALID_POSITIONER: + return version >= 1; + case XDG_WM_BASE_ERROR_UNRESPONSIVE: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_WM_BASE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_xdg_wm_base + * @struct xdg_wm_base_interface + */ +struct xdg_wm_base_interface { + /** + * destroy xdg_wm_base + * + * Destroy this xdg_wm_base object. + * + * Destroying a bound xdg_wm_base object while there are surfaces + * still alive created by this xdg_wm_base object instance is + * illegal and will result in a defunct_surfaces error. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * create a positioner object + * + * Create a positioner object. A positioner object is used to + * position surfaces relative to some parent surface. See the + * interface description and xdg_surface.get_popup for details. + */ + void (*create_positioner)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * create a shell surface from a surface + * + * This creates an xdg_surface for the given surface. While + * xdg_surface itself is not a role, the corresponding surface may + * only be assigned a role extending xdg_surface, such as + * xdg_toplevel or xdg_popup. It is illegal to create an + * xdg_surface for a wl_surface which already has an assigned role + * and this will result in a role error. + * + * This creates an xdg_surface for the given surface. An + * xdg_surface is used as basis to define a role to a given + * surface, such as xdg_toplevel or xdg_popup. It also manages + * functionality shared between xdg_surface based surface roles. + * + * See the documentation of xdg_surface for more details about what + * an xdg_surface is and how it is used. + */ + void (*get_xdg_surface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); + /** + * respond to a ping event + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. See xdg_wm_base.ping and + * xdg_wm_base.error.unresponsive. + * @param serial serial of the ping event + */ + void (*pong)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); +}; + +#define XDG_WM_BASE_PING 0 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PING_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PONG_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + * Sends an ping event to the client owning the resource. + * @param resource_ The client's resource + * @param serial pass this to the pong request + */ +static inline void +xdg_wm_base_send_ping(struct wl_resource *resource_, uint32_t serial) +{ + wl_resource_post_event(resource_, XDG_WM_BASE_PING, serial); +} + +#ifndef XDG_POSITIONER_ERROR_ENUM +#define XDG_POSITIONER_ERROR_ENUM +enum xdg_positioner_error { + /** + * invalid input provided + */ + XDG_POSITIONER_ERROR_INVALID_INPUT = 0, +}; +#endif /* XDG_POSITIONER_ERROR_ENUM */ + +#ifndef XDG_POSITIONER_ERROR_ENUM_IS_VALID +#define XDG_POSITIONER_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_positioner + * Validate a xdg_positioner error value. + * + * @return true on success, false on error. + * @ref xdg_positioner_error + */ +static inline bool +xdg_positioner_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_POSITIONER_ERROR_INVALID_INPUT: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_POSITIONER_ERROR_ENUM_IS_VALID */ + +#ifndef XDG_POSITIONER_ANCHOR_ENUM +#define XDG_POSITIONER_ANCHOR_ENUM +enum xdg_positioner_anchor { + XDG_POSITIONER_ANCHOR_NONE = 0, + XDG_POSITIONER_ANCHOR_TOP = 1, + XDG_POSITIONER_ANCHOR_BOTTOM = 2, + XDG_POSITIONER_ANCHOR_LEFT = 3, + XDG_POSITIONER_ANCHOR_RIGHT = 4, + XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, + XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, + XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_ANCHOR_ENUM */ + +#ifndef XDG_POSITIONER_ANCHOR_ENUM_IS_VALID +#define XDG_POSITIONER_ANCHOR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_positioner + * Validate a xdg_positioner anchor value. + * + * @return true on success, false on error. + * @ref xdg_positioner_anchor + */ +static inline bool +xdg_positioner_anchor_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_POSITIONER_ANCHOR_NONE: + return version >= 1; + case XDG_POSITIONER_ANCHOR_TOP: + return version >= 1; + case XDG_POSITIONER_ANCHOR_BOTTOM: + return version >= 1; + case XDG_POSITIONER_ANCHOR_LEFT: + return version >= 1; + case XDG_POSITIONER_ANCHOR_RIGHT: + return version >= 1; + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + return version >= 1; + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + return version >= 1; + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + return version >= 1; + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_POSITIONER_ANCHOR_ENUM_IS_VALID */ + +#ifndef XDG_POSITIONER_GRAVITY_ENUM +#define XDG_POSITIONER_GRAVITY_ENUM +enum xdg_positioner_gravity { + XDG_POSITIONER_GRAVITY_NONE = 0, + XDG_POSITIONER_GRAVITY_TOP = 1, + XDG_POSITIONER_GRAVITY_BOTTOM = 2, + XDG_POSITIONER_GRAVITY_LEFT = 3, + XDG_POSITIONER_GRAVITY_RIGHT = 4, + XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, + XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, + XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_GRAVITY_ENUM */ + +#ifndef XDG_POSITIONER_GRAVITY_ENUM_IS_VALID +#define XDG_POSITIONER_GRAVITY_ENUM_IS_VALID +/** + * @ingroup iface_xdg_positioner + * Validate a xdg_positioner gravity value. + * + * @return true on success, false on error. + * @ref xdg_positioner_gravity + */ +static inline bool +xdg_positioner_gravity_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_POSITIONER_GRAVITY_NONE: + return version >= 1; + case XDG_POSITIONER_GRAVITY_TOP: + return version >= 1; + case XDG_POSITIONER_GRAVITY_BOTTOM: + return version >= 1; + case XDG_POSITIONER_GRAVITY_LEFT: + return version >= 1; + case XDG_POSITIONER_GRAVITY_RIGHT: + return version >= 1; + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + return version >= 1; + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + return version >= 1; + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + return version >= 1; + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_POSITIONER_GRAVITY_ENUM_IS_VALID */ + +#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +/** + * @ingroup iface_xdg_positioner + * constraint adjustments + * + * The constraint adjustment value define ways the compositor will adjust + * the position of the surface, if the unadjusted position would result + * in the surface being partly constrained. + * + * Whether a surface is considered 'constrained' is left to the compositor + * to determine. For example, the surface may be partly outside the + * compositor's defined 'work area', thus necessitating the child surface's + * position be adjusted until it is entirely inside the work area. + * + * The adjustments can be combined, according to a defined precedence: 1) + * Flip, 2) Slide, 3) Resize. + */ +enum xdg_positioner_constraint_adjustment { + /** + * don't move the child surface when constrained + * + * Don't alter the surface position even if it is constrained on + * some axis, for example partially outside the edge of an output. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, + /** + * move along the x axis until unconstrained + * + * Slide the surface along the x axis until it is no longer + * constrained. + * + * First try to slide towards the direction of the gravity on the x + * axis until either the edge in the opposite direction of the + * gravity is unconstrained or the edge in the direction of the + * gravity is constrained. + * + * Then try to slide towards the opposite direction of the gravity + * on the x axis until either the edge in the direction of the + * gravity is unconstrained or the edge in the opposite direction + * of the gravity is constrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, + /** + * move along the y axis until unconstrained + * + * Slide the surface along the y axis until it is no longer + * constrained. + * + * First try to slide towards the direction of the gravity on the y + * axis until either the edge in the opposite direction of the + * gravity is unconstrained or the edge in the direction of the + * gravity is constrained. + * + * Then try to slide towards the opposite direction of the gravity + * on the y axis until either the edge in the direction of the + * gravity is unconstrained or the edge in the opposite direction + * of the gravity is constrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, + /** + * invert the anchor and gravity on the x axis + * + * Invert the anchor and gravity on the x axis if the surface is + * constrained on the x axis. For example, if the left edge of the + * surface is constrained, the gravity is 'left' and the anchor is + * 'left', change the gravity to 'right' and the anchor to 'right'. + * + * If the adjusted position also ends up being constrained, the + * resulting position of the flip_x adjustment will be the one + * before the adjustment. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, + /** + * invert the anchor and gravity on the y axis + * + * Invert the anchor and gravity on the y axis if the surface is + * constrained on the y axis. For example, if the bottom edge of + * the surface is constrained, the gravity is 'bottom' and the + * anchor is 'bottom', change the gravity to 'top' and the anchor + * to 'top'. + * + * The adjusted position is calculated given the original anchor + * rectangle and offset, but with the new flipped anchor and + * gravity values. + * + * If the adjusted position also ends up being constrained, the + * resulting position of the flip_y adjustment will be the one + * before the adjustment. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, + /** + * horizontally resize the surface + * + * Resize the surface horizontally so that it is completely + * unconstrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, + /** + * vertically resize the surface + * + * Resize the surface vertically so that it is completely + * unconstrained. + */ + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, +}; +#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ + +#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM_IS_VALID +#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM_IS_VALID +/** + * @ingroup iface_xdg_positioner + * Validate a xdg_positioner constraint_adjustment value. + * + * @return true on success, false on error. + * @ref xdg_positioner_constraint_adjustment + */ +static inline bool +xdg_positioner_constraint_adjustment_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X; + if (version >= 1) + valid |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y; + return (value & ~valid) == 0; +} +#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM_IS_VALID */ + +/** + * @ingroup iface_xdg_positioner + * @struct xdg_positioner_interface + */ +struct xdg_positioner_interface { + /** + * destroy the xdg_positioner object + * + * Notify the compositor that the xdg_positioner will no longer + * be used. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the size of the to-be positioned rectangle + * + * Set the size of the surface that is to be positioned with the + * positioner object. The size is in surface-local coordinates and + * corresponds to the window geometry. See + * xdg_surface.set_window_geometry. + * + * If a zero or negative size is set the invalid_input error is + * raised. + * @param width width of positioned rectangle + * @param height height of positioned rectangle + */ + void (*set_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); + /** + * set the anchor rectangle within the parent surface + * + * Specify the anchor rectangle within the parent surface that + * the child surface will be placed relative to. The rectangle is + * relative to the window geometry as defined by + * xdg_surface.set_window_geometry of the parent surface. + * + * When the xdg_positioner object is used to position a child + * surface, the anchor rectangle may not extend outside the window + * geometry of the positioned child's parent surface. + * + * If a negative size is set the invalid_input error is raised. + * @param x x position of anchor rectangle + * @param y y position of anchor rectangle + * @param width width of anchor rectangle + * @param height height of anchor rectangle + */ + void (*set_anchor_rect)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * set anchor rectangle anchor + * + * Defines the anchor point for the anchor rectangle. The + * specified anchor is used derive an anchor point that the child + * surface will be positioned relative to. If a corner anchor is + * set (e.g. 'top_left' or 'bottom_right'), the anchor point will + * be at the specified corner; otherwise, the derived anchor point + * will be centered on the specified edge, or in the center of the + * anchor rectangle if no edge is specified. + * @param anchor anchor + */ + void (*set_anchor)(struct wl_client *client, + struct wl_resource *resource, + uint32_t anchor); + /** + * set child surface gravity + * + * Defines in what direction a surface should be positioned, + * relative to the anchor point of the parent surface. If a corner + * gravity is specified (e.g. 'bottom_right' or 'top_left'), then + * the child surface will be placed towards the specified gravity; + * otherwise, the child surface will be centered over the anchor + * point on any axis that had no gravity specified. If the gravity + * is not in the โ€˜gravityโ€™ enum, an invalid_input error is + * raised. + * @param gravity gravity direction + */ + void (*set_gravity)(struct wl_client *client, + struct wl_resource *resource, + uint32_t gravity); + /** + * set the adjustment to be done when constrained + * + * Specify how the window should be positioned if the originally + * intended position caused the surface to be constrained, meaning + * at least partially outside positioning boundaries set by the + * compositor. The adjustment is set by constructing a bitmask + * describing the adjustment to be made when the surface is + * constrained on that axis. + * + * If no bit for one axis is set, the compositor will assume that + * the child surface should not change its position on that axis + * when constrained. + * + * If more than one bit for one axis is set, the order of how + * adjustments are applied is specified in the corresponding + * adjustment descriptions. + * + * The default adjustment is none. + * @param constraint_adjustment bit mask of constraint adjustments + */ + void (*set_constraint_adjustment)(struct wl_client *client, + struct wl_resource *resource, + uint32_t constraint_adjustment); + /** + * set surface position offset + * + * Specify the surface position offset relative to the position + * of the anchor on the anchor rectangle and the anchor on the + * surface. For example if the anchor of the anchor rectangle is at + * (x, y), the surface has the gravity bottom|right, and the offset + * is (ox, oy), the calculated surface position will be (x + ox, y + * + oy). The offset position of the surface is the one used for + * constraint testing. See set_constraint_adjustment. + * + * An example use case is placing a popup menu on top of a user + * interface element, while aligning the user interface element of + * the parent surface with some user interface element placed + * somewhere in the popup surface. + * @param x surface position x offset + * @param y surface position y offset + */ + void (*set_offset)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y); + /** + * continuously reconstrain the surface + * + * When set reactive, the surface is reconstrained if the + * conditions used for constraining changed, e.g. the parent window + * moved. + * + * If the conditions changed and the popup was reconstrained, an + * xdg_popup.configure event is sent with updated geometry, + * followed by an xdg_surface.configure event. + * @since 3 + */ + void (*set_reactive)(struct wl_client *client, + struct wl_resource *resource); + /** + * + * + * Set the parent window geometry the compositor should use when + * positioning the popup. The compositor may use this information + * to determine the future state the popup should be constrained + * using. If this doesn't match the dimension of the parent the + * popup is eventually positioned against, the behavior is + * undefined. + * + * The arguments are given in the surface-local coordinate space. + * @param parent_width future window geometry width of parent + * @param parent_height future window geometry height of parent + * @since 3 + */ + void (*set_parent_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t parent_width, + int32_t parent_height); + /** + * set parent configure this is a response to + * + * Set the serial of an xdg_surface.configure event this + * positioner will be used in response to. The compositor may use + * this information together with set_parent_size to determine what + * future state the popup should be constrained using. + * @param serial serial of parent configure event + * @since 3 + */ + void (*set_parent_configure)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); +}; + + +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 + +#ifndef XDG_SURFACE_ERROR_ENUM +#define XDG_SURFACE_ERROR_ENUM +enum xdg_surface_error { + /** + * Surface was not fully constructed + */ + XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, + /** + * Surface was already constructed + */ + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, + /** + * Attaching a buffer to an unconfigured surface + */ + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, + /** + * Invalid serial number when acking a configure event + */ + XDG_SURFACE_ERROR_INVALID_SERIAL = 4, + /** + * Width or height was zero or negative + */ + XDG_SURFACE_ERROR_INVALID_SIZE = 5, + /** + * Surface was destroyed before its role object + */ + XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 6, +}; +#endif /* XDG_SURFACE_ERROR_ENUM */ + +#ifndef XDG_SURFACE_ERROR_ENUM_IS_VALID +#define XDG_SURFACE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_surface + * Validate a xdg_surface error value. + * + * @return true on success, false on error. + * @ref xdg_surface_error + */ +static inline bool +xdg_surface_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_SURFACE_ERROR_NOT_CONSTRUCTED: + return version >= 1; + case XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED: + return version >= 1; + case XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER: + return version >= 1; + case XDG_SURFACE_ERROR_INVALID_SERIAL: + return version >= 1; + case XDG_SURFACE_ERROR_INVALID_SIZE: + return version >= 1; + case XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_SURFACE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_xdg_surface + * @struct xdg_surface_interface + */ +struct xdg_surface_interface { + /** + * destroy the xdg_surface + * + * Destroy the xdg_surface object. An xdg_surface must only be + * destroyed after its role object has been destroyed, otherwise a + * defunct_role_object error is raised. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * assign the xdg_toplevel surface role + * + * This creates an xdg_toplevel object for the given xdg_surface + * and gives the associated wl_surface the xdg_toplevel role. + * + * See the documentation of xdg_toplevel for more details about + * what an xdg_toplevel is and how it is used. + */ + void (*get_toplevel)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * assign the xdg_popup surface role + * + * This creates an xdg_popup object for the given xdg_surface and + * gives the associated wl_surface the xdg_popup role. + * + * If null is passed as a parent, a parent surface must be + * specified using some other protocol, before committing the + * initial state. + * + * See the documentation of xdg_popup for more details about what + * an xdg_popup is and how it is used. + */ + void (*get_popup)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent, + struct wl_resource *positioner); + /** + * set the new window geometry + * + * The window geometry of a surface is its "visible bounds" from + * the user's perspective. Client-side decorations often have + * invisible portions like drop-shadows which should be ignored for + * the purposes of aligning, placing and constraining windows. + * + * The window geometry is double buffered, and will be applied at + * the time wl_surface.commit of the corresponding wl_surface is + * called. + * + * When maintaining a position, the compositor should treat the (x, + * y) coordinate of the window geometry as the top left corner of + * the window. A client changing the (x, y) window geometry + * coordinate should in general not alter the position of the + * window. + * + * Once the window geometry of the surface is set, it is not + * possible to unset it, and it will remain the same until + * set_window_geometry is called again, even if a new subsurface or + * buffer is attached. + * + * If never set, the value is the full bounds of the surface, + * including any subsurfaces. This updates dynamically on every + * commit. This unset is meant for extremely simple clients. + * + * The arguments are given in the surface-local coordinate space of + * the wl_surface associated with this xdg_surface. + * + * The width and height must be greater than zero. Setting an + * invalid size will raise an invalid_size error. When applied, the + * effective window geometry will be the set window geometry + * clamped to the bounding rectangle of the combined geometry of + * the surface of the xdg_surface and the associated subsurfaces. + */ + void (*set_window_geometry)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * ack a configure event + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client must + * make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * For instance, for toplevel surfaces the compositor might use + * this information to move a surface to the top left only when the + * client has drawn itself for the maximized or fullscreen state. + * + * If the client receives multiple configure events before it can + * respond to one, it only has to ack the last configure event. + * Acking a configure event that was never sent raises an + * invalid_serial error. + * + * A client is not required to commit immediately after sending an + * ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before + * committing, but only the last request sent before a commit + * indicates which configure event the client really is responding + * to. + * + * Sending an ack_configure request consumes the serial number sent + * with the request, as well as serial numbers sent by all + * configure events sent on this xdg_surface prior to the configure + * event referenced by the committed serial. + * + * It is an error to issue multiple ack_configure requests + * referencing a serial from the same configure event, or to issue + * an ack_configure request referencing a serial from a configure + * event issued before the event identified by the last + * ack_configure request for the same xdg_surface. Doing so will + * raise an invalid_serial error. + * @param serial the serial from the configure event + */ + void (*ack_configure)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); +}; + +#define XDG_SURFACE_CONFIGURE 0 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial of the configure event + */ +static inline void +xdg_surface_send_configure(struct wl_resource *resource_, uint32_t serial) +{ + wl_resource_post_event(resource_, XDG_SURFACE_CONFIGURE, serial); +} + +#ifndef XDG_TOPLEVEL_ERROR_ENUM +#define XDG_TOPLEVEL_ERROR_ENUM +enum xdg_toplevel_error { + /** + * provided value is not a valid variant of the resize_edge enum + */ + XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE = 0, + /** + * invalid parent toplevel + */ + XDG_TOPLEVEL_ERROR_INVALID_PARENT = 1, + /** + * client provided an invalid min or max size + */ + XDG_TOPLEVEL_ERROR_INVALID_SIZE = 2, +}; +#endif /* XDG_TOPLEVEL_ERROR_ENUM */ + +#ifndef XDG_TOPLEVEL_ERROR_ENUM_IS_VALID +#define XDG_TOPLEVEL_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_toplevel + * Validate a xdg_toplevel error value. + * + * @return true on success, false on error. + * @ref xdg_toplevel_error + */ +static inline bool +xdg_toplevel_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE: + return version >= 1; + case XDG_TOPLEVEL_ERROR_INVALID_PARENT: + return version >= 1; + case XDG_TOPLEVEL_ERROR_INVALID_SIZE: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_TOPLEVEL_ERROR_ENUM_IS_VALID */ + +#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM +#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM +/** + * @ingroup iface_xdg_toplevel + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. + */ +enum xdg_toplevel_resize_edge { + XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, + XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, + XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, + XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, +}; +#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ + +#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM_IS_VALID +#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM_IS_VALID +/** + * @ingroup iface_xdg_toplevel + * Validate a xdg_toplevel resize_edge value. + * + * @return true on success, false on error. + * @ref xdg_toplevel_resize_edge + */ +static inline bool +xdg_toplevel_resize_edge_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_TOPLEVEL_RESIZE_EDGE_NONE: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_TOP: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_LEFT: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT: + return version >= 1; + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM_IS_VALID */ + +#ifndef XDG_TOPLEVEL_STATE_ENUM +#define XDG_TOPLEVEL_STATE_ENUM +/** + * @ingroup iface_xdg_toplevel + * types of state on the surface + * + * The different state values used on the surface. This is designed for + * state values like maximized, fullscreen. It is paired with the + * configure event to ensure that both the client and the compositor + * setting the state can be synchronized. + * + * States set in this way are double-buffered. They will get applied on + * the next commit. + */ +enum xdg_toplevel_state { + /** + * the surface is maximized + * the surface is maximized + * + * The surface is maximized. The window geometry specified in the + * configure event must be obeyed by the client. + * + * The client should draw without shadow or other decoration + * outside of the window geometry. + */ + XDG_TOPLEVEL_STATE_MAXIMIZED = 1, + /** + * the surface is fullscreen + * the surface is fullscreen + * + * The surface is fullscreen. The window geometry specified in + * the configure event is a maximum; the client cannot resize + * beyond it. For a surface to cover the whole fullscreened area, + * the geometry dimensions must be obeyed by the client. For more + * details, see xdg_toplevel.set_fullscreen. + */ + XDG_TOPLEVEL_STATE_FULLSCREEN = 2, + /** + * the surface is being resized + * the surface is being resized + * + * The surface is being resized. The window geometry specified in + * the configure event is a maximum; the client cannot resize + * beyond it. Clients that have aspect ratio or cell sizing + * configuration can use a smaller size, however. + */ + XDG_TOPLEVEL_STATE_RESIZING = 3, + /** + * the surface is now activated + * the surface is now activated + * + * Client window decorations should be painted as if the window + * is active. Do not assume this means that the window actually has + * keyboard or pointer focus. + */ + XDG_TOPLEVEL_STATE_ACTIVATED = 4, + /** + * the surfaceโ€™s left edge is tiled + * + * The window is currently in a tiled layout and the left edge is + * considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_LEFT = 5, + /** + * the surfaceโ€™s right edge is tiled + * + * The window is currently in a tiled layout and the right edge + * is considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, + /** + * the surfaceโ€™s top edge is tiled + * + * The window is currently in a tiled layout and the top edge is + * considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_TOP = 7, + /** + * the surfaceโ€™s bottom edge is tiled + * + * The window is currently in a tiled layout and the bottom edge + * is considered to be adjacent to another part of the tiling grid. + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, +}; +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 +#endif /* XDG_TOPLEVEL_STATE_ENUM */ + +#ifndef XDG_TOPLEVEL_STATE_ENUM_IS_VALID +#define XDG_TOPLEVEL_STATE_ENUM_IS_VALID +/** + * @ingroup iface_xdg_toplevel + * Validate a xdg_toplevel state value. + * + * @return true on success, false on error. + * @ref xdg_toplevel_state + */ +static inline bool +xdg_toplevel_state_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_TOPLEVEL_STATE_MAXIMIZED: + return version >= 1; + case XDG_TOPLEVEL_STATE_FULLSCREEN: + return version >= 1; + case XDG_TOPLEVEL_STATE_RESIZING: + return version >= 1; + case XDG_TOPLEVEL_STATE_ACTIVATED: + return version >= 1; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + return version >= 2; + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + return version >= 2; + case XDG_TOPLEVEL_STATE_TILED_TOP: + return version >= 2; + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: + return version >= 2; + default: + return false; + } +} +#endif /* XDG_TOPLEVEL_STATE_ENUM_IS_VALID */ + +#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM +#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM +enum xdg_toplevel_wm_capabilities { + /** + * show_window_menu is available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1, + /** + * set_maximized and unset_maximized are available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 2, + /** + * set_fullscreen and unset_fullscreen are available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 3, + /** + * set_minimized is available + */ + XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 4, +}; +#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM */ + +#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM_IS_VALID +#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM_IS_VALID +/** + * @ingroup iface_xdg_toplevel + * Validate a xdg_toplevel wm_capabilities value. + * + * @return true on success, false on error. + * @ref xdg_toplevel_wm_capabilities + */ +static inline bool +xdg_toplevel_wm_capabilities_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: + return version >= 1; + case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: + return version >= 1; + case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: + return version >= 1; + case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM_IS_VALID */ + +/** + * @ingroup iface_xdg_toplevel + * @struct xdg_toplevel_interface + */ +struct xdg_toplevel_interface { + /** + * destroy the xdg_toplevel + * + * This request destroys the role surface and unmaps the surface; + * see "Unmapping" behavior in interface section for details. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the parent of this surface + * + * Set the "parent" of this surface. This surface should be + * stacked above the parent surface and all other ancestor + * surfaces. + * + * Parent surfaces should be set on dialogs, toolboxes, or other + * "auxiliary" surfaces, so that the parent is raised when the + * dialog is raised. + * + * Setting a null parent for a child surface unsets its parent. + * Setting a null parent for a surface which currently has no + * parent is a no-op. + * + * Only mapped surfaces can have child surfaces. Setting a parent + * which is not mapped is equivalent to setting a null parent. If a + * surface becomes unmapped, its children's parent is set to the + * parent of the now-unmapped surface. If the now-unmapped surface + * has no parent, its children's parent is unset. If the + * now-unmapped surface becomes mapped again, its parent-child + * relationship is not restored. + * + * The parent toplevel must not be one of the child toplevel's + * descendants, and the parent must be different from the child + * toplevel, otherwise the invalid_parent protocol error is raised. + */ + void (*set_parent)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *parent); + /** + * set surface title + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + */ + void (*set_title)(struct wl_client *client, + struct wl_resource *resource, + const char *title); + /** + * set application ID + * + * Set an application identifier for the surface. + * + * The app ID identifies the general class of applications to which + * the surface belongs. The compositor can use this to group + * multiple surfaces together, or to determine how to launch a new + * application. + * + * For D-Bus activatable applications, the app ID is used as the + * D-Bus service name. + * + * The compositor shell will try to group application surfaces + * together by their app ID. As a best practice, it is suggested to + * select app ID's that match the basename of the application's + * .desktop file. For example, "org.freedesktop.FooViewer" where + * the .desktop file is "org.freedesktop.FooViewer.desktop". + * + * Like other properties, a set_app_id request can be sent after + * the xdg_toplevel has been mapped to update the property. + * + * See the desktop-entry specification [0] for more details on + * application identifiers and how they relate to well-known D-Bus + * names and .desktop files. + * + * [0] https://standards.freedesktop.org/desktop-entry-spec/ + */ + void (*set_app_id)(struct wl_client *client, + struct wl_resource *resource, + const char *app_id); + /** + * show the window menu + * + * Clients implementing client-side decorations might want to + * show a context menu when right-clicking on the decorations, + * giving the user a menu that they can use to maximize or minimize + * the window. + * + * This request asks the compositor to pop up such a window menu at + * the given position, relative to the local surface coordinates of + * the parent surface. There are no guarantees as to what menu + * items the window menu contains, or even if a window menu will be + * drawn at all. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + * @param x the x position to pop up the window menu at + * @param y the y position to pop up the window menu at + */ + void (*show_window_menu)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial, + int32_t x, + int32_t y); + /** + * start an interactive move + * + * Start an interactive, user-driven move of the surface. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. The + * passed serial is used to determine the type of interactive move + * (touch, pointer, etc). + * + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized), or if the passed + * serial is no longer valid. + * + * If triggered, the surface will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the move. It is up to the + * compositor to visually indicate that the move is taking place, + * such as updating a pointer cursor, during the move. There is no + * guarantee that the device focus will return when the move is + * completed. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + */ + void (*move)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial); + /** + * start an interactive resize + * + * Start a user-driven, interactive resize of the surface. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. The + * passed serial is used to determine the type of interactive + * resize (touch, pointer, etc). + * + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * + * If triggered, the client will receive configure events with the + * "resize" state enum value and the expected sizes. See the + * "resize" enum value for more details about what is required. The + * client must also acknowledge configure events using + * "ack_configure". After the resize is completed, the client will + * receive another "configure" event without the resize state. + * + * If triggered, the surface also will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the resize. It is up to the + * compositor to visually indicate that the resize is taking place, + * such as updating a pointer cursor, during the resize. There is + * no guarantee that the device focus will return when the resize + * is completed. + * + * The edges parameter specifies how the surface should be resized, + * and is one of the values of the resize_edge enum. Values not + * matching a variant of the enum will cause a protocol error. The + * compositor may use this information to update the surface + * position for example when dragging the top left corner. The + * compositor may also use this information to adapt its behavior, + * e.g. choose an appropriate cursor image. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + * @param edges which edge or corner is being dragged + */ + void (*resize)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial, + uint32_t edges); + /** + * set the maximum size + * + * Set a maximum size for the window. + * + * The client can specify a maximum size so that the compositor + * does not try to configure the window beyond this size. + * + * The width and height arguments are in window geometry + * coordinates. See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get + * applied on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the maximum + * size. The compositor may decide to ignore the values set by the + * client and request a larger size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected maximum size in the given dimension. As a + * result, a client wishing to reset the maximum size to an + * unspecified state can use zero for width and height in the + * request. + * + * Requesting a maximum size to be smaller than the minimum size of + * a surface is illegal and will result in an invalid_size error. + * + * The width and height must be greater than or equal to zero. + * Using strictly negative values for width or height will result + * in a invalid_size error. + */ + void (*set_max_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); + /** + * set the minimum size + * + * Set a minimum size for the window. + * + * The client can specify a minimum size so that the compositor + * does not try to configure the window below this size. + * + * The width and height arguments are in window geometry + * coordinates. See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get + * applied on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the minimum + * size. The compositor may decide to ignore the values set by the + * client and request a smaller size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected minimum size in the given dimension. As a + * result, a client wishing to reset the minimum size to an + * unspecified state can use zero for width and height in the + * request. + * + * Requesting a minimum size to be larger than the maximum size of + * a surface is illegal and will result in an invalid_size error. + * + * The width and height must be greater than or equal to zero. + * Using strictly negative values for width and height will result + * in a invalid_size error. + */ + void (*set_min_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); + /** + * maximize the window + * + * Maximize the surface. + * + * After requesting that the surface should be maximized, the + * compositor will respond by emitting a configure event. Whether + * this configure actually sets the window maximized is subject to + * compositor policies. The client must then update its content, + * drawing in the configured state. The client must also + * acknowledge the configure when committing the new content (see + * ack_configure). + * + * It is up to the compositor to decide how and where to maximize + * the surface, for example which output and what region of the + * screen should be used. + * + * If the surface was already maximized, the compositor will still + * emit a configure event with the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no + * direct effect. It may alter the state the surface is returned to + * when unmaximized unless overridden by the compositor. + */ + void (*set_maximized)(struct wl_client *client, + struct wl_resource *resource); + /** + * unmaximize the window + * + * Unmaximize the surface. + * + * After requesting that the surface should be unmaximized, the + * compositor will respond by emitting a configure event. Whether + * this actually un-maximizes the window is subject to compositor + * policies. If available and applicable, the compositor will + * include the window geometry dimensions the window had prior to + * being maximized in the configure event. The client must then + * update its content, drawing it in the configured state. The + * client must also acknowledge the configure when committing the + * new content (see ack_configure). + * + * It is up to the compositor to position the surface after it was + * unmaximized; usually the position the surface had before + * maximizing, if applicable. + * + * If the surface was already not maximized, the compositor will + * still emit a configure event without the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no + * direct effect. It may alter the state the surface is returned to + * when unmaximized unless overridden by the compositor. + */ + void (*unset_maximized)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the window as fullscreen on an output + * + * Make the surface fullscreen. + * + * After requesting that the surface should be fullscreened, the + * compositor will respond by emitting a configure event. Whether + * the client is actually put into a fullscreen state is subject to + * compositor policies. The client must also acknowledge the + * configure when committing the new content (see ack_configure). + * + * The output passed by the request indicates the client's + * preference as to which display it should be set fullscreen on. + * If this value is NULL, it's up to the compositor to choose which + * display will be used to map this surface. + * + * If the surface doesn't cover the whole output, the compositor + * will position the surface in the center of the output and + * compensate with with border fill covering the rest of the + * output. The content of the border fill is undefined, but should + * be assumed to be in some way that attempts to blend into the + * surrounding area (e.g. solid black). + * + * If the fullscreened surface is not opaque, the compositor must + * make sure that other screen content not part of the same surface + * tree (made up of subsurfaces, popups or similarly coupled + * surfaces) are not visible below the fullscreened surface. + */ + void (*set_fullscreen)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output); + /** + * unset the window as fullscreen + * + * Make the surface no longer fullscreen. + * + * After requesting that the surface should be unfullscreened, the + * compositor will respond by emitting a configure event. Whether + * this actually removes the fullscreen state of the client is + * subject to compositor policies. + * + * Making a surface unfullscreen sets states for the surface based + * on the following: * the state(s) it may have had before becoming + * fullscreen * any state(s) decided by the compositor * any + * state(s) requested by the client while the surface was + * fullscreen + * + * The compositor may include the previous window geometry + * dimensions in the configure event, if applicable. + * + * The client must also acknowledge the configure when committing + * the new content (see ack_configure). + */ + void (*unset_fullscreen)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the window as minimized + * + * Request that the compositor minimize your surface. There is no + * way to know if the surface is currently minimized, nor is there + * any way to unset minimization on this surface. + * + * If you are looking to throttle redrawing when minimized, please + * instead use the wl_surface.frame event for this, as this will + * also work with live previews on windows in Alt-Tab, Expose or + * similar compositor features. + */ + void (*set_minimized)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define XDG_TOPLEVEL_CONFIGURE 0 +#define XDG_TOPLEVEL_CLOSE 1 +#define XDG_TOPLEVEL_CONFIGURE_BOUNDS 2 +#define XDG_TOPLEVEL_WM_CAPABILITIES 3 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION 4 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION 5 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_toplevel + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_toplevel_send_configure(struct wl_resource *resource_, int32_t width, int32_t height, struct wl_array *states) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_CONFIGURE, width, height, states); +} + +/** + * @ingroup iface_xdg_toplevel + * Sends an close event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_toplevel_send_close(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_CLOSE); +} + +/** + * @ingroup iface_xdg_toplevel + * Sends an configure_bounds event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_toplevel_send_configure_bounds(struct wl_resource *resource_, int32_t width, int32_t height) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_CONFIGURE_BOUNDS, width, height); +} + +/** + * @ingroup iface_xdg_toplevel + * Sends an wm_capabilities event to the client owning the resource. + * @param resource_ The client's resource + * @param capabilities array of 32-bit capabilities + */ +static inline void +xdg_toplevel_send_wm_capabilities(struct wl_resource *resource_, struct wl_array *capabilities) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_WM_CAPABILITIES, capabilities); +} + +#ifndef XDG_POPUP_ERROR_ENUM +#define XDG_POPUP_ERROR_ENUM +enum xdg_popup_error { + /** + * tried to grab after being mapped + */ + XDG_POPUP_ERROR_INVALID_GRAB = 0, +}; +#endif /* XDG_POPUP_ERROR_ENUM */ + +#ifndef XDG_POPUP_ERROR_ENUM_IS_VALID +#define XDG_POPUP_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_popup + * Validate a xdg_popup error value. + * + * @return true on success, false on error. + * @ref xdg_popup_error + */ +static inline bool +xdg_popup_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_POPUP_ERROR_INVALID_GRAB: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_POPUP_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_xdg_popup + * @struct xdg_popup_interface + */ +struct xdg_popup_interface { + /** + * remove xdg_popup interface + * + * This destroys the popup. Explicitly destroying the xdg_popup + * object will also dismiss the popup, and unmap the surface. + * + * If this xdg_popup is not the "topmost" popup, a protocol error + * will be sent. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * make the popup take an explicit grab + * + * This request makes the created popup take an explicit grab. An + * explicit grab will be dismissed when the user dismisses the + * popup, or when the client destroys the xdg_popup. This can be + * done by the user clicking outside the surface, using the + * keyboard, or even locking the screen through closing the lid or + * a timeout. + * + * If the compositor denies the grab, the popup will be immediately + * dismissed. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. The + * serial number of the event should be passed as 'serial'. + * + * The parent of a grabbing popup must either be an xdg_toplevel + * surface or another xdg_popup with an explicit grab. If the + * parent is another xdg_popup it means that the popups are nested, + * with this popup now being the topmost popup. + * + * Nested popups must be destroyed in the reverse order they were + * created in, e.g. the only popup you are allowed to destroy at + * all times is the topmost one. + * + * When compositors choose to dismiss a popup, they may dismiss + * every nested grabbing popup as well. When a compositor dismisses + * popups, it will follow the same dismissing order as required + * from the client. + * + * If the topmost grabbing popup is destroyed, the grab will be + * returned to the parent of the popup, if that parent previously + * had an explicit grab. + * + * If the parent is a grabbing popup which has already been + * dismissed, this popup will be immediately dismissed. If the + * parent is a popup that did not take an explicit grab, an error + * will be raised. + * + * During a popup grab, the client owning the grab will receive + * pointer and touch events for all their surfaces as normal + * (similar to an "owner-events" grab in X11 parlance), while the + * top most grabbing popup will always have keyboard focus. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + */ + void (*grab)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial); + /** + * recalculate the popup's location + * + * Reposition an already-mapped popup. The popup will be placed + * given the details in the passed xdg_positioner object, and a + * xdg_popup.repositioned followed by xdg_popup.configure and + * xdg_surface.configure will be emitted in response. Any + * parameters set by the previous positioner will be discarded. + * + * The passed token will be sent in the corresponding + * xdg_popup.repositioned event. The new popup position will not + * take effect until the corresponding configure event is + * acknowledged by the client. See xdg_popup.repositioned for + * details. The token itself is opaque, and has no other special + * meaning. + * + * If multiple reposition requests are sent, the compositor may + * skip all but the last one. + * + * If the popup is repositioned in response to a configure event + * for its parent, the client should send an + * xdg_positioner.set_parent_configure and possibly an + * xdg_positioner.set_parent_size request to allow the compositor + * to properly constrain the popup. + * + * If the popup is repositioned together with a parent that is + * being resized, but not in response to a configure event, the + * client should send an xdg_positioner.set_parent_size request. + * @param token reposition request token + * @since 3 + */ + void (*reposition)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *positioner, + uint32_t token); +}; + +#define XDG_POPUP_CONFIGURE 0 +#define XDG_POPUP_POPUP_DONE 1 +#define XDG_POPUP_REPOSITIONED 2 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_GRAB_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + * @param x x position relative to parent surface window geometry + * @param y y position relative to parent surface window geometry + * @param width window geometry width + * @param height window geometry height + */ +static inline void +xdg_popup_send_configure(struct wl_resource *resource_, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_resource_post_event(resource_, XDG_POPUP_CONFIGURE, x, y, width, height); +} + +/** + * @ingroup iface_xdg_popup + * Sends an popup_done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_popup_send_popup_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, XDG_POPUP_POPUP_DONE); +} + +/** + * @ingroup iface_xdg_popup + * Sends an repositioned event to the client owning the resource. + * @param resource_ The client's resource + * @param token reposition request token + */ +static inline void +xdg_popup_send_repositioned(struct wl_resource *resource_, uint32_t token) +{ + wl_resource_post_event(resource_, XDG_POPUP_REPOSITIONED, token); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/generated/xdg-toplevel-icon-v1-protocol.c b/app/src/main/cpp/waylandcomp/generated/xdg-toplevel-icon-v1-protocol.c new file mode 100644 index 000000000..73d39d207 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/xdg-toplevel-icon-v1-protocol.c @@ -0,0 +1,83 @@ +/* Generated by wayland-scanner 1.24.0 */ + +/* + * Copyright ยฉ 2023-2024 Matthias Klumpp + * Copyright ยฉ 2024 David Edmundson + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_buffer_interface; +extern const struct wl_interface xdg_toplevel_interface; +extern const struct wl_interface xdg_toplevel_icon_v1_interface; + +static const struct wl_interface *xdg_toplevel_icon_v1_types[] = { + NULL, + &xdg_toplevel_icon_v1_interface, + &xdg_toplevel_interface, + &xdg_toplevel_icon_v1_interface, + &wl_buffer_interface, + NULL, +}; + +static const struct wl_message xdg_toplevel_icon_manager_v1_requests[] = { + { "destroy", "", xdg_toplevel_icon_v1_types + 0 }, + { "create_icon", "n", xdg_toplevel_icon_v1_types + 1 }, + { "set_icon", "o?o", xdg_toplevel_icon_v1_types + 2 }, +}; + +static const struct wl_message xdg_toplevel_icon_manager_v1_events[] = { + { "icon_size", "i", xdg_toplevel_icon_v1_types + 0 }, + { "done", "", xdg_toplevel_icon_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_toplevel_icon_manager_v1_interface = { + "xdg_toplevel_icon_manager_v1", 1, + 3, xdg_toplevel_icon_manager_v1_requests, + 2, xdg_toplevel_icon_manager_v1_events, +}; + +static const struct wl_message xdg_toplevel_icon_v1_requests[] = { + { "destroy", "", xdg_toplevel_icon_v1_types + 0 }, + { "set_name", "s", xdg_toplevel_icon_v1_types + 0 }, + { "add_buffer", "oi", xdg_toplevel_icon_v1_types + 4 }, +}; + +WL_PRIVATE const struct wl_interface xdg_toplevel_icon_v1_interface = { + "xdg_toplevel_icon_v1", 1, + 3, xdg_toplevel_icon_v1_requests, + 0, NULL, +}; + diff --git a/app/src/main/cpp/waylandcomp/generated/xdg-toplevel-icon-v1-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/xdg-toplevel-icon-v1-server-protocol.h new file mode 100644 index 000000000..b186d8897 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/xdg-toplevel-icon-v1-server-protocol.h @@ -0,0 +1,362 @@ +/* Generated by wayland-scanner 1.24.0 */ + +#ifndef XDG_TOPLEVEL_ICON_V1_SERVER_PROTOCOL_H +#define XDG_TOPLEVEL_ICON_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_xdg_toplevel_icon_v1 The xdg_toplevel_icon_v1 protocol + * protocol to assign icons to toplevels + * + * @section page_desc_xdg_toplevel_icon_v1 Description + * + * This protocol allows clients to set icons for their toplevel surfaces + * either via the XDG icon stock (using an icon name), or from pixel data. + * + * A toplevel icon represents the individual toplevel (unlike the application + * or launcher icon, which represents the application as a whole), and may be + * shown in window switchers, window overviews and taskbars that list + * individual windows. + * + * This document adheres to RFC 2119 when using words like "must", + * "should", "may", etc. + * + * Warning! The protocol described in this file is currently in the testing + * phase. Backward compatible changes may be added together with the + * corresponding interface version bump. Backward incompatible changes can + * only be done by creating a new major version of the extension. + * + * @section page_ifaces_xdg_toplevel_icon_v1 Interfaces + * - @subpage page_iface_xdg_toplevel_icon_manager_v1 - interface to manage toplevel icons + * - @subpage page_iface_xdg_toplevel_icon_v1 - a toplevel window icon + * @section page_copyright_xdg_toplevel_icon_v1 Copyright + *
+ *
+ * Copyright ยฉ 2023-2024 Matthias Klumpp
+ * Copyright ยฉ      2024 David Edmundson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_buffer; +struct xdg_toplevel; +struct xdg_toplevel_icon_manager_v1; +struct xdg_toplevel_icon_v1; + +#ifndef XDG_TOPLEVEL_ICON_MANAGER_V1_INTERFACE +#define XDG_TOPLEVEL_ICON_MANAGER_V1_INTERFACE +/** + * @page page_iface_xdg_toplevel_icon_manager_v1 xdg_toplevel_icon_manager_v1 + * @section page_iface_xdg_toplevel_icon_manager_v1_desc Description + * + * This interface allows clients to create toplevel window icons and set + * them on toplevel windows to be displayed to the user. + * @section page_iface_xdg_toplevel_icon_manager_v1_api API + * See @ref iface_xdg_toplevel_icon_manager_v1. + */ +/** + * @defgroup iface_xdg_toplevel_icon_manager_v1 The xdg_toplevel_icon_manager_v1 interface + * + * This interface allows clients to create toplevel window icons and set + * them on toplevel windows to be displayed to the user. + */ +extern const struct wl_interface xdg_toplevel_icon_manager_v1_interface; +#endif +#ifndef XDG_TOPLEVEL_ICON_V1_INTERFACE +#define XDG_TOPLEVEL_ICON_V1_INTERFACE +/** + * @page page_iface_xdg_toplevel_icon_v1 xdg_toplevel_icon_v1 + * @section page_iface_xdg_toplevel_icon_v1_desc Description + * + * This interface defines a toplevel icon. + * An icon can have a name, and multiple buffers. + * In order to be applied, the icon must have either a name, or at least + * one buffer assigned. Applying an empty icon (with no buffer or name) to + * a toplevel should reset its icon to the default icon. + * + * It is up to compositor policy whether to prefer using a buffer or loading + * an icon via its name. See 'set_name' and 'add_buffer' for details. + * @section page_iface_xdg_toplevel_icon_v1_api API + * See @ref iface_xdg_toplevel_icon_v1. + */ +/** + * @defgroup iface_xdg_toplevel_icon_v1 The xdg_toplevel_icon_v1 interface + * + * This interface defines a toplevel icon. + * An icon can have a name, and multiple buffers. + * In order to be applied, the icon must have either a name, or at least + * one buffer assigned. Applying an empty icon (with no buffer or name) to + * a toplevel should reset its icon to the default icon. + * + * It is up to compositor policy whether to prefer using a buffer or loading + * an icon via its name. See 'set_name' and 'add_buffer' for details. + */ +extern const struct wl_interface xdg_toplevel_icon_v1_interface; +#endif + +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + * @struct xdg_toplevel_icon_manager_v1_interface + */ +struct xdg_toplevel_icon_manager_v1_interface { + /** + * destroy the toplevel icon manager + * + * Destroy the toplevel icon manager. This does not destroy + * objects created with the manager. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * create a new icon instance + * + * Creates a new icon object. This icon can then be attached to a + * xdg_toplevel via the 'set_icon' request. + */ + void (*create_icon)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * set an icon on a toplevel window + * + * This request assigns the icon 'icon' to 'toplevel', or clears + * the toplevel icon if 'icon' was null. This state is + * double-buffered and is applied on the next wl_surface.commit of + * the toplevel. + * + * After making this call, the xdg_toplevel_icon_v1 provided as + * 'icon' can be destroyed by the client without 'toplevel' losing + * its icon. The xdg_toplevel_icon_v1 is immutable from this point, + * and any future attempts to change it must raise the + * 'xdg_toplevel_icon_v1.immutable' protocol error. + * + * The compositor must set the toplevel icon from either the pixel + * data the icon provides, or by loading a stock icon using the + * icon name. See the description of 'xdg_toplevel_icon_v1' for + * details. + * + * If 'icon' is set to null, the icon of the respective toplevel is + * reset to its default icon (usually the icon of the application, + * derived from its desktop-entry file, or a placeholder icon). If + * this request is passed an icon with no pixel buffers or icon + * name assigned, the icon must be reset just like if 'icon' was + * null. + * @param toplevel the toplevel to act on + */ + void (*set_icon)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *toplevel, + struct wl_resource *icon); +}; + +#define XDG_TOPLEVEL_ICON_MANAGER_V1_ICON_SIZE 0 +#define XDG_TOPLEVEL_ICON_MANAGER_V1_DONE 1 + +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + */ +#define XDG_TOPLEVEL_ICON_MANAGER_V1_ICON_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + */ +#define XDG_TOPLEVEL_ICON_MANAGER_V1_DONE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + */ +#define XDG_TOPLEVEL_ICON_MANAGER_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + */ +#define XDG_TOPLEVEL_ICON_MANAGER_V1_CREATE_ICON_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + */ +#define XDG_TOPLEVEL_ICON_MANAGER_V1_SET_ICON_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + * Sends an icon_size event to the client owning the resource. + * @param resource_ The client's resource + * @param size the edge size of the square icon in surface-local coordinates, e.g. 64 + */ +static inline void +xdg_toplevel_icon_manager_v1_send_icon_size(struct wl_resource *resource_, int32_t size) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_ICON_MANAGER_V1_ICON_SIZE, size); +} + +/** + * @ingroup iface_xdg_toplevel_icon_manager_v1 + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_toplevel_icon_manager_v1_send_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_ICON_MANAGER_V1_DONE); +} + +#ifndef XDG_TOPLEVEL_ICON_V1_ERROR_ENUM +#define XDG_TOPLEVEL_ICON_V1_ERROR_ENUM +enum xdg_toplevel_icon_v1_error { + /** + * the provided buffer does not satisfy requirements + */ + XDG_TOPLEVEL_ICON_V1_ERROR_INVALID_BUFFER = 1, + /** + * the icon has already been assigned to a toplevel and must not be changed + */ + XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE = 2, + /** + * the provided buffer has been destroyed before the toplevel icon + */ + XDG_TOPLEVEL_ICON_V1_ERROR_NO_BUFFER = 3, +}; +#endif /* XDG_TOPLEVEL_ICON_V1_ERROR_ENUM */ + +#ifndef XDG_TOPLEVEL_ICON_V1_ERROR_ENUM_IS_VALID +#define XDG_TOPLEVEL_ICON_V1_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_xdg_toplevel_icon_v1 + * Validate a xdg_toplevel_icon_v1 error value. + * + * @return true on success, false on error. + * @ref xdg_toplevel_icon_v1_error + */ +static inline bool +xdg_toplevel_icon_v1_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case XDG_TOPLEVEL_ICON_V1_ERROR_INVALID_BUFFER: + return version >= 1; + case XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE: + return version >= 1; + case XDG_TOPLEVEL_ICON_V1_ERROR_NO_BUFFER: + return version >= 1; + default: + return false; + } +} +#endif /* XDG_TOPLEVEL_ICON_V1_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_xdg_toplevel_icon_v1 + * @struct xdg_toplevel_icon_v1_interface + */ +struct xdg_toplevel_icon_v1_interface { + /** + * destroy the icon object + * + * Destroys the 'xdg_toplevel_icon_v1' object. The icon must + * still remain set on every toplevel it was assigned to, until the + * toplevel icon is reset explicitly. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set an icon name + * + * This request assigns an icon name to this icon. Any previously + * set name is overridden. + * + * The compositor must resolve 'icon_name' according to the lookup + * rules described in the XDG icon theme specification[1] using the + * environment's current icon theme. + * + * If the compositor does not support icon names or cannot resolve + * 'icon_name' according to the XDG icon theme specification it + * must fall back to using pixel buffer data instead. + * + * If this request is made after the icon has been assigned to a + * toplevel via 'set_icon', an 'immutable' error must be raised. + * + * [1]: + * https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ + void (*set_name)(struct wl_client *client, + struct wl_resource *resource, + const char *icon_name); + /** + * add icon data from a pixel buffer + * + * This request adds pixel data supplied as wl_buffer to the + * icon. + * + * The client should add pixel data for all icon sizes and scales + * that it can provide, or which are explicitly requested by the + * compositor via 'icon_size' events on + * xdg_toplevel_icon_manager_v1. + * + * The wl_buffer supplying pixel data as 'buffer' must be backed by + * wl_shm and must be a square (width and height being equal). If + * any of these buffer requirements are not fulfilled, a + * 'invalid_buffer' error must be raised. + * + * If this icon instance already has a buffer of the same size and + * scale from a previous 'add_buffer' request, data from the last + * request overrides the preexisting pixel data. + * + * The wl_buffer must be kept alive for as long as the + * xdg_toplevel_icon it is associated with is not destroyed, + * otherwise a 'no_buffer' error is raised. The buffer contents + * must not be modified after it was assigned to the icon. As a + * result, the region of the wl_shm_pool's backing storage used for + * the wl_buffer must not be modified after this request is sent. + * The wl_buffer.release event is unused. + * + * If this request is made after the icon has been assigned to a + * toplevel via 'set_icon', an 'immutable' error must be raised. + * @param scale the scaling factor of the icon, e.g. 1 + */ + void (*add_buffer)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer, + int32_t scale); +}; + + +/** + * @ingroup iface_xdg_toplevel_icon_v1 + */ +#define XDG_TOPLEVEL_ICON_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel_icon_v1 + */ +#define XDG_TOPLEVEL_ICON_V1_SET_NAME_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel_icon_v1 + */ +#define XDG_TOPLEVEL_ICON_V1_ADD_BUFFER_SINCE_VERSION 1 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server-core.h b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server-core.h new file mode 100644 index 000000000..757f6a1f1 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server-core.h @@ -0,0 +1,747 @@ +/* + * Copyright ยฉ 2008 Kristian Hรธgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WAYLAND_SERVER_CORE_H +#define WAYLAND_SERVER_CORE_H + +#include +#include +#include +#include "wayland-util.h" +#include "wayland-version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + WL_EVENT_READABLE = 0x01, + WL_EVENT_WRITABLE = 0x02, + WL_EVENT_HANGUP = 0x04, + WL_EVENT_ERROR = 0x08 +}; + +/** File descriptor dispatch function type + * + * Functions of this type are used as callbacks for file descriptor events. + * + * \param fd The file descriptor delivering the event. + * \param mask Describes the kind of the event as a bitwise-or of: + * \c WL_EVENT_READABLE, \c WL_EVENT_WRITABLE, \c WL_EVENT_HANGUP, + * \c WL_EVENT_ERROR. + * \param data The user data argument of the related wl_event_loop_add_fd() + * call. + * \return If the event source is registered for re-check with + * wl_event_source_check(): 0 for all done, 1 for needing a re-check. + * If not registered, the return value is ignored and should be zero. + * + * \sa wl_event_loop_add_fd() + * \memberof wl_event_source + */ +typedef int (*wl_event_loop_fd_func_t)(int fd, uint32_t mask, void *data); + +/** Timer dispatch function type + * + * Functions of this type are used as callbacks for timer expiry. + * + * \param data The user data argument of the related wl_event_loop_add_timer() + * call. + * \return If the event source is registered for re-check with + * wl_event_source_check(): 0 for all done, 1 for needing a re-check. + * If not registered, the return value is ignored and should be zero. + * + * \sa wl_event_loop_add_timer() + * \memberof wl_event_source + */ +typedef int (*wl_event_loop_timer_func_t)(void *data); + +/** Signal dispatch function type + * + * Functions of this type are used as callbacks for (POSIX) signals. + * + * \param signal_number + * \param data The user data argument of the related wl_event_loop_add_signal() + * call. + * \return If the event source is registered for re-check with + * wl_event_source_check(): 0 for all done, 1 for needing a re-check. + * If not registered, the return value is ignored and should be zero. + * + * \sa wl_event_loop_add_signal() + * \memberof wl_event_source + */ +typedef int (*wl_event_loop_signal_func_t)(int signal_number, void *data); + +/** Idle task function type + * + * Functions of this type are used as callbacks before blocking in + * wl_event_loop_dispatch(). + * + * \param data The user data argument of the related wl_event_loop_add_idle() + * call. + * + * \sa wl_event_loop_add_idle() wl_event_loop_dispatch() + * \memberof wl_event_source + */ +typedef void (*wl_event_loop_idle_func_t)(void *data); + +/** \struct wl_event_loop + * + * \brief An event loop context + * + * Usually you create an event loop context, add sources to it, and call + * wl_event_loop_dispatch() in a loop to process events. + * + * \sa wl_event_source + */ + +/** \struct wl_event_source + * + * \brief An abstract event source + * + * This is the generic type for fd, timer, signal, and idle sources. + * Functions that operate on specific source types must not be used with + * a different type, even if the function signature allows it. + */ + +struct wl_event_loop * +wl_event_loop_create(void); + +void +wl_event_loop_destroy(struct wl_event_loop *loop); + +struct wl_event_source * +wl_event_loop_add_fd(struct wl_event_loop *loop, + int fd, uint32_t mask, + wl_event_loop_fd_func_t func, + void *data); + +int +wl_event_source_fd_update(struct wl_event_source *source, uint32_t mask); + +struct wl_event_source * +wl_event_loop_add_timer(struct wl_event_loop *loop, + wl_event_loop_timer_func_t func, + void *data); + +struct wl_event_source * +wl_event_loop_add_signal(struct wl_event_loop *loop, + int signal_number, + wl_event_loop_signal_func_t func, + void *data); + +int +wl_event_source_timer_update(struct wl_event_source *source, + int ms_delay); + +int +wl_event_source_remove(struct wl_event_source *source); + +void +wl_event_source_check(struct wl_event_source *source); + +int +wl_event_loop_dispatch(struct wl_event_loop *loop, int timeout); + +void +wl_event_loop_dispatch_idle(struct wl_event_loop *loop); + +struct wl_event_source * +wl_event_loop_add_idle(struct wl_event_loop *loop, + wl_event_loop_idle_func_t func, + void *data); + +int +wl_event_loop_get_fd(struct wl_event_loop *loop); + +struct wl_listener; + +typedef void (*wl_notify_func_t)(struct wl_listener *listener, void *data); + +void +wl_event_loop_add_destroy_listener(struct wl_event_loop *loop, + struct wl_listener *listener); + +struct wl_listener * +wl_event_loop_get_destroy_listener(struct wl_event_loop *loop, + wl_notify_func_t notify); + +struct wl_display * +wl_display_create(void); + +void +wl_display_destroy(struct wl_display *display); + +struct wl_event_loop * +wl_display_get_event_loop(struct wl_display *display); + +int +wl_display_add_socket(struct wl_display *display, const char *name); + +const char * +wl_display_add_socket_auto(struct wl_display *display); + +int +wl_display_add_socket_fd(struct wl_display *display, int sock_fd); + +int +wl_display_remove_socket_fd(struct wl_display *display, int sock_fd); + +void +wl_display_terminate(struct wl_display *display); + +void +wl_display_run(struct wl_display *display); + +void +wl_display_flush_clients(struct wl_display *display); + +void +wl_display_destroy_clients(struct wl_display *display); + +void +wl_display_set_default_max_buffer_size(struct wl_display *display, + size_t max_buffer_size); + +struct wl_client; +struct wl_global; + +typedef void (*wl_global_bind_func_t)(struct wl_client *client, void *data, + uint32_t version, uint32_t id); + +typedef void (*wl_global_withdrawn_func_t)(struct wl_global *global); + +uint32_t +wl_display_get_serial(const struct wl_display *display); + +uint32_t +wl_display_next_serial(struct wl_display *display); + +void +wl_display_add_destroy_listener(struct wl_display *display, + struct wl_listener *listener); + +void +wl_display_add_client_created_listener(struct wl_display *display, + struct wl_listener *listener); + +struct wl_listener * +wl_display_get_destroy_listener(struct wl_display *display, + wl_notify_func_t notify); + +struct wl_global * +wl_global_create(struct wl_display *display, + const struct wl_interface *interface, + int version, + void *data, wl_global_bind_func_t bind); + +void +wl_global_remove(struct wl_global *global); + +void +wl_global_set_withdrawn_listener(struct wl_global *global, + wl_global_withdrawn_func_t func); + +void +wl_global_destroy(struct wl_global *global); + +/** A filter function for wl_global objects + * + * \param client The client object + * \param global The global object to show or hide + * \param data The user data pointer + * + * A filter function enables the server to decide which globals to + * advertise to each client. + * + * When a wl_global filter is set, the given callback function will be + * called during wl_global advertisement and binding. + * + * This function should return true if the global object should be made + * visible to the client or false otherwise. + */ +typedef bool (*wl_display_global_filter_func_t)(const struct wl_client *client, + const struct wl_global *global, + void *data); + +void +wl_display_set_global_filter(struct wl_display *display, + wl_display_global_filter_func_t filter, + void *data); + +const struct wl_interface * +wl_global_get_interface(const struct wl_global *global); + +uint32_t +wl_global_get_name(const struct wl_global *global, + const struct wl_client *client); + +uint32_t +wl_global_get_version(const struct wl_global *global); + +struct wl_display * +wl_global_get_display(const struct wl_global *global); + +void * +wl_global_get_user_data(const struct wl_global *global); + +void +wl_global_set_user_data(struct wl_global *global, void *data); + +struct wl_client * +wl_client_create(struct wl_display *display, int fd); + +struct wl_list * +wl_display_get_client_list(struct wl_display *display); + +struct wl_list * +wl_client_get_link(struct wl_client *client); + +struct wl_client * +wl_client_from_link(struct wl_list *link); + +/** Iterate over a list of clients. */ +#define wl_client_for_each(client, list) \ + for (client = wl_client_from_link((list)->next); \ + wl_client_get_link(client) != (list); \ + client = wl_client_from_link(wl_client_get_link(client)->next)) + +void +wl_client_destroy(struct wl_client *client); + +void +wl_client_flush(struct wl_client *client); + +void +wl_client_get_credentials(const struct wl_client *client, + pid_t *pid, uid_t *uid, gid_t *gid); + +int +wl_client_get_fd(struct wl_client *client); + +void +wl_client_add_destroy_listener(struct wl_client *client, + struct wl_listener *listener); + +struct wl_listener * +wl_client_get_destroy_listener(struct wl_client *client, + wl_notify_func_t notify); + +void +wl_client_add_destroy_late_listener(struct wl_client *client, + struct wl_listener *listener); + +struct wl_listener * +wl_client_get_destroy_late_listener(struct wl_client *client, + wl_notify_func_t notify); + +struct wl_resource * +wl_client_get_object(struct wl_client *client, uint32_t id); + +void +wl_client_post_no_memory(struct wl_client *client); + +void +wl_client_post_implementation_error(struct wl_client *client, + const char* msg, ...) WL_PRINTF(2,3); + +void +wl_client_add_resource_created_listener(struct wl_client *client, + struct wl_listener *listener); + +/** Callback function type for wl_client_for_each_resource() */ +typedef enum wl_iterator_result (*wl_client_for_each_resource_iterator_func_t)( + struct wl_resource *resource, + void *user_data); + +void +wl_client_for_each_resource(struct wl_client *client, + wl_client_for_each_resource_iterator_func_t iterator, + void *user_data); + +typedef void (*wl_user_data_destroy_func_t)(void *data); + +void +wl_client_set_user_data(struct wl_client *client, + void *data, + wl_user_data_destroy_func_t dtor); + +void * +wl_client_get_user_data(struct wl_client *client); + +void +wl_client_set_max_buffer_size(struct wl_client *client, size_t max_buffer_size); + +/** \class wl_listener + * + * \brief A single listener for Wayland signals + * + * wl_listener provides the means to listen for wl_signal notifications. Many + * Wayland objects use wl_listener for notification of significant events like + * object destruction. + * + * Clients should create wl_listener objects manually and can register them as + * listeners to signals using wl_signal_add(), assuming the signal is + * directly accessible. For opaque structs like wl_event_loop, adding a + * listener should be done through provided accessor methods. A listener can + * only listen to one signal at a time. + * + * \code + * struct wl_listener your_listener; + * + * your_listener.notify = your_callback_method; + * + * // Direct access + * wl_signal_add(&some_object->destroy_signal, &your_listener); + * + * // Accessor access + * wl_event_loop *loop = ...; + * wl_event_loop_add_destroy_listener(loop, &your_listener); + * \endcode + * + * If the listener is part of a larger struct, #wl_container_of can be used + * to retrieve a pointer to it: + * + * \code + * void your_listener(struct wl_listener *listener, void *data) + * { + * struct your_data *data; + * + * your_data = wl_container_of(listener, data, your_member_name); + * } + * \endcode + * + * If you need to remove a listener from a signal, use wl_list_remove(). + * + * \code + * wl_list_remove(&your_listener.link); + * \endcode + * + * \sa wl_signal + */ +struct wl_listener { + /** Part of wl_signal::listener_list */ + struct wl_list link; + + /** Callback function pointer */ + wl_notify_func_t notify; +}; + +/** \class wl_signal + * + * \brief A source of a type of observable event + * + * Signals are recognized points where significant events can be observed. + * Compositors as well as the server can provide signals. Observers are + * wl_listener's that are added through #wl_signal_add. Signals are emitted + * using #wl_signal_emit, which will invoke all listeners until that + * listener is removed by wl_list_remove() (or whenever the signal is + * destroyed). + * + * \sa wl_listener for more information on using wl_signal + */ +struct wl_signal { + struct wl_list listener_list; +}; + +/** Initialize a new \ref wl_signal for use. + * + * \param signal The signal that will be initialized + * + * \memberof wl_signal + */ +static inline void +wl_signal_init(struct wl_signal *signal) +{ + wl_list_init(&signal->listener_list); +} + +/** Add the specified listener to this signal. + * + * \param signal The signal that will emit events to the listener + * \param listener The listener to add + * + * \memberof wl_signal + */ +static inline void +wl_signal_add(struct wl_signal *signal, struct wl_listener *listener) +{ + wl_list_insert(signal->listener_list.prev, &listener->link); +} + +/** Gets the listener struct for the specified callback. + * + * \param signal The signal that contains the specified listener + * \param notify The listener that is the target of this search + * \return the list item that corresponds to the specified listener, or NULL + * if none was found + * + * \memberof wl_signal + */ +static inline struct wl_listener * +wl_signal_get(struct wl_signal *signal, wl_notify_func_t notify) +{ + struct wl_listener *l; + + wl_list_for_each(l, &signal->listener_list, link) + if (l->notify == notify) + return l; + + return NULL; +} + +/** Emits this signal, notifying all registered listeners. + * + * \param signal The signal object that will emit the signal + * \param data The data that will be emitted with the signal + * + * \memberof wl_signal + */ +static inline void +wl_signal_emit(struct wl_signal *signal, void *data) +{ + struct wl_listener *l, *next; + + wl_list_for_each_safe(l, next, &signal->listener_list, link) + l->notify(l, data); +} + +void +wl_signal_emit_mutable(struct wl_signal *signal, void *data); + +typedef void (*wl_resource_destroy_func_t)(struct wl_resource *resource); + +/* + * Post an event to the client's object referred to by 'resource'. + * 'opcode' is the event number generated from the protocol XML + * description (the event name). The variable arguments are the event + * parameters, in the order they appear in the protocol XML specification. + * + * The variable arguments' types are: + * - type=uint: uint32_t + * - type=int: int32_t + * - type=fixed: wl_fixed_t + * - type=string: (const char *) to a nil-terminated string + * - type=array: (struct wl_array *) + * - type=fd: int, that is an open file descriptor + * - type=new_id: (struct wl_object *) or (struct wl_resource *) + * - type=object: (struct wl_object *) or (struct wl_resource *) + */ +void +wl_resource_post_event(struct wl_resource *resource, + uint32_t opcode, ...); + +void +wl_resource_post_event_array(struct wl_resource *resource, + uint32_t opcode, union wl_argument *args); + +void +wl_resource_queue_event(struct wl_resource *resource, + uint32_t opcode, ...); + +void +wl_resource_queue_event_array(struct wl_resource *resource, + uint32_t opcode, union wl_argument *args); + +void +wl_resource_post_error_vargs(struct wl_resource *resource, + uint32_t code, const char *msg, va_list argp); + +void +wl_resource_post_error(struct wl_resource *resource, + uint32_t code, const char *msg, ...) WL_PRINTF(3, 4); + +void +wl_resource_post_no_memory(struct wl_resource *resource); + +struct wl_display * +wl_client_get_display(struct wl_client *client); + +struct wl_resource * +wl_resource_create(struct wl_client *client, + const struct wl_interface *interface, + int version, uint32_t id); + +void +wl_resource_set_implementation(struct wl_resource *resource, + const void *implementation, + void *data, + wl_resource_destroy_func_t destroy); + +void +wl_resource_set_dispatcher(struct wl_resource *resource, + wl_dispatcher_func_t dispatcher, + const void *implementation, + void *data, + wl_resource_destroy_func_t destroy); + +void +wl_resource_destroy(struct wl_resource *resource); + +uint32_t +wl_resource_get_id(const struct wl_resource *resource); + +struct wl_list * +wl_resource_get_link(struct wl_resource *resource); + +struct wl_resource * +wl_resource_from_link(struct wl_list *resource); + +struct wl_resource * +wl_resource_find_for_client(struct wl_list *list, struct wl_client *client); + +struct wl_client * +wl_resource_get_client(struct wl_resource *resource); + +void +wl_resource_set_user_data(struct wl_resource *resource, void *data); + +void * +wl_resource_get_user_data(struct wl_resource *resource); + +int +wl_resource_get_version(const struct wl_resource *resource); + +void +wl_resource_set_destructor(struct wl_resource *resource, + wl_resource_destroy_func_t destroy); + +int +wl_resource_instance_of(struct wl_resource *resource, + const struct wl_interface *interface, + const void *implementation); + +const char * +wl_resource_get_class(const struct wl_resource *resource); + +const struct wl_interface * +wl_resource_get_interface(struct wl_resource *resource); + +void +wl_resource_add_destroy_listener(struct wl_resource *resource, + struct wl_listener *listener); + +struct wl_listener * +wl_resource_get_destroy_listener(struct wl_resource *resource, + wl_notify_func_t notify); + +#define wl_resource_for_each(resource, list) \ + for (resource = 0, resource = wl_resource_from_link((list)->next); \ + wl_resource_get_link(resource) != (list); \ + resource = wl_resource_from_link(wl_resource_get_link(resource)->next)) + +#define wl_resource_for_each_safe(resource, tmp, list) \ + for (resource = 0, tmp = 0, \ + resource = wl_resource_from_link((list)->next), \ + tmp = wl_resource_from_link((list)->next->next); \ + wl_resource_get_link(resource) != (list); \ + resource = tmp, \ + tmp = wl_resource_from_link(wl_resource_get_link(resource)->next)) + +struct wl_shm_buffer * +wl_shm_buffer_get(struct wl_resource *resource); + +void +wl_shm_buffer_begin_access(struct wl_shm_buffer *buffer); + +void +wl_shm_buffer_end_access(struct wl_shm_buffer *buffer); + +void * +wl_shm_buffer_get_data(struct wl_shm_buffer *buffer); + +int32_t +wl_shm_buffer_get_stride(const struct wl_shm_buffer *buffer); + +uint32_t +wl_shm_buffer_get_format(const struct wl_shm_buffer *buffer); + +int32_t +wl_shm_buffer_get_width(const struct wl_shm_buffer *buffer); + +int32_t +wl_shm_buffer_get_height(const struct wl_shm_buffer *buffer); + +struct wl_shm_buffer * +wl_shm_buffer_ref(struct wl_shm_buffer *buffer); + +void +wl_shm_buffer_unref(struct wl_shm_buffer *buffer); + +struct wl_shm_pool * +wl_shm_buffer_ref_pool(struct wl_shm_buffer *buffer); + +void +wl_shm_pool_unref(struct wl_shm_pool *pool); + +int +wl_display_init_shm(struct wl_display *display); + +uint32_t * +wl_display_add_shm_format(struct wl_display *display, uint32_t format); + +WL_DEPRECATED +struct wl_shm_buffer * +wl_shm_buffer_create(struct wl_client *client, + uint32_t id, int32_t width, int32_t height, + int32_t stride, uint32_t format); + +void +wl_log_set_handler_server(wl_log_func_t handler); + +enum wl_protocol_logger_type { + WL_PROTOCOL_LOGGER_REQUEST, + WL_PROTOCOL_LOGGER_EVENT, +}; + +struct wl_protocol_logger_message { + struct wl_resource *resource; + int message_opcode; + const struct wl_message *message; + int arguments_count; + const union wl_argument *arguments; +}; + +/** Callback function type for wl_display_add_protocol_logger() */ +typedef void (*wl_protocol_logger_func_t)(void *user_data, + enum wl_protocol_logger_type direction, + const struct wl_protocol_logger_message *message); + +struct wl_protocol_logger * +wl_display_add_protocol_logger(struct wl_display *display, + wl_protocol_logger_func_t, void *user_data); + +void +wl_protocol_logger_destroy(struct wl_protocol_logger *logger); + +void +wl_fixes_handle_ack_global_remove(struct wl_resource *fixes_resource, + struct wl_resource *registry_resource, + uint32_t global_name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server-protocol.h b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server-protocol.h new file mode 100644 index 000000000..484faff1b --- /dev/null +++ b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server-protocol.h @@ -0,0 +1,6252 @@ +/* Generated by wayland-scanner 1.26.0 */ + +#ifndef WAYLAND_SERVER_PROTOCOL_H +#define WAYLAND_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_wayland The wayland protocol + * @section page_ifaces_wayland Interfaces + * - @subpage page_iface_wl_display - core global object + * - @subpage page_iface_wl_registry - global registry object + * - @subpage page_iface_wl_callback - callback object + * - @subpage page_iface_wl_compositor - the compositor singleton + * - @subpage page_iface_wl_shm_pool - a shared memory pool + * - @subpage page_iface_wl_shm - shared memory support + * - @subpage page_iface_wl_buffer - content for a wl_surface + * - @subpage page_iface_wl_data_offer - offer to transfer data + * - @subpage page_iface_wl_data_source - offer to transfer data + * - @subpage page_iface_wl_data_device - data transfer device + * - @subpage page_iface_wl_data_device_manager - data transfer interface + * - @subpage page_iface_wl_shell - create desktop-style surfaces + * - @subpage page_iface_wl_shell_surface - desktop-style metadata interface + * - @subpage page_iface_wl_surface - an onscreen surface + * - @subpage page_iface_wl_seat - group of input devices + * - @subpage page_iface_wl_pointer - pointer input device + * - @subpage page_iface_wl_keyboard - keyboard input device + * - @subpage page_iface_wl_touch - touchscreen input device + * - @subpage page_iface_wl_output - compositor output region + * - @subpage page_iface_wl_region - region interface + * - @subpage page_iface_wl_subcompositor - sub-surface compositing + * - @subpage page_iface_wl_subsurface - sub-surface interface to a wl_surface + * - @subpage page_iface_wl_fixes - wayland protocol fixes + * @section page_copyright_wayland Copyright + *
+ *
+ * Copyright ยฉ 2008-2011 Kristian Hรธgsberg
+ * Copyright ยฉ 2010-2011 Intel Corporation
+ * Copyright ยฉ 2012-2013 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ */ +struct wl_buffer; +struct wl_callback; +struct wl_compositor; +struct wl_data_device; +struct wl_data_device_manager; +struct wl_data_offer; +struct wl_data_source; +struct wl_display; +struct wl_fixes; +struct wl_keyboard; +struct wl_output; +struct wl_pointer; +struct wl_region; +struct wl_registry; +struct wl_seat; +struct wl_shell; +struct wl_shell_surface; +struct wl_shm; +struct wl_shm_pool; +struct wl_subcompositor; +struct wl_subsurface; +struct wl_surface; +struct wl_touch; + +#ifndef WL_DISPLAY_INTERFACE +#define WL_DISPLAY_INTERFACE +/** + * @page page_iface_wl_display wl_display + * @section page_iface_wl_display_desc Description + * + * The core global object. This is a special singleton object. It + * is used for internal Wayland protocol features. + * @section page_iface_wl_display_api API + * See @ref iface_wl_display. + */ +/** + * @defgroup iface_wl_display The wl_display interface + * + * The core global object. This is a special singleton object. It + * is used for internal Wayland protocol features. + */ +extern const struct wl_interface wl_display_interface; +#endif +#ifndef WL_REGISTRY_INTERFACE +#define WL_REGISTRY_INTERFACE +/** + * @page page_iface_wl_registry wl_registry + * @section page_iface_wl_registry_desc Description + * + * The singleton global registry object. The server has a number of + * global objects that are available to all clients. These objects + * typically represent an actual object in the server (for example, + * an input device) or they are singleton objects that provide + * extension functionality. + * + * When a client creates a registry object, the registry object + * will emit a global event for each global currently in the + * registry. Globals come and go as a result of device or + * monitor hotplugs, reconfiguration or other events, and the + * registry will send out global and global_remove events to + * keep the client up to date with the changes. To mark the end + * of the initial burst of events, the client can use the + * wl_display.sync request immediately after calling + * wl_display.get_registry. + * + * A client can bind to a global object by using the bind + * request. This creates a client-side handle that lets the object + * emit events to the client and lets the client invoke requests on + * the object. + * @section page_iface_wl_registry_api API + * See @ref iface_wl_registry. + */ +/** + * @defgroup iface_wl_registry The wl_registry interface + * + * The singleton global registry object. The server has a number of + * global objects that are available to all clients. These objects + * typically represent an actual object in the server (for example, + * an input device) or they are singleton objects that provide + * extension functionality. + * + * When a client creates a registry object, the registry object + * will emit a global event for each global currently in the + * registry. Globals come and go as a result of device or + * monitor hotplugs, reconfiguration or other events, and the + * registry will send out global and global_remove events to + * keep the client up to date with the changes. To mark the end + * of the initial burst of events, the client can use the + * wl_display.sync request immediately after calling + * wl_display.get_registry. + * + * A client can bind to a global object by using the bind + * request. This creates a client-side handle that lets the object + * emit events to the client and lets the client invoke requests on + * the object. + */ +extern const struct wl_interface wl_registry_interface; +#endif +#ifndef WL_CALLBACK_INTERFACE +#define WL_CALLBACK_INTERFACE +/** + * @page page_iface_wl_callback wl_callback + * @section page_iface_wl_callback_desc Description + * + * Clients can handle the 'done' event to get notified when + * the related request is done. + * + * Note, because wl_callback objects are created from multiple independent + * factory interfaces, the wl_callback interface is frozen at version 1. + * @section page_iface_wl_callback_api API + * See @ref iface_wl_callback. + */ +/** + * @defgroup iface_wl_callback The wl_callback interface + * + * Clients can handle the 'done' event to get notified when + * the related request is done. + * + * Note, because wl_callback objects are created from multiple independent + * factory interfaces, the wl_callback interface is frozen at version 1. + */ +extern const struct wl_interface wl_callback_interface; +#endif +#ifndef WL_COMPOSITOR_INTERFACE +#define WL_COMPOSITOR_INTERFACE +/** + * @page page_iface_wl_compositor wl_compositor + * @section page_iface_wl_compositor_desc Description + * + * A compositor. This object is a singleton global. The + * compositor is in charge of combining the contents of multiple + * surfaces into one displayable output. + * @section page_iface_wl_compositor_api API + * See @ref iface_wl_compositor. + */ +/** + * @defgroup iface_wl_compositor The wl_compositor interface + * + * A compositor. This object is a singleton global. The + * compositor is in charge of combining the contents of multiple + * surfaces into one displayable output. + */ +extern const struct wl_interface wl_compositor_interface; +#endif +#ifndef WL_SHM_POOL_INTERFACE +#define WL_SHM_POOL_INTERFACE +/** + * @page page_iface_wl_shm_pool wl_shm_pool + * @section page_iface_wl_shm_pool_desc Description + * + * The wl_shm_pool object encapsulates a piece of memory shared + * between the compositor and client. Through the wl_shm_pool + * object, the client can allocate shared memory wl_buffer objects. + * All objects created through the same pool share the same + * underlying mapped memory. Reusing the mapped memory avoids the + * setup/teardown overhead and is useful when interactively resizing + * a surface or for many small buffers. + * @section page_iface_wl_shm_pool_api API + * See @ref iface_wl_shm_pool. + */ +/** + * @defgroup iface_wl_shm_pool The wl_shm_pool interface + * + * The wl_shm_pool object encapsulates a piece of memory shared + * between the compositor and client. Through the wl_shm_pool + * object, the client can allocate shared memory wl_buffer objects. + * All objects created through the same pool share the same + * underlying mapped memory. Reusing the mapped memory avoids the + * setup/teardown overhead and is useful when interactively resizing + * a surface or for many small buffers. + */ +extern const struct wl_interface wl_shm_pool_interface; +#endif +#ifndef WL_SHM_INTERFACE +#define WL_SHM_INTERFACE +/** + * @page page_iface_wl_shm wl_shm + * @section page_iface_wl_shm_desc Description + * + * A singleton global object that provides support for shared + * memory. + * + * Clients can create wl_shm_pool objects using the create_pool + * request. + * + * On binding the wl_shm object one or more format events + * are emitted to inform clients about the valid pixel formats + * that can be used for buffers. + * @section page_iface_wl_shm_api API + * See @ref iface_wl_shm. + */ +/** + * @defgroup iface_wl_shm The wl_shm interface + * + * A singleton global object that provides support for shared + * memory. + * + * Clients can create wl_shm_pool objects using the create_pool + * request. + * + * On binding the wl_shm object one or more format events + * are emitted to inform clients about the valid pixel formats + * that can be used for buffers. + */ +extern const struct wl_interface wl_shm_interface; +#endif +#ifndef WL_BUFFER_INTERFACE +#define WL_BUFFER_INTERFACE +/** + * @page page_iface_wl_buffer wl_buffer + * @section page_iface_wl_buffer_desc Description + * + * A buffer provides the content for a wl_surface. Buffers are + * created through factory interfaces such as wl_shm, wp_linux_buffer_params + * (from the linux-dmabuf protocol extension) or similar. It has a width and + * a height and can be attached to a wl_surface, but the mechanism by which a + * client provides and updates the contents is defined by the buffer factory + * interface. + * + * Color channels are assumed to be electrical rather than optical (in other + * words, encoded with a transfer function) unless otherwise specified. If + * the buffer uses a format that has an alpha channel, the alpha channel is + * assumed to be premultiplied into the electrical color channel values + * (after transfer function encoding) unless otherwise specified. + * + * Note, because wl_buffer objects are created from multiple independent + * factory interfaces, the wl_buffer interface is frozen at version 1. + * @section page_iface_wl_buffer_api API + * See @ref iface_wl_buffer. + */ +/** + * @defgroup iface_wl_buffer The wl_buffer interface + * + * A buffer provides the content for a wl_surface. Buffers are + * created through factory interfaces such as wl_shm, wp_linux_buffer_params + * (from the linux-dmabuf protocol extension) or similar. It has a width and + * a height and can be attached to a wl_surface, but the mechanism by which a + * client provides and updates the contents is defined by the buffer factory + * interface. + * + * Color channels are assumed to be electrical rather than optical (in other + * words, encoded with a transfer function) unless otherwise specified. If + * the buffer uses a format that has an alpha channel, the alpha channel is + * assumed to be premultiplied into the electrical color channel values + * (after transfer function encoding) unless otherwise specified. + * + * Note, because wl_buffer objects are created from multiple independent + * factory interfaces, the wl_buffer interface is frozen at version 1. + */ +extern const struct wl_interface wl_buffer_interface; +#endif +#ifndef WL_DATA_OFFER_INTERFACE +#define WL_DATA_OFFER_INTERFACE +/** + * @page page_iface_wl_data_offer wl_data_offer + * @section page_iface_wl_data_offer_desc Description + * + * A wl_data_offer represents a piece of data offered for transfer + * by another client (the source client). It is used by the + * copy-and-paste and drag-and-drop mechanisms. The offer + * describes the different mime types that the data can be + * converted to and provides the mechanism for transferring the + * data directly from the source client. + * @section page_iface_wl_data_offer_api API + * See @ref iface_wl_data_offer. + */ +/** + * @defgroup iface_wl_data_offer The wl_data_offer interface + * + * A wl_data_offer represents a piece of data offered for transfer + * by another client (the source client). It is used by the + * copy-and-paste and drag-and-drop mechanisms. The offer + * describes the different mime types that the data can be + * converted to and provides the mechanism for transferring the + * data directly from the source client. + */ +extern const struct wl_interface wl_data_offer_interface; +#endif +#ifndef WL_DATA_SOURCE_INTERFACE +#define WL_DATA_SOURCE_INTERFACE +/** + * @page page_iface_wl_data_source wl_data_source + * @section page_iface_wl_data_source_desc Description + * + * The wl_data_source object is the source side of a wl_data_offer. + * It is created by the source client in a data transfer and + * provides a way to describe the offered data and a way to respond + * to requests to transfer the data. + * @section page_iface_wl_data_source_api API + * See @ref iface_wl_data_source. + */ +/** + * @defgroup iface_wl_data_source The wl_data_source interface + * + * The wl_data_source object is the source side of a wl_data_offer. + * It is created by the source client in a data transfer and + * provides a way to describe the offered data and a way to respond + * to requests to transfer the data. + */ +extern const struct wl_interface wl_data_source_interface; +#endif +#ifndef WL_DATA_DEVICE_INTERFACE +#define WL_DATA_DEVICE_INTERFACE +/** + * @page page_iface_wl_data_device wl_data_device + * @section page_iface_wl_data_device_desc Description + * + * There is one wl_data_device per seat which can be obtained + * from the global wl_data_device_manager singleton. + * + * A wl_data_device provides access to inter-client data transfer + * mechanisms such as copy-and-paste and drag-and-drop. + * @section page_iface_wl_data_device_api API + * See @ref iface_wl_data_device. + */ +/** + * @defgroup iface_wl_data_device The wl_data_device interface + * + * There is one wl_data_device per seat which can be obtained + * from the global wl_data_device_manager singleton. + * + * A wl_data_device provides access to inter-client data transfer + * mechanisms such as copy-and-paste and drag-and-drop. + */ +extern const struct wl_interface wl_data_device_interface; +#endif +#ifndef WL_DATA_DEVICE_MANAGER_INTERFACE +#define WL_DATA_DEVICE_MANAGER_INTERFACE +/** + * @page page_iface_wl_data_device_manager wl_data_device_manager + * @section page_iface_wl_data_device_manager_desc Description + * + * The wl_data_device_manager is a singleton global object that + * provides access to inter-client data transfer mechanisms such as + * copy-and-paste and drag-and-drop. These mechanisms are tied to + * a wl_seat and this interface lets a client get a wl_data_device + * corresponding to a wl_seat. + * + * Depending on the version bound, the objects created from the bound + * wl_data_device_manager object will have different requirements for + * functioning properly. See wl_data_source.set_actions, + * wl_data_offer.accept and wl_data_offer.finish for details. + * @section page_iface_wl_data_device_manager_api API + * See @ref iface_wl_data_device_manager. + */ +/** + * @defgroup iface_wl_data_device_manager The wl_data_device_manager interface + * + * The wl_data_device_manager is a singleton global object that + * provides access to inter-client data transfer mechanisms such as + * copy-and-paste and drag-and-drop. These mechanisms are tied to + * a wl_seat and this interface lets a client get a wl_data_device + * corresponding to a wl_seat. + * + * Depending on the version bound, the objects created from the bound + * wl_data_device_manager object will have different requirements for + * functioning properly. See wl_data_source.set_actions, + * wl_data_offer.accept and wl_data_offer.finish for details. + */ +extern const struct wl_interface wl_data_device_manager_interface; +#endif +#ifndef WL_SHELL_INTERFACE +#define WL_SHELL_INTERFACE +/** + * @page page_iface_wl_shell wl_shell + * @section page_iface_wl_shell_desc Description + * + * This interface is implemented by servers that provide + * desktop-style user interfaces. + * + * It allows clients to associate a wl_shell_surface with + * a basic surface. + * + * Note! This protocol is deprecated and not intended for production use. + * For desktop-style user interfaces, use xdg_shell. Compositors and clients + * should not implement this interface. + * @section page_iface_wl_shell_api API + * See @ref iface_wl_shell. + */ +/** + * @defgroup iface_wl_shell The wl_shell interface + * + * This interface is implemented by servers that provide + * desktop-style user interfaces. + * + * It allows clients to associate a wl_shell_surface with + * a basic surface. + * + * Note! This protocol is deprecated and not intended for production use. + * For desktop-style user interfaces, use xdg_shell. Compositors and clients + * should not implement this interface. + */ +extern const struct wl_interface wl_shell_interface; +#endif +#ifndef WL_SHELL_SURFACE_INTERFACE +#define WL_SHELL_SURFACE_INTERFACE +/** + * @page page_iface_wl_shell_surface wl_shell_surface + * @section page_iface_wl_shell_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides requests to treat surfaces like toplevel, fullscreen + * or popup windows, move, resize or maximize them, associate + * metadata like title and class, etc. + * + * On the server side the object is automatically destroyed when + * the related wl_surface is destroyed. On the client side, + * wl_shell_surface_destroy() must be called before destroying + * the wl_surface object. + * @section page_iface_wl_shell_surface_api API + * See @ref iface_wl_shell_surface. + */ +/** + * @defgroup iface_wl_shell_surface The wl_shell_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides requests to treat surfaces like toplevel, fullscreen + * or popup windows, move, resize or maximize them, associate + * metadata like title and class, etc. + * + * On the server side the object is automatically destroyed when + * the related wl_surface is destroyed. On the client side, + * wl_shell_surface_destroy() must be called before destroying + * the wl_surface object. + */ +extern const struct wl_interface wl_shell_surface_interface; +#endif +#ifndef WL_SURFACE_INTERFACE +#define WL_SURFACE_INTERFACE +/** + * @page page_iface_wl_surface wl_surface + * @section page_iface_wl_surface_desc Description + * + * A surface is a rectangular area that may be displayed on zero + * or more outputs, and shown any number of times at the compositor's + * discretion. They can present wl_buffers, receive user input, and + * define a local coordinate system. + * + * The size of a surface (and relative positions on it) is described + * in surface-local coordinates, which may differ from the buffer + * coordinates of the pixel content, in case a buffer_transform + * or a buffer_scale is used. + * + * A surface without a "role" is fairly useless: a compositor does + * not know where, when or how to present it. The role is the + * purpose of a wl_surface. Examples of roles are a cursor for a + * pointer (as set by wl_pointer.set_cursor), a drag icon + * (wl_data_device.start_drag), a sub-surface + * (wl_subcompositor.get_subsurface), and a window as defined by a + * shell protocol (e.g. wl_shell.get_shell_surface). + * + * A surface can have only one role at a time. Initially a + * wl_surface does not have a role. Once a wl_surface is given a + * role, it is set permanently for the whole lifetime of the + * wl_surface object. Giving the current role again is allowed, + * unless explicitly forbidden by the relevant interface + * specification. + * + * Surface roles are given by requests in other interfaces such as + * wl_pointer.set_cursor. The request should explicitly mention + * that this request gives a role to a wl_surface. Often, this + * request also creates a new protocol object that represents the + * role and adds additional functionality to wl_surface. When a + * client wants to destroy a wl_surface, they must destroy this role + * object before the wl_surface, otherwise a defunct_role_object error is + * sent. + * + * Destroying the role object does not remove the role from the + * wl_surface, but it may stop the wl_surface from "playing the role". + * For instance, if a wl_subsurface object is destroyed, the wl_surface + * it was created for will be unmapped and forget its position and + * z-order. It is allowed to create a wl_subsurface for the same + * wl_surface again, but it is not allowed to use the wl_surface as + * a cursor (cursor is a different role than sub-surface, and role + * switching is not allowed). + * @section page_iface_wl_surface_api API + * See @ref iface_wl_surface. + */ +/** + * @defgroup iface_wl_surface The wl_surface interface + * + * A surface is a rectangular area that may be displayed on zero + * or more outputs, and shown any number of times at the compositor's + * discretion. They can present wl_buffers, receive user input, and + * define a local coordinate system. + * + * The size of a surface (and relative positions on it) is described + * in surface-local coordinates, which may differ from the buffer + * coordinates of the pixel content, in case a buffer_transform + * or a buffer_scale is used. + * + * A surface without a "role" is fairly useless: a compositor does + * not know where, when or how to present it. The role is the + * purpose of a wl_surface. Examples of roles are a cursor for a + * pointer (as set by wl_pointer.set_cursor), a drag icon + * (wl_data_device.start_drag), a sub-surface + * (wl_subcompositor.get_subsurface), and a window as defined by a + * shell protocol (e.g. wl_shell.get_shell_surface). + * + * A surface can have only one role at a time. Initially a + * wl_surface does not have a role. Once a wl_surface is given a + * role, it is set permanently for the whole lifetime of the + * wl_surface object. Giving the current role again is allowed, + * unless explicitly forbidden by the relevant interface + * specification. + * + * Surface roles are given by requests in other interfaces such as + * wl_pointer.set_cursor. The request should explicitly mention + * that this request gives a role to a wl_surface. Often, this + * request also creates a new protocol object that represents the + * role and adds additional functionality to wl_surface. When a + * client wants to destroy a wl_surface, they must destroy this role + * object before the wl_surface, otherwise a defunct_role_object error is + * sent. + * + * Destroying the role object does not remove the role from the + * wl_surface, but it may stop the wl_surface from "playing the role". + * For instance, if a wl_subsurface object is destroyed, the wl_surface + * it was created for will be unmapped and forget its position and + * z-order. It is allowed to create a wl_subsurface for the same + * wl_surface again, but it is not allowed to use the wl_surface as + * a cursor (cursor is a different role than sub-surface, and role + * switching is not allowed). + */ +extern const struct wl_interface wl_surface_interface; +#endif +#ifndef WL_SEAT_INTERFACE +#define WL_SEAT_INTERFACE +/** + * @page page_iface_wl_seat wl_seat + * @section page_iface_wl_seat_desc Description + * + * A seat is a group of keyboards, pointer and touch devices. This + * object is published as a global during start up, or when such a + * device is hot plugged. A seat typically has a pointer and + * maintains a keyboard focus and a pointer focus. + * @section page_iface_wl_seat_api API + * See @ref iface_wl_seat. + */ +/** + * @defgroup iface_wl_seat The wl_seat interface + * + * A seat is a group of keyboards, pointer and touch devices. This + * object is published as a global during start up, or when such a + * device is hot plugged. A seat typically has a pointer and + * maintains a keyboard focus and a pointer focus. + */ +extern const struct wl_interface wl_seat_interface; +#endif +#ifndef WL_POINTER_INTERFACE +#define WL_POINTER_INTERFACE +/** + * @page page_iface_wl_pointer wl_pointer + * @section page_iface_wl_pointer_desc Description + * + * The wl_pointer interface represents one or more input devices, + * such as mice, which control the pointer location and pointer_focus + * of a seat. + * + * The wl_pointer interface generates motion, enter and leave + * events for the surfaces that the pointer is located over, + * and button and axis events for button presses, button releases + * and scrolling. + * @section page_iface_wl_pointer_api API + * See @ref iface_wl_pointer. + */ +/** + * @defgroup iface_wl_pointer The wl_pointer interface + * + * The wl_pointer interface represents one or more input devices, + * such as mice, which control the pointer location and pointer_focus + * of a seat. + * + * The wl_pointer interface generates motion, enter and leave + * events for the surfaces that the pointer is located over, + * and button and axis events for button presses, button releases + * and scrolling. + */ +extern const struct wl_interface wl_pointer_interface; +#endif +#ifndef WL_KEYBOARD_INTERFACE +#define WL_KEYBOARD_INTERFACE +/** + * @page page_iface_wl_keyboard wl_keyboard + * @section page_iface_wl_keyboard_desc Description + * + * The wl_keyboard interface represents one or more keyboards + * associated with a seat. + * + * Each wl_keyboard has the following logical state: + * + * - an active surface (possibly null), + * - the keys currently logically down, + * - the active modifiers, + * - the active group. + * + * By default, the active surface is null, the keys currently logically down + * are empty, the active modifiers and the active group are 0. + * @section page_iface_wl_keyboard_api API + * See @ref iface_wl_keyboard. + */ +/** + * @defgroup iface_wl_keyboard The wl_keyboard interface + * + * The wl_keyboard interface represents one or more keyboards + * associated with a seat. + * + * Each wl_keyboard has the following logical state: + * + * - an active surface (possibly null), + * - the keys currently logically down, + * - the active modifiers, + * - the active group. + * + * By default, the active surface is null, the keys currently logically down + * are empty, the active modifiers and the active group are 0. + */ +extern const struct wl_interface wl_keyboard_interface; +#endif +#ifndef WL_TOUCH_INTERFACE +#define WL_TOUCH_INTERFACE +/** + * @page page_iface_wl_touch wl_touch + * @section page_iface_wl_touch_desc Description + * + * The wl_touch interface represents a touchscreen + * associated with a seat. + * + * Touch interactions can consist of one or more contacts. + * For each contact, a series of events is generated, starting + * with a down event, followed by zero or more motion events, + * and ending with an up event. Events relating to the same + * contact point can be identified by the ID of the sequence. + * @section page_iface_wl_touch_api API + * See @ref iface_wl_touch. + */ +/** + * @defgroup iface_wl_touch The wl_touch interface + * + * The wl_touch interface represents a touchscreen + * associated with a seat. + * + * Touch interactions can consist of one or more contacts. + * For each contact, a series of events is generated, starting + * with a down event, followed by zero or more motion events, + * and ending with an up event. Events relating to the same + * contact point can be identified by the ID of the sequence. + */ +extern const struct wl_interface wl_touch_interface; +#endif +#ifndef WL_OUTPUT_INTERFACE +#define WL_OUTPUT_INTERFACE +/** + * @page page_iface_wl_output wl_output + * @section page_iface_wl_output_desc Description + * + * An output describes part of the compositor geometry. The + * compositor works in the 'compositor coordinate system' and an + * output corresponds to a rectangular area in that space that is + * actually visible. This typically corresponds to a monitor that + * displays part of the compositor space. This object is published + * as global during start up, or when a monitor is hotplugged. + * @section page_iface_wl_output_api API + * See @ref iface_wl_output. + */ +/** + * @defgroup iface_wl_output The wl_output interface + * + * An output describes part of the compositor geometry. The + * compositor works in the 'compositor coordinate system' and an + * output corresponds to a rectangular area in that space that is + * actually visible. This typically corresponds to a monitor that + * displays part of the compositor space. This object is published + * as global during start up, or when a monitor is hotplugged. + */ +extern const struct wl_interface wl_output_interface; +#endif +#ifndef WL_REGION_INTERFACE +#define WL_REGION_INTERFACE +/** + * @page page_iface_wl_region wl_region + * @section page_iface_wl_region_desc Description + * + * A region object describes an area. + * + * Region objects are used to describe the opaque and input + * regions of a surface. + * @section page_iface_wl_region_api API + * See @ref iface_wl_region. + */ +/** + * @defgroup iface_wl_region The wl_region interface + * + * A region object describes an area. + * + * Region objects are used to describe the opaque and input + * regions of a surface. + */ +extern const struct wl_interface wl_region_interface; +#endif +#ifndef WL_SUBCOMPOSITOR_INTERFACE +#define WL_SUBCOMPOSITOR_INTERFACE +/** + * @page page_iface_wl_subcompositor wl_subcompositor + * @section page_iface_wl_subcompositor_desc Description + * + * The global interface exposing sub-surface compositing capabilities. + * A wl_surface, that has sub-surfaces associated, is called the + * parent surface. Sub-surfaces can be arbitrarily nested and create + * a tree of sub-surfaces. + * + * The root surface in a tree of sub-surfaces is the main + * surface. The main surface cannot be a sub-surface, because + * sub-surfaces must always have a parent. + * + * A main surface with its sub-surfaces forms a (compound) window. + * For window management purposes, this set of wl_surface objects is + * to be considered as a single window, and it should also behave as + * such. + * + * The aim of sub-surfaces is to offload some of the compositing work + * within a window from clients to the compositor. A prime example is + * a video player with decorations and video in separate wl_surface + * objects. This should allow the compositor to pass YUV video buffer + * processing to dedicated overlay hardware when possible. + * @section page_iface_wl_subcompositor_api API + * See @ref iface_wl_subcompositor. + */ +/** + * @defgroup iface_wl_subcompositor The wl_subcompositor interface + * + * The global interface exposing sub-surface compositing capabilities. + * A wl_surface, that has sub-surfaces associated, is called the + * parent surface. Sub-surfaces can be arbitrarily nested and create + * a tree of sub-surfaces. + * + * The root surface in a tree of sub-surfaces is the main + * surface. The main surface cannot be a sub-surface, because + * sub-surfaces must always have a parent. + * + * A main surface with its sub-surfaces forms a (compound) window. + * For window management purposes, this set of wl_surface objects is + * to be considered as a single window, and it should also behave as + * such. + * + * The aim of sub-surfaces is to offload some of the compositing work + * within a window from clients to the compositor. A prime example is + * a video player with decorations and video in separate wl_surface + * objects. This should allow the compositor to pass YUV video buffer + * processing to dedicated overlay hardware when possible. + */ +extern const struct wl_interface wl_subcompositor_interface; +#endif +#ifndef WL_SUBSURFACE_INTERFACE +#define WL_SUBSURFACE_INTERFACE +/** + * @page page_iface_wl_subsurface wl_subsurface + * @section page_iface_wl_subsurface_desc Description + * + * An additional interface to a wl_surface object, which has been + * made a sub-surface. A sub-surface has one parent surface. A + * sub-surface's size and position are not limited to that of the parent. + * Particularly, a sub-surface is not automatically clipped to its + * parent's area. + * + * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + * and the parent surface is mapped. The order of which one happens + * first is irrelevant. A sub-surface is hidden if the parent becomes + * hidden, or if a NULL wl_buffer is applied. These rules apply + * recursively through the tree of surfaces. + * + * A sub-surface can be in one of two modes. The possible modes are + * synchronized and desynchronized, see methods wl_subsurface.set_sync and + * wl_subsurface.set_desync. + * + * The main surface can be thought to be always in desynchronized mode, + * since it does not have a parent in the sub-surfaces sense. + * + * Even if a sub-surface is in desynchronized mode, it will behave as + * in synchronized mode, if its parent surface behaves as in + * synchronized mode. This rule is applied recursively throughout the + * tree of surfaces. This means, that one can set a sub-surface into + * synchronized mode, and then assume that all its child and grand-child + * sub-surfaces are synchronized, too, without explicitly setting them. + * + * If a surface behaves as in synchronized mode, it is effectively + * synchronized, otherwise it is effectively desynchronized. + * + * A sub-surface is initially in the synchronized mode. + * + * The wl_subsurface interface has requests which modify double-buffered + * state of the parent surface (wl_subsurface.set_position, .place_above and + * .place_below). + * + * Destroying a sub-surface takes effect immediately. If you need to + * synchronize the removal of a sub-surface to the parent surface update, + * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, + * and then destroy the sub-surface. + * + * If the parent wl_surface object is destroyed, the sub-surface is + * unmapped. + * + * A sub-surface never has the keyboard focus of any seat. + * + * The wl_surface.offset request is ignored: clients must use set_position + * instead to move the sub-surface. + * @section page_iface_wl_subsurface_api API + * See @ref iface_wl_subsurface. + */ +/** + * @defgroup iface_wl_subsurface The wl_subsurface interface + * + * An additional interface to a wl_surface object, which has been + * made a sub-surface. A sub-surface has one parent surface. A + * sub-surface's size and position are not limited to that of the parent. + * Particularly, a sub-surface is not automatically clipped to its + * parent's area. + * + * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + * and the parent surface is mapped. The order of which one happens + * first is irrelevant. A sub-surface is hidden if the parent becomes + * hidden, or if a NULL wl_buffer is applied. These rules apply + * recursively through the tree of surfaces. + * + * A sub-surface can be in one of two modes. The possible modes are + * synchronized and desynchronized, see methods wl_subsurface.set_sync and + * wl_subsurface.set_desync. + * + * The main surface can be thought to be always in desynchronized mode, + * since it does not have a parent in the sub-surfaces sense. + * + * Even if a sub-surface is in desynchronized mode, it will behave as + * in synchronized mode, if its parent surface behaves as in + * synchronized mode. This rule is applied recursively throughout the + * tree of surfaces. This means, that one can set a sub-surface into + * synchronized mode, and then assume that all its child and grand-child + * sub-surfaces are synchronized, too, without explicitly setting them. + * + * If a surface behaves as in synchronized mode, it is effectively + * synchronized, otherwise it is effectively desynchronized. + * + * A sub-surface is initially in the synchronized mode. + * + * The wl_subsurface interface has requests which modify double-buffered + * state of the parent surface (wl_subsurface.set_position, .place_above and + * .place_below). + * + * Destroying a sub-surface takes effect immediately. If you need to + * synchronize the removal of a sub-surface to the parent surface update, + * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, + * and then destroy the sub-surface. + * + * If the parent wl_surface object is destroyed, the sub-surface is + * unmapped. + * + * A sub-surface never has the keyboard focus of any seat. + * + * The wl_surface.offset request is ignored: clients must use set_position + * instead to move the sub-surface. + */ +extern const struct wl_interface wl_subsurface_interface; +#endif +#ifndef WL_FIXES_INTERFACE +#define WL_FIXES_INTERFACE +/** + * @page page_iface_wl_fixes wl_fixes + * @section page_iface_wl_fixes_desc Description + * + * This global fixes problems with other core-protocol interfaces that + * cannot be fixed in these interfaces themselves. + * @section page_iface_wl_fixes_api API + * See @ref iface_wl_fixes. + */ +/** + * @defgroup iface_wl_fixes The wl_fixes interface + * + * This global fixes problems with other core-protocol interfaces that + * cannot be fixed in these interfaces themselves. + */ +extern const struct wl_interface wl_fixes_interface; +#endif + +#ifndef WL_DISPLAY_ERROR_ENUM +#define WL_DISPLAY_ERROR_ENUM +/** + * @ingroup iface_wl_display + * global error values + * + * These errors are global and can be emitted in response to any + * server request. + */ +enum wl_display_error { + /** + * server couldn't find object + */ + WL_DISPLAY_ERROR_INVALID_OBJECT = 0, + /** + * method doesn't exist on the specified interface or malformed request + */ + WL_DISPLAY_ERROR_INVALID_METHOD = 1, + /** + * server is out of memory + */ + WL_DISPLAY_ERROR_NO_MEMORY = 2, + /** + * implementation error in compositor + */ + WL_DISPLAY_ERROR_IMPLEMENTATION = 3, +}; +#endif /* WL_DISPLAY_ERROR_ENUM */ + +#ifndef WL_DISPLAY_ERROR_ENUM_IS_VALID +#define WL_DISPLAY_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_display + * Validate a wl_display error value. + * + * @return true on success, false on error. + * @ref wl_display_error + */ +static inline bool +wl_display_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_DISPLAY_ERROR_INVALID_OBJECT: + return version >= 1; + case WL_DISPLAY_ERROR_INVALID_METHOD: + return version >= 1; + case WL_DISPLAY_ERROR_NO_MEMORY: + return version >= 1; + case WL_DISPLAY_ERROR_IMPLEMENTATION: + return version >= 1; + default: + return false; + } +} +#endif /* WL_DISPLAY_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_display + * @struct wl_display_interface + */ +struct wl_display_interface { + /** + * asynchronous roundtrip + * + * The sync request asks the server to emit the 'done' event on + * the returned wl_callback object. Since requests are handled + * in-order and events are delivered in-order, this can be used as + * a barrier to ensure all previous requests and the resulting + * events have been handled. + * + * The object returned by this request will be destroyed by the + * compositor after the callback is fired and as such the client + * must not attempt to use it after that point. + * + * The callback_data passed in the callback is undefined and should + * be ignored. + * @param callback callback object for the sync request + */ + void (*sync)(struct wl_client *client, + struct wl_resource *resource, + uint32_t callback); + /** + * get global registry object + * + * This request creates a registry object that allows the client + * to list and bind the global objects available from the + * compositor. + * + * It should be noted that the server side resources consumed in + * response to a get_registry request can only be released when the + * client disconnects, not when the client side proxy is destroyed. + * Therefore, clients should invoke get_registry as infrequently as + * possible to avoid wasting memory. + * @param registry global registry object + */ + void (*get_registry)(struct wl_client *client, + struct wl_resource *resource, + uint32_t registry); +}; + +#define WL_DISPLAY_ERROR 0 +#define WL_DISPLAY_DELETE_ID 1 + +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_ERROR_SINCE_VERSION 1 +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_DELETE_ID_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_SYNC_SINCE_VERSION 1 +/** + * @ingroup iface_wl_display + */ +#define WL_DISPLAY_GET_REGISTRY_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_registry + * @struct wl_registry_interface + */ +struct wl_registry_interface { + /** + * bind an object to the display + * + * Binds a new, client-created object to the server using the + * specified name as the identifier. + * @param name unique numeric name of the object + * @param interface name of the objects interface + * @param version version of the objects interface + * @param id bound object + */ + void (*bind)(struct wl_client *client, + struct wl_resource *resource, + uint32_t name, + const char *interface, uint32_t version, uint32_t id); +}; + +#define WL_REGISTRY_GLOBAL 0 +#define WL_REGISTRY_GLOBAL_REMOVE 1 + +/** + * @ingroup iface_wl_registry + */ +#define WL_REGISTRY_GLOBAL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_registry + */ +#define WL_REGISTRY_GLOBAL_REMOVE_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_registry + */ +#define WL_REGISTRY_BIND_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_registry + * Sends an global event to the client owning the resource. + * @param resource_ The client's resource + * @param name numeric name of the global object + * @param interface interface implemented by the object + * @param version interface version + */ +static inline void +wl_registry_send_global(struct wl_resource *resource_, uint32_t name, const char *interface, uint32_t version) +{ + wl_resource_post_event(resource_, WL_REGISTRY_GLOBAL, name, interface, version); +} + +/** + * @ingroup iface_wl_registry + * Sends an global_remove event to the client owning the resource. + * @param resource_ The client's resource + * @param name numeric name of the global object + */ +static inline void +wl_registry_send_global_remove(struct wl_resource *resource_, uint32_t name) +{ + wl_resource_post_event(resource_, WL_REGISTRY_GLOBAL_REMOVE, name); +} + +#define WL_CALLBACK_DONE 0 + +/** + * @ingroup iface_wl_callback + */ +#define WL_CALLBACK_DONE_SINCE_VERSION 1 + + +/** + * @ingroup iface_wl_callback + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + * @param callback_data request-specific data for the callback + */ +static inline void +wl_callback_send_done(struct wl_resource *resource_, uint32_t callback_data) +{ + wl_resource_post_event(resource_, WL_CALLBACK_DONE, callback_data); +} + +/** + * @ingroup iface_wl_compositor + * @struct wl_compositor_interface + */ +struct wl_compositor_interface { + /** + * create new surface + * + * Ask the compositor to create a new surface. + * @param id the new surface + */ + void (*create_surface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * create new region + * + * Ask the compositor to create a new region. + * @param id the new region + */ + void (*create_region)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * destroy wl_compositor + * + * This request destroys the wl_compositor. This has no effect on + * any other objects. + * @since 7 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_wl_compositor + */ +#define WL_COMPOSITOR_CREATE_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_compositor + */ +#define WL_COMPOSITOR_CREATE_REGION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_compositor + */ +#define WL_COMPOSITOR_RELEASE_SINCE_VERSION 7 + +#ifndef WL_SHM_POOL_ERROR_ENUM +#define WL_SHM_POOL_ERROR_ENUM +/** + * @ingroup iface_wl_shm_pool + * wl_shm_pool error values + * + * These errors can be emitted in response to wl_shm_pool requests. + */ +enum wl_shm_pool_error { + /** + * buffer format is not known + */ + WL_SHM_POOL_ERROR_INVALID_FORMAT = 0, + /** + * invalid size or stride during buffer creation + */ + WL_SHM_POOL_ERROR_INVALID_STRIDE = 1, +}; +#endif /* WL_SHM_POOL_ERROR_ENUM */ + +#ifndef WL_SHM_POOL_ERROR_ENUM_IS_VALID +#define WL_SHM_POOL_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_shm_pool + * Validate a wl_shm_pool error value. + * + * @return true on success, false on error. + * @ref wl_shm_pool_error + */ +static inline bool +wl_shm_pool_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SHM_POOL_ERROR_INVALID_FORMAT: + return version >= 1; + case WL_SHM_POOL_ERROR_INVALID_STRIDE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SHM_POOL_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_shm_pool + * @struct wl_shm_pool_interface + */ +struct wl_shm_pool_interface { + /** + * create a buffer from the pool + * + * Create a wl_buffer object from the pool. + * + * The buffer is created offset bytes into the pool and has width + * and height as specified. The stride argument specifies the + * number of bytes from the beginning of one row to the beginning + * of the next. The format is the pixel format of the buffer and + * must be one of those advertised through the wl_shm.format event. + * + * A buffer will keep a reference to the pool it was created from + * so it is valid to destroy the pool immediately after creating a + * buffer from it. + * @param id buffer to create + * @param offset buffer byte offset within the pool + * @param width buffer width, in pixels + * @param height buffer height, in pixels + * @param stride number of bytes from the beginning of one row to the beginning of the next row + * @param format buffer pixel format + */ + void (*create_buffer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + int32_t offset, + int32_t width, + int32_t height, + int32_t stride, + uint32_t format); + /** + * destroy the pool + * + * Destroy the shared memory pool. + * + * The mmapped memory will be released when all buffers that have + * been created from this pool are gone. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * change the size of the pool mapping + * + * This request will cause the server to remap the backing memory + * for the pool from the file descriptor passed when the pool was + * created, but using the new size. This request can only be used + * to make the pool bigger. + * + * This request only changes the amount of bytes that are mmapped + * by the server and does not touch the file corresponding to the + * file descriptor passed at creation time. It is the client's + * responsibility to ensure that the file is at least as big as the + * new pool size. + * @param size new size of the pool, in bytes + */ + void (*resize)(struct wl_client *client, + struct wl_resource *resource, + int32_t size); +}; + + +/** + * @ingroup iface_wl_shm_pool + */ +#define WL_SHM_POOL_CREATE_BUFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shm_pool + */ +#define WL_SHM_POOL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shm_pool + */ +#define WL_SHM_POOL_RESIZE_SINCE_VERSION 1 + +#ifndef WL_SHM_ERROR_ENUM +#define WL_SHM_ERROR_ENUM +/** + * @ingroup iface_wl_shm + * wl_shm error values + * + * These errors can be emitted in response to wl_shm requests. + */ +enum wl_shm_error { + /** + * buffer format is not known + * @deprecated Deprecated since version 3 + */ + WL_SHM_ERROR_INVALID_FORMAT = 0, + /** + * invalid size or stride during pool creation + */ + WL_SHM_ERROR_INVALID_STRIDE = 1, + /** + * mmapping the file descriptor failed + */ + WL_SHM_ERROR_INVALID_FD = 2, +}; +#endif /* WL_SHM_ERROR_ENUM */ + +#ifndef WL_SHM_ERROR_ENUM_IS_VALID +#define WL_SHM_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_shm + * Validate a wl_shm error value. + * + * @return true on success, false on error. + * @ref wl_shm_error + */ +static inline bool +wl_shm_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SHM_ERROR_INVALID_FORMAT: + return version >= 1; + case WL_SHM_ERROR_INVALID_STRIDE: + return version >= 1; + case WL_SHM_ERROR_INVALID_FD: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SHM_ERROR_ENUM_IS_VALID */ + +#ifndef WL_SHM_FORMAT_ENUM +#define WL_SHM_FORMAT_ENUM +/** + * @ingroup iface_wl_shm + * pixel formats + * + * This describes the memory layout of an individual pixel. + * + * All renderers should support argb8888 and xrgb8888 but any other + * formats are optional and may not be supported by the particular + * renderer in use. + * + * The drm format codes match the macros defined in drm_fourcc.h, except + * argb8888 and xrgb8888. The formats actually supported by the compositor + * will be reported by the format event. See drm_fourcc.h for more detailed + * format descriptions. + * + * For all wl_shm formats and unless specified in another protocol + * extension, pre-multiplied alpha is used for pixel values. + */ +enum wl_shm_format { + /** + * 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian + */ + WL_SHM_FORMAT_ARGB8888 = 0, + /** + * 32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XRGB8888 = 1, + /** + * 8-bit color index format, [7:0] C + */ + WL_SHM_FORMAT_C8 = 0x20203843, + /** + * 8-bit RGB format, [7:0] R:G:B 3:3:2 + */ + WL_SHM_FORMAT_RGB332 = 0x38424752, + /** + * 8-bit BGR format, [7:0] B:G:R 2:3:3 + */ + WL_SHM_FORMAT_BGR233 = 0x38524742, + /** + * 16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian + */ + WL_SHM_FORMAT_XRGB4444 = 0x32315258, + /** + * 16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian + */ + WL_SHM_FORMAT_XBGR4444 = 0x32314258, + /** + * 16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian + */ + WL_SHM_FORMAT_RGBX4444 = 0x32315852, + /** + * 16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian + */ + WL_SHM_FORMAT_BGRX4444 = 0x32315842, + /** + * 16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian + */ + WL_SHM_FORMAT_ARGB4444 = 0x32315241, + /** + * 16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian + */ + WL_SHM_FORMAT_ABGR4444 = 0x32314241, + /** + * 16-bit RGBA format, [15:0] R:G:B:A 4:4:4:4 little endian + */ + WL_SHM_FORMAT_RGBA4444 = 0x32314152, + /** + * 16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian + */ + WL_SHM_FORMAT_BGRA4444 = 0x32314142, + /** + * 16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian + */ + WL_SHM_FORMAT_XRGB1555 = 0x35315258, + /** + * 16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian + */ + WL_SHM_FORMAT_XBGR1555 = 0x35314258, + /** + * 16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian + */ + WL_SHM_FORMAT_RGBX5551 = 0x35315852, + /** + * 16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian + */ + WL_SHM_FORMAT_BGRX5551 = 0x35315842, + /** + * 16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian + */ + WL_SHM_FORMAT_ARGB1555 = 0x35315241, + /** + * 16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian + */ + WL_SHM_FORMAT_ABGR1555 = 0x35314241, + /** + * 16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian + */ + WL_SHM_FORMAT_RGBA5551 = 0x35314152, + /** + * 16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian + */ + WL_SHM_FORMAT_BGRA5551 = 0x35314142, + /** + * 16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian + */ + WL_SHM_FORMAT_RGB565 = 0x36314752, + /** + * 16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian + */ + WL_SHM_FORMAT_BGR565 = 0x36314742, + /** + * 24-bit RGB format, [23:0] R:G:B little endian + */ + WL_SHM_FORMAT_RGB888 = 0x34324752, + /** + * 24-bit BGR format, [23:0] B:G:R little endian + */ + WL_SHM_FORMAT_BGR888 = 0x34324742, + /** + * 32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XBGR8888 = 0x34324258, + /** + * 32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian + */ + WL_SHM_FORMAT_RGBX8888 = 0x34325852, + /** + * 32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian + */ + WL_SHM_FORMAT_BGRX8888 = 0x34325842, + /** + * 32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian + */ + WL_SHM_FORMAT_ABGR8888 = 0x34324241, + /** + * 32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian + */ + WL_SHM_FORMAT_RGBA8888 = 0x34324152, + /** + * 32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian + */ + WL_SHM_FORMAT_BGRA8888 = 0x34324142, + /** + * 32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XRGB2101010 = 0x30335258, + /** + * 32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XBGR2101010 = 0x30334258, + /** + * 32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian + */ + WL_SHM_FORMAT_RGBX1010102 = 0x30335852, + /** + * 32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian + */ + WL_SHM_FORMAT_BGRX1010102 = 0x30335842, + /** + * 32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian + */ + WL_SHM_FORMAT_ARGB2101010 = 0x30335241, + /** + * 32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian + */ + WL_SHM_FORMAT_ABGR2101010 = 0x30334241, + /** + * 32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian + */ + WL_SHM_FORMAT_RGBA1010102 = 0x30334152, + /** + * 32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian + */ + WL_SHM_FORMAT_BGRA1010102 = 0x30334142, + /** + * packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_YUYV = 0x56595559, + /** + * packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_YVYU = 0x55595659, + /** + * packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_UYVY = 0x59565955, + /** + * packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian + */ + WL_SHM_FORMAT_VYUY = 0x59555956, + /** + * packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian + */ + WL_SHM_FORMAT_AYUV = 0x56555941, + /** + * 2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV12 = 0x3231564e, + /** + * 2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane + */ + WL_SHM_FORMAT_NV21 = 0x3132564e, + /** + * 2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV16 = 0x3631564e, + /** + * 2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane + */ + WL_SHM_FORMAT_NV61 = 0x3136564e, + /** + * 3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV410 = 0x39565559, + /** + * 3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU410 = 0x39555659, + /** + * 3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV411 = 0x31315559, + /** + * 3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU411 = 0x31315659, + /** + * 3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV420 = 0x32315559, + /** + * 3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU420 = 0x32315659, + /** + * 3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV422 = 0x36315559, + /** + * 3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU422 = 0x36315659, + /** + * 3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes + */ + WL_SHM_FORMAT_YUV444 = 0x34325559, + /** + * 3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes + */ + WL_SHM_FORMAT_YVU444 = 0x34325659, + /** + * [7:0] R + */ + WL_SHM_FORMAT_R8 = 0x20203852, + /** + * [15:0] R little endian + */ + WL_SHM_FORMAT_R16 = 0x20363152, + /** + * [15:0] R:G 8:8 little endian + */ + WL_SHM_FORMAT_RG88 = 0x38384752, + /** + * [15:0] G:R 8:8 little endian + */ + WL_SHM_FORMAT_GR88 = 0x38385247, + /** + * [31:0] R:G 16:16 little endian + */ + WL_SHM_FORMAT_RG1616 = 0x32334752, + /** + * [31:0] G:R 16:16 little endian + */ + WL_SHM_FORMAT_GR1616 = 0x32335247, + /** + * [63:0] x:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XRGB16161616F = 0x48345258, + /** + * [63:0] x:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XBGR16161616F = 0x48344258, + /** + * [63:0] A:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ARGB16161616F = 0x48345241, + /** + * [63:0] A:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ABGR16161616F = 0x48344241, + /** + * [31:0] X:Y:Cb:Cr 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XYUV8888 = 0x56555958, + /** + * [23:0] Cr:Cb:Y 8:8:8 little endian + */ + WL_SHM_FORMAT_VUY888 = 0x34325556, + /** + * Y followed by U then V, 10:10:10. Non-linear modifier only + */ + WL_SHM_FORMAT_VUY101010 = 0x30335556, + /** + * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 10:6:10:6:10:6:10:6 little endian per 2 Y pixels + */ + WL_SHM_FORMAT_Y210 = 0x30313259, + /** + * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 12:4:12:4:12:4:12:4 little endian per 2 Y pixels + */ + WL_SHM_FORMAT_Y212 = 0x32313259, + /** + * [63:0] Cr0:Y1:Cb0:Y0 16:16:16:16 little endian per 2 Y pixels + */ + WL_SHM_FORMAT_Y216 = 0x36313259, + /** + * [31:0] A:Cr:Y:Cb 2:10:10:10 little endian + */ + WL_SHM_FORMAT_Y410 = 0x30313459, + /** + * [63:0] A:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian + */ + WL_SHM_FORMAT_Y412 = 0x32313459, + /** + * [63:0] A:Cr:Y:Cb 16:16:16:16 little endian + */ + WL_SHM_FORMAT_Y416 = 0x36313459, + /** + * [31:0] X:Cr:Y:Cb 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XVYU2101010 = 0x30335658, + /** + * [63:0] X:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian + */ + WL_SHM_FORMAT_XVYU12_16161616 = 0x36335658, + /** + * [63:0] X:Cr:Y:Cb 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XVYU16161616 = 0x38345658, + /** + * [63:0] A3:A2:Y3:0:Cr0:0:Y2:0:A1:A0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian + */ + WL_SHM_FORMAT_Y0L0 = 0x304c3059, + /** + * [63:0] X3:X2:Y3:0:Cr0:0:Y2:0:X1:X0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian + */ + WL_SHM_FORMAT_X0L0 = 0x304c3058, + /** + * [63:0] A3:A2:Y3:Cr0:Y2:A1:A0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian + */ + WL_SHM_FORMAT_Y0L2 = 0x324c3059, + /** + * [63:0] X3:X2:Y3:Cr0:Y2:X1:X0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian + */ + WL_SHM_FORMAT_X0L2 = 0x324c3058, + WL_SHM_FORMAT_YUV420_8BIT = 0x38305559, + WL_SHM_FORMAT_YUV420_10BIT = 0x30315559, + WL_SHM_FORMAT_XRGB8888_A8 = 0x38415258, + WL_SHM_FORMAT_XBGR8888_A8 = 0x38414258, + WL_SHM_FORMAT_RGBX8888_A8 = 0x38415852, + WL_SHM_FORMAT_BGRX8888_A8 = 0x38415842, + WL_SHM_FORMAT_RGB888_A8 = 0x38413852, + WL_SHM_FORMAT_BGR888_A8 = 0x38413842, + WL_SHM_FORMAT_RGB565_A8 = 0x38413552, + WL_SHM_FORMAT_BGR565_A8 = 0x38413542, + /** + * non-subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV24 = 0x3432564e, + /** + * non-subsampled Cb:Cr plane + */ + WL_SHM_FORMAT_NV42 = 0x3234564e, + /** + * 2x1 subsampled Cr:Cb plane, 10 bit per channel + */ + WL_SHM_FORMAT_P210 = 0x30313250, + /** + * 2x2 subsampled Cr:Cb plane 10 bits per channel + */ + WL_SHM_FORMAT_P010 = 0x30313050, + /** + * 2x2 subsampled Cr:Cb plane 12 bits per channel + */ + WL_SHM_FORMAT_P012 = 0x32313050, + /** + * 2x2 subsampled Cr:Cb plane 16 bits per channel + */ + WL_SHM_FORMAT_P016 = 0x36313050, + /** + * [63:0] A:x:B:x:G:x:R:x 10:6:10:6:10:6:10:6 little endian + */ + WL_SHM_FORMAT_AXBXGXRX106106106106 = 0x30314241, + /** + * 2x2 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV15 = 0x3531564e, + WL_SHM_FORMAT_Q410 = 0x30313451, + WL_SHM_FORMAT_Q401 = 0x31303451, + /** + * [63:0] x:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XRGB16161616 = 0x38345258, + /** + * [63:0] x:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_XBGR16161616 = 0x38344258, + /** + * [63:0] A:R:G:B 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ARGB16161616 = 0x38345241, + /** + * [63:0] A:B:G:R 16:16:16:16 little endian + */ + WL_SHM_FORMAT_ABGR16161616 = 0x38344241, + /** + * [7:0] C0:C1:C2:C3:C4:C5:C6:C7 1:1:1:1:1:1:1:1 eight pixels/byte + */ + WL_SHM_FORMAT_C1 = 0x20203143, + /** + * [7:0] C0:C1:C2:C3 2:2:2:2 four pixels/byte + */ + WL_SHM_FORMAT_C2 = 0x20203243, + /** + * [7:0] C0:C1 4:4 two pixels/byte + */ + WL_SHM_FORMAT_C4 = 0x20203443, + /** + * [7:0] D0:D1:D2:D3:D4:D5:D6:D7 1:1:1:1:1:1:1:1 eight pixels/byte + */ + WL_SHM_FORMAT_D1 = 0x20203144, + /** + * [7:0] D0:D1:D2:D3 2:2:2:2 four pixels/byte + */ + WL_SHM_FORMAT_D2 = 0x20203244, + /** + * [7:0] D0:D1 4:4 two pixels/byte + */ + WL_SHM_FORMAT_D4 = 0x20203444, + /** + * [7:0] D + */ + WL_SHM_FORMAT_D8 = 0x20203844, + /** + * [7:0] R0:R1:R2:R3:R4:R5:R6:R7 1:1:1:1:1:1:1:1 eight pixels/byte + */ + WL_SHM_FORMAT_R1 = 0x20203152, + /** + * [7:0] R0:R1:R2:R3 2:2:2:2 four pixels/byte + */ + WL_SHM_FORMAT_R2 = 0x20203252, + /** + * [7:0] R0:R1 4:4 two pixels/byte + */ + WL_SHM_FORMAT_R4 = 0x20203452, + /** + * [15:0] x:R 6:10 little endian + */ + WL_SHM_FORMAT_R10 = 0x20303152, + /** + * [15:0] x:R 4:12 little endian + */ + WL_SHM_FORMAT_R12 = 0x20323152, + /** + * [31:0] A:Cr:Cb:Y 8:8:8:8 little endian + */ + WL_SHM_FORMAT_AVUY8888 = 0x59555641, + /** + * [31:0] X:Cr:Cb:Y 8:8:8:8 little endian + */ + WL_SHM_FORMAT_XVUY8888 = 0x59555658, + /** + * 2x2 subsampled Cr:Cb plane 10 bits per channel packed + */ + WL_SHM_FORMAT_P030 = 0x30333050, + /** + * [47:0] R:G:B 16:16:16 little endian + */ + WL_SHM_FORMAT_RGB161616 = 0x38344752, + /** + * [47:0] B:G:R 16:16:16 little endian + */ + WL_SHM_FORMAT_BGR161616 = 0x38344742, + /** + * [15:0] R 16 little endian + */ + WL_SHM_FORMAT_R16F = 0x48202052, + /** + * [31:0] G:R 16:16 little endian + */ + WL_SHM_FORMAT_GR1616F = 0x48205247, + /** + * [47:0] B:G:R 16:16:16 little endian + */ + WL_SHM_FORMAT_BGR161616F = 0x48524742, + /** + * [31:0] R 32 little endian + */ + WL_SHM_FORMAT_R32F = 0x46202052, + /** + * [63:0] G:R 32:32 little endian + */ + WL_SHM_FORMAT_GR3232F = 0x46205247, + /** + * [95:0] B:G:R 32:32:32 little endian + */ + WL_SHM_FORMAT_BGR323232F = 0x46524742, + /** + * [127:0] A:B:G:R 32:32:32:32 little endian + */ + WL_SHM_FORMAT_ABGR32323232F = 0x46384241, + /** + * 2x1 subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV20 = 0x3032564e, + /** + * non-subsampled Cr:Cb plane + */ + WL_SHM_FORMAT_NV30 = 0x3033564e, + /** + * 2x2 subsampled Cb (1) and Cr (2) planes 10 bits per channel + */ + WL_SHM_FORMAT_S010 = 0x30313053, + /** + * 2x1 subsampled Cb (1) and Cr (2) planes 10 bits per channel + */ + WL_SHM_FORMAT_S210 = 0x30313253, + /** + * non-subsampled Cb (1) and Cr (2) planes 10 bits per channel + */ + WL_SHM_FORMAT_S410 = 0x30313453, + /** + * 2x2 subsampled Cb (1) and Cr (2) planes 12 bits per channel + */ + WL_SHM_FORMAT_S012 = 0x32313053, + /** + * 2x1 subsampled Cb (1) and Cr (2) planes 12 bits per channel + */ + WL_SHM_FORMAT_S212 = 0x32313253, + /** + * non-subsampled Cb (1) and Cr (2) planes 12 bits per channel + */ + WL_SHM_FORMAT_S412 = 0x32313453, + /** + * 2x2 subsampled Cb (1) and Cr (2) planes 16 bits per channel + */ + WL_SHM_FORMAT_S016 = 0x36313053, + /** + * 2x1 subsampled Cb (1) and Cr (2) planes 16 bits per channel + */ + WL_SHM_FORMAT_S216 = 0x36313253, + /** + * non-subsampled Cb (1) and Cr (2) planes 16 bits per channel + */ + WL_SHM_FORMAT_S416 = 0x36313453, + /** + * [31:0] x:Cr:Cb:Y 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XVUY2101010 = 0x30335958, + /** + * 2x1 subsampled Cr:Cb plane 10 bits per channel packed + */ + WL_SHM_FORMAT_P230 = 0x30333250, + WL_SHM_FORMAT_T430 = 0x30333454, + /** + * 8-bit Y-only + */ + WL_SHM_FORMAT_Y8 = 0x59455247, + /** + * [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian + */ + WL_SHM_FORMAT_XYYY2101010 = 0x34415059, +}; +#endif /* WL_SHM_FORMAT_ENUM */ + +#ifndef WL_SHM_FORMAT_ENUM_IS_VALID +#define WL_SHM_FORMAT_ENUM_IS_VALID +/** + * @ingroup iface_wl_shm + * Validate a wl_shm format value. + * + * @return true on success, false on error. + * @ref wl_shm_format + */ +static inline bool +wl_shm_format_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SHM_FORMAT_ARGB8888: + return version >= 1; + case WL_SHM_FORMAT_XRGB8888: + return version >= 1; + case WL_SHM_FORMAT_C8: + return version >= 1; + case WL_SHM_FORMAT_RGB332: + return version >= 1; + case WL_SHM_FORMAT_BGR233: + return version >= 1; + case WL_SHM_FORMAT_XRGB4444: + return version >= 1; + case WL_SHM_FORMAT_XBGR4444: + return version >= 1; + case WL_SHM_FORMAT_RGBX4444: + return version >= 1; + case WL_SHM_FORMAT_BGRX4444: + return version >= 1; + case WL_SHM_FORMAT_ARGB4444: + return version >= 1; + case WL_SHM_FORMAT_ABGR4444: + return version >= 1; + case WL_SHM_FORMAT_RGBA4444: + return version >= 1; + case WL_SHM_FORMAT_BGRA4444: + return version >= 1; + case WL_SHM_FORMAT_XRGB1555: + return version >= 1; + case WL_SHM_FORMAT_XBGR1555: + return version >= 1; + case WL_SHM_FORMAT_RGBX5551: + return version >= 1; + case WL_SHM_FORMAT_BGRX5551: + return version >= 1; + case WL_SHM_FORMAT_ARGB1555: + return version >= 1; + case WL_SHM_FORMAT_ABGR1555: + return version >= 1; + case WL_SHM_FORMAT_RGBA5551: + return version >= 1; + case WL_SHM_FORMAT_BGRA5551: + return version >= 1; + case WL_SHM_FORMAT_RGB565: + return version >= 1; + case WL_SHM_FORMAT_BGR565: + return version >= 1; + case WL_SHM_FORMAT_RGB888: + return version >= 1; + case WL_SHM_FORMAT_BGR888: + return version >= 1; + case WL_SHM_FORMAT_XBGR8888: + return version >= 1; + case WL_SHM_FORMAT_RGBX8888: + return version >= 1; + case WL_SHM_FORMAT_BGRX8888: + return version >= 1; + case WL_SHM_FORMAT_ABGR8888: + return version >= 1; + case WL_SHM_FORMAT_RGBA8888: + return version >= 1; + case WL_SHM_FORMAT_BGRA8888: + return version >= 1; + case WL_SHM_FORMAT_XRGB2101010: + return version >= 1; + case WL_SHM_FORMAT_XBGR2101010: + return version >= 1; + case WL_SHM_FORMAT_RGBX1010102: + return version >= 1; + case WL_SHM_FORMAT_BGRX1010102: + return version >= 1; + case WL_SHM_FORMAT_ARGB2101010: + return version >= 1; + case WL_SHM_FORMAT_ABGR2101010: + return version >= 1; + case WL_SHM_FORMAT_RGBA1010102: + return version >= 1; + case WL_SHM_FORMAT_BGRA1010102: + return version >= 1; + case WL_SHM_FORMAT_YUYV: + return version >= 1; + case WL_SHM_FORMAT_YVYU: + return version >= 1; + case WL_SHM_FORMAT_UYVY: + return version >= 1; + case WL_SHM_FORMAT_VYUY: + return version >= 1; + case WL_SHM_FORMAT_AYUV: + return version >= 1; + case WL_SHM_FORMAT_NV12: + return version >= 1; + case WL_SHM_FORMAT_NV21: + return version >= 1; + case WL_SHM_FORMAT_NV16: + return version >= 1; + case WL_SHM_FORMAT_NV61: + return version >= 1; + case WL_SHM_FORMAT_YUV410: + return version >= 1; + case WL_SHM_FORMAT_YVU410: + return version >= 1; + case WL_SHM_FORMAT_YUV411: + return version >= 1; + case WL_SHM_FORMAT_YVU411: + return version >= 1; + case WL_SHM_FORMAT_YUV420: + return version >= 1; + case WL_SHM_FORMAT_YVU420: + return version >= 1; + case WL_SHM_FORMAT_YUV422: + return version >= 1; + case WL_SHM_FORMAT_YVU422: + return version >= 1; + case WL_SHM_FORMAT_YUV444: + return version >= 1; + case WL_SHM_FORMAT_YVU444: + return version >= 1; + case WL_SHM_FORMAT_R8: + return version >= 1; + case WL_SHM_FORMAT_R16: + return version >= 1; + case WL_SHM_FORMAT_RG88: + return version >= 1; + case WL_SHM_FORMAT_GR88: + return version >= 1; + case WL_SHM_FORMAT_RG1616: + return version >= 1; + case WL_SHM_FORMAT_GR1616: + return version >= 1; + case WL_SHM_FORMAT_XRGB16161616F: + return version >= 1; + case WL_SHM_FORMAT_XBGR16161616F: + return version >= 1; + case WL_SHM_FORMAT_ARGB16161616F: + return version >= 1; + case WL_SHM_FORMAT_ABGR16161616F: + return version >= 1; + case WL_SHM_FORMAT_XYUV8888: + return version >= 1; + case WL_SHM_FORMAT_VUY888: + return version >= 1; + case WL_SHM_FORMAT_VUY101010: + return version >= 1; + case WL_SHM_FORMAT_Y210: + return version >= 1; + case WL_SHM_FORMAT_Y212: + return version >= 1; + case WL_SHM_FORMAT_Y216: + return version >= 1; + case WL_SHM_FORMAT_Y410: + return version >= 1; + case WL_SHM_FORMAT_Y412: + return version >= 1; + case WL_SHM_FORMAT_Y416: + return version >= 1; + case WL_SHM_FORMAT_XVYU2101010: + return version >= 1; + case WL_SHM_FORMAT_XVYU12_16161616: + return version >= 1; + case WL_SHM_FORMAT_XVYU16161616: + return version >= 1; + case WL_SHM_FORMAT_Y0L0: + return version >= 1; + case WL_SHM_FORMAT_X0L0: + return version >= 1; + case WL_SHM_FORMAT_Y0L2: + return version >= 1; + case WL_SHM_FORMAT_X0L2: + return version >= 1; + case WL_SHM_FORMAT_YUV420_8BIT: + return version >= 1; + case WL_SHM_FORMAT_YUV420_10BIT: + return version >= 1; + case WL_SHM_FORMAT_XRGB8888_A8: + return version >= 1; + case WL_SHM_FORMAT_XBGR8888_A8: + return version >= 1; + case WL_SHM_FORMAT_RGBX8888_A8: + return version >= 1; + case WL_SHM_FORMAT_BGRX8888_A8: + return version >= 1; + case WL_SHM_FORMAT_RGB888_A8: + return version >= 1; + case WL_SHM_FORMAT_BGR888_A8: + return version >= 1; + case WL_SHM_FORMAT_RGB565_A8: + return version >= 1; + case WL_SHM_FORMAT_BGR565_A8: + return version >= 1; + case WL_SHM_FORMAT_NV24: + return version >= 1; + case WL_SHM_FORMAT_NV42: + return version >= 1; + case WL_SHM_FORMAT_P210: + return version >= 1; + case WL_SHM_FORMAT_P010: + return version >= 1; + case WL_SHM_FORMAT_P012: + return version >= 1; + case WL_SHM_FORMAT_P016: + return version >= 1; + case WL_SHM_FORMAT_AXBXGXRX106106106106: + return version >= 1; + case WL_SHM_FORMAT_NV15: + return version >= 1; + case WL_SHM_FORMAT_Q410: + return version >= 1; + case WL_SHM_FORMAT_Q401: + return version >= 1; + case WL_SHM_FORMAT_XRGB16161616: + return version >= 1; + case WL_SHM_FORMAT_XBGR16161616: + return version >= 1; + case WL_SHM_FORMAT_ARGB16161616: + return version >= 1; + case WL_SHM_FORMAT_ABGR16161616: + return version >= 1; + case WL_SHM_FORMAT_C1: + return version >= 1; + case WL_SHM_FORMAT_C2: + return version >= 1; + case WL_SHM_FORMAT_C4: + return version >= 1; + case WL_SHM_FORMAT_D1: + return version >= 1; + case WL_SHM_FORMAT_D2: + return version >= 1; + case WL_SHM_FORMAT_D4: + return version >= 1; + case WL_SHM_FORMAT_D8: + return version >= 1; + case WL_SHM_FORMAT_R1: + return version >= 1; + case WL_SHM_FORMAT_R2: + return version >= 1; + case WL_SHM_FORMAT_R4: + return version >= 1; + case WL_SHM_FORMAT_R10: + return version >= 1; + case WL_SHM_FORMAT_R12: + return version >= 1; + case WL_SHM_FORMAT_AVUY8888: + return version >= 1; + case WL_SHM_FORMAT_XVUY8888: + return version >= 1; + case WL_SHM_FORMAT_P030: + return version >= 1; + case WL_SHM_FORMAT_RGB161616: + return version >= 1; + case WL_SHM_FORMAT_BGR161616: + return version >= 1; + case WL_SHM_FORMAT_R16F: + return version >= 1; + case WL_SHM_FORMAT_GR1616F: + return version >= 1; + case WL_SHM_FORMAT_BGR161616F: + return version >= 1; + case WL_SHM_FORMAT_R32F: + return version >= 1; + case WL_SHM_FORMAT_GR3232F: + return version >= 1; + case WL_SHM_FORMAT_BGR323232F: + return version >= 1; + case WL_SHM_FORMAT_ABGR32323232F: + return version >= 1; + case WL_SHM_FORMAT_NV20: + return version >= 1; + case WL_SHM_FORMAT_NV30: + return version >= 1; + case WL_SHM_FORMAT_S010: + return version >= 1; + case WL_SHM_FORMAT_S210: + return version >= 1; + case WL_SHM_FORMAT_S410: + return version >= 1; + case WL_SHM_FORMAT_S012: + return version >= 1; + case WL_SHM_FORMAT_S212: + return version >= 1; + case WL_SHM_FORMAT_S412: + return version >= 1; + case WL_SHM_FORMAT_S016: + return version >= 1; + case WL_SHM_FORMAT_S216: + return version >= 1; + case WL_SHM_FORMAT_S416: + return version >= 1; + case WL_SHM_FORMAT_XVUY2101010: + return version >= 1; + case WL_SHM_FORMAT_P230: + return version >= 1; + case WL_SHM_FORMAT_T430: + return version >= 1; + case WL_SHM_FORMAT_Y8: + return version >= 1; + case WL_SHM_FORMAT_XYYY2101010: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SHM_FORMAT_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_shm + * @struct wl_shm_interface + */ +struct wl_shm_interface { + /** + * create a shm pool + * + * Create a new wl_shm_pool object. + * + * The pool can be used to create shared memory based buffer + * objects. The server will mmap size bytes of the passed file + * descriptor, to use as backing memory for the pool. + * @param id pool to create + * @param fd file descriptor for the pool + * @param size pool size, in bytes + */ + void (*create_pool)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + int32_t fd, + int32_t size); + /** + * release the shm object + * + * Using this request a client can tell the server that it is not + * going to use the shm object anymore. + * + * Objects created via this interface remain unaffected. + * @since 2 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_SHM_FORMAT 0 + +/** + * @ingroup iface_wl_shm + */ +#define WL_SHM_FORMAT_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_shm + */ +#define WL_SHM_CREATE_POOL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shm + */ +#define WL_SHM_RELEASE_SINCE_VERSION 2 + +/** + * @ingroup iface_wl_shm + * Sends an format event to the client owning the resource. + * @param resource_ The client's resource + * @param format buffer pixel format + */ +static inline void +wl_shm_send_format(struct wl_resource *resource_, uint32_t format) +{ + wl_resource_post_event(resource_, WL_SHM_FORMAT, format); +} + +/** + * @ingroup iface_wl_buffer + * @struct wl_buffer_interface + */ +struct wl_buffer_interface { + /** + * destroy a buffer + * + * Destroy a buffer. If and how you need to release the backing + * storage is defined by the buffer factory interface. + * + * For possible side-effects to a surface, see wl_surface.attach. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_BUFFER_RELEASE 0 + +/** + * @ingroup iface_wl_buffer + */ +#define WL_BUFFER_RELEASE_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_buffer + */ +#define WL_BUFFER_DESTROY_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_buffer + * Sends an release event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_buffer_send_release(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_BUFFER_RELEASE); +} + +#ifndef WL_DATA_OFFER_ERROR_ENUM +#define WL_DATA_OFFER_ERROR_ENUM +enum wl_data_offer_error { + /** + * finish request was called untimely + */ + WL_DATA_OFFER_ERROR_INVALID_FINISH = 0, + /** + * action mask contains invalid values + */ + WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK = 1, + /** + * action argument has an invalid value + */ + WL_DATA_OFFER_ERROR_INVALID_ACTION = 2, + /** + * offer doesn't accept this request + */ + WL_DATA_OFFER_ERROR_INVALID_OFFER = 3, +}; +#endif /* WL_DATA_OFFER_ERROR_ENUM */ + +#ifndef WL_DATA_OFFER_ERROR_ENUM_IS_VALID +#define WL_DATA_OFFER_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_data_offer + * Validate a wl_data_offer error value. + * + * @return true on success, false on error. + * @ref wl_data_offer_error + */ +static inline bool +wl_data_offer_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_DATA_OFFER_ERROR_INVALID_FINISH: + return version >= 1; + case WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK: + return version >= 1; + case WL_DATA_OFFER_ERROR_INVALID_ACTION: + return version >= 1; + case WL_DATA_OFFER_ERROR_INVALID_OFFER: + return version >= 1; + default: + return false; + } +} +#endif /* WL_DATA_OFFER_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_data_offer + * @struct wl_data_offer_interface + */ +struct wl_data_offer_interface { + /** + * accept one of the offered mime types + * + * Indicate that the client can accept the given mime type, or + * NULL for not accepted. + * + * For objects of version 2 or older, this request is used by the + * client to give feedback whether the client can receive the given + * mime type, or NULL if none is accepted; the feedback does not + * determine whether the drag-and-drop operation succeeds or not. + * + * For objects of version 3 or newer, this request determines the + * final result of the drag-and-drop operation. If the end result + * is that no mime types were accepted, the drag-and-drop operation + * will be cancelled and the corresponding drag source will receive + * wl_data_source.cancelled. Clients may still use this event in + * conjunction with wl_data_source.action for feedback. + * @param serial serial number of the accept request + * @param mime_type mime type accepted by the client + */ + void (*accept)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + const char *mime_type); + /** + * request that the data is transferred + * + * To transfer the offered data, the client issues this request + * and indicates the mime type it wants to receive. The transfer + * happens through the passed file descriptor (typically created + * with the pipe system call). The source client writes the data in + * the mime type representation requested and then closes the file + * descriptor. + * + * The receiving client reads from the read end of the pipe until + * EOF and then closes its end, at which point the transfer is + * complete. + * + * This request may happen multiple times for different mime types, + * both before and after wl_data_device.drop. Drag-and-drop + * destination clients may preemptively fetch data or examine it + * more closely to determine acceptance. + * @param mime_type mime type desired by receiver + * @param fd file descriptor for data transfer + */ + void (*receive)(struct wl_client *client, + struct wl_resource *resource, + const char *mime_type, + int32_t fd); + /** + * destroy data offer + * + * Destroy the data offer. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * the offer will no longer be used + * + * Notifies the compositor that the drag destination successfully + * finished the drag-and-drop operation. + * + * Upon receiving this request, the compositor will emit + * wl_data_source.dnd_finished on the drag source client. + * + * It is a client error to perform other requests than + * wl_data_offer.destroy after this one. It is also an error to + * perform this request after a NULL mime type has been set in + * wl_data_offer.accept or no action was received through + * wl_data_offer.action. + * + * If wl_data_offer.finish request is received for a non drag and + * drop operation, the invalid_finish protocol error is raised. + * @since 3 + */ + void (*finish)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the available/preferred drag-and-drop actions + * + * Sets the actions that the destination side client supports for + * this operation. This request may trigger the emission of + * wl_data_source.action and wl_data_offer.action events if the + * compositor needs to change the selected action. + * + * This request can be called multiple times throughout the + * drag-and-drop operation, typically in response to + * wl_data_device.enter or wl_data_device.motion events. + * + * This request determines the final result of the drag-and-drop + * operation. If the end result is that no action is accepted, the + * drag source will receive wl_data_source.cancelled. + * + * The dnd_actions argument must contain only values expressed in + * the wl_data_device_manager.dnd_actions enum, and the + * preferred_action argument must only contain one of those values + * set, otherwise it will result in a protocol error. + * + * While managing an "ask" action, the destination drag-and-drop + * client may perform further wl_data_offer.receive requests, and + * is expected to perform one last wl_data_offer.set_actions + * request with a preferred action other than "ask" (and optionally + * wl_data_offer.accept) before requesting wl_data_offer.finish, in + * order to convey the action selected by the user. If the + * preferred action is not in the wl_data_offer.source_actions + * mask, an error will be raised. + * + * If the "ask" action is dismissed (e.g. user cancellation), the + * client is expected to perform wl_data_offer.destroy right away. + * + * This request can only be made on drag-and-drop offers, a + * protocol error will be raised otherwise. + * @param dnd_actions actions supported by the destination client + * @param preferred_action action preferred by the destination client + * @since 3 + */ + void (*set_actions)(struct wl_client *client, + struct wl_resource *resource, + uint32_t dnd_actions, + uint32_t preferred_action); +}; + +#define WL_DATA_OFFER_OFFER 0 +#define WL_DATA_OFFER_SOURCE_ACTIONS 1 +#define WL_DATA_OFFER_ACTION 2 + +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_ACTION_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_ACCEPT_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_RECEIVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_FINISH_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_offer + */ +#define WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_data_offer + * Sends an offer event to the client owning the resource. + * @param resource_ The client's resource + * @param mime_type offered mime type + */ +static inline void +wl_data_offer_send_offer(struct wl_resource *resource_, const char *mime_type) +{ + wl_resource_post_event(resource_, WL_DATA_OFFER_OFFER, mime_type); +} + +/** + * @ingroup iface_wl_data_offer + * Sends an source_actions event to the client owning the resource. + * @param resource_ The client's resource + * @param source_actions actions offered by the data source + */ +static inline void +wl_data_offer_send_source_actions(struct wl_resource *resource_, uint32_t source_actions) +{ + wl_resource_post_event(resource_, WL_DATA_OFFER_SOURCE_ACTIONS, source_actions); +} + +/** + * @ingroup iface_wl_data_offer + * Sends an action event to the client owning the resource. + * @param resource_ The client's resource + * @param dnd_action action selected by the compositor + */ +static inline void +wl_data_offer_send_action(struct wl_resource *resource_, uint32_t dnd_action) +{ + wl_resource_post_event(resource_, WL_DATA_OFFER_ACTION, dnd_action); +} + +#ifndef WL_DATA_SOURCE_ERROR_ENUM +#define WL_DATA_SOURCE_ERROR_ENUM +enum wl_data_source_error { + /** + * action mask contains invalid values + */ + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK = 0, + /** + * source doesn't accept this request + */ + WL_DATA_SOURCE_ERROR_INVALID_SOURCE = 1, +}; +#endif /* WL_DATA_SOURCE_ERROR_ENUM */ + +#ifndef WL_DATA_SOURCE_ERROR_ENUM_IS_VALID +#define WL_DATA_SOURCE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_data_source + * Validate a wl_data_source error value. + * + * @return true on success, false on error. + * @ref wl_data_source_error + */ +static inline bool +wl_data_source_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK: + return version >= 1; + case WL_DATA_SOURCE_ERROR_INVALID_SOURCE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_DATA_SOURCE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_data_source + * @struct wl_data_source_interface + */ +struct wl_data_source_interface { + /** + * add an offered mime type + * + * This request adds a mime type to the set of mime types + * advertised to targets. Can be called several times to offer + * multiple types. + * @param mime_type mime type offered by the data source + */ + void (*offer)(struct wl_client *client, + struct wl_resource *resource, + const char *mime_type); + /** + * destroy the data source + * + * Destroy the data source. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the available drag-and-drop actions + * + * Sets the actions that the source side client supports for this + * operation. This request may trigger wl_data_source.action and + * wl_data_offer.action events if the compositor needs to change + * the selected action. + * + * The dnd_actions argument must contain only values expressed in + * the wl_data_device_manager.dnd_actions enum, otherwise it will + * result in a protocol error. + * + * This request must be made once only, and can only be made on + * sources used in drag-and-drop, so it must be performed before + * wl_data_device.start_drag. Attempting to use the source other + * than for drag-and-drop will raise a protocol error. + * @param dnd_actions actions supported by the data source + * @since 3 + */ + void (*set_actions)(struct wl_client *client, + struct wl_resource *resource, + uint32_t dnd_actions); +}; + +#define WL_DATA_SOURCE_TARGET 0 +#define WL_DATA_SOURCE_SEND 1 +#define WL_DATA_SOURCE_CANCELLED 2 +#define WL_DATA_SOURCE_DND_DROP_PERFORMED 3 +#define WL_DATA_SOURCE_DND_FINISHED 4 +#define WL_DATA_SOURCE_ACTION 5 + +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_TARGET_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_SEND_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_CANCELLED_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION 3 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_ACTION_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_source + */ +#define WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_data_source + * Sends an target event to the client owning the resource. + * @param resource_ The client's resource + * @param mime_type mime type accepted by the target + */ +static inline void +wl_data_source_send_target(struct wl_resource *resource_, const char *mime_type) +{ + wl_resource_post_event(resource_, WL_DATA_SOURCE_TARGET, mime_type); +} + +/** + * @ingroup iface_wl_data_source + * Sends an send event to the client owning the resource. + * @param resource_ The client's resource + * @param mime_type mime type for the data + * @param fd file descriptor for the data + */ +static inline void +wl_data_source_send_send(struct wl_resource *resource_, const char *mime_type, int32_t fd) +{ + wl_resource_post_event(resource_, WL_DATA_SOURCE_SEND, mime_type, fd); +} + +/** + * @ingroup iface_wl_data_source + * Sends an cancelled event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_data_source_send_cancelled(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_DATA_SOURCE_CANCELLED); +} + +/** + * @ingroup iface_wl_data_source + * Sends an dnd_drop_performed event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_data_source_send_dnd_drop_performed(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_DATA_SOURCE_DND_DROP_PERFORMED); +} + +/** + * @ingroup iface_wl_data_source + * Sends an dnd_finished event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_data_source_send_dnd_finished(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_DATA_SOURCE_DND_FINISHED); +} + +/** + * @ingroup iface_wl_data_source + * Sends an action event to the client owning the resource. + * @param resource_ The client's resource + * @param dnd_action action selected by the compositor + */ +static inline void +wl_data_source_send_action(struct wl_resource *resource_, uint32_t dnd_action) +{ + wl_resource_post_event(resource_, WL_DATA_SOURCE_ACTION, dnd_action); +} + +#ifndef WL_DATA_DEVICE_ERROR_ENUM +#define WL_DATA_DEVICE_ERROR_ENUM +enum wl_data_device_error { + /** + * given wl_surface has another role + */ + WL_DATA_DEVICE_ERROR_ROLE = 0, + /** + * source has already been used + */ + WL_DATA_DEVICE_ERROR_USED_SOURCE = 1, +}; +#endif /* WL_DATA_DEVICE_ERROR_ENUM */ + +#ifndef WL_DATA_DEVICE_ERROR_ENUM_IS_VALID +#define WL_DATA_DEVICE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_data_device + * Validate a wl_data_device error value. + * + * @return true on success, false on error. + * @ref wl_data_device_error + */ +static inline bool +wl_data_device_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_DATA_DEVICE_ERROR_ROLE: + return version >= 1; + case WL_DATA_DEVICE_ERROR_USED_SOURCE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_DATA_DEVICE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_data_device + * @struct wl_data_device_interface + */ +struct wl_data_device_interface { + /** + * start drag-and-drop operation + * + * This request asks the compositor to start a drag-and-drop + * operation on behalf of the client. + * + * The source argument is the data source that provides the data + * for the eventual data transfer. If source is NULL, enter, leave + * and motion events are sent only to the client that initiated the + * drag and the client is expected to handle the data passing + * internally. If source is destroyed, the drag-and-drop session + * will be cancelled. + * + * The origin surface is the surface where the drag originates and + * the client must have an active implicit grab that matches the + * serial. + * + * The icon surface is an optional (can be NULL) surface that + * provides an icon to be moved around with the cursor. Initially, + * the top-left corner of the icon surface is placed at the cursor + * hotspot, but subsequent wl_surface.offset requests can move the + * relative position. Attach requests must be confirmed with + * wl_surface.commit as usual. The icon surface is given the role + * of a drag-and-drop icon. If the icon surface already has another + * role, it raises a protocol error. + * + * The input region is ignored for wl_surfaces with the role of a + * drag-and-drop icon. + * + * The given source may not be used in any further set_selection or + * start_drag requests. Attempting to reuse a previously-used + * source may send a used_source error. + * @param source data source for the eventual transfer + * @param origin surface where the drag originates + * @param icon drag-and-drop icon surface + * @param serial serial number of the implicit grab on the origin + */ + void (*start_drag)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *source, + struct wl_resource *origin, + struct wl_resource *icon, + uint32_t serial); + /** + * copy data to the selection + * + * This request asks the compositor to set the selection to the + * data from the source on behalf of the client. + * + * To unset the selection, set the source to NULL. + * + * The given source may not be used in any further set_selection or + * start_drag requests. Attempting to reuse a previously-used + * source may send a used_source error. + * @param source data source for the selection + * @param serial serial number of the event that triggered this request + */ + void (*set_selection)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *source, + uint32_t serial); + /** + * destroy data device + * + * This request destroys the data device. + * @since 2 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_DATA_DEVICE_DATA_OFFER 0 +#define WL_DATA_DEVICE_ENTER 1 +#define WL_DATA_DEVICE_LEAVE 2 +#define WL_DATA_DEVICE_MOTION 3 +#define WL_DATA_DEVICE_DROP 4 +#define WL_DATA_DEVICE_SELECTION 5 + +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_DATA_OFFER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_MOTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_DROP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_SELECTION_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_START_DRAG_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_SET_SELECTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device + */ +#define WL_DATA_DEVICE_RELEASE_SINCE_VERSION 2 + +/** + * @ingroup iface_wl_data_device + * Sends an data_offer event to the client owning the resource. + * @param resource_ The client's resource + * @param id the new data_offer object + */ +static inline void +wl_data_device_send_data_offer(struct wl_resource *resource_, struct wl_resource *id) +{ + wl_resource_post_event(resource_, WL_DATA_DEVICE_DATA_OFFER, id); +} + +/** + * @ingroup iface_wl_data_device + * Sends an enter event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the enter event + * @param surface client surface entered + * @param x surface-local x coordinate + * @param y surface-local y coordinate + * @param id source data_offer object + */ +static inline void +wl_data_device_send_enter(struct wl_resource *resource_, uint32_t serial, struct wl_resource *surface, wl_fixed_t x, wl_fixed_t y, struct wl_resource *id) +{ + wl_resource_post_event(resource_, WL_DATA_DEVICE_ENTER, serial, surface, x, y, id); +} + +/** + * @ingroup iface_wl_data_device + * Sends an leave event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_data_device_send_leave(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_DATA_DEVICE_LEAVE); +} + +/** + * @ingroup iface_wl_data_device + * Sends an motion event to the client owning the resource. + * @param resource_ The client's resource + * @param time timestamp with millisecond granularity + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ +static inline void +wl_data_device_send_motion(struct wl_resource *resource_, uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + wl_resource_post_event(resource_, WL_DATA_DEVICE_MOTION, time, x, y); +} + +/** + * @ingroup iface_wl_data_device + * Sends an drop event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_data_device_send_drop(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_DATA_DEVICE_DROP); +} + +/** + * @ingroup iface_wl_data_device + * Sends an selection event to the client owning the resource. + * @param resource_ The client's resource + * @param id selection data_offer object + */ +static inline void +wl_data_device_send_selection(struct wl_resource *resource_, struct wl_resource *id) +{ + wl_resource_post_event(resource_, WL_DATA_DEVICE_SELECTION, id); +} + +#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM +#define WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM +/** + * @ingroup iface_wl_data_device_manager + * drag and drop actions + * + * This is a bitmask of the available/preferred actions in a + * drag-and-drop operation. + * + * In the compositor, the selected action is a result of matching the + * actions offered by the source and destination sides. "action" events + * with a "none" action will be sent to both source and destination if + * there is no match. All further checks will effectively happen on + * (source actions โˆฉ destination actions). + * + * In addition, compositors may also pick different actions in + * reaction to key modifiers being pressed. One common design that + * is used in major toolkits (and the behavior recommended for + * compositors) is: + * + * - If no modifiers are pressed, the first match (in bit order) + * will be used. + * - Pressing Shift selects "move", if enabled in the mask. + * - Pressing Control selects "copy", if enabled in the mask. + * + * Behavior beyond that is considered implementation-dependent. + * Compositors may for example bind other modifiers (like Alt/Meta) + * or drags initiated with other buttons than BTN_LEFT to specific + * actions (e.g. "ask"). + */ +enum wl_data_device_manager_dnd_action { + /** + * no action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0, + /** + * copy action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1, + /** + * move action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2, + /** + * ask action + */ + WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4, +}; +#endif /* WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM */ + +#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM_IS_VALID +#define WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM_IS_VALID +/** + * @ingroup iface_wl_data_device_manager + * Validate a wl_data_device_manager dnd_action value. + * + * @return true on success, false on error. + * @ref wl_data_device_manager_dnd_action + */ +static inline bool +wl_data_device_manager_dnd_action_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (version >= 1) + valid |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (version >= 1) + valid |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + if (version >= 1) + valid |= WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + return (value & ~valid) == 0; +} +#endif /* WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_data_device_manager + * @struct wl_data_device_manager_interface + */ +struct wl_data_device_manager_interface { + /** + * create a new data source + * + * Create a new data source. + * @param id data source to create + */ + void (*create_data_source)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * create a new data device + * + * Create a new data device for a given seat. + * @param id data device to create + * @param seat seat associated with the data device + */ + void (*get_data_device)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *seat); + /** + * destroy wl_data_device_manager + * + * This request destroys the wl_data_device_manager. This has no + * effect on any other objects. + * @since 4 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_wl_data_device_manager + */ +#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device_manager + */ +#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_data_device_manager + */ +#define WL_DATA_DEVICE_MANAGER_RELEASE_SINCE_VERSION 4 + +#ifndef WL_SHELL_ERROR_ENUM +#define WL_SHELL_ERROR_ENUM +enum wl_shell_error { + /** + * given wl_surface has another role + */ + WL_SHELL_ERROR_ROLE = 0, +}; +#endif /* WL_SHELL_ERROR_ENUM */ + +#ifndef WL_SHELL_ERROR_ENUM_IS_VALID +#define WL_SHELL_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_shell + * Validate a wl_shell error value. + * + * @return true on success, false on error. + * @ref wl_shell_error + */ +static inline bool +wl_shell_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SHELL_ERROR_ROLE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SHELL_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_shell + * @struct wl_shell_interface + */ +struct wl_shell_interface { + /** + * create a shell surface from a surface + * + * Create a shell surface for an existing surface. This gives the + * wl_surface the role of a shell surface. If the wl_surface + * already has another role, it raises a protocol error. + * + * Only one shell surface can be associated with a given surface. + * @param id shell surface to create + * @param surface surface to be given the shell surface role + */ + void (*get_shell_surface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); +}; + + +/** + * @ingroup iface_wl_shell + */ +#define WL_SHELL_GET_SHELL_SURFACE_SINCE_VERSION 1 + +#ifndef WL_SHELL_SURFACE_RESIZE_ENUM +#define WL_SHELL_SURFACE_RESIZE_ENUM +/** + * @ingroup iface_wl_shell_surface + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. The server may + * use this information to adapt its behavior, e.g. choose + * an appropriate cursor image. + */ +enum wl_shell_surface_resize { + /** + * no edge + */ + WL_SHELL_SURFACE_RESIZE_NONE = 0, + /** + * top edge + */ + WL_SHELL_SURFACE_RESIZE_TOP = 1, + /** + * bottom edge + */ + WL_SHELL_SURFACE_RESIZE_BOTTOM = 2, + /** + * left edge + */ + WL_SHELL_SURFACE_RESIZE_LEFT = 4, + /** + * top and left edges + */ + WL_SHELL_SURFACE_RESIZE_TOP_LEFT = 5, + /** + * bottom and left edges + */ + WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT = 6, + /** + * right edge + */ + WL_SHELL_SURFACE_RESIZE_RIGHT = 8, + /** + * top and right edges + */ + WL_SHELL_SURFACE_RESIZE_TOP_RIGHT = 9, + /** + * bottom and right edges + */ + WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT = 10, +}; +#endif /* WL_SHELL_SURFACE_RESIZE_ENUM */ + +#ifndef WL_SHELL_SURFACE_RESIZE_ENUM_IS_VALID +#define WL_SHELL_SURFACE_RESIZE_ENUM_IS_VALID +/** + * @ingroup iface_wl_shell_surface + * Validate a wl_shell_surface resize value. + * + * @return true on success, false on error. + * @ref wl_shell_surface_resize + */ +static inline bool +wl_shell_surface_resize_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_NONE; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_TOP; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_BOTTOM; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_LEFT; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_TOP_LEFT; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_RIGHT; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_TOP_RIGHT; + if (version >= 1) + valid |= WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT; + return (value & ~valid) == 0; +} +#endif /* WL_SHELL_SURFACE_RESIZE_ENUM_IS_VALID */ + +#ifndef WL_SHELL_SURFACE_TRANSIENT_ENUM +#define WL_SHELL_SURFACE_TRANSIENT_ENUM +/** + * @ingroup iface_wl_shell_surface + * details of transient behaviour + * + * These flags specify details of the expected behaviour + * of transient surfaces. Used in the set_transient request. + */ +enum wl_shell_surface_transient { + /** + * do not set keyboard focus + */ + WL_SHELL_SURFACE_TRANSIENT_INACTIVE = 0x1, +}; +#endif /* WL_SHELL_SURFACE_TRANSIENT_ENUM */ + +#ifndef WL_SHELL_SURFACE_TRANSIENT_ENUM_IS_VALID +#define WL_SHELL_SURFACE_TRANSIENT_ENUM_IS_VALID +/** + * @ingroup iface_wl_shell_surface + * Validate a wl_shell_surface transient value. + * + * @return true on success, false on error. + * @ref wl_shell_surface_transient + */ +static inline bool +wl_shell_surface_transient_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= WL_SHELL_SURFACE_TRANSIENT_INACTIVE; + return (value & ~valid) == 0; +} +#endif /* WL_SHELL_SURFACE_TRANSIENT_ENUM_IS_VALID */ + +#ifndef WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM +#define WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM +/** + * @ingroup iface_wl_shell_surface + * different method to set the surface fullscreen + * + * Hints to indicate to the compositor how to deal with a conflict + * between the dimensions of the surface and the dimensions of the + * output. The compositor is free to ignore this parameter. + */ +enum wl_shell_surface_fullscreen_method { + /** + * no preference, apply default policy + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT = 0, + /** + * scale, preserve the surface's aspect ratio and center on output + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE = 1, + /** + * switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER = 2, + /** + * no upscaling, center on output and add black borders to compensate size mismatch + */ + WL_SHELL_SURFACE_FULLSCREEN_METHOD_FILL = 3, +}; +#endif /* WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM */ + +#ifndef WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM_IS_VALID +#define WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM_IS_VALID +/** + * @ingroup iface_wl_shell_surface + * Validate a wl_shell_surface fullscreen_method value. + * + * @return true on success, false on error. + * @ref wl_shell_surface_fullscreen_method + */ +static inline bool +wl_shell_surface_fullscreen_method_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT: + return version >= 1; + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE: + return version >= 1; + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER: + return version >= 1; + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_FILL: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_shell_surface + * @struct wl_shell_surface_interface + */ +struct wl_shell_surface_interface { + /** + * respond to a ping event + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. + * @param serial serial number of the ping event + */ + void (*pong)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); + /** + * start an interactive move + * + * Start a pointer-driven move of the surface. + * + * This request must be used in response to a button press event. + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * @param seat seat whose pointer is used + * @param serial serial number of the implicit grab on the pointer + */ + void (*move)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial); + /** + * start an interactive resize + * + * Start a pointer-driven resizing of the surface. + * + * This request must be used in response to a button press event. + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * @param seat seat whose pointer is used + * @param serial serial number of the implicit grab on the pointer + * @param edges which edge or corner is being dragged + */ + void (*resize)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial, + uint32_t edges); + /** + * make the surface a toplevel surface + * + * Map the surface as a toplevel surface. + * + * A toplevel surface is not fullscreen, maximized or transient. + */ + void (*set_toplevel)(struct wl_client *client, + struct wl_resource *resource); + /** + * make the surface a transient surface + * + * Map the surface relative to an existing surface. + * + * The x and y arguments specify the location of the upper left + * corner of the surface relative to the upper left corner of the + * parent surface, in surface-local coordinates. + * + * The flags argument controls details of the transient behaviour. + * @param parent parent surface + * @param x surface-local x coordinate + * @param y surface-local y coordinate + * @param flags transient surface behavior + */ + void (*set_transient)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *parent, + int32_t x, + int32_t y, + uint32_t flags); + /** + * make the surface a fullscreen surface + * + * Map the surface as a fullscreen surface. + * + * If an output parameter is given then the surface will be made + * fullscreen on that output. If the client does not specify the + * output then the compositor will apply its policy - usually + * choosing the output on which the surface has the biggest surface + * area. + * + * The client may specify a method to resolve a size conflict + * between the output size and the surface size - this is provided + * through the method parameter. + * + * The framerate parameter is used only when the method is set to + * "driver", to indicate the preferred framerate. A value of 0 + * indicates that the client does not care about framerate. The + * framerate is specified in mHz, that is framerate of 60000 is + * 60Hz. + * + * A method of "scale" or "driver" implies a scaling operation of + * the surface, either via a direct scaling operation or a change + * of the output mode. This will override any kind of output + * scaling, so that mapping a surface with a buffer size equal to + * the mode can fill the screen independent of buffer_scale. + * + * A method of "fill" means we don't scale up the buffer, however + * any output scale is applied. This means that you may run into an + * edge case where the application maps a buffer with the same size + * of the output mode but buffer_scale 1 (thus making a surface + * larger than the output). In this case it is allowed to downscale + * the results to fit the screen. + * + * The compositor must reply to this request with a configure event + * with the dimensions for the output on which the surface will be + * made fullscreen. + * @param method method for resolving size conflict + * @param framerate framerate in mHz + * @param output output on which the surface is to be fullscreen + */ + void (*set_fullscreen)(struct wl_client *client, + struct wl_resource *resource, + uint32_t method, + uint32_t framerate, + struct wl_resource *output); + /** + * make the surface a popup surface + * + * Map the surface as a popup. + * + * A popup surface is a transient surface with an added pointer + * grab. + * + * An existing implicit grab will be changed to owner-events mode, + * and the popup grab will continue after the implicit grab ends + * (i.e. releasing the mouse button does not cause the popup to be + * unmapped). + * + * The popup grab continues until the window is destroyed or a + * mouse button is pressed in any other client's window. A click in + * any of the client's surfaces is reported as normal, however, + * clicks in other clients' surfaces will be discarded and trigger + * the callback. + * + * The x and y arguments specify the location of the upper left + * corner of the surface relative to the upper left corner of the + * parent surface, in surface-local coordinates. + * @param seat seat whose pointer is used + * @param serial serial number of the implicit grab on the pointer + * @param parent parent surface + * @param x surface-local x coordinate + * @param y surface-local y coordinate + * @param flags transient surface behavior + */ + void (*set_popup)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial, + struct wl_resource *parent, + int32_t x, + int32_t y, + uint32_t flags); + /** + * make the surface a maximized surface + * + * Map the surface as a maximized surface. + * + * If an output parameter is given then the surface will be + * maximized on that output. If the client does not specify the + * output then the compositor will apply its policy - usually + * choosing the output on which the surface has the biggest surface + * area. + * + * The compositor will reply with a configure event telling the + * expected new surface size. The operation is completed on the + * next buffer attach to this surface. + * + * A maximized surface typically fills the entire output it is + * bound to, except for desktop elements such as panels. This is + * the main difference between a maximized shell surface and a + * fullscreen shell surface. + * + * The details depend on the compositor implementation. + * @param output output on which the surface is to be maximized + */ + void (*set_maximized)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output); + /** + * set surface title + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + * @param title surface title + */ + void (*set_title)(struct wl_client *client, + struct wl_resource *resource, + const char *title); + /** + * set surface class + * + * Set a class for the surface. + * + * The surface class identifies the general class of applications + * to which the surface belongs. A common convention is to use the + * file name (or the full path if it is a non-standard location) of + * the application's .desktop file as the class. + * @param class_ surface class + */ + void (*set_class)(struct wl_client *client, + struct wl_resource *resource, + const char *class_); +}; + +#define WL_SHELL_SURFACE_PING 0 +#define WL_SHELL_SURFACE_CONFIGURE 1 +#define WL_SHELL_SURFACE_POPUP_DONE 2 + +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_PING_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_POPUP_DONE_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_PONG_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_TRANSIENT_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_shell_surface + */ +#define WL_SHELL_SURFACE_SET_CLASS_SINCE_VERSION 1 + +/** + * @ingroup iface_wl_shell_surface + * Sends an ping event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the ping + */ +static inline void +wl_shell_surface_send_ping(struct wl_resource *resource_, uint32_t serial) +{ + wl_resource_post_event(resource_, WL_SHELL_SURFACE_PING, serial); +} + +/** + * @ingroup iface_wl_shell_surface + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + * @param edges how the surface was resized + * @param width new width of the surface + * @param height new height of the surface + */ +static inline void +wl_shell_surface_send_configure(struct wl_resource *resource_, uint32_t edges, int32_t width, int32_t height) +{ + wl_resource_post_event(resource_, WL_SHELL_SURFACE_CONFIGURE, edges, width, height); +} + +/** + * @ingroup iface_wl_shell_surface + * Sends an popup_done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_shell_surface_send_popup_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_SHELL_SURFACE_POPUP_DONE); +} + +#ifndef WL_SURFACE_ERROR_ENUM +#define WL_SURFACE_ERROR_ENUM +/** + * @ingroup iface_wl_surface + * wl_surface error values + * + * These errors can be emitted in response to wl_surface requests. + */ +enum wl_surface_error { + /** + * buffer scale value is invalid + */ + WL_SURFACE_ERROR_INVALID_SCALE = 0, + /** + * buffer transform value is invalid + */ + WL_SURFACE_ERROR_INVALID_TRANSFORM = 1, + /** + * buffer size is invalid + */ + WL_SURFACE_ERROR_INVALID_SIZE = 2, + /** + * buffer offset is invalid + */ + WL_SURFACE_ERROR_INVALID_OFFSET = 3, + /** + * surface was destroyed before its role object + */ + WL_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 4, + /** + * no buffer was attached + */ + WL_SURFACE_ERROR_NO_BUFFER = 5, +}; +#endif /* WL_SURFACE_ERROR_ENUM */ + +#ifndef WL_SURFACE_ERROR_ENUM_IS_VALID +#define WL_SURFACE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_surface + * Validate a wl_surface error value. + * + * @return true on success, false on error. + * @ref wl_surface_error + */ +static inline bool +wl_surface_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SURFACE_ERROR_INVALID_SCALE: + return version >= 1; + case WL_SURFACE_ERROR_INVALID_TRANSFORM: + return version >= 1; + case WL_SURFACE_ERROR_INVALID_SIZE: + return version >= 1; + case WL_SURFACE_ERROR_INVALID_OFFSET: + return version >= 1; + case WL_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT: + return version >= 1; + case WL_SURFACE_ERROR_NO_BUFFER: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SURFACE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_surface + * @struct wl_surface_interface + */ +struct wl_surface_interface { + /** + * delete surface + * + * Deletes the surface and invalidates its object ID. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the surface contents + * + * Set a buffer as the content of this surface. + * + * The new size of the surface is calculated based on the buffer + * size transformed by the inverse buffer_transform and the inverse + * buffer_scale. This means that at commit time the supplied buffer + * size must be an integer multiple of the buffer_scale. If that's + * not the case, an invalid_size error is sent. + * + * The x and y arguments specify the location of the new pending + * buffer's upper left corner, relative to the current buffer's + * upper left corner, in surface-local coordinates. In other words, + * the x and y, combined with the new surface size define in which + * directions the surface's size changes. Setting anything other + * than 0 as x and y arguments is discouraged, and should instead + * be replaced with using the separate wl_surface.offset request. + * + * When the bound wl_surface version is 5 or higher, passing any + * non-zero x or y is a protocol violation, and will result in an + * 'invalid_offset' error being raised. The x and y arguments are + * ignored and do not change the pending state. To achieve + * equivalent semantics, use wl_surface.offset. + * + * Surface contents are double-buffered state, see + * wl_surface.commit. + * + * The initial surface contents are void; there is no content. + * wl_surface.attach assigns the given wl_buffer as the pending + * wl_buffer. wl_surface.commit makes the pending wl_buffer the new + * surface contents, and the size of the surface becomes the size + * calculated from the wl_buffer, as described above. After commit, + * there is no pending buffer until the next attach. + * + * Committing a pending wl_buffer allows the compositor to read the + * pixels in the wl_buffer. The compositor may access the pixels at + * any time after the wl_surface.commit request. When the + * compositor will not access the pixels anymore, it will send the + * wl_buffer.release event. Only after receiving wl_buffer.release, + * the client may reuse the wl_buffer. A wl_buffer that has been + * attached and then replaced by another attach instead of + * committed will not receive a release event, and is not used by + * the compositor. + * + * If a pending wl_buffer has been committed to more than one + * wl_surface, the delivery of wl_buffer.release events becomes + * undefined. A well behaved client should not rely on + * wl_buffer.release events in this case. Instead, clients hitting + * this case should use wl_surface.get_release or use a protocol + * extension providing per-commit release notifications (if none of + * these options are available, a fallback can be implemented by + * creating multiple wl_buffer objects from the same backing + * storage). + * + * Destroying the wl_buffer after wl_buffer.release does not change + * the surface contents. Destroying the wl_buffer before + * wl_buffer.release is allowed as long as the underlying buffer + * storage isn't re-used (this can happen e.g. on client process + * termination). However, if the client destroys the wl_buffer + * before receiving the wl_buffer.release event and mutates the + * underlying buffer storage, the surface contents become undefined + * immediately. + * + * If wl_surface.attach is sent with a NULL wl_buffer, the + * following wl_surface.commit will remove the surface content. + * + * If a pending wl_buffer has been destroyed, the result is not + * specified. Many compositors are known to remove the surface + * content on the following wl_surface.commit, but this behaviour + * is not universal. Clients seeking to maximise compatibility + * should not destroy pending buffers and should ensure that they + * explicitly remove content from surfaces, even after destroying + * buffers. + * @param buffer buffer of surface contents + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ + void (*attach)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer, + int32_t x, + int32_t y); + /** + * mark part of the surface damaged + * + * This request is used to describe the regions where the pending + * buffer is different from the current surface contents, and where + * the surface therefore needs to be repainted. The compositor + * ignores the parts of the damage that fall outside of the + * surface. + * + * Damage is double-buffered state, see wl_surface.commit. + * + * The damage rectangle is specified in surface-local coordinates, + * where x and y specify the upper left corner of the damage + * rectangle. + * + * The initial value for pending damage is empty: no damage. + * wl_surface.damage adds pending damage: the new pending damage is + * the union of old pending damage and the given rectangle. + * + * wl_surface.commit assigns pending damage as the current damage, + * and clears pending damage. The server will clear the current + * damage as it repaints the surface. + * + * Note! New clients should not use this request. Instead damage + * can be posted with wl_surface.damage_buffer which uses buffer + * coordinates instead of surface coordinates. + * @param x surface-local x coordinate + * @param y surface-local y coordinate + * @param width width of damage rectangle + * @param height height of damage rectangle + */ + void (*damage)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * request a frame throttling hint + * + * Request a notification when it is a good time to start drawing + * a new frame, by creating a frame callback. This is useful for + * throttling redrawing operations, and driving animations. + * + * When a client is animating on a wl_surface, it can use the + * 'frame' request to get notified when it is a good time to draw + * and commit the next frame of animation. If the client commits an + * update earlier than that, it is likely that some updates will + * not make it to the display, and the client is wasting resources + * by drawing too often. + * + * The frame request will take effect on the next + * wl_surface.commit. The notification will only be posted for one + * frame unless requested again. For a wl_surface, the + * notifications are posted in the order the frame requests were + * committed. + * + * The server must send the notifications so that a client will not + * send excessive updates, while still allowing the highest + * possible update rate for clients that wait for the reply before + * drawing again. The server should give some time for the client + * to draw and commit after sending the frame callback events to + * let it hit the next output refresh. + * + * A server should avoid signaling the frame callbacks if the + * surface is not visible in any way, e.g. the surface is + * off-screen, or completely obscured by other opaque surfaces. + * + * The object returned by this request will be destroyed by the + * compositor after the callback is fired and as such the client + * must not attempt to use it after that point. + * + * The callback_data passed in the callback is the current time, in + * milliseconds, with an undefined base. + * @param callback callback object for the frame request + */ + void (*frame)(struct wl_client *client, + struct wl_resource *resource, + uint32_t callback); + /** + * set opaque region + * + * This request sets the region of the surface that contains + * opaque content. + * + * The opaque region is an optimization hint for the compositor + * that lets it optimize the redrawing of content behind opaque + * regions. Setting an opaque region is not required for correct + * behaviour, but marking transparent content as opaque will result + * in repaint artifacts. + * + * The opaque region is specified in surface-local coordinates. + * + * The compositor ignores the parts of the opaque region that fall + * outside of the surface. + * + * Opaque region is double-buffered state, see wl_surface.commit. + * + * wl_surface.set_opaque_region changes the pending opaque region. + * wl_surface.commit copies the pending region to the current + * region. Otherwise, the pending and current regions are never + * changed. + * + * The initial value for an opaque region is empty. Setting the + * pending opaque region has copy semantics, and the wl_region + * object can be destroyed immediately. A NULL wl_region causes the + * pending opaque region to be set to empty. + * @param region opaque region of the surface + */ + void (*set_opaque_region)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region); + /** + * set input region + * + * This request sets the region of the surface that can receive + * pointer and touch events. + * + * Input events happening outside of this region will try the next + * surface in the server surface stack. The compositor ignores the + * parts of the input region that fall outside of the surface. + * + * The input region is specified in surface-local coordinates. + * + * Input region is double-buffered state, see wl_surface.commit. + * + * wl_surface.set_input_region changes the pending input region. + * wl_surface.commit copies the pending region to the current + * region. Otherwise the pending and current regions are never + * changed, except cursor and icon surfaces are special cases, see + * wl_pointer.set_cursor and wl_data_device.start_drag. + * + * The initial value for an input region is infinite. That means + * the whole surface will accept input. Setting the pending input + * region has copy semantics, and the wl_region object can be + * destroyed immediately. A NULL wl_region causes the input region + * to be set to infinite. + * @param region input region of the surface + */ + void (*set_input_region)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region); + /** + * commit pending surface state + * + * Surface state (input, opaque, and damage regions, attached + * buffers, etc.) is double-buffered. Protocol requests modify the + * pending state, as opposed to the active state in use by the + * compositor. + * + * All requests that need a commit to become effective are + * documented to affect double-buffered state. + * + * Other interfaces may add further double-buffered surface state. + * + * A commit request atomically creates a Content Update (CU) from + * the pending state, even if the pending state has not been + * touched. The content update is placed at the end of a + * per-surface queue until it becomes active. After commit, the new + * pending state is as documented for each related request. + * + * A CU is either a Desync Content Update (DCU) or a Sync Content + * Update (SCU). If the surface is effectively synchronized at the + * commit request, it is a SCU, otherwise a DCU. + * + * When a surface transitions from effectively synchronized to + * effectively desynchronized, all SCUs in its queue which are not + * reachable by any DCU become DCUs and dependency edges from + * outside the queue to these CUs are removed. + * + * See wl_subsurface for the definition of 'effectively + * synchronized' and 'effectively desynchronized'. + * + * When a CU is placed in the queue, the CU has a dependency on the + * CU in front of it and to the SCU at end of the queue of every + * direct child surface if that SCU exists and does not have + * another dependent. This can form a directed acyclic graph of CUs + * with dependencies as edges. + * + * In addition to surface state, the CU can have constraints that + * must be satisfied before it can be applied. Other interfaces may + * add CU constraints. + * + * All DCUs which do not have a SCU in front of themselves in their + * queue, are candidates. If the graph that's reachable by a + * candidate does not have any unsatisfied constraints, the entire + * graph must be applied atomically. + * + * When a CU is applied, the wl_buffer is applied before all other + * state. This means that all coordinates in double-buffered state + * are relative to the newly attached wl_buffers, except for + * wl_surface.attach itself. If there is no newly attached + * wl_buffer, the coordinates are relative to the previous content + * update. + */ + void (*commit)(struct wl_client *client, + struct wl_resource *resource); + /** + * sets the buffer transformation + * + * This request sets the transformation that the client has + * already applied to the content of the buffer. The accepted + * values for the transform parameter are the values for + * wl_output.transform. + * + * The compositor applies the inverse of this transformation + * whenever it uses the buffer contents. + * + * Buffer transform is double-buffered state, see + * wl_surface.commit. + * + * A newly created surface has its buffer transformation set to + * normal. + * + * wl_surface.set_buffer_transform changes the pending buffer + * transformation. wl_surface.commit copies the pending buffer + * transformation to the current one. Otherwise, the pending and + * current values are never changed. + * + * The purpose of this request is to allow clients to render + * content according to the output transform, thus permitting the + * compositor to use certain optimizations even if the display is + * rotated. Using hardware overlays and scanning out a client + * buffer for fullscreen surfaces are examples of such + * optimizations. Those optimizations are highly dependent on the + * compositor implementation, so the use of this request should be + * considered on a case-by-case basis. + * + * Note that if the transform value includes 90 or 270 degree + * rotation, the width of the buffer will become the surface height + * and the height of the buffer will become the surface width. + * + * If transform is not one of the values from the + * wl_output.transform enum the invalid_transform protocol error is + * raised. + * @param transform transform for interpreting buffer contents + * @since 2 + */ + void (*set_buffer_transform)(struct wl_client *client, + struct wl_resource *resource, + int32_t transform); + /** + * sets the buffer scaling factor + * + * This request sets an optional scaling factor on how the + * compositor interprets the contents of the buffer attached to the + * window. + * + * Buffer scale is double-buffered state, see wl_surface.commit. + * + * A newly created surface has its buffer scale set to 1. + * + * wl_surface.set_buffer_scale changes the pending buffer scale. + * wl_surface.commit copies the pending buffer scale to the current + * one. Otherwise, the pending and current values are never + * changed. + * + * The purpose of this request is to allow clients to supply higher + * resolution buffer data for use on high resolution outputs. It is + * intended that you pick the same buffer scale as the scale of the + * output that the surface is displayed on. This means the + * compositor can avoid scaling when rendering the surface on that + * output. + * + * Note that if the scale is larger than 1, then you have to attach + * a buffer that is larger (by a factor of scale in each dimension) + * than the desired surface size. + * + * If scale is not greater than 0 the invalid_scale protocol error + * is raised. + * @param scale scale for interpreting buffer contents + * @since 3 + */ + void (*set_buffer_scale)(struct wl_client *client, + struct wl_resource *resource, + int32_t scale); + /** + * mark part of the surface damaged using buffer coordinates + * + * This request is used to describe the regions where the pending + * buffer is different from the current surface contents, and where + * the surface therefore needs to be repainted. The compositor + * ignores the parts of the damage that fall outside of the + * surface. + * + * Damage is double-buffered state, see wl_surface.commit. + * + * The damage rectangle is specified in buffer coordinates, where x + * and y specify the upper left corner of the damage rectangle. + * + * The initial value for pending damage is empty: no damage. + * wl_surface.damage_buffer adds pending damage: the new pending + * damage is the union of old pending damage and the given + * rectangle. + * + * wl_surface.commit assigns pending damage as the current damage, + * and clears pending damage. The server will clear the current + * damage as it repaints the surface. + * + * This request differs from wl_surface.damage in only one way - it + * takes damage in buffer coordinates instead of surface-local + * coordinates. While this generally is more intuitive than surface + * coordinates, it is especially desirable when using wp_viewport + * or when a drawing library (like EGL) is unaware of buffer scale + * and buffer transform. + * + * Note: Because buffer transformation changes and damage requests + * may be interleaved in the protocol stream, it is impossible to + * determine the actual mapping between surface and buffer damage + * until wl_surface.commit time. Therefore, compositors wishing to + * take both kinds of damage into account will have to accumulate + * damage from the two requests separately and only transform from + * one to the other after receiving the wl_surface.commit. + * @param x buffer-local x coordinate + * @param y buffer-local y coordinate + * @param width width of damage rectangle + * @param height height of damage rectangle + * @since 4 + */ + void (*damage_buffer)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * set the surface contents offset + * + * The x and y arguments specify the location of the new pending + * buffer's upper left corner, relative to the current buffer's + * upper left corner, in surface-local coordinates. In other words, + * the x and y, combined with the new surface size define in which + * directions the surface's size changes. + * + * The exact semantics of wl_surface.offset are role-specific. + * Refer to the documentation of specific roles for more + * information. + * + * Surface location offset is double-buffered state, see + * wl_surface.commit. + * + * This request is semantically equivalent to and the replaces the + * x and y arguments in the wl_surface.attach request in wl_surface + * versions prior to 5. See wl_surface.attach for details. + * @param x surface-local x coordinate + * @param y surface-local y coordinate + * @since 5 + */ + void (*offset)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y); + /** + * get a release callback + * + * Create a callback for the release of the buffer attached by + * the client with wl_surface.attach. + * + * The compositor will release the buffer when it has finished its + * usage of the underlying storage for the relevant commit. Once + * the client receives this event, and assuming the associated + * buffer is not pending release from other wl_surface.commit + * requests, the client can safely re-use the buffer. + * + * Release callbacks are double-buffered state, and will be + * associated with the pending buffer at wl_surface.commit time. + * + * The callback_data passed in the wl_callback.done event is unused + * and is always zero. + * + * Sending this request without attaching a non-null buffer in the + * same content update is a protocol error. The compositor will + * send the no_buffer error in this case. + * @param callback callback object for the release + * @since 7 + */ + void (*get_release)(struct wl_client *client, + struct wl_resource *resource, + uint32_t callback); +}; + +#define WL_SURFACE_ENTER 0 +#define WL_SURFACE_LEAVE 1 +#define WL_SURFACE_PREFERRED_BUFFER_SCALE 2 +#define WL_SURFACE_PREFERRED_BUFFER_TRANSFORM 3 + +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION 6 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION 6 + +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_ATTACH_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_DAMAGE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_FRAME_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_OPAQUE_REGION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_INPUT_REGION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_COMMIT_SINCE_VERSION 1 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_BUFFER_TRANSFORM_SINCE_VERSION 2 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION 3 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION 4 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_OFFSET_SINCE_VERSION 5 +/** + * @ingroup iface_wl_surface + */ +#define WL_SURFACE_GET_RELEASE_SINCE_VERSION 7 + +/** + * @ingroup iface_wl_surface + * Sends an enter event to the client owning the resource. + * @param resource_ The client's resource + * @param output output entered by the surface + */ +static inline void +wl_surface_send_enter(struct wl_resource *resource_, struct wl_resource *output) +{ + wl_resource_post_event(resource_, WL_SURFACE_ENTER, output); +} + +/** + * @ingroup iface_wl_surface + * Sends an leave event to the client owning the resource. + * @param resource_ The client's resource + * @param output output left by the surface + */ +static inline void +wl_surface_send_leave(struct wl_resource *resource_, struct wl_resource *output) +{ + wl_resource_post_event(resource_, WL_SURFACE_LEAVE, output); +} + +/** + * @ingroup iface_wl_surface + * Sends an preferred_buffer_scale event to the client owning the resource. + * @param resource_ The client's resource + * @param factor preferred scaling factor + */ +static inline void +wl_surface_send_preferred_buffer_scale(struct wl_resource *resource_, int32_t factor) +{ + wl_resource_post_event(resource_, WL_SURFACE_PREFERRED_BUFFER_SCALE, factor); +} + +/** + * @ingroup iface_wl_surface + * Sends an preferred_buffer_transform event to the client owning the resource. + * @param resource_ The client's resource + * @param transform preferred transform + */ +static inline void +wl_surface_send_preferred_buffer_transform(struct wl_resource *resource_, uint32_t transform) +{ + wl_resource_post_event(resource_, WL_SURFACE_PREFERRED_BUFFER_TRANSFORM, transform); +} + +#ifndef WL_SEAT_CAPABILITY_ENUM +#define WL_SEAT_CAPABILITY_ENUM +/** + * @ingroup iface_wl_seat + * seat capability bitmask + * + * This is a bitmask of capabilities this seat has; if a member is + * set, then it is present on the seat. + */ +enum wl_seat_capability { + /** + * the seat has pointer devices + */ + WL_SEAT_CAPABILITY_POINTER = 1, + /** + * the seat has one or more keyboards + */ + WL_SEAT_CAPABILITY_KEYBOARD = 2, + /** + * the seat has touch devices + */ + WL_SEAT_CAPABILITY_TOUCH = 4, +}; +#endif /* WL_SEAT_CAPABILITY_ENUM */ + +#ifndef WL_SEAT_CAPABILITY_ENUM_IS_VALID +#define WL_SEAT_CAPABILITY_ENUM_IS_VALID +/** + * @ingroup iface_wl_seat + * Validate a wl_seat capability value. + * + * @return true on success, false on error. + * @ref wl_seat_capability + */ +static inline bool +wl_seat_capability_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= WL_SEAT_CAPABILITY_POINTER; + if (version >= 1) + valid |= WL_SEAT_CAPABILITY_KEYBOARD; + if (version >= 1) + valid |= WL_SEAT_CAPABILITY_TOUCH; + return (value & ~valid) == 0; +} +#endif /* WL_SEAT_CAPABILITY_ENUM_IS_VALID */ + +#ifndef WL_SEAT_ERROR_ENUM +#define WL_SEAT_ERROR_ENUM +/** + * @ingroup iface_wl_seat + * wl_seat error values + * + * These errors can be emitted in response to wl_seat requests. + */ +enum wl_seat_error { + /** + * get_pointer, get_keyboard or get_touch called on seat without the matching capability + */ + WL_SEAT_ERROR_MISSING_CAPABILITY = 0, +}; +#endif /* WL_SEAT_ERROR_ENUM */ + +#ifndef WL_SEAT_ERROR_ENUM_IS_VALID +#define WL_SEAT_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_seat + * Validate a wl_seat error value. + * + * @return true on success, false on error. + * @ref wl_seat_error + */ +static inline bool +wl_seat_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SEAT_ERROR_MISSING_CAPABILITY: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SEAT_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_seat + * @struct wl_seat_interface + */ +struct wl_seat_interface { + /** + * return pointer object + * + * The ID provided will be initialized to the wl_pointer + * interface for this seat. + * + * This request only takes effect if the seat has the pointer + * capability, or has had the pointer capability in the past. It is + * a protocol violation to issue this request on a seat that has + * never had the pointer capability. The missing_capability error + * will be sent in this case. + * @param id seat pointer + */ + void (*get_pointer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * return keyboard object + * + * The ID provided will be initialized to the wl_keyboard + * interface for this seat. + * + * This request only takes effect if the seat has the keyboard + * capability, or has had the keyboard capability in the past. It + * is a protocol violation to issue this request on a seat that has + * never had the keyboard capability. The missing_capability error + * will be sent in this case. + * @param id seat keyboard + */ + void (*get_keyboard)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * return touch object + * + * The ID provided will be initialized to the wl_touch interface + * for this seat. + * + * This request only takes effect if the seat has the touch + * capability, or has had the touch capability in the past. It is a + * protocol violation to issue this request on a seat that has + * never had the touch capability. The missing_capability error + * will be sent in this case. + * @param id seat touch interface + */ + void (*get_touch)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * release the seat object + * + * Using this request a client can tell the server that it is not + * going to use the seat object anymore. + * @since 5 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_SEAT_CAPABILITIES 0 +#define WL_SEAT_NAME 1 + +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_CAPABILITIES_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_NAME_SINCE_VERSION 2 + +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_GET_POINTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_GET_KEYBOARD_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_GET_TOUCH_SINCE_VERSION 1 +/** + * @ingroup iface_wl_seat + */ +#define WL_SEAT_RELEASE_SINCE_VERSION 5 + +/** + * @ingroup iface_wl_seat + * Sends an capabilities event to the client owning the resource. + * @param resource_ The client's resource + * @param capabilities capabilities of the seat + */ +static inline void +wl_seat_send_capabilities(struct wl_resource *resource_, uint32_t capabilities) +{ + wl_resource_post_event(resource_, WL_SEAT_CAPABILITIES, capabilities); +} + +/** + * @ingroup iface_wl_seat + * Sends an name event to the client owning the resource. + * @param resource_ The client's resource + * @param name seat identifier + */ +static inline void +wl_seat_send_name(struct wl_resource *resource_, const char *name) +{ + wl_resource_post_event(resource_, WL_SEAT_NAME, name); +} + +#ifndef WL_POINTER_ERROR_ENUM +#define WL_POINTER_ERROR_ENUM +enum wl_pointer_error { + /** + * given wl_surface has another role + */ + WL_POINTER_ERROR_ROLE = 0, +}; +#endif /* WL_POINTER_ERROR_ENUM */ + +#ifndef WL_POINTER_ERROR_ENUM_IS_VALID +#define WL_POINTER_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_pointer + * Validate a wl_pointer error value. + * + * @return true on success, false on error. + * @ref wl_pointer_error + */ +static inline bool +wl_pointer_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_POINTER_ERROR_ROLE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_POINTER_ERROR_ENUM_IS_VALID */ + +#ifndef WL_POINTER_BUTTON_STATE_ENUM +#define WL_POINTER_BUTTON_STATE_ENUM +/** + * @ingroup iface_wl_pointer + * physical button state + * + * Describes the physical state of a button that produced the button + * event. + */ +enum wl_pointer_button_state { + /** + * the button is not pressed + */ + WL_POINTER_BUTTON_STATE_RELEASED = 0, + /** + * the button is pressed + */ + WL_POINTER_BUTTON_STATE_PRESSED = 1, +}; +#endif /* WL_POINTER_BUTTON_STATE_ENUM */ + +#ifndef WL_POINTER_BUTTON_STATE_ENUM_IS_VALID +#define WL_POINTER_BUTTON_STATE_ENUM_IS_VALID +/** + * @ingroup iface_wl_pointer + * Validate a wl_pointer button_state value. + * + * @return true on success, false on error. + * @ref wl_pointer_button_state + */ +static inline bool +wl_pointer_button_state_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_POINTER_BUTTON_STATE_RELEASED: + return version >= 1; + case WL_POINTER_BUTTON_STATE_PRESSED: + return version >= 1; + default: + return false; + } +} +#endif /* WL_POINTER_BUTTON_STATE_ENUM_IS_VALID */ + +#ifndef WL_POINTER_AXIS_ENUM +#define WL_POINTER_AXIS_ENUM +/** + * @ingroup iface_wl_pointer + * axis types + * + * Describes the axis types of scroll events. + */ +enum wl_pointer_axis { + /** + * vertical axis + */ + WL_POINTER_AXIS_VERTICAL_SCROLL = 0, + /** + * horizontal axis + */ + WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1, +}; +#endif /* WL_POINTER_AXIS_ENUM */ + +#ifndef WL_POINTER_AXIS_ENUM_IS_VALID +#define WL_POINTER_AXIS_ENUM_IS_VALID +/** + * @ingroup iface_wl_pointer + * Validate a wl_pointer axis value. + * + * @return true on success, false on error. + * @ref wl_pointer_axis + */ +static inline bool +wl_pointer_axis_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + return version >= 1; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + return version >= 1; + default: + return false; + } +} +#endif /* WL_POINTER_AXIS_ENUM_IS_VALID */ + +#ifndef WL_POINTER_AXIS_SOURCE_ENUM +#define WL_POINTER_AXIS_SOURCE_ENUM +/** + * @ingroup iface_wl_pointer + * axis source types + * + * Describes the source types for axis events. This indicates to the + * client how an axis event was physically generated; a client may + * adjust the user interface accordingly. For example, scroll events + * from a "finger" source may be in a smooth coordinate space with + * kinetic scrolling whereas a "wheel" source may be in discrete steps + * of a number of lines. + * + * The "continuous" axis source is a device generating events in a + * continuous coordinate space, but using something other than a + * finger. One example for this source is button-based scrolling where + * the vertical motion of a device is converted to scroll events while + * a button is held down. + * + * The "wheel tilt" axis source indicates that the actual device is a + * wheel but the scroll event is not caused by a rotation but a + * (usually sideways) tilt of the wheel. + */ +enum wl_pointer_axis_source { + /** + * a physical wheel rotation + */ + WL_POINTER_AXIS_SOURCE_WHEEL = 0, + /** + * finger on a touch surface + */ + WL_POINTER_AXIS_SOURCE_FINGER = 1, + /** + * continuous coordinate space + */ + WL_POINTER_AXIS_SOURCE_CONTINUOUS = 2, + /** + * a physical wheel tilt + * @since 6 + */ + WL_POINTER_AXIS_SOURCE_WHEEL_TILT = 3, +}; +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION 6 +#endif /* WL_POINTER_AXIS_SOURCE_ENUM */ + +#ifndef WL_POINTER_AXIS_SOURCE_ENUM_IS_VALID +#define WL_POINTER_AXIS_SOURCE_ENUM_IS_VALID +/** + * @ingroup iface_wl_pointer + * Validate a wl_pointer axis_source value. + * + * @return true on success, false on error. + * @ref wl_pointer_axis_source + */ +static inline bool +wl_pointer_axis_source_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_POINTER_AXIS_SOURCE_WHEEL: + return version >= 1; + case WL_POINTER_AXIS_SOURCE_FINGER: + return version >= 1; + case WL_POINTER_AXIS_SOURCE_CONTINUOUS: + return version >= 1; + case WL_POINTER_AXIS_SOURCE_WHEEL_TILT: + return version >= 6; + default: + return false; + } +} +#endif /* WL_POINTER_AXIS_SOURCE_ENUM_IS_VALID */ + +#ifndef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM +#define WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM +/** + * @ingroup iface_wl_pointer + * axis relative direction + * + * This specifies the direction of the physical motion that caused a + * wl_pointer.axis event, relative to the wl_pointer.axis direction. + */ +enum wl_pointer_axis_relative_direction { + /** + * physical motion matches axis direction + */ + WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL = 0, + /** + * physical motion is the inverse of the axis direction + */ + WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED = 1, +}; +#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */ + +#ifndef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM_IS_VALID +#define WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM_IS_VALID +/** + * @ingroup iface_wl_pointer + * Validate a wl_pointer axis_relative_direction value. + * + * @return true on success, false on error. + * @ref wl_pointer_axis_relative_direction + */ +static inline bool +wl_pointer_axis_relative_direction_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL: + return version >= 1; + case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED: + return version >= 1; + default: + return false; + } +} +#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_pointer + * @struct wl_pointer_interface + */ +struct wl_pointer_interface { + /** + * set the pointer surface + * + * Set the pointer surface, i.e., the surface that contains the + * pointer image (cursor). This request gives the surface the role + * of a cursor. If the surface already has another role, it raises + * a protocol error. + * + * The cursor actually changes only if the pointer focus for this + * device is one of the requesting client's surfaces or the surface + * parameter is the current pointer surface. If there was a + * previous surface set with this request it is replaced. If + * surface is NULL, the pointer image is hidden. + * + * The parameters hotspot_x and hotspot_y define the position of + * the pointer surface relative to the pointer location. Its + * top-left corner is always at (x, y) - (hotspot_x, hotspot_y), + * where (x, y) are the coordinates of the pointer location, in + * surface-local coordinates. + * + * On wl_surface.offset requests to the pointer surface, hotspot_x + * and hotspot_y are decremented by the x and y parameters passed + * to the request. The offset must be applied by wl_surface.commit + * as usual. + * + * The hotspot can also be updated by passing the currently set + * pointer surface to this request with new values for hotspot_x + * and hotspot_y. + * + * The input region is ignored for wl_surfaces with the role of a + * cursor. When the use as a cursor ends, the wl_surface is + * unmapped. + * + * The serial parameter must match the latest wl_pointer.enter + * serial number sent to the client. Otherwise the request will be + * ignored. + * @param serial serial number of the enter event + * @param surface pointer surface + * @param hotspot_x surface-local x coordinate + * @param hotspot_y surface-local y coordinate + */ + void (*set_cursor)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + struct wl_resource *surface, + int32_t hotspot_x, + int32_t hotspot_y); + /** + * release the pointer object + * + * Using this request a client can tell the server that it is not + * going to use the pointer object anymore. + * + * This request destroys the pointer proxy object, so clients must + * not call wl_pointer_destroy() after using this request. + * @since 3 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_POINTER_ENTER 0 +#define WL_POINTER_LEAVE 1 +#define WL_POINTER_MOTION 2 +#define WL_POINTER_BUTTON 3 +#define WL_POINTER_AXIS 4 +#define WL_POINTER_FRAME 5 +#define WL_POINTER_AXIS_SOURCE 6 +#define WL_POINTER_AXIS_STOP 7 +#define WL_POINTER_AXIS_DISCRETE 8 +#define WL_POINTER_AXIS_VALUE120 9 +#define WL_POINTER_AXIS_RELATIVE_DIRECTION 10 +#define WL_POINTER_WARP 11 + +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_MOTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_BUTTON_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_FRAME_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_SOURCE_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_STOP_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_DISCRETE_SINCE_VERSION 5 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_VALUE120_SINCE_VERSION 8 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION 9 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_WARP_SINCE_VERSION 11 + +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_SET_CURSOR_SINCE_VERSION 1 +/** + * @ingroup iface_wl_pointer + */ +#define WL_POINTER_RELEASE_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_pointer + * Sends an enter event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the enter event + * @param surface surface entered by the pointer + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + */ +static inline void +wl_pointer_send_enter(struct wl_resource *resource_, uint32_t serial, struct wl_resource *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + wl_resource_post_event(resource_, WL_POINTER_ENTER, serial, surface, surface_x, surface_y); +} + +/** + * @ingroup iface_wl_pointer + * Sends an leave event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the leave event + * @param surface surface left by the pointer + */ +static inline void +wl_pointer_send_leave(struct wl_resource *resource_, uint32_t serial, struct wl_resource *surface) +{ + wl_resource_post_event(resource_, WL_POINTER_LEAVE, serial, surface); +} + +/** + * @ingroup iface_wl_pointer + * Sends an motion event to the client owning the resource. + * @param resource_ The client's resource + * @param time timestamp with millisecond granularity + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + */ +static inline void +wl_pointer_send_motion(struct wl_resource *resource_, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + wl_resource_post_event(resource_, WL_POINTER_MOTION, time, surface_x, surface_y); +} + +/** + * @ingroup iface_wl_pointer + * Sends an button event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the button event + * @param time timestamp with millisecond granularity + * @param button button that produced the event + * @param state physical state of the button + */ +static inline void +wl_pointer_send_button(struct wl_resource *resource_, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) +{ + wl_resource_post_event(resource_, WL_POINTER_BUTTON, serial, time, button, state); +} + +/** + * @ingroup iface_wl_pointer + * Sends an axis event to the client owning the resource. + * @param resource_ The client's resource + * @param time timestamp with millisecond granularity + * @param axis axis type + * @param value length of vector in surface-local coordinate space + */ +static inline void +wl_pointer_send_axis(struct wl_resource *resource_, uint32_t time, uint32_t axis, wl_fixed_t value) +{ + wl_resource_post_event(resource_, WL_POINTER_AXIS, time, axis, value); +} + +/** + * @ingroup iface_wl_pointer + * Sends an frame event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_pointer_send_frame(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_POINTER_FRAME); +} + +/** + * @ingroup iface_wl_pointer + * Sends an axis_source event to the client owning the resource. + * @param resource_ The client's resource + * @param axis_source source of the axis event + */ +static inline void +wl_pointer_send_axis_source(struct wl_resource *resource_, uint32_t axis_source) +{ + wl_resource_post_event(resource_, WL_POINTER_AXIS_SOURCE, axis_source); +} + +/** + * @ingroup iface_wl_pointer + * Sends an axis_stop event to the client owning the resource. + * @param resource_ The client's resource + * @param time timestamp with millisecond granularity + * @param axis the axis stopped with this event + */ +static inline void +wl_pointer_send_axis_stop(struct wl_resource *resource_, uint32_t time, uint32_t axis) +{ + wl_resource_post_event(resource_, WL_POINTER_AXIS_STOP, time, axis); +} + +/** + * @ingroup iface_wl_pointer + * Sends an axis_discrete event to the client owning the resource. + * @param resource_ The client's resource + * @param axis axis type + * @param discrete number of steps + */ +static inline void +wl_pointer_send_axis_discrete(struct wl_resource *resource_, uint32_t axis, int32_t discrete) +{ + wl_resource_post_event(resource_, WL_POINTER_AXIS_DISCRETE, axis, discrete); +} + +/** + * @ingroup iface_wl_pointer + * Sends an axis_value120 event to the client owning the resource. + * @param resource_ The client's resource + * @param axis axis type + * @param value120 scroll distance as fraction of 120 + */ +static inline void +wl_pointer_send_axis_value120(struct wl_resource *resource_, uint32_t axis, int32_t value120) +{ + wl_resource_post_event(resource_, WL_POINTER_AXIS_VALUE120, axis, value120); +} + +/** + * @ingroup iface_wl_pointer + * Sends an axis_relative_direction event to the client owning the resource. + * @param resource_ The client's resource + * @param axis axis type + * @param direction physical direction relative to axis motion + */ +static inline void +wl_pointer_send_axis_relative_direction(struct wl_resource *resource_, uint32_t axis, uint32_t direction) +{ + wl_resource_post_event(resource_, WL_POINTER_AXIS_RELATIVE_DIRECTION, axis, direction); +} + +/** + * @ingroup iface_wl_pointer + * Sends an warp event to the client owning the resource. + * @param resource_ The client's resource + * @param surface_x surface-local x coordinate + * @param surface_y surface-local y coordinate + */ +static inline void +wl_pointer_send_warp(struct wl_resource *resource_, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + wl_resource_post_event(resource_, WL_POINTER_WARP, surface_x, surface_y); +} + +#ifndef WL_KEYBOARD_KEYMAP_FORMAT_ENUM +#define WL_KEYBOARD_KEYMAP_FORMAT_ENUM +/** + * @ingroup iface_wl_keyboard + * keyboard mapping format + * + * This specifies the format of the keymap provided to the + * client with the wl_keyboard.keymap event. + */ +enum wl_keyboard_keymap_format { + /** + * no keymap; client must understand how to interpret the raw keycode + */ + WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP = 0, + /** + * libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode + */ + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 = 1, +}; +#endif /* WL_KEYBOARD_KEYMAP_FORMAT_ENUM */ + +#ifndef WL_KEYBOARD_KEYMAP_FORMAT_ENUM_IS_VALID +#define WL_KEYBOARD_KEYMAP_FORMAT_ENUM_IS_VALID +/** + * @ingroup iface_wl_keyboard + * Validate a wl_keyboard keymap_format value. + * + * @return true on success, false on error. + * @ref wl_keyboard_keymap_format + */ +static inline bool +wl_keyboard_keymap_format_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: + return version >= 1; + case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1: + return version >= 1; + default: + return false; + } +} +#endif /* WL_KEYBOARD_KEYMAP_FORMAT_ENUM_IS_VALID */ + +#ifndef WL_KEYBOARD_KEY_STATE_ENUM +#define WL_KEYBOARD_KEY_STATE_ENUM +/** + * @ingroup iface_wl_keyboard + * physical key state + * + * Describes the physical state of a key that produced the key event. + * + * Since version 10, the key can be in a "repeated" pseudo-state which + * means the same as "pressed", but is used to signal repetition in the + * key event. + * + * The key may only enter the repeated state after entering the pressed + * state and before entering the released state. This event may be + * generated multiple times while the key is down. + */ +enum wl_keyboard_key_state { + /** + * key is not pressed + */ + WL_KEYBOARD_KEY_STATE_RELEASED = 0, + /** + * key is pressed + */ + WL_KEYBOARD_KEY_STATE_PRESSED = 1, + /** + * key was repeated + * @since 10 + */ + WL_KEYBOARD_KEY_STATE_REPEATED = 2, +}; +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION 10 +#endif /* WL_KEYBOARD_KEY_STATE_ENUM */ + +#ifndef WL_KEYBOARD_KEY_STATE_ENUM_IS_VALID +#define WL_KEYBOARD_KEY_STATE_ENUM_IS_VALID +/** + * @ingroup iface_wl_keyboard + * Validate a wl_keyboard key_state value. + * + * @return true on success, false on error. + * @ref wl_keyboard_key_state + */ +static inline bool +wl_keyboard_key_state_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + return version >= 1; + case WL_KEYBOARD_KEY_STATE_PRESSED: + return version >= 1; + case WL_KEYBOARD_KEY_STATE_REPEATED: + return version >= 10; + default: + return false; + } +} +#endif /* WL_KEYBOARD_KEY_STATE_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_keyboard + * @struct wl_keyboard_interface + */ +struct wl_keyboard_interface { + /** + * release the keyboard object + * + * + * @since 3 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_KEYBOARD_KEYMAP 0 +#define WL_KEYBOARD_ENTER 1 +#define WL_KEYBOARD_LEAVE 2 +#define WL_KEYBOARD_KEY 3 +#define WL_KEYBOARD_MODIFIERS 4 +#define WL_KEYBOARD_REPEAT_INFO 5 + +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_KEYMAP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_ENTER_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_LEAVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_KEY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_MODIFIERS_SINCE_VERSION 1 +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION 4 + +/** + * @ingroup iface_wl_keyboard + */ +#define WL_KEYBOARD_RELEASE_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_keyboard + * Sends an keymap event to the client owning the resource. + * @param resource_ The client's resource + * @param format keymap format + * @param fd keymap file descriptor + * @param size keymap size, in bytes + */ +static inline void +wl_keyboard_send_keymap(struct wl_resource *resource_, uint32_t format, int32_t fd, uint32_t size) +{ + wl_resource_post_event(resource_, WL_KEYBOARD_KEYMAP, format, fd, size); +} + +/** + * @ingroup iface_wl_keyboard + * Sends an enter event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the enter event + * @param surface surface gaining keyboard focus + * @param keys the keys currently logically down + */ +static inline void +wl_keyboard_send_enter(struct wl_resource *resource_, uint32_t serial, struct wl_resource *surface, struct wl_array *keys) +{ + wl_resource_post_event(resource_, WL_KEYBOARD_ENTER, serial, surface, keys); +} + +/** + * @ingroup iface_wl_keyboard + * Sends an leave event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the leave event + * @param surface surface that lost keyboard focus + */ +static inline void +wl_keyboard_send_leave(struct wl_resource *resource_, uint32_t serial, struct wl_resource *surface) +{ + wl_resource_post_event(resource_, WL_KEYBOARD_LEAVE, serial, surface); +} + +/** + * @ingroup iface_wl_keyboard + * Sends an key event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the key event + * @param time timestamp with millisecond granularity + * @param key key that produced the event + * @param state physical state of the key + */ +static inline void +wl_keyboard_send_key(struct wl_resource *resource_, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + wl_resource_post_event(resource_, WL_KEYBOARD_KEY, serial, time, key, state); +} + +/** + * @ingroup iface_wl_keyboard + * Sends an modifiers event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the modifiers event + * @param mods_depressed depressed modifiers + * @param mods_latched latched modifiers + * @param mods_locked locked modifiers + * @param group keyboard layout + */ +static inline void +wl_keyboard_send_modifiers(struct wl_resource *resource_, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) +{ + wl_resource_post_event(resource_, WL_KEYBOARD_MODIFIERS, serial, mods_depressed, mods_latched, mods_locked, group); +} + +/** + * @ingroup iface_wl_keyboard + * Sends an repeat_info event to the client owning the resource. + * @param resource_ The client's resource + * @param rate the rate of repeating keys in characters per second + * @param delay delay in milliseconds since key down until repeating starts + */ +static inline void +wl_keyboard_send_repeat_info(struct wl_resource *resource_, int32_t rate, int32_t delay) +{ + wl_resource_post_event(resource_, WL_KEYBOARD_REPEAT_INFO, rate, delay); +} + +/** + * @ingroup iface_wl_touch + * @struct wl_touch_interface + */ +struct wl_touch_interface { + /** + * release the touch object + * + * + * @since 3 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_TOUCH_DOWN 0 +#define WL_TOUCH_UP 1 +#define WL_TOUCH_MOTION 2 +#define WL_TOUCH_FRAME 3 +#define WL_TOUCH_CANCEL 4 +#define WL_TOUCH_SHAPE 5 +#define WL_TOUCH_ORIENTATION 6 + +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_DOWN_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_UP_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_MOTION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_FRAME_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_CANCEL_SINCE_VERSION 1 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_SHAPE_SINCE_VERSION 6 +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_ORIENTATION_SINCE_VERSION 6 + +/** + * @ingroup iface_wl_touch + */ +#define WL_TOUCH_RELEASE_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_touch + * Sends an down event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the touch down event + * @param time timestamp with millisecond granularity + * @param surface surface touched + * @param id the unique ID of this touch point + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ +static inline void +wl_touch_send_down(struct wl_resource *resource_, uint32_t serial, uint32_t time, struct wl_resource *surface, int32_t id, wl_fixed_t x, wl_fixed_t y) +{ + wl_resource_post_event(resource_, WL_TOUCH_DOWN, serial, time, surface, id, x, y); +} + +/** + * @ingroup iface_wl_touch + * Sends an up event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial number of the touch up event + * @param time timestamp with millisecond granularity + * @param id the unique ID of this touch point + */ +static inline void +wl_touch_send_up(struct wl_resource *resource_, uint32_t serial, uint32_t time, int32_t id) +{ + wl_resource_post_event(resource_, WL_TOUCH_UP, serial, time, id); +} + +/** + * @ingroup iface_wl_touch + * Sends an motion event to the client owning the resource. + * @param resource_ The client's resource + * @param time timestamp with millisecond granularity + * @param id the unique ID of this touch point + * @param x surface-local x coordinate + * @param y surface-local y coordinate + */ +static inline void +wl_touch_send_motion(struct wl_resource *resource_, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) +{ + wl_resource_post_event(resource_, WL_TOUCH_MOTION, time, id, x, y); +} + +/** + * @ingroup iface_wl_touch + * Sends an frame event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_touch_send_frame(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_TOUCH_FRAME); +} + +/** + * @ingroup iface_wl_touch + * Sends an cancel event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_touch_send_cancel(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_TOUCH_CANCEL); +} + +/** + * @ingroup iface_wl_touch + * Sends an shape event to the client owning the resource. + * @param resource_ The client's resource + * @param id the unique ID of this touch point + * @param major length of the major axis in surface-local coordinates + * @param minor length of the minor axis in surface-local coordinates + */ +static inline void +wl_touch_send_shape(struct wl_resource *resource_, int32_t id, wl_fixed_t major, wl_fixed_t minor) +{ + wl_resource_post_event(resource_, WL_TOUCH_SHAPE, id, major, minor); +} + +/** + * @ingroup iface_wl_touch + * Sends an orientation event to the client owning the resource. + * @param resource_ The client's resource + * @param id the unique ID of this touch point + * @param orientation angle between major axis and positive surface y-axis in degrees + */ +static inline void +wl_touch_send_orientation(struct wl_resource *resource_, int32_t id, wl_fixed_t orientation) +{ + wl_resource_post_event(resource_, WL_TOUCH_ORIENTATION, id, orientation); +} + +#ifndef WL_OUTPUT_SUBPIXEL_ENUM +#define WL_OUTPUT_SUBPIXEL_ENUM +/** + * @ingroup iface_wl_output + * subpixel geometry information + * + * This enumeration describes how the physical + * pixels on an output are laid out. + */ +enum wl_output_subpixel { + /** + * unknown geometry + */ + WL_OUTPUT_SUBPIXEL_UNKNOWN = 0, + /** + * no geometry + */ + WL_OUTPUT_SUBPIXEL_NONE = 1, + /** + * horizontal RGB + */ + WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB = 2, + /** + * horizontal BGR + */ + WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR = 3, + /** + * vertical RGB + */ + WL_OUTPUT_SUBPIXEL_VERTICAL_RGB = 4, + /** + * vertical BGR + */ + WL_OUTPUT_SUBPIXEL_VERTICAL_BGR = 5, +}; +#endif /* WL_OUTPUT_SUBPIXEL_ENUM */ + +#ifndef WL_OUTPUT_SUBPIXEL_ENUM_IS_VALID +#define WL_OUTPUT_SUBPIXEL_ENUM_IS_VALID +/** + * @ingroup iface_wl_output + * Validate a wl_output subpixel value. + * + * @return true on success, false on error. + * @ref wl_output_subpixel + */ +static inline bool +wl_output_subpixel_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_OUTPUT_SUBPIXEL_UNKNOWN: + return version >= 1; + case WL_OUTPUT_SUBPIXEL_NONE: + return version >= 1; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: + return version >= 1; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: + return version >= 1; + case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: + return version >= 1; + case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: + return version >= 1; + default: + return false; + } +} +#endif /* WL_OUTPUT_SUBPIXEL_ENUM_IS_VALID */ + +#ifndef WL_OUTPUT_TRANSFORM_ENUM +#define WL_OUTPUT_TRANSFORM_ENUM +/** + * @ingroup iface_wl_output + * transformation applied to buffer contents + * + * This describes transformations that clients and compositors apply to + * buffer contents. + * + * The flipped values correspond to an initial flip around a + * vertical axis followed by rotation. + * + * The purpose is mainly to allow clients to render accordingly and + * tell the compositor, so that for fullscreen surfaces, the + * compositor will still be able to scan out directly from client + * surfaces. + */ +enum wl_output_transform { + /** + * no transform + */ + WL_OUTPUT_TRANSFORM_NORMAL = 0, + /** + * 90 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_90 = 1, + /** + * 180 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_180 = 2, + /** + * 270 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_270 = 3, + /** + * 180 degree flip around a vertical axis + */ + WL_OUTPUT_TRANSFORM_FLIPPED = 4, + /** + * flip and rotate 90 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_FLIPPED_90 = 5, + /** + * flip and rotate 180 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_FLIPPED_180 = 6, + /** + * flip and rotate 270 degrees counter-clockwise + */ + WL_OUTPUT_TRANSFORM_FLIPPED_270 = 7, +}; +#endif /* WL_OUTPUT_TRANSFORM_ENUM */ + +#ifndef WL_OUTPUT_TRANSFORM_ENUM_IS_VALID +#define WL_OUTPUT_TRANSFORM_ENUM_IS_VALID +/** + * @ingroup iface_wl_output + * Validate a wl_output transform value. + * + * @return true on success, false on error. + * @ref wl_output_transform + */ +static inline bool +wl_output_transform_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_OUTPUT_TRANSFORM_NORMAL: + return version >= 1; + case WL_OUTPUT_TRANSFORM_90: + return version >= 1; + case WL_OUTPUT_TRANSFORM_180: + return version >= 1; + case WL_OUTPUT_TRANSFORM_270: + return version >= 1; + case WL_OUTPUT_TRANSFORM_FLIPPED: + return version >= 1; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + return version >= 1; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + return version >= 1; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + return version >= 1; + default: + return false; + } +} +#endif /* WL_OUTPUT_TRANSFORM_ENUM_IS_VALID */ + +#ifndef WL_OUTPUT_MODE_ENUM +#define WL_OUTPUT_MODE_ENUM +/** + * @ingroup iface_wl_output + * mode information + * + * These flags describe properties of an output mode. + * They are used in the flags bitfield of the mode event. + */ +enum wl_output_mode { + /** + * indicates this is the current mode + */ + WL_OUTPUT_MODE_CURRENT = 0x1, + /** + * indicates this is the preferred mode + */ + WL_OUTPUT_MODE_PREFERRED = 0x2, +}; +#endif /* WL_OUTPUT_MODE_ENUM */ + +#ifndef WL_OUTPUT_MODE_ENUM_IS_VALID +#define WL_OUTPUT_MODE_ENUM_IS_VALID +/** + * @ingroup iface_wl_output + * Validate a wl_output mode value. + * + * @return true on success, false on error. + * @ref wl_output_mode + */ +static inline bool +wl_output_mode_is_valid(uint32_t value, uint32_t version) { + uint32_t valid = 0; + if (version >= 1) + valid |= WL_OUTPUT_MODE_CURRENT; + if (version >= 1) + valid |= WL_OUTPUT_MODE_PREFERRED; + return (value & ~valid) == 0; +} +#endif /* WL_OUTPUT_MODE_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_output + * @struct wl_output_interface + */ +struct wl_output_interface { + /** + * release the output object + * + * Using this request a client can tell the server that it is not + * going to use the output object anymore. + * @since 3 + */ + void (*release)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define WL_OUTPUT_GEOMETRY 0 +#define WL_OUTPUT_MODE 1 +#define WL_OUTPUT_DONE 2 +#define WL_OUTPUT_SCALE 3 +#define WL_OUTPUT_NAME 4 +#define WL_OUTPUT_DESCRIPTION 5 + +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_MODE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_DONE_SINCE_VERSION 2 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_SCALE_SINCE_VERSION 2 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_NAME_SINCE_VERSION 4 +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_DESCRIPTION_SINCE_VERSION 4 + +/** + * @ingroup iface_wl_output + */ +#define WL_OUTPUT_RELEASE_SINCE_VERSION 3 + +/** + * @ingroup iface_wl_output + * Sends an geometry event to the client owning the resource. + * @param resource_ The client's resource + * @param x x position within the global compositor space + * @param y y position within the global compositor space + * @param physical_width width in millimeters of the output + * @param physical_height height in millimeters of the output + * @param subpixel subpixel orientation of the output + * @param make textual description of the manufacturer + * @param model textual description of the model + * @param transform additional transformation applied to buffer contents during presentation + */ +static inline void +wl_output_send_geometry(struct wl_resource *resource_, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) +{ + wl_resource_post_event(resource_, WL_OUTPUT_GEOMETRY, x, y, physical_width, physical_height, subpixel, make, model, transform); +} + +/** + * @ingroup iface_wl_output + * Sends an mode event to the client owning the resource. + * @param resource_ The client's resource + * @param flags bitfield of mode flags + * @param width width of the mode in hardware units + * @param height height of the mode in hardware units + * @param refresh vertical refresh rate in mHz + */ +static inline void +wl_output_send_mode(struct wl_resource *resource_, uint32_t flags, int32_t width, int32_t height, int32_t refresh) +{ + wl_resource_post_event(resource_, WL_OUTPUT_MODE, flags, width, height, refresh); +} + +/** + * @ingroup iface_wl_output + * Sends an done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +wl_output_send_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, WL_OUTPUT_DONE); +} + +/** + * @ingroup iface_wl_output + * Sends an scale event to the client owning the resource. + * @param resource_ The client's resource + * @param factor scaling factor of output + */ +static inline void +wl_output_send_scale(struct wl_resource *resource_, int32_t factor) +{ + wl_resource_post_event(resource_, WL_OUTPUT_SCALE, factor); +} + +/** + * @ingroup iface_wl_output + * Sends an name event to the client owning the resource. + * @param resource_ The client's resource + * @param name output name + */ +static inline void +wl_output_send_name(struct wl_resource *resource_, const char *name) +{ + wl_resource_post_event(resource_, WL_OUTPUT_NAME, name); +} + +/** + * @ingroup iface_wl_output + * Sends an description event to the client owning the resource. + * @param resource_ The client's resource + * @param description output description + */ +static inline void +wl_output_send_description(struct wl_resource *resource_, const char *description) +{ + wl_resource_post_event(resource_, WL_OUTPUT_DESCRIPTION, description); +} + +/** + * @ingroup iface_wl_region + * @struct wl_region_interface + */ +struct wl_region_interface { + /** + * destroy region + * + * Destroy the region. This will invalidate the object ID. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * add rectangle to region + * + * Add the specified rectangle to the region. + * @param x region-local x coordinate + * @param y region-local y coordinate + * @param width rectangle width + * @param height rectangle height + */ + void (*add)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * subtract rectangle from region + * + * Subtract the specified rectangle from the region. + * @param x region-local x coordinate + * @param y region-local y coordinate + * @param width rectangle width + * @param height rectangle height + */ + void (*subtract)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); +}; + + +/** + * @ingroup iface_wl_region + */ +#define WL_REGION_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_region + */ +#define WL_REGION_ADD_SINCE_VERSION 1 +/** + * @ingroup iface_wl_region + */ +#define WL_REGION_SUBTRACT_SINCE_VERSION 1 + +#ifndef WL_SUBCOMPOSITOR_ERROR_ENUM +#define WL_SUBCOMPOSITOR_ERROR_ENUM +enum wl_subcompositor_error { + /** + * the to-be sub-surface is invalid + */ + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE = 0, + /** + * the to-be sub-surface parent is invalid + */ + WL_SUBCOMPOSITOR_ERROR_BAD_PARENT = 1, +}; +#endif /* WL_SUBCOMPOSITOR_ERROR_ENUM */ + +#ifndef WL_SUBCOMPOSITOR_ERROR_ENUM_IS_VALID +#define WL_SUBCOMPOSITOR_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_subcompositor + * Validate a wl_subcompositor error value. + * + * @return true on success, false on error. + * @ref wl_subcompositor_error + */ +static inline bool +wl_subcompositor_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE: + return version >= 1; + case WL_SUBCOMPOSITOR_ERROR_BAD_PARENT: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SUBCOMPOSITOR_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_subcompositor + * @struct wl_subcompositor_interface + */ +struct wl_subcompositor_interface { + /** + * unbind from the subcompositor interface + * + * Informs the server that the client will not be using this + * protocol object anymore. This does not affect any other objects, + * wl_subsurface objects included. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * give a surface the role sub-surface + * + * Create a sub-surface interface for the given surface, and + * associate it with the given parent surface. This turns a plain + * wl_surface into a sub-surface. + * + * The to-be sub-surface must not already have another role, and it + * must not have an existing wl_subsurface object. Otherwise the + * bad_surface protocol error is raised. + * + * Adding sub-surfaces to a parent is a double-buffered operation + * on the parent (see wl_surface.commit). The effect of adding a + * sub-surface becomes visible on the next time the state of the + * parent surface is applied. + * + * The parent surface must not be one of the child surface's + * descendants, and the parent must be different from the child + * surface, otherwise the bad_parent protocol error is raised. + * + * This request modifies the behaviour of wl_surface.commit request + * on the sub-surface, see the documentation on wl_subsurface + * interface. + * @param id the new sub-surface object ID + * @param surface the surface to be turned into a sub-surface + * @param parent the parent surface + */ + void (*get_subsurface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface, + struct wl_resource *parent); +}; + + +/** + * @ingroup iface_wl_subcompositor + */ +#define WL_SUBCOMPOSITOR_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subcompositor + */ +#define WL_SUBCOMPOSITOR_GET_SUBSURFACE_SINCE_VERSION 1 + +#ifndef WL_SUBSURFACE_ERROR_ENUM +#define WL_SUBSURFACE_ERROR_ENUM +enum wl_subsurface_error { + /** + * wl_surface is not a sibling or the parent + */ + WL_SUBSURFACE_ERROR_BAD_SURFACE = 0, +}; +#endif /* WL_SUBSURFACE_ERROR_ENUM */ + +#ifndef WL_SUBSURFACE_ERROR_ENUM_IS_VALID +#define WL_SUBSURFACE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_subsurface + * Validate a wl_subsurface error value. + * + * @return true on success, false on error. + * @ref wl_subsurface_error + */ +static inline bool +wl_subsurface_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_SUBSURFACE_ERROR_BAD_SURFACE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_SUBSURFACE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_subsurface + * @struct wl_subsurface_interface + */ +struct wl_subsurface_interface { + /** + * remove sub-surface interface + * + * The sub-surface interface is removed from the wl_surface + * object that was turned into a sub-surface with a + * wl_subcompositor.get_subsurface request. The wl_surface's + * association to the parent is deleted. The wl_surface is unmapped + * immediately. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * reposition the sub-surface + * + * This sets the position of the sub-surface, relative to the + * parent surface. + * + * The sub-surface will be moved so that its origin (top left + * corner pixel) will be at the location x, y of the parent surface + * coordinate system. The coordinates are not restricted to the + * parent surface area. Negative values are allowed. + * + * The initial position is 0, 0. + * + * Position is double-buffered state on the parent surface, see + * wl_subsurface and wl_surface.commit for more information. + * @param x x coordinate in the parent surface + * @param y y coordinate in the parent surface + */ + void (*set_position)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y); + /** + * restack the sub-surface + * + * This sub-surface is taken from the stack, and put back just + * above the reference surface, changing the z-order of the + * sub-surfaces. The reference surface must be one of the sibling + * surfaces, or the parent surface. Using any other surface, + * including this sub-surface, will cause a protocol error. + * + * A new sub-surface is initially added as the top-most in the + * stack of its siblings and parent. + * + * Z-order is double-buffered state on the parent surface, see + * wl_subsurface and wl_surface.commit for more information. + * @param sibling the reference surface + */ + void (*place_above)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *sibling); + /** + * restack the sub-surface + * + * The sub-surface is placed just below the reference surface. + * + * See wl_subsurface.place_above. + * @param sibling the reference surface + */ + void (*place_below)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *sibling); + /** + * set sub-surface to synchronized mode + * + * Change the commit behaviour of the sub-surface to synchronized + * mode. + * + * See wl_subsurface and wl_surface.commit for more information. + */ + void (*set_sync)(struct wl_client *client, + struct wl_resource *resource); + /** + * set sub-surface to desynchronized mode + * + * Change the commit behaviour of the sub-surface to + * desynchronized mode. + * + * See wl_subsurface and wl_surface.commit for more information. + */ + void (*set_desync)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_SET_POSITION_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_PLACE_ABOVE_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_PLACE_BELOW_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_SET_SYNC_SINCE_VERSION 1 +/** + * @ingroup iface_wl_subsurface + */ +#define WL_SUBSURFACE_SET_DESYNC_SINCE_VERSION 1 + +#ifndef WL_FIXES_ERROR_ENUM +#define WL_FIXES_ERROR_ENUM +/** + * @ingroup iface_wl_fixes + * wl_fixes error values + * + * These errors can be emitted in response to wl_fixes requests. + */ +enum wl_fixes_error { + /** + * unknown global or the global is not removed + */ + WL_FIXES_ERROR_INVALID_ACK_REMOVE = 0, +}; +#endif /* WL_FIXES_ERROR_ENUM */ + +#ifndef WL_FIXES_ERROR_ENUM_IS_VALID +#define WL_FIXES_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_wl_fixes + * Validate a wl_fixes error value. + * + * @return true on success, false on error. + * @ref wl_fixes_error + */ +static inline bool +wl_fixes_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case WL_FIXES_ERROR_INVALID_ACK_REMOVE: + return version >= 1; + default: + return false; + } +} +#endif /* WL_FIXES_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_wl_fixes + * @struct wl_fixes_interface + */ +struct wl_fixes_interface { + /** + * destroys this object + * + * + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * destroy a wl_registry + * + * This request destroys a wl_registry object. + * + * The client should no longer use the wl_registry after making + * this request. + * + * The compositor will emit a wl_display.delete_id event with the + * object ID of the registry and will no longer emit any events on + * the registry. The client should re-use the object ID once it + * receives the wl_display.delete_id event. + * @param registry the registry to destroy + */ + void (*destroy_registry)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *registry); + /** + * acknowledge global removal + * + * Acknowledge the removal of the specified global. + * + * If no global with the specified name exists or the global is not + * removed, the wl_fixes.invalid_ack_remove protocol error will be + * posted. + * + * Due to the Wayland protocol being asynchronous, the wl_global + * objects cannot be destroyed immediately. For example, if a + * wl_global is removed and a client attempts to bind that global + * around same time, it can result in a protocol error due to an + * unknown global name in the bind request. + * + * In order to avoid crashing clients, the compositor should remove + * the wl_global once it is guaranteed that no more bind requests + * will come. + * + * The wl_fixes.ack_global_remove() request is used to signal to + * the compositor that the client will not bind the given global + * anymore. After all clients acknowledge the removal of the + * global, the compositor can safely destroy it. + * + * The client must call the wl_fixes.ack_global_remove() request in + * response to a wl_registry.global_remove() event even if it did + * not bind the corresponding global. + * @param registry the registry object + * @param name unique name of the global + * @since 2 + */ + void (*ack_global_remove)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *registry, + uint32_t name); +}; + + +/** + * @ingroup iface_wl_fixes + */ +#define WL_FIXES_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_fixes + */ +#define WL_FIXES_DESTROY_REGISTRY_SINCE_VERSION 1 +/** + * @ingroup iface_wl_fixes + */ +#define WL_FIXES_ACK_GLOBAL_REMOVE_SINCE_VERSION 2 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server.h b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server.h new file mode 100644 index 000000000..48fab1dda --- /dev/null +++ b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-server.h @@ -0,0 +1,111 @@ +/* + * Copyright ยฉ 2008 Kristian Hรธgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** \file + * + * \brief Include the server API, deprecations and protocol C API. + * + * \warning Use of this header file is discouraged. Prefer including + * wayland-server-core.h instead, which does not include the + * server protocol header and as such only defines the library + * API, excluding the deprecated API below. + */ + +#ifndef WAYLAND_SERVER_H +#define WAYLAND_SERVER_H + +#include +#include "wayland-server-core.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The user can set this macro to hide the wl_object, wl_resource and wl_buffer + * objects alongside the associated API. + * + * The structs were meant to be opaque, although we missed that in the early days. + * + * NOTE: the list of structs, functions, etc in this section MUST NEVER GROW. + * Otherwise we will break forward compatibility and applications that used to + * build fine will no longer be able to do so. + */ +#ifndef WL_HIDE_DEPRECATED + +struct wl_object { + const struct wl_interface *interface; + const void *implementation; + uint32_t id; +}; + +struct wl_resource { + struct wl_object object; + wl_resource_destroy_func_t destroy; + struct wl_list link; + struct wl_signal destroy_signal; + struct wl_client *client; + void *data; +}; + +WL_DEPRECATED +uint32_t +wl_client_add_resource(struct wl_client *client, + struct wl_resource *resource); + +WL_DEPRECATED +struct wl_resource * +wl_client_add_object(struct wl_client *client, + const struct wl_interface *interface, + const void *implementation, + uint32_t id, void *data); + +WL_DEPRECATED +struct wl_resource * +wl_client_new_object(struct wl_client *client, + const struct wl_interface *interface, + const void *implementation, void *data); + +WL_DEPRECATED +struct wl_global * +wl_display_add_global(struct wl_display *display, + const struct wl_interface *interface, + void *data, + wl_global_bind_func_t bind); + +WL_DEPRECATED +void +wl_display_remove_global(struct wl_display *display, + struct wl_global *global); + +#endif + +#ifdef __cplusplus +} +#endif + +#include "wayland-server-protocol.h" + +#endif diff --git a/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-util.h b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-util.h new file mode 100644 index 000000000..98c72fded --- /dev/null +++ b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-util.h @@ -0,0 +1,768 @@ +/* + * Copyright ยฉ 2008 Kristian Hรธgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** \file wayland-util.h + * + * \brief Utility classes, functions, and macros. + */ + +#ifndef WAYLAND_UTIL_H +#define WAYLAND_UTIL_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Visibility attribute */ +#if defined(__GNUC__) && __GNUC__ >= 4 +#define WL_EXPORT __attribute__ ((visibility("default"))) +#else +#define WL_EXPORT +#endif + +/** Deprecated attribute */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || (defined(__cplusplus) && __cplusplus >= 201402L) +#define WL_DEPRECATED [[deprecated]] +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define WL_DEPRECATED __attribute__ ((deprecated)) +#else +#define WL_DEPRECATED +#endif + +/** + * Printf-style argument attribute + * + * \param x Ordinality of the format string argument + * \param y Ordinality of the argument to check against the format string + * + * \sa https://gcc.gnu.org/onlinedocs/gcc-3.2.1/gcc/Function-Attributes.html + */ +#if defined(__GNUC__) && __GNUC__ >= 4 +#define WL_PRINTF(x, y) __attribute__((__format__(__printf__, x, y))) +#else +#define WL_PRINTF(x, y) +#endif + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#define WL_TYPEOF(expr) typeof(expr) +#else +#define WL_TYPEOF(expr) __typeof__(expr) +#endif + +/** \class wl_object + * + * \brief A protocol object. + * + * A `wl_object` is an opaque struct identifying the protocol object + * underlying a `wl_proxy` or `wl_resource`. + * + * \note Functions accessing a `wl_object` are not normally used by client code. + * Clients should normally use the higher level interface generated by the + * scanner to interact with compositor objects. + * + */ +struct wl_object; + +/** + * The maximum size of a protocol message. + * + * If a message size exceeds this value, the connection will be dropped. + * Servers will send an invalid_method error before disconnecting. + */ +#define WL_MAX_MESSAGE_SIZE 4096 + +/** + * Protocol message signature + * + * A wl_message describes the signature of an actual protocol message, such as a + * request or event, that adheres to the Wayland protocol wire format. The + * protocol implementation uses a wl_message within its demarshal machinery for + * decoding messages between a compositor and its clients. In a sense, a + * wl_message is to a protocol message like a class is to an object. + * + * The `name` of a wl_message is the name of the corresponding protocol message. + * + * The `signature` is an ordered list of symbols representing the data types + * of message arguments and, optionally, a protocol version and indicators for + * nullability. A leading integer in the `signature` indicates the _since_ + * version of the protocol message. A `?` preceding a data type symbol indicates + * that the following argument type is nullable. While it is a protocol violation + * to send messages with non-nullable arguments set to `NULL`, event handlers in + * clients might still get called with non-nullable object arguments set to + * `NULL`. This can happen when the client destroyed the object being used as + * argument on its side and an event referencing that object was sent before the + * server knew about its destruction. As this race cannot be prevented, clients + * should - as a general rule - program their event handlers such that they can + * handle object arguments declared non-nullable being `NULL` gracefully. + * + * When no arguments accompany a message, `signature` is an empty string. + * + * Symbols: + * + * * `i`: int + * * `u`: uint + * * `f`: fixed + * * `s`: string + * * `o`: object + * * `n`: new_id + * * `a`: array + * * `h`: fd + * * `?`: following argument (`o` or `s`) is nullable + * + * While demarshaling primitive arguments is straightforward, when demarshaling + * messages containing `object` or `new_id` arguments, the protocol + * implementation often must determine the type of the object. The `types` of a + * wl_message is an array of wl_interface references that correspond to `o` and + * `n` arguments in `signature`, with `NULL` placeholders for arguments with + * non-object types. + * + * Consider the protocol event wl_display `delete_id` that has a single `uint` + * argument. The wl_message is: + * + * \code + * { "delete_id", "u", [NULL] } + * \endcode + * + * Here, the message `name` is `"delete_id"`, the `signature` is `"u"`, and the + * argument `types` is `[NULL]`, indicating that the `uint` argument has no + * corresponding wl_interface since it is a primitive argument. + * + * In contrast, consider a `wl_foo` interface supporting protocol request `bar` + * that has existed since version 2, and has two arguments: a `uint` and an + * object of type `wl_baz_interface` that may be `NULL`. Such a `wl_message` + * might be: + * + * \code + * { "bar", "2u?o", [NULL, &wl_baz_interface] } + * \endcode + * + * Here, the message `name` is `"bar"`, and the `signature` is `"2u?o"`. Notice + * how the `2` indicates the protocol version, the `u` indicates the first + * argument type is `uint`, and the `?o` indicates that the second argument + * is an object that may be `NULL`. Lastly, the argument `types` array indicates + * that no wl_interface corresponds to the first argument, while the type + * `wl_baz_interface` corresponds to the second argument. + * + * \sa wl_argument + * \sa wl_interface + * \sa
Wire Format + */ +struct wl_message { + /** Message name */ + const char *name; + /** Message signature */ + const char *signature; + /** Object argument interfaces */ + const struct wl_interface **types; +}; + +/** + * Protocol object interface + * + * A wl_interface describes the API of a protocol object defined in the Wayland + * protocol specification. The protocol implementation uses a wl_interface + * within its marshalling machinery for encoding client requests. + * + * The `name` of a wl_interface is the name of the corresponding protocol + * interface, and `version` represents the version of the interface. The members + * `method_count` and `event_count` represent the number of `methods` (requests) + * and `events` in the respective wl_message members. + * + * For example, consider a protocol interface `foo`, marked as version `1`, with + * two requests and one event. + * + * \code{.xml} + * + * + * + * + * + * \endcode + * + * Given two wl_message arrays `foo_requests` and `foo_events`, a wl_interface + * for `foo` might be: + * + * \code + * struct wl_interface foo_interface = { + * "foo", 1, + * 2, foo_requests, + * 1, foo_events + * }; + * \endcode + * + * \note The server side of the protocol may define interface implementation + * types that incorporate the term `interface` in their name. Take + * care to not confuse these server-side `struct`s with a wl_interface + * variable whose name also ends in `interface`. For example, while the + * server may define a type `struct wl_foo_interface`, the client may + * define a `struct wl_interface wl_foo_interface`. + * + * \sa wl_message + * \sa wl_proxy + * \sa Interfaces + * \sa Versioning + */ +struct wl_interface { + /** Interface name */ + const char *name; + /** Interface version */ + int version; + /** Number of methods (requests) */ + int method_count; + /** Method (request) signatures */ + const struct wl_message *methods; + /** Number of events */ + int event_count; + /** Event signatures */ + const struct wl_message *events; +}; + +/** \class wl_list + * + * \brief Doubly-linked list + * + * On its own, an instance of `struct wl_list` represents the sentinel head of + * a doubly-linked list, and must be initialized using wl_list_init(). + * When empty, the list head's `next` and `prev` members point to the list head + * itself, otherwise `next` references the first element in the list, and `prev` + * refers to the last element in the list. + * + * Use the `struct wl_list` type to represent both the list head and the links + * between elements within the list. Use wl_list_empty() to determine if the + * list is empty in O(1). + * + * All elements in the list must be of the same type. The element type must have + * a `struct wl_list` member, often named `link` by convention. Prior to + * insertion, there is no need to initialize an element's `link` - invoking + * wl_list_init() on an individual list element's `struct wl_list` member is + * unnecessary if the very next operation is wl_list_insert(). However, a + * common idiom is to initialize an element's `link` prior to removal - ensure + * safety by invoking wl_list_init() before wl_list_remove(). + * + * Consider a list reference `struct wl_list foo_list`, an element type as + * `struct element`, and an element's link member as `struct wl_list link`. + * + * The following code initializes a list and adds three elements to it. + * + * \code + * struct wl_list foo_list; + * + * struct element { + * int foo; + * struct wl_list link; + * }; + * struct element e1, e2, e3; + * + * wl_list_init(&foo_list); + * wl_list_insert(&foo_list, &e1.link); // e1 is the first element + * wl_list_insert(&foo_list, &e2.link); // e2 is now the first element + * wl_list_insert(&e2.link, &e3.link); // insert e3 after e2 + * \endcode + * + * The list now looks like [e2, e3, e1]. + * + * The `wl_list` API provides some iterator macros. For example, to iterate + * a list in ascending order: + * + * \code + * struct element *e; + * wl_list_for_each(e, foo_list, link) { + * do_something_with_element(e); + * } + * \endcode + * + * See the documentation of each iterator for details. + * \sa http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/linux/list.h + */ +struct wl_list { + /** Previous list element */ + struct wl_list *prev; + /** Next list element */ + struct wl_list *next; +}; + +/** + * Initializes the list. + * + * \param list List to initialize + * + * \memberof wl_list + */ +void +wl_list_init(struct wl_list *list); + +/** + * Inserts an element into the list, after the element represented by \p list. + * When \p list is a reference to the list itself (the head), set the containing + * struct of \p elm as the first element in the list. + * + * \note If \p elm is already part of a list, inserting it again will lead to + * list corruption. + * + * \param list List element after which the new element is inserted + * \param elm Link of the containing struct to insert into the list + * + * \memberof wl_list + */ +void +wl_list_insert(struct wl_list *list, struct wl_list *elm); + +/** + * Removes an element from the list. + * + * \note This operation leaves \p elm in an invalid state. + * + * \param elm Link of the containing struct to remove from the list + * + * \memberof wl_list + */ +void +wl_list_remove(struct wl_list *elm); + +/** + * Determines the length of the list. + * + * \note This is an O(n) operation. + * + * \param list List whose length is to be determined + * + * \return Number of elements in the list + * + * \memberof wl_list + */ +int +wl_list_length(const struct wl_list *list); + +/** + * Determines if the list is empty. + * + * \param list List whose emptiness is to be determined + * + * \return 1 if empty, or 0 if not empty + * + * \memberof wl_list + */ +int +wl_list_empty(const struct wl_list *list); + +/** + * Inserts all of the elements of one list into another, after the element + * represented by \p list. + * + * \note This leaves \p other in an invalid state. + * + * \param list List element after which the other list elements will be inserted + * \param other List of elements to insert + * + * \memberof wl_list + */ +void +wl_list_insert_list(struct wl_list *list, struct wl_list *other); + +/** + * Retrieves a pointer to a containing struct, given a member name. + * + * This macro allows "conversion" from a pointer to a member to its containing + * struct. This is useful if you have a contained item like a wl_list, + * wl_listener, or wl_signal, provided via a callback or other means, and would + * like to retrieve the struct that contains it. + * + * To demonstrate, the following example retrieves a pointer to + * `example_container` given only its `destroy_listener` member: + * + * \code + * struct example_container { + * struct wl_listener destroy_listener; + * // other members... + * }; + * + * void example_container_destroy(struct wl_listener *listener, void *data) + * { + * struct example_container *ctr; + * + * ctr = wl_container_of(listener, ctr, destroy_listener); + * // destroy ctr... + * } + * \endcode + * + * \note `sample` need not be a valid pointer. A null or uninitialised pointer + * is sufficient. + * + * \param ptr Valid pointer to the contained member + * \param sample Pointer to a struct whose type contains \p ptr + * \param member Named location of \p ptr within the \p sample type + * + * \return The container for the specified pointer + */ +#define wl_container_of(ptr, sample, member) \ + (WL_TYPEOF(sample))((char *)(ptr) - \ + offsetof(WL_TYPEOF(*sample), member)) + +/** + * Iterates over a list. + * + * This macro expresses a for-each iterator for wl_list. Given a list and + * wl_list link member name (often named `link` by convention), this macro + * assigns each element in the list to \p pos, which can then be referenced in + * a trailing code block. For example, given a wl_list of `struct message` + * elements: + * + * \code + * struct message { + * char *contents; + * wl_list link; + * }; + * + * struct wl_list *message_list; + * // Assume message_list now "contains" many messages + * + * struct message *m; + * wl_list_for_each(m, message_list, link) { + * do_something_with_message(m); + * } + * \endcode + * + * \param pos Cursor that each list element will be assigned to + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each(pos, head, member) \ + for (pos = wl_container_of((head)->next, pos, member); \ + &pos->member != (head); \ + pos = wl_container_of(pos->member.next, pos, member)) + +/** + * Iterates over a list, safe against removal of the list element. + * + * \note Only removal of the current element, \p pos, is safe. Removing + * any other element during traversal may lead to a loop malfunction. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param tmp Temporary pointer of the same type as \p pos + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_safe(pos, tmp, head, member) \ + for (pos = wl_container_of((head)->next, pos, member), \ + tmp = wl_container_of((pos)->member.next, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = wl_container_of(pos->member.next, tmp, member)) + +/** + * Iterates backwards over a list. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_reverse(pos, head, member) \ + for (pos = wl_container_of((head)->prev, pos, member); \ + &pos->member != (head); \ + pos = wl_container_of(pos->member.prev, pos, member)) + +/** + * Iterates backwards over a list, safe against removal of the list element. + * + * \note Only removal of the current element, \p pos, is safe. Removing + * any other element during traversal may lead to a loop malfunction. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param tmp Temporary pointer of the same type as \p pos + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_reverse_safe(pos, tmp, head, member) \ + for (pos = wl_container_of((head)->prev, pos, member), \ + tmp = wl_container_of((pos)->member.prev, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = wl_container_of(pos->member.prev, tmp, member)) + +/** + * \class wl_array + * + * Dynamic array + * + * A wl_array is a dynamic array that can only grow until released. It is + * intended for relatively small allocations whose size is variable or not known + * in advance. While construction of a wl_array does not require all elements to + * be of the same size, wl_array_for_each() does require all elements to have + * the same type and size. + * + */ +struct wl_array { + /** Array size */ + size_t size; + /** Allocated space */ + size_t alloc; + /** Array data */ + void *data; +}; + +/** + * Initializes the array. + * + * \param array Array to initialize + * + * \memberof wl_array + */ +void +wl_array_init(struct wl_array *array); + +/** + * Releases the array data. + * + * \note Leaves the array in an invalid state. + * + * \param array Array whose data is to be released + * + * \memberof wl_array + */ +void +wl_array_release(struct wl_array *array); + +/** + * Increases the size of the array by \p size bytes. + * + * \param array Array whose size is to be increased + * \param size Number of bytes to increase the size of the array by + * + * \return A pointer to the beginning of the newly appended space, or NULL when + * resizing fails. + * + * \memberof wl_array + */ +void * +wl_array_add(struct wl_array *array, size_t size); + +/** + * Copies the contents of \p source to \p array. + * + * \param array Destination array to copy to + * \param source Source array to copy from + * + * \return 0 on success, or -1 on failure + * + * \memberof wl_array + */ +int +wl_array_copy(struct wl_array *array, struct wl_array *source); + +/** + * Iterates over an array. + * + * This macro expresses a for-each iterator for wl_array. It assigns each + * element in the array to \p pos, which can then be referenced in a trailing + * code block. \p pos must be a pointer to the array element type, and all + * array elements must be of the same type and size. + * + * \param pos Cursor that each array element will be assigned to + * \param array Array to iterate over + * + * \relates wl_array + * \sa wl_list_for_each() + */ +#define wl_array_for_each(pos, array) \ + for (pos = (array)->data; \ + (array)->size != 0 && \ + (const char *) pos < ((const char *) (array)->data + (array)->size); \ + (pos)++) + +/** + * Fixed-point number + * + * A `wl_fixed_t` is a 24.8 signed fixed-point number with a sign bit, 23 bits + * of integer precision and 8 bits of decimal precision. Consider `wl_fixed_t` + * as an opaque struct with methods that facilitate conversion to and from + * `double` and `int` types. + */ +typedef int32_t wl_fixed_t; + +/** + * Converts a fixed-point number to a floating-point number. + * + * \param f Fixed-point number to convert + * + * \return Floating-point representation of the fixed-point argument + */ +static inline double +wl_fixed_to_double(wl_fixed_t f) +{ + return f / 256.0; +} + +/** + * Converts a floating-point number to a fixed-point number. + * + * \param d Floating-point number to convert + * + * \return Fixed-point representation of the floating-point argument + */ +static inline wl_fixed_t +wl_fixed_from_double(double d) +{ + return (wl_fixed_t) (round(d * 256.0)); +} + +/** + * Converts a fixed-point number to an integer. + * + * \param f Fixed-point number to convert + * + * \return Integer component of the fixed-point argument + */ +static inline int +wl_fixed_to_int(wl_fixed_t f) +{ + return f / 256; +} + +/** + * Converts an integer to a fixed-point number. + * + * \param i Integer to convert + * + * \return Fixed-point representation of the integer argument + */ +static inline wl_fixed_t +wl_fixed_from_int(int i) +{ + return i * 256; +} + +/** + * Protocol message argument data types + * + * This union represents all of the argument types in the Wayland protocol wire + * format. The protocol implementation uses wl_argument within its marshalling + * machinery for dispatching messages between a client and a compositor. + * + * \sa wl_message + * \sa wl_interface + * \sa Wire Format + */ +union wl_argument { + int32_t i; /**< `int` */ + uint32_t u; /**< `uint` */ + wl_fixed_t f; /**< `fixed` */ + const char *s; /**< `string` */ + struct wl_object *o; /**< `object` */ + uint32_t n; /**< `new_id` */ + struct wl_array *a; /**< `array` */ + int32_t h; /**< `fd` */ +}; + +/** + * Dispatcher function type alias + * + * A dispatcher is a function that handles the emitting of callbacks in client + * code. For programs directly using the C library, this is done by using + * libffi to call function pointers. When binding to languages other than C, + * dispatchers provide a way to abstract the function calling process to be + * friendlier to other function calling systems. + * + * A dispatcher takes five arguments: The first is the dispatcher-specific + * implementation associated with the target object. The second is the object + * upon which the callback is being invoked (either wl_proxy or wl_resource). + * The third and fourth arguments are the opcode and the wl_message + * corresponding to the callback. The final argument is an array of arguments + * received from the other process via the wire protocol. + * + * \param user_data Dispatcher-specific implementation data + * \param target Callback invocation target (wl_proxy or `wl_resource`) + * \param opcode Callback opcode + * \param msg Callback message signature + * \param args Array of received arguments + * + * \return 0 on success, or -1 on failure + */ +typedef int (*wl_dispatcher_func_t)(const void *user_data, void *target, + uint32_t opcode, const struct wl_message *msg, + union wl_argument *args); + +/** + * Log function type alias + * + * The C implementation of the Wayland protocol abstracts the details of + * logging. Users may customize the logging behavior, with a function conforming + * to the `wl_log_func_t` type, via `wl_log_set_handler_client` and + * `wl_log_set_handler_server`. + * + * A `wl_log_func_t` must conform to the expectations of `vprintf`, and + * expects two arguments: a string to write and a corresponding variable + * argument list. While the string to write may contain format specifiers and + * use values in the variable argument list, the behavior of any `wl_log_func_t` + * depends on the implementation. + * + * \note Take care to not confuse this with `wl_protocol_logger_func_t`, which + * is a specific server-side logger for requests and events. + * + * \param fmt String to write to the log, containing optional format + * specifiers + * \param args Variable argument list + * + * \sa wl_log_set_handler_client + * \sa wl_log_set_handler_server + */ +typedef void (*wl_log_func_t)(const char *fmt, va_list args) WL_PRINTF(1, 0); + +/** + * Return value of an iterator function + * + * \sa wl_client_for_each_resource_iterator_func_t + * \sa wl_client_for_each_resource + */ +enum wl_iterator_result { + /** Stop the iteration */ + WL_ITERATOR_STOP, + /** Continue the iteration */ + WL_ITERATOR_CONTINUE +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-version.h b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-version.h new file mode 100644 index 000000000..c7e04c2cd --- /dev/null +++ b/app/src/main/cpp/waylandcomp/prebuilt/include/wayland-version.h @@ -0,0 +1,34 @@ +/* + * Copyright ยฉ 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WAYLAND_VERSION_H +#define WAYLAND_VERSION_H + +#define WAYLAND_VERSION_MAJOR 1 +#define WAYLAND_VERSION_MINOR 26 +#define WAYLAND_VERSION_MICRO 0 +#define WAYLAND_VERSION "1.26.0" + +#endif diff --git a/app/src/main/cpp/waylandcomp/prebuilt/lib/libwayland-server.so b/app/src/main/cpp/waylandcomp/prebuilt/lib/libwayland-server.so new file mode 100755 index 000000000..4eda47685 Binary files /dev/null and b/app/src/main/cpp/waylandcomp/prebuilt/lib/libwayland-server.so differ diff --git a/app/src/main/cpp/waylandcomp/protocols/ahb-v1.xml b/app/src/main/cpp/waylandcomp/protocols/ahb-v1.xml new file mode 100644 index 000000000..1560c8f7c --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/ahb-v1.xml @@ -0,0 +1,105 @@ + + + + Copyright 2026 The412Banner + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Zero-copy window layers on Android. A Vulkan swapchain image that the + client allocated as an Android AHardwareBuffer (gralloc) is still shared + as a dma-buf through zwp_linux_dmabuf_v1, so the compositor can import + and draw it the usual way. This interface additionally hands the + compositor the AHardwareBuffer itself, once per wl_buffer, so the + compositor can put the buffer straight on an Android SurfaceControl + layer (SurfaceFlinger / the display hardware composite it) without + copying it. + + The AHardwareBuffer travels over a Unix socket: the client creates a + socketpair, writes the buffer's handle into one end with + AHardwareBuffer_sendHandleToUnixSocket, then sends the other end in the + attach request. The compositor reads it with + AHardwareBuffer_recvHandleFromUnixSocket and closes the socket. Its + reference to the AHardwareBuffer lives as long as the wl_buffer (plus + whatever the display still holds). + + The compositor advertises this global whenever it can put buffers on a + display layer at all; whether it WANTS gralloc buffers right now is the + mode event (version 2), sent on bind and every time the user switches + zero-copy on or off during the session. A client decides per swapchain, + when it creates the swapchain, from the last mode it received, and a + client whose live swapchain was built for the other mode retires it + (VK_ERROR_OUT_OF_DATE_KHR on its next acquire or present) so the program + rebuilds it for the new mode. Buffers of the old swapchain stay fully + presentable until then: an attached AHardwareBuffer keeps its association + for the wl_buffer's lifetime whatever the mode is now. A version 1 + client (no mode event) keeps deciding from its own environment. A + client that does not see the global (or does not want it) allocates + swapchain images the standard way; nothing else changes. + + Synchronisation stays implicit: the dma-buf carries the client's render + fence (DMA_BUF_IOCTL_IMPORT_SYNC_FILE by the client's driver), the + compositor exports it as the layer's acquire fence, and the compositor + imports the display's release fence into the dma-buf before it sends + wl_buffer.release, so the client's next acquire waits on it. + + + + + Buffers already attached keep their AHardwareBuffer association. + + + + + + Associates the AHardwareBuffer readable from socket with the + (already created) dma-buf wl_buffer. width, height and stride + (pixels) describe the gralloc allocation; modifier_hi/lo is the DRM + format modifier the dma-buf was shared with (the same value passed to + zwp_linux_buffer_params_v1.add); image_count is the number of images + in the swapchain this buffer belongs to (for the compositor's log). + Sent once per wl_buffer, before the buffer is first attached to a + surface. + + + + + + + + + + + + + + Sent once right after bind and again whenever the compositor's + zero-copy state flips (the in-game switch). enabled = 1: swapchains + created from now on should use gralloc AHardwareBuffers and attach + them; 0: standard buffers. A live swapchain built for the other value + should be retired by the client (out of date) so the program rebuilds + it; until it is, its buffers are shown as before. + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/color-management-v1.xml b/app/src/main/cpp/waylandcomp/protocols/color-management-v1.xml new file mode 100644 index 000000000..7f8da78f1 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/color-management-v1.xml @@ -0,0 +1,1631 @@ + + + + Copyright 2019 Sebastian Wick + Copyright 2019 Erwin Burema + Copyright 2020 AMD + Copyright 2020-2024 Collabora, Ltd. + Copyright 2024 Xaver Hugl + Copyright 2022-2025 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The aim of the color management extension is to allow clients to know + the color properties of outputs, and to tell the compositor about the color + properties of their content on surfaces. Doing this enables a compositor + to perform automatic color management of content for different outputs + according to how content is intended to look like. + + The color properties are represented as an image description object which + is immutable after it has been created. A wl_output always has an + associated image description that clients can observe. A wl_surface + always has an associated preferred image description as a hint chosen by + the compositor that clients can also observe. Clients can set an image + description on a wl_surface to denote the color characteristics of the + surface contents. + + An image description includes SDR and HDR colorimetry and encoding, HDR + metadata, and viewing environment parameters. An image description does + not include the properties set through color-representation extension. + It is expected that the color-representation extension is used in + conjunction with the color management extension when necessary, + particularly with the YUV family of pixel formats. + + Recommendation ITU-T H.273 + "Coding-independent code points for video signal type identification" + shall be referred to as simply H.273 here. + + The color-and-hdr repository + (https://gitlab.freedesktop.org/pq/color-and-hdr) contains + background information on the protocol design and legacy color management. + It also contains a glossary, learning resources for digital color, tools, + samples and more. + + The terminology used in this protocol is based on common color science and + color encoding terminology where possible. The glossary in the color-and-hdr + repository shall be the authority on the definition of terms in this + protocol. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A singleton global interface used for getting color management extensions + for wl_surface and wl_output objects, and for creating client defined + image description objects. The extension interfaces allow + getting the image description of outputs and setting the image + description of surfaces. + + Compositors should never remove this global. + + + + + Destroy the wp_color_manager_v1 object. This does not affect any other + objects in any way. + + + + + + + + + + + See the ICC.1:2022 specification from the International Color Consortium + for more details about rendering intents. + + The principles of ICC defined rendering intents apply with all types of + image descriptions, not only those with ICC file profiles. + + Compositors must support the perceptual rendering intent. Other + rendering intents are optional. + + + + + + + + + + + + + + + + + + + + The compositor supports set_mastering_display_primaries request with a + target color volume fully contained inside the primary color volume. + + + + + The compositor additionally supports target color volumes that + extend outside of the primary color volume. + + This can only be advertised if feature set_mastering_display_primaries + is supported as well. + + + + + + + + Named color primaries used to encode well-known sets of primaries. H.273 + is the authority, when it comes to the exact values of primaries and + authoritative specifications, where an equivalent code point exists. + + A value of 0 is invalid and will never be present in the list of enums. + + Descriptions do list the specifications for convenience. + + + + + Color primaries as defined by + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.1361-0 conventional colour gamut system and extended + colour gamut system (historical) + - IEC 61966-2-1 sRGB or sYCC + - IEC 61966-2-4 + - Society of Motion Picture and Television Engineers (SMPTE) RP 177 + (1993) Annex B + Equivalent to H.273 ColourPrimaries code point 1. + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a)(20) + Equivalent to H.273 ColourPrimaries code point 4. + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + - Rec. ITU-R BT.601-7 625 + - Rec. ITU-R BT.1358-0 625 (historical) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + Equivalent to H.273 ColourPrimaries code point 5. + + + + + Color primaries as defined by + - Rec. ITU-R BT.601-7 525 + - Rec. ITU-R BT.1358-1 525 or 625 (historical) + - Rec. ITU-R BT.1700-0 NTSC + - SMPTE 170M (2004) + - SMPTE 240M (1999) (historical) + Equivalent to H.273 ColourPrimaries code point 6 and 7. + + + + + Color primaries as defined by H.273 for generic film. + Equivalent to H.273 ColourPrimaries code point 8. + + + + + Color primaries as defined by + - Rec. ITU-R BT.2020-2 + - Rec. ITU-R BT.2100-0 + Equivalent to H.273 ColourPrimaries code point 9. + + + + + Color primaries as defined as the maximum of the CIE 1931 XYZ color + space by + - SMPTE ST 428-1 + - (CIE 1931 XYZ as in ISO 11664-1) + Equivalent to H.273 ColourPrimaries code point 10. + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE RP 431-2 (2011). Equivalent to H.273 ColourPrimaries code point + 11. + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE EG 432-1 (2010). + Equivalent to H.273 ColourPrimaries code point 12. + + + + + Color primaries as defined by Adobe as "Adobe RGB" and later published + by ISO 12640-4 (2011). + + + + + + + Named transfer functions used to represent well-known transfer + characteristics. H.273 is the authority, when it comes to the exact + formulas and authoritative specifications, where an equivalent code + point exists. + + A value of 0 is invalid and will never be present in the list of enums. + + Descriptions do list the specifications for convenience. + + + + + Rec. ITU-R BT.1886 is the display transfer characteristic assumed by + - Rec. ITU-R BT.601-7 525 and 625 + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.2020-2 + These recommendations are referred to by H.273 TransferCharacteristics + code points 1, 6, 14, and 15, which are all equivalent. + + This TF implies these default luminances from Rec. ITU-R BT.2035: + - primary color volume minimum: 0.01 cd/mยฒ + - primary color volume maximum: 100 cd/mยฒ + - reference white: 100 cd/mยฒ + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a) (20) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + Equivalent to H.273 TransferCharacteristics code point 4. + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + Equivalent to H.273 TransferCharacteristics code point 5. + + + + + Transfer characteristics as defined by + - SMPTE ST 240 (1999) + Equivalent to H.273 TransferCharacteristics code point 7. + + + + + Linear transfer function defined over all real numbers. + Normalised electrical values are equal the normalised optical values. + + The differences to H.273 TransferCharacteristics code point 8 are + the definition over all real numbers. + + + + + Logarithmic transfer characteristic (100:1 range). + Equivalent to H.273 TransferCharacteristics code point 9. + + + + + Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). + Equivalent to H.273 TransferCharacteristics code point 10. + + + + + Transfer characteristics as defined by + - IEC 61966-2-4 + Equivalent to H.273 TransferCharacteristics code point 11. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sRGB + Equivalent to H.273 TransferCharacteristics code point 13 with + MatrixCoefficients set to 0. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sYCC + Equivalent to H.273 TransferCharacteristics code point 13 with + MatrixCoefficients set to anything but 0. + + + + + Transfer characteristics as defined by + - SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems + - Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system + Equivalent to H.273 TransferCharacteristics code point 16. + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/mยฒ + - primary color volume maximum: 10000 cd/mยฒ + - reference white: 203 cd/mยฒ + + The difference between the primary color volume minimum and maximum + must be approximately 10000 cd/mยฒ as that is the swing of the EOTF + defined by ST 2084 and BT.2100. The default value for the + reference white is a protocol addition: it is suggested by + Report ITU-R BT.2408-7 and is not part of ST 2084 or BT.2100. + + + + + Transfer characteristics as defined by + - SMPTE ST 428-1 (2019) + Equivalent to H.273 TransferCharacteristics code point 17. + + + + + Transfer characteristics as defined by + - ARIB STD-B67 (2015) + - Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system + Equivalent to H.273 TransferCharacteristics code point 18. + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/mยฒ + - primary color volume maximum: 1000 cd/mยฒ + - reference white: 203 cd/mยฒ + + HLG is a relative display-referred signal with a specified + non-linear mapping to the display peak luminance (the HLG OOTF). + All absolute luminance values used here for HLG assume a 1000 cd/mยฒ + peak display. + + The default value for the reference white is a protocol addition: + it is suggested by Report ITU-R BT.2408-7 and is not part of + ARIB STD-B67 or BT.2100. + + + + + + + This creates a new wp_color_management_output_v1 object for the + given wl_output. + + See the wp_color_management_output_v1 interface for more details. + + + + + + + + + If a wp_color_management_surface_v1 object already exists for the given + wl_surface, the protocol error surface_exists is raised. + + This creates a new color wp_color_management_surface_v1 object for the + given wl_surface. + + See the wp_color_management_surface_v1 interface for more details. + + + + + + + + + This creates a new color wp_color_management_surface_feedback_v1 object + for the given wl_surface. + + See the wp_color_management_surface_feedback_v1 interface for more + details. + + + + + + + + + Makes a new ICC-based image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a wp_image_description_v1 object. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.icc_v2_v4. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + Makes a new parametric image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a wp_image_description_v1 object. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.parametric. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + This creates a pre-defined image description for the so-called + Windows-scRGB stimulus encoding. This comes from the Windows 10 handling + of its own definition of an scRGB color space for an HDR screen + driven in BT.2100/PQ signalling mode. + + Windows-scRGB uses sRGB (BT.709) color primaries and white point. + The transfer characteristic is extended linear. + + The nominal color channel value range is extended, meaning it includes + negative and greater than 1.0 values. Negative values are used to + escape the sRGB color gamut boundaries. To make use of the extended + range, the client needs to use a pixel format that can represent those + values, e.g. floating-point 16 bits per channel. + + Nominal color value R=G=B=0.0 corresponds to BT.2100/PQ system + 0 cd/mยฒ, and R=G=B=1.0 corresponds to BT.2100/PQ system 80 cd/mยฒ. + The maximum is R=G=B=125.0 corresponding to 10k cd/mยฒ. + + Windows-scRGB is displayed by Windows 10 by converting it to + BT.2100/PQ, maintaining the CIE 1931 chromaticity and mapping the + luminance as above. No adjustment is made to the signal to account + for the viewing conditions. + + The reference white level of Windows-scRGB is unknown. If a + reference white level must be assumed for compositor processing, it + should be R=G=B=2.5375 corresponding to 203 cd/mยฒ of Report ITU-R + BT.2408-7. + + The target color volume of Windows-scRGB is unknown. The color gamut + may be anything between sRGB and BT.2100. + + Note: EGL_EXT_gl_colorspace_scrgb_linear definition differs from + Windows-scRGB by using R=G=B=1.0 as the reference white level, while + Windows-scRGB reference white level is unknown or varies. However, + it seems probable that Windows implements both + EGL_EXT_gl_colorspace_scrgb_linear and Vulkan + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT as Windows-scRGB. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.windows_scrgb. + Otherwise this request raises the protocol error unsupported_feature. + + The resulting image description object does not allow get_information + request. The wp_image_description_v1.ready event shall be sent. + + + + + + + + When this object is created, it shall immediately send this event once + for each rendering intent the compositor supports. + + + + + + + + When this object is created, it shall immediately send this event once + for each compositor supported feature listed in the enumeration. + + + + + + + + When this object is created, it shall immediately send this event once + for each named transfer function the compositor supports with the + parametric image description creator. + + + + + + + + When this object is created, it shall immediately send this event once + for each named set of primaries the compositor supports with the + parametric image description creator. + + + + + + + + This event is sent when all supported rendering intents, features, + transfer functions and named primaries have been sent. + + + + + + + A wp_color_management_output_v1 describes the color properties of an + output. + + The wp_color_management_output_v1 is associated with the wl_output global + underlying the wl_output object. Therefore the client destroying the + wl_output object has no impact, but the compositor removing the output + global makes the wp_color_management_output_v1 object inert. + + + + + Destroy the color wp_color_management_output_v1 object. This does not + affect any remaining protocol objects. + + + + + + This event is sent whenever the image description of the output changed, + followed by one wl_output.done event common to output events across all + extensions. + + If the client wants to use the updated image description, it needs to do + get_image_description again, because image description objects are + immutable. + + + + + + This creates a new wp_image_description_v1 object for the current image + description of the output. There always is exactly one image description + active for an output so the client should destroy the image description + created by earlier invocations of this request. This request is usually + sent as a reaction to the image_description_changed event or when + creating a wp_color_management_output_v1 object. + + The image description of an output represents the color encoding the + output expects. There might be performance and power advantages, as well + as improved color reproduction, if a content update matches the image + description of the output it is being shown on. If a content update is + shown on any other output than the one it matches the image description + of, then the color reproduction on those outputs might be considerably + worse. + + The created wp_image_description_v1 object preserves the image + description of the output from the time the object was created. + + The resulting image description object allows get_information request. + + If this protocol object is inert, the resulting image description object + shall immediately deliver the wp_image_description_v1.failed event with + the no_output cause. + + If the interface version is inadequate for the output's image + description, meaning that the client does not support all the events + needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + wp_image_description_v1.failed event with the low_version cause. + + Otherwise the object shall immediately deliver the ready event. + + + + + + + + + A wp_color_management_surface_v1 allows the client to set the color + space and HDR properties of a surface. + + If the wl_surface associated with the wp_color_management_surface_v1 is + destroyed, the wp_color_management_surface_v1 object becomes inert. + + + + + Destroy the wp_color_management_surface_v1 object and do the same as + unset_image_description. + + + + + + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + Set the image description of the underlying surface. The image + description and rendering intent are double-buffered state, see + wl_surface.commit. + + It is the client's responsibility to understand the image description + it sets on a surface, and to provide content that matches that image + description. Compositors might convert images to match their own or any + other image descriptions. + + Image descriptions which are not ready (see wp_image_description_v1) + are forbidden in this request, and in such case the protocol error + image_description is raised. + + All image descriptions which are ready (see wp_image_description_v1) + are allowed and must always be accepted by the compositor. + + A rendering intent provides the client's preference on how content + colors should be mapped to each output. The render_intent value must + be one advertised by the compositor with + wp_color_manager_v1.render_intent event, otherwise the protocol error + render_intent is raised. + + When an image description is set on a surface, the Transfer + Characteristics of the image description defines the valid range of + the nominal (real-valued) color channel values. The processing of + out-of-range color channel values is undefined, but compositors are + recommended to clamp the values to the valid range when possible. + + By default, a surface does not have an associated image description + nor a rendering intent. The handling of color on such surfaces is + compositor implementation defined. Compositors should handle such + surfaces as sRGB, but may handle them differently if they have specific + requirements. + + Setting the image description has copy semantics; after this request, + the image description can be immediately destroyed without affecting + the pending state of the surface. + + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + This request removes any image description from the surface. See + set_image_description for how a compositor handles a surface without + an image description. This is double-buffered state, see + wl_surface.commit. + + + + + + + A wp_color_management_surface_feedback_v1 allows the client to get the + preferred image description of a surface. + + If the wl_surface associated with this object is destroyed, the + wp_color_management_surface_feedback_v1 object becomes inert. + + + + + Destroy the wp_color_management_surface_feedback_v1 object. + + + + + + + + + + + + The preferred image description is the one which likely has the most + performance and/or quality benefits for the compositor if used by the + client for its wl_surface contents. This event is sent whenever the + compositor changes the wl_surface's preferred image description. + + This event sends the identity of the new preferred state as the argument, + so clients who are aware of the image description already can reuse it. + Otherwise, if the client client wants to know what the preferred image + description is, it shall use the get_preferred request. + + The preferred image description is not automatically used for anything. + It is only a hint, and clients may set any valid image description with + set_image_description, but there might be performance and color accuracy + improvements by providing the wl_surface contents in the preferred + image description. Therefore clients that can, should render according + to the preferred image description + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + The preferred image description represents the compositor's preferred + color encoding for this wl_surface at the current time. There might be + performance and power advantages, as well as improved color + reproduction, if the image description of a content update matches the + preferred image description. + + This creates a new wp_image_description_v1 object for the currently + preferred image description for the wl_surface. The client should + stop using and destroy the image descriptions created by earlier + invocations of this request for the associated wl_surface. + This request is usually sent as a reaction to the preferred_changed + event or when creating a wp_color_management_surface_feedback_v1 object + if the client is capable of adapting to image descriptions. + + The created wp_image_description_v1 object preserves the preferred image + description of the wl_surface from the time the object was created. + + The resulting image description object allows get_information request. + + If the image description is parametric, the client should set it on its + wl_surface only if the image description is an exact match with the + client content. Particularly if everything else matches, but the target + color volume is greater than what the client needs, the client should + create its own parameric image description with its exact parameters. + + If the interface version is inadequate for the preferred image + description, meaning that the client does not support all the + events needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + wp_image_description_v1.failed event with the low_version cause, + otherwise the object shall immediately deliver the ready event. + + + + + + + + The same description as for get_preferred applies, except the returned + image description is guaranteed to be parametric. This is meant for + clients that can only deal with parametric image descriptions. + + If the compositor doesn't support parametric image descriptions, the + unsupported_feature error is emitted. + + + + + + + + + This type of object is used for collecting all the information required + to create a wp_image_description_v1 object from an ICC file. A complete + set of required parameters consists of these properties: + - ICC file + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + + + + + + + + + + + + + + Create an image description object based on the ICC information + previously set on this object. A compositor must parse the ICC data in + some undefined but finite amount of time. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + If the particular combination of the information is not supported + by the compositor, the resulting image description object shall + immediately deliver the wp_image_description_v1.failed event with the + 'unsupported' cause. If a valid image description was created from the + information, the wp_image_description_v1.ready event will eventually + be sent instead. + + This request destroys the wp_image_description_creator_icc_v1 object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the ICC profile file to be used as the basis of the image + description. + + The data shall be found through the given fd at the given offset, having + the given length. The fd must be seekable and readable. Violating these + requirements raises the bad_fd protocol error. + + If reading the data fails due to an error independent of the client, the + compositor shall send the wp_image_description_v1.failed event on the + created wp_image_description_v1 with the 'operating_system' cause. + + The maximum size of the ICC profile is 32 MB. If length is greater than + that or zero, the protocol error bad_size is raised. If offset + length + exceeds the file size, the protocol error out_of_file is raised. + + A compositor may read the file at any time starting from this request + and only until whichever happens first: + - If create request was issued, the wp_image_description_v1 object + delivers either failed or ready event; or + - if create request was not issued, this + wp_image_description_creator_icc_v1 object is destroyed. + + A compositor shall not modify the contents of the file, and the fd may + be sealed for writes and size changes. The client must ensure to its + best ability that the data does not change while the compositor is + reading it. + + The data must represent a valid ICC profile. The ICC profile version + must be 2 or 4, it must be a 3 channel profile and the class must be + Display or ColorSpace. Violating these requirements will not result in a + protocol error, but will eventually send the + wp_image_description_v1.failed event on the created + wp_image_description_v1 with the 'unsupported' cause. + + See the International Color Consortium specification ICC.1:2022 for more + details about ICC profiles. + + If ICC file has already been set on this object, the protocol error + already_set is raised. + + + + + + + + + + + This type of object is used for collecting all the parameters required + to create a wp_image_description_v1 object. A complete set of required + parameters consists of these properties: + - transfer characteristic function (tf) + - chromaticities of primaries and white point (primary color volume) + + The following properties are optional and have a well-defined default + if not explicitly set: + - primary color volume luminance range + - reference white luminance level + - mastering display primaries and white point (target color volume) + - mastering luminance range + + The following properties are optional and will be ignored + if not explicitly set: + - maximum content light level + - maximum frame-average light level + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + + + + + + + + + + + + + + + Create an image description object based on the parameters previously + set on this object. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + The protocol error invalid_luminance is raised if any of the following + requirements is not met: + - When max_cll is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When max_fall is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When both max_cll and max_fall are set, max_fall must be less or equal + to max_cll. + + If the particular combination of the parameter set is not supported + by the compositor, the resulting image description object shall + immediately deliver the wp_image_description_v1.failed event with the + 'unsupported' cause. If a valid image description was created from the + parameter set, the wp_image_description_v1.ready event will eventually + be sent instead. + + This request destroys the wp_image_description_creator_params_v1 + object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the transfer characteristic using explicitly enumerated named + functions. + + When the resulting image description is attached to an image, the + content should be encoded and decoded according to the industry standard + practices for the transfer characteristic. + + Only names advertised with wp_color_manager_v1 event supported_tf_named + are allowed. Other values shall raise the protocol error invalid_tf. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + + + + + + + Sets the color component transfer characteristic to a power curve with + the given exponent. Negative values are handled by mirroring the + positive half of the curve through the origin. The valid domain and + range of the curve are all finite real numbers. This curve represents + the conversion from electrical to optical color channel values. + + When the resulting image description is attached to an image, the + content should be encoded with the inverse of the power curve. + + The curve exponent shall be multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + The curve exponent must be at least 1.0 and at most 10.0. Otherwise the + protocol error invalid_tf is raised. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.set_tf_power. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + Sets the color primaries and white point using explicitly named sets. + This describes the primary color volume which is the basis for color + value encoding. + + Only names advertised with wp_color_manager_v1 event + supported_primaries_named are allowed. Other values shall raise the + protocol error invalid_primaries_named. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + + + + + + + Sets the color primaries and white point using CIE 1931 xy chromaticity + coordinates. This describes the primary color volume which is the basis + for color value encoding. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_primaries. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + + + + + + + + Sets the primary color volume luminance range and the reference white + luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. + + The default luminances from + https://www.color.org/chardata/rgb/srgb.xalter are + - primary color volume minimum: 0.2 cd/mยฒ + - primary color volume maximum: 80 cd/mยฒ + - reference white: 80 cd/mยฒ + + Setting a named transfer characteristic can imply other default + luminances. + + The default luminances get overwritten when this request is used. + With transfer_function.st2084_pq the given 'max_lum' value is ignored, + and 'max_lum' is taken as 'min_lum' + 10000 cd/mยฒ. + + 'min_lum' and 'max_lum' specify the minimum and maximum luminances of + the primary color volume as reproduced by the targeted display. + + 'reference_lum' specifies the luminance of the reference white as + reproduced by the targeted display, and reflects the targeted viewing + environment. + + Compositors should make sure that all content is anchored, meaning that + an input signal level of 'reference_lum' on one image description and + another input signal level of 'reference_lum' on another image + description should produce the same output level, even though the + 'reference_lum' on both image representations can be different. + + 'reference_lum' may be higher than 'max_lum'. In that case reaching + the reference white output level in image content requires the + 'extended_target_volume' feature support. + + If 'max_lum' or 'reference_lum' are less than or equal to 'min_lum', + the protocol error invalid_luminance is raised. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + If the primary color volume luminance range and the reference white + luminance level have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_luminances. Otherwise this request + raises the protocol error unsupported_feature. + + + + + + + + + + Provides the color primaries and white point of the mastering display + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering display primaries are not explicitly set, the target color + volume is assumed to have the same primaries as the primary color volume. + + The target color volume is defined by all tristimulus values between 0.0 + and 1.0 (inclusive) of the color space defined by the given mastering + display primaries and white point. The colorimetry is identical between + the container color space and the mastering display color space, + including that no chromatic adaptation is applied even if the white + points differ. + + The target color volume can exceed the primary color volume to allow for + a greater color volume with an existing color space definition (for + example scRGB). It can be smaller than the primary color volume to + minimize gamut and tone mapping distances for big color spaces (HDR + metadata). + + To make use of the entire target color volume a suitable pixel format + has to be chosen (e.g. floating point to exceed the primary color + volume, or abusing limited quantization range as with xvYCC). + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + If mastering display primaries have already been set on this object, the + protocol error already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + + + + + + + Sets the luminance range that was used during the content mastering + process as the minimum and maximum absolute luminance L. These values + include the minimum display emission and ambient flare luminances, + assumed to be optically additive and have the chromaticity of the + primary color volume white point. This should be + compatible with the SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering luminances are not explicitly set, the target color volume + is assumed to have the same min and max luminances as the primary color + volume. + + If max L is less than or equal to min L, the protocol error + invalid_luminance is raised. + + Min L value is multiplied by 10000 to get the argument min_lum value + and carry precision of 4 decimals. Max L value is unscaled for max_lum. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + Sets the maximum content light level (max_cll) as defined by CTA-861-H. + + max_cll is undefined by default. + + + + + + + + Sets the maximum frame-average light level (max_fall) as defined by + CTA-861-H. + + max_fall is undefined by default. + + + + + + + + + An image description carries information about the color encoding used on + a surface when attached to a wl_surface via + wp_color_management_surface_v1.set_image_description. A compositor can use + this information to decode pixel values into colorimetrically meaningful + quantities. + + Note, that the wp_image_description_v1 object is not ready to be used + immediately after creation. The object eventually delivers either the + 'ready' or the 'failed' event, specified in all requests creating it. The + object is deemed "ready" after receiving the 'ready' event. + + An object which is not ready is illegal to use, it can only be destroyed. + Any other request in this interface shall result in the 'not_ready' + protocol error. Attempts to use an object which is not ready through other + interfaces shall raise protocol errors defined there. + + Once created and regardless of how it was created, a + wp_image_description_v1 object always refers to one fixed image + description. It cannot change after creation. + + + + + Destroy this object. It is safe to destroy an object which is not ready. + + Destroying a wp_image_description_v1 object has no side-effects, not + even if a wp_color_management_surface_v1.set_image_description has not + yet been followed by a wl_surface.commit. + + + + + + + + + + + + + + + + + + + + + + If creating a wp_image_description_v1 object fails for a reason that is + not defined as a protocol error, this event is sent. + + The requests that create image description objects define whether and + when this can occur. Only such creation requests can trigger this event. + This event cannot be triggered after the image description was + successfully formed. + + Once this event has been sent, the wp_image_description_v1 object will + never become ready and it can only be destroyed. + + + + + + + + + Once this event has been sent, the wp_image_description_v1 object is + deemed "ready". Ready objects can be used to send requests and can be + used through other interfaces. + + Every ready wp_image_description_v1 protocol object refers to an + underlying image description record in the compositor. Multiple protocol + objects may end up referring to the same record. Clients may identify + these "copies" by comparing their id numbers: if the numbers from two + protocol objects are identical, the protocol objects refer to the same + image description record. Two different image description records + cannot have the same id number simultaneously. The id number does not + change during the lifetime of the image description record. + + The id number is valid only as long as the protocol object is alive. If + all protocol objects referring to the same image description record are + destroyed, the id number may be recycled for a different image + description record. + + Image description id number is not a protocol object id. Zero is + reserved as an invalid id number. It shall not be possible for a client + to refer to an image description by its id number in protocol. The id + numbers might not be portable between Wayland connections. A compositor + shall not send an invalid id number. + + This identity allows clients to de-duplicate image description records + and avoid get_information request if they already have the image + description information. + + + + + + + + Creates a wp_image_description_info_v1 object which delivers the + information that makes up the image description. + + Not all image description protocol objects allow get_information + request. Whether it is allowed or not is defined by the request that + created the object. If get_information is not allowed, the protocol + error no_information is raised. + + + + + + + + + Sends all matching events describing an image description object exactly + once and finally sends the 'done' event. + + This means + - if the image description is parametric, it must send + - primaries + - named_primaries, if applicable + - at least one of tf_power and tf_named, as applicable + - luminances + - target_primaries + - target_luminance + - if the image description is parametric, it may send, if applicable, + - target_max_cll + - target_max_fall + - if the image description contains an ICC profile, it must send the + icc_file event + + Once a wp_image_description_info_v1 object has delivered a 'done' event it + is automatically destroyed. + + Every wp_image_description_info_v1 created from the same + wp_image_description_v1 shall always return the exact same data. + + + + + Signals the end of information events and destroys the object. + + + + + + The icc argument provides a file descriptor to the client which may be + memory-mapped to provide the ICC profile matching the image description. + The fd is read-only, and if mapped then it must be mapped with + MAP_PRIVATE by the client. + + The ICC profile version and other details are determined by the + compositor. There is no provision for a client to ask for a specific + kind of a profile. + + + + + + + + + + Delivers the primary color volume primaries and white point using CIE + 1931 xy chromaticity coordinates. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + + + + + + + + + + + + + + Delivers the primary color volume primaries and white point using an + explicitly enumerated named set. + + + + + + + + The color component transfer characteristic of this image description is + a pure power curve. This event provides the exponent of the power + function. This curve represents the conversion from electrical to + optical pixel or color values. + + The curve exponent has been multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + + + + + + + Delivers the transfer characteristic using an explicitly enumerated + named function. + + + + + + + + Delivers the primary color volume luminance range and the reference + white luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + + + + + + + + + Provides the color primaries and white point of the target color volume + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata for mastering displays. + + While primary color volume is about how color is encoded, the target + color volume is the actually displayable color volume. If target color + volume is equal to the primary color volume, then this event is not + sent. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + + + + + + + + + + + + + + Provides the luminance range that the image description is targeting as + the minimum and maximum absolute luminance L. These values include the + minimum display emission and ambient flare luminances, assumed to be + optically additive and have the chromaticity of the primary color + volume white point. This should be compatible with the SMPTE ST 2086 + definition of HDR static metadata. + + This luminance range is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + Min L value is multiplied by 10000 to get the argument min_lum value and + carry precision of 4 decimals. Max L value is unscaled for max_lum. + + + + + + + + + Provides the targeted max_cll of the image description. max_cll is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + + + Provides the targeted max_fall of the image description. max_fall is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/desktop-v1.xml b/app/src/main/cpp/waylandcomp/protocols/desktop-v1.xml new file mode 100644 index 000000000..7c62b2a2e --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/desktop-v1.xml @@ -0,0 +1,65 @@ + + + + Copyright 2026 The412Banner + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Each Wine process is a separate Wayland client, so a Windows virtual + desktop can't be built from subsurfaces. Instead the process that owns + the desktop window marks one surface as the desktop, and every process + reports where its top-level windows sit on that desktop and the global + window stacking order. The compositor draws the desktop at the bottom, + the windows at the reported positions, and sends pointer and keyboard + input to the desktop surface; Wine routes it to the right window. + + + + + + + The surface is drawn at the origin, below every window, and sets the + size of the desktop. + + + + + + + Position of the top-left corner of the surface in desktop + coordinates, and the window handle used by set_zorder. + + + + + + + + + + Visible top-level window handles as 32-bit values, topmost first. + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/linux-dmabuf-v1.xml b/app/src/main/cpp/waylandcomp/protocols/linux-dmabuf-v1.xml new file mode 100644 index 000000000..733717545 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/linux-dmabuf-v1.xml @@ -0,0 +1,628 @@ + + + + + Copyright ยฉ 2014, 2015 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface offers ways to create generic dmabuf-based wl_buffers. + + For more information about dmabuf, see: + https://www.kernel.org/doc/html/next/userspace-api/dma-buf-alloc-exchange.html + + Clients can use the get_surface_feedback request to get dmabuf feedback + for a particular surface. If the client wants to retrieve feedback not + tied to a surface, they can use the get_default_feedback request. + + The following are required from clients: + + - Clients must ensure that either all data in the dma-buf is + coherent for all subsequent read access or that coherency is + correctly handled by the underlying kernel-side dma-buf + implementation. + + - Don't make any more attachments after sending the buffer to the + compositor. Making more attachments later increases the risk of + the compositor not being able to use (re-import) an existing + dmabuf-based wl_buffer. + + The underlying graphics stack must ensure the following: + + - The dmabuf file descriptors relayed to the server will stay valid + for the whole lifetime of the wl_buffer. This means the server may + at any time use those fds to import the dmabuf into any kernel + sub-system that might accept it. + + However, when the underlying graphics stack fails to deliver the + promise, because of e.g. a device hot-unplug which raises internal + errors, after the wl_buffer has been successfully created the + compositor must not raise protocol errors to the client when dmabuf + import later fails. + + To create a wl_buffer from one or more dmabufs, a client creates a + zwp_linux_buffer_params_v1 object with a zwp_linux_dmabuf_v1.create_params + request. All planes required by the intended format are added with + the 'add' request. Finally, a 'create' or 'create_immed' request is + issued, which has the following outcome depending on the import success. + + The 'create' request, + - on success, triggers a 'created' event which provides the final + wl_buffer to the client. + - on failure, triggers a 'failed' event to convey that the server + cannot use the dmabufs received from the client. + + For the 'create_immed' request, + - on success, the server immediately imports the added dmabufs to + create a wl_buffer. No event is sent from the server in this case. + - on failure, the server can choose to either: + - terminate the client by raising a fatal error. + - mark the wl_buffer as failed, and send a 'failed' event to the + client. If the client uses a failed wl_buffer as an argument to any + request, the behaviour is compositor implementation-defined. + + For all DRM formats and unless specified in another protocol extension, + pre-multiplied alpha is used for pixel values. + + Unless specified otherwise in another protocol extension, implicit + synchronization is used. In other words, compositors and clients must + wait and signal fences implicitly passed via the DMA-BUF's reservation + mechanism. + + + + + Objects created through this interface, especially wl_buffers, will + remain valid. + + + + + + This temporary object is used to collect multiple dmabuf handles into + a single batch to create a wl_buffer. It can only be used once and + should be destroyed after a 'created' or 'failed' event has been + received. + + + + + + + This event advertises one buffer format that the server supports. + All the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees + that the client has received all supported formats. + + For the definition of the format codes, see the + zwp_linux_buffer_params_v1::create request. + + Starting version 4, the format event is deprecated and must not be + sent by compositors. Instead, use get_default_feedback or + get_surface_feedback. + + + + + + + This event advertises the formats that the server supports, along with + the modifiers supported for each format. All the supported modifiers + for all the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees that + the client has received all supported format-modifier pairs. + + For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi == + 0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event. + It indicates that the server can support the format with an implicit + modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it + is as if no explicit modifier is specified. The effective modifier + will be derived from the dmabuf. + + A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for + a given format supports both explicit modifiers and implicit modifiers. + + For the definition of the format and modifier codes, see the + zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add + requests. + + Starting version 4, the modifier event is deprecated and must not be + sent by compositors. Instead, use get_default_feedback or + get_surface_feedback. + + + + + + + + + + + This request creates a new zwp_linux_dmabuf_feedback_v1 object not bound + to a particular surface. This object will deliver feedback about dmabuf + parameters to use if the client doesn't support per-surface feedback + (see get_surface_feedback). + + + + + + + This request creates a new zwp_linux_dmabuf_feedback_v1 object for the + specified wl_surface. This object will deliver feedback about dmabuf + parameters to use for buffers attached to this surface. + + If the surface is destroyed before the zwp_linux_dmabuf_feedback_v1 object, + the feedback object becomes inert. + + + + + + + + + This temporary object is a collection of dmabufs and other + parameters that together form a single logical buffer. The temporary + object may eventually create one wl_buffer unless cancelled by + destroying it before requesting 'create'. + + Single-planar formats only require one dmabuf, however + multi-planar formats may require more than one dmabuf. For all + formats, an 'add' request must be called once per plane (even if the + underlying dmabuf fd is identical). + + You must use consecutive plane indices ('plane_idx' argument for 'add') + from zero to the number of planes used by the drm_fourcc format code. + All planes required by the format must be given exactly once, but can + be given in any order. Each plane index can only be set once; subsequent + calls with a plane index which has already been set will result in a + plane_set error being generated. + + + + + + + + + + + + + + + + + Cleans up the temporary data sent to the server for dmabuf-based + wl_buffer creation. + + + + + + This request adds one dmabuf to the set in this + zwp_linux_buffer_params_v1. + + The 64-bit unsigned value combined from modifier_hi and modifier_lo + is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the + fb modifier, which is defined in drm_mode.h of Linux UAPI. + This is an opaque token. Drivers use this token to express tiling, + compression, etc. driver-specific modifications to the base format + defined by the DRM fourcc code. + + Starting from version 4, the invalid_format protocol error is sent if + the format + modifier pair was not advertised as supported. + + Starting from version 5, the invalid_format protocol error is sent if + all planes don't use the same modifier. + + This request raises the PLANE_IDX error if plane_idx is too large. + The error PLANE_SET is raised if attempting to set a plane that + was already set. + + + + + + + + + + + + + + + + + + This asks for creation of a wl_buffer from the added dmabuf + buffers. The wl_buffer is not created immediately but returned via + the 'created' event if the dmabuf sharing succeeds. The sharing + may fail at runtime for reasons a client cannot predict, in + which case the 'failed' event is triggered. + + The 'format' argument is a DRM_FORMAT code, as defined by the + libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + authoritative source on how the format codes should work. + + The 'flags' is a bitfield of the flags defined in enum "flags". + 'y_invert' means that the image needs to be y-flipped. + + Flag 'interlaced' means that the frame in the buffer is not + progressive as usual, but interlaced. An interlaced buffer as + supported here must always contain both top and bottom fields. + The top field always begins on the first pixel row. The temporal + ordering between the two fields is top field first, unless + 'bottom_first' is specified. It is undefined whether 'bottom_first' + is ignored if 'interlaced' is not set. + + This protocol does not convey any information about field rate, + duration, or timing, other than the relative ordering between the + two fields in one buffer. A compositor may have to estimate the + intended field rate from the incoming buffer rate. It is undefined + whether the time of receiving wl_surface.commit with a new buffer + attached, applying the wl_surface state, wl_surface.frame callback + trigger, presentation, or any other point in the compositor cycle + is used to measure the frame or field times. There is no support + for detecting missed or late frames/fields/buffers either, and + there is no support whatsoever for cooperating with interlaced + compositor output. + + The composited image quality resulting from the use of interlaced + buffers is explicitly undefined. A compositor may use elaborate + hardware features or software to deinterlace and create progressive + output frames from a sequence of interlaced input buffers, or it + may produce substandard image quality. However, compositors that + cannot guarantee reasonable image quality in all cases are recommended + to just reject all interlaced buffers. + + Any argument errors, including non-positive width or height, + mismatch between the number of planes and the format, bad + format, bad offset or stride, may be indicated by fatal protocol + errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + OUT_OF_BOUNDS. + + Dmabuf import errors in the server that are not obvious client + bugs are returned via the 'failed' event as non-fatal. This + allows attempting dmabuf sharing and falling back in the client + if it fails. + + This request can be sent only once in the object's lifetime, after + which the only legal request is destroy. This object should be + destroyed after issuing a 'create' request. Attempting to use this + object after issuing 'create' raises the ALREADY_USED protocol error. + + It is not mandatory to issue 'create'. If a client wants to + cancel the buffer creation, it can just destroy this object. + + + + + + + + + + This event indicates that the attempted buffer creation was + successful. It provides the new wl_buffer referencing the dmabuf(s). + + Upon receiving this event, the client should destroy the + zwp_linux_buffer_params_v1 object. + + + + + + + This event indicates that the attempted buffer creation has + failed. It usually means that one of the dmabuf constraints + has not been fulfilled. + + Upon receiving this event, the client should destroy the + zwp_linux_buffer_params_v1 object. + + + + + + This asks for immediate creation of a wl_buffer by importing the + added dmabufs. + + In case of import success, no event is sent from the server, and the + wl_buffer is ready to be used by the client. + + Upon import failure, either of the following may happen, as seen fit + by the implementation: + - the client is terminated with one of the following fatal protocol + errors: + - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, + in case of argument errors such as mismatch between the number + of planes and the format, bad format, non-positive width or + height, or bad offset or stride. + - INVALID_WL_BUFFER, in case the cause for failure is unknown or + platform specific. + - the server creates an invalid wl_buffer, marks it as failed and + sends a 'failed' event to the client. The result of using this + invalid wl_buffer as an argument in any request by the client is + defined by the compositor implementation. + + This takes the same arguments as a 'create' request, and obeys the + same restrictions. + + + + + + + + + + + Set the device the compositor should import the dmabufs to for sampling + in the next create or create_immed request. + + To avoid race conditions when the compositor removes a device from the + tranches, it is not a protocol error if the device hasn't been advertised + by the compositor in a tranche with the sampling flag, but the import is + likely to fail in that case. + + If the client doesn't know a suitable target device, it shouldn't set one, + and the compositor should attempt import on all devices it supports. + + If the array is too small to contain a dev_t or larger than required, the + invalid_dev_t_size error will be emitted. + + + + + + + + This object advertises dmabuf parameters feedback. This includes the + preferred devices and the supported formats/modifiers. + + The parameters are sent once when this object is created and whenever they + change. The done event is always sent once after all parameters have been + sent. When a single parameter changes, all parameters are re-sent by the + compositor. + + Compositors can re-send the parameters when the current client buffer + allocations are sub-optimal. Compositors should not re-send the + parameters if re-allocating the buffers would not result in a more optimal + configuration. In particular, compositors should avoid sending the exact + same parameters multiple times in a row. + + The tranche_target_device and tranche_formats events are grouped by + tranches of preference. For each tranche, a tranche_target_device, one + tranche_flags and one or more tranche_formats events are sent, followed + by a tranche_done event finishing the list. The tranches are sent in + descending order of preference. All formats and modifiers in the same + tranche have the same preference. + + To send parameters, the compositor sends one main_device event (unless + the client bound version 6 or above), tranches (each consisting of one + tranche_target_device event, one tranche_flags event, tranche_formats + events and then a tranche_done event), then one done event. + + With version 6 and above, the compositor must always advertise at least + one tranche with the sampling flag set. + + The compositor sends a format_table event at least once for each dmabuf + parameters feedback object, before any tranche events. Unless the format + table contents change later, the compositor is free not to send another + format_table event for the same dmabuf parameters feedback object. + + + + + Using this request a client can tell the server that it is not going to + use the zwp_linux_dmabuf_feedback_v1 object anymore. + + + + + + This event is sent after all parameters of a zwp_linux_dmabuf_feedback_v1 + object have been sent. + + This allows changes to the zwp_linux_dmabuf_feedback_v1 parameters to be + seen as atomic, even if they happen via multiple events. + + + + + + This event provides a file descriptor which can be memory-mapped to + access the format and modifier table. + + The table contains a tightly packed array of consecutive format + + modifier pairs. Each pair is 16 bytes wide. It contains a format as a + 32-bit unsigned integer, followed by 4 bytes of unused padding, and a + modifier as a 64-bit unsigned integer. The native endianness is used. + + The client must map the file descriptor in read-only private mode. + + Compositors are not allowed to mutate the table file contents once this + event has been sent. Instead, compositors must create a new, separate + table file and re-send feedback parameters. Compositors are allowed to + store duplicate format + modifier pairs in the table. + + + + + + + + This event advertises the main device that the server prefers to use + when direct scan-out to the target device isn't possible. The + advertised main device may be different for each + zwp_linux_dmabuf_feedback_v1 object, and may change over time. + + There is exactly one main device. The compositor must send at least + one preference tranche with tranche_target_device equal to main_device. + + Clients need to create buffers that the main device can import and + read from, otherwise creating the dmabuf wl_buffer will fail (see the + zwp_linux_buffer_params_v1.create and create_immed requests for details). + The main device will also likely be kept active by the compositor, + so clients can use it instead of waking up another device for power + savings. + + In general the device is a DRM node. The DRM node type (primary vs. + render) is unspecified. Clients must not rely on the compositor sending + a particular node type. Clients cannot check two devices for equality + by comparing the dev_t value. + + If explicit modifiers are not supported and the client performs buffer + allocations on a different device than the main device, then the client + must force the buffer to have a linear layout. + + With version 6 and above, this event is no longer sent. Clients should + use a device with the sampling flag in the tranches instead. + + + + + + + This event splits tranche_target_device and tranche_formats events into + preference tranches. It is sent after a set of tranche_target_device + and tranche_formats events; it represents the end of a tranche. The + next tranche will have a lower preference. + + + + + + This event advertises the target device that the server prefers to use + for a buffer created given this tranche. The advertised target device + may be different for each preference tranche, and may change over time. + + There is exactly one target device per tranche. + + The target device may be a scan-out device, for example if the + compositor prefers to directly scan-out a buffer created given this + tranche. The target device may be a rendering device, for example if + the compositor prefers to texture from said buffer. + + The client can use this hint to allocate the buffer in a way that makes + it accessible from the target device, ideally directly. The buffer must + still be accessible from a device with the sampling flag, either through + direct import or a potentially more expensive fallback path. If the + buffer can't be directly imported for sampling, then clients must be + prepared for the compositor changing the tranche priority or making + wl_buffer creation fail (see the zwp_linux_buffer_params_v1.create and + create_immed requests for details). + + If the device is a DRM node, the DRM node type (primary vs. render) is + unspecified. Clients must not rely on the compositor sending a + particular node type. Clients cannot check two devices for equality by + comparing the dev_t value. + + This event is tied to a preference tranche, see the tranche_done event. + + + + + + + This event advertises the format + modifier combinations that the + compositor supports. + + It carries an array of indices, each referring to a format + modifier + pair in the last received format table (see the format_table event). + Each index is a 16-bit unsigned integer in native endianness. + + For legacy support, DRM_FORMAT_MOD_INVALID is an allowed modifier. + It indicates that the server can support the format with an implicit + modifier. When a buffer has DRM_FORMAT_MOD_INVALID as its modifier, it + is as if no explicit modifier is specified. The effective modifier + will be derived from the dmabuf. + + A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for + a given format supports both explicit modifiers and implicit modifiers. + + Compositors must not send duplicate format + modifier pairs within the + same tranche or across two different tranches with the same target + device and flags. + + This event is tied to a preference tranche, see the tranche_done event. + + For the definition of the format and modifier codes, see the + zwp_linux_buffer_params_v1.create request. + + + + + + + + The scanout flag is a hint that direct scan-out may be attempted by + the compositor on the target device if the client appropriately + allocates a buffer. How to allocate a buffer that can be scanned out + on the target device is implementation-defined. + + + + + The sampling flag describes that the compositor is able to efficiently + sample from buffers imported to the target device if the client + appropriately allocates a buffer. How to allocate a buffer that can be + efficiently sampled on the target device is implementation defined. + + + + + + + This event sets tranche-specific flags. This event is tied to a + preference tranche, see the tranche_done event. + With version 6 and above, the compositor must set at least one flag + in each tranche. + + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/pointer-constraints-unstable-v1.xml b/app/src/main/cpp/waylandcomp/protocols/pointer-constraints-unstable-v1.xml new file mode 100644 index 000000000..efd64b660 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/pointer-constraints-unstable-v1.xml @@ -0,0 +1,339 @@ + + + + + Copyright ยฉ 2014 Jonas ร…dahl + Copyright ยฉ 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used for adding constraints to + the motion of a pointer. Possible constraints include confining pointer + motions to a given region, or locking it to its current position. + + In order to constrain the pointer, a client must first bind the global + interface "wp_pointer_constraints" which, if a compositor supports pointer + constraints, is exposed by the registry. Using the bound global object, the + client uses the request that corresponds to the type of constraint it wants + to make. See wp_pointer_constraints for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + The global interface exposing pointer constraining functionality. It + exposes two requests: lock_pointer for locking the pointer to its + position, and confine_pointer for locking the pointer to a region. + + The lock_pointer and confine_pointer requests create the objects + wp_locked_pointer and wp_confined_pointer respectively, and the client can + use these objects to interact with the lock. + + For any surface, only one lock or confinement may be active across all + wl_pointer objects of the same seat. If a lock or confinement is requested + when another lock or confinement is active or requested on the same surface + and with any of the wl_pointer objects of the same seat, an + 'already_constrained' error will be raised. + + + + + These errors can be emitted in response to wp_pointer_constraints + requests. + + + + + + + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + + + + A oneshot pointer constraint will never reactivate once it has been + deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + A persistent pointer constraint may again reactivate once it has + been deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + + + Used by the client to notify the server that it will no longer use this + pointer constraints object. + + + + + + The lock_pointer request lets the client request to disable movements of + the virtual pointer (i.e. the cursor), effectively locking the pointer + to a position. This request may not take effect immediately; in the + future, when the compositor deems implementation-specific constraints + are satisfied, the pointer lock will be activated and the compositor + sends a locked event. + + The protocol provides no guarantee that the constraints are ever + satisfied, and does not require the compositor to send an error if the + constraints cannot ever be satisfied. It is thus possible to request a + lock that will never activate. + + There may not be another pointer constraint of any kind requested or + active on the surface for any of the wl_pointer objects of the seat of + the passed pointer when requesting a lock. If there is, an error will be + raised. See general pointer lock documentation for more details. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the lock to activate. It is up to the compositor whether to + warp the pointer or require some kind of user interaction for the lock + to activate. If the region is null the surface input region is used. + + A surface may receive pointer focus without the lock being activated. + + The request creates a new object wp_locked_pointer which is used to + interact with the lock as well as receive updates about its state. See + the the description of wp_locked_pointer for further information. + + Note that while a pointer is locked, the wl_pointer objects of the + corresponding seat will not emit any wl_pointer.motion events, but + relative motion events will still be emitted via wp_relative_pointer + objects of the same seat. wl_pointer.axis and wl_pointer.button events + are unaffected. + + + + + + + + + + + The confine_pointer request lets the client request to confine the + pointer cursor to a given region. This request may not take effect + immediately; in the future, when the compositor deems implementation- + specific constraints are satisfied, the pointer confinement will be + activated and the compositor sends a confined event. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the confinement to activate. It is up to the compositor + whether to warp the pointer or require some kind of user interaction for + the confinement to activate. If the region is null the surface input + region is used. + + The request will create a new object wp_confined_pointer which is used + to interact with the confinement as well as receive updates about its + state. See the the description of wp_confined_pointer for further + information. + + + + + + + + + + + + The wp_locked_pointer interface represents a locked pointer state. + + While the lock of this object is active, the wl_pointer objects of the + associated seat will not emit any wl_pointer.motion events. + + This object will send the event 'locked' when the lock is activated. + Whenever the lock is activated, it is guaranteed that the locked surface + will already have received pointer focus and that the pointer will be + within the region passed to the request creating this object. + + To unlock the pointer, send the destroy request. This will also destroy + the wp_locked_pointer object. + + If the compositor decides to unlock the pointer the unlocked event is + sent. See wp_locked_pointer.unlock for details. + + When unlocking, the compositor may warp the cursor position to the set + cursor position hint. If it does, it will not result in any relative + motion events emitted via wp_relative_pointer. + + If the surface the lock was requested on is destroyed and the lock is not + yet activated, the wp_locked_pointer object is now defunct and must be + destroyed. + + + + + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + + + + + + Set the cursor position hint relative to the top left corner of the + surface. + + If the client is drawing its own cursor, it should update the position + hint to the position of its own cursor. A compositor may use this + information to warp the pointer upon unlock in order to avoid pointer + jumps. + + The cursor position hint is double buffered. The new hint will only take + effect when the associated surface gets it pending state applied. See + wl_surface.commit for details. + + + + + + + + Set a new region used to lock the pointer. + + The new lock region is double-buffered. The new lock region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + For details about the lock region, see wp_locked_pointer. + + + + + + + Notification that the pointer lock of the seat's pointer is activated. + + + + + + Notification that the pointer lock of the seat's pointer is no longer + active. If this is a oneshot pointer lock (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer lock (see + wp_pointer_constraints.lifetime) this pointer lock may again + reactivate in the future. + + + + + + + The wp_confined_pointer interface represents a confined pointer state. + + This object will send the event 'confined' when the confinement is + activated. Whenever the confinement is activated, it is guaranteed that + the surface the pointer is confined to will already have received pointer + focus and that the pointer will be within the region passed to the request + creating this object. It is up to the compositor to decide whether this + requires some user interaction and if the pointer will warp to within the + passed region if outside. + + To unconfine the pointer, send the destroy request. This will also destroy + the wp_confined_pointer object. + + If the compositor decides to unconfine the pointer the unconfined event is + sent. The wp_confined_pointer object is at this point defunct and should + be destroyed. + + + + + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + + + + + + Set a new region used to confine the pointer. + + The new confine region is double-buffered. The new confine region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + If the confinement is active when the new confinement region is applied + and the pointer ends up outside of newly applied region, the pointer may + warped to a position within the new confinement region. If warped, a + wl_pointer.motion event will be emitted, but no + wp_relative_pointer.relative_motion event. + + The compositor may also, instead of using the new region, unconfine the + pointer. + + For details about the confine region, see wp_confined_pointer. + + + + + + + Notification that the pointer confinement of the seat's pointer is + activated. + + + + + + Notification that the pointer confinement of the seat's pointer is no + longer active. If this is a oneshot pointer confinement (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer confinement (see + wp_pointer_constraints.lifetime) this pointer confinement may again + reactivate in the future. + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/presentation-time.xml b/app/src/main/cpp/waylandcomp/protocols/presentation-time.xml new file mode 100644 index 000000000..c2431c500 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/presentation-time.xml @@ -0,0 +1,268 @@ + + + + + + Copyright ยฉ 2013-2014 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + + + + The main feature of this interface is accurate presentation + timing feedback to ensure smooth video playback while maintaining + audio/video synchronization. Some features use the concept of a + presentation clock, which is defined in the + presentation.clock_id event. + + A content update for a wl_surface is submitted by a + wl_surface.commit request. Request 'feedback' associates with + the wl_surface.commit and provides feedback on the content + update, particularly the final realized presentation time. + + + + When the final realized presentation time is available, e.g. + after a framebuffer flip completes, the requested + presentation_feedback.presented events are sent. The final + presentation time can differ from the compositor's predicted + display update time and the update's target time, especially + when the compositor misses its target vertical blanking period. + + + + + These fatal protocol errors may be emitted in response to + illegal presentation requests. + + + + + + + + Informs the server that the client will no longer be using + this protocol object. Existing objects created by this object + are not affected. + + + + + + Request presentation feedback for the current content submission + on the given surface. This creates a new presentation_feedback + object, which will deliver the feedback information once. If + multiple presentation_feedback objects are created for the same + submission, they will all deliver the same information. + + For details on what information is returned, see the + presentation_feedback interface. + + + + + + + + This event tells the client in which clock domain the + compositor interprets the timestamps used by the presentation + extension. This clock is called the presentation clock. + + The compositor sends this event when the client binds to the + presentation interface. The presentation clock does not change + during the lifetime of the client connection. + + The clock identifier is platform dependent. On POSIX platforms, the + identifier value is one of the clockid_t values accepted by + clock_gettime(). clock_gettime() is defined by POSIX.1-2001. + + Timestamps in this clock domain are expressed as tv_sec_hi, + tv_sec_lo, tv_nsec triples, each component being an unsigned + 32-bit value. Whole seconds are in tv_sec which is a 64-bit + value combined from tv_sec_hi and tv_sec_lo, and the + additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. + + Note that clock_id applies only to the presentation clock, + and implies nothing about e.g. the timestamps used in the + Wayland core protocol input events. + + Compositors should prefer a clock which does not jump and is + not slewed e.g. by NTP. The absolute value of the clock is + irrelevant. Precision of one millisecond or better is + recommended. Clients must be able to query the current clock + value directly, not by asking the compositor. + + + + + + + + + A presentation_feedback object returns an indication that a + wl_surface content update has become visible to the user. + One object corresponds to one content update submission + (wl_surface.commit). There are two possible outcomes: the + content update is presented to the user, and a presentation + timestamp delivered; or, the user did not see the content + update because it was superseded or its surface destroyed, + and the content update is discarded. + + Once a presentation_feedback object has delivered a 'presented' + or 'discarded' event it is automatically destroyed. + + + + + As presentation can be synchronized to only one output at a + time, this event tells which output it was. This event is only + sent prior to the presented event. + + As clients may bind to the same global wl_output multiple + times, this event is sent for each bound instance that matches + the synchronized output. If a client has not bound to the + right wl_output global at all, this event is not sent. + + + + + + + These flags provide information about how the presentation of + the related content update was done. The intent is to help + clients assess the reliability of the feedback and the visual + quality with respect to possible tearing and timings. + + + + The presentation was synchronized to the "vertical retrace" by + the display hardware such that tearing does not happen. + Relying on software scheduling is not acceptable for this + flag. If presentation is done by a copy to the active + frontbuffer, then it must guarantee that tearing cannot + happen. + + + + + The display hardware provided measurements that the hardware + driver converted into a presentation timestamp. Sampling a + clock in software is not acceptable for this flag. + + + + + The display hardware signalled that it started using the new + image content. The opposite of this is e.g. a timer being used + to guess when the display hardware has switched to the new + image content. + + + + + The presentation of this update was done zero-copy. This means + the buffer from the client was given to display hardware as + is, without copying it. Compositing with OpenGL counts as + copying, even if textured directly from the client buffer. + Possible zero-copy cases include direct scanout of a + fullscreen surface and a surface on a hardware overlay. + + + + + + + The associated content update was displayed to the user at the + indicated time (tv_sec_hi/lo, tv_nsec). For the interpretation of + the timestamp, see presentation.clock_id event. + + The timestamp corresponds to the time when the content update + turned into light the first time on the surface's main output. + Compositors may approximate this from the framebuffer flip + completion events from the system, and the latency of the + physical display path if known. + + This event is preceded by all related sync_output events + telling which output's refresh cycle the feedback corresponds + to, i.e. the main output for the surface. Compositors are + recommended to choose the output containing the largest part + of the wl_surface, or keeping the output they previously + chose. Having a stable presentation output association helps + clients predict future output refreshes (vblank). + + The 'refresh' argument gives the compositor's prediction of how + many nanoseconds after tv_sec, tv_nsec the very next output + refresh may occur. This is to further aid clients in + predicting future refreshes, i.e., estimating the timestamps + targeting the next few vblanks. If such prediction cannot + usefully be done, the argument is zero. + + For version 2 and later, if the output does not have a constant + refresh rate, explicit video mode switches excluded, then the + refresh argument must be either an appropriate rate picked by the + compositor (e.g. fastest rate), or 0 if no such rate exists. + For version 1, if the output does not have a constant refresh rate, + the refresh argument must be zero. + + The 64-bit value combined from seq_hi and seq_lo is the value + of the output's vertical retrace counter when the content + update was first scanned out to the display. This value must + be compatible with the definition of MSC in + GLX_OML_sync_control specification. Note, that if the display + path has a non-zero latency, the time instant specified by + this counter may differ from the timestamp's. + + If the output does not have a concept of vertical retrace or a + refresh cycle, or the output device is self-refreshing without + a way to query the refresh count, then the arguments seq_hi + and seq_lo must be zero. + + + + + + + + + + + + + The content update was never displayed to the user. + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/relative-pointer-unstable-v1.xml b/app/src/main/cpp/waylandcomp/protocols/relative-pointer-unstable-v1.xml new file mode 100644 index 000000000..ca6f81d12 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/relative-pointer-unstable-v1.xml @@ -0,0 +1,136 @@ + + + + + Copyright ยฉ 2014 Jonas ร…dahl + Copyright ยฉ 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used for making clients able to + receive relative pointer events not obstructed by barriers (such as the + monitor edge or other pointer barriers). + + To start receiving relative pointer events, a client must first bind the + global interface "wp_relative_pointer_manager" which, if a compositor + supports relative pointer motion events, is exposed by the registry. After + having created the relative pointer manager proxy object, the client uses + it to create the actual relative pointer object using the + "get_relative_pointer" request given a wl_pointer. The relative pointer + motion events will then, when applicable, be transmitted via the proxy of + the newly created relative pointer object. See the documentation of the + relative pointer interface for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + A global interface used for getting the relative pointer object for a + given pointer. + + + + + Used by the client to notify the server that it will no longer use this + relative pointer manager object. + + + + + + Create a relative pointer interface given a wl_pointer object. See the + wp_relative_pointer interface for more details. + + + + + + + + + A wp_relative_pointer object is an extension to the wl_pointer interface + used for emitting relative pointer events. It shares the same focus as + wl_pointer objects of the same seat and will only emit events when it has + focus. + + + + + + + + + Relative x/y pointer motion from the pointer of the seat associated with + this object. + + A relative motion is in the same dimension as regular wl_pointer motion + events, except they do not represent an absolute position. For example, + moving a pointer from (x, y) to (x', y') would have the equivalent + relative motion (x' - x, y' - y). If a pointer motion caused the + absolute pointer position to be clipped by for example the edge of the + monitor, the relative motion is unaffected by the clipping and will + represent the unclipped motion. + + This event also contains non-accelerated motion deltas. The + non-accelerated delta is, when applicable, the regular pointer motion + delta as it was before having applied motion acceleration and other + transformations such as normalization. + + Note that the non-accelerated delta does not represent 'raw' events as + they were read from some device. Pointer motion acceleration is device- + and configuration-specific and non-accelerated deltas and accelerated + deltas may have the same value on some devices. + + Relative motions are not coupled to wl_pointer.motion events, and can be + sent in combination with such events, but also independently. There may + also be scenarios where wl_pointer.motion is sent, but there is no + relative motion. The order of an absolute and relative motion event + originating from the same physical motion is not guaranteed. + + If the client needs button events or focus state, it can receive them + from a wl_pointer object of the same seat that the wp_relative_pointer + object is associated with. + + + + + + + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/text-input-unstable-v3.xml b/app/src/main/cpp/waylandcomp/protocols/text-input-unstable-v3.xml new file mode 100644 index 000000000..01c1e2104 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/text-input-unstable-v3.xml @@ -0,0 +1,604 @@ + + + + + Copyright ยฉ 2012, 2013 Intel Corporation + Copyright ยฉ 2015, 2016 Jan Arne Petersen + Copyright ยฉ 2017, 2018 Red Hat, Inc. + Copyright ยฉ 2018 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows compositors to act as input methods and to send text + to applications. A text input object is used to manage state of what are + typically text entry fields in the application. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + The zwp_text_input_v3 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the preedit_string and commit_string events. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + must not point to middle bytes inside a code point: they must either + point to the first byte of a code point or to the end of the buffer. + Lengths must be measured between two valid indices. + + Focus moving throughout surfaces will result in the emission of + zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + surface must commit zwp_text_input_v3.enable and + zwp_text_input_v3.disable requests as the keyboard focus moves across + editable and non-editable elements of the UI. Those two requests are not + expected to be paired with each other, the compositor must be able to + handle consecutive series of the same request. + + State is sent by the state requests (set_surrounding_text, + set_content_type and set_cursor_rectangle) and a commit request. After an + enter event or disable request all state information is invalidated and + needs to be resent by the client. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object. + + + + + + Requests text input on the surface previously obtained from the enter + event. + + This request must be issued every time the focused text input changes + to a new one, including within the current surface. Use + zwp_text_input_v3.disable when there is no longer any input focus on + the current surface. + + Clients must not enable more than one text input on the single seat + and should disable the current text input before enabling the new one. + Requests to enable a text input when another text input is enabled + on the same seat must be ignored by compositor. + + This request resets all state associated with previous enable, disable, + set_surrounding_text, set_text_change_cause, set_content_type, and + set_cursor_rectangle requests, as well as the state associated with + preedit_string, commit_string, and delete_surrounding_text events. + + The set_surrounding_text, set_content_type and set_cursor_rectangle + requests must follow if the text input supports the necessary + functionality. + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The changes must be applied by the compositor after issuing a + zwp_text_input_v3.commit request. + + + + + + Explicitly disable text input on the current surface (typically when + there is no focus on any text entry inside the surface). + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request. + + + + + + Sets the surrounding plain text around the input, excluding the preedit + text. + + The client should notify the compositor of any changes in any of the + values carried with this request, including changes caused by handling + incoming text-input events as well as changes caused by other + mechanisms like keyboard typing. + + If the client is unaware of the text around the cursor, it should not + issue this request, to signify lack of support to the compositor. + + Text is UTF-8 encoded, and should include the cursor position, the + complete selection and additional characters before and after them. + There is a maximum length of wayland messages, so text can not be + longer than 4000 bytes. + + Cursor is the byte offset of the cursor within text buffer. + + Anchor is the byte offset of the selection anchor within text buffer. + If there is no selected text, anchor is the same as cursor. + + If any preedit text is present, it is replaced with a cursor for the + purpose of this event. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Reason for the change of surrounding text or cursor posision. + + + + + + + + Tells the compositor why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor posision, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this request is double-buffered. It must be applied + and reset to initial at the next zwp_text_input_v3.commit request. + + The initial value of cause is input_method. + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some of + the behavior. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request. + Subsequent attempts to update them may have no effect. The values + remain valid until the next committed enable or disable request. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Marks an area around the cursor as a x, y, width, height rectangle in + surface local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor, without obstructing the text being input. + + If the client is unaware of the position of edited text, it should not + issue this request, to signify lack of support to the compositor. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial values describing a cursor rectangle are empty. That means + the text input does not support describing the cursor area. If the + empty values get applied, subsequent attempts to change them may have + no effect. + + As of version 2, the zwp_text_input_v3.commit request does not apply + values sent with this request. Instead, it stores them in a separate + "committed" area. The committed values, if still valid, get applied on + the next wl_surface.commit request on the surface with text-input focus. + Both committed and applied values get invalidated on: + + - the next committed enable or disable request, or + - a change of the focused surface of the text-input (leave or enter events). + + This double stage application allows the compositor to position + the input method popup in the same frame as the contents + of the text on the surface are updated. + + + + + + + + + + Atomically applies state changes recently sent to the compositor. + + The commit request establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (enabled status, content purpose, content hint, + surrounding text and change cause, cursor rectangle) is conceptually + double-buffered within the context of a text input, i.e. between a + committed enable request and the following committed enable or disable + request. + + Protocol requests modify the pending state, as opposed to the current + state in use by the input method. A commit request atomically applies + all pending state, replacing the current state. After commit, the new + pending state is as documented for each related request. + + Requests are applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + The compositor must count the number of commit requests coming from + each zwp_text_input_v3 object and use the count as the serial in done + events. + + + + + + Notification that this seat's text-input focus is on a certain surface. + + If client has created multiple text input objects, compositor must send + this event to all of them. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. This event sets the current surface for the + text-input object. + + + + + + + Notification that this seat's text-input focus is no longer on a + certain surface. The client should reset any preedit string previously + set. + + The leave notification clears the current surface. It is sent before + the enter notification for the new focus. After leave event, compositor + must ignore requests from any text input instances until next enter + event. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + Notify when a new composing text (pre-edit) should be set at the + current cursor position. Any previously set composing text must be + removed. Any previously existing selected text must be removed. + + The argument text contains the pre-edit string buffer. + + The parameters cursor_begin and cursor_end are counted in bytes + relative to the beginning of the submitted text buffer. Cursor should + be hidden when both are equal to -1. + + They could be represented by the client as a line if both values are + the same, or as a text highlight otherwise. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string, and cursor_begin, + cursor_end and cursor_hidden are all 0. + + + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string. + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Before_length and after_length are the number of bytes before and after + the current cursor index (excluding the selection) to delete. + + If a preedit text is present, in effect before_length is counted from + the beginning of it, and after_length from its end (see done event + sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial values of both before_length and after_length are 0. + + + + + + + + Instruct the application to apply changes to state requested by the + preedit_string, commit_string delete_surrounding_text, and action + events. + + The state relating to these events is double-buffered, and each one + modifies the pending state. This event replaces the current state with + the pending state. + + The application must proceed by evaluating the changes in the following + order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + 7. Perform the requested action. + + The serial number reflects the last state of the zwp_text_input_v3 + object known to the compositor. The value of the serial argument must + be equal to the number of commit requests already issued on that object. + + When the client receives a done event with a serial different than the + number of past commit requests, it must proceed with evaluating and + applying the changes as normal, except it should not change the current + state of the zwp_text_input_v3 object. All pending state requests + (set_surrounding_text, set_content_type and set_cursor_rectangle) on + the zwp_text_input_v3 object should be sent and committed after + receiving a zwp_text_input_v3.done event with a matching serial. + + + + + + + + + + + + A possible action to perform on a text input. + + The submit action is intended for input entries that expect some sort of + activation after user interaction, e.g. the URL entry in a browser. + + + + + + + + An action was performed on this text input. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of action is none. + + + + + + + + Notify the application of language used by the input method. + + This event will be sent on creation if known and for all subsequent changes. + + The language should be specified as an IETF BCP 47 tag. + Setting an empty string will reset any known language back to the default unknown state. + + + + + + + Set the actions available for this text input. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request. + + If the available_actions array contains the none action, or contains the + same action multiple times, the compositor must raise the invalid_action + protocol error. + + Initially, no actions are available. + + + + + + + Requests an input panel to be shown (e.g. a on-screen keyboard). + + This request only hints the desired interaction pattern from the + client side, and its effect may be ignored by compositors given + other environmental factors. Repeated calls will be ignored. + + + + + + Requests an input panel to be hidden. + + This request only hints the desired interaction pattern from the + client side, and its effect may be ignored by compositors given + other environmental factors. Repeated calls will be ignored. + + + + + + Style hints for the preedit string. + + + + + + + + + + + + + Notify of contextual hints for the pre-edit string. This + event is always sent together with a zwp_text_input_v3.preedit_string + event. + + The parameters start and end are counted in bytes relative to the + beginning of the text buffer submitted through + zwp_text_input_v3.preedit_string, and represent the substring in the + pre-edit text affected by the hint. + + Multiple events may be submitted if the preedit string has different + sections. The extent of hints may overlap. The parts of the preedit + string that are not covered by any zwp_text_input_v3.preedit_hint event, + the text will be considered unhinted. This is also the case if no + preedit_hint event is sent. + + Clients should provide recognizable visuals to these hints. if they are + unable to comply with this requisition, it may be preferable for them + keep the preedit_shown content hint disabled. + + Values set with this event are double-buffered. They must be applied + and reset on the next zwp_text_input_v3.done event. + + + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + + Destroy the wp_text_input_manager object. + + + + + + Creates a new text-input object for a given seat. + + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/viewporter.xml b/app/src/main/cpp/waylandcomp/protocols/viewporter.xml new file mode 100644 index 000000000..d1048d1f3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/viewporter.xml @@ -0,0 +1,180 @@ + + + + + Copyright ยฉ 2013-2016 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/wlr-data-control-unstable-v1.xml b/app/src/main/cpp/waylandcomp/protocols/wlr-data-control-unstable-v1.xml new file mode 100644 index 000000000..567a09e52 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/wlr-data-control-unstable-v1.xml @@ -0,0 +1,281 @@ + + + + Copyright ยฉ 2018 Simon Ser + Copyright ยฉ 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + Note! This protocol is deprecated and not intended for production use. + For clipboard management, use the ext-data-control-v1 protocol. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/xdg-shell.xml b/app/src/main/cpp/waylandcomp/protocols/xdg-shell.xml new file mode 100644 index 000000000..1df700146 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/xdg-shell.xml @@ -0,0 +1,1351 @@ + + + + + Copyright ยฉ 2008-2013 Kristian Hรธgsberg + Copyright ยฉ 2013 Rafael Antognolli + Copyright ยฉ 2013 Jasper St. Pierre + Copyright ยฉ 2010-2013 Intel Corporation + Copyright ยฉ 2015-2017 Samsung Electronics Co., Ltd + Copyright ยฉ 2015-2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + + + + + + + + + + + + + + + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a role error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The โ€œunresponsiveโ€ + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + + + + + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + + + + + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the โ€˜gravityโ€™ enum, an + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up, the client must + perform an initial commit without any buffer attached. The compositor + will reply with an xdg_surface.configure event. The client must + acknowledge it and is then allowed to attach a buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + + + + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an invalid_size error. When applied, the effective window + geometry will be the set window geometry clamped to the bounding + rectangle of the combined geometry of the surface of the xdg_surface and + the associated subsurfaces. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by performing a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + + + + + + + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause a protocol error. The compositor + may use this information to update the surface position for example + when dragging the top left corner. The compositor may also use + this information to adapt its behavior, e.g. choose an appropriate + cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + The client should draw without shadow or other + decoration outside of the window geometry. + + + + + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in a + invalid_size error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + invalid_size error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + + + + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + + + + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + + + diff --git a/app/src/main/cpp/waylandcomp/protocols/xdg-toplevel-icon-v1.xml b/app/src/main/cpp/waylandcomp/protocols/xdg-toplevel-icon-v1.xml new file mode 100644 index 000000000..985c3f3da --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/xdg-toplevel-icon-v1.xml @@ -0,0 +1,205 @@ + + + + + Copyright ยฉ 2023-2024 Matthias Klumpp + Copyright ยฉ 2024 David Edmundson + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to set icons for their toplevel surfaces + either via the XDG icon stock (using an icon name), or from pixel data. + + A toplevel icon represents the individual toplevel (unlike the application + or launcher icon, which represents the application as a whole), and may be + shown in window switchers, window overviews and taskbars that list + individual windows. + + This document adheres to RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + This interface allows clients to create toplevel window icons and set + them on toplevel windows to be displayed to the user. + + + + + Destroy the toplevel icon manager. + This does not destroy objects created with the manager. + + + + + + Creates a new icon object. This icon can then be attached to a + xdg_toplevel via the 'set_icon' request. + + + + + + + This request assigns the icon 'icon' to 'toplevel', or clears the + toplevel icon if 'icon' was null. + This state is double-buffered and is applied on the next + wl_surface.commit of the toplevel. + + After making this call, the xdg_toplevel_icon_v1 provided as 'icon' + can be destroyed by the client without 'toplevel' losing its icon. + The xdg_toplevel_icon_v1 is immutable from this point, and any + future attempts to change it must raise the + 'xdg_toplevel_icon_v1.immutable' protocol error. + + The compositor must set the toplevel icon from either the pixel data + the icon provides, or by loading a stock icon using the icon name. + See the description of 'xdg_toplevel_icon_v1' for details. + + If 'icon' is set to null, the icon of the respective toplevel is reset + to its default icon (usually the icon of the application, derived from + its desktop-entry file, or a placeholder icon). + If this request is passed an icon with no pixel buffers or icon name + assigned, the icon must be reset just like if 'icon' was null. + + + + + + + + This event indicates an icon size the compositor prefers to be + available if the client has scalable icons and can render to any size. + + When the 'xdg_toplevel_icon_manager_v1' object is created, the + compositor may send one or more 'icon_size' events to describe the list + of preferred icon sizes. If the compositor has no size preference, it + may not send any 'icon_size' event, and it is up to the client to + decide a suitable icon size. + + A sequence of 'icon_size' events must be finished with a 'done' event. + If the compositor has no size preferences, it must still send the + 'done' event, without any preceding 'icon_size' events. + + + + + + + This event is sent after all 'icon_size' events have been sent. + + + + + + + This interface defines a toplevel icon. + An icon can have a name, and multiple buffers. + In order to be applied, the icon must have either a name, or at least + one buffer assigned. Applying an empty icon (with no buffer or name) to + a toplevel should reset its icon to the default icon. + + It is up to compositor policy whether to prefer using a buffer or loading + an icon via its name. See 'set_name' and 'add_buffer' for details. + + + + + + + + + + + Destroys the 'xdg_toplevel_icon_v1' object. + The icon must still remain set on every toplevel it was assigned to, + until the toplevel icon is reset explicitly. + + + + + + This request assigns an icon name to this icon. + Any previously set name is overridden. + + The compositor must resolve 'icon_name' according to the lookup rules + described in the XDG icon theme specification[1] using the + environment's current icon theme. + + If the compositor does not support icon names or cannot resolve + 'icon_name' according to the XDG icon theme specification it must + fall back to using pixel buffer data instead. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', an 'immutable' error must be raised. + + [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + + + + + + This request adds pixel data supplied as wl_buffer to the icon. + + The client should add pixel data for all icon sizes and scales that + it can provide, or which are explicitly requested by the compositor + via 'icon_size' events on xdg_toplevel_icon_manager_v1. + + The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm + and must be a square (width and height being equal). + If any of these buffer requirements are not fulfilled, a 'invalid_buffer' + error must be raised. + + If this icon instance already has a buffer of the same size and scale + from a previous 'add_buffer' request, data from the last request + overrides the preexisting pixel data. + + The wl_buffer must be kept alive for as long as the xdg_toplevel_icon + it is associated with is not destroyed, otherwise a 'no_buffer' error + is raised. The buffer contents must not be modified after it was + assigned to the icon. As a result, the region of the wl_shm_pool's + backing storage used for the wl_buffer must not be modified after this + request is sent. The wl_buffer.release event is unused. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', an 'immutable' error must be raised. + + + + + + diff --git a/app/src/main/cpp/waylandcomp/shaders/cas_frag.h b/app/src/main/cpp/waylandcomp/shaders/cas_frag.h new file mode 100644 index 000000000..21347e38d --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/cas_frag.h @@ -0,0 +1,181 @@ + // 1116.2.0 + #pragma once +const uint32_t cas_code[] = { + 0x07230203,0x00010000,0x0008000b,0x000000f3,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x000000ed,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00030005,0x00000009,0x00007675,0x00060005,0x0000000b,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00030005,0x0000000d,0x00007870,0x00030005,0x00000010,0x00004350, + 0x00040006,0x00000010,0x00000000,0x0063646e,0x00060006,0x00000010,0x00000001,0x6f736572, + 0x6974756c,0x00006e6f,0x00060006,0x00000010,0x00000002,0x72616873,0x73656e70,0x00000073, + 0x00030005,0x00000012,0x00006370,0x00050005,0x0000001b,0x52414853,0x53454e50,0x00000053, + 0x00030005,0x00000022,0x00000061,0x00060005,0x00000026,0x75706e49,0x78655474,0x65727574, + 0x00000000,0x00030005,0x00000036,0x00000062,0x00030005,0x00000041,0x00000063,0x00030005, + 0x0000004d,0x00000064,0x00030005,0x00000057,0x00000065,0x00030005,0x0000005c,0x00000066, + 0x00030005,0x00000065,0x00000067,0x00030005,0x00000071,0x00000068,0x00030005,0x0000007a, + 0x00000069,0x00040005,0x00000085,0x47526e6d,0x00000042,0x00040005,0x0000008f,0x47526e6d, + 0x00003242,0x00040005,0x0000009c,0x4752786d,0x00000042,0x00040005,0x000000a6,0x4752786d, + 0x00003242,0x00050005,0x000000b3,0x4d706372,0x42475278,0x00000000,0x00040005,0x000000b7, + 0x52706d61,0x00004247,0x00040005,0x000000c5,0x6b616570,0x00000000,0x00040005,0x000000cb, + 0x42475277,0x00000000,0x00060005,0x000000d2,0x57706372,0x68676965,0x42475274,0x00000000, + 0x00040005,0x000000d9,0x646e6977,0x0000776f,0x00030005,0x000000e1,0x006c6f63,0x00050005, + 0x000000ed,0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000000b,0x0000001e,0x00000000, + 0x00030047,0x00000010,0x00000002,0x00050048,0x00000010,0x00000000,0x00000023,0x00000000, + 0x00050048,0x00000010,0x00000001,0x00000023,0x00000010,0x00050048,0x00000010,0x00000002, + 0x00000023,0x00000018,0x00040047,0x00000026,0x00000021,0x00000000,0x00040047,0x00000026, + 0x00000022,0x00000000,0x00040047,0x000000ed,0x0000001e,0x00000000,0x00020013,0x00000002, + 0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007, + 0x00000006,0x00000002,0x00040020,0x00000008,0x00000007,0x00000007,0x00040020,0x0000000a, + 0x00000001,0x00000007,0x0004003b,0x0000000a,0x0000000b,0x00000001,0x0004002b,0x00000006, + 0x0000000e,0x3f800000,0x00040017,0x0000000f,0x00000006,0x00000004,0x0005001e,0x00000010, + 0x0000000f,0x00000007,0x00000006,0x00040020,0x00000011,0x00000009,0x00000010,0x0004003b, + 0x00000011,0x00000012,0x00000009,0x00040015,0x00000013,0x00000020,0x00000001,0x0004002b, + 0x00000013,0x00000014,0x00000001,0x00040020,0x00000015,0x00000009,0x00000007,0x00040020, + 0x0000001a,0x00000007,0x00000006,0x0004002b,0x00000013,0x0000001c,0x00000002,0x00040020, + 0x0000001d,0x00000009,0x00000006,0x00040017,0x00000020,0x00000006,0x00000003,0x00040020, + 0x00000021,0x00000007,0x00000020,0x00090019,0x00000023,0x00000006,0x00000001,0x00000000, + 0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x00000024,0x00000023,0x00040020, + 0x00000025,0x00000000,0x00000024,0x0004003b,0x00000025,0x00000026,0x00000000,0x00040015, + 0x00000029,0x00000020,0x00000000,0x0004002b,0x00000029,0x0000002a,0x00000000,0x0004002b, + 0x00000029,0x0000002e,0x00000001,0x0004002b,0x00000006,0x00000039,0x00000000,0x0006002c, + 0x00000020,0x000000b4,0x0000000e,0x0000000e,0x0000000e,0x0004002b,0x00000006,0x000000b9, + 0x40000000,0x0004002b,0x00000006,0x000000c6,0x41000000,0x0004002b,0x00000006,0x000000c7, + 0x40400000,0x0004002b,0x00000006,0x000000cc,0xbf800000,0x0006002c,0x00000020,0x000000cd, + 0x000000cc,0x000000cc,0x000000cc,0x0004002b,0x00000006,0x000000d3,0x40800000,0x00040020, + 0x000000ec,0x00000003,0x0000000f,0x0004003b,0x000000ec,0x000000ed,0x00000003,0x00050036, + 0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008, + 0x00000009,0x00000007,0x0004003b,0x00000008,0x0000000d,0x00000007,0x0004003b,0x0000001a, + 0x0000001b,0x00000007,0x0004003b,0x00000021,0x00000022,0x00000007,0x0004003b,0x00000021, + 0x00000036,0x00000007,0x0004003b,0x00000021,0x00000041,0x00000007,0x0004003b,0x00000021, + 0x0000004d,0x00000007,0x0004003b,0x00000021,0x00000057,0x00000007,0x0004003b,0x00000021, + 0x0000005c,0x00000007,0x0004003b,0x00000021,0x00000065,0x00000007,0x0004003b,0x00000021, + 0x00000071,0x00000007,0x0004003b,0x00000021,0x0000007a,0x00000007,0x0004003b,0x00000021, + 0x00000085,0x00000007,0x0004003b,0x00000021,0x0000008f,0x00000007,0x0004003b,0x00000021, + 0x0000009c,0x00000007,0x0004003b,0x00000021,0x000000a6,0x00000007,0x0004003b,0x00000021, + 0x000000b3,0x00000007,0x0004003b,0x00000021,0x000000b7,0x00000007,0x0004003b,0x0000001a, + 0x000000c5,0x00000007,0x0004003b,0x00000021,0x000000cb,0x00000007,0x0004003b,0x00000021, + 0x000000d2,0x00000007,0x0004003b,0x00000021,0x000000d9,0x00000007,0x0004003b,0x00000021, + 0x000000e1,0x00000007,0x0004003d,0x00000007,0x0000000c,0x0000000b,0x0003003e,0x00000009, + 0x0000000c,0x00050041,0x00000015,0x00000016,0x00000012,0x00000014,0x0004003d,0x00000007, + 0x00000017,0x00000016,0x00050050,0x00000007,0x00000018,0x0000000e,0x0000000e,0x00050088, + 0x00000007,0x00000019,0x00000018,0x00000017,0x0003003e,0x0000000d,0x00000019,0x00050041, + 0x0000001d,0x0000001e,0x00000012,0x0000001c,0x0004003d,0x00000006,0x0000001f,0x0000001e, + 0x0003003e,0x0000001b,0x0000001f,0x0004003d,0x00000024,0x00000027,0x00000026,0x0004003d, + 0x00000007,0x00000028,0x00000009,0x00050041,0x0000001a,0x0000002b,0x0000000d,0x0000002a, + 0x0004003d,0x00000006,0x0000002c,0x0000002b,0x0004007f,0x00000006,0x0000002d,0x0000002c, + 0x00050041,0x0000001a,0x0000002f,0x0000000d,0x0000002e,0x0004003d,0x00000006,0x00000030, + 0x0000002f,0x0004007f,0x00000006,0x00000031,0x00000030,0x00050050,0x00000007,0x00000032, + 0x0000002d,0x00000031,0x00050081,0x00000007,0x00000033,0x00000028,0x00000032,0x00050057, + 0x0000000f,0x00000034,0x00000027,0x00000033,0x0008004f,0x00000020,0x00000035,0x00000034, + 0x00000034,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000022,0x00000035,0x0004003d, + 0x00000024,0x00000037,0x00000026,0x0004003d,0x00000007,0x00000038,0x00000009,0x00050041, + 0x0000001a,0x0000003a,0x0000000d,0x0000002e,0x0004003d,0x00000006,0x0000003b,0x0000003a, + 0x0004007f,0x00000006,0x0000003c,0x0000003b,0x00050050,0x00000007,0x0000003d,0x00000039, + 0x0000003c,0x00050081,0x00000007,0x0000003e,0x00000038,0x0000003d,0x00050057,0x0000000f, + 0x0000003f,0x00000037,0x0000003e,0x0008004f,0x00000020,0x00000040,0x0000003f,0x0000003f, + 0x00000000,0x00000001,0x00000002,0x0003003e,0x00000036,0x00000040,0x0004003d,0x00000024, + 0x00000042,0x00000026,0x0004003d,0x00000007,0x00000043,0x00000009,0x00050041,0x0000001a, + 0x00000044,0x0000000d,0x0000002a,0x0004003d,0x00000006,0x00000045,0x00000044,0x00050041, + 0x0000001a,0x00000046,0x0000000d,0x0000002e,0x0004003d,0x00000006,0x00000047,0x00000046, + 0x0004007f,0x00000006,0x00000048,0x00000047,0x00050050,0x00000007,0x00000049,0x00000045, + 0x00000048,0x00050081,0x00000007,0x0000004a,0x00000043,0x00000049,0x00050057,0x0000000f, + 0x0000004b,0x00000042,0x0000004a,0x0008004f,0x00000020,0x0000004c,0x0000004b,0x0000004b, + 0x00000000,0x00000001,0x00000002,0x0003003e,0x00000041,0x0000004c,0x0004003d,0x00000024, + 0x0000004e,0x00000026,0x0004003d,0x00000007,0x0000004f,0x00000009,0x00050041,0x0000001a, + 0x00000050,0x0000000d,0x0000002a,0x0004003d,0x00000006,0x00000051,0x00000050,0x0004007f, + 0x00000006,0x00000052,0x00000051,0x00050050,0x00000007,0x00000053,0x00000052,0x00000039, + 0x00050081,0x00000007,0x00000054,0x0000004f,0x00000053,0x00050057,0x0000000f,0x00000055, + 0x0000004e,0x00000054,0x0008004f,0x00000020,0x00000056,0x00000055,0x00000055,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x0000004d,0x00000056,0x0004003d,0x00000024,0x00000058, + 0x00000026,0x0004003d,0x00000007,0x00000059,0x00000009,0x00050057,0x0000000f,0x0000005a, + 0x00000058,0x00000059,0x0008004f,0x00000020,0x0000005b,0x0000005a,0x0000005a,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x00000057,0x0000005b,0x0004003d,0x00000024,0x0000005d, + 0x00000026,0x0004003d,0x00000007,0x0000005e,0x00000009,0x00050041,0x0000001a,0x0000005f, + 0x0000000d,0x0000002a,0x0004003d,0x00000006,0x00000060,0x0000005f,0x00050050,0x00000007, + 0x00000061,0x00000060,0x00000039,0x00050081,0x00000007,0x00000062,0x0000005e,0x00000061, + 0x00050057,0x0000000f,0x00000063,0x0000005d,0x00000062,0x0008004f,0x00000020,0x00000064, + 0x00000063,0x00000063,0x00000000,0x00000001,0x00000002,0x0003003e,0x0000005c,0x00000064, + 0x0004003d,0x00000024,0x00000066,0x00000026,0x0004003d,0x00000007,0x00000067,0x00000009, + 0x00050041,0x0000001a,0x00000068,0x0000000d,0x0000002a,0x0004003d,0x00000006,0x00000069, + 0x00000068,0x0004007f,0x00000006,0x0000006a,0x00000069,0x00050041,0x0000001a,0x0000006b, + 0x0000000d,0x0000002e,0x0004003d,0x00000006,0x0000006c,0x0000006b,0x00050050,0x00000007, + 0x0000006d,0x0000006a,0x0000006c,0x00050081,0x00000007,0x0000006e,0x00000067,0x0000006d, + 0x00050057,0x0000000f,0x0000006f,0x00000066,0x0000006e,0x0008004f,0x00000020,0x00000070, + 0x0000006f,0x0000006f,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000065,0x00000070, + 0x0004003d,0x00000024,0x00000072,0x00000026,0x0004003d,0x00000007,0x00000073,0x00000009, + 0x00050041,0x0000001a,0x00000074,0x0000000d,0x0000002e,0x0004003d,0x00000006,0x00000075, + 0x00000074,0x00050050,0x00000007,0x00000076,0x00000039,0x00000075,0x00050081,0x00000007, + 0x00000077,0x00000073,0x00000076,0x00050057,0x0000000f,0x00000078,0x00000072,0x00000077, + 0x0008004f,0x00000020,0x00000079,0x00000078,0x00000078,0x00000000,0x00000001,0x00000002, + 0x0003003e,0x00000071,0x00000079,0x0004003d,0x00000024,0x0000007b,0x00000026,0x0004003d, + 0x00000007,0x0000007c,0x00000009,0x00050041,0x0000001a,0x0000007d,0x0000000d,0x0000002a, + 0x0004003d,0x00000006,0x0000007e,0x0000007d,0x00050041,0x0000001a,0x0000007f,0x0000000d, + 0x0000002e,0x0004003d,0x00000006,0x00000080,0x0000007f,0x00050050,0x00000007,0x00000081, + 0x0000007e,0x00000080,0x00050081,0x00000007,0x00000082,0x0000007c,0x00000081,0x00050057, + 0x0000000f,0x00000083,0x0000007b,0x00000082,0x0008004f,0x00000020,0x00000084,0x00000083, + 0x00000083,0x00000000,0x00000001,0x00000002,0x0003003e,0x0000007a,0x00000084,0x0004003d, + 0x00000020,0x00000086,0x0000004d,0x0004003d,0x00000020,0x00000087,0x00000057,0x0007000c, + 0x00000020,0x00000088,0x00000001,0x00000025,0x00000086,0x00000087,0x0004003d,0x00000020, + 0x00000089,0x0000005c,0x0004003d,0x00000020,0x0000008a,0x00000036,0x0007000c,0x00000020, + 0x0000008b,0x00000001,0x00000025,0x00000089,0x0000008a,0x0007000c,0x00000020,0x0000008c, + 0x00000001,0x00000025,0x00000088,0x0000008b,0x0004003d,0x00000020,0x0000008d,0x00000071, + 0x0007000c,0x00000020,0x0000008e,0x00000001,0x00000025,0x0000008c,0x0000008d,0x0003003e, + 0x00000085,0x0000008e,0x0004003d,0x00000020,0x00000090,0x00000085,0x0004003d,0x00000020, + 0x00000091,0x00000022,0x0007000c,0x00000020,0x00000092,0x00000001,0x00000025,0x00000090, + 0x00000091,0x0004003d,0x00000020,0x00000093,0x00000065,0x0004003d,0x00000020,0x00000094, + 0x00000041,0x0007000c,0x00000020,0x00000095,0x00000001,0x00000025,0x00000093,0x00000094, + 0x0007000c,0x00000020,0x00000096,0x00000001,0x00000025,0x00000092,0x00000095,0x0004003d, + 0x00000020,0x00000097,0x0000007a,0x0007000c,0x00000020,0x00000098,0x00000001,0x00000025, + 0x00000096,0x00000097,0x0003003e,0x0000008f,0x00000098,0x0004003d,0x00000020,0x00000099, + 0x0000008f,0x0004003d,0x00000020,0x0000009a,0x00000085,0x00050081,0x00000020,0x0000009b, + 0x0000009a,0x00000099,0x0003003e,0x00000085,0x0000009b,0x0004003d,0x00000020,0x0000009d, + 0x0000004d,0x0004003d,0x00000020,0x0000009e,0x00000057,0x0007000c,0x00000020,0x0000009f, + 0x00000001,0x00000028,0x0000009d,0x0000009e,0x0004003d,0x00000020,0x000000a0,0x0000005c, + 0x0004003d,0x00000020,0x000000a1,0x00000036,0x0007000c,0x00000020,0x000000a2,0x00000001, + 0x00000028,0x000000a0,0x000000a1,0x0007000c,0x00000020,0x000000a3,0x00000001,0x00000028, + 0x0000009f,0x000000a2,0x0004003d,0x00000020,0x000000a4,0x00000071,0x0007000c,0x00000020, + 0x000000a5,0x00000001,0x00000028,0x000000a3,0x000000a4,0x0003003e,0x0000009c,0x000000a5, + 0x0004003d,0x00000020,0x000000a7,0x0000009c,0x0004003d,0x00000020,0x000000a8,0x00000022, + 0x0007000c,0x00000020,0x000000a9,0x00000001,0x00000028,0x000000a7,0x000000a8,0x0004003d, + 0x00000020,0x000000aa,0x00000065,0x0004003d,0x00000020,0x000000ab,0x00000041,0x0007000c, + 0x00000020,0x000000ac,0x00000001,0x00000028,0x000000aa,0x000000ab,0x0007000c,0x00000020, + 0x000000ad,0x00000001,0x00000028,0x000000a9,0x000000ac,0x0004003d,0x00000020,0x000000ae, + 0x0000007a,0x0007000c,0x00000020,0x000000af,0x00000001,0x00000028,0x000000ad,0x000000ae, + 0x0003003e,0x000000a6,0x000000af,0x0004003d,0x00000020,0x000000b0,0x000000a6,0x0004003d, + 0x00000020,0x000000b1,0x0000009c,0x00050081,0x00000020,0x000000b2,0x000000b1,0x000000b0, + 0x0003003e,0x0000009c,0x000000b2,0x0004003d,0x00000020,0x000000b5,0x0000009c,0x00050088, + 0x00000020,0x000000b6,0x000000b4,0x000000b5,0x0003003e,0x000000b3,0x000000b6,0x0004003d, + 0x00000020,0x000000b8,0x00000085,0x0004003d,0x00000020,0x000000ba,0x0000009c,0x00060050, + 0x00000020,0x000000bb,0x000000b9,0x000000b9,0x000000b9,0x00050083,0x00000020,0x000000bc, + 0x000000bb,0x000000ba,0x0007000c,0x00000020,0x000000bd,0x00000001,0x00000025,0x000000b8, + 0x000000bc,0x0004003d,0x00000020,0x000000be,0x000000b3,0x00050085,0x00000020,0x000000bf, + 0x000000bd,0x000000be,0x00060050,0x00000020,0x000000c0,0x00000039,0x00000039,0x00000039, + 0x00060050,0x00000020,0x000000c1,0x0000000e,0x0000000e,0x0000000e,0x0008000c,0x00000020, + 0x000000c2,0x00000001,0x0000002b,0x000000bf,0x000000c0,0x000000c1,0x0003003e,0x000000b7, + 0x000000c2,0x0004003d,0x00000020,0x000000c3,0x000000b7,0x0006000c,0x00000020,0x000000c4, + 0x00000001,0x00000020,0x000000c3,0x0003003e,0x000000b7,0x000000c4,0x0004003d,0x00000006, + 0x000000c8,0x0000001b,0x00050085,0x00000006,0x000000c9,0x000000c7,0x000000c8,0x00050083, + 0x00000006,0x000000ca,0x000000c6,0x000000c9,0x0003003e,0x000000c5,0x000000ca,0x0004003d, + 0x00000020,0x000000ce,0x000000b7,0x0004003d,0x00000006,0x000000cf,0x000000c5,0x0005008e, + 0x00000020,0x000000d0,0x000000ce,0x000000cf,0x00050088,0x00000020,0x000000d1,0x000000cd, + 0x000000d0,0x0003003e,0x000000cb,0x000000d1,0x0004003d,0x00000020,0x000000d4,0x000000cb, + 0x0005008e,0x00000020,0x000000d5,0x000000d4,0x000000d3,0x00060050,0x00000020,0x000000d6, + 0x0000000e,0x0000000e,0x0000000e,0x00050081,0x00000020,0x000000d7,0x000000d6,0x000000d5, + 0x00050088,0x00000020,0x000000d8,0x000000b4,0x000000d7,0x0003003e,0x000000d2,0x000000d8, + 0x0004003d,0x00000020,0x000000da,0x00000036,0x0004003d,0x00000020,0x000000db,0x0000004d, + 0x00050081,0x00000020,0x000000dc,0x000000da,0x000000db,0x0004003d,0x00000020,0x000000dd, + 0x0000005c,0x0004003d,0x00000020,0x000000de,0x00000071,0x00050081,0x00000020,0x000000df, + 0x000000dd,0x000000de,0x00050081,0x00000020,0x000000e0,0x000000dc,0x000000df,0x0003003e, + 0x000000d9,0x000000e0,0x0004003d,0x00000020,0x000000e2,0x000000d9,0x0004003d,0x00000020, + 0x000000e3,0x000000cb,0x00050085,0x00000020,0x000000e4,0x000000e2,0x000000e3,0x0004003d, + 0x00000020,0x000000e5,0x00000057,0x00050081,0x00000020,0x000000e6,0x000000e4,0x000000e5, + 0x0004003d,0x00000020,0x000000e7,0x000000d2,0x00050085,0x00000020,0x000000e8,0x000000e6, + 0x000000e7,0x00060050,0x00000020,0x000000e9,0x00000039,0x00000039,0x00000039,0x00060050, + 0x00000020,0x000000ea,0x0000000e,0x0000000e,0x0000000e,0x0008000c,0x00000020,0x000000eb, + 0x00000001,0x0000002b,0x000000e8,0x000000e9,0x000000ea,0x0003003e,0x000000e1,0x000000eb, + 0x0004003d,0x00000020,0x000000ee,0x000000e1,0x00050051,0x00000006,0x000000ef,0x000000ee, + 0x00000000,0x00050051,0x00000006,0x000000f0,0x000000ee,0x00000001,0x00050051,0x00000006, + 0x000000f1,0x000000ee,0x00000002,0x00070050,0x0000000f,0x000000f2,0x000000ef,0x000000f0, + 0x000000f1,0x0000000e,0x0003003e,0x000000ed,0x000000f2,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/color_frag.h b/app/src/main/cpp/waylandcomp/shaders/color_frag.h new file mode 100644 index 000000000..bde543ab4 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/color_frag.h @@ -0,0 +1,76 @@ + // 1116.5.0 + #pragma once +const uint32_t color_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000059,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000011,0x0000004e,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00050005,0x00000009,0x65786574,0x6c6f436c,0x0000726f,0x00060005,0x0000000d, + 0x75706e49,0x78655474,0x65727574,0x00000000,0x00060005,0x00000011,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00040005,0x00000016,0x6f6c6f63,0x00000072,0x00030005,0x0000001a, + 0x00004350,0x00040006,0x0000001a,0x00000000,0x0063646e,0x00060006,0x0000001a,0x00000001, + 0x67697262,0x656e7468,0x00007373,0x00060006,0x0000001a,0x00000002,0x746e6f63,0x74736172, + 0x00000000,0x00050006,0x0000001a,0x00000003,0x6d6d6167,0x00000061,0x00060006,0x0000001a, + 0x00000004,0x75746173,0x69746172,0x00006e6f,0x00030005,0x0000001c,0x00006370,0x00040005, + 0x00000036,0x79657267,0x00000000,0x00050005,0x0000004e,0x4374756f,0x726f6c6f,0x00000000, + 0x00040047,0x0000000d,0x00000021,0x00000000,0x00040047,0x0000000d,0x00000022,0x00000000, + 0x00040047,0x00000011,0x0000001e,0x00000000,0x00030047,0x0000001a,0x00000002,0x00050048, + 0x0000001a,0x00000000,0x00000023,0x00000000,0x00050048,0x0000001a,0x00000001,0x00000023, + 0x00000010,0x00050048,0x0000001a,0x00000002,0x00000023,0x00000014,0x00050048,0x0000001a, + 0x00000003,0x00000023,0x00000018,0x00050048,0x0000001a,0x00000004,0x00000023,0x0000001c, + 0x00040047,0x0000004e,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003, + 0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004, + 0x00040020,0x00000008,0x00000007,0x00000007,0x00090019,0x0000000a,0x00000006,0x00000001, + 0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x0000000b,0x0000000a, + 0x00040020,0x0000000c,0x00000000,0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000000, + 0x00040017,0x0000000f,0x00000006,0x00000002,0x00040020,0x00000010,0x00000001,0x0000000f, + 0x0004003b,0x00000010,0x00000011,0x00000001,0x00040017,0x00000014,0x00000006,0x00000003, + 0x00040020,0x00000015,0x00000007,0x00000014,0x0007001e,0x0000001a,0x00000007,0x00000006, + 0x00000006,0x00000006,0x00000006,0x00040020,0x0000001b,0x00000009,0x0000001a,0x0004003b, + 0x0000001b,0x0000001c,0x00000009,0x00040015,0x0000001d,0x00000020,0x00000001,0x0004002b, + 0x0000001d,0x0000001e,0x00000001,0x00040020,0x0000001f,0x00000009,0x00000006,0x0004002b, + 0x00000006,0x00000024,0x00000000,0x0004002b,0x00000006,0x00000025,0x3f800000,0x0004002b, + 0x00000006,0x0000002a,0x3f000000,0x0004002b,0x0000001d,0x0000002d,0x00000002,0x0004002b, + 0x00000006,0x00000031,0x40000000,0x0004002b,0x00000006,0x00000038,0x3e59b3d0,0x0004002b, + 0x00000006,0x00000039,0x3f371759,0x0004002b,0x00000006,0x0000003a,0x3d93dd98,0x0006002c, + 0x00000014,0x0000003b,0x00000038,0x00000039,0x0000003a,0x0004002b,0x0000001d,0x00000040, + 0x00000004,0x0004002b,0x0000001d,0x00000047,0x00000003,0x00040020,0x0000004d,0x00000003, + 0x00000007,0x0004003b,0x0000004d,0x0000004e,0x00000003,0x00040015,0x00000050,0x00000020, + 0x00000000,0x0004002b,0x00000050,0x00000051,0x00000003,0x00040020,0x00000052,0x00000007, + 0x00000006,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005, + 0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003b,0x00000015,0x00000016,0x00000007, + 0x0004003b,0x00000015,0x00000036,0x00000007,0x0004003d,0x0000000b,0x0000000e,0x0000000d, + 0x0004003d,0x0000000f,0x00000012,0x00000011,0x00050057,0x00000007,0x00000013,0x0000000e, + 0x00000012,0x0003003e,0x00000009,0x00000013,0x0004003d,0x00000007,0x00000017,0x00000009, + 0x0008004f,0x00000014,0x00000018,0x00000017,0x00000017,0x00000000,0x00000001,0x00000002, + 0x0003003e,0x00000016,0x00000018,0x0004003d,0x00000014,0x00000019,0x00000016,0x00050041, + 0x0000001f,0x00000020,0x0000001c,0x0000001e,0x0004003d,0x00000006,0x00000021,0x00000020, + 0x00060050,0x00000014,0x00000022,0x00000021,0x00000021,0x00000021,0x00050081,0x00000014, + 0x00000023,0x00000019,0x00000022,0x00060050,0x00000014,0x00000026,0x00000024,0x00000024, + 0x00000024,0x00060050,0x00000014,0x00000027,0x00000025,0x00000025,0x00000025,0x0008000c, + 0x00000014,0x00000028,0x00000001,0x0000002b,0x00000023,0x00000026,0x00000027,0x0003003e, + 0x00000016,0x00000028,0x0004003d,0x00000014,0x00000029,0x00000016,0x00060050,0x00000014, + 0x0000002b,0x0000002a,0x0000002a,0x0000002a,0x00050083,0x00000014,0x0000002c,0x00000029, + 0x0000002b,0x00050041,0x0000001f,0x0000002e,0x0000001c,0x0000002d,0x0004003d,0x00000006, + 0x0000002f,0x0000002e,0x00050081,0x00000006,0x00000030,0x0000002f,0x00000025,0x0008000c, + 0x00000006,0x00000032,0x00000001,0x0000002b,0x00000030,0x0000002a,0x00000031,0x0005008e, + 0x00000014,0x00000033,0x0000002c,0x00000032,0x00060050,0x00000014,0x00000034,0x0000002a, + 0x0000002a,0x0000002a,0x00050081,0x00000014,0x00000035,0x00000033,0x00000034,0x0003003e, + 0x00000016,0x00000035,0x0004003d,0x00000014,0x00000037,0x00000016,0x00050094,0x00000006, + 0x0000003c,0x00000037,0x0000003b,0x00060050,0x00000014,0x0000003d,0x0000003c,0x0000003c, + 0x0000003c,0x0003003e,0x00000036,0x0000003d,0x0004003d,0x00000014,0x0000003e,0x00000036, + 0x0004003d,0x00000014,0x0000003f,0x00000016,0x00050041,0x0000001f,0x00000041,0x0000001c, + 0x00000040,0x0004003d,0x00000006,0x00000042,0x00000041,0x0008000c,0x00000006,0x00000043, + 0x00000001,0x0000002b,0x00000042,0x00000024,0x00000031,0x00060050,0x00000014,0x00000044, + 0x00000043,0x00000043,0x00000043,0x0008000c,0x00000014,0x00000045,0x00000001,0x0000002e, + 0x0000003e,0x0000003f,0x00000044,0x0003003e,0x00000016,0x00000045,0x0004003d,0x00000014, + 0x00000046,0x00000016,0x00050041,0x0000001f,0x00000048,0x0000001c,0x00000047,0x0004003d, + 0x00000006,0x00000049,0x00000048,0x00050088,0x00000006,0x0000004a,0x00000025,0x00000049, + 0x00060050,0x00000014,0x0000004b,0x0000004a,0x0000004a,0x0000004a,0x0007000c,0x00000014, + 0x0000004c,0x00000001,0x0000001a,0x00000046,0x0000004b,0x0003003e,0x00000016,0x0000004c, + 0x0004003d,0x00000014,0x0000004f,0x00000016,0x00050041,0x00000052,0x00000053,0x00000009, + 0x00000051,0x0004003d,0x00000006,0x00000054,0x00000053,0x00050051,0x00000006,0x00000055, + 0x0000004f,0x00000000,0x00050051,0x00000006,0x00000056,0x0000004f,0x00000001,0x00050051, + 0x00000006,0x00000057,0x0000004f,0x00000002,0x00070050,0x00000007,0x00000058,0x00000055, + 0x00000056,0x00000057,0x00000054,0x0003003e,0x0000004e,0x00000058,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/crt_frag.h b/app/src/main/cpp/waylandcomp/shaders/crt_frag.h new file mode 100644 index 000000000..2239f2274 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/crt_frag.h @@ -0,0 +1,78 @@ + // 1116.2.0 + #pragma once +const uint32_t crt_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000063,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000004f,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00030005,0x00000009,0x00565576,0x00060005,0x0000000b,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00050005,0x0000000f,0x616e6966,0x6c6f436c,0x0000726f,0x00060005, + 0x00000013,0x75706e49,0x78655474,0x65727574,0x00000000,0x00050005,0x0000003b,0x6e616373, + 0x656e696c,0x00000058,0x00050005,0x00000045,0x6e616373,0x656e696c,0x00000059,0x00050005, + 0x0000004f,0x4374756f,0x726f6c6f,0x00000000,0x00030005,0x00000060,0x00004350,0x00040006, + 0x00000060,0x00000000,0x0063646e,0x00060006,0x00000060,0x00000001,0x6f736572,0x6974756c, + 0x00006e6f,0x00030005,0x00000062,0x00006370,0x00040047,0x0000000b,0x0000001e,0x00000000, + 0x00040047,0x00000013,0x00000021,0x00000000,0x00040047,0x00000013,0x00000022,0x00000000, + 0x00040047,0x0000004f,0x0000001e,0x00000000,0x00030047,0x00000060,0x00000002,0x00050048, + 0x00000060,0x00000000,0x00000023,0x00000000,0x00050048,0x00000060,0x00000001,0x00000023, + 0x00000010,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, + 0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,0x00040020,0x00000008,0x00000007, + 0x00000007,0x00040020,0x0000000a,0x00000001,0x00000007,0x0004003b,0x0000000a,0x0000000b, + 0x00000001,0x00040017,0x0000000d,0x00000006,0x00000004,0x00040020,0x0000000e,0x00000007, + 0x0000000d,0x00090019,0x00000010,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, + 0x00000001,0x00000000,0x0003001b,0x00000011,0x00000010,0x00040020,0x00000012,0x00000000, + 0x00000011,0x0004003b,0x00000012,0x00000013,0x00000000,0x0004002b,0x00000006,0x00000019, + 0x3f000000,0x0004002b,0x00000006,0x0000001c,0x3f8051ec,0x00040015,0x00000021,0x00000020, + 0x00000000,0x0004002b,0x00000021,0x00000022,0x00000000,0x0004002b,0x00000021,0x00000024, + 0x00000001,0x00040020,0x00000025,0x00000007,0x00000006,0x0004002b,0x00000021,0x00000031, + 0x00000002,0x00040017,0x00000033,0x00000006,0x00000003,0x0004002b,0x00000006,0x0000003e, + 0x44800000,0x0004002b,0x00000006,0x00000042,0x3e000000,0x0004002b,0x00000006,0x0000004b, + 0x3ec00000,0x00040020,0x0000004e,0x00000003,0x0000000d,0x0004003b,0x0000004e,0x0000004f, + 0x00000003,0x0004002b,0x00000006,0x00000052,0x00000000,0x0006002c,0x00000033,0x00000053, + 0x00000052,0x00000052,0x00000052,0x0004002b,0x00000021,0x00000059,0x00000003,0x0004001e, + 0x00000060,0x0000000d,0x00000007,0x00040020,0x00000061,0x00000009,0x00000060,0x0004003b, + 0x00000061,0x00000062,0x00000009,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003, + 0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003b,0x0000000e, + 0x0000000f,0x00000007,0x0004003b,0x00000025,0x0000003b,0x00000007,0x0004003b,0x00000025, + 0x00000045,0x00000007,0x0004003d,0x00000007,0x0000000c,0x0000000b,0x0003003e,0x00000009, + 0x0000000c,0x0004003d,0x00000011,0x00000014,0x00000013,0x0004003d,0x00000007,0x00000015, + 0x00000009,0x00050057,0x0000000d,0x00000016,0x00000014,0x00000015,0x0003003e,0x0000000f, + 0x00000016,0x0004003d,0x00000011,0x00000017,0x00000013,0x0004003d,0x00000007,0x00000018, + 0x00000009,0x00050050,0x00000007,0x0000001a,0x00000019,0x00000019,0x00050083,0x00000007, + 0x0000001b,0x00000018,0x0000001a,0x0005008e,0x00000007,0x0000001d,0x0000001b,0x0000001c, + 0x00050050,0x00000007,0x0000001e,0x00000019,0x00000019,0x00050081,0x00000007,0x0000001f, + 0x0000001d,0x0000001e,0x00050057,0x0000000d,0x00000020,0x00000017,0x0000001f,0x00050051, + 0x00000006,0x00000023,0x00000020,0x00000000,0x00050041,0x00000025,0x00000026,0x0000000f, + 0x00000024,0x0004003d,0x00000006,0x00000027,0x00000026,0x0004003d,0x00000011,0x00000028, + 0x00000013,0x0004003d,0x00000007,0x00000029,0x00000009,0x00050050,0x00000007,0x0000002a, + 0x00000019,0x00000019,0x00050083,0x00000007,0x0000002b,0x00000029,0x0000002a,0x00050050, + 0x00000007,0x0000002c,0x0000001c,0x0000001c,0x00050088,0x00000007,0x0000002d,0x0000002b, + 0x0000002c,0x00050050,0x00000007,0x0000002e,0x00000019,0x00000019,0x00050081,0x00000007, + 0x0000002f,0x0000002d,0x0000002e,0x00050057,0x0000000d,0x00000030,0x00000028,0x0000002f, + 0x00050051,0x00000006,0x00000032,0x00000030,0x00000002,0x00060050,0x00000033,0x00000034, + 0x00000023,0x00000027,0x00000032,0x00050041,0x00000025,0x00000035,0x0000000f,0x00000022, + 0x00050051,0x00000006,0x00000036,0x00000034,0x00000000,0x0003003e,0x00000035,0x00000036, + 0x00050041,0x00000025,0x00000037,0x0000000f,0x00000024,0x00050051,0x00000006,0x00000038, + 0x00000034,0x00000001,0x0003003e,0x00000037,0x00000038,0x00050041,0x00000025,0x00000039, + 0x0000000f,0x00000031,0x00050051,0x00000006,0x0000003a,0x00000034,0x00000002,0x0003003e, + 0x00000039,0x0000003a,0x00050041,0x00000025,0x0000003c,0x00000009,0x00000022,0x0004003d, + 0x00000006,0x0000003d,0x0000003c,0x00050085,0x00000006,0x0000003f,0x0000003d,0x0000003e, + 0x0006000c,0x00000006,0x00000040,0x00000001,0x0000000d,0x0000003f,0x00050085,0x00000006, + 0x00000041,0x00000040,0x00000019,0x00050085,0x00000006,0x00000043,0x00000041,0x00000042, + 0x0006000c,0x00000006,0x00000044,0x00000001,0x00000004,0x00000043,0x0003003e,0x0000003b, + 0x00000044,0x00050041,0x00000025,0x00000046,0x00000009,0x00000024,0x0004003d,0x00000006, + 0x00000047,0x00000046,0x00050085,0x00000006,0x00000048,0x00000047,0x0000003e,0x0006000c, + 0x00000006,0x00000049,0x00000001,0x0000000d,0x00000048,0x00050085,0x00000006,0x0000004a, + 0x00000049,0x00000019,0x00050085,0x00000006,0x0000004c,0x0000004a,0x0000004b,0x0006000c, + 0x00000006,0x0000004d,0x00000001,0x00000004,0x0000004c,0x0003003e,0x00000045,0x0000004d, + 0x0004003d,0x0000000d,0x00000050,0x0000000f,0x0008004f,0x00000033,0x00000051,0x00000050, + 0x00000050,0x00000000,0x00000001,0x00000002,0x0004003d,0x00000006,0x00000054,0x0000003b, + 0x0004003d,0x00000006,0x00000055,0x00000045,0x00050081,0x00000006,0x00000056,0x00000054, + 0x00000055,0x00060050,0x00000033,0x00000057,0x00000056,0x00000056,0x00000056,0x0008000c, + 0x00000033,0x00000058,0x00000001,0x0000002e,0x00000051,0x00000053,0x00000057,0x00050041, + 0x00000025,0x0000005a,0x0000000f,0x00000059,0x0004003d,0x00000006,0x0000005b,0x0000005a, + 0x00050051,0x00000006,0x0000005c,0x00000058,0x00000000,0x00050051,0x00000006,0x0000005d, + 0x00000058,0x00000001,0x00050051,0x00000006,0x0000005e,0x00000058,0x00000002,0x00070050, + 0x0000000d,0x0000005f,0x0000005c,0x0000005d,0x0000005e,0x0000005b,0x0003003e,0x0000004f, + 0x0000005f,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/deband_frag.h b/app/src/main/cpp/waylandcomp/shaders/deband_frag.h new file mode 100644 index 000000000..4a59f2135 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/deband_frag.h @@ -0,0 +1,70 @@ + // 1116.2.0 + #pragma once +const uint32_t deband_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000053,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0008000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000021,0x00000029,0x0000004d, + 0x00030010,0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004, + 0x6e69616d,0x00000000,0x00050005,0x0000000b,0x286e6769,0x3b326676,0x00000000,0x00030005, + 0x0000000a,0x00000070,0x00030005,0x0000001a,0x006c6f63,0x00060005,0x0000001e,0x75706e49, + 0x78655474,0x65727574,0x00000000,0x00060005,0x00000021,0x67617266,0x43786554,0x64726f6f, + 0x00000000,0x00030005,0x00000027,0x0000316e,0x00060005,0x00000029,0x465f6c67,0x43676172, + 0x64726f6f,0x00000000,0x00040005,0x0000002a,0x61726170,0x0000006d,0x00030005,0x0000002e, + 0x0000326e,0x00040005,0x00000035,0x61726170,0x0000006d,0x00030005,0x00000037,0x00697274, + 0x00030005,0x0000003e,0x00004350,0x00040006,0x0000003e,0x00000000,0x0063646e,0x00060006, + 0x0000003e,0x00000001,0x6f736572,0x6974756c,0x00006e6f,0x00060006,0x0000003e,0x00000002, + 0x65727473,0x6874676e,0x00000000,0x00030005,0x00000040,0x00006370,0x00050005,0x0000004d, + 0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000001e,0x00000021,0x00000000,0x00040047, + 0x0000001e,0x00000022,0x00000000,0x00040047,0x00000021,0x0000001e,0x00000000,0x00040047, + 0x00000029,0x0000000b,0x0000000f,0x00030047,0x0000003e,0x00000002,0x00050048,0x0000003e, + 0x00000000,0x00000023,0x00000000,0x00050048,0x0000003e,0x00000001,0x00000023,0x00000010, + 0x00050048,0x0000003e,0x00000002,0x00000023,0x00000018,0x00040047,0x0000004d,0x0000001e, + 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, + 0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,0x00040020,0x00000008,0x00000007, + 0x00000007,0x00040021,0x00000009,0x00000006,0x00000008,0x0004002b,0x00000006,0x0000000d, + 0x4253ee82,0x0004002b,0x00000006,0x0000000f,0x3d897143,0x0004002b,0x00000006,0x00000010, + 0x3bbf4590,0x0005002c,0x00000007,0x00000011,0x0000000f,0x00000010,0x00040017,0x00000018, + 0x00000006,0x00000003,0x00040020,0x00000019,0x00000007,0x00000018,0x00090019,0x0000001b, + 0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b, + 0x0000001c,0x0000001b,0x00040020,0x0000001d,0x00000000,0x0000001c,0x0004003b,0x0000001d, + 0x0000001e,0x00000000,0x00040020,0x00000020,0x00000001,0x00000007,0x0004003b,0x00000020, + 0x00000021,0x00000001,0x00040017,0x00000023,0x00000006,0x00000004,0x00040020,0x00000026, + 0x00000007,0x00000006,0x00040020,0x00000028,0x00000001,0x00000023,0x0004003b,0x00000028, + 0x00000029,0x00000001,0x0004002b,0x00000006,0x00000031,0x41300000,0x0004002b,0x00000006, + 0x00000032,0x41880000,0x0005002c,0x00000007,0x00000033,0x00000031,0x00000032,0x0004002b, + 0x00000006,0x0000003b,0x3f800000,0x0005001e,0x0000003e,0x00000023,0x00000007,0x00000006, + 0x00040020,0x0000003f,0x00000009,0x0000003e,0x0004003b,0x0000003f,0x00000040,0x00000009, + 0x00040015,0x00000041,0x00000020,0x00000001,0x0004002b,0x00000041,0x00000042,0x00000002, + 0x00040020,0x00000043,0x00000009,0x00000006,0x0004002b,0x00000006,0x00000046,0x437f0000, + 0x00040020,0x0000004c,0x00000003,0x00000023,0x0004003b,0x0000004c,0x0000004d,0x00000003, + 0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b, + 0x00000019,0x0000001a,0x00000007,0x0004003b,0x00000026,0x00000027,0x00000007,0x0004003b, + 0x00000008,0x0000002a,0x00000007,0x0004003b,0x00000026,0x0000002e,0x00000007,0x0004003b, + 0x00000008,0x00000035,0x00000007,0x0004003b,0x00000026,0x00000037,0x00000007,0x0004003d, + 0x0000001c,0x0000001f,0x0000001e,0x0004003d,0x00000007,0x00000022,0x00000021,0x00050057, + 0x00000023,0x00000024,0x0000001f,0x00000022,0x0008004f,0x00000018,0x00000025,0x00000024, + 0x00000024,0x00000000,0x00000001,0x00000002,0x0003003e,0x0000001a,0x00000025,0x0004003d, + 0x00000023,0x0000002b,0x00000029,0x0007004f,0x00000007,0x0000002c,0x0000002b,0x0000002b, + 0x00000000,0x00000001,0x0003003e,0x0000002a,0x0000002c,0x00050039,0x00000006,0x0000002d, + 0x0000000b,0x0000002a,0x0003003e,0x00000027,0x0000002d,0x0004003d,0x00000023,0x0000002f, + 0x00000029,0x0007004f,0x00000007,0x00000030,0x0000002f,0x0000002f,0x00000000,0x00000001, + 0x00050081,0x00000007,0x00000034,0x00000030,0x00000033,0x0003003e,0x00000035,0x00000034, + 0x00050039,0x00000006,0x00000036,0x0000000b,0x00000035,0x0003003e,0x0000002e,0x00000036, + 0x0004003d,0x00000006,0x00000038,0x00000027,0x0004003d,0x00000006,0x00000039,0x0000002e, + 0x00050081,0x00000006,0x0000003a,0x00000038,0x00000039,0x00050083,0x00000006,0x0000003c, + 0x0000003a,0x0000003b,0x0003003e,0x00000037,0x0000003c,0x0004003d,0x00000006,0x0000003d, + 0x00000037,0x00050041,0x00000043,0x00000044,0x00000040,0x00000042,0x0004003d,0x00000006, + 0x00000045,0x00000044,0x00050088,0x00000006,0x00000047,0x00000045,0x00000046,0x00050085, + 0x00000006,0x00000048,0x0000003d,0x00000047,0x00060050,0x00000018,0x00000049,0x00000048, + 0x00000048,0x00000048,0x0004003d,0x00000018,0x0000004a,0x0000001a,0x00050081,0x00000018, + 0x0000004b,0x0000004a,0x00000049,0x0003003e,0x0000001a,0x0000004b,0x0004003d,0x00000018, + 0x0000004e,0x0000001a,0x00050051,0x00000006,0x0000004f,0x0000004e,0x00000000,0x00050051, + 0x00000006,0x00000050,0x0000004e,0x00000001,0x00050051,0x00000006,0x00000051,0x0000004e, + 0x00000002,0x00070050,0x00000023,0x00000052,0x0000004f,0x00000050,0x00000051,0x0000003b, + 0x0003003e,0x0000004d,0x00000052,0x000100fd,0x00010038,0x00050036,0x00000006,0x0000000b, + 0x00000000,0x00000009,0x00030037,0x00000008,0x0000000a,0x000200f8,0x0000000c,0x0004003d, + 0x00000007,0x0000000e,0x0000000a,0x00050094,0x00000006,0x00000012,0x0000000e,0x00000011, + 0x0006000c,0x00000006,0x00000013,0x00000001,0x0000000a,0x00000012,0x00050085,0x00000006, + 0x00000014,0x0000000d,0x00000013,0x0006000c,0x00000006,0x00000015,0x00000001,0x0000000a, + 0x00000014,0x000200fe,0x00000015,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/fsr_easu_frag.h b/app/src/main/cpp/waylandcomp/shaders/fsr_easu_frag.h new file mode 100644 index 000000000..eb6d36af4 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/fsr_easu_frag.h @@ -0,0 +1,907 @@ + // 1116.2.0 + #pragma once +const uint32_t fsr_easu_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000046e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000447,0x00000468,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00050005,0x0000000a,0x70635241,0x66283146,0x00003b31,0x00030005,0x00000009, + 0x00000078,0x00060005,0x0000000d,0x78725041,0x63526f4c,0x28314670,0x003b3166,0x00030005, + 0x0000000c,0x00000078,0x00060005,0x00000010,0x78725041,0x73526f4c,0x28314671,0x003b3166, + 0x00030005,0x0000000f,0x00000078,0x00050005,0x00000013,0x74615341,0x66283146,0x00003b31, + 0x00030005,0x00000012,0x00000078,0x00080005,0x0000001b,0x6e694d41,0x28334633,0x3b336676, + 0x3b336676,0x3b336676,0x00000000,0x00030005,0x00000018,0x00000061,0x00030005,0x00000019, + 0x00000062,0x00030005,0x0000001a,0x00000063,0x00080005,0x00000020,0x78614d41,0x28334633, + 0x3b336676,0x3b336676,0x3b336676,0x00000000,0x00030005,0x0000001d,0x00000061,0x00030005, + 0x0000001e,0x00000062,0x00030005,0x0000001f,0x00000063,0x00060005,0x00000027,0x45727346, + 0x52757361,0x66762846,0x00003b32,0x00030005,0x00000026,0x00000070,0x00060005,0x0000002a, + 0x45727346,0x47757361,0x66762846,0x00003b32,0x00030005,0x00000029,0x00000070,0x00060005, + 0x0000002d,0x45727346,0x42757361,0x66762846,0x00003b32,0x00030005,0x0000002c,0x00000070, + 0x000d0005,0x00000038,0x45727346,0x54757361,0x28467061,0x3b336676,0x763b3166,0x763b3266, + 0x763b3266,0x663b3266,0x31663b31,0x3366763b,0x0000003b,0x00030005,0x00000030,0x00004361, + 0x00030005,0x00000031,0x00005761,0x00030005,0x00000032,0x0066666f,0x00030005,0x00000033, + 0x00726964,0x00030005,0x00000034,0x006e656c,0x00030005,0x00000035,0x00626f6c,0x00030005, + 0x00000036,0x00706c63,0x00030005,0x00000037,0x00000063,0x000f0005,0x00000049,0x45727346, + 0x53757361,0x28467465,0x3b326676,0x763b3166,0x623b3266,0x31623b31,0x3b31623b,0x663b3162, + 0x31663b31,0x3b31663b,0x663b3166,0x00003b31,0x00030005,0x0000003d,0x00726964,0x00030005, + 0x0000003e,0x006e656c,0x00030005,0x0000003f,0x00007070,0x00030005,0x00000040,0x00536962, + 0x00030005,0x00000041,0x00546962,0x00030005,0x00000042,0x00556962,0x00030005,0x00000043, + 0x00566962,0x00030005,0x00000044,0x0000416c,0x00030005,0x00000045,0x0000426c,0x00030005, + 0x00000046,0x0000436c,0x00030005,0x00000047,0x0000446c,0x00030005,0x00000048,0x0000456c, + 0x000b0005,0x00000057,0x45727346,0x46757361,0x33667628,0x3275763b,0x3475763b,0x3475763b, + 0x3475763b,0x3475763b,0x0000003b,0x00030005,0x00000051,0x00786970,0x00030005,0x00000052, + 0x00007069,0x00040005,0x00000053,0x306e6f63,0x00000000,0x00040005,0x00000054,0x316e6f63, + 0x00000000,0x00040005,0x00000055,0x326e6f63,0x00000000,0x00040005,0x00000056,0x336e6f63, + 0x00000000,0x00060005,0x0000007c,0x75706e49,0x78655474,0x65727574,0x00000000,0x00030005, + 0x00000090,0x00000076,0x00030005,0x000000af,0x00003264,0x00030005,0x000000be,0x00004277, + 0x00030005,0x000000c4,0x00004177,0x00030005,0x000000d4,0x00000077,0x00030005,0x000000e0, + 0x00000077,0x00030005,0x00000105,0x00006364,0x00030005,0x00000109,0x00006263,0x00040005, + 0x0000010d,0x586e656c,0x00000000,0x00040005,0x00000113,0x61726170,0x0000006d,0x00040005, + 0x00000116,0x58726964,0x00000000,0x00040005,0x00000125,0x61726170,0x0000006d,0x00030005, + 0x0000012f,0x00006365,0x00030005,0x00000133,0x00006163,0x00040005,0x00000137,0x596e656c, + 0x00000000,0x00040005,0x0000013d,0x61726170,0x0000006d,0x00040005,0x00000140,0x59726964, + 0x00000000,0x00040005,0x0000014f,0x61726170,0x0000006d,0x00030005,0x00000159,0x00007070, + 0x00030005,0x00000164,0x00007066,0x00030005,0x0000016a,0x00003070,0x00030005,0x00000174, + 0x00003170,0x00030005,0x0000017a,0x00003270,0x00030005,0x00000180,0x00003370,0x00040005, + 0x00000187,0x7a7a6362,0x00000052,0x00040005,0x00000188,0x61726170,0x0000006d,0x00040005, + 0x0000018b,0x7a7a6362,0x00000047,0x00040005,0x0000018c,0x61726170,0x0000006d,0x00040005, + 0x0000018f,0x7a7a6362,0x00000042,0x00040005,0x00000190,0x61726170,0x0000006d,0x00040005, + 0x00000193,0x65666a69,0x00000052,0x00040005,0x00000194,0x61726170,0x0000006d,0x00040005, + 0x00000197,0x65666a69,0x00000047,0x00040005,0x00000198,0x61726170,0x0000006d,0x00040005, + 0x0000019b,0x65666a69,0x00000042,0x00040005,0x0000019c,0x61726170,0x0000006d,0x00040005, + 0x0000019f,0x67686c6b,0x00000052,0x00040005,0x000001a0,0x61726170,0x0000006d,0x00040005, + 0x000001a3,0x67686c6b,0x00000047,0x00040005,0x000001a4,0x61726170,0x0000006d,0x00040005, + 0x000001a7,0x67686c6b,0x00000042,0x00040005,0x000001a8,0x61726170,0x0000006d,0x00040005, + 0x000001ab,0x6e6f7a7a,0x00000052,0x00040005,0x000001ac,0x61726170,0x0000006d,0x00040005, + 0x000001af,0x6e6f7a7a,0x00000047,0x00040005,0x000001b0,0x61726170,0x0000006d,0x00040005, + 0x000001b3,0x6e6f7a7a,0x00000042,0x00040005,0x000001b4,0x61726170,0x0000006d,0x00040005, + 0x000001b7,0x7a7a6362,0x0000004c,0x00040005,0x000001c1,0x65666a69,0x0000004c,0x00040005, + 0x000001c9,0x67686c6b,0x0000004c,0x00040005,0x000001d1,0x6e6f7a7a,0x0000004c,0x00030005, + 0x000001d9,0x00004c62,0x00030005,0x000001dc,0x00004c63,0x00030005,0x000001df,0x00004c69, + 0x00030005,0x000001e2,0x00004c6a,0x00030005,0x000001e5,0x00004c66,0x00030005,0x000001e9, + 0x00004c65,0x00030005,0x000001ed,0x00004c6b,0x00030005,0x000001f0,0x00004c6c,0x00030005, + 0x000001f3,0x00004c68,0x00030005,0x000001f6,0x00004c67,0x00030005,0x000001f9,0x00004c6f, + 0x00030005,0x000001fc,0x00004c6e,0x00030005,0x000001ff,0x00726964,0x00030005,0x00000201, + 0x006e656c,0x00040005,0x00000204,0x61726170,0x0000006d,0x00040005,0x00000206,0x61726170, + 0x0000006d,0x00040005,0x00000208,0x61726170,0x0000006d,0x00040005,0x0000020a,0x61726170, + 0x0000006d,0x00040005,0x0000020b,0x61726170,0x0000006d,0x00040005,0x0000020c,0x61726170, + 0x0000006d,0x00040005,0x0000020d,0x61726170,0x0000006d,0x00040005,0x0000020e,0x61726170, + 0x0000006d,0x00040005,0x00000210,0x61726170,0x0000006d,0x00040005,0x00000212,0x61726170, + 0x0000006d,0x00040005,0x00000214,0x61726170,0x0000006d,0x00040005,0x00000216,0x61726170, + 0x0000006d,0x00040005,0x0000021b,0x61726170,0x0000006d,0x00040005,0x0000021d,0x61726170, + 0x0000006d,0x00040005,0x0000021f,0x61726170,0x0000006d,0x00040005,0x00000221,0x61726170, + 0x0000006d,0x00040005,0x00000222,0x61726170,0x0000006d,0x00040005,0x00000223,0x61726170, + 0x0000006d,0x00040005,0x00000224,0x61726170,0x0000006d,0x00040005,0x00000225,0x61726170, + 0x0000006d,0x00040005,0x00000227,0x61726170,0x0000006d,0x00040005,0x00000229,0x61726170, + 0x0000006d,0x00040005,0x0000022b,0x61726170,0x0000006d,0x00040005,0x0000022d,0x61726170, + 0x0000006d,0x00040005,0x00000232,0x61726170,0x0000006d,0x00040005,0x00000234,0x61726170, + 0x0000006d,0x00040005,0x00000236,0x61726170,0x0000006d,0x00040005,0x00000238,0x61726170, + 0x0000006d,0x00040005,0x00000239,0x61726170,0x0000006d,0x00040005,0x0000023a,0x61726170, + 0x0000006d,0x00040005,0x0000023b,0x61726170,0x0000006d,0x00040005,0x0000023c,0x61726170, + 0x0000006d,0x00040005,0x0000023e,0x61726170,0x0000006d,0x00040005,0x00000240,0x61726170, + 0x0000006d,0x00040005,0x00000242,0x61726170,0x0000006d,0x00040005,0x00000244,0x61726170, + 0x0000006d,0x00040005,0x00000249,0x61726170,0x0000006d,0x00040005,0x0000024b,0x61726170, + 0x0000006d,0x00040005,0x0000024d,0x61726170,0x0000006d,0x00040005,0x0000024f,0x61726170, + 0x0000006d,0x00040005,0x00000250,0x61726170,0x0000006d,0x00040005,0x00000251,0x61726170, + 0x0000006d,0x00040005,0x00000252,0x61726170,0x0000006d,0x00040005,0x00000253,0x61726170, + 0x0000006d,0x00040005,0x00000255,0x61726170,0x0000006d,0x00040005,0x00000257,0x61726170, + 0x0000006d,0x00040005,0x00000259,0x61726170,0x0000006d,0x00040005,0x0000025b,0x61726170, + 0x0000006d,0x00040005,0x00000260,0x32726964,0x00000000,0x00040005,0x00000264,0x52726964, + 0x00000000,0x00030005,0x0000026a,0x006f727a,0x00040005,0x0000026e,0x61726170,0x0000006d, + 0x00040005,0x00000286,0x65727473,0x00686374,0x00040005,0x00000299,0x61726170,0x0000006d, + 0x00040005,0x0000029c,0x326e656c,0x00000000,0x00030005,0x000002a7,0x00626f6c,0x00030005, + 0x000002ac,0x00706c63,0x00040005,0x000002ad,0x61726170,0x0000006d,0x00040005,0x000002b0, + 0x346e696d,0x00000000,0x00040005,0x000002c6,0x61726170,0x0000006d,0x00040005,0x000002c7, + 0x61726170,0x0000006d,0x00040005,0x000002c8,0x61726170,0x0000006d,0x00040005,0x000002d2, + 0x3478616d,0x00000000,0x00040005,0x000002e8,0x61726170,0x0000006d,0x00040005,0x000002e9, + 0x61726170,0x0000006d,0x00040005,0x000002ea,0x61726170,0x0000006d,0x00030005,0x000002f4, + 0x00004361,0x00030005,0x000002f6,0x00005761,0x00040005,0x00000301,0x61726170,0x0000006d, + 0x00040005,0x00000303,0x61726170,0x0000006d,0x00040005,0x00000305,0x61726170,0x0000006d, + 0x00040005,0x00000306,0x61726170,0x0000006d,0x00040005,0x00000308,0x61726170,0x0000006d, + 0x00040005,0x0000030a,0x61726170,0x0000006d,0x00040005,0x0000030c,0x61726170,0x0000006d, + 0x00040005,0x0000030e,0x61726170,0x0000006d,0x00040005,0x0000031c,0x61726170,0x0000006d, + 0x00040005,0x0000031e,0x61726170,0x0000006d,0x00040005,0x00000320,0x61726170,0x0000006d, + 0x00040005,0x00000321,0x61726170,0x0000006d,0x00040005,0x00000323,0x61726170,0x0000006d, + 0x00040005,0x00000325,0x61726170,0x0000006d,0x00040005,0x00000327,0x61726170,0x0000006d, + 0x00040005,0x00000329,0x61726170,0x0000006d,0x00040005,0x00000337,0x61726170,0x0000006d, + 0x00040005,0x00000339,0x61726170,0x0000006d,0x00040005,0x0000033b,0x61726170,0x0000006d, + 0x00040005,0x0000033c,0x61726170,0x0000006d,0x00040005,0x0000033e,0x61726170,0x0000006d, + 0x00040005,0x00000340,0x61726170,0x0000006d,0x00040005,0x00000342,0x61726170,0x0000006d, + 0x00040005,0x00000344,0x61726170,0x0000006d,0x00040005,0x00000352,0x61726170,0x0000006d, + 0x00040005,0x00000354,0x61726170,0x0000006d,0x00040005,0x00000356,0x61726170,0x0000006d, + 0x00040005,0x00000357,0x61726170,0x0000006d,0x00040005,0x00000359,0x61726170,0x0000006d, + 0x00040005,0x0000035b,0x61726170,0x0000006d,0x00040005,0x0000035d,0x61726170,0x0000006d, + 0x00040005,0x0000035f,0x61726170,0x0000006d,0x00040005,0x0000036c,0x61726170,0x0000006d, + 0x00040005,0x0000036e,0x61726170,0x0000006d,0x00040005,0x00000370,0x61726170,0x0000006d, + 0x00040005,0x00000371,0x61726170,0x0000006d,0x00040005,0x00000373,0x61726170,0x0000006d, + 0x00040005,0x00000375,0x61726170,0x0000006d,0x00040005,0x00000377,0x61726170,0x0000006d, + 0x00040005,0x00000379,0x61726170,0x0000006d,0x00040005,0x00000387,0x61726170,0x0000006d, + 0x00040005,0x00000389,0x61726170,0x0000006d,0x00040005,0x0000038b,0x61726170,0x0000006d, + 0x00040005,0x0000038c,0x61726170,0x0000006d,0x00040005,0x0000038e,0x61726170,0x0000006d, + 0x00040005,0x00000390,0x61726170,0x0000006d,0x00040005,0x00000392,0x61726170,0x0000006d, + 0x00040005,0x00000394,0x61726170,0x0000006d,0x00040005,0x000003a2,0x61726170,0x0000006d, + 0x00040005,0x000003a4,0x61726170,0x0000006d,0x00040005,0x000003a6,0x61726170,0x0000006d, + 0x00040005,0x000003a7,0x61726170,0x0000006d,0x00040005,0x000003a9,0x61726170,0x0000006d, + 0x00040005,0x000003ab,0x61726170,0x0000006d,0x00040005,0x000003ad,0x61726170,0x0000006d, + 0x00040005,0x000003af,0x61726170,0x0000006d,0x00040005,0x000003be,0x61726170,0x0000006d, + 0x00040005,0x000003c0,0x61726170,0x0000006d,0x00040005,0x000003c2,0x61726170,0x0000006d, + 0x00040005,0x000003c3,0x61726170,0x0000006d,0x00040005,0x000003c5,0x61726170,0x0000006d, + 0x00040005,0x000003c7,0x61726170,0x0000006d,0x00040005,0x000003c9,0x61726170,0x0000006d, + 0x00040005,0x000003cb,0x61726170,0x0000006d,0x00040005,0x000003d9,0x61726170,0x0000006d, + 0x00040005,0x000003db,0x61726170,0x0000006d,0x00040005,0x000003dd,0x61726170,0x0000006d, + 0x00040005,0x000003de,0x61726170,0x0000006d,0x00040005,0x000003e0,0x61726170,0x0000006d, + 0x00040005,0x000003e2,0x61726170,0x0000006d,0x00040005,0x000003e4,0x61726170,0x0000006d, + 0x00040005,0x000003e6,0x61726170,0x0000006d,0x00040005,0x000003f4,0x61726170,0x0000006d, + 0x00040005,0x000003f6,0x61726170,0x0000006d,0x00040005,0x000003f8,0x61726170,0x0000006d, + 0x00040005,0x000003f9,0x61726170,0x0000006d,0x00040005,0x000003fb,0x61726170,0x0000006d, + 0x00040005,0x000003fd,0x61726170,0x0000006d,0x00040005,0x000003ff,0x61726170,0x0000006d, + 0x00040005,0x00000401,0x61726170,0x0000006d,0x00040005,0x0000040f,0x61726170,0x0000006d, + 0x00040005,0x00000411,0x61726170,0x0000006d,0x00040005,0x00000413,0x61726170,0x0000006d, + 0x00040005,0x00000414,0x61726170,0x0000006d,0x00040005,0x00000416,0x61726170,0x0000006d, + 0x00040005,0x00000418,0x61726170,0x0000006d,0x00040005,0x0000041a,0x61726170,0x0000006d, + 0x00040005,0x0000041c,0x61726170,0x0000006d,0x00040005,0x0000042a,0x61726170,0x0000006d, + 0x00040005,0x0000042c,0x61726170,0x0000006d,0x00040005,0x0000042e,0x61726170,0x0000006d, + 0x00040005,0x0000042f,0x61726170,0x0000006d,0x00040005,0x00000431,0x61726170,0x0000006d, + 0x00040005,0x00000433,0x61726170,0x0000006d,0x00040005,0x00000435,0x61726170,0x0000006d, + 0x00040005,0x00000437,0x61726170,0x0000006d,0x00040005,0x0000043e,0x61726170,0x0000006d, + 0x00030005,0x00000445,0x00007069,0x00060005,0x00000447,0x67617266,0x43786554,0x64726f6f, + 0x00000000,0x00030005,0x00000449,0x00004350,0x00040006,0x00000449,0x00000000,0x0063646e, + 0x00050006,0x00000449,0x00000001,0x306e6f63,0x00000000,0x00050006,0x00000449,0x00000002, + 0x316e6f63,0x00000000,0x00050006,0x00000449,0x00000003,0x326e6f63,0x00000000,0x00050006, + 0x00000449,0x00000004,0x336e6f63,0x00000000,0x00060006,0x00000449,0x00000005,0x7074756f, + 0x69537475,0x0000657a,0x00030005,0x0000044b,0x00006370,0x00030005,0x00000452,0x00000063, + 0x00040005,0x00000455,0x61726170,0x0000006d,0x00040005,0x00000456,0x61726170,0x0000006d, + 0x00040005,0x00000458,0x61726170,0x0000006d,0x00040005,0x0000045c,0x61726170,0x0000006d, + 0x00040005,0x0000045f,0x61726170,0x0000006d,0x00040005,0x00000462,0x61726170,0x0000006d, + 0x00050005,0x00000468,0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000007c,0x00000021, + 0x00000000,0x00040047,0x0000007c,0x00000022,0x00000000,0x00040047,0x00000447,0x0000001e, + 0x00000000,0x00030047,0x00000449,0x00000002,0x00050048,0x00000449,0x00000000,0x00000023, + 0x00000000,0x00050048,0x00000449,0x00000001,0x00000023,0x00000010,0x00050048,0x00000449, + 0x00000002,0x00000023,0x00000020,0x00050048,0x00000449,0x00000003,0x00000023,0x00000030, + 0x00050048,0x00000449,0x00000004,0x00000023,0x00000040,0x00050048,0x00000449,0x00000005, + 0x00000023,0x00000050,0x00040047,0x00000468,0x0000001e,0x00000000,0x00020013,0x00000002, + 0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040020,0x00000007, + 0x00000007,0x00000006,0x00040021,0x00000008,0x00000006,0x00000007,0x00040017,0x00000015, + 0x00000006,0x00000003,0x00040020,0x00000016,0x00000007,0x00000015,0x00060021,0x00000017, + 0x00000015,0x00000016,0x00000016,0x00000016,0x00040017,0x00000022,0x00000006,0x00000002, + 0x00040020,0x00000023,0x00000007,0x00000022,0x00040017,0x00000024,0x00000006,0x00000004, + 0x00040021,0x00000025,0x00000024,0x00000023,0x000b0021,0x0000002f,0x00000002,0x00000016, + 0x00000007,0x00000023,0x00000023,0x00000023,0x00000007,0x00000007,0x00000016,0x00020014, + 0x0000003a,0x00040020,0x0000003b,0x00000007,0x0000003a,0x000f0021,0x0000003c,0x00000002, + 0x00000023,0x00000007,0x00000023,0x0000003b,0x0000003b,0x0000003b,0x0000003b,0x00000007, + 0x00000007,0x00000007,0x00000007,0x00000007,0x00040015,0x0000004b,0x00000020,0x00000000, + 0x00040017,0x0000004c,0x0000004b,0x00000002,0x00040020,0x0000004d,0x00000007,0x0000004c, + 0x00040017,0x0000004e,0x0000004b,0x00000004,0x00040020,0x0000004f,0x00000007,0x0000004e, + 0x00090021,0x00000050,0x00000002,0x00000016,0x0000004d,0x0000004f,0x0000004f,0x0000004f, + 0x0000004f,0x0004002b,0x00000006,0x00000059,0x3f800000,0x0004002b,0x00000006,0x00000067, + 0x00000000,0x00090019,0x00000079,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, + 0x00000001,0x00000000,0x0003001b,0x0000007a,0x00000079,0x00040020,0x0000007b,0x00000000, + 0x0000007a,0x0004003b,0x0000007b,0x0000007c,0x00000000,0x00040015,0x0000007f,0x00000020, + 0x00000001,0x0004002b,0x0000007f,0x00000080,0x00000000,0x0004002b,0x0000007f,0x00000086, + 0x00000001,0x0004002b,0x0000007f,0x0000008c,0x00000002,0x0004002b,0x0000004b,0x00000091, + 0x00000000,0x0004002b,0x0000004b,0x00000097,0x00000001,0x0004002b,0x00000006,0x000000bf, + 0x3ecccccd,0x0004002b,0x00000006,0x000000c2,0xbf800000,0x0004002b,0x00000006,0x000000cf, + 0x3fc80000,0x0004002b,0x00000006,0x000000d2,0xbf100000,0x00040020,0x00000186,0x00000007, + 0x00000024,0x0004002b,0x00000006,0x000001b9,0x3f000000,0x0007002c,0x00000024,0x000001ba, + 0x000001b9,0x000001b9,0x000001b9,0x000001b9,0x0004002b,0x0000004b,0x000001e6,0x00000002, + 0x0004002b,0x0000004b,0x000001ea,0x00000003,0x0005002c,0x00000022,0x00000200,0x00000067, + 0x00000067,0x00030029,0x0000003a,0x00000202,0x0003002a,0x0000003a,0x00000203,0x0004002b, + 0x00000006,0x0000026c,0x38000000,0x0004002b,0x00000006,0x000002a2,0xbf000000,0x0004002b, + 0x00000006,0x000002a8,0xbe947ae1,0x0006002c,0x00000015,0x000002f5,0x00000067,0x00000067, + 0x00000067,0x0005002c,0x00000022,0x000002f7,0x00000067,0x000000c2,0x0005002c,0x00000022, + 0x00000312,0x00000059,0x000000c2,0x0005002c,0x00000022,0x0000032d,0x000000c2,0x00000059, + 0x0005002c,0x00000022,0x00000348,0x00000067,0x00000059,0x0005002c,0x00000022,0x0000037d, + 0x000000c2,0x00000067,0x0005002c,0x00000022,0x00000398,0x00000059,0x00000059,0x0004002b, + 0x00000006,0x000003b3,0x40000000,0x0005002c,0x00000022,0x000003b4,0x000003b3,0x00000059, + 0x0005002c,0x00000022,0x000003cf,0x000003b3,0x00000067,0x0005002c,0x00000022,0x000003ea, + 0x00000059,0x00000067,0x0005002c,0x00000022,0x00000405,0x00000059,0x000003b3,0x0005002c, + 0x00000022,0x00000420,0x00000067,0x000003b3,0x00040020,0x00000446,0x00000001,0x00000022, + 0x0004003b,0x00000446,0x00000447,0x00000001,0x0008001e,0x00000449,0x00000024,0x0000004e, + 0x0000004e,0x0000004e,0x0000004e,0x00000022,0x00040020,0x0000044a,0x00000009,0x00000449, + 0x0004003b,0x0000044a,0x0000044b,0x00000009,0x0004002b,0x0000007f,0x0000044c,0x00000005, + 0x00040020,0x0000044d,0x00000009,0x00000022,0x0004002b,0x0000007f,0x00000453,0x00000003, + 0x0004002b,0x0000007f,0x00000454,0x00000004,0x00040020,0x00000459,0x00000009,0x0000004e, + 0x00040020,0x00000467,0x00000003,0x00000024,0x0004003b,0x00000467,0x00000468,0x00000003, + 0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b, + 0x0000004d,0x00000445,0x00000007,0x0004003b,0x00000016,0x00000452,0x00000007,0x0004003b, + 0x00000016,0x00000455,0x00000007,0x0004003b,0x0000004d,0x00000456,0x00000007,0x0004003b, + 0x0000004f,0x00000458,0x00000007,0x0004003b,0x0000004f,0x0000045c,0x00000007,0x0004003b, + 0x0000004f,0x0000045f,0x00000007,0x0004003b,0x0000004f,0x00000462,0x00000007,0x0004003d, + 0x00000022,0x00000448,0x00000447,0x00050041,0x0000044d,0x0000044e,0x0000044b,0x0000044c, + 0x0004003d,0x00000022,0x0000044f,0x0000044e,0x00050085,0x00000022,0x00000450,0x00000448, + 0x0000044f,0x0004006d,0x0000004c,0x00000451,0x00000450,0x0003003e,0x00000445,0x00000451, + 0x0004003d,0x0000004c,0x00000457,0x00000445,0x0003003e,0x00000456,0x00000457,0x00050041, + 0x00000459,0x0000045a,0x0000044b,0x00000086,0x0004003d,0x0000004e,0x0000045b,0x0000045a, + 0x0003003e,0x00000458,0x0000045b,0x00050041,0x00000459,0x0000045d,0x0000044b,0x0000008c, + 0x0004003d,0x0000004e,0x0000045e,0x0000045d,0x0003003e,0x0000045c,0x0000045e,0x00050041, + 0x00000459,0x00000460,0x0000044b,0x00000453,0x0004003d,0x0000004e,0x00000461,0x00000460, + 0x0003003e,0x0000045f,0x00000461,0x00050041,0x00000459,0x00000463,0x0000044b,0x00000454, + 0x0004003d,0x0000004e,0x00000464,0x00000463,0x0003003e,0x00000462,0x00000464,0x000a0039, + 0x00000002,0x00000465,0x00000057,0x00000455,0x00000456,0x00000458,0x0000045c,0x0000045f, + 0x00000462,0x0004003d,0x00000015,0x00000466,0x00000455,0x0003003e,0x00000452,0x00000466, + 0x0004003d,0x00000015,0x00000469,0x00000452,0x00050051,0x00000006,0x0000046a,0x00000469, + 0x00000000,0x00050051,0x00000006,0x0000046b,0x00000469,0x00000001,0x00050051,0x00000006, + 0x0000046c,0x00000469,0x00000002,0x00070050,0x00000024,0x0000046d,0x0000046a,0x0000046b, + 0x0000046c,0x00000059,0x0003003e,0x00000468,0x0000046d,0x000100fd,0x00010038,0x00050036, + 0x00000006,0x0000000a,0x00000000,0x00000008,0x00030037,0x00000007,0x00000009,0x000200f8, + 0x0000000b,0x0004003d,0x00000006,0x0000005a,0x00000009,0x00050088,0x00000006,0x0000005b, + 0x00000059,0x0000005a,0x000200fe,0x0000005b,0x00010038,0x00050036,0x00000006,0x0000000d, + 0x00000000,0x00000008,0x00030037,0x00000007,0x0000000c,0x000200f8,0x0000000e,0x0004003d, + 0x00000006,0x0000005e,0x0000000c,0x00050088,0x00000006,0x0000005f,0x00000059,0x0000005e, + 0x000200fe,0x0000005f,0x00010038,0x00050036,0x00000006,0x00000010,0x00000000,0x00000008, + 0x00030037,0x00000007,0x0000000f,0x000200f8,0x00000011,0x0004003d,0x00000006,0x00000062, + 0x0000000f,0x0006000c,0x00000006,0x00000063,0x00000001,0x00000020,0x00000062,0x000200fe, + 0x00000063,0x00010038,0x00050036,0x00000006,0x00000013,0x00000000,0x00000008,0x00030037, + 0x00000007,0x00000012,0x000200f8,0x00000014,0x0004003d,0x00000006,0x00000066,0x00000012, + 0x0008000c,0x00000006,0x00000068,0x00000001,0x0000002b,0x00000066,0x00000067,0x00000059, + 0x000200fe,0x00000068,0x00010038,0x00050036,0x00000015,0x0000001b,0x00000000,0x00000017, + 0x00030037,0x00000016,0x00000018,0x00030037,0x00000016,0x00000019,0x00030037,0x00000016, + 0x0000001a,0x000200f8,0x0000001c,0x0004003d,0x00000015,0x0000006b,0x00000018,0x0004003d, + 0x00000015,0x0000006c,0x00000019,0x0004003d,0x00000015,0x0000006d,0x0000001a,0x0007000c, + 0x00000015,0x0000006e,0x00000001,0x00000025,0x0000006c,0x0000006d,0x0007000c,0x00000015, + 0x0000006f,0x00000001,0x00000025,0x0000006b,0x0000006e,0x000200fe,0x0000006f,0x00010038, + 0x00050036,0x00000015,0x00000020,0x00000000,0x00000017,0x00030037,0x00000016,0x0000001d, + 0x00030037,0x00000016,0x0000001e,0x00030037,0x00000016,0x0000001f,0x000200f8,0x00000021, + 0x0004003d,0x00000015,0x00000072,0x0000001d,0x0004003d,0x00000015,0x00000073,0x0000001e, + 0x0004003d,0x00000015,0x00000074,0x0000001f,0x0007000c,0x00000015,0x00000075,0x00000001, + 0x00000028,0x00000073,0x00000074,0x0007000c,0x00000015,0x00000076,0x00000001,0x00000028, + 0x00000072,0x00000075,0x000200fe,0x00000076,0x00010038,0x00050036,0x00000024,0x00000027, + 0x00000000,0x00000025,0x00030037,0x00000023,0x00000026,0x000200f8,0x00000028,0x0004003d, + 0x0000007a,0x0000007d,0x0000007c,0x0004003d,0x00000022,0x0000007e,0x00000026,0x00060060, + 0x00000024,0x00000081,0x0000007d,0x0000007e,0x00000080,0x000200fe,0x00000081,0x00010038, + 0x00050036,0x00000024,0x0000002a,0x00000000,0x00000025,0x00030037,0x00000023,0x00000029, + 0x000200f8,0x0000002b,0x0004003d,0x0000007a,0x00000084,0x0000007c,0x0004003d,0x00000022, + 0x00000085,0x00000029,0x00060060,0x00000024,0x00000087,0x00000084,0x00000085,0x00000086, + 0x000200fe,0x00000087,0x00010038,0x00050036,0x00000024,0x0000002d,0x00000000,0x00000025, + 0x00030037,0x00000023,0x0000002c,0x000200f8,0x0000002e,0x0004003d,0x0000007a,0x0000008a, + 0x0000007c,0x0004003d,0x00000022,0x0000008b,0x0000002c,0x00060060,0x00000024,0x0000008d, + 0x0000008a,0x0000008b,0x0000008c,0x000200fe,0x0000008d,0x00010038,0x00050036,0x00000002, + 0x00000038,0x00000000,0x0000002f,0x00030037,0x00000016,0x00000030,0x00030037,0x00000007, + 0x00000031,0x00030037,0x00000023,0x00000032,0x00030037,0x00000023,0x00000033,0x00030037, + 0x00000023,0x00000034,0x00030037,0x00000007,0x00000035,0x00030037,0x00000007,0x00000036, + 0x00030037,0x00000016,0x00000037,0x000200f8,0x00000039,0x0004003b,0x00000023,0x00000090, + 0x00000007,0x0004003b,0x00000007,0x000000af,0x00000007,0x0004003b,0x00000007,0x000000be, + 0x00000007,0x0004003b,0x00000007,0x000000c4,0x00000007,0x0004003b,0x00000007,0x000000d4, + 0x00000007,0x00050041,0x00000007,0x00000092,0x00000032,0x00000091,0x0004003d,0x00000006, + 0x00000093,0x00000092,0x00050041,0x00000007,0x00000094,0x00000033,0x00000091,0x0004003d, + 0x00000006,0x00000095,0x00000094,0x00050085,0x00000006,0x00000096,0x00000093,0x00000095, + 0x00050041,0x00000007,0x00000098,0x00000032,0x00000097,0x0004003d,0x00000006,0x00000099, + 0x00000098,0x00050041,0x00000007,0x0000009a,0x00000033,0x00000097,0x0004003d,0x00000006, + 0x0000009b,0x0000009a,0x00050085,0x00000006,0x0000009c,0x00000099,0x0000009b,0x00050081, + 0x00000006,0x0000009d,0x00000096,0x0000009c,0x00050041,0x00000007,0x0000009e,0x00000090, + 0x00000091,0x0003003e,0x0000009e,0x0000009d,0x00050041,0x00000007,0x0000009f,0x00000032, + 0x00000091,0x0004003d,0x00000006,0x000000a0,0x0000009f,0x00050041,0x00000007,0x000000a1, + 0x00000033,0x00000097,0x0004003d,0x00000006,0x000000a2,0x000000a1,0x0004007f,0x00000006, + 0x000000a3,0x000000a2,0x00050085,0x00000006,0x000000a4,0x000000a0,0x000000a3,0x00050041, + 0x00000007,0x000000a5,0x00000032,0x00000097,0x0004003d,0x00000006,0x000000a6,0x000000a5, + 0x00050041,0x00000007,0x000000a7,0x00000033,0x00000091,0x0004003d,0x00000006,0x000000a8, + 0x000000a7,0x00050085,0x00000006,0x000000a9,0x000000a6,0x000000a8,0x00050081,0x00000006, + 0x000000aa,0x000000a4,0x000000a9,0x00050041,0x00000007,0x000000ab,0x00000090,0x00000097, + 0x0003003e,0x000000ab,0x000000aa,0x0004003d,0x00000022,0x000000ac,0x00000034,0x0004003d, + 0x00000022,0x000000ad,0x00000090,0x00050085,0x00000022,0x000000ae,0x000000ad,0x000000ac, + 0x0003003e,0x00000090,0x000000ae,0x00050041,0x00000007,0x000000b0,0x00000090,0x00000091, + 0x0004003d,0x00000006,0x000000b1,0x000000b0,0x00050041,0x00000007,0x000000b2,0x00000090, + 0x00000091,0x0004003d,0x00000006,0x000000b3,0x000000b2,0x00050085,0x00000006,0x000000b4, + 0x000000b1,0x000000b3,0x00050041,0x00000007,0x000000b5,0x00000090,0x00000097,0x0004003d, + 0x00000006,0x000000b6,0x000000b5,0x00050041,0x00000007,0x000000b7,0x00000090,0x00000097, + 0x0004003d,0x00000006,0x000000b8,0x000000b7,0x00050085,0x00000006,0x000000b9,0x000000b6, + 0x000000b8,0x00050081,0x00000006,0x000000ba,0x000000b4,0x000000b9,0x0003003e,0x000000af, + 0x000000ba,0x0004003d,0x00000006,0x000000bb,0x000000af,0x0004003d,0x00000006,0x000000bc, + 0x00000036,0x0007000c,0x00000006,0x000000bd,0x00000001,0x00000025,0x000000bb,0x000000bc, + 0x0003003e,0x000000af,0x000000bd,0x0004003d,0x00000006,0x000000c0,0x000000af,0x00050085, + 0x00000006,0x000000c1,0x000000bf,0x000000c0,0x00050081,0x00000006,0x000000c3,0x000000c1, + 0x000000c2,0x0003003e,0x000000be,0x000000c3,0x0004003d,0x00000006,0x000000c5,0x00000035, + 0x0004003d,0x00000006,0x000000c6,0x000000af,0x00050085,0x00000006,0x000000c7,0x000000c5, + 0x000000c6,0x00050081,0x00000006,0x000000c8,0x000000c7,0x000000c2,0x0003003e,0x000000c4, + 0x000000c8,0x0004003d,0x00000006,0x000000c9,0x000000be,0x0004003d,0x00000006,0x000000ca, + 0x000000be,0x00050085,0x00000006,0x000000cb,0x000000ca,0x000000c9,0x0003003e,0x000000be, + 0x000000cb,0x0004003d,0x00000006,0x000000cc,0x000000c4,0x0004003d,0x00000006,0x000000cd, + 0x000000c4,0x00050085,0x00000006,0x000000ce,0x000000cd,0x000000cc,0x0003003e,0x000000c4, + 0x000000ce,0x0004003d,0x00000006,0x000000d0,0x000000be,0x00050085,0x00000006,0x000000d1, + 0x000000cf,0x000000d0,0x00050081,0x00000006,0x000000d3,0x000000d1,0x000000d2,0x0003003e, + 0x000000be,0x000000d3,0x0004003d,0x00000006,0x000000d5,0x000000be,0x0004003d,0x00000006, + 0x000000d6,0x000000c4,0x00050085,0x00000006,0x000000d7,0x000000d5,0x000000d6,0x0003003e, + 0x000000d4,0x000000d7,0x0004003d,0x00000015,0x000000d8,0x00000037,0x0004003d,0x00000006, + 0x000000d9,0x000000d4,0x0005008e,0x00000015,0x000000da,0x000000d8,0x000000d9,0x0004003d, + 0x00000015,0x000000db,0x00000030,0x00050081,0x00000015,0x000000dc,0x000000db,0x000000da, + 0x0003003e,0x00000030,0x000000dc,0x0004003d,0x00000006,0x000000dd,0x000000d4,0x0004003d, + 0x00000006,0x000000de,0x00000031,0x00050081,0x00000006,0x000000df,0x000000de,0x000000dd, + 0x0003003e,0x00000031,0x000000df,0x000100fd,0x00010038,0x00050036,0x00000002,0x00000049, + 0x00000000,0x0000003c,0x00030037,0x00000023,0x0000003d,0x00030037,0x00000007,0x0000003e, + 0x00030037,0x00000023,0x0000003f,0x00030037,0x0000003b,0x00000040,0x00030037,0x0000003b, + 0x00000041,0x00030037,0x0000003b,0x00000042,0x00030037,0x0000003b,0x00000043,0x00030037, + 0x00000007,0x00000044,0x00030037,0x00000007,0x00000045,0x00030037,0x00000007,0x00000046, + 0x00030037,0x00000007,0x00000047,0x00030037,0x00000007,0x00000048,0x000200f8,0x0000004a, + 0x0004003b,0x00000007,0x000000e0,0x00000007,0x0004003b,0x00000007,0x00000105,0x00000007, + 0x0004003b,0x00000007,0x00000109,0x00000007,0x0004003b,0x00000007,0x0000010d,0x00000007, + 0x0004003b,0x00000007,0x00000113,0x00000007,0x0004003b,0x00000007,0x00000116,0x00000007, + 0x0004003b,0x00000007,0x00000125,0x00000007,0x0004003b,0x00000007,0x0000012f,0x00000007, + 0x0004003b,0x00000007,0x00000133,0x00000007,0x0004003b,0x00000007,0x00000137,0x00000007, + 0x0004003b,0x00000007,0x0000013d,0x00000007,0x0004003b,0x00000007,0x00000140,0x00000007, + 0x0004003b,0x00000007,0x0000014f,0x00000007,0x0003003e,0x000000e0,0x00000067,0x0004003d, + 0x0000003a,0x000000e1,0x00000040,0x000300f7,0x000000e3,0x00000000,0x000400fa,0x000000e1, + 0x000000e2,0x000000e3,0x000200f8,0x000000e2,0x00050041,0x00000007,0x000000e4,0x0000003f, + 0x00000091,0x0004003d,0x00000006,0x000000e5,0x000000e4,0x00050083,0x00000006,0x000000e6, + 0x00000059,0x000000e5,0x00050041,0x00000007,0x000000e7,0x0000003f,0x00000097,0x0004003d, + 0x00000006,0x000000e8,0x000000e7,0x00050083,0x00000006,0x000000e9,0x00000059,0x000000e8, + 0x00050085,0x00000006,0x000000ea,0x000000e6,0x000000e9,0x0003003e,0x000000e0,0x000000ea, + 0x000200f9,0x000000e3,0x000200f8,0x000000e3,0x0004003d,0x0000003a,0x000000eb,0x00000041, + 0x000300f7,0x000000ed,0x00000000,0x000400fa,0x000000eb,0x000000ec,0x000000ed,0x000200f8, + 0x000000ec,0x00050041,0x00000007,0x000000ee,0x0000003f,0x00000091,0x0004003d,0x00000006, + 0x000000ef,0x000000ee,0x00050041,0x00000007,0x000000f0,0x0000003f,0x00000097,0x0004003d, + 0x00000006,0x000000f1,0x000000f0,0x00050083,0x00000006,0x000000f2,0x00000059,0x000000f1, + 0x00050085,0x00000006,0x000000f3,0x000000ef,0x000000f2,0x0003003e,0x000000e0,0x000000f3, + 0x000200f9,0x000000ed,0x000200f8,0x000000ed,0x0004003d,0x0000003a,0x000000f4,0x00000042, + 0x000300f7,0x000000f6,0x00000000,0x000400fa,0x000000f4,0x000000f5,0x000000f6,0x000200f8, + 0x000000f5,0x00050041,0x00000007,0x000000f7,0x0000003f,0x00000091,0x0004003d,0x00000006, + 0x000000f8,0x000000f7,0x00050083,0x00000006,0x000000f9,0x00000059,0x000000f8,0x00050041, + 0x00000007,0x000000fa,0x0000003f,0x00000097,0x0004003d,0x00000006,0x000000fb,0x000000fa, + 0x00050085,0x00000006,0x000000fc,0x000000f9,0x000000fb,0x0003003e,0x000000e0,0x000000fc, + 0x000200f9,0x000000f6,0x000200f8,0x000000f6,0x0004003d,0x0000003a,0x000000fd,0x00000043, + 0x000300f7,0x000000ff,0x00000000,0x000400fa,0x000000fd,0x000000fe,0x000000ff,0x000200f8, + 0x000000fe,0x00050041,0x00000007,0x00000100,0x0000003f,0x00000091,0x0004003d,0x00000006, + 0x00000101,0x00000100,0x00050041,0x00000007,0x00000102,0x0000003f,0x00000097,0x0004003d, + 0x00000006,0x00000103,0x00000102,0x00050085,0x00000006,0x00000104,0x00000101,0x00000103, + 0x0003003e,0x000000e0,0x00000104,0x000200f9,0x000000ff,0x000200f8,0x000000ff,0x0004003d, + 0x00000006,0x00000106,0x00000047,0x0004003d,0x00000006,0x00000107,0x00000046,0x00050083, + 0x00000006,0x00000108,0x00000106,0x00000107,0x0003003e,0x00000105,0x00000108,0x0004003d, + 0x00000006,0x0000010a,0x00000046,0x0004003d,0x00000006,0x0000010b,0x00000045,0x00050083, + 0x00000006,0x0000010c,0x0000010a,0x0000010b,0x0003003e,0x00000109,0x0000010c,0x0004003d, + 0x00000006,0x0000010e,0x00000105,0x0006000c,0x00000006,0x0000010f,0x00000001,0x00000004, + 0x0000010e,0x0004003d,0x00000006,0x00000110,0x00000109,0x0006000c,0x00000006,0x00000111, + 0x00000001,0x00000004,0x00000110,0x0007000c,0x00000006,0x00000112,0x00000001,0x00000028, + 0x0000010f,0x00000111,0x0003003e,0x0000010d,0x00000112,0x0004003d,0x00000006,0x00000114, + 0x0000010d,0x0003003e,0x00000113,0x00000114,0x00050039,0x00000006,0x00000115,0x0000000d, + 0x00000113,0x0003003e,0x0000010d,0x00000115,0x0004003d,0x00000006,0x00000117,0x00000047, + 0x0004003d,0x00000006,0x00000118,0x00000045,0x00050083,0x00000006,0x00000119,0x00000117, + 0x00000118,0x0003003e,0x00000116,0x00000119,0x0004003d,0x00000006,0x0000011a,0x00000116, + 0x0004003d,0x00000006,0x0000011b,0x000000e0,0x00050085,0x00000006,0x0000011c,0x0000011a, + 0x0000011b,0x00050041,0x00000007,0x0000011d,0x0000003d,0x00000091,0x0004003d,0x00000006, + 0x0000011e,0x0000011d,0x00050081,0x00000006,0x0000011f,0x0000011e,0x0000011c,0x00050041, + 0x00000007,0x00000120,0x0000003d,0x00000091,0x0003003e,0x00000120,0x0000011f,0x0004003d, + 0x00000006,0x00000121,0x00000116,0x0006000c,0x00000006,0x00000122,0x00000001,0x00000004, + 0x00000121,0x0004003d,0x00000006,0x00000123,0x0000010d,0x00050085,0x00000006,0x00000124, + 0x00000122,0x00000123,0x0003003e,0x00000125,0x00000124,0x00050039,0x00000006,0x00000126, + 0x00000013,0x00000125,0x0003003e,0x0000010d,0x00000126,0x0004003d,0x00000006,0x00000127, + 0x0000010d,0x0004003d,0x00000006,0x00000128,0x0000010d,0x00050085,0x00000006,0x00000129, + 0x00000128,0x00000127,0x0003003e,0x0000010d,0x00000129,0x0004003d,0x00000006,0x0000012a, + 0x0000010d,0x0004003d,0x00000006,0x0000012b,0x000000e0,0x00050085,0x00000006,0x0000012c, + 0x0000012a,0x0000012b,0x0004003d,0x00000006,0x0000012d,0x0000003e,0x00050081,0x00000006, + 0x0000012e,0x0000012d,0x0000012c,0x0003003e,0x0000003e,0x0000012e,0x0004003d,0x00000006, + 0x00000130,0x00000048,0x0004003d,0x00000006,0x00000131,0x00000046,0x00050083,0x00000006, + 0x00000132,0x00000130,0x00000131,0x0003003e,0x0000012f,0x00000132,0x0004003d,0x00000006, + 0x00000134,0x00000046,0x0004003d,0x00000006,0x00000135,0x00000044,0x00050083,0x00000006, + 0x00000136,0x00000134,0x00000135,0x0003003e,0x00000133,0x00000136,0x0004003d,0x00000006, + 0x00000138,0x0000012f,0x0006000c,0x00000006,0x00000139,0x00000001,0x00000004,0x00000138, + 0x0004003d,0x00000006,0x0000013a,0x00000133,0x0006000c,0x00000006,0x0000013b,0x00000001, + 0x00000004,0x0000013a,0x0007000c,0x00000006,0x0000013c,0x00000001,0x00000028,0x00000139, + 0x0000013b,0x0003003e,0x00000137,0x0000013c,0x0004003d,0x00000006,0x0000013e,0x00000137, + 0x0003003e,0x0000013d,0x0000013e,0x00050039,0x00000006,0x0000013f,0x0000000d,0x0000013d, + 0x0003003e,0x00000137,0x0000013f,0x0004003d,0x00000006,0x00000141,0x00000048,0x0004003d, + 0x00000006,0x00000142,0x00000044,0x00050083,0x00000006,0x00000143,0x00000141,0x00000142, + 0x0003003e,0x00000140,0x00000143,0x0004003d,0x00000006,0x00000144,0x00000140,0x0004003d, + 0x00000006,0x00000145,0x000000e0,0x00050085,0x00000006,0x00000146,0x00000144,0x00000145, + 0x00050041,0x00000007,0x00000147,0x0000003d,0x00000097,0x0004003d,0x00000006,0x00000148, + 0x00000147,0x00050081,0x00000006,0x00000149,0x00000148,0x00000146,0x00050041,0x00000007, + 0x0000014a,0x0000003d,0x00000097,0x0003003e,0x0000014a,0x00000149,0x0004003d,0x00000006, + 0x0000014b,0x00000140,0x0006000c,0x00000006,0x0000014c,0x00000001,0x00000004,0x0000014b, + 0x0004003d,0x00000006,0x0000014d,0x00000137,0x00050085,0x00000006,0x0000014e,0x0000014c, + 0x0000014d,0x0003003e,0x0000014f,0x0000014e,0x00050039,0x00000006,0x00000150,0x00000013, + 0x0000014f,0x0003003e,0x00000137,0x00000150,0x0004003d,0x00000006,0x00000151,0x00000137, + 0x0004003d,0x00000006,0x00000152,0x00000137,0x00050085,0x00000006,0x00000153,0x00000152, + 0x00000151,0x0003003e,0x00000137,0x00000153,0x0004003d,0x00000006,0x00000154,0x00000137, + 0x0004003d,0x00000006,0x00000155,0x000000e0,0x00050085,0x00000006,0x00000156,0x00000154, + 0x00000155,0x0004003d,0x00000006,0x00000157,0x0000003e,0x00050081,0x00000006,0x00000158, + 0x00000157,0x00000156,0x0003003e,0x0000003e,0x00000158,0x000100fd,0x00010038,0x00050036, + 0x00000002,0x00000057,0x00000000,0x00000050,0x00030037,0x00000016,0x00000051,0x00030037, + 0x0000004d,0x00000052,0x00030037,0x0000004f,0x00000053,0x00030037,0x0000004f,0x00000054, + 0x00030037,0x0000004f,0x00000055,0x00030037,0x0000004f,0x00000056,0x000200f8,0x00000058, + 0x0004003b,0x00000023,0x00000159,0x00000007,0x0004003b,0x00000023,0x00000164,0x00000007, + 0x0004003b,0x00000023,0x0000016a,0x00000007,0x0004003b,0x00000023,0x00000174,0x00000007, + 0x0004003b,0x00000023,0x0000017a,0x00000007,0x0004003b,0x00000023,0x00000180,0x00000007, + 0x0004003b,0x00000186,0x00000187,0x00000007,0x0004003b,0x00000023,0x00000188,0x00000007, + 0x0004003b,0x00000186,0x0000018b,0x00000007,0x0004003b,0x00000023,0x0000018c,0x00000007, + 0x0004003b,0x00000186,0x0000018f,0x00000007,0x0004003b,0x00000023,0x00000190,0x00000007, + 0x0004003b,0x00000186,0x00000193,0x00000007,0x0004003b,0x00000023,0x00000194,0x00000007, + 0x0004003b,0x00000186,0x00000197,0x00000007,0x0004003b,0x00000023,0x00000198,0x00000007, + 0x0004003b,0x00000186,0x0000019b,0x00000007,0x0004003b,0x00000023,0x0000019c,0x00000007, + 0x0004003b,0x00000186,0x0000019f,0x00000007,0x0004003b,0x00000023,0x000001a0,0x00000007, + 0x0004003b,0x00000186,0x000001a3,0x00000007,0x0004003b,0x00000023,0x000001a4,0x00000007, + 0x0004003b,0x00000186,0x000001a7,0x00000007,0x0004003b,0x00000023,0x000001a8,0x00000007, + 0x0004003b,0x00000186,0x000001ab,0x00000007,0x0004003b,0x00000023,0x000001ac,0x00000007, + 0x0004003b,0x00000186,0x000001af,0x00000007,0x0004003b,0x00000023,0x000001b0,0x00000007, + 0x0004003b,0x00000186,0x000001b3,0x00000007,0x0004003b,0x00000023,0x000001b4,0x00000007, + 0x0004003b,0x00000186,0x000001b7,0x00000007,0x0004003b,0x00000186,0x000001c1,0x00000007, + 0x0004003b,0x00000186,0x000001c9,0x00000007,0x0004003b,0x00000186,0x000001d1,0x00000007, + 0x0004003b,0x00000007,0x000001d9,0x00000007,0x0004003b,0x00000007,0x000001dc,0x00000007, + 0x0004003b,0x00000007,0x000001df,0x00000007,0x0004003b,0x00000007,0x000001e2,0x00000007, + 0x0004003b,0x00000007,0x000001e5,0x00000007,0x0004003b,0x00000007,0x000001e9,0x00000007, + 0x0004003b,0x00000007,0x000001ed,0x00000007,0x0004003b,0x00000007,0x000001f0,0x00000007, + 0x0004003b,0x00000007,0x000001f3,0x00000007,0x0004003b,0x00000007,0x000001f6,0x00000007, + 0x0004003b,0x00000007,0x000001f9,0x00000007,0x0004003b,0x00000007,0x000001fc,0x00000007, + 0x0004003b,0x00000023,0x000001ff,0x00000007,0x0004003b,0x00000007,0x00000201,0x00000007, + 0x0004003b,0x00000023,0x00000204,0x00000007,0x0004003b,0x00000007,0x00000206,0x00000007, + 0x0004003b,0x00000023,0x00000208,0x00000007,0x0004003b,0x0000003b,0x0000020a,0x00000007, + 0x0004003b,0x0000003b,0x0000020b,0x00000007,0x0004003b,0x0000003b,0x0000020c,0x00000007, + 0x0004003b,0x0000003b,0x0000020d,0x00000007,0x0004003b,0x00000007,0x0000020e,0x00000007, + 0x0004003b,0x00000007,0x00000210,0x00000007,0x0004003b,0x00000007,0x00000212,0x00000007, + 0x0004003b,0x00000007,0x00000214,0x00000007,0x0004003b,0x00000007,0x00000216,0x00000007, + 0x0004003b,0x00000023,0x0000021b,0x00000007,0x0004003b,0x00000007,0x0000021d,0x00000007, + 0x0004003b,0x00000023,0x0000021f,0x00000007,0x0004003b,0x0000003b,0x00000221,0x00000007, + 0x0004003b,0x0000003b,0x00000222,0x00000007,0x0004003b,0x0000003b,0x00000223,0x00000007, + 0x0004003b,0x0000003b,0x00000224,0x00000007,0x0004003b,0x00000007,0x00000225,0x00000007, + 0x0004003b,0x00000007,0x00000227,0x00000007,0x0004003b,0x00000007,0x00000229,0x00000007, + 0x0004003b,0x00000007,0x0000022b,0x00000007,0x0004003b,0x00000007,0x0000022d,0x00000007, + 0x0004003b,0x00000023,0x00000232,0x00000007,0x0004003b,0x00000007,0x00000234,0x00000007, + 0x0004003b,0x00000023,0x00000236,0x00000007,0x0004003b,0x0000003b,0x00000238,0x00000007, + 0x0004003b,0x0000003b,0x00000239,0x00000007,0x0004003b,0x0000003b,0x0000023a,0x00000007, + 0x0004003b,0x0000003b,0x0000023b,0x00000007,0x0004003b,0x00000007,0x0000023c,0x00000007, + 0x0004003b,0x00000007,0x0000023e,0x00000007,0x0004003b,0x00000007,0x00000240,0x00000007, + 0x0004003b,0x00000007,0x00000242,0x00000007,0x0004003b,0x00000007,0x00000244,0x00000007, + 0x0004003b,0x00000023,0x00000249,0x00000007,0x0004003b,0x00000007,0x0000024b,0x00000007, + 0x0004003b,0x00000023,0x0000024d,0x00000007,0x0004003b,0x0000003b,0x0000024f,0x00000007, + 0x0004003b,0x0000003b,0x00000250,0x00000007,0x0004003b,0x0000003b,0x00000251,0x00000007, + 0x0004003b,0x0000003b,0x00000252,0x00000007,0x0004003b,0x00000007,0x00000253,0x00000007, + 0x0004003b,0x00000007,0x00000255,0x00000007,0x0004003b,0x00000007,0x00000257,0x00000007, + 0x0004003b,0x00000007,0x00000259,0x00000007,0x0004003b,0x00000007,0x0000025b,0x00000007, + 0x0004003b,0x00000023,0x00000260,0x00000007,0x0004003b,0x00000007,0x00000264,0x00000007, + 0x0004003b,0x0000003b,0x0000026a,0x00000007,0x0004003b,0x00000007,0x0000026e,0x00000007, + 0x0004003b,0x00000007,0x00000275,0x00000007,0x0004003b,0x00000007,0x00000286,0x00000007, + 0x0004003b,0x00000007,0x00000299,0x00000007,0x0004003b,0x00000023,0x0000029c,0x00000007, + 0x0004003b,0x00000007,0x000002a7,0x00000007,0x0004003b,0x00000007,0x000002ac,0x00000007, + 0x0004003b,0x00000007,0x000002ad,0x00000007,0x0004003b,0x00000016,0x000002b0,0x00000007, + 0x0004003b,0x00000016,0x000002c6,0x00000007,0x0004003b,0x00000016,0x000002c7,0x00000007, + 0x0004003b,0x00000016,0x000002c8,0x00000007,0x0004003b,0x00000016,0x000002d2,0x00000007, + 0x0004003b,0x00000016,0x000002e8,0x00000007,0x0004003b,0x00000016,0x000002e9,0x00000007, + 0x0004003b,0x00000016,0x000002ea,0x00000007,0x0004003b,0x00000016,0x000002f4,0x00000007, + 0x0004003b,0x00000007,0x000002f6,0x00000007,0x0004003b,0x00000016,0x00000301,0x00000007, + 0x0004003b,0x00000007,0x00000303,0x00000007,0x0004003b,0x00000023,0x00000305,0x00000007, + 0x0004003b,0x00000023,0x00000306,0x00000007,0x0004003b,0x00000023,0x00000308,0x00000007, + 0x0004003b,0x00000007,0x0000030a,0x00000007,0x0004003b,0x00000007,0x0000030c,0x00000007, + 0x0004003b,0x00000016,0x0000030e,0x00000007,0x0004003b,0x00000016,0x0000031c,0x00000007, + 0x0004003b,0x00000007,0x0000031e,0x00000007,0x0004003b,0x00000023,0x00000320,0x00000007, + 0x0004003b,0x00000023,0x00000321,0x00000007,0x0004003b,0x00000023,0x00000323,0x00000007, + 0x0004003b,0x00000007,0x00000325,0x00000007,0x0004003b,0x00000007,0x00000327,0x00000007, + 0x0004003b,0x00000016,0x00000329,0x00000007,0x0004003b,0x00000016,0x00000337,0x00000007, + 0x0004003b,0x00000007,0x00000339,0x00000007,0x0004003b,0x00000023,0x0000033b,0x00000007, + 0x0004003b,0x00000023,0x0000033c,0x00000007,0x0004003b,0x00000023,0x0000033e,0x00000007, + 0x0004003b,0x00000007,0x00000340,0x00000007,0x0004003b,0x00000007,0x00000342,0x00000007, + 0x0004003b,0x00000016,0x00000344,0x00000007,0x0004003b,0x00000016,0x00000352,0x00000007, + 0x0004003b,0x00000007,0x00000354,0x00000007,0x0004003b,0x00000023,0x00000356,0x00000007, + 0x0004003b,0x00000023,0x00000357,0x00000007,0x0004003b,0x00000023,0x00000359,0x00000007, + 0x0004003b,0x00000007,0x0000035b,0x00000007,0x0004003b,0x00000007,0x0000035d,0x00000007, + 0x0004003b,0x00000016,0x0000035f,0x00000007,0x0004003b,0x00000016,0x0000036c,0x00000007, + 0x0004003b,0x00000007,0x0000036e,0x00000007,0x0004003b,0x00000023,0x00000370,0x00000007, + 0x0004003b,0x00000023,0x00000371,0x00000007,0x0004003b,0x00000023,0x00000373,0x00000007, + 0x0004003b,0x00000007,0x00000375,0x00000007,0x0004003b,0x00000007,0x00000377,0x00000007, + 0x0004003b,0x00000016,0x00000379,0x00000007,0x0004003b,0x00000016,0x00000387,0x00000007, + 0x0004003b,0x00000007,0x00000389,0x00000007,0x0004003b,0x00000023,0x0000038b,0x00000007, + 0x0004003b,0x00000023,0x0000038c,0x00000007,0x0004003b,0x00000023,0x0000038e,0x00000007, + 0x0004003b,0x00000007,0x00000390,0x00000007,0x0004003b,0x00000007,0x00000392,0x00000007, + 0x0004003b,0x00000016,0x00000394,0x00000007,0x0004003b,0x00000016,0x000003a2,0x00000007, + 0x0004003b,0x00000007,0x000003a4,0x00000007,0x0004003b,0x00000023,0x000003a6,0x00000007, + 0x0004003b,0x00000023,0x000003a7,0x00000007,0x0004003b,0x00000023,0x000003a9,0x00000007, + 0x0004003b,0x00000007,0x000003ab,0x00000007,0x0004003b,0x00000007,0x000003ad,0x00000007, + 0x0004003b,0x00000016,0x000003af,0x00000007,0x0004003b,0x00000016,0x000003be,0x00000007, + 0x0004003b,0x00000007,0x000003c0,0x00000007,0x0004003b,0x00000023,0x000003c2,0x00000007, + 0x0004003b,0x00000023,0x000003c3,0x00000007,0x0004003b,0x00000023,0x000003c5,0x00000007, + 0x0004003b,0x00000007,0x000003c7,0x00000007,0x0004003b,0x00000007,0x000003c9,0x00000007, + 0x0004003b,0x00000016,0x000003cb,0x00000007,0x0004003b,0x00000016,0x000003d9,0x00000007, + 0x0004003b,0x00000007,0x000003db,0x00000007,0x0004003b,0x00000023,0x000003dd,0x00000007, + 0x0004003b,0x00000023,0x000003de,0x00000007,0x0004003b,0x00000023,0x000003e0,0x00000007, + 0x0004003b,0x00000007,0x000003e2,0x00000007,0x0004003b,0x00000007,0x000003e4,0x00000007, + 0x0004003b,0x00000016,0x000003e6,0x00000007,0x0004003b,0x00000016,0x000003f4,0x00000007, + 0x0004003b,0x00000007,0x000003f6,0x00000007,0x0004003b,0x00000023,0x000003f8,0x00000007, + 0x0004003b,0x00000023,0x000003f9,0x00000007,0x0004003b,0x00000023,0x000003fb,0x00000007, + 0x0004003b,0x00000007,0x000003fd,0x00000007,0x0004003b,0x00000007,0x000003ff,0x00000007, + 0x0004003b,0x00000016,0x00000401,0x00000007,0x0004003b,0x00000016,0x0000040f,0x00000007, + 0x0004003b,0x00000007,0x00000411,0x00000007,0x0004003b,0x00000023,0x00000413,0x00000007, + 0x0004003b,0x00000023,0x00000414,0x00000007,0x0004003b,0x00000023,0x00000416,0x00000007, + 0x0004003b,0x00000007,0x00000418,0x00000007,0x0004003b,0x00000007,0x0000041a,0x00000007, + 0x0004003b,0x00000016,0x0000041c,0x00000007,0x0004003b,0x00000016,0x0000042a,0x00000007, + 0x0004003b,0x00000007,0x0000042c,0x00000007,0x0004003b,0x00000023,0x0000042e,0x00000007, + 0x0004003b,0x00000023,0x0000042f,0x00000007,0x0004003b,0x00000023,0x00000431,0x00000007, + 0x0004003b,0x00000007,0x00000433,0x00000007,0x0004003b,0x00000007,0x00000435,0x00000007, + 0x0004003b,0x00000016,0x00000437,0x00000007,0x0004003b,0x00000007,0x0000043e,0x00000007, + 0x0004003d,0x0000004c,0x0000015a,0x00000052,0x00040070,0x00000022,0x0000015b,0x0000015a, + 0x0004003d,0x0000004e,0x0000015c,0x00000053,0x0007004f,0x0000004c,0x0000015d,0x0000015c, + 0x0000015c,0x00000000,0x00000001,0x0004007c,0x00000022,0x0000015e,0x0000015d,0x00050085, + 0x00000022,0x0000015f,0x0000015b,0x0000015e,0x0004003d,0x0000004e,0x00000160,0x00000053, + 0x0007004f,0x0000004c,0x00000161,0x00000160,0x00000160,0x00000002,0x00000003,0x0004007c, + 0x00000022,0x00000162,0x00000161,0x00050081,0x00000022,0x00000163,0x0000015f,0x00000162, + 0x0003003e,0x00000159,0x00000163,0x0004003d,0x00000022,0x00000165,0x00000159,0x0006000c, + 0x00000022,0x00000166,0x00000001,0x00000008,0x00000165,0x0003003e,0x00000164,0x00000166, + 0x0004003d,0x00000022,0x00000167,0x00000164,0x0004003d,0x00000022,0x00000168,0x00000159, + 0x00050083,0x00000022,0x00000169,0x00000168,0x00000167,0x0003003e,0x00000159,0x00000169, + 0x0004003d,0x00000022,0x0000016b,0x00000164,0x0004003d,0x0000004e,0x0000016c,0x00000054, + 0x0007004f,0x0000004c,0x0000016d,0x0000016c,0x0000016c,0x00000000,0x00000001,0x0004007c, + 0x00000022,0x0000016e,0x0000016d,0x00050085,0x00000022,0x0000016f,0x0000016b,0x0000016e, + 0x0004003d,0x0000004e,0x00000170,0x00000054,0x0007004f,0x0000004c,0x00000171,0x00000170, + 0x00000170,0x00000002,0x00000003,0x0004007c,0x00000022,0x00000172,0x00000171,0x00050081, + 0x00000022,0x00000173,0x0000016f,0x00000172,0x0003003e,0x0000016a,0x00000173,0x0004003d, + 0x00000022,0x00000175,0x0000016a,0x0004003d,0x0000004e,0x00000176,0x00000055,0x0007004f, + 0x0000004c,0x00000177,0x00000176,0x00000176,0x00000000,0x00000001,0x0004007c,0x00000022, + 0x00000178,0x00000177,0x00050081,0x00000022,0x00000179,0x00000175,0x00000178,0x0003003e, + 0x00000174,0x00000179,0x0004003d,0x00000022,0x0000017b,0x0000016a,0x0004003d,0x0000004e, + 0x0000017c,0x00000055,0x0007004f,0x0000004c,0x0000017d,0x0000017c,0x0000017c,0x00000002, + 0x00000003,0x0004007c,0x00000022,0x0000017e,0x0000017d,0x00050081,0x00000022,0x0000017f, + 0x0000017b,0x0000017e,0x0003003e,0x0000017a,0x0000017f,0x0004003d,0x00000022,0x00000181, + 0x0000016a,0x0004003d,0x0000004e,0x00000182,0x00000056,0x0007004f,0x0000004c,0x00000183, + 0x00000182,0x00000182,0x00000000,0x00000001,0x0004007c,0x00000022,0x00000184,0x00000183, + 0x00050081,0x00000022,0x00000185,0x00000181,0x00000184,0x0003003e,0x00000180,0x00000185, + 0x0004003d,0x00000022,0x00000189,0x0000016a,0x0003003e,0x00000188,0x00000189,0x00050039, + 0x00000024,0x0000018a,0x00000027,0x00000188,0x0003003e,0x00000187,0x0000018a,0x0004003d, + 0x00000022,0x0000018d,0x0000016a,0x0003003e,0x0000018c,0x0000018d,0x00050039,0x00000024, + 0x0000018e,0x0000002a,0x0000018c,0x0003003e,0x0000018b,0x0000018e,0x0004003d,0x00000022, + 0x00000191,0x0000016a,0x0003003e,0x00000190,0x00000191,0x00050039,0x00000024,0x00000192, + 0x0000002d,0x00000190,0x0003003e,0x0000018f,0x00000192,0x0004003d,0x00000022,0x00000195, + 0x00000174,0x0003003e,0x00000194,0x00000195,0x00050039,0x00000024,0x00000196,0x00000027, + 0x00000194,0x0003003e,0x00000193,0x00000196,0x0004003d,0x00000022,0x00000199,0x00000174, + 0x0003003e,0x00000198,0x00000199,0x00050039,0x00000024,0x0000019a,0x0000002a,0x00000198, + 0x0003003e,0x00000197,0x0000019a,0x0004003d,0x00000022,0x0000019d,0x00000174,0x0003003e, + 0x0000019c,0x0000019d,0x00050039,0x00000024,0x0000019e,0x0000002d,0x0000019c,0x0003003e, + 0x0000019b,0x0000019e,0x0004003d,0x00000022,0x000001a1,0x0000017a,0x0003003e,0x000001a0, + 0x000001a1,0x00050039,0x00000024,0x000001a2,0x00000027,0x000001a0,0x0003003e,0x0000019f, + 0x000001a2,0x0004003d,0x00000022,0x000001a5,0x0000017a,0x0003003e,0x000001a4,0x000001a5, + 0x00050039,0x00000024,0x000001a6,0x0000002a,0x000001a4,0x0003003e,0x000001a3,0x000001a6, + 0x0004003d,0x00000022,0x000001a9,0x0000017a,0x0003003e,0x000001a8,0x000001a9,0x00050039, + 0x00000024,0x000001aa,0x0000002d,0x000001a8,0x0003003e,0x000001a7,0x000001aa,0x0004003d, + 0x00000022,0x000001ad,0x00000180,0x0003003e,0x000001ac,0x000001ad,0x00050039,0x00000024, + 0x000001ae,0x00000027,0x000001ac,0x0003003e,0x000001ab,0x000001ae,0x0004003d,0x00000022, + 0x000001b1,0x00000180,0x0003003e,0x000001b0,0x000001b1,0x00050039,0x00000024,0x000001b2, + 0x0000002a,0x000001b0,0x0003003e,0x000001af,0x000001b2,0x0004003d,0x00000022,0x000001b5, + 0x00000180,0x0003003e,0x000001b4,0x000001b5,0x00050039,0x00000024,0x000001b6,0x0000002d, + 0x000001b4,0x0003003e,0x000001b3,0x000001b6,0x0004003d,0x00000024,0x000001b8,0x0000018f, + 0x00050085,0x00000024,0x000001bb,0x000001b8,0x000001ba,0x0004003d,0x00000024,0x000001bc, + 0x00000187,0x00050085,0x00000024,0x000001bd,0x000001bc,0x000001ba,0x0004003d,0x00000024, + 0x000001be,0x0000018b,0x00050081,0x00000024,0x000001bf,0x000001bd,0x000001be,0x00050081, + 0x00000024,0x000001c0,0x000001bb,0x000001bf,0x0003003e,0x000001b7,0x000001c0,0x0004003d, + 0x00000024,0x000001c2,0x0000019b,0x00050085,0x00000024,0x000001c3,0x000001c2,0x000001ba, + 0x0004003d,0x00000024,0x000001c4,0x00000193,0x00050085,0x00000024,0x000001c5,0x000001c4, + 0x000001ba,0x0004003d,0x00000024,0x000001c6,0x00000197,0x00050081,0x00000024,0x000001c7, + 0x000001c5,0x000001c6,0x00050081,0x00000024,0x000001c8,0x000001c3,0x000001c7,0x0003003e, + 0x000001c1,0x000001c8,0x0004003d,0x00000024,0x000001ca,0x000001a7,0x00050085,0x00000024, + 0x000001cb,0x000001ca,0x000001ba,0x0004003d,0x00000024,0x000001cc,0x0000019f,0x00050085, + 0x00000024,0x000001cd,0x000001cc,0x000001ba,0x0004003d,0x00000024,0x000001ce,0x000001a3, + 0x00050081,0x00000024,0x000001cf,0x000001cd,0x000001ce,0x00050081,0x00000024,0x000001d0, + 0x000001cb,0x000001cf,0x0003003e,0x000001c9,0x000001d0,0x0004003d,0x00000024,0x000001d2, + 0x000001b3,0x00050085,0x00000024,0x000001d3,0x000001d2,0x000001ba,0x0004003d,0x00000024, + 0x000001d4,0x000001ab,0x00050085,0x00000024,0x000001d5,0x000001d4,0x000001ba,0x0004003d, + 0x00000024,0x000001d6,0x000001af,0x00050081,0x00000024,0x000001d7,0x000001d5,0x000001d6, + 0x00050081,0x00000024,0x000001d8,0x000001d3,0x000001d7,0x0003003e,0x000001d1,0x000001d8, + 0x00050041,0x00000007,0x000001da,0x000001b7,0x00000091,0x0004003d,0x00000006,0x000001db, + 0x000001da,0x0003003e,0x000001d9,0x000001db,0x00050041,0x00000007,0x000001dd,0x000001b7, + 0x00000097,0x0004003d,0x00000006,0x000001de,0x000001dd,0x0003003e,0x000001dc,0x000001de, + 0x00050041,0x00000007,0x000001e0,0x000001c1,0x00000091,0x0004003d,0x00000006,0x000001e1, + 0x000001e0,0x0003003e,0x000001df,0x000001e1,0x00050041,0x00000007,0x000001e3,0x000001c1, + 0x00000097,0x0004003d,0x00000006,0x000001e4,0x000001e3,0x0003003e,0x000001e2,0x000001e4, + 0x00050041,0x00000007,0x000001e7,0x000001c1,0x000001e6,0x0004003d,0x00000006,0x000001e8, + 0x000001e7,0x0003003e,0x000001e5,0x000001e8,0x00050041,0x00000007,0x000001eb,0x000001c1, + 0x000001ea,0x0004003d,0x00000006,0x000001ec,0x000001eb,0x0003003e,0x000001e9,0x000001ec, + 0x00050041,0x00000007,0x000001ee,0x000001c9,0x00000091,0x0004003d,0x00000006,0x000001ef, + 0x000001ee,0x0003003e,0x000001ed,0x000001ef,0x00050041,0x00000007,0x000001f1,0x000001c9, + 0x00000097,0x0004003d,0x00000006,0x000001f2,0x000001f1,0x0003003e,0x000001f0,0x000001f2, + 0x00050041,0x00000007,0x000001f4,0x000001c9,0x000001e6,0x0004003d,0x00000006,0x000001f5, + 0x000001f4,0x0003003e,0x000001f3,0x000001f5,0x00050041,0x00000007,0x000001f7,0x000001c9, + 0x000001ea,0x0004003d,0x00000006,0x000001f8,0x000001f7,0x0003003e,0x000001f6,0x000001f8, + 0x00050041,0x00000007,0x000001fa,0x000001d1,0x000001e6,0x0004003d,0x00000006,0x000001fb, + 0x000001fa,0x0003003e,0x000001f9,0x000001fb,0x00050041,0x00000007,0x000001fd,0x000001d1, + 0x000001ea,0x0004003d,0x00000006,0x000001fe,0x000001fd,0x0003003e,0x000001fc,0x000001fe, + 0x0003003e,0x000001ff,0x00000200,0x0003003e,0x00000201,0x00000067,0x0004003d,0x00000022, + 0x00000205,0x000001ff,0x0003003e,0x00000204,0x00000205,0x0004003d,0x00000006,0x00000207, + 0x00000201,0x0003003e,0x00000206,0x00000207,0x0004003d,0x00000022,0x00000209,0x00000159, + 0x0003003e,0x00000208,0x00000209,0x0003003e,0x0000020a,0x00000202,0x0003003e,0x0000020b, + 0x00000203,0x0003003e,0x0000020c,0x00000203,0x0003003e,0x0000020d,0x00000203,0x0004003d, + 0x00000006,0x0000020f,0x000001d9,0x0003003e,0x0000020e,0x0000020f,0x0004003d,0x00000006, + 0x00000211,0x000001e9,0x0003003e,0x00000210,0x00000211,0x0004003d,0x00000006,0x00000213, + 0x000001e5,0x0003003e,0x00000212,0x00000213,0x0004003d,0x00000006,0x00000215,0x000001f6, + 0x0003003e,0x00000214,0x00000215,0x0004003d,0x00000006,0x00000217,0x000001e2,0x0003003e, + 0x00000216,0x00000217,0x00100039,0x00000002,0x00000218,0x00000049,0x00000204,0x00000206, + 0x00000208,0x0000020a,0x0000020b,0x0000020c,0x0000020d,0x0000020e,0x00000210,0x00000212, + 0x00000214,0x00000216,0x0004003d,0x00000022,0x00000219,0x00000204,0x0003003e,0x000001ff, + 0x00000219,0x0004003d,0x00000006,0x0000021a,0x00000206,0x0003003e,0x00000201,0x0000021a, + 0x0004003d,0x00000022,0x0000021c,0x000001ff,0x0003003e,0x0000021b,0x0000021c,0x0004003d, + 0x00000006,0x0000021e,0x00000201,0x0003003e,0x0000021d,0x0000021e,0x0004003d,0x00000022, + 0x00000220,0x00000159,0x0003003e,0x0000021f,0x00000220,0x0003003e,0x00000221,0x00000203, + 0x0003003e,0x00000222,0x00000202,0x0003003e,0x00000223,0x00000203,0x0003003e,0x00000224, + 0x00000203,0x0004003d,0x00000006,0x00000226,0x000001dc,0x0003003e,0x00000225,0x00000226, + 0x0004003d,0x00000006,0x00000228,0x000001e5,0x0003003e,0x00000227,0x00000228,0x0004003d, + 0x00000006,0x0000022a,0x000001f6,0x0003003e,0x00000229,0x0000022a,0x0004003d,0x00000006, + 0x0000022c,0x000001f3,0x0003003e,0x0000022b,0x0000022c,0x0004003d,0x00000006,0x0000022e, + 0x000001ed,0x0003003e,0x0000022d,0x0000022e,0x00100039,0x00000002,0x0000022f,0x00000049, + 0x0000021b,0x0000021d,0x0000021f,0x00000221,0x00000222,0x00000223,0x00000224,0x00000225, + 0x00000227,0x00000229,0x0000022b,0x0000022d,0x0004003d,0x00000022,0x00000230,0x0000021b, + 0x0003003e,0x000001ff,0x00000230,0x0004003d,0x00000006,0x00000231,0x0000021d,0x0003003e, + 0x00000201,0x00000231,0x0004003d,0x00000022,0x00000233,0x000001ff,0x0003003e,0x00000232, + 0x00000233,0x0004003d,0x00000006,0x00000235,0x00000201,0x0003003e,0x00000234,0x00000235, + 0x0004003d,0x00000022,0x00000237,0x00000159,0x0003003e,0x00000236,0x00000237,0x0003003e, + 0x00000238,0x00000203,0x0003003e,0x00000239,0x00000203,0x0003003e,0x0000023a,0x00000202, + 0x0003003e,0x0000023b,0x00000203,0x0004003d,0x00000006,0x0000023d,0x000001e5,0x0003003e, + 0x0000023c,0x0000023d,0x0004003d,0x00000006,0x0000023f,0x000001df,0x0003003e,0x0000023e, + 0x0000023f,0x0004003d,0x00000006,0x00000241,0x000001e2,0x0003003e,0x00000240,0x00000241, + 0x0004003d,0x00000006,0x00000243,0x000001ed,0x0003003e,0x00000242,0x00000243,0x0004003d, + 0x00000006,0x00000245,0x000001fc,0x0003003e,0x00000244,0x00000245,0x00100039,0x00000002, + 0x00000246,0x00000049,0x00000232,0x00000234,0x00000236,0x00000238,0x00000239,0x0000023a, + 0x0000023b,0x0000023c,0x0000023e,0x00000240,0x00000242,0x00000244,0x0004003d,0x00000022, + 0x00000247,0x00000232,0x0003003e,0x000001ff,0x00000247,0x0004003d,0x00000006,0x00000248, + 0x00000234,0x0003003e,0x00000201,0x00000248,0x0004003d,0x00000022,0x0000024a,0x000001ff, + 0x0003003e,0x00000249,0x0000024a,0x0004003d,0x00000006,0x0000024c,0x00000201,0x0003003e, + 0x0000024b,0x0000024c,0x0004003d,0x00000022,0x0000024e,0x00000159,0x0003003e,0x0000024d, + 0x0000024e,0x0003003e,0x0000024f,0x00000203,0x0003003e,0x00000250,0x00000203,0x0003003e, + 0x00000251,0x00000203,0x0003003e,0x00000252,0x00000202,0x0004003d,0x00000006,0x00000254, + 0x000001f6,0x0003003e,0x00000253,0x00000254,0x0004003d,0x00000006,0x00000256,0x000001e2, + 0x0003003e,0x00000255,0x00000256,0x0004003d,0x00000006,0x00000258,0x000001ed,0x0003003e, + 0x00000257,0x00000258,0x0004003d,0x00000006,0x0000025a,0x000001f0,0x0003003e,0x00000259, + 0x0000025a,0x0004003d,0x00000006,0x0000025c,0x000001f9,0x0003003e,0x0000025b,0x0000025c, + 0x00100039,0x00000002,0x0000025d,0x00000049,0x00000249,0x0000024b,0x0000024d,0x0000024f, + 0x00000250,0x00000251,0x00000252,0x00000253,0x00000255,0x00000257,0x00000259,0x0000025b, + 0x0004003d,0x00000022,0x0000025e,0x00000249,0x0003003e,0x000001ff,0x0000025e,0x0004003d, + 0x00000006,0x0000025f,0x0000024b,0x0003003e,0x00000201,0x0000025f,0x0004003d,0x00000022, + 0x00000261,0x000001ff,0x0004003d,0x00000022,0x00000262,0x000001ff,0x00050085,0x00000022, + 0x00000263,0x00000261,0x00000262,0x0003003e,0x00000260,0x00000263,0x00050041,0x00000007, + 0x00000265,0x00000260,0x00000091,0x0004003d,0x00000006,0x00000266,0x00000265,0x00050041, + 0x00000007,0x00000267,0x00000260,0x00000097,0x0004003d,0x00000006,0x00000268,0x00000267, + 0x00050081,0x00000006,0x00000269,0x00000266,0x00000268,0x0003003e,0x00000264,0x00000269, + 0x0004003d,0x00000006,0x0000026b,0x00000264,0x000500b8,0x0000003a,0x0000026d,0x0000026b, + 0x0000026c,0x0003003e,0x0000026a,0x0000026d,0x0004003d,0x00000006,0x0000026f,0x00000264, + 0x0003003e,0x0000026e,0x0000026f,0x00050039,0x00000006,0x00000270,0x00000010,0x0000026e, + 0x0003003e,0x00000264,0x00000270,0x0004003d,0x0000003a,0x00000271,0x0000026a,0x0004003d, + 0x00000006,0x00000272,0x00000264,0x000600a9,0x00000006,0x00000273,0x00000271,0x00000059, + 0x00000272,0x0003003e,0x00000264,0x00000273,0x0004003d,0x0000003a,0x00000274,0x0000026a, + 0x000300f7,0x00000277,0x00000000,0x000400fa,0x00000274,0x00000276,0x00000278,0x000200f8, + 0x00000276,0x0003003e,0x00000275,0x00000059,0x000200f9,0x00000277,0x000200f8,0x00000278, + 0x00050041,0x00000007,0x00000279,0x000001ff,0x00000091,0x0004003d,0x00000006,0x0000027a, + 0x00000279,0x0003003e,0x00000275,0x0000027a,0x000200f9,0x00000277,0x000200f8,0x00000277, + 0x0004003d,0x00000006,0x0000027b,0x00000275,0x00050041,0x00000007,0x0000027c,0x000001ff, + 0x00000091,0x0003003e,0x0000027c,0x0000027b,0x0004003d,0x00000006,0x0000027d,0x00000264, + 0x00050050,0x00000022,0x0000027e,0x0000027d,0x0000027d,0x0004003d,0x00000022,0x0000027f, + 0x000001ff,0x00050085,0x00000022,0x00000280,0x0000027f,0x0000027e,0x0003003e,0x000001ff, + 0x00000280,0x0004003d,0x00000006,0x00000281,0x00000201,0x00050085,0x00000006,0x00000282, + 0x00000281,0x000001b9,0x0003003e,0x00000201,0x00000282,0x0004003d,0x00000006,0x00000283, + 0x00000201,0x0004003d,0x00000006,0x00000284,0x00000201,0x00050085,0x00000006,0x00000285, + 0x00000284,0x00000283,0x0003003e,0x00000201,0x00000285,0x00050041,0x00000007,0x00000287, + 0x000001ff,0x00000091,0x0004003d,0x00000006,0x00000288,0x00000287,0x00050041,0x00000007, + 0x00000289,0x000001ff,0x00000091,0x0004003d,0x00000006,0x0000028a,0x00000289,0x00050085, + 0x00000006,0x0000028b,0x00000288,0x0000028a,0x00050041,0x00000007,0x0000028c,0x000001ff, + 0x00000097,0x0004003d,0x00000006,0x0000028d,0x0000028c,0x00050041,0x00000007,0x0000028e, + 0x000001ff,0x00000097,0x0004003d,0x00000006,0x0000028f,0x0000028e,0x00050085,0x00000006, + 0x00000290,0x0000028d,0x0000028f,0x00050081,0x00000006,0x00000291,0x0000028b,0x00000290, + 0x00050041,0x00000007,0x00000292,0x000001ff,0x00000091,0x0004003d,0x00000006,0x00000293, + 0x00000292,0x0006000c,0x00000006,0x00000294,0x00000001,0x00000004,0x00000293,0x00050041, + 0x00000007,0x00000295,0x000001ff,0x00000097,0x0004003d,0x00000006,0x00000296,0x00000295, + 0x0006000c,0x00000006,0x00000297,0x00000001,0x00000004,0x00000296,0x0007000c,0x00000006, + 0x00000298,0x00000001,0x00000028,0x00000294,0x00000297,0x0003003e,0x00000299,0x00000298, + 0x00050039,0x00000006,0x0000029a,0x0000000d,0x00000299,0x00050085,0x00000006,0x0000029b, + 0x00000291,0x0000029a,0x0003003e,0x00000286,0x0000029b,0x0004003d,0x00000006,0x0000029d, + 0x00000286,0x00050083,0x00000006,0x0000029e,0x0000029d,0x00000059,0x0004003d,0x00000006, + 0x0000029f,0x00000201,0x00050085,0x00000006,0x000002a0,0x0000029e,0x0000029f,0x00050081, + 0x00000006,0x000002a1,0x00000059,0x000002a0,0x0004003d,0x00000006,0x000002a3,0x00000201, + 0x00050085,0x00000006,0x000002a4,0x000002a2,0x000002a3,0x00050081,0x00000006,0x000002a5, + 0x00000059,0x000002a4,0x00050050,0x00000022,0x000002a6,0x000002a1,0x000002a5,0x0003003e, + 0x0000029c,0x000002a6,0x0004003d,0x00000006,0x000002a9,0x00000201,0x00050085,0x00000006, + 0x000002aa,0x000002a8,0x000002a9,0x00050081,0x00000006,0x000002ab,0x000001b9,0x000002aa, + 0x0003003e,0x000002a7,0x000002ab,0x0004003d,0x00000006,0x000002ae,0x000002a7,0x0003003e, + 0x000002ad,0x000002ae,0x00050039,0x00000006,0x000002af,0x0000000d,0x000002ad,0x0003003e, + 0x000002ac,0x000002af,0x00050041,0x00000007,0x000002b1,0x00000193,0x000001e6,0x0004003d, + 0x00000006,0x000002b2,0x000002b1,0x00050041,0x00000007,0x000002b3,0x00000197,0x000001e6, + 0x0004003d,0x00000006,0x000002b4,0x000002b3,0x00050041,0x00000007,0x000002b5,0x0000019b, + 0x000001e6,0x0004003d,0x00000006,0x000002b6,0x000002b5,0x00060050,0x00000015,0x000002b7, + 0x000002b2,0x000002b4,0x000002b6,0x00050041,0x00000007,0x000002b8,0x0000019f,0x000001ea, + 0x0004003d,0x00000006,0x000002b9,0x000002b8,0x00050041,0x00000007,0x000002ba,0x000001a3, + 0x000001ea,0x0004003d,0x00000006,0x000002bb,0x000002ba,0x00050041,0x00000007,0x000002bc, + 0x000001a7,0x000001ea,0x0004003d,0x00000006,0x000002bd,0x000002bc,0x00060050,0x00000015, + 0x000002be,0x000002b9,0x000002bb,0x000002bd,0x00050041,0x00000007,0x000002bf,0x00000193, + 0x00000097,0x0004003d,0x00000006,0x000002c0,0x000002bf,0x00050041,0x00000007,0x000002c1, + 0x00000197,0x00000097,0x0004003d,0x00000006,0x000002c2,0x000002c1,0x00050041,0x00000007, + 0x000002c3,0x0000019b,0x00000097,0x0004003d,0x00000006,0x000002c4,0x000002c3,0x00060050, + 0x00000015,0x000002c5,0x000002c0,0x000002c2,0x000002c4,0x0003003e,0x000002c6,0x000002b7, + 0x0003003e,0x000002c7,0x000002be,0x0003003e,0x000002c8,0x000002c5,0x00070039,0x00000015, + 0x000002c9,0x0000001b,0x000002c6,0x000002c7,0x000002c8,0x00050041,0x00000007,0x000002ca, + 0x0000019f,0x00000091,0x0004003d,0x00000006,0x000002cb,0x000002ca,0x00050041,0x00000007, + 0x000002cc,0x000001a3,0x00000091,0x0004003d,0x00000006,0x000002cd,0x000002cc,0x00050041, + 0x00000007,0x000002ce,0x000001a7,0x00000091,0x0004003d,0x00000006,0x000002cf,0x000002ce, + 0x00060050,0x00000015,0x000002d0,0x000002cb,0x000002cd,0x000002cf,0x0007000c,0x00000015, + 0x000002d1,0x00000001,0x00000025,0x000002c9,0x000002d0,0x0003003e,0x000002b0,0x000002d1, + 0x00050041,0x00000007,0x000002d3,0x00000193,0x000001e6,0x0004003d,0x00000006,0x000002d4, + 0x000002d3,0x00050041,0x00000007,0x000002d5,0x00000197,0x000001e6,0x0004003d,0x00000006, + 0x000002d6,0x000002d5,0x00050041,0x00000007,0x000002d7,0x0000019b,0x000001e6,0x0004003d, + 0x00000006,0x000002d8,0x000002d7,0x00060050,0x00000015,0x000002d9,0x000002d4,0x000002d6, + 0x000002d8,0x00050041,0x00000007,0x000002da,0x0000019f,0x000001ea,0x0004003d,0x00000006, + 0x000002db,0x000002da,0x00050041,0x00000007,0x000002dc,0x000001a3,0x000001ea,0x0004003d, + 0x00000006,0x000002dd,0x000002dc,0x00050041,0x00000007,0x000002de,0x000001a7,0x000001ea, + 0x0004003d,0x00000006,0x000002df,0x000002de,0x00060050,0x00000015,0x000002e0,0x000002db, + 0x000002dd,0x000002df,0x00050041,0x00000007,0x000002e1,0x00000193,0x00000097,0x0004003d, + 0x00000006,0x000002e2,0x000002e1,0x00050041,0x00000007,0x000002e3,0x00000197,0x00000097, + 0x0004003d,0x00000006,0x000002e4,0x000002e3,0x00050041,0x00000007,0x000002e5,0x0000019b, + 0x00000097,0x0004003d,0x00000006,0x000002e6,0x000002e5,0x00060050,0x00000015,0x000002e7, + 0x000002e2,0x000002e4,0x000002e6,0x0003003e,0x000002e8,0x000002d9,0x0003003e,0x000002e9, + 0x000002e0,0x0003003e,0x000002ea,0x000002e7,0x00070039,0x00000015,0x000002eb,0x00000020, + 0x000002e8,0x000002e9,0x000002ea,0x00050041,0x00000007,0x000002ec,0x0000019f,0x00000091, + 0x0004003d,0x00000006,0x000002ed,0x000002ec,0x00050041,0x00000007,0x000002ee,0x000001a3, + 0x00000091,0x0004003d,0x00000006,0x000002ef,0x000002ee,0x00050041,0x00000007,0x000002f0, + 0x000001a7,0x00000091,0x0004003d,0x00000006,0x000002f1,0x000002f0,0x00060050,0x00000015, + 0x000002f2,0x000002ed,0x000002ef,0x000002f1,0x0007000c,0x00000015,0x000002f3,0x00000001, + 0x00000028,0x000002eb,0x000002f2,0x0003003e,0x000002d2,0x000002f3,0x0003003e,0x000002f4, + 0x000002f5,0x0003003e,0x000002f6,0x00000067,0x0004003d,0x00000022,0x000002f8,0x00000159, + 0x00050083,0x00000022,0x000002f9,0x000002f7,0x000002f8,0x00050041,0x00000007,0x000002fa, + 0x00000187,0x00000091,0x0004003d,0x00000006,0x000002fb,0x000002fa,0x00050041,0x00000007, + 0x000002fc,0x0000018b,0x00000091,0x0004003d,0x00000006,0x000002fd,0x000002fc,0x00050041, + 0x00000007,0x000002fe,0x0000018f,0x00000091,0x0004003d,0x00000006,0x000002ff,0x000002fe, + 0x00060050,0x00000015,0x00000300,0x000002fb,0x000002fd,0x000002ff,0x0004003d,0x00000015, + 0x00000302,0x000002f4,0x0003003e,0x00000301,0x00000302,0x0004003d,0x00000006,0x00000304, + 0x000002f6,0x0003003e,0x00000303,0x00000304,0x0003003e,0x00000305,0x000002f9,0x0004003d, + 0x00000022,0x00000307,0x000001ff,0x0003003e,0x00000306,0x00000307,0x0004003d,0x00000022, + 0x00000309,0x0000029c,0x0003003e,0x00000308,0x00000309,0x0004003d,0x00000006,0x0000030b, + 0x000002a7,0x0003003e,0x0000030a,0x0000030b,0x0004003d,0x00000006,0x0000030d,0x000002ac, + 0x0003003e,0x0000030c,0x0000030d,0x0003003e,0x0000030e,0x00000300,0x000c0039,0x00000002, + 0x0000030f,0x00000038,0x00000301,0x00000303,0x00000305,0x00000306,0x00000308,0x0000030a, + 0x0000030c,0x0000030e,0x0004003d,0x00000015,0x00000310,0x00000301,0x0003003e,0x000002f4, + 0x00000310,0x0004003d,0x00000006,0x00000311,0x00000303,0x0003003e,0x000002f6,0x00000311, + 0x0004003d,0x00000022,0x00000313,0x00000159,0x00050083,0x00000022,0x00000314,0x00000312, + 0x00000313,0x00050041,0x00000007,0x00000315,0x00000187,0x00000097,0x0004003d,0x00000006, + 0x00000316,0x00000315,0x00050041,0x00000007,0x00000317,0x0000018b,0x00000097,0x0004003d, + 0x00000006,0x00000318,0x00000317,0x00050041,0x00000007,0x00000319,0x0000018f,0x00000097, + 0x0004003d,0x00000006,0x0000031a,0x00000319,0x00060050,0x00000015,0x0000031b,0x00000316, + 0x00000318,0x0000031a,0x0004003d,0x00000015,0x0000031d,0x000002f4,0x0003003e,0x0000031c, + 0x0000031d,0x0004003d,0x00000006,0x0000031f,0x000002f6,0x0003003e,0x0000031e,0x0000031f, + 0x0003003e,0x00000320,0x00000314,0x0004003d,0x00000022,0x00000322,0x000001ff,0x0003003e, + 0x00000321,0x00000322,0x0004003d,0x00000022,0x00000324,0x0000029c,0x0003003e,0x00000323, + 0x00000324,0x0004003d,0x00000006,0x00000326,0x000002a7,0x0003003e,0x00000325,0x00000326, + 0x0004003d,0x00000006,0x00000328,0x000002ac,0x0003003e,0x00000327,0x00000328,0x0003003e, + 0x00000329,0x0000031b,0x000c0039,0x00000002,0x0000032a,0x00000038,0x0000031c,0x0000031e, + 0x00000320,0x00000321,0x00000323,0x00000325,0x00000327,0x00000329,0x0004003d,0x00000015, + 0x0000032b,0x0000031c,0x0003003e,0x000002f4,0x0000032b,0x0004003d,0x00000006,0x0000032c, + 0x0000031e,0x0003003e,0x000002f6,0x0000032c,0x0004003d,0x00000022,0x0000032e,0x00000159, + 0x00050083,0x00000022,0x0000032f,0x0000032d,0x0000032e,0x00050041,0x00000007,0x00000330, + 0x00000193,0x00000091,0x0004003d,0x00000006,0x00000331,0x00000330,0x00050041,0x00000007, + 0x00000332,0x00000197,0x00000091,0x0004003d,0x00000006,0x00000333,0x00000332,0x00050041, + 0x00000007,0x00000334,0x0000019b,0x00000091,0x0004003d,0x00000006,0x00000335,0x00000334, + 0x00060050,0x00000015,0x00000336,0x00000331,0x00000333,0x00000335,0x0004003d,0x00000015, + 0x00000338,0x000002f4,0x0003003e,0x00000337,0x00000338,0x0004003d,0x00000006,0x0000033a, + 0x000002f6,0x0003003e,0x00000339,0x0000033a,0x0003003e,0x0000033b,0x0000032f,0x0004003d, + 0x00000022,0x0000033d,0x000001ff,0x0003003e,0x0000033c,0x0000033d,0x0004003d,0x00000022, + 0x0000033f,0x0000029c,0x0003003e,0x0000033e,0x0000033f,0x0004003d,0x00000006,0x00000341, + 0x000002a7,0x0003003e,0x00000340,0x00000341,0x0004003d,0x00000006,0x00000343,0x000002ac, + 0x0003003e,0x00000342,0x00000343,0x0003003e,0x00000344,0x00000336,0x000c0039,0x00000002, + 0x00000345,0x00000038,0x00000337,0x00000339,0x0000033b,0x0000033c,0x0000033e,0x00000340, + 0x00000342,0x00000344,0x0004003d,0x00000015,0x00000346,0x00000337,0x0003003e,0x000002f4, + 0x00000346,0x0004003d,0x00000006,0x00000347,0x00000339,0x0003003e,0x000002f6,0x00000347, + 0x0004003d,0x00000022,0x00000349,0x00000159,0x00050083,0x00000022,0x0000034a,0x00000348, + 0x00000349,0x00050041,0x00000007,0x0000034b,0x00000193,0x00000097,0x0004003d,0x00000006, + 0x0000034c,0x0000034b,0x00050041,0x00000007,0x0000034d,0x00000197,0x00000097,0x0004003d, + 0x00000006,0x0000034e,0x0000034d,0x00050041,0x00000007,0x0000034f,0x0000019b,0x00000097, + 0x0004003d,0x00000006,0x00000350,0x0000034f,0x00060050,0x00000015,0x00000351,0x0000034c, + 0x0000034e,0x00000350,0x0004003d,0x00000015,0x00000353,0x000002f4,0x0003003e,0x00000352, + 0x00000353,0x0004003d,0x00000006,0x00000355,0x000002f6,0x0003003e,0x00000354,0x00000355, + 0x0003003e,0x00000356,0x0000034a,0x0004003d,0x00000022,0x00000358,0x000001ff,0x0003003e, + 0x00000357,0x00000358,0x0004003d,0x00000022,0x0000035a,0x0000029c,0x0003003e,0x00000359, + 0x0000035a,0x0004003d,0x00000006,0x0000035c,0x000002a7,0x0003003e,0x0000035b,0x0000035c, + 0x0004003d,0x00000006,0x0000035e,0x000002ac,0x0003003e,0x0000035d,0x0000035e,0x0003003e, + 0x0000035f,0x00000351,0x000c0039,0x00000002,0x00000360,0x00000038,0x00000352,0x00000354, + 0x00000356,0x00000357,0x00000359,0x0000035b,0x0000035d,0x0000035f,0x0004003d,0x00000015, + 0x00000361,0x00000352,0x0003003e,0x000002f4,0x00000361,0x0004003d,0x00000006,0x00000362, + 0x00000354,0x0003003e,0x000002f6,0x00000362,0x0004003d,0x00000022,0x00000363,0x00000159, + 0x00050083,0x00000022,0x00000364,0x00000200,0x00000363,0x00050041,0x00000007,0x00000365, + 0x00000193,0x000001e6,0x0004003d,0x00000006,0x00000366,0x00000365,0x00050041,0x00000007, + 0x00000367,0x00000197,0x000001e6,0x0004003d,0x00000006,0x00000368,0x00000367,0x00050041, + 0x00000007,0x00000369,0x0000019b,0x000001e6,0x0004003d,0x00000006,0x0000036a,0x00000369, + 0x00060050,0x00000015,0x0000036b,0x00000366,0x00000368,0x0000036a,0x0004003d,0x00000015, + 0x0000036d,0x000002f4,0x0003003e,0x0000036c,0x0000036d,0x0004003d,0x00000006,0x0000036f, + 0x000002f6,0x0003003e,0x0000036e,0x0000036f,0x0003003e,0x00000370,0x00000364,0x0004003d, + 0x00000022,0x00000372,0x000001ff,0x0003003e,0x00000371,0x00000372,0x0004003d,0x00000022, + 0x00000374,0x0000029c,0x0003003e,0x00000373,0x00000374,0x0004003d,0x00000006,0x00000376, + 0x000002a7,0x0003003e,0x00000375,0x00000376,0x0004003d,0x00000006,0x00000378,0x000002ac, + 0x0003003e,0x00000377,0x00000378,0x0003003e,0x00000379,0x0000036b,0x000c0039,0x00000002, + 0x0000037a,0x00000038,0x0000036c,0x0000036e,0x00000370,0x00000371,0x00000373,0x00000375, + 0x00000377,0x00000379,0x0004003d,0x00000015,0x0000037b,0x0000036c,0x0003003e,0x000002f4, + 0x0000037b,0x0004003d,0x00000006,0x0000037c,0x0000036e,0x0003003e,0x000002f6,0x0000037c, + 0x0004003d,0x00000022,0x0000037e,0x00000159,0x00050083,0x00000022,0x0000037f,0x0000037d, + 0x0000037e,0x00050041,0x00000007,0x00000380,0x00000193,0x000001ea,0x0004003d,0x00000006, + 0x00000381,0x00000380,0x00050041,0x00000007,0x00000382,0x00000197,0x000001ea,0x0004003d, + 0x00000006,0x00000383,0x00000382,0x00050041,0x00000007,0x00000384,0x0000019b,0x000001ea, + 0x0004003d,0x00000006,0x00000385,0x00000384,0x00060050,0x00000015,0x00000386,0x00000381, + 0x00000383,0x00000385,0x0004003d,0x00000015,0x00000388,0x000002f4,0x0003003e,0x00000387, + 0x00000388,0x0004003d,0x00000006,0x0000038a,0x000002f6,0x0003003e,0x00000389,0x0000038a, + 0x0003003e,0x0000038b,0x0000037f,0x0004003d,0x00000022,0x0000038d,0x000001ff,0x0003003e, + 0x0000038c,0x0000038d,0x0004003d,0x00000022,0x0000038f,0x0000029c,0x0003003e,0x0000038e, + 0x0000038f,0x0004003d,0x00000006,0x00000391,0x000002a7,0x0003003e,0x00000390,0x00000391, + 0x0004003d,0x00000006,0x00000393,0x000002ac,0x0003003e,0x00000392,0x00000393,0x0003003e, + 0x00000394,0x00000386,0x000c0039,0x00000002,0x00000395,0x00000038,0x00000387,0x00000389, + 0x0000038b,0x0000038c,0x0000038e,0x00000390,0x00000392,0x00000394,0x0004003d,0x00000015, + 0x00000396,0x00000387,0x0003003e,0x000002f4,0x00000396,0x0004003d,0x00000006,0x00000397, + 0x00000389,0x0003003e,0x000002f6,0x00000397,0x0004003d,0x00000022,0x00000399,0x00000159, + 0x00050083,0x00000022,0x0000039a,0x00000398,0x00000399,0x00050041,0x00000007,0x0000039b, + 0x0000019f,0x00000091,0x0004003d,0x00000006,0x0000039c,0x0000039b,0x00050041,0x00000007, + 0x0000039d,0x000001a3,0x00000091,0x0004003d,0x00000006,0x0000039e,0x0000039d,0x00050041, + 0x00000007,0x0000039f,0x000001a7,0x00000091,0x0004003d,0x00000006,0x000003a0,0x0000039f, + 0x00060050,0x00000015,0x000003a1,0x0000039c,0x0000039e,0x000003a0,0x0004003d,0x00000015, + 0x000003a3,0x000002f4,0x0003003e,0x000003a2,0x000003a3,0x0004003d,0x00000006,0x000003a5, + 0x000002f6,0x0003003e,0x000003a4,0x000003a5,0x0003003e,0x000003a6,0x0000039a,0x0004003d, + 0x00000022,0x000003a8,0x000001ff,0x0003003e,0x000003a7,0x000003a8,0x0004003d,0x00000022, + 0x000003aa,0x0000029c,0x0003003e,0x000003a9,0x000003aa,0x0004003d,0x00000006,0x000003ac, + 0x000002a7,0x0003003e,0x000003ab,0x000003ac,0x0004003d,0x00000006,0x000003ae,0x000002ac, + 0x0003003e,0x000003ad,0x000003ae,0x0003003e,0x000003af,0x000003a1,0x000c0039,0x00000002, + 0x000003b0,0x00000038,0x000003a2,0x000003a4,0x000003a6,0x000003a7,0x000003a9,0x000003ab, + 0x000003ad,0x000003af,0x0004003d,0x00000015,0x000003b1,0x000003a2,0x0003003e,0x000002f4, + 0x000003b1,0x0004003d,0x00000006,0x000003b2,0x000003a4,0x0003003e,0x000002f6,0x000003b2, + 0x0004003d,0x00000022,0x000003b5,0x00000159,0x00050083,0x00000022,0x000003b6,0x000003b4, + 0x000003b5,0x00050041,0x00000007,0x000003b7,0x0000019f,0x00000097,0x0004003d,0x00000006, + 0x000003b8,0x000003b7,0x00050041,0x00000007,0x000003b9,0x000001a3,0x00000097,0x0004003d, + 0x00000006,0x000003ba,0x000003b9,0x00050041,0x00000007,0x000003bb,0x000001a7,0x00000097, + 0x0004003d,0x00000006,0x000003bc,0x000003bb,0x00060050,0x00000015,0x000003bd,0x000003b8, + 0x000003ba,0x000003bc,0x0004003d,0x00000015,0x000003bf,0x000002f4,0x0003003e,0x000003be, + 0x000003bf,0x0004003d,0x00000006,0x000003c1,0x000002f6,0x0003003e,0x000003c0,0x000003c1, + 0x0003003e,0x000003c2,0x000003b6,0x0004003d,0x00000022,0x000003c4,0x000001ff,0x0003003e, + 0x000003c3,0x000003c4,0x0004003d,0x00000022,0x000003c6,0x0000029c,0x0003003e,0x000003c5, + 0x000003c6,0x0004003d,0x00000006,0x000003c8,0x000002a7,0x0003003e,0x000003c7,0x000003c8, + 0x0004003d,0x00000006,0x000003ca,0x000002ac,0x0003003e,0x000003c9,0x000003ca,0x0003003e, + 0x000003cb,0x000003bd,0x000c0039,0x00000002,0x000003cc,0x00000038,0x000003be,0x000003c0, + 0x000003c2,0x000003c3,0x000003c5,0x000003c7,0x000003c9,0x000003cb,0x0004003d,0x00000015, + 0x000003cd,0x000003be,0x0003003e,0x000002f4,0x000003cd,0x0004003d,0x00000006,0x000003ce, + 0x000003c0,0x0003003e,0x000002f6,0x000003ce,0x0004003d,0x00000022,0x000003d0,0x00000159, + 0x00050083,0x00000022,0x000003d1,0x000003cf,0x000003d0,0x00050041,0x00000007,0x000003d2, + 0x0000019f,0x000001e6,0x0004003d,0x00000006,0x000003d3,0x000003d2,0x00050041,0x00000007, + 0x000003d4,0x000001a3,0x000001e6,0x0004003d,0x00000006,0x000003d5,0x000003d4,0x00050041, + 0x00000007,0x000003d6,0x000001a7,0x000001e6,0x0004003d,0x00000006,0x000003d7,0x000003d6, + 0x00060050,0x00000015,0x000003d8,0x000003d3,0x000003d5,0x000003d7,0x0004003d,0x00000015, + 0x000003da,0x000002f4,0x0003003e,0x000003d9,0x000003da,0x0004003d,0x00000006,0x000003dc, + 0x000002f6,0x0003003e,0x000003db,0x000003dc,0x0003003e,0x000003dd,0x000003d1,0x0004003d, + 0x00000022,0x000003df,0x000001ff,0x0003003e,0x000003de,0x000003df,0x0004003d,0x00000022, + 0x000003e1,0x0000029c,0x0003003e,0x000003e0,0x000003e1,0x0004003d,0x00000006,0x000003e3, + 0x000002a7,0x0003003e,0x000003e2,0x000003e3,0x0004003d,0x00000006,0x000003e5,0x000002ac, + 0x0003003e,0x000003e4,0x000003e5,0x0003003e,0x000003e6,0x000003d8,0x000c0039,0x00000002, + 0x000003e7,0x00000038,0x000003d9,0x000003db,0x000003dd,0x000003de,0x000003e0,0x000003e2, + 0x000003e4,0x000003e6,0x0004003d,0x00000015,0x000003e8,0x000003d9,0x0003003e,0x000002f4, + 0x000003e8,0x0004003d,0x00000006,0x000003e9,0x000003db,0x0003003e,0x000002f6,0x000003e9, + 0x0004003d,0x00000022,0x000003eb,0x00000159,0x00050083,0x00000022,0x000003ec,0x000003ea, + 0x000003eb,0x00050041,0x00000007,0x000003ed,0x0000019f,0x000001ea,0x0004003d,0x00000006, + 0x000003ee,0x000003ed,0x00050041,0x00000007,0x000003ef,0x000001a3,0x000001ea,0x0004003d, + 0x00000006,0x000003f0,0x000003ef,0x00050041,0x00000007,0x000003f1,0x000001a7,0x000001ea, + 0x0004003d,0x00000006,0x000003f2,0x000003f1,0x00060050,0x00000015,0x000003f3,0x000003ee, + 0x000003f0,0x000003f2,0x0004003d,0x00000015,0x000003f5,0x000002f4,0x0003003e,0x000003f4, + 0x000003f5,0x0004003d,0x00000006,0x000003f7,0x000002f6,0x0003003e,0x000003f6,0x000003f7, + 0x0003003e,0x000003f8,0x000003ec,0x0004003d,0x00000022,0x000003fa,0x000001ff,0x0003003e, + 0x000003f9,0x000003fa,0x0004003d,0x00000022,0x000003fc,0x0000029c,0x0003003e,0x000003fb, + 0x000003fc,0x0004003d,0x00000006,0x000003fe,0x000002a7,0x0003003e,0x000003fd,0x000003fe, + 0x0004003d,0x00000006,0x00000400,0x000002ac,0x0003003e,0x000003ff,0x00000400,0x0003003e, + 0x00000401,0x000003f3,0x000c0039,0x00000002,0x00000402,0x00000038,0x000003f4,0x000003f6, + 0x000003f8,0x000003f9,0x000003fb,0x000003fd,0x000003ff,0x00000401,0x0004003d,0x00000015, + 0x00000403,0x000003f4,0x0003003e,0x000002f4,0x00000403,0x0004003d,0x00000006,0x00000404, + 0x000003f6,0x0003003e,0x000002f6,0x00000404,0x0004003d,0x00000022,0x00000406,0x00000159, + 0x00050083,0x00000022,0x00000407,0x00000405,0x00000406,0x00050041,0x00000007,0x00000408, + 0x000001ab,0x000001e6,0x0004003d,0x00000006,0x00000409,0x00000408,0x00050041,0x00000007, + 0x0000040a,0x000001af,0x000001e6,0x0004003d,0x00000006,0x0000040b,0x0000040a,0x00050041, + 0x00000007,0x0000040c,0x000001b3,0x000001e6,0x0004003d,0x00000006,0x0000040d,0x0000040c, + 0x00060050,0x00000015,0x0000040e,0x00000409,0x0000040b,0x0000040d,0x0004003d,0x00000015, + 0x00000410,0x000002f4,0x0003003e,0x0000040f,0x00000410,0x0004003d,0x00000006,0x00000412, + 0x000002f6,0x0003003e,0x00000411,0x00000412,0x0003003e,0x00000413,0x00000407,0x0004003d, + 0x00000022,0x00000415,0x000001ff,0x0003003e,0x00000414,0x00000415,0x0004003d,0x00000022, + 0x00000417,0x0000029c,0x0003003e,0x00000416,0x00000417,0x0004003d,0x00000006,0x00000419, + 0x000002a7,0x0003003e,0x00000418,0x00000419,0x0004003d,0x00000006,0x0000041b,0x000002ac, + 0x0003003e,0x0000041a,0x0000041b,0x0003003e,0x0000041c,0x0000040e,0x000c0039,0x00000002, + 0x0000041d,0x00000038,0x0000040f,0x00000411,0x00000413,0x00000414,0x00000416,0x00000418, + 0x0000041a,0x0000041c,0x0004003d,0x00000015,0x0000041e,0x0000040f,0x0003003e,0x000002f4, + 0x0000041e,0x0004003d,0x00000006,0x0000041f,0x00000411,0x0003003e,0x000002f6,0x0000041f, + 0x0004003d,0x00000022,0x00000421,0x00000159,0x00050083,0x00000022,0x00000422,0x00000420, + 0x00000421,0x00050041,0x00000007,0x00000423,0x000001ab,0x000001ea,0x0004003d,0x00000006, + 0x00000424,0x00000423,0x00050041,0x00000007,0x00000425,0x000001af,0x000001ea,0x0004003d, + 0x00000006,0x00000426,0x00000425,0x00050041,0x00000007,0x00000427,0x000001b3,0x000001ea, + 0x0004003d,0x00000006,0x00000428,0x00000427,0x00060050,0x00000015,0x00000429,0x00000424, + 0x00000426,0x00000428,0x0004003d,0x00000015,0x0000042b,0x000002f4,0x0003003e,0x0000042a, + 0x0000042b,0x0004003d,0x00000006,0x0000042d,0x000002f6,0x0003003e,0x0000042c,0x0000042d, + 0x0003003e,0x0000042e,0x00000422,0x0004003d,0x00000022,0x00000430,0x000001ff,0x0003003e, + 0x0000042f,0x00000430,0x0004003d,0x00000022,0x00000432,0x0000029c,0x0003003e,0x00000431, + 0x00000432,0x0004003d,0x00000006,0x00000434,0x000002a7,0x0003003e,0x00000433,0x00000434, + 0x0004003d,0x00000006,0x00000436,0x000002ac,0x0003003e,0x00000435,0x00000436,0x0003003e, + 0x00000437,0x00000429,0x000c0039,0x00000002,0x00000438,0x00000038,0x0000042a,0x0000042c, + 0x0000042e,0x0000042f,0x00000431,0x00000433,0x00000435,0x00000437,0x0004003d,0x00000015, + 0x00000439,0x0000042a,0x0003003e,0x000002f4,0x00000439,0x0004003d,0x00000006,0x0000043a, + 0x0000042c,0x0003003e,0x000002f6,0x0000043a,0x0004003d,0x00000015,0x0000043b,0x000002d2, + 0x0004003d,0x00000015,0x0000043c,0x000002b0,0x0004003d,0x00000015,0x0000043d,0x000002f4, + 0x0004003d,0x00000006,0x0000043f,0x000002f6,0x0003003e,0x0000043e,0x0000043f,0x00050039, + 0x00000006,0x00000440,0x0000000a,0x0000043e,0x00060050,0x00000015,0x00000441,0x00000440, + 0x00000440,0x00000440,0x00050085,0x00000015,0x00000442,0x0000043d,0x00000441,0x0007000c, + 0x00000015,0x00000443,0x00000001,0x00000028,0x0000043c,0x00000442,0x0007000c,0x00000015, + 0x00000444,0x00000001,0x00000025,0x0000043b,0x00000443,0x0003003e,0x00000051,0x00000444, + 0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/fsr_rcas_frag.h b/app/src/main/cpp/waylandcomp/shaders/fsr_rcas_frag.h new file mode 100644 index 000000000..066d125dd --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/fsr_rcas_frag.h @@ -0,0 +1,469 @@ + // 1116.2.0 + #pragma once +const uint32_t fsr_rcas_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000025e,0x00000000,0x00020011,0x00000001,0x00020011, + 0x00000032,0x0006000b,0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e, + 0x00000000,0x00000001,0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000023b, + 0x00000258,0x00030010,0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005, + 0x00000004,0x6e69616d,0x00000000,0x00050005,0x0000000a,0x70635241,0x66283146,0x00003b31, + 0x00030005,0x00000009,0x00000078,0x00070005,0x0000000d,0x78725041,0x5264654d,0x31467063, + 0x3b316628,0x00000000,0x00030005,0x0000000c,0x00000078,0x00050005,0x00000010,0x74615341, + 0x66283146,0x00003b31,0x00030005,0x0000000f,0x00000078,0x00070005,0x00000016,0x6e694d41, + 0x28314633,0x663b3166,0x31663b31,0x0000003b,0x00030005,0x00000013,0x00000061,0x00030005, + 0x00000014,0x00000062,0x00030005,0x00000015,0x00000063,0x00070005,0x0000001b,0x78614d41, + 0x28314633,0x663b3166,0x31663b31,0x0000003b,0x00030005,0x00000018,0x00000061,0x00030005, + 0x00000019,0x00000062,0x00030005,0x0000001a,0x00000063,0x00070005,0x00000023,0x52727346, + 0x4c736163,0x4664616f,0x32697628,0x0000003b,0x00030005,0x00000022,0x00000070,0x00080005, + 0x00000029,0x52727346,0x49736163,0x7475706e,0x31662846,0x3b31663b,0x003b3166,0x00030005, + 0x00000026,0x00000072,0x00030005,0x00000027,0x00000067,0x00030005,0x00000028,0x00000062, + 0x00090005,0x00000036,0x52727346,0x46736163,0x3b316628,0x663b3166,0x75763b31,0x75763b32, + 0x00003b34,0x00040005,0x00000031,0x52786970,0x00000000,0x00040005,0x00000032,0x47786970, + 0x00000000,0x00040005,0x00000033,0x42786970,0x00000000,0x00030005,0x00000034,0x00007069, + 0x00030005,0x00000035,0x006e6f63,0x00030005,0x00000054,0x00007a73,0x00060005,0x00000058, + 0x75706e49,0x78655474,0x65727574,0x00000000,0x00030005,0x00000069,0x00007073,0x00030005, + 0x0000006e,0x00000062,0x00040005,0x00000073,0x61726170,0x0000006d,0x00030005,0x00000076, + 0x00000064,0x00040005,0x0000007a,0x61726170,0x0000006d,0x00030005,0x0000007d,0x00000065, + 0x00040005,0x0000007e,0x61726170,0x0000006d,0x00030005,0x00000082,0x00000066,0x00040005, + 0x00000086,0x61726170,0x0000006d,0x00030005,0x00000089,0x00000068,0x00040005,0x0000008d, + 0x61726170,0x0000006d,0x00030005,0x00000090,0x00005262,0x00030005,0x00000094,0x00004762, + 0x00030005,0x00000098,0x00004262,0x00030005,0x0000009c,0x00005264,0x00030005,0x0000009f, + 0x00004764,0x00030005,0x000000a2,0x00004264,0x00030005,0x000000a5,0x00005265,0x00030005, + 0x000000a8,0x00004765,0x00030005,0x000000ab,0x00004265,0x00030005,0x000000ae,0x00005266, + 0x00030005,0x000000b1,0x00004766,0x00030005,0x000000b4,0x00004266,0x00030005,0x000000b7, + 0x00005268,0x00030005,0x000000ba,0x00004768,0x00030005,0x000000bd,0x00004268,0x00040005, + 0x000000c0,0x61726170,0x0000006d,0x00040005,0x000000c2,0x61726170,0x0000006d,0x00040005, + 0x000000c4,0x61726170,0x0000006d,0x00040005,0x000000ca,0x61726170,0x0000006d,0x00040005, + 0x000000cc,0x61726170,0x0000006d,0x00040005,0x000000ce,0x61726170,0x0000006d,0x00040005, + 0x000000d4,0x61726170,0x0000006d,0x00040005,0x000000d6,0x61726170,0x0000006d,0x00040005, + 0x000000d8,0x61726170,0x0000006d,0x00040005,0x000000de,0x61726170,0x0000006d,0x00040005, + 0x000000e0,0x61726170,0x0000006d,0x00040005,0x000000e2,0x61726170,0x0000006d,0x00040005, + 0x000000e8,0x61726170,0x0000006d,0x00040005,0x000000ea,0x61726170,0x0000006d,0x00040005, + 0x000000ec,0x61726170,0x0000006d,0x00030005,0x000000f2,0x00004c62,0x00030005,0x000000fb, + 0x00004c64,0x00030005,0x00000103,0x00004c65,0x00030005,0x0000010b,0x00004c66,0x00030005, + 0x00000113,0x00004c68,0x00030005,0x0000011b,0x00007a6e,0x00040005,0x0000012c,0x61726170, + 0x0000006d,0x00040005,0x0000012e,0x61726170,0x0000006d,0x00040005,0x00000130,0x61726170, + 0x0000006d,0x00040005,0x00000133,0x61726170,0x0000006d,0x00040005,0x00000134,0x61726170, + 0x0000006d,0x00040005,0x00000136,0x61726170,0x0000006d,0x00040005,0x00000139,0x61726170, + 0x0000006d,0x00040005,0x0000013b,0x61726170,0x0000006d,0x00040005,0x0000013d,0x61726170, + 0x0000006d,0x00040005,0x00000140,0x61726170,0x0000006d,0x00040005,0x00000141,0x61726170, + 0x0000006d,0x00040005,0x00000143,0x61726170,0x0000006d,0x00040005,0x00000147,0x61726170, + 0x0000006d,0x00040005,0x0000014a,0x61726170,0x0000006d,0x00040005,0x00000150,0x52346e6d, + 0x00000000,0x00040005,0x00000151,0x61726170,0x0000006d,0x00040005,0x00000153,0x61726170, + 0x0000006d,0x00040005,0x00000155,0x61726170,0x0000006d,0x00040005,0x0000015a,0x47346e6d, + 0x00000000,0x00040005,0x0000015b,0x61726170,0x0000006d,0x00040005,0x0000015d,0x61726170, + 0x0000006d,0x00040005,0x0000015f,0x61726170,0x0000006d,0x00040005,0x00000164,0x42346e6d, + 0x00000000,0x00040005,0x00000165,0x61726170,0x0000006d,0x00040005,0x00000167,0x61726170, + 0x0000006d,0x00040005,0x00000169,0x61726170,0x0000006d,0x00040005,0x0000016e,0x5234786d, + 0x00000000,0x00040005,0x0000016f,0x61726170,0x0000006d,0x00040005,0x00000171,0x61726170, + 0x0000006d,0x00040005,0x00000173,0x61726170,0x0000006d,0x00040005,0x00000178,0x4734786d, + 0x00000000,0x00040005,0x00000179,0x61726170,0x0000006d,0x00040005,0x0000017b,0x61726170, + 0x0000006d,0x00040005,0x0000017d,0x61726170,0x0000006d,0x00040005,0x00000182,0x4234786d, + 0x00000000,0x00040005,0x00000183,0x61726170,0x0000006d,0x00040005,0x00000185,0x61726170, + 0x0000006d,0x00040005,0x00000187,0x61726170,0x0000006d,0x00040005,0x0000018e,0x6b616570, + 0x00000043,0x00040005,0x00000191,0x4d746968,0x00526e69,0x00040005,0x00000198,0x61726170, + 0x0000006d,0x00040005,0x0000019b,0x4d746968,0x00476e69,0x00040005,0x000001a1,0x61726170, + 0x0000006d,0x00040005,0x000001a4,0x4d746968,0x00426e69,0x00040005,0x000001aa,0x61726170, + 0x0000006d,0x00040005,0x000001ad,0x4d746968,0x00527861,0x00040005,0x000001b9,0x61726170, + 0x0000006d,0x00040005,0x000001bc,0x4d746968,0x00477861,0x00040005,0x000001c8,0x61726170, + 0x0000006d,0x00040005,0x000001cb,0x4d746968,0x00427861,0x00040005,0x000001d7,0x61726170, + 0x0000006d,0x00040005,0x000001da,0x65626f6c,0x00000052,0x00040005,0x000001df,0x65626f6c, + 0x00000047,0x00040005,0x000001e4,0x65626f6c,0x00000042,0x00040005,0x000001e9,0x65626f6c, + 0x00000000,0x00040005,0x000001eb,0x61726170,0x0000006d,0x00040005,0x000001ed,0x61726170, + 0x0000006d,0x00040005,0x000001ef,0x61726170,0x0000006d,0x00040005,0x000001f9,0x4c706372, + 0x00000000,0x00040005,0x000001fd,0x61726170,0x0000006d,0x00030005,0x00000239,0x00007069, + 0x00060005,0x0000023b,0x67617266,0x43786554,0x64726f6f,0x00000000,0x00030005,0x0000023d, + 0x00004350,0x00040006,0x0000023d,0x00000000,0x0063646e,0x00040006,0x0000023d,0x00000001, + 0x006e6f63,0x00060006,0x0000023d,0x00000002,0x7074756f,0x69537475,0x0000657a,0x00030005, + 0x0000023f,0x00006370,0x00030005,0x00000246,0x00000063,0x00040005,0x00000247,0x61726170, + 0x0000006d,0x00040005,0x00000248,0x61726170,0x0000006d,0x00040005,0x00000249,0x61726170, + 0x0000006d,0x00040005,0x0000024a,0x61726170,0x0000006d,0x00040005,0x0000024c,0x61726170, + 0x0000006d,0x00050005,0x00000258,0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x00000058, + 0x00000021,0x00000000,0x00040047,0x00000058,0x00000022,0x00000000,0x00040047,0x0000023b, + 0x0000001e,0x00000000,0x00030047,0x0000023d,0x00000002,0x00050048,0x0000023d,0x00000000, + 0x00000023,0x00000000,0x00050048,0x0000023d,0x00000001,0x00000023,0x00000010,0x00050048, + 0x0000023d,0x00000002,0x00000023,0x00000020,0x00040047,0x00000258,0x0000001e,0x00000000, + 0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020, + 0x00040020,0x00000007,0x00000007,0x00000006,0x00040021,0x00000008,0x00000006,0x00000007, + 0x00060021,0x00000012,0x00000006,0x00000007,0x00000007,0x00000007,0x00040015,0x0000001d, + 0x00000020,0x00000001,0x00040017,0x0000001e,0x0000001d,0x00000002,0x00040020,0x0000001f, + 0x00000007,0x0000001e,0x00040017,0x00000020,0x00000006,0x00000004,0x00040021,0x00000021, + 0x00000020,0x0000001f,0x00060021,0x00000025,0x00000002,0x00000007,0x00000007,0x00000007, + 0x00040015,0x0000002b,0x00000020,0x00000000,0x00040017,0x0000002c,0x0000002b,0x00000002, + 0x00040020,0x0000002d,0x00000007,0x0000002c,0x00040017,0x0000002e,0x0000002b,0x00000004, + 0x00040020,0x0000002f,0x00000007,0x0000002e,0x00080021,0x00000030,0x00000002,0x00000007, + 0x00000007,0x00000007,0x0000002d,0x0000002f,0x0004002b,0x00000006,0x00000038,0x3f800000, + 0x0004002b,0x00000006,0x00000042,0x00000000,0x00090019,0x00000055,0x00000006,0x00000001, + 0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x00000056,0x00000055, + 0x00040020,0x00000057,0x00000000,0x00000056,0x0004003b,0x00000057,0x00000058,0x00000000, + 0x0004002b,0x0000001d,0x0000005a,0x00000000,0x0005002c,0x0000001e,0x0000005f,0x0000005a, + 0x0000005a,0x0004002b,0x0000001d,0x00000061,0x00000001,0x0005002c,0x0000001e,0x00000062, + 0x00000061,0x00000061,0x00040017,0x0000006c,0x00000006,0x00000003,0x00040020,0x0000006d, + 0x00000007,0x0000006c,0x0004002b,0x0000001d,0x00000070,0xffffffff,0x0005002c,0x0000001e, + 0x00000071,0x0000005a,0x00000070,0x0005002c,0x0000001e,0x00000078,0x00000070,0x0000005a, + 0x0005002c,0x0000001e,0x00000084,0x00000061,0x0000005a,0x0005002c,0x0000001e,0x0000008b, + 0x0000005a,0x00000061,0x0004002b,0x0000002b,0x00000091,0x00000000,0x0004002b,0x0000002b, + 0x00000095,0x00000001,0x0004002b,0x0000002b,0x00000099,0x00000002,0x0004002b,0x00000006, + 0x000000f4,0x3f000000,0x0004002b,0x00000006,0x0000011c,0x3e800000,0x0004002b,0x00000006, + 0x0000014c,0xbf000000,0x00040017,0x0000018c,0x00000006,0x00000002,0x00040020,0x0000018d, + 0x00000007,0x0000018c,0x0004002b,0x00000006,0x0000018f,0xc0800000,0x0005002c,0x0000018c, + 0x00000190,0x00000038,0x0000018f,0x0004002b,0x00000006,0x00000195,0x40800000,0x0004002b, + 0x00000006,0x000001ea,0xbe400000,0x00040020,0x000001f4,0x00000007,0x0000002b,0x00040020, + 0x0000023a,0x00000001,0x0000018c,0x0004003b,0x0000023a,0x0000023b,0x00000001,0x0005001e, + 0x0000023d,0x00000020,0x0000002e,0x0000018c,0x00040020,0x0000023e,0x00000009,0x0000023d, + 0x0004003b,0x0000023e,0x0000023f,0x00000009,0x0004002b,0x0000001d,0x00000240,0x00000002, + 0x00040020,0x00000241,0x00000009,0x0000018c,0x00040020,0x0000024d,0x00000009,0x0000002e, + 0x00040020,0x00000257,0x00000003,0x00000020,0x0004003b,0x00000257,0x00000258,0x00000003, + 0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b, + 0x0000002d,0x00000239,0x00000007,0x0004003b,0x0000006d,0x00000246,0x00000007,0x0004003b, + 0x00000007,0x00000247,0x00000007,0x0004003b,0x00000007,0x00000248,0x00000007,0x0004003b, + 0x00000007,0x00000249,0x00000007,0x0004003b,0x0000002d,0x0000024a,0x00000007,0x0004003b, + 0x0000002f,0x0000024c,0x00000007,0x0004003d,0x0000018c,0x0000023c,0x0000023b,0x00050041, + 0x00000241,0x00000242,0x0000023f,0x00000240,0x0004003d,0x0000018c,0x00000243,0x00000242, + 0x00050085,0x0000018c,0x00000244,0x0000023c,0x00000243,0x0004006d,0x0000002c,0x00000245, + 0x00000244,0x0003003e,0x00000239,0x00000245,0x0004003d,0x0000002c,0x0000024b,0x00000239, + 0x0003003e,0x0000024a,0x0000024b,0x00050041,0x0000024d,0x0000024e,0x0000023f,0x00000061, + 0x0004003d,0x0000002e,0x0000024f,0x0000024e,0x0003003e,0x0000024c,0x0000024f,0x00090039, + 0x00000002,0x00000250,0x00000036,0x00000247,0x00000248,0x00000249,0x0000024a,0x0000024c, + 0x0004003d,0x00000006,0x00000251,0x00000247,0x00050041,0x00000007,0x00000252,0x00000246, + 0x00000091,0x0003003e,0x00000252,0x00000251,0x0004003d,0x00000006,0x00000253,0x00000248, + 0x00050041,0x00000007,0x00000254,0x00000246,0x00000095,0x0003003e,0x00000254,0x00000253, + 0x0004003d,0x00000006,0x00000255,0x00000249,0x00050041,0x00000007,0x00000256,0x00000246, + 0x00000099,0x0003003e,0x00000256,0x00000255,0x0004003d,0x0000006c,0x00000259,0x00000246, + 0x00050051,0x00000006,0x0000025a,0x00000259,0x00000000,0x00050051,0x00000006,0x0000025b, + 0x00000259,0x00000001,0x00050051,0x00000006,0x0000025c,0x00000259,0x00000002,0x00070050, + 0x00000020,0x0000025d,0x0000025a,0x0000025b,0x0000025c,0x00000038,0x0003003e,0x00000258, + 0x0000025d,0x000100fd,0x00010038,0x00050036,0x00000006,0x0000000a,0x00000000,0x00000008, + 0x00030037,0x00000007,0x00000009,0x000200f8,0x0000000b,0x0004003d,0x00000006,0x00000039, + 0x00000009,0x00050088,0x00000006,0x0000003a,0x00000038,0x00000039,0x000200fe,0x0000003a, + 0x00010038,0x00050036,0x00000006,0x0000000d,0x00000000,0x00000008,0x00030037,0x00000007, + 0x0000000c,0x000200f8,0x0000000e,0x0004003d,0x00000006,0x0000003d,0x0000000c,0x00050088, + 0x00000006,0x0000003e,0x00000038,0x0000003d,0x000200fe,0x0000003e,0x00010038,0x00050036, + 0x00000006,0x00000010,0x00000000,0x00000008,0x00030037,0x00000007,0x0000000f,0x000200f8, + 0x00000011,0x0004003d,0x00000006,0x00000041,0x0000000f,0x0008000c,0x00000006,0x00000043, + 0x00000001,0x0000002b,0x00000041,0x00000042,0x00000038,0x000200fe,0x00000043,0x00010038, + 0x00050036,0x00000006,0x00000016,0x00000000,0x00000012,0x00030037,0x00000007,0x00000013, + 0x00030037,0x00000007,0x00000014,0x00030037,0x00000007,0x00000015,0x000200f8,0x00000017, + 0x0004003d,0x00000006,0x00000046,0x00000013,0x0004003d,0x00000006,0x00000047,0x00000014, + 0x0004003d,0x00000006,0x00000048,0x00000015,0x0007000c,0x00000006,0x00000049,0x00000001, + 0x00000025,0x00000047,0x00000048,0x0007000c,0x00000006,0x0000004a,0x00000001,0x00000025, + 0x00000046,0x00000049,0x000200fe,0x0000004a,0x00010038,0x00050036,0x00000006,0x0000001b, + 0x00000000,0x00000012,0x00030037,0x00000007,0x00000018,0x00030037,0x00000007,0x00000019, + 0x00030037,0x00000007,0x0000001a,0x000200f8,0x0000001c,0x0004003d,0x00000006,0x0000004d, + 0x00000018,0x0004003d,0x00000006,0x0000004e,0x00000019,0x0004003d,0x00000006,0x0000004f, + 0x0000001a,0x0007000c,0x00000006,0x00000050,0x00000001,0x00000028,0x0000004e,0x0000004f, + 0x0007000c,0x00000006,0x00000051,0x00000001,0x00000028,0x0000004d,0x00000050,0x000200fe, + 0x00000051,0x00010038,0x00050036,0x00000020,0x00000023,0x00000000,0x00000021,0x00030037, + 0x0000001f,0x00000022,0x000200f8,0x00000024,0x0004003b,0x0000001f,0x00000054,0x00000007, + 0x0004003d,0x00000056,0x00000059,0x00000058,0x00040064,0x00000055,0x0000005b,0x00000059, + 0x00050067,0x0000001e,0x0000005c,0x0000005b,0x0000005a,0x0003003e,0x00000054,0x0000005c, + 0x0004003d,0x00000056,0x0000005d,0x00000058,0x0004003d,0x0000001e,0x0000005e,0x00000022, + 0x0004003d,0x0000001e,0x00000060,0x00000054,0x00050082,0x0000001e,0x00000063,0x00000060, + 0x00000062,0x0008000c,0x0000001e,0x00000064,0x00000001,0x0000002d,0x0000005e,0x0000005f, + 0x00000063,0x00040064,0x00000055,0x00000065,0x0000005d,0x0007005f,0x00000020,0x00000066, + 0x00000065,0x00000064,0x00000002,0x0000005a,0x000200fe,0x00000066,0x00010038,0x00050036, + 0x00000002,0x00000029,0x00000000,0x00000025,0x00030037,0x00000007,0x00000026,0x00030037, + 0x00000007,0x00000027,0x00030037,0x00000007,0x00000028,0x000200f8,0x0000002a,0x000100fd, + 0x00010038,0x00050036,0x00000002,0x00000036,0x00000000,0x00000030,0x00030037,0x00000007, + 0x00000031,0x00030037,0x00000007,0x00000032,0x00030037,0x00000007,0x00000033,0x00030037, + 0x0000002d,0x00000034,0x00030037,0x0000002f,0x00000035,0x000200f8,0x00000037,0x0004003b, + 0x0000001f,0x00000069,0x00000007,0x0004003b,0x0000006d,0x0000006e,0x00000007,0x0004003b, + 0x0000001f,0x00000073,0x00000007,0x0004003b,0x0000006d,0x00000076,0x00000007,0x0004003b, + 0x0000001f,0x0000007a,0x00000007,0x0004003b,0x0000006d,0x0000007d,0x00000007,0x0004003b, + 0x0000001f,0x0000007e,0x00000007,0x0004003b,0x0000006d,0x00000082,0x00000007,0x0004003b, + 0x0000001f,0x00000086,0x00000007,0x0004003b,0x0000006d,0x00000089,0x00000007,0x0004003b, + 0x0000001f,0x0000008d,0x00000007,0x0004003b,0x00000007,0x00000090,0x00000007,0x0004003b, + 0x00000007,0x00000094,0x00000007,0x0004003b,0x00000007,0x00000098,0x00000007,0x0004003b, + 0x00000007,0x0000009c,0x00000007,0x0004003b,0x00000007,0x0000009f,0x00000007,0x0004003b, + 0x00000007,0x000000a2,0x00000007,0x0004003b,0x00000007,0x000000a5,0x00000007,0x0004003b, + 0x00000007,0x000000a8,0x00000007,0x0004003b,0x00000007,0x000000ab,0x00000007,0x0004003b, + 0x00000007,0x000000ae,0x00000007,0x0004003b,0x00000007,0x000000b1,0x00000007,0x0004003b, + 0x00000007,0x000000b4,0x00000007,0x0004003b,0x00000007,0x000000b7,0x00000007,0x0004003b, + 0x00000007,0x000000ba,0x00000007,0x0004003b,0x00000007,0x000000bd,0x00000007,0x0004003b, + 0x00000007,0x000000c0,0x00000007,0x0004003b,0x00000007,0x000000c2,0x00000007,0x0004003b, + 0x00000007,0x000000c4,0x00000007,0x0004003b,0x00000007,0x000000ca,0x00000007,0x0004003b, + 0x00000007,0x000000cc,0x00000007,0x0004003b,0x00000007,0x000000ce,0x00000007,0x0004003b, + 0x00000007,0x000000d4,0x00000007,0x0004003b,0x00000007,0x000000d6,0x00000007,0x0004003b, + 0x00000007,0x000000d8,0x00000007,0x0004003b,0x00000007,0x000000de,0x00000007,0x0004003b, + 0x00000007,0x000000e0,0x00000007,0x0004003b,0x00000007,0x000000e2,0x00000007,0x0004003b, + 0x00000007,0x000000e8,0x00000007,0x0004003b,0x00000007,0x000000ea,0x00000007,0x0004003b, + 0x00000007,0x000000ec,0x00000007,0x0004003b,0x00000007,0x000000f2,0x00000007,0x0004003b, + 0x00000007,0x000000fb,0x00000007,0x0004003b,0x00000007,0x00000103,0x00000007,0x0004003b, + 0x00000007,0x0000010b,0x00000007,0x0004003b,0x00000007,0x00000113,0x00000007,0x0004003b, + 0x00000007,0x0000011b,0x00000007,0x0004003b,0x00000007,0x0000012c,0x00000007,0x0004003b, + 0x00000007,0x0000012e,0x00000007,0x0004003b,0x00000007,0x00000130,0x00000007,0x0004003b, + 0x00000007,0x00000133,0x00000007,0x0004003b,0x00000007,0x00000134,0x00000007,0x0004003b, + 0x00000007,0x00000136,0x00000007,0x0004003b,0x00000007,0x00000139,0x00000007,0x0004003b, + 0x00000007,0x0000013b,0x00000007,0x0004003b,0x00000007,0x0000013d,0x00000007,0x0004003b, + 0x00000007,0x00000140,0x00000007,0x0004003b,0x00000007,0x00000141,0x00000007,0x0004003b, + 0x00000007,0x00000143,0x00000007,0x0004003b,0x00000007,0x00000147,0x00000007,0x0004003b, + 0x00000007,0x0000014a,0x00000007,0x0004003b,0x00000007,0x00000150,0x00000007,0x0004003b, + 0x00000007,0x00000151,0x00000007,0x0004003b,0x00000007,0x00000153,0x00000007,0x0004003b, + 0x00000007,0x00000155,0x00000007,0x0004003b,0x00000007,0x0000015a,0x00000007,0x0004003b, + 0x00000007,0x0000015b,0x00000007,0x0004003b,0x00000007,0x0000015d,0x00000007,0x0004003b, + 0x00000007,0x0000015f,0x00000007,0x0004003b,0x00000007,0x00000164,0x00000007,0x0004003b, + 0x00000007,0x00000165,0x00000007,0x0004003b,0x00000007,0x00000167,0x00000007,0x0004003b, + 0x00000007,0x00000169,0x00000007,0x0004003b,0x00000007,0x0000016e,0x00000007,0x0004003b, + 0x00000007,0x0000016f,0x00000007,0x0004003b,0x00000007,0x00000171,0x00000007,0x0004003b, + 0x00000007,0x00000173,0x00000007,0x0004003b,0x00000007,0x00000178,0x00000007,0x0004003b, + 0x00000007,0x00000179,0x00000007,0x0004003b,0x00000007,0x0000017b,0x00000007,0x0004003b, + 0x00000007,0x0000017d,0x00000007,0x0004003b,0x00000007,0x00000182,0x00000007,0x0004003b, + 0x00000007,0x00000183,0x00000007,0x0004003b,0x00000007,0x00000185,0x00000007,0x0004003b, + 0x00000007,0x00000187,0x00000007,0x0004003b,0x0000018d,0x0000018e,0x00000007,0x0004003b, + 0x00000007,0x00000191,0x00000007,0x0004003b,0x00000007,0x00000198,0x00000007,0x0004003b, + 0x00000007,0x0000019b,0x00000007,0x0004003b,0x00000007,0x000001a1,0x00000007,0x0004003b, + 0x00000007,0x000001a4,0x00000007,0x0004003b,0x00000007,0x000001aa,0x00000007,0x0004003b, + 0x00000007,0x000001ad,0x00000007,0x0004003b,0x00000007,0x000001b9,0x00000007,0x0004003b, + 0x00000007,0x000001bc,0x00000007,0x0004003b,0x00000007,0x000001c8,0x00000007,0x0004003b, + 0x00000007,0x000001cb,0x00000007,0x0004003b,0x00000007,0x000001d7,0x00000007,0x0004003b, + 0x00000007,0x000001da,0x00000007,0x0004003b,0x00000007,0x000001df,0x00000007,0x0004003b, + 0x00000007,0x000001e4,0x00000007,0x0004003b,0x00000007,0x000001e9,0x00000007,0x0004003b, + 0x00000007,0x000001eb,0x00000007,0x0004003b,0x00000007,0x000001ed,0x00000007,0x0004003b, + 0x00000007,0x000001ef,0x00000007,0x0004003b,0x00000007,0x000001f9,0x00000007,0x0004003b, + 0x00000007,0x000001fd,0x00000007,0x0004003d,0x0000002c,0x0000006a,0x00000034,0x0004007c, + 0x0000001e,0x0000006b,0x0000006a,0x0003003e,0x00000069,0x0000006b,0x0004003d,0x0000001e, + 0x0000006f,0x00000069,0x00050080,0x0000001e,0x00000072,0x0000006f,0x00000071,0x0003003e, + 0x00000073,0x00000072,0x00050039,0x00000020,0x00000074,0x00000023,0x00000073,0x0008004f, + 0x0000006c,0x00000075,0x00000074,0x00000074,0x00000000,0x00000001,0x00000002,0x0003003e, + 0x0000006e,0x00000075,0x0004003d,0x0000001e,0x00000077,0x00000069,0x00050080,0x0000001e, + 0x00000079,0x00000077,0x00000078,0x0003003e,0x0000007a,0x00000079,0x00050039,0x00000020, + 0x0000007b,0x00000023,0x0000007a,0x0008004f,0x0000006c,0x0000007c,0x0000007b,0x0000007b, + 0x00000000,0x00000001,0x00000002,0x0003003e,0x00000076,0x0000007c,0x0004003d,0x0000001e, + 0x0000007f,0x00000069,0x0003003e,0x0000007e,0x0000007f,0x00050039,0x00000020,0x00000080, + 0x00000023,0x0000007e,0x0008004f,0x0000006c,0x00000081,0x00000080,0x00000080,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x0000007d,0x00000081,0x0004003d,0x0000001e,0x00000083, + 0x00000069,0x00050080,0x0000001e,0x00000085,0x00000083,0x00000084,0x0003003e,0x00000086, + 0x00000085,0x00050039,0x00000020,0x00000087,0x00000023,0x00000086,0x0008004f,0x0000006c, + 0x00000088,0x00000087,0x00000087,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000082, + 0x00000088,0x0004003d,0x0000001e,0x0000008a,0x00000069,0x00050080,0x0000001e,0x0000008c, + 0x0000008a,0x0000008b,0x0003003e,0x0000008d,0x0000008c,0x00050039,0x00000020,0x0000008e, + 0x00000023,0x0000008d,0x0008004f,0x0000006c,0x0000008f,0x0000008e,0x0000008e,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x00000089,0x0000008f,0x00050041,0x00000007,0x00000092, + 0x0000006e,0x00000091,0x0004003d,0x00000006,0x00000093,0x00000092,0x0003003e,0x00000090, + 0x00000093,0x00050041,0x00000007,0x00000096,0x0000006e,0x00000095,0x0004003d,0x00000006, + 0x00000097,0x00000096,0x0003003e,0x00000094,0x00000097,0x00050041,0x00000007,0x0000009a, + 0x0000006e,0x00000099,0x0004003d,0x00000006,0x0000009b,0x0000009a,0x0003003e,0x00000098, + 0x0000009b,0x00050041,0x00000007,0x0000009d,0x00000076,0x00000091,0x0004003d,0x00000006, + 0x0000009e,0x0000009d,0x0003003e,0x0000009c,0x0000009e,0x00050041,0x00000007,0x000000a0, + 0x00000076,0x00000095,0x0004003d,0x00000006,0x000000a1,0x000000a0,0x0003003e,0x0000009f, + 0x000000a1,0x00050041,0x00000007,0x000000a3,0x00000076,0x00000099,0x0004003d,0x00000006, + 0x000000a4,0x000000a3,0x0003003e,0x000000a2,0x000000a4,0x00050041,0x00000007,0x000000a6, + 0x0000007d,0x00000091,0x0004003d,0x00000006,0x000000a7,0x000000a6,0x0003003e,0x000000a5, + 0x000000a7,0x00050041,0x00000007,0x000000a9,0x0000007d,0x00000095,0x0004003d,0x00000006, + 0x000000aa,0x000000a9,0x0003003e,0x000000a8,0x000000aa,0x00050041,0x00000007,0x000000ac, + 0x0000007d,0x00000099,0x0004003d,0x00000006,0x000000ad,0x000000ac,0x0003003e,0x000000ab, + 0x000000ad,0x00050041,0x00000007,0x000000af,0x00000082,0x00000091,0x0004003d,0x00000006, + 0x000000b0,0x000000af,0x0003003e,0x000000ae,0x000000b0,0x00050041,0x00000007,0x000000b2, + 0x00000082,0x00000095,0x0004003d,0x00000006,0x000000b3,0x000000b2,0x0003003e,0x000000b1, + 0x000000b3,0x00050041,0x00000007,0x000000b5,0x00000082,0x00000099,0x0004003d,0x00000006, + 0x000000b6,0x000000b5,0x0003003e,0x000000b4,0x000000b6,0x00050041,0x00000007,0x000000b8, + 0x00000089,0x00000091,0x0004003d,0x00000006,0x000000b9,0x000000b8,0x0003003e,0x000000b7, + 0x000000b9,0x00050041,0x00000007,0x000000bb,0x00000089,0x00000095,0x0004003d,0x00000006, + 0x000000bc,0x000000bb,0x0003003e,0x000000ba,0x000000bc,0x00050041,0x00000007,0x000000be, + 0x00000089,0x00000099,0x0004003d,0x00000006,0x000000bf,0x000000be,0x0003003e,0x000000bd, + 0x000000bf,0x0004003d,0x00000006,0x000000c1,0x00000090,0x0003003e,0x000000c0,0x000000c1, + 0x0004003d,0x00000006,0x000000c3,0x00000094,0x0003003e,0x000000c2,0x000000c3,0x0004003d, + 0x00000006,0x000000c5,0x00000098,0x0003003e,0x000000c4,0x000000c5,0x00070039,0x00000002, + 0x000000c6,0x00000029,0x000000c0,0x000000c2,0x000000c4,0x0004003d,0x00000006,0x000000c7, + 0x000000c0,0x0003003e,0x00000090,0x000000c7,0x0004003d,0x00000006,0x000000c8,0x000000c2, + 0x0003003e,0x00000094,0x000000c8,0x0004003d,0x00000006,0x000000c9,0x000000c4,0x0003003e, + 0x00000098,0x000000c9,0x0004003d,0x00000006,0x000000cb,0x0000009c,0x0003003e,0x000000ca, + 0x000000cb,0x0004003d,0x00000006,0x000000cd,0x0000009f,0x0003003e,0x000000cc,0x000000cd, + 0x0004003d,0x00000006,0x000000cf,0x000000a2,0x0003003e,0x000000ce,0x000000cf,0x00070039, + 0x00000002,0x000000d0,0x00000029,0x000000ca,0x000000cc,0x000000ce,0x0004003d,0x00000006, + 0x000000d1,0x000000ca,0x0003003e,0x0000009c,0x000000d1,0x0004003d,0x00000006,0x000000d2, + 0x000000cc,0x0003003e,0x0000009f,0x000000d2,0x0004003d,0x00000006,0x000000d3,0x000000ce, + 0x0003003e,0x000000a2,0x000000d3,0x0004003d,0x00000006,0x000000d5,0x000000a5,0x0003003e, + 0x000000d4,0x000000d5,0x0004003d,0x00000006,0x000000d7,0x000000a8,0x0003003e,0x000000d6, + 0x000000d7,0x0004003d,0x00000006,0x000000d9,0x000000ab,0x0003003e,0x000000d8,0x000000d9, + 0x00070039,0x00000002,0x000000da,0x00000029,0x000000d4,0x000000d6,0x000000d8,0x0004003d, + 0x00000006,0x000000db,0x000000d4,0x0003003e,0x000000a5,0x000000db,0x0004003d,0x00000006, + 0x000000dc,0x000000d6,0x0003003e,0x000000a8,0x000000dc,0x0004003d,0x00000006,0x000000dd, + 0x000000d8,0x0003003e,0x000000ab,0x000000dd,0x0004003d,0x00000006,0x000000df,0x000000ae, + 0x0003003e,0x000000de,0x000000df,0x0004003d,0x00000006,0x000000e1,0x000000b1,0x0003003e, + 0x000000e0,0x000000e1,0x0004003d,0x00000006,0x000000e3,0x000000b4,0x0003003e,0x000000e2, + 0x000000e3,0x00070039,0x00000002,0x000000e4,0x00000029,0x000000de,0x000000e0,0x000000e2, + 0x0004003d,0x00000006,0x000000e5,0x000000de,0x0003003e,0x000000ae,0x000000e5,0x0004003d, + 0x00000006,0x000000e6,0x000000e0,0x0003003e,0x000000b1,0x000000e6,0x0004003d,0x00000006, + 0x000000e7,0x000000e2,0x0003003e,0x000000b4,0x000000e7,0x0004003d,0x00000006,0x000000e9, + 0x000000b7,0x0003003e,0x000000e8,0x000000e9,0x0004003d,0x00000006,0x000000eb,0x000000ba, + 0x0003003e,0x000000ea,0x000000eb,0x0004003d,0x00000006,0x000000ed,0x000000bd,0x0003003e, + 0x000000ec,0x000000ed,0x00070039,0x00000002,0x000000ee,0x00000029,0x000000e8,0x000000ea, + 0x000000ec,0x0004003d,0x00000006,0x000000ef,0x000000e8,0x0003003e,0x000000b7,0x000000ef, + 0x0004003d,0x00000006,0x000000f0,0x000000ea,0x0003003e,0x000000ba,0x000000f0,0x0004003d, + 0x00000006,0x000000f1,0x000000ec,0x0003003e,0x000000bd,0x000000f1,0x0004003d,0x00000006, + 0x000000f3,0x00000098,0x00050085,0x00000006,0x000000f5,0x000000f3,0x000000f4,0x0004003d, + 0x00000006,0x000000f6,0x00000090,0x00050085,0x00000006,0x000000f7,0x000000f6,0x000000f4, + 0x0004003d,0x00000006,0x000000f8,0x00000094,0x00050081,0x00000006,0x000000f9,0x000000f7, + 0x000000f8,0x00050081,0x00000006,0x000000fa,0x000000f5,0x000000f9,0x0003003e,0x000000f2, + 0x000000fa,0x0004003d,0x00000006,0x000000fc,0x000000a2,0x00050085,0x00000006,0x000000fd, + 0x000000fc,0x000000f4,0x0004003d,0x00000006,0x000000fe,0x0000009c,0x00050085,0x00000006, + 0x000000ff,0x000000fe,0x000000f4,0x0004003d,0x00000006,0x00000100,0x0000009f,0x00050081, + 0x00000006,0x00000101,0x000000ff,0x00000100,0x00050081,0x00000006,0x00000102,0x000000fd, + 0x00000101,0x0003003e,0x000000fb,0x00000102,0x0004003d,0x00000006,0x00000104,0x000000ab, + 0x00050085,0x00000006,0x00000105,0x00000104,0x000000f4,0x0004003d,0x00000006,0x00000106, + 0x000000a5,0x00050085,0x00000006,0x00000107,0x00000106,0x000000f4,0x0004003d,0x00000006, + 0x00000108,0x000000a8,0x00050081,0x00000006,0x00000109,0x00000107,0x00000108,0x00050081, + 0x00000006,0x0000010a,0x00000105,0x00000109,0x0003003e,0x00000103,0x0000010a,0x0004003d, + 0x00000006,0x0000010c,0x000000b4,0x00050085,0x00000006,0x0000010d,0x0000010c,0x000000f4, + 0x0004003d,0x00000006,0x0000010e,0x000000ae,0x00050085,0x00000006,0x0000010f,0x0000010e, + 0x000000f4,0x0004003d,0x00000006,0x00000110,0x000000b1,0x00050081,0x00000006,0x00000111, + 0x0000010f,0x00000110,0x00050081,0x00000006,0x00000112,0x0000010d,0x00000111,0x0003003e, + 0x0000010b,0x00000112,0x0004003d,0x00000006,0x00000114,0x000000bd,0x00050085,0x00000006, + 0x00000115,0x00000114,0x000000f4,0x0004003d,0x00000006,0x00000116,0x000000b7,0x00050085, + 0x00000006,0x00000117,0x00000116,0x000000f4,0x0004003d,0x00000006,0x00000118,0x000000ba, + 0x00050081,0x00000006,0x00000119,0x00000117,0x00000118,0x00050081,0x00000006,0x0000011a, + 0x00000115,0x00000119,0x0003003e,0x00000113,0x0000011a,0x0004003d,0x00000006,0x0000011d, + 0x000000f2,0x00050085,0x00000006,0x0000011e,0x0000011c,0x0000011d,0x0004003d,0x00000006, + 0x0000011f,0x000000fb,0x00050085,0x00000006,0x00000120,0x0000011c,0x0000011f,0x00050081, + 0x00000006,0x00000121,0x0000011e,0x00000120,0x0004003d,0x00000006,0x00000122,0x0000010b, + 0x00050085,0x00000006,0x00000123,0x0000011c,0x00000122,0x00050081,0x00000006,0x00000124, + 0x00000121,0x00000123,0x0004003d,0x00000006,0x00000125,0x00000113,0x00050085,0x00000006, + 0x00000126,0x0000011c,0x00000125,0x00050081,0x00000006,0x00000127,0x00000124,0x00000126, + 0x0004003d,0x00000006,0x00000128,0x00000103,0x00050083,0x00000006,0x00000129,0x00000127, + 0x00000128,0x0003003e,0x0000011b,0x00000129,0x0004003d,0x00000006,0x0000012a,0x0000011b, + 0x0006000c,0x00000006,0x0000012b,0x00000001,0x00000004,0x0000012a,0x0004003d,0x00000006, + 0x0000012d,0x000000f2,0x0003003e,0x0000012c,0x0000012d,0x0004003d,0x00000006,0x0000012f, + 0x000000fb,0x0003003e,0x0000012e,0x0000012f,0x0004003d,0x00000006,0x00000131,0x00000103, + 0x0003003e,0x00000130,0x00000131,0x00070039,0x00000006,0x00000132,0x0000001b,0x0000012c, + 0x0000012e,0x00000130,0x0003003e,0x00000133,0x00000132,0x0004003d,0x00000006,0x00000135, + 0x0000010b,0x0003003e,0x00000134,0x00000135,0x0004003d,0x00000006,0x00000137,0x00000113, + 0x0003003e,0x00000136,0x00000137,0x00070039,0x00000006,0x00000138,0x0000001b,0x00000133, + 0x00000134,0x00000136,0x0004003d,0x00000006,0x0000013a,0x000000f2,0x0003003e,0x00000139, + 0x0000013a,0x0004003d,0x00000006,0x0000013c,0x000000fb,0x0003003e,0x0000013b,0x0000013c, + 0x0004003d,0x00000006,0x0000013e,0x00000103,0x0003003e,0x0000013d,0x0000013e,0x00070039, + 0x00000006,0x0000013f,0x00000016,0x00000139,0x0000013b,0x0000013d,0x0003003e,0x00000140, + 0x0000013f,0x0004003d,0x00000006,0x00000142,0x0000010b,0x0003003e,0x00000141,0x00000142, + 0x0004003d,0x00000006,0x00000144,0x00000113,0x0003003e,0x00000143,0x00000144,0x00070039, + 0x00000006,0x00000145,0x00000016,0x00000140,0x00000141,0x00000143,0x00050083,0x00000006, + 0x00000146,0x00000138,0x00000145,0x0003003e,0x00000147,0x00000146,0x00050039,0x00000006, + 0x00000148,0x0000000d,0x00000147,0x00050085,0x00000006,0x00000149,0x0000012b,0x00000148, + 0x0003003e,0x0000014a,0x00000149,0x00050039,0x00000006,0x0000014b,0x00000010,0x0000014a, + 0x0003003e,0x0000011b,0x0000014b,0x0004003d,0x00000006,0x0000014d,0x0000011b,0x00050085, + 0x00000006,0x0000014e,0x0000014c,0x0000014d,0x00050081,0x00000006,0x0000014f,0x0000014e, + 0x00000038,0x0003003e,0x0000011b,0x0000014f,0x0004003d,0x00000006,0x00000152,0x00000090, + 0x0003003e,0x00000151,0x00000152,0x0004003d,0x00000006,0x00000154,0x0000009c,0x0003003e, + 0x00000153,0x00000154,0x0004003d,0x00000006,0x00000156,0x000000ae,0x0003003e,0x00000155, + 0x00000156,0x00070039,0x00000006,0x00000157,0x00000016,0x00000151,0x00000153,0x00000155, + 0x0004003d,0x00000006,0x00000158,0x000000b7,0x0007000c,0x00000006,0x00000159,0x00000001, + 0x00000025,0x00000157,0x00000158,0x0003003e,0x00000150,0x00000159,0x0004003d,0x00000006, + 0x0000015c,0x00000094,0x0003003e,0x0000015b,0x0000015c,0x0004003d,0x00000006,0x0000015e, + 0x0000009f,0x0003003e,0x0000015d,0x0000015e,0x0004003d,0x00000006,0x00000160,0x000000b1, + 0x0003003e,0x0000015f,0x00000160,0x00070039,0x00000006,0x00000161,0x00000016,0x0000015b, + 0x0000015d,0x0000015f,0x0004003d,0x00000006,0x00000162,0x000000ba,0x0007000c,0x00000006, + 0x00000163,0x00000001,0x00000025,0x00000161,0x00000162,0x0003003e,0x0000015a,0x00000163, + 0x0004003d,0x00000006,0x00000166,0x00000098,0x0003003e,0x00000165,0x00000166,0x0004003d, + 0x00000006,0x00000168,0x000000a2,0x0003003e,0x00000167,0x00000168,0x0004003d,0x00000006, + 0x0000016a,0x000000b4,0x0003003e,0x00000169,0x0000016a,0x00070039,0x00000006,0x0000016b, + 0x00000016,0x00000165,0x00000167,0x00000169,0x0004003d,0x00000006,0x0000016c,0x000000bd, + 0x0007000c,0x00000006,0x0000016d,0x00000001,0x00000025,0x0000016b,0x0000016c,0x0003003e, + 0x00000164,0x0000016d,0x0004003d,0x00000006,0x00000170,0x00000090,0x0003003e,0x0000016f, + 0x00000170,0x0004003d,0x00000006,0x00000172,0x0000009c,0x0003003e,0x00000171,0x00000172, + 0x0004003d,0x00000006,0x00000174,0x000000ae,0x0003003e,0x00000173,0x00000174,0x00070039, + 0x00000006,0x00000175,0x0000001b,0x0000016f,0x00000171,0x00000173,0x0004003d,0x00000006, + 0x00000176,0x000000b7,0x0007000c,0x00000006,0x00000177,0x00000001,0x00000028,0x00000175, + 0x00000176,0x0003003e,0x0000016e,0x00000177,0x0004003d,0x00000006,0x0000017a,0x00000094, + 0x0003003e,0x00000179,0x0000017a,0x0004003d,0x00000006,0x0000017c,0x0000009f,0x0003003e, + 0x0000017b,0x0000017c,0x0004003d,0x00000006,0x0000017e,0x000000b1,0x0003003e,0x0000017d, + 0x0000017e,0x00070039,0x00000006,0x0000017f,0x0000001b,0x00000179,0x0000017b,0x0000017d, + 0x0004003d,0x00000006,0x00000180,0x000000ba,0x0007000c,0x00000006,0x00000181,0x00000001, + 0x00000028,0x0000017f,0x00000180,0x0003003e,0x00000178,0x00000181,0x0004003d,0x00000006, + 0x00000184,0x00000098,0x0003003e,0x00000183,0x00000184,0x0004003d,0x00000006,0x00000186, + 0x000000a2,0x0003003e,0x00000185,0x00000186,0x0004003d,0x00000006,0x00000188,0x000000b4, + 0x0003003e,0x00000187,0x00000188,0x00070039,0x00000006,0x00000189,0x0000001b,0x00000183, + 0x00000185,0x00000187,0x0004003d,0x00000006,0x0000018a,0x000000bd,0x0007000c,0x00000006, + 0x0000018b,0x00000001,0x00000028,0x00000189,0x0000018a,0x0003003e,0x00000182,0x0000018b, + 0x0003003e,0x0000018e,0x00000190,0x0004003d,0x00000006,0x00000192,0x00000150,0x0004003d, + 0x00000006,0x00000193,0x000000a5,0x0007000c,0x00000006,0x00000194,0x00000001,0x00000025, + 0x00000192,0x00000193,0x0004003d,0x00000006,0x00000196,0x0000016e,0x00050085,0x00000006, + 0x00000197,0x00000195,0x00000196,0x0003003e,0x00000198,0x00000197,0x00050039,0x00000006, + 0x00000199,0x0000000a,0x00000198,0x00050085,0x00000006,0x0000019a,0x00000194,0x00000199, + 0x0003003e,0x00000191,0x0000019a,0x0004003d,0x00000006,0x0000019c,0x0000015a,0x0004003d, + 0x00000006,0x0000019d,0x000000a8,0x0007000c,0x00000006,0x0000019e,0x00000001,0x00000025, + 0x0000019c,0x0000019d,0x0004003d,0x00000006,0x0000019f,0x00000178,0x00050085,0x00000006, + 0x000001a0,0x00000195,0x0000019f,0x0003003e,0x000001a1,0x000001a0,0x00050039,0x00000006, + 0x000001a2,0x0000000a,0x000001a1,0x00050085,0x00000006,0x000001a3,0x0000019e,0x000001a2, + 0x0003003e,0x0000019b,0x000001a3,0x0004003d,0x00000006,0x000001a5,0x00000164,0x0004003d, + 0x00000006,0x000001a6,0x000000ab,0x0007000c,0x00000006,0x000001a7,0x00000001,0x00000025, + 0x000001a5,0x000001a6,0x0004003d,0x00000006,0x000001a8,0x00000182,0x00050085,0x00000006, + 0x000001a9,0x00000195,0x000001a8,0x0003003e,0x000001aa,0x000001a9,0x00050039,0x00000006, + 0x000001ab,0x0000000a,0x000001aa,0x00050085,0x00000006,0x000001ac,0x000001a7,0x000001ab, + 0x0003003e,0x000001a4,0x000001ac,0x00050041,0x00000007,0x000001ae,0x0000018e,0x00000091, + 0x0004003d,0x00000006,0x000001af,0x000001ae,0x0004003d,0x00000006,0x000001b0,0x0000016e, + 0x0004003d,0x00000006,0x000001b1,0x000000a5,0x0007000c,0x00000006,0x000001b2,0x00000001, + 0x00000028,0x000001b0,0x000001b1,0x00050083,0x00000006,0x000001b3,0x000001af,0x000001b2, + 0x0004003d,0x00000006,0x000001b4,0x00000150,0x00050085,0x00000006,0x000001b5,0x00000195, + 0x000001b4,0x00050041,0x00000007,0x000001b6,0x0000018e,0x00000095,0x0004003d,0x00000006, + 0x000001b7,0x000001b6,0x00050081,0x00000006,0x000001b8,0x000001b5,0x000001b7,0x0003003e, + 0x000001b9,0x000001b8,0x00050039,0x00000006,0x000001ba,0x0000000a,0x000001b9,0x00050085, + 0x00000006,0x000001bb,0x000001b3,0x000001ba,0x0003003e,0x000001ad,0x000001bb,0x00050041, + 0x00000007,0x000001bd,0x0000018e,0x00000091,0x0004003d,0x00000006,0x000001be,0x000001bd, + 0x0004003d,0x00000006,0x000001bf,0x00000178,0x0004003d,0x00000006,0x000001c0,0x000000a8, + 0x0007000c,0x00000006,0x000001c1,0x00000001,0x00000028,0x000001bf,0x000001c0,0x00050083, + 0x00000006,0x000001c2,0x000001be,0x000001c1,0x0004003d,0x00000006,0x000001c3,0x0000015a, + 0x00050085,0x00000006,0x000001c4,0x00000195,0x000001c3,0x00050041,0x00000007,0x000001c5, + 0x0000018e,0x00000095,0x0004003d,0x00000006,0x000001c6,0x000001c5,0x00050081,0x00000006, + 0x000001c7,0x000001c4,0x000001c6,0x0003003e,0x000001c8,0x000001c7,0x00050039,0x00000006, + 0x000001c9,0x0000000a,0x000001c8,0x00050085,0x00000006,0x000001ca,0x000001c2,0x000001c9, + 0x0003003e,0x000001bc,0x000001ca,0x00050041,0x00000007,0x000001cc,0x0000018e,0x00000091, + 0x0004003d,0x00000006,0x000001cd,0x000001cc,0x0004003d,0x00000006,0x000001ce,0x00000182, + 0x0004003d,0x00000006,0x000001cf,0x000000ab,0x0007000c,0x00000006,0x000001d0,0x00000001, + 0x00000028,0x000001ce,0x000001cf,0x00050083,0x00000006,0x000001d1,0x000001cd,0x000001d0, + 0x0004003d,0x00000006,0x000001d2,0x00000164,0x00050085,0x00000006,0x000001d3,0x00000195, + 0x000001d2,0x00050041,0x00000007,0x000001d4,0x0000018e,0x00000095,0x0004003d,0x00000006, + 0x000001d5,0x000001d4,0x00050081,0x00000006,0x000001d6,0x000001d3,0x000001d5,0x0003003e, + 0x000001d7,0x000001d6,0x00050039,0x00000006,0x000001d8,0x0000000a,0x000001d7,0x00050085, + 0x00000006,0x000001d9,0x000001d1,0x000001d8,0x0003003e,0x000001cb,0x000001d9,0x0004003d, + 0x00000006,0x000001db,0x00000191,0x0004007f,0x00000006,0x000001dc,0x000001db,0x0004003d, + 0x00000006,0x000001dd,0x000001ad,0x0007000c,0x00000006,0x000001de,0x00000001,0x00000028, + 0x000001dc,0x000001dd,0x0003003e,0x000001da,0x000001de,0x0004003d,0x00000006,0x000001e0, + 0x0000019b,0x0004007f,0x00000006,0x000001e1,0x000001e0,0x0004003d,0x00000006,0x000001e2, + 0x000001bc,0x0007000c,0x00000006,0x000001e3,0x00000001,0x00000028,0x000001e1,0x000001e2, + 0x0003003e,0x000001df,0x000001e3,0x0004003d,0x00000006,0x000001e5,0x000001a4,0x0004007f, + 0x00000006,0x000001e6,0x000001e5,0x0004003d,0x00000006,0x000001e7,0x000001cb,0x0007000c, + 0x00000006,0x000001e8,0x00000001,0x00000028,0x000001e6,0x000001e7,0x0003003e,0x000001e4, + 0x000001e8,0x0004003d,0x00000006,0x000001ec,0x000001da,0x0003003e,0x000001eb,0x000001ec, + 0x0004003d,0x00000006,0x000001ee,0x000001df,0x0003003e,0x000001ed,0x000001ee,0x0004003d, + 0x00000006,0x000001f0,0x000001e4,0x0003003e,0x000001ef,0x000001f0,0x00070039,0x00000006, + 0x000001f1,0x0000001b,0x000001eb,0x000001ed,0x000001ef,0x0007000c,0x00000006,0x000001f2, + 0x00000001,0x00000025,0x000001f1,0x00000042,0x0007000c,0x00000006,0x000001f3,0x00000001, + 0x00000028,0x000001ea,0x000001f2,0x00050041,0x000001f4,0x000001f5,0x00000035,0x00000091, + 0x0004003d,0x0000002b,0x000001f6,0x000001f5,0x0004007c,0x00000006,0x000001f7,0x000001f6, + 0x00050085,0x00000006,0x000001f8,0x000001f3,0x000001f7,0x0003003e,0x000001e9,0x000001f8, + 0x0004003d,0x00000006,0x000001fa,0x000001e9,0x00050085,0x00000006,0x000001fb,0x00000195, + 0x000001fa,0x00050081,0x00000006,0x000001fc,0x000001fb,0x00000038,0x0003003e,0x000001fd, + 0x000001fc,0x00050039,0x00000006,0x000001fe,0x0000000d,0x000001fd,0x0003003e,0x000001f9, + 0x000001fe,0x0004003d,0x00000006,0x000001ff,0x000001e9,0x0004003d,0x00000006,0x00000200, + 0x00000090,0x00050085,0x00000006,0x00000201,0x000001ff,0x00000200,0x0004003d,0x00000006, + 0x00000202,0x000001e9,0x0004003d,0x00000006,0x00000203,0x0000009c,0x00050085,0x00000006, + 0x00000204,0x00000202,0x00000203,0x00050081,0x00000006,0x00000205,0x00000201,0x00000204, + 0x0004003d,0x00000006,0x00000206,0x000001e9,0x0004003d,0x00000006,0x00000207,0x000000b7, + 0x00050085,0x00000006,0x00000208,0x00000206,0x00000207,0x00050081,0x00000006,0x00000209, + 0x00000205,0x00000208,0x0004003d,0x00000006,0x0000020a,0x000001e9,0x0004003d,0x00000006, + 0x0000020b,0x000000ae,0x00050085,0x00000006,0x0000020c,0x0000020a,0x0000020b,0x00050081, + 0x00000006,0x0000020d,0x00000209,0x0000020c,0x0004003d,0x00000006,0x0000020e,0x000000a5, + 0x00050081,0x00000006,0x0000020f,0x0000020d,0x0000020e,0x0004003d,0x00000006,0x00000210, + 0x000001f9,0x00050085,0x00000006,0x00000211,0x0000020f,0x00000210,0x0003003e,0x00000031, + 0x00000211,0x0004003d,0x00000006,0x00000212,0x000001e9,0x0004003d,0x00000006,0x00000213, + 0x00000094,0x00050085,0x00000006,0x00000214,0x00000212,0x00000213,0x0004003d,0x00000006, + 0x00000215,0x000001e9,0x0004003d,0x00000006,0x00000216,0x0000009f,0x00050085,0x00000006, + 0x00000217,0x00000215,0x00000216,0x00050081,0x00000006,0x00000218,0x00000214,0x00000217, + 0x0004003d,0x00000006,0x00000219,0x000001e9,0x0004003d,0x00000006,0x0000021a,0x000000ba, + 0x00050085,0x00000006,0x0000021b,0x00000219,0x0000021a,0x00050081,0x00000006,0x0000021c, + 0x00000218,0x0000021b,0x0004003d,0x00000006,0x0000021d,0x000001e9,0x0004003d,0x00000006, + 0x0000021e,0x000000b1,0x00050085,0x00000006,0x0000021f,0x0000021d,0x0000021e,0x00050081, + 0x00000006,0x00000220,0x0000021c,0x0000021f,0x0004003d,0x00000006,0x00000221,0x000000a8, + 0x00050081,0x00000006,0x00000222,0x00000220,0x00000221,0x0004003d,0x00000006,0x00000223, + 0x000001f9,0x00050085,0x00000006,0x00000224,0x00000222,0x00000223,0x0003003e,0x00000032, + 0x00000224,0x0004003d,0x00000006,0x00000225,0x000001e9,0x0004003d,0x00000006,0x00000226, + 0x00000098,0x00050085,0x00000006,0x00000227,0x00000225,0x00000226,0x0004003d,0x00000006, + 0x00000228,0x000001e9,0x0004003d,0x00000006,0x00000229,0x000000a2,0x00050085,0x00000006, + 0x0000022a,0x00000228,0x00000229,0x00050081,0x00000006,0x0000022b,0x00000227,0x0000022a, + 0x0004003d,0x00000006,0x0000022c,0x000001e9,0x0004003d,0x00000006,0x0000022d,0x000000bd, + 0x00050085,0x00000006,0x0000022e,0x0000022c,0x0000022d,0x00050081,0x00000006,0x0000022f, + 0x0000022b,0x0000022e,0x0004003d,0x00000006,0x00000230,0x000001e9,0x0004003d,0x00000006, + 0x00000231,0x000000b4,0x00050085,0x00000006,0x00000232,0x00000230,0x00000231,0x00050081, + 0x00000006,0x00000233,0x0000022f,0x00000232,0x0004003d,0x00000006,0x00000234,0x000000ab, + 0x00050081,0x00000006,0x00000235,0x00000233,0x00000234,0x0004003d,0x00000006,0x00000236, + 0x000001f9,0x00050085,0x00000006,0x00000237,0x00000235,0x00000236,0x0003003e,0x00000033, + 0x00000237,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/fxaa_frag.h b/app/src/main/cpp/waylandcomp/shaders/fxaa_frag.h new file mode 100644 index 000000000..6b7be374b --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/fxaa_frag.h @@ -0,0 +1,165 @@ + // 1116.2.0 + #pragma once +const uint32_t fxaa_code[] = { + 0x07230203,0x00010000,0x0008000b,0x000000e4,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000018,0x000000d6,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00060005,0x00000009,0x52766e69,0x6c6f7365,0x6f697475,0x0000006e,0x00030005, + 0x0000000c,0x00004350,0x00040006,0x0000000c,0x00000000,0x0063646e,0x00060006,0x0000000c, + 0x00000001,0x6f736572,0x6974756c,0x00006e6f,0x00030005,0x0000000e,0x00006370,0x00030005, + 0x00000016,0x00007675,0x00060005,0x00000018,0x67617266,0x43786554,0x64726f6f,0x00000000, + 0x00040005,0x0000001c,0x4e626772,0x00000057,0x00060005,0x00000020,0x75706e49,0x78655474, + 0x65727574,0x00000000,0x00040005,0x0000002a,0x4e626772,0x00000045,0x00040005,0x00000033, + 0x53626772,0x00000057,0x00040005,0x0000003c,0x53626772,0x00000045,0x00040005,0x00000045, + 0x4d626772,0x00000000,0x00040005,0x0000004b,0x616d756c,0x0000574e,0x00040005,0x00000052, + 0x616d756c,0x0000454e,0x00040005,0x00000055,0x616d756c,0x00005753,0x00040005,0x00000058, + 0x616d756c,0x00004553,0x00040005,0x0000005b,0x616d756c,0x0000004d,0x00040005,0x0000005e, + 0x616d756c,0x006e694d,0x00040005,0x00000068,0x616d756c,0x0078614d,0x00030005,0x00000072, + 0x00726964,0x00050005,0x00000087,0x52726964,0x63756465,0x00000065,0x00060005,0x00000095, + 0x446e696d,0x61467269,0x726f7463,0x00000000,0x00040005,0x000000ab,0x41626772,0x00000000, + 0x00040005,0x000000bd,0x42626772,0x00000000,0x00040005,0x000000d0,0x616d756c,0x00000042, + 0x00050005,0x000000d6,0x4374756f,0x726f6c6f,0x00000000,0x00030047,0x0000000c,0x00000002, + 0x00050048,0x0000000c,0x00000000,0x00000023,0x00000000,0x00050048,0x0000000c,0x00000001, + 0x00000023,0x00000010,0x00040047,0x00000018,0x0000001e,0x00000000,0x00040047,0x00000020, + 0x00000021,0x00000000,0x00040047,0x00000020,0x00000022,0x00000000,0x00040047,0x000000d6, + 0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016, + 0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,0x00040020,0x00000008, + 0x00000007,0x00000007,0x0004002b,0x00000006,0x0000000a,0x3f800000,0x00040017,0x0000000b, + 0x00000006,0x00000004,0x0004001e,0x0000000c,0x0000000b,0x00000007,0x00040020,0x0000000d, + 0x00000009,0x0000000c,0x0004003b,0x0000000d,0x0000000e,0x00000009,0x00040015,0x0000000f, + 0x00000020,0x00000001,0x0004002b,0x0000000f,0x00000010,0x00000001,0x00040020,0x00000011, + 0x00000009,0x00000007,0x00040020,0x00000017,0x00000001,0x00000007,0x0004003b,0x00000017, + 0x00000018,0x00000001,0x00040017,0x0000001a,0x00000006,0x00000003,0x00040020,0x0000001b, + 0x00000007,0x0000001a,0x00090019,0x0000001d,0x00000006,0x00000001,0x00000000,0x00000000, + 0x00000000,0x00000001,0x00000000,0x0003001b,0x0000001e,0x0000001d,0x00040020,0x0000001f, + 0x00000000,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000000,0x0004002b,0x00000006, + 0x00000023,0xbf800000,0x0005002c,0x00000007,0x00000024,0x00000023,0x00000023,0x0005002c, + 0x00000007,0x0000002d,0x0000000a,0x00000023,0x0005002c,0x00000007,0x00000036,0x00000023, + 0x0000000a,0x0005002c,0x00000007,0x0000003f,0x0000000a,0x0000000a,0x00040020,0x0000004a, + 0x00000007,0x00000006,0x0004002b,0x00000006,0x0000004d,0x3e991687,0x0004002b,0x00000006, + 0x0000004e,0x3f1645a2,0x0004002b,0x00000006,0x0000004f,0x3de978d5,0x0006002c,0x0000001a, + 0x00000050,0x0000004d,0x0000004e,0x0000004f,0x00040015,0x0000007b,0x00000020,0x00000000, + 0x0004002b,0x0000007b,0x0000007c,0x00000000,0x0004002b,0x0000007b,0x00000085,0x00000001, + 0x0004002b,0x00000006,0x0000008f,0x3e800000,0x0004002b,0x00000006,0x00000091,0x3e000000, + 0x0004002b,0x00000006,0x00000093,0x3c000000,0x0004002b,0x00000006,0x000000a3,0xc1000000, + 0x0005002c,0x00000007,0x000000a4,0x000000a3,0x000000a3,0x0004002b,0x00000006,0x000000a5, + 0x41000000,0x0005002c,0x00000007,0x000000a6,0x000000a5,0x000000a5,0x00040020,0x000000aa, + 0x00000007,0x0000000b,0x0004002b,0x00000006,0x000000ac,0x3f000000,0x0004002b,0x00000006, + 0x000000b0,0xbe2aaaab,0x0004002b,0x00000006,0x000000b7,0x3e2aaaab,0x0004002b,0x00000006, + 0x000000c3,0xbf000000,0x0004002b,0x00000006,0x000000d2,0x00000000,0x0007002c,0x0000000b, + 0x000000d3,0x0000004d,0x0000004e,0x0000004f,0x000000d2,0x00040020,0x000000d5,0x00000003, + 0x0000000b,0x0004003b,0x000000d5,0x000000d6,0x00000003,0x00020014,0x000000d9,0x00040017, + 0x000000e1,0x000000d9,0x00000004,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003, + 0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003b,0x00000008, + 0x00000016,0x00000007,0x0004003b,0x0000001b,0x0000001c,0x00000007,0x0004003b,0x0000001b, + 0x0000002a,0x00000007,0x0004003b,0x0000001b,0x00000033,0x00000007,0x0004003b,0x0000001b, + 0x0000003c,0x00000007,0x0004003b,0x0000001b,0x00000045,0x00000007,0x0004003b,0x0000004a, + 0x0000004b,0x00000007,0x0004003b,0x0000004a,0x00000052,0x00000007,0x0004003b,0x0000004a, + 0x00000055,0x00000007,0x0004003b,0x0000004a,0x00000058,0x00000007,0x0004003b,0x0000004a, + 0x0000005b,0x00000007,0x0004003b,0x0000004a,0x0000005e,0x00000007,0x0004003b,0x0000004a, + 0x00000068,0x00000007,0x0004003b,0x00000008,0x00000072,0x00000007,0x0004003b,0x0000004a, + 0x00000087,0x00000007,0x0004003b,0x0000004a,0x00000095,0x00000007,0x0004003b,0x000000aa, + 0x000000ab,0x00000007,0x0004003b,0x000000aa,0x000000bd,0x00000007,0x0004003b,0x0000004a, + 0x000000d0,0x00000007,0x00050041,0x00000011,0x00000012,0x0000000e,0x00000010,0x0004003d, + 0x00000007,0x00000013,0x00000012,0x00050050,0x00000007,0x00000014,0x0000000a,0x0000000a, + 0x00050088,0x00000007,0x00000015,0x00000014,0x00000013,0x0003003e,0x00000009,0x00000015, + 0x0004003d,0x00000007,0x00000019,0x00000018,0x0003003e,0x00000016,0x00000019,0x0004003d, + 0x0000001e,0x00000021,0x00000020,0x0004003d,0x00000007,0x00000022,0x00000016,0x0004003d, + 0x00000007,0x00000025,0x00000009,0x00050085,0x00000007,0x00000026,0x00000024,0x00000025, + 0x00050081,0x00000007,0x00000027,0x00000022,0x00000026,0x00050057,0x0000000b,0x00000028, + 0x00000021,0x00000027,0x0008004f,0x0000001a,0x00000029,0x00000028,0x00000028,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x0000001c,0x00000029,0x0004003d,0x0000001e,0x0000002b, + 0x00000020,0x0004003d,0x00000007,0x0000002c,0x00000016,0x0004003d,0x00000007,0x0000002e, + 0x00000009,0x00050085,0x00000007,0x0000002f,0x0000002d,0x0000002e,0x00050081,0x00000007, + 0x00000030,0x0000002c,0x0000002f,0x00050057,0x0000000b,0x00000031,0x0000002b,0x00000030, + 0x0008004f,0x0000001a,0x00000032,0x00000031,0x00000031,0x00000000,0x00000001,0x00000002, + 0x0003003e,0x0000002a,0x00000032,0x0004003d,0x0000001e,0x00000034,0x00000020,0x0004003d, + 0x00000007,0x00000035,0x00000016,0x0004003d,0x00000007,0x00000037,0x00000009,0x00050085, + 0x00000007,0x00000038,0x00000036,0x00000037,0x00050081,0x00000007,0x00000039,0x00000035, + 0x00000038,0x00050057,0x0000000b,0x0000003a,0x00000034,0x00000039,0x0008004f,0x0000001a, + 0x0000003b,0x0000003a,0x0000003a,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000033, + 0x0000003b,0x0004003d,0x0000001e,0x0000003d,0x00000020,0x0004003d,0x00000007,0x0000003e, + 0x00000016,0x0004003d,0x00000007,0x00000040,0x00000009,0x00050085,0x00000007,0x00000041, + 0x0000003f,0x00000040,0x00050081,0x00000007,0x00000042,0x0000003e,0x00000041,0x00050057, + 0x0000000b,0x00000043,0x0000003d,0x00000042,0x0008004f,0x0000001a,0x00000044,0x00000043, + 0x00000043,0x00000000,0x00000001,0x00000002,0x0003003e,0x0000003c,0x00000044,0x0004003d, + 0x0000001e,0x00000046,0x00000020,0x0004003d,0x00000007,0x00000047,0x00000016,0x00050057, + 0x0000000b,0x00000048,0x00000046,0x00000047,0x0008004f,0x0000001a,0x00000049,0x00000048, + 0x00000048,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000045,0x00000049,0x0004003d, + 0x0000001a,0x0000004c,0x0000001c,0x00050094,0x00000006,0x00000051,0x0000004c,0x00000050, + 0x0003003e,0x0000004b,0x00000051,0x0004003d,0x0000001a,0x00000053,0x0000002a,0x00050094, + 0x00000006,0x00000054,0x00000053,0x00000050,0x0003003e,0x00000052,0x00000054,0x0004003d, + 0x0000001a,0x00000056,0x00000033,0x00050094,0x00000006,0x00000057,0x00000056,0x00000050, + 0x0003003e,0x00000055,0x00000057,0x0004003d,0x0000001a,0x00000059,0x0000003c,0x00050094, + 0x00000006,0x0000005a,0x00000059,0x00000050,0x0003003e,0x00000058,0x0000005a,0x0004003d, + 0x0000001a,0x0000005c,0x00000045,0x00050094,0x00000006,0x0000005d,0x0000005c,0x00000050, + 0x0003003e,0x0000005b,0x0000005d,0x0004003d,0x00000006,0x0000005f,0x0000005b,0x0004003d, + 0x00000006,0x00000060,0x0000004b,0x0004003d,0x00000006,0x00000061,0x00000052,0x0007000c, + 0x00000006,0x00000062,0x00000001,0x00000025,0x00000060,0x00000061,0x0004003d,0x00000006, + 0x00000063,0x00000055,0x0004003d,0x00000006,0x00000064,0x00000058,0x0007000c,0x00000006, + 0x00000065,0x00000001,0x00000025,0x00000063,0x00000064,0x0007000c,0x00000006,0x00000066, + 0x00000001,0x00000025,0x00000062,0x00000065,0x0007000c,0x00000006,0x00000067,0x00000001, + 0x00000025,0x0000005f,0x00000066,0x0003003e,0x0000005e,0x00000067,0x0004003d,0x00000006, + 0x00000069,0x0000005b,0x0004003d,0x00000006,0x0000006a,0x0000004b,0x0004003d,0x00000006, + 0x0000006b,0x00000052,0x0007000c,0x00000006,0x0000006c,0x00000001,0x00000028,0x0000006a, + 0x0000006b,0x0004003d,0x00000006,0x0000006d,0x00000055,0x0004003d,0x00000006,0x0000006e, + 0x00000058,0x0007000c,0x00000006,0x0000006f,0x00000001,0x00000028,0x0000006d,0x0000006e, + 0x0007000c,0x00000006,0x00000070,0x00000001,0x00000028,0x0000006c,0x0000006f,0x0007000c, + 0x00000006,0x00000071,0x00000001,0x00000028,0x00000069,0x00000070,0x0003003e,0x00000068, + 0x00000071,0x0004003d,0x00000006,0x00000073,0x0000004b,0x0004003d,0x00000006,0x00000074, + 0x00000052,0x00050081,0x00000006,0x00000075,0x00000073,0x00000074,0x0004003d,0x00000006, + 0x00000076,0x00000055,0x0004003d,0x00000006,0x00000077,0x00000058,0x00050081,0x00000006, + 0x00000078,0x00000076,0x00000077,0x00050083,0x00000006,0x00000079,0x00000075,0x00000078, + 0x0004007f,0x00000006,0x0000007a,0x00000079,0x00050041,0x0000004a,0x0000007d,0x00000072, + 0x0000007c,0x0003003e,0x0000007d,0x0000007a,0x0004003d,0x00000006,0x0000007e,0x0000004b, + 0x0004003d,0x00000006,0x0000007f,0x00000055,0x00050081,0x00000006,0x00000080,0x0000007e, + 0x0000007f,0x0004003d,0x00000006,0x00000081,0x00000052,0x0004003d,0x00000006,0x00000082, + 0x00000058,0x00050081,0x00000006,0x00000083,0x00000081,0x00000082,0x00050083,0x00000006, + 0x00000084,0x00000080,0x00000083,0x00050041,0x0000004a,0x00000086,0x00000072,0x00000085, + 0x0003003e,0x00000086,0x00000084,0x0004003d,0x00000006,0x00000088,0x0000004b,0x0004003d, + 0x00000006,0x00000089,0x00000052,0x00050081,0x00000006,0x0000008a,0x00000088,0x00000089, + 0x0004003d,0x00000006,0x0000008b,0x00000055,0x00050081,0x00000006,0x0000008c,0x0000008a, + 0x0000008b,0x0004003d,0x00000006,0x0000008d,0x00000058,0x00050081,0x00000006,0x0000008e, + 0x0000008c,0x0000008d,0x00050085,0x00000006,0x00000090,0x0000008e,0x0000008f,0x00050085, + 0x00000006,0x00000092,0x00000090,0x00000091,0x0007000c,0x00000006,0x00000094,0x00000001, + 0x00000028,0x00000092,0x00000093,0x0003003e,0x00000087,0x00000094,0x00050041,0x0000004a, + 0x00000096,0x00000072,0x0000007c,0x0004003d,0x00000006,0x00000097,0x00000096,0x0006000c, + 0x00000006,0x00000098,0x00000001,0x00000004,0x00000097,0x00050041,0x0000004a,0x00000099, + 0x00000072,0x00000085,0x0004003d,0x00000006,0x0000009a,0x00000099,0x0006000c,0x00000006, + 0x0000009b,0x00000001,0x00000004,0x0000009a,0x0007000c,0x00000006,0x0000009c,0x00000001, + 0x00000025,0x00000098,0x0000009b,0x0004003d,0x00000006,0x0000009d,0x00000087,0x00050081, + 0x00000006,0x0000009e,0x0000009c,0x0000009d,0x00050088,0x00000006,0x0000009f,0x0000000a, + 0x0000009e,0x0003003e,0x00000095,0x0000009f,0x0004003d,0x00000007,0x000000a0,0x00000072, + 0x0004003d,0x00000006,0x000000a1,0x00000095,0x0005008e,0x00000007,0x000000a2,0x000000a0, + 0x000000a1,0x0008000c,0x00000007,0x000000a7,0x00000001,0x0000002b,0x000000a2,0x000000a4, + 0x000000a6,0x0004003d,0x00000007,0x000000a8,0x00000009,0x00050085,0x00000007,0x000000a9, + 0x000000a7,0x000000a8,0x0003003e,0x00000072,0x000000a9,0x0004003d,0x0000001e,0x000000ad, + 0x00000020,0x0004003d,0x00000007,0x000000ae,0x00000016,0x0004003d,0x00000007,0x000000af, + 0x00000072,0x0005008e,0x00000007,0x000000b1,0x000000af,0x000000b0,0x00050081,0x00000007, + 0x000000b2,0x000000ae,0x000000b1,0x00050057,0x0000000b,0x000000b3,0x000000ad,0x000000b2, + 0x0004003d,0x0000001e,0x000000b4,0x00000020,0x0004003d,0x00000007,0x000000b5,0x00000016, + 0x0004003d,0x00000007,0x000000b6,0x00000072,0x0005008e,0x00000007,0x000000b8,0x000000b6, + 0x000000b7,0x00050081,0x00000007,0x000000b9,0x000000b5,0x000000b8,0x00050057,0x0000000b, + 0x000000ba,0x000000b4,0x000000b9,0x00050081,0x0000000b,0x000000bb,0x000000b3,0x000000ba, + 0x0005008e,0x0000000b,0x000000bc,0x000000bb,0x000000ac,0x0003003e,0x000000ab,0x000000bc, + 0x0004003d,0x0000000b,0x000000be,0x000000ab,0x0005008e,0x0000000b,0x000000bf,0x000000be, + 0x000000ac,0x0004003d,0x0000001e,0x000000c0,0x00000020,0x0004003d,0x00000007,0x000000c1, + 0x00000016,0x0004003d,0x00000007,0x000000c2,0x00000072,0x0005008e,0x00000007,0x000000c4, + 0x000000c2,0x000000c3,0x00050081,0x00000007,0x000000c5,0x000000c1,0x000000c4,0x00050057, + 0x0000000b,0x000000c6,0x000000c0,0x000000c5,0x0004003d,0x0000001e,0x000000c7,0x00000020, + 0x0004003d,0x00000007,0x000000c8,0x00000016,0x0004003d,0x00000007,0x000000c9,0x00000072, + 0x0005008e,0x00000007,0x000000ca,0x000000c9,0x000000ac,0x00050081,0x00000007,0x000000cb, + 0x000000c8,0x000000ca,0x00050057,0x0000000b,0x000000cc,0x000000c7,0x000000cb,0x00050081, + 0x0000000b,0x000000cd,0x000000c6,0x000000cc,0x0005008e,0x0000000b,0x000000ce,0x000000cd, + 0x0000008f,0x00050081,0x0000000b,0x000000cf,0x000000bf,0x000000ce,0x0003003e,0x000000bd, + 0x000000cf,0x0004003d,0x0000000b,0x000000d1,0x000000bd,0x00050094,0x00000006,0x000000d4, + 0x000000d1,0x000000d3,0x0003003e,0x000000d0,0x000000d4,0x0004003d,0x00000006,0x000000d7, + 0x000000d0,0x0004003d,0x00000006,0x000000d8,0x0000005e,0x000500b8,0x000000d9,0x000000da, + 0x000000d7,0x000000d8,0x0004003d,0x00000006,0x000000db,0x000000d0,0x0004003d,0x00000006, + 0x000000dc,0x00000068,0x000500ba,0x000000d9,0x000000dd,0x000000db,0x000000dc,0x000500a6, + 0x000000d9,0x000000de,0x000000da,0x000000dd,0x0004003d,0x0000000b,0x000000df,0x000000ab, + 0x0004003d,0x0000000b,0x000000e0,0x000000bd,0x00070050,0x000000e1,0x000000e2,0x000000de, + 0x000000de,0x000000de,0x000000de,0x000600a9,0x0000000b,0x000000e3,0x000000e2,0x000000df, + 0x000000e0,0x0003003e,0x000000d6,0x000000e3,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/hdr_frag.h b/app/src/main/cpp/waylandcomp/shaders/hdr_frag.h new file mode 100644 index 000000000..37e5783aa --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/hdr_frag.h @@ -0,0 +1,174 @@ + // 1116.2.0 + #pragma once +const uint32_t hdr_code[] = { + 0x07230203,0x00010000,0x0008000b,0x000000f4,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x000000e9,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00050005,0x00000009,0x63786574,0x64726f6f,0x00000000,0x00060005,0x0000000b, + 0x67617266,0x43786554,0x64726f6f,0x00000000,0x00030005,0x0000000d,0x00007870,0x00030005, + 0x00000010,0x00004350,0x00040006,0x00000010,0x00000000,0x0063646e,0x00060006,0x00000010, + 0x00000001,0x6f736572,0x6974756c,0x00006e6f,0x00030005,0x00000012,0x00006370,0x00040005, + 0x0000001c,0x6f6c6f63,0x00000072,0x00060005,0x00000020,0x75706e49,0x78655474,0x65727574, + 0x00000000,0x00050005,0x00000025,0x6f6f6c62,0x75735f6d,0x0000316d,0x00050005,0x0000007c, + 0x6f6f6c62,0x75735f6d,0x0000326d,0x00040005,0x000000d3,0x74736964,0x00000000,0x00030005, + 0x000000d5,0x00524448,0x00040005,0x000000dd,0x6e656c62,0x00000064,0x00050005,0x000000e9, + 0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000000b,0x0000001e,0x00000000,0x00030047, + 0x00000010,0x00000002,0x00050048,0x00000010,0x00000000,0x00000023,0x00000000,0x00050048, + 0x00000010,0x00000001,0x00000023,0x00000010,0x00040047,0x00000020,0x00000021,0x00000000, + 0x00040047,0x00000020,0x00000022,0x00000000,0x00040047,0x000000e9,0x0000001e,0x00000000, + 0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020, + 0x00040017,0x00000007,0x00000006,0x00000002,0x00040020,0x00000008,0x00000007,0x00000007, + 0x00040020,0x0000000a,0x00000001,0x00000007,0x0004003b,0x0000000a,0x0000000b,0x00000001, + 0x0004002b,0x00000006,0x0000000e,0x3f800000,0x00040017,0x0000000f,0x00000006,0x00000004, + 0x0004001e,0x00000010,0x0000000f,0x00000007,0x00040020,0x00000011,0x00000009,0x00000010, + 0x0004003b,0x00000011,0x00000012,0x00000009,0x00040015,0x00000013,0x00000020,0x00000001, + 0x0004002b,0x00000013,0x00000014,0x00000001,0x00040020,0x00000015,0x00000009,0x00000007, + 0x00040017,0x0000001a,0x00000006,0x00000003,0x00040020,0x0000001b,0x00000007,0x0000001a, + 0x00090019,0x0000001d,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001, + 0x00000000,0x0003001b,0x0000001e,0x0000001d,0x00040020,0x0000001f,0x00000000,0x0000001e, + 0x0004003b,0x0000001f,0x00000020,0x00000000,0x0004002b,0x00000006,0x00000028,0x3f984189, + 0x0004002b,0x00000006,0x00000029,0xbf984189,0x0005002c,0x00000007,0x0000002a,0x00000028, + 0x00000029,0x0005002c,0x00000007,0x00000032,0x00000029,0x00000029,0x0005002c,0x00000007, + 0x0000003c,0x00000028,0x00000028,0x0005002c,0x00000007,0x00000046,0x00000029,0x00000028, + 0x0004002b,0x00000006,0x00000050,0x00000000,0x0004002b,0x00000006,0x00000051,0xbffdc28f, + 0x0005002c,0x00000007,0x00000052,0x00000050,0x00000051,0x0004002b,0x00000006,0x0000005c, + 0x3ffdc28f,0x0005002c,0x00000007,0x0000005d,0x00000050,0x0000005c,0x0005002c,0x00000007, + 0x00000067,0x00000051,0x00000050,0x0005002c,0x00000007,0x00000071,0x0000005c,0x00000050, + 0x0004002b,0x00000006,0x00000079,0x3ba3d70a,0x0004002b,0x00000006,0x0000007f,0x3fa70a3d, + 0x0004002b,0x00000006,0x00000080,0xbfa70a3d,0x0005002c,0x00000007,0x00000081,0x0000007f, + 0x00000080,0x0005002c,0x00000007,0x00000089,0x00000080,0x00000080,0x0005002c,0x00000007, + 0x00000093,0x0000007f,0x0000007f,0x0005002c,0x00000007,0x0000009d,0x00000080,0x0000007f, + 0x0004002b,0x00000006,0x000000a7,0xc00b3333,0x0005002c,0x00000007,0x000000a8,0x00000050, + 0x000000a7,0x0004002b,0x00000006,0x000000b2,0x400b3333,0x0005002c,0x00000007,0x000000b3, + 0x00000050,0x000000b2,0x0005002c,0x00000007,0x000000bd,0x000000a7,0x00000050,0x0005002c, + 0x00000007,0x000000c7,0x000000b2,0x00000050,0x0004002b,0x00000006,0x000000cf,0x3c23d70a, + 0x00040020,0x000000d2,0x00000007,0x00000006,0x0004002b,0x00000006,0x000000d4,0x3d9db22d, + 0x0004002b,0x00000006,0x000000e3,0x3fa66666,0x0006002c,0x0000001a,0x000000e4,0x000000e3, + 0x000000e3,0x000000e3,0x00040020,0x000000e8,0x00000003,0x0000000f,0x0004003b,0x000000e8, + 0x000000e9,0x00000003,0x0004002b,0x00000006,0x000000f2,0x3f4b020c,0x0004002b,0x00000006, + 0x000000f3,0x3f5eb852,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, + 0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003b,0x00000008,0x0000000d, + 0x00000007,0x0004003b,0x0000001b,0x0000001c,0x00000007,0x0004003b,0x0000001b,0x00000025, + 0x00000007,0x0004003b,0x0000001b,0x0000007c,0x00000007,0x0004003b,0x000000d2,0x000000d3, + 0x00000007,0x0004003b,0x0000001b,0x000000d5,0x00000007,0x0004003b,0x0000001b,0x000000dd, + 0x00000007,0x0004003d,0x00000007,0x0000000c,0x0000000b,0x0003003e,0x00000009,0x0000000c, + 0x00050041,0x00000015,0x00000016,0x00000012,0x00000014,0x0004003d,0x00000007,0x00000017, + 0x00000016,0x00050050,0x00000007,0x00000018,0x0000000e,0x0000000e,0x00050088,0x00000007, + 0x00000019,0x00000018,0x00000017,0x0003003e,0x0000000d,0x00000019,0x0004003d,0x0000001e, + 0x00000021,0x00000020,0x0004003d,0x00000007,0x00000022,0x00000009,0x00050057,0x0000000f, + 0x00000023,0x00000021,0x00000022,0x0008004f,0x0000001a,0x00000024,0x00000023,0x00000023, + 0x00000000,0x00000001,0x00000002,0x0003003e,0x0000001c,0x00000024,0x0004003d,0x0000001e, + 0x00000026,0x00000020,0x0004003d,0x00000007,0x00000027,0x00000009,0x0004003d,0x00000007, + 0x0000002b,0x0000000d,0x00050085,0x00000007,0x0000002c,0x0000002a,0x0000002b,0x00050081, + 0x00000007,0x0000002d,0x00000027,0x0000002c,0x00050057,0x0000000f,0x0000002e,0x00000026, + 0x0000002d,0x0008004f,0x0000001a,0x0000002f,0x0000002e,0x0000002e,0x00000000,0x00000001, + 0x00000002,0x0003003e,0x00000025,0x0000002f,0x0004003d,0x0000001e,0x00000030,0x00000020, + 0x0004003d,0x00000007,0x00000031,0x00000009,0x0004003d,0x00000007,0x00000033,0x0000000d, + 0x00050085,0x00000007,0x00000034,0x00000032,0x00000033,0x00050081,0x00000007,0x00000035, + 0x00000031,0x00000034,0x00050057,0x0000000f,0x00000036,0x00000030,0x00000035,0x0008004f, + 0x0000001a,0x00000037,0x00000036,0x00000036,0x00000000,0x00000001,0x00000002,0x0004003d, + 0x0000001a,0x00000038,0x00000025,0x00050081,0x0000001a,0x00000039,0x00000038,0x00000037, + 0x0003003e,0x00000025,0x00000039,0x0004003d,0x0000001e,0x0000003a,0x00000020,0x0004003d, + 0x00000007,0x0000003b,0x00000009,0x0004003d,0x00000007,0x0000003d,0x0000000d,0x00050085, + 0x00000007,0x0000003e,0x0000003c,0x0000003d,0x00050081,0x00000007,0x0000003f,0x0000003b, + 0x0000003e,0x00050057,0x0000000f,0x00000040,0x0000003a,0x0000003f,0x0008004f,0x0000001a, + 0x00000041,0x00000040,0x00000040,0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a, + 0x00000042,0x00000025,0x00050081,0x0000001a,0x00000043,0x00000042,0x00000041,0x0003003e, + 0x00000025,0x00000043,0x0004003d,0x0000001e,0x00000044,0x00000020,0x0004003d,0x00000007, + 0x00000045,0x00000009,0x0004003d,0x00000007,0x00000047,0x0000000d,0x00050085,0x00000007, + 0x00000048,0x00000046,0x00000047,0x00050081,0x00000007,0x00000049,0x00000045,0x00000048, + 0x00050057,0x0000000f,0x0000004a,0x00000044,0x00000049,0x0008004f,0x0000001a,0x0000004b, + 0x0000004a,0x0000004a,0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a,0x0000004c, + 0x00000025,0x00050081,0x0000001a,0x0000004d,0x0000004c,0x0000004b,0x0003003e,0x00000025, + 0x0000004d,0x0004003d,0x0000001e,0x0000004e,0x00000020,0x0004003d,0x00000007,0x0000004f, + 0x00000009,0x0004003d,0x00000007,0x00000053,0x0000000d,0x00050085,0x00000007,0x00000054, + 0x00000052,0x00000053,0x00050081,0x00000007,0x00000055,0x0000004f,0x00000054,0x00050057, + 0x0000000f,0x00000056,0x0000004e,0x00000055,0x0008004f,0x0000001a,0x00000057,0x00000056, + 0x00000056,0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a,0x00000058,0x00000025, + 0x00050081,0x0000001a,0x00000059,0x00000058,0x00000057,0x0003003e,0x00000025,0x00000059, + 0x0004003d,0x0000001e,0x0000005a,0x00000020,0x0004003d,0x00000007,0x0000005b,0x00000009, + 0x0004003d,0x00000007,0x0000005e,0x0000000d,0x00050085,0x00000007,0x0000005f,0x0000005d, + 0x0000005e,0x00050081,0x00000007,0x00000060,0x0000005b,0x0000005f,0x00050057,0x0000000f, + 0x00000061,0x0000005a,0x00000060,0x0008004f,0x0000001a,0x00000062,0x00000061,0x00000061, + 0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a,0x00000063,0x00000025,0x00050081, + 0x0000001a,0x00000064,0x00000063,0x00000062,0x0003003e,0x00000025,0x00000064,0x0004003d, + 0x0000001e,0x00000065,0x00000020,0x0004003d,0x00000007,0x00000066,0x00000009,0x0004003d, + 0x00000007,0x00000068,0x0000000d,0x00050085,0x00000007,0x00000069,0x00000067,0x00000068, + 0x00050081,0x00000007,0x0000006a,0x00000066,0x00000069,0x00050057,0x0000000f,0x0000006b, + 0x00000065,0x0000006a,0x0008004f,0x0000001a,0x0000006c,0x0000006b,0x0000006b,0x00000000, + 0x00000001,0x00000002,0x0004003d,0x0000001a,0x0000006d,0x00000025,0x00050081,0x0000001a, + 0x0000006e,0x0000006d,0x0000006c,0x0003003e,0x00000025,0x0000006e,0x0004003d,0x0000001e, + 0x0000006f,0x00000020,0x0004003d,0x00000007,0x00000070,0x00000009,0x0004003d,0x00000007, + 0x00000072,0x0000000d,0x00050085,0x00000007,0x00000073,0x00000071,0x00000072,0x00050081, + 0x00000007,0x00000074,0x00000070,0x00000073,0x00050057,0x0000000f,0x00000075,0x0000006f, + 0x00000074,0x0008004f,0x0000001a,0x00000076,0x00000075,0x00000075,0x00000000,0x00000001, + 0x00000002,0x0004003d,0x0000001a,0x00000077,0x00000025,0x00050081,0x0000001a,0x00000078, + 0x00000077,0x00000076,0x0003003e,0x00000025,0x00000078,0x0004003d,0x0000001a,0x0000007a, + 0x00000025,0x0005008e,0x0000001a,0x0000007b,0x0000007a,0x00000079,0x0003003e,0x00000025, + 0x0000007b,0x0004003d,0x0000001e,0x0000007d,0x00000020,0x0004003d,0x00000007,0x0000007e, + 0x00000009,0x0004003d,0x00000007,0x00000082,0x0000000d,0x00050085,0x00000007,0x00000083, + 0x00000081,0x00000082,0x00050081,0x00000007,0x00000084,0x0000007e,0x00000083,0x00050057, + 0x0000000f,0x00000085,0x0000007d,0x00000084,0x0008004f,0x0000001a,0x00000086,0x00000085, + 0x00000085,0x00000000,0x00000001,0x00000002,0x0003003e,0x0000007c,0x00000086,0x0004003d, + 0x0000001e,0x00000087,0x00000020,0x0004003d,0x00000007,0x00000088,0x00000009,0x0004003d, + 0x00000007,0x0000008a,0x0000000d,0x00050085,0x00000007,0x0000008b,0x00000089,0x0000008a, + 0x00050081,0x00000007,0x0000008c,0x00000088,0x0000008b,0x00050057,0x0000000f,0x0000008d, + 0x00000087,0x0000008c,0x0008004f,0x0000001a,0x0000008e,0x0000008d,0x0000008d,0x00000000, + 0x00000001,0x00000002,0x0004003d,0x0000001a,0x0000008f,0x0000007c,0x00050081,0x0000001a, + 0x00000090,0x0000008f,0x0000008e,0x0003003e,0x0000007c,0x00000090,0x0004003d,0x0000001e, + 0x00000091,0x00000020,0x0004003d,0x00000007,0x00000092,0x00000009,0x0004003d,0x00000007, + 0x00000094,0x0000000d,0x00050085,0x00000007,0x00000095,0x00000093,0x00000094,0x00050081, + 0x00000007,0x00000096,0x00000092,0x00000095,0x00050057,0x0000000f,0x00000097,0x00000091, + 0x00000096,0x0008004f,0x0000001a,0x00000098,0x00000097,0x00000097,0x00000000,0x00000001, + 0x00000002,0x0004003d,0x0000001a,0x00000099,0x0000007c,0x00050081,0x0000001a,0x0000009a, + 0x00000099,0x00000098,0x0003003e,0x0000007c,0x0000009a,0x0004003d,0x0000001e,0x0000009b, + 0x00000020,0x0004003d,0x00000007,0x0000009c,0x00000009,0x0004003d,0x00000007,0x0000009e, + 0x0000000d,0x00050085,0x00000007,0x0000009f,0x0000009d,0x0000009e,0x00050081,0x00000007, + 0x000000a0,0x0000009c,0x0000009f,0x00050057,0x0000000f,0x000000a1,0x0000009b,0x000000a0, + 0x0008004f,0x0000001a,0x000000a2,0x000000a1,0x000000a1,0x00000000,0x00000001,0x00000002, + 0x0004003d,0x0000001a,0x000000a3,0x0000007c,0x00050081,0x0000001a,0x000000a4,0x000000a3, + 0x000000a2,0x0003003e,0x0000007c,0x000000a4,0x0004003d,0x0000001e,0x000000a5,0x00000020, + 0x0004003d,0x00000007,0x000000a6,0x00000009,0x0004003d,0x00000007,0x000000a9,0x0000000d, + 0x00050085,0x00000007,0x000000aa,0x000000a8,0x000000a9,0x00050081,0x00000007,0x000000ab, + 0x000000a6,0x000000aa,0x00050057,0x0000000f,0x000000ac,0x000000a5,0x000000ab,0x0008004f, + 0x0000001a,0x000000ad,0x000000ac,0x000000ac,0x00000000,0x00000001,0x00000002,0x0004003d, + 0x0000001a,0x000000ae,0x0000007c,0x00050081,0x0000001a,0x000000af,0x000000ae,0x000000ad, + 0x0003003e,0x0000007c,0x000000af,0x0004003d,0x0000001e,0x000000b0,0x00000020,0x0004003d, + 0x00000007,0x000000b1,0x00000009,0x0004003d,0x00000007,0x000000b4,0x0000000d,0x00050085, + 0x00000007,0x000000b5,0x000000b3,0x000000b4,0x00050081,0x00000007,0x000000b6,0x000000b1, + 0x000000b5,0x00050057,0x0000000f,0x000000b7,0x000000b0,0x000000b6,0x0008004f,0x0000001a, + 0x000000b8,0x000000b7,0x000000b7,0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a, + 0x000000b9,0x0000007c,0x00050081,0x0000001a,0x000000ba,0x000000b9,0x000000b8,0x0003003e, + 0x0000007c,0x000000ba,0x0004003d,0x0000001e,0x000000bb,0x00000020,0x0004003d,0x00000007, + 0x000000bc,0x00000009,0x0004003d,0x00000007,0x000000be,0x0000000d,0x00050085,0x00000007, + 0x000000bf,0x000000bd,0x000000be,0x00050081,0x00000007,0x000000c0,0x000000bc,0x000000bf, + 0x00050057,0x0000000f,0x000000c1,0x000000bb,0x000000c0,0x0008004f,0x0000001a,0x000000c2, + 0x000000c1,0x000000c1,0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a,0x000000c3, + 0x0000007c,0x00050081,0x0000001a,0x000000c4,0x000000c3,0x000000c2,0x0003003e,0x0000007c, + 0x000000c4,0x0004003d,0x0000001e,0x000000c5,0x00000020,0x0004003d,0x00000007,0x000000c6, + 0x00000009,0x0004003d,0x00000007,0x000000c8,0x0000000d,0x00050085,0x00000007,0x000000c9, + 0x000000c7,0x000000c8,0x00050081,0x00000007,0x000000ca,0x000000c6,0x000000c9,0x00050057, + 0x0000000f,0x000000cb,0x000000c5,0x000000ca,0x0008004f,0x0000001a,0x000000cc,0x000000cb, + 0x000000cb,0x00000000,0x00000001,0x00000002,0x0004003d,0x0000001a,0x000000cd,0x0000007c, + 0x00050081,0x0000001a,0x000000ce,0x000000cd,0x000000cc,0x0003003e,0x0000007c,0x000000ce, + 0x0004003d,0x0000001a,0x000000d0,0x0000007c,0x0005008e,0x0000001a,0x000000d1,0x000000d0, + 0x000000cf,0x0003003e,0x0000007c,0x000000d1,0x0003003e,0x000000d3,0x000000d4,0x0004003d, + 0x0000001a,0x000000d6,0x0000001c,0x0004003d,0x0000001a,0x000000d7,0x0000007c,0x0004003d, + 0x0000001a,0x000000d8,0x00000025,0x00050083,0x0000001a,0x000000d9,0x000000d7,0x000000d8, + 0x00050081,0x0000001a,0x000000da,0x000000d6,0x000000d9,0x0004003d,0x00000006,0x000000db, + 0x000000d3,0x0005008e,0x0000001a,0x000000dc,0x000000da,0x000000db,0x0003003e,0x000000d5, + 0x000000dc,0x0004003d,0x0000001a,0x000000de,0x000000d5,0x0004003d,0x0000001a,0x000000df, + 0x0000001c,0x00050081,0x0000001a,0x000000e0,0x000000de,0x000000df,0x0003003e,0x000000dd, + 0x000000e0,0x0004003d,0x0000001a,0x000000e1,0x000000dd,0x0006000c,0x0000001a,0x000000e2, + 0x00000001,0x00000004,0x000000e1,0x0007000c,0x0000001a,0x000000e5,0x00000001,0x0000001a, + 0x000000e2,0x000000e4,0x0004003d,0x0000001a,0x000000e6,0x000000d5,0x00050081,0x0000001a, + 0x000000e7,0x000000e5,0x000000e6,0x0003003e,0x0000001c,0x000000e7,0x0004003d,0x0000001a, + 0x000000ea,0x0000001c,0x00060050,0x0000001a,0x000000eb,0x00000050,0x00000050,0x00000050, + 0x00060050,0x0000001a,0x000000ec,0x0000000e,0x0000000e,0x0000000e,0x0008000c,0x0000001a, + 0x000000ed,0x00000001,0x0000002b,0x000000ea,0x000000eb,0x000000ec,0x00050051,0x00000006, + 0x000000ee,0x000000ed,0x00000000,0x00050051,0x00000006,0x000000ef,0x000000ed,0x00000001, + 0x00050051,0x00000006,0x000000f0,0x000000ed,0x00000002,0x00070050,0x0000000f,0x000000f1, + 0x000000ee,0x000000ef,0x000000f0,0x0000000e,0x0003003e,0x000000e9,0x000000f1,0x000100fd, + 0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/nis_frag.h b/app/src/main/cpp/waylandcomp/shaders/nis_frag.h new file mode 100644 index 000000000..468283d7f --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/nis_frag.h @@ -0,0 +1,1222 @@ + // 1116.2.0 + #pragma once +const uint32_t nis_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000644,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000051f,0x00000637,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00050005,0x0000000b,0x59746567,0x33667628,0x0000003b,0x00030005,0x0000000a, + 0x00626772,0x000b0005,0x00000019,0x65676465,0x66287441,0x31663b31,0x3b31663b,0x663b3166, + 0x31663b31,0x3b31663b,0x663b3166,0x00003b31,0x00030005,0x00000010,0x00303070,0x00030005, + 0x00000011,0x00313070,0x00030005,0x00000012,0x00323070,0x00030005,0x00000013,0x00303170, + 0x00030005,0x00000014,0x00313170,0x00030005,0x00000015,0x00323170,0x00030005,0x00000016, + 0x00303270,0x00030005,0x00000017,0x00313270,0x00030005,0x00000018,0x00323270,0x000c0005, + 0x00000023,0x49746567,0x7265746e,0x67644570,0x70614d65,0x34667628,0x3466763b,0x3466763b, + 0x3466763b,0x3b31663b,0x003b3166,0x00030005,0x0000001d,0x00303065,0x00030005,0x0000001e, + 0x00313065,0x00030005,0x0000001f,0x00303165,0x00030005,0x00000020,0x00313165,0x00030005, + 0x00000021,0x00007866,0x00030005,0x00000022,0x00007966,0x000a0005,0x0000002f,0x636c6163, + 0x2849544c,0x663b3166,0x31663b31,0x3b31663b,0x663b3166,0x31693b31,0x0000003b,0x00030005, + 0x00000028,0x00003070,0x00030005,0x00000029,0x00003170,0x00030005,0x0000002a,0x00003270, + 0x00030005,0x0000002b,0x00003370,0x00030005,0x0000002c,0x00003470,0x00030005,0x0000002d, + 0x00003570,0x00050005,0x0000002e,0x73616870,0x6e695f65,0x00786564,0x00070005,0x00000038, + 0x6c617665,0x796c6f50,0x31662836,0x3b5d365b,0x003b3169,0x00030005,0x00000036,0x006c7870, + 0x00050005,0x00000037,0x73616870,0x6e695f65,0x00000074,0x000a0005,0x00000040,0x746c6966, + 0x6f4e7265,0x6c616d72,0x5b316628,0x365b5d36,0x31693b5d,0x3b31693b,0x00000000,0x00030005, + 0x0000003d,0x00000070,0x00050005,0x0000003e,0x73616870,0x5f785f65,0x00746e69,0x00050005, + 0x0000003f,0x73616870,0x5f795f65,0x00746e69,0x000c0005,0x00000049,0x44646461,0x69467269, + 0x7265746c,0x31662873,0x5b5d365b,0x663b5d36,0x31663b31,0x3b31693b,0x763b3169,0x003b3466, + 0x00030005,0x00000043,0x00000070,0x00060005,0x00000044,0x73616870,0x5f785f65,0x63617266, + 0x00000000,0x00060005,0x00000045,0x73616870,0x5f795f65,0x63617266,0x00000000,0x00070005, + 0x00000046,0x73616870,0x5f785f65,0x63617266,0x746e695f,0x00000000,0x00070005,0x00000047, + 0x73616870,0x5f795f65,0x63617266,0x746e695f,0x00000000,0x00030005,0x00000048,0x00000077, + 0x00030005,0x0000005e,0x00305f67,0x00040005,0x0000006b,0x35345f67,0x00000000,0x00040005, + 0x00000078,0x30395f67,0x00000000,0x00040005,0x00000085,0x33315f67,0x00000035,0x00050005, + 0x00000092,0x5f305f67,0x6d5f3039,0x00007861,0x00050005,0x00000096,0x5f305f67,0x6d5f3039, + 0x00006e69,0x00060005,0x0000009a,0x35345f67,0x3533315f,0x78616d5f,0x00000000,0x00060005, + 0x0000009e,0x35345f67,0x3533315f,0x6e696d5f,0x00000000,0x00040005,0x000000ac,0x5f305f65, + 0x00003039,0x00050005,0x000000b4,0x35345f65,0x3533315f,0x00000000,0x00040005,0x000000b8, + 0x5f305f63,0x00003039,0x00050005,0x000000c6,0x35345f63,0x3533315f,0x00000000,0x00050005, + 0x000000d2,0x5f675f63,0x30395f30,0x00000000,0x00050005,0x000000d6,0x5f675f63,0x315f3534, + 0x00003533,0x00050005,0x000000da,0x5f655f66,0x30395f30,0x00000000,0x00050005,0x000000e0, + 0x5f655f66,0x315f3534,0x00003533,0x00030005,0x000000e6,0x00003077,0x00030005,0x000000ec, + 0x00303977,0x00030005,0x000000f3,0x00353477,0x00040005,0x000000f9,0x35333177,0x00000000, + 0x00030005,0x00000107,0x00003068,0x00030005,0x0000010d,0x00003168,0x00050005,0x0000011a, + 0x656c6573,0x726f7463,0x00000000,0x00030005,0x0000011e,0x006c6573,0x00040005,0x00000123, + 0x696d5f61,0x0000006e,0x00040005,0x00000129,0x616d5f61,0x00000078,0x00040005,0x00000133, + 0x696d5f62,0x0000006e,0x00040005,0x00000139,0x616d5f62,0x00000078,0x00040005,0x0000013f, + 0x6f635f61,0x0000746e,0x00040005,0x00000143,0x6f635f62,0x0000746e,0x00050005,0x00000147, + 0x746e6f63,0x7461725f,0x00006f69,0x00030005,0x0000015f,0x00000079,0x00030005,0x00000160, + 0x00000069,0x00050005,0x00000215,0x65646e69,0x6c626178,0x00000065,0x00040005,0x00000221, + 0x73755f79,0x0000006d,0x00030005,0x00000222,0x00000069,0x00050005,0x000002b0,0x65646e69, + 0x6c626178,0x00000065,0x00040005,0x000002bb,0x63735f79,0x00656c61,0x00050005,0x000002c3, + 0x68735f79,0x6e707261,0x00737365,0x00070005,0x000002c6,0x61685367,0x74537072,0x676e6572, + 0x63536874,0x00656c61,0x00070005,0x000002c9,0x61685367,0x74537072,0x676e6572,0x694d6874, + 0x0000006e,0x00070005,0x000002cf,0x68735f79,0x6e707261,0x5f737365,0x696d696c,0x00000074, + 0x00070005,0x000002d1,0x61685367,0x694c7072,0x5374696d,0x656c6163,0x00000000,0x00060005, + 0x000002d4,0x61685367,0x694c7072,0x4d74696d,0x00006e69,0x00040005,0x000002e3,0x61726170, + 0x0000006d,0x00040005,0x000002e6,0x61726170,0x0000006d,0x00040005,0x000002e9,0x61726170, + 0x0000006d,0x00040005,0x000002ec,0x61726170,0x0000006d,0x00040005,0x000002ef,0x61726170, + 0x0000006d,0x00040005,0x000002f2,0x61726170,0x0000006d,0x00040005,0x000002f5,0x61726170, + 0x0000006d,0x00040005,0x00000303,0x63615f68,0x00000063,0x00030005,0x00000304,0x0000006a, + 0x00040005,0x0000030c,0x63615f76,0x00000063,0x00030005,0x0000030d,0x00000069,0x00050005, + 0x0000031d,0x65646e69,0x6c626178,0x00000065,0x00050005,0x0000032a,0x65646e69,0x6c626178, + 0x00000065,0x00030005,0x00000335,0x00000066,0x00030005,0x0000033b,0x00000069,0x00050005, + 0x00000343,0x65746e69,0x44307072,0x00006765,0x00040005,0x00000350,0x61726170,0x0000006d, + 0x00040005,0x00000352,0x61726170,0x0000006d,0x00030005,0x0000035f,0x00000069,0x00050005, + 0x00000367,0x65746e69,0x30397072,0x00676544,0x00040005,0x00000374,0x61726170,0x0000006d, + 0x00040005,0x00000376,0x61726170,0x0000006d,0x00050005,0x00000383,0x61687070,0x625f6573, + 0x00003534,0x00070005,0x0000038d,0x706d6574,0x746e695f,0x34707265,0x67654435,0x00000000, + 0x00030005,0x000003a5,0x00000061,0x00030005,0x000003b1,0x00000062,0x00030005,0x000003bd, + 0x00000063,0x00030005,0x000003c9,0x00000064,0x00050005,0x000003f1,0x61687070,0x705f6573, + 0x00003534,0x00030005,0x000003f9,0x00000069,0x00050005,0x00000401,0x65746e69,0x35347072, + 0x00676544,0x00030005,0x0000040d,0x00000069,0x00040005,0x00000420,0x61726170,0x0000006d, + 0x00040005,0x00000422,0x61726170,0x0000006d,0x00050005,0x0000042f,0x61687070,0x625f6573, + 0x00353331,0x00070005,0x00000434,0x706d6574,0x746e695f,0x31707265,0x65443533,0x00000067, + 0x00030005,0x0000044c,0x00000061,0x00030005,0x00000458,0x00000062,0x00030005,0x00000464, + 0x00000063,0x00030005,0x00000470,0x00000064,0x00050005,0x00000498,0x61687070,0x705f6573, + 0x00353331,0x00030005,0x000004a1,0x00000069,0x00060005,0x000004a9,0x65746e69,0x33317072, + 0x67654435,0x00000000,0x00030005,0x000004b5,0x00000069,0x00040005,0x000004c7,0x61726170, + 0x0000006d,0x00040005,0x000004c9,0x61726170,0x0000006d,0x00050005,0x000004d3,0x72616873, + 0x73656e70,0x00000073,0x00030005,0x000004d4,0x00004350,0x00040006,0x000004d4,0x00000000, + 0x0063646e,0x00070006,0x000004d4,0x00000001,0x77656956,0x74726f70,0x6f666e49,0x00000000, + 0x00060006,0x000004d4,0x00000002,0x72616873,0x73656e70,0x00000073,0x00030005,0x000004d6, + 0x00006370,0x00060005,0x000004db,0x72616873,0x5f6e6570,0x64696c73,0x00007265,0x00050005, + 0x000004de,0x5378614d,0x656c6163,0x00000000,0x00050005,0x000004e4,0x536e694d,0x656c6163, + 0x00000000,0x00050005,0x000004e8,0x696d694c,0x61635374,0x0000656c,0x00070005,0x000004f4, + 0x72616873,0x72745370,0x74676e65,0x78614d68,0x00000000,0x00060005,0x00000508,0x72616873, + 0x6d694c70,0x614d7469,0x00000078,0x00040005,0x00000514,0x69536e69,0x0000657a,0x00040005, + 0x00000519,0x49766e69,0x0000006e,0x00040005,0x0000051d,0x58637273,0x00000000,0x00060005, + 0x0000051f,0x67617266,0x43786554,0x64726f6f,0x00000000,0x00040005,0x00000527,0x59637273, + 0x00000000,0x00030005,0x0000052e,0x00787366,0x00030005,0x00000531,0x00797366,0x00030005, + 0x00000534,0x00007866,0x00030005,0x00000538,0x00007966,0x00040005,0x0000053c,0x695f7866, + 0x0000746e,0x00040005,0x00000540,0x695f7966,0x0000746e,0x00030005,0x00000544,0x00000069, + 0x00030005,0x0000054c,0x0000006a,0x00030005,0x00000554,0x00006374,0x00030005,0x00000564, + 0x00000070,0x00060005,0x0000056a,0x75706e49,0x78655474,0x65727574,0x00000000,0x00040005, + 0x0000056e,0x61726170,0x0000006d,0x00030005,0x00000576,0x00303065,0x00040005,0x00000577, + 0x61726170,0x0000006d,0x00040005,0x0000057a,0x61726170,0x0000006d,0x00040005,0x0000057d, + 0x61726170,0x0000006d,0x00040005,0x00000580,0x61726170,0x0000006d,0x00040005,0x00000583, + 0x61726170,0x0000006d,0x00040005,0x00000586,0x61726170,0x0000006d,0x00040005,0x00000589, + 0x61726170,0x0000006d,0x00040005,0x0000058c,0x61726170,0x0000006d,0x00040005,0x0000058f, + 0x61726170,0x0000006d,0x00030005,0x00000593,0x00313065,0x00040005,0x00000594,0x61726170, + 0x0000006d,0x00040005,0x00000597,0x61726170,0x0000006d,0x00040005,0x0000059a,0x61726170, + 0x0000006d,0x00040005,0x0000059d,0x61726170,0x0000006d,0x00040005,0x000005a0,0x61726170, + 0x0000006d,0x00040005,0x000005a3,0x61726170,0x0000006d,0x00040005,0x000005a6,0x61726170, + 0x0000006d,0x00040005,0x000005a9,0x61726170,0x0000006d,0x00040005,0x000005ac,0x61726170, + 0x0000006d,0x00030005,0x000005b0,0x00303165,0x00040005,0x000005b1,0x61726170,0x0000006d, + 0x00040005,0x000005b4,0x61726170,0x0000006d,0x00040005,0x000005b7,0x61726170,0x0000006d, + 0x00040005,0x000005ba,0x61726170,0x0000006d,0x00040005,0x000005bd,0x61726170,0x0000006d, + 0x00040005,0x000005c0,0x61726170,0x0000006d,0x00040005,0x000005c3,0x61726170,0x0000006d, + 0x00040005,0x000005c6,0x61726170,0x0000006d,0x00040005,0x000005c9,0x61726170,0x0000006d, + 0x00030005,0x000005cd,0x00313165,0x00040005,0x000005ce,0x61726170,0x0000006d,0x00040005, + 0x000005d1,0x61726170,0x0000006d,0x00040005,0x000005d4,0x61726170,0x0000006d,0x00040005, + 0x000005d7,0x61726170,0x0000006d,0x00040005,0x000005da,0x61726170,0x0000006d,0x00040005, + 0x000005dd,0x61726170,0x0000006d,0x00040005,0x000005e0,0x61726170,0x0000006d,0x00040005, + 0x000005e3,0x61726170,0x0000006d,0x00040005,0x000005e6,0x61726170,0x0000006d,0x00030005, + 0x000005ea,0x00000077,0x00040005,0x000005eb,0x61726170,0x0000006d,0x00040005,0x000005ed, + 0x61726170,0x0000006d,0x00040005,0x000005ef,0x61726170,0x0000006d,0x00040005,0x000005f1, + 0x61726170,0x0000006d,0x00040005,0x000005f3,0x61726170,0x0000006d,0x00040005,0x000005f5, + 0x61726170,0x0000006d,0x00050005,0x000005f8,0x65736162,0x67696557,0x00007468,0x00030005, + 0x00000605,0x0059706f,0x00040005,0x00000606,0x61726170,0x0000006d,0x00040005,0x00000608, + 0x61726170,0x0000006d,0x00040005,0x0000060a,0x61726170,0x0000006d,0x00040005,0x0000060f, + 0x61726170,0x0000006d,0x00040005,0x00000611,0x61726170,0x0000006d,0x00040005,0x00000613, + 0x61726170,0x0000006d,0x00040005,0x00000615,0x61726170,0x0000006d,0x00040005,0x00000617, + 0x61726170,0x0000006d,0x00040005,0x00000619,0x61726170,0x0000006d,0x00030005,0x0000061e, + 0x0000706f,0x00030005,0x00000622,0x00000079,0x00040005,0x00000623,0x61726170,0x0000006d, + 0x00040005,0x00000627,0x72726f63,0x00000000,0x00050005,0x00000637,0x4374756f,0x726f6c6f, + 0x00000000,0x00030047,0x000004d4,0x00000002,0x00050048,0x000004d4,0x00000000,0x00000023, + 0x00000000,0x00050048,0x000004d4,0x00000001,0x00000023,0x00000010,0x00050048,0x000004d4, + 0x00000002,0x00000023,0x00000020,0x00040047,0x0000051f,0x0000001e,0x00000000,0x00040047, + 0x0000056a,0x00000021,0x00000000,0x00040047,0x0000056a,0x00000022,0x00000000,0x00040047, + 0x00000637,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000003,0x00040020, + 0x00000008,0x00000007,0x00000007,0x00040021,0x00000009,0x00000006,0x00000008,0x00040020, + 0x0000000d,0x00000007,0x00000006,0x00040017,0x0000000e,0x00000006,0x00000004,0x000c0021, + 0x0000000f,0x0000000e,0x0000000d,0x0000000d,0x0000000d,0x0000000d,0x0000000d,0x0000000d, + 0x0000000d,0x0000000d,0x0000000d,0x00040020,0x0000001b,0x00000007,0x0000000e,0x00090021, + 0x0000001c,0x0000000e,0x0000001b,0x0000001b,0x0000001b,0x0000001b,0x0000000d,0x0000000d, + 0x00040015,0x00000025,0x00000020,0x00000001,0x00040020,0x00000026,0x00000007,0x00000025, + 0x000a0021,0x00000027,0x00000006,0x0000000d,0x0000000d,0x0000000d,0x0000000d,0x0000000d, + 0x0000000d,0x00000026,0x00040015,0x00000031,0x00000020,0x00000000,0x0004002b,0x00000031, + 0x00000032,0x00000006,0x0004001c,0x00000033,0x00000006,0x00000032,0x00040020,0x00000034, + 0x00000007,0x00000033,0x00050021,0x00000035,0x00000006,0x00000034,0x00000026,0x0004001c, + 0x0000003a,0x00000033,0x00000032,0x00040020,0x0000003b,0x00000007,0x0000003a,0x00060021, + 0x0000003c,0x00000006,0x0000003b,0x00000026,0x00000026,0x00090021,0x00000042,0x00000006, + 0x0000003b,0x0000000d,0x0000000d,0x00000026,0x00000026,0x0000001b,0x0004002b,0x00000006, + 0x0000004b,0x3e59b3d0,0x0004002b,0x00000031,0x0000004c,0x00000000,0x0004002b,0x00000006, + 0x00000050,0x3f371759,0x0004002b,0x00000031,0x00000051,0x00000001,0x0004002b,0x00000006, + 0x00000056,0x3d93dd98,0x0004002b,0x00000031,0x00000057,0x00000002,0x0004002b,0x00000006, + 0x000000a5,0x00000000,0x00020014,0x000000a6,0x0007002c,0x0000000e,0x000000aa,0x000000a5, + 0x000000a5,0x000000a5,0x000000a5,0x0004002b,0x00000006,0x000000b2,0x3f800000,0x00040020, + 0x000000b7,0x00000007,0x000000a6,0x0004002b,0x00000006,0x000000bb,0x400ce000,0x0004002b, + 0x00000006,0x000000bf,0x3d800000,0x0004002b,0x00000025,0x0000011c,0x00000020,0x0004002b, + 0x00000006,0x0000014e,0x3b808081,0x0004002b,0x00000006,0x00000152,0x40000000,0x0004002b, + 0x00000006,0x00000154,0x3e000000,0x0004002b,0x00000025,0x0000015c,0x00000000,0x0004002b, + 0x00000025,0x0000015d,0x0000003f,0x0004002b,0x00000025,0x00000167,0x00000006,0x0004002b, + 0x00000031,0x00000169,0x00000180,0x0004001c,0x0000016a,0x00000006,0x00000169,0x0004002b, + 0x00000006,0x0000016b,0x3b3e0ded,0x0004002b,0x00000006,0x0000016c,0xbc5013a9,0x0004002b, + 0x00000006,0x0000016d,0x3c5844d0,0x0004002b,0x00000006,0x0000016e,0xbb5ed289,0x0004002b, + 0x00000006,0x0000016f,0x3bce703b,0x0004002b,0x00000006,0x00000170,0xbccbfb16,0x0004002b, + 0x00000006,0x00000171,0x3f7f9db2,0x0004002b,0x00000006,0x00000172,0x3cdc5d64,0x0004002b, + 0x00000006,0x00000173,0xbbded289,0x0004002b,0x00000006,0x00000174,0x3c102de0,0x0004002b, + 0x00000006,0x00000175,0xbd13dd98,0x0004002b,0x00000006,0x00000176,0x3f7edfa4,0x0004002b, + 0x00000006,0x00000177,0x3d29fbe7,0x0004002b,0x00000006,0x00000178,0xbc28c155,0x0004002b, + 0x00000006,0x00000179,0x3a03126f,0x0004002b,0x00000006,0x0000017a,0x3c3fb15b,0x0004002b, + 0x00000006,0x0000017b,0xbd422681,0x0004002b,0x00000006,0x0000017c,0x3f7e425b,0x0004002b, + 0x00000006,0x0000017d,0x3d6631f9,0x0004002b,0x00000006,0x0000017e,0xbc68a71e,0x0004002b, + 0x00000006,0x0000017f,0x3c68a71e,0x0004002b,0x00000006,0x00000180,0xbd6bedfa,0x0004002b, + 0x00000006,0x00000181,0x3f7d5cfb,0x0004002b,0x00000006,0x00000182,0x3d9205bc,0x0004002b, + 0x00000006,0x00000183,0xbc944674,0x0004002b,0x00000006,0x00000184,0x3c87fcb9,0x0004002b, + 0x00000006,0x00000185,0xbd8a0903,0x0004002b,0x00000006,0x00000186,0x3f7c01a3,0x0004002b, + 0x00000006,0x00000187,0x3db2fec5,0x0004002b,0x00000006,0x00000188,0xbcb43958,0x0004002b, + 0x00000006,0x00000189,0x3a83126f,0x0004002b,0x00000006,0x0000018a,0x3c985f07,0x0004002b, + 0x00000006,0x0000018b,0xbd9c0ebf,0x0004002b,0x00000006,0x0000018c,0x3f7a7efa,0x0004002b, + 0x00000006,0x0000018d,0x3dd4fdf4,0x0004002b,0x00000006,0x0000018e,0xbcd844d0,0x0004002b, + 0x00000006,0x0000018f,0x3ac49ba6,0x0004002b,0x00000006,0x00000190,0x3ca7ef9e,0x0004002b, + 0x00000006,0x00000191,0xbdae147b,0x0004002b,0x00000006,0x00000192,0x3f7902de,0x0004002b, + 0x00000006,0x00000193,0x3df6fd22,0x0004002b,0x00000006,0x00000194,0xbcfc5048,0x0004002b, + 0x00000006,0x00000195,0x3b03126f,0x0004002b,0x00000006,0x00000196,0x3cb851ec,0x0004002b, + 0x00000006,0x00000197,0xbdbe0ded,0x0004002b,0x00000006,0x00000198,0x3f76fd22,0x0004002b, + 0x00000006,0x00000199,0x3e0d844d,0x0004002b,0x00000006,0x0000019a,0xbd102de0,0x0004002b, + 0x00000006,0x0000019b,0x3b1d4952,0x0004002b,0x00000006,0x0000019c,0x3cc3c9ef,0x0004002b, + 0x00000006,0x0000019d,0xbdce075f,0x0004002b,0x00000006,0x0000019e,0x3f751eb8,0x0004002b, + 0x00000006,0x0000019f,0x3e1f8a09,0x0004002b,0x00000006,0x000001a0,0xbd22339c,0x0004002b, + 0x00000006,0x000001a1,0x3cd013a9,0x0004002b,0x00000006,0x000001a2,0xbddbf488,0x0004002b, + 0x00000006,0x000001a3,0x3f72de01,0x0004002b,0x00000006,0x000001a4,0x3e31f8a1,0x0004002b, + 0x00000006,0x000001a5,0xbd33d07d,0x0004002b,0x00000006,0x000001a6,0x3b5ed289,0x0004002b, + 0x00000006,0x000001a7,0x3cd844d0,0x0004002b,0x00000006,0x000001a8,0xbde90ff9,0x0004002b, + 0x00000006,0x000001a9,0x3f70624e,0x0004002b,0x00000006,0x000001aa,0x3e458794,0x0004002b, + 0x00000006,0x000001ab,0xbd47e282,0x0004002b,0x00000006,0x000001ac,0x3b902de0,0x0004002b, + 0x00000006,0x000001ad,0x3ce3bcd3,0x0004002b,0x00000006,0x000001ae,0xbdf3eab3,0x0004002b, + 0x00000006,0x000001af,0x3f6d9e84,0x0004002b,0x00000006,0x000001b0,0x3e58fc50,0x0004002b, + 0x00000006,0x000001b1,0xbd5bf488,0x0004002b,0x00000006,0x000001b2,0x3ba0902e,0x0004002b, + 0x00000006,0x000001b3,0x3cebedfa,0x0004002b,0x00000006,0x000001b4,0xbdfef9db,0x0004002b, + 0x00000006,0x000001b5,0x3f6ac083,0x0004002b,0x00000006,0x000001b6,0x3e6c8b44,0x0004002b, + 0x00000006,0x000001b7,0xbd6dfa44,0x0004002b,0x00000006,0x000001b8,0x3bc154ca,0x0004002b, + 0x00000006,0x000001b9,0x3cf0068e,0x0004002b,0x00000006,0x000001ba,0xbe04816f,0x0004002b, + 0x00000006,0x000001bb,0x3f67e282,0x0004002b,0x00000006,0x000001bc,0x3e808312,0x0004002b, + 0x00000006,0x000001bd,0xbd810625,0x0004002b,0x00000006,0x000001be,0x3cf837b5,0x0004002b, + 0x00000006,0x000001bf,0xbe087fcc,0x0004002b,0x00000006,0x000001c0,0x3f64816f,0x0004002b, + 0x00000006,0x000001c1,0x3e8ac083,0x0004002b,0x00000006,0x000001c2,0xbd8b0f28,0x0004002b, + 0x00000006,0x000001c3,0x3bef34d7,0x0004002b,0x00000006,0x000001c4,0x3cfc5048,0x0004002b, + 0x00000006,0x000001c5,0xbe0bfb16,0x0004002b,0x00000006,0x000001c6,0x3f60ff97,0x0004002b, + 0x00000006,0x000001c7,0x3e953f7d,0x0004002b,0x00000006,0x000001c8,0xbd95182b,0x0004002b, + 0x00000006,0x000001c9,0x3c07fcb9,0x0004002b,0x00000006,0x000001ca,0xbe0f7660,0x0004002b, + 0x00000006,0x000001cb,0x3f5d9e84,0x0004002b,0x00000006,0x000001cc,0x3e9fbe77,0x0004002b, + 0x00000006,0x000001cd,0xbd9eecc0,0x0004002b,0x00000006,0x000001ce,0x3c185f07,0x0004002b, + 0x00000006,0x000001cf,0x3d00346e,0x0004002b,0x00000006,0x000001d0,0xbe1205bc,0x0004002b, + 0x00000006,0x000001d1,0x3f59c0ec,0x0004002b,0x00000006,0x000001d2,0x3eaa7efa,0x0004002b, + 0x00000006,0x000001d3,0xbda8f5c3,0x0004002b,0x00000006,0x000001d4,0x3c28c155,0x0004002b, + 0x00000006,0x000001d5,0xbe13f7cf,0x0004002b,0x00000006,0x000001d6,0x3f55dcc6,0x0004002b, + 0x00000006,0x000001d7,0x3eb53f7d,0x0004002b,0x00000006,0x000001d8,0xbdb2fec5,0x0004002b, + 0x00000006,0x000001d9,0x3c378034,0x0004002b,0x00000006,0x000001da,0xbe158106,0x0004002b, + 0x00000006,0x000001db,0x3f51bda5,0x0004002b,0x00000006,0x000001dc,0x3ec04189,0x0004002b, + 0x00000006,0x000001dd,0xbdbd07c8,0x0004002b,0x00000006,0x000001de,0x3c47e282,0x0004002b, + 0x00000006,0x000001df,0xbe16872b,0x0004002b,0x00000006,0x000001e0,0x3f4d5cfb,0x0004002b, + 0x00000006,0x000001e1,0x3ecb020c,0x0004002b,0x00000006,0x000001e2,0xbdc60aa6,0x0004002b, + 0x00000006,0x000001e3,0x3c6075f7,0x0004002b,0x00000006,0x000001e4,0xbe177319,0x0004002b, + 0x00000006,0x000001e5,0x3f491d15,0x0004002b,0x00000006,0x000001e6,0x3ed645a2,0x0004002b, + 0x00000006,0x000001e7,0xbdd013a9,0x0004002b,0x00000006,0x000001e8,0x3c6f34d7,0x0004002b, + 0x00000006,0x000001e9,0x3f44a234,0x0004002b,0x00000006,0x000001ea,0x3ee13a93,0x0004002b, + 0x00000006,0x000001eb,0xbdd91687,0x0004002b,0x00000006,0x000001ec,0x3c7f9724,0x0004002b, + 0x00000006,0x000001ed,0x3cf41f21,0x0004002b,0x00000006,0x000001ee,0x3f4020c5,0x0004002b, + 0x00000006,0x000001ef,0x3eec3c9f,0x0004002b,0x00000006,0x000001f0,0xbde21965,0x0004002b, + 0x00000006,0x000001f1,0x3f3b3d08,0x0004002b,0x00000006,0x000001f2,0x3ef73eab,0x0004002b, + 0x00000006,0x000001f3,0xbdeae7d5,0x0004002b,0x00000006,0x000001f4,0x3c944674,0x0004002b, + 0x00000006,0x000001f5,0x3f363f14,0x0004002b,0x00000006,0x000001f6,0x3f014120,0x0004002b, + 0x00000006,0x000001f7,0xbdf318fc,0x0004002b,0x00000006,0x000001f8,0x3c9ba5e3,0x0004002b, + 0x00000006,0x000001f9,0x3f3161e5,0x0004002b,0x00000006,0x000001fa,0x3f06c227,0x0004002b, + 0x00000006,0x000001fb,0xbdfb15b5,0x0004002b,0x00000006,0x000001fc,0x3ca3d70a,0x0004002b, + 0x00000006,0x000001fd,0x3cdfa440,0x0004002b,0x00000006,0x000001fe,0xbe1288ce,0x0004002b, + 0x00000006,0x000001ff,0x3f2c2268,0x0004002b,0x00000006,0x00000200,0x3f0c432d,0x0004002b, + 0x00000006,0x00000201,0xbe010625,0x0004002b,0x00000006,0x00000202,0x3cb020c5,0x0004002b, + 0x00000006,0x00000203,0xbe107c85,0x0004002b,0x00000006,0x00000204,0x3f271de7,0x0004002b, + 0x00000006,0x00000205,0x3f11bda5,0x0004002b,0x00000006,0x00000206,0xbe050481,0x0004002b, + 0x00000006,0x00000207,0xbe0e075f,0x0004002b,0x00000006,0x00000208,0x3f21de6a,0x0004002b, + 0x00000006,0x00000209,0x3f171de7,0x0004002b,0x00000006,0x0000020a,0xbe07fcb9,0x0004002b, + 0x00000006,0x0000020b,0x3cbfb15b,0x0004002b,0x00000006,0x0000020c,0x3cc7e282,0x0004002b, + 0x00000006,0x0000020d,0xbe0af4f1,0x0004002b,0x00000006,0x0000020e,0x3f1c7e28,0x0183002c, + 0x0000016a,0x0000020f,0x000000a5,0x000000a5,0x000000b2,0x000000a5,0x000000a5,0x000000a5, + 0x0000016b,0x0000016c,0x000000b2,0x0000016d,0x0000016e,0x000000a5,0x0000016f,0x00000170, + 0x00000171,0x00000172,0x00000173,0x000000a5,0x00000174,0x00000175,0x00000176,0x00000177, + 0x00000178,0x00000179,0x0000017a,0x0000017b,0x0000017c,0x0000017d,0x0000017e,0x00000179, + 0x0000017f,0x00000180,0x00000181,0x00000182,0x00000183,0x00000179,0x00000184,0x00000185, + 0x00000186,0x00000187,0x00000188,0x00000189,0x0000018a,0x0000018b,0x0000018c,0x0000018d, + 0x0000018e,0x0000018f,0x00000190,0x00000191,0x00000192,0x00000193,0x00000194,0x00000195, + 0x00000196,0x00000197,0x00000198,0x00000199,0x0000019a,0x0000019b,0x0000019c,0x0000019d, + 0x0000019e,0x0000019f,0x000001a0,0x0000016b,0x000001a1,0x000001a2,0x000001a3,0x000001a4, + 0x000001a5,0x000001a6,0x000001a7,0x000001a8,0x000001a9,0x000001aa,0x000001ab,0x000001ac, + 0x000001ad,0x000001ae,0x000001af,0x000001b0,0x000001b1,0x000001b2,0x000001b3,0x000001b4, + 0x000001b5,0x000001b6,0x000001b7,0x000001b8,0x000001b9,0x000001ba,0x000001bb,0x000001bc, + 0x000001bd,0x0000016f,0x000001be,0x000001bf,0x000001c0,0x000001c1,0x000001c2,0x000001c3, + 0x000001c4,0x000001c5,0x000001c6,0x000001c7,0x000001c8,0x000001c9,0x000001c4,0x000001ca, + 0x000001cb,0x000001cc,0x000001cd,0x000001ce,0x000001cf,0x000001d0,0x000001d1,0x000001d2, + 0x000001d3,0x000001d4,0x000001cf,0x000001d5,0x000001d6,0x000001d7,0x000001d8,0x000001d9, + 0x000001cf,0x000001da,0x000001db,0x000001dc,0x000001dd,0x000001de,0x000001cf,0x000001df, + 0x000001e0,0x000001e1,0x000001e2,0x000001e3,0x000001c4,0x000001e4,0x000001e5,0x000001e6, + 0x000001e7,0x000001e8,0x000001be,0x000001e4,0x000001e9,0x000001ea,0x000001eb,0x000001ec, + 0x000001ed,0x000001e4,0x000001ee,0x000001ef,0x000001f0,0x00000184,0x000001b9,0x000001df, + 0x000001f1,0x000001f2,0x000001f3,0x000001f4,0x000001b3,0x000001da,0x000001f5,0x000001f6, + 0x000001f7,0x000001f8,0x000001ad,0x000001d5,0x000001f9,0x000001fa,0x000001fb,0x000001fc, + 0x000001fd,0x000001fe,0x000001ff,0x00000200,0x00000201,0x00000202,0x000001a7,0x00000203, + 0x00000204,0x00000205,0x00000206,0x00000196,0x000001a1,0x00000207,0x00000208,0x00000209, + 0x0000020a,0x0000020b,0x0000020c,0x0000020d,0x0000020e,0x0000020e,0x0000020d,0x0000020c, + 0x0000020b,0x0000020a,0x00000209,0x00000208,0x00000207,0x000001a1,0x00000196,0x00000206, + 0x00000205,0x00000204,0x00000203,0x000001a7,0x00000202,0x00000201,0x00000200,0x000001ff, + 0x000001fe,0x000001fd,0x000001fc,0x000001fb,0x000001fa,0x000001f9,0x000001d5,0x000001ad, + 0x000001f8,0x000001f7,0x000001f6,0x000001f5,0x000001da,0x000001b3,0x000001f4,0x000001f3, + 0x000001f2,0x000001f1,0x000001df,0x000001b9,0x00000184,0x000001f0,0x000001ef,0x000001ee, + 0x000001e4,0x000001ed,0x000001ec,0x000001eb,0x000001ea,0x000001e9,0x000001e4,0x000001be, + 0x000001e8,0x000001e7,0x000001e6,0x000001e5,0x000001e4,0x000001c4,0x000001e3,0x000001e2, + 0x000001e1,0x000001e0,0x000001df,0x000001cf,0x000001de,0x000001dd,0x000001dc,0x000001db, + 0x000001da,0x000001cf,0x000001d9,0x000001d8,0x000001d7,0x000001d6,0x000001d5,0x000001cf, + 0x000001d4,0x000001d3,0x000001d2,0x000001d1,0x000001d0,0x000001cf,0x000001ce,0x000001cd, + 0x000001cc,0x000001cb,0x000001ca,0x000001c4,0x000001c9,0x000001c8,0x000001c7,0x000001c6, + 0x000001c5,0x000001c4,0x000001c3,0x000001c2,0x000001c1,0x000001c0,0x000001bf,0x000001be, + 0x0000016f,0x000001bd,0x000001bc,0x000001bb,0x000001ba,0x000001b9,0x000001b8,0x000001b7, + 0x000001b6,0x000001b5,0x000001b4,0x000001b3,0x000001b2,0x000001b1,0x000001b0,0x000001af, + 0x000001ae,0x000001ad,0x000001ac,0x000001ab,0x000001aa,0x000001a9,0x000001a8,0x000001a7, + 0x000001a6,0x000001a5,0x000001a4,0x000001a3,0x000001a2,0x000001a1,0x0000016b,0x000001a0, + 0x0000019f,0x0000019e,0x0000019d,0x0000019c,0x0000019b,0x0000019a,0x00000199,0x00000198, + 0x00000197,0x00000196,0x00000195,0x00000194,0x00000193,0x00000192,0x00000191,0x00000190, + 0x0000018f,0x0000018e,0x0000018d,0x0000018c,0x0000018b,0x0000018a,0x00000189,0x00000188, + 0x00000187,0x00000186,0x00000185,0x00000184,0x00000179,0x00000183,0x00000182,0x00000181, + 0x00000180,0x0000017f,0x00000179,0x0000017e,0x0000017d,0x0000017c,0x0000017b,0x0000017a, + 0x00000179,0x00000178,0x00000177,0x00000176,0x00000175,0x00000174,0x000000a5,0x00000173, + 0x00000172,0x00000171,0x00000170,0x0000016f,0x000000a5,0x0000016e,0x0000016d,0x000000b2, + 0x0000016c,0x0000016b,0x00040020,0x00000214,0x00000007,0x0000016a,0x0004002b,0x00000025, + 0x0000021f,0x00000001,0x0004002b,0x00000006,0x0000022a,0xbf19a027,0x0004002b,0x00000006, + 0x0000022b,0x3f99a027,0x0004002b,0x00000006,0x0000022c,0xbf1bc01a,0x0004002b,0x00000006, + 0x0000022d,0x3f996f00,0x0004002b,0x00000006,0x0000022e,0xbf171de7,0x0004002b,0x00000006, + 0x0000022f,0xbb3e0ded,0x0004002b,0x00000006,0x00000230,0xbf1d5cfb,0x0004002b,0x00000006, + 0x00000231,0x3f990ff9,0x0004002b,0x00000006,0x00000232,0xbf143fe6,0x0004002b,0x00000006, + 0x00000233,0xbf1e9e1b,0x0004002b,0x00000006,0x00000234,0x3f983127,0x0004002b,0x00000006, + 0x00000235,0xbf10ded3,0x0004002b,0x00000006,0x00000236,0xbf1f9db2,0x0004002b,0x00000006, + 0x00000237,0x3f9710cb,0x0004002b,0x00000006,0x00000238,0xbf0d21ff,0x0004002b,0x00000006, + 0x00000239,0xbc7765fe,0x0004002b,0x00000006,0x0000023a,0xbf20624e,0x0004002b,0x00000006, + 0x0000023b,0x3f95bf48,0x0004002b,0x00000006,0x0000023c,0xbf0902de,0x0004002b,0x00000006, + 0x0000023d,0xbc9fbe77,0x0004002b,0x00000006,0x0000023e,0xbf208312,0x0004002b,0x00000006, + 0x0000023f,0x3f943fe6,0x0004002b,0x00000006,0x00000240,0xbf04a234,0x0004002b,0x00000006, + 0x00000241,0xbcd42c3d,0x0004002b,0x00000006,0x00000242,0xbf20ded3,0x0004002b,0x00000006, + 0x00000243,0x3f929fbe,0x0004002b,0x00000006,0x00000244,0xbf0020c5,0x0004002b,0x00000006, + 0x00000245,0xbd01d7dc,0x0004002b,0x00000006,0x00000246,0x3f904ea5,0x0004002b,0x00000006, + 0x00000247,0xbef53f7d,0x0004002b,0x00000006,0x00000248,0xbd1e1b09,0x0004002b,0x00000006, + 0x00000249,0x3f8df06f,0x0004002b,0x00000006,0x0000024a,0xbee9fbe7,0x0004002b,0x00000006, + 0x0000024b,0xbd39f55a,0x0004002b,0x00000006,0x0000024c,0x3c902de0,0x0004002b,0x00000006, + 0x0000024d,0xbf1e6320,0x0004002b,0x00000006,0x0000024e,0x3f8b404f,0x0004002b,0x00000006, + 0x0000024f,0xbede83e4,0x0004002b,0x00000006,0x00000250,0xbd59e83e,0x0004002b,0x00000006, + 0x00000251,0xbf1d21ff,0x0004002b,0x00000006,0x00000252,0x3f886f69,0x0004002b,0x00000006, + 0x00000253,0xbed205bc,0x0004002b,0x00000006,0x00000254,0xbd7be76d,0x0004002b,0x00000006, + 0x00000255,0xbf1b5dcc,0x0004002b,0x00000006,0x00000256,0x3f852f1b,0x0004002b,0x00000006, + 0x00000257,0xbec4c2f8,0x0004002b,0x00000006,0x00000258,0xbd8ef34d,0x0004002b,0x00000006, + 0x00000259,0x3c9fbe77,0x0004002b,0x00000006,0x0000025a,0xbf19c0ec,0x0004002b,0x00000006, + 0x0000025b,0x3f820f91,0x0004002b,0x00000006,0x0000025c,0xbeb6fd22,0x0004002b,0x00000006, + 0x0000025d,0xbda30553,0x0004002b,0x00000006,0x0000025e,0xbf17c1be,0x0004002b,0x00000006, + 0x0000025f,0x3f7d42c4,0x0004002b,0x00000006,0x00000260,0xbea83e42,0x0004002b,0x00000006, + 0x00000261,0xbdb7e910,0x0004002b,0x00000006,0x00000262,0xbf14fdf4,0x0004002b,0x00000006, + 0x00000263,0x3f753f7d,0x0004002b,0x00000006,0x00000264,0xbe98fc50,0x0004002b,0x00000006, + 0x00000265,0xbdcd013b,0x0004002b,0x00000006,0x00000266,0xbf12a305,0x0004002b,0x00000006, + 0x00000267,0x3f6de00d,0x0004002b,0x00000006,0x00000268,0xbe89ba5e,0x0004002b,0x00000006, + 0x00000269,0xbf0fdf3b,0x0004002b,0x00000006,0x0000026a,0x3f65c28f,0x0004002b,0x00000006, + 0x0000026b,0xbe727bb3,0x0004002b,0x00000006,0x0000026c,0x3b7f9724,0x0004002b,0x00000006, + 0x0000026d,0xbf0cbfb1,0x0004002b,0x00000006,0x0000026e,0x3f5d42c4,0x0004002b,0x00000006, + 0x0000026f,0xbe5182aa,0x0004002b,0x00000006,0x00000270,0xbe0985f0,0x0004002b,0x00000006, + 0x00000271,0xbf097f63,0x0004002b,0x00000006,0x00000272,0x3f54816f,0x0004002b,0x00000006, + 0x00000273,0xbe2f0069,0x0004002b,0x00000006,0x00000274,0xbe160419,0x0004002b,0x00000006, + 0x00000275,0xbf061e4f,0x0004002b,0x00000006,0x00000276,0x3f4b5dcc,0x0004002b,0x00000006, + 0x00000277,0xbe228241,0x0004002b,0x00000006,0x00000278,0x3bb0f27c,0x0004002b,0x00000006, + 0x00000279,0xbf02bd3c,0x0004002b,0x00000006,0x0000027a,0x3f428241,0x0004002b,0x00000006, + 0x0000027b,0xbdd119ce,0x0004002b,0x00000006,0x0000027c,0xbe3089a0,0x0004002b,0x00000006, + 0x0000027d,0xbefe425b,0x0004002b,0x00000006,0x0000027e,0x3f391d15,0x0004002b,0x00000006, + 0x0000027f,0xbd84ea4b,0x0004002b,0x00000006,0x00000280,0xbe3ef9db,0x0004002b,0x00000006, + 0x00000281,0xbef6bb99,0x0004002b,0x00000006,0x00000282,0x3f2f41f2,0x0004002b,0x00000006, + 0x00000283,0xbcebedfa,0x0004002b,0x00000006,0x00000284,0xbe4c7e28,0x0004002b,0x00000006, + 0x00000285,0x3bded289,0x0004002b,0x00000006,0x00000286,0xbeef0069,0x0004002b,0x00000006, + 0x00000287,0x3f256042,0x0004002b,0x00000006,0x00000288,0xbe5b8bac,0x0004002b,0x00000006, + 0x00000289,0xbee6c227,0x0004002b,0x00000006,0x0000028a,0x3f1b020c,0x0004002b,0x00000006, + 0x0000028b,0x3d4432ca,0x0004002b,0x00000006,0x0000028c,0xbe6a7efa,0x0004002b,0x00000006, + 0x0000028d,0x3c8c154d,0x0004002b,0x00000006,0x0000028e,0xbedfbe77,0x0004002b,0x00000006, + 0x0000028f,0x3dafec57,0x0004002b,0x00000006,0x00000290,0xbe7a786c,0x0004002b,0x00000006, + 0x00000291,0x3c83e426,0x0004002b,0x00000006,0x00000292,0xbed6fd22,0x0004002b,0x00000006, + 0x00000293,0x3f073eab,0x0004002b,0x00000006,0x00000294,0x3e008312,0x0004002b,0x00000006, + 0x00000295,0xbe850481,0x0004002b,0x00000006,0x00000296,0x3c20902e,0x0004002b,0x00000006, + 0x00000297,0xbecf41f2,0x0004002b,0x00000006,0x00000298,0x3efa0275,0x0004002b,0x00000006, + 0x00000299,0x3e2978d5,0x0004002b,0x00000006,0x0000029a,0xbe8d013b,0x0004002b,0x00000006, + 0x0000029b,0x3c7765fe,0x0004002b,0x00000006,0x0000029c,0xbec703b0,0x0004002b,0x00000006, + 0x0000029d,0x3ee63f14,0x0004002b,0x00000006,0x0000029e,0x3e50ff97,0x0004002b,0x00000006, + 0x0000029f,0xbe94fdf4,0x0004002b,0x00000006,0x000002a0,0x3c2f4f0e,0x0004002b,0x00000006, + 0x000002a1,0xbebe00d2,0x0004002b,0x00000006,0x000002a2,0x3ed07c85,0x0004002b,0x00000006, + 0x000002a3,0x3e7a786c,0x0004002b,0x00000006,0x000002a4,0xbe9cfaad,0x0004002b,0x00000006, + 0x000002a5,0xbeb60419,0x0004002b,0x00000006,0x000002a6,0x3ebc01a3,0x0004002b,0x00000006, + 0x000002a7,0x3e9205bc,0x0004002b,0x00000006,0x000002a8,0xbea5460b,0x0004002b,0x00000006, + 0x000002a9,0xbeadc5d6,0x0004002b,0x00000006,0x000002aa,0x3ea703b0,0x0183002c,0x0000016a, + 0x000002ab,0x000000a5,0x0000022a,0x0000022b,0x0000022a,0x000000a5,0x000000a5,0x0000016b, + 0x0000022c,0x0000022d,0x0000022e,0x0000022f,0x000000a5,0x000001b2,0x00000230,0x00000231, + 0x00000232,0x00000173,0x00000179,0x000001c3,0x00000233,0x00000234,0x00000235,0x00000178, + 0x000000a5,0x000001ce,0x00000236,0x00000237,0x00000238,0x00000239,0x000000a5,0x000001d9, + 0x0000023a,0x0000023b,0x0000023c,0x0000023d,0x00000179,0x000001de,0x0000023e,0x0000023f, + 0x00000240,0x00000241,0x00000179,0x0000017f,0x00000242,0x00000243,0x00000244,0x00000245, + 0x00000179,0x000001ec,0x0000023a,0x00000246,0x00000247,0x00000248,0x00000179,0x00000184, + 0x00000236,0x00000249,0x0000024a,0x0000024b,0x00000189,0x0000024c,0x0000024d,0x0000024e, + 0x0000024f,0x00000250,0x00000189,0x000001f4,0x00000251,0x00000252,0x00000253,0x00000254, + 0x0000018f,0x000001f8,0x00000255,0x00000256,0x00000257,0x00000258,0x0000018f,0x00000259, + 0x0000025a,0x0000025b,0x0000025c,0x0000025d,0x00000195,0x000001fc,0x0000025e,0x0000025f, + 0x00000260,0x00000261,0x0000019b,0x000001fc,0x00000262,0x00000263,0x00000264,0x00000265, + 0x0000016b,0x000001fc,0x00000266,0x00000267,0x00000268,0x000001f0,0x000001a6,0x000001fc, + 0x00000269,0x0000026a,0x0000026b,0x000001fb,0x0000026c,0x00000190,0x0000026d,0x0000026e, + 0x0000026f,0x00000270,0x000001ac,0x000001fc,0x00000271,0x00000272,0x00000273,0x00000274, + 0x000001b2,0x00000259,0x00000275,0x00000276,0x000001c5,0x00000277,0x00000278,0x00000259, + 0x00000279,0x0000027a,0x0000027b,0x0000027c,0x000001b8,0x000001f8,0x0000027d,0x0000027e, + 0x0000027f,0x00000280,0x0000016f,0x0000018a,0x00000281,0x00000282,0x00000283,0x00000284, + 0x00000285,0x0000018a,0x00000286,0x00000287,0x000001ce,0x00000288,0x000001c3,0x0000024c, + 0x00000289,0x0000028a,0x0000028b,0x0000028c,0x000001c9,0x0000028d,0x0000028e,0x00000205, + 0x0000028f,0x00000290,0x00000174,0x00000291,0x00000292,0x00000293,0x00000294,0x00000295, + 0x00000296,0x00000291,0x00000297,0x00000298,0x00000299,0x0000029a,0x000001d4,0x0000029b, + 0x0000029c,0x0000029d,0x0000029e,0x0000029f,0x000002a0,0x0000017f,0x000002a1,0x000002a2, + 0x000002a3,0x000002a4,0x0000017a,0x000001e3,0x000002a5,0x000002a6,0x000002a7,0x000002a8, + 0x000001de,0x0000016d,0x000002a9,0x000002aa,0x000002aa,0x000002a9,0x0000016d,0x000001de, + 0x000002a8,0x000002a7,0x000002a6,0x000002a5,0x000001e3,0x0000017a,0x000002a4,0x000002a3, + 0x000002a2,0x000002a1,0x0000017f,0x000002a0,0x0000029f,0x0000029e,0x0000029d,0x0000029c, + 0x0000029b,0x000001d4,0x0000029a,0x00000299,0x00000298,0x00000297,0x00000291,0x00000296, + 0x00000295,0x00000294,0x00000293,0x00000292,0x00000291,0x00000174,0x00000290,0x0000028f, + 0x00000205,0x0000028e,0x0000028d,0x000001c9,0x0000028c,0x0000028b,0x0000028a,0x00000289, + 0x0000024c,0x000001c3,0x00000288,0x000001ce,0x00000287,0x00000286,0x0000018a,0x00000285, + 0x00000284,0x00000283,0x00000282,0x00000281,0x0000018a,0x0000016f,0x00000280,0x0000027f, + 0x0000027e,0x0000027d,0x000001f8,0x000001b8,0x0000027c,0x0000027b,0x0000027a,0x00000279, + 0x00000259,0x00000278,0x00000277,0x000001c5,0x00000276,0x00000275,0x00000259,0x000001b2, + 0x00000274,0x00000273,0x00000272,0x00000271,0x000001fc,0x000001ac,0x00000270,0x0000026f, + 0x0000026e,0x0000026d,0x00000190,0x0000026c,0x000001fb,0x0000026b,0x0000026a,0x00000269, + 0x000001fc,0x000001a6,0x000001f0,0x00000268,0x00000267,0x00000266,0x000001fc,0x0000016b, + 0x00000265,0x00000264,0x00000263,0x00000262,0x000001fc,0x0000019b,0x00000261,0x00000260, + 0x0000025f,0x0000025e,0x000001fc,0x00000195,0x0000025d,0x0000025c,0x0000025b,0x0000025a, + 0x00000259,0x0000018f,0x00000258,0x00000257,0x00000256,0x00000255,0x000001f8,0x0000018f, + 0x00000254,0x00000253,0x00000252,0x00000251,0x000001f4,0x00000189,0x00000250,0x0000024f, + 0x0000024e,0x0000024d,0x0000024c,0x00000189,0x0000024b,0x0000024a,0x00000249,0x00000236, + 0x00000184,0x00000179,0x00000248,0x00000247,0x00000246,0x0000023a,0x000001ec,0x00000179, + 0x00000245,0x00000244,0x00000243,0x00000242,0x0000017f,0x00000179,0x00000241,0x00000240, + 0x0000023f,0x0000023e,0x000001de,0x00000179,0x0000023d,0x0000023c,0x0000023b,0x0000023a, + 0x000001d9,0x000000a5,0x00000239,0x00000238,0x00000237,0x00000236,0x000001ce,0x000000a5, + 0x00000178,0x00000235,0x00000234,0x00000233,0x000001c3,0x00000179,0x00000173,0x00000232, + 0x00000231,0x00000230,0x000001b2,0x000000a5,0x0000022f,0x0000022e,0x0000022d,0x0000022c, + 0x0000016b,0x0004002b,0x00000006,0x000002bd,0x3ee66666,0x0004002b,0x00000006,0x000002bf, + 0x400e38e4,0x00040020,0x000002c5,0x00000006,0x00000006,0x0004003b,0x000002c5,0x000002c6, + 0x00000006,0x0004003b,0x000002c5,0x000002c9,0x00000006,0x0004003b,0x000002c5,0x000002d1, + 0x00000006,0x0004003b,0x000002c5,0x000002d4,0x00000006,0x0004002b,0x00000025,0x000002df, + 0x00000002,0x0004002b,0x00000025,0x000002e0,0x00000003,0x0004002b,0x00000025,0x000002e1, + 0x00000004,0x0004002b,0x00000025,0x000002e2,0x00000005,0x0004002b,0x00000006,0x00000384, + 0x3f000000,0x0004002b,0x00000031,0x0000038a,0x00000007,0x0004001c,0x0000038b,0x00000006, + 0x0000038a,0x00040020,0x0000038c,0x00000007,0x0000038b,0x0004002b,0x00000006,0x0000041d, + 0x42800000,0x0004002b,0x00000031,0x00000429,0x00000003,0x0005001e,0x000004d4,0x0000000e, + 0x0000000e,0x00000006,0x00040020,0x000004d5,0x00000009,0x000004d4,0x0004003b,0x000004d5, + 0x000004d6,0x00000009,0x00040020,0x000004d7,0x00000009,0x00000006,0x0004002b,0x00000006, + 0x000004e1,0x3fa00000,0x0004002b,0x00000006,0x000004e2,0x3fe00000,0x0004002b,0x00000006, + 0x000004ec,0x3ecccccd,0x0004002b,0x00000006,0x000004f0,0x3f99999a,0x0004002b,0x00000006, + 0x000004f5,0x3fcccccd,0x0004002b,0x00000006,0x000004f9,0x3fe66666,0x0004002b,0x00000006, + 0x000004ff,0x3dcccccd,0x0004002b,0x00000006,0x00000500,0x3e0f5c29,0x0004002b,0x00000006, + 0x00000504,0x3ea3d70a,0x0004002b,0x00000006,0x0000050c,0x3f19999a,0x00040017,0x00000512, + 0x00000006,0x00000002,0x00040020,0x00000513,0x00000007,0x00000512,0x00040020,0x00000515, + 0x00000009,0x0000000e,0x00040020,0x0000051e,0x00000001,0x00000512,0x0004003b,0x0000051e, + 0x0000051f,0x00000001,0x00040020,0x00000520,0x00000001,0x00000006,0x00090019,0x00000567, + 0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b, + 0x00000568,0x00000567,0x00040020,0x00000569,0x00000000,0x00000568,0x0004003b,0x00000569, + 0x0000056a,0x00000000,0x00040020,0x00000636,0x00000003,0x0000000e,0x0004003b,0x00000636, + 0x00000637,0x00000003,0x0004002b,0x00000006,0x00000641,0x41200000,0x0004002b,0x00000006, + 0x00000642,0x3f666666,0x0004002b,0x00000025,0x00000643,0x00000040,0x00050036,0x00000002, + 0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x0000000d,0x000004d3, + 0x00000007,0x0004003b,0x0000000d,0x000004db,0x00000007,0x0004003b,0x0000000d,0x000004de, + 0x00000007,0x0004003b,0x0000000d,0x000004e4,0x00000007,0x0004003b,0x0000000d,0x000004e8, + 0x00000007,0x0004003b,0x0000000d,0x000004f4,0x00000007,0x0004003b,0x0000000d,0x00000508, + 0x00000007,0x0004003b,0x00000513,0x00000514,0x00000007,0x0004003b,0x00000513,0x00000519, + 0x00000007,0x0004003b,0x0000000d,0x0000051d,0x00000007,0x0004003b,0x0000000d,0x00000527, + 0x00000007,0x0004003b,0x0000000d,0x0000052e,0x00000007,0x0004003b,0x0000000d,0x00000531, + 0x00000007,0x0004003b,0x0000000d,0x00000534,0x00000007,0x0004003b,0x0000000d,0x00000538, + 0x00000007,0x0004003b,0x00000026,0x0000053c,0x00000007,0x0004003b,0x00000026,0x00000540, + 0x00000007,0x0004003b,0x00000026,0x00000544,0x00000007,0x0004003b,0x00000026,0x0000054c, + 0x00000007,0x0004003b,0x00000513,0x00000554,0x00000007,0x0004003b,0x0000003b,0x00000564, + 0x00000007,0x0004003b,0x00000008,0x0000056e,0x00000007,0x0004003b,0x0000001b,0x00000576, + 0x00000007,0x0004003b,0x0000000d,0x00000577,0x00000007,0x0004003b,0x0000000d,0x0000057a, + 0x00000007,0x0004003b,0x0000000d,0x0000057d,0x00000007,0x0004003b,0x0000000d,0x00000580, + 0x00000007,0x0004003b,0x0000000d,0x00000583,0x00000007,0x0004003b,0x0000000d,0x00000586, + 0x00000007,0x0004003b,0x0000000d,0x00000589,0x00000007,0x0004003b,0x0000000d,0x0000058c, + 0x00000007,0x0004003b,0x0000000d,0x0000058f,0x00000007,0x0004003b,0x0000001b,0x00000593, + 0x00000007,0x0004003b,0x0000000d,0x00000594,0x00000007,0x0004003b,0x0000000d,0x00000597, + 0x00000007,0x0004003b,0x0000000d,0x0000059a,0x00000007,0x0004003b,0x0000000d,0x0000059d, + 0x00000007,0x0004003b,0x0000000d,0x000005a0,0x00000007,0x0004003b,0x0000000d,0x000005a3, + 0x00000007,0x0004003b,0x0000000d,0x000005a6,0x00000007,0x0004003b,0x0000000d,0x000005a9, + 0x00000007,0x0004003b,0x0000000d,0x000005ac,0x00000007,0x0004003b,0x0000001b,0x000005b0, + 0x00000007,0x0004003b,0x0000000d,0x000005b1,0x00000007,0x0004003b,0x0000000d,0x000005b4, + 0x00000007,0x0004003b,0x0000000d,0x000005b7,0x00000007,0x0004003b,0x0000000d,0x000005ba, + 0x00000007,0x0004003b,0x0000000d,0x000005bd,0x00000007,0x0004003b,0x0000000d,0x000005c0, + 0x00000007,0x0004003b,0x0000000d,0x000005c3,0x00000007,0x0004003b,0x0000000d,0x000005c6, + 0x00000007,0x0004003b,0x0000000d,0x000005c9,0x00000007,0x0004003b,0x0000001b,0x000005cd, + 0x00000007,0x0004003b,0x0000000d,0x000005ce,0x00000007,0x0004003b,0x0000000d,0x000005d1, + 0x00000007,0x0004003b,0x0000000d,0x000005d4,0x00000007,0x0004003b,0x0000000d,0x000005d7, + 0x00000007,0x0004003b,0x0000000d,0x000005da,0x00000007,0x0004003b,0x0000000d,0x000005dd, + 0x00000007,0x0004003b,0x0000000d,0x000005e0,0x00000007,0x0004003b,0x0000000d,0x000005e3, + 0x00000007,0x0004003b,0x0000000d,0x000005e6,0x00000007,0x0004003b,0x0000001b,0x000005ea, + 0x00000007,0x0004003b,0x0000001b,0x000005eb,0x00000007,0x0004003b,0x0000001b,0x000005ed, + 0x00000007,0x0004003b,0x0000001b,0x000005ef,0x00000007,0x0004003b,0x0000001b,0x000005f1, + 0x00000007,0x0004003b,0x0000000d,0x000005f3,0x00000007,0x0004003b,0x0000000d,0x000005f5, + 0x00000007,0x0004003b,0x0000000d,0x000005f8,0x00000007,0x0004003b,0x0000000d,0x00000605, + 0x00000007,0x0004003b,0x0000003b,0x00000606,0x00000007,0x0004003b,0x00000026,0x00000608, + 0x00000007,0x0004003b,0x00000026,0x0000060a,0x00000007,0x0004003b,0x0000003b,0x0000060f, + 0x00000007,0x0004003b,0x0000000d,0x00000611,0x00000007,0x0004003b,0x0000000d,0x00000613, + 0x00000007,0x0004003b,0x00000026,0x00000615,0x00000007,0x0004003b,0x00000026,0x00000617, + 0x00000007,0x0004003b,0x0000001b,0x00000619,0x00000007,0x0004003b,0x0000001b,0x0000061e, + 0x00000007,0x0004003b,0x0000000d,0x00000622,0x00000007,0x0004003b,0x00000008,0x00000623, + 0x00000007,0x0004003b,0x0000000d,0x00000627,0x00000007,0x00050041,0x000004d7,0x000004d8, + 0x000004d6,0x000002df,0x0004003d,0x00000006,0x000004d9,0x000004d8,0x0008000c,0x00000006, + 0x000004da,0x00000001,0x0000002b,0x000004d9,0x000000a5,0x000000b2,0x0003003e,0x000004d3, + 0x000004da,0x0004003d,0x00000006,0x000004dc,0x000004d3,0x00050083,0x00000006,0x000004dd, + 0x000004dc,0x00000384,0x0003003e,0x000004db,0x000004dd,0x0004003d,0x00000006,0x000004df, + 0x000004db,0x000500be,0x000000a6,0x000004e0,0x000004df,0x000000a5,0x000600a9,0x00000006, + 0x000004e3,0x000004e0,0x000004e1,0x000004e2,0x0003003e,0x000004de,0x000004e3,0x0004003d, + 0x00000006,0x000004e5,0x000004db,0x000500be,0x000000a6,0x000004e6,0x000004e5,0x000000a5, + 0x000600a9,0x00000006,0x000004e7,0x000004e6,0x000004e1,0x000000b2,0x0003003e,0x000004e4, + 0x000004e7,0x0004003d,0x00000006,0x000004e9,0x000004db,0x000500be,0x000000a6,0x000004ea, + 0x000004e9,0x000000a5,0x000600a9,0x00000006,0x000004eb,0x000004ea,0x000004e1,0x000000b2, + 0x0003003e,0x000004e8,0x000004eb,0x0004003d,0x00000006,0x000004ed,0x000004db,0x0004003d, + 0x00000006,0x000004ee,0x000004e4,0x00050085,0x00000006,0x000004ef,0x000004ed,0x000004ee, + 0x00050085,0x00000006,0x000004f1,0x000004ef,0x000004f0,0x00050081,0x00000006,0x000004f2, + 0x000004ec,0x000004f1,0x0007000c,0x00000006,0x000004f3,0x00000001,0x00000028,0x000000a5, + 0x000004f2,0x0003003e,0x000002c9,0x000004f3,0x0004003d,0x00000006,0x000004f6,0x000004db, + 0x0004003d,0x00000006,0x000004f7,0x000004de,0x00050085,0x00000006,0x000004f8,0x000004f6, + 0x000004f7,0x00050085,0x00000006,0x000004fa,0x000004f8,0x000004f9,0x00050081,0x00000006, + 0x000004fb,0x000004f5,0x000004fa,0x0003003e,0x000004f4,0x000004fb,0x0004003d,0x00000006, + 0x000004fc,0x000004f4,0x0004003d,0x00000006,0x000004fd,0x000002c9,0x00050083,0x00000006, + 0x000004fe,0x000004fc,0x000004fd,0x0003003e,0x000002c6,0x000004fe,0x0004003d,0x00000006, + 0x00000501,0x000004db,0x0004003d,0x00000006,0x00000502,0x000004e8,0x00050085,0x00000006, + 0x00000503,0x00000501,0x00000502,0x00050085,0x00000006,0x00000505,0x00000503,0x00000504, + 0x00050081,0x00000006,0x00000506,0x00000500,0x00000505,0x0007000c,0x00000006,0x00000507, + 0x00000001,0x00000028,0x000004ff,0x00000506,0x0003003e,0x000002d4,0x00000507,0x0004003d, + 0x00000006,0x00000509,0x000004db,0x0004003d,0x00000006,0x0000050a,0x000004e8,0x00050085, + 0x00000006,0x0000050b,0x00000509,0x0000050a,0x00050085,0x00000006,0x0000050d,0x0000050b, + 0x0000050c,0x00050081,0x00000006,0x0000050e,0x00000384,0x0000050d,0x0003003e,0x00000508, + 0x0000050e,0x0004003d,0x00000006,0x0000050f,0x00000508,0x0004003d,0x00000006,0x00000510, + 0x000002d4,0x00050083,0x00000006,0x00000511,0x0000050f,0x00000510,0x0003003e,0x000002d1, + 0x00000511,0x00050041,0x00000515,0x00000516,0x000004d6,0x0000021f,0x0004003d,0x0000000e, + 0x00000517,0x00000516,0x0007004f,0x00000512,0x00000518,0x00000517,0x00000517,0x00000002, + 0x00000003,0x0003003e,0x00000514,0x00000518,0x00050041,0x00000515,0x0000051a,0x000004d6, + 0x0000021f,0x0004003d,0x0000000e,0x0000051b,0x0000051a,0x0007004f,0x00000512,0x0000051c, + 0x0000051b,0x0000051b,0x00000000,0x00000001,0x0003003e,0x00000519,0x0000051c,0x00050041, + 0x00000520,0x00000521,0x0000051f,0x0000004c,0x0004003d,0x00000006,0x00000522,0x00000521, + 0x00050041,0x0000000d,0x00000523,0x00000514,0x0000004c,0x0004003d,0x00000006,0x00000524, + 0x00000523,0x00050085,0x00000006,0x00000525,0x00000522,0x00000524,0x00050083,0x00000006, + 0x00000526,0x00000525,0x00000384,0x0003003e,0x0000051d,0x00000526,0x00050041,0x00000520, + 0x00000528,0x0000051f,0x00000051,0x0004003d,0x00000006,0x00000529,0x00000528,0x00050041, + 0x0000000d,0x0000052a,0x00000514,0x00000051,0x0004003d,0x00000006,0x0000052b,0x0000052a, + 0x00050085,0x00000006,0x0000052c,0x00000529,0x0000052b,0x00050083,0x00000006,0x0000052d, + 0x0000052c,0x00000384,0x0003003e,0x00000527,0x0000052d,0x0004003d,0x00000006,0x0000052f, + 0x0000051d,0x0006000c,0x00000006,0x00000530,0x00000001,0x00000008,0x0000052f,0x0003003e, + 0x0000052e,0x00000530,0x0004003d,0x00000006,0x00000532,0x00000527,0x0006000c,0x00000006, + 0x00000533,0x00000001,0x00000008,0x00000532,0x0003003e,0x00000531,0x00000533,0x0004003d, + 0x00000006,0x00000535,0x0000051d,0x0004003d,0x00000006,0x00000536,0x0000052e,0x00050083, + 0x00000006,0x00000537,0x00000535,0x00000536,0x0003003e,0x00000534,0x00000537,0x0004003d, + 0x00000006,0x00000539,0x00000527,0x0004003d,0x00000006,0x0000053a,0x00000531,0x00050083, + 0x00000006,0x0000053b,0x00000539,0x0000053a,0x0003003e,0x00000538,0x0000053b,0x0004003d, + 0x00000006,0x0000053d,0x00000534,0x00050085,0x00000006,0x0000053e,0x0000053d,0x0000041d, + 0x0004006e,0x00000025,0x0000053f,0x0000053e,0x0003003e,0x0000053c,0x0000053f,0x0004003d, + 0x00000006,0x00000541,0x00000538,0x00050085,0x00000006,0x00000542,0x00000541,0x0000041d, + 0x0004006e,0x00000025,0x00000543,0x00000542,0x0003003e,0x00000540,0x00000543,0x0003003e, + 0x00000544,0x0000015c,0x000200f9,0x00000545,0x000200f8,0x00000545,0x000400f6,0x00000547, + 0x00000548,0x00000000,0x000200f9,0x00000549,0x000200f8,0x00000549,0x0004003d,0x00000025, + 0x0000054a,0x00000544,0x000500b1,0x000000a6,0x0000054b,0x0000054a,0x00000167,0x000400fa, + 0x0000054b,0x00000546,0x00000547,0x000200f8,0x00000546,0x0003003e,0x0000054c,0x0000015c, + 0x000200f9,0x0000054d,0x000200f8,0x0000054d,0x000400f6,0x0000054f,0x00000550,0x00000000, + 0x000200f9,0x00000551,0x000200f8,0x00000551,0x0004003d,0x00000025,0x00000552,0x0000054c, + 0x000500b1,0x000000a6,0x00000553,0x00000552,0x00000167,0x000400fa,0x00000553,0x0000054e, + 0x0000054f,0x000200f8,0x0000054e,0x0004003d,0x00000006,0x00000555,0x0000052e,0x0004003d, + 0x00000025,0x00000556,0x0000054c,0x0004006f,0x00000006,0x00000557,0x00000556,0x00050081, + 0x00000006,0x00000558,0x00000555,0x00000557,0x00050083,0x00000006,0x00000559,0x00000558, + 0x00000152,0x0004003d,0x00000006,0x0000055a,0x00000531,0x0004003d,0x00000025,0x0000055b, + 0x00000544,0x0004006f,0x00000006,0x0000055c,0x0000055b,0x00050081,0x00000006,0x0000055d, + 0x0000055a,0x0000055c,0x00050083,0x00000006,0x0000055e,0x0000055d,0x00000152,0x00050050, + 0x00000512,0x0000055f,0x00000559,0x0000055e,0x00050050,0x00000512,0x00000560,0x00000384, + 0x00000384,0x00050081,0x00000512,0x00000561,0x0000055f,0x00000560,0x0004003d,0x00000512, + 0x00000562,0x00000519,0x00050085,0x00000512,0x00000563,0x00000561,0x00000562,0x0003003e, + 0x00000554,0x00000563,0x0004003d,0x00000025,0x00000565,0x00000544,0x0004003d,0x00000025, + 0x00000566,0x0000054c,0x0004003d,0x00000568,0x0000056b,0x0000056a,0x0004003d,0x00000512, + 0x0000056c,0x00000554,0x00070058,0x0000000e,0x0000056d,0x0000056b,0x0000056c,0x00000002, + 0x000000a5,0x0008004f,0x00000007,0x0000056f,0x0000056d,0x0000056d,0x00000000,0x00000001, + 0x00000002,0x0003003e,0x0000056e,0x0000056f,0x00050039,0x00000006,0x00000570,0x0000000b, + 0x0000056e,0x00060041,0x0000000d,0x00000571,0x00000564,0x00000565,0x00000566,0x0003003e, + 0x00000571,0x00000570,0x000200f9,0x00000550,0x000200f8,0x00000550,0x0004003d,0x00000025, + 0x00000572,0x0000054c,0x00050080,0x00000025,0x00000573,0x00000572,0x0000021f,0x0003003e, + 0x0000054c,0x00000573,0x000200f9,0x0000054d,0x000200f8,0x0000054f,0x000200f9,0x00000548, + 0x000200f8,0x00000548,0x0004003d,0x00000025,0x00000574,0x00000544,0x00050080,0x00000025, + 0x00000575,0x00000574,0x0000021f,0x0003003e,0x00000544,0x00000575,0x000200f9,0x00000545, + 0x000200f8,0x00000547,0x00060041,0x0000000d,0x00000578,0x00000564,0x0000021f,0x0000021f, + 0x0004003d,0x00000006,0x00000579,0x00000578,0x0003003e,0x00000577,0x00000579,0x00060041, + 0x0000000d,0x0000057b,0x00000564,0x0000021f,0x000002df,0x0004003d,0x00000006,0x0000057c, + 0x0000057b,0x0003003e,0x0000057a,0x0000057c,0x00060041,0x0000000d,0x0000057e,0x00000564, + 0x0000021f,0x000002e0,0x0004003d,0x00000006,0x0000057f,0x0000057e,0x0003003e,0x0000057d, + 0x0000057f,0x00060041,0x0000000d,0x00000581,0x00000564,0x000002df,0x0000021f,0x0004003d, + 0x00000006,0x00000582,0x00000581,0x0003003e,0x00000580,0x00000582,0x00060041,0x0000000d, + 0x00000584,0x00000564,0x000002df,0x000002df,0x0004003d,0x00000006,0x00000585,0x00000584, + 0x0003003e,0x00000583,0x00000585,0x00060041,0x0000000d,0x00000587,0x00000564,0x000002df, + 0x000002e0,0x0004003d,0x00000006,0x00000588,0x00000587,0x0003003e,0x00000586,0x00000588, + 0x00060041,0x0000000d,0x0000058a,0x00000564,0x000002e0,0x0000021f,0x0004003d,0x00000006, + 0x0000058b,0x0000058a,0x0003003e,0x00000589,0x0000058b,0x00060041,0x0000000d,0x0000058d, + 0x00000564,0x000002e0,0x000002df,0x0004003d,0x00000006,0x0000058e,0x0000058d,0x0003003e, + 0x0000058c,0x0000058e,0x00060041,0x0000000d,0x00000590,0x00000564,0x000002e0,0x000002e0, + 0x0004003d,0x00000006,0x00000591,0x00000590,0x0003003e,0x0000058f,0x00000591,0x000d0039, + 0x0000000e,0x00000592,0x00000019,0x00000577,0x0000057a,0x0000057d,0x00000580,0x00000583, + 0x00000586,0x00000589,0x0000058c,0x0000058f,0x0003003e,0x00000576,0x00000592,0x00060041, + 0x0000000d,0x00000595,0x00000564,0x0000021f,0x000002df,0x0004003d,0x00000006,0x00000596, + 0x00000595,0x0003003e,0x00000594,0x00000596,0x00060041,0x0000000d,0x00000598,0x00000564, + 0x0000021f,0x000002e0,0x0004003d,0x00000006,0x00000599,0x00000598,0x0003003e,0x00000597, + 0x00000599,0x00060041,0x0000000d,0x0000059b,0x00000564,0x0000021f,0x000002e1,0x0004003d, + 0x00000006,0x0000059c,0x0000059b,0x0003003e,0x0000059a,0x0000059c,0x00060041,0x0000000d, + 0x0000059e,0x00000564,0x000002df,0x000002df,0x0004003d,0x00000006,0x0000059f,0x0000059e, + 0x0003003e,0x0000059d,0x0000059f,0x00060041,0x0000000d,0x000005a1,0x00000564,0x000002df, + 0x000002e0,0x0004003d,0x00000006,0x000005a2,0x000005a1,0x0003003e,0x000005a0,0x000005a2, + 0x00060041,0x0000000d,0x000005a4,0x00000564,0x000002df,0x000002e1,0x0004003d,0x00000006, + 0x000005a5,0x000005a4,0x0003003e,0x000005a3,0x000005a5,0x00060041,0x0000000d,0x000005a7, + 0x00000564,0x000002e0,0x000002df,0x0004003d,0x00000006,0x000005a8,0x000005a7,0x0003003e, + 0x000005a6,0x000005a8,0x00060041,0x0000000d,0x000005aa,0x00000564,0x000002e0,0x000002e0, + 0x0004003d,0x00000006,0x000005ab,0x000005aa,0x0003003e,0x000005a9,0x000005ab,0x00060041, + 0x0000000d,0x000005ad,0x00000564,0x000002e0,0x000002e1,0x0004003d,0x00000006,0x000005ae, + 0x000005ad,0x0003003e,0x000005ac,0x000005ae,0x000d0039,0x0000000e,0x000005af,0x00000019, + 0x00000594,0x00000597,0x0000059a,0x0000059d,0x000005a0,0x000005a3,0x000005a6,0x000005a9, + 0x000005ac,0x0003003e,0x00000593,0x000005af,0x00060041,0x0000000d,0x000005b2,0x00000564, + 0x000002df,0x0000021f,0x0004003d,0x00000006,0x000005b3,0x000005b2,0x0003003e,0x000005b1, + 0x000005b3,0x00060041,0x0000000d,0x000005b5,0x00000564,0x000002df,0x000002df,0x0004003d, + 0x00000006,0x000005b6,0x000005b5,0x0003003e,0x000005b4,0x000005b6,0x00060041,0x0000000d, + 0x000005b8,0x00000564,0x000002df,0x000002e0,0x0004003d,0x00000006,0x000005b9,0x000005b8, + 0x0003003e,0x000005b7,0x000005b9,0x00060041,0x0000000d,0x000005bb,0x00000564,0x000002e0, + 0x0000021f,0x0004003d,0x00000006,0x000005bc,0x000005bb,0x0003003e,0x000005ba,0x000005bc, + 0x00060041,0x0000000d,0x000005be,0x00000564,0x000002e0,0x000002df,0x0004003d,0x00000006, + 0x000005bf,0x000005be,0x0003003e,0x000005bd,0x000005bf,0x00060041,0x0000000d,0x000005c1, + 0x00000564,0x000002e0,0x000002e0,0x0004003d,0x00000006,0x000005c2,0x000005c1,0x0003003e, + 0x000005c0,0x000005c2,0x00060041,0x0000000d,0x000005c4,0x00000564,0x000002e1,0x0000021f, + 0x0004003d,0x00000006,0x000005c5,0x000005c4,0x0003003e,0x000005c3,0x000005c5,0x00060041, + 0x0000000d,0x000005c7,0x00000564,0x000002e1,0x000002df,0x0004003d,0x00000006,0x000005c8, + 0x000005c7,0x0003003e,0x000005c6,0x000005c8,0x00060041,0x0000000d,0x000005ca,0x00000564, + 0x000002e1,0x000002e0,0x0004003d,0x00000006,0x000005cb,0x000005ca,0x0003003e,0x000005c9, + 0x000005cb,0x000d0039,0x0000000e,0x000005cc,0x00000019,0x000005b1,0x000005b4,0x000005b7, + 0x000005ba,0x000005bd,0x000005c0,0x000005c3,0x000005c6,0x000005c9,0x0003003e,0x000005b0, + 0x000005cc,0x00060041,0x0000000d,0x000005cf,0x00000564,0x000002df,0x000002df,0x0004003d, + 0x00000006,0x000005d0,0x000005cf,0x0003003e,0x000005ce,0x000005d0,0x00060041,0x0000000d, + 0x000005d2,0x00000564,0x000002df,0x000002e0,0x0004003d,0x00000006,0x000005d3,0x000005d2, + 0x0003003e,0x000005d1,0x000005d3,0x00060041,0x0000000d,0x000005d5,0x00000564,0x000002df, + 0x000002e1,0x0004003d,0x00000006,0x000005d6,0x000005d5,0x0003003e,0x000005d4,0x000005d6, + 0x00060041,0x0000000d,0x000005d8,0x00000564,0x000002e0,0x000002df,0x0004003d,0x00000006, + 0x000005d9,0x000005d8,0x0003003e,0x000005d7,0x000005d9,0x00060041,0x0000000d,0x000005db, + 0x00000564,0x000002e0,0x000002e0,0x0004003d,0x00000006,0x000005dc,0x000005db,0x0003003e, + 0x000005da,0x000005dc,0x00060041,0x0000000d,0x000005de,0x00000564,0x000002e0,0x000002e1, + 0x0004003d,0x00000006,0x000005df,0x000005de,0x0003003e,0x000005dd,0x000005df,0x00060041, + 0x0000000d,0x000005e1,0x00000564,0x000002e1,0x000002df,0x0004003d,0x00000006,0x000005e2, + 0x000005e1,0x0003003e,0x000005e0,0x000005e2,0x00060041,0x0000000d,0x000005e4,0x00000564, + 0x000002e1,0x000002e0,0x0004003d,0x00000006,0x000005e5,0x000005e4,0x0003003e,0x000005e3, + 0x000005e5,0x00060041,0x0000000d,0x000005e7,0x00000564,0x000002e1,0x000002e1,0x0004003d, + 0x00000006,0x000005e8,0x000005e7,0x0003003e,0x000005e6,0x000005e8,0x000d0039,0x0000000e, + 0x000005e9,0x00000019,0x000005ce,0x000005d1,0x000005d4,0x000005d7,0x000005da,0x000005dd, + 0x000005e0,0x000005e3,0x000005e6,0x0003003e,0x000005cd,0x000005e9,0x0004003d,0x0000000e, + 0x000005ec,0x00000576,0x0003003e,0x000005eb,0x000005ec,0x0004003d,0x0000000e,0x000005ee, + 0x00000593,0x0003003e,0x000005ed,0x000005ee,0x0004003d,0x0000000e,0x000005f0,0x000005b0, + 0x0003003e,0x000005ef,0x000005f0,0x0004003d,0x0000000e,0x000005f2,0x000005cd,0x0003003e, + 0x000005f1,0x000005f2,0x0004003d,0x00000006,0x000005f4,0x00000534,0x0003003e,0x000005f3, + 0x000005f4,0x0004003d,0x00000006,0x000005f6,0x00000538,0x0003003e,0x000005f5,0x000005f6, + 0x000a0039,0x0000000e,0x000005f7,0x00000023,0x000005eb,0x000005ed,0x000005ef,0x000005f1, + 0x000005f3,0x000005f5,0x0003003e,0x000005ea,0x000005f7,0x00050041,0x0000000d,0x000005f9, + 0x000005ea,0x0000004c,0x0004003d,0x00000006,0x000005fa,0x000005f9,0x00050083,0x00000006, + 0x000005fb,0x000000b2,0x000005fa,0x00050041,0x0000000d,0x000005fc,0x000005ea,0x00000051, + 0x0004003d,0x00000006,0x000005fd,0x000005fc,0x00050083,0x00000006,0x000005fe,0x000005fb, + 0x000005fd,0x00050041,0x0000000d,0x000005ff,0x000005ea,0x00000057,0x0004003d,0x00000006, + 0x00000600,0x000005ff,0x00050083,0x00000006,0x00000601,0x000005fe,0x00000600,0x00050041, + 0x0000000d,0x00000602,0x000005ea,0x00000429,0x0004003d,0x00000006,0x00000603,0x00000602, + 0x00050083,0x00000006,0x00000604,0x00000601,0x00000603,0x0003003e,0x000005f8,0x00000604, + 0x0004003d,0x0000003a,0x00000607,0x00000564,0x0003003e,0x00000606,0x00000607,0x0004003d, + 0x00000025,0x00000609,0x0000053c,0x0003003e,0x00000608,0x00000609,0x0004003d,0x00000025, + 0x0000060b,0x00000540,0x0003003e,0x0000060a,0x0000060b,0x00070039,0x00000006,0x0000060c, + 0x00000040,0x00000606,0x00000608,0x0000060a,0x0004003d,0x00000006,0x0000060d,0x000005f8, + 0x00050085,0x00000006,0x0000060e,0x0000060c,0x0000060d,0x0003003e,0x00000605,0x0000060e, + 0x0004003d,0x0000003a,0x00000610,0x00000564,0x0003003e,0x0000060f,0x00000610,0x0004003d, + 0x00000006,0x00000612,0x00000534,0x0003003e,0x00000611,0x00000612,0x0004003d,0x00000006, + 0x00000614,0x00000538,0x0003003e,0x00000613,0x00000614,0x0004003d,0x00000025,0x00000616, + 0x0000053c,0x0003003e,0x00000615,0x00000616,0x0004003d,0x00000025,0x00000618,0x00000540, + 0x0003003e,0x00000617,0x00000618,0x0004003d,0x0000000e,0x0000061a,0x000005ea,0x0003003e, + 0x00000619,0x0000061a,0x000a0039,0x00000006,0x0000061b,0x00000049,0x0000060f,0x00000611, + 0x00000613,0x00000615,0x00000617,0x00000619,0x0004003d,0x00000006,0x0000061c,0x00000605, + 0x00050081,0x00000006,0x0000061d,0x0000061c,0x0000061b,0x0003003e,0x00000605,0x0000061d, + 0x0004003d,0x00000568,0x0000061f,0x0000056a,0x0004003d,0x00000512,0x00000620,0x0000051f, + 0x00050057,0x0000000e,0x00000621,0x0000061f,0x00000620,0x0003003e,0x0000061e,0x00000621, + 0x0004003d,0x0000000e,0x00000624,0x0000061e,0x0008004f,0x00000007,0x00000625,0x00000624, + 0x00000624,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000623,0x00000625,0x00050039, + 0x00000006,0x00000626,0x0000000b,0x00000623,0x0003003e,0x00000622,0x00000626,0x0004003d, + 0x00000006,0x00000628,0x00000605,0x0004003d,0x00000006,0x00000629,0x00000622,0x00050083, + 0x00000006,0x0000062a,0x00000628,0x00000629,0x0003003e,0x00000627,0x0000062a,0x0004003d, + 0x00000006,0x0000062b,0x00000627,0x00060050,0x00000007,0x0000062c,0x0000062b,0x0000062b, + 0x0000062b,0x0004003d,0x0000000e,0x0000062d,0x0000061e,0x0008004f,0x00000007,0x0000062e, + 0x0000062d,0x0000062d,0x00000000,0x00000001,0x00000002,0x00050081,0x00000007,0x0000062f, + 0x0000062e,0x0000062c,0x00050041,0x0000000d,0x00000630,0x0000061e,0x0000004c,0x00050051, + 0x00000006,0x00000631,0x0000062f,0x00000000,0x0003003e,0x00000630,0x00000631,0x00050041, + 0x0000000d,0x00000632,0x0000061e,0x00000051,0x00050051,0x00000006,0x00000633,0x0000062f, + 0x00000001,0x0003003e,0x00000632,0x00000633,0x00050041,0x0000000d,0x00000634,0x0000061e, + 0x00000057,0x00050051,0x00000006,0x00000635,0x0000062f,0x00000002,0x0003003e,0x00000634, + 0x00000635,0x0004003d,0x0000000e,0x00000638,0x0000061e,0x0008004f,0x00000007,0x00000639, + 0x00000638,0x00000638,0x00000000,0x00000001,0x00000002,0x00060050,0x00000007,0x0000063a, + 0x000000a5,0x000000a5,0x000000a5,0x00060050,0x00000007,0x0000063b,0x000000b2,0x000000b2, + 0x000000b2,0x0008000c,0x00000007,0x0000063c,0x00000001,0x0000002b,0x00000639,0x0000063a, + 0x0000063b,0x00050051,0x00000006,0x0000063d,0x0000063c,0x00000000,0x00050051,0x00000006, + 0x0000063e,0x0000063c,0x00000001,0x00050051,0x00000006,0x0000063f,0x0000063c,0x00000002, + 0x00070050,0x0000000e,0x00000640,0x0000063d,0x0000063e,0x0000063f,0x000000b2,0x0003003e, + 0x00000637,0x00000640,0x000100fd,0x00010038,0x00050036,0x00000006,0x0000000b,0x00000000, + 0x00000009,0x00030037,0x00000008,0x0000000a,0x000200f8,0x0000000c,0x00050041,0x0000000d, + 0x0000004d,0x0000000a,0x0000004c,0x0004003d,0x00000006,0x0000004e,0x0000004d,0x00050085, + 0x00000006,0x0000004f,0x0000004b,0x0000004e,0x00050041,0x0000000d,0x00000052,0x0000000a, + 0x00000051,0x0004003d,0x00000006,0x00000053,0x00000052,0x00050085,0x00000006,0x00000054, + 0x00000050,0x00000053,0x00050081,0x00000006,0x00000055,0x0000004f,0x00000054,0x00050041, + 0x0000000d,0x00000058,0x0000000a,0x00000057,0x0004003d,0x00000006,0x00000059,0x00000058, + 0x00050085,0x00000006,0x0000005a,0x00000056,0x00000059,0x00050081,0x00000006,0x0000005b, + 0x00000055,0x0000005a,0x000200fe,0x0000005b,0x00010038,0x00050036,0x0000000e,0x00000019, + 0x00000000,0x0000000f,0x00030037,0x0000000d,0x00000010,0x00030037,0x0000000d,0x00000011, + 0x00030037,0x0000000d,0x00000012,0x00030037,0x0000000d,0x00000013,0x00030037,0x0000000d, + 0x00000014,0x00030037,0x0000000d,0x00000015,0x00030037,0x0000000d,0x00000016,0x00030037, + 0x0000000d,0x00000017,0x00030037,0x0000000d,0x00000018,0x000200f8,0x0000001a,0x0004003b, + 0x0000000d,0x0000005e,0x00000007,0x0004003b,0x0000000d,0x0000006b,0x00000007,0x0004003b, + 0x0000000d,0x00000078,0x00000007,0x0004003b,0x0000000d,0x00000085,0x00000007,0x0004003b, + 0x0000000d,0x00000092,0x00000007,0x0004003b,0x0000000d,0x00000096,0x00000007,0x0004003b, + 0x0000000d,0x0000009a,0x00000007,0x0004003b,0x0000000d,0x0000009e,0x00000007,0x0004003b, + 0x0000000d,0x000000ac,0x00000007,0x0004003b,0x0000000d,0x000000b4,0x00000007,0x0004003b, + 0x000000b7,0x000000b8,0x00000007,0x0004003b,0x000000b7,0x000000c6,0x00000007,0x0004003b, + 0x000000b7,0x000000d2,0x00000007,0x0004003b,0x000000b7,0x000000d6,0x00000007,0x0004003b, + 0x0000000d,0x000000da,0x00000007,0x0004003b,0x0000000d,0x000000e0,0x00000007,0x0004003b, + 0x0000000d,0x000000e6,0x00000007,0x0004003b,0x0000000d,0x000000ec,0x00000007,0x0004003b, + 0x0000000d,0x000000f3,0x00000007,0x0004003b,0x0000000d,0x000000f9,0x00000007,0x0004003d, + 0x00000006,0x0000005f,0x00000010,0x0004003d,0x00000006,0x00000060,0x00000011,0x00050081, + 0x00000006,0x00000061,0x0000005f,0x00000060,0x0004003d,0x00000006,0x00000062,0x00000012, + 0x00050081,0x00000006,0x00000063,0x00000061,0x00000062,0x0004003d,0x00000006,0x00000064, + 0x00000016,0x00050083,0x00000006,0x00000065,0x00000063,0x00000064,0x0004003d,0x00000006, + 0x00000066,0x00000017,0x00050083,0x00000006,0x00000067,0x00000065,0x00000066,0x0004003d, + 0x00000006,0x00000068,0x00000018,0x00050083,0x00000006,0x00000069,0x00000067,0x00000068, + 0x0006000c,0x00000006,0x0000006a,0x00000001,0x00000004,0x00000069,0x0003003e,0x0000005e, + 0x0000006a,0x0004003d,0x00000006,0x0000006c,0x00000013,0x0004003d,0x00000006,0x0000006d, + 0x00000010,0x00050081,0x00000006,0x0000006e,0x0000006c,0x0000006d,0x0004003d,0x00000006, + 0x0000006f,0x00000011,0x00050081,0x00000006,0x00000070,0x0000006e,0x0000006f,0x0004003d, + 0x00000006,0x00000071,0x00000017,0x00050083,0x00000006,0x00000072,0x00000070,0x00000071, + 0x0004003d,0x00000006,0x00000073,0x00000018,0x00050083,0x00000006,0x00000074,0x00000072, + 0x00000073,0x0004003d,0x00000006,0x00000075,0x00000015,0x00050083,0x00000006,0x00000076, + 0x00000074,0x00000075,0x0006000c,0x00000006,0x00000077,0x00000001,0x00000004,0x00000076, + 0x0003003e,0x0000006b,0x00000077,0x0004003d,0x00000006,0x00000079,0x00000010,0x0004003d, + 0x00000006,0x0000007a,0x00000013,0x00050081,0x00000006,0x0000007b,0x00000079,0x0000007a, + 0x0004003d,0x00000006,0x0000007c,0x00000016,0x00050081,0x00000006,0x0000007d,0x0000007b, + 0x0000007c,0x0004003d,0x00000006,0x0000007e,0x00000012,0x00050083,0x00000006,0x0000007f, + 0x0000007d,0x0000007e,0x0004003d,0x00000006,0x00000080,0x00000015,0x00050083,0x00000006, + 0x00000081,0x0000007f,0x00000080,0x0004003d,0x00000006,0x00000082,0x00000018,0x00050083, + 0x00000006,0x00000083,0x00000081,0x00000082,0x0006000c,0x00000006,0x00000084,0x00000001, + 0x00000004,0x00000083,0x0003003e,0x00000078,0x00000084,0x0004003d,0x00000006,0x00000086, + 0x00000013,0x0004003d,0x00000006,0x00000087,0x00000016,0x00050081,0x00000006,0x00000088, + 0x00000086,0x00000087,0x0004003d,0x00000006,0x00000089,0x00000017,0x00050081,0x00000006, + 0x0000008a,0x00000088,0x00000089,0x0004003d,0x00000006,0x0000008b,0x00000011,0x00050083, + 0x00000006,0x0000008c,0x0000008a,0x0000008b,0x0004003d,0x00000006,0x0000008d,0x00000012, + 0x00050083,0x00000006,0x0000008e,0x0000008c,0x0000008d,0x0004003d,0x00000006,0x0000008f, + 0x00000015,0x00050083,0x00000006,0x00000090,0x0000008e,0x0000008f,0x0006000c,0x00000006, + 0x00000091,0x00000001,0x00000004,0x00000090,0x0003003e,0x00000085,0x00000091,0x0004003d, + 0x00000006,0x00000093,0x0000005e,0x0004003d,0x00000006,0x00000094,0x00000078,0x0007000c, + 0x00000006,0x00000095,0x00000001,0x00000028,0x00000093,0x00000094,0x0003003e,0x00000092, + 0x00000095,0x0004003d,0x00000006,0x00000097,0x0000005e,0x0004003d,0x00000006,0x00000098, + 0x00000078,0x0007000c,0x00000006,0x00000099,0x00000001,0x00000025,0x00000097,0x00000098, + 0x0003003e,0x00000096,0x00000099,0x0004003d,0x00000006,0x0000009b,0x0000006b,0x0004003d, + 0x00000006,0x0000009c,0x00000085,0x0007000c,0x00000006,0x0000009d,0x00000001,0x00000028, + 0x0000009b,0x0000009c,0x0003003e,0x0000009a,0x0000009d,0x0004003d,0x00000006,0x0000009f, + 0x0000006b,0x0004003d,0x00000006,0x000000a0,0x00000085,0x0007000c,0x00000006,0x000000a1, + 0x00000001,0x00000025,0x0000009f,0x000000a0,0x0003003e,0x0000009e,0x000000a1,0x0004003d, + 0x00000006,0x000000a2,0x00000092,0x0004003d,0x00000006,0x000000a3,0x0000009a,0x00050081, + 0x00000006,0x000000a4,0x000000a2,0x000000a3,0x000500b4,0x000000a6,0x000000a7,0x000000a4, + 0x000000a5,0x000300f7,0x000000a9,0x00000000,0x000400fa,0x000000a7,0x000000a8,0x000000a9, + 0x000200f8,0x000000a8,0x000200fe,0x000000aa,0x000200f8,0x000000a9,0x0004003d,0x00000006, + 0x000000ad,0x00000092,0x0004003d,0x00000006,0x000000ae,0x00000092,0x0004003d,0x00000006, + 0x000000af,0x0000009a,0x00050081,0x00000006,0x000000b0,0x000000ae,0x000000af,0x00050088, + 0x00000006,0x000000b1,0x000000ad,0x000000b0,0x0007000c,0x00000006,0x000000b3,0x00000001, + 0x00000025,0x000000b1,0x000000b2,0x0003003e,0x000000ac,0x000000b3,0x0004003d,0x00000006, + 0x000000b5,0x000000ac,0x00050083,0x00000006,0x000000b6,0x000000b2,0x000000b5,0x0003003e, + 0x000000b4,0x000000b6,0x0004003d,0x00000006,0x000000b9,0x00000092,0x0004003d,0x00000006, + 0x000000ba,0x00000096,0x00050085,0x00000006,0x000000bc,0x000000ba,0x000000bb,0x000500ba, + 0x000000a6,0x000000bd,0x000000b9,0x000000bc,0x0004003d,0x00000006,0x000000be,0x00000092, + 0x000500ba,0x000000a6,0x000000c0,0x000000be,0x000000bf,0x000500a7,0x000000a6,0x000000c1, + 0x000000bd,0x000000c0,0x0004003d,0x00000006,0x000000c2,0x00000092,0x0004003d,0x00000006, + 0x000000c3,0x0000009e,0x000500ba,0x000000a6,0x000000c4,0x000000c2,0x000000c3,0x000500a7, + 0x000000a6,0x000000c5,0x000000c1,0x000000c4,0x0003003e,0x000000b8,0x000000c5,0x0004003d, + 0x00000006,0x000000c7,0x0000009a,0x0004003d,0x00000006,0x000000c8,0x0000009e,0x00050085, + 0x00000006,0x000000c9,0x000000c8,0x000000bb,0x000500ba,0x000000a6,0x000000ca,0x000000c7, + 0x000000c9,0x0004003d,0x00000006,0x000000cb,0x0000009a,0x000500ba,0x000000a6,0x000000cc, + 0x000000cb,0x000000bf,0x000500a7,0x000000a6,0x000000cd,0x000000ca,0x000000cc,0x0004003d, + 0x00000006,0x000000ce,0x0000009a,0x0004003d,0x00000006,0x000000cf,0x00000096,0x000500ba, + 0x000000a6,0x000000d0,0x000000ce,0x000000cf,0x000500a7,0x000000a6,0x000000d1,0x000000cd, + 0x000000d0,0x0003003e,0x000000c6,0x000000d1,0x0004003d,0x00000006,0x000000d3,0x00000092, + 0x0004003d,0x00000006,0x000000d4,0x0000005e,0x000500b4,0x000000a6,0x000000d5,0x000000d3, + 0x000000d4,0x0003003e,0x000000d2,0x000000d5,0x0004003d,0x00000006,0x000000d7,0x0000009a, + 0x0004003d,0x00000006,0x000000d8,0x0000006b,0x000500b4,0x000000a6,0x000000d9,0x000000d7, + 0x000000d8,0x0003003e,0x000000d6,0x000000d9,0x0004003d,0x000000a6,0x000000db,0x000000b8, + 0x0004003d,0x000000a6,0x000000dc,0x000000c6,0x000500a7,0x000000a6,0x000000dd,0x000000db, + 0x000000dc,0x0004003d,0x00000006,0x000000de,0x000000ac,0x000600a9,0x00000006,0x000000df, + 0x000000dd,0x000000de,0x000000b2,0x0003003e,0x000000da,0x000000df,0x0004003d,0x000000a6, + 0x000000e1,0x000000b8,0x0004003d,0x000000a6,0x000000e2,0x000000c6,0x000500a7,0x000000a6, + 0x000000e3,0x000000e1,0x000000e2,0x0004003d,0x00000006,0x000000e4,0x000000b4,0x000600a9, + 0x00000006,0x000000e5,0x000000e3,0x000000e4,0x000000b2,0x0003003e,0x000000e0,0x000000e5, + 0x0004003d,0x000000a6,0x000000e7,0x000000b8,0x0004003d,0x000000a6,0x000000e8,0x000000d2, + 0x000500a7,0x000000a6,0x000000e9,0x000000e7,0x000000e8,0x0004003d,0x00000006,0x000000ea, + 0x000000da,0x000600a9,0x00000006,0x000000eb,0x000000e9,0x000000ea,0x000000a5,0x0003003e, + 0x000000e6,0x000000eb,0x0004003d,0x000000a6,0x000000ed,0x000000b8,0x0004003d,0x000000a6, + 0x000000ee,0x000000d2,0x000400a8,0x000000a6,0x000000ef,0x000000ee,0x000500a7,0x000000a6, + 0x000000f0,0x000000ed,0x000000ef,0x0004003d,0x00000006,0x000000f1,0x000000da,0x000600a9, + 0x00000006,0x000000f2,0x000000f0,0x000000f1,0x000000a5,0x0003003e,0x000000ec,0x000000f2, + 0x0004003d,0x000000a6,0x000000f4,0x000000c6,0x0004003d,0x000000a6,0x000000f5,0x000000d6, + 0x000500a7,0x000000a6,0x000000f6,0x000000f4,0x000000f5,0x0004003d,0x00000006,0x000000f7, + 0x000000e0,0x000600a9,0x00000006,0x000000f8,0x000000f6,0x000000f7,0x000000a5,0x0003003e, + 0x000000f3,0x000000f8,0x0004003d,0x000000a6,0x000000fa,0x000000c6,0x0004003d,0x000000a6, + 0x000000fb,0x000000d6,0x000400a8,0x000000a6,0x000000fc,0x000000fb,0x000500a7,0x000000a6, + 0x000000fd,0x000000fa,0x000000fc,0x0004003d,0x00000006,0x000000fe,0x000000e0,0x000600a9, + 0x00000006,0x000000ff,0x000000fd,0x000000fe,0x000000a5,0x0003003e,0x000000f9,0x000000ff, + 0x0004003d,0x00000006,0x00000100,0x000000e6,0x0004003d,0x00000006,0x00000101,0x000000ec, + 0x0004003d,0x00000006,0x00000102,0x000000f3,0x0004003d,0x00000006,0x00000103,0x000000f9, + 0x00070050,0x0000000e,0x00000104,0x00000100,0x00000101,0x00000102,0x00000103,0x000200fe, + 0x00000104,0x00010038,0x00050036,0x0000000e,0x00000023,0x00000000,0x0000001c,0x00030037, + 0x0000001b,0x0000001d,0x00030037,0x0000001b,0x0000001e,0x00030037,0x0000001b,0x0000001f, + 0x00030037,0x0000001b,0x00000020,0x00030037,0x0000000d,0x00000021,0x00030037,0x0000000d, + 0x00000022,0x000200f8,0x00000024,0x0004003b,0x0000001b,0x00000107,0x00000007,0x0004003b, + 0x0000001b,0x0000010d,0x00000007,0x0004003d,0x0000000e,0x00000108,0x0000001d,0x0004003d, + 0x0000000e,0x00000109,0x0000001e,0x0004003d,0x00000006,0x0000010a,0x00000021,0x00070050, + 0x0000000e,0x0000010b,0x0000010a,0x0000010a,0x0000010a,0x0000010a,0x0008000c,0x0000000e, + 0x0000010c,0x00000001,0x0000002e,0x00000108,0x00000109,0x0000010b,0x0003003e,0x00000107, + 0x0000010c,0x0004003d,0x0000000e,0x0000010e,0x0000001f,0x0004003d,0x0000000e,0x0000010f, + 0x00000020,0x0004003d,0x00000006,0x00000110,0x00000021,0x00070050,0x0000000e,0x00000111, + 0x00000110,0x00000110,0x00000110,0x00000110,0x0008000c,0x0000000e,0x00000112,0x00000001, + 0x0000002e,0x0000010e,0x0000010f,0x00000111,0x0003003e,0x0000010d,0x00000112,0x0004003d, + 0x0000000e,0x00000113,0x00000107,0x0004003d,0x0000000e,0x00000114,0x0000010d,0x0004003d, + 0x00000006,0x00000115,0x00000022,0x00070050,0x0000000e,0x00000116,0x00000115,0x00000115, + 0x00000115,0x00000115,0x0008000c,0x0000000e,0x00000117,0x00000001,0x0000002e,0x00000113, + 0x00000114,0x00000116,0x000200fe,0x00000117,0x00010038,0x00050036,0x00000006,0x0000002f, + 0x00000000,0x00000027,0x00030037,0x0000000d,0x00000028,0x00030037,0x0000000d,0x00000029, + 0x00030037,0x0000000d,0x0000002a,0x00030037,0x0000000d,0x0000002b,0x00030037,0x0000000d, + 0x0000002c,0x00030037,0x0000000d,0x0000002d,0x00030037,0x00000026,0x0000002e,0x000200f8, + 0x00000030,0x0004003b,0x000000b7,0x0000011a,0x00000007,0x0004003b,0x0000000d,0x0000011e, + 0x00000007,0x0004003b,0x0000000d,0x00000123,0x00000007,0x0004003b,0x0000000d,0x00000129, + 0x00000007,0x0004003b,0x0000000d,0x00000133,0x00000007,0x0004003b,0x0000000d,0x00000139, + 0x00000007,0x0004003b,0x0000000d,0x0000013f,0x00000007,0x0004003b,0x0000000d,0x00000143, + 0x00000007,0x0004003b,0x0000000d,0x00000147,0x00000007,0x0004003d,0x00000025,0x0000011b, + 0x0000002e,0x000500b3,0x000000a6,0x0000011d,0x0000011b,0x0000011c,0x0003003e,0x0000011a, + 0x0000011d,0x0004003d,0x000000a6,0x0000011f,0x0000011a,0x0004003d,0x00000006,0x00000120, + 0x00000028,0x0004003d,0x00000006,0x00000121,0x0000002b,0x000600a9,0x00000006,0x00000122, + 0x0000011f,0x00000120,0x00000121,0x0003003e,0x0000011e,0x00000122,0x0004003d,0x00000006, + 0x00000124,0x00000029,0x0004003d,0x00000006,0x00000125,0x0000002a,0x0007000c,0x00000006, + 0x00000126,0x00000001,0x00000025,0x00000124,0x00000125,0x0004003d,0x00000006,0x00000127, + 0x0000011e,0x0007000c,0x00000006,0x00000128,0x00000001,0x00000025,0x00000126,0x00000127, + 0x0003003e,0x00000123,0x00000128,0x0004003d,0x00000006,0x0000012a,0x00000029,0x0004003d, + 0x00000006,0x0000012b,0x0000002a,0x0007000c,0x00000006,0x0000012c,0x00000001,0x00000028, + 0x0000012a,0x0000012b,0x0004003d,0x00000006,0x0000012d,0x0000011e,0x0007000c,0x00000006, + 0x0000012e,0x00000001,0x00000028,0x0000012c,0x0000012d,0x0003003e,0x00000129,0x0000012e, + 0x0004003d,0x000000a6,0x0000012f,0x0000011a,0x0004003d,0x00000006,0x00000130,0x0000002a, + 0x0004003d,0x00000006,0x00000131,0x0000002d,0x000600a9,0x00000006,0x00000132,0x0000012f, + 0x00000130,0x00000131,0x0003003e,0x0000011e,0x00000132,0x0004003d,0x00000006,0x00000134, + 0x0000002b,0x0004003d,0x00000006,0x00000135,0x0000002c,0x0007000c,0x00000006,0x00000136, + 0x00000001,0x00000025,0x00000134,0x00000135,0x0004003d,0x00000006,0x00000137,0x0000011e, + 0x0007000c,0x00000006,0x00000138,0x00000001,0x00000025,0x00000136,0x00000137,0x0003003e, + 0x00000133,0x00000138,0x0004003d,0x00000006,0x0000013a,0x0000002b,0x0004003d,0x00000006, + 0x0000013b,0x0000002c,0x0007000c,0x00000006,0x0000013c,0x00000001,0x00000028,0x0000013a, + 0x0000013b,0x0004003d,0x00000006,0x0000013d,0x0000011e,0x0007000c,0x00000006,0x0000013e, + 0x00000001,0x00000028,0x0000013c,0x0000013d,0x0003003e,0x00000139,0x0000013e,0x0004003d, + 0x00000006,0x00000140,0x00000129,0x0004003d,0x00000006,0x00000141,0x00000123,0x00050083, + 0x00000006,0x00000142,0x00000140,0x00000141,0x0003003e,0x0000013f,0x00000142,0x0004003d, + 0x00000006,0x00000144,0x00000139,0x0004003d,0x00000006,0x00000145,0x00000133,0x00050083, + 0x00000006,0x00000146,0x00000144,0x00000145,0x0003003e,0x00000143,0x00000146,0x0004003d, + 0x00000006,0x00000148,0x0000013f,0x0004003d,0x00000006,0x00000149,0x00000143,0x0007000c, + 0x00000006,0x0000014a,0x00000001,0x00000028,0x00000148,0x00000149,0x0004003d,0x00000006, + 0x0000014b,0x0000013f,0x0004003d,0x00000006,0x0000014c,0x00000143,0x0007000c,0x00000006, + 0x0000014d,0x00000001,0x00000025,0x0000014b,0x0000014c,0x00050081,0x00000006,0x0000014f, + 0x0000014d,0x0000014e,0x00050088,0x00000006,0x00000150,0x0000014a,0x0000014f,0x0003003e, + 0x00000147,0x00000150,0x0004003d,0x00000006,0x00000151,0x00000147,0x00050083,0x00000006, + 0x00000153,0x00000151,0x00000152,0x00050085,0x00000006,0x00000155,0x00000153,0x00000154, + 0x0008000c,0x00000006,0x00000156,0x00000001,0x0000002b,0x00000155,0x000000a5,0x000000b2, + 0x00050083,0x00000006,0x00000157,0x000000b2,0x00000156,0x00050085,0x00000006,0x00000158, + 0x00000157,0x000000b2,0x000200fe,0x00000158,0x00010038,0x00050036,0x00000006,0x00000038, + 0x00000000,0x00000035,0x00030037,0x00000034,0x00000036,0x00030037,0x00000026,0x00000037, + 0x000200f8,0x00000039,0x0004003b,0x0000000d,0x0000015f,0x00000007,0x0004003b,0x00000026, + 0x00000160,0x00000007,0x0004003b,0x00000214,0x00000215,0x00000007,0x0004003b,0x0000000d, + 0x00000221,0x00000007,0x0004003b,0x00000026,0x00000222,0x00000007,0x0004003b,0x00000214, + 0x000002b0,0x00000007,0x0004003b,0x0000000d,0x000002bb,0x00000007,0x0004003b,0x0000000d, + 0x000002c3,0x00000007,0x0004003b,0x0000000d,0x000002cf,0x00000007,0x0004003b,0x0000000d, + 0x000002e3,0x00000007,0x0004003b,0x0000000d,0x000002e6,0x00000007,0x0004003b,0x0000000d, + 0x000002e9,0x00000007,0x0004003b,0x0000000d,0x000002ec,0x00000007,0x0004003b,0x0000000d, + 0x000002ef,0x00000007,0x0004003b,0x0000000d,0x000002f2,0x00000007,0x0004003b,0x00000026, + 0x000002f5,0x00000007,0x0004003d,0x00000025,0x0000015b,0x00000037,0x0008000c,0x00000025, + 0x0000015e,0x00000001,0x0000002d,0x0000015b,0x0000015c,0x0000015d,0x0003003e,0x00000037, + 0x0000015e,0x0003003e,0x0000015f,0x000000a5,0x0003003e,0x00000160,0x0000015c,0x000200f9, + 0x00000161,0x000200f8,0x00000161,0x000400f6,0x00000163,0x00000164,0x00000000,0x000200f9, + 0x00000165,0x000200f8,0x00000165,0x0004003d,0x00000025,0x00000166,0x00000160,0x000500b1, + 0x000000a6,0x00000168,0x00000166,0x00000167,0x000400fa,0x00000168,0x00000162,0x00000163, + 0x000200f8,0x00000162,0x0004003d,0x00000025,0x00000210,0x00000037,0x00050084,0x00000025, + 0x00000211,0x00000210,0x00000167,0x0004003d,0x00000025,0x00000212,0x00000160,0x00050080, + 0x00000025,0x00000213,0x00000211,0x00000212,0x0003003e,0x00000215,0x0000020f,0x00050041, + 0x0000000d,0x00000216,0x00000215,0x00000213,0x0004003d,0x00000006,0x00000217,0x00000216, + 0x0004003d,0x00000025,0x00000218,0x00000160,0x00050041,0x0000000d,0x00000219,0x00000036, + 0x00000218,0x0004003d,0x00000006,0x0000021a,0x00000219,0x00050085,0x00000006,0x0000021b, + 0x00000217,0x0000021a,0x0004003d,0x00000006,0x0000021c,0x0000015f,0x00050081,0x00000006, + 0x0000021d,0x0000021c,0x0000021b,0x0003003e,0x0000015f,0x0000021d,0x000200f9,0x00000164, + 0x000200f8,0x00000164,0x0004003d,0x00000025,0x0000021e,0x00000160,0x00050080,0x00000025, + 0x00000220,0x0000021e,0x0000021f,0x0003003e,0x00000160,0x00000220,0x000200f9,0x00000161, + 0x000200f8,0x00000163,0x0003003e,0x00000221,0x000000a5,0x0003003e,0x00000222,0x0000015c, + 0x000200f9,0x00000223,0x000200f8,0x00000223,0x000400f6,0x00000225,0x00000226,0x00000000, + 0x000200f9,0x00000227,0x000200f8,0x00000227,0x0004003d,0x00000025,0x00000228,0x00000222, + 0x000500b1,0x000000a6,0x00000229,0x00000228,0x00000167,0x000400fa,0x00000229,0x00000224, + 0x00000225,0x000200f8,0x00000224,0x0004003d,0x00000025,0x000002ac,0x00000037,0x00050084, + 0x00000025,0x000002ad,0x000002ac,0x00000167,0x0004003d,0x00000025,0x000002ae,0x00000222, + 0x00050080,0x00000025,0x000002af,0x000002ad,0x000002ae,0x0003003e,0x000002b0,0x000002ab, + 0x00050041,0x0000000d,0x000002b1,0x000002b0,0x000002af,0x0004003d,0x00000006,0x000002b2, + 0x000002b1,0x0004003d,0x00000025,0x000002b3,0x00000222,0x00050041,0x0000000d,0x000002b4, + 0x00000036,0x000002b3,0x0004003d,0x00000006,0x000002b5,0x000002b4,0x00050085,0x00000006, + 0x000002b6,0x000002b2,0x000002b5,0x0004003d,0x00000006,0x000002b7,0x00000221,0x00050081, + 0x00000006,0x000002b8,0x000002b7,0x000002b6,0x0003003e,0x00000221,0x000002b8,0x000200f9, + 0x00000226,0x000200f8,0x00000226,0x0004003d,0x00000025,0x000002b9,0x00000222,0x00050080, + 0x00000025,0x000002ba,0x000002b9,0x0000021f,0x0003003e,0x00000222,0x000002ba,0x000200f9, + 0x00000223,0x000200f8,0x00000225,0x0004003d,0x00000006,0x000002bc,0x0000015f,0x00050083, + 0x00000006,0x000002be,0x000002bc,0x000002bd,0x00050085,0x00000006,0x000002c0,0x000002be, + 0x000002bf,0x0008000c,0x00000006,0x000002c1,0x00000001,0x0000002b,0x000002c0,0x000000a5, + 0x000000b2,0x00050083,0x00000006,0x000002c2,0x000000b2,0x000002c1,0x0003003e,0x000002bb, + 0x000002c2,0x0004003d,0x00000006,0x000002c4,0x000002bb,0x0004003d,0x00000006,0x000002c7, + 0x000002c6,0x00050085,0x00000006,0x000002c8,0x000002c4,0x000002c7,0x0004003d,0x00000006, + 0x000002ca,0x000002c9,0x00050081,0x00000006,0x000002cb,0x000002c8,0x000002ca,0x0003003e, + 0x000002c3,0x000002cb,0x0004003d,0x00000006,0x000002cc,0x000002c3,0x0004003d,0x00000006, + 0x000002cd,0x00000221,0x00050085,0x00000006,0x000002ce,0x000002cd,0x000002cc,0x0003003e, + 0x00000221,0x000002ce,0x0004003d,0x00000006,0x000002d0,0x000002bb,0x0004003d,0x00000006, + 0x000002d2,0x000002d1,0x00050085,0x00000006,0x000002d3,0x000002d0,0x000002d2,0x0004003d, + 0x00000006,0x000002d5,0x000002d4,0x00050081,0x00000006,0x000002d6,0x000002d3,0x000002d5, + 0x0004003d,0x00000006,0x000002d7,0x0000015f,0x00050085,0x00000006,0x000002d8,0x000002d6, + 0x000002d7,0x0003003e,0x000002cf,0x000002d8,0x0004003d,0x00000006,0x000002d9,0x000002cf, + 0x0004003d,0x00000006,0x000002da,0x000002cf,0x0004007f,0x00000006,0x000002db,0x000002da, + 0x0004003d,0x00000006,0x000002dc,0x00000221,0x0007000c,0x00000006,0x000002dd,0x00000001, + 0x00000028,0x000002db,0x000002dc,0x0007000c,0x00000006,0x000002de,0x00000001,0x00000025, + 0x000002d9,0x000002dd,0x0003003e,0x00000221,0x000002de,0x00050041,0x0000000d,0x000002e4, + 0x00000036,0x0000015c,0x0004003d,0x00000006,0x000002e5,0x000002e4,0x0003003e,0x000002e3, + 0x000002e5,0x00050041,0x0000000d,0x000002e7,0x00000036,0x0000021f,0x0004003d,0x00000006, + 0x000002e8,0x000002e7,0x0003003e,0x000002e6,0x000002e8,0x00050041,0x0000000d,0x000002ea, + 0x00000036,0x000002df,0x0004003d,0x00000006,0x000002eb,0x000002ea,0x0003003e,0x000002e9, + 0x000002eb,0x00050041,0x0000000d,0x000002ed,0x00000036,0x000002e0,0x0004003d,0x00000006, + 0x000002ee,0x000002ed,0x0003003e,0x000002ec,0x000002ee,0x00050041,0x0000000d,0x000002f0, + 0x00000036,0x000002e1,0x0004003d,0x00000006,0x000002f1,0x000002f0,0x0003003e,0x000002ef, + 0x000002f1,0x00050041,0x0000000d,0x000002f3,0x00000036,0x000002e2,0x0004003d,0x00000006, + 0x000002f4,0x000002f3,0x0003003e,0x000002f2,0x000002f4,0x0004003d,0x00000025,0x000002f6, + 0x00000037,0x0003003e,0x000002f5,0x000002f6,0x000b0039,0x00000006,0x000002f7,0x0000002f, + 0x000002e3,0x000002e6,0x000002e9,0x000002ec,0x000002ef,0x000002f2,0x000002f5,0x0004003d, + 0x00000006,0x000002f8,0x00000221,0x00050085,0x00000006,0x000002f9,0x000002f8,0x000002f7, + 0x0003003e,0x00000221,0x000002f9,0x0004003d,0x00000006,0x000002fa,0x0000015f,0x0004003d, + 0x00000006,0x000002fb,0x00000221,0x00050081,0x00000006,0x000002fc,0x000002fa,0x000002fb, + 0x000200fe,0x000002fc,0x00010038,0x00050036,0x00000006,0x00000040,0x00000000,0x0000003c, + 0x00030037,0x0000003b,0x0000003d,0x00030037,0x00000026,0x0000003e,0x00030037,0x00000026, + 0x0000003f,0x000200f8,0x00000041,0x0004003b,0x0000000d,0x00000303,0x00000007,0x0004003b, + 0x00000026,0x00000304,0x00000007,0x0004003b,0x0000000d,0x0000030c,0x00000007,0x0004003b, + 0x00000026,0x0000030d,0x00000007,0x0004003b,0x00000214,0x0000031d,0x00000007,0x0004003b, + 0x00000214,0x0000032a,0x00000007,0x0004003d,0x00000025,0x000002ff,0x0000003e,0x0008000c, + 0x00000025,0x00000300,0x00000001,0x0000002d,0x000002ff,0x0000015c,0x0000015d,0x0003003e, + 0x0000003e,0x00000300,0x0004003d,0x00000025,0x00000301,0x0000003f,0x0008000c,0x00000025, + 0x00000302,0x00000001,0x0000002d,0x00000301,0x0000015c,0x0000015d,0x0003003e,0x0000003f, + 0x00000302,0x0003003e,0x00000303,0x000000a5,0x0003003e,0x00000304,0x0000015c,0x000200f9, + 0x00000305,0x000200f8,0x00000305,0x000400f6,0x00000307,0x00000308,0x00000000,0x000200f9, + 0x00000309,0x000200f8,0x00000309,0x0004003d,0x00000025,0x0000030a,0x00000304,0x000500b1, + 0x000000a6,0x0000030b,0x0000030a,0x00000167,0x000400fa,0x0000030b,0x00000306,0x00000307, + 0x000200f8,0x00000306,0x0003003e,0x0000030c,0x000000a5,0x0003003e,0x0000030d,0x0000015c, + 0x000200f9,0x0000030e,0x000200f8,0x0000030e,0x000400f6,0x00000310,0x00000311,0x00000000, + 0x000200f9,0x00000312,0x000200f8,0x00000312,0x0004003d,0x00000025,0x00000313,0x0000030d, + 0x000500b1,0x000000a6,0x00000314,0x00000313,0x00000167,0x000400fa,0x00000314,0x0000030f, + 0x00000310,0x000200f8,0x0000030f,0x0004003d,0x00000025,0x00000315,0x0000030d,0x0004003d, + 0x00000025,0x00000316,0x00000304,0x00060041,0x0000000d,0x00000317,0x0000003d,0x00000315, + 0x00000316,0x0004003d,0x00000006,0x00000318,0x00000317,0x0004003d,0x00000025,0x00000319, + 0x0000003f,0x00050084,0x00000025,0x0000031a,0x00000319,0x00000167,0x0004003d,0x00000025, + 0x0000031b,0x0000030d,0x00050080,0x00000025,0x0000031c,0x0000031a,0x0000031b,0x0003003e, + 0x0000031d,0x0000020f,0x00050041,0x0000000d,0x0000031e,0x0000031d,0x0000031c,0x0004003d, + 0x00000006,0x0000031f,0x0000031e,0x00050085,0x00000006,0x00000320,0x00000318,0x0000031f, + 0x0004003d,0x00000006,0x00000321,0x0000030c,0x00050081,0x00000006,0x00000322,0x00000321, + 0x00000320,0x0003003e,0x0000030c,0x00000322,0x000200f9,0x00000311,0x000200f8,0x00000311, + 0x0004003d,0x00000025,0x00000323,0x0000030d,0x00050080,0x00000025,0x00000324,0x00000323, + 0x0000021f,0x0003003e,0x0000030d,0x00000324,0x000200f9,0x0000030e,0x000200f8,0x00000310, + 0x0004003d,0x00000006,0x00000325,0x0000030c,0x0004003d,0x00000025,0x00000326,0x0000003e, + 0x00050084,0x00000025,0x00000327,0x00000326,0x00000167,0x0004003d,0x00000025,0x00000328, + 0x00000304,0x00050080,0x00000025,0x00000329,0x00000327,0x00000328,0x0003003e,0x0000032a, + 0x0000020f,0x00050041,0x0000000d,0x0000032b,0x0000032a,0x00000329,0x0004003d,0x00000006, + 0x0000032c,0x0000032b,0x00050085,0x00000006,0x0000032d,0x00000325,0x0000032c,0x0004003d, + 0x00000006,0x0000032e,0x00000303,0x00050081,0x00000006,0x0000032f,0x0000032e,0x0000032d, + 0x0003003e,0x00000303,0x0000032f,0x000200f9,0x00000308,0x000200f8,0x00000308,0x0004003d, + 0x00000025,0x00000330,0x00000304,0x00050080,0x00000025,0x00000331,0x00000330,0x0000021f, + 0x0003003e,0x00000304,0x00000331,0x000200f9,0x00000305,0x000200f8,0x00000307,0x0004003d, + 0x00000006,0x00000332,0x00000303,0x000200fe,0x00000332,0x00010038,0x00050036,0x00000006, + 0x00000049,0x00000000,0x00000042,0x00030037,0x0000003b,0x00000043,0x00030037,0x0000000d, + 0x00000044,0x00030037,0x0000000d,0x00000045,0x00030037,0x00000026,0x00000046,0x00030037, + 0x00000026,0x00000047,0x00030037,0x0000001b,0x00000048,0x000200f8,0x0000004a,0x0004003b, + 0x0000000d,0x00000335,0x00000007,0x0004003b,0x00000026,0x0000033b,0x00000007,0x0004003b, + 0x00000034,0x00000343,0x00000007,0x0004003b,0x00000034,0x00000350,0x00000007,0x0004003b, + 0x00000026,0x00000352,0x00000007,0x0004003b,0x00000026,0x0000035f,0x00000007,0x0004003b, + 0x00000034,0x00000367,0x00000007,0x0004003b,0x00000034,0x00000374,0x00000007,0x0004003b, + 0x00000026,0x00000376,0x00000007,0x0004003b,0x0000000d,0x00000383,0x00000007,0x0004003b, + 0x0000038c,0x0000038d,0x00000007,0x0004003b,0x0000000d,0x000003a5,0x00000007,0x0004003b, + 0x0000000d,0x000003a8,0x00000007,0x0004003b,0x0000000d,0x000003b1,0x00000007,0x0004003b, + 0x0000000d,0x000003b4,0x00000007,0x0004003b,0x0000000d,0x000003bd,0x00000007,0x0004003b, + 0x0000000d,0x000003c0,0x00000007,0x0004003b,0x0000000d,0x000003c9,0x00000007,0x0004003b, + 0x0000000d,0x000003cc,0x00000007,0x0004003b,0x0000000d,0x000003f1,0x00000007,0x0004003b, + 0x00000026,0x000003f9,0x00000007,0x0004003b,0x00000034,0x00000401,0x00000007,0x0004003b, + 0x00000026,0x0000040d,0x00000007,0x0004003b,0x00000034,0x00000420,0x00000007,0x0004003b, + 0x00000026,0x00000422,0x00000007,0x0004003b,0x0000000d,0x0000042f,0x00000007,0x0004003b, + 0x0000038c,0x00000434,0x00000007,0x0004003b,0x0000000d,0x0000044c,0x00000007,0x0004003b, + 0x0000000d,0x0000044f,0x00000007,0x0004003b,0x0000000d,0x00000458,0x00000007,0x0004003b, + 0x0000000d,0x0000045b,0x00000007,0x0004003b,0x0000000d,0x00000464,0x00000007,0x0004003b, + 0x0000000d,0x00000467,0x00000007,0x0004003b,0x0000000d,0x00000470,0x00000007,0x0004003b, + 0x0000000d,0x00000473,0x00000007,0x0004003b,0x0000000d,0x00000498,0x00000007,0x0004003b, + 0x00000026,0x000004a1,0x00000007,0x0004003b,0x00000034,0x000004a9,0x00000007,0x0004003b, + 0x00000026,0x000004b5,0x00000007,0x0004003b,0x00000034,0x000004c7,0x00000007,0x0004003b, + 0x00000026,0x000004c9,0x00000007,0x0003003e,0x00000335,0x000000a5,0x00050041,0x0000000d, + 0x00000336,0x00000048,0x0000004c,0x0004003d,0x00000006,0x00000337,0x00000336,0x000500ba, + 0x000000a6,0x00000338,0x00000337,0x000000a5,0x000300f7,0x0000033a,0x00000000,0x000400fa, + 0x00000338,0x00000339,0x0000033a,0x000200f8,0x00000339,0x0003003e,0x0000033b,0x0000015c, + 0x000200f9,0x0000033c,0x000200f8,0x0000033c,0x000400f6,0x0000033e,0x0000033f,0x00000000, + 0x000200f9,0x00000340,0x000200f8,0x00000340,0x0004003d,0x00000025,0x00000341,0x0000033b, + 0x000500b1,0x000000a6,0x00000342,0x00000341,0x00000167,0x000400fa,0x00000342,0x0000033d, + 0x0000033e,0x000200f8,0x0000033d,0x0004003d,0x00000025,0x00000344,0x0000033b,0x0004003d, + 0x00000025,0x00000345,0x0000033b,0x00060041,0x0000000d,0x00000346,0x00000043,0x00000345, + 0x000002df,0x0004003d,0x00000006,0x00000347,0x00000346,0x0004003d,0x00000025,0x00000348, + 0x0000033b,0x00060041,0x0000000d,0x00000349,0x00000043,0x00000348,0x000002e0,0x0004003d, + 0x00000006,0x0000034a,0x00000349,0x0004003d,0x00000006,0x0000034b,0x00000044,0x0008000c, + 0x00000006,0x0000034c,0x00000001,0x0000002e,0x00000347,0x0000034a,0x0000034b,0x00050041, + 0x0000000d,0x0000034d,0x00000343,0x00000344,0x0003003e,0x0000034d,0x0000034c,0x000200f9, + 0x0000033f,0x000200f8,0x0000033f,0x0004003d,0x00000025,0x0000034e,0x0000033b,0x00050080, + 0x00000025,0x0000034f,0x0000034e,0x0000021f,0x0003003e,0x0000033b,0x0000034f,0x000200f9, + 0x0000033c,0x000200f8,0x0000033e,0x0004003d,0x00000033,0x00000351,0x00000343,0x0003003e, + 0x00000350,0x00000351,0x0004003d,0x00000025,0x00000353,0x00000047,0x0003003e,0x00000352, + 0x00000353,0x00060039,0x00000006,0x00000354,0x00000038,0x00000350,0x00000352,0x00050041, + 0x0000000d,0x00000355,0x00000048,0x0000004c,0x0004003d,0x00000006,0x00000356,0x00000355, + 0x00050085,0x00000006,0x00000357,0x00000354,0x00000356,0x0004003d,0x00000006,0x00000358, + 0x00000335,0x00050081,0x00000006,0x00000359,0x00000358,0x00000357,0x0003003e,0x00000335, + 0x00000359,0x000200f9,0x0000033a,0x000200f8,0x0000033a,0x00050041,0x0000000d,0x0000035a, + 0x00000048,0x00000051,0x0004003d,0x00000006,0x0000035b,0x0000035a,0x000500ba,0x000000a6, + 0x0000035c,0x0000035b,0x000000a5,0x000300f7,0x0000035e,0x00000000,0x000400fa,0x0000035c, + 0x0000035d,0x0000035e,0x000200f8,0x0000035d,0x0003003e,0x0000035f,0x0000015c,0x000200f9, + 0x00000360,0x000200f8,0x00000360,0x000400f6,0x00000362,0x00000363,0x00000000,0x000200f9, + 0x00000364,0x000200f8,0x00000364,0x0004003d,0x00000025,0x00000365,0x0000035f,0x000500b1, + 0x000000a6,0x00000366,0x00000365,0x00000167,0x000400fa,0x00000366,0x00000361,0x00000362, + 0x000200f8,0x00000361,0x0004003d,0x00000025,0x00000368,0x0000035f,0x0004003d,0x00000025, + 0x00000369,0x0000035f,0x00060041,0x0000000d,0x0000036a,0x00000043,0x000002df,0x00000369, + 0x0004003d,0x00000006,0x0000036b,0x0000036a,0x0004003d,0x00000025,0x0000036c,0x0000035f, + 0x00060041,0x0000000d,0x0000036d,0x00000043,0x000002e0,0x0000036c,0x0004003d,0x00000006, + 0x0000036e,0x0000036d,0x0004003d,0x00000006,0x0000036f,0x00000045,0x0008000c,0x00000006, + 0x00000370,0x00000001,0x0000002e,0x0000036b,0x0000036e,0x0000036f,0x00050041,0x0000000d, + 0x00000371,0x00000367,0x00000368,0x0003003e,0x00000371,0x00000370,0x000200f9,0x00000363, + 0x000200f8,0x00000363,0x0004003d,0x00000025,0x00000372,0x0000035f,0x00050080,0x00000025, + 0x00000373,0x00000372,0x0000021f,0x0003003e,0x0000035f,0x00000373,0x000200f9,0x00000360, + 0x000200f8,0x00000362,0x0004003d,0x00000033,0x00000375,0x00000367,0x0003003e,0x00000374, + 0x00000375,0x0004003d,0x00000025,0x00000377,0x00000046,0x0003003e,0x00000376,0x00000377, + 0x00060039,0x00000006,0x00000378,0x00000038,0x00000374,0x00000376,0x00050041,0x0000000d, + 0x00000379,0x00000048,0x00000051,0x0004003d,0x00000006,0x0000037a,0x00000379,0x00050085, + 0x00000006,0x0000037b,0x00000378,0x0000037a,0x0004003d,0x00000006,0x0000037c,0x00000335, + 0x00050081,0x00000006,0x0000037d,0x0000037c,0x0000037b,0x0003003e,0x00000335,0x0000037d, + 0x000200f9,0x0000035e,0x000200f8,0x0000035e,0x00050041,0x0000000d,0x0000037e,0x00000048, + 0x00000057,0x0004003d,0x00000006,0x0000037f,0x0000037e,0x000500ba,0x000000a6,0x00000380, + 0x0000037f,0x000000a5,0x000300f7,0x00000382,0x00000000,0x000400fa,0x00000380,0x00000381, + 0x00000382,0x000200f8,0x00000381,0x0004003d,0x00000006,0x00000385,0x00000044,0x0004003d, + 0x00000006,0x00000386,0x00000045,0x00050083,0x00000006,0x00000387,0x00000385,0x00000386, + 0x00050085,0x00000006,0x00000388,0x00000384,0x00000387,0x00050081,0x00000006,0x00000389, + 0x00000384,0x00000388,0x0003003e,0x00000383,0x00000389,0x00060041,0x0000000d,0x0000038e, + 0x00000043,0x000002df,0x0000021f,0x0004003d,0x00000006,0x0000038f,0x0000038e,0x00060041, + 0x0000000d,0x00000390,0x00000043,0x0000021f,0x000002df,0x0004003d,0x00000006,0x00000391, + 0x00000390,0x0004003d,0x00000006,0x00000392,0x00000383,0x0008000c,0x00000006,0x00000393, + 0x00000001,0x0000002e,0x0000038f,0x00000391,0x00000392,0x00050041,0x0000000d,0x00000394, + 0x0000038d,0x0000021f,0x0003003e,0x00000394,0x00000393,0x00060041,0x0000000d,0x00000395, + 0x00000043,0x000002e0,0x000002df,0x0004003d,0x00000006,0x00000396,0x00000395,0x00060041, + 0x0000000d,0x00000397,0x00000043,0x000002df,0x000002e0,0x0004003d,0x00000006,0x00000398, + 0x00000397,0x0004003d,0x00000006,0x00000399,0x00000383,0x0008000c,0x00000006,0x0000039a, + 0x00000001,0x0000002e,0x00000396,0x00000398,0x00000399,0x00050041,0x0000000d,0x0000039b, + 0x0000038d,0x000002e0,0x0003003e,0x0000039b,0x0000039a,0x00060041,0x0000000d,0x0000039c, + 0x00000043,0x000002e1,0x000002e0,0x0004003d,0x00000006,0x0000039d,0x0000039c,0x00060041, + 0x0000000d,0x0000039e,0x00000043,0x000002e0,0x000002e1,0x0004003d,0x00000006,0x0000039f, + 0x0000039e,0x0004003d,0x00000006,0x000003a0,0x00000383,0x0008000c,0x00000006,0x000003a1, + 0x00000001,0x0000002e,0x0000039d,0x0000039f,0x000003a0,0x00050041,0x0000000d,0x000003a2, + 0x0000038d,0x000002e2,0x0003003e,0x000003a2,0x000003a1,0x0004003d,0x00000006,0x000003a3, + 0x00000383,0x00050083,0x00000006,0x000003a4,0x000003a3,0x00000384,0x0003003e,0x00000383, + 0x000003a4,0x0004003d,0x00000006,0x000003a6,0x00000383,0x000500be,0x000000a6,0x000003a7, + 0x000003a6,0x000000a5,0x000300f7,0x000003aa,0x00000000,0x000400fa,0x000003a7,0x000003a9, + 0x000003ad,0x000200f8,0x000003a9,0x00060041,0x0000000d,0x000003ab,0x00000043,0x0000015c, + 0x000002df,0x0004003d,0x00000006,0x000003ac,0x000003ab,0x0003003e,0x000003a8,0x000003ac, + 0x000200f9,0x000003aa,0x000200f8,0x000003ad,0x00060041,0x0000000d,0x000003ae,0x00000043, + 0x000002df,0x0000015c,0x0004003d,0x00000006,0x000003af,0x000003ae,0x0003003e,0x000003a8, + 0x000003af,0x000200f9,0x000003aa,0x000200f8,0x000003aa,0x0004003d,0x00000006,0x000003b0, + 0x000003a8,0x0003003e,0x000003a5,0x000003b0,0x0004003d,0x00000006,0x000003b2,0x00000383, + 0x000500be,0x000000a6,0x000003b3,0x000003b2,0x000000a5,0x000300f7,0x000003b6,0x00000000, + 0x000400fa,0x000003b3,0x000003b5,0x000003b9,0x000200f8,0x000003b5,0x00060041,0x0000000d, + 0x000003b7,0x00000043,0x0000021f,0x000002e0,0x0004003d,0x00000006,0x000003b8,0x000003b7, + 0x0003003e,0x000003b4,0x000003b8,0x000200f9,0x000003b6,0x000200f8,0x000003b9,0x00060041, + 0x0000000d,0x000003ba,0x00000043,0x000002e0,0x0000021f,0x0004003d,0x00000006,0x000003bb, + 0x000003ba,0x0003003e,0x000003b4,0x000003bb,0x000200f9,0x000003b6,0x000200f8,0x000003b6, + 0x0004003d,0x00000006,0x000003bc,0x000003b4,0x0003003e,0x000003b1,0x000003bc,0x0004003d, + 0x00000006,0x000003be,0x00000383,0x000500be,0x000000a6,0x000003bf,0x000003be,0x000000a5, + 0x000300f7,0x000003c2,0x00000000,0x000400fa,0x000003bf,0x000003c1,0x000003c5,0x000200f8, + 0x000003c1,0x00060041,0x0000000d,0x000003c3,0x00000043,0x000002df,0x000002e1,0x0004003d, + 0x00000006,0x000003c4,0x000003c3,0x0003003e,0x000003c0,0x000003c4,0x000200f9,0x000003c2, + 0x000200f8,0x000003c5,0x00060041,0x0000000d,0x000003c6,0x00000043,0x000002e1,0x000002df, + 0x0004003d,0x00000006,0x000003c7,0x000003c6,0x0003003e,0x000003c0,0x000003c7,0x000200f9, + 0x000003c2,0x000200f8,0x000003c2,0x0004003d,0x00000006,0x000003c8,0x000003c0,0x0003003e, + 0x000003bd,0x000003c8,0x0004003d,0x00000006,0x000003ca,0x00000383,0x000500be,0x000000a6, + 0x000003cb,0x000003ca,0x000000a5,0x000300f7,0x000003ce,0x00000000,0x000400fa,0x000003cb, + 0x000003cd,0x000003d1,0x000200f8,0x000003cd,0x00060041,0x0000000d,0x000003cf,0x00000043, + 0x000002e0,0x000002e2,0x0004003d,0x00000006,0x000003d0,0x000003cf,0x0003003e,0x000003cc, + 0x000003d0,0x000200f9,0x000003ce,0x000200f8,0x000003d1,0x00060041,0x0000000d,0x000003d2, + 0x00000043,0x000002e2,0x000002e0,0x0004003d,0x00000006,0x000003d3,0x000003d2,0x0003003e, + 0x000003cc,0x000003d3,0x000200f9,0x000003ce,0x000200f8,0x000003ce,0x0004003d,0x00000006, + 0x000003d4,0x000003cc,0x0003003e,0x000003c9,0x000003d4,0x00060041,0x0000000d,0x000003d5, + 0x00000043,0x0000021f,0x0000021f,0x0004003d,0x00000006,0x000003d6,0x000003d5,0x0004003d, + 0x00000006,0x000003d7,0x000003a5,0x0004003d,0x00000006,0x000003d8,0x00000383,0x0006000c, + 0x00000006,0x000003d9,0x00000001,0x00000004,0x000003d8,0x0008000c,0x00000006,0x000003da, + 0x00000001,0x0000002e,0x000003d6,0x000003d7,0x000003d9,0x00050041,0x0000000d,0x000003db, + 0x0000038d,0x0000015c,0x0003003e,0x000003db,0x000003da,0x00060041,0x0000000d,0x000003dc, + 0x00000043,0x000002df,0x000002df,0x0004003d,0x00000006,0x000003dd,0x000003dc,0x0004003d, + 0x00000006,0x000003de,0x000003b1,0x0004003d,0x00000006,0x000003df,0x00000383,0x0006000c, + 0x00000006,0x000003e0,0x00000001,0x00000004,0x000003df,0x0008000c,0x00000006,0x000003e1, + 0x00000001,0x0000002e,0x000003dd,0x000003de,0x000003e0,0x00050041,0x0000000d,0x000003e2, + 0x0000038d,0x000002df,0x0003003e,0x000003e2,0x000003e1,0x00060041,0x0000000d,0x000003e3, + 0x00000043,0x000002e0,0x000002e0,0x0004003d,0x00000006,0x000003e4,0x000003e3,0x0004003d, + 0x00000006,0x000003e5,0x000003bd,0x0004003d,0x00000006,0x000003e6,0x00000383,0x0006000c, + 0x00000006,0x000003e7,0x00000001,0x00000004,0x000003e6,0x0008000c,0x00000006,0x000003e8, + 0x00000001,0x0000002e,0x000003e4,0x000003e5,0x000003e7,0x00050041,0x0000000d,0x000003e9, + 0x0000038d,0x000002e1,0x0003003e,0x000003e9,0x000003e8,0x00060041,0x0000000d,0x000003ea, + 0x00000043,0x000002e1,0x000002e1,0x0004003d,0x00000006,0x000003eb,0x000003ea,0x0004003d, + 0x00000006,0x000003ec,0x000003c9,0x0004003d,0x00000006,0x000003ed,0x00000383,0x0006000c, + 0x00000006,0x000003ee,0x00000001,0x00000004,0x000003ed,0x0008000c,0x00000006,0x000003ef, + 0x00000001,0x0000002e,0x000003eb,0x000003ec,0x000003ee,0x00050041,0x0000000d,0x000003f0, + 0x0000038d,0x00000167,0x0003003e,0x000003f0,0x000003ef,0x0004003d,0x00000006,0x000003f2, + 0x00000044,0x0004003d,0x00000006,0x000003f3,0x00000045,0x00050081,0x00000006,0x000003f4, + 0x000003f2,0x000003f3,0x0003003e,0x000003f1,0x000003f4,0x0004003d,0x00000006,0x000003f5, + 0x000003f1,0x000500be,0x000000a6,0x000003f6,0x000003f5,0x000000b2,0x000300f7,0x000003f8, + 0x00000000,0x000400fa,0x000003f6,0x000003f7,0x0000040c,0x000200f8,0x000003f7,0x0003003e, + 0x000003f9,0x0000015c,0x000200f9,0x000003fa,0x000200f8,0x000003fa,0x000400f6,0x000003fc, + 0x000003fd,0x00000000,0x000200f9,0x000003fe,0x000200f8,0x000003fe,0x0004003d,0x00000025, + 0x000003ff,0x000003f9,0x000500b1,0x000000a6,0x00000400,0x000003ff,0x00000167,0x000400fa, + 0x00000400,0x000003fb,0x000003fc,0x000200f8,0x000003fb,0x0004003d,0x00000025,0x00000402, + 0x000003f9,0x0004003d,0x00000025,0x00000403,0x000003f9,0x00050080,0x00000025,0x00000404, + 0x00000403,0x0000021f,0x00050041,0x0000000d,0x00000405,0x0000038d,0x00000404,0x0004003d, + 0x00000006,0x00000406,0x00000405,0x00050041,0x0000000d,0x00000407,0x00000401,0x00000402, + 0x0003003e,0x00000407,0x00000406,0x000200f9,0x000003fd,0x000200f8,0x000003fd,0x0004003d, + 0x00000025,0x00000408,0x000003f9,0x00050080,0x00000025,0x00000409,0x00000408,0x0000021f, + 0x0003003e,0x000003f9,0x00000409,0x000200f9,0x000003fa,0x000200f8,0x000003fc,0x0004003d, + 0x00000006,0x0000040a,0x000003f1,0x00050083,0x00000006,0x0000040b,0x0000040a,0x000000b2, + 0x0003003e,0x000003f1,0x0000040b,0x000200f9,0x000003f8,0x000200f8,0x0000040c,0x0003003e, + 0x0000040d,0x0000015c,0x000200f9,0x0000040e,0x000200f8,0x0000040e,0x000400f6,0x00000410, + 0x00000411,0x00000000,0x000200f9,0x00000412,0x000200f8,0x00000412,0x0004003d,0x00000025, + 0x00000413,0x0000040d,0x000500b1,0x000000a6,0x00000414,0x00000413,0x00000167,0x000400fa, + 0x00000414,0x0000040f,0x00000410,0x000200f8,0x0000040f,0x0004003d,0x00000025,0x00000415, + 0x0000040d,0x0004003d,0x00000025,0x00000416,0x0000040d,0x00050041,0x0000000d,0x00000417, + 0x0000038d,0x00000416,0x0004003d,0x00000006,0x00000418,0x00000417,0x00050041,0x0000000d, + 0x00000419,0x00000401,0x00000415,0x0003003e,0x00000419,0x00000418,0x000200f9,0x00000411, + 0x000200f8,0x00000411,0x0004003d,0x00000025,0x0000041a,0x0000040d,0x00050080,0x00000025, + 0x0000041b,0x0000041a,0x0000021f,0x0003003e,0x0000040d,0x0000041b,0x000200f9,0x0000040e, + 0x000200f8,0x00000410,0x000200f9,0x000003f8,0x000200f8,0x000003f8,0x0004003d,0x00000006, + 0x0000041c,0x000003f1,0x00050085,0x00000006,0x0000041e,0x0000041c,0x0000041d,0x0004006e, + 0x00000025,0x0000041f,0x0000041e,0x0004003d,0x00000033,0x00000421,0x00000401,0x0003003e, + 0x00000420,0x00000421,0x0003003e,0x00000422,0x0000041f,0x00060039,0x00000006,0x00000423, + 0x00000038,0x00000420,0x00000422,0x00050041,0x0000000d,0x00000424,0x00000048,0x00000057, + 0x0004003d,0x00000006,0x00000425,0x00000424,0x00050085,0x00000006,0x00000426,0x00000423, + 0x00000425,0x0004003d,0x00000006,0x00000427,0x00000335,0x00050081,0x00000006,0x00000428, + 0x00000427,0x00000426,0x0003003e,0x00000335,0x00000428,0x000200f9,0x00000382,0x000200f8, + 0x00000382,0x00050041,0x0000000d,0x0000042a,0x00000048,0x00000429,0x0004003d,0x00000006, + 0x0000042b,0x0000042a,0x000500ba,0x000000a6,0x0000042c,0x0000042b,0x000000a5,0x000300f7, + 0x0000042e,0x00000000,0x000400fa,0x0000042c,0x0000042d,0x0000042e,0x000200f8,0x0000042d, + 0x0004003d,0x00000006,0x00000430,0x00000044,0x0004003d,0x00000006,0x00000431,0x00000045, + 0x00050081,0x00000006,0x00000432,0x00000430,0x00000431,0x00050085,0x00000006,0x00000433, + 0x00000384,0x00000432,0x0003003e,0x0000042f,0x00000433,0x00060041,0x0000000d,0x00000435, + 0x00000043,0x000002e0,0x0000021f,0x0004003d,0x00000006,0x00000436,0x00000435,0x00060041, + 0x0000000d,0x00000437,0x00000043,0x000002e1,0x000002df,0x0004003d,0x00000006,0x00000438, + 0x00000437,0x0004003d,0x00000006,0x00000439,0x0000042f,0x0008000c,0x00000006,0x0000043a, + 0x00000001,0x0000002e,0x00000436,0x00000438,0x00000439,0x00050041,0x0000000d,0x0000043b, + 0x00000434,0x0000021f,0x0003003e,0x0000043b,0x0000043a,0x00060041,0x0000000d,0x0000043c, + 0x00000043,0x000002df,0x000002df,0x0004003d,0x00000006,0x0000043d,0x0000043c,0x00060041, + 0x0000000d,0x0000043e,0x00000043,0x000002e0,0x000002e0,0x0004003d,0x00000006,0x0000043f, + 0x0000043e,0x0004003d,0x00000006,0x00000440,0x0000042f,0x0008000c,0x00000006,0x00000441, + 0x00000001,0x0000002e,0x0000043d,0x0000043f,0x00000440,0x00050041,0x0000000d,0x00000442, + 0x00000434,0x000002e0,0x0003003e,0x00000442,0x00000441,0x00060041,0x0000000d,0x00000443, + 0x00000043,0x0000021f,0x000002e0,0x0004003d,0x00000006,0x00000444,0x00000443,0x00060041, + 0x0000000d,0x00000445,0x00000043,0x000002df,0x000002e1,0x0004003d,0x00000006,0x00000446, + 0x00000445,0x0004003d,0x00000006,0x00000447,0x0000042f,0x0008000c,0x00000006,0x00000448, + 0x00000001,0x0000002e,0x00000444,0x00000446,0x00000447,0x00050041,0x0000000d,0x00000449, + 0x00000434,0x000002e2,0x0003003e,0x00000449,0x00000448,0x0004003d,0x00000006,0x0000044a, + 0x0000042f,0x00050083,0x00000006,0x0000044b,0x0000044a,0x00000384,0x0003003e,0x0000042f, + 0x0000044b,0x0004003d,0x00000006,0x0000044d,0x0000042f,0x000500be,0x000000a6,0x0000044e, + 0x0000044d,0x000000a5,0x000300f7,0x00000451,0x00000000,0x000400fa,0x0000044e,0x00000450, + 0x00000454,0x000200f8,0x00000450,0x00060041,0x0000000d,0x00000452,0x00000043,0x000002e2, + 0x000002df,0x0004003d,0x00000006,0x00000453,0x00000452,0x0003003e,0x0000044f,0x00000453, + 0x000200f9,0x00000451,0x000200f8,0x00000454,0x00060041,0x0000000d,0x00000455,0x00000043, + 0x000002e0,0x0000015c,0x0004003d,0x00000006,0x00000456,0x00000455,0x0003003e,0x0000044f, + 0x00000456,0x000200f9,0x00000451,0x000200f8,0x00000451,0x0004003d,0x00000006,0x00000457, + 0x0000044f,0x0003003e,0x0000044c,0x00000457,0x0004003d,0x00000006,0x00000459,0x0000042f, + 0x000500be,0x000000a6,0x0000045a,0x00000459,0x000000a5,0x000300f7,0x0000045d,0x00000000, + 0x000400fa,0x0000045a,0x0000045c,0x00000460,0x000200f8,0x0000045c,0x00060041,0x0000000d, + 0x0000045e,0x00000043,0x000002e1,0x000002e0,0x0004003d,0x00000006,0x0000045f,0x0000045e, + 0x0003003e,0x0000045b,0x0000045f,0x000200f9,0x0000045d,0x000200f8,0x00000460,0x00060041, + 0x0000000d,0x00000461,0x00000043,0x000002df,0x0000021f,0x0004003d,0x00000006,0x00000462, + 0x00000461,0x0003003e,0x0000045b,0x00000462,0x000200f9,0x0000045d,0x000200f8,0x0000045d, + 0x0004003d,0x00000006,0x00000463,0x0000045b,0x0003003e,0x00000458,0x00000463,0x0004003d, + 0x00000006,0x00000465,0x0000042f,0x000500be,0x000000a6,0x00000466,0x00000465,0x000000a5, + 0x000300f7,0x00000469,0x00000000,0x000400fa,0x00000466,0x00000468,0x0000046c,0x000200f8, + 0x00000468,0x00060041,0x0000000d,0x0000046a,0x00000043,0x000002e0,0x000002e1,0x0004003d, + 0x00000006,0x0000046b,0x0000046a,0x0003003e,0x00000467,0x0000046b,0x000200f9,0x00000469, + 0x000200f8,0x0000046c,0x00060041,0x0000000d,0x0000046d,0x00000043,0x0000021f,0x000002df, + 0x0004003d,0x00000006,0x0000046e,0x0000046d,0x0003003e,0x00000467,0x0000046e,0x000200f9, + 0x00000469,0x000200f8,0x00000469,0x0004003d,0x00000006,0x0000046f,0x00000467,0x0003003e, + 0x00000464,0x0000046f,0x0004003d,0x00000006,0x00000471,0x0000042f,0x000500be,0x000000a6, + 0x00000472,0x00000471,0x000000a5,0x000300f7,0x00000475,0x00000000,0x000400fa,0x00000472, + 0x00000474,0x00000478,0x000200f8,0x00000474,0x00060041,0x0000000d,0x00000476,0x00000043, + 0x000002df,0x000002e2,0x0004003d,0x00000006,0x00000477,0x00000476,0x0003003e,0x00000473, + 0x00000477,0x000200f9,0x00000475,0x000200f8,0x00000478,0x00060041,0x0000000d,0x00000479, + 0x00000043,0x0000015c,0x000002e0,0x0004003d,0x00000006,0x0000047a,0x00000479,0x0003003e, + 0x00000473,0x0000047a,0x000200f9,0x00000475,0x000200f8,0x00000475,0x0004003d,0x00000006, + 0x0000047b,0x00000473,0x0003003e,0x00000470,0x0000047b,0x00060041,0x0000000d,0x0000047c, + 0x00000043,0x000002e1,0x0000021f,0x0004003d,0x00000006,0x0000047d,0x0000047c,0x0004003d, + 0x00000006,0x0000047e,0x0000044c,0x0004003d,0x00000006,0x0000047f,0x0000042f,0x0006000c, + 0x00000006,0x00000480,0x00000001,0x00000004,0x0000047f,0x0008000c,0x00000006,0x00000481, + 0x00000001,0x0000002e,0x0000047d,0x0000047e,0x00000480,0x00050041,0x0000000d,0x00000482, + 0x00000434,0x0000015c,0x0003003e,0x00000482,0x00000481,0x00060041,0x0000000d,0x00000483, + 0x00000043,0x000002e0,0x000002df,0x0004003d,0x00000006,0x00000484,0x00000483,0x0004003d, + 0x00000006,0x00000485,0x00000458,0x0004003d,0x00000006,0x00000486,0x0000042f,0x0006000c, + 0x00000006,0x00000487,0x00000001,0x00000004,0x00000486,0x0008000c,0x00000006,0x00000488, + 0x00000001,0x0000002e,0x00000484,0x00000485,0x00000487,0x00050041,0x0000000d,0x00000489, + 0x00000434,0x000002df,0x0003003e,0x00000489,0x00000488,0x00060041,0x0000000d,0x0000048a, + 0x00000043,0x000002df,0x000002e0,0x0004003d,0x00000006,0x0000048b,0x0000048a,0x0004003d, + 0x00000006,0x0000048c,0x00000464,0x0004003d,0x00000006,0x0000048d,0x0000042f,0x0006000c, + 0x00000006,0x0000048e,0x00000001,0x00000004,0x0000048d,0x0008000c,0x00000006,0x0000048f, + 0x00000001,0x0000002e,0x0000048b,0x0000048c,0x0000048e,0x00050041,0x0000000d,0x00000490, + 0x00000434,0x000002e1,0x0003003e,0x00000490,0x0000048f,0x00060041,0x0000000d,0x00000491, + 0x00000043,0x0000021f,0x000002e1,0x0004003d,0x00000006,0x00000492,0x00000491,0x0004003d, + 0x00000006,0x00000493,0x00000470,0x0004003d,0x00000006,0x00000494,0x0000042f,0x0006000c, + 0x00000006,0x00000495,0x00000001,0x00000004,0x00000494,0x0008000c,0x00000006,0x00000496, + 0x00000001,0x0000002e,0x00000492,0x00000493,0x00000495,0x00050041,0x0000000d,0x00000497, + 0x00000434,0x00000167,0x0003003e,0x00000497,0x00000496,0x0004003d,0x00000006,0x00000499, + 0x00000044,0x0004003d,0x00000006,0x0000049a,0x00000045,0x00050083,0x00000006,0x0000049b, + 0x00000499,0x0000049a,0x00050081,0x00000006,0x0000049c,0x000000b2,0x0000049b,0x0003003e, + 0x00000498,0x0000049c,0x0004003d,0x00000006,0x0000049d,0x00000498,0x000500be,0x000000a6, + 0x0000049e,0x0000049d,0x000000b2,0x000300f7,0x000004a0,0x00000000,0x000400fa,0x0000049e, + 0x0000049f,0x000004b4,0x000200f8,0x0000049f,0x0003003e,0x000004a1,0x0000015c,0x000200f9, + 0x000004a2,0x000200f8,0x000004a2,0x000400f6,0x000004a4,0x000004a5,0x00000000,0x000200f9, + 0x000004a6,0x000200f8,0x000004a6,0x0004003d,0x00000025,0x000004a7,0x000004a1,0x000500b1, + 0x000000a6,0x000004a8,0x000004a7,0x00000167,0x000400fa,0x000004a8,0x000004a3,0x000004a4, + 0x000200f8,0x000004a3,0x0004003d,0x00000025,0x000004aa,0x000004a1,0x0004003d,0x00000025, + 0x000004ab,0x000004a1,0x00050080,0x00000025,0x000004ac,0x000004ab,0x0000021f,0x00050041, + 0x0000000d,0x000004ad,0x00000434,0x000004ac,0x0004003d,0x00000006,0x000004ae,0x000004ad, + 0x00050041,0x0000000d,0x000004af,0x000004a9,0x000004aa,0x0003003e,0x000004af,0x000004ae, + 0x000200f9,0x000004a5,0x000200f8,0x000004a5,0x0004003d,0x00000025,0x000004b0,0x000004a1, + 0x00050080,0x00000025,0x000004b1,0x000004b0,0x0000021f,0x0003003e,0x000004a1,0x000004b1, + 0x000200f9,0x000004a2,0x000200f8,0x000004a4,0x0004003d,0x00000006,0x000004b2,0x00000498, + 0x00050083,0x00000006,0x000004b3,0x000004b2,0x000000b2,0x0003003e,0x00000498,0x000004b3, + 0x000200f9,0x000004a0,0x000200f8,0x000004b4,0x0003003e,0x000004b5,0x0000015c,0x000200f9, + 0x000004b6,0x000200f8,0x000004b6,0x000400f6,0x000004b8,0x000004b9,0x00000000,0x000200f9, + 0x000004ba,0x000200f8,0x000004ba,0x0004003d,0x00000025,0x000004bb,0x000004b5,0x000500b1, + 0x000000a6,0x000004bc,0x000004bb,0x00000167,0x000400fa,0x000004bc,0x000004b7,0x000004b8, + 0x000200f8,0x000004b7,0x0004003d,0x00000025,0x000004bd,0x000004b5,0x0004003d,0x00000025, + 0x000004be,0x000004b5,0x00050041,0x0000000d,0x000004bf,0x00000434,0x000004be,0x0004003d, + 0x00000006,0x000004c0,0x000004bf,0x00050041,0x0000000d,0x000004c1,0x000004a9,0x000004bd, + 0x0003003e,0x000004c1,0x000004c0,0x000200f9,0x000004b9,0x000200f8,0x000004b9,0x0004003d, + 0x00000025,0x000004c2,0x000004b5,0x00050080,0x00000025,0x000004c3,0x000004c2,0x0000021f, + 0x0003003e,0x000004b5,0x000004c3,0x000200f9,0x000004b6,0x000200f8,0x000004b8,0x000200f9, + 0x000004a0,0x000200f8,0x000004a0,0x0004003d,0x00000006,0x000004c4,0x00000498,0x00050085, + 0x00000006,0x000004c5,0x000004c4,0x0000041d,0x0004006e,0x00000025,0x000004c6,0x000004c5, + 0x0004003d,0x00000033,0x000004c8,0x000004a9,0x0003003e,0x000004c7,0x000004c8,0x0003003e, + 0x000004c9,0x000004c6,0x00060039,0x00000006,0x000004ca,0x00000038,0x000004c7,0x000004c9, + 0x00050041,0x0000000d,0x000004cb,0x00000048,0x00000429,0x0004003d,0x00000006,0x000004cc, + 0x000004cb,0x00050085,0x00000006,0x000004cd,0x000004ca,0x000004cc,0x0004003d,0x00000006, + 0x000004ce,0x00000335,0x00050081,0x00000006,0x000004cf,0x000004ce,0x000004cd,0x0003003e, + 0x00000335,0x000004cf,0x000200f9,0x0000042e,0x000200f8,0x0000042e,0x0004003d,0x00000006, + 0x000004d0,0x00000335,0x000200fe,0x000004d0,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/ntsc_frag.h b/app/src/main/cpp/waylandcomp/shaders/ntsc_frag.h new file mode 100644 index 000000000..9eb75f218 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/ntsc_frag.h @@ -0,0 +1,154 @@ + // 1116.2.0 + #pragma once +const uint32_t ntsc_code[] = { + 0x07230203,0x00010000,0x0008000b,0x000000d5,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x000000bc,0x000000cf,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00060005,0x0000000c,0x6c707061,0x53544e79,0x66762843,0x00003b32,0x00030005, + 0x0000000b,0x00007675,0x00070005,0x0000000f,0x6c707061,0x61635379,0x6e696c6e,0x76287365, + 0x003b3266,0x00030005,0x0000000e,0x00007675,0x00060005,0x00000013,0x6c707061,0x72615779, + 0x66762870,0x00003b32,0x00030005,0x00000012,0x00007675,0x00030005,0x00000016,0x006c6f63, + 0x00060005,0x0000001a,0x75706e49,0x78655474,0x65727574,0x00000000,0x00030005,0x00000020, + 0x00716979,0x00050005,0x00000032,0x6f726863,0x6850616d,0x00657361,0x00030005,0x00000038, + 0x00004350,0x00040006,0x00000038,0x00000000,0x0063646e,0x00060006,0x00000038,0x00000001, + 0x6f736572,0x6974756c,0x00006e6f,0x00060006,0x00000038,0x00000002,0x6d617266,0x756f4365, + 0x0000746e,0x00030005,0x0000003a,0x00006370,0x00030005,0x00000058,0x00626772,0x00050005, + 0x00000066,0x616e6966,0x6c6f436c,0x0000726f,0x00030005,0x00000082,0x006c6f63,0x00050005, + 0x00000087,0x6e616373,0x656e696c,0x00000000,0x00030005,0x0000009e,0x00000072,0x00050005, + 0x000000ba,0x70726177,0x56556465,0x00000000,0x00060005,0x000000bc,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00040005,0x000000bd,0x61726170,0x0000006d,0x00050005,0x000000c0, + 0x6373746e,0x6f6c6f43,0x00000072,0x00040005,0x000000c1,0x61726170,0x0000006d,0x00060005, + 0x000000c4,0x6e616373,0x656e696c,0x6f6c6f43,0x00000072,0x00040005,0x000000c5,0x61726170, + 0x0000006d,0x00050005,0x000000c8,0x616e6966,0x6c6f436c,0x0000726f,0x00050005,0x000000cf, + 0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000001a,0x00000021,0x00000000,0x00040047, + 0x0000001a,0x00000022,0x00000000,0x00030047,0x00000038,0x00000002,0x00050048,0x00000038, + 0x00000000,0x00000023,0x00000000,0x00050048,0x00000038,0x00000001,0x00000023,0x00000010, + 0x00050048,0x00000038,0x00000002,0x00000023,0x00000018,0x00040047,0x000000bc,0x0000001e, + 0x00000000,0x00040047,0x000000cf,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021, + 0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006, + 0x00000002,0x00040020,0x00000008,0x00000007,0x00000007,0x00040017,0x00000009,0x00000006, + 0x00000003,0x00040021,0x0000000a,0x00000009,0x00000008,0x00040021,0x00000011,0x00000007, + 0x00000008,0x00040020,0x00000015,0x00000007,0x00000009,0x00090019,0x00000017,0x00000006, + 0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x00000018, + 0x00000017,0x00040020,0x00000019,0x00000000,0x00000018,0x0004003b,0x00000019,0x0000001a, + 0x00000000,0x00040017,0x0000001d,0x00000006,0x00000004,0x00040018,0x00000022,0x00000009, + 0x00000003,0x0004002b,0x00000006,0x00000023,0x3e991687,0x0004002b,0x00000006,0x00000024, + 0x3f1645a2,0x0004002b,0x00000006,0x00000025,0x3de978d5,0x0006002c,0x00000009,0x00000026, + 0x00000023,0x00000024,0x00000025,0x0004002b,0x00000006,0x00000027,0x3f189375,0x0004002b, + 0x00000006,0x00000028,0xbe8ccccd,0x0004002b,0x00000006,0x00000029,0xbea45a1d,0x0006002c, + 0x00000009,0x0000002a,0x00000027,0x00000028,0x00000029,0x0004002b,0x00000006,0x0000002b, + 0x3e591687,0x0004002b,0x00000006,0x0000002c,0xbf05e354,0x0004002b,0x00000006,0x0000002d, + 0x3e9f3b64,0x0006002c,0x00000009,0x0000002e,0x0000002b,0x0000002c,0x0000002d,0x0006002c, + 0x00000022,0x0000002f,0x00000026,0x0000002a,0x0000002e,0x00040020,0x00000031,0x00000007, + 0x00000006,0x0004002b,0x00000006,0x00000033,0x40490fdb,0x00040015,0x00000034,0x00000020, + 0x00000000,0x0004002b,0x00000034,0x00000035,0x00000001,0x0005001e,0x00000038,0x0000001d, + 0x00000007,0x00000006,0x00040020,0x00000039,0x00000009,0x00000038,0x0004003b,0x00000039, + 0x0000003a,0x00000009,0x00040015,0x0000003b,0x00000020,0x00000001,0x0004002b,0x0000003b, + 0x0000003c,0x00000001,0x00040020,0x0000003d,0x00000009,0x00000006,0x0004002b,0x00000006, + 0x00000041,0x40000000,0x0004002b,0x0000003b,0x00000043,0x00000002,0x0004002b,0x00000006, + 0x00000049,0x3f000000,0x0004002b,0x00000034,0x00000053,0x00000002,0x0004002b,0x00000006, + 0x0000005a,0x3f800000,0x0004002b,0x00000006,0x0000005b,0x3f74bc6a,0x0004002b,0x00000006, + 0x0000005c,0x3f1ef9db,0x0006002c,0x00000009,0x0000005d,0x0000005a,0x0000005b,0x0000005c, + 0x0004002b,0x00000006,0x0000005e,0xbe8b4396,0x0004002b,0x00000006,0x0000005f,0xbf25a1cb, + 0x0006002c,0x00000009,0x00000060,0x0000005a,0x0000005e,0x0000005f,0x0004002b,0x00000006, + 0x00000061,0xbf8d9168,0x0004002b,0x00000006,0x00000062,0x3fda3d71,0x0006002c,0x00000009, + 0x00000063,0x0000005a,0x00000061,0x00000062,0x0006002c,0x00000022,0x00000064,0x0000005d, + 0x00000060,0x00000063,0x0004002b,0x00000006,0x00000069,0x3ba3d70a,0x0004002b,0x00000006, + 0x0000006a,0x00000000,0x0005002c,0x00000007,0x0000006b,0x00000069,0x0000006a,0x0004002b, + 0x00000034,0x0000006e,0x00000000,0x0004002b,0x00000006,0x00000073,0x3b03126f,0x0005002c, + 0x00000007,0x00000074,0x0000006a,0x00000073,0x0004002b,0x00000006,0x00000090,0x3eb33333, + 0x0004002b,0x00000006,0x000000b0,0x3c23d70a,0x00040020,0x000000bb,0x00000001,0x00000007, + 0x0004003b,0x000000bb,0x000000bc,0x00000001,0x0004002b,0x00000006,0x000000cb,0x3f333333, + 0x00040020,0x000000ce,0x00000003,0x0000001d,0x0004003b,0x000000ce,0x000000cf,0x00000003, + 0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b, + 0x00000008,0x000000ba,0x00000007,0x0004003b,0x00000008,0x000000bd,0x00000007,0x0004003b, + 0x00000015,0x000000c0,0x00000007,0x0004003b,0x00000008,0x000000c1,0x00000007,0x0004003b, + 0x00000015,0x000000c4,0x00000007,0x0004003b,0x00000008,0x000000c5,0x00000007,0x0004003b, + 0x00000015,0x000000c8,0x00000007,0x0004003d,0x00000007,0x000000be,0x000000bc,0x0003003e, + 0x000000bd,0x000000be,0x00050039,0x00000007,0x000000bf,0x00000013,0x000000bd,0x0003003e, + 0x000000ba,0x000000bf,0x0004003d,0x00000007,0x000000c2,0x000000ba,0x0003003e,0x000000c1, + 0x000000c2,0x00050039,0x00000009,0x000000c3,0x0000000c,0x000000c1,0x0003003e,0x000000c0, + 0x000000c3,0x0004003d,0x00000007,0x000000c6,0x000000ba,0x0003003e,0x000000c5,0x000000c6, + 0x00050039,0x00000009,0x000000c7,0x0000000f,0x000000c5,0x0003003e,0x000000c4,0x000000c7, + 0x0004003d,0x00000009,0x000000c9,0x000000c0,0x0004003d,0x00000009,0x000000ca,0x000000c4, + 0x00060050,0x00000009,0x000000cc,0x000000cb,0x000000cb,0x000000cb,0x0008000c,0x00000009, + 0x000000cd,0x00000001,0x0000002e,0x000000c9,0x000000ca,0x000000cc,0x0003003e,0x000000c8, + 0x000000cd,0x0004003d,0x00000009,0x000000d0,0x000000c8,0x00050051,0x00000006,0x000000d1, + 0x000000d0,0x00000000,0x00050051,0x00000006,0x000000d2,0x000000d0,0x00000001,0x00050051, + 0x00000006,0x000000d3,0x000000d0,0x00000002,0x00070050,0x0000001d,0x000000d4,0x000000d1, + 0x000000d2,0x000000d3,0x0000005a,0x0003003e,0x000000cf,0x000000d4,0x000100fd,0x00010038, + 0x00050036,0x00000009,0x0000000c,0x00000000,0x0000000a,0x00030037,0x00000008,0x0000000b, + 0x000200f8,0x0000000d,0x0004003b,0x00000015,0x00000016,0x00000007,0x0004003b,0x00000015, + 0x00000020,0x00000007,0x0004003b,0x00000031,0x00000032,0x00000007,0x0004003b,0x00000015, + 0x00000058,0x00000007,0x0004003b,0x00000015,0x00000066,0x00000007,0x0004003d,0x00000018, + 0x0000001b,0x0000001a,0x0004003d,0x00000007,0x0000001c,0x0000000b,0x00050057,0x0000001d, + 0x0000001e,0x0000001b,0x0000001c,0x0008004f,0x00000009,0x0000001f,0x0000001e,0x0000001e, + 0x00000000,0x00000001,0x00000002,0x0003003e,0x00000016,0x0000001f,0x0004003d,0x00000009, + 0x00000021,0x00000016,0x00050090,0x00000009,0x00000030,0x00000021,0x0000002f,0x0003003e, + 0x00000020,0x00000030,0x00050041,0x00000031,0x00000036,0x0000000b,0x00000035,0x0004003d, + 0x00000006,0x00000037,0x00000036,0x00060041,0x0000003d,0x0000003e,0x0000003a,0x0000003c, + 0x00000035,0x0004003d,0x00000006,0x0000003f,0x0000003e,0x00050085,0x00000006,0x00000040, + 0x00000037,0x0000003f,0x0005008d,0x00000006,0x00000042,0x00000040,0x00000041,0x00050041, + 0x0000003d,0x00000044,0x0000003a,0x00000043,0x0004003d,0x00000006,0x00000045,0x00000044, + 0x00050081,0x00000006,0x00000046,0x00000042,0x00000045,0x00050085,0x00000006,0x00000047, + 0x00000033,0x00000046,0x0003003e,0x00000032,0x00000047,0x0004003d,0x00000006,0x00000048, + 0x00000032,0x00050085,0x00000006,0x0000004a,0x00000048,0x00000049,0x0006000c,0x00000006, + 0x0000004b,0x00000001,0x0000000e,0x0000004a,0x00050041,0x00000031,0x0000004c,0x00000020, + 0x00000035,0x0004003d,0x00000006,0x0000004d,0x0000004c,0x00050085,0x00000006,0x0000004e, + 0x0000004d,0x0000004b,0x00050041,0x00000031,0x0000004f,0x00000020,0x00000035,0x0003003e, + 0x0000004f,0x0000004e,0x0004003d,0x00000006,0x00000050,0x00000032,0x00050085,0x00000006, + 0x00000051,0x00000050,0x00000049,0x0006000c,0x00000006,0x00000052,0x00000001,0x0000000d, + 0x00000051,0x00050041,0x00000031,0x00000054,0x00000020,0x00000053,0x0004003d,0x00000006, + 0x00000055,0x00000054,0x00050085,0x00000006,0x00000056,0x00000055,0x00000052,0x00050041, + 0x00000031,0x00000057,0x00000020,0x00000053,0x0003003e,0x00000057,0x00000056,0x0004003d, + 0x00000009,0x00000059,0x00000020,0x00050090,0x00000009,0x00000065,0x00000059,0x00000064, + 0x0003003e,0x00000058,0x00000065,0x0004003d,0x00000018,0x00000067,0x0000001a,0x0004003d, + 0x00000007,0x00000068,0x0000000b,0x00050081,0x00000007,0x0000006c,0x00000068,0x0000006b, + 0x00050057,0x0000001d,0x0000006d,0x00000067,0x0000006c,0x00050051,0x00000006,0x0000006f, + 0x0000006d,0x00000000,0x00050041,0x00000031,0x00000070,0x00000066,0x0000006e,0x0003003e, + 0x00000070,0x0000006f,0x0004003d,0x00000018,0x00000071,0x0000001a,0x0004003d,0x00000007, + 0x00000072,0x0000000b,0x00050081,0x00000007,0x00000075,0x00000072,0x00000074,0x00050057, + 0x0000001d,0x00000076,0x00000071,0x00000075,0x00050051,0x00000006,0x00000077,0x00000076, + 0x00000001,0x00050041,0x00000031,0x00000078,0x00000066,0x00000035,0x0003003e,0x00000078, + 0x00000077,0x0004003d,0x00000018,0x00000079,0x0000001a,0x0004003d,0x00000007,0x0000007a, + 0x0000000b,0x00050083,0x00000007,0x0000007b,0x0000007a,0x0000006b,0x00050057,0x0000001d, + 0x0000007c,0x00000079,0x0000007b,0x00050051,0x00000006,0x0000007d,0x0000007c,0x00000002, + 0x00050041,0x00000031,0x0000007e,0x00000066,0x00000053,0x0003003e,0x0000007e,0x0000007d, + 0x0004003d,0x00000009,0x0000007f,0x00000066,0x000200fe,0x0000007f,0x00010038,0x00050036, + 0x00000009,0x0000000f,0x00000000,0x0000000a,0x00030037,0x00000008,0x0000000e,0x000200f8, + 0x00000010,0x0004003b,0x00000015,0x00000082,0x00000007,0x0004003b,0x00000031,0x00000087, + 0x00000007,0x0004003d,0x00000018,0x00000083,0x0000001a,0x0004003d,0x00000007,0x00000084, + 0x0000000e,0x00050057,0x0000001d,0x00000085,0x00000083,0x00000084,0x0008004f,0x00000009, + 0x00000086,0x00000085,0x00000085,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000082, + 0x00000086,0x00050041,0x00000031,0x00000088,0x0000000e,0x00000035,0x0004003d,0x00000006, + 0x00000089,0x00000088,0x00060041,0x0000003d,0x0000008a,0x0000003a,0x0000003c,0x00000035, + 0x0004003d,0x00000006,0x0000008b,0x0000008a,0x00050085,0x00000006,0x0000008c,0x00000089, + 0x0000008b,0x00050085,0x00000006,0x0000008d,0x0000008c,0x00000041,0x0006000c,0x00000006, + 0x0000008e,0x00000001,0x0000000d,0x0000008d,0x0006000c,0x00000006,0x0000008f,0x00000001, + 0x00000004,0x0000008e,0x00050085,0x00000006,0x00000091,0x0000008f,0x00000090,0x0003003e, + 0x00000087,0x00000091,0x0004003d,0x00000006,0x00000092,0x00000087,0x00050085,0x00000006, + 0x00000093,0x00000092,0x00000049,0x00050083,0x00000006,0x00000094,0x0000005a,0x00000093, + 0x0004003d,0x00000009,0x00000095,0x00000082,0x0005008e,0x00000009,0x00000096,0x00000095, + 0x00000094,0x0003003e,0x00000082,0x00000096,0x0004003d,0x00000009,0x00000097,0x00000082, + 0x000200fe,0x00000097,0x00010038,0x00050036,0x00000007,0x00000013,0x00000000,0x00000011, + 0x00030037,0x00000008,0x00000012,0x000200f8,0x00000014,0x0004003b,0x00000031,0x0000009e, + 0x00000007,0x0004003d,0x00000007,0x0000009a,0x00000012,0x0005008e,0x00000007,0x0000009b, + 0x0000009a,0x00000041,0x00050050,0x00000007,0x0000009c,0x0000005a,0x0000005a,0x00050083, + 0x00000007,0x0000009d,0x0000009b,0x0000009c,0x0003003e,0x00000012,0x0000009d,0x00050041, + 0x00000031,0x0000009f,0x00000012,0x0000006e,0x0004003d,0x00000006,0x000000a0,0x0000009f, + 0x00050041,0x00000031,0x000000a1,0x00000012,0x0000006e,0x0004003d,0x00000006,0x000000a2, + 0x000000a1,0x00050085,0x00000006,0x000000a3,0x000000a0,0x000000a2,0x00050041,0x00000031, + 0x000000a4,0x00000012,0x00000035,0x0004003d,0x00000006,0x000000a5,0x000000a4,0x00050041, + 0x00000031,0x000000a6,0x00000012,0x00000035,0x0004003d,0x00000006,0x000000a7,0x000000a6, + 0x00050085,0x00000006,0x000000a8,0x000000a5,0x000000a7,0x00050081,0x00000006,0x000000a9, + 0x000000a3,0x000000a8,0x0006000c,0x00000006,0x000000aa,0x00000001,0x0000001f,0x000000a9, + 0x0003003e,0x0000009e,0x000000aa,0x0004003d,0x00000007,0x000000ab,0x00000012,0x0004003d, + 0x00000006,0x000000ac,0x0000009e,0x0004003d,0x00000006,0x000000ad,0x0000009e,0x00050085, + 0x00000006,0x000000ae,0x000000ac,0x000000ad,0x0005008e,0x00000007,0x000000af,0x000000ab, + 0x000000ae,0x0005008e,0x00000007,0x000000b1,0x000000af,0x000000b0,0x0004003d,0x00000007, + 0x000000b2,0x00000012,0x00050081,0x00000007,0x000000b3,0x000000b2,0x000000b1,0x0003003e, + 0x00000012,0x000000b3,0x0004003d,0x00000007,0x000000b4,0x00000012,0x0005008e,0x00000007, + 0x000000b5,0x000000b4,0x00000049,0x00050050,0x00000007,0x000000b6,0x00000049,0x00000049, + 0x00050081,0x00000007,0x000000b7,0x000000b5,0x000000b6,0x000200fe,0x000000b7,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/sgsr_frag.h b/app/src/main/cpp/waylandcomp/shaders/sgsr_frag.h new file mode 100644 index 000000000..2452fb68f --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/sgsr_frag.h @@ -0,0 +1,403 @@ + // 1116.2.0 + #pragma once +const uint32_t sgsr_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000216,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000051,0x00000214,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00070005,0x0000000a,0x74736166,0x636e614c,0x32736f7a,0x3b316628,0x00000000, + 0x00030005,0x00000009,0x00000078,0x00080005,0x00000012,0x67696577,0x28597468,0x663b3166, + 0x31663b31,0x3b31663b,0x00000000,0x00030005,0x0000000e,0x00007864,0x00030005,0x0000000f, + 0x00007964,0x00030005,0x00000010,0x00000063,0x00030005,0x00000011,0x00647473,0x00030005, + 0x00000014,0x00004177,0x00030005,0x00000018,0x00004277,0x00030005,0x00000026,0x00000078, + 0x00030005,0x00000038,0x00000077,0x00040005,0x00000039,0x61726170,0x0000006d,0x00060005, + 0x00000045,0x77656956,0x74726f70,0x6f666e49,0x00000000,0x00030005,0x00000046,0x00004350, + 0x00040006,0x00000046,0x00000000,0x0063646e,0x00070006,0x00000046,0x00000001,0x77656956, + 0x74726f70,0x6f666e49,0x00000000,0x00070006,0x00000046,0x00000002,0x65676445,0x72616853, + 0x73656e70,0x00000073,0x00030005,0x00000048,0x00006370,0x00030005,0x0000004f,0x00007675, + 0x00060005,0x00000051,0x67617266,0x43786554,0x64726f6f,0x00000000,0x00040005,0x00000053, + 0x6f6c6f63,0x00000072,0x00030005,0x00000057,0x00307370,0x00050005,0x00000067,0x43676d69, + 0x64726f6f,0x00000000,0x00060005,0x00000070,0x43676d69,0x64726f6f,0x65786950,0x0000006c, + 0x00040005,0x00000073,0x726f6f63,0x00000064,0x00030005,0x00000078,0x00006c70,0x00040005, + 0x0000007d,0x7466656c,0x00000000,0x00050005,0x00000081,0x65676465,0x65746f56,0x00000000, + 0x00040005,0x000000a2,0x68676972,0x00000074,0x00040005,0x000000aa,0x6f447075,0x00006e77, + 0x00040005,0x000000c5,0x6e61656d,0x00000000,0x00030005,0x000000e4,0x006d7573,0x00030005, + 0x00000114,0x00647473,0x00030005,0x00000118,0x00595761,0x00040005,0x0000011c,0x61726170, + 0x0000006d,0x00040005,0x0000011f,0x61726170,0x0000006d,0x00040005,0x00000120,0x61726170, + 0x0000006d,0x00040005,0x00000123,0x61726170,0x0000006d,0x00040005,0x0000012c,0x61726170, + 0x0000006d,0x00040005,0x0000012d,0x61726170,0x0000006d,0x00040005,0x0000012e,0x61726170, + 0x0000006d,0x00040005,0x00000131,0x61726170,0x0000006d,0x00040005,0x0000013d,0x61726170, + 0x0000006d,0x00040005,0x0000013e,0x61726170,0x0000006d,0x00040005,0x0000013f,0x61726170, + 0x0000006d,0x00040005,0x00000142,0x61726170,0x0000006d,0x00040005,0x0000014a,0x61726170, + 0x0000006d,0x00040005,0x0000014d,0x61726170,0x0000006d,0x00040005,0x0000014e,0x61726170, + 0x0000006d,0x00040005,0x00000151,0x61726170,0x0000006d,0x00040005,0x0000015c,0x61726170, + 0x0000006d,0x00040005,0x0000015d,0x61726170,0x0000006d,0x00040005,0x0000015e,0x61726170, + 0x0000006d,0x00040005,0x00000161,0x61726170,0x0000006d,0x00040005,0x00000169,0x61726170, + 0x0000006d,0x00040005,0x0000016c,0x61726170,0x0000006d,0x00040005,0x0000016d,0x61726170, + 0x0000006d,0x00040005,0x00000170,0x61726170,0x0000006d,0x00040005,0x00000175,0x61726170, + 0x0000006d,0x00040005,0x00000178,0x61726170,0x0000006d,0x00040005,0x0000017b,0x61726170, + 0x0000006d,0x00040005,0x0000017e,0x61726170,0x0000006d,0x00040005,0x00000186,0x61726170, + 0x0000006d,0x00040005,0x00000187,0x61726170,0x0000006d,0x00040005,0x0000018a,0x61726170, + 0x0000006d,0x00040005,0x0000018d,0x61726170,0x0000006d,0x00040005,0x00000198,0x61726170, + 0x0000006d,0x00040005,0x00000199,0x61726170,0x0000006d,0x00040005,0x0000019a,0x61726170, + 0x0000006d,0x00040005,0x0000019d,0x61726170,0x0000006d,0x00040005,0x000001a8,0x61726170, + 0x0000006d,0x00040005,0x000001a9,0x61726170,0x0000006d,0x00040005,0x000001aa,0x61726170, + 0x0000006d,0x00040005,0x000001ad,0x61726170,0x0000006d,0x00040005,0x000001b5,0x61726170, + 0x0000006d,0x00040005,0x000001b6,0x61726170,0x0000006d,0x00040005,0x000001b9,0x61726170, + 0x0000006d,0x00040005,0x000001bc,0x61726170,0x0000006d,0x00040005,0x000001c4,0x61726170, + 0x0000006d,0x00040005,0x000001c5,0x61726170,0x0000006d,0x00040005,0x000001c8,0x61726170, + 0x0000006d,0x00040005,0x000001cb,0x61726170,0x0000006d,0x00040005,0x000001d0,0x616e6966, + 0x0000596c,0x00040005,0x000001d6,0x5978616d,0x00000000,0x00040005,0x000001e2,0x596e696d, + 0x00000000,0x00040005,0x000001f7,0x746c6564,0x00005961,0x00050005,0x00000214,0x5f74756f, + 0x67726154,0x00307465,0x00030047,0x00000046,0x00000002,0x00050048,0x00000046,0x00000000, + 0x00000023,0x00000000,0x00050048,0x00000046,0x00000001,0x00000023,0x00000010,0x00050048, + 0x00000046,0x00000002,0x00000023,0x00000020,0x00040047,0x00000051,0x0000001e,0x00000000, + 0x00040047,0x00000057,0x00000021,0x00000000,0x00040047,0x00000057,0x00000022,0x00000000, + 0x00040047,0x00000214,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003, + 0x00000002,0x00030016,0x00000006,0x00000020,0x00040020,0x00000007,0x00000007,0x00000006, + 0x00040021,0x00000008,0x00000006,0x00000007,0x00040017,0x0000000c,0x00000006,0x00000002, + 0x00070021,0x0000000d,0x0000000c,0x00000007,0x00000007,0x00000007,0x00000007,0x0004002b, + 0x00000006,0x00000016,0x40800000,0x0004002b,0x00000006,0x0000002e,0x3f0ccccd,0x0004002b, + 0x00000006,0x00000034,0x00000000,0x0004002b,0x00000006,0x00000035,0x3f800000,0x00040017, + 0x00000043,0x00000006,0x00000004,0x00040020,0x00000044,0x00000007,0x00000043,0x0005001e, + 0x00000046,0x00000043,0x00000043,0x00000006,0x00040020,0x00000047,0x00000009,0x00000046, + 0x0004003b,0x00000047,0x00000048,0x00000009,0x00040015,0x00000049,0x00000020,0x00000001, + 0x0004002b,0x00000049,0x0000004a,0x00000001,0x00040020,0x0000004b,0x00000009,0x00000043, + 0x00040020,0x0000004e,0x00000007,0x0000000c,0x00040020,0x00000050,0x00000001,0x0000000c, + 0x0004003b,0x00000050,0x00000051,0x00000001,0x00090019,0x00000054,0x00000006,0x00000001, + 0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x00000055,0x00000054, + 0x00040020,0x00000056,0x00000000,0x00000055,0x0004003b,0x00000056,0x00000057,0x00000000, + 0x00040017,0x0000005b,0x00000006,0x00000003,0x00040015,0x0000005d,0x00000020,0x00000000, + 0x0004002b,0x0000005d,0x0000005e,0x00000000,0x0004002b,0x0000005d,0x00000061,0x00000001, + 0x0004002b,0x0000005d,0x00000064,0x00000002,0x0004002b,0x00000006,0x0000006c,0xbf000000, + 0x0004002b,0x00000006,0x0000006d,0x3f000000,0x0005002c,0x0000000c,0x0000006e,0x0000006c, + 0x0000006d,0x0004002b,0x00000006,0x00000097,0x3d008081,0x00020014,0x00000098,0x0004002b, + 0x0000005d,0x000000c2,0x00000003,0x0004002b,0x00000006,0x000000d1,0x3e800000,0x0004002b, + 0x00000006,0x00000115,0x400ba2e8,0x0004002b,0x00000006,0x0000013b,0x40000000,0x0004002b, + 0x00000049,0x000001ee,0x00000002,0x00040020,0x000001ef,0x00000009,0x00000006,0x0004002b, + 0x00000006,0x000001fd,0xbdb8b8b9,0x0004002b,0x00000006,0x000001fe,0x3db8b8b9,0x00040020, + 0x00000213,0x00000003,0x00000043,0x0004003b,0x00000213,0x00000214,0x00000003,0x00050036, + 0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000044, + 0x00000045,0x00000007,0x0004003b,0x0000004e,0x0000004f,0x00000007,0x0004003b,0x00000044, + 0x00000053,0x00000007,0x0004003b,0x0000004e,0x00000067,0x00000007,0x0004003b,0x0000004e, + 0x00000070,0x00000007,0x0004003b,0x0000004e,0x00000073,0x00000007,0x0004003b,0x0000004e, + 0x00000078,0x00000007,0x0004003b,0x00000044,0x0000007d,0x00000007,0x0004003b,0x00000007, + 0x00000081,0x00000007,0x0004003b,0x00000044,0x000000a2,0x00000007,0x0004003b,0x00000044, + 0x000000aa,0x00000007,0x0004003b,0x00000007,0x000000c5,0x00000007,0x0004003b,0x00000007, + 0x000000e4,0x00000007,0x0004003b,0x00000007,0x00000114,0x00000007,0x0004003b,0x0000004e, + 0x00000118,0x00000007,0x0004003b,0x00000007,0x0000011c,0x00000007,0x0004003b,0x00000007, + 0x0000011f,0x00000007,0x0004003b,0x00000007,0x00000120,0x00000007,0x0004003b,0x00000007, + 0x00000123,0x00000007,0x0004003b,0x00000007,0x0000012c,0x00000007,0x0004003b,0x00000007, + 0x0000012d,0x00000007,0x0004003b,0x00000007,0x0000012e,0x00000007,0x0004003b,0x00000007, + 0x00000131,0x00000007,0x0004003b,0x00000007,0x0000013d,0x00000007,0x0004003b,0x00000007, + 0x0000013e,0x00000007,0x0004003b,0x00000007,0x0000013f,0x00000007,0x0004003b,0x00000007, + 0x00000142,0x00000007,0x0004003b,0x00000007,0x0000014a,0x00000007,0x0004003b,0x00000007, + 0x0000014d,0x00000007,0x0004003b,0x00000007,0x0000014e,0x00000007,0x0004003b,0x00000007, + 0x00000151,0x00000007,0x0004003b,0x00000007,0x0000015c,0x00000007,0x0004003b,0x00000007, + 0x0000015d,0x00000007,0x0004003b,0x00000007,0x0000015e,0x00000007,0x0004003b,0x00000007, + 0x00000161,0x00000007,0x0004003b,0x00000007,0x00000169,0x00000007,0x0004003b,0x00000007, + 0x0000016c,0x00000007,0x0004003b,0x00000007,0x0000016d,0x00000007,0x0004003b,0x00000007, + 0x00000170,0x00000007,0x0004003b,0x00000007,0x00000175,0x00000007,0x0004003b,0x00000007, + 0x00000178,0x00000007,0x0004003b,0x00000007,0x0000017b,0x00000007,0x0004003b,0x00000007, + 0x0000017e,0x00000007,0x0004003b,0x00000007,0x00000186,0x00000007,0x0004003b,0x00000007, + 0x00000187,0x00000007,0x0004003b,0x00000007,0x0000018a,0x00000007,0x0004003b,0x00000007, + 0x0000018d,0x00000007,0x0004003b,0x00000007,0x00000198,0x00000007,0x0004003b,0x00000007, + 0x00000199,0x00000007,0x0004003b,0x00000007,0x0000019a,0x00000007,0x0004003b,0x00000007, + 0x0000019d,0x00000007,0x0004003b,0x00000007,0x000001a8,0x00000007,0x0004003b,0x00000007, + 0x000001a9,0x00000007,0x0004003b,0x00000007,0x000001aa,0x00000007,0x0004003b,0x00000007, + 0x000001ad,0x00000007,0x0004003b,0x00000007,0x000001b5,0x00000007,0x0004003b,0x00000007, + 0x000001b6,0x00000007,0x0004003b,0x00000007,0x000001b9,0x00000007,0x0004003b,0x00000007, + 0x000001bc,0x00000007,0x0004003b,0x00000007,0x000001c4,0x00000007,0x0004003b,0x00000007, + 0x000001c5,0x00000007,0x0004003b,0x00000007,0x000001c8,0x00000007,0x0004003b,0x00000007, + 0x000001cb,0x00000007,0x0004003b,0x00000007,0x000001d0,0x00000007,0x0004003b,0x00000007, + 0x000001d6,0x00000007,0x0004003b,0x00000007,0x000001e2,0x00000007,0x0004003b,0x00000007, + 0x000001f7,0x00000007,0x00050041,0x0000004b,0x0000004c,0x00000048,0x0000004a,0x0004003d, + 0x00000043,0x0000004d,0x0000004c,0x0003003e,0x00000045,0x0000004d,0x0004003d,0x0000000c, + 0x00000052,0x00000051,0x0003003e,0x0000004f,0x00000052,0x0004003d,0x00000055,0x00000058, + 0x00000057,0x0004003d,0x0000000c,0x00000059,0x0000004f,0x00070058,0x00000043,0x0000005a, + 0x00000058,0x00000059,0x00000002,0x00000034,0x0008004f,0x0000005b,0x0000005c,0x0000005a, + 0x0000005a,0x00000000,0x00000001,0x00000002,0x00050041,0x00000007,0x0000005f,0x00000053, + 0x0000005e,0x00050051,0x00000006,0x00000060,0x0000005c,0x00000000,0x0003003e,0x0000005f, + 0x00000060,0x00050041,0x00000007,0x00000062,0x00000053,0x00000061,0x00050051,0x00000006, + 0x00000063,0x0000005c,0x00000001,0x0003003e,0x00000062,0x00000063,0x00050041,0x00000007, + 0x00000065,0x00000053,0x00000064,0x00050051,0x00000006,0x00000066,0x0000005c,0x00000002, + 0x0003003e,0x00000065,0x00000066,0x0004003d,0x0000000c,0x00000068,0x0000004f,0x0004003d, + 0x00000043,0x00000069,0x00000045,0x0007004f,0x0000000c,0x0000006a,0x00000069,0x00000069, + 0x00000002,0x00000003,0x00050085,0x0000000c,0x0000006b,0x00000068,0x0000006a,0x00050081, + 0x0000000c,0x0000006f,0x0000006b,0x0000006e,0x0003003e,0x00000067,0x0000006f,0x0004003d, + 0x0000000c,0x00000071,0x00000067,0x0006000c,0x0000000c,0x00000072,0x00000001,0x00000008, + 0x00000071,0x0003003e,0x00000070,0x00000072,0x0004003d,0x0000000c,0x00000074,0x00000070, + 0x0004003d,0x00000043,0x00000075,0x00000045,0x0007004f,0x0000000c,0x00000076,0x00000075, + 0x00000075,0x00000000,0x00000001,0x00050085,0x0000000c,0x00000077,0x00000074,0x00000076, + 0x0003003e,0x00000073,0x00000077,0x0004003d,0x0000000c,0x00000079,0x00000067,0x0004003d, + 0x0000000c,0x0000007a,0x00000070,0x0004007f,0x0000000c,0x0000007b,0x0000007a,0x00050081, + 0x0000000c,0x0000007c,0x00000079,0x0000007b,0x0003003e,0x00000078,0x0000007c,0x0004003d, + 0x00000055,0x0000007e,0x00000057,0x0004003d,0x0000000c,0x0000007f,0x00000073,0x00060060, + 0x00000043,0x00000080,0x0000007e,0x0000007f,0x0000004a,0x0003003e,0x0000007d,0x00000080, + 0x00050041,0x00000007,0x00000082,0x0000007d,0x00000064,0x0004003d,0x00000006,0x00000083, + 0x00000082,0x00050041,0x00000007,0x00000084,0x0000007d,0x00000061,0x0004003d,0x00000006, + 0x00000085,0x00000084,0x00050083,0x00000006,0x00000086,0x00000083,0x00000085,0x0006000c, + 0x00000006,0x00000087,0x00000001,0x00000004,0x00000086,0x00050041,0x00000007,0x00000088, + 0x00000053,0x00000061,0x0004003d,0x00000006,0x00000089,0x00000088,0x00050041,0x00000007, + 0x0000008a,0x0000007d,0x00000061,0x0004003d,0x00000006,0x0000008b,0x0000008a,0x00050083, + 0x00000006,0x0000008c,0x00000089,0x0000008b,0x0006000c,0x00000006,0x0000008d,0x00000001, + 0x00000004,0x0000008c,0x00050081,0x00000006,0x0000008e,0x00000087,0x0000008d,0x00050041, + 0x00000007,0x0000008f,0x00000053,0x00000061,0x0004003d,0x00000006,0x00000090,0x0000008f, + 0x00050041,0x00000007,0x00000091,0x0000007d,0x00000064,0x0004003d,0x00000006,0x00000092, + 0x00000091,0x00050083,0x00000006,0x00000093,0x00000090,0x00000092,0x0006000c,0x00000006, + 0x00000094,0x00000001,0x00000004,0x00000093,0x00050081,0x00000006,0x00000095,0x0000008e, + 0x00000094,0x0003003e,0x00000081,0x00000095,0x0004003d,0x00000006,0x00000096,0x00000081, + 0x000500ba,0x00000098,0x00000099,0x00000096,0x00000097,0x000300f7,0x0000009b,0x00000000, + 0x000400fa,0x00000099,0x0000009a,0x0000009b,0x000200f8,0x0000009a,0x00050041,0x00000007, + 0x0000009c,0x00000045,0x0000005e,0x0004003d,0x00000006,0x0000009d,0x0000009c,0x00050041, + 0x00000007,0x0000009e,0x00000073,0x0000005e,0x0004003d,0x00000006,0x0000009f,0x0000009e, + 0x00050081,0x00000006,0x000000a0,0x0000009f,0x0000009d,0x00050041,0x00000007,0x000000a1, + 0x00000073,0x0000005e,0x0003003e,0x000000a1,0x000000a0,0x0004003d,0x00000055,0x000000a3, + 0x00000057,0x0004003d,0x0000000c,0x000000a4,0x00000073,0x00050041,0x00000007,0x000000a5, + 0x00000045,0x0000005e,0x0004003d,0x00000006,0x000000a6,0x000000a5,0x00050050,0x0000000c, + 0x000000a7,0x000000a6,0x00000034,0x00050081,0x0000000c,0x000000a8,0x000000a4,0x000000a7, + 0x00060060,0x00000043,0x000000a9,0x000000a3,0x000000a8,0x0000004a,0x0003003e,0x000000a2, + 0x000000a9,0x0004003d,0x00000055,0x000000ab,0x00000057,0x0004003d,0x0000000c,0x000000ac, + 0x00000073,0x00050041,0x00000007,0x000000ad,0x00000045,0x00000061,0x0004003d,0x00000006, + 0x000000ae,0x000000ad,0x0004007f,0x00000006,0x000000af,0x000000ae,0x00050050,0x0000000c, + 0x000000b0,0x00000034,0x000000af,0x00050081,0x0000000c,0x000000b1,0x000000ac,0x000000b0, + 0x00060060,0x00000043,0x000000b2,0x000000ab,0x000000b1,0x0000004a,0x0007004f,0x0000000c, + 0x000000b3,0x000000b2,0x000000b2,0x00000003,0x00000002,0x00050041,0x00000007,0x000000b4, + 0x000000aa,0x0000005e,0x00050051,0x00000006,0x000000b5,0x000000b3,0x00000000,0x0003003e, + 0x000000b4,0x000000b5,0x00050041,0x00000007,0x000000b6,0x000000aa,0x00000061,0x00050051, + 0x00000006,0x000000b7,0x000000b3,0x00000001,0x0003003e,0x000000b6,0x000000b7,0x0004003d, + 0x00000055,0x000000b8,0x00000057,0x0004003d,0x0000000c,0x000000b9,0x00000073,0x00050041, + 0x00000007,0x000000ba,0x00000045,0x00000061,0x0004003d,0x00000006,0x000000bb,0x000000ba, + 0x00050050,0x0000000c,0x000000bc,0x00000034,0x000000bb,0x00050081,0x0000000c,0x000000bd, + 0x000000b9,0x000000bc,0x00060060,0x00000043,0x000000be,0x000000b8,0x000000bd,0x0000004a, + 0x0007004f,0x0000000c,0x000000bf,0x000000be,0x000000be,0x00000001,0x00000000,0x00050041, + 0x00000007,0x000000c0,0x000000aa,0x00000064,0x00050051,0x00000006,0x000000c1,0x000000bf, + 0x00000000,0x0003003e,0x000000c0,0x000000c1,0x00050041,0x00000007,0x000000c3,0x000000aa, + 0x000000c2,0x00050051,0x00000006,0x000000c4,0x000000bf,0x00000001,0x0003003e,0x000000c3, + 0x000000c4,0x00050041,0x00000007,0x000000c6,0x0000007d,0x00000061,0x0004003d,0x00000006, + 0x000000c7,0x000000c6,0x00050041,0x00000007,0x000000c8,0x0000007d,0x00000064,0x0004003d, + 0x00000006,0x000000c9,0x000000c8,0x00050081,0x00000006,0x000000ca,0x000000c7,0x000000c9, + 0x00050041,0x00000007,0x000000cb,0x000000a2,0x0000005e,0x0004003d,0x00000006,0x000000cc, + 0x000000cb,0x00050081,0x00000006,0x000000cd,0x000000ca,0x000000cc,0x00050041,0x00000007, + 0x000000ce,0x000000a2,0x000000c2,0x0004003d,0x00000006,0x000000cf,0x000000ce,0x00050081, + 0x00000006,0x000000d0,0x000000cd,0x000000cf,0x00050085,0x00000006,0x000000d2,0x000000d0, + 0x000000d1,0x0003003e,0x000000c5,0x000000d2,0x0004003d,0x00000043,0x000000d3,0x0000007d, + 0x0004003d,0x00000006,0x000000d4,0x000000c5,0x00070050,0x00000043,0x000000d5,0x000000d4, + 0x000000d4,0x000000d4,0x000000d4,0x00050083,0x00000043,0x000000d6,0x000000d3,0x000000d5, + 0x0003003e,0x0000007d,0x000000d6,0x0004003d,0x00000043,0x000000d7,0x000000a2,0x0004003d, + 0x00000006,0x000000d8,0x000000c5,0x00070050,0x00000043,0x000000d9,0x000000d8,0x000000d8, + 0x000000d8,0x000000d8,0x00050083,0x00000043,0x000000da,0x000000d7,0x000000d9,0x0003003e, + 0x000000a2,0x000000da,0x0004003d,0x00000043,0x000000db,0x000000aa,0x0004003d,0x00000006, + 0x000000dc,0x000000c5,0x00070050,0x00000043,0x000000dd,0x000000dc,0x000000dc,0x000000dc, + 0x000000dc,0x00050083,0x00000043,0x000000de,0x000000db,0x000000dd,0x0003003e,0x000000aa, + 0x000000de,0x00050041,0x00000007,0x000000df,0x00000053,0x00000061,0x0004003d,0x00000006, + 0x000000e0,0x000000df,0x0004003d,0x00000006,0x000000e1,0x000000c5,0x00050083,0x00000006, + 0x000000e2,0x000000e0,0x000000e1,0x00050041,0x00000007,0x000000e3,0x00000053,0x000000c2, + 0x0003003e,0x000000e3,0x000000e2,0x00050041,0x00000007,0x000000e5,0x0000007d,0x0000005e, + 0x0004003d,0x00000006,0x000000e6,0x000000e5,0x0006000c,0x00000006,0x000000e7,0x00000001, + 0x00000004,0x000000e6,0x00050041,0x00000007,0x000000e8,0x0000007d,0x00000061,0x0004003d, + 0x00000006,0x000000e9,0x000000e8,0x0006000c,0x00000006,0x000000ea,0x00000001,0x00000004, + 0x000000e9,0x00050081,0x00000006,0x000000eb,0x000000e7,0x000000ea,0x00050041,0x00000007, + 0x000000ec,0x0000007d,0x00000064,0x0004003d,0x00000006,0x000000ed,0x000000ec,0x0006000c, + 0x00000006,0x000000ee,0x00000001,0x00000004,0x000000ed,0x00050081,0x00000006,0x000000ef, + 0x000000eb,0x000000ee,0x00050041,0x00000007,0x000000f0,0x0000007d,0x000000c2,0x0004003d, + 0x00000006,0x000000f1,0x000000f0,0x0006000c,0x00000006,0x000000f2,0x00000001,0x00000004, + 0x000000f1,0x00050081,0x00000006,0x000000f3,0x000000ef,0x000000f2,0x00050041,0x00000007, + 0x000000f4,0x000000a2,0x0000005e,0x0004003d,0x00000006,0x000000f5,0x000000f4,0x0006000c, + 0x00000006,0x000000f6,0x00000001,0x00000004,0x000000f5,0x00050041,0x00000007,0x000000f7, + 0x000000a2,0x00000061,0x0004003d,0x00000006,0x000000f8,0x000000f7,0x0006000c,0x00000006, + 0x000000f9,0x00000001,0x00000004,0x000000f8,0x00050081,0x00000006,0x000000fa,0x000000f6, + 0x000000f9,0x00050041,0x00000007,0x000000fb,0x000000a2,0x00000064,0x0004003d,0x00000006, + 0x000000fc,0x000000fb,0x0006000c,0x00000006,0x000000fd,0x00000001,0x00000004,0x000000fc, + 0x00050081,0x00000006,0x000000fe,0x000000fa,0x000000fd,0x00050041,0x00000007,0x000000ff, + 0x000000a2,0x000000c2,0x0004003d,0x00000006,0x00000100,0x000000ff,0x0006000c,0x00000006, + 0x00000101,0x00000001,0x00000004,0x00000100,0x00050081,0x00000006,0x00000102,0x000000fe, + 0x00000101,0x00050081,0x00000006,0x00000103,0x000000f3,0x00000102,0x00050041,0x00000007, + 0x00000104,0x000000aa,0x0000005e,0x0004003d,0x00000006,0x00000105,0x00000104,0x0006000c, + 0x00000006,0x00000106,0x00000001,0x00000004,0x00000105,0x00050041,0x00000007,0x00000107, + 0x000000aa,0x00000061,0x0004003d,0x00000006,0x00000108,0x00000107,0x0006000c,0x00000006, + 0x00000109,0x00000001,0x00000004,0x00000108,0x00050081,0x00000006,0x0000010a,0x00000106, + 0x00000109,0x00050041,0x00000007,0x0000010b,0x000000aa,0x00000064,0x0004003d,0x00000006, + 0x0000010c,0x0000010b,0x0006000c,0x00000006,0x0000010d,0x00000001,0x00000004,0x0000010c, + 0x00050081,0x00000006,0x0000010e,0x0000010a,0x0000010d,0x00050041,0x00000007,0x0000010f, + 0x000000aa,0x000000c2,0x0004003d,0x00000006,0x00000110,0x0000010f,0x0006000c,0x00000006, + 0x00000111,0x00000001,0x00000004,0x00000110,0x00050081,0x00000006,0x00000112,0x0000010e, + 0x00000111,0x00050081,0x00000006,0x00000113,0x00000103,0x00000112,0x0003003e,0x000000e4, + 0x00000113,0x0004003d,0x00000006,0x00000116,0x000000e4,0x00050088,0x00000006,0x00000117, + 0x00000115,0x00000116,0x0003003e,0x00000114,0x00000117,0x00050041,0x00000007,0x00000119, + 0x00000078,0x00000061,0x0004003d,0x00000006,0x0000011a,0x00000119,0x00050081,0x00000006, + 0x0000011b,0x0000011a,0x00000035,0x00050041,0x00000007,0x0000011d,0x00000078,0x0000005e, + 0x0004003d,0x00000006,0x0000011e,0x0000011d,0x0003003e,0x0000011c,0x0000011e,0x0003003e, + 0x0000011f,0x0000011b,0x00050041,0x00000007,0x00000121,0x000000aa,0x0000005e,0x0004003d, + 0x00000006,0x00000122,0x00000121,0x0003003e,0x00000120,0x00000122,0x0004003d,0x00000006, + 0x00000124,0x00000114,0x0003003e,0x00000123,0x00000124,0x00080039,0x0000000c,0x00000125, + 0x00000012,0x0000011c,0x0000011f,0x00000120,0x00000123,0x0003003e,0x00000118,0x00000125, + 0x00050041,0x00000007,0x00000126,0x00000078,0x0000005e,0x0004003d,0x00000006,0x00000127, + 0x00000126,0x00050083,0x00000006,0x00000128,0x00000127,0x00000035,0x00050041,0x00000007, + 0x00000129,0x00000078,0x00000061,0x0004003d,0x00000006,0x0000012a,0x00000129,0x00050081, + 0x00000006,0x0000012b,0x0000012a,0x00000035,0x0003003e,0x0000012c,0x00000128,0x0003003e, + 0x0000012d,0x0000012b,0x00050041,0x00000007,0x0000012f,0x000000aa,0x00000061,0x0004003d, + 0x00000006,0x00000130,0x0000012f,0x0003003e,0x0000012e,0x00000130,0x0004003d,0x00000006, + 0x00000132,0x00000114,0x0003003e,0x00000131,0x00000132,0x00080039,0x0000000c,0x00000133, + 0x00000012,0x0000012c,0x0000012d,0x0000012e,0x00000131,0x0004003d,0x0000000c,0x00000134, + 0x00000118,0x00050081,0x0000000c,0x00000135,0x00000134,0x00000133,0x0003003e,0x00000118, + 0x00000135,0x00050041,0x00000007,0x00000136,0x00000078,0x0000005e,0x0004003d,0x00000006, + 0x00000137,0x00000136,0x00050083,0x00000006,0x00000138,0x00000137,0x00000035,0x00050041, + 0x00000007,0x00000139,0x00000078,0x00000061,0x0004003d,0x00000006,0x0000013a,0x00000139, + 0x00050083,0x00000006,0x0000013c,0x0000013a,0x0000013b,0x0003003e,0x0000013d,0x00000138, + 0x0003003e,0x0000013e,0x0000013c,0x00050041,0x00000007,0x00000140,0x000000aa,0x00000064, + 0x0004003d,0x00000006,0x00000141,0x00000140,0x0003003e,0x0000013f,0x00000141,0x0004003d, + 0x00000006,0x00000143,0x00000114,0x0003003e,0x00000142,0x00000143,0x00080039,0x0000000c, + 0x00000144,0x00000012,0x0000013d,0x0000013e,0x0000013f,0x00000142,0x0004003d,0x0000000c, + 0x00000145,0x00000118,0x00050081,0x0000000c,0x00000146,0x00000145,0x00000144,0x0003003e, + 0x00000118,0x00000146,0x00050041,0x00000007,0x00000147,0x00000078,0x00000061,0x0004003d, + 0x00000006,0x00000148,0x00000147,0x00050083,0x00000006,0x00000149,0x00000148,0x0000013b, + 0x00050041,0x00000007,0x0000014b,0x00000078,0x0000005e,0x0004003d,0x00000006,0x0000014c, + 0x0000014b,0x0003003e,0x0000014a,0x0000014c,0x0003003e,0x0000014d,0x00000149,0x00050041, + 0x00000007,0x0000014f,0x000000aa,0x000000c2,0x0004003d,0x00000006,0x00000150,0x0000014f, + 0x0003003e,0x0000014e,0x00000150,0x0004003d,0x00000006,0x00000152,0x00000114,0x0003003e, + 0x00000151,0x00000152,0x00080039,0x0000000c,0x00000153,0x00000012,0x0000014a,0x0000014d, + 0x0000014e,0x00000151,0x0004003d,0x0000000c,0x00000154,0x00000118,0x00050081,0x0000000c, + 0x00000155,0x00000154,0x00000153,0x0003003e,0x00000118,0x00000155,0x00050041,0x00000007, + 0x00000156,0x00000078,0x0000005e,0x0004003d,0x00000006,0x00000157,0x00000156,0x00050081, + 0x00000006,0x00000158,0x00000157,0x00000035,0x00050041,0x00000007,0x00000159,0x00000078, + 0x00000061,0x0004003d,0x00000006,0x0000015a,0x00000159,0x00050083,0x00000006,0x0000015b, + 0x0000015a,0x00000035,0x0003003e,0x0000015c,0x00000158,0x0003003e,0x0000015d,0x0000015b, + 0x00050041,0x00000007,0x0000015f,0x0000007d,0x0000005e,0x0004003d,0x00000006,0x00000160, + 0x0000015f,0x0003003e,0x0000015e,0x00000160,0x0004003d,0x00000006,0x00000162,0x00000114, + 0x0003003e,0x00000161,0x00000162,0x00080039,0x0000000c,0x00000163,0x00000012,0x0000015c, + 0x0000015d,0x0000015e,0x00000161,0x0004003d,0x0000000c,0x00000164,0x00000118,0x00050081, + 0x0000000c,0x00000165,0x00000164,0x00000163,0x0003003e,0x00000118,0x00000165,0x00050041, + 0x00000007,0x00000166,0x00000078,0x00000061,0x0004003d,0x00000006,0x00000167,0x00000166, + 0x00050083,0x00000006,0x00000168,0x00000167,0x00000035,0x00050041,0x00000007,0x0000016a, + 0x00000078,0x0000005e,0x0004003d,0x00000006,0x0000016b,0x0000016a,0x0003003e,0x00000169, + 0x0000016b,0x0003003e,0x0000016c,0x00000168,0x00050041,0x00000007,0x0000016e,0x0000007d, + 0x00000061,0x0004003d,0x00000006,0x0000016f,0x0000016e,0x0003003e,0x0000016d,0x0000016f, + 0x0004003d,0x00000006,0x00000171,0x00000114,0x0003003e,0x00000170,0x00000171,0x00080039, + 0x0000000c,0x00000172,0x00000012,0x00000169,0x0000016c,0x0000016d,0x00000170,0x0004003d, + 0x0000000c,0x00000173,0x00000118,0x00050081,0x0000000c,0x00000174,0x00000173,0x00000172, + 0x0003003e,0x00000118,0x00000174,0x00050041,0x00000007,0x00000176,0x00000078,0x0000005e, + 0x0004003d,0x00000006,0x00000177,0x00000176,0x0003003e,0x00000175,0x00000177,0x00050041, + 0x00000007,0x00000179,0x00000078,0x00000061,0x0004003d,0x00000006,0x0000017a,0x00000179, + 0x0003003e,0x00000178,0x0000017a,0x00050041,0x00000007,0x0000017c,0x0000007d,0x00000064, + 0x0004003d,0x00000006,0x0000017d,0x0000017c,0x0003003e,0x0000017b,0x0000017d,0x0004003d, + 0x00000006,0x0000017f,0x00000114,0x0003003e,0x0000017e,0x0000017f,0x00080039,0x0000000c, + 0x00000180,0x00000012,0x00000175,0x00000178,0x0000017b,0x0000017e,0x0004003d,0x0000000c, + 0x00000181,0x00000118,0x00050081,0x0000000c,0x00000182,0x00000181,0x00000180,0x0003003e, + 0x00000118,0x00000182,0x00050041,0x00000007,0x00000183,0x00000078,0x0000005e,0x0004003d, + 0x00000006,0x00000184,0x00000183,0x00050081,0x00000006,0x00000185,0x00000184,0x00000035, + 0x0003003e,0x00000186,0x00000185,0x00050041,0x00000007,0x00000188,0x00000078,0x00000061, + 0x0004003d,0x00000006,0x00000189,0x00000188,0x0003003e,0x00000187,0x00000189,0x00050041, + 0x00000007,0x0000018b,0x0000007d,0x000000c2,0x0004003d,0x00000006,0x0000018c,0x0000018b, + 0x0003003e,0x0000018a,0x0000018c,0x0004003d,0x00000006,0x0000018e,0x00000114,0x0003003e, + 0x0000018d,0x0000018e,0x00080039,0x0000000c,0x0000018f,0x00000012,0x00000186,0x00000187, + 0x0000018a,0x0000018d,0x0004003d,0x0000000c,0x00000190,0x00000118,0x00050081,0x0000000c, + 0x00000191,0x00000190,0x0000018f,0x0003003e,0x00000118,0x00000191,0x00050041,0x00000007, + 0x00000192,0x00000078,0x0000005e,0x0004003d,0x00000006,0x00000193,0x00000192,0x00050083, + 0x00000006,0x00000194,0x00000193,0x00000035,0x00050041,0x00000007,0x00000195,0x00000078, + 0x00000061,0x0004003d,0x00000006,0x00000196,0x00000195,0x00050083,0x00000006,0x00000197, + 0x00000196,0x00000035,0x0003003e,0x00000198,0x00000194,0x0003003e,0x00000199,0x00000197, + 0x00050041,0x00000007,0x0000019b,0x000000a2,0x0000005e,0x0004003d,0x00000006,0x0000019c, + 0x0000019b,0x0003003e,0x0000019a,0x0000019c,0x0004003d,0x00000006,0x0000019e,0x00000114, + 0x0003003e,0x0000019d,0x0000019e,0x00080039,0x0000000c,0x0000019f,0x00000012,0x00000198, + 0x00000199,0x0000019a,0x0000019d,0x0004003d,0x0000000c,0x000001a0,0x00000118,0x00050081, + 0x0000000c,0x000001a1,0x000001a0,0x0000019f,0x0003003e,0x00000118,0x000001a1,0x00050041, + 0x00000007,0x000001a2,0x00000078,0x0000005e,0x0004003d,0x00000006,0x000001a3,0x000001a2, + 0x00050083,0x00000006,0x000001a4,0x000001a3,0x0000013b,0x00050041,0x00000007,0x000001a5, + 0x00000078,0x00000061,0x0004003d,0x00000006,0x000001a6,0x000001a5,0x00050083,0x00000006, + 0x000001a7,0x000001a6,0x00000035,0x0003003e,0x000001a8,0x000001a4,0x0003003e,0x000001a9, + 0x000001a7,0x00050041,0x00000007,0x000001ab,0x000000a2,0x00000061,0x0004003d,0x00000006, + 0x000001ac,0x000001ab,0x0003003e,0x000001aa,0x000001ac,0x0004003d,0x00000006,0x000001ae, + 0x00000114,0x0003003e,0x000001ad,0x000001ae,0x00080039,0x0000000c,0x000001af,0x00000012, + 0x000001a8,0x000001a9,0x000001aa,0x000001ad,0x0004003d,0x0000000c,0x000001b0,0x00000118, + 0x00050081,0x0000000c,0x000001b1,0x000001b0,0x000001af,0x0003003e,0x00000118,0x000001b1, + 0x00050041,0x00000007,0x000001b2,0x00000078,0x0000005e,0x0004003d,0x00000006,0x000001b3, + 0x000001b2,0x00050083,0x00000006,0x000001b4,0x000001b3,0x0000013b,0x0003003e,0x000001b5, + 0x000001b4,0x00050041,0x00000007,0x000001b7,0x00000078,0x00000061,0x0004003d,0x00000006, + 0x000001b8,0x000001b7,0x0003003e,0x000001b6,0x000001b8,0x00050041,0x00000007,0x000001ba, + 0x000000a2,0x00000064,0x0004003d,0x00000006,0x000001bb,0x000001ba,0x0003003e,0x000001b9, + 0x000001bb,0x0004003d,0x00000006,0x000001bd,0x00000114,0x0003003e,0x000001bc,0x000001bd, + 0x00080039,0x0000000c,0x000001be,0x00000012,0x000001b5,0x000001b6,0x000001b9,0x000001bc, + 0x0004003d,0x0000000c,0x000001bf,0x00000118,0x00050081,0x0000000c,0x000001c0,0x000001bf, + 0x000001be,0x0003003e,0x00000118,0x000001c0,0x00050041,0x00000007,0x000001c1,0x00000078, + 0x0000005e,0x0004003d,0x00000006,0x000001c2,0x000001c1,0x00050083,0x00000006,0x000001c3, + 0x000001c2,0x00000035,0x0003003e,0x000001c4,0x000001c3,0x00050041,0x00000007,0x000001c6, + 0x00000078,0x00000061,0x0004003d,0x00000006,0x000001c7,0x000001c6,0x0003003e,0x000001c5, + 0x000001c7,0x00050041,0x00000007,0x000001c9,0x000000a2,0x000000c2,0x0004003d,0x00000006, + 0x000001ca,0x000001c9,0x0003003e,0x000001c8,0x000001ca,0x0004003d,0x00000006,0x000001cc, + 0x00000114,0x0003003e,0x000001cb,0x000001cc,0x00080039,0x0000000c,0x000001cd,0x00000012, + 0x000001c4,0x000001c5,0x000001c8,0x000001cb,0x0004003d,0x0000000c,0x000001ce,0x00000118, + 0x00050081,0x0000000c,0x000001cf,0x000001ce,0x000001cd,0x0003003e,0x00000118,0x000001cf, + 0x00050041,0x00000007,0x000001d1,0x00000118,0x00000061,0x0004003d,0x00000006,0x000001d2, + 0x000001d1,0x00050041,0x00000007,0x000001d3,0x00000118,0x0000005e,0x0004003d,0x00000006, + 0x000001d4,0x000001d3,0x00050088,0x00000006,0x000001d5,0x000001d2,0x000001d4,0x0003003e, + 0x000001d0,0x000001d5,0x00050041,0x00000007,0x000001d7,0x0000007d,0x00000061,0x0004003d, + 0x00000006,0x000001d8,0x000001d7,0x00050041,0x00000007,0x000001d9,0x0000007d,0x00000064, + 0x0004003d,0x00000006,0x000001da,0x000001d9,0x0007000c,0x00000006,0x000001db,0x00000001, + 0x00000028,0x000001d8,0x000001da,0x00050041,0x00000007,0x000001dc,0x000000a2,0x0000005e, + 0x0004003d,0x00000006,0x000001dd,0x000001dc,0x00050041,0x00000007,0x000001de,0x000000a2, + 0x000000c2,0x0004003d,0x00000006,0x000001df,0x000001de,0x0007000c,0x00000006,0x000001e0, + 0x00000001,0x00000028,0x000001dd,0x000001df,0x0007000c,0x00000006,0x000001e1,0x00000001, + 0x00000028,0x000001db,0x000001e0,0x0003003e,0x000001d6,0x000001e1,0x00050041,0x00000007, + 0x000001e3,0x0000007d,0x00000061,0x0004003d,0x00000006,0x000001e4,0x000001e3,0x00050041, + 0x00000007,0x000001e5,0x0000007d,0x00000064,0x0004003d,0x00000006,0x000001e6,0x000001e5, + 0x0007000c,0x00000006,0x000001e7,0x00000001,0x00000025,0x000001e4,0x000001e6,0x00050041, + 0x00000007,0x000001e8,0x000000a2,0x0000005e,0x0004003d,0x00000006,0x000001e9,0x000001e8, + 0x00050041,0x00000007,0x000001ea,0x000000a2,0x000000c2,0x0004003d,0x00000006,0x000001eb, + 0x000001ea,0x0007000c,0x00000006,0x000001ec,0x00000001,0x00000025,0x000001e9,0x000001eb, + 0x0007000c,0x00000006,0x000001ed,0x00000001,0x00000025,0x000001e7,0x000001ec,0x0003003e, + 0x000001e2,0x000001ed,0x00050041,0x000001ef,0x000001f0,0x00000048,0x000001ee,0x0004003d, + 0x00000006,0x000001f1,0x000001f0,0x0004003d,0x00000006,0x000001f2,0x000001d0,0x00050085, + 0x00000006,0x000001f3,0x000001f1,0x000001f2,0x0004003d,0x00000006,0x000001f4,0x000001e2, + 0x0004003d,0x00000006,0x000001f5,0x000001d6,0x0008000c,0x00000006,0x000001f6,0x00000001, + 0x0000002b,0x000001f3,0x000001f4,0x000001f5,0x0003003e,0x000001d0,0x000001f6,0x0004003d, + 0x00000006,0x000001f8,0x000001d0,0x00050041,0x00000007,0x000001f9,0x00000053,0x000000c2, + 0x0004003d,0x00000006,0x000001fa,0x000001f9,0x00050083,0x00000006,0x000001fb,0x000001f8, + 0x000001fa,0x0003003e,0x000001f7,0x000001fb,0x0004003d,0x00000006,0x000001fc,0x000001f7, + 0x0008000c,0x00000006,0x000001ff,0x00000001,0x0000002b,0x000001fc,0x000001fd,0x000001fe, + 0x0003003e,0x000001f7,0x000001ff,0x00050041,0x00000007,0x00000200,0x00000053,0x0000005e, + 0x0004003d,0x00000006,0x00000201,0x00000200,0x0004003d,0x00000006,0x00000202,0x000001f7, + 0x00050081,0x00000006,0x00000203,0x00000201,0x00000202,0x0008000c,0x00000006,0x00000204, + 0x00000001,0x0000002b,0x00000203,0x00000034,0x00000035,0x00050041,0x00000007,0x00000205, + 0x00000053,0x0000005e,0x0003003e,0x00000205,0x00000204,0x00050041,0x00000007,0x00000206, + 0x00000053,0x00000061,0x0004003d,0x00000006,0x00000207,0x00000206,0x0004003d,0x00000006, + 0x00000208,0x000001f7,0x00050081,0x00000006,0x00000209,0x00000207,0x00000208,0x0008000c, + 0x00000006,0x0000020a,0x00000001,0x0000002b,0x00000209,0x00000034,0x00000035,0x00050041, + 0x00000007,0x0000020b,0x00000053,0x00000061,0x0003003e,0x0000020b,0x0000020a,0x00050041, + 0x00000007,0x0000020c,0x00000053,0x00000064,0x0004003d,0x00000006,0x0000020d,0x0000020c, + 0x0004003d,0x00000006,0x0000020e,0x000001f7,0x00050081,0x00000006,0x0000020f,0x0000020d, + 0x0000020e,0x0008000c,0x00000006,0x00000210,0x00000001,0x0000002b,0x0000020f,0x00000034, + 0x00000035,0x00050041,0x00000007,0x00000211,0x00000053,0x00000064,0x0003003e,0x00000211, + 0x00000210,0x000200f9,0x0000009b,0x000200f8,0x0000009b,0x00050041,0x00000007,0x00000212, + 0x00000053,0x000000c2,0x0003003e,0x00000212,0x00000035,0x0004003d,0x00000043,0x00000215, + 0x00000053,0x0003003e,0x00000214,0x00000215,0x000100fd,0x00010038,0x00050036,0x00000006, + 0x0000000a,0x00000000,0x00000008,0x00030037,0x00000007,0x00000009,0x000200f8,0x0000000b, + 0x0004003b,0x00000007,0x00000014,0x00000007,0x0004003b,0x00000007,0x00000018,0x00000007, + 0x0004003d,0x00000006,0x00000015,0x00000009,0x00050083,0x00000006,0x00000017,0x00000015, + 0x00000016,0x0003003e,0x00000014,0x00000017,0x0004003d,0x00000006,0x00000019,0x00000009, + 0x0004003d,0x00000006,0x0000001a,0x00000014,0x00050085,0x00000006,0x0000001b,0x00000019, + 0x0000001a,0x0004003d,0x00000006,0x0000001c,0x00000014,0x00050083,0x00000006,0x0000001d, + 0x0000001b,0x0000001c,0x0003003e,0x00000018,0x0000001d,0x0004003d,0x00000006,0x0000001e, + 0x00000014,0x0004003d,0x00000006,0x0000001f,0x00000014,0x00050085,0x00000006,0x00000020, + 0x0000001f,0x0000001e,0x0003003e,0x00000014,0x00000020,0x0004003d,0x00000006,0x00000021, + 0x00000018,0x0004003d,0x00000006,0x00000022,0x00000014,0x00050085,0x00000006,0x00000023, + 0x00000021,0x00000022,0x000200fe,0x00000023,0x00010038,0x00050036,0x0000000c,0x00000012, + 0x00000000,0x0000000d,0x00030037,0x00000007,0x0000000e,0x00030037,0x00000007,0x0000000f, + 0x00030037,0x00000007,0x00000010,0x00030037,0x00000007,0x00000011,0x000200f8,0x00000013, + 0x0004003b,0x00000007,0x00000026,0x00000007,0x0004003b,0x00000007,0x00000038,0x00000007, + 0x0004003b,0x00000007,0x00000039,0x00000007,0x0004003d,0x00000006,0x00000027,0x0000000e, + 0x0004003d,0x00000006,0x00000028,0x0000000e,0x00050085,0x00000006,0x00000029,0x00000027, + 0x00000028,0x0004003d,0x00000006,0x0000002a,0x0000000f,0x0004003d,0x00000006,0x0000002b, + 0x0000000f,0x00050085,0x00000006,0x0000002c,0x0000002a,0x0000002b,0x00050081,0x00000006, + 0x0000002d,0x00000029,0x0000002c,0x00050085,0x00000006,0x0000002f,0x0000002d,0x0000002e, + 0x0004003d,0x00000006,0x00000030,0x00000010,0x0006000c,0x00000006,0x00000031,0x00000001, + 0x00000004,0x00000030,0x0004003d,0x00000006,0x00000032,0x00000011,0x00050085,0x00000006, + 0x00000033,0x00000031,0x00000032,0x0008000c,0x00000006,0x00000036,0x00000001,0x0000002b, + 0x00000033,0x00000034,0x00000035,0x00050081,0x00000006,0x00000037,0x0000002f,0x00000036, + 0x0003003e,0x00000026,0x00000037,0x0004003d,0x00000006,0x0000003a,0x00000026,0x0003003e, + 0x00000039,0x0000003a,0x00050039,0x00000006,0x0000003b,0x0000000a,0x00000039,0x0003003e, + 0x00000038,0x0000003b,0x0004003d,0x00000006,0x0000003c,0x00000038,0x0004003d,0x00000006, + 0x0000003d,0x00000038,0x0004003d,0x00000006,0x0000003e,0x00000010,0x00050085,0x00000006, + 0x0000003f,0x0000003d,0x0000003e,0x00050050,0x0000000c,0x00000040,0x0000003c,0x0000003f, + 0x000200fe,0x00000040,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/sgsr_quality_frag.h b/app/src/main/cpp/waylandcomp/shaders/sgsr_quality_frag.h new file mode 100644 index 000000000..ca44e6a6a --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/sgsr_quality_frag.h @@ -0,0 +1,460 @@ + // 1116.5.0 + #pragma once +const uint32_t sgsr_quality_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000269,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000009c,0x00000267,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00070005,0x0000000a,0x74736166,0x636e614c,0x32736f7a,0x3b316628,0x00000000, + 0x00030005,0x00000009,0x00000078,0x00080005,0x00000014,0x67696577,0x28597468,0x663b3166, + 0x31663b31,0x3366763b,0x0000003b,0x00030005,0x00000010,0x00007864,0x00030005,0x00000011, + 0x00007964,0x00030005,0x00000012,0x00000063,0x00040005,0x00000013,0x61746164,0x00000000, + 0x00080005,0x0000001b,0x65676465,0x65726944,0x6f697463,0x6676286e,0x66763b34,0x00003b34, + 0x00040005,0x00000019,0x7466656c,0x00000000,0x00040005,0x0000001a,0x68676972,0x00000074, + 0x00030005,0x0000001d,0x00004177,0x00030005,0x00000021,0x00004277,0x00030005,0x0000002f, + 0x00647473,0x00030005,0x00000035,0x00726964,0x00040005,0x00000038,0x65676465,0x00736944, + 0x00030005,0x00000043,0x00000078,0x00030005,0x0000005b,0x00000077,0x00040005,0x0000005c, + 0x61726170,0x0000006d,0x00040005,0x00000066,0x7a4c7852,0x00000000,0x00040005,0x0000006d, + 0x794c7752,0x00000000,0x00040005,0x00000074,0x746c6564,0x00000061,0x00050005,0x0000007d, + 0x676e656c,0x6e496874,0x00000076,0x00060005,0x00000091,0x77656956,0x74726f70,0x6f666e49, + 0x00000000,0x00030005,0x00000092,0x00004350,0x00040006,0x00000092,0x00000000,0x0063646e, + 0x00070006,0x00000092,0x00000001,0x77656956,0x74726f70,0x6f666e49,0x00000000,0x00070006, + 0x00000092,0x00000002,0x65676445,0x72616853,0x73656e70,0x00000073,0x00030005,0x00000094, + 0x00006370,0x00030005,0x0000009a,0x00007675,0x00060005,0x0000009c,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00040005,0x0000009e,0x6f6c6f63,0x00000072,0x00030005,0x000000a2, + 0x00307370,0x00050005,0x000000ad,0x43676d69,0x64726f6f,0x00000000,0x00060005,0x000000b6, + 0x43676d69,0x64726f6f,0x65786950,0x0000006c,0x00040005,0x000000b9,0x726f6f63,0x00000064, + 0x00030005,0x000000be,0x00006c70,0x00040005,0x000000c3,0x7466656c,0x00000000,0x00050005, + 0x000000c7,0x65676465,0x65746f56,0x00000000,0x00040005,0x000000e8,0x68676972,0x00000074, + 0x00040005,0x000000f0,0x6f447075,0x00006e77,0x00040005,0x0000010a,0x6e61656d,0x00000000, + 0x00030005,0x00000129,0x006d7573,0x00040005,0x00000159,0x4d6d7573,0x006e6165,0x00030005, + 0x0000015d,0x00647473,0x00040005,0x00000161,0x61746164,0x00000000,0x00040005,0x00000163, + 0x61726170,0x0000006d,0x00040005,0x00000165,0x61726170,0x0000006d,0x00030005,0x0000016b, + 0x00595761,0x00040005,0x0000016f,0x61726170,0x0000006d,0x00040005,0x00000172,0x61726170, + 0x0000006d,0x00040005,0x00000173,0x61726170,0x0000006d,0x00040005,0x00000176,0x61726170, + 0x0000006d,0x00040005,0x0000017f,0x61726170,0x0000006d,0x00040005,0x00000180,0x61726170, + 0x0000006d,0x00040005,0x00000181,0x61726170,0x0000006d,0x00040005,0x00000184,0x61726170, + 0x0000006d,0x00040005,0x00000190,0x61726170,0x0000006d,0x00040005,0x00000191,0x61726170, + 0x0000006d,0x00040005,0x00000192,0x61726170,0x0000006d,0x00040005,0x00000195,0x61726170, + 0x0000006d,0x00040005,0x0000019d,0x61726170,0x0000006d,0x00040005,0x000001a0,0x61726170, + 0x0000006d,0x00040005,0x000001a1,0x61726170,0x0000006d,0x00040005,0x000001a4,0x61726170, + 0x0000006d,0x00040005,0x000001af,0x61726170,0x0000006d,0x00040005,0x000001b0,0x61726170, + 0x0000006d,0x00040005,0x000001b1,0x61726170,0x0000006d,0x00040005,0x000001b4,0x61726170, + 0x0000006d,0x00040005,0x000001bc,0x61726170,0x0000006d,0x00040005,0x000001bf,0x61726170, + 0x0000006d,0x00040005,0x000001c0,0x61726170,0x0000006d,0x00040005,0x000001c3,0x61726170, + 0x0000006d,0x00040005,0x000001c8,0x61726170,0x0000006d,0x00040005,0x000001cb,0x61726170, + 0x0000006d,0x00040005,0x000001ce,0x61726170,0x0000006d,0x00040005,0x000001d1,0x61726170, + 0x0000006d,0x00040005,0x000001d9,0x61726170,0x0000006d,0x00040005,0x000001da,0x61726170, + 0x0000006d,0x00040005,0x000001dd,0x61726170,0x0000006d,0x00040005,0x000001e0,0x61726170, + 0x0000006d,0x00040005,0x000001eb,0x61726170,0x0000006d,0x00040005,0x000001ec,0x61726170, + 0x0000006d,0x00040005,0x000001ed,0x61726170,0x0000006d,0x00040005,0x000001f0,0x61726170, + 0x0000006d,0x00040005,0x000001fb,0x61726170,0x0000006d,0x00040005,0x000001fc,0x61726170, + 0x0000006d,0x00040005,0x000001fd,0x61726170,0x0000006d,0x00040005,0x00000200,0x61726170, + 0x0000006d,0x00040005,0x00000208,0x61726170,0x0000006d,0x00040005,0x00000209,0x61726170, + 0x0000006d,0x00040005,0x0000020c,0x61726170,0x0000006d,0x00040005,0x0000020f,0x61726170, + 0x0000006d,0x00040005,0x00000217,0x61726170,0x0000006d,0x00040005,0x00000218,0x61726170, + 0x0000006d,0x00040005,0x0000021b,0x61726170,0x0000006d,0x00040005,0x0000021e,0x61726170, + 0x0000006d,0x00040005,0x00000223,0x616e6966,0x0000596c,0x00040005,0x00000229,0x5978616d, + 0x00000000,0x00040005,0x00000235,0x596e696d,0x00000000,0x00040005,0x0000024a,0x746c6564, + 0x00005961,0x00050005,0x00000267,0x5f74756f,0x67726154,0x00307465,0x00030047,0x00000092, + 0x00000002,0x00050048,0x00000092,0x00000000,0x00000023,0x00000000,0x00050048,0x00000092, + 0x00000001,0x00000023,0x00000010,0x00050048,0x00000092,0x00000002,0x00000023,0x00000020, + 0x00040047,0x0000009c,0x0000001e,0x00000000,0x00040047,0x000000a2,0x00000021,0x00000000, + 0x00040047,0x000000a2,0x00000022,0x00000000,0x00040047,0x00000267,0x0000001e,0x00000000, + 0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020, + 0x00040020,0x00000007,0x00000007,0x00000006,0x00040021,0x00000008,0x00000006,0x00000007, + 0x00040017,0x0000000c,0x00000006,0x00000003,0x00040020,0x0000000d,0x00000007,0x0000000c, + 0x00040017,0x0000000e,0x00000006,0x00000002,0x00070021,0x0000000f,0x0000000e,0x00000007, + 0x00000007,0x00000007,0x0000000d,0x00040017,0x00000016,0x00000006,0x00000004,0x00040020, + 0x00000017,0x00000007,0x00000016,0x00050021,0x00000018,0x0000000e,0x00000017,0x00000017, + 0x0004002b,0x00000006,0x0000001f,0x40800000,0x00040015,0x00000030,0x00000020,0x00000000, + 0x0004002b,0x00000030,0x00000031,0x00000000,0x00040020,0x00000034,0x00000007,0x0000000e, + 0x0004002b,0x00000030,0x0000003a,0x00000001,0x0004002b,0x00000006,0x00000053,0x00000000, + 0x0004002b,0x00000006,0x00000054,0x3f800000,0x0004002b,0x00000006,0x00000056,0x3f333333, + 0x0004002b,0x00000030,0x00000069,0x00000002,0x0004002b,0x00000030,0x0000006e,0x00000003, + 0x0004002b,0x00000006,0x00000083,0x38010182,0x0005001e,0x00000092,0x00000016,0x00000016, + 0x00000006,0x00040020,0x00000093,0x00000009,0x00000092,0x0004003b,0x00000093,0x00000094, + 0x00000009,0x00040015,0x00000095,0x00000020,0x00000001,0x0004002b,0x00000095,0x00000096, + 0x00000001,0x00040020,0x00000097,0x00000009,0x00000016,0x00040020,0x0000009b,0x00000001, + 0x0000000e,0x0004003b,0x0000009b,0x0000009c,0x00000001,0x00090019,0x0000009f,0x00000006, + 0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x000000a0, + 0x0000009f,0x00040020,0x000000a1,0x00000000,0x000000a0,0x0004003b,0x000000a1,0x000000a2, + 0x00000000,0x0004002b,0x00000006,0x000000b2,0xbf000000,0x0004002b,0x00000006,0x000000b3, + 0x3f000000,0x0005002c,0x0000000e,0x000000b4,0x000000b2,0x000000b3,0x0004002b,0x00000006, + 0x000000dd,0x3d008081,0x00020014,0x000000de,0x0004002b,0x00000006,0x00000116,0x3e800000, + 0x0004002b,0x00000006,0x0000015a,0x41224505,0x0004002b,0x00000006,0x0000018e,0x40000000, + 0x0004002b,0x00000095,0x00000241,0x00000002,0x00040020,0x00000242,0x00000009,0x00000006, + 0x0004002b,0x00000006,0x00000250,0xbdb8b8b9,0x0004002b,0x00000006,0x00000251,0x3db8b8b9, + 0x00040020,0x00000266,0x00000003,0x00000016,0x0004003b,0x00000266,0x00000267,0x00000003, + 0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b, + 0x00000017,0x00000091,0x00000007,0x0004003b,0x00000034,0x0000009a,0x00000007,0x0004003b, + 0x00000017,0x0000009e,0x00000007,0x0004003b,0x00000034,0x000000ad,0x00000007,0x0004003b, + 0x00000034,0x000000b6,0x00000007,0x0004003b,0x00000034,0x000000b9,0x00000007,0x0004003b, + 0x00000034,0x000000be,0x00000007,0x0004003b,0x00000017,0x000000c3,0x00000007,0x0004003b, + 0x00000007,0x000000c7,0x00000007,0x0004003b,0x00000017,0x000000e8,0x00000007,0x0004003b, + 0x00000017,0x000000f0,0x00000007,0x0004003b,0x00000007,0x0000010a,0x00000007,0x0004003b, + 0x00000007,0x00000129,0x00000007,0x0004003b,0x00000007,0x00000159,0x00000007,0x0004003b, + 0x00000007,0x0000015d,0x00000007,0x0004003b,0x0000000d,0x00000161,0x00000007,0x0004003b, + 0x00000017,0x00000163,0x00000007,0x0004003b,0x00000017,0x00000165,0x00000007,0x0004003b, + 0x00000034,0x0000016b,0x00000007,0x0004003b,0x00000007,0x0000016f,0x00000007,0x0004003b, + 0x00000007,0x00000172,0x00000007,0x0004003b,0x00000007,0x00000173,0x00000007,0x0004003b, + 0x0000000d,0x00000176,0x00000007,0x0004003b,0x00000007,0x0000017f,0x00000007,0x0004003b, + 0x00000007,0x00000180,0x00000007,0x0004003b,0x00000007,0x00000181,0x00000007,0x0004003b, + 0x0000000d,0x00000184,0x00000007,0x0004003b,0x00000007,0x00000190,0x00000007,0x0004003b, + 0x00000007,0x00000191,0x00000007,0x0004003b,0x00000007,0x00000192,0x00000007,0x0004003b, + 0x0000000d,0x00000195,0x00000007,0x0004003b,0x00000007,0x0000019d,0x00000007,0x0004003b, + 0x00000007,0x000001a0,0x00000007,0x0004003b,0x00000007,0x000001a1,0x00000007,0x0004003b, + 0x0000000d,0x000001a4,0x00000007,0x0004003b,0x00000007,0x000001af,0x00000007,0x0004003b, + 0x00000007,0x000001b0,0x00000007,0x0004003b,0x00000007,0x000001b1,0x00000007,0x0004003b, + 0x0000000d,0x000001b4,0x00000007,0x0004003b,0x00000007,0x000001bc,0x00000007,0x0004003b, + 0x00000007,0x000001bf,0x00000007,0x0004003b,0x00000007,0x000001c0,0x00000007,0x0004003b, + 0x0000000d,0x000001c3,0x00000007,0x0004003b,0x00000007,0x000001c8,0x00000007,0x0004003b, + 0x00000007,0x000001cb,0x00000007,0x0004003b,0x00000007,0x000001ce,0x00000007,0x0004003b, + 0x0000000d,0x000001d1,0x00000007,0x0004003b,0x00000007,0x000001d9,0x00000007,0x0004003b, + 0x00000007,0x000001da,0x00000007,0x0004003b,0x00000007,0x000001dd,0x00000007,0x0004003b, + 0x0000000d,0x000001e0,0x00000007,0x0004003b,0x00000007,0x000001eb,0x00000007,0x0004003b, + 0x00000007,0x000001ec,0x00000007,0x0004003b,0x00000007,0x000001ed,0x00000007,0x0004003b, + 0x0000000d,0x000001f0,0x00000007,0x0004003b,0x00000007,0x000001fb,0x00000007,0x0004003b, + 0x00000007,0x000001fc,0x00000007,0x0004003b,0x00000007,0x000001fd,0x00000007,0x0004003b, + 0x0000000d,0x00000200,0x00000007,0x0004003b,0x00000007,0x00000208,0x00000007,0x0004003b, + 0x00000007,0x00000209,0x00000007,0x0004003b,0x00000007,0x0000020c,0x00000007,0x0004003b, + 0x0000000d,0x0000020f,0x00000007,0x0004003b,0x00000007,0x00000217,0x00000007,0x0004003b, + 0x00000007,0x00000218,0x00000007,0x0004003b,0x00000007,0x0000021b,0x00000007,0x0004003b, + 0x0000000d,0x0000021e,0x00000007,0x0004003b,0x00000007,0x00000223,0x00000007,0x0004003b, + 0x00000007,0x00000229,0x00000007,0x0004003b,0x00000007,0x00000235,0x00000007,0x0004003b, + 0x00000007,0x0000024a,0x00000007,0x00050041,0x00000097,0x00000098,0x00000094,0x00000096, + 0x0004003d,0x00000016,0x00000099,0x00000098,0x0003003e,0x00000091,0x00000099,0x0004003d, + 0x0000000e,0x0000009d,0x0000009c,0x0003003e,0x0000009a,0x0000009d,0x0004003d,0x000000a0, + 0x000000a3,0x000000a2,0x0004003d,0x0000000e,0x000000a4,0x0000009a,0x00070058,0x00000016, + 0x000000a5,0x000000a3,0x000000a4,0x00000002,0x00000053,0x0008004f,0x0000000c,0x000000a6, + 0x000000a5,0x000000a5,0x00000000,0x00000001,0x00000002,0x00050041,0x00000007,0x000000a7, + 0x0000009e,0x00000031,0x00050051,0x00000006,0x000000a8,0x000000a6,0x00000000,0x0003003e, + 0x000000a7,0x000000a8,0x00050041,0x00000007,0x000000a9,0x0000009e,0x0000003a,0x00050051, + 0x00000006,0x000000aa,0x000000a6,0x00000001,0x0003003e,0x000000a9,0x000000aa,0x00050041, + 0x00000007,0x000000ab,0x0000009e,0x00000069,0x00050051,0x00000006,0x000000ac,0x000000a6, + 0x00000002,0x0003003e,0x000000ab,0x000000ac,0x0004003d,0x0000000e,0x000000ae,0x0000009a, + 0x0004003d,0x00000016,0x000000af,0x00000091,0x0007004f,0x0000000e,0x000000b0,0x000000af, + 0x000000af,0x00000002,0x00000003,0x00050085,0x0000000e,0x000000b1,0x000000ae,0x000000b0, + 0x00050081,0x0000000e,0x000000b5,0x000000b1,0x000000b4,0x0003003e,0x000000ad,0x000000b5, + 0x0004003d,0x0000000e,0x000000b7,0x000000ad,0x0006000c,0x0000000e,0x000000b8,0x00000001, + 0x00000008,0x000000b7,0x0003003e,0x000000b6,0x000000b8,0x0004003d,0x0000000e,0x000000ba, + 0x000000b6,0x0004003d,0x00000016,0x000000bb,0x00000091,0x0007004f,0x0000000e,0x000000bc, + 0x000000bb,0x000000bb,0x00000000,0x00000001,0x00050085,0x0000000e,0x000000bd,0x000000ba, + 0x000000bc,0x0003003e,0x000000b9,0x000000bd,0x0004003d,0x0000000e,0x000000bf,0x000000ad, + 0x0004003d,0x0000000e,0x000000c0,0x000000b6,0x0004007f,0x0000000e,0x000000c1,0x000000c0, + 0x00050081,0x0000000e,0x000000c2,0x000000bf,0x000000c1,0x0003003e,0x000000be,0x000000c2, + 0x0004003d,0x000000a0,0x000000c4,0x000000a2,0x0004003d,0x0000000e,0x000000c5,0x000000b9, + 0x00060060,0x00000016,0x000000c6,0x000000c4,0x000000c5,0x00000096,0x0003003e,0x000000c3, + 0x000000c6,0x00050041,0x00000007,0x000000c8,0x000000c3,0x00000069,0x0004003d,0x00000006, + 0x000000c9,0x000000c8,0x00050041,0x00000007,0x000000ca,0x000000c3,0x0000003a,0x0004003d, + 0x00000006,0x000000cb,0x000000ca,0x00050083,0x00000006,0x000000cc,0x000000c9,0x000000cb, + 0x0006000c,0x00000006,0x000000cd,0x00000001,0x00000004,0x000000cc,0x00050041,0x00000007, + 0x000000ce,0x0000009e,0x0000003a,0x0004003d,0x00000006,0x000000cf,0x000000ce,0x00050041, + 0x00000007,0x000000d0,0x000000c3,0x0000003a,0x0004003d,0x00000006,0x000000d1,0x000000d0, + 0x00050083,0x00000006,0x000000d2,0x000000cf,0x000000d1,0x0006000c,0x00000006,0x000000d3, + 0x00000001,0x00000004,0x000000d2,0x00050081,0x00000006,0x000000d4,0x000000cd,0x000000d3, + 0x00050041,0x00000007,0x000000d5,0x0000009e,0x0000003a,0x0004003d,0x00000006,0x000000d6, + 0x000000d5,0x00050041,0x00000007,0x000000d7,0x000000c3,0x00000069,0x0004003d,0x00000006, + 0x000000d8,0x000000d7,0x00050083,0x00000006,0x000000d9,0x000000d6,0x000000d8,0x0006000c, + 0x00000006,0x000000da,0x00000001,0x00000004,0x000000d9,0x00050081,0x00000006,0x000000db, + 0x000000d4,0x000000da,0x0003003e,0x000000c7,0x000000db,0x0004003d,0x00000006,0x000000dc, + 0x000000c7,0x000500ba,0x000000de,0x000000df,0x000000dc,0x000000dd,0x000300f7,0x000000e1, + 0x00000000,0x000400fa,0x000000df,0x000000e0,0x000000e1,0x000200f8,0x000000e0,0x00050041, + 0x00000007,0x000000e2,0x00000091,0x00000031,0x0004003d,0x00000006,0x000000e3,0x000000e2, + 0x00050041,0x00000007,0x000000e4,0x000000b9,0x00000031,0x0004003d,0x00000006,0x000000e5, + 0x000000e4,0x00050081,0x00000006,0x000000e6,0x000000e5,0x000000e3,0x00050041,0x00000007, + 0x000000e7,0x000000b9,0x00000031,0x0003003e,0x000000e7,0x000000e6,0x0004003d,0x000000a0, + 0x000000e9,0x000000a2,0x0004003d,0x0000000e,0x000000ea,0x000000b9,0x00050041,0x00000007, + 0x000000eb,0x00000091,0x00000031,0x0004003d,0x00000006,0x000000ec,0x000000eb,0x00050050, + 0x0000000e,0x000000ed,0x000000ec,0x00000053,0x00050081,0x0000000e,0x000000ee,0x000000ea, + 0x000000ed,0x00060060,0x00000016,0x000000ef,0x000000e9,0x000000ee,0x00000096,0x0003003e, + 0x000000e8,0x000000ef,0x0004003d,0x000000a0,0x000000f1,0x000000a2,0x0004003d,0x0000000e, + 0x000000f2,0x000000b9,0x00050041,0x00000007,0x000000f3,0x00000091,0x0000003a,0x0004003d, + 0x00000006,0x000000f4,0x000000f3,0x0004007f,0x00000006,0x000000f5,0x000000f4,0x00050050, + 0x0000000e,0x000000f6,0x00000053,0x000000f5,0x00050081,0x0000000e,0x000000f7,0x000000f2, + 0x000000f6,0x00060060,0x00000016,0x000000f8,0x000000f1,0x000000f7,0x00000096,0x0007004f, + 0x0000000e,0x000000f9,0x000000f8,0x000000f8,0x00000003,0x00000002,0x00050041,0x00000007, + 0x000000fa,0x000000f0,0x00000031,0x00050051,0x00000006,0x000000fb,0x000000f9,0x00000000, + 0x0003003e,0x000000fa,0x000000fb,0x00050041,0x00000007,0x000000fc,0x000000f0,0x0000003a, + 0x00050051,0x00000006,0x000000fd,0x000000f9,0x00000001,0x0003003e,0x000000fc,0x000000fd, + 0x0004003d,0x000000a0,0x000000fe,0x000000a2,0x0004003d,0x0000000e,0x000000ff,0x000000b9, + 0x00050041,0x00000007,0x00000100,0x00000091,0x0000003a,0x0004003d,0x00000006,0x00000101, + 0x00000100,0x00050050,0x0000000e,0x00000102,0x00000053,0x00000101,0x00050081,0x0000000e, + 0x00000103,0x000000ff,0x00000102,0x00060060,0x00000016,0x00000104,0x000000fe,0x00000103, + 0x00000096,0x0007004f,0x0000000e,0x00000105,0x00000104,0x00000104,0x00000001,0x00000000, + 0x00050041,0x00000007,0x00000106,0x000000f0,0x00000069,0x00050051,0x00000006,0x00000107, + 0x00000105,0x00000000,0x0003003e,0x00000106,0x00000107,0x00050041,0x00000007,0x00000108, + 0x000000f0,0x0000006e,0x00050051,0x00000006,0x00000109,0x00000105,0x00000001,0x0003003e, + 0x00000108,0x00000109,0x00050041,0x00000007,0x0000010b,0x000000c3,0x0000003a,0x0004003d, + 0x00000006,0x0000010c,0x0000010b,0x00050041,0x00000007,0x0000010d,0x000000c3,0x00000069, + 0x0004003d,0x00000006,0x0000010e,0x0000010d,0x00050081,0x00000006,0x0000010f,0x0000010c, + 0x0000010e,0x00050041,0x00000007,0x00000110,0x000000e8,0x00000031,0x0004003d,0x00000006, + 0x00000111,0x00000110,0x00050081,0x00000006,0x00000112,0x0000010f,0x00000111,0x00050041, + 0x00000007,0x00000113,0x000000e8,0x0000006e,0x0004003d,0x00000006,0x00000114,0x00000113, + 0x00050081,0x00000006,0x00000115,0x00000112,0x00000114,0x00050085,0x00000006,0x00000117, + 0x00000115,0x00000116,0x0003003e,0x0000010a,0x00000117,0x0004003d,0x00000016,0x00000118, + 0x000000c3,0x0004003d,0x00000006,0x00000119,0x0000010a,0x00070050,0x00000016,0x0000011a, + 0x00000119,0x00000119,0x00000119,0x00000119,0x00050083,0x00000016,0x0000011b,0x00000118, + 0x0000011a,0x0003003e,0x000000c3,0x0000011b,0x0004003d,0x00000016,0x0000011c,0x000000e8, + 0x0004003d,0x00000006,0x0000011d,0x0000010a,0x00070050,0x00000016,0x0000011e,0x0000011d, + 0x0000011d,0x0000011d,0x0000011d,0x00050083,0x00000016,0x0000011f,0x0000011c,0x0000011e, + 0x0003003e,0x000000e8,0x0000011f,0x0004003d,0x00000016,0x00000120,0x000000f0,0x0004003d, + 0x00000006,0x00000121,0x0000010a,0x00070050,0x00000016,0x00000122,0x00000121,0x00000121, + 0x00000121,0x00000121,0x00050083,0x00000016,0x00000123,0x00000120,0x00000122,0x0003003e, + 0x000000f0,0x00000123,0x00050041,0x00000007,0x00000124,0x0000009e,0x0000003a,0x0004003d, + 0x00000006,0x00000125,0x00000124,0x0004003d,0x00000006,0x00000126,0x0000010a,0x00050083, + 0x00000006,0x00000127,0x00000125,0x00000126,0x00050041,0x00000007,0x00000128,0x0000009e, + 0x0000006e,0x0003003e,0x00000128,0x00000127,0x00050041,0x00000007,0x0000012a,0x000000c3, + 0x00000031,0x0004003d,0x00000006,0x0000012b,0x0000012a,0x0006000c,0x00000006,0x0000012c, + 0x00000001,0x00000004,0x0000012b,0x00050041,0x00000007,0x0000012d,0x000000c3,0x0000003a, + 0x0004003d,0x00000006,0x0000012e,0x0000012d,0x0006000c,0x00000006,0x0000012f,0x00000001, + 0x00000004,0x0000012e,0x00050081,0x00000006,0x00000130,0x0000012c,0x0000012f,0x00050041, + 0x00000007,0x00000131,0x000000c3,0x00000069,0x0004003d,0x00000006,0x00000132,0x00000131, + 0x0006000c,0x00000006,0x00000133,0x00000001,0x00000004,0x00000132,0x00050081,0x00000006, + 0x00000134,0x00000130,0x00000133,0x00050041,0x00000007,0x00000135,0x000000c3,0x0000006e, + 0x0004003d,0x00000006,0x00000136,0x00000135,0x0006000c,0x00000006,0x00000137,0x00000001, + 0x00000004,0x00000136,0x00050081,0x00000006,0x00000138,0x00000134,0x00000137,0x00050041, + 0x00000007,0x00000139,0x000000e8,0x00000031,0x0004003d,0x00000006,0x0000013a,0x00000139, + 0x0006000c,0x00000006,0x0000013b,0x00000001,0x00000004,0x0000013a,0x00050041,0x00000007, + 0x0000013c,0x000000e8,0x0000003a,0x0004003d,0x00000006,0x0000013d,0x0000013c,0x0006000c, + 0x00000006,0x0000013e,0x00000001,0x00000004,0x0000013d,0x00050081,0x00000006,0x0000013f, + 0x0000013b,0x0000013e,0x00050041,0x00000007,0x00000140,0x000000e8,0x00000069,0x0004003d, + 0x00000006,0x00000141,0x00000140,0x0006000c,0x00000006,0x00000142,0x00000001,0x00000004, + 0x00000141,0x00050081,0x00000006,0x00000143,0x0000013f,0x00000142,0x00050041,0x00000007, + 0x00000144,0x000000e8,0x0000006e,0x0004003d,0x00000006,0x00000145,0x00000144,0x0006000c, + 0x00000006,0x00000146,0x00000001,0x00000004,0x00000145,0x00050081,0x00000006,0x00000147, + 0x00000143,0x00000146,0x00050081,0x00000006,0x00000148,0x00000138,0x00000147,0x00050041, + 0x00000007,0x00000149,0x000000f0,0x00000031,0x0004003d,0x00000006,0x0000014a,0x00000149, + 0x0006000c,0x00000006,0x0000014b,0x00000001,0x00000004,0x0000014a,0x00050041,0x00000007, + 0x0000014c,0x000000f0,0x0000003a,0x0004003d,0x00000006,0x0000014d,0x0000014c,0x0006000c, + 0x00000006,0x0000014e,0x00000001,0x00000004,0x0000014d,0x00050081,0x00000006,0x0000014f, + 0x0000014b,0x0000014e,0x00050041,0x00000007,0x00000150,0x000000f0,0x00000069,0x0004003d, + 0x00000006,0x00000151,0x00000150,0x0006000c,0x00000006,0x00000152,0x00000001,0x00000004, + 0x00000151,0x00050081,0x00000006,0x00000153,0x0000014f,0x00000152,0x00050041,0x00000007, + 0x00000154,0x000000f0,0x0000006e,0x0004003d,0x00000006,0x00000155,0x00000154,0x0006000c, + 0x00000006,0x00000156,0x00000001,0x00000004,0x00000155,0x00050081,0x00000006,0x00000157, + 0x00000153,0x00000156,0x00050081,0x00000006,0x00000158,0x00000148,0x00000157,0x0003003e, + 0x00000129,0x00000158,0x0004003d,0x00000006,0x0000015b,0x00000129,0x00050088,0x00000006, + 0x0000015c,0x0000015a,0x0000015b,0x0003003e,0x00000159,0x0000015c,0x0004003d,0x00000006, + 0x0000015e,0x00000159,0x0004003d,0x00000006,0x0000015f,0x00000159,0x00050085,0x00000006, + 0x00000160,0x0000015e,0x0000015f,0x0003003e,0x0000015d,0x00000160,0x0004003d,0x00000006, + 0x00000162,0x0000015d,0x0004003d,0x00000016,0x00000164,0x000000c3,0x0003003e,0x00000163, + 0x00000164,0x0004003d,0x00000016,0x00000166,0x000000e8,0x0003003e,0x00000165,0x00000166, + 0x00060039,0x0000000e,0x00000167,0x0000001b,0x00000163,0x00000165,0x00050051,0x00000006, + 0x00000168,0x00000167,0x00000000,0x00050051,0x00000006,0x00000169,0x00000167,0x00000001, + 0x00060050,0x0000000c,0x0000016a,0x00000162,0x00000168,0x00000169,0x0003003e,0x00000161, + 0x0000016a,0x00050041,0x00000007,0x0000016c,0x000000be,0x0000003a,0x0004003d,0x00000006, + 0x0000016d,0x0000016c,0x00050081,0x00000006,0x0000016e,0x0000016d,0x00000054,0x00050041, + 0x00000007,0x00000170,0x000000be,0x00000031,0x0004003d,0x00000006,0x00000171,0x00000170, + 0x0003003e,0x0000016f,0x00000171,0x0003003e,0x00000172,0x0000016e,0x00050041,0x00000007, + 0x00000174,0x000000f0,0x00000031,0x0004003d,0x00000006,0x00000175,0x00000174,0x0003003e, + 0x00000173,0x00000175,0x0004003d,0x0000000c,0x00000177,0x00000161,0x0003003e,0x00000176, + 0x00000177,0x00080039,0x0000000e,0x00000178,0x00000014,0x0000016f,0x00000172,0x00000173, + 0x00000176,0x0003003e,0x0000016b,0x00000178,0x00050041,0x00000007,0x00000179,0x000000be, + 0x00000031,0x0004003d,0x00000006,0x0000017a,0x00000179,0x00050083,0x00000006,0x0000017b, + 0x0000017a,0x00000054,0x00050041,0x00000007,0x0000017c,0x000000be,0x0000003a,0x0004003d, + 0x00000006,0x0000017d,0x0000017c,0x00050081,0x00000006,0x0000017e,0x0000017d,0x00000054, + 0x0003003e,0x0000017f,0x0000017b,0x0003003e,0x00000180,0x0000017e,0x00050041,0x00000007, + 0x00000182,0x000000f0,0x0000003a,0x0004003d,0x00000006,0x00000183,0x00000182,0x0003003e, + 0x00000181,0x00000183,0x0004003d,0x0000000c,0x00000185,0x00000161,0x0003003e,0x00000184, + 0x00000185,0x00080039,0x0000000e,0x00000186,0x00000014,0x0000017f,0x00000180,0x00000181, + 0x00000184,0x0004003d,0x0000000e,0x00000187,0x0000016b,0x00050081,0x0000000e,0x00000188, + 0x00000187,0x00000186,0x0003003e,0x0000016b,0x00000188,0x00050041,0x00000007,0x00000189, + 0x000000be,0x00000031,0x0004003d,0x00000006,0x0000018a,0x00000189,0x00050083,0x00000006, + 0x0000018b,0x0000018a,0x00000054,0x00050041,0x00000007,0x0000018c,0x000000be,0x0000003a, + 0x0004003d,0x00000006,0x0000018d,0x0000018c,0x00050083,0x00000006,0x0000018f,0x0000018d, + 0x0000018e,0x0003003e,0x00000190,0x0000018b,0x0003003e,0x00000191,0x0000018f,0x00050041, + 0x00000007,0x00000193,0x000000f0,0x00000069,0x0004003d,0x00000006,0x00000194,0x00000193, + 0x0003003e,0x00000192,0x00000194,0x0004003d,0x0000000c,0x00000196,0x00000161,0x0003003e, + 0x00000195,0x00000196,0x00080039,0x0000000e,0x00000197,0x00000014,0x00000190,0x00000191, + 0x00000192,0x00000195,0x0004003d,0x0000000e,0x00000198,0x0000016b,0x00050081,0x0000000e, + 0x00000199,0x00000198,0x00000197,0x0003003e,0x0000016b,0x00000199,0x00050041,0x00000007, + 0x0000019a,0x000000be,0x0000003a,0x0004003d,0x00000006,0x0000019b,0x0000019a,0x00050083, + 0x00000006,0x0000019c,0x0000019b,0x0000018e,0x00050041,0x00000007,0x0000019e,0x000000be, + 0x00000031,0x0004003d,0x00000006,0x0000019f,0x0000019e,0x0003003e,0x0000019d,0x0000019f, + 0x0003003e,0x000001a0,0x0000019c,0x00050041,0x00000007,0x000001a2,0x000000f0,0x0000006e, + 0x0004003d,0x00000006,0x000001a3,0x000001a2,0x0003003e,0x000001a1,0x000001a3,0x0004003d, + 0x0000000c,0x000001a5,0x00000161,0x0003003e,0x000001a4,0x000001a5,0x00080039,0x0000000e, + 0x000001a6,0x00000014,0x0000019d,0x000001a0,0x000001a1,0x000001a4,0x0004003d,0x0000000e, + 0x000001a7,0x0000016b,0x00050081,0x0000000e,0x000001a8,0x000001a7,0x000001a6,0x0003003e, + 0x0000016b,0x000001a8,0x00050041,0x00000007,0x000001a9,0x000000be,0x00000031,0x0004003d, + 0x00000006,0x000001aa,0x000001a9,0x00050081,0x00000006,0x000001ab,0x000001aa,0x00000054, + 0x00050041,0x00000007,0x000001ac,0x000000be,0x0000003a,0x0004003d,0x00000006,0x000001ad, + 0x000001ac,0x00050083,0x00000006,0x000001ae,0x000001ad,0x00000054,0x0003003e,0x000001af, + 0x000001ab,0x0003003e,0x000001b0,0x000001ae,0x00050041,0x00000007,0x000001b2,0x000000c3, + 0x00000031,0x0004003d,0x00000006,0x000001b3,0x000001b2,0x0003003e,0x000001b1,0x000001b3, + 0x0004003d,0x0000000c,0x000001b5,0x00000161,0x0003003e,0x000001b4,0x000001b5,0x00080039, + 0x0000000e,0x000001b6,0x00000014,0x000001af,0x000001b0,0x000001b1,0x000001b4,0x0004003d, + 0x0000000e,0x000001b7,0x0000016b,0x00050081,0x0000000e,0x000001b8,0x000001b7,0x000001b6, + 0x0003003e,0x0000016b,0x000001b8,0x00050041,0x00000007,0x000001b9,0x000000be,0x0000003a, + 0x0004003d,0x00000006,0x000001ba,0x000001b9,0x00050083,0x00000006,0x000001bb,0x000001ba, + 0x00000054,0x00050041,0x00000007,0x000001bd,0x000000be,0x00000031,0x0004003d,0x00000006, + 0x000001be,0x000001bd,0x0003003e,0x000001bc,0x000001be,0x0003003e,0x000001bf,0x000001bb, + 0x00050041,0x00000007,0x000001c1,0x000000c3,0x0000003a,0x0004003d,0x00000006,0x000001c2, + 0x000001c1,0x0003003e,0x000001c0,0x000001c2,0x0004003d,0x0000000c,0x000001c4,0x00000161, + 0x0003003e,0x000001c3,0x000001c4,0x00080039,0x0000000e,0x000001c5,0x00000014,0x000001bc, + 0x000001bf,0x000001c0,0x000001c3,0x0004003d,0x0000000e,0x000001c6,0x0000016b,0x00050081, + 0x0000000e,0x000001c7,0x000001c6,0x000001c5,0x0003003e,0x0000016b,0x000001c7,0x00050041, + 0x00000007,0x000001c9,0x000000be,0x00000031,0x0004003d,0x00000006,0x000001ca,0x000001c9, + 0x0003003e,0x000001c8,0x000001ca,0x00050041,0x00000007,0x000001cc,0x000000be,0x0000003a, + 0x0004003d,0x00000006,0x000001cd,0x000001cc,0x0003003e,0x000001cb,0x000001cd,0x00050041, + 0x00000007,0x000001cf,0x000000c3,0x00000069,0x0004003d,0x00000006,0x000001d0,0x000001cf, + 0x0003003e,0x000001ce,0x000001d0,0x0004003d,0x0000000c,0x000001d2,0x00000161,0x0003003e, + 0x000001d1,0x000001d2,0x00080039,0x0000000e,0x000001d3,0x00000014,0x000001c8,0x000001cb, + 0x000001ce,0x000001d1,0x0004003d,0x0000000e,0x000001d4,0x0000016b,0x00050081,0x0000000e, + 0x000001d5,0x000001d4,0x000001d3,0x0003003e,0x0000016b,0x000001d5,0x00050041,0x00000007, + 0x000001d6,0x000000be,0x00000031,0x0004003d,0x00000006,0x000001d7,0x000001d6,0x00050081, + 0x00000006,0x000001d8,0x000001d7,0x00000054,0x0003003e,0x000001d9,0x000001d8,0x00050041, + 0x00000007,0x000001db,0x000000be,0x0000003a,0x0004003d,0x00000006,0x000001dc,0x000001db, + 0x0003003e,0x000001da,0x000001dc,0x00050041,0x00000007,0x000001de,0x000000c3,0x0000006e, + 0x0004003d,0x00000006,0x000001df,0x000001de,0x0003003e,0x000001dd,0x000001df,0x0004003d, + 0x0000000c,0x000001e1,0x00000161,0x0003003e,0x000001e0,0x000001e1,0x00080039,0x0000000e, + 0x000001e2,0x00000014,0x000001d9,0x000001da,0x000001dd,0x000001e0,0x0004003d,0x0000000e, + 0x000001e3,0x0000016b,0x00050081,0x0000000e,0x000001e4,0x000001e3,0x000001e2,0x0003003e, + 0x0000016b,0x000001e4,0x00050041,0x00000007,0x000001e5,0x000000be,0x00000031,0x0004003d, + 0x00000006,0x000001e6,0x000001e5,0x00050083,0x00000006,0x000001e7,0x000001e6,0x00000054, + 0x00050041,0x00000007,0x000001e8,0x000000be,0x0000003a,0x0004003d,0x00000006,0x000001e9, + 0x000001e8,0x00050083,0x00000006,0x000001ea,0x000001e9,0x00000054,0x0003003e,0x000001eb, + 0x000001e7,0x0003003e,0x000001ec,0x000001ea,0x00050041,0x00000007,0x000001ee,0x000000e8, + 0x00000031,0x0004003d,0x00000006,0x000001ef,0x000001ee,0x0003003e,0x000001ed,0x000001ef, + 0x0004003d,0x0000000c,0x000001f1,0x00000161,0x0003003e,0x000001f0,0x000001f1,0x00080039, + 0x0000000e,0x000001f2,0x00000014,0x000001eb,0x000001ec,0x000001ed,0x000001f0,0x0004003d, + 0x0000000e,0x000001f3,0x0000016b,0x00050081,0x0000000e,0x000001f4,0x000001f3,0x000001f2, + 0x0003003e,0x0000016b,0x000001f4,0x00050041,0x00000007,0x000001f5,0x000000be,0x00000031, + 0x0004003d,0x00000006,0x000001f6,0x000001f5,0x00050083,0x00000006,0x000001f7,0x000001f6, + 0x0000018e,0x00050041,0x00000007,0x000001f8,0x000000be,0x0000003a,0x0004003d,0x00000006, + 0x000001f9,0x000001f8,0x00050083,0x00000006,0x000001fa,0x000001f9,0x00000054,0x0003003e, + 0x000001fb,0x000001f7,0x0003003e,0x000001fc,0x000001fa,0x00050041,0x00000007,0x000001fe, + 0x000000e8,0x0000003a,0x0004003d,0x00000006,0x000001ff,0x000001fe,0x0003003e,0x000001fd, + 0x000001ff,0x0004003d,0x0000000c,0x00000201,0x00000161,0x0003003e,0x00000200,0x00000201, + 0x00080039,0x0000000e,0x00000202,0x00000014,0x000001fb,0x000001fc,0x000001fd,0x00000200, + 0x0004003d,0x0000000e,0x00000203,0x0000016b,0x00050081,0x0000000e,0x00000204,0x00000203, + 0x00000202,0x0003003e,0x0000016b,0x00000204,0x00050041,0x00000007,0x00000205,0x000000be, + 0x00000031,0x0004003d,0x00000006,0x00000206,0x00000205,0x00050083,0x00000006,0x00000207, + 0x00000206,0x0000018e,0x0003003e,0x00000208,0x00000207,0x00050041,0x00000007,0x0000020a, + 0x000000be,0x0000003a,0x0004003d,0x00000006,0x0000020b,0x0000020a,0x0003003e,0x00000209, + 0x0000020b,0x00050041,0x00000007,0x0000020d,0x000000e8,0x00000069,0x0004003d,0x00000006, + 0x0000020e,0x0000020d,0x0003003e,0x0000020c,0x0000020e,0x0004003d,0x0000000c,0x00000210, + 0x00000161,0x0003003e,0x0000020f,0x00000210,0x00080039,0x0000000e,0x00000211,0x00000014, + 0x00000208,0x00000209,0x0000020c,0x0000020f,0x0004003d,0x0000000e,0x00000212,0x0000016b, + 0x00050081,0x0000000e,0x00000213,0x00000212,0x00000211,0x0003003e,0x0000016b,0x00000213, + 0x00050041,0x00000007,0x00000214,0x000000be,0x00000031,0x0004003d,0x00000006,0x00000215, + 0x00000214,0x00050083,0x00000006,0x00000216,0x00000215,0x00000054,0x0003003e,0x00000217, + 0x00000216,0x00050041,0x00000007,0x00000219,0x000000be,0x0000003a,0x0004003d,0x00000006, + 0x0000021a,0x00000219,0x0003003e,0x00000218,0x0000021a,0x00050041,0x00000007,0x0000021c, + 0x000000e8,0x0000006e,0x0004003d,0x00000006,0x0000021d,0x0000021c,0x0003003e,0x0000021b, + 0x0000021d,0x0004003d,0x0000000c,0x0000021f,0x00000161,0x0003003e,0x0000021e,0x0000021f, + 0x00080039,0x0000000e,0x00000220,0x00000014,0x00000217,0x00000218,0x0000021b,0x0000021e, + 0x0004003d,0x0000000e,0x00000221,0x0000016b,0x00050081,0x0000000e,0x00000222,0x00000221, + 0x00000220,0x0003003e,0x0000016b,0x00000222,0x00050041,0x00000007,0x00000224,0x0000016b, + 0x0000003a,0x0004003d,0x00000006,0x00000225,0x00000224,0x00050041,0x00000007,0x00000226, + 0x0000016b,0x00000031,0x0004003d,0x00000006,0x00000227,0x00000226,0x00050088,0x00000006, + 0x00000228,0x00000225,0x00000227,0x0003003e,0x00000223,0x00000228,0x00050041,0x00000007, + 0x0000022a,0x000000c3,0x0000003a,0x0004003d,0x00000006,0x0000022b,0x0000022a,0x00050041, + 0x00000007,0x0000022c,0x000000c3,0x00000069,0x0004003d,0x00000006,0x0000022d,0x0000022c, + 0x0007000c,0x00000006,0x0000022e,0x00000001,0x00000028,0x0000022b,0x0000022d,0x00050041, + 0x00000007,0x0000022f,0x000000e8,0x00000031,0x0004003d,0x00000006,0x00000230,0x0000022f, + 0x00050041,0x00000007,0x00000231,0x000000e8,0x0000006e,0x0004003d,0x00000006,0x00000232, + 0x00000231,0x0007000c,0x00000006,0x00000233,0x00000001,0x00000028,0x00000230,0x00000232, + 0x0007000c,0x00000006,0x00000234,0x00000001,0x00000028,0x0000022e,0x00000233,0x0003003e, + 0x00000229,0x00000234,0x00050041,0x00000007,0x00000236,0x000000c3,0x0000003a,0x0004003d, + 0x00000006,0x00000237,0x00000236,0x00050041,0x00000007,0x00000238,0x000000c3,0x00000069, + 0x0004003d,0x00000006,0x00000239,0x00000238,0x0007000c,0x00000006,0x0000023a,0x00000001, + 0x00000025,0x00000237,0x00000239,0x00050041,0x00000007,0x0000023b,0x000000e8,0x00000031, + 0x0004003d,0x00000006,0x0000023c,0x0000023b,0x00050041,0x00000007,0x0000023d,0x000000e8, + 0x0000006e,0x0004003d,0x00000006,0x0000023e,0x0000023d,0x0007000c,0x00000006,0x0000023f, + 0x00000001,0x00000025,0x0000023c,0x0000023e,0x0007000c,0x00000006,0x00000240,0x00000001, + 0x00000025,0x0000023a,0x0000023f,0x0003003e,0x00000235,0x00000240,0x00050041,0x00000242, + 0x00000243,0x00000094,0x00000241,0x0004003d,0x00000006,0x00000244,0x00000243,0x0004003d, + 0x00000006,0x00000245,0x00000223,0x00050085,0x00000006,0x00000246,0x00000244,0x00000245, + 0x0004003d,0x00000006,0x00000247,0x00000235,0x0004003d,0x00000006,0x00000248,0x00000229, + 0x0008000c,0x00000006,0x00000249,0x00000001,0x0000002b,0x00000246,0x00000247,0x00000248, + 0x0003003e,0x00000223,0x00000249,0x0004003d,0x00000006,0x0000024b,0x00000223,0x00050041, + 0x00000007,0x0000024c,0x0000009e,0x0000006e,0x0004003d,0x00000006,0x0000024d,0x0000024c, + 0x00050083,0x00000006,0x0000024e,0x0000024b,0x0000024d,0x0003003e,0x0000024a,0x0000024e, + 0x0004003d,0x00000006,0x0000024f,0x0000024a,0x0008000c,0x00000006,0x00000252,0x00000001, + 0x0000002b,0x0000024f,0x00000250,0x00000251,0x0003003e,0x0000024a,0x00000252,0x00050041, + 0x00000007,0x00000253,0x0000009e,0x00000031,0x0004003d,0x00000006,0x00000254,0x00000253, + 0x0004003d,0x00000006,0x00000255,0x0000024a,0x00050081,0x00000006,0x00000256,0x00000254, + 0x00000255,0x0008000c,0x00000006,0x00000257,0x00000001,0x0000002b,0x00000256,0x00000053, + 0x00000054,0x00050041,0x00000007,0x00000258,0x0000009e,0x00000031,0x0003003e,0x00000258, + 0x00000257,0x00050041,0x00000007,0x00000259,0x0000009e,0x0000003a,0x0004003d,0x00000006, + 0x0000025a,0x00000259,0x0004003d,0x00000006,0x0000025b,0x0000024a,0x00050081,0x00000006, + 0x0000025c,0x0000025a,0x0000025b,0x0008000c,0x00000006,0x0000025d,0x00000001,0x0000002b, + 0x0000025c,0x00000053,0x00000054,0x00050041,0x00000007,0x0000025e,0x0000009e,0x0000003a, + 0x0003003e,0x0000025e,0x0000025d,0x00050041,0x00000007,0x0000025f,0x0000009e,0x00000069, + 0x0004003d,0x00000006,0x00000260,0x0000025f,0x0004003d,0x00000006,0x00000261,0x0000024a, + 0x00050081,0x00000006,0x00000262,0x00000260,0x00000261,0x0008000c,0x00000006,0x00000263, + 0x00000001,0x0000002b,0x00000262,0x00000053,0x00000054,0x00050041,0x00000007,0x00000264, + 0x0000009e,0x00000069,0x0003003e,0x00000264,0x00000263,0x000200f9,0x000000e1,0x000200f8, + 0x000000e1,0x00050041,0x00000007,0x00000265,0x0000009e,0x0000006e,0x0003003e,0x00000265, + 0x00000054,0x0004003d,0x00000016,0x00000268,0x0000009e,0x0003003e,0x00000267,0x00000268, + 0x000100fd,0x00010038,0x00050036,0x00000006,0x0000000a,0x00000000,0x00000008,0x00030037, + 0x00000007,0x00000009,0x000200f8,0x0000000b,0x0004003b,0x00000007,0x0000001d,0x00000007, + 0x0004003b,0x00000007,0x00000021,0x00000007,0x0004003d,0x00000006,0x0000001e,0x00000009, + 0x00050083,0x00000006,0x00000020,0x0000001e,0x0000001f,0x0003003e,0x0000001d,0x00000020, + 0x0004003d,0x00000006,0x00000022,0x00000009,0x0004003d,0x00000006,0x00000023,0x0000001d, + 0x00050085,0x00000006,0x00000024,0x00000022,0x00000023,0x0004003d,0x00000006,0x00000025, + 0x0000001d,0x00050083,0x00000006,0x00000026,0x00000024,0x00000025,0x0003003e,0x00000021, + 0x00000026,0x0004003d,0x00000006,0x00000027,0x0000001d,0x0004003d,0x00000006,0x00000028, + 0x0000001d,0x00050085,0x00000006,0x00000029,0x00000028,0x00000027,0x0003003e,0x0000001d, + 0x00000029,0x0004003d,0x00000006,0x0000002a,0x00000021,0x0004003d,0x00000006,0x0000002b, + 0x0000001d,0x00050085,0x00000006,0x0000002c,0x0000002a,0x0000002b,0x000200fe,0x0000002c, + 0x00010038,0x00050036,0x0000000e,0x00000014,0x00000000,0x0000000f,0x00030037,0x00000007, + 0x00000010,0x00030037,0x00000007,0x00000011,0x00030037,0x00000007,0x00000012,0x00030037, + 0x0000000d,0x00000013,0x000200f8,0x00000015,0x0004003b,0x00000007,0x0000002f,0x00000007, + 0x0004003b,0x00000034,0x00000035,0x00000007,0x0004003b,0x00000007,0x00000038,0x00000007, + 0x0004003b,0x00000007,0x00000043,0x00000007,0x0004003b,0x00000007,0x0000005b,0x00000007, + 0x0004003b,0x00000007,0x0000005c,0x00000007,0x00050041,0x00000007,0x00000032,0x00000013, + 0x00000031,0x0004003d,0x00000006,0x00000033,0x00000032,0x0003003e,0x0000002f,0x00000033, + 0x0004003d,0x0000000c,0x00000036,0x00000013,0x0007004f,0x0000000e,0x00000037,0x00000036, + 0x00000036,0x00000001,0x00000002,0x0003003e,0x00000035,0x00000037,0x0004003d,0x00000006, + 0x00000039,0x00000010,0x00050041,0x00000007,0x0000003b,0x00000035,0x0000003a,0x0004003d, + 0x00000006,0x0000003c,0x0000003b,0x00050085,0x00000006,0x0000003d,0x00000039,0x0000003c, + 0x0004003d,0x00000006,0x0000003e,0x00000011,0x00050041,0x00000007,0x0000003f,0x00000035, + 0x00000031,0x0004003d,0x00000006,0x00000040,0x0000003f,0x00050085,0x00000006,0x00000041, + 0x0000003e,0x00000040,0x00050081,0x00000006,0x00000042,0x0000003d,0x00000041,0x0003003e, + 0x00000038,0x00000042,0x0004003d,0x00000006,0x00000044,0x00000010,0x0004003d,0x00000006, + 0x00000045,0x00000010,0x00050085,0x00000006,0x00000046,0x00000044,0x00000045,0x0004003d, + 0x00000006,0x00000047,0x00000011,0x0004003d,0x00000006,0x00000048,0x00000011,0x00050085, + 0x00000006,0x00000049,0x00000047,0x00000048,0x00050081,0x00000006,0x0000004a,0x00000046, + 0x00000049,0x0004003d,0x00000006,0x0000004b,0x00000038,0x0004003d,0x00000006,0x0000004c, + 0x00000038,0x00050085,0x00000006,0x0000004d,0x0000004b,0x0000004c,0x0004003d,0x00000006, + 0x0000004e,0x00000012,0x0004003d,0x00000006,0x0000004f,0x00000012,0x00050085,0x00000006, + 0x00000050,0x0000004e,0x0000004f,0x0004003d,0x00000006,0x00000051,0x0000002f,0x00050085, + 0x00000006,0x00000052,0x00000050,0x00000051,0x0008000c,0x00000006,0x00000055,0x00000001, + 0x0000002b,0x00000052,0x00000053,0x00000054,0x00050085,0x00000006,0x00000057,0x00000055, + 0x00000056,0x00050083,0x00000006,0x00000058,0x00000057,0x00000054,0x00050085,0x00000006, + 0x00000059,0x0000004d,0x00000058,0x00050081,0x00000006,0x0000005a,0x0000004a,0x00000059, + 0x0003003e,0x00000043,0x0000005a,0x0004003d,0x00000006,0x0000005d,0x00000043,0x0003003e, + 0x0000005c,0x0000005d,0x00050039,0x00000006,0x0000005e,0x0000000a,0x0000005c,0x0003003e, + 0x0000005b,0x0000005e,0x0004003d,0x00000006,0x0000005f,0x0000005b,0x0004003d,0x00000006, + 0x00000060,0x0000005b,0x0004003d,0x00000006,0x00000061,0x00000012,0x00050085,0x00000006, + 0x00000062,0x00000060,0x00000061,0x00050050,0x0000000e,0x00000063,0x0000005f,0x00000062, + 0x000200fe,0x00000063,0x00010038,0x00050036,0x0000000e,0x0000001b,0x00000000,0x00000018, + 0x00030037,0x00000017,0x00000019,0x00030037,0x00000017,0x0000001a,0x000200f8,0x0000001c, + 0x0004003b,0x00000007,0x00000066,0x00000007,0x0004003b,0x00000007,0x0000006d,0x00000007, + 0x0004003b,0x00000034,0x00000074,0x00000007,0x0004003b,0x00000007,0x0000007d,0x00000007, + 0x00050041,0x00000007,0x00000067,0x0000001a,0x00000031,0x0004003d,0x00000006,0x00000068, + 0x00000067,0x00050041,0x00000007,0x0000006a,0x00000019,0x00000069,0x0004003d,0x00000006, + 0x0000006b,0x0000006a,0x00050083,0x00000006,0x0000006c,0x00000068,0x0000006b,0x0003003e, + 0x00000066,0x0000006c,0x00050041,0x00000007,0x0000006f,0x0000001a,0x0000006e,0x0004003d, + 0x00000006,0x00000070,0x0000006f,0x00050041,0x00000007,0x00000071,0x00000019,0x0000003a, + 0x0004003d,0x00000006,0x00000072,0x00000071,0x00050083,0x00000006,0x00000073,0x00000070, + 0x00000072,0x0003003e,0x0000006d,0x00000073,0x0004003d,0x00000006,0x00000075,0x00000066, + 0x0004003d,0x00000006,0x00000076,0x0000006d,0x00050081,0x00000006,0x00000077,0x00000075, + 0x00000076,0x00050041,0x00000007,0x00000078,0x00000074,0x00000031,0x0003003e,0x00000078, + 0x00000077,0x0004003d,0x00000006,0x00000079,0x00000066,0x0004003d,0x00000006,0x0000007a, + 0x0000006d,0x00050083,0x00000006,0x0000007b,0x00000079,0x0000007a,0x00050041,0x00000007, + 0x0000007c,0x00000074,0x0000003a,0x0003003e,0x0000007c,0x0000007b,0x00050041,0x00000007, + 0x0000007e,0x00000074,0x00000031,0x0004003d,0x00000006,0x0000007f,0x0000007e,0x00050041, + 0x00000007,0x00000080,0x00000074,0x00000031,0x0004003d,0x00000006,0x00000081,0x00000080, + 0x00050085,0x00000006,0x00000082,0x0000007f,0x00000081,0x00050081,0x00000006,0x00000084, + 0x00000082,0x00000083,0x00050041,0x00000007,0x00000085,0x00000074,0x0000003a,0x0004003d, + 0x00000006,0x00000086,0x00000085,0x00050041,0x00000007,0x00000087,0x00000074,0x0000003a, + 0x0004003d,0x00000006,0x00000088,0x00000087,0x00050085,0x00000006,0x00000089,0x00000086, + 0x00000088,0x00050081,0x00000006,0x0000008a,0x00000084,0x00000089,0x0006000c,0x00000006, + 0x0000008b,0x00000001,0x00000020,0x0000008a,0x0003003e,0x0000007d,0x0000008b,0x0004003d, + 0x0000000e,0x0000008c,0x00000074,0x0004003d,0x00000006,0x0000008d,0x0000007d,0x0005008e, + 0x0000000e,0x0000008e,0x0000008c,0x0000008d,0x000200fe,0x0000008e,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/toon_frag.h b/app/src/main/cpp/waylandcomp/shaders/toon_frag.h new file mode 100644 index 000000000..aa54788ff --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/toon_frag.h @@ -0,0 +1,90 @@ + // 1116.2.0 + #pragma once +const uint32_t toon_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000006d,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x00000067,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00030005,0x00000009,0x00007675,0x00060005,0x0000000b,0x67617266,0x43786554, + 0x64726f6f,0x00000000,0x00060005,0x0000000e,0x65676465,0x65726854,0x6c6f6873,0x00000064, + 0x00040005,0x00000010,0x7366666f,0x00007465,0x00030005,0x00000014,0x00004350,0x00040006, + 0x00000014,0x00000000,0x0063646e,0x00060006,0x00000014,0x00000001,0x6f736572,0x6974756c, + 0x00006e6f,0x00030005,0x00000016,0x00006370,0x00050005,0x0000001f,0x6f6c6f63,0x6e654372, + 0x00726574,0x00060005,0x00000023,0x75706e49,0x78655474,0x65727574,0x00000000,0x00050005, + 0x00000028,0x6f6c6f63,0x66654c72,0x00000074,0x00050005,0x00000034,0x6f6c6f63,0x67695272, + 0x00007468,0x00040005,0x0000003d,0x6f6c6f63,0x00705572,0x00050005,0x00000047,0x6f6c6f63, + 0x776f4472,0x0000006e,0x00060005,0x00000050,0x66666964,0x69726f48,0x746e6f7a,0x00006c61, + 0x00060005,0x00000055,0x66666964,0x74726556,0x6c616369,0x00000000,0x00050005,0x0000005a, + 0x65676465,0x74636146,0x0000726f,0x00060005,0x00000060,0x6c74756f,0x43656e69,0x726f6c6f, + 0x00000000,0x00050005,0x00000067,0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000000b, + 0x0000001e,0x00000000,0x00030047,0x00000014,0x00000002,0x00050048,0x00000014,0x00000000, + 0x00000023,0x00000000,0x00050048,0x00000014,0x00000001,0x00000023,0x00000010,0x00040047, + 0x00000023,0x00000021,0x00000000,0x00040047,0x00000023,0x00000022,0x00000000,0x00040047, + 0x00000067,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,0x00040020, + 0x00000008,0x00000007,0x00000007,0x00040020,0x0000000a,0x00000001,0x00000007,0x0004003b, + 0x0000000a,0x0000000b,0x00000001,0x00040020,0x0000000d,0x00000007,0x00000006,0x0004002b, + 0x00000006,0x0000000f,0x3e4ccccd,0x0004002b,0x00000006,0x00000011,0x3f800000,0x0005002c, + 0x00000007,0x00000012,0x00000011,0x00000011,0x00040017,0x00000013,0x00000006,0x00000004, + 0x0004001e,0x00000014,0x00000013,0x00000007,0x00040020,0x00000015,0x00000009,0x00000014, + 0x0004003b,0x00000015,0x00000016,0x00000009,0x00040015,0x00000017,0x00000020,0x00000001, + 0x0004002b,0x00000017,0x00000018,0x00000001,0x00040020,0x00000019,0x00000009,0x00000007, + 0x00040017,0x0000001d,0x00000006,0x00000003,0x00040020,0x0000001e,0x00000007,0x0000001d, + 0x00090019,0x00000020,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001, + 0x00000000,0x0003001b,0x00000021,0x00000020,0x00040020,0x00000022,0x00000000,0x00000021, + 0x0004003b,0x00000022,0x00000023,0x00000000,0x00040015,0x0000002b,0x00000020,0x00000000, + 0x0004002b,0x0000002b,0x0000002c,0x00000000,0x0004002b,0x00000006,0x0000002f,0x00000000, + 0x0004002b,0x0000002b,0x00000040,0x00000001,0x0006002c,0x0000001d,0x00000062,0x0000002f, + 0x0000002f,0x0000002f,0x00040020,0x00000066,0x00000003,0x00000013,0x0004003b,0x00000066, + 0x00000067,0x00000003,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, + 0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003b,0x0000000d,0x0000000e, + 0x00000007,0x0004003b,0x00000008,0x00000010,0x00000007,0x0004003b,0x0000001e,0x0000001f, + 0x00000007,0x0004003b,0x0000001e,0x00000028,0x00000007,0x0004003b,0x0000001e,0x00000034, + 0x00000007,0x0004003b,0x0000001e,0x0000003d,0x00000007,0x0004003b,0x0000001e,0x00000047, + 0x00000007,0x0004003b,0x0000000d,0x00000050,0x00000007,0x0004003b,0x0000000d,0x00000055, + 0x00000007,0x0004003b,0x0000000d,0x0000005a,0x00000007,0x0004003b,0x0000001e,0x00000060, + 0x00000007,0x0004003d,0x00000007,0x0000000c,0x0000000b,0x0003003e,0x00000009,0x0000000c, + 0x0003003e,0x0000000e,0x0000000f,0x00050041,0x00000019,0x0000001a,0x00000016,0x00000018, + 0x0004003d,0x00000007,0x0000001b,0x0000001a,0x00050088,0x00000007,0x0000001c,0x00000012, + 0x0000001b,0x0003003e,0x00000010,0x0000001c,0x0004003d,0x00000021,0x00000024,0x00000023, + 0x0004003d,0x00000007,0x00000025,0x00000009,0x00050057,0x00000013,0x00000026,0x00000024, + 0x00000025,0x0008004f,0x0000001d,0x00000027,0x00000026,0x00000026,0x00000000,0x00000001, + 0x00000002,0x0003003e,0x0000001f,0x00000027,0x0004003d,0x00000021,0x00000029,0x00000023, + 0x0004003d,0x00000007,0x0000002a,0x00000009,0x00050041,0x0000000d,0x0000002d,0x00000010, + 0x0000002c,0x0004003d,0x00000006,0x0000002e,0x0000002d,0x00050050,0x00000007,0x00000030, + 0x0000002e,0x0000002f,0x00050083,0x00000007,0x00000031,0x0000002a,0x00000030,0x00050057, + 0x00000013,0x00000032,0x00000029,0x00000031,0x0008004f,0x0000001d,0x00000033,0x00000032, + 0x00000032,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000028,0x00000033,0x0004003d, + 0x00000021,0x00000035,0x00000023,0x0004003d,0x00000007,0x00000036,0x00000009,0x00050041, + 0x0000000d,0x00000037,0x00000010,0x0000002c,0x0004003d,0x00000006,0x00000038,0x00000037, + 0x00050050,0x00000007,0x00000039,0x00000038,0x0000002f,0x00050081,0x00000007,0x0000003a, + 0x00000036,0x00000039,0x00050057,0x00000013,0x0000003b,0x00000035,0x0000003a,0x0008004f, + 0x0000001d,0x0000003c,0x0000003b,0x0000003b,0x00000000,0x00000001,0x00000002,0x0003003e, + 0x00000034,0x0000003c,0x0004003d,0x00000021,0x0000003e,0x00000023,0x0004003d,0x00000007, + 0x0000003f,0x00000009,0x00050041,0x0000000d,0x00000041,0x00000010,0x00000040,0x0004003d, + 0x00000006,0x00000042,0x00000041,0x00050050,0x00000007,0x00000043,0x0000002f,0x00000042, + 0x00050083,0x00000007,0x00000044,0x0000003f,0x00000043,0x00050057,0x00000013,0x00000045, + 0x0000003e,0x00000044,0x0008004f,0x0000001d,0x00000046,0x00000045,0x00000045,0x00000000, + 0x00000001,0x00000002,0x0003003e,0x0000003d,0x00000046,0x0004003d,0x00000021,0x00000048, + 0x00000023,0x0004003d,0x00000007,0x00000049,0x00000009,0x00050041,0x0000000d,0x0000004a, + 0x00000010,0x00000040,0x0004003d,0x00000006,0x0000004b,0x0000004a,0x00050050,0x00000007, + 0x0000004c,0x0000002f,0x0000004b,0x00050081,0x00000007,0x0000004d,0x00000049,0x0000004c, + 0x00050057,0x00000013,0x0000004e,0x00000048,0x0000004d,0x0008004f,0x0000001d,0x0000004f, + 0x0000004e,0x0000004e,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000047,0x0000004f, + 0x0004003d,0x0000001d,0x00000051,0x00000034,0x0004003d,0x0000001d,0x00000052,0x00000028, + 0x00050083,0x0000001d,0x00000053,0x00000051,0x00000052,0x0006000c,0x00000006,0x00000054, + 0x00000001,0x00000042,0x00000053,0x0003003e,0x00000050,0x00000054,0x0004003d,0x0000001d, + 0x00000056,0x0000003d,0x0004003d,0x0000001d,0x00000057,0x00000047,0x00050083,0x0000001d, + 0x00000058,0x00000056,0x00000057,0x0006000c,0x00000006,0x00000059,0x00000001,0x00000042, + 0x00000058,0x0003003e,0x00000055,0x00000059,0x0004003d,0x00000006,0x0000005b,0x0000000e, + 0x0004003d,0x00000006,0x0000005c,0x00000050,0x0004003d,0x00000006,0x0000005d,0x00000055, + 0x00050081,0x00000006,0x0000005e,0x0000005c,0x0000005d,0x0007000c,0x00000006,0x0000005f, + 0x00000001,0x00000030,0x0000005b,0x0000005e,0x0003003e,0x0000005a,0x0000005f,0x0004003d, + 0x0000001d,0x00000061,0x0000001f,0x0004003d,0x00000006,0x00000063,0x0000005a,0x00060050, + 0x0000001d,0x00000064,0x00000063,0x00000063,0x00000063,0x0008000c,0x0000001d,0x00000065, + 0x00000001,0x0000002e,0x00000061,0x00000062,0x00000064,0x0003003e,0x00000060,0x00000065, + 0x0004003d,0x0000001d,0x00000068,0x00000060,0x00050051,0x00000006,0x00000069,0x00000068, + 0x00000000,0x00050051,0x00000006,0x0000006a,0x00000068,0x00000001,0x00050051,0x00000006, + 0x0000006b,0x00000068,0x00000002,0x00070050,0x00000013,0x0000006c,0x00000069,0x0000006a, + 0x0000006b,0x00000011,0x0003003e,0x00000067,0x0000006c,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/shaders/upscale_vert.h b/app/src/main/cpp/waylandcomp/shaders/upscale_vert.h new file mode 100644 index 000000000..c64f91729 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/shaders/upscale_vert.h @@ -0,0 +1,65 @@ + // 1116.2.0 + #pragma once +const uint32_t upscale_vert_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000004b,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0008000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000a,0x0000003b,0x00000045, + 0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00030005, + 0x00000008,0x00006978,0x00060005,0x0000000a,0x565f6c67,0x65747265,0x646e4978,0x00007865, + 0x00030005,0x0000000f,0x00006979,0x00030005,0x00000014,0x00000078,0x00030005,0x0000001c, + 0x00004350,0x00040006,0x0000001c,0x00000000,0x0063646e,0x00030005,0x0000001e,0x00006370, + 0x00030005,0x0000002a,0x00000079,0x00060005,0x00000039,0x505f6c67,0x65567265,0x78657472, + 0x00000000,0x00060006,0x00000039,0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006, + 0x00000039,0x00000001,0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x00000039, + 0x00000002,0x435f6c67,0x4470696c,0x61747369,0x0065636e,0x00070006,0x00000039,0x00000003, + 0x435f6c67,0x446c6c75,0x61747369,0x0065636e,0x00030005,0x0000003b,0x00000000,0x00060005, + 0x00000045,0x67617266,0x43786554,0x64726f6f,0x00000000,0x00040047,0x0000000a,0x0000000b, + 0x0000002a,0x00030047,0x0000001c,0x00000002,0x00050048,0x0000001c,0x00000000,0x00000023, + 0x00000000,0x00030047,0x00000039,0x00000002,0x00050048,0x00000039,0x00000000,0x0000000b, + 0x00000000,0x00050048,0x00000039,0x00000001,0x0000000b,0x00000001,0x00050048,0x00000039, + 0x00000002,0x0000000b,0x00000003,0x00050048,0x00000039,0x00000003,0x0000000b,0x00000004, + 0x00040047,0x00000045,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003, + 0x00000002,0x00040015,0x00000006,0x00000020,0x00000001,0x00040020,0x00000007,0x00000007, + 0x00000006,0x00040020,0x00000009,0x00000001,0x00000006,0x0004003b,0x00000009,0x0000000a, + 0x00000001,0x0004002b,0x00000006,0x0000000c,0x00000001,0x00030016,0x00000012,0x00000020, + 0x00040020,0x00000013,0x00000007,0x00000012,0x00020014,0x00000016,0x00040017,0x0000001b, + 0x00000012,0x00000004,0x0003001e,0x0000001c,0x0000001b,0x00040020,0x0000001d,0x00000009, + 0x0000001c,0x0004003b,0x0000001d,0x0000001e,0x00000009,0x0004002b,0x00000006,0x0000001f, + 0x00000000,0x00040015,0x00000020,0x00000020,0x00000000,0x0004002b,0x00000020,0x00000021, + 0x00000002,0x00040020,0x00000022,0x00000009,0x00000012,0x0004002b,0x00000020,0x00000026, + 0x00000000,0x0004002b,0x00000020,0x00000030,0x00000003,0x0004002b,0x00000020,0x00000034, + 0x00000001,0x0004001c,0x00000038,0x00000012,0x00000034,0x0006001e,0x00000039,0x0000001b, + 0x00000012,0x00000038,0x00000038,0x00040020,0x0000003a,0x00000003,0x00000039,0x0004003b, + 0x0000003a,0x0000003b,0x00000003,0x0004002b,0x00000012,0x0000003e,0x00000000,0x0004002b, + 0x00000012,0x0000003f,0x3f800000,0x00040020,0x00000041,0x00000003,0x0000001b,0x00040017, + 0x00000043,0x00000012,0x00000002,0x00040020,0x00000044,0x00000003,0x00000043,0x0004003b, + 0x00000044,0x00000045,0x00000003,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003, + 0x000200f8,0x00000005,0x0004003b,0x00000007,0x00000008,0x00000007,0x0004003b,0x00000007, + 0x0000000f,0x00000007,0x0004003b,0x00000013,0x00000014,0x00000007,0x0004003b,0x00000013, + 0x00000018,0x00000007,0x0004003b,0x00000013,0x0000002a,0x00000007,0x0004003b,0x00000013, + 0x0000002d,0x00000007,0x0004003d,0x00000006,0x0000000b,0x0000000a,0x000500c3,0x00000006, + 0x0000000d,0x0000000b,0x0000000c,0x000500c7,0x00000006,0x0000000e,0x0000000d,0x0000000c, + 0x0003003e,0x00000008,0x0000000e,0x0004003d,0x00000006,0x00000010,0x0000000a,0x000500c7, + 0x00000006,0x00000011,0x00000010,0x0000000c,0x0003003e,0x0000000f,0x00000011,0x0004003d, + 0x00000006,0x00000015,0x00000008,0x000500aa,0x00000016,0x00000017,0x00000015,0x0000000c, + 0x000300f7,0x0000001a,0x00000000,0x000400fa,0x00000017,0x00000019,0x00000025,0x000200f8, + 0x00000019,0x00060041,0x00000022,0x00000023,0x0000001e,0x0000001f,0x00000021,0x0004003d, + 0x00000012,0x00000024,0x00000023,0x0003003e,0x00000018,0x00000024,0x000200f9,0x0000001a, + 0x000200f8,0x00000025,0x00060041,0x00000022,0x00000027,0x0000001e,0x0000001f,0x00000026, + 0x0004003d,0x00000012,0x00000028,0x00000027,0x0003003e,0x00000018,0x00000028,0x000200f9, + 0x0000001a,0x000200f8,0x0000001a,0x0004003d,0x00000012,0x00000029,0x00000018,0x0003003e, + 0x00000014,0x00000029,0x0004003d,0x00000006,0x0000002b,0x0000000f,0x000500aa,0x00000016, + 0x0000002c,0x0000002b,0x0000000c,0x000300f7,0x0000002f,0x00000000,0x000400fa,0x0000002c, + 0x0000002e,0x00000033,0x000200f8,0x0000002e,0x00060041,0x00000022,0x00000031,0x0000001e, + 0x0000001f,0x00000030,0x0004003d,0x00000012,0x00000032,0x00000031,0x0003003e,0x0000002d, + 0x00000032,0x000200f9,0x0000002f,0x000200f8,0x00000033,0x00060041,0x00000022,0x00000035, + 0x0000001e,0x0000001f,0x00000034,0x0004003d,0x00000012,0x00000036,0x00000035,0x0003003e, + 0x0000002d,0x00000036,0x000200f9,0x0000002f,0x000200f8,0x0000002f,0x0004003d,0x00000012, + 0x00000037,0x0000002d,0x0003003e,0x0000002a,0x00000037,0x0004003d,0x00000012,0x0000003c, + 0x00000014,0x0004003d,0x00000012,0x0000003d,0x0000002a,0x00070050,0x0000001b,0x00000040, + 0x0000003c,0x0000003d,0x0000003e,0x0000003f,0x00050041,0x00000041,0x00000042,0x0000003b, + 0x0000001f,0x0003003e,0x00000042,0x00000040,0x0004003d,0x00000006,0x00000046,0x00000008, + 0x0004006f,0x00000012,0x00000047,0x00000046,0x0004003d,0x00000006,0x00000048,0x0000000f, + 0x0004006f,0x00000012,0x00000049,0x00000048,0x00050050,0x00000043,0x0000004a,0x00000047, + 0x00000049,0x0003003e,0x00000045,0x0000004a,0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/src/ahb_swapchain.c b/app/src/main/cpp/waylandcomp/src/ahb_swapchain.c new file mode 100644 index 000000000..87102d4a7 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/ahb_swapchain.c @@ -0,0 +1,426 @@ +/* Zero-copy game frames: banner_ahb_v1 + the game's AHardwareBuffers on the sc_layer SurfaceControl. + * See ahb_swapchain.h and ZERO_COPY_SPIKE.md. */ +#define _GNU_SOURCE +#include "ahb_swapchain.h" +#include "color_mgmt.h" +#include "desktop_ext.h" +#include "sc_layer.h" +#include "vk_present.h" +#include "ahb-v1-server-protocol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MOD_QCOM_COMPRESSED 0x0500000000000001ULL /* DRM_FORMAT_MOD_QCOM_COMPRESSED (UBWC) */ + +/* linux/dma-buf.h (not in the NDK sysroot's older headers): sync_file export / import. */ +struct wnc_dma_buf_sync_file { uint32_t flags; int32_t fd; }; +#define WNC_DMA_BUF_SYNC_READ 1u +#define WNC_DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR('b', 2, struct wnc_dma_buf_sync_file) +#define WNC_DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW('b', 3, struct wnc_dma_buf_sync_file) + +extern volatile int g_zero_copy; + +/* One per wl_buffer the game attached an AHardwareBuffer to. Lives while the wl_buffer exists or + * the buffer is still on the layer / held by SurfaceFlinger, whichever is longer. */ +struct ahb_buf { + uint64_t id; /* the token sc_layer.c hands back */ + struct dmabuf_buffer *b; /* one reference held for the life of this record */ + AHardwareBuffer *ahb; /* our reference to the game's buffer */ + int w, h; + uint32_t stride, image_count; + uint32_t format; /* AHARDWAREBUFFER_FORMAT_* gralloc gave it (10-bit for HDR10) */ + uint64_t modifier; + struct wl_client *client; + struct wl_resource *resource; /* the wl_buffer; NULL once the client destroyed it */ + struct wl_listener resource_destroy; + int on_layer; /* set on the SurfaceControl (or not yet released by SurfaceFlinger) */ + int release_pending; /* the surface let go of it while on the layer */ + int64_t deferred_ns; /* when it did (the perf line: how long the display kept it) */ + struct surface *surface; /* for the paced release; NULL = release at once */ + struct wl_event_source *fence_src; /* fallback: waiting the release fence in the event loop */ + int fence_fd; + struct wl_list link; +}; + +static struct wl_list g_bufs; +static struct wl_list g_clients; /* bound banner_ahb_v1 resources (wl_resource links) */ +static uint64_t g_next_id = 1; +static struct wl_event_loop *g_loop; +static unsigned g_stat_zero_copy; +static int g_export_failed_logged, g_import_failed_logged; +static int g_advertised; /* the global exists */ +static int g_mode_sent = -1; /* the mode clients were last told (-1 = none yet) */ +static struct surface *g_announced; /* "presenting X without a copy" said for this surface */ +static volatile int64_t g_last_zero_copy_ns; /* when a frame last went on the layer without a copy */ + +/* SurfaceFlinger's callback thread -> compositor thread: released tokens + their fences. */ +struct released { uint64_t id; int fd; }; +static pthread_mutex_t g_rel_lock = PTHREAD_MUTEX_INITIALIZER; +static struct released *g_rel; +static int g_rel_n, g_rel_cap; +static int g_rel_pipe[2] = {-1, -1}; + +/* Which swapchains were announced (one log line per swapchain, not per image). */ +struct chain_seen { struct wl_client *client; int w, h; uint32_t image_count; uint64_t modifier; uint32_t format; }; +static struct chain_seen g_last_chain; + +static int64_t now_ns(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static struct ahb_buf *find_buf(uint64_t id) { + struct ahb_buf *ab; + wl_list_for_each(ab, &g_bufs, link) + if (ab->id == id) return ab; + return NULL; +} + +static void buf_free(struct ahb_buf *ab) { + if (ab->resource) { wl_list_remove(&ab->resource_destroy.link); ab->resource = NULL; } + if (ab->fence_src) { wl_event_source_remove(ab->fence_src); ab->fence_src = NULL; } + if (ab->fence_fd >= 0) { close(ab->fence_fd); ab->fence_fd = -1; } + wl_list_remove(&ab->link); + *wnc_dmabuf_ahb_slot(ab->b) = NULL; + AHardwareBuffer_release(ab->ahb); + wnc_dmabuf_unref(ab->b); + free(ab); +} + +/* Free once neither the client (wl_buffer) nor the display (layer) refers to it. */ +static void maybe_free(struct ahb_buf *ab) { + if (!ab->resource && !ab->on_layer && !ab->fence_src) buf_free(ab); +} + +static void on_buffer_resource_destroyed(struct wl_listener *l, void *data) { + struct ahb_buf *ab = wl_container_of(l, ab, resource_destroy); + wl_list_remove(&ab->resource_destroy.link); + ab->resource = NULL; + ab->release_pending = 0; /* nothing left to release to */ + maybe_free(ab); +} + +/* ---- release: SurfaceFlinger is done with the buffer -> the game's next acquire waits, then wl_buffer.release */ + +static void send_deferred_release(struct ahb_buf *ab) { + if (ab->release_pending && ab->resource) + wnc_release_buffer(ab->surface, ab->resource, ab->surface != NULL, ab->deferred_ns); + ab->release_pending = 0; + ab->surface = NULL; +} + +static int on_fence_readable(int fd, uint32_t mask, void *data) { + struct ahb_buf *ab = data; + wl_event_source_remove(ab->fence_src); + ab->fence_src = NULL; + close(ab->fence_fd); + ab->fence_fd = -1; + send_deferred_release(ab); + maybe_free(ab); + return 0; +} + +/* Compositor thread. Puts the display's release fence into the dma-buf so the game's driver waits + * on it before rendering into the buffer again (Mesa exports every fence of the dma-buf as the + * acquire semaphore), then gives the buffer back. If the kernel refuses the import, the release + * itself waits for the fence instead. */ +static void handle_released(uint64_t id, int fd) { + struct ahb_buf *ab = find_buf(id); + if (!ab) { if (fd >= 0) close(fd); return; } + ab->on_layer = 0; + if (fd >= 0) { + struct wnc_dma_buf_sync_file imp = {.flags = WNC_DMA_BUF_SYNC_READ, .fd = fd}; + int dmabuf_fd = wnc_dmabuf_fd(ab->b); + if (dmabuf_fd >= 0 && ioctl(dmabuf_fd, WNC_DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &imp) == 0) { + close(fd); + } else { + if (!g_import_failed_logged) { + g_import_failed_logged = 1; + wnc_log("layer", "zero-copy: DMA_BUF_IOCTL_IMPORT_SYNC_FILE failed (%s): releases wait for the display's fence here instead", + strerror(errno)); + } + /* Wait it out in the event loop (a sync_file is readable once signalled). */ + struct pollfd p = {.fd = fd, .events = POLLIN}; + if (poll(&p, 1, 0) > 0) { + close(fd); + } else { + ab->fence_fd = fd; + ab->fence_src = wl_event_loop_add_fd(g_loop, fd, WL_EVENT_READABLE, on_fence_readable, ab); + if (!ab->fence_src) { close(fd); ab->fence_fd = -1; } + else return; /* released from on_fence_readable */ + } + } + } + send_deferred_release(ab); + maybe_free(ab); +} + +static int on_release_pipe(int fd, uint32_t mask, void *data) { + char buf[64]; + while (read(fd, buf, sizeof(buf)) > 0) {} + for (;;) { + struct released r; + pthread_mutex_lock(&g_rel_lock); + if (g_rel_n == 0) { pthread_mutex_unlock(&g_rel_lock); break; } + r = g_rel[0]; + memmove(g_rel, g_rel + 1, (size_t)(--g_rel_n) * sizeof(*g_rel)); + pthread_mutex_unlock(&g_rel_lock); + handle_released(r.id, r.fd); + } + return 0; +} + +/* SurfaceFlinger's callback thread. */ +void ahb_swapchain_layer_released(void *token, int release_fd) { + pthread_mutex_lock(&g_rel_lock); + if (g_rel_n == g_rel_cap) { + int cap = g_rel_cap ? g_rel_cap * 2 : 16; + struct released *n = realloc(g_rel, (size_t)cap * sizeof(*n)); + if (!n) { pthread_mutex_unlock(&g_rel_lock); if (release_fd >= 0) close(release_fd); return; } + g_rel = n; g_rel_cap = cap; + } + g_rel[g_rel_n++] = (struct released){(uint64_t)(uintptr_t)token, release_fd}; + pthread_mutex_unlock(&g_rel_lock); + if (g_rel_pipe[1] >= 0) { char c = 1; if (write(g_rel_pipe[1], &c, 1) < 0) {} } +} + +/* ---- present */ + +int ahb_swapchain_has_ahb(const struct dmabuf_buffer *b) { + return b && *wnc_dmabuf_ahb_slot((struct dmabuf_buffer *)b) != NULL; +} + +int ahb_swapchain_present(struct dmabuf_buffer *b, struct surface *s, int scene_w, int scene_h) { + struct ahb_buf *ab = b ? *wnc_dmabuf_ahb_slot(b) : NULL; + if (!ab) return -1; + int dmabuf_fd = wnc_dmabuf_fd(b); + /* Acquire fence: the game's render fence, which its driver put into the dma-buf before the + * commit (Mesa's implicit sync). The display waits on it, not the CPU. */ + int acquire = -1; + struct wnc_dma_buf_sync_file exp = {.flags = WNC_DMA_BUF_SYNC_READ, .fd = -1}; + if (dmabuf_fd >= 0 && ioctl(dmabuf_fd, WNC_DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &exp) == 0 && exp.fd >= 0) { + acquire = exp.fd; + } else if (dmabuf_fd >= 0) { + if (!g_export_failed_logged) { + g_export_failed_logged = 1; + wnc_log("layer", "zero-copy: DMA_BUF_IOCTL_EXPORT_SYNC_FILE failed (%s): waiting for each frame on the CPU instead", + strerror(errno)); + } + struct pollfd p = {.fd = dmabuf_fd, .events = POLLIN}; /* readable = the writers are done */ + int r; + do { r = poll(&p, 1, 100); } while (r < 0 && errno == EINTR); + } + int r = sc_layer_present_ahb(ab->ahb, ab->w, ab->h, acquire, (void *)(uintptr_t)ab->id, scene_w, scene_h, + s ? wnc_surface_color(s) : NULL, ab->format); + if (r < 0) return -1; + if (r == 1) return 0; /* nothing of it on screen: not on the layer, nothing to release later */ + if (!ab->on_layer) { + ab->on_layer = 1; + g_stat_zero_copy++; + g_last_zero_copy_ns = now_ns(); + } + if (s && g_announced != s) { + char name[160]; + g_announced = s; + wnc_surface_describe(s, name, sizeof(name)); + wnc_log("layer", "zero-copy: presenting %s without a copy", name); + } + return 0; +} + +int ahb_swapchain_last_frame_age_ms(void) { + int64_t t = g_last_zero_copy_ns; + if (!t) return -1; + int64_t age = (now_ns() - t) / 1000000LL; + return age > INT32_MAX ? INT32_MAX : (int)age; +} + +int ahb_swapchain_defer_release(struct dmabuf_buffer *b, struct wl_resource *buffer, struct surface *s, int paced) { + struct ahb_buf *ab = b ? *wnc_dmabuf_ahb_slot(b) : NULL; + if (!ab || !ab->on_layer || !buffer || ab->resource != buffer) return 0; + ab->release_pending = 1; + ab->deferred_ns = now_ns(); + ab->surface = paced ? s : NULL; + return 1; +} + +void ahb_swapchain_surface_gone(struct surface *s) { + struct ahb_buf *ab; + wl_list_for_each(ab, &g_bufs, link) + if (ab->surface == s) ab->surface = NULL; +} + +unsigned ahb_swapchain_stats_take(void) { + unsigned n = g_stat_zero_copy; + g_stat_zero_copy = 0; + return n; +} + +int ahb_swapchain_advertised(void) { return g_advertised; } + +uint32_t ahb_swapchain_ahb_format(const struct dmabuf_buffer *b) { + struct ahb_buf *ab = b ? *wnc_dmabuf_ahb_slot((struct dmabuf_buffer *)b) : NULL; + return ab ? ab->format : 0; +} + +/* ---- banner_ahb_v1 */ + +static void ahb_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } + +static void ahb_attach(struct wl_client *c, struct wl_resource *r, struct wl_resource *buffer, int32_t sock, + uint32_t width, uint32_t height, uint32_t stride, uint32_t mod_hi, uint32_t mod_lo, + uint32_t image_count) { + struct dmabuf_buffer *b = wnc_dmabuf_from_resource(buffer); + uint64_t modifier = ((uint64_t)mod_hi << 32) | mod_lo; + if (!b) { + wnc_log("layer", "zero-copy: %s attached an AHardwareBuffer to a non-dma-buf wl_buffer, ignored", wnc_client_name(c)); + close(sock); + return; + } + void **slot = wnc_dmabuf_ahb_slot(b); + if (*slot) { close(sock); return; } /* once per buffer */ + + /* The handle was written into the socket before the request was sent; a short wait is just + * insurance against a slow sender. */ + struct pollfd p = {.fd = sock, .events = POLLIN}; + int pr; + do { pr = poll(&p, 1, 1000); } while (pr < 0 && errno == EINTR); + AHardwareBuffer *ahb = NULL; + int rc = pr > 0 ? AHardwareBuffer_recvHandleFromUnixSocket(sock, &ahb) : -1; + close(sock); + if (rc != 0 || !ahb) { + wnc_log("layer", "zero-copy: receiving %s's AHardwareBuffer failed (%s)", wnc_client_name(c), + pr > 0 ? "recvHandleFromUnixSocket" : "nothing on the socket"); + return; + } + AHardwareBuffer_Desc d; + AHardwareBuffer_describe(ahb, &d); + int bw, bh; + wnc_dmabuf_size(b, &bw, &bh); + if ((int)d.width != bw || (int)d.height != bh || (int)width != bw || (int)height != bh) { + wnc_log("layer", "zero-copy: %s's AHardwareBuffer is %ux%u but its wl_buffer %dx%d, ignored", + wnc_client_name(c), d.width, d.height, bw, bh); + AHardwareBuffer_release(ahb); + return; + } + + struct ahb_buf *ab = calloc(1, sizeof(*ab)); + if (!ab) { AHardwareBuffer_release(ahb); wl_client_post_no_memory(c); return; } + ab->id = g_next_id++; + ab->b = b; + wnc_dmabuf_ref(b); + ab->ahb = ahb; + ab->w = bw; ab->h = bh; + ab->stride = d.stride ? d.stride : stride; + ab->image_count = image_count; + ab->format = d.format; + ab->modifier = modifier; + ab->client = c; + ab->resource = buffer; + ab->resource_destroy.notify = on_buffer_resource_destroyed; + wl_resource_add_destroy_listener(buffer, &ab->resource_destroy); + ab->fence_fd = -1; + wl_list_insert(g_bufs.prev, &ab->link); + *slot = ab; + + if (g_last_chain.client != c || g_last_chain.w != bw || g_last_chain.h != bh || + g_last_chain.image_count != image_count || g_last_chain.modifier != modifier || g_last_chain.format != d.format) { + g_last_chain = (struct chain_seen){c, bw, bh, image_count, modifier, d.format}; + wnc_log("layer", "zero-copy: AHB swapchain from %s (%u images, %dx%d, %s, %s, stride %u px)", wnc_client_name(c), + image_count, bw, bh, wnc_ahb_format_name(d.format), modifier == MOD_QCOM_COMPRESSED ? "UBWC (QCOM_COMPRESSED)" + : modifier == 0 ? "linear" : "unknown modifier", ab->stride); + } +} + +static const struct banner_ahb_v1_interface ahb_impl = { + .destroy = ahb_destroy, + .attach = ahb_attach, +}; + +static void on_client_resource_destroyed(struct wl_resource *r) { + wl_list_remove(wl_resource_get_link(r)); +} + +static void bind_ahb(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + static struct wl_client *last_named; + if (ver > 2) ver = 2; + struct wl_resource *r = wl_resource_create(c, &banner_ahb_v1_interface, (int)ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &ahb_impl, NULL, on_client_resource_destroyed); + wl_list_insert(&g_clients, wl_resource_get_link(r)); + /* The mode travels with the bind, so the client's registry roundtrip already has it when it + * decides about its first swapchain. */ + if (ver >= WNC_AHB_V1_MODE_SINCE_VERSION) banner_ahb_v1_send_mode(r, g_zero_copy ? 1u : 0u); + /* One line per program, not per surface-format query (each binds its own). */ + if (last_named != c) { + last_named = c; + wnc_log("layer", "zero-copy: %s bound banner_ahb_v1 version %u (%s)", wnc_client_name(c), ver, + ver >= 2 ? "follows the live switch" : "version 1: decides from its launch environment only"); + } +} + +void ahb_swapchain_set_mode(int on, int live) { + on = on ? 1 : 0; + g_zero_copy = on; + if (!g_advertised) { + if (live) wnc_log("layer", "zero-copy switched %s from the drawer, but no display layer is available on this device: no change", + on ? "on" : "off"); + return; + } + if (g_mode_sent == on) return; + g_mode_sent = on; + int told = 0; + struct wl_resource *r; + wl_resource_for_each(r, &g_clients) { + if (wl_resource_get_version(r) >= WNC_AHB_V1_MODE_SINCE_VERSION) { + banner_ahb_v1_send_mode(r, (uint32_t)on); + told++; + } + } + /* The next chain and the next fullscreen frame get announced again. */ + memset(&g_last_chain, 0, sizeof(g_last_chain)); + g_announced = NULL; + if (live) + wnc_log("layer", "zero-copy switched %s from the drawer: %d bound program%s told to rebuild their swapchains%s", + on ? "on" : "off", told, told == 1 ? "" : "s", + on ? "; frames go on the display layer once the new gralloc swapchain is up" + : "; gralloc frames still in flight stay on the layer (or take the copy path) until then"); + else + wnc_log("layer", "zero-copy: %s at launch (%s)", on ? "on" : "off", + on ? "BANNER_WAYLAND_ZERO_COPY=1: games present their own gralloc buffers on the display layer" + : "the drawer's Zero-copy presentation switch turns it on live"); + if (live) wl_display_flush_clients(wnc_get_display()); + wnc_request_redraw(); +} + +void ahb_swapchain_init(struct wl_display *display) { + wl_list_init(&g_bufs); + wl_list_init(&g_clients); + if (!sc_layer_available()) { + wnc_log("layer", "zero-copy: no display layer on this device (see the line above): banner_ahb_v1 not advertised"); + return; + } + g_loop = wl_display_get_event_loop(display); + if (pipe2(g_rel_pipe, O_CLOEXEC | O_NONBLOCK) != 0) { + wnc_log("error", "zero-copy: release pipe creation failed (%s); zero-copy presents disabled", strerror(errno)); + g_rel_pipe[0] = g_rel_pipe[1] = -1; + return; + } + wl_event_loop_add_fd(g_loop, g_rel_pipe[0], WL_EVENT_READABLE, on_release_pipe, NULL); + if (!wl_global_create(display, &banner_ahb_v1_interface, 2, NULL, bind_ahb)) { + wnc_log("error", "zero-copy: banner_ahb_v1 global creation failed"); + return; + } + g_advertised = 1; + wnc_log("layer", "zero-copy: banner_ahb_v1 version 2 advertised (games built for it present their own gralloc buffers while the switch is on)"); + ahb_swapchain_set_mode(g_zero_copy, 0); +} diff --git a/app/src/main/cpp/waylandcomp/src/ahb_swapchain.h b/app/src/main/cpp/waylandcomp/src/ahb_swapchain.h new file mode 100644 index 000000000..e9d05eeac --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/ahb_swapchain.h @@ -0,0 +1,83 @@ +#ifndef AHB_SWAPCHAIN_H +#define AHB_SWAPCHAIN_H +/* + * Zero-copy game frames (layer mode, BANNER_WAYLAND_ZERO_COPY=1 at launch, the drawer's "Zero-copy + * presentation" switch live): the guest half of the design in ZERO_COPY_SPIKE.md. Our Wayland Turnip + * (banners-turnip-wayland, patches/wayland/wnc_ahb_wsi.py) binds the private global banner_ahb_v1 + * (version 2), which this compositor advertises on every session where a display layer is possible, + * and follows its `mode` event: while the mode is on, each swapchain the game creates has its images + * allocated as gralloc AHardwareBuffers, shared as dma-bufs through zwp_linux_dmabuf_v1 as before (so + * the blit path still works) and handed to us once per wl_buffer over a socketpair + * (banner_ahb_v1.attach). When such a buffer is the one fullscreen frame, it goes straight onto the + * sc_layer SurfaceControl: SurfaceFlinger / the display scan out the game's own buffer, no copy + * anywhere. A mode flip (ahb_swapchain_set_mode, from the drawer through the host queue) is + * broadcast to every bound client; a client whose live swapchain was built for the other mode + * retires it (VK_ERROR_OUT_OF_DATE_KHR) so the program rebuilds it, and until then its buffers keep + * presenting as before: an attached AHardwareBuffer stays with its wl_buffer whatever the mode is. + * + * Fences stay implicit, in the dma-buf itself: Mesa imports the render fence into the dma-buf + * before it commits, we export it as the layer's acquire fence (DMA_BUF_IOCTL_EXPORT_SYNC_FILE), + * and import SurfaceFlinger's release fence back (DMA_BUF_IOCTL_IMPORT_SYNC_FILE) before sending + * wl_buffer.release, so Mesa's acquire (wsi_create_sync_for_dma_buf_wait) waits for the display. + * + * Compositor thread unless noted. Without a usable display layer (sc_layer_available() == 0) the + * global is not created and nothing here runs. + */ +#include +#include +#include + +struct dmabuf_buffer; +struct surface; + +/* ---- compositor.c -> ahb_swapchain.c */ +/* Create the banner_ahb_v1 global (when a display layer is possible at all) and the release queue. */ +void ahb_swapchain_init(struct wl_display *display); +/* Zero-copy on/off, live: sets g_zero_copy, tells every bound client (banner_ahb_v1.mode) and logs + * it (live = 1: a drawer switch mid-session; 0: the launch default). Compositor thread. */ +void ahb_swapchain_set_mode(int on, int live); +/* Milliseconds since a game frame was last put on the layer without a copy (-1 = never). Any thread: + * the drawer's status line. */ +int ahb_swapchain_last_frame_age_ms(void); +/* 1 if the buffer carries an AHardwareBuffer from the game. */ +int ahb_swapchain_has_ahb(const struct dmabuf_buffer *b); +/* Show b (the topmost fullscreen frame, shown by surface s) on the layer without a copy. + * 0 = done (or the same frame is already up), -1 = unavailable (draw the old way). */ +int ahb_swapchain_present(struct dmabuf_buffer *b, struct surface *s, int scene_w, int scene_h); +/* Surface s lets go of buffer b (its wl_buffer `buffer`, NULL if the client destroyed it); paced = + * the limiter's cadence applies. Returns 1 when the buffer is on the layer: the release is sent + * from here once SurfaceFlinger's release fence is known. 0 = the caller releases as usual. */ +int ahb_swapchain_defer_release(struct dmabuf_buffer *b, struct wl_resource *buffer, struct surface *s, int paced); +/* s is being destroyed: forget it (its deferred releases still go out, unpaced). */ +void ahb_swapchain_surface_gone(struct surface *s); +/* Zero-copy frames since the last call (the 10 s summary). */ +unsigned ahb_swapchain_stats_take(void); +/* 1 when the banner_ahb_v1 global exists (a display layer is possible): part of the HDR gate. */ +int ahb_swapchain_advertised(void); +/* The AHARDWAREBUFFER_FORMAT_* of the game's buffer behind b, 0 when it has none. */ +uint32_t ahb_swapchain_ahb_format(const struct dmabuf_buffer *b); + +/* ---- sc_layer.c -> ahb_swapchain.c (SurfaceFlinger's callback thread): the layer let go of the + * buffer behind `token`; release_fd (owned by the callee, -1 = none) signals when the display is + * done reading it. */ +void ahb_swapchain_layer_released(void *token, int release_fd); + +/* ---- ahb_swapchain.c -> compositor.c (hooks) */ +struct dmabuf_buffer *wnc_dmabuf_from_resource(struct wl_resource *buffer); +int wnc_dmabuf_fd(const struct dmabuf_buffer *b); /* plane 0's dma-buf */ +void wnc_dmabuf_size(const struct dmabuf_buffer *b, int *w, int *h); +void **wnc_dmabuf_ahb_slot(struct dmabuf_buffer *b); /* this module's per-buffer state */ +void wnc_dmabuf_ref(struct dmabuf_buffer *b); +void wnc_dmabuf_unref(struct dmabuf_buffer *b); +/* Give a wl_buffer back to its client now (paced = 0) or on the FPS limiter's cadence (s != NULL). + * since_ns = when the compositor let go of it (CLOCK_MONOTONIC; the perf line's release latency). */ +void wnc_release_buffer(struct surface *s, struct wl_resource *buffer, int paced, int64_t since_ns); +/* Redraw the scene on the next tick (the present path may have changed). */ +void wnc_request_redraw(void); +/* "" (program) of the window a surface belongs to, for the log. */ +void wnc_surface_describe(const struct surface *s, char *out, size_t size); +/* The surface's current image description (color_mgmt.h), NULL = none / HDR gate closed. */ +struct wnc_color; +const struct wnc_color *wnc_surface_color(const struct surface *s); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/blend.frag b/app/src/main/cpp/waylandcomp/src/blend.frag new file mode 100644 index 000000000..88739de25 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/blend.frag @@ -0,0 +1,9 @@ +#version 450 +// Wayland buffers carry premultiplied alpha; the pipeline blends ONE, ONE_MINUS_SRC_ALPHA. +layout(binding = 0) uniform sampler2D Surface; +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(Surface, uv); +} diff --git a/app/src/main/cpp/waylandcomp/src/blend.vert b/app/src/main/cpp/waylandcomp/src/blend.vert new file mode 100644 index 000000000..5c87cc352 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/blend.vert @@ -0,0 +1,10 @@ +#version 450 +// One surface as a quad: dst = the target rectangle in NDC, src = the buffer rectangle in UV. +layout(push_constant) uniform PC { vec4 dst; vec4 src; } pc; +layout(location = 0) out vec2 uv; + +void main() { + vec2 t = vec2(gl_VertexIndex & 1, gl_VertexIndex >> 1); + gl_Position = vec4(mix(pc.dst.xy, pc.dst.zw, t), 0.0, 1.0); + uv = mix(pc.src.xy, pc.src.zw, t); +} diff --git a/app/src/main/cpp/waylandcomp/src/blend_frag.h b/app/src/main/cpp/waylandcomp/src/blend_frag.h new file mode 100644 index 000000000..9d2af54a3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/blend_frag.h @@ -0,0 +1,22 @@ + // 1115.1.0 + #pragma once +const uint32_t blend_frag_code[] = { + 0x07230203,0x00010000,0x0008000b,0x00000014,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x00000011,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00050005,0x00000009,0x4374756f,0x726f6c6f,0x00000000,0x00040005,0x0000000d, + 0x66727553,0x00656361,0x00030005,0x00000011,0x00007675,0x00040047,0x00000009,0x0000001e, + 0x00000000,0x00040047,0x0000000d,0x00000021,0x00000000,0x00040047,0x0000000d,0x00000022, + 0x00000000,0x00040047,0x00000011,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021, + 0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006, + 0x00000004,0x00040020,0x00000008,0x00000003,0x00000007,0x0004003b,0x00000008,0x00000009, + 0x00000003,0x00090019,0x0000000a,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, + 0x00000001,0x00000000,0x0003001b,0x0000000b,0x0000000a,0x00040020,0x0000000c,0x00000000, + 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000000,0x00040017,0x0000000f,0x00000006, + 0x00000002,0x00040020,0x00000010,0x00000001,0x0000000f,0x0004003b,0x00000010,0x00000011, + 0x00000001,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005, + 0x0004003d,0x0000000b,0x0000000e,0x0000000d,0x0004003d,0x0000000f,0x00000012,0x00000011, + 0x00050057,0x00000007,0x00000013,0x0000000e,0x00000012,0x0003003e,0x00000009,0x00000013, + 0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/src/blend_pass.c b/app/src/main/cpp/waylandcomp/src/blend_pass.c new file mode 100644 index 000000000..ca717897d --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/blend_pass.c @@ -0,0 +1,315 @@ +/* Alpha composition: one surface drawn source-over onto the scene - see blend_pass.h. Compositor thread. */ +#define _POSIX_C_SOURCE 200809L +#include "blend_pass.h" +#include "vk_loader.h" +#include "vk_present.h" /* wnc_log, vkp_gpu_name */ +#include <string.h> + +/* SPIR-V, pre-compiled (glslangValidator -V blend.vert --vn blend_vert_code, the same for blend.frag) + * and committed next to the GLSL: the NDK build compiles no shaders. */ +#include "blend_vert.h" +#include "blend_frag.h" + +struct pc_blend { float dst[4]; float src[4]; }; + +static VkDevice g_dev; +static int g_ready; /* 0 = not built, 1 = ok, -1 = unavailable (said once) */ +static VkSampler g_sampler; +static VkDescriptorSetLayout g_dsl; +static VkPipelineLayout g_pl; +static VkDescriptorPool g_dpool; +static VkShaderModule g_vert, g_frag; +static unsigned g_frame = 1; + +/* One render pass + pipeline per target format: the screen swapchain's, the scene image's, the HDR + * composition's. */ +#define NFMT 4 +static struct { VkFormat fmt; VkRenderPass rp; VkPipeline pipe; } g_out[NFMT]; + +#define NSRC 32 +static struct { VkImage img; VkImageView view; VkDescriptorSet ds; unsigned frame; } g_src[NSRC]; +#define NDST 12 +static struct { VkImage img; VkFormat fmt; int w, h; VkImageView view; VkFramebuffer fb; unsigned frame; } g_dst[NDST]; + +void blendp_bind_device(VkDevice dev) { g_dev = dev; } + +void blendp_begin_frame(void) { g_frame++; } + +static void src_release(int i) { + if (g_src[i].ds) g_vk.FreeDescriptorSets(g_dev, g_dpool, 1, &g_src[i].ds); + if (g_src[i].view) g_vk.DestroyImageView(g_dev, g_src[i].view, NULL); + memset(&g_src[i], 0, sizeof(g_src[i])); +} + +static void dst_release(int i) { + if (g_dst[i].fb) g_vk.DestroyFramebuffer(g_dev, g_dst[i].fb, NULL); + if (g_dst[i].view) g_vk.DestroyImageView(g_dev, g_dst[i].view, NULL); + memset(&g_dst[i], 0, sizeof(g_dst[i])); +} + +void blendp_forget_image(VkImage img) { + if (!g_dev || !img) return; + for (int i = 0; i < NSRC; i++) if (g_src[i].img == img) src_release(i); + for (int i = 0; i < NDST; i++) if (g_dst[i].img == img) dst_release(i); +} + +static VkShaderModule shader(const uint32_t *code, size_t size) { + VkShaderModule m = VK_NULL_HANDLE; + VkShaderModuleCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = size, .pCode = code}; + if (g_vk.CreateShaderModule(g_dev, &ci, NULL, &m) != VK_SUCCESS) return VK_NULL_HANDLE; + return m; +} + +/* The target keeps what the blits put there (LOAD) and stays in TRANSFER_DST_OPTIMAL for the blits + * that follow. The dependencies order the pass between them. */ +static VkRenderPass make_rp(VkFormat fmt) { + VkAttachmentDescription att = {.format = fmt, .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL}; + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sub = {.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, + .pColorAttachments = &ref}; + VkSubpassDependency deps[2] = { + {.srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}, + {.srcSubpass = 0, .dstSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}}; + VkRenderPassCreateInfo rci = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, + .pAttachments = &att, .subpassCount = 1, .pSubpasses = &sub, + .dependencyCount = 2, .pDependencies = deps}; + VkRenderPass rp = VK_NULL_HANDLE; + if (g_vk.CreateRenderPass(g_dev, &rci, NULL, &rp) != VK_SUCCESS) return VK_NULL_HANDLE; + return rp; +} + +static VkPipeline make_pipe(VkRenderPass rp) { + VkPipelineShaderStageCreateInfo st[2] = { + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = g_vert, .pName = "main"}, + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = g_frag, .pName = "main"}}; + VkPipelineVertexInputStateCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo ia = {.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}; + VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo ds = {.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = 2, .pDynamicStates = dyn}; + VkPipelineViewportStateCreateInfo vp = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, .scissorCount = 1}; + VkPipelineRasterizationStateCreateInfo rs = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .lineWidth = 1.0f}; + VkPipelineMultisampleStateCreateInfo ms = {.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT}; + /* The target's alpha is left alone: the screen is opaque whatever is drawn onto it. */ + VkPipelineColorBlendAttachmentState ba = { + .blendEnable = VK_TRUE, .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT}; + VkPipelineColorBlendStateCreateInfo cb = {.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, .pAttachments = &ba}; + VkGraphicsPipelineCreateInfo pi = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, + .pStages = st, .pVertexInputState = &vi, .pInputAssemblyState = &ia, + .pViewportState = &vp, .pRasterizationState = &rs, .pMultisampleState = &ms, + .pColorBlendState = &cb, .pDynamicState = &ds, .layout = g_pl, + .renderPass = rp, .subpass = 0}; + VkPipeline p = VK_NULL_HANDLE; + if (g_vk.CreateGraphicsPipelines(g_dev, VK_NULL_HANDLE, 1, &pi, NULL, &p) != VK_SUCCESS) return VK_NULL_HANDLE; + return p; +} + +static void objects_destroy(void) { + if (g_vert) g_vk.DestroyShaderModule(g_dev, g_vert, NULL); + if (g_frag) g_vk.DestroyShaderModule(g_dev, g_frag, NULL); + if (g_dpool) g_vk.DestroyDescriptorPool(g_dev, g_dpool, NULL); + if (g_pl) g_vk.DestroyPipelineLayout(g_dev, g_pl, NULL); + if (g_dsl) g_vk.DestroyDescriptorSetLayout(g_dev, g_dsl, NULL); + if (g_sampler) g_vk.DestroySampler(g_dev, g_sampler, NULL); + g_vert = g_frag = VK_NULL_HANDLE; g_dpool = VK_NULL_HANDLE; g_pl = VK_NULL_HANDLE; + g_dsl = VK_NULL_HANDLE; g_sampler = VK_NULL_HANDLE; +} + +static int objects_ensure(void) { + if (g_ready) return g_ready == 1 ? 0 : -1; + g_ready = -1; + if (!g_dev || !g_vk.CreateGraphicsPipelines) goto fail; + VkSamplerCreateInfo sci = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE}; + if (g_vk.CreateSampler(g_dev, &sci, NULL, &g_sampler) != VK_SUCCESS) goto fail; + VkDescriptorSetLayoutBinding b = {.binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT}; + VkDescriptorSetLayoutCreateInfo dli = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, .pBindings = &b}; + if (g_vk.CreateDescriptorSetLayout(g_dev, &dli, NULL, &g_dsl) != VK_SUCCESS) goto fail; + VkPushConstantRange pcr = {.stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(struct pc_blend)}; + VkPipelineLayoutCreateInfo pli = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, + .pSetLayouts = &g_dsl, .pushConstantRangeCount = 1, .pPushConstantRanges = &pcr}; + if (g_vk.CreatePipelineLayout(g_dev, &pli, NULL, &g_pl) != VK_SUCCESS) goto fail; + VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, NSRC}; + VkDescriptorPoolCreateInfo dpi = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, .maxSets = NSRC, + .poolSizeCount = 1, .pPoolSizes = &ps}; + if (g_vk.CreateDescriptorPool(g_dev, &dpi, NULL, &g_dpool) != VK_SUCCESS) goto fail; + if (!(g_vert = shader(blend_vert_code, sizeof(blend_vert_code)))) goto fail; + if (!(g_frag = shader(blend_frag_code, sizeof(blend_frag_code)))) goto fail; + g_ready = 1; + wnc_log("gpu", "alpha composition ready on %s: translucent surfaces are blended over what is under them", + vkp_gpu_name()); + return 0; +fail: + objects_destroy(); + wnc_log("error", "gpu: the alpha composition pass could not be built on this driver; translucent surfaces " + "are drawn opaque this session"); + return -1; +} + +static int out_slot(VkFormat fmt) { + int i; + for (i = 0; i < NFMT && g_out[i].pipe; i++) if (g_out[i].fmt == fmt) return i; + if (i == NFMT) return -1; + VkRenderPass rp = make_rp(fmt); + VkPipeline pipe = rp ? make_pipe(rp) : VK_NULL_HANDLE; + if (!pipe) { + if (rp) g_vk.DestroyRenderPass(g_dev, rp, NULL); + return -1; + } + g_out[i].fmt = fmt; g_out[i].rp = rp; g_out[i].pipe = pipe; + return i; +} + +static VkImageView make_view(VkImage img, VkFormat fmt) { + VkImageView v = VK_NULL_HANDLE; + VkImageViewCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = img, + .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = fmt, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + if (g_vk.CreateImageView(g_dev, &ci, NULL, &v) != VK_SUCCESS) return VK_NULL_HANDLE; + return v; +} + +/* The slot to recycle: a free one, else the one unused for longest - never one this command buffer + * already refers to. -1 = every slot is in this frame. */ +#define OLDEST(arr, n, out) do { \ + unsigned age_ = 0; \ + (out) = -1; \ + for (int k_ = 0; k_ < (n); k_++) { \ + if (!(arr)[k_].img) { (out) = k_; break; } \ + if ((arr)[k_].frame != g_frame && g_frame - (arr)[k_].frame > age_) { age_ = g_frame - (arr)[k_].frame; (out) = k_; } \ + } \ + } while (0) + +static VkDescriptorSet src_ensure(VkImage img, VkFormat fmt, VkImageLayout layout) { + int i; + for (i = 0; i < NSRC; i++) + if (g_src[i].img == img) { g_src[i].frame = g_frame; return g_src[i].ds; } + OLDEST(g_src, NSRC, i); + if (i < 0) return VK_NULL_HANDLE; + src_release(i); + if (!(g_src[i].view = make_view(img, fmt))) return VK_NULL_HANDLE; + VkDescriptorSetAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = g_dpool, .descriptorSetCount = 1, .pSetLayouts = &g_dsl}; + if (g_vk.AllocateDescriptorSets(g_dev, &ai, &g_src[i].ds) != VK_SUCCESS) { + g_src[i].ds = VK_NULL_HANDLE; + src_release(i); + return VK_NULL_HANDLE; + } + VkDescriptorImageInfo ii = {.sampler = g_sampler, .imageView = g_src[i].view, .imageLayout = layout}; + VkWriteDescriptorSet wr = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = g_src[i].ds, .dstBinding = 0, + .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &ii}; + g_vk.UpdateDescriptorSets(g_dev, 1, &wr, 0, NULL); + g_src[i].img = img; g_src[i].frame = g_frame; + return g_src[i].ds; +} + +static VkFramebuffer dst_ensure(VkImage img, VkFormat fmt, int w, int h, VkRenderPass rp) { + int i; + for (i = 0; i < NDST; i++) { + if (g_dst[i].img != img) continue; + if (g_dst[i].fmt == fmt && g_dst[i].w == w && g_dst[i].h == h) { g_dst[i].frame = g_frame; return g_dst[i].fb; } + if (g_dst[i].frame == g_frame) return VK_NULL_HANDLE; + break; + } + if (i == NDST) OLDEST(g_dst, NDST, i); + if (i < 0) return VK_NULL_HANDLE; + dst_release(i); + if (!(g_dst[i].view = make_view(img, fmt))) return VK_NULL_HANDLE; + VkFramebufferCreateInfo fi = {.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = rp, + .attachmentCount = 1, .pAttachments = &g_dst[i].view, .width = (uint32_t)w, + .height = (uint32_t)h, .layers = 1}; + if (g_vk.CreateFramebuffer(g_dev, &fi, NULL, &g_dst[i].fb) != VK_SUCCESS) { + g_dst[i].fb = VK_NULL_HANDLE; + dst_release(i); + return VK_NULL_HANDLE; + } + g_dst[i].img = img; g_dst[i].fmt = fmt; g_dst[i].w = w; g_dst[i].h = h; g_dst[i].frame = g_frame; + return g_dst[i].fb; +} + +static void src_layout(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkImageLayout to) { + int sampling = to == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + VkImageMemoryBarrier b = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = from, .newLayout = to, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = img, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .srcAccessMask = sampling ? VK_ACCESS_TRANSFER_READ_BIT : VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = sampling ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT}; + g_vk.CmdPipelineBarrier(cmd, sampling ? VK_PIPELINE_STAGE_TRANSFER_BIT : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + sampling ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &b); +} + +int blendp_draw(VkCommandBuffer cmd, VkImage src, VkFormat src_fmt, int src_w, int src_h, int src_general, + VkImage dst, VkFormat dst_fmt, int dst_w, int dst_h, const VkImageBlit *blit) { + if (!src || !dst || src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0 || objects_ensure() != 0) return -1; + int slot = out_slot(dst_fmt); + if (slot < 0) return -1; + VkImageLayout sampled = src_general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + VkDescriptorSet ds = src_ensure(src, src_fmt, sampled); + if (!ds) return -1; + VkFramebuffer fb = dst_ensure(dst, dst_fmt, dst_w, dst_h, g_out[slot].rp); + if (!fb) return -1; + + const VkOffset3D *d = blit->dstOffsets, *s = blit->srcOffsets; + int x0 = d[0].x < d[1].x ? d[0].x : d[1].x, x1 = d[0].x < d[1].x ? d[1].x : d[0].x; + int y0 = d[0].y < d[1].y ? d[0].y : d[1].y, y1 = d[0].y < d[1].y ? d[1].y : d[0].y; + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 > dst_w) x1 = dst_w; + if (y1 > dst_h) y1 = dst_h; + if (x1 <= x0 || y1 <= y0) return 0; + + struct pc_blend pc = { + .dst = {2.0f * d[0].x / dst_w - 1.0f, 2.0f * d[0].y / dst_h - 1.0f, + 2.0f * d[1].x / dst_w - 1.0f, 2.0f * d[1].y / dst_h - 1.0f}, + .src = {(float)s[0].x / src_w, (float)s[0].y / src_h, (float)s[1].x / src_w, (float)s[1].y / src_h}}; + + if (!src_general) src_layout(cmd, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, sampled); + VkRenderPassBeginInfo rpi = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = g_out[slot].rp, + .framebuffer = fb, + .renderArea = {{x0, y0}, {(uint32_t)(x1 - x0), (uint32_t)(y1 - y0)}}}; + VkViewport vp = {0, 0, (float)dst_w, (float)dst_h, 0, 1}; + g_vk.CmdBeginRenderPass(cmd, &rpi, VK_SUBPASS_CONTENTS_INLINE); + g_vk.CmdSetViewport(cmd, 0, 1, &vp); + g_vk.CmdSetScissor(cmd, 0, 1, &rpi.renderArea); + g_vk.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_out[slot].pipe); + g_vk.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pl, 0, 1, &ds, 0, NULL); + g_vk.CmdPushConstants(cmd, g_pl, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc); + g_vk.CmdDraw(cmd, 4, 1, 0, 0); + g_vk.CmdEndRenderPass(cmd); + if (!src_general) src_layout(cmd, src, sampled, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + return 0; +} diff --git a/app/src/main/cpp/waylandcomp/src/blend_pass.h b/app/src/main/cpp/waylandcomp/src/blend_pass.h new file mode 100644 index 000000000..2faade252 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/blend_pass.h @@ -0,0 +1,28 @@ +#ifndef BLEND_PASS_H +#define BLEND_PASS_H +/* + * Alpha composition. The scene is composed with blits, which replace what is under them; a surface + * with translucent pixels (gamescope's overlay and notification planes over a game, a toolkit's + * shadowed popup) is drawn through this pass instead, source-over with premultiplied alpha. + * + * Compositor thread. Objects are built on the first use; if the driver refuses them the pass is + * unavailable for the session (logged once) and the caller keeps blitting. + */ +#define VK_USE_PLATFORM_ANDROID_KHR +#include <vulkan/vulkan.h> + +/* Called from dev_init (device up). */ +void blendp_bind_device(VkDevice dev); +/* A source or target image is about to be destroyed: drop what is cached for it. Frames are fenced, + * so nothing submitted still uses it. */ +void blendp_forget_image(VkImage img); +/* A new command buffer is being recorded: what the previous one used may be recycled again. */ +void blendp_begin_frame(void); + +/* Record one surface over `dst`. `dst` is in TRANSFER_DST_OPTIMAL before and after. `src` was created + * with SAMPLED usage and is in GENERAL (src_general) or TRANSFER_SRC_OPTIMAL, and is left there. + * `blit` is the draw as the blit path maps it. 0 = recorded, -1 = unavailable (nothing recorded). */ +int blendp_draw(VkCommandBuffer cmd, VkImage src, VkFormat src_fmt, int src_w, int src_h, int src_general, + VkImage dst, VkFormat dst_fmt, int dst_w, int dst_h, const VkImageBlit *blit); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/blend_vert.h b/app/src/main/cpp/waylandcomp/src/blend_vert.h new file mode 100644 index 000000000..f402c6a92 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/blend_vert.h @@ -0,0 +1,55 @@ + // 1115.1.0 + #pragma once +const uint32_t blend_vert_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000003a,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0008000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000c,0x0000001b,0x00000031, + 0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00030005, + 0x00000009,0x00000074,0x00060005,0x0000000c,0x565f6c67,0x65747265,0x646e4978,0x00007865, + 0x00060005,0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019, + 0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006,0x00000019,0x00000001,0x505f6c67, + 0x746e696f,0x657a6953,0x00000000,0x00070006,0x00000019,0x00000002,0x435f6c67,0x4470696c, + 0x61747369,0x0065636e,0x00070006,0x00000019,0x00000003,0x435f6c67,0x446c6c75,0x61747369, + 0x0065636e,0x00030005,0x0000001b,0x00000000,0x00030005,0x0000001d,0x00004350,0x00040006, + 0x0000001d,0x00000000,0x00747364,0x00040006,0x0000001d,0x00000001,0x00637273,0x00030005, + 0x0000001f,0x00006370,0x00030005,0x00000031,0x00007675,0x00040047,0x0000000c,0x0000000b, + 0x0000002a,0x00030047,0x00000019,0x00000002,0x00050048,0x00000019,0x00000000,0x0000000b, + 0x00000000,0x00050048,0x00000019,0x00000001,0x0000000b,0x00000001,0x00050048,0x00000019, + 0x00000002,0x0000000b,0x00000003,0x00050048,0x00000019,0x00000003,0x0000000b,0x00000004, + 0x00030047,0x0000001d,0x00000002,0x00050048,0x0000001d,0x00000000,0x00000023,0x00000000, + 0x00050048,0x0000001d,0x00000001,0x00000023,0x00000010,0x00040047,0x00000031,0x0000001e, + 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, + 0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,0x00040020,0x00000008,0x00000007, + 0x00000007,0x00040015,0x0000000a,0x00000020,0x00000001,0x00040020,0x0000000b,0x00000001, + 0x0000000a,0x0004003b,0x0000000b,0x0000000c,0x00000001,0x0004002b,0x0000000a,0x0000000e, + 0x00000001,0x00040017,0x00000015,0x00000006,0x00000004,0x00040015,0x00000016,0x00000020, + 0x00000000,0x0004002b,0x00000016,0x00000017,0x00000001,0x0004001c,0x00000018,0x00000006, + 0x00000017,0x0006001e,0x00000019,0x00000015,0x00000006,0x00000018,0x00000018,0x00040020, + 0x0000001a,0x00000003,0x00000019,0x0004003b,0x0000001a,0x0000001b,0x00000003,0x0004002b, + 0x0000000a,0x0000001c,0x00000000,0x0004001e,0x0000001d,0x00000015,0x00000015,0x00040020, + 0x0000001e,0x00000009,0x0000001d,0x0004003b,0x0000001e,0x0000001f,0x00000009,0x00040020, + 0x00000020,0x00000009,0x00000015,0x0004002b,0x00000006,0x00000029,0x00000000,0x0004002b, + 0x00000006,0x0000002a,0x3f800000,0x00040020,0x0000002e,0x00000003,0x00000015,0x00040020, + 0x00000030,0x00000003,0x00000007,0x0004003b,0x00000030,0x00000031,0x00000003,0x00050036, + 0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008, + 0x00000009,0x00000007,0x0004003d,0x0000000a,0x0000000d,0x0000000c,0x000500c7,0x0000000a, + 0x0000000f,0x0000000d,0x0000000e,0x0004006f,0x00000006,0x00000010,0x0000000f,0x0004003d, + 0x0000000a,0x00000011,0x0000000c,0x000500c3,0x0000000a,0x00000012,0x00000011,0x0000000e, + 0x0004006f,0x00000006,0x00000013,0x00000012,0x00050050,0x00000007,0x00000014,0x00000010, + 0x00000013,0x0003003e,0x00000009,0x00000014,0x00050041,0x00000020,0x00000021,0x0000001f, + 0x0000001c,0x0004003d,0x00000015,0x00000022,0x00000021,0x0007004f,0x00000007,0x00000023, + 0x00000022,0x00000022,0x00000000,0x00000001,0x00050041,0x00000020,0x00000024,0x0000001f, + 0x0000001c,0x0004003d,0x00000015,0x00000025,0x00000024,0x0007004f,0x00000007,0x00000026, + 0x00000025,0x00000025,0x00000002,0x00000003,0x0004003d,0x00000007,0x00000027,0x00000009, + 0x0008000c,0x00000007,0x00000028,0x00000001,0x0000002e,0x00000023,0x00000026,0x00000027, + 0x00050051,0x00000006,0x0000002b,0x00000028,0x00000000,0x00050051,0x00000006,0x0000002c, + 0x00000028,0x00000001,0x00070050,0x00000015,0x0000002d,0x0000002b,0x0000002c,0x00000029, + 0x0000002a,0x00050041,0x0000002e,0x0000002f,0x0000001b,0x0000001c,0x0003003e,0x0000002f, + 0x0000002d,0x00050041,0x00000020,0x00000032,0x0000001f,0x0000000e,0x0004003d,0x00000015, + 0x00000033,0x00000032,0x0007004f,0x00000007,0x00000034,0x00000033,0x00000033,0x00000000, + 0x00000001,0x00050041,0x00000020,0x00000035,0x0000001f,0x0000000e,0x0004003d,0x00000015, + 0x00000036,0x00000035,0x0007004f,0x00000007,0x00000037,0x00000036,0x00000036,0x00000002, + 0x00000003,0x0004003d,0x00000007,0x00000038,0x00000009,0x0008000c,0x00000007,0x00000039, + 0x00000001,0x0000002e,0x00000034,0x00000037,0x00000038,0x0003003e,0x00000031,0x00000039, + 0x000100fd,0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/src/color_mgmt.h b/app/src/main/cpp/waylandcomp/src/color_mgmt.h new file mode 100644 index 000000000..94ce03412 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/color_mgmt.h @@ -0,0 +1,150 @@ +#ifndef WNC_COLOR_H +#define WNC_COLOR_H +/* + * HDR10 output on the Wayland backend (opt-in) โ€” the compositor half of HDR_RECON.md Phase A (round 1) + * plus HDR-aware composition (round 2, hdr_compose.h), implemented in wl_color_mgmt.c. The game half + * already ships: Mesa's Wayland WSI in our Turnip is a wp_color_manager_v1 client and exposes + * VK_COLOR_SPACE_HDR10_ST2084_EXT as soon as a compositor advertises BT.2020 + ST 2084; DXVK takes it + * with DXVK_HDR=1 (the app exports it when the setting is on). + * + * THE GATE. Nothing here exists for a session unless all of these hold when the compositor starts: + * - the container's / game's "HDR output" setting is on, or BANNER_WAYLAND_HDR=1 in their + * environment (which overrides the setting either way); + * - the display the game is on lists HDR10 among its supported HDR types (read by the app from + * android.view.Display โ€” a property of the connector, not of the device); + * - display layers with dataspace control (ASurfaceTransaction_setBufferDataSpace, Android 10+) + * and the zero-copy global (banner_ahb_v1): the HDR frame (or the composed HDR picture) is shown + * on the game's own display layer, tagged BT2020_PQ. + * In game, the drawer's HDR output switch (wnc_color_set_output) flips between that and the same + * frames tone-mapped to SDR, live; the gate itself never changes during a session. + * A closed gate advertises nothing โ€” no colour-management global, no 10-bit dma-buf formats โ€” so the + * session is byte-for-byte what it was before this file existed, and the session log says why. + * BANNER_WAYLAND_HDR=force skips the display check only (testing the negotiation on an SDR panel; + * SurfaceFlinger then tone-maps the layer, and says so in its composition type). + * + * WHAT AN OPEN GATE OFFERS (exactly the subset Mesa binds, HDR_RECON.md ยง2.2): wp_color_manager_v1 + * version 1 with the perceptual intent, the parametric creator + mastering-display metadata, + * BT.2020 primaries and the ST 2084 (PQ) transfer function; zwp_linux_dmabuf_v1 adds AB30/XB30 + * (A2B10G10R10, the only 10-bit layout our zero-copy WSI can put in a gralloc buffer). A surface's + * image description is double-buffered on wl_surface.commit; its frames go onto the game's display + * layer tagged BT2020_PQ with the game's SMPTE 2086 / CTA-861.3 metadata (sc_layer.c). + * + * Compositor thread unless noted. + */ +#include <stdint.h> +#include <wayland-server.h> + +/* ADataSpace values (NDK <android/data_space.h>) the layer path emits. */ +#define WNC_ADATASPACE_UNKNOWN 0 +#define WNC_ADATASPACE_BT2020_PQ 163971072 /* STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL = 0x09c60000 */ +#define WNC_ADATASPACE_BT2020_HLG 168165376 /* STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL */ + +/* One image description as the program made it, and what it becomes on a display layer. Immutable: + * a changed description is a new record with a new identity. */ +struct wnc_color { + uint32_t identity; /* the id the `ready` event carried (never 0) */ + uint32_t primaries, tf; /* wp_color_manager_v1 named values */ + int32_t dataspace; /* ADataSpace on a display layer; 0 = none (treated as sRGB) */ + int has_st2086; /* SMPTE ST 2086: mastering display primaries + luminance */ + float red[2], green[2], blue[2], white[2]; /* CIE 1931 xy */ + float max_lum, min_lum; /* nits */ + int has_cta861; /* CTA-861.3 */ + float max_cll, max_fall; /* nits; 0 = not given */ + char text[256]; /* the session log's one-line summary of it */ +}; + +/* ---- app -> compositor (JNI, any thread) */ +/* mode 0 = off, 1 = BANNER_WAYLAND_HDR=1, 2 = BANNER_WAYLAND_HDR=force (testing). source names where + * the switch came from; dxvk_hdr = DXVK_HDR=1 is in the game's environment; zero_copy_forced = the + * app turned zero-copy presentation on for this session because HDR needs it. Before the start. */ +void wnc_color_set_request(int mode, const char *source, int dxvk_hdr, int zero_copy_forced); +/* The display the game is on, as android.view.Display reports it. Before the start (it feeds the + * gate) and again whenever it changes (logged; the gate is decided once per session). */ +void wnc_color_set_display(int id, const char *name, const char *formats, int hdr10, float max_lum, + float max_avg, float min_lum, int ratio_available, float ratio, int api); +/* One reading of Display.getHdrSdrRatio() (API 34+; < 0 = not available). listener = it came from + * the display's ratio listener rather than the app's periodic sampler. */ +void wnc_color_ratio_sample(float ratio, int listener); +/* Milliseconds since an HDR frame last went onto a display layer; -1 = none this session. */ +int wnc_color_last_frame_age_ms(void); +/* -1 = the compositor has not decided yet, 0 = closed, 1 = open. */ +int wnc_color_gate_state(void); +/* The session is ending: write the summary line ("HDR on screen: โ€ฆ"). */ +void wnc_color_session_end(void); +/* 1 while HDR frames are really on screen: a frame tagged BT2020_PQ reached a display layer or the HDR + * swapchain in the last 1.5 s AND, where the display reports an HDR/SDR ratio, its last reading is + * above 1.01 (the HUD badge). */ +int wnc_color_hdr_on_screen(void); +/* The HUD / drawer state: 0 = no HDR frames on screen (or not confirmed yet), 1 = HDR frames on screen + * with headroom (as wnc_color_hdr_on_screen), 2 = HDR frames on screen but the display has given + * them NO headroom (ratio <= 1.01) for 5 s or more - the brightness slider at maximum, or a screen + * recording (Android turns HDR headroom off while the screen is recorded). */ +int wnc_color_hdr_state(void); +/* Nits SDR content is placed at inside an HDR picture (default 203, BT.2408); any thread. */ +void wnc_color_set_sdr_white(float nits); +float wnc_color_sdr_white(void); +/* The HDR session was asked for (BANNER_WAYLAND_HDR / the setting resolved on or force) โ€” known before + * the compositor starts, so the Vulkan instance can enable VK_EXT_swapchain_colorspace for it. */ +int wnc_color_requested(void); +/* The drawer's live "HDR output" switch, per session, starting ON. On = HDR frames go to the display as + * HDR; off = the SAME frames are composed tone-mapped to SDR (hdr_compose.h) - the game is told nothing + * and keeps rendering HDR (DXVK_HDR and the colour-manager offer were decided at launch). Compositor + * thread (the app posts it through wnc_host_hdr_output); logs the flip and redraws. */ +void wnc_color_set_output(int on); +/* 1 = HDR output on (any thread). */ +int wnc_color_output(void); +/* 1 while an HDR game's frames are being shown tone-mapped to SDR (the last one < 1.5 s ago); any thread. */ +int wnc_color_tonemapped_on_screen(void); +/* Device evidence beside the HDR/SDR headroom (the app, non-root APIs; any thread; HDR sessions only): + * PowerManager thermal status (0..6, -1 unknown) + getThermalHeadroom(10) (< 0 = not available), + * Settings.System SCREEN_BRIGHTNESS (0..255, -1 unknown) + SCREEN_BRIGHTNESS_MODE (1 auto, 0 manual). + * Changes of status and brightness are logged; every no-headroom line, the 10 s line and the verdict + * carry the last values. */ +void wnc_color_env_sample(int thermal, float headroom, int brightness, int bmode); + +/* ---- the explicit HDR headroom request (Android 15+: ASurfaceTransaction_setDesiredHdrHeadroom on the game + * layer, SurfaceView.setDesiredHdrHeadroom on the screen surface). Some phones only boost HDR when asked. */ +/* The ratio to ask for an HDR frame of `c`: content peak (max CLL, else mastering max, else the display's + * peak) / SDR white (203), capped at the display's highest ratio when it reports one; 0 = ask nothing + * (not HDR, or the display reports it cannot boost). `why` gets the one-line reason. Any thread. */ +float wnc_color_desired_headroom(const struct wnc_color *c, char *why, size_t n); +/* What was last asked for (> 0 the ratio, 0 nothing, -1 the API is missing): for the no-headroom lines. */ +void wnc_color_note_headroom_request(float ratio); +/* Display.getHighestHdrSdrRatio() (Android 16+; <= 0 = not reported). Any thread; logged when it arrives. */ +void wnc_color_set_highest_ratio(float ratio); +/* The headroom the SCREEN surface should ask for (frames through the HDR10 swapchain in the last 1.5 s), + * 0 = none; `why` gets the reason. The app polls it and applies it. Any thread. */ +float wnc_color_screen_headroom(char *why, size_t n); + +/* ---- compositor.c -> here */ +/* Decide the gate (call after ahb_swapchain_init) and create the global when it is open. */ +void wnc_color_init(struct wl_display *display); +/* The gate is open: the 10-bit dma-buf formats may be advertised, descriptions may exist. */ +int wnc_color_hdr_open(void); +/* wl_surface.commit: the pending image description (if any request touched it) becomes current. */ +void wnc_color_commit(struct wl_resource *surface); +/* The surface's current image description; NULL = none (sRGB, today's default). */ +const struct wnc_color *wnc_color_of(struct wl_resource *surface); +/* A program disconnected: if it presented HDR, its part of the summary is written now. */ +void wnc_color_client_gone(struct wl_client *client); +/* The 10 s summary tick (one `color` line when anything HDR happened in the window). */ +void wnc_color_stats_tick(void); +/* A frame of an HDR-described surface went through the compositor's own 8-bit sRGB pass this scene + * (who = the window, reason = why it did not get the display layer). Shown untone-mapped. */ +void wnc_color_frame_copied(const char *who, const char *reason); + +/* ---- sc_layer.c / compositor.c -> here: a NEW frame of an HDR-described surface was shown, and how */ +enum wnc_hdr_path { + WNC_HDR_ZERO_COPY = 0, /* the game's own gralloc buffer on its display layer, tagged */ + WNC_HDR_LAYER_COPY, /* one 8-bit copy of the game's frame on its layer, tagged */ + WNC_HDR_COMPOSED, /* the whole scene composed into one PQ picture on the game layer (hdr_compose.h) */ + WNC_HDR_SWAPCHAIN, /* composed into PQ and presented through an HDR10 swapchain (frame generation) */ + WNC_HDR_TONEMAPPED, /* composed and tone-mapped to SDR (HDR output switched off, or a present that + * cannot carry HDR) - NOT HDR */ +}; +/* ahb_format = the buffer's AHARDWAREBUFFER_FORMAT_* where one exists (0 otherwise). */ +void wnc_color_frame_shown(const struct wnc_color *c, int path, uint32_t ahb_format); +/* "RGBA1010102 (10-bit)" etc. for an AHARDWAREBUFFER_FORMAT_* value (static buffer). */ +const char *wnc_ahb_format_name(uint32_t format); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/compositor.c b/app/src/main/cpp/waylandcomp/src/compositor.c new file mode 100644 index 000000000..6540c8252 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/compositor.c @@ -0,0 +1,3423 @@ +/* + * The embedded compositor of the Wayland display path. + * + * Clients are the Wine processes of a container (winewayland.drv) and, through them, + * Mesa's Vulkan WSI. Each process is its own client, so a Windows virtual desktop is + * described with banner_desktop_v1: the desktop owner marks one surface as the desktop, + * and every process reports its top-level windows' desktop positions and the Windows + * stacking order. + * + * The scene is the desktop surface at the bottom, then every mapped xdg_toplevel at its + * reported position in stacking order, each with its subsurface tree (a game's Vulkan + * swapchain is a subsurface of its window) and wp_viewport crop/scale applied. It is + * redrawn once per event-loop pass after any commit or layout change, stretched to the + * Android surface. + * + * Input goes to the desktop surface in desktop coordinates: the Wine server routes it to + * the window under the pointer (or the focus window) in whichever process owns it, the + * same as winex11's virtual desktop. Without a desktop (a client that doesn't speak + * banner_desktop_v1) the topmost window under the pointer gets it. + */ +#define _GNU_SOURCE 1 +#define _POSIX_C_SOURCE 200809L +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdint.h> +#include <fcntl.h> +#include <time.h> +#include <stdarg.h> +#include <errno.h> +#include <sys/timerfd.h> +#include <sys/syscall.h> +#include <sys/sysmacros.h> +#include <android/log.h> +#include <wayland-server.h> + +#include "xdg-shell-server-protocol.h" +#include "linux-dmabuf-v1-server-protocol.h" +#include "sc_layer.h" +#include "ahb_swapchain.h" +#include "viewporter-server-protocol.h" +#include "desktop-v1-server-protocol.h" +#include "presentation-time-server-protocol.h" +#include "pointer-constraints-unstable-v1-server-protocol.h" +#include "relative-pointer-unstable-v1-server-protocol.h" +#include "vk_present.h" +#include "framegen_bridge.h" +#include <pthread.h> +#include "effects_chain.h" +#include "desktop_ext.h" +#include "color_mgmt.h" + +#define WLOGI(...) __android_log_print(ANDROID_LOG_INFO, "WNWayland", __VA_ARGS__) +#define WLOGE(...) __android_log_print(ANDROID_LOG_ERROR, "WNWayland", __VA_ARGS__) + +/* ------------------------------------------------------------------ session log + * One readable, time-stamped file per Wayland session in Download/Wayland-logs, also + * mirrored to logcat: which programs connect, the desktop, windows opening and closing, + * Vulkan frames arriving, a frame-rate summary every 10 seconds, and errors. */ + +static FILE *g_log; +static char g_log_dir[512]; /* set by the app (nativeSetLogDir); empty = logcat only */ + +/* Lines logged before the session file exists are kept here and written into it, in order, the + * moment it opens โ€” the compositor runs on its own thread, so anything the app reports as that + * thread starts (the display's HDR capability, for one) would otherwise only reach logcat. The + * lock also serialises the file writes, which now come from the app's threads too. */ +#define PRELOG_MAX 32 +static pthread_mutex_t g_log_lock = PTHREAD_MUTEX_INITIALIZER; +static char *g_prelog[PRELOG_MAX]; +static int g_prelog_n; + +void wnc_log(const char *tag, const char *fmt, ...) { + char msg[512]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + WLOGI("[%s] %s", tag, msg); + struct timespec ts; + struct tm tm; + clock_gettime(CLOCK_REALTIME, &ts); + localtime_r(&ts.tv_sec, &tm); + char line[640]; + snprintf(line, sizeof(line), "%02d:%02d:%02d.%03ld %-9s %s\n", tm.tm_hour, tm.tm_min, tm.tm_sec, + ts.tv_nsec / 1000000, tag, msg); + pthread_mutex_lock(&g_log_lock); + if (g_log) fputs(line, g_log); + else if (g_prelog_n < PRELOG_MAX) g_prelog[g_prelog_n++] = strdup(line); + pthread_mutex_unlock(&g_log_lock); +} + +void wnc_wayland_set_log_dir(const char *dir) { + pthread_mutex_lock(&g_log_lock); + snprintf(g_log_dir, sizeof(g_log_dir), "%s", dir ? dir : ""); + pthread_mutex_unlock(&g_log_lock); +} + +static void close_session_log(void) { + pthread_mutex_lock(&g_log_lock); + if (g_log) fclose(g_log); + g_log = NULL; + for (int i = 0; i < g_prelog_n; i++) { free(g_prelog[i]); g_prelog[i] = NULL; } + g_prelog_n = 0; + pthread_mutex_unlock(&g_log_lock); +} + +static void open_session_log(void) { + char dir[512], path[640], stamp[32]; + time_t now = time(NULL); + struct tm tm; + FILE *f; + + pthread_mutex_lock(&g_log_lock); + snprintf(dir, sizeof(dir), "%s", g_log_dir); + pthread_mutex_unlock(&g_log_lock); + if (!dir[0]) return; + localtime_r(&now, &tm); + strftime(stamp, sizeof(stamp), "%Y-%m-%d_%H-%M-%S", &tm); + if (mkdir(dir, 0775) != 0 && errno != EEXIST) + WLOGE("can't create %s: %s", dir, strerror(errno)); + snprintf(path, sizeof(path), "%s/wayland-%s.log", dir, stamp); + if (!(f = fopen(path, "w"))) { + WLOGE("can't open session log %s: %s", path, strerror(errno)); + return; + } + setvbuf(f, NULL, _IOLBF, 0); + strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", &tm); + fprintf(f, + "WinNative Wayland session\n" + "=========================\n" + "Started %s\n" + "Display Wayland: Windows programs draw through the embedded compositor\n" + " No X server is used for this session.\n" + "Log %s\n\n" + "Time Area Event\n" + "------------ -------- -----------------------------------------------------\n", + stamp, path); + /* Publish the file and flush anything logged before it existed, in one critical section, so a + * line from another thread can never land ahead of the header or be dropped between the two. */ + pthread_mutex_lock(&g_log_lock); + for (int i = 0; i < g_prelog_n; i++) { fputs(g_prelog[i], f); free(g_prelog[i]); g_prelog[i] = NULL; } + g_prelog_n = 0; + g_log = f; + pthread_mutex_unlock(&g_log_lock); + WLOGI("session log: %s", path); +} + +/* At most ~10 lines a second for chatty events (window moves), so a drag can't flood logcat. */ +static int log_budget(void) { + static struct timespec window; + static int used; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_sec != window.tv_sec) { window = now; used = 0; } + return used++ < 10; +} + +#ifndef BTN_LEFT +#define BTN_LEFT 0x110 /* linux/input-event-codes.h */ +#endif +#ifndef BTN_RIGHT +#define BTN_RIGHT 0x111 +#endif + +/* Java sends pointer coordinates in this space, covering the whole output surface. */ +#define INPUT_SPACE_W 1920 +#define INPUT_SPACE_H 1080 + +static struct wl_display *g_display; + +/* Which program each Wayland client is (from /proc/<pid>/cmdline), for the session log. */ +struct client_info { + struct wl_client *client; + struct wl_listener destroy; + pid_t pid; + char name[64]; + /* An OpenGL program whose EGL gave up on the GPU: it asked for dma-buf feedback (EGL's + * Wayland GPU path always does), never made a dma-buf buffer, and draws wl_shm frames. */ + unsigned asked_feedback : 1, shm_gl_said : 1; + /* It has declared an opaque region, so where it declares none its alpha channel is meant. Programs + * that never do (Wine's windows, whose alpha is whatever the game left there) stay opaque. */ + unsigned declares_opaque : 1; + unsigned dmabuf_buffers, shm_frames; + struct client_info *next; +}; +static struct client_info *g_clients; + +static struct client_info *client_info_of(struct wl_client *client) { + for (struct client_info *ci = g_clients; ci; ci = ci->next) + if (ci->client == client) return ci; + return NULL; +} + +static const char *client_name(struct wl_client *client) { + struct client_info *ci = client_info_of(client); + return ci ? ci->name : "a program"; +} + +static void on_client_destroyed(struct wl_listener *l, void *data) { + struct client_info *ci = wl_container_of(l, ci, destroy), **pp; + wnc_log("program", "disconnected: %s (pid %d)", ci->name, (int)ci->pid); + wnc_color_client_gone(ci->client); /* HDR summary for a program that presented HDR (gate open only) */ + for (pp = &g_clients; *pp; pp = &(*pp)->next) + if (*pp == ci) { *pp = ci->next; break; } + free(ci); +} + +static void on_client_created(struct wl_listener *l, void *data) { + struct wl_client *client = data; + struct client_info *ci = calloc(1, sizeof(*ci)); + char path[64], buf[256]; + uid_t uid; gid_t gid; + int fd; + ssize_t n; + + if (!ci) return; + ci->client = client; + wl_client_get_credentials(client, &ci->pid, &uid, &gid); + snprintf(ci->name, sizeof(ci->name), "pid %d", (int)ci->pid); + snprintf(path, sizeof(path), "/proc/%d/cmdline", (int)ci->pid); + if ((fd = open(path, O_RDONLY | O_CLOEXEC)) >= 0) { + if ((n = read(fd, buf, sizeof(buf) - 1)) > 0) { + char *base = buf, *p; + buf[n] = 0; /* argv[0] only; Wine puts the Windows program path there */ + for (p = buf; *p; p++) if (*p == '\\' || *p == '/') base = p + 1; + if (*base) snprintf(ci->name, sizeof(ci->name), "%s", base); + } + close(fd); + } + ci->destroy.notify = on_client_destroyed; + wl_client_add_destroy_listener(client, &ci->destroy); + ci->next = g_clients; + g_clients = ci; + wnc_log("program", "connected over Wayland: %s (pid %d)", ci->name, (int)ci->pid); +} + +/* ------------------------------------------------------------------ surfaces */ + +enum surface_role { ROLE_NONE, ROLE_TOPLEVEL, ROLE_SUBSURFACE, ROLE_DESKTOP }; + +struct surface { + struct wl_resource *resource; + struct wl_list link; /* g_surfaces */ + + /* Pending state, applied on commit. */ + struct wl_resource *pending_buffer; + struct wl_listener pending_buffer_destroy; + int pending_attach; + int pending_src_set, pending_dst_set; + float pending_src[4]; /* x, y, w, h; w < 0 = unset */ + int pending_dst[2]; /* w, h; w < 0 = unset */ + struct wl_list pending_frames; + struct wl_list pending_feedback; /* wp_presentation feedback asked for before commit */ + + /* Current content. */ + struct vkp_image *shm_img; /* our copy of the last wl_shm buffer */ + struct wl_resource *dmabuf; /* current dmabuf wl_buffer (NULL once the client destroyed it) */ + struct dmabuf_buffer *dmabuf_buf; /* its imported image, kept until replaced or the surface goes */ + struct wl_listener dmabuf_destroy; + int buf_w, buf_h, has_content; + int buf_alpha; /* the buffer's format has an alpha channel */ + int opaque[4], pending_opaque[4], pending_opaque_set; /* x, y, w, h; w = 0: none */ + int src_set, dst_set; + float src[4]; + int dst[2]; + struct wl_list frames; /* frame callbacks for the next redraw */ + struct wl_list feedback; /* presentation feedback for the current content */ + int drawn; /* part of the last rendered scene */ + int64_t next_release_ns; /* FPS limiter: when this surface's last buffer goes back */ + int releases_pending; /* FPS limiter: buffers of this surface still to be released */ + + enum surface_role role; + struct wl_resource *xdg_surface, *xdg_toplevel; + struct wl_resource *viewport; + + /* Toplevel placement on the scene. */ + int mapped; /* in g_toplevels */ + struct wl_list toplevel_link; + int x, y, placed; + uint32_t hwnd; + + /* Subsurface tree. */ + struct surface *parent; + struct wl_resource *subsurface; + struct wl_list children; /* bottom to top */ + struct wl_list child_link; + int sub_x, sub_y, sub_pending_x, sub_pending_y, sub_pending; + int sub_sync; /* wl_subsurface mode: its frames reach the screen with the parent's commit */ + int hud_frame; /* a GPU frame arrived in this window since the HUD last counted one */ + int below_parent; + int fullscreen; /* xdg_toplevel.set_fullscreen: no client decorations */ + + char *title; /* xdg_toplevel title, for the session log */ + int announced_vulkan; /* logged its first dmabuf frame */ + uint32_t hdr_fmt_logged; /* HDR session: the buffer format last named in the log */ +}; + +static struct wl_list g_surfaces; /* every surface */ +static struct wl_list g_toplevels; /* mapped toplevels, bottom to top */ +static struct surface *g_desktop; +static uint32_t *g_zorder; /* last reported Windows stacking order, top first */ +static size_t g_zorder_count; +static struct wl_event_source *g_render_idle, *g_frame_timer; + +/* Rendering is driven by the screen's vsync (Java's Choreographer ticks, nativeVsync): one scene + * per refresh showing the newest buffers, so the event loop is never parked in the swapchain and + * clients get their buffers back as soon as they're replaced. Without ticks (an older app, no + * window) a fallback timer renders instead. */ +static int g_dirty; /* something changed since the last render */ +static int64_t g_last_vsync_ns; /* last tick, 0 = none yet */ +static int64_t g_refresh_ns = 16666667; /* screen refresh interval, from the ticks */ +static struct wl_event_source *g_fallback_timer; +static int g_fallback_armed; + +/* FPS limiter (the in-game drawer's): frames per second, 0 = unlimited. Set from the app thread, + * read here. Like the X11 path, which delays the "your buffer is free" notice, the limit paces + * when a replaced buffer is released to its client, so the game itself slows to the cap. */ +volatile int g_fps_limit; + +/* Shortcut launches: explorer's windows (the desktop, taskbar, Start menu) aren't drawn, matching + * the X11 renderer's unviewable "explorer.exe". They still exist for input routing. */ +volatile int g_hide_shell; + +/* Experimental layer mode (BANNER_WAYLAND_ZERO_COPY=1 in the container's environment): a single + * fullscreen window is shown on its own Android layer (sc_layer.c) instead of being blitted into the + * screen swapchain. Set from the app before the compositor starts; read per frame. */ +volatile int g_zero_copy; +static int g_zero_copy_paused; /* effects hold the layer path off (render_scene) */ +static int g_zero_copy_fx_skip_said; + +/* Compressed (UBWC) game buffers: zwp_linux_dmabuf_v1 advertises DRM_FORMAT_MOD_QCOM_COMPRESSED next + * to LINEAR for every format the renderer's own driver can import that way, so Turnip's Wayland WSI + * in the game allocates UBWC swapchain images instead of resolving each frame to a linear copy. + * BANNER_WAYLAND_UBWC=0 in the container's environment turns the advertisement off (A/B switch). Set + * from the app before the compositor starts; read at every zwp_linux_dmabuf_v1 bind. */ +volatile int g_ubwc = 1; + +/* Debug (BANNER_WAYLAND_NO_RENDER_NODE=1): name no DRM device in the dma-buf feedback, as a phone + * that exposes no /dev/dri node to apps does, to reproduce its OpenGL path here. Set from the app + * before the compositor starts. */ +volatile int g_no_render_node; + +/* The panel's refresh rate in mHz, from the app; wl_output advertises it so Wine's display modes + * carry the real rate (games pick their saved 144 Hz mode, as on X11). 0 = 60 Hz. */ +volatile int g_output_refresh_mhz; + +/* The advertised output size: the container's screen (what the app's X server reports on X11), + * so Wine's display-mode list stops at the desktop size. 0 = 1920x1080. */ +volatile int g_output_w, g_output_h; +struct pending_release { + struct wl_resource *buffer; + struct wl_listener destroy; + struct surface *surface; /* NULL once the surface is gone */ + int64_t at_ns; + int64_t since_ns; /* when the compositor let go of it (the perf line) */ + struct wl_list link; +}; +static struct wl_list g_pending_releases; +static int g_release_timer_fd = -1; +static struct wl_event_source *g_release_source; +static int g_scene_w, g_scene_h; /* size of the last drawn scene */ +static int g_desktop_w, g_desktop_h; /* last size the desktop had content at */ +static unsigned g_stat_frames, g_stat_dmabuf, g_stat_shm; /* since the last 10 s summary */ + +/* The 10 s `perf` line (on_stats_timer): where the compositor thread's time goes and how long games + * wait for their buffers back. The driver-side times (acquire, present, fence waits) are counted in + * vk_present.c (vkp_perf_take). Compositor thread only. */ +static struct { + unsigned ticks; /* screen refreshes (Choreographer ticks) */ + unsigned scenes; /* render_scene() calls ... */ + int64_t scene_ns, scene_max_ns; /* ... and their wall time */ + unsigned copy_scenes; /* scenes drawn into the screen swapchain (the copy path) */ + unsigned releases, releases_held; /* wl_buffer.release after a replacement; held = 1 ms or more */ + int64_t release_ns, release_max_ns; /* replaced (or taken off the layer) -> released to the game */ +} g_perf; + +static void schedule_render(void); + +static int64_t now_ns(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +/* A game got a replaced buffer back; since_ns = when the compositor let go of it (0 = not counted). */ +static void perf_note_release(int64_t since_ns) { + if (since_ns <= 0) return; + int64_t d = now_ns() - since_ns; + if (d < 0) d = 0; + g_perf.releases++; + g_perf.release_ns += d; + if (d > g_perf.release_max_ns) g_perf.release_max_ns = d; + if (d >= 1000000LL) g_perf.releases_held++; +} + +/* ------------------------------------------------------------------ buffer release pacing */ + +static void pending_release_free(struct pending_release *pr) { + if (pr->surface && pr->surface->releases_pending > 0) pr->surface->releases_pending--; + wl_list_remove(&pr->destroy.link); + wl_list_remove(&pr->link); + free(pr); +} + +/* A surface is going away: its queued releases still go out on time, they just stop counting. */ +static void pending_releases_forget_surface(struct surface *s) { + struct pending_release *pr; + wl_list_for_each(pr, &g_pending_releases, link) + if (pr->surface == s) pr->surface = NULL; +} + +static void on_pending_release_buffer_destroyed(struct wl_listener *l, void *data) { + struct pending_release *pr = wl_container_of(l, pr, destroy); + pending_release_free(pr); +} + +static void arm_release_timer(void) { + struct pending_release *pr; + int64_t earliest = 0; + if (g_release_timer_fd < 0) return; + wl_list_for_each(pr, &g_pending_releases, link) + if (!earliest || pr->at_ns < earliest) earliest = pr->at_ns; + struct itimerspec its = {0}; + if (earliest) { its.it_value.tv_sec = earliest / 1000000000LL; its.it_value.tv_nsec = earliest % 1000000000LL; } + timerfd_settime(g_release_timer_fd, TFD_TIMER_ABSTIME, &its, NULL); +} + +static int on_release_timer(int fd, uint32_t mask, void *data) { + struct pending_release *pr, *tmp; + uint64_t expirations; + int64_t now = now_ns() + 300000; /* anything due within 0.3 ms goes now */ + if (read(fd, &expirations, sizeof(expirations)) < 0 && errno != EAGAIN) return 0; + wl_list_for_each_safe(pr, tmp, &g_pending_releases, link) { + if (pr->at_ns <= now) { + wl_buffer_send_release(pr->buffer); + perf_note_release(pr->since_ns); + pending_release_free(pr); + } + } + wl_display_flush_clients(g_display); + arm_release_timer(); + return 0; +} + +/* Give a replaced buffer back to its client: now, or on the limiter's cadence. + * + * Each release takes the next slot of a per-surface cadence (one slot per interval), so a game + * gets one buffer back per interval however fast it commits. The schedule is kept honest at both + * ends: a slot in the past is brought to now (the game was slower than the cap, no catch-up burst + * is owed), and it never runs further ahead than the releases actually queued โ€” with p releases + * still pending the new one lands at most (p + 1) intervals out, nothing pending means at most + * one interval. Without that bound the schedule kept slots that were consumed but never + * delivered (a swapchain rebuild destroys buffers with queued releases) or that a coarser earlier + * limit had spaced out, and the game's first frames after that waited on empty slots. */ +static void release_buffer(struct surface *s, struct wl_resource *buffer, int64_t since_ns) { + const int limit = g_fps_limit; + /* Without a limit of its own a game is paced to the screen. A swapchain that does not wait for + * frame callbacks (MAILBOX, IMMEDIATE) is held back by nothing else here, so handing a replaced + * buffer straight back let it draw as fast as the GPU allowed and everything above the refresh + * rate was thrown away - 178 frames a second for 91 on screen, measured. X11 never did that: + * there a buffer comes back only once the scene that used it has been composed. One release per + * refresh is the same back-pressure, and the screen shows the same frames either way. */ + const int64_t interval = limit > 0 ? 1000000000LL / limit : g_refresh_ns; + if (interval <= 0 || g_release_timer_fd < 0) { wl_buffer_send_release(buffer); perf_note_release(since_ns); return; } + int64_t now = now_ns(); + int64_t at = s->next_release_ns + interval; + int64_t latest = now + interval * (int64_t)(s->releases_pending + 1); + if (at < now) at = now; + if (at > latest) at = latest; + s->next_release_ns = at; + struct pending_release *pr = calloc(1, sizeof(*pr)); + if (!pr) { wl_buffer_send_release(buffer); perf_note_release(since_ns); return; } + pr->buffer = buffer; + pr->surface = s; + pr->at_ns = at; + pr->since_ns = since_ns; + pr->destroy.notify = on_pending_release_buffer_destroyed; + wl_resource_add_destroy_listener(buffer, &pr->destroy); + wl_list_insert(g_pending_releases.prev, &pr->link); + s->releases_pending++; + arm_release_timer(); +} + +/* ------------------------------------------------------------------ seat state */ + +/* Each Wine process is a separate client with its own wl_pointer / wl_keyboard. */ +struct seat_pointer { struct wl_resource *ptr; struct wl_resource *focus; }; +struct seat_keyboard { struct wl_resource *kb; struct wl_resource *focus; }; +#define MAX_PTRS 32 +static struct seat_pointer g_ptrs[MAX_PTRS]; +static int g_nptrs; +static struct seat_keyboard g_kbs[MAX_PTRS]; +static int g_nkbs; +static struct surface *g_grab; /* no-desktop fallback: surface holding the button */ +static struct surface *g_key_target; /* no-desktop fallback: last clicked surface */ +static struct surface *g_ime_click; /* last clicked program window: where text input goes */ +static int g_input_pipe[2] = {-1, -1}; +/* type 0 = pointer (p1=action 0down/1move/2up, p2=x, p3=y); type 1 = key (p1=evdev, p2=state 1down/0up) */ +struct input_msg { int type; int p1; int p2; int p3; }; + +/* Pointer constraints (zwp_pointer_constraints_v1) and relative pointers + * (zwp_relative_pointer_manager_v1); see the "pointer constraints" section. */ +struct surface; +static void constraints_surface_gone(struct surface *s); +static void constraints_surface_commit(struct surface *s); +static void constraints_pointer_gone(struct wl_resource *pointer); +static void relative_pointers_pointer_gone(struct wl_resource *pointer); +static void constraints_focus_entered(struct wl_resource *target, struct wl_client *client); + +/* ------------------------------------------------------------------ dmabuf buffers */ + +#define FOURCC(a, b, c, d) \ + ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) +#define DRM_ARGB8888 FOURCC('A', 'R', '2', '4') +#define DRM_XRGB8888 FOURCC('X', 'R', '2', '4') +#define DRM_ABGR8888 FOURCC('A', 'B', '2', '4') +#define DRM_XBGR8888 FOURCC('X', 'B', '2', '4') +#define MOD_LINEAR VKP_MOD_LINEAR +#define MOD_INVALID VKP_MOD_INVALID +#define MAX_PLANES 4 + +struct dmabuf_params { + int fd[MAX_PLANES]; + uint32_t offset[MAX_PLANES], stride[MAX_PLANES]; + uint64_t modifier[MAX_PLANES]; + int n_planes; +}; +struct dmabuf_buffer { + int fd[MAX_PLANES]; + uint32_t offset[MAX_PLANES], stride[MAX_PLANES]; + int n_planes; + int32_t width, height; + uint32_t format; + uint64_t modifier; + struct vkp_image *img; /* imported once, reused for every frame */ + int import_failed; + /* One reference for the wl_buffer resource, one per surface showing the buffer. Mesa destroys + * a swapchain's wl_buffers the moment the game rebuilds its swapchain, i.e. while the last + * committed one is still what is on screen: the import (and the dma-buf memory it pins) stays + * until the surface commits something newer, so the picture never blinks to black. */ + int refs; + void *ahb_state; /* zero-copy: ahb_swapchain.c's record (the game's AHardwareBuffer) */ +}; + +static void dmabuf_buffer_unref(struct dmabuf_buffer *b) { + if (!b || --b->refs > 0) return; + vkp_image_destroy(b->img); + for (int i = 0; i < b->n_planes; i++) + if (b->fd[i] >= 0) close(b->fd[i]); + free(b); +} + +static void dbuf_buffer_destroy_req(struct wl_client *c, struct wl_resource *r) { + wl_resource_destroy(r); +} +static const struct wl_buffer_interface dbuf_buffer_impl = { + .destroy = dbuf_buffer_destroy_req, +}; + +static struct dmabuf_buffer *get_dmabuf(struct wl_resource *buffer) { + if (buffer && wl_resource_instance_of(buffer, &wl_buffer_interface, &dbuf_buffer_impl)) + return wl_resource_get_user_data(buffer); + return NULL; +} + +/* ------------------------------------------------------------------ surface state */ + +static void surface_size(const struct surface *s, int *w, int *h) { + if (s->dst_set) { *w = s->dst[0]; *h = s->dst[1]; } + else if (s->src_set) { *w = (int)(s->src[2] + 0.5f); *h = (int)(s->src[3] + 0.5f); } + else { *w = s->buf_w; *h = s->buf_h; } +} + +/* "Title" (program) for the log; subsurfaces are named after the window they belong to. */ +static void describe(const struct surface *s, char *out, size_t size) { + const struct surface *w = s; + while (w->parent) w = w->parent; + const char *prog = client_name(wl_resource_get_client(w->resource)); + if (w->title && *w->title) snprintf(out, size, "\"%s\" (%s)", w->title, prog); + else if (w->role == ROLE_DESKTOP) snprintf(out, size, "the desktop (%s)", prog); + else snprintf(out, size, "window %#x (%s)", w->hwnd, prog); +} + +static struct surface *toplevel_by_hwnd(uint32_t hwnd) { + struct surface *s; + wl_list_for_each(s, &g_toplevels, toplevel_link) + if (s->hwnd == hwnd) return s; + return NULL; +} + +/* Reorder the mapped toplevels to match the last reported Windows z-order. Windows the + * report doesn't mention keep their relative order below the ones it does. */ +static void apply_zorder(void) { + for (size_t i = g_zorder_count; i-- > 0;) { + struct surface *s = toplevel_by_hwnd(g_zorder[i]); + if (!s) continue; + wl_list_remove(&s->toplevel_link); + wl_list_insert(g_toplevels.prev, &s->toplevel_link); + } +} + +static void map_toplevel(struct surface *s) { + int w, h; + if (s->mapped) return; + s->mapped = 1; + surface_size(s, &w, &h); + { + char name[160]; + describe(s, name, sizeof(name)); + wnc_log("window", "opened %s %dx%d at %d,%d%s", name, w, h, s->x, s->y, + s->placed ? "" : " (no desktop position yet)"); + } + wl_list_insert(g_toplevels.prev, &s->toplevel_link); /* new windows start on top */ + apply_zorder(); +} + +static void unmap_toplevel(struct surface *s) { + if (!s->mapped) return; + s->mapped = 0; + { + char name[160]; + describe(s, name, sizeof(name)); + wnc_log("window", "closed %s", name); + } + wl_list_remove(&s->toplevel_link); + wl_list_init(&s->toplevel_link); + if (g_ime_click == s) g_ime_click = NULL; + wnc_text_input_refocus(); +} + +/* Let go of the surface's dmabuf content. paced = 1: the buffer was replaced, give it back on the + * limiter's cadence; paced = 0: the surface is going away, give it back at once (a buffer of a + * destroyed surface is not shown again and must not sit unreleased with the client). */ +static void drop_dmabuf(struct surface *s, int paced) { + if (s->dmabuf) { + wl_list_remove(&s->dmabuf_destroy.link); + /* A buffer on the zero-copy layer is the display's until SurfaceFlinger says otherwise: + * ahb_swapchain.c releases it then. */ + if (!ahb_swapchain_defer_release(s->dmabuf_buf, s->dmabuf, s, paced)) { + if (paced) release_buffer(s, s->dmabuf, now_ns()); + else wl_buffer_send_release(s->dmabuf); + } + s->dmabuf = NULL; + } + dmabuf_buffer_unref(s->dmabuf_buf); + s->dmabuf_buf = NULL; +} + +/* The client destroyed the wl_buffer we are showing (a swapchain rebuild): keep showing its + * image (s->dmabuf_buf holds it) until the next commit replaces it; only the resource is gone. */ +static void on_dmabuf_destroyed(struct wl_listener *l, void *data) { + struct surface *s = wl_container_of(l, s, dmabuf_destroy); + wl_list_remove(&s->dmabuf_destroy.link); + s->dmabuf = NULL; +} + +static void on_pending_buffer_destroyed(struct wl_listener *l, void *data) { + struct surface *s = wl_container_of(l, s, pending_buffer_destroy); + wl_list_remove(&s->pending_buffer_destroy.link); + wl_list_init(&s->pending_buffer_destroy.link); + s->pending_buffer = NULL; +} + +static void fire_frames(struct wl_list *frames) { + struct wl_resource *cb, *tmp; + uint32_t t; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + t = (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); + wl_resource_for_each_safe(cb, tmp, frames) { + wl_callback_send_done(cb, t); + wl_resource_destroy(cb); + } +} + +/* Copy a committed wl_shm buffer into the surface's image and release the buffer. */ +static void take_shm(struct surface *s, struct wl_shm_buffer *shm, struct wl_resource *buffer) { + int32_t w = wl_shm_buffer_get_width(shm), h = wl_shm_buffer_get_height(shm); + int32_t stride = wl_shm_buffer_get_stride(shm); + + if (s->shm_img && (vkp_image_width(s->shm_img) != w || vkp_image_height(s->shm_img) != h)) { + vkp_image_destroy(s->shm_img); + s->shm_img = NULL; + } + if (!s->shm_img) s->shm_img = vkp_image_create_shm(w, h); + if (s->shm_img) { + wl_shm_buffer_begin_access(shm); + vkp_image_upload_shm(s->shm_img, wl_shm_buffer_get_data(shm), stride); + wl_shm_buffer_end_access(shm); + } + wl_buffer_send_release(buffer); + s->buf_w = w; + s->buf_h = h; + s->buf_alpha = wl_shm_buffer_get_format(shm) == WL_SHM_FORMAT_ARGB8888; + s->has_content = s->shm_img != NULL; + g_stat_shm++; + struct client_info *ci = client_info_of(wl_resource_get_client(s->resource)); + if (ci && ci->asked_feedback) ci->shm_frames++; /* since it asked for GPU buffers */ +} + +/* The window the app's performance HUD follows: the latest one to start presenting GPU frames + * (X11 binds the HUD to the _MESA_DRV window and counts X presents instead). JNI upcalls. + * + * A synchronized subsurface belongs to its parent's frame: gamescope presents through a toplevel + * and a plane per layer, attaches to all of them and commits the toplevel last. Following one + * plane counts nothing once the game moves to another, so such a surface is counted as the window + * it is part of, once per commit of that window. */ +static struct surface *g_hud_surface; +static struct surface *hud_window(struct surface *s) { + while (s->parent && s->sub_sync) s = s->parent; + return s; +} +extern void wnc_on_game_surface(const char *window, const char *gpu); /* window NULL = gone */ +extern void wnc_on_game_frame(void); +/* The program behind that window: its Linux pid (the Wayland client's credentials) and executable name + * ("" when /proc gave none) - the app arms its CPU affinity on it (X11 does that from window events). */ +extern void wnc_on_game_program(int pid, const char *program); + +static void take_dmabuf(struct surface *s, struct dmabuf_buffer *b, struct wl_resource *buffer) { + if (s->dmabuf != buffer || s->dmabuf_buf != b) { + drop_dmabuf(s, 1); + s->dmabuf = buffer; + s->dmabuf_buf = b; + b->refs++; + s->dmabuf_destroy.notify = on_dmabuf_destroyed; + wl_resource_add_destroy_listener(buffer, &s->dmabuf_destroy); + } + if (!b->img && !b->import_failed && b->n_planes >= 1) { + b->img = vkp_image_from_dmabuf(b->fd[0], b->format, b->modifier, b->width, b->height, + b->stride[0], b->offset[0]); + if (b->img && g_zero_copy) sc_layer_probe_dmabuf_fd(b->fd[0]); + if (!b->img) { + b->import_failed = 1; + WLOGE("dmabuf import failed (%dx%d fmt=0x%08x mod=0x%llx)", b->width, b->height, + b->format, (unsigned long long)b->modifier); + } + } + if (s->shm_img) { vkp_image_destroy(s->shm_img); s->shm_img = NULL; } + s->buf_w = b->width; + s->buf_h = b->height; + s->buf_alpha = (b->format & 0xff) == 'A'; + s->has_content = b->img != NULL; + g_stat_dmabuf++; + if (!s->announced_vulkan) { + char name[160]; + s->announced_vulkan = 1; + describe(s, name, sizeof(name)); + if (b->img) + /* Imported for the copy path. Whether its frames ALSO go on the display layer without a copy + * is decided per frame (fullscreen, zero-copy on) and counted in the 10 s lines. */ + wnc_log("vulkan", "%s is presenting GPU frames through Wayland: %dx%d, format %c%c%c%c, %s (%s)", + name, b->width, b->height, b->format & 0xff, (b->format >> 8) & 0xff, + (b->format >> 16) & 0xff, (b->format >> 24) & 0xff, + vkp_modifier_name(b->modifier), + ahb_swapchain_has_ahb(b) ? "gralloc buffers: can go on the display layer without a copy" + : "dma-buf: copied into the screen swapchain"); + else if (ahb_swapchain_has_ahb(b)) + wnc_log("vulkan", "%s is presenting GPU frames through Wayland: %dx%d on its own display layer only " + "(gralloc buffers the compositor cannot import for the copy path)", + name, b->width, b->height); + else + wnc_log("error", "could not import GPU frames from %s (%dx%d, modifier %#llx)", + name, b->width, b->height, (unsigned long long)b->modifier); + /* The HUD follows the window whether its frames are copied or go straight to the layer: + * a zero-copy frame the compositor never imported is still a presented game frame. */ + if ((b->img || ahb_swapchain_has_ahb(b)) && g_hud_surface != hud_window(s)) { + g_hud_surface = hud_window(s); + g_hud_surface->hud_frame = 0; + wnc_on_game_surface(name, vkp_gpu_name()); + struct client_info *ci = client_info_of(wl_resource_get_client(s->resource)); + if (ci) wnc_on_game_program((int)ci->pid, strncmp(ci->name, "pid ", 4) ? ci->name : ""); + } + } + /* HDR session (gate open): a DXVK game switches to HDR by REBUILDING its swapchain on the same + * surface, so the one-shot line above never sees the 10-bit buffers - name every format change. */ + if (b->format != s->hdr_fmt_logged && wnc_color_hdr_open()) { + char name[160]; + uint32_t af = ahb_swapchain_ahb_format(b); + int ten = b->format == FOURCC('A', 'B', '3', '0') || b->format == FOURCC('X', 'B', '3', '0'); + describe(s, name, sizeof(name)); + wnc_log("color", "%s presents %dx%d buffers in %c%c%c%c (%s), %s%s%s; %s", name, b->width, b->height, + b->format & 0xff, (b->format >> 8) & 0xff, (b->format >> 16) & 0xff, (b->format >> 24) & 0xff, + ten ? "10-bit A2B10G10R10" : "8-bit", vkp_modifier_name(b->modifier), + af ? ", gralloc " : ", no gralloc buffer (copy path only)", af ? wnc_ahb_format_name(af) : "", + b->img ? "the compositor imported it" + : af ? "the compositor's driver could NOT import it (display layer only, fullscreen)" + : "the compositor's driver could NOT import it (nothing can show it)"); + s->hdr_fmt_logged = b->format; + } + if (hud_window(s) == g_hud_surface && (b->img || ahb_swapchain_has_ahb(b))) g_hud_surface->hud_frame = 1; +} + +/* ---- hooks for ahb_swapchain.c (zero-copy layers) */ +struct dmabuf_buffer *wnc_dmabuf_from_resource(struct wl_resource *buffer) { return get_dmabuf(buffer); } +int wnc_dmabuf_fd(const struct dmabuf_buffer *b) { return b && b->n_planes > 0 ? b->fd[0] : -1; } +void wnc_dmabuf_size(const struct dmabuf_buffer *b, int *w, int *h) { *w = b->width; *h = b->height; } +void **wnc_dmabuf_ahb_slot(struct dmabuf_buffer *b) { return &b->ahb_state; } +void wnc_dmabuf_ref(struct dmabuf_buffer *b) { b->refs++; } +void wnc_dmabuf_unref(struct dmabuf_buffer *b) { dmabuf_buffer_unref(b); } +void wnc_release_buffer(struct surface *s, struct wl_resource *buffer, int paced, int64_t since_ns) { + if (paced && s) release_buffer(s, buffer, since_ns); + else { wl_buffer_send_release(buffer); perf_note_release(since_ns); } +} +void wnc_surface_describe(const struct surface *s, char *out, size_t size) { describe(s, out, size); } +const struct wnc_color *wnc_surface_color(const struct surface *s) { + return s ? wnc_color_of(s->resource) : NULL; +} + +/* ------------------------------------------------------------------ wl_surface */ + +static void surface_destroy(struct wl_client *c, struct wl_resource *r) { + wl_resource_destroy(r); +} +static void surface_attach(struct wl_client *c, struct wl_resource *r, + struct wl_resource *buffer, int32_t x, int32_t y) { + struct surface *s = wl_resource_get_user_data(r); + if (s->pending_buffer) wl_list_remove(&s->pending_buffer_destroy.link); + wl_list_init(&s->pending_buffer_destroy.link); + s->pending_buffer = buffer; + s->pending_attach = 1; + if (buffer) { + s->pending_buffer_destroy.notify = on_pending_buffer_destroyed; + wl_resource_add_destroy_listener(buffer, &s->pending_buffer_destroy); + } +} +static void surface_damage(struct wl_client *c, struct wl_resource *r, + int32_t x, int32_t y, int32_t w, int32_t h) {} +static void frame_callback_destroy(struct wl_resource *r) { + wl_list_remove(wl_resource_get_link(r)); +} + +/* wp_presentation: tells Mesa's Vulkan driver when a frame reached the screen or was replaced + * before it did. Without it the driver waits for a frame callback (one per refresh) for every + * frame, which paced games to the screen rate even in mailbox mode. */ +static void feedback_resource_destroy(struct wl_resource *r) { + wl_list_remove(wl_resource_get_link(r)); +} + +static void feedback_discard_all(struct wl_list *list) { + struct wl_resource *fb, *tmp; + wl_resource_for_each_safe(fb, tmp, list) { + wp_presentation_feedback_send_discarded(fb); + wl_resource_destroy(fb); + } +} + +/* The refresh a presentation timestamp carries is the output's nominal interval - the same value + * every frame, matching the mode wl_output advertises. The measured one drives this compositor's + * own pacing and drifts by a few hundred microseconds a frame; reported here it reads as a mode + * change that often, and a client tracking the output's mode acts on every one of them. */ +static uint32_t output_refresh_ns(void) { + int mhz = g_output_refresh_mhz > 0 ? g_output_refresh_mhz : 60000; + return (uint32_t)(1000000000000LL / mhz); +} + +static void feedback_present_all(struct wl_list *list, int64_t t) { + struct wl_resource *fb, *tmp; + uint64_t sec = (uint64_t)(t / 1000000000LL); + uint32_t nsec = (uint32_t)(t % 1000000000LL); + uint32_t refresh = output_refresh_ns(); + wl_resource_for_each_safe(fb, tmp, list) { + wp_presentation_feedback_send_presented(fb, (uint32_t)(sec >> 32), (uint32_t)sec, nsec, + refresh, 0, 0, + WP_PRESENTATION_FEEDBACK_KIND_VSYNC); + wl_resource_destroy(fb); + } +} + +static void presentation_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void presentation_feedback(struct wl_client *c, struct wl_resource *r, + struct wl_resource *surface, uint32_t id) { + struct surface *s = wl_resource_get_user_data(surface); + struct wl_resource *fb = wl_resource_create(c, &wp_presentation_feedback_interface, + wl_resource_get_version(r), id); + if (!fb) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(fb, NULL, NULL, feedback_resource_destroy); + if (!s) { wp_presentation_feedback_send_discarded(fb); wl_resource_destroy(fb); return; } + wl_list_insert(s->pending_feedback.prev, wl_resource_get_link(fb)); +} +static const struct wp_presentation_interface presentation_impl = { + .destroy = presentation_destroy, + .feedback = presentation_feedback, +}; +static void bind_presentation(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wp_presentation_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &presentation_impl, NULL, NULL); + wp_presentation_send_clock_id(r, CLOCK_MONOTONIC); +} +static void surface_frame(struct wl_client *c, struct wl_resource *r, uint32_t cb) { + struct surface *s = wl_resource_get_user_data(r); + struct wl_resource *callback = wl_resource_create(c, &wl_callback_interface, 1, cb); + if (!callback) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(callback, NULL, NULL, frame_callback_destroy); + wl_list_insert(s->pending_frames.prev, wl_resource_get_link(callback)); +} +/* A region is kept as its bounding box; `exact` while that box is the region itself. */ +struct region { int set, exact; int x, y, w, h; }; + +static void surface_set_opaque(struct wl_client *c, struct wl_resource *r, + struct wl_resource *region) { + struct surface *s = wl_resource_get_user_data(r); + struct region *rg = region ? wl_resource_get_user_data(region) : NULL; + memset(s->pending_opaque, 0, sizeof(s->pending_opaque)); + s->pending_opaque_set = 1; + if (!rg || !rg->set) return; + if (rg->exact) { + s->pending_opaque[0] = rg->x; s->pending_opaque[1] = rg->y; + s->pending_opaque[2] = rg->w; s->pending_opaque[3] = rg->h; + } + struct client_info *ci = client_info_of(c); + if (ci) ci->declares_opaque = 1; +} +static void surface_set_input(struct wl_client *c, struct wl_resource *r, + struct wl_resource *region) {} + +static void surface_commit(struct wl_client *c, struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + struct surface *child; + + if (s->pending_src_set) { + s->src_set = s->pending_src[2] > 0; + memcpy(s->src, s->pending_src, sizeof(s->src)); + s->pending_src_set = 0; + } + if (s->pending_dst_set) { + s->dst_set = s->pending_dst[0] > 0; + memcpy(s->dst, s->pending_dst, sizeof(s->dst)); + s->pending_dst_set = 0; + } + if (s->pending_opaque_set) { + memcpy(s->opaque, s->pending_opaque, sizeof(s->opaque)); + s->pending_opaque_set = 0; + } + + if (s->pending_attach) { + struct wl_resource *buffer = s->pending_buffer; + struct dmabuf_buffer *db = get_dmabuf(buffer); + struct wl_shm_buffer *shm = buffer && !db ? wl_shm_buffer_get(buffer) : NULL; + + /* The previous content is replaced before reaching the screen. */ + feedback_discard_all(&s->feedback); + + if (buffer) { + wl_list_remove(&s->pending_buffer_destroy.link); + wl_list_init(&s->pending_buffer_destroy.link); + } + s->pending_buffer = NULL; + s->pending_attach = 0; + + if (s->role == ROLE_NONE) { + /* Cursor or role-less surface: never drawn (the app draws its own pointer). */ + if (buffer) wl_buffer_send_release(buffer); + } else if (db) { + take_dmabuf(s, db, buffer); + } else if (shm) { + drop_dmabuf(s, 1); + take_shm(s, shm, buffer); + } else { + drop_dmabuf(s, 1); + s->has_content = 0; + if (buffer) wl_buffer_send_release(buffer); + } + } + + /* Subsurface positions are applied when the parent commits. */ + wl_list_for_each(child, &s->children, child_link) { + if (child->sub_pending) { + child->sub_x = child->sub_pending_x; + child->sub_y = child->sub_pending_y; + child->sub_pending = 0; + } + } + + wl_list_insert_list(s->frames.prev, &s->pending_frames); + wl_list_init(&s->pending_frames); + wl_list_insert_list(s->feedback.prev, &s->pending_feedback); + wl_list_init(&s->pending_feedback); + if (s == g_hud_surface && s->hud_frame) { + s->hud_frame = 0; + wnc_on_game_frame(); + } + constraints_surface_commit(s); + wnc_color_commit(s->resource); /* wp_color_management_surface_v1 state (returns at once when HDR is off) */ + + if (s->role == ROLE_TOPLEVEL && s->xdg_toplevel) { + if (s->has_content) map_toplevel(s); + else unmap_toplevel(s); + } + schedule_render(); +} +static void surface_set_buffer_transform(struct wl_client *c, struct wl_resource *r, int32_t t) {} +static void surface_set_buffer_scale(struct wl_client *c, struct wl_resource *r, int32_t s) {} +static void surface_damage_buffer(struct wl_client *c, struct wl_resource *r, + int32_t x, int32_t y, int32_t w, int32_t h) {} +static void surface_offset(struct wl_client *c, struct wl_resource *r, int32_t x, int32_t y) {} + +static const struct wl_surface_interface surface_impl = { + .destroy = surface_destroy, + .attach = surface_attach, + .damage = surface_damage, + .frame = surface_frame, + .set_opaque_region = surface_set_opaque, + .set_input_region = surface_set_input, + .commit = surface_commit, + .set_buffer_transform = surface_set_buffer_transform, + .set_buffer_scale = surface_set_buffer_scale, + .damage_buffer = surface_damage_buffer, + .offset = surface_offset, +}; + +static void detach_from_parent(struct surface *s) { + if (!s->parent) return; + wl_list_remove(&s->child_link); + wl_list_init(&s->child_link); + s->parent = NULL; +} + +static void surface_resource_destroy(struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + struct surface *child, *tmp; + struct wl_resource *cb, *cbtmp; + + if (!s) return; + for (int i = 0; i < g_nptrs; i++) if (g_ptrs[i].focus == r) g_ptrs[i].focus = NULL; + for (int i = 0; i < g_nkbs; i++) if (g_kbs[i].focus == r) g_kbs[i].focus = NULL; + if (g_grab == s) g_grab = NULL; + if (g_key_target == s) g_key_target = NULL; + if (g_ime_click == s) g_ime_click = NULL; + wnc_text_input_surface_gone(r); + if (g_desktop == s) { g_desktop = NULL; wnc_log("desktop", "the desktop closed"); } + if (g_hud_surface == s) { g_hud_surface = NULL; wnc_on_game_surface(NULL, NULL); } + constraints_surface_gone(s); + + unmap_toplevel(s); + detach_from_parent(s); + wl_list_for_each_safe(child, tmp, &s->children, child_link) { + wl_list_remove(&child->child_link); + wl_list_init(&child->child_link); + child->parent = NULL; + } + if (s->pending_buffer) wl_list_remove(&s->pending_buffer_destroy.link); + pending_releases_forget_surface(s); + drop_dmabuf(s, 0); + ahb_swapchain_surface_gone(s); + vkp_image_destroy(s->shm_img); + wl_resource_for_each_safe(cb, cbtmp, &s->pending_frames) wl_resource_destroy(cb); + wl_resource_for_each_safe(cb, cbtmp, &s->frames) wl_resource_destroy(cb); + feedback_discard_all(&s->pending_feedback); + feedback_discard_all(&s->feedback); + if (s->viewport) wl_resource_set_user_data(s->viewport, NULL); + if (s->subsurface) wl_resource_set_user_data(s->subsurface, NULL); + if (s->xdg_surface) wl_resource_set_user_data(s->xdg_surface, NULL); + if (s->xdg_toplevel) wl_resource_set_user_data(s->xdg_toplevel, NULL); + wl_list_remove(&s->link); + free(s->title); + free(s); + schedule_render(); +} + +/* ------------------------------------------------------------------ wl_region */ + +/* A region is kept as the bounding box of its rectangles: enough for pointer confinement, + * where winewayland sends one rectangle (the ClipCursor area). Subtractions are ignored. */ +static void region_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void region_add(struct wl_client *c, struct wl_resource *r, + int32_t x, int32_t y, int32_t w, int32_t h) { + struct region *rg = wl_resource_get_user_data(r); + if (!rg || w <= 0 || h <= 0) return; + if (!rg->set) { rg->x = x; rg->y = y; rg->w = w; rg->h = h; rg->set = rg->exact = 1; return; } + long long x2 = (long long)rg->x + rg->w, y2 = (long long)rg->y + rg->h; + long long nx2 = (long long)x + w, ny2 = (long long)y + h; + int inside = x >= rg->x && y >= rg->y && nx2 <= x2 && ny2 <= y2; + int around = x <= rg->x && y <= rg->y && nx2 >= x2 && ny2 >= y2; + if (!inside && !around) rg->exact = 0; + if (nx2 > x2) x2 = nx2; + if (ny2 > y2) y2 = ny2; + if (x < rg->x) rg->x = x; + if (y < rg->y) rg->y = y; + rg->w = (int)(x2 - rg->x > INT32_MAX ? INT32_MAX : x2 - rg->x); + rg->h = (int)(y2 - rg->y > INT32_MAX ? INT32_MAX : y2 - rg->y); +} +static void region_subtract(struct wl_client *c, struct wl_resource *r, + int32_t x, int32_t y, int32_t w, int32_t h) { + struct region *rg = wl_resource_get_user_data(r); + if (rg && w > 0 && h > 0) rg->exact = 0; +} +static void region_resource_destroy(struct wl_resource *r) { free(wl_resource_get_user_data(r)); } +static const struct wl_region_interface region_impl = { + .destroy = region_destroy, + .add = region_add, + .subtract = region_subtract, +}; + +/* ------------------------------------------------------------------ wl_compositor */ + +static void compositor_create_surface(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct surface *s = calloc(1, sizeof(*s)); + if (!s) { wl_client_post_no_memory(c); return; } + s->resource = wl_resource_create(c, &wl_surface_interface, wl_resource_get_version(r), id); + if (!s->resource) { free(s); wl_client_post_no_memory(c); return; } + wl_list_init(&s->pending_frames); + wl_list_init(&s->frames); + wl_list_init(&s->pending_feedback); + wl_list_init(&s->feedback); + wl_list_init(&s->children); + wl_list_init(&s->child_link); + wl_list_init(&s->toplevel_link); + wl_list_init(&s->pending_buffer_destroy.link); + wl_list_insert(&g_surfaces, &s->link); + wl_resource_set_implementation(s->resource, &surface_impl, s, surface_resource_destroy); +} +static void compositor_create_region(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct wl_resource *reg = wl_resource_create(c, &wl_region_interface, 1, id); + if (!reg) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(reg, ®ion_impl, calloc(1, sizeof(struct region)), + region_resource_destroy); +} +static const struct wl_compositor_interface compositor_impl = { + .create_surface = compositor_create_surface, + .create_region = compositor_create_region, +}; +static void bind_compositor(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wl_compositor_interface, ver, id); + wl_resource_set_implementation(r, &compositor_impl, NULL, NULL); +} + +/* --------------------------------------------------------------- wl_subcompositor + * winewayland puts a window's Vulkan/GL swapchain in a subsurface over the window. */ + +static void subsurface_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void subsurface_set_position(struct wl_client *c, struct wl_resource *r, int32_t x, int32_t y) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + s->sub_pending_x = x; + s->sub_pending_y = y; + s->sub_pending = 1; +} +static void restack_child(struct surface *s, struct surface *sibling, int above) { + if (!s || !s->parent || !sibling) return; + if (sibling == s->parent) { + /* Relative to the parent: directly above it or directly below it. */ + s->below_parent = !above; + wl_list_remove(&s->child_link); + if (above) wl_list_insert(&s->parent->children, &s->child_link); + else wl_list_insert(s->parent->children.prev, &s->child_link); + } else if (sibling->parent == s->parent) { + s->below_parent = sibling->below_parent; + wl_list_remove(&s->child_link); + if (above) wl_list_insert(&sibling->child_link, &s->child_link); + else wl_list_insert(sibling->child_link.prev, &s->child_link); + } + schedule_render(); +} +static void subsurface_place_above(struct wl_client *c, struct wl_resource *r, + struct wl_resource *sibling) { + restack_child(wl_resource_get_user_data(r), wl_resource_get_user_data(sibling), 1); +} +static void subsurface_place_below(struct wl_client *c, struct wl_resource *r, + struct wl_resource *sibling) { + restack_child(wl_resource_get_user_data(r), wl_resource_get_user_data(sibling), 0); +} +static void subsurface_set_sync(struct wl_client *c, struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (s) s->sub_sync = 1; +} +static void subsurface_set_desync(struct wl_client *c, struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (s) s->sub_sync = 0; +} +static const struct wl_subsurface_interface subsurface_impl = { + .destroy = subsurface_destroy, + .set_position = subsurface_set_position, + .place_above = subsurface_place_above, + .place_below = subsurface_place_below, + .set_sync = subsurface_set_sync, + .set_desync = subsurface_set_desync, +}; +static void subsurface_resource_destroy(struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + detach_from_parent(s); + s->subsurface = NULL; + s->role = ROLE_NONE; + s->has_content = 0; + drop_dmabuf(s, 1); + schedule_render(); +} + +static void subcompositor_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void subcompositor_get_subsurface(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface, struct wl_resource *parent) { + struct surface *s = wl_resource_get_user_data(surface); + struct surface *p = wl_resource_get_user_data(parent); + struct wl_resource *sub = wl_resource_create(c, &wl_subsurface_interface, wl_resource_get_version(r), id); + if (!sub) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(sub, &subsurface_impl, s, subsurface_resource_destroy); + if (!s || !p || s == p) return; + detach_from_parent(s); + s->role = ROLE_SUBSURFACE; + s->subsurface = sub; + s->parent = p; + s->below_parent = 0; + s->sub_sync = 1; + s->sub_x = s->sub_y = 0; + wl_list_insert(p->children.prev, &s->child_link); /* new subsurfaces go on top */ +} +static const struct wl_subcompositor_interface subcompositor_impl = { + .destroy = subcompositor_destroy, + .get_subsurface = subcompositor_get_subsurface, +}; +static void bind_subcompositor(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wl_subcompositor_interface, ver, id); + wl_resource_set_implementation(r, &subcompositor_impl, NULL, NULL); +} + +/* ---------------------------------------------------------------- wp_viewporter + * winewayland crops window buffers to the window size and scales swapchains to the + * client area with viewports, so they must be honoured for things to line up. */ + +static void viewport_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void viewport_set_source(struct wl_client *c, struct wl_resource *r, + wl_fixed_t x, wl_fixed_t y, wl_fixed_t w, wl_fixed_t h) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + s->pending_src[0] = (float)wl_fixed_to_double(x); + s->pending_src[1] = (float)wl_fixed_to_double(y); + s->pending_src[2] = w == wl_fixed_from_int(-1) ? -1.0f : (float)wl_fixed_to_double(w); + s->pending_src[3] = h == wl_fixed_from_int(-1) ? -1.0f : (float)wl_fixed_to_double(h); + s->pending_src_set = 1; +} +static void viewport_set_destination(struct wl_client *c, struct wl_resource *r, int32_t w, int32_t h) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + s->pending_dst[0] = w; + s->pending_dst[1] = h; + s->pending_dst_set = 1; +} +static const struct wp_viewport_interface viewport_impl = { + .destroy = viewport_destroy, + .set_source = viewport_set_source, + .set_destination = viewport_set_destination, +}; +static void viewport_resource_destroy(struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + s->viewport = NULL; + s->pending_src[2] = -1.0f; s->pending_src_set = 1; + s->pending_dst[0] = -1; s->pending_dst_set = 1; +} + +static void viewporter_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void viewporter_get_viewport(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface) { + struct surface *s = wl_resource_get_user_data(surface); + struct wl_resource *vp = wl_resource_create(c, &wp_viewport_interface, wl_resource_get_version(r), id); + if (!vp) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(vp, &viewport_impl, s, viewport_resource_destroy); + if (s) s->viewport = vp; +} +static const struct wp_viewporter_interface viewporter_impl = { + .destroy = viewporter_destroy, + .get_viewport = viewporter_get_viewport, +}; +static void bind_viewporter(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wp_viewporter_interface, ver, id); + wl_resource_set_implementation(r, &viewporter_impl, NULL, NULL); +} + +/* ------------------------------------------------------------------ xdg_shell */ + +static void xdg_toplevel_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void xdg_toplevel_noop_parent(struct wl_client *c, struct wl_resource *r, struct wl_resource *p) {} +static void xdg_toplevel_set_title(struct wl_client *c, struct wl_resource *r, const char *title) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + free(s->title); + s->title = title ? strdup(title) : NULL; +} +static void xdg_toplevel_set_app_id(struct wl_client *c, struct wl_resource *r, const char *id) {} +static void xdg_toplevel_show_menu(struct wl_client *c, struct wl_resource *r, struct wl_resource *seat, + uint32_t serial, int32_t x, int32_t y) {} +static void xdg_toplevel_move(struct wl_client *c, struct wl_resource *r, struct wl_resource *seat, + uint32_t serial) {} +static void xdg_toplevel_resize(struct wl_client *c, struct wl_resource *r, struct wl_resource *seat, + uint32_t serial, uint32_t edges) {} +static void xdg_toplevel_set_i32(struct wl_client *c, struct wl_resource *r, int32_t w, int32_t h) {} +static void xdg_toplevel_noop(struct wl_client *c, struct wl_resource *r) {} +/* Size 0x0 = the client picks; active, and fullscreen when asked (libdecor draws no frame then). */ +static void send_toplevel_configure(struct surface *s) { + struct wl_array states; + wl_array_init(&states); + uint32_t *st = wl_array_add(&states, sizeof(uint32_t)); + *st = XDG_TOPLEVEL_STATE_ACTIVATED; + if (s->fullscreen) { + st = wl_array_add(&states, sizeof(uint32_t)); + *st = XDG_TOPLEVEL_STATE_FULLSCREEN; + } + xdg_toplevel_send_configure(s->xdg_toplevel, 0, 0, &states); + wl_array_release(&states); + xdg_surface_send_configure(s->xdg_surface, wl_display_next_serial(g_display)); +} +static void xdg_toplevel_set_fullscreen(struct wl_client *c, struct wl_resource *r, struct wl_resource *output) { + struct surface *s = wl_resource_get_user_data(r); + if (!s || !s->xdg_surface || s->fullscreen) return; + s->fullscreen = 1; + send_toplevel_configure(s); +} +static void xdg_toplevel_unset_fullscreen(struct wl_client *c, struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (!s || !s->xdg_surface || !s->fullscreen) return; + s->fullscreen = 0; + send_toplevel_configure(s); +} +static const struct xdg_toplevel_interface xdg_toplevel_impl = { + .destroy = xdg_toplevel_destroy_req, + .set_parent = xdg_toplevel_noop_parent, + .set_title = xdg_toplevel_set_title, + .set_app_id = xdg_toplevel_set_app_id, + .show_window_menu = xdg_toplevel_show_menu, + .move = xdg_toplevel_move, + .resize = xdg_toplevel_resize, + .set_max_size = xdg_toplevel_set_i32, + .set_min_size = xdg_toplevel_set_i32, + .set_maximized = xdg_toplevel_noop, + .unset_maximized = xdg_toplevel_noop, + .set_fullscreen = xdg_toplevel_set_fullscreen, + .unset_fullscreen = xdg_toplevel_unset_fullscreen, + .set_minimized = xdg_toplevel_noop, +}; +static void xdg_toplevel_resource_destroy(struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (!s) return; + s->xdg_toplevel = NULL; + unmap_toplevel(s); + s->placed = 0; + s->hwnd = 0; + schedule_render(); +} + +static void xdg_surface_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void xdg_surface_get_toplevel(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct surface *s = wl_resource_get_user_data(r); + struct wl_resource *tl = wl_resource_create(c, &xdg_toplevel_interface, wl_resource_get_version(r), id); + if (!tl) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(tl, &xdg_toplevel_impl, s, xdg_toplevel_resource_destroy); + if (!s) return; + s->role = ROLE_TOPLEVEL; + s->xdg_toplevel = tl; + s->fullscreen = 0; + send_toplevel_configure(s); +} +static void xdg_surface_get_popup(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *parent, struct wl_resource *positioner) {} +static void xdg_surface_set_geometry(struct wl_client *c, struct wl_resource *r, + int32_t x, int32_t y, int32_t w, int32_t h) {} +static void xdg_surface_ack_configure(struct wl_client *c, struct wl_resource *r, uint32_t serial) {} +static const struct xdg_surface_interface xdg_surface_impl = { + .destroy = xdg_surface_destroy_req, + .get_toplevel = xdg_surface_get_toplevel, + .get_popup = xdg_surface_get_popup, + .set_window_geometry = xdg_surface_set_geometry, + .ack_configure = xdg_surface_ack_configure, +}; +static void xdg_surface_resource_destroy(struct wl_resource *r) { + struct surface *s = wl_resource_get_user_data(r); + if (s) s->xdg_surface = NULL; +} + +static void xdg_wm_base_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void xdg_wm_base_create_positioner(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct wl_resource *p = wl_resource_create(c, &xdg_positioner_interface, wl_resource_get_version(r), id); + if (p) wl_resource_set_implementation(p, NULL, NULL, NULL); +} +static void xdg_wm_base_get_xdg_surface(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surf) { + struct surface *s = wl_resource_get_user_data(surf); + struct wl_resource *xs = wl_resource_create(c, &xdg_surface_interface, wl_resource_get_version(r), id); + if (!xs) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(xs, &xdg_surface_impl, s, xdg_surface_resource_destroy); + if (s) s->xdg_surface = xs; +} +static void xdg_wm_base_pong(struct wl_client *c, struct wl_resource *r, uint32_t serial) {} +static const struct xdg_wm_base_interface xdg_wm_base_impl = { + .destroy = xdg_wm_base_destroy, + .create_positioner = xdg_wm_base_create_positioner, + .get_xdg_surface = xdg_wm_base_get_xdg_surface, + .pong = xdg_wm_base_pong, +}; +static void bind_xdg_wm_base(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &xdg_wm_base_interface, ver, id); + wl_resource_set_implementation(r, &xdg_wm_base_impl, NULL, NULL); +} + +/* ------------------------------------------------------------------ banner_desktop_v1 */ + +static void desktop_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void desktop_set_desktop(struct wl_client *c, struct wl_resource *r, struct wl_resource *surface) { + struct surface *s = wl_resource_get_user_data(surface); + if (!s || (s->role != ROLE_NONE && s->role != ROLE_DESKTOP)) return; + s->role = ROLE_DESKTOP; + g_desktop = s; + wnc_log("desktop", "Windows virtual desktop created by %s", client_name(c)); + schedule_render(); +} +static void desktop_set_window(struct wl_client *c, struct wl_resource *r, struct wl_resource *surface, + uint32_t hwnd, int32_t x, int32_t y) { + struct surface *s = wl_resource_get_user_data(surface); + if (!s) return; + if (s->placed && (s->x != x || s->y != y) && s->mapped && log_budget()) { + char name[160]; + describe(s, name, sizeof(name)); + wnc_log("window", "moved %s to %d,%d", name, x, y); + } + s->hwnd = hwnd; + s->x = x; + s->y = y; + s->placed = 1; + if (s->mapped) apply_zorder(); + schedule_render(); +} +static void desktop_set_zorder(struct wl_client *c, struct wl_resource *r, struct wl_array *hwnds) { + size_t count = hwnds->size / sizeof(uint32_t); + uint32_t *copy = count ? malloc(count * sizeof(uint32_t)) : NULL; + if (count && !copy) return; + if (count) memcpy(copy, hwnds->data, count * sizeof(uint32_t)); + free(g_zorder); + g_zorder = copy; + g_zorder_count = count; + apply_zorder(); + schedule_render(); +} +static const struct banner_desktop_v1_interface desktop_impl = { + .destroy = desktop_destroy_req, + .set_desktop = desktop_set_desktop, + .set_window = desktop_set_window, + .set_zorder = desktop_set_zorder, +}; +static void bind_desktop(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &banner_desktop_v1_interface, ver, id); + wl_resource_set_implementation(r, &desktop_impl, NULL, NULL); +} + +/* ------------------------------------------------------------ zwp_linux_dmabuf_v1 */ + +static void dbuf_buffer_resource_destroy(struct wl_resource *r) { + dmabuf_buffer_unref(wl_resource_get_user_data(r)); +} + +static void params_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void params_add(struct wl_client *c, struct wl_resource *r, int32_t fd, uint32_t plane, + uint32_t offset, uint32_t stride, uint32_t mod_hi, uint32_t mod_lo) { + struct dmabuf_params *p = wl_resource_get_user_data(r); + if (plane >= MAX_PLANES) { close(fd); return; } + if (p->fd[plane] >= 0) close(p->fd[plane]); + p->fd[plane] = fd; + p->offset[plane] = offset; + p->stride[plane] = stride; + p->modifier[plane] = ((uint64_t)mod_hi << 32) | mod_lo; + if ((int)plane + 1 > p->n_planes) p->n_planes = plane + 1; +} +static struct wl_resource *params_do_create(struct wl_client *c, struct wl_resource *r, uint32_t id, + int32_t w, int32_t h, uint32_t format, uint32_t flags) { + struct dmabuf_params *p = wl_resource_get_user_data(r); + struct dmabuf_buffer *b = calloc(1, sizeof(*b)); + if (!b) return NULL; + b->n_planes = p->n_planes; + b->refs = 1; /* the wl_buffer resource's */ + b->width = w; b->height = h; b->format = format; + b->modifier = p->modifier[0]; + for (int i = 0; i < MAX_PLANES; i++) b->fd[i] = -1; + for (int i = 0; i < p->n_planes; i++) { + b->fd[i] = p->fd[i]; + b->offset[i] = p->offset[i]; + b->stride[i] = p->stride[i]; + p->fd[i] = -1; /* ownership moves to the buffer */ + } + struct wl_resource *buf = wl_resource_create(c, &wl_buffer_interface, 1, id); + if (!buf) { + for (int i = 0; i < b->n_planes; i++) if (b->fd[i] >= 0) close(b->fd[i]); + free(b); + wl_client_post_no_memory(c); + return NULL; + } + wl_resource_set_implementation(buf, &dbuf_buffer_impl, b, dbuf_buffer_resource_destroy); + struct client_info *ci = client_info_of(c); + if (ci) ci->dmabuf_buffers++; + return buf; +} +static void params_create(struct wl_client *c, struct wl_resource *r, int32_t w, int32_t h, + uint32_t format, uint32_t flags) { + struct wl_resource *buf = params_do_create(c, r, 0, w, h, format, flags); + if (buf) zwp_linux_buffer_params_v1_send_created(r, buf); + else zwp_linux_buffer_params_v1_send_failed(r); +} +static void params_create_immed(struct wl_client *c, struct wl_resource *r, uint32_t buffer_id, + int32_t w, int32_t h, uint32_t format, uint32_t flags) { + params_do_create(c, r, buffer_id, w, h, format, flags); +} +static const struct zwp_linux_buffer_params_v1_interface params_impl = { + .destroy = params_destroy, + .add = params_add, + .create = params_create, + .create_immed = params_create_immed, +}; +static void params_resource_destroy(struct wl_resource *r) { + struct dmabuf_params *p = wl_resource_get_user_data(r); + if (!p) return; + for (int i = 0; i < MAX_PLANES; i++) + if (p->fd[i] >= 0) close(p->fd[i]); + free(p); +} + +static void dmabuf_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void dmabuf_create_params(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct dmabuf_params *p = calloc(1, sizeof(*p)); + if (!p) { wl_client_post_no_memory(c); return; } + for (int i = 0; i < MAX_PLANES; i++) p->fd[i] = -1; + struct wl_resource *pr = wl_resource_create(c, &zwp_linux_buffer_params_v1_interface, + wl_resource_get_version(r), id); + if (!pr) { free(p); wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(pr, ¶ms_impl, p, params_resource_destroy); +} +static void dmabuf_get_default_feedback(struct wl_client *c, struct wl_resource *r, uint32_t id); +static void dmabuf_get_surface_feedback(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface); +static const struct zwp_linux_dmabuf_v1_interface dmabuf_impl = { + .destroy = dmabuf_destroy, + .create_params = dmabuf_create_params, + .get_default_feedback = dmabuf_get_default_feedback, + .get_surface_feedback = dmabuf_get_surface_feedback, +}; +/* The advertised format/modifier table, built at the first bind from what the renderer's driver + * can import (vkp_dmabuf_modifiers). INVALID is always offered too (Mesa drops it; other clients + * may use it for the implicit path). Without a renderer the list is LINEAR + INVALID, as before. */ +/* The last two rows are HDR10's (color_mgmt.h): A2B10G10R10 as AB30 (alpha) + XB30 (opaque) - Mesa + * lists a VkFormat only when both are advertised, and it is the one 10-bit layout our zero-copy WSI + * can put in a gralloc buffer (AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM). They are advertised ONLY + * while the HDR gate is open, and after the 8-bit rows, so every other session - and every client that + * takes the first format it sees - gets exactly the table it always had. */ +#define DMABUF_NFMT_MAX 6 +#define DMABUF_NFMT_SDR 4 +#define DMABUF_NMOD 4 +#define DRM_ABGR2101010 FOURCC('A', 'B', '3', '0') +#define DRM_XBGR2101010 FOURCC('X', 'B', '3', '0') +static struct { uint32_t fmt; uint64_t mods[DMABUF_NMOD]; int n; } g_dmabuf_fmts[DMABUF_NFMT_MAX] = { + {DRM_ARGB8888}, {DRM_XRGB8888}, {DRM_ABGR8888}, {DRM_XBGR8888}, {DRM_ABGR2101010}, {DRM_XBGR2101010}}; +static int g_dmabuf_nfmt = DMABUF_NFMT_SDR; /* rows advertised: the SDR four, + 2 with the HDR gate open */ +static int g_dmabuf_fmts_ready; +static void dmabuf_build_feedback(void); + +static void dmabuf_build_formats(void) { + char line[320]; + int pos = 0, compressed = 0; + g_dmabuf_fmts_ready = 1; + g_dmabuf_nfmt = wnc_color_hdr_open() ? DMABUF_NFMT_MAX : DMABUF_NFMT_SDR; + for (int f = 0; f < g_dmabuf_nfmt; f++) { + uint64_t got[DMABUF_NMOD]; + int n = vkp_dmabuf_modifiers(g_dmabuf_fmts[f].fmt, got, DMABUF_NMOD), k = 0; + /* LINEAR first: it is the layout every client and the shm fallback agree on, and the one + * we advertised before this table existed. */ + g_dmabuf_fmts[f].mods[k++] = MOD_LINEAR; + for (int i = 0; i < n && k < DMABUF_NMOD - 1; i++) { + if (got[i] == MOD_LINEAR) continue; + if (got[i] == VKP_MOD_QCOM_COMPRESSED && !g_ubwc) continue; + g_dmabuf_fmts[f].mods[k++] = got[i]; + if (got[i] == VKP_MOD_QCOM_COMPRESSED) compressed++; + } + g_dmabuf_fmts[f].mods[k++] = MOD_INVALID; + g_dmabuf_fmts[f].n = k; + uint32_t fmt = g_dmabuf_fmts[f].fmt; + pos += snprintf(line + pos, sizeof(line) - (size_t)pos, "%s%c%c%c%c", f ? ", " : "", + fmt & 0xff, (fmt >> 8) & 0xff, (fmt >> 16) & 0xff, (fmt >> 24) & 0xff); + for (int i = 0; i < k - 1 && pos < (int)sizeof(line); i++) + pos += snprintf(line + pos, sizeof(line) - (size_t)pos, "%s%s", i ? "+" : " ", + vkp_modifier_name(g_dmabuf_fmts[f].mods[i])); + if (pos >= (int)sizeof(line)) pos = (int)sizeof(line) - 1; + } + wnc_log("dmabuf", "formats: %s%s", line, + !g_ubwc ? " (BANNER_WAYLAND_UBWC=0: qcom_compressed not advertised)" : ""); + if (g_ubwc && !compressed) + wnc_log("dmabuf", "the compositor's driver (%s) reports no importable qcom_compressed layout: " + "game swapchains stay linear", vkp_gpu_name()); + if (g_dmabuf_nfmt > DMABUF_NFMT_SDR) { + /* The rows above always carry LINEAR; say what the compositor's own driver can really import + * for 10-bit, since a gralloc buffer it cannot import is shown on the display layer only. */ + uint64_t got[DMABUF_NMOD]; + int n = vkp_dmabuf_modifiers(DRM_XBGR2101010, got, DMABUF_NMOD), ubwc10 = 0; + for (int i = 0; i < n; i++) if (got[i] == VKP_MOD_QCOM_COMPRESSED) ubwc10 = 1; + wnc_log("color", "10-bit dma-buf formats AB30/XB30 advertised for HDR10; the compositor's driver (%s) " + "imports XB30 %s", vkp_gpu_name(), + n == 0 ? "with no layout it reports (copy path unlikely; display layer only)" + : ubwc10 ? "linear and UBWC" : "linear only (UBWC 10-bit frames: display layer only)"); + } + dmabuf_build_feedback(); +} + +/* ---- dmabuf feedback (zwp_linux_dmabuf_v1 version 4) + * + * Turnip's Vulkan WSI is happy with the version 3 format/modifier events, so every Vulkan game + * worked while we only advertised 3. Mesa's EGL is not: its Wayland platform only takes the GPU + * (kopper/Zink) path when it can bind this interface with FEEDBACK, and with 3 it silently drops + * to its wl_shm software path. On this Proton layer that path has no software rasteriser to fall + * back to (the gallium build is zink+kopper+swrast, no llvmpipe), so the shm buffer it commits is + * never written: a native OpenGL window came out solid black, ~30 shm commits/s and no GPU frame + * at all (Wizardry: The Labyrinth of Lost Souls). Feedback is what makes that path work. + * + * What a client needs from us is one tranche describing "everything the compositor can import": + * a format table it mmaps read-only, the device to allocate on, and the indices it may use. + * Clients binding versions 1-3 keep getting the old format/modifier events instead. */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif +#ifndef F_ADD_SEALS +#define F_ADD_SEALS 1033 +#define F_SEAL_SEAL 0x0001 +#define F_SEAL_SHRINK 0x0002 +#define F_SEAL_GROW 0x0004 +#define F_SEAL_WRITE 0x0008 +#endif + +struct dmabuf_fmt_entry { uint32_t format; uint32_t pad; uint64_t modifier; }; /* the wire layout */ + +static int g_fmt_table_fd = -1; /* sealed read-only memfd of dmabuf_fmt_entry[] */ +static size_t g_fmt_table_size; +static uint16_t g_fmt_table_n; /* entries, == the indices a tranche may name */ +static dev_t g_main_device; /* the render node clients should allocate on */ + +/* The GPU we import through is reached with KGSL, not DRM, so there is no render node of our own + * to name. Clients only use main_device to match "the same device as the compositor" and to pick + * a driver; the one DRM render node this platform has is the right answer, and Zink ignores it + * anyway (it renders on the Vulkan device it already has). 0 if the platform has none. */ +static dev_t dmabuf_render_node(void) { + static const char *nodes[] = {"/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/card0"}; + struct stat st; + if (g_no_render_node) return 0; + for (size_t i = 0; i < sizeof(nodes) / sizeof(nodes[0]); i++) + if (!stat(nodes[i], &st) && S_ISCHR(st.st_mode)) return st.st_rdev; + return 0; +} + +static void dmabuf_build_feedback(void) { + struct dmabuf_fmt_entry entries[DMABUF_NFMT_MAX * DMABUF_NMOD]; + int n = 0; + + g_main_device = dmabuf_render_node(); + for (int f = 0; f < g_dmabuf_nfmt; f++) + for (int m = 0; m < g_dmabuf_fmts[f].n; m++) { + if (g_dmabuf_fmts[f].mods[m] == MOD_INVALID) continue; /* never offer INVALID here */ + entries[n].format = g_dmabuf_fmts[f].fmt; + entries[n].pad = 0; + entries[n].modifier = g_dmabuf_fmts[f].mods[m]; + n++; + } + if (!n) return; + + int fd = (int)syscall(__NR_memfd_create, "wn-dmabuf-formats", + MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd < 0) { WLOGE("dmabuf feedback: memfd_create failed (%s)", strerror(errno)); return; } + size_t size = (size_t)n * sizeof(entries[0]); + if (write(fd, entries, size) != (ssize_t)size) { + WLOGE("dmabuf feedback: could not write the format table (%s)", strerror(errno)); + close(fd); + return; + } + /* The client mmaps this read-only and trusts it not to change under it. */ + fcntl(fd, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE); + g_fmt_table_fd = fd; + g_fmt_table_size = size; + g_fmt_table_n = (uint16_t)n; + wnc_log("dmabuf", "feedback ready: %d format/modifier pairs, main device %u:%u", + n, (unsigned)major(g_main_device), (unsigned)minor(g_main_device)); + /* Vulkan games never need the node. Mesa's EGL did until Wayland layer versionCode 9: without + * one it fell back to a software path that draws nothing here (black window, sound plays). */ + if (!g_main_device) + wnc_log("dmabuf", "%s: OpenGL games need Wayland layer versionCode 9 or newer, " + "which runs OpenGL on the GPU without a DRM node; older layers show a black window", + g_no_render_node ? "no DRM device named (forced by BANNER_WAYLAND_NO_RENDER_NODE=1)" + : "this device gives apps no display (DRM) device (/dev/dri)"); +} + +/* One tranche: our device, every pair in the table, no scanout flag. */ +static void dmabuf_feedback_send(struct wl_resource *fb) { + struct wl_array dev, idx; + uint16_t *ind; + + if (g_fmt_table_fd < 0) { zwp_linux_dmabuf_feedback_v1_send_done(fb); return; } + + zwp_linux_dmabuf_feedback_v1_send_format_table(fb, g_fmt_table_fd, (uint32_t)g_fmt_table_size); + + wl_array_init(&dev); + memcpy(wl_array_add(&dev, sizeof(dev_t)), &g_main_device, sizeof(dev_t)); + zwp_linux_dmabuf_feedback_v1_send_main_device(fb, &dev); + zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(fb, &dev); + wl_array_release(&dev); + + wl_array_init(&idx); + ind = wl_array_add(&idx, (size_t)g_fmt_table_n * sizeof(uint16_t)); + if (ind) for (uint16_t i = 0; i < g_fmt_table_n; i++) ind[i] = i; + zwp_linux_dmabuf_feedback_v1_send_tranche_formats(fb, &idx); + wl_array_release(&idx); + + zwp_linux_dmabuf_feedback_v1_send_tranche_flags(fb, 0); + zwp_linux_dmabuf_feedback_v1_send_tranche_done(fb); + zwp_linux_dmabuf_feedback_v1_send_done(fb); +} + +static void dmabuf_feedback_destroy(struct wl_client *c, struct wl_resource *r) { + wl_resource_destroy(r); +} +static const struct zwp_linux_dmabuf_feedback_v1_interface dmabuf_feedback_impl = { + .destroy = dmabuf_feedback_destroy, +}; + +static void dmabuf_new_feedback(struct wl_client *c, struct wl_resource *parent, uint32_t id) { + struct wl_resource *fb = wl_resource_create(c, &zwp_linux_dmabuf_feedback_v1_interface, + wl_resource_get_version(parent), id); + if (!fb) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(fb, &dmabuf_feedback_impl, NULL, NULL); + if (!g_dmabuf_fmts_ready) dmabuf_build_formats(); + dmabuf_feedback_send(fb); +} + +static void dmabuf_get_default_feedback(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct client_info *ci = client_info_of(c); + if (ci) ci->asked_feedback = 1; + dmabuf_new_feedback(c, r, id); +} +/* Per-surface feedback would let us hint a different tranche for a window on its own display + * layer; we have nothing better to say per surface, so it is the default one. */ +static void dmabuf_get_surface_feedback(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface) { + dmabuf_new_feedback(c, r, id); +} + +static void bind_dmabuf(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &zwp_linux_dmabuf_v1_interface, ver, id); + wl_resource_set_implementation(r, &dmabuf_impl, NULL, NULL); + if (!g_dmabuf_fmts_ready) dmabuf_build_formats(); + /* From version 4 the format and modifier events are gone: the client asks for feedback + * instead, and sending both would only confuse it about which list is authoritative. */ + if (ver >= 4) return; + for (int f = 0; f < g_dmabuf_nfmt; f++) { + zwp_linux_dmabuf_v1_send_format(r, g_dmabuf_fmts[f].fmt); + if (ver >= 3) + for (int m = 0; m < g_dmabuf_fmts[f].n; m++) + zwp_linux_dmabuf_v1_send_modifier(r, g_dmabuf_fmts[f].fmt, + (uint32_t)(g_dmabuf_fmts[f].mods[m] >> 32), + (uint32_t)(g_dmabuf_fmts[f].mods[m] & 0xffffffff)); + } +} + +/* ------------------------------------------------------------------ wl_output */ + +static void bind_output(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wl_output_interface, ver, id); + wl_resource_set_implementation(r, NULL, NULL, NULL); + wl_output_send_geometry(r, 0, 0, 340, 190, WL_OUTPUT_SUBPIXEL_UNKNOWN, + "WinNative", "Wayland", WL_OUTPUT_TRANSFORM_NORMAL); + wl_output_send_mode(r, WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED, + g_output_w > 0 ? g_output_w : 1920, g_output_h > 0 ? g_output_h : 1080, + g_output_refresh_mhz > 0 ? g_output_refresh_mhz : 60000); + if (ver >= WL_OUTPUT_NAME_SINCE_VERSION) { + wl_output_send_name(r, "WinNative-1"); + wl_output_send_description(r, "WinNative display"); + } + if (ver >= 2) { + wl_output_send_scale(r, 1); + wl_output_send_done(r); + } +} + +/* ------------------------------------------------------------------ rendering */ + +struct draw_list { struct vkp_draw *d; int n, cap; }; + +static struct vkp_image *surface_image(struct surface *s) { + if (!s->has_content) return NULL; + if (s->shm_img) return s->shm_img; + return s->dmabuf_buf ? s->dmabuf_buf->img : NULL; +} + +/* HDR (gate open) only: the scene's HDR surface whose current frame is one of the game's gralloc buffers + * that the compositor's own driver could NOT import (a 10-bit UBWC layout it does not take, say). Such a + * frame has no draw, so neither layer_candidate() nor ahb_layer_only_candidate() (toplevels only) can + * find it - and a Vulkan game's swapchain is a SUBSURFACE of its window. Remembered here while the scene + * is built so it can still go on the display layer, which never needed the import. Reset per scene; + * never set while the HDR gate is closed. */ +static struct surface *g_hdr_unimported; +static int g_hdr_unimported_rect[4], g_hdr_unimported_below; /* scene x,y,w,h; draws under it */ + +static void note_hdr_unimported(const struct draw_list *dl, struct surface *s, int ox, int oy) { + if (!s->dmabuf_buf || s->dmabuf_buf->img || !ahb_swapchain_has_ahb(s->dmabuf_buf)) return; + const struct wnc_color *c = wnc_color_of(s->resource); + if (!c || !c->dataspace) return; + if (s->src_set && (s->src[0] != 0 || s->src[1] != 0 || (int)(s->src[2] + 0.5f) != s->buf_w || + (int)(s->src[3] + 0.5f) != s->buf_h)) return; /* cropped: the layer shows whole buffers */ + int dw, dh; + surface_size(s, &dw, &dh); + g_hdr_unimported = s; + g_hdr_unimported_rect[0] = ox; g_hdr_unimported_rect[1] = oy; + g_hdr_unimported_rect[2] = dw; g_hdr_unimported_rect[3] = dh; + g_hdr_unimported_below = dl->n; +} + +/* Whether what is under the surface shows through it: its buffer has alpha, and the program - one + * that says where its surfaces are opaque - has not said so for all of this one. */ +static int surface_translucent(const struct surface *s, int w, int h) { + if (!s->buf_alpha) return 0; + const struct client_info *ci = client_info_of(wl_resource_get_client(s->resource)); + if (!ci || !ci->declares_opaque) return 0; + const int *o = s->opaque; + return !(o[2] > 0 && o[0] <= 0 && o[1] <= 0 && (long long)o[0] + o[2] >= w && (long long)o[1] + o[3] >= h); +} + +static void add_surface(struct draw_list *dl, struct surface *s, int ox, int oy) { + struct vkp_image *img = surface_image(s); + float sx = 0, sy = 0, sw = (float)s->buf_w, sh = (float)s->buf_h; + int dw, dh; + + if (!img) { + if (g_zero_copy && wnc_color_hdr_open()) note_hdr_unimported(dl, s, ox, oy); + return; + } + if (s->src_set) { sx = s->src[0]; sy = s->src[1]; sw = s->src[2]; sh = s->src[3]; } + surface_size(s, &dw, &dh); + if (dw <= 0 || dh <= 0 || sw <= 0 || sh <= 0) return; + if (dl->n == dl->cap) { + int cap = dl->cap ? dl->cap * 2 : 32; + struct vkp_draw *d = realloc(dl->d, (size_t)cap * sizeof(*d)); + if (!d) return; + dl->d = d; + dl->cap = cap; + } + dl->d[dl->n++] = (struct vkp_draw){img, sx, sy, sw, sh, ox, oy, dw, dh, surface_translucent(s, dw, dh)}; + s->drawn = 1; +} + +static void add_tree(struct draw_list *dl, struct surface *s, int ox, int oy, int depth) { + struct surface *c; + if (depth > 8) return; + wl_list_for_each(c, &s->children, child_link) + if (c->below_parent) add_tree(dl, c, ox + c->sub_x, oy + c->sub_y, depth + 1); + add_surface(dl, s, ox, oy); + wl_list_for_each(c, &s->children, child_link) + if (!c->below_parent) add_tree(dl, c, ox + c->sub_x, oy + c->sub_y, depth + 1); +} + +/* Scene size: the desktop's, or without one the largest window's (a single + * fullscreen game then fills the screen exactly as before). */ +static void scene_size(int *w, int *h) { + struct surface *s; + long long best = 0; + + if (g_desktop && g_desktop->has_content) { + surface_size(g_desktop, w, h); + g_desktop_w = *w; + g_desktop_h = *h; + return; + } + if (g_desktop && g_desktop_w > 0) { *w = g_desktop_w; *h = g_desktop_h; return; } + *w = *h = 0; + wl_list_for_each(s, &g_toplevels, toplevel_link) { + int sw, sh; + surface_size(s, &sw, &sh); + if ((long long)sw * sh > best) { best = (long long)sw * sh; *w = sw; *h = sh; } + } + if (*w <= 0 || *h <= 0) { *w = INPUT_SPACE_W; *h = INPUT_SPACE_H; } +} + +static void fire_all_frames(void) { + struct surface *s; + wl_list_for_each(s, &g_surfaces, link) fire_frames(&s->frames); +} + +/* With no output window (the app is in the background, or the SurfaceView is being rebuilt) + * nothing gets drawn, but the clients must not be left waiting on us. A FIFO swapchain blocks + * in its present until its wp_presentation feedback and frame callback arrive, and a client + * blocked there stops pumping messages: winhandler.exe then wedges behind it and the app + * concludes the program has exited and closes the whole session. (DXVK's mailbox presents + * never wait, which is why only OpenGL/Zink clients died on a background/resume.) Answer every + * pending frame and feedback as discarded, and keep doing so until a window is back. */ +static void pace_without_output(void) { + struct surface *s; + wl_list_for_each(s, &g_surfaces, link) { + fire_frames(&s->frames); + feedback_discard_all(&s->feedback); + } + wl_display_flush_clients(g_display); +} + +static int on_frame_timer(void *data) { + pace_without_output(); + if (vkp_has_window()) schedule_render(); /* it is back: draw the current scene */ + else if (g_frame_timer) wl_event_source_timer_update(g_frame_timer, 16); + return 0; +} + +/* Layer mode: the index of a draw that shows a whole client GPU frame over the whole scene (one + * fullscreen game), or -1. Only the top two positions are looked at: the game must be the topmost + * draw, or have exactly ONE draw above it, which then goes on the overlay layer (sc_layer.h). + * Anything deeper would need more display layers than HWC will compose. */ +static int layer_candidate(const struct draw_list *dl, int scene_w, int scene_h) { + for (int i = dl->n - 1; i >= 0 && i >= dl->n - 2; i--) { + const struct vkp_draw *d = &dl->d[i]; + if (!vkp_image_is_dmabuf(d->img)) continue; + if (d->dx != 0 || d->dy != 0 || d->dw != scene_w || d->dh != scene_h) continue; + if (d->sx != 0 || d->sy != 0 || (int)d->sw != vkp_image_width(d->img) || + (int)d->sh != vkp_image_height(d->img)) continue; + return i; + } + return -1; +} + +/* Zero-copy: the surface whose current GPU frame `img` is (layer_candidate found the draw). */ +static struct surface *surface_for_image(const struct vkp_image *img) { + struct surface *s; + wl_list_for_each(s, &g_surfaces, link) + if (s->dmabuf_buf && s->dmabuf_buf->img == img) return s; + return NULL; +} + +/* Zero-copy: the topmost window whose frame is one of the game's AHardwareBuffers that this + * renderer could NOT import (so it has no draw), covering the whole scene at 0,0 with nothing of + * its own above it: it can only be shown on the layer. NULL otherwise. */ +static struct surface *ahb_layer_only_candidate(int scene_w, int scene_h) { + struct surface *s; + int w, h; + if (wl_list_empty(&g_toplevels)) return NULL; + s = wl_container_of(g_toplevels.prev, s, toplevel_link); + if (!s->dmabuf_buf || s->dmabuf_buf->img || !ahb_swapchain_has_ahb(s->dmabuf_buf)) return NULL; + if ((g_desktop && !s->placed) || s->x != 0 || s->y != 0 || s->src_set || s->dst_set) return NULL; + if (!wl_list_empty(&s->children)) return NULL; + if (g_hide_shell && !strcmp(client_name(wl_resource_get_client(s->resource)), "explorer.exe")) return NULL; + surface_size(s, &w, &h); + if (w != scene_w || h != scene_h || s->buf_w != scene_w || s->buf_h != scene_h) return NULL; + return s; +} + +/* ---- HDR (color_mgmt.h) in the scene + * An HDR frame is only right on the game's own display layer: everything the compositor draws itself - + * the scene blit, the effects chain, frame generation, its swapchain - is 8-bit sRGB, and a PQ-encoded + * frame pushed through it as it stands comes out washed out. So a scene with HDR in it takes one of + * three routes (hdr_plan): + * 0 the HDR game ALONE on its display layer (zero-copy, or one copy) - with at most one SDR window + * above it on the overlay layer where this display can compose that. Best: nothing is converted. + * 1 the HDR PICTURE: everything else without frame generation - screen effects on, a window above + * the game on a display that cannot take a second layer, a windowed game, zero-copy off. The whole + * scene is composed into ONE 10-bit PQ BT.2020 picture (SDR content placed at the SDR white; the + * effects run on it in 10-bit) and put on the game layer tagged BT2020_PQ (hdr_compose.h). + * 2 frame generation: its extra frames need presents of their own, so the scene is composed into PQ + * and presented through an HDR10 swapchain - or, where the screen surface offers none, + * tone-mapped to SDR (correct colours instead of washed out). + * 3 a frame the compositor could not import: only its display layer can show it, so effects and + * frame generation are skipped for it (said in the log). + * The drawer's HDR output switch (wnc_color_output) OFF: the same routes, tone-mapped - route 0 + * becomes route 1 (the whole scene composed, tone-mapped to sRGB, onto the game layer UNtagged), route 2 + * tone-maps into an SDR swapchain, route 3 cannot (nothing can read those frames) and stays HDR, said + * in the log. The game is told nothing; it keeps rendering HDR. + * Nothing here runs while the HDR gate is closed. */ + +struct hdr_scene { + struct vkp_hdr_frame hf; + unsigned char *is_hdr; /* per draw (calloc'd; free after the frame) */ + int n_hdr; + const struct wnc_color *color; /* the topmost HDR draw's description: what the picture is tagged */ + struct surface *who; /* its surface, for the log */ + const char *why; /* route 1: why the game cannot be alone on its layer */ +}; + +/* The HDR surface note_hdr_unimported() saw in THIS scene, when it covers the whole scene at 0,0 and + * nothing is drawn above it: the display layer is the only place its frame can be shown. */ +static struct surface *hdr_unimported_fullscreen(const struct draw_list *dl, int w, int h) { + if (!g_hdr_unimported || g_hdr_unimported_below != dl->n) return NULL; + if (g_hdr_unimported_rect[0] != 0 || g_hdr_unimported_rect[1] != 0 || + g_hdr_unimported_rect[2] != w || g_hdr_unimported_rect[3] != h) return NULL; + return g_hdr_unimported; +} + +static int is_hdr_surface(struct surface *s) { + const struct wnc_color *c = s ? wnc_color_of(s->resource) : NULL; + return c && c->dataspace; +} + +/* Which route this scene takes (see above); fills hs. 0 whenever the gate is closed or no HDR is drawn. */ +static int hdr_plan(const struct draw_list *dl, int w, int h, int fx_on, int framegen, struct hdr_scene *hs) { + memset(hs, 0, sizeof(*hs)); + if (!wnc_color_hdr_open()) return 0; + /* A fullscreen HDR frame this compositor could not import has no draw: layer only (route 3). */ + struct surface *un = hdr_unimported_fullscreen(dl, w, h); + if (!un) { + struct surface *lo = ahb_layer_only_candidate(w, h); + if (lo && is_hdr_surface(lo)) un = lo; + } + if (un && g_zero_copy) { hs->color = wnc_color_of(un->resource); hs->who = un; return 3; } + hs->hf.tonemap = !wnc_color_output(); + if (dl->n <= 0 || !(hs->is_hdr = calloc((size_t)dl->n, 1))) return 0; + for (int i = 0; i < dl->n; i++) { + struct surface *s = surface_for_image(dl->d[i].img); + if (!is_hdr_surface(s)) continue; + hs->is_hdr[i] = 1; + hs->n_hdr++; + hs->color = wnc_color_of(s->resource); /* later draws are above: the topmost wins */ + hs->who = s; + } + if (!hs->n_hdr) { free(hs->is_hdr); hs->is_hdr = NULL; return 0; } + hs->hf.is_hdr = hs->is_hdr; + hs->hf.color = hs->color; + hs->hf.peak_nits = hs->color->max_cll > 0.0f ? hs->color->max_cll + : hs->color->has_st2086 ? hs->color->max_lum : 1000.0f; + if (framegen) return 2; + /* Route 0 when the game can be alone on its layer: no effects, zero-copy on, the fullscreen draw + * is the HDR game, and above it nothing - or one window the overlay layer can carry. Not while the + * HDR output switch is off: its frame as it stands is PQ, and only the composition can tone-map it. */ + int li = g_zero_copy ? layer_candidate(dl, w, h) : -1; + int over = li >= 0 ? dl->n - 1 - li : 0; + if (!hs->hf.tonemap && !fx_on && li >= 0 && hs->is_hdr[li] && + (over == 0 || (over == 1 && sc_layer_overlay_affordable()))) + return 0; + hs->why = hs->hf.tonemap ? "HDR output is switched off in the drawer" + : fx_on ? "screen effects are on" + : !g_zero_copy ? "zero-copy presentation is off" + : (li >= 0 && hs->is_hdr[li]) ? "a window is above the game and this display cannot compose a second layer" + : "the HDR game is not one fullscreen window"; + return 1; +} + +/* One line per change of route (and of its reason, and of the HDR output switch). */ +static void hdr_note_route(int route, const struct hdr_scene *hs) { + static int said = -1, said_tm = -1; + static const char *said_why; + const int tm = route != 0 && !wnc_color_output(); /* route 0 never runs with the switch off */ + if (route == said && tm == said_tm && (route != 1 || hs->why == said_why)) return; + int was = said; + said = route; said_why = hs->why; said_tm = tm; + char name[160] = "the HDR game"; + if (hs->who) describe(hs->who, name, sizeof(name)); + switch (route) { + case 1: + if (tm) + wnc_log("color", "tone-mapped picture for %s: %s - the whole scene is composed and tone-mapped to SDR " + "(HDR peak %.0f nits rolled off to SDR white, screen effects applied after it) on the game's " + "display layer, untagged", name, hs->why ? hs->why : "?", hs->hf.peak_nits); + else + wnc_log("color", "HDR picture for %s: %s - the whole scene is composed into one 10-bit PQ BT.2020 picture " + "(SDR content at %.0f nits, screen effects applied in 10-bit) on the game's display layer, tagged " + "BT2020_PQ", name, hs->why ? hs->why : "?", wnc_color_sdr_white()); + break; + case 2: + if (tm) + wnc_log("color", "HDR with frame generation for %s, HDR output switched off: the scene is tone-mapped to " + "SDR and presented through an SDR screen swapchain", name); + else + wnc_log("color", "HDR with frame generation for %s: the scene is composed into PQ and presented through " + "the screen swapchain (HDR10 where the surface offers it, else tone-mapped to SDR)", name); + break; + case 3: + if (tm) + wnc_log("color", "HDR output is switched off, but %s's HDR frames cannot be imported by the compositor, " + "so nothing can tone-map them: they stay HDR on its own display layer", name); + break; /* otherwise hdr_note_precedence says it */ + default: + if (was == 1 || was == 2) + wnc_log("color", "%s is back on its own display layer (no composition needed)", name); + break; + } +} + +/* Say when effects / frame generation start or stop being skipped for an HDR game (once per change). */ +static void hdr_note_precedence(struct surface *hs, int fx_on, int framegen) { + static int fx_said = -1, fg_said = -1; + int fx_skip = hs && fx_on, fg_skip = hs && framegen; + char name[160] = "the game"; + if (hs) describe(hs, name, sizeof(name)); + if (fx_skip != fx_said) { + if (fx_skip) + wnc_log("color", "screen effects are NOT applied to %s: its HDR frames cannot be imported by the " + "compositor, so only its own display layer can show them (as they are)", name); + else if (fx_said == 1) + wnc_log("color", "screen effects apply to the scene again (no HDR game on its layer, or effects off)"); + fx_said = fx_skip; + } + if (fg_skip != fg_said) { + if (fg_skip) + wnc_log("color", "frame generation is NOT applied to %s: its HDR frames cannot be imported by the " + "compositor, so only its own display layer can show them (as they are)", name); + else if (fg_said == 1) + wnc_log("color", "frame generation applies again (no HDR game on its layer, or frame generation off)"); + fg_said = fg_skip; + } +} + +/* This scene went through the copy path: count every HDR-described surface in it, with the reason. */ +static void hdr_count_copied(const struct draw_list *dl, const char *reason) { + for (int i = 0; i < dl->n; i++) { + struct surface *s = surface_for_image(dl->d[i].img); + const struct wnc_color *c = s ? wnc_color_of(s->resource) : NULL; + if (!c || !c->dataspace) continue; + char name[160]; + describe(s, name, sizeof(name)); + wnc_color_frame_copied(name, reason); + } +} + +static void render_scene(void) { + struct draw_list dl = {0}; + struct surface *s; + int w, h; + const int64_t t_scene = now_ns(); + int copy = 0; /* this scene went through the screen swapchain (the perf line's "copy") */ + + g_dirty = 0; + g_hdr_unimported = NULL; /* found again while the scene is built (HDR gate open only) */ + wl_list_for_each(s, &g_surfaces, link) s->drawn = 0; + scene_size(&w, &h); + if (w != g_scene_w || h != g_scene_h) { + if (g_desktop) wnc_log("desktop", "size %dx%d", w, h); + else wnc_log("desktop", "no desktop: showing %dx%d (largest window)", w, h); + g_scene_w = w; + g_scene_h = h; + } + if (g_desktop && !g_hide_shell) add_tree(&dl, g_desktop, 0, 0, 0); + wl_list_for_each(s, &g_toplevels, toplevel_link) { + if (g_desktop && !s->placed) continue; /* wait for its position */ + if (g_hide_shell && !strcmp(client_name(wl_resource_get_client(s->resource)), "explorer.exe")) continue; + add_tree(&dl, s, s->placed ? s->x : 0, s->placed ? s->y : 0, 0); + } + + int rendered = 0; + int fx_on = vkp_effects_active(); + int framegen = vkp_framegen_active(); + /* HDR (see hdr_plan): route 0 = the old paths below; 1 / 2 = composed; 3 = layer only, effects and + * frame generation skipped. Always 0 while the HDR gate is closed: nothing changes for that session. */ + struct hdr_scene hs; + int hdr_route = hdr_plan(&dl, w, h, fx_on, framegen, &hs); + const char *hdr_copy = NULL; /* why this scene's HDR frames (if any) took the copy path */ + if (wnc_color_hdr_open()) { + hdr_note_route(hdr_route, &hs); + hdr_note_precedence(hdr_route == 3 ? hs.who : NULL, fx_on, framegen); + } + if (hdr_route == 3) { fx_on = 0; framegen = 0; } + if (hdr_route == 1) { + static int fell_back; + vkp_apply_window_request(); + sc_layer_hide_overlay(); + if (sc_layer_present_hdr_scene(dl.d, dl.n, &hs.hf, w, h, hs.color) == 0) { + rendered = vkp_base_black(w, h) == 0; /* black under the composed picture, presented only when it is not already */ + if (fell_back) { fell_back = 0; wnc_log("color", "the composed picture is on the game's display layer again"); } + } else { + hdr_route = 2; /* no layer this frame: the picture goes through the swapchain instead */ + if (!fell_back) { + fell_back = 1; + wnc_log("color", "the composed picture could not go on the game's display layer (no layer, or the " + "10-bit pass failed): it goes through the screen swapchain instead until that changes"); + } + } + } + if (hdr_route == 2) { + int how = 0; + sc_layer_hide(); + rendered = vkp_render_hdr(w, h, dl.d, dl.n, &hs.hf, &how) == 0; + copy = 1; + if (rendered && how == 1) wnc_color_frame_shown(hs.color, WNC_HDR_SWAPCHAIN, 0); + else if (rendered && how == 2) wnc_color_frame_shown(hs.color, WNC_HDR_TONEMAPPED, 0); + else if (rendered) hdr_copy = "the HDR composition pass is unavailable on this driver"; + } + if (hdr_route == 0 || hdr_route == 3) { + /* What still forces the whole scene back through the compositor pass (and so through the app's + * own swapchain), and what no longer does: + * - frame generation ALWAYS does: its extra frames each need a present of their own on + * consecutive vblanks, and a display layer latches one buffer per refresh; + * - screen effects do NOT any more when the game is alone on screen - the chain runs and its + * result is copied into the game layer's own gralloc buffer (sc_layer_present_pass), so the + * game keeps its layer and the display does the scene -> output mapping; + * - screen effects DO when a window is drawn above the game: the chain has to see the whole + * scene to look the way it does on the copy path, and here the game is only part of it. */ + const int pass_on = fx_on || framegen; + int li = (g_zero_copy && !framegen) ? layer_candidate(&dl, w, h) : -1; + int over = li >= 0 ? dl.n - 1 - li : 0; /* draws above the fullscreen game (0 or 1) */ + const int fx_blocks = (li >= 0 && fx_on && over > 0); + if (fx_blocks) { li = -1; over = 0; } + /* A window above the game needs a SECOND display layer, and on some displays that costs the + * hardware composition the first layer was worth having (sc_layer_overlay_affordable). Where it + * does, the whole scene goes down the copy path exactly as it did before the layer set existed. + * Decided HERE, before the game is committed to a layer: deciding it after the present would + * show the game layer and hide it again on every frame. */ + const int ov_blocks = (li >= 0 && over > 0 && !fx_blocks && !sc_layer_overlay_affordable()); + if (ov_blocks) { li = -1; over = 0; } + const int blocked = framegen ? 1 : (fx_blocks ? 2 : (ov_blocks ? 3 : 0)); + if (g_zero_copy && blocked != g_zero_copy_paused) { + g_zero_copy_paused = blocked; + if (blocked == 1) + wnc_log("framegen", "zero-copy paused: frame generation needs the compositor pass"); + else if (blocked == 2) + wnc_log("effects", "zero-copy paused: a window above the game needs the compositor pass for the whole scene"); + else if (blocked == 3) + wnc_log("layer", "zero-copy paused: a window above the game would need a second display layer, " + "which this display cannot compose in hardware - whole scene on the copy path"); + else + wnc_log("effects", "zero-copy resumed: the game is back on its own display layer"); + } + struct surface *ls = li >= 0 ? surface_for_image(dl.d[li].img) : NULL; + /* A frame this renderer could not import can only be shown on the layer, effects or not. */ + if (li < 0) ls = ahb_layer_only_candidate(w, h); + /* HDR: the same, for a game's SUBSURFACE swapchain whose 10-bit frame the compositor could not + * import (NULL whenever the HDR gate is closed - nothing is ever noted then). */ + if (li < 0 && !ls) ls = hdr_unimported_fullscreen(&dl, w, h); + if (ls && pass_on && li < 0 && !g_zero_copy_fx_skip_said) { + g_zero_copy_fx_skip_said = 1; + wnc_log(framegen ? "framegen" : "effects", + "the game's frames cannot be imported by the compositor: shown zero-copy, %s skipped", + framegen ? "frame generation" : "effects"); + } + if (li >= 0 || ls) { + /* Layer mode: the fullscreen window goes on the game layer and (at most) one window above + * it on the overlay layer; whatever is under the game is hidden by it, so the screen + * surface only needs to be black. The game's frame goes on its layer as is when it is one + * of the game's own AHardwareBuffers (zero-copy, ahb_swapchain.c), through the effects + * chain's result when a Look is on, else through one blit into the pool. If a layer can't + * take its frame, the whole scene is drawn the usual way. */ + /* The layers go up FIRST and the base surface's black frame is presented after them. The + * order matters: a present holds an acquired swapchain image, and the acquire semaphore is + * a vblank gate - putting the effects chain or the layer transaction behind it costs a + * whole refresh (measured on the Pocket FIT: 72 fps that way, 111 fps this way, with Retro + * CRT at 1920x1080). The window request the app may have left is applied here instead, + * since vkp_render no longer runs before the layer path. */ + vkp_apply_window_request(); + int r = -1; + /* The game's own buffer can only go on the layer AS IS - with a Look on, the frame has to + * go through the chain first, so the raw buffer is skipped in favour of the pass (a frame + * this renderer could not import has no draw, li < 0, and stays zero-copy with the effects + * skipped, said once above). */ + if (!(fx_on && li >= 0) && ls && ls->dmabuf_buf && ahb_swapchain_has_ahb(ls->dmabuf_buf)) + r = ahb_swapchain_present(ls->dmabuf_buf, ls, w, h); + if (r != 0 && li >= 0) + r = fx_on ? sc_layer_present_pass(&dl.d[li], 1, w, h) + : sc_layer_present(dl.d[li].img, w, h, ls ? wnc_surface_color(ls) : NULL); + if (r == 0) { + if (ls) ls->drawn = 1; + /* The one window above the game keeps the game off the copy path entirely: it goes on + * its own layer, cropped and placed by the display. */ + int go[8], ov = 0; + if (over == 1 && li >= 0 && vkp_map_draw(&dl.d[li + 1], go)) + ov = sc_layer_present_overlay(dl.d[li + 1].img, go) == 0 ? 1 : -1; + if (ov <= 0) sc_layer_hide_overlay(); + if (ov < 0) { /* the overlay layer refused it: draw the whole scene the old way */ + sc_layer_hide(); + rendered = vkp_render(w, h, dl.d, dl.n) == 0; + copy = 1; + hdr_copy = "the overlay layer refused the window above it"; + } else { + /* Black under the opaque layers. It is a plain black frame (no effects, no frame + * generation on it: they would only run on black), and it is presented only when the + * base surface does not already show one - entering layer mode, after a copy-path or + * frame-generation frame, after a swapchain rebuild. Every other layer frame draws and + * presents nothing here: the display layers are all that changes on screen. This loop + * is paced by the Choreographer's ticks (on_vsync), not by this present. */ + rendered = vkp_base_black(w, h) == 0; + } + } else { + rendered = vkp_render(w, h, dl.d, dl.n) == 0; + copy = 1; + hdr_copy = "its display layer was unavailable this frame"; + } + } else { + sc_layer_hide(); + rendered = vkp_render(w, h, dl.d, dl.n) == 0; + copy = 1; + hdr_copy = ov_blocks ? "a window is above it and this display cannot compose a second layer in hardware" + : fx_blocks ? "a window is above it and screen effects need the whole scene" + : !g_zero_copy ? "zero-copy presentation is off" + : framegen ? "frame generation needs the compositor pass" + : "it is not the one fullscreen window (only that one gets its own display layer)"; + } + } /* routes 0 and 3 */ + free(hs.is_hdr); + if (hdr_copy && rendered && wnc_color_hdr_open()) hdr_count_copied(&dl, hdr_copy); + if (rendered) { + int64_t t = now_ns(); + g_stat_frames++; + /* A surface outside the scene (role-less, not placed yet, a hidden helper window such + * as wined3d's device window) was not shown, so its feedback is discarded rather than + * left pending: a FIFO present waits on it, and a client blocked there never commits + * again. */ + wl_list_for_each(s, &g_surfaces, link) { + if (s->drawn) feedback_present_all(&s->feedback, t); + else feedback_discard_all(&s->feedback); + } + fire_all_frames(); + } else { + /* No output surface yet (or it went away): keep clients paced without it, now and on a + * timer that re-arms itself until a window is back (see pace_without_output). */ + pace_without_output(); + if (!g_frame_timer) + g_frame_timer = wl_event_loop_add_timer(wl_display_get_event_loop(g_display), + on_frame_timer, NULL); + if (g_frame_timer) wl_event_source_timer_update(g_frame_timer, 16); + } + free(dl.d); + wl_display_flush_clients(g_display); + if (rendered && copy) g_perf.copy_scenes++; + g_perf.scenes++; + const int64_t scene_ns = now_ns() - t_scene; + g_perf.scene_ns += scene_ns; + if (scene_ns > g_perf.scene_max_ns) g_perf.scene_max_ns = scene_ns; +} + +static int on_fallback_timer(void *data) { + g_fallback_armed = 0; + if (g_dirty) render_scene(); + return 0; +} + +static void schedule_render(void) { + g_dirty = 1; + if (!g_display) return; + /* With vsync ticks flowing the next tick renders; otherwise render on a timer. */ + if (g_last_vsync_ns && now_ns() - g_last_vsync_ns < 100000000LL) return; + if (g_fallback_armed) return; + if (!g_fallback_timer) + g_fallback_timer = wl_event_loop_add_timer(wl_display_get_event_loop(g_display), + on_fallback_timer, NULL); + if (!g_fallback_timer) return; + g_fallback_armed = 1; + wl_event_source_timer_update(g_fallback_timer, 8); +} + +/* A screen refresh (Choreographer tick): draw the newest state once. */ +static void on_vsync(int64_t frame_time_ns) { + int64_t now = now_ns(); + g_perf.ticks++; + if (g_last_vsync_ns) { + int64_t d = now - g_last_vsync_ns; + if (d > 3000000LL && d < 40000000LL) g_refresh_ns = (g_refresh_ns * 7 + d) / 8; + } + g_last_vsync_ns = now; + (void)frame_time_ns; + /* The app's surface may have been replaced or taken away since the last frame: apply that now + * (the app never waits for us), and redraw the scene onto a new one. */ + if (vkp_apply_window_request()) g_dirty = 1; + /* A screen-effect setting changed (JNI, any thread): redraw so it shows on a static scene too. */ + if (vkp_effects_sync()) g_dirty = 1; + if (g_dirty) render_scene(); +} + +/* ------------------------------------------------------------------ wl_seat */ + +static uint32_t now_ms(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); +} + +static void pointer_set_cursor(struct wl_client *c, struct wl_resource *r, uint32_t serial, + struct wl_resource *surface, int32_t hx, int32_t hy) { + /* The app draws its own pointer; cursor surfaces stay role-less and aren't drawn. */ +} +static void pointer_release(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct wl_pointer_interface pointer_impl = { + .set_cursor = pointer_set_cursor, .release = pointer_release, +}; +static void pointer_res_destroy(struct wl_resource *r) { + constraints_pointer_gone(r); + relative_pointers_pointer_gone(r); + for (int i = 0; i < g_nptrs; i++) + if (g_ptrs[i].ptr == r) { g_ptrs[i] = g_ptrs[--g_nptrs]; break; } +} + +static void keyboard_release(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct wl_keyboard_interface keyboard_impl = { .release = keyboard_release }; +static void keyboard_res_destroy(struct wl_resource *r) { + for (int i = 0; i < g_nkbs; i++) + if (g_kbs[i].kb == r) { g_kbs[i] = g_kbs[--g_nkbs]; break; } +} +static void touch_release(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct wl_touch_interface touch_impl = { .release = touch_release }; + +static void seat_get_pointer(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct wl_resource *p = wl_resource_create(c, &wl_pointer_interface, wl_resource_get_version(r), id); + if (!p) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(p, &pointer_impl, NULL, pointer_res_destroy); + if (g_nptrs < MAX_PTRS) { g_ptrs[g_nptrs].ptr = p; g_ptrs[g_nptrs].focus = NULL; g_nptrs++; } +} +static void seat_get_keyboard(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct wl_resource *k = wl_resource_create(c, &wl_keyboard_interface, wl_resource_get_version(r), id); + if (!k) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(k, &keyboard_impl, NULL, keyboard_res_destroy); + if (g_nkbs < MAX_PTRS) { g_kbs[g_nkbs].kb = k; g_kbs[g_nkbs].focus = NULL; g_nkbs++; } + /* Send the xkb keymap the app extracted to $XDG_RUNTIME_DIR/keymap.xkb. Without a keymap the + * client can't interpret our evdev key codes. */ + const char *rt = getenv("XDG_RUNTIME_DIR"); + char path[512]; + snprintf(path, sizeof(path), "%s/keymap.xkb", rt ? rt : "."); + int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + struct stat st; + if (fstat(fd, &st) == 0 && st.st_size > 0) + wl_keyboard_send_keymap(k, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, fd, (uint32_t)st.st_size); + close(fd); + } else { + WLOGE("keymap open failed: %s", path); + } + if (wl_resource_get_version(k) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) + wl_keyboard_send_repeat_info(k, 25, 500); +} +static void seat_get_touch(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct wl_resource *t = wl_resource_create(c, &wl_touch_interface, wl_resource_get_version(r), id); + if (t) wl_resource_set_implementation(t, &touch_impl, NULL, NULL); +} +static void seat_release(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct wl_seat_interface seat_impl = { + .get_pointer = seat_get_pointer, .get_keyboard = seat_get_keyboard, + .get_touch = seat_get_touch, .release = seat_release, +}; +static void bind_seat(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wl_seat_interface, ver, id); + wl_resource_set_implementation(r, &seat_impl, NULL, NULL); + wl_seat_send_capabilities(r, WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD); + if (ver >= 2) wl_seat_send_name(r, "winnative-seat"); +} + +/* A client may hold several wl_pointer/wl_keyboard objects (gamescope reads input on its own + * thread through a second pair): events go to every one of them. */ +static int client_has_pointer(struct wl_client *client) { + for (int i = 0; i < g_nptrs; i++) + if (wl_resource_get_client(g_ptrs[i].ptr) == client) return 1; + return 0; +} +static int client_has_keyboard(struct wl_client *client) { + for (int i = 0; i < g_nkbs; i++) + if (wl_resource_get_client(g_kbs[i].kb) == client) return 1; + return 0; +} +static void pointer_frame(struct wl_resource *ptr) { + if (wl_resource_get_version(ptr) >= WL_POINTER_FRAME_SINCE_VERSION) wl_pointer_send_frame(ptr); +} + +/* Topmost window whose scene rectangle contains the point (no-desktop fallback). */ +static struct surface *toplevel_at(double x, double y) { + struct surface *s; + wl_list_for_each_reverse(s, &g_toplevels, toplevel_link) { + int w, h, sx = s->placed ? s->x : 0, sy = s->placed ? s->y : 0; + surface_size(s, &w, &h); + if (x >= sx && y >= sy && x < sx + w && y < sy + h) return s; + } + return NULL; +} + +static void pointer_focus(struct wl_resource *target, wl_fixed_t fx, wl_fixed_t fy) { + struct wl_client *client = wl_resource_get_client(target); + int entered = 0; + for (int i = 0; i < g_nptrs; i++) { + if (!g_ptrs[i].focus || g_ptrs[i].focus == target) continue; + wl_pointer_send_leave(g_ptrs[i].ptr, wl_display_next_serial(g_display), g_ptrs[i].focus); + pointer_frame(g_ptrs[i].ptr); + g_ptrs[i].focus = NULL; + } + for (int i = 0; i < g_nptrs; i++) { + struct seat_pointer *sp = &g_ptrs[i]; + if (wl_resource_get_client(sp->ptr) != client || sp->focus == target) continue; + sp->focus = target; + wl_pointer_send_enter(sp->ptr, wl_display_next_serial(g_display), target, fx, fy); + entered = 1; + } + if (entered) constraints_focus_entered(target, client); +} + +/* The seat's modifiers in the keymap's real-modifier bits. Wine and gamescope work them out from + * the keys they are sent; a nested wlroots compositor (the desktop's labwc) takes them from + * wl_keyboard.modifiers alone, and without it Shift never reaches its programs. */ +static uint32_t g_mod_keys_held, g_mods_locked; + +static uint32_t mods_depressed(void) { + static const uint32_t bits[] = { 1u << 0, 1u << 0, 1u << 2, 1u << 2, 1u << 3, 1u << 3, 1u << 6, 1u << 6 }; + uint32_t mods = 0; + for (unsigned i = 0; i < sizeof(bits) / sizeof(bits[0]); i++) + if (g_mod_keys_held & (1u << i)) mods |= bits[i]; + return mods; +} + +/* Returns whether the key changed the seat's modifiers. */ +static int mods_key(uint32_t evdev, int pressed) { + static const uint32_t keys[] = { 42, 54, 29, 97, 56, 100, 125, 126 }; /* L/R Shift, Ctrl, Alt, Meta */ + if (evdev == 58) { /* Caps Lock toggles on its press */ + if (pressed) g_mods_locked ^= 1u << 1; + return pressed; + } + for (unsigned i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) { + if (keys[i] != evdev) continue; + uint32_t held = pressed ? g_mod_keys_held | (1u << i) : g_mod_keys_held & ~(1u << i); + if (held == g_mod_keys_held) return 0; + g_mod_keys_held = held; + return 1; + } + return 0; +} + +static void keyboard_focus(struct wl_resource *target) { + struct wl_client *client = wl_resource_get_client(target); + int entered = 0; + struct wl_array keys; + wl_array_init(&keys); + for (int i = 0; i < g_nkbs; i++) { + struct seat_keyboard *sk = &g_kbs[i]; + if (sk->focus == target) continue; + if (sk->focus) { + wl_keyboard_send_leave(sk->kb, wl_display_next_serial(g_display), sk->focus); + sk->focus = NULL; + } + if (wl_resource_get_client(sk->kb) != client) continue; + sk->focus = target; + wl_keyboard_send_enter(sk->kb, wl_display_next_serial(g_display), target, &keys); + wl_keyboard_send_modifiers(sk->kb, wl_display_next_serial(g_display), mods_depressed(), 0, g_mods_locked, 0); + entered = 1; + } + wl_array_release(&keys); + if (entered) wnc_clipboard_keyboard_focus(client); /* wl_data_device selection follows focus */ +} + +/* ------------------------------------------------------------------ pointer constraints + * zwp_pointer_constraints_v1 and zwp_relative_pointer_manager_v1: what winewayland uses for + * SetCursorPos, ClipCursor and hidden-cursor (mouse-look) games. + * + * A lock freezes the pointer where it is: no wl_pointer.motion is sent while it holds, and + * every input delta (a relative event from the app, or the difference between absolute + * positions) reaches the locking program as zwp_relative_pointer_v1.relative_motion. A confine + * clamps the pointer to the surface (and the program's region inside it). Relative motion is + * also sent while unlocked, to any program holding a relative pointer. + * + * Input normally goes to the desktop surface; while a constraint holds, pointer focus moves + * to the constrained surface, so the constraining program receives the events itself + * (winewayland only enables relative motion for a window its pointer has entered). One + * constraint holds at a time; a newer request ends the older one. A lock's cursor position + * hint is applied on the surface's commit and is where the pointer ends up when the lock + * ends (winewayland's SetCursorPos: lock, hint, commit, unlock). */ + +struct constraint { + struct wl_list link; /* g_constraints */ + struct wl_resource *resource; /* zwp_locked_pointer_v1 or zwp_confined_pointer_v1 */ + struct wl_resource *pointer; /* the program's wl_pointer; NULL once released */ + struct surface *surface; /* NULL once the surface is gone */ + int is_lock; + uint32_t lifetime; + int active; + int defunct; /* a oneshot that ended, or surface/pointer gone */ + struct region region, pending_region; /* confine area, surface-local; unset = whole surface */ + int pending_region_set; + int hint_set, pending_hint_set; /* lock: cursor position hint, surface-local */ + double hint_x, hint_y, pending_hint_x, pending_hint_y; +}; +static struct wl_list g_constraints; +static struct constraint *g_active_constraint; + +struct relative_pointer { + struct wl_list link; /* g_relative_pointers */ + struct wl_resource *resource; + struct wl_resource *pointer; /* the program's wl_pointer; NULL once released */ +}; +static struct wl_list g_relative_pointers; + +/* Last pointer position in scene coordinates (buttons and scrolls from the app's X-server + * input path arrive without one). */ +static double g_ptr_x, g_ptr_y; +/* Last absolute input position: absolute input becomes deltas while the pointer is locked. */ +static double g_raw_x, g_raw_y; +static int g_raw_valid; + +/* JNI: the app switches its touch/mouse path to deltas while a lock holds and re-syncs its + * pointer to x,y when it ends. */ +extern void wnc_on_pointer_lock(int locked, int x, int y); + +static void sync_lock_notify(void) { + static int last; + int locked = g_active_constraint && g_active_constraint->is_lock; + if (locked == last) return; + last = locked; + wnc_on_pointer_lock(locked, (int)g_ptr_x, (int)g_ptr_y); +} + +static void constraint_describe(const struct constraint *k, char *out, size_t size) { + if (k->surface) describe(k->surface, out, size); + else snprintf(out, size, "a closed window"); +} + +/* Scene position of the constrained surface. */ +static void constraint_origin(const struct constraint *k, int *x, int *y) { + *x = *y = 0; + if (k->surface && k->surface != g_desktop && k->surface->placed) { *x = k->surface->x; *y = k->surface->y; } +} + +/* Keep x,y (scene coordinates) inside the confine area. */ +static void confine_clamp(const struct constraint *k, double *x, double *y) { + int ox, oy, w, h, rx = 0, ry = 0, rw, rh; + if (!k->surface) return; + constraint_origin(k, &ox, &oy); + surface_size(k->surface, &w, &h); + rw = w; rh = h; + if (k->region.set) { + int x2 = rx + rw < k->region.x + k->region.w ? rx + rw : k->region.x + k->region.w; + int y2 = ry + rh < k->region.y + k->region.h ? ry + rh : k->region.y + k->region.h; + if (k->region.x > rx) rx = k->region.x; + if (k->region.y > ry) ry = k->region.y; + rw = x2 - rx; rh = y2 - ry; + } + if (rw <= 0 || rh <= 0) return; + if (*x < ox + rx) *x = ox + rx; + if (*x > ox + rx + rw - 1) *x = ox + rx + rw - 1; + if (*y < oy + ry) *y = oy + ry; + if (*y > oy + ry + rh - 1) *y = oy + ry + rh - 1; +} + +static void constraint_send_state(struct constraint *k, int on) { + if (k->is_lock) { + if (on) zwp_locked_pointer_v1_send_locked(k->resource); + else zwp_locked_pointer_v1_send_unlocked(k->resource); + } else { + if (on) zwp_confined_pointer_v1_send_confined(k->resource); + else zwp_confined_pointer_v1_send_unconfined(k->resource); + } +} + +/* A constraint stops holding. tell_client = 0 when its resource is being destroyed. */ +static void constraint_end(struct constraint *k, int tell_client, const char *why) { + char name[160]; + if (!k->active) return; + k->active = 0; + if (g_active_constraint == k) g_active_constraint = NULL; + if (k->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) k->defunct = 1; + if (k->is_lock && k->hint_set && k->surface) { + int ox, oy; + constraint_origin(k, &ox, &oy); + g_ptr_x = ox + k->hint_x; + g_ptr_y = oy + k->hint_y; + } + g_raw_valid = 0; + if (tell_client) constraint_send_state(k, 0); + constraint_describe(k, name, sizeof(name)); + wnc_log("pointer", "%s: %s (%s), pointer at %d,%d", k->is_lock ? "unlocked" : "unconfined", + name, why, (int)g_ptr_x, (int)g_ptr_y); + sync_lock_notify(); +} + +static void constraint_activate(struct constraint *k) { + char name[160]; + int ox, oy; + if (k->active || k->defunct || !k->surface || !k->pointer) return; + if (g_active_constraint && g_active_constraint != k) + constraint_end(g_active_constraint, 1, "replaced by a newer request"); + k->active = 1; + g_active_constraint = k; + if (!k->is_lock) confine_clamp(k, &g_ptr_x, &g_ptr_y); + /* Pointer focus follows the constraint: the program gets the events itself. */ + constraint_origin(k, &ox, &oy); + pointer_focus(k->surface->resource, wl_fixed_from_double(g_ptr_x - ox), wl_fixed_from_double(g_ptr_y - oy)); + constraint_send_state(k, 1); + constraint_describe(k, name, sizeof(name)); + if (k->is_lock) + wnc_log("pointer", "locked: %s (%s), pointer frozen at %d,%d", name, + k->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT ? "oneshot" : "persistent", + (int)g_ptr_x, (int)g_ptr_y); + else if (k->region.set) + wnc_log("pointer", "confined: %s (%s) to %dx%d at %d,%d of the window", name, + k->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT ? "oneshot" : "persistent", + k->region.w, k->region.h, k->region.x, k->region.y); + else + wnc_log("pointer", "confined: %s (%s) to the whole window", name, + k->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT ? "oneshot" : "persistent"); + sync_lock_notify(); + wl_display_flush_clients(g_display); +} + +/* Pointer focus entered target: a persistent constraint waiting on it takes hold again. */ +static void constraints_focus_entered(struct wl_resource *target, struct wl_client *client) { + struct constraint *k; + if (g_active_constraint) return; + wl_list_for_each(k, &g_constraints, link) { + if (k->active || k->defunct || !k->surface || !k->pointer) continue; + if (k->surface->resource != target || wl_resource_get_client(k->pointer) != client) continue; + constraint_activate(k); + return; + } +} + +static void constraints_surface_gone(struct surface *s) { + struct constraint *k; + wl_list_for_each(k, &g_constraints, link) { + if (k->surface != s) continue; + constraint_end(k, 1, "window closed"); + k->surface = NULL; + k->defunct = 1; + } +} + +static void constraints_pointer_gone(struct wl_resource *pointer) { + struct constraint *k; + wl_list_for_each(k, &g_constraints, link) { + if (k->pointer != pointer) continue; + constraint_end(k, 1, "pointer released"); + k->pointer = NULL; + k->defunct = 1; + } +} + +/* Double-buffered state (position hint, confine region) applies on the surface's commit. */ +static void constraints_surface_commit(struct surface *s) { + struct constraint *k; + wl_list_for_each(k, &g_constraints, link) { + if (k->surface != s) continue; + if (k->pending_hint_set) { + k->hint_set = 1; + k->hint_x = k->pending_hint_x; + k->hint_y = k->pending_hint_y; + k->pending_hint_set = 0; + if (k->active && k->is_lock) { + int ox, oy; + constraint_origin(k, &ox, &oy); + g_ptr_x = ox + k->hint_x; + g_ptr_y = oy + k->hint_y; + if (log_budget()) + wnc_log("pointer", "position hint: pointer moved to %d,%d", (int)g_ptr_x, (int)g_ptr_y); + } + } + if (k->pending_region_set) { + k->region = k->pending_region; + k->pending_region_set = 0; + if (k->active && !k->is_lock) confine_clamp(k, &g_ptr_x, &g_ptr_y); + } + } +} + +static void constraint_resource_destroy(struct wl_resource *r) { + struct constraint *k = wl_resource_get_user_data(r); + if (!k) return; + constraint_end(k, 0, "released by the program"); + wl_list_remove(&k->link); + free(k); +} + +static void constraint_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void locked_pointer_set_cursor_position_hint(struct wl_client *c, struct wl_resource *r, + wl_fixed_t x, wl_fixed_t y) { + struct constraint *k = wl_resource_get_user_data(r); + if (!k) return; + k->pending_hint_set = 1; + k->pending_hint_x = wl_fixed_to_double(x); + k->pending_hint_y = wl_fixed_to_double(y); +} +static void locked_pointer_set_region(struct wl_client *c, struct wl_resource *r, struct wl_resource *region) { + /* A lock's region only says where it may take hold; a lock here takes hold anywhere. */ +} +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl = { + .destroy = constraint_destroy_req, + .set_cursor_position_hint = locked_pointer_set_cursor_position_hint, + .set_region = locked_pointer_set_region, +}; +static void confined_pointer_set_region(struct wl_client *c, struct wl_resource *r, struct wl_resource *region) { + struct constraint *k = wl_resource_get_user_data(r); + struct region *rg = region ? wl_resource_get_user_data(region) : NULL; + if (!k) return; + k->pending_region_set = 1; + if (rg) k->pending_region = *rg; + else memset(&k->pending_region, 0, sizeof(k->pending_region)); +} +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl = { + .destroy = constraint_destroy_req, + .set_region = confined_pointer_set_region, +}; + +static void constraint_create(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface_res, struct wl_resource *pointer_res, + struct wl_resource *region_res, uint32_t lifetime, int is_lock) { + struct surface *s = wl_resource_get_user_data(surface_res); + struct constraint *k; + struct wl_resource *res; + char name[160]; + + wl_list_for_each(k, &g_constraints, link) { + if (k->defunct || k->surface != s || k->pointer != pointer_res) continue; + wl_resource_post_error(r, ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, + "the surface already has a pointer constraint for this pointer"); + return; + } + res = wl_resource_create(c, is_lock ? &zwp_locked_pointer_v1_interface : &zwp_confined_pointer_v1_interface, + wl_resource_get_version(r), id); + if (!res || !(k = calloc(1, sizeof(*k)))) { wl_client_post_no_memory(c); return; } + k->resource = res; + k->pointer = pointer_res; + k->surface = s; + k->is_lock = is_lock; + k->lifetime = lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT + ? ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT : ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; + if (!is_lock && region_res) { + struct region *rg = wl_resource_get_user_data(region_res); + if (rg) k->region = *rg; + } + wl_resource_set_implementation(res, is_lock ? (const void *)&locked_pointer_impl : (const void *)&confined_pointer_impl, + k, constraint_resource_destroy); + wl_list_insert(g_constraints.prev, &k->link); + if (!s) { k->defunct = 1; return; } + constraint_describe(k, name, sizeof(name)); + wnc_log("pointer", "%s requested by %s for %s", is_lock ? "lock" : "confine", client_name(c), name); + constraint_activate(k); +} + +static void pointer_constraints_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void pointer_constraints_lock_pointer(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, uint32_t lifetime) { + constraint_create(c, r, id, surface, pointer, region, lifetime, 1); +} +static void pointer_constraints_confine_pointer(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, uint32_t lifetime) { + constraint_create(c, r, id, surface, pointer, region, lifetime, 0); +} +static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl = { + .destroy = pointer_constraints_destroy, + .lock_pointer = pointer_constraints_lock_pointer, + .confine_pointer = pointer_constraints_confine_pointer, +}; +static void bind_pointer_constraints(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &zwp_pointer_constraints_v1_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &pointer_constraints_impl, NULL, NULL); +} + +/* --- relative pointers --- */ + +static void relative_pointer_resource_destroy(struct wl_resource *r) { + struct relative_pointer *rp = wl_resource_get_user_data(r); + if (!rp) return; + wnc_log("pointer", "relative pointer released by %s", client_name(wl_resource_get_client(r))); + wl_list_remove(&rp->link); + free(rp); +} +static void relative_pointer_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct zwp_relative_pointer_v1_interface relative_pointer_impl = { + .destroy = relative_pointer_destroy_req, +}; + +static void relative_pointer_manager_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void relative_pointer_manager_get(struct wl_client *c, struct wl_resource *r, uint32_t id, + struct wl_resource *pointer) { + struct wl_resource *res = wl_resource_create(c, &zwp_relative_pointer_v1_interface, wl_resource_get_version(r), id); + struct relative_pointer *rp; + if (!res || !(rp = calloc(1, sizeof(*rp)))) { wl_client_post_no_memory(c); return; } + rp->resource = res; + rp->pointer = pointer; + wl_resource_set_implementation(res, &relative_pointer_impl, rp, relative_pointer_resource_destroy); + wl_list_insert(g_relative_pointers.prev, &rp->link); + wnc_log("pointer", "relative pointer created for %s (motion arrives as deltas)", client_name(c)); +} +static const struct zwp_relative_pointer_manager_v1_interface relative_pointer_manager_impl = { + .destroy = relative_pointer_manager_destroy, + .get_relative_pointer = relative_pointer_manager_get, +}; +static void bind_relative_pointer_manager(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &zwp_relative_pointer_manager_v1_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &relative_pointer_manager_impl, NULL, NULL); +} + +static void relative_pointers_pointer_gone(struct wl_resource *pointer) { + struct relative_pointer *rp; + wl_list_for_each(rp, &g_relative_pointers, link) + if (rp->pointer == pointer) rp->pointer = NULL; +} + +/* relative_motion to every relative pointer of the program owning the focus (winewayland wants + * it whenever it holds one, locked or not). Returns whether any was sent. */ +static int send_relative_motion(struct wl_client *client, double dx, double dy) { + struct relative_pointer *rp; + struct timespec ts; + uint64_t ut; + int sent = 0; + if (dx == 0 && dy == 0) return 0; + clock_gettime(CLOCK_MONOTONIC, &ts); + ut = (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000; + wl_list_for_each(rp, &g_relative_pointers, link) { + if (!rp->pointer || wl_resource_get_client(rp->pointer) != client) continue; + zwp_relative_pointer_v1_send_relative_motion(rp->resource, (uint32_t)(ut >> 32), (uint32_t)ut, + wl_fixed_from_double(dx), wl_fixed_from_double(dy), + wl_fixed_from_double(dx), wl_fixed_from_double(dy)); + sent = 1; + } + return sent; +} + +/* ------------------------------------------------------------------ pointer events */ + +/* One pointer event: a motion, then an optional button change (button 0 = none). Absolute + * x,y are scene coordinates; with `relative` they are a delta. While a lock holds the pointer + * stays put and the delta (or the change between absolute positions) becomes relative motion + * for the locking program; a confine keeps the pointer inside its area. */ +static void pointer_input(double x, double y, int relative, uint32_t button, int pressed) { + struct surface *target; + struct constraint *k = g_active_constraint; + double dx, dy; + + if (relative) { + dx = x; dy = y; + } else { + dx = g_raw_valid ? x - g_raw_x : 0; + dy = g_raw_valid ? y - g_raw_y : 0; + g_raw_x = x; g_raw_y = y; g_raw_valid = 1; + } + + if (k && k->is_lock) { + target = k->surface; + } else { + if (relative) { + int w, h; + scene_size(&w, &h); + x = g_ptr_x + dx; y = g_ptr_y + dy; + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x > w - 1) x = w - 1; + if (y > h - 1) y = h - 1; + } + if (k) confine_clamp(k, &x, &y); + g_ptr_x = x; g_ptr_y = y; + if (k) { + target = k->surface; + } else if (g_desktop) { + target = g_desktop; + } else { + target = g_grab ? g_grab : toplevel_at(x, y); + if (button && pressed) { g_grab = target; g_key_target = target; } + if (button && !pressed) g_grab = NULL; + } + } + if (!target) return; + + struct wl_client *client = wl_resource_get_client(target->resource); + if (!client_has_pointer(client)) return; + int tx = 0, ty = 0; + if (target != g_desktop && target->placed) { tx = target->x; ty = target->y; } + wl_fixed_t fx = wl_fixed_from_double(g_ptr_x - tx), fy = wl_fixed_from_double(g_ptr_y - ty); + uint32_t t = now_ms(); + + pointer_focus(target->resource, fx, fy); + for (int i = 0; i < g_nptrs; i++) { + struct wl_resource *ptr = g_ptrs[i].ptr; + if (wl_resource_get_client(ptr) != client) continue; + if (!(k && k->is_lock)) wl_pointer_send_motion(ptr, t, fx, fy); + if (button) + wl_pointer_send_button(ptr, wl_display_next_serial(g_display), t, button, + pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED); + pointer_frame(ptr); + } + send_relative_motion(client, dx, dy); + /* A click lands text input (the soft keyboard's IME text) on the window under the pointer: + * that's where Wine's focus goes, and winewayland routes IME updates per process. */ + if (button && pressed && !(k && k->is_lock)) { + struct surface *clicked = toplevel_at(g_ptr_x, g_ptr_y); + if (clicked) g_ime_click = clicked; + wnc_text_input_refocus(); + } + wl_display_flush_clients(g_display); +} + +/* Absolute motion to scene x,y, then an optional button change. */ +static void pointer_event(double x, double y, uint32_t button, int pressed) { + pointer_input(x, y, 0, button, pressed); +} + +/* A delta from the app's relative input path (Relative Mouse, captured mouse, stick-as-mouse). */ +static void pointer_delta(double dx, double dy) { + pointer_input(dx, dy, 1, 0, 0); +} + +/* A button change at the current pointer position. */ +static void pointer_button(uint32_t button, int pressed) { + pointer_input(0, 0, 1, button, pressed); +} + +/* Java touch events arrive in INPUT_SPACE over the whole output: action 0=press, 1=move, 2=release. + * Output pixels go through the inverse of the scale mode's mapping (letterbox bars, FILL crop, a + * TOP/BOTTOM half), so the touch lands on the scene pixel that is drawn under the finger. */ +static void deliver_pointer(const struct input_msg *m) { + int w, h, ow, oh; + double x, y; + scene_size(&w, &h); + vkp_output_size(&ow, &oh); + if (ow <= 0 || oh <= 0 || + !vkp_output_to_scene((double)m->p2 * ow / INPUT_SPACE_W, (double)m->p3 * oh / INPUT_SPACE_H, &x, &y)) { + x = (double)m->p2 * w / INPUT_SPACE_W; /* nothing drawn yet: plain stretch */ + y = (double)m->p3 * h / INPUT_SPACE_H; + } + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x > w - 1) x = w - 1; + if (y > h - 1) y = h - 1; + pointer_event(x, y, m->p1 == 1 ? 0 : BTN_LEFT, m->p1 == 0); +} + +static void key_event(uint32_t evdev, int pressed); +struct wl_resource *wnc_ime_target(void); +static void deliver_key(const struct input_msg *m) { + key_event((uint32_t)m->p1, m->p2); +} + +/* Vertical wheel steps (negative = up) at the current pointer position. */ +static void scroll_event(int steps) { + struct surface *target = g_active_constraint ? g_active_constraint->surface + : g_desktop ? g_desktop : (g_grab ? g_grab : toplevel_at(g_ptr_x, g_ptr_y)); + if (!target || !steps) return; + struct wl_client *client = wl_resource_get_client(target->resource); + if (!client_has_pointer(client)) return; + int tx = 0, ty = 0; + if (target != g_desktop && target->placed) { tx = target->x; ty = target->y; } + uint32_t t = now_ms(); + pointer_focus(target->resource, wl_fixed_from_double(g_ptr_x - tx), wl_fixed_from_double(g_ptr_y - ty)); + for (int i = 0; i < g_nptrs; i++) { + struct wl_resource *ptr = g_ptrs[i].ptr; + if (wl_resource_get_client(ptr) != client) continue; + /* From version 8 the discrete event is replaced by value120 and must not be sent. */ + if (wl_resource_get_version(ptr) >= WL_POINTER_AXIS_VALUE120_SINCE_VERSION) + wl_pointer_send_axis_value120(ptr, WL_POINTER_AXIS_VERTICAL_SCROLL, steps * 120); + else if (wl_resource_get_version(ptr) >= WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) + wl_pointer_send_axis_discrete(ptr, WL_POINTER_AXIS_VERTICAL_SCROLL, steps); + wl_pointer_send_axis(ptr, t, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(steps * 10)); + pointer_frame(ptr); + } + wl_display_flush_clients(g_display); +} + +static void key_event(uint32_t evdev, int pressed) { + /* Keys go to the program window the user last clicked (else the topmost non-shell window), + * never to Wine's desktop surface: winewayland hands each key to the hwnd of the surface that + * holds keyboard focus, and a key handed to explorer's desktop hwnd is queued to explorer's + * thread, not to the foreground program (X11 delivers keys to the focused app window, so + * this mirrors it). The desktop itself is the last resort. */ + struct surface *target = NULL; + struct wl_resource *ime = wnc_ime_target(); + if (ime) target = wl_resource_get_user_data(ime); + if (!target && g_key_target && g_key_target->mapped && g_key_target != g_desktop) target = g_key_target; + if (!target) target = g_desktop; + if (!target) { + struct surface *s; + wl_list_for_each_reverse(s, &g_toplevels, toplevel_link) { target = s; break; } + } + if (!target) return; + struct wl_client *client = wl_resource_get_client(target->resource); + if (!client_has_keyboard(client)) return; + keyboard_focus(target->resource); + int mods_changed = mods_key(evdev, pressed); + for (int i = 0; i < g_nkbs; i++) { + if (wl_resource_get_client(g_kbs[i].kb) != client) continue; + wl_keyboard_send_key(g_kbs[i].kb, wl_display_next_serial(g_display), now_ms(), evdev, + pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED); + if (mods_changed) + wl_keyboard_send_modifiers(g_kbs[i].kb, wl_display_next_serial(g_display), mods_depressed(), 0, g_mods_locked, 0); + } + wl_display_flush_clients(g_display); +} + +/* The compositor lives as long as the process; a game session is what starts and ends. + * WinNative returns to its launcher without killing the process, so the session that ends here + * must leave nothing behind for the next one: every client is disconnected (their surfaces, + * buffers, constraints and seat resources go through the ordinary destroy paths), the display + * layer is hidden, the per-session counters and the dma-buf format table are reset (the next + * session may run with another UBWC setting) and the session log is closed. The screen surface + * is dropped by the app (nativeSetSurface(null) / nativeEndSession). Compositor thread. */ +static void session_end(void) { + while (g_clients) wl_client_destroy(g_clients->client); + g_grab = NULL; g_key_target = NULL; g_ime_click = NULL; + g_desktop = NULL; g_hud_surface = NULL; g_hdr_unimported = NULL; + g_active_constraint = NULL; + g_mod_keys_held = g_mods_locked = 0; + sc_layer_hide(); + wnc_color_session_end(); + g_zero_copy_paused = 0; + g_zero_copy_fx_skip_said = 0; + g_stat_frames = g_stat_dmabuf = g_stat_shm = 0; + g_scene_w = g_scene_h = 0; + g_desktop_w = g_desktop_h = 0; + g_dirty = 1; + if (g_fmt_table_fd >= 0) { close(g_fmt_table_fd); g_fmt_table_fd = -1; } + g_fmt_table_size = 0; g_fmt_table_n = 0; + g_dmabuf_fmts_ready = 0; + wnc_log("display", "session ended: clients disconnected, state reset"); + close_session_log(); +} + +/* A new session on the running compositor: a fresh session log. The app has already stored the + * session's settings (hide shell, output mode, FPS limit, zero-copy...). Compositor thread. */ +static void session_begin(void) { + open_session_log(); + wnc_log("display", "session started on the running compositor (%s)", vkp_gpu_name()); +} + +/* wl event-loop callback: drain queued input events written by the Android UI thread. */ +static int on_input_readable(int fd, uint32_t mask, void *data) { + struct input_msg m; + while (read(fd, &m, sizeof(m)) == (ssize_t)sizeof(m)) { + switch (m.type) { + case 1: deliver_key(&m); break; + case 2: pointer_event(m.p1, m.p2, 0, 0); break; /* scene motion */ + case 3: pointer_button(m.p1, m.p2); break; /* button at pointer */ + case 4: scroll_event(m.p1); break; + case 5: on_vsync(((int64_t)m.p1 << 32) | (uint32_t)m.p2); break; + case 6: pointer_delta(m.p1 / 256.0, m.p2 / 256.0); break; /* relative motion, 1/256 px */ + case 7: session_end(); break; + case 8: session_begin(); break; + default: deliver_pointer(&m); break; + } + } + return 0; +} + +/* Called from JNI (Android UI thread). Queues a pointer event; the compositor thread + * dispatches it. x/y are in INPUT_SPACE (0..1919, 0..1079) over the whole output. */ +void wnc_wayland_send_pointer(int action, int x, int y) { + if (g_input_pipe[1] < 0) return; + struct input_msg m = { 0, action, x, y }; + ssize_t n = write(g_input_pipe[1], &m, sizeof(m)); + (void)n; +} + +/* Called from JNI. Queues a key event. evdev = Linux input keycode (e.g. KEY_A=30); state 1=down 0=up. */ +void wnc_wayland_send_key(int evdev, int state) { + if (g_input_pipe[1] < 0) return; + struct input_msg m = { 1, evdev, state, 0 }; + ssize_t n = write(g_input_pipe[1], &m, sizeof(m)); + (void)n; +} + +/* Called from JNI when a game session ends / a new one starts on the running compositor. */ +void wnc_wayland_end_session(void) { + if (g_input_pipe[1] < 0) return; + struct input_msg m = { 7, 0, 0, 0 }; + ssize_t n = write(g_input_pipe[1], &m, sizeof(m)); + (void)n; +} + +void wnc_wayland_begin_session(void) { + if (g_input_pipe[1] < 0) return; + struct input_msg m = { 8, 0, 0, 0 }; + ssize_t n = write(g_input_pipe[1], &m, sizeof(m)); + (void)n; +} + +/* Called from JNI on every screen refresh (Choreographer). */ +void wnc_wayland_vsync(int64_t frame_time_ns) { + if (g_input_pipe[1] < 0) return; + struct input_msg m = { 5, (int)(frame_time_ns >> 32), (int)(uint32_t)frame_time_ns, 0 }; + ssize_t n = write(g_input_pipe[1], &m, sizeof(m)); + (void)n; +} + +/* Called from JNI with the app's X-server input (on-screen controls, mouse): type 2 = motion + * to scene x,y; 3 = evdev button a pressed/released (b); 4 = a wheel steps (negative = up); + * 6 = relative motion by a,b in 1/256 pixel (the app's Relative Mouse / captured-mouse path). */ +void wnc_wayland_send_scene_input(int type, int a, int b) { + if (g_input_pipe[1] < 0) return; + struct input_msg m = { type, a, b, 0 }; + ssize_t n = write(g_input_pipe[1], &m, sizeof(m)); + (void)n; +} + +/* ------------------------------------------------------------------ extension module hooks + * What wl_clipboard.c / wl_text_input.c need from the scene (see desktop_ext.h). */ + +const char *wnc_client_name(struct wl_client *client) { return client_name(client); } +struct wl_display *wnc_get_display(void) { return g_display; } +void wnc_request_redraw(void) { schedule_render(); } + +/* The surface text input follows: the last clicked mapped program window, else the topmost + * mapped window not owned by the desktop's process (explorer). NULL when there is none. */ +struct wl_resource *wnc_ime_target(void) { + struct surface *s; + if (g_ime_click && g_ime_click->mapped) return g_ime_click->resource; + struct wl_client *shell = g_desktop ? wl_resource_get_client(g_desktop->resource) : NULL; + wl_list_for_each_reverse(s, &g_toplevels, toplevel_link) + if (!shell || wl_resource_get_client(s->resource) != shell) return s->resource; + return NULL; +} + +void wnc_surface_scene_origin(struct wl_resource *surface, int *x, int *y) { + struct surface *s = surface ? wl_resource_get_user_data(surface) : NULL; + *x = 0; *y = 0; + if (s && s->placed) { *x = s->x; *y = s->y; } +} + +void wnc_inject_key(uint32_t evdev, int pressed) { key_event(evdev, pressed); } + +/* ------------------------------------------------------------------ 10 s summary */ + +static struct wl_event_source *g_stats_timer; +/* The last window's zero-copy count, kept for the app (WaylandCompositor.nativeZeroCopyFrames). */ +volatile unsigned g_zero_copy_last; + +static int on_stats_timer(void *data) { + int windows = 0; + struct surface *s; + wl_list_for_each(s, &g_toplevels, toplevel_link) windows++; + unsigned zero_copy = ahb_swapchain_stats_take(); + unsigned layer_frames = sc_layer_frames_take(); + g_zero_copy_last = zero_copy; + /* Interpolated frames the compositor added (framegen_bridge.c). They are on screen, so they + * count there; they are NOT GPU frames from games and never inflate that number. */ + unsigned generated = vkp_framegen_stats_take(); + if (g_stat_frames || g_stat_dmabuf || g_stat_shm || generated) { + char extra[160] = ""; + int off = 0; + if (g_zero_copy || zero_copy) off += snprintf(extra + off, sizeof(extra) - (size_t)off, " | %u zero-copy frames", zero_copy); + /* Frames the compositor put on a display layer through one of its OWN gralloc buffers (the + * plain layer blit, or the effects chain's result): still hardware-composed, but not + * zero-copy, so they are counted apart from the line above. */ + if (layer_frames) off += snprintf(extra + off, sizeof(extra) - (size_t)off, " | %u layer frames", layer_frames); + if (generated) snprintf(extra + off, sizeof(extra) - (size_t)off, " | %u generated frames", generated); + wnc_log("stats", "last 10 s: %u frames on screen (%.1f fps) | %u GPU frames from games | %u window redraws | %d windows open%s", + g_stat_frames + generated, (g_stat_frames + generated) / 10.0, g_stat_dmabuf, g_stat_shm, windows, extra); + } + /* The perf line, next to the stats line and only when something happened: the compositor thread's + * time per scene and inside the driver (avg/max), what the base surface did, and how long games + * waited for their buffers back. See the audit memory for how to read it. */ + { + struct vkp_perf vp; + vkp_perf_take(&vp); + const unsigned drops = sc_layer_drops_take(); +#define PERF_MS(sum, n) ((n) ? (double)(sum) / 1e6 / (double)(n) : 0.0) + if (g_perf.scenes || vp.presents || g_perf.releases || drops) + wnc_log("perf", "last 10 s: %u ticks, %u scenes, %u on screen (copy %u, zero-copy %u, layer copy %u) | " + "render_scene %.2f/%.2f ms | base %u black kept, %u presented | acquire %.2f/%.2f ms | " + "present %.2f/%.2f ms (%u) | fence wait %.2f/%.2f ms (%u, %u GPU release waits) | " + "release %.2f/%.2f ms (%u, %u held) | %u pool drops", + g_perf.ticks, g_perf.scenes, g_stat_frames, g_perf.copy_scenes, zero_copy, layer_frames, + PERF_MS(g_perf.scene_ns, g_perf.scenes), (double)g_perf.scene_max_ns / 1e6, + vp.base_kept, vp.base_presents, + PERF_MS(vp.acquire_ns, vp.acquires), (double)vp.acquire_max_ns / 1e6, + PERF_MS(vp.present_ns, vp.presents), (double)vp.present_max_ns / 1e6, vp.presents, + PERF_MS(vp.wait_ns, vp.waits), (double)vp.wait_max_ns / 1e6, vp.waits, vp.gpu_release_waits, + PERF_MS(g_perf.release_ns, g_perf.releases), (double)g_perf.release_max_ns / 1e6, + g_perf.releases, g_perf.releases_held, drops); +#undef PERF_MS + memset(&g_perf, 0, sizeof(g_perf)); + } + g_stat_frames = g_stat_dmabuf = g_stat_shm = 0; + wnc_color_stats_tick(); /* HDR evidence for the same window (nothing when the HDR gate is closed) */ + /* A program that asked for dma-buf feedback (EGL's GPU path does, first thing) but has drawn + * only wl_shm frames since is an OpenGL program whose EGL gave up on the GPU. The software + * path it fell to has no rasteriser in the Wayland layers, so what it commits is black. */ + for (struct client_info *ci = g_clients; ci; ci = ci->next) { + if (!ci->asked_feedback || ci->dmabuf_buffers || ci->shm_gl_said || ci->shm_frames < 150) + continue; + ci->shm_gl_said = 1; + wnc_log("opengl", "%s asked for GPU buffers but has drawn only software (shared-memory) " + "frames: its OpenGL fell back to software rendering, which the Wayland layer cannot " + "draw - expect a black picture (main device %u:%u)", ci->name, + (unsigned)major(g_main_device), (unsigned)minor(g_main_device)); + } + wl_event_source_timer_update(g_stats_timer, 10000); + return 0; +} + +/* ------------------------------------------------------------------ test input + * $XDG_RUNTIME_DIR/test-input is a FIFO for scripted testing from a root shell, in scene + * (desktop) coordinates: "move X Y", "click X Y", "rclick X Y", "dclick X Y", "down X Y", + * "up X Y", "rdown X Y", "rup X Y", "rel DX DY" (relative motion, like the app's Relative + * Mouse path), "key CODE" (evdev), "keydown CODE", "keyup CODE". + * It lives in the app's private directory, so only the app and root can reach it. */ + +static char g_test_buf[512]; +static size_t g_test_len; + +static void test_input_line(const char *line) { + double x, y; + unsigned code; + char cmd[16]; + int n = sscanf(line, "%15s %lf %lf", cmd, &x, &y); + + if (n < 2) return; + wnc_log("test", "input: %s", line); + if (!strcmp(cmd, "key") || !strcmp(cmd, "keydown") || !strcmp(cmd, "keyup")) { + code = (unsigned)x; + if (strcmp(cmd, "keyup")) key_event(code, 1); + if (strcmp(cmd, "keydown")) key_event(code, 0); + return; + } + if (n < 3) return; + if (!strcmp(cmd, "move")) pointer_event(x, y, 0, 0); + else if (!strcmp(cmd, "rel")) pointer_delta(x, y); + else if (!strcmp(cmd, "down")) pointer_event(x, y, BTN_LEFT, 1); + else if (!strcmp(cmd, "up")) pointer_event(x, y, BTN_LEFT, 0); + else if (!strcmp(cmd, "rdown")) pointer_event(x, y, BTN_RIGHT, 1); + else if (!strcmp(cmd, "rup")) pointer_event(x, y, BTN_RIGHT, 0); + else if (!strcmp(cmd, "click") || !strcmp(cmd, "rclick") || !strcmp(cmd, "dclick")) { + uint32_t button = cmd[0] == 'r' ? BTN_RIGHT : BTN_LEFT; + int times = cmd[0] == 'd' ? 2 : 1; + pointer_event(x, y, 0, 0); + for (int i = 0; i < times; i++) { + pointer_event(x, y, button, 1); + pointer_event(x, y, button, 0); + } + } +} + +static int on_test_input(int fd, uint32_t mask, void *data) { + ssize_t r; + while ((r = read(fd, g_test_buf + g_test_len, sizeof(g_test_buf) - 1 - g_test_len)) > 0) { + char *start = g_test_buf, *nl; + g_test_len += (size_t)r; + g_test_buf[g_test_len] = 0; + while ((nl = strchr(start, '\n'))) { + *nl = 0; + test_input_line(start); + start = nl + 1; + } + g_test_len = strlen(start); + memmove(g_test_buf, start, g_test_len); + if (g_test_len >= sizeof(g_test_buf) - 1) g_test_len = 0; /* overlong line: drop */ + } + return 0; +} + +static void start_test_input(struct wl_event_loop *loop) { + const char *rt = getenv("XDG_RUNTIME_DIR"); + char path[512]; + int fd; + + if (!rt || !*rt) return; + snprintf(path, sizeof(path), "%s/test-input", rt); + unlink(path); + if (mkfifo(path, 0600) != 0) return; + /* O_RDWR keeps a writer open ourselves, so a closing test shell never leaves us at EOF. */ + if ((fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC)) < 0) return; + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, on_test_input, NULL); +} + +/* ------------------------------------------------------------------ entry + * Blocks in the wl event loop, so the JNI wrapper runs it on a dedicated thread. + * XDG_RUNTIME_DIR must be set by the caller before this runs. */ + +static struct wl_listener g_client_created = { .notify = on_client_created }; + +int wnc_wayland_run(void) { + wl_list_init(&g_surfaces); + wl_list_init(&g_toplevels); + wl_list_init(&g_constraints); + wl_list_init(&g_relative_pointers); + open_session_log(); + + /* Bring the GPU up before any client can connect: loading Turnip the first time + * can take many seconds, and it must not stall a client mid-handshake. */ + if (vkp_ready() != 0) WLOGE("renderer unavailable; clients will connect but nothing is drawn"); + + struct wl_display *display = wl_display_create(); + if (!display) { + WLOGE("wl_display_create failed"); + return 1; + } + + /* Use a FIXED socket name so it always matches the guest's WAYLAND_DISPLAY=wayland-0 + * (wl_display_add_socket_auto would drift to wayland-1/2/โ€ฆ if a stale socket exists, + * and the guest only ever looks for wayland-0). Unlink any stale socket+lock first so + * a previous run that didn't clean up can't block the bind. */ + const char *rt = getenv("XDG_RUNTIME_DIR"); + if (rt && *rt) { + char p[512]; + snprintf(p, sizeof(p), "%s/wayland-0", rt); unlink(p); + snprintf(p, sizeof(p), "%s/wayland-0.lock", rt); unlink(p); + } + if (wl_display_add_socket(display, "wayland-0") != 0) { + WLOGE("add_socket(wayland-0) failed in XDG_RUNTIME_DIR=%s (errno path/perms?)", + rt ? rt : "(null)"); + return 1; + } + wnc_log("display", "Wayland compositor listening on %s/wayland-0", rt ? rt : "?"); + wl_display_add_client_created_listener(display, &g_client_created); + + wl_global_create(display, &wl_compositor_interface, 6, NULL, bind_compositor); + wl_global_create(display, &wl_subcompositor_interface, 1, NULL, bind_subcompositor); + wl_global_create(display, &wp_viewporter_interface, 1, NULL, bind_viewporter); + wl_display_init_shm(display); /* wl_shm global + pool/buffer handling */ + /* libdecor binds wl_output at 4; a lower version is a protocol error for the client. */ + wl_global_create(display, &wl_output_interface, 4, NULL, bind_output); + wl_global_create(display, &xdg_wm_base_interface, 1, NULL, bind_xdg_wm_base); + wl_global_create(display, &zwp_linux_dmabuf_v1_interface, 4, NULL, bind_dmabuf); + /* gamescope's Wayland backend refuses a seat older than 8. */ + wl_global_create(display, &wl_seat_interface, 9, NULL, bind_seat); + wl_global_create(display, &banner_desktop_v1_interface, 1, NULL, bind_desktop); + wl_global_create(display, &wp_presentation_interface, 2, NULL, bind_presentation); + wl_global_create(display, &zwp_pointer_constraints_v1_interface, 1, NULL, bind_pointer_constraints); + wl_global_create(display, &zwp_relative_pointer_manager_v1_interface, 1, NULL, bind_relative_pointer_manager); + wnc_ext_init(display); /* clipboard, text input, toplevel icons (own files, see desktop_ext.h) */ + ahb_swapchain_init(display); /* zero-copy layers: banner_ahb_v1, advertised whenever a display + * layer is possible; the mode event carries the live switch + * (ahb_swapchain.h) */ + wnc_color_init(display); /* HDR10 (opt-in): decides the gate from the app's request + the + * display + the layer path above; creates wp_color_manager_v1 + * only when it is open (color_mgmt.h). Before any client can + * bind zwp_linux_dmabuf_v1, whose table it widens. */ + wl_list_init(&g_pending_releases); + g_release_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (g_release_timer_fd >= 0) + g_release_source = wl_event_loop_add_fd(wl_display_get_event_loop(display), g_release_timer_fd, + WL_EVENT_READABLE, on_release_timer, NULL); + + /* Input injection: the Android UI thread writes events to g_input_pipe[1]; + * the wl event loop drains them on this thread. */ + g_display = display; + if (pipe2(g_input_pipe, O_CLOEXEC | O_NONBLOCK) == 0) { + wl_event_loop_add_fd(wl_display_get_event_loop(display), g_input_pipe[0], + WL_EVENT_READABLE, on_input_readable, NULL); + } else { + WLOGE("input pipe creation failed"); + } + start_test_input(wl_display_get_event_loop(display)); + if ((g_stats_timer = wl_event_loop_add_timer(wl_display_get_event_loop(display), on_stats_timer, NULL))) + wl_event_source_timer_update(g_stats_timer, 10000); + + wl_display_run(display); /* blocks, dispatches the event loop */ + + wl_display_destroy(display); + return 0; +} diff --git a/app/src/main/cpp/waylandcomp/src/desktop_ext.c b/app/src/main/cpp/waylandcomp/src/desktop_ext.c new file mode 100644 index 000000000..a9e6201e6 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/desktop_ext.c @@ -0,0 +1,125 @@ +/* + * Host โ†’ compositor message queue plus the init of the extension modules. + * + * The app's threads (UI thread, clipboard listener) hand text to the compositor through + * wnc_host_* ; the messages are queued under a mutex and a pipe byte wakes the wl event + * loop, which drains them on the compositor thread โ€” the only thread that may touch + * wl_resources. + */ +#define _GNU_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> +#include <android/log.h> +#include "desktop_ext.h" +#include "ahb_swapchain.h" +#include "color_mgmt.h" + +enum host_kind { HOST_CLIPBOARD = 1, HOST_TEXT_COMMIT, HOST_TEXT_PREEDIT, HOST_TEXT_DELETE, HOST_ZERO_COPY, HOST_HDR_OUTPUT }; + +struct host_msg { + enum host_kind kind; + char *text; + size_t len; + int a, b; + struct host_msg *next; +}; + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static struct host_msg *g_head, *g_tail; +static int g_wake[2] = {-1, -1}; + +static void host_post(enum host_kind kind, const char *text, size_t len, int a, int b) { + struct host_msg *m = calloc(1, sizeof(*m)); + if (!m) return; + m->kind = kind; + m->a = a; m->b = b; + if (text && len) { + if (!(m->text = malloc(len + 1))) { free(m); return; } + memcpy(m->text, text, len); + m->text[len] = 0; + m->len = len; + } + pthread_mutex_lock(&g_mu); + if (g_tail) g_tail->next = m; else g_head = m; + g_tail = m; + pthread_mutex_unlock(&g_mu); + if (g_wake[1] >= 0) { + char c = 1; + ssize_t n = write(g_wake[1], &c, 1); + (void)n; + } +} + +void wnc_host_clipboard_text(const char *utf8, int len) { + host_post(HOST_CLIPBOARD, utf8, len > 0 ? (size_t)len : 0, 0, 0); +} +void wnc_host_text_commit(const char *utf8, int len) { + host_post(HOST_TEXT_COMMIT, utf8, len > 0 ? (size_t)len : 0, 0, 0); +} +void wnc_host_text_preedit(const char *utf8, int len, int cursor_begin, int cursor_end) { + host_post(HOST_TEXT_PREEDIT, utf8, len > 0 ? (size_t)len : 0, cursor_begin, cursor_end); +} +void wnc_host_text_delete(int before, int after) { + host_post(HOST_TEXT_DELETE, NULL, 0, before, after); +} +void wnc_host_zero_copy(int on, int live) { + host_post(HOST_ZERO_COPY, NULL, 0, on, live); +} +void wnc_host_hdr_output(int on) { + host_post(HOST_HDR_OUTPUT, NULL, 0, on, 0); +} + +static int on_wake(int fd, uint32_t mask, void *data) { + char buf[64]; + while (read(fd, buf, sizeof(buf)) > 0) {} + for (;;) { + pthread_mutex_lock(&g_mu); + struct host_msg *m = g_head; + if (m) { g_head = m->next; if (!g_head) g_tail = NULL; } + pthread_mutex_unlock(&g_mu); + if (!m) break; + switch (m->kind) { + case HOST_CLIPBOARD: + clipboard_host_text(m->text, m->len); /* takes the buffer */ + m->text = NULL; + break; + case HOST_TEXT_COMMIT: + text_input_host_commit(m->text ? m->text : "", m->len); + break; + case HOST_TEXT_PREEDIT: + text_input_host_preedit(m->text ? m->text : "", m->len, m->a, m->b); + break; + case HOST_TEXT_DELETE: + text_input_host_delete(m->a, m->b); + break; + case HOST_ZERO_COPY: + ahb_swapchain_set_mode(m->a, m->b); + break; + case HOST_HDR_OUTPUT: + wnc_color_set_output(m->a); + break; + } + free(m->text); + free(m); + } + wl_display_flush_clients(wnc_get_display()); + return 0; +} + +void wnc_ext_init(struct wl_display *display) { + struct wl_event_loop *loop = wl_display_get_event_loop(display); + if (pipe2(g_wake, O_CLOEXEC | O_NONBLOCK) == 0) { + wl_event_loop_add_fd(loop, g_wake[0], WL_EVENT_READABLE, on_wake, NULL); + pthread_mutex_lock(&g_mu); + int queued = g_head != NULL; /* posted before the loop existed (clipboard refresh) */ + pthread_mutex_unlock(&g_mu); + if (queued) { char c = 1; ssize_t n = write(g_wake[1], &c, 1); (void)n; } + } else + __android_log_print(ANDROID_LOG_ERROR, "WNWayland", "host queue pipe failed"); + clipboard_init(display); + text_input_init(display); + toplevel_icon_init(display); +} diff --git a/app/src/main/cpp/waylandcomp/src/desktop_ext.h b/app/src/main/cpp/waylandcomp/src/desktop_ext.h new file mode 100644 index 000000000..f05ee87b3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/desktop_ext.h @@ -0,0 +1,50 @@ +/* + * Interface between compositor.c and the extension modules that live in their own files: + * wl_clipboard.c wl_data_device_manager + zwlr_data_control_manager_v1 (selection only) + * wl_text_input.c zwp_text_input_manager_v3 (host IME / soft keyboard) + * wl_toplevel_icon.c xdg_toplevel_icon_manager_v1 (accepted, not rendered) + * desktop_ext.c the hostโ†’compositor message queue shared by the modules + * Everything here runs on the compositor (dispatch) thread unless noted. + */ +#pragma once +#include <stdint.h> +#include <wayland-server.h> + +void wnc_log(const char *tag, const char *fmt, ...); + +/* ---- compositor.c โ†’ modules */ +void wnc_ext_init(struct wl_display *display); /* creates the globals + host queue */ +void wnc_clipboard_keyboard_focus(struct wl_client *client); /* keyboard focus moved; NULL = none */ +void wnc_text_input_refocus(void); /* the text-input target may have changed */ +void wnc_text_input_surface_gone(struct wl_resource *surface); + +/* ---- modules โ†’ compositor.c */ +const char *wnc_client_name(struct wl_client *client); +struct wl_display *wnc_get_display(void); +/* The surface text input follows (the last clicked window, else the topmost program window). */ +struct wl_resource *wnc_ime_target(void); +/* Scene (virtual desktop) origin of a toplevel surface; 0,0 when unknown. */ +void wnc_surface_scene_origin(struct wl_resource *surface, int *x, int *y); +/* Deliver a key press/release through the wl_keyboard path as if typed. */ +void wnc_inject_key(uint32_t evdev, int pressed); + +/* ---- modules โ†’ app (JNI upcalls; waylandcomp_jni.c) */ +void wnc_on_clipboard_text(const char *utf8, int len); +void wnc_on_text_input(int enabled, const char *program, int x, int y, int w, int h); + +/* ---- app โ†’ modules (callable from any thread; queued to the compositor thread) */ +void wnc_host_clipboard_text(const char *utf8, int len); /* len 0 = clear */ +void wnc_host_text_commit(const char *utf8, int len); +void wnc_host_text_preedit(const char *utf8, int len, int cursor_begin, int cursor_end); +void wnc_host_text_delete(int before, int after); +void wnc_host_zero_copy(int on, int live); /* the drawer's zero-copy switch (ahb_swapchain_set_mode) */ +void wnc_host_hdr_output(int on); /* the drawer's HDR output switch (wnc_color_set_output) */ + +/* ---- module internals reached through the queue (desktop_ext.c โ†’ modules) */ +void clipboard_init(struct wl_display *display); +void clipboard_host_text(char *utf8, size_t len); /* takes ownership of utf8 */ +void text_input_init(struct wl_display *display); +void text_input_host_commit(const char *utf8, size_t len); +void text_input_host_preedit(const char *utf8, size_t len, int cursor_begin, int cursor_end); +void text_input_host_delete(int before, int after); +void toplevel_icon_init(struct wl_display *display); diff --git a/app/src/main/cpp/waylandcomp/src/effects_chain.c b/app/src/main/cpp/waylandcomp/src/effects_chain.c new file mode 100644 index 000000000..f3267e099 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/effects_chain.c @@ -0,0 +1,654 @@ +/* Screen-effect chain โ€” see effects_chain.h. Compositor thread unless noted. */ +#define _POSIX_C_SOURCE 200809L +#include "effects_chain.h" +#include "vk_loader.h" +#include "vk_present.h" /* wnc_log */ +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* The X11 Vulkan renderer's SPIR-V, pre-compiled by app/src/main/cpp/winlator/gen_shaders.sh + * (glslangValidator --vn) and committed next to the GLSL: the NDK build compiles no shaders, so + * there is no build-time tooling for CI to break. Same bytes as the X11 chain runs. */ +#include "upscale_vert.h" +#include "sgsr_frag.h" +#include "sgsr_quality_frag.h" +#include "nis_frag.h" +#include "fsr_easu_frag.h" +#include "fsr_rcas_frag.h" +#include "cas_frag.h" +#include "color_frag.h" +#include "fxaa_frag.h" +#include "toon_frag.h" +#include "hdr_frag.h" +#include "ntsc_frag.h" +#include "crt_frag.h" +#include "deband_frag.h" + +#define FX_FORMAT VK_FORMAT_R8G8B8A8_UNORM /* the default: every SDR frame */ +/* The chain's target format and the scene's view format. R8G8B8A8 always, except while an HDR scene + * runs through the chain (hdr_compose.h): then the scene and the targets are A2B10G10R10, so a PQ + * picture keeps 10 bits through every pass. A change rebuilds the chain's objects once + * (vkp_effects_set_formats). */ +static VkFormat g_fx_fmt = FX_FORMAT, g_scene_fmt = FX_FORMAT; +#define FX_MAX_DIM 4096 /* a FILL-mapped scene can exceed the panel; cap the chain resolution */ + +/* ------------------------------------------------------------------ settings */ + +struct fx_settings { + int scaling; /* 0..8 */ + int sharp_pct; /* 0..100, the upscaler's own slider */ + int cas_on, cas_pct; + int hdr_on; + int deband_on, deband_pct; /* 0..200 */ + int color_on; /* derived: grade not neutral */ + float brightness, contrast, gamma, saturation; /* shader units (already mapped/clamped) */ + float b_raw, c_raw, s_raw; /* slider units, for the log */ + int fxaa, toon, crt, ntsc; + char look[48]; +}; + +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; +static struct fx_settings g_set = { + .scaling = 0, .sharp_pct = 75, .cas_on = 0, .cas_pct = 60, .deband_pct = 100, + .gamma = 1.0f, .saturation = 1.0f, .s_raw = 100.0f, .look = "Off"}; +static volatile int g_changed; +static struct fx_settings g_cur; /* the compositor thread's per-frame snapshot */ +static int g_cur_valid; + +static void set_begin(void) { pthread_mutex_lock(&g_lock); } +static void set_end(void) { g_changed = 1; pthread_mutex_unlock(&g_lock); } + +void vkp_effects_set_scaling(int mode) { + if (mode < 0 || mode > 8) mode = 0; + set_begin(); g_set.scaling = mode; set_end(); +} +void vkp_effects_set_upscale_sharpness(int pct) { + if (pct < 0) pct = 0; + if (pct > 100) pct = 100; + set_begin(); g_set.sharp_pct = pct; set_end(); +} +void vkp_effects_set_cas(int enabled, int pct) { + if (pct < 0) pct = 0; + if (pct > 100) pct = 100; + set_begin(); g_set.cas_on = enabled ? 1 : 0; g_set.cas_pct = pct; set_end(); +} +void vkp_effects_set_hdr(int enabled) { set_begin(); g_set.hdr_on = enabled ? 1 : 0; set_end(); } +void vkp_effects_set_deband(int enabled, int pct) { + if (pct < 0) pct = 0; + if (pct > 200) pct = 200; + set_begin(); g_set.deband_on = enabled ? 1 : 0; g_set.deband_pct = pct; set_end(); +} +static float clampf(float v, float lo, float hi) { return v < lo ? lo : v > hi ? hi : v; } +void vkp_effects_set_screen(float brightness, float contrast, float gamma, float saturation, + int fxaa, int toon, int crt, int ntsc) { + /* Same mapping as VulkanRendererContext::setColorGrade (GL ColorEffect clamps). */ + int on = !(brightness == 0.0f && contrast == 0.0f && gamma == 1.0f && saturation == 100.0f); + set_begin(); + g_set.color_on = on; + g_set.b_raw = brightness; g_set.c_raw = contrast; g_set.s_raw = saturation; + g_set.brightness = clampf(brightness / 100.0f, -1.0f, 1.0f); + g_set.contrast = clampf(contrast / 100.0f, 0.0f, 2.0f); + g_set.saturation = clampf(saturation / 100.0f, 0.0f, 2.0f); + g_set.gamma = clampf(gamma, 0.1f, 5.0f); + g_set.fxaa = fxaa ? 1 : 0; g_set.toon = toon ? 1 : 0; g_set.crt = crt ? 1 : 0; g_set.ntsc = ntsc ? 1 : 0; + set_end(); +} +void vkp_effects_set_look(const char *name) { + set_begin(); + snprintf(g_set.look, sizeof(g_set.look), "%s", name && *name ? name : "Custom"); + set_end(); +} + +static const char *scaling_name(int m) { + static const char *n[] = {"None", "Linear", "Nearest", "SGSR", "FSR", "FSR Fit", "Sharpen", "NIS", "SGSR HQ"}; + return (m >= 0 && m <= 8) ? n[m] : "?"; +} +static int spatial_mode(int m) { return m >= 3 && m <= 8; } +static int cas_active(const struct fx_settings *s) { return s->cas_on && s->cas_pct > 0; } +static int effect_count(const struct fx_settings *s) { + return s->fxaa + s->toon + s->color_on + cas_active(s) + s->hdr_on + s->ntsc + s->crt + s->deband_on; +} + +static void log_settings(const struct fx_settings *s) { + char line[400]; + int n; + if (!spatial_mode(s->scaling) && !effect_count(s)) { + wnc_log("effects", "all off: scaling=%s (plain blit)", scaling_name(s->scaling)); + return; + } + n = snprintf(line, sizeof(line), "scaling=%s", scaling_name(s->scaling)); + if (spatial_mode(s->scaling)) n += snprintf(line + n, sizeof(line) - n, " %d%%", s->sharp_pct); + if (cas_active(s)) n += snprintf(line + n, sizeof(line) - n, ", CAS on %d%%", s->cas_pct); + else n += snprintf(line + n, sizeof(line) - n, ", CAS off"); + n += snprintf(line + n, sizeof(line) - n, ", Look=\"%s\"", s->look); + if (s->color_on) + n += snprintf(line + n, sizeof(line) - n, ", colour b=%+.0f c=%+.0f g=%.2f s=%.0f%%", + s->b_raw, s->c_raw, s->gamma, s->s_raw); + if (s->fxaa) n += snprintf(line + n, sizeof(line) - n, ", FXAA on"); + if (s->toon) n += snprintf(line + n, sizeof(line) - n, ", Toon on"); + if (s->hdr_on) n += snprintf(line + n, sizeof(line) - n, ", HDR on"); + if (s->ntsc) n += snprintf(line + n, sizeof(line) - n, ", NTSC on"); + if (s->crt) n += snprintf(line + n, sizeof(line) - n, ", CRT on"); + if (s->deband_on) n += snprintf(line + n, sizeof(line) - n, ", Deband on %d%%", s->deband_pct); + (void)n; + wnc_log("effects", "%s", line); +} + +int vkp_effects_sync(void) { + if (!g_changed && g_cur_valid) return 0; + pthread_mutex_lock(&g_lock); + g_changed = 0; + g_cur = g_set; + pthread_mutex_unlock(&g_lock); + g_cur_valid = 1; + log_settings(&g_cur); + return 1; +} + +int vkp_effects_active(void) { + vkp_effects_sync(); + return spatial_mode(g_cur.scaling) || effect_count(&g_cur) > 0; +} + +VkFilter vkp_effects_blit_filter(void) { + vkp_effects_sync(); + return g_cur.scaling == 2 ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; +} + +/* ------------------------------------------------------------------ push constants + * Byte-identical to VulkanRendererContext.h: vec4 ndc first (upscale.vert), the rest fragment-only. */ +struct pc_sgsr { float ndc[4]; float vp[4]; float edge; }; /* also NIS: vp + sharpness */ +struct pc_easu { float ndc[4]; uint32_t con0[4], con1[4], con2[4], con3[4]; float outW, outH; }; +struct pc_rcas { float ndc[4]; uint32_t con[4]; float outW, outH; }; +struct pc_res { float ndc[4]; float res[2]; float extra; float pad; }; /* fxaa/toon/hdr/crt (res), cas (sharpness), ntsc (frameCount), deband (strength) */ +struct pc_color { float ndc[4]; float b, c, g, s; }; +#define PC_MAX_SIZE sizeof(struct pc_easu) /* 88 bytes, the largest */ + +static uint32_t packf(float f) { uint32_t u; memcpy(&u, &f, 4); return u; } + +/* Mirrors ffx_fsr1.h FsrEasuCon / FsrRcasCon exactly as the X11 renderer packs them. */ +static void easu_con(struct pc_easu *e, float inW, float inH, float outW, float outH) { + e->con0[0] = packf(inW / outW); e->con0[1] = packf(inH / outH); + e->con0[2] = packf(0.5f * inW / outW - 0.5f); e->con0[3] = packf(0.5f * inH / outH - 0.5f); + e->con1[0] = packf(1.0f / inW); e->con1[1] = packf(1.0f / inH); + e->con1[2] = packf(1.0f / inW); e->con1[3] = packf(-1.0f / inH); + e->con2[0] = packf(-1.0f / inW); e->con2[1] = packf(2.0f / inH); + e->con2[2] = packf(1.0f / inW); e->con2[3] = packf(2.0f / inH); + e->con3[0] = packf(0.0f); e->con3[1] = packf(4.0f / inH); + e->con3[2] = 0; e->con3[3] = 0; +} +static void rcas_con(struct pc_rcas *r, float scale01) { + r->con[0] = packf(scale01); r->con[1] = 0; r->con[2] = 0; r->con[3] = 0; +} + +/* ------------------------------------------------------------------ Vulkan objects */ + +struct fx_target { + VkImage img; VkDeviceMemory mem; VkImageView view; VkFramebuffer fb; VkDescriptorSet ds; + int w, h; +}; + +static VkDevice g_dev; +static VkPhysicalDevice g_pd; +static VkPhysicalDeviceMemoryProperties g_mp; +static int g_ready; /* 0 = objects not built, 1 = ok, -1 = failed (chain disabled, said once) */ +static VkRenderPass g_rp; +static VkDescriptorSetLayout g_dsl; +static VkPipelineLayout g_pl; +static VkDescriptorPool g_dpool; +static VkSampler g_sampler; /* linear, clamp-to-edge: what the X11 chain samples its targets with */ +enum { P_SGSR, P_SGSR_HQ, P_NIS, P_EASU, P_RCAS, P_CAS, P_COLOR, P_FXAA, P_TOON, P_HDR, P_NTSC, P_CRT, P_DEBAND, P_COUNT }; +static VkPipeline g_pipe[P_COUNT]; +static struct fx_target g_t[2], g_mid; /* ping-pong pair + the EASU output (FSR only) */ +/* The scene image belongs to vk_present; only its view + descriptor live here. */ +static struct { VkImage img; VkImageView view; VkDescriptorSet ds; int w, h; } g_scene; +static uint32_t g_ntsc_frame; + +void vkp_effects_bind_device(VkDevice dev, VkPhysicalDevice pd, + const VkPhysicalDeviceMemoryProperties *memprops) { + g_dev = dev; g_pd = pd; g_mp = *memprops; +} + +static int memtype(uint32_t bits, VkMemoryPropertyFlags want) { + for (uint32_t i = 0; i < g_mp.memoryTypeCount; i++) + if ((bits & (1u << i)) && (g_mp.memoryTypes[i].propertyFlags & want) == want) return (int)i; + return -1; +} + +static VkDescriptorSet alloc_ds(VkImageView view) { + VkDescriptorSet ds = VK_NULL_HANDLE; + VkDescriptorSetAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = g_dpool, .descriptorSetCount = 1, .pSetLayouts = &g_dsl}; + if (g_vk.AllocateDescriptorSets(g_dev, &ai, &ds) != VK_SUCCESS) return VK_NULL_HANDLE; + VkDescriptorImageInfo ii = {.sampler = g_sampler, .imageView = view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + VkWriteDescriptorSet wr = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = ds, .dstBinding = 0, + .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &ii}; + g_vk.UpdateDescriptorSets(g_dev, 1, &wr, 0, NULL); + return ds; +} + +static VkImageView make_view(VkImage img, VkFormat fmt) { + VkImageView v = VK_NULL_HANDLE; + VkImageViewCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = img, + .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = fmt, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + if (g_vk.CreateImageView(g_dev, &ci, NULL, &v) != VK_SUCCESS) return VK_NULL_HANDLE; + return v; +} + +static void target_destroy(struct fx_target *t) { + if (t->ds) g_vk.FreeDescriptorSets(g_dev, g_dpool, 1, &t->ds); + if (t->fb) g_vk.DestroyFramebuffer(g_dev, t->fb, NULL); + if (t->view) g_vk.DestroyImageView(g_dev, t->view, NULL); + if (t->img) g_vk.DestroyImage(g_dev, t->img, NULL); + if (t->mem) g_vk.FreeMemory(g_dev, t->mem, NULL); + memset(t, 0, sizeof(*t)); +} + +/* A chain target: rendered into (colour attachment), sampled by the next pass, and blitted to + * the swapchain when it ends up holding the result. Frames are fenced, so a resize never races + * the GPU. 0 = ok. */ +static int target_ensure(struct fx_target *t, int w, int h, const char *what) { + if (t->img && t->w == w && t->h == h) return 0; + target_destroy(t); + VkImageCreateInfo ii = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, + .format = g_fx_fmt, .extent = {(uint32_t)w, (uint32_t)h, 1}, .mipLevels = 1, + .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + VkResult r = g_vk.CreateImage(g_dev, &ii, NULL, &t->img); + if (r != VK_SUCCESS) { wnc_log("error", "effects: %s image %dx%d: vkCreateImage %d", what, w, h, (int)r); return -1; } + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, t->img, &req); + int idx = memtype(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (idx < 0) idx = memtype(req.memoryTypeBits, 0); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = req.size, + .memoryTypeIndex = (uint32_t)(idx < 0 ? 0 : idx)}; + r = g_vk.AllocateMemory(g_dev, &mai, NULL, &t->mem); + if (r != VK_SUCCESS) { + wnc_log("error", "effects: %s image %dx%d (%llu bytes): vkAllocateMemory %d", what, w, h, + (unsigned long long)req.size, (int)r); + target_destroy(t); return -1; + } + g_vk.BindImageMemory(g_dev, t->img, t->mem, 0); + if (!(t->view = make_view(t->img, g_fx_fmt))) { target_destroy(t); return -1; } + VkFramebufferCreateInfo fi = {.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = g_rp, + .attachmentCount = 1, .pAttachments = &t->view, .width = (uint32_t)w, + .height = (uint32_t)h, .layers = 1}; + if (g_vk.CreateFramebuffer(g_dev, &fi, NULL, &t->fb) != VK_SUCCESS) { target_destroy(t); return -1; } + if (!(t->ds = alloc_ds(t->view))) { target_destroy(t); return -1; } + t->w = w; t->h = h; + return 0; +} + +static void scene_release(void) { + if (g_scene.ds) g_vk.FreeDescriptorSets(g_dev, g_dpool, 1, &g_scene.ds); + if (g_scene.view) g_vk.DestroyImageView(g_dev, g_scene.view, NULL); + memset(&g_scene, 0, sizeof(g_scene)); +} + +static int scene_ensure(VkImage img, int w, int h) { + if (g_scene.img == img && g_scene.w == w && g_scene.h == h && g_scene.ds) return 0; + scene_release(); + if (!(g_scene.view = make_view(img, g_scene_fmt))) return -1; + if (!(g_scene.ds = alloc_ds(g_scene.view))) { scene_release(); return -1; } + g_scene.img = img; g_scene.w = w; g_scene.h = h; + return 0; +} + +static VkShaderModule shader(const uint32_t *code, size_t size) { + VkShaderModule m = VK_NULL_HANDLE; + VkShaderModuleCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = size, .pCode = code}; + if (g_vk.CreateShaderModule(g_dev, &ci, NULL, &m) != VK_SUCCESS) return VK_NULL_HANDLE; + return m; +} + +/* One full-quad post pipeline: upscale.vert + the effect's fragment shader, opaque, dynamic + * viewport/scissor โ€” the X11 renderer's createPostPipeline. */ +static VkPipeline post_pipeline(VkShaderModule vert, const uint32_t *frag_code, size_t frag_size, const char *name) { + VkShaderModule frag = shader(frag_code, frag_size); + if (!frag) { wnc_log("error", "effects: %s: vkCreateShaderModule failed", name); return VK_NULL_HANDLE; } + VkPipelineShaderStageCreateInfo st[2] = { + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vert, .pName = "main"}, + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = frag, .pName = "main"}}; + VkPipelineVertexInputStateCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo ia = {.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}; + VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo ds = {.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = 2, .pDynamicStates = dyn}; + VkPipelineViewportStateCreateInfo vp = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, .scissorCount = 1}; + VkPipelineRasterizationStateCreateInfo rs = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .lineWidth = 1.0f}; + VkPipelineMultisampleStateCreateInfo ms = {.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT}; + VkPipelineColorBlendAttachmentState ba = {.blendEnable = VK_FALSE, .colorWriteMask = 0xF}; + VkPipelineColorBlendStateCreateInfo cb = {.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, .pAttachments = &ba}; + VkGraphicsPipelineCreateInfo pi = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, + .pStages = st, .pVertexInputState = &vi, .pInputAssemblyState = &ia, + .pViewportState = &vp, .pRasterizationState = &rs, .pMultisampleState = &ms, + .pColorBlendState = &cb, .pDynamicState = &ds, .layout = g_pl, + .renderPass = g_rp, .subpass = 0}; + VkPipeline p = VK_NULL_HANDLE; + VkResult r = g_vk.CreateGraphicsPipelines(g_dev, VK_NULL_HANDLE, 1, &pi, NULL, &p); + g_vk.DestroyShaderModule(g_dev, frag, NULL); + if (r != VK_SUCCESS) { wnc_log("error", "effects: %s: vkCreateGraphicsPipelines %d", name, (int)r); return VK_NULL_HANDLE; } + return p; +} + +static void objects_destroy(void) { + if (!g_dev) return; + scene_release(); + target_destroy(&g_t[0]); target_destroy(&g_t[1]); target_destroy(&g_mid); + for (int i = 0; i < P_COUNT; i++) { if (g_pipe[i]) g_vk.DestroyPipeline(g_dev, g_pipe[i], NULL); g_pipe[i] = VK_NULL_HANDLE; } + if (g_dpool) g_vk.DestroyDescriptorPool(g_dev, g_dpool, NULL); + if (g_pl) g_vk.DestroyPipelineLayout(g_dev, g_pl, NULL); + if (g_dsl) g_vk.DestroyDescriptorSetLayout(g_dev, g_dsl, NULL); + if (g_sampler) g_vk.DestroySampler(g_dev, g_sampler, NULL); + if (g_rp) g_vk.DestroyRenderPass(g_dev, g_rp, NULL); + g_dpool = VK_NULL_HANDLE; g_pl = VK_NULL_HANDLE; g_dsl = VK_NULL_HANDLE; g_sampler = VK_NULL_HANDLE; g_rp = VK_NULL_HANDLE; + g_ready = 0; +} + +void vkp_effects_destroy(void) { objects_destroy(); g_dev = VK_NULL_HANDLE; } + +void vkp_effects_set_formats(VkFormat scene_fmt, VkFormat target_fmt) { + if (target_fmt != g_fx_fmt) { + /* The render pass, the pipelines and the targets are built for one format: start over. */ + if (g_ready) objects_destroy(); + g_fx_fmt = target_fmt; + g_scene_fmt = scene_fmt; + if (g_dev) + wnc_log("effects", "chain now works in %s (%s)", + target_fmt == FX_FORMAT ? "8-bit RGBA" + : target_fmt == VK_FORMAT_R16G16B16A16_SFLOAT ? "FP16 RGBA" : "10-bit RGB", + target_fmt == FX_FORMAT ? "SDR frames" + : target_fmt == VK_FORMAT_R16G16B16A16_SFLOAT ? "the HDR picture for frame generation" + : "the HDR composition's picture: PQ, or tone-mapped SDR with HDR output off"); + } else if (scene_fmt != g_scene_fmt) { + scene_release(); + g_scene_fmt = scene_fmt; + } +} + +/* Build every device object once, on the first active frame. 0 = ok, -1 = the chain is + * unavailable for this session (logged once; frames fall back to the plain blit). */ +static int objects_ensure(void) { + if (g_ready) return g_ready == 1 ? 0 : -1; + if (!g_dev || !g_vk.CreateGraphicsPipelines) { g_ready = -1; wnc_log("error", "effects: no device for the chain"); return -1; } + /* Render pass: one colour target, overwritten whole (DONT_CARE), left SHADER_READ_ONLY for the + * next pass. The dependencies order this pass after whoever read the target last (previous + * pass / previous frame's blit) and before whoever samples or blits it next. */ + VkAttachmentDescription att = {.format = g_fx_fmt, .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sub = {.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &ref}; + VkSubpassDependency deps[2] = { + {.srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}, + {.srcSubpass = 0, .dstSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT}}; + VkRenderPassCreateInfo rci = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &att, + .subpassCount = 1, .pSubpasses = &sub, .dependencyCount = 2, .pDependencies = deps}; + if (g_vk.CreateRenderPass(g_dev, &rci, NULL, &g_rp) != VK_SUCCESS) goto fail; + + VkSamplerCreateInfo sci = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE}; + if (g_vk.CreateSampler(g_dev, &sci, NULL, &g_sampler) != VK_SUCCESS) goto fail; + + VkDescriptorSetLayoutBinding b = {.binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT}; + VkDescriptorSetLayoutCreateInfo dli = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &b}; + if (g_vk.CreateDescriptorSetLayout(g_dev, &dli, NULL, &g_dsl) != VK_SUCCESS) goto fail; + VkPushConstantRange pcr = {.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, .offset = 0, .size = PC_MAX_SIZE}; + VkPipelineLayoutCreateInfo pli = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &g_dsl, + .pushConstantRangeCount = 1, .pPushConstantRanges = &pcr}; + if (g_vk.CreatePipelineLayout(g_dev, &pli, NULL, &g_pl) != VK_SUCCESS) goto fail; + VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8}; + VkDescriptorPoolCreateInfo dpi = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, .maxSets = 8, + .poolSizeCount = 1, .pPoolSizes = &ps}; + if (g_vk.CreateDescriptorPool(g_dev, &dpi, NULL, &g_dpool) != VK_SUCCESS) goto fail; + + VkShaderModule vert = shader(upscale_vert_code, sizeof(upscale_vert_code)); + if (!vert) goto fail; +#define MK(slot, code, name) do { g_pipe[slot] = post_pipeline(vert, code, sizeof(code), name); if (!g_pipe[slot]) { g_vk.DestroyShaderModule(g_dev, vert, NULL); goto fail; } } while (0) + MK(P_SGSR, sgsr_code, "sgsr"); + MK(P_SGSR_HQ, sgsr_quality_code, "sgsr_quality"); + MK(P_NIS, nis_code, "nis"); + MK(P_EASU, fsr_easu_code, "fsr_easu"); + MK(P_RCAS, fsr_rcas_code, "fsr_rcas"); + MK(P_CAS, cas_code, "cas"); + MK(P_COLOR, color_code, "color"); + MK(P_FXAA, fxaa_code, "fxaa"); + MK(P_TOON, toon_code, "toon"); + MK(P_HDR, hdr_code, "hdr"); + MK(P_NTSC, ntsc_code, "ntsc"); + MK(P_CRT, crt_code, "crt"); + MK(P_DEBAND, deband_code, "deband"); +#undef MK + g_vk.DestroyShaderModule(g_dev, vert, NULL); + g_ready = 1; + wnc_log("effects", "chain ready: 13 passes (SGSR, SGSR HQ, NIS, FSR EASU+RCAS, CAS, colour, FXAA, Toon, HDR, NTSC, CRT, deband) on %s", vkp_gpu_name()); + return 0; +fail: + objects_destroy(); + g_ready = -1; + wnc_log("error", "effects: the screen-effect chain could not be built on this driver; effects are off for this session"); + return -1; +} + +/* ------------------------------------------------------------------ recording */ + +static void barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkImageLayout to, + VkPipelineStageFlags src_stage, VkAccessFlags src_access, + VkPipelineStageFlags dst_stage, VkAccessFlags dst_access) { + VkImageMemoryBarrier b = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = from, .newLayout = to, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = img, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .srcAccessMask = src_access, .dstAccessMask = dst_access}; + g_vk.CmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &b); +} + +/* One pass: sample `src` (a descriptor in SHADER_READ_ONLY_OPTIMAL), draw the full quad into `dst`. */ +static void pass(VkCommandBuffer cmd, VkPipeline p, struct fx_target *dst, VkDescriptorSet src, + const void *pc, uint32_t pcsz) { + VkRenderPassBeginInfo rpi = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = g_rp, .framebuffer = dst->fb, + .renderArea = {{0, 0}, {(uint32_t)dst->w, (uint32_t)dst->h}}}; + VkViewport vp = {0, 0, (float)dst->w, (float)dst->h, 0, 1}; + VkRect2D sc = {{0, 0}, {(uint32_t)dst->w, (uint32_t)dst->h}}; + g_vk.CmdBeginRenderPass(cmd, &rpi, VK_SUBPASS_CONTENTS_INLINE); + g_vk.CmdSetViewport(cmd, 0, 1, &vp); + g_vk.CmdSetScissor(cmd, 0, 1, &sc); + g_vk.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, p); + g_vk.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pl, 0, 1, &src, 0, NULL); + g_vk.CmdPushConstants(cmd, g_pl, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, pcsz, pc); + g_vk.CmdDraw(cmd, 4, 1, 0, 0); + g_vk.CmdEndRenderPass(cmd); +} + +static const float FULL_NDC[4] = {-1.0f, -1.0f, 1.0f, 1.0f}; +#define SET_NDC(pc) memcpy((pc).ndc, FULL_NDC, sizeof(FULL_NDC)) + +VkImage vkp_effects_run(VkCommandBuffer cmd, VkImage scene, int scene_w, int scene_h, + int out_w, int out_h, int *res_w, int *res_h) { + const struct fx_settings *s; + vkp_effects_sync(); + s = &g_cur; + *res_w = scene_w; *res_h = scene_h; + + /* The chain's resolution: the scene's mapped size (X11 runs its effects at the output extent, + * with the scene already scaled by the base sampler). Clamped so a FILL-mapped 4K scene does + * not allocate beyond reason; the final blit resizes the remainder. */ + int cw = out_w > 0 ? out_w : scene_w, ch = out_h > 0 ? out_h : scene_h; + if (cw > FX_MAX_DIM) cw = FX_MAX_DIM; + if (ch > FX_MAX_DIM) ch = FX_MAX_DIM; + if (cw < 1) cw = 1; + if (ch < 1) ch = 1; + /* Spatial upscalers only have work when the scene is shown larger than it is (the X11 + * renderer's "render below display" gate); Sharpen (RCAS) runs at any size. */ + const int upscaling = cw > scene_w || ch > scene_h; + int spatial = 0; + if (spatial_mode(s->scaling) && (s->scaling == 6 || upscaling)) spatial = s->scaling; + const int nfx = effect_count(s); + const int resize = cw != scene_w || ch != scene_h; + /* Passes that write the ping-pong pair: the scaling/resize stage (if any) + the effects. + * Two targets are needed once any pass reads what an earlier one wrote. */ + const int npasses = ((spatial || resize) ? 1 : 0) + nfx; + + if ((spatial || nfx) && objects_ensure() == 0 && scene_ensure(scene, scene_w, scene_h) == 0 && + target_ensure(&g_t[0], cw, ch, "chain") == 0 && (npasses < 2 || target_ensure(&g_t[1], cw, ch, "chain") == 0) && + (!(spatial == 4 || spatial == 5) || target_ensure(&g_mid, cw, ch, "EASU") == 0)) { + /* `cur` = the image the next pass samples; `cur_ds` its descriptor. Every pass writes the + * target that is not being read (the pair alternates; the scene is read only first). */ + VkDescriptorSet cur_ds; + struct fx_target *cur = NULL; /* NULL = the scene image */ +#define FREE_TARGET() ((cur == &g_t[0]) ? &g_t[1] : &g_t[0]) + const float sharp01 = (float)s->sharp_pct / 100.0f; + + /* ---- stage 1: scaling (scene -> chain res) */ + if (spatial) { + barrier(cmd, scene, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); + struct fx_target *dst = FREE_TARGET(); + if (spatial == 3 || spatial == 8 || spatial == 7) { + struct pc_sgsr pc = {0}; + SET_NDC(pc); + pc.vp[0] = 1.0f / (float)scene_w; pc.vp[1] = 1.0f / (float)scene_h; + pc.vp[2] = (float)scene_w; pc.vp[3] = (float)scene_h; + /* SGSR EdgeSharpness 0.5 (slider 0) .. 4.5 (slider 100); NIS takes the slider as is. */ + pc.edge = spatial == 7 ? sharp01 : 0.5f + sharp01 * 4.0f; + pass(cmd, g_pipe[spatial == 3 ? P_SGSR : spatial == 8 ? P_SGSR_HQ : P_NIS], dst, g_scene.ds, &pc, sizeof(pc)); + } else if (spatial == 6) { + /* Sharpen: RCAS fetches the scene 1:1 (outputSize = scene size) while the quad + * covers the chain target = nearest scale + sharpen, as the X11 renderer does. */ + struct pc_rcas pc = {0}; + SET_NDC(pc); + rcas_con(&pc, sharp01); + pc.outW = (float)scene_w; pc.outH = (float)scene_h; + pass(cmd, g_pipe[P_RCAS], dst, g_scene.ds, &pc, sizeof(pc)); + } else { /* FSR (4) / FSR Fit (5): EASU scene -> mid, RCAS mid -> dst, both at chain res. + * Fill vs fit is the Fullscreen Mode's business here (the mapping), so both + * run the same EASU+RCAS pair. */ + struct pc_easu e = {0}; + struct pc_rcas r = {0}; + SET_NDC(e); + easu_con(&e, (float)scene_w, (float)scene_h, (float)cw, (float)ch); + e.outW = (float)cw; e.outH = (float)ch; + pass(cmd, g_pipe[P_EASU], &g_mid, g_scene.ds, &e, sizeof(e)); + SET_NDC(r); + rcas_con(&r, sharp01); + r.outW = (float)cw; r.outH = (float)ch; + pass(cmd, g_pipe[P_RCAS], dst, g_mid.ds, &r, sizeof(r)); + } + cur = dst; cur_ds = dst->ds; + } else if (resize) { + /* No spatial pass: a plain filtered resize to the chain resolution first (Linear or + * Nearest per the scaling mode), so the effects run at output resolution like X11. */ + struct fx_target *dst = FREE_TARGET(); + barrier(cmd, scene, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT); + barrier(cmd, dst->img, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + VkImageBlit bl = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .srcOffsets = {{0, 0, 0}, {scene_w, scene_h, 1}}, + .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffsets = {{0, 0, 0}, {cw, ch, 1}}}; + g_vk.CmdBlitImage(cmd, scene, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst->img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &bl, vkp_effects_blit_filter()); + barrier(cmd, dst->img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); + cur = dst; cur_ds = dst->ds; + } else { + /* Scene already at output size: the first effect samples it directly. */ + barrier(cmd, scene, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); + cur = NULL; cur_ds = g_scene.ds; + } + + /* ---- stage 2: the effects, in the X11 renderer's locked order + * FXAA -> Toon -> Colour -> CAS -> HDR -> NTSC -> CRT -> Debanding (terminal dither). + * Each writes the free ping-pong target; the one just read becomes free. */ + enum { FX_FXAA, FX_TOON, FX_COLOR, FX_CAS, FX_HDR, FX_NTSC, FX_CRT, FX_DEBAND }; + int order[8], n = 0; + if (s->fxaa) order[n++] = FX_FXAA; + if (s->toon) order[n++] = FX_TOON; + if (s->color_on) order[n++] = FX_COLOR; + if (cas_active(s)) order[n++] = FX_CAS; + if (s->hdr_on) order[n++] = FX_HDR; + if (s->ntsc) order[n++] = FX_NTSC; + if (s->crt) order[n++] = FX_CRT; + if (s->deband_on) order[n++] = FX_DEBAND; + for (int i = 0; i < n; i++) { + struct fx_target *dst = FREE_TARGET(); + struct pc_res pr = {0}; + SET_NDC(pr); + pr.res[0] = (float)cw; pr.res[1] = (float)ch; + switch (order[i]) { + case FX_FXAA: pass(cmd, g_pipe[P_FXAA], dst, cur_ds, &pr, sizeof(pr)); break; + case FX_TOON: pass(cmd, g_pipe[P_TOON], dst, cur_ds, &pr, sizeof(pr)); break; + case FX_COLOR: { + struct pc_color pc = {0}; + SET_NDC(pc); + pc.b = s->brightness; pc.c = s->contrast; pc.g = s->gamma; pc.s = s->saturation; + pass(cmd, g_pipe[P_COLOR], dst, cur_ds, &pc, sizeof(pc)); + break; + } + case FX_CAS: pr.extra = (float)s->cas_pct / 100.0f; pass(cmd, g_pipe[P_CAS], dst, cur_ds, &pr, sizeof(pr)); break; + case FX_HDR: pass(cmd, g_pipe[P_HDR], dst, cur_ds, &pr, sizeof(pr)); break; + case FX_NTSC: + /* cos/sin(chromaPhase*0.5) has period 4 in the counter: mod 4 keeps the shimmer exact. */ + pr.extra = (float)(g_ntsc_frame++ & 3u); + pass(cmd, g_pipe[P_NTSC], dst, cur_ds, &pr, sizeof(pr)); + break; + case FX_CRT: pass(cmd, g_pipe[P_CRT], dst, cur_ds, &pr, sizeof(pr)); break; + case FX_DEBAND: pr.extra = (float)s->deband_pct / 100.0f; pass(cmd, g_pipe[P_DEBAND], dst, cur_ds, &pr, sizeof(pr)); break; + } + cur = dst; cur_ds = dst->ds; + } + +#undef FREE_TARGET + if (cur) { + /* The result: written by the last render pass, now for the output blit. */ + barrier(cmd, cur->img, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT); + *res_w = cur->w; *res_h = cur->h; + return cur->img; + } + /* Nothing ran after all (cannot happen with nfx > 0): the scene was moved to + * SHADER_READ_ONLY above; bring it to TRANSFER_SRC for the blit. */ + barrier(cmd, scene, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT); + return scene; + } + + /* Nothing to do (a spatial mode with no room to upscale and no effect), or the chain is + * unavailable: present the scene as composited. */ + barrier(cmd, scene, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT); + return scene; +} diff --git a/app/src/main/cpp/waylandcomp/src/effects_chain.h b/app/src/main/cpp/waylandcomp/src/effects_chain.h new file mode 100644 index 000000000..3ceb979e8 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/effects_chain.h @@ -0,0 +1,81 @@ +#ifndef EFFECTS_CHAIN_H +#define EFFECTS_CHAIN_H +/* + * Screen-effect chain for the embedded Wayland compositor: the X11 Vulkan renderer's post + * chain (the .frag files in app/src/main/cpp/winlator, VulkanRendererContext::recordUpscalePasses) run + * between the composited scene and the output blit, with the same SPIR-V, push-constant + * layouts, uniform ranges and pass order, so a saved preset looks the same on both backends. + * + * Present-pass hook order (vk_present.c, vkp_render): + * scene (all draws blitted 1:1 into a scene-sized image) + * -> vkp_effects_run() this file: scaling -> effects, in the X11 order + * -> vkp_framegen_run() frame generation (feat/wayland-framegen; inserted after this) + * -> update_map / blit Fullscreen Mode + Screen Alignment, onto the swapchain image + * -> present + * When nothing is on, vkp_effects_active() is 0 and vkp_render keeps its plain path (the draws + * are blitted straight through the mapping; only the blit filter follows Linear/Nearest). + * + * Pass order (LOCKED to the X11 Vulkan renderer): + * scaling (SGSR / SGSR HQ / NIS / FSR EASU+RCAS / Sharpen=RCAS; None/Linear/Nearest = a + * filtered resize) -> FXAA -> Toon -> Colour -> CAS -> HDR -> NTSC -> CRT -> Debanding. + * Scaling resizes the scene to its mapped output size (there is no render scale on Wayland); + * every later pass runs at that resolution and ping-pongs between two targets. + * + * Settings come from any thread (JNI); the compositor thread snapshots them once per frame and + * logs one `effects` line per change. Vulkan objects are created lazily on the first active frame. + */ +#define VK_USE_PLATFORM_ANDROID_KHR +#include <vulkan/vulkan.h> + +/* Compositor thread, from dev_init: the device the chain builds its objects on. */ +void vkp_effects_bind_device(VkDevice dev, VkPhysicalDevice pd, + const VkPhysicalDeviceMemoryProperties *memprops); + +/* Compositor thread: pick up settings changed since the last call. Returns 1 when something + * changed (the caller redraws even a static scene); logs the `effects` line. */ +int vkp_effects_sync(void); + +/* Compositor thread: 1 when the frame must go through the scene image + chain (any effect on, + * or a spatial scaling mode). Modes None/Linear/Nearest alone are NOT active: the plain blit + * handles them through vkp_effects_blit_filter(). */ +int vkp_effects_active(void); + +/* The filter for the scene -> output resize: NEAREST for scaling mode 2, else LINEAR. */ +VkFilter vkp_effects_blit_filter(void); + +/* THE HOOK. Records the chain into `cmd`. `scene` (scene_w x scene_h, R8G8B8A8_UNORM, in + * TRANSFER_DST_OPTIMAL after the composite) -> the image to present, returned in + * TRANSFER_SRC_OPTIMAL with its size in res_w / res_h. out_w/out_h = the scene's mapped size on + * the output (what the scaling modes resize to; the caller maps the result over the whole scene + * afterwards, so Fullscreen Mode / Alignment still apply after the chain). Returns `scene` itself + * (transitioned to TRANSFER_SRC_OPTIMAL) when there is nothing to do or the chain is unavailable. */ +VkImage vkp_effects_run(VkCommandBuffer cmd, VkImage scene, int scene_w, int scene_h, + int out_w, int out_h, int *res_w, int *res_h); + +/* Compositor thread: drop every Vulkan object (device teardown / device lost). */ +void vkp_effects_destroy(void); + +/* Compositor thread, before vkp_effects_run(): the format of the scene image it will be handed and the + * format its own targets use. R8G8B8A8 / R8G8B8A8 for every SDR frame (the default); A2B10G10R10 / + * A2B10G10R10 while an HDR picture runs through it (hdr_compose.h). A target-format change rebuilds the + * chain's objects on its next run (once per switch). */ +void vkp_effects_set_formats(VkFormat scene_fmt, VkFormat target_fmt); + +/* ---- settings (any thread; values 1:1 with the X11 Vulkan renderer's setters) ---- */ +/* 0=None 1=Linear 2=Nearest 3=SGSR 4=FSR 5=FSR Fit 6=Sharpen 7=NIS 8=SGSR HQ */ +void vkp_effects_set_scaling(int mode); +/* Slider 0..100: RCAS lobe scale (FSR/Sharpen), SGSR EdgeSharpness 0.5..4.5, NIS sharpness. */ +void vkp_effects_set_upscale_sharpness(int pct); +/* CAS toggle + slider 0..100 (0 = the pass is off). */ +void vkp_effects_set_cas(int enabled, int pct); +void vkp_effects_set_hdr(int enabled); +/* Terminal dither: slider 0..200 -> strength/100 LSBs. */ +void vkp_effects_set_deband(int enabled, int pct); +/* Colour grade in slider units (brightness/contrast -100..100, gamma 0.5..3, saturation 0..200 + * percent; neutral = 0,0,1,100 turns the pass off) + the four shader toggles. */ +void vkp_effects_set_screen(float brightness, float contrast, float gamma, float saturation, + int fxaa, int toon, int crt, int ntsc); +/* The Look the current controls match ("Custom" when none), for the log line only. */ +void vkp_effects_set_look(const char *name); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/framegen_bridge.c b/app/src/main/cpp/waylandcomp/src/framegen_bridge.c new file mode 100644 index 000000000..cc768e9c3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/framegen_bridge.c @@ -0,0 +1,468 @@ +/* Frame generation on the Wayland backend - see framegen_bridge.h. */ +#define _POSIX_C_SOURCE 200809L +#include "framegen_bridge.h" +#include "framegen_engine.h" +#include "vk_loader.h" +#include <pthread.h> +#include <stdatomic.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <android/log.h> + +void wnc_log(const char *tag, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +#define FGLOG(...) wnc_log("framegen", __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "WNWayland", __VA_ARGS__) + +/* ---- app controls (written from any thread, read on the compositor thread) ------------- */ +static _Atomic int g_kind = VKP_FG_ENGINE_LSFG; +static _Atomic int g_armed; +static _Atomic int g_mult = 2; +static _Atomic int g_dis_min_side = 180; +static _Atomic int g_target_fps; +static _Atomic int g_cfg_dirty = 1; +static float g_flow = 0.8f, g_refresh_hz; /* under g_lock */ +static char *g_cache_path; /* under g_lock */ +static _Atomic int g_cache_gen; /* bumps when the path changes */ +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; + +/* ---- device -------------------------------------------------------------------------- */ +static VkDevice g_dev; +static VkInstance g_inst; +static PFN_vkGetInstanceProcAddr g_gipa; +static VkPhysicalDevice g_pd; +static VkQueue g_queue; +static uint32_t g_qfam; +static int g_dev_ready, g_dev_lost, g_features_enabled; +static VkPhysicalDeviceMemoryProperties g_memprops; +static struct { + PFN_vkCreateImage CreateImage; + PFN_vkDestroyImage DestroyImage; + PFN_vkCreateImageView CreateImageView; + PFN_vkDestroyImageView DestroyImageView; + PFN_vkGetImageMemoryRequirements GetImageMemoryRequirements; + PFN_vkAllocateMemory AllocateMemory; + PFN_vkFreeMemory FreeMemory; + PFN_vkBindImageMemory BindImageMemory; + PFN_vkCmdPipelineBarrier CmdPipelineBarrier; + PFN_vkDeviceWaitIdle DeviceWaitIdle; +} vk; + +/* ---- engine + generation ring (compositor thread) ------------------------------------ */ +static int g_engine_kind = -1; /* kind of the running engine, -1 = none */ +static int g_engine_ok; +/* The engine of this kind (with this cache) could not start or build its chain; not retried + * every frame - a new engine selection or a new cache clears it. */ +static _Atomic int g_failed_kind = -1; +static _Atomic int g_failed_cache_gen = -1; + +struct ring_image { VkImage img; VkDeviceMemory mem; VkImageView view; int fresh; }; +static struct ring_image g_ring[VKP_FG_MAX_GENERATIONS]; +static uint32_t g_ring_n, g_ring_w, g_ring_h; +static VkFormat g_ring_fmt; + +static uint64_t g_source_frames; +static uint32_t g_planned; /* generations planned for the current source frame */ +static int g_was_armed, g_generating_logged; +static uint32_t g_built_w, g_built_h; +static VkFormat g_built_fmt; +/* Deeper formats (FP16 for HDR frames) the engine failed to build its chain in this session. */ +static VkFormat g_refused_fmt[4]; +static int g_refused_n; +static int format_refused(VkFormat f) { + for (int i = 0; i < g_refused_n; i++) if (g_refused_fmt[i] == f) return 1; + return 0; +} + +/* ---- stats ------------------------------------------------------------------------- */ +static _Atomic unsigned g_stat_generated; /* since the last stats_take */ +static _Atomic uint64_t g_total_generated; +static _Atomic uint64_t g_total_presented; +static float g_presented_rate, g_source_rate; /* smoothed, 0.5 s windows */ +static uint32_t g_present_accum, g_source_accum; +static int64_t g_window_start_ns; + +static int64_t now_ns(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +/* ====================================================================== app controls */ + +void vkp_framegen_set_engine(int kind) { + if (kind != VKP_FG_ENGINE_LSFG && kind != VKP_FG_ENGINE_DIS) kind = VKP_FG_ENGINE_LSFG; + atomic_store(&g_kind, kind); + atomic_store(&g_cfg_dirty, 1); +} + +void vkp_framegen_set_armed(int armed, int multiplier) { + if (multiplier < 2) multiplier = 2; + if (multiplier > VKP_FG_MAX_GENERATIONS + 1) multiplier = VKP_FG_MAX_GENERATIONS + 1; + if (atomic_exchange(&g_mult, multiplier) != multiplier) atomic_store(&g_cfg_dirty, 1); + atomic_store(&g_armed, armed ? 1 : 0); + LOGD("framegen: set_armed(%d, x%d)", armed, multiplier); +} + +void vkp_framegen_set_lsfg_cache_path(const char *path) { + pthread_mutex_lock(&g_lock); + int changed = (path == NULL) != (g_cache_path == NULL) || + (path && g_cache_path && strcmp(path, g_cache_path) != 0); + if (changed) { + free(g_cache_path); + g_cache_path = path ? strdup(path) : NULL; + atomic_fetch_add(&g_cache_gen, 1); + } + pthread_mutex_unlock(&g_lock); +} + +void vkp_framegen_set_tuning(float flow_scale, float refresh_hz) { + if (flow_scale < 0.25f) flow_scale = 0.25f; + if (flow_scale > 1.0f) flow_scale = 1.0f; + pthread_mutex_lock(&g_lock); + g_flow = flow_scale; + g_refresh_hz = refresh_hz > 1.0f ? refresh_hz : 0.0f; + pthread_mutex_unlock(&g_lock); + atomic_store(&g_cfg_dirty, 1); +} + +void vkp_framegen_set_engine_tuning(int flow_min_side, int target_fps) { + if (flow_min_side < 64) flow_min_side = 64; + if (flow_min_side > 1080) flow_min_side = 1080; + if (target_fps < 0) target_fps = 0; + atomic_store(&g_dis_min_side, flow_min_side); + atomic_store(&g_target_fps, target_fps); + atomic_store(&g_cfg_dirty, 1); +} + +static int start_failed_for(int kind) { + return atomic_load(&g_failed_kind) == kind && atomic_load(&g_failed_cache_gen) == atomic_load(&g_cache_gen); +} + +static void mark_failed(int kind) { + atomic_store(&g_failed_cache_gen, atomic_load(&g_cache_gen)); + atomic_store(&g_failed_kind, kind); +} + +/* Same codes as VulkanRenderer.getFrameGenProblem(); computed from the live state so an engine + * switch from the app is answered at once. */ +int vkp_framegen_problem(void) { + if (!g_dev_ready) return -1; + int kind = atomic_load(&g_kind); + if (!fge_caps_ok(kind)) return 1; + if (start_failed_for(kind)) return 2; + if (g_engine_ok && g_engine_kind == kind && fge_unavailable()) return 2; + return 0; +} +const char *vkp_framegen_caps_reason(void) { return fge_caps_reason(); } + +void vkp_framegen_stats(float out[6]) { + float accepted = 0.0f, src = 0.0f, thermal = -1.0f; + if (g_engine_ok) fge_telemetry(&accepted, &src, &thermal); + /* Planned is what is generated; the governor is off on both paths. Our own measured + * source rate serves both engines (DIS measures none). */ + out[0] = (float)g_planned; + out[1] = (float)g_planned; + out[2] = g_source_rate; + out[3] = g_presented_rate; + out[4] = thermal; + out[5] = -1.0f; +} + +unsigned vkp_framegen_stats_take(void) { return atomic_exchange(&g_stat_generated, 0u); } + +uint64_t vkp_framegen_total_presented(void) { return atomic_load(&g_total_presented); } +uint64_t vkp_framegen_total_generated(void) { return atomic_load(&g_total_generated); } + +/* ====================================================================== device setup */ + +const void *vkp_framegen_device_features(VkInstance inst, PFN_vkGetInstanceProcAddr gipa, + VkPhysicalDevice pd) { + g_pd = pd; g_inst = inst; g_gipa = gipa; + return fge_probe(gipa, inst, pd, VK_FORMAT_R8G8B8A8_UNORM); +} + +void vkp_framegen_device_ready(VkDevice dev, VkQueue queue, uint32_t qfam, int features_enabled) { + g_dev = dev; g_queue = queue; g_qfam = qfam; g_features_enabled = features_enabled; +#define R(n) vk.n = (PFN_vk##n)g_vk.GetDeviceProcAddr(dev, "vk" #n) + R(CreateImage); R(DestroyImage); R(CreateImageView); R(DestroyImageView); + R(GetImageMemoryRequirements); R(AllocateMemory); R(FreeMemory); R(BindImageMemory); + R(CmdPipelineBarrier); R(DeviceWaitIdle); +#undef R + g_vk.GetPhysicalDeviceMemoryProperties(g_pd, &g_memprops); + fge_device_ready(g_gipa, g_inst, g_pd, dev, queue, qfam, features_enabled); + g_dev_ready = vk.CreateImage && vk.CreateImageView && vk.CmdPipelineBarrier; + FGLOG("engines ready on the compositor's device: LSFG Native %s (%s), DIS Native %s", + fge_caps_ok(VKP_FG_ENGINE_LSFG) ? "available" : "unavailable", fge_caps_reason(), + fge_caps_ok(VKP_FG_ENGINE_DIS) ? "available" : "unavailable"); +} + +void vkp_framegen_device_lost(void) { g_dev_lost = 1; } + +/* ====================================================================== ring */ + +static void destroy_ring(void) { + if (!g_ring_n) return; + if (vk.DeviceWaitIdle) vk.DeviceWaitIdle(g_dev); + for (uint32_t i = 0; i < g_ring_n; i++) { + if (g_ring[i].view) vk.DestroyImageView(g_dev, g_ring[i].view, NULL); + if (g_ring[i].img) vk.DestroyImage(g_dev, g_ring[i].img, NULL); + if (g_ring[i].mem) vk.FreeMemory(g_dev, g_ring[i].mem, NULL); + memset(&g_ring[i], 0, sizeof(g_ring[i])); + } + g_ring_n = 0; + if (g_engine_ok) fge_forget_targets(); +} + +static int memory_type(uint32_t bits, VkMemoryPropertyFlags want) { + for (uint32_t i = 0; i < g_memprops.memoryTypeCount; i++) + if ((bits & (1u << i)) && (g_memprops.memoryTypes[i].propertyFlags & want) == want) return (int)i; + return -1; +} + +/* The generation targets: written by the chain through a storage view, read by the screen + * blit. Kept in GENERAL for their whole life (both uses allow it), so there is no layout dance + * per frame - only access barriers. */ +static int ensure_ring(uint32_t w, uint32_t h, VkFormat fmt, uint32_t n) { + if (n > VKP_FG_MAX_GENERATIONS) n = VKP_FG_MAX_GENERATIONS; + if (g_ring_n == n && g_ring_w == w && g_ring_h == h && g_ring_fmt == fmt) return 1; + destroy_ring(); + for (uint32_t i = 0; i < n; i++) { + struct ring_image *r = &g_ring[i]; + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = fmt, + .extent = {w, h, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + if (vk.CreateImage(g_dev, &ici, NULL, &r->img) != VK_SUCCESS) { destroy_ring(); return 0; } + VkMemoryRequirements req; + vk.GetImageMemoryRequirements(g_dev, r->img, &req); + int idx = memory_type(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = req.size, .memoryTypeIndex = (uint32_t)idx}; + if (idx < 0 || vk.AllocateMemory(g_dev, &mai, NULL, &r->mem) != VK_SUCCESS) { destroy_ring(); return 0; } + vk.BindImageMemory(g_dev, r->img, r->mem, 0); + VkImageViewCreateInfo vci = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = r->img, + .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = fmt, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + if (vk.CreateImageView(g_dev, &vci, NULL, &r->view) != VK_SUCCESS) { destroy_ring(); return 0; } + r->fresh = 1; + g_ring_n = i + 1; + } + g_ring_w = w; g_ring_h = h; g_ring_fmt = fmt; + LOGD("framegen: generation ring %ux%u x%u", w, h, n); + return 1; +} + +/* ====================================================================== present path */ + +static int engine_usable(void) { + int kind = atomic_load(&g_kind); + return g_dev_ready && !g_dev_lost && fge_caps_ok(kind) && !start_failed_for(kind); +} + +int vkp_framegen_format_ok(VkFormat fmt) { + if (fmt == VK_FORMAT_R8G8B8A8_UNORM) return 1; + if (!g_dev_ready || g_dev_lost || format_refused(fmt)) return 0; + return fge_format_ok(fmt); +} + +/* The engine could not build its chain in a deeper format: refuse the format for the session and + * restart the engine on the next frame (a failed build is sticky inside both engines). */ +static void refuse_format(VkFormat fmt, int kind, int w, int h, const char *what) { + if (g_refused_n < (int)(sizeof(g_refused_fmt) / sizeof(g_refused_fmt[0]))) g_refused_fmt[g_refused_n++] = fmt; + FGLOG("%s could not %s in format %d (the HDR picture's FP16) at %dx%d: it restarts, and HDR frames go through " + "it in 8 bits from the next frame", fge_engine_name(kind), what, (int)fmt, w, h); + destroy_ring(); + fge_stop(); + g_engine_ok = 0; g_engine_kind = -1; +} + +int vkp_framegen_active(void) { + return atomic_load(&g_armed) && engine_usable(); +} + +int vkp_framegen_extra_images(void) { + if (!vkp_framegen_active()) return 0; + int m = atomic_load(&g_mult); + return m - 1 > VKP_FG_MAX_GENERATIONS ? VKP_FG_MAX_GENERATIONS : m - 1; +} + +static int ensure_engine(int kind) { + if (g_engine_ok && g_engine_kind == kind) return 1; + if (start_failed_for(kind)) return 0; + if (g_engine_ok) { destroy_ring(); fge_stop(); g_engine_ok = 0; g_engine_kind = -1; } + char *path = NULL; + pthread_mutex_lock(&g_lock); + if (g_cache_path) path = strdup(g_cache_path); + pthread_mutex_unlock(&g_lock); + if (kind == VKP_FG_ENGINE_LSFG && !path) { + FGLOG("LSFG Native can't start: no shader cache (import Lossless.dll in Settings)"); + mark_failed(kind); + return 0; + } + int r = fge_start(kind, path); + free(path); + if (r != 0) { + FGLOG("%s failed to start on the compositor's driver; frame generation stays off", + fge_engine_name(kind)); + mark_failed(kind); + return 0; + } + g_engine_ok = 1; g_engine_kind = kind; + atomic_store(&g_cfg_dirty, 1); + g_built_w = g_built_h = 0; + FGLOG("%s engine ready (%s)", fge_engine_name(kind), fge_build_info()); + return 1; +} + +static int g_logged_mult; +static void log_arm_transition(int armed, int kind, int mult) { + if (armed == g_was_armed) { + if (armed && mult != g_logged_mult) { + g_logged_mult = mult; + FGLOG("%s multiplier changed to x%d (%d interpolated frames per game frame)", fge_engine_name(kind), mult, mult - 1); + } + return; + } + g_was_armed = armed; + if (armed) { + g_logged_mult = mult; + float flow, hz; + pthread_mutex_lock(&g_lock); flow = g_flow; hz = g_refresh_hz; pthread_mutex_unlock(&g_lock); + FGLOG("%s x%d armed (flow scale %.2f, panel %.0f Hz): real frames are presented one slot late " + "with the interpolated frames ahead of them", fge_engine_name(kind), mult, (double)flow, (double)hz); + } else { + FGLOG("frame generation off (%llu frames generated this session)", (unsigned long long)atomic_load(&g_total_generated)); + g_generating_logged = 0; + } +} + +int vkp_framegen_run(VkCommandBuffer cmd, VkImage scene, VkImageView scene_view, int w, int h, + VkFormat fmt, VkImage gens[VKP_FG_MAX_GENERATIONS]) { + (void)scene_view; + int kind = atomic_load(&g_kind); + int armed = atomic_load(&g_armed); + int mult = atomic_load(&g_mult); + g_planned = 0; + if (!armed || !engine_usable() || w <= 0 || h <= 0) { log_arm_transition(0, kind, mult); return 0; } + if (!ensure_engine(kind)) { log_arm_transition(0, kind, mult); return 0; } + log_arm_transition(1, kind, mult); + + if (atomic_exchange(&g_cfg_dirty, 0)) { + float flow, hz; + pthread_mutex_lock(&g_lock); flow = g_flow; hz = g_refresh_hz; pthread_mutex_unlock(&g_lock); + fge_configure((uint32_t)mult, flow, hz, atomic_load(&g_dis_min_side), + atomic_load(&g_target_fps)); + } + if (!fge_prepare((uint32_t)w, (uint32_t)h, fmt)) { + if (fmt != VK_FORMAT_R8G8B8A8_UNORM && fge_unavailable()) { + refuse_format(fmt, kind, w, h, "build its chain"); + return 0; + } + if (fge_unavailable()) { + FGLOG("%s could not build its chain at %dx%d; frame generation stays off", fge_engine_name(kind), w, h); + mark_failed(kind); + } + return 0; + } + if (g_built_w != (uint32_t)w || g_built_h != (uint32_t)h || g_built_fmt != fmt) { + g_built_w = (uint32_t)w; g_built_h = (uint32_t)h; g_built_fmt = fmt; + g_generating_logged = 0; + } + /* With a target rate the pacer picks the generation count itself (it may exceed the + * multiplier to reach the target, as on X11), so the ring has to hold the engine's maximum + * rather than the multiplier's share. */ + uint32_t want = atomic_load(&g_target_fps) > 0 ? (uint32_t)VKP_FG_MAX_GENERATIONS + : (uint32_t)(mult - 1); + if (!ensure_ring((uint32_t)w, (uint32_t)h, fmt, want)) { + if (fmt != VK_FORMAT_R8G8B8A8_UNORM) { refuse_format(fmt, kind, w, h, "make its generation ring"); return 0; } + FGLOG("no memory for the generation ring (%dx%d x%u); frame generation stays off", w, h, want); + mark_failed(kind); + return 0; + } + + uint32_t n_gen = fge_plan(g_ring_n, ++g_source_frames, g_presented_rate); + if (n_gen > g_ring_n) n_gen = g_ring_n; + g_planned = n_gen; + + /* The scene image was just written (blits, and whatever the effects chain did); make that + * visible to the engine's copy (TRANSFER) and shaders (COMPUTE). Fresh ring images get their + * one UNDEFINED -> GENERAL transition here; the others need TRANSFER-read -> COMPUTE-write. */ + VkImageMemoryBarrier bars[1 + VKP_FG_MAX_GENERATIONS]; + uint32_t nb = 0; + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = scene, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + for (uint32_t g = 0; g < n_gen; g++) { + struct ring_image *r = &g_ring[g]; + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = r->fresh ? 0 : VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT, + .oldLayout = r->fresh ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = r->img, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + r->fresh = 0; + } + vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, 0, NULL, 0, NULL, nb, bars); + + /* Frame N in; the shared passes (the whole flow pyramid) run only when generating. */ + fge_process(cmd, scene, (uint32_t)w, (uint32_t)h, n_gen); + for (uint32_t g = 0; g < n_gen; g++) { + fge_generate(cmd, g, n_gen, g_ring[g].img, g_ring[g].view, (uint32_t)w, (uint32_t)h); + gens[g] = g_ring[g].img; + } + if (n_gen) { + /* Compute writes -> the screen blits (this command buffer and the later per-present ones; + * a barrier's first scope covers everything submitted earlier on the queue). */ + nb = 0; + for (uint32_t g = 0; g < n_gen; g++) + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = g_ring[g].img, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, nb, bars); + if (!g_generating_logged) { + g_generating_logged = 1; + FGLOG("generating: %s x%d at %dx%d (%u interpolated frame%s per game frame%s)", + fge_engine_name(kind), mult, w, h, n_gen, n_gen == 1 ? "" : "s", + fmt == VK_FORMAT_R16G16B16A16_SFLOAT ? ", FP16: the HDR picture" : ""); + } + } + return (int)n_gen; +} + +void vkp_framegen_presented(int generated) { + if (generated < 0) generated = 0; + int64_t now = now_ns(); + if (!g_window_start_ns) { g_window_start_ns = now; g_present_accum = 0; g_source_accum = 0; } + g_present_accum += (uint32_t)generated + 1u; + g_source_accum += 1u; + atomic_fetch_add(&g_total_presented, (uint64_t)generated + 1u); + if (generated) { + atomic_fetch_add(&g_stat_generated, (unsigned)generated); + atomic_fetch_add(&g_total_generated, (uint64_t)generated); + } + float elapsed = (float)(now - g_window_start_ns) / 1e9f; + if (elapsed < 0.5f) return; /* half-second window, like the X11 renderer */ + float rate = (float)g_present_accum / elapsed, src = (float)g_source_accum / elapsed; + g_presented_rate = g_presented_rate > 0.0f ? g_presented_rate + (rate - g_presented_rate) * 0.25f : rate; + g_source_rate = g_source_rate > 0.0f ? g_source_rate + (src - g_source_rate) * 0.25f : src; + g_window_start_ns = now; + g_present_accum = 0; + g_source_accum = 0; +} diff --git a/app/src/main/cpp/waylandcomp/src/framegen_bridge.h b/app/src/main/cpp/waylandcomp/src/framegen_bridge.h new file mode 100644 index 000000000..e9a91b8c0 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/framegen_bridge.h @@ -0,0 +1,92 @@ +#ifndef FRAMEGEN_BRIDGE_H +#define FRAMEGEN_BRIDGE_H +/* + * Frame generation on the Wayland backend. + * + * The two engines the X11 renderer runs - LSFG (the user's Lossless.dll chain, vk/lsfg) and + * DIS (the dense inverse search optical-flow chain, vk/dis) - are hosted here on the Wayland + * compositor's Turnip device. Both consume consecutive presented frames and emit interpolated + * frames between them; the guest never sees any of it. + * + * Present order per scene frame (vk_present.c): + * scene -> effects -> FRAME GENERATION -> mapping/blit -> swapchain + * The scene is composed 1:1 into an off-screen image, the effects chain runs on it, then + * vkp_framegen_run() (the one hook) feeds that image to the engine and hands back up to three + * generated images. vk_present.c blits the generated frames to the screen FIRST and the real + * frame LAST (interpolation produces frames that belong between N-1 and N), one FIFO present + * each, so the panel shows them on consecutive vblanks - the same pacing the X11 native path + * is device-proven with. No sleeps, no host-side pacer. + * + * Threads: the set_* controls are called from the app (JNI, any thread) and only store + * values; everything that touches Vulkan runs on the compositor thread inside vkp_render(). + */ +#include <stdint.h> +#ifndef VK_USE_PLATFORM_ANDROID_KHR +#define VK_USE_PLATFORM_ANDROID_KHR +#endif +#include <vulkan/vulkan.h> + +enum { VKP_FG_ENGINE_LSFG = 0, VKP_FG_ENGINE_DIS = 1 }; +/* Hard ceiling on generated frames per source frame (2x..4x -> 1..3), as on X11. */ +#define VKP_FG_MAX_GENERATIONS 3 + +/* ---- app controls (any thread) ------------------------------------------------------- */ +void vkp_framegen_set_engine(int kind); +/* multiplier 2..4; armed = 0 stops generating (the ring and the engine stay for a re-arm). */ +void vkp_framegen_set_armed(int armed, int multiplier); +/* LSFG Native: the SPIR-V cache built from the user's Lossless.dll (LsfgNative.ensureCache). */ +void vkp_framegen_set_lsfg_cache_path(const char *path); +/* Flow scale 0.25..1.0 and the panel's real refresh rate (the pacer never generates above it). */ +void vkp_framegen_set_tuning(float flow_scale, float refresh_hz); +/* Flow resolution in pixels of the frame's short side (DIS only) and the target output rate + * both engines pace to (0 = follow the panel). */ +void vkp_framegen_set_engine_tuning(int flow_min_side, int target_fps); +/* Same codes as VulkanRenderer.getFrameGenProblem(): -1 not known yet (device not up), 0 fine, + * 1 the driver lacks what the selected engine needs (vkp_framegen_caps_reason says what), + * 2 the engine failed to start / build its chain. */ +int vkp_framegen_problem(void); +const char *vkp_framegen_caps_reason(void); +/* Same shape as VulkanRenderer.getFrameGenStats(): {generations trusted, generations planned, + * real (source) fps, presented fps incl. generated frames, thermal (-1 = none), ms per + * generated frame (-1 = unknown)}. */ +void vkp_framegen_stats(float out[6]); +/* Generated frames presented since the last call (compositor.c, the 10 s stats line). */ +unsigned vkp_framegen_stats_take(void); +/* Cumulative counters for the HUD's output-frame source; never reset within a process. */ +uint64_t vkp_framegen_total_presented(void); +uint64_t vkp_framegen_total_generated(void); + +/* ---- vk_present.c: device setup (compositor thread) ----------------------------------- */ +/* Before vkCreateDevice: probe what the engines need on this physical device and return the + * pNext chain to hang on VkDeviceCreateInfo (NULL when the device cannot run the LSFG chain - + * DIS needs no feature). Owned by the bridge, valid until the next call. */ +const void *vkp_framegen_device_features(VkInstance inst, PFN_vkGetInstanceProcAddr gipa, + VkPhysicalDevice pd); +/* After the device exists (features_enabled = the chain above was accepted). */ +void vkp_framegen_device_ready(VkDevice dev, VkQueue queue, uint32_t qfam, int features_enabled); +/* VK_ERROR_DEVICE_LOST: never touch the device again. */ +void vkp_framegen_device_lost(void); + +/* ---- vk_present.c: present path (compositor thread) ------------------------------------ */ +/* 1 when this frame must go through the compositor pass (armed and the engine can run). Safe + * before the device is up (0). */ +int vkp_framegen_active(void); +/* Extra swapchain images wanted so every present of one scene frame is queued without waiting + * for a vblank: multiplier - 1 while armed, 0 otherwise. A change means "rebuild the swapchain". */ +int vkp_framegen_extra_images(void); +/* THE hook. Called once per composed scene frame, after the effects hook, with the scene image + * (GENERAL, w x h, fmt, TRANSFER_SRC|SAMPLED). Records the engine's chain into `cmd` and + * returns how many generated frames (0..VKP_FG_MAX_GENERATIONS) it produced, their images in + * gens[] (GENERAL, w x h, already made visible to TRANSFER reads). 0 = present the scene alone. */ +int vkp_framegen_run(VkCommandBuffer cmd, VkImage scene, VkImageView scene_view, int w, int h, + VkFormat fmt, VkImage gens[VKP_FG_MAX_GENERATIONS]); +/* After the presents of one scene frame: how many generated frames actually reached the + * swapchain (rate tracking + the stats counters). */ +void vkp_framegen_presented(int generated); +/* 1 when vkp_framegen_run can be fed `fmt` (the 8-bit scene always; FP16 for HDR frames where the + * device can generate in it and the engine has not refused it this session). A chain that fails to + * build in a deeper format is not an engine failure: the engine restarts and the format is refused, + * so the caller goes back to 8 bits. */ +int vkp_framegen_format_ok(VkFormat fmt); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/framegen_engine.c b/app/src/main/cpp/waylandcomp/src/framegen_engine.c new file mode 100644 index 000000000..46af9101b --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/framegen_engine.c @@ -0,0 +1,220 @@ +/* Runs the X11 renderer's frame-generation engines on the compositor's Turnip device. */ +#include "vk_dispatch.h" + +#include "framegen_engine.h" +#include "framegen_bridge.h" +#include "dis/vkr_dis.h" +#include "lsfg/vkr_lsfg.h" + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define DIS_FLOW_MIN_SIDE_DEFAULT 180u + +static VkPhysicalDevice g_pd; +static VkDevice g_dev; +static VkrLsfg *g_lsfg; +static VkrDis *g_dis; +static int g_kind = -1; +static int g_chain_ok; +static int g_chain_failed; + +static VkImage g_source; +static uint64_t g_last_plan_ns; + +static uint32_t g_multiplier = 2; +static float g_flow_scale = 0.7f; +static float g_refresh_hz; +static uint32_t g_dis_min_side = DIS_FLOW_MIN_SIDE_DEFAULT; +static uint32_t g_target_fps; + +static int g_caps_lsfg, g_caps_dis; +static const char *g_reason = "not probed"; + +static int format_supports(VkFormat fmt, VkFormatFeatureFlags want) { + if (!g_pd || !vkd.GetPhysicalDeviceFormatProperties) return 0; + VkFormatProperties props; + memset(&props, 0, sizeof(props)); + vkd.GetPhysicalDeviceFormatProperties(g_pd, fmt, &props); + return (props.optimalTilingFeatures & want) == want; +} + +static uint64_t now_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +const void *fge_probe(PFN_vkGetInstanceProcAddr gipa, VkInstance inst, VkPhysicalDevice pd, + VkFormat ring_fmt) { + g_pd = pd; + if (!vkd_bind_proc(gipa, inst)) { + g_caps_lsfg = g_caps_dis = 0; + g_reason = "the compositor's Vulkan entry points could not be resolved"; + return NULL; + } + + const VkFormatFeatureFlags ring_want = + VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT; + if (!format_supports(ring_fmt, ring_want)) { + g_caps_lsfg = g_caps_dis = 0; + g_reason = "the driver cannot store to the generation ring format"; + return NULL; + } + + /* LSFG keeps its motion pyramid in half-float; DIS needs only the ring. */ + g_caps_lsfg = format_supports(VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT + | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT); + g_caps_dis = 1; + g_reason = g_caps_lsfg ? "supported" : "the driver has no storage half-float images"; + + /* Neither engine needs a feature enabled at device creation. */ + return NULL; +} + +void fge_device_ready(PFN_vkGetInstanceProcAddr gipa, VkInstance inst, VkPhysicalDevice pd, + VkDevice dev, VkQueue queue, uint32_t qfam, int features_enabled) { + (void)queue; (void)qfam; (void)features_enabled; + g_pd = pd; + g_dev = dev; + if (!vkd.CreateInstance) vkd_bind_proc(gipa, inst); +} + +int fge_caps_ok(int kind) { + if (!g_dev) return 0; + return kind == VKP_FG_ENGINE_DIS ? g_caps_dis : g_caps_lsfg; +} + +const char *fge_caps_reason(void) { return g_reason; } + +int fge_format_ok(VkFormat fmt) { + return format_supports(fmt, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT + | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT); +} + +static void apply_config(void) { + if (g_lsfg) { + vkr_lsfg_configure(g_lsfg, g_multiplier, g_target_fps, g_flow_scale, g_refresh_hz, 0.0f); + } else if (g_dis) { + vkr_dis_configure(g_dis, g_dis_min_side, g_target_fps, g_refresh_hz); + } +} + +int fge_start(int kind, const char *cache_path) { + fge_stop(); + if (!g_dev || !g_pd) return -1; + + if (kind == VKP_FG_ENGINE_DIS) { + g_dis = vkr_dis_create(g_dev, g_pd); + if (!g_dis) return -1; + } else { + if (!cache_path || !*cache_path) return -1; + g_lsfg = vkr_lsfg_create(g_dev, g_pd, cache_path); + if (!g_lsfg) return -1; + } + g_kind = kind; + g_chain_ok = 0; + g_chain_failed = 0; + g_last_plan_ns = 0; + apply_config(); + return 0; +} + +void fge_stop(void) { + if (g_lsfg) { vkr_lsfg_destroy(g_lsfg); g_lsfg = NULL; } + if (g_dis) { vkr_dis_destroy(g_dis); g_dis = NULL; } + g_kind = -1; + g_chain_ok = 0; + g_chain_failed = 0; + g_source = VK_NULL_HANDLE; +} + +int fge_unavailable(void) { return (!g_lsfg && !g_dis) || g_chain_failed; } + +void fge_configure(uint32_t multiplier, float flow_scale, float refresh_hz, int flow_min_side, + int target_fps) { + g_multiplier = multiplier ? multiplier : 2u; + g_flow_scale = flow_scale > 0.0f ? flow_scale : 0.7f; + g_refresh_hz = refresh_hz; + if (flow_min_side > 0) g_dis_min_side = (uint32_t)flow_min_side; + g_target_fps = target_fps > 0 ? (uint32_t)target_fps : 0u; + apply_config(); +} + +int fge_prepare(uint32_t w, uint32_t h, VkFormat fmt) { + if (!w || !h) return 0; + + if (g_lsfg) { + if (g_chain_ok && !vkr_lsfg_needs_rebuild(g_lsfg, w, h, fmt)) return 1; + vkr_lsfg_forget_targets(g_lsfg); + vkr_lsfg_set_guest_extent(g_lsfg, w, h); + g_chain_ok = vkr_lsfg_prepare(g_lsfg, w, h, fmt) ? 1 : 0; + } else if (g_dis) { + const VkrDisContentRect content = {0, 0, w, h}; + if (g_chain_ok && !vkr_dis_needs_rebuild(g_dis, w, h, fmt, content)) return 1; + vkr_dis_forget_targets(g_dis); + g_chain_ok = vkr_dis_prepare(g_dis, w, h, fmt, content) ? 1 : 0; + } else { + return 0; + } + + g_chain_failed = !g_chain_ok; + return g_chain_ok; +} + +uint32_t fge_plan(uint32_t capacity, uint64_t source_frames, float presented_rate) { + (void)presented_rate; + if (!g_chain_ok) return 0; + if (g_lsfg) { + vkr_lsfg_set_refresh_rate(g_lsfg, g_refresh_hz); + return vkr_lsfg_plan(g_lsfg, capacity, source_frames); + } + if (g_dis) return vkr_dis_plan(g_dis, capacity, source_frames); + return 0; +} + +void fge_process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h, uint32_t gens) { + if (!g_chain_ok) return; + g_source = source; + if (g_lsfg) { + vkr_lsfg_process(g_lsfg, cmd, source, w, h, gens); + /* The pacer wants the source cadence; the compositor's own frame clock is the measure. */ + uint64_t now = now_ns(); + if (g_last_plan_ns) vkr_lsfg_note_frame(g_lsfg, now - g_last_plan_ns, gens); + g_last_plan_ns = now; + } else if (g_dis) { + vkr_dis_process(g_dis, cmd, source, w, h, gens); + } +} + +void fge_generate(VkCommandBuffer cmd, uint32_t g, uint32_t count, VkImage img, VkImageView view, + uint32_t w, uint32_t h) { + (void)count; + if (!g_chain_ok) return; + /* The ring slot is stable for the life of the chain, so it doubles as the descriptor key. */ + if (g_lsfg) { + vkr_lsfg_generate_into(g_lsfg, cmd, g, g, img, view, w, h); + } else if (g_dis) { + vkr_dis_generate_into(g_dis, cmd, g, g, img, view, w, h, g_source); + } +} + +void fge_forget_targets(void) { + if (g_lsfg) vkr_lsfg_forget_targets(g_lsfg); + if (g_dis) vkr_dis_forget_targets(g_dis); +} + +void fge_telemetry(float *accepted, float *source_rate, float *thermal) { + *accepted = 0.0f; + *source_rate = 0.0f; + *thermal = -1.0f; +} + +const char *fge_engine_name(int kind) { + return kind == VKP_FG_ENGINE_DIS ? "DIS Native" : "LSFG Native"; +} + +const char *fge_build_info(void) { return "lsfg-native/dis-native"; } diff --git a/app/src/main/cpp/waylandcomp/src/framegen_engine.h b/app/src/main/cpp/waylandcomp/src/framegen_engine.h new file mode 100644 index 000000000..0fc00d424 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/framegen_engine.h @@ -0,0 +1,69 @@ +#ifndef FRAMEGEN_ENGINE_H +#define FRAMEGEN_ENGINE_H +/* + * C face of the frame-generation engines for framegen_bridge.c. + * + * framegen_engine.c drives vkr_lsfg and vkr_dis - the same engines the X11 renderer runs - + * on the compositor's own Turnip device. A build without the winlator tree compiles + * framegen_engine_stub.c instead, which reports "engines not built in" and generates nothing. + * + * Every function runs on the compositor thread. + */ +#include <stdint.h> +#ifndef VK_USE_PLATFORM_ANDROID_KHR +#define VK_USE_PLATFORM_ANDROID_KHR +#endif +#include <vulkan/vulkan.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Probe the physical device and build the VkDeviceCreateInfo pNext chain the LSFG chain needs + * (vulkanMemoryModel + storage-image features; nothing for DIS). Returns the chain, or NULL + * when the device fails a gate. `ring_fmt` is the format the engines generate into; it must be + * storage-capable for either engine. */ +const void *fge_probe(PFN_vkGetInstanceProcAddr gipa, VkInstance inst, VkPhysicalDevice pd, + VkFormat ring_fmt); +/* The device exists; resolve device-level entry points. features_enabled = fge_probe's chain + * was accepted by vkCreateDevice. */ +void fge_device_ready(PFN_vkGetInstanceProcAddr gipa, VkInstance inst, VkPhysicalDevice pd, + VkDevice dev, VkQueue queue, uint32_t qfam, int features_enabled); +/* 1 when engine `kind` can run on this device (its capability gates pass). */ +int fge_caps_ok(int kind); +/* Why the LSFG gates fail ("supported" when they pass); static string. */ +const char *fge_caps_reason(void); +/* 1 when the engines can generate in `fmt` on this device (storage + linear sampling), beyond the + * ring format probed at fge_probe - the FP16 scene of HDR frames (vk_present.c). */ +int fge_format_ok(VkFormat fmt); + +/* Create engine `kind` (LSFG needs the cache path; DIS ignores it). 0 = ready, -1 = failed. + * Only one engine holds GPU resources at a time; a different kind replaces it. */ +int fge_start(int kind, const char *cache_path); +void fge_stop(void); +/* The engine came up but later found it cannot generate (chain build failed). */ +int fge_unavailable(void); +void fge_configure(uint32_t multiplier, float flow_scale, float refresh_hz, int flow_min_side, + int target_fps); /* flow_min_side is DIS-only; target_fps paces both */ +/* Build/rebuild the chain for this extent; the guest extent for the flow pyramid is the same + * (the Wayland scene is the game's own size in fullscreen). 1 = chain valid. */ +int fge_prepare(uint32_t w, uint32_t h, VkFormat fmt); +/* How many frames to generate for this source frame (0 until the history is valid). */ +uint32_t fge_plan(uint32_t capacity, uint64_t source_frames, float presented_rate); +/* Take the scene image (GENERAL) as frame N and run the shared part of the chain. */ +void fge_process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h, uint32_t gens); +/* Synthesise generated frame g of `count` into a storage image (GENERAL). */ +void fge_generate(VkCommandBuffer cmd, uint32_t g, uint32_t count, VkImage img, VkImageView view, + uint32_t w, uint32_t h); +/* The generation targets were recreated: drop cached descriptors. */ +void fge_forget_targets(void); +/* Engine-side telemetry: accepted generations, source rate (0 = engine has none), thermal. */ +void fge_telemetry(float *accepted, float *source_rate, float *thermal); +const char *fge_engine_name(int kind); +/* "lsfg-native"/"dis-native" build provenance for the start log line. */ +const char *fge_build_info(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/src/main/cpp/waylandcomp/src/framegen_engine_stub.c b/app/src/main/cpp/waylandcomp/src/framegen_engine_stub.c new file mode 100644 index 000000000..8a0074e8a --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/framegen_engine_stub.c @@ -0,0 +1,40 @@ +/* Stand-in for framegen_engine.cpp when the compositor is built on its own (standalone/, no + * winlator tree, no dxbc). Every engine is reported unavailable; nothing is generated. */ +#include "framegen_engine.h" + +const void *fge_probe(PFN_vkGetInstanceProcAddr gipa, VkInstance inst, VkPhysicalDevice pd, + VkFormat ring_fmt) { + (void)gipa; (void)inst; (void)pd; (void)ring_fmt; + return NULL; +} +void fge_device_ready(PFN_vkGetInstanceProcAddr gipa, VkInstance inst, VkPhysicalDevice pd, + VkDevice dev, VkQueue queue, uint32_t qfam, int features_enabled) { + (void)gipa; (void)inst; (void)pd; (void)dev; (void)queue; (void)qfam; (void)features_enabled; +} +int fge_caps_ok(int kind) { (void)kind; return 0; } +const char *fge_caps_reason(void) { return "frame-generation engines not built into this compositor"; } +int fge_format_ok(VkFormat fmt) { (void)fmt; return 0; } +int fge_start(int kind, const char *cache_path) { (void)kind; (void)cache_path; return -1; } +void fge_stop(void) {} +int fge_unavailable(void) { return 1; } +void fge_configure(uint32_t multiplier, float flow_scale, float refresh_hz, int flow_min_side, + int target_fps) { + (void)multiplier; (void)flow_scale; (void)refresh_hz; (void)flow_min_side; (void)target_fps; +} +int fge_prepare(uint32_t w, uint32_t h, VkFormat fmt) { (void)w; (void)h; (void)fmt; return 0; } +uint32_t fge_plan(uint32_t capacity, uint64_t source_frames, float presented_rate) { + (void)capacity; (void)source_frames; (void)presented_rate; return 0; +} +void fge_process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h, uint32_t gens) { + (void)cmd; (void)source; (void)w; (void)h; (void)gens; +} +void fge_generate(VkCommandBuffer cmd, uint32_t g, uint32_t count, VkImage img, VkImageView view, + uint32_t w, uint32_t h) { + (void)cmd; (void)g; (void)count; (void)img; (void)view; (void)w; (void)h; +} +void fge_forget_targets(void) {} +void fge_telemetry(float *accepted, float *source_rate, float *thermal) { + *accepted = 0.0f; *source_rate = 0.0f; *thermal = -1.0f; +} +const char *fge_engine_name(int kind) { return kind == 1 ? "DIS Native" : "LSFG Native"; } +const char *fge_build_info(void) { return "stub"; } diff --git a/app/src/main/cpp/waylandcomp/src/framegen_jni.c b/app/src/main/cpp/waylandcomp/src/framegen_jni.c new file mode 100644 index 000000000..7334ccb35 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/framegen_jni.c @@ -0,0 +1,78 @@ +/* JNI for frame generation on the Wayland backend (WaylandCompositor.nativeFrameGen* / + * nativeSet*FrameGen* / nativeSetLsfgCachePath / nativeSetEngineTuning). Every call only stores + * a value in framegen_bridge.c; the compositor thread picks it up on its next frame, so these + * are safe from any thread and before the compositor is up (the app arms the engine from its + * launch code while the SurfaceView is still coming). */ +#include <jni.h> +#include "framegen_bridge.h" + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetFrameGenEngine(JNIEnv *env, jclass clazz, jint kind) { + (void)env; (void)clazz; + vkp_framegen_set_engine((int)kind); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetFrameGenArmed(JNIEnv *env, jclass clazz, + jboolean armed, jint multiplier) { + (void)env; (void)clazz; + vkp_framegen_set_armed(armed ? 1 : 0, (int)multiplier); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetLsfgCachePath(JNIEnv *env, jclass clazz, jstring path) { + (void)clazz; + if (!path) { vkp_framegen_set_lsfg_cache_path(NULL); return; } + const char *c = (*env)->GetStringUTFChars(env, path, NULL); + vkp_framegen_set_lsfg_cache_path(c); + if (c) (*env)->ReleaseStringUTFChars(env, path, c); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetFrameGenTuning(JNIEnv *env, jclass clazz, + jfloat flowScale, jfloat refreshHz) { + (void)env; (void)clazz; + vkp_framegen_set_tuning((float)flowScale, (float)refreshHz); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetEngineTuning(JNIEnv *env, jclass clazz, + jint flowMinSide, jint targetFps) { + (void)env; (void)clazz; + vkp_framegen_set_engine_tuning((int)flowMinSide, (int)targetFps); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeFrameGenProblem(JNIEnv *env, jclass clazz) { + (void)env; (void)clazz; + return (jint)vkp_framegen_problem(); +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeFrameGenCapsReason(JNIEnv *env, jclass clazz) { + (void)clazz; + const char *r = vkp_framegen_caps_reason(); + return (*env)->NewStringUTF(env, r ? r : ""); +} + +JNIEXPORT jfloatArray JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeFrameGenStats(JNIEnv *env, jclass clazz) { + (void)clazz; + float st[6] = {0.f, 0.f, 0.f, 0.f, -1.f, -1.f}; + vkp_framegen_stats(st); + jfloatArray arr = (*env)->NewFloatArray(env, 6); + if (arr) (*env)->SetFloatArrayRegion(env, arr, 0, 6, st); + return arr; +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeFrameGenPresentedFrames(JNIEnv *env, jclass clazz) { + (void)env; (void)clazz; + return (jlong)vkp_framegen_total_presented(); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeFrameGenGeneratedFrames(JNIEnv *env, jclass clazz) { + (void)env; (void)clazz; + return (jlong)vkp_framegen_total_generated(); +} diff --git a/app/src/main/cpp/waylandcomp/src/hdr_compose.c b/app/src/main/cpp/waylandcomp/src/hdr_compose.c new file mode 100644 index 000000000..b99ebb6b3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/hdr_compose.c @@ -0,0 +1,298 @@ +/* HDR composition: the encode pass over the mixed scene - see hdr_compose.h. Compositor thread. */ +#define _POSIX_C_SOURCE 200809L +#include "hdr_compose.h" +#include "vk_loader.h" +#include "vk_present.h" /* wnc_log, vkp_gpu_name */ +#include <stdio.h> +#include <string.h> + +/* SPIR-V, pre-compiled (glslangValidator -V hdr_encode.frag --vn hdr_encode_code) and committed next + * to the GLSL, like every other shader of the compositor: the NDK build compiles no shaders. The quad + * is the effect chain's upscale.vert (same bytes, from app/src/main/cpp/winlator). */ +/* The headers define plain (external) arrays and effects_chain.c includes upscale_vert.h too: this + * translation unit gets its own copy under another name, so the link sees no duplicate symbol. */ +#define upscale_vert_code hdrc_upscale_vert_code +#include "upscale_vert.h" +#undef upscale_vert_code +#include "hdr_encode_frag.h" + +#define MIXED_FORMAT VK_FORMAT_A2B10G10R10_UNORM_PACK32 + +struct pc_encode { float ndc[4]; float params[4]; float rects[HDRC_MAX_RECTS][4]; }; /* 128 bytes */ + +static VkDevice g_dev; +static int g_ready; /* 0 = not built, 1 = ok, -1 = unavailable (said once) */ +static VkSampler g_sampler; +static VkDescriptorSetLayout g_dsl; +static VkPipelineLayout g_pl; +static VkDescriptorPool g_dpool; +static VkShaderModule g_vert, g_frag; + +/* One render pass + pipeline per output format: the HDR picture is 10-bit, frame generation's scene + * 8-bit - or FP16 where the engine takes it (vk_present.c FG_HDR_FMT). */ +#define NFMT 3 +static struct { VkFormat fmt; VkRenderPass rp; VkPipeline pipe; } g_out[NFMT] = { + {VK_FORMAT_A2B10G10R10_UNORM_PACK32}, {VK_FORMAT_R8G8B8A8_UNORM}, {VK_FORMAT_R16G16B16A16_SFLOAT}}; + +/* The mixed image's view + descriptor, and each output image's view + framebuffer: kept while the + * image handle (and size) stays the same, rebuilt when vk_present re-creates the image. */ +static struct { VkImage img; int w, h; VkImageView view; VkDescriptorSet ds; } g_src; +#define NOUT 3 +static struct { VkImage img; VkFormat fmt; int w, h; VkImageView view; VkFramebuffer fb; } g_dst[NOUT]; +static int g_dst_next; + +void hdrc_bind_device(VkDevice dev, const VkPhysicalDeviceMemoryProperties *memprops) { + (void)memprops; + g_dev = dev; +} + +static void src_release(void) { + if (g_src.ds) g_vk.FreeDescriptorSets(g_dev, g_dpool, 1, &g_src.ds); + if (g_src.view) g_vk.DestroyImageView(g_dev, g_src.view, NULL); + memset(&g_src, 0, sizeof(g_src)); +} + +static void dst_release(int i) { + if (g_dst[i].fb) g_vk.DestroyFramebuffer(g_dev, g_dst[i].fb, NULL); + if (g_dst[i].view) g_vk.DestroyImageView(g_dev, g_dst[i].view, NULL); + memset(&g_dst[i], 0, sizeof(g_dst[i])); +} + +void hdrc_forget_image(VkImage img) { + if (!g_dev || !img) return; + if (g_src.img == img) src_release(); + for (int i = 0; i < NOUT; i++) if (g_dst[i].img == img) dst_release(i); +} + +void hdrc_destroy(void) { + if (!g_dev) return; + src_release(); + for (int i = 0; i < NOUT; i++) dst_release(i); + for (int i = 0; i < NFMT; i++) { + if (g_out[i].pipe) g_vk.DestroyPipeline(g_dev, g_out[i].pipe, NULL); + if (g_out[i].rp) g_vk.DestroyRenderPass(g_dev, g_out[i].rp, NULL); + g_out[i].pipe = VK_NULL_HANDLE; g_out[i].rp = VK_NULL_HANDLE; + } + if (g_vert) g_vk.DestroyShaderModule(g_dev, g_vert, NULL); + if (g_frag) g_vk.DestroyShaderModule(g_dev, g_frag, NULL); + if (g_dpool) g_vk.DestroyDescriptorPool(g_dev, g_dpool, NULL); + if (g_pl) g_vk.DestroyPipelineLayout(g_dev, g_pl, NULL); + if (g_dsl) g_vk.DestroyDescriptorSetLayout(g_dev, g_dsl, NULL); + if (g_sampler) g_vk.DestroySampler(g_dev, g_sampler, NULL); + g_vert = g_frag = VK_NULL_HANDLE; g_dpool = VK_NULL_HANDLE; g_pl = VK_NULL_HANDLE; + g_dsl = VK_NULL_HANDLE; g_sampler = VK_NULL_HANDLE; + g_ready = 0; + g_dev = VK_NULL_HANDLE; +} + +static VkShaderModule shader(const uint32_t *code, size_t size) { + VkShaderModule m = VK_NULL_HANDLE; + VkShaderModuleCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = size, .pCode = code}; + if (g_vk.CreateShaderModule(g_dev, &ci, NULL, &m) != VK_SUCCESS) return VK_NULL_HANDLE; + return m; +} + +/* The render pass: one colour target written whole (DONT_CARE), left in TRANSFER_DST_OPTIMAL for the + * effects chain / the copies that follow. The dependencies order it after whoever read or wrote the + * target last (last frame's blit or chain) and before whoever samples, blits or copies it next. */ +static VkRenderPass make_rp(VkFormat fmt) { + VkAttachmentDescription att = {.format = fmt, .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL}; + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sub = {.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, + .pColorAttachments = &ref}; + VkSubpassDependency deps[2] = { + {.srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}, + {.srcSubpass = 0, .dstSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT}}; + VkRenderPassCreateInfo rci = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, + .pAttachments = &att, .subpassCount = 1, .pSubpasses = &sub, + .dependencyCount = 2, .pDependencies = deps}; + VkRenderPass rp = VK_NULL_HANDLE; + if (g_vk.CreateRenderPass(g_dev, &rci, NULL, &rp) != VK_SUCCESS) return VK_NULL_HANDLE; + return rp; +} + +static VkPipeline make_pipe(VkRenderPass rp) { + VkPipelineShaderStageCreateInfo st[2] = { + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = g_vert, .pName = "main"}, + {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = g_frag, .pName = "main"}}; + VkPipelineVertexInputStateCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo ia = {.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}; + VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo ds = {.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = 2, .pDynamicStates = dyn}; + VkPipelineViewportStateCreateInfo vp = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, .scissorCount = 1}; + VkPipelineRasterizationStateCreateInfo rs = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .lineWidth = 1.0f}; + VkPipelineMultisampleStateCreateInfo ms = {.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT}; + VkPipelineColorBlendAttachmentState ba = {.blendEnable = VK_FALSE, .colorWriteMask = 0xF}; + VkPipelineColorBlendStateCreateInfo cb = {.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, .pAttachments = &ba}; + VkGraphicsPipelineCreateInfo pi = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, + .pStages = st, .pVertexInputState = &vi, .pInputAssemblyState = &ia, + .pViewportState = &vp, .pRasterizationState = &rs, .pMultisampleState = &ms, + .pColorBlendState = &cb, .pDynamicState = &ds, .layout = g_pl, + .renderPass = rp, .subpass = 0}; + VkPipeline p = VK_NULL_HANDLE; + if (g_vk.CreateGraphicsPipelines(g_dev, VK_NULL_HANDLE, 1, &pi, NULL, &p) != VK_SUCCESS) return VK_NULL_HANDLE; + return p; +} + +static int objects_ensure(void) { + if (g_ready) return g_ready == 1 ? 0 : -1; + g_ready = -1; + if (!g_dev || !g_vk.CreateGraphicsPipelines) goto fail; + VkSamplerCreateInfo sci = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE}; + if (g_vk.CreateSampler(g_dev, &sci, NULL, &g_sampler) != VK_SUCCESS) goto fail; + VkDescriptorSetLayoutBinding b = {.binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT}; + VkDescriptorSetLayoutCreateInfo dli = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, .pBindings = &b}; + if (g_vk.CreateDescriptorSetLayout(g_dev, &dli, NULL, &g_dsl) != VK_SUCCESS) goto fail; + VkPushConstantRange pcr = {.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, .size = sizeof(struct pc_encode)}; + VkPipelineLayoutCreateInfo pli = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, + .pSetLayouts = &g_dsl, .pushConstantRangeCount = 1, .pPushConstantRanges = &pcr}; + if (g_vk.CreatePipelineLayout(g_dev, &pli, NULL, &g_pl) != VK_SUCCESS) goto fail; + VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2}; + VkDescriptorPoolCreateInfo dpi = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, .maxSets = 2, + .poolSizeCount = 1, .pPoolSizes = &ps}; + if (g_vk.CreateDescriptorPool(g_dev, &dpi, NULL, &g_dpool) != VK_SUCCESS) goto fail; + if (!(g_vert = shader(hdrc_upscale_vert_code, sizeof(hdrc_upscale_vert_code)))) goto fail; + if (!(g_frag = shader(hdr_encode_code, sizeof(hdr_encode_code)))) goto fail; + for (int i = 0; i < NFMT; i++) { + if (!(g_out[i].rp = make_rp(g_out[i].fmt))) goto fail; + if (!(g_out[i].pipe = make_pipe(g_out[i].rp))) goto fail; + } + g_ready = 1; + wnc_log("color", "HDR composition ready on %s: scenes that mix HDR and SDR content are put into one " + "encoding (PQ BT.2020, or tone-mapped sRGB) instead of being shown washed out", vkp_gpu_name()); + return 0; +fail: + { + VkDevice keep = g_dev; + hdrc_destroy(); + g_dev = keep; + } + g_ready = -1; + wnc_log("error", "color: the HDR composition pass could not be built on this driver; HDR scenes that cannot " + "stay on the game's own layer are shown the old way (washed out) this session"); + return -1; +} + +static VkImageView make_view(VkImage img, VkFormat fmt) { + VkImageView v = VK_NULL_HANDLE; + VkImageViewCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = img, + .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = fmt, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + if (g_vk.CreateImageView(g_dev, &ci, NULL, &v) != VK_SUCCESS) return VK_NULL_HANDLE; + return v; +} + +static int src_ensure(VkImage img, int w, int h) { + if (g_src.img == img && g_src.w == w && g_src.h == h && g_src.ds) return 0; + src_release(); + if (!(g_src.view = make_view(img, MIXED_FORMAT))) return -1; + VkDescriptorSetAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = g_dpool, .descriptorSetCount = 1, .pSetLayouts = &g_dsl}; + if (g_vk.AllocateDescriptorSets(g_dev, &ai, &g_src.ds) != VK_SUCCESS) { g_src.ds = VK_NULL_HANDLE; src_release(); return -1; } + VkDescriptorImageInfo ii = {.sampler = g_sampler, .imageView = g_src.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + VkWriteDescriptorSet wr = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = g_src.ds, .dstBinding = 0, + .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &ii}; + g_vk.UpdateDescriptorSets(g_dev, 1, &wr, 0, NULL); + g_src.img = img; g_src.w = w; g_src.h = h; + return 0; +} + +static int fmt_slot(VkFormat fmt) { + for (int i = 0; i < NFMT; i++) if (g_out[i].fmt == fmt) return i; + return -1; +} + +static VkFramebuffer dst_ensure(VkImage img, VkFormat fmt, int w, int h, VkRenderPass rp) { + for (int i = 0; i < NOUT; i++) + if (g_dst[i].img == img && g_dst[i].fmt == fmt && g_dst[i].w == w && g_dst[i].h == h && g_dst[i].fb) + return g_dst[i].fb; + int i = g_dst_next; + g_dst_next = (g_dst_next + 1) % NOUT; + dst_release(i); + if (!(g_dst[i].view = make_view(img, fmt))) return VK_NULL_HANDLE; + VkFramebufferCreateInfo fi = {.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = rp, + .attachmentCount = 1, .pAttachments = &g_dst[i].view, .width = (uint32_t)w, + .height = (uint32_t)h, .layers = 1}; + if (g_vk.CreateFramebuffer(g_dev, &fi, NULL, &g_dst[i].fb) != VK_SUCCESS) { g_dst[i].fb = VK_NULL_HANDLE; dst_release(i); return VK_NULL_HANDLE; } + g_dst[i].img = img; g_dst[i].fmt = fmt; g_dst[i].w = w; g_dst[i].h = h; + return g_dst[i].fb; +} + +int hdrc_encode(VkCommandBuffer cmd, VkImage mixed, VkImage out, VkFormat out_fmt, int w, int h, + const struct hdrc_params *p) { + if (!mixed || !out || w <= 0 || h <= 0 || !p || objects_ensure() != 0) return -1; + int slot = fmt_slot(out_fmt); + if (slot < 0) return -1; + if (src_ensure(mixed, w, h) != 0) return -1; + VkFramebuffer fb = dst_ensure(out, out_fmt, w, h, g_out[slot].rp); + if (!fb) return -1; + + /* The composite blits wrote the mixed image; the pass samples it. */ + VkImageMemoryBarrier b = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = mixed, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, NULL, 0, NULL, 1, &b); + + struct pc_encode pc; + memset(&pc, 0, sizeof(pc)); + pc.ndc[0] = -1.0f; pc.ndc[1] = -1.0f; pc.ndc[2] = 1.0f; pc.ndc[3] = 1.0f; + int count = p->count < 0 ? 0 : p->count > HDRC_MAX_RECTS ? HDRC_MAX_RECTS : p->count; + unsigned mask = p->hdr_mask & ((1u << HDRC_MAX_RECTS) - 1u); + pc.params[0] = p->sdr_white_nits > 0.0f ? p->sdr_white_nits : 203.0f; + pc.params[1] = p->peak_nits > 0.0f ? p->peak_nits : 1000.0f; + pc.params[2] = p->mode == HDRC_OUT_SDR ? 1.0f : 0.0f; + pc.params[3] = (float)count + 16.0f * (float)mask; + for (int i = 0; i < count; i++) memcpy(pc.rects[i], p->rects[i], sizeof(pc.rects[i])); + + VkRenderPassBeginInfo rpi = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = g_out[slot].rp, + .framebuffer = fb, .renderArea = {{0, 0}, {(uint32_t)w, (uint32_t)h}}}; + VkViewport vp = {0, 0, (float)w, (float)h, 0, 1}; + VkRect2D sc = {{0, 0}, {(uint32_t)w, (uint32_t)h}}; + g_vk.CmdBeginRenderPass(cmd, &rpi, VK_SUBPASS_CONTENTS_INLINE); + g_vk.CmdSetViewport(cmd, 0, 1, &vp); + g_vk.CmdSetScissor(cmd, 0, 1, &sc); + g_vk.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_out[slot].pipe); + g_vk.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pl, 0, 1, &g_src.ds, 0, NULL); + g_vk.CmdPushConstants(cmd, g_pl, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc); + g_vk.CmdDraw(cmd, 4, 1, 0, 0); + g_vk.CmdEndRenderPass(cmd); + return 0; +} diff --git a/app/src/main/cpp/waylandcomp/src/hdr_compose.h b/app/src/main/cpp/waylandcomp/src/hdr_compose.h new file mode 100644 index 000000000..cfeef7497 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/hdr_compose.h @@ -0,0 +1,52 @@ +#ifndef HDR_COMPOSE_H +#define HDR_COMPOSE_H +/* + * HDR composition (round 2 of HDR_RECON.md's Phase B): when an HDR game cannot be alone on its own + * display layer - screen effects on, frame generation on, a window above it on a display that cannot + * compose a second layer, a windowed game, a frame without a gralloc buffer - the whole scene is put + * into ONE encoding instead of being blitted into an 8-bit sRGB target as it stands (which shows the + * game's PQ frames washed out). + * + * vk_present.c blits every draw 1:1 into a 10-bit "mixed" image (each pixel still in its own + * encoding) and hdrc_encode() runs hdr_encode.frag over it into the output image: + * HDRC_OUT_PQ - PQ BT.2020 (SDR pixels placed at the SDR reference white): goes onto the game's + * display layer tagged BT2020_PQ (sc_layer.c), or into an HDR10 swapchain; + * HDRC_OUT_SDR - sRGB BT.709 with the HDR pixels tone-mapped: for a present that cannot carry HDR. + * Which encoding a pixel is in comes from the draws' rectangles (the topmost draw decides). + * + * Compositor thread. Objects are built lazily on the first use; if the driver refuses them, the + * encode is unavailable for the session (logged once) and the caller falls back. + */ +#define VK_USE_PLATFORM_ANDROID_KHR +#include <vulkan/vulkan.h> + +#define HDRC_MAX_RECTS 6 + +enum hdrc_mode { HDRC_OUT_PQ = 0, HDRC_OUT_SDR = 1 }; + +struct hdrc_params { + enum hdrc_mode mode; + float sdr_white_nits; /* SDR reference white inside the HDR picture (203) */ + float peak_nits; /* HDR content peak for the tone-map (max CLL / mastering max, else 1000) */ + int count; /* rects, top draw first */ + unsigned hdr_mask; /* bit i: rects[i] is an HDR draw */ + float rects[HDRC_MAX_RECTS][4]; /* scene pixels x0, y0, x1, y1 */ +}; + +/* Called from dev_init (device up). */ +void hdrc_bind_device(VkDevice dev, const VkPhysicalDeviceMemoryProperties *memprops); +/* Device teardown. */ +void hdrc_destroy(void); +/* vk_present is about to destroy `img` (a resize of the mixed / scene / picture image): drop the view, + * descriptor and framebuffer cached for it, so a later image that happens to reuse the handle value is + * never drawn through a view of the destroyed one. */ +void hdrc_forget_image(VkImage img); + +/* Record the encode: `mixed` (w x h, VK_FORMAT_A2B10G10R10_UNORM_PACK32, in TRANSFER_DST_OPTIMAL after + * the composite blits) -> `out` (w x h, out_fmt, created with COLOR_ATTACHMENT + SAMPLED usage), which + * is left in TRANSFER_DST_OPTIMAL - the layout vkp_effects_run() takes its scene in. 0 = recorded, + * -1 = unavailable (nothing recorded; `mixed` untouched). */ +int hdrc_encode(VkCommandBuffer cmd, VkImage mixed, VkImage out, VkFormat out_fmt, int w, int h, + const struct hdrc_params *p); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/hdr_encode.frag b/app/src/main/cpp/waylandcomp/src/hdr_encode.frag new file mode 100644 index 000000000..4b75ed586 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/hdr_encode.frag @@ -0,0 +1,129 @@ +#version 450 + +// ============================================================================= +// HDR composition: one encoding for the whole scene (waylandcomp/src/hdr_compose.c). +// +// The compositor composes with blits, which copy values and know nothing about what they mean. +// When an HDR game (PQ-encoded BT.2020 frames) shares the scene with SDR content (sRGB windows, the +// desktop, a window above the game), every draw is first blitted 1:1 into a 10-bit "mixed" image - +// each pixel still in its own encoding - and this pass turns the mixed image into ONE encoding: +// +// mode 0 (HDR out, PQ BT.2020): HDR pixels pass through; SDR pixels are decoded (sRGB), +// converted BT.709 -> BT.2020 and placed at the SDR reference white (params.x nits, +// 203 = Report ITU-R BT.2408), then PQ-encoded. +// mode 1 (SDR out, sRGB): SDR pixels pass through; HDR pixels are PQ-decoded, converted +// BT.2020 -> BT.709, tone-mapped relative to the SDR reference white (linear below a +// knee, a smooth roll-off above it that puts the content's peak, params.y nits, at 98 %) +// and sRGB-encoded - what a display that cannot take HDR gets instead of washed-out PQ. +// +// Which encoding a pixel is in: the topmost draw covering it decides. params.w packs the number of +// rects (count, 0..6) and a bitmask of which of them are HDR (count + 16 * mask); rects are scene +// pixels (x0, y0, x1, y1), TOP draw first, listed down to the lowest HDR draw (everything below +// that is SDR, and so is any pixel no listed rect covers). +// +// Conventions match upscale.vert / the effect passes: combined-image-sampler at binding 0, the +// push-constant block leads with vec4 ndc (offset 0). The target is exactly scene-sized, so +// gl_FragCoord is the scene pixel and texelFetch reads the mixed image 1:1 (no filtering). +// Float-only mask arithmetic (no bitwise ops). +// ============================================================================= + +layout(binding = 0) uniform sampler2D Mixed; + +layout(push_constant) uniform PC { + vec4 ndc; // upscale.vert's quad + vec4 params; // x = SDR white nits, y = HDR content peak nits (mode 1), z = mode, w = count + 16 * mask + vec4 rects[6]; // top-first scene rects +} pc; + +layout(location = 0) in vec2 fragTexCoord; +layout(location = 0) out vec4 outColor; + +const float PQ_M1 = 0.1593017578125; +const float PQ_M2 = 78.84375; +const float PQ_C1 = 0.8359375; +const float PQ_C2 = 18.8515625; +const float PQ_C3 = 18.6875; + +// Column-major: each group of three is one COLUMN (the coefficients of R, then of G, then of B). +const mat3 BT709_TO_BT2020 = mat3(0.6274, 0.0691, 0.0164, + 0.3293, 0.9195, 0.0880, + 0.0433, 0.0114, 0.8956); +const mat3 BT2020_TO_BT709 = mat3( 1.6605, -0.1246, -0.0182, + -0.5876, 1.1329, -0.1006, + -0.0728, -0.0083, 1.1187); + +vec3 srgbToLinear(vec3 c) { + vec3 lo = c / 12.92; + vec3 hi = pow((c + 0.055) / 1.055, vec3(2.4)); + return mix(lo, hi, step(vec3(0.04045), c)); +} + +vec3 linearToSrgb(vec3 c) { + vec3 lo = c * 12.92; + vec3 hi = 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055; + return mix(lo, hi, step(vec3(0.0031308), c)); +} + +// PQ code (0..1) -> luminance / 10000 cd/m2 (SMPTE ST 2084 EOTF). +vec3 pqToLinear(vec3 e) { + vec3 p = pow(clamp(e, 0.0, 1.0), vec3(1.0 / PQ_M2)); + return pow(max(p - PQ_C1, 0.0) / max(PQ_C2 - PQ_C3 * p, 1e-6), vec3(1.0 / PQ_M1)); +} + +// luminance / 10000 cd/m2 -> PQ code (inverse EOTF). +vec3 linearToPq(vec3 y) { + vec3 p = pow(clamp(y, 0.0, 1.0), vec3(PQ_M1)); + return pow((PQ_C1 + PQ_C2 * p) / (1.0 + PQ_C3 * p), vec3(PQ_M2)); +} + +// Tone curve in units of SDR white: identity up to the knee, then an exponential roll-off towards 1 +// whose rate puts `peak` (also in SDR-white units) at 98 % of the range. +float toneCurve(float x, float peak) { + const float knee = 0.8; + if (x <= knee) return x; + float span = max(peak - knee, 0.05); + float rate = max(span / 3.912, 1.0 - knee); // ln(50) = 3.912; never steeper than the identity + return knee + (1.0 - knee) * (1.0 - exp(-(x - knee) / rate)); +} + +bool isHdrPixel(vec2 p) { + float packed = pc.params.w; + float count = mod(packed, 16.0); + float mask = floor(packed / 16.0); + for (int i = 0; i < 6; i++) { + if (float(i) >= count - 0.5) break; + vec4 r = pc.rects[i]; + if (p.x >= r.x && p.y >= r.y && p.x < r.z && p.y < r.w) { + float bit = mod(floor(mask / exp2(float(i))), 2.0); + return bit > 0.5; + } + } + return false; +} + +void main() { + vec3 c = texelFetch(Mixed, ivec2(gl_FragCoord.xy), 0).rgb; + bool hdr = isHdrPixel(gl_FragCoord.xy); + float sdrWhite = max(pc.params.x, 1.0); + vec3 outc; + if (pc.params.z < 0.5) { + // HDR out: PQ BT.2020. + if (hdr) { + outc = c; + } else { + vec3 nits = (BT709_TO_BT2020 * srgbToLinear(c)) * sdrWhite; + outc = linearToPq(nits / 10000.0); + } + } else { + // SDR out: sRGB BT.709, HDR tone-mapped. + if (!hdr) { + outc = c; + } else { + vec3 rel = max(BT2020_TO_BT709 * (pqToLinear(c) * 10000.0), 0.0) / sdrWhite; + float m = max(max(rel.r, rel.g), rel.b); + if (m > 1e-6) rel *= toneCurve(m, max(pc.params.y, sdrWhite) / sdrWhite) / m; + outc = linearToSrgb(clamp(rel, 0.0, 1.0)); + } + } + outColor = vec4(outc, 1.0); +} diff --git a/app/src/main/cpp/waylandcomp/src/hdr_encode_frag.h b/app/src/main/cpp/waylandcomp/src/hdr_encode_frag.h new file mode 100644 index 000000000..eb1536f1f --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/hdr_encode_frag.h @@ -0,0 +1,282 @@ + // 1116.5.0 + #pragma once +const uint32_t hdr_encode_code[] = { + 0x07230203,0x00010000,0x0008000b,0x0000018e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0008000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000010d,0x00000186,0x0000018d, + 0x00030010,0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004, + 0x6e69616d,0x00000000,0x00070005,0x0000000b,0x62677273,0x694c6f54,0x7261656e,0x33667628, + 0x0000003b,0x00030005,0x0000000a,0x00000063,0x00070005,0x0000000e,0x656e696c,0x6f547261, + 0x62677253,0x33667628,0x0000003b,0x00030005,0x0000000d,0x00000063,0x00060005,0x00000011, + 0x6f547170,0x656e694c,0x76287261,0x003b3366,0x00030005,0x00000010,0x00000065,0x00060005, + 0x00000014,0x656e696c,0x6f547261,0x76287150,0x003b3366,0x00030005,0x00000013,0x00000079, + 0x00070005,0x0000001a,0x656e6f74,0x76727543,0x31662865,0x3b31663b,0x00000000,0x00030005, + 0x00000018,0x00000078,0x00040005,0x00000019,0x6b616570,0x00000000,0x00060005,0x00000021, + 0x64487369,0x78695072,0x76286c65,0x003b3266,0x00030005,0x00000020,0x00000070,0x00030005, + 0x00000023,0x00006f6c,0x00030005,0x00000028,0x00006968,0x00030005,0x0000003c,0x00006f6c, + 0x00030005,0x0000003f,0x00006968,0x00030005,0x00000050,0x00000070,0x00030005,0x0000006f, + 0x00000070,0x00040005,0x0000008c,0x6e617073,0x00000000,0x00040005,0x00000091,0x65746172, + 0x00000000,0x00040005,0x000000a2,0x6b636170,0x00006465,0x00030005,0x000000a7,0x00004350, + 0x00040006,0x000000a7,0x00000000,0x0063646e,0x00050006,0x000000a7,0x00000001,0x61726170, + 0x0000736d,0x00050006,0x000000a7,0x00000002,0x74636572,0x00000073,0x00030005,0x000000a9, + 0x00006370,0x00040005,0x000000b0,0x6e756f63,0x00000074,0x00040005,0x000000b4,0x6b73616d, + 0x00000000,0x00030005,0x000000b9,0x00000069,0x00030005,0x000000cd,0x00000072,0x00030005, + 0x000000f5,0x00746962,0x00030005,0x00000106,0x00000063,0x00040005,0x0000010a,0x6578694d, + 0x00000064,0x00060005,0x0000010d,0x465f6c67,0x43676172,0x64726f6f,0x00000000,0x00030005, + 0x00000116,0x00726468,0x00040005,0x00000117,0x61726170,0x0000006d,0x00050005,0x0000011b, + 0x57726473,0x65746968,0x00000000,0x00040005,0x00000127,0x6374756f,0x00000000,0x00040005, + 0x0000012a,0x7374696e,0x00000000,0x00040005,0x00000139,0x61726170,0x0000006d,0x00040005, + 0x00000143,0x61726170,0x0000006d,0x00030005,0x0000014c,0x006c6572,0x00040005,0x0000015a, + 0x61726170,0x0000006d,0x00030005,0x00000164,0x0000006d,0x00040005,0x00000177,0x61726170, + 0x0000006d,0x00040005,0x00000179,0x61726170,0x0000006d,0x00040005,0x00000183,0x61726170, + 0x0000006d,0x00050005,0x00000186,0x4374756f,0x726f6c6f,0x00000000,0x00060005,0x0000018d, + 0x67617266,0x43786554,0x64726f6f,0x00000000,0x00040047,0x000000a6,0x00000006,0x00000010, + 0x00030047,0x000000a7,0x00000002,0x00050048,0x000000a7,0x00000000,0x00000023,0x00000000, + 0x00050048,0x000000a7,0x00000001,0x00000023,0x00000010,0x00050048,0x000000a7,0x00000002, + 0x00000023,0x00000020,0x00040047,0x0000010a,0x00000021,0x00000000,0x00040047,0x0000010a, + 0x00000022,0x00000000,0x00040047,0x0000010d,0x0000000b,0x0000000f,0x00040047,0x00000186, + 0x0000001e,0x00000000,0x00040047,0x0000018d,0x0000001e,0x00000000,0x00020013,0x00000002, + 0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007, + 0x00000006,0x00000003,0x00040020,0x00000008,0x00000007,0x00000007,0x00040021,0x00000009, + 0x00000007,0x00000008,0x00040020,0x00000016,0x00000007,0x00000006,0x00050021,0x00000017, + 0x00000006,0x00000016,0x00000016,0x00040017,0x0000001c,0x00000006,0x00000002,0x00040020, + 0x0000001d,0x00000007,0x0000001c,0x00020014,0x0000001e,0x00040021,0x0000001f,0x0000001e, + 0x0000001d,0x0004002b,0x00000006,0x00000025,0x414eb852,0x0004002b,0x00000006,0x0000002a, + 0x3d6147ae,0x0004002b,0x00000006,0x0000002d,0x3f870a3d,0x0004002b,0x00000006,0x00000030, + 0x4019999a,0x0006002c,0x00000007,0x00000031,0x00000030,0x00000030,0x00000030,0x0004002b, + 0x00000006,0x00000035,0x3d25aee6,0x0006002c,0x00000007,0x00000036,0x00000035,0x00000035, + 0x00000035,0x0004002b,0x00000006,0x00000041,0x3ed55555,0x0006002c,0x00000007,0x00000042, + 0x00000041,0x00000041,0x00000041,0x0004002b,0x00000006,0x00000049,0x3b4d2e1c,0x0006002c, + 0x00000007,0x0000004a,0x00000049,0x00000049,0x00000049,0x0004002b,0x00000006,0x00000052, + 0x00000000,0x0004002b,0x00000006,0x00000053,0x3f800000,0x0004002b,0x00000006,0x00000057, + 0x3c4fcdac,0x0006002c,0x00000007,0x00000058,0x00000057,0x00000057,0x00000057,0x0004002b, + 0x00000006,0x0000005b,0x3f560000,0x0004002b,0x00000006,0x00000060,0x4196d000,0x0004002b, + 0x00000006,0x00000061,0x41958000,0x0004002b,0x00000006,0x00000066,0x358637bd,0x0004002b, + 0x00000006,0x0000006a,0x40c8e06b,0x0006002c,0x00000007,0x0000006b,0x0000006a,0x0000006a, + 0x0000006a,0x0004002b,0x00000006,0x00000074,0x3e232000,0x0006002c,0x00000007,0x00000075, + 0x00000074,0x00000074,0x00000074,0x0004002b,0x00000006,0x00000080,0x429db000,0x0006002c, + 0x00000007,0x00000081,0x00000080,0x00000080,0x00000080,0x0004002b,0x00000006,0x00000086, + 0x3f4ccccd,0x0004002b,0x00000006,0x0000008f,0x3d4ccccd,0x0004002b,0x00000006,0x00000093, + 0x407a5e35,0x0004002b,0x00000006,0x00000095,0x3e4ccccd,0x00040017,0x000000a3,0x00000006, + 0x00000004,0x00040015,0x000000a4,0x00000020,0x00000000,0x0004002b,0x000000a4,0x000000a5, + 0x00000006,0x0004001c,0x000000a6,0x000000a3,0x000000a5,0x0005001e,0x000000a7,0x000000a3, + 0x000000a3,0x000000a6,0x00040020,0x000000a8,0x00000009,0x000000a7,0x0004003b,0x000000a8, + 0x000000a9,0x00000009,0x00040015,0x000000aa,0x00000020,0x00000001,0x0004002b,0x000000aa, + 0x000000ab,0x00000001,0x0004002b,0x000000a4,0x000000ac,0x00000003,0x00040020,0x000000ad, + 0x00000009,0x00000006,0x0004002b,0x00000006,0x000000b2,0x41800000,0x00040020,0x000000b8, + 0x00000007,0x000000aa,0x0004002b,0x000000aa,0x000000ba,0x00000000,0x0004002b,0x000000aa, + 0x000000c1,0x00000006,0x0004002b,0x00000006,0x000000c6,0x3f000000,0x00040020,0x000000cc, + 0x00000007,0x000000a3,0x0004002b,0x000000aa,0x000000ce,0x00000002,0x00040020,0x000000d0, + 0x00000009,0x000000a3,0x0004002b,0x000000a4,0x000000d3,0x00000000,0x0004002b,0x000000a4, + 0x000000db,0x00000001,0x0004002b,0x000000a4,0x000000e6,0x00000002,0x0004002b,0x00000006, + 0x000000fc,0x40000000,0x0003002a,0x0000001e,0x00000103,0x00090019,0x00000107,0x00000006, + 0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x0003001b,0x00000108, + 0x00000107,0x00040020,0x00000109,0x00000000,0x00000108,0x0004003b,0x00000109,0x0000010a, + 0x00000000,0x00040020,0x0000010c,0x00000001,0x000000a3,0x0004003b,0x0000010c,0x0000010d, + 0x00000001,0x00040017,0x00000110,0x000000aa,0x00000002,0x00040020,0x00000115,0x00000007, + 0x0000001e,0x00040018,0x0000012b,0x00000007,0x00000003,0x0004002b,0x00000006,0x0000012c, + 0x3f209d49,0x0004002b,0x00000006,0x0000012d,0x3d8d844d,0x0004002b,0x00000006,0x0000012e, + 0x3c86594b,0x0006002c,0x00000007,0x0000012f,0x0000012c,0x0000012d,0x0000012e,0x0004002b, + 0x00000006,0x00000130,0x3ea89a02,0x0004002b,0x00000006,0x00000131,0x3f6b645a,0x0004002b, + 0x00000006,0x00000132,0x3db43958,0x0006002c,0x00000007,0x00000133,0x00000130,0x00000131, + 0x00000132,0x0004002b,0x00000006,0x00000134,0x3d315b57,0x0004002b,0x00000006,0x00000135, + 0x3c3ac711,0x0004002b,0x00000006,0x00000136,0x3f65460b,0x0006002c,0x00000007,0x00000137, + 0x00000134,0x00000135,0x00000136,0x0006002c,0x0000012b,0x00000138,0x0000012f,0x00000133, + 0x00000137,0x0004002b,0x00000006,0x00000140,0x461c4000,0x0004002b,0x00000006,0x0000014d, + 0x3fd48b44,0x0004002b,0x00000006,0x0000014e,0xbdff2e49,0x0004002b,0x00000006,0x0000014f, + 0xbc95182b,0x0006002c,0x00000007,0x00000150,0x0000014d,0x0000014e,0x0000014f,0x0004002b, + 0x00000006,0x00000151,0xbf166cf4,0x0004002b,0x00000006,0x00000152,0x3f9102de,0x0004002b, + 0x00000006,0x00000153,0xbdce075f,0x0006002c,0x00000007,0x00000154,0x00000151,0x00000152, + 0x00000153,0x0004002b,0x00000006,0x00000155,0xbd95182b,0x0004002b,0x00000006,0x00000156, + 0xbc07fcb9,0x0004002b,0x00000006,0x00000157,0x3f8f3190,0x0006002c,0x00000007,0x00000158, + 0x00000155,0x00000156,0x00000157,0x0006002c,0x0000012b,0x00000159,0x00000150,0x00000154, + 0x00000158,0x00040020,0x00000185,0x00000003,0x000000a3,0x0004003b,0x00000185,0x00000186, + 0x00000003,0x00040020,0x0000018c,0x00000001,0x0000001c,0x0004003b,0x0000018c,0x0000018d, + 0x00000001,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005, + 0x0004003b,0x00000008,0x00000106,0x00000007,0x0004003b,0x00000115,0x00000116,0x00000007, + 0x0004003b,0x0000001d,0x00000117,0x00000007,0x0004003b,0x00000016,0x0000011b,0x00000007, + 0x0004003b,0x00000008,0x00000127,0x00000007,0x0004003b,0x00000008,0x0000012a,0x00000007, + 0x0004003b,0x00000008,0x00000139,0x00000007,0x0004003b,0x00000008,0x00000143,0x00000007, + 0x0004003b,0x00000008,0x0000014c,0x00000007,0x0004003b,0x00000008,0x0000015a,0x00000007, + 0x0004003b,0x00000016,0x00000164,0x00000007,0x0004003b,0x00000016,0x00000177,0x00000007, + 0x0004003b,0x00000016,0x00000179,0x00000007,0x0004003b,0x00000008,0x00000183,0x00000007, + 0x0004003d,0x00000108,0x0000010b,0x0000010a,0x0004003d,0x000000a3,0x0000010e,0x0000010d, + 0x0007004f,0x0000001c,0x0000010f,0x0000010e,0x0000010e,0x00000000,0x00000001,0x0004006e, + 0x00000110,0x00000111,0x0000010f,0x00040064,0x00000107,0x00000112,0x0000010b,0x0007005f, + 0x000000a3,0x00000113,0x00000112,0x00000111,0x00000002,0x000000ba,0x0008004f,0x00000007, + 0x00000114,0x00000113,0x00000113,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000106, + 0x00000114,0x0004003d,0x000000a3,0x00000118,0x0000010d,0x0007004f,0x0000001c,0x00000119, + 0x00000118,0x00000118,0x00000000,0x00000001,0x0003003e,0x00000117,0x00000119,0x00050039, + 0x0000001e,0x0000011a,0x00000021,0x00000117,0x0003003e,0x00000116,0x0000011a,0x00060041, + 0x000000ad,0x0000011c,0x000000a9,0x000000ab,0x000000d3,0x0004003d,0x00000006,0x0000011d, + 0x0000011c,0x0007000c,0x00000006,0x0000011e,0x00000001,0x00000028,0x0000011d,0x00000053, + 0x0003003e,0x0000011b,0x0000011e,0x00060041,0x000000ad,0x0000011f,0x000000a9,0x000000ab, + 0x000000e6,0x0004003d,0x00000006,0x00000120,0x0000011f,0x000500b8,0x0000001e,0x00000121, + 0x00000120,0x000000c6,0x000300f7,0x00000123,0x00000000,0x000400fa,0x00000121,0x00000122, + 0x00000145,0x000200f8,0x00000122,0x0004003d,0x0000001e,0x00000124,0x00000116,0x000300f7, + 0x00000126,0x00000000,0x000400fa,0x00000124,0x00000125,0x00000129,0x000200f8,0x00000125, + 0x0004003d,0x00000007,0x00000128,0x00000106,0x0003003e,0x00000127,0x00000128,0x000200f9, + 0x00000126,0x000200f8,0x00000129,0x0004003d,0x00000007,0x0000013a,0x00000106,0x0003003e, + 0x00000139,0x0000013a,0x00050039,0x00000007,0x0000013b,0x0000000b,0x00000139,0x00050091, + 0x00000007,0x0000013c,0x00000138,0x0000013b,0x0004003d,0x00000006,0x0000013d,0x0000011b, + 0x0005008e,0x00000007,0x0000013e,0x0000013c,0x0000013d,0x0003003e,0x0000012a,0x0000013e, + 0x0004003d,0x00000007,0x0000013f,0x0000012a,0x00060050,0x00000007,0x00000141,0x00000140, + 0x00000140,0x00000140,0x00050088,0x00000007,0x00000142,0x0000013f,0x00000141,0x0003003e, + 0x00000143,0x00000142,0x00050039,0x00000007,0x00000144,0x00000014,0x00000143,0x0003003e, + 0x00000127,0x00000144,0x000200f9,0x00000126,0x000200f8,0x00000126,0x000200f9,0x00000123, + 0x000200f8,0x00000145,0x0004003d,0x0000001e,0x00000146,0x00000116,0x000400a8,0x0000001e, + 0x00000147,0x00000146,0x000300f7,0x00000149,0x00000000,0x000400fa,0x00000147,0x00000148, + 0x0000014b,0x000200f8,0x00000148,0x0004003d,0x00000007,0x0000014a,0x00000106,0x0003003e, + 0x00000127,0x0000014a,0x000200f9,0x00000149,0x000200f8,0x0000014b,0x0004003d,0x00000007, + 0x0000015b,0x00000106,0x0003003e,0x0000015a,0x0000015b,0x00050039,0x00000007,0x0000015c, + 0x00000011,0x0000015a,0x0005008e,0x00000007,0x0000015d,0x0000015c,0x00000140,0x00050091, + 0x00000007,0x0000015e,0x00000159,0x0000015d,0x00060050,0x00000007,0x0000015f,0x00000052, + 0x00000052,0x00000052,0x0007000c,0x00000007,0x00000160,0x00000001,0x00000028,0x0000015e, + 0x0000015f,0x0004003d,0x00000006,0x00000161,0x0000011b,0x00060050,0x00000007,0x00000162, + 0x00000161,0x00000161,0x00000161,0x00050088,0x00000007,0x00000163,0x00000160,0x00000162, + 0x0003003e,0x0000014c,0x00000163,0x00050041,0x00000016,0x00000165,0x0000014c,0x000000d3, + 0x0004003d,0x00000006,0x00000166,0x00000165,0x00050041,0x00000016,0x00000167,0x0000014c, + 0x000000db,0x0004003d,0x00000006,0x00000168,0x00000167,0x0007000c,0x00000006,0x00000169, + 0x00000001,0x00000028,0x00000166,0x00000168,0x00050041,0x00000016,0x0000016a,0x0000014c, + 0x000000e6,0x0004003d,0x00000006,0x0000016b,0x0000016a,0x0007000c,0x00000006,0x0000016c, + 0x00000001,0x00000028,0x00000169,0x0000016b,0x0003003e,0x00000164,0x0000016c,0x0004003d, + 0x00000006,0x0000016d,0x00000164,0x000500ba,0x0000001e,0x0000016e,0x0000016d,0x00000066, + 0x000300f7,0x00000170,0x00000000,0x000400fa,0x0000016e,0x0000016f,0x00000170,0x000200f8, + 0x0000016f,0x00060041,0x000000ad,0x00000171,0x000000a9,0x000000ab,0x000000db,0x0004003d, + 0x00000006,0x00000172,0x00000171,0x0004003d,0x00000006,0x00000173,0x0000011b,0x0007000c, + 0x00000006,0x00000174,0x00000001,0x00000028,0x00000172,0x00000173,0x0004003d,0x00000006, + 0x00000175,0x0000011b,0x00050088,0x00000006,0x00000176,0x00000174,0x00000175,0x0004003d, + 0x00000006,0x00000178,0x00000164,0x0003003e,0x00000177,0x00000178,0x0003003e,0x00000179, + 0x00000176,0x00060039,0x00000006,0x0000017a,0x0000001a,0x00000177,0x00000179,0x0004003d, + 0x00000006,0x0000017b,0x00000164,0x00050088,0x00000006,0x0000017c,0x0000017a,0x0000017b, + 0x0004003d,0x00000007,0x0000017d,0x0000014c,0x0005008e,0x00000007,0x0000017e,0x0000017d, + 0x0000017c,0x0003003e,0x0000014c,0x0000017e,0x000200f9,0x00000170,0x000200f8,0x00000170, + 0x0004003d,0x00000007,0x0000017f,0x0000014c,0x00060050,0x00000007,0x00000180,0x00000052, + 0x00000052,0x00000052,0x00060050,0x00000007,0x00000181,0x00000053,0x00000053,0x00000053, + 0x0008000c,0x00000007,0x00000182,0x00000001,0x0000002b,0x0000017f,0x00000180,0x00000181, + 0x0003003e,0x00000183,0x00000182,0x00050039,0x00000007,0x00000184,0x0000000e,0x00000183, + 0x0003003e,0x00000127,0x00000184,0x000200f9,0x00000149,0x000200f8,0x00000149,0x000200f9, + 0x00000123,0x000200f8,0x00000123,0x0004003d,0x00000007,0x00000187,0x00000127,0x00050051, + 0x00000006,0x00000188,0x00000187,0x00000000,0x00050051,0x00000006,0x00000189,0x00000187, + 0x00000001,0x00050051,0x00000006,0x0000018a,0x00000187,0x00000002,0x00070050,0x000000a3, + 0x0000018b,0x00000188,0x00000189,0x0000018a,0x00000053,0x0003003e,0x00000186,0x0000018b, + 0x000100fd,0x00010038,0x00050036,0x00000007,0x0000000b,0x00000000,0x00000009,0x00030037, + 0x00000008,0x0000000a,0x000200f8,0x0000000c,0x0004003b,0x00000008,0x00000023,0x00000007, + 0x0004003b,0x00000008,0x00000028,0x00000007,0x0004003d,0x00000007,0x00000024,0x0000000a, + 0x00060050,0x00000007,0x00000026,0x00000025,0x00000025,0x00000025,0x00050088,0x00000007, + 0x00000027,0x00000024,0x00000026,0x0003003e,0x00000023,0x00000027,0x0004003d,0x00000007, + 0x00000029,0x0000000a,0x00060050,0x00000007,0x0000002b,0x0000002a,0x0000002a,0x0000002a, + 0x00050081,0x00000007,0x0000002c,0x00000029,0x0000002b,0x00060050,0x00000007,0x0000002e, + 0x0000002d,0x0000002d,0x0000002d,0x00050088,0x00000007,0x0000002f,0x0000002c,0x0000002e, + 0x0007000c,0x00000007,0x00000032,0x00000001,0x0000001a,0x0000002f,0x00000031,0x0003003e, + 0x00000028,0x00000032,0x0004003d,0x00000007,0x00000033,0x00000023,0x0004003d,0x00000007, + 0x00000034,0x00000028,0x0004003d,0x00000007,0x00000037,0x0000000a,0x0007000c,0x00000007, + 0x00000038,0x00000001,0x00000030,0x00000036,0x00000037,0x0008000c,0x00000007,0x00000039, + 0x00000001,0x0000002e,0x00000033,0x00000034,0x00000038,0x000200fe,0x00000039,0x00010038, + 0x00050036,0x00000007,0x0000000e,0x00000000,0x00000009,0x00030037,0x00000008,0x0000000d, + 0x000200f8,0x0000000f,0x0004003b,0x00000008,0x0000003c,0x00000007,0x0004003b,0x00000008, + 0x0000003f,0x00000007,0x0004003d,0x00000007,0x0000003d,0x0000000d,0x0005008e,0x00000007, + 0x0000003e,0x0000003d,0x00000025,0x0003003e,0x0000003c,0x0000003e,0x0004003d,0x00000007, + 0x00000040,0x0000000d,0x0007000c,0x00000007,0x00000043,0x00000001,0x0000001a,0x00000040, + 0x00000042,0x0005008e,0x00000007,0x00000044,0x00000043,0x0000002d,0x00060050,0x00000007, + 0x00000045,0x0000002a,0x0000002a,0x0000002a,0x00050083,0x00000007,0x00000046,0x00000044, + 0x00000045,0x0003003e,0x0000003f,0x00000046,0x0004003d,0x00000007,0x00000047,0x0000003c, + 0x0004003d,0x00000007,0x00000048,0x0000003f,0x0004003d,0x00000007,0x0000004b,0x0000000d, + 0x0007000c,0x00000007,0x0000004c,0x00000001,0x00000030,0x0000004a,0x0000004b,0x0008000c, + 0x00000007,0x0000004d,0x00000001,0x0000002e,0x00000047,0x00000048,0x0000004c,0x000200fe, + 0x0000004d,0x00010038,0x00050036,0x00000007,0x00000011,0x00000000,0x00000009,0x00030037, + 0x00000008,0x00000010,0x000200f8,0x00000012,0x0004003b,0x00000008,0x00000050,0x00000007, + 0x0004003d,0x00000007,0x00000051,0x00000010,0x00060050,0x00000007,0x00000054,0x00000052, + 0x00000052,0x00000052,0x00060050,0x00000007,0x00000055,0x00000053,0x00000053,0x00000053, + 0x0008000c,0x00000007,0x00000056,0x00000001,0x0000002b,0x00000051,0x00000054,0x00000055, + 0x0007000c,0x00000007,0x00000059,0x00000001,0x0000001a,0x00000056,0x00000058,0x0003003e, + 0x00000050,0x00000059,0x0004003d,0x00000007,0x0000005a,0x00000050,0x00060050,0x00000007, + 0x0000005c,0x0000005b,0x0000005b,0x0000005b,0x00050083,0x00000007,0x0000005d,0x0000005a, + 0x0000005c,0x00060050,0x00000007,0x0000005e,0x00000052,0x00000052,0x00000052,0x0007000c, + 0x00000007,0x0000005f,0x00000001,0x00000028,0x0000005d,0x0000005e,0x0004003d,0x00000007, + 0x00000062,0x00000050,0x0005008e,0x00000007,0x00000063,0x00000062,0x00000061,0x00060050, + 0x00000007,0x00000064,0x00000060,0x00000060,0x00000060,0x00050083,0x00000007,0x00000065, + 0x00000064,0x00000063,0x00060050,0x00000007,0x00000067,0x00000066,0x00000066,0x00000066, + 0x0007000c,0x00000007,0x00000068,0x00000001,0x00000028,0x00000065,0x00000067,0x00050088, + 0x00000007,0x00000069,0x0000005f,0x00000068,0x0007000c,0x00000007,0x0000006c,0x00000001, + 0x0000001a,0x00000069,0x0000006b,0x000200fe,0x0000006c,0x00010038,0x00050036,0x00000007, + 0x00000014,0x00000000,0x00000009,0x00030037,0x00000008,0x00000013,0x000200f8,0x00000015, + 0x0004003b,0x00000008,0x0000006f,0x00000007,0x0004003d,0x00000007,0x00000070,0x00000013, + 0x00060050,0x00000007,0x00000071,0x00000052,0x00000052,0x00000052,0x00060050,0x00000007, + 0x00000072,0x00000053,0x00000053,0x00000053,0x0008000c,0x00000007,0x00000073,0x00000001, + 0x0000002b,0x00000070,0x00000071,0x00000072,0x0007000c,0x00000007,0x00000076,0x00000001, + 0x0000001a,0x00000073,0x00000075,0x0003003e,0x0000006f,0x00000076,0x0004003d,0x00000007, + 0x00000077,0x0000006f,0x0005008e,0x00000007,0x00000078,0x00000077,0x00000060,0x00060050, + 0x00000007,0x00000079,0x0000005b,0x0000005b,0x0000005b,0x00050081,0x00000007,0x0000007a, + 0x00000079,0x00000078,0x0004003d,0x00000007,0x0000007b,0x0000006f,0x0005008e,0x00000007, + 0x0000007c,0x0000007b,0x00000061,0x00060050,0x00000007,0x0000007d,0x00000053,0x00000053, + 0x00000053,0x00050081,0x00000007,0x0000007e,0x0000007d,0x0000007c,0x00050088,0x00000007, + 0x0000007f,0x0000007a,0x0000007e,0x0007000c,0x00000007,0x00000082,0x00000001,0x0000001a, + 0x0000007f,0x00000081,0x000200fe,0x00000082,0x00010038,0x00050036,0x00000006,0x0000001a, + 0x00000000,0x00000017,0x00030037,0x00000016,0x00000018,0x00030037,0x00000016,0x00000019, + 0x000200f8,0x0000001b,0x0004003b,0x00000016,0x0000008c,0x00000007,0x0004003b,0x00000016, + 0x00000091,0x00000007,0x0004003d,0x00000006,0x00000085,0x00000018,0x000500bc,0x0000001e, + 0x00000087,0x00000085,0x00000086,0x000300f7,0x00000089,0x00000000,0x000400fa,0x00000087, + 0x00000088,0x00000089,0x000200f8,0x00000088,0x0004003d,0x00000006,0x0000008a,0x00000018, + 0x000200fe,0x0000008a,0x000200f8,0x00000089,0x0004003d,0x00000006,0x0000008d,0x00000019, + 0x00050083,0x00000006,0x0000008e,0x0000008d,0x00000086,0x0007000c,0x00000006,0x00000090, + 0x00000001,0x00000028,0x0000008e,0x0000008f,0x0003003e,0x0000008c,0x00000090,0x0004003d, + 0x00000006,0x00000092,0x0000008c,0x00050088,0x00000006,0x00000094,0x00000092,0x00000093, + 0x0007000c,0x00000006,0x00000096,0x00000001,0x00000028,0x00000094,0x00000095,0x0003003e, + 0x00000091,0x00000096,0x0004003d,0x00000006,0x00000097,0x00000018,0x00050083,0x00000006, + 0x00000098,0x00000097,0x00000086,0x0004007f,0x00000006,0x00000099,0x00000098,0x0004003d, + 0x00000006,0x0000009a,0x00000091,0x00050088,0x00000006,0x0000009b,0x00000099,0x0000009a, + 0x0006000c,0x00000006,0x0000009c,0x00000001,0x0000001b,0x0000009b,0x00050083,0x00000006, + 0x0000009d,0x00000053,0x0000009c,0x00050085,0x00000006,0x0000009e,0x00000095,0x0000009d, + 0x00050081,0x00000006,0x0000009f,0x00000086,0x0000009e,0x000200fe,0x0000009f,0x00010038, + 0x00050036,0x0000001e,0x00000021,0x00000000,0x0000001f,0x00030037,0x0000001d,0x00000020, + 0x000200f8,0x00000022,0x0004003b,0x00000016,0x000000a2,0x00000007,0x0004003b,0x00000016, + 0x000000b0,0x00000007,0x0004003b,0x00000016,0x000000b4,0x00000007,0x0004003b,0x000000b8, + 0x000000b9,0x00000007,0x0004003b,0x000000cc,0x000000cd,0x00000007,0x0004003b,0x00000016, + 0x000000f5,0x00000007,0x00060041,0x000000ad,0x000000ae,0x000000a9,0x000000ab,0x000000ac, + 0x0004003d,0x00000006,0x000000af,0x000000ae,0x0003003e,0x000000a2,0x000000af,0x0004003d, + 0x00000006,0x000000b1,0x000000a2,0x0005008d,0x00000006,0x000000b3,0x000000b1,0x000000b2, + 0x0003003e,0x000000b0,0x000000b3,0x0004003d,0x00000006,0x000000b5,0x000000a2,0x00050088, + 0x00000006,0x000000b6,0x000000b5,0x000000b2,0x0006000c,0x00000006,0x000000b7,0x00000001, + 0x00000008,0x000000b6,0x0003003e,0x000000b4,0x000000b7,0x0003003e,0x000000b9,0x000000ba, + 0x000200f9,0x000000bb,0x000200f8,0x000000bb,0x000400f6,0x000000bd,0x000000be,0x00000000, + 0x000200f9,0x000000bf,0x000200f8,0x000000bf,0x0004003d,0x000000aa,0x000000c0,0x000000b9, + 0x000500b1,0x0000001e,0x000000c2,0x000000c0,0x000000c1,0x000400fa,0x000000c2,0x000000bc, + 0x000000bd,0x000200f8,0x000000bc,0x0004003d,0x000000aa,0x000000c3,0x000000b9,0x0004006f, + 0x00000006,0x000000c4,0x000000c3,0x0004003d,0x00000006,0x000000c5,0x000000b0,0x00050083, + 0x00000006,0x000000c7,0x000000c5,0x000000c6,0x000500be,0x0000001e,0x000000c8,0x000000c4, + 0x000000c7,0x000300f7,0x000000ca,0x00000000,0x000400fa,0x000000c8,0x000000c9,0x000000ca, + 0x000200f8,0x000000c9,0x000200f9,0x000000bd,0x000200f8,0x000000ca,0x0004003d,0x000000aa, + 0x000000cf,0x000000b9,0x00060041,0x000000d0,0x000000d1,0x000000a9,0x000000ce,0x000000cf, + 0x0004003d,0x000000a3,0x000000d2,0x000000d1,0x0003003e,0x000000cd,0x000000d2,0x00050041, + 0x00000016,0x000000d4,0x00000020,0x000000d3,0x0004003d,0x00000006,0x000000d5,0x000000d4, + 0x00050041,0x00000016,0x000000d6,0x000000cd,0x000000d3,0x0004003d,0x00000006,0x000000d7, + 0x000000d6,0x000500be,0x0000001e,0x000000d8,0x000000d5,0x000000d7,0x000300f7,0x000000da, + 0x00000000,0x000400fa,0x000000d8,0x000000d9,0x000000da,0x000200f8,0x000000d9,0x00050041, + 0x00000016,0x000000dc,0x00000020,0x000000db,0x0004003d,0x00000006,0x000000dd,0x000000dc, + 0x00050041,0x00000016,0x000000de,0x000000cd,0x000000db,0x0004003d,0x00000006,0x000000df, + 0x000000de,0x000500be,0x0000001e,0x000000e0,0x000000dd,0x000000df,0x000200f9,0x000000da, + 0x000200f8,0x000000da,0x000700f5,0x0000001e,0x000000e1,0x000000d8,0x000000ca,0x000000e0, + 0x000000d9,0x000300f7,0x000000e3,0x00000000,0x000400fa,0x000000e1,0x000000e2,0x000000e3, + 0x000200f8,0x000000e2,0x00050041,0x00000016,0x000000e4,0x00000020,0x000000d3,0x0004003d, + 0x00000006,0x000000e5,0x000000e4,0x00050041,0x00000016,0x000000e7,0x000000cd,0x000000e6, + 0x0004003d,0x00000006,0x000000e8,0x000000e7,0x000500b8,0x0000001e,0x000000e9,0x000000e5, + 0x000000e8,0x000200f9,0x000000e3,0x000200f8,0x000000e3,0x000700f5,0x0000001e,0x000000ea, + 0x000000e1,0x000000da,0x000000e9,0x000000e2,0x000300f7,0x000000ec,0x00000000,0x000400fa, + 0x000000ea,0x000000eb,0x000000ec,0x000200f8,0x000000eb,0x00050041,0x00000016,0x000000ed, + 0x00000020,0x000000db,0x0004003d,0x00000006,0x000000ee,0x000000ed,0x00050041,0x00000016, + 0x000000ef,0x000000cd,0x000000ac,0x0004003d,0x00000006,0x000000f0,0x000000ef,0x000500b8, + 0x0000001e,0x000000f1,0x000000ee,0x000000f0,0x000200f9,0x000000ec,0x000200f8,0x000000ec, + 0x000700f5,0x0000001e,0x000000f2,0x000000ea,0x000000e3,0x000000f1,0x000000eb,0x000300f7, + 0x000000f4,0x00000000,0x000400fa,0x000000f2,0x000000f3,0x000000f4,0x000200f8,0x000000f3, + 0x0004003d,0x00000006,0x000000f6,0x000000b4,0x0004003d,0x000000aa,0x000000f7,0x000000b9, + 0x0004006f,0x00000006,0x000000f8,0x000000f7,0x0006000c,0x00000006,0x000000f9,0x00000001, + 0x0000001d,0x000000f8,0x00050088,0x00000006,0x000000fa,0x000000f6,0x000000f9,0x0006000c, + 0x00000006,0x000000fb,0x00000001,0x00000008,0x000000fa,0x0005008d,0x00000006,0x000000fd, + 0x000000fb,0x000000fc,0x0003003e,0x000000f5,0x000000fd,0x0004003d,0x00000006,0x000000fe, + 0x000000f5,0x000500ba,0x0000001e,0x000000ff,0x000000fe,0x000000c6,0x000200fe,0x000000ff, + 0x000200f8,0x000000f4,0x000200f9,0x000000be,0x000200f8,0x000000be,0x0004003d,0x000000aa, + 0x00000101,0x000000b9,0x00050080,0x000000aa,0x00000102,0x00000101,0x000000ab,0x0003003e, + 0x000000b9,0x00000102,0x000200f9,0x000000bb,0x000200f8,0x000000bd,0x000200fe,0x00000103, + 0x00010038 +}; diff --git a/app/src/main/cpp/waylandcomp/src/sc_layer.c b/app/src/main/cpp/waylandcomp/src/sc_layer.c new file mode 100644 index 000000000..f98005a4c --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/sc_layer.c @@ -0,0 +1,1112 @@ +/* Layer mode: the scene on a small set of ASurfaceControl layers โ€” see sc_layer.h and + * ZERO_COPY_SPIKE.md. The ASurfaceControl/ASurfaceTransaction API is dlsym'd from libandroid.so + * (API 29+), the same way the X11 renderers' scanout code does it, so the library still loads on + * older devices and the prototype stays off every other path. */ +#define _GNU_SOURCE +#include "sc_layer.h" +#include "ahb_swapchain.h" +#include "color_mgmt.h" +#include "vk_present.h" +#include <android/hardware_buffer.h> +#include <android/native_window.h> +#include <android/rect.h> +#include <dlfcn.h> +#include <errno.h> +#include <math.h> +#include <poll.h> +#include <pthread.h> +#include <stdatomic.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <time.h> +#include <unistd.h> + +#define FOURCC(a, b, c, d) \ + ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) +#define DRM_ABGR8888 FOURCC('A', 'B', '2', '4') /* R,G,B,A in memory = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM */ +#define DRM_ABGR2101010 FOURCC('A', 'B', '3', '0') /* = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM (VK A2B10G10R10) */ +#define AHB_RGBA8 AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM +#define AHB_RGB10A2 AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM +#define MOD_LINEAR 0ULL +#define MOD_QCOM_COMPRESSED 0x0500000000000001ULL /* DRM_FORMAT_MOD_QCOM_COMPRESSED (UBWC) */ + +/* The gralloc buffer handle behind an AHardwareBuffer (AOSP cutils/native_handle.h, which the NDK + * does not ship; the layout is a stable ABI: fds first, then ints). */ +struct wnc_native_handle { int version; int numFds; int numInts; int data[]; }; + +/* ---- libandroid SurfaceControl API (dlsym) ---------------------------------------------------- */ + +typedef struct ASurfaceControl ASurfaceControl; +typedef struct ASurfaceTransaction ASurfaceTransaction; +typedef struct ASurfaceTransactionStats ASurfaceTransactionStats; +typedef void (*sc_complete_fn)(void *context, ASurfaceTransactionStats *stats); + +/* <android/hdr_metadata.h> (NDK, API 29), declared here like the rest of this table: stable ABI. */ +struct wnc_color_xy { float x, y; }; +struct wnc_hdr_smpte2086 { + struct wnc_color_xy displayPrimaryRed, displayPrimaryGreen, displayPrimaryBlue, whitePoint; + float maxLuminance, minLuminance; +}; +struct wnc_hdr_cta861_3 { float maxContentLightLevel, maxFrameAverageLightLevel; }; + +static struct { + ASurfaceControl *(*createFromWindow)(ANativeWindow *, const char *); + void (*release)(ASurfaceControl *); + ASurfaceTransaction *(*txCreate)(void); + void (*txDelete)(ASurfaceTransaction *); + void (*txApply)(ASurfaceTransaction *); + void (*setBuffer)(ASurfaceTransaction *, ASurfaceControl *, AHardwareBuffer *, int); + void (*setZOrder)(ASurfaceTransaction *, ASurfaceControl *, int32_t); + void (*setVisibility)(ASurfaceTransaction *, ASurfaceControl *, int8_t); + void (*setGeometry)(ASurfaceTransaction *, ASurfaceControl *, const ARect *, const ARect *, int32_t); + void (*setBufferTransparency)(ASurfaceTransaction *, ASurfaceControl *, int8_t); + void (*reparent)(ASurfaceTransaction *, ASurfaceControl *, ASurfaceControl *); + void (*setOnComplete)(ASurfaceTransaction *, void *, sc_complete_fn); + int (*prevReleaseFence)(ASurfaceTransactionStats *, ASurfaceControl *); + /* Display frame-rate vote for the layer (API 30 / 31). Optional: absent on older Android, where + * the layer simply carries no vote and the app's surface vote is all there is. */ + void (*setFrameRate)(ASurfaceTransaction *, ASurfaceControl *, float, int8_t); + void (*setFrameRateStrategy)(ASurfaceTransaction *, ASurfaceControl *, float, int8_t, int8_t); + /* Colour (API 29), optional: only an HDR session ever calls them (color_mgmt.h's gate needs + * setBufferDataSpace; the metadata calls are sent when present). */ + void (*setBufferDataSpace)(ASurfaceTransaction *, ASurfaceControl *, int32_t); + void (*setHdrMetadata_smpte2086)(ASurfaceTransaction *, ASurfaceControl *, const struct wnc_hdr_smpte2086 *); + void (*setHdrMetadata_cta861_3)(ASurfaceTransaction *, ASurfaceControl *, const struct wnc_hdr_cta861_3 *); + /* Not in the public NDK headers (vndk/hardware_buffer.h) but exported by libnativewindow.so on + * every device; Mesa's Android WSI calls it for every gralloc buffer it imports. */ + const void *(*getNativeHandle)(const AHardwareBuffer *); + /* API 35: the HDR headroom this layer asks the display for (0 = no preference). Round 2e asks + * explicitly for every PQ layer - some phones only boost HDR when a layer asks. */ + void (*setDesiredHdrHeadroom)(ASurfaceTransaction *, ASurfaceControl *, float); + int state; /* 0 = untried, 1 = loaded, -1 = unavailable */ +} api; + +#define ASC_VISIBILITY_HIDE 0 +#define ASC_VISIBILITY_SHOW 1 +#define ASC_TRANSPARENCY_OPAQUE 2 + +static int load_api(void) { + if (api.state) return api.state == 1 ? 0 : -1; + api.state = -1; + void *nw = dlopen("libnativewindow.so", RTLD_NOW | RTLD_NOLOAD); + if (!nw) nw = dlopen("libnativewindow.so", RTLD_NOW); + api.getNativeHandle = nw ? dlsym(nw, "AHardwareBuffer_getNativeHandle") : NULL; + if (!api.getNativeHandle) { + wnc_log("layer", "unavailable: libnativewindow.so has no AHardwareBuffer_getNativeHandle"); + return -1; + } + void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_NOLOAD); + if (!lib) lib = dlopen("libandroid.so", RTLD_NOW); + if (!lib) { wnc_log("layer", "unavailable: dlopen(libandroid.so): %s", dlerror()); return -1; } +#define SYM(field, name) api.field = dlsym(lib, name) + SYM(createFromWindow, "ASurfaceControl_createFromWindow"); + SYM(release, "ASurfaceControl_release"); + SYM(txCreate, "ASurfaceTransaction_create"); + SYM(txDelete, "ASurfaceTransaction_delete"); + SYM(txApply, "ASurfaceTransaction_apply"); + SYM(setBuffer, "ASurfaceTransaction_setBuffer"); + SYM(setZOrder, "ASurfaceTransaction_setZOrder"); + SYM(setVisibility, "ASurfaceTransaction_setVisibility"); + SYM(setGeometry, "ASurfaceTransaction_setGeometry"); + SYM(setBufferTransparency, "ASurfaceTransaction_setBufferTransparency"); + SYM(reparent, "ASurfaceTransaction_reparent"); + SYM(setOnComplete, "ASurfaceTransaction_setOnComplete"); + SYM(prevReleaseFence, "ASurfaceTransactionStats_getPreviousReleaseFenceFd"); + SYM(setFrameRate, "ASurfaceTransaction_setFrameRate"); + SYM(setFrameRateStrategy, "ASurfaceTransaction_setFrameRateWithChangeStrategy"); + SYM(setBufferDataSpace, "ASurfaceTransaction_setBufferDataSpace"); + SYM(setHdrMetadata_smpte2086, "ASurfaceTransaction_setHdrMetadata_smpte2086"); + SYM(setHdrMetadata_cta861_3, "ASurfaceTransaction_setHdrMetadata_cta861_3"); + SYM(setDesiredHdrHeadroom, "ASurfaceTransaction_setDesiredHdrHeadroom"); +#undef SYM + if (!api.createFromWindow || !api.release || !api.txCreate || !api.txDelete || !api.txApply || + !api.setBuffer || !api.setZOrder || !api.setVisibility || !api.setGeometry || + !api.setBufferTransparency || !api.reparent || !api.setOnComplete || !api.prevReleaseFence) { + wnc_log("layer", "unavailable: libandroid.so lacks part of the ASurfaceControl API (Android 10+)"); + return -1; + } + api.state = 1; + return 0; +} + +/* ---- display frame-rate vote (VRR / refresh-rate matching) ------------------------------------ */ +/* The app votes a panel cadence with Surface.setFrameRate on the compositor's SurfaceView, but a + * zero-copy game's frames never touch that surface - they go straight onto the GAME layer - so + * SurfaceFlinger needs the same vote there or the layer's cadence is invisible to it. The vote + * belongs to the layer the game is actually presenting on and to no other: the overlay layer + * carries a window that updates on its own (slow) schedule and is explicitly voted 0, so it never + * drags the panel. The rate is set from the app thread and applied on the compositor thread with + * the next transaction, so no transaction is ever created off-thread. 0 = no vote (panel free). */ +#define ASC_FRAME_RATE_COMPAT_DEFAULT 0 /* == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT */ +#define ASC_CHANGE_FRAME_RATE_ALWAYS 1 /* == ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS */ + +static _Atomic float g_fps_want; /* what the app asked for (the game's cadence) */ + +void sc_layer_set_frame_rate(float fps) { + atomic_store(&g_fps_want, fps > 0.0f ? fps : 0.0f); +} + +/* ---- the layers and their buffer pools -------------------------------------------------------- */ + +/* Five for the game layer: one on screen, one queued, and the ones SurfaceFlinger has not reported + * back yet (its OnComplete for a transaction arrives only after the NEXT frame is presented), with + * room to spare, so a free buffer is nearly always one the display already let go of. With three, + * a late callback left none free ("no free layer buffer โ€ฆ frame dropped"). */ +#define POOL_MAX 5 + +struct slot { + AHardwareBuffer *ahb; + struct vkp_image *img; /* the AHB imported into the compositor's Turnip as a blit target */ + int w, h; + uint32_t fmt; /* AHARDWAREBUFFER_FORMAT_*: RGBA8888, or RGBA1010102 for an HDR picture */ + int release_fd; /* SurfaceFlinger's release fence: signals when it stopped reading; -1 = none */ + int busy; /* set on the layer, or still referenced by SurfaceFlinger */ +}; + +/* One Android display layer: its SurfaceControl, its own buffer pool and its own lifetime. The + * z-order is fixed by the id (game below, overlay above); nothing is ever re-ordered at runtime. */ +struct layer { + const char *name; + int32_t z; + int pool_n; /* buffers in this layer's pool (the game cycles more) */ + struct slot slots[POOL_MAX]; + ASurfaceControl *sc; + ANativeWindow *win; /* the output window sc was created on */ + int shown; + int cur_slot; /* pool slot of the buffer on the layer, -1 = none / not ours */ + void *cur_token; /* zero-copy: the game's buffer on the layer (NULL = a pool slot) */ + ARect geo_src, geo_dst; + int geo_valid; + int alloc_tier; /* how pool buffers are asked of gralloc (TIER_*), lowered on failure */ + uint64_t pool_modifier; + int first_logged; + int64_t drop_logged_ns; + unsigned drops_unlogged; /* frames dropped since the last "no free layer buffer" line */ + /* The last pool layout written to the log, so a line is written per change, not per buffer. */ + int logged_w, logged_h, logged_tier; + uint32_t logged_fmt; + uint64_t logged_mod; + int votes_rate; /* 1: this layer carries the game's cadence (the game layer) */ + float fps_applied; /* the vote the live SurfaceControl already carries (-1 = none yet) */ + int recreate_pending; /* composition recovery: swap this layer's SurfaceControl for a fresh + * one inside the next frame's transaction (see swap_sc_begin) */ + int32_t ds_applied; /* dataspace the live SurfaceControl carries; -1 = never set on it + * (untouched, today's UNKNOWN), see apply_colour */ + uint32_t md_applied; /* identity of the image description whose metadata it carries, 0 = none */ + float hr_applied; /* desired HDR headroom the live SurfaceControl carries; < 0 = never set */ +}; + +static struct layer g_layers[SC_LAYER_COUNT]; +static void apply_frame_rate(ASurfaceTransaction *tx, struct layer *l); +static void coverage_text(const struct layer *l, char *out, size_t n); +static int g_layers_ready; +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; /* pools + callback state */ +static int g_pending_cb; /* OnComplete callbacks not yet delivered */ +static unsigned g_stat_layer_frames; /* frames put on a layer through our own buffers */ +static int g_two_logged; +/* A small buffer set on a layer when it is hidden or retired, so SurfaceFlinger replaces (and + * releases, with a fence) the game's buffer instead of holding it while the layer is invisible. */ +static AHardwareBuffer *g_blank; + +static void layers_init(void) { + if (g_layers_ready) return; + g_layers_ready = 1; + g_layers[SC_LAYER_GAME] = (struct layer){.name = "wnc_wayland_game", .z = 1, .pool_n = POOL_MAX, + .cur_slot = -1, .votes_rate = 1, .fps_applied = -1.0f, .hr_applied = -1.0f, + .ds_applied = -1}; + g_layers[SC_LAYER_OVERLAY] = (struct layer){.name = "wnc_wayland_overlay", .z = 2, .pool_n = 3, + .cur_slot = -1, .votes_rate = 0, .fps_applied = -1.0f, + .hr_applied = -1.0f, .ds_applied = -1}; + for (int i = 0; i < SC_LAYER_COUNT; i++) + for (int j = 0; j < POOL_MAX; j++) g_layers[i].slots[j].release_fd = -1; +} + +static struct layer *layer_of(int id) { + layers_init(); + return (id >= 0 && id < SC_LAYER_COUNT) ? &g_layers[id] : NULL; +} + +struct complete_ctx { + ASurfaceControl *sc; + int layer; /* which layer's pool prev_slot belongs to */ + int prev_slot; /* pool buffer this transaction replaced: free once its release fence is known */ + void *prev_token; /* or the game's buffer it replaced: ahb_swapchain gets its release fence */ + int retire; /* release sc after this transaction (hide + reparent) */ +}; + +static int64_t now_ns(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +/* Binder thread. */ +static void on_complete(void *context, ASurfaceTransactionStats *stats) { + struct complete_ctx *ctx = context; + pthread_mutex_lock(&g_lock); + if (ctx->layer >= 0 && ctx->layer < SC_LAYER_COUNT && ctx->prev_slot >= 0 && ctx->prev_slot < POOL_MAX) { + struct slot *s = &g_layers[ctx->layer].slots[ctx->prev_slot]; + int fd = (stats && ctx->sc) ? api.prevReleaseFence(stats, ctx->sc) : -1; + if (s->release_fd >= 0) close(s->release_fd); + s->release_fd = fd; + s->busy = 0; + } + if (g_pending_cb > 0) g_pending_cb--; + pthread_mutex_unlock(&g_lock); + if (ctx->prev_token) { + int fd = (stats && ctx->sc) ? api.prevReleaseFence(stats, ctx->sc) : -1; + ahb_swapchain_layer_released(ctx->prev_token, fd); /* takes the fd */ + } + if (ctx->retire && ctx->sc) api.release(ctx->sc); + free(ctx); +} + +/* `sc` is the SurfaceControl the REPLACED buffer sits on, which is not always l->sc: during a + * composition-recovery swap the outgoing buffer belongs to the old SurfaceControl, and both its + * release fence and its release must be taken from that one. */ +static int add_complete_on(ASurfaceTransaction *tx, struct layer *l, ASurfaceControl *sc, + int prev_slot, void *prev_token, int retire) { + struct complete_ctx *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) return -1; + ctx->sc = sc; ctx->layer = (int)(l - g_layers); ctx->prev_slot = prev_slot; + ctx->prev_token = prev_token; ctx->retire = retire; + pthread_mutex_lock(&g_lock); + g_pending_cb++; + pthread_mutex_unlock(&g_lock); + api.setOnComplete(tx, ctx, on_complete); + return 0; +} + +static int add_complete(ASurfaceTransaction *tx, struct layer *l, int prev_slot, void *prev_token, int retire) { + return add_complete_on(tx, l, l->sc, prev_slot, prev_token, retire); +} + +/* The stand-in buffer for a hidden/retired layer (allocated once, black, shared by both layers). */ +static AHardwareBuffer *blank_buffer(void) { + if (g_blank) return g_blank; + AHardwareBuffer_Desc d = { + .width = 16, .height = 16, .layers = 1, .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + .usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | + AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY}; + if (AHardwareBuffer_allocate(&d, &g_blank) != 0 || !g_blank) { g_blank = NULL; return NULL; } + void *p = NULL; + if (AHardwareBuffer_lock(g_blank, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, -1, NULL, &p) == 0 && p) { + AHardwareBuffer_Desc got; AHardwareBuffer_describe(g_blank, &got); + memset(p, 0, (size_t)got.stride * 4 * 16); + AHardwareBuffer_unlock(g_blank, NULL); + } + return g_blank; +} + +/* Take the current buffer (pool slot or the game's) off the layer by putting the blank one on it, + * so SurfaceFlinger releases it with a fence through this transaction's callback. */ +static void replace_with_blank(ASurfaceTransaction *tx, struct layer *l) { + AHardwareBuffer *b = blank_buffer(); + if (b) api.setBuffer(tx, l->sc, b, -1); +} + +/* Everything a SurfaceControl needs on its way out, as transaction ops: give the buffer back (so + * SurfaceFlinger releases the game's, with a fence), hide it, unparent it. */ +static void retire_ops(ASurfaceTransaction *tx, ASurfaceControl *sc) { + AHardwareBuffer *b = blank_buffer(); + if (b) api.setBuffer(tx, sc, b, -1); + api.setVisibility(tx, sc, ASC_VISIBILITY_HIDE); + api.reparent(tx, sc, NULL); +} + +/* Hide + detach one layer; the SurfaceControl is released from the transaction's callback (the + * buffer on it stays referenced by SurfaceFlinger until then). */ +static void retire_sc(struct layer *l) { + if (!l->sc) return; + ASurfaceTransaction *tx = api.txCreate(); + if (tx) { + retire_ops(tx, l->sc); + if (add_complete(tx, l, l->cur_slot, l->cur_token, 1) != 0) api.release(l->sc); + api.txApply(tx); + api.txDelete(tx); + } else { + if (l->cur_token) ahb_swapchain_layer_released(l->cur_token, -1); + api.release(l->sc); + } + wnc_log("layer", "%s: SurfaceControl retired (window %p)", l->name, (void *)l->win); + l->sc = NULL; l->win = NULL; + l->shown = 0; l->cur_slot = -1; l->cur_token = NULL; l->geo_valid = 0; + l->fps_applied = -1.0f; /* the next SurfaceControl carries no vote until it is re-applied */ + l->recreate_pending = 0; /* a fresh SurfaceControl is coming anyway */ + l->ds_applied = -1; l->md_applied = 0; l->hr_applied = -1.0f; /* ...and no colour tag either */ +} + +/* Wait (bounded) for SurfaceFlinger to finish with every pool buffer of every layer, then free + * the pools. */ +static void drain_and_free_pools(void) { + int64_t deadline = now_ns() + 300000000LL; + for (;;) { + pthread_mutex_lock(&g_lock); + int pending = g_pending_cb; + pthread_mutex_unlock(&g_lock); + if (!pending || now_ns() > deadline) break; + usleep(2000); + } + pthread_mutex_lock(&g_lock); + for (int i = 0; i < SC_LAYER_COUNT; i++) { + for (int j = 0; j < POOL_MAX; j++) { + struct slot *s = &g_layers[i].slots[j]; + if (s->release_fd >= 0) { struct pollfd p = {.fd = s->release_fd, .events = POLLIN}; poll(&p, 1, 100); close(s->release_fd); } + if (s->img) vkp_image_destroy(s->img); + if (s->ahb) AHardwareBuffer_release(s->ahb); + memset(s, 0, sizeof(*s)); + s->release_fd = -1; + } + g_layers[i].first_logged = 0; + g_layers[i].logged_w = g_layers[i].logged_h = 0; /* the next pool's layout is logged again */ + } + pthread_mutex_unlock(&g_lock); +} + +/* Native-handle sniff, the same one Mesa's u_gralloc fallback uses (u_gralloc_fallback.c): a QTI + * gralloc private_handle_t carries the magic 'gmsm' as its first int and the UBWC flag + * (PRIV_FLAGS_UBWC_ALIGNED, 0x08000000) in the next one. Returns 0 when the layout is unknown. */ +static int sniff_modifier(const struct wnc_native_handle *h, uint64_t *mod) { + const uint32_t gmsm = ('g' << 24) | ('m' << 16) | ('s' << 8) | 'm'; + if (!h || h->numFds < 1 || h->numInts < 2) return 0; + if ((uint32_t)h->data[h->numFds] != gmsm) return 0; + *mod = (h->data[h->numFds + 1] & 0x08000000) ? MOD_QCOM_COMPRESSED : MOD_LINEAR; + return 1; +} + +static int g_alloc_failed; /* set by alloc_slot when gralloc or the import refused a buffer */ + +/* How a layer's pool buffers are asked of gralloc, best first; a layer only ever moves down. + * TIER_UBWC GPU render target + sampled + composer overlay + AHARDWAREBUFFER_USAGE_VENDOR_0 (bit 28): + * QTI gralloc's GRALLOC_USAGE_PRIVATE_ALLOC_UBWC. Without it QTI gralloc allocates LINEAR even + * for a GPU render target (gr_allocator.cpp IsUBwcEnabled: an explicit UBWC format, the + * private UBWC bit or CLIENT_TARGET, and no CPU bit). Whether gralloc really compressed the + * buffer is read back from its handle (sniff_modifier), never assumed. + * TIER_PLAIN the same without the vendor bit (a gralloc that refuses unknown bits; what we asked before). + * TIER_LINEAR plus a CPU bit, so the layout is known to be linear even when the handle is unreadable. */ +enum { TIER_UBWC, TIER_PLAIN, TIER_LINEAR }; +#define WNC_AHB_USAGE_VENDOR_UBWC (1ULL << 28) /* AHARDWAREBUFFER_USAGE_VENDOR_0 */ + +static const char *tier_name(int tier) { + return tier == TIER_UBWC ? "asked for UBWC" : tier == TIER_PLAIN ? "no UBWC request" : "linear by request (CPU bit)"; +} + +static int alloc_slot(struct layer *l, struct slot *s, int w, int h, uint32_t fmt) { + g_alloc_failed = 0; + while (l->alloc_tier <= TIER_LINEAR) { + const int tier = l->alloc_tier; + AHardwareBuffer_Desc d = { + .width = (uint32_t)w, .height = (uint32_t)h, .layers = 1, + .format = fmt, + /* GPU render target (the blit writes it) + sampled (SurfaceFlinger's GPU fallback reads it) + + * composer overlay (the display scans it out). See the tiers above for the rest. */ + .usage = AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | + AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | + (tier == TIER_UBWC ? WNC_AHB_USAGE_VENDOR_UBWC : 0) | + (tier == TIER_LINEAR ? AHARDWAREBUFFER_USAGE_CPU_READ_RARELY : 0)}; + AHardwareBuffer *ahb = NULL; + if (AHardwareBuffer_allocate(&d, &ahb) != 0 || !ahb) { + if (tier < TIER_LINEAR) { + wnc_log("layer", "%s: gralloc refused a %dx%d pool buffer (%s): trying %s", l->name, w, h, + tier_name(tier), tier_name(tier + 1)); + l->alloc_tier = tier + 1; + continue; + } + wnc_log("error", "layer: %s: AHardwareBuffer_allocate %dx%d (format %#x) failed", l->name, w, h, fmt); + g_alloc_failed = 1; + return -1; + } + AHardwareBuffer_Desc got; AHardwareBuffer_describe(ahb, &got); + const struct wnc_native_handle *nh = api.getNativeHandle(ahb); + uint64_t mod = MOD_LINEAR; + if (!sniff_modifier(nh, &mod)) { + /* Not a handle we can read: the only layout we can assume is linear, and only if the buffer + * was asked for with a CPU bit (gralloc must not have compressed it). */ + if (tier < TIER_LINEAR) { + AHardwareBuffer_release(ahb); + l->alloc_tier = TIER_LINEAR; + wnc_log("layer", "%s: gralloc handle layout unknown (%d fds, %d ints): using linear pool buffers", + l->name, nh ? nh->numFds : -1, nh ? nh->numInts : -1); + continue; + } + mod = MOD_LINEAR; + } + int fd = (nh && nh->numFds > 0) ? nh->data[0] : -1; + /* The test create: vkp_image_import_dmabuf creates the image with gralloc's explicit layout, so a + * layout the compositor's Turnip refuses falls back a tier instead of drawing garbage. */ + struct vkp_image *img = fd >= 0 ? vkp_image_import_dmabuf(fd, fmt == AHB_RGB10A2 ? DRM_ABGR2101010 : DRM_ABGR8888, + mod, w, h, got.stride * 4, 0, 1) : NULL; + if (!img) { + wnc_log("layer", "%s: import of a %s %dx%d pool buffer (stride %u px) into the compositor's Turnip failed", + l->name, mod == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", w, h, got.stride); + AHardwareBuffer_release(ahb); + if (tier < TIER_LINEAR) { l->alloc_tier = tier + 1; continue; } + g_alloc_failed = 1; + return -1; + } + s->ahb = ahb; s->img = img; s->w = w; s->h = h; s->fmt = fmt; s->release_fd = -1; s->busy = 0; + l->pool_modifier = mod; + if (l->logged_w != w || l->logged_h != h || l->logged_fmt != fmt || l->logged_mod != mod || + l->logged_tier != tier) { + l->logged_w = w; l->logged_h = h; l->logged_fmt = fmt; l->logged_mod = mod; l->logged_tier = tier; + wnc_log("layer", "%s: pool buffers %dx%d%s: %s (%s), stride %u px, up to %d buffers (gralloc handle %d fds / %d ints)", + l->name, w, h, fmt == AHB_RGB10A2 ? " 10-bit RGBA1010102" : "", + mod == MOD_QCOM_COMPRESSED ? "UBWC (QCOM_COMPRESSED)" : "linear", tier_name(tier), + got.stride, l->pool_n, nh ? nh->numFds : -1, nh ? nh->numInts : -1); + } + return 0; + } + g_alloc_failed = 1; + return -1; +} + +/* 1 when a sync_file has signalled (the display has stopped reading that buffer). */ +static int fence_signalled(int fd) { + struct pollfd p = {.fd = fd, .events = POLLIN}; + int r; + do { r = poll(&p, 1, 0); } while (r < 0 && errno == EINTR); + return r > 0; +} + +/* A slot SurfaceFlinger has handed back, holding a w x h buffer of AHB format fmt. -1 = none free (or + * the buffer could not be made). A free slot whose release fence has already signalled is preferred; + * otherwise the fence of the first free one is handed back in *wait_fd for the copy to wait on ON THE + * GPU (vkp_blit_image / vkp_pass_copy_to consume it), so the compositor thread no longer blocks here. + * Only a driver without VK_KHR_external_semaphore_fd still waits on the CPU, bounded, as before. + * *wait_fd is -1 whenever nothing needs waiting for (and on every failure). */ +static int take_free_slot(struct layer *l, int w, int h, uint32_t fmt, int *wait_fd) { + int idx = -1, pending = -1, fd = -1; + *wait_fd = -1; + pthread_mutex_lock(&g_lock); + for (int i = 0; i < l->pool_n; i++) { + if (i == l->cur_slot || l->slots[i].busy) continue; + const int rf = l->slots[i].release_fd; + if (rf < 0 || fence_signalled(rf)) { idx = i; break; } + if (pending < 0) pending = i; + } + if (idx < 0) idx = pending; + if (idx >= 0) { fd = l->slots[idx].release_fd; l->slots[idx].release_fd = -1; } + pthread_mutex_unlock(&g_lock); + if (idx < 0) return -1; + if (fd >= 0 && fence_signalled(fd)) { close(fd); fd = -1; } + struct slot *s = &l->slots[idx]; + if (s->ahb && (s->w != w || s->h != h || s->fmt != fmt)) { + /* Size or format change: this slot is free, so it can be replaced at once (the display keeps its + * own reference to the old buffer for as long as it reads it; the new one needs no wait). */ + if (fd >= 0) { close(fd); fd = -1; } + vkp_image_destroy(s->img); AHardwareBuffer_release(s->ahb); + memset(s, 0, sizeof(*s)); s->release_fd = -1; + } + if (fd >= 0 && !vkp_can_wait_sync_fd()) { + struct pollfd p = {.fd = fd, .events = POLLIN}; + int r; + do { r = poll(&p, 1, 100); } while (r < 0 && errno == EINTR); + if (r == 0) { + /* Still read by the display: keep its fence with it (the next use waits again), drop this frame. */ + pthread_mutex_lock(&g_lock); + if (s->release_fd < 0) s->release_fd = fd; else close(fd); + pthread_mutex_unlock(&g_lock); + return -1; + } + close(fd); + fd = -1; + } + if (!s->ahb && alloc_slot(l, s, w, h, fmt) != 0) { + if (fd >= 0) close(fd); + return -1; + } + *wait_fd = fd; + return idx; +} + +/* ---- public --------------------------------------------------------------------------------- */ + +int sc_layer_available(void) { return load_api() == 0; } + +void sc_layer_probe_dmabuf_fd(int fd) { + static int done; + if (done || fd < 0) return; + done = 1; + struct { uint32_t flags; int32_t fd; } exp = {.flags = 1u /* DMA_BUF_SYNC_READ */, .fd = -1}; + /* DMA_BUF_IOCTL_EXPORT_SYNC_FILE = _IOWR('b', 2, struct dma_buf_export_sync_file) */ + if (ioctl(fd, _IOWR('b', 2, exp), &exp) == 0) { + wnc_log("layer", "kernel exports sync_file fences from the game's dma-buf: zero-copy acquire fences can come from the buffer itself"); + if (exp.fd >= 0) close(exp.fd); + } else { + wnc_log("layer", "DMA_BUF_IOCTL_EXPORT_SYNC_FILE on the game's dma-buf failed (%s): acquire fences must be exported by the game's driver (sync_fd) instead", + strerror(errno)); + } +} + +/* One layer's SurfaceControl on the current output window (created on first use, re-created after + * a window change). -1 = no window / no API. */ +static int ensure_sc(struct layer *l) { + if (load_api() != 0) return -1; + ANativeWindow *win = vkp_window(); + if (!win) return -1; + if (l->sc && l->win != win) retire_sc(l); + if (!l->sc) { + l->sc = api.createFromWindow(win, l->name); + if (!l->sc) { wnc_log("error", "layer: ASurfaceControl_createFromWindow(%s) failed", l->name); return -1; } + l->win = win; + l->ds_applied = -1; l->md_applied = 0; l->hr_applied = -1.0f; + ASurfaceTransaction *tx = api.txCreate(); + if (tx) { + api.setZOrder(tx, l->sc, l->z); + api.setVisibility(tx, l->sc, ASC_VISIBILITY_HIDE); + apply_frame_rate(tx, l); + api.txApply(tx); api.txDelete(tx); + } + wnc_log("layer", "SurfaceControl \"%s\" created as a child of the screen surface (z=%d)", l->name, (int)l->z); + } + return 0; +} + +/* Geometry of a w x h buffer covering the scene, through the same mapping the blit path and the + * app's touch mapping use. 1 = r filled, 0 = nothing of it is on screen, -1 = no mapping. */ +static int layer_geometry(int w, int h, int scene_w, int scene_h, int r[8]) { + if (vkp_update_map(scene_w, scene_h) != 0) return -1; + return vkp_map_rect(w, h, scene_w, scene_h, r) ? 1 : 0; +} + +/* The GAME layer's last placement, kept across SurfaceControl retires and swaps (l->geo_valid is + * per-SurfaceControl and is cleared by both). This is the src -> dst the display actually works + * with, and it is what decides whether a second layer is affordable โ€” see overlay_affordable(). */ +static ARect g_game_src, g_game_dst; +static int g_game_geo_known; + +static void apply_geometry(ASurfaceTransaction *tx, struct layer *l, const int r[8]) { + ARect srcR = {r[0], r[1], r[2], r[3]}, dstR = {r[4], r[5], r[6], r[7]}; + if (l == &g_layers[SC_LAYER_GAME]) { g_game_src = srcR; g_game_dst = dstR; g_game_geo_known = 1; } + if (!l->geo_valid || memcmp(&srcR, &l->geo_src, sizeof(srcR)) || memcmp(&dstR, &l->geo_dst, sizeof(dstR))) { + api.setGeometry(tx, l->sc, &srcR, &dstR, 0 /* no transform: the DPU scales, never rotates */); + l->geo_src = srcR; l->geo_dst = dstR; l->geo_valid = 1; + char cov[96] = ""; + if (l->ds_applied > 0) coverage_text(l, cov, sizeof(cov)); + wnc_log("layer", "%s geometry: buffer %d,%d-%d,%d -> screen %d,%d-%d,%d%s%s%s", l->name, + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], cov[0] ? " (HDR layer " : "", cov, cov[0] ? ")" : ""); + } +} + +/* Add the vote to tx when it differs from what this layer carries (compositor thread). Only the + * layer the game presents on carries the game's rate; every other layer is voted 0, so a slow + * overlay window can never hold the panel at the game's cadence (or the game's at the overlay's). */ +static void apply_frame_rate(ASurfaceTransaction *tx, struct layer *l) { + float want = l->votes_rate ? atomic_load(&g_fps_want) : 0.0f; + if (want == l->fps_applied) return; + if (!api.setFrameRateStrategy && !api.setFrameRate) return; + if (api.setFrameRateStrategy) { + /* ALWAYS, like the app's own surface vote: the seamless-only default is ignored by a panel + * sitting at its peak rate, which is exactly the case we need to move. */ + api.setFrameRateStrategy(tx, l->sc, want, ASC_FRAME_RATE_COMPAT_DEFAULT, ASC_CHANGE_FRAME_RATE_ALWAYS); + } else { + api.setFrameRate(tx, l->sc, want, ASC_FRAME_RATE_COMPAT_DEFAULT); + } + int first = l->fps_applied < 0.0f; + l->fps_applied = want; + if (want > 0.0f) wnc_log("layer", "display frame-rate vote on %s: %.2f Hz", l->name, want); + else if (!first) wnc_log("layer", "display frame-rate vote on %s cleared (panel runs free)", l->name); +} + +/* ---- colour (HDR, color_mgmt.h) ------------------------------------------------------------- + * The layer is told what its buffer's pixels MEAN โ€” the dataspace (BT2020_PQ for an HDR10 frame) and + * the game's mastering / content-light metadata โ€” in the same transaction as the buffer, so the + * display never shows an HDR frame decoded as sRGB or the other way round. A layer that has never been + * tagged is never touched: sessions without an HDR description make no colour call at all. */ +/* How much of the screen the layer's picture covers ("covers 80% of the screen (1920x1080 of 2400x1080)"): + * some phones only switch to HDR for an HDR layer above a minimum area. Empty when unknown. */ +static void coverage_text(const struct layer *l, char *out, size_t n) { + int ow = 0, oh = 0; + vkp_output_size(&ow, &oh); + if (!l->geo_valid || ow <= 0 || oh <= 0) { if (n) out[0] = 0; return; } + int w = l->geo_dst.right - l->geo_dst.left, h = l->geo_dst.bottom - l->geo_dst.top; + if (w < 0) w = 0; + if (h < 0) h = 0; + const double pct = 100.0 * ((double)w * h) / ((double)ow * oh); + snprintf(out, n, "covers %.0f%% of the screen (%dx%d of %dx%d)", pct > 100.0 ? 100.0 : pct, w, h, ow, oh); +} + +/* API 35: ask the display for HDR headroom explicitly while the layer carries an HDR frame, and clear the + * request (0 = no preference) when it stops. Only on change; a layer never tagged is never touched. */ +static void apply_headroom(ASurfaceTransaction *tx, struct layer *l, const struct wnc_color *c) { + static int said_missing; + if (!api.setDesiredHdrHeadroom) { + if (c && c->dataspace && !said_missing) { + said_missing = 1; + wnc_color_note_headroom_request(-1.0f); + wnc_log("color", "HDR headroom request not available on %s (Android < 15 has no " + "ASurfaceTransaction_setDesiredHdrHeadroom): the layer relies on Android's default", l->name); + } + return; + } + const float want = (c && c->dataspace) ? wnc_color_desired_headroom(c, NULL, 0) : 0.0f; + if (l->hr_applied < 0.0f && want == 0.0f && !(c && c->dataspace)) return; /* never asked: leave it be */ + if (l->hr_applied >= 0.0f && fabsf(want - l->hr_applied) < 0.005f) return; + char why[200] = ""; + if (c && c->dataspace) wnc_color_desired_headroom(c, why, sizeof(why)); + api.setDesiredHdrHeadroom(tx, l->sc, want); + const int first = l->hr_applied < 0.0f; + l->hr_applied = want; + wnc_color_note_headroom_request(want); + if (want > 0.0f) + wnc_log("color", "requested HDR headroom %.1fx on %s (%s)", want, l->name, why); + else if (c && c->dataspace) + wnc_log("color", "no HDR headroom requested on %s: %s", l->name, why); + else if (!first) + wnc_log("color", "HDR headroom request on %s cleared (no preference): the frame on the layer is not HDR", l->name); +} + +static void apply_colour(ASurfaceTransaction *tx, struct layer *l, const struct wnc_color *c) { + int32_t want = c ? c->dataspace : WNC_ADATASPACE_UNKNOWN; + uint32_t md = (c && c->dataspace) ? c->identity : 0; + if (!api.setBufferDataSpace) return; + if (l->ds_applied < 0 && want == WNC_ADATASPACE_UNKNOWN) return; /* never tagged: leave it be */ + apply_headroom(tx, l, want ? c : NULL); + if (want == l->ds_applied && md == l->md_applied) return; + api.setBufferDataSpace(tx, l->sc, want); + struct wnc_hdr_smpte2086 st; + struct wnc_hdr_cta861_3 cta; + int st_on = c && c->dataspace && c->has_st2086, cta_on = c && c->dataspace && c->has_cta861; + if (st_on) { + st = (struct wnc_hdr_smpte2086){ + .displayPrimaryRed = {c->red[0], c->red[1]}, .displayPrimaryGreen = {c->green[0], c->green[1]}, + .displayPrimaryBlue = {c->blue[0], c->blue[1]}, .whitePoint = {c->white[0], c->white[1]}, + .maxLuminance = c->max_lum, .minLuminance = c->min_lum}; + } + if (cta_on) cta = (struct wnc_hdr_cta861_3){.maxContentLightLevel = c->max_cll, .maxFrameAverageLightLevel = c->max_fall}; + /* NULL clears what a previous description set: metadata never outlives the frame it belongs to. */ + if (api.setHdrMetadata_smpte2086) api.setHdrMetadata_smpte2086(tx, l->sc, st_on ? &st : NULL); + if (api.setHdrMetadata_cta861_3) api.setHdrMetadata_cta861_3(tx, l->sc, cta_on ? &cta : NULL); + if (want) { + char cov[96]; + coverage_text(l, cov, sizeof(cov)); + wnc_log("color", "%s: dataspace %s (%#x) set on the display layer for image description #%u%s%s; SMPTE 2086 " + "%s, CTA-861.3 %s [%s]", l->name, want == WNC_ADATASPACE_BT2020_PQ ? "BT2020_PQ" : "HDR", + (unsigned)want, c->identity, cov[0] ? ", " : "", cov, + st_on ? (api.setHdrMetadata_smpte2086 ? "sent" : "not supported by this Android") : "none given", + cta_on ? (api.setHdrMetadata_cta861_3 ? "sent" : "not supported by this Android") : "none given", + c->text); + } + else + wnc_log("color", "%s: dataspace back to UNKNOWN (sRGB), HDR metadata cleared - the frame on the layer is " + "not an HDR frame", l->name); + l->ds_applied = want; + l->md_applied = md; +} + +int sc_layer_can_tag_hdr(void) { return load_api() == 0 && api.setBufferDataSpace != NULL; } + +void sc_layer_hdr_symbols(char *out, size_t size) { + if (load_api() != 0) { snprintf(out, size, "no display layers"); return; } + snprintf(out, size, "setBufferDataSpace %s, setHdrMetadata_smpte2086 %s, setHdrMetadata_cta861_3 %s, " + "setDesiredHdrHeadroom %s", api.setBufferDataSpace ? "yes" : "NO", + api.setHdrMetadata_smpte2086 ? "yes" : "no", api.setHdrMetadata_cta861_3 ? "yes" : "no", + api.setDesiredHdrHeadroom ? "yes (asked for per HDR frame: content peak / SDR white)" : "no (Android < 15)"); +} + +/* Once, when a second layer first goes up: HWC only composes a few layers before SurfaceFlinger + * falls back to GPU client composition, so the count is deliberately capped and said out loud. */ +static void log_layer_count(void) { + if (g_two_logged) return; + g_two_logged = 1; + wnc_log("layer", "%d display layers in use: \"%s\" (z=%d) and \"%s\" (z=%d) above it, both children of the " + "screen surface; the app's own views (drawer, HUD, pointer) stay above both. %d is the cap - more " + "would push SurfaceFlinger into GPU client composition", + SC_LAYER_COUNT, g_layers[SC_LAYER_GAME].name, (int)g_layers[SC_LAYER_GAME].z, + g_layers[SC_LAYER_OVERLAY].name, (int)g_layers[SC_LAYER_OVERLAY].z, SC_LAYER_COUNT); +} + +/* ---- composition recovery --------------------------------------------------------------------- + * When the overlay is retired and the scene is one fullscreen window again, the game layer is + * marked for a swap and the swap rides the NEXT frame: a fresh SurfaceControl is created here, the + * frame is put on it, and the old one is hidden and unparented IN THE SAME TRANSACTION. Because + * SurfaceFlinger applies a transaction atomically there is never a composited frame with neither + * layer on it โ€” no black frame, and no dropped frame beyond the one layer creation. The outgoing + * buffer is released through the OLD SurfaceControl's callback, which also releases it. + * + * โš ๏ธ MEASURED 2026-09-14, and the news is bad: on the Pocket FIT this does NOT bring hardware + * composition back. The swap fires 6 ms after the overlay goes, SurfaceFlinger really does hand out + * a new layer (its id changes), the game never drops a frame โ€” and the composer still reports + * `DEVICE/CLIENT` 24 s later. Neither does dropping the layer path entirely and re-creating the + * SurfaceControl after a gap. The ONE thing that clears it is HOME + resume, which re-creates the + * app's whole window and SurfaceView (`VRI[XServerDisplayActivity]#0` becomes `#4`) โ€” so the sticky + * client-composition state belongs to the PARENT surface (or the display), not to this child layer. + * The phase-4 note that "only re-creating the GAME layer's SurfaceControl clears it" was inferred + * from HOME + resume and is wrong; see sc_layer.h. The swap is kept because it is free and correct + * and the mechanism may differ on hardware that does not rotate every layer โ€” but do not claim it + * restores DEVICE composition, and do not log as if it did. + * + * Returns the old SurfaceControl (the caller must add retire_ops for it to the same transaction and + * name it in add_complete_on), or NULL when no swap is due. Compositor thread. */ +static ASurfaceControl *swap_sc_begin(struct layer *l) { + if (!l->recreate_pending) return NULL; + l->recreate_pending = 0; + if (!l->sc) return NULL; /* nothing to swap; ensure_sc made a fresh one already */ + ANativeWindow *win = vkp_window(); + if (!win || win != l->win) return NULL; /* the window changed: ensure_sc re-creates it anyway */ + ASurfaceControl *fresh = api.createFromWindow(win, l->name); + if (!fresh) { + wnc_log("error", "layer: %s: composition recovery could not create a new SurfaceControl", l->name); + return NULL; + } + ASurfaceControl *old = l->sc; + l->sc = fresh; + /* These describe the SurfaceControl, not the layer: the new one carries none of them yet. + * cur_slot / cur_token are deliberately NOT cleared โ€” they name the buffer still on the OLD + * SurfaceControl, which is what the caller passes to add_complete_on as the one being replaced. */ + l->shown = 0; + l->geo_valid = 0; + l->fps_applied = -1.0f; + l->ds_applied = -1; l->md_applied = 0; l->hr_applied = -1.0f; + wnc_log("layer", "composition recovery: %s got a fresh SurfaceControl now that nothing is above " + "the game (measured on this panel: hardware composition does NOT return from this alone)", l->name); + return old; +} + +/* The transaction that puts pool slot `idx` of layer `l` on screen at `r`, as `color` (NULL = no + * description). 0 = applied. */ +static int present_slot(struct layer *l, int idx, const int r[8], const struct wnc_color *color) { + struct slot *s = &l->slots[idx]; + ASurfaceTransaction *tx = api.txCreate(); + if (!tx) return -1; + ASurfaceControl *old = swap_sc_begin(l); /* this frame carries the recovery swap, if one is due */ + if (old) api.setZOrder(tx, l->sc, l->z); + /* The blit was waited for on the CPU, so no acquire fence is needed (-1). */ + api.setBuffer(tx, l->sc, s->ahb, -1); + /* The compositor composes with blits, which overwrite: nothing is ever alpha-blended on the + * copy path either, so every layer is opaque and the picture matches it pixel for pixel. */ + api.setBufferTransparency(tx, l->sc, ASC_TRANSPARENCY_OPAQUE); + apply_geometry(tx, l, r); + apply_frame_rate(tx, l); + apply_colour(tx, l, color); + if (!l->shown) api.setVisibility(tx, l->sc, ASC_VISIBILITY_SHOW); + if (old) retire_ops(tx, old); + if (add_complete_on(tx, l, old ? old : l->sc, l->cur_slot, l->cur_token, old ? 1 : 0) != 0 && old) + api.release(old); /* no callback to retire it from (out of memory): let it go here */ + pthread_mutex_lock(&g_lock); + s->busy = 1; + pthread_mutex_unlock(&g_lock); + api.txApply(tx); + api.txDelete(tx); + l->cur_slot = idx; + l->cur_token = NULL; + if (!l->shown) { l->shown = 1; wnc_log("layer", "%s: layer shown", l->name); } + g_stat_layer_frames++; + return 0; +} + +/* No free buffer this frame: the display still holds all of them. Counted for the 10 s perf line, and + * logged at most every 30 s with the number of frames dropped since the last line. */ +static unsigned g_pool_drops; +static void log_drop(struct layer *l) { + g_pool_drops++; + l->drops_unlogged++; + int64_t t = now_ns(); + if (!l->drop_logged_ns || t - l->drop_logged_ns > 30000000000LL) { + wnc_log("layer", "%s: no free layer buffer (display still holds all %d): %u frame%s dropped%s", l->name, + l->pool_n, l->drops_unlogged, l->drops_unlogged == 1 ? "" : "s", + l->drop_logged_ns ? " since the last such line" : ""); + l->drop_logged_ns = t; + l->drops_unlogged = 0; + } +} + +unsigned sc_layer_drops_take(void) { + unsigned n = g_pool_drops; + g_pool_drops = 0; + return n; +} + +int sc_layer_present_ahb(AHardwareBuffer *ahb, int w, int h, int acquire_fd, void *token, int scene_w, int scene_h, + const struct wnc_color *color, uint32_t ahb_format) { + struct layer *l = layer_of(SC_LAYER_GAME); + int r[8]; + if (!ahb || !token || ensure_sc(l) != 0) goto unavailable; + int g = layer_geometry(w, h, scene_w, scene_h, r); + if (g < 0) goto unavailable; + if (g == 0) { if (acquire_fd >= 0) close(acquire_fd); sc_layer_hide(); return 1; } + if (token == l->cur_token && l->shown && !l->recreate_pending) { + /* The same frame again (the scene was redrawn for another reason): the display already + * has it; only the placement may have changed (or, rarely, its description). */ + if (acquire_fd >= 0) close(acquire_fd); + ASurfaceTransaction *tx = api.txCreate(); + if (tx) { + apply_geometry(tx, l, r); apply_frame_rate(tx, l); apply_colour(tx, l, color); + api.txApply(tx); api.txDelete(tx); + } + return 0; + } + ASurfaceTransaction *tx = api.txCreate(); + if (!tx) goto unavailable; + ASurfaceControl *old = swap_sc_begin(l); /* this frame carries the recovery swap, if one is due */ + if (old) api.setZOrder(tx, l->sc, l->z); + api.setBuffer(tx, l->sc, ahb, acquire_fd); /* the transaction owns the fence */ + api.setBufferTransparency(tx, l->sc, ASC_TRANSPARENCY_OPAQUE); + apply_geometry(tx, l, r); + apply_frame_rate(tx, l); + apply_colour(tx, l, color); + if (!l->shown) api.setVisibility(tx, l->sc, ASC_VISIBILITY_SHOW); + if (old) retire_ops(tx, old); + /* Same buffer, new SurfaceControl: it is NOT free, so no release is reported for it โ€” the swap + * moved it rather than taking it off screen. */ + if (add_complete_on(tx, l, old ? old : l->sc, l->cur_slot, + l->cur_token == token ? NULL : l->cur_token, old ? 1 : 0) != 0) { + api.txDelete(tx); /* the fence went with the transaction */ + if (old) api.release(old); + l->ds_applied = -1; l->md_applied = 0; l->hr_applied = -1.0f; /* the colour tag went with it too: re-send it next time */ + return -1; + } + api.txApply(tx); + api.txDelete(tx); + l->cur_slot = -1; + l->cur_token = token; + if (!l->shown) { l->shown = 1; wnc_log("layer", "%s: layer shown", l->name); } + if (!l->first_logged) { l->first_logged = 1; vkp_signal_first_frame(); } + if (color && color->dataspace) wnc_color_frame_shown(color, WNC_HDR_ZERO_COPY, ahb_format); + return 0; +unavailable: + if (acquire_fd >= 0) close(acquire_fd); + return -1; +} + +int sc_layer_present(struct vkp_image *src, int scene_w, int scene_h, const struct wnc_color *color) { + struct layer *l = layer_of(SC_LAYER_GAME); + if (!src || ensure_sc(l) != 0) return -1; + int sw = vkp_image_width(src), sh = vkp_image_height(src); + int r[8]; + int g = layer_geometry(sw, sh, scene_w, scene_h, r); + if (g < 0) return -1; + if (g == 0) { sc_layer_hide(); return 0; } /* nothing of it is on screen */ + + int wait_fd; + int idx = take_free_slot(l, sw, sh, AHB_RGBA8, &wait_fd); + if (idx < 0) { log_drop(l); return 0; } + if (vkp_blit_image(src, l->slots[idx].img, wait_fd) != 0) return -1; + if (present_slot(l, idx, r, color) != 0) return -1; + if (color && color->dataspace) wnc_color_frame_shown(color, WNC_HDR_LAYER_COPY, AHB_RGBA8); + if (!l->first_logged) { + l->first_logged = 1; + wnc_log("layer", "presenting %dx%d game frames on their own SurfaceControl layer (%s pool, %d buffers); " + "HUD and pointer stay Android views above it", sw, sh, + l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", l->pool_n); + vkp_signal_first_frame(); + } + return 0; +} + +int sc_layer_present_pass(const struct vkp_draw *draws, int n, int scene_w, int scene_h) { + struct layer *l = layer_of(SC_LAYER_GAME); + int rw = 0, rh = 0, r[8]; + if (!draws || n <= 0 || ensure_sc(l) != 0) return -1; + if (vkp_update_map(scene_w, scene_h) != 0) return -1; + /* Compose + run the effects chain first: the chain's result size (a scaling mode resizes to + * the scene's mapped output size) decides how big the layer buffer has to be. */ + if (vkp_pass_begin(scene_w, scene_h, draws, n, &rw, &rh) != 0) return -1; + if (!vkp_map_rect(rw, rh, scene_w, scene_h, r)) { vkp_pass_abort(); sc_layer_hide(); return 0; } + int wait_fd; + int idx = take_free_slot(l, rw, rh, AHB_RGBA8, &wait_fd); + if (idx < 0) { vkp_pass_abort(); log_drop(l); return 0; } + if (vkp_pass_copy_to(l->slots[idx].img, wait_fd) != 0) return -1; + if (present_slot(l, idx, r, NULL) != 0) return -1; /* the effects chain's result is 8-bit sRGB */ + if (!l->first_logged) { + l->first_logged = 1; + wnc_log("layer", "presenting %dx%d frames on their own SurfaceControl layer (%s pool, %d buffers); " + "HUD and pointer stay Android views above it", rw, rh, + l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", l->pool_n); + vkp_signal_first_frame(); + } + return 0; +} + +/* ---- the HDR picture (hdr_compose.h) ------------------------------------------------------------ + * An HDR game that cannot be alone on its layer: the WHOLE scene, composed into one 10-bit PQ BT.2020 + * picture with the effects applied, goes onto the game layer - one layer, tagged BT2020_PQ with the + * game's metadata, so the display composes it as HDR (and a window above the game never needs the + * second layer that costs this panel its hardware composition). 10-bit buffers; if gralloc or the + * import refuses those, 8-bit ones carry the same tagged picture (colours right, precision less). + * With the drawer's HDR output switch off (hf->tonemap) the same picture is composed tone-mapped to + * sRGB instead and goes on the ordinary 8-bit buffers, UNtagged - an SDR frame like any other. */ +static uint32_t g_hdr_pool_fmt = AHB_RGB10A2; + +int sc_layer_present_hdr_scene(const struct vkp_draw *draws, int n, const struct vkp_hdr_frame *hf, + int scene_w, int scene_h, const struct wnc_color *color) { + struct layer *l = layer_of(SC_LAYER_GAME); + int rw = 0, rh = 0, r[8]; + if (!draws || n <= 0 || !hf || ensure_sc(l) != 0) return -1; + if (vkp_update_map(scene_w, scene_h) != 0) return -1; + if (vkp_pass_begin_hdr(scene_w, scene_h, draws, n, hf, &rw, &rh) != 0) return -1; + if (!vkp_map_rect(rw, rh, scene_w, scene_h, r)) { vkp_pass_abort(); sc_layer_hide(); return 0; } + const int tm = hf->tonemap; + const uint32_t fmt = tm ? AHB_RGBA8 : g_hdr_pool_fmt; + g_alloc_failed = 0; + int wait_fd; + int idx = take_free_slot(l, rw, rh, fmt, &wait_fd); + if (idx < 0 && g_alloc_failed && fmt == AHB_RGB10A2) { + g_hdr_pool_fmt = AHB_RGBA8; + wnc_log("color", "%s: this device will not make a 10-bit layer buffer (RGBA1010102): the HDR picture goes " + "on 8-bit buffers instead (still tagged BT2020_PQ; less precision)", l->name); + idx = take_free_slot(l, rw, rh, g_hdr_pool_fmt, &wait_fd); + } + if (idx < 0) { vkp_pass_abort(); log_drop(l); return 0; } + if (vkp_pass_copy_to(l->slots[idx].img, wait_fd) != 0) return -1; + if (present_slot(l, idx, r, tm ? NULL : color) != 0) return -1; /* tone-mapped = plain sRGB: no tag */ + if (color && color->dataspace) + wnc_color_frame_shown(color, tm ? WNC_HDR_TONEMAPPED : WNC_HDR_COMPOSED, tm ? AHB_RGBA8 : g_hdr_pool_fmt); + static int said = -1; + if (said != tm) { + said = tm; + if (tm) + wnc_log("color", "tone-mapped picture on the game's display layer: %dx%d, %s 8-bit buffers, untagged " + "(sRGB) - HDR output is switched off", rw, rh, + l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear"); + else { + char cov[96]; + coverage_text(l, cov, sizeof(cov)); + wnc_log("color", "HDR picture on its own display layer: %dx%d, %s %s buffers, tagged %s%s%s", rw, rh, + l->pool_modifier == MOD_QCOM_COMPRESSED ? "UBWC" : "linear", + g_hdr_pool_fmt == AHB_RGB10A2 ? "10-bit" : "8-bit", + color && color->dataspace == WNC_ADATASPACE_BT2020_PQ ? "BT2020_PQ" : "HDR", + cov[0] ? ", " : "", cov); + } + } + if (!l->first_logged) { l->first_logged = 1; vkp_signal_first_frame(); } + return 0; +} + +/* ---- is a SECOND display layer affordable on this display? ----------------------------------- + * MEASURED on the Pocket FIT (2026-09-14, three times): while a second layer is up, this hardware + * composer hands the WHOLE frame back to the GPU (`DEVICE/CLIENT`), and nothing short of re-creating + * the app's window brings it back โ€” so on this display the overlay layer costs more than it saves, + * every time, for the rest of the session. Where it costs nothing it is still the better path (the + * game keeps copy-free frames with a window on top), so this is a GATE, not a removal. + * + * The gate is read from what the layer path actually knows, never from a device or panel list: + * - rotation: vkp_surface_rotation_degrees(), i.e. VkSurfaceCapabilitiesKHR::currentTransform โ€” + * what the presentation engine says it does to every layer we hand it; + * - scale: the GAME layer's own src -> dst rectangles, the ones passed to setGeometry. + * Rotated AND scaled is the combination that was measured to cost composition (the DPU's rotator + * has to take a scaled source); either alone, or neither, is left as it was. + * + * Why not just measure the composition type after raising the layer and back out if it comes back + * CLIENT โ€” which would beat predicting? Because no app can read it. The composition type lives in + * the Composer HAL; ASurfaceTransactionStats exposes latch time and fences and nothing else, and + * the only place the value is published is `dumpsys android.hardware.graphics.composer3.IComposer`, + * which needs android.permission.DUMP and string-parsing. It would also arrive at least a frame + * late, so backing out would itself be the visible change we are avoiding. Hence the rule. */ +static int overlay_affordable(int *deg, int sw[2], int dw[2]) { + *deg = vkp_surface_rotation_degrees(); + sw[0] = sw[1] = dw[0] = dw[1] = 0; + if (!g_game_geo_known) return 1; /* the game has never been placed: nothing to weigh against */ + if (*deg <= 0) return 1; /* unknown (-1) or upright: leave today's behaviour alone */ + sw[0] = g_game_src.right - g_game_src.left; sw[1] = g_game_src.bottom - g_game_src.top; + dw[0] = g_game_dst.right - g_game_dst.left; dw[1] = g_game_dst.bottom - g_game_dst.top; + if (sw[0] <= 0 || sw[1] <= 0) return 1; + return !(dw[0] != sw[0] || dw[1] != sw[1]); /* rotated AND scaled -> not affordable */ +} + +/* -1 = not decided yet, 0 = raising the overlay, 1 = declining it. Logged on every change, so a + * tester's log says why they are seeing the copy path instead of two layers โ€” and says it again if + * the placement changes the answer (e.g. a fullscreen mode that stops scaling the game). */ +static int g_overlay_declined = -1; + +int sc_layer_overlay_affordable(void) { + layers_init(); + int deg, sr[2], ds[2]; + int ok = overlay_affordable(°, sr, ds); + int want = ok ? 0 : 1; + if (want != g_overlay_declined) { + int first = g_overlay_declined < 0; + g_overlay_declined = want; + if (want) + wnc_log("layer", "overlay layer declined: this display rotates every layer %dยฐ and the game " + "layer is scaled %dx%d -> %dx%d, and on that combination a second layer drops the " + "whole frame to GPU composition for the rest of the session (measured). The window " + "above the game goes on the copy path instead - same picture, one blit.", + deg, sr[0], sr[1], ds[0], ds[1]); + else if (!first) + wnc_log("layer", "overlay layer allowed again: the game layer is no longer both rotated and " + "scaled, so a second display layer costs nothing here"); + } + return ok; +} + +int sc_layer_present_overlay(struct vkp_image *src, const int geo[8]) { + struct layer *l = layer_of(SC_LAYER_OVERLAY); + if (!src || !geo) return -1; + /* The caller decides with sc_layer_overlay_affordable() BEFORE it commits the game to a layer; + * this is only a guard so the layer can never go up behind that decision's back. */ + if (!sc_layer_overlay_affordable()) return -1; + if (ensure_sc(l) != 0) return -1; + int sw = vkp_image_width(src), sh = vkp_image_height(src); + if (sw <= 0 || sh <= 0) return -1; + int wait_fd; + int idx = take_free_slot(l, sw, sh, AHB_RGBA8, &wait_fd); + if (idx < 0) { log_drop(l); return 0; } + /* The window is copied into the layer buffer 1:1; `geo` crops it and places it, so the layer + * is exactly the window's rectangle on screen and nothing else is blended anywhere. */ + if (vkp_blit_image(src, l->slots[idx].img, wait_fd) != 0) return -1; + if (present_slot(l, idx, geo, NULL) != 0) return -1; + if (!l->first_logged) { + l->first_logged = 1; + log_layer_count(); + } + return 0; +} + +/* Hide one layer: the buffer comes off with it (a hidden layer would keep the game's buffer - or + * the pool slot - referenced, and the game needs it back to keep presenting the other way). */ +static void hide_layer(struct layer *l) { + if (!l->sc || !l->shown || api.state != 1) return; + ASurfaceTransaction *tx = api.txCreate(); + if (!tx) return; + replace_with_blank(tx, l); + api.setVisibility(tx, l->sc, ASC_VISIBILITY_HIDE); + add_complete(tx, l, l->cur_slot, l->cur_token, 0); + api.txApply(tx); api.txDelete(tx); + l->shown = 0; l->cur_slot = -1; l->cur_token = NULL; +} + +void sc_layer_hide(void) { + layers_init(); + int said = g_layers[SC_LAYER_GAME].shown || g_layers[SC_LAYER_OVERLAY].sc; + /* Top down, so nothing of the scene is ever uncovered for a frame. The overlay layer is let go + * of entirely rather than hidden (same reason as sc_layer_hide_overlay: a live second + * SurfaceControl keeps SurfaceFlinger composing on the GPU); the game layer keeps its + * SurfaceControl, since it goes up and down with every effects/frame-generation toggle. */ + if (g_layers[SC_LAYER_OVERLAY].sc) sc_layer_hide_overlay(); + if (g_layers[SC_LAYER_GAME].shown) hide_layer(&g_layers[SC_LAYER_GAME]); + if (said) wnc_log("layer", "layers hidden (scene is not a single fullscreen window)"); +} + +void sc_layer_hide_overlay(void) { + layers_init(); + struct layer *l = &g_layers[SC_LAYER_OVERLAY]; + if (!l->sc) return; + /* RETIRED, not just hidden, so the overlay layer lives exactly as long as the window above the + * game: a live second SurfaceControl is what puts SurfaceFlinger into GPU client composition + * on this hardware (see sc_layer.h), and a hidden one is still live. Measured caveat: letting + * it go does NOT by itself bring hardware composition back - on the Pocket FIT the fallback + * outlives the overlay and only clears when the GAME layer's SurfaceControl is re-created + * (HOME + resume). Retiring is still the right thing; it just is not the whole cure. The pool + * buffers stay allocated for the next window. */ + if (l->shown) hide_layer(l); + retire_sc(l); + wnc_log("layer", "%s: gone (nothing is above the game any more)", l->name); + /* ...and that is the half the measurement said is not enough: arm the game layer's + * SurfaceControl swap, which the next frame performs (swap_sc_begin). */ + if (g_layers[SC_LAYER_GAME].sc) g_layers[SC_LAYER_GAME].recreate_pending = 1; +} + +void sc_layer_window_gone(void) { + if (api.state != 1) return; + layers_init(); + for (int i = SC_LAYER_COUNT - 1; i >= 0; i--) retire_sc(&g_layers[i]); + drain_and_free_pools(); +} + +unsigned sc_layer_frames_take(void) { + unsigned n = g_stat_layer_frames; + g_stat_layer_frames = 0; + return n; +} diff --git a/app/src/main/cpp/waylandcomp/src/sc_layer.h b/app/src/main/cpp/waylandcomp/src/sc_layer.h new file mode 100644 index 000000000..c1c9031d6 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/sc_layer.h @@ -0,0 +1,174 @@ +#ifndef SC_LAYER_H +#define SC_LAYER_H +/* + * Layer mode (BANNER_WAYLAND_ZERO_COPY=1): the scene is handed to SurfaceFlinger as a small, + * deliberately ordered SET of Android display layers โ€” ASurfaceControl children of the + * compositor's SurfaceView โ€” instead of being blitted into the compositor's own swapchain. + * + * app window ......... Compose UI, the in-game drawer, the HUD, the on-screen controls and + * the pointer arrow: ordinary Android views, ALWAYS above everything + * below (the SurfaceView is not Z-on-top, so its whole subtree is under + * the window's own content). None of the layers below take input: + * an ASurfaceControl has no input channel, so touch and mouse keep + * reaching the SurfaceView exactly as they did. + * +-- SurfaceView ..... the compositor's Vulkan swapchain; black while layer mode is up + * +-- z=1 "wnc_wayland_game" ...... the one fullscreen game window + * +-- z=2 "wnc_wayland_overlay" ... at most one window drawn ABOVE the game + * + * TWO layers is the hard cap (SC_LAYER_COUNT), and deliberately so: HWC composes only a few + * layers before SurfaceFlinger falls back to GPU client composition, which would throw away the + * whole benefit. The count is logged when the second layer first goes up. + * + * MEASURED on the Pocket FIT (Adreno 750, portrait panel + landscape session, so every layer is + * ROT_90 + scaled), `dumpsys android.hardware.graphics.composer3.IComposer/default`: one layer is + * `composition: DEVICE/DEVICE`, effects on the layer keep it DEVICE/DEVICE โ€” but a SECOND layer + * flips the whole frame to `DEVICE/CLIENT`. It does not come back when the overlay goes away. + * The overlay layer is retired rather than hidden for that reason, but the fallback outlives it. + * The likely mechanism is the DPU's rotator budget (one rotated+scaled layer), not the layer count + * as such, so a device or orientation needing no rotation may well take both on the DPU. Even in + * client composition the overlay layer is not a loss โ€” SurfaceFlinger does the one blit the + * compositor would have done โ€” but the hardware-composition win is only real for one layer. + * + * COMPOSITION RECOVERY (sc_layer.c, swap_sc_begin): retiring the overlay arms the game layer, and + * the NEXT frame is presented on a brand-new SurfaceControl while the old one is hidden and + * unparented in the SAME transaction. SurfaceFlinger applies a transaction atomically, so no + * composited frame is ever missing the game โ€” no black frame, no dropped frame beyond the layer + * creation itself. One `layer` line is written when it happens. + * + * โš ๏ธ It does NOT restore hardware composition on this panel, and that was measured, not assumed + * (2026-09-14, Wizardry on the game layer + Wine's Task Manager on the overlay, reproduced twice): + * - two layers -> DEVICE/CLIENT + * - overlay retired + fresh game SurfaceControl -> still DEVICE/CLIENT, 24 s later too + * - layer path dropped entirely and re-created -> still DEVICE/CLIENT + * - drawer opened and closed, no second window -> DEVICE/DEVICE (control: the drawer is not the cause) + * - HOME + resume -> DEVICE/DEVICE + * HOME + resume re-creates the app's whole window and SurfaceView (`VRI[XServerDisplayActivity]#0` + * becomes `#4`), not just this child layer โ€” so the sticky state belongs to the parent surface or + * the display. Re-creating the parent would cost a real black frame and a swapchain rebuild, which + * is worse than the few percent of GPU that client composition costs. The cure that actually keeps + * the win is PREVENTION, and it IS implemented: sc_layer_overlay_affordable() declines the second + * layer when the game layer is both rotated and scaled, and the window above the game goes down the + * copy path instead (what the pre-phase-4 code did) โ€” one blit SurfaceFlinger would have done + * anyway, and the session keeps DEVICE composition throughout. Where a second layer costs nothing, + * nothing changes. + * + * What can be on the game layer, cheapest first: + * - the game's own gralloc buffer (ahb_swapchain.c, true zero-copy: no copy anywhere); + * - one blit of the game's frame into a compositor-allocated AHardwareBuffer (sc_layer_present); + * - with screen effects on, the compositor pass's result blitted into such a buffer + * (sc_layer_present_pass) โ€” the game STAYS on its layer while a Look is applied, and the + * scene -> output mapping is done by the display (setGeometry) instead of a second GPU blit. + * The compositor never alpha-blends (it composes with blits, which overwrite), so every layer is + * marked OPAQUE and the overlay layer is cropped to the window it carries: the picture is the + * same as the copy path's, pixel for pixel. + * + * Compositor thread only, except the SurfaceFlinger OnComplete callbacks (binder threads), which + * only touch the pools under g_lock. sc_layer_hide*() is safe at any time. + */ +#include <stddef.h> +#include <stdint.h> +#include <android/hardware_buffer.h> + +struct vkp_image; +struct vkp_draw; +struct wnc_color; + +/* The layers, bottom first. The z-order is the array order (z = id + 1). */ +enum sc_layer_id { SC_LAYER_GAME = 0, SC_LAYER_OVERLAY = 1, SC_LAYER_COUNT = 2 }; + +/* 1 when this device has the SurfaceControl API the layers need (Android 10+, libnativewindow's + * getNativeHandle); the reason is logged (tag `layer`) the first time it is missing. */ +int sc_layer_available(void); + +/* One-shot: does the kernel export a sync_file from the game's dma-buf (DMA_BUF_IOCTL_EXPORT_SYNC_FILE)? + * Decides where a zero-copy acquire fence can come from; result goes to the session log. */ +void sc_layer_probe_dmabuf_fd(int fd); + +/* HDR (color_mgmt.h): can a display layer be told its buffer's colour encoding here + * (ASurfaceTransaction_setBufferDataSpace, Android 10+)? Part of the HDR gate. */ +int sc_layer_can_tag_hdr(void); +/* Which of the colour calls this libandroid has, for the gate's log line. */ +void sc_layer_hdr_symbols(char *out, size_t size); + +/* COLOUR on the game layer: `color` is the image description of the frame being presented (NULL = + * none, i.e. sRGB). A layer that was never tagged is never touched (no setBufferDataSpace call at all: + * a session without an HDR description behaves exactly as before); once tagged, a later untagged frame + * puts the layer back to UNKNOWN. SMPTE 2086 / CTA-861.3 metadata travel with the dataspace. */ + +/* GAME layer: show `src` (the fullscreen window's imported frame, scene-sized) on it, placed + * through the current fullscreen mode / alignment mapping. 0 = shown (or deliberately dropped: no + * free buffer), -1 = the layer path is unavailable this frame and the caller must draw the old way. + * The copy into the layer buffer is 8-bit: an HDR frame keeps its tag (its colours stay right), and + * loses precision. */ +int sc_layer_present(struct vkp_image *src, int scene_w, int scene_h, const struct wnc_color *color); + +/* GAME layer, screen effects on: run the compositor pass (composite `draws` + the effects chain, + * vkp_pass_begin) and put its result on the layer. Same return values as sc_layer_present. */ +int sc_layer_present_pass(const struct vkp_draw *draws, int n, int scene_w, int scene_h); + +/* GAME layer, HDR (hdr_compose.h): the whole scene - `draws`, hf->is_hdr marking the HDR ones - composed + * into one 10-bit PQ BT.2020 picture with the effects applied (vkp_pass_begin_hdr), copied into a + * 10-bit layer buffer (8-bit where gralloc refuses those) and shown tagged with `color` (the topmost HDR + * draw's description). The overlay layer is not used: the picture already holds what is above the game. + * Same return values as sc_layer_present. */ +struct vkp_hdr_frame; +int sc_layer_present_hdr_scene(const struct vkp_draw *draws, int n, const struct vkp_hdr_frame *hf, + int scene_w, int scene_h, const struct wnc_color *color); + +/* GAME layer, zero-copy (ahb_swapchain.c): show the game's own w x h AHardwareBuffer, gated by + * acquire_fd (a sync_file the layer waits on before reading; owned by the callee, -1 = none). + * token identifies the buffer: once a later transaction replaces it (or the layer is hidden or + * retired), ahb_swapchain_layer_released(token, release_fd) reports SurfaceFlinger's release fence + * for it. Presenting the token already on the layer only updates the placement. 0 = on the layer, + * 1 = nothing of it is on screen (layer hidden, the buffer was not taken), -1 = unavailable this + * frame (the caller draws the old way). `color` / `ahb_format`: the frame's image description (NULL = + * none) and the buffer's AHARDWAREBUFFER_FORMAT_*, for the dataspace and the HDR evidence. */ +int sc_layer_present_ahb(AHardwareBuffer *ahb, int w, int h, int acquire_fd, void *token, + int scene_w, int scene_h, const struct wnc_color *color, uint32_t ahb_format); + +/* Vote a panel refresh rate for the layer the game presents on (VRR / refresh-rate matching), the + * same rate and compatibility the app votes on its own surface with Surface.setFrameRate; 0 = no + * vote. Needed because a zero-copy game's frames go onto the GAME layer and never reach the app's + * surface, so the surface vote alone does not describe the game's cadence to SurfaceFlinger. Only + * the game layer ever carries it: the overlay layer is explicitly voted 0, so a window that redraws + * once a second can never hold (or drop) the panel at the game's cadence. Callable from any thread + * at any time (it only stores the rate); the compositor applies it with the layer's next + * transaction, and re-applies it whenever a SurfaceControl is re-created. A no-op on devices whose + * libandroid has no ASurfaceTransaction_setFrameRate. */ +void sc_layer_set_frame_rate(float fps); + +/* Is a SECOND display layer worth raising on THIS display? Ask before committing the game to its + * layer, not after: on a display where the answer is no, the whole scene must go down the copy path + * from the start, or the game layer would be shown and hidden again on every frame. + * + * The answer is computed from what the layer path knows โ€” the rotation the presentation engine + * applies to everything we hand it (VkSurfaceCapabilitiesKHR::currentTransform) and the game + * layer's own src -> dst rectangles โ€” and never from a device or panel allowlist. ROTATED AND + * SCALED is the combination measured to cost hardware composition (below); either alone is fine. + * The reason is written to the session log once, and again if the answer changes. */ +int sc_layer_overlay_affordable(void); + +/* OVERLAY layer: show `src` (one window's imported frame) above the game layer, at the placement + * `geo` = {src x0,y0,x1,y1 in image pixels, dst x0,y0,x1,y1 in output pixels} from vkp_map_draw. + * 0 = shown or dropped, -1 = unavailable (the caller must fall back to the copy path). */ +int sc_layer_present_overlay(struct vkp_image *src, const int geo[8]); + +/* The scene is not a single fullscreen window this frame: hide every layer that is up. */ +void sc_layer_hide(void); +/* Only the overlay layer: nothing is above the game any more. The game keeps its layer, but its + * SurfaceControl is swapped for a fresh one on the next frame (composition recovery, above โ€” which + * on this panel does not in fact win hardware composition back; see the measurement there). */ +void sc_layer_hide_overlay(void); + +/* The output window changed or went away (compositor thread, from vkp_apply_window_request). */ +void sc_layer_window_gone(void); + +/* Frames the compositor put on a layer through one of its own buffers (a blit or the effects + * pass) since the last call โ€” the zero-copy frames ahb_swapchain.c counts are NOT included. + * For the 10 s summary. */ +unsigned sc_layer_frames_take(void); +/* Frames dropped since the last call because every layer buffer was still held by the display (the + * 10 s perf line; the "no free layer buffer" log line itself is rate-limited). */ +unsigned sc_layer_drops_take(void); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/vk_import.c b/app/src/main/cpp/waylandcomp/src/vk_import.c new file mode 100644 index 000000000..08c1351a3 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_import.c @@ -0,0 +1,167 @@ +/* Compositor-side dmabuf import into Turnip (see vk_import.h). */ +#define _POSIX_C_SOURCE 200809L +#include "vk_import.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <vulkan/vulkan.h> + +#define MOD_INVALID 0x00ffffffffffffffULL + +static int g_ready = -1; /* -1 = untried, 0 = ok, 1 = init failed */ +static VkInstance g_inst; +static VkPhysicalDevice g_pd; +static VkDevice g_dev; +static PFN_vkGetMemoryFdPropertiesKHR p_getMemFdProps; + +static int has_ext(VkExtensionProperties *e, uint32_t n, const char *name) { + for (uint32_t i = 0; i < n; i++) + if (!strcmp(e[i].extensionName, name)) return 1; + return 0; +} + +static int vk_init(void) { + if (g_ready >= 0) return g_ready == 0 ? 0 : -1; + + VkApplicationInfo app = {.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "spike-compositor-import", + .apiVersion = VK_API_VERSION_1_1}; + VkInstanceCreateInfo ici = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app}; + if (vkCreateInstance(&ici, NULL, &g_inst) != VK_SUCCESS) { + fprintf(stderr, "[imp] vkCreateInstance failed\n"); + g_ready = 1; return -1; + } + uint32_t npd = 0; + vkEnumeratePhysicalDevices(g_inst, &npd, NULL); + if (!npd) { g_ready = 1; return -1; } + VkPhysicalDevice pds[8]; + if (npd > 8) npd = 8; + vkEnumeratePhysicalDevices(g_inst, &npd, pds); + g_pd = pds[0]; /* Turnip is forced via VK_ICD_FILENAMES */ + + const char *want[] = {"VK_KHR_external_memory_fd", + "VK_EXT_external_memory_dma_buf", + "VK_EXT_image_drm_format_modifier", + "VK_KHR_image_format_list"}; + uint32_t ne = 0; + vkEnumerateDeviceExtensionProperties(g_pd, NULL, &ne, NULL); + VkExtensionProperties *exts = calloc(ne, sizeof(*exts)); + vkEnumerateDeviceExtensionProperties(g_pd, NULL, &ne, exts); + for (unsigned i = 0; i < 4; i++) + if (!has_ext(exts, ne, want[i])) + fprintf(stderr, "[imp] WARNING device missing %s\n", want[i]); + free(exts); + + float prio = 1.0f; + VkDeviceQueueCreateInfo qci = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = 0, .queueCount = 1, + .pQueuePriorities = &prio}; + VkDeviceCreateInfo dci = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = 1, .pQueueCreateInfos = &qci, + .enabledExtensionCount = 4, .ppEnabledExtensionNames = want}; + if (vkCreateDevice(g_pd, &dci, NULL, &g_dev) != VK_SUCCESS) { + fprintf(stderr, "[imp] vkCreateDevice failed\n"); + g_ready = 1; return -1; + } + p_getMemFdProps = (PFN_vkGetMemoryFdPropertiesKHR)vkGetDeviceProcAddr( + g_dev, "vkGetMemoryFdPropertiesKHR"); + g_ready = 0; + fprintf(stderr, "[imp] Vulkan import device ready (Turnip)\n"); + return 0; +} + +static VkFormat drm_to_vk(uint32_t drm) { + /* XR24/AR24 (XRGB/ARGB8888, little-endian bytes B,G,R,X) -> BGRA8. */ + return VK_FORMAT_B8G8R8A8_UNORM; +} + +int vk_import_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, int w, int h, + uint32_t stride, uint32_t offset) { + if (modifier == MOD_INVALID) { + fprintf(stderr, "[imp] skip: INVALID modifier (needs implicit path)\n"); + return -1; + } + if (vk_init() != 0) return -1; + + VkSubresourceLayout plane = {.offset = offset, .rowPitch = stride}; + VkImageDrmFormatModifierExplicitCreateInfoEXT modInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .drmFormatModifier = modifier, + .drmFormatModifierPlaneCount = 1, + .pPlaneLayouts = &plane}; + VkExternalMemoryImageCreateInfo extImg = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .pNext = &modInfo, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT}; + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &extImg, + .imageType = VK_IMAGE_TYPE_2D, + .format = drm_to_vk(drm_format), + .extent = {w, h, 1}, + .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + VkImage image; + VkResult r = vkCreateImage(g_dev, &ici, NULL, &image); + if (r != VK_SUCCESS) { + fprintf(stderr, "[imp] vkCreateImage(drm_modifier) -> %d\n", r); + return -1; + } + + int dupfd = dup(fd); + uint32_t allowed_types = 0xffffffff; + if (p_getMemFdProps) { + VkMemoryFdPropertiesKHR fp = {.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR}; + if (p_getMemFdProps(g_dev, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + dupfd, &fp) == VK_SUCCESS) + allowed_types = fp.memoryTypeBits; + } + VkMemoryRequirements req; + vkGetImageMemoryRequirements(g_dev, image, &req); + uint32_t bits = req.memoryTypeBits & allowed_types; + int type_idx = -1; + for (int i = 0; i < 32; i++) + if (bits & (1u << i)) { type_idx = i; break; } + if (type_idx < 0) { + fprintf(stderr, "[imp] no compatible memory type\n"); + vkDestroyImage(g_dev, image, NULL); close(dupfd); return -1; + } + + VkImportMemoryFdInfoKHR imp = { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .fd = dupfd}; + VkMemoryDedicatedAllocateInfo ded = { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .pNext = &imp, .image = image}; + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &ded, + .allocationSize = req.size, + .memoryTypeIndex = (uint32_t)type_idx}; + VkDeviceMemory mem; + r = vkAllocateMemory(g_dev, &mai, NULL, &mem); + if (r != VK_SUCCESS) { + fprintf(stderr, "[imp] vkAllocateMemory(import fd) -> %d\n", r); + vkDestroyImage(g_dev, image, NULL); close(dupfd); return -1; + } + r = vkBindImageMemory(g_dev, image, mem, 0); + if (r != VK_SUCCESS) { + fprintf(stderr, "[imp] vkBindImageMemory -> %d\n", r); + vkFreeMemory(g_dev, mem, NULL); vkDestroyImage(g_dev, image, NULL); + return -1; + } + + fprintf(stderr, + "[imp] *** IMPORTED dmabuf -> VkImage OK (memType %d, size %llu) " + "==> compositor can turn game frames into VkImages\n", + type_idx, (unsigned long long)req.size); + /* import took ownership of dupfd; free our objects */ + vkFreeMemory(g_dev, mem, NULL); + vkDestroyImage(g_dev, image, NULL); + return 0; +} diff --git a/app/src/main/cpp/waylandcomp/src/vk_import.h b/app/src/main/cpp/waylandcomp/src/vk_import.h new file mode 100644 index 000000000..b333e6db9 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_import.h @@ -0,0 +1,13 @@ +#ifndef VK_IMPORT_H +#define VK_IMPORT_H +#include <stdint.h> +/* + * Import a received dmabuf into the compositor's own Vulkan (Turnip) device as a + * VkImage and bind its memory. This is the M4-import de-risk: if we can import + + * bind, sampling/blitting it to screen is standard Vulkan. Non-fatal: returns 0 + * on success, negative on failure, and logs details. Does NOT consume `fd` + * (dups internally). + */ +int vk_import_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, + int w, int h, uint32_t stride, uint32_t offset); +#endif diff --git a/app/src/main/cpp/waylandcomp/src/vk_loader.c b/app/src/main/cpp/waylandcomp/src/vk_loader.c new file mode 100644 index 000000000..54043e462 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_loader.c @@ -0,0 +1,61 @@ +/* See vk_loader.h. */ +#define _GNU_SOURCE +#include "vk_loader.h" +#include <adrenotools/driver.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <android/log.h> + +#define TAG "WNWayland" + +struct vk_api g_vk; +static void *g_handle; +static PFN_vkGetInstanceProcAddr g_gip; + +int vk_loader_open(const char *driver_path, const char *library_name, + const char *native_lib_dir) { + if (driver_path && library_name && native_lib_dir) { + char *tmpdir = NULL; + if (asprintf(&tmpdir, "%stemp", driver_path) < 0) tmpdir = NULL; + if (tmpdir) mkdir(tmpdir, S_IRWXU | S_IRWXG); + g_handle = adrenotools_open_libvulkan( + RTLD_LOCAL | RTLD_NOW, ADRENOTOOLS_DRIVER_CUSTOM, tmpdir, + native_lib_dir, driver_path, library_name, NULL, NULL); + free(tmpdir); + __android_log_print(g_handle ? ANDROID_LOG_INFO : ANDROID_LOG_ERROR, TAG, + "adrenotools Turnip handle=%p (%s / %s)", g_handle, + driver_path, library_name); + } + if (!g_handle) { + g_handle = dlopen("libvulkan.so", RTLD_LOCAL | RTLD_NOW); + __android_log_print(ANDROID_LOG_WARN, TAG, + "no adrenotools driver -> system libvulkan handle=%p " + "(dmabuf import likely unsupported)", g_handle); + } + if (!g_handle) return -1; + g_gip = (PFN_vkGetInstanceProcAddr)dlsym(g_handle, "vkGetInstanceProcAddr"); + if (!g_gip) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "no vkGetInstanceProcAddr"); + return -1; + } +#define X(n) g_vk.n = (PFN_vk##n)g_gip(NULL, "vk" #n); + VK_GLOBAL_FUNCS(X) +#undef X + return g_vk.CreateInstance ? 0 : -1; +} + +PFN_vkGetInstanceProcAddr vk_loader_gipa(void) { return g_gip; } + +void vk_loader_load_instance(VkInstance instance) { +#define X(n) g_vk.n = (PFN_vk##n)g_gip(instance, "vk" #n); + VK_INSTANCE_FUNCS(X) +#undef X +} + +void vk_loader_load_device(VkDevice device) { +#define X(n) g_vk.n = (PFN_vk##n)g_vk.GetDeviceProcAddr(device, "vk" #n); + VK_DEVICE_FUNCS(X) +#undef X +} diff --git a/app/src/main/cpp/waylandcomp/src/vk_loader.h b/app/src/main/cpp/waylandcomp/src/vk_loader.h new file mode 100644 index 000000000..c7609d67a --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_loader.h @@ -0,0 +1,60 @@ +#ifndef VK_LOADER_H +#define VK_LOADER_H +/* + * Loads Turnip via adrenotools (the same path the app uses for the guest renderer) + * and resolves every Vulkan entry point through its vkGetInstanceProcAddr โ€” NOT the + * process-default system Adreno driver, which lacks VK_EXT_image_drm_format_modifier / + * dmabuf import. All compositor Vulkan calls go through g_vk.* . + */ +#define VK_USE_PLATFORM_ANDROID_KHR +#include <vulkan/vulkan.h> + +#define VK_GLOBAL_FUNCS(X) X(CreateInstance) X(EnumerateInstanceExtensionProperties) + +#define VK_INSTANCE_FUNCS(X) \ + X(EnumeratePhysicalDevices) X(GetPhysicalDeviceProperties) \ + X(GetPhysicalDeviceQueueFamilyProperties) X(GetPhysicalDeviceSurfaceSupportKHR) \ + X(GetPhysicalDeviceSurfaceCapabilitiesKHR) X(GetPhysicalDeviceSurfaceFormatsKHR) \ + X(CreateAndroidSurfaceKHR) X(DestroySurfaceKHR) X(CreateDevice) \ + X(GetDeviceProcAddr) X(EnumerateDeviceExtensionProperties) \ + X(GetPhysicalDeviceMemoryProperties) X(GetPhysicalDeviceFormatProperties2) \ + X(GetPhysicalDeviceImageFormatProperties2) + +#define VK_DEVICE_FUNCS(X) \ + X(GetDeviceQueue) X(CreateSwapchainKHR) X(DestroySwapchainKHR) X(GetSwapchainImagesKHR) \ + X(CreateCommandPool) X(AllocateCommandBuffers) X(CreateSemaphore) X(CreateFence) \ + X(DeviceWaitIdle) X(CreateImage) X(GetImageMemoryRequirements) X(AllocateMemory) \ + X(BindImageMemory) X(DestroyImage) X(FreeMemory) X(AcquireNextImageKHR) \ + X(ResetCommandBuffer) X(BeginCommandBuffer) X(CmdPipelineBarrier) X(CmdBlitImage) \ + X(EndCommandBuffer) X(ResetFences) X(QueueSubmit) X(QueuePresentKHR) X(WaitForFences) \ + X(QueueWaitIdle) X(GetMemoryFdPropertiesKHR) \ + X(GetImageSubresourceLayout) X(MapMemory) X(UnmapMemory) X(CmdClearColorImage) \ + /* screen-effect chain (effects_chain.c): graphics pipelines over full-screen quads */ \ + X(CreateShaderModule) X(DestroyShaderModule) X(CreatePipelineLayout) X(DestroyPipelineLayout) \ + X(CreateGraphicsPipelines) X(DestroyPipeline) X(CreateRenderPass) X(DestroyRenderPass) \ + X(CreateFramebuffer) X(DestroyFramebuffer) X(CreateImageView) X(DestroyImageView) \ + X(CreateSampler) X(DestroySampler) X(CreateDescriptorSetLayout) X(DestroyDescriptorSetLayout) \ + X(CreateDescriptorPool) X(DestroyDescriptorPool) X(AllocateDescriptorSets) X(FreeDescriptorSets) \ + X(UpdateDescriptorSets) X(CmdBeginRenderPass) X(CmdEndRenderPass) X(CmdBindPipeline) \ + X(CmdBindDescriptorSets) X(CmdPushConstants) X(CmdDraw) X(CmdSetViewport) X(CmdSetScissor) \ + /* frame generation (framegen_bridge.c / vk_present.c per-present sync slots) */ \ + X(DestroySemaphore) + +struct vk_api { +#define X(n) PFN_vk##n n; + VK_GLOBAL_FUNCS(X) VK_INSTANCE_FUNCS(X) VK_DEVICE_FUNCS(X) +#undef X +}; +extern struct vk_api g_vk; + +/* Open Turnip via adrenotools (falls back to system libvulkan if any arg is NULL) + * and load global entry points. Returns 0 on success. */ +int vk_loader_open(const char *driver_path, const char *library_name, + const char *native_lib_dir); +void vk_loader_load_instance(VkInstance instance); +void vk_loader_load_device(VkDevice device); +/* The driver's vkGetInstanceProcAddr (NULL before vk_loader_open): for modules that resolve their + * own entry points, e.g. the frame-generation engines (framegen_engine.cpp). */ +PFN_vkGetInstanceProcAddr vk_loader_gipa(void); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/vk_present.c b/app/src/main/cpp/waylandcomp/src/vk_present.c new file mode 100644 index 000000000..be09b3ad6 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_present.c @@ -0,0 +1,2153 @@ +/* Android-surface render backend โ€” see vk_present.h. Uses Turnip via vk_loader + * (g_vk.*), not the process-default system Adreno driver. */ +#define _POSIX_C_SOURCE 200809L +#include "vk_present.h" +#include "vk_loader.h" +#include "sc_layer.h" +#include "effects_chain.h" +#include "framegen_bridge.h" +#include "hdr_compose.h" +#include "blend_pass.h" +#include "color_mgmt.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <poll.h> +#include <time.h> +#include <pthread.h> +#include <android/log.h> + +#define TAG "WNWayland" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGE(...) wnc_log("error", __VA_ARGS__) +#define MOD_INVALID VKP_MOD_INVALID + +struct vkp_image { + VkImage image; + VkDeviceMemory mem; + int w, h; + int dmabuf; /* imported from a client; owned by the foreign queue family */ + int blit_dst; /* layer mode: a pool buffer this backend blits into (never a source) */ + void *map; /* shm images: persistently mapped linear memory */ + VkDeviceSize offset, row_pitch; + int in_general; /* shm images: moved from PREINITIALIZED to GENERAL */ + VkFormat fmt; + int sampled; /* created with SAMPLED usage: the alpha pass can read it */ +}; + +static ANativeWindow *g_window; /* the window frames go to; compositor thread only */ +static char *g_driver_path, *g_library_name, *g_native_lib_dir; +static int g_dev_state; /* 0 = not yet, 1 = ok, -1 = failed, -2 = lost (VK_ERROR_DEVICE_LOST) */ +static VkInstance g_inst; +static VkPhysicalDevice g_pd; +static VkDevice g_dev; +static VkQueue g_queue; +static uint32_t g_qfam; +static VkCommandPool g_pool; +/* One command buffer + acquire/render-done semaphore pair per PRESENT. A scene frame is one + * present, or up to 1 + VKP_FG_MAX_GENERATIONS with frame generation (the generated frames go + * out first, the real frame last), each on its own slot so no semaphore ever carries two + * pending signals. Slot 0 is also the layer-mode blit's command buffer. */ +#define MAX_PRESENTS (1 + VKP_FG_MAX_GENERATIONS) +static VkCommandBuffer g_cmds[MAX_PRESENTS]; +static VkSemaphore g_acqs[MAX_PRESENTS], g_rnds[MAX_PRESENTS]; +static VkFence g_fence; +/* Extra swapchain images this swapchain was built with (frame generation: one per generated + * frame, so all presents of a scene frame queue without waiting for a vblank). */ +static int g_swap_extra; +/* Format of the compositor pass's images (the scene image below, the effects chain's targets and + * the frame-generation ring): a blit converts every client format into it, the chains read RGBA. */ +#define SCENE_FMT VK_FORMAT_R8G8B8A8_UNORM +static VkPhysicalDeviceMemoryProperties g_memprops; + +static VkSurfaceKHR g_surface; +static VkSwapchainKHR g_swapchain; +/* The display's own rotation of everything we present (VkSurfaceCapabilitiesKHR::currentTransform), + * captured when the swapchain is built. Not a panel or device allowlist: it is what the presentation + * engine says it will do to our layers. */ +static VkSurfaceTransformFlagBitsKHR g_surface_transform; +static int g_surface_transform_known; +static VkImage *g_images; +static uint32_t g_nimg; +static VkExtent2D g_extent; + +static int g_first_frame_done; /* one-shot: fire wnc_on_first_frame() on first present */ + +/* The base surface under the display layers (vkp_base_black): 1 once a plain black frame has been + * presented on the CURRENT swapchain and nothing else since. Every other present clears it, and so does + * every swapchain teardown, so the next layer frame puts one black frame back first. While it is set, + * a frame that lives entirely on the display layers draws and presents nothing here. */ +static int g_base_black; +/* The layer path presents nothing on the base surface while the game keeps its layer, so the swapchain's + * OUT_OF_DATE (a resized surface, a new transform) would never be seen there: check_surface_changed() + * asks the surface directly instead, at most every 100 ms. caps.currentExtent at the last build. */ +static VkExtent2D g_caps_extent; +static int64_t g_surface_checked_ns; + +/* Letterbox bars: vkCmdClearColorImage clears whole images only, so the bars around the picture are + * blitted from this small black image instead of clearing the whole output under a picture that covers + * most of it. Created and cleared once with the device. 1 = ready, 0/-1 = not available (whole clear). */ +#define BLACK_DIM 16 +static VkImage g_black_img; +static VkDeviceMemory g_black_mem; +static int g_black_state; + +/* The display's release fence of a layer buffer, as a GPU wait (VK_KHR_external_semaphore_fd): the + * pool's next copy into that buffer waits for it on the GPU instead of the compositor thread polling it. */ +static PFN_vkImportSemaphoreFdKHR g_import_sem_fd; +static VkSemaphore g_wait_sem; + +/* The 10 s perf line's counters (vkp_perf_take). Compositor thread only. */ +static struct vkp_perf g_perf; +static int64_t perf_now(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} +static void perf_add(int64_t *sum, int64_t *max, unsigned *count, int64_t d) { + if (d < 0) d = 0; + *sum += d; + if (d > *max) *max = d; + (*count)++; +} +/* vkWaitForFences on the compositor's own work, timed for the perf line. */ +static VkResult timed_wait(VkFence fence, uint64_t timeout) { + int64_t t0 = perf_now(); + VkResult r = g_vk.WaitForFences(g_dev, 1, &fence, VK_TRUE, timeout); + perf_add(&g_perf.wait_ns, &g_perf.wait_max_ns, &g_perf.waits, perf_now() - t0); + return r; +} + +/* The scene image (effects path only): every draw composited 1:1 at scene size, the input of the + * screen-effect chain (effects_chain.c) โ€” and of frame generation once that lands. Recreated on a + * scene size change; frames are fenced, so never while the GPU reads it. */ +static struct { VkImage img; VkDeviceMemory mem; int w, h; } g_scene; + +/* HDR composition (hdr_compose.h): the 10-bit image every draw is blitted into 1:1 in its own encoding + * ("mixed"), and the 10-bit HDR picture the encode pass makes of it for the game's display layer. Only + * ever created while an HDR game shares the scene with something the display layer cannot show alone. */ +struct vkp_img_slot { VkImage img; VkDeviceMemory mem; int w, h; VkFormat fmt; }; +static struct vkp_img_slot g_mixed, g_hdrscene, g_fgscene; +#define HDR_FMT VK_FORMAT_A2B10G10R10_UNORM_PACK32 +/* Frame generation with HDR frames through an HDR10 swapchain: the scene, the effects and the engine's + * frames in FP16 - the format lsfg-vk itself uses for HDR - where the engine can build its chain in it, + * so the PQ picture keeps more than 8 bits through interpolation (framegen_bridge.h). */ +#define FG_HDR_FMT VK_FORMAT_R16G16B16A16_SFLOAT + +/* HDR10 swapchain (frame generation with an HDR game): VK_EXT_swapchain_colorspace is enabled on the + * instance only when the session asked for HDR; the swapchain is built as A2B10G10R10 + HDR10_ST2084 + * while such frames are being presented and the Android surface lists that pair (its WSI then sets the + * surface dataspace to BT2020_PQ). */ +static int g_colorspace_ext; /* the instance has VK_EXT_swapchain_colorspace */ +static int g_swap_hdr_want; /* the next swapchain should be HDR10 */ +static int g_swap_is_hdr; /* the live swapchain is HDR10 */ +static int g_swap_hdr_unavailable; /* this surface lists no HDR10 format: tone-map instead (said once) */ +static VkFormat g_swap_fmt; /* the live swapchain's format */ +/* VK_EXT_hdr_metadata (HDR sessions, where the device offers it): the HDR10 swapchain carries the game's + * mastering metadata, sent once per swapchain and image description. */ +static PFN_vkSetHdrMetadataEXT g_set_hdr_metadata; +static uint32_t g_swap_md_identity; +static int g_surface_formats_said; +static const char *vk_result_name(VkResult r); + +/* The Android surface is created/destroyed on the app's UI thread while the compositor thread + * may be inside a WSI call (acquire and present can block for a refresh or more). The UI thread + * therefore never touches the swapchain: it leaves the new window here and returns at once; the + * compositor thread picks it up before its next frame (vkp_apply_window_request). g_req_lock is + * only ever held for these few assignments, never across a Vulkan call. */ +static pthread_mutex_t g_req_lock = PTHREAD_MUTEX_INITIALIZER; +static ANativeWindow *g_req_window; +static int g_req_pending; + +/* Scale mode + alignment (app values, see vk_present.h); written from any thread, read per frame. */ +static volatile int g_mode = VKP_MODE_STRETCH, g_align = VKP_ALIGN_CENTER; + +/* The scene -> output mapping of the last frame (compositor thread): scene pixel (x,y) lands at + * output (off_x + x * kx, off_y + y * ky), clipped to the region rectangle. */ +static struct { + int scene_w, scene_h, out_w, out_h, mode, align; + float kx, ky, off_x, off_y; + int rx, ry, rw, rh; /* region the picture may occupy */ + int valid; +} g_map; + +/* A failed swapchain creation is retried no sooner than this (the window may be mid-teardown), + * and the failure is logged once per streak instead of every frame. */ +static int64_t g_swap_retry_at_ns; +static int g_swap_fail_logged; + +/* Implemented in waylandcomp_jni.c โ€” notifies Java (dismiss launch overlay). */ +extern void wnc_on_first_frame(void); + +static char g_gpu_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; +const char *vkp_gpu_name(void) { return g_gpu_name; } + +/* DRM fourccs name the channels of a little-endian 32-bit word: XRGB8888 is B,G,R,X in memory + * (= VK B8G8R8A8) and XBGR8888 is R,G,B,X (= VK R8G8B8A8). Turnip's Wayland WSI sends XB24 for + * R8G8B8A8 swapchains, so reading everything as BGRA swaps red and blue. */ +static VkFormat drm_to_vk(uint32_t drm) { + switch (drm) { + case 0x34324241: /* AB24 */ + case 0x34324258: /* XB24 */ + return VK_FORMAT_R8G8B8A8_UNORM; + case 0x30334241: /* AB30 */ + case 0x30334258: /* XB30 */ + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case 0x30335241: /* AR30 */ + case 0x30335258: /* XR30 */ + return VK_FORMAT_A2R10G10B10_UNORM_PACK32; + case 0x48344241: /* AB4H */ + case 0x48344258: /* XB4H */ + return VK_FORMAT_R16G16B16A16_SFLOAT; + default: /* AR24 / XR24 */ + return VK_FORMAT_B8G8R8A8_UNORM; + } +} + +void vk_present_set_driver(const char *driver_path, const char *library_name, + const char *native_lib_dir) { + free(g_driver_path); free(g_library_name); free(g_native_lib_dir); + g_driver_path = driver_path ? strdup(driver_path) : NULL; + g_library_name = library_name ? strdup(library_name) : NULL; + g_native_lib_dir = native_lib_dir ? strdup(native_lib_dir) : NULL; +} + +/* Destroy and recreate every acquire/render-done semaphore. Done with the swapchain (after the + * device went idle): a frame that acquired an image and then bailed out leaves its acquire + * semaphore signalled with nothing to wait on it, and reusing it in the next acquire is invalid + * (Adreno answers OUT_OF_DATE, which rebuilds again - a loop). Fresh objects cannot carry that. */ +static void reset_sync(void) { + VkSemaphoreCreateInfo semci = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + for (int k = 0; k < MAX_PRESENTS; k++) { + if (g_acqs[k]) g_vk.DestroySemaphore(g_dev, g_acqs[k], NULL); + if (g_rnds[k]) g_vk.DestroySemaphore(g_dev, g_rnds[k], NULL); + g_vk.CreateSemaphore(g_dev, &semci, NULL, &g_acqs[k]); + g_vk.CreateSemaphore(g_dev, &semci, NULL, &g_rnds[k]); + } +} + +static void destroy_swapchain(void) { + g_base_black = 0; /* a new swapchain starts with nothing on it */ + if (g_dev_state != 1) return; + g_vk.DeviceWaitIdle(g_dev); + reset_sync(); + for (uint32_t i = 0; i < g_nimg; i++) blendp_forget_image(g_images[i]); + if (g_swapchain) g_vk.DestroySwapchainKHR(g_dev, g_swapchain, NULL); + g_swapchain = VK_NULL_HANDLE; + g_swap_md_identity = 0; /* a new swapchain gets the metadata again */ + if (g_surface) g_vk.DestroySurfaceKHR(g_inst, g_surface, NULL); + g_surface = VK_NULL_HANDLE; + free(g_images); + g_images = NULL; + g_nimg = 0; +} + +void vk_present_set_window(ANativeWindow *window) { + ANativeWindow *superseded = NULL; + pthread_mutex_lock(&g_req_lock); + /* Two requests before the compositor thread got to the first: the first window was never + * used, so its reference is dropped here. */ + if (g_req_pending && g_req_window && g_req_window != window) superseded = g_req_window; + g_req_window = window; + g_req_pending = 1; + pthread_mutex_unlock(&g_req_lock); + if (superseded) ANativeWindow_release(superseded); +} + +int vkp_apply_window_request(void) { + ANativeWindow *w; + pthread_mutex_lock(&g_req_lock); + if (!g_req_pending) { pthread_mutex_unlock(&g_req_lock); return 0; } + w = g_req_window; + g_req_window = NULL; + g_req_pending = 0; + pthread_mutex_unlock(&g_req_lock); + if (w == g_window) { + /* The same window handed over again (ANativeWindow_fromSurface adds a reference each time). */ + if (w) ANativeWindow_release(w); + return 0; + } + sc_layer_window_gone(); /* the layer (if any) belongs to the old window */ + destroy_swapchain(); /* recreated against the new window on the next frame */ + g_swap_hdr_unavailable = 0; /* a new surface (another display, say) may offer HDR10 */ + if (g_window) ANativeWindow_release(g_window); + g_window = w; + g_swap_retry_at_ns = 0; + g_swap_fail_logged = 0; + wnc_log("gpu", w ? "screen surface attached" : "screen surface gone: presenting paused"); + return 1; +} + +int vkp_has_window(void) { + int r; + pthread_mutex_lock(&g_req_lock); + r = g_req_pending ? g_req_window != NULL : g_window != NULL; + pthread_mutex_unlock(&g_req_lock); + return r; +} + +int vkp_device_lost(void) { return g_dev_state == -2; } + +void vk_present_set_scale_mode(int mode, int alignment) { + if (mode < VKP_MODE_OFF || mode > VKP_MODE_INTEGER) mode = VKP_MODE_FIT; + if (alignment < VKP_ALIGN_CENTER || alignment > VKP_ALIGN_BOTTOM) alignment = VKP_ALIGN_CENTER; + g_mode = mode; + g_align = alignment; +} + +static const char *mode_name(int m) { + switch (m) { + case VKP_MODE_OFF: return "off (letterbox)"; + case VKP_MODE_FIT: return "fit"; + case VKP_MODE_STRETCH: return "stretch"; + case VKP_MODE_FILL: return "fill"; + case VKP_MODE_INTEGER: return "integer"; + default: return "?"; + } +} +static const char *align_name(int a) { + switch (a) { + case VKP_ALIGN_TOP: return "top"; + case VKP_ALIGN_BOTTOM: return "bottom"; + default: return "center"; + } +} + +/* Mirror of ViewTransformation.update(outer = output, inner = scene, mode, alignment) โ€” keep the + * arithmetic identical: the app maps touch input through that class with the same inputs, so any + * difference here puts the pointer beside what it is pointing at. OFF and FIT are both an + * aspect-preserving letterbox (OFF only differs in the app's fullscreen gates); TOP/BOTTOM confine + * the picture to the top/bottom half of the output (the app's handheld split, #413). */ +static void update_map(int scene_w, int scene_h) { + int mode = g_mode, align = g_align; + int W = (int)g_extent.width, H = (int)g_extent.height; + if (g_map.valid && g_map.scene_w == scene_w && g_map.scene_h == scene_h && g_map.out_w == W && + g_map.out_h == H && g_map.mode == mode && g_map.align == align) + return; + g_map.scene_w = scene_w; g_map.scene_h = scene_h; g_map.out_w = W; g_map.out_h = H; + g_map.mode = mode; g_map.align = align; + + int half = H / 2; + switch (align) { + case VKP_ALIGN_TOP: g_map.rx = 0; g_map.ry = 0; g_map.rw = W; g_map.rh = half; break; + case VKP_ALIGN_BOTTOM: g_map.rx = 0; g_map.ry = half; g_map.rw = W; g_map.rh = H - half; break; + default: g_map.rx = 0; g_map.ry = 0; g_map.rw = W; g_map.rh = H; break; + } + float sx = (float)g_map.rw / scene_w, sy = (float)g_map.rh / scene_h; + if (mode == VKP_MODE_STRETCH) { + g_map.kx = sx; g_map.ky = sy; + g_map.off_x = (float)g_map.rx; g_map.off_y = (float)g_map.ry; + } else { + float aspect; + if (mode == VKP_MODE_FILL) aspect = sx > sy ? sx : sy; + else if (mode == VKP_MODE_INTEGER) { + float m = sx < sy ? sx : sy; + aspect = (float)(int)m; /* floor for m >= 1 */ + if (aspect < 1.0f) aspect = 1.0f; + } else aspect = sx < sy ? sx : sy; + g_map.kx = g_map.ky = aspect; + /* Same integer truncation as ViewTransformation's viewOffsetX/Y. */ + g_map.off_x = (float)(g_map.rx + (int)((g_map.rw - scene_w * aspect) * 0.5f)); + g_map.off_y = (float)(g_map.ry + (int)((g_map.rh - scene_h * aspect) * 0.5f)); + } + g_map.valid = 1; + wnc_log("screen", "%s, %s: %dx%d scene shown %dx%d at %d,%d on the %dx%d output", + mode_name(mode), align_name(align), scene_w, scene_h, + (int)(scene_w * g_map.kx + 0.5f), (int)(scene_h * g_map.ky + 0.5f), + (int)g_map.off_x, (int)g_map.off_y, W, H); +} + +int vkp_output_to_scene(double ox, double oy, double *sx, double *sy) { + if (!g_map.valid || g_map.kx <= 0 || g_map.ky <= 0) return 0; + *sx = (ox - g_map.off_x) / g_map.kx; + *sy = (oy - g_map.off_y) / g_map.ky; + return 1; +} + +void vkp_output_size(int *w, int *h) { + *w = (int)g_extent.width; + *h = (int)g_extent.height; +} + +static void init_black_image(void); + +static int has_ext(VkExtensionProperties *e, uint32_t n, const char *name) { + for (uint32_t i = 0; i < n; i++) + if (!strcmp(e[i].extensionName, name)) return 1; + return 0; +} + +/* Instance + device + command objects. Doesn't need the window. */ +static int dev_init(void) { + if (g_dev_state != 0) return g_dev_state == 1 ? 0 : -1; + + /* Load Turnip (adrenotools) and its entry points โ€” NOT the system driver. */ + if (vk_loader_open(g_driver_path, g_library_name, g_native_lib_dir) != 0) { + LOGE("present: vk_loader_open failed"); g_dev_state = -1; return -1; + } + + const char *inst_exts[3] = {VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, NULL}; + uint32_t n_inst_exts = 2; + /* HDR sessions only (color_mgmt.h): the colour-space extension lets the swapchain carry HDR10 for + * frame generation. Asked for only when the loader has it, and only when HDR was asked for, so + * every other session creates exactly the instance it always did. */ + if (wnc_color_requested() && g_vk.EnumerateInstanceExtensionProperties) { + uint32_t ne = 0; + g_vk.EnumerateInstanceExtensionProperties(NULL, &ne, NULL); + VkExtensionProperties *ie = ne ? calloc(ne, sizeof(*ie)) : NULL; + if (ie && g_vk.EnumerateInstanceExtensionProperties(NULL, &ne, ie) == VK_SUCCESS) { + for (uint32_t i = 0; i < ne; i++) + if (!strcmp(ie[i].extensionName, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) { + inst_exts[n_inst_exts++] = VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + g_colorspace_ext = 1; + break; + } + } + free(ie); + wnc_log("color", "compositor instance %s VK_EXT_swapchain_colorspace (an HDR10 swapchain for frame " + "generation %s)", g_colorspace_ext ? "enables" : "has no", + g_colorspace_ext ? "is possible where the screen surface offers one" : "is not possible: tone-mapped instead"); + } + /* 1.3 like the X11 renderer's instance: the frame-generation probe (framegen_engine.cpp) + * queries VkPhysicalDeviceVulkan12Features, and a 1.1 instance may be answered as 1.1. */ + VkApplicationInfo app = {.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "wn-wayland-present", + .apiVersion = VK_API_VERSION_1_3}; + VkInstanceCreateInfo ici = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app, + .enabledExtensionCount = n_inst_exts, + .ppEnabledExtensionNames = inst_exts}; + if (g_vk.CreateInstance(&ici, NULL, &g_inst) != VK_SUCCESS) { + LOGE("present: vkCreateInstance failed"); g_dev_state = -1; return -1; + } + vk_loader_load_instance(g_inst); + + uint32_t npd = 0; + g_vk.EnumeratePhysicalDevices(g_inst, &npd, NULL); + if (!npd) { LOGE("present: no physical devices"); g_dev_state = -1; return -1; } + VkPhysicalDevice pds[8]; if (npd > 8) npd = 8; + g_vk.EnumeratePhysicalDevices(g_inst, &npd, pds); + g_pd = VK_NULL_HANDLE; + for (uint32_t i = 0; i < npd && g_pd == VK_NULL_HANDLE; i++) { + uint32_t nq = 0; + g_vk.GetPhysicalDeviceQueueFamilyProperties(pds[i], &nq, NULL); + VkQueueFamilyProperties qs[16]; if (nq > 16) nq = 16; + g_vk.GetPhysicalDeviceQueueFamilyProperties(pds[i], &nq, qs); + for (uint32_t q = 0; q < nq; q++) + if (qs[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) { g_pd = pds[i]; g_qfam = q; break; } + } + if (g_pd == VK_NULL_HANDLE) { LOGE("present: no graphics queue"); g_dev_state = -1; return -1; } + { + VkPhysicalDeviceProperties props; + g_vk.GetPhysicalDeviceProperties(g_pd, &props); + snprintf(g_gpu_name, sizeof(g_gpu_name), "%s", props.deviceName); + wnc_log("gpu", "compositor renders on %s with %s", props.deviceName, + g_library_name ? g_library_name : "the system Vulkan driver"); + if (g_driver_path) wnc_log("gpu", "driver folder %s", g_driver_path); + } + g_vk.GetPhysicalDeviceMemoryProperties(g_pd, &g_memprops); + + /* Verify the dmabuf-import extensions are present, and log any that are missing. */ + const char *dev_exts[7] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, "VK_KHR_external_memory_fd", + "VK_EXT_external_memory_dma_buf", "VK_EXT_image_drm_format_modifier", + "VK_KHR_image_format_list", NULL, NULL}; + uint32_t n_dev_exts = 5; + uint32_t ne = 0; + g_vk.EnumerateDeviceExtensionProperties(g_pd, NULL, &ne, NULL); + VkExtensionProperties *exts = calloc(ne ? ne : 1, sizeof(*exts)); + g_vk.EnumerateDeviceExtensionProperties(g_pd, NULL, &ne, exts); + for (unsigned i = 0; i < 5; i++) + if (!has_ext(exts, ne, dev_exts[i])) + LOGE("present: driver MISSING %s (dmabuf import will fail)", dev_exts[i]); + /* HDR sessions only: the HDR10 swapchain (frame generation) can carry the game's metadata. */ + int want_hdr_md = 0; + if (wnc_color_requested()) { + want_hdr_md = has_ext(exts, ne, VK_EXT_HDR_METADATA_EXTENSION_NAME); + if (want_hdr_md) dev_exts[n_dev_exts++] = VK_EXT_HDR_METADATA_EXTENSION_NAME; + } + /* The layer pool's release fences as GPU waits (sync_file -> semaphore): optional, the pool falls + * back to waiting for them on the CPU without it. */ + const int want_sem_fd = has_ext(exts, ne, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); + if (want_sem_fd) dev_exts[n_dev_exts++] = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; + free(exts); + + float prio = 1.0f; + VkDeviceQueueCreateInfo qci = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = g_qfam, .queueCount = 1, .pQueuePriorities = &prio}; + /* Frame generation (framegen_bridge.c): the LSFG chain needs the memory-model and + * storage-image features enabled at device creation; the probe hands back the pNext chain + * for them, or NULL on a device that cannot run it (then DIS Native alone is offered). A + * driver that rejects the chain costs nothing: retried once without it, exactly as before. */ + const void *fg_features = vkp_framegen_device_features(g_inst, vk_loader_gipa(), g_pd); + VkDeviceCreateInfo dci = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = fg_features, + .queueCreateInfoCount = 1, .pQueueCreateInfos = &qci, + .enabledExtensionCount = n_dev_exts, .ppEnabledExtensionNames = dev_exts}; + VkResult dr = g_vk.CreateDevice(g_pd, &dci, NULL, &g_dev); + if (dr != VK_SUCCESS && fg_features) { + wnc_log("framegen", "the driver refused the LSFG feature set (%s); device created without it", + vk_result_name(dr)); + dci.pNext = NULL; + fg_features = NULL; + dr = g_vk.CreateDevice(g_pd, &dci, NULL, &g_dev); + } + if (dr != VK_SUCCESS) { + LOGE("present: vkCreateDevice failed"); g_dev_state = -1; return -1; + } + vk_loader_load_device(g_dev); + g_vk.GetDeviceQueue(g_dev, g_qfam, 0, &g_queue); + if (want_sem_fd) { + g_import_sem_fd = (PFN_vkImportSemaphoreFdKHR)g_vk.GetDeviceProcAddr(g_dev, "vkImportSemaphoreFdKHR"); + VkSemaphoreCreateInfo wsci = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + if (!g_import_sem_fd || g_vk.CreateSemaphore(g_dev, &wsci, NULL, &g_wait_sem) != VK_SUCCESS) { + g_import_sem_fd = NULL; + g_wait_sem = VK_NULL_HANDLE; + } + } + wnc_log("perf", "layer buffers: the display's release fences are waited for %s", + g_import_sem_fd ? "on the GPU (VK_KHR_external_semaphore_fd)" + : "on the CPU (this driver has no VK_KHR_external_semaphore_fd)"); + if (want_hdr_md) + g_set_hdr_metadata = (PFN_vkSetHdrMetadataEXT)g_vk.GetDeviceProcAddr(g_dev, "vkSetHdrMetadataEXT"); + if (wnc_color_requested()) + wnc_log("color", "compositor device %s VK_EXT_hdr_metadata (the HDR10 swapchain for frame generation %s)", + g_set_hdr_metadata ? "enables" : "has no", + g_set_hdr_metadata ? "carries the game's mastering metadata" : "goes without the game's metadata"); + + VkCommandPoolCreateInfo pci = {.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = g_qfam}; + g_vk.CreateCommandPool(g_dev, &pci, NULL, &g_pool); + VkCommandBufferAllocateInfo cai = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = g_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = MAX_PRESENTS}; + g_vk.AllocateCommandBuffers(g_dev, &cai, g_cmds); + VkFenceCreateInfo fci = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + g_vk.CreateFence(g_dev, &fci, NULL, &g_fence); + + vkp_effects_bind_device(g_dev, g_pd, &g_memprops); + hdrc_bind_device(g_dev, &g_memprops); + blendp_bind_device(g_dev); + g_dev_state = 1; + reset_sync(); + init_black_image(); + vkp_framegen_device_ready(g_dev, g_queue, g_qfam, fg_features != NULL); + return 0; +} + +int vkp_ready(void) { return dev_init(); } + +static int swap_init_locked(void); + +static int swap_init(void) { + struct timespec ts; + int64_t now; + if (!g_window) return -1; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; + if (now < g_swap_retry_at_ns) return -1; + if (swap_init_locked() == 0) { + g_swap_retry_at_ns = 0; + g_swap_fail_logged = 0; + return 0; + } + destroy_swapchain(); + g_swap_retry_at_ns = now + 500000000LL; + if (!g_swap_fail_logged) { + g_swap_fail_logged = 1; + LOGE("present: no swapchain on the screen surface; retrying every 0.5 s"); + } + return -1; +} + +#define SWLOGE(...) do { if (!g_swap_fail_logged) LOGE(__VA_ARGS__); } while (0) + +/* Short names for the session log (the formats and colour spaces an Android surface lists). */ +static void vk_format_short(VkFormat f, char *out, size_t n) { + const char *s = NULL; + switch (f) { + case VK_FORMAT_R8G8B8A8_UNORM: s = "RGBA8"; break; + case VK_FORMAT_R8G8B8A8_SRGB: s = "RGBA8_SRGB"; break; + case VK_FORMAT_B8G8R8A8_UNORM: s = "BGRA8"; break; + case VK_FORMAT_B8G8R8A8_SRGB: s = "BGRA8_SRGB"; break; + case VK_FORMAT_R5G6B5_UNORM_PACK16: s = "RGB565"; break; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: s = "A2B10G10R10"; break; + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: s = "A2R10G10B10"; break; + case VK_FORMAT_R16G16B16A16_SFLOAT: s = "RGBA16F"; break; + default: break; + } + if (s) snprintf(out, n, "%s", s); else snprintf(out, n, "format%d", (int)f); +} +static int vk_format_bits(VkFormat f) { + switch (f) { + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return 10; + case VK_FORMAT_R16G16B16A16_SFLOAT: return 16; + default: return 8; + } +} +static const char *vk_colorspace_short(VkColorSpaceKHR c) { + switch ((int)c) { + case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: return "sRGB"; + case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT: return "P3"; + case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: return "extended-sRGB-linear"; + case VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT: return "P3-linear"; + case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT: return "DCI-P3"; + case VK_COLOR_SPACE_BT709_LINEAR_EXT: return "BT709-linear"; + case VK_COLOR_SPACE_BT709_NONLINEAR_EXT: return "BT709"; + case VK_COLOR_SPACE_BT2020_LINEAR_EXT: return "BT2020-linear"; + case VK_COLOR_SPACE_HDR10_ST2084_EXT: return "HDR10"; + case VK_COLOR_SPACE_DOLBYVISION_EXT: return "DolbyVision"; + case VK_COLOR_SPACE_HDR10_HLG_EXT: return "HLG"; + case VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT: return "AdobeRGB-linear"; + case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT: return "AdobeRGB"; + case VK_COLOR_SPACE_PASS_THROUGH_EXT: return "pass-through"; + case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT: return "extended-sRGB"; + default: return NULL; + } +} +/* One line: the total, how many colour spaces each format comes with, and every HDR-capable pair + * (HDR10 / HLG / extended sRGB, any format) - never "the first N pairs". */ +static int hdr_capable_space(VkColorSpaceKHR cs) { + int c = (int)cs; + return c == VK_COLOR_SPACE_HDR10_ST2084_EXT || c == VK_COLOR_SPACE_HDR10_HLG_EXT || + c == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT || c == VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT; +} +static void log_surface_formats(const VkSurfaceFormatKHR *f, uint32_t n) { + char per[200] = "", hdr[256] = ""; + size_t pp = 0, hp = 0; + unsigned hdr_n = 0, hdr_cut = 0; + for (uint32_t i = 0; i < n; i++) { + int first = 1; + for (uint32_t j = 0; j < i; j++) if (f[j].format == f[i].format) { first = 0; break; } + if (first && pp < sizeof(per) - 32) { + unsigned cnt = 0; + for (uint32_t j = i; j < n; j++) if (f[j].format == f[i].format) cnt++; + char nm[24]; + vk_format_short(f[i].format, nm, sizeof(nm)); + pp += (size_t)snprintf(per + pp, sizeof(per) - pp, "%s%s x%u", pp ? ", " : "", nm, cnt); + } + } + /* The deep formats (10-bit, FP16) first, so a long list never pushes them out of the line. */ + for (int pass = 0; pass < 2; pass++) + for (uint32_t i = 0; i < n; i++) { + if (!hdr_capable_space(f[i].colorSpace) || (vk_format_bits(f[i].format) > 8) != (pass == 0)) continue; + hdr_n++; + char nm[24]; + vk_format_short(f[i].format, nm, sizeof(nm)); + if (hp < sizeof(hdr) - 48) + hp += (size_t)snprintf(hdr + hp, sizeof(hdr) - hp, "%s%s/%s", hp ? ", " : "", nm, + vk_colorspace_short(f[i].colorSpace)); + else hdr_cut++; + } + wnc_log("color", "screen surface lists %u format/colour-space pairs (%s); HDR-capable: %s%s", n, per, + hdr_n ? hdr : "none", hdr_cut ? " (+more)" : ""); +} + +static int swap_init_locked(void) { + + VkAndroidSurfaceCreateInfoKHR aci = { + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, .window = g_window}; + if (g_vk.CreateAndroidSurfaceKHR(g_inst, &aci, NULL, &g_surface) != VK_SUCCESS) { + SWLOGE("present: create android surface failed"); return -1; + } + VkBool32 sup = VK_FALSE; + g_vk.GetPhysicalDeviceSurfaceSupportKHR(g_pd, g_qfam, g_surface, &sup); + if (!sup) { SWLOGE("present: queue can't present to the window"); return -1; } + + VkSurfaceCapabilitiesKHR caps; + g_vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(g_pd, g_surface, &caps); + /* Remembered for the layer path: currentTransform is the rotation the presentation engine (and + * therefore SurfaceFlinger and the DPU) applies to everything we put on this display. Anything + * but IDENTITY means every display layer we hand over is a ROTATED layer. sc_layer.c needs that + * to decide whether a second layer is affordable โ€” see sc_layer_present_overlay(). */ + g_surface_transform = caps.currentTransform; + g_surface_transform_known = 1; + g_caps_extent = caps.currentExtent; /* check_surface_changed() compares against this */ + g_surface_checked_ns = perf_now(); + /* Every format/colour-space pair the surface lists. With VK_EXT_swapchain_colorspace the Android + * WSI lists each format once per colour space (the Fold: 11 per format), so a fixed-size array + * silently drops the 10-bit and FP16 rows - which is where HDR10 lives. */ + uint32_t nfmt = 0; + g_vk.GetPhysicalDeviceSurfaceFormatsKHR(g_pd, g_surface, &nfmt, NULL); + VkSurfaceFormatKHR *fmts = nfmt ? calloc(nfmt, sizeof(*fmts)) : NULL; + if (!fmts) { SWLOGE("present: the surface lists no formats"); return -1; } + if (g_vk.GetPhysicalDeviceSurfaceFormatsKHR(g_pd, g_surface, &nfmt, fmts) < 0 || !nfmt) { + free(fmts); SWLOGE("present: vkGetPhysicalDeviceSurfaceFormatsKHR failed"); return -1; + } + VkSurfaceFormatKHR chosen = fmts[0]; + const VkSurfaceFormatKHR sdr_choice = fmts[0]; + if (wnc_color_requested() && (!g_surface_formats_said || g_swap_hdr_want)) { + g_surface_formats_said = 1; + log_surface_formats(fmts, nfmt); + } + /* HDR10 for frame generation with an HDR game (render_impl sets g_swap_hdr_want): the best format + * the surface lists with VK_COLOR_SPACE_HDR10_ST2084_EXT - 10-bit, then FP16, then 8-bit (still a + * PQ BT.2020 picture, with less precision). None listed -> the frames are tone-mapped into the + * ordinary swapchain, and that is said once. */ + g_swap_is_hdr = 0; + if (g_swap_hdr_want) { + static const VkFormat pref[] = {VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_B8G8R8A8_UNORM}; + int pick = -1; + for (unsigned k = 0; k < sizeof(pref) / sizeof(pref[0]) && pick < 0; k++) + for (uint32_t i = 0; i < nfmt; i++) + if (fmts[i].colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT && fmts[i].format == pref[k]) { + pick = (int)i; break; + } + if (pick >= 0) { + chosen = fmts[pick]; + g_swap_is_hdr = 1; + char fname[24]; + vk_format_short(chosen.format, fname, sizeof(fname)); + wnc_log("color", "screen swapchain built as HDR10 (format %d %s, HDR10_ST2084): frame-generated HDR " + "frames reach the display as PQ BT.2020%s", (int)chosen.format, fname, + vk_format_bits(chosen.format) <= 8 ? " - an 8-bit swapchain: banding possible in smooth gradients" + : ""); + } else { + g_swap_hdr_unavailable = 1; + wnc_log("color", "the screen surface lists no HDR10 swapchain format among its %u format/colour-space " + "pairs (see the line above): frames with frame generation are tone-mapped to SDR instead", + nfmt); + } + } + free(fmts); + g_swap_fmt = chosen.format; + + g_extent = caps.currentExtent; + if (g_extent.width == 0xFFFFFFFF) { + g_extent.width = ANativeWindow_getWidth(g_window); + g_extent.height = ANativeWindow_getHeight(g_window); + } + /* + one image per generated frame while frame generation is armed: every present of a scene + * frame can then be queued at once and FIFO spaces them onto consecutive vblanks. */ + g_swap_extra = vkp_framegen_extra_images(); + uint32_t want = caps.minImageCount + 1 + (uint32_t)g_swap_extra; + if (caps.maxImageCount && want > caps.maxImageCount) want = caps.maxImageCount; + + /* Use IDENTITY preTransform when the surface supports it. Setting preTransform = + * currentTransform tells the presentation engine our content is ALREADY pre-rotated by + * that amount โ€” but our blit doesn't rotate, so on a device whose surface reports a 90ยฐ + * currentTransform the display then rotates our upright frame 90ยฐ (game shows sideways). + * IDENTITY = "don't rotate what I present", which is what we want. */ + VkSurfaceTransformFlagBitsKHR pretrans = + (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) + ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : caps.currentTransform; + + VkSwapchainCreateInfoKHR sci = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = g_surface, + .minImageCount = want, .imageFormat = chosen.format, .imageColorSpace = chosen.colorSpace, + .imageExtent = g_extent, .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .preTransform = pretrans, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = VK_PRESENT_MODE_FIFO_KHR, .clipped = VK_TRUE}; + VkResult cr = g_vk.CreateSwapchainKHR(g_dev, &sci, NULL, &g_swapchain); + if (cr != VK_SUCCESS && g_swap_is_hdr) { + /* The surface listed the HDR10 pair but refused the swapchain: say so, never ask again this + * surface, and build the ordinary one now (frames tone-mapped) - never a black screen. */ + wnc_log("color", "the driver refused the HDR10 swapchain (%s): frames with frame generation are " + "tone-mapped to SDR instead", vk_result_name(cr)); + g_swap_is_hdr = 0; + g_swap_hdr_unavailable = 1; + g_swapchain = VK_NULL_HANDLE; + sci.imageFormat = sdr_choice.format; + sci.imageColorSpace = sdr_choice.colorSpace; + g_swap_fmt = sdr_choice.format; + cr = g_vk.CreateSwapchainKHR(g_dev, &sci, NULL, &g_swapchain); + } + if (cr != VK_SUCCESS) { + g_swapchain = VK_NULL_HANDLE; + SWLOGE("present: vkCreateSwapchainKHR failed (%d)", (int)cr); + return -1; + } + g_vk.GetSwapchainImagesKHR(g_dev, g_swapchain, &g_nimg, NULL); + g_images = calloc(g_nimg, sizeof(VkImage)); + g_vk.GetSwapchainImagesKHR(g_dev, g_swapchain, &g_nimg, g_images); + + wnc_log("gpu", "screen output %ux%u, %u buffers, vsync%s", g_extent.width, g_extent.height, g_nimg, + g_swap_extra ? " (frame generation: several presents per frame)" : ""); + return 0; +} + +static int memory_type(uint32_t bits, VkMemoryPropertyFlags want) { + for (uint32_t i = 0; i < g_memprops.memoryTypeCount; i++) + if ((bits & (1u << i)) && (g_memprops.memoryTypes[i].propertyFlags & want) == want) + return (int)i; + return -1; +} + +/* The small black image the letterbox bars are blitted from (see g_black_img): created, cleared and left + * in TRANSFER_SRC_OPTIMAL once, with a one-time submit, right after the device comes up. Any failure only + * means the bars are cleared the old way (the whole output). */ +static void init_black_image(void) { + g_black_state = -1; + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, .extent = {BLACK_DIM, BLACK_DIM, 1}, .mipLevels = 1, .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + if (g_vk.CreateImage(g_dev, &ici, NULL, &g_black_img) != VK_SUCCESS) { g_black_img = VK_NULL_HANDLE; return; } + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, g_black_img, &req); + int idx = memory_type(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (idx < 0) idx = memory_type(req.memoryTypeBits, 0); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = req.size, + .memoryTypeIndex = (uint32_t)(idx < 0 ? 0 : idx)}; + if (idx < 0 || g_vk.AllocateMemory(g_dev, &mai, NULL, &g_black_mem) != VK_SUCCESS) { + g_vk.DestroyImage(g_dev, g_black_img, NULL); + g_black_img = VK_NULL_HANDLE; g_black_mem = VK_NULL_HANDLE; + return; + } + g_vk.BindImageMemory(g_dev, g_black_img, g_black_mem, 0); + + VkCommandBuffer cmd = g_cmds[0]; + g_vk.ResetCommandBuffer(cmd, 0); + VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + g_vk.BeginCommandBuffer(cmd, &bi); + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + VkImageMemoryBarrier to_dst = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = g_black_img, .subresourceRange = range, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &to_dst); + VkClearColorValue black = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}; + g_vk.CmdClearColorImage(cmd, g_black_img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range); + VkImageMemoryBarrier to_src = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = g_black_img, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &to_src); + g_vk.EndCommandBuffer(cmd); + VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd}; + g_vk.ResetFences(g_dev, 1, &g_fence); + if (g_vk.QueueSubmit(g_queue, 1, &si, g_fence) == VK_SUCCESS && + g_vk.WaitForFences(g_dev, 1, &g_fence, VK_TRUE, 1000000000ULL) == VK_SUCCESS) + g_black_state = 1; + else + wnc_log("gpu", "letterbox black image unavailable: the whole output is cleared every copy-path frame"); +} + +/* Blit the black image over everything of swapchain image `img` (in TRANSFER_DST_OPTIMAL) OUTSIDE the + * destination rectangle of `covered` - the letterbox bars around a picture that fills that rectangle + * completely. Up to four rectangles, one blit command. 0 = not possible (no black image): the caller + * clears the whole image instead. */ +static int record_bars_clear(VkCommandBuffer cmd, VkImage img, const VkImageBlit *covered) { + if (g_black_state != 1 || !covered) return 0; + const int W = (int)g_extent.width, H = (int)g_extent.height; + int x0 = covered->dstOffsets[0].x, x1 = covered->dstOffsets[1].x; + int y0 = covered->dstOffsets[0].y, y1 = covered->dstOffsets[1].y; + if (x0 > x1) { int t = x0; x0 = x1; x1 = t; } + if (y0 > y1) { int t = y0; y0 = y1; y1 = t; } + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 > W) x1 = W; + if (y1 > H) y1 = H; + if (x1 <= x0 || y1 <= y0) return 0; + int rects[4][4], n = 0; + if (y0 > 0) { rects[n][0] = 0; rects[n][1] = 0; rects[n][2] = W; rects[n][3] = y0; n++; } /* top */ + if (y1 < H) { rects[n][0] = 0; rects[n][1] = y1; rects[n][2] = W; rects[n][3] = H; n++; } /* bottom */ + if (x0 > 0) { rects[n][0] = 0; rects[n][1] = y0; rects[n][2] = x0; rects[n][3] = y1; n++; } /* left */ + if (x1 < W) { rects[n][0] = x1; rects[n][1] = y0; rects[n][2] = W; rects[n][3] = y1; n++; } /* right */ + if (!n) return 1; /* the picture covers the whole output: nothing to clear */ + VkImageBlit blits[4]; + for (int i = 0; i < n; i++) + blits[i] = (VkImageBlit){.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .srcOffsets = {{0, 0, 0}, {BLACK_DIM, BLACK_DIM, 1}}, + .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .dstOffsets = {{rects[i][0], rects[i][1], 0}, {rects[i][2], rects[i][3], 1}}}; + g_vk.CmdBlitImage(cmd, g_black_img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, img, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)n, blits, VK_FILTER_NEAREST); + return 1; +} + +const char *vkp_modifier_name(uint64_t modifier) { + static char other[40]; + if (modifier == VKP_MOD_LINEAR) return "linear"; + if (modifier == VKP_MOD_QCOM_COMPRESSED) return "qcom_compressed"; + if (modifier == VKP_MOD_INVALID) return "implicit"; + snprintf(other, sizeof(other), "modifier %#llx", (unsigned long long)modifier); + return other; +} + +/* Can the driver create the image vkp_image_import_dmabuf() creates for this format+modifier + * (2D, DRM_FORMAT_MODIFIER tiling, this usage, dma-buf memory) โ€” and import it? */ +static int modifier_importable(VkFormat fmt, uint64_t modifier, VkImageUsageFlags usage) { + if (!g_vk.GetPhysicalDeviceImageFormatProperties2) return 1; /* can't ask; the create will tell */ + VkPhysicalDeviceExternalImageFormatInfo ext = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT}; + VkPhysicalDeviceImageDrmFormatModifierInfoEXT mod = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, .pNext = &ext, + .drmFormatModifier = modifier, .sharingMode = VK_SHARING_MODE_EXCLUSIVE}; + VkPhysicalDeviceImageFormatInfo2 info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, .pNext = &mod, + .format = fmt, .type = VK_IMAGE_TYPE_2D, .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .usage = usage}; + VkExternalImageFormatProperties extp = {.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES}; + VkImageFormatProperties2 props = {.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, .pNext = &extp}; + if (g_vk.GetPhysicalDeviceImageFormatProperties2(g_pd, &info, &props) != VK_SUCCESS) return 0; + return (extp.externalMemoryProperties.externalMemoryFeatures & + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) != 0; +} + +int vkp_dmabuf_modifiers(uint32_t drm_format, uint64_t *out, int max) { + if (max <= 0 || dev_init() != 0 || !g_vk.GetPhysicalDeviceFormatProperties2) return 0; + VkFormat fmt = drm_to_vk(drm_format); + VkDrmFormatModifierPropertiesListEXT list = { + .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT}; + VkFormatProperties2 fp = {.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, .pNext = &list}; + g_vk.GetPhysicalDeviceFormatProperties2(g_pd, fmt, &fp); + if (!list.drmFormatModifierCount) return 0; + VkDrmFormatModifierPropertiesEXT *props = calloc(list.drmFormatModifierCount, sizeof(*props)); + if (!props) return 0; + list.pDrmFormatModifierProperties = props; + g_vk.GetPhysicalDeviceFormatProperties2(g_pd, fmt, &fp); + + int n = 0; + for (uint32_t i = 0; i < list.drmFormatModifierCount; i++) { + uint64_t m = props[i].drmFormatModifier; + const char *why = NULL; + if (m != VKP_MOD_LINEAR && m != VKP_MOD_QCOM_COMPRESSED) why = "unknown layout"; + else if (props[i].drmFormatModifierPlaneCount != 1) why = "not single-plane"; + else if (!(props[i].drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) why = "no blit source"; + else if (!modifier_importable(fmt, m, VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) why = "not importable as a dma-buf"; + if (why) { + LOGI("dmabuf: %c%c%c%c %s reported by the driver but not advertised: %s", + drm_format & 0xff, (drm_format >> 8) & 0xff, (drm_format >> 16) & 0xff, + (drm_format >> 24) & 0xff, vkp_modifier_name(m), why); + continue; + } + if (n < max) out[n++] = m; + } + free(props); + return n; +} + +struct vkp_image *vkp_image_from_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, int w, int h, + uint32_t stride, uint32_t offset) { + return vkp_image_import_dmabuf(fd, drm_format, modifier, w, h, stride, offset, 0); +} + +int vkp_image_is_dmabuf(const struct vkp_image *img) { return img && img->dmabuf && !img->blit_dst; } + +struct vkp_image *vkp_image_import_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, int w, int h, + uint32_t stride, uint32_t offset, int as_blit_dst) { + if (modifier == MOD_INVALID || w <= 0 || h <= 0) return NULL; + if (dev_init() != 0) return NULL; + + struct vkp_image *img = calloc(1, sizeof(*img)); + if (!img) return NULL; + img->w = w; img->h = h; img->dmabuf = 1; img->blit_dst = as_blit_dst ? 1 : 0; + img->fmt = drm_to_vk(drm_format); + /* Sampled as well where the driver says this layout can be: only then can the alpha pass read it. */ + const VkImageUsageFlags sampled_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + img->sampled = !as_blit_dst && g_vk.GetPhysicalDeviceImageFormatProperties2 && + modifier_importable(img->fmt, modifier, sampled_usage); + + VkSubresourceLayout plane = {.offset = offset, .rowPitch = stride}; + VkImageDrmFormatModifierExplicitCreateInfoEXT modInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .drmFormatModifier = modifier, .drmFormatModifierPlaneCount = 1, .pPlaneLayouts = &plane}; + VkExternalMemoryImageCreateInfo extImg = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, .pNext = &modInfo, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT}; + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = &extImg, + .imageType = VK_IMAGE_TYPE_2D, .format = img->fmt, .extent = {w, h, 1}, + .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .usage = as_blit_dst ? VK_IMAGE_USAGE_TRANSFER_DST_BIT + : img->sampled ? sampled_usage : VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + VkResult cr = g_vk.CreateImage(g_dev, &ici, NULL, &img->image); + if (cr != VK_SUCCESS) { + LOGE("%s: vkCreateImage(%s, %dx%d, pitch %u, offset %u) -> %s%s", as_blit_dst ? "layer" : "dmabuf", + vkp_modifier_name(modifier), w, h, stride, offset, vk_result_name(cr), + (!as_blit_dst && modifier == VKP_MOD_QCOM_COMPRESSED) + ? ": the game's UBWC layout was refused; BANNER_WAYLAND_UBWC=0 forces linear buffers" : ""); + free(img); return NULL; + } + + int dupfd = dup(fd); + uint32_t allowed = 0xffffffff; + if (g_vk.GetMemoryFdPropertiesKHR) { + VkMemoryFdPropertiesKHR fp = {.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR}; + if (g_vk.GetMemoryFdPropertiesKHR(g_dev, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + dupfd, &fp) == VK_SUCCESS) + allowed = fp.memoryTypeBits; + } + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, img->image, &req); + uint32_t bits = req.memoryTypeBits & allowed; + int idx = -1; + for (int i = 0; i < 32; i++) if (bits & (1u << i)) { idx = i; break; } + if (idx < 0) { + LOGE("%s: no memory type can import the %s dma-buf (image types %#x, fd types %#x)", + as_blit_dst ? "layer" : "dmabuf", vkp_modifier_name(modifier), req.memoryTypeBits, allowed); + g_vk.DestroyImage(g_dev, img->image, NULL); close(dupfd); free(img); return NULL; + } + + VkImportMemoryFdInfoKHR imp = {.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .fd = dupfd}; + VkMemoryDedicatedAllocateInfo ded = {.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .pNext = &imp, .image = img->image}; + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &ded, + .allocationSize = req.size, .memoryTypeIndex = (uint32_t)idx}; + VkResult mr = g_vk.AllocateMemory(g_dev, &mai, NULL, &img->mem); + if (mr != VK_SUCCESS) { + LOGE("%s: importing the %s dma-buf (%dx%d, %llu bytes) -> %s", as_blit_dst ? "layer" : "dmabuf", + vkp_modifier_name(modifier), w, h, (unsigned long long)req.size, vk_result_name(mr)); + g_vk.DestroyImage(g_dev, img->image, NULL); close(dupfd); free(img); return NULL; + } + VkResult br = g_vk.BindImageMemory(g_dev, img->image, img->mem, 0); + if (br != VK_SUCCESS) { + LOGE("%s: vkBindImageMemory(%s dma-buf) -> %s", as_blit_dst ? "layer" : "dmabuf", + vkp_modifier_name(modifier), vk_result_name(br)); + g_vk.FreeMemory(g_dev, img->mem, NULL); g_vk.DestroyImage(g_dev, img->image, NULL); + free(img); return NULL; + } + return img; +} + +/* Whether the alpha pass can read a wl_shm image: linear B8G8R8A8, sampled with a linear filter. */ +static int shm_sampled(void) { + static int known; + if (!known) { + const VkFormatFeatureFlags need = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + VkFormatProperties2 fp = {.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2}; + if (g_vk.GetPhysicalDeviceFormatProperties2) + g_vk.GetPhysicalDeviceFormatProperties2(g_pd, VK_FORMAT_B8G8R8A8_UNORM, &fp); + known = (fp.formatProperties.linearTilingFeatures & need) == need ? 1 : -1; + } + return known == 1; +} + +struct vkp_image *vkp_image_create_shm(int w, int h) { + if (w <= 0 || h <= 0 || dev_init() != 0) return NULL; + + struct vkp_image *img = calloc(1, sizeof(*img)); + if (!img) return NULL; + img->w = w; img->h = h; + img->fmt = VK_FORMAT_B8G8R8A8_UNORM; + img->sampled = shm_sampled(); + + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, + .format = img->fmt, .extent = {w, h, 1}, .mipLevels = 1, .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_LINEAR, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | (img->sampled ? VK_IMAGE_USAGE_SAMPLED_BIT : 0), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED}; + if (g_vk.CreateImage(g_dev, &ici, NULL, &img->image) != VK_SUCCESS) { free(img); return NULL; } + + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, img->image, &req); + int idx = memory_type(req.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = req.size, .memoryTypeIndex = (uint32_t)idx}; + if (idx < 0 || g_vk.AllocateMemory(g_dev, &mai, NULL, &img->mem) != VK_SUCCESS) { + g_vk.DestroyImage(g_dev, img->image, NULL); free(img); return NULL; + } + g_vk.BindImageMemory(g_dev, img->image, img->mem, 0); + + VkImageSubresource subr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0}; + VkSubresourceLayout lay; + g_vk.GetImageSubresourceLayout(g_dev, img->image, &subr, &lay); + img->offset = lay.offset; + img->row_pitch = lay.rowPitch; + if (g_vk.MapMemory(g_dev, img->mem, 0, req.size, 0, &img->map) != VK_SUCCESS) { + vkp_image_destroy(img); return NULL; + } + return img; +} + +/* Renders are synchronous (we wait for the frame's fence), so writing between frames + * never races the GPU. */ +void vkp_image_upload_shm(struct vkp_image *img, const void *data, int stride) { + if (!img || !img->map || !data) return; + size_t rowbytes = (size_t)img->w * 4; + if ((size_t)stride < rowbytes) rowbytes = (size_t)stride; + for (int y = 0; y < img->h; y++) + memcpy((uint8_t *)img->map + img->offset + (size_t)y * img->row_pitch, + (const uint8_t *)data + (size_t)y * stride, rowbytes); +} + +int vkp_image_width(const struct vkp_image *img) { return img ? img->w : 0; } +int vkp_image_height(const struct vkp_image *img) { return img ? img->h : 0; } + +void vkp_image_destroy(struct vkp_image *img) { + if (!img) return; + if (g_dev) { + blendp_forget_image(img->image); + if (img->map) g_vk.UnmapMemory(g_dev, img->mem); + if (img->image) g_vk.DestroyImage(g_dev, img->image, NULL); + if (img->mem) g_vk.FreeMemory(g_dev, img->mem, NULL); + } + free(img); +} + +/* Map a draw into swapchain pixels through the scale mode, clipping the destination to the + * picture's region (the whole output, or its half on TOP/BOTTOM; FILL's overflow is cut here) + * and trimming the source to match. Returns 0 if nothing is left to draw. */ +static int map_draw(const struct vkp_draw *d, float kx, float ky, float off_x, float off_y, + float L, float T, float R, float B, VkImageBlit *blit) { + float x0 = off_x + d->dx * kx, y0 = off_y + d->dy * ky; + float x1 = off_x + (d->dx + d->dw) * kx, y1 = off_y + (d->dy + d->dh) * ky; + float sx0 = d->sx, sy0 = d->sy, sx1 = d->sx + d->sw, sy1 = d->sy + d->sh; + + if (x1 <= x0 || y1 <= y0 || sx1 <= sx0 || sy1 <= sy0) return 0; + if (x0 < L) { sx0 += (L - x0) / (x1 - x0) * (sx1 - sx0); x0 = L; } + if (y0 < T) { sy0 += (T - y0) / (y1 - y0) * (sy1 - sy0); y0 = T; } + if (x1 > R) { sx1 -= (x1 - R) / (x1 - x0) * (sx1 - sx0); x1 = R; } + if (y1 > B) { sy1 -= (y1 - B) / (y1 - y0) * (sy1 - sy0); y1 = B; } + + int ix0 = (int)(x0 + 0.5f), iy0 = (int)(y0 + 0.5f), ix1 = (int)(x1 + 0.5f), iy1 = (int)(y1 + 0.5f); + int isx0 = (int)sx0, isy0 = (int)sy0, isx1 = (int)(sx1 + 0.5f), isy1 = (int)(sy1 + 0.5f); + if (isx0 < 0) isx0 = 0; + if (isy0 < 0) isy0 = 0; + if (isx1 > d->img->w) isx1 = d->img->w; + if (isy1 > d->img->h) isy1 = d->img->h; + if (ix1 <= ix0 || iy1 <= iy0 || isx1 <= isx0 || isy1 <= isy0) return 0; + + *blit = (VkImageBlit){.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .srcOffsets = {{isx0, isy0, 0}, {isx1, isy1, 1}}, + .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .dstOffsets = {{ix0, iy0, 0}, {ix1, iy1, 1}}}; + return 1; +} + +static int draw_to_blit(const struct vkp_draw *d, VkImageBlit *blit) { + float R = (float)(g_map.rx + g_map.rw), B = (float)(g_map.ry + g_map.rh); + if (R > (float)g_extent.width) R = (float)g_extent.width; + if (B > (float)g_extent.height) B = (float)g_extent.height; + return map_draw(d, g_map.kx, g_map.ky, g_map.off_x, g_map.off_y, (float)g_map.rx, (float)g_map.ry, R, B, blit); +} + +/* The same draw 1:1 into the scene image (no mapping: scene pixels are the destination). */ +static int draw_to_scene_blit(const struct vkp_draw *d, int scene_w, int scene_h, VkImageBlit *blit) { + return map_draw(d, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, (float)scene_w, (float)scene_h, blit); +} + +/* One draw onto a composition target (in TRANSFER_DST_OPTIMAL): a translucent surface through the + * alpha pass where that can read it, everything else - and any surface it cannot take - as a blit. */ +static void record_draw(VkCommandBuffer cmd, const struct vkp_draw *d, const VkImageBlit *blit, VkImage target, + VkFormat target_fmt, int target_w, int target_h, VkFilter filter) { + const struct vkp_image *im = d->img; + if (d->blend && im->sampled && + blendp_draw(cmd, im->image, im->fmt, im->w, im->h, !im->dmabuf, target, target_fmt, target_w, target_h, + blit) == 0) + return; + g_vk.CmdBlitImage(cmd, im->image, im->dmabuf ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL, + target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, filter); +} + +static void destroy_scene_image(void) { + if (g_scene.img) hdrc_forget_image(g_scene.img); /* the HDR pass may have drawn into it */ + if (g_scene.img) blendp_forget_image(g_scene.img); + if (g_scene.img) g_vk.DestroyImage(g_dev, g_scene.img, NULL); + if (g_scene.mem) g_vk.FreeMemory(g_dev, g_scene.mem, NULL); + memset(&g_scene, 0, sizeof(g_scene)); +} + +/* The scene image at this size (R8G8B8A8: a blit converts every client format into it, and the + * chain's shaders read it as RGBA). 0 = ready. */ +static int ensure_scene_image(int w, int h) { + if (g_scene.img && g_scene.w == w && g_scene.h == h) return 0; + destroy_scene_image(); + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, .extent = {(uint32_t)w, (uint32_t)h, 1}, .mipLevels = 1, .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, /* the HDR encode pass writes it (frame generation) */ + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + VkResult r = g_vk.CreateImage(g_dev, &ici, NULL, &g_scene.img); + if (r != VK_SUCCESS) { LOGE("effects: scene image %dx%d: vkCreateImage %s", w, h, vk_result_name(r)); g_scene.img = VK_NULL_HANDLE; return -1; } + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, g_scene.img, &req); + int idx = memory_type(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (idx < 0) idx = memory_type(req.memoryTypeBits, 0); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = req.size, + .memoryTypeIndex = (uint32_t)(idx < 0 ? 0 : idx)}; + r = g_vk.AllocateMemory(g_dev, &mai, NULL, &g_scene.mem); + if (r != VK_SUCCESS) { LOGE("effects: scene image %dx%d: vkAllocateMemory %s", w, h, vk_result_name(r)); destroy_scene_image(); return -1; } + g_vk.BindImageMemory(g_dev, g_scene.img, g_scene.mem, 0); + g_scene.w = w; g_scene.h = h; + wnc_log("effects", "scene image %dx%d for the effect chain", w, h); + return 0; +} + +/* One of the HDR composition's device-local images at this size and format. 0 = ready. */ +static int ensure_img(struct vkp_img_slot *s, int w, int h, VkFormat fmt, VkImageUsageFlags usage, const char *what) { + if (s->img && s->w == w && s->h == h && s->fmt == fmt) return 0; + if (s->img) hdrc_forget_image(s->img); + if (s->img) blendp_forget_image(s->img); + if (s->img) g_vk.DestroyImage(g_dev, s->img, NULL); + if (s->mem) g_vk.FreeMemory(g_dev, s->mem, NULL); + memset(s, 0, sizeof(*s)); + VkImageCreateInfo ici = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = fmt, + .extent = {(uint32_t)w, (uint32_t)h, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + VkResult r = g_vk.CreateImage(g_dev, &ici, NULL, &s->img); + if (r != VK_SUCCESS) { LOGE("color: %s image %dx%d: vkCreateImage %s", what, w, h, vk_result_name(r)); s->img = VK_NULL_HANDLE; return -1; } + VkMemoryRequirements req; + g_vk.GetImageMemoryRequirements(g_dev, s->img, &req); + int idx = memory_type(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (idx < 0) idx = memory_type(req.memoryTypeBits, 0); + VkMemoryAllocateInfo mai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = req.size, + .memoryTypeIndex = (uint32_t)(idx < 0 ? 0 : idx)}; + r = g_vk.AllocateMemory(g_dev, &mai, NULL, &s->mem); + if (r != VK_SUCCESS) { + LOGE("color: %s image %dx%d: vkAllocateMemory %s", what, w, h, vk_result_name(r)); + g_vk.DestroyImage(g_dev, s->img, NULL); + memset(s, 0, sizeof(*s)); + return -1; + } + g_vk.BindImageMemory(g_dev, s->img, s->mem, 0); + s->w = w; s->h = h; s->fmt = fmt; + char fname[24]; + vk_format_short(fmt, fname, sizeof(fname)); + wnc_log("color", "%s image %dx%d (%s)", what, w, h, fname); + return 0; +} + +/* The HDR composition (hdr_compose.h): clear the mixed image, blit every draw into it 1:1 in its own + * encoding, then encode it into `out` (left in TRANSFER_DST_OPTIMAL). The caller has already taken the + * draws' images (queue-family acquire / host-write barriers). Which rectangles are HDR comes from + * hf->is_hdr: listed top draw first, down to the lowest HDR draw (everything under that is SDR). + * 0 = done, -1 = the HDR pass is unavailable (nothing was recorded into `out`). */ +static int compose_hdr(VkCommandBuffer cmd, const struct vkp_draw *draws, int n, const struct vkp_hdr_frame *hf, + int scene_w, int scene_h, VkImage out, VkFormat out_fmt, enum hdrc_mode mode) { + if (!hf || !hf->is_hdr || n <= 0) return -1; + if (ensure_img(&g_mixed, scene_w, scene_h, HDR_FMT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + "HDR composition (mixed)") != 0) + return -1; + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + VkImageMemoryBarrier b = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = g_mixed.img, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &b); + VkClearColorValue black = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}; + g_vk.CmdClearColorImage(cmd, g_mixed.img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range); + VkMemoryBarrier mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &mb, 0, NULL, 0, NULL); + + struct hdrc_params p = {.mode = mode, .sdr_white_nits = wnc_color_sdr_white(), + .peak_nits = hf->peak_nits > 0.0f ? hf->peak_nits : 1000.0f}; + int lowest_hdr = -1; + for (int i = 0; i < n; i++) { + VkImageBlit blit; + if (!draws[i].img || !draw_to_scene_blit(&draws[i], scene_w, scene_h, &blit)) continue; + /* A blit converts UNORM to UNORM by value: 8-bit sRGB and 10-bit PQ both land unchanged. */ + record_draw(cmd, &draws[i], &blit, g_mixed.img, HDR_FMT, scene_w, scene_h, VK_FILTER_LINEAR); + if (hf->is_hdr[i] && lowest_hdr < 0) lowest_hdr = i; + } + static int truncated_said; + if (lowest_hdr >= 0) { + int i; + for (i = n - 1; i >= lowest_hdr && p.count < HDRC_MAX_RECTS; i--) { + VkImageBlit blit; + if (!draws[i].img || !draw_to_scene_blit(&draws[i], scene_w, scene_h, &blit)) continue; + p.rects[p.count][0] = (float)blit.dstOffsets[0].x; p.rects[p.count][1] = (float)blit.dstOffsets[0].y; + p.rects[p.count][2] = (float)blit.dstOffsets[1].x; p.rects[p.count][3] = (float)blit.dstOffsets[1].y; + if (hf->is_hdr[i]) p.hdr_mask |= 1u << p.count; + p.count++; + } + if (i >= lowest_hdr && !truncated_said) { + truncated_said = 1; + wnc_log("color", "HDR composition: more than %d windows are stacked over the HDR game - the lowest " + "ones are treated as SDR", HDRC_MAX_RECTS); + } + } + return hdrc_encode(cmd, g_mixed.img, out, out_fmt, scene_w, scene_h, &p); +} + +/* VK_ERROR_DEVICE_LOST: nothing on this device works any more, and there is no way back short + * of a new session. Say so once and stop touching the swapchain; clients keep being paced by the + * compositor (see pace_without_output) so they don't wedge, they just aren't shown. */ +static void device_lost(const char *where) { + if (g_dev_state == -2) return; + g_dev_state = -2; + g_base_black = 0; + vkp_framegen_device_lost(); + wnc_log("error", "GPU device lost (VK_ERROR_DEVICE_LOST in %s): the compositor has stopped presenting; " + "restart the session", where); + for (uint32_t i = 0; i < g_nimg; i++) blendp_forget_image(g_images[i]); + if (g_swapchain) g_vk.DestroySwapchainKHR(g_dev, g_swapchain, NULL); + g_swapchain = VK_NULL_HANDLE; + if (g_surface) g_vk.DestroySurfaceKHR(g_inst, g_surface, NULL); + g_surface = VK_NULL_HANDLE; + free(g_images); + g_images = NULL; + g_nimg = 0; +} + +static const char *vk_result_name(VkResult r) { + switch (r) { + case VK_ERROR_OUT_OF_DATE_KHR: return "OUT_OF_DATE"; + case VK_SUBOPTIMAL_KHR: return "SUBOPTIMAL"; + case VK_ERROR_SURFACE_LOST_KHR: return "SURFACE_LOST"; + case VK_ERROR_DEVICE_LOST: return "DEVICE_LOST"; + case VK_TIMEOUT: return "TIMEOUT"; + case VK_NOT_READY: return "NOT_READY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_OUT_OF_HOST_MEMORY: return "OUT_OF_HOST_MEMORY"; + case VK_ERROR_FORMAT_NOT_SUPPORTED: return "FORMAT_NOT_SUPPORTED"; + case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "INVALID_EXTERNAL_HANDLE"; + case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT"; + default: { + static char other[24]; + snprintf(other, sizeof(other), "error %d", (int)r); + return other; + } + } +} + +/* ---------------------------------------------------------------- the compositor pass */ + +/* The mapping/blit stage for one present: clear the swapchain image to black (the letterbox), + * blit `src` (src_w x src_h, in GENERAL: the effects chain's result or a generated frame, either + * standing for the whole scene) through the scene -> output mapping, and leave the image ready + * to present. */ +static void record_screen_blit(VkCommandBuffer cmd, VkImage src, int src_w, int src_h, + int scene_w, int scene_h, uint32_t img, VkFilter filter) { + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + VkImageMemoryBarrier pre[2] = { + {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = g_images[img], .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}, + /* Whatever wrote src last (blits, the effects chain, a compute dispatch, in this or an + * earlier submission on this queue) must be visible to the blit's read. */ + {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 2, pre); + struct vkp_image tmp = {.w = src_w, .h = src_h}; /* only its size is looked at */ + struct vkp_draw d = {&tmp, 0, 0, (float)src_w, (float)src_h, 0, 0, scene_w, scene_h}; + VkImageBlit blit; + const int have_blit = draw_to_blit(&d, &blit); + /* The result fills its whole mapped rectangle, so only the letterbox bars around it need black. */ + if (!have_blit || !record_bars_clear(cmd, g_images[img], &blit)) { + VkClearColorValue black = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}; + g_vk.CmdClearColorImage(cmd, g_images[img], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range); + } + VkMemoryBarrier mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 1, &mb, 0, NULL, 0, NULL); + if (have_blit) + g_vk.CmdBlitImage(cmd, src, VK_IMAGE_LAYOUT_GENERAL, g_images[img], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, filter); + VkImageMemoryBarrier b_present = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = g_images[img], + .subresourceRange = range, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 1, &b_present); +} + +/* Acquire a swapchain image for present slot k. On the frame's FIRST acquire the swapchain is + * (re)created when needed and rebuilt once when the surface changed under us (OUT_OF_DATE after a + * resize/rotation, SURFACE_LOST when the window was torn down) - once, so a frame is not lost to + * a recreate that would have succeeded. Later slots have presents in flight on this swapchain, + * so they never rebuild; they fail and the frame ends early. 0 on success. */ +static int acquire_image(int k, int first, uint32_t *img, VkResult *ar_out) { + for (int attempt = 0; ; attempt++) { + if (!g_swapchain && (!first || swap_init() != 0)) return -1; + /* A bounded wait: a surface that went away without telling us must not park the + * compositor thread forever (clients are paced from this thread). */ + const int64_t t_acq = perf_now(); + VkResult ar = g_vk.AcquireNextImageKHR(g_dev, g_swapchain, 1000000000ULL, g_acqs[k], VK_NULL_HANDLE, img); + perf_add(&g_perf.acquire_ns, &g_perf.acquire_max_ns, &g_perf.acquires, perf_now() - t_acq); + if (ar == VK_SUCCESS || ar == VK_SUBOPTIMAL_KHR) { *ar_out = ar; return 0; } + if (ar == VK_ERROR_DEVICE_LOST) { device_lost("acquire"); return -1; } + if (ar == VK_ERROR_OUT_OF_DATE_KHR || ar == VK_ERROR_SURFACE_LOST_KHR) { + wnc_log("gpu", "screen surface %s on acquire: rebuilding the swapchain", vk_result_name(ar)); + destroy_swapchain(); + if (first && attempt == 0) continue; + return -1; + } + wnc_log("error", "present: acquire %d failed (%s %d)", k, vk_result_name(ar), (int)ar); + if (ar == VK_TIMEOUT || ar == VK_NOT_READY) return -1; + destroy_swapchain(); /* anything else: start over next frame */ + return -1; + } +} + +/* vkp_render_plain: the base surface's black frame with the compositor pass skipped (HDR game on its + * layer, compositor.c) - neither the effects chain nor frame generation runs, and so neither paces + * this loop with extra presents. */ +static int g_plain_frame; +int vkp_render_plain(int scene_w, int scene_h) { + g_plain_frame = 1; + int r = vkp_render(scene_w, scene_h, NULL, 0); + g_plain_frame = 0; + return r; +} + +/* A surface that changed size or transform under a swapchain nobody presents to (the base surface + * while the game is on its display layer) never reports OUT_OF_DATE, so ask it - at most every + * 100 ms, on the compositor thread - and tear the swapchain down when it no longer matches; the next + * frame rebuilds it (and, in layer mode, puts one black frame on it). */ +static void check_surface_changed(void) { + if (!g_swapchain || !g_surface || g_dev_state != 1) return; + const int64_t now = perf_now(); + if (now - g_surface_checked_ns < 100000000LL) return; + g_surface_checked_ns = now; + VkSurfaceCapabilitiesKHR caps; + if (g_vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(g_pd, g_surface, &caps) != VK_SUCCESS) return; + if (caps.currentExtent.width == g_caps_extent.width && caps.currentExtent.height == g_caps_extent.height && + caps.currentTransform == g_surface_transform) + return; + wnc_log("gpu", "screen surface changed (%ux%u -> %ux%u, transform %d -> %d): rebuilding the swapchain", + g_caps_extent.width, g_caps_extent.height, caps.currentExtent.width, caps.currentExtent.height, + (int)g_surface_transform, (int)caps.currentTransform); + destroy_swapchain(); +} + +int vkp_base_black(int scene_w, int scene_h) { + vkp_apply_window_request(); + check_surface_changed(); + if (g_base_black && g_swapchain && g_window && g_dev_state == 1) { + g_perf.base_kept++; + return 0; /* the black frame from before is still what the base surface shows */ + } + int r = vkp_render_plain(scene_w, scene_h); + if (r == 0) g_perf.base_presents++; + return r; +} + +void vkp_perf_take(struct vkp_perf *out) { + *out = g_perf; + memset(&g_perf, 0, sizeof(g_perf)); +} + +int vkp_can_wait_sync_fd(void) { return g_import_sem_fd != NULL && g_wait_sem != VK_NULL_HANDLE; } + +/* Hand a sync_file to the next submit as a GPU wait (temporary import into g_wait_sem; the driver + * owns the fd from then on). Returns 1 when the submit must wait on g_wait_sem, 0 when there is nothing + * to wait on - the fd was -1, or it could not be imported and was waited for on the CPU (bounded) and + * closed instead. -1 = the display is still reading the buffer after 100 ms: do not write it. */ +static int take_wait_fd(int fd) { + if (fd < 0) return 0; + if (vkp_can_wait_sync_fd()) { + VkImportSemaphoreFdInfoKHR ii = {.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .semaphore = g_wait_sem, .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, .fd = fd}; + if (g_import_sem_fd(g_dev, &ii) == VK_SUCCESS) { + g_perf.gpu_release_waits++; + return 1; + } + static int said; + if (!said) { + said = 1; + wnc_log("perf", "layer buffers: importing a release fence as a GPU wait failed; waiting for it on the CPU"); + } + } + struct pollfd p = {.fd = fd, .events = POLLIN}; + int r; + do { r = poll(&p, 1, 100); } while (r < 0 && errno == EINTR); + close(fd); + return r == 0 ? -1 : 0; +} + +/* The HDR10 swapchain's metadata: the game's SMPTE 2086 / CTA-861.3 values (its image description) via + * VK_EXT_hdr_metadata, once per swapchain and description. A game that gave none gets none. */ +static void send_hdr_metadata(const struct wnc_color *c) { + static int said_none, said_nodesc; + if (!c) return; + if (!g_set_hdr_metadata) { + if (!said_none) { said_none = 1; wnc_log("color", "HDR10 swapchain without the game's metadata: this device " + "has no VK_EXT_hdr_metadata (the display uses its defaults)"); } + return; + } + if (!c->has_st2086 && !c->has_cta861) { + if (said_nodesc != (int)c->identity) { + said_nodesc = (int)c->identity; + wnc_log("color", "HDR10 swapchain: image description #%u carries no HDR metadata (the game sent none), " + "so none is set - the display uses its defaults", c->identity); + } + return; + } + VkHdrMetadataEXT md = {.sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT}; + if (c->has_st2086) { + md.displayPrimaryRed = (VkXYColorEXT){c->red[0], c->red[1]}; + md.displayPrimaryGreen = (VkXYColorEXT){c->green[0], c->green[1]}; + md.displayPrimaryBlue = (VkXYColorEXT){c->blue[0], c->blue[1]}; + md.whitePoint = (VkXYColorEXT){c->white[0], c->white[1]}; + md.maxLuminance = c->max_lum; + md.minLuminance = c->min_lum; + } + if (c->has_cta861) { + md.maxContentLightLevel = c->max_cll; + md.maxFrameAverageLightLevel = c->max_fall; + } + g_set_hdr_metadata(g_dev, 1, &g_swapchain, &md); + wnc_log("color", "HDR10 swapchain: the game's metadata set via VK_EXT_hdr_metadata (image description #%u: %s)", + c->identity, c->text); +} + +/* The whole present: vkp_render (hf == NULL, exactly as it always was) and vkp_render_hdr (a scene with + * HDR draws: composed into one encoding, hdr_compose.h). */ +static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *how) { + if (how) *how = 0; + vkp_apply_window_request(); + if (g_dev_state == -2) return -1; + if (dev_init() != 0 || !g_window || scene_w <= 0 || scene_h <= 0) return -1; + + /* Frame generation queues several presents per scene frame and needs swapchain images for + * them: rebuild when that changes (armed, disarmed, another multiplier). */ + if (g_swapchain && vkp_framegen_extra_images() != g_swap_extra) { + wnc_log("gpu", "frame generation now wants %d present%s per frame: rebuilding the swapchain", + 1 + vkp_framegen_extra_images(), vkp_framegen_extra_images() ? "s" : ""); + destroy_swapchain(); + } + /* HDR frames presented here (frame generation with an HDR game) want an HDR10 swapchain - unless the + * drawer's HDR output switch is off (tone-mapped) - nothing else does. A change of that rebuilds the + * swapchain (only in HDR sessions: g_colorspace_ext). */ + const int want_hdr = hf && !hf->tonemap && g_colorspace_ext && !g_swap_hdr_unavailable; + if (g_swapchain && want_hdr != g_swap_is_hdr) { + wnc_log("color", want_hdr ? "HDR frames with frame generation: rebuilding the screen swapchain as HDR10" + : hf ? "HDR output switched off: rebuilding the screen swapchain as SDR (frames tone-mapped)" + : "no HDR frames through the screen swapchain any more: rebuilding it as SDR"); + destroy_swapchain(); + } + g_swap_hdr_want = want_hdr; + + uint32_t img = 0; + VkResult ar; + if (acquire_image(0, 1, &img, &ar) != 0) return -1; + update_map(scene_w, scene_h); + /* A new HDR10 swapchain, or a new image description on it: hand the game's metadata over. */ + if (g_swap_is_hdr && hf && hf->color && hf->color->identity != g_swap_md_identity) { + g_swap_md_identity = hf->color->identity; + send_hdr_metadata(hf->color); + } + + /* The compositor pass - scene -> effects -> frame generation -> mapping/blit -> swapchain - + * composes the scene off-screen when either stage needs the whole frame: the screen-effect + * chain (effects_chain.c) or frame generation (framegen_bridge.c). With both off the draws are + * blitted straight through the mapping into the swapchain image, as they always were; only the + * blit filter follows the scaling mode. */ + const int fx = !g_plain_frame && vkp_effects_active(); + const int fg = !g_plain_frame && vkp_framegen_active(); + /* HDR frames with frame generation through an HDR10 swapchain: the scene, the effects and the + * engine's frames in FP16 (FG_HDR_FMT) where the engine can take that format - more than 8 bits of + * PQ through interpolation - else the 8-bit scene, as before. */ + const int deep = hf && n > 0 && fg && g_swap_is_hdr && !hf->tonemap && vkp_framegen_format_ok(FG_HDR_FMT) && + ensure_img(&g_fgscene, scene_w, scene_h, FG_HDR_FMT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + "HDR frame generation (scene)") == 0; + { + static int said = -1; + if (fg && hf && g_swap_is_hdr && deep != said) { + said = deep; + char sw[24]; + vk_format_short(g_swap_fmt, sw, sizeof(sw)); + if (deep) + wnc_log("color", "frame generation runs on the HDR picture in FP16 (RGBA16F): more than 8 bits of PQ " + "through interpolation, into the HDR10 swapchain (%s)", sw); + else + wnc_log("color", "frame generation runs on the HDR picture in 8 bits (the engine cannot take FP16 here), " + "into the HDR10 swapchain (%s): banding possible in smooth gradients", sw); + } + } + /* An HDR scene always takes the pass: its draws are composed into one encoding first. */ + const int pass = deep || ((fx || fg || (hf && n > 0)) && ensure_scene_image(scene_w, scene_h) == 0); + VkImage scene_img = deep ? g_fgscene.img : g_scene.img; + const VkFormat scene_fmt = deep ? FG_HDR_FMT : SCENE_FMT; + const VkFilter blit_filter = vkp_effects_blit_filter(); + + VkCommandBuffer cmd = g_cmds[0]; + g_vk.ResetCommandBuffer(cmd, 0); + VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + g_vk.BeginCommandBuffer(cmd, &bi); + blendp_begin_frame(); + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + + /* Source images: take dmabufs from the client's queue family, move shm images to + * GENERAL once (host writes stay visible across frames: memory is coherent and each + * frame is a new submission). One barrier per distinct image. The destination joins them: + * the swapchain image on the direct path, the scene image on the compositor pass (the + * per-present blits transition the swapchain images themselves there). */ + VkImageMemoryBarrier *bars = calloc((size_t)n + 1, sizeof(*bars)); + int nb = 0; + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = pass ? scene_img : g_images[img], .subresourceRange = range, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + for (int i = 0; i < n; i++) { + struct vkp_image *im = draws[i].img; + int seen = 0; + for (int j = 0; j < i; j++) if (draws[j].img == im) { seen = 1; break; } + if (seen || !im) continue; + if (im->dmabuf) { + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = g_qfam, + .image = im->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; + } else if (!im->in_general) { + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = im->image, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; + im->in_general = 1; + } + } + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | + (pass ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : 0), + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, (uint32_t)nb, bars); + free(bars); + + /* Clear the composition target to black (the letterbox on the direct path, the desktop's + * background on the pass) before the draws land on it. A draw that covers the whole scene + * overwrites every pixel of its rectangle (blits never blend), so then only what lies outside it + * needs black: the letterbox bars on the direct path, nothing at all on the pass (the scene image + * is exactly the scene). Anything else - no such draw, the HDR composition - clears the whole + * target as before. */ + VkImage target = pass ? scene_img : g_images[img]; + int covered = 0; + VkImageBlit cover_blit; + if (!(hf && pass)) { + for (int i = n - 1; i >= 0 && !covered; i--) { + const struct vkp_draw *d = &draws[i]; + if (!d->img || d->blend || d->dx > 0 || d->dy > 0 || d->dx + d->dw < scene_w || d->dy + d->dh < scene_h) continue; + covered = pass ? draw_to_scene_blit(d, scene_w, scene_h, &cover_blit) : draw_to_blit(d, &cover_blit); + } + } + const int cleared = covered && (pass || record_bars_clear(cmd, target, &cover_blit)); + if (!cleared) { + VkClearColorValue black = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}; + g_vk.CmdClearColorImage(cmd, target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range); + } + { + VkMemoryBarrier mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 1, &mb, 0, NULL, 0, NULL); + } + + /* 1. Scene: the draws in order, 1:1 into the scene image (pass) or mapped onto the output. An HDR + * scene is composed into ONE encoding instead (hdr_compose.h): PQ BT.2020 for an HDR10 + * swapchain, tone-mapped sRGB for an ordinary one; the scene image comes back in + * TRANSFER_DST_OPTIMAL like the blits leave it. If that pass is unavailable, the blits below. */ + int drawn = 0; + int hdr_how = 0; + if (hf && pass && n > 0 && + compose_hdr(cmd, draws, n, hf, scene_w, scene_h, scene_img, scene_fmt, + g_swap_is_hdr ? HDRC_OUT_PQ : HDRC_OUT_SDR) == 0) { + hdr_how = g_swap_is_hdr ? 1 : 2; + drawn = n; + } + for (int i = 0; i < n && !hdr_how; i++) { + VkImageBlit blit; + if (!draws[i].img) continue; + if (pass ? !draw_to_scene_blit(&draws[i], scene_w, scene_h, &blit) : !draw_to_blit(&draws[i], &blit)) continue; + record_draw(cmd, &draws[i], &blit, target, pass ? scene_fmt : g_swap_fmt, + pass ? scene_w : (int)g_extent.width, pass ? scene_h : (int)g_extent.height, + pass ? VK_FILTER_LINEAR : blit_filter); + drawn++; + } + + int ngen = 0; + VkImage gens[VKP_FG_MAX_GENERATIONS] = {VK_NULL_HANDLE}; + VkImage result = scene_img; + int rw = scene_w, rh = scene_h; + if (!pass) { + VkImageMemoryBarrier b_present = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = g_images[img], + .subresourceRange = range, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 1, &b_present); + } else { + /* 2. Effects (the hook): scaling to the scene's mapped size, then the effects, in the X11 + * order. The result stands for the whole scene (rw x rh) and comes back in + * TRANSFER_SRC_OPTIMAL; with nothing on the scene stays as composited. */ + VkImageLayout result_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + if (fx) { + int mapped_w = (int)(scene_w * g_map.kx + 0.5f), mapped_h = (int)(scene_h * g_map.ky + 0.5f); + /* This path's scene is 8-bit. Set only where the chain really runs: a plain frame (the black + * base under an HDR picture) must not flip the chain's format back every frame. */ + vkp_effects_set_formats(scene_fmt, scene_fmt); + result = vkp_effects_run(cmd, scene_img, scene_w, scene_h, mapped_w, mapped_h, &rw, &rh); + result_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } + /* Hand-off: from here the frame lives in GENERAL - the frame-generation engines copy and + * sample it there, and every screen blit reads it there. Whatever wrote it last (the + * composite blits or the chain's last pass) is made visible to both. */ + VkImageMemoryBarrier b_general = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = result_layout, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = result, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, 0, NULL, 0, NULL, 1, &b_general); + + /* 3. Frame generation (the hook): the result is frame N; the engine interpolates between + * N-1 and N and hands back the frames that belong in between. Shown first, N last. */ + if (fg) { + ngen = vkp_framegen_run(cmd, result, VK_NULL_HANDLE, rw, rh, scene_fmt, gens); + if (ngen < 0) ngen = 0; + if (ngen > VKP_FG_MAX_GENERATIONS) ngen = VKP_FG_MAX_GENERATIONS; + } + + /* 4. Mapping/blit: the first present carries generated frame 0, or the result itself. */ + record_screen_blit(cmd, ngen ? gens[0] : result, rw, rh, scene_w, scene_h, img, blit_filter); + } + g_vk.EndCommandBuffer(cmd); + + /* One submit + present per slot: generated frames first, the real frame last. The frame + * fence rides on the last submit (a fence signal is ordered after everything submitted + * earlier on the queue, so it still covers all of this frame's work). With FIFO the + * presentation engine shows them on consecutive vblanks: that IS the pacing, no sleeps. */ + const int presents = 1 + ngen; + int fence_submitted = 0, presented_gen = 0; + VkResult pr = VK_SUCCESS; + g_vk.ResetFences(g_dev, 1, &g_fence); + for (int k = 0; k < presents; k++) { + if (k > 0) { + VkResult ark; + if (acquire_image(k, 0, &img, &ark) != 0) { + wnc_log("framegen", "present %d/%d: no swapchain image; the frame ends early", k + 1, presents); + pr = VK_ERROR_OUT_OF_DATE_KHR; + break; + } + if (ark == VK_SUBOPTIMAL_KHR) ar = ark; + VkCommandBuffer ck = g_cmds[k]; + g_vk.ResetCommandBuffer(ck, 0); + g_vk.BeginCommandBuffer(ck, &bi); + record_screen_blit(ck, k < ngen ? gens[k] : result, rw, rh, scene_w, scene_h, img, blit_filter); + g_vk.EndCommandBuffer(ck); + } + const int last = (k + 1 == presents); + VkPipelineStageFlags wait = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, + .pWaitSemaphores = &g_acqs[k], .pWaitDstStageMask = &wait, .commandBufferCount = 1, + .pCommandBuffers = &g_cmds[k], .signalSemaphoreCount = 1, .pSignalSemaphores = &g_rnds[k]}; + VkResult qr = g_vk.QueueSubmit(g_queue, 1, &si, last ? g_fence : VK_NULL_HANDLE); + if (qr != VK_SUCCESS) { + if (qr == VK_ERROR_DEVICE_LOST) device_lost("submit"); + else LOGE("present: submit %d/%d failed (%d)", k + 1, presents, (int)qr); + /* The acquired image is never presented; the swapchain would be stuck with it. */ + if (g_dev_state != -2) destroy_swapchain(); + return -1; + } + fence_submitted = last; + VkPresentInfoKHR pi = {.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, + .pWaitSemaphores = &g_rnds[k], .swapchainCount = 1, + .pSwapchains = &g_swapchain, .pImageIndices = &img}; + const int64_t t_pres = perf_now(); + pr = g_vk.QueuePresentKHR(g_queue, &pi); + perf_add(&g_perf.present_ns, &g_perf.present_max_ns, &g_perf.presents, perf_now() - t_pres); + if (k < ngen) presented_gen++; + if (pr == VK_ERROR_OUT_OF_DATE_KHR || pr == VK_ERROR_SURFACE_LOST_KHR || pr == VK_ERROR_DEVICE_LOST) break; + } + if (!fence_submitted) { + /* Ended early: an empty submit carrying the fence is ordered after everything queued + * above, so the wait below still means "this frame's work is done". */ + VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO}; + g_vk.QueueSubmit(g_queue, 1, &si, g_fence); + } + VkResult fr = timed_wait(g_fence, UINT64_MAX); + /* Counted on every screen frame, not just generating ones: the HUD's output-frame source + * reads these totals even when the engine is off (a panel-side interpolator is still on). */ + vkp_framegen_presented(presented_gen); + if (fr == VK_ERROR_DEVICE_LOST || pr == VK_ERROR_DEVICE_LOST) { device_lost("present"); return -1; } + if (pr == VK_ERROR_OUT_OF_DATE_KHR || pr == VK_ERROR_SURFACE_LOST_KHR) { + /* The surface changed or went away under this frame: rebuild before the next one. */ + wnc_log("gpu", "screen surface %s on present: rebuilding the swapchain", vk_result_name(pr)); + destroy_swapchain(); + return -1; + } else if (pr == VK_SUBOPTIMAL_KHR || ar == VK_SUBOPTIMAL_KHR) { + /* Expected and harmless here: the swapchain uses IDENTITY preTransform on purpose (see + * swap_init), so a rotated panel reports SUBOPTIMAL on every frame. Rebuilding would change + * nothing (and a rebuild per frame would be far worse), so present as is; say so once. */ + static int said; + if (!said) { said = 1; wnc_log("gpu", "surface reports SUBOPTIMAL (panel rotation); presenting as is"); } + } else if (pr != VK_SUCCESS) { + wnc_log("error", "present: vkQueuePresentKHR failed (%s %d)", vk_result_name(pr), (int)pr); + destroy_swapchain(); + return -1; + } + + /* What the base surface shows from now on: black only after a plain frame with nothing drawn + * (vkp_base_black keeps presenting nothing while that holds); anything else it presented is + * content, and the next layer frame must put a black frame back under the layers first. */ + g_base_black = (g_plain_frame && n == 0 && !hf) ? 1 : 0; + + /* Signal the app once, on the first real client frame reaching the screen, so the + * launch/preloader overlay can dismiss (wayland has no XServer window-content hook). */ + if (drawn && !g_first_frame_done) { + g_first_frame_done = 1; + wnc_on_first_frame(); + } + if (how) *how = hdr_how; + return 0; +} + +int vkp_render(int scene_w, int scene_h, const struct vkp_draw *draws, int n) { + return render_impl(scene_w, scene_h, draws, n, NULL, NULL); +} + +int vkp_render_hdr(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *how) { + return render_impl(scene_w, scene_h, draws, n, hf, how); +} + +/* ---------------------------------------------------------------- layer mode helpers */ + +/* Degrees the display rotates our layers by: 0, 90, 180 or 270; -1 before the swapchain exists. + * Read from the surface, so it follows the panel's install orientation and the session's own + * orientation together, on any device. */ +int vkp_surface_rotation_degrees(void) { + if (!g_surface_transform_known) return -1; + switch (g_surface_transform) { + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: return 90; + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: return 180; + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: return 270; + default: return 0; /* IDENTITY, and the mirrored forms we never see here */ + } +} + +ANativeWindow *vkp_window(void) { return g_window; } + +void vkp_signal_first_frame(void) { + if (g_first_frame_done) return; + g_first_frame_done = 1; + wnc_on_first_frame(); +} + +int vkp_update_map(int scene_w, int scene_h) { + if (g_dev_state == -2 || dev_init() != 0 || !g_window || scene_w <= 0 || scene_h <= 0) return -1; + check_surface_changed(); /* layer mode may present nothing on the base surface for a long time */ + if (!g_swapchain && swap_init() != 0) return -1; + update_map(scene_w, scene_h); + return g_map.valid ? 0 : -1; +} + +int vkp_map_rect(int img_w, int img_h, int scene_w, int scene_h, int out[8]) { + struct vkp_image tmp = {.w = img_w, .h = img_h}; /* only its size is looked at */ + struct vkp_draw d = {&tmp, 0, 0, (float)img_w, (float)img_h, 0, 0, scene_w, scene_h}; + return vkp_map_draw(&d, out); +} + +int vkp_map_draw(const struct vkp_draw *d, int out[8]) { + VkImageBlit blit; + if (!d || !d->img || !g_map.valid || !draw_to_blit(d, &blit)) return 0; + out[0] = blit.srcOffsets[0].x; out[1] = blit.srcOffsets[0].y; + out[2] = blit.srcOffsets[1].x; out[3] = blit.srcOffsets[1].y; + out[4] = blit.dstOffsets[0].x; out[5] = blit.dstOffsets[0].y; + out[6] = blit.dstOffsets[1].x; out[7] = blit.dstOffsets[1].y; + return 1; +} + +/* Copy src (a client frame) into dst (a layer pool buffer) 1:1 and wait for it. Both images are + * owned by the "foreign" queue family (the game's driver / the display) between our uses, so each + * use acquires them and the destination is released back for the display to read. */ +int vkp_blit_image(struct vkp_image *src, struct vkp_image *dst, int wait_fd) { + if (!src || !dst || !dst->blit_dst || g_dev_state == -2 || dev_init() != 0) { + if (wait_fd >= 0) close(wait_fd); + return -1; + } + const int gpu_wait = take_wait_fd(wait_fd); /* the display's release of dst, before the copy writes it */ + if (gpu_wait < 0) return -1; + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + + VkCommandBuffer g_cmd = g_cmds[0]; + g_vk.ResetCommandBuffer(g_cmd, 0); + VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + g_vk.BeginCommandBuffer(g_cmd, &bi); + VkImageMemoryBarrier acq[2] = { + {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = src->dmabuf ? VK_QUEUE_FAMILY_FOREIGN_EXT : VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = src->dmabuf ? g_qfam : VK_QUEUE_FAMILY_IGNORED, + .image = src->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}, + {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = g_qfam, + .image = dst->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}}; + if (!src->dmabuf) { /* shm image: host-written, GENERAL */ + acq[0].oldLayout = src->in_general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_PREINITIALIZED; + acq[0].newLayout = VK_IMAGE_LAYOUT_GENERAL; + acq[0].srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + src->in_general = 1; + } + g_vk.CmdPipelineBarrier(g_cmd, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 2, acq); + int bw = src->w < dst->w ? src->w : dst->w, bh = src->h < dst->h ? src->h : dst->h; + VkImageBlit blit = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .srcOffsets = {{0, 0, 0}, {bw, bh, 1}}, + .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .dstOffsets = {{0, 0, 0}, {bw, bh, 1}}}; + g_vk.CmdBlitImage(g_cmd, src->image, + src->dmabuf ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL, + dst->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST); + VkImageMemoryBarrier rel = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = g_qfam, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .image = dst->image, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(g_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 1, &rel); + g_vk.EndCommandBuffer(g_cmd); + + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &g_cmd, + .waitSemaphoreCount = gpu_wait ? 1u : 0u, .pWaitSemaphores = gpu_wait ? &g_wait_sem : NULL, + .pWaitDstStageMask = gpu_wait ? &wait_stage : NULL}; + g_vk.ResetFences(g_dev, 1, &g_fence); + VkResult qr = g_vk.QueueSubmit(g_queue, 1, &si, g_fence); + if (qr != VK_SUCCESS) { + if (qr == VK_ERROR_DEVICE_LOST) device_lost("layer blit"); + else LOGE("layer: blit submit failed (%d)", (int)qr); + return -1; + } + VkResult fr = timed_wait(g_fence, 1000000000ULL); + if (fr == VK_ERROR_DEVICE_LOST) { device_lost("layer blit"); return -1; } + if (fr != VK_SUCCESS) { LOGE("layer: blit fence wait -> %s", vk_result_name(fr)); return -1; } + return 0; +} + +/* ---------------------------------------------------------------- the compositor pass, off-screen + * + * Layer mode with screen effects on: the game's frame still has to go through the compositor pass + * (scene image -> effects chain), but the RESULT does not have to end up in the app's swapchain - + * it is copied into a gralloc buffer and put on the game's own Android layer, so hardware + * composition stays alive while a Look is applied and the scene -> output mapping is done by the + * display (setGeometry) instead of a second full-screen GPU blit. + * + * The chain runs in its OWN submit, with no swapchain image acquired. That is deliberate and was + * measured: folding it into the submit that owns the acquired image (one command buffer, one + * present, which looks cheaper on paper) puts the whole 13-pass chain behind the acquire + * semaphore, i.e. behind a vblank - 72 fps instead of 111 fps on the Pocket FIT with Retro CRT at + * 1920x1080. The base surface's black frame is presented by the caller AFTER the layer + * transaction, so the layer never waits for a vblank either. + * + * Three steps because the chain's result size is only known once the chain has run, and the layer + * buffer is allocated from it: + * vkp_pass_begin() -> records composite + effects into the frame's command buffer + * vkp_pass_copy_to()-> appends the copy into the layer buffer, submits, waits + * vkp_pass_abort() -> drops it unsubmitted (no free layer buffer this frame) + * Frame generation is NOT run here: its extra frames need a present each, on consecutive vblanks, + * which one buffer per layer transaction cannot pace (see WAYLAND_RUNTIME.md). */ + +static struct { + int active; + VkImage result; /* in TRANSFER_SRC_OPTIMAL */ + int rw, rh; +} g_pass; + +static int pass_begin_impl(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *rw, int *rh); + +int vkp_pass_begin(int scene_w, int scene_h, const struct vkp_draw *draws, int n, int *rw, int *rh) { + return pass_begin_impl(scene_w, scene_h, draws, n, NULL, rw, rh); +} + +int vkp_pass_begin_hdr(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *rw, int *rh) { + if (!hf) return -1; + return pass_begin_impl(scene_w, scene_h, draws, n, hf, rw, rh); +} + +/* The off-screen pass for a layer: the SDR one (hf == NULL: composite into the 8-bit scene image, the + * effects in 8-bit - unchanged) or the HDR one (the draws composed into one 10-bit PQ BT.2020 picture, + * the effects run on it in 10-bit). */ +static int pass_begin_impl(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *rw, int *rh) { + if (g_pass.active) vkp_pass_abort(); + if (g_dev_state == -2 || dev_init() != 0 || !g_window || scene_w <= 0 || scene_h <= 0) return -1; + if (!g_swapchain && swap_init() != 0) return -1; /* the mapping is in output pixels */ + update_map(scene_w, scene_h); + if (!g_map.valid) return -1; + if (hf) { + if (ensure_img(&g_hdrscene, scene_w, scene_h, HDR_FMT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + "HDR composition (picture)") != 0) + return -1; + } else if (ensure_scene_image(scene_w, scene_h) != 0) return -1; + + VkCommandBuffer cmd = g_cmds[0]; + g_vk.ResetCommandBuffer(cmd, 0); + VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + g_vk.BeginCommandBuffer(cmd, &bi); + blendp_begin_frame(); + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + + /* Same acquisition as vkp_render's compositor pass: the sources come from the client's queue + * family (dmabuf) or from a host write (shm), the scene image is the transfer destination. */ + VkImageMemoryBarrier *bars = calloc((size_t)n + 1, sizeof(*bars)); + if (!bars) { g_vk.EndCommandBuffer(cmd); return -1; } + int nb = 0; + VkImage scene_img = hf ? g_hdrscene.img : g_scene.img; + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = scene_img, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + for (int i = 0; i < n; i++) { + struct vkp_image *im = draws[i].img; + int seen = 0; + for (int j = 0; j < i; j++) if (draws[j].img == im) { seen = 1; break; } + if (seen || !im) continue; + if (im->dmabuf) { + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = g_qfam, + .image = im->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; + } else if (!im->in_general) { + bars[nb++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = im->image, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; + im->in_general = 1; + } + } + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, (uint32_t)nb, bars); + free(bars); + + if (hf) { + /* HDR: every draw into the mixed image, then ONE encoding into the 10-bit picture: PQ BT.2020, or + * tone-mapped sRGB while the drawer's HDR output switch is off. */ + if (compose_hdr(cmd, draws, n, hf, scene_w, scene_h, g_hdrscene.img, HDR_FMT, + hf->tonemap ? HDRC_OUT_SDR : HDRC_OUT_PQ) != 0) { + g_vk.EndCommandBuffer(cmd); /* never submitted; reset before its next use */ + return -1; + } + if (vkp_effects_active()) vkp_effects_set_formats(HDR_FMT, HDR_FMT); + } else { + VkClearColorValue black = {.float32 = {0.0f, 0.0f, 0.0f, 1.0f}}; + g_vk.CmdClearColorImage(cmd, g_scene.img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range); + VkMemoryBarrier mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 1, &mb, 0, NULL, 0, NULL); + for (int i = 0; i < n; i++) { + VkImageBlit blit; + if (!draws[i].img || !draw_to_scene_blit(&draws[i], scene_w, scene_h, &blit)) continue; + record_draw(cmd, &draws[i], &blit, g_scene.img, VK_FORMAT_R8G8B8A8_UNORM, scene_w, scene_h, + VK_FILTER_LINEAR); + } + if (vkp_effects_active()) vkp_effects_set_formats(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM); + } + + int mapped_w = (int)(scene_w * g_map.kx + 0.5f), mapped_h = (int)(scene_h * g_map.ky + 0.5f); + g_pass.rw = scene_w; g_pass.rh = scene_h; + g_pass.result = vkp_effects_run(cmd, scene_img, scene_w, scene_h, mapped_w, mapped_h, + &g_pass.rw, &g_pass.rh); + g_pass.active = 1; + if (rw) *rw = g_pass.rw; + if (rh) *rh = g_pass.rh; + return 0; +} + +int vkp_pass_copy_to(struct vkp_image *dst, int wait_fd) { + if (!g_pass.active) { if (wait_fd >= 0) close(wait_fd); return -1; } + g_pass.active = 0; + VkCommandBuffer cmd = g_cmds[0]; + if (!dst || !dst->blit_dst || g_dev_state == -2) { + if (wait_fd >= 0) close(wait_fd); + g_vk.EndCommandBuffer(cmd); + return -1; + } + const int gpu_wait = take_wait_fd(wait_fd); /* the display's release of dst, before the copy writes it */ + if (gpu_wait < 0) { g_vk.EndCommandBuffer(cmd); return -1; } /* never submitted; reset before its next use */ + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + + VkImageMemoryBarrier acq = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .dstQueueFamilyIndex = g_qfam, .image = dst->image, .subresourceRange = range, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &acq); + /* The result stands for the whole scene, and so does the layer buffer (the DPU maps it onto + * the output), so this is 1:1 whenever the buffer was allocated at the size begin() reported; + * a stale-sized buffer is resampled rather than shown wrong. */ + VkImageBlit blit = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .srcOffsets = {{0, 0, 0}, {g_pass.rw, g_pass.rh, 1}}, + .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .dstOffsets = {{0, 0, 0}, {dst->w, dst->h, 1}}}; + g_vk.CmdBlitImage(cmd, g_pass.result, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, + (g_pass.rw == dst->w && g_pass.rh == dst->h) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR); + VkImageMemoryBarrier rel = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = g_qfam, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .image = dst->image, .subresourceRange = range, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 1, &rel); + g_vk.EndCommandBuffer(cmd); + + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd, + .waitSemaphoreCount = gpu_wait ? 1u : 0u, .pWaitSemaphores = gpu_wait ? &g_wait_sem : NULL, + .pWaitDstStageMask = gpu_wait ? &wait_stage : NULL}; + g_vk.ResetFences(g_dev, 1, &g_fence); + VkResult qr = g_vk.QueueSubmit(g_queue, 1, &si, g_fence); + if (qr != VK_SUCCESS) { + if (qr == VK_ERROR_DEVICE_LOST) device_lost("layer pass"); + else LOGE("layer: effects pass submit failed (%d)", (int)qr); + return -1; + } + VkResult fr = timed_wait(g_fence, 1000000000ULL); + if (fr == VK_ERROR_DEVICE_LOST) { device_lost("layer pass"); return -1; } + if (fr != VK_SUCCESS) { LOGE("layer: effects pass fence wait -> %s", vk_result_name(fr)); return -1; } + return 0; +} + +void vkp_pass_abort(void) { + if (!g_pass.active) return; + g_pass.active = 0; + g_vk.EndCommandBuffer(g_cmds[0]); /* never submitted; the buffer is reset before its next use */ +} diff --git a/app/src/main/cpp/waylandcomp/src/vk_present.h b/app/src/main/cpp/waylandcomp/src/vk_present.h new file mode 100644 index 000000000..36a9e2122 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_present.h @@ -0,0 +1,186 @@ +#ifndef VK_PRESENT_H +#define VK_PRESENT_H +#include <stdint.h> +#include <android/native_window.h> +/* + * Android-surface render backend for the embedded Wayland compositor. + * Owns a Turnip VkDevice + a swapchain on the SurfaceView's ANativeWindow. Each + * client buffer becomes an image (a dmabuf from winewayland's Vulkan WSI is + * imported zero-copy; a wl_shm buffer is copied into a host-visible image), and + * every frame blits the whole scene โ€” desktop, windows, subsurfaces โ€” in order. + */ + +// Set the Turnip driver to load (adrenotools). Call before the first frame. +// NULL args -> the backend falls back to the system libvulkan (dmabuf import will +// likely fail โ€” Adreno lacks drm_format_modifier). driver_path ends with '/'. +void vk_present_set_driver(const char *driver_path, const char *library_name, + const char *native_lib_dir); + +// Set/replace the output window (from Surface via ANativeWindow_fromSurface; the backend +// owns the reference from then on). NULL = the surface is gone. Callable from any thread +// and never blocks: the request is applied by the compositor thread (vkp_render / +// vkp_apply_window_request), which tears the old swapchain down and releases the old window. +void vk_present_set_window(ANativeWindow *window); +/* Compositor thread: apply a pending window change now. Returns 1 if the window changed. */ +int vkp_apply_window_request(void); + +/* How the scene is mapped onto the output (the app's Container.FULLSCREEN_* / ALIGN_* values, + * mirrored 1:1 from ViewTransformation.java so touch input, which is mapped by the app with + * that class, lands on the same pixels). Callable from any thread, applies on the next frame. */ +enum vkp_scale_mode { VKP_MODE_OFF = 0, VKP_MODE_FIT = 1, VKP_MODE_STRETCH = 2, VKP_MODE_FILL = 3, + VKP_MODE_INTEGER = 4 }; +enum vkp_align { VKP_ALIGN_CENTER = 0, VKP_ALIGN_TOP = 1, VKP_ALIGN_BOTTOM = 2 }; +void vk_present_set_scale_mode(int mode, int alignment); +/* Output pixel (0..output size) -> scene pixel through the current mapping (compositor thread). + * Returns 0 before the first frame has established a mapping (sx/sy untouched). */ +int vkp_output_to_scene(double ox, double oy, double *sx, double *sy); +/* Output size in pixels (0x0 before the first swapchain). */ +void vkp_output_size(int *w, int *h); + +struct vkp_image; + +/* DRM format modifiers this backend knows the memory layout of (both single-plane on Adreno): + * LINEAR, and QCOM_COMPRESSED = UBWC (drm_fourcc.h: fourcc_mod_code(QCOM = 0x05, 1)). */ +#define VKP_MOD_LINEAR 0x0000000000000000ULL +#define VKP_MOD_QCOM_COMPRESSED 0x0500000000000001ULL +#define VKP_MOD_INVALID 0x00ffffffffffffffULL +/* "linear", "qcom_compressed", or "modifier 0xโ€ฆ" for anything else (static buffer). */ +const char *vkp_modifier_name(uint64_t modifier); +/* The modifiers a dma-buf of this DRM fourcc can be imported with as a blit source, asked of the + * renderer's own driver (VkDrmFormatModifierPropertiesListEXT, each confirmed for a dma-buf-backed + * TRANSFER_SRC image with vkGetPhysicalDeviceImageFormatProperties2). Only LINEAR and + * QCOM_COMPRESSED are ever returned (the ones this file can describe a plane layout for); other + * modifiers the driver reports are logged once. Returns the count, 0 when the device is not up + * (LINEAR is then the only safe assumption). Compositor thread. */ +int vkp_dmabuf_modifiers(uint32_t drm_format, uint64_t *out, int max); + +// Import a dmabuf (single plane). NULL on failure. The image aliases the buffer, so +// later client frames rendered into the same buffer show up without re-importing. +struct vkp_image *vkp_image_from_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, + int w, int h, uint32_t stride, uint32_t offset); +// Same, choosing the role: as_blit_dst = 0 imports a client frame (blit source), 1 imports a +// buffer this backend blits INTO (layer mode's AHardwareBuffer pool, see sc_layer.h). +struct vkp_image *vkp_image_import_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, + int w, int h, uint32_t stride, uint32_t offset, + int as_blit_dst); +int vkp_image_is_dmabuf(const struct vkp_image *img); + +// Create a host-visible image and copy BGRA/XRGB8888 pixels into it. NULL on failure. +struct vkp_image *vkp_image_create_shm(int w, int h); +void vkp_image_upload_shm(struct vkp_image *img, const void *data, int stride); + +int vkp_image_width(const struct vkp_image *img); +int vkp_image_height(const struct vkp_image *img); +void vkp_image_destroy(struct vkp_image *img); + +// One scene draw: the src rectangle of an image (image pixels) scaled into the dst +// rectangle (scene pixels). The scene is mapped onto the output by the scale mode. +struct vkp_draw { + struct vkp_image *img; + float sx, sy, sw, sh; + int dx, dy, dw, dh; + int blend; /* translucent: composed over what is under it (blend_pass.h) */ +}; + +// The GPU the renderer runs on ("Adreno (TM) 750"), empty before the device is up. +const char *vkp_gpu_name(void); + +// 0 if the renderer can create images (device up), -1 otherwise. +int vkp_ready(void); +/* Whether an output window is attached (or requested); without one vkp_render() draws nothing. */ +int vkp_has_window(void); +/* 1 once the Vulkan device was lost: nothing is presented any more (the session must restart). */ +int vkp_device_lost(void); + +// Clear to black, blit the draws in order (first = bottom) and present. +// Returns 0 on success, -1 if nothing could be presented (no window yet, etc.). +int vkp_render(int scene_w, int scene_h, const struct vkp_draw *draws, int n); +/* A black frame with no compositor pass (no effects, no frame generation), whatever is armed: the + * base surface under an HDR game that keeps its display layer (compositor.c). */ +int vkp_render_plain(int scene_w, int scene_h); +/* Layer mode's base surface: make sure it shows a plain black frame under the display layers. A frame + * is presented only when the base is not already black on the current swapchain (entering layer mode, + * after a copy-path / frame-generation frame, after a swapchain rebuild); otherwise nothing is drawn + * or presented at all. 0 = the base is black, -1 = no output. Compositor thread. */ +int vkp_base_black(int scene_w, int scene_h); + +/* ---- perf counters (the 10 s `perf` line, compositor.c) ---- + * Everything the compositor thread spends in the driver, since the last vkp_perf_take(). Times in ns. */ +struct vkp_perf { + unsigned acquires, presents, waits; + int64_t acquire_ns, acquire_max_ns; /* vkAcquireNextImageKHR */ + int64_t present_ns, present_max_ns; /* vkQueuePresentKHR */ + int64_t wait_ns, wait_max_ns; /* vkWaitForFences on the compositor's own work */ + unsigned base_presents; /* black frames presented under the display layers */ + unsigned base_kept; /* layer frames that kept the black frame already there */ + unsigned gpu_release_waits; /* layer-buffer release fences waited for on the GPU */ +}; +void vkp_perf_take(struct vkp_perf *out); +/* 1 when a release fence (sync_file) can be handed to the GPU as a wait (VK_KHR_external_semaphore_fd): + * the layer pool then reuses a buffer the display is still reading without blocking on the CPU. */ +int vkp_can_wait_sync_fd(void); + +/* ---- HDR composition (hdr_compose.h) ---- + * An HDR game that cannot be alone on its display layer: the scene is composed into ONE encoding. */ +struct wnc_color; +struct vkp_hdr_frame { + const unsigned char *is_hdr; /* per draw: 1 = its image holds PQ BT.2020 (the surface's description) */ + float peak_nits; /* the HDR content's peak, for a tone-map (max CLL / mastering max; 0 = 1000) */ + int tonemap; /* 1 = the drawer's HDR output switch is off: tone-map to SDR, never PQ */ + const struct wnc_color *color; /* the topmost HDR draw's description: an HDR10 swapchain's metadata */ +}; +/* vkp_render for a scene with HDR draws (frame generation): composed into PQ and presented through an + * HDR10 swapchain where the surface offers one (and hf->tonemap is 0), else tone-mapped into the ordinary + * one. *how = 1 HDR10, 2 tone-mapped, 0 the HDR pass was unavailable (shown the old way). Same return as + * vkp_render. */ +int vkp_render_hdr(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *how); +/* vkp_pass_begin for a scene with HDR draws: composed into a 10-bit picture - PQ BT.2020, or sRGB + * tone-mapped when hf->tonemap - the effects run on it in 10-bit, the result (rw x rh, A2B10G10R10) is + * left for vkp_pass_copy_to() into a layer buffer (a blit: 10-bit or 8-bit). -1 = not possible (nothing + * recorded). */ +int vkp_pass_begin_hdr(int scene_w, int scene_h, const struct vkp_draw *draws, int n, + const struct vkp_hdr_frame *hf, int *rw, int *rh); + +/* ---- layer mode helpers (sc_layer.c; compositor thread) ---- */ +/* Whole-image copy of src into dst (a blit-destination dmabuf image), waited for on the CPU. + * wait_fd: a sync_file the copy must wait for before writing dst (the display's release fence of that + * buffer), -1 = none. Always consumed (handed to the GPU, or waited for and closed). */ +int vkp_blit_image(struct vkp_image *src, struct vkp_image *dst, int wait_fd); +/* Refresh the scene -> output mapping for this scene size without presenting (creates the + * swapchain if needed, since the mapping is in output pixels). 0 = mapping valid. */ +int vkp_update_map(int scene_w, int scene_h); +/* Map a draw through the current mapping: out = {src x0,y0,x1,y1 (image px), dst x0,y0,x1,y1 + * (output px)}, clipped like the blit path. 0 = nothing of it is visible. */ +int vkp_map_draw(const struct vkp_draw *d, int out[8]); +/* The same for a bare w x h buffer shown over the whole scene (no vkp_image: zero-copy layers). */ +int vkp_map_rect(int img_w, int img_h, int scene_w, int scene_h, int out[8]); +/* The output window frames go to (NULL = none); compositor thread. */ +/* Degrees the display rotates every layer we present by (0/90/180/270), -1 before the swapchain + * exists. From VkSurfaceCapabilitiesKHR::currentTransform, i.e. what the presentation engine says + * it does โ€” never a device allowlist. The layer path uses it to decide whether a SECOND display + * layer is affordable on this display (sc_layer_present_overlay). */ +int vkp_surface_rotation_degrees(void); + +ANativeWindow *vkp_window(void); +/* Fire the one-shot first-frame notification (layer mode presents outside vkp_render). */ +void vkp_signal_first_frame(void); + +/* ---- the compositor pass into a layer buffer (sc_layer.c; compositor thread) ---- + * Screen effects on the game's own Android layer: compose `draws` into the scene image and run the + * effects chain WITHOUT presenting, then copy the result into a gralloc layer buffer. Three steps + * because the chain's result size (a scaling mode resizes to the scene's mapped output size) is + * only known once the chain has run, and the layer buffer is allocated from it. + * begin: 0 = a pass is in progress, its result is *rw x *rh; -1 = not possible (draw the old way). + * Exactly one of copy_to (blit into dst, submit, wait; 0 = done) / abort (drop it unsubmitted) + * must follow a successful begin. The chain deliberately runs with NO swapchain image acquired - + * see the comment in vk_present.c. Frame generation is not run here (see WAYLAND_RUNTIME.md). */ +int vkp_pass_begin(int scene_w, int scene_h, const struct vkp_draw *draws, int n, int *rw, int *rh); +/* wait_fd: as for vkp_blit_image (consumed in every case). */ +int vkp_pass_copy_to(struct vkp_image *dst, int wait_fd); +void vkp_pass_abort(void); + +// Session log (compositor.c): one line to Download/Wayland-logs and logcat. +void wnc_log(const char *tag, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c b/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c new file mode 100644 index 000000000..544f0a814 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/waylandcomp_jni.c @@ -0,0 +1,679 @@ +/* + * JNI entry for the embedded Wayland compositor (experimental parallel runtime). + * Starts the compositor on a dedicated thread (it blocks in the wl event loop). + * The render-to-Surface backend + input are added in the M4 phase; this brings up + * the server so a Wayland client (eventually winewayland.drv) can connect. + */ +#include <jni.h> +#include <errno.h> +#include <stdint.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/resource.h> +#include <android/log.h> +#include <android/native_window_jni.h> +#include "vk_present.h" +#include "desktop_ext.h" +#include "ahb_swapchain.h" +#include "sc_layer.h" +#include "effects_chain.h" +#include "color_mgmt.h" + +extern int wnc_wayland_run(void); +extern void wnc_wayland_set_log_dir(const char *dir); +extern void wnc_wayland_end_session(void); +extern void wnc_wayland_begin_session(void); +extern void wnc_wayland_send_pointer(int action, int x, int y); +extern void wnc_wayland_send_key(int evdev, int state); +extern void wnc_wayland_send_scene_input(int type, int a, int b); +extern void wnc_wayland_vsync(int64_t frame_time_ns); +extern volatile int g_fps_limit; +extern volatile int g_hide_shell; +extern volatile int g_zero_copy; +extern volatile unsigned g_zero_copy_last; +extern volatile int g_ubwc; +extern volatile int g_no_render_node; +extern volatile int g_output_refresh_mhz; +extern volatile int g_output_w, g_output_h; + +#define TAG "WNWayland" + +static JavaVM *g_jvm; +static jclass g_compositor_cls; /* global ref */ +static jmethodID g_on_first_frame; /* static void onFirstFramePresented() */ +static jmethodID g_on_game_surface; /* static void onGameSurface(String, String) */ +static jmethodID g_on_game_frame; /* static void onGameFrame() */ +static jmethodID g_on_game_program; /* static void onGameProgram(int, String) */ +static jmethodID g_on_pointer_lock; /* static void onPointerLock(boolean, int, int) */ +static jmethodID g_on_clipboard; /* static void onClipboardText(byte[]) */ +static jmethodID g_on_text_input; /* static void onTextInput(boolean, String, int, int, int, int) */ + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + (void)reserved; + g_jvm = vm; + JNIEnv *env; + if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) == JNI_OK) { + jclass c = (*env)->FindClass(env, "com/winlator/cmod/runtime/display/wayland/WaylandCompositor"); + if (c) { + g_compositor_cls = (*env)->NewGlobalRef(env, c); + g_on_first_frame = (*env)->GetStaticMethodID(env, g_compositor_cls, + "onFirstFramePresented", "()V"); + g_on_game_surface = (*env)->GetStaticMethodID(env, g_compositor_cls, "onGameSurface", + "(Ljava/lang/String;Ljava/lang/String;)V"); + g_on_game_frame = (*env)->GetStaticMethodID(env, g_compositor_cls, "onGameFrame", "()V"); + g_on_game_program = (*env)->GetStaticMethodID(env, g_compositor_cls, "onGameProgram", + "(ILjava/lang/String;)V"); + if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); g_on_game_program = NULL; } + g_on_pointer_lock = (*env)->GetStaticMethodID(env, g_compositor_cls, "onPointerLock", "(ZII)V"); + g_on_clipboard = (*env)->GetStaticMethodID(env, g_compositor_cls, "onClipboardText", "([B)V"); + g_on_text_input = (*env)->GetStaticMethodID(env, g_compositor_cls, "onTextInput", + "(ZLjava/lang/String;IIII)V"); + } + } + return JNI_VERSION_1_6; +} + +/* Called from vk_present.c on the compositor thread when the first client frame is + * presented. Attaches to the JVM (this thread is a bare pthread) and calls back into + * Java so the launch overlay can dismiss. Fires exactly once. */ +void wnc_on_first_frame(void) { + if (!g_jvm || !g_compositor_cls || !g_on_first_frame) return; + JNIEnv *env = NULL; + int attached = 0; + if ((*g_jvm)->GetEnv(g_jvm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) { + if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK) return; + attached = 1; + } + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_first_frame); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); + if (attached) (*g_jvm)->DetachCurrentThread(g_jvm); + __android_log_print(ANDROID_LOG_INFO, TAG, "first client frame presented -> notified app"); +} + +/* The compositor thread stays attached once it first calls into Java: the HUD gets an upcall + * for every game frame. */ +static __thread JNIEnv *t_env; +static __thread int t_attached; + +static JNIEnv *thread_env(void) { + if (t_env) return t_env; + if (!g_jvm) return NULL; + JNIEnv *env = NULL; + if ((*g_jvm)->GetEnv(g_jvm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) { + if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK) return NULL; + t_attached = 1; + } + return t_env = env; +} + +/* A window started presenting GPU frames (window = its description), or NULL when it closed. */ +void wnc_on_game_surface(const char *window, const char *gpu) { + JNIEnv *env; + if (!g_compositor_cls || !g_on_game_surface || !(env = thread_env())) return; + jstring jw = window ? (*env)->NewStringUTF(env, window) : NULL; + jstring jg = gpu ? (*env)->NewStringUTF(env, gpu) : NULL; + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_game_surface, jw, jg); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); + if (jw) (*env)->DeleteLocalRef(env, jw); + if (jg) (*env)->DeleteLocalRef(env, jg); +} + +/* The program behind the game window that just started presenting: its Linux pid and executable name + * ("" = unknown). The app arms its launch-time CPU affinity on it. Compositor thread. */ +void wnc_on_game_program(int pid, const char *program) { + JNIEnv *env; + if (!g_compositor_cls || !g_on_game_program || !(env = thread_env())) return; + jstring jp = (*env)->NewStringUTF(env, program ? program : ""); + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_game_program, (jint)pid, jp); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); + if (jp) (*env)->DeleteLocalRef(env, jp); +} + +/* One GPU frame from that window. */ +void wnc_on_game_frame(void) { + JNIEnv *env; + if (!g_compositor_cls || !g_on_game_frame || !(env = thread_env())) return; + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_game_frame); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); +} + +/* A program locked the pointer (locked = 1; the app's input path switches to deltas) or the lock + * ended (locked = 0; x,y = where the pointer is now, in scene coordinates, for the app to re-sync + * its own pointer to). Compositor thread. */ +void wnc_on_pointer_lock(int locked, int x, int y) { + JNIEnv *env; + if (!g_compositor_cls || !g_on_pointer_lock || !(env = thread_env())) return; + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_pointer_lock, (jboolean)(locked != 0), (jint)x, (jint)y); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); +} + +/* A program copied text (UTF-8, not NUL-terminated for the app's sake โ€” a byte[] so emoji and + * NULs survive JNI). Compositor thread. */ +void wnc_on_clipboard_text(const char *utf8, int len) { + JNIEnv *env; + if (!g_compositor_cls || !g_on_clipboard || !(env = thread_env())) return; + jbyteArray arr = (*env)->NewByteArray(env, len); + if (!arr) { (*env)->ExceptionClear(env); return; } + (*env)->SetByteArrayRegion(env, arr, 0, len, (const jbyte *)utf8); + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_clipboard, arr); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, arr); +} + +/* A program started (enabled) or stopped accepting IME text; x,y,w,h = its caret rectangle in + * scene pixels (all 0 = unknown). Compositor thread. */ +void wnc_on_text_input(int enabled, const char *program, int x, int y, int w, int h) { + JNIEnv *env; + if (!g_compositor_cls || !g_on_text_input || !(env = thread_env())) return; + jstring jp = (*env)->NewStringUTF(env, program ? program : ""); + (*env)->CallStaticVoidMethod(env, g_compositor_cls, g_on_text_input, (jboolean)(enabled != 0), jp, + (jint)x, (jint)y, (jint)w, (jint)h); + if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); + if (jp) (*env)->DeleteLocalRef(env, jp); +} + +/* android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY: the level the hwui RenderThread runs at. */ +#define COMPOSITOR_NICE (-8) + +static void *comp_thread(void *arg) { + (void)arg; + __android_log_print(ANDROID_LOG_INFO, TAG, "compositor thread starting"); + /* Every buffer release, frame callback and layer transaction of the session goes through this one + * thread. It is named, so `ps -T`, the logs and the drawer's Thread Priority Boost (PerfPriority + * matches thread names) can find it, and it runs at display priority instead of whatever the + * thread that started it had. Never lowered: a thread that already runs hotter keeps its value. */ + pthread_setname_np(pthread_self(), "wl-compositor"); + const id_t tid = (id_t)gettid(); + errno = 0; + const int before = getpriority(PRIO_PROCESS, tid); + int refused = 0; + if (before > COMPOSITOR_NICE && setpriority(PRIO_PROCESS, tid, COMPOSITOR_NICE) != 0) refused = errno ? errno : -1; + const int after = getpriority(PRIO_PROCESS, tid); + if (refused) + wnc_log("perf", "compositor thread %d \"wl-compositor\": stays at nice %d, a higher priority was refused (%s)", + (int)tid, after, refused > 0 ? strerror(refused) : "?"); + else + wnc_log("perf", "compositor thread %d \"wl-compositor\": nice %d -> %d", (int)tid, before, after); + wnc_wayland_run(); + __android_log_print(ANDROID_LOG_INFO, TAG, "compositor thread exited"); + if (t_attached) (*g_jvm)->DetachCurrentThread(g_jvm); + t_env = NULL; t_attached = 0; + return NULL; +} + +static void set_runtime_dir(JNIEnv *env, jstring xdgRuntimeDir) { + if (!xdgRuntimeDir) return; + const char *dir = (*env)->GetStringUTFChars(env, xdgRuntimeDir, NULL); + if (dir) { + setenv("XDG_RUNTIME_DIR", dir, 1); + (*env)->ReleaseStringUTFChars(env, xdgRuntimeDir, dir); + } +} + +static char *dup_jstr(JNIEnv *env, jstring s) { + if (!s) return NULL; + const char *c = (*env)->GetStringUTFChars(env, s, NULL); + char *out = c ? strdup(c) : NULL; + if (c) (*env)->ReleaseStringUTFChars(env, s, c); + return out; +} + +/* One compositor thread per process: WinNative keeps its process alive between sessions, so the + * thread is started once and sessions begin and end on it (wnc_wayland_begin/end_session). */ +static pthread_mutex_t g_start_lock = PTHREAD_MUTEX_INITIALIZER; +static int g_started; + +/* Returns 1 when this call started the thread, 0 when it was already running. */ +static int start_thread(void) { + pthread_t t; + pthread_mutex_lock(&g_start_lock); + if (g_started) { pthread_mutex_unlock(&g_start_lock); return 0; } + if (pthread_create(&t, NULL, comp_thread, NULL) == 0) { + pthread_detach(t); + g_started = 1; + } else { + __android_log_print(ANDROID_LOG_ERROR, TAG, "pthread_create failed"); + } + pthread_mutex_unlock(&g_start_lock); + return g_started; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeIsRunning(JNIEnv *env, jclass clazz) { + int r; + pthread_mutex_lock(&g_start_lock); + r = g_started; + pthread_mutex_unlock(&g_start_lock); + return r ? JNI_TRUE : JNI_FALSE; +} + +/* Where session logs go (one file per session); empty/null = logcat only. Any thread, before the start. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetLogDir(JNIEnv *env, jclass clazz, + jstring dir) { + char *d = dup_jstr(env, dir); + wnc_wayland_set_log_dir(d); + free(d); +} + +/* The game session is over: drop the screen surface and have the compositor thread disconnect every + * client and reset its per-session state. The thread and its Vulkan device stay up for the next + * session. Any thread. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeEndSession(JNIEnv *env, jclass clazz) { + vk_present_set_window(NULL); + wnc_wayland_end_session(); +} + +/* Headless start (no output window) โ€” used for bring-up tests. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeStart(JNIEnv *env, jclass clazz, + jstring xdgRuntimeDir) { + set_runtime_dir(env, xdgRuntimeDir); + if (!start_thread()) wnc_wayland_begin_session(); +} + +/* Start with a real output Surface + the container's Turnip driver (adrenotools). + * Frames committed by clients are composited to this Surface via Turnip. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeStartWithSurface( + JNIEnv *env, jclass clazz, jobject surface, jstring xdgRuntimeDir, + jstring driverPath, jstring libraryName, jstring nativeLibDir) { + set_runtime_dir(env, xdgRuntimeDir); + char *dp = dup_jstr(env, driverPath); + char *ln = dup_jstr(env, libraryName); + char *nl = dup_jstr(env, nativeLibDir); + vk_present_set_driver(dp, ln, nl); + free(dp); free(ln); free(nl); + if (surface) { + ANativeWindow *win = ANativeWindow_fromSurface(env, surface); + vk_present_set_window(win); /* backend acquires; released on nativeSetSurface(null) */ + __android_log_print(ANDROID_LOG_INFO, TAG, "output window bound (%p)", (void *)win); + } + if (!start_thread()) { + /* Already running: this is a new session on the same compositor. The driver above is only + * taken by the first start (the Vulkan device is up already). */ + wnc_wayland_begin_session(); + __android_log_print(ANDROID_LOG_INFO, TAG, "new session on the running compositor"); + } +} + +/* Inject a pointer event from the Android SurfaceView touch listener (UI thread). + * action: 0=down 1=move 2=up; x/y in output space (0..1919, 0..1079). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSendPointer( + JNIEnv *env, jclass clazz, jint action, jint x, jint y) { + wnc_wayland_send_pointer(action, x, y); +} + +/* Inject a key event. evdev = Linux input keycode (KEY_A=30โ€ฆ); state 1=down 0=up. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSendKey( + JNIEnv *env, jclass clazz, jint evdev, jint state) { + wnc_wayland_send_key(evdev, state); +} + +/* App X-server input in scene (virtual desktop) coordinates; see wnc_wayland_send_scene_input. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSendSceneInput( + JNIEnv *env, jclass clazz, jint type, jint a, jint b) { + wnc_wayland_send_scene_input(type, a, b); +} + +/* One screen refresh (Choreographer frame callback, UI thread): the compositor draws once. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeVsync(JNIEnv *env, jclass clazz, jlong frameTimeNanos) { + wnc_wayland_vsync((int64_t)frameTimeNanos); +} + +/* Shortcut launches: don't draw explorer's windows (desktop, taskbar), like X11's unviewable classes. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHideShell(JNIEnv *env, jclass clazz, jboolean hide) { + g_hide_shell = hide ? 1 : 0; +} + +/* Zero-copy layer mode (BANNER_WAYLAND_ZERO_COPY=1 at launch, the drawer's switch live): one fullscreen + * window on its own Android layer instead of the swapchain blit (sc_layer.c / ahb_swapchain.c). Any + * thread, any time: before the compositor starts it is the launch default; afterwards the compositor + * thread flips the state, tells the bound games to rebuild their swapchains for it and redraws. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetZeroCopy(JNIEnv *env, jclass clazz, jboolean on) { + int live = wnc_get_display() != NULL; + if (!live) g_zero_copy = on ? 1 : 0; /* read by ahb_swapchain_init before the queue drains */ + wnc_host_zero_copy(on ? 1 : 0, live); + __android_log_print(ANDROID_LOG_INFO, TAG, "zero-copy layer mode %s%s", on ? "on" : "off", live ? " (live)" : ""); +} + +/* Milliseconds since the compositor last put a game frame on the layer without a copy; -1 = never + * this session. The drawer's status line ("switching..." until the frames arrive / stop). */ +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeZeroCopyLastFrameAgeMs(JNIEnv *env, jclass clazz) { + return (jint)ahb_swapchain_last_frame_age_ms(); +} + +/* Zero-copy frames in the last completed 10 s stats window (on_stats_timer), for the drawer's live line. */ +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeZeroCopyFrames(JNIEnv *env, jclass clazz) { + return (jint)g_zero_copy_last; +} + +/* Compressed (UBWC) game buffers: advertise DRM_FORMAT_MOD_QCOM_COMPRESSED on zwp_linux_dmabuf_v1 when + * the renderer's driver imports it (default on; BANNER_WAYLAND_UBWC=0 = off). Set before the compositor + * starts. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetUbwc(JNIEnv *env, jclass clazz, jboolean on) { + g_ubwc = on ? 1 : 0; + __android_log_print(ANDROID_LOG_INFO, TAG, "compressed (UBWC) game buffers %s", on ? "on" : "off"); +} + +/* Debug: name no DRM device in the dma-buf feedback (BANNER_WAYLAND_NO_RENDER_NODE=1), the way a + * phone that exposes no /dev/dri node to apps does. Set before the compositor starts. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetNoRenderNode(JNIEnv *env, jclass clazz, jboolean on) { + g_no_render_node = on ? 1 : 0; + if (on) __android_log_print(ANDROID_LOG_INFO, TAG, "debug: advertising no DRM device (main device 0:0)"); +} + +/* VRR / refresh-rate matching: the rate the app is voting for the panel, mirrored onto the game's + * own SurfaceControl layer. Under zero-copy the game's frames bypass the app's surface entirely, so + * without this SurfaceFlinger never sees the game's cadence. 0 = clear the vote. Any thread, any + * time; the compositor applies it with its next layer transaction and logs the change. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetLayerFrameRate(JNIEnv *env, jclass clazz, jfloat hz) { + (void)env; (void)clazz; + sc_layer_set_frame_rate((float)hz); +} + +/* The panel's refresh rate (Hz) for the advertised wl_output mode. Set before the compositor starts. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetOutputRefreshRate(JNIEnv *env, jclass clazz, jfloat hz) { + g_output_refresh_mhz = hz > 1.0f ? (int)(hz * 1000.0f + 0.5f) : 0; +} + +/* The container's screen size for the advertised wl_output mode. Set before the compositor starts. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetOutputSize(JNIEnv *env, jclass clazz, jint w, jint h) { + g_output_w = w > 0 ? w : 0; + g_output_h = h > 0 ? h : 0; +} + +/* Fullscreen mode + screen alignment (Container.FULLSCREEN_* / ALIGN_* values): how the scene is + * fitted onto the output. Any thread, any time; the next frame uses it. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetScaleMode(JNIEnv *env, jclass clazz, jint mode, jint alignment) { + vk_present_set_scale_mode(mode, alignment); + __android_log_print(ANDROID_LOG_INFO, TAG, "scale mode %d alignment %d", mode, alignment); +} + +/* ---- screen effects (effects_chain.c): the X11 Vulkan renderer's post chain in the compositor. + * Any thread, any time; the compositor thread applies them on its next frame and logs one + * `effects` line per change. Values are 1:1 with VulkanRenderer's setters. */ + +/* Scaling mode: 0=None 1=Linear 2=Nearest 3=SGSR 4=FSR 5=FSR Fit 6=Sharpen 7=NIS 8=SGSR HQ. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetUpscaler(JNIEnv *env, jclass clazz, jint mode) { + vkp_effects_set_scaling(mode); +} + +/* The upscaler's sharpness slider 0..100 (RCAS lobe / SGSR edge / NIS sharpness). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetUpscaleSharpness(JNIEnv *env, jclass clazz, jint sharpness) { + vkp_effects_set_upscale_sharpness(sharpness); +} + +/* AMD CAS sharpen toggle + level 0..100 (0 = pass off). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetCas(JNIEnv *env, jclass clazz, jboolean enabled, jint sharpness) { + vkp_effects_set_cas(enabled ? 1 : 0, sharpness); +} + +/* Fake-HDR toggle. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHdr(JNIEnv *env, jclass clazz, jboolean enabled) { + vkp_effects_set_hdr(enabled ? 1 : 0); +} + +/* Terminal debanding toggle + strength 0..200 (100 = 1 LSB). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetDeband(JNIEnv *env, jclass clazz, jboolean enabled, jint strength) { + vkp_effects_set_deband(enabled ? 1 : 0, strength); +} + +/* Colour grade (slider units: brightness/contrast -100..100, gamma 0.5..3, saturation 0..200 %) and the + * FXAA / Toon / CRT / NTSC toggles โ€” VulkanRenderer.setScreenEffects. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetScreenEffects(JNIEnv *env, jclass clazz, jfloat brightness, + jfloat contrast, jfloat gamma, jfloat saturation, jboolean fxaa, jboolean toon, jboolean crt, jboolean ntsc) { + vkp_effects_set_screen(brightness, contrast, gamma, saturation, fxaa ? 1 : 0, toon ? 1 : 0, crt ? 1 : 0, ntsc ? 1 : 0); +} + +/* One "display" line in the session log, written from Java: facts that live in the Android + * framework (the panel's HDR capability) rather than in the compositor. wnc_log() mirrors to + * logcat and appends to the session file when one is open, so this is safe at any point. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeLogDisplay(JNIEnv *env, jclass clazz, jstring message) { + char *s = dup_jstr(env, message); + if (s) wnc_log("display", "%s", s); + free(s); +} + +/* The same under the "perf" area: facts the app knows about the session's performance setup (the CPU + * cores the game is pinned to, say). Same safety as nativeLogDisplay. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeLogPerf(JNIEnv *env, jclass clazz, jstring message) { + char *s = dup_jstr(env, message); + if (s) wnc_log("perf", "%s", s); + free(s); +} + +/* ---- HDR10 output, round 1 (color_mgmt.h / wl_color_mgmt.c) ---- */ + +/* The opt-in: mode 0 off, 1 BANNER_WAYLAND_HDR=1, 2 =force (testing). Before the compositor starts. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHdrRequest(JNIEnv *env, jclass clazz, jint mode, + jstring source, jboolean dxvkHdr, jboolean zeroCopyForced) { + char *s = dup_jstr(env, source); + wnc_color_set_request((int)mode, s, dxvkHdr ? 1 : 0, zeroCopyForced ? 1 : 0); + free(s); +} + +/* The game's display as android.view.Display reports it (before the start; again on every change). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHdrDisplay(JNIEnv *env, jclass clazz, jint id, jstring name, + jstring formats, jboolean hdr10, jfloat maxLum, jfloat maxAvg, jfloat minLum, jboolean ratioAvailable, + jfloat ratio, jint api) { + char *n = dup_jstr(env, name), *f = dup_jstr(env, formats); + wnc_color_set_display((int)id, n, f, hdr10 ? 1 : 0, (float)maxLum, (float)maxAvg, (float)minLum, + ratioAvailable ? 1 : 0, (float)ratio, (int)api); + free(n); free(f); +} + +/* One Display.getHdrSdrRatio() reading (listener = from the display's ratio listener). Any thread. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrSdrRatioSample(JNIEnv *env, jclass clazz, jfloat ratio, + jboolean listener) { + wnc_color_ratio_sample((float)ratio, listener ? 1 : 0); +} + +/* ms since an HDR frame last reached a display layer, -1 = never this session. Any thread. */ +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrLastFrameAgeMs(JNIEnv *env, jclass clazz) { + return (jint)wnc_color_last_frame_age_ms(); +} + +/* -1 = not decided yet, 0 = closed, 1 = open. Any thread. */ +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrGateState(JNIEnv *env, jclass clazz) { + return (jint)wnc_color_gate_state(); +} + +/* The session is ending: the "HDR on screen: ..." summary line. Any thread, once. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrSessionEnd(JNIEnv *env, jclass clazz) { + wnc_color_session_end(); +} + +/* HDR frames really on screen right now (the HUD badge). Any thread. */ +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrOnScreen(JNIEnv *env, jclass clazz) { + return wnc_color_hdr_on_screen() ? JNI_TRUE : JNI_FALSE; +} + +/* One "color" line in the session log from Java. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeLogColor(JNIEnv *env, jclass clazz, jstring message) { + char *s = dup_jstr(env, message); + if (s) wnc_log("color", "%s", s); + free(s); +} + +/* One line in the session log under an area Java names ("gpu", "nvapi"): launch facts the app decides. + * The area is the log's 9-character column; an empty one reads "app". */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeLog(JNIEnv *env, jclass clazz, jstring area, jstring message) { + char *a = dup_jstr(env, area), *s = dup_jstr(env, message); + if (s) wnc_log(a && a[0] ? a : "app", "%s", s); + free(a); + free(s); +} + +/* SDR content's level inside an HDR picture, in nits (default 203). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHdrSdrWhite(JNIEnv *env, jclass clazz, jfloat nits) { + wnc_color_set_sdr_white((float)nits); +} + +/* The drawer's live HDR output switch: on = HDR frames as HDR, off = the same frames tone-mapped to SDR. + * Applied on the compositor thread (logged there, with a redraw). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHdrOutput(JNIEnv *env, jclass clazz, jboolean on) { + if (wnc_get_display()) wnc_host_hdr_output(on ? 1 : 0); + else wnc_color_set_output(on ? 1 : 0); /* no compositor thread yet: nothing is drawing either */ +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrOutput(JNIEnv *env, jclass clazz) { + return wnc_color_output() ? JNI_TRUE : JNI_FALSE; +} + +/* Device evidence for the HDR lines: thermal status + headroom, brightness + mode. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrEnvSample(JNIEnv *env, jclass clazz, jint thermal, + jfloat headroom, jint brightness, jint mode) { + wnc_color_env_sample((int)thermal, (float)headroom != (float)headroom ? -1.0f : (float)headroom, + (int)brightness, (int)mode); +} + +/* Display.getHighestHdrSdrRatio() (Android 16+), <= 0 = not reported. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetHdrHighestRatio(JNIEnv *env, jclass clazz, jfloat ratio) { + wnc_color_set_highest_ratio((float)ratio == (float)ratio ? (float)ratio : -1.0f); +} + +/* The HDR headroom the screen surface should ask for (HDR10 swapchain frames in the last 1.5 s), 0 = none. */ +JNIEXPORT jfloat JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrScreenHeadroom(JNIEnv *env, jclass clazz) { + return (jfloat)wnc_color_screen_headroom(NULL, 0); +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrScreenHeadroomWhy(JNIEnv *env, jclass clazz) { + char why[200]; + wnc_color_screen_headroom(why, sizeof(why)); + return (*env)->NewStringUTF(env, why); +} + +/* The app's screen-surface request, for the no-headroom lines: > 0 asked, 0 cleared, -1 not possible. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrNoteHeadroomRequest(JNIEnv *env, jclass clazz, jfloat ratio) { + wnc_color_note_headroom_request((float)ratio); +} + +/* 0 none, 1 HDR frames on screen with headroom, 2 HDR frames on screen without headroom for 5 s+. */ +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrState(JNIEnv *env, jclass clazz) { + return (jint)wnc_color_hdr_state(); +} + +/* An HDR game's frames were shown tone-mapped to SDR in the last 1.5 s (the drawer's status line). */ +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeHdrToneMappedOnScreen(JNIEnv *env, jclass clazz) { + return wnc_color_tonemapped_on_screen() ? JNI_TRUE : JNI_FALSE; +} + +/* The Look the controls currently match (null = Custom) โ€” only named in the session log. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetLookName(JNIEnv *env, jclass clazz, jstring name) { + char *s = dup_jstr(env, name); + vkp_effects_set_look(s); + free(s); +} + +/* The in-game FPS limiter: frames per second, 0 = unlimited. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetFpsLimit(JNIEnv *env, jclass clazz, jint fps) { + g_fps_limit = fps > 0 ? fps : 0; + __android_log_print(ANDROID_LOG_INFO, TAG, "fps limit %d", fps); +} + +/* Swap/clear the output window (e.g. SurfaceView recreated/destroyed). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetSurface( + JNIEnv *env, jclass clazz, jobject surface) { + if (surface) { + vk_present_set_window(ANativeWindow_fromSurface(env, surface)); + } else { + vk_present_set_window(NULL); + } +} + +/* ---- clipboard + text input (any thread; queued to the compositor thread) ---- + * Text crosses as UTF-8 byte arrays: JNI's modified UTF-8 would mangle emoji. */ + +static char *dup_bytes(JNIEnv *env, jbyteArray arr, int *len) { + *len = 0; + if (!arr) return NULL; + jsize n = (*env)->GetArrayLength(env, arr); + char *buf = malloc((size_t)n + 1); + if (!buf) return NULL; + if (n) (*env)->GetByteArrayRegion(env, arr, 0, n, (jbyte *)buf); + buf[n] = 0; + *len = (int)n; + return buf; +} + +/* Android's clipboard text becomes the guest's selection (empty/null = clear). */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeSetClipboardText(JNIEnv *env, jclass clazz, jbyteArray utf8) { + int len; + char *buf = dup_bytes(env, utf8, &len); + wnc_host_clipboard_text(buf ? buf : "", len); + free(buf); +} + +/* Soft-keyboard text committed to the program accepting text input. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeTextInputCommit(JNIEnv *env, jclass clazz, jbyteArray utf8) { + int len; + char *buf = dup_bytes(env, utf8, &len); + if (buf && len) wnc_host_text_commit(buf, len); + free(buf); +} + +/* Composing (pre-edit) text; cursorBegin/cursorEnd are character indexes into it, -1 = end. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeTextInputPreedit(JNIEnv *env, jclass clazz, jbyteArray utf8, + jint cursorBegin, jint cursorEnd) { + int len; + char *buf = dup_bytes(env, utf8, &len); + wnc_host_text_preedit(buf ? buf : "", len, cursorBegin, cursorEnd); + free(buf); +} + +/* The IME deleted characters around the caret. */ +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_wayland_WaylandCompositor_nativeTextInputDelete(JNIEnv *env, jclass clazz, jint before, jint after) { + wnc_host_text_delete(before, after); +} diff --git a/app/src/main/cpp/waylandcomp/src/wl_clipboard.c b/app/src/main/cpp/waylandcomp/src/wl_clipboard.c new file mode 100644 index 000000000..ef321861c --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/wl_clipboard.c @@ -0,0 +1,524 @@ +/* + * Clipboard: wl_data_device_manager (core, v3) and zwlr_data_control_manager_v1 (v1), selection + * only โ€” drag and drop is refused. Both protocols share one selection: + * + * owner CLIENT a program's wl_data_source / zwlr_data_control_source_v1 (its mime list) + * owner HOST text the app pushed from Android's clipboard (offered under the text mimes) + * owner NONE nothing + * + * Every selection change is announced to every data-control device (they see the selection + * regardless of focus, as the protocol says) and to the wl_data_devices of the client holding + * keyboard focus; a client also gets the current selection when its keyboard focus arrives. + * + * winewayland prefers zwlr_data_control (it then runs the clipboard from the desktop process + * without needing keyboard focus) and tags its own offers with application/x.winewayland.tag + * so it can ignore them when they come back; we pass mime lists through untouched. + * + * Guest โ†’ Android: when a program sets a selection with a text mime, the compositor asks for it + * once over a pipe on the event loop (non-blocking, 1 MiB cap, 3 s timeout) and hands the text + * to the app (wnc_on_clipboard_text โ†’ ClipboardManager). Android โ†’ guest: the app's text + * becomes the HOST selection; receive requests are answered with a non-blocking writer. + */ +#define _GNU_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include "desktop_ext.h" +#include "wlr-data-control-unstable-v1-server-protocol.h" + +#define CLIP_MAX_BYTES (1024 * 1024) +#define CLIP_READ_TIMEOUT_MS 3000 +#define CLIP_WRITE_TIMEOUT_MS 5000 +#define MAX_MIMES 64 + +static struct wl_display *g_disp; + +enum owner { OWNER_NONE, OWNER_CLIENT, OWNER_HOST }; +static enum owner g_owner; +static uint32_t g_gen; /* bumps on every selection change */ + +struct source { /* a client's data source (either protocol) */ + struct wl_resource *res; + int is_control; + char *mimes[MAX_MIMES]; + int nmimes; +}; +static struct source *g_src; /* current CLIENT selection */ + +static char *g_host_text; /* current HOST selection */ +static size_t g_host_len; +static const char *const k_text_mimes[] = { + "text/plain;charset=utf-8", "text/plain", "UTF8_STRING", "TEXT", "STRING", NULL +}; + +struct device { + struct wl_resource *res; + int is_control; + struct wl_list link; +}; +static struct wl_list g_devices; +static struct wl_client *g_kb_client; /* keyboard focus holder */ + +struct offer { + struct wl_resource *res; + int is_control; + uint32_t gen; /* the selection this offer describes */ +}; + +static char *g_last_guest_text; /* echo guard: the last text handed to Android */ +static size_t g_last_guest_len; + +/* ------------------------------------------------------------------ helpers */ + +static int is_text_mime(const char *m) { + for (int i = 0; k_text_mimes[i]; i++) if (!strcmp(m, k_text_mimes[i])) return 1; + return 0; +} + +static const char *source_text_mime(const struct source *s) { + for (int i = 0; k_text_mimes[i]; i++) + for (int j = 0; j < s->nmimes; j++) + if (!strcmp(s->mimes[j], k_text_mimes[i])) return s->mimes[j]; + return NULL; +} + +static void source_send_send(struct source *s, const char *mime, int fd) { + if (s->is_control) zwlr_data_control_source_v1_send_send(s->res, mime, fd); + else wl_data_source_send_send(s->res, mime, fd); +} + +static void source_send_cancelled(struct source *s) { + if (s->is_control) zwlr_data_control_source_v1_send_cancelled(s->res); + else wl_data_source_send_cancelled(s->res); +} + +/* ------------------------------------------------------------------ host โ†’ client writer */ + +struct writer { + int fd; + char *data; + size_t len, off; + struct wl_event_source *src, *timer; +}; + +static void writer_free(struct writer *w) { + if (w->src) wl_event_source_remove(w->src); + if (w->timer) wl_event_source_remove(w->timer); + close(w->fd); + free(w->data); + free(w); +} + +static int writer_pump(struct writer *w) { + while (w->off < w->len) { + ssize_t n = write(w->fd, w->data + w->off, w->len - w->off); + if (n > 0) { w->off += (size_t)n; continue; } + if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return 0; /* wait */ + if (n < 0 && errno == EINTR) continue; + break; /* gone */ + } + return 1; +} + +static int on_writer_writable(int fd, uint32_t mask, void *data) { + struct writer *w = data; + if (mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP) || writer_pump(w)) writer_free(w); + return 0; +} + +static int on_writer_timeout(void *data) { + struct writer *w = data; + wnc_log("clipboard", "a program never read the clipboard text it asked for; giving up"); + writer_free(w); + return 0; +} + +static void writer_start(int fd, const char *data, size_t len) { + struct writer *w = calloc(1, sizeof(*w)); + if (!w) { close(fd); return; } + w->fd = fd; + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + if (len && !(w->data = malloc(len))) { close(fd); free(w); return; } + if (len) memcpy(w->data, data, len); + w->len = len; + if (writer_pump(w)) { writer_free(w); return; } + struct wl_event_loop *loop = wl_display_get_event_loop(g_disp); + w->src = wl_event_loop_add_fd(loop, fd, WL_EVENT_WRITABLE, on_writer_writable, w); + w->timer = wl_event_loop_add_timer(loop, on_writer_timeout, w); + if (w->timer) wl_event_source_timer_update(w->timer, CLIP_WRITE_TIMEOUT_MS); +} + +/* ------------------------------------------------------------------ client โ†’ host reader */ + +struct reader { + int fd; + char *buf; + size_t len; + const char *mime; /* points into the source's list */ + struct wl_event_source *src, *timer; +}; +static struct reader *g_reader; + +static void reader_free(struct reader *r) { + if (r->src) wl_event_source_remove(r->src); + if (r->timer) wl_event_source_remove(r->timer); + close(r->fd); + free(r->buf); + free(r); + if (g_reader == r) g_reader = NULL; +} + +static void reader_finish(struct reader *r) { + if (r->len > 0) { + char *copy = malloc(r->len + 1); + if (copy) { + memcpy(copy, r->buf, r->len); + copy[r->len] = 0; + free(g_last_guest_text); + g_last_guest_text = copy; + g_last_guest_len = r->len; + wnc_log("clipboard", "guest copied %zu bytes (%s)", r->len, r->mime); + wnc_on_clipboard_text(copy, (int)r->len); + } + } else { + wnc_log("clipboard", "guest copied an empty text; Android clipboard left alone"); + } + reader_free(r); +} + +static int on_reader_readable(int fd, uint32_t mask, void *data) { + struct reader *r = data; + for (;;) { + if (r->len >= CLIP_MAX_BYTES) { + wnc_log("clipboard", "guest text exceeds %d bytes; dropped", CLIP_MAX_BYTES); + reader_free(r); + return 0; + } + size_t room = CLIP_MAX_BYTES - r->len, chunk = room < 65536 ? room : 65536; + char *nb = realloc(r->buf, r->len + chunk); + if (!nb) { reader_free(r); return 0; } + r->buf = nb; + ssize_t n = read(fd, r->buf + r->len, chunk); + if (n > 0) { r->len += (size_t)n; continue; } + if (n == 0) { reader_finish(r); return 0; } /* EOF */ + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (mask & WL_EVENT_HANGUP) { reader_finish(r); return 0; } + return 0; + } + if (errno == EINTR) continue; + reader_free(r); + return 0; + } +} + +static int on_reader_timeout(void *data) { + struct reader *r = data; + wnc_log("clipboard", "timed out reading the guest's clipboard text (%zu bytes so far)", r->len); + reader_free(r); + return 0; +} + +/* Ask the current client source for its text, once. */ +static void read_client_text(void) { + const char *mime; + int p[2]; + if (g_reader) reader_free(g_reader); + if (!g_src || !(mime = source_text_mime(g_src))) return; + if (pipe2(p, O_CLOEXEC) != 0) return; + fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL) | O_NONBLOCK); /* our end only; the client writes blocking */ + struct reader *r = calloc(1, sizeof(*r)); + if (!r) { close(p[0]); close(p[1]); return; } + r->fd = p[0]; + r->mime = mime; + struct wl_event_loop *loop = wl_display_get_event_loop(g_disp); + r->src = wl_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, on_reader_readable, r); + r->timer = wl_event_loop_add_timer(loop, on_reader_timeout, r); + if (r->timer) wl_event_source_timer_update(r->timer, CLIP_READ_TIMEOUT_MS); + g_reader = r; + source_send_send(g_src, mime, p[1]); + close(p[1]); + wl_display_flush_clients(g_disp); +} + +/* ------------------------------------------------------------------ offers */ + +static void offer_res_destroy(struct wl_resource *r) { free(wl_resource_get_user_data(r)); } + +static void offer_receive(struct wl_client *c, struct wl_resource *r, const char *mime, int32_t fd) { + struct offer *o = wl_resource_get_user_data(r); + if (!o || o->gen != g_gen) { close(fd); return; } /* stale offer */ + if (g_owner == OWNER_HOST) { + if (is_text_mime(mime)) { + wnc_log("clipboard", "Android clipboard โ†’ guest (%zu bytes) for %s", g_host_len, wnc_client_name(c)); + writer_start(fd, g_host_text, g_host_len); + } else { + close(fd); + } + } else if (g_owner == OWNER_CLIENT && g_src) { + source_send_send(g_src, mime, fd); /* libwayland dups the fd */ + close(fd); + wl_display_flush_clients(g_disp); + } else { + close(fd); + } +} +static void offer_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void offer_accept(struct wl_client *c, struct wl_resource *r, uint32_t serial, const char *mime) {} +static void offer_finish(struct wl_client *c, struct wl_resource *r) {} +static void offer_set_actions(struct wl_client *c, struct wl_resource *r, uint32_t a, uint32_t p) {} + +static const struct wl_data_offer_interface data_offer_impl = { + .accept = offer_accept, .receive = offer_receive, .destroy = offer_destroy, + .finish = offer_finish, .set_actions = offer_set_actions, +}; +static const struct zwlr_data_control_offer_v1_interface control_offer_impl = { + .receive = offer_receive, .destroy = offer_destroy, +}; + +/* Tell one device what the selection is now (a fresh offer, or nothing). */ +static void device_send_selection(struct device *d) { + struct wl_client *c = wl_resource_get_client(d->res); + if (g_owner == OWNER_NONE) { + if (d->is_control) zwlr_data_control_device_v1_send_selection(d->res, NULL); + else wl_data_device_send_selection(d->res, NULL); + return; + } + struct offer *o = calloc(1, sizeof(*o)); + if (!o) return; + o->is_control = d->is_control; + o->gen = g_gen; + if (d->is_control) { + o->res = wl_resource_create(c, &zwlr_data_control_offer_v1_interface, 1, 0); + if (!o->res) { free(o); return; } + wl_resource_set_implementation(o->res, &control_offer_impl, o, offer_res_destroy); + zwlr_data_control_device_v1_send_data_offer(d->res, o->res); + } else { + o->res = wl_resource_create(c, &wl_data_offer_interface, wl_resource_get_version(d->res), 0); + if (!o->res) { free(o); return; } + wl_resource_set_implementation(o->res, &data_offer_impl, o, offer_res_destroy); + wl_data_device_send_data_offer(d->res, o->res); + } + if (g_owner == OWNER_CLIENT && g_src) { + for (int i = 0; i < g_src->nmimes; i++) { + if (d->is_control) zwlr_data_control_offer_v1_send_offer(o->res, g_src->mimes[i]); + else wl_data_offer_send_offer(o->res, g_src->mimes[i]); + } + } else { + for (int i = 0; k_text_mimes[i]; i++) { + if (d->is_control) zwlr_data_control_offer_v1_send_offer(o->res, k_text_mimes[i]); + else wl_data_offer_send_offer(o->res, k_text_mimes[i]); + } + } + if (d->is_control) zwlr_data_control_device_v1_send_selection(d->res, o->res); + else wl_data_device_send_selection(d->res, o->res); +} + +/* The selection changed: every data-control device hears it; wl_data_devices only if their + * client holds keyboard focus. */ +static void broadcast_selection(void) { + struct device *d; + g_gen++; + wl_list_for_each(d, &g_devices, link) { + if (d->is_control || (g_kb_client && wl_resource_get_client(d->res) == g_kb_client)) + device_send_selection(d); + } + wl_display_flush_clients(g_disp); +} + +void wnc_clipboard_keyboard_focus(struct wl_client *client) { + struct device *d; + if (client == g_kb_client) return; + g_kb_client = client; + if (!client) return; + wl_list_for_each(d, &g_devices, link) + if (!d->is_control && wl_resource_get_client(d->res) == client) device_send_selection(d); +} + +/* ------------------------------------------------------------------ sources */ + +static void source_res_destroy(struct wl_resource *r) { + struct source *s = wl_resource_get_user_data(r); + if (!s) return; + if (g_src == s) { + g_src = NULL; + g_owner = OWNER_NONE; + if (g_reader) reader_free(g_reader); + wnc_log("clipboard", "%s withdrew its clipboard contents", wnc_client_name(wl_resource_get_client(r))); + broadcast_selection(); + } + for (int i = 0; i < s->nmimes; i++) free(s->mimes[i]); + free(s); +} + +static void source_offer(struct wl_client *c, struct wl_resource *r, const char *mime) { + struct source *s = wl_resource_get_user_data(r); + if (!s || s->nmimes >= MAX_MIMES) return; + if (!(s->mimes[s->nmimes] = strdup(mime))) return; + s->nmimes++; +} +static void source_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void source_set_actions(struct wl_client *c, struct wl_resource *r, uint32_t actions) {} + +static const struct wl_data_source_interface data_source_impl = { + .offer = source_offer, .destroy = source_destroy, .set_actions = source_set_actions, +}; +static const struct zwlr_data_control_source_v1_interface control_source_impl = { + .offer = source_offer, .destroy = source_destroy, +}; + +static void create_source(struct wl_client *c, uint32_t id, int is_control, uint32_t ver) { + struct source *s = calloc(1, sizeof(*s)); + if (!s) { wl_client_post_no_memory(c); return; } + s->is_control = is_control; + s->res = wl_resource_create(c, is_control ? &zwlr_data_control_source_v1_interface + : &wl_data_source_interface, ver, id); + if (!s->res) { free(s); wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(s->res, is_control ? (const void *)&control_source_impl + : (const void *)&data_source_impl, + s, source_res_destroy); +} + +/* A device set (or cleared) the selection. */ +static void set_selection(struct wl_client *c, struct wl_resource *source_res) { + struct source *s = source_res ? wl_resource_get_user_data(source_res) : NULL; + if (s && s == g_src) return; + if (g_src && g_src != s) source_send_cancelled(g_src); + if (g_reader) reader_free(g_reader); + free(g_host_text); g_host_text = NULL; g_host_len = 0; + g_src = s; + g_owner = s ? OWNER_CLIENT : OWNER_NONE; + if (s) { + char list[200] = ""; + for (int i = 0; i < s->nmimes && strlen(list) < 150; i++) { + if (!strncmp(s->mimes[i], "application/x.winewayland", 25)) continue; + if (*list) strncat(list, ", ", sizeof(list) - strlen(list) - 1); + strncat(list, s->mimes[i], sizeof(list) - strlen(list) - 1); + } + wnc_log("clipboard", "selection set by %s (%s)", wnc_client_name(c), *list ? list : "no mime types"); + } else { + wnc_log("clipboard", "%s cleared the clipboard", wnc_client_name(c)); + } + broadcast_selection(); + read_client_text(); +} + +/* ------------------------------------------------------------------ devices */ + +static void device_res_destroy(struct wl_resource *r) { + struct device *d = wl_resource_get_user_data(r); + if (!d) return; + wl_list_remove(&d->link); + free(d); +} + +static void device_start_drag(struct wl_client *c, struct wl_resource *r, struct wl_resource *source, + struct wl_resource *origin, struct wl_resource *icon, uint32_t serial) { + wnc_log("clipboard", "%s started a drag and drop; not supported, cancelled", wnc_client_name(c)); + if (source) { + struct source *s = wl_resource_get_user_data(source); + if (s && s != g_src) source_send_cancelled(s); + } +} +static void device_set_selection(struct wl_client *c, struct wl_resource *r, struct wl_resource *source, uint32_t serial) { + set_selection(c, source); +} +static void device_release(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void control_device_set_selection(struct wl_client *c, struct wl_resource *r, struct wl_resource *source) { + set_selection(c, source); +} +static void control_device_set_primary(struct wl_client *c, struct wl_resource *r, struct wl_resource *source) { + /* No primary selection on Android; a source given here just gets cancelled. */ + if (source) { + struct source *s = wl_resource_get_user_data(source); + if (s && s != g_src) source_send_cancelled(s); + } +} + +static const struct wl_data_device_interface data_device_impl = { + .start_drag = device_start_drag, .set_selection = device_set_selection, .release = device_release, +}; +static const struct zwlr_data_control_device_v1_interface control_device_impl = { + .set_selection = control_device_set_selection, .destroy = device_release, + .set_primary_selection = control_device_set_primary, +}; + +static void create_device(struct wl_client *c, uint32_t id, int is_control, uint32_t ver) { + struct device *d = calloc(1, sizeof(*d)); + if (!d) { wl_client_post_no_memory(c); return; } + d->is_control = is_control; + d->res = wl_resource_create(c, is_control ? &zwlr_data_control_device_v1_interface + : &wl_data_device_interface, ver, id); + if (!d->res) { free(d); wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(d->res, is_control ? (const void *)&control_device_impl + : (const void *)&data_device_impl, + d, device_res_destroy); + wl_list_insert(&g_devices, &d->link); + /* Data-control devices see the selection at once; wl_data_devices when focus arrives. */ + if (is_control || (g_kb_client && c == g_kb_client)) device_send_selection(d); +} + +/* ------------------------------------------------------------------ managers */ + +static void dm_create_source(struct wl_client *c, struct wl_resource *r, uint32_t id) { + create_source(c, id, 0, wl_resource_get_version(r)); +} +static void dm_get_device(struct wl_client *c, struct wl_resource *r, uint32_t id, struct wl_resource *seat) { + create_device(c, id, 0, wl_resource_get_version(r)); +} +static void dm_release(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct wl_data_device_manager_interface dm_impl = { + .create_data_source = dm_create_source, .get_data_device = dm_get_device, .release = dm_release, +}; +static void bind_data_device_manager(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &wl_data_device_manager_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &dm_impl, NULL, NULL); +} + +static void cm_create_source(struct wl_client *c, struct wl_resource *r, uint32_t id) { + create_source(c, id, 1, 1); +} +static void cm_get_device(struct wl_client *c, struct wl_resource *r, uint32_t id, struct wl_resource *seat) { + create_device(c, id, 1, wl_resource_get_version(r)); +} +static void cm_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static const struct zwlr_data_control_manager_v1_interface cm_impl = { + .create_data_source = cm_create_source, .get_data_device = cm_get_device, .destroy = cm_destroy, +}; +static void bind_data_control_manager(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &zwlr_data_control_manager_v1_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &cm_impl, NULL, NULL); +} + +/* ------------------------------------------------------------------ host side */ + +/* Android's clipboard text (compositor thread, via the host queue). Takes the buffer. */ +void clipboard_host_text(char *utf8, size_t len) { + if (len && g_last_guest_text && len == g_last_guest_len && !memcmp(utf8, g_last_guest_text, len)) { + free(utf8); /* our own copy coming back: not a change */ + return; + } + if (len && g_owner == OWNER_HOST && g_host_len == len && !memcmp(utf8, g_host_text, len)) { + free(utf8); + return; + } + if (g_src) { source_send_cancelled(g_src); g_src = NULL; } + if (g_reader) reader_free(g_reader); + free(g_host_text); + g_host_text = len ? utf8 : NULL; + if (!len) free(utf8); + g_host_len = len; + g_owner = len ? OWNER_HOST : OWNER_NONE; + wnc_log("clipboard", len ? "Android clipboard โ†’ guest (%zu bytes)" : "Android clipboard cleared (%zu bytes)", len); + broadcast_selection(); +} + +void clipboard_init(struct wl_display *display) { + g_disp = display; + wl_list_init(&g_devices); + wl_global_create(display, &wl_data_device_manager_interface, 3, NULL, bind_data_device_manager); + wl_global_create(display, &zwlr_data_control_manager_v1_interface, 1, NULL, bind_data_control_manager); +} diff --git a/app/src/main/cpp/waylandcomp/src/wl_color_mgmt.c b/app/src/main/cpp/waylandcomp/src/wl_color_mgmt.c new file mode 100644 index 000000000..d75060871 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/wl_color_mgmt.c @@ -0,0 +1,1242 @@ +/* + * wp_color_manager_v1 (wayland-protocols staging color-management-v1, version 1) โ€” the subset Mesa's + * Wayland WSI binds โ€” plus the HDR gate, the per-surface image descriptions and the session log's + * HDR evidence. See color_mgmt.h for the design and HDR_RECON.md for why it is shaped this way. + * + * Strictness policy. A protocol error disconnects the client, and the client here is a GAME, so + * errors are only raised where the protocol leaves no choice and a conforming client can never hit + * them: a request gated on a feature we did not advertise, a property set twice, an incomplete + * parameter set, a description that is not ready, an intent we did not advertise. Everything a + * conforming client CAN produce on a bad day is accepted and logged instead: out-of-range HDR + * metadata is dropped (Android gets none rather than nonsense), a second colour-management object + * for the same surface replaces the first, a request on an inert object is ignored. + */ +#define _GNU_SOURCE +#include "color_mgmt.h" +#include "desktop_ext.h" +#include "ahb_swapchain.h" +#include "sc_layer.h" +#include "color-management-v1-server-protocol.h" +#include <pthread.h> +#include <stdarg.h> +#include <stdatomic.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +extern volatile int g_zero_copy; + +#define TAG "color" + +/* AHARDWAREBUFFER_FORMAT_* (android/hardware_buffer.h), for the log only. */ +const char *wnc_ahb_format_name(uint32_t f) { + static char other[32]; + switch (f) { + case 1: return "RGBA8888 (8-bit)"; + case 2: return "RGBX8888 (8-bit)"; + case 3: return "RGB888 (8-bit)"; + case 4: return "RGB565"; + case 0x16: return "RGBA16F (16-bit float)"; + case 0x2b: return "RGBA1010102 (10-bit)"; + default: snprintf(other, sizeof(other), "format %#x", f); return other; + } +} + +static int64_t now_ns(void) { + struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static const char *primaries_name(uint32_t p) { + switch (p) { + case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: return "BT.709/sRGB"; + case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: return "BT.2020"; + case WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3: return "Display P3"; + case WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3: return "DCI-P3"; + case WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB: return "Adobe RGB"; + default: return "other primaries"; + } +} + +static const char *tf_name(uint32_t tf) { + switch (tf) { + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: return "ST 2084 (PQ)"; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG: return "HLG"; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: return "sRGB"; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: return "extended linear"; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22: return "gamma 2.2"; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886: return "BT.1886"; + default: return "other transfer function"; + } +} + +static const char *dataspace_name(int32_t ds) { + switch (ds) { + case WNC_ADATASPACE_BT2020_PQ: return "BT2020_PQ"; + case WNC_ADATASPACE_BT2020_HLG: return "BT2020_HLG"; + case WNC_ADATASPACE_UNKNOWN: return "UNKNOWN (sRGB)"; + default: return "other"; + } +} + +/* ---------------------------------------------------------------- request (app) + gate */ + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; /* g_req, g_hdr (app threads + compositor) */ + +static struct { + int mode; /* 0 off, 1 on, 2 forced */ + char source[64]; + int dxvk_hdr, zero_copy_forced; + int display_known; + int display_id, hdr10, ratio_available, api; + char display_name[96], formats[96]; + float max_lum, max_avg, min_lum, ratio; + float highest_ratio; /* Display.getHighestHdrSdrRatio() (Android 16+), < 0 = not reported */ +} g_req = {.highest_ratio = -1.0f}; + +static _Atomic int g_gate = -1; /* -1 undecided, 0 closed, 1 open */ +static char g_gate_why[320]; /* the closed gate's reason (for the summary) */ +static _Atomic int g_output_on = 1; /* the drawer's HDR output switch (per session, starts on) */ + +void wnc_color_set_request(int mode, const char *source, int dxvk_hdr, int zero_copy_forced) { + pthread_mutex_lock(&g_mu); + g_req.mode = mode < 0 ? 0 : mode > 2 ? 2 : mode; + snprintf(g_req.source, sizeof(g_req.source), "%s", source ? source : "environment"); + g_req.dxvk_hdr = dxvk_hdr ? 1 : 0; + g_req.zero_copy_forced = zero_copy_forced ? 1 : 0; + pthread_mutex_unlock(&g_mu); + atomic_store(&g_output_on, 1); /* the drawer's switch is per session and starts on */ +} + +void wnc_color_set_display(int id, const char *name, const char *formats, int hdr10, float max_lum, + float max_avg, float min_lum, int ratio_available, float ratio, int api) { + pthread_mutex_lock(&g_mu); + int was_known = g_req.display_known, was_hdr10 = g_req.hdr10, was_id = g_req.display_id; + g_req.display_known = 1; + g_req.display_id = id; + snprintf(g_req.display_name, sizeof(g_req.display_name), "%s", name ? name : "?"); + snprintf(g_req.formats, sizeof(g_req.formats), "%s", formats ? formats : "unknown"); + g_req.hdr10 = hdr10 ? 1 : 0; + g_req.max_lum = max_lum; g_req.max_avg = max_avg; g_req.min_lum = min_lum; + g_req.ratio_available = ratio_available ? 1 : 0; + g_req.ratio = ratio; + g_req.api = api; + pthread_mutex_unlock(&g_mu); + /* The gate is decided once, when the compositor starts: a display that changes under a running + * session is recorded, and what it means is said out loud. */ + if (was_known && atomic_load(&g_gate) >= 0 && (was_hdr10 != (hdr10 ? 1 : 0) || was_id != id)) { + if (atomic_load(&g_gate) == 1 && !hdr10) + wnc_log(TAG, "the game is now on \"%s\" (display %d), which reports no HDR10: HDR frames stay tagged " + "BT2020_PQ and SurfaceFlinger tone-maps them for this display (the offer to games is fixed " + "for the session)", name ? name : "?", id); + else if (atomic_load(&g_gate) == 0 && hdr10) + wnc_log(TAG, "the game is now on \"%s\" (display %d), which reports HDR10 - HDR stays off for this " + "session (the gate is decided when the compositor starts); relaunch to use it", name ? name : "?", id); + } +} + +int wnc_color_gate_state(void) { return atomic_load(&g_gate); } +int wnc_color_hdr_open(void) { return atomic_load(&g_gate) == 1; } + +/* ---------------------------------------------------------------- HDR evidence (summary) */ + +static struct { + unsigned descs; /* HDR image descriptions made ready */ + unsigned applied; /* commits that made one current on a surface */ + char applied_who[160]; /* the last surface that got one */ + uint64_t layer_frames, layer_10bit, layer_copy8, copy_frames; /* layer_frames = every frame shown AS HDR */ + uint64_t composed, swapchain, tonemapped; + uint64_t tm_off; /* of `tonemapped`: while the drawer's HDR output switch was off */ + unsigned output_offs; /* times the switch was turned off */ + unsigned win_layer, win_copy, win_copy8, win_zc, win_composed, win_swapchain, win_tonemapped; + char copy_reason[160]; + char layer_fmt[48]; + int ratio_n, win_ratio_n, ratio_live_n; + float ratio_min, ratio_max, ratio_last, win_ratio_min, win_ratio_max, ratio_logged; + float ratio_live_max; /* highest reading taken WHILE HDR frames were on screen (the verdict's) */ + int64_t ratio_logged_ns, ratio_periodic_ns; + /* Live headroom. The display may grant HDR frames no headroom at all while they are on screen - seen + * on the Fold while the screen was being RECORDED (Android turns HDR headroom off for a recording, + * which is SDR), and plausibly with the brightness slider at maximum (SDR white already at the panel's + * limit) - so "the ratio rose once" is not enough for a reader at minute 3: time with HDR frames on + * screen, the part of it with the ratio above 1, and the current no-headroom streak. */ + int64_t prev_sample_ns; int prev_live; float prev_ratio; + int64_t live_ns, headroom_ns; + int64_t nohead_since_ns; /* HDR frames on screen and ratio <= 1.01 since then (0 = not now) */ + int nohead_said; /* the streak's "no headroom" line is written */ + char verdict[512]; +} g_hdr; +static _Atomic int64_t g_last_frame_ns; +static _Atomic int64_t g_last_tm_ns; /* the last HDR frame shown tone-mapped to SDR */ + +/* What the device says beside the headroom (the app, non-root APIs; wnc_color_env_*). The Fold showed + * three ways to lose HDR headroom with HDR frames on screen - a screen recording, heat under load, and + * (plausibly) the brightness slider at maximum - so every no-headroom line carries this. Under g_mu. */ +static struct { + int known; + int thermal; /* PowerManager THERMAL_STATUS_* (0 none .. 6 shutdown), -1 unknown */ + float headroom; /* PowerManager.getThermalHeadroom(10), < 0 = not available */ + int brightness; /* Settings.System.SCREEN_BRIGHTNESS (0..255), -1 unknown */ + int bmode; /* SCREEN_BRIGHTNESS_MODE: 1 automatic, 0 manual, -1 unknown */ + float requested; /* the last HDR headroom asked for (layer or screen surface): > 0 the ratio, + * 0 none asked, -1 the API is missing on this Android (< 15) */ +} g_env = {0, -1, -1.0f, -1, -1, 0.0f}; + +static const char *thermal_name(int t) { + switch (t) { + case 0: return "NONE"; case 1: return "LIGHT"; case 2: return "MODERATE"; case 3: return "SEVERE"; + case 4: return "CRITICAL"; case 5: return "EMERGENCY"; case 6: return "SHUTDOWN"; default: return "?"; + } +} + +/* "thermal MODERATE (headroom 0.83), brightness 180/255 manual". Caller holds g_mu. */ +static void env_text_locked(char *out, size_t n) { + char t[48], b[40]; + if (g_env.thermal < 0) snprintf(t, sizeof(t), "thermal ?"); + else if (g_env.headroom >= 0.0f) snprintf(t, sizeof(t), "thermal %s (headroom %.2f)", thermal_name(g_env.thermal), + g_env.headroom); + else snprintf(t, sizeof(t), "thermal %s", thermal_name(g_env.thermal)); + if (g_env.brightness < 0) snprintf(b, sizeof(b), "brightness ?"); + else snprintf(b, sizeof(b), "brightness %d/255%s", g_env.brightness, + g_env.bmode == 1 ? " auto" : g_env.bmode == 0 ? " manual" : ""); + snprintf(out, n, "%s, %s", t, b); +} + +/* What the evidence says about headroom lost with HDR frames on screen. A screenshot or a screen recording + * is not detected in this build (that needs extra permissions), so it is named as a possibility when + * nothing the app can measure explains the loss. Caller holds g_mu. */ +static void nohead_causes_locked(char *out, size_t n, int brief) { + const char *parts[2]; + char hot[48]; + int np = 0; + if (g_env.thermal >= 2) { + snprintf(hot, sizeof(hot), "the device is hot (thermal %s)", thermal_name(g_env.thermal)); + parts[np++] = hot; + } + /* Maximum brightness only counts when it is MANUAL: the Fold kept 3.00 of headroom at 255 auto. */ + if (g_env.brightness >= 250 && g_env.bmode == 0) parts[np++] = "brightness at maximum (manual)"; + if (np == 1) snprintf(out, n, "likely %s", parts[0]); + else if (np == 2) snprintf(out, n, "likely %s and %s", parts[0], parts[1]); + else if (g_env.known) { + char req[48], hi[48]; + if (g_env.requested > 0.0f) snprintf(req, sizeof(req), "%.1fx", g_env.requested); + else snprintf(req, sizeof(req), "%s", g_env.requested < 0.0f ? "not possible below Android 15" : "none"); + if (g_req.highest_ratio > 0.0f) snprintf(hi, sizeof(hi), "%.2f", g_req.highest_ratio); + else snprintf(hi, sizeof(hi), "not reported"); + if (brief) + snprintf(out, n, "no visible cause (a screenshot or recording? no HDR boost for apps?) - asked %s, highest " + "ratio %s", req, hi); + else + snprintf(out, n, "no cause the app can see (not hot, brightness below maximum): a screenshot or screen " + "recording, or this phone does not boost HDR from apps (the requested headroom was %s, the " + "display's highest ratio is %s)", req, hi); + } else + snprintf(out, n, "a screenshot or screen recording, heat, or manual brightness at maximum"); +} + +/* ---- the explicit HDR headroom request (API 35; sc_layer.c for the game layer, the app for the screen + * surface): content peak / SDR white, capped at the display's highest ratio when it reports one. */ +static _Atomic int g_screen_hr_x1000; /* the screen surface's wanted headroom x1000 (HDR10 swapchain) */ +static _Atomic int64_t g_screen_hr_ns; /* the last HDR10-swapchain frame */ +static char g_screen_hr_why[200]; /* under g_mu */ + +static float peak_of(const struct wnc_color *c, float disp_max, const char **src) { + if (c->max_cll > 0.0f) { *src = "max CLL"; return c->max_cll; } + if (c->has_st2086 && c->max_lum > 0.0f) { *src = "mastering max"; return c->max_lum; } + if (disp_max > 0.0f) { *src = "the display's peak"; return disp_max; } + *src = "assumed"; return 1000.0f; +} + +float wnc_color_desired_headroom(const struct wnc_color *c, char *why, size_t n) { + if (!c || !c->dataspace) { if (why && n) why[0] = 0; return 0.0f; } + pthread_mutex_lock(&g_mu); + const float disp_max = g_req.max_lum, highest = g_req.highest_ratio; + pthread_mutex_unlock(&g_mu); + const char *src; + const float peak = peak_of(c, disp_max, &src), white = wnc_color_sdr_white(); + if (highest > 0.0f && highest <= 1.01f) { + if (why && n) snprintf(why, n, "the display reports its highest HDR/SDR ratio as %.2f: no app can get a " + "boost here, so nothing is asked", highest); + return 0.0f; + } + float r = peak / (white > 0.0f ? white : 203.0f); + const int capped = highest > 1.0f && r > highest; + if (capped) r = highest; + if (r < 1.0f) r = 1.0f; + if (why && n) { + if (capped) snprintf(why, n, "content peak %.0f nits (%s) / SDR %.0f, capped at the display's highest ratio %.2f", + peak, src, white, highest); + else snprintf(why, n, "content peak %.0f nits (%s) / SDR %.0f%s", peak, src, white, + highest > 0.0f ? "" : "; the display reports no highest ratio"); + } + return r; +} + +void wnc_color_note_headroom_request(float ratio) { + pthread_mutex_lock(&g_mu); + g_env.requested = ratio; + pthread_mutex_unlock(&g_mu); +} + +void wnc_color_set_highest_ratio(float ratio) { + pthread_mutex_lock(&g_mu); + const float was = g_req.highest_ratio; + g_req.highest_ratio = ratio > 0.0f ? ratio : -1.0f; + pthread_mutex_unlock(&g_mu); + if (ratio > 0.0f && (was < 0.0f || was != ratio) && atomic_load(&g_gate) == 1) + wnc_log(TAG, "the display's highest HDR/SDR ratio is %.2f%s", ratio, + ratio <= 1.01f ? " - it reports no HDR boost at all: no app can raise HDR highlights above SDR white here" + : " (the most HDR headroom it can give)"); +} + +float wnc_color_screen_headroom(char *why, size_t n) { + const int64_t t = atomic_load(&g_screen_hr_ns); + if (!t || now_ns() - t > 1500000000LL) { if (why && n) why[0] = 0; return 0.0f; } + if (why && n) { + pthread_mutex_lock(&g_mu); + snprintf(why, n, "%s", g_screen_hr_why); + pthread_mutex_unlock(&g_mu); + } + return atomic_load(&g_screen_hr_x1000) / 1000.0f; +} + +int wnc_color_last_frame_age_ms(void) { + int64_t t = atomic_load(&g_last_frame_ns); + if (!t) return -1; + int64_t age = (now_ns() - t) / 1000000LL; + return age > 0x7fffffff ? 0x7fffffff : (int)age; +} + +/* The one-line answer a tester quotes. Caller holds g_mu. */ +static void verdict_locked(char *out, size_t size) { + int gate = atomic_load(&g_gate); + if (gate < 0) { snprintf(out, size, "not decided yet (the compositor has not started)"); return; } + if (gate == 0) { snprintf(out, size, "no, because %s", g_gate_why); return; } + if (g_hdr.layer_frames) { + const char *who = g_hdr.applied_who[0] ? g_hdr.applied_who : "the game"; + /* Only the paths that carried frames, so the ratio, the share and the device evidence still fit + * the log line. */ + char paths[200] = ""; + size_t pp = 0; + const uint64_t zc_other = g_hdr.layer_frames - g_hdr.layer_10bit - g_hdr.layer_copy8 - g_hdr.composed - + g_hdr.swapchain; + const struct { uint64_t n; const char *what; } path[] = { + {g_hdr.layer_10bit, "10-bit zero-copy"}, {zc_other, "zero-copy"}, {g_hdr.layer_copy8, "8-bit layer copy"}, + {g_hdr.composed, "composed"}, {g_hdr.swapchain, "HDR10 swapchain"}}; + for (unsigned i = 0; i < sizeof(path) / sizeof(path[0]); i++) + if (path[i].n && path[i].n <= g_hdr.layer_frames && pp < sizeof(paths) - 40) + pp += (size_t)snprintf(paths + pp, sizeof(paths) - pp, "%s%llu %s", pp ? ", " : "", + (unsigned long long)path[i].n, path[i].what); + if (g_hdr.tonemapped && pp < sizeof(paths) - 60) + pp += (size_t)snprintf(paths + pp, sizeof(paths) - pp, "; %llu more tone-mapped (%llu with HDR output off)", + (unsigned long long)g_hdr.tonemapped, (unsigned long long)g_hdr.tm_off); + char frames[340]; + snprintf(frames, sizeof(frames), "%llu frames of %s shown as BT2020_PQ (%s)", + (unsigned long long)g_hdr.layer_frames, who, paths); + char env[128] = "", causes[256] = ""; + if (g_env.known) env_text_locked(env, sizeof(env)); + /* Brief: the verdict has to fit one log line with everything else in it. */ + if (g_hdr.nohead_said) nohead_causes_locked(causes, sizeof(causes), 1); + /* Only readings taken while HDR frames were on screen count: the ratio says what the display did + * with THEM, not with whatever else was up at another moment. And the share of that time with any + * headroom, plus where it stands now: a ratio that rose once and then fell to 1.00 for minutes is + * not the same answer. */ + char share[384] = ""; + const long long live_s = (long long)(g_hdr.live_ns / 1000000000LL); + if (g_hdr.live_ns >= 1000000000LL) + snprintf(share, sizeof(share), "; headroom above 1.00 for %d%% of the HDR time (%lld of %lld s), now %.2f%s%s" + "%s%s%s", + (int)(100.0 * (double)g_hdr.headroom_ns / (double)g_hdr.live_ns + 0.5), + (long long)(g_hdr.headroom_ns / 1000000000LL), live_s, g_hdr.ratio_last, + g_hdr.nohead_said ? " - no headroom now: " : "", g_hdr.nohead_said ? causes : "", + env[0] ? " [" : "", env, env[0] ? "]" : ""); + /* The display's own ceiling (Android 16+) and what was asked for, where known. */ + char ceil[80] = "", req[48] = ""; + if (g_req.highest_ratio > 0.0f) snprintf(ceil, sizeof(ceil), ", highest possible %.2f", g_req.highest_ratio); + if (g_env.requested > 0.0f) snprintf(req, sizeof(req), ", %.1fx requested", g_env.requested); + else if (g_env.requested < 0.0f) snprintf(req, sizeof(req), ", no request possible below Android 15"); + if (g_hdr.ratio_live_n && g_hdr.ratio_live_max > 1.01f) + snprintf(out, size, "yes - %s; the display's HDR/SDR ratio rose to %.2f while they were on screen " + "(1.00 = SDR only%s%s)%s", frames, g_hdr.ratio_live_max, g_hdr.nohead_said ? "" : ceil, + g_hdr.nohead_said ? "" : req, share); + else if (g_hdr.ratio_live_n) + snprintf(out, size, "tagged but NOT confirmed - %s, but the display's HDR/SDR ratio stayed at %.2f while they " + "were on screen (%s%s%s): Android gave them no HDR headroom - %s%s%s%s", frames, + g_hdr.ratio_live_max, g_req.highest_ratio > 0.0f ? "" : "highest ratio not reported", + ceil[0] ? ceil + 2 : "", g_hdr.nohead_said ? "" : req, g_hdr.nohead_said ? causes : "a screenshot or " + "screen recording? heat? brightness at maximum (manual)? HDR off for this display?", + env[0] ? " [" : "", env, env[0] ? "]" : ""); + else if (g_hdr.ratio_n) + snprintf(out, size, "tagged, not measured - %s, but no HDR/SDR ratio reading was taken while they were on " + "screen (highest reading otherwise %.2f)", frames, g_hdr.ratio_max); + else + snprintf(out, size, "yes by the tag only - %s (this display reports no HDR/SDR ratio to confirm it)", frames); + return; + } + if (g_hdr.applied || g_hdr.descs) { + if (g_hdr.tonemapped && g_hdr.tm_off == g_hdr.tonemapped) + snprintf(out, size, "no - HDR output was switched off in the drawer whenever %s showed HDR: its %llu HDR " + "frames were shown tone-mapped to SDR - correct colours, no HDR brightness (switch it on to see HDR)", + g_hdr.applied_who[0] ? g_hdr.applied_who : "the game", (unsigned long long)g_hdr.tonemapped); + else if (g_hdr.tonemapped) + snprintf(out, size, "no - %s's HDR frames were shown tone-mapped to SDR (%llu frames: %llu with frame " + "generation through a screen surface that offers no HDR10 swapchain, %llu with HDR output switched " + "off in the drawer) - correct colours, no HDR brightness", + g_hdr.applied_who[0] ? g_hdr.applied_who : "the game", (unsigned long long)g_hdr.tonemapped, + (unsigned long long)(g_hdr.tonemapped - g_hdr.tm_off), (unsigned long long)g_hdr.tm_off); + else if (g_hdr.copy_frames) + snprintf(out, size, "no, because %s asked for HDR but all %llu of its HDR frames went through the " + "compositor's 8-bit SDR copy (%s) and were shown without tone mapping", + g_hdr.applied_who[0] ? g_hdr.applied_who : "the game", (unsigned long long)g_hdr.copy_frames, + g_hdr.copy_reason[0] ? g_hdr.copy_reason : "no display layer"); + else + snprintf(out, size, "no, because %s made an HDR image description but no frame of it was ever shown " + "(the swapchain may have failed after that - see the DXVK log)", + g_hdr.applied_who[0] ? g_hdr.applied_who : "a program"); + return; + } + snprintf(out, size, "no, because no program asked for HDR: the colour manager was offered but nothing set an HDR " + "image description (is DXVK_HDR=1 set, and is HDR switched on in the game's own display options?)"); +} + +/* Log the verdict when it changed (force = log it even if not). */ +static void log_verdict(int force) { + char v[512]; + pthread_mutex_lock(&g_mu); + verdict_locked(v, sizeof(v)); + int changed = strcmp(v, g_hdr.verdict) != 0; + if (changed) snprintf(g_hdr.verdict, sizeof(g_hdr.verdict), "%s", v); + pthread_mutex_unlock(&g_mu); + if (changed || force) wnc_log(TAG, "HDR on screen: %s", v); +} + +void wnc_color_frame_shown(const struct wnc_color *c, int path, uint32_t ahb_format) { + if (!c || !c->dataspace) return; + int first8 = 0; + pthread_mutex_lock(&g_mu); + switch (path) { + case WNC_HDR_ZERO_COPY: + if (ahb_format == 0x2b) g_hdr.layer_10bit++; + g_hdr.win_zc++; + snprintf(g_hdr.layer_fmt, sizeof(g_hdr.layer_fmt), "%s", wnc_ahb_format_name(ahb_format)); + break; + case WNC_HDR_LAYER_COPY: + if (!g_hdr.layer_copy8) first8 = 1; + g_hdr.layer_copy8++; g_hdr.win_copy8++; + snprintf(g_hdr.layer_fmt, sizeof(g_hdr.layer_fmt), "RGBA8888 layer copy"); + break; + case WNC_HDR_COMPOSED: + g_hdr.composed++; g_hdr.win_composed++; + snprintf(g_hdr.layer_fmt, sizeof(g_hdr.layer_fmt), "composed %s", wnc_ahb_format_name(ahb_format)); + break; + case WNC_HDR_SWAPCHAIN: + g_hdr.swapchain++; g_hdr.win_swapchain++; + snprintf(g_hdr.layer_fmt, sizeof(g_hdr.layer_fmt), "HDR10 swapchain"); + atomic_store(&g_screen_hr_ns, now_ns()); + break; + default: /* WNC_HDR_TONEMAPPED: shown, but not as HDR */ + g_hdr.tonemapped++; g_hdr.win_tonemapped++; + if (!atomic_load(&g_output_on)) g_hdr.tm_off++; + pthread_mutex_unlock(&g_mu); + atomic_store(&g_last_tm_ns, now_ns()); + return; + } + g_hdr.layer_frames++; + g_hdr.win_layer++; + pthread_mutex_unlock(&g_mu); + atomic_store(&g_last_frame_ns, now_ns()); + if (path == WNC_HDR_SWAPCHAIN) { + /* The screen surface's headroom request (the app applies it): worked out again when the + * description changes, else once a second. */ + static uint32_t ident; + static int64_t calc_ns; + const int64_t t = now_ns(); + if (c->identity != ident || t - calc_ns > 1000000000LL) { + char why[200]; + const float r = wnc_color_desired_headroom(c, why, sizeof(why)); + ident = c->identity; calc_ns = t; + pthread_mutex_lock(&g_mu); + snprintf(g_screen_hr_why, sizeof(g_screen_hr_why), "%s", why); + pthread_mutex_unlock(&g_mu); + atomic_store(&g_screen_hr_x1000, (int)(r * 1000.0f + 0.5f)); + } + } + if (first8) + wnc_log(TAG, "HDR frames are reaching the display layer through the compositor's 8-bit layer copy (the game's " + "buffer is not a gralloc buffer this frame): colours stay correct (the frame keeps its BT2020_PQ tag), " + "precision drops to 8 bits (banding possible)"); +} + +void wnc_color_frame_copied(const char *who, const char *reason) { + int first = 0; + pthread_mutex_lock(&g_mu); + g_hdr.copy_frames++; + g_hdr.win_copy++; + if (reason && strcmp(reason, g_hdr.copy_reason)) { + snprintf(g_hdr.copy_reason, sizeof(g_hdr.copy_reason), "%s", reason); + first = 1; + } + pthread_mutex_unlock(&g_mu); + if (first) + wnc_log(TAG, "HDR frames of %s go through the compositor's 8-bit SDR copy now, because %s: shown WITHOUT " + "tone mapping (washed out) until that changes", who ? who : "a window", reason ? reason : "?"); +} + +void wnc_color_ratio_sample(float ratio, int listener) { + if (atomic_load(&g_gate) != 1) return; + int64_t t = now_ns(); + int age = wnc_color_last_frame_age_ms(); + int live = age >= 0 && age < 1500; + int log_it = 0, periodic = 0, nohead_now = 0, head_back = 0; + float was; + char env[128] = "", causes[256] = ""; + pthread_mutex_lock(&g_mu); + was = g_hdr.ratio_logged; + if (ratio > 0.0f) { + /* Time with HDR frames on screen, credited to the reading that stood over it. */ + if (g_hdr.prev_sample_ns && g_hdr.prev_live) { + int64_t dt = t - g_hdr.prev_sample_ns; + if (dt > 0 && dt < 3000000000LL) { + g_hdr.live_ns += dt; + if (g_hdr.prev_ratio > 1.01f) g_hdr.headroom_ns += dt; + } + } + g_hdr.prev_sample_ns = t; g_hdr.prev_live = live; g_hdr.prev_ratio = ratio; + if (live && ratio <= 1.01f) { + if (!g_hdr.nohead_since_ns) g_hdr.nohead_since_ns = t; + if (!g_hdr.nohead_said && t - g_hdr.nohead_since_ns >= 5000000000LL) g_hdr.nohead_said = nohead_now = 1; + } else { + if (live && g_hdr.nohead_said) head_back = 1; + g_hdr.nohead_since_ns = 0; g_hdr.nohead_said = 0; + } + if (!g_hdr.ratio_n || ratio < g_hdr.ratio_min) g_hdr.ratio_min = ratio; + if (!g_hdr.ratio_n || ratio > g_hdr.ratio_max) g_hdr.ratio_max = ratio; + if (!g_hdr.win_ratio_n || ratio < g_hdr.win_ratio_min) g_hdr.win_ratio_min = ratio; + if (!g_hdr.win_ratio_n || ratio > g_hdr.win_ratio_max) g_hdr.win_ratio_max = ratio; + g_hdr.ratio_n++; g_hdr.win_ratio_n++; + g_hdr.ratio_last = ratio; + if (live) { + if (!g_hdr.ratio_live_n || ratio > g_hdr.ratio_live_max) g_hdr.ratio_live_max = ratio; + g_hdr.ratio_live_n++; + } + /* A change is logged at once (at most 4 lines a second); while HDR frames are on screen the + * steady value is restated every 5 s, so a tester's log shows it holding, not just moving. */ + float moved = ratio - g_hdr.ratio_logged; + if (moved < 0.0f) moved = -moved; + if ((g_hdr.ratio_n == 1 || moved >= 0.02f) && t - g_hdr.ratio_logged_ns > 250000000LL) + log_it = 1; + else if (live && t - g_hdr.ratio_periodic_ns > 5000000000LL) + log_it = periodic = 1; + if (log_it) { g_hdr.ratio_logged = ratio; g_hdr.ratio_logged_ns = t; g_hdr.ratio_periodic_ns = t; } + if (nohead_now || (periodic && live && ratio <= 1.01f)) { + env_text_locked(env, sizeof(env)); + nohead_causes_locked(causes, sizeof(causes), 0); + } + } + pthread_mutex_unlock(&g_mu); + if (nohead_now) + wnc_log(TAG, "no HDR headroom for 5 s while HDR frames are on screen (display HDR/SDR ratio %.2f): %s - Android " + "drops HDR headroom while the screen is recorded and when the device is hot, and some phones never " + "boost HDR for apps; HDR highlights look no brighter until that ends [%s]", ratio, causes, env); + if (head_back) + wnc_log(TAG, "HDR headroom is back: display HDR/SDR ratio %.2f with HDR frames on screen", ratio); + if (!log_it) return; + char when[64]; + if (age < 0) snprintf(when, sizeof(when), "none yet this session"); + else snprintf(when, sizeof(when), "%s, last one %d ms ago", live ? "yes" : "no", age); + if (periodic) + wnc_log(TAG, "display HDR/SDR ratio %.2f (steady; HDR frames on screen: %s)%s%s%s%s%s", ratio, when, + live && ratio <= 1.01f ? " - no HDR headroom: " : "", live && ratio <= 1.01f ? causes : "", + env[0] ? " [" : "", env, env[0] ? "]" : ""); + else if (was > 0.0f) + wnc_log(TAG, "display HDR/SDR ratio %.2f (was %.2f; HDR frames on screen: %s) [%s]", ratio, was, when, + listener ? "display listener" : "sampler"); + else + wnc_log(TAG, "display HDR/SDR ratio %.2f (first reading; HDR frames on screen: %s) [%s]", ratio, when, + listener ? "display listener" : "sampler"); +} + +void wnc_color_stats_tick(void) { + if (atomic_load(&g_gate) != 1) return; + char line[512]; + int any; + pthread_mutex_lock(&g_mu); + any = g_hdr.win_layer || g_hdr.win_copy || g_hdr.win_tonemapped; /* the ratio alone is logged when it moves */ + if (any) { + char ratio[256] = "no HDR/SDR ratio reading", env[128] = "", causes[256] = ""; + if (g_env.known) env_text_locked(env, sizeof(env)); + if (g_hdr.nohead_said) nohead_causes_locked(causes, sizeof(causes), 1); + if (g_hdr.win_ratio_n) + snprintf(ratio, sizeof(ratio), "display HDR/SDR ratio %.2f-%.2f (now %.2f)%s%s", g_hdr.win_ratio_min, + g_hdr.win_ratio_max, g_hdr.ratio_last, g_hdr.nohead_said ? " - NO headroom: " : "", + g_hdr.nohead_said ? causes : ""); + snprintf(line, sizeof(line), "HDR last 10 s: %u frames shown as BT2020_PQ (zero-copy %u, 8-bit layer copy %u, " + "composed picture %u, HDR10 swapchain %u; last buffer %s) | %u tone-mapped to SDR | %u washed-out " + "copies | %s%s%s", + g_hdr.win_layer, g_hdr.win_zc, g_hdr.win_copy8, g_hdr.win_composed, g_hdr.win_swapchain, + g_hdr.layer_fmt[0] ? g_hdr.layer_fmt : "-", g_hdr.win_tonemapped, g_hdr.win_copy, ratio, + env[0] ? " | " : "", env); + } + g_hdr.win_layer = g_hdr.win_copy = g_hdr.win_copy8 = 0; + g_hdr.win_zc = g_hdr.win_composed = g_hdr.win_swapchain = g_hdr.win_tonemapped = 0; + g_hdr.win_ratio_n = 0; + pthread_mutex_unlock(&g_mu); + if (any) wnc_log(TAG, "%s", line); + log_verdict(0); +} + +int wnc_color_hdr_state(void) { + if (atomic_load(&g_gate) != 1) return 0; + int age = wnc_color_last_frame_age_ms(); + if (age < 0 || age >= 1500) return 0; + pthread_mutex_lock(&g_mu); + int confirmed = !g_hdr.ratio_n || g_hdr.ratio_last > 1.01f; /* no ratio on this display: the tag is all there is */ + int nohead = g_hdr.nohead_said; + pthread_mutex_unlock(&g_mu); + return confirmed ? 1 : nohead ? 2 : 0; +} + +int wnc_color_hdr_on_screen(void) { return wnc_color_hdr_state() == 1; } + +static _Atomic int g_sdr_white_x100 = 20300; +void wnc_color_set_sdr_white(float nits) { + if (!(nits >= 10.0f && nits <= 2000.0f)) return; + atomic_store(&g_sdr_white_x100, (int)(nits * 100.0f + 0.5f)); + wnc_log(TAG, "SDR content inside an HDR picture is placed at %.0f nits (BANNER_WAYLAND_HDR_SDR_NITS)", nits); +} +float wnc_color_sdr_white(void) { return atomic_load(&g_sdr_white_x100) / 100.0f; } + +int wnc_color_requested(void) { + pthread_mutex_lock(&g_mu); + int m = g_req.mode; + pthread_mutex_unlock(&g_mu); + return m != 0; +} + +int wnc_color_output(void) { return atomic_load(&g_output_on); } + +void wnc_color_env_sample(int thermal, float headroom, int brightness, int bmode) { + if (atomic_load(&g_gate) != 1) return; /* evidence for the HDR lines only */ + char msg[2][200]; + int nmsg = 0; + pthread_mutex_lock(&g_mu); + const int first = !g_env.known; + if (!first && thermal != g_env.thermal) { + char hr[32] = ""; + if (headroom >= 0.0f) snprintf(hr, sizeof(hr), ", headroom %.2f", headroom); + snprintf(msg[nmsg++], sizeof(msg[0]), "thermal status now %s (was %s%s)%s", thermal_name(thermal), + thermal_name(g_env.thermal), hr, thermal >= 2 ? " - Android lowers HDR headroom when the device is hot" : ""); + } + if (!first && (brightness != g_env.brightness || bmode != g_env.bmode)) + snprintf(msg[nmsg++], sizeof(msg[0]), "screen brightness now %d/255 %s (was %d/255 %s)", brightness, + bmode == 1 ? "auto" : bmode == 0 ? "manual" : "?", g_env.brightness, + g_env.bmode == 1 ? "auto" : g_env.bmode == 0 ? "manual" : "?"); + g_env.thermal = thermal; + g_env.headroom = headroom; + g_env.brightness = brightness; + g_env.bmode = bmode; + g_env.known = 1; + if (first) { + char e[128]; + env_text_locked(e, sizeof(e)); + snprintf(msg[nmsg++], sizeof(msg[0]), "device evidence beside the HDR/SDR headroom: %s (thermal status and " + "brightness changes are logged as they happen)", e); + } + pthread_mutex_unlock(&g_mu); + for (int i = 0; i < nmsg; i++) wnc_log(TAG, "%s", msg[i]); +} + +int wnc_color_tonemapped_on_screen(void) { + if (atomic_load(&g_gate) != 1) return 0; + int64_t t = atomic_load(&g_last_tm_ns); + return t && now_ns() - t < 1500000000LL; +} + +void wnc_color_set_output(int on) { + on = on ? 1 : 0; + if (atomic_load(&g_gate) != 1) { + /* The drawer only shows the switch in sessions whose gate is open; say so if it ever gets here. */ + wnc_log(TAG, "HDR output switch (%s) ignored: HDR is not open in this session", on ? "on" : "off"); + return; + } + if (atomic_exchange(&g_output_on, on) == on) return; + char who[160]; + int age = wnc_color_last_frame_age_ms(), tm = wnc_color_tonemapped_on_screen(); + pthread_mutex_lock(&g_mu); + if (!on) g_hdr.output_offs++; + snprintf(who, sizeof(who), "%s", g_hdr.applied_who[0] ? g_hdr.applied_who : "no HDR program yet"); + pthread_mutex_unlock(&g_mu); + if (on) + wnc_log(TAG, "HDR output switched ON in the drawer: HDR frames go to the display as HDR again from the next " + "frame (%s; %s)", who, tm ? "they were being tone-mapped to SDR until now" : "none were on screen just now"); + else + wnc_log(TAG, "HDR output switched OFF in the drawer: HDR frames are tone-mapped to SDR from the next frame " + "(%s; %s). The game is not told - it keeps rendering HDR; its own HDR setting and DXVK_HDR are " + "untouched", who, age >= 0 && age < 1500 ? "HDR frames were on screen" : "no HDR frames on screen just now"); + wnc_request_redraw(); /* a paused game commits nothing: show the change now */ +} + +static _Atomic int g_session_ended; + +void wnc_color_session_end(void) { + if (atomic_exchange(&g_session_ended, 1)) return; + if (atomic_load(&g_gate) < 0) return; + pthread_mutex_lock(&g_mu); + int asked = g_req.mode != 0; + pthread_mutex_unlock(&g_mu); + if (asked) log_verdict(1); /* nobody asked for HDR: no summary to write */ +} + +/* ---------------------------------------------------------------- image descriptions */ + +struct cm_desc { + int refs; /* protocol objects + surfaces (pending / current) */ + struct wnc_color c; +}; + +static uint32_t g_next_identity = 1; + +static struct cm_desc *desc_ref(struct cm_desc *d) { if (d) d->refs++; return d; } +static void desc_unref(struct cm_desc *d) { if (d && --d->refs <= 0) free(d); } + +/* The parametric creator's collected properties. 0 / has_* = 0 means unset. */ +struct cm_params { + uint32_t tf, primaries; + int has_mprim; int32_t mprim[8]; + int has_mlum; uint32_t mlum_min, mlum_max; /* min is cd/mยฒ * 10000, max is cd/mยฒ */ + int has_cll; uint32_t cll; + int has_fall; uint32_t fall; +}; + +static void image_description_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void image_description_get_information(struct wl_client *c, struct wl_resource *r, uint32_t id) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_V1_ERROR_NO_INFORMATION, + "get_information is not allowed on this image description"); +} +static const struct wp_image_description_v1_interface image_description_impl = { + .destroy = image_description_destroy_req, + .get_information = image_description_get_information, +}; +static void image_description_resource_destroy(struct wl_resource *r) { + desc_unref(wl_resource_get_user_data(r)); +} + +/* An image description object that will never be ready (output / preferred descriptions are not + * implemented in round 1; nothing we serve asks for them). */ +static void make_failed_description(struct wl_client *c, struct wl_resource *parent, uint32_t id, const char *what) { + struct wl_resource *r = wl_resource_create(c, &wp_image_description_v1_interface, wl_resource_get_version(parent), id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &image_description_impl, NULL, image_description_resource_destroy); + wp_image_description_v1_send_failed(r, WP_IMAGE_DESCRIPTION_V1_CAUSE_OPERATING_SYSTEM, + "not implemented by this compositor"); + wnc_log(TAG, "%s asked for %s: not implemented in this build, answered 'failed'", wnc_client_name(c), what); +} + +static void params_create(struct wl_client *client, struct wl_resource *r, uint32_t id) { + struct cm_params *p = wl_resource_get_user_data(r); + if (!p->tf || !p->primaries) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, + "transfer function and primaries must both be set"); + return; + } + struct cm_desc *d = calloc(1, sizeof(*d)); + if (!d) { wl_client_post_no_memory(client); return; } + struct wnc_color *c = &d->c; + c->identity = g_next_identity++; + if (!g_next_identity) g_next_identity = 1; + c->primaries = p->primaries; + c->tf = p->tf; + if (p->primaries == WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 && p->tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ) + c->dataspace = WNC_ADATASPACE_BT2020_PQ; + else if (p->primaries == WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 && p->tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG) + c->dataspace = WNC_ADATASPACE_BT2020_HLG; + + /* HDR metadata, kept only when it makes sense. The PQ defaults stand in for what was not given + * (0.005 - 10000 cd/mยฒ, the target volume = the primary one); CTA-861.3 treats 0 as unknown. */ + char dropped[160] = ""; + float mlum_min = 0.005f, mlum_max = 10000.0f; + if (p->has_mlum) { + float mn = p->mlum_min / 10000.0f, mx = (float)p->mlum_max; + if (mx > mn && mx > 0.0f) { mlum_min = mn; mlum_max = mx; } + else snprintf(dropped + strlen(dropped), sizeof(dropped) - strlen(dropped), " mastering luminance %.4f-%.0f (max <= min),", mn, mx); + } + if (p->has_mprim || p->has_mlum) { + c->has_st2086 = 1; + if (p->has_mprim) { + c->red[0] = p->mprim[0] / 1e6f; c->red[1] = p->mprim[1] / 1e6f; + c->green[0] = p->mprim[2] / 1e6f; c->green[1] = p->mprim[3] / 1e6f; + c->blue[0] = p->mprim[4] / 1e6f; c->blue[1] = p->mprim[5] / 1e6f; + c->white[0] = p->mprim[6] / 1e6f; c->white[1] = p->mprim[7] / 1e6f; + } else { /* the container's own primaries (BT.2020, D65) */ + c->red[0] = 0.708f; c->red[1] = 0.292f; c->green[0] = 0.170f; c->green[1] = 0.797f; + c->blue[0] = 0.131f; c->blue[1] = 0.046f; c->white[0] = 0.3127f; c->white[1] = 0.3290f; + } + c->min_lum = mlum_min; + c->max_lum = mlum_max; + } + float cll = p->has_cll ? (float)p->cll : 0.0f, fall = p->has_fall ? (float)p->fall : 0.0f; + if (cll > 0.0f && (cll <= mlum_min || cll > mlum_max)) { + snprintf(dropped + strlen(dropped), sizeof(dropped) - strlen(dropped), " max CLL %.0f (outside %.4f-%.0f),", cll, mlum_min, mlum_max); + cll = 0.0f; + } + if (fall > 0.0f && (fall <= mlum_min || fall > mlum_max || (cll > 0.0f && fall > cll))) { + snprintf(dropped + strlen(dropped), sizeof(dropped) - strlen(dropped), " max FALL %.0f (outside the range or above max CLL),", fall); + fall = 0.0f; + } + if (cll > 0.0f || fall > 0.0f) { c->has_cta861 = 1; c->max_cll = cll; c->max_fall = fall; } + + int n = snprintf(c->text, sizeof(c->text), "%s, %s", primaries_name(c->primaries), tf_name(c->tf)); + if (c->has_st2086 && n < (int)sizeof(c->text)) + n += snprintf(c->text + n, sizeof(c->text) - (size_t)n, + "; mastering R %.4f,%.4f G %.4f,%.4f B %.4f,%.4f W %.4f,%.4f, %.4f-%.0f nits%s", + c->red[0], c->red[1], c->green[0], c->green[1], c->blue[0], c->blue[1], c->white[0], c->white[1], + c->min_lum, c->max_lum, p->has_mprim ? "" : " (container primaries)"); + if (n < (int)sizeof(c->text)) + n += snprintf(c->text + n, sizeof(c->text) - (size_t)n, "; max CLL %s%.0f, max FALL %s%.0f", + c->max_cll > 0.0f ? "" : "unknown ", c->max_cll, c->max_fall > 0.0f ? "" : "unknown ", c->max_fall); + if (!c->has_st2086 && !c->has_cta861 && n < (int)sizeof(c->text)) + snprintf(c->text + n, sizeof(c->text) - (size_t)n, " (no HDR metadata: the game has not called vkSetHdrMetadataEXT)"); + + struct wl_resource *out = wl_resource_create(client, &wp_image_description_v1_interface, + wl_resource_get_version(r), id); + if (!out) { free(d); wl_client_post_no_memory(client); return; } + d->refs = 1; + wl_resource_set_implementation(out, &image_description_impl, d, image_description_resource_destroy); + /* Sent from inside the request: Mesa blocks its swapchain on this event (it dispatches its queue + * until ready/failed arrives), and the event loop flushes it before it sleeps again. */ + wp_image_description_v1_send_ready(out, c->identity); + if (c->dataspace) { + pthread_mutex_lock(&g_mu); + g_hdr.descs++; + pthread_mutex_unlock(&g_mu); + } + wnc_log(TAG, "image description #%u from %s: %s -> ready (on a display layer: dataspace %s)", c->identity, + wnc_client_name(client), c->text, dataspace_name(c->dataspace)); + if (dropped[0]) { + size_t l = strlen(dropped); + if (l && dropped[l - 1] == ',') dropped[l - 1] = 0; + wnc_log(TAG, "image description #%u: dropped HDR metadata that breaks the protocol's rules:%s " + "(Android gets the rest)", c->identity, dropped); + } + wl_resource_destroy(r); /* create is the creator's destructor */ +} + +#define PARAMS_ONCE(field, what) \ + do { \ + if (field) { \ + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, \ + what " already set"); \ + return; \ + } \ + } while (0) + +static void params_set_tf_named(struct wl_client *c, struct wl_resource *r, uint32_t tf) { + struct cm_params *p = wl_resource_get_user_data(r); + PARAMS_ONCE(p->tf, "transfer function"); + if (tf != WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF, + "transfer function %u was not advertised", tf); + return; + } + p->tf = tf; +} +static void params_set_tf_power(struct wl_client *c, struct wl_resource *r, uint32_t eexp) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_tf_power is not supported"); +} +static void params_set_primaries_named(struct wl_client *c, struct wl_resource *r, uint32_t primaries) { + struct cm_params *p = wl_resource_get_user_data(r); + PARAMS_ONCE(p->primaries, "primaries"); + if (primaries != WP_COLOR_MANAGER_V1_PRIMARIES_BT2020) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED, + "primaries %u were not advertised", primaries); + return; + } + p->primaries = primaries; +} +static void params_set_primaries(struct wl_client *c, struct wl_resource *r, int32_t rx, int32_t ry, int32_t gx, + int32_t gy, int32_t bx, int32_t by, int32_t wx, int32_t wy) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_primaries is not supported"); +} +static void params_set_luminances(struct wl_client *c, struct wl_resource *r, uint32_t mn, uint32_t mx, uint32_t ref) { + wl_resource_post_error(r, WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_luminances is not supported"); +} +static void params_set_mastering_display_primaries(struct wl_client *c, struct wl_resource *r, int32_t rx, int32_t ry, + int32_t gx, int32_t gy, int32_t bx, int32_t by, int32_t wx, int32_t wy) { + struct cm_params *p = wl_resource_get_user_data(r); + PARAMS_ONCE(p->has_mprim, "mastering display primaries"); + p->has_mprim = 1; + p->mprim[0] = rx; p->mprim[1] = ry; p->mprim[2] = gx; p->mprim[3] = gy; + p->mprim[4] = bx; p->mprim[5] = by; p->mprim[6] = wx; p->mprim[7] = wy; +} +static void params_set_mastering_luminance(struct wl_client *c, struct wl_resource *r, uint32_t mn, uint32_t mx) { + struct cm_params *p = wl_resource_get_user_data(r); + PARAMS_ONCE(p->has_mlum, "mastering luminance"); + p->has_mlum = 1; p->mlum_min = mn; p->mlum_max = mx; +} +static void params_set_max_cll(struct wl_client *c, struct wl_resource *r, uint32_t v) { + struct cm_params *p = wl_resource_get_user_data(r); + p->has_cll = v != 0; p->cll = v; +} +static void params_set_max_fall(struct wl_client *c, struct wl_resource *r, uint32_t v) { + struct cm_params *p = wl_resource_get_user_data(r); + p->has_fall = v != 0; p->fall = v; +} +static const struct wp_image_description_creator_params_v1_interface params_impl = { + .create = params_create, + .set_tf_named = params_set_tf_named, + .set_tf_power = params_set_tf_power, + .set_primaries_named = params_set_primaries_named, + .set_primaries = params_set_primaries, + .set_luminances = params_set_luminances, + .set_mastering_display_primaries = params_set_mastering_display_primaries, + .set_mastering_luminance = params_set_mastering_luminance, + .set_max_cll = params_set_max_cll, + .set_max_fall = params_set_max_fall, +}; +static void params_resource_destroy(struct wl_resource *r) { free(wl_resource_get_user_data(r)); } + +/* ---------------------------------------------------------------- per-surface state */ + +/* One per wl_surface that ever had a colour-management object. Lives until the wl_surface goes (a + * swapchain rebuild destroys the object and makes a new one for the same surface). */ +struct cm_surf { + struct wl_resource *surface; /* the wl_surface */ + struct wl_listener surface_destroy; + struct wl_resource *owner; /* the live wp_color_management_surface_v1, NULL = none */ + struct cm_desc *pending, *current; + int pending_dirty; +}; + +static void cm_surface_destroyed(struct wl_listener *l, void *data); + +static struct cm_surf *surf_of(struct wl_resource *surface) { + if (!surface) return NULL; + struct wl_listener *l = wl_resource_get_destroy_listener(surface, cm_surface_destroyed); + if (!l) return NULL; + struct cm_surf *cs = wl_container_of(l, cs, surface_destroy); + return cs; +} + +static void describe_surface(struct wl_resource *surface, char *out, size_t size) { + struct surface *s = surface ? wl_resource_get_user_data(surface) : NULL; + if (s) wnc_surface_describe(s, out, size); + else snprintf(out, size, "a closed window"); +} + +static void cm_surface_destroyed(struct wl_listener *l, void *data) { + struct cm_surf *cs = wl_container_of(l, cs, surface_destroy); + wl_list_remove(&cs->surface_destroy.link); + if (cs->owner) wl_resource_set_user_data(cs->owner, NULL); /* inert from now on */ + desc_unref(cs->pending); + desc_unref(cs->current); + free(cs); +} + +static void cm_surface_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } + +static void cm_surface_set_image_description(struct wl_client *c, struct wl_resource *r, + struct wl_resource *desc_res, uint32_t intent) { + struct cm_surf *cs = wl_resource_get_user_data(r); + struct cm_desc *d = desc_res ? wl_resource_get_user_data(desc_res) : NULL; + if (!cs) { + wnc_log(TAG, "%s set an image description on a colour-management object whose surface is gone: ignored", + wnc_client_name(c)); + return; + } + if (!d) { + wl_resource_post_error(r, WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION, + "the image description is not ready"); + return; + } + if (intent != WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL) { + wl_resource_post_error(r, WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_RENDER_INTENT, + "rendering intent %u was not advertised", intent); + return; + } + desc_unref(cs->pending); + cs->pending = desc_ref(d); + cs->pending_dirty = 1; +} + +static void cm_surface_unset_image_description(struct wl_client *c, struct wl_resource *r) { + struct cm_surf *cs = wl_resource_get_user_data(r); + if (!cs) return; + desc_unref(cs->pending); + cs->pending = NULL; + cs->pending_dirty = 1; +} + +static const struct wp_color_management_surface_v1_interface cm_surface_impl = { + .destroy = cm_surface_destroy_req, + .set_image_description = cm_surface_set_image_description, + .unset_image_description = cm_surface_unset_image_description, +}; + +/* Destroying the object does what unset_image_description does (double-buffered, like it). */ +static void cm_surface_resource_destroy(struct wl_resource *r) { + struct cm_surf *cs = wl_resource_get_user_data(r); + if (!cs || cs->owner != r) return; + cs->owner = NULL; + desc_unref(cs->pending); + cs->pending = NULL; + cs->pending_dirty = 1; +} + +void wnc_color_commit(struct wl_resource *surface) { + if (atomic_load(&g_gate) != 1) return; + struct cm_surf *cs = surf_of(surface); + if (!cs || !cs->pending_dirty) return; + cs->pending_dirty = 0; + if (cs->pending == cs->current) return; + struct cm_desc *old = cs->current; + cs->current = desc_ref(cs->pending); + char who[160]; + describe_surface(surface, who, sizeof(who)); + if (cs->current) { + wnc_log(TAG, "%s: image description #%u now applies to its frames (%s; perceptual intent)", who, + cs->current->c.identity, cs->current->c.text); + if (cs->current->c.dataspace) { + pthread_mutex_lock(&g_mu); + g_hdr.applied++; + snprintf(g_hdr.applied_who, sizeof(g_hdr.applied_who), "%s", who); + pthread_mutex_unlock(&g_mu); + if (!g_zero_copy) + wnc_log(TAG, "%s: zero-copy presentation is OFF, so its HDR frames are composed into the HDR picture " + "(one extra pass per frame) - switch Zero-copy presentation on for the direct path", who); + } + } else if (old) { + wnc_log(TAG, "%s: image description #%u removed - its frames are sRGB again", who, old->c.identity); + } + desc_unref(old); +} + +const struct wnc_color *wnc_color_of(struct wl_resource *surface) { + if (atomic_load(&g_gate) != 1) return NULL; + struct cm_surf *cs = surf_of(surface); + return cs && cs->current ? &cs->current->c : NULL; +} + +/* ---------------------------------------------------------------- output + feedback (minimal) */ + +static void cm_output_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void cm_output_get_image_description(struct wl_client *c, struct wl_resource *r, uint32_t id) { + make_failed_description(c, r, id, "the output's image description"); +} +static const struct wp_color_management_output_v1_interface cm_output_impl = { + .destroy = cm_output_destroy_req, + .get_image_description = cm_output_get_image_description, +}; + +static void cm_feedback_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void cm_feedback_get_preferred(struct wl_client *c, struct wl_resource *r, uint32_t id) { + make_failed_description(c, r, id, "a surface's preferred image description"); +} +static const struct wp_color_management_surface_feedback_v1_interface cm_feedback_impl = { + .destroy = cm_feedback_destroy_req, + .get_preferred = cm_feedback_get_preferred, + .get_preferred_parametric = cm_feedback_get_preferred, +}; + +/* ---------------------------------------------------------------- wp_color_manager_v1 */ + +static void cm_destroy_req(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } + +static void cm_get_output(struct wl_client *c, struct wl_resource *r, uint32_t id, struct wl_resource *output) { + struct wl_resource *o = wl_resource_create(c, &wp_color_management_output_v1_interface, wl_resource_get_version(r), id); + if (!o) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(o, &cm_output_impl, NULL, NULL); +} + +static void cm_get_surface(struct wl_client *c, struct wl_resource *r, uint32_t id, struct wl_resource *surface) { + struct wl_resource *o = wl_resource_create(c, &wp_color_management_surface_v1_interface, wl_resource_get_version(r), id); + if (!o) { wl_client_post_no_memory(c); return; } + struct cm_surf *cs = surf_of(surface); + if (!cs) { + cs = calloc(1, sizeof(*cs)); + if (!cs) { wl_resource_destroy(o); wl_client_post_no_memory(c); return; } + cs->surface = surface; + cs->surface_destroy.notify = cm_surface_destroyed; + wl_resource_add_destroy_listener(surface, &cs->surface_destroy); + } + char who[160]; + describe_surface(surface, who, sizeof(who)); + if (cs->owner) { + /* The protocol says surface_exists; a game would be disconnected for it. The newer object wins. */ + wnc_log(TAG, "%s: a second colour-management object for the same surface - the newer one takes over " + "(protocol error surface_exists not raised)", who); + wl_resource_set_user_data(cs->owner, NULL); + } + cs->owner = o; + wl_resource_set_implementation(o, &cm_surface_impl, cs, cm_surface_resource_destroy); + wnc_log(TAG, "%s: colour-management surface created by %s (its Vulkan swapchain asks for a colour space " + "other than sRGB)", who, wnc_client_name(c)); +} + +static void cm_get_surface_feedback(struct wl_client *c, struct wl_resource *r, uint32_t id, struct wl_resource *surface) { + struct wl_resource *o = wl_resource_create(c, &wp_color_management_surface_feedback_v1_interface, + wl_resource_get_version(r), id); + if (!o) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(o, &cm_feedback_impl, NULL, NULL); +} + +static void cm_create_icc_creator(struct wl_client *c, struct wl_resource *r, uint32_t id) { + wl_resource_post_error(r, WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC image descriptions are not supported"); +} + +static void cm_create_parametric_creator(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct cm_params *p = calloc(1, sizeof(*p)); + if (!p) { wl_client_post_no_memory(c); return; } + struct wl_resource *o = wl_resource_create(c, &wp_image_description_creator_params_v1_interface, + wl_resource_get_version(r), id); + if (!o) { free(p); wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(o, ¶ms_impl, p, params_resource_destroy); +} + +static void cm_create_windows_scrgb(struct wl_client *c, struct wl_resource *r, uint32_t id) { + wl_resource_post_error(r, WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "windows_scrgb is not supported"); +} + +static const struct wp_color_manager_v1_interface cm_impl = { + .destroy = cm_destroy_req, + .get_output = cm_get_output, + .get_surface = cm_get_surface, + .get_surface_feedback = cm_get_surface_feedback, + .create_icc_creator = cm_create_icc_creator, + .create_parametric_creator = cm_create_parametric_creator, + .create_windows_scrgb = cm_create_windows_scrgb, +}; + +static void bind_cm(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + static struct wl_client *last_named; + struct wl_resource *r = wl_resource_create(c, &wp_color_manager_v1_interface, ver > 1 ? 1 : (int)ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &cm_impl, NULL, NULL); + /* All at bind, then done: Mesa reads them in the registry roundtrip that follows the bind and + * lists VK_COLOR_SPACE_HDR10_ST2084_EXT from exactly this pair (bt2020 + st2084_pq). Only what + * the display layer can honour is offered. */ + wp_color_manager_v1_send_supported_intent(r, WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + wp_color_manager_v1_send_supported_feature(r, WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC); + wp_color_manager_v1_send_supported_feature(r, WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); + wp_color_manager_v1_send_supported_primaries_named(r, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020); + wp_color_manager_v1_send_supported_tf_named(r, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ); + wp_color_manager_v1_send_done(r); + /* One line per program: Mesa binds again for every surface-format query. */ + if (last_named != c) { + last_named = c; + wnc_log(TAG, "%s bound wp_color_manager_v1 version %u (offered: perceptual intent; parametric descriptions " + "with mastering metadata; BT.2020 primaries; ST 2084 PQ) - its Vulkan driver can now list " + "VK_COLOR_SPACE_HDR10_ST2084_EXT", wnc_client_name(c), ver > 1 ? 1u : ver); + } +} + +void wnc_color_client_gone(struct wl_client *client) { + if (atomic_load(&g_gate) != 1 || atomic_load(&g_session_ended)) return; + /* A program that presented HDR has left: say where the session stands now, while it is fresh. */ + const char *name = wnc_client_name(client); + int mine; + pthread_mutex_lock(&g_mu); + mine = g_hdr.applied_who[0] && name && strstr(g_hdr.applied_who, name) != NULL; + pthread_mutex_unlock(&g_mu); + if (mine) log_verdict(1); +} + +/* ---------------------------------------------------------------- the gate */ + +void wnc_color_init(struct wl_display *display) { + char why[320] = ""; + int mode, dxvk_hdr, zc_forced, known, hdr10, id, ratio_avail, api; + char name[96], formats[96], source[64]; + float max_lum, max_avg, min_lum, ratio; + pthread_mutex_lock(&g_mu); + mode = g_req.mode; dxvk_hdr = g_req.dxvk_hdr; zc_forced = g_req.zero_copy_forced; + known = g_req.display_known; hdr10 = g_req.hdr10; id = g_req.display_id; + ratio_avail = g_req.ratio_available; api = g_req.api; + snprintf(name, sizeof(name), "%s", g_req.display_name); + snprintf(formats, sizeof(formats), "%s", g_req.formats); + snprintf(source, sizeof(source), "%s", g_req.source); + max_lum = g_req.max_lum; max_avg = g_req.max_avg; min_lum = g_req.min_lum; ratio = g_req.ratio; + pthread_mutex_unlock(&g_mu); + + char disp[256]; + if (known) + snprintf(disp, sizeof(disp), "\"%s\" (display %d, Android API %d) reports HDR types %s, peak %.0f nits, HDR/SDR " + "ratio %s", name, id, api, formats, max_lum, ratio_avail ? "available" : "not available"); + else + snprintf(disp, sizeof(disp), "the app could not read the display's HDR capability"); + + /* What asked for HDR, in the words the user knows it by: the editors' setting, or the env override. */ + char asked[128]; + if (mode == 2) snprintf(asked, sizeof(asked), "BANNER_WAYLAND_HDR=force (%s)", source); + else if (strstr(source, "env")) snprintf(asked, sizeof(asked), "BANNER_WAYLAND_HDR=1 (%s)", source); + else snprintf(asked, sizeof(asked), "HDR output is on (%s)", source); + + int layer_ok = sc_layer_can_tag_hdr(); + int ahb_ok = ahb_swapchain_advertised(); + char off_why[128]; + if (strstr(source, "env")) snprintf(off_why, sizeof(off_why), "BANNER_WAYLAND_HDR=0 in the %s overrides the setting", source); + else snprintf(off_why, sizeof(off_why), "the HDR output setting is off"); + if (mode == 0) + snprintf(why, sizeof(why), "HDR output is off (%s)", off_why); + else if (mode == 1 && !known) + snprintf(why, sizeof(why), "%s but %s", asked, disp); + else if (mode == 1 && !hdr10) + snprintf(why, sizeof(why), "%s but this display cannot show HDR10: %s", asked, disp); + else if (!layer_ok) + snprintf(why, sizeof(why), "this Android has no display layers with dataspace control " + "(ASurfaceControl + ASurfaceTransaction_setBufferDataSpace, Android 10+)"); + else if (!ahb_ok) + snprintf(why, sizeof(why), "zero-copy presentation is unavailable here (banner_ahb_v1 not advertised), and an " + "HDR frame is only honest on the game's own display layer"); + + if (why[0]) { + snprintf(g_gate_why, sizeof(g_gate_why), "%s", why); + atomic_store(&g_gate, 0); + if (mode) { + wnc_log(TAG, "HDR gate CLOSED: %s. Nothing is advertised (no wp_color_manager_v1, no 10-bit dma-buf " + "formats): games see an SDR display, exactly as without the switch", why); + if (dxvk_hdr) + wnc_log(TAG, "DXVK_HDR=1 is set: DXGI will still claim an HDR display that no swapchain can get here " + "(games that check fall back to SDR; some show washed-out colours) - remove it on this display"); + log_verdict(1); + } else if (dxvk_hdr || (known && hdr10)) { + /* Off, and silent unless it is worth a line: a display that could show HDR10, or a DXVK + * switch that promises games an HDR display they cannot get. */ + wnc_log(TAG, "HDR output off for this session (%s)%s%s", off_why, + (known && hdr10) ? " - this display lists HDR10, so switching HDR output on for this game would " + "offer it to games" : "", + dxvk_hdr ? " - but DXVK_HDR=1 is set, so DXGI claims an HDR display the game cannot get a " + "swapchain for" : ""); + } + return; + } + + if (!wl_global_create(display, &wp_color_manager_v1_interface, 1, NULL, bind_cm)) { + snprintf(g_gate_why, sizeof(g_gate_why), "creating the wp_color_manager_v1 global failed"); + atomic_store(&g_gate, 0); + wnc_log("error", "color: wp_color_manager_v1 global creation failed - HDR gate closed"); + log_verdict(1); + return; + } + atomic_store(&g_gate, 1); + char syms[160]; + sc_layer_hdr_symbols(syms, sizeof(syms)); + wnc_log(TAG, "HDR gate %s: %s and %s. Offering games HDR10: wp_color_manager_v1 version 1 (BT.2020 primaries " + "+ ST 2084 PQ, parametric descriptions with mastering metadata) and 10-bit AB30/XB30 dma-buf formats; HDR " + "frames go on the game's own display layer as BT2020_PQ (%s)", + mode == 2 ? "FORCED OPEN (testing)" : "OPEN", asked, disp, syms); + if (mode == 2 && !hdr10) + wnc_log(TAG, "BANNER_WAYLAND_HDR=force on a display without HDR10 - testing only: SurfaceFlinger will tone-map " + "the game layer for this panel (expect GPU/CLIENT composition for it), nothing looks HDR"); + if (zc_forced) + wnc_log(TAG, "zero-copy presentation turned on for this session: the HDR game's own 10-bit frames go straight " + "to its display layer (the best path; anything that needs the compositor gets the composed HDR picture)"); + if (!dxvk_hdr) + wnc_log(TAG, "DXVK_HDR=1 is not in the game's environment: DXVK games will not see an HDR display in DXGI " + "(dxgi.enableHDR in a dxvk.conf does the same) - add DXVK_HDR=1 to the container's or shortcut's " + "environment variables"); + (void)max_avg; (void)min_lum; +} diff --git a/app/src/main/cpp/waylandcomp/src/wl_text_input.c b/app/src/main/cpp/waylandcomp/src/wl_text_input.c new file mode 100644 index 000000000..31416b269 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/wl_text_input.c @@ -0,0 +1,276 @@ +/* + * Text input: zwp_text_input_manager_v3 (advertised at version 1), the host input method + * bridge winewayland.drv uses for IME text (our soft keyboard). + * + * Focus model. winewayland enables its text input as soon as our `enter` names a surface and + * routes every IME update to that surface's window โ€” inside that window's process (the update + * list is per process). Keyboard focus in this compositor sits on the desktop surface (the + * desktop owner's process), so text input can't follow it; it follows wnc_ime_target(): + * the program window last clicked, else the topmost program window. Clicking into notepad + * moves the text-input focus to notepad's process, which is where the edit control lives. + * + * Protocol state. Requests between commits are pending; `commit` bumps the serial and applies + * them (enable resets the state as the protocol says). We answer with `done(serial)` only when + * we sent events (preedit/commit strings), never for every commit. + * + * Host side. wnc_on_text_input(enabled, program, cursor rect in scene pixels) tells the app + * when a program starts/stops accepting IME text or moves its caret (Wine reports the rect only + * when an edit control positions its IME composition window, via ImmSetCompositionWindow). + * Typed text arrives through text_input_host_*: preedit_string / commit_string + done. + * delete_surrounding_text is NOT used: winewayland's handler for it is empty, so deletions + * become Backspace/Delete key presses on the wl_keyboard path instead. + */ +#define _GNU_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include "desktop_ext.h" +#include "text-input-unstable-v3-server-protocol.h" + +#ifndef KEY_BACKSPACE +#define KEY_BACKSPACE 14 +#endif +#ifndef KEY_DELETE +#define KEY_DELETE 111 +#endif + +static struct wl_display *g_disp; + +struct text_input { + struct wl_resource *res; + struct wl_client *client; + struct wl_list link; + struct wl_resource *entered; /* surface we sent enter for, or NULL */ + uint32_t serial; /* commits received */ + /* pending, applied on commit */ + int p_enable, p_disable, p_rect_set, p_content_set; + int p_rect[4]; + uint32_t p_hint, p_purpose; + /* current */ + int enabled; + int rect[4]; + uint32_t hint, purpose; + int has_preedit; /* we sent a non-empty preedit last */ +}; +static struct wl_list g_inputs; +static struct wl_resource *g_target; /* surface text input is focused on */ + +/* What the app was last told. */ +static int g_told_enabled, g_told_rect[4]; +static struct text_input *g_told_ti; + +static const char *ti_program(struct text_input *ti) { return wnc_client_name(ti->client); } + +/* The enabled text input focused on the current target, if any. */ +static struct text_input *active_input(void) { + struct text_input *ti; + if (!g_target) return NULL; + wl_list_for_each(ti, &g_inputs, link) + if (ti->entered == g_target && ti->enabled) return ti; + return NULL; +} + +static void tell_app(void) { + struct text_input *ti = active_input(); + int enabled = ti != NULL, rect[4] = {0, 0, 0, 0}; + if (ti) { + int ox = 0, oy = 0; + wnc_surface_scene_origin(ti->entered, &ox, &oy); + rect[0] = ti->rect[0] + ox; rect[1] = ti->rect[1] + oy; + rect[2] = ti->rect[2]; rect[3] = ti->rect[3]; + } + if (enabled == g_told_enabled && ti == g_told_ti && !memcmp(rect, g_told_rect, sizeof(rect))) return; + if (enabled && (!g_told_enabled || ti != g_told_ti)) + wnc_log("text-input", "text input enabled by %s (cursor rect %d,%d %dx%d)", ti_program(ti), + rect[0], rect[1], rect[2], rect[3]); + else if (enabled) + wnc_log("text-input", "cursor rect from %s: %d,%d %dx%d", ti_program(ti), rect[0], rect[1], rect[2], rect[3]); + else + wnc_log("text-input", "text input disabled%s%s", g_told_ti ? " by " : "", g_told_ti ? ti_program(g_told_ti) : ""); + g_told_enabled = enabled; + g_told_ti = ti; + memcpy(g_told_rect, rect, sizeof(rect)); + wnc_on_text_input(enabled, enabled ? ti_program(ti) : "", rect[0], rect[1], rect[2], rect[3]); +} + +/* ------------------------------------------------------------------ focus */ + +static void send_leave(struct text_input *ti) { + if (!ti->entered) return; + zwp_text_input_v3_send_leave(ti->res, ti->entered); + ti->entered = NULL; + ti->enabled = 0; /* the protocol disables on leave */ + ti->has_preedit = 0; +} + +void wnc_text_input_refocus(void) { + struct wl_resource *t = wnc_ime_target(); + struct text_input *ti; + if (t == g_target) return; + wl_list_for_each(ti, &g_inputs, link) + if (ti->entered && ti->entered != t) send_leave(ti); + g_target = t; + if (t) { + struct wl_client *c = wl_resource_get_client(t); + wl_list_for_each(ti, &g_inputs, link) { + if (ti->client != c || ti->entered == t) continue; + ti->entered = t; + zwp_text_input_v3_send_enter(ti->res, t); + } + } + tell_app(); + wl_display_flush_clients(g_disp); +} + +void wnc_text_input_surface_gone(struct wl_resource *surface) { + struct text_input *ti; + wl_list_for_each(ti, &g_inputs, link) + if (ti->entered == surface) { ti->entered = NULL; ti->enabled = 0; ti->has_preedit = 0; } + if (g_target == surface) g_target = NULL; + if (g_told_ti && g_told_ti->entered == NULL && g_told_enabled) tell_app(); +} + +/* ------------------------------------------------------------------ requests */ + +static void ti_res_destroy(struct wl_resource *r) { + struct text_input *ti = wl_resource_get_user_data(r); + if (!ti) return; + wl_list_remove(&ti->link); + if (g_told_ti == ti) { g_told_ti = NULL; g_told_enabled = 0; memset(g_told_rect, 0, sizeof(g_told_rect)); + wnc_on_text_input(0, "", 0, 0, 0, 0); } + free(ti); +} + +static void ti_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void ti_enable(struct wl_client *c, struct wl_resource *r) { + struct text_input *ti = wl_resource_get_user_data(r); + if (ti) { ti->p_enable = 1; ti->p_disable = 0; } +} +static void ti_disable(struct wl_client *c, struct wl_resource *r) { + struct text_input *ti = wl_resource_get_user_data(r); + if (ti) { ti->p_disable = 1; ti->p_enable = 0; } +} +static void ti_set_surrounding_text(struct wl_client *c, struct wl_resource *r, const char *text, int32_t cursor, int32_t anchor) {} +static void ti_set_text_change_cause(struct wl_client *c, struct wl_resource *r, uint32_t cause) {} +static void ti_set_content_type(struct wl_client *c, struct wl_resource *r, uint32_t hint, uint32_t purpose) { + struct text_input *ti = wl_resource_get_user_data(r); + if (ti) { ti->p_hint = hint; ti->p_purpose = purpose; ti->p_content_set = 1; } +} +static void ti_set_cursor_rectangle(struct wl_client *c, struct wl_resource *r, int32_t x, int32_t y, int32_t w, int32_t h) { + struct text_input *ti = wl_resource_get_user_data(r); + if (!ti) return; + ti->p_rect[0] = x; ti->p_rect[1] = y; ti->p_rect[2] = w; ti->p_rect[3] = h; + ti->p_rect_set = 1; +} +static void ti_commit(struct wl_client *c, struct wl_resource *r) { + struct text_input *ti = wl_resource_get_user_data(r); + if (!ti) return; + ti->serial++; + if (ti->p_enable) { + ti->enabled = ti->entered != NULL; /* enable only counts while focused */ + memset(ti->rect, 0, sizeof(ti->rect)); + ti->hint = 0; ti->purpose = 0; + ti->has_preedit = 0; + } + if (ti->p_disable) { ti->enabled = 0; ti->has_preedit = 0; } + if (ti->p_rect_set) memcpy(ti->rect, ti->p_rect, sizeof(ti->rect)); + if (ti->p_content_set) { ti->hint = ti->p_hint; ti->purpose = ti->p_purpose; } + ti->p_enable = ti->p_disable = ti->p_rect_set = ti->p_content_set = 0; + tell_app(); +} +static void ti_set_available_actions(struct wl_client *c, struct wl_resource *r, struct wl_array *a) {} +static void ti_show_input_panel(struct wl_client *c, struct wl_resource *r) {} +static void ti_hide_input_panel(struct wl_client *c, struct wl_resource *r) {} + +static const struct zwp_text_input_v3_interface ti_impl = { + .destroy = ti_destroy, .enable = ti_enable, .disable = ti_disable, + .set_surrounding_text = ti_set_surrounding_text, .set_text_change_cause = ti_set_text_change_cause, + .set_content_type = ti_set_content_type, .set_cursor_rectangle = ti_set_cursor_rectangle, + .commit = ti_commit, .set_available_actions = ti_set_available_actions, + .show_input_panel = ti_show_input_panel, .hide_input_panel = ti_hide_input_panel, +}; + +static void tim_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void tim_get_text_input(struct wl_client *c, struct wl_resource *r, uint32_t id, struct wl_resource *seat) { + struct text_input *ti = calloc(1, sizeof(*ti)); + if (!ti) { wl_client_post_no_memory(c); return; } + ti->res = wl_resource_create(c, &zwp_text_input_v3_interface, wl_resource_get_version(r), id); + if (!ti->res) { free(ti); wl_client_post_no_memory(c); return; } + ti->client = c; + wl_resource_set_implementation(ti->res, &ti_impl, ti, ti_res_destroy); + wl_list_insert(&g_inputs, &ti->link); + if (g_target && wl_resource_get_client(g_target) == c) { + ti->entered = g_target; + zwp_text_input_v3_send_enter(ti->res, g_target); + } +} +static const struct zwp_text_input_manager_v3_interface tim_impl = { + .destroy = tim_destroy, .get_text_input = tim_get_text_input, +}; +static void bind_text_input_manager(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &zwp_text_input_manager_v3_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &tim_impl, NULL, NULL); +} + +/* ------------------------------------------------------------------ host side (compositor thread) */ + +static size_t utf8_chars(const char *s, size_t len) { + size_t n = 0; + for (size_t i = 0; i < len; i++) if (((unsigned char)s[i] & 0xC0) != 0x80) n++; + return n; +} + +/* Byte offset of UTF-8 character index `chars` (negative = end). */ +static int32_t utf8_offset(const char *s, size_t len, int chars) { + if (chars < 0) return (int32_t)len; + size_t i = 0; + while (i < len && chars > 0) { + i++; + while (i < len && ((unsigned char)s[i] & 0xC0) == 0x80) i++; + chars--; + } + return (int32_t)i; +} + +void text_input_host_commit(const char *utf8, size_t len) { + struct text_input *ti = active_input(); + if (!ti) { + wnc_log("text-input", "typed text (%zu chars) dropped: no program accepts text input", utf8_chars(utf8, len)); + return; + } + if (ti->has_preedit) { zwp_text_input_v3_send_preedit_string(ti->res, "", 0, 0); ti->has_preedit = 0; } + zwp_text_input_v3_send_commit_string(ti->res, utf8); + zwp_text_input_v3_send_done(ti->res, ti->serial); + wnc_log("text-input", "committed %zu chars to %s", utf8_chars(utf8, len), ti_program(ti)); +} + +void text_input_host_preedit(const char *utf8, size_t len, int cursor_begin, int cursor_end) { + struct text_input *ti = active_input(); + if (!ti) return; + if (!len && !ti->has_preedit) return; + zwp_text_input_v3_send_preedit_string(ti->res, len ? utf8 : "", + utf8_offset(utf8, len, cursor_begin), utf8_offset(utf8, len, cursor_end)); + zwp_text_input_v3_send_done(ti->res, ti->serial); + ti->has_preedit = len > 0; +} + +void text_input_host_delete(int before, int after) { + if (before <= 0 && after <= 0) return; + if (before > 64) before = 64; + if (after > 64) after = 64; + struct text_input *ti = active_input(); + if (ti && ti->has_preedit) { /* deleting inside a composition: clear it first */ + zwp_text_input_v3_send_preedit_string(ti->res, "", 0, 0); + zwp_text_input_v3_send_done(ti->res, ti->serial); + ti->has_preedit = 0; + } + for (int i = 0; i < before; i++) { wnc_inject_key(KEY_BACKSPACE, 1); wnc_inject_key(KEY_BACKSPACE, 0); } + for (int i = 0; i < after; i++) { wnc_inject_key(KEY_DELETE, 1); wnc_inject_key(KEY_DELETE, 0); } + wnc_log("text-input", "deleted %d before / %d after the caret (as key presses)", before, after); +} + +void text_input_init(struct wl_display *display) { + g_disp = display; + wl_list_init(&g_inputs); + wl_global_create(display, &zwp_text_input_manager_v3_interface, 1, NULL, bind_text_input_manager); +} diff --git a/app/src/main/cpp/waylandcomp/src/wl_toplevel_icon.c b/app/src/main/cpp/waylandcomp/src/wl_toplevel_icon.c new file mode 100644 index 000000000..7f82addb6 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/wl_toplevel_icon.c @@ -0,0 +1,36 @@ +/* + * xdg_toplevel_icon_manager_v1 (version 1): window icons are accepted and dropped. The scene + * has no title bars to draw them in; winewayland only stops complaining that the global is + * missing. We send no icon_size (any size is fine) and `done` at bind, as the protocol asks. + */ +#include <stdlib.h> +#include "desktop_ext.h" +#include "xdg-toplevel-icon-v1-server-protocol.h" + +static void icon_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void icon_set_name(struct wl_client *c, struct wl_resource *r, const char *name) {} +static void icon_add_buffer(struct wl_client *c, struct wl_resource *r, struct wl_resource *buffer, int32_t scale) {} +static const struct xdg_toplevel_icon_v1_interface icon_impl = { + .destroy = icon_destroy, .set_name = icon_set_name, .add_buffer = icon_add_buffer, +}; + +static void mgr_destroy(struct wl_client *c, struct wl_resource *r) { wl_resource_destroy(r); } +static void mgr_create_icon(struct wl_client *c, struct wl_resource *r, uint32_t id) { + struct wl_resource *icon = wl_resource_create(c, &xdg_toplevel_icon_v1_interface, wl_resource_get_version(r), id); + if (!icon) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(icon, &icon_impl, NULL, NULL); +} +static void mgr_set_icon(struct wl_client *c, struct wl_resource *r, struct wl_resource *toplevel, struct wl_resource *icon) {} +static const struct xdg_toplevel_icon_manager_v1_interface mgr_impl = { + .destroy = mgr_destroy, .create_icon = mgr_create_icon, .set_icon = mgr_set_icon, +}; +static void bind_icon_manager(struct wl_client *c, void *data, uint32_t ver, uint32_t id) { + struct wl_resource *r = wl_resource_create(c, &xdg_toplevel_icon_manager_v1_interface, ver, id); + if (!r) { wl_client_post_no_memory(c); return; } + wl_resource_set_implementation(r, &mgr_impl, NULL, NULL); + xdg_toplevel_icon_manager_v1_send_done(r); +} + +void toplevel_icon_init(struct wl_display *display) { + wl_global_create(display, &xdg_toplevel_icon_manager_v1_interface, 1, NULL, bind_icon_manager); +} diff --git a/app/src/main/cpp/winlator/fakeinput.cpp b/app/src/main/cpp/winlator/fakeinput.cpp index fe6cac891..d09365167 100644 --- a/app/src/main/cpp/winlator/fakeinput.cpp +++ b/app/src/main/cpp/winlator/fakeinput.cpp @@ -1,5 +1,6 @@ #include <algorithm> #include <cerrno> +#include <climits> #include <cstdint> #include <cstdlib> #include <cstring> @@ -39,10 +40,22 @@ #define EXPORT __attribute__((visibility("default"))) extern "C" +// glibc declares the request as unsigned long; bionic as int. The same source also serves the +// Linux runtime's preload (tools/linuxfs), where Steam and SDL run against glibc. +#ifdef __GLIBC__ +typedef unsigned long ioctl_request_t; +#else +typedef int ioctl_request_t; +#endif + static constexpr uint16_t GAMEPAD_VENDOR_ID_BASE = 0x1234; static constexpr uint16_t GAMEPAD_PRODUCT_ID_BASE = 0x5678; static constexpr uint16_t GAMEPAD_VERSION = 0x0110; static constexpr const char *GAMEPAD_NAME_TEMPLATE = "Generic HID Gamepad %d"; +// The gamepad Steam Input presents to a game, and the name SDL reads its slot from. +static constexpr uint16_t STEAM_VIRTUAL_VENDOR_ID = 0x28de; +static constexpr uint16_t STEAM_VIRTUAL_PRODUCT_ID = 0x11ff; +static constexpr const char *STEAM_VIRTUAL_NAME_TEMPLATE = "Microsoft X-Box 360 pad %d"; static constexpr const char *GAMEPAD_PHYS_TEMPLATE = "usb-fakeinput/input%d"; static constexpr const char *GAMEPAD_UNIQ_TEMPLATE = "0000000000%02d"; static constexpr uint8_t GAMEPAD_AXIS_COUNT = 8; @@ -130,9 +143,11 @@ static constexpr size_t kNeutralEventCount = static const uint16_t kSnapshotAxisCodes[8] = { ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y}; // Bit i of FakeInputRingHeader::snapshot_buttons maps to this button code. -static const uint16_t kSnapshotButtons[10] = { - BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, - BTN_TR, BTN_SELECT, BTN_START, BTN_THUMBL, BTN_THUMBR}; +static const uint16_t kSnapshotButtons[] = { + BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, BTN_TR, + BTN_SELECT, BTN_START, BTN_THUMBL, BTN_THUMBR, BTN_MODE}; +static const int kSnapshotButtonCount = + sizeof(kSnapshotButtons) / sizeof(kSnapshotButtons[0]); static std::unordered_map<int, std::shared_ptr<FakeController>> controller_map; static std::unordered_map<int, std::string> ring_paths; @@ -145,6 +160,29 @@ static bool vibration_enabled = true; static std::unordered_map<int, struct ff_effect> ff_effects; static int next_ff_id = 0; +// fork() carries over only the calling thread, so a lock another thread was holding at that +// instant stays held in the child by a thread that is not there to release it. Every hook below +// takes controller_mutex, and the Steam client forks while its other threads are inside them, so +// the child blocked on its first open() and never reached exec: the spawn never completed, the +// client's main loop stalled past its own watchdog and it tore the session down. +// +// Taking the lock before the fork is what makes the copy consistent - no thread is part way +// through the tables it guards. The parent then unlocks it. The child cannot: a recursive mutex +// records the owning thread, the child's one thread has a new id, and unlocking one it does not +// own is refused, which would leave the lock held for good. It gets a fresh mutex instead, which +// is sound precisely because the fork was taken with the lock held. +static void controller_fork_prepare() { controller_mutex.lock(); } +static void controller_fork_parent() { controller_mutex.unlock(); } + +static void controller_fork_child() { + pthread_mutexattr_t attr; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(controller_mutex.native_handle(), &attr); + pthread_mutexattr_destroy(&attr); +} + namespace Logger { int log_enabled; @@ -174,6 +212,9 @@ __attribute__((constructor)) static void library_init() { vibration_enabled = getenv("FAKE_EVDEV_VIBRATION") && atoi(getenv("FAKE_EVDEV_VIBRATION")); + pthread_atfork(controller_fork_prepare, controller_fork_parent, + controller_fork_child); + Logger::init(); } @@ -409,10 +450,10 @@ capture_keyframe(FakeController &fake, const SnapshotState &snap) { __attribute__((visibility("hidden"))) static int32_t keyframe_value(const FakeController &fake, uint16_t type, uint16_t code) { if (type == EV_KEY) { - for (int i = 0; i < 10; i++) + for (int i = 0; i < kSnapshotButtonCount; i++) if (kSnapshotButtons[i] == code) return (fake.keyframe_buttons >> i) & 1u; - return 0; // e.g. BTN_MODE, which the writer never presses + return 0; } if (type == EV_ABS) { for (int i = 0; i < 8; i++) @@ -427,6 +468,7 @@ open_fake_input_ring(const char *event, int flags) { int slot = get_event_number(event); std::string ring_path = get_ring_path_for_slot(slot); if (ring_path.empty()) { + Logger::log("No input ring is configured for %s (slot %d)\n", event, slot); errno = ENODEV; return -1; } @@ -449,6 +491,7 @@ open_fake_input_ring(const char *event, int flags) { FakeInputRingHeader *ring = reinterpret_cast<FakeInputRingHeader *>(mapping); if (!ring_header_is_valid(ring)) { + Logger::log("Input ring %s has no valid header\n", ring_path.c_str()); munmap(mapping, FAKE_INPUT_RING_SIZE); syscall(SYS_close, fd); errno = ENODEV; @@ -476,6 +519,47 @@ open_fake_input_ring(const char *event, int flags) { return fd; } +// Under the Steam client a pad it manages is hidden from the game: the client lists it in +// SDL_GAMECONTROLLER_IGNORE_DEVICES, its overlay refuses the game's open() of the node, and the +// game is meant to see Steam Input's virtual gamepad instead. That one is a uinput device, which +// does not exist here, so the game was left with no controller at all. With +// FAKE_EVDEV_STEAM_VIRTUAL set the pad carries the virtual gamepad's identity for every process +// but the client's own, which must keep seeing the pad as the physical one it reads. +__attribute__((visibility("hidden"))) static bool presents_steam_virtual() { + static int known; + if (known == 0) { + const char *enabled = getenv("FAKE_EVDEV_STEAM_VIRTUAL"); + char exe[PATH_MAX]; + ssize_t length = enabled && atoi(enabled) ? readlink("/proc/self/exe", exe, sizeof(exe) - 1) : -1; + bool client = false; + if (length > 0) { + exe[length] = '\0'; + const char *name = strrchr(exe, '/'); + client = !strncmp(name ? name + 1 : exe, "steam", 5); + } + known = length > 0 && !client ? 1 : -1; + } + return known == 1; +} + +// The virtual gamepad is an X-Box 360 pad, whose triggers are ABS_Z and ABS_RZ. Wine places a +// gamepad's axes by their position among the ones it advertises, so the triggers have to sit +// where that pad has them: as ABS_GAS and ABS_BRAKE they follow the sticks, the right stick +// lands on the left trigger, and a released right trigger reads as the right stick held up. +__attribute__((visibility("hidden"))) static uint16_t presented_abs_code(uint16_t code) { + if (!presents_steam_virtual()) return code; + if (code == ABS_BRAKE) return ABS_Z; + if (code == ABS_GAS) return ABS_RZ; + return code; +} + +__attribute__((visibility("hidden"))) static uint16_t ring_abs_code(uint16_t code) { + if (!presents_steam_virtual()) return code; + if (code == ABS_Z) return ABS_BRAKE; + if (code == ABS_RZ) return ABS_GAS; + return code; +} + __attribute__((visibility("hidden"))) static void copy_slot_ioctl_string(int op, void *argp, const char *format, int event_number) { size_t size = _IOC_SIZE(op); @@ -866,7 +950,7 @@ static bool wait_snapshot(std::shared_ptr<FakeController> fake, } } -EXPORT int ioctl(int fd, int op, ...) { +EXPORT int ioctl(int fd, ioctl_request_t op, ...) { va_list va; void *argp; @@ -899,14 +983,21 @@ EXPORT int ioctl(int fd, int op, ...) { struct input_id id; memset(&id, 0, sizeof(id)); id.bustype = 0x03; - id.vendor = static_cast<uint16_t>(GAMEPAD_VENDOR_ID_BASE + event_number); - id.product = static_cast<uint16_t>(GAMEPAD_PRODUCT_ID_BASE + event_number); + if (presents_steam_virtual()) { + id.vendor = STEAM_VIRTUAL_VENDOR_ID; + id.product = STEAM_VIRTUAL_PRODUCT_ID; + } else { + id.vendor = static_cast<uint16_t>(GAMEPAD_VENDOR_ID_BASE + event_number); + id.product = static_cast<uint16_t>(GAMEPAD_PRODUCT_ID_BASE + event_number); + } id.version = GAMEPAD_VERSION; memcpy(argp, (void *)&id, sizeof(id)); return 0; } else if (type == 0x45 && number == 0x6) { Logger::log("Hooking ioctl EVIOCGNAME for event %s\n", event); - copy_slot_ioctl_string(op, argp, GAMEPAD_NAME_TEMPLATE, event_number); + copy_slot_ioctl_string(op, argp, + presents_steam_virtual() ? STEAM_VIRTUAL_NAME_TEMPLATE : GAMEPAD_NAME_TEMPLATE, + event_number); return 0; } else if (type == 0x45 && number == 0x7) { Logger::log("Hooking ioctl EVIOCGPHYS for event %s\n", event); @@ -923,7 +1014,7 @@ EXPORT int ioctl(int fd, int op, ...) { SnapshotState snap; if (!wait_snapshot(controller->second, guard, snap)) return -1; unsigned char bitmask[(KEY_MAX + 8) / 8] = {}; - for (int i = 0; i < 10; ++i) { + for (int i = 0; i < kSnapshotButtonCount; ++i) { if (snap.buttons & (1u << i)) bitmask[kSnapshotButtons[i] / 8] |= 1u << (kSnapshotButtons[i] % 8); } @@ -955,8 +1046,10 @@ EXPORT int ioctl(int fd, int op, ...) { bitmask[ABS_Y / 8] |= (1 << (ABS_Y % 8)); bitmask[ABS_RX / 8] |= (1 << (ABS_RX % 8)); bitmask[ABS_RY / 8] |= (1 << (ABS_RY % 8)); - bitmask[ABS_GAS / 8] |= (1 << (ABS_GAS % 8)); - bitmask[ABS_BRAKE / 8] |= (1 << (ABS_BRAKE % 8)); + for (uint16_t trigger : {ABS_GAS, ABS_BRAKE}) { + uint16_t code = presented_abs_code(trigger); + bitmask[code / 8] |= (1 << (code % 8)); + } bitmask[ABS_HAT0X / 8] |= (1 << (ABS_HAT0X % 8)); bitmask[ABS_HAT0Y / 8] |= (1 << (ABS_HAT0Y % 8)); return copy_ioctl_bits(op, argp, bitmask); @@ -994,15 +1087,16 @@ EXPORT int ioctl(int fd, int op, ...) { Logger::log("Hooking ioctl EVIOCGABS(ABS) for event %s\n", event); struct input_absinfo abs_info; memset(&abs_info, 0, sizeof(abs_info)); - if (number >= 0x40 && number <= 0x44) { - abs_info.value = 0; - abs_info.minimum = -32768; - abs_info.maximum = 32767; - } else if (number >= 0x49 && number <= 0x4A) { + uint16_t code = ring_abs_code(number - 0x40); + if (code == ABS_GAS || code == ABS_BRAKE) { abs_info.value = 0; abs_info.minimum = 0; abs_info.maximum = 255; - } else if (number >= 0x50 && number <= 0x51) { + } else if (code <= ABS_RY) { + abs_info.value = 0; + abs_info.minimum = -32768; + abs_info.maximum = 32767; + } else if (code == ABS_HAT0X || code == ABS_HAT0Y) { abs_info.value = 0; abs_info.minimum = -1; abs_info.maximum = 1; @@ -1010,7 +1104,7 @@ EXPORT int ioctl(int fd, int op, ...) { SnapshotState snap; if (!wait_snapshot(controller->second, guard, snap)) return -1; for (int i = 0; i < 8; ++i) - if (kSnapshotAxisCodes[i] == number - 0x40) abs_info.value = snap.axes[i]; + if (kSnapshotAxisCodes[i] == code) abs_info.value = snap.axes[i]; memcpy(argp, &abs_info, std::min<size_t>(_IOC_SIZE(op), sizeof(abs_info))); return 0; } else if (type == 0x45 && number == 0x90) { @@ -1106,8 +1200,11 @@ EXPORT ssize_t read(int fd, void *buf, size_t count) { FAKE_INPUT_RING_HEADER_SIZE; for (size_t i = 0; i < events; ++i) { size_t index = (fake.read_seq + i) % FAKE_INPUT_RING_CAPACITY; - memcpy(static_cast<uint8_t *>(buf) + i * FAKE_INPUT_EVENT_SIZE, - ring_events + index * FAKE_INPUT_EVENT_SIZE, FAKE_INPUT_EVENT_SIZE); + struct input_event ev; + memcpy(&ev, ring_events + index * FAKE_INPUT_EVENT_SIZE, FAKE_INPUT_EVENT_SIZE); + if (ev.type == EV_ABS) ev.code = presented_abs_code(ev.code); + memcpy(static_cast<uint8_t *>(buf) + i * FAKE_INPUT_EVENT_SIZE, &ev, + FAKE_INPUT_EVENT_SIZE); } // A producer can lap the reader while it copies. Discard that copy // instead of delivering torn/overwritten events or losing a release. @@ -1131,6 +1228,7 @@ EXPORT ssize_t read(int fd, void *buf, size_t count) { ev.type = kNeutralEvents[index].type; ev.code = kNeutralEvents[index].code; ev.value = keyframe_value(fake, ev.type, ev.code); + if (ev.type == EV_ABS) ev.code = presented_abs_code(ev.code); memcpy(static_cast<uint8_t *>(buf) + i * FAKE_INPUT_EVENT_SIZE, &ev, FAKE_INPUT_EVENT_SIZE); --fake.keyframe_remaining; @@ -1471,3 +1569,55 @@ EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds, backoff_ms *= 2; } } + +#ifdef __GLIBC__ +// Steam's binaries were linked against an older glibc and reach the calls above through these +// names instead. +static mode_t open_mode(int flags, va_list va) { + return (flags & (O_CREAT | O_TMPFILE)) ? va_arg(va, mode_t) : 0; +} + +EXPORT int open64(const char *pathname, int flags, ...) { + va_list va; + va_start(va, flags); + mode_t mode = open_mode(flags, va); + va_end(va); + return open(pathname, flags, mode); +} + +EXPORT int openat64(int dirfd, const char *pathname, int flags, ...) { + va_list va; + va_start(va, flags); + mode_t mode = open_mode(flags, va); + va_end(va); + return openat(dirfd, pathname, flags, mode); +} + +EXPORT int stat64(const char *pathname, struct stat64 *statbuf) { + return stat(pathname, reinterpret_cast<struct stat *>(statbuf)); +} + +EXPORT int fstat64(int fd, struct stat64 *buf) { + return fstat(fd, reinterpret_cast<struct stat *>(buf)); +} + +EXPORT int __xstat(int version, const char *pathname, struct stat *statbuf) { + (void)version; + return stat(pathname, statbuf); +} + +EXPORT int __xstat64(int version, const char *pathname, struct stat64 *statbuf) { + (void)version; + return stat(pathname, reinterpret_cast<struct stat *>(statbuf)); +} + +EXPORT int __fxstat(int version, int fd, struct stat *buf) { + (void)version; + return fstat(fd, buf); +} + +EXPORT int __fxstat64(int version, int fd, struct stat64 *buf) { + (void)version; + return fstat(fd, reinterpret_cast<struct stat *>(buf)); +} +#endif diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.c b/app/src/main/cpp/winlator/vk/vk_dispatch.c index 984c91e29..77335b211 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.c +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.c @@ -192,6 +192,22 @@ bool vkd_bind(void* libvulkan_handle, VkInstance instance) { return vkd_load_instance(instance); } +bool vkd_bind_proc(PFN_vkGetInstanceProcAddr gipa, VkInstance instance) { + if (!gipa || instance == VK_NULL_HANDLE) return false; + if (vkd.GetInstanceProcAddr == gipa && vkd_owner_instance == instance) return true; + + memset(&vkd, 0, sizeof(vkd)); + vkd_owner_handle = NULL; + vkd_owner_instance = VK_NULL_HANDLE; + vkd.GetInstanceProcAddr = gipa; + vkd.CreateInstance = (PFN_vkCreateInstance)gipa(VK_NULL_HANDLE, "vkCreateInstance"); + vkd.EnumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties) + gipa(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + vkd.EnumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties) + gipa(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"); + return vkd_load_instance(instance); +} + void vkd_unload(void) { memset(&vkd, 0, sizeof(vkd)); vkd_owner_handle = NULL; diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.h b/app/src/main/cpp/winlator/vk/vk_dispatch.h index c3a4284d3..4e7072c2f 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.h +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.h @@ -162,6 +162,10 @@ void vkd_unload(void); bool vkd_bind(void* libvulkan_handle, VkInstance instance); +// For callers that already hold the driver's vkGetInstanceProcAddr rather than a dlopen handle +// (the Wayland compositor resolves Turnip through adrenotools). +bool vkd_bind_proc(PFN_vkGetInstanceProcAddr gipa, VkInstance instance); + #ifdef __cplusplus } #endif diff --git a/app/src/main/cpp/wnaudiohook/wn_aaudio_host.c b/app/src/main/cpp/wnaudiohook/wn_aaudio_host.c new file mode 100644 index 000000000..6d424e021 --- /dev/null +++ b/app/src/main/cpp/wnaudiohook/wn_aaudio_host.c @@ -0,0 +1,381 @@ +/* + * The app's end of DirectAudio in a Linux session: opens the AAudio streams a session's + * DirectAudio driver asks for and moves their PCM through shared memory. See + * wn_aaudio_protocol.h. Nothing the client writes is trusted with the host's memory: the ring's + * geometry is kept here, and the positions read from the ring are clamped to it. + */ +#include <aaudio/AAudio.h> +#include <android/log.h> +#include <android/sharedmem.h> +#include <dlfcn.h> +#include <errno.h> +#include <jni.h> +#include <limits.h> +#include <linux/futex.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/un.h> +#include <unistd.h> + +#include "wn_aaudio_protocol.h" + +#define TAG "DirectAudioHost" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) + +#define MAX_CONNECTIONS 32 +#define MIN_RING_FRAMES 4096u + +typedef void (*builder_int_setter)(AAudioStreamBuilder *, int32_t); + +struct stream { + AAudioStream *aaudio; + /* Set once the stream is open; the error callback may run before that. */ + _Atomic(struct wn_audio_ring *) ring; + _Atomic int32_t early_error; + size_t map_size; + int ring_fd; + uint32_t frames; + uint32_t frame_bytes; + bool input; +}; + +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_idle = PTHREAD_COND_INITIALIZER; +static pthread_t g_accept_thread; +static int g_listen_fd = -1; +static int g_connections[MAX_CONNECTIONS]; +static int g_connection_count; +static bool g_running; +static bool g_allow_capture; + +static void wake_client(struct wn_audio_ring *ring) { + atomic_fetch_add(&ring->wake, 1); + syscall(SYS_futex, &ring->wake, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); +} + +static void copy_out(const struct stream *s, uint8_t *dest, uint32_t pos, uint32_t count) { + const uint8_t *data = WN_AUDIO_RING_DATA(atomic_load(&s->ring)); + uint32_t at = pos & (s->frames - 1); + uint32_t first = count < s->frames - at ? count : s->frames - at; + memcpy(dest, data + (size_t)at * s->frame_bytes, (size_t)first * s->frame_bytes); + memcpy(dest + (size_t)first * s->frame_bytes, data, (size_t)(count - first) * s->frame_bytes); +} + +static void copy_in(const struct stream *s, const uint8_t *src, uint32_t pos, uint32_t count) { + uint8_t *data = WN_AUDIO_RING_DATA(atomic_load(&s->ring)); + uint32_t at = pos & (s->frames - 1); + uint32_t first = count < s->frames - at ? count : s->frames - at; + memcpy(data + (size_t)at * s->frame_bytes, src, (size_t)first * s->frame_bytes); + memcpy(data, src + (size_t)first * s->frame_bytes, (size_t)(count - first) * s->frame_bytes); +} + +static aaudio_data_callback_result_t on_data(AAudioStream *aaudio, void *user, void *audio, int32_t num_frames) { + struct stream *s = user; + struct wn_audio_ring *ring = atomic_load(&s->ring); + uint32_t wanted = (uint32_t)num_frames; + uint32_t write_pos = atomic_load(&ring->write_pos); + uint32_t read_pos = atomic_load(&ring->read_pos); + uint32_t held = write_pos - read_pos; + (void)aaudio; + if (held > s->frames) held = s->input ? s->frames : 0; + + if (s->input) { + uint32_t room = s->frames - held; + uint32_t count = wanted < room ? wanted : room; + copy_in(s, audio, write_pos, count); + atomic_store(&ring->write_pos, write_pos + count); + if (count < wanted) atomic_fetch_add(&ring->xruns, 1); + } else { + uint32_t count = wanted < held ? wanted : held; + copy_out(s, audio, read_pos, count); + memset((uint8_t *)audio + (size_t)count * s->frame_bytes, 0, (size_t)(wanted - count) * s->frame_bytes); + atomic_store(&ring->read_pos, read_pos + count); + if (count < wanted) atomic_fetch_add(&ring->xruns, 1); + } + if (wanted > atomic_load(&ring->callback_frames)) atomic_store(&ring->callback_frames, wanted); + wake_client(ring); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +static void on_error(AAudioStream *aaudio, void *user, aaudio_result_t error) { + struct stream *s = user; + (void)aaudio; + LOGW("stream error %d (%s)", error, AAudio_convertResultToText(error)); + atomic_store(&s->early_error, error); + struct wn_audio_ring *ring = atomic_load(&s->ring); + if (!ring) return; + atomic_store(&ring->error, error); + wake_client(ring); +} + +static void close_stream(struct stream *s) { + if (s->aaudio) { + AAudioStream_requestStop(s->aaudio); + AAudioStream_close(s->aaudio); + s->aaudio = NULL; + } + struct wn_audio_ring *ring = atomic_exchange(&s->ring, NULL); + if (ring) munmap(ring, s->map_size); + if (s->ring_fd >= 0) { + close(s->ring_fd); + s->ring_fd = -1; + } +} + +static uint32_t frame_bytes(aaudio_format_t format, int32_t channels) { + uint32_t sample = format == AAUDIO_FORMAT_PCM_I16 ? 2 : format == AAUDIO_FORMAT_PCM_I24_PACKED ? 3 : 4; + return sample * (uint32_t)channels; +} + +static aaudio_result_t open_stream(struct stream *s, const struct wn_audio_request *request, struct wn_audio_reply *reply) { + AAudioStreamBuilder *builder = NULL; + aaudio_result_t result; + + if (s->aaudio) return AAUDIO_ERROR_INVALID_STATE; + s->input = request->direction == AAUDIO_DIRECTION_INPUT; + if (s->input && !g_allow_capture) return AAUDIO_ERROR_UNAVAILABLE; + + result = AAudio_createStreamBuilder(&builder); + if (result != AAUDIO_OK) return result; + AAudioStreamBuilder_setDirection(builder, request->direction); + if (request->sample_rate > 0) AAudioStreamBuilder_setSampleRate(builder, request->sample_rate); + if (request->channels > 0) AAudioStreamBuilder_setChannelCount(builder, request->channels); + if (request->format > 0) AAudioStreamBuilder_setFormat(builder, request->format); + if (request->performance_mode > 0) AAudioStreamBuilder_setPerformanceMode(builder, request->performance_mode); + AAudioStreamBuilder_setSharingMode(builder, request->sharing_mode); + if (request->buffer_capacity > 0) AAudioStreamBuilder_setBufferCapacityInFrames(builder, request->buffer_capacity); + /* Both arrived with Android 9, above what the app still installs on. */ + builder_int_setter set_usage = dlsym(RTLD_DEFAULT, "AAudioStreamBuilder_setUsage"); + builder_int_setter set_input_preset = dlsym(RTLD_DEFAULT, "AAudioStreamBuilder_setInputPreset"); + if (set_usage && request->usage > 0) set_usage(builder, request->usage); + if (set_input_preset && s->input && request->input_preset > 0) set_input_preset(builder, request->input_preset); + AAudioStreamBuilder_setDataCallback(builder, on_data, s); + AAudioStreamBuilder_setErrorCallback(builder, on_error, s); + result = AAudioStreamBuilder_openStream(builder, &s->aaudio); + AAudioStreamBuilder_delete(builder); + if (result != AAUDIO_OK) { + s->aaudio = NULL; + return result; + } + + reply->sample_rate = AAudioStream_getSampleRate(s->aaudio); + reply->channels = AAudioStream_getChannelCount(s->aaudio); + reply->format = AAudioStream_getFormat(s->aaudio); + reply->performance_mode = AAudioStream_getPerformanceMode(s->aaudio); + reply->sharing_mode = AAudioStream_getSharingMode(s->aaudio); + reply->frames_per_burst = AAudioStream_getFramesPerBurst(s->aaudio); + reply->buffer_capacity = AAudioStream_getBufferCapacityInFrames(s->aaudio); + reply->buffer_size = AAudioStream_getBufferSizeInFrames(s->aaudio); + if (reply->channels <= 0 || reply->channels > 16 || reply->frames_per_burst <= 0) { + close_stream(s); + return AAUDIO_ERROR_INTERNAL; + } + + uint32_t frames = MIN_RING_FRAMES; + while (frames < (uint32_t)reply->frames_per_burst * 8 && frames < (1u << 20)) frames <<= 1; + s->frames = frames; + s->frame_bytes = frame_bytes(reply->format, reply->channels); + s->map_size = sizeof(struct wn_audio_ring) + (size_t)frames * s->frame_bytes; + s->ring_fd = ASharedMemory_create("wn-directaudio", s->map_size); + if (s->ring_fd < 0) { + close_stream(s); + return AAUDIO_ERROR_NO_MEMORY; + } + void *map = mmap(NULL, s->map_size, PROT_READ | PROT_WRITE, MAP_SHARED, s->ring_fd, 0); + if (map == MAP_FAILED) { + close_stream(s); + return AAUDIO_ERROR_NO_MEMORY; + } + struct wn_audio_ring *ring = map; + memset(ring, 0, s->map_size); + ring->frames = frames; + ring->frame_bytes = s->frame_bytes; + atomic_store(&s->ring, ring); + atomic_store(&ring->error, atomic_load(&s->early_error)); + LOGI("%s open: %d Hz, %d ch, format %d, burst %d, mode %d", s->input ? "capture" : "render", + reply->sample_rate, reply->channels, reply->format, reply->frames_per_burst, reply->performance_mode); + return AAUDIO_OK; +} + +static bool send_reply(int fd, const struct wn_audio_reply *reply, int pass_fd) { + struct iovec iov = { .iov_base = (void *)reply, .iov_len = sizeof(*reply) }; + union { + char buffer[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } control; + struct msghdr message = { .msg_iov = &iov, .msg_iovlen = 1 }; + if (pass_fd >= 0) { + memset(&control, 0, sizeof(control)); + message.msg_control = control.buffer; + message.msg_controllen = sizeof(control.buffer); + struct cmsghdr *header = CMSG_FIRSTHDR(&message); + header->cmsg_level = SOL_SOCKET; + header->cmsg_type = SCM_RIGHTS; + header->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(header), &pass_fd, sizeof(int)); + } + ssize_t sent; + do { + sent = sendmsg(fd, &message, MSG_NOSIGNAL); + } while (sent < 0 && errno == EINTR); + return sent == (ssize_t)sizeof(*reply); +} + +static bool read_request(int fd, struct wn_audio_request *request) { + size_t got = 0; + while (got < sizeof(*request)) { + ssize_t n = recv(fd, (char *)request + got, sizeof(*request) - got, 0); + if (n == 0 || (n < 0 && errno != EINTR)) return false; + if (n > 0) got += (size_t)n; + } + return request->magic == WN_AUDIO_MAGIC; +} + +static void forget_connection(int fd) { + pthread_mutex_lock(&g_lock); + for (int i = 0; i < g_connection_count; i++) { + if (g_connections[i] != fd) continue; + g_connections[i] = g_connections[--g_connection_count]; + break; + } + /* Closed under the lock, so nativeStop never shuts down a descriptor number that has + * already been handed to something else. */ + close(fd); + pthread_cond_broadcast(&g_idle); + pthread_mutex_unlock(&g_lock); +} + +static void *serve(void *arg) { + int fd = (int)(intptr_t)arg; + struct stream s = { .ring_fd = -1 }; + struct wn_audio_request request; + + while (read_request(fd, &request)) { + struct wn_audio_reply reply = { .result = AAUDIO_ERROR_INVALID_STATE }; + int pass_fd = -1; + bool closing = false; + switch (request.op) { + case WN_AUDIO_OPEN: + reply.result = open_stream(&s, &request, &reply); + if (reply.result == AAUDIO_OK) pass_fd = s.ring_fd; + break; + case WN_AUDIO_START: + if (s.aaudio) reply.result = AAudioStream_requestStart(s.aaudio); + break; + case WN_AUDIO_STOP: + if (s.aaudio) reply.result = AAudioStream_requestStop(s.aaudio); + break; + case WN_AUDIO_SET_BUFFER_SIZE: + if (s.aaudio) { + aaudio_result_t size = AAudioStream_setBufferSizeInFrames(s.aaudio, request.value); + reply.result = size < 0 ? size : AAUDIO_OK; + reply.buffer_size = size < 0 ? AAudioStream_getBufferSizeInFrames(s.aaudio) : size; + } + break; + case WN_AUDIO_CLOSE: + close_stream(&s); + reply.result = AAUDIO_OK; + closing = true; + break; + default: + reply.result = AAUDIO_ERROR_ILLEGAL_ARGUMENT; + break; + } + if (!send_reply(fd, &reply, pass_fd) || closing) break; + } + close_stream(&s); + forget_connection(fd); + return NULL; +} + +static void *accept_loop(void *arg) { + int listen_fd = (int)(intptr_t)arg; + for (;;) { + int fd = accept4(listen_fd, NULL, NULL, SOCK_CLOEXEC); + if (fd < 0) { + if (errno == EINTR) continue; + break; + } + pthread_mutex_lock(&g_lock); + bool taken = g_running && g_connection_count < MAX_CONNECTIONS; + if (taken) g_connections[g_connection_count++] = fd; + pthread_mutex_unlock(&g_lock); + if (!taken) { + close(fd); + continue; + } + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + int failed = pthread_create(&thread, &attr, serve, (void *)(intptr_t)fd); + pthread_attr_destroy(&attr); + if (failed) forget_connection(fd); + } + return NULL; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_runtime_audio_directaudio_DirectAudioHost_nativeStart( + JNIEnv *env, jclass clazz, jstring socket_path, jboolean allow_capture) { + struct sockaddr_un address = { .sun_family = AF_UNIX }; + (void)clazz; + const char *path = (*env)->GetStringUTFChars(env, socket_path, NULL); + if (!path) return JNI_FALSE; + bool fits = strlen(path) < sizeof(address.sun_path); + if (fits) strcpy(address.sun_path, path); + (*env)->ReleaseStringUTFChars(env, socket_path, path); + if (!fits) return JNI_FALSE; + + pthread_mutex_lock(&g_lock); + if (g_running) { + pthread_mutex_unlock(&g_lock); + return JNI_TRUE; + } + int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + unlink(address.sun_path); + if (fd < 0 || bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0 || listen(fd, 8) < 0 || + pthread_create(&g_accept_thread, NULL, accept_loop, (void *)(intptr_t)fd) != 0) { + LOGW("could not listen on %s: %s", address.sun_path, strerror(errno)); + if (fd >= 0) close(fd); + pthread_mutex_unlock(&g_lock); + return JNI_FALSE; + } + g_listen_fd = fd; + g_allow_capture = allow_capture; + g_running = true; + pthread_mutex_unlock(&g_lock); + LOGI("listening on %s (capture %s)", address.sun_path, allow_capture ? "allowed" : "off"); + return JNI_TRUE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_audio_directaudio_DirectAudioHost_nativeStop(JNIEnv *env, jclass clazz) { + (void)env; + (void)clazz; + pthread_mutex_lock(&g_lock); + if (!g_running) { + pthread_mutex_unlock(&g_lock); + return; + } + g_running = false; + int listen_fd = g_listen_fd; + g_listen_fd = -1; + shutdown(listen_fd, SHUT_RDWR); + pthread_mutex_unlock(&g_lock); + pthread_join(g_accept_thread, NULL); + close(listen_fd); + + /* Each connection's thread closes its own stream and descriptor once its read fails. */ + pthread_mutex_lock(&g_lock); + for (int i = 0; i < g_connection_count; i++) shutdown(g_connections[i], SHUT_RDWR); + while (g_connection_count > 0) pthread_cond_wait(&g_idle, &g_lock); + pthread_mutex_unlock(&g_lock); + LOGI("stopped"); +} diff --git a/app/src/main/cpp/wnaudiohook/wn_aaudio_protocol.h b/app/src/main/cpp/wnaudiohook/wn_aaudio_protocol.h new file mode 100644 index 000000000..db04dfd14 --- /dev/null +++ b/app/src/main/cpp/wnaudiohook/wn_aaudio_protocol.h @@ -0,0 +1,76 @@ +/* + * What a Linux session's AAudio client (tools/linuxfs/directaudio/wn_aaudio_client.c) and the + * app's AAudio host (wn_aaudio_host.c) say to each other. A glibc process cannot load Android's + * libaaudio, so the DirectAudio driver built for Valve's Proton calls a client that has the app + * open the stream for it. One unix socket connection is one stream: requests and replies go over + * it, and the PCM goes through a ring in shared memory whose descriptor comes with the reply to + * WN_AUDIO_OPEN, so no sample crosses the socket. + */ +#ifndef WN_AAUDIO_PROTOCOL_H +#define WN_AAUDIO_PROTOCOL_H + +#include <stdatomic.h> +#include <stdint.h> + +#define WN_AUDIO_MAGIC 0x31414e57u /* "WNA1" */ + +enum { + WN_AUDIO_OPEN = 1, + WN_AUDIO_START, + WN_AUDIO_STOP, + WN_AUDIO_SET_BUFFER_SIZE, /* value = frames; the reply's buffer_size is what was granted */ + WN_AUDIO_CLOSE, +}; + +/* The stream fields are AAudio's own values and only read for WN_AUDIO_OPEN; 0 leaves a field + * at AAudio's default, as an unset builder field does. */ +struct wn_audio_request { + uint32_t magic; + uint32_t op; + int32_t direction; + int32_t sample_rate; + int32_t channels; + int32_t format; + int32_t performance_mode; + int32_t sharing_mode; + int32_t usage; + int32_t input_preset; + int32_t buffer_capacity; + int32_t value; +}; + +/* result is an aaudio_result_t. The rest describes the stream AAudio granted and is only + * filled in for WN_AUDIO_OPEN and WN_AUDIO_SET_BUFFER_SIZE. */ +struct wn_audio_reply { + int32_t result; + int32_t sample_rate; + int32_t channels; + int32_t format; + int32_t performance_mode; + int32_t sharing_mode; + int32_t frames_per_burst; + int32_t buffer_capacity; + int32_t buffer_size; +}; + +/* + * The ring, at the start of the shared mapping. Positions count frames and run freely; the + * distance between them is what is held. An output stream is written by the client and read by + * the host's data callback, an input stream the other way round. The host bumps `wake` and wakes + * its futex after every callback, which is what paces the client's thread. + */ +struct wn_audio_ring { + _Atomic uint32_t write_pos; + _Atomic uint32_t read_pos; + _Atomic uint32_t wake; + _Atomic int32_t error; /* an aaudio_result_t once the stream has failed */ + _Atomic int32_t xruns; + _Atomic uint32_t callback_frames; /* the most frames one host callback has asked for */ + uint32_t frames; /* capacity, a power of two */ + uint32_t frame_bytes; + uint32_t reserved[8]; +}; + +#define WN_AUDIO_RING_DATA(ring) ((uint8_t *)(ring) + sizeof(struct wn_audio_ring)) + +#endif diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index 1357438f4..a9fc99497 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -1,4 +1,6 @@ package com.winlator.cmod.feature.library + +import android.content.Context import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween @@ -91,6 +93,9 @@ import androidx.compose.material3.SliderState import androidx.compose.material3.Switch import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.content.DriverPackages +import com.winlator.cmod.runtime.display.wayland.WineWaylandSupport import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.window.Dialog @@ -106,6 +111,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -383,6 +389,16 @@ data class WinComponentItem(val key: String, val label: String, val selectedInde data class EnvVarItem(val key: String, val value: String) // Row-preserving parse: duplicate names stay as separate rows (EnvVars would collapse them into a map). +const val DISPLAY_SERVER_X11_INDEX = 0 +const val DISPLAY_SERVER_WAYLAND_INDEX = 1 +fun displayServerEntries(context: android.content.Context): List<String> = listOf( + context.getString(R.string.display_server_x11), + context.getString(R.string.display_server_wayland), +) + +fun displayBackendFromIndex(index: Int): String = + if (index == DISPLAY_SERVER_WAYLAND_INDEX) Container.DISPLAY_BACKEND_WAYLAND else Container.DISPLAY_BACKEND_X11 + fun parseEnvVarItems(envVarsStr: String?): List<EnvVarItem> = envVarsStr.orEmpty().split(" ").mapNotNull { part -> val index = part.indexOf('=') @@ -424,6 +440,13 @@ class GameSettingsStateHolder { val fpsLimit = mutableIntStateOf(0) // Display + val displayServerEntries = mutableStateOf<List<String>>(emptyList()) + val selectedDisplayServer = mutableIntStateOf(0) + val displayServerWaylandAvailable = mutableStateOf(false) + /** The GameScope container always draws through the Wayland compositor. */ + val gamescopeContainer = mutableStateOf(false) + // Wine identifier the session will use when the wine dropdown is not shown (shortcut editor). + val wineVersionIdentifier = mutableStateOf("") val graphicsDriverEntries = mutableStateOf<List<String>>(emptyList()) val selectedGraphicsDriver = mutableIntStateOf(0) val isArm64EC = mutableStateOf(false) @@ -594,6 +617,8 @@ class GameSettingsStateHolder { val selectedFexcoreVersion = mutableIntStateOf(0) val fexcorePresetEntries = mutableStateOf<List<String>>(emptyList()) val selectedFexcorePreset = mutableIntStateOf(0) + val linuxProtonEntries = mutableStateOf<List<String>>(emptyList()) + val selectedLinuxProton = mutableIntStateOf(0) val useUnixLibs = mutableStateOf(true) // Advanced - System @@ -752,9 +777,24 @@ private const val SEC_DRIVES = 9 private const val SEC_SAVES = 10 private const val SEC_NETWORKING = 11 -private fun buildSections(isSteam: Boolean, isContainer: Boolean): List<Pair<Int, SidebarSection>> { +private fun buildSections( + isSteam: Boolean, + isContainer: Boolean, + gamescope: Boolean, +): List<Pair<Int, SidebarSection>> { val list = mutableListOf<Pair<Int, SidebarSection>>() list += SEC_GENERAL to SidebarSection(Icons.Outlined.Tune, R.string.settings_general_title) + // A GameScope session runs the Linux Steam client and Proton: there is no Wine prefix of ours + // to configure, so the Steam launcher, ReShade, Windows components, drives and prefix pages + // would change nothing. + if (gamescope) { + list += SEC_DISPLAY to SidebarSection(Icons.Outlined.Monitor, R.string.common_ui_graphics) + list += SEC_ADVANCED to SidebarSection(Icons.Outlined.Settings, R.string.common_ui_advanced) + list += SEC_VARIABLES to SidebarSection(Icons.Outlined.Code, R.string.container_config_variables) + list += SEC_INPUT to SidebarSection(Icons.Outlined.SportsEsports, R.string.common_ui_input_controls) + list += SEC_NETWORKING to SidebarSection(Icons.Outlined.Wifi, R.string.networking_section_title) + return list + } if (isSteam) list += SEC_STEAM to SidebarSection(Icons.Outlined.Science, R.string.steam_section_title) list += SEC_DISPLAY to SidebarSection(Icons.Outlined.Monitor, R.string.common_ui_graphics) list += SEC_ADVANCED to SidebarSection(Icons.Outlined.Settings, R.string.common_ui_advanced) @@ -781,9 +821,16 @@ fun GameSettingsContent( ) { val isSteam by state.isSteamGame val isContainer by state.isContainerEditMode - val sections = remember(isSteam, isContainer) { buildSections(isSteam, isContainer) } + val gamescope by state.gamescopeContainer + val sections = remember(isSteam, isContainer, gamescope) { buildSections(isSteam, isContainer, gamescope) } + // The pages change with the container; stay on the page that was open, by what it is. + var openSectionId by remember { mutableIntStateOf(SEC_GENERAL) } + LaunchedEffect(sections) { + state.currentSection.intValue = sections.indexOfFirst { it.first == openSectionId }.coerceAtLeast(0) + } val selectedIdx by state.currentSection val currentSectionId = sections.getOrNull(selectedIdx)?.first ?: SEC_GENERAL + SideEffect { openSectionId = currentSectionId } val saveEnabled by state.isLoaded if (nav != null) { @@ -1419,7 +1466,7 @@ private fun GeneralSection( onValueChange = { state.name.value = it } ) - if (!isContainer) { + if (!isContainer && !state.gamescopeContainer.value) { val launchExeDisplayText = state.launchExeDisplayPath.value.ifBlank { state.launchExePath.value } val hasLaunchExePath = launchExeDisplayText.isNotEmpty() Spacer(Modifier.height(SettingItemGap)) @@ -1463,7 +1510,7 @@ private fun GeneralSection( ) } - if (isContainer && state.wineVersionEntries.value.isNotEmpty()) { + if (isContainer && !state.gamescopeContainer.value && state.wineVersionEntries.value.isNotEmpty()) { Spacer(Modifier.height(SettingItemGap)) SettingDropdown( label = stringResource(R.string.container_wine_version), @@ -1702,12 +1749,14 @@ private fun GeneralSection( ) } Box(Modifier.weight(1f)) { - SettingDropdown( - label = stringResource(R.string.settings_audio_midi_sound_font), - entries = state.midiSoundFontEntries.value, - selectedIndex = state.selectedMidiSoundFont.intValue, - onSelected = { state.selectedMidiSoundFont.intValue = it } - ) + if (!state.gamescopeContainer.value) { + SettingDropdown( + label = stringResource(R.string.settings_audio_midi_sound_font), + entries = state.midiSoundFontEntries.value, + selectedIndex = state.selectedMidiSoundFont.intValue, + onSelected = { state.selectedMidiSoundFont.intValue = it } + ) + } } } @@ -1796,6 +1845,11 @@ private fun DisplaySection( callbacks: GameSettingsCallbacks ) { + if (state.gamescopeContainer.value) { + GamescopeDisplaySection(state, callbacks) + return + } + SettingGroup { SettingPairRow { Box(Modifier.weight(1f)) { @@ -1818,6 +1872,10 @@ private fun DisplaySection( Spacer(Modifier.height(SettingSectionGap)) + DisplayServerRow(state) + + Spacer(Modifier.height(SettingSectionGap)) + SettingPairRow { Box(Modifier.weight(1f)) { SettingDropdown( @@ -1869,6 +1927,133 @@ private fun DisplaySection( } +/** + * A GameScope container draws with the Turnip the Linux runtime carries, and its games bring their + * own DXVK and VKD3D inside Proton, so the Android drivers and the DX wrappers are not offered. + */ +@Composable +private fun GamescopeDisplaySection( + state: GameSettingsStateHolder, + callbacks: GameSettingsCallbacks +) { + SettingGroup { + SettingPairRow { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.container_graphics_driver), + entries = state.gfxDriverVersionEntries.value, + selectedIndex = state.gfxSelectedDriverVersion.intValue, + onSelected = { + state.gfxSelectedDriverVersion.intValue = it + state.graphicsDriverVersion.value = state.gfxDriverVersionEntries.value.getOrElse(it) { "" } + callbacks.onGfxDriverVersionChanged(it) + } + ) + } + Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) { + Text( + text = stringResource(R.string.container_graphics_driver_gamescope_help), + color = TextDim, + fontSize = SettingLabelSize, + modifier = Modifier.padding(top = SettingLabelRowHeight) + ) + } + } + + Spacer(Modifier.height(SettingSectionGap)) + + DisplayServerRow(state) + } + + Spacer(Modifier.height(SettingItemGap)) + + FrameGenerationCard(state) +} + +/** + * X11 or the embedded Wayland compositor. Wayland is selectable only when the device has an Adreno + * GPU and the selected Wine/Proton ships winewayland; otherwise the row is greyed out with the + * requirement text and the stored choice is left untouched. + */ +@Composable +private fun DisplayServerRow(state: GameSettingsStateHolder) { + val entries = state.displayServerEntries.value + if (entries.isEmpty()) return + val context = LocalContext.current + if (state.gamescopeContainer.value) { + SettingPairRow { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.container_display_server), + entries = entries, + selectedIndex = DISPLAY_SERVER_WAYLAND_INDEX, + onSelected = {}, + enabled = false + ) + } + Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) { + Text( + text = stringResource(R.string.container_display_server_gamescope), + color = TextDim, + fontSize = SettingLabelSize, + modifier = Modifier.padding(top = SettingLabelRowHeight) + ) + } + } + return + } + val wineIdentifier = displayServerWineIdentifier(state) + val initial = DisplayServerStatus( + if (state.displayServerWaylandAvailable.value) WAYLAND_AVAILABLE else WAYLAND_NEEDS_PROTON, "" + ) + val status by produceState(initialValue = initial, key1 = wineIdentifier) { + val result = withContext(Dispatchers.IO) { displayServerStatus(context, wineIdentifier) } + state.displayServerWaylandAvailable.value = result.reason == WAYLAND_AVAILABLE + value = result + } + val enabled = status.reason == WAYLAND_AVAILABLE + SettingPairRow { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.container_display_server), + entries = entries, + selectedIndex = state.selectedDisplayServer.intValue, + onSelected = { state.selectedDisplayServer.intValue = it }, + enabled = enabled + ) + } + Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) { + Text( + text = when (status.reason) { + WAYLAND_AVAILABLE -> stringResource(R.string.container_display_server_help) + WAYLAND_NEEDS_ADRENO -> stringResource(R.string.container_display_server_wayland_requirements) + else -> stringResource(R.string.container_display_server_needs_wayland_proton) + }, + color = TextDim, + fontSize = SettingLabelSize, + modifier = Modifier.padding(top = SettingLabelRowHeight) + ) + } + } +} + +private const val WAYLAND_AVAILABLE = 0 +private const val WAYLAND_NEEDS_ADRENO = 1 +private const val WAYLAND_NEEDS_PROTON = 2 + +private data class DisplayServerStatus(val reason: Int, val donor: String) + +private fun displayServerWineIdentifier(state: GameSettingsStateHolder): String = + if (state.wineVersionEditable.value && state.wineVersionEntries.value.isNotEmpty()) + state.wineVersionEntries.value.getOrElse(state.selectedWineVersion.intValue) { "" } + else state.wineVersionIdentifier.value + +private fun displayServerStatus(context: Context, wineIdentifier: String): DisplayServerStatus { + if (!WineWaylandSupport.isAdrenoDevice(context)) return DisplayServerStatus(WAYLAND_NEEDS_ADRENO, "") + if (WineWaylandSupport.isWaylandCapable(context, wineIdentifier)) return DisplayServerStatus(WAYLAND_AVAILABLE, "") + return DisplayServerStatus(WAYLAND_NEEDS_PROTON, "") +} + @Composable private fun FrameGenerationCard(state: GameSettingsStateHolder) { val context = LocalContext.current @@ -4111,7 +4296,8 @@ private fun VariablesSection( val hasDraftEnvVar = state.envVars.value.any { it.key.isBlank() } // Ensure the required toggles exist upon entering the variables section - LaunchedEffect(Unit) { + LaunchedEffect(state.gamescopeContainer.value) { + if (state.gamescopeContainer.value) return@LaunchedEffect val current = state.envVars.value.toMutableList() var changed = false if (current.none { it.key == "WINEESYNC" }) { @@ -4171,7 +4357,8 @@ private fun VariablesSection( list[index] = EnvVarItem(envVar.key, v) state.envVars.value = list }, - onRemove = { callbacks.onRemoveEnvVar(index) } + onRemove = { callbacks.onRemoveEnvVar(index) }, + gamescope = state.gamescopeContainer.value ) } } @@ -4433,6 +4620,7 @@ private fun EnvVarRow( onNameChange: (String) -> Unit, onValueChange: (String) -> Unit, onRemove: (() -> Unit)?, + gamescope: Boolean = false, trailing: (@Composable () -> Unit)? = null ) { var nameMenuExpanded by remember { mutableStateOf(false) } @@ -4545,6 +4733,7 @@ private fun EnvVarRow( EnvVarsView.knownEnvVars .map { it[0] } + .filter { !gamescope || EnvVarsView.appliesToGamescope(it) } .sortedBy { it.uppercase() } .forEach { knownName -> DropdownMenuItem( @@ -4955,6 +5144,7 @@ private fun EnvValueTextField( @Composable private fun InputSection(state: GameSettingsStateHolder) { val isContainer = state.isContainerEditMode.value + val gamescope = state.gamescopeContainer.value SubsectionLabel(stringResource(R.string.common_ui_input_controls)) Spacer(Modifier.height(8.dp)) @@ -4982,37 +5172,38 @@ private fun InputSection(state: GameSettingsStateHolder) { Spacer(Modifier.height(SettingItemGap)) } - val exclusiveChecked = if (isContainer) state.containerExclusiveInput.value - else state.shortcutExclusiveXInput.value - SettingPairRow { - Box(Modifier.weight(1f)) { - SettingCheckbox( - label = stringResource(R.string.shortcuts_properties_exclusive_input), - checked = exclusiveChecked, - onCheckedChange = { enabled -> - if (isContainer) { - state.containerExclusiveInput.value = enabled - } else { - state.shortcutExclusiveXInput.value = enabled - } - if (!enabled) { - state.enableXInput.value = true - state.enableDInput.value = true - } else if (state.enableXInput.value && state.enableDInput.value) { - state.enableDInput.value = false + if (!gamescope) { + val exclusiveChecked = if (isContainer) state.containerExclusiveInput.value + else state.shortcutExclusiveXInput.value + SettingPairRow { + Box(Modifier.weight(1f)) { + SettingCheckbox( + label = stringResource(R.string.shortcuts_properties_exclusive_input), + checked = exclusiveChecked, + onCheckedChange = { enabled -> + if (isContainer) { + state.containerExclusiveInput.value = enabled + } else { + state.shortcutExclusiveXInput.value = enabled + } + if (!enabled) { + state.enableXInput.value = true + state.enableDInput.value = true + } else if (state.enableXInput.value && state.enableDInput.value) { + state.enableDInput.value = false + } } - } - ) - } - Box(Modifier.weight(1f)) { - SettingCheckbox( - label = stringResource(R.string.container_config_sdl2_compatibility), - checked = state.sdl2Compatibility.value, - onCheckedChange = { state.sdl2Compatibility.value = it } - ) + ) + } + Box(Modifier.weight(1f)) { + SettingCheckbox( + label = stringResource(R.string.container_config_sdl2_compatibility), + checked = state.sdl2Compatibility.value, + onCheckedChange = { state.sdl2Compatibility.value = it } + ) + } } } - Spacer(Modifier.height(4.dp)) SettingCheckbox( @@ -5080,137 +5271,183 @@ private fun InputSection(state: GameSettingsStateHolder) { } } - Spacer(Modifier.height(SettingSectionGap)) - - SubsectionLabel(stringResource(R.string.session_gamepad_game_controller)) - Spacer(Modifier.height(8.dp)) - SettingGroup { - // DInput Mapper Type (only visible when DInput enabled) - if (state.enableDInput.value) { - SettingDropdown( - label = stringResource(R.string.container_config_directinput_mapper_type), - entries = state.dInputMapperTypeEntries.value, - selectedIndex = state.selectedDInputMapperType.intValue, - onSelected = { state.selectedDInputMapperType.intValue = it } - ) - Spacer(Modifier.height(SettingItemGap)) - } + // XInput, DirectInput and their mapper are Wine's; a Linux session's pad is an evdev device. + if (!gamescope) { + Spacer(Modifier.height(SettingSectionGap)) - // Enable XInput with help โ€” only toggleable when Exclusive Input is on. - val inputApisLocked = if (isContainer) !state.containerExclusiveInput.value - else !state.shortcutExclusiveXInput.value - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Box(Modifier.weight(1f)) { - SettingCheckbox( - label = stringResource(R.string.container_config_enable_xinput), - checked = state.enableXInput.value, - onCheckedChange = { - state.enableXInput.value = it - if (!inputApisLocked && it && state.enableDInput.value) state.enableDInput.value = false - }, - enabled = !inputApisLocked + SubsectionLabel(stringResource(R.string.session_gamepad_game_controller)) + Spacer(Modifier.height(8.dp)) + SettingGroup { + // DInput Mapper Type (only visible when DInput enabled) + if (state.enableDInput.value) { + SettingDropdown( + label = stringResource(R.string.container_config_directinput_mapper_type), + entries = state.dInputMapperTypeEntries.value, + selectedIndex = state.selectedDInputMapperType.intValue, + onSelected = { state.selectedDInputMapperType.intValue = it } ) + Spacer(Modifier.height(SettingItemGap)) } - var showXInputHelp by remember { mutableStateOf(false) } - val xInputHelpOffset = rememberSmartDropdownOffset() - Box { - Box( - modifier = Modifier - .size(30.dp) - .clip(RoundedCornerShape(6.dp)) - .background(InputSurface) - .border(1.dp, InputBorder, RoundedCornerShape(6.dp)) - .paneNavItem(cornerRadius = 6.dp, onActivate = { showXInputHelp = !showXInputHelp }, highlightColor = NavHighlight) - .smartDropdownAnchor(offset = xInputHelpOffset) { showXInputHelp = !showXInputHelp }, - contentAlignment = Alignment.Center - ) { - Icon( - Icons.AutoMirrored.Outlined.HelpOutline, - contentDescription = null, - tint = TextPrimary, - modifier = Modifier.size(18.dp) + + // Enable XInput with help โ€” only toggleable when Exclusive Input is on. + val inputApisLocked = if (isContainer) !state.containerExclusiveInput.value + else !state.shortcutExclusiveXInput.value + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box(Modifier.weight(1f)) { + SettingCheckbox( + label = stringResource(R.string.container_config_enable_xinput), + checked = state.enableXInput.value, + onCheckedChange = { + state.enableXInput.value = it + if (!inputApisLocked && it && state.enableDInput.value) state.enableDInput.value = false + }, + enabled = !inputApisLocked ) } - DropdownMenu( - expanded = showXInputHelp, - onDismissRequest = { showXInputHelp = false }, - offset = xInputHelpOffset.value, - shape = RoundedCornerShape(8.dp), - containerColor = CardSurface, - modifier = Modifier - .padding(10.dp) - .width(280.dp) - ) { - HtmlText( - stringResource(R.string.container_config_help_xinput), - color = TextPrimary, - fontSize = SettingLabelSize, - lineHeight = 16.sp - ) + var showXInputHelp by remember { mutableStateOf(false) } + val xInputHelpOffset = rememberSmartDropdownOffset() + Box { + Box( + modifier = Modifier + .size(30.dp) + .clip(RoundedCornerShape(6.dp)) + .background(InputSurface) + .border(1.dp, InputBorder, RoundedCornerShape(6.dp)) + .paneNavItem(cornerRadius = 6.dp, onActivate = { showXInputHelp = !showXInputHelp }, highlightColor = NavHighlight) + .smartDropdownAnchor(offset = xInputHelpOffset) { showXInputHelp = !showXInputHelp }, + contentAlignment = Alignment.Center + ) { + Icon( + Icons.AutoMirrored.Outlined.HelpOutline, + contentDescription = null, + tint = TextPrimary, + modifier = Modifier.size(18.dp) + ) + } + DropdownMenu( + expanded = showXInputHelp, + onDismissRequest = { showXInputHelp = false }, + offset = xInputHelpOffset.value, + shape = RoundedCornerShape(8.dp), + containerColor = CardSurface, + modifier = Modifier + .padding(10.dp) + .width(280.dp) + ) { + HtmlText( + stringResource(R.string.container_config_help_xinput), + color = TextPrimary, + fontSize = SettingLabelSize, + lineHeight = 16.sp + ) + } } } - } - Spacer(Modifier.height(4.dp)) + Spacer(Modifier.height(4.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Box(Modifier.weight(1f)) { - SettingCheckbox( - label = stringResource(R.string.container_config_enable_dinput), - checked = state.enableDInput.value, - onCheckedChange = { - state.enableDInput.value = it - if (!inputApisLocked && it && state.enableXInput.value) state.enableXInput.value = false - }, - enabled = !inputApisLocked - ) - } - var showDInputHelp by remember { mutableStateOf(false) } - val dInputHelpOffset = rememberSmartDropdownOffset() - Box { - Box( - modifier = Modifier - .size(30.dp) - .clip(RoundedCornerShape(6.dp)) - .background(InputSurface) - .border(1.dp, InputBorder, RoundedCornerShape(6.dp)) - .paneNavItem(cornerRadius = 6.dp, onActivate = { showDInputHelp = !showDInputHelp }, highlightColor = NavHighlight) - .smartDropdownAnchor(offset = dInputHelpOffset) { showDInputHelp = !showDInputHelp }, - contentAlignment = Alignment.Center - ) { - Icon( - Icons.AutoMirrored.Outlined.HelpOutline, - contentDescription = null, - tint = TextPrimary, - modifier = Modifier.size(18.dp) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box(Modifier.weight(1f)) { + SettingCheckbox( + label = stringResource(R.string.container_config_enable_dinput), + checked = state.enableDInput.value, + onCheckedChange = { + state.enableDInput.value = it + if (!inputApisLocked && it && state.enableXInput.value) state.enableXInput.value = false + }, + enabled = !inputApisLocked ) } - DropdownMenu( - expanded = showDInputHelp, - onDismissRequest = { showDInputHelp = false }, - offset = dInputHelpOffset.value, - shape = RoundedCornerShape(8.dp), - containerColor = CardSurface, - modifier = Modifier - .padding(10.dp) - .width(280.dp) - ) { - HtmlText( - stringResource(R.string.container_config_help_dinput), - color = TextPrimary, - fontSize = SettingLabelSize, - lineHeight = 16.sp - ) + var showDInputHelp by remember { mutableStateOf(false) } + val dInputHelpOffset = rememberSmartDropdownOffset() + Box { + Box( + modifier = Modifier + .size(30.dp) + .clip(RoundedCornerShape(6.dp)) + .background(InputSurface) + .border(1.dp, InputBorder, RoundedCornerShape(6.dp)) + .paneNavItem(cornerRadius = 6.dp, onActivate = { showDInputHelp = !showDInputHelp }, highlightColor = NavHighlight) + .smartDropdownAnchor(offset = dInputHelpOffset) { showDInputHelp = !showDInputHelp }, + contentAlignment = Alignment.Center + ) { + Icon( + Icons.AutoMirrored.Outlined.HelpOutline, + contentDescription = null, + tint = TextPrimary, + modifier = Modifier.size(18.dp) + ) + } + DropdownMenu( + expanded = showDInputHelp, + onDismissRequest = { showDInputHelp = false }, + offset = dInputHelpOffset.value, + shape = RoundedCornerShape(8.dp), + containerColor = CardSurface, + modifier = Modifier + .padding(10.dp) + .width(280.dp) + ) { + HtmlText( + stringResource(R.string.container_config_help_dinput), + color = TextPrimary, + fontSize = SettingLabelSize, + lineHeight = 16.sp + ) + } } } + } + } +} +/** + * Proton's ARM64 build brings its own FEX for 64-bit and 32-bit games alike, so the emulators + * installed in the app, their versions, the Wine startup options and the Wine-side processor + * affinity have nothing to act on. FEX still reads its settings from the environment, which is + * what a preset is. Which Proton build runs the game is chosen here instead of a Wine version. + */ +@Composable +private fun GamescopeAdvancedSection(state: GameSettingsStateHolder) { + if (state.linuxProtonEntries.value.isNotEmpty()) { + SettingGroup { + SettingDropdown( + label = stringResource(R.string.gamescope_proton_title), + entries = state.linuxProtonEntries.value, + selectedIndex = state.selectedLinuxProton.intValue, + onSelected = { state.selectedLinuxProton.intValue = it } + ) + Spacer(Modifier.height(SettingTightGap)) + Text( + text = stringResource(R.string.gamescope_proton_summary), + color = TextDim, + fontSize = SettingLabelSize + ) + } + Spacer(Modifier.height(SettingSectionGap)) + } + EmulatorSectionHeader(stringResource(R.string.gamescope_fex_title), null) + Spacer(Modifier.height(8.dp)) + SettingGroup { + SettingDropdown( + label = stringResource(R.string.gamescope_fex_preset), + entries = state.fexcorePresetEntries.value, + selectedIndex = state.selectedFexcorePreset.intValue, + onSelected = { state.selectedFexcorePreset.intValue = it } + ) + Spacer(Modifier.height(SettingTightGap)) + Text( + text = stringResource(R.string.gamescope_fex_summary), + color = TextDim, + fontSize = SettingLabelSize + ) } } @@ -5220,6 +5457,10 @@ private fun AdvancedSection( state: GameSettingsStateHolder, callbacks: GameSettingsCallbacks ) { + if (state.gamescopeContainer.value) { + GamescopeAdvancedSection(state) + return + } // Wine/Proton version (read-only); shown only on existing containers where it isn't editable (new ones pick it in General). val wineVersionDisplay = state.wineVersionDisplay.value diff --git a/app/src/main/feature/library/LibraryCache.kt b/app/src/main/feature/library/LibraryCache.kt new file mode 100644 index 000000000..317a696cd --- /dev/null +++ b/app/src/main/feature/library/LibraryCache.kt @@ -0,0 +1,101 @@ +package com.winlator.cmod.feature.library + +import android.content.Context +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.runtime.container.Shortcut +import org.json.JSONArray +import org.json.JSONObject + +/** + * The library the app drew last time, so the grid is on screen the moment it opens rather than + * after Room, the stores and the shortcut scan have all reported in. + * + * It is a picture of a finished scan, never a source of truth: the scan that runs on every launch + * replaces it, and until that scan finishes the app is offline or simply still starting, which is + * exactly when the picture is worth having. Entries carry only what a card draws - artwork is + * already cached on disk under the app id. + */ +object LibraryCache { + private const val PREFS = "library_cache" + private const val KEY_ENTRIES = "installed" + + /** The custom artwork a card draws, by library id. */ + data class Artwork( + val cover: Map<Int, String> = emptyMap(), + val iconArtwork: Map<Int, String> = emptyMap(), + val icon: Map<Int, String> = emptyMap(), + val hero: Map<Int, String> = emptyMap(), + val carousel: Map<Int, String> = emptyMap(), + val list: Map<Int, String> = emptyMap(), + ) + + /** Everything the library screen had worked out when it was last on screen in this process. */ + data class Session( + val apps: List<SteamApp>, + val merged: List<SteamApp>, + val gogByPseudoId: Map<Int, GOGGame>, + val epicByPseudoId: Map<Int, EpicGame>, + val shortcuts: List<Shortcut>, + val customApps: List<SteamApp>, + val customStoragePaths: Map<Int, String>, + val artwork: Artwork = Artwork(), + ) + + /** + * The library screen leaves the composition whenever another tab or a game's page covers it. + * Coming back starts from this rather than from nothing, so the grid never empties while the + * scan that follows confirms it. Main thread only. + */ + var session: Session? = null + + /** The saved picture, read ahead of the first composition by [preload]. */ + @Volatile + var preloaded: List<SteamApp>? = null + private set + + /** Worker thread. */ + fun preload(context: Context) { + if (preloaded == null) preloaded = load(context) + } + + /** Worker thread. */ + fun load(context: Context): List<SteamApp> = + runCatching { + val raw = prefs(context).getString(KEY_ENTRIES, null) ?: return emptyList() + val array = JSONArray(raw) + (0 until array.length()).mapNotNull { index -> + val entry = array.optJSONObject(index) ?: return@mapNotNull null + val id = entry.optInt("id", 0) + if (id == 0) return@mapNotNull null + SteamApp( + id = id, + name = entry.optString("name"), + developer = entry.optString("developer"), + gameDir = entry.optString("gameDir"), + ) + } + }.getOrDefault(emptyList()) + + /** Worker thread. */ + fun save( + context: Context, + apps: List<SteamApp>, + ) { + val array = JSONArray() + for (app in apps) { + array.put( + JSONObject() + .put("id", app.id) + .put("name", app.name) + .put("developer", app.developer) + .put("gameDir", app.gameDir), + ) + } + runCatching { prefs(context).edit().putString(KEY_ENTRIES, array.toString()).apply() } + } + + private fun prefs(context: Context) = + context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) +} diff --git a/app/src/main/feature/library/LibraryItemType.kt b/app/src/main/feature/library/LibraryItemType.kt new file mode 100644 index 000000000..3d0edb832 --- /dev/null +++ b/app/src/main/feature/library/LibraryItemType.kt @@ -0,0 +1,33 @@ +package com.winlator.cmod.feature.library + +import com.winlator.cmod.feature.stores.steam.enums.AppType +import com.winlator.cmod.runtime.container.Shortcut + +/** What a library entry is, for the Games / Applications filters. Stored as the `library_type` extra. */ +enum class LibraryItemType(val key: String, val appType: AppType) { + GAME("game", AppType.game), + APPLICATION("application", AppType.application), + ; + + companion object { + const val EXTRA_KEY = "library_type" + + @JvmStatic + fun fromKey(key: String?): LibraryItemType = entries.firstOrNull { it.key == key } ?: GAME + + @JvmStatic + fun of(shortcut: Shortcut): LibraryItemType = fromKey(shortcut.getExtra(EXTRA_KEY)) + } +} + +/** The library drawer's content filters, keyed as they are persisted: `games`, `dlc`, `applications`, `tools`. */ +object LibraryContentFilters { + fun allows(type: AppType, filters: Map<String, Boolean>): Boolean = + when (type) { + AppType.game, AppType.demo -> filters["games"] == true + AppType.dlc -> filters["dlc"] == true + AppType.application -> filters["applications"] == true + AppType.tool, AppType.config -> filters["tools"] == true + else -> filters["games"] == true + } +} diff --git a/app/src/main/feature/library/LibraryStorageMove.kt b/app/src/main/feature/library/LibraryStorageMove.kt new file mode 100644 index 000000000..d4a5b395b --- /dev/null +++ b/app/src/main/feature/library/LibraryStorageMove.kt @@ -0,0 +1,188 @@ +package com.winlator.cmod.feature.library + +import android.content.Context +import android.os.StatFs +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.feature.stores.steam.data.AppInfo +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.shared.android.StoragePathUtils +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import kotlin.coroutines.coroutineContext +import timber.log.Timber + +/** + * Moves an installed game between the download folder the user picked (external) and the app's + * own storage (internal). The native Steam client runs a game from either. The app's own directory + * is on the same partition, so the move costs no extra space and, when the two are the same + * filesystem, is a rename rather than a copy. + */ +object LibraryStorageMove { + /** Where a game currently sits, and so which way [move] would take it. */ + enum class Target { APP_STORAGE, DOWNLOAD_FOLDER } + + data class Plan( + val appId: Int, + val source: File, + val destination: File, + val target: Target, + ) + + /** The app's own library root, created on demand. */ + fun appStorageRoot(context: Context): File? = StoragePathUtils.appPrivateGamesRoot(context) + + /** The root new downloads go to, as the store settings resolve it. */ + fun downloadRoot(context: Context): File? = + runCatching { SteamService.defaultAppInstallPath } + .getOrNull() + ?.takeIf { it.isNotBlank() } + ?.let(::File) + + private fun isUnder( + candidate: File, + root: File?, + ): Boolean = root != null && StoragePathUtils.isSameOrDescendant(candidate, root) + + /** + * Worker thread - resolves the install path. Returns the move this game is due, or null when + * there is nothing sensible to offer: no install on disk, a download still running, no second + * root to move to, or a download folder that is itself inside the app's own storage. + */ + fun plan( + context: Context, + appId: Int, + ): Plan? { + if (appId <= 0) return null + if (!SteamService.isAppInstalled(appId)) return null + if (SteamService.getAppDownloadInfo(appId)?.isActive() == true) return null + + val source = runCatching { SteamService.getAppDirPath(appId) }.getOrNull().orEmpty() + if (source.isBlank()) return null + val sourceDir = File(source).absoluteFile + if (!sourceDir.isDirectory) return null + + val dataDir = context.dataDir.absoluteFile + val appRoot = appStorageRoot(context) ?: return null + val downloadRoot = downloadRoot(context) + // A download folder inside the app's own directory leaves no second location, so neither + // direction is offered. + if (isUnder(sourceDir, dataDir) && (downloadRoot == null || isUnder(downloadRoot, dataDir))) return null + + val toAppStorage = !isUnder(sourceDir, dataDir) + val targetRoot = if (toAppStorage) appRoot else downloadRoot ?: return null + if (isUnder(sourceDir, targetRoot)) return null + + val destination = File(targetRoot, sourceDir.name).absoluteFile + if (StoragePathUtils.samePath(sourceDir, destination)) return null + return Plan( + appId = appId, + source = sourceDir, + destination = destination, + target = if (toAppStorage) Target.APP_STORAGE else Target.DOWNLOAD_FOLDER, + ) + } + + /** + * Moves the game and records the new location. [onProgress] is called with bytes copied so far + * and the total, and is not called at all when the move turns out to be a rename. + * + * The source is only removed once every file has been written, so a failure part way through + * leaves the playable copy where it was. + */ + suspend fun move( + plan: Plan, + onProgress: (Long, Long) -> Unit = { _, _ -> }, + ): Result<File> = + withContext(Dispatchers.IO) { + runCatching { + val source = plan.source + val destination = plan.destination + check(source.isDirectory) { "${source.path} is not a directory" } + check(!destination.exists() || destination.list()?.isEmpty() != false) { + "${destination.path} already exists" + } + val parent = destination.parentFile + check(parent != null && (parent.isDirectory || parent.mkdirs())) { + "cannot create ${destination.parent}" + } + + val total = sizeOf(source) + check(freeSpace(parent) >= total) { "not enough room in ${parent.path}" } + + if (!source.renameTo(destination)) { + copyTree(source, destination, total, onProgress) + check(sizeOf(destination) == total) { "copy of ${source.name} is incomplete" } + check(deleteTree(source)) { "could not remove ${source.path}" } + } + + record(plan.appId, destination) + destination + }.onFailure { Timber.e(it, "Could not move appId=%d to %s", plan.appId, plan.destination.path) } + } + + /** The two places the app looks an install up: the catalogue row and the durable install record. */ + private suspend fun record( + appId: Int, + destination: File, + ) { + val db = PluviaDatabase.getInstance() + db.steamAppDao().findApp(appId)?.let { app -> + db.steamAppDao().update(app.copy(installDir = destination.path)) + } + val existing = db.appInfoDao().get(appId) + if (existing != null) { + db.appInfoDao().update(existing.copy(isDownloaded = true, installPath = destination.path)) + } else { + db.appInfoDao().insert(AppInfo(id = appId, isDownloaded = true, installPath = destination.path)) + } + } + + private fun sizeOf(dir: File): Long = + dir.walkTopDown().filter { it.isFile }.sumOf { it.length() } + + private fun freeSpace(dir: File): Long = + runCatching { + val stat = StatFs(dir.path) + stat.availableBlocksLong * stat.blockSizeLong + }.getOrDefault(Long.MAX_VALUE) + + private suspend fun copyTree( + source: File, + destination: File, + total: Long, + onProgress: (Long, Long) -> Unit, + ) { + var copied = 0L + var reported = 0L + val prefix = source.path.length + for (entry in source.walkTopDown()) { + coroutineContext.ensureActive() + val target = File(destination, entry.path.substring(prefix).trimStart(File.separatorChar)) + if (entry.isDirectory) { + check(target.isDirectory || target.mkdirs()) { "cannot create ${target.path}" } + continue + } + entry.inputStream().use { input -> + target.outputStream().use { output -> input.copyTo(output, DEFAULT_BUFFER_SIZE) } + } + // The executable bit is what shared storage could not keep, so it is carried over here. + if (entry.canExecute()) target.setExecutable(true, false) + target.setLastModified(entry.lastModified()) + copied += entry.length() + // Progress redraws cost more than the copy does when a game holds thousands of files. + if (copied - reported >= PROGRESS_STEP || copied == total) { + reported = copied + onProgress(copied, total) + } + } + } + + private fun deleteTree(dir: File): Boolean { + dir.walkBottomUp().forEach { if (!it.delete() && it.exists()) return false } + return !dir.exists() + } + + private const val PROGRESS_STEP = 16L * 1024 * 1024 +} diff --git a/app/src/main/feature/library/LinuxApps.kt b/app/src/main/feature/library/LinuxApps.kt new file mode 100644 index 000000000..d8b9443a8 --- /dev/null +++ b/app/src/main/feature/library/LinuxApps.kt @@ -0,0 +1,225 @@ +package com.winlator.cmod.feature.library + +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Typeface +import android.widget.Toast +import com.winlator.cmod.R +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.linux.LinuxClientInstaller +import com.winlator.cmod.runtime.linux.LinuxRuntime +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.toast.WinToast +import java.io.File +import java.io.FileOutputStream +import java.util.Locale +import java.util.UUID + +/** + * Library entries for Linux programs. They are `.desktop` files like every other shortcut, marked + * with `runtime=linux` and `Exec=linux:native`, and start in the Linux runtime rather than Wine. + */ +object LinuxApps { + const val KEY_RUNTIME = "runtime" + const val RUNTIME_LINUX = "linux" + const val EXEC = "linux:native" + const val KEY_SESSION = "linux_session" + const val SESSION_STEAM = "steam" + const val STEAM_SHORTCUT_NAME = "Steam" + private const val STEAM_ICON = "steam_client" + private const val STEAM_GROUND = 0xFF1B2838.toInt() + private const val STEAM_INK = 0xFFE7EEF5.toInt() + + /** Extensions Linux programs ship with. A bare ELF with no extension is not recognised. */ + val Extensions = setOf("appimage", "sh", "run", "bin", "elf", "x86_64", "x86", "aarch64", "arm64") + + @JvmStatic + fun isLinuxExecutable(file: File): Boolean = + file.isFile && file.extension.lowercase(Locale.ROOT) in Extensions + + @JvmStatic + fun isLinuxShortcut(shortcut: Shortcut): Boolean = shortcut.getExtra(KEY_RUNTIME) == RUNTIME_LINUX + + /** The library entry that opens the native Steam client. */ + @JvmStatic + fun isSteamClientShortcut(shortcut: Shortcut): Boolean = shortcut.getExtra(KEY_SESSION) == SESSION_STEAM + + @JvmStatic + fun gamescopeContainer(manager: ContainerManager): Container? = + manager.containers.firstOrNull { it.isGamescopeRuntime } + + /** Worker thread. Whether the Library lacks the Steam entry, also when there is no GameScope container yet. */ + @JvmStatic + fun isSteamShortcutMissing(context: Context): Boolean { + val container = gamescopeContainer(ContainerManager(context)) ?: return true + return !File(container.desktopDir, "$STEAM_SHORTCUT_NAME.desktop").exists() + } + + /** + * Writes the Steam entry into the GameScope container's desktop directory if it is missing, + * with an icon rendered from the app's own drawable. + */ + @JvmStatic + fun ensureSteamShortcut( + context: Context, + container: Container, + ) { + val desktopDir = container.desktopDir + if (!desktopDir.exists()) desktopDir.mkdirs() + val shortcutFile = File(desktopDir, "$STEAM_SHORTCUT_NAME.desktop") + // The artwork is refreshed even for an entry that already exists, so an upgrade picks it up. + val stamp = File(context.filesDir, "custom_icons/.steam_art") + renderSteamIcon(File(container.getIconsDir(64), "$STEAM_ICON.png"), 64, stamp) + renderSteamCover(File(context.filesDir, "custom_icons/$STEAM_SHORTCUT_NAME.png"), stamp) + runCatching { stamp.writeText(ART_VERSION.toString()) } + if (shortcutFile.exists()) return + val content = + buildString { + append("[Desktop Entry]\n") + append("Type=Application\n") + append("Name=$STEAM_SHORTCUT_NAME\n") + append("Exec=$EXEC\n") + append("Icon=$STEAM_ICON\n") + append("\n[Extra Data]\n") + append("game_source=CUSTOM\n") + append("custom_name=$STEAM_SHORTCUT_NAME\n") + append("$KEY_RUNTIME=$RUNTIME_LINUX\n") + append("$KEY_SESSION=$SESSION_STEAM\n") + append("${LibraryItemType.EXTRA_KEY}=${LibraryItemType.APPLICATION.key}\n") + append("uuid=${UUID.randomUUID()}\n") + append("container_id=${container.id}\n") + append("use_container_defaults=1\n") + } + FileUtils.writeString(shortcutFile, content) + } + + /** + * The library artwork. Valve's marks are theirs to license, so the entry is drawn as the plain + * word instead of the client's logo: bold, letter-spaced, centred on the client's dark ground. + * + * [stamp] is what tells an install whose artwork was drawn by an older build to redraw it; the + * files are otherwise written once and left alone. + */ + private const val ART_VERSION = 2 + + private fun steamArtIsCurrent(stamp: File): Boolean = + runCatching { stamp.readText().trim().toInt() }.getOrNull() == ART_VERSION + + private fun drawSteamWordmark( + canvas: Canvas, + width: Int, + height: Int, + ) { + canvas.drawColor(STEAM_GROUND) + val text = "STEAM" + val paint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = STEAM_INK + typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD) + textAlign = Paint.Align.CENTER + letterSpacing = 0.18f + textSize = height * 0.42f + } + // Shrink to fit rather than clip, so the word reads whole at every size it is drawn at. + val maxWidth = width * 0.78f + val measured = paint.measureText(text) + if (measured > maxWidth) paint.textSize *= maxWidth / measured + val metrics = paint.fontMetrics + val baseline = height / 2f - (metrics.ascent + metrics.descent) / 2f + // Centring accounts for the trailing letter space the paint adds after the last glyph. + canvas.drawText(text, width / 2f + paint.letterSpacing * paint.textSize / 2f, baseline, paint) + } + + /** The wide card the library draws. */ + private fun renderSteamCover( + file: File, + stamp: File, + ) { + if (file.exists() && steamArtIsCurrent(stamp)) return + file.parentFile?.mkdirs() + val width = 920 + val height = 430 + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + drawSteamWordmark(Canvas(bitmap), width, height) + FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } + } + + /** The library card reads `custom_icons/<name>.png`; the desktop entry reads the container's icon dir. */ + private fun renderSteamIcon( + file: File, + size: Int, + stamp: File, + ) { + if (file.exists() && steamArtIsCurrent(stamp)) return + file.parentFile?.mkdirs() + val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + drawSteamWordmark(Canvas(bitmap), size, size) + FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } + } + + /** Worker thread. Writes the shortcut into the GameScope container's desktop directory. */ + fun create( + context: Context, + name: String, + exePath: String, + type: LibraryItemType, + ): Boolean { + val container = gamescopeContainer(ContainerManager(context)) + if (container == null) { + WinToast.show(context, R.string.linux_apps_need_gamescope_container, Toast.LENGTH_LONG) + return false + } + val desktopDir = container.desktopDir + if (!desktopDir.exists()) desktopDir.mkdirs() + val safeName = name.replace("/", "_").replace("\\", "_") + val shortcutFile = File(desktopDir, "$safeName.desktop") + val content = + buildString { + append("[Desktop Entry]\n") + append("Type=Application\n") + append("Name=$name\n") + append("Exec=$EXEC\n") + append("Icon=custom_game\n") + append("\n[Extra Data]\n") + append("game_source=CUSTOM\n") + append("custom_name=$name\n") + append("custom_exe=$exePath\n") + append("custom_game_folder=${File(exePath).parent.orEmpty()}\n") + append("$KEY_RUNTIME=$RUNTIME_LINUX\n") + append("${LibraryItemType.EXTRA_KEY}=${type.key}\n") + append("uuid=${UUID.randomUUID()}\n") + append("container_id=${container.id}\n") + append("use_container_defaults=1\n") + } + FileUtils.writeString(shortcutFile, content) + return true + } + + /** UI thread. Starts a session in the Linux runtime, or says why it cannot. */ + fun launch( + context: Context, + shortcut: Shortcut, + ) { + val name = shortcut.getExtra("custom_name").ifEmpty { shortcut.name } + if (LinuxClientInstaller.isWorking) { + WinToast.show(context, context.getString(R.string.linux_client_busy), Toast.LENGTH_LONG) + return + } + if (!LinuxRuntime.isInstalled(context)) { + WinToast.show(context, context.getString(R.string.linux_runtime_not_installed, name), Toast.LENGTH_LONG) + return + } + val intent = + Intent(context, XServerDisplayActivity::class.java) + .putExtra("container_id", shortcut.container.id) + .putExtra("shortcut_path", shortcut.file.path) + .putExtra("shortcut_name", name) + context.startActivity(intent) + } +} diff --git a/app/src/main/feature/library/LinuxSteamLibrary.kt b/app/src/main/feature/library/LinuxSteamLibrary.kt new file mode 100644 index 000000000..11f9b66f3 --- /dev/null +++ b/app/src/main/feature/library/LinuxSteamLibrary.kt @@ -0,0 +1,275 @@ +package com.winlator.cmod.feature.library + +import android.content.Context +import android.util.Log +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.feature.stores.common.InstallOwnership +import com.winlator.cmod.feature.stores.common.InstallStore +import com.winlator.cmod.feature.stores.steam.data.AppInfo +import com.winlator.cmod.feature.stores.steam.enums.Marker +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.MarkerUtils +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.SteamUtils +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.shared.io.FileUtils +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import org.json.JSONObject + +/** + * The games WinNative downloaded, presented to the native Steam client as a library folder at + * [GUEST_ROOT]: the manifests live in the runtime's rootfs and each game folder is bound in under + * its Steam install name. winnative-steam-library registers the folder with the client. + * + * Both sides update titles, so the manifest for each is reconciled rather than rewritten: a build + * the client installed is adopted into the app's own install records, and the app's manifest only + * replaces the client's when the app holds the newer build. + */ +object LinuxSteamLibrary { + const val GUEST_ROOT = "/mnt/winnative" + private const val TAG = "LinuxSteamLibrary" + private val BUILD_ID = Regex("^\\s*\"buildid\"\\s*\"(\\d+)\"", RegexOption.MULTILINE) + private val STATE_FLAGS = Regex("^\\s*\"StateFlags\"\\s*\"(\\d+)\"", RegexOption.MULTILINE) + private val INSTALL_DIR = Regex("^\\s*\"installdir\"\\s*\"([^\"]+)\"", RegexOption.MULTILINE) + private val APP_NAME = Regex("^\\s*\"name\"\\s*\"([^\"]+)\"", RegexOption.MULTILINE) + private val MANIFEST_NAME = Regex("^appmanifest_(\\d+)\\.acf$") + + /** The client's own library inside the rootfs, where it installs unless told otherwise. */ + private const val CLIENT_STEAMAPPS = "root/.local/share/Steam/steamapps" + + /** + * StateFlags is a bit field: a finished download keeps this bit while an update is due (2) or + * paused (512), so it is tested rather than compared. + */ + private const val STATE_FULLY_INSTALLED = 4 + private const val STATE_UNINSTALLING = 2048 + + /** Marks a library entry this scan wrote, so the scan may also take it away. */ + const val KEY_CLIENT_INSTALL = "linux_client_install" + + /** Runtimes and redistributables the client installs for itself; none of them is a game. */ + private val TOOL_APP_IDS = setOf(228980, 1070560, 1391110, 1493710, 3127680, 4183110, 4185400, 4427310, 4628740) + private val DEPOT_MANIFEST = Regex("\"(\\d+)\"\\s*\\{[^{}]*?\"manifest\"\\s*\"(\\d+)\"") + + /** Worker thread. Returns the proot bind specs (`host:guest`) for the installed games. */ + @JvmStatic + fun prepare( + context: Context, + rootfs: File, + ): List<String> { + val steamapps = File(rootfs, "mnt/winnative/steamapps") + val common = File(steamapps, "common") + if (!common.isDirectory && !common.mkdirs()) return emptyList() + val recorded = + runCatching { + runBlocking(Dispatchers.IO) { PluviaDatabase.getInstance(context).appInfoDao().getAllInstalledAppIds() } + }.getOrElse { + Log.w(TAG, "Installed Steam games unavailable", it) + emptyList() + } + val installed = recorded.filter { SteamService.isAppInstalled(it) } + val language = PrefManager.containerLanguage.ifBlank { "english" } + val prefixManifests = File(ImageFs.find(context).wineprefix, "drive_c/Program Files (x86)/Steam/steamapps") + val binds = ArrayList<String>() + for (appId in installed) { + val gameDir = File(SteamService.getAppDirPath(appId)) + if (!gameDir.isDirectory) continue + val runtimeManifest = File(steamapps, "appmanifest_$appId.acf") + val runtimeBuild = buildId(runtimeManifest) + if (runtimeBuild > 0L && runtimeBuild >= PrefManager.getInstalledBuildId(appId)) { + adopt(appId, runtimeManifest, runtimeBuild, gameDir) + } + SteamUtils.createAppManifest(context, appId, language) + val manifest = File(prefixManifests, "appmanifest_$appId.acf") + if (!manifest.isFile) continue + val installDir = SteamService.getAppDirName(SteamService.getAppInfoOf(appId)).ifBlank { gameDir.name } + if (runtimeBuild == 0L || buildId(manifest) > runtimeBuild) { + manifest.copyTo(runtimeManifest, overwrite = true) + } + File(common, installDir).mkdirs() + binds.add("${gameDir.path}:$GUEST_ROOT/steamapps/common/$installDir") + } + // A manifest is only the app's to remove when the app recorded the title and has since + // uninstalled it; the client's own installs are left to the client. + val uninstalled = recorded.toSet() - installed.toSet() + for (appId in uninstalled) { + File(steamapps, "appmanifest_$appId.acf").delete() + } + return binds + } + + /** The build a manifest records, or 0 when there is no readable manifest. */ + private fun buildId(manifest: File): Long { + if (!manifest.isFile) return 0L + val text = runCatching { manifest.readText() }.getOrElse { return 0L } + return BUILD_ID.find(text)?.groupValues?.get(1)?.toLongOrNull() ?: 0L + } + + /** + * Records a build the client installed where the app's own updater looks: the build id it + * compares against the branch, and the depot manifests it compares against the store's. + */ + private fun adopt( + appId: Int, + manifest: File, + build: Long, + gameDir: File, + ) { + val text = runCatching { manifest.readText() }.getOrElse { return } + PrefManager.setInstalledBuildId(appId, build) + val start = text.indexOf("\"InstalledDepots\"") + if (start < 0) return + val depots = DEPOT_MANIFEST.findAll(text, start).associate { it.groupValues[1] to it.groupValues[2] } + if (depots.isEmpty()) return + val configDir = File(gameDir, ".DepotDownloader") + val configFile = File(configDir, "depot.config") + runCatching { + val config = if (configFile.isFile) JSONObject(configFile.readText()) else JSONObject() + val ids = config.optJSONObject("installedManifestIDs") ?: JSONObject() + for ((depot, gid) in depots) ids.put(depot, gid.toLongOrNull() ?: continue) + config.put("installedManifestIDs", ids) + if (configDir.isDirectory || configDir.mkdirs()) configFile.writeText(config.toString()) + }.onFailure { Log.w(TAG, "Could not record the client's build of $appId", it) } + } + + /** + * Worker thread. Titles the client installed on its own are recorded the way the app's own + * downloads are - an install record and ownership marker for the store, and a STEAM entry in + * the GameScope container that launches them through the client - so the library shows them + * and their settings, artwork and removal follow the game data. A title the client has since + * uninstalled loses its record and entry again. Returns whether the library changed. + */ + @JvmStatic + fun adoptClientInstalls( + context: Context, + rootfs: File, + ): Boolean { + val container = LinuxApps.gamescopeContainer(ContainerManager(context)) ?: return false + PrefManager.init(context) + val present = HashSet<Int>() + var changed = false + for (library in listOf(File(rootfs, CLIENT_STEAMAPPS), File(rootfs, "mnt/winnative/steamapps"))) { + val manifests = library.listFiles() ?: continue + for (manifest in manifests) { + val appId = MANIFEST_NAME.find(manifest.name)?.groupValues?.get(1)?.toIntOrNull() ?: continue + if (appId in TOOL_APP_IDS) continue + val text = runCatching { manifest.readText() }.getOrNull() ?: continue + val flags = STATE_FLAGS.find(text)?.groupValues?.get(1)?.toIntOrNull() ?: continue + if ((flags and STATE_FULLY_INSTALLED) == 0 || (flags and STATE_UNINSTALLING) != 0) continue + val installDir = INSTALL_DIR.find(text)?.groupValues?.get(1) ?: continue + val gameDir = File(library, "common/$installDir") + // A folder bound in from the app's own download is empty on this side of proot, + // and that title is recorded already. + if (!gameDir.isDirectory || gameDir.list().isNullOrEmpty()) continue + present.add(appId) + if (SteamService.isAppInstalled(appId)) continue + val name = SteamService.getAppInfoOf(appId)?.name?.ifBlank { null } + ?: APP_NAME.find(text)?.groupValues?.get(1) + ?: installDir + if (record(context, appId, gameDir, buildId(manifest)) && writeEntry(container, appId, name, gameDir)) { + Log.i(TAG, "Adopted the client's install of $appId at $gameDir") + changed = true + } + } + } + val entries = container.desktopDir.listFiles { f -> f.name.endsWith(".desktop") } ?: return changed + for (file in entries) { + val shortcut = Shortcut(container, file) + if (shortcut.getExtra(KEY_CLIENT_INSTALL) != "1") continue + val appId = shortcut.getExtra("app_id").toIntOrNull() ?: continue + if (appId in present) continue + if (release(context, appId) && file.delete()) { + Log.i(TAG, "Dropped the entry for $appId, which the client no longer has installed") + changed = true + } + } + return changed + } + + /** Records a client install where the store looks for its own downloads. */ + private fun record( + context: Context, + appId: Int, + gameDir: File, + build: Long, + ): Boolean { + val dir = gameDir.path + if (!InstallOwnership.claim(dir, InstallStore.STEAM)) return false + if (!MarkerUtils.addMarker(dir, Marker.DOWNLOAD_COMPLETE_MARKER)) return false + return runCatching { + runBlocking(Dispatchers.IO) { + val db = PluviaDatabase.getInstance(context) + db.steamAppDao().findApp(appId)?.let { app -> db.steamAppDao().update(app.copy(installDir = dir)) } + val existing = db.appInfoDao().get(appId) + if (existing != null) { + db.appInfoDao().update(existing.copy(isDownloaded = true, installPath = dir)) + } else { + db.appInfoDao().insert(AppInfo(id = appId, isDownloaded = true, installPath = dir)) + } + } + if (build > 0L) PrefManager.setInstalledBuildId(appId, build) + true + }.getOrElse { + Log.w(TAG, "Could not record the client's install of $appId", it) + false + } + } + + /** Forgets a client install the client itself has removed. */ + private fun release( + context: Context, + appId: Int, + ): Boolean = + runCatching { + runBlocking(Dispatchers.IO) { + val db = PluviaDatabase.getInstance(context) + db.appInfoDao().get(appId)?.let { db.appInfoDao().update(it.copy(isDownloaded = false)) } + db.steamAppDao().findApp(appId)?.let { db.steamAppDao().update(it.copy(installDir = "")) } + } + true + }.getOrElse { + Log.w(TAG, "Could not release the client's install of $appId", it) + false + } + + /** + * The library entry: the same STEAM entry the store writes for its downloads, in the GameScope + * container, where a launch hands the title to the client as a rungameid URL. + */ + private fun writeEntry( + container: Container, + appId: Int, + name: String, + gameDir: File, + ): Boolean { + val desktopDir = container.desktopDir + if (!desktopDir.exists() && !desktopDir.mkdirs()) return false + val safeName = name.replace("/", "_").replace("\\", "_") + val file = File(desktopDir, "$safeName.desktop") + if (file.exists() && file.length() > 0L) return true + val content = + buildString { + append("[Desktop Entry]\n") + append("Type=Application\n") + append("Name=$name\n") + append("Exec=${LinuxApps.EXEC}\n") + append("Icon=steam_icon_$appId\n") + append("\n[Extra Data]\n") + append("game_source=STEAM\n") + append("app_id=$appId\n") + append("container_id=${container.id}\n") + append("game_install_path=${gameDir.path}\n") + append("use_container_defaults=1\n") + append("$KEY_CLIENT_INSTALL=1\n") + } + return runCatching { FileUtils.writeString(file, content); true }.getOrElse { + Log.w(TAG, "Could not write the entry for $appId", it) + false + } + } +} diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index 17a850580..032759ba9 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -30,6 +30,10 @@ import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner import com.winlator.cmod.R import com.winlator.cmod.feature.library.DriveItem +import com.winlator.cmod.feature.library.DISPLAY_SERVER_WAYLAND_INDEX +import com.winlator.cmod.feature.library.DISPLAY_SERVER_X11_INDEX +import com.winlator.cmod.feature.library.displayBackendFromIndex +import com.winlator.cmod.feature.library.displayServerEntries import com.winlator.cmod.feature.library.EnvVarItem import com.winlator.cmod.feature.library.GameSettingsCallbacks import com.winlator.cmod.feature.library.GameSettingsContent @@ -46,6 +50,7 @@ import com.winlator.cmod.runtime.container.ContainerCreation import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.content.ContentProfile import com.winlator.cmod.runtime.content.ContentsManager +import com.winlator.cmod.runtime.content.DriverPackages import com.winlator.cmod.feature.settings.DXVKConfigUtils import com.winlator.cmod.feature.settings.GraphicsDriverConfigUtils import com.winlator.cmod.feature.settings.OtherSettingsFragment @@ -59,10 +64,13 @@ import com.winlator.cmod.shared.io.AssetPaths import com.winlator.cmod.runtime.wine.EnvVars import com.winlator.cmod.runtime.wine.LocaleEnv import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.widget.EnvVarsView import com.winlator.cmod.shared.util.KeyValueSet import com.winlator.cmod.shared.theme.WinNativeTheme import com.winlator.cmod.shared.util.StringUtils import com.winlator.cmod.runtime.input.ui.InputControlsView +import com.winlator.cmod.runtime.linux.LinuxDriverChoices +import com.winlator.cmod.runtime.linux.LinuxProtons import com.winlator.cmod.runtime.wine.WineInfo import com.winlator.cmod.runtime.wine.WineRegistryEditor import com.winlator.cmod.runtime.wine.WineThemeManager @@ -115,6 +123,8 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( // Parallel id/display lists for presets and wine versions. private var box64PresetIds = mutableListOf<String>() private var fexcorePresetIds = mutableListOf<String>() + /** Tool names behind [GameSettingsStateHolder.linuxProtonEntries]; null until they are read. */ + private var linuxProtonIds: List<String>? = null private var wineVersionIdentifiers = mutableListOf<String>() private var autogeneratedContainerName: String? = null @@ -483,7 +493,9 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.directXComponents.value = directX state.generalComponents.value = general - val envVarsStr = c?.getEnvVars() ?: Container.DEFAULT_ENV_VARS + val envVarsStr = (c?.getEnvVars() ?: Container.DEFAULT_ENV_VARS).let { + if (c?.isGamescopeRuntime == true) EnvVarsView.forGamescope(it) else it + } val items = parseEnvVarItems(envVarsStr) state.sdl2Compatibility.value = EnvVars(envVarsStr).get("SDL_XINPUT_ENABLED") == "1" state.envVars.value = if (state.sdl2Compatibility.value) { @@ -574,6 +586,13 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.selectedGraphicsDriver ) + state.displayServerEntries.value = displayServerEntries(context) + state.selectedDisplayServer.intValue = + if (c?.isWaylandBackend == true) DISPLAY_SERVER_WAYLAND_INDEX else DISPLAY_SERVER_X11_INDEX + state.gamescopeContainer.value = c?.isGamescopeRuntime == true + loadLinuxProtons() + state.wineVersionIdentifier.value = c?.getWineVersion() ?: WineInfo.MAIN_WINE_VERSION.identifier() + state.zinkModeEntries.value = context.resources.getStringArray(R.array.zink_mode_entries).toList() state.selectedZinkMode.intValue = if ((c?.getZinkMode() ?: Container.DEFAULT_ZINK_MODE) == "windows") 1 else 0 @@ -887,6 +906,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( c.setCPUList(cpuList) c.setCPUListWoW64(cpuListWoW64) c.setGraphicsDriver(graphicsDriver) + c.setDisplayBackend(displayBackendFromIndex(state.selectedDisplayServer.intValue)) c.setZinkMode(if (state.selectedZinkMode.intValue == 1) "windows" else "unix") c.setGraphicsDriverConfig(graphicsDriverConfig) c.setDXWrapper(dxwrapper) @@ -936,6 +956,9 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( c.setBox64Preset(box64Preset) c.setFEXCoreVersion(fexcoreVersion) c.setFEXCorePreset(fexcorePreset) + linuxProtonIds?.let { ids -> + c.setLinuxProton(ids.getOrElse(state.selectedLinuxProton.intValue) { Container.LINUX_PROTON_DEFAULT }) + } c.setDesktopTheme(desktopTheme) c.setMidiSoundFont(midiSoundFont) c.setLC_ALL(state.lcAll.value) @@ -944,6 +967,10 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( // doesn't expose them, and saveData() round-trips the loaded // values from c's in-memory state. c.saveData() + if (linuxProtonIds != null) { + LinuxProtons.updateChoices(context) + LinuxDriverChoices.update(context) + } saveMouseWarpOverride(c) dismiss() } else { @@ -1205,6 +1232,8 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( private fun audioDriverEntriesFor(wineVersion: String?, selected: String): List<String> { val all = context.resources.getStringArray(R.array.audio_driver_entries).toList() + // A Linux session has a PulseAudio server and DirectAudio's host, and nothing that speaks ALSA. + if (state.gamescopeContainer.value) return all.filter { !it.equals("ALSA", true) } if (wineVersion == null || DirectAudioDriver.isSupportedFor(wineVersion)) return all if (DirectAudioDriver.isSelected(selected)) return all return all.filter { !it.equals("DirectAudio", true) } @@ -1328,13 +1357,30 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( container?.getGraphicsDriverConfig() ?: Container.DEFAULT_GRAPHICSDRIVERCONFIG, ) + private fun loadLinuxProtons() { + linuxProtonIds = null + state.linuxProtonEntries.value = emptyList() + val c = container?.takeIf { it.isGamescopeRuntime } ?: return + val saved = c.getLinuxProton() + scope.launch { + val choices = withContext(Dispatchers.IO) { LinuxProtons.choices(context) } + linuxProtonIds = choices.map { it.first } + state.linuxProtonEntries.value = choices.map { it.second } + state.selectedLinuxProton.intValue = choices.indexOfFirst { it.first == saved }.coerceAtLeast(0) + } + } + private fun loadGraphicsDriverVersions() { val savedVersion = savedGraphicsDriverConfig().get("version") ?: "" state.gfxDriverVersionEntries.value = listOf(if (savedVersion.isNotEmpty()) savedVersion else "System") state.gfxSelectedDriverVersion.intValue = 0 scope.launch { val versions = withContext(Dispatchers.IO) { - com.winlator.cmod.runtime.system.GraphicsDriverCatalog.supportedVersions(context) + if (state.gamescopeContainer.value) { + DriverPackages.linuxSelectionEntries(context) + } else { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.supportedVersions(context) + } } state.gfxDriverVersionEntries.value = versions val idx = versions.indexOfFirst { it.equals(savedVersion, ignoreCase = true) } @@ -1348,7 +1394,11 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( val request = ++extensionsRequest scope.launch { val extensions = withContext(Dispatchers.IO) { - com.winlator.cmod.runtime.system.GraphicsDriverCatalog.extensions(context, version) + if (state.gamescopeContainer.value) { + emptyList() + } else { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.extensions(context, version) + } } if (request != extensionsRequest) return@launch state.gfxAvailableExtensions.value = extensions diff --git a/app/src/main/feature/settings/containers/ContainersFragment.kt b/app/src/main/feature/settings/containers/ContainersFragment.kt index 9e92fb692..5072386af 100644 --- a/app/src/main/feature/settings/containers/ContainersFragment.kt +++ b/app/src/main/feature/settings/containers/ContainersFragment.kt @@ -22,6 +22,7 @@ import com.winlator.cmod.app.shell.UnifiedActivity import com.winlator.cmod.feature.settings.ContainerSettingsComposeDialog import com.winlator.cmod.feature.shortcuts.ShortcutsFragment import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.container.ContainerCreation import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.content.ContentsManager import com.winlator.cmod.runtime.display.XServerDisplayActivity @@ -59,6 +60,7 @@ class ContainersFragment : Fragment() { ContainersScreen( state = screenState, onAddContainer = ::openAddContainer, + onAddGamescope = ::addGamescopeContainer, onRunContainer = ::runContainer, onEditContainer = ::editContainer, onDuplicateContainer = ::duplicateContainer, @@ -92,7 +94,33 @@ class ContainersFragment : Fragment() { private fun loadContainersList() { val context = context ?: return manager = ContainerManager(context) - screenState = screenState.copy(containers = manager.containers.toList()) + val all = manager.containers.toList() + screenState = + screenState.copy( + containers = all.filter { !it.isGamescopeRuntime }, + gamescope = all.filter { it.isGamescopeRuntime }, + ) + } + + private fun addGamescopeContainer() { + val context = context ?: return + if (!ImageFs.find(context).isUpToDate) { + WinToast.show(context, R.string.setup_wizard_system_image_not_installed, Toast.LENGTH_LONG) + return + } + val contentsManager = ContentsManager(context) + contentsManager.syncContents() + val runtime = ContainerCreation.newestInstalledRuntime(contentsManager) + val creatingPopup = ContainerProgressPopup(requireActivity(), R.string.containers_list_creating) + creatingPopup.show() + ContainerCreation.createGamescopeContainerAsync(context, manager, contentsManager, runtime) { created -> + creatingPopup.close() + if (!isAdded) return@createGamescopeContainerAsync + if (created == null) { + WinToast.show(context, R.string.containers_gamescope_create_failed, Toast.LENGTH_LONG) + } + loadContainersList() + } } private fun openAddContainer() { diff --git a/app/src/main/feature/settings/containers/ContainersScreen.kt b/app/src/main/feature/settings/containers/ContainersScreen.kt index 8e6f37771..8d5ac0983 100644 --- a/app/src/main/feature/settings/containers/ContainersScreen.kt +++ b/app/src/main/feature/settings/containers/ContainersScreen.kt @@ -91,6 +91,8 @@ private const val ContainerCardAspect = 1.2f data class ContainersScreenState( val containers: List<Container> = emptyList(), + /** The GameScope containers; Linux programs and Steam boot into the first of them. */ + val gamescope: List<Container> = emptyList(), val dialog: ContainersDialogUiState = ContainersDialogUiState.None, ) @@ -132,6 +134,7 @@ data class ContainerStorageInfoUiState( fun ContainersScreen( state: ContainersScreenState, onAddContainer: () -> Unit, + onAddGamescope: () -> Unit, onRunContainer: (Container) -> Unit, onEditContainer: (Container) -> Unit, onDuplicateContainer: (Container) -> Unit, @@ -163,7 +166,7 @@ fun ContainersScreen( end = 16.dp + navBarEndPadding, ), ) { - SectionLabel(text = stringResource(R.string.common_ui_containers)) + SectionLabel(text = stringResource(R.string.containers_wine_proton_section)) Spacer(Modifier.height(6.dp)) val cardSpacing = 8.dp @@ -194,6 +197,7 @@ fun ContainersScreen( when { cellIndex == 0 -> AddContainerCard( + label = stringResource(R.string.containers_list_new), onClick = onAddContainer, navRow = gyBase, navCol = gxBase, @@ -219,6 +223,54 @@ fun ContainersScreen( } } } + + Spacer(Modifier.height(10.dp)) + SectionLabel(text = stringResource(R.string.containers_gamescope_section)) + Spacer(Modifier.height(6.dp)) + // The card that makes one sits first, so this section reads the same way the + // Wine/Proton one above it does, and it only ever creates a GameScope container. + val gamescopeCells = state.gamescope.size + 1 + val gamescopeRows = (gamescopeCells + columns - 1) / columns + for (rowIndex in 0 until gamescopeRows) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(cardSpacing), + ) { + for (colIndex in 0 until columns) { + val cellIndex = rowIndex * columns + colIndex + val navRow = (rowCount + rowIndex) * 2 + val navCol = colIndex * 2 + Box(modifier = Modifier.weight(1f)) { + when { + cellIndex == 0 -> + AddContainerCard( + label = stringResource(R.string.containers_gamescope_new), + onClick = onAddGamescope, + navRow = navRow, + navCol = navCol, + ) + cellIndex <= state.gamescope.size -> { + val gamescope = state.gamescope[cellIndex - 1] + key(gamescope.id) { + ContainerCard( + container = gamescope, + navRow = navRow, + navCol = navCol, + onRun = { onRunContainer(gamescope) }, + onEdit = { onEditContainer(gamescope) }, + onDuplicate = null, + onInstallComponents = null, + onRemove = { onRemoveContainer(gamescope) }, + onShowInfo = { onShowInfo(gamescope) }, + ) + } + } + } + } + } + } + if (rowIndex < gamescopeRows - 1) Spacer(Modifier.height(cardSpacing)) + } Spacer(Modifier.height(4.dp + navBarBottomPadding)) } } @@ -339,6 +391,7 @@ private fun SectionLabel(text: String) { @Composable private fun AddContainerCard( + label: String, onClick: () -> Unit, navRow: Int, navCol: Int, @@ -385,7 +438,7 @@ private fun AddContainerCard( } Spacer(Modifier.height(8.dp)) Text( - text = stringResource(R.string.containers_list_new), + text = label, color = ContainersTextSecondary, fontSize = 12.sp, fontWeight = FontWeight.Bold, @@ -401,8 +454,8 @@ private fun ContainerCard( navCol: Int, onRun: () -> Unit, onEdit: () -> Unit, - onDuplicate: () -> Unit, - onInstallComponents: () -> Unit, + onDuplicate: (() -> Unit)?, + onInstallComponents: (() -> Unit)?, onRemove: () -> Unit, onShowInfo: () -> Unit, ) { @@ -434,15 +487,17 @@ private fun ContainerCard( tint = ContainersTextSecondary, ) Spacer(Modifier.weight(1f)) - SmallVectorIconButton( - image = ComponentContainerIcon, - contentDescription = "Install components", - tint = ContainersAccent, - onClick = onInstallComponents, - navRow = navRow, - navCol = navCol, - ) - Spacer(Modifier.width(8.dp)) + if (onInstallComponents != null) { + SmallVectorIconButton( + image = ComponentContainerIcon, + contentDescription = "Install components", + tint = ContainersAccent, + onClick = onInstallComponents, + navRow = navRow, + navCol = navCol, + ) + Spacer(Modifier.width(8.dp)) + } Box { SmallVectorIconButton( image = Icons.Outlined.MoreVert, @@ -457,13 +512,15 @@ private fun ContainerCard( onDismissRequest = { menuExpanded = false }, containerColor = ContainersCard, ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.common_ui_duplicate), color = ContainersTextPrimary) }, - onClick = { - menuExpanded = false - onDuplicate() - }, - ) + if (onDuplicate != null) { + DropdownMenuItem( + text = { Text(stringResource(R.string.common_ui_duplicate), color = ContainersTextPrimary) }, + onClick = { + menuExpanded = false + onDuplicate() + }, + ) + } DropdownMenuItem( text = { Text(stringResource(R.string.container_config_storage_info), color = ContainersTextPrimary) }, onClick = { diff --git a/app/src/main/feature/settings/contents/ComponentsScreen.kt b/app/src/main/feature/settings/contents/ComponentsScreen.kt index 76919eb37..088366a04 100644 --- a/app/src/main/feature/settings/contents/ComponentsScreen.kt +++ b/app/src/main/feature/settings/contents/ComponentsScreen.kt @@ -111,6 +111,18 @@ data class ComponentItem( val isOfficial: Boolean = false, ) +data class LinuxComponentItem( + val key: String, + val type: ContentProfile.ContentType, + val verName: String, + val isInstalled: Boolean, + val hasRemote: Boolean, + val sizeBytes: Long? = null, + val isOfficial: Boolean = false, + val isWorking: Boolean = false, + val progress: Float = Float.NaN, +) + data class ComponentsDownloadProgress( val title: String, val message: String, @@ -122,10 +134,19 @@ data class ComponentsConflict( val path: String, ) +enum class ComponentsPlatform { + ANDROID, + LINUX, +} + data class ComponentsState( + val platform: ComponentsPlatform = ComponentsPlatform.ANDROID, val currentType: ContentProfile.ContentType = ContentProfile.ContentType.CONTENT_TYPE_WINE, val installed: List<ComponentItem> = emptyList(), val available: List<ComponentItem> = emptyList(), + val linuxCurrentType: ContentProfile.ContentType = ContentProfile.ContentType.CONTENT_TYPE_PROTON, + val linuxInstalled: List<LinuxComponentItem> = emptyList(), + val linuxAvailable: List<LinuxComponentItem> = emptyList(), val downloadProgress: ComponentsDownloadProgress? = null, val conflict: ComponentsConflict? = null, val autoCreateContainer: Boolean = true, @@ -139,15 +160,20 @@ data class ComponentsState( fun ComponentsScreen( bridge: SettingsNavBridge? = null, state: ComponentsState, + onPlatformSelected: (ComponentsPlatform) -> Unit, onTypeSelected: (ContentProfile.ContentType) -> Unit, onInstallFromFile: () -> Unit, onDownloadItem: (ComponentItem) -> Unit, onRemoveItem: (ComponentItem) -> Unit, + onDownloadLinuxItem: (LinuxComponentItem) -> Unit, + onRemoveLinuxItem: (LinuxComponentItem) -> Unit, + onCancelLinuxItem: (LinuxComponentItem) -> Unit, onDismissConflict: () -> Unit, onToggleAutoCreateContainer: (Boolean) -> Unit, onRefresh: () -> Unit, ) { var itemPendingRemoval by remember { mutableStateOf<ComponentItem?>(null) } + var linuxItemPendingRemoval by remember { mutableStateOf<LinuxComponentItem?>(null) } val layoutDirection = LocalLayoutDirection.current val navBarPadding = WindowInsets.navigationBars.asPaddingValues() val navBarStartPadding = navBarPadding.calculateStartPadding(layoutDirection) @@ -155,7 +181,6 @@ fun ComponentsScreen( val navBarBottomPadding = navBarPadding.calculateBottomPadding() val contentNav = rememberSettingsContentNav(bridge) - // L1/R1 cycle the component type (Wine -> Proton -> DXVK โ€ฆ) while navigating the list. val sectionSignal = bridge?.contentSectionSignal ?: 0 var lastSectionSignal by remember { mutableStateOf(sectionSignal) } LaunchedEffect(sectionSignal) { @@ -163,9 +188,13 @@ fun ComponentsScreen( lastSectionSignal = sectionSignal val dir = bridge?.contentSectionDir ?: 0 if (dir != 0) { - val types = ContentProfile.ContentType.values() - val idx = types.indexOf(state.currentType).coerceAtLeast(0) - onTypeSelected(types[((idx + dir) % types.size + types.size) % types.size]) + if (state.platform == ComponentsPlatform.ANDROID) { + val types = ContentProfile.ContentType.values() + val idx = types.indexOf(state.currentType).coerceAtLeast(0) + onTypeSelected(types[((idx + dir) % types.size + types.size) % types.size]) + } else { + onPlatformSelected(ComponentsPlatform.ANDROID) + } } } } @@ -192,6 +221,28 @@ fun ComponentsScreen( } } + linuxItemPendingRemoval?.let { item -> + val nav = remember { PaneNavRegistry() } + Dialog(onDismissRequest = { linuxItemPendingRemoval = null }) { + DialogPaneNav(nav, onDismiss = { linuxItemPendingRemoval = null }) + CompositionLocalProvider(LocalPaneNav provides nav) { + PopupDialog( + title = stringResource(R.string.settings_content_remove_title), + message = stringResource(R.string.settings_content_confirm_remove), + confirmLabel = stringResource(R.string.common_ui_remove), + modifier = Modifier.widthIn(min = 280.dp, max = 360.dp), + icon = Icons.Outlined.Delete, + accentColor = DangerRed, + onCancel = { linuxItemPendingRemoval = null }, + onConfirm = { + onRemoveLinuxItem(item) + linuxItemPendingRemoval = null + }, + ) + } + } + } + state.downloadProgress?.let { progress -> DownloadProgressDialog(progress = progress) } @@ -214,6 +265,11 @@ fun ComponentsScreen( } } + val installedCount = + if (state.platform == ComponentsPlatform.LINUX) state.linuxInstalled.size else state.installed.size + val availableCount = + if (state.platform == ComponentsPlatform.LINUX) state.linuxAvailable.size else state.available.size + CompositionLocalProvider(LocalPaneNav provides contentNav) { Column( modifier = @@ -230,50 +286,91 @@ fun ComponentsScreen( verticalArrangement = Arrangement.spacedBy(8.dp), ) { HeroHeader( - installedCount = state.installed.size, - availableCount = state.available.size, + installedCount = installedCount, + availableCount = availableCount, + platform = state.platform, currentType = state.currentType, + linuxCurrentType = state.linuxCurrentType, autoCreateContainer = state.autoCreateContainer, isRefreshing = state.isRefreshing, loadFailed = state.loadFailed, + onPlatformSelected = onPlatformSelected, onTypeSelected = onTypeSelected, onInstallFromFile = onInstallFromFile, onToggleAutoCreateContainer = onToggleAutoCreateContainer, onRefresh = onRefresh, ) - if (state.installed.isEmpty() && state.available.isEmpty() && !state.isRefreshing) { - EmptyState() - } + if (state.platform == ComponentsPlatform.LINUX) { + if (state.linuxInstalled.isEmpty() && state.linuxAvailable.isEmpty() && !state.isRefreshing) { + EmptyState() + } else { + if (state.linuxInstalled.isNotEmpty()) { + SectionLabel( + text = stringResource(R.string.common_ui_installed), + modifier = Modifier.padding(top = 8.dp), + ) + state.linuxInstalled.forEach { item -> + key("linux_installed_${item.key}") { + LinuxComponentItemCard( + item = item, + onDownload = { onDownloadLinuxItem(item) }, + onRemove = { linuxItemPendingRemoval = item }, + onCancel = { onCancelLinuxItem(item) }, + ) + } + } + } - if (state.installed.isNotEmpty()) { - SectionLabel( - text = stringResource(R.string.common_ui_installed), - modifier = Modifier.padding(top = 8.dp), - ) - state.installed.forEach { item -> - key("installed_${state.currentType.name}_${item.key}") { - ComponentItemCard( - item = item, - onDownload = { onDownloadItem(item) }, - onRemove = { itemPendingRemoval = item }, + if (state.linuxAvailable.isNotEmpty()) { + SectionLabel( + text = stringResource(R.string.common_ui_available), + modifier = Modifier.padding(top = 6.dp), ) + state.linuxAvailable.forEach { item -> + key("linux_available_${item.key}") { + LinuxComponentItemCard( + item = item, + onDownload = { onDownloadLinuxItem(item) }, + onRemove = { linuxItemPendingRemoval = item }, + onCancel = { onCancelLinuxItem(item) }, + ) + } + } + } + } + } else if (state.installed.isEmpty() && state.available.isEmpty() && !state.isRefreshing) { + EmptyState() + } else { + if (state.installed.isNotEmpty()) { + SectionLabel( + text = stringResource(R.string.common_ui_installed), + modifier = Modifier.padding(top = 8.dp), + ) + state.installed.forEach { item -> + key("installed_${state.currentType.name}_${item.key}") { + ComponentItemCard( + item = item, + onDownload = { onDownloadItem(item) }, + onRemove = { itemPendingRemoval = item }, + ) + } } } - } - if (state.available.isNotEmpty()) { - SectionLabel( - text = stringResource(R.string.common_ui_available), - modifier = Modifier.padding(top = 6.dp), - ) - state.available.forEach { item -> - key("available_${state.currentType.name}_${item.key}") { - ComponentItemCard( - item = item, - onDownload = { onDownloadItem(item) }, - onRemove = { itemPendingRemoval = item }, - ) + if (state.available.isNotEmpty()) { + SectionLabel( + text = stringResource(R.string.common_ui_available), + modifier = Modifier.padding(top = 6.dp), + ) + state.available.forEach { item -> + key("available_${state.currentType.name}_${item.key}") { + ComponentItemCard( + item = item, + onDownload = { onDownloadItem(item) }, + onRemove = { itemPendingRemoval = item }, + ) + } } } } @@ -289,10 +386,13 @@ fun ComponentsScreen( private fun HeroHeader( installedCount: Int, availableCount: Int, + platform: ComponentsPlatform, currentType: ContentProfile.ContentType, + linuxCurrentType: ContentProfile.ContentType, autoCreateContainer: Boolean, isRefreshing: Boolean, loadFailed: Boolean, + onPlatformSelected: (ComponentsPlatform) -> Unit, onTypeSelected: (ContentProfile.ContentType) -> Unit, onInstallFromFile: () -> Unit, onToggleAutoCreateContainer: (Boolean) -> Unit, @@ -314,13 +414,17 @@ private fun HeroHeader( CountPill(label = stringResource(R.string.common_ui_available), count = availableCount) } val toggle: @Composable (Modifier) -> Unit = { toggleModifier -> - ToggleChip( - label = stringResource(R.string.settings_content_auto_create_container), - enabled = autoCreateContainer, - compact = true, - modifier = toggleModifier, - onToggle = { onToggleAutoCreateContainer(!autoCreateContainer) }, - ) + if (platform == ComponentsPlatform.ANDROID) { + ToggleChip( + label = stringResource(R.string.settings_content_auto_create_container), + enabled = autoCreateContainer, + compact = true, + modifier = toggleModifier, + onToggle = { onToggleAutoCreateContainer(!autoCreateContainer) }, + ) + } else { + Spacer(toggleModifier) + } } val refresh: @Composable () -> Unit = { RefreshChip( @@ -330,13 +434,15 @@ private fun HeroHeader( ) } val install: @Composable () -> Unit = { - SmallPillButton( - label = stringResource(R.string.settings_content_install), - icon = Icons.Outlined.Upload, - tint = Accent, - compact = true, - onClick = onInstallFromFile, - ) + if (platform == ComponentsPlatform.ANDROID) { + SmallPillButton( + label = stringResource(R.string.settings_content_install), + icon = Icons.Outlined.Upload, + tint = Accent, + compact = true, + onClick = onInstallFromFile, + ) + } } if (isPortraitLayout()) { @@ -385,8 +491,14 @@ private fun HeroHeader( } Spacer(Modifier.height(12.dp)) + PlatformTabsContent( + platform = platform, + onPlatformSelected = onPlatformSelected, + ) + Spacer(Modifier.height(10.dp)) TypeTabsContent( - currentType = currentType, + currentType = if (platform == ComponentsPlatform.LINUX) linuxCurrentType else currentType, + types = if (platform == ComponentsPlatform.LINUX) LINUX_CONTENT_TYPES else ContentProfile.ContentType.values().toList(), onTypeSelected = onTypeSelected, ) } @@ -527,11 +639,73 @@ private fun CountPill( } } -// Content type tabs +@Composable +private fun PlatformTabsContent( + platform: ComponentsPlatform, + onPlatformSelected: (ComponentsPlatform) -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + PlatformTabChip( + label = "${stringResource(R.string.settings_drivers_android)} ${stringResource(R.string.settings_content_components)}", + selected = platform == ComponentsPlatform.ANDROID, + onClick = { onPlatformSelected(ComponentsPlatform.ANDROID) }, + modifier = Modifier.weight(1f), + ) + Spacer(Modifier.width(8.dp)) + PlatformTabChip( + label = "${stringResource(R.string.settings_drivers_linux)} ${stringResource(R.string.settings_content_components)}", + selected = platform == ComponentsPlatform.LINUX, + onClick = { onPlatformSelected(ComponentsPlatform.LINUX) }, + modifier = Modifier.weight(1f), + ) + } +} + +@Composable +private fun PlatformTabChip( + label: String, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val background = if (selected) Accent.copy(alpha = 0.18f) else SurfaceDark + val borderColor = if (selected) Accent.copy(alpha = 0.45f) else CardBorder + val textColor = if (selected) Accent else TextSecondary + Box( + modifier = + modifier + .height(34.dp) + .clip(RoundedCornerShape(10.dp)) + .background(background) + .border(1.dp, borderColor, RoundedCornerShape(10.dp)) + .paneNavItem( + cornerRadius = 10.dp, + onActivate = onClick, + highlightColor = NavHighlight, + tapToSelect = true, + ) + .padding(horizontal = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + color = textColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Ellipsis, + ) + } +} @Composable private fun TypeTabsContent( currentType: ContentProfile.ContentType, + types: List<ContentProfile.ContentType>, onTypeSelected: (ContentProfile.ContentType) -> Unit, ) { Column(modifier = Modifier.fillMaxWidth()) { @@ -542,7 +716,6 @@ private fun TypeTabsContent( .horizontalScroll(rememberScrollState()), verticalAlignment = Alignment.CenterVertically, ) { - val types = ContentProfile.ContentType.values() types.forEachIndexed { index, type -> TypeTabChip( label = type.toString(), @@ -587,6 +760,8 @@ private fun descriptionResFor(type: ContentProfile.ContentType): Int = ContentProfile.ContentType.CONTENT_TYPE_D7VK -> R.string.settings_content_desc_d7vk } +private val LINUX_CONTENT_TYPES = listOf(ContentProfile.ContentType.CONTENT_TYPE_PROTON) + @Composable private fun TypeTabChip( label: String, @@ -716,8 +891,8 @@ private fun ComponentItemCard( OfficialBadge(Modifier.fillMaxHeight()) Spacer(Modifier.width(8.dp)) } - if (isSteamCompatible(item)) { - SteamCompatBadge(Modifier.fillMaxHeight()) + if (isOnlineCapable(item)) { + OnlineCapableBadge(Modifier.fillMaxHeight()) Spacer(Modifier.width(8.dp)) } if (item.isInstalled) { @@ -748,6 +923,130 @@ private fun ComponentItemCard( } } +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun LinuxComponentItemCard( + item: LinuxComponentItem, + onDownload: () -> Unit, + onRemove: () -> Unit, + onCancel: () -> Unit, +) { + Box( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(CardDark) + .border(1.dp, CardBorder, RoundedCornerShape(12.dp)), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 11.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(38.dp) + .clip(RoundedCornerShape(10.dp)) + .background(IconBoxBg), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = iconFor(item.type), + contentDescription = null, + tint = Accent, + modifier = Modifier.size(20.dp), + ) + } + Spacer(Modifier.width(13.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = item.verName, + color = TextPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.basicMarquee( + iterations = Int.MAX_VALUE, + initialDelayMillis = 5000, + repeatDelayMillis = 5000, + velocity = 25.dp, + spacing = MarqueeSpacing(40.dp), + ), + ) + val sizeLabel = formatSizeLabel(item) + if (sizeLabel != null) { + Text( + text = sizeLabel, + color = TextSecondary, + fontSize = 11.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + Spacer(Modifier.width(8.dp)) + Row( + modifier = Modifier.height(IntrinsicSize.Min), + verticalAlignment = Alignment.CenterVertically, + ) { + if (item.isOfficial) { + OfficialBadge(Modifier.fillMaxHeight()) + Spacer(Modifier.width(8.dp)) + } + when { + item.isWorking -> { + CircularProgressIndicator( + modifier = Modifier.size(22.dp), + strokeWidth = 2.dp, + color = Accent, + progress = { if (item.progress.isFinite()) item.progress.coerceIn(0f, 1f) else 0f }, + ) + // These builds are several hundred megabytes; without this a mistaken tap + // can only be stopped by killing the app. + Spacer(Modifier.width(8.dp)) + SmallPillButton( + label = stringResource(R.string.common_ui_cancel), + icon = null, + tint = DangerRed, + compact = true, + onClick = onCancel, + ) + } + item.isInstalled -> { + IconTapButton( + icon = Icons.Outlined.Delete, + tint = DangerRed, + onClick = onRemove, + ) + } + item.hasRemote -> { + SmallPillButton( + label = stringResource(R.string.common_ui_download), + icon = Icons.Outlined.Download, + tint = Accent, + compact = true, + onClick = onDownload, + ) + } + else -> { + Icon( + imageVector = Icons.Outlined.CloudDownload, + contentDescription = null, + tint = TextSecondary.copy(alpha = 0.5f), + modifier = Modifier.size(18.dp), + ) + } + } + } + } + } +} + // Generic small controls @Composable @@ -780,9 +1079,15 @@ private fun IconTapButton( } } -private fun isSteamCompatible(item: ComponentItem): Boolean = - item.verName.contains("steam", ignoreCase = true) || - item.key.contains("steam", ignoreCase = true) +private const val ONLINE_CAPABLE_MARKER = "online" + +// Shared with the Contents list, which orders online-capable layers first. Only a layer that +// names itself online qualifies; a Steam-enabled build is not automatically online-capable. +internal fun isOnlineCapableName(vararg fields: String?): Boolean = + fields.any { it != null && it.contains(ONLINE_CAPABLE_MARKER, ignoreCase = true) } + +private fun isOnlineCapable(item: ComponentItem): Boolean = + isOnlineCapableName(item.verName, item.key) // Badge marking first-party "WinNative" builds. A perfect square (width follows // the filled height) in WinNative blue, carrying only the WinNative logo for @@ -806,7 +1111,7 @@ private fun OfficialBadge(modifier: Modifier = Modifier) { } @Composable -private fun SteamCompatBadge(modifier: Modifier = Modifier) { +private fun OnlineCapableBadge(modifier: Modifier = Modifier) { Row( modifier = modifier .clip(RoundedCornerShape(8.dp)) @@ -816,7 +1121,7 @@ private fun SteamCompatBadge(modifier: Modifier = Modifier) { verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "Steam", + text = stringResource(R.string.settings_content_badge_online), color = SuccessGreen, fontSize = 11.sp, fontWeight = FontWeight.SemiBold, @@ -953,6 +1258,13 @@ private fun formatSizeLabel(item: ComponentItem): String? { return "${stringResource(R.string.common_ui_size)}: ${formatBytes(bytes)}" } +@Composable +private fun formatSizeLabel(item: LinuxComponentItem): String? { + val bytes = item.sizeBytes ?: return "${stringResource(R.string.common_ui_size)}: --" + if (bytes <= 0L) return null + return "${stringResource(R.string.common_ui_size)}: ${formatBytes(bytes)}" +} + private fun formatBytes(bytes: Long): String { val units = listOf("B", "KB", "MB", "GB") var value = bytes.toDouble() diff --git a/app/src/main/feature/settings/contents/ContentsFragment.kt b/app/src/main/feature/settings/contents/ContentsFragment.kt index c07efd7c5..65fe38094 100644 --- a/app/src/main/feature/settings/contents/ContentsFragment.kt +++ b/app/src/main/feature/settings/contents/ContentsFragment.kt @@ -11,8 +11,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.runtime.collectAsState import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy @@ -26,6 +28,7 @@ import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.content.ContentProfile import com.winlator.cmod.runtime.content.ContentsManager import com.winlator.cmod.runtime.content.Downloader +import com.winlator.cmod.runtime.linux.LinuxProtons import com.winlator.cmod.shared.ui.toast.WinToast import com.winlator.cmod.shared.android.DirectoryPickerDialog import com.winlator.cmod.shared.io.FileUtils @@ -43,6 +46,7 @@ class ContentsFragment : Fragment() { private lateinit var manager: ContentsManager private var componentsState by mutableStateOf(ComponentsState()) + private var currentPlatform = ComponentsPlatform.ANDROID private var currentContentType = ContentProfile.ContentType.CONTENT_TYPE_WINE private var profilesByKey = emptyMap<String, ContentProfile>() @@ -63,6 +67,11 @@ class ContentsFragment : Fragment() { super.onCreate(savedInstanceState) manager = ContentsManager(requireContext()) + savedInstanceState + ?.getString(STATE_COMPONENTS_PLATFORM) + ?.let { name -> runCatching { ComponentsPlatform.valueOf(name) }.getOrNull() } + ?.let { currentPlatform = it } + savedInstanceState ?.getString(STATE_CONTENT_TYPE) ?.let(ContentProfile.ContentType::getTypeByName) @@ -93,9 +102,31 @@ class ContentsFragment : Fragment() { surface = Color(0xFF1E252E), ), ) { + val linuxProtons by LinuxProtons.state.collectAsState() + val linuxBuildsById = linuxProtons.builds.associateBy { it.id } + val linuxInstalled = + linuxProtons + .builds + .filter { it.id in linuxProtons.installed } + .map { build -> build.toLinuxComponentItem(linuxProtons) } + val linuxAvailable = + linuxProtons + .builds + .filterNot { it.id in linuxProtons.installed } + .map { build -> build.toLinuxComponentItem(linuxProtons) } + LaunchedEffect(currentPlatform) { + if (currentPlatform == ComponentsPlatform.LINUX) { + LinuxProtons.refresh(ctx) + } + } ComponentsScreen( bridge = (requireActivity() as? UnifiedActivity)?.settingsNavBridge, - state = componentsState, + state = + componentsState.copy( + linuxInstalled = linuxInstalled, + linuxAvailable = linuxAvailable, + ), + onPlatformSelected = { platform -> selectPlatform(platform) }, onTypeSelected = { type -> selectContentType(type) }, onInstallFromFile = { promptInstallFromFile() }, onDownloadItem = { item -> @@ -104,6 +135,13 @@ class ContentsFragment : Fragment() { onRemoveItem = { item -> profilesByKey[item.key]?.let { onRemoveRequested(it) } }, + onDownloadLinuxItem = { item -> + linuxBuildsById[item.key]?.let { LinuxProtons.install(ctx, it) } + }, + onRemoveLinuxItem = { item -> + linuxBuildsById[item.key]?.let { LinuxProtons.remove(ctx, it) } + }, + onCancelLinuxItem = { LinuxProtons.cancel() }, onDismissConflict = { conflictingContentPath = null publishState() @@ -117,7 +155,13 @@ class ContentsFragment : Fragment() { .apply() publishState() }, - onRefresh = { refreshRemoteProfiles() }, + onRefresh = { + if (currentPlatform == ComponentsPlatform.LINUX) { + LinuxProtons.refresh(ctx) + } else { + refreshRemoteProfiles() + } + }, ) } } @@ -147,6 +191,7 @@ class ContentsFragment : Fragment() { } override fun onSaveInstanceState(outState: Bundle) { + outState.putString(STATE_COMPONENTS_PLATFORM, currentPlatform.name) outState.putString(STATE_CONTENT_TYPE, currentContentType.toString()) super.onSaveInstanceState(outState) } @@ -164,25 +209,20 @@ class ContentsFragment : Fragment() { publishState() } + private fun selectPlatform(platform: ComponentsPlatform) { + if (platform == currentPlatform) return + currentPlatform = platform + if (platform == ComponentsPlatform.LINUX) { + LinuxProtons.refresh(requireContext()) + } + publishState() + } + private fun publishState() { val profiles = manager.getProfiles(currentContentType).orEmpty() - val installed = - profiles - .filter { it.isInstalled } - .sortedWith( - compareByDescending<ContentProfile> { it.isOfficial } - .thenBy { it.verName.lowercase() } - .thenByDescending { it.verCode }, - ) - val available = - profiles - .filterNot { it.isInstalled } - .sortedWith( - compareByDescending<ContentProfile> { it.isOfficial } - .thenBy { it.verName.lowercase() } - .thenByDescending { it.verCode }, - ) + val installed = profiles.filter { it.isInstalled }.sortedWith(CONTENT_ORDER) + val available = profiles.filterNot { it.isInstalled }.sortedWith(CONTENT_ORDER) val keyedProfiles = linkedMapOf<String, ContentProfile>() val installedItems = @@ -201,6 +241,7 @@ class ContentsFragment : Fragment() { profilesByKey = keyedProfiles componentsState = ComponentsState( + platform = currentPlatform, currentType = currentContentType, installed = installedItems, available = availableItems, @@ -269,6 +310,19 @@ class ContentsFragment : Fragment() { ) } + private fun LinuxProtons.Build.toLinuxComponentItem(state: LinuxProtons.State): LinuxComponentItem = + LinuxComponentItem( + key = id, + type = ContentProfile.ContentType.CONTENT_TYPE_PROTON, + verName = displayName, + isInstalled = id in state.installed, + hasRemote = true, + sizeBytes = size, + isOfficial = false, + isWorking = state.working == id, + progress = state.progress, + ) + private fun scheduleRemoteSizeFetches(items: List<ComponentItem>) { val urlsToFetch = items @@ -675,8 +729,41 @@ class ContentsFragment : Fragment() { } companion object { + private const val STATE_COMPONENTS_PLATFORM = "state_components_platform" private const val STATE_CONTENT_TYPE = "state_content_type" private const val TAG = "ContentsFragment" private const val PREF_AUTO_CREATE_CONTAINER = "components_auto_create_container" } } + +// Online-capable layers first, then everything else; within each group newest version first. +// The version is the first numeric run in the version name, which for remote entries is +// prefixed by the type ("Proton-11.0-2-arm64ec" -> 11.0.2). Compared component by component, +// so 11.0-2.1 outranks 11.0-2 and 11.10 outranks 11.9. +private val VERSION_RUN = Regex("""\d+(?:[.\-]\d+)*""") + +private fun versionParts(verName: String?): List<Int> = + VERSION_RUN.find(verName.orEmpty()) + ?.value + ?.split('.', '-') + ?.mapNotNull(String::toIntOrNull) + .orEmpty() + +private fun compareVersionDescending(a: ContentProfile, b: ContentProfile): Int { + val left = versionParts(a.verName) + val right = versionParts(b.verName) + for (i in 0 until maxOf(left.size, right.size)) { + // A missing component ranks below a present one, so 11.0-2 follows 11.0-2.1. + val l = left.getOrElse(i) { -1 } + val r = right.getOrElse(i) { -1 } + if (l != r) return r.compareTo(l) + } + return 0 +} + +private val CONTENT_ORDER: Comparator<ContentProfile> = + compareByDescending<ContentProfile> { isOnlineCapableName(it.verName, it.remoteUrl) } + .thenComparator(::compareVersionDescending) + .thenByDescending { it.verCode } + .thenByDescending { it.isOfficial } + .thenBy { it.verName.orEmpty().lowercase() } diff --git a/app/src/main/feature/settings/debug/DebugFragment.kt b/app/src/main/feature/settings/debug/DebugFragment.kt index 928b42337..7f3dc2836 100644 --- a/app/src/main/feature/settings/debug/DebugFragment.kt +++ b/app/src/main/feature/settings/debug/DebugFragment.kt @@ -389,11 +389,12 @@ class DebugFragment : Fragment() { dest: File, files: Array<File>, ) { + val ctx = requireContext() ZipOutputStream(FileOutputStream(dest)).use { zos -> files.forEach { file -> if (file.isFile) { - zos.putNextEntry(ZipEntry(file.name)) - file.inputStream().use { it.copyTo(zos) } + zos.putNextEntry(ZipEntry(com.winlator.cmod.runtime.system.LogManager.archiveName(ctx, file))) + com.winlator.cmod.runtime.system.LogManager.copyShareable(ctx, file, zos) zos.closeEntry() } } @@ -490,8 +491,16 @@ class DebugFragment : Fragment() { return } try { + // Never the file itself: the Steam client's logs carry its account's session token, + // and what leaves here is the copy that has had it taken out. One directory of its + // own, emptied first, so two logs of the same name cannot be confused for each other. + val outbox = File(ctx.cacheDir, "shared-logs") + outbox.deleteRecursively() + outbox.mkdirs() + val shared = File(outbox, file.name) + FileOutputStream(shared).use { com.winlator.cmod.runtime.system.LogManager.copyShareable(ctx, file, it) } val authority = "${ctx.packageName}.tileprovider" - val uri = FileProvider.getUriForFile(ctx, authority, file) + val uri = FileProvider.getUriForFile(ctx, authority, shared) val shareIntent = Intent(Intent.ACTION_SEND).apply { type = "text/plain" @@ -529,7 +538,9 @@ class DebugFragment : Fragment() { } return try { val dest = File(logsDownloadDir(), exportFileName(file)) - file.inputStream().use { input -> FileOutputStream(dest).use { input.copyTo(it) } } + FileOutputStream(dest).use { + com.winlator.cmod.runtime.system.LogManager.copyShareable(ctx, file, it) + } markLogDownloaded(file) "/WinNative/logs/${dest.name}" } catch (e: Exception) { diff --git a/app/src/main/feature/settings/drivers/DriversFragment.kt b/app/src/main/feature/settings/drivers/DriversFragment.kt index 03916cf00..55d901a7e 100644 --- a/app/src/main/feature/settings/drivers/DriversFragment.kt +++ b/app/src/main/feature/settings/drivers/DriversFragment.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -22,9 +21,12 @@ import com.winlator.cmod.app.shell.UnifiedActivity import com.winlator.cmod.feature.setup.SetupWizardActivity import com.winlator.cmod.runtime.content.AdrenotoolsManager import com.winlator.cmod.runtime.content.Downloader -import com.winlator.cmod.shared.ui.toast.WinToast +import com.winlator.cmod.runtime.content.DriverPackages +import com.winlator.cmod.runtime.content.DriverPackages.Platform +import com.winlator.cmod.runtime.linux.LinuxRuntime import com.winlator.cmod.shared.android.DirectoryPickerDialog import com.winlator.cmod.shared.theme.WinNativeTheme +import com.winlator.cmod.shared.ui.toast.WinToast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -42,6 +44,7 @@ class DriversFragment : Fragment() { private lateinit var adrenotoolsManager: AdrenotoolsManager private lateinit var preferences: SharedPreferences + private var platform = Platform.ANDROID private var driversState by mutableStateOf(DriversState()) private val releasesBySource = linkedMapOf<String, List<DriverReleaseItem>>() @@ -55,6 +58,8 @@ class DriversFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + platform = + savedInstanceState?.getString("driverPlatform")?.let { runCatching { Platform.valueOf(it) }.getOrNull() } ?: Platform.ANDROID adrenotoolsManager = AdrenotoolsManager(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) } @@ -90,7 +95,16 @@ class DriversFragment : Fragment() { ) { DriversScreen( state = driversState, - onInstallFromFile = { promptInstallDriverFromFile() }, + onInstallFromFile = { selected -> + selectPlatform(selected) + promptInstallDriverFromFile() + }, + onPlatformSelected = { selectPlatform(it) }, + onSelectDriver = { driver -> + DriverPackages.selectLinux(requireContext(), driver.id) + refreshInstalledDrivers() + publishState() + }, onSourceTapped = { source -> onSourceSelected(source) }, onReleaseTapped = { release -> expandedReleaseId = if (expandedReleaseId == release.id) null else release.id @@ -98,7 +112,11 @@ class DriversFragment : Fragment() { }, onDownloadAsset = { asset -> downloadReleaseAsset(asset) }, onRemoveDriver = { driver -> - adrenotoolsManager.removeDriver(driver.id) + if (platform == Platform.LINUX) { + DriverPackages.removeLinux(requireContext(), driver.id) + } else { + adrenotoolsManager.removeDriver(driver.id) + } refreshInstalledDrivers() publishState() }, @@ -135,12 +153,27 @@ class DriversFragment : Fragment() { } } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString("driverPlatform", platform.name) + } + override fun onResume() { super.onResume() refreshInstalledDrivers() publishState() } + private fun selectPlatform(selected: Platform) { + if (platform == selected) return + platform = selected + expandedSourceApiUrl = null + expandedReleaseId = null + loadRepos() + refreshInstalledDrivers() + publishState() + } + private fun promptInstallDriverFromFile() { val activity = activity ?: return DirectoryPickerDialog.showFile( @@ -157,9 +190,16 @@ class DriversFragment : Fragment() { val hasMissingDefaults = defaultRepoList().any { it.apiUrl !in existingApiUrls } driversState = DriversState( + platform = platform, installedDrivers = installedDrivers, sources = sources.toList(), - releasesBySource = releasesBySource.toMap(), + releasesBySource = + releasesBySource.mapValues { (_, releases) -> + releases.mapNotNull { release -> + val assets = release.assets.filter { (it.name.startsWith("WN-Linux-")) == (platform == Platform.LINUX) } + if (assets.isEmpty()) null else release.copy(assets = assets) + } + }, expandedSourceApiUrl = expandedSourceApiUrl, expandedReleaseId = expandedReleaseId, loadingSourceApiUrl = loadingSourceApiUrl, @@ -170,6 +210,57 @@ class DriversFragment : Fragment() { } private fun refreshInstalledDrivers() { + if (platform == Platform.LINUX) { + val context = requireContext() + val selected = DriverPackages.selectedLinux(context) + val custom = + DriverPackages.linuxDrivers(context).mapNotNull { directory -> + runCatching { + val meta = JSONObject(File(directory, "meta.json").readText()) + InstalledDriverItem( + id = directory.name, + name = meta.getString("name"), + version = meta.optString("driverVersion"), + selected = + selected == directory.name, + ) to + meta.optString("sourceAsset") + }.getOrNull() + } + installedDrivers = + buildList { + add( + InstalledDriverItem( + id = DriverPackages.BUNDLED, + name = getString(R.string.settings_drivers_bundled), + version = LinuxRuntime.TURNIP_VERSION, + selected = + selected == DriverPackages.BUNDLED, + removable = false, + ), + ) + if (LinuxRuntime.downloadedDriverVersion(context) > 0) { + add( + InstalledDriverItem( + id = DriverPackages.LEGACY, + name = getString(R.string.settings_drivers_legacy), + version = + File(LinuxRuntime.driverDir(context), LinuxRuntime.DRIVER_NAME_FILE) + .takeIf { + it.isFile + }?.readText() + ?.trim() + .orEmpty(), + selected = + selected == DriverPackages.LEGACY, + ), + ) + } + addAll(custom.map { it.first }.sortedBy { it.name.lowercase(Locale.ROOT) }) + } + installedAssetNames = custom.map { it.second }.filter { it.isNotBlank() }.toSet() + return + } val driverIds = adrenotoolsManager.enumarateInstalledDrivers() installedDrivers = driverIds @@ -189,14 +280,27 @@ class DriversFragment : Fragment() { private fun defaultRepoList(): List<DriverRepo> = listOf( DriverRepo( - name = WINNATIVE_COMPONENTS_REPO_NAME, - repoUrl = WINNATIVE_COMPONENTS_REPO_URL, - apiUrl = WINNATIVE_COMPONENTS_API_URL, + name = "WinNative Drivers", + repoUrl = "https://github.com/WinNative-Emu/Drivers/releases", + apiUrl = "https://api.github.com/repos/WinNative-Emu/Drivers/releases", ), - ) + ) + + if (platform == Platform.LINUX) { + emptyList() + } else { + listOf( + DriverRepo( + name = WINNATIVE_COMPONENTS_REPO_NAME, + repoUrl = WINNATIVE_COMPONENTS_REPO_URL, + apiUrl = WINNATIVE_COMPONENTS_API_URL, + ), + ) + } + + private fun repoPreferenceKey() = if (platform == Platform.LINUX) "custom_linux_driver_repos" else "custom_driver_repos" private fun loadRepos() { - val jsonStr = preferences.getString("custom_driver_repos", null) + val jsonStr = preferences.getString(repoPreferenceKey(), null) val newSources = mutableListOf<DriverRepo>() if (jsonStr == null) { @@ -234,7 +338,7 @@ class DriversFragment : Fragment() { saveRepos() publishState() } else { - WinToast.show(requireContext(), "Default repositories already present") + WinToast.show(requireContext(), R.string.settings_drivers_defaults_present) } } @@ -250,7 +354,7 @@ class DriversFragment : Fragment() { } preferences .edit() - .putString("custom_driver_repos", array.toString()) + .putString(repoPreferenceKey(), array.toString()) .apply() } catch (e: Exception) { e.printStackTrace() @@ -304,7 +408,7 @@ class DriversFragment : Fragment() { if (loadingSourceApiUrl == source.apiUrl) { loadingSourceApiUrl = null } - releasesBySource[source.apiUrl] = releases + if (releases.isNotEmpty()) releasesBySource[source.apiUrl] = releases publishState() if (releases.isEmpty()) { @@ -345,28 +449,29 @@ class DriversFragment : Fragment() { return connection.useResponse { responseText -> val json = JSONArray(responseText) - val parsed = buildList { - for (index in 0 until json.length()) { - val releaseObject = json.optJSONObject(index) ?: continue - val assets = releaseObject.optJSONArray("assets").toZipAssets() - if (assets.isEmpty()) continue - - val tagName = releaseObject.optString("tag_name") - val releaseName = releaseObject.optString("name").ifBlank { tagName } - val publishedAt = releaseObject.optString("published_at") - val releaseNotes = releaseObject.optString("body").toReleaseNotes() - - add( - DriverReleaseItem( - id = releaseObject.optLong("id"), - title = releaseName.ifBlank { getString(R.string.common_ui_unnamed) }, - subtitle = buildReleaseSubtitle(tagName, publishedAt, assets.size), - notes = releaseNotes, - assets = assets, - ), - ) + val parsed = + buildList { + for (index in 0 until json.length()) { + val releaseObject = json.optJSONObject(index) ?: continue + val assets = releaseObject.optJSONArray("assets").toZipAssets() + if (assets.isEmpty()) continue + + val tagName = releaseObject.optString("tag_name") + val releaseName = releaseObject.optString("name").ifBlank { tagName } + val publishedAt = releaseObject.optString("published_at") + val releaseNotes = releaseObject.optString("body").toReleaseNotes() + + add( + DriverReleaseItem( + id = releaseObject.optLong("id"), + title = releaseName.ifBlank { getString(R.string.common_ui_unnamed) }, + subtitle = buildReleaseSubtitle(tagName, publishedAt, assets.size), + notes = releaseNotes, + assets = assets, + ), + ) + } } - } json.length() to parsed } } @@ -470,7 +575,8 @@ class DriversFragment : Fragment() { // interrupt it. installDriverPackage owns its own keep-alive scope. val keepAliveTag = "drivers_download_${asset.downloadUrl}" val appCtx = requireContext().applicationContext - com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(appCtx, keepAliveTag) + com.winlator.cmod.runtime.system.SessionKeepAliveService + .startDownload(appCtx, keepAliveTag) viewLifecycleOwner.lifecycleScope.launch { val output = File(requireContext().cacheDir, "driver_${System.currentTimeMillis()}.zip") val success = @@ -520,7 +626,8 @@ class DriversFragment : Fragment() { onComplete = { output.delete() }, ) } finally { - com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(appCtx, keepAliveTag) + com.winlator.cmod.runtime.system.SessionKeepAliveService + .stopDownload(appCtx, keepAliveTag) } } } @@ -530,6 +637,7 @@ class DriversFragment : Fragment() { sourceAssetName: String? = null, onComplete: (() -> Unit)? = null, ) { + val requestedPlatform = platform val installTitle = getString(R.string.settings_drivers_install) val installMessage = getString(R.string.settings_content_preparing_package) updateDownloadProgress( @@ -544,14 +652,15 @@ class DriversFragment : Fragment() { // move). Released after the lifecycleScope coroutine finishes. val installKeepAliveTag = "drivers_install_${sourceAssetName ?: uri}" val appCtx = requireContext().applicationContext - com.winlator.cmod.runtime.system.SessionKeepAliveService.startDownload(appCtx, installKeepAliveTag) + com.winlator.cmod.runtime.system.SessionKeepAliveService + .startDownload(appCtx, installKeepAliveTag) viewLifecycleOwner.lifecycleScope.launch { try { - val installedDriverId = + val installed = withContext(Dispatchers.IO) { runCatching { - adrenotoolsManager.installDriver(uri, sourceAssetName) - }.getOrDefault("") + DriverPackages.install(appCtx, uri, sourceAssetName, null) + }.getOrNull() } if (!isAdded || view == null) { @@ -563,16 +672,31 @@ class DriversFragment : Fragment() { clearDownloadProgress() onComplete?.invoke() - if (installedDriverId.isBlank()) { + if (installed == null) { WinToast.show(requireContext(), R.string.settings_drivers_install_failed) return@launch } - SetupWizardActivity.recordInstalledDriver(requireContext(), installedDriverId) + if (installed.platform == Platform.ANDROID) SetupWizardActivity.recordInstalledDriver(requireContext(), installed.id) + selectPlatform(installed.platform) + if (installed.platform != requestedPlatform) { + val destination = + getString( + if (installed.platform == + Platform.LINUX + ) { + R.string.settings_drivers_linux + } else { + R.string.settings_drivers_android + }, + ) + WinToast.show(requireContext(), getString(R.string.settings_drivers_routed, destination)) + } refreshInstalledDrivers() publishState() } finally { - com.winlator.cmod.runtime.system.SessionKeepAliveService.stopDownload(appCtx, installKeepAliveTag) + com.winlator.cmod.runtime.system.SessionKeepAliveService + .stopDownload(appCtx, installKeepAliveTag) } } } diff --git a/app/src/main/feature/settings/drivers/DriversScreen.kt b/app/src/main/feature/settings/drivers/DriversScreen.kt index 05b32524c..ed35d3ed8 100644 --- a/app/src/main/feature/settings/drivers/DriversScreen.kt +++ b/app/src/main/feature/settings/drivers/DriversScreen.kt @@ -85,6 +85,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import com.winlator.cmod.runtime.content.DriverPackages.Platform import com.winlator.cmod.R import com.winlator.cmod.shared.ui.dialog.PopupDialog import androidx.compose.ui.focus.FocusRequester @@ -125,6 +126,7 @@ private fun Modifier.noRippleClickable( // State data class DriversState( + val platform: Platform = Platform.ANDROID, val installedDrivers: List<InstalledDriverItem> = emptyList(), val sources: List<DriverRepo> = emptyList(), val releasesBySource: Map<String, List<DriverReleaseItem>> = emptyMap(), @@ -152,7 +154,8 @@ data class DownloadProgress( ) data class InstalledDriverItem( - val id: String, + val selected: Boolean = false, + val removable: Boolean = true, val id: String, val name: String, val version: String, ) @@ -182,7 +185,9 @@ data class DriverAssetItem( @Composable fun DriversScreen( state: DriversState, - onInstallFromFile: () -> Unit, + onInstallFromFile: (Platform) -> Unit, + onPlatformSelected: (Platform) -> Unit, + onSelectDriver: (InstalledDriverItem) -> Unit, onSourceTapped: (DriverRepo) -> Unit, onReleaseTapped: (DriverReleaseItem) -> Unit, onDownloadAsset: (DriverAssetItem) -> Unit, @@ -193,6 +198,7 @@ fun DriversScreen( onRestoreDefaultRepos: () -> Unit, bridge: SettingsNavBridge? = null, ) { + var showInstallDialog by remember { mutableStateOf(false) } var showAddRepoDialog by remember { mutableStateOf(false) } var editingRepo by remember { mutableStateOf<Pair<Int, DriverRepo>?>(null) } var driverPendingRemoval by remember { mutableStateOf<InstalledDriverItem?>(null) } @@ -204,6 +210,26 @@ fun DriversScreen( val navBarBottomPadding = navBarPadding.calculateBottomPadding() val contentNav = rememberSettingsContentNav(bridge) + if (showInstallDialog) { + val nav = remember { PaneNavRegistry() } + Dialog(onDismissRequest = { showInstallDialog = false }) { + DialogPaneNav(nav, onDismiss = { showInstallDialog = false }) + CompositionLocalProvider(LocalPaneNav provides nav) { + Column( + modifier = Modifier.widthIn(max = 360.dp).clip(RoundedCornerShape(14.dp)).background(CardDark).padding(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text(stringResource(R.string.settings_drivers_install), color = TextPrimary) + Text(stringResource(R.string.settings_drivers_platform_hint), color = TextSecondary) + PlatformOptions(state.platform) { platform -> + showInstallDialog = false + onInstallFromFile(platform) + } + } + } + } + } + if (showAddRepoDialog || editingRepo != null) { val editing = editingRepo RepoEditDialog( @@ -287,10 +313,11 @@ fun DriversScreen( ), verticalArrangement = Arrangement.spacedBy(8.dp), ) { + PlatformOptions(state.platform, onPlatformSelected) HeroHeader( installedCount = state.installedDrivers.size, repoCount = state.sources.size, - onInstall = onInstallFromFile, + onInstall = { showInstallDialog = true }, onAddRepo = { showAddRepoDialog = true }, ) @@ -308,6 +335,7 @@ fun DriversScreen( InstalledDriverCard( driver = driver, onRemove = { driverPendingRemoval = driver }, + onSelect = if (state.platform == Platform.LINUX) ({ onSelectDriver(driver) }) else null, ) } } @@ -326,7 +354,7 @@ fun DriversScreen( ) if (state.hasMissingDefaults) { SmallPillButton( - label = "Restore defaults", + label = stringResource(R.string.settings_drivers_restore_defaults), icon = Icons.Outlined.Restore, tint = Accent, onClick = onRestoreDefaultRepos, @@ -364,6 +392,25 @@ fun DriversScreen( } } +@Composable +private fun PlatformOptions(selected: Platform, onSelect: (Platform) -> Unit) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Platform.entries.forEach { platform -> + val label = stringResource(if (platform == Platform.ANDROID) R.string.settings_drivers_android else R.string.settings_drivers_linux) + Box( + modifier = Modifier.weight(1f).clip(RoundedCornerShape(10.dp)) + .background(if (selected == platform) Accent.copy(alpha = 0.22f) else CardDark) + .border(1.dp, if (selected == platform) Accent else CardBorder, RoundedCornerShape(10.dp)) + .paneNavItem(cornerRadius = 10.dp, onActivate = { onSelect(platform) }, highlightColor = NavHighlight, tapToSelect = true) + .padding(14.dp), + contentAlignment = Alignment.Center, + ) { + Text(label, color = if (selected == platform) Accent else TextPrimary, fontWeight = FontWeight.SemiBold) + } + } + } +} + // Hero header @Composable @@ -590,6 +637,7 @@ private fun SectionLabel( private fun InstalledDriverCard( driver: InstalledDriverItem, onRemove: () -> Unit, + onSelect: (() -> Unit)?, ) { var menuOpen by remember { mutableStateOf(false) } Box( @@ -643,7 +691,14 @@ private fun InstalledDriverCard( overflow = TextOverflow.Ellipsis, ) } - Box { + if (onSelect != null) { + HeroButton( + label = stringResource(if (driver.selected) R.string.settings_drivers_active else R.string.common_ui_select), + icon = Icons.Outlined.Check, + onClick = onSelect, + ) + } + if (driver.removable) Box { IconTapButton( icon = Icons.Outlined.MoreVert, tint = TextSecondary, @@ -665,7 +720,7 @@ private fun InstalledDriverCard( ) Spacer(Modifier.width(8.dp)) Text( - text = "Remove", + text = stringResource(R.string.common_ui_remove), color = DangerRed, fontSize = 13.sp, ) diff --git a/app/src/main/feature/settings/other/OtherSettingsFragment.kt b/app/src/main/feature/settings/other/OtherSettingsFragment.kt index 5292eecc6..e805c6926 100644 --- a/app/src/main/feature/settings/other/OtherSettingsFragment.kt +++ b/app/src/main/feature/settings/other/OtherSettingsFragment.kt @@ -27,7 +27,6 @@ import androidx.preference.PreferenceManager import com.winlator.cmod.R import com.winlator.cmod.app.config.SettingsConfig import com.winlator.cmod.app.shell.UnifiedActivity -import com.winlator.cmod.app.update.UpdateChannel import com.winlator.cmod.app.update.UpdateService import com.winlator.cmod.feature.shortcuts.FrontendExporter import com.winlator.cmod.feature.setup.SetupWizardActivity @@ -112,10 +111,6 @@ class OtherSettingsFragment : Fragment() { ) } }, - onUpdateChannelSelected = { index -> - UpdateService.setChannel(ctx, UpdateChannel.entries[index]) - refresh() - }, onLanguageSelected = { index -> val currentIndex = LocaleHelper.indexForTag( @@ -252,7 +247,7 @@ class OtherSettingsFragment : Fragment() { uiState = OtherSettingsState( checkForUpdates = preferences.getBoolean("check_for_updates", true), - updateChannelIndex = UpdateChannel.entries.indexOf(UpdateService.channel(ctx)), + updateSource = updateSourceLabel(ctx), languageLabels = languageLabels, languageIndex = languageIndex, soundFontFiles = soundFontFiles, @@ -282,6 +277,12 @@ class OtherSettingsFragment : Fragment() { ) } + /** The pull request a CI build follows, or the official releases. Blank when neither applies. */ + private fun updateSourceLabel(ctx: Context): String { + if (!UpdateService.isSupported(ctx)) return "" + return UpdateService.updateSourceName().ifEmpty { getString(R.string.update_channel_official) } + } + private fun loadSoundFontFiles(ctx: Context): List<String> { val files = mutableListOf(MidiManager.DEFAULT_SF2_FILE) MidiManager.getSoundFontDir(ctx).listFiles()?.forEach { file -> diff --git a/app/src/main/feature/settings/other/OtherSettingsScreen.kt b/app/src/main/feature/settings/other/OtherSettingsScreen.kt index d4360a318..7667a72be 100644 --- a/app/src/main/feature/settings/other/OtherSettingsScreen.kt +++ b/app/src/main/feature/settings/other/OtherSettingsScreen.kt @@ -121,7 +121,8 @@ private val Error = Color(0xFFFF4444) // State data class OtherSettingsState( val checkForUpdates: Boolean = true, - val updateChannelIndex: Int = 0, + /** What this install follows, already worded for the screen; empty hides the row. */ + val updateSource: String = "", val languageLabels: List<String> = emptyList(), val languageIndex: Int = 0, val soundFontFiles: List<String> = emptyList(), @@ -169,7 +170,6 @@ fun OtherSettingsScreen( state: OtherSettingsState, onCheckForUpdatesChanged: (Boolean) -> Unit, onCheckForUpdatesNow: () -> Unit, - onUpdateChannelSelected: (Int) -> Unit, onLanguageSelected: (Int) -> Unit, onSoundFontSelected: (Int) -> Unit, onInstallSoundFont: () -> Unit, @@ -238,18 +238,9 @@ fun OtherSettingsScreen( onCheckNow = onCheckForUpdatesNow, ) - SettingsDropdownCard( - title = stringResource(R.string.settings_general_update_channel), - subtitle = stringResource(R.string.settings_general_update_channel_summary), - icon = Icons.Outlined.Sync, - options = - listOf( - stringResource(R.string.update_channel_official), - stringResource(R.string.update_channel_development), - ), - selectedIndex = state.updateChannelIndex, - onOptionSelected = onUpdateChannelSelected, - ) + if (state.updateSource.isNotEmpty()) { + UpdateSourceCard(source = state.updateSource) + } SettingsDropdownCard( title = stringResource(R.string.settings_other_language_title), @@ -565,7 +556,62 @@ private fun UpdatesCard( } } -// Generic dropdown card (labels list + index selection) +/** + * What this install takes updates from. It is shown rather than chosen: an official install and a + * pull request build are signed with different keys, so neither can be replaced by the other. + */ +@Composable +private fun UpdateSourceCard(source: String) { + Box( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(CardDark) + .border(1.dp, CardBorder, RoundedCornerShape(12.dp)), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 11.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(34.dp) + .clip(RoundedCornerShape(9.dp)) + .background(IconBoxBg), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Outlined.Sync, + contentDescription = null, + tint = Accent, + modifier = Modifier.size(17.dp), + ) + } + Spacer(Modifier.width(13.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + stringResource(R.string.settings_general_update_source), + color = TextPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + ) + Text( + stringResource(R.string.settings_general_update_source_summary), + color = TextSecondary, + fontSize = 11.sp, + ) + } + Spacer(Modifier.width(8.dp)) + Text(source, color = Accent, fontSize = 13.sp, fontWeight = FontWeight.Medium) + } + } +} + @Composable private fun SettingsDropdownCard( title: String, diff --git a/app/src/main/feature/settings/stores/LinuxClientDialog.kt b/app/src/main/feature/settings/stores/LinuxClientDialog.kt new file mode 100644 index 000000000..fc925fa45 --- /dev/null +++ b/app/src/main/feature/settings/stores/LinuxClientDialog.kt @@ -0,0 +1,189 @@ +package com.winlator.cmod.feature.settings + +import android.text.format.Formatter +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDownward +import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import com.winlator.cmod.R +import com.winlator.cmod.runtime.linux.LinuxClientInstaller +import com.winlator.cmod.runtime.linux.LinuxClientInstaller.Stage +import com.winlator.cmod.runtime.linux.LinuxClientInstaller.State +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupProgressBar +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry + +private val InstallBlue = Color(0xFF1A9FFF) +private val InstalledGreen = Color(0xFF3FB950) +private val DialogTextPrimary = Color(0xFFF0F4FF) +private val DialogTextSecondary = Color(0xFF93A6BC) + +/** + * The Linux Steam Client's install window. It only reflects [state]; the install itself runs in + * [LinuxClientInstaller] and carries on when the window is closed, so reopening it shows where + * the install has got to. + */ +@Composable +internal fun LinuxClientDialog( + state: State, + onStart: () -> Unit, + onCancelInstall: () -> Unit, + onDismiss: () -> Unit, +) { + val nav = remember { PaneNavRegistry() } + val installed = state is State.Installed + val accent = if (installed) InstalledGreen else InstallBlue + + Dialog(onDismissRequest = onDismiss) { + DialogPaneNav(nav, onDismiss = onDismiss) + CompositionLocalProvider(LocalPaneNav provides nav) { + PopupDialog( + title = stringResource(R.string.linux_client_title), + message = linuxClientMessage(state), + icon = if (installed) Icons.Outlined.CheckCircle else Icons.Outlined.ArrowDownward, + accentColor = accent, + modifier = Modifier.widthIn(min = 300.dp, max = 420.dp), + content = { + if (state is State.Working) WorkingBody(state) + }, + footer = { + LinuxClientFooter( + state = state, + accent = accent, + onStart = onStart, + onCancelInstall = onCancelInstall, + onDismiss = onDismiss, + ) + }, + ) + } + } +} + +@Composable +internal fun linuxClientMessage(state: State): String? { + val context = LocalContext.current + return when (state) { + State.Checking, State.Missing -> stringResource(R.string.linux_client_install_message) + State.Installed -> stringResource(R.string.linux_client_installed_message) + is State.UpdateAvailable -> stringResource(R.string.linux_client_update_message) + State.Failed -> stringResource(R.string.linux_client_failed_message) + is State.NoSpace -> + stringResource( + R.string.linux_client_no_space_message, + Formatter.formatShortFileSize(context, state.needed), + Formatter.formatShortFileSize(context, state.available), + ) + is State.Blocked -> stringResource(state.message) + is State.Working -> null + } +} + +@StringRes +internal fun linuxClientStageLabel(stage: Stage): Int = + when (stage) { + Stage.CONNECT -> R.string.linux_client_stage_connect + Stage.DOWNLOAD_RUNTIME -> R.string.linux_client_stage_download_runtime + Stage.INSTALL_RUNTIME -> R.string.linux_client_stage_install_runtime + Stage.DOWNLOAD_PROTON -> R.string.linux_client_stage_download_proton + Stage.INSTALL_PROTON -> R.string.linux_client_stage_install_proton + Stage.DOWNLOAD_STEAM -> R.string.linux_client_stage_download_steam + Stage.INSTALL_STEAM -> R.string.linux_client_stage_install_steam + Stage.LIBRARY -> R.string.linux_client_stage_library + } + +@Composable +private fun WorkingBody(state: State.Working) { + val context = LocalContext.current + val stageLabel = stringResource(linuxClientStageLabel(state.stage)) + val known = state.total > 0 + val downloading = + state.stage == Stage.DOWNLOAD_RUNTIME || state.stage == Stage.DOWNLOAD_PROTON || state.stage == Stage.DOWNLOAD_STEAM + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text( + text = stageLabel, + color = DialogTextPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + ) + PopupProgressBar( + progress = if (known) state.done.toFloat() / state.total else Float.NaN, + progressLabel = stageLabel, + accentColor = InstallBlue, + textSecondaryColor = DialogTextSecondary, + ) + if (known && downloading) { + Text( + text = + stringResource( + R.string.linux_client_progress_amount, + Formatter.formatShortFileSize(context, state.done), + Formatter.formatShortFileSize(context, state.total), + ), + color = DialogTextSecondary, + fontSize = 11.sp, + ) + } + } +} + +@Composable +private fun LinuxClientFooter( + state: State, + accent: Color, + onStart: () -> Unit, + onCancelInstall: () -> Unit, + onDismiss: () -> Unit, +) { + // The last step writes the container in one go and cannot be stopped part way. + val single = state is State.Installed || (state is State.Working && state.stage == Stage.LIBRARY) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = if (single) Arrangement.End else Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + when (state) { + State.Installed -> + PopupTextAction(stringResource(R.string.common_ui_ok), accent, onDismiss, isEntry = true) + is State.Working -> { + if (!single) { + PopupTextAction(stringResource(R.string.common_ui_cancel), DialogTextSecondary, onCancelInstall) + } + PopupTextAction(stringResource(R.string.common_ui_ok), accent, onDismiss, isEntry = true) + } + State.Failed, is State.NoSpace, is State.Blocked -> { + PopupTextAction(stringResource(R.string.common_ui_cancel), DialogTextSecondary, onDismiss) + PopupTextAction(stringResource(R.string.linux_client_retry), accent, onStart, isEntry = true) + } + State.Checking, State.Missing -> { + PopupTextAction(stringResource(R.string.common_ui_cancel), DialogTextSecondary, onDismiss) + PopupTextAction(stringResource(R.string.common_ui_download), accent, onStart, isEntry = true) + } + is State.UpdateAvailable -> { + PopupTextAction(stringResource(R.string.common_ui_cancel), DialogTextSecondary, onDismiss) + PopupTextAction(stringResource(R.string.update_action_update), accent, onStart, isEntry = true) + } + } + } +} diff --git a/app/src/main/feature/settings/stores/StoresScreen.kt b/app/src/main/feature/settings/stores/StoresScreen.kt index 1af993a31..91693b6c8 100644 --- a/app/src/main/feature/settings/stores/StoresScreen.kt +++ b/app/src/main/feature/settings/stores/StoresScreen.kt @@ -41,6 +41,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.ArrowDownward +import androidx.compose.material.icons.outlined.Check import androidx.compose.material.icons.outlined.Folder import androidx.compose.material.icons.outlined.FolderShared import androidx.compose.material.icons.outlined.Gamepad @@ -60,9 +62,12 @@ import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -72,6 +77,7 @@ import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -80,6 +86,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import com.winlator.cmod.R +import com.winlator.cmod.runtime.linux.LinuxClientInstaller import com.winlator.cmod.shared.ui.focus.rememberSettingsContentNav import com.winlator.cmod.shared.ui.nav.LocalPaneNav import com.winlator.cmod.shared.ui.nav.paneNavItem @@ -159,6 +166,21 @@ fun StoresScreen( val navBarEndPadding = navBarPadding.calculateEndPadding(layoutDirection) val navBarBottomPadding = navBarPadding.calculateBottomPadding() val contentNav = rememberSettingsContentNav(bridge) + val context = LocalContext.current + val linuxClient by LinuxClientInstaller.state.collectAsState() + var linuxClientDialog by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(Unit) { LinuxClientInstaller.refresh(context) } + if (linuxClientDialog) { + LinuxClientDialog( + state = linuxClient, + onStart = { LinuxClientInstaller.start(context) }, + onCancelInstall = { + LinuxClientInstaller.cancel() + linuxClientDialog = false + }, + onDismiss = { linuxClientDialog = false }, + ) + } val downloadSpeedOptions = listOf( 8 to stringResource(R.string.stores_accounts_download_speed_conservative), @@ -190,6 +212,20 @@ fun StoresScreen( isLoggedIn = state.isSteamLoggedIn, onSignIn = onSteamSignIn, onSignOut = onSteamSignOut, + extraAction = { + val installed = linuxClient is LinuxClientInstaller.State.Installed + // An update is offered in the window, not begun by opening it. + val offered = linuxClient is LinuxClientInstaller.State.UpdateAvailable + ActionButton( + label = stringResource(R.string.linux_client_button), + textColor = if (installed) StatusGreen else Accent, + icon = if (installed) Icons.Outlined.Check else Icons.Outlined.ArrowDownward, + onClick = { + if (!installed && !offered) LinuxClientInstaller.start(context) + linuxClientDialog = true + }, + ) + }, ) StoreCard( name = stringResource(R.string.preloader_platform_epic), @@ -404,6 +440,7 @@ private fun StoreCard( isLoggedIn: Boolean, onSignIn: () -> Unit, onSignOut: () -> Unit, + extraAction: (@Composable () -> Unit)? = null, ) { var showSignOutDialog by remember { mutableStateOf(false) } if (showSignOutDialog) { @@ -520,6 +557,10 @@ private fun StoreCard( } } + if (extraAction != null) { + extraAction() + Spacer(Modifier.width(8.dp)) + } ActionButton( label = if (isLoggedIn) stringResource(R.string.common_ui_sign_out) else stringResource(R.string.common_ui_sign_in), textColor = if (isLoggedIn) DangerRed else accentColor, @@ -534,6 +575,7 @@ private fun ActionButton( label: String, textColor: Color, onClick: () -> Unit, + icon: ImageVector? = null, ) { var isPressed by remember { mutableStateOf(false) } val scale by animateFloatAsState( @@ -565,12 +607,23 @@ private fun ActionButton( }.padding(horizontal = 12.dp, vertical = 7.dp), contentAlignment = Alignment.Center, ) { - Text( - text = label, - color = textColor, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold, - ) + Row(verticalAlignment = Alignment.CenterVertically) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = null, + tint = textColor, + modifier = Modifier.size(14.dp), + ) + Spacer(Modifier.width(4.dp)) + } + Text( + text = label, + color = textColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + ) + } } } diff --git a/app/src/main/feature/setup/SetupWizardActivity.kt b/app/src/main/feature/setup/SetupWizardActivity.kt index eed530ec5..e650e935c 100644 --- a/app/src/main/feature/setup/SetupWizardActivity.kt +++ b/app/src/main/feature/setup/SetupWizardActivity.kt @@ -79,6 +79,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf @@ -116,7 +117,12 @@ import androidx.lifecycle.lifecycleScope import com.winlator.cmod.R import com.winlator.cmod.app.shell.UnifiedActivity import com.winlator.cmod.feature.settings.DriversFragment +import com.winlator.cmod.feature.settings.linuxClientMessage +import com.winlator.cmod.feature.settings.linuxClientStageLabel import com.winlator.cmod.feature.settings.ContainerSettingsComposeDialog +import com.winlator.cmod.shared.ui.dialog.ContentDialog +import com.winlator.cmod.runtime.linux.ChildProcessRestrictions +import com.winlator.cmod.runtime.linux.LinuxClientInstaller import com.winlator.cmod.runtime.container.Container import com.winlator.cmod.runtime.container.ContainerCreation import com.winlator.cmod.runtime.container.ContainerManager @@ -212,12 +218,15 @@ private suspend fun LazyListState.scrollToSelected(index: Int) { } class SetupWizardActivity : FixedFontScaleFragmentActivity() { + private var childProcessPromptShown = false + companion object { private const val PREFS_NAME = "winnative_setup" private const val EXTRA_FORCE_SHOW = "force_show" private const val EXTRA_RETURN_TO_CALLER = "return_to_caller" private const val KEY_SETUP_COMPLETE = "setup_complete" private const val KEY_RECOMMENDED_COMPONENTS_DONE = "recommended_components_done" + private const val LAST_PAGE = 3 private const val KEY_DRIVERS_VISITED = "drivers_visited" private const val KEY_DEFAULT_X86_CONTAINER_ID = "default_x86_container_id" private const val KEY_DEFAULT_ARM64_CONTAINER_ID = "default_arm64_container_id" @@ -392,7 +401,7 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() { private fun isContainerUsable( contentsManager: ContentsManager, container: Container, - ): Boolean = isWineVersionInstalled(contentsManager, container.wineVersion) + ): Boolean = !container.isGamescopeRuntime && isWineVersionInstalled(contentsManager, container.wineVersion) private fun hasInstalledRuntimes(context: Context): Boolean = ContentsManager.hasInstalledRuntimes(context) @@ -618,7 +627,7 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() { private fun advanceWizardPage() { val page = pageIndex.intValue - if (page < 2) { + if (page < LAST_PAGE) { val canGoNext = if (page == 0) storageGranted.value && imageFsDone.value else true if (canGoNext) pageIndex.intValue += 1 } else if (!creatingContainer.value && transferState.value == null) { @@ -1562,12 +1571,13 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() { @Composable private fun SetupWizardScreen() { val page by pageIndex - val totalPages = 3 + val totalPages = LAST_PAGE + 1 val pageTitle = when (page) { 0 -> stringResource(R.string.setup_wizard_required_access) - 1 -> stringResource(R.string.setup_wizard_select_components) - 2 -> stringResource(R.string.setup_wizard_containers) + 1 -> stringResource(R.string.setup_wizard_steam_client) + 2 -> stringResource(R.string.setup_wizard_select_components) + 3 -> stringResource(R.string.setup_wizard_containers) else -> "" } val canGoNext = @@ -1883,8 +1893,9 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() { ) { when (targetPage) { 0 -> PagePermissions(isCompact) - 1 -> PageAdvancedComponents(isCompact) - 2 -> PageDefaultSettings() + 1 -> PageSteamClient(isCompact) + 2 -> PageAdvancedComponents(isCompact) + 3 -> PageDefaultSettings() } } } @@ -2309,6 +2320,134 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() { } } + /** + * Said once the Linux client is on its way, because the install is the point at which the + * device's own limit starts to matter and there are minutes of download to act in. + */ + private fun promptChildProcessRestrictions() { + if (childProcessPromptShown || !ChildProcessRestrictions.active(this)) return + childProcessPromptShown = true + if (!ChildProcessRestrictions.hasSwitch()) { + ContentDialog.alert(this, getString(R.string.linux_child_processes_adb), null) + return + } + ContentDialog.confirm(this, getString(R.string.linux_child_processes_message)) { + if (!ChildProcessRestrictions.openDeveloperOptions(this)) { + ContentDialog.alert(this, getString(R.string.linux_child_processes_adb), null) + } + } + } + + /** + * Which Steam client the user is after. The Linux one is the install Settings > Stores offers, + * run from here and shared with it, so it goes on while the rest of the wizard is filled in; + * the Windows one is made of the components and the container of the pages that follow. + */ + @Composable + private fun PageSteamClient(isCompact: Boolean) { + val region by navRegion + val navIdx by navIndex + val activate by activateSignal + val controller by controllerConnected + val client by LinuxClientInstaller.state.collectAsState() + val working = client as? LinuxClientInstaller.State.Working + val installed = + client is LinuxClientInstaller.State.Installed || client is LinuxClientInstaller.State.UpdateAvailable + val startLinuxClient = { + if (working == null && !installed && client !is LinuxClientInstaller.State.Blocked) { + LinuxClientInstaller.start(this) + promptChildProcessRestrictions() + } + } + + LaunchedEffect(Unit) { LinuxClientInstaller.refresh(this@SetupWizardActivity) } + LaunchedEffect(isCompact) { + setTabCount(0) + setContentLayout(2, if (isCompact) 1 else 2) + } + val lastActivate = remember { mutableStateOf(activate) } + LaunchedEffect(activate) { + if (activate == lastActivate.value) return@LaunchedEffect + lastActivate.value = activate + if (region != REGION_CONTENT) return@LaunchedEffect + when (navIdx) { + 0 -> startLinuxClient() + 1 -> advanceWizardPage() + } + } + + val linuxCard: @Composable (Modifier) -> Unit = { mod -> + WizardActionCard( + modifier = mod, + title = stringResource(R.string.linux_client_title), + subtitle = + stringResource( + if (working != null) linuxClientStageLabel(working.stage) else R.string.common_ui_optional, + ), + completed = installed, + buttonLabel = + when { + installed -> stringResource(R.string.common_ui_installed) + working != null && working.total > 0 -> "${working.done * 100 / working.total}%" + working != null -> stringResource(linuxClientStageLabel(working.stage)) + client is LinuxClientInstaller.State.Failed || client is LinuxClientInstaller.State.NoSpace -> + stringResource(R.string.linux_client_retry) + else -> stringResource(R.string.common_ui_download) + }, + highlighted = controller && region == REGION_CONTENT && navIdx == 0, + onClick = { setNav(REGION_CONTENT, 0); startLinuxClient() }, + enabled = working == null && client !is LinuxClientInstaller.State.Blocked, + progress = working?.let { if (it.total > 0) it.done.toFloat() / it.total else 0f }, + ) + } + val windowsCard: @Composable (Modifier) -> Unit = { mod -> + WizardActionCard( + modifier = mod, + title = stringResource(R.string.setup_wizard_windows_steam_client), + subtitle = stringResource(R.string.common_ui_optional), + completed = false, + buttonLabel = stringResource(R.string.common_ui_continue), + highlighted = controller && region == REGION_CONTENT && navIdx == 1, + onClick = { setNav(REGION_CONTENT, 1); advanceWizardPage() }, + ) + } + val message = + if (working != null) { + stringResource(R.string.setup_wizard_linux_client_continues) + } else { + linuxClientMessage(client).orEmpty() + } + + Column( + modifier = + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically), + ) { + if (isCompact) { + linuxCard(Modifier.fillMaxWidth()) + windowsCard(Modifier.fillMaxWidth()) + } else { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + linuxCard(Modifier.weight(1f)) + windowsCard(Modifier.weight(1f)) + } + } + Text( + text = message + "\n" + stringResource(R.string.setup_wizard_windows_steam_summary), + color = Color(0xFF8B949E), + fontFamily = InterFont, + fontSize = 11.sp, + lineHeight = 15.sp, + ) + } + } + @Composable private fun PageAdvancedComponents(isCompact: Boolean) { val typeOrder = diff --git a/app/src/main/feature/shortcuts/LibraryShortcutUtils.kt b/app/src/main/feature/shortcuts/LibraryShortcutUtils.kt index 9a3e103e9..0f6aefa62 100644 --- a/app/src/main/feature/shortcuts/LibraryShortcutUtils.kt +++ b/app/src/main/feature/shortcuts/LibraryShortcutUtils.kt @@ -6,6 +6,7 @@ import android.util.Log import com.winlator.cmod.R import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.linux.LinuxSteamShortcuts import java.io.File import java.util.Locale @@ -164,6 +165,7 @@ object LibraryShortcutUtils { deletedAny = lnkFile.delete() || deletedAny } + LinuxSteamShortcuts.removed(context, shortcut) return deletedAny } diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 100036bc4..035d11217 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner +import com.winlator.cmod.shared.ui.widget.EnvVarsView import kotlinx.coroutines.launch import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -35,6 +36,10 @@ import com.winlator.cmod.BuildConfig import com.winlator.cmod.R import com.winlator.cmod.app.PluviaApp import com.winlator.cmod.feature.library.DriveItem +import com.winlator.cmod.feature.library.DISPLAY_SERVER_WAYLAND_INDEX +import com.winlator.cmod.feature.library.DISPLAY_SERVER_X11_INDEX +import com.winlator.cmod.feature.library.displayBackendFromIndex +import com.winlator.cmod.feature.library.displayServerEntries import com.winlator.cmod.feature.library.EnvVarItem import com.winlator.cmod.feature.library.parseEnvVarItems import androidx.compose.runtime.getValue @@ -49,6 +54,7 @@ import androidx.compose.ui.Modifier import androidx.core.net.toUri import com.winlator.cmod.shared.ui.focus.controllerMenuInput import com.winlator.cmod.feature.library.GameSettingsStateHolder +import com.winlator.cmod.feature.library.LinuxApps import com.winlator.cmod.feature.library.WinComponentItem import com.winlator.cmod.feature.settings.DXVKConfigUtils import com.winlator.cmod.feature.settings.GraphicsDriverConfigUtils @@ -64,6 +70,7 @@ import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.container.Shortcut import com.winlator.cmod.runtime.content.ContentProfile import com.winlator.cmod.runtime.content.ContentsManager +import com.winlator.cmod.runtime.content.DriverPackages import com.winlator.cmod.shared.android.AppUtils import com.winlator.cmod.shared.ui.toast.WinToast import com.winlator.cmod.shared.android.DirectoryPickerDialog @@ -87,6 +94,8 @@ import com.winlator.cmod.feature.shortcuts.ShortcutsFragment import com.winlator.cmod.runtime.display.XServerDisplayActivity import com.winlator.cmod.runtime.input.controls.GestureProfileManager import com.winlator.cmod.runtime.input.controls.InputControlsManager +import com.winlator.cmod.runtime.linux.LinuxDriverChoices +import com.winlator.cmod.runtime.linux.LinuxProtons import com.winlator.cmod.runtime.audio.midi.MidiManager import com.winlator.cmod.runtime.display.winhandler.WinHandler import com.winlator.cmod.feature.artwork.SteamArtworkScraper @@ -131,6 +140,9 @@ class ShortcutSettingsComposeDialog private constructor( // Preset ID lists (parallel to display name lists) private var box64PresetIds = mutableListOf<String>() private var fexcorePresetIds = mutableListOf<String>() + /** Tool names behind [GameSettingsStateHolder.linuxProtonEntries]; null until they are read. */ + private var linuxProtonIds: List<String>? = null + private var linuxProtonRequest = 0 private var shouldRefreshLibraryOnSave = false // SDL2 Compatibility env vars โ€” must match ContainerDetailFragment.SDL2_ENV_VARS. @@ -406,7 +418,7 @@ class ShortcutSettingsComposeDialog private constructor( state.simTouchScreen.value = shortcut.getExtra("simTouchScreen", "0") == "1" state.screenTouchMode.intValue = shortcut.getExtra( "screenTouchMode", - if (shortcut.getExtra("simTouchScreen", "0") == "1") "1" else "0" + if (shortcut.getExtra("simTouchScreen", "0") == "1" || container.isGamescopeRuntime) "1" else "0" ).toIntOrNull() ?: 0 val gestureProfiles = gestureProfileManager.profiles state.gestureProfileEntries.value = @@ -619,6 +631,14 @@ class ShortcutSettingsComposeDialog private constructor( state.selectedZinkMode.intValue = if (getShortcutSetting("zinkMode", container.getZinkMode()) == "windows") 1 else 0 + state.displayServerEntries.value = displayServerEntries(context) + state.selectedDisplayServer.intValue = + if (getShortcutSetting(Container.EXTRA_DISPLAY_BACKEND, container.getDisplayBackend()) == + Container.DISPLAY_BACKEND_WAYLAND + ) DISPLAY_SERVER_WAYLAND_INDEX else DISPLAY_SERVER_X11_INDEX + state.gamescopeContainer.value = container.isGamescopeRuntime + loadLinuxProtons(container) + // DX Wrapper val dxWrapperArr = context.resources.getStringArray(R.array.dxwrapper_entries).toList() @@ -664,6 +684,7 @@ class ShortcutSettingsComposeDialog private constructor( isArm64EC = wineInfo.isArm64EC state.isArm64EC.value = isArm64EC state.wineVersionDisplay.value = formatWineVersionDisplay(wineInfo) + state.wineVersionIdentifier.value = wineVersionStr rebuildEmulatorLists() selectByIdentifier( @@ -898,6 +919,30 @@ class ShortcutSettingsComposeDialog private constructor( state.selectedFexcorePreset.intValue = if (idx >= 0) idx else 0 } + private fun loadLinuxProtons(container: Container) { + val request = ++linuxProtonRequest + linuxProtonIds = null + state.linuxProtonEntries.value = emptyList() + // The client's own entry and native programs start no Proton; the container's choice covers Steam. + if (!container.isGamescopeRuntime || LinuxApps.isLinuxShortcut(shortcut)) return + val own = shouldUseShortcutOverrides(container) + val stored = if (own) + getShortcutSetting(Container.EXTRA_LINUX_PROTON, container.getLinuxProton()) + else + container.getLinuxProton() + Thread({ + // A change made in Steam's own Properties shows here too. + val saved = (if (own) LinuxProtons.choiceFor(context, shortcut) else null) ?: stored + val choices = LinuxProtons.choices(context) + activity.runOnUiThread { + if (request != linuxProtonRequest) return@runOnUiThread + linuxProtonIds = choices.map { it.first } + state.linuxProtonEntries.value = choices.map { it.second } + state.selectedLinuxProton.intValue = choices.indexOfFirst { it.first == saved }.coerceAtLeast(0) + } + }, "LinuxProtons").start() + } + private fun loadBox64Versions(container: Container = shortcut.container) { val itemList: MutableList<String> = if (isArm64EC) { context.resources.getStringArray(R.array.wowbox64_version_entries).toMutableList() @@ -1055,10 +1100,7 @@ class ShortcutSettingsComposeDialog private constructor( private fun loadEnvVars() { val container = shortcut.container - val envVarsStr = getShortcutSetting( - "envVars", - container?.getEnvVars() ?: Container.DEFAULT_ENV_VARS - ) + val envVarsStr = getShortcutSetting("envVars", containerEnvVars(container)) val items = parseEnvVarItems(envVarsStr) state.envVars.value = items @@ -1133,6 +1175,12 @@ class ShortcutSettingsComposeDialog private constructor( hasContainerOverride = hasContainerOverride or saveOverride("zinkMode", zinkMode, container.getZinkMode()) + hasContainerOverride = hasContainerOverride or saveOverride( + Container.EXTRA_DISPLAY_BACKEND, + displayBackendFromIndex(state.selectedDisplayServer.intValue), + container.getDisplayBackend() + ) + val graphicsDriverConfig = buildGraphicsDriverConfigFromState() hasContainerOverride = hasContainerOverride or saveOverride( "graphicsDriverConfig", graphicsDriverConfig, container.getGraphicsDriverConfig() @@ -1215,7 +1263,7 @@ class ShortcutSettingsComposeDialog private constructor( // Env vars val envVarsStr = buildEnvVarsString() hasContainerOverride = - hasContainerOverride or saveOverride("envVars", envVarsStr, container.getEnvVars()) + hasContainerOverride or saveOverride("envVars", envVarsStr, containerEnvVars(container)) // FEXCore val fexcoreVersionEntries = state.fexcoreVersionEntries.value @@ -1234,6 +1282,14 @@ class ShortcutSettingsComposeDialog private constructor( "fexcorePreset", fexcorePreset, container.getFEXCorePreset() ) + linuxProtonIds?.let { ids -> + hasContainerOverride = hasContainerOverride or saveOverride( + Container.EXTRA_LINUX_PROTON, + ids.getOrElse(state.selectedLinuxProton.intValue) { Container.LINUX_PROTON_DEFAULT }, + container.getLinuxProton() + ) + } + // Box64 val box64VersionEntries = state.box64VersionEntries.value val box64VersionIdx = state.selectedBox64Version.intValue @@ -1554,6 +1610,10 @@ class ShortcutSettingsComposeDialog private constructor( } else { shortcut.saveData() } + if (container.isGamescopeRuntime || originalContainer.isGamescopeRuntime) { + LinuxProtons.updateChoices(context) + LinuxDriverChoices.update(context) + } com.winlator.cmod.app.shell.UnifiedActivity.refreshLibrary() } } @@ -1946,6 +2006,8 @@ class ShortcutSettingsComposeDialog private constructor( val entries = context.resources.getStringArray(R.array.audio_driver_entries).toList() .filter { + // A Linux session has a PulseAudio server and DirectAudio's host, and nothing that speaks ALSA. + if (container.isGamescopeRuntime) return@filter !it.equals("ALSA", true) !it.equals("DirectAudio", true) || DirectAudioDriver.isSupportedFor(wineVersion) || DirectAudioDriver.isSelected(resolved) @@ -1990,6 +2052,12 @@ class ShortcutSettingsComposeDialog private constructor( target.intValue = if (idx >= 0) idx else 0 } + /** What the container hands a shortcut: a GameScope one leaves out what its sessions never read. */ + private fun containerEnvVars(container: Container?): String { + val envVars = container?.getEnvVars() ?: Container.DEFAULT_ENV_VARS + return if (container?.isGamescopeRuntime == true) EnvVarsView.forGamescope(envVars) else envVars + } + private fun saveOverride( extraName: String, newValue: String, @@ -2179,7 +2247,11 @@ class ShortcutSettingsComposeDialog private constructor( state.gfxDriverVersionEntries.value = listOf(if (savedVersion.isNotEmpty()) savedVersion else "System") state.gfxSelectedDriverVersion.intValue = 0 Thread({ - val versions = com.winlator.cmod.runtime.system.GraphicsDriverCatalog.supportedVersions(context) + val versions = if (container.isGamescopeRuntime) { + DriverPackages.linuxSelectionEntries(context) + } else { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.supportedVersions(context) + } activity.runOnUiThread { state.gfxDriverVersionEntries.value = versions val idx = versions.indexOfFirst { it.equals(savedVersion, ignoreCase = true) } @@ -2193,7 +2265,11 @@ class ShortcutSettingsComposeDialog private constructor( val version = state.gfxDriverVersionEntries.value.getOrElse(versionIndex) { return } val request = ++extensionsRequest Thread({ - val extensions = com.winlator.cmod.runtime.system.GraphicsDriverCatalog.extensions(context, version) + val extensions = if (shortcut.container.isGamescopeRuntime) { + emptyList() + } else { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.extensions(context, version) + } activity.runOnUiThread { if (request != extensionsRequest) return@runOnUiThread state.gfxAvailableExtensions.value = extensions @@ -2344,6 +2420,12 @@ class ShortcutSettingsComposeDialog private constructor( isArm64EC = wineInfo.isArm64EC state.isArm64EC.value = isArm64EC state.wineVersionDisplay.value = formatWineVersionDisplay(wineInfo) + // The Wayland row keys its capability check on this, so it has to move with the container + // or the row keeps the previous container's verdict until the sheet is reopened. + state.wineVersionIdentifier.value = wineVersionStr + // Which pages and rows exist depends on this, so it moves with the container too. + state.gamescopeContainer.value = newContainer.isGamescopeRuntime + loadLinuxProtons(newContainer) rebuildEmulatorLists() selectByIdentifier( @@ -2433,7 +2515,7 @@ class ShortcutSettingsComposeDialog private constructor( state.directXComponents.value = directX state.generalComponents.value = general - val containerEnvVarsStr = container.getEnvVars() ?: Container.DEFAULT_ENV_VARS + val containerEnvVarsStr = containerEnvVars(container) val items = parseEnvVarItems(containerEnvVarsStr) state.sdl2Compatibility.value = EnvVars(containerEnvVarsStr).get("SDL_XINPUT_ENABLED") == "1" state.envVars.value = if (state.sdl2Compatibility.value) { diff --git a/app/src/main/feature/shortcuts/ShortcutsFragment.java b/app/src/main/feature/shortcuts/ShortcutsFragment.java index 46bd533fa..0f878831a 100644 --- a/app/src/main/feature/shortcuts/ShortcutsFragment.java +++ b/app/src/main/feature/shortcuts/ShortcutsFragment.java @@ -171,6 +171,7 @@ private void removeShortcut(Shortcut shortcut) { } if (fileDeleted) { + com.winlator.cmod.runtime.linux.LinuxSteamShortcuts.removed(requireContext(), shortcut); disableShortcutOnScreen(requireContext(), shortcut); loadShortcutsList(); WinToast.show(context, R.string.shortcuts_list_removed); diff --git a/app/src/main/feature/stores/epic/service/EpicGameLauncher.kt b/app/src/main/feature/stores/epic/service/EpicGameLauncher.kt index 3523fbf1a..8dd2a1b69 100644 --- a/app/src/main/feature/stores/epic/service/EpicGameLauncher.kt +++ b/app/src/main/feature/stores/epic/service/EpicGameLauncher.kt @@ -33,7 +33,8 @@ object EpicGameLauncher { * Returns a list of command-line arguments to pass to the game executable for Epic Games * Services authentication. When [container] is non-null and the game requires an ownership * token, the token is materialised inside the container's Wine prefix and referenced as a - * Windows-style path; without a container the token cannot be delivered to DRM titles. + * Windows-style path; without a container the token cannot be delivered to DRM titles, unless + * [stageOwnershipToken] writes it where the game can reach it and returns that path. */ suspend fun buildLaunchParameters( context: Context, @@ -41,6 +42,7 @@ object EpicGameLauncher { offline: Boolean = false, languageCode: String = "en-US", container: Container? = null, + stageOwnershipToken: ((hex: String) -> String)? = null, ): Result<List<String>> = try { val params = mutableListOf<String>() @@ -51,10 +53,10 @@ object EpicGameLauncher { Result.success(params) } else { Timber.tag("EPIC").w("${game.appName} cannot run offline, will attempt online launch") - buildOnlineLaunch(context, game, languageCode, container, params) + buildOnlineLaunch(context, game, languageCode, container, stageOwnershipToken, params) } } else { - buildOnlineLaunch(context, game, languageCode, container, params) + buildOnlineLaunch(context, game, languageCode, container, stageOwnershipToken, params) } } catch (e: Exception) { Timber.tag("EPIC").e(e, "Failed to build launch parameters") @@ -66,6 +68,7 @@ object EpicGameLauncher { game: EpicGame, languageCode: String, container: Container?, + stageOwnershipToken: ((hex: String) -> String)?, params: MutableList<String>, ): Result<List<String>> { Timber.tag("EPIC").d("Launching ${game.appName} online, getting game launch token...") @@ -97,14 +100,18 @@ object EpicGameLauncher { // verify ownership at startup. val ownershipTokenPath: String? = gameToken.ownershipToken?.let { hex -> - if (container == null) { - Timber.tag("EPIC").w( - "Ownership token requested for ${game.appName} but no container was supplied; " + - "DRM verification will be skipped", - ) - null - } else { - saveOwnershipTokenToPrefix(container, game.namespace, game.catalogId, hex) + when { + // A caller that runs the game outside a container of ours - the Linux Steam + // client does - says where the token goes and what path reads it back. + stageOwnershipToken != null -> stageOwnershipToken(hex) + container == null -> { + Timber.tag("EPIC").w( + "Ownership token requested for ${game.appName} but no container was supplied; " + + "DRM verification will be skipped", + ) + null + } + else -> saveOwnershipTokenToPrefix(container, game.namespace, game.catalogId, hex) } } diff --git a/app/src/main/feature/stores/epic/service/EpicService.kt b/app/src/main/feature/stores/epic/service/EpicService.kt index 44b1a29e7..61eac663c 100644 --- a/app/src/main/feature/stores/epic/service/EpicService.kt +++ b/app/src/main/feature/stores/epic/service/EpicService.kt @@ -1295,6 +1295,15 @@ class EpicService : Service() { ): Int { Timber.tag("EPIC").d("onStartCommand() - action: ${intent?.action}") + // A null intent is Android restarting this service on its own after the process died. + // Nobody asked for it and there is no UI, so registering the keep-alive component below + // would leave a foreground service and its wakelock running for hours over nothing. + if (intent == null) { + Timber.tag("EPIC").i("Restarted by Android with no UI; stopping instead") + stopSelf() + return START_NOT_STICKY + } + SessionKeepAliveService.startComponent(this, SessionKeepAliveService.COMPONENT_EPIC, "Connected") val shouldSync = @@ -1310,7 +1319,9 @@ class EpicService : Service() { } null -> { - // START_STICKY restart: sync only if initial sync is missing or throttle elapsed. + // Started without an action, which is how the throttled path above asks for a + // start with no sync: sync only if the initial one is missing or the throttle + // has since elapsed. val timeSinceLastSync = System.currentTimeMillis() - lastSyncTimestamp val shouldResync = !hasPerformedInitialSync || timeSinceLastSync >= SYNC_THROTTLE_MILLIS @@ -1319,11 +1330,11 @@ class EpicService : Service() { .tag( "EPIC", ).i( - "Service restarted by Android - performing sync (hasPerformedInitialSync=$hasPerformedInitialSync, timeSinceLastSync=${timeSinceLastSync}ms)", + "Started without a sync action - performing sync (hasPerformedInitialSync=$hasPerformedInitialSync, timeSinceLastSync=${timeSinceLastSync}ms)", ) true } else { - Timber.tag("EPIC").d("Service restarted by Android - skipping sync (throttled)") + Timber.tag("EPIC").d("Started without a sync action - skipping sync (throttled)") false } } diff --git a/app/src/main/feature/stores/gog/service/GOGService.kt b/app/src/main/feature/stores/gog/service/GOGService.kt index af8f40031..84436374a 100644 --- a/app/src/main/feature/stores/gog/service/GOGService.kt +++ b/app/src/main/feature/stores/gog/service/GOGService.kt @@ -1610,6 +1610,15 @@ class GOGService : Service() { ): Int { Timber.d("[GOGService] onStartCommand() - action: ${intent?.action}") + // A null intent is Android restarting this service on its own after the process died. + // Nobody asked for it and there is no UI, so registering the keep-alive component below + // would leave a foreground service and its wakelock running for hours over nothing. + if (intent == null) { + Timber.i("[GOGService] Restarted by Android with no UI; stopping instead") + stopSelf() + return START_NOT_STICKY + } + SessionKeepAliveService.startComponent(this, SessionKeepAliveService.COMPONENT_GOG, "Connected") val shouldSync = @@ -1625,17 +1634,19 @@ class GOGService : Service() { } null -> { - // START_STICKY restart: sync only if initial sync is missing or throttle elapsed. + // Started without an action, which is how the throttled path above asks for a + // start with no sync: sync only if the initial one is missing or the throttle + // has since elapsed. val timeSinceLastSync = System.currentTimeMillis() - lastSyncTimestamp val shouldResync = !hasPerformedInitialSync || timeSinceLastSync >= SYNC_THROTTLE_MILLIS if (shouldResync) { Timber.i( - "[GOGService] Service restarted by Android - performing sync (hasPerformedInitialSync=$hasPerformedInitialSync, timeSinceLastSync=${timeSinceLastSync}ms)", + "[GOGService] Started without a sync action - performing sync (hasPerformedInitialSync=$hasPerformedInitialSync, timeSinceLastSync=${timeSinceLastSync}ms)", ) true } else { - Timber.d("[GOGService] Service restarted by Android - skipping sync (throttled)") + Timber.d("[GOGService] Started without a sync action - skipping sync (throttled)") false } } diff --git a/app/src/main/feature/stores/steam/service/SteamService.kt b/app/src/main/feature/stores/steam/service/SteamService.kt index 62e201553..5c77fa768 100644 --- a/app/src/main/feature/stores/steam/service/SteamService.kt +++ b/app/src/main/feature/stores/steam/service/SteamService.kt @@ -3681,6 +3681,7 @@ class SteamService : Service() { fun logOut() { // Capture username before clearing anything val username = PrefManager.username + val steamId64 = PrefManager.steamUserSteamId64 // โ”€โ”€ Atomic state flip โ”€โ”€ isLoggingOut = true @@ -3733,6 +3734,15 @@ class SteamService : Service() { // Emit event synchronously so the UI can react in the same frame PluviaApp.events.emit(SteamEvent.LoggedOut(username)) + // The Linux client was signed in with this account's token, so it goes with it. A client + // that is running owns its files and writes them back on exit; it is left to its own menu. + if (!SessionKeepAliveService.isLinuxSessionActive()) { + val appContext = PluviaApp.instance.applicationContext + kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch { + com.winlator.cmod.runtime.linux.LinuxSteamLogin.clear(appContext, username, steamId64) + } + } + // Session already disconnected above; just clear the local database (best-effort). instance?.let { svc -> svc.scope.launch(Dispatchers.Default) { @@ -4199,11 +4209,31 @@ class SteamService : Service() { } } + /** Whether the user asked chat to outlive the app, and there is a login for it to come back to. */ + private fun wantsBackgroundChat(): Boolean = + PrefManager.chatStayRunningOnExit && PrefManager.refreshToken.isNotBlank() + override fun onStartCommand( intent: Intent?, flags: Int, startId: Int, ): Int { + // A null intent is Android restarting this service on its own after the process died. + // Nobody asked for it and there is no UI, so coming back would only re-register the + // keep-alive component and leave a foreground service and its wakelock running for hours + // over nothing. Downloads do not need it: their state is in the database and + // DownloadCoordinator restores them on the next app start. + // + // Background chat is the one thing that does need it. When the user turns that on, + // stopManagedServices deliberately leaves this service started while onTaskRemoved kills + // the process anyway, so this restart is the only way chat ever comes back - refusing it + // would make the setting stop doing what it says. + if (intent == null && !wantsBackgroundChat()) { + Timber.i("Restarted by Android with no session, no UI and no background chat; stopping instead") + stopSelf() + return START_NOT_STICKY + } + // Notification intents when (intent?.action) { NotificationHelper.ACTION_EXIT -> { diff --git a/app/src/main/jniLibs/arm64-v8a/libffi.so b/app/src/main/jniLibs/arm64-v8a/libffi.so new file mode 100755 index 000000000..eceb85edf Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libffi.so differ diff --git a/app/src/main/jniLibs/arm64-v8a/libwayland-server.so b/app/src/main/jniLibs/arm64-v8a/libwayland-server.so new file mode 100755 index 000000000..4eda47685 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libwayland-server.so differ diff --git a/app/src/main/res/drawable/library_steam_client.xml b/app/src/main/res/drawable/library_steam_client.xml new file mode 100644 index 000000000..1f086d8d0 --- /dev/null +++ b/app/src/main/res/drawable/library_steam_client.xml @@ -0,0 +1,15 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="64dp" + android:height="64dp" + android:viewportWidth="64" + android:viewportHeight="64"> + <path + android:fillColor="#1B2838" + android:pathData="M12,0h40a12,12 0 0 1 12,12v40a12,12 0 0 1 -12,12h-40a12,12 0 0 1 -12,-12v-40a12,12 0 0 1 12,-12z" /> + <path + android:fillColor="#66C0F4" + android:pathData="M42,14a10,10 0 1 0 0.01,0zM42,19a5,5 0 1 1 -0.01,0z" /> + <path + android:fillColor="#66C0F4" + android:pathData="M8,38l15,6.2a8,8 0 1 0 3.2,-7.4l-3.9,-1.6a8,8 0 0 0 -1.6,-0.6l-8.5,-3.5a12,12 0 0 1 13.1,-8.8l4.3,-1.8a16,16 0 0 0 -21.6,17.5zM23,40.2l-3.6,-1.5a5,5 0 1 0 3.6,1.5z" /> +</vector> diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index f9ba9da0d..d20bd8e0e 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">Ingresar ruta</string> <string name="common_ui_path_not_found">Ruta no encontrada</string> <string name="common_ui_select_valid_exe_file">Selecciona un archivo .exe vรกlido</string> + <string name="common_ui_select_executable">Seleccionar ejecutable</string> + <string name="common_ui_select_valid_executable">Selecciona un ejecutable de Windows o Linux</string> <string name="common_ui_select_valid_icon_file">Selecciona un archivo .ico o .png vรกlido.</string> <string name="common_ui_exit">Salir</string> <string name="common_ui_exit_app">Salir de la aplicaciรณn</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">Versiรณn establecida en %1$s. Usa Buscar actualizaciรณn para cambiar los archivos instalados.</string> <string name="common_ui_select">Seleccionar</string> <string name="common_ui_storage_roots">Raรญces de almacenamiento</string> + <string name="common_ui_app_storage">Almacenamiento de la app</string> + <string name="common_ui_move">Mover</string> + <string name="library_games_move_to_app_storage_title">Mover a interno</string> + <string name="library_games_move_to_download_folder_title">Mover a externo</string> + <string name="library_games_move_to_app_storage_confirm">ยฟMover %1$s al almacenamiento interno?</string> + <string name="library_games_move_to_download_folder_confirm">ยฟMover %1$s al almacenamiento externo?</string> + <string name="library_games_moving">Moviendo el juego</string> + <string name="library_games_move_done">%1$s movido</string> + <string name="library_games_move_failed">No se pudo mover %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Explorar carpetas locales directamente</string> <string name="common_ui_no_folders_available_here">No hay carpetas disponibles aquรญ</string> <string name="common_ui_grant_all_files_access_browse">Concede acceso a todos los archivos para explorar carpetas directamente.</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32 bits</string> <string name="hero_graphics_test_64">64 bits</string> <string name="hero_input_tests_title">Pruebas de entrada</string> - <string name="library_games_add_custom_game">Agregar juego personalizado</string> <string name="library_games_game_name">Nombre del juego</string> <string name="library_games_game_folder_mapped_drive">Carpeta del juego (unidad de juego asignada)</string> <string name="library_games_select_exe_provide_name">Selecciona un exe y proporciona un nombre</string> <string name="library_games_sign_in_prompt">Inicia sesiรณn en una tienda o agrega un juego personalizado para armar tu biblioteca.</string> + <string name="library_games_add_title">Agregar juego o aplicaciรณn</string> + <string name="library_games_add_linux_steam">Agregar cliente de Steam</string> + <string name="library_games_type">Tipo</string> + <string name="library_games_type_game">Juego</string> + <string name="library_games_type_app">Aplicaciรณn</string> + <string name="library_games_select_executable_or_rom">Selecciona un ejecutable o una ROM de consola</string> + <string name="library_games_added">%1$s agregado</string> + <string name="library_games_add_failed">No se pudo agregar %1$s</string> + <string name="linux_runtime_not_installed">El entorno de ejecuciรณn de Linux aรบn no estรก instalado, por lo que %1$s no se puede iniciar. Instรกlalo con Cliente Linux en Configuraciรณn > Tiendas.</string> + <string name="linux_client_button">Cliente Linux</string> + <string name="linux_client_title">Cliente de Steam para Linux</string> + <string name="linux_client_install_message">Descarga el entorno de ejecuciรณn de Linux desde WinNative y el cliente de Steam desde Valve, y luego instala ambos. La descarga supera 2 GB, asรญ que usa Wi-Fi.</string> + <string name="linux_client_installed_message">El entorno de ejecuciรณn de Linux y el cliente de Steam estรกn instalados, y Steam estรก en la Biblioteca.</string> + <string name="linux_client_failed_message">La instalaciรณn no terminรณ. Comprueba tu conexiรณn e intรฉntalo de nuevo.</string> + <string name="linux_client_no_space_message">No hay suficiente espacio libre: se necesitan %1$s y hay %2$s disponibles.</string> + <string name="linux_client_stage_connect">Conectando</string> + <string name="linux_client_stage_download_runtime">Descargando el entorno de ejecuciรณn de Linux</string> + <string name="linux_client_stage_install_runtime">Instalando el entorno de ejecuciรณn de Linux</string> + <string name="linux_client_stage_download_proton">Descargando Proton</string> + <string name="linux_client_stage_install_proton">Instalando Proton</string> + <string name="linux_client_stage_download_steam">Descargando el cliente de Steam</string> + <string name="linux_client_stage_install_steam">Instalando el cliente de Steam</string> + <string name="linux_client_stage_library">Agregando Steam a la Biblioteca</string> + <string name="linux_client_progress_amount">%1$s de %2$s</string> + <string name="linux_client_retry">Reintentar</string> + <string name="linux_client_busy">El cliente de Steam para Linux aรบn se estรก instalando. Intรฉntalo de nuevo cuando termine.</string> + <string name="linux_client_update_message">Hay un entorno de Linux mรกs reciente. Al actualizar se conservan tu sesiรณn de Steam, los juegos instalados, las partidas guardadas y los contenedores.</string> + <string name="linux_client_update_session_running">Hay una sesiรณn de Linux en curso. Ciรฉrrala y luego actualiza.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">La sesiรณn de Linux se detuvo apenas iniciรณ. Su registro estรก en Configuraciรณn > Depuraciรณn > Abrir administrador de registros; compรกrtelo para que se pueda encontrar la causa.</string> + <string name="linux_session_stopped">La sesiรณn de Linux se detuvo sola (estado %1$d). Su registro estรก en Configuraciรณn > Depuraciรณn > Abrir administrador de registros; compรกrtelo para que se pueda encontrar la causa.</string> + <string name="linux_session_trimmed">Android detuvo la sesiรณn de Linux. Normalmente significa que se alcanzรณ su lรญmite de procesos en segundo plano: activa โ€œDesactivar restricciones de procesos secundariosโ€ en Opciones para desarrolladores e intรฉntalo de nuevo.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Tiendas</string> @@ -1357,6 +1401,10 @@ Ruta instalada: <string name="setup_wizard_title">Asistente de configuraciรณn</string> <string name="setup_wizard_advanced_user">Usuario avanzado</string> <string name="setup_wizard_next">Siguiente</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Finalizar</string> <string name="setup_wizard_download_and_install">Descargar + Instalar</string> <string name="setup_wizard_open_drivers">Abrir controladores</string> @@ -2863,10 +2911,9 @@ Ruta instalada: <string name="update_apk_notice">Esta actualizaciรณn cambia la app en sรญ, asรญ que Android te pedirรก que confirmes la instalaciรณn. Tus juegos, contenedores y ajustes se conservan.</string> <string name="update_up_to_date">WinNative estรก actualizado.</string> <string name="update_unsupported_build">Las actualizaciones dentro de la app solo se ofrecen en versiones oficiales firmadas.</string> - <string name="settings_general_update_channel">Canal de actualizaciones</string> - <string name="settings_general_update_channel_summary">Oficial entrega versiones probadas. Desarrollo entrega compilaciones tempranas.</string> + <string name="settings_general_update_source">Origen de actualizaciones</string> + <string name="settings_general_update_source_summary">Una compilaciรณn de un pull request sigue a ese pull request; una instalaciรณn oficial sigue las versiones oficiales.</string> <string name="update_channel_official">Oficial</string> - <string name="update_channel_development">Desarrollo</string> <string name="networking_section_title">Red</string> <string name="networking_driver">Controlador de red</string> <string name="networking_driver_winnative">Controlador de red de WinNative</string> @@ -2879,4 +2926,35 @@ Ruta instalada: <string name="steam_controller_left_pad_click">Clic del panel izquierdo</string> <string name="steam_controller_right_pad_click">Clic del panel derecho</string> <string name="controller_test_generic">Genรฉrico</string> + <string name="container_display_server">Servidor de pantalla</string> + <string name="container_display_server_help">Experimental. La sesiรณn se dibuja a travรฉs del compositor Wayland integrado en lugar del servidor X.</string> + <string name="container_display_server_wayland_requirements">Wayland necesita una GPU Adreno y un Proton compatible con Wayland que incluya winewayland y el controlador Turnip para Wayland. Instala uno desde Contenidos.</string> + <string name="container_display_server_needs_wayland_proton">Este Proton no incluye winewayland ni el controlador Turnip para Wayland, asรญ que se usa X11. Instala una capa Proton Wayland desde Contenidos y selecciรณnala como Proton del contenedor.</string> + <string name="wayland_unavailable_fallback">Wayland no estรก disponible para este juego. Iniciando en X11.</string> + <string name="wayland_needs_turnip_driver">Wayland necesita un controlador grรกfico Turnip para mostrar el juego. Instala uno en Contenidos y selecciรณnalo como controlador grรกfico.</string> + <string name="containers_gamescope_new">Crear contenedor GameScope</string> + <string name="containers_gamescope_create_failed">No se pudo crear el contenedor GameScope</string> + <string name="linux_apps_need_gamescope_container">Primero crea el contenedor GameScope en Ajustes โ€บ Contenedores.</string> + <string name="linux_runtime_missing">El entorno de ejecuciรณn de Linux no estรก instalado, asรญ que este contenedor aรบn no puede iniciarse. Instรกlalo con Cliente Linux en Configuraciรณn > Tiendas.</string> + <string name="container_display_server_gamescope">Gamescope siempre dibuja a travรฉs del compositor Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Integrado en el entorno Linux. Los controladores de Android descargados solo se aplican a contenedores Wine y Proton.</string> + <string name="linux_runtime_windows_program">Este es un programa de Windows. Muรฉvelo a un contenedor de Wine en los ajustes del acceso directo para iniciarlo.</string> + <string name="settings_content_badge_online">En lรญnea</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Elige dรณnde instalar el controlador. Los paquetes reconocidos usan automรกticamente la plataforma correcta.</string> + <string name="settings_drivers_restore_defaults">Restaurar valores predeterminados</string> + <string name="settings_drivers_defaults_present">Los repositorios predeterminados ya estรกn presentes</string> + <string name="settings_drivers_active">Activo</string> + <string name="settings_drivers_bundled">Mesa incluido</string> + <string name="settings_drivers_legacy">Controlador del cliente Linux</string> + <string name="settings_drivers_routed">Controlador identificado e instalado para %1$s.</string> + <string name="linux_child_processes_message">Android limita cuรกntos procesos en segundo plano puede tener una aplicaciรณn, y una sesiรณn de Linux usa muchos mรกs: Steam o un juego pueden cerrarse mientras juegas. Activa โ€œDesactivar restricciones de procesos secundariosโ€ en Opciones de desarrollador. ยฟAbrirlas ahora?</string> + <string name="linux_child_processes_adb">Android limita cuรกntos procesos en segundo plano puede tener una aplicaciรณn, y una sesiรณn de Linux usa muchos mรกs: Steam o un juego pueden cerrarse mientras juegas. Esta versiรณn de Android no tiene esa opciรณn. Desde una computadora, ejecuta:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index bda71e2a6..267787495 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Gendan ikon</string> <string name="common_ui_select_exe">Vรฆlg EXE</string> <string name="common_ui_select_valid_exe_file">Vรฆlg venligst en gyldig .exe-fil</string> + <string name="common_ui_select_executable">Vรฆlg eksekverbar fil</string> + <string name="common_ui_select_valid_executable">Vรฆlg en eksekverbar Windows- eller Linux-fil</string> <string name="common_ui_select_valid_icon_file">Vรฆlg venligst en gyldig .ico- eller .png-fil.</string> <string name="common_ui_exit">Afslut</string> <string name="common_ui_exit_app">Afslut app</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Indtast sti</string> <string name="common_ui_path_not_found">Stien blev ikke fundet</string> <string name="common_ui_storage_roots">Lagringsrรธdder</string> + <string name="common_ui_app_storage">App-lager</string> + <string name="common_ui_move">Flyt</string> + <string name="library_games_move_to_app_storage_title">Flyt til intern</string> + <string name="library_games_move_to_download_folder_title">Flyt til ekstern</string> + <string name="library_games_move_to_app_storage_confirm">Flyt %1$s til internt lager?</string> + <string name="library_games_move_to_download_folder_confirm">Flyt %1$s til eksternt lager?</string> + <string name="library_games_moving">Flytter spillet</string> + <string name="library_games_move_done">%1$s er flyttet</string> + <string name="library_games_move_failed">Kunne ikke flytte %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Gennemse lokale mapper direkte</string> <string name="common_ui_no_folders_available_here">Ingen mapper tilgรฆngelige her</string> <string name="common_ui_grant_all_files_access_browse">Giv fuld filadgang for at gennemse lokale mapper direkte.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Inputtests</string> - <string name="library_games_add_custom_game">Tilfรธj brugerdefineret spil</string> <string name="library_games_game_name">Spilnavn</string> <string name="library_games_game_folder_mapped_drive">Spilmappe (tilknyttet spildrev)</string> <string name="library_games_select_exe_provide_name">Vรฆlg en exe og angiv et navn</string> <string name="library_games_sign_in_prompt">Log ind pรฅ en butik eller tilfรธj et brugerdefineret spil for at opbygge dit bibliotek.</string> + <string name="library_games_add_title">Tilfรธj spil eller app</string> + <string name="library_games_add_linux_steam">Tilfรธj Steam-klient</string> + <string name="library_games_type">Type</string> + <string name="library_games_type_game">Spil</string> + <string name="library_games_type_app">App</string> + <string name="library_games_select_executable_or_rom">Vรฆlg en eksekverbar fil eller konsol-ROM</string> + <string name="library_games_added">%1$s tilfรธjet</string> + <string name="library_games_add_failed">Kunne ikke tilfรธje %1$s</string> + <string name="linux_runtime_not_installed">Linux-runtimen er ikke installeret endnu, sรฅ %1$s kan ikke startes. Installer det med Linux-klient under Indstillinger > Butikker.</string> + <string name="linux_client_button">Linux-klient</string> + <string name="linux_client_title">Linux Steam-klient</string> + <string name="linux_client_install_message">Downloader Linux-runtimen fra WinNative og Steam-klienten fra Valve og installerer begge. Downloaden er over 2 GB, sรฅ brug Wi-Fi.</string> + <string name="linux_client_installed_message">Linux-runtimen og Steam-klienten er installeret, og Steam er i Bibliotek.</string> + <string name="linux_client_failed_message">Installationen blev ikke fuldfรธrt. Tjek din forbindelse, og prรธv igen.</string> + <string name="linux_client_no_space_message">Ikke nok ledig plads: %1$s krรฆves, og %2$s er ledig.</string> + <string name="linux_client_stage_connect">Forbinder</string> + <string name="linux_client_stage_download_runtime">Downloader Linux-runtimen</string> + <string name="linux_client_stage_install_runtime">Installerer Linux-runtimen</string> + <string name="linux_client_stage_download_proton">Downloader Proton</string> + <string name="linux_client_stage_install_proton">Installerer Proton</string> + <string name="linux_client_stage_download_steam">Downloader Steam-klienten</string> + <string name="linux_client_stage_install_steam">Installerer Steam-klienten</string> + <string name="linux_client_stage_library">Tilfรธjer Steam til Bibliotek</string> + <string name="linux_client_progress_amount">%1$s af %2$s</string> + <string name="linux_client_retry">Prรธv igen</string> + <string name="linux_client_busy">Linux Steam-klienten installeres stadig. Prรธv igen, nรฅr den er fรฆrdig.</string> + <string name="linux_client_update_message">Et nyere Linux-miljรธ er tilgรฆngeligt. Opdateringen bevarer dit Steam-login, installerede spil, gemte spil og containere.</string> + <string name="linux_client_update_session_running">En Linux-session kรธrer. Afslut den, og opdater derefter.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux-sessionen stoppede, sรฅ snart den startede. Dens log ligger under Indstillinger > Fejlfinding > ร…bn loghรฅndtering; del den, sรฅ รฅrsagen kan findes.</string> + <string name="linux_session_stopped">Linux-sessionen stoppede af sig selv (status %1$d). Dens log ligger under Indstillinger > Fejlfinding > ร…bn loghรฅndtering; del den, sรฅ รฅrsagen kan findes.</string> + <string name="linux_session_trimmed">Android stoppede Linux-sessionen. Det betyder som regel, at grรฆnsen for baggrundsprocesser blev nรฅet: slรฅ โ€Deaktiver begrรฆnsninger for underordnede processerโ€ til i Udviklerindstillinger, og prรธv igen.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Butikker</string> @@ -1065,6 +1109,10 @@ Installeret sti: <string name="setup_wizard_title">Opsรฆtningsguide</string> <string name="setup_wizard_advanced_user">Avanceret bruger</string> <string name="setup_wizard_next">Nรฆste</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Afslut</string> <string name="setup_wizard_download_and_install">Download + Installer</string> <string name="setup_wizard_open_drivers">ร…bn drivere</string> @@ -1263,10 +1311,9 @@ Installeret sti: <string name="update_apk_notice">Denne opdatering รฆndrer selve appen, sรฅ Android beder dig bekrรฆfte installationen. Dine spil, containere og indstillinger bevares.</string> <string name="update_up_to_date">WinNative er opdateret.</string> <string name="update_unsupported_build">Opdateringer i appen tilbydes kun pรฅ officielle signerede udgivelser.</string> - <string name="settings_general_update_channel">Opdateringskanal</string> - <string name="settings_general_update_channel_summary">Officiel leverer testede udgivelser. Udvikling leverer tidlige builds.</string> + <string name="settings_general_update_source">Opdateringskilde</string> + <string name="settings_general_update_source_summary">En build fra en pull request fรธlger den pull request; en officiel installation fรธlger de officielle udgivelser.</string> <string name="update_channel_official">Officiel</string> - <string name="update_channel_development">Udvikling</string> <string name="setup_wizard_containers">Containeropsรฆtning</string> <string name="cloud_saves_title">Cloud-saves</string> <string name="cloud_saves_title_for_provider">%1$s cloud-saves - %2$s</string> @@ -2882,4 +2929,35 @@ Installeret sti: <string name="steam_controller_left_pad_click">Klik pรฅ venstre pegefelt</string> <string name="steam_controller_right_pad_click">Klik pรฅ hรธjre pegefelt</string> <string name="controller_test_generic">Generisk</string> + <string name="container_display_server">Skรฆrmserver</string> + <string name="container_display_server_help">Eksperimentel. Sessionen tegnes gennem den indbyggede Wayland-compositor i stedet for X-serveren.</string> + <string name="container_display_server_wayland_requirements">Wayland krรฆver en Adreno-GPU og en Wayland-kompatibel Proton med winewayland og Wayland Turnip-driveren. Installรฉr en fra Indhold.</string> + <string name="container_display_server_needs_wayland_proton">Denne Proton har hverken winewayland eller Wayland Turnip-driveren, sรฅ X11 bruges. Installรฉr et Wayland Proton-lag fra Indhold, og vรฆlg det som containerens Proton.</string> + <string name="wayland_unavailable_fallback">Wayland er ikke tilgรฆngelig for dette spil. Starter pรฅ X11.</string> + <string name="wayland_needs_turnip_driver">Wayland krรฆver en Turnip-grafikdriver for at vise spillet. Installรฉr en under Indhold, og vรฆlg den som grafikdriver.</string> + <string name="containers_gamescope_new">Opret GameScope-container</string> + <string name="containers_gamescope_create_failed">GameScope-containeren kunne ikke oprettes</string> + <string name="linux_apps_need_gamescope_container">Opret fรธrst GameScope-containeren under Indstillinger โ€บ Containere.</string> + <string name="linux_runtime_missing">Linux-runtimen er ikke installeret, sรฅ denne container kan ikke starte endnu. Installer det med Linux-klient under Indstillinger > Butikker.</string> + <string name="container_display_server_gamescope">Gamescope tegner altid gennem Wayland-compositoren.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Indbygget i Linux-miljรธet. Hentede Android-drivere gรฆlder kun for Wine- og Proton-containere.</string> + <string name="linux_runtime_windows_program">Dette er et Windows-program. Flyt det til en Wine-container i genvejsindstillingerne for at starte det.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Vรฆlg, hvor driveren skal installeres. Genkendte pakker bruger automatisk den korrekte platform.</string> + <string name="settings_drivers_restore_defaults">Gendan standarder</string> + <string name="settings_drivers_defaults_present">Standardlagrene findes allerede</string> + <string name="settings_drivers_active">Aktiv</string> + <string name="settings_drivers_bundled">Medfรธlgende Mesa</string> + <string name="settings_drivers_legacy">Linux-klientdriver</string> + <string name="settings_drivers_routed">Driveren blev identificeret og installeret til %1$s.</string> + <string name="linux_child_processes_message">Android begrรฆnser, hvor mange baggrundsprocesser en app mรฅ have, og en Linux-session bruger langt flere: Steam eller et spil kan blive lukket midt i spillet. Slรฅ โ€Deaktiver begrรฆnsninger for underordnede processerโ€ til i Udviklerindstillinger. ร…bn dem nu?</string> + <string name="linux_child_processes_adb">Android begrรฆnser, hvor mange baggrundsprocesser en app mรฅ have, og en Linux-session bruger langt flere: Steam eller et spil kan blive lukket midt i spillet. Denne Android-version har ikke den indstilling. Kรธr fra en computer:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d7a716c7a..3cd1d2241 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Symbol zurรผcksetzen</string> <string name="common_ui_select_exe">EXE auswรคhlen</string> <string name="common_ui_select_valid_exe_file">Bitte eine gรผltige .exe-Datei auswรคhlen</string> + <string name="common_ui_select_executable">Ausfรผhrbare Datei auswรคhlen</string> + <string name="common_ui_select_valid_executable">Eine ausfรผhrbare Windows- oder Linux-Datei auswรคhlen</string> <string name="common_ui_select_valid_icon_file">Bitte eine gรผltige .ico- oder .png-Datei auswรคhlen.</string> <string name="common_ui_exit">Beenden</string> <string name="common_ui_exit_app">App beenden</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Pfad eingeben</string> <string name="common_ui_path_not_found">Pfad nicht gefunden</string> <string name="common_ui_storage_roots">Speicherorte</string> + <string name="common_ui_app_storage">App-Speicher</string> + <string name="common_ui_move">Verschieben</string> + <string name="library_games_move_to_app_storage_title">Nach intern verschieben</string> + <string name="library_games_move_to_download_folder_title">Nach extern verschieben</string> + <string name="library_games_move_to_app_storage_confirm">%1$s in den internen Speicher verschieben?</string> + <string name="library_games_move_to_download_folder_confirm">%1$s in den externen Speicher verschieben?</string> + <string name="library_games_moving">Spiel wird verschoben</string> + <string name="library_games_move_done">%1$s verschoben</string> + <string name="library_games_move_failed">%1$s konnte nicht verschoben werden</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Lokale Ordner direkt durchsuchen</string> <string name="common_ui_no_folders_available_here">Hier sind keine Ordner verfรผgbar</string> <string name="common_ui_grant_all_files_access_browse">Gewรคhre Vollzugriff auf Dateien, um lokale Ordner direkt zu durchsuchen.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Eingabetests</string> - <string name="library_games_add_custom_game">Benutzerdefiniertes Spiel hinzufรผgen</string> <string name="library_games_game_name">Spielname</string> <string name="library_games_game_folder_mapped_drive">Spieleordner (zugeordnetes Spielelaufwerk)</string> <string name="library_games_select_exe_provide_name">Wรคhle eine EXE-Datei und gib einen Namen ein</string> <string name="library_games_sign_in_prompt">Melde dich bei einem Store an oder fรผge ein benutzerdefiniertes Spiel hinzu, um deine Bibliothek aufzubauen.</string> + <string name="library_games_add_title">Spiel oder App hinzufรผgen</string> + <string name="library_games_add_linux_steam">Steam-Client hinzufรผgen</string> + <string name="library_games_type">Typ</string> + <string name="library_games_type_game">Spiel</string> + <string name="library_games_type_app">App</string> + <string name="library_games_select_executable_or_rom">Ausfรผhrbare Datei oder Konsolen-ROM auswรคhlen</string> + <string name="library_games_added">%1$s hinzugefรผgt</string> + <string name="library_games_add_failed">%1$s konnte nicht hinzugefรผgt werden</string> + <string name="linux_runtime_not_installed">Die Linux-Laufzeitumgebung ist noch nicht installiert, daher kann %1$s nicht gestartet werden. Installiere sie mit Linux-Client unter Einstellungen > Stores.</string> + <string name="linux_client_button">Linux-Client</string> + <string name="linux_client_title">Linux-Steam-Client</string> + <string name="linux_client_install_message">Lรคdt die Linux-Laufzeitumgebung von WinNative und den Steam-Client von Valve herunter und installiert beides. Der Download ist รผber 2 GB groรŸ, nutze also WLAN.</string> + <string name="linux_client_installed_message">Die Linux-Laufzeitumgebung und der Steam-Client sind installiert, und Steam ist in der Bibliothek.</string> + <string name="linux_client_failed_message">Die Installation wurde nicht abgeschlossen. Prรผfe deine Verbindung und versuche es erneut.</string> + <string name="linux_client_no_space_message">Nicht genug freier Speicher: %1$s werden benรถtigt, %2$s sind verfรผgbar.</string> + <string name="linux_client_stage_connect">Verbinden</string> + <string name="linux_client_stage_download_runtime">Linux-Laufzeitumgebung wird heruntergeladen</string> + <string name="linux_client_stage_install_runtime">Linux-Laufzeitumgebung wird installiert</string> + <string name="linux_client_stage_download_proton">Proton wird heruntergeladen</string> + <string name="linux_client_stage_install_proton">Proton wird installiert</string> + <string name="linux_client_stage_download_steam">Steam-Client wird heruntergeladen</string> + <string name="linux_client_stage_install_steam">Steam-Client wird installiert</string> + <string name="linux_client_stage_library">Steam wird zur Bibliothek hinzugefรผgt</string> + <string name="linux_client_progress_amount">%1$s von %2$s</string> + <string name="linux_client_retry">Erneut versuchen</string> + <string name="linux_client_busy">Der Linux-Steam-Client wird noch installiert. Versuche es nach Abschluss erneut.</string> + <string name="linux_client_update_message">Eine neuere Linux-Laufzeitumgebung ist verfรผgbar. Beim Aktualisieren bleiben Steam-Anmeldung, installierte Spiele, Spielstรคnde und Container erhalten.</string> + <string name="linux_client_update_session_running">Eine Linux-Sitzung lรคuft. Beende sie und aktualisiere dann.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Die Linux-Sitzung wurde sofort nach dem Start beendet. Ihr Protokoll liegt unter Einstellungen > Debug > Protokollverwaltung รถffnen; teile es, damit die Ursache gefunden werden kann.</string> + <string name="linux_session_stopped">Die Linux-Sitzung wurde von selbst beendet (Status %1$d). Ihr Protokoll liegt unter Einstellungen > Debug > Protokollverwaltung รถffnen; teile es, damit die Ursache gefunden werden kann.</string> + <string name="linux_session_trimmed">Android hat die Linux-Sitzung beendet. Meist wurde dabei das Limit fรผr Hintergrundprozesse erreicht: Aktiviere โ€žEinschrรคnkungen fรผr untergeordnete Prozesse deaktivierenโ€œ in den Entwickleroptionen und versuche es erneut.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Stores</string> @@ -1065,6 +1109,10 @@ Installierter Pfad: <string name="setup_wizard_title">Einrichtungsassistent</string> <string name="setup_wizard_advanced_user">Erfahrener Benutzer</string> <string name="setup_wizard_next">Weiter</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Fertigstellen</string> <string name="setup_wizard_download_and_install">Herunterladen + Installieren</string> <string name="setup_wizard_open_drivers">Treiber รถffnen</string> @@ -2866,10 +2914,9 @@ Installierter Pfad: <string name="update_apk_notice">Dieses Update รคndert die App selbst, daher bittet dich Android, die Installation zu bestรคtigen. Deine Spiele, Container und Einstellungen bleiben erhalten.</string> <string name="update_up_to_date">WinNative ist aktuell.</string> <string name="update_unsupported_build">In-App-Updates werden nur fรผr offiziell signierte Releases angeboten.</string> - <string name="settings_general_update_channel">Update-Kanal</string> - <string name="settings_general_update_channel_summary">Offiziell liefert getestete Releases. Entwicklung liefert frรผhe Builds.</string> + <string name="settings_general_update_source">Update-Quelle</string> + <string name="settings_general_update_source_summary">Ein Build aus einem Pull Request folgt diesem Pull Request; eine offizielle Installation folgt den offiziellen Releases.</string> <string name="update_channel_official">Offiziell</string> - <string name="update_channel_development">Entwicklung</string> <string name="networking_section_title">Netzwerk</string> <string name="networking_driver">Netzwerktreiber</string> <string name="networking_driver_winnative">WinNative-Netzwerktreiber</string> @@ -2882,4 +2929,35 @@ Installierter Pfad: <string name="steam_controller_left_pad_click">Linkes Touchpad drรผcken</string> <string name="steam_controller_right_pad_click">Rechtes Touchpad drรผcken</string> <string name="controller_test_generic">Allgemein</string> + <string name="container_display_server">Display-Server</string> + <string name="container_display_server_help">Experimentell. Die Sitzung wird รผber den eingebetteten Wayland-Compositor statt รผber den X-Server dargestellt.</string> + <string name="container_display_server_wayland_requirements">Wayland benรถtigt eine Adreno-GPU und ein Wayland-fรคhiges Proton mit winewayland und dem Wayland-Turnip-Treiber. Installiere eines unter Inhalte.</string> + <string name="container_display_server_needs_wayland_proton">Dieses Proton enthรคlt weder winewayland noch den Wayland-Turnip-Treiber, daher wird X11 verwendet. Installiere unter Inhalte eine Wayland-Proton-Ebene und wรคhle sie als Proton des Containers.</string> + <string name="wayland_unavailable_fallback">Wayland ist fรผr dieses Spiel nicht verfรผgbar. Start รผber X11.</string> + <string name="wayland_needs_turnip_driver">Wayland benรถtigt einen Turnip-Grafiktreiber, um das Spiel anzuzeigen. Installiere einen unter Inhalte und wรคhle ihn als Grafiktreiber aus.</string> + <string name="containers_gamescope_new">GameScope-Container erstellen</string> + <string name="containers_gamescope_create_failed">Der GameScope-Container konnte nicht erstellt werden</string> + <string name="linux_apps_need_gamescope_container">Erstelle zuerst den GameScope-Container unter Einstellungen โ€บ Container.</string> + <string name="linux_runtime_missing">Die Linux-Laufzeitumgebung ist nicht installiert, daher kann dieser Container noch nicht starten. Installiere sie mit Linux-Client unter Einstellungen > Stores.</string> + <string name="container_display_server_gamescope">Gamescope zeichnet immer รผber den Wayland-Compositor.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">In die Linux-Laufzeit integriert. Heruntergeladene Android-Treiber gelten nur fรผr Wine- und Proton-Container.</string> + <string name="linux_runtime_windows_program">Dies ist ein Windows-Programm. Verschiebe es in den Verknรผpfungseinstellungen in einen Wine-Container, um es zu starten.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Wรคhle, wo der Treiber installiert werden soll. Erkannte Pakete verwenden automatisch die richtige Plattform.</string> + <string name="settings_drivers_restore_defaults">Standard wiederherstellen</string> + <string name="settings_drivers_defaults_present">Standardquellen sind bereits vorhanden</string> + <string name="settings_drivers_active">Aktiv</string> + <string name="settings_drivers_bundled">Mitgeliefertes Mesa</string> + <string name="settings_drivers_legacy">Linux-Client-Treiber</string> + <string name="settings_drivers_routed">Treiber erkannt und fรผr %1$s installiert.</string> + <string name="linux_child_processes_message">Android begrenzt die Anzahl der Hintergrundprozesse einer App, und eine Linux-Sitzung nutzt weit mehr: Steam oder ein Spiel kann wรคhrend des Spielens beendet werden. Aktiviere โ€žEinschrรคnkungen fรผr untergeordnete Prozesse deaktivierenโ€œ in den Entwickleroptionen. Jetzt รถffnen?</string> + <string name="linux_child_processes_adb">Android begrenzt die Anzahl der Hintergrundprozesse einer App, und eine Linux-Sitzung nutzt weit mehr: Steam oder ein Spiel kann wรคhrend des Spielens beendet werden. Diese Android-Version hat dafรผr keinen Schalter. Fรผhre an einem Computer aus:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 27a13f299..50f777d9d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Restaurar icono</string> <string name="common_ui_select_exe">Seleccionar EXE</string> <string name="common_ui_select_valid_exe_file">Por favor, selecciona un archivo .exe vรกlido</string> + <string name="common_ui_select_executable">Seleccionar ejecutable</string> + <string name="common_ui_select_valid_executable">Selecciona un ejecutable de Windows o Linux</string> <string name="common_ui_select_valid_icon_file">Por favor, selecciona un archivo .ico o .png vรกlido.</string> <string name="common_ui_exit">Salir</string> <string name="common_ui_exit_app">Salir de la aplicaciรณn</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Introducir ruta</string> <string name="common_ui_path_not_found">Ruta no encontrada</string> <string name="common_ui_storage_roots">Ubicaciones de almacenamiento</string> + <string name="common_ui_app_storage">Almacenamiento de la app</string> + <string name="common_ui_move">Mover</string> + <string name="library_games_move_to_app_storage_title">Mover a interno</string> + <string name="library_games_move_to_download_folder_title">Mover a externo</string> + <string name="library_games_move_to_app_storage_confirm">ยฟMover %1$s al almacenamiento interno?</string> + <string name="library_games_move_to_download_folder_confirm">ยฟMover %1$s al almacenamiento externo?</string> + <string name="library_games_moving">Moviendo el juego</string> + <string name="library_games_move_done">%1$s movido</string> + <string name="library_games_move_failed">No se pudo mover %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Explorar carpetas locales directamente</string> <string name="common_ui_no_folders_available_here">No hay carpetas disponibles aquรญ</string> <string name="common_ui_grant_all_files_access_browse">Concede acceso total a los archivos para explorar carpetas locales directamente.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Pruebas de entrada</string> - <string name="library_games_add_custom_game">Aรฑadir juego personalizado</string> <string name="library_games_game_name">Nombre del juego</string> <string name="library_games_game_folder_mapped_drive">Carpeta del juego (unidad de juego asignada)</string> <string name="library_games_select_exe_provide_name">Selecciona un exe e indica un nombre</string> <string name="library_games_sign_in_prompt">Inicia sesiรณn en una tienda o aรฑade un juego personalizado para construir tu biblioteca.</string> + <string name="library_games_add_title">Aรฑadir juego o aplicaciรณn</string> + <string name="library_games_add_linux_steam">Aรฑadir cliente de Steam</string> + <string name="library_games_type">Tipo</string> + <string name="library_games_type_game">Juego</string> + <string name="library_games_type_app">Aplicaciรณn</string> + <string name="library_games_select_executable_or_rom">Selecciona un ejecutable o una ROM de consola</string> + <string name="library_games_added">%1$s aรฑadido</string> + <string name="library_games_add_failed">No se pudo aรฑadir %1$s</string> + <string name="linux_runtime_not_installed">El entorno de ejecuciรณn de Linux aรบn no estรก instalado, por lo que %1$s no se puede iniciar. Instรกlalo con Cliente Linux en Ajustes > Tiendas.</string> + <string name="linux_client_button">Cliente Linux</string> + <string name="linux_client_title">Cliente de Steam para Linux</string> + <string name="linux_client_install_message">Descarga el entorno de ejecuciรณn de Linux desde WinNative y el cliente de Steam desde Valve, y luego instala ambos. La descarga supera 2 GB, asรญ que usa Wi-Fi.</string> + <string name="linux_client_installed_message">El entorno de ejecuciรณn de Linux y el cliente de Steam estรกn instalados, y Steam estรก en la Biblioteca.</string> + <string name="linux_client_failed_message">La instalaciรณn no terminรณ. Comprueba tu conexiรณn e intรฉntalo de nuevo.</string> + <string name="linux_client_no_space_message">No hay suficiente espacio libre: se necesitan %1$s y hay %2$s disponibles.</string> + <string name="linux_client_stage_connect">Conectando</string> + <string name="linux_client_stage_download_runtime">Descargando el entorno de ejecuciรณn de Linux</string> + <string name="linux_client_stage_install_runtime">Instalando el entorno de ejecuciรณn de Linux</string> + <string name="linux_client_stage_download_proton">Descargando Proton</string> + <string name="linux_client_stage_install_proton">Instalando Proton</string> + <string name="linux_client_stage_download_steam">Descargando el cliente de Steam</string> + <string name="linux_client_stage_install_steam">Instalando el cliente de Steam</string> + <string name="linux_client_stage_library">Aรฑadiendo Steam a la Biblioteca</string> + <string name="linux_client_progress_amount">%1$s de %2$s</string> + <string name="linux_client_retry">Reintentar</string> + <string name="linux_client_busy">El cliente de Steam para Linux aรบn se estรก instalando. Intรฉntalo de nuevo cuando termine.</string> + <string name="linux_client_update_message">Hay un entorno de Linux mรกs reciente. Al actualizar se conservan tu sesiรณn de Steam, los juegos instalados, las partidas guardadas y los contenedores.</string> + <string name="linux_client_update_session_running">Hay una sesiรณn de Linux en curso. Ciรฉrrala y luego actualiza.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">La sesiรณn de Linux se detuvo nada mรกs iniciarse. Su registro estรก en Ajustes > Depuraciรณn > Abrir gestor de registros; compรกrtelo para que se pueda encontrar la causa.</string> + <string name="linux_session_stopped">La sesiรณn de Linux se detuvo sola (estado %1$d). Su registro estรก en Ajustes > Depuraciรณn > Abrir gestor de registros; compรกrtelo para que se pueda encontrar la causa.</string> + <string name="linux_session_trimmed">Android detuvo la sesiรณn de Linux. Normalmente significa que se alcanzรณ su lรญmite de procesos en segundo plano: activa โ€œDesactivar restricciones de procesos secundariosโ€ en Opciones para desarrolladores e intรฉntalo de nuevo.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Tiendas</string> @@ -1065,6 +1109,10 @@ Ruta instalada: <string name="setup_wizard_title">Asistente de configuraciรณn</string> <string name="setup_wizard_advanced_user">Usuario avanzado</string> <string name="setup_wizard_next">Siguiente</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Finalizar</string> <string name="setup_wizard_download_and_install">Descargar + Instalar</string> <string name="setup_wizard_open_drivers">Abrir controladores</string> @@ -2866,10 +2914,9 @@ Ruta instalada: <string name="update_apk_notice">Esta actualizaciรณn cambia la propia app, asรญ que Android te pedirรก confirmar la instalaciรณn. Tus juegos, contenedores y ajustes se conservan.</string> <string name="update_up_to_date">WinNative estรก actualizado.</string> <string name="update_unsupported_build">Las actualizaciones dentro de la app solo se ofrecen en versiones oficiales firmadas.</string> - <string name="settings_general_update_channel">Canal de actualizaciรณn</string> - <string name="settings_general_update_channel_summary">Oficial publica versiones probadas. Desarrollo publica compilaciones tempranas.</string> + <string name="settings_general_update_source">Origen de actualizaciones</string> + <string name="settings_general_update_source_summary">Una compilaciรณn de un pull request sigue a ese pull request; una instalaciรณn oficial sigue las versiones oficiales.</string> <string name="update_channel_official">Oficial</string> - <string name="update_channel_development">Desarrollo</string> <string name="networking_section_title">Red</string> <string name="networking_driver">Controlador de red</string> <string name="networking_driver_winnative">Controlador de red de WinNative</string> @@ -2882,5 +2929,36 @@ Ruta instalada: <string name="steam_controller_left_pad_click">Clic del panel izquierdo</string> <string name="steam_controller_right_pad_click">Clic del panel derecho</string> <string name="controller_test_generic">Genรฉrico</string> + <string name="container_display_server">Servidor de pantalla</string> + <string name="container_display_server_help">Experimental. La sesiรณn se dibuja a travรฉs del compositor Wayland integrado en lugar del servidor X.</string> + <string name="container_display_server_wayland_requirements">Wayland necesita una GPU Adreno y un Proton compatible con Wayland que incluya winewayland y el controlador Turnip para Wayland. Instala uno desde Contenidos.</string> + <string name="container_display_server_needs_wayland_proton">Este Proton no incluye winewayland ni el controlador Turnip para Wayland, asรญ que se usa X11. Instala una capa Proton Wayland desde Contenidos y selecciรณnala como Proton del contenedor.</string> + <string name="wayland_unavailable_fallback">Wayland no estรก disponible para este juego. Iniciando en X11.</string> + <string name="wayland_needs_turnip_driver">Wayland necesita un controlador grรกfico Turnip para mostrar el juego. Instala uno en Contenidos y selecciรณnalo como controlador grรกfico.</string> + <string name="containers_gamescope_new">Crear contenedor GameScope</string> + <string name="containers_gamescope_create_failed">No se pudo crear el contenedor GameScope</string> + <string name="linux_apps_need_gamescope_container">Crea primero el contenedor GameScope en Ajustes โ€บ Contenedores.</string> + <string name="linux_runtime_missing">El entorno de ejecuciรณn de Linux no estรก instalado, asรญ que este contenedor aรบn no puede iniciarse. Instรกlalo con Cliente Linux en Ajustes > Tiendas.</string> + <string name="container_display_server_gamescope">Gamescope siempre dibuja a travรฉs del compositor Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Integrado en el entorno Linux. Los controladores de Android descargados solo se aplican a contenedores Wine y Proton.</string> + <string name="linux_runtime_windows_program">Este es un programa de Windows. Muรฉvelo a un contenedor de Wine en los ajustes del acceso directo para iniciarlo.</string> + <string name="settings_content_badge_online">En lรญnea</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Elige dรณnde instalar el controlador. Los paquetes reconocidos usan automรกticamente la plataforma correcta.</string> + <string name="settings_drivers_restore_defaults">Restaurar valores predeterminados</string> + <string name="settings_drivers_defaults_present">Los repositorios predeterminados ya estรกn presentes</string> + <string name="settings_drivers_active">Activo</string> + <string name="settings_drivers_bundled">Mesa incluido</string> + <string name="settings_drivers_legacy">Controlador del cliente Linux</string> + <string name="settings_drivers_routed">Controlador identificado e instalado para %1$s.</string> + <string name="linux_child_processes_message">Android limita cuรกntos procesos en segundo plano puede tener una aplicaciรณn, y una sesiรณn de Linux usa muchos mรกs: Steam o un juego pueden cerrarse mientras juegas. Activa โ€œDesactivar restricciones de procesos secundariosโ€ en Opciones para desarrolladores. ยฟAbrirlas ahora?</string> + <string name="linux_child_processes_adb">Android limita cuรกntos procesos en segundo plano puede tener una aplicaciรณn, y una sesiรณn de Linux usa muchos mรกs: Steam o un juego pueden cerrarse mientras juegas. Esta versiรณn de Android no tiene esa opciรณn. Desde un ordenador, ejecuta:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index fd48f2b05..82c354e7b 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">Anna polku</string> <string name="common_ui_path_not_found">Polkua ei lรถytynyt</string> <string name="common_ui_select_valid_exe_file">Valitse kelvollinen .exe-tiedosto</string> + <string name="common_ui_select_executable">Valitse suoritettava tiedosto</string> + <string name="common_ui_select_valid_executable">Valitse suoritettava Windows- tai Linux-tiedosto</string> <string name="common_ui_select_valid_icon_file">Valitse kelvollinen .ico- tai .png-tiedosto.</string> <string name="common_ui_exit">Poistu</string> <string name="common_ui_exit_app">Poistu sovelluksesta</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">Versioksi asetettu %1$s. Vaihda asennetut tiedostot Tarkista pรคivitykset -toiminnolla.</string> <string name="common_ui_select">Valitse</string> <string name="common_ui_storage_roots">Tallennussijainnit</string> + <string name="common_ui_app_storage">Sovelluksen tallennustila</string> + <string name="common_ui_move">Siirrรค</string> + <string name="library_games_move_to_app_storage_title">Siirrรค sisรคiseen</string> + <string name="library_games_move_to_download_folder_title">Siirrรค ulkoiseen</string> + <string name="library_games_move_to_app_storage_confirm">Siirretรครคnkรถ %1$s sisรคiseen tallennustilaan?</string> + <string name="library_games_move_to_download_folder_confirm">Siirretรครคnkรถ %1$s ulkoiseen tallennustilaan?</string> + <string name="library_games_moving">Siirretรครคn peliรค</string> + <string name="library_games_move_done">%1$s siirretty</string> + <string name="library_games_move_failed">Peliรค %1$s ei voitu siirtรครค</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Selaa paikallisia kansioita suoraan</string> <string name="common_ui_no_folders_available_here">Tรครคllรค ei ole kansioita saatavilla</string> <string name="common_ui_grant_all_files_access_browse">Myรถnnรค kaikkien tiedostojen kรคyttรถoikeus selataksesi kansioita suoraan.</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32-bittinen</string> <string name="hero_graphics_test_64">64-bittinen</string> <string name="hero_input_tests_title">Syรถtetestit</string> - <string name="library_games_add_custom_game">Lisรครค mukautettu peli</string> <string name="library_games_game_name">Pelin nimi</string> <string name="library_games_game_folder_mapped_drive">Pelikansio (yhdistetty peliasema)</string> <string name="library_games_select_exe_provide_name">Valitse exe ja anna nimi</string> <string name="library_games_sign_in_prompt">Kirjaudu kauppaan tai lisรครค mukautettu peli rakentaaksesi kirjastosi.</string> + <string name="library_games_add_title">Lisรครค peli tai sovellus</string> + <string name="library_games_add_linux_steam">Lisรครค Steam-asiakas</string> + <string name="library_games_type">Tyyppi</string> + <string name="library_games_type_game">Peli</string> + <string name="library_games_type_app">Sovellus</string> + <string name="library_games_select_executable_or_rom">Valitse suoritettava tiedosto tai konsolin ROM</string> + <string name="library_games_added">%1$s lisรคtty</string> + <string name="library_games_add_failed">Kohdetta %1$s ei voitu lisรคtรค</string> + <string name="linux_runtime_not_installed">Linux-ajoympรคristรถรค ei ole vielรค asennettu, joten kohdetta %1$s ei voi kรคynnistรครค. Asenna se kohdasta Linux-asiakas valikossa Asetukset > Kaupat.</string> + <string name="linux_client_button">Linux-asiakas</string> + <string name="linux_client_title">Linux Steam -asiakas</string> + <string name="linux_client_install_message">Lataa Linux-ajoympรคristรถn WinNativelta ja Steam-asiakkaan Valvelta ja asentaa molemmat. Lataus on yli 2 Gt, joten kรคytรค Wi-Fiรค.</string> + <string name="linux_client_installed_message">Linux-ajoympรคristรถ ja Steam-asiakas on asennettu, ja Steam on Kirjastossa.</string> + <string name="linux_client_failed_message">Asennus ei valmistunut. Tarkista yhteys ja yritรค uudelleen.</string> + <string name="linux_client_no_space_message">Vapaata tilaa ei ole tarpeeksi: tarvitaan %1$s, kรคytettรคvissรค on %2$s.</string> + <string name="linux_client_stage_connect">Yhdistetรครคn</string> + <string name="linux_client_stage_download_runtime">Ladataan Linux-ajoympรคristรถรค</string> + <string name="linux_client_stage_install_runtime">Asennetaan Linux-ajoympรคristรถรค</string> + <string name="linux_client_stage_download_proton">Ladataan Protonia</string> + <string name="linux_client_stage_install_proton">Asennetaan Protonia</string> + <string name="linux_client_stage_download_steam">Ladataan Steam-asiakasta</string> + <string name="linux_client_stage_install_steam">Asennetaan Steam-asiakasta</string> + <string name="linux_client_stage_library">Lisรคtรครคn Steam Kirjastoon</string> + <string name="linux_client_progress_amount">%1$s / %2$s</string> + <string name="linux_client_retry">Yritรค uudelleen</string> + <string name="linux_client_busy">Linux Steam -asiakasta asennetaan yhรค. Yritรค uudelleen, kun asennus on valmis.</string> + <string name="linux_client_update_message">Uudempi Linux-ympรคristรถ on saatavilla. Pรคivitys sรคilyttรครค Steam-kirjautumisen, asennetut pelit, tallennukset ja sรคiliรถt.</string> + <string name="linux_client_update_session_running">Linux-istunto on kรคynnissรค. Sulje se ja pรคivitรค sitten.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux-istunto pysรคhtyi heti kรคynnistyttyรครคn. Sen loki on kohdassa Asetukset > Virheenkorjaus > Avaa lokien hallinta; jaa se, jotta syy lรถytyy.</string> + <string name="linux_session_stopped">Linux-istunto pysรคhtyi itsestรครคn (tila %1$d). Sen loki on kohdassa Asetukset > Virheenkorjaus > Avaa lokien hallinta; jaa se, jotta syy lรถytyy.</string> + <string name="linux_session_trimmed">Android pysรคytti Linux-istunnon. Yleensรค se tarkoittaa, ettรค taustaprosessien raja tuli vastaan: ota kรคyttรถรถn โ€Poista aliprosessien rajoitukset kรคytรถstรคโ€ kehittรคjรคasetuksissa ja yritรค uudelleen.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Kaupat</string> @@ -1357,6 +1401,10 @@ Asennuspolku: <string name="setup_wizard_title">Ohjattu asennus</string> <string name="setup_wizard_advanced_user">Edistynyt kรคyttรคjรค</string> <string name="setup_wizard_next">Seuraava</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Valmis</string> <string name="setup_wizard_download_and_install">Lataa + asenna</string> <string name="setup_wizard_open_drivers">Avaa ajurit</string> @@ -2864,10 +2912,9 @@ Asennuspolku: <string name="update_apk_notice">Tรคmรค pรคivitys muuttaa itse sovellusta, joten Android pyytรครค sinua vahvistamaan asennuksen. Pelisi, sรคiliรถsi ja asetuksesi sรคilyvรคt.</string> <string name="update_up_to_date">WinNative on ajan tasalla.</string> <string name="update_unsupported_build">Sovelluksen sisรคisiรค pรคivityksiรค tarjotaan vain virallisissa allekirjoitetuissa julkaisuissa.</string> - <string name="settings_general_update_channel">Pรคivityskanava</string> - <string name="settings_general_update_channel_summary">Virallinen tarjoaa testattuja julkaisuja. Kehitys tarjoaa varhaisia koontiversioita.</string> + <string name="settings_general_update_source">Pรคivitysten lรคhde</string> + <string name="settings_general_update_source_summary">Vetopyynnรถstรค tehty koonti seuraa sitรค vetopyyntรถรค; virallinen asennus seuraa virallisia julkaisuja.</string> <string name="update_channel_official">Virallinen</string> - <string name="update_channel_development">Kehitys</string> <string name="networking_section_title">Verkko</string> <string name="networking_driver">Verkkoajuri</string> <string name="networking_driver_winnative">WinNative-verkkoajuri</string> @@ -2880,4 +2927,35 @@ Asennuspolku: <string name="steam_controller_left_pad_click">Vasemman kosketuslevyn painallus</string> <string name="steam_controller_right_pad_click">Oikean kosketuslevyn painallus</string> <string name="controller_test_generic">Yleinen</string> + <string name="container_display_server">Nรคyttรถpalvelin</string> + <string name="container_display_server_help">Kokeellinen. Istunto piirretรครคn sisรครคnrakennetun Wayland-koostajan kautta X-palvelimen sijaan.</string> + <string name="container_display_server_wayland_requirements">Wayland vaatii Adreno-nรคytรถnohjaimen ja Wayland-yhteensopivan Protonin, jossa on winewayland ja Wayland Turnip -ajuri. Asenna sellainen Sisรคltรถ-nรคkymรคstรค.</string> + <string name="container_display_server_needs_wayland_proton">Tรคssรค Protonissa ei ole winewaylandia eikรค Wayland Turnip -ajuria, joten kรคytetรครคn X11:tรค. Asenna Wayland-Proton-kerros Sisรคltรถ-nรคkymรคstรค ja valitse se sรคiliรถn Protoniksi.</string> + <string name="wayland_unavailable_fallback">Wayland ei ole kรคytettรคvissรค tรคlle pelille. Kรคynnistetรครคn X11:llรค.</string> + <string name="wayland_needs_turnip_driver">Wayland tarvitsee Turnip-nรคytรถnohjainajurin nรคyttรครคkseen pelin. Asenna sellainen Sisรคltรถ-nรคkymรคstรค ja valitse se nรคytรถnohjainajuriksi.</string> + <string name="containers_gamescope_new">Luo GameScope-sรคiliรถ</string> + <string name="containers_gamescope_create_failed">GameScope-sรคiliรถn luominen epรคonnistui</string> + <string name="linux_apps_need_gamescope_container">Luo ensin GameScope-sรคiliรถ kohdassa Asetukset โ€บ Sรคiliรถt.</string> + <string name="linux_runtime_missing">Linux-ajoympรคristรถรค ei ole asennettu, joten tรคtรค konttia ei voi vielรค kรคynnistรครค. Asenna se kohdasta Linux-asiakas valikossa Asetukset > Kaupat.</string> + <string name="container_display_server_gamescope">Gamescope piirtรครค aina Wayland-koostajan kautta.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Sisรคltyy Linux-ajoympรคristรถรถn. Ladatut Android-ajurit koskevat vain Wine- ja Proton-sรคiliรถitรค.</string> + <string name="linux_runtime_windows_program">Tรคmรค on Windows-ohjelma. Siirrรค se Wine-sรคiliรถรถn pikakuvakkeen asetuksissa kรคynnistรครคksesi sen.</string> + <string name="settings_content_badge_online">Verkossa</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Valitse ajurin asennuskohde. Tunnistetut paketit kรคyttรคvรคt automaattisesti oikeaa alustaa.</string> + <string name="settings_drivers_restore_defaults">Palauta oletukset</string> + <string name="settings_drivers_defaults_present">Oletuslรคhteet ovat jo kรคytรถssรค</string> + <string name="settings_drivers_active">Aktiivinen</string> + <string name="settings_drivers_bundled">Mukana tuleva Mesa</string> + <string name="settings_drivers_legacy">Linux-asiakasohjelman ajuri</string> + <string name="settings_drivers_routed">Ajuri tunnistettiin ja asennettiin alustalle %1$s.</string> + <string name="linux_child_processes_message">Android rajoittaa sovelluksen taustaprosessien mรครคrรครค, ja Linux-istunto kรคyttรครค niitรค paljon enemmรคn: Steam tai peli voi sulkeutua kesken pelin. Ota kรคyttรถรถn โ€Poista aliprosessien rajoitukset kรคytรถstรคโ€ kehittรคjรคasetuksissa. Avataanko ne nyt?</string> + <string name="linux_child_processes_adb">Android rajoittaa sovelluksen taustaprosessien mรครคrรครค, ja Linux-istunto kรคyttรครค niitรค paljon enemmรคn: Steam tai peli voi sulkeutua kesken pelin. Tรคssรค Android-versiossa ei ole sitรค asetusta. Suorita tietokoneella:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e1619d65f..77fbf2b91 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Restaurer l\'icรดne</string> <string name="common_ui_select_exe">Sรฉlectionner un EXE</string> <string name="common_ui_select_valid_exe_file">Veuillez sรฉlectionner un fichier .exe valide</string> + <string name="common_ui_select_executable">Sรฉlectionner un exรฉcutable</string> + <string name="common_ui_select_valid_executable">Sรฉlectionnez un exรฉcutable Windows ou Linux</string> <string name="common_ui_select_valid_icon_file">Veuillez sรฉlectionner un fichier .ico ou .png valide.</string> <string name="common_ui_exit">Quitter</string> <string name="common_ui_exit_app">Quitter l\'application</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Saisir le chemin</string> <string name="common_ui_path_not_found">Chemin introuvable</string> <string name="common_ui_storage_roots">Emplacements de stockage</string> + <string name="common_ui_app_storage">Stockage de l\'application</string> + <string name="common_ui_move">Dรฉplacer</string> + <string name="library_games_move_to_app_storage_title">Dรฉplacer vers l\'interne</string> + <string name="library_games_move_to_download_folder_title">Dรฉplacer vers l\'externe</string> + <string name="library_games_move_to_app_storage_confirm">Dรฉplacer %1$s vers le stockage interne ?</string> + <string name="library_games_move_to_download_folder_confirm">Dรฉplacer %1$s vers le stockage externe ?</string> + <string name="library_games_moving">Dรฉplacement du jeu</string> + <string name="library_games_move_done">%1$s dรฉplacรฉ</string> + <string name="library_games_move_failed">Impossible de dรฉplacer %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Parcourir directement les dossiers locaux</string> <string name="common_ui_no_folders_available_here">Aucun dossier disponible ici</string> <string name="common_ui_grant_all_files_access_browse">Accordez l\'accรจs complet aux fichiers pour parcourir directement les dossiers locaux.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Tests d\'entrรฉe</string> - <string name="library_games_add_custom_game">Ajouter un jeu personnalisรฉ</string> <string name="library_games_game_name">Nom du jeu</string> <string name="library_games_game_folder_mapped_drive">Dossier du jeu (lecteur de jeu mappรฉ)</string> <string name="library_games_select_exe_provide_name">Sรฉlectionnez un exe et fournissez un nom</string> <string name="library_games_sign_in_prompt">Connectez-vous ร  une boutique ou ajoutez un jeu personnalisรฉ pour crรฉer votre bibliothรจque.</string> + <string name="library_games_add_title">Ajouter un jeu ou une application</string> + <string name="library_games_add_linux_steam">Ajouter le client Steam</string> + <string name="library_games_type">Type</string> + <string name="library_games_type_game">Jeu</string> + <string name="library_games_type_app">Application</string> + <string name="library_games_select_executable_or_rom">Sรฉlectionnez un exรฉcutable ou une ROM de console</string> + <string name="library_games_added">%1$s ajoutรฉ</string> + <string name="library_games_add_failed">Impossible d\'ajouter %1$s</string> + <string name="linux_runtime_not_installed">L\'environnement Linux n\'est pas encore installรฉ, %1$s ne peut donc pas dรฉmarrer. Installez-le avec Client Linux dans Paramรจtres > Boutiques.</string> + <string name="linux_client_button">Client Linux</string> + <string name="linux_client_title">Client Steam Linux</string> + <string name="linux_client_install_message">Tรฉlรฉcharge l\'environnement Linux depuis WinNative et le client Steam depuis Valve, puis installe les deux. Le tรฉlรฉchargement dรฉpasse 2 Go, utilisez donc le Wi-Fi.</string> + <string name="linux_client_installed_message">L\'environnement Linux et le client Steam sont installรฉs, et Steam est dans la Bibliothรจque.</string> + <string name="linux_client_failed_message">L\'installation n\'a pas abouti. Vรฉrifiez votre connexion et rรฉessayez.</string> + <string name="linux_client_no_space_message">Espace libre insuffisant : %1$s nรฉcessaires, %2$s disponibles.</string> + <string name="linux_client_stage_connect">Connexion</string> + <string name="linux_client_stage_download_runtime">Tรฉlรฉchargement de l\'environnement Linux</string> + <string name="linux_client_stage_install_runtime">Installation de l\'environnement Linux</string> + <string name="linux_client_stage_download_proton">Tรฉlรฉchargement de Proton</string> + <string name="linux_client_stage_install_proton">Installation de Proton</string> + <string name="linux_client_stage_download_steam">Tรฉlรฉchargement du client Steam</string> + <string name="linux_client_stage_install_steam">Installation du client Steam</string> + <string name="linux_client_stage_library">Ajout de Steam ร  la Bibliothรจque</string> + <string name="linux_client_progress_amount">%1$s sur %2$s</string> + <string name="linux_client_retry">Rรฉessayer</string> + <string name="linux_client_busy">Le client Steam Linux est encore en cours d\'installation. Rรฉessayez une fois terminรฉ.</string> + <string name="linux_client_update_message">Un environnement Linux plus rรฉcent est disponible. La mise ร  jour conserve votre connexion Steam, vos jeux installรฉs, vos sauvegardes et vos conteneurs.</string> + <string name="linux_client_update_session_running">Une session Linux est en cours. Quittez-la, puis mettez ร  jour.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">La session Linux sโ€™est arrรชtรฉe dรจs son dรฉmarrage. Son journal se trouve dans Paramรจtres > Dรฉbogage > Ouvrir le gestionnaire des journaux ; partagez-le pour que la cause puisse รชtre trouvรฉe.</string> + <string name="linux_session_stopped">La session Linux sโ€™est arrรชtรฉe dโ€™elle-mรชme (รฉtat %1$d). Son journal se trouve dans Paramรจtres > Dรฉbogage > Ouvrir le gestionnaire des journaux ; partagez-le pour que la cause puisse รชtre trouvรฉe.</string> + <string name="linux_session_trimmed">Android a arrรชtรฉ la session Linux. Cela signifie gรฉnรฉralement que sa limite de processus en arriรจre-plan a รฉtรฉ atteinte : activez ยซ Dรฉsactiver les restrictions des processus enfants ยป dans les options pour dรฉveloppeurs, puis rรฉessayez.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Boutiques</string> @@ -1065,6 +1109,10 @@ Chemin installรฉ : <string name="setup_wizard_title">Assistant de configuration</string> <string name="setup_wizard_advanced_user">Utilisateur avancรฉ</string> <string name="setup_wizard_next">Suivant</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Terminer</string> <string name="setup_wizard_download_and_install">Tรฉlรฉcharger + Installer</string> <string name="setup_wizard_open_drivers">Ouvrir les pilotes</string> @@ -2866,10 +2914,9 @@ Chemin installรฉ : <string name="update_apk_notice">Cette mise ร  jour modifie l\'application elle-mรชme ; Android vous demandera donc de confirmer l\'installation. Vos jeux, conteneurs et paramรจtres sont conservรฉs.</string> <string name="update_up_to_date">WinNative est ร  jour.</string> <string name="update_unsupported_build">Les mises ร  jour intรฉgrรฉes ne sont proposรฉes que sur les versions officielles signรฉes.</string> - <string name="settings_general_update_channel">Canal de mise ร  jour</string> - <string name="settings_general_update_channel_summary">Officiel livre des versions testรฉes. Dรฉveloppement livre des builds anticipรฉs.</string> + <string name="settings_general_update_source">Source des mises ร  jour</string> + <string name="settings_general_update_source_summary">Une version issue dโ€™une pull request suit cette pull request ; une installation officielle suit les versions officielles.</string> <string name="update_channel_official">Officiel</string> - <string name="update_channel_development">Dรฉveloppement</string> <string name="networking_section_title">Rรฉseau</string> <string name="networking_driver">Pilote rรฉseau</string> <string name="networking_driver_winnative">Pilote rรฉseau WinNative</string> @@ -2882,5 +2929,36 @@ Chemin installรฉ : <string name="steam_controller_left_pad_click">Clic du pavรฉ gauche</string> <string name="steam_controller_right_pad_click">Clic du pavรฉ droit</string> <string name="controller_test_generic">Gรฉnรฉrique</string> + <string name="container_display_server">Serveur d\'affichage</string> + <string name="container_display_server_help">Expรฉrimental. La session est rendue via le compositeur Wayland intรฉgrรฉ au lieu du serveur X.</string> + <string name="container_display_server_wayland_requirements">Wayland nรฉcessite un GPU Adreno et un Proton compatible Wayland incluant winewayland et le pilote Turnip pour Wayland. Installez-en un depuis Contenus.</string> + <string name="container_display_server_needs_wayland_proton">Ce Proton n\'inclut ni winewayland ni le pilote Turnip pour Wayland, X11 est donc utilisรฉ. Installez une couche Proton Wayland depuis Contenus, puis sรฉlectionnez-la comme Proton du conteneur.</string> + <string name="wayland_unavailable_fallback">Wayland n\'est pas disponible pour ce jeu. Lancement sur X11.</string> + <string name="wayland_needs_turnip_driver">Wayland a besoin d\'un pilote graphique Turnip pour afficher le jeu. Installez-en un dans Contenus et sรฉlectionnez-le comme pilote graphique.</string> + <string name="containers_gamescope_new">Crรฉer le conteneur GameScope</string> + <string name="containers_gamescope_create_failed">Impossible de crรฉer le conteneur GameScope</string> + <string name="linux_apps_need_gamescope_container">Crรฉez d\'abord le conteneur GameScope dans Paramรจtres โ€บ Conteneurs.</string> + <string name="linux_runtime_missing">L\'environnement Linux n\'est pas installรฉ, ce conteneur ne peut donc pas encore dรฉmarrer. Installez-le avec Client Linux dans Paramรจtres > Boutiques.</string> + <string name="container_display_server_gamescope">Gamescope dessine toujours via le compositeur Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Intรฉgrรฉ ร  l\'environnement Linux. Les pilotes Android tรฉlรฉchargรฉs ne s\'appliquent qu\'aux conteneurs Wine et Proton.</string> + <string name="linux_runtime_windows_program">Ceci est un programme Windows. Dรฉplacez-le dans un conteneur Wine dans les paramรจtres du raccourci pour le lancer.</string> + <string name="settings_content_badge_online">En ligne</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Choisissez oรน installer le pilote. Les paquets reconnus utilisent automatiquement la bonne plateforme.</string> + <string name="settings_drivers_restore_defaults">Rรฉtablir les valeurs par dรฉfaut</string> + <string name="settings_drivers_defaults_present">Les dรฉpรดts par dรฉfaut sont dรฉjร  prรฉsents</string> + <string name="settings_drivers_active">Actif</string> + <string name="settings_drivers_bundled">Mesa intรฉgrรฉ</string> + <string name="settings_drivers_legacy">Pilote du client Linux</string> + <string name="settings_drivers_routed">Pilote identifiรฉ et installรฉ pour %1$s.</string> + <string name="linux_child_processes_message">Android limite le nombre de processus en arriรจre-plan dโ€™une application, et une session Linux en utilise bien plus : Steam ou un jeu peut รชtre arrรชtรฉ en cours de partie. Activez ยซย Dรฉsactiver les restrictions des processus enfantsย ยป dans les options pour dรฉveloppeurs. Les ouvrirย ?</string> + <string name="linux_child_processes_adb">Android limite le nombre de processus en arriรจre-plan dโ€™une application, et une session Linux en utilise bien plus : Steam ou un jeu peut รชtre arrรชtรฉ en cours de partie. Cette version dโ€™Android nโ€™a pas ce rรฉglage. Depuis un ordinateur, exรฉcutezย :\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index cebb3c24b..ab6bc1ab3 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">เคชเคฅ เคฆเคฐเฅเคœ เค•เคฐเฅ‡เค‚</string> <string name="common_ui_path_not_found">เคชเคฅ เคจเคนเฅ€เค‚ เคฎเคฟเคฒเคพ</string> <string name="common_ui_select_valid_exe_file">เค•เฅƒเคชเคฏเคพ เคฎเคพเคจเฅเคฏ .exe เคซเคผเคพเค‡เคฒ เคšเฅเคจเฅ‡เค‚</string> + <string name="common_ui_select_executable">เคเค•เฅเคœเคผเฅ€เค•เฅเคฏเฅ‚เคŸเฅ‡เคฌเคฒ เคšเฅเคจเฅ‡เค‚</string> + <string name="common_ui_select_valid_executable">เค•เฅ‹เคˆ Windows เคฏเคพ Linux เคเค•เฅเคœเคผเฅ€เค•เฅเคฏเฅ‚เคŸเฅ‡เคฌเคฒ เคšเฅเคจเฅ‡เค‚</string> <string name="common_ui_select_valid_icon_file">เค•เฅƒเคชเคฏเคพ เคฎเคพเคจเฅเคฏ .ico เคฏเคพ .png เคซเคผเคพเค‡เคฒ เคšเฅเคจเฅ‡เค‚เฅค</string> <string name="common_ui_exit">เคฌเคพเคนเคฐ เคจเคฟเค•เคฒเฅ‡เค‚</string> <string name="common_ui_exit_app">เคเคช เคธเฅ‡ เคฌเคพเคนเคฐ เคจเคฟเค•เคฒเฅ‡เค‚</string> @@ -148,6 +150,16 @@ <string name="store_game_branch_switch_installed">เคธเค‚เคธเฅเค•เคฐเคฃ %1$s เคชเคฐ เคธเฅ‡เคŸ เค•เคฟเคฏเคพ เค—เคฏเคพเฅค เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคซเคผเคพเค‡เคฒเฅ‡เค‚ เคฌเคฆเคฒเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เค…เคชเคกเฅ‡เคŸ เค•เฅ€ เคœเคพเคเคš เค•เคฐเฅ‡เค‚เฅค</string> <string name="common_ui_select">เคšเฅเคจเฅ‡เค‚</string> <string name="common_ui_storage_roots">เคธเฅเคŸเฅ‹เคฐเฅ‡เคœ เคฐเฅ‚เคŸ</string> + <string name="common_ui_app_storage">เคเคช เคธเฅเคŸเฅ‹เคฐเฅ‡เคœ</string> + <string name="common_ui_move">เคฒเฅ‡ เคœเคพเคเค</string> + <string name="library_games_move_to_app_storage_title">เค‡เค‚เคŸเคฐเคจเคฒ เคฎเฅ‡เค‚ เคฒเฅ‡ เคœเคพเคเค</string> + <string name="library_games_move_to_download_folder_title">เคเค•เฅเคธเคŸเคฐเฅเคจเคฒ เคฎเฅ‡เค‚ เคฒเฅ‡ เคœเคพเคเค</string> + <string name="library_games_move_to_app_storage_confirm">%1$s เค•เฅ‹ เค‡เค‚เคŸเคฐเคจเคฒ เคธเฅเคŸเฅ‹เคฐเฅ‡เคœ เคฎเฅ‡เค‚ เคฒเฅ‡ เคœเคพเคเค?</string> + <string name="library_games_move_to_download_folder_confirm">%1$s เค•เฅ‹ เคเค•เฅเคธเคŸเคฐเฅเคจเคฒ เคธเฅเคŸเฅ‹เคฐเฅ‡เคœ เคฎเฅ‡เค‚ เคฒเฅ‡ เคœเคพเคเค?</string> + <string name="library_games_moving">เค—เฅ‡เคฎ เคฒเฅ‡ เคœเคพเคฏเคพ เคœเคพ เคฐเคนเคพ เคนเฅˆ</string> + <string name="library_games_move_done">%1$s เคฒเฅ‡ เคœเคพเคฏเคพ เค—เคฏเคพ</string> + <string name="library_games_move_failed">%1$s เค•เฅ‹ เคฒเฅ‡ เคœเคพเคฏเคพ เคจเคนเฅ€เค‚ เคœเคพ เคธเค•เคพ</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">เคธเฅเคฅเคพเคจเฅ€เคฏ เคซเคผเฅ‹เคฒเฅเคกเคฐ เคธเฅ€เคงเฅ‡ เคฌเฅเคฐเคพเค‰เคœเคผ เค•เคฐเฅ‡เค‚</string> <string name="common_ui_no_folders_available_here">เคฏเคนเคพเค เค•เฅ‹เคˆ เคซเคผเฅ‹เคฒเฅเคกเคฐ เค‰เคชเคฒเคฌเฅเคง เคจเคนเฅ€เค‚</string> <string name="common_ui_grant_all_files_access_browse">เคซเคผเฅ‹เคฒเฅเคกเคฐเฅ‹เค‚ เค•เฅ‹ เคธเฅ€เคงเฅ‡ เคฌเฅเคฐเคพเค‰เคœเคผ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคธเคญเฅ€ เคซเคผเคพเค‡เคฒเฅ‹เค‚ เคคเค• เคชเคนเฅเค‚เคš เคชเฅเคฐเคฆเคพเคจ เค•เคฐเฅ‡เค‚เฅค</string> @@ -224,11 +236,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">เค‡เคจเคชเฅเคŸ เคชเคฐเฅ€เค•เฅเคทเคฃ</string> - <string name="library_games_add_custom_game">เค•เคธเฅเคŸเคฎ เค—เฅ‡เคฎ เคœเฅ‹เคกเคผเฅ‡เค‚</string> <string name="library_games_game_name">เค—เฅ‡เคฎ เค•เคพ เคจเคพเคฎ</string> <string name="library_games_game_folder_mapped_drive">เค—เฅ‡เคฎ เคซเคผเฅ‹เคฒเฅเคกเคฐ (เคฎเฅˆเคชเฅเคก เค—เฅ‡เคฎ เคกเฅเคฐเคพเค‡เคต)</string> <string name="library_games_select_exe_provide_name">เคเค• exe เคšเฅเคจเฅ‡เค‚ เค”เคฐ เคจเคพเคฎ เคฆเฅ‡เค‚</string> <string name="library_games_sign_in_prompt">เค•เคฟเคธเฅ€ เคธเฅเคŸเฅ‹เคฐ เคฎเฅ‡เค‚ เคธเคพเค‡เคจ เค‡เคจ เค•เคฐเฅ‡เค‚ เคฏเคพ เค…เคชเคจเฅ€ เคฒเคพเค‡เคฌเฅเคฐเฅ‡เคฐเฅ€ เคฌเคจเคพเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคเค• เค•เคธเฅเคŸเคฎ เค—เฅ‡เคฎ เคœเฅ‹เคกเคผเฅ‡เค‚เฅค</string> + <string name="library_games_add_title">เค—เฅ‡เคฎ เคฏเคพ เคเคช เคœเฅ‹เคกเคผเฅ‡เค‚</string> + <string name="library_games_add_linux_steam">Steam เค•เฅเคฒเคพเค‡เค‚เคŸ เคœเฅ‹เคกเคผเฅ‡เค‚</string> + <string name="library_games_type">เคชเฅเคฐเค•เคพเคฐ</string> + <string name="library_games_type_game">เค—เฅ‡เคฎ</string> + <string name="library_games_type_app">เคเคช</string> + <string name="library_games_select_executable_or_rom">เค•เฅ‹เคˆ เคเค•เฅเคœเคผเฅ€เค•เฅเคฏเฅ‚เคŸเฅ‡เคฌเคฒ เคฏเคพ เค•เค‚เคธเฅ‹เคฒ ROM เคšเฅเคจเฅ‡เค‚</string> + <string name="library_games_added">%1$s เคœเฅ‹เคกเคผเคพ เค—เคฏเคพ</string> + <string name="library_games_add_failed">%1$s เคœเฅ‹เคกเคผเคพ เคจเคนเฅ€เค‚ เคœเคพ เคธเค•เคพ</string> + <string name="linux_runtime_not_installed">Linux เคฐเคจเคŸเคพเค‡เคฎ เค…เคญเฅ€ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคจเคนเฅ€เค‚ เคนเฅˆ, เค‡เคธเคฒเคฟเค %1$s เคถเฅเคฐเฅ‚ เคจเคนเฅ€เค‚ เค•เคฟเคฏเคพ เคœเคพ เคธเค•เคคเคพเฅค เค‡เคธเฅ‡ เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ > เคธเฅเคŸเฅ‹เคฐ เคฎเฅ‡เค‚ Linux เค•เฅเคฒเคพเค‡เค‚เคŸ เคธเฅ‡ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_client_button">Linux เค•เฅเคฒเคพเค‡เค‚เคŸ</string> + <string name="linux_client_title">Linux Steam เค•เฅเคฒเคพเค‡เค‚เคŸ</string> + <string name="linux_client_install_message">WinNative เคธเฅ‡ Linux เคฐเคจเคŸเคพเค‡เคฎ เค”เคฐ Valve เคธเฅ‡ Steam เค•เฅเคฒเคพเค‡เค‚เคŸ เคกเคพเค‰เคจเคฒเฅ‹เคก เค•เคฐเคคเคพ เคนเฅˆ, เคซเคฟเคฐ เคฆเฅ‹เคจเฅ‹เค‚ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเคคเคพ เคนเฅˆเฅค เคกเคพเค‰เคจเคฒเฅ‹เคก 2 GB เคธเฅ‡ เค…เคงเคฟเค• เคนเฅˆ, เค‡เคธเคฒเคฟเค Wi-Fi เค•เคพ เค‰เคชเคฏเฅ‹เค— เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_client_installed_message">Linux เคฐเคจเคŸเคพเค‡เคฎ เค”เคฐ Steam เค•เฅเคฒเคพเค‡เค‚เคŸ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคนเฅˆเค‚, เค”เคฐ Steam เคฒเคพเค‡เคฌเฅเคฐเฅ‡เคฐเฅ€ เคฎเฅ‡เค‚ เคนเฅˆเฅค</string> + <string name="linux_client_failed_message">เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคชเฅ‚เคฐเคพ เคจเคนเฅ€เค‚ เคนเฅเค†เฅค เค…เคชเคจเคพ เค•เคจเฅ‡เค•เฅเคถเคจ เคœเคพเคเคšเฅ‡เค‚ เค”เคฐ เคซเคฟเคฐ เคธเฅ‡ เคชเฅเคฐเคฏเคพเคธ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_client_no_space_message">เคชเคฐเฅเคฏเคพเคชเฅเคค เค–เคพเคฒเฅ€ เคœเค—เคน เคจเคนเฅ€เค‚ เคนเฅˆ: %1$s เคšเคพเคนเคฟเค เค”เคฐ %2$s เค‰เคชเคฒเคฌเฅเคง เคนเฅˆเฅค</string> + <string name="linux_client_stage_connect">เค•เคจเฅ‡เค•เฅเคŸ เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_download_runtime">Linux เคฐเคจเคŸเคพเค‡เคฎ เคกเคพเค‰เคจเคฒเฅ‹เคก เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_install_runtime">Linux เคฐเคจเคŸเคพเค‡เคฎ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_download_proton">Proton เคกเคพเค‰เคจเคฒเฅ‹เคก เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_install_proton">Proton เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_download_steam">Steam เค•เฅเคฒเคพเค‡เค‚เคŸ เคกเคพเค‰เคจเคฒเฅ‹เคก เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_install_steam">Steam เค•เฅเคฒเคพเค‡เค‚เคŸ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_stage_library">Steam เค•เฅ‹ เคฒเคพเค‡เคฌเฅเคฐเฅ‡เคฐเฅ€ เคฎเฅ‡เค‚ เคœเฅ‹เคกเคผเคพ เคœเคพ เคฐเคนเคพ เคนเฅˆ</string> + <string name="linux_client_progress_amount">%2$s เคฎเฅ‡เค‚ เคธเฅ‡ %1$s</string> + <string name="linux_client_retry">เคซเคฟเคฐ เคธเฅ‡ เคชเฅเคฐเคฏเคพเคธ เค•เคฐเฅ‡เค‚</string> + <string name="linux_client_busy">Linux Steam เค•เฅเคฒเคพเค‡เค‚เคŸ เค…เคญเฅ€ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคนเฅ‹ เคฐเคนเคพ เคนเฅˆเฅค เคชเฅ‚เคฐเคพ เคนเฅ‹เคจเฅ‡ เคชเคฐ เคซเคฟเคฐ เคธเฅ‡ เคชเฅเคฐเคฏเคพเคธ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_client_update_message">เคจเคฏเคพ Linux เคฐเคจเคŸเคพเค‡เคฎ เค‰เคชเคฒเคฌเฅเคง เคนเฅˆเฅค เค…เคชเคกเฅ‡เคŸ เค•เคฐเคจเฅ‡ เคชเคฐ เค†เคชเค•เคพ Steam เคธเคพเค‡เคจ-เค‡เคจ, เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฟเค เค—เค เค—เฅ‡เคฎ, เคธเฅ‡เคต เค”เคฐ เค•เค‚เคŸเฅ‡เคจเคฐ เคฌเคจเฅ‡ เคฐเคนเคคเฅ‡ เคนเฅˆเค‚เฅค</string> + <string name="linux_client_update_session_running">เคเค• Linux เคธเคคเฅเคฐ เคšเคฒ เคฐเคนเคพ เคนเฅˆเฅค เค‰เคธเฅ‡ เคฌเค‚เคฆ เค•เคฐเฅ‡เค‚, เคซเคฟเคฐ เค…เคชเคกเฅ‡เคŸ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux เคธเคคเฅเคฐ เคถเฅเคฐเฅ‚ เคนเฅ‹เคคเฅ‡ เคนเฅ€ เคฐเฅเค• เค—เคฏเคพเฅค เค‡เคธเค•เคพ เคฒเฅ‰เค— เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ > เคกเฅ€เคฌเค— > เคฒเฅ‰เค— เคฎเฅˆเคจเฅ‡เคœเคฐ เค–เฅ‹เคฒเฅ‡เค‚ เคฎเฅ‡เค‚ เคนเฅˆ; เค•เคพเคฐเคฃ เคชเคคเคพ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เค‡เคธเฅ‡ เคธเคพเคเคพ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_session_stopped">Linux เคธเคคเฅเคฐ เค…เคชเคจเฅ‡ เค†เคช เคฐเฅเค• เค—เคฏเคพ (เคธเฅเคฅเคฟเคคเคฟ %1$d)เฅค เค‡เคธเค•เคพ เคฒเฅ‰เค— เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ > เคกเฅ€เคฌเค— > เคฒเฅ‰เค— เคฎเฅˆเคจเฅ‡เคœเคฐ เค–เฅ‹เคฒเฅ‡เค‚ เคฎเฅ‡เค‚ เคนเฅˆ; เค•เคพเคฐเคฃ เคชเคคเคพ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เค‡เคธเฅ‡ เคธเคพเคเคพ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="linux_session_trimmed">Android เคจเฅ‡ Linux เคธเคคเฅเคฐ เคฐเฅ‹เค• เคฆเคฟเคฏเคพเฅค เค†เคฎเคคเฅŒเคฐ เคชเคฐ เค‡เคธเค•เคพ เคฎเคคเคฒเคฌ เคนเฅˆ เค•เคฟ เคฌเฅˆเค•เค—เฅเคฐเคพเค‰เค‚เคก เคชเฅเคฐเฅ‹เคธเฅ‡เคธ เค•เฅ€ เคธเฅ€เคฎเคพ เคชเฅ‚เคฐเฅ€ เคนเฅ‹ เค—เคˆ: เคกเฅ‡เคตเคฒเคชเคฐ เคตเคฟเค•เคฒเฅเคช เคฎเฅ‡เค‚ โ€œเคšเคพเค‡เคฒเฅเคก เคชเฅเคฐเฅ‹เคธเฅ‡เคธ เคชเฅเคฐเคคเคฟเคฌเค‚เคง เค…เค•เฅเคทเคฎ เค•เคฐเฅ‡เค‚โ€ เคšเคพเคฒเฅ‚ เค•เคฐเฅ‡เค‚ เค”เคฐ เคซเคฟเคฐ เคธเฅ‡ เค•เฅ‹เคถเคฟเคถ เค•เคฐเฅ‡เค‚เฅค</string> <string name="stores_accounts_title">เคธเฅเคŸเฅ‹เคฐ</string> <string name="stores_accounts_steam_integration_title">Steam</string> <string name="stores_accounts_shared_downloads_folder">เคธเคพเคเคพ เคกเคพเค‰เคจเคฒเฅ‹เคก เคซเคผเฅ‹เคฒเฅเคกเคฐ</string> @@ -1135,6 +1179,10 @@ <string name="setup_wizard_title">เคธเฅ‡เคŸเค…เคช เคตเคฟเคœเคผเคพเคฐเฅเคก</string> <string name="setup_wizard_advanced_user">เค‰เคจเฅเคจเคค เค‰เคชเคฏเฅ‹เค—เค•เคฐเฅเคคเคพ</string> <string name="setup_wizard_next">เค…เค—เคฒเคพ</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">เคธเคฎเคพเคชเฅเคค</string> <string name="setup_wizard_download_and_install">เคกเคพเค‰เคจเคฒเฅ‹เคก + เค‡เค‚เคธเฅเคŸเฅ‰เคฒ</string> <string name="setup_wizard_open_drivers">เคกเฅเคฐเคพเค‡เคตเคฐ เค–เฅ‹เคฒเฅ‡เค‚</string> @@ -2760,10 +2808,9 @@ <string name="update_apk_notice">เคฏเคน เค…เคชเคกเฅ‡เคŸ เคเคช เค•เฅ‹ เคนเฅ€ เคฌเคฆเคฒเคคเคพ เคนเฅˆ, เค‡เคธเคฒเคฟเค Android เค†เคชเคธเฅ‡ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เฅ€ เคชเฅเคทเฅเคŸเคฟ เคฎเคพเคเค—เฅ‡เค—เคพเฅค เค†เคชเค•เฅ‡ เค—เฅ‡เคฎ, เค•เค‚เคŸเฅ‡เคจเคฐ เค”เคฐ เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ เคฌเคจเฅ€ เคฐเคนเคคเฅ€ เคนเฅˆเค‚เฅค</string> <string name="update_up_to_date">WinNative เค…เคฆเฅเคฏเคคเคฟเคค เคนเฅˆเฅค</string> <string name="update_unsupported_build">เค‡เคจ-เคเคช เค…เคชเคกเฅ‡เคŸ เค•เฅ‡เคตเคฒ เค†เคงเคฟเค•เคพเคฐเคฟเค• เคนเคธเฅเคคเคพเค•เฅเคทเคฐเคฟเคค เคฐเคฟเคฒเฅ€เคœเคผ เคชเคฐ เคนเฅ€ เคฆเคฟเค เคœเคพเคคเฅ‡ เคนเฅˆเค‚เฅค</string> - <string name="settings_general_update_channel">เค…เคชเคกเฅ‡เคŸ เคšเฅˆเคจเคฒ</string> - <string name="settings_general_update_channel_summary">เค†เคงเคฟเค•เคพเคฐเคฟเค• เคชเคฐเค–เฅ€ เคนเฅเคˆ เคฐเคฟเคฒเฅ€เคœเคผ เคฆเฅ‡เคคเคพ เคนเฅˆเฅค เคกเฅ‡เคตเคฒเคชเคฎเฅ‡เค‚เคŸ เคถเฅเคฐเฅเค†เคคเฅ€ เคฌเคฟเคฒเฅเคก เคฆเฅ‡เคคเคพ เคนเฅˆเฅค</string> + <string name="settings_general_update_source">เค…เคชเคกเฅ‡เคŸ เคธเฅเคฐเฅ‹เคค</string> + <string name="settings_general_update_source_summary">เค•เคฟเคธเฅ€ เคชเฅเคฒ เคฐเคฟเค•เฅเคตเฅ‡เคธเฅเคŸ เคธเฅ‡ เคฌเคจเคพ เคฌเคฟเคฒเฅเคก เค‰เคธเฅ€ เคชเฅเคฒ เคฐเคฟเค•เฅเคตเฅ‡เคธเฅเคŸ เค•เคพ เค…เคจเฅเคธเคฐเคฃ เค•เคฐเคคเคพ เคนเฅˆ; เค†เคงเคฟเค•เคพเคฐเคฟเค• เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค†เคงเคฟเค•เคพเคฐเคฟเค• เคฐเคฟเคฒเฅ€เคœเคผ เค•เคพเฅค</string> <string name="update_channel_official">เค†เคงเคฟเค•เคพเคฐเคฟเค•</string> - <string name="update_channel_development">เคกเฅ‡เคตเคฒเคชเคฎเฅ‡เค‚เคŸ</string> <string name="networking_section_title">เคจเฅ‡เคŸเคตเคฐเฅเค•เคฟเค‚เค—</string> <string name="networking_driver">เคจเฅ‡เคŸเคตเคฐเฅเค• เคกเฅเคฐเคพเค‡เคตเคฐ</string> <string name="networking_driver_winnative">WinNative เคจเฅ‡เคŸเคตเคฐเฅเค• เคกเฅเคฐเคพเค‡เคตเคฐ</string> @@ -2815,4 +2862,35 @@ <string name="steam_controller_left_pad_click">เคฌเคพเคเค เคชเฅˆเคก เคชเคฐ เค•เฅเคฒเคฟเค•</string> <string name="steam_controller_right_pad_click">เคฆเคพเคเค เคชเฅˆเคก เคชเคฐ เค•เฅเคฒเคฟเค•</string> <string name="controller_test_generic">เคธเคพเคฎเคพเคจเฅเคฏ</string> + <string name="container_display_server">เคกเคฟเคธเฅเคชเฅเคฒเฅ‡ เคธเคฐเฅเคตเคฐ</string> + <string name="container_display_server_help">เคชเฅเคฐเคพเคฏเฅ‹เค—เคฟเค•เฅค เคธเคคเฅเคฐ X เคธเคฐเฅเคตเคฐ เค•เฅ‡ เคฌเคœเคพเคฏ เค…เค‚เคคเคฐเฅเคจเคฟเคนเคฟเคค Wayland เค•เค‚เคชเฅ‹เคœเคผเคฟเคŸเคฐ เค•เฅ‡ เคฎเคพเคงเฅเคฏเคฎ เคธเฅ‡ เคฌเคจเคพเคฏเคพ เคœเคพเคคเคพ เคนเฅˆเฅค</string> + <string name="container_display_server_wayland_requirements">Wayland เค•เฅ‡ เคฒเคฟเค Adreno GPU เค”เคฐ winewayland เคคเคฅเคพ Wayland Turnip เคกเฅเคฐเคพเค‡เคตเคฐ เคตเคพเคฒเคพ Wayland-เคธเค•เฅเคทเคฎ Proton เคšเคพเคนเคฟเคเฅค เคธเคพเคฎเค—เฅเคฐเฅ€ เคธเฅ‡ เคเค• เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="container_display_server_needs_wayland_proton">เค‡เคธ Proton เคฎเฅ‡เค‚ winewayland เคฏเคพ Wayland Turnip เคกเฅเคฐเคพเค‡เคตเคฐ เคจเคนเฅ€เค‚ เคนเฅˆ, เค‡เคธเคฒเคฟเค X11 เค‰เคชเคฏเฅ‹เค— เคนเฅ‹เคคเคพ เคนเฅˆเฅค เคธเคพเคฎเค—เฅเคฐเฅ€ เคธเฅ‡ เคเค• Wayland Proton เคฒเฅ‡เคฏเคฐ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเฅ‡เค‚ เค”เคฐ เค‰เคธเฅ‡ เค•เค‚เคŸเฅ‡เคจเคฐ เค•เฅ‡ Proton เค•เฅ‡ เคฐเฅ‚เคช เคฎเฅ‡เค‚ เคšเฅเคจเฅ‡เค‚เฅค</string> + <string name="wayland_unavailable_fallback">เค‡เคธ เค—เฅ‡เคฎ เค•เฅ‡ เคฒเคฟเค Wayland เค‰เคชเคฒเคฌเฅเคง เคจเคนเฅ€เค‚ เคนเฅˆเฅค X11 เคชเคฐ เคฒเฅ‰เคจเฅเคš เค•เคฟเคฏเคพ เคœเคพ เคฐเคนเคพ เคนเฅˆเฅค</string> + <string name="wayland_needs_turnip_driver">เค—เฅ‡เคฎ เคฆเคฟเค–เคพเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค Wayland เค•เฅ‹ Turnip เค—เฅเคฐเคพเคซเคผเคฟเค•เฅเคธ เคกเฅเคฐเคพเค‡เคตเคฐ เคšเคพเคนเคฟเคเฅค เคธเคพเคฎเค—เฅเคฐเฅ€ เคธเฅ‡ เคเค• เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเฅ‡เค‚ เค”เคฐ เค‰เคธเฅ‡ เค—เฅเคฐเคพเคซเคผเคฟเค•เฅเคธ เคกเฅเคฐเคพเค‡เคตเคฐ เค•เฅ‡ เคฐเฅ‚เคช เคฎเฅ‡เค‚ เคšเฅเคจเฅ‡เค‚เฅค</string> + <string name="containers_gamescope_new">GameScope เค•เค‚เคŸเฅ‡เคจเคฐ เคฌเคจเคพเคเค</string> + <string name="containers_gamescope_create_failed">GameScope เค•เค‚เคŸเฅ‡เคจเคฐ เคจเคนเฅ€เค‚ เคฌเคจเคพเคฏเคพ เคœเคพ เคธเค•เคพ</string> + <string name="linux_apps_need_gamescope_container">เคชเคนเคฒเฅ‡ เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ โ€บ เค•เค‚เคŸเฅ‡เคจเคฐ เคฎเฅ‡เค‚ GameScope เค•เค‚เคŸเฅ‡เคจเคฐ เคฌเคจเคพเคเคเฅค</string> + <string name="linux_runtime_missing">Linux เคฐเคจเคŸเคพเค‡เคฎ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เคจเคนเฅ€เค‚ เคนเฅˆ, เค‡เคธเคฒเคฟเค เคฏเคน เค•เค‚เคŸเฅ‡เคจเคฐ เค…เคญเฅ€ เคถเฅเคฐเฅ‚ เคจเคนเฅ€เค‚ เคนเฅ‹ เคธเค•เคคเคพเฅค เค‡เคธเฅ‡ เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ > เคธเฅเคŸเฅ‹เคฐ เคฎเฅ‡เค‚ Linux เค•เฅเคฒเคพเค‡เค‚เคŸ เคธเฅ‡ เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเฅ‡เค‚เฅค</string> + <string name="container_display_server_gamescope">Gamescope เคนเคฎเฅ‡เคถเคพ Wayland เค•เค‚เคชเฅ‹เคœเคผเคฟเคŸเคฐ เค•เฅ‡ เคฎเคพเคงเฅเคฏเคฎ เคธเฅ‡ เคกเฅเคฐเฅ‰ เค•เคฐเคคเคพ เคนเฅˆเฅค</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Linux เคฐเคจเคŸเคพเค‡เคฎ เคฎเฅ‡เค‚ เคถเคพเคฎเคฟเคฒเฅค เคกเคพเค‰เคจเคฒเฅ‹เคก เค•เคฟเค เค—เค Android เคกเฅเคฐเคพเค‡เคตเคฐ เค•เฅ‡เคตเคฒ Wine เค”เคฐ Proton เค•เค‚เคŸเฅ‡เคจเคฐเฅ‹เค‚ เคชเคฐ เคฒเคพเค—เฅ‚ เคนเฅ‹เคคเฅ‡ เคนเฅˆเค‚เฅค</string> + <string name="linux_runtime_windows_program">เคฏเคน เคเค• Windows เคชเฅเคฐเฅ‹เค—เฅเคฐเคพเคฎ เคนเฅˆเฅค เค‡เคธเฅ‡ เคšเคฒเคพเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคถเฅ‰เคฐเฅเคŸเค•เคŸ เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ เคฎเฅ‡เค‚ เค‡เคธเฅ‡ เค•เคฟเคธเฅ€ Wine เค•เค‚เคŸเฅ‡เคจเคฐ เคฎเฅ‡เค‚ เคฒเฅ‡ เคœเคพเคเคเฅค</string> + <string name="settings_content_badge_online">เค‘เคจเคฒเคพเค‡เคจ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">เคกเฅเคฐเคพเค‡เคตเคฐ เค•เคนเคพเค เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฐเคจเคพ เคนเฅˆ, เคšเฅเคจเฅ‡เค‚เฅค เคชเคนเคšเคพเคจเฅ‡ เค—เค เคชเฅˆเค•เฅ‡เคœ เค…เคชเคจเฅ‡ เค†เคช เคธเคนเฅ€ เคชเฅเคฒเฅ‡เคŸเคซเคผเฅ‰เคฐเฅเคฎ เค•เคพ เค‰เคชเคฏเฅ‹เค— เค•เคฐเคคเฅ‡ เคนเฅˆเค‚เฅค</string> + <string name="settings_drivers_restore_defaults">เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ เคฌเคนเคพเคฒ เค•เคฐเฅ‡เค‚</string> + <string name="settings_drivers_defaults_present">เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ เคฐเคฟเคชเฅ‰เคœเคผเคฟเคŸเคฐเฅ€ เคชเคนเคฒเฅ‡ เคธเฅ‡ เคฎเฅŒเคœเฅ‚เคฆ เคนเฅˆเค‚</string> + <string name="settings_drivers_active">เคธเค•เฅเคฐเคฟเคฏ</string> + <string name="settings_drivers_bundled">เคฌเค‚เคกเคฒ เค•เคฟเคฏเคพ เค—เคฏเคพ Mesa</string> + <string name="settings_drivers_legacy">Linux เค•เฅเคฒเคพเค‡เค‚เคŸ เคกเฅเคฐเคพเค‡เคตเคฐ</string> + <string name="settings_drivers_routed">เคกเฅเคฐเคพเค‡เคตเคฐ เค•เฅ€ เคชเคนเคšเคพเคจ เค•เคฐเค•เฅ‡ %1$s เค•เฅ‡ เคฒเคฟเค เค‡เค‚เคธเฅเคŸเฅ‰เคฒ เค•เคฟเคฏเคพ เค—เคฏเคพเฅค</string> + <string name="linux_child_processes_message">Android เค•เคฟเคธเฅ€ เคเคช เค•เฅ€ เคฌเฅˆเค•เค—เฅเคฐเคพเค‰เค‚เคก เคชเฅเคฐเค•เฅเคฐเคฟเคฏเคพเค“เค‚ เค•เฅ€ เคธเค‚เค–เฅเคฏเคพ เคธเฅ€เคฎเคฟเคค เค•เคฐเคคเคพ เคนเฅˆ, เค”เคฐ Linux เคธเคคเฅเคฐ เค‰เคธเคธเฅ‡ เค•เคนเฅ€เค‚ เค…เคงเคฟเค• เคšเคฒเคพเคคเคพ เคนเฅˆ: เค–เฅ‡เคฒเคคเฅ‡ เคธเคฎเคฏ Steam เคฏเคพ เค—เฅ‡เคฎ เคฌเค‚เคฆ เคนเฅ‹ เคธเค•เคคเคพ เคนเฅˆเฅค เคกเฅ‡เคตเคฒเคชเคฐ เคตเคฟเค•เคฒเฅเคชเฅ‹เค‚ เคฎเฅ‡เค‚ โ€œเคšเคพเค‡เคฒเฅเคก เคชเฅเคฐเฅ‹เคธเฅ‡เคธ เคชเฅเคฐเคคเคฟเคฌเค‚เคง เค…เค•เฅเคทเคฎ เค•เคฐเฅ‡เค‚โ€ เคšเคพเคฒเฅ‚ เค•เคฐเฅ‡เค‚เฅค เค…เคญเฅ€ เค–เฅ‹เคฒเฅ‡เค‚?</string> + <string name="linux_child_processes_adb">Android เค•เคฟเคธเฅ€ เคเคช เค•เฅ€ เคฌเฅˆเค•เค—เฅเคฐเคพเค‰เค‚เคก เคชเฅเคฐเค•เฅเคฐเคฟเคฏเคพเค“เค‚ เค•เฅ€ เคธเค‚เค–เฅเคฏเคพ เคธเฅ€เคฎเคฟเคค เค•เคฐเคคเคพ เคนเฅˆ, เค”เคฐ Linux เคธเคคเฅเคฐ เค‰เคธเคธเฅ‡ เค•เคนเฅ€เค‚ เค…เคงเคฟเค• เคšเคฒเคพเคคเคพ เคนเฅˆ: เค–เฅ‡เคฒเคคเฅ‡ เคธเคฎเคฏ Steam เคฏเคพ เค—เฅ‡เคฎ เคฌเค‚เคฆ เคนเฅ‹ เคธเค•เคคเคพ เคนเฅˆเฅค Android เค•เฅ‡ เค‡เคธ เคธเค‚เคธเฅเค•เคฐเคฃ เคฎเฅ‡เค‚ เคฏเคน เคตเคฟเค•เคฒเฅเคช เคจเคนเฅ€เค‚ เคนเฅˆเฅค เค•เค‚เคชเฅเคฏเฅ‚เคŸเคฐ เคธเฅ‡ เคšเคฒเคพเคเค:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e85af9389..25c126507 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Ripristina icona</string> <string name="common_ui_select_exe">Seleziona EXE</string> <string name="common_ui_select_valid_exe_file">Seleziona un file .exe valido</string> + <string name="common_ui_select_executable">Seleziona eseguibile</string> + <string name="common_ui_select_valid_executable">Seleziona un eseguibile Windows o Linux</string> <string name="common_ui_select_valid_icon_file">Seleziona un file .ico o .png valido.</string> <string name="common_ui_exit">Esci</string> <string name="common_ui_exit_app">Esci dall\'app</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Inserisci percorso</string> <string name="common_ui_path_not_found">Percorso non trovato</string> <string name="common_ui_storage_roots">Percorsi di archiviazione</string> + <string name="common_ui_app_storage">Memoria dell\'app</string> + <string name="common_ui_move">Sposta</string> + <string name="library_games_move_to_app_storage_title">Sposta in interna</string> + <string name="library_games_move_to_download_folder_title">Sposta in esterna</string> + <string name="library_games_move_to_app_storage_confirm">Spostare %1$s nella memoria interna?</string> + <string name="library_games_move_to_download_folder_confirm">Spostare %1$s nella memoria esterna?</string> + <string name="library_games_moving">Spostamento del gioco</string> + <string name="library_games_move_done">%1$s spostato</string> + <string name="library_games_move_failed">Impossibile spostare %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Sfoglia direttamente le cartelle locali</string> <string name="common_ui_no_folders_available_here">Nessuna cartella disponibile qui</string> <string name="common_ui_grant_all_files_access_browse">Concedi l\'accesso completo ai file per sfogliare direttamente le cartelle locali.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Test di input</string> - <string name="library_games_add_custom_game">Aggiungi gioco personalizzato</string> <string name="library_games_game_name">Nome del gioco</string> <string name="library_games_game_folder_mapped_drive">Cartella del gioco (unitร  di gioco mappata)</string> <string name="library_games_select_exe_provide_name">Seleziona un exe e inserisci un nome</string> <string name="library_games_sign_in_prompt">Accedi a un negozio o aggiungi un gioco personalizzato per costruire la tua libreria.</string> + <string name="library_games_add_title">Aggiungi gioco o app</string> + <string name="library_games_add_linux_steam">Aggiungi client Steam</string> + <string name="library_games_type">Tipo</string> + <string name="library_games_type_game">Gioco</string> + <string name="library_games_type_app">App</string> + <string name="library_games_select_executable_or_rom">Seleziona un eseguibile o una ROM per console</string> + <string name="library_games_added">%1$s aggiunto</string> + <string name="library_games_add_failed">Impossibile aggiungere %1$s</string> + <string name="linux_runtime_not_installed">Il runtime Linux non รจ ancora installato, quindi %1$s non puรฒ essere avviato. Installalo con Client Linux in Impostazioni > Negozi.</string> + <string name="linux_client_button">Client Linux</string> + <string name="linux_client_title">Client Steam per Linux</string> + <string name="linux_client_install_message">Scarica il runtime Linux da WinNative e il client Steam da Valve, poi li installa entrambi. Il download supera 2 GB, quindi usa il Wi-Fi.</string> + <string name="linux_client_installed_message">Il runtime Linux e il client Steam sono installati, e Steam รจ nella Libreria.</string> + <string name="linux_client_failed_message">L\'installazione non รจ stata completata. Controlla la connessione e riprova.</string> + <string name="linux_client_no_space_message">Spazio libero insufficiente: servono %1$s e ne sono disponibili %2$s.</string> + <string name="linux_client_stage_connect">Connessione</string> + <string name="linux_client_stage_download_runtime">Download del runtime Linux</string> + <string name="linux_client_stage_install_runtime">Installazione del runtime Linux</string> + <string name="linux_client_stage_download_proton">Download di Proton</string> + <string name="linux_client_stage_install_proton">Installazione di Proton</string> + <string name="linux_client_stage_download_steam">Download del client Steam</string> + <string name="linux_client_stage_install_steam">Installazione del client Steam</string> + <string name="linux_client_stage_library">Aggiunta di Steam alla Libreria</string> + <string name="linux_client_progress_amount">%1$s di %2$s</string> + <string name="linux_client_retry">Riprova</string> + <string name="linux_client_busy">Il client Steam per Linux รจ ancora in installazione. Riprova al termine.</string> + <string name="linux_client_update_message">รˆ disponibile un ambiente Linux piรน recente. L\'aggiornamento mantiene l\'accesso a Steam, i giochi installati, i salvataggi e i container.</string> + <string name="linux_client_update_session_running">รˆ in corso una sessione Linux. Chiudila, poi aggiorna.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">La sessione Linux si รจ interrotta appena avviata. Il suo log si trova in Impostazioni > Debug > Apri gestione log; condividilo per individuare la causa.</string> + <string name="linux_session_stopped">La sessione Linux si รจ interrotta da sola (stato %1$d). Il suo log si trova in Impostazioni > Debug > Apri gestione log; condividilo per individuare la causa.</string> + <string name="linux_session_trimmed">Android ha interrotto la sessione Linux. Di solito significa che รจ stato raggiunto il limite di processi in background: attiva โ€œDisattiva restrizioni sui processi figliโ€ nelle Opzioni sviluppatore e riprova.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Negozi</string> @@ -1065,6 +1109,10 @@ Percorso installato: <string name="setup_wizard_title">Configurazione guidata</string> <string name="setup_wizard_advanced_user">Utente avanzato</string> <string name="setup_wizard_next">Avanti</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Fine</string> <string name="setup_wizard_download_and_install">Scarica + Installa</string> <string name="setup_wizard_open_drivers">Apri driver</string> @@ -2866,10 +2914,9 @@ Percorso installato: <string name="update_apk_notice">Questo aggiornamento modifica l\'app stessa, quindi Android ti chiederร  di confermare l\'installazione. I tuoi giochi, contenitori e impostazioni vengono mantenuti.</string> <string name="update_up_to_date">WinNative รจ aggiornato.</string> <string name="update_unsupported_build">Gli aggiornamenti in-app sono disponibili solo sulle release ufficiali firmate.</string> - <string name="settings_general_update_channel">Canale di aggiornamento</string> - <string name="settings_general_update_channel_summary">Ufficiale distribuisce release testate. Sviluppo distribuisce build preliminari.</string> + <string name="settings_general_update_source">Origine aggiornamenti</string> + <string name="settings_general_update_source_summary">Una build di una pull request segue quella pull request; unโ€™installazione ufficiale segue le versioni ufficiali.</string> <string name="update_channel_official">Ufficiale</string> - <string name="update_channel_development">Sviluppo</string> <string name="networking_section_title">Rete</string> <string name="networking_driver">Driver di rete</string> <string name="networking_driver_winnative">Driver di rete WinNative</string> @@ -2882,5 +2929,36 @@ Percorso installato: <string name="steam_controller_left_pad_click">Clic del pad sinistro</string> <string name="steam_controller_right_pad_click">Clic del pad destro</string> <string name="controller_test_generic">Generico</string> + <string name="container_display_server">Server grafico</string> + <string name="container_display_server_help">Sperimentale. La sessione viene disegnata tramite il compositor Wayland integrato invece del server X.</string> + <string name="container_display_server_wayland_requirements">Wayland richiede una GPU Adreno e un Proton compatibile con Wayland con winewayland e il driver Turnip per Wayland. Installane uno da Contenuti.</string> + <string name="container_display_server_needs_wayland_proton">Questo Proton non include winewayland nรฉ il driver Turnip per Wayland, quindi viene usato X11. Installa un layer Proton Wayland da Contenuti e selezionalo come Proton del container.</string> + <string name="wayland_unavailable_fallback">Wayland non รจ disponibile per questo gioco. Avvio su X11.</string> + <string name="wayland_needs_turnip_driver">Wayland ha bisogno di un driver grafico Turnip per mostrare il gioco. Installane uno da Contenuti e selezionalo come driver grafico.</string> + <string name="containers_gamescope_new">Crea contenitore GameScope</string> + <string name="containers_gamescope_create_failed">Impossibile creare il contenitore GameScope</string> + <string name="linux_apps_need_gamescope_container">Crea prima il contenitore GameScope in Impostazioni โ€บ Contenitori.</string> + <string name="linux_runtime_missing">Il runtime Linux non รจ installato, quindi questo container non puรฒ ancora avviarsi. Installalo con Client Linux in Impostazioni > Negozi.</string> + <string name="container_display_server_gamescope">Gamescope disegna sempre tramite il compositor Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Integrato nel runtime Linux. I driver Android scaricati valgono solo per i contenitori Wine e Proton.</string> + <string name="linux_runtime_windows_program">Questo รจ un programma Windows. Spostalo in un contenitore Wine nelle impostazioni del collegamento per avviarlo.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Scegli dove installare il driver. I pacchetti riconosciuti usano automaticamente la piattaforma corretta.</string> + <string name="settings_drivers_restore_defaults">Ripristina predefiniti</string> + <string name="settings_drivers_defaults_present">I repository predefiniti sono giร  presenti</string> + <string name="settings_drivers_active">Attivo</string> + <string name="settings_drivers_bundled">Mesa incluso</string> + <string name="settings_drivers_legacy">Driver del client Linux</string> + <string name="settings_drivers_routed">Driver identificato e installato per %1$s.</string> + <string name="linux_child_processes_message">Android limita quanti processi in background puรฒ avere unโ€™app, e una sessione Linux ne usa molti di piรน: Steam o un gioco puรฒ essere chiuso mentre giochi. Attiva โ€œDisattiva restrizioni sui processi figliโ€ nelle Opzioni sviluppatore. Aprirle ora?</string> + <string name="linux_child_processes_adb">Android limita quanti processi in background puรฒ avere unโ€™app, e una sessione Linux ne usa molti di piรน: Steam o un gioco puรฒ essere chiuso mentre giochi. Questa versione di Android non ha quellโ€™opzione. Da un computer esegui:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6054e1ba3..883174171 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">ใƒ‘ใ‚นใ‚’ๅ…ฅๅŠ›</string> <string name="common_ui_path_not_found">ใƒ‘ใ‚นใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“</string> <string name="common_ui_select_valid_exe_file">ๆœ‰ๅŠนใช.exeใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„</string> + <string name="common_ui_select_executable">ๅฎŸ่กŒใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠž</string> + <string name="common_ui_select_valid_executable">Windows ใพใŸใฏ Linux ใฎๅฎŸ่กŒใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„</string> <string name="common_ui_select_valid_icon_file">ๆœ‰ๅŠนใช.icoใพใŸใฏ.pngใƒ•ใ‚กใ‚คใƒซใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚</string> <string name="common_ui_exit">็ต‚ไบ†</string> <string name="common_ui_exit_app">ใ‚ขใƒ—ใƒชใ‚’็ต‚ไบ†</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">ใƒใƒผใ‚ธใƒงใƒณใ‚’ %1$s ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚ใ€Œๆ›ดๆ–ฐใ‚’็ขบ่ชใ€ใงใ‚คใƒณใ‚นใƒˆใƒผใƒซๆธˆใฟใฎใƒ•ใ‚กใ‚คใƒซใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใฆใใ ใ•ใ„ใ€‚</string> <string name="common_ui_select">้ธๆŠž</string> <string name="common_ui_storage_roots">ใ‚นใƒˆใƒฌใƒผใ‚ธใฎใƒซใƒผใƒˆ</string> + <string name="common_ui_app_storage">ใ‚ขใƒ—ใƒชใ‚นใƒˆใƒฌใƒผใ‚ธ</string> + <string name="common_ui_move">็งปๅ‹•</string> + <string name="library_games_move_to_app_storage_title">ๅ†…้ƒจใธ็งปๅ‹•</string> + <string name="library_games_move_to_download_folder_title">ๅค–้ƒจใธ็งปๅ‹•</string> + <string name="library_games_move_to_app_storage_confirm">%1$s ใ‚’ๅ†…้ƒจใ‚นใƒˆใƒฌใƒผใ‚ธใธ็งปๅ‹•ใ—ใพใ™ใ‹๏ผŸ</string> + <string name="library_games_move_to_download_folder_confirm">%1$s ใ‚’ๅค–้ƒจใ‚นใƒˆใƒฌใƒผใ‚ธใธ็งปๅ‹•ใ—ใพใ™ใ‹๏ผŸ</string> + <string name="library_games_moving">ใ‚ฒใƒผใƒ ใ‚’็งปๅ‹•ใ—ใฆใ„ใพใ™</string> + <string name="library_games_move_done">%1$s ใ‚’็งปๅ‹•ใ—ใพใ—ใŸ</string> + <string name="library_games_move_failed">%1$s ใ‚’็งปๅ‹•ใงใใพใ›ใ‚“ใงใ—ใŸ</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">ใƒญใƒผใ‚ซใƒซใƒ•ใ‚ฉใƒซใƒ€ใ‚’็›ดๆŽฅๅ‚็…ง</string> <string name="common_ui_no_folders_available_here">ใ“ใ“ใซๅˆฉ็”จๅฏ่ƒฝใชใƒ•ใ‚ฉใƒซใƒ€ใฏใ‚ใ‚Šใพใ›ใ‚“</string> <string name="common_ui_grant_all_files_access_browse">ใƒ•ใ‚ฉใƒซใƒ€ใ‚’็›ดๆŽฅๅ‚็…งใ™ใ‚‹ใซใฏใ€ใ™ในใฆใฎใƒ•ใ‚กใ‚คใƒซใธใฎใ‚ขใ‚ฏใ‚ปใ‚นใ‚’่จฑๅฏใ—ใฆใใ ใ•ใ„ใ€‚</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32ใƒ“ใƒƒใƒˆ</string> <string name="hero_graphics_test_64">64ใƒ“ใƒƒใƒˆ</string> <string name="hero_input_tests_title">ๅ…ฅๅŠ›ใƒ†ใ‚นใƒˆ</string> - <string name="library_games_add_custom_game">ใ‚ซใ‚นใ‚ฟใƒ ใ‚ฒใƒผใƒ ใ‚’่ฟฝๅŠ </string> <string name="library_games_game_name">ใ‚ฒใƒผใƒ ๅ</string> <string name="library_games_game_folder_mapped_drive">ใ‚ฒใƒผใƒ ใƒ•ใ‚ฉใƒซใƒ€ (ใƒžใƒƒใƒ”ใƒณใ‚ฐใ•ใ‚ŒใŸใ‚ฒใƒผใƒ ใƒ‰ใƒฉใ‚คใƒ–)</string> <string name="library_games_select_exe_provide_name">exeใ‚’้ธๆŠžใ—ใฆๅๅ‰ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„</string> <string name="library_games_sign_in_prompt">ใ‚นใƒˆใ‚ขใซใ‚ตใ‚คใƒณใ‚คใƒณใ™ใ‚‹ใ‹ใ€ใ‚ซใ‚นใ‚ฟใƒ ใ‚ฒใƒผใƒ ใ‚’่ฟฝๅŠ ใ—ใฆใƒฉใ‚คใƒ–ใƒฉใƒชใ‚’ๆง‹็ฏ‰ใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="library_games_add_title">ใ‚ฒใƒผใƒ ใพใŸใฏใ‚ขใƒ—ใƒชใ‚’่ฟฝๅŠ </string> + <string name="library_games_add_linux_steam">Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚’่ฟฝๅŠ </string> + <string name="library_games_type">็จฎ้กž</string> + <string name="library_games_type_game">ใ‚ฒใƒผใƒ </string> + <string name="library_games_type_app">ใ‚ขใƒ—ใƒช</string> + <string name="library_games_select_executable_or_rom">ๅฎŸ่กŒใƒ•ใ‚กใ‚คใƒซใพใŸใฏใ‚ณใƒณใ‚ฝใƒผใƒซROMใ‚’้ธๆŠž</string> + <string name="library_games_added">%1$s ใ‚’่ฟฝๅŠ ใ—ใพใ—ใŸ</string> + <string name="library_games_add_failed">%1$s ใ‚’่ฟฝๅŠ ใงใใพใ›ใ‚“ใงใ—ใŸ</string> + <string name="linux_runtime_not_installed">Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใŒใพใ ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€%1$s ใ‚’่ตทๅ‹•ใงใใพใ›ใ‚“ใ€‚่จญๅฎš > ใ‚นใƒˆใ‚ข ใฎ Linux ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ ใ‹ใ‚‰ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="linux_client_button">Linux ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ</string> + <string name="linux_client_title">Linux Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ</string> + <string name="linux_client_install_message">WinNative ใ‹ใ‚‰ Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใ‚’ใ€Valve ใ‹ใ‚‰ Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใ€ไธกๆ–นใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ™ใ€‚ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใฏ 2 GB ใ‚’่ถ…ใˆใ‚‹ใŸใ‚ Wi-Fi ใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="linux_client_installed_message">Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใจ Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใฏใ‚คใƒณใ‚นใƒˆใƒผใƒซๆธˆใฟใงใ€Steam ใฏใƒฉใ‚คใƒ–ใƒฉใƒชใซใ‚ใ‚Šใพใ™ใ€‚</string> + <string name="linux_client_failed_message">ใ‚คใƒณใ‚นใƒˆใƒผใƒซใŒๅฎŒไบ†ใ—ใพใ›ใ‚“ใงใ—ใŸใ€‚ๆŽฅ็ถšใ‚’็ขบ่ชใ—ใฆใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚</string> + <string name="linux_client_no_space_message">็ฉบใๅฎน้‡ใŒไธ่ถณใ—ใฆใ„ใพใ™: %1$s ๅฟ…่ฆใงใ€็ฉบใใฏ %2$s ใงใ™ใ€‚</string> + <string name="linux_client_stage_connect">ๆŽฅ็ถšไธญ</string> + <string name="linux_client_stage_download_runtime">Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ไธญ</string> + <string name="linux_client_stage_install_runtime">Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซไธญ</string> + <string name="linux_client_stage_download_proton">Proton ใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ไธญ</string> + <string name="linux_client_stage_install_proton">Proton ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซไธญ</string> + <string name="linux_client_stage_download_steam">Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ไธญ</string> + <string name="linux_client_stage_install_steam">Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซไธญ</string> + <string name="linux_client_stage_library">Steam ใ‚’ใƒฉใ‚คใƒ–ใƒฉใƒชใซ่ฟฝๅŠ ไธญ</string> + <string name="linux_client_progress_amount">%1$s / %2$s</string> + <string name="linux_client_retry">ๅ†่ฉฆ่กŒ</string> + <string name="linux_client_busy">Linux Steam ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใฏใพใ ใ‚คใƒณใ‚นใƒˆใƒผใƒซไธญใงใ™ใ€‚ๅฎŒไบ†ใ—ใฆใ‹ใ‚‰ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚</string> + <string name="linux_client_update_message">ๆ–ฐใ—ใ„ Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใŒใ‚ใ‚Šใพใ™ใ€‚ๆ›ดๆ–ฐใ—ใฆใ‚‚ Steam ใฎใ‚ตใ‚คใƒณใ‚คใƒณใ€ใ‚คใƒณใ‚นใƒˆใƒผใƒซๆธˆใฟใฎใ‚ฒใƒผใƒ ใ€ใ‚ปใƒผใƒ–ใƒ‡ใƒผใ‚ฟใ€ใ‚ณใƒณใƒ†ใƒŠใฏไฟๆŒใ•ใ‚Œใพใ™ใ€‚</string> + <string name="linux_client_update_session_running">Linux ใ‚ปใƒƒใ‚ทใƒงใƒณใŒๅฎŸ่กŒไธญใงใ™ใ€‚็ต‚ไบ†ใ—ใฆใ‹ใ‚‰ๆ›ดๆ–ฐใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux ใ‚ปใƒƒใ‚ทใƒงใƒณใฏ้–‹ๅง‹็›ดๅพŒใซๅœๆญขใ—ใพใ—ใŸใ€‚ใƒญใ‚ฐใฏ ่จญๅฎš > ใƒ‡ใƒใƒƒใ‚ฐ > ใƒญใ‚ฐใƒžใƒใƒผใ‚ธใƒฃใƒผใ‚’้–‹ใ ใซใ‚ใ‚Šใพใ™ใ€‚ๅŽŸๅ› ใ‚’็‰นๅฎšใงใใ‚‹ใ‚ˆใ†ๅ…ฑๆœ‰ใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="linux_session_stopped">Linux ใ‚ปใƒƒใ‚ทใƒงใƒณใŒ่‡ชใ‚‰ๅœๆญขใ—ใพใ—ใŸ (็Šถๆ…‹ %1$d)ใ€‚ใƒญใ‚ฐใฏ ่จญๅฎš > ใƒ‡ใƒใƒƒใ‚ฐ > ใƒญใ‚ฐใƒžใƒใƒผใ‚ธใƒฃใƒผใ‚’้–‹ใ ใซใ‚ใ‚Šใพใ™ใ€‚ๅŽŸๅ› ใ‚’็‰นๅฎšใงใใ‚‹ใ‚ˆใ†ๅ…ฑๆœ‰ใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="linux_session_trimmed">Android ใŒ Linux ใ‚ปใƒƒใ‚ทใƒงใƒณใ‚’ๅœๆญขใ—ใพใ—ใŸใ€‚้€šๅธธใฏใƒใƒƒใ‚ฏใ‚ฐใƒฉใ‚ฆใƒณใƒ‰ใƒ—ใƒญใ‚ปใ‚นใฎไธŠ้™ใซ้”ใ—ใŸใ“ใจใ‚’ๆ„ๅ‘ณใ—ใพใ™ใ€‚้–‹็™บ่€…ๅ‘ใ‘ใ‚ชใƒ—ใ‚ทใƒงใƒณใงใ€Œๅญใƒ—ใƒญใ‚ปใ‚นใฎๅˆถ้™ใ‚’็„กๅŠนใซใ™ใ‚‹ใ€ใ‚’ใ‚ชใƒณใซใ—ใฆใ€ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">ใ‚นใƒˆใ‚ข</string> @@ -1357,6 +1401,10 @@ <string name="setup_wizard_title">ใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—ใ‚ฆใ‚ฃใ‚ถใƒผใƒ‰</string> <string name="setup_wizard_advanced_user">ไธŠ็ดšใƒฆใƒผใ‚ถใƒผ</string> <string name="setup_wizard_next">ๆฌกใธ</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">ๅฎŒไบ†</string> <string name="setup_wizard_download_and_install">ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ + ใ‚คใƒณใ‚นใƒˆใƒผใƒซ</string> <string name="setup_wizard_open_drivers">ใƒ‰ใƒฉใ‚คใƒใƒผใ‚’้–‹ใ</string> @@ -2864,10 +2912,9 @@ <string name="update_apk_notice">ใ“ใฎใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใฏใ‚ขใƒ—ใƒชๆœฌไฝ“ใ‚’ๅค‰ๆ›ดใ™ใ‚‹ใŸใ‚ใ€Android ใŒใ‚คใƒณใ‚นใƒˆใƒผใƒซใฎ็ขบ่ชใ‚’ๆฑ‚ใ‚ใพใ™ใ€‚ใ‚ฒใƒผใƒ ใ€ใ‚ณใƒณใƒ†ใƒŠใ€่จญๅฎšใฏใใฎใพใพไฟๆŒใ•ใ‚Œใพใ™ใ€‚</string> <string name="update_up_to_date">WinNative ใฏๆœ€ๆ–ฐใงใ™ใ€‚</string> <string name="update_unsupported_build">ใ‚ขใƒ—ใƒชๅ†…ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใฏใ€ๅ…ฌๅผใฎ็ฝฒๅๆธˆใฟใƒชใƒชใƒผใ‚นใงใฎใฟๆไพ›ใ•ใ‚Œใพใ™ใ€‚</string> - <string name="settings_general_update_channel">ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใƒใƒฃใƒณใƒใƒซ</string> - <string name="settings_general_update_channel_summary">ใ€Œๅ…ฌๅผใ€ใฏใƒ†ใ‚นใƒˆๆธˆใฟใฎใƒชใƒชใƒผใ‚นใ‚’้…ไฟกใ—ใพใ™ใ€‚ใ€Œ้–‹็™บใ€ใฏๆ—ฉๆœŸใƒ“ใƒซใƒ‰ใ‚’้…ไฟกใ—ใพใ™ใ€‚</string> + <string name="settings_general_update_source">ๆ›ดๆ–ฐใฎๅ–ๅพ—ๅ…ƒ</string> + <string name="settings_general_update_source_summary">ใƒ—ใƒซใƒชใ‚ฏใ‚จใ‚นใƒˆใฎใƒ“ใƒซใƒ‰ใฏใใฎใƒ—ใƒซใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ใ€ๅ…ฌๅผใ‚คใƒณใ‚นใƒˆใƒผใƒซใฏๅ…ฌๅผใƒชใƒชใƒผใ‚นใ‚’่ฟฝ่ทกใ—ใพใ™ใ€‚</string> <string name="update_channel_official">ๅ…ฌๅผ</string> - <string name="update_channel_development">้–‹็™บ</string> <string name="networking_section_title">ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ</string> <string name="networking_driver">ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒ‰ใƒฉใ‚คใƒใƒผ</string> <string name="networking_driver_winnative">WinNative ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใƒ‰ใƒฉใ‚คใƒใƒผ</string> @@ -2880,4 +2927,35 @@ <string name="steam_controller_left_pad_click">ๅทฆใƒ‘ใƒƒใƒ‰ใฎใ‚ฏใƒชใƒƒใ‚ฏ</string> <string name="steam_controller_right_pad_click">ๅณใƒ‘ใƒƒใƒ‰ใฎใ‚ฏใƒชใƒƒใ‚ฏ</string> <string name="controller_test_generic">ๆฑŽ็”จ</string> + <string name="container_display_server">ใƒ‡ใ‚ฃใ‚นใƒ—ใƒฌใ‚คใ‚ตใƒผใƒใƒผ</string> + <string name="container_display_server_help">ๅฎŸ้จ“็š„ๆฉŸ่ƒฝใ€‚ใ‚ปใƒƒใ‚ทใƒงใƒณใฏ X ใ‚ตใƒผใƒใƒผใฎไปฃใ‚ใ‚Šใซๅ†…่”ตใฎ Wayland ใ‚ณใƒณใƒใ‚ธใ‚ฟใƒผใงๆ็”ปใ•ใ‚Œใพใ™ใ€‚</string> + <string name="container_display_server_wayland_requirements">Wayland ใซใฏ Adreno GPU ใจใ€winewayland ใŠใ‚ˆใณ Wayland ็”จ Turnip ใƒ‰ใƒฉใ‚คใƒใƒผใ‚’ๅซใ‚€ Wayland ๅฏพๅฟœใฎ Proton ใŒๅฟ…่ฆใงใ™ใ€‚ใ‚ณใƒณใƒ†ใƒณใƒ„ใ‹ใ‚‰ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="container_display_server_needs_wayland_proton">ใ“ใฎ Proton ใซใฏ winewayland ใจ Wayland ็”จ Turnip ใƒ‰ใƒฉใ‚คใƒใƒผใŒๅซใพใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€X11 ใŒไฝฟ็”จใ•ใ‚Œใพใ™ใ€‚ใ‚ณใƒณใƒ†ใƒณใƒ„ใ‹ใ‚‰ Wayland Proton ใƒฌใ‚คใƒคใƒผใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใ€ใ‚ณใƒณใƒ†ใƒŠใฎ Proton ใจใ—ใฆ้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="wayland_unavailable_fallback">ใ“ใฎใ‚ฒใƒผใƒ ใงใฏ Wayland ใ‚’ๅˆฉ็”จใงใใพใ›ใ‚“ใ€‚X11 ใง่ตทๅ‹•ใ—ใพใ™ใ€‚</string> + <string name="wayland_needs_turnip_driver">Wayland ใงใ‚ฒใƒผใƒ ใ‚’่กจ็คบใ™ใ‚‹ใซใฏ Turnip ใ‚ฐใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏใƒ‰ใƒฉใ‚คใƒใƒผใŒๅฟ…่ฆใงใ™ใ€‚ใ‚ณใƒณใƒ†ใƒณใƒ„ใ‹ใ‚‰ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใ€ใ‚ฐใƒฉใƒ•ใ‚ฃใƒƒใ‚ฏใƒ‰ใƒฉใ‚คใƒใƒผใจใ—ใฆ้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="containers_gamescope_new">GameScope ใ‚ณใƒณใƒ†ใƒŠใ‚’ไฝœๆˆ</string> + <string name="containers_gamescope_create_failed">GameScope ใ‚ณใƒณใƒ†ใƒŠใ‚’ไฝœๆˆใงใใพใ›ใ‚“ใงใ—ใŸ</string> + <string name="linux_apps_need_gamescope_container">ใพใš่จญๅฎš โ€บ ใ‚ณใƒณใƒ†ใƒŠใง GameScope ใ‚ณใƒณใƒ†ใƒŠใ‚’ไฝœๆˆใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="linux_runtime_missing">Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใŒใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใฆใ„ใชใ„ใŸใ‚ใ€ใ“ใฎใ‚ณใƒณใƒ†ใƒŠใฏใพใ ่ตทๅ‹•ใงใใพใ›ใ‚“ใ€‚่จญๅฎš > ใ‚นใƒˆใ‚ข ใฎ Linux ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ ใ‹ใ‚‰ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="container_display_server_gamescope">Gamescope ใฏๅธธใซ Wayland ใ‚ณใƒณใƒใ‚ธใ‚ฟใƒผใ‚’้€šใ—ใฆๆ็”ปใ—ใพใ™ใ€‚</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Linux ใƒฉใƒณใ‚ฟใ‚คใƒ ใซ็ต„ใฟ่พผใพใ‚Œใฆใ„ใพใ™ใ€‚ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใŸ Android ใƒ‰ใƒฉใ‚คใƒใƒผใฏ Wine ใจ Proton ใฎใ‚ณใƒณใƒ†ใƒŠใซใฎใฟ้ฉ็”จใ•ใ‚Œใพใ™ใ€‚</string> + <string name="linux_runtime_windows_program">ใ“ใ‚Œใฏ Windows ใƒ—ใƒญใ‚ฐใƒฉใƒ ใงใ™ใ€‚่ตทๅ‹•ใ™ใ‚‹ใซใฏใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ่จญๅฎšใง Wine ใ‚ณใƒณใƒ†ใƒŠใซ็งปๅ‹•ใ—ใฆใใ ใ•ใ„ใ€‚</string> + <string name="settings_content_badge_online">ใ‚ชใƒณใƒฉใ‚คใƒณ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">ใƒ‰ใƒฉใ‚คใƒใƒผใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซๅ…ˆใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚่ช่ญ˜ใ•ใ‚ŒใŸใƒ‘ใƒƒใ‚ฑใƒผใ‚ธใฏ่‡ชๅ‹•็š„ใซๆญฃใ—ใ„ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใพใ™ใ€‚</string> + <string name="settings_drivers_restore_defaults">ๆ—ขๅฎšๅ€คใซๆˆปใ™</string> + <string name="settings_drivers_defaults_present">ๆ—ขๅฎšใฎใƒชใƒใ‚ธใƒˆใƒชใฏ็™ป้Œฒๆธˆใฟใงใ™</string> + <string name="settings_drivers_active">ไฝฟ็”จไธญ</string> + <string name="settings_drivers_bundled">ๅŒๆขฑใฎMesa</string> + <string name="settings_drivers_legacy">Linuxใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใฎใƒ‰ใƒฉใ‚คใƒใƒผ</string> + <string name="settings_drivers_routed">ใƒ‰ใƒฉใ‚คใƒใƒผใ‚’่ญ˜ๅˆฅใ—ใ€%1$s็”จใจใ—ใฆใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ—ใŸใ€‚</string> + <string name="linux_child_processes_message">Android ใฏใ‚ขใƒ—ใƒชใฎใƒใƒƒใ‚ฏใ‚ฐใƒฉใ‚ฆใƒณใƒ‰ใƒ—ใƒญใ‚ปใ‚นๆ•ฐใ‚’ๅˆถ้™ใ—ใพใ™ใŒใ€Linux ใ‚ปใƒƒใ‚ทใƒงใƒณใฏใใ‚Œใ‚’ๅคงใใไธŠๅ›žใ‚Šใพใ™ใ€‚ใƒ—ใƒฌใ‚คไธญใซ Steam ใ‚„ใ‚ฒใƒผใƒ ใŒ็ต‚ไบ†ใ•ใ‚Œใ‚‹ใ“ใจใŒใ‚ใ‚Šใพใ™ใ€‚้–‹็™บ่€…ๅ‘ใ‘ใ‚ชใƒ—ใ‚ทใƒงใƒณใงใ€Œๅญใƒ—ใƒญใ‚ปใ‚นใฎๅˆถ้™ใ‚’็„กๅŠนใซใ™ใ‚‹ใ€ใ‚’ใ‚ชใƒณใซใ—ใฆใใ ใ•ใ„ใ€‚ไปŠ้–‹ใใพใ™ใ‹๏ผŸ</string> + <string name="linux_child_processes_adb">Android ใฏใ‚ขใƒ—ใƒชใฎใƒใƒƒใ‚ฏใ‚ฐใƒฉใ‚ฆใƒณใƒ‰ใƒ—ใƒญใ‚ปใ‚นๆ•ฐใ‚’ๅˆถ้™ใ—ใพใ™ใŒใ€Linux ใ‚ปใƒƒใ‚ทใƒงใƒณใฏใใ‚Œใ‚’ๅคงใใไธŠๅ›žใ‚Šใพใ™ใ€‚ใƒ—ใƒฌใ‚คไธญใซ Steam ใ‚„ใ‚ฒใƒผใƒ ใŒ็ต‚ไบ†ใ•ใ‚Œใ‚‹ใ“ใจใŒใ‚ใ‚Šใพใ™ใ€‚ใ“ใฎ Android ใƒใƒผใ‚ธใƒงใƒณใซใใฎ่จญๅฎšใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚ใƒ‘ใ‚ฝใ‚ณใƒณใ‹ใ‚‰ๆฌกใ‚’ๅฎŸ่กŒใ—ใฆใใ ใ•ใ„:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 192917b0f..e64d35c89 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">์•„์ด์ฝ˜ ๋˜๋Œ๋ฆฌ๊ธฐ</string> <string name="common_ui_select_exe">EXE ์„ ํƒ</string> <string name="common_ui_select_valid_exe_file">์˜ฌ๋ฐ”๋ฅธ .exe ํŒŒ์ผ์„ ์„ ํƒํ•˜์„ธ์š”</string> + <string name="common_ui_select_executable">์‹คํ–‰ ํŒŒ์ผ ์„ ํƒ</string> + <string name="common_ui_select_valid_executable">Windows ๋˜๋Š” Linux ์‹คํ–‰ ํŒŒ์ผ์„ ์„ ํƒํ•˜์„ธ์š”</string> <string name="common_ui_select_valid_icon_file">์˜ฌ๋ฐ”๋ฅธ .ico ๋˜๋Š” .png ํŒŒ์ผ์„ ์„ ํƒํ•˜์„ธ์š”.</string> <string name="common_ui_exit">์ข…๋ฃŒ</string> <string name="common_ui_exit_app">์•ฑ ์ข…๋ฃŒ</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">๊ฒฝ๋กœ ์ž…๋ ฅ</string> <string name="common_ui_path_not_found">๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ</string> <string name="common_ui_storage_roots">์ €์žฅ์†Œ ๋ฃจํŠธ</string> + <string name="common_ui_app_storage">์•ฑ ์ €์žฅ์†Œ</string> + <string name="common_ui_move">์ด๋™</string> + <string name="library_games_move_to_app_storage_title">๋‚ด๋ถ€๋กœ ์ด๋™</string> + <string name="library_games_move_to_download_folder_title">์™ธ๋ถ€๋กœ ์ด๋™</string> + <string name="library_games_move_to_app_storage_confirm">%1$s์„(๋ฅผ) ๋‚ด๋ถ€ ์ €์žฅ์†Œ๋กœ ์ด๋™ํ• ๊นŒ์š”?</string> + <string name="library_games_move_to_download_folder_confirm">%1$s์„(๋ฅผ) ์™ธ๋ถ€ ์ €์žฅ์†Œ๋กœ ์ด๋™ํ• ๊นŒ์š”?</string> + <string name="library_games_moving">๊ฒŒ์ž„ ์ด๋™ ์ค‘</string> + <string name="library_games_move_done">%1$s ์ด๋™ ์™„๋ฃŒ</string> + <string name="library_games_move_failed">%1$s์„(๋ฅผ) ์ด๋™ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">๋กœ์ปฌ ํด๋”๋ฅผ ๋ฐ”๋กœ ํƒ์ƒ‰</string> <string name="common_ui_no_folders_available_here">์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค</string> <string name="common_ui_grant_all_files_access_browse">๋กœ์ปฌ ํด๋”๋ฅผ ๋ฐ”๋กœ ํƒ์ƒ‰ํ•˜๋ ค๋ฉด ๋ชจ๋“  ํŒŒ์ผ ์ ‘๊ทผ ๊ถŒํ•œ์„ ํ—ˆ์šฉํ•˜์„ธ์š”.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32๋น„ํŠธ</string> <string name="hero_graphics_test_64">64๋น„ํŠธ</string> <string name="hero_input_tests_title">์ž…๋ ฅ ํ…Œ์ŠคํŠธ</string> - <string name="library_games_add_custom_game">์‚ฌ์šฉ์ž ์ง€์ • ๊ฒŒ์ž„ ์ถ”๊ฐ€</string> <string name="library_games_game_name">๊ฒŒ์ž„ ์ด๋ฆ„</string> <string name="library_games_game_folder_mapped_drive">๊ฒŒ์ž„ ํด๋”(๋งคํ•‘๋œ ๊ฒŒ์ž„ ๋“œ๋ผ์ด๋ธŒ)</string> <string name="library_games_select_exe_provide_name">exe๋ฅผ ์„ ํƒํ•˜๊ณ  ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”</string> <string name="library_games_sign_in_prompt">์Šคํ† ์–ด์— ๋กœ๊ทธ์ธํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž ์ง€์ • ๊ฒŒ์ž„์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•˜์„ธ์š”.</string> + <string name="library_games_add_title">๊ฒŒ์ž„ ๋˜๋Š” ์•ฑ ์ถ”๊ฐ€</string> + <string name="library_games_add_linux_steam">Steam ํด๋ผ์ด์–ธํŠธ ์ถ”๊ฐ€</string> + <string name="library_games_type">์œ ํ˜•</string> + <string name="library_games_type_game">๊ฒŒ์ž„</string> + <string name="library_games_type_app">์•ฑ</string> + <string name="library_games_select_executable_or_rom">์‹คํ–‰ ํŒŒ์ผ ๋˜๋Š” ์ฝ˜์†” ROM ์„ ํƒ</string> + <string name="library_games_added">%1$s ์ถ”๊ฐ€๋จ</string> + <string name="library_games_add_failed">%1$s์„(๋ฅผ) ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค</string> + <string name="linux_runtime_not_installed">Linux ๋Ÿฐํƒ€์ž„์ด ์•„์ง ์„ค์น˜๋˜์ง€ ์•Š์•„ %1$s์„(๋ฅผ) ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์„ค์ • > ์Šคํ† ์–ด์˜ Linux ํด๋ผ์ด์–ธํŠธ์—์„œ ์„ค์น˜ํ•˜์„ธ์š”.</string> + <string name="linux_client_button">Linux ํด๋ผ์ด์–ธํŠธ</string> + <string name="linux_client_title">Linux Steam ํด๋ผ์ด์–ธํŠธ</string> + <string name="linux_client_install_message">WinNative์—์„œ Linux ๋Ÿฐํƒ€์ž„์„, Valve์—์„œ Steam ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•œ ๋’ค ๋‘˜ ๋‹ค ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์šด๋กœ๋“œ ์šฉ๋Ÿ‰์ด 2GB๋ฅผ ๋„˜์œผ๋ฏ€๋กœ Wi-Fi๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.</string> + <string name="linux_client_installed_message">Linux ๋Ÿฐํƒ€์ž„๊ณผ Steam ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์œผ๋ฉฐ Steam์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์žˆ์Šต๋‹ˆ๋‹ค.</string> + <string name="linux_client_failed_message">์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</string> + <string name="linux_client_no_space_message">์—ฌ์œ  ๊ณต๊ฐ„์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค: %1$s์ด(๊ฐ€) ํ•„์š”ํ•˜๋ฉฐ %2$s์„(๋ฅผ) ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</string> + <string name="linux_client_stage_connect">์—ฐ๊ฒฐ ์ค‘</string> + <string name="linux_client_stage_download_runtime">Linux ๋Ÿฐํƒ€์ž„ ๋‹ค์šด๋กœ๋“œ ์ค‘</string> + <string name="linux_client_stage_install_runtime">Linux ๋Ÿฐํƒ€์ž„ ์„ค์น˜ ์ค‘</string> + <string name="linux_client_stage_download_proton">Proton ๋‹ค์šด๋กœ๋“œ ์ค‘</string> + <string name="linux_client_stage_install_proton">Proton ์„ค์น˜ ์ค‘</string> + <string name="linux_client_stage_download_steam">Steam ํด๋ผ์ด์–ธํŠธ ๋‹ค์šด๋กœ๋“œ ์ค‘</string> + <string name="linux_client_stage_install_steam">Steam ํด๋ผ์ด์–ธํŠธ ์„ค์น˜ ์ค‘</string> + <string name="linux_client_stage_library">Steam์„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘</string> + <string name="linux_client_progress_amount">%1$s / %2$s</string> + <string name="linux_client_retry">๋‹ค์‹œ ์‹œ๋„</string> + <string name="linux_client_busy">Linux Steam ํด๋ผ์ด์–ธํŠธ๋ฅผ ์•„์ง ์„ค์น˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์™„๋ฃŒ๋œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</string> + <string name="linux_client_update_message">์ƒˆ Linux ๋Ÿฐํƒ€์ž„์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธํ•ด๋„ Steam ๋กœ๊ทธ์ธ, ์„ค์น˜๋œ ๊ฒŒ์ž„, ์„ธ์ด๋ธŒ ๋ฐ ์ปจํ…Œ์ด๋„ˆ๋Š” ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.</string> + <string name="linux_client_update_session_running">Linux ์„ธ์…˜์ด ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. ์ข…๋ฃŒํ•œ ํ›„ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux ์„ธ์…˜์ด ์‹œ์ž‘ํ•˜์ž๋งˆ์ž ์ค‘์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ๋Š” ์„ค์ • > ๋””๋ฒ„๊ทธ > ๋กœ๊ทธ ๊ด€๋ฆฌ์ž ์—ด๊ธฐ์— ์žˆ์Šต๋‹ˆ๋‹ค. ์›์ธ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก ๊ณต์œ ํ•ด ์ฃผ์„ธ์š”.</string> + <string name="linux_session_stopped">Linux ์„ธ์…˜์ด ์ €์ ˆ๋กœ ์ค‘์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค(์ƒํƒœ %1$d). ๋กœ๊ทธ๋Š” ์„ค์ • > ๋””๋ฒ„๊ทธ > ๋กœ๊ทธ ๊ด€๋ฆฌ์ž ์—ด๊ธฐ์— ์žˆ์Šต๋‹ˆ๋‹ค. ์›์ธ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก ๊ณต์œ ํ•ด ์ฃผ์„ธ์š”.</string> + <string name="linux_session_trimmed">Android๊ฐ€ Linux ์„ธ์…˜์„ ์ค‘์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๊ฐœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ”„๋กœ์„ธ์Šค ํ•œ๋„์— ๋„๋‹ฌํ–ˆ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž ์˜ต์…˜์—์„œ โ€œํ•˜์œ„ ํ”„๋กœ์„ธ์Šค ์ œํ•œ ํ•ด์ œโ€์„ ์ผœ๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">์Šคํ† ์–ด</string> @@ -1065,6 +1109,10 @@ <string name="setup_wizard_title">์„ค์ • ๋งˆ๋ฒ•์‚ฌ</string> <string name="setup_wizard_advanced_user">๊ณ ๊ธ‰ ์‚ฌ์šฉ์ž</string> <string name="setup_wizard_next">๋‹ค์Œ</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">์™„๋ฃŒ</string> <string name="setup_wizard_download_and_install">๋‹ค์šด๋กœ๋“œ + ์„ค์น˜</string> <string name="setup_wizard_open_drivers">๋“œ๋ผ์ด๋ฒ„ ์—ด๊ธฐ</string> @@ -2866,10 +2914,9 @@ <string name="update_apk_notice">์ด ์—…๋ฐ์ดํŠธ๋Š” ์•ฑ ์ž์ฒด๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฏ€๋กœ Android๊ฐ€ ์„ค์น˜ ํ™•์ธ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ๊ฒŒ์ž„, ์ปจํ…Œ์ด๋„ˆ, ์„ค์ •์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.</string> <string name="update_up_to_date">WinNative๊ฐ€ ์ตœ์‹  ์ƒํƒœ์ž…๋‹ˆ๋‹ค.</string> <string name="update_unsupported_build">์ธ์•ฑ ์—…๋ฐ์ดํŠธ๋Š” ๊ณต์‹ ์„œ๋ช…๋œ ๋ฆด๋ฆฌ์Šค์—์„œ๋งŒ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.</string> - <string name="settings_general_update_channel">์—…๋ฐ์ดํŠธ ์ฑ„๋„</string> - <string name="settings_general_update_channel_summary">๊ณต์‹์€ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑฐ์นœ ๋ฆด๋ฆฌ์Šค๋ฅผ, ๊ฐœ๋ฐœ์€ ์ดˆ๊ธฐ ๋นŒ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.</string> + <string name="settings_general_update_source">์—…๋ฐ์ดํŠธ ์ถœ์ฒ˜</string> + <string name="settings_general_update_source_summary">ํ’€ ๋ฆฌํ€˜์ŠคํŠธ ๋นŒ๋“œ๋Š” ํ•ด๋‹น ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋ฅผ, ๊ณต์‹ ์„ค์น˜๋Š” ๊ณต์‹ ๋ฆด๋ฆฌ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.</string> <string name="update_channel_official">๊ณต์‹</string> - <string name="update_channel_development">๊ฐœ๋ฐœ</string> <string name="networking_section_title">๋„คํŠธ์›Œํฌ</string> <string name="networking_driver">๋„คํŠธ์›Œํฌ ๋“œ๋ผ์ด๋ฒ„</string> <string name="networking_driver_winnative">WinNative ๋„คํŠธ์›Œํฌ ๋“œ๋ผ์ด๋ฒ„</string> @@ -2882,5 +2929,36 @@ <string name="steam_controller_left_pad_click">์™ผ์ชฝ ํŒจ๋“œ ํด๋ฆญ</string> <string name="steam_controller_right_pad_click">์˜ค๋ฅธ์ชฝ ํŒจ๋“œ ํด๋ฆญ</string> <string name="controller_test_generic">์ผ๋ฐ˜</string> + <string name="container_display_server">๋””์Šคํ”Œ๋ ˆ์ด ์„œ๋ฒ„</string> + <string name="container_display_server_help">์‹คํ—˜์  ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์„ธ์…˜์ด X ์„œ๋ฒ„ ๋Œ€์‹  ๋‚ด์žฅ Wayland ์ปดํฌ์ง€ํ„ฐ๋ฅผ ํ†ตํ•ด ๊ทธ๋ ค์ง‘๋‹ˆ๋‹ค.</string> + <string name="container_display_server_wayland_requirements">Wayland์—๋Š” Adreno GPU์™€ winewayland ๋ฐ Wayland Turnip ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ํฌํ•จ๋œ Wayland ์ง€์› Proton์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฝ˜ํ…์ธ ์—์„œ ์„ค์น˜ํ•˜์„ธ์š”.</string> + <string name="container_display_server_needs_wayland_proton">์ด Proton์—๋Š” winewayland์™€ Wayland Turnip ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์—†์–ด X11์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฝ˜ํ…์ธ ์—์„œ Wayland Proton ๋ ˆ์ด์–ด๋ฅผ ์„ค์น˜ํ•˜๊ณ  ์ปจํ…Œ์ด๋„ˆ์˜ Proton์œผ๋กœ ์„ ํƒํ•˜์„ธ์š”.</string> + <string name="wayland_unavailable_fallback">์ด ๊ฒŒ์ž„์—์„œ๋Š” Wayland๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. X11๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.</string> + <string name="wayland_needs_turnip_driver">Wayland์—์„œ ๊ฒŒ์ž„์„ ํ‘œ์‹œํ•˜๋ ค๋ฉด Turnip ๊ทธ๋ž˜ํ”ฝ ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฝ˜ํ…์ธ ์—์„œ ์„ค์น˜ํ•œ ๋’ค ๊ทธ๋ž˜ํ”ฝ ๋“œ๋ผ์ด๋ฒ„๋กœ ์„ ํƒํ•˜์„ธ์š”.</string> + <string name="containers_gamescope_new">GameScope ์ปจํ…Œ์ด๋„ˆ ๋งŒ๋“ค๊ธฐ</string> + <string name="containers_gamescope_create_failed">GameScope ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋งŒ๋“ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค</string> + <string name="linux_apps_need_gamescope_container">๋จผ์ € ์„ค์ • โ€บ ์ปจํ…Œ์ด๋„ˆ์—์„œ GameScope ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋งŒ๋“œ์„ธ์š”.</string> + <string name="linux_runtime_missing">Linux ๋Ÿฐํƒ€์ž„์ด ์„ค์น˜๋˜์ง€ ์•Š์•„ ์ด ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์•„์ง ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์„ค์ • > ์Šคํ† ์–ด์˜ Linux ํด๋ผ์ด์–ธํŠธ์—์„œ ์„ค์น˜ํ•˜์„ธ์š”.</string> + <string name="container_display_server_gamescope">Gamescope๋Š” ํ•ญ์ƒ Wayland ์ปดํฌ์ง€ํ„ฐ๋ฅผ ํ†ตํ•ด ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Linux ๋Ÿฐํƒ€์ž„์— ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์šด๋กœ๋“œํ•œ Android ๋“œ๋ผ์ด๋ฒ„๋Š” Wine ๋ฐ Proton ์ปจํ…Œ์ด๋„ˆ์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.</string> + <string name="linux_runtime_windows_program">Windows ํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค. ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋ฐ”๋กœ๊ฐ€๊ธฐ ์„ค์ •์—์„œ Wine ์ปจํ…Œ์ด๋„ˆ๋กœ ์˜ฎ๊ธฐ์„ธ์š”.</string> + <string name="settings_content_badge_online">์˜จ๋ผ์ธ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">๋“œ๋ผ์ด๋ฒ„๋ฅผ ์„ค์น˜ํ•  ์œ„์น˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”. ์ธ์‹๋œ ํŒจํ‚ค์ง€๋Š” ์ž๋™์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ํ”Œ๋žซํผ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.</string> + <string name="settings_drivers_restore_defaults">๊ธฐ๋ณธ๊ฐ’ ๋ณต์›</string> + <string name="settings_drivers_defaults_present">๊ธฐ๋ณธ ์ €์žฅ์†Œ๊ฐ€ ์ด๋ฏธ ์žˆ์Šต๋‹ˆ๋‹ค</string> + <string name="settings_drivers_active">์‚ฌ์šฉ ์ค‘</string> + <string name="settings_drivers_bundled">๊ธฐ๋ณธ ์ œ๊ณต Mesa</string> + <string name="settings_drivers_legacy">Linux ํด๋ผ์ด์–ธํŠธ ๋“œ๋ผ์ด๋ฒ„</string> + <string name="settings_drivers_routed">๋“œ๋ผ์ด๋ฒ„๋ฅผ ์‹๋ณ„ํ•˜์—ฌ %1$s์šฉ์œผ๋กœ ์„ค์น˜ํ–ˆ์Šต๋‹ˆ๋‹ค.</string> + <string name="linux_child_processes_message">Android๋Š” ์•ฑ์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ”„๋กœ์„ธ์Šค ์ˆ˜๋ฅผ ์ œํ•œํ•˜์ง€๋งŒ Linux ์„ธ์…˜์€ ๊ทธ๋ณด๋‹ค ํ›จ์”ฌ ๋งŽ์ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ”Œ๋ ˆ์ด ์ค‘์— Steam์ด๋‚˜ ๊ฒŒ์ž„์ด ์ข…๋ฃŒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž ์˜ต์…˜์—์„œ โ€œํ•˜์œ„ ํ”„๋กœ์„ธ์Šค ์ œํ•œ ํ•ด์ œโ€๋ฅผ ์ผœ์„ธ์š”. ์ง€๊ธˆ ์—ด๊นŒ์š”?</string> + <string name="linux_child_processes_adb">Android๋Š” ์•ฑ์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ”„๋กœ์„ธ์Šค ์ˆ˜๋ฅผ ์ œํ•œํ•˜์ง€๋งŒ Linux ์„ธ์…˜์€ ๊ทธ๋ณด๋‹ค ํ›จ์”ฌ ๋งŽ์ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ”Œ๋ ˆ์ด ์ค‘์— Steam์ด๋‚˜ ๊ฒŒ์ž„์ด ์ข…๋ฃŒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด Android ๋ฒ„์ „์—๋Š” ํ•ด๋‹น ์„ค์ •์ด ์—†์Šต๋‹ˆ๋‹ค. ์ปดํ“จํ„ฐ์—์„œ ๋‹ค์Œ์„ ์‹คํ–‰ํ•˜์„ธ์š”:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 4e8873d15..b94ec99f5 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">Angi sti</string> <string name="common_ui_path_not_found">Fant ikke stien</string> <string name="common_ui_select_valid_exe_file">Velg en gyldig .exe-fil</string> + <string name="common_ui_select_executable">Velg kjรธrbar fil</string> + <string name="common_ui_select_valid_executable">Velg en kjรธrbar Windows- eller Linux-fil</string> <string name="common_ui_select_valid_icon_file">Velg en gyldig .ico- eller .png-fil.</string> <string name="common_ui_exit">Avslutt</string> <string name="common_ui_exit_app">Avslutt app</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">Versjon satt til %1$s. Bruk Se etter oppdatering for รฅ bytte de installerte filene.</string> <string name="common_ui_select">Velg</string> <string name="common_ui_storage_roots">Lagringsrรธtter</string> + <string name="common_ui_app_storage">App-lagring</string> + <string name="common_ui_move">Flytt</string> + <string name="library_games_move_to_app_storage_title">Flytt til intern</string> + <string name="library_games_move_to_download_folder_title">Flytt til ekstern</string> + <string name="library_games_move_to_app_storage_confirm">Flytte %1$s til intern lagring?</string> + <string name="library_games_move_to_download_folder_confirm">Flytte %1$s til ekstern lagring?</string> + <string name="library_games_moving">Flytter spillet</string> + <string name="library_games_move_done">%1$s er flyttet</string> + <string name="library_games_move_failed">Kunne ikke flytte %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Bla gjennom lokale mapper direkte</string> <string name="common_ui_no_folders_available_here">Ingen mapper tilgjengelig her</string> <string name="common_ui_grant_all_files_access_browse">Gi tilgang til alle filer for รฅ bla gjennom mapper direkte.</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Inndatatester</string> - <string name="library_games_add_custom_game">Legg til egendefinert spill</string> <string name="library_games_game_name">Spillnavn</string> <string name="library_games_game_folder_mapped_drive">Spillmappe (tilordnet spillstasjon)</string> <string name="library_games_select_exe_provide_name">Velg en exe og oppgi et navn</string> <string name="library_games_sign_in_prompt">Logg inn pรฅ en butikk eller legg til et egendefinert spill for รฅ bygge biblioteket ditt.</string> + <string name="library_games_add_title">Legg til spill eller app</string> + <string name="library_games_add_linux_steam">Legg til Steam-klient</string> + <string name="library_games_type">Type</string> + <string name="library_games_type_game">Spill</string> + <string name="library_games_type_app">App</string> + <string name="library_games_select_executable_or_rom">Velg en kjรธrbar fil eller konsoll-ROM</string> + <string name="library_games_added">%1$s lagt til</string> + <string name="library_games_add_failed">Kunne ikke legge til %1$s</string> + <string name="linux_runtime_not_installed">Linux-kjรธretidsmiljรธet er ikke installert ennรฅ, sรฅ %1$s kan ikke startes. Installer det med Linux-klient under Innstillinger > Butikker.</string> + <string name="linux_client_button">Linux-klient</string> + <string name="linux_client_title">Linux Steam-klient</string> + <string name="linux_client_install_message">Laster ned Linux-kjรธretidsmiljรธet fra WinNative og Steam-klienten fra Valve, og installerer begge. Nedlastingen er over 2 GB, sรฅ bruk Wi-Fi.</string> + <string name="linux_client_installed_message">Linux-kjรธretidsmiljรธet og Steam-klienten er installert, og Steam er i Bibliotek.</string> + <string name="linux_client_failed_message">Installasjonen ble ikke fullfรธrt. Sjekk tilkoblingen og prรธv igjen.</string> + <string name="linux_client_no_space_message">Ikke nok ledig plass: %1$s trengs, og %2$s er ledig.</string> + <string name="linux_client_stage_connect">Kobler til</string> + <string name="linux_client_stage_download_runtime">Laster ned Linux-kjรธretidsmiljรธet</string> + <string name="linux_client_stage_install_runtime">Installerer Linux-kjรธretidsmiljรธet</string> + <string name="linux_client_stage_download_proton">Laster ned Proton</string> + <string name="linux_client_stage_install_proton">Installerer Proton</string> + <string name="linux_client_stage_download_steam">Laster ned Steam-klienten</string> + <string name="linux_client_stage_install_steam">Installerer Steam-klienten</string> + <string name="linux_client_stage_library">Legger Steam til i Bibliotek</string> + <string name="linux_client_progress_amount">%1$s av %2$s</string> + <string name="linux_client_retry">Prรธv igjen</string> + <string name="linux_client_busy">Linux Steam-klienten installeres fortsatt. Prรธv igjen nรฅr den er ferdig.</string> + <string name="linux_client_update_message">Et nyere Linux-miljรธ er tilgjengelig. Oppdateringen beholder Steam-innloggingen, installerte spill, lagrede spill og containere.</string> + <string name="linux_client_update_session_running">En Linux-รธkt kjรธrer. Avslutt den, og oppdater deretter.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux-รธkten stoppet straks den startet. Loggen ligger under Innstillinger > Feilsรธking > ร…pne loggbehandler; del den sรฅ รฅrsaken kan finnes.</string> + <string name="linux_session_stopped">Linux-รธkten stoppet av seg selv (status %1$d). Loggen ligger under Innstillinger > Feilsรธking > ร…pne loggbehandler; del den sรฅ รฅrsaken kan finnes.</string> + <string name="linux_session_trimmed">Android stoppet Linux-รธkten. Det betyr som regel at grensen for bakgrunnsprosesser ble nรฅdd: slรฅ pรฅ โ€œDeaktiver begrensninger for underordnede prosesserโ€ i Utvikleralternativer og prรธv igjen.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Butikker</string> @@ -1357,6 +1401,10 @@ Installert bane: <string name="setup_wizard_title">Oppsettsveiviser</string> <string name="setup_wizard_advanced_user">Avansert bruker</string> <string name="setup_wizard_next">Neste</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Fullfรธr</string> <string name="setup_wizard_download_and_install">Last ned + installer</string> <string name="setup_wizard_open_drivers">ร…pne drivere</string> @@ -2864,10 +2912,9 @@ Installert bane: <string name="update_apk_notice">Denne oppdateringen endrer selve appen, sรฅ Android ber deg bekrefte installasjonen. Spillene, containerne og innstillingene dine beholdes.</string> <string name="update_up_to_date">WinNative er oppdatert.</string> <string name="update_unsupported_build">Oppdateringer i appen tilbys bare pรฅ offisielle signerte utgivelser.</string> - <string name="settings_general_update_channel">Oppdateringskanal</string> - <string name="settings_general_update_channel_summary">Offisiell leverer testede utgivelser. Utvikling leverer tidlige builder.</string> + <string name="settings_general_update_source">Oppdateringskilde</string> + <string name="settings_general_update_source_summary">En build fra en pull request fรธlger den pull requesten; en offisiell installasjon fรธlger de offisielle utgivelsene.</string> <string name="update_channel_official">Offisiell</string> - <string name="update_channel_development">Utvikling</string> <string name="networking_section_title">Nettverk</string> <string name="networking_driver">Nettverksdriver</string> <string name="networking_driver_winnative">WinNative-nettverksdriver</string> @@ -2880,4 +2927,35 @@ Installert bane: <string name="steam_controller_left_pad_click">Klikk pรฅ venstre styreflate</string> <string name="steam_controller_right_pad_click">Klikk pรฅ hรธyre styreflate</string> <string name="controller_test_generic">Generisk</string> + <string name="container_display_server">Skjermserver</string> + <string name="container_display_server_help">Eksperimentell. ร˜kten tegnes gjennom den innebygde Wayland-compositoren i stedet for X-serveren.</string> + <string name="container_display_server_wayland_requirements">Wayland krever en Adreno-GPU og en Wayland-kompatibel Proton med winewayland og Wayland Turnip-driveren. Installer en fra Innhold.</string> + <string name="container_display_server_needs_wayland_proton">Denne Proton har verken winewayland eller Wayland Turnip-driveren, sรฅ X11 brukes. Installer et Wayland Proton-lag fra Innhold, og velg det som containerens Proton.</string> + <string name="wayland_unavailable_fallback">Wayland er ikke tilgjengelig for dette spillet. Starter pรฅ X11.</string> + <string name="wayland_needs_turnip_driver">Wayland trenger en Turnip-grafikkdriver for รฅ vise spillet. Installer en under Innhold og velg den som grafikkdriver.</string> + <string name="containers_gamescope_new">Opprett GameScope-container</string> + <string name="containers_gamescope_create_failed">Kunne ikke opprette GameScope-containeren</string> + <string name="linux_apps_need_gamescope_container">Opprett fรธrst GameScope-containeren under Innstillinger โ€บ Containere.</string> + <string name="linux_runtime_missing">Linux-kjรธretidsmiljรธet er ikke installert, sรฅ denne containeren kan ikke startes ennรฅ. Installer det med Linux-klient under Innstillinger > Butikker.</string> + <string name="container_display_server_gamescope">Gamescope tegner alltid gjennom Wayland-kompositoren.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Innebygd i Linux-miljรธet. Nedlastede Android-drivere gjelder bare for Wine- og Proton-containere.</string> + <string name="linux_runtime_windows_program">Dette er et Windows-program. Flytt det til en Wine-container i snarveisinnstillingene for รฅ starte det.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Velg hvor driveren skal installeres. Gjenkjente pakker bruker automatisk riktig plattform.</string> + <string name="settings_drivers_restore_defaults">Gjenopprett standarder</string> + <string name="settings_drivers_defaults_present">Standardlagrene finnes allerede</string> + <string name="settings_drivers_active">Aktiv</string> + <string name="settings_drivers_bundled">Medfรธlgende Mesa</string> + <string name="settings_drivers_legacy">Linux-klientdriver</string> + <string name="settings_drivers_routed">Driveren ble identifisert og installert for %1$s.</string> + <string name="linux_child_processes_message">Android begrenser hvor mange bakgrunnsprosesser en app kan ha, og en Linux-รธkt bruker langt flere: Steam eller et spill kan bli avsluttet mens du spiller. Slรฅ pรฅ ยซDeaktiver begrensninger for underordnede prosesserยป i utvikleralternativer. ร…pne dem nรฅ?</string> + <string name="linux_child_processes_adb">Android begrenser hvor mange bakgrunnsprosesser en app kan ha, og en Linux-รธkt bruker langt flere: Steam eller et spill kan bli avsluttet mens du spiller. Denne Android-versjonen har ikke den innstillingen. Kjรธr fra en datamaskin:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index afc361981..96335ccaf 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Przywrรณฤ‡ ikonฤ™</string> <string name="common_ui_select_exe">Wybierz EXE</string> <string name="common_ui_select_valid_exe_file">Wybierz prawidล‚owy plik .exe</string> + <string name="common_ui_select_executable">Wybierz plik wykonywalny</string> + <string name="common_ui_select_valid_executable">Wybierz plik wykonywalny systemu Windows lub Linux</string> <string name="common_ui_select_valid_icon_file">Wybierz prawidล‚owy plik .ico lub .png.</string> <string name="common_ui_exit">Wyjล›cie</string> <string name="common_ui_exit_app">Zamknij aplikacjฤ™</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Wprowadลบ ล›cieลผkฤ™</string> <string name="common_ui_path_not_found">Nie znaleziono ล›cieลผki</string> <string name="common_ui_storage_roots">Lokalizacje pamiฤ™ci</string> + <string name="common_ui_app_storage">Pamiฤ™ฤ‡ aplikacji</string> + <string name="common_ui_move">Przenieล›</string> + <string name="library_games_move_to_app_storage_title">Przenieล› do wewnฤ™trznej</string> + <string name="library_games_move_to_download_folder_title">Przenieล› do zewnฤ™trznej</string> + <string name="library_games_move_to_app_storage_confirm">Przenieล›ฤ‡ %1$s do pamiฤ™ci wewnฤ™trznej?</string> + <string name="library_games_move_to_download_folder_confirm">Przenieล›ฤ‡ %1$s do pamiฤ™ci zewnฤ™trznej?</string> + <string name="library_games_moving">Przenoszenie gry</string> + <string name="library_games_move_done">Przeniesiono %1$s</string> + <string name="library_games_move_failed">Nie udaล‚o siฤ™ przenieล›ฤ‡ %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Przeglฤ…daj lokalne foldery bezpoล›rednio</string> <string name="common_ui_no_folders_available_here">Brak dostฤ™pnych folderรณw w tej lokalizacji</string> <string name="common_ui_grant_all_files_access_browse">Przyznaj peล‚ny dostฤ™p do plikรณw, aby bezpoล›rednio przeglฤ…daฤ‡ lokalne foldery.</string> @@ -230,11 +242,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Testy wejล›cia</string> - <string name="library_games_add_custom_game">Dodaj wล‚asnฤ… grฤ™</string> <string name="library_games_game_name">Nazwa gry</string> <string name="library_games_game_folder_mapped_drive">Folder gry (zmapowany dysk gry)</string> <string name="library_games_select_exe_provide_name">Wybierz plik exe i podaj nazwฤ™</string> <string name="library_games_sign_in_prompt">Zaloguj siฤ™ do sklepu lub dodaj wล‚asnฤ… grฤ™, aby zbudowaฤ‡ swojฤ… bibliotekฤ™.</string> + <string name="library_games_add_title">Dodaj grฤ™ lub aplikacjฤ™</string> + <string name="library_games_add_linux_steam">Dodaj klienta Steam</string> + <string name="library_games_type">Typ</string> + <string name="library_games_type_game">Gra</string> + <string name="library_games_type_app">Aplikacja</string> + <string name="library_games_select_executable_or_rom">Wybierz plik wykonywalny lub ROM konsoli</string> + <string name="library_games_added">Dodano %1$s</string> + <string name="library_games_add_failed">Nie udaล‚o siฤ™ dodaฤ‡ %1$s</string> + <string name="linux_runtime_not_installed">ลšrodowisko Linux nie jest jeszcze zainstalowane, wiฤ™c nie moลผna uruchomiฤ‡ %1$s. Zainstaluj je przyciskiem Klient Linux w Ustawienia > Sklepy.</string> + <string name="linux_client_button">Klient Linux</string> + <string name="linux_client_title">Klient Steam dla Linuksa</string> + <string name="linux_client_install_message">Pobiera ล›rodowisko Linux z WinNative i klienta Steam od Valve, a nastฤ™pnie instaluje oba. Pobieranie przekracza 2 GB, wiฤ™c uลผyj Wi-Fi.</string> + <string name="linux_client_installed_message">ลšrodowisko Linux i klient Steam sฤ… zainstalowane, a Steam jest w Bibliotece.</string> + <string name="linux_client_failed_message">Instalacja nie zostaล‚a ukoล„czona. Sprawdลบ poล‚ฤ…czenie i sprรณbuj ponownie.</string> + <string name="linux_client_no_space_message">Za maล‚o wolnego miejsca: potrzeba %1$s, dostฤ™pne jest %2$s.</string> + <string name="linux_client_stage_connect">ลฤ…czenie</string> + <string name="linux_client_stage_download_runtime">Pobieranie ล›rodowiska Linux</string> + <string name="linux_client_stage_install_runtime">Instalowanie ล›rodowiska Linux</string> + <string name="linux_client_stage_download_proton">Pobieranie Protona</string> + <string name="linux_client_stage_install_proton">Instalowanie Protona</string> + <string name="linux_client_stage_download_steam">Pobieranie klienta Steam</string> + <string name="linux_client_stage_install_steam">Instalowanie klienta Steam</string> + <string name="linux_client_stage_library">Dodawanie Steam do Biblioteki</string> + <string name="linux_client_progress_amount">%1$s z %2$s</string> + <string name="linux_client_retry">Ponรณw</string> + <string name="linux_client_busy">Klient Steam dla Linuksa wciฤ…ลผ siฤ™ instaluje. Sprรณbuj ponownie po zakoล„czeniu.</string> + <string name="linux_client_update_message">Dostฤ™pne jest nowsze ล›rodowisko Linux. Aktualizacja zachowuje logowanie do Steam, zainstalowane gry, zapisy i kontenery.</string> + <string name="linux_client_update_session_running">Trwa sesja Linux. Zakoล„cz jฤ…, a nastฤ™pnie zaktualizuj.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Sesja Linux zatrzymaล‚a siฤ™ zaraz po uruchomieniu. Jej log znajdziesz w Ustawienia > Debugowanie > Otwรณrz menedลผer logรณw; udostฤ™pnij go, aby moลผna byล‚o znaleลบฤ‡ przyczynฤ™.</string> + <string name="linux_session_stopped">Sesja Linux zatrzymaล‚a siฤ™ sama (status %1$d). Jej log znajdziesz w Ustawienia > Debugowanie > Otwรณrz menedลผer logรณw; udostฤ™pnij go, aby moลผna byล‚o znaleลบฤ‡ przyczynฤ™.</string> + <string name="linux_session_trimmed">Android zatrzymaล‚ sesjฤ™ Linux. Zwykle oznacza to osiฤ…gniฤ™cie limitu procesรณw w tle: wล‚ฤ…cz โ€žWyล‚ฤ…cz ograniczenia procesรณw podrzฤ™dnychโ€ w Opcjach programisty i sprรณbuj ponownie.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Sklepy</string> @@ -1071,6 +1115,10 @@ Zainstalowana ล›cieลผka: <string name="setup_wizard_title">Kreator konfiguracji</string> <string name="setup_wizard_advanced_user">Zaawansowany uลผytkownik</string> <string name="setup_wizard_next">Dalej</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Zakoล„cz</string> <string name="setup_wizard_download_and_install">Pobierz + Zainstaluj</string> <string name="setup_wizard_open_drivers">Otwรณrz sterowniki</string> @@ -2871,10 +2919,9 @@ Zainstalowana ล›cieลผka: <string name="update_apk_notice">Ta aktualizacja zmienia samฤ… aplikacjฤ™, wiฤ™c Android poprosi o potwierdzenie instalacji. Twoje gry, kontenery i ustawienia zostanฤ… zachowane.</string> <string name="update_up_to_date">WinNative jest aktualny.</string> <string name="update_unsupported_build">Aktualizacje w aplikacji sฤ… dostฤ™pne tylko w oficjalnych podpisanych wydaniach.</string> - <string name="settings_general_update_channel">Kanaล‚ aktualizacji</string> - <string name="settings_general_update_channel_summary">Kanaล‚ Oficjalny dostarcza przetestowane wydania. Rozwojowy dostarcza wczesne kompilacje.</string> + <string name="settings_general_update_source">ลนrรณdล‚o aktualizacji</string> + <string name="settings_general_update_source_summary">Kompilacja z pull requesta ล›ledzi ten pull request; instalacja oficjalna ล›ledzi oficjalne wydania.</string> <string name="update_channel_official">Oficjalny</string> - <string name="update_channel_development">Rozwojowy</string> <string name="networking_section_title">Sieฤ‡</string> <string name="networking_driver">Sterownik sieciowy</string> <string name="networking_driver_winnative">Sterownik sieciowy WinNative</string> @@ -2887,5 +2934,36 @@ Zainstalowana ล›cieลผka: <string name="steam_controller_left_pad_click">Klikniฤ™cie lewego panelu</string> <string name="steam_controller_right_pad_click">Klikniฤ™cie prawego panelu</string> <string name="controller_test_generic">Ogรณlny</string> + <string name="container_display_server">Serwer wyล›wietlania</string> + <string name="container_display_server_help">Eksperymentalne. Sesja jest rysowana przez wbudowany kompozytor Wayland zamiast serwera X.</string> + <string name="container_display_server_wayland_requirements">Wayland wymaga GPU Adreno oraz Protona obsล‚ugujฤ…cego Wayland z winewayland i sterownikiem Turnip dla Wayland. Zainstaluj go w Zawartoล›ci.</string> + <string name="container_display_server_needs_wayland_proton">Ten Proton nie zawiera winewayland ani sterownika Turnip dla Wayland, wiฤ™c uลผywany jest X11. Zainstaluj warstwฤ™ Proton Wayland w Zawartoล›ci i wybierz jฤ… jako Proton kontenera.</string> + <string name="wayland_unavailable_fallback">Wayland nie jest dostฤ™pny dla tej gry. Uruchamianie na X11.</string> + <string name="wayland_needs_turnip_driver">Wayland potrzebuje sterownika graficznego Turnip, aby wyล›wietliฤ‡ grฤ™. Zainstaluj go w Zawartoล›ci i wybierz jako sterownik graficzny.</string> + <string name="containers_gamescope_new">Utwรณrz kontener GameScope</string> + <string name="containers_gamescope_create_failed">Nie udaล‚o siฤ™ utworzyฤ‡ kontenera GameScope</string> + <string name="linux_apps_need_gamescope_container">Najpierw utwรณrz kontener GameScope w Ustawienia โ€บ Kontenery.</string> + <string name="linux_runtime_missing">ลšrodowisko Linux nie jest zainstalowane, wiฤ™c ten kontener nie moลผe jeszcze wystartowaฤ‡. Zainstaluj je przyciskiem Klient Linux w Ustawienia > Sklepy.</string> + <string name="container_display_server_gamescope">Gamescope zawsze rysuje przez kompozytor Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Wbudowany w ล›rodowisko Linux. Pobrane sterowniki Androida dotyczฤ… tylko kontenerรณw Wine i Proton.</string> + <string name="linux_runtime_windows_program">To program dla Windows. Aby go uruchomiฤ‡, przenieล› go do kontenera Wine w ustawieniach skrรณtu.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Wybierz miejsce instalacji sterownika. Rozpoznane pakiety automatycznie uลผywajฤ… wล‚aล›ciwej platformy.</string> + <string name="settings_drivers_restore_defaults">Przywrรณฤ‡ domyล›lne</string> + <string name="settings_drivers_defaults_present">Domyล›lne repozytoria sฤ… juลผ dostฤ™pne</string> + <string name="settings_drivers_active">Aktywny</string> + <string name="settings_drivers_bundled">Doล‚ฤ…czona Mesa</string> + <string name="settings_drivers_legacy">Sterownik klienta Linux</string> + <string name="settings_drivers_routed">Rozpoznano sterownik i zainstalowano dla %1$s.</string> + <string name="linux_child_processes_message">Android ogranicza liczbฤ™ procesรณw w tle aplikacji, a sesja Linuksa uruchamia ich znacznie wiฤ™cej: Steam lub gra mogฤ… zostaฤ‡ zamkniฤ™te w trakcie grania. Wล‚ฤ…cz โ€žWyล‚ฤ…cz ograniczenia procesรณw podrzฤ™dnychโ€ w opcjach programisty. Otworzyฤ‡ je teraz?</string> + <string name="linux_child_processes_adb">Android ogranicza liczbฤ™ procesรณw w tle aplikacji, a sesja Linuksa uruchamia ich znacznie wiฤ™cej: Steam lub gra mogฤ… zostaฤ‡ zamkniฤ™te w trakcie grania. Ta wersja Androida nie ma takiego przeล‚ฤ…cznika. Na komputerze wykonaj:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index bb6e5caeb..0e9fa96ed 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Reverter Icone</string> <string name="common_ui_select_exe">Selecionar EXE</string> <string name="common_ui_select_valid_exe_file">Por favor, selecione um arquivo .exe valido</string> + <string name="common_ui_select_executable">Selecionar executรกvel</string> + <string name="common_ui_select_valid_executable">Selecione um executรกvel do Windows ou do Linux</string> <string name="common_ui_select_valid_icon_file">Por favor, selecione um arquivo .ico ou .png valido.</string> <string name="common_ui_exit">Sair</string> <string name="common_ui_exit_app">Sair do aplicativo</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Inserir caminho</string> <string name="common_ui_path_not_found">Caminho nรฃo encontrado</string> <string name="common_ui_storage_roots">Locais de armazenamento</string> + <string name="common_ui_app_storage">Armazenamento do app</string> + <string name="common_ui_move">Mover</string> + <string name="library_games_move_to_app_storage_title">Mover para interno</string> + <string name="library_games_move_to_download_folder_title">Mover para externo</string> + <string name="library_games_move_to_app_storage_confirm">Mover %1$s para o armazenamento interno?</string> + <string name="library_games_move_to_download_folder_confirm">Mover %1$s para o armazenamento externo?</string> + <string name="library_games_moving">Movendo o jogo</string> + <string name="library_games_move_done">%1$s movido</string> + <string name="library_games_move_failed">Nรฃo foi possรญvel mover %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Navegar pelas pastas locais diretamente</string> <string name="common_ui_no_folders_available_here">Nenhuma pasta disponรญvel aqui</string> <string name="common_ui_grant_all_files_access_browse">Conceda acesso total aos arquivos para navegar pelas pastas locais diretamente.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Testes de entrada</string> - <string name="library_games_add_custom_game">Adicionar Jogo Personalizado</string> <string name="library_games_game_name">Nome do Jogo</string> <string name="library_games_game_folder_mapped_drive">Pasta do jogo (unidade de jogo mapeada)</string> <string name="library_games_select_exe_provide_name">Selecione um exe e forneca um nome</string> <string name="library_games_sign_in_prompt">Entre em uma loja ou adicione um jogo personalizado para montar sua biblioteca.</string> + <string name="library_games_add_title">Adicionar jogo ou aplicativo</string> + <string name="library_games_add_linux_steam">Adicionar cliente Steam</string> + <string name="library_games_type">Tipo</string> + <string name="library_games_type_game">Jogo</string> + <string name="library_games_type_app">Aplicativo</string> + <string name="library_games_select_executable_or_rom">Selecione um executรกvel ou uma ROM de console</string> + <string name="library_games_added">%1$s adicionado</string> + <string name="library_games_add_failed">Nรฃo foi possรญvel adicionar %1$s</string> + <string name="linux_runtime_not_installed">O runtime Linux ainda nรฃo estรก instalado, entรฃo %1$s nรฃo pode ser iniciado. Instale com Cliente Linux em Configuracoes > Lojas.</string> + <string name="linux_client_button">Cliente Linux</string> + <string name="linux_client_title">Cliente Steam para Linux</string> + <string name="linux_client_install_message">Baixa o runtime Linux do WinNative e o cliente Steam da Valve e instala os dois. O download passa de 2 GB, entรฃo use Wi-Fi.</string> + <string name="linux_client_installed_message">O runtime Linux e o cliente Steam estรฃo instalados, e o Steam estรก na Biblioteca.</string> + <string name="linux_client_failed_message">A instalaรงรฃo nรฃo terminou. Verifique sua conexรฃo e tente novamente.</string> + <string name="linux_client_no_space_message">Espaรงo livre insuficiente: sรฃo necessรกrios %1$s e hรก %2$s disponรญveis.</string> + <string name="linux_client_stage_connect">Conectando</string> + <string name="linux_client_stage_download_runtime">Baixando o runtime Linux</string> + <string name="linux_client_stage_install_runtime">Instalando o runtime Linux</string> + <string name="linux_client_stage_download_proton">Baixando o Proton</string> + <string name="linux_client_stage_install_proton">Instalando o Proton</string> + <string name="linux_client_stage_download_steam">Baixando o cliente Steam</string> + <string name="linux_client_stage_install_steam">Instalando o cliente Steam</string> + <string name="linux_client_stage_library">Adicionando o Steam ร  Biblioteca</string> + <string name="linux_client_progress_amount">%1$s de %2$s</string> + <string name="linux_client_retry">Tentar novamente</string> + <string name="linux_client_busy">O cliente Steam para Linux ainda estรก sendo instalado. Tente novamente quando terminar.</string> + <string name="linux_client_update_message">Hรก um ambiente Linux mais recente. A atualizaรงรฃo mantรฉm seu login na Steam, os jogos instalados, os saves e os contรชineres.</string> + <string name="linux_client_update_session_running">Uma sessรฃo Linux estรก em execuรงรฃo. Saia dela e depois atualize.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">A sessao Linux parou assim que comecou. O log esta em Configuracoes > Depuracao > Abrir gerenciador de logs; compartilhe-o para que a causa possa ser encontrada.</string> + <string name="linux_session_stopped">A sessao Linux parou sozinha (status %1$d). O log esta em Configuracoes > Depuracao > Abrir gerenciador de logs; compartilhe-o para que a causa possa ser encontrada.</string> + <string name="linux_session_trimmed">O Android parou a sessao Linux. Normalmente significa que o limite de processos em segundo plano foi atingido: ative โ€œDesativar restriรงรตes de processos filhosโ€ nas Opcoes do desenvolvedor e tente novamente.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Lojas</string> @@ -1065,6 +1109,10 @@ Caminho instalado: <string name="setup_wizard_title">Assistente de Configuracao</string> <string name="setup_wizard_advanced_user">Usuario Avancado</string> <string name="setup_wizard_next">Proximo</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Concluir</string> <string name="setup_wizard_download_and_install">Baixar + Instalar</string> <string name="setup_wizard_open_drivers">Abrir Drivers</string> @@ -2864,10 +2912,9 @@ Caminho instalado: <string name="update_apk_notice">Esta atualizaรงรฃo altera o prรณprio app, entรฃo o Android vai pedir que vocรช confirme a instalaรงรฃo. Seus jogos, contรชineres e configuraรงรตes sรฃo mantidos.</string> <string name="update_up_to_date">O WinNative estรก atualizado.</string> <string name="update_unsupported_build">As atualizaรงรตes pelo app sรณ sรฃo oferecidas em versรตes oficiais assinadas.</string> - <string name="settings_general_update_channel">Canal de atualizaรงรฃo</string> - <string name="settings_general_update_channel_summary">Oficial entrega versรตes testadas. Desenvolvimento entrega builds antecipadas.</string> + <string name="settings_general_update_source">Origem das atualizacoes</string> + <string name="settings_general_update_source_summary">Uma compilacao de um pull request segue esse pull request; uma instalacao oficial segue as versoes oficiais.</string> <string name="update_channel_official">Oficial</string> - <string name="update_channel_development">Desenvolvimento</string> <string name="networking_section_title">Rede</string> <string name="networking_driver">Driver de rede</string> <string name="networking_driver_winnative">Driver de Rede WinNative</string> @@ -2880,5 +2927,36 @@ Caminho instalado: <string name="steam_controller_left_pad_click">Clique no painel esquerdo</string> <string name="steam_controller_right_pad_click">Clique no painel direito</string> <string name="controller_test_generic">Genรฉrico</string> + <string name="container_display_server">Servidor de exibiรงรฃo</string> + <string name="container_display_server_help">Experimental. A sessรฃo รฉ desenhada pelo compositor Wayland embutido em vez do servidor X.</string> + <string name="container_display_server_wayland_requirements">O Wayland precisa de uma GPU Adreno e de um Proton compatรญvel com Wayland com winewayland e o driver Turnip para Wayland. Instale um em Conteรบdos.</string> + <string name="container_display_server_needs_wayland_proton">Este Proton nรฃo inclui winewayland nem o driver Turnip para Wayland, entรฃo o X11 รฉ usado. Instale uma camada Proton Wayland em Conteรบdos e selecione-a como Proton do contรชiner.</string> + <string name="wayland_unavailable_fallback">O Wayland nรฃo estรก disponรญvel para este jogo. Iniciando no X11.</string> + <string name="wayland_needs_turnip_driver">O Wayland precisa de um driver grรกfico Turnip para exibir o jogo. Instale um em Conteรบdos e selecione-o como driver grรกfico.</string> + <string name="containers_gamescope_new">Criar contรชiner GameScope</string> + <string name="containers_gamescope_create_failed">Nรฃo foi possรญvel criar o contรชiner GameScope</string> + <string name="linux_apps_need_gamescope_container">Crie primeiro o contรชiner GameScope em Configuraรงรตes โ€บ Contรชineres.</string> + <string name="linux_runtime_missing">O runtime Linux nรฃo estรก instalado, entรฃo este contรชiner ainda nรฃo pode iniciar. Instale com Cliente Linux em Configuracoes > Lojas.</string> + <string name="container_display_server_gamescope">O Gamescope sempre desenha atravรฉs do compositor Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Integrado ao ambiente Linux. Os drivers Android baixados valem apenas para contรชineres Wine e Proton.</string> + <string name="linux_runtime_windows_program">Este รฉ um programa do Windows. Mova-o para um contรชiner Wine nas configuraรงรตes do atalho para iniciรก-lo.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Escolha onde instalar o driver. Os pacotes reconhecidos usam automaticamente a plataforma correta.</string> + <string name="settings_drivers_restore_defaults">Restaurar padrรตes</string> + <string name="settings_drivers_defaults_present">Os repositรณrios padrรฃo jรก estรฃo presentes</string> + <string name="settings_drivers_active">Ativo</string> + <string name="settings_drivers_bundled">Mesa incluรญdo</string> + <string name="settings_drivers_legacy">Driver do cliente Linux</string> + <string name="settings_drivers_routed">Driver identificado e instalado para %1$s.</string> + <string name="linux_child_processes_message">O Android limita quantos processos em segundo plano um app pode ter, e uma sessรฃo Linux usa muito mais: o Steam ou um jogo pode ser encerrado durante a partida. Ative โ€œDesativar restriรงรตes de processos filhosโ€ nas Opรงรตes do desenvolvedor. Abrir agora?</string> + <string name="linux_child_processes_adb">O Android limita quantos processos em segundo plano um app pode ter, e uma sessรฃo Linux usa muito mais: o Steam ou um jogo pode ser encerrado durante a partida. Esta versรฃo do Android nรฃo tem essa opรงรฃo. Em um computador, execute:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b31d0e32d..4651fe9e6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">Introduzir caminho</string> <string name="common_ui_path_not_found">Caminho nรฃo encontrado</string> <string name="common_ui_select_valid_exe_file">Selecione um ficheiro .exe vรกlido</string> + <string name="common_ui_select_executable">Selecionar executรกvel</string> + <string name="common_ui_select_valid_executable">Selecione um executรกvel Windows ou Linux</string> <string name="common_ui_select_valid_icon_file">Selecione um ficheiro .ico ou .png vรกlido.</string> <string name="common_ui_exit">Sair</string> <string name="common_ui_exit_app">Sair da Aplicaรงรฃo</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">Versรฃo definida para %1$s. Use Procurar Atualizaรงรฃo para trocar os ficheiros instalados.</string> <string name="common_ui_select">Selecionar</string> <string name="common_ui_storage_roots">Raรญzes de Armazenamento</string> + <string name="common_ui_app_storage">Armazenamento da app</string> + <string name="common_ui_move">Mover</string> + <string name="library_games_move_to_app_storage_title">Mover para interno</string> + <string name="library_games_move_to_download_folder_title">Mover para externo</string> + <string name="library_games_move_to_app_storage_confirm">Mover %1$s para o armazenamento interno?</string> + <string name="library_games_move_to_download_folder_confirm">Mover %1$s para o armazenamento externo?</string> + <string name="library_games_moving">A mover o jogo</string> + <string name="library_games_move_done">%1$s movido</string> + <string name="library_games_move_failed">Nรฃo foi possรญvel mover %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Procurar pastas locais diretamente</string> <string name="common_ui_no_folders_available_here">Nenhuma pasta disponรญvel aqui</string> <string name="common_ui_grant_all_files_access_browse">Conceda acesso a todos os ficheiros para procurar pastas diretamente.</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Testes de Entrada</string> - <string name="library_games_add_custom_game">Adicionar Jogo Personalizado</string> <string name="library_games_game_name">Nome do Jogo</string> <string name="library_games_game_folder_mapped_drive">Pasta do Jogo (Unidade de Jogo Mapeada)</string> <string name="library_games_select_exe_provide_name">Selecione um exe e indique um nome</string> <string name="library_games_sign_in_prompt">Inicie sessรฃo numa loja ou adicione um jogo personalizado para construir a sua biblioteca.</string> + <string name="library_games_add_title">Adicionar jogo ou aplicaรงรฃo</string> + <string name="library_games_add_linux_steam">Adicionar cliente Steam</string> + <string name="library_games_type">Tipo</string> + <string name="library_games_type_game">Jogo</string> + <string name="library_games_type_app">Aplicaรงรฃo</string> + <string name="library_games_select_executable_or_rom">Selecione um executรกvel ou uma ROM de consola</string> + <string name="library_games_added">%1$s adicionado</string> + <string name="library_games_add_failed">Nรฃo foi possรญvel adicionar %1$s</string> + <string name="linux_runtime_not_installed">O runtime Linux ainda nรฃo estรก instalado, por isso %1$s nรฃo pode ser iniciado. Instale-o com Cliente Linux em Definiรงรตes > Lojas.</string> + <string name="linux_client_button">Cliente Linux</string> + <string name="linux_client_title">Cliente Steam para Linux</string> + <string name="linux_client_install_message">Transfere o runtime Linux do WinNative e o cliente Steam da Valve e instala ambos. A transferรชncia ultrapassa 2 GB, por isso use Wi-Fi.</string> + <string name="linux_client_installed_message">O runtime Linux e o cliente Steam estรฃo instalados, e o Steam estรก na Biblioteca.</string> + <string name="linux_client_failed_message">A instalaรงรฃo nรฃo terminou. Verifique a ligaรงรฃo e tente novamente.</string> + <string name="linux_client_no_space_message">Espaรงo livre insuficiente: sรฃo necessรกrios %1$s e estรฃo disponรญveis %2$s.</string> + <string name="linux_client_stage_connect">A ligar</string> + <string name="linux_client_stage_download_runtime">A transferir o runtime Linux</string> + <string name="linux_client_stage_install_runtime">A instalar o runtime Linux</string> + <string name="linux_client_stage_download_proton">A transferir o Proton</string> + <string name="linux_client_stage_install_proton">A instalar o Proton</string> + <string name="linux_client_stage_download_steam">A transferir o cliente Steam</string> + <string name="linux_client_stage_install_steam">A instalar o cliente Steam</string> + <string name="linux_client_stage_library">A adicionar o Steam ร  Biblioteca</string> + <string name="linux_client_progress_amount">%1$s de %2$s</string> + <string name="linux_client_retry">Tentar novamente</string> + <string name="linux_client_busy">O cliente Steam para Linux ainda estรก a ser instalado. Tente novamente quando terminar.</string> + <string name="linux_client_update_message">Estรก disponรญvel um ambiente Linux mais recente. A atualizaรงรฃo mantรฉm o inรญcio de sessรฃo na Steam, os jogos instalados, as gravaรงรตes e os contentores.</string> + <string name="linux_client_update_session_running">Estรก a decorrer uma sessรฃo Linux. Saia dela e depois atualize.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">A sessรฃo Linux parou assim que comeรงou. O registo estรก em Definiรงรตes > Depuraรงรฃo > Abrir Gestor de Registos; partilhe-o para que a causa possa ser encontrada.</string> + <string name="linux_session_stopped">A sessรฃo Linux parou sozinha (estado %1$d). O registo estรก em Definiรงรตes > Depuraรงรฃo > Abrir Gestor de Registos; partilhe-o para que a causa possa ser encontrada.</string> + <string name="linux_session_trimmed">O Android parou a sessรฃo Linux. Normalmente significa que o limite de processos em segundo plano foi atingido: ative โ€œDesativar restriรงรตes de processos subordinadosโ€ nas Opรงรตes de programador e tente novamente.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Lojas</string> @@ -1357,6 +1401,10 @@ Caminho instalado: <string name="setup_wizard_title">Assistente de Configuraรงรฃo</string> <string name="setup_wizard_advanced_user">Utilizador Avanรงado</string> <string name="setup_wizard_next">Seguinte</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Concluir</string> <string name="setup_wizard_download_and_install">Transferir + Instalar</string> <string name="setup_wizard_open_drivers">Abrir Controladores</string> @@ -2863,10 +2911,9 @@ Caminho instalado: <string name="update_apk_notice">Esta atualizaรงรฃo altera a prรณpria aplicaรงรฃo, por isso o Android vai pedir-lhe que confirme a instalaรงรฃo. Os seus jogos, contentores e definiรงรตes sรฃo mantidos.</string> <string name="update_up_to_date">O WinNative estรก atualizado.</string> <string name="update_unsupported_build">As atualizaรงรตes na aplicaรงรฃo sรณ estรฃo disponรญveis em versรตes oficiais assinadas.</string> - <string name="settings_general_update_channel">Canal de Atualizaรงรตes</string> - <string name="settings_general_update_channel_summary">Oficial disponibiliza versรตes testadas. Desenvolvimento disponibiliza compilaรงรตes iniciais.</string> + <string name="settings_general_update_source">Origem das atualizaรงรตes</string> + <string name="settings_general_update_source_summary">Uma compilaรงรฃo de um pull request segue esse pull request; uma instalaรงรฃo oficial segue as versรตes oficiais.</string> <string name="update_channel_official">Oficial</string> - <string name="update_channel_development">Desenvolvimento</string> <string name="networking_section_title">Rede</string> <string name="networking_driver">Controlador de rede</string> <string name="networking_driver_winnative">Controlador de Rede WinNative</string> @@ -2879,4 +2926,35 @@ Caminho instalado: <string name="steam_controller_left_pad_click">Clique no painel esquerdo</string> <string name="steam_controller_right_pad_click">Clique no painel direito</string> <string name="controller_test_generic">Genรฉrico</string> + <string name="container_display_server">Servidor de ecrรฃ</string> + <string name="container_display_server_help">Experimental. A sessรฃo รฉ desenhada atravรฉs do compositor Wayland integrado em vez do servidor X.</string> + <string name="container_display_server_wayland_requirements">O Wayland precisa de uma GPU Adreno e de um Proton compatรญvel com Wayland com winewayland e o controlador Turnip para Wayland. Instale um em Conteรบdos.</string> + <string name="container_display_server_needs_wayland_proton">Este Proton nรฃo inclui winewayland nem o controlador Turnip para Wayland, por isso รฉ usado o X11. Instale uma camada Proton Wayland em Conteรบdos e selecione-a como Proton do contentor.</string> + <string name="wayland_unavailable_fallback">O Wayland nรฃo estรก disponรญvel para este jogo. A iniciar em X11.</string> + <string name="wayland_needs_turnip_driver">O Wayland precisa de um controlador grรกfico Turnip para mostrar o jogo. Instale um em Conteรบdos e selecione-o como controlador grรกfico.</string> + <string name="containers_gamescope_new">Criar contentor GameScope</string> + <string name="containers_gamescope_create_failed">Nรฃo foi possรญvel criar o contentor GameScope</string> + <string name="linux_apps_need_gamescope_container">Crie primeiro o contentor GameScope em Definiรงรตes โ€บ Contentores.</string> + <string name="linux_runtime_missing">O runtime Linux nรฃo estรก instalado, por isso este contentor ainda nรฃo pode iniciar. Instale-o com Cliente Linux em Definiรงรตes > Lojas.</string> + <string name="container_display_server_gamescope">O Gamescope desenha sempre atravรฉs do compositor Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Integrado no ambiente Linux. Os controladores Android transferidos aplicam-se apenas a contentores Wine e Proton.</string> + <string name="linux_runtime_windows_program">Este รฉ um programa Windows. Mova-o para um contentor Wine nas definiรงรตes do atalho para o iniciar.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Escolha onde instalar o controlador. Os pacotes reconhecidos utilizam automaticamente a plataforma correta.</string> + <string name="settings_drivers_restore_defaults">Restaurar predefiniรงรตes</string> + <string name="settings_drivers_defaults_present">Os repositรณrios predefinidos jรก estรฃo presentes</string> + <string name="settings_drivers_active">Ativo</string> + <string name="settings_drivers_bundled">Mesa incluรญdo</string> + <string name="settings_drivers_legacy">Controlador do cliente Linux</string> + <string name="settings_drivers_routed">Controlador identificado e instalado para %1$s.</string> + <string name="linux_child_processes_message">O Android limita quantos processos em segundo plano uma aplicaรงรฃo pode ter, e uma sessรฃo Linux usa muitos mais: o Steam ou um jogo pode ser terminado a meio. Ative โ€œDesativar restriรงรตes de processos subordinadosโ€ nas Opรงรตes de programador. Abrir agora?</string> + <string name="linux_child_processes_adb">O Android limita quantos processos em segundo plano uma aplicaรงรฃo pode ter, e uma sessรฃo Linux usa muitos mais: o Steam ou um jogo pode ser terminado a meio. Esta versรฃo do Android nรฃo tem essa opรงรฃo. Num computador, execute:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 79a211091..87f8b877d 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">Restaureaza pictograma</string> <string name="common_ui_select_exe">Selecteaza EXE</string> <string name="common_ui_select_valid_exe_file">Selecteaza un fisier .exe valid</string> + <string name="common_ui_select_executable">Selecteazฤƒ executabilul</string> + <string name="common_ui_select_valid_executable">Selecteazฤƒ un executabil Windows sau Linux</string> <string name="common_ui_select_valid_icon_file">Selecteaza un fisier .ico sau .png valid.</string> <string name="common_ui_exit">Iesire</string> <string name="common_ui_exit_app">Ieศ™i din aplicaศ›ie</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">Introduceศ›i calea</string> <string name="common_ui_path_not_found">Calea nu a fost gฤƒsitฤƒ</string> <string name="common_ui_storage_roots">Locaศ›ii de stocare</string> + <string name="common_ui_app_storage">Stocarea aplicaศ›iei</string> + <string name="common_ui_move">Mutฤƒ</string> + <string name="library_games_move_to_app_storage_title">Mutฤƒ รฎn internฤƒ</string> + <string name="library_games_move_to_download_folder_title">Mutฤƒ รฎn externฤƒ</string> + <string name="library_games_move_to_app_storage_confirm">Muศ›i %1$s รฎn stocarea internฤƒ?</string> + <string name="library_games_move_to_download_folder_confirm">Muศ›i %1$s รฎn stocarea externฤƒ?</string> + <string name="library_games_moving">Se mutฤƒ jocul</string> + <string name="library_games_move_done">%1$s a fost mutat</string> + <string name="library_games_move_failed">%1$s nu a putut fi mutat</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Rฤƒsfoieศ™te direct folderele locale</string> <string name="common_ui_no_folders_available_here">Niciun folder disponibil aici</string> <string name="common_ui_grant_all_files_access_browse">Acordฤƒ acces complet la fiศ™iere pentru a rฤƒsfoi direct folderele locale.</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Teste de intrare</string> - <string name="library_games_add_custom_game">Adauga joc personalizat</string> <string name="library_games_game_name">Nume joc</string> <string name="library_games_game_folder_mapped_drive">Folder joc (unitate de joc mapata)</string> <string name="library_games_select_exe_provide_name">Selecteaza un exe si introdu un nume</string> <string name="library_games_sign_in_prompt">Autentifica-te intr-un magazin sau adauga un joc personalizat pentru a-ti construi biblioteca.</string> + <string name="library_games_add_title">Adaugฤƒ joc sau aplicaศ›ie</string> + <string name="library_games_add_linux_steam">Adaugฤƒ clientul Steam</string> + <string name="library_games_type">Tip</string> + <string name="library_games_type_game">Joc</string> + <string name="library_games_type_app">Aplicaศ›ie</string> + <string name="library_games_select_executable_or_rom">Selecteazฤƒ un executabil sau un ROM de consolฤƒ</string> + <string name="library_games_added">%1$s a fost adฤƒugat</string> + <string name="library_games_add_failed">Nu s-a putut adฤƒuga %1$s</string> + <string name="linux_runtime_not_installed">Mediul de execuศ›ie Linux nu este รฎncฤƒ instalat, aศ™a cฤƒ %1$s nu poate fi pornit. Instaleazฤƒ-l cu Client Linux din Setari > Magazine.</string> + <string name="linux_client_button">Client Linux</string> + <string name="linux_client_title">Client Steam pentru Linux</string> + <string name="linux_client_install_message">Descarcฤƒ mediul de execuศ›ie Linux de la WinNative ศ™i clientul Steam de la Valve, apoi le instaleazฤƒ pe amรขndouฤƒ. Descฤƒrcarea depฤƒศ™eศ™te 2 GB, aศ™a cฤƒ foloseศ™te Wi-Fi.</string> + <string name="linux_client_installed_message">Mediul de execuศ›ie Linux ศ™i clientul Steam sunt instalate, iar Steam este รฎn Bibliotecฤƒ.</string> + <string name="linux_client_failed_message">Instalarea nu s-a finalizat. Verificฤƒ conexiunea ศ™i รฎncearcฤƒ din nou.</string> + <string name="linux_client_no_space_message">Spaศ›iu liber insuficient: sunt necesari %1$s ศ™i sunt disponibili %2$s.</string> + <string name="linux_client_stage_connect">Se conecteazฤƒ</string> + <string name="linux_client_stage_download_runtime">Se descarcฤƒ mediul de execuศ›ie Linux</string> + <string name="linux_client_stage_install_runtime">Se instaleazฤƒ mediul de execuศ›ie Linux</string> + <string name="linux_client_stage_download_proton">Se descarcฤƒ Proton</string> + <string name="linux_client_stage_install_proton">Se instaleazฤƒ Proton</string> + <string name="linux_client_stage_download_steam">Se descarcฤƒ clientul Steam</string> + <string name="linux_client_stage_install_steam">Se instaleazฤƒ clientul Steam</string> + <string name="linux_client_stage_library">Se adaugฤƒ Steam รฎn Bibliotecฤƒ</string> + <string name="linux_client_progress_amount">%1$s din %2$s</string> + <string name="linux_client_retry">Reรฎncearcฤƒ</string> + <string name="linux_client_busy">Clientul Steam pentru Linux รฎncฤƒ se instaleazฤƒ. รŽncearcฤƒ din nou dupฤƒ ce se terminฤƒ.</string> + <string name="linux_client_update_message">Este disponibil un mediu Linux mai nou. Actualizarea pฤƒstreazฤƒ autentificarea Steam, jocurile instalate, salvฤƒrile ศ™i containerele.</string> + <string name="linux_client_update_session_running">O sesiune Linux este รฎn desfฤƒศ™urare. รŽnchide-o, apoi actualizeazฤƒ.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Sesiunea Linux s-a oprit imediat dupa pornire. Jurnalul ei este in Setari > Depanare > Deschide managerul de jurnale; distribuie-l pentru a se putea gasi cauza.</string> + <string name="linux_session_stopped">Sesiunea Linux s-a oprit singura (stare %1$d). Jurnalul ei este in Setari > Depanare > Deschide managerul de jurnale; distribuie-l pentru a se putea gasi cauza.</string> + <string name="linux_session_trimmed">Android a oprit sesiunea Linux. De obicei inseamna ca s-a atins limita de procese din fundal: activeaza โ€œDezactiveazฤƒ restricศ›iile proceselor secundareโ€ in Optiuni pentru dezvoltatori si incearca din nou.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Magazine</string> @@ -1065,6 +1109,10 @@ Cale instalata: <string name="setup_wizard_title">Asistent de configurare</string> <string name="setup_wizard_advanced_user">Utilizator avansat</string> <string name="setup_wizard_next">Urmatorul</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Finalizare</string> <string name="setup_wizard_download_and_install">Descarca + Instaleaza</string> <string name="setup_wizard_open_drivers">Deschide driverele</string> @@ -2864,10 +2912,9 @@ Cale instalata: <string name="update_apk_notice">Aceastฤƒ actualizare modificฤƒ aplicaศ›ia รฎn sine, aศ™a cฤƒ Android รฎศ›i va cere sฤƒ confirmi instalarea. Jocurile, containerele ศ™i setฤƒrile tale se pฤƒstreazฤƒ.</string> <string name="update_up_to_date">WinNative este la zi.</string> <string name="update_unsupported_build">Actualizฤƒrile din aplicaศ›ie sunt oferite doar pentru versiunile oficiale semnate.</string> - <string name="settings_general_update_channel">Canal de actualizare</string> - <string name="settings_general_update_channel_summary">Oficial livreazฤƒ versiuni testate. Dezvoltare livreazฤƒ compilฤƒri timpurii.</string> + <string name="settings_general_update_source">Sursa actualizarilor</string> + <string name="settings_general_update_source_summary">O compilare dintr-un pull request urmeaza acel pull request; o instalare oficiala urmeaza versiunile oficiale.</string> <string name="update_channel_official">Oficial</string> - <string name="update_channel_development">Dezvoltare</string> <string name="networking_section_title">Reศ›ea</string> <string name="networking_driver">Driver de reศ›ea</string> <string name="networking_driver_winnative">Driver de reศ›ea WinNative</string> @@ -2880,5 +2927,36 @@ Cale instalata: <string name="steam_controller_left_pad_click">Clic pe panoul stรขng</string> <string name="steam_controller_right_pad_click">Clic pe panoul drept</string> <string name="controller_test_generic">Generic</string> + <string name="container_display_server">Server de afiศ™are</string> + <string name="container_display_server_help">Experimental. Sesiunea este desenatฤƒ prin compozitorul Wayland integrat รฎn locul serverului X.</string> + <string name="container_display_server_wayland_requirements">Wayland necesitฤƒ un GPU Adreno ศ™i un Proton compatibil Wayland cu winewayland ศ™i driverul Turnip pentru Wayland. Instaleazฤƒ unul din Conศ›inut.</string> + <string name="container_display_server_needs_wayland_proton">Acest Proton nu conศ›ine winewayland sau driverul Turnip pentru Wayland, aศ™a cฤƒ se foloseศ™te X11. Instaleazฤƒ un strat Proton Wayland din Conศ›inut ศ™i selecteazฤƒ-l ca Proton al containerului.</string> + <string name="wayland_unavailable_fallback">Wayland nu este disponibil pentru acest joc. Se lanseazฤƒ pe X11.</string> + <string name="wayland_needs_turnip_driver">Wayland are nevoie de un driver grafic Turnip pentru a afiศ™a jocul. Instaleazฤƒ unul din Conศ›inut ศ™i selecteazฤƒ-l ca driver grafic.</string> + <string name="containers_gamescope_new">Creeazฤƒ containerul GameScope</string> + <string name="containers_gamescope_create_failed">Containerul GameScope nu a putut fi creat</string> + <string name="linux_apps_need_gamescope_container">Creeazฤƒ mai รฎntรขi containerul GameScope รฎn Setฤƒri โ€บ Containere.</string> + <string name="linux_runtime_missing">Mediul de execuศ›ie Linux nu este instalat, aศ™a cฤƒ acest container nu poate porni รฎncฤƒ. Instaleazฤƒ-l cu Client Linux din Setari > Magazine.</string> + <string name="container_display_server_gamescope">Gamescope deseneazฤƒ รฎntotdeauna prin compozitorul Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Inclus รฎn mediul Linux. Driverele Android descฤƒrcate se aplicฤƒ doar containerelor Wine ศ™i Proton.</string> + <string name="linux_runtime_windows_program">Acesta este un program Windows. Mutฤƒ-l รฎntr-un container Wine din setฤƒrile scurtฤƒturii pentru a-l porni.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Alege unde se instaleazฤƒ driverul. Pachetele recunoscute folosesc automat platforma corectฤƒ.</string> + <string name="settings_drivers_restore_defaults">Restabileศ™te valorile implicite</string> + <string name="settings_drivers_defaults_present">Depozitele implicite sunt deja prezente</string> + <string name="settings_drivers_active">Activ</string> + <string name="settings_drivers_bundled">Mesa inclus</string> + <string name="settings_drivers_legacy">Driverul clientului Linux</string> + <string name="settings_drivers_routed">Driver identificat ศ™i instalat pentru %1$s.</string> + <string name="linux_child_processes_message">Android limiteazฤƒ numฤƒrul de procese de fundal ale unei aplicaศ›ii, iar o sesiune Linux foloseศ™te mult mai multe: Steam sau un joc poate fi รฎnchis รฎn timpul jocului. Activeazฤƒ โ€žDezactiveazฤƒ restricศ›iile proceselor secundareโ€ รฎn Opศ›iuni pentru dezvoltatori. Le deschizi acum?</string> + <string name="linux_child_processes_adb">Android limiteazฤƒ numฤƒrul de procese de fundal ale unei aplicaศ›ii, iar o sesiune Linux foloseศ™te mult mai multe: Steam sau un joc poate fi รฎnchis รฎn timpul jocului. Aceastฤƒ versiune de Android nu are acea opศ›iune. De pe un computer, ruleazฤƒ:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 76a656958..c3bfef421 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">ะ’ะฒะตะดะธั‚ะต ะฟัƒั‚ัŒ</string> <string name="common_ui_path_not_found">ะŸัƒั‚ัŒ ะฝะต ะฝะฐะนะดะตะฝ</string> <string name="common_ui_select_valid_exe_file">ะ’ั‹ะฑะตั€ะธั‚ะต ะบะพั€ั€ะตะบั‚ะฝั‹ะน ั„ะฐะนะป .exe</string> + <string name="common_ui_select_executable">ะ’ั‹ะฑั€ะฐั‚ัŒ ะธัะฟะพะปะฝัะตะผั‹ะน ั„ะฐะนะป</string> + <string name="common_ui_select_valid_executable">ะ’ั‹ะฑะตั€ะธั‚ะต ะธัะฟะพะปะฝัะตะผั‹ะน ั„ะฐะนะป Windows ะธะปะธ Linux</string> <string name="common_ui_select_valid_icon_file">ะ’ั‹ะฑะตั€ะธั‚ะต ะบะพั€ั€ะตะบั‚ะฝั‹ะน ั„ะฐะนะป .ico ะธะปะธ .png.</string> <string name="common_ui_exit">ะ’ั‹ั…ะพะด</string> <string name="common_ui_exit_app">ะ’ั‹ะนั‚ะธ ะธะท ะฟั€ะธะปะพะถะตะฝะธั</string> @@ -148,6 +150,16 @@ <string name="store_game_branch_switch_installed">ะ’ั‹ะฑั€ะฐะฝะฐ ะฒะตั€ัะธั %1$s. ะะฐะถะผะธั‚ะต ยซะŸั€ะพะฒะตั€ะธั‚ัŒ ะพะฑะฝะพะฒะปะตะฝะธัยป, ั‡ั‚ะพะฑั‹ ะฟะตั€ะตะบะปัŽั‡ะธั‚ัŒ ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะต ั„ะฐะนะปั‹.</string> <string name="common_ui_select">ะ’ั‹ะฑั€ะฐั‚ัŒ</string> <string name="common_ui_storage_roots">ะšะพั€ะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ</string> + <string name="common_ui_app_storage">ะฅั€ะฐะฝะธะปะธั‰ะต ะฟั€ะธะปะพะถะตะฝะธั</string> + <string name="common_ui_move">ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ</string> + <string name="library_games_move_to_app_storage_title">ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะพ ะฒะฝัƒั‚ั€ะตะฝะฝัŽัŽ</string> + <string name="library_games_move_to_download_folder_title">ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ ะฒะพ ะฒะฝะตัˆะฝัŽัŽ</string> + <string name="library_games_move_to_app_storage_confirm">ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ %1$s ะฒะพ ะฒะฝัƒั‚ั€ะตะฝะฝะตะต ั…ั€ะฐะฝะธะปะธั‰ะต?</string> + <string name="library_games_move_to_download_folder_confirm">ะŸะตั€ะตะผะตัั‚ะธั‚ัŒ %1$s ะฒะพ ะฒะฝะตัˆะฝะตะต ั…ั€ะฐะฝะธะปะธั‰ะต?</string> + <string name="library_games_moving">ะŸะตั€ะตะผะตั‰ะตะฝะธะต ะธะณั€ั‹</string> + <string name="library_games_move_done">%1$s ะฟะตั€ะตะผะตั‰ะตะฝะฐ</string> + <string name="library_games_move_failed">ะะต ัƒะดะฐะปะพััŒ ะฟะตั€ะตะผะตัั‚ะธั‚ัŒ %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">ะŸั€ะพัะผะฐั‚ั€ะธะฒะฐั‚ัŒ ะปะพะบะฐะปัŒะฝั‹ะต ะฟะฐะฟะบะธ ะฝะฐะฟั€ัะผัƒัŽ</string> <string name="common_ui_no_folders_available_here">ะ—ะดะตััŒ ะฝะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ะฟะฐะฟะพะบ</string> <string name="common_ui_grant_all_files_access_browse">ะŸั€ะตะดะพัั‚ะฐะฒัŒั‚ะต ะฟะพะปะฝั‹ะน ะดะพัั‚ัƒะฟ ะบ ั„ะฐะนะปะฐะผ, ั‡ั‚ะพะฑั‹ ะฟั€ะพัะผะฐั‚ั€ะธะฒะฐั‚ัŒ ะฟะฐะฟะบะธ ะฝะฐะฟั€ัะผัƒัŽ.</string> @@ -230,11 +242,43 @@ <string name="hero_graphics_test_32">32-ะฑะธั‚</string> <string name="hero_graphics_test_64">64-ะฑะธั‚</string> <string name="hero_input_tests_title">ะขะตัั‚ั‹ ะฒะฒะพะดะฐ</string> - <string name="library_games_add_custom_game">ะ”ะพะฑะฐะฒะธั‚ัŒ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบัƒัŽ ะธะณั€ัƒ</string> <string name="library_games_game_name">ะะฐะทะฒะฐะฝะธะต ะธะณั€ั‹</string> <string name="library_games_game_folder_mapped_drive">ะŸะฐะฟะบะฐ ั ะธะณั€ะพะน (ะฟะพะดะบะปัŽั‡ะตะฝะฝั‹ะน ะธะณั€ะพะฒะพะน ะดะธัะบ)</string> <string name="library_games_select_exe_provide_name">ะ’ั‹ะฑะตั€ะธั‚ะต EXE-ั„ะฐะนะป ะธ ัƒะบะฐะถะธั‚ะต ะฝะฐะทะฒะฐะฝะธะต.</string> <string name="library_games_sign_in_prompt">ะ’ะพะนะดะธั‚ะต ะฒ ะผะฐะณะฐะทะธะฝ ะธะปะธ ะดะพะฑะฐะฒัŒั‚ะต ัะพะฑัั‚ะฒะตะฝะฝัƒัŽ ะธะณั€ัƒ, ั‡ั‚ะพะฑั‹ ัะพะทะดะฐั‚ัŒ ัะฒะพัŽ ะฑะธะฑะปะธะพั‚ะตะบัƒ.</string> + <string name="library_games_add_title">ะ”ะพะฑะฐะฒะธั‚ัŒ ะธะณั€ัƒ ะธะปะธ ะฟั€ะธะปะพะถะตะฝะธะต</string> + <string name="library_games_add_linux_steam">ะ”ะพะฑะฐะฒะธั‚ัŒ ะบะปะธะตะฝั‚ Steam</string> + <string name="library_games_type">ะขะธะฟ</string> + <string name="library_games_type_game">ะ˜ะณั€ะฐ</string> + <string name="library_games_type_app">ะŸั€ะธะปะพะถะตะฝะธะต</string> + <string name="library_games_select_executable_or_rom">ะ’ั‹ะฑะตั€ะธั‚ะต ะธัะฟะพะปะฝัะตะผั‹ะน ั„ะฐะนะป ะธะปะธ ROM ะบะพะฝัะพะปะธ</string> + <string name="library_games_added">%1$s ะดะพะฑะฐะฒะปะตะฝะพ</string> + <string name="library_games_add_failed">ะะต ัƒะดะฐะปะพััŒ ะดะพะฑะฐะฒะธั‚ัŒ %1$s</string> + <string name="linux_runtime_not_installed">ะกั€ะตะดะฐ ะฒั‹ะฟะพะปะฝะตะฝะธั Linux ะตั‰ั‘ ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะฐ, ะฟะพัั‚ะพะผัƒ %1$s ะฝะตะปัŒะทั ะทะฐะฟัƒัั‚ะธั‚ัŒ. ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะตั‘ ะบะฝะพะฟะบะพะน ะšะปะธะตะฝั‚ Linux ะฒ ั€ะฐะทะดะตะปะต ะะฐัั‚ั€ะพะนะบะธ > ะœะฐะณะฐะทะธะฝั‹.</string> + <string name="linux_client_button">ะšะปะธะตะฝั‚ Linux</string> + <string name="linux_client_title">ะšะปะธะตะฝั‚ Steam ะดะปั Linux</string> + <string name="linux_client_install_message">ะ—ะฐะณั€ัƒะถะฐะตั‚ ัั€ะตะดัƒ ะฒั‹ะฟะพะปะฝะตะฝะธั Linux ั WinNative ะธ ะบะปะธะตะฝั‚ Steam ั ัะตั€ะฒะตั€ะพะฒ Valve, ะทะฐั‚ะตะผ ัƒัั‚ะฐะฝะฐะฒะปะธะฒะฐะตั‚ ะพะฑะฐ. ะžะฑัŠั‘ะผ ะทะฐะณั€ัƒะทะบะธ ะฑะพะปัŒัˆะต 2 ะ“ะ‘, ะฟะพัั‚ะพะผัƒ ะธัะฟะพะปัŒะทัƒะนั‚ะต Wi-Fi.</string> + <string name="linux_client_installed_message">ะกั€ะตะดะฐ ะฒั‹ะฟะพะปะฝะตะฝะธั Linux ะธ ะบะปะธะตะฝั‚ Steam ัƒัั‚ะฐะฝะพะฒะปะตะฝั‹, ะฐ Steam ะตัั‚ัŒ ะฒ ะ‘ะธะฑะปะธะพั‚ะตะบะต.</string> + <string name="linux_client_failed_message">ะฃัั‚ะฐะฝะพะฒะบะฐ ะฝะต ะทะฐะฒะตั€ัˆะธะปะฐััŒ. ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะธ ะฟะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ.</string> + <string name="linux_client_no_space_message">ะะตะดะพัั‚ะฐั‚ะพั‡ะฝะพ ัะฒะพะฑะพะดะฝะพะณะพ ะผะตัั‚ะฐ: ะฝัƒะถะฝะพ %1$s, ะดะพัั‚ัƒะฟะฝะพ %2$s.</string> + <string name="linux_client_stage_connect">ะŸะพะดะบะปัŽั‡ะตะฝะธะต</string> + <string name="linux_client_stage_download_runtime">ะ—ะฐะณั€ัƒะทะบะฐ ัั€ะตะดั‹ ะฒั‹ะฟะพะปะฝะตะฝะธั Linux</string> + <string name="linux_client_stage_install_runtime">ะฃัั‚ะฐะฝะพะฒะบะฐ ัั€ะตะดั‹ ะฒั‹ะฟะพะปะฝะตะฝะธั Linux</string> + <string name="linux_client_stage_download_proton">ะ—ะฐะณั€ัƒะทะบะฐ Proton</string> + <string name="linux_client_stage_install_proton">ะฃัั‚ะฐะฝะพะฒะบะฐ Proton</string> + <string name="linux_client_stage_download_steam">ะ—ะฐะณั€ัƒะทะบะฐ ะบะปะธะตะฝั‚ะฐ Steam</string> + <string name="linux_client_stage_install_steam">ะฃัั‚ะฐะฝะพะฒะบะฐ ะบะปะธะตะฝั‚ะฐ Steam</string> + <string name="linux_client_stage_library">ะ”ะพะฑะฐะฒะปะตะฝะธะต Steam ะฒ ะ‘ะธะฑะปะธะพั‚ะตะบัƒ</string> + <string name="linux_client_progress_amount">%1$s ะธะท %2$s</string> + <string name="linux_client_retry">ะŸะพะฒั‚ะพั€ะธั‚ัŒ</string> + <string name="linux_client_busy">ะšะปะธะตะฝั‚ Steam ะดะปั Linux ะตั‰ั‘ ัƒัั‚ะฐะฝะฐะฒะปะธะฒะฐะตั‚ัั. ะŸะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ ะฟะพัะปะต ะทะฐะฒะตั€ัˆะตะฝะธั.</string> + <string name="linux_client_update_message">ะ”ะพัั‚ัƒะฟะฝะฐ ะฑะพะปะตะต ะฝะพะฒะฐั ัั€ะตะดะฐ Linux. ะŸั€ะธ ะพะฑะฝะพะฒะปะตะฝะธะธ ัะพั…ั€ะฐะฝััŽั‚ัั ะฒั…ะพะด ะฒ Steam, ัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะต ะธะณั€ั‹, ัะพั…ั€ะฐะฝะตะฝะธั ะธ ะบะพะฝั‚ะตะนะฝะตั€ั‹.</string> + <string name="linux_client_update_session_running">ะ—ะฐะฟัƒั‰ะตะฝ ัะตะฐะฝั Linux. ะ—ะฐะฒะตั€ัˆะธั‚ะต ะตะณะพ, ะทะฐั‚ะตะผ ะพะฑะฝะพะฒะธั‚ะต.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">ะกะตะฐะฝั Linux ะทะฐะฒะตั€ัˆะธะปัั ัั€ะฐะทัƒ ะฟะพัะปะต ะทะฐะฟัƒัะบะฐ. ะ•ะณะพ ะถัƒั€ะฝะฐะป ะฝะฐั…ะพะดะธั‚ัั ะฒ ะะฐัั‚ั€ะพะนะบะธ > ะžั‚ะปะฐะดะบะฐ > ะžั‚ะบั€ั‹ั‚ัŒ ะดะธัะฟะตั‚ั‡ะตั€ ะถัƒั€ะฝะฐะปะพะฒ; ะฟะพะดะตะปะธั‚ะตััŒ ะธะผ, ั‡ั‚ะพะฑั‹ ะผะพะถะฝะพ ะฑั‹ะปะพ ะฝะฐะนั‚ะธ ะฟั€ะธั‡ะธะฝัƒ.</string> + <string name="linux_session_stopped">ะกะตะฐะฝั Linux ะทะฐะฒะตั€ัˆะธะปัั ัะฐะผ (ัั‚ะฐั‚ัƒั %1$d). ะ•ะณะพ ะถัƒั€ะฝะฐะป ะฝะฐั…ะพะดะธั‚ัั ะฒ ะะฐัั‚ั€ะพะนะบะธ > ะžั‚ะปะฐะดะบะฐ > ะžั‚ะบั€ั‹ั‚ัŒ ะดะธัะฟะตั‚ั‡ะตั€ ะถัƒั€ะฝะฐะปะพะฒ; ะฟะพะดะตะปะธั‚ะตััŒ ะธะผ, ั‡ั‚ะพะฑั‹ ะผะพะถะฝะพ ะฑั‹ะปะพ ะฝะฐะนั‚ะธ ะฟั€ะธั‡ะธะฝัƒ.</string> + <string name="linux_session_trimmed">Android ะพัั‚ะฐะฝะพะฒะธะป ัะตะฐะฝั Linux. ะžะฑั‹ั‡ะฝะพ ัั‚ะพ ะทะฝะฐั‡ะธั‚, ั‡ั‚ะพ ะดะพัั‚ะธะณะฝัƒั‚ ะฟั€ะตะดะตะป ั„ะพะฝะพะฒั‹ั… ะฟั€ะพั†ะตััะพะฒ: ะฒะบะปัŽั‡ะธั‚ะต ยซะžั‚ะบะปัŽั‡ะธั‚ัŒ ะพะณั€ะฐะฝะธั‡ะตะฝะธั ะดะพั‡ะตั€ะฝะธั… ะฟั€ะพั†ะตััะพะฒยป ะฒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะฐ ะธ ะฟะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">ะœะฐะณะฐะทะธะฝั‹</string> <string name="stores_accounts_steam_integration_title">Steam</string> @@ -1135,6 +1179,10 @@ <string name="setup_wizard_title">ะœะฐัั‚ะตั€ ัƒัั‚ะฐะฝะพะฒะบะธ</string> <string name="setup_wizard_advanced_user">ะžะฟั‹ั‚ะฝั‹ะน ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ</string> <string name="setup_wizard_next">ะ”ะฐะปะตะต</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">ะ“ะพั‚ะพะฒะพ</string> <string name="setup_wizard_download_and_install">ะกะบะฐั‡ะฐั‚ัŒ + ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ</string> <string name="setup_wizard_open_drivers">ะžั‚ะบั€ั‹ั‚ัŒ ะดั€ะฐะนะฒะตั€ั‹</string> @@ -2770,10 +2818,9 @@ <string name="update_apk_notice">ะญั‚ะพ ะพะฑะฝะพะฒะปะตะฝะธะต ะธะทะผะตะฝัะตั‚ ัะฐะผะพ ะฟั€ะธะปะพะถะตะฝะธะต, ะฟะพัั‚ะพะผัƒ Android ะฟะพะฟั€ะพัะธั‚ ะฟะพะดั‚ะฒะตั€ะดะธั‚ัŒ ัƒัั‚ะฐะฝะพะฒะบัƒ. ะ’ะฐัˆะธ ะธะณั€ั‹, ะบะพะฝั‚ะตะนะฝะตั€ั‹ ะธ ะฝะฐัั‚ั€ะพะนะบะธ ัะพั…ั€ะฐะฝัั‚ัั.</string> <string name="update_up_to_date">ะฃ ะฒะฐั ะฟะพัะปะตะดะฝัั ะฒะตั€ัะธั WinNative.</string> <string name="update_unsupported_build">ะžะฑะฝะพะฒะปะตะฝะธั ะฒะฝัƒั‚ั€ะธ ะฟั€ะธะปะพะถะตะฝะธั ะดะพัั‚ัƒะฟะฝั‹ ั‚ะพะปัŒะบะพ ะดะปั ะพั„ะธั†ะธะฐะปัŒะฝั‹ั… ะฟะพะดะฟะธัะฐะฝะฝั‹ั… ะฒั‹ะฟัƒัะบะพะฒ.</string> - <string name="settings_general_update_channel">ะšะฐะฝะฐะป ะพะฑะฝะพะฒะปะตะฝะธะน</string> - <string name="settings_general_update_channel_summary">ยซะžั„ะธั†ะธะฐะปัŒะฝั‹ะนยป โ€” ะฟั€ะพะฒะตั€ะตะฝะฝั‹ะต ะฒั‹ะฟัƒัะบะธ. ยซะ ะฐะทั€ะฐะฑะพั‚ะบะฐยป โ€” ั€ะฐะฝะฝะธะต ัะฑะพั€ะบะธ.</string> + <string name="settings_general_update_source">ะ˜ัั‚ะพั‡ะฝะธะบ ะพะฑะฝะพะฒะปะตะฝะธะน</string> + <string name="settings_general_update_source_summary">ะกะฑะพั€ะบะฐ ะธะท ะฟัƒะป-ั€ะตะบะฒะตัั‚ะฐ ัะปะตะดัƒะตั‚ ะทะฐ ัั‚ะธะผ ะฟัƒะป-ั€ะตะบะฒะตัั‚ะพะผ; ะพั„ะธั†ะธะฐะปัŒะฝะฐั ัƒัั‚ะฐะฝะพะฒะบะฐ โ€” ะทะฐ ะพั„ะธั†ะธะฐะปัŒะฝั‹ะผะธ ะฒั‹ะฟัƒัะบะฐะผะธ.</string> <string name="update_channel_official">ะžั„ะธั†ะธะฐะปัŒะฝั‹ะน</string> - <string name="update_channel_development">ะ ะฐะทั€ะฐะฑะพั‚ะบะฐ</string> <string name="networking_section_title">ะกะตั‚ัŒ</string> <string name="networking_driver">ะกะตั‚ะตะฒะพะน ะดั€ะฐะนะฒะตั€</string> <string name="networking_driver_winnative">ะกะตั‚ะตะฒะพะน ะดั€ะฐะนะฒะตั€ WinNative</string> @@ -2786,4 +2833,35 @@ <string name="steam_controller_left_pad_click">ะะฐะถะฐั‚ะธะต ะปะตะฒะพะน ะฟะฐะฝะตะปะธ</string> <string name="steam_controller_right_pad_click">ะะฐะถะฐั‚ะธะต ะฟั€ะฐะฒะพะน ะฟะฐะฝะตะปะธ</string> <string name="controller_test_generic">ะฃะฝะธะฒะตั€ัะฐะปัŒะฝั‹ะน</string> + <string name="container_display_server">ะ”ะธัะฟะปะตะนะฝั‹ะน ัะตั€ะฒะตั€</string> + <string name="container_display_server_help">ะญะบัะฟะตั€ะธะผะตะฝั‚ะฐะปัŒะฝะพ. ะกะตะฐะฝั ะพั‚ั€ะธัะพะฒั‹ะฒะฐะตั‚ัั ั‡ะตั€ะตะท ะฒัั‚ั€ะพะตะฝะฝั‹ะน ะบะพะผะฟะพะทะธั‚ะพั€ Wayland ะฒะผะตัั‚ะพ X-ัะตั€ะฒะตั€ะฐ.</string> + <string name="container_display_server_wayland_requirements">ะ”ะปั Wayland ะฝัƒะถะฝั‹ GPU Adreno ะธ Proton ั ะฟะพะดะดะตั€ะถะบะพะน Wayland, ะฒะบะปัŽั‡ะฐัŽั‰ะธะน winewayland ะธ ะดั€ะฐะนะฒะตั€ Turnip ะดะปั Wayland. ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะตะณะพ ะฒ ั€ะฐะทะดะตะปะต ยซะกะพะดะตั€ะถะธะผะพะตยป.</string> + <string name="container_display_server_needs_wayland_proton">ะ’ ัั‚ะพะผ Proton ะฝะตั‚ winewayland ะธ ะดั€ะฐะนะฒะตั€ะฐ Turnip ะดะปั Wayland, ะฟะพัั‚ะพะผัƒ ะธัะฟะพะปัŒะทัƒะตั‚ัั X11. ะฃัั‚ะฐะฝะพะฒะธั‚ะต ัะปะพะน Proton ะดะปั Wayland ะฒ ั€ะฐะทะดะตะปะต ยซะกะพะดะตั€ะถะธะผะพะตยป ะธ ะฒั‹ะฑะตั€ะธั‚ะต ะตะณะพ ะบะฐะบ Proton ะบะพะฝั‚ะตะนะฝะตั€ะฐ.</string> + <string name="wayland_unavailable_fallback">Wayland ะฝะตะดะพัั‚ัƒะฟะตะฝ ะดะปั ัั‚ะพะน ะธะณั€ั‹. ะ—ะฐะฟัƒัะบ ั‡ะตั€ะตะท X11.</string> + <string name="wayland_needs_turnip_driver">ะ”ะปั ะฟะพะบะฐะทะฐ ะธะณั€ั‹ Wayland ะฝัƒะถะตะฝ ะณั€ะฐั„ะธั‡ะตัะบะธะน ะดั€ะฐะนะฒะตั€ Turnip. ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะตะณะพ ะฒ ั€ะฐะทะดะตะปะต ยซะกะพะดะตั€ะถะธะผะพะตยป ะธ ะฒั‹ะฑะตั€ะธั‚ะต ะบะฐะบ ะณั€ะฐั„ะธั‡ะตัะบะธะน ะดั€ะฐะนะฒะตั€.</string> + <string name="containers_gamescope_new">ะกะพะทะดะฐั‚ัŒ ะบะพะฝั‚ะตะนะฝะตั€ GameScope</string> + <string name="containers_gamescope_create_failed">ะะต ัƒะดะฐะปะพััŒ ัะพะทะดะฐั‚ัŒ ะบะพะฝั‚ะตะนะฝะตั€ GameScope</string> + <string name="linux_apps_need_gamescope_container">ะกะฝะฐั‡ะฐะปะฐ ัะพะทะดะฐะนั‚ะต ะบะพะฝั‚ะตะนะฝะตั€ GameScope ะฒ ั€ะฐะทะดะตะปะต ะะฐัั‚ั€ะพะนะบะธ โ€บ ะšะพะฝั‚ะตะนะฝะตั€ั‹.</string> + <string name="linux_runtime_missing">ะกั€ะตะดะฐ ะฒั‹ะฟะพะปะฝะตะฝะธั Linux ะฝะต ัƒัั‚ะฐะฝะพะฒะปะตะฝะฐ, ะฟะพัั‚ะพะผัƒ ัั‚ะพั‚ ะบะพะฝั‚ะตะนะฝะตั€ ะฟะพะบะฐ ะฝะตะปัŒะทั ะทะฐะฟัƒัั‚ะธั‚ัŒ. ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะตั‘ ะบะฝะพะฟะบะพะน ะšะปะธะตะฝั‚ Linux ะฒ ั€ะฐะทะดะตะปะต ะะฐัั‚ั€ะพะนะบะธ > ะœะฐะณะฐะทะธะฝั‹.</string> + <string name="container_display_server_gamescope">Gamescope ะฒัะตะณะดะฐ ั€ะธััƒะตั‚ ั‡ะตั€ะตะท ะบะพะผะฟะพะทะธั‚ะพั€ Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">ะ’ัั‚ั€ะพะตะฝ ะฒ ัั€ะตะดัƒ Linux. ะ—ะฐะณั€ัƒะถะตะฝะฝั‹ะต ะดั€ะฐะนะฒะตั€ั‹ Android ะฟั€ะธะผะตะฝััŽั‚ัั ั‚ะพะปัŒะบะพ ะบ ะบะพะฝั‚ะตะนะฝะตั€ะฐะผ Wine ะธ Proton.</string> + <string name="linux_runtime_windows_program">ะญั‚ะพ ะฟั€ะพะณั€ะฐะผะผะฐ ะดะปั Windows. ะงั‚ะพะฑั‹ ะทะฐะฟัƒัั‚ะธั‚ัŒ ะตั‘, ะฟะตั€ะตะฝะตัะธั‚ะต ะตั‘ ะฒ ะบะพะฝั‚ะตะนะฝะตั€ Wine ะฒ ะฝะฐัั‚ั€ะพะนะบะฐั… ัั€ะปั‹ะบะฐ.</string> + <string name="settings_content_badge_online">ะžะฝะปะฐะนะฝ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">ะ’ั‹ะฑะตั€ะธั‚ะต, ะบัƒะดะฐ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะดั€ะฐะนะฒะตั€. ะ”ะปั ั€ะฐัะฟะพะทะฝะฐะฝะฝั‹ั… ะฟะฐะบะตั‚ะพะฒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฒั‹ะฑะธั€ะฐะตั‚ัั ะฝัƒะถะฝะฐั ะฟะปะฐั‚ั„ะพั€ะผะฐ.</string> + <string name="settings_drivers_restore_defaults">ะ’ะพััั‚ะฐะฝะพะฒะธั‚ัŒ ะธัั…ะพะดะฝั‹ะต</string> + <string name="settings_drivers_defaults_present">ะกั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะต ั€ะตะฟะพะทะธั‚ะพั€ะธะธ ัƒะถะต ะดะพะฑะฐะฒะปะตะฝั‹</string> + <string name="settings_drivers_active">ะะบั‚ะธะฒะตะฝ</string> + <string name="settings_drivers_bundled">ะ’ัั‚ั€ะพะตะฝะฝั‹ะน Mesa</string> + <string name="settings_drivers_legacy">ะ”ั€ะฐะนะฒะตั€ ะบะปะธะตะฝั‚ะฐ Linux</string> + <string name="settings_drivers_routed">ะ”ั€ะฐะนะฒะตั€ ั€ะฐัะฟะพะทะฝะฐะฝ ะธ ัƒัั‚ะฐะฝะพะฒะปะตะฝ ะดะปั %1$s.</string> + <string name="linux_child_processes_message">Android ะพะณั€ะฐะฝะธั‡ะธะฒะฐะตั‚ ั‡ะธัะปะพ ั„ะพะฝะพะฒั‹ั… ะฟั€ะพั†ะตััะพะฒ ะฟั€ะธะปะพะถะตะฝะธั, ะฐ ัะตะฐะฝั Linux ะทะฐะฟัƒัะบะฐะตั‚ ะธั… ะณะพั€ะฐะทะดะพ ะฑะพะปัŒัˆะต: Steam ะธะปะธ ะธะณั€ัƒ ะผะพะถะตั‚ ะทะฐะบั€ั‹ั‚ัŒ ะฟั€ัะผะพ ะฒะพ ะฒั€ะตะผั ะธะณั€ั‹. ะ’ะบะปัŽั‡ะธั‚ะต ยซะžั‚ะบะปัŽั‡ะธั‚ัŒ ะพะณั€ะฐะฝะธั‡ะตะฝะธั ะดะพั‡ะตั€ะฝะธั… ะฟั€ะพั†ะตััะพะฒยป ะฒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะฐ. ะžั‚ะบั€ั‹ั‚ัŒ?</string> + <string name="linux_child_processes_adb">Android ะพะณั€ะฐะฝะธั‡ะธะฒะฐะตั‚ ั‡ะธัะปะพ ั„ะพะฝะพะฒั‹ั… ะฟั€ะพั†ะตััะพะฒ ะฟั€ะธะปะพะถะตะฝะธั, ะฐ ัะตะฐะฝั Linux ะทะฐะฟัƒัะบะฐะตั‚ ะธั… ะณะพั€ะฐะทะดะพ ะฑะพะปัŒัˆะต: Steam ะธะปะธ ะธะณั€ัƒ ะผะพะถะตั‚ ะทะฐะบั€ั‹ั‚ัŒ ะฟั€ัะผะพ ะฒะพ ะฒั€ะตะผั ะธะณั€ั‹. ะ’ ัั‚ะพะน ะฒะตั€ัะธะธ Android ั‚ะฐะบะพะน ะฝะฐัั‚ั€ะพะนะบะธ ะฝะตั‚. ะะฐ ะบะพะผะฟัŒัŽั‚ะตั€ะต ะฒั‹ะฟะพะปะฝะธั‚ะต:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index af65b3c4a..1ac2a0c33 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">Ange sรถkvรคg</string> <string name="common_ui_path_not_found">Sรถkvรคgen hittades inte</string> <string name="common_ui_select_valid_exe_file">Vรคlj en giltig .exe-fil</string> + <string name="common_ui_select_executable">Vรคlj kรถrbar fil</string> + <string name="common_ui_select_valid_executable">Vรคlj en kรถrbar Windows- eller Linux-fil</string> <string name="common_ui_select_valid_icon_file">Vรคlj en giltig .ico- eller .png-fil.</string> <string name="common_ui_exit">Avsluta</string> <string name="common_ui_exit_app">Avsluta app</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">Versionen รคr instรคlld pรฅ %1$s. Anvรคnd Sรถk efter uppdatering fรถr att byta de installerade filerna.</string> <string name="common_ui_select">Vรคlj</string> <string name="common_ui_storage_roots">Lagringsrรถtter</string> + <string name="common_ui_app_storage">Applagring</string> + <string name="common_ui_move">Flytta</string> + <string name="library_games_move_to_app_storage_title">Flytta till intern</string> + <string name="library_games_move_to_download_folder_title">Flytta till extern</string> + <string name="library_games_move_to_app_storage_confirm">Flytta %1$s till intern lagring?</string> + <string name="library_games_move_to_download_folder_confirm">Flytta %1$s till extern lagring?</string> + <string name="library_games_moving">Flyttar spelet</string> + <string name="library_games_move_done">%1$s har flyttats</string> + <string name="library_games_move_failed">Kunde inte flytta %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Blรคddra bland lokala mappar direkt</string> <string name="common_ui_no_folders_available_here">Inga mappar tillgรคngliga hรคr</string> <string name="common_ui_grant_all_files_access_browse">Bevilja รฅtkomst till alla filer fรถr att blรคddra bland mappar direkt.</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32-bitars</string> <string name="hero_graphics_test_64">64-bitars</string> <string name="hero_input_tests_title">Inmatningstester</string> - <string name="library_games_add_custom_game">Lรคgg till anpassat spel</string> <string name="library_games_game_name">Spelnamn</string> <string name="library_games_game_folder_mapped_drive">Spelmapp (Mappad speldisk)</string> <string name="library_games_select_exe_provide_name">Vรคlj en exe och ange ett namn</string> <string name="library_games_sign_in_prompt">Logga in pรฅ en butik eller lรคgg till ett anpassat spel fรถr att bygga ditt bibliotek.</string> + <string name="library_games_add_title">Lรคgg till spel eller app</string> + <string name="library_games_add_linux_steam">Lรคgg till Steam-klient</string> + <string name="library_games_type">Typ</string> + <string name="library_games_type_game">Spel</string> + <string name="library_games_type_app">App</string> + <string name="library_games_select_executable_or_rom">Vรคlj en kรถrbar fil eller konsol-ROM</string> + <string name="library_games_added">%1$s tillagd</string> + <string name="library_games_add_failed">Kunde inte lรคgga till %1$s</string> + <string name="linux_runtime_not_installed">Linux-kรถrmiljรถn รคr inte installerad รคnnu, sรฅ %1$s kan inte startas. Installera den med Linux-klient under Instรคllningar > Butiker.</string> + <string name="linux_client_button">Linux-klient</string> + <string name="linux_client_title">Linux Steam-klient</string> + <string name="linux_client_install_message">Laddar ned Linux-kรถrmiljรถn frรฅn WinNative och Steam-klienten frรฅn Valve och installerar bรฅda. Nedladdningen รคr รถver 2 GB, sรฅ anvรคnd Wi-Fi.</string> + <string name="linux_client_installed_message">Linux-kรถrmiljรถn och Steam-klienten รคr installerade, och Steam finns i Bibliotek.</string> + <string name="linux_client_failed_message">Installationen slutfรถrdes inte. Kontrollera anslutningen och fรถrsรถk igen.</string> + <string name="linux_client_no_space_message">Inte tillrรคckligt med ledigt utrymme: %1$s behรถvs och %2$s รคr ledigt.</string> + <string name="linux_client_stage_connect">Ansluter</string> + <string name="linux_client_stage_download_runtime">Laddar ned Linux-kรถrmiljรถn</string> + <string name="linux_client_stage_install_runtime">Installerar Linux-kรถrmiljรถn</string> + <string name="linux_client_stage_download_proton">Laddar ned Proton</string> + <string name="linux_client_stage_install_proton">Installerar Proton</string> + <string name="linux_client_stage_download_steam">Laddar ned Steam-klienten</string> + <string name="linux_client_stage_install_steam">Installerar Steam-klienten</string> + <string name="linux_client_stage_library">Lรคgger till Steam i Bibliotek</string> + <string name="linux_client_progress_amount">%1$s av %2$s</string> + <string name="linux_client_retry">Fรถrsรถk igen</string> + <string name="linux_client_busy">Linux Steam-klienten installeras fortfarande. Fรถrsรถk igen nรคr den รคr klar.</string> + <string name="linux_client_update_message">En nyare Linux-miljรถ finns tillgรคnglig. Uppdateringen behรฅller din Steam-inloggning, installerade spel, sparfiler och containrar.</string> + <string name="linux_client_update_session_running">En Linux-session kรถrs. Avsluta den och uppdatera sedan.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux-sessionen stoppade sรฅ snart den startade. Loggen finns under Instรคllningar > Felsรถkning > ร–ppna logghanteraren; dela den sรฅ att orsaken kan hittas.</string> + <string name="linux_session_stopped">Linux-sessionen stoppade av sig sjรคlv (status %1$d). Loggen finns under Instรคllningar > Felsรถkning > ร–ppna logghanteraren; dela den sรฅ att orsaken kan hittas.</string> + <string name="linux_session_trimmed">Android stoppade Linux-sessionen. Det betyder oftast att grรคnsen fรถr bakgrundsprocesser nรฅddes: slรฅ pรฅ โ€Inaktivera begrรคnsningar fรถr underordnade processerโ€ i Utvecklaralternativ och fรถrsรถk igen.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Butiker</string> @@ -1357,6 +1401,10 @@ Installerad sรถkvรคg: <string name="setup_wizard_title">Installationsguide</string> <string name="setup_wizard_advanced_user">Avancerad anvรคndare</string> <string name="setup_wizard_next">Nรคsta</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Slutfรถr</string> <string name="setup_wizard_download_and_install">Ladda ner + installera</string> <string name="setup_wizard_open_drivers">ร–ppna drivrutiner</string> @@ -2864,10 +2912,9 @@ Installerad sรถkvรคg: <string name="update_apk_notice">Den hรคr uppdateringen รคndrar sjรคlva appen, sรฅ Android ber dig bekrรคfta installationen. Dina spel, containrar och instรคllningar behรฅlls.</string> <string name="update_up_to_date">WinNative รคr uppdaterad.</string> <string name="update_unsupported_build">Uppdateringar i appen erbjuds endast fรถr officiella signerade versioner.</string> - <string name="settings_general_update_channel">Uppdateringskanal</string> - <string name="settings_general_update_channel_summary">Officiell levererar testade versioner. Utveckling levererar tidiga byggen.</string> + <string name="settings_general_update_source">Uppdateringskรคlla</string> + <string name="settings_general_update_source_summary">Ett bygge frรฅn en pull request fรถljer den pull requesten; en officiell installation fรถljer de officiella utgรฅvorna.</string> <string name="update_channel_official">Officiell</string> - <string name="update_channel_development">Utveckling</string> <string name="networking_section_title">Nรคtverk</string> <string name="networking_driver">Nรคtverksdrivrutin</string> <string name="networking_driver_winnative">WinNative nรคtverksdrivrutin</string> @@ -2880,4 +2927,35 @@ Installerad sรถkvรคg: <string name="steam_controller_left_pad_click">Klick pรฅ vรคnster styrplatta</string> <string name="steam_controller_right_pad_click">Klick pรฅ hรถger styrplatta</string> <string name="controller_test_generic">Generisk</string> + <string name="container_display_server">Skรคrmserver</string> + <string name="container_display_server_help">Experimentell. Sessionen ritas via den inbyggda Wayland-kompositรถren i stรคllet fรถr X-servern.</string> + <string name="container_display_server_wayland_requirements">Wayland krรคver en Adreno-GPU och en Wayland-kompatibel Proton med winewayland och Wayland Turnip-drivrutinen. Installera en frรฅn Innehรฅll.</string> + <string name="container_display_server_needs_wayland_proton">Denna Proton saknar winewayland och Wayland Turnip-drivrutinen, sรฅ X11 anvรคnds. Installera ett Wayland Proton-lager frรฅn Innehรฅll och vรคlj det som containerns Proton.</string> + <string name="wayland_unavailable_fallback">Wayland รคr inte tillgรคngligt fรถr detta spel. Startar pรฅ X11.</string> + <string name="wayland_needs_turnip_driver">Wayland behรถver en Turnip-grafikdrivrutin fรถr att visa spelet. Installera en under Innehรฅll och vรคlj den som grafikdrivrutin.</string> + <string name="containers_gamescope_new">Skapa GameScope-container</string> + <string name="containers_gamescope_create_failed">Det gick inte att skapa GameScope-containern</string> + <string name="linux_apps_need_gamescope_container">Skapa fรถrst GameScope-containern under Instรคllningar โ€บ Containrar.</string> + <string name="linux_runtime_missing">Linux-kรถrmiljรถn รคr inte installerad, sรฅ den hรคr containern kan inte startas รคnnu. Installera den med Linux-klient under Instรคllningar > Butiker.</string> + <string name="container_display_server_gamescope">Gamescope ritar alltid genom Wayland-kompositรถren.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Inbyggd i Linux-miljรถn. Nedladdade Android-drivrutiner gรคller bara Wine- och Proton-containrar.</string> + <string name="linux_runtime_windows_program">Detta รคr ett Windows-program. Flytta det till en Wine-container i genvรคgsinstรคllningarna fรถr att starta det.</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Vรคlj var drivrutinen ska installeras. Identifierade paket anvรคnder automatiskt rรคtt plattform.</string> + <string name="settings_drivers_restore_defaults">ร…terstรคll standarder</string> + <string name="settings_drivers_defaults_present">Standardkรคllorna finns redan</string> + <string name="settings_drivers_active">Aktiv</string> + <string name="settings_drivers_bundled">Medfรถljande Mesa</string> + <string name="settings_drivers_legacy">Linux-klientens drivrutin</string> + <string name="settings_drivers_routed">Drivrutinen identifierades och installerades fรถr %1$s.</string> + <string name="linux_child_processes_message">Android begrรคnsar hur mรฅnga bakgrundsprocesser en app fรฅr ha, och en Linux-session anvรคnder betydligt fler: Steam eller ett spel kan avslutas mitt i spelandet. Slรฅ pรฅ โ€Inaktivera begrรคnsningar fรถr underordnade processerโ€ i utvecklaralternativen. ร–ppna dem nu?</string> + <string name="linux_child_processes_adb">Android begrรคnsar hur mรฅnga bakgrundsprocesser en app fรฅr ha, och en Linux-session anvรคnder betydligt fler: Steam eller ett spel kan avslutas mitt i spelandet. Den hรคr Android-versionen saknar den instรคllningen. Kรถr frรฅn en dator:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 4d64dabe1..d639bc1d0 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">เธ›เน‰เธญเธ™เธžเธฒเธ˜</string> <string name="common_ui_path_not_found">เน„เธกเนˆเธžเธšเธžเธฒเธ˜</string> <string name="common_ui_select_valid_exe_file">เน‚เธ›เธฃเธ”เน€เธฅเธทเธญเธเน„เธŸเธฅเนŒ .exe เธ—เธตเนˆเธ–เธนเธเธ•เน‰เธญเธ‡</string> + <string name="common_ui_select_executable">เน€เธฅเธทเธญเธเน„เธŸเธฅเนŒเธ›เธเธดเธšเธฑเธ•เธดเธเธฒเธฃ</string> + <string name="common_ui_select_valid_executable">เน€เธฅเธทเธญเธเน„เธŸเธฅเนŒเธ›เธเธดเธšเธฑเธ•เธดเธเธฒเธฃเธ‚เธญเธ‡ Windows เธซเธฃเธทเธญ Linux</string> <string name="common_ui_select_valid_icon_file">เน‚เธ›เธฃเธ”เน€เธฅเธทเธญเธเน„เธŸเธฅเนŒ .ico เธซเธฃเธทเธญ .png เธ—เธตเนˆเธ–เธนเธเธ•เน‰เธญเธ‡</string> <string name="common_ui_exit">เธญเธญเธ</string> <string name="common_ui_exit_app">เธญเธญเธเธˆเธฒเธเนเธญเธ›</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">เธ•เธฑเน‰เธ‡เน€เธงเธญเธฃเนŒเธŠเธฑเธ™เน€เธ›เน‡เธ™ %1$s เนเธฅเน‰เธง เนƒเธŠเน‰เธ•เธฃเธงเธˆเธชเธญเธšเธเธฒเธฃเธญเธฑเธ›เน€เธ”เธ•เน€เธžเธทเนˆเธญเธชเธฅเธฑเธšเน„เธŸเธฅเนŒเธ—เธตเนˆเธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธงเน‰</string> <string name="common_ui_select">เน€เธฅเธทเธญเธ</string> <string name="common_ui_storage_roots">เธฃเธนเธ—เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅ</string> + <string name="common_ui_app_storage">เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเธ‚เธญเธ‡เนเธญเธ›</string> + <string name="common_ui_move">เธขเน‰เธฒเธข</string> + <string name="library_games_move_to_app_storage_title">เธขเน‰เธฒเธขเน„เธ›เธ เธฒเธขเนƒเธ™</string> + <string name="library_games_move_to_download_folder_title">เธขเน‰เธฒเธขเน„เธ›เธ เธฒเธขเธ™เธญเธ</string> + <string name="library_games_move_to_app_storage_confirm">เธขเน‰เธฒเธข %1$s เน„เธ›เธขเธฑเธ‡เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเธ เธฒเธขเนƒเธ™เธซเธฃเธทเธญเน„เธกเนˆ</string> + <string name="library_games_move_to_download_folder_confirm">เธขเน‰เธฒเธข %1$s เน„เธ›เธขเธฑเธ‡เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเธ เธฒเธขเธ™เธญเธเธซเธฃเธทเธญเน„เธกเนˆ</string> + <string name="library_games_moving">เธเธณเธฅเธฑเธ‡เธขเน‰เธฒเธขเน€เธเธก</string> + <string name="library_games_move_done">เธขเน‰เธฒเธข %1$s เนเธฅเน‰เธง</string> + <string name="library_games_move_failed">เธขเน‰เธฒเธข %1$s เน„เธกเนˆเธชเธณเน€เธฃเน‡เธˆ</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">เน€เธฃเธตเธขเธเธ”เธนเน‚เธŸเธฅเน€เธ”เธญเธฃเนŒเนƒเธ™เน€เธ„เธฃเธทเนˆเธญเธ‡เน‚เธ”เธขเธ•เธฃเธ‡</string> <string name="common_ui_no_folders_available_here">เน„เธกเนˆเธกเธตเน‚เธŸเธฅเน€เธ”เธญเธฃเนŒเธ—เธตเนˆเธ™เธตเนˆ</string> <string name="common_ui_grant_all_files_access_browse">เนƒเธซเน‰เธชเธดเธ—เธ˜เธดเนŒเน€เธ‚เน‰เธฒเธ–เธถเธ‡เน„เธŸเธฅเนŒเธ—เธฑเน‰เธ‡เธซเธกเธ”เน€เธžเธทเนˆเธญเน€เธฃเธตเธขเธเธ”เธนเน‚เธŸเธฅเน€เธ”เธญเธฃเนŒเน‚เธ”เธขเธ•เธฃเธ‡</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">เธ—เธ”เธชเธญเธšเธญเธดเธ™เธžเธธเธ•</string> - <string name="library_games_add_custom_game">เน€เธžเธดเนˆเธกเน€เธเธกเธเธณเธซเธ™เธ”เน€เธญเธ‡</string> <string name="library_games_game_name">เธŠเธทเนˆเธญเน€เธเธก</string> <string name="library_games_game_folder_mapped_drive">เน‚เธŸเธฅเน€เธ”เธญเธฃเนŒเน€เธเธก (เน„เธ”เธฃเธŸเนŒเน€เธเธกเธ—เธตเนˆเนเธกเธ›)</string> <string name="library_games_select_exe_provide_name">เน€เธฅเธทเธญเธเน„เธŸเธฅเนŒ exe เนเธฅเธฐเธฃเธฐเธšเธธเธŠเธทเนˆเธญ</string> <string name="library_games_sign_in_prompt">เน€เธ‚เน‰เธฒเธชเธนเนˆเธฃเธฐเธšเธšเธฃเน‰เธฒเธ™เธ„เน‰เธฒเธซเธฃเธทเธญเน€เธžเธดเนˆเธกเน€เธเธกเธเธณเธซเธ™เธ”เน€เธญเธ‡เน€เธžเธทเนˆเธญเธชเธฃเน‰เธฒเธ‡เธ„เธฅเธฑเธ‡เธ‚เธญเธ‡เธ„เธธเธ“</string> + <string name="library_games_add_title">เน€เธžเธดเนˆเธกเน€เธเธกเธซเธฃเธทเธญเนเธญเธ›</string> + <string name="library_games_add_linux_steam">เน€เธžเธดเนˆเธกเน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam</string> + <string name="library_games_type">เธ›เธฃเธฐเน€เธ เธ—</string> + <string name="library_games_type_game">เน€เธเธก</string> + <string name="library_games_type_app">เนเธญเธ›</string> + <string name="library_games_select_executable_or_rom">เน€เธฅเธทเธญเธเน„เธŸเธฅเนŒเธ›เธเธดเธšเธฑเธ•เธดเธเธฒเธฃเธซเธฃเธทเธญ ROM เธ„เธญเธ™เน‚เธ‹เธฅ</string> + <string name="library_games_added">เน€เธžเธดเนˆเธก %1$s เนเธฅเน‰เธง</string> + <string name="library_games_add_failed">เน„เธกเนˆเธชเธฒเธกเธฒเธฃเธ–เน€เธžเธดเนˆเธก %1$s เน„เธ”เน‰</string> + <string name="linux_runtime_not_installed">เธขเธฑเธ‡เน„เธกเนˆเน„เธ”เน‰เธ•เธดเธ”เธ•เธฑเน‰เธ‡เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux เธˆเธถเธ‡เน„เธกเนˆเธชเธฒเธกเธฒเธฃเธ–เน€เธฃเธดเนˆเธก %1$s เน„เธ”เน‰ เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธ”เน‰เธ”เน‰เธงเธข เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Linux เนƒเธ™ เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ > เธฃเน‰เธฒเธ™เธ„เน‰เธฒ</string> + <string name="linux_client_button">เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Linux</string> + <string name="linux_client_title">เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam เธชเธณเธซเธฃเธฑเธš Linux</string> + <string name="linux_client_install_message">เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux เธˆเธฒเธ WinNative เนเธฅเธฐเน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam เธˆเธฒเธ Valve เนเธฅเน‰เธงเธ•เธดเธ”เธ•เธฑเน‰เธ‡เธ—เธฑเน‰เธ‡เธชเธญเธ‡เธญเธขเนˆเธฒเธ‡ เธเธฒเธฃเธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธกเธตเธ‚เธ™เธฒเธ”เน€เธเธดเธ™ 2 GB เธˆเธถเธ‡เธ„เธงเธฃเนƒเธŠเน‰ Wi-Fi</string> + <string name="linux_client_installed_message">เธ•เธดเธ”เธ•เธฑเน‰เธ‡เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux เนเธฅเธฐเน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam เนเธฅเน‰เธง เนเธฅเธฐ Steam เธญเธขเธนเนˆเนƒเธ™เธ„เธฅเธฑเธ‡เนเธฅเน‰เธง</string> + <string name="linux_client_failed_message">เธเธฒเธฃเธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธกเนˆเน€เธชเธฃเน‡เธˆเธชเธกเธšเธนเธฃเธ“เนŒ เธ•เธฃเธงเธˆเธชเธญเธšเธเธฒเธฃเน€เธŠเธทเนˆเธญเธกเธ•เนˆเธญเนเธฅเน‰เธงเธฅเธญเธ‡เธญเธตเธเธ„เธฃเธฑเน‰เธ‡</string> + <string name="linux_client_no_space_message">เธžเธทเน‰เธ™เธ—เธตเนˆเธงเนˆเธฒเธ‡เน„เธกเนˆเธžเธญ: เธ•เน‰เธญเธ‡เนƒเธŠเน‰ %1$s เนเธฅเธฐเธกเธตเธงเนˆเธฒเธ‡ %2$s</string> + <string name="linux_client_stage_connect">เธเธณเธฅเธฑเธ‡เน€เธŠเธทเนˆเธญเธกเธ•เนˆเธญ</string> + <string name="linux_client_stage_download_runtime">เธเธณเธฅเธฑเธ‡เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux</string> + <string name="linux_client_stage_install_runtime">เธเธณเธฅเธฑเธ‡เธ•เธดเธ”เธ•เธฑเน‰เธ‡เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux</string> + <string name="linux_client_stage_download_proton">เธเธณเธฅเธฑเธ‡เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ” Proton</string> + <string name="linux_client_stage_install_proton">เธเธณเธฅเธฑเธ‡เธ•เธดเธ”เธ•เธฑเน‰เธ‡ Proton</string> + <string name="linux_client_stage_download_steam">เธเธณเธฅเธฑเธ‡เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam</string> + <string name="linux_client_stage_install_steam">เธเธณเธฅเธฑเธ‡เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam</string> + <string name="linux_client_stage_library">เธเธณเธฅเธฑเธ‡เน€เธžเธดเนˆเธก Steam เธฅเธ‡เนƒเธ™เธ„เธฅเธฑเธ‡</string> + <string name="linux_client_progress_amount">%1$s เธˆเธฒเธ %2$s</string> + <string name="linux_client_retry">เธฅเธญเธ‡เธญเธตเธเธ„เธฃเธฑเน‰เธ‡</string> + <string name="linux_client_busy">เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Steam เธชเธณเธซเธฃเธฑเธš Linux เธขเธฑเธ‡เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธกเนˆเน€เธชเธฃเน‡เธˆ เธฅเธญเธ‡เธญเธตเธเธ„เธฃเธฑเน‰เธ‡เน€เธกเธทเนˆเธญเธ•เธดเธ”เธ•เธฑเน‰เธ‡เน€เธชเธฃเน‡เธˆ</string> + <string name="linux_client_update_message">เธกเธตเธฃเธฑเธ™เน„เธ—เธกเนŒ Linux เน€เธงเธญเธฃเนŒเธŠเธฑเธ™เนƒเธซเธกเนˆ เธเธฒเธฃเธญเธฑเธ›เน€เธ”เธ•เธˆเธฐเน€เธเน‡เธšเธเธฒเธฃเธฅเธ‡เธŠเธทเนˆเธญเน€เธ‚เน‰เธฒเนƒเธŠเน‰ Steam เน€เธเธกเธ—เธตเนˆเธ•เธดเธ”เธ•เธฑเน‰เธ‡ เน€เธ‹เธŸ เนเธฅเธฐเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเน„เธงเน‰</string> + <string name="linux_client_update_session_running">เน€เธ‹เธชเธŠเธฑเธ™ Linux เธเธณเธฅเธฑเธ‡เธ—เธณเธ‡เธฒเธ™เธญเธขเธนเนˆ เธญเธญเธเธˆเธฒเธเน€เธ‹เธชเธŠเธฑเธ™เธเนˆเธญเธ™เนเธฅเน‰เธงเธˆเธถเธ‡เธญเธฑเธ›เน€เธ”เธ•</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">เน€เธ‹เธชเธŠเธฑเธ™ Linux เธซเธขเธธเธ”เธ—เธณเธ‡เธฒเธ™เธ—เธฑเธ™เธ—เธตเธ—เธตเนˆเน€เธฃเธดเนˆเธก เธšเธฑเธ™เธ—เธถเธเธญเธขเธนเนˆเธ—เธตเนˆ เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ > เธ”เธตเธšเธฑเธ > เน€เธ›เธดเธ”เธ•เธฑเธงเธˆเธฑเธ”เธเธฒเธฃเธšเธฑเธ™เธ—เธถเธ เน‚เธ›เธฃเธ”เนเธŠเธฃเนŒเน€เธžเธทเนˆเธญเนƒเธซเน‰เธซเธฒเธชเธฒเน€เธซเธ•เธธเน„เธ”เน‰</string> + <string name="linux_session_stopped">เน€เธ‹เธชเธŠเธฑเธ™ Linux เธซเธขเธธเธ”เธ—เธณเธ‡เธฒเธ™เน€เธญเธ‡ (เธชเธ–เธฒเธ™เธฐ %1$d) เธšเธฑเธ™เธ—เธถเธเธญเธขเธนเนˆเธ—เธตเนˆ เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ > เธ”เธตเธšเธฑเธ > เน€เธ›เธดเธ”เธ•เธฑเธงเธˆเธฑเธ”เธเธฒเธฃเธšเธฑเธ™เธ—เธถเธ เน‚เธ›เธฃเธ”เนเธŠเธฃเนŒเน€เธžเธทเนˆเธญเนƒเธซเน‰เธซเธฒเธชเธฒเน€เธซเธ•เธธเน„เธ”เน‰</string> + <string name="linux_session_trimmed">Android เธซเธขเธธเธ”เน€เธ‹เธชเธŠเธฑเธ™ Linux เน‚เธ”เธขเธ›เธเธ•เธดเธซเธกเธฒเธขเธ„เธงเธฒเธกเธงเนˆเธฒเธ–เธถเธ‡เธ‚เธตเธ”เธˆเธณเธเธฑเธ”เน‚เธ›เธฃเน€เธ‹เธชเน€เธšเธทเน‰เธญเธ‡เธซเธฅเธฑเธ‡เนเธฅเน‰เธง เนƒเธซเน‰เน€เธ›เธดเธ” โ€œเธ›เธดเธ”เนƒเธŠเน‰เธ‚เน‰เธญเธˆเธณเธเธฑเธ”เน‚เธ›เธฃเน€เธ‹เธชเธฅเธนเธโ€ เนƒเธ™เธ•เธฑเธงเน€เธฅเธทเธญเธเธชเธณเธซเธฃเธฑเธšเธ™เธฑเธเธžเธฑเธ’เธ™เธฒ เนเธฅเน‰เธงเธฅเธญเธ‡เธญเธตเธเธ„เธฃเธฑเน‰เธ‡</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">เธฃเน‰เธฒเธ™เธ„เน‰เธฒ</string> @@ -1357,6 +1401,10 @@ <string name="setup_wizard_title">เธ•เธฑเธงเธŠเนˆเธงเธขเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ</string> <string name="setup_wizard_advanced_user">เธœเธนเน‰เนƒเธŠเน‰เธ‚เธฑเน‰เธ™เธชเธนเธ‡</string> <string name="setup_wizard_next">เธ–เธฑเธ”เน„เธ›</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">เน€เธชเธฃเน‡เธˆเธชเธดเน‰เธ™</string> <string name="setup_wizard_download_and_install">เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ” + เธ•เธดเธ”เธ•เธฑเน‰เธ‡</string> <string name="setup_wizard_open_drivers">เน€เธ›เธดเธ”เน„เธ”เธฃเน€เธงเธญเธฃเนŒ</string> @@ -2863,10 +2911,9 @@ <string name="update_apk_notice">เธเธฒเธฃเธญเธฑเธ›เน€เธ”เธ•เธ™เธตเน‰เน€เธ›เธฅเธตเนˆเธขเธ™เธ•เธฑเธงเนเธญเธ›เน€เธญเธ‡ Android เธˆเธถเธ‡เธˆเธฐเธ‚เธญเนƒเธซเน‰เธ„เธธเธ“เธขเธทเธ™เธขเธฑเธ™เธเธฒเธฃเธ•เธดเธ”เธ•เธฑเน‰เธ‡ เน€เธเธก เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ เนเธฅเธฐเธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒเธ‚เธญเธ‡เธ„เธธเธ“เธˆเธฐเธขเธฑเธ‡เธญเธขเธนเนˆเธ„เธฃเธš</string> <string name="update_up_to_date">WinNative เน€เธ›เน‡เธ™เน€เธงเธญเธฃเนŒเธŠเธฑเธ™เธฅเนˆเธฒเธชเธธเธ”เนเธฅเน‰เธง</string> <string name="update_unsupported_build">เธเธฒเธฃเธญเธฑเธ›เน€เธ”เธ•เนƒเธ™เนเธญเธ›เธกเธตเนƒเธซเน‰เน€เธ‰เธžเธฒเธฐเธฃเธธเนˆเธ™เธ—เธตเนˆเธฅเธ‡เธ™เธฒเธกเธญเธขเนˆเธฒเธ‡เน€เธ›เน‡เธ™เธ—เธฒเธ‡เธเธฒเธฃเน€เธ—เนˆเธฒเธ™เธฑเน‰เธ™</string> - <string name="settings_general_update_channel">เธŠเนˆเธญเธ‡เธ—เธฒเธ‡เธญเธฑเธ›เน€เธ”เธ•</string> - <string name="settings_general_update_channel_summary">เธ—เธฒเธ‡เธเธฒเธฃเธˆเธฐเธ›เธฅเนˆเธญเธขเธฃเธธเนˆเธ™เธ—เธตเนˆเธœเนˆเธฒเธ™เธเธฒเธฃเธ—เธ”เธชเธญเธšเนเธฅเน‰เธง เธชเนˆเธงเธ™เธเธฒเธฃเธžเธฑเธ’เธ™เธฒเธˆเธฐเธ›เธฅเนˆเธญเธขเธšเธดเธฅเธ”เนŒเธฃเธธเนˆเธ™เนเธฃเธ</string> + <string name="settings_general_update_source">เนเธซเธฅเนˆเธ‡เธญเธฑเธ›เน€เธ”เธ•</string> + <string name="settings_general_update_source_summary">เธšเธดเธฅเธ”เนŒเธˆเธฒเธเธžเธนเธฅเธฃเธตเน€เธ„เธงเธชเธ•เนŒเธˆเธฐเธ•เธดเธ”เธ•เธฒเธกเธžเธนเธฅเธฃเธตเน€เธ„เธงเธชเธ•เนŒเธ™เธฑเน‰เธ™ เธชเนˆเธงเธ™เธเธฒเธฃเธ•เธดเธ”เธ•เธฑเน‰เธ‡เธญเธขเนˆเธฒเธ‡เน€เธ›เน‡เธ™เธ—เธฒเธ‡เธเธฒเธฃเธˆเธฐเธ•เธดเธ”เธ•เธฒเธกเธฃเธธเนˆเธ™เธ—เธตเนˆเน€เธœเธขเนเธžเธฃเนˆเธญเธขเนˆเธฒเธ‡เน€เธ›เน‡เธ™เธ—เธฒเธ‡เธเธฒเธฃ</string> <string name="update_channel_official">เธ—เธฒเธ‡เธเธฒเธฃ</string> - <string name="update_channel_development">เธเธฒเธฃเธžเธฑเธ’เธ™เธฒ</string> <string name="networking_section_title">เธฃเธฐเธšเธšเน€เธ„เธฃเธทเธญเธ‚เนˆเธฒเธข</string> <string name="networking_driver">เน„เธ”เธฃเน€เธงเธญเธฃเนŒเน€เธ„เธฃเธทเธญเธ‚เนˆเธฒเธข</string> <string name="networking_driver_winnative">เน„เธ”เธฃเน€เธงเธญเธฃเนŒเน€เธ„เธฃเธทเธญเธ‚เนˆเธฒเธข WinNative</string> @@ -2879,4 +2926,35 @@ <string name="steam_controller_left_pad_click">เธ„เธฅเธดเธเนเธžเธ”เธ‹เน‰เธฒเธข</string> <string name="steam_controller_right_pad_click">เธ„เธฅเธดเธเนเธžเธ”เธ‚เธงเธฒ</string> <string name="controller_test_generic">เธ—เธฑเนˆเธงเน„เธ›</string> + <string name="container_display_server">เน€เธ‹เธดเธฃเนŒเธŸเน€เธงเธญเธฃเนŒเนเธชเธ”เธ‡เธœเธฅ</string> + <string name="container_display_server_help">เธ—เธ”เธฅเธญเธ‡ เน€เธ‹เธชเธŠเธฑเธ™เธˆเธฐเธงเธฒเธ”เธœเนˆเธฒเธ™เธ„เธญเธกเน‚เธžเธชเธดเน€เธ•เธญเธฃเนŒ Wayland เนƒเธ™เธ•เธฑเธงเนเธ—เธ™เน€เธ‹เธดเธฃเนŒเธŸเน€เธงเธญเธฃเนŒ X</string> + <string name="container_display_server_wayland_requirements">Wayland เธ•เน‰เธญเธ‡เนƒเธŠเน‰ GPU Adreno เนเธฅเธฐ Proton เธ—เธตเนˆเธฃเธญเธ‡เธฃเธฑเธš Wayland เธžเธฃเน‰เธญเธก winewayland เนเธฅเธฐเน„เธ”เธฃเน€เธงเธญเธฃเนŒ Turnip เธชเธณเธซเธฃเธฑเธš Wayland เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธ”เน‰เธˆเธฒเธเน€เธ™เธทเน‰เธญเธซเธฒ</string> + <string name="container_display_server_needs_wayland_proton">Proton เธ™เธตเน‰เน„เธกเนˆเธกเธต winewayland เธซเธฃเธทเธญเน„เธ”เธฃเน€เธงเธญเธฃเนŒ Wayland Turnip เธˆเธถเธ‡เนƒเธŠเน‰ X11 เนเธ—เธ™ เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน€เธฅเน€เธขเธญเธฃเนŒ Proton เธชเธณเธซเธฃเธฑเธš Wayland เธˆเธฒเธเน€เธ™เธทเน‰เธญเธซเธฒ เนเธฅเน‰เธงเน€เธฅเธทเธญเธเน€เธ›เน‡เธ™ Proton เธ‚เธญเธ‡เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ</string> + <string name="wayland_unavailable_fallback">เน€เธเธกเธ™เธตเน‰เน„เธกเนˆเธชเธฒเธกเธฒเธฃเธ–เนƒเธŠเน‰ Wayland เน„เธ”เน‰ เธเธณเธฅเธฑเธ‡เน€เธ›เธดเธ”เธ”เน‰เธงเธข X11</string> + <string name="wayland_needs_turnip_driver">Wayland เธ•เน‰เธญเธ‡เนƒเธŠเน‰เน„เธ”เธฃเน€เธงเธญเธฃเนŒเธเธฃเธฒเธŸเธดเธ Turnip เน€เธžเธทเนˆเธญเนเธชเธ”เธ‡เน€เธเธก เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธ”เน‰เธˆเธฒเธเน€เธ™เธทเน‰เธญเธซเธฒเนเธฅเน‰เธงเน€เธฅเธทเธญเธเน€เธ›เน‡เธ™เน„เธ”เธฃเน€เธงเธญเธฃเนŒเธเธฃเธฒเธŸเธดเธ</string> + <string name="containers_gamescope_new">เธชเธฃเน‰เธฒเธ‡เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ GameScope</string> + <string name="containers_gamescope_create_failed">เธชเธฃเน‰เธฒเธ‡เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ GameScope เน„เธกเนˆเธชเธณเน€เธฃเน‡เธˆ</string> + <string name="linux_apps_need_gamescope_container">เน‚เธ›เธฃเธ”เธชเธฃเน‰เธฒเธ‡เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ GameScope เนƒเธ™เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ โ€บ เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธเนˆเธญเธ™</string> + <string name="linux_runtime_missing">เธขเธฑเธ‡เน„เธกเนˆเน„เธ”เน‰เธ•เธดเธ”เธ•เธฑเน‰เธ‡เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux เธˆเธถเธ‡เธขเธฑเธ‡เน„เธกเนˆเธชเธฒเธกเธฒเธฃเธ–เน€เธฃเธดเนˆเธกเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธ™เธตเน‰เน„เธ”เน‰ เธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธ”เน‰เธ”เน‰เธงเธข เน„เธ„เธฅเน€เธญเน‡เธ™เธ•เนŒ Linux เนƒเธ™ เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ > เธฃเน‰เธฒเธ™เธ„เน‰เธฒ</string> + <string name="container_display_server_gamescope">Gamescope เธงเธฒเธ”เธœเนˆเธฒเธ™เธ„เธญเธกเน‚เธžเธชเธดเน€เธ•เธญเธฃเนŒ Wayland เน€เธชเธกเธญ</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">เธฃเธงเธกเธญเธขเธนเนˆเนƒเธ™เธฃเธฑเธ™เน„เธ—เธกเนŒ Linux เน„เธ”เธฃเน€เธงเธญเธฃเนŒ Android เธ—เธตเนˆเธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เนƒเธŠเน‰เน„เธ”เน‰เธเธฑเธšเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ Wine เนเธฅเธฐ Proton เน€เธ—เนˆเธฒเธ™เธฑเน‰เธ™</string> + <string name="linux_runtime_windows_program">เธ™เธตเนˆเธ„เธทเธญเน‚เธ›เธฃเนเธเธฃเธก Windows เธขเน‰เธฒเธขเน„เธ›เธขเธฑเธ‡เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ Wine เนƒเธ™เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒเธ—เธฒเธ‡เธฅเธฑเธ”เน€เธžเธทเนˆเธญเน€เธฃเธดเนˆเธกเนƒเธŠเน‰เธ‡เธฒเธ™</string> + <string name="settings_content_badge_online">เธญเธญเธ™เน„เธฅเธ™เนŒ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">เน€เธฅเธทเธญเธเธ•เธณเนเธซเธ™เนˆเธ‡เธ—เธตเนˆเธˆเธฐเธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธ”เธฃเน€เธงเธญเธฃเนŒ เนเธžเน‡เธเน€เธเธˆเธ—เธตเนˆเธฃเธนเน‰เธˆเธฑเธเธˆเธฐเนƒเธŠเน‰เนเธžเธฅเธ•เธŸเธญเธฃเนŒเธกเธ—เธตเนˆเธ–เธนเธเธ•เน‰เธญเธ‡เน‚เธ”เธขเธญเธฑเธ•เน‚เธ™เธกเธฑเธ•เธด</string> + <string name="settings_drivers_restore_defaults">เธ„เธทเธ™เธ„เนˆเธฒเน€เธฃเธดเนˆเธกเธ•เน‰เธ™</string> + <string name="settings_drivers_defaults_present">เธกเธตเธ„เธฅเธฑเธ‡เน€เธฃเธดเนˆเธกเธ•เน‰เธ™เธญเธขเธนเนˆเนเธฅเน‰เธง</string> + <string name="settings_drivers_active">เนƒเธŠเน‰เธ‡เธฒเธ™เธญเธขเธนเนˆ</string> + <string name="settings_drivers_bundled">Mesa เธ—เธตเนˆเธกเธฒเธžเธฃเน‰เธญเธกเนเธญเธ›</string> + <string name="settings_drivers_legacy">เน„เธ”เธฃเน€เธงเธญเธฃเนŒเน„เธ„เธฅเน€เธญเธ™เธ•เนŒ Linux</string> + <string name="settings_drivers_routed">เธฃเธฐเธšเธธเน„เธ”เธฃเน€เธงเธญเธฃเนŒเนเธฅเธฐเธ•เธดเธ”เธ•เธฑเน‰เธ‡เธชเธณเธซเธฃเธฑเธš %1$s เนเธฅเน‰เธง</string> + <string name="linux_child_processes_message">Android เธˆเธณเธเธฑเธ”เธˆเธณเธ™เธงเธ™เน‚เธ›เธฃเน€เธ‹เธชเน€เธšเธทเน‰เธญเธ‡เธซเธฅเธฑเธ‡เธ‚เธญเธ‡เนเธญเธ› เนเธ•เนˆเน€เธ‹เธชเธŠเธฑเธ™ Linux เนƒเธŠเน‰เธกเธฒเธเธเธงเนˆเธฒเธ™เธฑเน‰เธ™เธกเธฒเธ: Steam เธซเธฃเธทเธญเน€เธเธกเธญเธฒเธˆเธ–เธนเธเธ›เธดเธ”เธฃเธฐเธซเธงเนˆเธฒเธ‡เน€เธฅเนˆเธ™ เน€เธ›เธดเธ” โ€œเธ›เธดเธ”เนƒเธŠเน‰เธ‚เน‰เธญเธˆเธณเธเธฑเธ”เน‚เธ›เธฃเน€เธ‹เธชเธฅเธนเธโ€ เนƒเธ™เธ•เธฑเธงเน€เธฅเธทเธญเธเธชเธณเธซเธฃเธฑเธšเธ™เธฑเธเธžเธฑเธ’เธ™เธฒ เน€เธ›เธดเธ”เธ•เธญเธ™เธ™เธตเน‰เน„เธซเธก?</string> + <string name="linux_child_processes_adb">Android เธˆเธณเธเธฑเธ”เธˆเธณเธ™เธงเธ™เน‚เธ›เธฃเน€เธ‹เธชเน€เธšเธทเน‰เธญเธ‡เธซเธฅเธฑเธ‡เธ‚เธญเธ‡เนเธญเธ› เนเธ•เนˆเน€เธ‹เธชเธŠเธฑเธ™ Linux เนƒเธŠเน‰เธกเธฒเธเธเธงเนˆเธฒเธ™เธฑเน‰เธ™เธกเธฒเธ: Steam เธซเธฃเธทเธญเน€เธเธกเธญเธฒเธˆเธ–เธนเธเธ›เธดเธ”เธฃเธฐเธซเธงเนˆเธฒเธ‡เน€เธฅเนˆเธ™ Android เธฃเธธเนˆเธ™เธ™เธตเน‰เน„เธกเนˆเธกเธตเธชเธงเธดเธ•เธŠเนŒเธ™เธฑเน‰เธ™ เนƒเธซเน‰เธฃเธฑเธ™เธˆเธฒเธเธ„เธญเธกเธžเธดเธงเน€เธ•เธญเธฃเนŒ:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3e58e93b4..5bedf1a69 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -18,6 +18,8 @@ <string name="common_ui_enter_path">Yol girin</string> <string name="common_ui_path_not_found">Yol bulunamadฤฑ</string> <string name="common_ui_select_valid_exe_file">Lรผtfen geรงerli bir .exe dosyasฤฑ seรงin</string> + <string name="common_ui_select_executable">ร‡alฤฑลŸtฤฑrฤฑlabilir dosya seรง</string> + <string name="common_ui_select_valid_executable">Bir Windows veya Linux รงalฤฑลŸtฤฑrฤฑlabilir dosyasฤฑ seรงin</string> <string name="common_ui_select_valid_icon_file">Lรผtfen geรงerli bir .ico veya .png dosyasฤฑ seรงin.</string> <string name="common_ui_exit">ร‡ฤฑkฤฑลŸ</string> <string name="common_ui_exit_app">Uygulamadan ร‡ฤฑk</string> @@ -149,6 +151,16 @@ <string name="store_game_branch_switch_installed">Sรผrรผm %1$s olarak ayarlandฤฑ. Kurulu dosyalarฤฑ deฤŸiลŸtirmek iรงin Gรผncelleme Denetle\'yi kullanฤฑn.</string> <string name="common_ui_select">Seรง</string> <string name="common_ui_storage_roots">Depolama Kรถkleri</string> + <string name="common_ui_app_storage">Uygulama depolamasฤฑ</string> + <string name="common_ui_move">TaลŸฤฑ</string> + <string name="library_games_move_to_app_storage_title">Dahiliye taลŸฤฑ</string> + <string name="library_games_move_to_download_folder_title">Hariciye taลŸฤฑ</string> + <string name="library_games_move_to_app_storage_confirm">%1$s dahili depolamaya taลŸฤฑnsฤฑn mฤฑ?</string> + <string name="library_games_move_to_download_folder_confirm">%1$s harici depolamaya taลŸฤฑnsฤฑn mฤฑ?</string> + <string name="library_games_moving">Oyun taลŸฤฑnฤฑyor</string> + <string name="library_games_move_done">%1$s taลŸฤฑndฤฑ</string> + <string name="library_games_move_failed">%1$s taลŸฤฑnamadฤฑ</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Yerel klasรถrlere doฤŸrudan gรถzat</string> <string name="common_ui_no_folders_available_here">Burada mevcut klasรถr yok</string> <string name="common_ui_grant_all_files_access_browse">Klasรถrlere doฤŸrudan gรถzatmak iรงin tรผm dosyalara eriลŸim izni verin.</string> @@ -227,11 +239,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">GiriลŸ Testleri</string> - <string name="library_games_add_custom_game">ร–zel Oyun Ekle</string> <string name="library_games_game_name">Oyun Adฤฑ</string> <string name="library_games_game_folder_mapped_drive">Oyun Klasรถrรผ (EลŸlenmiลŸ Oyun Sรผrรผcรผsรผ)</string> <string name="library_games_select_exe_provide_name">Bir exe seรงin ve bir ad girin</string> <string name="library_games_sign_in_prompt">Kรผtรผphanenizi oluลŸturmak iรงin bir maฤŸazaya giriลŸ yapฤฑn veya รถzel bir oyun ekleyin.</string> + <string name="library_games_add_title">Oyun veya uygulama ekle</string> + <string name="library_games_add_linux_steam">Steam istemcisi ekle</string> + <string name="library_games_type">Tรผr</string> + <string name="library_games_type_game">Oyun</string> + <string name="library_games_type_app">Uygulama</string> + <string name="library_games_select_executable_or_rom">Bir รงalฤฑลŸtฤฑrฤฑlabilir dosya veya konsol ROM\'u seรงin</string> + <string name="library_games_added">%1$s eklendi</string> + <string name="library_games_add_failed">%1$s eklenemedi</string> + <string name="linux_runtime_not_installed">Linux รงalฤฑลŸma zamanฤฑ henรผz yรผklรผ deฤŸil, bu yรผzden %1$s baลŸlatฤฑlamฤฑyor. Ayarlar > MaฤŸazalar iรงindeki Linux ฤฐstemcisi ile kurun.</string> + <string name="linux_client_button">Linux ฤฐstemcisi</string> + <string name="linux_client_title">Linux Steam ฤฐstemcisi</string> + <string name="linux_client_install_message">Linux รงalฤฑลŸma zamanฤฑnฤฑ WinNative\'den, Steam istemcisini Valve\'dan indirir ve ikisini de kurar. ฤฐndirme 2 GB\'ฤฑ aลŸtฤฑฤŸฤฑ iรงin Wi-Fi kullanฤฑn.</string> + <string name="linux_client_installed_message">Linux รงalฤฑลŸma zamanฤฑ ve Steam istemcisi kurulu, Steam de Kรผtรผphane\'de.</string> + <string name="linux_client_failed_message">Kurulum tamamlanmadฤฑ. BaฤŸlantฤฑnฤฑzฤฑ kontrol edip yeniden deneyin.</string> + <string name="linux_client_no_space_message">Yeterli boลŸ alan yok: %1$s gerekli, %2$s kullanฤฑlabilir.</string> + <string name="linux_client_stage_connect">BaฤŸlanฤฑyor</string> + <string name="linux_client_stage_download_runtime">Linux รงalฤฑลŸma zamanฤฑ indiriliyor</string> + <string name="linux_client_stage_install_runtime">Linux รงalฤฑลŸma zamanฤฑ kuruluyor</string> + <string name="linux_client_stage_download_proton">Proton indiriliyor</string> + <string name="linux_client_stage_install_proton">Proton kuruluyor</string> + <string name="linux_client_stage_download_steam">Steam istemcisi indiriliyor</string> + <string name="linux_client_stage_install_steam">Steam istemcisi kuruluyor</string> + <string name="linux_client_stage_library">Steam Kรผtรผphane\'ye ekleniyor</string> + <string name="linux_client_progress_amount">%1$s / %2$s</string> + <string name="linux_client_retry">Yeniden dene</string> + <string name="linux_client_busy">Linux Steam ฤฐstemcisi hรขlรข kuruluyor. BittiฤŸinde yeniden deneyin.</string> + <string name="linux_client_update_message">Daha yeni bir Linux รงalฤฑลŸma ortamฤฑ var. Gรผncelleme; Steam oturumunuzu, yรผklรผ oyunlarฤฑ, kayฤฑtlarฤฑ ve kapsayฤฑcฤฑlarฤฑ korur.</string> + <string name="linux_client_update_session_running">Bir Linux oturumu รงalฤฑลŸฤฑyor. Oturumdan รงฤฑkฤฑn, sonra gรผncelleyin.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux oturumu baลŸlar baลŸlamaz durdu. GรผnlรผฤŸรผ Ayarlar > Hata Ayฤฑklama > Gรผnlรผk Yรถneticisini Aรง altฤฑndadฤฑr; nedeni bulunabilsin diye paylaลŸ.</string> + <string name="linux_session_stopped">Linux oturumu kendiliฤŸinden durdu (durum %1$d). GรผnlรผฤŸรผ Ayarlar > Hata Ayฤฑklama > Gรผnlรผk Yรถneticisini Aรง altฤฑndadฤฑr; nedeni bulunabilsin diye paylaลŸ.</string> + <string name="linux_session_trimmed">Android Linux oturumunu durdurdu. Bu genellikle arka plan iลŸlem sฤฑnฤฑrฤฑna ulaลŸฤฑldฤฑฤŸฤฑ anlamฤฑna gelir: GeliลŸtirici seรงeneklerinde โ€œAlt iลŸlem kฤฑsฤฑtlamalarฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrakโ€ฤฑ aรง ve yeniden dene.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">MaฤŸazalar</string> @@ -1357,6 +1401,10 @@ Yรผklรผ konum: <string name="setup_wizard_title">Kurulum Sihirbazฤฑ</string> <string name="setup_wizard_advanced_user">GeliลŸmiลŸ Kullanฤฑcฤฑ</string> <string name="setup_wizard_next">ฤฐleri</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Bitir</string> <string name="setup_wizard_download_and_install">ฤฐndir + Yรผkle</string> <string name="setup_wizard_open_drivers">Sรผrรผcรผleri Aรง</string> @@ -2864,10 +2912,9 @@ Yรผklรผ konum: <string name="update_apk_notice">Bu gรผncelleme uygulamanฤฑn kendisini deฤŸiลŸtirir, bu yรผzden Android kurulumu onaylamanฤฑzฤฑ isteyecek. Oyunlarฤฑnฤฑz, kapsayฤฑcฤฑlarฤฑnฤฑz ve ayarlarฤฑnฤฑz korunur.</string> <string name="update_up_to_date">WinNative gรผncel.</string> <string name="update_unsupported_build">Uygulama iรงi gรผncellemeler yalnฤฑzca resmi imzalฤฑ sรผrรผmlerde sunulur.</string> - <string name="settings_general_update_channel">Gรผncelleme Kanalฤฑ</string> - <string name="settings_general_update_channel_summary">Resmi kanal test edilmiลŸ sรผrรผmler yayฤฑnlar. GeliลŸtirme kanalฤฑ erken derlemeler yayฤฑnlar.</string> + <string name="settings_general_update_source">Gรผncelleme kaynaฤŸฤฑ</string> + <string name="settings_general_update_source_summary">Bir pull requestโ€™ten alฤฑnan yapฤฑ o pull requestโ€™i, resmi bir kurulum ise resmi sรผrรผmleri izler.</string> <string name="update_channel_official">Resmi</string> - <string name="update_channel_development">GeliลŸtirme</string> <string name="networking_section_title">AฤŸ</string> <string name="networking_driver">AฤŸ sรผrรผcรผsรผ</string> <string name="networking_driver_winnative">WinNative AฤŸ Sรผrรผcรผsรผ</string> @@ -2880,4 +2927,35 @@ Yรผklรผ konum: <string name="steam_controller_left_pad_click">Sol yรผzeye tฤฑklama</string> <string name="steam_controller_right_pad_click">SaฤŸ yรผzeye tฤฑklama</string> <string name="controller_test_generic">Genel</string> + <string name="container_display_server">Gรถrรผntรผ Sunucusu</string> + <string name="container_display_server_help">Deneysel. Oturum X sunucusu yerine yerleลŸik Wayland birleลŸtiricisi รผzerinden รงizilir.</string> + <string name="container_display_server_wayland_requirements">Wayland iรงin bir Adreno GPU ve winewayland ile Wayland Turnip sรผrรผcรผsรผnรผ iรงeren Wayland uyumlu bir Proton gerekir. ฤฐรงerikler bรถlรผmรผnden kurun.</string> + <string name="container_display_server_needs_wayland_proton">Bu Proton winewayland veya Wayland Turnip sรผrรผcรผsรผnรผ iรงermediฤŸinden X11 kullanฤฑlฤฑyor. ฤฐรงerikler bรถlรผmรผnden bir Wayland Proton katmanฤฑ kurun ve onu kapsayฤฑcฤฑnฤฑn Proton\'u olarak seรงin.</string> + <string name="wayland_unavailable_fallback">Bu oyun iรงin Wayland kullanฤฑlamฤฑyor. X11 ile baลŸlatฤฑlฤฑyor.</string> + <string name="wayland_needs_turnip_driver">Wayland\'ฤฑn oyunu gรถsterebilmesi iรงin bir Turnip grafik sรผrรผcรผsรผ gerekir. ฤฐรงerikler bรถlรผmรผnden kurun ve grafik sรผrรผcรผsรผ olarak seรงin.</string> + <string name="containers_gamescope_new">GameScope kapsayฤฑcฤฑsฤฑ oluลŸtur</string> + <string name="containers_gamescope_create_failed">GameScope kapsayฤฑcฤฑsฤฑ oluลŸturulamadฤฑ</string> + <string name="linux_apps_need_gamescope_container">ร–nce Ayarlar โ€บ Kapsayฤฑcฤฑlar altฤฑnda GameScope kapsayฤฑcฤฑsฤฑnฤฑ oluลŸturun.</string> + <string name="linux_runtime_missing">Linux รงalฤฑลŸma zamanฤฑ yรผklรผ deฤŸil, bu yรผzden bu kapsayฤฑcฤฑ henรผz baลŸlatฤฑlamฤฑyor. Ayarlar > MaฤŸazalar iรงindeki Linux ฤฐstemcisi ile kurun.</string> + <string name="container_display_server_gamescope">Gamescope her zaman Wayland birleลŸtiricisi รผzerinden รงizer.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Linux รงalฤฑลŸma ortamฤฑna dahildir. ฤฐndirilen Android sรผrรผcรผleri yalnฤฑzca Wine ve Proton kapsayฤฑcฤฑlarฤฑ iรงin geรงerlidir.</string> + <string name="linux_runtime_windows_program">Bu bir Windows programฤฑ. BaลŸlatmak iรงin kฤฑsayol ayarlarฤฑndan bir Wine kapsayฤฑcฤฑsฤฑna taลŸฤฑyฤฑn.</string> + <string name="settings_content_badge_online">ร‡evrimiรงi</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Sรผrรผcรผnรผn nereye kurulacaฤŸฤฑnฤฑ seรงin. Tanฤฑnan paketler otomatik olarak doฤŸru platformu kullanฤฑr.</string> + <string name="settings_drivers_restore_defaults">Varsayฤฑlanlarฤฑ geri yรผkle</string> + <string name="settings_drivers_defaults_present">Varsayฤฑlan depolar zaten mevcut</string> + <string name="settings_drivers_active">Etkin</string> + <string name="settings_drivers_bundled">Dahili Mesa</string> + <string name="settings_drivers_legacy">Linux istemci sรผrรผcรผsรผ</string> + <string name="settings_drivers_routed">Sรผrรผcรผ tanฤฑmlandฤฑ ve %1$s iรงin kuruldu.</string> + <string name="linux_child_processes_message">Android bir uygulamanฤฑn arka plan iลŸlem sayฤฑsฤฑnฤฑ sฤฑnฤฑrlar, Linux oturumu ise รงok daha fazlasฤฑnฤฑ รงalฤฑลŸtฤฑrฤฑr: Steam veya bir oyun sen oynarken kapatฤฑlabilir. GeliลŸtirici seรงeneklerinde โ€œAlt iลŸlem kฤฑsฤฑtlamalarฤฑnฤฑ devre dฤฑลŸฤฑ bฤฑrakโ€ฤฑ aรง. ลžimdi aรงฤฑlsฤฑn mฤฑ?</string> + <string name="linux_child_processes_adb">Android bir uygulamanฤฑn arka plan iลŸlem sayฤฑsฤฑnฤฑ sฤฑnฤฑrlar, Linux oturumu ise รงok daha fazlasฤฑnฤฑ รงalฤฑลŸtฤฑrฤฑr: Steam veya bir oyun sen oynarken kapatฤฑlabilir. Bu Android sรผrรผmรผnde bu seรงenek yok. Bir bilgisayardan ลŸunu รงalฤฑลŸtฤฑr:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e777636b9..5e8526093 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">ะกะบะธะฝัƒั‚ะธ ั–ะบะพะฝะบัƒ</string> <string name="common_ui_select_exe">ะ’ะธะฑั€ะฐั‚ะธ EXE</string> <string name="common_ui_select_valid_exe_file">ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะธะฑะตั€ั–ั‚ัŒ ะดั–ะนัะฝะธะน ั„ะฐะนะป .exe</string> + <string name="common_ui_select_executable">ะ’ะธะฑั€ะฐั‚ะธ ะฒะธะบะพะฝัƒะฒะฐะฝะธะน ั„ะฐะนะป</string> + <string name="common_ui_select_valid_executable">ะ’ะธะฑะตั€ั–ั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐะฝะธะน ั„ะฐะนะป Windows ะฐะฑะพ Linux</string> <string name="common_ui_select_valid_icon_file">ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะธะฑะตั€ั–ั‚ัŒ ะดั–ะนัะฝะธะน ั„ะฐะนะป .ico ะฐะฑะพ .png.</string> <string name="common_ui_exit">ะ’ะธะนั‚ะธ</string> <string name="common_ui_exit_app">ะ’ะธะนั‚ะธ ั–ะท ะทะฐัั‚ะพััƒะฝะบัƒ</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">ะ’ะฒะตะดั–ั‚ัŒ ัˆะปัั…</string> <string name="common_ui_path_not_found">ะจะปัั… ะฝะต ะทะฝะฐะนะดะตะฝะพ</string> <string name="common_ui_storage_roots">ะกั…ะพะฒะธั‰ะฐ</string> + <string name="common_ui_app_storage">ะกั…ะพะฒะธั‰ะต ะทะฐัั‚ะพััƒะฝะบัƒ</string> + <string name="common_ui_move">ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ</string> + <string name="library_games_move_to_app_storage_title">ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะดะพ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพั—</string> + <string name="library_games_move_to_download_folder_title">ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ะดะพ ะทะพะฒะฝั–ัˆะฝัŒะพั—</string> + <string name="library_games_move_to_app_storage_confirm">ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ %1$s ะดะพ ะฒะฝัƒั‚ั€ั–ัˆะฝัŒะพะณะพ ัั…ะพะฒะธั‰ะฐ?</string> + <string name="library_games_move_to_download_folder_confirm">ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ %1$s ะดะพ ะทะพะฒะฝั–ัˆะฝัŒะพะณะพ ัั…ะพะฒะธั‰ะฐ?</string> + <string name="library_games_moving">ะŸะตั€ะตะผั–ั‰ะตะฝะฝั ะณั€ะธ</string> + <string name="library_games_move_done">%1$s ะฟะตั€ะตะผั–ั‰ะตะฝะพ</string> + <string name="library_games_move_failed">ะะต ะฒะดะฐะปะพัั ะฟะตั€ะตะผั–ัั‚ะธั‚ะธ %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">ะŸะตั€ะตะณะปัะดะฐั‚ะธ ะปะพะบะฐะปัŒะฝั– ั‚ะตะบะธ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ</string> <string name="common_ui_no_folders_available_here">ะขัƒั‚ ะฝะตะผะฐั” ะดะพัั‚ัƒะฟะฝะธั… ั‚ะตะบ</string> <string name="common_ui_grant_all_files_access_browse">ะะฐะดะฐะนั‚ะต ะฟะพะฒะฝะธะน ะดะพัั‚ัƒะฟ ะดะพ ั„ะฐะนะปั–ะฒ, ั‰ะพะฑ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะตั€ะตะณะปัะดะฐั‚ะธ ะปะพะบะฐะปัŒะฝั– ั‚ะตะบะธ.</string> @@ -230,11 +242,43 @@ <string name="hero_graphics_test_32">32-ะฑั–ั‚</string> <string name="hero_graphics_test_64">64-ะฑั–ั‚</string> <string name="hero_input_tests_title">ะขะตัั‚ะธ ะฒะฒะตะดะตะฝะฝั</string> - <string name="library_games_add_custom_game">ะ”ะพะดะฐั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบัƒ ะณั€ัƒ</string> <string name="library_games_game_name">ะะฐะทะฒะฐ ะณั€ะธ</string> <string name="library_games_game_folder_mapped_drive">ะŸะฐะฟะบะฐ ะณั€ะธ (ะทั–ัั‚ะฐะฒะปะตะฝะธะน ั–ะณั€ะพะฒะธะน ะดะธัะบ)</string> <string name="library_games_select_exe_provide_name">ะ’ะธะฑะตั€ั–ั‚ัŒ exe ั‚ะฐ ะฒะบะฐะถั–ั‚ัŒ ะฝะฐะทะฒัƒ</string> <string name="library_games_sign_in_prompt">ะฃะฒั–ะนะดั–ั‚ัŒ ะดะพ ะผะฐะณะฐะทะธะฝัƒ ะฐะฑะพ ะดะพะดะฐะนั‚ะต ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบัƒ ะณั€ัƒ, ั‰ะพะฑ ะฟะพะฑัƒะดัƒะฒะฐั‚ะธ ัะฒะพัŽ ะฑั–ะฑะปั–ะพั‚ะตะบัƒ.</string> + <string name="library_games_add_title">ะ”ะพะดะฐั‚ะธ ะณั€ัƒ ะฐะฑะพ ะทะฐัั‚ะพััƒะฝะพะบ</string> + <string name="library_games_add_linux_steam">ะ”ะพะดะฐั‚ะธ ะบะปั–ั”ะฝั‚ Steam</string> + <string name="library_games_type">ะขะธะฟ</string> + <string name="library_games_type_game">ะ“ั€ะฐ</string> + <string name="library_games_type_app">ะ—ะฐัั‚ะพััƒะฝะพะบ</string> + <string name="library_games_select_executable_or_rom">ะ’ะธะฑะตั€ั–ั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐะฝะธะน ั„ะฐะนะป ะฐะฑะพ ROM ะบะพะฝัะพะปั–</string> + <string name="library_games_added">%1$s ะดะพะดะฐะฝะพ</string> + <string name="library_games_add_failed">ะะต ะฒะดะฐะปะพัั ะดะพะดะฐั‚ะธ %1$s</string> + <string name="linux_runtime_not_installed">ะกะตั€ะตะดะพะฒะธั‰ะต ะฒะธะบะพะฝะฐะฝะฝั Linux ั‰ะต ะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ, ั‚ะพะผัƒ %1$s ะฝะต ะผะพะถะฝะฐ ะทะฐะฟัƒัั‚ะธั‚ะธ. ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะนะพะณะพ ะบะฝะพะฟะบะพัŽ ะšะปั–ั”ะฝั‚ Linux ัƒ ั€ะพะทะดั–ะปั– ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั > ะœะฐะณะฐะทะธะฝะธ.</string> + <string name="linux_client_button">ะšะปั–ั”ะฝั‚ Linux</string> + <string name="linux_client_title">ะšะปั–ั”ะฝั‚ Steam ะดะปั Linux</string> + <string name="linux_client_install_message">ะ—ะฐะฒะฐะฝั‚ะฐะถัƒั” ัะตั€ะตะดะพะฒะธั‰ะต ะฒะธะบะพะฝะฐะฝะฝั Linux ั–ะท WinNative ั‚ะฐ ะบะปั–ั”ะฝั‚ Steam ั–ะท ัะตั€ะฒะตั€ั–ะฒ Valve, ะฟะพั‚ั–ะผ ะฒัั‚ะฐะฝะพะฒะปัŽั” ะพะฑะธะดะฒะฐ. ะžะฑััะณ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะฟะพะฝะฐะด 2 ะ“ะ‘, ั‚ะพะถ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต Wi-Fi.</string> + <string name="linux_client_installed_message">ะกะตั€ะตะดะพะฒะธั‰ะต ะฒะธะบะพะฝะฐะฝะฝั Linux ั– ะบะปั–ั”ะฝั‚ Steam ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ, ะฐ Steam ั” ะฒ ะ‘ั–ะฑะปั–ะพั‚ะตั†ั–.</string> + <string name="linux_client_failed_message">ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั ะฝะต ะทะฐะฒะตั€ัˆะธะปะพัั. ะŸะตั€ะตะฒั–ั€ั‚ะต ะท\'ั”ะดะฝะฐะฝะฝั ั– ัะฟั€ะพะฑัƒะนั‚ะต ั‰ะต ั€ะฐะท.</string> + <string name="linux_client_no_space_message">ะะตะดะพัั‚ะฐั‚ะฝัŒะพ ะฒั–ะปัŒะฝะพะณะพ ะผั–ัั†ั: ะฟะพั‚ั€ั–ะฑะฝะพ %1$s, ะดะพัั‚ัƒะฟะฝะพ %2$s.</string> + <string name="linux_client_stage_connect">ะŸั–ะดะบะปัŽั‡ะตะฝะฝั</string> + <string name="linux_client_stage_download_runtime">ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ัะตั€ะตะดะพะฒะธั‰ะฐ ะฒะธะบะพะฝะฐะฝะฝั Linux</string> + <string name="linux_client_stage_install_runtime">ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั ัะตั€ะตะดะพะฒะธั‰ะฐ ะฒะธะบะพะฝะฐะฝะฝั Linux</string> + <string name="linux_client_stage_download_proton">ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั Proton</string> + <string name="linux_client_stage_install_proton">ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั Proton</string> + <string name="linux_client_stage_download_steam">ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะบะปั–ั”ะฝั‚ะฐ Steam</string> + <string name="linux_client_stage_install_steam">ะ’ัั‚ะฐะฝะพะฒะปะตะฝะฝั ะบะปั–ั”ะฝั‚ะฐ Steam</string> + <string name="linux_client_stage_library">ะ”ะพะดะฐะฒะฐะฝะฝั Steam ะดะพ ะ‘ั–ะฑะปั–ะพั‚ะตะบะธ</string> + <string name="linux_client_progress_amount">%1$s ะท %2$s</string> + <string name="linux_client_retry">ะŸะพะฒั‚ะพั€ะธั‚ะธ</string> + <string name="linux_client_busy">ะšะปั–ั”ะฝั‚ Steam ะดะปั Linux ั‰ะต ะฒัั‚ะฐะฝะพะฒะปัŽั”ั‚ัŒัั. ะกะฟั€ะพะฑัƒะนั‚ะต ั‰ะต ั€ะฐะท ะฟั–ัะปั ะทะฐะฒะตั€ัˆะตะฝะฝั.</string> + <string name="linux_client_update_message">ะ”ะพัั‚ัƒะฟะฝะต ะฝะพะฒั–ัˆะต ัะตั€ะตะดะพะฒะธั‰ะต Linux. ะŸั–ะด ั‡ะฐั ะพะฝะพะฒะปะตะฝะฝั ะทะฑะตั€ั–ะณะฐัŽั‚ัŒัั ะฒั…ั–ะด ัƒ Steam, ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ั–ะณั€ะธ, ะทะฑะตั€ะตะถะตะฝะฝั ั‚ะฐ ะบะพะฝั‚ะตะนะฝะตั€ะธ.</string> + <string name="linux_client_update_session_running">ะ—ะฐะฟัƒั‰ะตะฝะพ ัะตะฐะฝั Linux. ะ—ะฐะฒะตั€ัˆั–ั‚ัŒ ะนะพะณะพ, ะฐ ะฟะพั‚ั–ะผ ะพะฝะพะฒั–ั‚ัŒ.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">ะกะตะฐะฝั Linux ะทัƒะฟะธะฝะธะฒัั ะพะดั€ะฐะทัƒ ะฟั–ัะปั ะทะฐะฟัƒัะบัƒ. ะ™ะพะณะพ ะถัƒั€ะฝะฐะป ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะธะน ัƒ ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั > ะะฐะปะฐะณะพะดะถะตะฝะฝั > ะ’ั–ะดะบั€ะธั‚ะธ ะผะตะฝะตะดะถะตั€ ะถัƒั€ะฝะฐะปั–ะฒ; ะฟะพะดั–ะปั–ั‚ัŒัั ะฝะธะผ, ั‰ะพะฑ ะผะพะถะฝะฐ ะฑัƒะปะพ ะทะฝะฐะนั‚ะธ ะฟั€ะธั‡ะธะฝัƒ.</string> + <string name="linux_session_stopped">ะกะตะฐะฝั Linux ะทัƒะฟะธะฝะธะฒัั ัะฐะผ (ัั‚ะฐะฝ %1$d). ะ™ะพะณะพ ะถัƒั€ะฝะฐะป ั€ะพะทั‚ะฐัˆะพะฒะฐะฝะธะน ัƒ ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั > ะะฐะปะฐะณะพะดะถะตะฝะฝั > ะ’ั–ะดะบั€ะธั‚ะธ ะผะตะฝะตะดะถะตั€ ะถัƒั€ะฝะฐะปั–ะฒ; ะฟะพะดั–ะปั–ั‚ัŒัั ะฝะธะผ, ั‰ะพะฑ ะผะพะถะฝะฐ ะฑัƒะปะพ ะทะฝะฐะนั‚ะธ ะฟั€ะธั‡ะธะฝัƒ.</string> + <string name="linux_session_trimmed">Android ะทัƒะฟะธะฝะธะฒ ัะตะฐะฝั Linux. ะ—ะฐะทะฒะธั‡ะฐะน ั†ะต ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะดะพััะณะฝัƒั‚ะพ ะผะตะถัƒ ั„ะพะฝะพะฒะธั… ะฟั€ะพั†ะตัั–ะฒ: ัƒะฒั–ะผะบะฝั–ั‚ัŒ ยซะ’ะธะผะบะฝัƒั‚ะธ ะพะฑะผะตะถะตะฝะฝั ะดะพั‡ั–ั€ะฝั–ั… ะฟั€ะพั†ะตัั–ะฒยป ัƒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ั€ะพะทั€ะพะฑะฝะธะบะฐ ั‚ะฐ ัะฟั€ะพะฑัƒะนั‚ะต ั‰ะต ั€ะฐะท.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">ะœะฐะณะฐะทะธะฝะธ</string> @@ -1071,6 +1115,10 @@ <string name="setup_wizard_title">ะœะฐะนัั‚ะตั€ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั</string> <string name="setup_wizard_advanced_user">ะ”ะพัะฒั–ะดั‡ะตะฝะธะน ะบะพั€ะธัั‚ัƒะฒะฐั‡</string> <string name="setup_wizard_next">ะ”ะฐะปั–</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">ะ—ะฐะฒะตั€ัˆะธั‚ะธ</string> <string name="setup_wizard_download_and_install">ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ + ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ</string> <string name="setup_wizard_open_drivers">ะ’ั–ะดะบั€ะธั‚ะธ ะดั€ะฐะนะฒะตั€ะธ</string> @@ -2870,10 +2918,9 @@ <string name="update_apk_notice">ะฆะต ะพะฝะพะฒะปะตะฝะฝั ะทะผั–ะฝัŽั” ัะฐะผ ะทะฐัั‚ะพััƒะฝะพะบ, ั‚ะพะผัƒ Android ะฟะพะฟั€ะพัะธั‚ัŒ ะฟั–ะดั‚ะฒะตั€ะดะธั‚ะธ ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั. ะ’ะฐัˆั– ั–ะณั€ะธ, ะบะพะฝั‚ะตะนะฝะตั€ะธ ั‚ะฐ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะทะฑะตั€ะตะถัƒั‚ัŒัั.</string> <string name="update_up_to_date">WinNative ะฐะบั‚ัƒะฐะปัŒะฝะธะน.</string> <string name="update_unsupported_build">ะžะฝะพะฒะปะตะฝะฝั ะฒ ะทะฐัั‚ะพััƒะฝะบัƒ ะดะพัั‚ัƒะฟะฝั– ะปะธัˆะต ะดะปั ะพั„ั–ั†ั–ะนะฝะธั… ะฟั–ะดะฟะธัะฐะฝะธั… ะฒะธะฟัƒัะบั–ะฒ.</string> - <string name="settings_general_update_channel">ะšะฐะฝะฐะป ะพะฝะพะฒะปะตะฝัŒ</string> - <string name="settings_general_update_channel_summary">ะžั„ั–ั†ั–ะนะฝะธะน ะฟะพัั‚ะฐั‡ะฐั” ะฟะตั€ะตะฒั–ั€ะตะฝั– ะฒะธะฟัƒัะบะธ. ะ ะพะทั€ะพะฑะฝะธั†ัŒะบะธะน โ€” ั€ะฐะฝะฝั– ะทะฑั–ั€ะบะธ.</string> + <string name="settings_general_update_source">ะ”ะถะตั€ะตะปะพ ะพะฝะพะฒะปะตะฝัŒ</string> + <string name="settings_general_update_source_summary">ะ—ะฑั–ั€ะบะฐ ะท ะฟัƒะปั€ะตะบะฒะตัั‚ัƒ ัั‚ะตะถะธั‚ัŒ ะทะฐ ั†ะธะผ ะฟัƒะปั€ะตะบะฒะตัั‚ะพะผ; ะพั„ั–ั†ั–ะนะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะฝั โ€” ะทะฐ ะพั„ั–ั†ั–ะนะฝะธะผะธ ะฒะธะฟัƒัะบะฐะผะธ.</string> <string name="update_channel_official">ะžั„ั–ั†ั–ะนะฝะธะน</string> - <string name="update_channel_development">ะ ะพะทั€ะพะฑะฝะธั†ัŒะบะธะน</string> <string name="networking_section_title">ะœะตั€ะตะถะฐ</string> <string name="networking_driver">ะœะตั€ะตะถะตะฒะธะน ะดั€ะฐะนะฒะตั€</string> <string name="networking_driver_winnative">ะœะตั€ะตะถะตะฒะธะน ะดั€ะฐะนะฒะตั€ WinNative</string> @@ -2886,5 +2933,36 @@ <string name="steam_controller_left_pad_click">ะะฐั‚ะธัะบะฐะฝะฝั ะปั–ะฒะพั— ะฟะฐะฝะตะปั–</string> <string name="steam_controller_right_pad_click">ะะฐั‚ะธัะบะฐะฝะฝั ะฟั€ะฐะฒะพั— ะฟะฐะฝะตะปั–</string> <string name="controller_test_generic">ะฃะฝั–ะฒะตั€ัะฐะปัŒะฝะธะน</string> + <string name="container_display_server">ะ”ะธัะฟะปะตะนะฝะธะน ัะตั€ะฒะตั€</string> + <string name="container_display_server_help">ะ•ะบัะฟะตั€ะธะผะตะฝั‚ะฐะปัŒะฝะพ. ะกะตะฐะฝั ะผะฐะปัŽั”ั‚ัŒัั ั‡ะตั€ะตะท ะฒะฑัƒะดะพะฒะฐะฝะธะน ะบะพะผะฟะพะทะธั‚ะพั€ Wayland ะทะฐะผั–ัั‚ัŒ X-ัะตั€ะฒะตั€ะฐ.</string> + <string name="container_display_server_wayland_requirements">ะ”ะปั Wayland ะฟะพั‚ั€ั–ะฑะฝั– GPU Adreno ั‚ะฐ Proton ะท ะฟั–ะดั‚ั€ะธะผะบะพัŽ Wayland, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ winewayland ั– ะดั€ะฐะนะฒะตั€ Turnip ะดะปั Wayland. ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะนะพะณะพ ะฒ ั€ะพะทะดั–ะปั– ยซะ’ะผั–ัั‚ยป.</string> + <string name="container_display_server_needs_wayland_proton">ะฃ ั†ัŒะพะผัƒ Proton ะฝะตะผะฐั” winewayland ั– ะดั€ะฐะนะฒะตั€ะฐ Turnip ะดะปั Wayland, ั‚ะพะผัƒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั X11. ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ัˆะฐั€ Proton ะดะปั Wayland ัƒ ั€ะพะทะดั–ะปั– ยซะ’ะผั–ัั‚ยป ั– ะฒะธะฑะตั€ั–ั‚ัŒ ะนะพะณะพ ัะบ Proton ะบะพะฝั‚ะตะนะฝะตั€ะฐ.</string> + <string name="wayland_unavailable_fallback">Wayland ะฝะตะดะพัั‚ัƒะฟะฝะธะน ะดะปั ั†ั–ั”ั— ะณั€ะธ. ะ—ะฐะฟัƒัะบ ั‡ะตั€ะตะท X11.</string> + <string name="wayland_needs_turnip_driver">ะ”ะปั ะฟะพะบะฐะทัƒ ะณั€ะธ Wayland ะฟะพั‚ั€ั–ะฑะตะฝ ะณั€ะฐั„ั–ั‡ะฝะธะน ะดั€ะฐะนะฒะตั€ Turnip. ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะนะพะณะพ ะฒ ั€ะพะทะดั–ะปั– ยซะ’ะผั–ัั‚ยป ั– ะฒะธะฑะตั€ั–ั‚ัŒ ัะบ ะณั€ะฐั„ั–ั‡ะฝะธะน ะดั€ะฐะนะฒะตั€.</string> + <string name="containers_gamescope_new">ะกั‚ะฒะพั€ะธั‚ะธ ะบะพะฝั‚ะตะนะฝะตั€ GameScope</string> + <string name="containers_gamescope_create_failed">ะะต ะฒะดะฐะปะพัั ัั‚ะฒะพั€ะธั‚ะธ ะบะพะฝั‚ะตะนะฝะตั€ GameScope</string> + <string name="linux_apps_need_gamescope_container">ะกะฟะพั‡ะฐั‚ะบัƒ ัั‚ะฒะพั€ั–ั‚ัŒ ะบะพะฝั‚ะตะนะฝะตั€ GameScope ัƒ ั€ะพะทะดั–ะปั– ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั โ€บ ะšะพะฝั‚ะตะนะฝะตั€ะธ.</string> + <string name="linux_runtime_missing">ะกะตั€ะตะดะพะฒะธั‰ะต ะฒะธะบะพะฝะฐะฝะฝั Linux ะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ, ั‚ะพะผัƒ ั†ะตะน ะบะพะฝั‚ะตะนะฝะตั€ ะฟะพะบะธ ะฝะต ะผะพะถะฝะฐ ะทะฐะฟัƒัั‚ะธั‚ะธ. ะ’ัั‚ะฐะฝะพะฒั–ั‚ัŒ ะนะพะณะพ ะบะฝะพะฟะบะพัŽ ะšะปั–ั”ะฝั‚ Linux ัƒ ั€ะพะทะดั–ะปั– ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั > ะœะฐะณะฐะทะธะฝะธ.</string> + <string name="container_display_server_gamescope">Gamescope ะทะฐะฒะถะดะธ ะผะฐะปัŽั” ั‡ะตั€ะตะท ะบะพะผะฟะพะทะธั‚ะพั€ Wayland.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">ะ’ะฑัƒะดะพะฒะฐะฝะธะน ัƒ ัะตั€ะตะดะพะฒะธั‰ะต Linux. ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝั– ะดั€ะฐะนะฒะตั€ะธ Android ะทะฐัั‚ะพัะพะฒัƒัŽั‚ัŒัั ะปะธัˆะต ะดะพ ะบะพะฝั‚ะตะนะฝะตั€ั–ะฒ Wine ั– Proton.</string> + <string name="linux_runtime_windows_program">ะฆะต ะฟั€ะพะณั€ะฐะผะฐ ะดะปั Windows. ะฉะพะฑ ะทะฐะฟัƒัั‚ะธั‚ะธ ั—ั—, ะฟะตั€ะตะฝะตัั–ั‚ัŒ ั—ั— ะดะพ ะบะพะฝั‚ะตะนะฝะตั€ะฐ Wine ัƒ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝัั… ัั€ะปะธะบะฐ.</string> + <string name="settings_content_badge_online">ะžะฝะปะฐะนะฝ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">ะ’ะธะฑะตั€ั–ั‚ัŒ, ะบัƒะดะธ ะฒัั‚ะฐะฝะพะฒะธั‚ะธ ะดั€ะฐะนะฒะตั€. ะ”ะปั ั€ะพะทะฟั–ะทะฝะฐะฝะธั… ะฟะฐะบะตั‚ั–ะฒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฒะธะฑะธั€ะฐั”ั‚ัŒัั ะฟั€ะฐะฒะธะปัŒะฝะฐ ะฟะปะฐั‚ั„ะพั€ะผะฐ.</string> + <string name="settings_drivers_restore_defaults">ะ’ั–ะดะฝะพะฒะธั‚ะธ ั‚ะธะฟะพะฒั–</string> + <string name="settings_drivers_defaults_present">ะขะธะฟะพะฒั– ั€ะตะฟะพะทะธั‚ะพั€ั–ั— ะฒะถะต ะดะพะดะฐะฝะพ</string> + <string name="settings_drivers_active">ะะบั‚ะธะฒะฝะธะน</string> + <string name="settings_drivers_bundled">ะ’ะฑัƒะดะพะฒะฐะฝะธะน Mesa</string> + <string name="settings_drivers_legacy">ะ”ั€ะฐะนะฒะตั€ ะบะปั–ั”ะฝั‚ะฐ Linux</string> + <string name="settings_drivers_routed">ะ”ั€ะฐะนะฒะตั€ ั€ะพะทะฟั–ะทะฝะฐะฝะพ ะน ัƒัั‚ะฐะฝะพะฒะปะตะฝะพ ะดะปั %1$s.</string> + <string name="linux_child_processes_message">Android ะพะฑะผะตะถัƒั” ะบั–ะปัŒะบั–ัั‚ัŒ ั„ะพะฝะพะฒะธั… ะฟั€ะพั†ะตัั–ะฒ ะดะพะดะฐั‚ะบะฐ, ะฐ ัะตะฐะฝั Linux ะทะฐะฟัƒัะบะฐั” ั—ั… ะทะฝะฐั‡ะฝะพ ะฑั–ะปัŒัˆะต: Steam ะฐะฑะพ ะณั€ัƒ ะผะพะถะต ะฑัƒั‚ะธ ะทะฐะบั€ะธั‚ะพ ะฟั–ะด ั‡ะฐั ะณั€ะธ. ะฃะฒั–ะผะบะฝั–ั‚ัŒ ยซะ’ะธะผะบะฝัƒั‚ะธ ะพะฑะผะตะถะตะฝะฝั ะดะพั‡ั–ั€ะฝั–ั… ะฟั€ะพั†ะตัั–ะฒยป ัƒ ะฟะฐั€ะฐะผะตั‚ั€ะฐั… ั€ะพะทั€ะพะฑะฝะธะบะฐ. ะ’ั–ะดะบั€ะธั‚ะธ?</string> + <string name="linux_child_processes_adb">Android ะพะฑะผะตะถัƒั” ะบั–ะปัŒะบั–ัั‚ัŒ ั„ะพะฝะพะฒะธั… ะฟั€ะพั†ะตัั–ะฒ ะดะพะดะฐั‚ะบะฐ, ะฐ ัะตะฐะฝั Linux ะทะฐะฟัƒัะบะฐั” ั—ั… ะทะฝะฐั‡ะฝะพ ะฑั–ะปัŒัˆะต: Steam ะฐะฑะพ ะณั€ัƒ ะผะพะถะต ะฑัƒั‚ะธ ะทะฐะบั€ะธั‚ะพ ะฟั–ะด ั‡ะฐั ะณั€ะธ. ะฃ ั†ั–ะน ะฒะตั€ัั–ั— Android ั‚ะฐะบะพะณะพ ะฟะตั€ะตะผะธะบะฐั‡ะฐ ะฝะตะผะฐั”. ะะฐ ะบะพะผะฟโ€™ัŽั‚ะตั€ั– ะฒะธะบะพะฝะฐะนั‚ะต:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5e4151eec..84dbdce21 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">่ฟ˜ๅŽŸๅ›พๆ ‡</string> <string name="common_ui_select_exe">้€‰ๆ‹ฉ EXE</string> <string name="common_ui_select_valid_exe_file">่ฏท้€‰ๆ‹ฉไธ€ไธชๆœ‰ๆ•ˆ็š„ .exe ๆ–‡ไปถ</string> + <string name="common_ui_select_executable">้€‰ๆ‹ฉๅฏๆ‰ง่กŒๆ–‡ไปถ</string> + <string name="common_ui_select_valid_executable">่ฏท้€‰ๆ‹ฉ Windows ๆˆ– Linux ๅฏๆ‰ง่กŒๆ–‡ไปถ</string> <string name="common_ui_select_valid_icon_file">่ฏท้€‰ๆ‹ฉไธ€ไธชๆœ‰ๆ•ˆ็š„ .ico ๆˆ– .png ๆ–‡ไปถใ€‚</string> <string name="common_ui_exit">้€€ๅ‡บ</string> <string name="common_ui_exit_app">้€€ๅ‡บๅบ”็”จ</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">่พ“ๅ…ฅ่ทฏๅพ„</string> <string name="common_ui_path_not_found">ๆ‰พไธๅˆฐ่ทฏๅพ„</string> <string name="common_ui_storage_roots">ๅญ˜ๅ‚จๆ น็›ฎๅฝ•</string> + <string name="common_ui_app_storage">ๅบ”็”จๅญ˜ๅ‚จ</string> + <string name="common_ui_move">็งปๅŠจ</string> + <string name="library_games_move_to_app_storage_title">็งป่‡ณๅ†…้ƒจ</string> + <string name="library_games_move_to_download_folder_title">็งป่‡ณๅค–้ƒจ</string> + <string name="library_games_move_to_app_storage_confirm">ๅฐ† %1$s ็งป่‡ณๅ†…้ƒจๅญ˜ๅ‚จ๏ผŸ</string> + <string name="library_games_move_to_download_folder_confirm">ๅฐ† %1$s ็งป่‡ณๅค–้ƒจๅญ˜ๅ‚จ๏ผŸ</string> + <string name="library_games_moving">ๆญฃๅœจ็งปๅŠจๆธธๆˆ</string> + <string name="library_games_move_done">ๅทฒ็งปๅŠจ %1$s</string> + <string name="library_games_move_failed">ๆ— ๆณ•็งปๅŠจ %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">็›ดๆŽฅๆต่งˆๆœฌๅœฐๆ–‡ไปถๅคน</string> <string name="common_ui_no_folders_available_here">ๆญคๅค„ๆฒกๆœ‰ๅฏ็”จ็š„ๆ–‡ไปถๅคน</string> <string name="common_ui_grant_all_files_access_browse">่ฏทๆŽˆไบˆๆ‰€ๆœ‰ๆ–‡ไปถ่ฎฟ้—ฎๆƒ้™ไปฅ็›ดๆŽฅๆต่งˆๆœฌๅœฐๆ–‡ไปถๅคนใ€‚</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32 ไฝ</string> <string name="hero_graphics_test_64">64 ไฝ</string> <string name="hero_input_tests_title">่พ“ๅ…ฅๆต‹่ฏ•</string> - <string name="library_games_add_custom_game">ๆทปๅŠ ่‡ชๅฎšไน‰ๆธธๆˆ</string> <string name="library_games_game_name">ๆธธๆˆๅ็งฐ</string> <string name="library_games_game_folder_mapped_drive">ๆธธๆˆๆ–‡ไปถๅคน๏ผˆๆ˜ ๅฐ„็š„ๆธธๆˆ้ฉฑๅŠจๅ™จ๏ผ‰</string> <string name="library_games_select_exe_provide_name">้€‰ๆ‹ฉ exe ๆ–‡ไปถๅนถ่พ“ๅ…ฅๅ็งฐ</string> <string name="library_games_sign_in_prompt">็™ปๅฝ•ๅ•†ๅบ—ๆˆ–ๆทปๅŠ ่‡ชๅฎšไน‰ๆธธๆˆๆฅๆž„ๅปบๆ‚จ็š„ๆธธๆˆๅบ“ใ€‚</string> + <string name="library_games_add_title">ๆทปๅŠ ๆธธๆˆๆˆ–ๅบ”็”จ</string> + <string name="library_games_add_linux_steam">ๆทปๅŠ  Steam ๅฎขๆˆท็ซฏ</string> + <string name="library_games_type">็ฑปๅž‹</string> + <string name="library_games_type_game">ๆธธๆˆ</string> + <string name="library_games_type_app">ๅบ”็”จ</string> + <string name="library_games_select_executable_or_rom">้€‰ๆ‹ฉๅฏๆ‰ง่กŒๆ–‡ไปถๆˆ–ไธปๆœบ ROM</string> + <string name="library_games_added">ๅทฒๆทปๅŠ  %1$s</string> + <string name="library_games_add_failed">ๆ— ๆณ•ๆทปๅŠ  %1$s</string> + <string name="linux_runtime_not_installed">Linux ่ฟ่กŒๆ—ถๅฐšๆœชๅฎ‰่ฃ…๏ผŒๅ› ๆญคๆ— ๆณ•ๅฏๅŠจ %1$sใ€‚่ฏทๅœจ ่ฎพ็ฝฎ > ๅ•†ๅบ— ไธญ้€š่ฟ‡ Linux ๅฎขๆˆท็ซฏ ๅฎ‰่ฃ…ใ€‚</string> + <string name="linux_client_button">Linux ๅฎขๆˆท็ซฏ</string> + <string name="linux_client_title">Linux Steam ๅฎขๆˆท็ซฏ</string> + <string name="linux_client_install_message">ไปŽ WinNative ไธ‹่ฝฝ Linux ่ฟ่กŒๆ—ถ๏ผŒไปŽ Valve ไธ‹่ฝฝ Steam ๅฎขๆˆท็ซฏ๏ผŒ็„ถๅŽๅฎ‰่ฃ…ไธค่€…ใ€‚ไธ‹่ฝฝ่ถ…่ฟ‡ 2 GB๏ผŒ่ฏทไฝฟ็”จ Wi-Fiใ€‚</string> + <string name="linux_client_installed_message">Linux ่ฟ่กŒๆ—ถๅ’Œ Steam ๅฎขๆˆท็ซฏๅทฒๅฎ‰่ฃ…๏ผŒSteam ๅทฒๅœจๆธธๆˆๅบ“ไธญใ€‚</string> + <string name="linux_client_failed_message">ๅฎ‰่ฃ…ๆœชๅฎŒๆˆใ€‚่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅๅŽ้‡่ฏ•ใ€‚</string> + <string name="linux_client_no_space_message">ๅฏ็”จ็ฉบ้—ดไธ่ถณ๏ผš้œ€่ฆ %1$s๏ผŒๅฏ็”จ %2$sใ€‚</string> + <string name="linux_client_stage_connect">ๆญฃๅœจ่ฟžๆŽฅ</string> + <string name="linux_client_stage_download_runtime">ๆญฃๅœจไธ‹่ฝฝ Linux ่ฟ่กŒๆ—ถ</string> + <string name="linux_client_stage_install_runtime">ๆญฃๅœจๅฎ‰่ฃ… Linux ่ฟ่กŒๆ—ถ</string> + <string name="linux_client_stage_download_proton">ๆญฃๅœจไธ‹่ฝฝ Proton</string> + <string name="linux_client_stage_install_proton">ๆญฃๅœจๅฎ‰่ฃ… Proton</string> + <string name="linux_client_stage_download_steam">ๆญฃๅœจไธ‹่ฝฝ Steam ๅฎขๆˆท็ซฏ</string> + <string name="linux_client_stage_install_steam">ๆญฃๅœจๅฎ‰่ฃ… Steam ๅฎขๆˆท็ซฏ</string> + <string name="linux_client_stage_library">ๆญฃๅœจๅฐ† Steam ๆทปๅŠ ๅˆฐๆธธๆˆๅบ“</string> + <string name="linux_client_progress_amount">%1$s / %2$s</string> + <string name="linux_client_retry">้‡่ฏ•</string> + <string name="linux_client_busy">Linux Steam ๅฎขๆˆท็ซฏไปๅœจๅฎ‰่ฃ…ใ€‚่ฏทๅœจๅฎŒๆˆๅŽ้‡่ฏ•ใ€‚</string> + <string name="linux_client_update_message">ๆœ‰ๆ›ดๆ–ฐ็š„ Linux ่ฟ่กŒ็Žฏๅขƒๅฏ็”จใ€‚ๆ›ดๆ–ฐไผšไฟ็•™ Steam ็™ปๅฝ•ใ€ๅทฒๅฎ‰่ฃ…็š„ๆธธๆˆใ€ๅญ˜ๆกฃๅ’Œๅฎนๅ™จใ€‚</string> + <string name="linux_client_update_session_running">Linux ไผš่ฏๆญฃๅœจ่ฟ่กŒใ€‚่ฏทๅ…ˆ้€€ๅ‡บ๏ผŒ็„ถๅŽๅ†ๆ›ดๆ–ฐใ€‚</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux ไผš่ฏๅˆšๅฏๅŠจๅฐฑๅœๆญขไบ†ใ€‚ๆ—ฅๅฟ—ไฝไบŽ ่ฎพ็ฝฎ > ่ฐƒ่ฏ• > ๆ‰“ๅผ€ๆ—ฅๅฟ—็ฎก็†ๅ™จ๏ผ›่ฏทๅˆ†ไบซๆ—ฅๅฟ—ไปฅไพฟๆŸฅๆ˜ŽๅŽŸๅ› ใ€‚</string> + <string name="linux_session_stopped">Linux ไผš่ฏ่‡ช่กŒๅœๆญข๏ผˆ็Šถๆ€ %1$d๏ผ‰ใ€‚ๆ—ฅๅฟ—ไฝไบŽ ่ฎพ็ฝฎ > ่ฐƒ่ฏ• > ๆ‰“ๅผ€ๆ—ฅๅฟ—็ฎก็†ๅ™จ๏ผ›่ฏทๅˆ†ไบซๆ—ฅๅฟ—ไปฅไพฟๆŸฅๆ˜ŽๅŽŸๅ› ใ€‚</string> + <string name="linux_session_trimmed">Android ๅœๆญขไบ† Linux ไผš่ฏใ€‚่ฟ™้€šๅธธ่กจ็คบๅทฒ่พพๅˆฐๅŽๅฐ่ฟ›็จ‹ไธŠ้™๏ผš่ฏทๅœจๅผ€ๅ‘่€…้€‰้กนไธญๆ‰“ๅผ€โ€œ็ฆ็”จๅญ่ฟ›็จ‹้™ๅˆถโ€๏ผŒ็„ถๅŽ้‡่ฏ•ใ€‚</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">ๅ•†ๅบ—</string> @@ -1065,6 +1109,10 @@ <string name="setup_wizard_title">่ฎพ็ฝฎๅ‘ๅฏผ</string> <string name="setup_wizard_advanced_user">้ซ˜็บง็”จๆˆท</string> <string name="setup_wizard_next">ไธ‹ไธ€ๆญฅ</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">ๅฎŒๆˆ</string> <string name="setup_wizard_download_and_install">ไธ‹่ฝฝ + ๅฎ‰่ฃ…</string> <string name="setup_wizard_open_drivers">ๆ‰“ๅผ€้ฉฑๅŠจ</string> @@ -2864,10 +2912,9 @@ <string name="update_apk_notice">ๆญคๆ›ดๆ–ฐไผšๆ›ดๆ”นๅบ”็”จๆœฌ่บซ๏ผŒๅ› ๆญค Android ไผš่ฆๆฑ‚ไฝ ็กฎ่ฎคๅฎ‰่ฃ…ใ€‚ไฝ ็š„ๆธธๆˆใ€ๅฎนๅ™จๅ’Œ่ฎพ็ฝฎ้ƒฝไผšไฟ็•™ใ€‚</string> <string name="update_up_to_date">WinNative ๅทฒๆ˜ฏๆœ€ๆ–ฐ็‰ˆๆœฌใ€‚</string> <string name="update_unsupported_build">ไป…ๅฎ˜ๆ–น็ญพๅ็‰ˆๆœฌๆไพ›ๅบ”็”จๅ†…ๆ›ดๆ–ฐใ€‚</string> - <string name="settings_general_update_channel">ๆ›ดๆ–ฐ้€š้“</string> - <string name="settings_general_update_channel_summary">ๅฎ˜ๆ–น้€š้“ๅ‘ๅธƒ็ป่ฟ‡ๆต‹่ฏ•็š„็‰ˆๆœฌ๏ผŒๅผ€ๅ‘้€š้“ๅ‘ๅธƒๆ—ฉๆœŸๆž„ๅปบใ€‚</string> + <string name="settings_general_update_source">ๆ›ดๆ–ฐๆฅๆบ</string> + <string name="settings_general_update_source_summary">ๆฅ่‡ชๆ‹‰ๅ–่ฏทๆฑ‚็š„ๆž„ๅปบ่ทŸ้š่ฏฅๆ‹‰ๅ–่ฏทๆฑ‚๏ผ›ๅฎ˜ๆ–นๅฎ‰่ฃ…่ทŸ้šๅฎ˜ๆ–น็‰ˆๆœฌใ€‚</string> <string name="update_channel_official">ๅฎ˜ๆ–น</string> - <string name="update_channel_development">ๅผ€ๅ‘</string> <string name="networking_section_title">็ฝ‘็ปœ</string> <string name="networking_driver">็ฝ‘็ปœ้ฉฑๅŠจ</string> <string name="networking_driver_winnative">WinNative ็ฝ‘็ปœ้ฉฑๅŠจ</string> @@ -2880,5 +2927,36 @@ <string name="steam_controller_left_pad_click">ๅทฆ่งฆๆŽงๆฟๆŒ‰ไธ‹</string> <string name="steam_controller_right_pad_click">ๅณ่งฆๆŽงๆฟๆŒ‰ไธ‹</string> <string name="controller_test_generic">้€š็”จ</string> + <string name="container_display_server">ๆ˜พ็คบๆœๅŠกๅ™จ</string> + <string name="container_display_server_help">ๅฎž้ชŒๆ€งๅŠŸ่ƒฝใ€‚ไผš่ฏ้€š่ฟ‡ๅ†…็ฝฎ็š„ Wayland ๅˆๆˆๅ™จ็ป˜ๅˆถ๏ผŒ่€Œไธๆ˜ฏ X ๆœๅŠกๅ™จใ€‚</string> + <string name="container_display_server_wayland_requirements">Wayland ้œ€่ฆ Adreno GPU ไปฅๅŠๅŒ…ๅซ winewayland ๅ’Œ Wayland Turnip ้ฉฑๅŠจ็š„ Wayland ๅ…ผๅฎน Protonใ€‚่ฏทๅœจโ€œๅ†…ๅฎนโ€ไธญๅฎ‰่ฃ…ใ€‚</string> + <string name="container_display_server_needs_wayland_proton">ๆญค Proton ไธๅŒ…ๅซ winewayland ๆˆ– Wayland Turnip ้ฉฑๅŠจ๏ผŒๅ› ๆญคไฝฟ็”จ X11ใ€‚่ฏทๅœจโ€œๅ†…ๅฎนโ€ไธญๅฎ‰่ฃ… Wayland Proton ๅฑ‚๏ผŒๅนถๅฐ†ๅ…ถ้€‰ไธบๅฎนๅ™จ็š„ Protonใ€‚</string> + <string name="wayland_unavailable_fallback">ๆญคๆธธๆˆๆ— ๆณ•ไฝฟ็”จ Wayland๏ผŒๅฐ†ๆ”น็”จ X11 ๅฏๅŠจใ€‚</string> + <string name="wayland_needs_turnip_driver">Wayland ้œ€่ฆ Turnip ๅ›พๅฝข้ฉฑๅŠจๆ‰่ƒฝๆ˜พ็คบๆธธๆˆใ€‚่ฏทๅœจโ€œๅ†…ๅฎนโ€ไธญๅฎ‰่ฃ…ๅนถๅฐ†ๅ…ถ้€‰ไธบๅ›พๅฝข้ฉฑๅŠจใ€‚</string> + <string name="containers_gamescope_new">ๅˆ›ๅปบ GameScope ๅฎนๅ™จ</string> + <string name="containers_gamescope_create_failed">ๆ— ๆณ•ๅˆ›ๅปบ GameScope ๅฎนๅ™จ</string> + <string name="linux_apps_need_gamescope_container">่ฏทๅ…ˆๅœจโ€œ่ฎพ็ฝฎ โ€บ ๅฎนๅ™จโ€ไธญๅˆ›ๅปบ GameScope ๅฎนๅ™จใ€‚</string> + <string name="linux_runtime_missing">Linux ่ฟ่กŒๆ—ถๅฐšๆœชๅฎ‰่ฃ…๏ผŒๅ› ๆญคๆญคๅฎนๅ™จๆš‚ๆ—ถๆ— ๆณ•ๅฏๅŠจใ€‚่ฏทๅœจ ่ฎพ็ฝฎ > ๅ•†ๅบ— ไธญ้€š่ฟ‡ Linux ๅฎขๆˆท็ซฏ ๅฎ‰่ฃ…ใ€‚</string> + <string name="container_display_server_gamescope">Gamescope ๅง‹็ปˆ้€š่ฟ‡ Wayland ๅˆๆˆๅ™จ็ป˜ๅˆถใ€‚</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">ๅ†…็ฝฎไบŽ Linux ่ฟ่กŒ็Žฏๅขƒใ€‚ไธ‹่ฝฝ็š„ Android ้ฉฑๅŠจไป…้€‚็”จไบŽ Wine ๅ’Œ Proton ๅฎนๅ™จใ€‚</string> + <string name="linux_runtime_windows_program">่ฟ™ๆ˜ฏไธ€ไธช Windows ็จ‹ๅบใ€‚่ฏทๅœจๅฟซๆทๆ–นๅผ่ฎพ็ฝฎไธญๅฐ†ๅ…ถ็งปๅˆฐ Wine ๅฎนๅ™จๅŽๅ†ๅฏๅŠจใ€‚</string> + <string name="settings_content_badge_online">่”ๆœบ</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">้€‰ๆ‹ฉ้ฉฑๅŠจ็จ‹ๅบ็š„ๅฎ‰่ฃ…ไฝ็ฝฎใ€‚ๅทฒ่ฏ†ๅˆซ็š„่ฝฏไปถๅŒ…ไผš่‡ชๅŠจไฝฟ็”จๆญฃ็กฎ็š„ๅนณๅฐใ€‚</string> + <string name="settings_drivers_restore_defaults">ๆขๅค้ป˜่ฎค่ฎพ็ฝฎ</string> + <string name="settings_drivers_defaults_present">้ป˜่ฎคไป“ๅบ“ๅทฒๅญ˜ๅœจ</string> + <string name="settings_drivers_active">ๆญฃๅœจไฝฟ็”จ</string> + <string name="settings_drivers_bundled">ๅ†…็ฝฎ Mesa</string> + <string name="settings_drivers_legacy">Linux ๅฎขๆˆท็ซฏ้ฉฑๅŠจ็จ‹ๅบ</string> + <string name="settings_drivers_routed">ๅทฒ่ฏ†ๅˆซ้ฉฑๅŠจ็จ‹ๅบๅนถๅฎ‰่ฃ…ๅˆฐ %1$sใ€‚</string> + <string name="linux_child_processes_message">Android ไผš้™ๅˆถๅบ”็”จ็š„ๅŽๅฐ่ฟ›็จ‹ๆ•ฐ้‡๏ผŒ่€Œ Linux ไผš่ฏ่ฟœ่ถ…ๆญค้™ๅˆถ๏ผšSteam ๆˆ–ๆธธๆˆๅฏ่ƒฝๅœจๆธธ็Žฉๆ—ถ่ขซ็ปˆๆญขใ€‚่ฏทๅœจๅผ€ๅ‘่€…้€‰้กนไธญๆ‰“ๅผ€โ€œ็ฆ็”จๅญ่ฟ›็จ‹้™ๅˆถโ€ใ€‚็Žฐๅœจๆ‰“ๅผ€ๅ—๏ผŸ</string> + <string name="linux_child_processes_adb">Android ไผš้™ๅˆถๅบ”็”จ็š„ๅŽๅฐ่ฟ›็จ‹ๆ•ฐ้‡๏ผŒ่€Œ Linux ไผš่ฏ่ฟœ่ถ…ๆญค้™ๅˆถ๏ผšSteam ๆˆ–ๆธธๆˆๅฏ่ƒฝๅœจๆธธ็Žฉๆ—ถ่ขซ็ปˆๆญขใ€‚ๆญค Android ็‰ˆๆœฌๆฒกๆœ‰่ฏฅๅผ€ๅ…ณใ€‚่ฏทๅœจ็”ต่„‘ไธŠ่ฟ่กŒ๏ผš\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8866d87f8..2a8698a46 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -15,6 +15,8 @@ <string name="common_ui_revert_icon">้‚„ๅŽŸๅœ–็คบ</string> <string name="common_ui_select_exe">้ธๆ“‡ EXE</string> <string name="common_ui_select_valid_exe_file">่ซ‹้ธๆ“‡ๆœ‰ๆ•ˆ็š„ .exe ๆช”ๆกˆ</string> + <string name="common_ui_select_executable">้ธๆ“‡ๅฏๅŸท่กŒๆช”</string> + <string name="common_ui_select_valid_executable">่ซ‹้ธๆ“‡ Windows ๆˆ– Linux ๅฏๅŸท่กŒๆช”</string> <string name="common_ui_select_valid_icon_file">่ซ‹้ธๆ“‡ๆœ‰ๆ•ˆ็š„ .ico ๆˆ– .png ๆช”ๆกˆใ€‚</string> <string name="common_ui_exit">้›ข้–‹</string> <string name="common_ui_exit_app">้›ข้–‹ๆ‡‰็”จ็จ‹ๅผ</string> @@ -149,6 +151,16 @@ <string name="common_ui_enter_path">่ผธๅ…ฅ่ทฏๅพ‘</string> <string name="common_ui_path_not_found">ๆ‰พไธๅˆฐ่ทฏๅพ‘</string> <string name="common_ui_storage_roots">ๅ„ฒๅญ˜ๆ น็›ฎ้Œ„</string> + <string name="common_ui_app_storage">ๆ‡‰็”จ็จ‹ๅผๅ„ฒๅญ˜็ฉบ้–“</string> + <string name="common_ui_move">็งปๅ‹•</string> + <string name="library_games_move_to_app_storage_title">็งป่‡ณๅ…ง้ƒจ</string> + <string name="library_games_move_to_download_folder_title">็งป่‡ณๅค–้ƒจ</string> + <string name="library_games_move_to_app_storage_confirm">ๅฐ‡ %1$s ็งป่‡ณๅ…ง้ƒจๅ„ฒๅญ˜็ฉบ้–“๏ผŸ</string> + <string name="library_games_move_to_download_folder_confirm">ๅฐ‡ %1$s ็งป่‡ณๅค–้ƒจๅ„ฒๅญ˜็ฉบ้–“๏ผŸ</string> + <string name="library_games_moving">ๆญฃๅœจ็งปๅ‹•้Šๆˆฒ</string> + <string name="library_games_move_done">ๅทฒ็งปๅ‹• %1$s</string> + <string name="library_games_move_failed">็„กๆณ•็งปๅ‹• %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">็›ดๆŽฅ็€่ฆฝๆœฌๆฉŸ่ณ‡ๆ–™ๅคพ</string> <string name="common_ui_no_folders_available_here">้€™่ฃกๆฒ’ๆœ‰ๅฏ็”จ็š„่ณ‡ๆ–™ๅคพ</string> <string name="common_ui_grant_all_files_access_browse">่ซ‹ๆŽˆไบˆๆ‰€ๆœ‰ๆช”ๆกˆๅญ˜ๅ–ๆฌŠ้™๏ผŒไปฅ็›ดๆŽฅ็€่ฆฝๆœฌๆฉŸ่ณ‡ๆ–™ๅคพใ€‚</string> @@ -226,11 +238,43 @@ <string name="hero_graphics_test_32">32 ไฝๅ…ƒ</string> <string name="hero_graphics_test_64">64 ไฝๅ…ƒ</string> <string name="hero_input_tests_title">่ผธๅ…ฅๆธฌ่ฉฆ</string> - <string name="library_games_add_custom_game">ๆ–ฐๅขž่‡ช่จ‚้Šๆˆฒ</string> <string name="library_games_game_name">้Šๆˆฒๅ็จฑ</string> <string name="library_games_game_folder_mapped_drive">้Šๆˆฒ่ณ‡ๆ–™ๅคพ๏ผˆๆ˜ ๅฐ„็š„้Šๆˆฒ็ฃ็ขŸๆฉŸ๏ผ‰</string> <string name="library_games_select_exe_provide_name">้ธๆ“‡ไธ€ๅ€‹ exe ไธฆๆไพ›ๅ็จฑ</string> <string name="library_games_sign_in_prompt">็™ปๅ…ฅๅ•†ๅบ—ๆˆ–ๆ–ฐๅขž่‡ช่จ‚้Šๆˆฒไพ†ๅปบ็ซ‹ๆ‚จ็š„้Šๆˆฒๅบซใ€‚</string> + <string name="library_games_add_title">ๆ–ฐๅขž้Šๆˆฒๆˆ–ๆ‡‰็”จ็จ‹ๅผ</string> + <string name="library_games_add_linux_steam">ๆ–ฐๅขž Steam ็”จๆˆถ็ซฏ</string> + <string name="library_games_type">้กžๅž‹</string> + <string name="library_games_type_game">้Šๆˆฒ</string> + <string name="library_games_type_app">ๆ‡‰็”จ็จ‹ๅผ</string> + <string name="library_games_select_executable_or_rom">้ธๆ“‡ๅฏๅŸท่กŒๆช”ๆˆ–ไธปๆฉŸ ROM</string> + <string name="library_games_added">ๅทฒๆ–ฐๅขž %1$s</string> + <string name="library_games_add_failed">็„กๆณ•ๆ–ฐๅขž %1$s</string> + <string name="linux_runtime_not_installed">Linux ๅŸท่กŒ็’ฐๅขƒๅฐšๆœชๅฎ‰่ฃ๏ผŒๅ› ๆญค็„กๆณ•ๅ•Ÿๅ‹• %1$sใ€‚่ซ‹ๅœจ ่จญๅฎš > ๅ•†ๅบ— ไธญ้€้Ž Linux ็”จๆˆถ็ซฏ ๅฎ‰่ฃใ€‚</string> + <string name="linux_client_button">Linux ็”จๆˆถ็ซฏ</string> + <string name="linux_client_title">Linux Steam ็”จๆˆถ็ซฏ</string> + <string name="linux_client_install_message">ๅพž WinNative ไธ‹่ผ‰ Linux ๅŸท่กŒ็’ฐๅขƒ๏ผŒๅพž Valve ไธ‹่ผ‰ Steam ็”จๆˆถ็ซฏ๏ผŒ็„ถๅพŒๅฎ‰่ฃๅ…ฉ่€…ใ€‚ไธ‹่ผ‰่ถ…้Ž 2 GB๏ผŒ่ซ‹ไฝฟ็”จ Wi-Fiใ€‚</string> + <string name="linux_client_installed_message">Linux ๅŸท่กŒ็’ฐๅขƒๅ’Œ Steam ็”จๆˆถ็ซฏๅทฒๅฎ‰่ฃ๏ผŒSteam ๅทฒๅœจ้Šๆˆฒๅบซไธญใ€‚</string> + <string name="linux_client_failed_message">ๅฎ‰่ฃๆœชๅฎŒๆˆใ€‚่ซ‹ๆชขๆŸฅ็ถฒ่ทฏ้€ฃ็ทšๅพŒๅ†่ฉฆไธ€ๆฌกใ€‚</string> + <string name="linux_client_no_space_message">ๅฏ็”จ็ฉบ้–“ไธ่ถณ๏ผš้œ€่ฆ %1$s๏ผŒๅฏ็”จ %2$sใ€‚</string> + <string name="linux_client_stage_connect">ๆญฃๅœจ้€ฃ็ทš</string> + <string name="linux_client_stage_download_runtime">ๆญฃๅœจไธ‹่ผ‰ Linux ๅŸท่กŒ็’ฐๅขƒ</string> + <string name="linux_client_stage_install_runtime">ๆญฃๅœจๅฎ‰่ฃ Linux ๅŸท่กŒ็’ฐๅขƒ</string> + <string name="linux_client_stage_download_proton">ๆญฃๅœจไธ‹่ผ‰ Proton</string> + <string name="linux_client_stage_install_proton">ๆญฃๅœจๅฎ‰่ฃ Proton</string> + <string name="linux_client_stage_download_steam">ๆญฃๅœจไธ‹่ผ‰ Steam ็”จๆˆถ็ซฏ</string> + <string name="linux_client_stage_install_steam">ๆญฃๅœจๅฎ‰่ฃ Steam ็”จๆˆถ็ซฏ</string> + <string name="linux_client_stage_library">ๆญฃๅœจๅฐ‡ Steam ๅŠ ๅ…ฅ้Šๆˆฒๅบซ</string> + <string name="linux_client_progress_amount">%1$s / %2$s</string> + <string name="linux_client_retry">้‡่ฉฆ</string> + <string name="linux_client_busy">Linux Steam ็”จๆˆถ็ซฏไปๅœจๅฎ‰่ฃใ€‚่ซ‹ๅœจๅฎŒๆˆๅพŒๅ†่ฉฆไธ€ๆฌกใ€‚</string> + <string name="linux_client_update_message">ๆœ‰่ผƒๆ–ฐ็š„ Linux ๅŸท่กŒ็’ฐๅขƒๅฏ็”จใ€‚ๆ›ดๆ–ฐๆœƒไฟ็•™ Steam ็™ปๅ…ฅใ€ๅทฒๅฎ‰่ฃ็š„้Šๆˆฒใ€ๅญ˜ๆช”ๅ’Œๅฎนๅ™จใ€‚</string> + <string name="linux_client_update_session_running">Linux ๅทฅไฝœ้šŽๆฎตๆญฃๅœจๅŸท่กŒใ€‚่ซ‹ๅ…ˆ็ตๆŸ๏ผŒ็„ถๅพŒๅ†ๆ›ดๆ–ฐใ€‚</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">Linux ๅทฅไฝœ้šŽๆฎตๅ‰›ๅ•Ÿๅ‹•ๅฐฑๅœๆญขไบ†ใ€‚ๆ—ฅ่ชŒไฝๆ–ผ ่จญๅฎš > ้™ค้Œฏ > ้–‹ๅ•Ÿๆ—ฅ่ชŒ็ฎก็†ๅ™จ๏ผ›่ซ‹ๅˆ†ไบซๆ—ฅ่ชŒไปฅไพฟๆŸฅๆ˜ŽๅŽŸๅ› ใ€‚</string> + <string name="linux_session_stopped">Linux ๅทฅไฝœ้šŽๆฎต่‡ช่กŒๅœๆญข๏ผˆ็‹€ๆ…‹ %1$d๏ผ‰ใ€‚ๆ—ฅ่ชŒไฝๆ–ผ ่จญๅฎš > ้™ค้Œฏ > ้–‹ๅ•Ÿๆ—ฅ่ชŒ็ฎก็†ๅ™จ๏ผ›่ซ‹ๅˆ†ไบซๆ—ฅ่ชŒไปฅไพฟๆŸฅๆ˜ŽๅŽŸๅ› ใ€‚</string> + <string name="linux_session_trimmed">Android ๅœๆญขไบ† Linux ๅทฅไฝœ้šŽๆฎตใ€‚้€™้€šๅธธ่กจ็คบๅทฒ้”ๅˆฐ่ƒŒๆ™ฏ็จ‹ๅบไธŠ้™๏ผš่ซ‹ๅœจ้–‹็™ผไบบๅ“ก้ธ้ …ไธญ้–‹ๅ•Ÿใ€Œๅœ็”จๅญ็จ‹ๅบ้™ๅˆถใ€๏ผŒ็„ถๅพŒๅ†่ฉฆไธ€ๆฌกใ€‚</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">ๅ•†ๅบ—</string> @@ -1065,6 +1109,10 @@ <string name="setup_wizard_title">่จญๅฎš็ฒพ้ˆ</string> <string name="setup_wizard_advanced_user">้€ฒ้šŽไฝฟ็”จ่€…</string> <string name="setup_wizard_next">ไธ‹ไธ€ๆญฅ</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">ๅฎŒๆˆ</string> <string name="setup_wizard_download_and_install">ไธ‹่ผ‰ + ๅฎ‰่ฃ</string> <string name="setup_wizard_open_drivers">้–‹ๅ•Ÿ้ฉ…ๅ‹•็จ‹ๅผ</string> @@ -2863,10 +2911,9 @@ <string name="update_apk_notice">ๆญคๆ›ดๆ–ฐๆœƒ่ฎŠๆ›ดๆ‡‰็”จ็จ‹ๅผๆœฌ่บซ๏ผŒๅ› ๆญค Android ๆœƒ่ฆๆฑ‚ไฝ ็ขบ่ชๅฎ‰่ฃใ€‚ไฝ ็š„้Šๆˆฒใ€ๅฎนๅ™จๅ’Œ่จญๅฎš้ƒฝๆœƒไฟ็•™ใ€‚</string> <string name="update_up_to_date">WinNative ๅทฒๆ˜ฏๆœ€ๆ–ฐ็‰ˆๆœฌใ€‚</string> <string name="update_unsupported_build">ๆ‡‰็”จ็จ‹ๅผๅ…งๆ›ดๆ–ฐๅƒ…ๆไพ›็ตฆๅฎ˜ๆ–น็ฐฝ็ฝฒ็š„ๆญฃๅผ็‰ˆๆœฌใ€‚</string> - <string name="settings_general_update_channel">ๆ›ดๆ–ฐ็ฎก้“</string> - <string name="settings_general_update_channel_summary">ใ€Œๅฎ˜ๆ–นใ€็™ผๅธƒ็ถ“้Žๆธฌ่ฉฆ็š„็‰ˆๆœฌใ€‚ใ€Œ้–‹็™ผใ€็™ผๅธƒๆ—ฉๆœŸๅปบ็ฝฎใ€‚</string> + <string name="settings_general_update_source">ๆ›ดๆ–ฐไพ†ๆบ</string> + <string name="settings_general_update_source_summary">ไพ†่‡ชๆๅ–่ซ‹ๆฑ‚็š„็ต„ๅปบๆœƒ่ทŸ้šจ่ฉฒๆๅ–่ซ‹ๆฑ‚๏ผ›ๅฎ˜ๆ–นๅฎ‰่ฃๅ‰‡่ทŸ้šจๅฎ˜ๆ–น็‰ˆๆœฌใ€‚</string> <string name="update_channel_official">ๅฎ˜ๆ–น</string> - <string name="update_channel_development">้–‹็™ผ</string> <string name="networking_section_title">็ถฒ่ทฏ</string> <string name="networking_driver">็ถฒ่ทฏ้ฉ…ๅ‹•็จ‹ๅผ</string> <string name="networking_driver_winnative">WinNative ็ถฒ่ทฏ้ฉ…ๅ‹•็จ‹ๅผ</string> @@ -2879,5 +2926,36 @@ <string name="steam_controller_left_pad_click">ๅทฆ่งธๆŽงๆฟๆŒ‰ไธ‹</string> <string name="steam_controller_right_pad_click">ๅณ่งธๆŽงๆฟๆŒ‰ไธ‹</string> <string name="controller_test_generic">้€š็”จ</string> + <string name="container_display_server">้กฏ็คบไผบๆœๅ™จ</string> + <string name="container_display_server_help">ๅฏฆ้ฉ—ๆ€งๅŠŸ่ƒฝใ€‚ๅทฅไฝœ้šŽๆฎต้€้Žๅ…งๅปบ็š„ Wayland ๅˆๆˆๅ™จ็นช่ฃฝ๏ผŒ่€Œไธๆ˜ฏ X ไผบๆœๅ™จใ€‚</string> + <string name="container_display_server_wayland_requirements">Wayland ้œ€่ฆ Adreno GPU ไปฅๅŠๅŒ…ๅซ winewayland ๅ’Œ Wayland Turnip ้ฉ…ๅ‹•็จ‹ๅผ็š„ Wayland ็›ธๅฎน Protonใ€‚่ซ‹ๅœจใ€Œๅ…งๅฎนใ€ไธญๅฎ‰่ฃใ€‚</string> + <string name="container_display_server_needs_wayland_proton">ๆญค Proton ไธๅŒ…ๅซ winewayland ๆˆ– Wayland Turnip ้ฉ…ๅ‹•็จ‹ๅผ๏ผŒๅ› ๆญคไฝฟ็”จ X11ใ€‚่ซ‹ๅœจใ€Œๅ…งๅฎนใ€ไธญๅฎ‰่ฃ Wayland Proton ๅฑค๏ผŒไธฆๅฐ‡ๅ…ถ้ธ็‚บๅฎนๅ™จ็š„ Protonใ€‚</string> + <string name="wayland_unavailable_fallback">ๆญค้Šๆˆฒ็„กๆณ•ไฝฟ็”จ Wayland๏ผŒๅฐ‡ๆ”น็”จ X11 ๅ•Ÿๅ‹•ใ€‚</string> + <string name="wayland_needs_turnip_driver">Wayland ้œ€่ฆ Turnip ๅœ–ๅฝข้ฉ…ๅ‹•็จ‹ๅผๆ‰่ƒฝ้กฏ็คบ้Šๆˆฒใ€‚่ซ‹ๅœจใ€Œๅ…งๅฎนใ€ไธญๅฎ‰่ฃไธฆๅฐ‡ๅ…ถ้ธ็‚บๅœ–ๅฝข้ฉ…ๅ‹•็จ‹ๅผใ€‚</string> + <string name="containers_gamescope_new">ๅปบ็ซ‹ GameScope ๅฎนๅ™จ</string> + <string name="containers_gamescope_create_failed">็„กๆณ•ๅปบ็ซ‹ GameScope ๅฎนๅ™จ</string> + <string name="linux_apps_need_gamescope_container">่ซ‹ๅ…ˆๅœจใ€Œ่จญๅฎš โ€บ ๅฎนๅ™จใ€ไธญๅปบ็ซ‹ GameScope ๅฎนๅ™จใ€‚</string> + <string name="linux_runtime_missing">Linux ๅŸท่กŒ็’ฐๅขƒๅฐšๆœชๅฎ‰่ฃ๏ผŒๅ› ๆญคๆญคๅฎนๅ™จๆšซๆ™‚็„กๆณ•ๅ•Ÿๅ‹•ใ€‚่ซ‹ๅœจ ่จญๅฎš > ๅ•†ๅบ— ไธญ้€้Ž Linux ็”จๆˆถ็ซฏ ๅฎ‰่ฃใ€‚</string> + <string name="container_display_server_gamescope">Gamescope ไธ€ๅพ‹้€้Ž Wayland ๅˆๆˆๅ™จ็นช่ฃฝใ€‚</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">ๅ…งๅปบๆ–ผ Linux ๅŸท่กŒ็’ฐๅขƒใ€‚ไธ‹่ผ‰็š„ Android ้ฉ…ๅ‹•็จ‹ๅผๅƒ…้ฉ็”จๆ–ผ Wine ๅ’Œ Proton ๅฎนๅ™จใ€‚</string> + <string name="linux_runtime_windows_program">้€™ๆ˜ฏ Windows ็จ‹ๅผใ€‚่ซ‹ๅœจๆทๅพ‘่จญๅฎšไธญๅฐ‡ๅฎƒ็งป่‡ณ Wine ๅฎนๅ™จๅพŒๅ†ๅ•Ÿๅ‹•ใ€‚</string> + <string name="settings_content_badge_online">้€ฃ็ทš</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">้ธๆ“‡้ฉ…ๅ‹•็จ‹ๅผ็š„ๅฎ‰่ฃไฝ็ฝฎใ€‚ๅทฒ่พจ่ญ˜็š„ๅฅ—ไปถๆœƒ่‡ชๅ‹•ไฝฟ็”จๆญฃ็ขบ็š„ๅนณๅฐใ€‚</string> + <string name="settings_drivers_restore_defaults">้‚„ๅŽŸ้ ่จญๅ€ผ</string> + <string name="settings_drivers_defaults_present">้ ่จญๅ„ฒๅญ˜ๅบซๅทฒๅญ˜ๅœจ</string> + <string name="settings_drivers_active">ไฝฟ็”จไธญ</string> + <string name="settings_drivers_bundled">ๅ…งๅปบ Mesa</string> + <string name="settings_drivers_legacy">Linux ็”จๆˆถ็ซฏ้ฉ…ๅ‹•็จ‹ๅผ</string> + <string name="settings_drivers_routed">ๅทฒ่พจ่ญ˜้ฉ…ๅ‹•็จ‹ๅผไธฆๅฎ‰่ฃ่‡ณ %1$sใ€‚</string> + <string name="linux_child_processes_message">Android ๆœƒ้™ๅˆถๆ‡‰็”จ็จ‹ๅผ็š„่ƒŒๆ™ฏ็จ‹ๅบๆ•ธ้‡๏ผŒ่€Œ Linux ๅทฅไฝœ้šŽๆฎต้ ่ถ…ๆญค้™ๅˆถ๏ผšSteam ๆˆ–้Šๆˆฒๅฏ่ƒฝๅœจ้Š็Žฉๆ™‚่ขซ็ต‚ๆญขใ€‚่ซ‹ๅœจ้–‹็™ผไบบๅ“ก้ธ้ …ไธญ้–‹ๅ•Ÿใ€Œๅœ็”จๅญ็จ‹ๅบ้™ๅˆถใ€ใ€‚็พๅœจ้–‹ๅ•Ÿๅ—Ž๏ผŸ</string> + <string name="linux_child_processes_adb">Android ๆœƒ้™ๅˆถๆ‡‰็”จ็จ‹ๅผ็š„่ƒŒๆ™ฏ็จ‹ๅบๆ•ธ้‡๏ผŒ่€Œ Linux ๅทฅไฝœ้šŽๆฎต้ ่ถ…ๆญค้™ๅˆถ๏ผšSteam ๆˆ–้Šๆˆฒๅฏ่ƒฝๅœจ้Š็Žฉๆ™‚่ขซ็ต‚ๆญขใ€‚ๆญค Android ็‰ˆๆœฌๆฒ’ๆœ‰่ฉฒ้–‹้—œใ€‚่ซ‹ๅœจ้›ป่…ฆไธŠๅŸท่กŒ๏ผš\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20af0f982..a1a17483e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,6 +19,8 @@ <string name="common_ui_enter_path">Enter Path</string> <string name="common_ui_path_not_found">Path not found</string> <string name="common_ui_select_valid_exe_file">Please select a valid .exe file</string> + <string name="common_ui_select_executable">Select Executable</string> + <string name="common_ui_select_valid_executable">Select a Windows or Linux executable</string> <string name="common_ui_select_valid_icon_file">Please select a valid .ico or .png file.</string> <string name="common_ui_exit">Exit</string> <string name="common_ui_exit_app">Exit App</string> @@ -150,6 +152,16 @@ <string name="store_game_branch_switch_installed">Version set to %1$s. Check for Update to switch the installed files.</string> <string name="common_ui_select">Select</string> <string name="common_ui_storage_roots">Storage Roots</string> + <string name="common_ui_app_storage">App storage</string> + <string name="common_ui_move">Move</string> + <string name="library_games_move_to_app_storage_title">Move to Internal</string> + <string name="library_games_move_to_download_folder_title">Move to External</string> + <string name="library_games_move_to_app_storage_confirm">Move %1$s to internal storage?</string> + <string name="library_games_move_to_download_folder_confirm">Move %1$s to external storage?</string> + <string name="library_games_moving">Moving game</string> + <string name="library_games_move_done">%1$s moved</string> + <string name="library_games_move_failed">Could not move %1$s</string> + <string name="containers_wine_proton_section">Wine/Proton</string> <string name="common_ui_browse_local_folders_directly">Browse local folders directly</string> <string name="common_ui_no_folders_available_here">No folders available here</string> <string name="common_ui_grant_all_files_access_browse">Grant all file access to browse folders directly.</string> @@ -228,11 +240,43 @@ <string name="hero_graphics_test_32">32-bit</string> <string name="hero_graphics_test_64">64-bit</string> <string name="hero_input_tests_title">Input Tests</string> - <string name="library_games_add_custom_game">Add Custom Game</string> <string name="library_games_game_name">Game Name</string> <string name="library_games_game_folder_mapped_drive">Game Folder (Mapped Game Drive)</string> <string name="library_games_select_exe_provide_name">Select an exe and provide a name</string> <string name="library_games_sign_in_prompt">Sign into a store or add a custom game to build your library.</string> + <string name="library_games_add_title">Add Game or App</string> + <string name="library_games_add_linux_steam">Add Steam Client</string> + <string name="library_games_type">Type</string> + <string name="library_games_type_game">Game</string> + <string name="library_games_type_app">App</string> + <string name="library_games_select_executable_or_rom">Select an executable or console ROM</string> + <string name="library_games_added">%1$s added</string> + <string name="library_games_add_failed">Could not add %1$s</string> + <string name="linux_runtime_not_installed">The Linux runtime is not installed yet, so %1$s cannot be started. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_client_button">Linux Client</string> + <string name="linux_client_title">Linux Steam Client</string> + <string name="linux_client_install_message">Downloads the Linux runtime from WinNative and the Steam client from Valve, then installs both. The download is over 2 GB, so use Wi-Fi.</string> + <string name="linux_client_installed_message">The Linux runtime and the Steam client are installed, and Steam is in the Library.</string> + <string name="linux_client_failed_message">The install did not finish. Check your connection and try again.</string> + <string name="linux_client_no_space_message">Not enough free space: %1$s is needed and %2$s is available.</string> + <string name="linux_client_stage_connect">Connecting</string> + <string name="linux_client_stage_download_runtime">Downloading the Linux runtime</string> + <string name="linux_client_stage_install_runtime">Installing the Linux runtime</string> + <string name="linux_client_stage_download_proton">Downloading Proton</string> + <string name="linux_client_stage_install_proton">Installing Proton</string> + <string name="linux_client_stage_download_steam">Downloading the Steam client</string> + <string name="linux_client_stage_install_steam">Installing the Steam client</string> + <string name="linux_client_stage_library">Adding Steam to the Library</string> + <string name="linux_client_progress_amount">%1$s of %2$s</string> + <string name="linux_client_retry">Retry</string> + <string name="linux_client_busy">The Linux Steam Client is still installing. Try again when it finishes.</string> + <string name="linux_client_update_message">A newer Linux runtime is available. Updating keeps your Steam sign-in, installed games, saves and containers.</string> + <string name="linux_client_update_session_running">A Linux session is running. Exit it, then update.</string> + <string name="linux_client_gpu_unsupported">The Linux client draws with the Turnip Vulkan driver, which needs an Adreno GPU. This device has a different GPU.</string> + <string name="linux_client_driver_missing">The display driver of the Linux client is not installed, so the screen would stay black. Install it with Linux Client in Settings > Stores.</string> + <string name="linux_session_failed">The Linux session stopped as soon as it started. Its log is under Settings > Debug > Open Logs Manager; share it so the cause can be found.</string> + <string name="linux_session_stopped">The Linux session stopped on its own (status %1$d). Its log is under Settings > Debug > Open Logs Manager; share it so the cause can be found.</string> + <string name="linux_session_trimmed">Android stopped the Linux session. This usually means its limit on background processes was reached: turn on โ€œDisable child process restrictionsโ€ in Developer options and try again.</string> <!-- Stores > Accounts --> <string name="stores_accounts_title">Stores</string> @@ -1557,6 +1601,10 @@ Installed path: <string name="setup_wizard_title">Setup Wizard</string> <string name="setup_wizard_advanced_user">Advanced User</string> <string name="setup_wizard_next">Next</string> + <string name="setup_wizard_steam_client">Steam Client</string> + <string name="setup_wizard_windows_steam_client">Windows Steam Client</string> + <string name="setup_wizard_windows_steam_summary">The Windows Steam Client runs through Wine or Proton; its components and container are set up on the next pages.</string> + <string name="setup_wizard_linux_client_continues">The install carries on while you finish the setup, and shows under Linux Client in Settings > Stores.</string> <string name="setup_wizard_finish">Finish</string> <string name="setup_wizard_download_and_install">Download + Install</string> <string name="setup_wizard_open_drivers">Open Drivers</string> @@ -2791,10 +2839,9 @@ Installed path: <string name="update_apk_notice">This update changes the app itself, so Android will ask you to confirm the install. Your games, containers and settings are kept.</string> <string name="update_up_to_date">WinNative is up to date.</string> <string name="update_unsupported_build">In-app updates are only offered on official signed releases.</string> - <string name="settings_general_update_channel">Update Channel</string> - <string name="settings_general_update_channel_summary">Official ships tested releases. Development ships early builds.</string> + <string name="settings_general_update_source">Update Source</string> + <string name="settings_general_update_source_summary">A build from a pull request follows that pull request; an official install follows the official releases.</string> <string name="update_channel_official">Official</string> - <string name="update_channel_development">Development</string> <string name="networking_section_title">Networking</string> <string name="networking_driver">Network driver</string> <string name="networking_driver_winnative">WinNative Network Driver</string> @@ -2892,4 +2939,39 @@ Installed path: <string name="steam_controller_left_pad_click">Left pad click</string> <string name="steam_controller_right_pad_click">Right pad click</string> <string name="controller_test_generic">Generic</string> + <string name="container_display_server">Display Server</string> + <string name="container_display_server_help">Experimental. The session draws through the embedded Wayland compositor instead of the X server.</string> + <string name="container_display_server_wayland_requirements">Wayland needs an Adreno GPU and a Wayland-capable Proton with winewayland and the Wayland Turnip driver. Install one from Contents.</string> + <string name="container_display_server_needs_wayland_proton">This Proton has no winewayland or Wayland Turnip driver, so X11 is used. Install a Wayland Proton layer from Contents and select it as the container\'s Proton.</string> + <string name="wayland_unavailable_fallback">Wayland is not available for this game. Launching on X11.</string> + <string name="wayland_needs_turnip_driver">Wayland needs a Turnip graphics driver to show the game. Install one under Contents and select it as the graphics driver.</string> + <string name="containers_gamescope_section" translatable="false">GameScope</string> + <string name="containers_gamescope_new">Create GameScope container</string> + <string name="containers_gamescope_create_failed">Failed to create the GameScope container</string> + <string name="linux_apps_need_gamescope_container">Create the GameScope container under Settings โ€บ Containers first.</string> + <string name="linux_runtime_missing">The Linux runtime is not installed, so this container cannot start yet. Install it with Linux Client in Settings > Stores.</string> + <string name="container_display_server_gamescope">Gamescope always draws through the Wayland compositor.</string> + <string name="container_graphics_driver_gamescope">Turnip %1$s (Linux ARM64)</string> + <string name="container_graphics_driver_gamescope_help">Built into the Linux runtime. Downloaded Android drivers apply to Wine and Proton containers only.</string> + <string name="linux_runtime_windows_program">This is a Windows program. Move it to a Wine container in Shortcut Settings to start it.</string> + <string name="display_server_x11" translatable="false">X11</string> + <string name="display_server_wayland" translatable="false">Wayland</string> + <string name="settings_content_badge_online">Online</string> + <string name="gamescope_fex_title">FEX (64-bit and 32-bit)</string> + <string name="gamescope_fex_preset">FEX Preset</string> + <string name="gamescope_fex_summary">Games here run on the FEX that Steam\'s Proton (ARM64) carries, for 64-bit and 32-bit titles alike. The FEXCore, Box64 and WoWBox64 builds installed in WinNative are not used; the preset is.</string> + <string name="gamescope_proton_title">Proton</string> + <string name="gamescope_proton_summary">Games here start with this Proton. WN Proton Experimental comes with WinNative, and the builds downloaded under Components, Linux are listed with it. A game\'s own choice stays in step with its Compatibility setting in Steam, whichever was changed last.</string> + <string name="gamescope_proton_default" translatable="false">WN Proton Experimental</string> + <string name="settings_drivers_android">Android</string> + <string name="settings_drivers_linux">Linux</string> + <string name="settings_drivers_platform_hint">Choose where to install the driver. Recognized packages automatically use the correct platform.</string> + <string name="settings_drivers_restore_defaults">Restore defaults</string> + <string name="settings_drivers_defaults_present">Default repositories already present</string> + <string name="settings_drivers_active">Active</string> + <string name="settings_drivers_bundled">Bundled Mesa</string> + <string name="settings_drivers_legacy">Linux Client driver</string> + <string name="settings_drivers_routed">Driver identified and installed for %1$s.</string> + <string name="linux_child_processes_message">Android limits how many background processes an app may have, and a Linux session runs far more than that: Steam or a game can be killed while you play. Turn on โ€œDisable child process restrictionsโ€ in Developer options. Open it now?</string> + <string name="linux_child_processes_adb">Android limits how many background processes an app may have, and a Linux session runs far more than that: Steam or a game can be killed while you play. This version of Android has no switch for it. From a computer, run:\n\nadb shell settings put global settings_enable_monitor_phantom_procs false</string> </resources> diff --git a/app/src/main/runtime/audio/directaudio/DirectAudioHost.kt b/app/src/main/runtime/audio/directaudio/DirectAudioHost.kt new file mode 100644 index 000000000..eb80e786b --- /dev/null +++ b/app/src/main/runtime/audio/directaudio/DirectAudioHost.kt @@ -0,0 +1,96 @@ +package com.winlator.cmod.runtime.audio.directaudio + +import android.content.Context +import android.util.Log +import com.winlator.cmod.runtime.display.environment.EnvironmentComponent +import com.winlator.cmod.shared.io.FileUtils +import java.io.File +import java.io.IOException +import java.util.zip.ZipInputStream + +/** + * DirectAudio for a Linux session. Valve's Proton is a glibc program and cannot load Android's + * AAudio, so the driver built for it (tools/linuxfs/directaudio) has the app open each stream + * and exchanges the PCM with it through shared memory (cpp/wnaudiohook/wn_aaudio_host.c). + * PulseAudio keeps running beside it for everything that is not a Windows game. + */ +class DirectAudioHost( + private val socket: File, + private val allowCapture: Boolean, +) : EnvironmentComponent() { + override fun start() { + val dir = socket.parentFile + if (dir != null && !dir.isDirectory && !dir.mkdirs()) { + Log.w(TAG, "Could not create $dir") + return + } + if (!nativeStart(socket.path, allowCapture)) Log.w(TAG, "DirectAudio host did not start") + } + + override fun stop() { + nativeStop() + socket.delete() + } + + companion object { + private const val TAG = "DirectAudioHost" + const val SOCKET_PATH = "/usr/tmp/.directaudio/DA0" + const val ENV_SOCKET = "WN_DIRECTAUDIO_SOCKET" + + /** Tells the session script to point the client's Proton prefixes at the driver. */ + const val ENV_ENABLED = "WN_DIRECTAUDIO" + + /** Where the session script finds the driver, under the runtime's root. */ + private const val STAGE_DIR = "usr/local/share/winnative/directaudio" + + /** + * Valve's Proton 11 walks the same mmdevapi call table as the wine11 build. The page size + * a build is for only shapes its unixlib, which is not taken from here. + */ + private const val PE_ARCHIVE = "directaudio/directaudio-wine11-arm64ec-sdk28.zip" + private const val UNIXLIB_ASSET = "directaudio/linux/winedirectaudio.so" + private val PE_HALVES = listOf("aarch64-windows/winedirectaudio.drv", "i386-windows/winedirectaudio.drv") + + init { + System.loadLibrary("wnaudiohost") + } + + @JvmStatic + private external fun nativeStart(socketPath: String, allowCapture: Boolean): Boolean + + @JvmStatic + private external fun nativeStop() + + /** + * Worker thread. Puts the driver where the session script picks it up: the unixlib built + * for the runtime, and the two PE halves out of the upstream release, which hold no + * platform code. Each lands through a rename, so a session never sees half a file. + */ + @JvmStatic + @Throws(IOException::class) + fun stage(context: Context, runtimeRoot: File) { + val stage = File(runtimeRoot, STAGE_DIR) + val unixlib = File(stage, "aarch64-unix/winedirectaudio.so") + context.assets.open(UNIXLIB_ASSET).use { place(it.readBytes(), unixlib) } + val wanted = PE_HALVES.toMutableSet() + ZipInputStream(context.assets.open(PE_ARCHIVE)).use { zip -> + generateSequence { zip.nextEntry }.forEach { entry -> + if (wanted.remove(entry.name)) place(zip.readBytes(), File(stage, entry.name)) + } + } + if (wanted.isNotEmpty()) throw IOException("The DirectAudio release holds no $wanted") + } + + private fun place(bytes: ByteArray, target: File) { + val dir = target.parentFile + if (dir != null && !dir.isDirectory && !dir.mkdirs()) throw IOException("Could not create $dir") + val staged = File(dir, target.name + ".staged") + try { + staged.writeBytes(bytes) + if (!staged.renameTo(target)) throw IOException("Could not place ${target.path}") + } finally { + FileUtils.delete(staged) + } + } + } +} diff --git a/app/src/main/runtime/container/Container.java b/app/src/main/runtime/container/Container.java index 62769548e..ad5baa93e 100644 --- a/app/src/main/runtime/container/Container.java +++ b/app/src/main/runtime/container/Container.java @@ -164,6 +164,56 @@ public void setZinkMode(String zinkMode) { putExtra("zinkMode", zinkMode); } + /** Display server the session runs on: the X server (default) or the embedded Wayland compositor. */ + public static final String EXTRA_DISPLAY_BACKEND = "displayBackend"; + public static final String DISPLAY_BACKEND_X11 = "x11"; + public static final String DISPLAY_BACKEND_WAYLAND = "wayland"; + + public String getDisplayBackend() { + String value = getExtra(EXTRA_DISPLAY_BACKEND, DISPLAY_BACKEND_X11); + return DISPLAY_BACKEND_WAYLAND.equals(value) ? DISPLAY_BACKEND_WAYLAND : DISPLAY_BACKEND_X11; + } + + public void setDisplayBackend(String value) { + putExtra(EXTRA_DISPLAY_BACKEND, DISPLAY_BACKEND_WAYLAND.equals(value) ? DISPLAY_BACKEND_WAYLAND : null); + } + + public boolean isWaylandBackend() { + return DISPLAY_BACKEND_WAYLAND.equals(getDisplayBackend()); + } + + /** What the container boots: Wine on the bionic imagefs (default) or gamescope in the Linux runtime. */ + public static final String EXTRA_RUNTIME = "runtime"; + public static final String RUNTIME_WINE = "wine"; + public static final String RUNTIME_GAMESCOPE = "gamescope"; + + public String getRuntime() { + String value = getExtra(EXTRA_RUNTIME, RUNTIME_WINE); + return RUNTIME_GAMESCOPE.equals(value) ? RUNTIME_GAMESCOPE : RUNTIME_WINE; + } + + public void setRuntime(String value) { + putExtra(EXTRA_RUNTIME, RUNTIME_GAMESCOPE.equals(value) ? RUNTIME_GAMESCOPE : null); + } + + public boolean isGamescopeRuntime() { + return RUNTIME_GAMESCOPE.equals(getRuntime()); + } + + /** The Proton a GameScope container's games run under, by its compatibilitytools.d name. */ + public static final String EXTRA_LINUX_PROTON = "linuxProton"; + /** WinNative's own Proton Experimental, the tool every title is mapped to until the user picks another. */ + public static final String LINUX_PROTON_DEFAULT = "winnative-proton"; + + public String getLinuxProton() { + String value = getExtra(EXTRA_LINUX_PROTON, ""); + return value.isEmpty() ? LINUX_PROTON_DEFAULT : value; + } + + public void setLinuxProton(String value) { + putExtra(EXTRA_LINUX_PROTON, value == null || value.isEmpty() || LINUX_PROTON_DEFAULT.equals(value) ? null : value); + } + public String getGraphicsDriverConfig() { return this.graphicsDriverConfig; } public void setGraphicsDriverConfig(String graphicsDriverConfig) { this.graphicsDriverConfig = graphicsDriverConfig; } diff --git a/app/src/main/runtime/container/ContainerCreation.kt b/app/src/main/runtime/container/ContainerCreation.kt index 6d7dc9350..49b821a90 100644 --- a/app/src/main/runtime/container/ContainerCreation.kt +++ b/app/src/main/runtime/container/ContainerCreation.kt @@ -1,6 +1,9 @@ package com.winlator.cmod.runtime.container import android.content.Context +import android.os.Handler +import android.os.Looper +import com.winlator.cmod.feature.library.LinuxApps import com.winlator.cmod.runtime.compat.box64.Box64Preset import com.winlator.cmod.runtime.compat.fexcore.FEXCorePreset import com.winlator.cmod.runtime.content.ContentProfile @@ -10,10 +13,12 @@ import com.winlator.cmod.runtime.wine.WineInfo import com.winlator.cmod.runtime.wine.WineThemeManager import com.winlator.cmod.runtime.wine.WineUtils import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.shared.ui.widget.EnvVarsView import com.winlator.cmod.shared.util.Callback import org.json.JSONObject object ContainerCreation { + const val GAMESCOPE_CONTAINER_NAME = "GameScope" private const val WINE_DISPLAY_NAME = "Wine" private const val PROTON_DISPLAY_NAME = "Proton" private const val BOX64_EMULATOR = "box64" @@ -250,6 +255,65 @@ object ContainerCreation { } } + /** The newest installed Wine or Proton, which every container's prefix is built from. */ + @JvmStatic + fun newestInstalledRuntime(contentsManager: ContentsManager): ContentProfile? = + ( + contentsManager.getProfiles(ContentProfile.ContentType.CONTENT_TYPE_WINE).orEmpty() + + contentsManager.getProfiles(ContentProfile.ContentType.CONTENT_TYPE_PROTON).orEmpty() + ).filter { it.isInstalled } + .maxWithOrNull(compareBy<ContentProfile> { it.verCode }.thenBy { it.verName.lowercase() }) + + /** + * Creates the GameScope container: Linux programs and the native Steam client boot into it, + * so it always draws through Wayland and carries a Steam library entry. The callback gets null + * when creation fails. + */ + @JvmStatic + fun createGamescopeContainerAsync( + context: Context, + containerManager: ContainerManager, + contentsManager: ContentsManager, + runtime: ContentProfile?, + callback: Callback<Container?>, + ) { + val handler = Handler(Looper.getMainLooper()) + Thread { + val container = createGamescopeContainer(context, containerManager, contentsManager, runtime) + handler.post { callback.call(container) } + }.start() + } + + /** + * Worker thread. [createGamescopeContainerAsync] without the thread; null when creation fails. + * Without a [runtime] the container gets no Wine prefix, which its sessions never use. + */ + @JvmStatic + fun createGamescopeContainer( + context: Context, + containerManager: ContainerManager, + contentsManager: ContentsManager, + runtime: ContentProfile?, + ): Container? { + val name = uniqueName(containerManager, GAMESCOPE_CONTAINER_NAME) + val created = + if (runtime != null) { + val data = buildLaunchReadyData(context, contentsManager, name, ContentsManager.getEntryName(runtime)) + containerManager.createContainer(data, contentsManager) + } else { + val data = buildLaunchReadyData(context, contentsManager, name, WineInfo.MAIN_WINE_VERSION.identifier()) + containerManager.createPrefixlessContainer(data) + } + val container = created ?: return null + applyLaunchReadyDefaults(context, contentsManager, container) + container.setRuntime(Container.RUNTIME_GAMESCOPE) + container.setEnvVars(EnvVarsView.forGamescope(container.getEnvVars())) + container.setDisplayBackend(Container.DISPLAY_BACKEND_WAYLAND) + container.saveData() + LinuxApps.ensureSteamShortcut(context, container) + return container + } + @JvmStatic fun createContainerForProfileAsync( context: Context, diff --git a/app/src/main/runtime/container/ContainerManager.java b/app/src/main/runtime/container/ContainerManager.java index 9e420ead2..65198ec80 100644 --- a/app/src/main/runtime/container/ContainerManager.java +++ b/app/src/main/runtime/container/ContainerManager.java @@ -220,6 +220,37 @@ public void removeContainerAsync(Container container, Runnable callback) { }); } + /** + * A container without a Wine prefix, for the GameScope runtime: its sessions never start Wine, + * so it only holds settings and library entries and needs no Wine or Proton to be installed. + */ + public Container createPrefixlessContainer(JSONObject data) { + try { + int id = maxContainerId + 1; + File containerDir = new File(homeDir, ImageFs.USER + "-" + id); + while (containerDir.exists()) { + id++; + containerDir = new File(homeDir, ImageFs.USER + "-" + id); + } + data.put("id", id); + Container container = new Container(id, this); + container.setRootDir(containerDir); + container.loadData(data); + File desktopDir = container.getDesktopDir(); + if (!desktopDir.isDirectory() && !desktopDir.mkdirs()) { + FileUtils.delete(containerDir); + return null; + } + container.saveData(); + maxContainerId = Math.max(maxContainerId, id); + containers.add(container); + return container; + } catch (Throwable e) { + Log.e("ContainerManager", "Error creating prefixless container", e); + } + return null; + } + public Container createContainer(JSONObject data, ContentsManager contentsManager) { try { int id = maxContainerId + 1; diff --git a/app/src/main/runtime/content/AdrenotoolsManager.java b/app/src/main/runtime/content/AdrenotoolsManager.java index f80943c2b..702e0eeda 100644 --- a/app/src/main/runtime/content/AdrenotoolsManager.java +++ b/app/src/main/runtime/content/AdrenotoolsManager.java @@ -6,7 +6,6 @@ import android.content.Context; import android.util.Log; -import com.winlator.cmod.app.config.SettingsConfig; import com.winlator.cmod.runtime.container.Container; import com.winlator.cmod.runtime.container.Shortcut; import com.winlator.cmod.runtime.container.ContainerManager; @@ -18,12 +17,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.HashMap; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; import org.json.JSONException; import org.json.JSONObject; @@ -53,6 +48,23 @@ public String getLibraryName(String adrenoToolsDriverId) { return libraryName; } + public boolean isTurnipDriver(String driverId) { + if (driverId == null || driverId.isEmpty() || "System".equalsIgnoreCase(driverId)) return false; + File directory = new File(adrenotoolsContentDir, driverId); + String metadata = FileUtils.readString(new File(directory, "meta.json")); + if (metadata == null) return false; + try { + JSONObject json = new JSONObject(metadata); + String library = json.optString("libraryName"); + if (library.isEmpty() || !new File(directory, library).isFile()) return false; + String identity = library + " " + json.optString("name") + " " + json.optString("driverName"); + identity = identity.toLowerCase(java.util.Locale.ROOT); + return identity.contains("turnip") || identity.contains("freedreno"); + } catch (JSONException e) { + return false; + } + } + public String getDriverName(String adrenoToolsDriverId) { String driverName = ""; File driverPath = new File(adrenotoolsContentDir, adrenoToolsDriverId); @@ -199,64 +211,14 @@ public String installDriver(Uri driverUri) { * detect that a remote GitHub asset with that filename is already installed. */ public String installDriver(Uri driverUri, String sourceAssetName) { - com.winlator.cmod.runtime.system.GraphicsDriverCatalog.invalidate(); - File tmpDir = new File(adrenotoolsContentDir, "tmp"); - if (tmpDir.exists()) tmpDir.delete(); - tmpDir.mkdirs(); - ZipInputStream zis; - InputStream is; - String name = ""; - try { - is = mContext.getContentResolver().openInputStream(driverUri); - zis = new ZipInputStream(is); - ZipEntry entry = zis.getNextEntry(); - while (entry != null) { - File dstFile = new File(tmpDir, entry.getName()); - Files.copy(zis, dstFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - entry = zis.getNextEntry(); - } - zis.close(); - if (new File(tmpDir, "meta.json").exists()) { - name = getDriverName(tmpDir.getName()); - File dst = new File(adrenotoolsContentDir, name); - if (!dst.exists() && !name.equals("")) { - tmpDir.renameTo(dst); - if (sourceAssetName != null && !sourceAssetName.isEmpty()) { - stampSourceAsset(dst, sourceAssetName); - } - } - else { - name = ""; - FileUtils.delete(tmpDir); - } - } - else { - Log.d("AdrenotoolsManager", "Failed to install driver, a valid driver has not been selected"); - tmpDir.delete(); - } - } - catch (IOException e) { - Log.d("AdrenotoolsManager", "Failed to install driver, a valid driver has not been selected"); - tmpDir.delete(); + return DriverPackages.install(mContext, driverUri, sourceAssetName, DriverPackages.Platform.ANDROID).id; + } catch (Exception error) { + Log.w("AdrenotoolsManager", "Driver installation failed", error); + return ""; } - - return name; } - private void stampSourceAsset(File driverDir, String sourceAssetName) { - File metaFile = new File(driverDir, "meta.json"); - try { - String jsonStr = FileUtils.readString(metaFile); - JSONObject jsonObject = new JSONObject(jsonStr != null ? jsonStr : "{}"); - jsonObject.put("sourceAsset", sourceAssetName); - FileUtils.writeString(metaFile, jsonObject.toString(2)); - } - catch (JSONException e) { - Log.w("AdrenotoolsManager", "Failed to stamp sourceAsset into meta.json: " + e.getMessage()); - } - } - public void setDriverById(EnvVars envVars, ImageFs imagefs, String adrenotoolsDriverId) { if (adrenotoolsDriverId == null || adrenotoolsDriverId.isEmpty()) { Log.w("AdrenotoolsManager", "setDriverById called with empty driver id - system driver will be used"); diff --git a/app/src/main/runtime/content/ContentsManager.java b/app/src/main/runtime/content/ContentsManager.java index a4d4e7400..f92eb95a9 100644 --- a/app/src/main/runtime/content/ContentsManager.java +++ b/app/src/main/runtime/content/ContentsManager.java @@ -383,6 +383,7 @@ public void finishInstallContent(ContentProfile profile, OnInstallFinishedCallba } repairInstalledContentPermissions(profile); + com.winlator.cmod.runtime.display.wayland.WineWaylandSupport.invalidate(); callback.onSucceed(profile); } @@ -650,6 +651,7 @@ public void removeContent(ContentProfile profile) { FileUtils.delete(getInstallDir(context, profile)); forgetRemoteProfileAliases(profile); syncContents(); + com.winlator.cmod.runtime.display.wayland.WineWaylandSupport.invalidate(); } private void forgetRemoteProfileAliases(ContentProfile profile) { diff --git a/app/src/main/runtime/content/DriverPackages.java b/app/src/main/runtime/content/DriverPackages.java new file mode 100644 index 000000000..18fee0af1 --- /dev/null +++ b/app/src/main/runtime/content/DriverPackages.java @@ -0,0 +1,346 @@ +package com.winlator.cmod.runtime.content; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import androidx.preference.PreferenceManager; +import com.winlator.cmod.runtime.linux.LinuxRuntime; +import com.winlator.cmod.shared.io.FileUtils; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.json.JSONObject; +import org.json.JSONException; + +public final class DriverPackages { + public enum Platform { + ANDROID, + LINUX + } + + public static final String BUNDLED = "@bundled"; + public static final String LEGACY = "@legacy"; + private static final String SELECTED = "linux_driver_selected"; + private static final long MAX_BYTES = 128L * 1024 * 1024; + + public static final class Installed { + public final String id; + public final Platform platform; + + public Installed(String id, Platform platform) { + this.id = id; + this.platform = platform; + } + } + + private DriverPackages() {} + + public static File linuxDirectory(Context context) { + return new File(context.getFilesDir(), "contents/linux-drivers"); + } + + public static List<File> linuxDrivers(Context context) { + List<File> result = new ArrayList<>(); + File[] directories = linuxDirectory(context).listFiles(); + if (directories != null) + for (File directory : directories) { + if (!directory.getName().startsWith(".") + && new File(directory, "meta.json").isFile() + && new File(directory, LinuxRuntime.DRIVER_ICD).isFile()) result.add(directory); + } + return result; + } + + public static String selectedLinux(Context context) { + String selected = + PreferenceManager.getDefaultSharedPreferences(context).getString(SELECTED, null); + if (BUNDLED.equals(selected)) return BUNDLED; + if (LEGACY.equals(selected) && LinuxRuntime.downloadedDriverVersion(context) > 0) return LEGACY; + if (selected != null + && linuxDrivers(context).stream().anyMatch(file -> file.getName().equals(selected))) + return selected; + return LinuxRuntime.downloadedDriverVersion(context) > LinuxRuntime.TURNIP_BUILD + ? LEGACY + : BUNDLED; + } + + public static void selectLinux(Context context, String id) { + boolean valid = + BUNDLED.equals(id) + || (LEGACY.equals(id) && LinuxRuntime.downloadedDriverVersion(context) > 0) + || linuxDrivers(context).stream().anyMatch(file -> file.getName().equals(id)); + if (!valid) throw new IllegalArgumentException("Unknown Linux driver"); + PreferenceManager.getDefaultSharedPreferences(context).edit().putString(SELECTED, id).apply(); + com.winlator.cmod.runtime.linux.LinuxDriverChoices.update(context); + } + + public static File selectedLinuxIcd(Context context) { + return linuxIcd(context, selectedLinux(context)); + } + + /** + * The driver an entry runs on. Nothing saved means the entry never chose one, so it gets the + * driver chosen for the app - the one the Drivers page calls active - and a container made + * before this setting existed keeps the driver it was running on. A name that was saved and is + * now gone is a choice that cannot be honoured, and that falls back to the bundled Mesa rather + * than to someone else's driver. + */ + public static File selectedLinuxIcd(Context context, String selection) { + if (selection == null || selection.isEmpty()) return selectedLinuxIcd(context); + if (selection.equalsIgnoreCase("System")) return linuxIcd(context, BUNDLED); + return linuxIcd(context, linuxIdForSelection(context, selection)); + } + + private static File linuxIcd(Context context, String id) { + if (BUNDLED.equals(id)) return null; + File directory = + LEGACY.equals(id) + ? LinuxRuntime.driverDir(context) + : new File(linuxDirectory(context), id); + File manifest = new File(directory, LinuxRuntime.DRIVER_ICD); + try { + if (!manifest.isFile() || manifest.length() > 65536) return null; + JSONObject icd = new JSONObject(new String(Files.readAllBytes(manifest.toPath()), + java.nio.charset.StandardCharsets.UTF_8)).getJSONObject("ICD"); + String path = icd.getString("library_path"); + File library = new File(path); + if (!library.isAbsolute()) library = new File(directory, path); + if (library.isFile() && library.canRead()) return manifest; + } catch (IOException | JSONException error) { + android.util.Log.w("DriverPackages", "Cannot read Linux driver " + id, error); + } + android.util.Log.w("DriverPackages", "Linux driver unavailable; using bundled Mesa: " + id); + return null; + } + + public static List<String> linuxSelectionEntries(Context context) { + List<String> entries = new ArrayList<>(); + entries.add(LinuxRuntime.TURNIP_VERSION); + if (LinuxRuntime.downloadedDriverVersion(context) > 0) { + entries.add(linuxName(context, LEGACY)); + } + for (File driver : linuxDrivers(context)) entries.add(linuxName(context, driver.getName())); + return entries; + } + + public static String selectedLinuxName(Context context) { + return linuxName(context, selectedLinux(context)); + } + + private static String linuxName(Context context, String selected) { + if (BUNDLED.equals(selected)) return LinuxRuntime.TURNIP_VERSION; + if (LEGACY.equals(selected)) { + String name = + FileUtils.readString( + new File(LinuxRuntime.driverDir(context), LinuxRuntime.DRIVER_NAME_FILE)); + return name == null ? LinuxRuntime.TURNIP_VERSION : name.trim(); + } + try { + return new JSONObject( + new String( + Files.readAllBytes( + new File(linuxDirectory(context), selected + "/meta.json").toPath()), + java.nio.charset.StandardCharsets.UTF_8)) + .getString("name"); + } catch (Exception error) { + return LinuxRuntime.TURNIP_VERSION; + } + } + + private static String linuxIdForSelection(Context context, String selection) { + if (selection.equals(LinuxRuntime.TURNIP_VERSION) || selection.equals(BUNDLED)) return BUNDLED; + if (selection.equals(LEGACY) || selection.equals(linuxName(context, LEGACY))) return LEGACY; + for (File driver : linuxDrivers(context)) { + String id = driver.getName(); + if (selection.equals(id) || selection.equals(linuxName(context, id))) return id; + } + return BUNDLED; + } + + public static synchronized void removeLinux(Context context, String id) { + if (BUNDLED.equals(id)) return; + boolean selected = id.equals(selectedLinux(context)); + if (LEGACY.equals(id)) FileUtils.delete(LinuxRuntime.driverDir(context)); + else + for (File file : linuxDrivers(context)) if (file.getName().equals(id)) FileUtils.delete(file); + if (selected) selectLinux(context, BUNDLED); + } + + public static synchronized Installed install( + Context context, Uri uri, String asset, Platform required) throws Exception { + File staging = Files.createTempDirectory(context.getCacheDir().toPath(), "driver-").toFile(); + try { + Set<String> entries = new HashSet<>(); + long total = 0; + try (InputStream input = context.getContentResolver().openInputStream(uri)) { + if (input == null) throw new IOException("Cannot read driver"); + try (ZipInputStream zip = new ZipInputStream(input)) { + ZipEntry entry; + byte[] buffer = new byte[16384]; + while ((entry = zip.getNextEntry()) != null) { + String name = entry.getName(); + if (!safeName(name) || entry.isDirectory() || !entries.add(name) || entries.size() > 32) + throw new IOException("Invalid driver archive"); + File output = new File(staging, name); + try (java.io.OutputStream stream = Files.newOutputStream(output.toPath())) { + int count; + while ((count = zip.read(buffer)) != -1) { + total += count; + if (total > MAX_BYTES) throw new IOException("Driver archive too large"); + stream.write(buffer, 0, count); + } + } + } + } + } + File metadata = new File(staging, "meta.json"); + if (!metadata.isFile() || metadata.length() > 65536) + throw new IOException("Missing driver metadata"); + JSONObject meta = + new JSONObject( + new String( + Files.readAllBytes(metadata.toPath()), java.nio.charset.StandardCharsets.UTF_8)); + String name = meta.getString("name").trim(); + String library = meta.getString("libraryName"); + if (!safeName(name) || name.startsWith("@") || !safeName(library) || !library.endsWith(".so")) + throw new IOException("Invalid driver name"); + byte[] libraryBytes = Files.readAllBytes(new File(staging, library).toPath()); + Platform platform = identify(libraryBytes); + String expectedHash = meta.optString("librarySha256", ""); + if (!expectedHash.isEmpty()) { + byte[] hash = java.security.MessageDigest.getInstance("SHA-256").digest(libraryBytes); + StringBuilder actualHash = new StringBuilder(); + for (byte value : hash) + actualHash.append(String.format(java.util.Locale.ROOT, "%02x", value & 255)); + if (!expectedHash.equalsIgnoreCase(actualHash.toString())) + throw new IOException("Driver checksum mismatch"); + } + if (required != null && platform != required) + throw new IOException("Driver belongs to " + platform); + String declared = meta.optString("platform", ""); + if (!declared.isEmpty() && !declared.equalsIgnoreCase(platform.name())) + throw new IOException("Driver platform mismatch"); + if (platform == Platform.ANDROID && meta.optInt("minApi", 0) > Build.VERSION.SDK_INT) + throw new IOException("Driver requires newer Android"); + for (String entry : entries) { + if (entry.endsWith(".so") + && identify(Files.readAllBytes(new File(staging, entry).toPath())) != platform) + throw new IOException("Mixed driver platforms"); + } + meta.put("platform", platform.name().toLowerCase(java.util.Locale.ROOT)); + if (asset != null) meta.put("sourceAsset", asset); + Files.write( + metadata.toPath(), meta.toString(2).getBytes(java.nio.charset.StandardCharsets.UTF_8)); + File root = + platform == Platform.LINUX + ? linuxDirectory(context) + : new File(context.getFilesDir(), "contents/adrenotools"); + if (!root.isDirectory() && !root.mkdirs()) + throw new IOException("Cannot create driver directory"); + File target = new File(root, name); + if (target.exists()) throw new IOException("Driver already installed"); + if (platform == Platform.LINUX) { + JSONObject icd = + new JSONObject() + .put("file_format_version", "1.0.0") + .put( + "ICD", + new JSONObject() + .put("api_version", "1.4.0") + .put("library_path", new File(target, library).getAbsolutePath())); + Files.write( + new File(staging, LinuxRuntime.DRIVER_ICD).toPath(), + icd.toString(2).getBytes(java.nio.charset.StandardCharsets.UTF_8)); + } + if (!staging.renameTo(target)) throw new IOException("Cannot install driver"); + if (platform == Platform.LINUX) selectLinux(context, name); + else com.winlator.cmod.runtime.system.GraphicsDriverCatalog.invalidate(); + return new Installed(name, platform); + } finally { + FileUtils.delete(staging); + } + } + + private static boolean safeName(String name) { + return !name.isEmpty() + && !name.startsWith(".") + && !name.contains("/") + && !name.contains("\\") + && name.length() <= 180 + && name.chars().noneMatch(character -> character < 32); + } + + public static Platform identify(byte[] elf) throws IOException { + try { + ByteBuffer data = ByteBuffer.wrap(elf).order(ByteOrder.LITTLE_ENDIAN); + if (elf.length < 64 + || data.getInt(0) != 0x464c457f + || elf[4] != 2 + || elf[5] != 1 + || data.getShort(16) != 3 + || data.getShort(18) != 183) throw new IOException("Expected ARM64 shared library"); + long headers = data.getLong(32); + int headerSize = Short.toUnsignedInt(data.getShort(54)); + int count = Short.toUnsignedInt(data.getShort(56)); + if (headerSize < 56 || count > 4096) throw new IOException("Invalid ELF headers"); + List<long[]> loads = new ArrayList<>(); + long dynamicOffset = -1, dynamicSize = 0; + for (int i = 0; i < count; i++) { + int offset = checked(headers + (long) i * headerSize, 56, elf.length); + int type = data.getInt(offset); + long fileOffset = data.getLong(offset + 8), + virtualAddress = data.getLong(offset + 16), + size = data.getLong(offset + 32); + checked(fileOffset, size, elf.length); + if (type == 1) loads.add(new long[] {virtualAddress, fileOffset, size}); + if (type == 2) { + dynamicOffset = fileOffset; + dynamicSize = size; + } + } + if (dynamicOffset < 0) throw new IOException("Missing ELF dependencies"); + long strings = -1; + List<Long> needed = new ArrayList<>(); + for (long offset = dynamicOffset; offset + 16 <= dynamicOffset + dynamicSize; offset += 16) { + long tag = data.getLong((int) offset), value = data.getLong((int) offset + 8); + if (tag == 0) break; + if (tag == 5) strings = value; + if (tag == 1) needed.add(value); + } + long stringOffset = -1; + for (long[] load : loads) + if (strings >= load[0] && strings - load[0] < load[2]) + stringOffset = load[1] + strings - load[0]; + if (stringOffset < 0) throw new IOException("Missing ELF string table"); + boolean android = false, linux = false; + for (long index : needed) { + int start = checked(stringOffset + index, 1, elf.length), end = start; + while (end < elf.length && elf[end] != 0 && end - start < 256) end++; + if (end == elf.length || elf[end] != 0) throw new IOException("Invalid ELF dependency"); + String dependency = + new String(elf, start, end - start, java.nio.charset.StandardCharsets.UTF_8); + android |= dependency.equals("libc.so"); + linux |= dependency.equals("libc.so.6"); + } + if (android == linux) throw new IOException("Cannot identify driver ABI"); + return linux ? Platform.LINUX : Platform.ANDROID; + } catch (IndexOutOfBoundsException | ArithmeticException error) { + throw new IOException("Invalid ELF", error); + } + } + + private static int checked(long offset, long length, int size) throws IOException { + if (offset < 0 || length < 0 || offset > size || length > size - offset) + throw new IOException("Invalid ELF bounds"); + return (int) offset; + } +} diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index ed3c09158..00461f0a6 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -64,6 +64,8 @@ import com.winlator.cmod.feature.settings.DebugFragment; import com.winlator.cmod.feature.setup.SetupWizardActivity; import com.winlator.cmod.runtime.container.Container; +import com.winlator.cmod.runtime.display.environment.components.LinuxProgramLauncherComponent; +import com.winlator.cmod.runtime.linux.LinuxRuntime; import com.winlator.cmod.runtime.display.environment.components.NetworkingSettings; import com.winlator.cmod.runtime.container.ContainerManager; import com.winlator.cmod.runtime.container.Shortcut; @@ -84,8 +86,14 @@ import com.winlator.cmod.runtime.system.LogManager; import com.winlator.cmod.shared.android.AppUtils; import com.winlator.cmod.shared.android.AppTerminationHelper; +import com.winlator.cmod.shared.ui.widget.EnvVarsView; import com.winlator.cmod.shared.ui.toast.WinToast; import com.winlator.cmod.runtime.wine.EnvVars; +import com.winlator.cmod.runtime.display.wayland.WaylandCompositor; +import com.winlator.cmod.runtime.display.wayland.WaylandGameDriver; +import com.winlator.cmod.runtime.display.wayland.WaylandPrefixRegistry; +import com.winlator.cmod.runtime.display.wayland.WaylandSession; +import com.winlator.cmod.runtime.display.wayland.WineWaylandSupport; import com.winlator.cmod.runtime.reshade.ReshadeConfigWriter; import com.winlator.cmod.runtime.reshade.ReshadeManager; import com.winlator.cmod.runtime.wine.LocaleEnv; @@ -96,6 +104,7 @@ import com.winlator.cmod.shared.util.Callback; import com.winlator.cmod.shared.util.OnExtractFileListener; import com.winlator.cmod.shared.ui.dialog.PreloaderDialog; +import com.winlator.cmod.runtime.system.LinuxTaskList; import com.winlator.cmod.runtime.system.ProcessHelper; import com.winlator.cmod.runtime.system.SessionKeepAliveService; import com.winlator.cmod.shared.android.RefreshRateUtils; @@ -124,12 +133,14 @@ import com.winlator.cmod.runtime.wine.WineThemeManager; import com.winlator.cmod.runtime.wine.WineUtils; import com.winlator.cmod.runtime.compat.fexcore.FEXCoreManager; +import com.winlator.cmod.runtime.compat.fexcore.FEXCorePresetManager; import com.winlator.cmod.runtime.compat.gamefixes.GameFixes; import com.winlator.cmod.runtime.audio.alsaserver.ALSAClient; import com.winlator.cmod.runtime.input.ControllerAssignmentDialog; import com.winlator.cmod.runtime.input.controls.ControlsProfile; import com.winlator.cmod.runtime.input.controls.ControllerManager; import com.winlator.cmod.runtime.input.controls.ExternalController; +import com.winlator.cmod.runtime.input.controls.FakeInputWriter; import com.winlator.cmod.runtime.input.controls.GestureProfile; import com.winlator.cmod.runtime.input.controls.GestureProfileManager; import com.winlator.cmod.runtime.input.controls.InputControlsManager; @@ -161,6 +172,7 @@ import com.winlator.cmod.runtime.display.environment.XEnvironment; import com.winlator.cmod.feature.stores.steam.SteamClientManager; import com.winlator.cmod.runtime.audio.directaudio.DirectAudioDriver; +import com.winlator.cmod.runtime.audio.directaudio.DirectAudioHost; import com.winlator.cmod.runtime.display.environment.components.ALSAServerComponent; import com.winlator.cmod.runtime.display.environment.components.GuestProgramLauncherComponent; import com.winlator.cmod.runtime.display.environment.components.NetworkInfoUpdateComponent; @@ -193,6 +205,7 @@ import java.util.List; import java.util.HashSet; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Iterator; import java.util.Locale; import java.util.Timer; @@ -496,6 +509,17 @@ private boolean isAnyControllerConnected() { private static final long SYSTEM_FRAME_GEN_POLL_MS = 2000L; private static final int SYSTEM_FRAME_GEN_IDLE_PROBES = 3; private String systemFrameGenSignal = ""; + /* Fast until the renderer is known, then slow: it only changes if the session starts + * another game, as a store front end does. */ + private static final long WAYLAND_RENDERER_POLL_MS = 2000L; + private static final long WAYLAND_RENDERER_SETTLED_POLL_MS = 15000L; + private static final long WAYLAND_RENDERER_SESSION_POLL_MS = 5000L; + private static final long LINUX_SESSION_START_MS = 20000L; + /** 128 + SIGKILL: the session was stopped from outside, which on Android is Android itself. */ + private static final int SIGKILL_STATUS = 137; + private Thread waylandRendererThread; + /* The process behind the window the compositor is showing, from the compositor itself. */ + private volatile int waylandGamePid; private static final int[] DIS_FLOW_MIN_SIDES = {180, 252, 360}; private static final int DIS_FRAME_GEN_SCALE_DEFAULT = 180; @@ -573,6 +597,8 @@ private static class ReshadeLiveEffect { private int savedRenderMode = XServerSurfaceView.RENDERMODE_WHEN_DIRTY; private Timer taskManagerTimer; private final ArrayList<TaskManagerProcess> taskManagerAccum = new ArrayList<>(); + /** UI thread only: the pid behind each name the GameScope task manager is showing. */ + private final LinkedHashMap<String, Integer> linuxTaskPids = new LinkedHashMap<>(); private boolean taskManagerCpuExpanded = false; private boolean taskManagerPaneVisible = false; private CPUStatus.AppCpuSample prevTaskCpuSample; @@ -610,6 +636,16 @@ private static class ReshadeLiveEffect { private Runnable hideControlsRunnable; private volatile boolean startFullscreenStretched; + private static final long WAYLAND_OVERLAY_GRACE_MS = 2000L; + private final AtomicBoolean firstGuestWindowShown = new AtomicBoolean(false); + + // Display server of this session: the X server, or the embedded Wayland compositor. + private boolean waylandMode; + /* The container boots gamescope in the Linux runtime; the compositor is its display. */ + private boolean gamescopeMode; + private WaylandSession waylandSession; + private final java.util.concurrent.atomic.AtomicInteger waylandFrameSerial = + new java.util.concurrent.atomic.AtomicInteger(); private final AtomicBoolean exitRequested = new AtomicBoolean(false); private final AtomicBoolean steamExitWatchRunning = new AtomicBoolean(false); @@ -904,14 +940,36 @@ private void applyFrameGeneration(VulkanRenderer renderer) { + " refreshRate=" + refreshRate); } + /* The Wayland compositor hosts the engines itself, so it is armed from the same fields the + * X11 renderer is given; the setters are stores its thread picks up on its next frame. */ + private void applyWaylandFrameGeneration() { + if (!waylandMode) return; + + if (frameGenEnabled && frameGenCachePath != null) { + WaylandCompositor.nativeSetLsfgCachePath(frameGenCachePath); + WaylandCompositor.nativeSetFrameGenEngine(WaylandCompositor.ENGINE_LSFG); + WaylandCompositor.nativeSetEngineTuning(disFrameGenScale, frameGenTargetRate); + WaylandCompositor.nativeSetFrameGenTuning(frameGenFlowScale / 100f, frameGenRefreshRate); + WaylandCompositor.nativeSetFrameGenArmed(true, frameGenMultiplier); + } else if (disFrameGenEnabled) { + WaylandCompositor.nativeSetFrameGenEngine(WaylandCompositor.ENGINE_DIS); + WaylandCompositor.nativeSetEngineTuning(disFrameGenScale, disFrameGenTargetFps); + WaylandCompositor.nativeSetFrameGenTuning(frameGenFlowScale / 100f, frameGenRefreshRate); + WaylandCompositor.nativeSetFrameGenArmed(true, frameGenMultiplier); + } else { + WaylandCompositor.nativeSetFrameGenArmed(false, frameGenMultiplier); + } + } + private void syncFrameGenerationHud() { + applyWaylandFrameGeneration(); boolean ourFrameGen = (frameGenEnabled && frameGenCachePath != null) || disFrameGenEnabled; boolean systemFrameGen = !ourFrameGen && systemFrameGenHudEnabled; boolean active = ourFrameGen || systemFrameGen; FrameRating.OutputFrameSource source; if (ourFrameGen || frameGenEnabled || disFrameGenEnabled) { - source = frameGenOutputSource; + source = waylandMode ? waylandFrameGenOutputSource : frameGenOutputSource; } else if (systemFrameGen) { source = ensureSystemFrameGenMonitor(); } else { @@ -943,6 +1001,7 @@ private SystemFrameGenMonitor ensureSystemFrameGenMonitor() { if (systemFrameGenMonitor == null) { systemFrameGenMonitor = new SystemFrameGenMonitor( () -> { + if (waylandMode) return WaylandCompositor.nativeFrameGenPresentedFrames(); VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; return renderer != null ? renderer.getPresentedFrameCount() : 0L; }, @@ -1022,6 +1081,70 @@ private void stopSystemFrameGenPolling() { systemFrameGenPollRunnable = null; } + /* A Wayland session has no window property to read the renderer from, so the process behind the + * window is asked instead; WaylandRendererProbe says why. Off the UI thread: a tick that has no + * pid to go on falls back to reading every process on the device. */ + private void startWaylandRendererPolling() { + if (!waylandMode || waylandRendererThread != null) return; + final java.io.File root = container != null ? container.getRootDir() : null; + if (root == null) return; + waylandRendererThread = new Thread(() -> { + String reported = null; + while (!activityDestroyed.get()) { + String name; + if (gamescopeMode) { + name = com.winlator.cmod.runtime.display.wayland.WaylandRendererProbe.probeLinuxSession(); + // Between games the client's own window is back, and that is drawn with Vulkan. + if (name == null) name = com.winlator.cmod.runtime.display.wayland.WaylandRendererProbe.VULKAN; + } else { + name = com.winlator.cmod.runtime.display.wayland.WaylandRendererProbe.probe(root, waylandGamePid); + } + if (name != null && !name.equals(reported)) { + reported = name; + final String resolved = name; + runOnUiThread(() -> applyWaylandRendererName(resolved)); + } + try { + // A Linux session goes from one game to the next without this window changing. + Thread.sleep(reported == null ? WAYLAND_RENDERER_POLL_MS + : gamescopeMode ? WAYLAND_RENDERER_SESSION_POLL_MS + : WAYLAND_RENDERER_SETTLED_POLL_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + }, "WaylandRendererProbe"); + waylandRendererThread.setDaemon(true); + waylandRendererThread.start(); + } + + private void stopWaylandRendererPolling() { + if (waylandRendererThread == null) return; + waylandRendererThread.interrupt(); + waylandRendererThread = null; + } + + private void applyWaylandRendererName(String name) { + if (activityDestroyed.get() || name.equals(lastRendererName)) return; + lastRendererName = name; + if (frameRating != null) frameRating.setRenderer(name); + if (mangoHud != null) mangoHud.setEngineName(mangoEngineLabel()); + } + + private final FrameRating.OutputFrameSource waylandFrameGenOutputSource = + new FrameRating.OutputFrameSource() { + @Override + public long getPresentedFrameCount() { + return WaylandCompositor.nativeFrameGenPresentedFrames(); + } + + @Override + public long getGeneratedFrameCount() { + return WaylandCompositor.nativeFrameGenGeneratedFrames(); + } + }; + private final FrameRating.OutputFrameSource frameGenOutputSource = new FrameRating.OutputFrameSource() { @Override @@ -1181,6 +1304,7 @@ private void applyDisFrameGeneration(VulkanRenderer renderer) { } float refreshRate = applyDisFrameGenerationDisplayMode(); + frameGenRefreshRate = refreshRate; renderer.setDisFrameGenerationScale(disFrameGenScale); renderer.setDisFrameGenerationTargetFps(disFrameGenTargetFps); renderer.setDisDebugFlow(disFrameGenDebugFlow); @@ -1536,6 +1660,7 @@ private void handleDisplayCapabilitiesChanged() { if (xServerView != null && xServerView.getRenderer() != null) { xServerView.getRenderer().setFpsLimit(runtimeFpsLimit); } + if (waylandSession != null) waylandSession.setFpsLimit(runtimeFpsLimit); if (shortcut != null) { shortcut.putExtra("fpsLimit", String.valueOf(runtimeFpsLimit)); shortcut.saveData(); @@ -2151,6 +2276,7 @@ public void handleOnBackPressed() { wineInfo = WineInfo.fromIdentifier(this, contentsManager, wineVersion); imageFs.setWinePath(wineInfo.path); + resolveDisplayBackend(); ProcessHelper.removeAllDebugCallbacks(); if (enableLogsMenu) { @@ -2205,6 +2331,7 @@ public void handleOnBackPressed() { } wineInfo = WineInfo.fromIdentifier(this, contentsManager, wineVersion); imageFs.setWinePath(wineInfo.path); + resolveDisplayBackend(); } } @@ -2420,44 +2547,12 @@ public void handleOnBackPressed() { sgsrRuntimeEnabled = sgsrEnabled; xServer.setWinHandler(winHandler); - boolean[] winStarted = {false}; - xServer.windowManager.addOnWindowModificationListener(new WindowManager.OnWindowModificationListener() { @Override public void onUpdateWindowContent(Window window) { - if (!winStarted[0] && window.isApplicationWindow()) { - if (!isMouseDisabled) { - touchpadView.setMouseEnabled(true); - } else { - xServerView.getRenderer().setCursorVisible(false); - } - if (!wnLauncherDrivesDismiss.get()) { - preloaderDialog.closeOnUiThread(); - stopWnLauncherStatusTailer(); - } - winStarted[0] = true; - runOnUiThread(() -> { - inputControlsRevealAllowed = true; - if (inputControlsView != null) { - ControlsProfile activeProfile = inputControlsView.getProfile(); - if (activeProfile != null) showInputControls(activeProfile); - else startTouchscreenTimeout(); - } - }); - if (startFullscreenStretched) { - timeoutHandler.post(() -> { - if (activityDestroyed.get()) return; - VulkanRenderer r = xServerView != null ? xServerView.getRenderer() : null; - if (r != null && !r.isFullscreen()) { - r.toggleFullscreen(); - touchpadView.toggleFullscreen(); - renderDrawerMenu(); - } - }); - } - } + if (window.isApplicationWindow()) onFirstGuestWindow(); } - + @Override public void onMapWindow(Window window) { assignTaskAffinity(window); @@ -3258,6 +3353,7 @@ public void onResume() { if (!cleaningUp && environment != null) { xServerView.onResume(); environment.onResume(); + if (waylandSession != null) waylandSession.onResume(); ensureAudioFocusHandler(); if (audioFocusHandler != null) audioFocusHandler.request(); } @@ -3297,6 +3393,7 @@ public void onResume() { syncFrameGenerationHud(); startSystemFrameGenPolling(); } + startWaylandRendererPolling(); SessionKeepAliveService.onResumeSession(this); LogManager.log(TAG, "Session resumed", getApplicationContext()); @@ -3323,6 +3420,7 @@ public void onPause() { refreshSteamControllerInput(); super.onPause(); stopSystemFrameGenPolling(); + stopWaylandRendererPolling(); if (systemFrameGenMonitor != null) systemFrameGenMonitor.stop(); isVolumeUpPressed = false; isVolumeDownPressed = false; @@ -3754,12 +3852,9 @@ private void shareLogStream() { new java.util.zip.ZipOutputStream(new java.io.FileOutputStream(zipFile))) { for (File file : logFiles) { if (file == null || !file.isFile()) continue; - zos.putNextEntry(new java.util.zip.ZipEntry(file.getName())); - try (java.io.InputStream in = new java.io.FileInputStream(file)) { - byte[] buf = new byte[8192]; - int n; - while ((n = in.read(buf)) > 0) zos.write(buf, 0, n); - } + zos.putNextEntry(new java.util.zip.ZipEntry( + com.winlator.cmod.runtime.system.LogManager.archiveName(this, file))); + com.winlator.cmod.runtime.system.LogManager.copyShareable(this, file, zos); zos.closeEntry(); } } @@ -3921,6 +4016,7 @@ private void performForcedSessionCleanup(String trigger) { } stopXServer("forced cleanup (" + trigger + ")"); + endWaylandSession(); xServer = null; xServerView = null; @@ -3979,6 +4075,7 @@ private void exit() { LogManager.log(TAG, "Process snapshot after environment stop: " + ProcessHelper.listRunningWineProcessDetails(), this); stopXServer("exit"); + endWaylandSession(); wineRequestHandler = null; midiHandler = null; xServer = null; @@ -4537,10 +4634,21 @@ private void reportLaunchFailure(Throwable t) { final String message = reason; runOnUiThread(() -> { if (activityDestroyed.get() || isFinishing() || isDestroyed()) return; - WinToast.show(this, getString(R.string.preloader_launch_failed, message)); + if (t instanceof LinuxSessionUnavailable) { + com.winlator.cmod.shared.ui.dialog.ContentDialog.alert(this, message, this::exit); + } else { + WinToast.show(this, getString(R.string.preloader_launch_failed, message)); + } }); } + /** A Linux session that cannot start: nothing would ever draw, so it is said and the screen left. */ + private static final class LinuxSessionUnavailable extends IllegalStateException { + LinuxSessionUnavailable(String message) { + super(message); + } + } + private void stopWnLauncherStatusTailer() { wnLauncherDrivesDismiss.set(false); if (wnLauncherStatusTailer == null) return; @@ -4973,9 +5081,11 @@ private void uploadSavesIfAgentExitSyncLeftThemLocal(String trigger) { @Override protected void onDestroy() { activityDestroyed.set(true); + detachWaylandSession(); stopSteamControllerSupport(); hideControllerTestDialog(); stopSystemFrameGenPolling(); + stopWaylandRendererPolling(); if (systemFrameGenMonitor != null) { systemFrameGenMonitor.stop(); systemFrameGenMonitor = null; @@ -4988,6 +5098,7 @@ protected void onDestroy() { reshadeLiveThread = null; } com.winlator.cmod.feature.stores.steam.service.GameSessionState.setInGame(this, false); + if (gamescopeMode) adoptClientInstalls(); // Finalize any in-progress recording before the renderer tears down. if (screenRecorder != null && screenRecorder.isRecording()) { stopScreenRecording(); @@ -5618,6 +5729,7 @@ public void onFPSLimitChanged(int limit) { if (xServerView != null) { xServerView.getRenderer().setFpsLimit(runtimeFpsLimit); } + if (waylandSession != null) waylandSession.setFpsLimit(runtimeFpsLimit); applyPreferredRefreshRate(); if (shortcut != null) { shortcut.putExtra("fpsLimit", runtimeFpsLimit > 0 ? String.valueOf(runtimeFpsLimit) : null); @@ -6260,6 +6372,11 @@ public void onTaskManagerCpuExpandedChanged(boolean expanded) { @Override public void onTaskManagerEndProcess(String name) { + if (gamescopeMode) { + Integer pid = linuxTaskPids.get(name); + if (pid != null) ProcessHelper.terminateProcess(pid); + return; + } if (winHandler != null) winHandler.killProcess(name); } @@ -6381,6 +6498,10 @@ public boolean isControllerConnected() { } private void startTaskManagerPolling() { + if (gamescopeMode) { + startLinuxTaskManagerPolling(); + return; + } if (winHandler == null) return; stopTaskManagerPolling(); winHandler.setOnGetProcessInfoListener(new OnGetProcessInfoListener() { @@ -6403,12 +6524,55 @@ public void run() { }, 0, 1000); } + /** + * A GameScope session has no Wine to ask, so its processes are read from /proc. The scan + * touches a few files per process and so runs off the UI thread; only the finished list is + * handed back to it. + */ + private void startLinuxTaskManagerPolling() { + stopTaskManagerPolling(); + Timer timer = new Timer(); + taskManagerTimer = timer; + timer.schedule(new TimerTask() { + @Override + public void run() { + ArrayList<LinuxTaskList.Task> tasks = LinuxTaskList.list(); + runOnUiThread(() -> { + if (taskManagerTimer != timer) return; + pushLinuxTaskManagerProcesses(tasks); + pushTaskManagerSystemStats(); + }); + } + }, 0, 1000); + } + + /** UI thread. */ + private void pushLinuxTaskManagerProcesses(ArrayList<LinuxTaskList.Task> tasks) { + if (drawerStateHolder == null) return; + linuxTaskPids.clear(); + ArrayList<TaskManagerProcess> processes = new ArrayList<>(); + for (LinuxTaskList.Task task : tasks) { + linuxTaskPids.put(task.name, task.pid); + processes.add(new TaskManagerProcess( + task.pid, task.name, task.memory, task.affinityMask, false)); + } + TaskManagerPaneState current = drawerStateHolder.getTaskManagerState(); + drawerStateHolder.setTaskManagerState(new TaskManagerPaneState( + processes, + current.getCpuPercent(), + current.getCpuCoreCount(), + current.getCpuCorePercents(), + current.getMemoryPercent(), + current.getMemoryDetail())); + } + private void stopTaskManagerPolling() { if (taskManagerTimer != null) { taskManagerTimer.cancel(); taskManagerTimer = null; } if (winHandler != null) winHandler.setOnGetProcessInfoListener(null); + linuxTaskPids.clear(); taskManagerAccum.clear(); taskManagerCpuExpanded = false; prevTaskCpuSample = null; @@ -6829,6 +6993,7 @@ private boolean handleDrawerAction(int itemId) { break; case R.id.main_menu_keyboard: AppUtils.showKeyboard(this); + if (waylandSession != null) waylandSession.onUserToggledKeyboard(); closeDrawerMenu(); break; case R.id.main_menu_controller_manager: @@ -6875,6 +7040,7 @@ private boolean handleDrawerAction(int itemId) { case R.id.main_menu_toggle_fullscreen: renderer.toggleFullscreen(); touchpadView.toggleFullscreen(); + syncWaylandScaleMode(); renderDrawerMenu(); break; case R.id.main_menu_refactor_size: @@ -7237,6 +7403,7 @@ private boolean requiresDisplayReady(int itemId) { @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); + if (waylandSession != null) waylandSession.onWindowFocusChanged(hasFocus); if (hasFocus && shouldUsePointerCapture()) { updatePointerCapture(); @@ -7577,6 +7744,7 @@ private void setupWineSystemFiles() { " rootDir=" + container.getRootDir().getAbsolutePath()); ensureWinePrefixReady(); + applyWaylandRegistry(); ensureLaunchRuntimeFilesReady(); String appVersion = String.valueOf(AppUtils.getVersionCode(this)); @@ -7931,6 +8099,13 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { String rootPath = imageFs.getRootDir().getPath(); FileUtils.clear(imageFs.getTmpDir()); + if (gamescopeMode) { + setupLinuxSession(rootPath); + return; + } + if (container != null && container.isGamescopeRuntime() && !isDependencyInstall) { + throw new LinuxSessionUnavailable(getString(R.string.linux_client_gpu_unsupported)); + } guestProgramLauncherComponent = new GuestProgramLauncherComponent( contentsManager, @@ -7941,6 +8116,7 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { if (container != null) { guestProgramLauncherComponent.setContainer(this.container); guestProgramLauncherComponent.setWineInfo(this.wineInfo); + guestProgramLauncherComponent.setWaylandMode(waylandMode); GameFixes.applyForLaunch(container, shortcut); @@ -8011,9 +8187,7 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { String rawShortcutFEXCorePreset = (shortcut != null && !shortcutUsesContainerDefaults()) ? shortcut.getExtra("fexcorePreset") : ""; - String effectiveFEXCorePreset = shortcut != null - ? getShortcutSetting("fexcorePreset", container.getFEXCorePreset()) - : container.getFEXCorePreset(); + String effectiveFEXCorePreset = effectiveFEXCorePreset(); Log.d("XServerDisplayActivity", "FEXCore preset source=shortcutOrContainer shortcutRaw='" + rawShortcutFEXCorePreset + "' container='" + container.getFEXCorePreset() + "' effective='" + effectiveFEXCorePreset + "'"); @@ -8064,12 +8238,15 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { UnixSocketConfig.createSocket(rootPath, UnixSocketConfig.SYSVSHM_SERVER_PATH) ) ); - environment.addComponent( - new XServerComponent( - xServer, - UnixSocketConfig.createSocket(rootPath, UnixSocketConfig.XSERVER_PATH) - ) - ); + // The embedded compositor is the display server on Wayland, so the X server stays off. + if (!waylandMode) { + environment.addComponent( + new XServerComponent( + xServer, + UnixSocketConfig.createSocket(rootPath, UnixSocketConfig.XSERVER_PATH) + ) + ); + } if (audioDriver.equals("alsa")) { envVars.put("ANDROID_ALSA_SERVER", rootPath + UnixSocketConfig.ALSA_SERVER_PATH); @@ -8559,6 +8736,7 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { getShortcutSetting(NetworkingSettings.EXTRA_DRIVER, container.getExtra(NetworkingSettings.EXTRA_DRIVER, NetworkingSettings.DEFAULT_DRIVER)), getShortcutSetting(NetworkingSettings.EXTRA_MAC, container.getExtra(NetworkingSettings.EXTRA_MAC, ""))); + if (waylandMode) applyWaylandLaunchEnv(envVars); guestProgramLauncherComponent.setEnvVars(envVars); guestProgramLauncherComponent.setTerminationCallback((status) -> { LogManager.log(TAG, "Guest process [" + guestProgramLauncherComponent.getGuestExecutable() + "] terminated with status: " + status, this); @@ -8625,6 +8803,13 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException { } } + // Wayland has no X11 window-content hook to clear the launch overlay, and the compositor's + // first-frame callback never fires for a desktop that only ever presents shm. Clear it on a + // timer so the guest is never hidden behind a stuck spinner. + if (waylandMode) { + new Handler(getMainLooper()).postDelayed(this::onFirstGuestWindow, WAYLAND_OVERLAY_GRACE_MS); + } + winHandler.start(); com.winlator.cmod.shared.ui.controllertest.ControllerTestBus.setDialogOpen(false); runOnUiThread(() -> { @@ -8643,6 +8828,516 @@ private void createWrapperScript(String path, String content) { scriptFile.setExecutable(true); } + /** + * The first application window (X11: window content; Wayland: the first presented frame). + * Dismisses the launch overlay, reveals the controls and applies the saved fullscreen choice. + */ + private void onFirstGuestWindow() { + if (!firstGuestWindowShown.compareAndSet(false, true)) return; + if (!isMouseDisabled) { + if (touchpadView != null) touchpadView.setMouseEnabled(true); + } else if (xServerView != null) { + xServerView.getRenderer().setCursorVisible(false); + } + if (!wnLauncherDrivesDismiss.get()) { + preloaderDialog.closeOnUiThread(); + stopWnLauncherStatusTailer(); + } + runOnUiThread(() -> { + inputControlsRevealAllowed = true; + if (inputControlsView != null) { + ControlsProfile activeProfile = inputControlsView.getProfile(); + if (activeProfile != null) showInputControls(activeProfile); + else startTouchscreenTimeout(); + } + }); + if (startFullscreenStretched) { + timeoutHandler.post(() -> { + if (activityDestroyed.get()) return; + VulkanRenderer r = xServerView != null ? xServerView.getRenderer() : null; + if (r != null && !r.isFullscreen()) { + r.toggleFullscreen(); + if (touchpadView != null) touchpadView.toggleFullscreen(); + syncWaylandScaleMode(); + renderDrawerMenu(); + } + }); + } + } + + /** + * Resolves the session's display server: the shortcut's choice, else the container's, and + * only when the device and the selected Wine/Proton can drive the compositor. A reattached + * background session keeps whatever it was started with. + */ + private boolean hasPendingContainerOverride() { + if (shortcut == null || container == null) return false; + String id = shortcut.getExtra("container_id"); + if (id == null || id.isEmpty()) return false; + try { + return Integer.parseInt(id) != container.id; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * A gamescope container: proot runs the Linux runtime's session script under gamescope, which + * is a Wayland client of the compositor started by {@link #startWaylandSession}. The script + * starts gamescope itself from WN_WIDTH/WN_HEIGHT/WN_FPS and logs to WN_LOG. Nothing of Wine + * is involved; the audio socket is the only imagefs service the guest reaches. + */ + private void setupLinuxSession(String rootPath) { + if (com.winlator.cmod.runtime.linux.LinuxClientInstaller.INSTANCE.isWorking()) { + throw new LinuxSessionUnavailable(getString(R.string.linux_client_busy)); + } + if (!LinuxRuntime.isInstalled(this)) { + throw new LinuxSessionUnavailable(getString(R.string.linux_runtime_missing)); + } + if (!com.winlator.cmod.runtime.linux.LinuxClientInstaller.hasCompositorDriver(this)) { + throw new LinuxSessionUnavailable(getString(R.string.linux_client_driver_missing)); + } + try { + LinuxRuntime.writeAccounts(this); + LinuxRuntime.syncSessionFiles(this); + // A session that is being rejoined has its client running, and the files are the client's then. + if (!reusingSession) com.winlator.cmod.runtime.linux.LinuxSteamLogin.seed(this); + if (!reusingSession) LinuxRuntime.clearSharedMemory(this); + } catch (IOException e) { + throw new IllegalStateException(e); + } + com.winlator.cmod.runtime.linux.LinuxProtons.INSTANCE.reconcile(this); + com.winlator.cmod.runtime.linux.LinuxDriverChoices.write(this); + List<String> session = linuxSessionArgs(); + File runtimeDir = GuestProgramLauncherComponent.getWaylandRuntimeDir(this); + runtimeDir.mkdirs(); + + environment = new XEnvironment(this, imageFs); + List<String> guest = new ArrayList<>(); + guest.add("/usr/bin/env"); + guest.add("-i"); + guest.add("HOME=/root"); + guest.add("USER=root"); + guest.add("PATH=/usr/local/bin:/usr/bin:/bin"); + guest.add("TERM=xterm-256color"); + guest.add("LANG=C.UTF-8"); + guest.add("TZ=" + java.util.TimeZone.getDefault().getID()); + guest.add("XDG_RUNTIME_DIR=" + runtimeDir.getPath()); + guest.add("XDG_SESSION_TYPE=wayland"); + guest.add("WAYLAND_DISPLAY=wayland-0"); + guest.add("GAMESCOPE_FORCE_GENERAL_QUEUE=1"); + // The session's own preloads are named by /etc/ld.so.preload in the runtime, not here: + // the Steam client rebuilds LD_PRELOAD for every process it starts and appends its overlay + // without a separator, which silently drops whatever was already in the variable. + File devInputDir = new File(imageFs.getRootDir(), "dev/input"); + FakeInputWriter.prepareRingSlots(devInputDir, 4); + String inputRings = FakeInputWriter.getRingEnv(devInputDir); + if (!inputRings.isEmpty()) guest.add("FAKE_EVDEV_MEMFD_PATHS=" + inputRings); + guest.add("FAKE_EVDEV_DIR=" + devInputDir.getPath()); + guest.add("FAKE_EVDEV_VIBRATION=1"); + // Steam Input hides a pad it manages from the game and shows it a virtual one instead, which + // needs /dev/uinput: the pads carry that identity themselves for everything but the client. + guest.add("FAKE_EVDEV_STEAM_VIRTUAL=1"); + // No udev runs in the runtime: SDL and Steam's hidapi must scan /dev/input themselves. + guest.add("SDL_JOYSTICK_DISABLE_UDEV=1"); + guest.add("SDL_HIDAPI_JOYSTICK_DISABLE_UDEV=1"); + guest.add("SDL_JOYSTICK_HIDAPI=0"); + guest.add("MESA_LOADER_DRIVER_OVERRIDE=zink"); + guest.add("GALLIUM_DRIVER=zink"); + guest.add("LIBGL_KOPPER_DRI2=true"); + File icd = LinuxRuntime.vulkanIcd(this, graphicsDriverConfig != null ? graphicsDriverConfig.get("version") : null); + if (icd != null) guest.add("VK_ICD_FILENAMES=" + icd.getPath()); + // A container made before it was a Linux one still carries the Android side's variables, + // so it is read here as a Linux session reads it rather than as it was saved. + EnvVars userEnv = new EnvVars(EnvVarsView.forGamescope(effectiveUserEnv().toString())); + // The client, its web helper and native games only know PulseAudio, so it runs whatever + // the entry chose; DirectAudio takes the Windows games off it. The server is built from the + // entry's own variables: the activity's hold only the few a Wine session starts with. + PulseAudioComponent.Options pulseOptions = PulseAudioComponent.Options.fromEnvVars(userEnv); + guest.add("PULSE_SERVER=unix:" + rootPath + UnixSocketConfig.PULSE_SERVER_PATH); + guest.add("PULSE_LATENCY_MSEC=" + pulseOptions.latencyMillis); + environment.addComponent( + new PulseAudioComponent( + UnixSocketConfig.createSocket(rootPath, UnixSocketConfig.PULSE_SERVER_PATH), + pulseOptions)); + if (DirectAudioDriver.INSTANCE.isSelected(audioDriver)) addLinuxDirectAudio(rootPath, guest); + // The games the client starts run under FEX, which a Wine session configures from the + // container's preset. Nothing did so here, so anything the client launched ran on FEX's + // bare defaults - single-block translation, no store ordering - and a multithreaded x86 + // title can sit at its loading screen for good waiting on a store it never sees. The + // user's own variables are merged over the preset, so an explicit one still wins. + EnvVars sessionEnv = FEXCorePresetManager.getEnvVars(this, effectiveFEXCorePreset()); + FEXCorePresetManager.normalizeSmcChecksEnvVars(sessionEnv, userEnv); + sessionEnv.putAll(userEnv); + for (String entry : sessionEnv.toStringArray()) { + if (!entry.startsWith("PROOT_NO_SECCOMP=")) guest.add(entry); + } + guest.add("WN_WIDTH=" + xServer.screenInfo.width); + guest.add("WN_HEIGHT=" + xServer.screenInfo.height); + guest.add("WN_FPS=" + Math.max(0, runtimeFpsLimit)); + // What gamescope advertises when no limit is set, and what a game reads as the display's: + // left out, gamescope says 60 and titles cap themselves there on a faster panel. + android.view.Display panel = getDisplayCompat(); + int panelHz = panel != null ? Math.round(panel.getRefreshRate()) : 0; + if (panelHz > 1) guest.add("WN_REFRESH=" + panelHz); + File logDir = com.winlator.cmod.runtime.system.LogManager.getSessionLogsDir(this); + File linuxLog = new File(logDir, "linux-session-" + java.time.LocalDateTime.now().format( + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss", java.util.Locale.US)) + ".log"); + guest.add("WN_LOG=" + linuxLog.getPath()); + guest.add(LinuxRuntime.SESSION_SCRIPT); + guest.addAll(session); + + com.winlator.cmod.runtime.linux.LinuxNetworkLinkComponent networkLink = + new com.winlator.cmod.runtime.linux.LinuxNetworkLinkComponent( + this, LinuxRuntime.rootDir(this), + NetworkingSettings.driverOrDefault(getShortcutSetting(NetworkingSettings.EXTRA_DRIVER, + container.getExtra(NetworkingSettings.EXTRA_DRIVER, NetworkingSettings.DEFAULT_DRIVER))), + getShortcutSetting(NetworkingSettings.EXTRA_MAC, + container.getExtra(NetworkingSettings.EXTRA_MAC, ""))); + networkLink.publish(); + environment.addComponent(networkLink); + + EnvVars hostEnv = LinuxRuntime.hostEnvironment(this, sessionEnv); + List<String> binds = new ArrayList<>(com.winlator.cmod.feature.library.LinuxSteamLibrary.prepare( + this, LinuxRuntime.rootDir(this))); + if (!reusingSession) binds.addAll(com.winlator.cmod.runtime.linux.LinuxSteamShortcuts.sync(this)); + com.winlator.cmod.runtime.linux.LinuxEpicTokens.start(this); + List<String> command = LinuxRuntime.command(this, imageFs, runtimeDir, + android.os.Environment.getExternalStorageDirectory(), devInputDir, binds, guest); + final long startedAt = android.os.SystemClock.elapsedRealtime(); + LinuxProgramLauncherComponent launcher = new LinuxProgramLauncherComponent( + command, hostEnv, LinuxRuntime.rootDir(this), linuxLog, (status) -> { + com.winlator.cmod.runtime.linux.LinuxEpicTokens.stop(); + LogManager.log(TAG, "Linux session [" + String.join(" ", session) + + "] ended with status: " + status, this); + // A session nobody asked to end did not end well, whenever it went. Leaving + // without a word is what a crash looks like from the outside, and it leaves + // the one person who can send the log with no reason to go and find it. + boolean unexpected = status != 0 && !exitRequested.get(); + if (!unexpected) { + exit(); + } else if (status == SIGKILL_STATUS) { + reportLaunchFailure(new LinuxSessionUnavailable(getString(R.string.linux_session_trimmed))); + } else if (android.os.SystemClock.elapsedRealtime() - startedAt < LINUX_SESSION_START_MS) { + reportLaunchFailure(new LinuxSessionUnavailable(getString(R.string.linux_session_failed))); + } else { + reportLaunchFailure(new LinuxSessionUnavailable(getString(R.string.linux_session_stopped, status))); + } + }); + environment.addComponent(launcher); + winHandler.preAssignConnectedControllers(); + if (!reusingSession) { + if (preloaderDialog != null) { + preloaderDialog.setStepOnUiThread(R.string.preloader_launching); + } + environment.startEnvironmentComponents(); + if (backgroundSessionEnabled) { + SessionKeepAliveService.setActiveEnvironment(environment); + SessionKeepAliveService.setActiveXServer(xServer); + SessionKeepAliveService.setLinuxSessionActive(true); + } + } + new Handler(getMainLooper()).postDelayed(this::onFirstGuestWindow, WAYLAND_OVERLAY_GRACE_MS); + winHandler.start(); + com.winlator.cmod.shared.ui.controllertest.ControllerTestBus.setDialogOpen(false); + runOnUiThread(() -> { + steamControllerSessionReady = true; + startSteamControllerSupport(); + }); + } + + /** + * The client may have installed titles during the session; the library learns of them once it + * is over. Off the UI thread, since it reads manifests and writes the store's records. + */ + private void adoptClientInstalls() { + Context appContext = getApplicationContext(); + new Thread(() -> { + boolean changed; + try { + changed = com.winlator.cmod.feature.library.LinuxSteamLibrary.adoptClientInstalls( + appContext, LinuxRuntime.rootDir(appContext)); + } catch (RuntimeException e) { + Log.w("XServerDisplayActivity", "Could not adopt the client's installs", e); + return; + } + if (changed) { + new Handler(Looper.getMainLooper()).post( + com.winlator.cmod.app.shell.UnifiedActivity.Companion::refreshLibrary); + } + }, "linux-library").start(); + } + + /** What the session script runs: the desktop, a Linux program, or the native Steam client. */ + private List<String> linuxSessionArgs() { + List<String> args = new ArrayList<>(); + if (shortcut == null) { + args.add(LinuxRuntime.MODE_DESKTOP); + return args; + } + if (com.winlator.cmod.feature.library.LinuxApps.isSteamClientShortcut(shortcut)) { + args.add(LinuxRuntime.MODE_STEAM); + return args; + } + if (com.winlator.cmod.feature.library.LinuxApps.isLinuxShortcut(shortcut)) { + args.add(LinuxRuntime.MODE_RUN); + args.add(shortcut.getExtra("custom_exe")); + return args; + } + if ("STEAM".equals(shortcut.getExtra("game_source"))) { + args.add(LinuxRuntime.MODE_STEAM); + String appId = shortcut.getExtra("app_id"); + if (!appId.isEmpty()) args.add("steam://rungameid/" + appId); + return args; + } + String nonSteamGame = com.winlator.cmod.runtime.linux.LinuxSteamShortcuts.launchUrl(this, shortcut); + if (nonSteamGame != null) { + args.add(LinuxRuntime.MODE_STEAM); + args.add(nonSteamGame); + return args; + } + throw new LinuxSessionUnavailable(getString(R.string.linux_runtime_windows_program)); + } + + private void resolveDisplayBackend() { + boolean reattaching = SessionKeepAliveService.isSessionActive() + && SessionKeepAliveService.getActiveEnvironment() != null + && SessionKeepAliveService.getActiveXServer() != null; + if (reattaching) { + waylandMode = WaylandSession.hasActiveSession(); + gamescopeMode = waylandMode && SessionKeepAliveService.isLinuxSessionActive(); + return; + } + gamescopeMode = container.isGamescopeRuntime() && !isDependencyInstall; + if (gamescopeMode) { + // gamescope is a client of the compositor; the Wine checks below do not apply to it. + if (!WineWaylandSupport.isAdrenoDevice(this)) { + // Nothing of a GameScope container can run as a Wine session; the launch says so. + Log.w(TAG, "gamescope: this GPU cannot drive the compositor"); + gamescopeMode = false; + } else { + waylandMode = true; + Log.i(TAG, "display server: Wayland (gamescope)"); + return; + } + } + String backend = shortcut != null + ? getShortcutSetting(Container.EXTRA_DISPLAY_BACKEND, container.getDisplayBackend()) + : container.getDisplayBackend(); + boolean wanted = Container.DISPLAY_BACKEND_WAYLAND.equals(backend) && !isDependencyInstall; + if (wanted && !(WineWaylandSupport.isAdrenoDevice(this) && WineWaylandSupport.isWaylandCapable(wineInfo))) { + Log.w(TAG, "wayland: " + wineVersion + " (" + wineInfo.path + + ") or this GPU cannot drive the compositor; launching on X11"); + // A shortcut that swaps the container resolves again against it; announcing the + // fallback here would name a container the session never launches in. + if (!hasPendingContainerOverride()) { + android.widget.Toast.makeText(this, R.string.wayland_unavailable_fallback, + android.widget.Toast.LENGTH_LONG).show(); + } + wanted = false; + } + waylandMode = wanted; + Log.i(TAG, "display server: " + (waylandMode ? "Wayland" : "X11")); + } + + private static boolean envFlag(EnvVars env, String name, boolean fallback) { + String v = env != null ? env.get(name) : null; + if (v == null || v.isEmpty()) return fallback; + return v.equals("1") || v.equalsIgnoreCase("true") || v.equalsIgnoreCase("on"); + } + + /** The FEXCore preset a launch runs under: the shortcut's when it has one, else the container's. */ + private String effectiveFEXCorePreset() { + return shortcut != null + ? getShortcutSetting("fexcorePreset", container.getFEXCorePreset()) + : container.getFEXCorePreset(); + } + + private EnvVars effectiveUserEnv() { + String raw = shortcut != null + ? getShortcutSetting("envVars", container.getEnvVars()) + : container.getEnvVars(); + return raw != null && !raw.isEmpty() ? new EnvVars(raw) : new EnvVars(); + } + + private void startWaylandSession(FrameLayout rootView, int index) { + WaylandSession.Config cfg = new WaylandSession.Config(); + boolean hasDriver = false; + try { + hasDriver = resolveCompositorDriver(cfg); + } catch (Exception e) { + Log.e(TAG, "wayland: compositor driver resolve failed", e); + } + // The compositor keeps the driver it started with for the life of the process, so one + // started without a Turnip would stay black after the driver is installed. The launch of + // a Linux session reports the missing driver itself. + if (!hasDriver && gamescopeMode) return; + cfg.hideShell = shortcut != null || (bootExePath != null && !bootExePath.isEmpty()); + try { + android.view.Display display = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + ? getDisplay() : getWindowManager().getDefaultDisplay(); + cfg.refreshHz = display != null ? display.getRefreshRate() : 60f; + } catch (Exception e) { + cfg.refreshHz = 60f; + } + cfg.outputWidth = xServer.screenInfo.width; + cfg.outputHeight = xServer.screenInfo.height; + cfg.fpsLimit = runtimeFpsLimit; + EnvVars env = effectiveUserEnv(); + cfg.zeroCopy = envFlag(env, "BANNER_WAYLAND_ZERO_COPY", false); + cfg.ubwc = envFlag(env, "BANNER_WAYLAND_UBWC", true); + cfg.noRenderNode = envFlag(env, "BANNER_WAYLAND_NO_RENDER_NODE", false); + VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; + cfg.scaleMode = renderer != null && renderer.isFullscreen() + ? WaylandCompositor.SCALE_STRETCH : WaylandCompositor.SCALE_FIT; + cfg.logDir = com.winlator.cmod.runtime.system.LogManager.getSessionLogsDir(this); + boolean reattach = WaylandSession.hasActiveSession() + && SessionKeepAliveService.isSessionActive() + && SessionKeepAliveService.getActiveEnvironment() != null; + + waylandSession = new WaylandSession(this, new WaylandSession.Host() { + @Override + public void onPointerLockChanged(boolean locked) { + if (locked) updatePointerCapture(); + } + + @Override + public void onGameSurface(boolean present, String gpuName) { + if (frameRating == null) return; + boolean wanted = present && (effectiveShowFPS || controllerHudMode); + frameRating.setVisibility(wanted ? View.VISIBLE : View.GONE); + } + + @Override + public void onGameFrame() { + if (frameRating != null && (effectiveShowFPS || controllerHudMode)) { + frameRating.recordGameFrame(true, waylandFrameSerial.incrementAndGet()); + } + if (mangoHud != null) mangoHud.recordGameFrame(true); + } + + @Override + public void onFirstFrame() { + onFirstGuestWindow(); + } + + @Override + public void onGameProgram(int pid, String program) { + waylandGamePid = pid; + } + }, xServer, winHandler); + waylandSession.attach(rootView, index, cfg, reattach); + } + + /** + * Picks the Turnip the compositor imports the guest's frames with. The stock Vulkan driver has + * no VK_EXT_image_drm_format_modifier, so vkCreateDevice fails and the session shows nothing; + * a shortcut or container left on "System" therefore falls back to the container's own driver + * and then to any installed one, the way the X11 path never needs to. A GameScope container's + * driver setting names its Linux Turnip, so there the WN Turnip the Linux Client install + * fetched comes first; its swapchain carries the UBWC usage the display expects. + */ + private boolean resolveCompositorDriver(WaylandSession.Config cfg) { + AdrenotoolsManager atm = new AdrenotoolsManager(this); + ArrayList<String> installed = atm.enumarateInstalledDrivers(); + ArrayList<String> candidates = new ArrayList<>(); + if (gamescopeMode) { + for (String driverId : installed) { + if (com.winlator.cmod.runtime.linux.LinuxClientInstaller.isCompositorDriver(atm, driverId)) { + candidates.add(driverId); + } + } + } + if (graphicsDriverConfig != null) candidates.add(graphicsDriverConfig.get("version")); + candidates.add(GraphicsDriverConfigUtils + .parseGraphicsDriverConfig(container.getGraphicsDriverConfig()).get("version")); + candidates.addAll(installed); + for (String driverId : candidates) { + if (!atm.isTurnipDriver(driverId)) continue; + String libraryName = atm.getLibraryName(driverId); + if (libraryName == null || libraryName.isEmpty()) continue; + cfg.driverPath = atm.getDriverPath(driverId); + cfg.libraryName = libraryName; + Log.i(TAG, "wayland: compositor driver '" + driverId + "'"); + return true; + } + Log.w(TAG, "wayland: no Turnip installed; the compositor cannot import the guest's frames"); + if (!gamescopeMode) { + android.widget.Toast.makeText(this, R.string.wayland_needs_turnip_driver, + android.widget.Toast.LENGTH_LONG).show(); + } + return false; + } + + private void syncWaylandScaleMode() { + if (waylandSession == null || xServerView == null) return; + VulkanRenderer r = xServerView.getRenderer(); + if (r == null) return; + waylandSession.setScaleMode(r.isFullscreen() + ? WaylandCompositor.SCALE_STRETCH : WaylandCompositor.SCALE_FIT); + } + + /** The activity is going away; a background session keeps the compositor and the guest. */ + private void detachWaylandSession() { + WaylandSession session = waylandSession; + if (session != null) session.detach(); + } + + /** The session is over: the compositor disconnects the guest and resets for the next one. */ + private void endWaylandSession() { + WaylandSession session = waylandSession; + waylandSession = null; + if (session != null) { + session.end(); + } else if (WaylandSession.hasActiveSession() && waylandMode) { + WaylandCompositor.nativeEndSession(); + } + } + + /** + * Guest-side environment of a Wayland session: the game's Wayland Turnip variant, Mesa's + * threaded GL context off (its helper thread crashes outside Wine's signal handling), the + * gralloc swapchain hint for zero-copy, and winex11.drv disabled so Wine loads winewayland. + */ + private void applyWaylandLaunchEnv(EnvVars envVars) { + WaylandGameDriver.applyToLaunchEnv(this, envVars, new File(wineInfo.path)); + if (!envVars.has("GALLIUM_THREAD")) envVars.put("GALLIUM_THREAD", "0"); + if (envFlag(envVars, "BANNER_WAYLAND_ZERO_COPY", false) && !envVars.has("BANNER_WSI_AHB")) { + envVars.put("BANNER_WSI_AHB", "1"); + } + String overrides = envVars.has("WINEDLLOVERRIDES") ? envVars.get("WINEDLLOVERRIDES") : ""; + if (!overrides.contains("winex11.drv")) { + envVars.put("WINEDLLOVERRIDES", overrides.isEmpty() ? "winex11.drv=d" : overrides + ";winex11.drv=d"); + } + } + + /** + * Wine picks its graphics driver from the prefix registry. A Wayland session selects + * winewayland and seeds the "shell" desktop so every process of the session, explorer's own + * threads included, is born on it; an X11 session undoes both, so a prefix is never left on + * a driver the session cannot serve. + */ + private void applyWaylandRegistry() { + File userRegFile = new File(container.getRootDir(), ".wine/user.reg"); + final boolean wayland = waylandMode; + final String size = xServer.screenInfo.width + "x" + xServer.screenInfo.height; + WaylandPrefixRegistry.edit(userRegFile, reg -> { + if (wayland) { + reg.set("Software\\Wine\\Drivers", "Graphics", "wayland"); + reg.set("Software\\Wine\\Explorer\\Desktops", "shell", size); + reg.remove("Software\\Wine\\Explorer", "Desktop"); + } else { + if ("wayland".equals(reg.get("Software\\Wine\\Drivers", "Graphics"))) { + reg.set("Software\\Wine\\Drivers", "Graphics", "x11"); + } + if ("shell".equals(reg.get("Software\\Wine\\Explorer", "Desktop"))) { + reg.remove("Software\\Wine\\Explorer", "Desktop"); + reg.remove("Software\\Wine\\Explorer\\Desktops", "shell"); + } + } + }); + } + private void setupUI() { FrameLayout rootView = xServerDisplayFrame; xServerView = new XServerSurfaceView(this, xServer); @@ -8683,9 +9378,15 @@ private void setupUI() { applyScreenEffects(); xServer.setRenderer(renderer); rootView.addView(xServerView); + if (waylandMode) { + xServerView.setVisibility(View.GONE); + startWaylandSession(rootView, rootView.indexOfChild(xServerView) + 1); + } globalCursorSpeed = preferences.getFloat("cursor_speed", 1.0f); touchpadView = new TouchpadView(this, xServer, timeoutHandler, hideControlsRunnable); + touchpadView.setTouchscreenSink((action, rawX, rawY) -> + waylandSession != null && waylandSession.sendTouch(action, rawX, rawY)); touchpadView.setTapToClickEnabled(isTapToClickEnabled); touchpadView.setSensitivity(globalCursorSpeed); touchpadView.setMouseEnabled(!isMouseDisabled); @@ -8702,6 +9403,7 @@ private void setupUI() { inputControlsView.setReverseBindingOrder(preferences.getBoolean("reverse_binding_order", false)); inputControlsView.setTouchpadView(touchpadView); inputControlsView.setXServer(xServer); + inputControlsView.setGuideButtonShown(gamescopeMode); inputControlsView.setAdaptiveJoysticks(isAdaptiveJoysticksEnabled()); applyTouchscreenOverlayPreference(); applyInputVisualStylePreferences(); @@ -8750,7 +9452,9 @@ private void setupUI() { } String simTouchScreen = shortcut.getExtra("simTouchScreen"); - int touchModeFallback = simTouchScreen.equals("1") ? 1 : 0; + // A Linux session is a desktop or the Steam client: a tap is a click where it lands + // unless the entry says otherwise. + int touchModeFallback = simTouchScreen.equals("1") || gamescopeMode ? 1 : 0; screenTouchMode = parseSettingInt( shortcut.getExtra("screenTouchMode", String.valueOf(touchModeFallback)), touchModeFallback); @@ -8759,6 +9463,9 @@ private void setupUI() { if (winHandler != null) winHandler.setScreenTouchStickActive(screenTouchMode == 2); rtsGesturesEnabled = shortcut.getExtra("rtsGestures", "0").equals("1"); touchpadView.setRtsGesturesEnabled(rtsGesturesEnabled); + } else if (gamescopeMode) { + screenTouchMode = 1; + touchpadView.setScreenTouchMode(screenTouchMode); } if (rtsGesturesEnabled) pushSelectedGestureConfig(); @@ -9199,6 +9906,10 @@ private static String decodeManifest(byte[] blob) { } private boolean ensureRequestedWineVersionInstalled() { + // A GameScope session starts the Linux runtime and never Wine. + if (container.isGamescopeRuntime() && !isDependencyInstall && WineWaylandSupport.isAdrenoDevice(this)) { + return true; + } if (SetupWizardActivity.isWineVersionInstalled(this, wineVersion)) { return true; } @@ -13253,8 +13964,33 @@ private boolean ensureDirectAudioInstalled() { return directAudioAvailable; } + /** + * Worker thread. A session the driver cannot be staged for keeps PulseAudio for its games too: + * without WN_DIRECTAUDIO the session script takes the driver's name out of their prefixes. + */ + private void addLinuxDirectAudio(String rootPath, List<String> guest) { + try { + DirectAudioHost.stage(this, LinuxRuntime.rootDir(this)); + } catch (IOException e) { + Log.w("XServerDisplayActivity", "DirectAudio could not be staged for the Linux session", e); + runOnUiThread(() -> android.widget.Toast.makeText( + this, R.string.directaudio_unavailable, android.widget.Toast.LENGTH_LONG).show()); + return; + } + boolean micRequested = DirectAudioDriver.INSTANCE.isMicEnabled( + getShortcutSetting(DirectAudioDriver.EXTRA_MIC, container.getExtra(DirectAudioDriver.EXTRA_MIC))); + boolean micExposed = DirectAudioDriver.INSTANCE.shouldExposeMic(this, micRequested); + File socket = new File(rootPath, DirectAudioHost.SOCKET_PATH); + guest.add(DirectAudioHost.ENV_ENABLED + "=1"); + guest.add(DirectAudioHost.ENV_SOCKET + "=" + socket.getPath()); + if (micExposed) guest.add(DirectAudioDriver.ENV_MIC + "=1"); + environment.addComponent( + new DirectAudioHost(socket, micExposed)); + } + private void resolveAudioDriver() { - if (!DirectAudioDriver.INSTANCE.isSelected(audioDriver)) return; + // A Linux session stages its own build of the driver, see setupLinuxSession(). + if (gamescopeMode || !DirectAudioDriver.INSTANCE.isSelected(audioDriver)) return; if (ensureDirectAudioInstalled()) return; Log.w("XServerDisplayActivity", "DirectAudio is unavailable for this container; falling back to " + Container.DEFAULT_AUDIO_DRIVER + " so mmdevapi keeps a loadable backend"); diff --git a/app/src/main/runtime/display/environment/components/GuestProgramLauncherComponent.java b/app/src/main/runtime/display/environment/components/GuestProgramLauncherComponent.java index b5409d1c9..4425bd239 100644 --- a/app/src/main/runtime/display/environment/components/GuestProgramLauncherComponent.java +++ b/app/src/main/runtime/display/environment/components/GuestProgramLauncherComponent.java @@ -19,6 +19,7 @@ import com.winlator.cmod.runtime.display.connector.UnixSocketConfig; import com.winlator.cmod.runtime.display.environment.EnvironmentComponent; import com.winlator.cmod.runtime.display.environment.ImageFs; +import com.winlator.cmod.runtime.display.wayland.WineWaylandSupport; import com.winlator.cmod.runtime.input.controls.FakeInputWriter; import com.winlator.cmod.runtime.system.GPUInformation; import com.winlator.cmod.runtime.system.ProcessHelper; @@ -58,6 +59,169 @@ public class GuestProgramLauncherComponent extends EnvironmentComponent { private Runnable preUnpackCallback; private boolean fexUnixLibsActive = false; + // The session runs through winewayland.drv into the embedded compositor instead of the X server. + private boolean waylandMode = false; + + public void setWaylandMode(boolean waylandMode) { + this.waylandMode = waylandMode; + } + + public boolean isWaylandMode() { + return waylandMode; + } + + private static final String GUEST_WAYLAND_LIB_DIR = "wayland-x86_64"; + + /** Directory holding the compositor's wayland-0 socket; the compositor and the guest share it. */ + public static File getWaylandRuntimeDir(Context context) { + return new File(context.getFilesDir(), ".wayland-rt"); + } + + /** + * Brings a stale prefix up to date before the session starts, with no display driver attached. + * + * Wine's ntdll runs {@code wineboot --init} from the session's first process when the prefix's + * .update-timestamp no longer matches the layer's wine.inf. That blocks explorer before main() + * while wineboot installs wine.inf, and wineboot's wait dialog needs a desktop that does not + * exist yet, so win32u spawns its own explorer for it. Doing the update here keeps it off the + * session's critical path. Wine Mono's download prompt is switched off for this step only. + */ + private void updatePrefixBeforeSession(EnvVars guestEnv, String wineLauncher, File rootDir, ImageFs imageFs) { + try { + File wineInf = new File(imageFs.getWinePath(), "share/wine/wine.inf"); + File stamp = new File(rootDir, ImageFs.WINEPREFIX + "/.update-timestamp"); + if (!wineInf.isFile()) return; + long infMtime = wineInf.lastModified() / 1000L; + String current = stamp.isFile() ? FileUtils.readString(stamp) : ""; + if (current == null) current = ""; + current = current.trim(); + if (current.startsWith("disable")) return; + int digits = 0; + while (digits < current.length() && Character.isDigit(current.charAt(digits))) digits++; + if (digits > 0) { + try { + if (Long.parseLong(current.substring(0, digits)) == infMtime) return; + } catch (NumberFormatException ignored) { + } + } + + EnvVars env = new EnvVars(); + env.putAll(guestEnv); + if (waylandMode) { + env.remove("WAYLAND_DISPLAY"); + env.remove("XDG_RUNTIME_DIR"); + env.remove("DISPLAY"); + } + env.put("WINEDLLOVERRIDES", withMscoreeDisabled(env.get("WINEDLLOVERRIDES"))); + Log.i(TAG, "prefix update: .update-timestamp \"" + current + "\" != wine.inf mtime " + infMtime + + "; updating the prefix before the " + (waylandMode ? "Wayland" : "X11") + " session"); + long started = System.currentTimeMillis(); + ProcessHelper.exec(wineLauncher + " wineboot -h", env.toStringArray(), rootDir); + long deadline = started + 180_000; + while (System.currentTimeMillis() < deadline) { + String stamped = stamp.isFile() ? FileUtils.readString(stamp) : ""; + if (stamped != null && stamped.trim().startsWith(String.valueOf(infMtime))) break; + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + deadline = System.currentTimeMillis() + 30_000; + while (!ProcessHelper.listRunningWineProcesses().isEmpty() && System.currentTimeMillis() < deadline) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + if (!ProcessHelper.listRunningWineProcesses().isEmpty()) { + Log.w(TAG, "prefix update: wine processes still alive; terminating them"); + ProcessHelper.terminateAllWineProcesses(); + } + Log.i(TAG, "prefix update: finished in " + (System.currentTimeMillis() - started) + " ms"); + } catch (Throwable t) { + Log.w(TAG, "prefix update: the step before the session failed; launching anyway", t); + } + } + + /** A WINEDLLOVERRIDES value with mscoree disabled appended, so Wine Mono never prompts. */ + static String withMscoreeDisabled(String overrides) { + String value = overrides == null ? "" : overrides.trim(); + while (value.endsWith(";")) value = value.substring(0, value.length() - 1).trim(); + return value.isEmpty() ? "mscoree=d" : value + ";mscoree=d"; + } + + /** + * The compositor hands wl_keyboard clients the xkb keymap from this directory, and Wine only + * connects once wayland-0 exists; the socket is bound on the compositor thread, so the guest + * launch waits for it briefly instead of racing it. + */ + private void prepareWaylandRuntime() { + Context context = environment.getContext(); + File runtimeDir = getWaylandRuntimeDir(context); + if (!runtimeDir.isDirectory() && !runtimeDir.mkdirs()) { + Log.e(TAG, "wayland: cannot create " + runtimeDir); + return; + } + File keymap = new File(runtimeDir, "keymap.xkb"); + if (!keymap.isFile()) { + File tmp = new File(runtimeDir, "keymap.xkb.part"); + try (InputStream in = context.getAssets().open("wayland/keymap.xkb")) { + java.nio.file.Files.copy(in, tmp.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + if (!tmp.renameTo(keymap)) tmp.delete(); + } catch (IOException e) { + Log.e(TAG, "wayland: keymap extract failed", e); + tmp.delete(); + } + } + String winePath = environment.getImageFs().getWinePath(); + if (!WineWaylandSupport.writeManifests(new File(winePath))) { + Log.w(TAG, "wayland: no Turnip manifests written under " + winePath); + } + File socket = new File(runtimeDir, "wayland-0"); + long deadline = System.currentTimeMillis() + 8000; + while (!socket.exists() && System.currentTimeMillis() < deadline) { + try { + Thread.sleep(25); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + if (!socket.exists()) Log.w(TAG, "wayland: " + socket + " not present after 8 s; Wine may not find the compositor"); + } + + /** + * Points Wine at the compositor's socket. winewayland.so's Wayland client libraries ship inside + * the Proton with unversioned sonames, so the Proton's lib dir is put ahead of the image + * libraries; their own dependencies still resolve from the image. + * + * An x86_64 Proton cannot fall back on the image for those dependencies, whose copies there are + * aarch64, so it vendors its own in {@link #GUEST_WAYLAND_LIB_DIR}. That directory is kept off + * LD_LIBRARY_PATH and given to Box64 alone: some of its sonames (libc++_shared.so, libz.so.1) + * are also needed by the native box64 binary, which refuses to start if it links the x86_64 one. + */ + private static void applyWaylandEnv(Context context, ImageFs imageFs, EnvVars envVars, String imageLibPath) { + envVars.remove("DISPLAY"); + envVars.put("WAYLAND_DISPLAY", "wayland-0"); + envVars.put("XDG_RUNTIME_DIR", getWaylandRuntimeDir(context).getPath()); + String wineLibPath = imageFs.getWinePath() + "/lib"; + envVars.put("LD_LIBRARY_PATH", wineLibPath + ":" + imageLibPath); + + File guestDeps = new File(wineLibPath, GUEST_WAYLAND_LIB_DIR); + if (guestDeps.isDirectory()) { + String current = envVars.get("BOX64_LD_LIBRARY_PATH"); + envVars.put( + "BOX64_LD_LIBRARY_PATH", + current == null || current.isEmpty() + ? guestDeps.getPath() + : guestDeps.getPath() + ":" + current); + } + } + public static File ensureImageFsNativeLibrary( Context context, ImageFs imageFs, String libraryName) { File destFile = new File(imageFs.getLibDir(), libraryName); @@ -199,6 +363,9 @@ public String execShellCommand(String command, boolean includeStderr) { envVars.put("LD_LIBRARY_PATH", imageFs.getRootDir().getPath() + "/usr/lib"); envVars.put( "BOX64_LD_LIBRARY_PATH", imageFs.getRootDir().getPath() + "/usr/lib/x86_64-linux-gnu"); + // After the two library paths above, which would otherwise drop the Proton's own lib dir. + if (waylandMode) + applyWaylandEnv(context, imageFs, envVars, imageFs.getRootDir().getPath() + "/usr/lib"); envVars.put( "ANDROID_SYSVSHM_SERVER", imageFs.getRootDir().getPath() + UnixSocketConfig.SYSVSHM_SERVER_PATH); @@ -534,6 +701,7 @@ public void start() { } } + if (waylandMode) prepareWaylandRuntime(); launchGeneration++; pid = execGuestProgram(); Log.d( @@ -926,6 +1094,7 @@ private int execGuestProgram() { envVars.put("WINE_NO_DUPLICATE_EXPLORER", "1"); envVars.put("PREFIX", rootDir.getPath() + "/usr"); envVars.put("DISPLAY", ":0"); + if (waylandMode) applyWaylandEnv(context, imageFs, envVars, rootDir.getPath() + "/usr/lib" + ":" + "/system/lib64"); envVars.put("WINE_DISABLE_FULLSCREEN_HACK", "1"); envVars.put("GST_PLUGIN_FEATURE_RANK", "ximagesink:3000"); envVars.put( @@ -1221,6 +1390,11 @@ private int execGuestProgram() { + "MESA_VK_WSI_DEBUG=" + envVars.get("MESA_VK_WSI_DEBUG")); + String wineLauncher = wineInfo != null && wineInfo.isArm64EC() + ? imageFs.getWinePath() + "/bin/wine" + : imageFs.getBinDir() + "/box64 wine"; + updatePrefixBeforeSession(envVars, wineLauncher, rootDir, imageFs); + return ProcessHelper.exec( command, envVars.toStringArray(), diff --git a/app/src/main/runtime/display/environment/components/LinuxProgramLauncherComponent.java b/app/src/main/runtime/display/environment/components/LinuxProgramLauncherComponent.java new file mode 100644 index 000000000..e2cd48f20 --- /dev/null +++ b/app/src/main/runtime/display/environment/components/LinuxProgramLauncherComponent.java @@ -0,0 +1,129 @@ +package com.winlator.cmod.runtime.display.environment.components; + +import android.util.Log; +import com.winlator.cmod.runtime.display.environment.EnvironmentComponent; +import com.winlator.cmod.runtime.wine.EnvVars; +import com.winlator.cmod.shared.util.Callback; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.List; + +public class LinuxProgramLauncherComponent extends EnvironmentComponent { + private static final String TAG = "LinuxLauncher"; + /** What every session runs under proot, and so where its options stop. */ + private static final String GUEST_COMMAND_HEAD = "/usr/bin/env"; + private final List<String> command; + private final EnvVars envVars; + private final File workingDir; + private final File logFile; + private final Callback<Integer> terminationCallback; + private final Object lock = new Object(); + private Process process; + + public LinuxProgramLauncherComponent( + List<String> command, EnvVars envVars, File workingDir, File logFile, + Callback<Integer> terminationCallback) { + this.command = command; + this.envVars = envVars; + this.workingDir = workingDir; + this.logFile = logFile; + this.terminationCallback = terminationCallback; + } + + @Override + public void start() { + synchronized (lock) { + stop(); + try { + File parent = logFile.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) throw new IOException("Cannot create " + parent); + record("Linux session starting: " + android.os.Build.MANUFACTURER + " " + + android.os.Build.MODEL + ", Android " + android.os.Build.VERSION.RELEASE + + ", SDK " + android.os.Build.VERSION.SDK_INT + + ", WinNative " + com.winlator.cmod.BuildConfig.VERSION_NAME); + record("proot options: " + prootOptions()); + ProcessBuilder builder = new ProcessBuilder(command).directory(workingDir); + for (String entry : envVars.toStringArray()) { + int separator = entry.indexOf('='); + if (separator > 0) builder.environment().put(entry.substring(0, separator), entry.substring(separator + 1)); + } + builder.redirectErrorStream(true).redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)); + Process started = builder.start(); + process = started; + new Thread(() -> { + try { + int status = started.waitFor(); + synchronized (lock) { + if (process != started) return; + process = null; + } + record("Linux session exited with status " + status + describeSignal(status)); + if (terminationCallback != null) terminationCallback.call(status); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }, "LinuxSessionWait").start(); + } catch (IOException e) { + Log.e(TAG, "Linux process could not start", e); + record("Linux process could not start: " + e); + if (terminationCallback != null) { + new Thread(() -> terminationCallback.call(127), "LinuxSessionFailure").start(); + } + } + } + } + + /** + * A process taken down by a signal is reported as 128 plus that signal, so the statuses that + * mean Android stopped the session read like an exit code it chose. + */ + private static String describeSignal(int status) { + switch (status) { + case 137: return " (SIGKILL: stopped from outside, usually by Android)"; + case 143: return " (SIGTERM: asked to stop)"; + default: return ""; + } + } + + /** + * proot's own options, which the app writes and a reader of a failed session needs. The guest + * command that follows carries the container's environment, where a user may have typed + * anything, so it is left out and an unrecognised command line reports nothing rather than + * risking that content in a log made to be shared. + */ + private String prootOptions() { + int guest = command.indexOf(GUEST_COMMAND_HEAD); + if (guest < 1) return "not recognised"; + return String.join(" ", command.subList(1, guest)); + } + + private void record(String message) { + Log.i(TAG, message); + try { + Files.write(logFile.toPath(), (message + "\n").getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException e) { + Log.w(TAG, "Could not write Linux session log", e); + } + } + + /** + * Ends the session, where it stands. Asking proot first would do nothing - it answers SIG_IGN to + * every signal but the fatal few - and a kill is enough on its own: its tracees are traced with + * {@code PTRACE_O_EXITKILL}, so the kernel takes gamescope and everything under it down with it. + * Killing on a thread of its own would let the exit path reach {@code killProcess} first and + * leave that tree running after the app is gone. + */ + @Override + public void stop() { + synchronized (lock) { + if (process != null) { + process.destroyForcibly(); + process = null; + } + } + } +} diff --git a/app/src/main/runtime/display/wayland/WaylandClipboardSync.java b/app/src/main/runtime/display/wayland/WaylandClipboardSync.java new file mode 100644 index 000000000..e269153c2 --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandClipboardSync.java @@ -0,0 +1,88 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +/** + * Keeps Android's clipboard and the Wayland guest's selection in sync (text only, both ways). + * Android hides clipboard changes from background apps, so {@link #refresh()} re-reads it on + * resume and focus gain. Text that came from the guest is not pushed back. + */ +public final class WaylandClipboardSync implements WaylandCompositor.ClipboardListener, + ClipboardManager.OnPrimaryClipChangedListener { + private static final String TAG = "WaylandClipboard"; + + private final Context context; + private final ClipboardManager clipboard; + private final Handler main = new Handler(Looper.getMainLooper()); + private String lastFromGuest; // echo guard: the last text the guest gave us + private String lastToGuest; // the last text we pushed down + private boolean started; + + public WaylandClipboardSync(Activity activity) { + context = activity.getApplicationContext(); + clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + } + + /** Register both directions. Call once the compositor is up (main thread). */ + public void start() { + if (started || clipboard == null) return; + started = true; + WaylandCompositor.setClipboardListener(this); + clipboard.addPrimaryClipChangedListener(this); + refresh(); + } + + public void stop() { + if (!started) return; + started = false; + WaylandCompositor.setClipboardListener(null); + if (clipboard != null) clipboard.removePrimaryClipChangedListener(this); + } + + /** Re-read Android's clipboard and hand any new text to the guest (resume / window focus). */ + public void refresh() { + if (!started || clipboard == null) return; + String text = currentText(); + if (text == null || text.isEmpty()) return; + if (text.equals(lastFromGuest) || text.equals(lastToGuest)) return; + lastToGuest = text; + WaylandCompositor.setClipboardText(text); + } + + private String currentText() { + try { + if (!clipboard.hasPrimaryClip()) return null; + ClipData clip = clipboard.getPrimaryClip(); + if (clip == null || clip.getItemCount() == 0) return null; + CharSequence cs = clip.getItemAt(0).coerceToText(context); + return cs == null ? null : cs.toString(); + } catch (Exception e) { + Log.w(TAG, "clipboard read failed", e); + return null; + } + } + + @Override + public void onPrimaryClipChanged() { + refresh(); + } + + @Override + public void onGuestClipboardText(String text) { + if (text == null || clipboard == null) return; + main.post(() -> { + lastFromGuest = text; + try { + clipboard.setPrimaryClip(ClipData.newPlainText("Wayland guest", text)); + } catch (Exception e) { + Log.w(TAG, "clipboard write failed", e); + } + }); + } +} diff --git a/app/src/main/runtime/display/wayland/WaylandCompositor.java b/app/src/main/runtime/display/wayland/WaylandCompositor.java new file mode 100644 index 000000000..f5dbba39b --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandCompositor.java @@ -0,0 +1,220 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.view.Surface; + +import java.nio.charset.StandardCharsets; + +/** + * JNI surface of the embedded Wayland compositor (libwnwayland.so); see CREDITS.md for + * attribution. One compositor thread lives for the whole process; each game session begins with + * {@link #nativeStartWithSurface} and ends with {@link #nativeEndSession}. Listener callbacks run + * on the compositor thread and must marshal to the UI thread themselves. + */ +public final class WaylandCompositor { + static { + System.loadLibrary("wnwayland"); + } + + private WaylandCompositor() {} + + public static final int HDR_MODE_OFF = 0, HDR_MODE_ON = 1, HDR_MODE_FORCE = 2; + + /** Scale modes of the output mapping (mirrors the compositor's vkp_scale_mode). */ + public static final int SCALE_OFF = 0, SCALE_FIT = 1, SCALE_STRETCH = 2, SCALE_FILL = 3, SCALE_INTEGER = 4; + public static final int ALIGN_CENTER = 0, ALIGN_TOP = 1, ALIGN_BOTTOM = 2; + + /** Fired once per session when the first client frame reaches the screen. */ + public interface FirstFrameListener { + void onFirstFrame(); + } + + /** A window presenting GPU frames, its frames, and the program behind it. */ + public interface GameListener { + void onGameSurface(String window, String gpuName); + void onGameFrame(); + default void onGameProgram(int pid, String program) {} + } + + /** A program locked or released the pointer (zwp_pointer_constraints_v1). */ + public interface PointerLockListener { + void onPointerLock(boolean locked, int x, int y); + } + + public interface ClipboardListener { + void onGuestClipboardText(String text); + } + + public interface TextInputListener { + void onTextInput(boolean enabled, String program, int x, int y, int w, int h); + } + + private static volatile FirstFrameListener firstFrameListener; + private static volatile GameListener gameListener; + private static volatile PointerLockListener pointerLockListener; + private static volatile ClipboardListener clipboardListener; + private static volatile TextInputListener textInputListener; + + public static void setFirstFrameListener(FirstFrameListener l) { firstFrameListener = l; } + public static void setGameListener(GameListener l) { gameListener = l; } + public static void setPointerLockListener(PointerLockListener l) { pointerLockListener = l; } + public static void setClipboardListener(ClipboardListener l) { clipboardListener = l; } + public static void setTextInputListener(TextInputListener l) { textInputListener = l; } + + /** Drops every listener; called when a session ends so a stale activity is never called back. */ + public static void clearListeners() { + firstFrameListener = null; + gameListener = null; + pointerLockListener = null; + clipboardListener = null; + textInputListener = null; + } + + @SuppressWarnings("unused") + static void onFirstFramePresented() { + FirstFrameListener l = firstFrameListener; + if (l != null) l.onFirstFrame(); + } + + @SuppressWarnings("unused") + static void onGameSurface(String window, String gpuName) { + GameListener l = gameListener; + if (l != null) l.onGameSurface(window, gpuName); + } + + @SuppressWarnings("unused") + static void onGameFrame() { + GameListener l = gameListener; + if (l != null) l.onGameFrame(); + } + + @SuppressWarnings("unused") + static void onGameProgram(int pid, String program) { + GameListener l = gameListener; + if (l != null) l.onGameProgram(pid, program != null ? program : ""); + } + + @SuppressWarnings("unused") + static void onPointerLock(boolean locked, int x, int y) { + PointerLockListener l = pointerLockListener; + if (l != null) l.onPointerLock(locked, x, y); + } + + @SuppressWarnings("unused") + static void onClipboardText(byte[] utf8) { + ClipboardListener l = clipboardListener; + if (l != null && utf8 != null) l.onGuestClipboardText(new String(utf8, StandardCharsets.UTF_8)); + } + + @SuppressWarnings("unused") + static void onTextInput(boolean enabled, String program, int x, int y, int w, int h) { + TextInputListener l = textInputListener; + if (l != null) l.onTextInput(enabled, program, x, y, w, h); + } + + private static byte[] utf8(String s) { + return s == null ? new byte[0] : s.getBytes(StandardCharsets.UTF_8); + } + + public static void setClipboardText(String text) { nativeSetClipboardText(utf8(text)); } + public static void textInputCommit(String text) { if (text != null && !text.isEmpty()) nativeTextInputCommit(utf8(text)); } + public static void textInputPreedit(String text, int cursor) { nativeTextInputPreedit(utf8(text), cursor, cursor); } + public static void textInputDelete(int before, int after) { nativeTextInputDelete(before, after); } + + /** Relative pointer motion in scene pixels: the pointer-lock and relative-mouse path. */ + public static void sendPointerDelta(int dx, int dy) { + if (dx == 0 && dy == 0) return; + nativeSendSceneInput(6, dx * 256, dy * 256); + } + + private static native void nativeSetClipboardText(byte[] utf8); + private static native void nativeTextInputCommit(byte[] utf8); + private static native void nativeTextInputPreedit(byte[] utf8, int cursorBegin, int cursorEnd); + private static native void nativeTextInputDelete(int before, int after); + + // Lifecycle. Session settings (hide shell, output size and rate, FPS limit, zero-copy, UBWC) + // are stored before the start call; the compositor thread reads them as it runs. + public static native boolean nativeIsRunning(); + public static native void nativeSetLogDir(String dir); + public static native void nativeStart(String xdgRuntimeDir); + public static native void nativeStartWithSurface(Surface surface, String xdgRuntimeDir, + String driverPath, String libraryName, + String nativeLibDir); + public static native void nativeSetSurface(Surface surface); + public static native void nativeEndSession(); + + // Input. Scene input types: 2 = move to a,b; 3 = evdev button a pressed (b=1) or released; + // 4 = a wheel steps (negative = up); 6 = relative motion by a,b in 1/256 px. + public static native void nativeSendPointer(int action, int x, int y); + public static native void nativeSendKey(int evdev, int state); + public static native void nativeSendSceneInput(int type, int a, int b); + public static native void nativeVsync(long frameTimeNanos); + + // Output. + public static native void nativeSetHideShell(boolean hide); + public static native void nativeSetOutputRefreshRate(float hz); + public static native void nativeSetOutputSize(int width, int height); + public static native void nativeSetScaleMode(int scaleMode, int alignment); + public static native void nativeSetFpsLimit(int fps); + public static native void nativeSetLayerFrameRate(float hz); + + // Zero-copy display layer and buffer layout. + public static native void nativeSetZeroCopy(boolean on); + public static native int nativeZeroCopyFrames(); + public static native int nativeZeroCopyLastFrameAgeMs(); + public static native void nativeSetUbwc(boolean on); + public static native void nativeSetNoRenderNode(boolean on); + + // Session log lines written from Java. + public static native void nativeLogDisplay(String message); + public static native void nativeLogPerf(String message); + public static native void nativeLogColor(String message); + public static native void nativeLog(String area, String message); + + // HDR10 output. WinNative leaves the request at HDR_MODE_OFF; the entry points stay bound to + // the library's exports. + public static native void nativeSetHdrRequest(int mode, String source, boolean dxvkHdr, boolean zeroCopyForced); + public static native void nativeSetHdrDisplay(int displayId, String name, String formats, boolean hdr10, + float maxLuminance, float maxAverageLuminance, float minLuminance, + boolean hdrSdrRatioAvailable, float hdrSdrRatio, int apiLevel); + public static native void nativeHdrSdrRatioSample(float ratio, boolean listener); + public static native int nativeHdrLastFrameAgeMs(); + public static native int nativeHdrGateState(); + public static native void nativeHdrSessionEnd(); + public static native boolean nativeHdrOnScreen(); + public static native void nativeSetHdrSdrWhite(float nits); + public static native void nativeSetHdrOutput(boolean on); + public static native boolean nativeHdrOutput(); + public static native boolean nativeHdrToneMappedOnScreen(); + public static native int nativeHdrState(); + public static native void nativeHdrEnvSample(int thermalStatus, float thermalHeadroom, int brightness, int brightnessMode); + public static native void nativeSetHdrHighestRatio(float ratio); + public static native float nativeHdrScreenHeadroom(); + public static native String nativeHdrScreenHeadroomWhy(); + public static native void nativeHdrNoteHeadroomRequest(float ratio); + + // Screen effects run by the compositor between the composed scene and the output blit. + public static native void nativeSetUpscaler(int mode); + public static native void nativeSetUpscaleSharpness(int sharpness); + public static native void nativeSetCas(boolean enabled, int sharpness); + public static native void nativeSetHdr(boolean enabled); + public static native void nativeSetDeband(boolean enabled, int strength); + public static native void nativeSetScreenEffects(float brightness, float contrast, float gamma, float saturation, + boolean fxaa, boolean toon, boolean crt, boolean ntsc); + public static native void nativeSetLookName(String name); + + // Frame generation. kind is ENGINE_LSFG or ENGINE_DIS; every setter is a store the compositor + // thread picks up on its next frame, so they are safe to call before the compositor is up. + public static final int ENGINE_LSFG = 0; + public static final int ENGINE_DIS = 1; + + public static native void nativeSetFrameGenEngine(int kind); + public static native void nativeSetFrameGenArmed(boolean armed, int multiplier); + public static native void nativeSetLsfgCachePath(String path); + public static native void nativeSetFrameGenTuning(float flowScale, float refreshHz); + public static native void nativeSetEngineTuning(int flowMinSide, int targetFps); + public static native int nativeFrameGenProblem(); + public static native String nativeFrameGenCapsReason(); + public static native float[] nativeFrameGenStats(); + public static native long nativeFrameGenPresentedFrames(); + public static native long nativeFrameGenGeneratedFrames(); +} diff --git a/app/src/main/runtime/display/wayland/WaylandGameDriver.java b/app/src/main/runtime/display/wayland/WaylandGameDriver.java new file mode 100644 index 000000000..f1071854c --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandGameDriver.java @@ -0,0 +1,75 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.content.Context; +import android.util.Log; + +import com.winlator.cmod.runtime.system.GPUInformation; +import com.winlator.cmod.runtime.wine.EnvVars; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Picks the bundled Wayland Turnip variant the game renders on. A Wayland-capable Proton ships + * several builds and reads BANNER_WAYLAND_VK_VARIANT to choose one; the variant follows the GPU + * generation (Adreno 7xx, Adreno 8xx, or the plain build for everything else). A value the user + * put in the container or shortcut environment is kept. + */ +public final class WaylandGameDriver { + private WaylandGameDriver() {} + + private static final String TAG = "WaylandGameDriver"; + + public static final String ENV_VARIANT = "BANNER_WAYLAND_VK_VARIANT"; + public static final String ENV_ICD = "BANNER_WAYLAND_VK_ICD"; + + public static final String VARIANT_PLAIN = ""; + public static final String VARIANT_A7XX = "a7xx"; + public static final String VARIANT_A8XX = "a8xx"; + + private static final Pattern ADRENO_MODEL = Pattern.compile("(?i)Adreno\\s*\\(?(?:TM\\)?\\s*)?(\\d+)"); + + private static volatile String cachedAutoVariant; + + public static String variantForRenderer(String renderer) { + if (renderer == null) return VARIANT_PLAIN; + Matcher m = ADRENO_MODEL.matcher(renderer); + if (!m.find()) return VARIANT_PLAIN; + String n = m.group(1); + switch (n) { + case "710": + case "720": + case "722": + return VARIANT_A7XX; + default: + return n.length() == 3 && n.charAt(0) == '8' ? VARIANT_A8XX : VARIANT_PLAIN; + } + } + + /** Probes the GPU once per process; call off the main thread. */ + public static String autoVariant(Context context) { + String known = cachedAutoVariant; + if (known == null) { + String renderer; + try { + renderer = GPUInformation.getRenderer(null, context.getApplicationContext()); + } catch (Throwable t) { + renderer = ""; + } + cachedAutoVariant = known = variantForRenderer(renderer); + } + return known; + } + + /** Exports the variant for a Wayland launch unless the user set one of the two variables. */ + public static void applyToLaunchEnv(Context context, EnvVars envVars, java.io.File wineRoot) { + if (envVars.has(ENV_ICD) || envVars.has(ENV_VARIANT)) { + Log.i(TAG, "wayland game driver: keeping the user's " + ENV_VARIANT + "/" + ENV_ICD); + return; + } + String variant = autoVariant(context); + if (!variant.isEmpty()) envVars.put(ENV_VARIANT, variant); + envVars.put(ENV_ICD, WineWaylandSupport.manifestFor(wineRoot, variant).getPath()); + Log.i(TAG, "wayland game driver: auto -> " + (variant.isEmpty() ? "plain" : variant)); + } +} diff --git a/app/src/main/runtime/display/wayland/WaylandPrefixRegistry.java b/app/src/main/runtime/display/wayland/WaylandPrefixRegistry.java new file mode 100644 index 000000000..f7877e415 --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandPrefixRegistry.java @@ -0,0 +1,144 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.util.Log; + +import com.winlator.cmod.runtime.wine.WineRegistryEditor; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Sets and removes string values in a Wine registry file at the text level. A missing key is + * appended as its own block, which wineserver accepts anywhere in the file, so no parent key has to + * exist first. Used for the handful of values a Wayland session needs in user.reg. + */ +public final class WaylandPrefixRegistry { + private static final String TAG = "WaylandPrefixRegistry"; + + private final File file; + private final List<String> lines = new ArrayList<>(); + private boolean modified; + + private WaylandPrefixRegistry(File file) { + this.file = file; + } + + public interface Edit { + void apply(WaylandPrefixRegistry reg); + } + + public static void edit(File file, Edit edit) { + if (!file.isFile()) return; + WineRegistryEditor.withFileLock(file, () -> { + WaylandPrefixRegistry reg = new WaylandPrefixRegistry(file); + try { + reg.lines.addAll(Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)); + } catch (IOException e) { + Log.e(TAG, "cannot read " + file, e); + return; + } + edit.apply(reg); + if (reg.modified) reg.save(); + }); + } + + public String get(String key, String name) { + int start = keyStart(key); + if (start < 0) return null; + int index = valueIndex(start, name); + if (index < 0) return null; + String line = lines.get(index); + int eq = line.indexOf("\"=\""); + if (eq < 0 || !line.endsWith("\"")) return null; + return unescape(line.substring(eq + 3, line.length() - 1)); + } + + public void set(String key, String name, String value) { + String entry = "\"" + escape(name) + "\"=\"" + escape(value) + "\""; + int start = keyStart(key); + if (start < 0) { + long now = System.currentTimeMillis(); + long ticks1601To1970 = 86400L * (369 * 365 + 89) * 10000000L; + long time = now * 10000L + ticks1601To1970; + if (!lines.isEmpty() && !lines.get(lines.size() - 1).isEmpty()) lines.add(""); + lines.add("[" + escape(key) + "] " + (now / 1000)); + lines.add(String.format(Locale.ENGLISH, "#time=%x", time)); + lines.add(entry); + lines.add(""); + modified = true; + return; + } + int index = valueIndex(start, name); + if (index >= 0) { + if (lines.get(index).equals(entry)) return; + lines.set(index, entry); + } else { + lines.add(keyEnd(start), entry); + } + modified = true; + } + + public void remove(String key, String name) { + int start = keyStart(key); + if (start < 0) return; + int index = valueIndex(start, name); + if (index < 0) return; + lines.remove(index); + modified = true; + } + + private int keyStart(String key) { + String header = "[" + escape(key) + "]"; + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + if (line.startsWith(header) && (line.length() == header.length() || line.charAt(header.length()) == ' ')) { + return i; + } + } + return -1; + } + + /** Index just past the last value line of the key block starting at start. */ + private int keyEnd(int start) { + int end = start + 1; + int last = end; + while (end < lines.size() && !lines.get(end).startsWith("[")) { + if (!lines.get(end).isEmpty()) last = end + 1; + end++; + } + return last; + } + + private int valueIndex(int start, String name) { + String prefix = "\"" + escape(name) + "\"="; + for (int i = start + 1; i < lines.size() && !lines.get(i).startsWith("["); i++) { + if (lines.get(i).startsWith(prefix)) return i; + } + return -1; + } + + private void save() { + File tmp = new File(file.getParentFile(), file.getName() + ".wayland.tmp"); + try { + Files.write(tmp.toPath(), lines, StandardCharsets.UTF_8); + Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + Log.e(TAG, "cannot write " + file, e); + tmp.delete(); + } + } + + private static String escape(String s) { + return s.replace("\\", "\\\\").replace("\"", "\\\""); + } + + private static String unescape(String s) { + return s.replace("\\\"", "\"").replace("\\\\", "\\"); + } +} diff --git a/app/src/main/runtime/display/wayland/WaylandRendererProbe.java b/app/src/main/runtime/display/wayland/WaylandRendererProbe.java new file mode 100644 index 000000000..e14de960b --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandRendererProbe.java @@ -0,0 +1,127 @@ +package com.winlator.cmod.runtime.display.wayland; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Locale; + +/** + * Names the graphics API a Wayland session runs on, for the HUD. + * + * On X11 the Vulkan wrapper publishes _MESA_DRV_ENGINE_NAME on the game's window and the HUD reads + * the name from there. A Wayland session has neither half of that: the game has no X11 window, and + * winewayland.drv repoints VK_ICD_FILENAMES at the bundled Turnip, so the wrapper never loads. The + * container's own processes still say the same thing - whichever translation layer the game runs on + * is mapped into it - so read it from there instead. + */ +public final class WaylandRendererProbe { + /** What the HUD shows before anything is known, and the answer for a native Vulkan title. */ + public static final String VULKAN = "Vulkan"; + + private WaylandRendererProbe() {} + + /** + * The renderer of the container rooted at {@code containerRoot}, or null while no process has + * loaded one yet. Callers keep asking: a Steam session maps the store's own Direct3D before the + * game maps its, so the answer can rise from DXVK to VKD3D once the game is up. + * + * <p>{@code gamePid} is the process behind the window the compositor is showing. Reading that + * one process answers the question outright; without it every process on the device has to be + * read instead, which is about a megabyte of /proc a tick. + */ + public static String probe(File containerRoot, int gamePid) { + if (containerRoot == null) return null; + // Matched on the container directory's name, not its full path: the same directory is + // reachable as both /data/data/<pkg> and /data/user/0/<pkg>, and maps reports whichever + // path the process opened it through. + String marker = "/" + containerRoot.getName() + "/.wine/drive_c"; + + if (gamePid > 0) { + int rank = scan(new File("/proc/" + gamePid, "maps"), marker); + // Nothing yet (the process is still starting, or it has already gone): fall through to + // the sweep rather than report a renderer the session is not on. + if (rank != 0) return name(rank); + } + + File[] entries = new File("/proc").listFiles(); + if (entries == null) return null; + int best = 0; + for (File entry : entries) { + if (!isPid(entry.getName())) continue; + best = Math.max(best, scan(new File(entry, "maps"), marker)); + if (best == VKD3D) break; + } + return name(best); + } + + /* Every Proton maps its PE modules from here, whichever prefix the game runs in. */ + private static final String PROTON_MARKER = "/lib/wine/"; + + /** + * The renderer of the game a Linux session is running, or null while none is. The window the + * compositor shows there is gamescope's, which says nothing about the game behind it, so the + * Proton processes are read instead. + */ + public static String probeLinuxSession() { + File[] entries = new File("/proc").listFiles(); + if (entries == null) return null; + int best = 0; + for (File entry : entries) { + if (!isPid(entry.getName())) continue; + best = Math.max(best, scan(new File(entry, "maps"), PROTON_MARKER)); + if (best == VKD3D) break; + } + return name(best); + } + + /* Ranked so the highest-level translation layer in the container wins, as the X11 window + * scoring does. d3d12 outranks the rest because a VKD3D title maps dxgi as well. */ + private static final int VULKAN_RANK = 1, OPENGL = 2, DXVK = 3, WINED3D = 4, VKD3D = 5; + + /** The best rank in one process, or 0 if it is not this container's or maps no renderer. */ + private static int scan(File maps, String marker) { + int rank = 0; + boolean ours = false; + try (BufferedReader reader = new BufferedReader(new FileReader(maps), 1 << 16)) { + String line; + while ((line = reader.readLine()) != null) { + if (!ours && line.contains(marker)) ours = true; + int slash = line.lastIndexOf('/'); + if (slash < 0) continue; + String file = line.substring(slash + 1).toLowerCase(Locale.ROOT); + if (!file.endsWith(".dll")) continue; + if (file.startsWith("d3d12")) rank = Math.max(rank, VKD3D); + else if (file.equals("wined3d.dll")) rank = Math.max(rank, WINED3D); + else if (file.startsWith("d3d8") || file.startsWith("d3d9") + || file.startsWith("d3d10") || file.startsWith("d3d11") + || file.equals("dxgi.dll")) rank = Math.max(rank, DXVK); + else if (file.equals("opengl32.dll")) rank = Math.max(rank, OPENGL); + else if (file.equals("winevulkan.dll")) rank = Math.max(rank, VULKAN_RANK); + } + } catch (IOException | RuntimeException e) { + // /proc reads race with process exit, and other apps' processes are not ours to read. + return 0; + } + return ours ? rank : 0; + } + + private static String name(int rank) { + switch (rank) { + case VKD3D: return "VKD3D"; + case WINED3D: return "WineD3D"; + case DXVK: return "DXVK"; + case OPENGL: return "OpenGL"; + case VULKAN_RANK: return VULKAN; + default: return null; + } + } + + private static boolean isPid(String name) { + if (name.isEmpty()) return false; + for (int i = 0; i < name.length(); i++) { + if (name.charAt(i) < '0' || name.charAt(i) > '9') return false; + } + return true; + } +} diff --git a/app/src/main/runtime/display/wayland/WaylandSession.java b/app/src/main/runtime/display/wayland/WaylandSession.java new file mode 100644 index 000000000..3ac9cf48d --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandSession.java @@ -0,0 +1,399 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.Choreographer; +import android.view.SurfaceHolder; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.winlator.cmod.runtime.display.renderer.ViewTransformation; +import com.winlator.cmod.runtime.display.winhandler.WinHandler; +import com.winlator.cmod.runtime.display.xserver.Pointer; +import com.winlator.cmod.runtime.display.xserver.XServer; + +import java.io.File; + +/** + * One game session on the embedded Wayland compositor: the output SurfaceView, the overlay + * pointer, clipboard and IME sync, the vsync feed and the input bridge from the X server's + * injected input. The native compositor outlives the activity; {@link #detach()} releases the + * activity's views and listeners while the guest keeps running (background sessions) and + * {@link #end()} closes the session for good. + */ +public final class WaylandSession { + private static final String TAG = "WaylandSession"; + + public interface Host { + /** UI thread: a program took or released the pointer lock. */ + void onPointerLockChanged(boolean locked); + + /** UI thread: a window started ({@code present}) or stopped presenting GPU frames. */ + void onGameSurface(boolean present, String gpuName); + + /** Compositor thread, must stay cheap: one GPU frame reached the screen. */ + void onGameFrame(); + + /** UI thread, once per session: the first client frame is on screen. */ + void onFirstFrame(); + + /** UI thread: the Linux process behind the window now presenting ({@code pid} 0 = unknown). */ + default void onGameProgram(int pid, String program) {} + } + + /** Settings the compositor reads for the session; stored before the surface comes up. */ + public static final class Config { + public String driverPath; + public String libraryName; + public boolean hideShell; + public float refreshHz; + public int outputWidth; + public int outputHeight; + public int fpsLimit; + public boolean zeroCopy; + public boolean ubwc = true; + public boolean noRenderNode; + public int scaleMode = WaylandCompositor.SCALE_FIT; + public File logDir; + } + + private static volatile boolean sessionActive; + + /** A compositor session is live in this process (a background session may be reattached). */ + public static boolean hasActiveSession() { + return sessionActive; + } + + private final Activity activity; + private final Host host; + private final XServer xServer; + private final WinHandler winHandler; + private final Handler main = new Handler(Looper.getMainLooper()); + + private WaylandTextInput.SurfaceInputView surfaceView; + /** The space nativeSendPointer takes its coordinates in, over the whole output. */ + private static final int INPUT_SPACE_WIDTH = 1920; + private static final int INPUT_SPACE_HEIGHT = 1080; + private ImageView cursorView; + private WaylandClipboardSync clipboard; + private WaylandTextInput textInput; + private volatile boolean attached; + private boolean vsyncRunning; + private volatile boolean pointerLocked; + private volatile int scaleMode = WaylandCompositor.SCALE_FIT; + private boolean firstFrameSeen; + + private final Choreographer.FrameCallback vsyncCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + if (!vsyncRunning) return; + WaylandCompositor.nativeVsync(frameTimeNanos); + Choreographer.getInstance().postFrameCallback(this); + } + }; + + public WaylandSession(Activity activity, Host host, XServer xServer, WinHandler winHandler) { + this.activity = activity; + this.host = host; + this.xServer = xServer; + this.winHandler = winHandler; + } + + public boolean isPointerLocked() { + return pointerLocked; + } + + /** + * Puts the compositor's surface into {@code root} at {@code index} and starts (or, for a + * reattached background session, resumes) the session. UI thread. + */ + public void attach(FrameLayout root, int index, Config cfg, boolean reattach) { + if (attached) return; + attached = true; + final boolean fresh = !reattach || !sessionActive; + sessionActive = true; + scaleMode = cfg.scaleMode; + if (fresh) { + WaylandCompositor.nativeSetLogDir(cfg.logDir != null ? cfg.logDir.getPath() : ""); + WaylandCompositor.nativeSetHideShell(cfg.hideShell); + WaylandCompositor.nativeSetOutputRefreshRate(cfg.refreshHz); + WaylandCompositor.nativeSetOutputSize(cfg.outputWidth, cfg.outputHeight); + WaylandCompositor.nativeSetFpsLimit(cfg.fpsLimit); + // The cadence the session is aiming for, on the layer the game presents on. Left + // unsaid, the layer is posted with a vote of zero and the system is free to read the + // rate it is already achieving as the rate it wants - which is the wrong way round on + // a device whose scheduler decides clocks from it. + WaylandCompositor.nativeSetLayerFrameRate(cfg.fpsLimit > 0 ? cfg.fpsLimit : cfg.refreshHz); + WaylandCompositor.nativeSetZeroCopy(cfg.zeroCopy); + WaylandCompositor.nativeSetUbwc(cfg.ubwc); + WaylandCompositor.nativeSetNoRenderNode(cfg.noRenderNode); + WaylandCompositor.nativeSetHdrRequest(WaylandCompositor.HDR_MODE_OFF, "", false, false); + } + WaylandCompositor.nativeSetScaleMode(scaleMode, WaylandCompositor.ALIGN_CENTER); + registerListeners(); + + WaylandTextInput.SurfaceInputView view = new WaylandTextInput.SurfaceInputView(activity); + view.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + surfaceView = view; + cursorView = new ImageView(activity); + cursorView.setImageBitmap(makeArrowCursorBitmap()); + cursorView.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT)); + cursorView.setVisibility(View.GONE); + + final File runtimeDir = new File(activity.getFilesDir(), ".wayland-rt"); + if (!runtimeDir.isDirectory() && !runtimeDir.mkdirs()) { + Log.e(TAG, "wayland: cannot create " + runtimeDir); + } + final String driverPath = cfg.driverPath; + final String libraryName = cfg.libraryName; + final String nativeLibDir = activity.getApplicationInfo().nativeLibraryDir; + view.getHolder().addCallback(new SurfaceHolder.Callback() { + private boolean started; + + @Override + public void surfaceCreated(SurfaceHolder holder) { + if (!attached) return; + if (!started && fresh) { + started = true; + WaylandCompositor.nativeStartWithSurface(holder.getSurface(), runtimeDir.getPath(), + driverPath, libraryName, nativeLibDir); + } else { + started = true; + WaylandCompositor.nativeSetSurface(holder.getSurface()); + } + startVsync(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + /* The ticks drive the compositor's drawing, and there is nothing to draw on until + * surfaceCreated hands it another surface. Left running they woke the UI thread and + * the compositor thread at the panel's refresh rate for a screen the session no + * longer has - all of it while the app sat in the background. */ + stopVsync(); + WaylandCompositor.nativeSetSurface(null); + } + }); + root.addView(view, index); + root.addView(cursorView, index + 1); + + if (winHandler != null) winHandler.setWaylandMouseRouting(true); + clipboard = new WaylandClipboardSync(activity); + clipboard.start(); + textInput = new WaylandTextInput(activity, view); + textInput.start(); + Log.i(TAG, (fresh ? "session started" : "session reattached") + " (scale mode " + scaleMode + ")"); + } + + /** + * UI thread. A touchscreen-mode touch at screen coordinates: 0 = down, 1 = move, 2 = up. The + * compositor places it on the scene pixel drawn under the finger and clicks the left button. + */ + public boolean sendTouch(int action, float rawX, float rawY) { + View sv = surfaceView; + if (!attached || sv == null || sv.getWidth() <= 0 || sv.getHeight() <= 0) return false; + int[] origin = new int[2]; + sv.getLocationOnScreen(origin); + int x = Math.round((rawX - origin[0]) * INPUT_SPACE_WIDTH / sv.getWidth()); + int y = Math.round((rawY - origin[1]) * INPUT_SPACE_HEIGHT / sv.getHeight()); + WaylandCompositor.nativeSendPointer(action, + Math.max(0, Math.min(INPUT_SPACE_WIDTH - 1, x)), Math.max(0, Math.min(INPUT_SPACE_HEIGHT - 1, y))); + return true; + } + + /** Releases the activity's side of the session; the guest and the compositor keep running. */ + public void detach() { + if (!attached) return; + attached = false; + stopVsync(); + WaylandCompositor.clearListeners(); + if (xServer != null) { + xServer.setInputSink(null); + xServer.setExternalRelativeMode(false); + } + if (winHandler != null) winHandler.setWaylandMouseRouting(false); + if (clipboard != null) { clipboard.stop(); clipboard = null; } + if (textInput != null) { textInput.stop(); textInput = null; } + WaylandCompositor.nativeSetSurface(null); + final View sv = surfaceView, cv = cursorView; + surfaceView = null; + cursorView = null; + main.post(() -> { + if (sv != null && sv.getParent() instanceof FrameLayout) ((FrameLayout) sv.getParent()).removeView(sv); + if (cv != null && cv.getParent() instanceof FrameLayout) ((FrameLayout) cv.getParent()).removeView(cv); + }); + } + + /** The session is over: the guest has been terminated and the compositor resets for the next one. */ + public void end() { + detach(); + if (sessionActive) { + sessionActive = false; + WaylandCompositor.nativeEndSession(); + Log.i(TAG, "session ended"); + } + } + + public void onResume() { + if (clipboard != null) clipboard.refresh(); + } + + public void onWindowFocusChanged(boolean hasFocus) { + if (hasFocus && clipboard != null) clipboard.refresh(); + } + + public void onUserToggledKeyboard() { + if (textInput != null) textInput.onUserToggledKeyboard(); + } + + public void setFpsLimit(int fps) { + WaylandCompositor.nativeSetFpsLimit(fps); + } + + /** Mirrors the X renderer's letterbox/stretch choice so the picture and the touch map agree. */ + public void setScaleMode(int mode) { + scaleMode = mode; + WaylandCompositor.nativeSetScaleMode(mode, WaylandCompositor.ALIGN_CENTER); + } + + private void registerListeners() { + WaylandCompositor.setFirstFrameListener(() -> main.post(() -> { + if (!attached || firstFrameSeen) return; + firstFrameSeen = true; + host.onFirstFrame(); + })); + WaylandCompositor.setGameListener(new WaylandCompositor.GameListener() { + @Override + public void onGameSurface(String window, String gpuName) { + final boolean present = window != null; + main.post(() -> { if (attached) host.onGameSurface(present, gpuName); }); + } + + @Override + public void onGameFrame() { + if (attached) host.onGameFrame(); + } + + @Override + public void onGameProgram(int pid, String program) { + main.post(() -> { if (attached) host.onGameProgram(pid, program); }); + } + }); + WaylandCompositor.setPointerLockListener((locked, x, y) -> main.post(() -> { + if (!attached || xServer == null) return; + pointerLocked = locked; + xServer.setExternalRelativeMode(locked); + if (locked) { + if (cursorView != null) cursorView.setVisibility(View.GONE); + } else { + xServer.injectPointerMove(x, y); + } + host.onPointerLockChanged(locked); + })); + if (xServer != null) xServer.setInputSink(new XServer.InputSink() { + @Override + public void onPointerMove(int x, int y) { + WaylandCompositor.nativeSendSceneInput(2, x, y); + main.post(() -> moveCursorView(x, y)); + } + + @Override + public void onPointerButton(Pointer.Button button, boolean pressed) { + switch (button) { + case BUTTON_LEFT: WaylandCompositor.nativeSendSceneInput(3, 0x110, pressed ? 1 : 0); break; + case BUTTON_RIGHT: WaylandCompositor.nativeSendSceneInput(3, 0x111, pressed ? 1 : 0); break; + case BUTTON_MIDDLE: WaylandCompositor.nativeSendSceneInput(3, 0x112, pressed ? 1 : 0); break; + case BUTTON_SCROLL_UP: if (pressed) WaylandCompositor.nativeSendSceneInput(4, -1, 0); break; + case BUTTON_SCROLL_DOWN: if (pressed) WaylandCompositor.nativeSendSceneInput(4, 1, 0); break; + default: break; + } + } + + @Override + public void onKey(int evdev, boolean pressed) { + if (evdev > 0) WaylandCompositor.nativeSendKey(evdev, pressed ? 1 : 0); + } + }); + } + + private void moveCursorView(int x, int y) { + View sv = surfaceView; + ImageView cv = cursorView; + if (!attached || sv == null || cv == null || pointerLocked || xServer == null) return; + int vw = sv.getWidth(), vh = sv.getHeight(); + if (vw <= 0 || vh <= 0) return; + int sw = xServer.screenInfo.width, sh = xServer.screenInfo.height; + ViewTransformation vt = new ViewTransformation(); + vt.mode = scaleMode == WaylandCompositor.SCALE_STRETCH + ? ViewTransformation.FILL_MODE_STRETCH : ViewTransformation.FILL_MODE_FIT; + vt.update(vw, vh, sw, sh); + float vx, vy; + if (vt.mode == ViewTransformation.FILL_MODE_STRETCH) { + vx = (float) x * vw / sw; + vy = (float) y * vh / sh; + } else { + vx = vt.viewOffsetX + x * vt.aspect; + vy = vt.viewOffsetY + y * vt.aspect; + } + cv.setX(vx); + cv.setY(vy); + if (cv.getVisibility() != View.VISIBLE) cv.setVisibility(View.VISIBLE); + } + + private void startVsync() { + if (Looper.myLooper() != Looper.getMainLooper()) { + main.post(this::startVsync); + return; + } + if (vsyncRunning) return; + vsyncRunning = true; + Choreographer.getInstance().postFrameCallback(vsyncCallback); + } + + private void stopVsync() { + vsyncRunning = false; + if (Looper.myLooper() != Looper.getMainLooper()) { + main.post(() -> Choreographer.getInstance().removeFrameCallback(vsyncCallback)); + return; + } + Choreographer.getInstance().removeFrameCallback(vsyncCallback); + } + + private static Bitmap makeArrowCursorBitmap() { + int w = 22, h = 34; + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bmp); + Path p = new Path(); + p.moveTo(1, 1); + p.lineTo(1, 26); + p.lineTo(7, 20); + p.lineTo(11, 30); + p.lineTo(15, 28); + p.lineTo(11, 19); + p.lineTo(19, 19); + p.close(); + Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG); + fill.setStyle(Paint.Style.FILL); + fill.setColor(Color.WHITE); + Paint stroke = new Paint(Paint.ANTI_ALIAS_FLAG); + stroke.setStyle(Paint.Style.STROKE); + stroke.setStrokeWidth(1.5f); + stroke.setColor(Color.BLACK); + c.drawPath(p, fill); + c.drawPath(p, stroke); + return bmp; + } +} diff --git a/app/src/main/runtime/display/wayland/WaylandTextInput.java b/app/src/main/runtime/display/wayland/WaylandTextInput.java new file mode 100644 index 000000000..f6bb205b9 --- /dev/null +++ b/app/src/main/runtime/display/wayland/WaylandTextInput.java @@ -0,0 +1,199 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Handler; +import android.os.Looper; +import android.text.InputType; +import android.util.Log; +import android.view.KeyEvent; +import android.view.SurfaceView; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; + +/** + * Soft-keyboard text for the Wayland guest (zwp_text_input_v3). Keys still travel the wl_keyboard + * path; this adds an {@link InputConnection} on the compositor's SurfaceView while a program accepts + * IME text, so committed and composing text reaches the guest as text. The keyboard is shown only + * when the program also reports a caret rectangle and no hardware keyboard is attached, and hidden + * again when text input ends unless the user opened it with the app's own toggle. + */ +public final class WaylandTextInput implements WaylandCompositor.TextInputListener { + private static final String TAG = "WaylandTextInput"; + + /** The compositor's SurfaceView, able to host the soft keyboard's InputConnection. */ + public static final class SurfaceInputView extends SurfaceView { + private WaylandTextInput owner; + + public SurfaceInputView(Context context) { super(context); } + + @Override public boolean onCheckIsTextEditor() { return owner != null && owner.textMode(); } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + if (owner == null || !owner.textMode()) return null; // key-event mode, as on X11 + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE | EditorInfo.IME_FLAG_NO_FULLSCREEN + | EditorInfo.IME_FLAG_NO_EXTRACT_UI; + outAttrs.initialSelStart = 0; + outAttrs.initialSelEnd = 0; + return owner.new GuestInputConnection(this); + } + } + + private final Activity activity; + private final SurfaceInputView view; + private final InputMethodManager imm; + private final Handler main = new Handler(Looper.getMainLooper()); + private boolean textActive; // a program accepts IME text + private boolean caretKnown; // it reported a caret rectangle + private boolean autoShown; // we opened the keyboard ourselves + private boolean userForced; // the app's keyboard toggle opened it + private View focusBefore; // who had focus before we took it for the IME + + public WaylandTextInput(Activity activity, SurfaceInputView view) { + this.activity = activity; + this.view = view; + this.imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + view.owner = this; + // Not focusable until we need the IME: a focused SurfaceView would take joystick/D-pad + // motion away from InputControlsView (the framework focus-routes motion events). + view.setFocusable(false); + view.setFocusableInTouchMode(false); + } + + /** IME text mode: only once a program has both enabled text input AND placed a caret. Without + * the caret (winewayland enables for every focused window) the keyboard stays in key mode, so + * a game gets key events, not IME strings. */ + boolean textMode() { return textActive && caretKnown; } + + public void start() { WaylandCompositor.setTextInputListener(this); } + + public void stop() { WaylandCompositor.setTextInputListener(null); } + + /** The user pressed the app's keyboard toggle: mirror its state and stop auto-hiding. */ + public void onUserToggledKeyboard() { + userForced = !userForced; + autoShown = false; + if (userForced && textMode() && !view.hasFocus()) takeFocus(); + else if (!userForced && !autoShown) giveFocusBack(); + } + + private boolean hardwareKeyboardPresent() { + Configuration c = activity.getResources().getConfiguration(); + return c.keyboard == Configuration.KEYBOARD_QWERTY + && c.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; + } + + private void takeFocus() { + View cur = activity.getCurrentFocus(); + if (cur != view) focusBefore = cur; + view.setFocusable(true); + view.setFocusableInTouchMode(true); + view.requestFocus(); + } + + private void giveFocusBack() { + view.setFocusable(false); + view.setFocusableInTouchMode(false); + if (focusBefore != null && focusBefore.isAttachedToWindow()) focusBefore.requestFocus(); + focusBefore = null; + } + + @Override + public void onTextInput(boolean enabled, String program, int x, int y, int w, int h) { + main.post(() -> { + boolean wasTextMode = textMode(); + textActive = enabled; + caretKnown = enabled && w > 0 && h > 0; + if (textMode() && !wasTextMode) { + if (!autoShown && !userForced && !hardwareKeyboardPresent()) { + takeFocus(); + if (imm != null && imm.showSoftInput(view, 0)) autoShown = true; + Log.i(TAG, "keyboard shown for " + program); + } else if (userForced) { + takeFocus(); /* the user's keyboard switches to text mode */ + } + if (imm != null) imm.restartInput(view); + } else if (!textMode() && wasTextMode) { + if (autoShown) { + if (imm != null) imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + autoShown = false; + Log.i(TAG, "keyboard hidden"); + } + giveFocusBack(); /* back to key mode for whoever had focus */ + if (imm != null) imm.restartInput(view); + } + }); + } + + /** What the IME types goes to the guest as text; keys it sends go the wl_keyboard way. */ + final class GuestInputConnection extends BaseInputConnection { + private String composing; + + GuestInputConnection(View target) { super(target, true); } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + composing = null; + WaylandCompositor.textInputCommit(text == null ? "" : text.toString()); + return true; + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + composing = text == null ? "" : text.toString(); + WaylandCompositor.textInputPreedit(composing, -1); + return true; + } + + @Override + public boolean setComposingRegion(int start, int end) { return true; } + + @Override + public boolean finishComposingText() { + if (composing != null && !composing.isEmpty()) WaylandCompositor.textInputCommit(composing); + else if (composing != null) WaylandCompositor.textInputPreedit("", 0); + composing = null; + return true; + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + composing = null; + WaylandCompositor.textInputDelete(beforeLength, afterLength); + return true; + } + + @Override + public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { + return deleteSurroundingText(beforeLength, afterLength); + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + return activity.dispatchKeyEvent(event); + } + + @Override + public boolean performEditorAction(int actionCode) { + long t = System.currentTimeMillis(); + activity.dispatchKeyEvent(new KeyEvent(t, t, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0)); + activity.dispatchKeyEvent(new KeyEvent(t, t, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0)); + return true; + } + + @Override + public CharSequence getTextBeforeCursor(int n, int flags) { return ""; } + + @Override + public CharSequence getTextAfterCursor(int n, int flags) { return ""; } + + @Override + public CharSequence getSelectedText(int flags) { return null; } + } +} diff --git a/app/src/main/runtime/display/wayland/WineWaylandSupport.java b/app/src/main/runtime/display/wayland/WineWaylandSupport.java new file mode 100644 index 000000000..5ceab151c --- /dev/null +++ b/app/src/main/runtime/display/wayland/WineWaylandSupport.java @@ -0,0 +1,174 @@ +package com.winlator.cmod.runtime.display.wayland; + +import android.content.Context; + +import com.winlator.cmod.runtime.container.Container; +import com.winlator.cmod.runtime.container.Shortcut; +import com.winlator.cmod.runtime.content.ContentsManager; +import com.winlator.cmod.runtime.system.GPUInformation; +import com.winlator.cmod.runtime.wine.WineInfo; + +import android.util.Log; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.Map; + +/** + * Decides whether a session can run on the embedded Wayland compositor. A Wine/Proton install is + * Wayland-capable when it ships winewayland.so and the Wayland Turnip driver the guest renders on; + * the device must have an Adreno GPU, since the compositor imports the game's frames through Turnip. + * The bundled main Proton is never capable. Verdicts are cached per wine identifier. + * + * The files cannot be copied in from another Proton: winewayland.so is a Wine unixlib bound to the + * win32u it was built against, so a foreign copy loads and connects but creates no window, leaving + * a black screen. The Proton has to ship its own. + */ +public final class WineWaylandSupport { + private WineWaylandSupport() {} + + private static final String TAG = "WineWaylandSupport"; + private static final Map<String, Boolean> cache = new HashMap<>(); + private static volatile Boolean adrenoGpu; + + private static final String UNIX_DRIVER = "lib/wine/aarch64-unix/winewayland.so"; + private static final String WAYLAND_TURNIP = "lib/libvulkan_freedreno_wayland.so"; + private static final String ICD_DIR = "share/vulkan/icd.d"; + private static final String TURNIP_PREFIX = "libvulkan_freedreno_wayland"; + public static final String MANIFEST_PREFIX = "wayland_turnip"; + + + public static boolean isAdrenoDevice(Context context) { + Boolean known = adrenoGpu; + if (known == null) { + boolean adreno; + try { + adreno = GPUInformation.isAdrenoGPU(context.getApplicationContext()); + } catch (Throwable t) { + adreno = false; + } + adrenoGpu = known = adreno; + } + return known; + } + + public static boolean isWaylandCapable(WineInfo wineInfo) { + if (wineInfo == null || wineInfo.path == null || wineInfo.path.isEmpty()) return false; + if (WineInfo.isMainWineVersion(wineInfo.identifier())) return false; + return isWaylandCapable(wineInfo.identifier(), wineInfo.path); + } + + public static boolean isWaylandCapable(Context context, ContentsManager contentsManager, String identifier) { + if (identifier == null || identifier.isEmpty() || WineInfo.isMainWineVersion(identifier)) return false; + synchronized (cache) { + Boolean cached = cache.get(identifier); + if (cached != null) return cached; + } + try { + return isWaylandCapable(WineInfo.fromIdentifier(context, contentsManager, identifier)); + } catch (Exception e) { + return false; + } + } + + /** Builds a ContentsManager on a cache miss; call off the main thread when possible. */ + public static boolean isWaylandCapable(Context context, String identifier) { + if (identifier == null || identifier.isEmpty() || WineInfo.isMainWineVersion(identifier)) return false; + synchronized (cache) { + Boolean cached = cache.get(identifier); + if (cached != null) return cached; + } + try { + ContentsManager contentsManager = new ContentsManager(context); + contentsManager.syncContents(); + return isWaylandCapable(context, contentsManager, identifier); + } catch (Exception e) { + return false; + } + } + + /** The device and the given wine version together allow a Wayland session. */ + public static boolean isAvailable(Context context, String wineIdentifier) { + return isAdrenoDevice(context) && isWaylandCapable(context, wineIdentifier); + } + + /** The container selected Wayland and can drive it. */ + public static boolean runsOnWayland(Context context, Container container) { + if (container == null) return false; + return container.isWaylandBackend() && isAvailable(context, container.getWineVersion()); + } + + /** The shortcut's own choice, else the container's, gated the same way. */ + public static boolean runsOnWayland(Context context, Shortcut shortcut) { + if (shortcut == null || shortcut.container == null) return false; + String backend = shortcut.getSettingExtra(Container.EXTRA_DISPLAY_BACKEND, shortcut.container.getDisplayBackend()); + String wineVersion = shortcut.getSettingExtra("wineVersion", shortcut.container.getWineVersion()); + return Container.DISPLAY_BACKEND_WAYLAND.equals(backend) && isAvailable(context, wineVersion); + } + + /** The manifest path winewayland is pointed at for a driver variant ("" for the plain driver). */ + public static File manifestFor(File wineRoot, String variant) { + String suffix = variant == null || variant.isEmpty() ? "" : "_" + variant; + return new File(wineRoot, ICD_DIR + "/" + MANIFEST_PREFIX + suffix + ".json"); + } + + /** + * Writes one Vulkan ICD manifest per bundled Wayland Turnip under WinNative's own names, so the + * guest driver is addressed without the donor's branding. Existing files are left alone. + */ + public static boolean writeManifests(File wineRoot) { + File[] turnips = new File(wineRoot, "lib").listFiles((dir, name) -> + name.startsWith(TURNIP_PREFIX) && name.endsWith(".so")); + if (turnips == null || turnips.length == 0) return false; + File icdDir = new File(wineRoot, ICD_DIR); + if (!icdDir.isDirectory() && !icdDir.mkdirs()) return false; + for (File turnip : turnips) { + String rest = turnip.getName().substring(TURNIP_PREFIX.length(), turnip.getName().length() - 3); + String variant = rest.startsWith("_") ? rest.substring(1) : rest; + File manifest = manifestFor(wineRoot, variant); + if (manifest.isFile()) continue; + String json = "{\n \"ICD\": {\n \"api_version\": \"1.4.0\",\n" + + " \"library_arch\": \"64\",\n" + + " \"library_path\": \"../../../lib/" + turnip.getName() + "\"\n" + + " },\n \"file_format_version\": \"1.0.1\"\n}\n"; + File tmp = new File(icdDir, manifest.getName() + ".part"); + try { + Files.write(tmp.toPath(), json.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + Files.move(tmp.toPath(), manifest.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + Log.e(TAG, "cannot write " + manifest, e); + tmp.delete(); + return false; + } + } + return true; + } + + /** Drops every cached verdict; call after a wine/proton install or removal. */ + public static void invalidate() { + synchronized (cache) { + cache.clear(); + } + } + + private static boolean isWaylandCapable(String cacheKey, String installPath) { + synchronized (cache) { + Boolean cached = cache.get(cacheKey); + if (cached != null) return cached; + } + // aarch64 only. An x86_64 layer's winewayland.so loads and drives the compositor under + // Box64, but it cannot dlopen the aarch64 Wayland Turnip the game has to present through, + // so the session would come up as a desktop that never renders a frame. + File winewayland = new File(installPath, UNIX_DRIVER); + File waylandTurnip = new File(installPath, WAYLAND_TURNIP); + boolean capable = winewayland.isFile() && waylandTurnip.isFile(); + synchronized (cache) { + cache.put(cacheKey, capable); + } + return capable; + } +} diff --git a/app/src/main/runtime/display/winhandler/WinHandler.java b/app/src/main/runtime/display/winhandler/WinHandler.java index f20221a46..abefea429 100644 --- a/app/src/main/runtime/display/winhandler/WinHandler.java +++ b/app/src/main/runtime/display/winhandler/WinHandler.java @@ -1,5 +1,7 @@ package com.winlator.cmod.runtime.display.winhandler; +import com.winlator.cmod.runtime.display.wayland.WaylandCompositor; + import android.content.Context; import android.content.SharedPreferences; import android.hardware.input.InputManager; @@ -419,8 +421,33 @@ public void setProcessAffinity(final int pid, final int affinityMask) { }); } + private volatile boolean waylandMouseRouting; + + /** Wayland mode: relative mouse input goes to the compositor instead of the guest-side bridge. */ + public void setWaylandMouseRouting(boolean on) { + waylandMouseRouting = on; + } + + private static void waylandMouseEvent(int flags, int dx, int dy, int wheelDelta) { + if ((flags & MouseEventFlags.MOVE) != 0) WaylandCompositor.sendPointerDelta(dx, dy); + if ((flags & MouseEventFlags.LEFTDOWN) != 0) WaylandCompositor.nativeSendSceneInput(3, 0x110, 1); + if ((flags & MouseEventFlags.LEFTUP) != 0) WaylandCompositor.nativeSendSceneInput(3, 0x110, 0); + if ((flags & MouseEventFlags.RIGHTDOWN) != 0) WaylandCompositor.nativeSendSceneInput(3, 0x111, 1); + if ((flags & MouseEventFlags.RIGHTUP) != 0) WaylandCompositor.nativeSendSceneInput(3, 0x111, 0); + if ((flags & MouseEventFlags.MIDDLEDOWN) != 0) WaylandCompositor.nativeSendSceneInput(3, 0x112, 1); + if ((flags & MouseEventFlags.MIDDLEUP) != 0) WaylandCompositor.nativeSendSceneInput(3, 0x112, 0); + if ((flags & MouseEventFlags.WHEEL) != 0 && wheelDelta != 0) { + int steps = wheelDelta > 0 ? -Math.max(1, wheelDelta / 120) : Math.max(1, -wheelDelta / 120); + WaylandCompositor.nativeSendSceneInput(4, steps, 0); + } + } + public void mouseEvent(final int flags, final int dx, final int dy, final int wheelDelta) { checkGyroActivatorMouseFlags(flags); + if (waylandMouseRouting) { + waylandMouseEvent(flags, dx, dy, wheelDelta); + return; + } if (!this.initReceived) { return; } diff --git a/app/src/main/runtime/display/xserver/XServer.java b/app/src/main/runtime/display/xserver/XServer.java index 4877170d7..a222c8989 100644 --- a/app/src/main/runtime/display/xserver/XServer.java +++ b/app/src/main/runtime/display/xserver/XServer.java @@ -86,8 +86,50 @@ public boolean isDri3Enabled() { return dri3Enabled; } + /** Delta mouse delivery: the user's relative-mouse toggle, or a Wayland pointer lock. */ public boolean isRelativeMouseMovement() { - return relativeMouseMovement; + return relativeMouseMovement || externalRelativeMode; + } + + private volatile boolean externalRelativeMode; + + /** Wayland mode: a program holds a pointer lock, so input must arrive as deltas. */ + public void setExternalRelativeMode(boolean on) { + externalRelativeMode = on; + } + + /** + * Wayland mode: no X client is connected, so the input the app injects (touchpad, on-screen + * controls, keyboard) is handed to the compositor as well. Coordinates are screen pixels, keys + * are evdev codes. + */ + public interface InputSink { + void onPointerMove(int x, int y); + + void onPointerButton(Pointer.Button button, boolean pressed); + + void onKey(int evdev, boolean pressed); + } + + private volatile InputSink inputSink; + + public void setInputSink(InputSink sink) { + inputSink = sink; + } + + private void sinkPointerMove() { + InputSink sink = inputSink; + if (sink != null) sink.onPointerMove(pointer.getX(), pointer.getY()); + } + + private void sinkPointerButton(Pointer.Button button, boolean pressed) { + InputSink sink = inputSink; + if (sink != null) sink.onPointerButton(button, pressed); + } + + private void sinkKey(XKeycode xKeycode, boolean pressed) { + InputSink sink = inputSink; + if (sink != null) sink.onKey((xKeycode.id & 0xff) - 8, pressed); } public boolean isPointerCaptureActive() { @@ -219,6 +261,7 @@ public void injectPointerMove(int x, int y) { try (XLock lock = lock(Lockable.WINDOW_MANAGER, Lockable.INPUT_DEVICE)) { pointer.setPosition(x, y); } + sinkPointerMove(); } public void injectPointerMoveDelta(int dx, int dy) { @@ -259,6 +302,7 @@ public void injectPointerMoveDelta(int dx, int dy) { XInput2Extension xi = getExtension(XInput2Extension.MAJOR_OPCODE); if (xi != null) xi.emitRawMotion(2, (double)dx, (double)dy); } + sinkPointerMove(); if (renderer != null) renderer.requestCursorRender(); } @@ -298,6 +342,7 @@ public void injectPointerButtonPress(Pointer.Button buttonCode) { XInput2Extension xInput2Extension = getExtension(XInput2Extension.MAJOR_OPCODE); if (xInput2Extension != null) xInput2Extension.emitRawButton(2, buttonCode.ordinal() + 1, true); } + sinkPointerButton(buttonCode, true); } public void injectPointerButtonRelease(Pointer.Button buttonCode) { @@ -308,6 +353,7 @@ public void injectPointerButtonRelease(Pointer.Button buttonCode) { XInput2Extension xInput2Extension = getExtension(XInput2Extension.MAJOR_OPCODE); if (xInput2Extension != null) xInput2Extension.emitRawButton(2, buttonCode.ordinal() + 1, false); } + sinkPointerButton(buttonCode, false); } public void injectKeyPress(XKeycode xKeycode) { @@ -319,6 +365,7 @@ public void injectKeyPress(XKeycode xKeycode, int keysym) { try (XLock lock = lock(Lockable.WINDOW_MANAGER, Lockable.INPUT_DEVICE)) { keyboard.setKeyPress(xKeycode.id, keysym); } + sinkKey(xKeycode, true); } public void injectKeyRelease(XKeycode xKeycode) { @@ -326,6 +373,7 @@ public void injectKeyRelease(XKeycode xKeycode) { try (XLock lock = lock(Lockable.WINDOW_MANAGER, Lockable.INPUT_DEVICE)) { keyboard.setKeyRelease(xKeycode.id); } + sinkKey(xKeycode, false); } private void registerExtension(Extension ext, int[] nextEventId, int[] nextErrorId) { diff --git a/app/src/main/runtime/input/controls/Binding.java b/app/src/main/runtime/input/controls/Binding.java index 25e6e7785..5124bfd50 100644 --- a/app/src/main/runtime/input/controls/Binding.java +++ b/app/src/main/runtime/input/controls/Binding.java @@ -121,6 +121,7 @@ public enum Binding { GAMEPAD_BUTTON_R3, GAMEPAD_BUTTON_L2, GAMEPAD_BUTTON_R2, + GAMEPAD_BUTTON_GUIDE, GAMEPAD_LEFT_THUMB_UP, GAMEPAD_LEFT_THUMB_RIGHT, GAMEPAD_LEFT_THUMB_DOWN, diff --git a/app/src/main/runtime/input/controls/ControlElement.java b/app/src/main/runtime/input/controls/ControlElement.java index 6292dd892..50348d85d 100644 --- a/app/src/main/runtime/input/controls/ControlElement.java +++ b/app/src/main/runtime/input/controls/ControlElement.java @@ -464,7 +464,7 @@ private int resolveAccentColor() { return customColor; } - /** Bumper/trigger labels follow the standard gamepad naming; null for every other binding. */ + /** Bumper, trigger and guide labels follow the standard gamepad naming; null for the rest. */ private static String bumperTriggerLabel(Binding binding) { if (binding == null) return null; switch (binding) { @@ -476,6 +476,8 @@ private static String bumperTriggerLabel(Binding binding) { return "LT"; case GAMEPAD_BUTTON_R2: return "RT"; + case GAMEPAD_BUTTON_GUIDE: + return "WN"; default: return null; } @@ -627,7 +629,7 @@ private static void endBloom(Paint paint) { } public void draw(Canvas canvas) { - if (isAdaptiveHidden()) return; + if (isAdaptiveHidden() || isGuideOutsideLinuxSession()) return; VisualStyle style = inputControlsView.getVisualStyle(); if (style == VisualStyle.GAMEHUB) { drawGameHub(canvas); @@ -3785,7 +3787,19 @@ private boolean isAdaptiveHidden() { return isAdaptiveStick() && !isEngaged(); } + // Only a Linux session's Steam answers the guide button, so it stays out of the way elsewhere. + private boolean isGuideOutsideLinuxSession() { + if (inputControlsView.isGuideButtonShown()) return false; + boolean guide = false; + for (Binding binding : bindings) { + if (binding == Binding.GAMEPAD_BUTTON_GUIDE) guide = true; + else if (binding != Binding.NONE) return false; + } + return guide; + } + private boolean acceptsTouchAt(float x, float y) { + if (isGuideOutsideLinuxSession()) return false; if (!isAdaptiveStick()) return containsPoint(x, y); Rect box = getBoundingBox(); float reach = box.width() * ADAPTIVE_REACH; diff --git a/app/src/main/runtime/input/controls/FakeInputWriter.java b/app/src/main/runtime/input/controls/FakeInputWriter.java index 026686ae5..08cf443c1 100644 --- a/app/src/main/runtime/input/controls/FakeInputWriter.java +++ b/app/src/main/runtime/input/controls/FakeInputWriter.java @@ -76,8 +76,11 @@ public class FakeInputWriter { public static final short BTN_START = 315; public static final short BTN_THUMBL = 317; public static final short BTN_THUMBR = 318; + public static final short BTN_MODE = 316; + private static final int GUIDE_INDEX = 10; private static final short[] BUTTON_MAP = { - BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, BTN_TR, BTN_SELECT, BTN_START, BTN_THUMBL, BTN_THUMBR + BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, BTN_TR, BTN_SELECT, BTN_START, BTN_THUMBL, BTN_THUMBR, + BTN_MODE }; private boolean isOpen = false; private volatile boolean destroyed = false; @@ -601,9 +604,10 @@ public synchronized void writeGamepadState(GamepadState state) throws IOExceptio } this.buffer.clear(); this.hasChanges = false; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < GUIDE_INDEX; i++) { writeButton(i, state.isPressed((byte) i)); } + writeButton(GUIDE_INDEX, state.isPressed(GamepadState.BUTTON_GUIDE)); int rx = (int) (state.thumbRX * 32767.0f); int ry = (int) (state.thumbRY * 32767.0f); int lx = (int) (state.thumbLX * 32767.0f); diff --git a/app/src/main/runtime/input/controls/InputControlsManager.java b/app/src/main/runtime/input/controls/InputControlsManager.java index 081ed5f74..693bb3f44 100644 --- a/app/src/main/runtime/input/controls/InputControlsManager.java +++ b/app/src/main/runtime/input/controls/InputControlsManager.java @@ -26,7 +26,7 @@ import org.json.JSONObject; public class InputControlsManager { - private static final int ASSET_PROFILE_SYNC_REVISION = 13; + private static final int ASSET_PROFILE_SYNC_REVISION = 14; private static final String ASSET_PROFILES_DIR = "inputcontrols/profiles"; public static final int LAST_BUILTIN_PROFILE_ID = 8; public static final int VIRTUAL_GAMEPAD_BUILTIN_ID = 3; diff --git a/app/src/main/runtime/input/ui/InputControlsView.java b/app/src/main/runtime/input/ui/InputControlsView.java index 3750c936b..fd03d6680 100644 --- a/app/src/main/runtime/input/ui/InputControlsView.java +++ b/app/src/main/runtime/input/ui/InputControlsView.java @@ -60,6 +60,7 @@ public class InputControlsView extends View { public static final String EXTRA_ADAPTIVE_JOYSTICKS = "adaptiveJoysticks"; private static final byte MOUSE_WHEEL_DELTA = 120; private boolean editMode = false; + private boolean guideButtonShown = true; private final HashMap<Integer, SteamPadInput> steamPadInputs = new HashMap<>(); private static final class SteamPadInput { @@ -193,6 +194,15 @@ public boolean isEditMode() { return editMode; } + public void setGuideButtonShown(boolean shown) { + guideButtonShown = shown; + invalidate(); + } + + public boolean isGuideButtonShown() { + return guideButtonShown || editMode; + } + public void setOverlayOpacity(float overlayOpacity) { this.overlayOpacity = overlayOpacity; invalidate(); @@ -1347,6 +1357,9 @@ public void handleInputEvent( stateChanged = state.isPressed(buttonIdx) != isActionDown; if (stateChanged) state.setPressed(buttonIdx, isActionDown); } + } else if (binding == Binding.GAMEPAD_BUTTON_GUIDE) { + stateChanged = state.isPressed(GamepadState.BUTTON_GUIDE) != isActionDown; + if (stateChanged) state.setPressed(GamepadState.BUTTON_GUIDE, isActionDown); } else if (binding == Binding.GAMEPAD_LEFT_THUMB_UP || binding == Binding.GAMEPAD_LEFT_THUMB_DOWN) { float val = (isActionDown && offset == 0) ? 1.0f : Math.abs(offset); diff --git a/app/src/main/runtime/input/ui/TouchpadView.kt b/app/src/main/runtime/input/ui/TouchpadView.kt index 11de1e166..3e3f7b683 100644 --- a/app/src/main/runtime/input/ui/TouchpadView.kt +++ b/app/src/main/runtime/input/ui/TouchpadView.kt @@ -65,6 +65,10 @@ class TouchpadView( private var scrolling = false private var sensitivity = 1.0f private var simTouchScreen = false + private var sinkTouchActive = false + private var sinkTapTime = 0L + private var sinkTapX = 0f + private var sinkTapY = 0f private var screenTouchMode = MODE_TRACKPAD private var rtsGesturesEnabled = false private val xform = XForm.getInstance() @@ -362,6 +366,10 @@ class TouchpadView( 1, 6 -> { if (event.pointerCount == 2) handleTwoFingerTap(event) else handleTouchUp(event); return true } 2 -> { if (event.pointerCount == 2) handleTwoFingerScroll(event) else handleTouchMove(event); return true } 3 -> { + if (sinkTouchActive) { + sinkTouchActive = false + touchscreenSink?.onTouch(2, event.rawX, event.rawY) + } xServer.injectPointerButtonRelease(Pointer.Button.BUTTON_LEFT) xServer.injectPointerButtonRelease(Pointer.Button.BUTTON_RIGHT) return true @@ -370,7 +378,20 @@ class TouchpadView( return true } + /** + * Takes touchscreen-mode touches (0 = down, 1 = move, 2 = up, in screen coordinates) while a + * Wayland session presents: the compositor knows where on the screen it draws the scene, + * which this view's own transform does not. Returns false when no such session is attached. + */ + fun interface TouchscreenSink { + fun onTouch(action: Int, rawX: Float, rawY: Float): Boolean + } + + var touchscreenSink: TouchscreenSink? = null + private fun handleTouchDown(event: MotionEvent) { + if (sinkTouchActive) return + if (event.pointerCount == 1 && tapToClickEnabled && !isInputSuspended && sinkTouchDown(event)) return val transformedPoint = XForm.transformPoint(xform, event.x, event.y) var tx = transformedPoint[0].toInt() var ty = transformedPoint[1].toInt() @@ -393,13 +414,36 @@ class TouchpadView( } } + /** A second tap close to the first lands exactly on it, so a double tap is a double click. */ + private fun sinkTouchDown(event: MotionEvent): Boolean { + val sink = touchscreenSink ?: return false + val now = System.currentTimeMillis() + val near = Math.hypot((event.rawX - sinkTapX).toDouble(), (event.rawY - sinkTapY).toDouble()) < TOUCHSCREEN_DOUBLE_TAP_DISTANCE + if (now - sinkTapTime >= TOUCHSCREEN_DOUBLE_TAP_MS || !near) { + sinkTapX = event.rawX + sinkTapY = event.rawY + } + sinkTapTime = now + sinkTouchActive = sink.onTouch(0, sinkTapX, sinkTapY) + return sinkTouchActive + } + private fun handleTouchMove(event: MotionEvent) { if (isInputSuspended) return + if (sinkTouchActive) { + touchscreenSink?.onTouch(1, event.rawX, event.rawY) + return + } val transformedPoint = XForm.transformPoint(xform, event.x, event.y) xServer.injectPointerMove(transformedPoint[0].toInt(), transformedPoint[1].toInt()) } private fun handleTouchUp(event: MotionEvent) { + if (sinkTouchActive) { + sinkTouchActive = false + touchscreenSink?.onTouch(2, event.rawX, event.rawY) + return + } if (!isInputSuspended) xServer.injectPointerButtonRelease(Pointer.Button.BUTTON_LEFT) } diff --git a/app/src/main/runtime/linux/ChildProcessRestrictions.kt b/app/src/main/runtime/linux/ChildProcessRestrictions.kt new file mode 100644 index 000000000..d1b434d5e --- /dev/null +++ b/app/src/main/runtime/linux/ChildProcessRestrictions.kt @@ -0,0 +1,52 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.Settings + +/** + * Android's phantom process monitor, as it applies to a Linux session. + * + * A session is one proot holding gamescope, the Steam client, its web helper and a game, which + * passes the thirty-two child processes Android 12 and later allow an app; over that the system + * kills them, and killing proot ends the session. The monitor is off while + * `settings_enable_monitor_phantom_procs` is false. Developer options carries a switch for it from + * Android 14 ("Disable child process restrictions"); before that only adb can set it, and the app + * can do neither itself - writing it needs a permission only the system grants. + */ +object ChildProcessRestrictions { + private const val SETTING = "settings_enable_monitor_phantom_procs" + + /** Whether this device kills a session's processes, so the user still has something to do. */ + @JvmStatic + fun active(context: Context): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return false + val value = + try { + Settings.Global.getString(context.contentResolver, SETTING) + } catch (e: SecurityException) { + return true + } + return !"false".equals(value, ignoreCase = true) && value != "0" + } + + /** Whether Developer options has the switch, or the user needs adb. */ + @JvmStatic + fun hasSwitch(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE + + /** Opens Developer options, and reports whether the device let it. */ + @JvmStatic + fun openDeveloperOptions(context: Context): Boolean { + val intent = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + return try { + context.startActivity(intent) + true + } catch (e: android.content.ActivityNotFoundException) { + false + } catch (e: SecurityException) { + false + } + } +} diff --git a/app/src/main/runtime/linux/LinuxClientInstaller.kt b/app/src/main/runtime/linux/LinuxClientInstaller.kt new file mode 100644 index 000000000..9e5f77d4b --- /dev/null +++ b/app/src/main/runtime/linux/LinuxClientInstaller.kt @@ -0,0 +1,818 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.net.Uri +import android.os.StatFs +import android.os.SystemClock +import android.system.ErrnoException +import android.system.Os +import android.util.Log +import androidx.annotation.StringRes +import com.winlator.cmod.R +import com.winlator.cmod.feature.library.LinuxApps +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.container.ContainerCreation +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.content.AdrenotoolsManager +import com.winlator.cmod.runtime.content.ContentsManager +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.display.wayland.WineWaylandSupport +import com.winlator.cmod.runtime.system.SessionKeepAliveService +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.io.TarCompressorUtils +import com.winlator.cmod.shared.util.OnExtractFileListener +import java.io.File +import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL +import java.security.MessageDigest +import java.util.Locale +import java.util.TimeZone +import kotlin.coroutines.coroutineContext +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.job +import kotlinx.coroutines.launch +import org.apache.commons.compress.archivers.zip.ZipFile +import org.json.JSONObject + +/** + * Installs what the GameScope container needs for the native Steam client: the Linux runtime, + * published as a release asset of WinNative's Components repository, Valve's arm64 Proton from + * the same release so the first Windows title has its compatibility tool, then the arm64 Steam client + * from Valve's update servers, and finally the GameScope container with its Steam library entry. + * Valve's client is not redistributable, so it always comes from Valve. winnative-steam-install performs the same client steps inside a session and skips them + * once the stamp written here exists. + */ +object LinuxClientInstaller { + private const val TAG = "LinuxClientInstaller" + private const val RELEASE = "https://github.com/WinNative-Emu/Components/releases/download/Assets" + private const val RUNTIME_ARCHIVE = "$RELEASE/linuxfs.tar.zst" + private const val RUNTIME_INFO = "$RELEASE/linuxfs.json" + private const val PROTON_ARCHIVE = "$RELEASE/proton-arm64.tar.zst" + private const val PROTON_INFO = "$RELEASE/proton-arm64.json" + private const val PROTON_DIR = "/opt/winnative-proton" + private const val PROTON_STAGING_DIR = "proton.staging" + private const val DRIVER_ARCHIVE = "$RELEASE/linux-turnip.tar.zst" + private const val DRIVER_INFO = "$RELEASE/linux-turnip.json" + private const val DRIVER_STAGING_DIR = "linux-driver.staging" + private const val COMPOSITOR_DRIVER_ASSET = "WN-Turnip-1.16-p_Axxx.zip" + private const val COMPOSITOR_DRIVER = + "https://github.com/nicholasx417/WinNative-Components/releases/download/WinNative-Turnip/$COMPOSITOR_DRIVER_ASSET" + private const val COMPOSITOR_DRIVER_SHA256 = "146023e8cb402390d2792087f3af6569d24a95d490105cbf41be1a7dacee8957" + private const val COMPOSITOR_DRIVER_SIZE = 2673455L + private const val RUNTIME_VERSION_FILE = "etc/winnative/version" + private const val PROTON_VERSION_FILE = "winnative-version" + + /** + * What a runtime being replaced hands on to the new one: the home directory with the client + * and its sign-in, the library the app maps in with its prefixes and saves, Proton, and the + * machine id the client ties its sign-in to. + */ + private val KEPT_PATHS = listOf("root", "mnt/winnative", PROTON_DIR.substring(1), "etc/machine-id") + private const val PREFERENCES = "linux_client" + private const val DISMISSED_UPDATE = "dismissed_update" + private const val STEAM_CDN = "https://client-update.fastly.steamstatic.com" + private const val STEAM_MANIFEST = "steam_client_publicbeta_linuxarm64" + private const val STEAM_ROOT = "/root/.local/share/Steam" + private const val STEAM_STAMP = "package/winnative-installed" + private const val WORK_DIR = "linux-client-download" + private const val STAGING_DIR = "linuxfs.staging" + private const val RETIRED_DIR = "linuxfs.old" + private const val SPACE_MARGIN = 512L shl 20 + private const val STEAM_UNPACK_FACTOR = 3L + private const val RECORD_DIRECTORY = -1L + private const val RECORD_LINK = -2L + private const val RECORD_OS_VERSION = -184 + private const val RECORD_VERSION = 3 + private const val CLIENT_ZONE_SECONDS = 8 * 3600L + private const val PROGRESS_INTERVAL_MS = 100L + + enum class Stage { CONNECT, DOWNLOAD_RUNTIME, INSTALL_RUNTIME, DOWNLOAD_PROTON, INSTALL_PROTON, DOWNLOAD_STEAM, INSTALL_STEAM, LIBRARY } + + sealed interface State { + data object Checking : State + + data object Missing : State + + data object Installed : State + + /** + * Installed, and the release carries a newer runtime, Proton or driver, published as [version]. + * [isNews] until the user has been shown that version once. + */ + data class UpdateAvailable(val version: Long, val isNews: Boolean) : State + + data class Working(val stage: Stage, val done: Long, val total: Long) : State + + data object Failed : State + + data class NoSpace(val needed: Long, val available: Long) : State + + /** The GameScope container cannot be created; [message] says what is missing. */ + data class Blocked(@StringRes val message: Int) : State + } + + private class NoSpaceException(val needed: Long, val available: Long) : IOException() + + private class BlockedException(@StringRes val messageRes: Int) : IOException() + + private class Part(val name: String, val file: String, val size: Long, val sha256: String) + + /** One line of the client's install record: a file's size, or -1 for a directory and -2 for a link. */ + private class Installed(val name: String, val size: Long, val modified: Long, val crc: Long) + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val lock = Any() + private val mutableState = MutableStateFlow<State>(State.Checking) + private var generation = 0 + private var job: Job? = null + private var updating = false + private val swapLock = Any() + + val state: StateFlow<State> = mutableState.asStateFlow() + + val isWorking: Boolean get() = mutableState.value is State.Working + + /** Worker thread. */ + fun isInstalled(context: Context): Boolean = + LinuxRuntime.isInstalled(context) && isProtonInstalled(context) && isSteamInstalled(context) && + hasCompositorDriver(context) && !LinuxApps.isSteamShortcutMissing(context) + + /** + * Worker thread. The app's compositor takes gamescope's frames in with a Turnip of its own, + * loaded through adrenotools; the system's Vulkan driver cannot import them and the session + * stays black. Any Turnip from Drivers serves, so one is only fetched when there is none. + */ + @JvmStatic + fun hasCompositorDriver(context: Context): Boolean { + val drivers = AdrenotoolsManager(context) + return drivers.enumarateInstalledDrivers().any { drivers.isTurnipDriver(it) } + } + + /** Whether an installed driver is the Turnip this install fetches for the compositor. */ + @JvmStatic + fun isCompositorDriver( + drivers: AdrenotoolsManager, + driverId: String, + ): Boolean = drivers.getSourceAsset(driverId) == COMPOSITOR_DRIVER_ASSET + + /** Reads what is on disk, off the calling thread, unless an install is running. */ + fun refresh(context: Context) { + val appContext = context.applicationContext + val seen = synchronized(lock) { if (isWorking) return else generation } + scope.launch { + if (isWorking) return@launch + recoverSwap(appContext) + if (!isInstalled(appContext)) { + publishFound(seen) { State.Missing } + return@launch + } + // What is on disk is known at once; whether the release has moved on takes the network. + publishFound(seen) { it as? State.UpdateAvailable ?: State.Installed } + val version = pendingUpdate(appContext) + val found = + if (version > 0) State.UpdateAvailable(version, preferences(appContext).getLong(DISMISSED_UPDATE, 0L) < version) + else State.Installed + publishFound(seen) { found } + } + } + + private fun publishFound( + seen: Int, + found: (State) -> State, + ) { + synchronized(lock) { if (generation == seen && !isWorking) mutableState.value = found(mutableState.value) } + } + + /** Installs what is missing. Started from an offered update, or retried after one, it updates too. */ + fun start(context: Context) { + val appContext = context.applicationContext + synchronized(lock) { + if (isWorking) return + val current = mutableState.value + updating = current is State.UpdateAvailable || + (updating && current != State.Installed && current != State.Missing) + val update = updating + generation++ + mutableState.value = State.Working(Stage.CONNECT, 0, 0) + job = scope.launch { install(appContext, update) } + } + } + + /** UI thread. The offer has been put in front of the user; it is not pressed on them again. */ + fun dismissUpdate( + context: Context, + update: State.UpdateAvailable, + ) { + synchronized(lock) { if (mutableState.value == update) mutableState.value = update.copy(isNews = false) } + preferences(context).edit().putLong(DISMISSED_UPDATE, update.version).apply() + } + + private fun preferences(context: Context) = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) + + fun cancel() { + synchronized(lock) { job?.cancel() } + } + + private suspend fun install( + context: Context, + update: Boolean, + ) { + val work = File(context.filesDir, WORK_DIR) + val outcome: State = + try { + // Told before a gigabyte is downloaded for a GPU the runtime's driver cannot use. + if (!WineWaylandSupport.isAdrenoDevice(context)) throw BlockedException(R.string.linux_client_gpu_unsupported) + recoverSwap(context) + FileUtils.delete(work) + if (!work.mkdirs()) throw IOException("Could not create $work") + // Checked first, so a device that cannot hold the container is told before the download. + if (gamescopeContainer(context) == null) requireSystemImage(context) + val updateRuntime = update && runtimeUpdate(context) > 0 + val updateProton = update && protonUpdate(context) > 0 + val updateDriver = update && driverUpdate(context) > 0 + // A session has the runtime's files open and mapped; they cannot change under it. + if ((updateRuntime || updateProton || updateDriver) && SessionKeepAliveService.isLinuxSessionActive()) { + throw BlockedException(R.string.linux_client_update_session_running) + } + if (updateRuntime || !LinuxRuntime.isInstalled(context)) installRuntime(context, work) + if (updateProton || !isProtonInstalled(context)) installProton(context, work) + if (updateDriver) installDriver(context, work) + if (!hasCompositorDriver(context)) installCompositorDriver(context, work) + if (!isSteamInstalled(context)) installSteam(context, work) + addToLibrary(context) + State.Installed + } catch (e: CancellationException) { + discard(context, work) + publishSettled(if (isInstalled(context)) State.Installed else State.Missing) + throw e + } catch (e: NoSpaceException) { + State.NoSpace(e.needed, e.available) + } catch (e: BlockedException) { + State.Blocked(e.messageRes) + } catch (e: Exception) { + Log.w(TAG, "Linux client install failed", e) + State.Failed + } + discard(context, work) + publishSettled(outcome) + } + + private fun installedVersion(file: File): Long = runCatching { file.readText().trim().toLong() }.getOrDefault(0L) + + /** 0 when the release cannot be read or, as before versions were published, names none. */ + private fun publishedVersion(infoUrl: String): Long = + runCatching { JSONObject(fetchText(infoUrl)).optLong("version", 0L) } + .getOrElse { + Log.d(TAG, "No published version at $infoUrl", it) + 0L + } + + /** Each of these: the published version when what is installed is older, else 0. */ + private fun runtimeUpdate(context: Context): Long = + if (LinuxRuntime.isInstalled(context)) { + newer(RUNTIME_INFO, installedVersion(File(LinuxRuntime.rootDir(context), RUNTIME_VERSION_FILE))) + } else { + 0L + } + + /** Only the copy installed here is updated; a depot of Proton is the client's to keep current. */ + private fun protonUpdate(context: Context): Long { + val proton = hostPath(context, PROTON_DIR) + return if (File(proton, "proton").isFile) newer(PROTON_INFO, installedVersion(File(proton, PROTON_VERSION_FILE))) else 0L + } + + /** A Turnip newer than the one the app ships and any downloaded before. */ + private fun driverUpdate(context: Context): Long = + newer(DRIVER_INFO, maxOf(LinuxRuntime.TURNIP_BUILD, LinuxRuntime.downloadedDriverVersion(context))) + + private fun newer( + infoUrl: String, + installed: Long, + ): Long = publishedVersion(infoUrl).takeIf { it > installed } ?: 0L + + /** Worker thread. The published version the install is behind, or 0 when it is current. */ + private fun pendingUpdate(context: Context): Long = + runtimeUpdate(context).takeIf { it > 0 } ?: protonUpdate(context).takeIf { it > 0 } ?: driverUpdate(context) + + private fun publishSettled(outcome: State) { + synchronized(lock) { mutableState.value = outcome } + } + + private fun discard( + context: Context, + work: File, + ) { + FileUtils.delete(work) + FileUtils.delete(File(context.filesDir, STAGING_DIR)) + FileUtils.delete(File(context.filesDir, PROTON_STAGING_DIR)) + FileUtils.delete(File(context.filesDir, DRIVER_STAGING_DIR)) + FileUtils.delete(File(context.filesDir, RETIRED_DIR)) + } + + private suspend fun installRuntime( + context: Context, + work: File, + ) { + val staging = File(context.filesDir, STAGING_DIR) + unpackRelease(context, work, RUNTIME_INFO, RUNTIME_ARCHIVE, staging, Stage.DOWNLOAD_RUNTIME, Stage.INSTALL_RUNTIME) + replaceRootfs(context, staging) + } + + /** A Steam depot of the ARM64 Proton serves as well as the copy installed here. */ + private suspend fun installCompositorDriver( + context: Context, + work: File, + ) { + val archive = File(work, COMPOSITOR_DRIVER_ASSET) + val digest = download(COMPOSITOR_DRIVER, archive, Meter(Stage.DOWNLOAD_RUNTIME, COMPOSITOR_DRIVER_SIZE)) + if (digest != COMPOSITOR_DRIVER_SHA256) throw IOException("The display driver did not arrive intact") + if (AdrenotoolsManager(context).installDriver(Uri.fromFile(archive), COMPOSITOR_DRIVER_ASSET).isEmpty()) { + throw IOException("The display driver could not be installed") + } + } + + private fun isProtonInstalled(context: Context): Boolean = + File(hostPath(context, PROTON_DIR), "proton").isFile || + listOf("Proton Experimental (ARM64)", "Proton 11.0 (ARM64)").any { + File(hostPath(context, STEAM_ROOT), "steamapps/common/$it/proton").isFile + } + + private suspend fun installProton( + context: Context, + work: File, + ) { + val staging = File(context.filesDir, PROTON_STAGING_DIR) + val version = + unpackRelease(context, work, PROTON_INFO, PROTON_ARCHIVE, staging, Stage.DOWNLOAD_PROTON, Stage.INSTALL_PROTON) + File(staging, PROTON_VERSION_FILE).writeText("$version\n") + val target = hostPath(context, PROTON_DIR) + FileUtils.delete(target) + val parent = target.parentFile + if (parent != null && !parent.isDirectory && !parent.mkdirs()) throw IOException("Could not create $parent") + if (!staging.renameTo(target)) throw IOException("Could not move Proton into place") + } + + /** + * The driver archive holds the library and the name of its Mesa release; the manifest that + * points the Vulkan loader at it is written here, where the path it lands at is known. + */ + private suspend fun installDriver( + context: Context, + work: File, + ) { + val staging = File(context.filesDir, DRIVER_STAGING_DIR) + val version = + unpackRelease(context, work, DRIVER_INFO, DRIVER_ARCHIVE, staging, Stage.DOWNLOAD_RUNTIME, Stage.INSTALL_RUNTIME) + val target = LinuxRuntime.driverDir(context) + if (!File(staging, LinuxRuntime.DRIVER_LIBRARY).isFile) throw IOException("The driver archive holds no driver") + val manifest = + JSONObject() + .put("file_format_version", "1.0.0") + .put( + "ICD", + JSONObject() + .put("api_version", "1.4.0") + .put("library_path", File(target, LinuxRuntime.DRIVER_LIBRARY).path), + ) + File(staging, LinuxRuntime.DRIVER_ICD).writeText(manifest.toString()) + File(staging, LinuxRuntime.DRIVER_VERSION_FILE).writeText("$version\n") + FileUtils.delete(target) + if (!staging.renameTo(target)) throw IOException("Could not move the driver into place") + } + + /** + * Downloads a release archive, checks it against its published digest and unpacks it into + * [staging]. Returns the version the release gives it. + */ + private suspend fun unpackRelease( + context: Context, + work: File, + infoUrl: String, + archiveUrl: String, + staging: File, + downloadStage: Stage, + installStage: Stage, + ): Long { + mutableState.value = State.Working(Stage.CONNECT, 0, 0) + val info = JSONObject(fetchText(infoUrl)) + val sha256 = info.getString("sha256") + val size = info.getLong("size") + val unpacked = info.getLong("unpacked") + requireSpace(context.filesDir, size + unpacked) + + val archive = File(work, archiveUrl.substringAfterLast('/')) + val downloaded = Meter(downloadStage, size) + if (!download(archiveUrl, archive, downloaded).equals(sha256, ignoreCase = true)) { + throw IOException("${archive.name} does not match its published checksum") + } + + FileUtils.delete(staging) + if (!staging.mkdirs()) throw IOException("Could not create $staging") + val written = Meter(installStage, unpacked) + val installJob = coroutineContext.job + val listener = + object : OnExtractFileListener { + override fun onExtractFile( + destination: File, + size: Long, + ): File = destination + + override fun mapsExtractedFiles(): Boolean = false + + override fun reportsExtractedBytesOnly(): Boolean = true + + override fun onExtractedBytes(size: Long) { + if (!installJob.isActive) throw CancellationException() + written.add(size) + } + } + val extracted = TarCompressorUtils.extract(TarCompressorUtils.Type.ZSTD, archive, staging, listener) + coroutineContext.ensureActive() + if (!extracted) throw IOException("${archive.name} could not be unpacked") + FileUtils.delete(archive) + return info.optLong("version", 0L) + } + + /** Swaps the unpacked tree in, handing [KEPT_PATHS] on from a runtime being replaced. */ + private fun replaceRootfs( + context: Context, + staging: File, + ) { + val root = LinuxRuntime.rootDir(context) + if (!root.exists()) { + if (!staging.renameTo(root)) throw IOException("Could not move the runtime into place") + return + } + val retired = File(context.filesDir, RETIRED_DIR) + FileUtils.delete(retired) + synchronized(swapLock) { swap(root, staging, retired) } + FileUtils.delete(retired) + } + + private fun swap( + root: File, + staging: File, + retired: File, + ) { + val handedOn = ArrayList<Pair<File, File>>() + try { + for (path in KEPT_PATHS) { + val old = File(root, path) + if (!old.exists()) continue + val fresh = File(staging, path) + FileUtils.delete(fresh) + val parent = fresh.parentFile + if (parent != null && !parent.isDirectory && !parent.mkdirs()) throw IOException("Could not create $parent") + if (!old.renameTo(fresh)) throw IOException("Could not keep the runtime's /$path") + handedOn += old to fresh + } + if (!root.renameTo(retired)) throw IOException("Could not move the old runtime aside") + if (!staging.renameTo(root)) { + retired.renameTo(root) + throw IOException("Could not move the runtime into place") + } + } catch (e: IOException) { + // The old runtime stays in use, so it takes back what it had handed on. + for ((old, fresh) in handedOn.asReversed()) fresh.renameTo(old) + throw e + } + } + + /** + * Puts right a swap the process did not live to finish, before anything discards the staged + * tree: by then it may hold what the old runtime handed on. The old runtime is only moved + * aside once the new one is whole, so with it aside the new one goes in; otherwise the old one + * takes back whatever it no longer has. + */ + private fun recoverSwap(context: Context) { + val root = LinuxRuntime.rootDir(context) + val staging = File(context.filesDir, STAGING_DIR) + val retired = File(context.filesDir, RETIRED_DIR) + synchronized(swapLock) { + if (!root.exists()) { + if (retired.isDirectory) (if (staging.isDirectory) staging else retired).renameTo(root) + } else if (staging.isDirectory) { + for (path in KEPT_PATHS) { + val handedOn = File(staging, path) + val home = File(root, path) + if (handedOn.exists() && !home.exists()) handedOn.renameTo(home) + } + } + } + } + + private fun gamescopeContainer(context: Context): Container? = LinuxApps.gamescopeContainer(ContainerManager(context)) + + /** Containers live in the system image, so the GameScope container cannot be made without it. */ + private fun requireSystemImage(context: Context) { + if (!ImageFs.find(context).isUpToDate) throw BlockedException(R.string.setup_wizard_system_image_not_installed) + } + + /** Creates the GameScope container if there is none and makes sure it carries the Steam entry. */ + private fun addToLibrary(context: Context) { + mutableState.value = State.Working(Stage.LIBRARY, 0, 0) + val manager = ContainerManager(context) + val container = + LinuxApps.gamescopeContainer(manager) ?: run { + requireSystemImage(context) + val contents = ContentsManager(context) + contents.syncContents() + val runtime = ContainerCreation.newestInstalledRuntime(contents) + ContainerCreation.createGamescopeContainer(context, manager, contents, runtime) + ?: throw BlockedException(R.string.containers_gamescope_create_failed) + } + LinuxApps.ensureSteamShortcut(context, container) + } + + private fun isSteamInstalled(context: Context): Boolean { + val steamRoot = hostPath(context, STEAM_ROOT) + return File(steamRoot, STEAM_STAMP).isFile && File(steamRoot, "steamrtarm64/steam").isFile + } + + private suspend fun installSteam( + context: Context, + work: File, + ) { + mutableState.value = State.Working(Stage.CONNECT, 0, 0) + val manifest = fetchText("$STEAM_CDN/$STEAM_MANIFEST") + val version = manifestVersion(manifest) + val parts = manifestParts(manifest) + val total = parts.sumOf { it.size } + requireSpace(context.filesDir, total * STEAM_UNPACK_FACTOR) + + val downloaded = Meter(Stage.DOWNLOAD_STEAM, total) + val zips = + parts.map { part -> + val zip = File(work, part.file) + if (!download("$STEAM_CDN/${part.file}", zip, downloaded).equals(part.sha256, ignoreCase = true)) { + throw IOException("${part.name} does not match the checksum in Valve's manifest") + } + zip + } + + val steamRoot = hostPath(context, STEAM_ROOT) + val unpacked = Meter(Stage.INSTALL_STEAM, zips.sumOf { it.length() }) + val installed = LinkedHashMap<String, Installed>() + for (zip in zips) { + unzip(zip, steamRoot, unpacked, installed) + FileUtils.delete(zip) + } + writeInstallRecord(steamRoot, manifest, installed.values) + finishSteam(context, steamRoot, version) + } + + private fun manifestVersion(manifest: String): String = + Regex("^\\s*\"version\"\\s+\"([^\"]+)\"", RegexOption.MULTILINE).find(manifest)?.groupValues?.get(1) + ?: throw IOException("Valve's manifest names no client version") + + /** + * Every component in the manifest. The client installs them all, and one left out here is one + * its updater fetches behind a black screen at first launch. + */ + private fun manifestParts(manifest: String): List<Part> { + val token = Regex("\"([^\"]*)\"") + val blocks = linkedMapOf<String, MutableMap<String, String>>() + var depth = 0 + var block: String? = null + for (line in manifest.lineSequence()) { + val trimmed = line.trim() + when { + trimmed == "{" -> depth++ + trimmed == "}" -> { + depth-- + if (depth <= 1) block = null + } + else -> { + val values = token.findAll(trimmed).map { it.groupValues[1] }.toList() + when { + values.size == 1 && depth == 1 -> block = values[0] + values.size == 2 && depth == 2 && block != null -> + blocks.getOrPut(block) { mutableMapOf() }[values[0]] = values[1] + } + } + } + } + val parts = + blocks + .mapNotNull { (name, fields) -> + val file = fields["file"] ?: return@mapNotNull null + val sha256 = fields["sha2"] ?: return@mapNotNull null + Part(name, file, fields["size"]?.toLongOrNull() ?: 0L, sha256) + } + if (parts.isEmpty()) throw IOException("Valve's manifest lists no client components") + return parts + } + + /** + * Valve's zips start with a short prefix before the first entry, which Android's own zip reader + * refuses; this one reads them through the central directory. Some entries are packed with + * Windows separators and are written where the name means. Each entry is dated as the client + * dates it and noted in [installed]; a directory two components share is noted as the later + * one has it, which is why the components keep the manifest's order. + */ + private suspend fun unzip( + zip: File, + steamRoot: File, + meter: Meter, + installed: MutableMap<String, Installed>, + ) { + val rootPath = steamRoot.canonicalPath + File.separator + ZipFile.builder().setFile(zip).get().use { archive -> + val entries = archive.entries + while (entries.hasMoreElements()) { + coroutineContext.ensureActive() + val entry = entries.nextElement() + val name = entry.name.replace('\\', '/') + val target = File(steamRoot, name) + if (!target.canonicalPath.startsWith(rootPath)) { + throw IOException("${zip.name} has an entry outside the client directory: $name") + } + val modified = clientTime(entry.time) + if (name.endsWith("/")) { + if (!target.isDirectory && !target.mkdirs()) throw IOException("Could not create $target") + installed[name] = Installed(name, RECORD_DIRECTORY, modified, 0L) + } else { + val parent = target.parentFile + if (parent != null && !parent.isDirectory && !parent.mkdirs()) { + throw IOException("Could not create $parent") + } + if (entry.isUnixSymlink) { + link(archive.getUnixSymlink(entry), target) + installed[name] = Installed(name, RECORD_LINK, modified, entry.crc) + } else { + archive.getInputStream(entry).use { input -> + target.outputStream().use { output -> input.copyTo(output) } + } + // The client's updater leaves every file it unpacks executable. + target.setExecutable(true, true) + target.setLastModified(modified * 1000L) + installed[name] = Installed(name, entry.size, modified, entry.crc) + } + } + meter.add(entry.compressedSize.coerceAtLeast(0L)) + } + } + } + + /** + * The client reads a zip's clock fields as Pacific Standard Time all year round. [zipTime] is + * those fields already read in this device's zone, so the zone's offset is taken back out. + */ + private fun clientTime(zipTime: Long): Long = + (zipTime + TimeZone.getDefault().getOffset(zipTime)) / 1000L + CLIENT_ZONE_SECONDS + + /** + * What the client's updater leaves in `package/` once it has installed a manifest: the manifest, + * and every entry it unpacked, ordered without regard to case and closed by a SHA-1 of the lines + * before it. With both in place the client starts from the files here rather than fetching and + * unpacking them all again. + */ + private fun writeInstallRecord( + steamRoot: File, + manifest: String, + installed: Collection<Installed>, + ) { + val body = + buildString { + for (entry in installed.sortedBy { it.name.uppercase(Locale.ROOT) }) { + append(entry.name).append(',').append(entry.size).append(';') + append(entry.modified).append(';').append(entry.crc).append('\n') + } + append("OSVER=").append(RECORD_OS_VERSION).append("\nVERSION=").append(RECORD_VERSION).append('\n') + } + val digest = MessageDigest.getInstance("SHA-1").digest(body.toByteArray(Charsets.UTF_8)) + val sha1 = digest.joinToString("") { "%02X".format(it) } + val packageDir = File(steamRoot, "package") + if (!packageDir.isDirectory && !packageDir.mkdirs()) throw IOException("Could not create $packageDir") + File(packageDir, "$STEAM_MANIFEST.manifest").writeText(manifest) + File(packageDir, "$STEAM_MANIFEST.installed").writeText("${body}SHA1=$sha1\n") + } + + /** What winnative-steam-install and Valve's steam.sh set up around the client. */ + private fun finishSteam( + context: Context, + steamRoot: File, + version: String, + ) { + val packageDir = File(steamRoot, "package") + if (!packageDir.isDirectory && !packageDir.mkdirs()) throw IOException("Could not create $packageDir") + File(packageDir, "beta").writeText("publicbeta\n") + link("$STEAM_ROOT/steamrtarm64", File(steamRoot, "steamrtarm32")) + + val dotSteam = hostPath(context, "/root/.steam") + if (!dotSteam.isDirectory && !dotSteam.mkdirs()) throw IOException("Could not create $dotSteam") + link(STEAM_ROOT, File(dotSteam, "root")) + link(STEAM_ROOT, File(dotSteam, "steam")) + link("$STEAM_ROOT/steamrtarm64", File(dotSteam, "bin64")) + link("$STEAM_ROOT/steamrtarm64", File(dotSteam, "binarm64")) + link("$STEAM_ROOT/linux64", File(dotSteam, "sdk64")) + link("$STEAM_ROOT/linux32", File(dotSteam, "sdk32")) + link("$STEAM_ROOT/linuxarm64", File(dotSteam, "sdkarm64")) + File(steamRoot, STEAM_STAMP).writeText("$version\n") + } + + /** A link whose target is a path inside the runtime, which proot resolves against its root. */ + private fun link( + target: String, + file: File, + ) { + try { + if (FileUtils.isSymlink(file) || file.exists()) FileUtils.delete(file) + Os.symlink(target, file.path) + } catch (e: ErrnoException) { + throw IOException("Could not link $file to $target", e) + } + } + + private fun hostPath( + context: Context, + guestPath: String, + ): File = File(LinuxRuntime.rootDir(context), guestPath.removePrefix("/")) + + private fun requireSpace( + dir: File, + bytes: Long, + ) { + val needed = bytes + SPACE_MARGIN + val available = StatFs(dir.path).availableBytes + if (available < needed) throw NoSpaceException(needed, available) + } + + private fun open(url: String): HttpURLConnection = + (URL(url).openConnection() as HttpURLConnection).apply { + connectTimeout = 15000 + readTimeout = 30000 + instanceFollowRedirects = true + setRequestProperty("User-Agent", "WinNative") + } + + private fun fetchText(url: String): String { + val connection = open(url) + try { + if (connection.responseCode !in 200..299) throw IOException("HTTP ${connection.responseCode} for $url") + return connection.inputStream.bufferedReader().use { it.readText() } + } finally { + connection.disconnect() + } + } + + /** Downloads [url] into [target] and returns the SHA-256 of what arrived. */ + private suspend fun download( + url: String, + target: File, + meter: Meter, + ): String { + val digest = MessageDigest.getInstance("SHA-256") + val connection = open(url) + try { + if (connection.responseCode !in 200..299) throw IOException("HTTP ${connection.responseCode} for $url") + connection.inputStream.use { input -> + target.outputStream().use { output -> + val buffer = ByteArray(1 shl 16) + while (true) { + coroutineContext.ensureActive() + val read = input.read(buffer) + if (read < 0) break + output.write(buffer, 0, read) + digest.update(buffer, 0, read) + meter.add(read.toLong()) + } + } + } + } finally { + connection.disconnect() + } + return digest.digest().joinToString("") { "%02x".format(it) } + } + + /** Publishes one stage's progress, no more often than the dialog can show it. */ + private class Meter( + private val stage: Stage, + private val total: Long, + ) { + private var done = 0L + private var published = 0L + + init { + mutableState.value = State.Working(stage, 0, total) + } + + fun add(bytes: Long) { + done += bytes + val now = SystemClock.uptimeMillis() + if (now - published >= PROGRESS_INTERVAL_MS || done >= total) { + published = now + mutableState.value = State.Working(stage, done.coerceAtMost(total), total) + } + } + } +} diff --git a/app/src/main/runtime/linux/LinuxDriverChoices.kt b/app/src/main/runtime/linux/LinuxDriverChoices.kt new file mode 100644 index 000000000..182980ef7 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxDriverChoices.kt @@ -0,0 +1,68 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.util.Log +import com.winlator.cmod.feature.library.LinuxApps +import com.winlator.cmod.feature.settings.GraphicsDriverConfigUtils +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import java.io.File + +/** + * The Vulkan driver each GameScope game draws with. A session draws with the driver of the entry + * that started it, and a game the client starts inherits that, so `winnative-proton-launch` reads + * this file as the game starts: the driver its shortcut's settings chose, else the container's `*`. + */ +object LinuxDriverChoices { + private const val TAG = "LinuxDriverChoices" + private const val CHOICES = "etc/winnative/driver-choices" + private val lock = Any() + + /** Worker thread. */ + @JvmStatic + fun write(context: Context) { + synchronized(lock) { + if (!LinuxRuntime.isInstalled(context)) return + val file = File(LinuxRuntime.rootDir(context), CHOICES) + try { + val container = LinuxApps.gamescopeContainer(ContainerManager(context)) + if (container == null) { + file.delete() + return + } + val containerConfig = container.graphicsDriverConfig.orEmpty() + val lines = ArrayList<String>() + icd(context, containerConfig)?.let { lines += "* $it" } + for (entry in container.desktopDir.listFiles { f -> f.name.endsWith(".desktop") }.orEmpty()) { + val shortcut = Shortcut(container, entry) + if (LinuxApps.isLinuxShortcut(shortcut)) continue + val appId = LinuxSteamShortcuts.clientAppId(context, shortcut) ?: continue + val config = shortcut.getSettingExtra("graphicsDriverConfig", containerConfig).orEmpty() + icd(context, config)?.let { lines += "$appId $it" } + } + if (lines.isEmpty()) { + file.delete() + } else { + LinuxSteamVdf.replace(file) { it.writeText(lines.joinToString("\n", postfix = "\n")) } + } + } catch (error: Exception) { + Log.w(TAG, "Could not record the drivers", error) + } + } + } + + /** [write] off the caller's thread, for a settings screen that has just saved. */ + @JvmStatic + fun update(context: Context) { + val app = context.applicationContext + Thread({ write(app) }, "LinuxDriverChoices").start() + } + + /** The manifest a session given [config] would draw with, as the session resolves it. */ + private fun icd(context: Context, config: String): String? { + val settings = GraphicsDriverConfigUtils.parseGraphicsDriverConfig(Container.DEFAULT_GRAPHICSDRIVERCONFIG) + settings.putAll(GraphicsDriverConfigUtils.parseGraphicsDriverConfig(config)) + return LinuxRuntime.vulkanIcd(context, settings["version"])?.path?.takeUnless { '\n' in it } + } +} diff --git a/app/src/main/runtime/linux/LinuxEpicTokens.kt b/app/src/main/runtime/linux/LinuxEpicTokens.kt new file mode 100644 index 000000000..9b7dc1c39 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxEpicTokens.kt @@ -0,0 +1,192 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.util.Log +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.db.dao.EpicGameDao +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.steam.enums.AppType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import java.io.File +import java.io.IOException +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.ServerSocket +import java.net.SocketException +import java.security.SecureRandom +import java.util.Locale + +/** + * Signs an Epic game in as the Linux Steam client starts it. + * + * Epic authenticates a game with an exchange code that lasts five minutes and is spent the first + * time it is used, so there is nothing to write into a shortcut ahead of time: the client may sit + * in the library for an hour before the user presses Play, and pressing it twice would replay a + * code that is already gone. The shortcut carries the game's Epic name instead, and + * `winnative-epic-launch` in the runtime asks this server for the command line as the game starts. + * + * The server listens on the loopback address alone and answers only a caller that quotes the + * secret written beside its port. That file is in the app's own runtime directory: the session can + * read it, and another app on the device - which shares the loopback address - cannot. + */ +object LinuxEpicTokens { + private const val TAG = "LinuxEpicTokens" + + /** Under the runtime's root, and `/run/winnative-epic` inside the session. */ + private const val DIR = "run/winnative-epic" + private const val ENDPOINT = "endpoint" + private const val TOKENS = "tokens" + + /** Long enough for the two Epic calls a DRM title needs, short enough to not hold a launch. */ + private const val REQUEST_TIMEOUT_MS = 45000 + + private var server: ServerSocket? = null + private var endpointFile: File? = null + + /** + * Starts the server for this session, unless the user has no Epic account signed in. Does + * nothing when one is already running, which is a session being rejoined. + */ + @JvmStatic + @Synchronized + fun start(context: Context) { + if (server != null) return + val appContext = context.applicationContext + val dir = File(LinuxRuntime.rootDir(appContext), DIR) + val tokens = File(dir, TOKENS) + // A session of an app run that is gone left its port behind; nothing answers there now, + // and an account signed out since is not to be asked for again. + File(dir, ENDPOINT).delete() + if (!EpicAuthManager.isLoggedIn(appContext)) return + try { + // Tokens of an earlier session are spent; the game reads the one written for this run. + tokens.listFiles()?.forEach { it.delete() } + if (!tokens.isDirectory && !tokens.mkdirs()) throw IOException("could not create ${tokens.path}") + val socket = ServerSocket() + socket.bind(InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0), 4) + val secret = ByteArray(16).also { SecureRandom().nextBytes(it) }.joinToString("") { "%02x".format(it) } + val endpoint = File(dir, ENDPOINT) + LinuxSteamVdf.replace(endpoint) { it.writeText("${socket.localPort} $secret\n") } + server = socket + endpointFile = endpoint + Thread({ serve(appContext, socket, secret) }, "epic-tokens").start() + } catch (e: IOException) { + Log.w(TAG, "could not start the Epic sign-in server: ${e.javaClass.simpleName}") + } + } + + /** Ends the session's server. The endpoint goes with it, so a stale port is never answered. */ + @JvmStatic + @Synchronized + fun stop() { + endpointFile?.delete() + endpointFile = null + try { + server?.close() + } catch (e: IOException) { + Log.w(TAG, "could not close the Epic sign-in server: ${e.javaClass.simpleName}") + } + server = null + } + + /** + * One request at a time: a launch is a rare event, and a game that is starting has the whole + * answer before the next one asks. + */ + private fun serve(context: Context, socket: ServerSocket, secret: String) { + while (true) { + try { + socket.accept().use { client -> + client.soTimeout = REQUEST_TIMEOUT_MS + val request = client.getInputStream().bufferedReader().readLine().orEmpty() + val reply = + if (!request.startsWith("$secret ")) { + Log.w(TAG, "a caller without the session's secret was refused") + "error unauthorized" + } else { + answer(context, request.substring(secret.length + 1).trim()) + } + client.getOutputStream().apply { + write(reply.toByteArray()) + flush() + } + } + } catch (e: SocketException) { + return // The session ended and stop() closed the socket. + } catch (e: IOException) { + if (socket.isClosed) return + Log.w(TAG, "Epic sign-in request failed: ${e.javaClass.simpleName}") + } catch (e: RuntimeException) { + // One request that could not be answered is not the session's other games. + Log.w(TAG, "Epic sign-in request failed: ${e.javaClass.simpleName}") + } + } + } + + /** `ok` and one command-line argument per line, or `error <what went wrong>`. */ + private fun answer(context: Context, appName: String): String { + if (appName.isEmpty()) return "error no game named" + val game = gameOf(context, appName) ?: return "error $appName is not in the Epic library" + val built = + runBlocking(Dispatchers.IO) { + EpicGameLauncher.buildLaunchParameters( + context = context, + game = game, + stageOwnershipToken = { hex -> stageOwnershipToken(context, game, hex) }, + ) + } + val params = + built.getOrElse { e -> + Log.w(TAG, "could not sign $appName in: ${e.javaClass.simpleName}") + return "error ${e.message ?: e.javaClass.simpleName}" + } + Log.i(TAG, "signed $appName in for the Linux client") + return (listOf("ok") + params).joinToString("\n", postfix = "\n") + } + + /** + * The library's game [id] as the store recorded it, read from the store's own records: they are + * there whether or not the Epic service happens to be running. + */ + @JvmStatic + fun gameOf(context: Context, id: Int): EpicGame? = read(context) { it.getById(id) } + + private fun gameOf(context: Context, appName: String): EpicGame? = read(context) { it.getByAppName(appName) } + + /** + * The Epic games that are installed, as the store recorded them. The library writes a shortcut + * for one only once it has been played, so this is what the client is given to show. + */ + @JvmStatic + fun installedGames(context: Context): List<EpicGame> = + try { + runBlocking(Dispatchers.IO) { PluviaDatabase.getInstance(context).epicGameDao().getAllAsList() } + .filter { it.isInstalled && it.installPath.isNotEmpty() && !it.isDLC && it.type == AppType.game } + } catch (e: RuntimeException) { + Log.w(TAG, "the Epic library is unavailable: ${e.javaClass.simpleName}") + emptyList() + } + + private fun read(context: Context, query: suspend (EpicGameDao) -> EpicGame?): EpicGame? = + try { + runBlocking(Dispatchers.IO) { query(PluviaDatabase.getInstance(context).epicGameDao()) } + } catch (e: RuntimeException) { + Log.w(TAG, "the Epic library is unavailable: ${e.javaClass.simpleName}") + null + } + + /** + * Writes the ownership token a DRM title is started with and returns the path the game reads it + * back by. Proton maps `Z:` to the session's root, so the runtime's own directory is reachable + * without touching the prefix, which Proton has not built yet when the game is started. + */ + private fun stageOwnershipToken(context: Context, game: EpicGame, hex: String): String { + val name = (game.namespace + game.catalogId).lowercase(Locale.US).filter { it.isLetterOrDigit() } + ".ovt" + val file = File(File(LinuxRuntime.rootDir(context), DIR), "$TOKENS/$name") + LinuxSteamVdf.replace(file) { it.writeBytes(hex.chunked(2).map { byte -> byte.toInt(16).toByte() }.toByteArray()) } + return "Z:\\${DIR.replace('/', '\\')}\\$TOKENS\\$name" + } +} diff --git a/app/src/main/runtime/linux/LinuxGogGames.kt b/app/src/main/runtime/linux/LinuxGogGames.kt new file mode 100644 index 000000000..f80c52b79 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxGogGames.kt @@ -0,0 +1,81 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.util.Log +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.feature.stores.steam.enums.AppType +import com.winlator.cmod.feature.stores.steam.utils.FileUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import org.json.JSONException +import org.json.JSONObject +import java.io.File + +/** + * The GOG games that are installed, as the store recorded them, and the program each one starts. + * + * The library writes a shortcut for a game only once it has been played, so an installed game the + * user has not started yet is known by the store's records alone. Those records say where a game + * was installed but not what to run: that is in the `goggame-<id>.info` the installer leaves + * beside the game, whose primary play task is the one GOG's own client would start. + */ +object LinuxGogGames { + private const val TAG = "LinuxGogGames" + + /** How far below the install directory a game's info file is looked for. */ + private const val INFO_DEPTH = 3 + + class Installed(val id: String, val title: String, val exe: File) + + /** Worker thread. Every installed game whose program could be resolved. */ + @JvmStatic + fun installed(context: Context): List<Installed> { + val games = + try { + runBlocking(Dispatchers.IO) { PluviaDatabase.getInstance(context).gogGameDao().getAllAsList() } + } catch (e: RuntimeException) { + Log.w(TAG, "the GOG library is unavailable: ${e.javaClass.simpleName}") + return emptyList() + } + return games + .filter { it.isInstalled && it.installPath.isNotEmpty() && it.type == AppType.game } + .mapNotNull { game -> + exeOf(File(game.installPath), game.id)?.let { Installed(game.id, game.title, it) } + } + } + + /** The program the game in [install] starts, or null when its install says nothing usable. */ + private fun exeOf(install: File, id: String): File? { + val info = infoFile(install, id) ?: return null + return try { + val tasks = JSONObject(info.readText()).optJSONArray("playTasks") ?: return null + for (i in 0 until tasks.length()) { + val task = tasks.optJSONObject(i) ?: continue + if (!task.optBoolean("isPrimary")) continue + val path = task.optString("path") + if (path.isEmpty()) return null + return FileUtils.findFileCaseInsensitive(info.parentFile ?: install, path)?.takeIf { it.isFile } + } + null + } catch (e: JSONException) { + Log.w(TAG, "could not read ${info.name}: ${e.javaClass.simpleName}") + null + } + } + + /** + * The game's own info file, which sits beside its program - in the install directory for an + * older game, and a level or two down for one the installer laid out in parts. + */ + private fun infoFile(install: File, id: String): File? { + var level = listOf(install) + repeat(INFO_DEPTH + 1) { + val files = level.flatMap { it.listFiles()?.asList().orEmpty() } + files.firstOrNull { it.isFile && it.name.equals("goggame-$id.info", ignoreCase = true) }?.let { return it } + files.firstOrNull { it.isFile && it.name.startsWith("goggame-") && it.extension == "info" }?.let { return it } + level = files.filter { it.isDirectory } + if (level.isEmpty()) return null + } + return null + } +} diff --git a/app/src/main/runtime/linux/LinuxNetworkLinkComponent.kt b/app/src/main/runtime/linux/LinuxNetworkLinkComponent.kt new file mode 100644 index 000000000..9de05caa0 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxNetworkLinkComponent.kt @@ -0,0 +1,107 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.net.ConnectivityManager +import android.net.LinkProperties +import android.net.Network +import android.os.Build +import android.util.Log +import com.winlator.cmod.runtime.display.environment.EnvironmentComponent +import com.winlator.cmod.runtime.display.environment.components.NetworkingSettings +import java.io.File +import java.io.IOException +import java.net.Inet4Address +import java.net.Inet6Address + +/** + * Tells the Linux runtime which network link the device is on. The sandbox hides interfaces, + * addresses, routes and the MAC from the runtime's processes, so its preload answers those + * queries from the file written here (tools/linuxfs/preload/netif.c). + */ +class LinuxNetworkLinkComponent( + context: Context, + private val rootDir: File, + private val driver: String, + private val fixedMac: String, +) : EnvironmentComponent() { + private val appContext = context.applicationContext + private val connectivity = + appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + private val lock = Any() + private var callback: ConnectivityManager.NetworkCallback? = null + + /** Called before the session starts, so its first process already sees the link. */ + fun publish() = write(connectivity.activeNetwork?.let(connectivity::getLinkProperties)) + + override fun start() { + if (driver == NetworkingSettings.DRIVER_NONE) return + val registered = object : ConnectivityManager.NetworkCallback() { + override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) = write(properties) + + override fun onLost(network: Network) = write(null) + } + synchronized(lock) { callback = registered } + connectivity.registerDefaultNetworkCallback(registered) + } + + override fun stop() { + val registered = synchronized(lock) { callback.also { callback = null } } ?: return + try { + connectivity.unregisterNetworkCallback(registered) + } catch (e: IllegalArgumentException) { + Log.w(TAG, "Network callback was already gone", e) + } + } + + private fun write(properties: LinkProperties?) { + val file = File(rootDir, LINK_FILE) + synchronized(lock) { + try { + if (driver == NetworkingSettings.DRIVER_NONE) { + file.delete() + return + } + val staged = File(file.path + ".staged") + staged.writeText(describe(properties)) + if (!staged.renameTo(file)) throw IOException("Could not replace $file") + } catch (e: IOException) { + Log.w(TAG, "Could not publish the network link", e) + } + } + } + + private fun describe(properties: LinkProperties?): String { + val addresses = properties?.linkAddresses.orEmpty() + .filter { it.address is Inet4Address || it.address is Inet6Address } + val name = properties?.interfaceName?.takeIf { addresses.isNotEmpty() } ?: OFFLINE_NAME + val mtu = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) properties?.mtu ?: 0 else 0 + val mac = NetworkingSettings.normalizeMac(fixedMac) + .ifEmpty { NetworkingSettings.automaticMac(appContext, name) } + return buildString { + append("if $name $LINK_INDEX ${if (mtu > 0) mtu else DEFAULT_MTU}\n") + append("mac $mac\n") + if (addresses.isEmpty()) { + append("addr $OFFLINE_ADDRESS\n") + return@buildString + } + for (address in addresses) { + append("addr ${address.address.hostAddress?.substringBefore('%')} ${address.prefixLength}\n") + } + for (family in listOf(Inet4Address::class.java, Inet6Address::class.java)) { + val gateway = properties?.routes.orEmpty() + .firstOrNull { it.isDefaultRoute && family.isInstance(it.gateway) && !it.gateway!!.isAnyLocalAddress } + ?.gateway ?: continue + append("gw ${gateway.hostAddress?.substringBefore('%')}\n") + } + } + } + + companion object { + private const val TAG = "LinuxNetworkLink" + private const val LINK_FILE = "etc/winnative-net" + private const val LINK_INDEX = 2 + private const val DEFAULT_MTU = 1500 + private const val OFFLINE_NAME = "eth0" + private const val OFFLINE_ADDRESS = "10.0.0.2 24" + } +} diff --git a/app/src/main/runtime/linux/LinuxProtons.kt b/app/src/main/runtime/linux/LinuxProtons.kt new file mode 100644 index 000000000..c6b65fbf8 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxProtons.kt @@ -0,0 +1,379 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.system.Os +import android.util.Log +import com.winlator.cmod.R +import com.winlator.cmod.feature.library.LinuxApps +import com.winlator.cmod.feature.stores.steam.utils.KeyValue +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.system.SessionKeepAliveService +import java.io.File +import java.net.HttpURLConnection +import java.net.URL +import java.security.MessageDigest +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream +import org.json.JSONArray +import org.json.JSONObject +import kotlin.coroutines.coroutineContext + +object LinuxProtons { + data class Build(val id: String, val name: String, val url: String, val sha256: String, val size: Long) { + val displayName: String get() = when { + id.startsWith("GE-Proton") -> "GE-Proton ${id.removePrefix("GE-Proton").removeSuffix("-aarch64")} (ARM64)" + id.startsWith("proton-cachyos-") -> "CachyOS Proton ${id.removePrefix("proton-cachyos-").removeSuffix("-slr-arm64")}" + else -> name + } + fun json() = JSONObject().put("id", id).put("name", name).put("url", url).put("sha256", sha256).put("size", size) + } + data class State(val builds: List<Build> = emptyList(), val installed: Set<String> = emptySet(), val loading: Boolean = false, val working: String? = null, val progress: Float = Float.NaN, val stage: Int = R.string.common_ui_downloading_file, val failed: Boolean = false, val removing: Boolean = false) + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val mutable = MutableStateFlow(State()) + val state = mutable.asStateFlow() + private var job: Job? = null + private val sources = listOf("GloriousEggroll/proton-ge-custom" to "aarch64", "CachyOS/proton-cachyos" to "arm64") + private const val TAG = "LinuxProtons" + private const val MARKER = "winnative-proton.json" + private const val CHOICES = "etc/winnative/proton-choices" + private val TOOL_NAME = Regex("[A-Za-z0-9_-][A-Za-z0-9._-]*") + private val choicesLock = Any() + private const val ORIGINAL_MANIFEST = "toolmanifest.vdf.winnative-original" + private const val WRAPPER = "winnative-proton-wrap" + private const val TOOL_MANIFEST = """"manifest" +{ + "version" "2" + "commandline" "/winnative-proton-wrap %verb%" + "use_sessions" "1" + "compatmanager_layer_name" "proton" +} +""" + private val WRAPPER_SCRIPT = """#!/bin/sh +here=${'$'}(dirname "${'$'}0") +exec /usr/local/bin/winnative-proton-launch "${'$'}here/proton" "${'$'}@" +""" + + fun directory(context: Context) = File(LinuxRuntime.rootDir(context), "root/.local/share/Steam/compatibilitytools.d") + private fun installed(context: Context) = directory(context).listFiles().orEmpty().mapNotNull { file -> + runCatching { parse(JSONObject(File(file, MARKER).readText())) }.getOrNull()?.takeIf { it.id == file.name } + } + /** + * Worker thread. What a GameScope game can be set to run under, as tool name to label: WinNative's + * own Proton Experimental first, then the builds downloaded from Components. + */ + fun choices(context: Context): List<Pair<String, String>> = + listOf(Container.LINUX_PROTON_DEFAULT to context.getString(R.string.gamescope_proton_default)) + + installed(context).sortedBy { it.displayName }.map { it.id to it.displayName } + + /** + * Worker thread. Settles each GameScope game's Proton between the app and the Steam client, + * whichever changed it last, and writes what `winnative-proton-launch` reads as the game starts. + * + * A game's line keeps the tool the client mapped when the line was written. While the client + * still maps that tool, the app's choice is the newer one and the launcher follows it; once the + * client maps something else, the change was made in Steam and the shortcut takes it. With no + * client running, the app's choice is also written into the client's mapping so Steam shows it. + * The container's `*` covers games with no shortcut that the client leaves on WinNative's own. + * + * Returns each game's tool by app id. + */ + fun reconcile(context: Context): Map<String, String> = synchronized(choicesLock) { + if (!LinuxRuntime.isInstalled(context)) return emptyMap() + val rootfs = LinuxRuntime.rootDir(context) + val file = File(rootfs, CHOICES) + val chosen = HashMap<String, String>() + try { + val container = LinuxApps.gamescopeContainer(ContainerManager(context)) + if (container == null) { + file.delete() + return emptyMap() + } + val config = File(LinuxSteamVdf.steamRoot(rootfs), "config/config.vdf") + val tree = if (config.isFile) LinuxSteamVdf.load(config, "InstallConfigStore") else null + val mapping = tree?.let { LinuxSteamVdf.section(it, LinuxSteamVdf.STEAM_KEY + "CompatToolMapping") } + val recorded = readChoices(file) + val clientIdle = !SessionKeepAliveService.isLinuxSessionActive() + val fallback = container.getLinuxProton() + val lines = ArrayList<String>() + if (fallback != Container.LINUX_PROTON_DEFAULT && fallback.matches(TOOL_NAME)) lines += "* $fallback" + var pushed = false + for (entry in container.desktopDir.listFiles { f -> f.name.endsWith(".desktop") }.orEmpty()) { + val shortcut = Shortcut(container, entry) + if (LinuxApps.isLinuxShortcut(shortcut)) continue + val appId = LinuxSteamShortcuts.clientAppId(context, shortcut)?.toString() ?: continue + val own = if (shortcut.usesContainerDefaults()) "" else shortcut.getExtra(Container.EXTRA_LINUX_PROTON) + var tool = own.ifEmpty { fallback } + // Steam's "Default" deletes the game's entry, which leaves it on the "0" one. + val mapped = mapping?.let { toolOf(it[appId]) ?: toolOf(it["0"]) ?: Container.LINUX_PROTON_DEFAULT } + val base = recorded[appId] + val changedInClient = + if (base != null) mapped != null && mapped != base + // Nothing recorded yet: a tool the user picked in Steam outranks a default. + else mapped != null && mapped != Container.LINUX_PROTON_DEFAULT && own.isEmpty() + if (changedInClient && mapped != tool) { + tool = mapped!! + if (tool == fallback) { + shortcut.putExtra(Container.EXTRA_LINUX_PROTON, null) + } else { + shortcut.putExtra(Container.EXTRA_LINUX_PROTON, tool) + shortcut.putExtra("use_container_defaults", "0") + } + shortcut.saveData() + } + if (!tool.matches(TOOL_NAME)) continue + var current = mapped ?: Container.LINUX_PROTON_DEFAULT + if (clientIdle && mapping != null && current != tool) { + val item = mapping[appId].takeIf { it !== KeyValue.INVALID } ?: KeyValue(appId).apply { + isSection = true + children += KeyValue("name", tool) + children += KeyValue("config", "") + children += KeyValue("priority", "250") + mapping.children += this + } + val name = item["name"] + if (name === KeyValue.INVALID) item.children += KeyValue("name", tool) else name.value = tool + current = tool + pushed = true + } + chosen[appId] = tool + lines += "$appId $tool $current" + } + if (pushed && tree != null) LinuxSteamVdf.save(config, tree) + if (lines.isEmpty()) { + file.delete() + } else { + LinuxSteamVdf.replace(file) { it.writeText(lines.joinToString("\n", postfix = "\n")) } + } + } catch (error: Exception) { + Log.w(TAG, "Could not settle the Proton choices", error) + } + chosen + } + + private fun toolOf(entry: KeyValue): String? = entry["name"].value?.takeIf { it.matches(TOOL_NAME) } + + /** What the last [reconcile] recorded for each game: the tool the client had mapped then. */ + private fun readChoices(file: File): Map<String, String> = + runCatching { file.readLines() }.getOrDefault(emptyList()).mapNotNull { line -> + val fields = line.split(' ') + if (fields.size == 3 && fields[0] != "*") fields[0] to fields[2] else null + }.toMap() + + /** [reconcile] off the caller's thread, for a settings screen that has just saved. */ + fun updateChoices(context: Context) { + val app = context.applicationContext + Thread({ reconcile(app) }, "LinuxProtonChoices").start() + } + + /** + * Worker thread. The tool [shortcut] runs its game under once the client's side is taken into + * account, or null when it is not a game the client starts. + */ + fun choiceFor(context: Context, shortcut: Shortcut): String? { + val appId = LinuxSteamShortcuts.clientAppId(context, shortcut)?.toString() ?: return null + return reconcile(context)[appId] + } + + private fun parse(json: JSONObject) = Build(json.getString("id"), json.getString("name"), json.getString("url"), json.getString("sha256"), json.getLong("size")) + + @Synchronized + fun refresh(context: Context) { + if (job?.isActive == true) return + val app = context.applicationContext + mutable.value = mutable.value.copy(loading = true, failed = false) + job = scope.launch { + // An install the system killed part-way leaves its half-unpacked tree behind, and + // nothing else ever looks at it again: a couple of gigabytes that no screen shows. + directory(app).listFiles().orEmpty() + .filter { it.name.startsWith(".") && (it.name.endsWith(".winnative-removing") || it.name.endsWith(".staging")) } + .forEach { it.deleteRecursively() } + val local = installed(app) + val results = sources.map { (repo, arch) -> runCatching { fetch(repo, arch) } } + val builds = (results.flatMap { it.getOrDefault(emptyList()) } + local).distinctBy { it.id } + mutable.value = State(builds = builds, installed = local.map { it.id }.toSet(), failed = results.any { it.isFailure }) + } + } + + private fun connection(url: String) = (URL(url).openConnection() as HttpURLConnection).apply { + connectTimeout = 20000 + readTimeout = 30000 + setRequestProperty("User-Agent", "WinNative") + setRequestProperty("Accept", "application/vnd.github+json") + } + + private fun fetch(repo: String, arch: String): List<Build> { + val connection = connection("https://api.github.com/repos/$repo/releases?per_page=30") + val releases = try { JSONArray(connection.inputStream.bufferedReader().use { it.readText() }) } finally { connection.disconnect() } + val result = mutableListOf<Build>() + for (index in 0 until releases.length()) { + val release = releases.getJSONObject(index) + if (release.optBoolean("draft") || release.optBoolean("prerelease")) continue + val assets = release.getJSONArray("assets") + for (assetIndex in 0 until assets.length()) { + val asset = assets.getJSONObject(assetIndex) + val name = asset.getString("name") + if (!name.matches(Regex("[A-Za-z0-9._-]+-$arch\\.tar\\.(gz|xz)"))) continue + val digest = asset.optString("digest").removePrefix("sha256:") + if (!digest.matches(Regex("[a-fA-F0-9]{64}"))) continue + val id = name.substringBefore(".tar.") + result += Build(id, id, asset.getString("browser_download_url"), digest, asset.getLong("size")) + } + if (result.size >= 3) break + } + check(result.isNotEmpty()) + return result.take(3) + } + + @Synchronized + fun install(context: Context, build: Build) { + if (job?.isActive == true) return + val app = context.applicationContext + mutable.value = mutable.value.copy(working = build.id, progress = Float.NaN, failed = false, stage = R.string.common_ui_downloading_file) + job = scope.launch { + val archive = File(app.cacheDir, "linux-proton-${build.id}.part") + SessionKeepAliveService.startDownload(app, "linux_proton") + try { + check(LinuxRuntime.isInstalled(app)) + // What the tree unpacks to, against the archive it unpacks from: measured at 3.4x + // for the gzip builds and 6.5x for the xz ones, and both are on disk at once. + val expansion = if (build.url.endsWith(".xz")) 8L else 5L + check(app.filesDir.usableSpace > build.size * expansion + (512L shl 20)) + val connection = connection(build.url) + val digest = MessageDigest.getInstance("SHA-256") + var done = 0L + var updated = 0L + try { + connection.inputStream.use { input -> archive.outputStream().buffered().use { output -> + val buffer = ByteArray(131072) + while (true) { + coroutineContext.ensureActive() + val count = input.read(buffer) + if (count < 0) break + done += count + check(done <= build.size) + digest.update(buffer, 0, count) + output.write(buffer, 0, count) + val now = android.os.SystemClock.elapsedRealtime() + if (now - updated > 150) { + mutable.value = mutable.value.copy(progress = done.toFloat() / build.size) + updated = now + } + } + } } + } finally { connection.disconnect() } + check(done == build.size && digest.digest().joinToString("") { "%02x".format(it.toInt() and 255) }.equals(build.sha256, true)) + mutable.value = mutable.value.copy(stage = R.string.linux_client_stage_install_proton, progress = Float.NaN) + installArchive(app, build, archive) + mutable.value = mutable.value.copy(installed = installed(app).map { it.id }.toSet(), working = null) + } catch (error: CancellationException) { + mutable.value = mutable.value.copy(working = null) + throw error + } catch (error: Exception) { + mutable.value = mutable.value.copy(working = null, failed = true) + } finally { + archive.delete() + SessionKeepAliveService.stopDownload(app, "linux_proton") + } + } + } + + internal suspend fun installArchive(context: Context, build: Build, archive: File) { + require(build.id.matches(Regex("[A-Za-z0-9_-][A-Za-z0-9._-]*"))) + val tools = directory(context).apply { mkdirs() } + val staging = File(tools, ".${build.id}.staging") + staging.deleteRecursively() + check(staging.mkdirs()) + try { + val links = mutableListOf<Triple<File, String, Boolean>>() + val root = staging.canonicalFile.toPath() + var unpacked = 0L + archive.inputStream().buffered().use { input -> + val decoded = if (build.url.endsWith(".xz")) XZCompressorInputStream(input) else GzipCompressorInputStream(input) + TarArchiveInputStream(decoded).use { tar -> + while (true) { + coroutineContext.ensureActive() + val entry = tar.nextTarEntry ?: break + require(!entry.name.startsWith("/")) + val output = File(staging, entry.name).canonicalFile + require(output.toPath().startsWith(root)) + require(output != staging.canonicalFile || entry.isDirectory) + when { + entry.isDirectory -> check(output.isDirectory || output.mkdirs()) + entry.isSymbolicLink || entry.isLink -> links += Triple(output, entry.linkName, entry.isLink) + entry.isFile -> { + unpacked += entry.size + check(unpacked <= build.size * 8 + (1L shl 30)) + output.parentFile!!.mkdirs() + output.outputStream().buffered().use { tar.copyTo(it) } + Os.chmod(output.path, entry.mode and 511) + } + else -> error("Unsupported archive entry") + } + } + } + } + for ((output, target, hard) in links.sortedByDescending { it.third }) { + require(!File(target).isAbsolute) + val resolved = File(if (hard) staging else output.parentFile!!, target).canonicalFile + require(resolved.toPath().startsWith(root)) + require(!output.exists()) + output.parentFile!!.mkdirs() + if (hard) Os.link(resolved.path, output.path) else Os.symlink(target, output.path) + } + val tree = staging.listFiles().orEmpty().single { it.isDirectory && File(it, "proton").isFile } + val wine = listOf("files/bin-arm64/wine", "files/bin/wine", "files/bin/wine64").map { File(tree, it) }.firstOrNull { file -> + if (!file.isFile) false else file.inputStream().use { input -> + val header = ByteArray(20) + input.read(header) == 20 && header.take(4) == listOf<Byte>(127, 69, 76, 70) && header[4] == 2.toByte() && (header[18].toInt() and 255) == 183 && header[19] == 0.toByte() + } + } + check(wine != null) + val manifest = File(tree, "toolmanifest.vdf") + check(manifest.isFile && File(tree, "compatibilitytool.vdf").isFile) + val original = File(tree, ORIGINAL_MANIFEST) + if (!original.isFile) manifest.copyTo(original, overwrite = false) + check(Regex(""""commandline"\s+"/proton(?:\s+[^"]*)?"""").containsMatchIn(original.readText())) + File(tree, WRAPPER).writeText(WRAPPER_SCRIPT) + Os.chmod(File(tree, WRAPPER).path, 493) + manifest.writeText(TOOL_MANIFEST) + File(tree, MARKER).writeText(build.json().toString()) + val target = File(tools, build.id) + check(!target.exists() && tree.renameTo(target)) + } finally { staging.deleteRecursively() } + } + + @Synchronized + fun remove(context: Context, build: Build): Job? { + if (job?.isActive == true) return null + val app = context.applicationContext + mutable.value = mutable.value.copy(working = build.id, progress = Float.NaN, stage = R.string.common_ui_remove, failed = false, removing = true) + job = scope.launch { + try { + check(installed(app).any { it.id == build.id }) + val target = File(directory(app), build.id) + val retired = File(directory(app), ".${build.id}.winnative-removing") + check(!retired.exists() || retired.deleteRecursively()) + check(target.renameTo(retired)) + check(retired.deleteRecursively()) + mutable.value = mutable.value.copy(installed = installed(app).map { it.id }.toSet(), working = null, removing = false) + } catch (error: Exception) { + mutable.value = mutable.value.copy(installed = installed(app).map { it.id }.toSet(), working = null, removing = false, failed = true) + } + } + return job + } + + fun cancel() { if (!mutable.value.removing) job?.cancel() } +} diff --git a/app/src/main/runtime/linux/LinuxRuntime.java b/app/src/main/runtime/linux/LinuxRuntime.java new file mode 100644 index 000000000..86ed23872 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxRuntime.java @@ -0,0 +1,335 @@ +package com.winlator.cmod.runtime.linux; + +import android.content.Context; +import android.os.Process; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; +import com.winlator.cmod.runtime.display.environment.ImageFs; +import com.winlator.cmod.shared.io.FileUtils; +import com.winlator.cmod.runtime.wine.EnvVars; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +/** + * The glibc arm64 rootfs at {@code files/linuxfs} and the proot invocation that runs a program in it + * as this app's own uid. proot is packaged as {@code libproot.so} so the installer places it, with + * its loader, in the native library directory where it may be executed. + */ +public final class LinuxRuntime { + public static final String DIR = "linuxfs"; + public static final String SESSION_SCRIPT = "/usr/local/bin/winnative-session"; + public static final String MODE_DESKTOP = "desktop"; + public static final String MODE_STEAM = "steam"; + public static final String MODE_RUN = "run"; + private static final String KGSL_DEVICE = "/dev/kgsl-3d0"; + /** The Mesa release tools/linuxfs/build-turnip.sh builds the shipped driver from. */ + public static final String TURNIP_VERSION = "26.2.2"; + /** + * The shipped driver's place among the published ones, in the release's version numbering. A + * downloaded driver is used only while it is newer, so an app update that ships a later driver + * takes over from it. + */ + public static final long TURNIP_BUILD = 202609220000L; + /** A driver the Linux Client install downloaded, beside the rootfs so a new runtime leaves it be. */ + public static final String DRIVER_DIR = "linux-driver"; + public static final String DRIVER_LIBRARY = "libvulkan_freedreno.so"; + public static final String DRIVER_ICD = "freedreno_icd.json"; + public static final String DRIVER_VERSION_FILE = "version"; + public static final String DRIVER_NAME_FILE = "name"; + /** Refreshed into the rootfs at every session start; see {@link #syncSessionFiles}. */ + + private static final String[] SESSION_FILES = { + "usr/lib/libvulkan_freedreno.so", + "usr/local/lib/libwninput.so", + "usr/local/lib/libwnsession.so", + "usr/local/bin/winnative-directaudio", + "usr/local/bin/winnative-epic-launch", + "usr/local/bin/winnative-netmanager", + "usr/local/bin/winnative-proton-launch", + "usr/local/bin/winnative-seed-redists", + "usr/local/bin/winnative-session", + "usr/local/bin/winnative-steam-compat", + "usr/local/bin/winnative-steam-install", + "usr/local/bin/winnative-steam-library", + "usr/local/share/winnative/system-bus.conf", + }; + + private LinuxRuntime() {} + + public static File rootDir(Context context) { + return new File(context.getFilesDir(), DIR); + } + + public static File prootBinary(Context context) { + return new File(context.getApplicationInfo().nativeLibraryDir, "libproot.so"); + } + + public static File prootLoader(Context context) { + return new File(context.getApplicationInfo().nativeLibraryDir, "libproot-loader.so"); + } + + public static EnvVars hostEnvironment(Context context, EnvVars sessionEnv) { + EnvVars host = new EnvVars(); + host.put("PROOT_LOADER", prootLoader(context).getPath()); + host.put("PROOT_TMP_DIR", context.getCacheDir().getPath()); + String noSeccomp = sessionEnv.get("PROOT_NO_SECCOMP"); + if ("1".equals(noSeccomp) || "true".equalsIgnoreCase(noSeccomp) || "on".equalsIgnoreCase(noSeccomp)) { + host.put("PROOT_NO_SECCOMP", "1"); + } + return host; + } + + /** The rootfs is present with gamescope and the session script the launcher hands control to. */ + public static boolean isInstalled(Context context) { + File root = rootDir(context); + return new File(root, "usr/bin/gamescope").isFile() + && new File(root, SESSION_SCRIPT.substring(1)).isFile() + && prootBinary(context).isFile() + && prootLoader(context).isFile(); + } + + public static File driverDir(Context context) { + return new File(context.getFilesDir(), DRIVER_DIR); + } + + /** Worker thread. The downloaded driver's version, or 0 when there is none that can be used. */ + public static long downloadedDriverVersion(Context context) { + File dir = driverDir(context); + if (!new File(dir, DRIVER_LIBRARY).isFile() || !new File(dir, DRIVER_ICD).isFile()) return 0; + String version = FileUtils.readString(new File(dir, DRIVER_VERSION_FILE)); + if (version == null) return 0; + try { + return Long.parseLong(version.trim()); + } catch (NumberFormatException e) { + return 0; + } + } + + /** Worker thread. The Mesa release of the driver sessions draw with. */ + public static String driverName(Context context) { + return com.winlator.cmod.runtime.content.DriverPackages.selectedLinuxName(context); + } + + public static File vulkanIcd(Context context) { + return vulkanIcd(context, null); + } + + public static File vulkanIcd(Context context, String selection) { + File selected = com.winlator.cmod.runtime.content.DriverPackages.selectedLinuxIcd(context, selection); + if (selected != null) return selected; + File icdDir = new File(rootDir(context), "usr/share/vulkan/icd.d"); + File[] manifests = icdDir.listFiles((dir, name) -> name.endsWith(".json")); + if (manifests == null) return null; + for (File manifest : manifests) { + if (manifest.getName().contains("freedreno")) return manifest; + } + return manifests.length > 0 ? manifests[0] : null; + } + + /** + * The proot command line running {@code guestCommand} inside the rootfs. Host paths the session + * needs (the app's files directory for the compositor and audio sockets, external storage for the + * user's games) are bound at their own paths so nothing on either side needs translating; @binds + * adds {@code host:guest} pairs. Android has no /dev/shm; a directory under the cache stands in + * for it, which glibc's shm_open and Chromium's shared memory are content with. + */ + public static List<String> command( + Context context, + ImageFs imageFs, + File runtimeDir, + File externalStorage, + File inputDir, + List<String> binds, + List<String> guestCommand) { + File root = rootDir(context); + List<String> cmd = new ArrayList<>(); + cmd.add(prootBinary(context).getPath()); + cmd.add("--kill-on-exit"); + cmd.add("-r"); + cmd.add(root.getPath()); + cmd.add("-w"); + cmd.add("/root"); + bind(cmd, "/dev"); + bind(cmd, "/proc"); + bind(cmd, "/sys"); + bind(cmd, "/dev/urandom:/dev/random"); + bind(cmd, "/proc/self/fd:/dev/fd"); + bind(cmd, "/proc/self/fd/0:/dev/stdin"); + bind(cmd, "/proc/self/fd/1:/dev/stdout"); + bind(cmd, "/proc/self/fd/2:/dev/stderr"); + bind(cmd, new File(root, "etc/winnative/empty").getPath() + ":/sys/fs/selinux"); + bind(cmd, context.getFilesDir().getPath()); + bind(cmd, context.getCacheDir().getPath()); + bind(cmd, runtimeDir.getPath()); + bind(cmd, imageFs.getRootDir().getPath()); + if (externalStorage != null && externalStorage.isDirectory()) { + bind(cmd, externalStorage.getPath()); + } + File shm = new File(context.getCacheDir(), "shm"); + shm.mkdirs(); + bind(cmd, shm.getPath() + ":/dev/shm"); + // Apps may not list /dev/input; the fake evdev nodes the input rings back stand in for it. + if (inputDir != null && inputDir.isDirectory()) { + bind(cmd, inputDir.getPath() + ":/dev/input"); + } + for (String spec : binds) { + bind(cmd, spec); + } + // Android denies apps these; glibc, Steam and libcap read them at startup. + File fakeProc = new File(root, "etc/winnative/proc"); + // The last two the rootfs does not ship, so they are written here: an install that already has + // a rootfs would never receive them from the overlay. + // + // libpci picks its procfs backend on whether it can read the /proc/bus/pci directory, which the + // app can, then die()s - exit(1) - on the devices file inside it, which the app cannot. Chromium + // loads libpci in its GPU process to name the video card, so that exit took the GPU process with + // it and the Steam UI ran at half the frame rate. An empty list is the truthful answer from in + // here: nothing the app can see is on a PCI bus. + stage(new File(fakeProc, "pci_devices"), ""); + // GE-Proton and CachyOS Proton import protonfixes from the top of their proton script, and its + // esync check opens file-max with nothing catching a failure, so the PermissionError ended the + // launch before the game started and Steam fell back to the Play button. The check only warns + // below 8192; what matters is that the read succeeds. + stage(new File(fakeProc, "file_max"), "1048576\n"); + String[][] procFiles = { + {"stat", "/proc/stat"}, + {"version", "/proc/version"}, + {"loadavg", "/proc/loadavg"}, + {"uptime", "/proc/uptime"}, + {"vmstat", "/proc/vmstat"}, + {"pci_devices", "/proc/bus/pci/devices"}, + {"file_max", "/proc/sys/fs/file-max"}, + {"cap_last_cap", "/proc/sys/kernel/cap_last_cap"}, + {"overflowuid", "/proc/sys/kernel/overflowuid"}, + {"overflowgid", "/proc/sys/kernel/overflowgid"}, + }; + for (String[] entry : procFiles) { + File fake = new File(fakeProc, entry[0]); + if (fake.isFile() && !new File(entry[1]).canRead()) { + bind(cmd, fake.getPath() + ":" + entry[1]); + } + } + bindGpuNode(context, cmd); + cmd.addAll(guestCommand); + return cmd; + } + + /** + * Apps may not touch {@code /dev/dri} or read sysfs, yet libdrm and the compositors built on it + * identify a GPU by a DRM render node. The KGSL device Turnip drives stands in: it appears as a + * render node with the sysfs entries libdrm reads, and Turnip reports the same device numbers. + */ + private static void bindGpuNode(Context context, List<String> cmd) { + StructStat st; + try { + st = Os.stat(KGSL_DEVICE); + } catch (ErrnoException e) { + return; + } + long dev = st.st_rdev; + long major = ((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffL); + long minor = (dev & 0xff) | ((dev >> 12) & ~0xffL); + String node = "renderD" + minor; + File base = new File(context.getCacheDir(), "drm"); + File dri = new File(base, "dri"); + File device = new File(base, "sys/" + major + ":" + minor + "/device"); + File drm = new File(device, "drm/" + node); + try { + if ((!dri.isDirectory() && !dri.mkdirs()) || (!drm.isDirectory() && !drm.mkdirs())) { + return; + } + new File(dri, node).createNewFile(); + Files.write(new File(drm, "dev").toPath(), (major + ":" + minor + "\n").getBytes(StandardCharsets.UTF_8)); + Files.write(new File(device, "uevent").toPath(), + "DRIVER=kgsl-3d0\nMODALIAS=platform:kgsl-3d0\n".getBytes(StandardCharsets.UTF_8)); + File subsystem = new File(device, "subsystem"); + if (!Files.isSymbolicLink(subsystem.toPath())) { + Os.symlink("/sys/bus/platform", subsystem.getPath()); + } + } catch (IOException | ErrnoException e) { + return; + } + bind(cmd, new File(base, "sys").getPath() + ":/sys/dev/char"); + bind(cmd, dri.getPath() + ":/dev/dri"); + bind(cmd, KGSL_DEVICE + ":/dev/dri/" + node); + } + + private static void bind(List<String> cmd, String spec) { + cmd.add("-b"); + cmd.add(spec); + } + + /** Writes a stand-in /proc file the rootfs does not carry, leaving one already there alone. */ + private static void stage(File file, String contents) { + if (file.isFile()) return; + try { + Files.write(file.toPath(), contents.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + // Absent, it fails the isFile() test in command() and the session runs as it did before. + } + } + + /** + * Empties the directory standing in for /dev/shm. On a real system nothing in it outlives the + * processes that made it; here the files stay, and the client alone leaves some fifty megabytes + * of streams behind every time it runs. + */ + public static void clearSharedMemory(Context context) { + File shm = new File(context.getCacheDir(), "shm"); + if (shm.isDirectory()) FileUtils.clear(shm); + } + + /** + * The session's own files, refreshed from the app's copies: the Vulkan driver, the preload + * libraries that {@code /etc/ld.so.preload} names, and the scripts the session runs. + * + * They have to match the build that starts the session - a rootfs installed by an earlier one + * carries older copies, and the device has no way to replace them from outside the app. They are + * copied in whole rather than compared: the driver's 15 MB takes well under a second, and each + * copy lands through a rename, so a library another session still has mapped keeps the file + * it opened. + */ + public static void syncSessionFiles(Context context) throws IOException { + File root = rootDir(context); + for (String path : SESSION_FILES) { + File target = new File(root, path); + File dir = target.getParentFile(); + if (!dir.isDirectory() && !dir.mkdirs()) { + throw new IOException("could not create " + dir.getPath()); + } + File staged = new File(dir, target.getName() + ".staged"); + boolean installed = false; + try { + try (InputStream in = context.getAssets().open(DIR + "/" + path); + OutputStream out = new FileOutputStream(staged)) { + byte[] buffer = new byte[1 << 16]; + for (int read = in.read(buffer); read > 0; read = in.read(buffer)) { + out.write(buffer, 0, read); + } + } + installed = staged.setExecutable(true, false) && staged.renameTo(target); + } finally { + if (!installed) staged.delete(); + } + if (!installed) throw new IOException("could not install " + path); + } + } + + /** X access control and Steam look the session user up by uid: the app uid is root inside. */ + public static void writeAccounts(Context context) throws IOException { + File root = rootDir(context); + int uid = Process.myUid(); + Files.write(new File(root, "etc/passwd").toPath(), + ("root:x:" + uid + ":" + uid + ":root:/root:/bin/bash\n").getBytes(StandardCharsets.UTF_8)); + Files.write(new File(root, "etc/group").toPath(), + ("root:x:" + uid + ":\n").getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/app/src/main/runtime/linux/LinuxSteamLogin.kt b/app/src/main/runtime/linux/LinuxSteamLogin.kt new file mode 100644 index 000000000..0802c9b45 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxSteamLogin.kt @@ -0,0 +1,143 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.util.Log +import com.winlator.cmod.feature.stores.steam.utils.KeyValue +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.runtime.linux.LinuxSteamVdf.STEAM_KEY +import com.winlator.cmod.runtime.linux.LinuxSteamVdf.load +import com.winlator.cmod.runtime.linux.LinuxSteamVdf.save +import com.winlator.cmod.runtime.linux.LinuxSteamVdf.section +import java.io.File +import java.security.MessageDigest +import java.security.SecureRandom +import java.util.zip.CRC32 +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +/** + * Keeps the Linux Steam client signed in as the account the Steam store is signed in with. + * + * The client remembers an account in three files: `local.vdf` holds the refresh token under + * `ConnectCache`, `config/loginusers.vdf` lists the account and that it signs in by itself, and + * `registry.vdf` names it as the one to sign in at start. The token is stored the way the client + * stores it on Linux: AES-256 keyed with the SHA-256 of the account name, in CBC with the IV sent + * ahead as one ECB block, under the CRC-32 of the account name followed by "1". + * + * The token never leaves the app's files: it goes from the app's own encrypted preferences into + * the runtime's home directory and nowhere else - not an argument, not the environment, not a log. + */ +object LinuxSteamLogin { + private const val TAG = "LinuxSteamLogin" + private fun steamRoot(context: Context) = LinuxSteamVdf.steamRoot(LinuxRuntime.rootDir(context)) + + private fun registry(context: Context) = File(LinuxRuntime.rootDir(context), "root/.steam/registry.vdf") + + /** + * Worker thread, with no client running. Signs the client in as the store's account unless it + * already remembers that account, whose own token is then the newer one. Does nothing while the + * store is signed out: an account the user signed the client into by hand stays. + */ + @JvmStatic + fun seed(context: Context) { + val account = PrefManager.username + val token = PrefManager.refreshToken + val steamId = PrefManager.steamUserSteamId64 + if (account.isBlank() || token.isBlank() || steamId == 0L) return + try { + val root = steamRoot(context) + val local = File(root, "local.vdf") + val machine = load(local, "MachineUserConfigStore") + val cache = section(machine, STEAM_KEY + "ConnectCache") + val users = File(root, "config/loginusers.vdf") + val known = load(users, "users") + val remembered = + cache.children.any { it.name == cacheKey(account) } && known[steamId.toString()] !== KeyValue.INVALID + if (!remembered) { + cache.children.removeAll { it.name == cacheKey(account) } + cache.children += KeyValue(cacheKey(account), encrypt(token, account)) + save(local, machine) + known.children.removeAll { it.name == steamId.toString() } + known.children += loginUser(steamId, account) + save(users, known) + Log.i(TAG, "signed the Linux client in as the store's account") + } + val registryFile = registry(context) + val registryRoot = load(registryFile, "Registry") + val steam = section(registryRoot, listOf("HKCU") + STEAM_KEY) + if (steam["AutoLoginUser"].asString() != account) { + steam.children.removeAll { it.name.equals("AutoLoginUser", ignoreCase = true) } + steam.children += KeyValue("AutoLoginUser", account) + save(registryFile, registryRoot) + } + } catch (e: Exception) { + Log.w(TAG, "could not sign the Linux client in: ${e.javaClass.simpleName}") + } + } + + /** + * Worker thread. The store was signed out of [account]: the client forgets it too. Other + * accounts the client remembers are not the store's to remove. + */ + @JvmStatic + fun clear(context: Context, account: String, steamId: Long) { + if (account.isBlank() || !LinuxRuntime.isInstalled(context)) return + try { + val root = steamRoot(context) + val local = File(root, "local.vdf") + if (local.isFile) { + val tree = load(local, "MachineUserConfigStore") + val cache = section(tree, STEAM_KEY + "ConnectCache") + if (cache.children.removeAll { it.name == cacheKey(account) }) save(local, tree) + } + val users = File(root, "config/loginusers.vdf") + if (users.isFile) { + val known = load(users, "users") + if (known.children.removeAll { it.name == steamId.toString() }) save(users, known) + } + val registryFile = registry(context) + if (registryFile.isFile) { + val registryRoot = load(registryFile, "Registry") + val steam = section(registryRoot, listOf("HKCU") + STEAM_KEY) + if (steam["AutoLoginUser"].asString() == account) { + steam.children.removeAll { it.name.equals("AutoLoginUser", ignoreCase = true) } + save(registryFile, registryRoot) + } + } + } catch (e: Exception) { + Log.w(TAG, "could not sign the Linux client out: ${e.javaClass.simpleName}") + } + } + + private fun loginUser(steamId: Long, account: String): KeyValue = + KeyValue(steamId.toString()).apply { + isSection = true + children += KeyValue("AccountName", account) + children += KeyValue("PersonaName", account) + children += KeyValue("RememberPassword", "1") + children += KeyValue("WantsOfflineMode", "0") + children += KeyValue("SkipOfflineModeWarning", "0") + children += KeyValue("AutoLogin", "1") + children += KeyValue("timestamp", (System.currentTimeMillis() / 1000).toString()) + } + + private fun cacheKey(account: String): String { + val crc = CRC32().apply { update(account.toByteArray()) }.value + return "%08x1".format(crc) + } + + private fun encrypt(token: String, account: String): String { + val key = SecretKeySpec(MessageDigest.getInstance("SHA-256").digest(account.toByteArray()), "AES") + val iv = ByteArray(16).also { SecureRandom().nextBytes(it) } + val wrappedIv = Cipher.getInstance("AES/ECB/NoPadding").run { + init(Cipher.ENCRYPT_MODE, key) + doFinal(iv) + } + val body = Cipher.getInstance("AES/CBC/PKCS5Padding").run { + init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv)) + doFinal(token.toByteArray()) + } + return (wrappedIv + body).joinToString("") { "%02x".format(it) } + } +} diff --git a/app/src/main/runtime/linux/LinuxSteamShortcuts.kt b/app/src/main/runtime/linux/LinuxSteamShortcuts.kt new file mode 100644 index 000000000..cf49b758b --- /dev/null +++ b/app/src/main/runtime/linux/LinuxSteamShortcuts.kt @@ -0,0 +1,410 @@ +package com.winlator.cmod.runtime.linux + +import android.content.Context +import android.os.Environment +import android.util.Log +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.feature.library.LinuxApps +import com.winlator.cmod.feature.retro.RetroShortcuts +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.steam.enums.AppType +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.KeyValue +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.linux.LinuxSteamVdf.STEAM_KEY +import com.winlator.cmod.runtime.system.SessionKeepAliveService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.Locale +import java.util.zip.CRC32 + +/** + * The library's Windows games that are not Steam's - custom, itch.io, GOG and Epic - kept in the + * Linux Steam client as non-Steam games, each set to run under the ARM64 Proton tool. A game of a + * store is taken from its shortcut, and from the store's own records when it has none: the library + * writes a shortcut for a game only once it has been played. + * + * An Epic game is signed in as it starts rather than here: its entry names the game in its launch + * options, and the runtime's `winnative-epic-launch` asks [LinuxEpicTokens] for the command line. + * + * The client keeps such games in `userdata/<account>/config/shortcuts.vdf`, a binary VDF. Entries + * the user added in the client are carried over as they are; the ones written here are listed in + * [OWNED] beside the file, which is how an entry whose game has left the library is known to be + * ours to remove. + */ +object LinuxSteamShortcuts { + private const val TAG = "LinuxSteamShortcuts" + private const val OWNED = "winnative-shortcuts" + private const val TOOL = "winnative-proton" + + /** Read by `winnative-launch` in the runtime, which hands an Epic game to its own launcher. */ + private const val EPIC_GAME = "WN_EPIC" + private const val NON_STEAM = 0x02000000L + private const val TYPE_SECTION = 0 + private const val TYPE_STRING = 1 + private const val TYPE_INT = 2 + private const val TYPE_END = 8 + + private class Node(val name: String, var value: Any) + + private class Game( + val appId: Int, + val name: String, + val exe: File, + val icon: String, + /** Set for an Epic game alone, and the only launch options this class writes. */ + val epicAppName: String = "", + ) + + /** The URL that has the client run [shortcut], or null when it is not a game this class adds. */ + @JvmStatic + fun launchUrl(context: Context, shortcut: Shortcut): String? = + game(context, shortcut)?.let { "steam://rungameid/" + java.lang.Long.toUnsignedString((unsigned(it.appId) shl 32) or NON_STEAM) } + + /** Worker thread. The id the client knows [shortcut] by as a non-Steam game, or null when it is not one. */ + @JvmStatic + fun steamAppId(context: Context, shortcut: Shortcut): Long? = game(context, shortcut)?.let { unsigned(it.appId) } + + /** Worker thread. The id the client starts [shortcut]'s game under, or null when the client does not start it. */ + @JvmStatic + fun clientAppId(context: Context, shortcut: Shortcut): Long? = + if (shortcut.getExtra("game_source") == "STEAM") shortcut.getExtra("app_id").toLongOrNull() + else steamAppId(context, shortcut) + + /** + * Worker thread, with no client running. Brings the client's non-Steam games in line with the + * library and returns the proot bind specs for game folders the session does not already see. + */ + @JvmStatic + @Synchronized + fun sync(context: Context): List<String> { + if (!LinuxRuntime.isInstalled(context)) return emptyList() + val games = + try { + // A shortcut first: it carries the name and the artwork the user gave the game. + (ContainerManager(context).loadShortcuts().mapNotNull { game(context, it) } + + LinuxEpicTokens.installedGames(context).mapNotNull(::epicGame) + + LinuxGogGames.installed(context).map(::gogGame)) + .distinctBy { it.appId } + } catch (e: RuntimeException) { + Log.w(TAG, "library unavailable: ${e.javaClass.simpleName}") + return emptyList() + } + val steamRoot = LinuxSteamVdf.steamRoot(LinuxRuntime.rootDir(context)) + val retired = HashSet<Int>() + for (config in userConfigs(steamRoot)) { + try { + retired += write(config, games) + } catch (e: Exception) { + Log.w(TAG, "could not update ${config.parentFile?.name}: ${e.javaClass.simpleName}") + } + } + try { + val mapped = games.map { unsigned(it.appId).toString() } + ownedSteamGames(context) + mapTool(File(steamRoot, "config/config.vdf"), mapped, retired - games.map { it.appId }.toSet()) + } catch (e: Exception) { + Log.w(TAG, "could not set the compatibility tool: ${e.javaClass.simpleName}") + } + val visible = listOf(context.filesDir, context.cacheDir, Environment.getExternalStorageDirectory()) + return games + .mapNotNull { it.exe.parentFile } + .filter { dir -> visible.none { dir.startsWith(it) } } + .distinct() + .map { "${it.path}:${it.path}" } + } + + /** + * A game left the library. With no client running its entry goes now; a running client owns the + * file, and the next session's [sync] takes the entry out instead. + */ + @JvmStatic + fun removed(context: Context, shortcut: Shortcut) { + if (!isCandidate(shortcut) || SessionKeepAliveService.isLinuxSessionActive()) return + val appContext = context.applicationContext + Thread({ sync(appContext) }, "steam-shortcuts").start() + } + + private fun isCandidate(shortcut: Shortcut): Boolean = + !LinuxApps.isLinuxShortcut(shortcut) && + !RetroShortcuts.isRetroShortcut(shortcut) && + LibraryShortcutUtils.inferGameSource(shortcut).let { it == "CUSTOM" || it == "GOG" || it == "EPIC" } + + private fun game(context: Context, shortcut: Shortcut): Game? { + if (!isCandidate(shortcut)) return null + var epicAppName = "" + val (identity, exe) = + when (LibraryShortcutUtils.inferGameSource(shortcut)) { + "CUSTOM" -> + "custom:" + shortcut.getExtra("uuid").ifEmpty { shortcut.file?.path ?: shortcut.name } to + File(shortcut.getExtra("custom_exe")) + "GOG" -> + "gog:" + shortcut.getExtra("gog_id").ifEmpty { return null } to + installed(shortcut) + "EPIC" -> { + // The shortcut holds the library's own id; the game is signed in by the name + // Epic knows it as, and started by the program the store recorded, neither of + // which the shortcut carries. + val id = shortcut.getExtra("app_id").toIntOrNull() ?: return null + val record = LinuxEpicTokens.gameOf(context, id) ?: return null + epicAppName = epicName(record) ?: return null + "epic:" + epicAppName to (installed(shortcut).takeIf { it.isFile } ?: epicExe(record) ?: return null) + } + else -> return null + } + if (!exe.isFile) return null + val name = shortcut.getExtra("custom_name").ifBlank { shortcut.name } + return Game(appIdOf(identity), name, exe, shortcut.getExtra("customCoverArtPath"), epicAppName) + } + + /** + * An installed game the library has written no shortcut for yet, from the store's record alone. + * It carries no artwork of the user's, which the client fills in with its own. + */ + private fun gogGame(record: LinuxGogGames.Installed): Game = + Game(appIdOf("gog:${record.id}"), record.title, record.exe, "") + + /** As above, for Epic, which is also signed in by the name the store knows the game as. */ + private fun epicGame(record: EpicGame): Game? { + val name = epicName(record) ?: return null + val exe = epicExe(record) ?: return null + return Game(appIdOf("epic:$name"), record.title.ifBlank { name }, exe, "", name) + } + + /** The id the client files a non-Steam game under, which is the same for the same game. */ + private fun appIdOf(identity: String): Int { + val crc = CRC32().apply { update(identity.toByteArray()) }.value + return (crc or 0x80000000L).toInt() + } + + /** + * The name Epic knows [record] by, or null when it cannot be written: the name goes into the + * launch options as one word of a shell-like line, and Epic gives a game a single word. + */ + private fun epicName(record: EpicGame): String? = + record.appName.takeIf { it.isNotEmpty() && it.none { c -> c.isWhitespace() || c == '"' } } + + /** The program an Epic game starts, by the store's record and then by what is on disk. */ + private fun epicExe(record: EpicGame): File? { + val install = File(record.installPath) + val recorded = File(install, record.executable.replace('\\', '/')) + if (record.executable.isNotEmpty() && recorded.isFile) return recorded + // A record written before the manifest named one. Epic's own stubs are not the game. + val stubs = setOf("epicgameslauncher.exe", "eosbootstrapper.exe") + return install + .walkTopDown() + .maxDepth(3) + .firstOrNull { + it.isFile && it.extension.equals("exe", ignoreCase = true) && + it.name.lowercase(Locale.US) !in stubs + } + } + + /** Where a store's game was installed, by the path its shortcut records. */ + private fun installed(shortcut: Shortcut): File { + val exe = shortcut.getExtra("launch_exe_path").replace('\\', '/') + return if (exe.startsWith("/")) File(exe) else File(shortcut.getExtra("game_install_path"), exe) + } + + private fun unsigned(appId: Int) = appId.toLong() and 0xFFFFFFFFL + + /** The config directory of every account the client knows, and of the store's account. */ + private fun userConfigs(steamRoot: File): List<File> { + val userdata = File(steamRoot, "userdata") + val accounts = userdata.list()?.filter { it.toLongOrNull()?.let { id -> id > 0 } == true }.orEmpty().toMutableSet() + val steamId = PrefManager.steamUserSteamId64 + if (steamId != 0L) accounts += (steamId and 0xFFFFFFFFL).toString() + return accounts.map { File(userdata, "$it/config") } + } + + /** Returns the app ids that were ours in this account before, for their tool mapping to go. */ + private fun write(config: File, games: List<Game>): Set<Int> { + val file = File(config, "shortcuts.vdf") + val ownedFile = File(config, OWNED) + val owned = if (ownedFile.isFile) ownedFile.readLines().mapNotNull { it.trim().toLongOrNull()?.toInt() }.toSet() else emptySet() + if (games.isEmpty() && owned.isEmpty()) return owned + val entries = if (file.isFile && file.length() > 0) parse(file.readBytes()) else ArrayList() + val before = serialize(entries) + val wanted = games.associateBy { it.appId } + entries.removeAll { entry -> appId(entry).let { it in owned && it !in wanted } } + for (game in games) { + val fields = + entries.firstOrNull { appId(it) == game.appId }?.let { fieldsOf(it) } + ?: newEntry(game.appId).also { entries += Node("", it) } + set(fields, "AppName", game.name) + set(fields, "Exe", "\"${game.exe.path}\"") + set(fields, "StartDir", "\"${game.exe.parent}\"") + set(fields, "icon", game.icon) + set(fields, "LaunchOptions", launchOptions(fields, game)) + } + val after = serialize(entries) + if (!after.contentEquals(before)) LinuxSteamVdf.replace(file) { it.writeBytes(after) } + if (wanted.keys != owned) { + LinuxSteamVdf.replace(ownedFile) { it.writeText(wanted.keys.joinToString("\n") { id -> unsigned(id).toString() }) } + } + return owned + } + + /** + * The entry's launch options with the Epic game's name in them, keeping whatever else the user + * put there. `%command%` must stay: without it the client passes the options to the game as + * arguments instead of running the line. + */ + private fun launchOptions(fields: MutableList<Node>, game: Game): String { + val current = fields.firstOrNull { it.name.equals("LaunchOptions", ignoreCase = true) }?.value as? String ?: "" + // A game of another store keeps the line it has, down to the spacing the user typed. + if (game.epicAppName.isEmpty() && !current.contains("$EPIC_GAME=")) return current + val kept = current.split(" ").filter { it.isNotEmpty() && !it.startsWith("$EPIC_GAME=") } + if (game.epicAppName.isEmpty()) return kept.joinToString(" ") + val named = listOf("$EPIC_GAME=${game.epicAppName}") + kept + return if (named.any { it.contains("%command%") }) named.joinToString(" ") else (named + "%command%").joinToString(" ") + } + + /** + * The Steam games the store's account owns, which are set to the ARM64 tool before the client + * installs any of them. Valve names a Proton of its own for thousands of titles, and that + * beats the client's default: installing one has the client fetch an x86-64 Proton, its + * runtime and FEX - gigabytes that cannot run here - and the game then fails to start. + */ + private fun ownedSteamGames(context: Context): List<String> = + try { + runBlocking(Dispatchers.IO) { PluviaDatabase.getInstance(context).steamAppDao().getAllAsList() } + .filter { it.packageId != SteamService.INVALID_PKG_ID && (it.type == AppType.game || it.type == AppType.demo) } + .map { it.id.toString() } + } catch (e: RuntimeException) { + Log.w(TAG, "the Steam library is unavailable: ${e.javaClass.simpleName}") + emptyList() + } + + private fun mapTool(config: File, apps: List<String>, retired: Set<Int>) { + // The client writes this file on its first start; until then there is nothing to add to. + if (!config.isFile) return + val tree = LinuxSteamVdf.load(config, "InstallConfigStore") + val mapping = LinuxSteamVdf.section(tree, STEAM_KEY + "CompatToolMapping") + var changed = mapping.children.removeAll { entry -> retired.any { unsigned(it).toString() == entry.name } } + for (key in apps) { + if (mapping[key] !== KeyValue.INVALID) continue + mapping.children += + KeyValue(key).apply { + isSection = true + children += KeyValue("name", TOOL) + children += KeyValue("config", "") + children += KeyValue("priority", "250") + } + changed = true + } + if (changed) LinuxSteamVdf.save(config, tree) + } + + @Suppress("UNCHECKED_CAST") + private fun fieldsOf(entry: Node) = entry.value as MutableList<Node> + + private fun appId(entry: Node): Int? = + fieldsOf(entry).firstOrNull { it.name.equals("appid", ignoreCase = true) }?.value as? Int + + private fun set(fields: MutableList<Node>, name: String, value: String) { + val field = fields.firstOrNull { it.name.equals(name, ignoreCase = true) } + if (field != null) field.value = value else fields += Node(name, value) + } + + /** The fields the client itself writes for a non-Steam game, in its order. */ + private fun newEntry(appId: Int): MutableList<Node> = + mutableListOf( + Node("appid", appId), + Node("AppName", ""), + Node("Exe", ""), + Node("StartDir", ""), + Node("icon", ""), + Node("ShortcutPath", ""), + Node("LaunchOptions", ""), + Node("IsHidden", 0), + Node("AllowDesktopConfig", 1), + Node("AllowOverlay", 1), + Node("OpenVR", 0), + Node("Devkit", 0), + Node("DevkitGameID", ""), + Node("DevkitOverrideAppID", 0), + Node("LastPlayTime", 0), + Node("FlatpakAppID", ""), + Node("tags", ArrayList<Node>()), + ) + + /** The entries under the file's one `shortcuts` section. Anything else is not ours to rewrite. */ + private fun parse(bytes: ByteArray): MutableList<Node> { + val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN) + if (buffer.get().toInt() != TYPE_SECTION || !readString(buffer).equals("shortcuts", ignoreCase = true)) { + throw IOException("not a shortcuts file") + } + val entries = readSection(buffer) + if (entries.any { it.value !is MutableList<*> }) throw IOException("unexpected entry") + return entries + } + + private fun readSection(buffer: ByteBuffer): MutableList<Node> { + val nodes = ArrayList<Node>() + while (true) { + val type = buffer.get().toInt() + if (type == TYPE_END) return nodes + val name = readString(buffer) + nodes += + when (type) { + TYPE_SECTION -> Node(name, readSection(buffer)) + TYPE_STRING -> Node(name, readString(buffer)) + TYPE_INT -> Node(name, buffer.int) + else -> throw IOException("unknown field type $type") + } + } + } + + private fun readString(buffer: ByteBuffer): String { + val start = buffer.position() + while (buffer.get().toInt() != 0) Unit + return String(buffer.array(), start, buffer.position() - 1 - start, Charsets.UTF_8) + } + + private fun serialize(entries: List<Node>): ByteArray { + val out = ByteArrayOutputStream() + out.write(TYPE_SECTION) + writeString(out, "shortcuts") + // The client numbers the entries from zero and reads them back by that number. + writeSection(out, entries.mapIndexed { index, entry -> Node(index.toString(), entry.value) }) + out.write(TYPE_END) + return out.toByteArray() + } + + private fun writeSection(out: ByteArrayOutputStream, nodes: List<Node>) { + for (node in nodes) { + when (val value = node.value) { + is String -> { + out.write(TYPE_STRING) + writeString(out, node.name) + writeString(out, value) + } + is Int -> { + out.write(TYPE_INT) + writeString(out, node.name) + out.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array()) + } + else -> { + out.write(TYPE_SECTION) + writeString(out, node.name) + @Suppress("UNCHECKED_CAST") + writeSection(out, value as List<Node>) + } + } + } + out.write(TYPE_END) + } + + private fun writeString(out: ByteArrayOutputStream, value: String) { + out.write(value.toByteArray(Charsets.UTF_8)) + out.write(0) + } +} diff --git a/app/src/main/runtime/linux/LinuxSteamVdf.kt b/app/src/main/runtime/linux/LinuxSteamVdf.kt new file mode 100644 index 000000000..0ecb25440 --- /dev/null +++ b/app/src/main/runtime/linux/LinuxSteamVdf.kt @@ -0,0 +1,44 @@ +package com.winlator.cmod.runtime.linux + +import com.winlator.cmod.feature.stores.steam.utils.KeyValue +import java.io.File +import java.io.IOException + +/** The Steam client's text VDF files, read and replaced whole. Worker thread, no client running. */ +internal object LinuxSteamVdf { + val STEAM_KEY = listOf("Software", "Valve", "Steam") + + fun steamRoot(rootfs: File) = File(rootfs, "root/.local/share/Steam") + + fun load(file: File, rootName: String): KeyValue { + val parsed = if (file.isFile) KeyValue.loadFromString(file.readText()) else null + // A file that is there but does not parse is the client's to repair, not ours to replace. + if (parsed == null && file.isFile && file.length() > 0) throw IOException("unreadable ${file.name}") + return parsed ?: KeyValue(rootName).apply { isSection = true } + } + + fun section(root: KeyValue, path: List<String>): KeyValue { + var node = root + for (name in path) { + var next = node[name] + if (next === KeyValue.INVALID) { + next = KeyValue(name).apply { isSection = true } + node.children += next + } + node = next + } + return node + } + + fun save(file: File, tree: KeyValue) = replace(file) { it.writeText(tree.serialize()) } + + fun replace(file: File, write: (File) -> Unit) { + file.parentFile?.mkdirs() + val staged = File(file.path + ".staged") + write(staged) + if (!staged.renameTo(file)) { + staged.delete() + throw IOException("could not replace ${file.name}") + } + } +} diff --git a/app/src/main/runtime/system/LinuxTaskList.java b/app/src/main/runtime/system/LinuxTaskList.java new file mode 100644 index 000000000..9ae64b1e6 --- /dev/null +++ b/app/src/main/runtime/system/LinuxTaskList.java @@ -0,0 +1,153 @@ +package com.winlator.cmod.runtime.system; + +import android.os.Process; +import com.winlator.cmod.shared.util.StringUtils; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Locale; + +/** + * What is running in a GameScope session, read from {@code /proc}. + * + * Wine sessions get their process list from the Wine side over WinHandler, which a Linux session + * has none of. Everything a GameScope session starts is a child of this app and carries its uid, + * so the kernel's own view is the list: the session's processes are this uid's, minus this app's. + */ +public final class LinuxTaskList { + private LinuxTaskList() {} + + public static final class Task { + public final int pid; + public final String name; + public final String memory; + public final int affinityMask; + + Task(int pid, String name, String memory, int affinityMask) { + this.pid = pid; + this.name = name; + this.memory = memory; + this.affinityMask = affinityMask; + } + } + + /** Worker thread: this reads a few files per process. */ + public static ArrayList<Task> list() { + ArrayList<Task> tasks = new ArrayList<>(); + File proc = new File("/proc"); + String[] entries = proc.list(); + if (entries == null) return tasks; + + int self = Process.myPid(); + int uid = Process.myUid(); + for (String entry : entries) { + int pid = parsePid(entry); + if (pid < 0 || pid == self) continue; + + Status status = readStatus(new File(proc, entry)); + if (status == null || status.uid != uid) continue; + + String[] argv = readCmdline(new File(proc, entry)); + tasks.add( + new Task( + pid, + displayName(argv, status.name), + StringUtils.formatBytes(status.residentBytes), + status.affinityMask)); + } + return tasks; + } + + private static int parsePid(String entry) { + for (int i = 0; i < entry.length(); i++) { + if (entry.charAt(i) < '0' || entry.charAt(i) > '9') return -1; + } + try { + return entry.isEmpty() ? -1 : Integer.parseInt(entry); + } catch (NumberFormatException e) { + return -1; + } + } + + private static final class Status { + int uid = -1; + String name = ""; + long residentBytes; + int affinityMask; + } + + private static Status readStatus(File dir) { + Status status = new Status(); + try (FileInputStream in = new FileInputStream(new File(dir, "status")); + BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("Name:")) { + status.name = line.substring(5).trim(); + } else if (line.startsWith("Uid:")) { + String[] fields = line.trim().split("\\s+"); + if (fields.length > 1) status.uid = Integer.parseInt(fields[1]); + } else if (line.startsWith("VmRSS:")) { + String[] fields = line.trim().split("\\s+"); + if (fields.length > 1) status.residentBytes = Long.parseLong(fields[1]) * 1024L; + } else if (line.startsWith("Cpus_allowed:")) { + status.affinityMask = parseAffinity(line.substring(13).trim()); + } + } + } catch (IOException | RuntimeException e) { + // The process exited while it was being read, or belongs to someone else. + return null; + } + return status.uid < 0 ? null : status; + } + + /** The kernel prints the mask in comma separated 32 bit groups, least significant last. */ + private static int parseAffinity(String value) { + String last = value; + int comma = value.lastIndexOf(','); + if (comma >= 0) last = value.substring(comma + 1); + try { + return (int) Long.parseLong(last, 16); + } catch (NumberFormatException e) { + return 0; + } + } + + private static String[] readCmdline(File dir) { + byte[] buffer = new byte[4096]; + int length = 0; + try (FileInputStream in = new FileInputStream(new File(dir, "cmdline"))) { + int read; + while (length < buffer.length && (read = in.read(buffer, length, buffer.length - length)) > 0) { + length += read; + } + } catch (IOException e) { + return new String[0]; + } + if (length <= 0) return new String[0]; + return new String(buffer, 0, length).split("\0"); + } + + /** + * The name worth showing. A Windows program is named by its executable wherever one appears in + * the command line, since everything ahead of it is the emulator and Proton getting there. FEX + * itself says nothing about what is running, so it is named by the program it was handed. + */ + private static String displayName(String[] argv, String comm) { + for (int i = argv.length - 1; i >= 0; i--) { + if (argv[i].toLowerCase(Locale.ROOT).endsWith(".exe")) return baseName(argv[i]); + } + if (argv.length == 0 || argv[0].isEmpty()) return comm; + String leader = baseName(argv[0]); + if (leader.equals("FEX") && argv.length > 1) return baseName(argv[1]); + return leader.isEmpty() ? comm : leader; + } + + private static String baseName(String path) { + int cut = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); + return cut >= 0 ? path.substring(cut + 1) : path; + } +} diff --git a/app/src/main/runtime/system/LogManager.kt b/app/src/main/runtime/system/LogManager.kt index fce3780cc..c9411dfd6 100644 --- a/app/src/main/runtime/system/LogManager.kt +++ b/app/src/main/runtime/system/LogManager.kt @@ -13,6 +13,7 @@ import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.preference.PreferenceManager import com.winlator.cmod.app.config.SettingsConfig +import com.winlator.cmod.runtime.linux.LinuxRuntime import com.winlator.cmod.shared.io.FileUtils import timber.log.Timber import java.io.Closeable @@ -27,6 +28,10 @@ import java.time.format.DateTimeFormatter object LogManager { private const val TAG = "LogManager" + private const val SESSION_LOGS_DIR = "wayland-logs" + private const val STEAM_CLIENT_LOGS = "root/.local/share/Steam/logs" + private const val KEPT_PER_TYPE = 10 + private val STAMP = Regex("[_-]\\d{4}-?\\d{2}-?\\d{2}[_-]\\d{2}-?\\d{2}-?\\d{2}") private const val APP_LOG_FILE = "app_filtered-logs.log" private const val EXIT_REASONS_FILE = "exit_reasons.log" private const val CRASH_FILE = "crash.log" @@ -307,6 +312,8 @@ object LogManager { stopAppLogging() val logsDir = getLogsDir(context) logsDir.listFiles()?.filter { it.name.endsWith(".log") }?.forEach { it.delete() } + val appContext = context.applicationContext + Thread({ pruneLogs(appContext) }, "log-prune").start() startAppLogging(context, reset = true) } @@ -547,24 +554,86 @@ object LogManager { } } + /** Where the compositor and the Linux session (gamescope, the Steam client's output) log. */ + @JvmStatic + fun getSessionLogsDir(context: Context): File { + val dir = File(context.getExternalFilesDir(null) ?: context.filesDir, SESSION_LOGS_DIR) + if (!dir.exists()) dir.mkdirs() + return dir + } + + /** The Linux Steam client's own logs, which it keeps inside the runtime. */ + private fun steamClientLogsDir(context: Context): File = File(LinuxRuntime.rootDir(context), STEAM_CLIENT_LOGS) + @JvmStatic fun getShareableLogFiles(context: Context): Array<File> { val filter: (File) -> Boolean = { - it.isFile && (it.name.endsWith(".log") || it.name.endsWith(".txt") || it.name.endsWith(".csv")) + it.isFile && (it.name.endsWith(".log") || it.name.endsWith(".txt") || it.name.endsWith(".csv") || it.name.endsWith(".log.1")) } - // Aggregate files from both the internal and external directories - val external = getLogsDir(context, true).listFiles()?.filter(filter) ?: emptyList() - val private = getLogsDir(context, false).listFiles()?.filter(filter) ?: emptyList() - return (external + private).toTypedArray() + return listOf( + getLogsDir(context, true), + getLogsDir(context, false), + getSessionLogsDir(context), + steamClientLogsDir(context), + ).flatMap { it.listFiles()?.filter(filter) ?: emptyList() }.toTypedArray() } + /** + * A token as Steam's own logs print one: three base64url pieces, the first starting `eyJ`. + * Its connection log records the account's session token in full on every logon. + */ + private val SESSION_TOKEN = Regex("eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}") + + /** + * Writes [file] to [out] as it should leave the device. A log is shared to be read by someone + * else, and the Steam client's own logs carry a token that signs its account in, so those are + * copied a line at a time with any token taken out. [out] is left open for the caller. + */ + @JvmStatic + fun copyShareable(context: Context, file: File, out: java.io.OutputStream) { + if (file.parentFile != steamClientLogsDir(context)) { + file.inputStream().use { it.copyTo(out) } + return + } + val writer = java.io.OutputStreamWriter(out, Charsets.UTF_8) + file.bufferedReader(Charsets.UTF_8).use { reader -> + reader.lineSequence().forEach { line -> + writer.write(SESSION_TOKEN.replace(line, "[token removed by WinNative]")) + writer.write("\n") + } + } + writer.flush() + } + + /** + * The name a log goes by in an archive: the session's and the Steam client's logs in folders + * of their own, so that none of them can take the name of another. + */ + @JvmStatic + fun archiveName(context: Context, file: File): String = + when (file.parentFile) { + getSessionLogsDir(context) -> "gamescope/${file.name}" + steamClientLogsDir(context) -> "steam-client/${file.name}" + else -> file.name + } + /** Total bytes of all shareable log files. */ @JvmStatic - fun getShareableLogsSize(context: Context): Long { - // Sum sizes from both internal and external directories - val internalSize = getLogsDir(context, false).walk().filter { it.isFile }.sumOf { it.length() } - val externalSize = getLogsDir(context, true).walk().filter { it.isFile }.sumOf { it.length() } - return internalSize + externalSize + fun getShareableLogsSize(context: Context): Long = getShareableLogFiles(context).sumOf { it.length() } + + /** + * Worker thread, as a session starts. A type of log is its name without the time it was + * started, and the session is about to add one of each, so one fewer than [KEPT_PER_TYPE] of + * the newest stay. The Steam client's logs are its own to rotate. + */ + private fun pruneLogs(context: Context) { + for (dir in listOf(getLogsDir(context, true), getLogsDir(context, false), getSessionLogsDir(context))) { + dir.listFiles() + ?.filter { it.isFile } + ?.groupBy { it.name.replace(STAMP, "") } + ?.values + ?.forEach { type -> type.sortedByDescending { it.lastModified() }.drop(KEPT_PER_TYPE - 1).forEach { it.delete() } } + } } /** Deletes all shareable log files; returns the count removed. */ diff --git a/app/src/main/runtime/system/ProcessHelper.java b/app/src/main/runtime/system/ProcessHelper.java index 6d7de0ee0..c787ed31a 100644 --- a/app/src/main/runtime/system/ProcessHelper.java +++ b/app/src/main/runtime/system/ProcessHelper.java @@ -48,6 +48,18 @@ public abstract class ProcessHelper { "gameoverlayui", ".exe" }; + /* Matched against the process name when the filters above cannot see a process at all. A Chromium + * helper (Steam's web helper is one) clears its own argv, so /proc/<pid>/cmdline is empty and every + * filter above - all of them Windows executable names - misses it. Two such processes were found + * orphaned to init with three threads spinning at 100%, having survived every session teardown for + * the better part of seven hours. Only ever acted on for a process of this app's own uid. */ + private static final String[] SESSION_COMM_FILTERS = { + "crbrowsermain", + "crrenderermain", + "crgpumain", + "crutilitymain", + "crashpad_handle" + }; private static final byte SIGCONT = 18; private static final byte SIGSTOP = 19; private static final byte SIGTERM = 15; @@ -240,6 +252,11 @@ private static boolean isCoreProcess(String normalizedData) { for (String filter : CORE_PROCESS_FILTERS) { if (normalizedData.contains(filter)) return true; } + // A Chromium helper is the web helper above under the name it reports; keeping it out of + // GAME_ONLY's SIGSTOP is the point of listing the web helper in the first place. + for (String filter : SESSION_COMM_FILTERS) { + if (normalizedData.contains(filter)) return true; + } return false; } @@ -715,7 +732,7 @@ public boolean accept(File proc, String filename) { String statData = readProcStat(proc, pid); String cmdlineData = readProcCmdline(proc, pid); String normalized = (statData + " " + cmdlineData).toLowerCase(Locale.ROOT); - if (isSessionProcess(normalized) && !filteredPids.contains(pid)) filteredPids.add(pid); + if (isSessionProcess(proc, pid, normalized) && !filteredPids.contains(pid)) filteredPids.add(pid); } return filteredPids; } @@ -736,7 +753,7 @@ public boolean accept(File proc, String filename) { String statData = readProcStat(proc, pid); String cmdlineData = readProcCmdline(proc, pid); String normalized = (statData + " " + cmdlineData).toLowerCase(Locale.ROOT); - if (!isSessionProcess(normalized)) continue; + if (!isSessionProcess(proc, pid, normalized)) continue; String name = getStatProcessName(statData); String command = cmdlineData.trim(); @@ -750,10 +767,35 @@ public boolean accept(File proc, String filename) { return details; } - private static boolean isSessionProcess(String normalizedProcessData) { + private static boolean isSessionProcess(File proc, String pid, String normalizedProcessData) { for (String filter : SESSION_PROCESS_FILTERS) { if (normalizedProcessData.contains(filter)) return true; } + for (String filter : SESSION_COMM_FILTERS) { + if (normalizedProcessData.contains(filter)) return isSessionHelper(proc, pid); + } + return false; + } + + /** + * Whether {@code pid} is a helper of this app's own session: this app's uid, and not this process. + * A process name matched on its own says nothing about who owns it, and a match the session cannot + * signal would leave every teardown waiting out its timeout on a process that never goes away. + * WebView's renderers carry Chromium's names too, but they run under an isolated uid of their own. + */ + private static boolean isSessionHelper(File proc, String pid) { + if (pid.equals(Integer.toString(Process.myPid()))) return false; + try (FileInputStream fr = new FileInputStream(proc + "/" + pid + "/status"); + BufferedReader br = new BufferedReader(new InputStreamReader(fr))) { + String line; + while ((line = br.readLine()) != null) { + if (!line.startsWith("Uid:")) continue; + String[] fields = line.trim().split("\\s+"); + return fields.length > 1 && Integer.parseInt(fields[1]) == Process.myUid(); + } + } catch (IOException | RuntimeException e) { + // Another app's process, or one that exited while it was being read. + } return false; } diff --git a/app/src/main/runtime/system/SessionKeepAliveService.java b/app/src/main/runtime/system/SessionKeepAliveService.java index feec28510..9bc64f1ce 100644 --- a/app/src/main/runtime/system/SessionKeepAliveService.java +++ b/app/src/main/runtime/system/SessionKeepAliveService.java @@ -72,6 +72,8 @@ public class SessionKeepAliveService extends Service { private static volatile XEnvironment activeEnvironment; private static volatile XServer activeXServer; + /* The active environment runs gamescope in the Linux runtime rather than Wine. */ + private static volatile boolean linuxSessionActive; private static volatile boolean isContainerPaused = false; @@ -186,6 +188,7 @@ private static void teardownEnvironmentAsync() { final XEnvironment env = activeEnvironment; activeEnvironment = null; activeXServer = null; + linuxSessionActive = false; if (env == null) return; new Thread(() -> { try { @@ -215,6 +218,15 @@ public static void setActiveXServer(XServer xServer) { public static void clearActiveSession() { activeEnvironment = null; activeXServer = null; + linuxSessionActive = false; + } + + public static boolean isLinuxSessionActive() { + return linuxSessionActive; + } + + public static void setLinuxSessionActive(boolean active) { + linuxSessionActive = active; } public static void setPipMode(boolean inPip) { @@ -396,7 +408,11 @@ public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_SCREEN_OFF.equals(action)) { isScreenLocked = true; - acquireWakeLock(); + // Only hold the CPU for a session that is actually running, which is the one + // thing this lock exists for. The service outlives the session whenever a + // download or Steam chat keeps it alive, and the lock is released only when + // the user unlocks - so without this an idle app holds the CPU awake all night. + if (sessionActive.get()) acquireWakeLock(); LogManager.log(TAG, "Screen turned off / device locked"); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { isScreenLocked = false; diff --git a/app/src/main/runtime/wine/WineRegistryEditor.java b/app/src/main/runtime/wine/WineRegistryEditor.java index 2a9e7119c..0ede1f1ab 100644 --- a/app/src/main/runtime/wine/WineRegistryEditor.java +++ b/app/src/main/runtime/wine/WineRegistryEditor.java @@ -54,6 +54,17 @@ public int length() { } } + /** Runs an edit that touches the registry file directly, under the same lock the editor uses. */ + public static void withFileLock(File file, Runnable edit) { + ReentrantLock lock = lockFor(file); + lock.lock(); + try { + edit.run(); + } finally { + lock.unlock(); + } + } + public WineRegistryEditor(File file) { this.file = file; this.fileLock = lockFor(file); diff --git a/app/src/main/shared/android/DirectoryPickerDialog.kt b/app/src/main/shared/android/DirectoryPickerDialog.kt index bd69daee7..62ab8f70c 100644 --- a/app/src/main/shared/android/DirectoryPickerDialog.kt +++ b/app/src/main/shared/android/DirectoryPickerDialog.kt @@ -298,6 +298,11 @@ object DirectoryPickerDialog { if (!ensureAllFilesAccess(activity)) return val roots = buildRootDirectories(activity) + // Offered first, because it is the only root a game library can be installed to intact. + val selectableRoots = + StoragePathUtils.appPrivateGamesRoot(activity)?.let { appRoot -> + listOf(ManagedRoot(activity.getString(R.string.common_ui_app_storage), appRoot.path)) + extraRoots + } ?: extraRoots val initialDir = resolveInitialDirectory(initialPath, roots) val dialog = @@ -365,7 +370,7 @@ object DirectoryPickerDialog { mode = mode, allowedExtensions = allowedExtensions, managedRoots = managedRoots, - extraRoots = extraRoots, + extraRoots = selectableRoots, containers = containers, onRunFile = onRunFile, onCreateShortcut = onCreateShortcut, diff --git a/app/src/main/shared/android/StoragePathUtils.kt b/app/src/main/shared/android/StoragePathUtils.kt index dffba981c..87e94285a 100644 --- a/app/src/main/shared/android/StoragePathUtils.kt +++ b/app/src/main/shared/android/StoragePathUtils.kt @@ -125,6 +125,23 @@ object StoragePathUtils { return roots.values.toList() } + /** + * The app's own games directory, offered next to the device's volumes. + * + * Shared storage is not a filesystem here: it is served over FUSE by the media provider, which + * refuses a symlink, drops the mode a chmod asks for, and is mounted noexec. A game library + * needs all three - the client sets the executable bit after an install, Proton's prefixes and + * the Steam runtime are built out of symlinks, and a native title is an ELF that has to run. + * The app's own directory is on the same partition as shared storage, so a library kept here + * costs no extra space and behaves like a real disk. + */ + @JvmStatic + fun appPrivateGamesRoot(context: Context): File? { + val root = File(context.dataDir, "Games") + if (!root.isDirectory && !root.mkdirs()) return null + return root.takeIf(::canBrowse) + } + @JvmStatic fun buildBrowsableStorageRoots(context: Context): List<File> { val roots = linkedMapOf<String, File>() diff --git a/app/src/main/shared/ui/dialog/ContentDialog.java b/app/src/main/shared/ui/dialog/ContentDialog.java index 1969caef2..e90f4f283 100644 --- a/app/src/main/shared/ui/dialog/ContentDialog.java +++ b/app/src/main/shared/ui/dialog/ContentDialog.java @@ -202,6 +202,7 @@ public static void alert(Context context, int msgResId, Runnable callback) { dialog.setMessage(msgResId); dialog.setOnConfirmCallback(callback); dialog.findViewById(R.id.BTCancel).setVisibility(View.GONE); + if (callback != null) dialog.setOnCancelListener(d -> callback.run()); dialog.show(); } @@ -211,6 +212,7 @@ public static void alert(Context context, String msg, Runnable callback) { dialog.setMessage(msg); dialog.setOnConfirmCallback(callback); dialog.findViewById(R.id.BTCancel).setVisibility(View.GONE); + if (callback != null) dialog.setOnCancelListener(d -> callback.run()); dialog.show(); } diff --git a/app/src/main/shared/ui/dialog/PopupDialog.kt b/app/src/main/shared/ui/dialog/PopupDialog.kt index 298d93393..170722c1a 100644 --- a/app/src/main/shared/ui/dialog/PopupDialog.kt +++ b/app/src/main/shared/ui/dialog/PopupDialog.kt @@ -125,7 +125,7 @@ fun PopupDialog( footer() progress != null -> - ProgressBarFooter( + PopupProgressBar( progress = progress, progressLabel = progressLabel, accentColor = accentColor, @@ -155,13 +155,14 @@ fun PopupDialog( } } +/** The popup's progress bar: [progress] in 0f..1f shows a percentage, `Float.NaN` or negative an indeterminate bar. */ @Composable -private fun ProgressBarFooter( +fun PopupProgressBar( progress: Float, progressLabel: String?, - accentColor: Color, - trackColor: Color, - textSecondaryColor: Color, + accentColor: Color = DefaultAccent, + trackColor: Color = DefaultTrack, + textSecondaryColor: Color = DefaultTextSecondary, ) { val barHeight = 5.dp val barShape = RoundedCornerShape(3.dp) @@ -272,11 +273,12 @@ fun PopupTextAction( label: String, textColor: Color, onClick: () -> Unit, + isEntry: Boolean = false, ) { val nav = LocalPaneNav.current val clickModifier = if (nav != null) { - Modifier.paneNavItem(cornerRadius = 8.dp, onActivate = onClick, tapToSelect = true) + Modifier.paneNavItem(cornerRadius = 8.dp, onActivate = onClick, tapToSelect = true, isEntry = isEntry) } else { Modifier.clickable(onClick = onClick) } diff --git a/app/src/main/shared/ui/dialog/WinNativeComposeDialogs.kt b/app/src/main/shared/ui/dialog/WinNativeComposeDialogs.kt index 83c8d17a1..83e978f5f 100644 --- a/app/src/main/shared/ui/dialog/WinNativeComposeDialogs.kt +++ b/app/src/main/shared/ui/dialog/WinNativeComposeDialogs.kt @@ -118,6 +118,10 @@ object WinNativeComposeDialogs { } }, ) + // An alert has nothing to cancel, so back or a tap outside means what OK means. Without + // this the caller's callback is skipped, and one that was closing a screen behind the + // alert leaves it open. dismiss() does not come through here, so it runs once either way. + dialog.setOnCancelListener { onConfirm?.run() } dialog.show() return true } diff --git a/app/src/main/shared/ui/widget/EnvVarsView.java b/app/src/main/shared/ui/widget/EnvVarsView.java index 7a097b198..ca5d0b1d7 100644 --- a/app/src/main/shared/ui/widget/EnvVarsView.java +++ b/app/src/main/shared/ui/widget/EnvVarsView.java @@ -24,6 +24,37 @@ import java.util.List; public class EnvVarsView extends FrameLayout { + /** + * Variables a Linux session has no reader for: the Android Vulkan wrapper and the ALSA server are + * not part of it, Proton decides WINEESYNC for itself, /dev/ntsync does not exist on Android, and + * the rest are switches of WinNative's own Wine patches, which Valve's Proton does not carry. + */ + private static final java.util.Set<String> GAMESCOPE_UNUSED = + new java.util.HashSet<>( + java.util.Arrays.asList( + "WINEESYNC", + "WINENTSYNC", + "WINE_FAST_YIELD", + "WINE_DO_NOT_CREATE_DXGI_DEVICE_MANAGER", + "WINE_NEW_MEDIASOURCE", + "WRAPPER_MAX_IMAGE_COUNT", + "WRAPPER_DMAHEAP_CACHED", + "ALSA_LATENCY_MS", + "ALSA_VOLUME", + "ALSA_BASS_BOOST", + "ALSA_PERFORMANCE_MODE")); + + public static boolean appliesToGamescope(String name) { + return !GAMESCOPE_UNUSED.contains(name); + } + + /** {@code envVars} as a Linux session reads it: what it has no reader for is dropped. */ + public static String forGamescope(String envVars) { + EnvVars kept = new EnvVars(envVars); + for (String name : GAMESCOPE_UNUSED) kept.remove(name); + return kept.toString(); + } + public static final String[][] knownEnvVars = { {"ZINK_DESCRIPTORS", "SELECT", "auto", "lazy", "cached", "notemplates"}, { @@ -39,6 +70,13 @@ public class EnvVarsView extends FrameLayout { }, {"MESA_SHADER_CACHE_DISABLE", "CHECKBOX", "false", "true"}, {"mesa_glthread", "CHECKBOX", "false", "true"}, + // Tells a game and Proton they are on a Steam Deck: gamepad-first menus and Deck presets. + {"SteamDeck", "CHECKBOX", "0", "1"}, + // xalia gives Windows programs gamepad navigation. It is an x86 program, so under FEX it + // cannot load the session's aarch64 preload and its calls reach the vendor's seccomp filter + // as they are; where that answers ENOSYS it does so in the hundreds and the session goes with + // it. Proton's own gate, so 0 takes the helper away without touching anything else. + {"PROTON_USE_XALIA", "CHECKBOX", "0", "1"}, {"WINEESYNC", "CHECKBOX", "0", "1"}, {"WINENTSYNC", "CHECKBOX", "0", "1"}, {"FD_DEV_FEATURES", "SELECT_MULTIPLE", "enable_tp_ubwc_flag_hint=1", "storage_8bit=1"}, diff --git a/docs/DRIVERS.md b/docs/DRIVERS.md new file mode 100644 index 000000000..243af3858 --- /dev/null +++ b/docs/DRIVERS.md @@ -0,0 +1,71 @@ +# Android and Linux driver review + +Reviewed against WinNative `feature/wayland-gamescope` at `fd74ab12` and WinNative-Emu/Drivers main on 2026-09-21. Linux implementation and release recipes live on [Drivers feature/linux-drivers](https://github.com/WinNative-Emu/Drivers/tree/feature/linux-drivers). + +## What runs where + +| Consumer | Driver ABI | Installation and selection | +| --- | --- | --- | +| Android compositor, Android emulators and the existing Android graphics paths | ARM64 Android bionic, Adrenotools | `files/contents/adrenotools/<name>`; existing container/emulator graphics selectors | +| GameScope, Linux applications and games inside the Linux runtime | ARM64 Linux glibc, KGSL plus Wayland/X11 WSI | `files/contents/linux-drivers/<name>`; Settings โ†’ Drivers โ†’ Linux โ†’ Select | + +GameScope uses both sides: Linux renders through its glibc driver; the host compositor remains an Android process with its own Android driver. An Android ZIP cannot replace the Linux ICD library. Both can be called `libvulkan_freedreno.so`; filename alone does not identify their ABI. + +The earlier Bannerlator/Banners-Turnip Wayland work is another bionic build with Wayland WSI. Its source notes remain in the prior workspace at `/home/max/Build/Claude/Wayland/proton-wine/android/wayland-deps/TURNIP.md`. Those eight bionic variants are not the glibc driver shipped by this GameScope branch. + +## Linux source and original build + +The tracked recipe is `tools/linuxfs/build-turnip.sh`. It downloads [Mesa 26.2.2](https://archive.mesa3d.org/mesa-26.2.2.tar.xz), verifies SHA-256 `eeb29ca7e56cfaa8e8a79538dcf834e3b18e501c31bef5145e959ea437cc4216`, extracts it and applies `tools/linuxfs/turnip/*.patch`: + +* `kgsl-drm-node.patch`: reports KGSL device major/minor numbers for the compositor's dma-buf feedback and advertises `VK_EXT_physical_device_drm` for that device. +* `kgsl-no-calibrated-timestamps.patch`: removes the unimplemented KGSL GPU timestamp callback, guards its caller and disables calibrated timestamps/present timing when that callback is unavailable. + +The build uses `aarch64-linux-gnu-gcc/g++`, Meson and Ninja, against the Arch Linux ARM rootfs assembled by `tools/linuxfs/build-linuxfs.sh`. Key options are `vulkan-drivers=freedreno`, `freedreno-kmds=msm,kgsl`, `platforms=wayland,x11`, no Gallium/OpenGL/EGL/GLX/LLVM, release optimization. The result is stripped before packaging. The rootfs starts with Arch packages, including Mesa and GameScope; the custom KGSL Turnip replaces Arch's MSM-only Vulkan driver. This change updates Turnip, not the other Mesa libraries or GameScope itself. + +Original local inputs and intermediate outputs still exist: + +* `/home/max/Build/Claude/Wayland/turnip/mesa-26.2.2`, `cross-aarch64.ini`, `setup.log`, `build/`. +* `/home/max/Build/Claude/Wayland/linuxfs/work/turnip/mesa-26.2.2`, `cross.ini`, `build/`, `libvulkan_freedreno.so`. +* `/home/max/Build/Claude/Wayland/linuxfs/work/rootfs` supplies the original sysroot. Its Meson records confirm the options above. + +Commit `8b0cd7d2` introduced the app-bundled library at `app/src/main/assets/linuxfs/usr/lib/libvulkan_freedreno.so`. It identifies itself as Mesa 26.2.2, has build ID `b60cd6d31c6bf4f951a78e1279f30e9653a8316e`, and SHA-256 `7a390576bacb603ab7c17c0924a0b63e8f1307ed3413dd20cbbbdb70dc8f53f1`. The prior WinNative workspace holds identical bytes. The leftover `linuxfs/work/turnip/libvulkan_freedreno.so` differs (`0fa4747c91df906570e5ab0a23be67c6d17e0b03501e6c9cad06ce0312a794bb`), so it is not evidence of an exact reproducible rebuild of the bundled binary. + +`LinuxRuntime.syncSessionFiles` refreshes the bundled library in the rootfs at session startup. Previously a newer `linux-turnip.tar.zst` plus `linux-turnip.json` from [Components/Assets](https://github.com/WinNative-Emu/Components/releases/tag/Assets) could override it through `files/linux-driver`. Linux Client still supports that channel. Settings now exposes that legacy driver as a separate entry and preserves its previous version-based default until the user explicitly selects a driver. A selected custom package survives runtime refreshes; removing it selects bundled Mesa. + +## Android builds and distribution + +[WinNative-Emu/Drivers](https://github.com/WinNative-Emu/Drivers) uses `build_wn_turnip.sh` to clone [Mesa main](https://gitlab.freedesktop.org/mesa/mesa) once, then build both variants from that same commit. `build_turnip.sh` uses NDK r26d, API 34, the Android platform and KGSL. Each release contains `WN-Turnip-<version>-{b,p}_Axxx.zip`, with `meta.json` and `libvulkan_freedreno.so`. + +Common patches add the A810/A829 GMEM workaround and IDs, A825 support, A7xx quirks/UBWC hint, gralloc fixes, the IMapper5 backend, and Android UBWC swapchain usage. Balanced adjusts the GMEM bandwidth multiplier from 11 to 10. The old draw-call threshold anchor is absent in current Mesa and explicitly skipped. Performance currently uses the same bandwidth multiplier and adds `KGSL_CONTEXT_PWR_CONSTRAINT`, `KGSL_CMDBATCH_PWR_CONSTRAINT`, PWR_MAX at queue creation, and refresh every 1000 submissions. It requests maximum clocks; thermal limits and kernel policy still apply. + +`verify_patches.sh` checks logs, but some anchor drift is only warned about. The Linux recipe instead fails on unrecognized patch drift, excluding only the explicitly retired draw-call threshold. + +`.github/workflows/build.yml` runs Wednesdays at 12:00 UTC (`0 12 * * 3`). Scheduled runs publish; manual runs default to drafts; pushes/PRs build previews. Published Android assets can also be mirrored to `nicholasx417/WinNative-Components`, tag `WinNative-Turnip`, when `COMPONENTS_TOKEN` exists. Before this change Settings used that mirror by default. New Settings defaults include the direct Drivers repository plus the existing mirror for Android; Linux uses the direct Drivers repository. Existing saved Android repository choices remain intact and the restore-defaults action adds missing defaults. + +## New Linux build and package contract + +`Drivers/build_linux_turnip.sh` builds `WN-Linux-Turnip-<version>-{b,p}_Axxx.zip` from one fresh Mesa main commit. It reuses the WN GPU and power-tuning scripts, excludes Android gralloc/AHB patches, and applies the Linux KGSL fixes in `linux/patch_mesa.py`. Native CI builds on Ubuntu 24.04 ARM64; local cross builds accept `LINUX_SYSROOT`. The script preserves each applied source diff, patch log and Mesa SHA. The ZIP metadata records `platform=linux`, `architecture=aarch64`, `libc=glibc`, variant, Mesa source/SHA, recipe commit and library SHA-256. + +The Linux series starts at `WN Linux Turnip 0.1.0-b` and `WN Linux Turnip 0.1.0-p`, with `WN-Linux-Turnip-0.1.0-{b,p}_Axxx.zip` archives. It increments the patch version after published stable Linux releases independently of Android; preview builds and drafts do not advance it. Metadata keeps the Mesa version separately from the WN package version. + +The app unpacks into a unique temporary directory with size/path checks, inspects the ELF64/AArch64 DT_NEEDED entries (`libc.so` versus `libc.so.6`), verifies an optional library digest and platform metadata, then atomically installs into the correct store. An incorrect destination choice is corrected and shown in a localized message. Contradictory metadata, unsupported architecture, mixed libraries, unsafe paths and duplicates are rejected. Linux ICD paths are generated on the device. Android-only importer calls reject Linux packages. + +The release feed separates WN Linux assets by the reserved `WN-Linux-` prefix. Local imports are classified from their contents. New controls use the existing pane navigation registry, and all new strings are present in every existing locale. + +The Linux workflow uses the same Wednesday 12:00 UTC schedule and separate `linux-v...` tags; Linux releases do not replace Android as GitHub's latest release. GitHub only executes schedules from the default branch. Keeping the recipe exclusively on a feature branch therefore requires a default-branch scheduler or merging the workflow before automatic weekly execution is active. + +## Verification + +On 2026-09-21 both Linux variants built from Mesa `5ff61a7646d29b54c324af0a60aa3bfb5cdd24d1` (`26.3.0-devel`). Package checks verified distinct binaries, matching Mesa commits, ARM64/glibc ABI, Wayland/X11 entry points and PWR_MAX only in performance. Both libraries load with all dynamic symbols resolved against the original rootfs under QEMU. Native Ubuntu ARM64 CI also built both variants successfully. The public `linux-v0.1.0` release contains `WN-Linux-Turnip-0.1.0-b_Axxx.zip` and `WN-Linux-Turnip-0.1.0-p_Axxx.zip`. The native build/release workflow run `35666533240` passed, and the in-app Linux catalog discovered the release and downloaded and installed both variants successfully. All four AVD tests passed, and actual Settings imports were verified with both wrong destination choices; the app corrected each destination and displayed the driver in the correct list. + +`./gradlew :app:assemblePubgDebug :app:assemblePubgDebugAndroidTest` builds the app and instrumentation APK. AVD API 35 supports ARM64 translation and runs the actual PUBG APK. `DriverPackagesTest` tests real Android/Linux ZIP installation, selection, removal fallback, duplicates, wrong metadata and malformed/traversal packages. `DriversScreenTest` exercises the platform controls through `SettingsNavBridge`, the install destination dialog, and captures screenshots. + +For these device tests, place real ZIPs at the app-private `files/driver-tests/{android,linux-b,linux-p}.zip` using `adb shell run-as com.tencent.ig`. Run: + +```sh +adb shell am instrument -w \ + -e class com.winlator.cmod.runtime.content.DriverPackagesTest,com.winlator.cmod.feature.settings.DriversScreenTest \ + com.tencent.ig.test/com.winlator.cmod.StoreUiTestRunner +``` + +AVD validates the manager and UI, not Adreno/KGSL execution, frame pacing, power behavior or game compatibility. Those need a physical Adreno device. The existing bundled 26.2.2 library remains the fallback rather than silently replacing it with an untested-on-hardware driver. diff --git a/docs/GAMESCOPE.md b/docs/GAMESCOPE.md new file mode 100644 index 000000000..41f980ae1 --- /dev/null +++ b/docs/GAMESCOPE.md @@ -0,0 +1,432 @@ +# gamescope and Linux programs + +This is the working plan for running Linux programs - AppImages, shell launchers, native Linux +game builds, eventually the Linux Steam client - inside WinNative, with Valve's +[gamescope](https://github.com/ValveSoftware/gamescope) available as the session compositor for +them. It records what gamescope is, what WinNative already has, what is missing, and the order the +work goes in. Sources for the gamescope facts are the tree at master `4004d0b` (2026-09-16). + +## What gamescope is + +A micro-compositor. It is a Wayland compositor built on wlroots that hosts one Xwayland server, takes +the game's frames, composites them with Vulkan compute, and hands the result to whatever is under it. +The pieces, by source file: + +| Piece | File | What it does | +|---|---|---| +| Window manager and pacing | `src/steamcompmgr.cpp` (10.8k lines) | X11 focus and window rules, the frame limiter, resolution spoofing, and spawning the game as its child | +| Compositor server | `src/wlserver.cpp` | The wlroots `wl_display`, the Xwayland server(s), xdg-shell v3 for Wayland-native clients, keyboard/pointer/touch delivery | +| Renderer | `src/rendervulkan.cpp` | Vulkan device setup, dma-buf import of client buffers, the compositing compute shaders, FSR 1 / NIS / SGSR / integer upscaling, HDR | +| Backends | `src/Backends/{DRM,SDL,Wayland,Headless,OpenVR}Backend.cpp` | Where the composited frame goes: a KMS display (embedded mode), an SDL or Wayland window of a parent compositor (nested mode), nowhere (headless), a VR overlay | +| WSI layer | `layer/` | A Vulkan implicit layer the game loads; it feeds the limiter and HDR metadata back to gamescope | +| Protocols | `protocol/` | Thirteen private Wayland protocols between the layer/game and gamescope | + +Facts that shape the plan: + +- **Xwayland is mandatory.** `wlserver_init()` starts at least one `wlr_xwayland_server` and blocks + until it is up. Wayland-native clients work too, but only get `WAYLAND_DISPLAY` if gamescope is run + with `--expose-wayland`; by default it is blanked so a game cannot reach the parent compositor. +- **The child is exec'd by gamescope.** Everything after `--` becomes its child with `DISPLAY` set to + the nested Xwayland and `ENABLE_GAMESCOPE_WSI=1`; when gamescope exits it kills its children. +- **The nested `wayland` backend is the only fit here.** `drm` needs KMS, `sdl` needs a desktop, and + `headless` draws nothing. As a Wayland client it demands these globals from the parent and + refuses to start without all of them: `wl_compositor` v4, `wl_subcompositor`, `wl_shm`, + `xdg_wm_base`, `zwp_linux_dmabuf_v1` v3+, `wp_viewporter`, `wp_presentation`, + `zwp_relative_pointer_manager_v1`, `zwp_pointer_constraints_v1`. +- **Vulkan needs**, unconditionally: `VK_KHR_external_memory_fd`, `VK_EXT_external_memory_dma_buf`, + `VK_KHR_external_semaphore_fd`, `VK_EXT_robustness2`; for the nested swapchain + `VK_KHR_swapchain_mutable_format`, `VK_KHR_present_id`, `VK_KHR_present_wait`. Missing ones abort. +- **Build deps** (meson): wlroots 0.20 with Xwayland, libdrm (DRM backend only), libliftoff (DRM only), + SDL2 (SDL only), pixman, libudev, libinput, libseat (through wlroots `session=enabled`), libdecor, + luajit, xkbcommon, glslang at build time, and the X client libraries + (`x11 xdamage xcomposite xcursor xrender xext xfixes xxf86vm xtst xres xmu xi`). No x86-only code; + Arch Linux ARM ships an aarch64 package. +- License BSD-2-Clause. + +## What WinNative has today + +- **One bionic rootfs** (`files/imagefs`: Android libc, `usr/lib/libc.so`), Wine arm64ec on FEX or + x86_64 Wine on Box64. There is no glibc, no `/bin/sh`, and no way to run a Linux ELF. Every + library entry is `Exec=wine ...` or `Exec=retro:<system>`. +- **A proot source tree** (`app/src/main/cpp/proot`) that is not in `CMakeLists.txt`, and an + `XvfbInstaller` for a "sniper-arm64" rootfs that nothing calls. They are the remains of an + earlier Linux Steam attempt, not a working path. +- **The embedded Wayland compositor** (`app/src/main/cpp/waylandcomp`) already exports every global + gamescope's Wayland backend requires, plus `wp_color_manager_v1`, `zwp_text_input_v3` and + `xdg_toplevel_icon_v1`, and it already imports client dma-bufs and presents through Turnip. +- **A Wayland-capable Turnip** (native ICD, `libwayland-client`) in the container that exports + dma-bufs through `/dev/dri/renderD128`. That is the same extension set gamescope's renderer asks + for, so on this device the graphics side is not the blocker it would be on a phone that hides + `/dev/dri`. +- **The library** (this branch): the Add dialog accepts Windows executables, Linux executables + (`.AppImage .sh .run .bin .elf .x86_64 .x86 .aarch64 .arm64`) and console ROMs; every entry + carries a Game / App type (`library_type` extra) that the Games and Applications filters honour; + Linux entries are written with `runtime=linux` and `Exec=linux:native`, and launching one reports + that the Linux runtime is not installed yet. A `.bin` is treated as a console image first, as it + was before. Files with no extension are not offered yet. + +## What WayLandIE shows + +[WayLandIE](https://github.com/AstroCODEsky/WayLandIE) (2 commits, 2026-06) is the closest existing +run at this: gamescope and native arm64 Steam on Android. Its shape is the shape planned above, +which is worth knowing before building it. + +- **Root is only for the rootfs, not the display.** The display is an unprivileged app + (`io.waylandie.display`) presenting through `SurfaceControl` and `AHardwareBuffer`; it asks for no + special permissions. `chroot`/`lxc` backends need `su`; `proot` is the documented rootless + backend, with the caveat that dma-buf under proot is "experimental" and Steam/FEX/Proton "may be + poor" from the ptrace overhead. Root buys reliable device nodes and speed, not screen access. +- **The compositor is a small custom libwayland-server** (no wlroots) that only accepts dma-buf + client buffers and forwards the fds to the app. `libwnwayland.so` already is that, with more + protocols. +- **gamescope runs nested in it, as a Wayland client**, exactly as Phase 2 proposes: + + ``` + gamescope --backend wayland -f -e --expose-wayland --xwayland-count 2 --keep-alive \ + --prefer-vk-device 5143:44050a31 -W 2688 -H 1216 -w 2688 -h 1216 -r 144 -o 144 \ + [--max-scale 1] [--force-windows-fullscreen] -- <session child> + ``` + + with `VK_ICD_FILENAMES=.../freedreno_icd.aarch64.json` (Turnip), + `GAMESCOPE_DRM_RENDER_NODE=/dev/dri/renderD128`, `WLR_DRM_DEVICES=/dev/dri/renderD128`, + `GAMESCOPE_FORCE_GENERAL_QUEUE=1`, and OpenGL routed through Zink + (`MESA_LOADER_DRIVER_OVERRIDE=zink`, `LIBGL_KOPPER_DRI2=true`) because Steam's CEF needs GL. +- **Input is bridged from Android**, not libinput: touch and keys go over the socket and are + injected as `wl_seat` events, with an XTEST fallback for X11 clients. No seatd, udev or evdev. + Same as our compositor's seat. +- **Audio is PulseAudio** over a unix socket (`PULSE_LATENCY_MSEC=20`). We ship libpulse already. + With DirectAudio chosen, Windows games bypass it for AAudio through the app + (`docs/direct-audio-integration.md`, GameScope sessions). +- **Settings** for a GameScope container or a shortcut in one show only what a Linux session + reads: name, container, screen size, refresh rate, FPS limit, audio (PulseAudio or DirectAudio), + the FEX preset, variables, controls and networking. The Steam launcher, ReShade, Windows + components, drives, Wine and the emulator pickers are hidden - Proton (ARM64) carries its own + FEX for 64-bit and 32-bit games, so the FEXCore/Box64/WoWBox64 builds installed in the app are + never loaded, while a FEX preset is environment and does reach it. Variables with no reader in a + Linux session (`EnvVarsView.GAMESCOPE_UNUSED`) are dropped from the list; `SteamDeck` is offered + but off, since it makes games pick Deck presets and a 1280x800 layout. +- **Sign-in** follows the Steam store: `LinuxSteamLogin` writes the store's refresh token into the + client's `local.vdf` the way the client stores it (AES-256, key SHA-256 of the account name, IV + ahead as an ECB block, under CRC-32 of the name + "1") with `loginusers.vdf` and `AutoLoginUser`, + unless the client already remembers that account. Signing the store out removes the same entries. +- **First launch.** The app's compositor imports gamescope's frames with a bionic Turnip loaded + through adrenotools; on the system Vulkan driver the import fails (`could not import GPU frames` + in `wayland-*.log`) and the session is black. A user who went straight to Linux Client had no + such driver, so the installer now fetches one (`WN-Turnip`, pinned and checked by SHA-256) when + Drivers holds none, and counts it in `isInstalled`, so the button repairs an existing install. + A Linux session that cannot start - no runtime, no driver, a GPU that is not Adreno, an install + in progress - says so in a dialog that closes it, and the compositor is not started without its + driver: it keeps the driver it started with for the life of the process. +- **A session that never starts.** One that dies within 20 s with an error status says so in a + dialog instead of a black screen. The case that led to it (a Poco F6 on HyperOS 3, kernel 6.1): + `execve("/usr/bin/env"): Function not implemented`, then `ptrace(PEEKDATA): I/O error`, the + same with `PROOT_NO_SECCOMP=1`. `proot -v 9` shows why - the first `execve` comes from proot's + own bionic child with heap pointers tagged in the top byte (`0xb400007b80852f10`), and that + kernel will not read a tagged address for a tracer, so the path is unreadable and the call is + answered EFAULT. `tracee/mem.c` strips the tag before every peek and poke. proot is given only + the options this tree has - it exits on one it does not know, before any session starts. + Upstream's `-i uid:gid` is one this tree does not have: passing it ended every session on every + device with `proot error: unknown option '-i'.` until it was taken out. Identity is answered + instead in `tracee/seccomp.c`, reached from Android's own SIGSYS and from the ENOSYS restart in + `syscall/exit.c`, not from proot's trace filter - so it is not in `proot_sysnums` and does not + belong there. `LinuxRuntimeTest.passesOnlyOptionsThisProotAccepts` runs the packaged binary with + the real option list; a list of accepted options kept by hand is what let `-i` ship. +- **What a session log has to carry.** The build that wrote it, proot's own options, and a signal + named rather than left as a number, since `137` reads like a chosen exit code when it means the + session was killed from outside. The guest command is left out: the container's environment + rides in it, and a user may have typed anything there. +- **A session that stops on its own.** Any non-zero end nobody asked for is said in a dialog, not + only one inside the first 20 s. Leaving silently is what a crash looks like from the outside, + and it leaves the one person who can send the log with no reason to go and find it. `137` names + Android's limit on background processes, which is the likeliest cause of a mid-session death. +- **Ending a session.** proot gets a SIGTERM and 1.2 s before it is killed, so its own + `--kill-on-exit` can take its tracees down; a tracee that outlives its tracer gets `ENOSYS` from + every call proot used to answer and spins. The kernel is the backstop either way - + `PTRACE_O_EXITKILL` is in proot's default options. Inside the session the same rule holds for + every background job `winnative-session` starts: each one is killed and waited for before + `reap_orphans`. Steam mode's heartbeat was not, and that is a hang, not a leak - it outlives the + script by up to an hour, gamescope's reaper waits for it, gamescope is proot's root tracee, so + proot never exits, `Process.waitFor()` never returns and the activity never leaves the black + screen. The sweep is no second chance: it matches `ppid == $PPID`, so a job this shell still + owns is not yet an orphan it can see. +- **HDR.** The compositor's `wp_color_manager_v1` is only advertised once the HDR gate opens, and + the app always asks for `HDR_MODE_OFF`, so it is never reached. Before that changes, note that + `get_information` answers every request with a protocol error and the output's preferred + description is a failed one: gamescope asks for both in one breath, and the error ends its + connection. A real description has to come first. +- **proot's memory transfers.** Paths and buffers move between proot and a traced program with + `process_vm_readv`/`process_vm_writev`, one call each, instead of a `ptrace` call per eight + bytes; a transfer the kernel refuses (an unmapped or read-only page) falls back to `ptrace`. + Strings are read in 1 KiB pieces so none crosses into an unmapped page. +- **gamescope's refresh rate.** `-r` is the frame limit when one is set and otherwise the panel's + rate (`WN_REFRESH`); without it gamescope advertises 60 Hz and games cap themselves there. +- **Child process restrictions.** Android 12 and later kill an app's background child processes + past thirty-two, and a session is well past that: killing proot ends it. The wizard says so once, + when the Linux client download starts, and offers Developer options where Android 14 and later + carry the switch; `ChildProcessRestrictions` reads + `settings_enable_monitor_phantom_procs` to know whether the device still needs it, and on + Android 12 and 13 it gives the `adb` line instead, since only adb can set it there. +- **The environment a Linux session reads.** `EnvVarsView.forGamescope` is applied to the + container's variables as the session starts, not only when one is created, so a container made + before it was a Linux one is read the same way as one made after. `TU_DEBUG` is left as it is: + `sysmem` reads like a frame cost - it keeps a render pass out of tile memory - but an A/B on the + RedMagic (Palworld in-world, same spot, GPU 91-93%) put it at 42.8 fps against 41.3 without, so + the reasoning loses to the measurement. +- **The frame rate the session votes.** The game's layer is posted with the cadence the session is + aiming for - the frame limit when one is set, otherwise the panel's rate - through + `ASurfaceTransaction_setFrameRate`. It had been posted with a vote of zero every session, which + leaves the system to infer the cadence from the rate already being achieved; on a phone whose + vendor picks clocks from that, a session that has been slowed reads as a session that wants to be. +- **Sharing logs.** The Steam client writes the token that signs its account in to its own + connection log, and Logs Manager hands those files to whoever the user is sending them to. + `LogManager.copyShareable` is what every export goes through - both archives and a single file - + and it copies the client's logs a line at a time with any token replaced. The app's own logs are + copied unchanged. +- **Setup wizard.** Its second page (after access, before components) offers the Linux Steam + Client and the Windows one. The Linux card runs `LinuxClientInstaller` - the same install and + the same state Settings > Stores shows - and carries on while the user goes through the other + pages; the Windows card moves on to the components and the container that make it up. +- **Logs.** The compositor's `wayland-*.log`, `linux-session.log` and the Steam client's own logs + (`~/.local/share/Steam/logs`) are part of the Logs Manager (Settings > Debug): listed, shared, + downloaded and deleted with the rest, under `gamescope/` and `steam-client/` in an archive. As a + session starts, `LogManager` keeps the nine newest logs of each type - a type is the name without + its start time - so that with the session's own there are never more than ten. +- **Network page.** The client builds Settings > Internet and its connection indicator on + NetworkManager, which it looks for on the D-Bus system bus as it starts; without one it leaves + out the calls its own interface then makes ("StartScanningForNetworks is not a function") and + shows no connection. The network is Android's, so the session runs a system bus of its own + (`system-bus.conf`) with `winnative-netmanager` on it: one connection named Android, up while + Android has a route out, under the address the app was given. It scans and joins nothing. +- **Which Protons run.** Only an ARM64 Proton can: arm64ec Wine with FEX inside it for the game's + x86 code. Valve's own entry for it stacks the arm64 Steam Linux Runtime underneath, which is + pressure-vessel and needs user namespaces an Android app never gets - hence `winnative-proton`, + the same depot without that line. The x86-64 Protons (Experimental, 10, 9, hotfix, GE) run whole + under Valve's FEX-Emu tool inside the x86 runtime, which needs namespaces and mounts twice over; + a game pinned to any of them is moved to `winnative-proton` as a session starts. Native Linux + x86 titles go the same FEX-Emu way and do not run. Valve names a Proton of its own for thousands + of titles at a priority above the client's default, and installing one has the client fetch + that x86 Proton, its runtime and FEX; so `LinuxSteamShortcuts` sets every game the store's + account owns to `winnative-proton` before the client can install it. The anti-cheat runtimes + a game brings (BattlEye, EasyAntiCheat) are Linux apps to the client, which would fetch the + scout and soldier runtimes to run them in; `winnative-steam-compat` sets those to the tool as + well, so they depend on nothing. None of FEX-Emu, either x86 runtime or an x86 Proton is + needed: games run with all of them uninstalled. +- **Epic games.** They are non-Steam games of the client like the others, but Epic signs a game in + with an exchange code that lasts five minutes and is spent on first use, so a shortcut cannot + carry one. The entry names the game in its launch options (`WN_EPIC`), `winnative-launch` hands + such a game to `winnative-epic-launch`, and that asks `LinuxEpicTokens` in the app - loopback + only, behind a secret written into the runtime for that session alone - for the command line as + the game starts. A DRM title's ownership token is written into the runtime and read back through + Proton's `Z:`, which needs no prefix. A game whose sign-in cannot be had still starts. +- **A store game with no shortcut.** The library writes a shortcut for one only once it has been + played, so an installed GOG or Epic game is also taken from the store's own records + (`LinuxGogGames`, `LinuxEpicTokens.installedGames`) under the same id a shortcut would give it. + GOG records where a game was installed but not what to run: that is the primary play task of the + `goggame-<id>.info` the installer leaves beside it. +- **Non-Steam games.** `LinuxSteamShortcuts` keeps the library's custom, itch.io, GOG and Epic entries in + the client's `userdata/<account>/config/shortcuts.vdf` (binary VDF), each mapped to + `winnative-proton` in `CompatToolMapping`. The entries it wrote are listed in + `winnative-shortcuts` beside the file, so ones the user added in the client are left alone and + one whose game left the library is removed - at once when no client is running, else at the next + session start. Such an entry in the GameScope container launches as + `steam://rungameid/<appid << 32 | 0x02000000>`, which the session script sends down `steam.pipe` + once the interface has loaded: given as an argument it is launched within a second of sign-in, + before the interface answers the launch's prompts, and hangs at `ShowInterstitials`. The Proton + prefix (`compatdata/<appid>`, about 370 MB) holds the saves and is not removed with the entry. +- **The HUD** counts a game frame per commit of gamescope's window, not per plane: gamescope + presents through a toplevel and a synchronized subsurface for each layer, and the game moves + between them. The renderer name is read from the Proton processes' mapped modules + (`WaylandRendererProbe.probeLinuxSession`), since the window itself is always gamescope's. + Frame generation runs in the app's compositor, so its output rate needs nothing from gamescope. +- **The WN button** on the Virtual Gamepad is the pad's guide button (`BTN_MODE`): Steam opens + its menu on it. It is an ordinary control element (`GAMEPAD_BUTTON_GUIDE`, icon 40) and is only + drawn in Linux sessions and the editor. +- **Steam is the native arm64 client** (`steamrtarm64/steam -gamepadui`, CEF forced to + ANGLE-on-Vulkan), with x86-64 games through Proton11ARM + FEX (`FEX_APP_CONFIG`, + `PROTON_USE_NTSYNC=1`). No box64. One game is named as working: Metro: Last Light Redux. +- **What it does not give us:** no gamescope/wlroots/Xwayland build recipe, no packages, no pinned + versions - `gamescope` must already be in `PATH`, and its bundle is exported from the author's own + container and marked non-redistributable. No published logs, benchmarks or reproductions. + +Takeaways for the phases: Phase 1 should plan for chroot-quality device nodes without root, which +argues for the prefixed-glibc model over proot; Phase 2's command line and env are settled; Phase 3 +can target native arm64 Steam first (Valve's `steamrtarm64`) and needs Zink for CEF, which means a +Mesa GL build in the rootfs beside Turnip. + +## What "a gamescope option" means + +Three layers, each usable on its own: + +1. **A Linux runtime.** A glibc arm64 rootfs, separate from the Wine imagefs, in which a Linux ELF + can be exec'd as the app's own uid with no root. This is a new container kind, not a Wine + container. +2. **gamescope on the compositor.** gamescope built for aarch64 with the DRM/SDL/OpenVR/PipeWire + backends disabled, run as a Wayland client of `libwnwayland.so` with `--expose-wayland`, hosting + Xwayland for X11 programs. It is a normal `xdg_toplevel` to our compositor; nothing in the + compositor's rendering path changes. +3. **The app around them.** A Linux container type, shortcut settings for Linux entries, and the + gamescope switch with its options (upscaler, frame limit, game resolution vs output resolution). + +## How it is wired + +**Choosing it.** Container Settings and Shortcut Settings have a **Runtime** row above Display Server: +*Wine* (the default) or *Gamescope*. It is stored as the `runtime` extra (`wine` / `gamescope`), and a +shortcut overrides its container the same way Display Server does, so one library entry can move +between the two without touching the container. Choosing Gamescope pins Display Server to Wayland; +gamescope is a client of the compositor and has nothing to draw on otherwise. + +**Booting.** `XServerDisplayActivity.resolveDisplayBackend` reads the runtime first. For Gamescope +it starts the compositor as for any Wayland session (same Turnip, same output size, same input +seat) and `setupLinuxSession` replaces the Wine launcher with `LinuxProgramLauncherComponent`, +which execs proot from the native library directory: + +``` +libproot.so --kill-on-exit -r files/linuxfs -w /root + -b /dev -b /proc -b /sys -b /dev/urandom:/dev/random -b /proc/self/fd:/dev/fd ... + -b files -b cache -b <XDG_RUNTIME_DIR> -b imagefs -b /storage/emulated/0 + -b cache/shm:/dev/shm -b etc/winnative/empty:/sys/fs/selinux + -b etc/winnative/proc/<x>:/proc/<x> (only where Android denies the real file) + /usr/bin/env -i HOME=/root PATH=... WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=<same host path> + GAMESCOPE_DRM_RENDER_NODE=/dev/dri/renderD128 GAMESCOPE_FORCE_GENERAL_QUEUE=1 + MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink LIBGL_KOPPER_DRI2=true + VK_ICD_FILENAMES=<rootfs freedreno icd> PULSE_SERVER=unix:<imagefs pulse socket> <user env> + gamescope --backend wayland --expose-wayland -f -W <w> -H <h> -w <w> -h <h> [-r <fps>] [-e] + -- /usr/local/bin/winnative-session <mode> [arg] +``` + +Host paths are bound at their own paths on purpose: the compositor socket, the PulseAudio socket +and the user's storage need no translation on either side, and proot never touches the fds a +dma-buf travels in. `-e` is added for the Steam mode only. + +**What runs** is decided by `linuxSessionArgs` from the library entry: + +| Entry | `winnative-session` | Notes | +|---|---|---| +| Boot from Edit Containers (no shortcut) | `desktop` | pcmanfm under gamescope's Xwayland: the file explorer | +| Linux entry (`runtime=linux`) | `run <path>` | AppImage with `APPIMAGE_EXTRACT_AND_RUN=1` (no FUSE), `.sh` through bash, else exec | +| Steam entry (`game_source=STEAM`) | `steam steam://rungameid/<id>` | the native arm64 client, `-gamepadui`, after `winnative-steam-install` | +| A Windows entry | refused | the launch reports it and asks for Runtime = Wine | + +The session ends when gamescope exits; the activity exits with it. A background session stays +reattachable through `SessionKeepAliveService`, which now records that the environment is a Linux +one so a reattach resolves to gamescope and not Wine. + +**The runtime** is `files/linuxfs`, assembled by `tools/linuxfs/build-linuxfs.sh` from Arch Linux +ARM packages (gamescope 3.16.29, Xwayland 24.1, Mesa 26.2 with Turnip and Zink, pcmanfm, foot, +PulseAudio and X client libraries, ibus and glib for steamwebhelper) plus the `overlay/` scripts +and fake `/proc` files. It contains no Valve software. `LinuxRuntime.isInstalled` checks for +gamescope, the session script, and the packaged proot binaries. + +**Installing.** Settings > Stores > Steam > Linux Client (`LinuxClientInstaller`) installs the +runtime and then the Steam client in one go. The runtime is the `linuxfs.tar.zst` asset of the +`Assets` release on WinNative-Emu/Components (zstd 19, 128 MiB window), checked against the +SHA-256 in `linuxfs.json` and unpacked by the app's native extractor into a staging directory that +replaces `files/linuxfs` only once it is complete; a runtime being replaced keeps its `root` home. +`build-linuxfs.sh` writes both files; upload them, plus the package list, to that release. The +client steps are the ones below, done by the app with a progress bar; Valve's zips carry a short +prefix Android's own zip reader refuses, so they are read with Commons Compress. + +**Steam.** `winnative-steam-install` reads Valve's `steam_client_publicbeta_linuxarm64` manifest +from the client-update CDN, downloads the `*_all` and `*_linuxarm64_linuxarm64` zips (sha256 +checked), unpacks them into `~/.local/share/Steam`, writes `package/beta = publicbeta`, and +repairs the zip entries Valve packs with backslash separators. The `*_linuxarm64_linuxarm64` +components are the native aarch64 client (`steamrtarm64/steam`, verified as an aarch64 ELF); +the `*_linuxarm64` and `*_steamrt_linuxarm64` components in the same manifest are the x86 client +and its pressure-vessel runtime and are skipped. Valve's own `steam.sh` has no arm64 branch, so +the client is started directly. Proton and FEX are Steam depots the client fetches itself. The +client is downloaded from Valve at first use and never redistributed. + +**proot** is the tree at `app/src/main/cpp/proot` (a Termux-derived build without the extension +layer), now in the CMake build as `libproot.so` and `libproot-loader.so`. Changes made for this: +the loader is linked freestanding at `LOADER_ADDRESS` as upstream does; `statx` is translated +(glibc uses it for stat); `PROOT_NO_SECCOMP` disables the seccomp accelerator for debugging. Its +SIGSYS handler is what lets glibc survive Android's zygote filter (`set_robust_list`, `rseq` +return `ENOSYS` instead of killing the process). `targetSdk 28` is load-bearing: it keeps the app +in `untrusted_app_27`, the last domain allowed to exec a file under `files/`. + +## Verified so far + +On the NP06J (Adreno 840, Android 16, app uid 10516), 2026-09-17: + +- The proot rootfs runs as the app's own uid. Android's app seccomp policy traps `setuid`, + `setgid`, `setreuid`, `setregid`, `setfsuid`, `setfsgid` and `get_robust_list`; proot answers + the id calls itself (a process without privileges may only take an id it already holds), and + `libwnsession.so` answers `get_robust_list` in the process (see below). +- gamescope 3.16.29 starts as a client of `libwnwayland.so`, drops its libdecor frame once the + compositor confirms `xdg_toplevel.set_fullscreen`, and delivers input: it holds two + `wl_pointer`/`wl_keyboard` pairs (one on its input thread), so the compositor sends seat events + to every such object of a client. pcmanfm shows, its menus open on tap, GTK icons render + (glycin's bwrap sandbox needs `/proc/sys/kernel/overflowuid`, which is faked). +- **The GPU.** Arch's Turnip cannot open the GPU (msm only); Termux's 24.2.6 KGSL build rejects + the Adreno 840; Mesa 26.2.2 built with `-Dfreedreno-kmds=msm,kgsl` drives it (vkcube at 121 fps + through the compositor). `/dev/dri` is not visible to an app process at all (not even `stat`), and + neither is `/proc/net` nor `NETLINK_SOCK_DIAG`: only `/dev/kgsl-3d0` is. gamescope refuses to + offer `linux-dmabuf` without a DRM node from `VK_EXT_physical_device_drm`, so the runtime + presents the KGSL device as `/dev/dri/renderD<minor>` (a proot bind) with the sysfs entries + libdrm reads under `/sys/dev/char/<maj>:<min>`, and the Turnip patch reports that device as both + nodes. Consumers then run PRIME ioctls on it, which KGSL cannot answer; `libwnsession.so` + keeps an fd/handle table for `drmPrimeFDToHandle` and friends on that device. With that, Xwayland + runs glamor on Zink and every Vulkan client under gamescope gets a swapchain; the compositor log + shows gamescope presenting through dma-buf. +- **Steam.** The arm64 client installs, updates itself (exit code 42 = restart, as `steam.sh` + handles), loads `steamui.so` and `vgui2_s.so` (GTK 2 from Debian, `openal`, `libvdpau`), shows its + update window on the GPU, passes its System V semaphore and robust-mutex checks, connects to + Steam's network, and launches `steamwebhelper`; the gamepad UI comes up and is usable. +- **Steam's IPC peer check.** The client identifies a websocket peer by running + `lsof -P -F upnR -i TCP@127.0.0.1:<port>` (`GetIPCConnectionDetails`), which cannot work in the + app sandbox (`/proc/net` and `sock_diag` are denied). `libwnsession.so` records every loopback + port a session process binds, connects or accepts, with its peer, and answers that lsof from the + record. The client splits the `n` line on `->` and requires two halves, takes the port from the + left one, requires `u` to be its own uid, discards a record whose `R` is its own pid as a leaked + fd, and finally accepts the peer if it is the client itself or a descendant; `Checked: %d/%d` is + peer pid over the client's pid, so `Checked: 0/<pid>` meant no pid had been parsed at all. +- libdrm-based checks: `drmGetDevice2` on the presented node reports a platform device + `kgsl-3d0`; Turnip reports `hasRender=1` for it. + +On the RedMagic (Adreno 840), 2026-09-20: + +- **The desktop.** gamescope shows one program at a time and cannot hold a panel and its windows, + so `winnative-session desktop` runs XFCE 4.20 on labwc (`WLR_BACKENDS=wayland`, pixman renderer) + as a client of the app's compositor. A nested wlroots compositor takes modifiers from + `wl_keyboard.modifiers` alone, which the compositor now sends; without it Shift never reached a + program on the desktop. +- **Taps.** Touchscreen mode hands a touch to the compositor (`nativeSendPointer`), which maps it + through the scale mode's letterbox; the view's own stretch landed taps some 35 px off. The Steam + client sets `STEAM_TOUCH_CLICK_MODE` to 4 (passthrough), in which gamescope's Wayland backend + turns a nested pointer's motion into touch motion without a touch down, so the cursor never + moves and a click lands where it was left. gamescope's Wayland backend takes no `wl_touch` + either, so the session holds the mode at 0 (hover): the position warps the cursor and the + button clicks. +- **Terminals.** glibc 2.42+ uses the termios2 ioctls (`TCGETS2`, `TCSETS*2`); Android's SELinux + policy allows an app only the older ones on a pty, so `isatty` failed everywhere (bash without + a prompt, `stty: Permission denied`). proot's seccomp filter traces those four requests by + argument and rewrites them; every other ioctl is allowed before the syscall list. +- **Updates.** `linuxfs.json` carries `version` (the build's UTC time, also in + `/etc/winnative/version`). The installer offers a newer runtime, Proton (`proton-arm64.json`, + once it carries a version) or Turnip (`linux-turnip.json` + `linux-turnip.tar.zst` holding + `libvulkan_freedreno.so` and `name`; not published until a driver newer than + `LinuxRuntime.TURNIP_BUILD` exists). An update keeps `/root`, `/mnt/winnative`, + `/opt/winnative-proton` and `/etc/machine-id`: the client stayed signed in and nine prefixes + came through byte for byte. + +## Not yet done + +- Processes a desktop program double-forks are reparented to the app (the subreaper) and stay + zombies until the session's cleanup sweep; they are only reaped then. + +- Xwayland's swapchain through the gamescope WSI layer still fails once at startup + (`CreateSwapchainKHR failed with VK_ERROR_INITIALIZATION_FAILED`) and recovers; harmless so far. +- `steam-runtime-launcher-service` is not shipped for arm64; Steam disables it and goes on. +- Shortcut Settings still shows the Wine pages for a Linux entry; a Linux settings page is owed. +- Extensionless ELFs in the picker; AppImage icons. +- gamescope logs `Changed refresh` on every presentation-feedback event because the compositor's + reported refresh jitters around 120 Hz; sending a fixed refresh would quiet it. + +## Risks + +- ptrace cost if proot stays past bring-up. +- Android's seccomp policy for app processes versus what glibc, Xwayland and gamescope call. +- `/dev/dri` visibility differs per device; the compositor already reports the case where there is + none, and the Linux side must fail the same way rather than crash. +- gamescope is 50k lines of C++ that expects a desktop; each build option we turn off is one fewer + place it can assume one. diff --git a/docs/LINUX-PROTONS.md b/docs/LINUX-PROTONS.md new file mode 100644 index 000000000..2b2d57f15 --- /dev/null +++ b/docs/LINUX-PROTONS.md @@ -0,0 +1,28 @@ +# Linux Proton sources and integration + +Reviewed Bannerlator branch `feat/linux-gamescope-runtime` at `e58a2befdd8f5b7ce0e42f81dbbf920194a74ae4`, cloned into `/tmp/bannerlator-review.s2FtH5/Bannerlator`. + +Bannerlator's `LinuxProtons.java` reads `https://raw.githubusercontent.com/The412Banner/winlator-contents/main/linux-protons.json`. The catalog points directly to upstream release archives, not Bannerlator-hosted binaries: + +- GE: `GloriousEggroll/proton-ge-custom`, `GE-Proton11-7-aarch64.tar.gz`, 645,786,140 bytes. +- CachyOS: `CachyOS/proton-cachyos`, `proton-cachyos-11.0-20260703-slr-arm64.tar.xz`, 339,912,088 bytes. + +Both files were downloaded and verified against the upstream SHA-512 assets. Their native Wine executables are ARM64 ELF under `files/bin-arm64/wine`. Both `toolmanifest.vdf` files declare `/proton %verb%` and require Steam Linux Runtime app 4185400. + +Bannerlator downloads and verifies in Android, stages archives in `compatibilitytools.d/.bannerlator-download`, and writes requests to `root/.bl-proton-extra`. Its session invokes `bannerlator-proton-extra` to unpack before starting Steam. `bannerlator-steam-compat.adopt_extras` preserves the original manifest, wraps the Proton entry point without the `require_tool_appid` dependency, and protects the tool's internal name from default compatibility remapping. The original dependency invokes pressure-vessel/user namespaces, unavailable inside this Android runtime. Its catalog reports downloaded archives as pending until a session processes them. + +WinNative now discovers the latest three stable ARM64 releases from each official repository through GitHub's releases API, requiring the release asset's SHA-256 digest. It does not offer x86_64 archives or mix these builds into Android Wine/Proton component catalogs. Installed older builds remain listed through their local metadata. + +Settings โ†’ Stores โ†’ Linux Client โ†’ Linux Proton builds exposes download, progress, cancellation, retry and removal using dialog pane navigation. Installation requires the Linux runtime. Downloads continue with the existing keep-alive service when the dialog closes. Each archive is size/hash checked, unpacked into a private staging directory, checked for contained paths/links and ARM64 Wine, and renamed into `root/.local/share/Steam/compatibilitytools.d`. Failed/cancelled work removes its staging files. The original archives remain hosted by GE and CachyOS. + +The shipped `winnative-steam-compat` registers managed extra builds at session startup, saves the original tool manifest, uses WinNative's existing launch wrapper and retains the upstream compatibility tool name. Default mapping protects those names, including CachyOS's `proton-` prefix. Restart the Linux client after installation, then select a build per game under Steam โ†’ Properties โ†’ Compatibility. The rootfs update already preserves `root`, including all these installed tools. + +This does not redistribute or rebuild Proton. Runtime registration is tested separately from game compatibility, which still requires an ARM64 device and a real Steam session. + +## Verification + +The PUBG debug app and instrumentation APK build successfully. AVD tests installed and removed the actual GE-Proton 11-7 ARM64 and CachyOS 11.0 (20260703) ARM64 archives, checked executable permissions and metadata, and rejected traversal and x86 packages without leaving partial installations. A larger 16 GB AVD was used after the initial 6 GB AVD ran out of space during GE extraction; cleanup removed the failed partial tree. + +The live catalog was checked in the app against both upstream repositories. All four new strings exist in all 23 locales, and the dialog uses `DialogPaneNav` with the existing navigable popup actions. Large removals run off the UI thread, rename to an owned hidden directory first, and clean up interrupted removals on refresh. + +The Python registration test verifies that both tool manifests lose the unavailable container dependency, originals are preserved, registration is idempotent, and CachyOS per-game selection survives default remapping. Both native Wine executables ran `--version` under QEMU with the original Linux rootfs and reported Wine 11.0 (Staging/CachyOS). diff --git a/docs/WAYLAND-DISPLAY.md b/docs/WAYLAND-DISPLAY.md new file mode 100644 index 000000000..84525d6c1 --- /dev/null +++ b/docs/WAYLAND-DISPLAY.md @@ -0,0 +1,99 @@ +# Wayland display server + +WinNative can run a session on an embedded Wayland compositor instead of its X server. Wine's +`winewayland.drv` connects to the compositor over a socket, the game's Vulkan swapchain is shared +as dma-buf frames, and the compositor blits them to the screen through Turnip. The X server is +still created for input bookkeeping but never draws. + +The compositor (`app/src/main/cpp/waylandcomp`) and the session design were written by +[The412Banner](https://github.com/The412Banner) for +[Bannerlator](https://github.com/The412Banner/Bannerlator) and ported here; see +[CREDITS.md](../CREDITS.md). + +## Choosing the display server + +- **Container Settings โ†’ Display โ†’ Display Server** sets the container default (X11 or Wayland). +- **Shortcut Settings โ†’ Display โ†’ Display Server** follows the container until it is changed and + saved for that shortcut; only that shortcut is affected. Setting it back to the container's + value removes the override again. +- The choice is stored as the `displayBackend` extra (`x11` or `wayland`). + +Wayland can only be selected, and is only used at launch, when both hold: + +1. The device has an Adreno GPU. The compositor imports the game's frames with Turnip through + adrenotools; Mali and Xclipse devices stay on X11. +2. The selected Wine/Proton ships `lib/wine/aarch64-unix/winewayland.so` and + `lib/libvulkan_freedreno_wayland.so`, the Wayland Turnip the game renders on. + WinNative writes its own Vulkan ICD manifests for the bundled Turnips as + `share/vulkan/icd.d/wayland_turnip[_variant].json` and points winewayland at the chosen one + through `BANNER_WAYLAND_VK_ICD`, so the donor's manifest names are never relied on. + The stock WinNative Proton ships only `winex11.so`, so the Display Server dropdown stays on + X11 with it. Install a Wayland layer from the Contents screen, for example Banner's + `proton-11.0-2.1-arm64ec-wayland-v16.wcp` from the Bannerlator Wayland pre-releases + (installs as `Proton-11.0-2.1-arm64ec-16`), then pick it as the container's Proton. The + dropdown enables as soon as the selected Proton passes the check. +An x86_64 layer does not qualify, even when it ships a Wayland driver. Its `winewayland.so` is +a unixlib like any other and Box64 runs it: the compositor gets a connection, a desktop and +windows. But the driver also has to `dlopen` the Wayland Turnip to hand the game its ICD, and +that Turnip is aarch64, so the pin fails and the session is a desktop that never presents a +frame. Pointing the driver at Box64's native `libwayland-client.so.0` wrapper instead makes +Wine and Turnip share one connection, but the driver then faults at the entry to its event +thread. x86_64 layers therefore stay on X11, where DXVK reaches the GPU through Box64's Vulkan +wrapper. + +The files cannot be copied into another Proton. `winewayland.so` is a Wine unixlib bound to the +`win32u` it was built against, so a copy taken from a different Proton loads, connects to the +compositor and enumerates its outputs, but creates no window: the session runs with a black screen +and nothing in the log says why. Selecting a Proton that ships its own Wayland driver is the only +supported route. + +The compositor needs a Turnip of its own to import the guest's frames. The stock Vulkan driver has +no `VK_EXT_image_drm_format_modifier`, so `vkCreateDevice` fails and nothing is ever presented; a +container or shortcut left on `System` therefore falls back to the container's own graphics driver +and then to any installed one, and says so in the log. + +When a shortcut or container asks for Wayland and either condition fails, the session starts on +X11 and a toast says so. Before Wine launches on Wayland the launcher creates the runtime +directory, extracts the xkb keymap into it and waits up to eight seconds for the compositor's +`wayland-0` socket, so Wine never starts against a socket that does not exist yet. The check is cached per Wine version and refreshed when contents are +installed or removed. + +## What a Wayland session changes + +- `WAYLAND_DISPLAY=wayland-0` and `XDG_RUNTIME_DIR=<app files>/.wayland-rt` are exported to the + guest; `DISPLAY` is not. The Proton's `lib/` directory is placed first on `LD_LIBRARY_PATH` + because `winewayland.so` links the Wayland client libraries with unversioned sonames. +- The prefix registry gets `Software\Wine\Drivers\Graphics = wayland` and Wine's `shell` desktop + is seeded at the container size; an X11 launch restores both. `winex11.drv` is disabled + through `WINEDLLOVERRIDES` for the session so Wine falls through to winewayland. +- `BANNER_WAYLAND_VK_VARIANT` selects the bundled Wayland Turnip by GPU generation (`a7xx` for + Adreno 710/720/722, `a8xx` for Adreno 8xx). A value set in the container or shortcut + environment wins, as does `BANNER_WAYLAND_VK_ICD=<path to icd.json>`. +- `GALLIUM_THREAD=0` is exported unless the user set it: Mesa's threaded GL context has a helper + thread that crashes outside Wine's signal handling. +- Touch, on-screen controls, keyboard and mouse still go through the X server's input path and + are mirrored to the compositor; relative mouse input and pointer locks are delivered as deltas. + The soft keyboard commits text through `zwp_text_input_v3`; the clipboard is synced both ways. +- The FPS limiter, fullscreen stretch and the FPS HUD follow the session's normal controls. + +## Environment switches + +| Variable | Effect | +|---|---| +| `BANNER_WAYLAND_ZERO_COPY=1` | Present a fullscreen game window on its own display layer without a copy (also exports `BANNER_WSI_AHB=1`). | +| `BANNER_WAYLAND_UBWC=0` | Advertise linear buffers only; default advertises UBWC (compressed) layouts. | +| `BANNER_WAYLAND_NO_RENDER_NODE=1` | Debug: advertise no DRM device in the dma-buf feedback. | +| `BANNER_WAYLAND_VK_VARIANT` / `BANNER_WAYLAND_VK_ICD` | Game-side Turnip selection, see above. | + +Session logs are written to `Android/data/<package>/files/wayland-logs/`, one file per session. + +## Lifecycle + +The compositor thread is started once per process and kept; a session begins when the display +activity attaches its surface and ends when the guest has been terminated. Ending a session +disconnects every client, drops the screen surface and resets the compositor's per-session +state, so the next launch, X11 or Wayland, starts clean. A background session that is reattached +to a new activity keeps the compositor running and only receives the new surface. + +The compositor's own Turnip driver is the one selected in the graphics driver settings at the +time of the first Wayland launch in a process; the game's driver is chosen per launch. diff --git a/docs/direct-audio-integration.md b/docs/direct-audio-integration.md index d7bc35a81..0c9c98400 100644 --- a/docs/direct-audio-integration.md +++ b/docs/direct-audio-integration.md @@ -221,6 +221,37 @@ AYANEO Pocket FIT (Adreno 750, Android 14). Reproduce that first. --- +## GameScope sessions + +A GameScope container runs Valve's ARM64 Proton under glibc, where the bionic unixlib cannot load +and there is no `libaaudio` to call. The driver's source is the same; what changes is who opens +the stream. + +- `tools/linuxfs/directaudio/build-directaudio.sh` builds upstream's `directaudio.c`, unmodified, + for aarch64 glibc against the headers of Valve's Wine 11 (pinned commit), and links it with + `wn_aaudio_client.c`: the 27 `AAudio_*` entry points, implemented as a client of the app. The + result ships as `assets/directaudio/linux/winedirectaudio.so`; the two PE halves are upstream's + own, taken from the `wine11` archive. +- `DirectAudioHost` (`libwnaudiohost.so`) listens on a unix socket inside the session. One + connection is one stream: open/start/stop/close go over the socket, the PCM goes through a ring + in `ASharedMemory` whose descriptor rides on the open reply, and the real AAudio data callback + wakes the client's pump thread through a futex after every burst. The protocol is + `app/src/main/cpp/wnaudiohook/wn_aaudio_protocol.h`. +- PulseAudio always runs in these sessions, because the Steam client and native games know + nothing else. DirectAudio only takes the Windows games. +- `winnative-directaudio <steam root> on|off` runs at session start. It copies the driver into + every Proton whose `winealsa.so` walks the same unixlib table (37 entries today; the x86 Proton + has no `aarch64-unix` and is left alone), and writes or removes `Audio=directaudio` in each + prefix that Proton runs, templates included. With the driver off the value is removed, so a + prefix never names a driver its session cannot serve. +- Capture is refused by the host unless the mic opt-in and `RECORD_AUDIO` both hold, the same + gate as a Wine session. + +Measured on a RedMagic (Brawlhalla, 48 kHz float stereo): no underruns after the stream's first +half second, 76 ms reported track latency against 116 ms for the PulseAudio track beside it. That +device denies `AUDIO_OUTPUT_FLAG_FAST` to every app track, this one included, so the burst is +960 frames; a device that grants it gets the fast mixer with no change here. + ## Known gaps Upstream's own honest list, plus what applies on the WinNative side: diff --git a/docs/linux/odin3-proton-review-2026-09-22.md b/docs/linux/odin3-proton-review-2026-09-22.md new file mode 100644 index 000000000..7bb5fd71b --- /dev/null +++ b/docs/linux/odin3-proton-review-2026-09-22.md @@ -0,0 +1,42 @@ +# Odin 3 and Linux Proton review โ€” 2026-09-22 + +## Evidence and limits + +The supplied Wayland log starts a compositor on Adreno 830 at 12:50:55 and reports zero guest windows. The application log reports a nonzero Linux session exit at 12:51:09, triggering `LinuxSessionUnavailable`. It does not contain the child process output or exit status. This establishes early Linux session termination, not a confirmed Steam crash or a specific GPU fault. + +The `26.2.2` Android-driver warning uses the Linux Mesa version in the Android renderer's driver lookup. It is not proof of which driver the separate Wayland compositor loaded. The `libjpeg.so` namespace errors arise from optional vendor-driver dependency preloads; there is no supplied native crash trace establishing them as fatal. + +An Odin 3 reproduction still needs its Linux session log. The x86_64 AVD verifies Android installation, UI, and process-launch behavior; it cannot verify ARM64 Steam, KGSL rendering, or Windows gameplay. + +## Corrections + +- Require an installed Turnip library for the Android Wayland compositor. Previously any installed Android driver's metadata satisfied the Linux install gate, and the compositor could select a proprietary Qualcomm driver. Linux Mesa selection remains separate. +- Launch proot with a structured argument list. Preserve empty arguments, spaces, and quotes. Capture stdout/stderr from the host process start, before the guest script can redirect output. Record launch failures and exit status in a dated Linux session log. +- Use one shared Proton launch helper for Valve, GE, and CachyOS. Existing downloaded tools are updated by the registrar at the next session start. Keep their own Wine and FEX files intact. +- Prepare DirectAudio for the selected Proton, checking its Wine unix-call table before installing the driver. Update the selected prefix and first-launch templates; remove stale DirectAudio overrides for incompatible builds. The old wrapper checked a nonexistent `directaudio/lib/wine` directory, while staging uses `directaudio/aarch64-unix` and architecture siblings. +- Update generated wrappers/manifests atomically, only when content changes. Stop deleting/recreating the default compatibility tool every fifteen seconds. During live refresh, leave Steam's saved configuration alone and stop periodically editing audio registries while games run. +- Populate per-title mappings on the first creation of CompatToolMapping, not only subsequent launches. Preserve selected adopted GE/CachyOS tool names. +- Enable Proton logs by default for these Linux sessions, respecting an explicit PROTON_LOG setting, and place them alongside the session log. +- Synchronize the APK scripts and runtime-build overlay so a runtime rebuild cannot restore the older launch implementation. + +## Bannerlator comparison + +Reviewed `The412Banner/Bannerlator`, branch `feat/linux-gamescope-runtime`, commit `7b012b0913b51d315059072e257e810382de6c57`, fetched from GitHub on 2026-09-22. + +Its latest source/documentation includes the Turnip compositor fallback correction, seccomp-probe removal, and mappings for owned games/anti-cheat runtimes. WinNative already removed the seccomp probe and maps anti-cheat runtimes. This change corrects its incomplete Turnip gate. The newer owned-game mapping is a download-selection improvement; it does not establish the cause of the supplied early session exit. + +Its third-party Proton wrappers remove the Steam Linux Runtime dependency to avoid pressure-vessel's unavailable user namespaces and prepend the input interposer. WinNative retains both behaviors. Bannerlator's progress log reports successful GTA V Legacy gameplay on GE 11-7, but also unresolved GE/CachyOS failures with GTA V Enhanced. It explicitly reports that swapping its own FEX into GE broke exception handling. No such component mixing is introduced here. + +Reference: https://github.com/The412Banner/Bannerlator/tree/7b012b0913b51d315059072e257e810382de6c57 + +## Validation + +- PUBG debug APK and instrumentation APK assembled successfully. +- Five Python regression tests cover source/asset parity, stable manifests, preserved tool choices, first-run mapping, live-refresh configuration preservation, compatible/incompatible DirectAudio preparation, argument forwarding, and exit status. +- AVD installed and removed the actual `GE-Proton11-7-aarch64.tar.gz` and `proton-cachyos-11.0-20260703-slr-arm64.tar.xz` archives; both archive test runs passed, including traversal/x86 rejection. +- AVD launcher tests verify argument preservation, early stderr capture, exit status, and failure reporting when the executable cannot start. +- Components navigation tested in portrait and landscape; landscape screenshot inspected. No UI strings or navigation structure changed. + +## Device retest + +Install the new PUBG debug APK, close any retained Linux session, and start Steam again so the runtime scripts and installed Proton wrappers refresh. Select the downloaded GE/CachyOS ARM64 entry in the game's Steam Compatibility settings and launch it. If startup still fails, export the dated `linux-session-*.log`; for a game failure also include `steam-<appid>.log`. Include the game and Proton version. These logs are needed before claiming the Odin failure or gameplay is fixed end to end. diff --git a/docs/linux/steamdeck-review-2026-09-22.md b/docs/linux/steamdeck-review-2026-09-22.md new file mode 100644 index 000000000..6bb927d2a --- /dev/null +++ b/docs/linux/steamdeck-review-2026-09-22.md @@ -0,0 +1,32 @@ +# SteamDeck comparison โ€” 2026-09-22 + +Compared WinNative `980ae6a2` with The412Banner/SteamDeck default branch at `2cbd015d80ec3320eb34cfa1b83d5c63240ae192` (fetched from GitHub). Source: https://github.com/The412Banner/SteamDeck/tree/2cbd015d80ec3320eb34cfa1b83d5c63240ae192 + +## Applied + +| Difference | WinNative correction | +| --- | --- | +| SteamDeck passes `-i <app uid>:<app uid>` to proot. WinNative did not emulate identity calls. | Enable the same identity emulation while retaining the app's numeric identity. This allows Xwayland's child setup to complete where Android refuses setuid/setgid. | +| SteamDeck puts the optional `PROOT_NO_SECCOMP=1` in proot's host environment. WinNative's environment editor only sent it into the guest. | Route this specific override to the host. Leave acceleration enabled by default; `0`, `false`, and `off` do not disable it. | +| SteamDeck checks an imported Vulkan manifest's referenced library and falls back when unreadable. | Validate imported ICD JSON and its library with the JSON parser, including escaped separators and relative paths. Fall back to bundled Mesa without modifying the saved choice. An unknown/removed selection also falls back to bundled Mesa instead of another global driver. | + +The identity difference is relevant to the Odin report's failure before any guest window. It is a concrete missing startup behavior, but the supplied report lacks the Linux process log needed to attribute that device's failure to it. + +## Already present or intentionally different + +- The ten shared preload C files are functionally equivalent after normalizing project prefixes, comments, and local fork-handler names. No additional syscall fixes were found there. +- SteamDeck's GE/CachyOS wrappers remove pressure-vessel dependencies and restore input-preload ordering, as WinNative already does. They do not supply an additional GE/CachyOS startup shim missing from WinNative's shared launcher. WinNative retains its stricter DirectAudio compatibility preparation. +- `TU_DEBUG=sysmem` and `ZINK_DESCRIPTORS=lazy` are in the container default and reach the guest. `PROTON_USE_XALIA` was not offered anywhere; it is now one of the known variables, unset by default, so Proton's own gate is reachable without typing the name. No device-specific workaround is enabled globally by this change. +- SteamDeck optionally reapplies CPU affinity to the client and separately applies a mask to Proton games. This is performance tuning, not a demonstrated cause of the Odin early exit. It was not imposed on WinNative's sessions. +- SteamDeck's additional `space.c` redirects free-space queries for a whole SD library whose `steamapps/common` is bound to the card. WinNative binds individual game directories, potentially from different locations. Copying this shim would report the wrong filesystem for that layout. +- SteamDeck prefers a runtime-shipped proot/talloc pair. WinNative packages proot and its loader with the APK. No binary replacement was made without evidence that WinNative's packaged build is faulty. +- SteamDeck's orphan sweep kills every other process under its app UID. WinNative supports retained sessions and other app-owned services, so that sweep is not safe to copy indiscriminately. +- SteamDeck additionally creates crash-surviving session bundles with scrubbed Steam logs. WinNative now captures launcher stderr from process start and exposes Steam logs through its existing manager, but does not have the same bundle/redaction implementation. This remains a diagnostics difference, not a boot fix applied here. + +## Verification + +Identity emulation is not a command line option here. Upstream proot takes `-i uid:gid`; the bundled build has no such option and answers `setuid`, `setgid`, `setresuid`, `setresgid`, `setreuid` and `setregid` from its seccomp handler instead, granting an id the process already holds and refusing any other. A host proot cannot stand in for that, so the check belongs on device: `LinuxRuntimeTest.passesOnlyOptionsThisProotAccepts` runs the packaged binary with the real option list and fails if it reports an option it does not know. + +The PUBG debug and instrumentation APKs build. AVD tests cover proot argument construction, opt-in host environment routing, missing/malformed imported drivers, launcher output and argument handling, compositor driver gating, and Components navigation in portrait/landscape. The existing Python Proton regression suite also passes. + +On the Odin, install the new APK and start a fresh Linux session. If it still exits early, collect `linux-session-*.log`. Try `PROOT_NO_SECCOMP=1` only as a separate diagnostic run when that log suggests syscall failures. For a Proton game failure, include `steam-<appid>.log` and the exact game/build; compilation and AVD testing cannot establish ARM64/KGSL gameplay success. diff --git a/tools/linuxfs/build-linuxfs.sh b/tools/linuxfs/build-linuxfs.sh new file mode 100755 index 000000000..9a093620b --- /dev/null +++ b/tools/linuxfs/build-linuxfs.sh @@ -0,0 +1,175 @@ +#!/bin/bash +# Assembles the Linux runtime (files/linuxfs on the device) from Arch Linux ARM packages: +# the base rootfs, gamescope with Xwayland and Mesa, the XFCE desktop on labwc, and the WinNative +# session scripts from overlay/. No Valve software is included; winnative-steam-install fetches the +# native arm64 Steam client from Valve at first use. +# +# tools/linuxfs/build-linuxfs.sh <work dir> <output.tar.zst> +# +# Needs curl, tar, zstd, ar, python3, proot, qemu-aarch64-static, aarch64-linux-gnu-gcc/g++, meson >= 1.5 and ninja. +set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +work=${1:?work dir} +out=${2:?output tarball} +mirror=http://mirror.archlinuxarm.org/aarch64 +base_url=http://os.archlinuxarm.org/os/ArchLinuxARM-aarch64-latest.tar.gz +seeds=(gamescope mesa vulkan-freedreno xorg-xwayland xorg-xhost xorg-xrandr vulkan-tools wayland-utils + mesa-utils foot unzip dbus libpulse nss libnm curl ca-certificates fontconfig freetype2 + bash coreutils grep sed gawk which findutils glib2 libglvnd ibus libxcomposite libxdamage libxrandr + libxtst libxi ttf-dejavu openal libvdpau lsof labwc xfce4-session xfce4-panel xfdesktop thunar + xfce4-terminal xfce4-settings xfce4-appfinder mousepad adwaita-icon-theme + # An app build from before the desktop starts this file manager in its place. + pcmanfm) + +mkdir -p "$work/db" "$work/pkgs" "$work/rootfs" +cd "$work" + +for repo in core extra alarm; do + [ -s "db/$repo.db" ] || curl -fsSLo "db/$repo.db" "$mirror/$repo/$repo.db" + mkdir -p "db/x_$repo" + tar -xzf "db/$repo.db" -C "db/x_$repo" +done + +# The package closure over the repository databases. Arch Linux ARM keeps %DEPENDS% in a +# separate `depends` file, and several dependencies are virtual names (libseat, sdl2, +# xorg-server-xwayland) satisfied through %PROVIDES%. +python3 - "${seeds[@]}" > pkglist.txt <<'PY' +import os, sys, collections +pkgs, provides = {}, collections.defaultdict(list) +def strip(d): + for op in (">=", "<=", "==", ">", "<", "="): + if op in d: return d.split(op)[0] + return d +for repo in ("core", "extra", "alarm"): + base = os.path.join("db", "x_" + repo) + for entry in os.listdir(base): + fields, key = {}, None + for name in ("desc", "depends"): + path = os.path.join(base, entry, name) + if not os.path.exists(path): continue + for line in open(path, encoding="utf-8", errors="replace"): + line = line.rstrip("\n") + if line.startswith("%") and line.endswith("%"): key = line.strip("%"); fields[key] = [] + elif line == "": key = None + elif key: fields[key].append(line) + n = fields.get("NAME", [None])[0] + if not n: continue + rec = {"repo": repo, "file": fields["FILENAME"][0], + "depends": [strip(d) for d in fields.get("DEPENDS", [])], + "provides": [strip(p) for p in fields.get("PROVIDES", [])]} + pkgs[n] = rec + provides[n].append(n) + for p in rec["provides"]: provides[p].append(n) +seen, queue, missing = set(), list(sys.argv[1:]), [] +while queue: + want = queue.pop() + real = want if want in pkgs else (provides.get(want) or [None])[0] + if real is None: missing.append(want); continue + if real in seen: continue + seen.add(real) + queue.extend(pkgs[real]["depends"]) +if missing: sys.exit("unresolved: " + " ".join(missing)) +for n in sorted(seen): print(pkgs[n]["repo"] + "/" + pkgs[n]["file"]) +PY +echo "$(wc -l < pkglist.txt) packages" + +while read -r entry; do + file=${entry#*/} + # A mirror error page is not a package; fetch again rather than fail at extraction. + if ! tar -tf "pkgs/$file" >/dev/null 2>&1; then + rm -f "pkgs/$file" + curl -fsSLo "pkgs/$file" "$mirror/$entry" + tar -tf "pkgs/$file" >/dev/null + fi +done < pkglist.txt + +[ -s base.tar.gz ] || curl -fsSLo base.tar.gz "$base_url" + +rm -rf rootfs && mkdir rootfs +# The base tarball is owned by root:root with device nodes; extracted unprivileged it becomes +# the build user's, which is what proot presents on the device anyway. +tar -xzf base.tar.gz -C rootfs --no-same-owner --no-same-permissions --exclude=dev 2>/dev/null || true +while read -r entry; do + file=${entry#*/} + tar -xf "pkgs/$file" -C rootfs --no-same-owner --no-same-permissions \ + --exclude=.PKGINFO --exclude=.MTREE --exclude=.INSTALL --exclude=.BUILDINFO --exclude=.CHANGELOG +done < pkglist.txt + +chmod -R u+rwX rootfs +# Arch's Turnip only knows the msm DRM kernel driver; Android reaches the Adreno through KGSL. +# build-turnip.sh cross-builds Mesa's Turnip with the KGSL backend against this rootfs. +"$here/build-turnip.sh" "$work/turnip" "$work/rootfs" +install -m 755 "$work/turnip/libvulkan_freedreno.so" rootfs/usr/lib/libvulkan_freedreno.so +printf '{\n "ICD": {\n "api_version": "1.4.0",\n "library_path": "/usr/lib/libvulkan_freedreno.so"\n },\n "file_format_version": "1.0.0"\n}\n' \ + > rootfs/usr/share/vulkan/icd.d/freedreno_icd.json +rm -f rootfs/usr/share/vulkan/icd.d/nvidia_icd.json + +# Steam's arm64 UI (steamui.so, vgui2_s.so) still links GTK 2, which Arch no longer packages; +# Debian's build links only sonames the rootfs has, so its two libraries are enough. +gtk2_deb=libgtk2.0-0t64_2.24.33-7_arm64.deb +gtk2_sha=28b2f1622197443f07f25a93e03db1a964184946ac12f501b8221c895026d0ca +[ -s "pkgs/$gtk2_deb" ] || curl -fsSLo "pkgs/$gtk2_deb" "http://deb.debian.org/debian/pool/main/g/gtk+2.0/$gtk2_deb" +echo "$gtk2_sha pkgs/$gtk2_deb" | sha256sum -c --quiet +rm -rf gtk2 && mkdir gtk2 && (cd gtk2 && ar x "../pkgs/$gtk2_deb" && tar -xf data.tar.*) +for n in gtk gdk; do + install -m 755 "gtk2/usr/lib/aarch64-linux-gnu/lib$n-x11-2.0.so.0.2400.33" rootfs/usr/lib/ + ln -sfn "lib$n-x11-2.0.so.0.2400.33" "rootfs/usr/lib/lib$n-x11-2.0.so.0" +done + +cp -a "$here/overlay/." rootfs/ +# Preloaded into every session process: what the kernel or the app sandbox withholds, answered +# in the process itself; see preload/*.c. +mkdir -p rootfs/usr/local/lib +aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread -o rootfs/usr/local/lib/libwnsession.so "$here"/preload/*.c -ldl +# The same fake evdev layer Wine sessions use, built against glibc: controllers reach Steam and +# SDL through /dev/input nodes backed by the app's input rings. +aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare -pthread \ + -o rootfs/usr/local/lib/libwninput.so "$here/../../app/src/main/cpp/winlator/fakeinput.cpp" -ldl +aarch64-linux-gnu-strip rootfs/usr/local/lib/libwnsession.so rootfs/usr/local/lib/libwninput.so +# The app ships the same two, the Vulkan driver and the session's scripts, and refreshes them into +# an installed rootfs at every session start, so a build and the rootfs it boots never disagree +# about them. +install -Dm644 rootfs/usr/lib/libvulkan_freedreno.so "$here/../../app/src/main/assets/linuxfs/usr/lib/libvulkan_freedreno.so" +install -Dm644 rootfs/usr/local/lib/libwnsession.so "$here/../../app/src/main/assets/linuxfs/usr/local/lib/libwnsession.so" +install -Dm644 rootfs/usr/local/lib/libwninput.so "$here/../../app/src/main/assets/linuxfs/usr/local/lib/libwninput.so" +for script in "$here"/overlay/usr/local/bin/winnative-*; do + install -Dm644 "$script" "$here/../../app/src/main/assets/linuxfs/usr/local/bin/$(basename "$script")" +done +mkdir -p rootfs/dev rootfs/proc rootfs/sys rootfs/tmp rootfs/root rootfs/run/user +chmod 1777 rootfs/tmp +# The dynamic loader takes its search path from here; ldconfig cannot run without the target CPU. +printf '/usr/local/lib\n/usr/lib\n/usr/lib32\n' > rootfs/etc/ld.so.conf +rm -f rootfs/etc/ld.so.cache +# Xwayland and Steam want a machine id and a resolver. +rm -f rootfs/etc/machine-id rootfs/etc/resolv.conf +head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n' > rootfs/etc/machine-id +printf 'nameserver 8.8.8.8\nnameserver 1.1.1.1\n' > rootfs/etc/resolv.conf +printf 'root:x:0:0:root:/root:/bin/bash\n' > rootfs/etc/passwd +printf 'root:x:0:\n' > rootfs/etc/group + +# What pacman's hooks would have built: the mime database, pixbuf loader and icon caches, +# GSettings schemas and the font cache. Only these run from the rootfs, under qemu. +proot -q "$(command -v qemu-aarch64-static)" -r rootfs -w / -b /dev -b /proc /bin/bash -c ' + export PATH=/usr/bin:/bin + update-mime-database /usr/share/mime + gdk-pixbuf-query-loaders --update-cache + glib-compile-schemas /usr/share/glib-2.0/schemas + fc-cache -f + for d in /usr/share/icons/*/; do [ -f "$d/index.theme" ] && gtk-update-icon-cache -q -t -f "$d"; done + rm -rf /root/.cache' >/dev/null + +# The build's version, newest highest: the installer offers a newer archive to a runtime carrying +# an older one. The same number goes into the archive's description below. +version=$(date -u +%Y%m%d%H%M) +mkdir -p rootfs/etc/winnative +echo "$version" > rootfs/etc/winnative/version + +# Level 19 with a 128 MiB window: the same libraries recur across the tree, and the app's decoder +# accepts that window by default, so the archive shrinks while unpacking stays as fast as zstd is. +tar -C rootfs -cf - . | zstd -T0 -19 --long=27 -o "$out" --force +# What the app's installer checks before it unpacks: the archive's digest and size, and the bytes +# it writes, counting a hard-linked file once as tar stores it. +unpacked=$(find rootfs -type f -printf '%i %s\n' | sort -u | awk '{ total += $2 } END { print total }') +printf '{\n "version": %s,\n "sha256": "%s",\n "size": %s,\n "unpacked": %s\n}\n' \ + "$version" "$(sha256sum "$out" | cut -d' ' -f1)" "$(stat -c %s "$out")" "$unpacked" > "${out%.tar.zst}.json" +ls -la "$out" "${out%.tar.zst}.json" diff --git a/tools/linuxfs/build-turnip.sh b/tools/linuxfs/build-turnip.sh new file mode 100755 index 000000000..e122896db --- /dev/null +++ b/tools/linuxfs/build-turnip.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# Cross-builds Mesa's Turnip Vulkan driver for the Linux runtime with the KGSL backend, against +# the Arch Linux ARM rootfs build-linuxfs.sh assembled, and applies turnip/*.patch. +# +# tools/linuxfs/build-turnip.sh <work dir> <rootfs> -> <work dir>/libvulkan_freedreno.so +# +# Needs aarch64-linux-gnu-gcc/g++, meson >= 1.5, ninja, python3 with mako and pyyaml. +set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +work=$(mkdir -p "${1:?work dir}" && cd "$1" && pwd) +rootfs=$(cd "${2:?rootfs}" && pwd) +version=26.2.2 +sha256=eeb29ca7e56cfaa8e8a79538dcf834e3b18e501c31bef5145e959ea437cc4216 +src=$work/mesa-$version + +cd "$work" +if [ ! -f "$src/.patched" ]; then + [ -s "mesa-$version.tar.xz" ] || curl -fsSLo "mesa-$version.tar.xz" "https://archive.mesa3d.org/mesa-$version.tar.xz" + echo "$sha256 mesa-$version.tar.xz" | sha256sum -c --quiet + rm -rf "$src" && tar -xJf "mesa-$version.tar.xz" + for p in "$here"/turnip/*.patch; do + patch -d "$src" -p1 < "$p" + done + touch "$src/.patched" +fi + +cat > cross.ini <<EOF +[binaries] +c = 'aarch64-linux-gnu-gcc' +cpp = 'aarch64-linux-gnu-g++' +ar = 'aarch64-linux-gnu-ar' +strip = 'aarch64-linux-gnu-strip' +pkg-config = 'pkg-config' + +[properties] +sys_root = '$rootfs' +pkg_config_libdir = '$rootfs/usr/lib/pkgconfig:$rootfs/usr/share/pkgconfig' + +[built-in options] +c_args = ['--sysroot=$rootfs'] +cpp_args = ['--sysroot=$rootfs'] +c_link_args = ['--sysroot=$rootfs', '-L$rootfs/usr/lib'] +cpp_link_args = ['--sysroot=$rootfs', '-L$rootfs/usr/lib'] + +[host_machine] +system = 'linux' +cpu_family = 'aarch64' +cpu = 'aarch64' +endian = 'little' +EOF + +[ -f build/build.ninja ] || meson setup build "$src" --cross-file cross.ini --buildtype release \ + -Dvulkan-drivers=freedreno -Dfreedreno-kmds=msm,kgsl -Dgallium-drivers= -Dplatforms=wayland,x11 \ + -Dopengl=false -Dgbm=disabled -Dglx=disabled -Degl=disabled -Dllvm=disabled -Dvulkan-layers= -Dtools= +ninja -C build src/freedreno/vulkan/libvulkan_freedreno.so +aarch64-linux-gnu-strip -o libvulkan_freedreno.so build/src/freedreno/vulkan/libvulkan_freedreno.so +ls -la libvulkan_freedreno.so diff --git a/tools/linuxfs/directaudio/build-directaudio.sh b/tools/linuxfs/directaudio/build-directaudio.sh new file mode 100755 index 000000000..3c66a6fbb --- /dev/null +++ b/tools/linuxfs/directaudio/build-directaudio.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Builds DirectAudio's unixlib for a Linux session: The412Banner's driver, unmodified, against the +# headers of Valve's Wine 11, with wn_aaudio_client.c standing in for Android's libaaudio. The two +# PE halves are not built here - they hold no platform code, and the app takes them from the +# upstream release it already ships (app/src/main/assets/directaudio). +# +# tools/linuxfs/directaudio/build-directaudio.sh <work dir> -> <work dir>/winedirectaudio.so +# +# Needs git, widl (wine's IDL compiler, any recent version) and aarch64-linux-gnu-gcc. +set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +work=${1:?work dir} +driver_repo=https://github.com/The412Banner/directaudio +driver_tag=directaudio-v1.3.2 +wine_repo=https://github.com/ValveSoftware/wine +wine_branch=experimental_11.0 +wine_commit=c1928082ac141b7f03982eacce980a4fe4840d0b + +mkdir -p "$work" +cd "$work" +[ -d driver ] || git -c advice.detachedHead=false clone -q --depth 1 --branch "$driver_tag" "$driver_repo" driver +if [ ! -d wine ]; then + git clone -q --depth 1 --filter=blob:none --sparse --branch "$wine_branch" "$wine_repo" wine + git -C wine sparse-checkout set include dlls/mmdevapi +fi +# The unixlib's call table has to be the one Valve's mmdevapi walks; a branch that has moved on +# is fetched back to the commit this was proven against. +if [ "$(git -C wine rev-parse HEAD)" != "$wine_commit" ]; then + git -C wine fetch -q --depth 1 origin "$wine_commit" + git -C wine checkout -q "$wine_commit" +fi + +# The driver includes "../mmdevapi/unixlib.h", as it does inside a Wine tree. +rm -rf src gen +mkdir -p src/winedirectaudio.drv src/mmdevapi gen +cp driver/directaudio.c src/winedirectaudio.drv/ +cp wine/dlls/mmdevapi/unixlib.h src/mmdevapi/ + +cflags=(-fPIC -O2 -fvisibility=hidden -DWINE_UNIX_LIB -D__WINESRC__ -D_GNU_SOURCE + -I"$here/include" -Igen -Iwine/include -I"$here/../../../app/src/main/cpp/wnaudiohook") +# Wine generates its COM headers from IDL at build time; make the ones the driver reaches. +for _ in $(seq 1 64); do + missing=$(aarch64-linux-gnu-gcc "${cflags[@]}" -E src/winedirectaudio.drv/directaudio.c 2>&1 >/dev/null | + sed -n 's/.*fatal error: \(.*\)\.h: No such file.*/\1/p' | head -1 || true) + [ -n "$missing" ] || break + [ -f "wine/include/$missing.idl" ] || { echo "no header $missing.h" >&2; exit 1; } + widl -Iwine/include -h -o "gen/$missing.h" "wine/include/$missing.idl" +done + +aarch64-linux-gnu-gcc "${cflags[@]}" -Wno-attributes -c -o directaudio.o src/winedirectaudio.drv/directaudio.c +aarch64-linux-gnu-gcc "${cflags[@]}" -Wall -Wextra -c -o client.o "$here/wn_aaudio_client.c" +# Like Wine's own drivers the unixlib names ntdll.so as a dependency, which the loader satisfies +# with the one Wine already has loaded; an empty library of that name stands in for it here. +mkdir -p stub +aarch64-linux-gnu-gcc -shared -Wl,-soname,ntdll.so -o stub/ntdll.so -x c /dev/null +aarch64-linux-gnu-gcc -shared -Wl,-soname,winedirectaudio.so -Wl,--allow-shlib-undefined -Wl,--no-as-needed \ + -o winedirectaudio.so directaudio.o client.o -Lstub -l:ntdll.so -lpthread +aarch64-linux-gnu-strip winedirectaudio.so +entries=$(aarch64-linux-gnu-readelf -sW winedirectaudio.so | awk '$8 == "__wine_unix_call_funcs" { print $3 / 8 }') +echo "winedirectaudio.so: $entries unixlib entries" +ls -la winedirectaudio.so diff --git a/tools/linuxfs/directaudio/include/aaudio/AAudio.h b/tools/linuxfs/directaudio/include/aaudio/AAudio.h new file mode 100644 index 000000000..dc9e7acfe --- /dev/null +++ b/tools/linuxfs/directaudio/include/aaudio/AAudio.h @@ -0,0 +1,74 @@ +/* + * The part of Android's AAudio API the DirectAudio driver uses, for a glibc build. The values + * are AAudio's own: wn_aaudio_client.c hands them to the app, which passes them to the real + * library unchanged. + */ +#ifndef WN_AAUDIO_H +#define WN_AAUDIO_H + +#include <stdint.h> + +typedef struct AAudioStreamStruct AAudioStream; +typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder; + +typedef int32_t aaudio_result_t; +typedef int32_t aaudio_direction_t; +typedef int32_t aaudio_format_t; +typedef int32_t aaudio_sharing_mode_t; +typedef int32_t aaudio_performance_mode_t; +typedef int32_t aaudio_usage_t; +typedef int32_t aaudio_input_preset_t; +typedef int32_t aaudio_data_callback_result_t; + +enum { AAUDIO_DIRECTION_OUTPUT, AAUDIO_DIRECTION_INPUT }; +enum { AAUDIO_FORMAT_UNSPECIFIED, AAUDIO_FORMAT_PCM_I16, AAUDIO_FORMAT_PCM_FLOAT, AAUDIO_FORMAT_PCM_I24_PACKED, AAUDIO_FORMAT_PCM_I32 }; +enum { AAUDIO_SHARING_MODE_EXCLUSIVE, AAUDIO_SHARING_MODE_SHARED }; +enum { AAUDIO_PERFORMANCE_MODE_NONE = 10, AAUDIO_PERFORMANCE_MODE_POWER_SAVING, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY }; +enum { AAUDIO_USAGE_GAME = 14 }; +enum { AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION = 7 }; +enum { AAUDIO_CALLBACK_RESULT_CONTINUE, AAUDIO_CALLBACK_RESULT_STOP }; +enum { + AAUDIO_OK = 0, + AAUDIO_ERROR_DISCONNECTED = -899, + AAUDIO_ERROR_ILLEGAL_ARGUMENT = -898, + AAUDIO_ERROR_INTERNAL = -896, + AAUDIO_ERROR_INVALID_STATE = -895, + AAUDIO_ERROR_INVALID_HANDLE = -892, + AAUDIO_ERROR_UNAVAILABLE = -889, + AAUDIO_ERROR_NO_MEMORY = -887, + AAUDIO_ERROR_NULL = -886, + AAUDIO_ERROR_TIMEOUT = -885, +}; + +typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames); +typedef void (*AAudioStream_errorCallback)(AAudioStream *stream, void *userData, aaudio_result_t error); + +aaudio_result_t AAudio_createStreamBuilder(AAudioStreamBuilder **builder); +void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder *builder, int32_t sampleRate); +void AAudioStreamBuilder_setChannelCount(AAudioStreamBuilder *builder, int32_t channelCount); +void AAudioStreamBuilder_setFormat(AAudioStreamBuilder *builder, aaudio_format_t format); +void AAudioStreamBuilder_setSharingMode(AAudioStreamBuilder *builder, aaudio_sharing_mode_t sharingMode); +void AAudioStreamBuilder_setDirection(AAudioStreamBuilder *builder, aaudio_direction_t direction); +void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder *builder, int32_t numFrames); +void AAudioStreamBuilder_setPerformanceMode(AAudioStreamBuilder *builder, aaudio_performance_mode_t mode); +void AAudioStreamBuilder_setUsage(AAudioStreamBuilder *builder, aaudio_usage_t usage); +void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder *builder, aaudio_input_preset_t inputPreset); +void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder *builder, AAudioStream_dataCallback callback, void *userData); +void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder *builder, AAudioStream_errorCallback callback, void *userData); +aaudio_result_t AAudioStreamBuilder_openStream(AAudioStreamBuilder *builder, AAudioStream **stream); +aaudio_result_t AAudioStreamBuilder_delete(AAudioStreamBuilder *builder); +aaudio_result_t AAudioStream_close(AAudioStream *stream); +aaudio_result_t AAudioStream_requestStart(AAudioStream *stream); +aaudio_result_t AAudioStream_requestStop(AAudioStream *stream); +aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream *stream, int32_t numFrames); +int32_t AAudioStream_getBufferSizeInFrames(AAudioStream *stream); +int32_t AAudioStream_getFramesPerBurst(AAudioStream *stream); +int32_t AAudioStream_getBufferCapacityInFrames(AAudioStream *stream); +int32_t AAudioStream_getXRunCount(AAudioStream *stream); +int32_t AAudioStream_getSampleRate(AAudioStream *stream); +int32_t AAudioStream_getChannelCount(AAudioStream *stream); +aaudio_format_t AAudioStream_getFormat(AAudioStream *stream); +aaudio_sharing_mode_t AAudioStream_getSharingMode(AAudioStream *stream); +aaudio_performance_mode_t AAudioStream_getPerformanceMode(AAudioStream *stream); + +#endif diff --git a/tools/linuxfs/directaudio/include/android/api-level.h b/tools/linuxfs/directaudio/include/android/api-level.h new file mode 100644 index 000000000..15669f8ce --- /dev/null +++ b/tools/linuxfs/directaudio/include/android/api-level.h @@ -0,0 +1,4 @@ +#ifndef WN_ANDROID_API_LEVEL_H +#define WN_ANDROID_API_LEVEL_H +int android_get_device_api_level(void); +#endif diff --git a/tools/linuxfs/directaudio/include/android/log.h b/tools/linuxfs/directaudio/include/android/log.h new file mode 100644 index 000000000..909ceadb3 --- /dev/null +++ b/tools/linuxfs/directaudio/include/android/log.h @@ -0,0 +1,6 @@ +/* The driver logs through Android's logger; in a Linux session that is the session log. */ +#ifndef WN_ANDROID_LOG_H +#define WN_ANDROID_LOG_H +enum { ANDROID_LOG_VERBOSE = 2, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR }; +int __android_log_print(int prio, const char *tag, const char *fmt, ...) __attribute__((format(printf, 3, 4))); +#endif diff --git a/tools/linuxfs/directaudio/include/config.h b/tools/linuxfs/directaudio/include/config.h new file mode 100644 index 000000000..139faa0af --- /dev/null +++ b/tools/linuxfs/directaudio/include/config.h @@ -0,0 +1 @@ +/* Wine sources include config.h first; the driver needs nothing from it. */ diff --git a/tools/linuxfs/directaudio/wn_aaudio_client.c b/tools/linuxfs/directaudio/wn_aaudio_client.c new file mode 100644 index 000000000..8f8a4963f --- /dev/null +++ b/tools/linuxfs/directaudio/wn_aaudio_client.c @@ -0,0 +1,373 @@ +/* + * AAudio for the DirectAudio driver in a Linux session. Android's libaaudio cannot be loaded + * into a glibc process, so the app opens each stream on the driver's behalf and the PCM moves + * through a ring in shared memory; see app/src/main/cpp/wnaudiohook/wn_aaudio_protocol.h. + * + * AAudio calls a stream's data callback from a thread of its own, once a burst. Here a thread + * per stream does: it keeps an output ring a little ahead of the app's real callback, which + * wakes it each time it has taken a burst, and hands an input ring's frames on as they arrive. + */ +#include <aaudio/AAudio.h> +#include <android/api-level.h> +#include <android/log.h> +#include <errno.h> +#include <limits.h> +#include <linux/futex.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/un.h> +#include <time.h> +#include <unistd.h> + +#include "wn_aaudio_protocol.h" + +#define SOCKET_ENV "WN_DIRECTAUDIO_SOCKET" +#define LOG_ENV "BANNER_AUDIO_DIRECT_LOG" +/* How long the thread sleeps when nothing wakes it: a stopped stream, or a host gone quiet. */ +#define IDLE_WAIT_NS 20000000L +/* An output ring is kept this many bursts ahead of the host, over its largest callback. */ +#define LEAD_BURSTS 2 + +struct AAudioStreamBuilderStruct { + struct wn_audio_request request; + AAudioStream_dataCallback on_data; + AAudioStream_errorCallback on_error; + void *data_user; + void *error_user; +}; + +struct AAudioStreamStruct { + int fd; + struct wn_audio_ring *ring; + size_t map_size; + uint32_t frames; + uint32_t frame_bytes; + bool input; + struct wn_audio_reply granted; + _Atomic int32_t buffer_size; + AAudioStream_dataCallback on_data; + AAudioStream_errorCallback on_error; + void *data_user; + void *error_user; + pthread_mutex_t call_lock; /* one request and its reply at a time */ + pthread_t thread; + _Atomic bool started; + _Atomic bool flush; /* a stop leaves frames behind that a later start must not play */ + _Atomic bool closing; + _Atomic bool self_closed; /* closed from its own callback: the thread frees the stream */ + uint8_t *burst; +}; + +int android_get_device_api_level(void) { + /* The driver only asks whether usage and input presets exist (28); the app applies them + * by the device's real level. */ + return 28; +} + +int __android_log_print(int prio, const char *tag, const char *fmt, ...) { + static int enabled = -1; + if (enabled < 0) { + const char *value = getenv(LOG_ENV); + enabled = value && *value && *value != '0'; + } + if (!enabled && prio < ANDROID_LOG_WARN) return 0; + va_list args; + va_start(args, fmt); + fprintf(stderr, "%s: ", tag); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + va_end(args); + return 1; +} + +static bool call(AAudioStream *stream, const struct wn_audio_request *request, struct wn_audio_reply *reply, int *passed_fd) { + union { + char buffer[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } control; + struct iovec iov = { .iov_base = reply, .iov_len = sizeof(*reply) }; + struct msghdr message = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = control.buffer, .msg_controllen = sizeof(control.buffer) }; + bool ok = false; + + pthread_mutex_lock(&stream->call_lock); + ssize_t n; + do { + n = send(stream->fd, request, sizeof(*request), MSG_NOSIGNAL); + } while (n < 0 && errno == EINTR); + if (n == (ssize_t)sizeof(*request)) { + do { + n = recvmsg(stream->fd, &message, MSG_WAITALL | MSG_CMSG_CLOEXEC); + } while (n < 0 && errno == EINTR); + ok = n == (ssize_t)sizeof(*reply); + } + pthread_mutex_unlock(&stream->call_lock); + if (!ok) return false; + + if (passed_fd) { + *passed_fd = -1; + struct cmsghdr *header = CMSG_FIRSTHDR(&message); + if (header && header->cmsg_level == SOL_SOCKET && header->cmsg_type == SCM_RIGHTS) + memcpy(passed_fd, CMSG_DATA(header), sizeof(int)); + } + return true; +} + +static aaudio_result_t simple_call(AAudioStream *stream, uint32_t op, int32_t value, struct wn_audio_reply *reply) { + struct wn_audio_request request = { .magic = WN_AUDIO_MAGIC, .op = op, .value = value }; + struct wn_audio_reply local; + if (!reply) reply = &local; + if (!call(stream, &request, reply, NULL)) return AAUDIO_ERROR_DISCONNECTED; + return reply->result; +} + +static void wait_for_host(struct wn_audio_ring *ring, uint32_t seen) { + struct timespec timeout = { .tv_sec = 0, .tv_nsec = IDLE_WAIT_NS }; + syscall(SYS_futex, &ring->wake, FUTEX_WAIT, seen, &timeout, NULL, 0); +} + +static void ring_write(AAudioStream *stream, uint32_t pos, uint32_t count) { + uint8_t *data = WN_AUDIO_RING_DATA(stream->ring); + uint32_t at = pos & (stream->frames - 1); + uint32_t first = count < stream->frames - at ? count : stream->frames - at; + memcpy(data + (size_t)at * stream->frame_bytes, stream->burst, (size_t)first * stream->frame_bytes); + memcpy(data, stream->burst + (size_t)first * stream->frame_bytes, (size_t)(count - first) * stream->frame_bytes); +} + +static void ring_read(AAudioStream *stream, uint32_t pos, uint32_t count) { + const uint8_t *data = WN_AUDIO_RING_DATA(stream->ring); + uint32_t at = pos & (stream->frames - 1); + uint32_t first = count < stream->frames - at ? count : stream->frames - at; + memcpy(stream->burst, data + (size_t)at * stream->frame_bytes, (size_t)first * stream->frame_bytes); + memcpy(stream->burst + (size_t)first * stream->frame_bytes, data, (size_t)(count - first) * stream->frame_bytes); +} + +/* Returns false once the driver's callback asks for the stream to stop. */ +static bool pump(AAudioStream *stream) { + struct wn_audio_ring *ring = stream->ring; + uint32_t burst = (uint32_t)stream->granted.frames_per_burst; + + if (stream->input) { + for (;;) { + uint32_t read_pos = atomic_load(&ring->read_pos); + uint32_t held = atomic_load(&ring->write_pos) - read_pos; + if (held > stream->frames) held = stream->frames; + if (held == 0) return true; + uint32_t count = held < burst ? held : burst; + ring_read(stream, read_pos, count); + atomic_store(&ring->read_pos, read_pos + count); + if (stream->on_data(stream, stream->data_user, stream->burst, (int32_t)count) != AAUDIO_CALLBACK_RESULT_CONTINUE) + return false; + } + } + + uint32_t lead = atomic_load(&ring->callback_frames); + if (lead < burst) lead = burst; + lead += burst * LEAD_BURSTS; + if (lead > stream->frames) lead = stream->frames; + for (;;) { + uint32_t write_pos = atomic_load(&ring->write_pos); + uint32_t held = write_pos - atomic_load(&ring->read_pos); + if (held + burst > lead) return true; + if (stream->on_data(stream, stream->data_user, stream->burst, (int32_t)burst) != AAUDIO_CALLBACK_RESULT_CONTINUE) + return false; + ring_write(stream, write_pos, burst); + atomic_store(&ring->write_pos, write_pos + burst); + } +} + +static void release(AAudioStream *stream) { + if (stream->ring) munmap(stream->ring, stream->map_size); + if (stream->fd >= 0) close(stream->fd); + pthread_mutex_destroy(&stream->call_lock); + free(stream->burst); + free(stream); +} + +static void *stream_thread(void *arg) { + AAudioStream *stream = arg; + struct wn_audio_ring *ring = stream->ring; + bool failed = false; + + setpriority(PRIO_PROCESS, (id_t)syscall(SYS_gettid), -16); + while (!atomic_load(&stream->closing)) { + uint32_t seen = atomic_load(&ring->wake); + int32_t error = atomic_load(&ring->error); + /* Only this thread moves the position it empties the ring with. */ + if (atomic_exchange(&stream->flush, false)) { + if (stream->input) atomic_store(&ring->read_pos, atomic_load(&ring->write_pos)); + else atomic_store(&ring->write_pos, atomic_load(&ring->read_pos)); + } + if (error && !failed) { + /* As with AAudio, the stream is dead from here and the driver closes it. */ + failed = true; + if (stream->on_error) stream->on_error(stream, stream->error_user, error); + continue; + } + if (!failed && atomic_load(&stream->started) && !pump(stream)) atomic_store(&stream->started, false); + wait_for_host(ring, seen); + } + if (atomic_load(&stream->self_closed)) release(stream); + return NULL; +} + +aaudio_result_t AAudio_createStreamBuilder(AAudioStreamBuilder **builder) { + if (!builder) return AAUDIO_ERROR_NULL; + AAudioStreamBuilder *created = calloc(1, sizeof(*created)); + if (!created) return AAUDIO_ERROR_NO_MEMORY; + created->request.magic = WN_AUDIO_MAGIC; + created->request.op = WN_AUDIO_OPEN; + created->request.direction = AAUDIO_DIRECTION_OUTPUT; + created->request.sharing_mode = AAUDIO_SHARING_MODE_SHARED; + *builder = created; + return AAUDIO_OK; +} + +void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder *builder, int32_t sampleRate) { builder->request.sample_rate = sampleRate; } +void AAudioStreamBuilder_setChannelCount(AAudioStreamBuilder *builder, int32_t channelCount) { builder->request.channels = channelCount; } +void AAudioStreamBuilder_setFormat(AAudioStreamBuilder *builder, aaudio_format_t format) { builder->request.format = format; } +void AAudioStreamBuilder_setSharingMode(AAudioStreamBuilder *builder, aaudio_sharing_mode_t sharingMode) { builder->request.sharing_mode = sharingMode; } +void AAudioStreamBuilder_setDirection(AAudioStreamBuilder *builder, aaudio_direction_t direction) { builder->request.direction = direction; } +void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder *builder, int32_t numFrames) { builder->request.buffer_capacity = numFrames; } +void AAudioStreamBuilder_setPerformanceMode(AAudioStreamBuilder *builder, aaudio_performance_mode_t mode) { builder->request.performance_mode = mode; } +void AAudioStreamBuilder_setUsage(AAudioStreamBuilder *builder, aaudio_usage_t usage) { builder->request.usage = usage; } +void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder *builder, aaudio_input_preset_t inputPreset) { builder->request.input_preset = inputPreset; } + +void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder *builder, AAudioStream_dataCallback callback, void *userData) { + builder->on_data = callback; + builder->data_user = userData; +} + +void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder *builder, AAudioStream_errorCallback callback, void *userData) { + builder->on_error = callback; + builder->error_user = userData; +} + +aaudio_result_t AAudioStreamBuilder_delete(AAudioStreamBuilder *builder) { + free(builder); + return AAUDIO_OK; +} + +aaudio_result_t AAudioStreamBuilder_openStream(AAudioStreamBuilder *builder, AAudioStream **out) { + struct sockaddr_un address = { .sun_family = AF_UNIX }; + const char *path = getenv(SOCKET_ENV); + int ring_fd = -1; + + if (!builder || !out) return AAUDIO_ERROR_NULL; + /* The driver only streams through callbacks; AAudio's blocking read and write are not here. */ + if (!builder->on_data) return AAUDIO_ERROR_ILLEGAL_ARGUMENT; + if (!path || strlen(path) >= sizeof(address.sun_path)) return AAUDIO_ERROR_UNAVAILABLE; + strcpy(address.sun_path, path); + + AAudioStream *stream = calloc(1, sizeof(*stream)); + if (!stream) return AAUDIO_ERROR_NO_MEMORY; + pthread_mutex_init(&stream->call_lock, NULL); + stream->input = builder->request.direction == AAUDIO_DIRECTION_INPUT; + stream->on_data = builder->on_data; + stream->on_error = builder->on_error; + stream->data_user = builder->data_user; + stream->error_user = builder->error_user; + stream->fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (stream->fd < 0 || connect(stream->fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + release(stream); + return AAUDIO_ERROR_UNAVAILABLE; + } + + aaudio_result_t result = AAUDIO_ERROR_DISCONNECTED; + if (call(stream, &builder->request, &stream->granted, &ring_fd)) result = stream->granted.result; + if (result == AAUDIO_OK && ring_fd < 0) result = AAUDIO_ERROR_INTERNAL; + if (result != AAUDIO_OK) { + if (ring_fd >= 0) close(ring_fd); + release(stream); + return result; + } + + struct wn_audio_ring header; + bool mapped = pread(ring_fd, &header, sizeof(header), 0) == (ssize_t)sizeof(header) && + header.frames >= (uint32_t)stream->granted.frames_per_burst && header.frame_bytes > 0 && + (header.frames & (header.frames - 1)) == 0; + if (mapped) { + stream->frames = header.frames; + stream->frame_bytes = header.frame_bytes; + stream->map_size = sizeof(header) + (size_t)header.frames * header.frame_bytes; + void *map = mmap(NULL, stream->map_size, PROT_READ | PROT_WRITE, MAP_SHARED, ring_fd, 0); + mapped = map != MAP_FAILED; + if (mapped) stream->ring = map; + } + close(ring_fd); + if (mapped) stream->burst = malloc((size_t)stream->granted.frames_per_burst * stream->frame_bytes); + if (!mapped || !stream->burst) { + release(stream); + return AAUDIO_ERROR_NO_MEMORY; + } + atomic_store(&stream->buffer_size, stream->granted.buffer_size); + if (pthread_create(&stream->thread, NULL, stream_thread, stream) != 0) { + release(stream); + return AAUDIO_ERROR_NO_MEMORY; + } + *out = stream; + return AAUDIO_OK; +} + +aaudio_result_t AAudioStream_requestStart(AAudioStream *stream) { + if (!stream) return AAUDIO_ERROR_INVALID_HANDLE; + aaudio_result_t result = simple_call(stream, WN_AUDIO_START, 0, NULL); + if (result == AAUDIO_OK) { + atomic_store(&stream->started, true); + syscall(SYS_futex, &stream->ring->wake, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + } + return result; +} + +aaudio_result_t AAudioStream_requestStop(AAudioStream *stream) { + if (!stream) return AAUDIO_ERROR_INVALID_HANDLE; + atomic_store(&stream->started, false); + aaudio_result_t result = simple_call(stream, WN_AUDIO_STOP, 0, NULL); + atomic_store(&stream->flush, true); + return result; +} + +aaudio_result_t AAudioStream_close(AAudioStream *stream) { + if (!stream) return AAUDIO_ERROR_INVALID_HANDLE; + atomic_store(&stream->started, false); + simple_call(stream, WN_AUDIO_CLOSE, 0, NULL); + /* AAudio forbids closing a stream from its own callback; a driver that does so anyway + * gets a thread that ends by itself rather than one that waits for itself. */ + if (pthread_equal(pthread_self(), stream->thread)) { + atomic_store(&stream->self_closed, true); + atomic_store(&stream->closing, true); + pthread_detach(stream->thread); + return AAUDIO_OK; + } + atomic_store(&stream->closing, true); + syscall(SYS_futex, &stream->ring->wake, FUTEX_WAKE, INT_MAX, NULL, NULL, 0); + pthread_join(stream->thread, NULL); + release(stream); + return AAUDIO_OK; +} + +aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream *stream, int32_t numFrames) { + struct wn_audio_reply reply; + if (!stream) return AAUDIO_ERROR_INVALID_HANDLE; + aaudio_result_t result = simple_call(stream, WN_AUDIO_SET_BUFFER_SIZE, numFrames, &reply); + if (result != AAUDIO_OK) return result; + atomic_store(&stream->buffer_size, reply.buffer_size); + return reply.buffer_size; +} + +int32_t AAudioStream_getBufferSizeInFrames(AAudioStream *stream) { return atomic_load(&stream->buffer_size); } +int32_t AAudioStream_getFramesPerBurst(AAudioStream *stream) { return stream->granted.frames_per_burst; } +int32_t AAudioStream_getBufferCapacityInFrames(AAudioStream *stream) { return stream->granted.buffer_capacity; } +int32_t AAudioStream_getXRunCount(AAudioStream *stream) { return atomic_load(&stream->ring->xruns); } +int32_t AAudioStream_getSampleRate(AAudioStream *stream) { return stream->granted.sample_rate; } +int32_t AAudioStream_getChannelCount(AAudioStream *stream) { return stream->granted.channels; } +aaudio_format_t AAudioStream_getFormat(AAudioStream *stream) { return stream->granted.format; } +aaudio_sharing_mode_t AAudioStream_getSharingMode(AAudioStream *stream) { return stream->granted.sharing_mode; } +aaudio_performance_mode_t AAudioStream_getPerformanceMode(AAudioStream *stream) { return stream->granted.performance_mode; } diff --git a/tools/linuxfs/overlay/etc/ld.so.preload b/tools/linuxfs/overlay/etc/ld.so.preload new file mode 100644 index 000000000..c0cdbba20 --- /dev/null +++ b/tools/linuxfs/overlay/etc/ld.so.preload @@ -0,0 +1,2 @@ +/usr/local/lib/libwnsession.so +/usr/local/lib/libwninput.so diff --git a/tools/linuxfs/overlay/etc/winnative/empty/.keep b/tools/linuxfs/overlay/etc/winnative/empty/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/tools/linuxfs/overlay/etc/winnative/proc/cap_last_cap b/tools/linuxfs/overlay/etc/winnative/proc/cap_last_cap new file mode 100644 index 000000000..425151f3a --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/cap_last_cap @@ -0,0 +1 @@ +40 diff --git a/tools/linuxfs/overlay/etc/winnative/proc/loadavg b/tools/linuxfs/overlay/etc/winnative/proc/loadavg new file mode 100644 index 000000000..376825638 --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/loadavg @@ -0,0 +1 @@ +0.00 0.00 0.00 1/100 1 diff --git a/tools/linuxfs/overlay/etc/winnative/proc/overflowgid b/tools/linuxfs/overlay/etc/winnative/proc/overflowgid new file mode 100644 index 000000000..e8f3792bb --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/overflowgid @@ -0,0 +1 @@ +65534 diff --git a/tools/linuxfs/overlay/etc/winnative/proc/overflowuid b/tools/linuxfs/overlay/etc/winnative/proc/overflowuid new file mode 100644 index 000000000..e8f3792bb --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/overflowuid @@ -0,0 +1 @@ +65534 diff --git a/tools/linuxfs/overlay/etc/winnative/proc/stat b/tools/linuxfs/overlay/etc/winnative/proc/stat new file mode 100644 index 000000000..9ea5a5eeb --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/stat @@ -0,0 +1,8 @@ +cpu 0 0 0 0 0 0 0 0 0 0 +cpu0 0 0 0 0 0 0 0 0 0 0 +intr 0 +ctxt 0 +btime 0 +processes 1 +procs_running 1 +procs_blocked 0 diff --git a/tools/linuxfs/overlay/etc/winnative/proc/uptime b/tools/linuxfs/overlay/etc/winnative/proc/uptime new file mode 100644 index 000000000..d8ccf3fa0 --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/uptime @@ -0,0 +1 @@ +100.00 100.00 diff --git a/tools/linuxfs/overlay/etc/winnative/proc/version b/tools/linuxfs/overlay/etc/winnative/proc/version new file mode 100644 index 000000000..d57753c5f --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/version @@ -0,0 +1 @@ +Linux version 6.1.0 (winnative@android) (gcc version 12.0) #1 SMP PREEMPT diff --git a/tools/linuxfs/overlay/etc/winnative/proc/vmstat b/tools/linuxfs/overlay/etc/winnative/proc/vmstat new file mode 100644 index 000000000..f13797199 --- /dev/null +++ b/tools/linuxfs/overlay/etc/winnative/proc/vmstat @@ -0,0 +1 @@ +nr_free_pages 0 diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-directaudio b/tools/linuxfs/overlay/usr/local/bin/winnative-directaudio new file mode 100755 index 000000000..1697ecc62 --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-directaudio @@ -0,0 +1,186 @@ +#!/usr/bin/python3 +# Points the Windows games the Steam client runs at DirectAudio, or back at Wine's own choice: +# winnative-directaudio <steam root> on|off +# +# The app stages the driver under /usr/local/share/winnative/directaudio. It goes into every +# Proton whose mmdevapi walks the same unixlib call table - a driver built for another table +# would crash the game at its first sound - and a prefix is only told to use it when the Proton +# that runs it took it: mmdevapi has no fallback once the registry names a driver, and a title +# that opens audio while loading then never starts. +import glob +import os +import shutil +import struct +import sys +import time + +STAGE = "/usr/local/share/winnative/directaudio" +UNIXLIB = "aarch64-unix/winedirectaudio.so" +HALVES = {"aarch64-windows/winedirectaudio.drv": "system32", "i386-windows/winedirectaudio.drv": "syswow64"} +SECTION = b"[Software\\\\Wine\\\\Drivers]" +VALUE = b'"Audio"="directaudio"' + + +def call_table_entries(path): + """The length of an ELF64 unixlib's __wine_unix_call_funcs, or 0 when it cannot be read.""" + try: + with open(path, "rb") as f: + data = f.read() + if data[:5] != b"\x7fELF\x02": + return 0 + shoff, = struct.unpack_from("<Q", data, 0x28) + shentsize, shnum = struct.unpack_from("<HH", data, 0x3A) + sections = [struct.unpack_from("<IIQQQQIIQQ", data, shoff + i * shentsize) for i in range(shnum)] + for section in sections: + if section[1] != 11: # SHT_DYNSYM + continue + strings = sections[section[6]][4] + for at in range(section[4], section[4] + section[5], 24): + name, _, _, _, _, size = struct.unpack_from("<IBBHQQ", data, at) + end = data.index(b"\0", strings + name) + if data[strings + name:end] == b"__wine_unix_call_funcs": + return size // 8 + except (OSError, ValueError, IndexError, struct.error): + pass + return 0 + + +def same(a, b): + try: + with open(a, "rb") as fa, open(b, "rb") as fb: + return fa.read() == fb.read() + except OSError: + return False + + +def place(source, target): + if same(source, target): + return + staged = target + ".staged" + shutil.copyfile(source, staged) + os.chmod(staged, 0o755) + os.replace(staged, target) + + +def install(files, wanted): + """Puts the driver into the Proton at `files`; False when that Proton cannot take it.""" + wine = os.path.join(files, "lib/wine") + if call_table_entries(os.path.join(wine, "aarch64-unix/winealsa.so")) != wanted: + return False + try: + for part in (UNIXLIB, *HALVES): + if not os.path.isdir(os.path.join(wine, os.path.dirname(part))): + return False + for part in (UNIXLIB, *HALVES): + place(os.path.join(STAGE, part), os.path.join(wine, part)) + except OSError as e: + print(f"== directaudio: {files}: {e}") + return False + return True + + +def set_driver(user_reg, enabled): + """Writes or removes the Audio value; the file keeps its time, which Proton compares.""" + try: + with open(user_reg, "rb") as f: + lines = f.read().split(b"\n") + except OSError: + return + start = next((i for i, line in enumerate(lines) if line.startswith(SECTION)), None) + if start is None: + if not enabled: + return + edited = lines + [SECTION + b" %d" % int(time.time()), VALUE, b""] + else: + end = next((i for i in range(start + 1, len(lines)) if not lines[i].strip()), len(lines)) + body = [line for line in lines[start + 1:end] if not line.startswith(b'"Audio"=') or (not enabled and line.strip() != VALUE)] + if enabled: + body.append(VALUE) + edited = lines[:start + 1] + body + lines[end:] + if edited == lines: + return + stat = os.stat(user_reg) + staged = user_reg + ".staged" + with open(staged, "wb") as f: + f.write(b"\n".join(edited)) + os.replace(staged, user_reg) + os.utime(user_reg, ns=(stat.st_atime_ns, stat.st_mtime_ns)) + print(f"== directaudio {'on' if enabled else 'off'}: {user_reg}") + + +def proton_of(prefix): + """The `files` directory of the Proton a compatdata prefix was last set up by.""" + try: + with open(os.path.join(os.path.dirname(prefix), "config_info")) as f: + for line in f: + line = line.strip() + if line.endswith("/files/lib/") or line.endswith("/files/lib"): + return os.path.realpath(os.path.dirname(line.rstrip("/"))) + except OSError: + pass + return None + + +def main(): + steam_root, enabled = sys.argv[1], sys.argv[2] == "on" + wanted = call_table_entries(os.path.join(STAGE, UNIXLIB)) if enabled else 0 + ready = {} + + def usable(files): + if not enabled or not wanted or not files: + return False + if files not in ready: + ready[files] = install(files, wanted) + print(f"== directaudio: {files}: {'installed' if ready[files] else 'not taken'}") + return ready[files] + + prefixes = [] + proton_files = glob.glob(steam_root + "/steamapps/common/Proton*/files") + proton_files += glob.glob(steam_root + "/compatibilitytools.d/*/files") + proton_files.append("/opt/winnative-proton/files") + for files in proton_files: + files = os.path.realpath(files) + for template in glob.glob(files + "/share/default_pfx*"): + prefixes.append((template, files)) + for library in (steam_root, "/mnt/winnative"): + for prefix in glob.glob(library + "/steamapps/compatdata/*/pfx"): + prefixes.append((prefix, proton_of(prefix))) + + for prefix, files in prefixes: + user_reg = os.path.join(prefix, "user.reg") + if not os.path.isfile(user_reg): + continue + on = usable(files) + prepare_prefix(prefix, on) + + +def prepare_prefix(prefix, on): + if on: + try: + for part, directory in HALVES.items(): + target = os.path.join(prefix, "drive_c/windows", directory) + if os.path.isdir(target): + place(os.path.join(STAGE, part), os.path.join(target, "winedirectaudio.drv")) + except OSError as e: + print(f"== directaudio: {prefix}: {e}") + on = False + set_driver(os.path.join(prefix, "user.reg"), on) + + +def prepare_launch(files, compatdata, enabled): + wanted = call_table_entries(os.path.join(STAGE, UNIXLIB)) if enabled else 0 + on = bool(wanted and install(files, wanted)) + print(f"== directaudio: {files}: {'on' if on else 'off'}") + for template in glob.glob(files + "/share/default_pfx*"): + prepare_prefix(template, on) + if compatdata: + prepare_prefix(os.path.join(compatdata, "pfx"), on) + + +if __name__ == "__main__": + if len(sys.argv) == 5 and sys.argv[1] == "--prepare" and sys.argv[4] in ("on", "off"): + prepare_launch(sys.argv[2], sys.argv[3], sys.argv[4] == "on") + elif len(sys.argv) == 3 and sys.argv[2] in ("on", "off"): + main() + else: + sys.exit("usage: winnative-directaudio <steam root> on|off | --prepare <files> <compatdata> on|off") diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-epic-launch b/tools/linuxfs/overlay/usr/local/bin/winnative-epic-launch new file mode 100644 index 000000000..919a369f3 --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-epic-launch @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +# Runs an Epic game the native Steam client started, signed in as the app's Epic account: +# winnative-epic-launch <proton> <verb> <program> [argument ...] +# +# The shortcut the app writes names the game in WN_EPIC. Epic signs a game in with an exchange +# code that lasts five minutes and is spent the first time it is used, so the command line cannot +# be written into the shortcut ahead of time - it is asked for here, as the game starts. +# +# A game whose sign-in cannot be had is started anyway: one that keeps its own session, or that +# plays offline, still runs, and a title that insists on Epic says so itself. Refusing to start +# would leave the user with a library entry that does nothing. +import os +import socket +import sys + +ENDPOINT = "/run/winnative-epic/endpoint" +TIMEOUT = 60 + + +def sign_in(app_name): + """The game's Epic command line, or [] when the app cannot supply one.""" + try: + with open(ENDPOINT, encoding="utf-8") as f: + port, secret = f.read().split() + except (OSError, ValueError): + print("== epic: the app is not signed in to Epic; starting %s without it" % app_name, file=sys.stderr) + return [] + try: + with socket.create_connection(("127.0.0.1", int(port)), TIMEOUT) as s: + s.settimeout(TIMEOUT) + s.sendall(("%s %s\n" % (secret, app_name)).encode("utf-8")) + reply = b"" + while True: + part = s.recv(4096) + if not part: + break + reply += part + except (OSError, ValueError) as e: + print("== epic: could not reach the app to sign %s in: %s" % (app_name, e), file=sys.stderr) + return [] + lines = reply.decode("utf-8", "replace").splitlines() + if not lines or lines[0] != "ok": + print("== epic: %s was not signed in: %s" % (app_name, lines[0] if lines else "no answer"), file=sys.stderr) + return [] + print("== epic: %s signed in" % app_name, file=sys.stderr) + return lines[1:] + + +def main(): + proton, args = sys.argv[1], sys.argv[2:] + app_name = os.environ.get("WN_EPIC", "") + # The variable is the app's own, and Proton passes the environment on to the game. + os.environ.pop("WN_EPIC", None) + os.execv(proton, [proton] + args + (sign_in(app_name) if app_name else [])) + + +if __name__ == "__main__": + if len(sys.argv) < 3: + sys.exit("usage: winnative-epic-launch <proton> <verb> <program> [argument ...]") + main() diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-netmanager b/tools/linuxfs/overlay/usr/local/bin/winnative-netmanager new file mode 100644 index 000000000..3f9f94051 --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-netmanager @@ -0,0 +1,243 @@ +#!/usr/bin/python3 +# Answers for NetworkManager on the runtime's system bus, for the Steam client: +# winnative-netmanager +# +# The client's network page and its connection indicator are built on NetworkManager. It asks for +# one over the system bus as it starts, and without an answer it leaves out half of what its +# interface then calls: Settings > Internet stops with "StartScanningForNetworks is not a function" +# and the header shows no connection while downloads run. +# +# The network here is Android's: the app has no say in Wi-Fi, and nothing in this runtime could +# scan or join one. So this reports what is true and no more - one connection that is up while +# Android has a route out, under the address the app was given - and refuses what it cannot do. +import socket +import sys + +import gi + +gi.require_version("Gio", "2.0") +from gi.repository import Gio, GLib # noqa: E402 + +NAME = "org.freedesktop.NetworkManager" +ROOT = "/org/freedesktop/NetworkManager" +SETTINGS = ROOT + "/Settings" +DEVICE = ROOT + "/Devices/1" +ACTIVE = ROOT + "/ActiveConnection/1" +IP4 = ROOT + "/IP4Config/1" +PROFILE = SETTINGS + "/1" +UUID = "b1d0c8e4-57f0-4c0e-9d60-77696e6e6174" +CHECK_SECONDS = 10 + +# NetworkManager's own numbers. +NM_STATE_DISCONNECTED, NM_STATE_CONNECTED_GLOBAL = 20, 70 +NM_CONNECTIVITY_NONE, NM_CONNECTIVITY_FULL = 1, 4 +NM_DEVICE_TYPE_ETHERNET = 1 +NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_ACTIVATED = 30, 100 +NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2 + +INTROSPECTION = """ +<node> + <interface name="org.freedesktop.DBus.ObjectManager"> + <method name="GetManagedObjects"> + <arg type="a{oa{sa{sv}}}" direction="out"/> + </method> + </interface> + <interface name="org.freedesktop.NetworkManager"> + <method name="GetPermissions"><arg type="a{ss}" direction="out"/></method> + <method name="CheckConnectivity"><arg type="u" direction="out"/></method> + <method name="GetDevices"><arg type="ao" direction="out"/></method> + <method name="GetAllDevices"><arg type="ao" direction="out"/></method> + <signal name="StateChanged"><arg type="u"/></signal> + </interface> + <interface name="org.freedesktop.NetworkManager.Settings.Connection"> + <method name="GetSettings"><arg type="a{sa{sv}}" direction="out"/></method> + </interface> + <interface name="org.freedesktop.NetworkManager.Device"> + <signal name="StateChanged"><arg type="u"/><arg type="u"/><arg type="u"/></signal> + </interface> +</node> +""" + + +def address(): + """The address Android routes out by, or None without a route. Nothing is sent.""" + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("192.0.2.1", 9)) + return s.getsockname()[0] + except OSError: + return None + + +def v(signature, value): + return GLib.Variant(signature, value) + + +def objects(ip): + """Every object NetworkManager would export, as its ObjectManager describes them.""" + up = ip is not None + found = { + ROOT: {NAME: { + "Devices": v("ao", [DEVICE]), + "AllDevices": v("ao", [DEVICE]), + "ActiveConnections": v("ao", [ACTIVE] if up else []), + "PrimaryConnection": v("o", ACTIVE if up else "/"), + "PrimaryConnectionType": v("s", "802-3-ethernet" if up else ""), + "ActivatingConnection": v("o", "/"), + "NetworkingEnabled": v("b", True), + "WirelessEnabled": v("b", False), + "WirelessHardwareEnabled": v("b", False), + "WwanEnabled": v("b", False), + "WwanHardwareEnabled": v("b", False), + "Startup": v("b", False), + "Version": v("s", "1.46.0"), + "State": v("u", NM_STATE_CONNECTED_GLOBAL if up else NM_STATE_DISCONNECTED), + "Connectivity": v("u", NM_CONNECTIVITY_FULL if up else NM_CONNECTIVITY_NONE), + "ConnectivityCheckAvailable": v("b", False), + "ConnectivityCheckEnabled": v("b", False), + "Metered": v("u", 0), + }}, + SETTINGS: {NAME + ".Settings": { + "Connections": v("ao", [PROFILE]), + "Hostname": v("s", socket.gethostname()), + "CanModify": v("b", False), + }}, + PROFILE: {NAME + ".Settings.Connection": { + "Unsaved": v("b", False), + "Flags": v("u", 0), + "Filename": v("s", ""), + }}, + DEVICE: { + NAME + ".Device": { + "Interface": v("s", "android"), + "IpInterface": v("s", "android" if up else ""), + "Driver": v("s", "android"), + "DeviceType": v("u", NM_DEVICE_TYPE_ETHERNET), + "State": v("u", NM_DEVICE_STATE_ACTIVATED if up else NM_DEVICE_STATE_DISCONNECTED), + "StateReason": v("(uu)", (NM_DEVICE_STATE_ACTIVATED if up else NM_DEVICE_STATE_DISCONNECTED, 0)), + "ActiveConnection": v("o", ACTIVE if up else "/"), + "Ip4Config": v("o", IP4 if up else "/"), + "Ip6Config": v("o", "/"), + "Dhcp4Config": v("o", "/"), + "Dhcp6Config": v("o", "/"), + "AvailableConnections": v("ao", [PROFILE]), + "Managed": v("b", True), + "Autoconnect": v("b", True), + "Real": v("b", True), + "Capabilities": v("u", 1), + "Mtu": v("u", 1500), + "HwAddress": v("s", ""), + }, + NAME + ".Device.Wired": { + "HwAddress": v("s", ""), + "PermHwAddress": v("s", ""), + "Speed": v("u", 0), + "Carrier": v("b", up), + }, + }, + } + if up: + found[ACTIVE] = {NAME + ".Connection.Active": { + "Connection": v("o", PROFILE), + "SpecificObject": v("o", "/"), + "Id": v("s", "Android"), + "Uuid": v("s", UUID), + "Type": v("s", "802-3-ethernet"), + "Devices": v("ao", [DEVICE]), + "State": v("u", NM_ACTIVE_CONNECTION_STATE_ACTIVATED), + "StateFlags": v("u", 0), + "Default": v("b", True), + "Ip4Config": v("o", IP4), + "Dhcp4Config": v("o", "/"), + "Default6": v("b", False), + "Ip6Config": v("o", "/"), + "Dhcp6Config": v("o", "/"), + "Vpn": v("b", False), + "Master": v("o", "/"), + }} + found[IP4] = {NAME + ".IP4Config": { + # The one address the app can see; the prefix says only that much is known. + "AddressData": v("aa{sv}", [{"address": v("s", ip), "prefix": v("u", 32)}]), + "Addresses": v("aau", []), + "Gateway": v("s", ""), + "RouteData": v("aa{sv}", []), + "Routes": v("aau", []), + "NameserverData": v("aa{sv}", []), + "Nameservers": v("au", []), + "Domains": v("as", []), + "Searches": v("as", []), + "DnsOptions": v("as", []), + "DnsPriority": v("i", 0), + "WinsServerData": v("as", []), + "WinsServers": v("au", []), + }} + return found + + +class NetworkManager: + def __init__(self, bus): + self.bus = bus + self.ip = address() + node = Gio.DBusNodeInfo.new_for_xml(INTROSPECTION) + manager, root, profile, device = node.interfaces + for path, interface in (("/org/freedesktop", manager), (ROOT, root), (PROFILE, profile), (DEVICE, device)): + bus.register_object_with_closures2(path, interface, self.call, None, None) + GLib.timeout_add_seconds(CHECK_SECONDS, self.check) + + def call(self, bus, sender, path, interface, method, args, invocation): + if method == "GetManagedObjects": + invocation.return_value(v("(a{oa{sa{sv}}})", (objects(self.ip),))) + elif method == "GetPermissions": + invocation.return_value(v("(a{ss})", ({},))) + elif method == "CheckConnectivity": + self.check() + invocation.return_value(v("(u)", (NM_CONNECTIVITY_FULL if self.ip else NM_CONNECTIVITY_NONE,))) + elif method == "GetSettings": + # The name the client shows the connection under. + invocation.return_value(v("(a{sa{sv}})", ({"connection": { + "id": v("s", "Android"), "uuid": v("s", UUID), "type": v("s", "802-3-ethernet")}},))) + else: + invocation.return_value(v("(ao)", ([DEVICE],))) + + def check(self): + """Tells the bus when Android gains or loses its route, as NetworkManager would.""" + ip = address() + if (ip is None) != (self.ip is None) or (ip and ip != self.ip): + before, after = objects(self.ip), objects(ip) + self.ip = ip + for path in before.keys() - after.keys(): + self.announce("InterfacesRemoved", path, v("(oas)", (path, list(before[path])))) + for path in after.keys() - before.keys(): + self.announce("InterfacesAdded", path, v("(oa{sa{sv}})", (path, after[path]))) + for path in after.keys() & before.keys(): + for interface, values in after[path].items(): + self.bus.emit_signal(None, path, "org.freedesktop.DBus.Properties", "PropertiesChanged", + v("(sa{sv}as)", (interface, values, []))) + state = after[ROOT][NAME]["State"].unpack() + device = after[DEVICE][NAME + ".Device"]["State"].unpack() + was = before[DEVICE][NAME + ".Device"]["State"].unpack() + self.bus.emit_signal(None, ROOT, NAME, "StateChanged", v("(u)", (state,))) + self.bus.emit_signal(None, DEVICE, NAME + ".Device", "StateChanged", v("(uuu)", (device, was, 0))) + return True + + def announce(self, signal, path, body): + self.bus.emit_signal(None, "/org/freedesktop", "org.freedesktop.DBus.ObjectManager", signal, body) + + +def main(): + loop = GLib.MainLoop() + state = {} + + def acquired(bus, name): + state["manager"] = NetworkManager(bus) + + def lost(bus, name): + print("== netmanager: the system bus is gone", file=sys.stderr) + loop.quit() + + Gio.bus_own_name(Gio.BusType.SYSTEM, NAME, Gio.BusNameOwnerFlags.NONE, acquired, None, lost) + loop.run() + + +if __name__ == "__main__": + main() diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-proton-launch b/tools/linuxfs/overlay/usr/local/bin/winnative-proton-launch new file mode 100755 index 000000000..14c6dfd7f --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-proton-launch @@ -0,0 +1,75 @@ +#!/bin/sh +proton=$1 +shift +# The Proton the app's settings chose for this game. Its line keeps the tool Steam mapped when it +# was written: while Steam still starts that one the app's choice is newer, otherwise the user +# changed it in Steam and Steam's stands. `*` is the container's, for games Steam leaves on ours. +choices=/etc/winnative/proton-choices +app=${STEAM_COMPAT_APP_ID:-${STEAM_COMPAT_DATA_PATH##*/}} +if [ -n "$app" ] && [ -r "$choices" ]; then + case $proton in + */compatibilitytools.d/*/proton) started=${proton%/proton}; started=${started##*/} ;; + *) started=winnative-proton ;; + esac + tool= + fallback= + while read -r key name mapped; do + if [ "$key" = "$app" ]; then + [ "$mapped" = "$started" ] && tool=$name + fallback= + break + fi + [ "$key" = "*" ] && [ "$started" = winnative-proton ] && fallback=$name + done < "$choices" + tool=${tool:-$fallback} + case $tool in + "" | */* | .*) chosen= ;; + winnative-proton) chosen=/opt/winnative-proton/proton ;; + *) chosen=${STEAM_COMPAT_CLIENT_INSTALL_PATH:-$HOME/.local/share/Steam}/compatibilitytools.d/$tool/proton ;; + esac + if [ -n "$chosen" ] && [ -x "$chosen" ] && [ -x "$(dirname "$chosen")/files/bin-arm64/wine" ]; then + proton=$chosen + fi +fi +# The Vulkan driver the app's settings chose for this game, else the container's `*`. The session +# draws with the driver of whichever entry started it, and the game would otherwise inherit that. +drivers=/etc/winnative/driver-choices +if [ -n "$app" ] && [ -r "$drivers" ]; then + icd= + while read -r key path; do + if [ "$key" = "$app" ]; then + icd=$path + break + fi + [ "$key" = "*" ] && icd=$path + done < "$drivers" + [ -n "$icd" ] && [ -r "$icd" ] && export VK_ICD_FILENAMES="$icd" +fi +base=$(dirname "$proton") +if [ ! -x "$proton" ] || [ ! -x "$base/files/bin-arm64/wine" ]; then + echo "winnative-proton: incomplete ARM64 tool: $base" >&2 + exit 126 +fi +export PROTON_USE_ARM64=1 +# CachyOS Proton defaults to its PipeWire audio driver, and with no PipeWire server in the session +# the game hung at audio init behind Steam's spinner. Off, it takes the PulseAudio path we do run. +export PROTON_USE_PIPEWIRE=${PROTON_USE_PIPEWIRE:-0} +fake=/usr/local/lib/libwninput.so +if [ -f "$fake" ]; then + case ":${LD_PRELOAD:-}:" in + *":$fake:"*) ;; + *) LD_PRELOAD="$fake${LD_PRELOAD:+:$LD_PRELOAD}"; export LD_PRELOAD ;; + esac +fi +if [ -n "${WN_LOG:-}" ]; then + export PROTON_LOG=${PROTON_LOG:-1} + export PROTON_LOG_DIR=${PROTON_LOG_DIR:-$(dirname "$WN_LOG")} +fi +audio=off +[ "${WN_DIRECTAUDIO:-0}" = 1 ] && audio=on +winnative-directaudio --prepare "$base/files" "${STEAM_COMPAT_DATA_PATH:-}" "$audio" || exit $? +echo "winnative-proton: tool=$base verb=${1:-missing} app=${SteamAppId:-unknown} icd=${VK_ICD_FILENAMES:-none}" >&2 +if [ -n "${WN_EPIC:-}" ] && [ -x /usr/local/bin/winnative-epic-launch ]; then + exec /usr/local/bin/winnative-epic-launch "$proton" "$@" +fi +exec "$proton" "$@" diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-seed-redists b/tools/linuxfs/overlay/usr/local/bin/winnative-seed-redists new file mode 100755 index 000000000..3662faf7d --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-seed-redists @@ -0,0 +1,93 @@ +#!/bin/bash +# Marks Steam's shared redistributables as already run in every Proton prefix, and in the +# template Proton makes new prefixes from. +# +# A game whose depot lists _CommonRedist makes the client run the real installer inside the +# prefix on first launch. Those are x86 bundles, so they run under FEX, and the 2019 one leaves +# a task behind every time: its main thread exits while a helper stays blocked reading a pipe it +# also holds the write end of, so the process never reaps and the client keeps waiting on it. +# +# The client skips a "Run Process" block whose HasRunKey is already set, which is the same escape +# WineUtils.seedVcRedistRegistryIfDllsPresent() takes for the app's own containers. The names and +# versions come out of the install scripts rather than a list kept here, because Valve revises +# them; a prefix already carrying the same set is left alone. +set -u +steam_root=${1:?steam root} + +redist="$steam_root/steamapps/common/Steamworks Shared/_CommonRedist" + +# Every process an install script would run, as the registry key and value the client writes once +# it has run it. Names sit one level inside "Run Process", the key they share a level further in. +pairs=$(find "$redist" -name installscript.vdf -print0 2>/dev/null | sort -z | xargs -0 -r awk ' + FNR == 1 { depth = 0; name = ""; key = "" } + /^[[:space:]]*\{/ { depth++; next } + /^[[:space:]]*\}/ { + if (depth == 3 && name != "" && key != "") + printf "%s\t%s\n", key, name + depth-- + next + } + { + n = split($0, part, "\"") + if (n >= 5 && tolower(part[2]) == "hasrunkey") { key = part[4]; next } + if (n >= 3 && depth == 2) { name = part[2]; key = "" } + } +') +# The client downloads the scripts with the first game that wants them, which is too late for that +# game; until then the set they held when this was written stands in. +[ -n "$pairs" ] || pairs=$(sed 's/^\([^ ]*\) /HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\\1\t/' <<'KNOWN' +DirectX\\Jun2010 dxsetup +.NET\\4.8 4.8 +vcredist\\2008 x86 SP1 +vcredist\\2008 x64 SP1 +vcredist\\2010 x86 +vcredist\\2010 x64 +vcredist\\2013 x86 12.0.30501 +vcredist\\2013 x64 12.0.30501 +vcredist\\2015 x86 Update 3 14.0.24215.0 +vcredist\\2015 x64 Update 3 14.0.24215.0 +vcredist\\2017 x86 14.10.25008.0 +vcredist\\2017 x64 14.10.25008.0 +vcredist\\2019 x86 14.28.29334.0 +vcredist\\2019 x64 14.28.29334.0 +vcredist\\2022 x86 14.51.36247.0 +vcredist\\2022 x64 14.51.36247.0 +KNOWN +) +stamp=$(printf '%s' "$pairs" | cksum) + +block=$(printf '%s\n' "$pairs" | awk -F'\t' -v now="$(date +%s)" ' + { + path = $1 + sub(/^HKEY_LOCAL_MACHINE\\\\/, "", path) + if (path == $1) + next + printf "\n[%s] %d\n\"%s\"=dword:00000001\n", path, now, $2 + # The client writes these from 32-bit steam.exe, so its own copy lands under Wow6432Node; + # both spellings are seeded because the reader picks by its own word size. + sub(/^Software\\\\/, "Software\\\\\\\\Wow6432Node\\\\\\\\", path) + printf "\n[%s] %d\n\"%s\"=dword:00000001\n", path, now, $2 + } +') +[ -n "$block" ] || exit 0 + +# Proton builds a new prefix from its template, and the client runs the install scripts in that +# prefix moments later, so the template carries the markers too. Its date is kept: Proton reads a +# changed one as a new template and upgrades every prefix made from the old. +for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ + "$steam_root"/compatibilitytools.d/*/files/share/default_pfx*/system.reg \ + /opt/winnative-proton/files/share/default_pfx*/system.reg \ + "$steam_root"/steamapps/compatdata/*/pfx/system.reg \ + /mnt/winnative/steamapps/compatdata/*/pfx/system.reg; do + [ -f "$reg" ] || continue + seeded=$(dirname "$reg")/.winnative-redists + [ "$(cat "$seeded" 2>/dev/null)" = "$stamp" ] && continue + dated=$(mktemp) || continue + touch -r "$reg" "$dated" + if printf '%s\n' "$block" >>"$reg"; then + printf '%s\n' "$stamp" >"$seeded" + echo "== seeded redist markers into $reg" + fi + touch -r "$dated" "$reg" + rm -f "$dated" +done diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-session b/tools/linuxfs/overlay/usr/local/bin/winnative-session new file mode 100755 index 000000000..60972860a --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-session @@ -0,0 +1,221 @@ +#!/bin/bash +# The Linux runtime's session. WinNative execs this under proot with the output size and mode; +# the first pass starts gamescope, which execs this script again inside it for the program: +# winnative-session desktop the XFCE desktop +# winnative-session run <path> a Linux program (AppImage, script or ELF) +# winnative-session steam [steam-url] the native Steam client +# WN_WIDTH / WN_HEIGHT size the output, WN_FPS limits it, WN_LOG collects everything said here. +set -u +export HOME=${HOME:-/root} +mode=${1:-desktop} +shift || true +if [ -n "${WN_LOG:-}" ] && [ -z "${WN_INSIDE:-}" ]; then + mkdir -p "$(dirname "$WN_LOG")" 2>/dev/null + # The client alone writes megabytes an hour; one earlier log is kept, not every session's. + if [ "$(stat -c %s "$WN_LOG" 2>/dev/null || echo 0)" -gt 8388608 ]; then + mv -f "$WN_LOG" "$WN_LOG.1" + fi + exec >>"$WN_LOG" 2>&1 + echo "== winnative-session $(date) mode=$mode $*" + echo "== uid=$(id -u) size=${WN_WIDTH:-?}x${WN_HEIGHT:-?} fps=${WN_FPS:-0} icd=${VK_ICD_FILENAMES:-none}" + echo "== gamescope: $(gamescope --version 2>&1 | head -1)" +fi +export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} +export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} +export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} +mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" + +if [ -z "${WN_INSIDE:-}" ]; then + # The compositor may still be starting, and a socket left by an earlier session is not + # a compositor: probe until a connection succeeds. + for _ in $(seq 1 100); do + wayland-info >/dev/null 2>&1 && break + sleep 0.1 + done + # The desktop is XFCE on labwc, a compositor of its own that draws into one window of the + # app's: gamescope shows a single program at a time and cannot hold a panel and its windows. + # A runtime installed before the desktop was part of it still gets the file manager. + if [ "$mode" = desktop ] && command -v labwc >/dev/null && command -v xfce4-session >/dev/null; then + export WN_INSIDE=1 XDG_SESSION_TYPE=wayland XDG_CURRENT_DESKTOP=XFCE + export WLR_BACKENDS=wayland WLR_RENDERER=${WN_DESKTOP_RENDERER:-pixman} + exec dbus-run-session -- labwc -S xfce4-session + fi + width=${WN_WIDTH:-1280} + height=${WN_HEIGHT:-720} + args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$width" -h "$height") + # -r is the refresh rate gamescope advertises, which a game reads as the display's; without it + # gamescope says 60. A frame limit is a deliberate cap, so it wins over the panel's rate. + if [ "${WN_FPS:-0}" -gt 0 ]; then + args+=(-r "$WN_FPS") + elif [ "${WN_REFRESH:-0}" -gt 0 ]; then + args+=(-r "$WN_REFRESH") + fi + [ "$mode" = steam ] && args+=(-e) + export WN_INSIDE=1 + exec gamescope "${args[@]}" -- "$0" "$mode" "$@" +fi + +# gamescope manages X windows: toolkits go through Xwayland, not gamescope's own Wayland socket. +export GDK_BACKEND=x11 QT_QPA_PLATFORM=xcb SDL_VIDEODRIVER=x11 + +# gamescope's reaper waits for every process left under it before the compositor comes down. A +# process a game left behind - a Wine service blocked on a server that is already gone - is +# reparented there when the client exits, and the screen stays black on it for good. Nothing of +# the session outlives the program it was started for. +reap_orphans() { + local round p stat pid left + for round in 1 2 3 4 5; do + left= + for p in /proc/[0-9]*; do + pid=${p#/proc/} + [ "$pid" = "$$" ] && continue + stat=$(cat "$p/stat" 2>/dev/null) || continue + # The parent follows the bracketed command name, which may itself contain spaces. + stat=${stat##*) } + set -- $stat + [ "${2:-}" = "$PPID" ] && left="$left $pid" + done + [ -n "$left" ] || return 0 + echo "== leaving behind:$left" + kill -9 $left 2>/dev/null + sleep 0.2 + done +} + +case $mode in + desktop) + exec pcmanfm + ;; + run) + target=${1:?program path} + shift + case ${target,,} in + *.appimage) + export APPIMAGE_EXTRACT_AND_RUN=1 + chmod +x "$target" 2>/dev/null + exec "$target" "$@" + ;; + *.sh) + exec bash "$target" "$@" + ;; + *) + chmod +x "$target" 2>/dev/null + cd "$(dirname "$target")" && exec "$target" "$@" + ;; + esac + ;; + steam) + winnative-steam-install || exit $? + steam_root=$XDG_DATA_HOME/Steam + # A non-Steam game has nothing to update or sync, so as an argument it is launched within a + # second of sign-in - before the client's interface listens for the launch's prompts, which + # the launch then waits on for good. Its URL is held back for the client's pipe instead. + deferred= + case ${1:-} in + steam://rungameid/?????????????????*) deferred=$1; shift ;; + esac + # Register the games the app downloaded (bound at /mnt/winnative) with the client, and the + # ARM64 Proton that lets it launch the Windows ones. Neither is worth losing the session over. + winnative-steam-library "$steam_root" || true + winnative-steam-compat "$steam_root" || true + # Steam runs a game's shared redistributables inside its prefix on first launch; marking + # them as already run keeps those x86 installers from starting under emulation at all. + winnative-seed-redists "$steam_root" || true + # With DirectAudio chosen the games' prefixes name it as their audio driver; otherwise they + # are handed back to Wine's own choice, which finds the PulseAudio server. + winnative-directaudio "$steam_root" "$([ -n "${WN_DIRECTAUDIO:-}" ] && echo on || echo off)" || true + ( + beat=0 + for _ in $(seq 1 720); do + sleep 5 + winnative-seed-redists "$steam_root" >/dev/null 2>&1 || true + beat=$((beat + 1)) + if [ "$((beat % 3))" -eq 0 ]; then + winnative-steam-compat "$steam_root" --refresh >/dev/null 2>&1 || true + fi + done + ) & + heartbeat=$! + export STEAM_RUNTIME=1 + export LD_LIBRARY_PATH=$steam_root/steamrtarm64:$steam_root/steamrtarm64/panorama${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} + export SDL_VIDEO_X11_DGAMOUSE=0 + [ -d /usr/share/guestos/fex-mesa ] && export FEX_ROOTFS=/usr/share/guestos/fex-mesa + # The compatibility tool runs Valve's arm64 Proton. The app installs a copy with the runtime; + # a runtime from before that has the client fetch the depot instead. + if [ ! -f /opt/winnative-proton/proton ] && [ ! -f "$steam_root/steamapps/appmanifest_4427310.acf" ]; then + set -- "$@" steam://install/4427310 + fi + # An app id left in ~/.wn-autolaunch is handed to the client as a URL, so a launch can be + # reproduced without touching the screen while the runtime is being brought up. + if [ -s "$HOME/.wn-autolaunch" ]; then + _wn_app=$(cat "$HOME/.wn-autolaunch") + # A bare app id runs the game; a full steam:// URL is passed through, which is how a + # validate or an install is driven without touching the screen. + case $_wn_app in + steam://*) _wn_url=$_wn_app ;; + *) _wn_url=steam://rungameid/$_wn_app ;; + esac + echo "== autolaunch $_wn_url" + set -- "$@" "$_wn_url" + fi + # The client asks gamescope to pass touches through as touches, and gamescope then drops a + # nested pointer's position: a click lands wherever the cursor was last left. The app sends + # a tap as a pointer, so the mode is held at "hover", where the position moves the cursor. + xprop -root -spy STEAM_TOUCH_CLICK_MODE 2>/dev/null | while read -r _ _ touch_mode; do + [ "$touch_mode" = 0 ] || xprop -root -f STEAM_TOUCH_CLICK_MODE 32c -set STEAM_TOUCH_CLICK_MODE 0 + done & + # The client builds its network page and its connection indicator on NetworkManager, which + # it looks for on the system bus as it starts. The network is Android's, so the runtime has + # neither; without them Settings > Internet fails and the header shows no connection. + mkdir -p /run/dbus + rm -f /run/dbus/system_bus_socket + dbus-daemon --config-file=/usr/local/share/winnative/system-bus.conf --nofork --nopidfile & + system_bus=$! + for _ in $(seq 1 50); do + [ -S /run/dbus/system_bus_socket ] && break + sleep 0.1 + done + winnative-netmanager & + netmanager=$! + waiter= + if [ -n "$deferred" ]; then + ( + ui_log=$steam_root/logs/webhelper_js.txt + loaded="SteamApp Init - After Login total time" + before=$(grep -ac "$loaded" "$ui_log" 2>/dev/null) + for _ in $(seq 1 180); do + sleep 1 + [ "$(grep -ac "$loaded" "$ui_log" 2>/dev/null)" = "$before" ] || break + done + sleep 2 + echo "== launching $deferred" + # A pipe nobody reads would hold this open for good; the client reads it while it lives. + timeout 10 bash -c 'echo "$0" > "$1"' "$deferred" "$HOME/.steam/steam.pipe" + ) & + waiter=$! + fi + # The client exits 42 when it has updated itself and wants a fresh start (steam.sh does this). + while :; do + "$steam_root/steamrtarm64/steam" -gamepadui -clientbeta publicbeta \ + -no-cef-sandbox -cef-force-gpu -cef-ozone-platform=x11 \ + -cef-use-gl=angle -cef-use-angle=vulkan "$@" + rc=$? + echo "== steam exited rc=$rc" + [ "$rc" -eq 42 ] || break + done + pkill -x xprop + # Every job started here is ended here and waited for, before the sweep. One left running is + # adopted by gamescope's reaper on the way out, which waits for it and so holds the + # compositor and the proot above it open on a black screen; and the sweep is no second + # chance, since a job this shell still owns is not an orphan for it to find. + kill "$netmanager" "$system_bus" "$heartbeat" 2>/dev/null + [ -z "$waiter" ] || kill "$waiter" 2>/dev/null + wait "$netmanager" "$system_bus" "$heartbeat" ${waiter:+"$waiter"} 2>/dev/null + reap_orphans + exit "$rc" + ;; + *) + echo "winnative-session: unknown mode '$mode'" >&2 + exit 64 + ;; +esac diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-steam-compat b/tools/linuxfs/overlay/usr/local/bin/winnative-steam-compat new file mode 100755 index 000000000..384660cc4 --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-steam-compat @@ -0,0 +1,339 @@ +#!/usr/bin/python3 +import os +import re +import shutil +import sys + +TOOL = "winnative-proton" +LAUNCHER = "winnative-launch" +EXTRA_WRAPPER = "winnative-proton-wrap" +SOURCES = ("Proton Experimental (ARM64)", "Proton 11.0 (ARM64)") +BUNDLED = "/opt/winnative-proton" +LIBRARY = "/mnt/winnative" +TOKEN = re.compile(r'\s*(?:"((?:[^"\\]|\\.)*)"|([{}])|//[^\n]*)') +NOT_GAMES = frozenset(( + "228980", "1070560", "1391110", "1493710", "3127680", "4183110", + "4185400", "4427310", "4628740", +)) +ANTICHEAT_RUNTIMES = ("1161040", "1826330") + +TOOLMANIFEST = '''"manifest" +{ + "version" "2" + "commandline" "/%s %%verb%%" + "use_sessions" "1" + "compatmanager_layer_name" "proton" +} +''' % LAUNCHER + +EXTRA_TOOLMANIFEST = '''"manifest" +{ + "version" "2" + "commandline" "/%s %%verb%%" + "use_sessions" "1" + "compatmanager_layer_name" "proton" +} +''' % EXTRA_WRAPPER + +COMPAT_TOOL_VDF = '''"compatibilitytools" +{ + "compat_tools" + { + "%s" + { + "install_path" "." + "display_name" "WN Proton Experimental" + "from_oslist" "windows" + "to_oslist" "linux" + } + } +} +''' % TOOL + +EXTRA_COMPAT_TOOL_VDF = '''"compatibilitytools" +{ + "compat_tools" + { + "%s" + { + "install_path" "." + "display_name" "%s (WinNative)" + "from_oslist" "windows" + "to_oslist" "linux" + } + } +} +''' + +LAUNCHER_SH = '''#!/bin/sh +verb=$1 +shift || true +steam_root=${STEAM_COMPAT_CLIENT_INSTALL_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/Steam} +run_proton() { + exec /usr/local/bin/winnative-proton-launch "$@" +} +if [ -d "%s/files/bin-arm64" ] && [ -x "%s/proton" ]; then + run_proton "%s/proton" "$verb" "$@" +fi +for name in %s; do + depot="$steam_root/steamapps/common/$name" + if [ -d "$depot/files/bin-arm64" ] && [ -x "$depot/proton" ]; then + run_proton "$depot/proton" "$verb" "$@" + fi +done +echo "winnative-proton: no ARM64 Proton depot is installed yet" >&2 +exit 1 +''' % (BUNDLED, BUNDLED, BUNDLED, " ".join('"%s"' % name for name in SOURCES)) + +EXTRA_WRAPPER_SH = '''#!/bin/sh +here=$(dirname "$0") +exec /usr/local/bin/winnative-proton-launch "$here/%s" "$@" +''' + + +def write_changed(path, body, mode=0o644): + try: + with open(path, encoding="utf-8") as source: + if source.read() == body: + os.chmod(path, mode) + return + except OSError: + pass + staged = path + ".winnative-staged" + with open(staged, "w", encoding="utf-8") as output: + output.write(body) + os.chmod(staged, mode) + os.replace(staged, path) + + +def tokenize(text): + tokens = [] + pos = 0 + while pos < len(text): + m = TOKEN.match(text, pos) + if not m or m.end() == pos: + break + pos = m.end() + if m.group(2): + tokens.append(m.group(2)) + elif m.group(1) is not None: + tokens.append('"' + m.group(1) + '"') + return tokens + + +def find_block(tokens, path): + depth, want, i = 0, 0, 0 + while i < len(tokens): + token = tokens[i] + if token == "{": + depth += 1 + elif token == "}": + depth -= 1 + if depth < want: + return None + elif depth == want and i + 1 < len(tokens) and tokens[i + 1] == "{": + if token.lower() == '"%s"' % path[want].lower(): + if want == len(path) - 1: + return i + 1 + want += 1 + i += 1 + return None + + +def block_end(tokens, start): + depth = 0 + for i in range(start, len(tokens)): + if tokens[i] == "{": + depth += 1 + elif tokens[i] == "}": + depth -= 1 + if depth == 0: + return i + return len(tokens) + + +def serialize(tokens): + out, depth, i = [], 0, 0 + while i < len(tokens): + token = tokens[i] + if token == "}": + depth -= 1 + out.append("\t" * depth + "}") + i += 1 + elif i + 1 < len(tokens) and tokens[i + 1] == "{": + out.append("\t" * depth + token) + out.append("\t" * depth + "{") + depth += 1 + i += 2 + else: + out.append("\t" * depth + token + "\t\t" + tokens[i + 1]) + i += 2 + return "\n".join(out) + "\n" + + +def first_key(path, section): + try: + with open(path, encoding="utf-8", errors="replace") as source: + tokens = tokenize(source.read()) + except OSError: + return None + block = find_block(tokens, section) + if block is None: + return None + for i in range(block + 1, block_end(tokens, block)): + if i + 1 < len(tokens) and tokens[i + 1] == "{": + return tokens[i].strip('"') + return None + + +def manifest_entry_point(path): + try: + with open(path, encoding="utf-8", errors="replace") as source: + tokens = tokenize(source.read()) + except OSError: + return None + for i in range(len(tokens) - 1): + if tokens[i].lower() == '"commandline"': + parts = tokens[i + 1].strip('"').split() + if parts: + return parts[0].lstrip("/") + return None + + +def mapped_apps(steam_root): + apps = set() + for library in (steam_root, LIBRARY): + steamapps = os.path.join(library, "steamapps") + try: + names = os.listdir(steamapps) + except OSError: + continue + for name in names: + match = re.fullmatch(r"appmanifest_(\d+)\.acf", name) + if match and match.group(1) not in NOT_GAMES: + apps.add(match.group(1)) + return sorted(apps.union(ANTICHEAT_RUNTIMES), key=int) + + +def build_tool(tools): + tool_dir = os.path.join(tools, TOOL) + if os.path.islink(tool_dir): + os.unlink(tool_dir) + os.makedirs(tool_dir, exist_ok=True) + files = { + "toolmanifest.vdf": TOOLMANIFEST, + "compatibilitytool.vdf": COMPAT_TOOL_VDF, + LAUNCHER: LAUNCHER_SH, + } + for name, body in files.items(): + write_changed(os.path.join(tool_dir, name), body, 0o755 if name == LAUNCHER else 0o644) + + +def adopt_extras(tools): + adopted = set() + try: + entries = sorted(os.listdir(tools)) + except OSError: + return adopted + for entry in entries: + if entry == TOOL or entry.startswith("."): + continue + tool_dir = os.path.join(tools, entry) + manifest = os.path.join(tool_dir, "toolmanifest.vdf") + compat = os.path.join(tool_dir, "compatibilitytool.vdf") + if not os.path.isdir(tool_dir) or not os.path.isfile(manifest): + continue + original = manifest + ".winnative-original" + try: + if not os.path.isfile(original): + shutil.copy2(manifest, original) + entry_point = manifest_entry_point(original) + if not entry_point or entry_point in (EXTRA_WRAPPER, LAUNCHER): + entry_point = "proton" + if not os.path.exists(os.path.join(tool_dir, entry_point)): + continue + name = first_key(compat, ["compatibilitytools", "compat_tools"]) or entry + write_changed(os.path.join(tool_dir, EXTRA_WRAPPER), EXTRA_WRAPPER_SH % entry_point, 0o755) + write_changed(manifest, EXTRA_TOOLMANIFEST) + if os.path.isfile(compat): + write_changed(compat, EXTRA_COMPAT_TOOL_VDF % (name, entry)) + adopted.add(name) + except (OSError, ValueError, IndexError) as error: + print("winnative-steam-compat: could not adopt %s: %s" % (entry, error), file=sys.stderr) + return adopted + + +def register_default(config_path, apps=(), protect=()): + with open(config_path, encoding="utf-8", errors="replace") as source: + tokens = tokenize(source.read()) + steam = find_block(tokens, ["InstallConfigStore", "Software", "Valve", "Steam"]) + if steam is None: + return + protect = {name.lower() for name in protect} + mapping = find_block(tokens[steam + 1:], ["CompatToolMapping"]) + entry = ['"0"', "{", '"name"', '"%s"' % TOOL, '"config"', '""', '"priority"', '"75"', "}"] + changed = False + if mapping is None: + end = block_end(tokens, steam) + tokens[end:end] = ['"CompatToolMapping"', "{"] + entry + ["}"] + changed = True + mapping = find_block(tokens[steam + 1:], ["CompatToolMapping"]) + mapping += steam + 1 + end = block_end(tokens, mapping) + for i in range(mapping, end): + if tokens[i] != '"name"' or i + 1 >= end: + continue + name = tokens[i + 1].strip('"') + lowered = name.lower() + if lowered.startswith("proton") and lowered != TOOL.lower() and lowered not in protect: + tokens[i + 1] = '"%s"' % TOOL + changed = True + if find_block(tokens[mapping + 1:end], ["0"]) is None: + tokens[mapping + 1:mapping + 1] = entry + end += len(entry) + changed = True + for app in apps: + if find_block(tokens[mapping + 1:end], [app]) is not None: + continue + item = ['"%s"' % app, "{", '"name"', '"%s"' % TOOL, '"config"', '""', '"priority"', '"250"', "}"] + tokens[mapping + 1:mapping + 1] = item + end += len(item) + changed = True + if not changed: + return + tmp = config_path + ".winnative" + with open(tmp, "w", encoding="utf-8") as output: + output.write(serialize(tokens)) + os.replace(tmp, config_path) + + +def find_depot(steam_root): + if os.path.isdir(os.path.join(BUNDLED, "files", "bin-arm64")) and os.path.isfile(os.path.join(BUNDLED, "proton")): + return BUNDLED + for name in SOURCES: + depot = os.path.join(steam_root, "steamapps", "common", name) + if os.path.isdir(os.path.join(depot, "files", "bin-arm64")) and os.path.isfile(os.path.join(depot, "proton")): + return depot + return None + + +def main(): + if len(sys.argv) < 2: + sys.exit(2) + steam_root = sys.argv[1] + tools = os.path.join(steam_root, "compatibilitytools.d") + os.makedirs(tools, exist_ok=True) + build_tool(tools) + extras = adopt_extras(tools) + depot = find_depot(steam_root) + state = os.path.basename(depot) if depot else "waiting for ARM64 Proton depot" + print("winnative-steam-compat: registered %s (%s)" % (TOOL, state)) + config = os.path.join(steam_root, "config", "config.vdf") + if "--refresh" not in sys.argv[2:] and os.path.isfile(config): + apps = mapped_apps(steam_root) + register_default(config, apps, extras) + print("winnative-steam-compat: default and %d installed title(s) set to %s" % (len(apps), TOOL)) + + +if __name__ == "__main__": + main() diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-steam-install b/tools/linuxfs/overlay/usr/local/bin/winnative-steam-install new file mode 100755 index 000000000..52fd07ed2 --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-steam-install @@ -0,0 +1,59 @@ +#!/bin/bash +# Fetches the native arm64 Steam client from Valve's client-update CDN into ~/.local/share/Steam. +# The client is not redistributable, so the runtime ships this script instead of the client. +set -eu +cdn=https://client-update.fastly.steamstatic.com +manifest=steam_client_publicbeta_linuxarm64 +steam_root=${XDG_DATA_HOME:-$HOME/.local/share}/Steam +stamp=$steam_root/package/winnative-installed +[ -x "$steam_root/steamrtarm64/steam" ] && [ -f "$stamp" ] && exit 0 + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +curl -fsSL "$cdn/$manifest" -o "$work/manifest" +version=$(awk -F'"' '$2 == "version" { print $4; exit }' "$work/manifest") +# Every component block: its name, then the plain zip's file name and sha256. +awk -F'"' ' + /^\t"/ && NF >= 3 && $4 == "" { block = $2 } + $2 == "file" { file[block] = $4 } + $2 == "sha2" { sha[block] = $4 } + END { for (b in file) if (b ~ /_all$/ || b ~ /_linuxarm64_linuxarm64$/) print b, file[b], sha[b] } +' "$work/manifest" | sort > "$work/parts" +[ -s "$work/parts" ] || { echo "winnative-steam-install: no client components in $manifest" >&2; exit 1; } + +mkdir -p "$steam_root/package" +while read -r block file sum; do + echo "steam: $block" + curl -fsSL "$cdn/$file" -o "$work/$file" + echo "$sum $work/$file" | sha256sum -c --quiet + unzip -qo "$work/$file" -d "$steam_root" + rm -f "$work/$file" +done < "$work/parts" +# Some entries are zipped with Windows separators; unzip keeps the backslash in the file name. +find "$steam_root" -depth -name '*\\*' | while read -r odd; do + fixed=${odd//\\//} + mkdir -p "$(dirname "$fixed")" + if [ -d "$odd" ]; then + cp -a "$odd/." "$fixed/" && rm -rf "$odd" + else + mv -f "$odd" "$fixed" + fi +done +echo publicbeta > "$steam_root/package/beta" +echo "$version" > "$stamp" +ln -sfn "$steam_root/steamrtarm64" "$steam_root/steamrtarm32" +chmod +x "$steam_root/steamrtarm64/steam" 2>/dev/null || true +# What Valve's steam.sh sets up before it runs the client (it has no arm64 branch itself). +mkdir -p "$HOME/.steam" +ln -sfn "$steam_root" "$HOME/.steam/root" +ln -sfn "$steam_root" "$HOME/.steam/steam" +ln -sfn "$steam_root/steamrtarm64" "$HOME/.steam/bin64" +ln -sfn "$steam_root/steamrtarm64" "$HOME/.steam/binarm64" +# The Steamworks SDK paths a game's steamclient stub is dlopened from, one per architecture. +# Proton's lsteamclient runs emulated under FEX and loads the x86_64 (sdk64) or x86 (sdk32) +# stub, which is only an IPC shim to the arm64 client over ~/.steam/steam.pipe; native arm64 +# games take sdkarm64. Pointing sdk64 at the arm64 tree hands the emulated loader a library it +# cannot map and Steamworks aborts. +ln -sfn "$steam_root/linux64" "$HOME/.steam/sdk64" +ln -sfn "$steam_root/linux32" "$HOME/.steam/sdk32" +ln -sfn "$steam_root/linuxarm64" "$HOME/.steam/sdkarm64" diff --git a/tools/linuxfs/overlay/usr/local/bin/winnative-steam-library b/tools/linuxfs/overlay/usr/local/bin/winnative-steam-library new file mode 100755 index 000000000..82d081231 --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/bin/winnative-steam-library @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +# Registers the games WinNative downloaded (bound at /mnt/winnative) as a library folder of the +# native Steam client, so they show as installed there. +# +# winnative-steam-library <steam root> +import os +import re +import sys + +LIBRARY = "/mnt/winnative" + + +def main(): + steam_root = sys.argv[1] + steamapps = os.path.join(LIBRARY, "steamapps") + if not os.path.isdir(steamapps): + return + apps = sorted( + int(m.group(1)) + for m in (re.fullmatch(r"appmanifest_(\d+)\.acf", n) for n in os.listdir(steamapps)) + if m + ) + marker = os.path.join(LIBRARY, "libraryfolder.vdf") + if not os.path.isfile(marker): + with open(marker, "w", encoding="utf-8") as f: + f.write('"libraryfolder"\n{\n\t"contentid"\t\t"%d"\n\t"label"\t\t"WinNative"\n}\n' + % int.from_bytes(os.urandom(8), "little")) + # The client reads steamapps/libraryfolders.vdf at startup and rewrites config/ from it. + steamapps_dir = os.path.join(steam_root, "steamapps") + path = os.path.join(steamapps_dir, "libraryfolders.vdf") + text = "" + if os.path.isfile(path): + with open(path, encoding="utf-8", errors="replace") as f: + text = f.read() + if re.search(r'"path"\s+"%s"' % re.escape(LIBRARY), text): + return + indexes = [int(i) for i in re.findall(r'^\t"(\d+)"\s*$', text, re.M)] + if not text.strip(): + text = '"libraryfolders"\n{\n\t"0"\n\t{\n\t\t"path"\t\t"%s"\n\t\t"label"\t\t""\n\t}\n}\n' % steam_root + indexes = [0] + entry = ['\t"%d"' % (max(indexes) + 1), "\t{", '\t\t"path"\t\t"%s"' % LIBRARY, + '\t\t"label"\t\t"WinNative"', '\t\t"apps"', "\t\t{"] + entry += ['\t\t\t"%d"\t\t"0"' % app for app in apps] + entry += ["\t\t}", "\t}"] + closing = text.rstrip().rfind("}") + if closing < 0: + return + text = text[:closing] + "\n".join(entry) + "\n" + text[closing:] + os.makedirs(steamapps_dir, exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + f.write(text) + + +if __name__ == "__main__": + main() diff --git a/tools/linuxfs/overlay/usr/local/share/winnative/system-bus.conf b/tools/linuxfs/overlay/usr/local/share/winnative/system-bus.conf new file mode 100644 index 000000000..cbf364c9b --- /dev/null +++ b/tools/linuxfs/overlay/usr/local/share/winnative/system-bus.conf @@ -0,0 +1,15 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<!-- The runtime's system bus. One user runs everything in the session, so there is nobody to + keep apart: the bus exists for the Steam client to find winnative-netmanager on. --> +<busconfig> + <type>system</type> + <listen>unix:path=/run/dbus/system_bus_socket</listen> + <auth>EXTERNAL</auth> + <policy context="default"> + <allow user="*"/> + <allow own="*"/> + <allow send_destination="*"/> + <allow receive_sender="*"/> + </policy> +</busconfig> diff --git a/tools/linuxfs/preload/drm.c b/tools/linuxfs/preload/drm.c new file mode 100644 index 000000000..0cff32a7a --- /dev/null +++ b/tools/linuxfs/preload/drm.c @@ -0,0 +1,115 @@ +/* + * GEM handle emulation for the KGSL device presented as a DRM render node. + * + * Compositors and Mesa validate or convert dma-bufs with PRIME ioctls on the render node; KGSL + * has no GEM. Handles only serve as tokens to these callers, so a table of duplicated dma-buf + * descriptors stands in for the kernel's, and every other device is left to libdrm. + */ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#define MAX_HANDLES 4096 + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static int handle_fds[MAX_HANDLES]; +static dev_t kgsl_dev; +static int kgsl_state; /* 0 unknown, 1 known, -1 absent */ + +static int is_kgsl(int fd) { + struct stat st; + pthread_mutex_lock(&lock); + if (kgsl_state == 0) { + kgsl_state = stat("/dev/kgsl-3d0", &st) == 0 ? 1 : -1; + kgsl_dev = st.st_rdev; + } + pthread_mutex_unlock(&lock); + return kgsl_state > 0 && fstat(fd, &st) == 0 && S_ISCHR(st.st_mode) && st.st_rdev == kgsl_dev; +} + +static void *real(const char *name) { + void *fn = dlsym(RTLD_NEXT, name); + if (!fn) { + errno = ENOSYS; + } + return fn; +} + +int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle) { + if (!is_kgsl(fd)) { + int (*fn)(int, int, uint32_t *) = (int (*)(int, int, uint32_t *)) real("drmPrimeFDToHandle"); + return fn ? fn(fd, prime_fd, handle) : -ENOSYS; + } + int dup_fd = fcntl(prime_fd, F_DUPFD_CLOEXEC, 0); + if (dup_fd < 0) { + return -errno; + } + int ret = -ENOMEM; + pthread_mutex_lock(&lock); + for (uint32_t i = 1; i < MAX_HANDLES; i++) { + if (handle_fds[i] == 0) { + handle_fds[i] = dup_fd; + *handle = i; + ret = 0; + break; + } + } + pthread_mutex_unlock(&lock); + if (ret) { + close(dup_fd); + } + return ret; +} + +int drmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags, int *prime_fd) { + if (!is_kgsl(fd)) { + int (*fn)(int, uint32_t, uint32_t, int *) = (int (*)(int, uint32_t, uint32_t, int *)) real("drmPrimeHandleToFD"); + return fn ? fn(fd, handle, flags, prime_fd) : -ENOSYS; + } + int ret = -EINVAL; + pthread_mutex_lock(&lock); + if (handle > 0 && handle < MAX_HANDLES && handle_fds[handle] > 0) { + *prime_fd = fcntl(handle_fds[handle], F_DUPFD_CLOEXEC, 0); + ret = *prime_fd < 0 ? -errno : 0; + } + pthread_mutex_unlock(&lock); + return ret; +} + +int drmCloseBufferHandle(int fd, uint32_t handle) { + if (!is_kgsl(fd)) { + int (*fn)(int, uint32_t) = (int (*)(int, uint32_t)) real("drmCloseBufferHandle"); + return fn ? fn(fd, handle) : -ENOSYS; + } + int ret = -EINVAL; + pthread_mutex_lock(&lock); + if (handle > 0 && handle < MAX_HANDLES && handle_fds[handle] > 0) { + close(handle_fds[handle]); + handle_fds[handle] = 0; + ret = 0; + } + pthread_mutex_unlock(&lock); + return ret; +} + +/* + * fork() carries over only the calling thread, so a lock another thread was holding at that + * instant stays held in the child by a thread that is not there to release it, and the child's + * next ioctl on a render node would block on it forever. Taking it before the fork makes the + * copy consistent; the parent then unlocks it and the child, whose one thread never locked it, + * gets a fresh one. + */ +static void lock_before_fork(void) { pthread_mutex_lock(&lock); } +static void unlock_after_fork(void) { pthread_mutex_unlock(&lock); } +/* The child's one thread did not lock it, so it is given a fresh mutex rather than an unlock. */ +static void reset_after_fork(void) { pthread_mutex_init(&lock, NULL); } + +__attribute__((constructor)) static void install_drm_fork_handlers(void) { + pthread_atfork(lock_before_fork, unlock_after_fork, reset_after_fork); +} diff --git a/tools/linuxfs/preload/inputudev.c b/tools/linuxfs/preload/inputudev.c new file mode 100644 index 000000000..78afbb126 --- /dev/null +++ b/tools/linuxfs/preload/inputudev.c @@ -0,0 +1,290 @@ +/* + * The session's controllers, described to libudev's callers. + * + * The pads are evdev nodes the app backs with its own input rings, bound over /dev/input. Wine's + * HID bus finds them there, but it identifies a node by resolving /sys/class/input/<name> and + * asking libudev about the result: on Android that resolves to whatever real input device the + * host numbered the same, whose device node libudev cannot read, so every pad is discarded before + * its identity is ever looked at. The fallback is Wine's SDL bus, and SDL enumerates through udev + * too, over a netlink socket the app sandbox refuses. A game was left with no controller at all + * while the Steam client, which reads the nodes itself, had one. + * + * So the pads are described here instead: the resolved path is answered for the session's own + * nodes, and libudev's accessors answer for that path from what the app already knows. They carry + * the identity of the pad Steam Input would present if it could - it has no /dev/uinput here to + * make one - which is the one identity Wine's own bus keeps rather than handing on to SDL. + * + * Only Wine's device host is answered, and only for the session's own nodes: the Steam client + * enumerates the same nodes for itself and would take a pad wearing Steam Input's identity for one + * of its own and ignore it. Every other caller, path and device object goes straight through. + */ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <limits.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/sysmacros.h> +#include <sys/types.h> + +/* The app opens one ring per configured controller; four is what its launcher offers. */ +#define PAD_MAX 4 +#define EVENT_MINOR_BASE 64 + +/* BUS_VIRTUAL, and the identity Steam Input gives the gamepad it presents to a game. */ +#define PAD_UEVENT_FORMAT \ + "PRODUCT=6/28de/11ff/110\n" \ + "NAME=\"Generic HID Gamepad %d\"\n" \ + "PHYS=\"usb-fakeinput/input%d\"\n" + +struct pad { + char syspath[64]; + char devnode[32]; + char sysname[16]; + char uevent[128]; + dev_t devnum; +}; + +static struct pad pads[PAD_MAX]; +static int pad_count; + +/* + * The slots the app published rings for, named in FAKE_EVDEV_MEMFD_PATHS as "<slot>=<path>" + * separated by semicolons. Without it the process has no pads of the session's and every call + * here is a passthrough. + */ +static void discover_pads(void) { + const char *spec = getenv("FAKE_EVDEV_MEMFD_PATHS"); + const char *entry; + + if (spec == NULL) return; + for (entry = spec; *entry != '\0';) { + char *end; + long slot = strtol(entry, &end, 10); + + if (end != entry && *end == '=' && slot >= 0 && slot < PAD_MAX) { + struct pad *pad = &pads[slot]; + + if (pad->devnode[0] == '\0') { + snprintf(pad->sysname, sizeof(pad->sysname), "event%ld", slot); + snprintf(pad->syspath, sizeof(pad->syspath), "/sys/devices/virtual/input/winnative%ld", + slot); + snprintf(pad->devnode, sizeof(pad->devnode), "/dev/input/event%ld", slot); + snprintf(pad->uevent, sizeof(pad->uevent), PAD_UEVENT_FORMAT, (int)slot, (int)slot); + pad->devnum = makedev(13, EVENT_MINOR_BASE + (unsigned int)slot); + if (slot >= pad_count) pad_count = (int)slot + 1; + } + } + entry = strchr(entry, ';'); + if (entry == NULL) break; + entry++; + } +} + +static void ensure_pads(void) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, discover_pads); +} + +/* The pad a synthetic syspath names, or NULL when the path is not one of ours. */ +static struct pad *pad_for_syspath(const char *syspath) { + int i; + + if (syspath == NULL) return NULL; + ensure_pads(); + for (i = 0; i < pad_count; i++) { + if (pads[i].devnode[0] != '\0' && strcmp(pads[i].syspath, syspath) == 0) return &pads[i]; + } + return NULL; +} + +/* A device object is ours exactly when it is one of the pads. */ +static struct pad *pad_for_device(void *device) { + int i; + + if (device == NULL) return NULL; + ensure_pads(); + for (i = 0; i < pad_count; i++) { + if (&pads[i] == device) return pads[i].devnode[0] != '\0' ? &pads[i] : NULL; + } + return NULL; +} + +/* The pad /sys/class/input/<name> stands for, or NULL when the path names something else. */ +static struct pad *pad_for_syslink(const char *path) { + static const char prefix[] = "/sys/class/input/"; + const char *name; + int i; + + if (path == NULL || strncmp(path, prefix, sizeof(prefix) - 1) != 0) return NULL; + name = path + sizeof(prefix) - 1; + ensure_pads(); + for (i = 0; i < pad_count; i++) { + if (pads[i].devnode[0] != '\0' && strcmp(pads[i].sysname, name) == 0) return &pads[i]; + } + return NULL; +} + +static void *next_symbol(const char *name) { return dlsym(RTLD_NEXT, name); } + +/* Wine reaches libudev from its HID bus driver, which is the only caller these pads are for. */ +static int caller_is_winebus(void *caller) { + Dl_info info; + + if (dladdr(caller, &info) == 0 || info.dli_fname == NULL) return 0; + return strstr(info.dli_fname, "winebus.so") != NULL; +} + +char *realpath(const char *path, char *resolved) { + static char *(*real_realpath)(const char *, char *); + void *caller = __builtin_return_address(0); + struct pad *pad = caller_is_winebus(caller) ? pad_for_syslink(path) : NULL; + + if (pad != NULL) { + if (resolved == NULL) return strdup(pad->syspath); + /* PATH_MAX is what the caller promises when it passes a buffer. */ + memcpy(resolved, pad->syspath, strlen(pad->syspath) + 1); + return resolved; + } + if (real_realpath == NULL) real_realpath = next_symbol("realpath"); + if (real_realpath == NULL) return NULL; + return real_realpath(path, resolved); +} + +void *udev_device_new_from_syspath(void *udev, const char *syspath) { + static void *(*real_new)(void *, const char *); + void *caller = __builtin_return_address(0); + struct pad *pad = caller_is_winebus(caller) ? pad_for_syspath(syspath) : NULL; + + if (pad != NULL) return pad; + if (real_new == NULL) real_new = next_symbol("udev_device_new_from_syspath"); + if (real_new == NULL) return NULL; + return real_new(udev, syspath); +} + +void *udev_device_ref(void *device) { + static void *(*real_ref)(void *); + + if (pad_for_device(device) != NULL) return device; + if (real_ref == NULL) real_ref = next_symbol("udev_device_ref"); + if (real_ref == NULL) return NULL; + return real_ref(device); +} + +void *udev_device_unref(void *device) { + static void *(*real_unref)(void *); + + if (pad_for_device(device) != NULL) return NULL; + if (real_unref == NULL) real_unref = next_symbol("udev_device_unref"); + if (real_unref == NULL) return NULL; + return real_unref(device); +} + +const char *udev_device_get_devnode(void *device) { + static const char *(*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return pad->devnode; + if (real_get == NULL) real_get = next_symbol("udev_device_get_devnode"); + if (real_get == NULL) return NULL; + return real_get(device); +} + +const char *udev_device_get_syspath(void *device) { + static const char *(*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return pad->syspath; + if (real_get == NULL) real_get = next_symbol("udev_device_get_syspath"); + if (real_get == NULL) return NULL; + return real_get(device); +} + +const char *udev_device_get_sysname(void *device) { + static const char *(*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return pad->sysname; + if (real_get == NULL) real_get = next_symbol("udev_device_get_sysname"); + if (real_get == NULL) return NULL; + return real_get(device); +} + +const char *udev_device_get_subsystem(void *device) { + static const char *(*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return "input"; + if (real_get == NULL) real_get = next_symbol("udev_device_get_subsystem"); + if (real_get == NULL) return NULL; + return real_get(device); +} + +const char *udev_device_get_devtype(void *device) { + static const char *(*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return NULL; + if (real_get == NULL) real_get = next_symbol("udev_device_get_devtype"); + if (real_get == NULL) return NULL; + return real_get(device); +} + +const char *udev_device_get_action(void *device) { + static const char *(*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return NULL; + if (real_get == NULL) real_get = next_symbol("udev_device_get_action"); + if (real_get == NULL) return NULL; + return real_get(device); +} + +dev_t udev_device_get_devnum(void *device) { + static dev_t (*real_get)(void *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return pad->devnum; + if (real_get == NULL) real_get = next_symbol("udev_device_get_devnum"); + if (real_get == NULL) return makedev(0, 0); + return real_get(device); +} + +/* + * A pad has no bus behind it, so it stands in for its own input device and has no other parent. + * The reference is a borrowed one either way. + */ +void *udev_device_get_parent_with_subsystem_devtype(void *device, const char *subsystem, + const char *devtype) { + static void *(*real_get)(void *, const char *, const char *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) { + if (subsystem != NULL && strcmp(subsystem, "input") == 0 && devtype == NULL) return pad; + return NULL; + } + if (real_get == NULL) real_get = next_symbol("udev_device_get_parent_with_subsystem_devtype"); + if (real_get == NULL) return NULL; + return real_get(device, subsystem, devtype); +} + +const char *udev_device_get_sysattr_value(void *device, const char *attr) { + static const char *(*real_get)(void *, const char *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return attr != NULL && strcmp(attr, "uevent") == 0 ? pad->uevent : NULL; + if (real_get == NULL) real_get = next_symbol("udev_device_get_sysattr_value"); + if (real_get == NULL) return NULL; + return real_get(device, attr); +} + +const char *udev_device_get_property_value(void *device, const char *key) { + static const char *(*real_get)(void *, const char *); + struct pad *pad = pad_for_device(device); + + if (pad != NULL) return key != NULL && strcmp(key, "ID_INPUT_JOYSTICK") == 0 ? "1" : NULL; + if (real_get == NULL) real_get = next_symbol("udev_device_get_property_value"); + if (real_get == NULL) return NULL; + return real_get(device, key); +} diff --git a/tools/linuxfs/preload/lock.c b/tools/linuxfs/preload/lock.c new file mode 100644 index 000000000..9cd269226 --- /dev/null +++ b/tools/linuxfs/preload/lock.c @@ -0,0 +1,106 @@ +/* + * File locking for the game library on external storage. + * + * The user's games are bound in from /storage/emulated, which Android serves over FUSE, and that + * filesystem implements no locking at all: flock() and fcntl()'s lock commands answer ENOSYS for + * every file under it. The Steam client takes a shared lock on each content file as it validates + * or updates a title and treats the refusal as a failure, so a download walks its progress back + * and forth instead of finishing; a game reading its own data hits the same wall. + * + * Where the kernel can lock, it does: the real call runs first and its answer stands, so files on + * the app's own f2fs storage keep genuine cross-process exclusion. Only the filesystem that cannot + * lock at all is answered here, and granting is the honest reading of it - a filesystem with no + * lock state has nothing to contend over, and the one other party that could contend is inside + * this same session. Reporting failure instead buys no safety and costs the download. + * + * F_GETLK is the exception: it asks who holds a lock, and on a filesystem holding none the true + * answer is "nobody", so it is reported unlocked rather than granted. + */ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stddef.h> +#include <sys/file.h> +#include <unistd.h> + +/* The filesystem refuses the whole operation, rather than declining this particular lock. */ +static int unsupported(int error) { + return error == ENOSYS || error == ENOTSUP || error == EOPNOTSUPP; +} + +int flock(int fd, int operation) { + static int (*real_flock)(int, int); + int result; + + if (real_flock == NULL) real_flock = dlsym(RTLD_NEXT, "flock"); + if (real_flock == NULL) return 0; + result = real_flock(fd, operation); + if (result < 0 && unsupported(errno)) return 0; + return result; +} + +/* + * fcntl()'s third argument is an int, a pointer, or absent depending on the command; only the + * lock commands are of interest here and all of those take a struct flock *. Anything else is + * passed straight through as a void * - wide enough for both an int and a pointer on every ABI + * the runtime is built for. + */ +static int is_lock_command(int cmd) { + switch (cmd) { + case F_SETLK: + case F_SETLKW: +#ifdef F_OFD_SETLK + case F_OFD_SETLK: +#endif +#ifdef F_OFD_SETLKW + case F_OFD_SETLKW: +#endif + return 1; + default: + return 0; + } +} + +static int is_lock_query(int cmd) { + switch (cmd) { + case F_GETLK: +#ifdef F_OFD_GETLK + case F_OFD_GETLK: +#endif + return 1; + default: + return 0; + } +} + +int fcntl(int fd, int cmd, ...) { + static int (*real_fcntl)(int, int, ...); + va_list ap; + void *arg; + int result; + + va_start(ap, cmd); + arg = va_arg(ap, void *); + va_end(ap); + + if (real_fcntl == NULL) real_fcntl = dlsym(RTLD_NEXT, "fcntl"); + if (real_fcntl == NULL) { + errno = ENOSYS; + return -1; + } + result = real_fcntl(fd, cmd, arg); + if (result >= 0 || !unsupported(errno)) return result; + if (is_lock_command(cmd)) return 0; + if (is_lock_query(cmd) && arg != NULL) { + ((struct flock *)arg)->l_type = F_UNLCK; + return 0; + } + return result; +} + +#if defined(__GLIBC__) && defined(F_SETLK64) +/* glibc's large-file build redirects both names; the runtime is 64-bit, so they are the same call. */ +int fcntl64(int fd, int cmd, ...) __attribute__((alias("fcntl"))); +#endif diff --git a/tools/linuxfs/preload/net.c b/tools/linuxfs/preload/net.c new file mode 100644 index 000000000..4a435075a --- /dev/null +++ b/tools/linuxfs/preload/net.c @@ -0,0 +1,247 @@ +/* + * Loopback socket ownership for Steam's IPC peer check. + * + * Steam validates a websocket peer by running lsof to find which process owns a 127.0.0.1 port. + * The Android sandbox denies both /proc/net/tcp and sock_diag, so no tool can answer. Every Steam + * process is preloaded with this library, so each records the ports it binds and connects, and the + * lsof Steam spawns is answered from that shared registry instead of being run. + */ +#define _GNU_SOURCE +#include <arpa/inet.h> +#include <spawn.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <linux/netlink.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> + +static const char *net_dir(void) { + static char dir[256]; + if (!dir[0]) { + const char *base = getenv("WN_SYSV_DIR"); + snprintf(dir, sizeof(dir), "%s/net", base && *base ? base : "/dev/shm/wnsysv"); + char parent[256]; + snprintf(parent, sizeof(parent), "%s", dir); + char *slash = strrchr(parent, '/'); + if (slash) { + *slash = '\0'; + mkdir(parent, 0777); + } + mkdir(dir, 0777); + } + return dir; +} + +/* Record that this process owns a loopback socket on its local port; peer is the far end's port, + * 0 for a listening socket. */ +static void record_port(int fd, unsigned peer) { + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) { + return; + } + unsigned port; + if (ss.ss_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in *)&ss; + if (in->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { + return; + } + port = ntohs(in->sin_port); + } else if (ss.ss_family == AF_INET6) { + port = ntohs(((struct sockaddr_in6 *)&ss)->sin6_port); + } else { + return; + } + if (port == 0) { + return; + } + char path[300]; + snprintf(path, sizeof(path), "%s/p%u", net_dir(), port); + char line[64]; + int n = snprintf(line, sizeof(line), "%d %d %d %u\n", (int)getpid(), (int)getppid(), (int)getuid(), peer); + int out = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (out >= 0) { + if (write(out, line, n) != n) { /* best effort */ } + close(out); + } +} + +static unsigned addr_port(const struct sockaddr *addr, socklen_t len) { + if (addr && addr->sa_family == AF_INET && len >= sizeof(struct sockaddr_in)) { + return ntohs(((const struct sockaddr_in *)addr)->sin_port); + } + if (addr && addr->sa_family == AF_INET6 && len >= sizeof(struct sockaddr_in6)) { + return ntohs(((const struct sockaddr_in6 *)addr)->sin6_port); + } + return 0; +} + +static unsigned peer_port(int fd) { + struct sockaddr_storage ps; + socklen_t plen = sizeof(ps); + if (getpeername(fd, (struct sockaddr *)&ps, &plen) != 0) { + return 0; + } + return addr_port((struct sockaddr *)&ps, plen); +} + +typedef int (*bind_fn)(int, const struct sockaddr *, socklen_t); +typedef int (*listen_fn)(int, int); +typedef int (*connect_fn)(int, const struct sockaddr *, socklen_t); +typedef int (*accept4_fn)(int, struct sockaddr *, socklen_t *, int); + +/* udevmon.c: a stand-in for the netlink socket the sandbox refuses, bound here without the kernel. */ +__attribute__((visibility("hidden"))) int wn_udevmon_stand_in(int fd); + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + static bind_fn real; + if (!real) real = (bind_fn)dlsym(RTLD_NEXT, "bind"); + if (wn_udevmon_stand_in(fd)) { + if (addr == NULL || len < sizeof(struct sockaddr_nl) || addr->sa_family != AF_NETLINK) { + errno = EINVAL; + return -1; + } + return 0; + } + int ret = real(fd, addr, len); + if (ret == 0) record_port(fd, 0); + return ret; +} + +int listen(int fd, int backlog) { + static listen_fn real; + if (!real) real = (listen_fn)dlsym(RTLD_NEXT, "listen"); + int ret = real(fd, backlog); + if (ret == 0) record_port(fd, 0); + return ret; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + static connect_fn real; + if (!real) real = (connect_fn)dlsym(RTLD_NEXT, "connect"); + int ret = real(fd, addr, len); + if (ret == 0 || errno == EINPROGRESS) { + /* The caller goes on to test for EINPROGRESS, which the bookkeeping must not disturb. */ + int saved = errno; + record_port(fd, addr_port(addr, len)); + errno = saved; + } + return ret; +} + +int accept4(int fd, struct sockaddr *addr, socklen_t *len, int flags) { + static accept4_fn real; + if (!real) real = (accept4_fn)dlsym(RTLD_NEXT, "accept4"); + int ret = real(fd, addr, len, flags); + if (ret >= 0) record_port(ret, peer_port(ret)); + return ret; +} + +int accept(int fd, struct sockaddr *addr, socklen_t *len) { + return accept4(fd, addr, len, 0); +} + +/* lsof would read the denied /proc/net/tcp; answer its -i TCP@host:port query from the registry + * instead of executing it. Recognised only when invoked as lsof with that query. */ +static int maybe_answer_lsof(const char *path, char *const argv[]) { + const char *base = strrchr(path, '/'); + base = base ? base + 1 : path; + if (strcmp(base, "lsof") != 0) { + return 0; + } + const char *port_str = NULL; + for (int i = 0; argv[i]; i++) { + const char *at = strstr(argv[i], "TCP@"); + if (at) { + const char *colon = strrchr(at, ':'); + if (colon) port_str = colon + 1; + } + } + if (!port_str) { + return 0; + } + char path_buf[300]; + snprintf(path_buf, sizeof(path_buf), "%s/p%u", net_dir(), (unsigned)atoi(port_str)); + int in = open(path_buf, O_RDONLY); + if (in < 0) { + _exit(1); /* lsof exit code for "nothing found" */ + } + char buf[64]; + int n = read(in, buf, sizeof(buf) - 1); + close(in); + if (n <= 0) { + _exit(1); + } + buf[n] = '\0'; + int pid = 0, ppid = 0, uid = 0; + unsigned peer = 0; + sscanf(buf, "%d %d %d %u", &pid, &ppid, &uid, &peer); + /* Steam splits the address on "->" and wants exactly two halves, taking the port it asked + * about from the left one; only a listening socket has no far end. */ + char out[160]; + int m = peer ? snprintf(out, sizeof(out), "p%d\nR%d\nu%d\nn127.0.0.1:%u->127.0.0.1:%u\n", + pid, ppid, uid, (unsigned)atoi(port_str), peer) + : snprintf(out, sizeof(out), "p%d\nR%d\nu%d\nn127.0.0.1:%u\n", + pid, ppid, uid, (unsigned)atoi(port_str)); + if (write(1, out, m) != m) { /* best effort */ } + _exit(0); +} + +int execve(const char *path, char *const argv[], char *const envp[]) { + static int (*real)(const char *, char *const[], char *const[]); + if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve"); + maybe_answer_lsof(path, argv); + return real(path, argv, envp); +} + +/* popen(3) and posix_spawn(3) reach lsof without going through the execve symbol above. The + * check is cheap, so a spawned lsof is answered here too by handling it in a short-lived child. */ +static int answer_lsof_via_spawn(const char *path, char *const argv[], pid_t *pid) { + pid_t child = fork(); + if (child < 0) { + return errno; + } + if (child == 0) { + maybe_answer_lsof(path, argv); + _exit(127); + } + if (pid) { + *pid = child; + } + return 0; +} + +int execvp(const char *file, char *const argv[]) { + static int (*real)(const char *, char *const[]); + if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execvp"); + maybe_answer_lsof(file, argv); + return real(file, argv); +} + +int execv(const char *path, char *const argv[]) { + static int (*real)(const char *, char *const[]); + if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execv"); + maybe_answer_lsof(path, argv); + return real(path, argv); +} + +int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { + const char *base = strrchr(path, '/'); + base = base ? base + 1 : path; + if (strcmp(base, "lsof") == 0 && !fa) { + return answer_lsof_via_spawn(path, argv, pid); + } + static int (*real)(pid_t *, const char *, const posix_spawn_file_actions_t *, + const posix_spawnattr_t *, char *const[], char *const[]); + if (!real) real = (int (*)(pid_t *, const char *, const posix_spawn_file_actions_t *, + const posix_spawnattr_t *, char *const[], char *const[]))dlsym(RTLD_NEXT, "posix_spawn"); + return real(pid, path, fa, attr, argv, envp); +} diff --git a/tools/linuxfs/preload/netif.c b/tools/linuxfs/preload/netif.c new file mode 100644 index 000000000..bed94f5b6 --- /dev/null +++ b/tools/linuxfs/preload/netif.c @@ -0,0 +1,503 @@ +/* + * Network interfaces for a sandbox that hides them. + * + * Android denies an app the rtnetlink dump behind getifaddrs() and if_nameindex(), the hardware + * address ioctl, and every table under /proc/net. Wine builds its adapter, address, route and + * neighbour tables from exactly those, so a game saw a machine with no adapter, no address, no + * gateway and no MAC, and one that checks its adapters before going online waited for good. + * The app knows the real link from ConnectivityManager and writes it to /etc/winnative-net; + * whatever the kernel refuses is answered from that file. Anything the kernel does answer wins. + */ +#define _GNU_SOURCE 1 +#include <arpa/inet.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <ifaddrs.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netpacket/packet.h> +#include <netinet/in.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <unistd.h> + +#define LINK_FILE "/etc/winnative-net" +#define LO_NAME "lo" +#define LO_INDEX 1 +#define LO_MTU 65536 +#define MAX_ADDRS 16 + +struct link_addr { + int family; + unsigned char bytes[16]; + unsigned prefix; +}; + +struct link { + char name[IFNAMSIZ]; + unsigned index; + int mtu; + unsigned char mac[6]; + struct link_addr addrs[MAX_ADDRS]; + int addr_count; + struct link_addr gateways[2]; + int gateway_count; +}; + +static int parse_addr(const char *text, struct link_addr *out) { + memset(out, 0, sizeof(*out)); + if (inet_pton(AF_INET, text, out->bytes) == 1) out->family = AF_INET; + else if (inet_pton(AF_INET6, text, out->bytes) == 1) out->family = AF_INET6; + else return 0; + return 1; +} + +/* Read on every call: the app rewrites the file when the device changes network. */ +static int load_link(struct link *link) { + typedef FILE *(*fopen_fn)(const char *, const char *); + fopen_fn real_fopen = (fopen_fn) dlsym(RTLD_NEXT, "fopen"); + FILE *f = real_fopen ? real_fopen(LINK_FILE, "re") : NULL; + if (f == NULL) return 0; + + memset(link, 0, sizeof(*link)); + char line[160], text[64]; + unsigned m[6], prefix; + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "if %15s %u %d", link->name, &link->index, &link->mtu) == 3) continue; + if (sscanf(line, "mac %2x:%2x:%2x:%2x:%2x:%2x", m, m + 1, m + 2, m + 3, m + 4, m + 5) == 6) { + for (int i = 0; i < 6; i++) link->mac[i] = (unsigned char) m[i]; + } else if (sscanf(line, "addr %63s %u", text, &prefix) == 2) { + struct link_addr addr; + if (link->addr_count < MAX_ADDRS && parse_addr(text, &addr) + && prefix <= (addr.family == AF_INET ? 32u : 128u)) { + addr.prefix = prefix; + link->addrs[link->addr_count++] = addr; + } + } else if (sscanf(line, "gw %63s", text) == 1) { + struct link_addr addr; + if (link->gateway_count < 2 && parse_addr(text, &addr)) { + link->gateways[link->gateway_count++] = addr; + } + } + } + fclose(f); + return link->name[0] != '\0' && link->index > LO_INDEX && link->addr_count > 0; +} + +static void prefix_mask(int family, unsigned prefix, unsigned char *out) { + int len = family == AF_INET ? 4 : 16; + for (int i = 0; i < len; i++, prefix -= prefix < 8 ? prefix : 8) { + out[i] = prefix >= 8 ? 0xff : (unsigned char) (0xff00 >> prefix); + } +} + +static int is_link_local(const struct link_addr *addr) { + return addr->family == AF_INET6 && addr->bytes[0] == 0xfe && (addr->bytes[1] & 0xc0) == 0x80; +} + +/* One allocation per list. ifa_data of the head points at the head itself, which glibc never + * does, so freeifaddrs() can tell the two kinds of list apart without keeping any state. */ +struct entry { + struct ifaddrs ifa; + struct sockaddr_storage addr, mask, extra; + char name[IFNAMSIZ]; +}; + +static struct entry *fill_entry(struct entry *e, const char *name, unsigned flags) { + snprintf(e->name, sizeof(e->name), "%s", name); + e->ifa.ifa_name = e->name; + e->ifa.ifa_flags = flags; + e->ifa.ifa_addr = (struct sockaddr *) &e->addr; + e[-1].ifa.ifa_next = &e->ifa; + return e; +} + +static void fill_packet(struct entry *e, unsigned index, const unsigned char *mac) { + struct sockaddr_ll *ll = (struct sockaddr_ll *) &e->addr; + ll->sll_family = AF_PACKET; + ll->sll_ifindex = (int) index; + ll->sll_hatype = mac ? ARPHRD_ETHER : ARPHRD_LOOPBACK; + ll->sll_halen = 6; + if (mac) memcpy(ll->sll_addr, mac, 6); +} + +static void fill_inet(struct entry *e, const struct link_addr *addr, unsigned index) { + unsigned char mask[16]; + prefix_mask(addr->family, addr->prefix, mask); + e->ifa.ifa_netmask = (struct sockaddr *) &e->mask; + if (addr->family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *) &e->addr; + struct sockaddr_in *smask = (struct sockaddr_in *) &e->mask; + sin->sin_family = smask->sin_family = AF_INET; + memcpy(&sin->sin_addr, addr->bytes, 4); + memcpy(&smask->sin_addr, mask, 4); + if (e->ifa.ifa_flags & IFF_BROADCAST) { + struct sockaddr_in *bcast = (struct sockaddr_in *) &e->extra; + bcast->sin_family = AF_INET; + bcast->sin_addr.s_addr = sin->sin_addr.s_addr | ~smask->sin_addr.s_addr; + e->ifa.ifa_broadaddr = (struct sockaddr *) bcast; + } + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &e->addr; + struct sockaddr_in6 *smask = (struct sockaddr_in6 *) &e->mask; + sin6->sin6_family = smask->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, addr->bytes, 16); + memcpy(&smask->sin6_addr, mask, 16); + if (is_link_local(addr)) sin6->sin6_scope_id = index; + } +} + +static const struct link_addr LO_V4 = { AF_INET, { 127, 0, 0, 1 }, 8 }; +static const struct link_addr LO_V6 = { AF_INET6, { [15] = 1 }, 128 }; + +static int real_list_usable(const struct ifaddrs *list) { + for (const struct ifaddrs *e = list; e; e = e->ifa_next) { + if (e->ifa_addr && e->ifa_addr->sa_family == AF_INET && !(e->ifa_flags & IFF_LOOPBACK)) { + return 1; + } + } + return 0; +} + +int getifaddrs(struct ifaddrs **out) { + typedef int (*getifaddrs_fn)(struct ifaddrs **); + typedef void (*freeifaddrs_fn)(struct ifaddrs *); + getifaddrs_fn real = (getifaddrs_fn) dlsym(RTLD_NEXT, "getifaddrs"); + struct ifaddrs *list = NULL; + int rc = real(&list); + int saved = errno; + if (rc == 0 && real_list_usable(list)) { + *out = list; + return 0; + } + + struct link link; + if (!load_link(&link)) { + if (rc == 0) *out = list; + errno = saved; + return rc; + } + if (rc == 0 && list) ((freeifaddrs_fn) dlsym(RTLD_NEXT, "freeifaddrs"))(list); + + /* A leading slot keeps fill_entry()'s back link in bounds; the list starts after it. */ + struct entry *block = calloc((size_t) link.addr_count + 5, sizeof(*block)); + if (block == NULL) { + errno = ENOMEM; + return -1; + } + unsigned lo_flags = IFF_UP | IFF_RUNNING | IFF_LOOPBACK; + unsigned up_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST; + struct entry *e = block; + fill_packet(fill_entry(++e, LO_NAME, lo_flags), LO_INDEX, NULL); + fill_inet(fill_entry(++e, LO_NAME, lo_flags), &LO_V4, LO_INDEX); + fill_inet(fill_entry(++e, LO_NAME, lo_flags), &LO_V6, LO_INDEX); + fill_packet(fill_entry(++e, link.name, up_flags), link.index, link.mac); + for (int i = 0; i < link.addr_count; i++) { + fill_inet(fill_entry(++e, link.name, up_flags), &link.addrs[i], link.index); + } + block[1].ifa.ifa_data = &block[1].ifa; + *out = &block[1].ifa; + return 0; +} + +void freeifaddrs(struct ifaddrs *list) { + typedef void (*freeifaddrs_fn)(struct ifaddrs *); + if (list == NULL) return; + if (list->ifa_data == list) { + free((struct entry *) list - 1); + return; + } + ((freeifaddrs_fn) dlsym(RTLD_NEXT, "freeifaddrs"))(list); +} + +/* Names live in the same allocation as the array, right behind it; glibc strdup()s each one. */ +struct name_block { + struct if_nameindex index[3]; + char names[2][IFNAMSIZ]; +}; + +struct if_nameindex *if_nameindex(void) { + typedef struct if_nameindex *(*if_nameindex_fn)(void); + struct if_nameindex *list = ((if_nameindex_fn) dlsym(RTLD_NEXT, "if_nameindex"))(); + int saved = errno; + if (list && list[0].if_index != 0) return list; + + struct link link; + struct name_block *block; + if (!load_link(&link) || (block = calloc(1, sizeof(*block))) == NULL) { + errno = saved; + return list; + } + if (list) if_freenameindex(list); + snprintf(block->names[0], IFNAMSIZ, "%s", LO_NAME); + snprintf(block->names[1], IFNAMSIZ, "%s", link.name); + block->index[0] = (struct if_nameindex) { LO_INDEX, block->names[0] }; + block->index[1] = (struct if_nameindex) { link.index, block->names[1] }; + return block->index; +} + +void if_freenameindex(struct if_nameindex *list) { + typedef void (*if_freenameindex_fn)(struct if_nameindex *); + if (list == NULL) return; + if (list[0].if_name == ((struct name_block *) list)->names[0]) { + free(list); + return; + } + ((if_freenameindex_fn) dlsym(RTLD_NEXT, "if_freenameindex"))(list); +} + +unsigned int if_nametoindex(const char *name) { + typedef unsigned int (*if_nametoindex_fn)(const char *); + unsigned int index = ((if_nametoindex_fn) dlsym(RTLD_NEXT, "if_nametoindex"))(name); + if (index != 0 || name == NULL) return index; + int saved = errno; + struct link link; + if (strcmp(name, LO_NAME) == 0) return LO_INDEX; + if (load_link(&link) && strcmp(name, link.name) == 0) return link.index; + errno = saved; + return 0; +} + +char *if_indextoname(unsigned int index, char name[IF_NAMESIZE]) { + typedef char *(*if_indextoname_fn)(unsigned int, char *); + char *found = ((if_indextoname_fn) dlsym(RTLD_NEXT, "if_indextoname"))(index, name); + if (found || name == NULL) return found; + int saved = errno; + struct link link; + if (index == LO_INDEX) return strcpy(name, LO_NAME); + if (load_link(&link) && index == link.index) return strcpy(name, link.name); + errno = saved; + return NULL; +} + +static int mac_unusable(const struct ifreq *ifr) { + const unsigned char *a = (const unsigned char *) ifr->ifr_hwaddr.sa_data; + if (ifr->ifr_hwaddr.sa_family != ARPHRD_ETHER) return 1; + /* Android hands out 02:00:00:00:00:00 where it hides the real address. */ + int rest = a[1] | a[2] | a[3] | a[4] | a[5]; + return (a[0] & 0x03) || (rest == 0 && (a[0] == 0 || a[0] == 0x02)); +} + +static const struct link_addr *first_v4(const struct link *link) { + for (int i = 0; i < link->addr_count; i++) { + if (link->addrs[i].family == AF_INET) return &link->addrs[i]; + } + return NULL; +} + +static int answer_ifreq(unsigned long request, struct ifreq *ifr, int real_rc) { + char name[IFNAMSIZ + 1] = { 0 }; + memcpy(name, ifr->ifr_name, IFNAMSIZ); + int loopback = strcmp(name, LO_NAME) == 0; + struct link link; + if (!load_link(&link) || (!loopback && strcmp(name, link.name) != 0)) return 0; + + if (request == SIOCGIFHWADDR) { + if (real_rc == 0 && (loopback || !mac_unusable(ifr))) return 0; + memset(&ifr->ifr_hwaddr, 0, sizeof(ifr->ifr_hwaddr)); + ifr->ifr_hwaddr.sa_family = loopback ? ARPHRD_LOOPBACK : ARPHRD_ETHER; + if (!loopback) memcpy(ifr->ifr_hwaddr.sa_data, link.mac, 6); + return 1; + } + if (real_rc == 0) return 0; + + const struct link_addr *v4 = loopback ? &LO_V4 : first_v4(&link); + struct sockaddr_in *sin = (struct sockaddr_in *) &ifr->ifr_addr; + unsigned char mask[16]; + switch (request) { + case SIOCGIFFLAGS: + ifr->ifr_flags = loopback ? IFF_UP | IFF_RUNNING | IFF_LOOPBACK + : IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST; + return 1; + case SIOCGIFMTU: + ifr->ifr_mtu = loopback ? LO_MTU : link.mtu; + return 1; + case SIOCGIFINDEX: + ifr->ifr_ifindex = loopback ? LO_INDEX : (int) link.index; + return 1; + case SIOCGIFADDR: + case SIOCGIFNETMASK: + case SIOCGIFBRDADDR: + if (v4 == NULL) return 0; + prefix_mask(AF_INET, v4->prefix, mask); + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + for (int i = 0; i < 4; i++) { + unsigned char *out = (unsigned char *) &sin->sin_addr + i; + *out = request == SIOCGIFNETMASK ? mask[i] + : request == SIOCGIFADDR ? v4->bytes[i] + : (unsigned char) (v4->bytes[i] | ~mask[i]); + } + return 1; + default: + return 0; + } +} + +static int is_link_query(unsigned long request) { + return request == SIOCGIFHWADDR || request == SIOCGIFFLAGS || request == SIOCGIFMTU + || request == SIOCGIFINDEX || request == SIOCGIFADDR || request == SIOCGIFNETMASK + || request == SIOCGIFBRDADDR; +} + +/* Every DRM and evdev call comes through here, so the lookup is done once. Two threads racing + * to store it store the same pointer. */ +int ioctl(int fd, unsigned long request, ...) { + typedef int (*ioctl_fn)(int, unsigned long, void *); + static ioctl_fn real; + va_list ap; + va_start(ap, request); + void *arg = va_arg(ap, void *); + va_end(ap); + + if (real == NULL) real = (ioctl_fn) dlsym(RTLD_NEXT, "ioctl"); + int rc = real(fd, request, arg); + if (arg == NULL || !is_link_query(request)) return rc; + int saved = errno; + if (answer_ifreq(request, (struct ifreq *) arg, rc)) return 0; + errno = saved; + return rc; +} + +static void print_v6(FILE *out, const unsigned char *bytes) { + for (int i = 0; i < 16; i++) fprintf(out, "%02x", bytes[i]); +} + +static unsigned v6_scope(const struct link_addr *addr) { + if (memcmp(addr, &LO_V6, sizeof(*addr)) == 0) return 0x10; + return is_link_local(addr) ? 0x20 : 0x00; +} + +static void write_table(FILE *out, const char *table, const struct link *link) { + static const unsigned char zero[16]; + unsigned char mask[16]; + if (strcmp(table, "route") == 0) { + fprintf(out, "Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n"); + for (int i = 0; i < link->gateway_count; i++) { + if (link->gateways[i].family != AF_INET) continue; + unsigned gw; + memcpy(&gw, link->gateways[i].bytes, 4); + fprintf(out, "%s\t00000000\t%08X\t0003\t0\t0\t0\t00000000\t0\t0\t0\n", link->name, gw); + } + for (int i = 0; i < link->addr_count; i++) { + if (link->addrs[i].family != AF_INET) continue; + unsigned addr, net; + prefix_mask(AF_INET, link->addrs[i].prefix, mask); + memcpy(&addr, link->addrs[i].bytes, 4); + memcpy(&net, mask, 4); + fprintf(out, "%s\t%08X\t00000000\t0001\t0\t0\t0\t%08X\t0\t0\t0\n", + link->name, addr & net, net); + } + } else if (strcmp(table, "ipv6_route") == 0) { + for (int i = 0; i < link->gateway_count; i++) { + if (link->gateways[i].family != AF_INET6) continue; + print_v6(out, zero); + fprintf(out, " 00 "); + print_v6(out, zero); + fprintf(out, " 00 "); + print_v6(out, link->gateways[i].bytes); + fprintf(out, " 00000400 00000001 00000000 00000003 %8s\n", link->name); + } + for (int i = 0; i < link->addr_count; i++) { + if (link->addrs[i].family != AF_INET6) continue; + unsigned char net[16]; + prefix_mask(AF_INET6, link->addrs[i].prefix, mask); + for (int b = 0; b < 16; b++) net[b] = link->addrs[i].bytes[b] & mask[b]; + print_v6(out, net); + fprintf(out, " %02x ", link->addrs[i].prefix); + print_v6(out, zero); + fprintf(out, " 00 "); + print_v6(out, zero); + fprintf(out, " 00000100 00000001 00000000 00000001 %8s\n", link->name); + } + } else if (strcmp(table, "if_inet6") == 0) { + print_v6(out, LO_V6.bytes); + fprintf(out, " %02x %02x %02x 80 %8s\n", LO_INDEX, LO_V6.prefix, v6_scope(&LO_V6), LO_NAME); + for (int i = 0; i < link->addr_count; i++) { + if (link->addrs[i].family != AF_INET6) continue; + print_v6(out, link->addrs[i].bytes); + fprintf(out, " %02x %02x %02x 80 %8s\n", link->index, link->addrs[i].prefix, + v6_scope(&link->addrs[i]), link->name); + } + } else if (strcmp(table, "dev") == 0) { + fprintf(out, "Inter-| Receive | Transmit\n" + " face |bytes packets errs drop fifo frame compressed multicast|" + "bytes packets errs drop fifo colls carrier compressed\n"); + fprintf(out, "%6s: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", LO_NAME); + fprintf(out, "%6s: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", link->name); + } else if (strcmp(table, "arp") == 0) { + fprintf(out, "IP address HW type Flags HW address Mask Device\n"); + } else { + fprintf(out, "1\n"); + } +} + +static const char *denied_table(const char *path) { + static const char *const tables[] = { "route", "ipv6_route", "if_inet6", "dev", "arp" }; + if (strncmp(path, "/proc/net/", 10) == 0) { + for (size_t i = 0; i < sizeof(tables) / sizeof(tables[0]); i++) { + if (strcmp(path + 10, tables[i]) == 0) return tables[i]; + } + return NULL; + } + size_t len = strlen(path); + if (strncmp(path, "/sys/class/net/", 15) == 0 && len > 8 && strcmp(path + len - 8, "/carrier") == 0) { + return "carrier"; + } + return NULL; +} + +/* A memfd rather than fmemopen(): the stream owns its contents, so fclose() frees everything. */ +static FILE *synthesize(const char *table) { + struct link link; + if (!load_link(&link)) return NULL; + int fd = memfd_create("winnative-net", MFD_CLOEXEC); + if (fd < 0) return NULL; + FILE *out = fdopen(fd, "w+"); + if (out == NULL) { + close(fd); + return NULL; + } + write_table(out, table, &link); + if (fflush(out) != 0) { + fclose(out); + return NULL; + } + rewind(out); + return out; +} + +/* tracer.c */ +int wn_status_without_tracer(const char *path, int flags) __attribute__((visibility("hidden"))); + +static FILE *open_stream(const char *symbol, const char *path, const char *mode) { + typedef FILE *(*fopen_fn)(const char *, const char *); + int status = mode && mode[0] == 'r' && !strchr(mode, '+') + ? wn_status_without_tracer(path, O_RDONLY | O_CLOEXEC) : -1; + if (status >= 0) { + FILE *copy = fdopen(status, "r"); + if (copy == NULL) close(status); + return copy; + } + FILE *f = ((fopen_fn) dlsym(RTLD_NEXT, symbol))(path, mode); + if (f || path == NULL || mode == NULL || mode[0] != 'r') return f; + int saved = errno; + const char *table = denied_table(path); + if (table && (f = synthesize(table)) != NULL) return f; + errno = saved; + return NULL; +} + +FILE *fopen(const char *path, const char *mode) { + return open_stream("fopen", path, mode); +} + +FILE *fopen64(const char *path, const char *mode) { + return open_stream("fopen64", path, mode); +} diff --git a/tools/linuxfs/preload/opens.c b/tools/linuxfs/preload/opens.c new file mode 100644 index 000000000..5c0548512 --- /dev/null +++ b/tools/linuxfs/preload/opens.c @@ -0,0 +1,96 @@ +/* + * The adjustments a session's opens need. + * + * O_NOATIME on the game library: the user's games are bound in from shared storage, which Android + * serves over FUSE with every file owned by its media provider rather than by the app. O_NOATIME + * is reserved for a file's owner, so the kernel refuses such an open outright with EPERM. The + * Steam client opens every content file it reads that way, and treats the refusal as the file + * being unreadable or missing: a validation reports corrupt or missing files and an update is + * cancelled. The flag only asks the filesystem to skip an access-time update; the open is retried + * without it. Every other refusal is passed through untouched, and a file the process may open + * with the flag still is. + * + * The kernel's trace marker: tracefs is world-writable here, so the client's tier0 opens + * /sys/kernel/tracing/trace_marker at startup - three times over, once per module that profiles - + * and writes a "[%s] tgid=%d begin_ctx=%lld" line for every scope it enters. Nothing in the + * session reads those markers, and they are one more thing that can land in the wrong file when + * a descriptor number changes hands (tracer.c has the cause). They have been found appended to an + * app manifest, which the client then cannot parse, to a download's state file, and to content + * chunks, which it quarantines as corrupt. Refused the descriptor, tier0 writes nothing at all and + * has nothing to lose. + * + * A process status is served without the container's tracer in it; see tracer.c. + */ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stddef.h> +#include <string.h> +#include <sys/types.h> + +int wn_status_without_tracer(const char *path, int flags) __attribute__((visibility("hidden"))); + +static int retry_without_noatime(int fd, int flags) { + return fd < 0 && errno == EPERM && (flags & O_NOATIME); +} + +/* Both spellings tier0 tries, the debugfs one first. */ +static int is_trace_marker(const char *path) { + return path != NULL + && (strcmp(path, "/sys/kernel/tracing/trace_marker") == 0 + || strcmp(path, "/sys/kernel/debug/tracing/trace_marker") == 0); +} + +int open(const char *path, int flags, ...) { + static int (*real_open)(const char *, int, ...); + va_list ap; + mode_t mode; + int fd; + + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + if (is_trace_marker(path)) { + errno = EACCES; + return -1; + } + if ((fd = wn_status_without_tracer(path, flags)) >= 0) return fd; + if (real_open == NULL) real_open = dlsym(RTLD_NEXT, "open"); + if (real_open == NULL) { + errno = ENOSYS; + return -1; + } + fd = real_open(path, flags, mode); + if (retry_without_noatime(fd, flags)) fd = real_open(path, flags & ~O_NOATIME, mode); + return fd; +} + +int openat(int dirfd, const char *path, int flags, ...) { + static int (*real_openat)(int, const char *, int, ...); + va_list ap; + mode_t mode; + int fd; + + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + if (is_trace_marker(path)) { + errno = EACCES; + return -1; + } + if ((fd = wn_status_without_tracer(path, flags)) >= 0) return fd; + if (real_openat == NULL) real_openat = dlsym(RTLD_NEXT, "openat"); + if (real_openat == NULL) { + errno = ENOSYS; + return -1; + } + fd = real_openat(dirfd, path, flags, mode); + if (retry_without_noatime(fd, flags)) fd = real_openat(dirfd, path, flags & ~O_NOATIME, mode); + return fd; +} + +/* The runtime is 64-bit, so the large-file names are the same calls. */ +int open64(const char *path, int flags, ...) __attribute__((alias("open"))); +int openat64(int dirfd, const char *path, int flags, ...) __attribute__((alias("openat"))); diff --git a/tools/linuxfs/preload/robust.c b/tools/linuxfs/preload/robust.c new file mode 100644 index 000000000..0f0b581ca --- /dev/null +++ b/tools/linuxfs/preload/robust.c @@ -0,0 +1,79 @@ +/* + * get_robust_list(2) for the session's own threads. + * + * Android's app policy refuses the call, and Steam's cross-process mutex insists on seeing the + * head glibc registered. The kernel's list protocol gives it away: a robust mutex this thread + * holds is linked directly behind the registered head, so locking a private one and following + * the link back finds the head without depending on libc's layout. + */ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <errno.h> +#include <linux/futex.h> +#include <pthread.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/syscall.h> +#include <sys/uio.h> +#include <unistd.h> + +static long (*real_syscall)(long, ...); + +static struct robust_list_head *thread_robust_head(void) { + pthread_mutexattr_t attr; + pthread_mutex_t mutex; + struct robust_list_head *head = NULL; + pthread_mutexattr_init(&attr); + pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(&mutex, &attr); + pthread_mutexattr_destroy(&attr); + if (pthread_mutex_lock(&mutex) != 0) { + return NULL; + } + /* The mutex holds tids and lock words beside the link, so a word is only followed + * through a read that fails instead of faulting on a non-pointer. */ + uintptr_t lo = (uintptr_t)&mutex, hi = lo + sizeof(mutex); + for (uintptr_t p = lo; p + sizeof(void *) <= hi && !head; p += sizeof(void *)) { + struct robust_list_head *candidate = *(struct robust_list_head **)p; + struct robust_list_head copy; + struct iovec local = {©, sizeof(copy)}, remote = {candidate, sizeof(copy)}; + if ((uintptr_t)candidate & (sizeof(void *) - 1)) { + continue; + } + if (process_vm_readv(getpid(), &local, 1, &remote, 1, 0) != (ssize_t)sizeof(copy)) { + continue; + } + uintptr_t entry = (uintptr_t)copy.list.next; + if (entry >= lo && entry < hi && copy.futex_offset < 0 && copy.futex_offset > -256) { + head = candidate; + } + } + pthread_mutex_unlock(&mutex); + pthread_mutex_destroy(&mutex); + return head; +} + +long syscall(long number, ...) { + va_list ap; + long args[6]; + va_start(ap, number); + for (int i = 0; i < 6; i++) { + args[i] = va_arg(ap, long); + } + va_end(ap); + if (number == SYS_get_robust_list && (args[0] == 0 || args[0] == gettid())) { + struct robust_list_head *head = thread_robust_head(); + if (!head) { + errno = ENOSYS; + return -1; + } + *(struct robust_list_head **)args[1] = head; + *(size_t *)args[2] = sizeof(*head); + return 0; + } + if (!real_syscall) { + real_syscall = (long (*)(long, ...)) dlsym(RTLD_NEXT, "syscall"); + } + return real_syscall(number, args[0], args[1], args[2], args[3], args[4], args[5]); +} diff --git a/tools/linuxfs/preload/sysv.c b/tools/linuxfs/preload/sysv.c new file mode 100644 index 000000000..1e5c134e2 --- /dev/null +++ b/tools/linuxfs/preload/sysv.c @@ -0,0 +1,408 @@ +/* System V semaphores and shared memory for programs in the Linux runtime. + * + * Android kernels are built without System V IPC, so semget/shmget fail with ENOSYS. Steam's tier0 + * creates its client-instance semaphore with semget and gives up when that fails. This library is + * LD_PRELOADed into the session and keeps each object in a file under /dev/shm (a bound directory + * the whole session shares): a semaphore set is an array of counters waited on with futexes, a + * segment is a plain file that shmat maps. Objects are found by the inode of their file, which is + * what the id is. semop applies its operations one by one (no all-or-nothing across a set) and + * SEM_UNDO is accepted but not performed - enough for the client, which uses single-counter sets. + * Message queues stay unsupported. */ +#define _GNU_SOURCE +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <linux/futex.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdatomic.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ipc.h> +#include <sys/mman.h> +#include <sys/sem.h> +#include <sys/shm.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> + +#define DIR_DEFAULT "/dev/shm/wnsysv" +#define SEM_MAGIC 0x574e5345u /* "WNSE" */ +#define MAX_OBJECTS 64 + +struct sem_header { + uint32_t magic; + uint32_t nsems; + uint32_t key; + uint32_t mode; + int32_t creator_pid; + int32_t pad[11]; +}; +/* The counters follow the header, one 32-bit word each (a futex word). */ + +struct sem_map { int id; struct sem_header *hdr; size_t len; }; +struct shm_map { void *addr; size_t len; int id; }; + +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; +static struct sem_map g_sems[MAX_OBJECTS]; +static struct shm_map g_shms[MAX_OBJECTS]; +static char g_dir[PATH_MAX - NAME_MAX - 2]; + +static const char *object_dir(void) { + if (!g_dir[0]) { + const char *d = getenv("WN_SYSV_DIR"); + snprintf(g_dir, sizeof(g_dir), "%s", d && *d ? d : DIR_DEFAULT); + mkdir(g_dir, 0700); + } + return g_dir; +} + +static int object_id(int fd) { + struct stat st; + if (fstat(fd, &st) != 0) return -1; + return (int)(st.st_ino & 0x7fffffff); +} + +/* Open the object file of kind ("sem"/"shm") for key, creating it as flags ask. */ +static int open_object(const char *kind, key_t key, int flags, int *created) { + char path[PATH_MAX]; + int oflags = O_RDWR | O_CLOEXEC; + *created = 0; + if (key == IPC_PRIVATE) { + static atomic_uint serial; + snprintf(path, sizeof(path), "%s/%s.p%d.%u", object_dir(), kind, (int)getpid(), + atomic_fetch_add(&serial, 1)); + oflags |= O_CREAT | O_EXCL; + } else { + snprintf(path, sizeof(path), "%s/%s.k%08x", object_dir(), kind, (unsigned)key); + if (flags & IPC_CREAT) oflags |= O_CREAT; + if ((flags & (IPC_CREAT | IPC_EXCL)) == (IPC_CREAT | IPC_EXCL)) oflags |= O_EXCL; + } + int fd = open(path, oflags, (flags & 0777) ? (flags & 0777) : 0600); + if (fd < 0) return -1; + struct stat st; + if (fstat(fd, &st) == 0 && st.st_size == 0) *created = 1; + return fd; +} + +/* The object file for an id, found by inode. */ +static int open_by_id(const char *kind, int id) { + DIR *d = opendir(object_dir()); + if (!d) return -1; + struct dirent *e; + int fd = -1; + size_t klen = strlen(kind); + while ((e = readdir(d))) { + if (strncmp(e->d_name, kind, klen) || e->d_name[klen] != '.') continue; + if ((int)(e->d_ino & 0x7fffffff) != id) continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", g_dir, e->d_name); + fd = open(path, O_RDWR | O_CLOEXEC); + break; + } + closedir(d); + if (fd < 0) errno = EINVAL; + return fd; +} + +static int unlink_by_id(const char *kind, int id) { + DIR *d = opendir(object_dir()); + if (!d) return -1; + struct dirent *e; + int rc = -1; + size_t klen = strlen(kind); + while ((e = readdir(d))) { + if (strncmp(e->d_name, kind, klen) || e->d_name[klen] != '.') continue; + if ((int)(e->d_ino & 0x7fffffff) != id) continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", g_dir, e->d_name); + rc = unlink(path); + break; + } + closedir(d); + if (rc < 0) errno = EINVAL; + return rc; +} + +static long futex(void *addr, int op, int val, const struct timespec *timeout) { + return syscall(SYS_futex, addr, op, val, timeout, NULL, 0); +} + +/* ------------------------------------------------------------------ semaphores */ + +static struct sem_map *sem_lookup(int id) { + for (int i = 0; i < MAX_OBJECTS; i++) + if (g_sems[i].hdr && g_sems[i].id == id) return &g_sems[i]; + int fd = open_by_id("sem", id); + if (fd < 0) return NULL; + struct stat st; + if (fstat(fd, &st) != 0 || st.st_size < (off_t)sizeof(struct sem_header)) { close(fd); errno = EINVAL; return NULL; } + void *m = mmap(NULL, (size_t)st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + close(fd); + if (m == MAP_FAILED) return NULL; + struct sem_header *h = m; + if (h->magic != SEM_MAGIC) { munmap(m, (size_t)st.st_size); errno = EINVAL; return NULL; } + for (int i = 0; i < MAX_OBJECTS; i++) { + if (g_sems[i].hdr) continue; + g_sems[i].id = id; g_sems[i].hdr = h; g_sems[i].len = (size_t)st.st_size; + return &g_sems[i]; + } + munmap(m, (size_t)st.st_size); + errno = ENOSPC; + return NULL; +} + +static void sem_forget(int id) { + for (int i = 0; i < MAX_OBJECTS; i++) + if (g_sems[i].hdr && g_sems[i].id == id) { munmap(g_sems[i].hdr, g_sems[i].len); g_sems[i].hdr = NULL; } +} + +static _Atomic int32_t *sem_words(struct sem_header *h) { return (_Atomic int32_t *)(h + 1); } + +int semget(key_t key, int nsems, int semflg) { + if (nsems < 0 || nsems > 250) { errno = EINVAL; return -1; } + int created; + int fd = open_object("sem", key, semflg, &created); + if (fd < 0) return -1; + if (created) { + if (nsems == 0) { close(fd); errno = EINVAL; return -1; } + size_t len = sizeof(struct sem_header) + (size_t)nsems * sizeof(int32_t); + if (ftruncate(fd, (off_t)len) != 0) { close(fd); return -1; } + struct sem_header h = {.magic = SEM_MAGIC, .nsems = (uint32_t)nsems, .key = (uint32_t)key, + .mode = (uint32_t)(semflg & 0777), .creator_pid = (int32_t)getpid()}; + if (pwrite(fd, &h, sizeof(h), 0) != (ssize_t)sizeof(h)) { close(fd); return -1; } + } else { + struct sem_header h; + if (pread(fd, &h, sizeof(h), 0) != (ssize_t)sizeof(h) || h.magic != SEM_MAGIC) { close(fd); errno = EINVAL; return -1; } + if (nsems > 0 && (uint32_t)nsems > h.nsems) { close(fd); errno = EINVAL; return -1; } + } + int id = object_id(fd); + close(fd); + return id; +} + +static int sem_wait_op(_Atomic int32_t *word, short op, short flg, const struct timespec *timeout) { + for (;;) { + int32_t v = atomic_load(word); + if (op == 0) { + if (v == 0) return 0; + } else if (v >= -op) { + if (atomic_compare_exchange_weak(word, &v, v + op)) return 0; + continue; + } + if (flg & IPC_NOWAIT) { errno = EAGAIN; return -1; } + long r = futex(word, FUTEX_WAIT, v, timeout); + if (r != 0 && errno == ETIMEDOUT) return -1; + if (r != 0 && errno == EINTR) { errno = EINTR; return -1; } + } +} + +int semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timespec *timeout) { + pthread_mutex_lock(&g_lock); + struct sem_map *m = sem_lookup(semid); + pthread_mutex_unlock(&g_lock); + if (!m) return -1; + struct sem_header *h = m->hdr; + _Atomic int32_t *words = sem_words(h); + for (size_t i = 0; i < nsops; i++) { + if (sops[i].sem_num >= h->nsems) { errno = EFBIG; return -1; } + _Atomic int32_t *w = &words[sops[i].sem_num]; + if (sops[i].sem_op > 0) { + atomic_fetch_add(w, sops[i].sem_op); + futex(w, FUTEX_WAKE, INT_MAX, NULL); + } else if (sem_wait_op(w, sops[i].sem_op, sops[i].sem_flg, timeout) != 0) { + return -1; + } + if (sops[i].sem_op < 0) futex(w, FUTEX_WAKE, INT_MAX, NULL); /* zero-waiters see the change */ + } + return 0; +} + +int semop(int semid, struct sembuf *sops, size_t nsops) { return semtimedop(semid, sops, nsops, NULL); } + +int semctl(int semid, int semnum, int cmd, ...) { + union { int val; struct semid_ds *buf; unsigned short *array; } arg = {0}; + if (cmd == SETVAL || cmd == IPC_STAT || cmd == IPC_SET || cmd == GETALL || cmd == SETALL) { + va_list ap; + va_start(ap, cmd); + arg.buf = va_arg(ap, struct semid_ds *); + va_end(ap); + } + if (cmd == IPC_RMID) { + pthread_mutex_lock(&g_lock); + sem_forget(semid); + int rc = unlink_by_id("sem", semid); + pthread_mutex_unlock(&g_lock); + return rc; + } + pthread_mutex_lock(&g_lock); + struct sem_map *m = sem_lookup(semid); + pthread_mutex_unlock(&g_lock); + if (!m) return -1; + struct sem_header *h = m->hdr; + _Atomic int32_t *words = sem_words(h); + switch (cmd) { + case GETVAL: + if (semnum < 0 || (uint32_t)semnum >= h->nsems) { errno = EINVAL; return -1; } + return atomic_load(&words[semnum]); + case SETVAL: + if (semnum < 0 || (uint32_t)semnum >= h->nsems) { errno = EINVAL; return -1; } + atomic_store(&words[semnum], (int32_t)(intptr_t)arg.buf); + futex(&words[semnum], FUTEX_WAKE, INT_MAX, NULL); + return 0; + case GETALL: + for (uint32_t i = 0; i < h->nsems; i++) arg.array[i] = (unsigned short)atomic_load(&words[i]); + return 0; + case SETALL: + for (uint32_t i = 0; i < h->nsems; i++) { + atomic_store(&words[i], arg.array[i]); + futex(&words[i], FUTEX_WAKE, INT_MAX, NULL); + } + return 0; + case GETPID: return h->creator_pid; + case GETNCNT: case GETZCNT: return 0; + case IPC_STAT: + memset(arg.buf, 0, sizeof(*arg.buf)); + arg.buf->sem_perm.__key = (key_t)h->key; + arg.buf->sem_perm.uid = arg.buf->sem_perm.cuid = getuid(); + arg.buf->sem_perm.gid = arg.buf->sem_perm.cgid = getgid(); + arg.buf->sem_perm.mode = (unsigned short)h->mode; + arg.buf->sem_nsems = h->nsems; + return 0; + case IPC_SET: return 0; + default: errno = EINVAL; return -1; + } +} + +/* ------------------------------------------------------------------ shared memory */ + +/* Linux keeps a removed segment attachable until the last detach, which X clients rely on: they + * remove the segment right after asking the server to attach it. A removed segment is renamed out + * of key lookup instead, and removed for good once it has been out of use for a while. */ +static int remove_shm(int id) { + DIR *d = opendir(object_dir()); + if (!d) return -1; + struct dirent *e; + int rc = -1; + while ((e = readdir(d))) { + if (strncmp(e->d_name, "shm.", 4) || !strncmp(e->d_name, "shm.d.", 6)) continue; + if ((int)(e->d_ino & 0x7fffffff) != id) continue; + char from[PATH_MAX], to[PATH_MAX]; + snprintf(from, sizeof(from), "%s/%s", g_dir, e->d_name); + snprintf(to, sizeof(to), "%s/shm.d.%s", g_dir, e->d_name + 4); + rc = rename(from, to); + break; + } + closedir(d); + if (rc < 0) errno = EINVAL; + return rc; +} + +static void expire_removed_shm(void) { + DIR *d = opendir(object_dir()); + if (!d) return; + struct dirent *e; + time_t now = time(NULL); + while ((e = readdir(d))) { + if (strncmp(e->d_name, "shm.d.", 6)) continue; + char path[PATH_MAX]; + struct stat st; + snprintf(path, sizeof(path), "%s/%s", g_dir, e->d_name); + if (stat(path, &st) == 0 && now - st.st_mtime > 30) unlink(path); + } + closedir(d); +} + +int shmget(key_t key, size_t size, int shmflg) { + int created; + expire_removed_shm(); + int fd = open_object("shm", key, shmflg, &created); + if (fd < 0) return -1; + if (created) { + if (size == 0) { close(fd); errno = EINVAL; return -1; } + if (ftruncate(fd, (off_t)size) != 0) { close(fd); return -1; } + } else { + struct stat st; + if (fstat(fd, &st) != 0 || (size_t)st.st_size < size) { close(fd); errno = EINVAL; return -1; } + } + int id = object_id(fd); + close(fd); + return id; +} + +void *shmat(int shmid, const void *shmaddr, int shmflg) { + int fd = open_by_id("shm", shmid); + if (fd < 0) return (void *)-1; + struct stat st; + if (fstat(fd, &st) != 0) { close(fd); return (void *)-1; } + int prot = PROT_READ | ((shmflg & SHM_RDONLY) ? 0 : PROT_WRITE); + void *m = mmap((void *)shmaddr, (size_t)st.st_size, prot, MAP_SHARED, fd, 0); + close(fd); + if (m == MAP_FAILED) return (void *)-1; + pthread_mutex_lock(&g_lock); + for (int i = 0; i < MAX_OBJECTS; i++) { + if (g_shms[i].addr) continue; + g_shms[i].addr = m; g_shms[i].len = (size_t)st.st_size; g_shms[i].id = shmid; + break; + } + pthread_mutex_unlock(&g_lock); + return m; +} + +int shmdt(const void *shmaddr) { + pthread_mutex_lock(&g_lock); + for (int i = 0; i < MAX_OBJECTS; i++) { + if (g_shms[i].addr != shmaddr) continue; + munmap(g_shms[i].addr, g_shms[i].len); + g_shms[i].addr = NULL; + pthread_mutex_unlock(&g_lock); + return 0; + } + pthread_mutex_unlock(&g_lock); + errno = EINVAL; + return -1; +} + +int shmctl(int shmid, int cmd, struct shmid_ds *buf) { + switch (cmd) { + case IPC_RMID: { + pthread_mutex_lock(&g_lock); + int rc = remove_shm(shmid); + pthread_mutex_unlock(&g_lock); + return rc; + } + case IPC_STAT: { + int fd = open_by_id("shm", shmid); + if (fd < 0) return -1; + struct stat st; + int rc = fstat(fd, &st); + close(fd); + if (rc != 0) return -1; + memset(buf, 0, sizeof(*buf)); + buf->shm_perm.uid = buf->shm_perm.cuid = getuid(); + buf->shm_perm.gid = buf->shm_perm.cgid = getgid(); + buf->shm_perm.mode = 0600; + buf->shm_segsz = (size_t)st.st_size; + buf->shm_nattch = 1; + buf->shm_cpid = (pid_t)getpid(); + return 0; + } + case IPC_SET: case SHM_LOCK: case SHM_UNLOCK: return 0; + default: errno = EINVAL; return -1; + } +} + +/* Held across a fork so the child never inherits it locked by a thread it does not have; see the + * same handlers in drm.c. */ +static void lock_before_fork(void) { pthread_mutex_lock(&g_lock); } +static void unlock_after_fork(void) { pthread_mutex_unlock(&g_lock); } +/* The child's one thread did not lock it, so it is given a fresh mutex rather than an unlock. */ +static void reset_after_fork(void) { pthread_mutex_init(&g_lock, NULL); } + +__attribute__((constructor)) static void install_sysv_fork_handlers(void) { + pthread_atfork(lock_before_fork, unlock_after_fork, reset_after_fork); +} diff --git a/tools/linuxfs/preload/tracer.c b/tools/linuxfs/preload/tracer.c new file mode 100644 index 000000000..71ac48d66 --- /dev/null +++ b/tools/linuxfs/preload/tracer.c @@ -0,0 +1,100 @@ +/* + * A process status without the container's tracer in it. + * + * proot runs the session under ptrace, so every process here reads a TracerPid in its + * /proc/<pid>/status. The Steam client takes that to mean a debugger is attached, and its arm64 + * build breaks into the supposed debugger with a bare "svc #0" after every failed assertion. That + * is no breakpoint: the registers still hold the call Plat_IsInDebugSession() just made, close(), + * with its own result, 1, as the argument - so each assertion closes descriptor 1. The number then + * goes to whatever the client opens next, and its standard output is written into that: app + * manifests and content chunks, its own connections, and the socket a game's Steam API talks + * over, which is how a game that stayed silent while loading found its pipe dead. + * + * The tracer is part of the container, not something a program should react to, so the status + * files are served with TracerPid 0. + */ +#define _GNU_SOURCE +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <unistd.h> + +#define STATUS_MAX 16384 +#define TRACER_FIELD "\nTracerPid:\t" + +static const char *skip_digits(const char *s) { + const char *end = s; + while (isdigit((unsigned char)*end)) end++; + return end == s ? NULL : end; +} + +/* /proc/{self,thread-self,<pid>}[/task/<tid>]/status */ +static int is_status_path(const char *path) { + const char *rest; + if (path == NULL || strncmp(path, "/proc/", 6) != 0) return 0; + path += 6; + if (strncmp(path, "self/", 5) == 0) rest = path + 4; + else if (strncmp(path, "thread-self/", 12) == 0) rest = path + 11; + else if ((rest = skip_digits(path)) == NULL) return 0; + if (strncmp(rest, "/task/", 6) == 0 && (rest = skip_digits(rest + 6)) == NULL) return 0; + return strcmp(rest, "/status") == 0; +} + +/* Chromium stands its hang watchdogs down when it sees a tracer, and the client's web helper does + * not come up within their patience here: it is left reading the truth. */ +static int is_web_helper(void) { + return strcmp(program_invocation_short_name, "steamwebhelper") == 0; +} + +static int write_all(int fd, const char *data, size_t len) { + while (len > 0) { + ssize_t n = write(fd, data, len); + if (n < 0 && errno == EINTR) continue; + if (n <= 0) return -1; + data += n; + len -= (size_t)n; + } + return 0; +} + +/* The descriptor to hand back for a read-only open of a status file, or -1 with errno untouched + * when the path is something else or the copy cannot be made, and the real open should run. */ +__attribute__((visibility("hidden"))) int wn_status_without_tracer(const char *path, int flags) { + char text[STATUS_MAX]; + size_t len = 0; + int saved = errno; + int out = -1; + + if ((flags & O_ACCMODE) != O_RDONLY || !is_status_path(path) || is_web_helper()) return -1; + int in = (int)syscall(SYS_openat, AT_FDCWD, path, O_RDONLY | O_CLOEXEC); + if (in < 0) goto done; + for (;;) { + ssize_t n = read(in, text + len, sizeof(text) - 1 - len); + if (n < 0 && errno == EINTR) continue; + if (n <= 0) break; + len += (size_t)n; + } + close(in); + text[len] = '\0'; + + char *field = strstr(text, TRACER_FIELD); + if (field == NULL) goto done; + char *value = field + strlen(TRACER_FIELD); + char *line_end = strchr(value, '\n'); + if (line_end == NULL) goto done; + + out = memfd_create("status", (flags & O_CLOEXEC) ? MFD_CLOEXEC : 0); + if (out < 0) goto done; + if (write_all(out, text, (size_t)(value - text)) != 0 || write_all(out, "0", 1) != 0 + || write_all(out, line_end, len - (size_t)(line_end - text)) != 0 + || lseek(out, 0, SEEK_SET) != 0) { + close(out); + out = -1; + } +done: + errno = saved; + return out; +} diff --git a/tools/linuxfs/preload/udevmon.c b/tools/linuxfs/preload/udevmon.c new file mode 100644 index 000000000..47bee0efd --- /dev/null +++ b/tools/linuxfs/preload/udevmon.c @@ -0,0 +1,188 @@ +/* + * udev's netlink monitor, stood in for where the sandbox refuses the socket it is built on. + * + * The app sandbox denies a NETLINK_KOBJECT_UEVENT socket outright. libudev cannot create a + * monitor without one, and SDL's HID layer cannot start without a monitor: every SDL_hid_init() + * fails, the Steam client's controller code retries it hundreds of times a second, its main loop + * stalls, and the client asserts out from under the running game. Refusing SDL the library + * altogether (the earlier answer here) is what produced that failure. + * + * So the socket is answered instead: one end of a datagram socketpair stands in for the netlink + * socket, its peer held open so it never reads as hung up, and the few calls libudev makes on + * the socket that only a netlink one could answer are answered here. Nothing is ever written to + * it, so the monitor is live and silent, which is the most the kernel would deliver in a + * container anyway. The real socket is tried first, so this stays out of the way wherever a + * monitor could genuinely be created. + * + * Wine is left without one. Its HID bus reads the session's pads itself only when its SDL bus + * cannot start, and that is the path inputudev.c describes them on. Given a monitor the SDL bus + * comes up and takes the pads over from it. + */ +#define _GNU_SOURCE +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <linux/netlink.h> +#include <pthread.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> + +struct stand_in { + int fd; /* the descriptor handed out, -1 when the slot is free */ + int peer; /* the other end, kept open */ + ino_t inode; /* identifies the socket after fd is closed and its number reused */ +}; + +static struct stand_in stand_ins[16]; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +static void lock_before_fork(void) { pthread_mutex_lock(&lock); } +static void unlock_after_fork(void) { pthread_mutex_unlock(&lock); } +static void reset_after_fork(void) { pthread_mutex_init(&lock, NULL); } + +__attribute__((constructor)) static void init_udevmon(void) { + for (size_t i = 0; i < sizeof(stand_ins) / sizeof(stand_ins[0]); i++) + stand_ins[i].fd = -1; + pthread_atfork(lock_before_fork, unlock_after_fork, reset_after_fork); +} + +static ino_t socket_inode(int fd) { + struct stat st; + return fstat(fd, &st) == 0 ? st.st_ino : 0; +} + +/* Whether fd is one of the stand-in sockets. A slot whose descriptor was closed and reused by + * something else is retired on sight, so a later socket with the same number is not mistaken. */ +static int is_stand_in(int fd) { + int found = 0; + if (fd < 0) + return 0; + pthread_mutex_lock(&lock); + for (size_t i = 0; i < sizeof(stand_ins) / sizeof(stand_ins[0]); i++) { + if (stand_ins[i].fd != fd) + continue; + if (stand_ins[i].inode == socket_inode(fd)) { + found = 1; + } else { + close(stand_ins[i].peer); + stand_ins[i].fd = -1; + } + break; + } + pthread_mutex_unlock(&lock); + return found; +} + +static int is_wine_process(void) { + static int known; + if (known == 0) { + char exe[PATH_MAX]; + ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); + const char *name = exe; + if (n > 0) { + exe[n] = '\0'; + name = strrchr(exe, '/'); + name = name != NULL ? name + 1 : exe; + } + known = n > 0 && strncmp(name, "wine", 4) == 0 ? 1 : -1; + } + return known == 1; +} + +static int make_stand_in(int type) { + int sv[2]; + int saved = errno; + int flags = SOCK_DGRAM | (type & (SOCK_CLOEXEC | SOCK_NONBLOCK)); + if (socketpair(AF_UNIX, flags, 0, sv) != 0) { + errno = saved; + return -1; + } + /* The peer is ours alone: it must not follow the socket into a child. */ + fcntl(sv[1], F_SETFD, FD_CLOEXEC); + pthread_mutex_lock(&lock); + for (size_t i = 0; i < sizeof(stand_ins) / sizeof(stand_ins[0]); i++) { + if (stand_ins[i].fd == -1) { + stand_ins[i].fd = sv[0]; + stand_ins[i].peer = sv[1]; + stand_ins[i].inode = socket_inode(sv[0]); + pthread_mutex_unlock(&lock); + return sv[0]; + } + } + pthread_mutex_unlock(&lock); + close(sv[0]); + close(sv[1]); + errno = saved; + return -1; +} + +int socket(int domain, int type, int protocol) { + static int (*real_socket)(int, int, int); + int fd; + + if (real_socket == NULL) real_socket = dlsym(RTLD_NEXT, "socket"); + if (real_socket == NULL) { + errno = ENOSYS; + return -1; + } + fd = real_socket(domain, type, protocol); + if (fd >= 0 || domain != AF_NETLINK || protocol != NETLINK_KOBJECT_UEVENT) + return fd; + int refused = errno; + if (is_wine_process()) { + errno = refused; + return -1; + } + return make_stand_in(type); +} + +/* For net.c's bind(): whether fd is a stand-in, which accepts any netlink address as bound. */ +__attribute__((visibility("hidden"))) int wn_udevmon_stand_in(int fd) { + return is_stand_in(fd); +} + +int getsockname(int fd, struct sockaddr *addr, socklen_t *len) { + static int (*real_getsockname)(int, struct sockaddr *, socklen_t *); + struct sockaddr_nl nl; + + if (is_stand_in(fd)) { + if (addr == NULL || len == NULL) { + errno = EFAULT; + return -1; + } + memset(&nl, 0, sizeof(nl)); + nl.nl_family = AF_NETLINK; + nl.nl_pid = (unsigned)getpid(); + memcpy(addr, &nl, *len < sizeof(nl) ? *len : sizeof(nl)); + *len = sizeof(nl); + return 0; + } + if (real_getsockname == NULL) real_getsockname = dlsym(RTLD_NEXT, "getsockname"); + if (real_getsockname == NULL) { + errno = ENOSYS; + return -1; + } + return real_getsockname(fd, addr, len); +} + +int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { + static int (*real_setsockopt)(int, int, int, const void *, socklen_t); + + if (is_stand_in(fd)) { + /* Group membership and packet filters only mean something on a socket that receives; this + * one never does, so they are accepted as set. The rest is genuine on a unix socket. */ + if (level == SOL_NETLINK) + return 0; + if (level == SOL_SOCKET && (optname == SO_ATTACH_FILTER || optname == SO_DETACH_FILTER)) + return 0; + } + if (real_setsockopt == NULL) real_setsockopt = dlsym(RTLD_NEXT, "setsockopt"); + if (real_setsockopt == NULL) { + errno = ENOSYS; + return -1; + } + return real_setsockopt(fd, level, optname, optval, optlen); +} diff --git a/tools/linuxfs/tests/test_proton.py b/tools/linuxfs/tests/test_proton.py new file mode 100644 index 000000000..637ba2256 --- /dev/null +++ b/tools/linuxfs/tests/test_proton.py @@ -0,0 +1,123 @@ +import importlib.machinery +import importlib.util +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[3] +ASSETS = ROOT / 'app/src/main/assets/linuxfs/usr/local/bin' + + +def load(name): + loader = importlib.machinery.SourceFileLoader(name, str(ASSETS / name)) + spec = importlib.util.spec_from_loader(name, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + + +class ProtonTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.compat = load('winnative-steam-compat') + self.audio = load('winnative-directaudio') + + def test_runtime_sources_match_apk(self): + for name in ('winnative-session', 'winnative-steam-compat', 'winnative-directaudio', + 'winnative-seed-redists', 'winnative-proton-launch'): + self.assertEqual((ASSETS / name).read_bytes(), + (ROOT / 'tools/linuxfs/overlay/usr/local/bin' / name).read_bytes()) + + def test_tools_are_stable_and_user_choices_survive(self): + tools = self.root / 'compatibilitytools.d' + tool = tools / 'proton-cachyos-arm64' + tool.mkdir(parents=True) + manifest = tool / 'toolmanifest.vdf' + original = '"manifest" { "commandline" "/proton %verb%" "require_tool_appid" "4185400" }' + manifest.write_text(original) + (tool / 'proton').write_text('') + (tool / 'compatibilitytool.vdf').write_text('"compatibilitytools" { "compat_tools" { "cachy-custom" {} } }') + self.compat.build_tool(str(tools)) + names = self.compat.adopt_extras(str(tools)) + self.assertEqual(names, {'cachy-custom'}) + self.assertNotIn('require_tool_appid', manifest.read_text()) + self.assertEqual((tool / 'toolmanifest.vdf.winnative-original').read_text(), original) + generated = [manifest, tool / 'winnative-proton-wrap', tools / 'winnative-proton/winnative-launch'] + before = [(p.stat().st_ino, p.stat().st_mtime_ns) for p in generated] + self.compat.build_tool(str(tools)) + self.compat.adopt_extras(str(tools)) + self.assertEqual(before, [(p.stat().st_ino, p.stat().st_mtime_ns) for p in generated]) + config = self.root / 'config.vdf' + config.write_text('"InstallConfigStore" { "Software" { "Valve" { "Steam" { "CompatToolMapping" { "7" { "name" "cachy-custom" } "8" { "name" "proton-experimental-arm64" } } } } } }') + self.compat.register_default(str(config), ['7', '8', '9'], names) + self.assertIn('"cachy-custom"', config.read_text()) + self.assertNotIn('"proton-experimental-arm64"', config.read_text()) + config.write_text('"InstallConfigStore" { "Software" { "Valve" { "Steam" {} } } }') + self.compat.register_default(str(config), ['9']) + self.assertIn('"9"', config.read_text()) + + def test_refresh_does_not_rewrite_steam_config(self): + config = self.root / 'config/config.vdf' + config.parent.mkdir() + content = '"InstallConfigStore" { "Software" { "Valve" { "Steam" {} } } }' + config.write_text(content) + subprocess.run(['python3', str(ASSETS / 'winnative-steam-compat'), str(self.root), '--refresh'], check=True, capture_output=True) + self.assertEqual(config.read_text(), content) + + def test_audio_prepares_selected_tool_and_clears_incompatible_override(self): + stage = self.root / 'stage' + self.audio.STAGE = str(stage) + files = self.root / 'GE/files' + template = files / 'share/default_pfx_arm64' + prefix = self.root / 'compat/pfx' + for p in (template, prefix): + (p / 'drive_c/windows/system32').mkdir(parents=True) + (p / 'drive_c/windows/syswow64').mkdir() + (p / 'user.reg').write_text('WINE REGISTRY Version 2\n') + for part in (self.audio.UNIXLIB, *self.audio.HALVES): + dest = stage / part + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(b'driver') + (files / 'lib/wine' / Path(part).parent).mkdir(parents=True, exist_ok=True) + def library(path, count): + subprocess.run(['cc', '-shared', '-fPIC', '-x', 'c', '-', '-o', str(path)], + input=f'void *__wine_unix_call_funcs[{count}];'.encode(), check=True) + library(stage / self.audio.UNIXLIB, 8) + alsa = files / 'lib/wine/aarch64-unix/winealsa.so' + library(alsa, 8) + self.audio.prepare_launch(str(files), str(prefix.parent), True) + for p in (prefix, template): + self.assertIn('"Audio"="directaudio"', (p / 'user.reg').read_text()) + self.assertEqual((p / 'drive_c/windows/system32/winedirectaudio.drv').read_bytes(), b'driver') + library(alsa, 9) + self.audio.prepare_launch(str(files), str(prefix.parent), True) + for p in (prefix, template): + self.assertNotIn('"Audio"="directaudio"', (p / 'user.reg').read_text()) + + def test_launch_preserves_arguments_environment_and_exit_status(self): + tool = self.root / 'GE Proton' + (tool / 'files/bin-arm64').mkdir(parents=True) + wine = tool / 'files/bin-arm64/wine' + wine.write_text('#!/bin/sh\nexit 0\n') + wine.chmod(0o755) + proton = tool / 'proton' + proton.write_text('#!/bin/sh\nprintf "%s\\n" "$PROTON_USE_ARM64" "$PROTON_LOG_DIR" "$@"\nexit 23\n') + proton.chmod(0o755) + audio = self.root / 'winnative-directaudio' + audio.write_text('#!/bin/sh\nexit 0\n') + audio.chmod(0o755) + env = dict(os.environ, PATH=str(self.root) + ':' + os.environ['PATH'], WN_LOG=str(self.root / 'logs/session.log')) + result = subprocess.run(['sh', str(ASSETS / 'winnative-proton-launch'), str(proton), + 'waitforexitandrun', '/game path/a.exe', '', 'one "two"'], env=env, capture_output=True, text=True) + self.assertEqual(result.returncode, 23) + self.assertEqual(result.stdout.splitlines(), ['1', str(self.root / 'logs'), 'waitforexitandrun', '/game path/a.exe', '', 'one "two"']) + wine.unlink() + self.assertEqual(subprocess.run(['sh', str(ASSETS / 'winnative-proton-launch'), str(proton)], env=env, capture_output=True).returncode, 126) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/linuxfs/tests/test_proton_registration.py b/tools/linuxfs/tests/test_proton_registration.py new file mode 100644 index 000000000..0413ad754 --- /dev/null +++ b/tools/linuxfs/tests/test_proton_registration.py @@ -0,0 +1,45 @@ +import json +from pathlib import Path +import runpy +import tempfile +import unittest + +MODULE = runpy.run_path(str(Path(__file__).resolve().parents[1] / 'overlay/usr/local/bin/winnative-steam-compat')) + + +class ProtonRegistrationTest(unittest.TestCase): + def test_adopts_arm_tools_without_overriding_per_game_choice(self): + with tempfile.TemporaryDirectory() as temporary: + steam = Path(temporary) + tools = steam / 'compatibilitytools.d' + names = ['GE-Proton11-7-aarch64', 'proton-cachyos-11.0-20260703-slr-arm64'] + for name in names: + tool = tools / name + tool.mkdir(parents=True) + (tool / 'winnative-proton.json').write_text(json.dumps({'id': name})) + (tool / 'proton').write_text('#!/bin/sh\nexit 0\n') + (tool / 'toolmanifest.vdf').write_text('"manifest" { "commandline" "/proton %verb%" "require_tool_appid" "4185400" }') + (tool / 'compatibilitytool.vdf').write_text('"compatibilitytools" { "compat_tools" { "%s" { "install_path" "." } } }' % name) + for _ in range(2): + adopted = MODULE['adopt_extras'](str(tools)) + self.assertEqual(set(names), adopted) + for name in names: + tool = tools / name + manifest = (tool / 'toolmanifest.vdf').read_text() + self.assertNotIn('require_tool_appid', manifest) + self.assertIn(MODULE['EXTRA_WRAPPER'], manifest) + self.assertIn('require_tool_appid', (tool / 'toolmanifest.vdf.winnative-original').read_text()) + wrapper = tool / MODULE['EXTRA_WRAPPER'] + self.assertIn('"$here/proton"', wrapper.read_text()) + self.assertTrue(wrapper.stat().st_mode & 0o111) + config = steam / 'config.vdf' + config.write_text('"InstallConfigStore" { "Software" { "Valve" { "Steam" { "CompatToolMapping" { "123" { "name" "%s" } "124" { "name" "proton_experimental" } } } } } }' % names[1]) + MODULE['register_default'](str(config), ['123', '124'], adopted) + content = config.read_text() + self.assertIn(names[1], content) + self.assertNotIn('proton_experimental', content) + self.assertIn('winnative-proton', content) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/linuxfs/turnip/kgsl-drm-node.patch b/tools/linuxfs/turnip/kgsl-drm-node.patch new file mode 100644 index 000000000..d56316faf --- /dev/null +++ b/tools/linuxfs/turnip/kgsl-drm-node.patch @@ -0,0 +1,40 @@ +--- a/src/freedreno/vulkan/tu_knl_kgsl.cc 2026-09-02 11:40:02.000000000 -0400 ++++ b/src/freedreno/vulkan/tu_knl_kgsl.cc 2026-09-17 12:43:30.981751690 -0400 +@@ -10,6 +10,8 @@ + #include <stdint.h> + #include <sys/ioctl.h> + #include <sys/mman.h> ++#include <sys/stat.h> ++#include <sys/sysmacros.h> + + #define __user + #include "ion/ion.h" +@@ -1948,6 +1950,17 @@ + + instance->knl = &kgsl_knl_funcs; + ++ /* KGSL has no DRM nodes. Report the device itself as both so a Wayland ++ * compositor's dma-buf feedback and the WSI can agree on a main device ++ * when the host exposes the device at a DRM path. ++ */ ++ struct stat kgsl_st; ++ if (fstat(fd, &kgsl_st) == 0 && S_ISCHR(kgsl_st.st_mode)) { ++ device->has_local = device->has_master = true; ++ device->local_major = device->master_major = major(kgsl_st.st_rdev); ++ device->local_minor = device->master_minor = minor(kgsl_st.st_rdev); ++ } ++ + result = tu_physical_device_init(device, instance); + if (result != VK_SUCCESS) + goto fail; +--- a/src/freedreno/vulkan/tu_device.cc 2026-09-02 11:40:02.000000000 -0400 ++++ b/src/freedreno/vulkan/tu_device.cc 2026-09-17 12:33:58.573289971 -0400 +@@ -353,7 +353,7 @@ + .EXT_mutable_descriptor_type = true, + .EXT_nested_command_buffer = true, + .EXT_non_seamless_cube_map = true, +- .EXT_physical_device_drm = !is_kgsl(device->instance), ++ .EXT_physical_device_drm = !is_kgsl(device->instance) || device->has_local, + .EXT_pipeline_creation_cache_control = true, + .EXT_pipeline_creation_feedback = true, + #ifdef TU_USE_WSI_PLATFORM diff --git a/tools/linuxfs/turnip/kgsl-no-calibrated-timestamps.patch b/tools/linuxfs/turnip/kgsl-no-calibrated-timestamps.patch new file mode 100644 index 000000000..9ab478c15 --- /dev/null +++ b/tools/linuxfs/turnip/kgsl-no-calibrated-timestamps.patch @@ -0,0 +1,92 @@ +--- a/src/freedreno/vulkan/tu_knl_kgsl.cc ++++ b/src/freedreno/vulkan/tu_knl_kgsl.cc +@@ -1705,13 +1705,6 @@ + } + + static int +-kgsl_device_get_gpu_timestamp(struct tu_device *dev, uint64_t *ts) +-{ +- UNREACHABLE(""); +- return 0; +-} +- +-static int + kgsl_device_get_suspend_count(struct tu_device *dev, uint64_t *suspend_count) + { + /* kgsl doesn't have a way to get it */ +@@ -1755,7 +1748,6 @@ + + .device_init = kgsl_device_init, + .device_finish = kgsl_device_finish, +- .device_get_gpu_timestamp = kgsl_device_get_gpu_timestamp, + .device_get_suspend_count = kgsl_device_get_suspend_count, + .device_check_status = kgsl_device_check_status, + .submitqueue_new = kgsl_submitqueue_new, +--- a/src/freedreno/vulkan/tu_knl.cc ++++ b/src/freedreno/vulkan/tu_knl.cc +@@ -247,6 +247,9 @@ + tu_device_get_gpu_timestamp(struct tu_device *dev, + uint64_t *ts) + { ++ /* Not every kernel interface can read the GPU's counter. */ ++ if (dev->instance->knl->device_get_gpu_timestamp == NULL) ++ return -1; + return dev->instance->knl->device_get_gpu_timestamp(dev, ts); + } + +--- a/src/freedreno/vulkan/tu_device.cc ++++ b/src/freedreno/vulkan/tu_device.cc +@@ -201,13 +201,21 @@ + device->info->props.has_ray_intersection && + (!device->info->props.has_sw_fuse || device->has_raytracing); + ++ /* Calibrating a timestamp means reading the GPU's counter, which the kgsl ++ * interface cannot do: the extension is only there where the kernel ++ * interface implements it, and present timing, which requires it, with it. ++ */ ++ bool has_calibrated_timestamps = ++ device->info->props.has_persistent_counter && ++ device->instance->knl->device_get_gpu_timestamp != NULL; ++ + *ext = (struct vk_device_extension_table) { .table = { + .KHR_8bit_storage = device->info->props.storage_8bit, + .KHR_16bit_storage = device->info->props.storage_16bit, + .KHR_acceleration_structure = has_raytracing, + .KHR_bind_memory2 = true, + .KHR_buffer_device_address = true, +- .KHR_calibrated_timestamps = device->info->props.has_persistent_counter, ++ .KHR_calibrated_timestamps = has_calibrated_timestamps, + .KHR_compute_shader_derivatives = true, + .KHR_copy_commands2 = true, + // TODO workaround for https://github.com/KhronosGroup/VK-GL-CTS/issues/525 +@@ -304,7 +312,7 @@ + .EXT_attachment_feedback_loop_dynamic_state = true, + .EXT_attachment_feedback_loop_layout = true, + .EXT_border_color_swizzle = true, +- .EXT_calibrated_timestamps = device->info->props.has_persistent_counter, ++ .EXT_calibrated_timestamps = has_calibrated_timestamps, + .EXT_color_write_enable = true, + .EXT_conditional_rendering = true, + .EXT_conservative_rasterization = device->info->chip >= 7, +@@ -357,7 +365,7 @@ + .EXT_pipeline_creation_cache_control = true, + .EXT_pipeline_creation_feedback = true, + #ifdef TU_USE_WSI_PLATFORM +- .EXT_present_timing = device->info->props.has_persistent_counter, ++ .EXT_present_timing = has_calibrated_timestamps, + #endif + .EXT_primitive_restart_index = true, + .EXT_primitive_topology_list_restart = true, +@@ -917,9 +925,9 @@ + + #ifdef TU_USE_WSI_PLATFORM + /* VK_EXT_present_timing */ +- features->presentTiming = true; +- features->presentAtRelativeTime = true; +- features->presentAtAbsoluteTime = true; ++ features->presentTiming = pdevice->vk.supported_extensions.EXT_present_timing; ++ features->presentAtRelativeTime = pdevice->vk.supported_extensions.EXT_present_timing; ++ features->presentAtAbsoluteTime = pdevice->vk.supported_extensions.EXT_present_timing; + #endif + } + diff --git a/tools/linuxfs/turnip/wn-gpu-fixes.patch b/tools/linuxfs/turnip/wn-gpu-fixes.patch new file mode 100644 index 000000000..0e440e507 --- /dev/null +++ b/tools/linuxfs/turnip/wn-gpu-fixes.patch @@ -0,0 +1,113 @@ +diff --git a/src/freedreno/common/freedreno_dev_info.h b/src/freedreno/common/freedreno_dev_info.h +index 3a6eeed..9b39d4c 100644 +--- a/src/freedreno/common/freedreno_dev_info.h ++++ b/src/freedreno/common/freedreno_dev_info.h +@@ -480,6 +480,8 @@ struct fd_dev_info { + + /* Whether the device supports the image processing opcode */ + bool has_image_processing; ++ /* If GMEM needs to be disabled for this GPU */ ++ bool disable_gmem; + + /* The amount of valid draw state IDs. */ + uint32_t max_draw_states; +diff --git a/src/freedreno/common/freedreno_devices.py b/src/freedreno/common/freedreno_devices.py +index 47791d6..9b93ceb 100644 +--- a/src/freedreno/common/freedreno_devices.py ++++ b/src/freedreno/common/freedreno_devices.py +@@ -842,6 +842,8 @@ a7xx_gen1 = GPUProps( + enable_tp_ubwc_flag_hint = True, + reading_shading_rate_requires_smask_quirk = True, + cs_lock_unlock_quirk = True, ++ has_early_preamble = False, ++ has_scalar_predicates = False, + ) + + a7xx_gen2 = GPUProps( +@@ -1104,7 +1106,7 @@ add_gpus([ + GPUId(chip_id=0xffff43050c01, name="Adreno X1-85"), + ], A6xxGPUInfo( + CHIP.A7XX, +- [a7xx_base, a7xx_gen2], ++ [a7xx_base, a7xx_gen2, GPUProps(enable_tp_ubwc_flag_hint = True)], + num_ccu = 6, + tile_align_w = 96, + tile_align_h = 32, +@@ -1355,6 +1357,7 @@ a8xx_gen2 = GPUProps( + ) + + add_gpus([ ++ GPUId(chip_id=0x44010000, name="Adreno (TM) 810"), # KGSL + GPUId(chip_id=0xffff44010000, name="Adreno (TM) 810"), + ], A6xxGPUInfo( + CHIP.A8XX, +@@ -1365,6 +1368,7 @@ add_gpus([ + # This is possibly also needed for a830 (and all of a8xx), + # move to a8xx_base if confirmed needed for a830. + has_fs_tex_prefetch = False, ++ disable_gmem = True, + )], + num_ccu = 1, + num_slices = 1, +@@ -1380,6 +1384,28 @@ add_gpus([ + raw_magic_regs = a8xx_base_raw_magic_regs, + )) + ++add_gpus([ ++ GPUId(chip_id=0x44030000, name="Adreno (TM) 825"), ++ ], A6xxGPUInfo( ++ CHIP.A8XX, ++ [a7xx_base, a7xx_gen3, a8xx_base, a8xx_gen1, GPUProps( ++ sysmem_ccu_depth_cache_fraction = CCUColorCacheFraction.THREE_QUARTER.value, ++ sysmem_per_ccu_depth_cache_size = 96 * 1024, ++ )], ++ num_ccu = 4, ++ num_slices = 2, ++ tile_align_w = 96, ++ tile_align_h = 32, ++ tile_max_w = 16416, ++ tile_max_h = 16384, ++ num_vsc_pipes = 32, ++ cs_shared_mem_size = 32 * 1024, ++ wave_granularity = 2, ++ fibers_per_sp = 128 * 2 * 16, ++ magic_regs = dict(), ++ raw_magic_regs = a8xx_base_raw_magic_regs, ++ )) ++ + add_gpus([ + GPUId(chip_id=0xffff44050000, name="Adreno (TM) 830"), + GPUId(chip_id=0x44050001, name="Adreno (TM) 830"), # KGSL +@@ -1401,6 +1427,8 @@ add_gpus([ + )) + + add_gpus([ ++ GPUId(chip_id=0x44030A00, name="Adreno (TM) 829"), # KGSL ++ GPUId(chip_id=0xffff44030A00, name="Adreno (TM) 829"), # KGSL fallback + GPUId(chip_id=0x44030a20, name="Adreno (TM) 829"), # KGSL + ], A6xxGPUInfo( + CHIP.A8XX, +@@ -1408,6 +1436,7 @@ add_gpus([ + GPUProps( + shading_rate_matches_vk = True, # TODO confirm this + sysmem_vpc_bv_pos_buf_size = 24576, ++ disable_gmem = True, + )], + num_ccu = 4, + num_slices = 2, +diff --git a/src/freedreno/vulkan/tu_cmd_buffer.cc b/src/freedreno/vulkan/tu_cmd_buffer.cc +index 6cf3050..a29d913 100644 +--- a/src/freedreno/vulkan/tu_cmd_buffer.cc ++++ b/src/freedreno/vulkan/tu_cmd_buffer.cc +@@ -1371,6 +1371,11 @@ use_sysmem_rendering(struct tu_cmd_buffer *cmd, + return true; + } + ++ if (cmd->device->physical_device->info->props.disable_gmem) { ++ cmd->state.rp.gmem_disable_reason = "GMEM disabled for this GPU"; ++ return true; ++ } ++ + /* can't fit attachments into gmem */ + if (!cmd->state.tiling->possible) { + cmd->state.rp.gmem_disable_reason = "Can't fit attachments into gmem";