diff --git a/go.mod b/go.mod index ecf68ef..487dee9 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module git.julianfamily.org/keepassgo go 1.26 -replace gioui.org/cmd => ./third_party/gioui-cmd - require ( gioui.org v0.8.0 gioui.org/x v0.8.0 diff --git a/go.sum b/go.sum index baebf9e..6bdda20 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKw eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= gioui.org v0.8.0 h1:QV5p5JvsmSmGiIXVYOKn6d9YDliTfjtLlVf5J+BZ9Pg= gioui.org v0.8.0/go.mod h1:vEMmpxMOd/iwJhXvGVIzWEbxMWhnMQ9aByOGQdlQ8rc= +gioui.org/cmd v0.8.0 h1:oy5qOlc1UXcglc5HBCMZQELiIzQ2obhT98mw+SuWafQ= +gioui.org/cmd v0.8.0/go.mod h1:wKLAyAgRR25VMYFzGX2Ecia0m0Td562wDcZ3LaPHPTI= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= diff --git a/third_party/gioui-cmd/.builds/apple.yml b/third_party/gioui-cmd/.builds/apple.yml deleted file mode 100644 index d683fff..0000000 --- a/third_party/gioui-cmd/.builds/apple.yml +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-License-Identifier: Unlicense OR MIT -image: debian/testing -packages: - - clang - - cmake - - curl - - autoconf - - libxml2-dev - - libssl-dev - - libz-dev - - llvm-dev # for cctools - - uuid-dev ## for cctools - - libplist-utils # for gogio -sources: - - https://git.sr.ht/~eliasnaur/gio-cmd - - https://git.sr.ht/~eliasnaur/applesdks - - https://git.sr.ht/~eliasnaur/giouiorg - - https://github.com/tpoechtrager/cctools-port.git - - https://github.com/tpoechtrager/apple-libtapi.git - - https://github.com/mackyle/xar.git -environment: - APPLE_TOOLCHAIN_ROOT: /home/build/appletools - PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin -tasks: - - install_go: | - mkdir -p /home/build/sdk - curl -s https://dl.google.com/go/go1.19.8.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf - - - prepare_toolchain: | - mkdir -p $APPLE_TOOLCHAIN_ROOT - cd $APPLE_TOOLCHAIN_ROOT - tar xJf /home/build/applesdks/applesdks.tar.xz - mkdir bin tools - cd bin - ln -s ../toolchain/bin/x86_64-apple-darwin19-ld ld - ln -s ../toolchain/bin/x86_64-apple-darwin19-ar ar - ln -s /home/build/cctools-port/cctools/misc/lipo lipo - ln -s ../tools/appletoolchain xcrun - ln -s /usr/bin/plistutil plutil - cd ../tools - ln -s appletoolchain clang-ios - ln -s appletoolchain clang-macos - - install_appletoolchain: | - cd giouiorg - go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain - - build_xar: | - cd xar/xar - ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr - make - sudo make install - - build_libtapi: | - cd apple-libtapi - INSTALLPREFIX=$APPLE_TOOLCHAIN_ROOT/libtapi ./build.sh - ./install.sh - - build_cctools: | - cd cctools-port/cctools - ./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --target=x86_64-apple-darwin19 - make install - - install_gogio: | - cd gio-cmd - go install ./gogio - - test_ios_gogio: | - mkdir tmp - cd tmp - go mod init example.com - go get -d gioui.org/example/kitchen - export PATH=/home/build/appletools/bin:$PATH - gogio -target ios -o app.app gioui.org/example/kitchen diff --git a/third_party/gioui-cmd/.builds/freebsd.yml b/third_party/gioui-cmd/.builds/freebsd.yml deleted file mode 100644 index fe3734c..0000000 --- a/third_party/gioui-cmd/.builds/freebsd.yml +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: Unlicense OR MIT -image: freebsd/13.x -packages: - - libX11 - - libxkbcommon - - libXcursor - - libXfixes - - vulkan-headers - - wayland - - mesa-libs - - xorg-vfbserver -sources: - - https://git.sr.ht/~eliasnaur/gio-cmd -environment: - PATH: /home/build/sdk/go/bin:/bin:/usr/local/bin:/usr/bin -tasks: - - install_go: | - mkdir -p /home/build/sdk - curl https://dl.google.com/go/go1.19.8.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf - - - test_cmd: | - cd gio-cmd - go test ./... diff --git a/third_party/gioui-cmd/.builds/linux.yml b/third_party/gioui-cmd/.builds/linux.yml deleted file mode 100644 index 1fcba05..0000000 --- a/third_party/gioui-cmd/.builds/linux.yml +++ /dev/null @@ -1,91 +0,0 @@ -# SPDX-License-Identifier: Unlicense OR MIT -image: debian/bookworm -packages: - - curl - - pkg-config - - libwayland-dev - - libx11-dev - - libx11-xcb-dev - - libxkbcommon-dev - - libxkbcommon-x11-dev - - libgles2-mesa-dev - - libegl1-mesa-dev - - libffi-dev - - libvulkan-dev - - libxcursor-dev - - libxrandr-dev - - libxinerama-dev - - libxi-dev - - libxxf86vm-dev - - mesa-vulkan-drivers - - wine - - xvfb - - xdotool - - scrot - - sway - - grim - - wine - - unzip -sources: - - https://git.sr.ht/~eliasnaur/gio-cmd -environment: - PATH: /home/build/sdk/go/bin:/usr/bin:/home/build/go/bin:/home/build/android/tools/bin - ANDROID_SDK_ROOT: /home/build/android - android_sdk_tools_zip: sdk-tools-linux-3859397.zip - android_ndk_zip: android-ndk-r20-linux-x86_64.zip - github_mirror: git@github.com:gioui/gio-cmd -secrets: - - fdc570bf-87f4-4528-8aee-4d1711b1c86f -tasks: - - install_go: | - mkdir -p /home/build/sdk - curl -s https://dl.google.com/go/go1.19.8.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf - - - check_gofmt: | - cd gio-cmd - test -z "$(gofmt -s -l .)" - - check_sign_off: | - set +x -e - cd gio-cmd - for hash in $(git log -n 20 --format="%H"); do - message=$(git log -1 --format=%B $hash) - if [[ ! "$message" =~ "Signed-off-by: " ]]; then - echo "Missing 'Signed-off-by' in commit $hash" - exit 1 - fi - done - - mirror: | - # mirror to github - ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio-cmd && git push --mirror "$github_mirror" || echo "failed mirroring" - - install_chrome: | - curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - sudo sh -c 'echo "deb [arch=amd64] https://dl-ssl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' - sudo apt-get -qq update - sudo apt-get -qq install -y google-chrome-stable - - test: | - cd gio-cmd - go test ./... - go test -race ./... - - install_jdk8: | - curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb" - sudo apt-get -qq install -y -f ./jdk.deb - - install_android: | - mkdir android - cd android - curl -so sdk-tools.zip https://dl.google.com/android/repository/$android_sdk_tools_zip - unzip -q sdk-tools.zip - rm sdk-tools.zip - curl -so ndk.zip https://dl.google.com/android/repository/$android_ndk_zip - unzip -q ndk.zip - rm ndk.zip - mv android-ndk-* ndk-bundle - yes|sdkmanager --licenses - sdkmanager "platforms;android-31" "build-tools;32.0.0" - - install_gogio: | - cd gio-cmd - go install ./gogio - - test_android_gogio: | - mkdir tmp - cd tmp - go mod init example.com - go get -d gioui.org/example/kitchen - gogio -target android gioui.org/example/kitchen diff --git a/third_party/gioui-cmd/.builds/openbsd.yml b/third_party/gioui-cmd/.builds/openbsd.yml deleted file mode 100644 index afcf5d1..0000000 --- a/third_party/gioui-cmd/.builds/openbsd.yml +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: Unlicense OR MIT -image: openbsd/latest -packages: - - libxkbcommon - - go -sources: - - https://git.sr.ht/~eliasnaur/gio-cmd -environment: - PATH: /home/build/sdk/go/bin:/bin:/usr/local/bin:/usr/bin -tasks: - - install_go: | - mkdir -p /home/build/sdk - curl https://dl.google.com/go/go1.19.8.src.tar.gz | tar -C /home/build/sdk -xzf - - cd /home/build/sdk/go/src - ./make.bash - - test_cmd: | - cd gio-cmd - go test ./... diff --git a/third_party/gioui-cmd/LICENSE b/third_party/gioui-cmd/LICENSE deleted file mode 100644 index 81f4733..0000000 --- a/third_party/gioui-cmd/LICENSE +++ /dev/null @@ -1,63 +0,0 @@ -This project is provided under the terms of the UNLICENSE or -the MIT license denoted by the following SPDX identifier: - -SPDX-License-Identifier: Unlicense OR MIT - -You may use the project under the terms of either license. - -Both licenses are reproduced below. - ----- -The MIT License (MIT) - -Copyright (c) 2019 The Gio authors - -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 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 UNLICENSE - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -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 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. - -For more information, please refer to ---- diff --git a/third_party/gioui-cmd/README.md b/third_party/gioui-cmd/README.md deleted file mode 100644 index 75e9166..0000000 --- a/third_party/gioui-cmd/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Gio Tools - -Tools for the [Gio project](https://gioui.org), most notably `gogio` for packaging Gio programs. - -[![builds.sr.ht status](https://builds.sr.ht/~eliasnaur/gio-cmd.svg)](https://builds.sr.ht/~eliasnaur/gio-cmd) - -## Issues - -File bugs and TODOs through the [issue tracker](https://todo.sr.ht/~eliasnaur/gio) or send an email -to [~eliasnaur/gio@todo.sr.ht](mailto:~eliasnaur/gio@todo.sr.ht). For general discussion, use the -mailing list: [~eliasnaur/gio@lists.sr.ht](mailto:~eliasnaur/gio@lists.sr.ht). - -## Contributing - -Post discussion to the [mailing list](https://lists.sr.ht/~eliasnaur/gio) and patches to -[gio-patches](https://lists.sr.ht/~eliasnaur/gio-patches). No Sourcehut -account is required and you can post without being subscribed. - -See the [contribution guide](https://gioui.org/doc/contribute) for more details. - -An [official GitHub mirror](https://github.com/gioui/gio-cmd) is available. diff --git a/third_party/gioui-cmd/go.mod b/third_party/gioui-cmd/go.mod deleted file mode 100644 index 9a506b0..0000000 --- a/third_party/gioui-cmd/go.mod +++ /dev/null @@ -1,28 +0,0 @@ -module gioui.org/cmd - -go 1.21 - -require ( - gioui.org v0.8.0 - github.com/akavel/rsrc v0.10.1 - github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 - github.com/chromedp/chromedp v0.5.2 - golang.org/x/image v0.18.0 - golang.org/x/sync v0.7.0 - golang.org/x/text v0.16.0 - golang.org/x/tools v0.23.0 -) - -require ( - gioui.org/shader v1.0.8 // indirect - github.com/go-text/typesetting v0.2.1 // indirect - github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect - github.com/gobwas/pool v0.2.0 // indirect - github.com/gobwas/ws v1.0.2 // indirect - github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 // indirect - github.com/mailru/easyjson v0.7.0 // indirect - golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/sys v0.22.0 // indirect -) diff --git a/third_party/gioui-cmd/go.sum b/third_party/gioui-cmd/go.sum deleted file mode 100644 index bc2c6ae..0000000 --- a/third_party/gioui-cmd/go.sum +++ /dev/null @@ -1,44 +0,0 @@ -eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= -eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= -gioui.org v0.8.0 h1:QV5p5JvsmSmGiIXVYOKn6d9YDliTfjtLlVf5J+BZ9Pg= -gioui.org v0.8.0/go.mod h1:vEMmpxMOd/iwJhXvGVIzWEbxMWhnMQ9aByOGQdlQ8rc= -gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= -gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= -gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= -github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o= -github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg= -github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= -github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194= -github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA= -github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= -github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= -github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= -github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs= -github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU= -golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= diff --git a/third_party/gioui-cmd/gogio/android_test.go b/third_party/gioui-cmd/gogio/android_test.go deleted file mode 100644 index e73386f..0000000 --- a/third_party/gioui-cmd/gogio/android_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main_test - -import ( - "bytes" - "context" - "fmt" - "image" - "image/png" - "os" - "os/exec" - "path/filepath" - "regexp" -) - -type AndroidTestDriver struct { - driverBase - - sdkDir string - adbPath string -} - -var rxAdbDevice = regexp.MustCompile(`(.*)\s+device$`) - -func (d *AndroidTestDriver) Start(path string) { - d.sdkDir = os.Getenv("ANDROID_SDK_ROOT") - if d.sdkDir == "" { - d.Skipf("Android SDK is required; set $ANDROID_SDK_ROOT") - } - d.adbPath = filepath.Join(d.sdkDir, "platform-tools", "adb") - if _, err := os.Stat(d.adbPath); os.IsNotExist(err) { - d.Skipf("adb not found") - } - - devOut := bytes.TrimSpace(d.adb("devices")) - devices := rxAdbDevice.FindAllSubmatch(devOut, -1) - switch len(devices) { - case 0: - d.Skipf("no Android devices attached via adb; skipping") - case 1: - default: - d.Skipf("multiple Android devices attached via adb; skipping") - } - - // If the device is attached but asleep, it's probably just charging. - // Don't use it; the screen needs to be on and unlocked for the test to - // work. - if !bytes.Contains( - d.adb("shell", "dumpsys", "power"), - []byte(" mWakefulness=Awake"), - ) { - d.Skipf("Android device isn't awake; skipping") - } - - // First, build the app. - apk := filepath.Join(d.tempDir("gio-endtoend-android"), "e2e.apk") - d.gogio("-target=android", "-appid="+appid, "-o="+apk, path) - - // Make sure the app isn't installed already, and try to uninstall it - // when we finish. Previous failed test runs might have left the app. - d.tryUninstall() - d.adb("install", apk) - d.Cleanup(d.tryUninstall) - - // Force our e2e app to be fullscreen, so that the android system bar at - // the top doesn't mess with our screenshots. - // TODO(mvdan): is there a way to do this via gio, so that we don't need - // to set up a global Android setting via the shell? - d.adb("shell", "settings", "put", "global", "policy_control", "immersive.full="+appid) - - // Make sure the app isn't already running. - d.adb("shell", "pm", "clear", appid) - - // Start listening for log messages. - { - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, d.adbPath, - "logcat", - "-s", // suppress other logs - "-T1", // don't show previous log messages - appid+":*", // show all logs from our gio app ID - ) - output, err := cmd.StdoutPipe() - if err != nil { - d.Fatal(err) - } - cmd.Stderr = cmd.Stdout - d.output = output - if err := cmd.Start(); err != nil { - d.Fatal(err) - } - d.Cleanup(cancel) - } - - // Start the app. - d.adb("shell", "monkey", "-p", appid, "1") - - // Wait for the gio app to render. - d.waitForFrame() -} - -func (d *AndroidTestDriver) Screenshot() image.Image { - out := d.adb("shell", "screencap", "-p") - img, err := png.Decode(bytes.NewReader(out)) - if err != nil { - d.Fatal(err) - } - return img -} - -func (d *AndroidTestDriver) tryUninstall() { - cmd := exec.Command(d.adbPath, "shell", "pm", "uninstall", appid) - out, err := cmd.CombinedOutput() - if err != nil { - if bytes.Contains(out, []byte("Unknown package")) { - // The package is not installed. Don't log anything. - return - } - d.Logf("could not uninstall: %v\n%s", err, out) - } -} - -func (d *AndroidTestDriver) adb(args ...interface{}) []byte { - strs := []string{} - for _, arg := range args { - strs = append(strs, fmt.Sprint(arg)) - } - cmd := exec.Command(d.adbPath, strs...) - out, err := cmd.CombinedOutput() - if err != nil { - d.Errorf("%s", out) - d.Fatal(err) - } - return out -} - -func (d *AndroidTestDriver) Click(x, y int) { - d.adb("shell", "input", "tap", x, y) - - // Wait for the gio app to render after this click. - d.waitForFrame() -} diff --git a/third_party/gioui-cmd/gogio/androidbuild.go b/third_party/gioui-cmd/gogio/androidbuild.go deleted file mode 100644 index 75c8e19..0000000 --- a/third_party/gioui-cmd/gogio/androidbuild.go +++ /dev/null @@ -1,1110 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main - -import ( - "archive/zip" - "bytes" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - "text/template" - - "golang.org/x/sync/errgroup" - "golang.org/x/tools/go/packages" -) - -type androidTools struct { - buildtools string - androidjar string -} - -// zip.Writer with a sticky error. -type zipWriter struct { - err error - w *zip.Writer -} - -// Writer that saves any errors. -type errWriter struct { - w io.Writer - err *error -} - -var exeSuffix string - -type manifestData struct { - AppID string - Version Semver - MinSDK int - TargetSDK int - Permissions []string - Features []string - IconSnip string - AppName string - ManifestSnip string - AppSnip string -} - -const ( - themes = ` - - -` - themesV21 = ` - - -` -) - -func init() { - if runtime.GOOS == "windows" { - exeSuffix = ".exe" - } -} - -func buildAndroid(tmpDir string, bi *buildInfo) error { - sdk := os.Getenv("ANDROID_SDK_ROOT") - if sdk == "" { - return errors.New("please set ANDROID_SDK_ROOT to the Android SDK path") - } - if _, err := os.Stat(sdk); err != nil { - return err - } - platform, err := latestPlatform(sdk) - if err != nil { - return err - } - buildtools, err := latestTools(sdk) - if err != nil { - return err - } - - tools := &androidTools{ - buildtools: buildtools, - androidjar: filepath.Join(platform, "android.jar"), - } - perms := []string{"default"} - const permPref = "gioui.org/app/permission/" - cfg := &packages.Config{ - Mode: packages.NeedName + - packages.NeedFiles + - packages.NeedImports + - packages.NeedDeps, - Env: append( - os.Environ(), - "GOOS=android", - "CGO_ENABLED=1", - ), - } - pkgs, err := packages.Load(cfg, bi.pkgPath) - if err != nil { - return err - } - var extraJars []string - visitedPkgs := make(map[string]bool) - var visitPkg func(*packages.Package) error - visitPkg = func(p *packages.Package) error { - if len(p.GoFiles) == 0 { - return nil - } - dir := filepath.Dir(p.GoFiles[0]) - for _, pattern := range []string{ - filepath.Join(dir, "*.jar"), - filepath.Join(dir, "android", "*.jar"), - } { - jars, err := filepath.Glob(pattern) - if err != nil { - return err - } - extraJars = append(extraJars, jars...) - } - switch { - case p.PkgPath == "net": - perms = append(perms, "network") - case strings.HasPrefix(p.PkgPath, permPref): - perms = append(perms, p.PkgPath[len(permPref):]) - } - - for _, imp := range p.Imports { - if !visitedPkgs[imp.ID] { - visitPkg(imp) - visitedPkgs[imp.ID] = true - } - } - return nil - } - if err := visitPkg(pkgs[0]); err != nil { - return err - } - - if err := compileAndroid(tmpDir, tools, bi); err != nil { - return err - } - switch *buildMode { - case "archive": - return archiveAndroid(tmpDir, bi, perms) - case "exe": - file := *destPath - if file == "" { - file = fmt.Sprintf("%s.apk", bi.name) - } - - isBundle := false - switch filepath.Ext(file) { - case ".apk": - case ".aab": - isBundle = true - default: - return fmt.Errorf("the specified output %q does not end in '.apk' or '.aab'", file) - } - - if err := exeAndroid(tmpDir, tools, bi, extraJars, perms, isBundle); err != nil { - return err - } - if isBundle { - return signAAB(tmpDir, file, tools, bi) - } - return signAPK(tmpDir, file, tools, bi) - default: - panic("unreachable") - } -} - -func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) { - androidHome := os.Getenv("ANDROID_SDK_ROOT") - if androidHome == "" { - return errors.New("ANDROID_SDK_ROOT is not set. Please point it to the root of the Android SDK") - } - javac, err := findJavaC() - if err != nil { - return fmt.Errorf("could not find javac: %v", err) - } - ndkRoot, err := findNDK(androidHome) - if err != nil { - return err - } - minSDK := 17 - if bi.minsdk > minSDK { - minSDK = bi.minsdk - } - tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK()) - var builds errgroup.Group - for _, a := range bi.archs { - arch := allArchs[a] - clang, err := latestCompiler(tcRoot, a, minSDK) - if err != nil { - return fmt.Errorf("%s. Please make sure you have NDK >= r19c installed. Use the command `sdkmanager ndk-bundle` to install it.", err) - } - if runtime.GOOS == "windows" { - // Because of https://github.com/android-ndk/ndk/issues/920, - // we need NDK r19c, not just r19b. Check for the presence of - // clang++.cmd which is only available in r19c. - clangpp := clang + "++.cmd" - if _, err := os.Stat(clangpp); err != nil { - return fmt.Errorf("NDK version r19b detected, but >= r19c is required. Use the command `sdkmanager ndk-bundle` to install it") - } - } - archDir := filepath.Join(tmpDir, "jni", arch.jniArch) - if err := os.MkdirAll(archDir, 0755); err != nil { - return fmt.Errorf("failed to create %q: %v", archDir, err) - } - libFile := filepath.Join(archDir, "libgio.so") - cmd := exec.Command( - "go", - "build", - "-ldflags=-w -s "+bi.ldflags, - "-buildmode=c-shared", - "-tags", bi.tags, - "-o", libFile, - bi.pkgPath, - ) - cmd.Env = append( - os.Environ(), - "GOOS=android", - "GOARCH="+a, - "GOARM=7", // Avoid softfloat. - "CGO_ENABLED=1", - "CC="+clang, - ) - builds.Go(func() error { - _, err := runCmd(cmd) - return err - }) - } - appDir, err := runCmd(exec.Command("go", "list", "-tags", bi.tags, "-f", "{{.Dir}}", "gioui.org/app/")) - if err != nil { - return err - } - javaFiles, err := filepath.Glob(filepath.Join(appDir, "*.java")) - if err != nil { - return err - } - if len(javaFiles) == 0 { - return fmt.Errorf("the gioui.org/app package contains no .java files (gioui.org module too old?)") - } - if len(javaFiles) > 0 { - classes := filepath.Join(tmpDir, "classes") - if err := os.MkdirAll(classes, 0755); err != nil { - return err - } - javac := exec.Command( - javac, - "-target", "1.8", - "-source", "1.8", - "-sourcepath", appDir, - "-bootclasspath", tools.androidjar, - "-d", classes, - ) - javac.Args = append(javac.Args, javaFiles...) - builds.Go(func() error { - _, err := runCmd(javac) - return err - }) - } - return builds.Wait() -} - -func archiveAndroid(tmpDir string, bi *buildInfo, perms []string) (err error) { - aarFile := *destPath - if aarFile == "" { - aarFile = fmt.Sprintf("%s.aar", bi.name) - } - if filepath.Ext(aarFile) != ".aar" { - return fmt.Errorf("the specified output %q does not end in '.aar'", aarFile) - } - aar, err := os.Create(aarFile) - if err != nil { - return err - } - defer func() { - if cerr := aar.Close(); err == nil { - err = cerr - } - }() - aarw := newZipWriter(aar) - defer aarw.Close() - aarw.Create("R.txt") - themesXML := aarw.Create("res/values/themes.xml") - themesXML.Write([]byte(themes)) - themesXML21 := aarw.Create("res/values-v21/themes.xml") - themesXML21.Write([]byte(themesV21)) - permissions, features := getPermissions(perms) - // Disable input emulation on ChromeOS. - manifest := aarw.Create("AndroidManifest.xml") - manifestSrc := manifestData{ - AppID: bi.appID, - MinSDK: bi.minsdk, - Permissions: permissions, - Features: features, - } - tmpl, err := template.New("manifest").Parse( - ` - -{{range .Permissions}} -{{end}}{{range .Features}} -{{end}} -`) - if err != nil { - panic(err) - } - err = tmpl.Execute(manifest, manifestSrc) - proguard := aarw.Create("proguard.txt") - proguard.Write([]byte(`-keep class org.gioui.** { *; }`)) - - for _, a := range bi.archs { - arch := allArchs[a] - libFile := filepath.Join("jni", arch.jniArch, "libgio.so") - aarw.Add(filepath.ToSlash(libFile), filepath.Join(tmpDir, libFile)) - } - classes := filepath.Join(tmpDir, "classes") - if _, err := os.Stat(classes); err == nil { - jarFile := filepath.Join(tmpDir, "classes.jar") - if err := writeJar(jarFile, classes); err != nil { - return err - } - aarw.Add("classes.jar", jarFile) - } - return aarw.Close() -} - -func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, perms []string, isBundle bool) (err error) { - classes := filepath.Join(tmpDir, "classes") - var classFiles []string - err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - if filepath.Ext(path) == ".class" { - classFiles = append(classFiles, path) - } - return nil - }) - classFiles = append(classFiles, extraJars...) - dexDir := filepath.Join(tmpDir, "apk") - if err := os.MkdirAll(dexDir, 0755); err != nil { - return err - } - minSDK := 16 - if bi.minsdk > minSDK { - minSDK = bi.minsdk - } - // https://developer.android.com/distribute/best-practices/develop/target-sdk - targetSDK := 33 - if bi.targetsdk > 0 { - targetSDK = bi.targetsdk - } - if minSDK > targetSDK { - targetSDK = minSDK - } - if len(classFiles) > 0 { - d8 := exec.Command( - filepath.Join(tools.buildtools, "d8"), - "--lib", tools.androidjar, - "--output", dexDir, - "--min-api", strconv.Itoa(minSDK), - ) - d8.Args = append(d8.Args, classFiles...) - if _, err := runCmd(d8); err != nil { - major, minor, ok := determineJDKVersion() - if ok && (major != 1 || minor != 8) { - return fmt.Errorf("unsupported JDK version %d.%d, expected 1.8\nd8 error: %v", major, minor, err) - } - return err - } - } - - // Compile resources. - resDir := filepath.Join(tmpDir, "res") - valDir := filepath.Join(resDir, "values") - v21Dir := filepath.Join(resDir, "values-v21") - v26mipmapDir := filepath.Join(resDir, `mipmap-anydpi-v26`) - for _, dir := range []string{valDir, v21Dir, v26mipmapDir} { - if err := os.MkdirAll(dir, 0755); err != nil { - return err - } - } - iconSnip := "" - if _, err := os.Stat(bi.iconPath); err == nil { - err := buildIcons(resDir, bi.iconPath, []iconVariant{ - {path: filepath.Join("mipmap-hdpi", "ic_launcher.png"), size: 72}, - {path: filepath.Join("mipmap-xhdpi", "ic_launcher.png"), size: 96}, - {path: filepath.Join("mipmap-xxhdpi", "ic_launcher.png"), size: 144}, - {path: filepath.Join("mipmap-xxxhdpi", "ic_launcher.png"), size: 192}, - {path: filepath.Join("mipmap-mdpi", "ic_launcher_adaptive.png"), size: 108}, - {path: filepath.Join("mipmap-hdpi", "ic_launcher_adaptive.png"), size: 162}, - {path: filepath.Join("mipmap-xhdpi", "ic_launcher_adaptive.png"), size: 216}, - {path: filepath.Join("mipmap-xxhdpi", "ic_launcher_adaptive.png"), size: 324}, - {path: filepath.Join("mipmap-xxxhdpi", "ic_launcher_adaptive.png"), size: 432}, - }) - if err != nil { - return err - } - err = os.WriteFile(filepath.Join(v26mipmapDir, `ic_launcher.xml`), []byte(` - - - -`), 0660) - if err != nil { - return err - } - iconSnip = `android:icon="@mipmap/ic_launcher"` - } - err = os.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0660) - if err != nil { - return err - } - err = os.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0660) - if err != nil { - return err - } - extraResDir := filepath.Join(bi.pkgDir, "android", "res") - if err := copyTree(extraResDir, resDir); err != nil { - return err - } - resZip := filepath.Join(tmpDir, "resources.zip") - aapt2 := filepath.Join(tools.buildtools, "aapt2") - _, err = runCmd(exec.Command( - aapt2, - "compile", - "-o", resZip, - "--dir", resDir)) - if err != nil { - return err - } - - // Link APK. - permissions, features := getPermissions(perms) - appName := UppercaseName(bi.name) - manifestSrc := manifestData{ - AppID: bi.appID, - Version: bi.version, - MinSDK: minSDK, - TargetSDK: targetSDK, - Permissions: permissions, - Features: features, - IconSnip: iconSnip, - AppName: appName, - ManifestSnip: readOptionalText(filepath.Join(bi.pkgDir, "android", "manifest_snippets.xml")), - AppSnip: readOptionalText(filepath.Join(bi.pkgDir, "android", "application_snippets.xml")), - } - tmpl, err := template.New("test").Parse( - ` - - -{{range .Permissions}} -{{end}}{{range .Features}} -{{end}}{{.ManifestSnip}} -{{.AppSnip}} - - - - - - - -`) - var manifestBuffer bytes.Buffer - if err := tmpl.Execute(&manifestBuffer, manifestSrc); err != nil { - return err - } - manifest := filepath.Join(tmpDir, "AndroidManifest.xml") - if err := os.WriteFile(manifest, manifestBuffer.Bytes(), 0660); err != nil { - return err - } - - linkAPK := filepath.Join(tmpDir, "link.apk") - - args := []string{ - "link", - "--manifest", manifest, - "-I", tools.androidjar, - "-o", linkAPK, - } - if isBundle { - args = append(args, "--proto-format") - } - args = append(args, resZip) - - if _, err := runCmd(exec.Command(aapt2, args...)); err != nil { - return err - } - - // The Go standard library archive/zip doesn't support appending to zip - // files. Copy files from `link.apk` (generated by aapt2) along with classes.dex and - // the Go libraries to a new `app.zip` file. - - // Load link.apk as zip. - linkAPKZip, err := zip.OpenReader(linkAPK) - if err != nil { - return err - } - defer linkAPKZip.Close() - - // Create new "APK". - unsignedAPK := filepath.Join(tmpDir, "app.zip") - unsignedAPKFile, err := os.Create(unsignedAPK) - if err != nil { - return err - } - defer func() { - if cerr := unsignedAPKFile.Close(); err == nil { - err = cerr - } - }() - unsignedAPKZip := zip.NewWriter(unsignedAPKFile) - defer unsignedAPKZip.Close() - - // Copy files from linkAPK to unsignedAPK. - for _, f := range linkAPKZip.File { - header := zip.FileHeader{ - Name: f.FileHeader.Name, - Method: f.FileHeader.Method, - } - - if isBundle { - // AAB have pre-defined folders. - switch header.Name { - case "AndroidManifest.xml": - header.Name = "manifest/AndroidManifest.xml" - } - } - - w, err := unsignedAPKZip.CreateHeader(&header) - if err != nil { - return err - } - r, err := f.Open() - if err != nil { - return err - } - if _, err := io.Copy(w, r); err != nil { - return err - } - } - - // Append new files (that doesn't exists inside the link.apk). - appendToZip := func(path string, file string) error { - f, err := os.Open(file) - if err != nil { - return err - } - defer f.Close() - w, err := unsignedAPKZip.CreateHeader(&zip.FileHeader{ - Name: filepath.ToSlash(path), - Method: zip.Deflate, - }) - if err != nil { - return err - } - _, err = io.Copy(w, f) - return err - } - - // Append Go binaries (libgio.so). - for _, a := range bi.archs { - arch := allArchs[a] - libFile := filepath.Join(arch.jniArch, "libgio.so") - if err := appendToZip(filepath.Join("lib", libFile), filepath.Join(tmpDir, "jni", libFile)); err != nil { - return err - } - } - - // Append classes.dex. - if len(classFiles) > 0 { - classesFolder := "classes.dex" - if isBundle { - classesFolder = "dex/classes.dex" - } - if err := appendToZip(classesFolder, filepath.Join(dexDir, "classes.dex")); err != nil { - return err - } - } - - return unsignedAPKZip.Close() -} - -func determineJDKVersion() (int, int, bool) { - path, err := findJavaC() - if err != nil { - return 0, 0, false - } - java := exec.Command(filepath.Join(filepath.Dir(path), "java"), "-version") - out, err := java.CombinedOutput() - if err != nil { - return 0, 0, false - } - var vendor string - var major, minor int - _, err = fmt.Sscanf(string(out), "%s version \"%d.%d", &vendor, &major, &minor) - return major, minor, err == nil -} - -func signAPK(tmpDir string, apkFile string, tools *androidTools, bi *buildInfo) error { - if err := zipalign(tools, filepath.Join(tmpDir, "app.zip"), apkFile); err != nil { - return err - } - - if bi.key == "" { - if err := defaultAndroidKeystore(tmpDir, bi); err != nil { - return err - } - } - - _, err := runCmd(exec.Command( - filepath.Join(tools.buildtools, "apksigner"), - "sign", - "--ks-pass", "pass:"+bi.password, - "--ks", bi.key, - apkFile, - )) - - return err -} - -func signAAB(tmpDir string, aabFile string, tools *androidTools, bi *buildInfo) error { - allBundleTools, err := filepath.Glob(filepath.Join(tools.buildtools, "bundletool*.jar")) - if err != nil { - return err - } - - bundletool := "" - for _, v := range allBundleTools { - bundletool = v - break - } - - if bundletool == "" { - return fmt.Errorf("bundletool was not found at %s. Download it from https://github.com/google/bundletool/releases and move to the respective folder", tools.buildtools) - } - - _, err = runCmd(exec.Command( - "java", - "-jar", bundletool, - "build-bundle", - "--modules="+filepath.Join(tmpDir, "app.zip"), - "--output="+filepath.Join(tmpDir, "app.aab"), - )) - if err != nil { - return err - } - - if err := zipalign(tools, filepath.Join(tmpDir, "app.aab"), aabFile); err != nil { - return err - } - - if bi.key == "" { - if err := defaultAndroidKeystore(tmpDir, bi); err != nil { - return err - } - } - - keytoolList, err := runCmd(exec.Command( - "keytool", - "-keystore", bi.key, - "-list", - "-keypass", bi.password, - "-v", - )) - if err != nil { - return err - } - - var alias string - for _, t := range strings.Split(keytoolList, "\n") { - if i, _ := fmt.Sscanf(t, "Alias name: %s", &alias); i > 0 { - break - } - } - - _, err = runCmd(exec.Command( - filepath.Join("jarsigner"), - "-sigalg", "SHA256withRSA", - "-digestalg", "SHA-256", - "-keystore", bi.key, - "-storepass", bi.password, - aabFile, - strings.TrimSpace(alias), - )) - - return err -} - -func zipalign(tools *androidTools, input, output string) error { - _, err := runCmd(exec.Command( - filepath.Join(tools.buildtools, "zipalign"), - "-f", - "4", // 32-bit alignment. - input, - output, - )) - return err -} - -func defaultAndroidKeystore(tmpDir string, bi *buildInfo) error { - home, err := os.UserHomeDir() - if err != nil { - return err - } - - // Use debug.keystore, if exists. - bi.key = filepath.Join(home, ".android", "debug.keystore") - bi.password = "android" - if _, err := os.Stat(bi.key); err == nil { - return nil - } - - // Generate new key. - bi.key = filepath.Join(tmpDir, "sign.keystore") - keytool, err := findKeytool() - if err != nil { - return err - } - _, err = runCmd(exec.Command( - keytool, - "-genkey", - "-keystore", bi.key, - "-storepass", bi.password, - "-alias", "android", - "-keyalg", "RSA", "-keysize", "2048", - "-validity", "10000", - "-noprompt", - "-dname", "CN=android", - )) - return err -} - -func readOptionalText(path string) string { - data, err := os.ReadFile(path) - if err != nil { - return "" - } - if len(data) == 0 { - return "" - } - return "\n" + string(data) + "\n" -} - -func copyTree(src, dst string) error { - info, err := os.Stat(src) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - if !info.IsDir() { - return fmt.Errorf("extra Android resources path is not a directory: %s", src) - } - return filepath.Walk(src, func(path string, entry os.FileInfo, walkErr error) error { - if walkErr != nil { - return walkErr - } - rel, err := filepath.Rel(src, path) - if err != nil { - return err - } - if rel == "." { - return nil - } - target := filepath.Join(dst, rel) - if entry.IsDir() { - return os.MkdirAll(target, 0755) - } - if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { - return err - } - data, err := os.ReadFile(path) - if err != nil { - return err - } - return os.WriteFile(target, data, 0660) - }) -} - -func findNDK(androidHome string) (string, error) { - ndks, err := filepath.Glob(filepath.Join(androidHome, "ndk", "*")) - if err != nil { - return "", err - } - if bestNDK, found := latestVersionPath(ndks); found { - return bestNDK, nil - } - // The old NDK path was $ANDROID_SDK_ROOT/ndk-bundle. - ndkBundle := filepath.Join(androidHome, "ndk-bundle") - if _, err := os.Stat(ndkBundle); err == nil { - return ndkBundle, nil - } - // Certain non-standard NDK isntallations set the $ANDROID_NDK_ROOT - // environment variable - if ndkBundle, ok := os.LookupEnv("ANDROID_NDK_ROOT"); ok { - if _, err := os.Stat(ndkBundle); err == nil { - return ndkBundle, nil - } - } - - return "", fmt.Errorf("no NDK found in $ANDROID_SDK_ROOT (%s). Set $ANDROID_NDK_ROOT or use `sdkmanager ndk-bundle` to install the NDK", androidHome) -} - -func findKeytool() (string, error) { - javaHome := os.Getenv("JAVA_HOME") - if javaHome == "" { - return exec.LookPath("keytool") - } - - // bin, instead of "jre". "jre" was for older JVM it seems. - keytool := filepath.Join(javaHome, "bin", "keytool"+exeSuffix) - if _, err := os.Stat(keytool); err != nil { - return "", err - } - return keytool, nil -} - -func findJavaC() (string, error) { - javaHome := os.Getenv("JAVA_HOME") - if javaHome == "" { - return exec.LookPath("javac") - } - javac := filepath.Join(javaHome, "bin", "javac"+exeSuffix) - if _, err := os.Stat(javac); err != nil { - return "", err - } - return javac, nil -} - -func writeJar(jarFile, dir string) (err error) { - jar, err := os.Create(jarFile) - if err != nil { - return err - } - defer func() { - if cerr := jar.Close(); err == nil { - err = cerr - } - }() - jarw := newZipWriter(jar) - const manifestHeader = `Manifest-Version: 1.0 -Created-By: 1.0 (Go) - -` - jarw.Create("META-INF/MANIFEST.MF").Write([]byte(manifestHeader)) - err = filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - if f.IsDir() { - return nil - } - if filepath.Ext(path) == ".class" { - rel := filepath.ToSlash(path[len(dir)+1:]) - jarw.Add(rel, path) - } - return nil - }) - if err != nil { - return err - } - return jarw.Close() -} - -func archNDK() string { - var arch string - switch runtime.GOARCH { - case "386": - arch = "x86" - case "amd64": - arch = "x86_64" - case "arm64": - if runtime.GOOS == "darwin" { - // Workaround for arm64 macOS. This will keep working until - // Apple deprecates Rosetta 2. - arch = "x86_64" - } else { - panic("unsupported GOARCH: " + runtime.GOARCH) - } - default: - panic("unsupported GOARCH: " + runtime.GOARCH) - } - return runtime.GOOS + "-" + arch -} - -func getPermissions(ps []string) ([]string, []string) { - var permissions, features []string - seenPermissions := make(map[string]bool) - seenFeatures := make(map[string]bool) - for _, perm := range ps { - for _, x := range AndroidPermissions[perm] { - if !seenPermissions[x] { - permissions = append(permissions, x) - seenPermissions[x] = true - } - } - for _, x := range AndroidFeatures[perm] { - if !seenFeatures[x] { - features = append(features, x) - seenFeatures[x] = true - } - } - } - return permissions, features -} - -func latestPlatform(sdk string) (string, error) { - allPlats, err := filepath.Glob(filepath.Join(sdk, "platforms", "android-*")) - if err != nil { - return "", err - } - var bestVer int - var bestPlat string - for _, platform := range allPlats { - _, name := filepath.Split(platform) - // The glob above guarantees the "android-" prefix. - verStr := name[len("android-"):] - ver, err := strconv.Atoi(verStr) - if err != nil { - continue - } - if ver < bestVer { - continue - } - bestVer = ver - bestPlat = platform - } - if bestPlat == "" { - return "", fmt.Errorf("no platforms found in %q", sdk) - } - return bestPlat, nil -} - -func latestCompiler(tcRoot, a string, minsdk int) (string, error) { - arch := allArchs[a] - allComps, err := filepath.Glob(filepath.Join(tcRoot, "bin", arch.clangArch+"*-clang")) - if err != nil { - return "", err - } - var bestVer int - var firstVer int - var bestCompiler string - var firstCompiler string - for _, compiler := range allComps { - var ver int - pattern := filepath.Join(tcRoot, "bin", arch.clangArch) + "%d-clang" - if n, err := fmt.Sscanf(compiler, pattern, &ver); n < 1 || err != nil { - continue - } - if firstCompiler == "" || ver < firstVer { - firstVer = ver - firstCompiler = compiler - } - if ver < bestVer { - continue - } - if ver > minsdk { - continue - } - bestVer = ver - bestCompiler = compiler - } - if bestCompiler == "" { - bestCompiler = firstCompiler - } - if bestCompiler == "" { - return "", fmt.Errorf("no NDK compiler found for architecture %s in %s", a, tcRoot) - } - return bestCompiler, nil -} - -func latestTools(sdk string) (string, error) { - allTools, err := filepath.Glob(filepath.Join(sdk, "build-tools", "*")) - if err != nil { - return "", err - } - tools, found := latestVersionPath(allTools) - if !found { - return "", fmt.Errorf("no build-tools found in %q", sdk) - } - return tools, nil -} - -// latestVersionFile finds the path with the highest version -// among paths on the form -// -// /some/path/major.minor.patch -func latestVersionPath(paths []string) (string, bool) { - var bestVer [3]int - var bestDir string -loop: - for _, path := range paths { - name := filepath.Base(path) - s := strings.SplitN(name, ".", 3) - var version [3]int - for i, v := range s { - v, err := strconv.Atoi(v) - if err != nil { - continue loop - } - if v < bestVer[i] { - continue loop - } - if v > bestVer[i] { - break - } - version[i] = v - } - bestVer = version - bestDir = path - } - return bestDir, bestDir != "" -} - -func newZipWriter(w io.Writer) *zipWriter { - return &zipWriter{ - w: zip.NewWriter(w), - } -} - -func (z *zipWriter) Close() error { - err := z.w.Close() - if z.err == nil { - z.err = err - } - return z.err -} - -func (z *zipWriter) Create(name string) io.Writer { - if z.err != nil { - return io.Discard - } - w, err := z.w.Create(name) - if err != nil { - z.err = err - return io.Discard - } - return &errWriter{w: w, err: &z.err} -} - -func (z *zipWriter) Store(name, file string) { - z.add(name, file, false) -} - -func (z *zipWriter) Add(name, file string) { - z.add(name, file, true) -} - -func (z *zipWriter) add(name, file string, compressed bool) { - if z.err != nil { - return - } - f, err := os.Open(file) - if err != nil { - z.err = err - return - } - defer f.Close() - fh := &zip.FileHeader{ - Name: name, - } - if compressed { - fh.Method = zip.Deflate - } - w, err := z.w.CreateHeader(fh) - if err != nil { - z.err = err - return - } - if _, err := io.Copy(w, f); err != nil { - z.err = err - return - } -} - -func (w *errWriter) Write(p []byte) (n int, err error) { - if err := *w.err; err != nil { - return 0, err - } - n, err = w.w.Write(p) - *w.err = err - return -} diff --git a/third_party/gioui-cmd/gogio/build_info.go b/third_party/gioui-cmd/gogio/build_info.go deleted file mode 100644 index cbdb7fe..0000000 --- a/third_party/gioui-cmd/gogio/build_info.go +++ /dev/null @@ -1,206 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "strings" - "unicode" - "unicode/utf8" -) - -type buildInfo struct { - appID string - archs []string - ldflags string - minsdk int - targetsdk int - name string - pkgDir string - pkgPath string - iconPath string - tags string - target string - version Semver - key string - password string - notaryAppleID string - notaryPassword string - notaryTeamID string -} - -type Semver struct { - Major, Minor, Patch int - VersionCode uint32 -} - -func newBuildInfo(pkgPath string) (*buildInfo, error) { - pkgMetadata, err := getPkgMetadata(pkgPath) - if err != nil { - return nil, err - } - appID := getAppID(pkgMetadata) - appIcon := filepath.Join(pkgMetadata.Dir, "appicon.png") - if *iconPath != "" { - appIcon = *iconPath - } - appName := getPkgName(pkgMetadata) - if *name != "" { - appName = *name - } - ver, err := parseSemver(*version) - if err != nil { - return nil, err - } - bi := &buildInfo{ - appID: appID, - archs: getArchs(), - ldflags: getLdFlags(appID), - minsdk: *minsdk, - targetsdk: *targetsdk, - name: appName, - pkgDir: pkgMetadata.Dir, - pkgPath: pkgPath, - iconPath: appIcon, - tags: *extraTags, - target: *target, - version: ver, - key: *signKey, - password: *signPass, - notaryAppleID: *notaryID, - notaryPassword: *notaryPass, - notaryTeamID: *notaryTeamID, - } - return bi, nil -} - -// UppercaseName returns a string with its first rune in uppercase. -func UppercaseName(name string) string { - ch, w := utf8.DecodeRuneInString(name) - return string(unicode.ToUpper(ch)) + name[w:] -} - -func (s Semver) String() string { - return fmt.Sprintf("%d.%d.%d.%d", s.Major, s.Minor, s.Patch, s.VersionCode) -} - -func parseSemver(v string) (Semver, error) { - var sv Semver - _, err := fmt.Sscanf(v, "%d.%d.%d.%d", &sv.Major, &sv.Minor, &sv.Patch, &sv.VersionCode) - if err != nil { - return Semver{}, fmt.Errorf("invalid semver: %q", v) - } - if sv.String() != v { - return Semver{}, fmt.Errorf("invalid semver: %q", v) - } - return sv, nil -} - -func getArchs() []string { - if *archNames != "" { - return strings.Split(*archNames, ",") - } - switch *target { - case "js": - return []string{"wasm"} - case "ios", "tvos": - // Only 64-bit support. - return []string{"arm64", "amd64"} - case "android": - return []string{"arm", "arm64", "386", "amd64"} - case "windows": - goarch := os.Getenv("GOARCH") - if goarch == "" { - goarch = runtime.GOARCH - } - return []string{goarch} - case "macos": - return []string{"arm64", "amd64"} - default: - // TODO: Add flag tests. - panic("The target value has already been validated, this will never execute.") - } -} - -func getLdFlags(appID string) string { - var ldflags []string - if extra := *extraLdflags; extra != "" { - ldflags = append(ldflags, strings.Split(extra, " ")...) - } - // Pass appID along, to be used for logging on platforms like Android. - ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app.ID=%s", appID)) - // Support earlier Gio versions that had a separate app id recorded. - // TODO: delete this in the future. - ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app/internal/log.appID=%s", appID)) - // Pass along all remaining arguments to the app. - if appArgs := flag.Args()[1:]; len(appArgs) > 0 { - ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app.extraArgs=%s", strings.Join(appArgs, "|"))) - } - if m := *linkMode; m != "" { - ldflags = append(ldflags, "-linkmode="+m) - } - return strings.Join(ldflags, " ") -} - -type packageMetadata struct { - PkgPath string - Dir string -} - -func getPkgMetadata(pkgPath string) (*packageMetadata, error) { - pkgImportPath, err := runCmd(exec.Command("go", "list", "-tags", *extraTags, "-f", "{{.ImportPath}}", pkgPath)) - if err != nil { - return nil, err - } - pkgDir, err := runCmd(exec.Command("go", "list", "-tags", *extraTags, "-f", "{{.Dir}}", pkgPath)) - if err != nil { - return nil, err - } - return &packageMetadata{ - PkgPath: pkgImportPath, - Dir: pkgDir, - }, nil -} - -func getAppID(pkgMetadata *packageMetadata) string { - if *appID != "" { - return *appID - } - elems := strings.Split(pkgMetadata.PkgPath, "/") - domain := strings.Split(elems[0], ".") - name := "" - if len(elems) > 1 { - name = "." + elems[len(elems)-1] - } - if len(elems) < 2 && len(domain) < 2 { - name = "." + domain[0] - domain[0] = "localhost" - } else { - for i := 0; i < len(domain)/2; i++ { - opp := len(domain) - 1 - i - domain[i], domain[opp] = domain[opp], domain[i] - } - } - - pkgDomain := strings.Join(domain, ".") - appid := []rune(pkgDomain + name) - - // a Java-language-style package name may contain upper- and lower-case - // letters and underscores with individual parts separated by '.'. - // https://developer.android.com/guide/topics/manifest/manifest-element - for i, c := range appid { - if !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || - c == '_' || c == '.') { - appid[i] = '_' - } - } - return string(appid) -} - -func getPkgName(pkgMetadata *packageMetadata) string { - return path.Base(pkgMetadata.PkgPath) -} diff --git a/third_party/gioui-cmd/gogio/build_info_test.go b/third_party/gioui-cmd/gogio/build_info_test.go deleted file mode 100644 index 397e2a3..0000000 --- a/third_party/gioui-cmd/gogio/build_info_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import "testing" - -type expval struct { - in, out string -} - -func TestAppID(t *testing.T) { - t.Parallel() - - tests := []expval{ - {"example", "localhost.example"}, - {"example.com", "com.example"}, - {"www.example.com", "com.example.www"}, - {"examplecom/app", "examplecom.app"}, - {"example.com/app", "com.example.app"}, - {"www.example.com/app", "com.example.www.app"}, - {"www.en.example.com/app", "com.example.en.www.app"}, - {"example.com/dir/app", "com.example.app"}, - {"example.com/dir.ext/app", "com.example.app"}, - {"example.com/dir/app.ext", "com.example.app.ext"}, - {"example-com.net/dir/app", "net.example_com.app"}, - } - - for i, test := range tests { - got := getAppID(&packageMetadata{PkgPath: test.in}) - if exp := test.out; got != exp { - t.Errorf("(%d): expected '%s', got '%s'", i, exp, got) - } - } -} diff --git a/third_party/gioui-cmd/gogio/doc.go b/third_party/gioui-cmd/gogio/doc.go deleted file mode 100644 index 82da812..0000000 --- a/third_party/gioui-cmd/gogio/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -/* -The gogio tool builds and packages Gio programs for Android, iOS/tvOS -and WebAssembly. - -Run gogio with no arguments for instructions, or see the examples at -https://gioui.org. -*/ -package main diff --git a/third_party/gioui-cmd/gogio/e2e_test.go b/third_party/gioui-cmd/gogio/e2e_test.go deleted file mode 100644 index 46eee7f..0000000 --- a/third_party/gioui-cmd/gogio/e2e_test.go +++ /dev/null @@ -1,337 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main_test - -import ( - "bufio" - "errors" - "flag" - "fmt" - "image" - "image/color" - "io" - "os" - "os/exec" - "runtime" - "strings" - "testing" - "time" -) - -var raceEnabled = false - -var headless = flag.Bool("headless", true, "run end-to-end tests in headless mode") - -const appid = "localhost.gogio.endtoend" - -// TestDriver is implemented by each of the platforms we can run end-to-end -// tests on. None of its methods return any errors, as the errors are directly -// reported to testing.T via methods like Fatal. -type TestDriver interface { - initBase(t *testing.T, width, height int) - - // Start opens the Gio app found at path. The driver should attempt to - // run the app with the base driver's width and height, and the - // platform's background should be white. - // - // When the function returns, the gio app must be ready to use on the - // platform, with its initial frame fully drawn. - Start(path string) - - // Screenshot takes a screenshot of the Gio app on the platform. - Screenshot() image.Image - - // Click performs a pointer click at the specified coordinates, - // including both press and release. It returns when the next frame is - // fully drawn. - Click(x, y int) -} - -type driverBase struct { - *testing.T - - width, height int - - output io.Reader - frameNotifs chan bool -} - -func (d *driverBase) initBase(t *testing.T, width, height int) { - d.T = t - d.width, d.height = width, height -} - -func TestEndToEnd(t *testing.T) { - if testing.Short() { - t.Skipf("end-to-end tests tend to be slow") - } - - t.Parallel() - - const ( - testdataWithGoImportPkgPath = "gioui.org/cmd/gogio/internal/normal" - testdataWithRelativePkgPath = "internal/normal/testdata.go" - customRenderTestdataWithRelativePkgPath = "internal/custom/testdata.go" - ) - // Keep this list local, to not reuse TestDriver objects. - subtests := []struct { - name string - driver TestDriver - pkgPath string - skipGeese string - }{ - {"X11 using go import path", &X11TestDriver{}, testdataWithGoImportPkgPath, ""}, - {"X11", &X11TestDriver{}, testdataWithRelativePkgPath, ""}, - {"X11 with custom rendering", &X11TestDriver{}, customRenderTestdataWithRelativePkgPath, "openbsd,darwin,windows,netbsd"}, - // Doesn't work on the builders. - //{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath}, - {"JS", &JSTestDriver{}, testdataWithRelativePkgPath, ""}, - {"Android", &AndroidTestDriver{}, testdataWithRelativePkgPath, ""}, - {"Windows", &WineTestDriver{}, testdataWithRelativePkgPath, ""}, - } - - for _, subtest := range subtests { - t.Run(subtest.name, func(t *testing.T) { - subtest := subtest // copy the changing loop variable - if strings.Contains(subtest.skipGeese, runtime.GOOS) { - t.Skipf("not supported on %s", runtime.GOOS) - } - t.Parallel() - runEndToEndTest(t, subtest.driver, subtest.pkgPath) - }) - } -} - -func runEndToEndTest(t *testing.T, driver TestDriver, pkgPath string) { - size := image.Point{X: 800, Y: 600} - driver.initBase(t, size.X, size.Y) - - t.Log("starting driver and gio app") - driver.Start(pkgPath) - - beef := color.NRGBA{R: 0xde, G: 0xad, B: 0xbe, A: 0xff} - white := color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - black := color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff} - gray := color.NRGBA{R: 0xbb, G: 0xbb, B: 0xbb, A: 0xff} - red := color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff} - - // These are the four colors at the beginning. - t.Log("taking initial screenshot") - withRetries(t, 4*time.Second, func() error { - img := driver.Screenshot() - size = img.Bounds().Size() // override the default size - return checkImageCorners(img, beef, white, black, gray) - }) - - // TODO(mvdan): implement this properly in the Wayland driver; swaymsg - // almost works to automate clicks, but the button presses end up in the - // wrong coordinates. - if _, ok := driver.(*WaylandTestDriver); ok { - return - } - - // Click the first and last sections to turn them red. - t.Log("clicking twice and taking another screenshot") - driver.Click(1*(size.X/4), 1*(size.Y/4)) - driver.Click(3*(size.X/4), 3*(size.Y/4)) - withRetries(t, 4*time.Second, func() error { - img := driver.Screenshot() - return checkImageCorners(img, red, white, black, red) - }) -} - -// withRetries keeps retrying fn until it succeeds, or until the timeout is hit. -// It uses a rudimentary kind of backoff, which starts with 100ms delays. As -// such, timeout should generally be in the order of seconds. -func withRetries(t *testing.T, timeout time.Duration, fn func() error) { - t.Helper() - - timeoutTimer := time.NewTimer(timeout) - defer timeoutTimer.Stop() - backoff := 100 * time.Millisecond - - tries := 0 - var lastErr error - for { - if lastErr = fn(); lastErr == nil { - return - } - tries++ - t.Logf("retrying after %s", backoff) - - // Use a timer instead of a sleep, so that the timeout can stop - // the backoff early. Don't reuse this timer, since we're not in - // a hot loop, and we don't want tricky code. - backoffTimer := time.NewTimer(backoff) - defer backoffTimer.Stop() - - select { - case <-timeoutTimer.C: - t.Errorf("last error: %v", lastErr) - t.Fatalf("hit timeout of %s after %d tries", timeout, tries) - case <-backoffTimer.C: - } - - // Keep doubling it until a maximum. With the start at 100ms, - // we'll do: 100ms, 200ms, 400ms, 800ms, 1.6s, and 2s forever. - backoff *= 2 - if max := 2 * time.Second; backoff > max { - backoff = max - } - } -} - -type colorMismatch struct { - x, y int - wantRGB, gotRGB [3]uint32 -} - -func (m colorMismatch) String() string { - return fmt.Sprintf("%3d,%-3d got 0x%04x%04x%04x, want 0x%04x%04x%04x", - m.x, m.y, - m.gotRGB[0], m.gotRGB[1], m.gotRGB[2], - m.wantRGB[0], m.wantRGB[1], m.wantRGB[2], - ) -} - -func checkImageCorners(img image.Image, topLeft, topRight, botLeft, botRight color.Color) error { - // The colors are split in four rectangular sections. Check the corners - // of each of the sections. We check the corners left to right, top to - // bottom, like when reading left-to-right text. - - size := img.Bounds().Size() - var mismatches []colorMismatch - - checkColor := func(x, y int, want color.Color) { - r, g, b, _ := want.RGBA() - got := img.At(x, y) - r_, g_, b_, _ := got.RGBA() - if r_ != r || g_ != g || b_ != b { - mismatches = append(mismatches, colorMismatch{ - x: x, - y: y, - wantRGB: [3]uint32{r, g, b}, - gotRGB: [3]uint32{r_, g_, b_}, - }) - } - } - - { - minX, minY := 5, 5 - maxX, maxY := (size.X/2)-5, (size.Y/2)-5 - checkColor(minX, minY, topLeft) - checkColor(maxX, minY, topLeft) - checkColor(minX, maxY, topLeft) - checkColor(maxX, maxY, topLeft) - } - { - minX, minY := (size.X/2)+5, 5 - maxX, maxY := size.X-5, (size.Y/2)-5 - checkColor(minX, minY, topRight) - checkColor(maxX, minY, topRight) - checkColor(minX, maxY, topRight) - checkColor(maxX, maxY, topRight) - } - { - minX, minY := 5, (size.Y/2)+5 - maxX, maxY := (size.X/2)-5, size.Y-5 - checkColor(minX, minY, botLeft) - checkColor(maxX, minY, botLeft) - checkColor(minX, maxY, botLeft) - checkColor(maxX, maxY, botLeft) - } - { - minX, minY := (size.X/2)+5, (size.Y/2)+5 - maxX, maxY := size.X-5, size.Y-5 - checkColor(minX, minY, botRight) - checkColor(maxX, minY, botRight) - checkColor(minX, maxY, botRight) - checkColor(maxX, maxY, botRight) - } - if n := len(mismatches); n > 0 { - b := new(strings.Builder) - fmt.Fprintf(b, "encountered %d color mismatches:\n", n) - for _, m := range mismatches { - fmt.Fprintf(b, "%s\n", m) - } - return errors.New(b.String()) - } - return nil -} - -func (d *driverBase) waitForFrame() { - d.Helper() - - if d.frameNotifs == nil { - // Start the goroutine that reads output lines and notifies of - // new frames via frameNotifs. The test doesn't wait for this - // goroutine to finish; it will naturally end when the output - // reader reaches an error like EOF. - d.frameNotifs = make(chan bool, 1) - if d.output == nil { - d.Fatal("need an output reader to be notified of frames") - } - go func() { - scanner := bufio.NewScanner(d.output) - for scanner.Scan() { - line := scanner.Text() - d.Log(line) - if strings.Contains(line, "gio frame ready") { - d.frameNotifs <- true - } - } - // Since we're only interested in the output while the - // app runs, and we don't know when it finishes here, - // ignore "already closed" pipe errors. - if err := scanner.Err(); err != nil && !errors.Is(err, os.ErrClosed) { - d.Errorf("reading app output: %v", err) - } - }() - } - - // Unfortunately, there isn't a way to select on a test failing, since - // testing.T doesn't have anything like a context or a "done" channel. - // - // We can't let selects block forever, since the default -test.timeout - // is ten minutes - far too long for tests that take seconds. - // - // For now, a static short timeout is better than nothing. 5s is plenty - // for our simple test app to render on any device. - select { - case <-d.frameNotifs: - case <-time.After(5 * time.Second): - d.Fatalf("timed out waiting for a frame to be ready") - } -} - -func (d *driverBase) needPrograms(names ...string) { - d.Helper() - for _, name := range names { - if _, err := exec.LookPath(name); err != nil { - d.Skipf("%s needed to run", name) - } - } -} - -func (d *driverBase) tempDir(name string) string { - d.Helper() - dir, err := os.MkdirTemp("", name) - if err != nil { - d.Fatal(err) - } - d.Cleanup(func() { os.RemoveAll(dir) }) - return dir -} - -func (d *driverBase) gogio(args ...string) { - d.Helper() - prog, err := os.Executable() - if err != nil { - d.Fatal(err) - } - cmd := exec.Command(prog, args...) - cmd.Env = append(os.Environ(), "RUN_GOGIO=1") - if out, err := cmd.CombinedOutput(); err != nil { - d.Fatalf("gogio error: %s:\n%s", err, out) - } -} diff --git a/third_party/gioui-cmd/gogio/help.go b/third_party/gioui-cmd/gogio/help.go deleted file mode 100644 index 0399e39..0000000 --- a/third_party/gioui-cmd/gogio/help.go +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main - -const mainUsage = `The gogio command builds and packages Gio (gioui.org) programs. - -Usage: - - gogio -target [flags] [run arguments] - -The gogio tool builds and packages Gio programs for platforms where additional -metadata or support files are required. - -The package argument specifies an import path or a single Go source file to -package. Any run arguments are appended to os.Args at runtime. - -Compiled Java class files from jar files in the package directory are -included in Android builds. - -The mandatory -target flag selects the target platform: ios or android for the -mobile platforms, tvos for Apple's tvOS, js for WebAssembly/WebGL, macos for -MacOS and windows for Windows. - -The -arch flag specifies a comma separated list of GOARCHs to include. The -default is all supported architectures. - -The -o flag specifies an output file or directory, depending on the target. - -The -buildmode flag selects the build mode. Two build modes are available, exe -and archive. Buildmode exe outputs an .ipa file for iOS or tvOS, an .apk file -for Android or a directory with the WebAssembly module and support files for -a browser. - -The -ldflags and -tags flags pass extra linker flags and tags to the go tool. - -As a special case for iOS or tvOS, specifying a path that ends with ".app" -will output an app directory suitable for a simulator. - -The other buildmode is archive, which will output an .aar library for Android -or a .framework for iOS and tvOS. - -The -icon flag specifies a path to a PNG image to use as app icon on iOS and Android. -If left unspecified, the appicon.png file from the main package is used -(if it exists). - -The -appid flag specifies the package name for Android or the bundle id for -iOS and tvOS. A bundle id must be provisioned through Xcode before the gogio -tool can use it. - -The -version flag specifies the integer version code for Android and the last -component of the 1.0.X version for iOS and tvOS. - -For Android builds the -minsdk flag specify the minimum SDK level. For example, -use -minsdk 22 to target Android 5.1 (Lollipop) and later. - -For Windows builds the -minsdk flag specify the minimum OS version. For example, -use -mindk 10 to target Windows 10 and later, -minsdk 6 for Windows Vista and later. - -For iOS builds the -minsdk flag specify the minimum iOS version. For example, -use -mindk 15 to target iOS 15.0 and later. - -For Android builds the -targetsdk flag specify the target SDK level. For example, -use -targetsdk 33 to target Android 13 (Tiramisu) and later. - -The -work flag prints the path to the working directory and suppress -its deletion. - -The -x flag will print all the external commands executed by the gogio tool. - -The -signkey flag specifies the path of the keystore, used for signing Android apk/aab files -or specifies the name of key on Keychain to sign MacOS app. - -The -signpass flag specifies the password of the keystore, ignored if -signkey is not provided. - -The -notaryid flag specifies the Apple ID to use for notarization of MacOS app. - -The -notarypass flag specifies the password of the Apple ID, ignored if -notaryid is not -provided. That must be an app-specific password, see https://support.apple.com/en-us/HT204397 -for details. If not provided, the password will be prompted. - -The -notaryteamid flag specifies the team ID to use for notarization of MacOS app, ignored if --notaryid is not provided. -` diff --git a/third_party/gioui-cmd/gogio/internal/custom/testdata.go b/third_party/gioui-cmd/gogio/internal/custom/testdata.go deleted file mode 100644 index 5a17448..0000000 --- a/third_party/gioui-cmd/gogio/internal/custom/testdata.go +++ /dev/null @@ -1,371 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -//go:build linux -// +build linux - -// This program demonstrates the use of a custom OpenGL ES context with -// app.Window. -package main - -import ( - "errors" - "fmt" - "image" - "image/color" - "log" - "os" - "runtime" - "strings" - "unsafe" - - "gioui.org/app" - "gioui.org/gpu" - "gioui.org/io/event" - "gioui.org/io/pointer" - "gioui.org/layout" - "gioui.org/op" - "gioui.org/op/clip" - "gioui.org/op/paint" -) - -/* -#cgo linux pkg-config: egl wayland-egl -#cgo freebsd openbsd CFLAGS: -I/usr/local/include -#cgo openbsd CFLAGS: -I/usr/X11R6/include -#cgo freebsd LDFLAGS: -L/usr/local/lib -#cgo openbsd LDFLAGS: -L/usr/X11R6/lib -#cgo freebsd openbsd LDFLAGS: -lwayland-egl -#cgo CFLAGS: -DEGL_NO_X11 -#cgo LDFLAGS: -lEGL -lGLESv2 - -#include -#include -#include -#include -#define EGL_EGLEXT_PROTOTYPES -#include - -*/ -import "C" - -func getDisplay(ve app.ViewEvent) C.EGLDisplay { - switch ve := ve.(type) { - case app.X11ViewEvent: - return C.eglGetDisplay(C.EGLNativeDisplayType(ve.Display)) - case app.WaylandViewEvent: - return C.eglGetDisplay(C.EGLNativeDisplayType(ve.Display)) - } - panic("no display available") -} - -func nativeViewFor(e app.ViewEvent, size image.Point) (C.EGLNativeWindowType, func()) { - switch e := e.(type) { - case app.X11ViewEvent: - return C.EGLNativeWindowType(uintptr(e.Window)), func() {} - case app.WaylandViewEvent: - eglWin := C.wl_egl_window_create((*C.struct_wl_surface)(e.Surface), C.int(size.X), C.int(size.Y)) - return C.EGLNativeWindowType(uintptr(unsafe.Pointer(eglWin))), func() { - C.wl_egl_window_destroy(eglWin) - } - } - panic("no native view available") -} - -type ( - C = layout.Context - D = layout.Dimensions -) - -type notifyFrame int - -const ( - notifyNone notifyFrame = iota - notifyInvalidate - notifyPrint -) - -// notify keeps track of whether we want to print to stdout to notify the user -// when a frame is ready. Initially we want to notify about the first frame. -var notify = notifyInvalidate - -type eglContext struct { - disp C.EGLDisplay - ctx C.EGLContext - surf C.EGLSurface - cleanup func() -} - -func main() { - go func() { - // Set CustomRenderer so we can provide our own rendering context. - w := new(app.Window) - w.Option(app.CustomRenderer(true)) - if err := loop(w); err != nil { - log.Fatal(err) - } - os.Exit(0) - }() - app.Main() -} - -func loop(w *app.Window) error { - var ops op.Ops - var ( - ctx *eglContext - gioCtx gpu.GPU - ve app.ViewEvent - init bool - size image.Point - ) - - recreateContext := func() { - w.Run(func() { - if gioCtx != nil { - gioCtx.Release() - gioCtx = nil - } - if ctx != nil { - C.eglMakeCurrent(ctx.disp, nil, nil, nil) - ctx.Release() - ctx = nil - } - c, err := createContext(ve, size) - if err != nil { - log.Fatal(err) - } - ctx = c - }) - if ok := C.eglMakeCurrent(ctx.disp, ctx.surf, ctx.surf, ctx.ctx); ok != C.EGL_TRUE { - err := fmt.Errorf("eglMakeCurrent failed (%#x)", C.eglGetError()) - log.Fatal(err) - } - glGetString := func(e C.GLenum) string { - return C.GoString((*C.char)(unsafe.Pointer(C.glGetString(e)))) - } - fmt.Printf("GL_VERSION: %s\nGL_RENDERER: %s\n", glGetString(C.GL_VERSION), glGetString(C.GL_RENDERER)) - var err error - gioCtx, err = gpu.New(gpu.OpenGL{ES: true, Shared: true}) - if err != nil { - log.Fatal(err) - } - } - - topLeft := quarterWidget{ - color: color.NRGBA{R: 0xde, G: 0xad, B: 0xbe, A: 0xff}, - } - topRight := quarterWidget{ - color: color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, - } - botLeft := quarterWidget{ - color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}, - } - botRight := quarterWidget{ - color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0x80}, - } - - // eglMakeCurrent binds a context to an operating system thread. Prevent Go from switching thread. - runtime.LockOSThread() - for { - switch e := w.Event().(type) { - case app.ViewEvent: - ve = e - init = true - if size != (image.Point{}) { - recreateContext() - } - case app.DestroyEvent: - return e.Err - case app.FrameEvent: - if init && size != e.Size { - size = e.Size - recreateContext() - } - if gioCtx == nil || !init { - break - } - // Build ops. - gtx := app.NewContext(&ops, e) - - // Clear background to white, even on embedded platforms such as webassembly. - paint.Fill(gtx.Ops, color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}) - layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Flexed(1, func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - // r1c1 - layout.Flexed(1, func(gtx C) D { return topLeft.Layout(gtx) }), - // r1c2 - layout.Flexed(1, func(gtx C) D { return topRight.Layout(gtx) }), - ) - }), - layout.Flexed(1, func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - // r2c1 - layout.Flexed(1, func(gtx C) D { return botLeft.Layout(gtx) }), - // r2c2 - layout.Flexed(1, func(gtx C) D { return botRight.Layout(gtx) }), - ) - }), - ) - gtx.Execute(op.InvalidateCmd{}) - log.Println("frame") - - // Trigger window resize detection in ANGLE. - C.eglWaitClient() - // Draw custom OpenGL content. - drawGL() - - // Render drawing ops. - if err := gioCtx.Frame(gtx.Ops, gpu.OpenGLRenderTarget{}, e.Size); err != nil { - log.Fatal(fmt.Errorf("render failed: %v", err)) - } - - // Process non-drawing ops. - e.Frame(gtx.Ops) - switch notify { - case notifyInvalidate: - notify = notifyPrint - w.Invalidate() - case notifyPrint: - notify = notifyNone - fmt.Println("gio frame ready") - } - - if ok := C.eglSwapBuffers(ctx.disp, ctx.surf); ok != C.EGL_TRUE { - log.Fatal(fmt.Errorf("swap failed: %v", C.eglGetError())) - } - - } - } - return nil -} - -func drawGL() { - C.glClearColor(0, 0, 0, 1) - C.glClear(C.GL_COLOR_BUFFER_BIT | C.GL_DEPTH_BUFFER_BIT) -} - -func createContext(ve app.ViewEvent, size image.Point) (*eglContext, error) { - view, cleanup := nativeViewFor(ve, size) - var nilv C.EGLNativeWindowType - if view == nilv { - return nil, fmt.Errorf("failed creating native view") - } - disp := getDisplay(ve) - if disp == 0 { - return nil, fmt.Errorf("eglGetPlatformDisplay failed: 0x%x", C.eglGetError()) - } - var major, minor C.EGLint - if ok := C.eglInitialize(disp, &major, &minor); ok != C.EGL_TRUE { - return nil, fmt.Errorf("eglInitialize failed: 0x%x", C.eglGetError()) - } - exts := strings.Split(C.GoString(C.eglQueryString(disp, C.EGL_EXTENSIONS)), " ") - srgb := hasExtension(exts, "EGL_KHR_gl_colorspace") - attribs := []C.EGLint{ - C.EGL_RENDERABLE_TYPE, C.EGL_OPENGL_ES2_BIT, - C.EGL_SURFACE_TYPE, C.EGL_WINDOW_BIT, - C.EGL_BLUE_SIZE, 8, - C.EGL_GREEN_SIZE, 8, - C.EGL_RED_SIZE, 8, - C.EGL_CONFIG_CAVEAT, C.EGL_NONE, - } - if srgb { - // Some drivers need alpha for sRGB framebuffers to work. - attribs = append(attribs, C.EGL_ALPHA_SIZE, 8) - } - attribs = append(attribs, C.EGL_NONE) - var ( - cfg C.EGLConfig - numCfgs C.EGLint - ) - if ok := C.eglChooseConfig(disp, &attribs[0], &cfg, 1, &numCfgs); ok != C.EGL_TRUE { - return nil, fmt.Errorf("eglChooseConfig failed: 0x%x", C.eglGetError()) - } - if numCfgs == 0 { - supportsNoCfg := hasExtension(exts, "EGL_KHR_no_config_context") - if !supportsNoCfg { - return nil, errors.New("eglChooseConfig returned no configs") - } - } - ctxAttribs := []C.EGLint{ - C.EGL_CONTEXT_CLIENT_VERSION, 3, - C.EGL_NONE, - } - ctx := C.eglCreateContext(disp, cfg, nil, &ctxAttribs[0]) - if ctx == nil { - return nil, fmt.Errorf("eglCreateContext failed: 0x%x", C.eglGetError()) - } - var surfAttribs []C.EGLint - if srgb { - surfAttribs = append(surfAttribs, C.EGL_GL_COLORSPACE, C.EGL_GL_COLORSPACE_SRGB) - } - surfAttribs = append(surfAttribs, C.EGL_NONE) - surf := C.eglCreateWindowSurface(disp, cfg, view, &surfAttribs[0]) - if surf == nil { - return nil, fmt.Errorf("eglCreateWindowSurface failed (0x%x)", C.eglGetError()) - } - return &eglContext{disp: disp, ctx: ctx, surf: surf, cleanup: cleanup}, nil -} - -func (c *eglContext) Release() { - if c.ctx != nil { - C.eglDestroyContext(c.disp, c.ctx) - } - if c.surf != nil { - C.eglDestroySurface(c.disp, c.surf) - } - if c.cleanup != nil { - c.cleanup() - } - *c = eglContext{} -} - -func hasExtension(exts []string, ext string) bool { - for _, e := range exts { - if ext == e { - return true - } - } - return false -} - -// quarterWidget paints a quarter of the screen with one color. When clicked, it -// turns red, going back to its normal color when clicked again. -type quarterWidget struct { - color color.NRGBA - - clicked bool -} - -var red = color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff} - -func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions { - var color color.NRGBA - if w.clicked { - color = red - } else { - color = w.color - } - - r := image.Rectangle{Max: gtx.Constraints.Max} - paint.FillShape(gtx.Ops, color, clip.Rect(r).Op()) - - defer clip.Rect(image.Rectangle{ - Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y), - }).Push(gtx.Ops).Pop() - event.Op(gtx.Ops, w) - for { - e, ok := gtx.Event(pointer.Filter{ - Target: w, - Kinds: pointer.Press, - }) - if !ok { - break - } - if e, ok := e.(pointer.Event); ok && e.Kind == pointer.Press { - w.clicked = !w.clicked - // notify when we're done updating the frame. - notify = notifyInvalidate - } - } - return layout.Dimensions{Size: gtx.Constraints.Max} -} diff --git a/third_party/gioui-cmd/gogio/internal/normal/testdata.go b/third_party/gioui-cmd/gogio/internal/normal/testdata.go deleted file mode 100644 index a78f884..0000000 --- a/third_party/gioui-cmd/gogio/internal/normal/testdata.go +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -// A simple app used for gogio's end-to-end tests. -package main - -import ( - "fmt" - "image" - "image/color" - "log" - - "gioui.org/app" - "gioui.org/io/event" - "gioui.org/io/pointer" - "gioui.org/layout" - "gioui.org/op" - "gioui.org/op/clip" - "gioui.org/op/paint" -) - -func main() { - go func() { - w := new(app.Window) - if err := loop(w); err != nil { - log.Fatal(err) - } - }() - app.Main() -} - -type notifyFrame int - -const ( - notifyNone notifyFrame = iota - notifyInvalidate - notifyPrint -) - -// notify keeps track of whether we want to print to stdout to notify the user -// when a frame is ready. Initially we want to notify about the first frame. -var notify = notifyInvalidate - -type ( - C = layout.Context - D = layout.Dimensions -) - -func loop(w *app.Window) error { - topLeft := quarterWidget{ - color: color.NRGBA{R: 0xde, G: 0xad, B: 0xbe, A: 0xff}, - } - topRight := quarterWidget{ - color: color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, - } - botLeft := quarterWidget{ - color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}, - } - botRight := quarterWidget{ - color: color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0x80}, - } - - var ops op.Ops - for { - e := w.Event() - switch e := e.(type) { - case app.DestroyEvent: - return e.Err - case app.FrameEvent: - gtx := app.NewContext(&ops, e) - // Clear background to white, even on embedded platforms such as webassembly. - paint.Fill(gtx.Ops, color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}) - layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Flexed(1, func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - // r1c1 - layout.Flexed(1, func(gtx C) D { return topLeft.Layout(gtx) }), - // r1c2 - layout.Flexed(1, func(gtx C) D { return topRight.Layout(gtx) }), - ) - }), - layout.Flexed(1, func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - // r2c1 - layout.Flexed(1, func(gtx C) D { return botLeft.Layout(gtx) }), - // r2c2 - layout.Flexed(1, func(gtx C) D { return botRight.Layout(gtx) }), - ) - }), - ) - - e.Frame(gtx.Ops) - - switch notify { - case notifyInvalidate: - notify = notifyPrint - w.Invalidate() - case notifyPrint: - notify = notifyNone - fmt.Println("gio frame ready") - } - } - } -} - -// quarterWidget paints a quarter of the screen with one color. When clicked, it -// turns red, going back to its normal color when clicked again. -type quarterWidget struct { - color color.NRGBA - - clicked bool -} - -var red = color.NRGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff} - -func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions { - var color color.NRGBA - if w.clicked { - color = red - } else { - color = w.color - } - - r := image.Rectangle{Max: gtx.Constraints.Max} - paint.FillShape(gtx.Ops, color, clip.Rect(r).Op()) - - defer clip.Rect(image.Rectangle{ - Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y), - }).Push(gtx.Ops).Pop() - event.Op(gtx.Ops, w) - filter := pointer.Filter{ - Target: w, - Kinds: pointer.Press, - } - - for { - e, ok := gtx.Event(filter) - if !ok { - break - } - if e, ok := e.(pointer.Event); ok && e.Kind == pointer.Press { - w.clicked = !w.clicked - // notify when we're done updating the frame. - notify = notifyInvalidate - } - } - return layout.Dimensions{Size: gtx.Constraints.Max} -} diff --git a/third_party/gioui-cmd/gogio/iosbuild.go b/third_party/gioui-cmd/gogio/iosbuild.go deleted file mode 100644 index 1126cd5..0000000 --- a/third_party/gioui-cmd/gogio/iosbuild.go +++ /dev/null @@ -1,546 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main - -import ( - "archive/zip" - "crypto/sha1" - "encoding/hex" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "time" - - "golang.org/x/sync/errgroup" -) - -const ( - minIOSVersion = 10 - // Some Metal features require tvOS 11 - minTVOSVersion = 11 - // Metal is available from iOS 8 on devices, yet from version 13 on the - // simulator. - minSimulatorVersion = 13 -) - -func buildIOS(tmpDir, target string, bi *buildInfo) error { - appName := bi.name - switch *buildMode { - case "archive": - framework := *destPath - if framework == "" { - framework = fmt.Sprintf("%s.framework", UppercaseName(appName)) - } - return archiveIOS(tmpDir, target, framework, bi) - case "exe": - out := *destPath - if out == "" { - out = appName + ".ipa" - } - forDevice := strings.HasSuffix(out, ".ipa") - // Filter out unsupported architectures. - for i := len(bi.archs) - 1; i >= 0; i-- { - switch bi.archs[i] { - case "arm", "arm64": - if forDevice { - continue - } - case "386", "amd64": - if !forDevice { - continue - } - } - - bi.archs = append(bi.archs[:i], bi.archs[i+1:]...) - } - if !forDevice && !strings.HasSuffix(out, ".app") { - return fmt.Errorf("the specified output directory %q does not end in .app or .ipa", out) - } - if !forDevice { - return exeIOS(tmpDir, target, out, bi) - } - payload := filepath.Join(tmpDir, "Payload") - appDir := filepath.Join(payload, appName+".app") - if err := os.MkdirAll(appDir, 0755); err != nil { - return err - } - if err := exeIOS(tmpDir, target, appDir, bi); err != nil { - return err - } - if err := signIOS(bi, tmpDir, appDir); err != nil { - return err - } - return zipDir(out, tmpDir, "Payload") - default: - panic("unreachable") - } -} - -func signIOS(bi *buildInfo, tmpDir, app string) error { - home, err := os.UserHomeDir() - if err != nil { - return err - } - provPattern := filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision") - provisions, err := filepath.Glob(provPattern) - if err != nil { - return err - } - provInfo := filepath.Join(tmpDir, "provision.plist") - var avail []string - for _, prov := range provisions { - // Decode the provision file to a plist. - _, err := runCmd(exec.Command("security", "cms", "-D", "-i", prov, "-o", provInfo)) - if err != nil { - return err - } - expUnix, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:ExpirationDate", provInfo)) - if err != nil { - return err - } - exp, err := time.Parse(time.UnixDate, expUnix) - if err != nil { - return fmt.Errorf("sign: failed to parse expiration date from %q: %v", prov, err) - } - if exp.Before(time.Now()) { - continue - } - appIDPrefix, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:ApplicationIdentifierPrefix:0", provInfo)) - if err != nil { - return err - } - provAppID, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:Entitlements:application-identifier", provInfo)) - if err != nil { - return err - } - expAppID := fmt.Sprintf("%s.%s", appIDPrefix, bi.appID) - avail = append(avail, provAppID) - if expAppID != provAppID { - continue - } - // Copy provisioning file. - embedded := filepath.Join(app, "embedded.mobileprovision") - if err := copyFile(embedded, prov); err != nil { - return err - } - certDER, err := runCmdRaw(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:DeveloperCertificates:0", provInfo)) - if err != nil { - return err - } - // Omit trailing newline. - certDER = certDER[:len(certDER)-1] - entitlements, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-x", "-c", "Print:Entitlements", provInfo)) - if err != nil { - return err - } - entFile := filepath.Join(tmpDir, "entitlements.plist") - if err := os.WriteFile(entFile, []byte(entitlements), 0660); err != nil { - return err - } - identity := sha1.Sum(certDER) - idHex := hex.EncodeToString(identity[:]) - _, err = runCmd(exec.Command("codesign", "-s", idHex, "-v", "--entitlements", entFile, app)) - return err - } - return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q among %v", bi.appID, avail) -} - -func exeIOS(tmpDir, target, app string, bi *buildInfo) error { - if bi.appID == "" { - return errors.New("app id is empty; use -appid to set it") - } - if err := os.RemoveAll(app); err != nil { - return err - } - if err := os.Mkdir(app, 0755); err != nil { - return err - } - appName := UppercaseName(bi.name) - exe := filepath.Join(app, appName) - lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create") - var builds errgroup.Group - for _, a := range bi.archs { - clang, cflags, err := iosCompilerFor(target, a, bi.minsdk) - if err != nil { - return err - } - cflags = append(cflags, - "-fobjc-arc", - ) - cflagsLine := strings.Join(cflags, " ") - exeSlice := filepath.Join(tmpDir, "app-"+a) - lipo.Args = append(lipo.Args, exeSlice) - compile := exec.Command( - "go", - "build", - "-ldflags=-s -w "+bi.ldflags, - "-o", exeSlice, - "-tags", bi.tags, - bi.pkgPath, - ) - compile.Env = append( - os.Environ(), - "GOOS=ios", - "GOARCH="+a, - "CGO_ENABLED=1", - "CC="+clang, - "CGO_CFLAGS="+cflagsLine, - "CGO_LDFLAGS=-lresolv "+cflagsLine, - ) - builds.Go(func() error { - _, err := runCmd(compile) - return err - }) - } - if err := builds.Wait(); err != nil { - return err - } - if _, err := runCmd(lipo); err != nil { - return err - } - infoPlist := buildInfoPlist(bi) - plistFile := filepath.Join(app, "Info.plist") - if err := os.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil { - return err - } - if _, err := os.Stat(bi.iconPath); err == nil { - assetPlist, err := iosIcons(bi, tmpDir, app, bi.iconPath) - if err != nil { - return err - } - // Merge assets plist with Info.plist - cmd := exec.Command( - "/usr/libexec/PlistBuddy", - "-c", "Merge "+assetPlist, - plistFile, - ) - if _, err := runCmd(cmd); err != nil { - return err - } - } - if _, err := runCmd(exec.Command("plutil", "-convert", "binary1", plistFile)); err != nil { - return err - } - return nil -} - -// iosIcons builds an asset catalog and compile it with the Xcode command actool. -// iosIcons returns the asset plist file to be merged into Info.plist. -func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) { - assets := filepath.Join(tmpDir, "Assets.xcassets") - if err := os.Mkdir(assets, 0700); err != nil { - return "", err - } - appIcon := filepath.Join(assets, "AppIcon.appiconset") - err := buildIcons(appIcon, icon, []iconVariant{ - {path: "ios_2x.png", size: 120}, - {path: "ios_3x.png", size: 180}, - // The App Store icon is not allowed to contain - // transparent pixels. - {path: "ios_store.png", size: 1024, fill: true}, - }) - if err != nil { - return "", err - } - contentJson := `{ - "images" : [ - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "ios_2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "ios_3x.png", - "scale" : "3x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "ios_store.png", - "scale" : "1x" - } - ] -}` - contentFile := filepath.Join(appIcon, "Contents.json") - if err := os.WriteFile(contentFile, []byte(contentJson), 0600); err != nil { - return "", err - } - assetPlist := filepath.Join(tmpDir, "assets.plist") - - minsdk := bi.minsdk - if minsdk == 0 { - minsdk = minIOSVersion - } - compile := exec.Command( - "actool", - "--compile", appDir, - "--platform", iosPlatformFor(bi.target), - "--minimum-deployment-target", strconv.Itoa(minsdk), - "--app-icon", "AppIcon", - "--output-partial-info-plist", assetPlist, - assets) - _, err = runCmd(compile) - return assetPlist, err -} - -func buildInfoPlist(bi *buildInfo) string { - appName := UppercaseName(bi.name) - platform := iosPlatformFor(bi.target) - var supportPlatform string - switch bi.target { - case "ios": - supportPlatform = "iPhoneOS" - case "tvos": - supportPlatform = "AppleTVOS" - } - return fmt.Sprintf(` - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - %s - CFBundleIdentifier - %s - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - %s - CFBundlePackageType - APPL - CFBundleShortVersionString - %s - CFBundleVersion - %d - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - arm64 - DTPlatformName - %s - DTPlatformVersion - 12.4 - MinimumOSVersion - %d - UIDeviceFamily - - 1 - 2 - - CFBundleSupportedPlatforms - - %s - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 16G73 - DTSDKBuild - 16G73 - DTSDKName - %s12.4 - DTXcode - 1030 - DTXcodeBuild - 10G8 - -`, appName, bi.appID, appName, bi.version, bi.version.VersionCode, platform, minIOSVersion, supportPlatform, platform) -} - -func iosPlatformFor(target string) string { - switch target { - case "ios": - return "iphoneos" - case "tvos": - return "appletvos" - default: - panic("invalid platform " + target) - } -} - -func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error { - framework := filepath.Base(frameworkRoot) - const suf = ".framework" - if !strings.HasSuffix(framework, suf) { - return fmt.Errorf("the specified output %q does not end in '.framework'", frameworkRoot) - } - framework = framework[:len(framework)-len(suf)] - if err := os.RemoveAll(frameworkRoot); err != nil { - return err - } - frameworkDir := filepath.Join(frameworkRoot, "Versions", "A") - for _, dir := range []string{"Headers", "Modules"} { - p := filepath.Join(frameworkDir, dir) - if err := os.MkdirAll(p, 0755); err != nil { - return err - } - } - symlinks := [][2]string{ - {"Versions/Current/Headers", "Headers"}, - {"Versions/Current/Modules", "Modules"}, - {"Versions/Current/" + framework, framework}, - {"A", filepath.Join("Versions", "Current")}, - } - for _, l := range symlinks { - if err := os.Symlink(l[0], filepath.Join(frameworkRoot, l[1])); err != nil && !os.IsExist(err) { - return err - } - } - exe := filepath.Join(frameworkDir, framework) - lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create") - var builds errgroup.Group - tags := bi.tags - for _, a := range bi.archs { - clang, cflags, err := iosCompilerFor(target, a, bi.minsdk) - if err != nil { - return err - } - lib := filepath.Join(tmpDir, "gio-"+a) - cmd := exec.Command( - "go", - "build", - "-ldflags=-s -w "+bi.ldflags, - "-buildmode=c-archive", - "-o", lib, - "-tags", tags, - bi.pkgPath, - ) - lipo.Args = append(lipo.Args, lib) - cflagsLine := strings.Join(cflags, " ") - cmd.Env = append( - os.Environ(), - "GOOS=ios", - "GOARCH="+a, - "CGO_ENABLED=1", - "CC="+clang, - "CGO_CFLAGS="+cflagsLine, - "CGO_LDFLAGS="+cflagsLine, - ) - builds.Go(func() error { - _, err := runCmd(cmd) - return err - }) - } - if err := builds.Wait(); err != nil { - return err - } - if _, err := runCmd(lipo); err != nil { - return err - } - appDir, err := runCmd(exec.Command("go", "list", "-tags", tags, "-f", "{{.Dir}}", "gioui.org/app/")) - if err != nil { - return err - } - headerDst := filepath.Join(frameworkDir, "Headers", framework+".h") - headerSrc := filepath.Join(appDir, "framework_ios.h") - if err := copyFile(headerDst, headerSrc); err != nil { - return err - } - module := fmt.Sprintf(`framework module "%s" { - header "%[1]s.h" - - export * -}`, framework) - moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap") - return os.WriteFile(moduleFile, []byte(module), 0644) -} - -func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) { - var ( - platformSDK string - platformOS string - ) - switch target { - case "ios": - platformOS = "ios" - platformSDK = "iphone" - case "tvos": - platformOS = "tvos" - platformSDK = "appletv" - } - switch arch { - case "arm", "arm64": - platformSDK += "os" - if minsdk == 0 { - minsdk = minIOSVersion - if target == "tvos" { - minsdk = minTVOSVersion - } - } - case "386", "amd64": - platformOS += "-simulator" - platformSDK += "simulator" - if minsdk == 0 { - minsdk = minSimulatorVersion - } - default: - return "", nil, fmt.Errorf("unsupported -arch: %s", arch) - } - sdkPath, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--show-sdk-path")) - if err != nil { - return "", nil, err - } - clang, err := runCmd(exec.Command("xcrun", "--sdk", platformSDK, "--find", "clang")) - if err != nil { - return "", nil, err - } - cflags := []string{ - "-fembed-bitcode", - "-arch", allArchs[arch].iosArch, - "-isysroot", sdkPath, - "-m" + platformOS + "-version-min=" + strconv.Itoa(minsdk), - } - return clang, cflags, nil -} - -func zipDir(dst, base, dir string) (err error) { - f, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - if cerr := f.Close(); err == nil { - err = cerr - } - }() - zipf := zip.NewWriter(f) - err = filepath.Walk(filepath.Join(base, dir), func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - if f.IsDir() { - return nil - } - rel := filepath.ToSlash(path[len(base)+1:]) - entry, err := zipf.Create(rel) - if err != nil { - return err - } - src, err := os.Open(path) - if err != nil { - return err - } - defer src.Close() - _, err = io.Copy(entry, src) - return err - }) - if err != nil { - return err - } - return zipf.Close() -} diff --git a/third_party/gioui-cmd/gogio/js_test.go b/third_party/gioui-cmd/gogio/js_test.go deleted file mode 100644 index 8584894..0000000 --- a/third_party/gioui-cmd/gogio/js_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main_test - -import ( - "bytes" - "context" - "errors" - "image" - "image/png" - "io" - "net/http" - "net/http/httptest" - "os/exec" - - "github.com/chromedp/cdproto/runtime" - "github.com/chromedp/chromedp" - - _ "gioui.org/unit" // the build tool adds it to go.mod, so keep it there -) - -type JSTestDriver struct { - driverBase - - // ctx is the chromedp context. - ctx context.Context -} - -func (d *JSTestDriver) Start(path string) { - if raceEnabled { - d.Skipf("js/wasm doesn't support -race; skipping") - } - - // First, build the app. - dir := d.tempDir("gio-endtoend-js") - d.gogio("-target=js", "-o="+dir, path) - - // Second, start Chrome. - opts := append(chromedp.DefaultExecAllocatorOptions[:], - chromedp.Flag("headless", *headless), - ) - - actx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) - d.Cleanup(cancel) - - ctx, cancel := chromedp.NewContext(actx, - // Send all logf/errf calls to t.Logf - chromedp.WithLogf(d.Logf), - ) - d.Cleanup(cancel) - d.ctx = ctx - - if err := chromedp.Run(ctx); err != nil { - if errors.Is(err, exec.ErrNotFound) { - d.Skipf("test requires Chrome to be installed: %v", err) - return - } - d.Fatal(err) - } - pr, pw := io.Pipe() - d.Cleanup(func() { pw.Close() }) - d.output = pr - chromedp.ListenTarget(ctx, func(ev interface{}) { - switch ev := ev.(type) { - case *runtime.EventConsoleAPICalled: - switch ev.Type { - case "log", "info", "warning", "error": - var b bytes.Buffer - b.WriteString("console.") - b.WriteString(string(ev.Type)) - b.WriteString("(") - for i, arg := range ev.Args { - if i > 0 { - b.WriteString(", ") - } - b.Write(arg.Value) - } - b.WriteString(")\n") - pw.Write(b.Bytes()) - } - } - }) - - // Third, serve the app folder, set the browser tab dimensions, and - // navigate to the folder. - ts := httptest.NewServer(http.FileServer(http.Dir(dir))) - d.Cleanup(ts.Close) - - if err := chromedp.Run(ctx, - chromedp.EmulateViewport(int64(d.width), int64(d.height)), - chromedp.Navigate(ts.URL), - ); err != nil { - d.Fatal(err) - } - - // Wait for the gio app to render. - d.waitForFrame() -} - -func (d *JSTestDriver) Screenshot() image.Image { - var buf []byte - if err := chromedp.Run(d.ctx, - chromedp.CaptureScreenshot(&buf), - ); err != nil { - d.Fatal(err) - } - img, err := png.Decode(bytes.NewReader(buf)) - if err != nil { - d.Fatal(err) - } - return img -} - -func (d *JSTestDriver) Click(x, y int) { - if err := chromedp.Run(d.ctx, - chromedp.MouseClickXY(float64(x), float64(y)), - ); err != nil { - d.Fatal(err) - } - - // Wait for the gio app to render after this click. - d.waitForFrame() -} diff --git a/third_party/gioui-cmd/gogio/jsbuild.go b/third_party/gioui-cmd/gogio/jsbuild.go deleted file mode 100644 index b99f048..0000000 --- a/third_party/gioui-cmd/gogio/jsbuild.go +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main - -import ( - "bytes" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "text/template" - - "golang.org/x/tools/go/packages" -) - -func buildJS(bi *buildInfo) error { - out := *destPath - if out == "" { - out = bi.name - } - if err := os.MkdirAll(out, 0700); err != nil { - return err - } - cmd := exec.Command( - "go", - "build", - "-ldflags="+bi.ldflags, - "-tags="+bi.tags, - "-o", filepath.Join(out, "main.wasm"), - bi.pkgPath, - ) - cmd.Env = append( - os.Environ(), - "GOOS=js", - "GOARCH=wasm", - ) - _, err := runCmd(cmd) - if err != nil { - return err - } - - var faviconPath string - if _, err := os.Stat(bi.iconPath); err == nil { - // Copy icon to the output folder - icon, err := os.ReadFile(bi.iconPath) - if err != nil { - return err - } - if err := os.WriteFile(filepath.Join(out, filepath.Base(bi.iconPath)), icon, 0600); err != nil { - return err - } - faviconPath = filepath.Base(bi.iconPath) - } - - indexTemplate, err := template.New("").Parse(jsIndex) - if err != nil { - return err - } - - var b bytes.Buffer - if err := indexTemplate.Execute(&b, struct { - Name string - Icon string - }{ - Name: bi.name, - Icon: faviconPath, - }); err != nil { - return err - } - - if err := os.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), 0600); err != nil { - return err - } - - goroot, err := runCmd(exec.Command("go", "env", "GOROOT")) - if err != nil { - return err - } - wasmJS := filepath.Join(goroot, "misc", "wasm", "wasm_exec.js") - if _, err := os.Stat(wasmJS); err != nil { - return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err) - } - pkgs, err := packages.Load(&packages.Config{ - Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps, - Env: append(os.Environ(), "GOOS=js", "GOARCH=wasm"), - }, bi.pkgPath) - if err != nil { - return err - } - extraJS, err := findPackagesJS(pkgs[0], make(map[string]bool)) - if err != nil { - return err - } - - return mergeJSFiles(filepath.Join(out, "wasm.js"), append([]string{wasmJS}, extraJS...)...) -} - -func findPackagesJS(p *packages.Package, visited map[string]bool) (extraJS []string, err error) { - if len(p.GoFiles) == 0 { - return nil, nil - } - js, err := filepath.Glob(filepath.Join(filepath.Dir(p.GoFiles[0]), "*_js.js")) - if err != nil { - return nil, err - } - extraJS = append(extraJS, js...) - for _, imp := range p.Imports { - if !visited[imp.ID] { - extra, err := findPackagesJS(imp, visited) - if err != nil { - return nil, err - } - extraJS = append(extraJS, extra...) - visited[imp.ID] = true - } - } - return extraJS, nil -} - -// mergeJSFiles will merge all files into a single `wasm.js`. It will prepend the jsSetGo -// and append the jsStartGo. -func mergeJSFiles(dst string, files ...string) (err error) { - w, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - if cerr := w.Close(); err != nil { - err = cerr - } - }() - _, err = io.Copy(w, strings.NewReader(jsSetGo)) - if err != nil { - return err - } - for i := range files { - r, err := os.Open(files[i]) - if err != nil { - return err - } - _, err = io.Copy(w, r) - r.Close() - if err != nil { - return err - } - } - _, err = io.Copy(w, strings.NewReader(jsStartGo)) - return err -} - -const ( - jsIndex = ` - - - - - - {{ if .Icon }}{{ end }} - {{ if .Name }}{{.Name}}{{ end }} - - - - - -` - // jsSetGo sets the `window.go` variable. - jsSetGo = `(() => { - window.go = {argv: [], env: {}, importObject: {go: {}}}; - const argv = new URLSearchParams(location.search).get("argv"); - if (argv) { - window.go["argv"] = argv.split(" "); - } -})();` - // jsStartGo initializes the main.wasm. - jsStartGo = `(() => { - defaultGo = new Go(); - Object.assign(defaultGo["argv"], defaultGo["argv"].concat(go["argv"])); - Object.assign(defaultGo["env"], go["env"]); - for (let key in go["importObject"]) { - if (typeof defaultGo["importObject"][key] === "undefined") { - defaultGo["importObject"][key] = {}; - } - Object.assign(defaultGo["importObject"][key], go["importObject"][key]); - } - window.go = defaultGo; - if (!WebAssembly.instantiateStreaming) { // polyfill - WebAssembly.instantiateStreaming = async (resp, importObject) => { - const source = await (await resp).arrayBuffer(); - return await WebAssembly.instantiate(source, importObject); - }; - } - WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => { - go.run(result.instance); - }); -})();` -) diff --git a/third_party/gioui-cmd/gogio/macosbuild.go b/third_party/gioui-cmd/gogio/macosbuild.go deleted file mode 100644 index 88e9463..0000000 --- a/third_party/gioui-cmd/gogio/macosbuild.go +++ /dev/null @@ -1,262 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "text/template" -) - -func buildMac(tmpDir string, bi *buildInfo) error { - builder := &macBuilder{TempDir: tmpDir} - builder.DestDir = *destPath - if builder.DestDir == "" { - builder.DestDir = bi.pkgPath - } - - name := bi.name - if *destPath != "" { - if filepath.Ext(*destPath) != ".app" { - return fmt.Errorf("invalid output name %q, it must end with `.app`", *destPath) - } - name = filepath.Base(*destPath) - } - name = strings.TrimSuffix(name, ".app") - - if bi.appID == "" { - return errors.New("app id is empty; use -appid to set it") - } - - if err := builder.setIcon(bi.iconPath); err != nil { - return err - } - - if err := builder.setInfo(bi, name); err != nil { - return fmt.Errorf("can't build the resources: %v", err) - } - - for _, arch := range bi.archs { - tmpDest := filepath.Join(builder.TempDir, filepath.Base(builder.DestDir)) - finalDest := builder.DestDir - if len(bi.archs) > 1 { - tmpDest = filepath.Join(builder.TempDir, name+"_"+arch+".app") - finalDest = filepath.Join(builder.DestDir, name+"_"+arch+".app") - } - - if err := builder.buildProgram(bi, tmpDest, name, arch); err != nil { - return err - } - - if bi.key != "" { - if err := builder.signProgram(bi, tmpDest, name, arch); err != nil { - return err - } - } - - if err := dittozip(tmpDest, tmpDest+".zip"); err != nil { - return err - } - - if bi.notaryAppleID != "" { - if err := builder.notarize(bi, tmpDest+".zip"); err != nil { - return err - } - } - - if err := dittounzip(tmpDest+".zip", finalDest); err != nil { - return err - } - } - - return nil -} - -type macBuilder struct { - TempDir string - DestDir string - - Icons []byte - Manifest []byte - Entitlements []byte -} - -func (b *macBuilder) setIcon(path string) (err error) { - if _, err := os.Stat(path); err != nil { - return nil - } - - out := filepath.Join(b.TempDir, "iconset.iconset") - if err := os.MkdirAll(out, 0777); err != nil { - return err - } - - err = buildIcons(out, path, []iconVariant{ - {path: "icon_512x512@2x.png", size: 1024}, - {path: "icon_512x512.png", size: 512}, - {path: "icon_256x256@2x.png", size: 512}, - {path: "icon_256x256.png", size: 256}, - {path: "icon_128x128@2x.png", size: 256}, - {path: "icon_128x128.png", size: 128}, - {path: "icon_64x64@2x.png", size: 128}, - {path: "icon_64x64.png", size: 64}, - {path: "icon_32x32@2x.png", size: 64}, - {path: "icon_32x32.png", size: 32}, - {path: "icon_16x16@2x.png", size: 32}, - {path: "icon_16x16.png", size: 16}, - }) - - if err != nil { - return err - } - - cmd := exec.Command("iconutil", - "-c", "icns", out, - "-o", filepath.Join(b.TempDir, "icon.icns")) - if _, err := runCmd(cmd); err != nil { - return err - } - - b.Icons, err = os.ReadFile(filepath.Join(b.TempDir, "icon.icns")) - return err -} - -func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error { - t, err := template.New("manifest").Parse(` - - - - CFBundleExecutable - {{.Name}} - CFBundleIconFile - icon.icns - CFBundleIdentifier - {{.Bundle}} - NSHighResolutionCapable - - CFBundlePackageType - APPL - -`) - if err != nil { - return err - } - - var manifest bufferCoff - if err := t.Execute(&manifest, struct { - Name, Bundle string - }{ - Name: name, - Bundle: buildInfo.appID, - }); err != nil { - return err - } - b.Manifest = manifest.Bytes() - - b.Entitlements = []byte(` - - - -com.apple.security.cs.allow-unsigned-executable-memory - -com.apple.security.cs.allow-jit - - -`) - - return nil -} - -func (b *macBuilder) buildProgram(buildInfo *buildInfo, binDest string, name string, arch string) error { - for _, path := range []string{"/Contents/MacOS", "/Contents/Resources"} { - if err := os.MkdirAll(filepath.Join(binDest, path), 0755); err != nil { - return err - } - } - - if len(b.Icons) > 0 { - if err := os.WriteFile(filepath.Join(binDest, "/Contents/Resources/icon.icns"), b.Icons, 0755); err != nil { - return err - } - } - - if err := os.WriteFile(filepath.Join(binDest, "/Contents/Info.plist"), b.Manifest, 0755); err != nil { - return err - } - - cmd := exec.Command( - "go", - "build", - "-ldflags="+buildInfo.ldflags, - "-tags="+buildInfo.tags, - "-o", filepath.Join(binDest, "/Contents/MacOS/"+name), - buildInfo.pkgPath, - ) - cmd.Env = append( - os.Environ(), - "GOOS=darwin", - "GOARCH="+arch, - "CGO_ENABLED=1", // Required to cross-compile between AMD/ARM - ) - _, err := runCmd(cmd) - return err -} - -func (b *macBuilder) signProgram(buildInfo *buildInfo, binDest string, name string, arch string) error { - options := filepath.Join(b.TempDir, "ent.ent") - if err := os.WriteFile(options, b.Entitlements, 0777); err != nil { - return err - } - - xattr := exec.Command("xattr", "-rc", binDest) - if _, err := runCmd(xattr); err != nil { - return err - } - - cmd := exec.Command( - "codesign", - "--deep", - "--force", - "--options", "runtime", - "--entitlements", options, - "--sign", buildInfo.key, - binDest, - ) - _, err := runCmd(cmd) - return err -} - -func (b *macBuilder) notarize(buildInfo *buildInfo, binDest string) error { - cmd := exec.Command( - "xcrun", - "notarytool", - "submit", - binDest, - "--apple-id", buildInfo.notaryAppleID, - "--team-id", buildInfo.notaryTeamID, - "--wait", - ) - - if buildInfo.notaryPassword != "" { - cmd.Args = append(cmd.Args, "--password", buildInfo.notaryPassword) - } - - _, err := runCmd(cmd) - return err -} - -func dittozip(input, output string) error { - cmd := exec.Command("ditto", "-c", "-k", "-X", "--rsrc", input, output) - - _, err := runCmd(cmd) - return err -} - -func dittounzip(input, output string) error { - cmd := exec.Command("ditto", "-x", "-k", "-X", "--rsrc", input, output) - - _, err := runCmd(cmd) - return err -} diff --git a/third_party/gioui-cmd/gogio/main.go b/third_party/gioui-cmd/gogio/main.go deleted file mode 100644 index 9917918..0000000 --- a/third_party/gioui-cmd/gogio/main.go +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main - -import ( - "bytes" - "errors" - "flag" - "fmt" - "image" - "image/color" - "image/png" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - - "golang.org/x/image/draw" - "golang.org/x/sync/errgroup" -) - -var ( - target = flag.String("target", "", "specify target (ios, tvos, android, js).\n") - archNames = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).") - minsdk = flag.Int("minsdk", 0, "specify the minimum supported operating system level") - targetsdk = flag.Int("targetsdk", 0, "specify the target supported operating system level for Android") - buildMode = flag.String("buildmode", "exe", "specify buildmode (archive, exe)") - destPath = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.") - appID = flag.String("appid", "", "app identifier (for -buildmode=exe)") - name = flag.String("name", "", "app name (for -buildmode=exe)") - version = flag.String("version", "1.0.0.1", "semver app version (for -buildmode=exe) on the form major.minor.patch.versioncode") - printCommands = flag.Bool("x", false, "print the commands") - keepWorkdir = flag.Bool("work", false, "print the name of the temporary work directory and do not delete it when exiting.") - linkMode = flag.String("linkmode", "", "set the -linkmode flag of the go tool") - extraLdflags = flag.String("ldflags", "", "extra flags to the Go linker") - extraTags = flag.String("tags", "", "extra tags to the Go tool") - iconPath = flag.String("icon", "", "specify an icon for iOS and Android") - signKey = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files.") - signPass = flag.String("signpass", "", "specify the password to decrypt the signkey.") - notaryID = flag.String("notaryid", "", "specify the apple id to use for notarization.") - notaryPass = flag.String("notarypass", "", "specify app-specific password of the Apple ID to be used for notarization.") - notaryTeamID = flag.String("notaryteamid", "", "specify the team id to use for notarization.") -) - -func main() { - flag.Usage = func() { - fmt.Fprint(os.Stderr, mainUsage) - } - flag.Parse() - if err := flagValidate(); err != nil { - fmt.Fprintf(os.Stderr, "gogio: %v\n", err) - os.Exit(1) - } - buildInfo, err := newBuildInfo(flag.Arg(0)) - if err != nil { - fmt.Fprintf(os.Stderr, "gogio: %v\n", err) - os.Exit(1) - } - if err := build(buildInfo); err != nil { - fmt.Fprintf(os.Stderr, "gogio: %v\n", err) - os.Exit(1) - } - os.Exit(0) -} - -func flagValidate() error { - pkgPathArg := flag.Arg(0) - if pkgPathArg == "" { - return errors.New("specify a package") - } - if *target == "" { - return errors.New("please specify -target") - } - switch *target { - case "ios", "tvos", "android", "js", "windows", "macos": - default: - return fmt.Errorf("invalid -target %s", *target) - } - switch *buildMode { - case "archive", "exe": - default: - return fmt.Errorf("invalid -buildmode %s", *buildMode) - } - return nil -} - -func build(bi *buildInfo) error { - tmpDir, err := os.MkdirTemp("", "gogio-") - if err != nil { - return err - } - if *keepWorkdir { - fmt.Fprintf(os.Stderr, "WORKDIR=%s\n", tmpDir) - } else { - defer os.RemoveAll(tmpDir) - } - switch *target { - case "js": - return buildJS(bi) - case "ios", "tvos": - return buildIOS(tmpDir, *target, bi) - case "android": - return buildAndroid(tmpDir, bi) - case "windows": - return buildWindows(tmpDir, bi) - case "macos": - return buildMac(tmpDir, bi) - default: - panic("unreachable") - } -} - -func runCmdRaw(cmd *exec.Cmd) ([]byte, error) { - if *printCommands { - fmt.Printf("%s\n", strings.Join(cmd.Args, " ")) - } - out, err := cmd.Output() - if err == nil { - return out, nil - } - if err, ok := err.(*exec.ExitError); ok { - return nil, fmt.Errorf("%s failed: %s%s", strings.Join(cmd.Args, " "), out, err.Stderr) - } - return nil, err -} - -func runCmd(cmd *exec.Cmd) (string, error) { - out, err := runCmdRaw(cmd) - return string(bytes.TrimSpace(out)), err -} - -func copyFile(dst, src string) (err error) { - r, err := os.Open(src) - if err != nil { - return err - } - defer r.Close() - w, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - if cerr := w.Close(); err == nil { - err = cerr - } - }() - _, err = io.Copy(w, r) - return err -} - -type arch struct { - iosArch string - jniArch string - clangArch string -} - -var allArchs = map[string]arch{ - "arm": { - iosArch: "armv7", - jniArch: "armeabi-v7a", - clangArch: "armv7a-linux-androideabi", - }, - "arm64": { - iosArch: "arm64", - jniArch: "arm64-v8a", - clangArch: "aarch64-linux-android", - }, - "386": { - iosArch: "i386", - jniArch: "x86", - clangArch: "i686-linux-android", - }, - "amd64": { - iosArch: "x86_64", - jniArch: "x86_64", - clangArch: "x86_64-linux-android", - }, -} - -type iconVariant struct { - path string - size int - fill bool -} - -func buildIcons(baseDir, icon string, variants []iconVariant) error { - f, err := os.Open(icon) - if err != nil { - return err - } - defer f.Close() - img, _, err := image.Decode(f) - if err != nil { - return err - } - var resizes errgroup.Group - for _, v := range variants { - v := v - resizes.Go(func() (err error) { - path := filepath.Join(baseDir, v.path) - if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return err - } - f, err := os.Create(path) - if err != nil { - return err - } - defer func() { - if cerr := f.Close(); err == nil { - err = cerr - } - }() - return png.Encode(f, resizeIcon(v, img)) - }) - } - return resizes.Wait() -} - -func resizeIcon(v iconVariant, img image.Image) *image.NRGBA { - scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}}) - op := draw.Src - if v.fill { - op = draw.Over - draw.Draw(scaled, scaled.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src) - } - draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), op, nil) - - return scaled -} diff --git a/third_party/gioui-cmd/gogio/main_test.go b/third_party/gioui-cmd/gogio/main_test.go deleted file mode 100644 index 98dcb27..0000000 --- a/third_party/gioui-cmd/gogio/main_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "os" - "testing" -) - -func TestMain(m *testing.M) { - if os.Getenv("RUN_GOGIO") != "" { - // Allow the end-to-end tests to call the gogio tool without - // having to build it from scratch, nor having to refactor the - // main function to avoid using global variables. - main() - os.Exit(0) // main already exits, but just in case. - } - os.Exit(m.Run()) -} diff --git a/third_party/gioui-cmd/gogio/permission.go b/third_party/gioui-cmd/gogio/permission.go deleted file mode 100644 index c3227ac..0000000 --- a/third_party/gioui-cmd/gogio/permission.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -var AndroidPermissions = map[string][]string{ - "network": { - "android.permission.INTERNET", - }, - "networkstate": { - "android.permission.ACCESS_NETWORK_STATE", - }, - "bluetooth": { - "android.permission.BLUETOOTH", - "android.permission.BLUETOOTH_ADMIN", - "android.permission.ACCESS_FINE_LOCATION", - }, - "camera": { - "android.permission.CAMERA", - }, - "storage": { - "android.permission.READ_EXTERNAL_STORAGE", - "android.permission.WRITE_EXTERNAL_STORAGE", - }, - "wakelock": { - "android.permission.WAKE_LOCK", - }, -} - -var AndroidFeatures = map[string][]string{ - "default": {`glEsVersion="0x00020000"`, `name="android.hardware.type.pc"`}, - "bluetooth": { - `name="android.hardware.bluetooth"`, - `name="android.hardware.bluetooth_le"`, - }, - "camera": { - `name="android.hardware.camera"`, - }, -} diff --git a/third_party/gioui-cmd/gogio/race_test.go b/third_party/gioui-cmd/gogio/race_test.go deleted file mode 100644 index 1f3c689..0000000 --- a/third_party/gioui-cmd/gogio/race_test.go +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -//go:build race -// +build race - -package main_test - -func init() { raceEnabled = true } diff --git a/third_party/gioui-cmd/gogio/wayland_test.go b/third_party/gioui-cmd/gogio/wayland_test.go deleted file mode 100644 index df10410..0000000 --- a/third_party/gioui-cmd/gogio/wayland_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main_test - -import ( - "bufio" - "bytes" - "context" - "fmt" - "image" - "image/png" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "sync" - "text/template" - "time" -) - -type WaylandTestDriver struct { - driverBase - - runtimeDir string - socket string - display string -} - -// No bars or anything fancy. Just a white background with our dimensions. -var tmplSwayConfig = template.Must(template.New("").Parse(` -output * bg #FFFFFF solid_color -output * mode {{.Width}}x{{.Height}} -default_border none -`)) - -var rxSwayReady = regexp.MustCompile(`Running compositor on wayland display '(.*)'`) - -func (d *WaylandTestDriver) Start(path string) { - // We want os.Environ, so that it can e.g. find $DISPLAY to run within - // X11. wlroots env vars are documented at: - // https://github.com/swaywm/wlroots/blob/master/docs/env_vars.md - env := os.Environ() - if *headless { - env = append(env, "WLR_BACKENDS=headless") - } - - d.needPrograms( - "sway", // to run a wayland compositor - "grim", // to take screenshots - "swaymsg", // to send input - ) - - // First, build the app. - dir := d.tempDir("gio-endtoend-wayland") - bin := filepath.Join(dir, "red") - flags := []string{"build", "-tags", "nox11", "-o=" + bin} - if raceEnabled { - flags = append(flags, "-race") - } - flags = append(flags, path) - cmd := exec.Command("go", flags...) - if out, err := cmd.CombinedOutput(); err != nil { - d.Fatalf("could not build app: %s:\n%s", err, out) - } - - conf := filepath.Join(dir, "config") - f, err := os.Create(conf) - if err != nil { - d.Fatal(err) - } - defer f.Close() - if err := tmplSwayConfig.Execute(f, struct{ Width, Height int }{ - d.width, d.height, - }); err != nil { - d.Fatal(err) - } - - d.socket = filepath.Join(dir, "socket") - env = append(env, "SWAYSOCK="+d.socket) - d.runtimeDir = dir - env = append(env, "XDG_RUNTIME_DIR="+d.runtimeDir) - - var wg sync.WaitGroup - d.Cleanup(wg.Wait) - - // First, start sway. - { - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, "sway", "--config", conf, "--verbose") - cmd.Env = env - stderr, err := cmd.StderrPipe() - if err != nil { - d.Fatal(err) - } - if err := cmd.Start(); err != nil { - d.Fatal(err) - } - d.Cleanup(cancel) - d.Cleanup(func() { - // Give it a chance to exit gracefully, cleaning up - // after itself. After 10ms, the deferred cancel above - // will signal an os.Kill. - cmd.Process.Signal(os.Interrupt) - time.Sleep(10 * time.Millisecond) - }) - - // Wait for sway to be ready. We probably don't need a deadline - // here. - br := bufio.NewReader(stderr) - for { - line, err := br.ReadString('\n') - if err != nil { - d.Fatal(err) - } - if m := rxSwayReady.FindStringSubmatch(line); m != nil { - d.display = m[1] - break - } - } - - wg.Add(1) - go func() { - if err := cmd.Wait(); err != nil && ctx.Err() == nil && !strings.Contains(err.Error(), "interrupt") { - // Don't print all stderr, since we use --verbose. - // TODO(mvdan): if it's useful, probably filter - // errors and show them. - d.Error(err) - } - wg.Done() - }() - } - - // Then, start our program on the sway compositor above. - { - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, bin) - cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display} - output, err := cmd.StdoutPipe() - if err != nil { - d.Fatal(err) - } - cmd.Stderr = cmd.Stdout - d.output = output - if err := cmd.Start(); err != nil { - d.Fatal(err) - } - d.Cleanup(cancel) - wg.Add(1) - go func() { - if err := cmd.Wait(); err != nil && ctx.Err() == nil { - d.Error(err) - } - wg.Done() - }() - } - - // Wait for the gio app to render. - d.waitForFrame() -} - -func (d *WaylandTestDriver) Screenshot() image.Image { - cmd := exec.Command("grim", "/dev/stdout") - cmd.Env = []string{"XDG_RUNTIME_DIR=" + d.runtimeDir, "WAYLAND_DISPLAY=" + d.display} - out, err := cmd.CombinedOutput() - if err != nil { - d.Errorf("%s", out) - d.Fatal(err) - } - img, err := png.Decode(bytes.NewReader(out)) - if err != nil { - d.Fatal(err) - } - return img -} - -func (d *WaylandTestDriver) swaymsg(args ...interface{}) { - strs := []string{"--socket", d.socket} - for _, arg := range args { - strs = append(strs, fmt.Sprint(arg)) - } - cmd := exec.Command("swaymsg", strs...) - if out, err := cmd.CombinedOutput(); err != nil { - d.Errorf("%s", out) - d.Fatal(err) - } -} - -func (d *WaylandTestDriver) Click(x, y int) { - d.swaymsg("seat", "-", "cursor", "set", x, y) - d.swaymsg("seat", "-", "cursor", "press", "button1") - d.swaymsg("seat", "-", "cursor", "release", "button1") - - // Wait for the gio app to render after this click. - d.waitForFrame() -} diff --git a/third_party/gioui-cmd/gogio/windows_test.go b/third_party/gioui-cmd/gogio/windows_test.go deleted file mode 100644 index 996b511..0000000 --- a/third_party/gioui-cmd/gogio/windows_test.go +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main_test - -import ( - "context" - "image" - "io" - "os" - "os/exec" - "path/filepath" - "runtime" - "sync" - "time" - - "golang.org/x/image/draw" -) - -// Wine is tightly coupled with X11 at the moment, and we can reuse the same -// methods to automate screenshots and clicks. The main difference is how we -// build and run the app. - -// The only quirk is that it seems impossible for the Wine window to take the -// entirety of the X server's dimensions, even if we try to resize it to take -// the entire display. It seems to want to leave some vertical space empty, -// presumably for window decorations or the "start" bar on Windows. To work -// around that, make the X server 50x50px bigger, and crop the screenshots back -// to the original size. - -type WineTestDriver struct { - X11TestDriver -} - -func (d *WineTestDriver) Start(path string) { - d.needPrograms("wine") - - // First, build the app. - bin := filepath.Join(d.tempDir("gio-endtoend-windows"), "red.exe") - flags := []string{"build", "-o=" + bin} - if raceEnabled { - if runtime.GOOS != "windows" { - // cross-compilation disables CGo, which breaks -race. - d.Skipf("can't cross-compile -race for Windows; skipping") - } - flags = append(flags, "-race") - } - flags = append(flags, path) - cmd := exec.Command("go", flags...) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "GOOS=windows") - if out, err := cmd.CombinedOutput(); err != nil { - d.Fatalf("could not build app: %s:\n%s", err, out) - } - - var wg sync.WaitGroup - d.Cleanup(wg.Wait) - - // Add 50x50px to the display dimensions, as discussed earlier. - d.startServer(&wg, d.width+50, d.height+50) - - // Then, start our program via Wine on the X server above. - { - cacheDir, err := os.UserCacheDir() - if err != nil { - d.Fatal(err) - } - // Use a wine directory separate from the default ~/.wine, so - // that the user's winecfg doesn't affect our test. This will - // default to ~/.cache/gio-e2e-wine. We use the user's cache, - // to reuse a previously set up wineprefix. - wineprefix := filepath.Join(cacheDir, "gio-e2e-wine") - - // First, ensure that wineprefix is up to date with wineboot. - // Wait for this separately from the first frame, as setting up - // a new prefix might take 5s on its own. - env := []string{ - "DISPLAY=" + d.display, - "WINEDEBUG=fixme-all", // hide "fixme" noise - "WINEPREFIX=" + wineprefix, - - // Disable wine-gecko (Explorer) and wine-mono (.NET). - // Otherwise, if not installed, wineboot will get stuck - // with a prompt to install them on the virtual X - // display. Moreover, Gio doesn't need either, and wine - // is faster without them. - "WINEDLLOVERRIDES=mscoree,mshtml=", - } - { - start := time.Now() - cmd := exec.Command("wine", "wineboot", "-i") - cmd.Env = env - // Use a combined output pipe instead of CombinedOutput, - // so that we only wait for the child process to exit, - // and we don't need to wait for all of wine's - // grandchildren to exit and stop writing. This is - // relevant as wine leaves "wineserver" lingering for - // three seconds by default, to be reused later. - stdout, err := cmd.StdoutPipe() - if err != nil { - d.Fatal(err) - } - cmd.Stderr = cmd.Stdout - if err := cmd.Run(); err != nil { - io.Copy(os.Stderr, stdout) - d.Fatal(err) - } - d.Logf("set up WINEPREFIX in %s", time.Since(start)) - } - - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, "wine", bin) - cmd.Env = env - output, err := cmd.StdoutPipe() - if err != nil { - d.Fatal(err) - } - cmd.Stderr = cmd.Stdout - d.output = output - if err := cmd.Start(); err != nil { - d.Fatal(err) - } - d.Cleanup(cancel) - wg.Add(1) - go func() { - if err := cmd.Wait(); err != nil && ctx.Err() == nil { - d.Error(err) - } - wg.Done() - }() - } - // Wait for the gio app to render. - d.waitForFrame() - - // xdotool seems to fail at actually moving the window if we use it - // immediately after Gio is ready. Why? - // We can't tell if the windowmove operation worked until we take a - // screenshot, because the getwindowgeometry op reports the 0x0 - // coordinates even if the window wasn't moved properly. - // A sleep of ~20ms seems to be enough on an idle laptop. Use 20x that. - // TODO(mvdan): revisit this, when you have a spare three hours. - time.Sleep(400 * time.Millisecond) - id := d.xdotool("search", "--sync", "--onlyvisible", "--name", "Gio") - d.xdotool("windowmove", "--sync", id, 0, 0) -} - -func (d *WineTestDriver) Screenshot() image.Image { - img := d.X11TestDriver.Screenshot() - // Crop the screenshot back to the original dimensions. - cropped := image.NewRGBA(image.Rect(0, 0, d.width, d.height)) - draw.Draw(cropped, cropped.Bounds(), img, image.Point{}, draw.Src) - return cropped -} diff --git a/third_party/gioui-cmd/gogio/windowsbuild.go b/third_party/gioui-cmd/gogio/windowsbuild.go deleted file mode 100644 index c867e03..0000000 --- a/third_party/gioui-cmd/gogio/windowsbuild.go +++ /dev/null @@ -1,410 +0,0 @@ -package main - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "image/png" - "io" - "os" - "os/exec" - "path/filepath" - "reflect" - "strings" - "text/template" - - "github.com/akavel/rsrc/binutil" - "github.com/akavel/rsrc/coff" - "golang.org/x/text/encoding/unicode" -) - -func buildWindows(tmpDir string, bi *buildInfo) error { - builder := &windowsBuilder{TempDir: tmpDir} - builder.DestDir = *destPath - if builder.DestDir == "" { - builder.DestDir = bi.pkgPath - } - - name := bi.name - if *destPath != "" { - if filepath.Ext(*destPath) != ".exe" { - return fmt.Errorf("invalid output name %q, it must end with `.exe`", *destPath) - } - name = filepath.Base(*destPath) - } - name = strings.TrimSuffix(name, ".exe") - sdk := bi.minsdk - if sdk > 10 { - return fmt.Errorf("invalid minsdk (%d) it's higher than Windows 10", sdk) - } - - for _, arch := range bi.archs { - builder.Coff = coff.NewRSRC() - builder.Coff.Arch(arch) - - if err := builder.embedIcon(bi.iconPath); err != nil { - return err - } - - if err := builder.embedManifest(windowsManifest{ - Version: bi.version.String(), - WindowsVersion: sdk, - Name: name, - }); err != nil { - return fmt.Errorf("can't create manifest: %v", err) - } - - if err := builder.embedInfo(windowsResources{ - Version: [2]uint32{uint32(bi.version.Major), uint32(bi.version.Minor)<<16 | uint32(bi.version.Patch)}, - VersionHuman: bi.version.String(), - Name: name, - Language: 0x0400, // Process Default Language: https://docs.microsoft.com/en-us/previous-versions/ms957130(v=msdn.10) - }); err != nil { - return fmt.Errorf("can't create info: %v", err) - } - - if err := builder.buildResource(bi, name, arch); err != nil { - return fmt.Errorf("can't build the resources: %v", err) - } - - if err := builder.buildProgram(bi, name, arch); err != nil { - return err - } - } - - return nil -} - -type ( - windowsResources struct { - Version [2]uint32 - VersionHuman string - Language uint16 - Name string - } - windowsManifest struct { - Version string - WindowsVersion int - Name string - } - windowsBuilder struct { - TempDir string - DestDir string - Coff *coff.Coff - } -) - -const ( - // https://docs.microsoft.com/en-us/windows/win32/menurc/resource-types - windowsResourceIcon = 3 - windowsResourceIconGroup = windowsResourceIcon + 11 - windowsResourceManifest = 24 - windowsResourceVersion = 16 -) - -type bufferCoff struct { - bytes.Buffer -} - -func (b *bufferCoff) Size() int64 { - return int64(b.Len()) -} - -func (b *windowsBuilder) embedIcon(path string) (err error) { - iconFile, err := os.Open(path) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil - } - return fmt.Errorf("can't read the icon located at %s: %v", path, err) - } - defer iconFile.Close() - - iconImage, err := png.Decode(iconFile) - if err != nil { - return fmt.Errorf("can't decode the PNG file (%s): %v", path, err) - } - - sizes := []int{16, 32, 48, 64, 128, 256} - var iconHeader bufferCoff - - // GRPICONDIR structure. - if err := binary.Write(&iconHeader, binary.LittleEndian, [3]uint16{0, 1, uint16(len(sizes))}); err != nil { - return err - } - - for _, size := range sizes { - var iconBuffer bufferCoff - - if err := png.Encode(&iconBuffer, resizeIcon(iconVariant{size: size, fill: false}, iconImage)); err != nil { - return fmt.Errorf("can't encode image: %v", err) - } - - b.Coff.AddResource(windowsResourceIcon, uint16(size), &iconBuffer) - - if err := binary.Write(&iconHeader, binary.LittleEndian, struct { - Size [2]uint8 - Color [2]uint8 - Planes uint16 - BitCount uint16 - Length uint32 - Id uint16 - }{ - Size: [2]uint8{uint8(size % 256), uint8(size % 256)}, // "0" means 256px. - Planes: 1, - BitCount: 32, - Length: uint32(iconBuffer.Len()), - Id: uint16(size), - }); err != nil { - return err - } - } - - b.Coff.AddResource(windowsResourceIconGroup, 1, &iconHeader) - - return nil -} - -func (b *windowsBuilder) buildResource(buildInfo *buildInfo, name string, arch string) error { - out, err := os.Create(filepath.Join(buildInfo.pkgPath, name+"_windows_"+arch+".syso")) - if err != nil { - return err - } - defer out.Close() - b.Coff.Freeze() - - // See https://github.com/akavel/rsrc/internal/write.go#L13. - w := binutil.Writer{W: out} - binutil.Walk(b.Coff, func(v reflect.Value, path string) error { - if binutil.Plain(v.Kind()) { - w.WriteLE(v.Interface()) - return nil - } - vv, ok := v.Interface().(binutil.SizedReader) - if ok { - w.WriteFromSized(vv) - return binutil.WALK_SKIP - } - return nil - }) - - if w.Err != nil { - return fmt.Errorf("error writing output file: %s", w.Err) - } - - return nil -} - -func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch string) error { - dest := b.DestDir - if len(buildInfo.archs) > 1 { - dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe") - } - - cmd := exec.Command( - "go", - "build", - "-ldflags=-H=windowsgui "+buildInfo.ldflags, - "-tags="+buildInfo.tags, - "-o", dest, - buildInfo.pkgPath, - ) - cmd.Env = append( - os.Environ(), - "GOOS=windows", - "GOARCH="+arch, - ) - _, err := runCmd(cmd) - return err -} - -func (b *windowsBuilder) embedManifest(v windowsManifest) error { - t, err := template.New("manifest").Parse(` - - - {{.Name}} - - - {{if (le .WindowsVersion 10)}} -{{end}} - {{if (le .WindowsVersion 9)}} -{{end}} - {{if (le .WindowsVersion 8)}} -{{end}} - {{if (le .WindowsVersion 7)}} -{{end}} - {{if (le .WindowsVersion 6)}} -{{end}} - - - - - - - - - - - - true - - -`) - if err != nil { - return err - } - - var manifest bufferCoff - if err := t.Execute(&manifest, v); err != nil { - return err - } - - b.Coff.AddResource(windowsResourceManifest, 1, &manifest) - - return nil -} - -func (b *windowsBuilder) embedInfo(v windowsResources) error { - page := uint16(1) - - // https://docs.microsoft.com/pt-br/windows/win32/menurc/vs-versioninfo - t := newValue(valueBinary, "VS_VERSION_INFO", []io.WriterTo{ - // https://docs.microsoft.com/pt-br/windows/win32/api/VerRsrc/ns-verrsrc-vs_fixedfileinfo - windowsInfoValueFixed{ - Signature: 0xFEEF04BD, - StructVersion: 0x00010000, - FileVersion: v.Version, - ProductVersion: v.Version, - FileFlagMask: 0x3F, - FileFlags: 0, - FileOS: 0x40004, - FileType: 0x1, - FileSubType: 0, - }, - // https://docs.microsoft.com/pt-br/windows/win32/menurc/stringfileinfo - newValue(valueText, "StringFileInfo", []io.WriterTo{ - // https://docs.microsoft.com/pt-br/windows/win32/menurc/stringtable - newValue(valueText, fmt.Sprintf("%04X%04X", v.Language, page), []io.WriterTo{ - // https://docs.microsoft.com/pt-br/windows/win32/menurc/string-str - newValue(valueText, "ProductVersion", v.VersionHuman), - newValue(valueText, "FileVersion", v.VersionHuman), - newValue(valueText, "FileDescription", v.Name), - newValue(valueText, "ProductName", v.Name), - // TODO include more data: gogio must have some way to provide such information (like Company Name, Copyright...) - }), - }), - // https://docs.microsoft.com/pt-br/windows/win32/menurc/varfileinfo - newValue(valueBinary, "VarFileInfo", []io.WriterTo{ - // https://docs.microsoft.com/pt-br/windows/win32/menurc/var-str - newValue(valueBinary, "Translation", uint32(page)<<16|uint32(v.Language)), - }), - }) - - // For some reason the ValueLength of the VS_VERSIONINFO must be the byte-length of `windowsInfoValueFixed`: - t.ValueLength = 52 - - var verrsrc bufferCoff - if _, err := t.WriteTo(&verrsrc); err != nil { - return err - } - - b.Coff.AddResource(windowsResourceVersion, 1, &verrsrc) - - return nil -} - -type windowsInfoValueFixed struct { - Signature uint32 - StructVersion uint32 - FileVersion [2]uint32 - ProductVersion [2]uint32 - FileFlagMask uint32 - FileFlags uint32 - FileOS uint32 - FileType uint32 - FileSubType uint32 - FileDate [2]uint32 -} - -func (v windowsInfoValueFixed) WriteTo(w io.Writer) (_ int64, err error) { - return 0, binary.Write(w, binary.LittleEndian, v) -} - -type windowsInfoValue struct { - Length uint16 - ValueLength uint16 - Type uint16 - Key []byte - Value []byte -} - -func (v windowsInfoValue) WriteTo(w io.Writer) (_ int64, err error) { - // binary.Write doesn't support []byte inside struct. - if err = binary.Write(w, binary.LittleEndian, [3]uint16{v.Length, v.ValueLength, v.Type}); err != nil { - return 0, err - } - if _, err = w.Write(v.Key); err != nil { - return 0, err - } - if _, err = w.Write(v.Value); err != nil { - return 0, err - } - return 0, nil -} - -const ( - valueBinary uint16 = 0 - valueText uint16 = 1 -) - -func newValue(valueType uint16, key string, input interface{}) windowsInfoValue { - v := windowsInfoValue{ - Type: valueType, - Length: 6, - } - - padding := func(in []byte) []byte { - if l := uint16(len(in)) + v.Length; l%4 != 0 { - return append(in, make([]byte, 4-l%4)...) - } - return in - } - - v.Key = padding(utf16Encode(key)) - v.Length += uint16(len(v.Key)) - - switch in := input.(type) { - case string: - v.Value = padding(utf16Encode(in)) - v.ValueLength = uint16(len(v.Value) / 2) - case []io.WriterTo: - var buff bytes.Buffer - for k := range in { - if _, err := in[k].WriteTo(&buff); err != nil { - panic(err) - } - } - v.Value = buff.Bytes() - default: - var buff bytes.Buffer - if err := binary.Write(&buff, binary.LittleEndian, in); err != nil { - panic(err) - } - v.ValueLength = uint16(buff.Len()) - v.Value = buff.Bytes() - } - - v.Length += uint16(len(v.Value)) - - return v -} - -// utf16Encode encodes the string to UTF16 with null-termination. -func utf16Encode(s string) []byte { - b, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes([]byte(s)) - if err != nil { - panic(err) - } - return append(b, 0x00, 0x00) // null-termination. -} diff --git a/third_party/gioui-cmd/gogio/x11_test.go b/third_party/gioui-cmd/gogio/x11_test.go deleted file mode 100644 index 9bb3174..0000000 --- a/third_party/gioui-cmd/gogio/x11_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package main_test - -import ( - "bytes" - "context" - "fmt" - "image" - "image/png" - "io" - "math/rand" - "os" - "os/exec" - "path/filepath" - "sync" - "time" -) - -type X11TestDriver struct { - driverBase - - display string -} - -func (d *X11TestDriver) Start(path string) { - // First, build the app. - bin := filepath.Join(d.tempDir("gio-endtoend-x11"), "red") - flags := []string{"build", "-tags", "nowayland", "-o=" + bin} - if raceEnabled { - flags = append(flags, "-race") - } - flags = append(flags, path) - cmd := exec.Command("go", flags...) - if out, err := cmd.CombinedOutput(); err != nil { - d.Fatalf("could not build app: %s:\n%s", err, out) - } - - var wg sync.WaitGroup - d.Cleanup(wg.Wait) - - d.startServer(&wg, d.width, d.height) - - // Then, start our program on the X server above. - { - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, bin) - cmd.Env = []string{"DISPLAY=" + d.display} - output, err := cmd.StdoutPipe() - if err != nil { - d.Fatal(err) - } - cmd.Stderr = cmd.Stdout - d.output = output - if err := cmd.Start(); err != nil { - d.Fatal(err) - } - d.Cleanup(cancel) - wg.Add(1) - go func() { - if err := cmd.Wait(); err != nil && ctx.Err() == nil { - d.Error(err) - } - wg.Done() - }() - } - - // Wait for the gio app to render. - d.waitForFrame() -} - -func (d *X11TestDriver) startServer(wg *sync.WaitGroup, width, height int) { - // Pick a random display number between 1 and 100,000. Most machines - // will only be using :0, so there's only a 0.001% chance of two - // concurrent test runs to run into a conflict. - rnd := rand.New(rand.NewSource(time.Now().UnixNano())) - d.display = fmt.Sprintf(":%d", rnd.Intn(100000)+1) - - var xprog string - xflags := []string{ - "-wr", // we want a white background; the default is black - } - if *headless { - xprog = "Xvfb" // virtual X server - xflags = append(xflags, "-screen", "0", fmt.Sprintf("%dx%dx24", width, height)) - } else { - xprog = "Xephyr" // nested X server as a window - xflags = append(xflags, "-screen", fmt.Sprintf("%dx%d", width, height)) - } - xflags = append(xflags, d.display) - - d.needPrograms( - xprog, // to run the X server - "scrot", // to take screenshots - "xdotool", // to send input - ) - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, xprog, xflags...) - combined := &bytes.Buffer{} - cmd.Stdout = combined - cmd.Stderr = combined - if err := cmd.Start(); err != nil { - d.Fatal(err) - } - d.Cleanup(cancel) - d.Cleanup(func() { - // Give it a chance to exit gracefully, cleaning up - // after itself. After 10ms, the deferred cancel above - // will signal an os.Kill. - cmd.Process.Signal(os.Interrupt) - time.Sleep(10 * time.Millisecond) - }) - - // Wait for the X server to be ready. The socket path isn't - // terribly portable, but that's okay for now. - withRetries(d.T, time.Second, func() error { - socket := fmt.Sprintf("/tmp/.X11-unix/X%s", d.display[1:]) - _, err := os.Stat(socket) - return err - }) - - wg.Add(1) - go func() { - if err := cmd.Wait(); err != nil && ctx.Err() == nil { - // Print all output and error. - io.Copy(os.Stdout, combined) - d.Error(err) - } - wg.Done() - }() -} - -func (d *X11TestDriver) Screenshot() image.Image { - cmd := exec.Command("scrot", "--silent", "--overwrite", "/dev/stdout") - cmd.Env = []string{"DISPLAY=" + d.display} - out, err := cmd.CombinedOutput() - if err != nil { - d.Errorf("%s", out) - d.Fatal(err) - } - img, err := png.Decode(bytes.NewReader(out)) - if err != nil { - d.Fatal(err) - } - return img -} - -func (d *X11TestDriver) xdotool(args ...interface{}) string { - d.Helper() - strs := make([]string, len(args)) - for i, arg := range args { - strs[i] = fmt.Sprint(arg) - } - cmd := exec.Command("xdotool", strs...) - cmd.Env = []string{"DISPLAY=" + d.display} - out, err := cmd.CombinedOutput() - if err != nil { - d.Errorf("%s", out) - d.Fatal(err) - } - return string(bytes.TrimSpace(out)) -} - -func (d *X11TestDriver) Click(x, y int) { - d.xdotool("mousemove", "--sync", x, y) - d.xdotool("click", "1") - - // Wait for the gio app to render after this click. - d.waitForFrame() -} diff --git a/third_party/gioui-cmd/svg2gio/main.go b/third_party/gioui-cmd/svg2gio/main.go deleted file mode 100644 index 81a0227..0000000 --- a/third_party/gioui-cmd/svg2gio/main.go +++ /dev/null @@ -1,582 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -// Command svg2gio converts SVG files to Gio functions. Only a limited subset of -// SVG files are supported. -package main - -import ( - "bytes" - "encoding/xml" - "errors" - "flag" - "fmt" - "io" - "os" - "path/filepath" - "strconv" - "strings" - "unicode" - - "go/format" - - "gioui.org/f32" -) - -var ( - pkg = flag.String("pkg", "", "Go package") - output = flag.String("o", "svg.go", "Output Go file") -) - -func main() { - flag.Parse() - if *pkg == "" { - fmt.Fprintf(os.Stderr, "specify a package name (-pkg)\n") - os.Exit(1) - } - args := flag.Args() - if err := convertAll(args); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(2) - } -} - -type Points []float32 - -func (p *Points) UnmarshalText(text []byte) error { - for { - text = bytes.TrimLeft(text, "\t\n") - if len(text) == 0 { - break - } - var num []byte - end := bytes.IndexAny(text, " ,") - if end != -1 { - num = text[:end] - text = text[end+1:] - } else { - num = text - text = nil - } - f, err := strconv.ParseFloat(string(num), 32) - if err != nil { - return err - } - *p = append(*p, float32(f)) - } - return nil -} - -type Transform f32.Affine2D - -func (t *Transform) UnmarshalText(text []byte) error { - switch { - case bytes.HasPrefix(text, []byte("matrix(")) && bytes.HasSuffix(text, []byte(")")): - trans := text[7 : len(text)-1] - var p Points - if err := p.UnmarshalText(trans); err != nil { - return err - } - if len(p) != 6 { - return fmt.Errorf("malformed transform matrix: %q", text) - } - *t = Transform(f32.NewAffine2D(p[0], p[2], p[4], p[1], p[3], p[5])) - return nil - default: - return fmt.Errorf("unsupported transform: %q", text) - } -} - -type Fill struct { - Transform Transform `xml:"transform,attr"` - Fill Color `xml:"fill,attr"` - Stroke Color `xml:"stroke,attr"` - StrokeLinejoin string `xml:"stroke-linejoin,attr"` - StrokeLinecap string `xml:"stroke-linecap,attr"` - StrokeWidth float32 `xml:"stroke-width,attr"` -} - -type Color struct { - Set bool - Value int -} - -func (c *Color) UnmarshalText(text []byte) error { - if string(text) == "none" { - *c = Color{} - return nil - } - if !bytes.HasPrefix(text, []byte("#")) { - return fmt.Errorf("invalid color: %q", text) - } - text = text[1:] - i, err := strconv.ParseInt(string(text), 16, 32) - // Implied alpha. - if len(text) == 6 { - i |= 0xff000000 - } - *c = Color{ - Set: true, - Value: int(i), - } - return err -} - -func convertAll(files []string) error { - w := new(bytes.Buffer) - fmt.Fprintf(w, "// Code generated by gioui.org/cmd/svg2gio; DO NOT EDIT.\n\n") - fmt.Fprintf(w, "package %s\n\n", *pkg) - fmt.Fprintf(w, "import \"image/color\"\n") - fmt.Fprintf(w, "import \"math\"\n") - fmt.Fprintf(w, "import \"gioui.org/op\"\n") - fmt.Fprintf(w, "import \"gioui.org/op/clip\"\n") - fmt.Fprintf(w, "import \"gioui.org/op/paint\"\n") - fmt.Fprintf(w, "import \"gioui.org/f32\"\n\n") - fmt.Fprintf(w, "var ops op.Ops\n\n") - fmt.Fprintf(w, funcs) - for _, filename := range files { - if err := convert(w, filename); err != nil { - return err - } - } - src, err := format.Source(w.Bytes()) - if err != nil { - return err - } - return os.WriteFile(*output, src, 0o660) -} - -func convert(w io.Writer, filename string) error { - base := filepath.Base(filename) - ext := filepath.Ext(base) - name := "Image_" + base[:len(base)-len(ext)] - - fmt.Fprintf(w, "var %s struct {\n", name) - fmt.Fprintf(w, "ViewBox struct { Min, Max f32.Point }\n") - fmt.Fprintf(w, "Call op.CallOp\n\n") - fmt.Fprintf(w, "}\n") - fmt.Fprintf(w, "func init() {\n") - defer fmt.Fprintf(w, "}\n") - f, err := os.Open(filename) - if err != nil { - return err - } - defer f.Close() - d := xml.NewDecoder(f) - if err := parse(w, d, name); err != nil { - line, col := d.InputPos() - return fmt.Errorf("%s:%d:%d: %w", filename, line, col, err) - } - return nil -} - -func parse(w io.Writer, d *xml.Decoder, name string) error { - for { - tok, err := d.Token() - if err != nil { - if err == io.EOF { - return errors.New("unexpected end of file") - } - return err - } - switch tok := tok.(type) { - case xml.StartElement: - if n := tok.Name.Local; n != "svg" { - return fmt.Errorf("invalid SVG root: <%s>", n) - } - if n := tok.Name.Space; n != "http://www.w3.org/2000/svg" { - return fmt.Errorf("unsupported SVG namespace: %s", n) - } - fmt.Fprintf(w, "m := op.Record(&ops)\n") - defer fmt.Fprintf(w, "%s.Call = m.Stop()\n", name) - for _, a := range tok.Attr { - if a.Name.Local == "viewBox" { - var p Points - if err := p.UnmarshalText([]byte(a.Value)); err != nil { - return fmt.Errorf("invalid viewBox attribute: %s", a.Value) - } - if len(p) != 4 { - return fmt.Errorf("invalid viewBox attribute: %s", a.Value) - } - fmt.Fprintf(w, "%s.ViewBox.Min = %s\n", name, point(f32.Pt(p[0], p[1]))) - fmt.Fprintf(w, "%s.ViewBox.Max = %s\n", name, point(f32.Pt(p[2], p[3]))) - } - } - return parseSVG(w, d) - } - } -} - -func point(p f32.Point) string { - return fmt.Sprintf("f32.Pt(%g, %g)", p.X, p.Y) -} - -type Poly struct { - XMLName xml.Name - Points Points `xml:"points,attr"` - Fill -} - -func (p *Poly) Path(w io.Writer) error { - if len(p.Points) <= 1 { - return nil - } - pen := f32.Pt(p.Points[0], p.Points[1]) - fmt.Fprintf(w, "p.MoveTo(%s)\n", point(pen)) - last := pen - for i := 2; i < len(p.Points); i += 2 { - last = f32.Pt(p.Points[i], p.Points[i+1]) - fmt.Fprintf(w, "p.LineTo(%s)\n", point(last)) - } - if p.XMLName.Local == "polygon" && last != pen { - fmt.Fprintf(w, "p.LineTo(%s)\n", point(pen)) - } - return nil -} - -type Path struct { - D string `xml:"d,attr"` - Fill -} - -func (p *Path) Path(w io.Writer) error { - return printPathCommands(w, p.D) -} - -type Line struct { - X1 float32 `xml:"x1,attr"` - Y1 float32 `xml:"y1,attr"` - X2 float32 `xml:"x2,attr"` - Y2 float32 `xml:"y2,attr"` - Fill -} - -func (l *Line) Path(w io.Writer) error { - fmt.Fprintf(w, "p.MoveTo(%s)\n", point(f32.Pt(l.X1, l.Y1))) - fmt.Fprintf(w, "p.LineTo(%s)\n", point(f32.Pt(l.X2, l.Y2))) - return nil -} - -type Ellipse struct { - Cx float32 `xml:"cx,attr"` - Cy float32 `xml:"cy,attr"` - Rx float32 `xml:"rx,attr"` - Ry float32 `xml:"ry,attr"` - Fill -} - -func (e *Ellipse) Path(w io.Writer) error { - c := f32.Pt(e.Cx, e.Cy) - r := f32.Pt(e.Rx, e.Ry) - fmt.Fprintf(w, "ellipse(&p, %s, %s)\n", point(c), point(r)) - return nil -} - -type Rect struct { - X float32 `xml:"x,attr"` - Y float32 `xml:"y,attr"` - Width float32 `xml:"width,attr"` - Height float32 `xml:"height,attr"` - Fill -} - -func (r *Rect) Path(w io.Writer) error { - o := f32.Pt(r.X, r.Y) - sz := f32.Pt(r.Width, r.Height) - fmt.Fprintf(w, "rect(&p, %s, %s)\n", point(o), point(sz)) - return nil -} - -type Circle struct { - Cx float32 `xml:"cx,attr"` - Cy float32 `xml:"cy,attr"` - R float32 `xml:"r,attr"` - Fill -} - -func (c *Circle) Path(w io.Writer) error { - center := f32.Pt(c.Cx, c.Cy) - r := f32.Pt(c.R, c.R) - fmt.Fprintf(w, "ellipse(&p, %s, %s)\n", point(center), point(r)) - return nil -} - -func parseSVG(w io.Writer, d *xml.Decoder) error { - for { - tok, err := d.Token() - if err != nil { - if err == io.EOF { - return errors.New("unexpected end of element") - } - return err - } - var start xml.StartElement - switch tok := tok.(type) { - case xml.EndElement: - return nil - case xml.StartElement: - start = tok - default: - continue - } - var elem interface { - Path(w io.Writer) error - } - var fill *Fill - switch n := start.Name.Local; n { - case "g": - // Flatten groups. - if err := parseSVG(w, d); err != nil { - return err - } - continue - case "title": - d.Skip() - continue - case "polygon", "polyline": - p := new(Poly) - elem = p - fill = &p.Fill - case "path": - p := new(Path) - elem = p - fill = &p.Fill - case "line": - l := new(Line) - elem = l - fill = &l.Fill - case "ellipse": - e := new(Ellipse) - elem = e - fill = &e.Fill - case "rect": - r := new(Rect) - elem = r - fill = &r.Fill - case "circle": - c := new(Circle) - elem = c - fill = &c.Fill - default: - return fmt.Errorf("unsupported tag: <%s>", n) - } - if err := d.DecodeElement(elem, &start); err != nil { - return err - } - if !fill.Fill.Set && !fill.Stroke.Set { - continue - } - fmt.Fprintf(w, "{\n") - trans := f32.Affine2D(fill.Transform) - if trans != (f32.Affine2D{}) { - sx, hx, ox, sy, hy, oy := trans.Elems() - fmt.Fprintf(w, "t := op.Affine(f32.NewAffine2D(%g, %g, %g, %g, %g, %g)).Push(&ops)\n", sx, hx, ox, sy, hy, oy) - } - fmt.Fprintf(w, "var p clip.Path\n") - fmt.Fprintf(w, "p.Begin(&ops)\n") - if err := elem.Path(w); err != nil { - return err - } - fmt.Fprintf(w, "spec := p.End()\n") - if fill.Fill.Set { - fmt.Fprintf(w, "paint.FillShape(&ops, argb(%#.8x), clip.Outline{Path: spec}.Op())\n", fill.Fill.Value) - } - if fill.Stroke.Set { - fmt.Fprintf(w, "paint.FillShape(&ops, argb(%#.8x), clip.Stroke{Width: %g, Path: spec}.Op())\n", fill.Stroke.Value, fill.StrokeWidth) - } - if trans != (f32.Affine2D{}) { - fmt.Fprintf(w, "t.Pop()\n") - } - fmt.Fprintf(w, "}\n") - } -} - -func printPathCommands(w io.Writer, cmds string) error { - moveTo := func(p f32.Point) { - fmt.Fprintf(w, "p.MoveTo(%s)\n", point(p)) - } - lineTo := func(p f32.Point) { - fmt.Fprintf(w, "p.LineTo(%s)\n", point(p)) - } - cubeTo := func(p0, p1, p2 f32.Point) { - fmt.Fprintf(w, "p.CubeTo(%s, %s, %s)\n", point(p0), point(p1), point(p2)) - } - cmds = strings.TrimSpace(cmds) - var pen f32.Point - initPoint := pen - ctrl2 := pen - for { - cmds = strings.TrimLeft(cmds, " ,\t\n") - if len(cmds) == 0 { - break - } - orig := cmds - op := rune(cmds[0]) - cmds = cmds[1:] - switch op { - case 'M', 'm', 'V', 'v', 'L', 'l', 'H', 'h', 'C', 'c', 'S', 's': - case 'Z', 'z': - if pen != initPoint { - lineTo(initPoint) - pen = initPoint - } - ctrl2 = initPoint - continue - default: - return fmt.Errorf("unknown command %s in %q", string(op), orig) - } - var coords []float64 - for { - cmds = strings.TrimLeft(cmds, " ,\t\n") - if len(cmds) == 0 { - break - } - n, x, ok := parseFloat(cmds) - if !ok { - break - } - cmds = cmds[n:] - coords = append(coords, x) - } - rel := unicode.IsLower(op) - newPen := pen - switch unicode.ToLower(op) { - case 'h': - for _, x := range coords { - p := f32.Pt(float32(x), pen.Y) - if rel { - p.X += pen.X - } - lineTo(p) - newPen = p - } - pen = newPen - ctrl2 = newPen - continue - case 'v': - for _, y := range coords { - p := f32.Pt(pen.X, float32(y)) - if rel { - p.Y += pen.Y - } - lineTo(p) - newPen = p - } - pen = newPen - ctrl2 = newPen - continue - } - if len(coords)%2 != 0 { - return fmt.Errorf("odd number of coordinates in data: %q", orig) - } - var off f32.Point - if rel { - // Relative command. - off = pen - } else { - off = f32.Pt(0, 0) - } - var points []f32.Point - for i := 0; i < len(coords); i += 2 { - p := f32.Pt(float32(coords[i]), float32(coords[i+1])) - p = p.Add(off) - points = append(points, p) - } - newCtrl2 := ctrl2 - switch op := unicode.ToLower(op); op { - case 'm', 'l': - sop := moveTo - if op == 'l' { - sop = lineTo - } - for _, p := range points { - sop(p) - newPen = p - } - if op == 'm' { - initPoint = newPen - } - case 'c': - for i := 0; i < len(points); i += 3 { - p1, p2, p3 := points[i], points[i+1], points[i+2] - cubeTo(p1, p2, p3) - newPen = p3 - newCtrl2 = p2 - } - case 's': - for i := 0; i < len(points); i += 2 { - p2, p3 := points[i], points[i+1] - // Compute p1 by reflecting p2 on to the line that contains pen and p2. - p1 := pen.Mul(2).Sub(ctrl2) - cubeTo(p1, p2, p3) - newPen = p3 - newCtrl2 = p2 - } - } - pen = newPen - ctrl2 = newCtrl2 - } - return nil -} - -func parseFloat(s string) (int, float64, bool) { - n := 0 - if len(s) > 0 && s[0] == '-' { - n++ - } - for ; n < len(s); n++ { - if !(unicode.IsDigit(rune(s[n])) || s[n] == '.') { - break - } - } - f, err := strconv.ParseFloat(s[:n], 64) - return n, f, err == nil -} - -const funcs = ` -func argb(c uint32) color.NRGBA { - return color.NRGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)} -} - -func rect(p *clip.Path, origin, size f32.Point) { - p.MoveTo(origin) - p.LineTo(origin.Add(f32.Pt(size.X, 0))) - p.LineTo(origin.Add(size)) - p.LineTo(origin.Add(f32.Pt(0, size.Y))) - p.Close() -} - -func ellipse(p *clip.Path, center, radius f32.Point) { - r := radius.X - // We'll model the ellipse as a circle scaled in the Y - // direction. - scale := radius.Y / r - - // https://pomax.github.io/bezierinfo/#circles_cubic. - const q = 4 * (math.Sqrt2 - 1) / 3 - - curve := r * q - top := f32.Point{X: center.X, Y: center.Y - r*scale} - - p.MoveTo(top) - p.CubeTo( - f32.Point{X: center.X + curve, Y: center.Y - r*scale}, - f32.Point{X: center.X + r, Y: center.Y - curve*scale}, - f32.Point{X: center.X + r, Y: center.Y}, - ) - p.CubeTo( - f32.Point{X: center.X + r, Y: center.Y + curve*scale}, - f32.Point{X: center.X + curve, Y: center.Y + r*scale}, - f32.Point{X: center.X, Y: center.Y + r*scale}, - ) - p.CubeTo( - f32.Point{X: center.X - curve, Y: center.Y + r*scale}, - f32.Point{X: center.X - r, Y: center.Y + curve*scale}, - f32.Point{X: center.X - r, Y: center.Y}, - ) - p.CubeTo( - f32.Point{X: center.X - r, Y: center.Y - curve*scale}, - f32.Point{X: center.X - curve, Y: center.Y - r*scale}, - top, - ) -} -`