From 0733c50964f1ea34379ffea22283c9bddfca7de8 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:14:37 +0200 Subject: [PATCH 01/44] Fix automatic playback resume for PiP videos when closed Update version to 2.1.0 in manifest and project files Expose update controller in AppDelegate for onboarding Enable automatic update checks in Info.plist Implement onboarding pages for user guidance Enhance main view with update settings and controls Refactor update controller to manage beta updates Handle onboarding completion and settings in ViewController Create build and release documentation Establish security policy for vulnerability reporting Update appcast for new versioning scheme Add Renovate configuration for dependency management Set version to 2.1.0 in semver file --- .github/ISSUE_TEMPLATE/bug_report.yml | 50 +++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 23 ++ .github/pull_request_template.md | 23 ++ .github/workflows/build-release.yml | 377 +++++++++++++++++++++ AutoPiP Extension/Resources/content.js | 54 +++ AutoPiP Extension/Resources/manifest.json | 2 +- AutoPiP.xcodeproj/project.pbxproj | 16 +- AutoPiP/AppDelegate.swift | 2 +- AutoPiP/Info.plist | 2 + AutoPiP/Resources/Base.lproj/Main.html | 116 ++++++- AutoPiP/Resources/Script.js | 122 ++++++- AutoPiP/Resources/Style.css | 252 +++++++++++++- AutoPiP/UpdateController.swift | 30 +- AutoPiP/ViewController.swift | 101 +++++- BUILD.md | 50 +++ README.md | 20 +- SECURITY.md | 18 + appcast.xml | 18 +- renovate.json | 4 + semver.txt | 1 + 21 files changed, 1219 insertions(+), 63 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build-release.yml create mode 100644 BUILD.md create mode 100644 SECURITY.md create mode 100644 renovate.json create mode 100644 semver.txt diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..b81221d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,50 @@ +name: "πŸ› Bug Report" +description: Report a bug or unexpected behavior +labels: [ "bug" ] +body: +- type: textarea + id: description + attributes: + label: Description + description: What happened? What did you expect to happen? + validations: + required: true +- type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: How can we reproduce the issue? + placeholder: | + 1. Open YouTube in Safari + 2. Play a video + 3. Switch to another tab + 4. ... + validations: + required: true +- type: input + id: macos-version + attributes: + label: macOS Version + placeholder: "e.g. 15.4" + validations: + required: true +- type: input + id: safari-version + attributes: + label: Safari Version + placeholder: "e.g. 18.3" + validations: + required: true +- type: input + id: app-version + attributes: + label: AutoPiP Version + description: Shown in the app menu under "About AutoPiP" + placeholder: "e.g. 2.0.0" + validations: + required: true +- type: textarea + id: additional + attributes: + label: Additional Context + description: Screenshots, screen recordings, or anything else that might help. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6d3dc84 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..fc7ef62 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,23 @@ +name: "✨ Feature Request" +description: Suggest an idea or improvement +labels: [ "enhancement" ] +body: +- type: textarea + id: description + attributes: + label: Description + description: What would you like to see added or changed? + validations: + required: true +- type: textarea + id: use-case + attributes: + label: Use Case + description: Why is this feature useful? What problem does it solve? + validations: + required: true +- type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Have you considered any workarounds or alternative approaches? diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..657d74c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,23 @@ +## Description + + + +## Type of Change + +- [ ] πŸ› Bug fix +- [ ] ✨ New feature +- [ ] πŸ”§ Refactoring +- [ ] πŸ“ Documentation +- [ ] πŸ—οΈ CI/CD +- [ ] πŸ”’ Security + +## Checklist + +- [ ] I have tested my changes locally +- [ ] I have updated `semver.txt` (if this change affects the released version) +- [ ] My changes do not introduce new warnings or errors +- [ ] I have updated documentation where necessary + +## Related Issues + + \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..26c43fd --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,377 @@ +name: Build and Release + +on: + push: + branches: [main, 'feature/*'] + paths: + [semver.txt] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + build-and-release: + runs-on: macos-15 + + steps: + - name: Generate GitHub App token + id: app-token + if: vars.APP_ID != '' + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token || github.token }} + + - name: Read version from semver.txt + id: version + run: | + VERSION=$(tr -d '[:space:]' < semver.txt) + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid semver in semver.txt: '$VERSION'" + exit 1 + fi + + if [ "${{ github.ref_name }}" = "main" ]; then + TAG="v${VERSION}" + PRERELEASE=false + CHANNEL=stable + else + TAG="v${VERSION}-beta" + PRERELEASE=true + CHANNEL=beta + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + echo "### $CHANNEL release β€” $TAG" >> "$GITHUB_STEP_SUMMARY" + + - name: Check for existing tag + id: check_tag + run: | + TAG="${{ steps.version.outputs.tag }}" + CHANNEL="${{ steps.version.outputs.channel }}" + + if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then + if [ "$CHANNEL" = "stable" ]; then + echo "::error::Stable release ${TAG} already exists β€” bump the version in semver.txt to create a new release." + exit 1 + fi + echo "::warning::Beta tag ${TAG} already exists β€” release will be overwritten." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + # ── Signing ──────────────────────────────────────────────── + - name: Import signing certificate + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + run: | + if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then + echo "::warning::No signing certificate configured β€” building with ad-hoc signing." + echo "SIGNING_AVAILABLE=false" >> "$GITHUB_ENV" + exit 0 + fi + + CERTIFICATE_PATH="$RUNNER_TEMP/build_certificate.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" + KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" + + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o "$CERTIFICATE_PATH" + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" + + echo "SIGNING_AVAILABLE=true" >> "$GITHUB_ENV" + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" + + # ── Build ────────────────────────────────────────────────── + - name: Set version in Xcode project + run: | + VERSION="${{ steps.version.outputs.version }}" + BUILD=$(( ${{ github.run_number }} + 10 )) + + sed -i '' "s/MARKETING_VERSION = .*;/MARKETING_VERSION = ${VERSION};/" \ + AutoPiP.xcodeproj/project.pbxproj + sed -i '' "s/CURRENT_PROJECT_VERSION = .*;/CURRENT_PROJECT_VERSION = ${BUILD};/" \ + AutoPiP.xcodeproj/project.pbxproj + + sed -i '' "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" \ + "AutoPiP Extension/Resources/manifest.json" + + echo "MARKETING_VERSION=$VERSION CURRENT_PROJECT_VERSION=$BUILD" + + - name: Resolve Swift packages + run: | + xcodebuild -resolvePackageDependencies \ + -project AutoPiP.xcodeproj \ + -scheme AutoPiP + + - name: Archive + run: | + ARCHIVE_PATH="$RUNNER_TEMP/AutoPiP.xcarchive" + + if [ "$SIGNING_AVAILABLE" = "true" ]; then + xcodebuild archive \ + -project AutoPiP.xcodeproj \ + -scheme AutoPiP \ + -configuration Release \ + -archivePath "$ARCHIVE_PATH" + else + xcodebuild archive \ + -project AutoPiP.xcodeproj \ + -scheme AutoPiP \ + -configuration Release \ + -archivePath "$ARCHIVE_PATH" \ + CODE_SIGN_IDENTITY="-" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + DEVELOPMENT_TEAM="" + fi + + - name: Create DMG + run: | + APP_PATH="$RUNNER_TEMP/AutoPiP.xcarchive/Products/Applications/AutoPiP.app" + + if [ ! -d "$APP_PATH" ]; then + echo "::error::AutoPiP.app not found in archive" + exit 1 + fi + + brew install create-dmg + + create-dmg \ + --volname "AutoPiP" \ + --window-pos 200 120 \ + --window-size 600 400 \ + --icon-size 100 \ + --icon "AutoPiP.app" 175 190 \ + --app-drop-link 425 190 \ + "$RUNNER_TEMP/AutoPiP.dmg" \ + "$APP_PATH" + + echo "AutoPiP.dmg β€” $(du -h "$RUNNER_TEMP/AutoPiP.dmg" | cut -f1)" + + # ── Sparkle ──────────────────────────────────────────────── + - name: Sign DMG and prepare Sparkle appcast + env: + SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} + run: | + VERSION="${{ steps.version.outputs.version }}" + BUILD=$(( ${{ github.run_number }} + 10 )) + TAG="${{ steps.version.outputs.tag }}" + CHANNEL="${{ steps.version.outputs.channel }}" + DMG_PATH="$RUNNER_TEMP/AutoPiP.dmg" + + FILE_LENGTH=$(stat -f%z "$DMG_PATH") + echo "DMG size: $FILE_LENGTH bytes" + + # ── Sparkle EdDSA signature (optional) ── + SIG_ATTR="" + if [ -n "$SPARKLE_PRIVATE_KEY" ]; then + echo "Downloading Sparkle tools…" + curl -fSL \ + "https://github.com/sparkle-project/Sparkle/releases/download/2.9.1/Sparkle-2.9.1.tar.xz" \ + -o "$RUNNER_TEMP/sparkle.tar.xz" + mkdir -p "$RUNNER_TEMP/sparkle" + tar -xJf "$RUNNER_TEMP/sparkle.tar.xz" -C "$RUNNER_TEMP/sparkle" + + SIGN_TOOL="$RUNNER_TEMP/sparkle/bin/sign_update" + if [ ! -x "$SIGN_TOOL" ]; then + echo "::error::sign_update not found or not executable at $SIGN_TOOL" + exit 1 + fi + + echo "Signing DMG with Sparkle EdDSA…" + set +e + SIGN_OUTPUT=$(echo "$SPARKLE_PRIVATE_KEY" | "$SIGN_TOOL" "$DMG_PATH" --ed-key-file -) + SIGN_RC=$? + set -e + + if [ $SIGN_RC -ne 0 ]; then + echo "::warning::sign_update exited with code $SIGN_RC β€” output was:" + echo "$SIGN_OUTPUT" + echo "Continuing without Sparkle signature." + else + SIGNATURE=$(echo "$SIGN_OUTPUT" | grep -oE 'sparkle:edSignature="[^"]+"' | cut -d'"' -f2) + if [ -n "$SIGNATURE" ]; then + SIG_ATTR=$'\n'" sparkle:edSignature=\"${SIGNATURE}\"" + echo "βœ… Sparkle EdDSA signature generated" + else + echo "::warning::sign_update succeeded but no edSignature found in output:" + echo "$SIGN_OUTPUT" + echo "Continuing without Sparkle signature." + fi + fi + else + echo "::warning::SPARKLE_PRIVATE_KEY not set β€” appcast entry will have no signature." + fi + + # ── Channel element (beta only) ── + CHANNEL_EL="" + if [ "$CHANNEL" = "beta" ]; then + CHANNEL_EL=$'\n'" beta" + fi + + DOWNLOAD_URL="https://github.com/vordenken/AutoPiP/releases/download/${TAG}/AutoPiP.dmg" + PUB_DATE=$(date -u '+%a, %d %b %Y %H:%M:%S %z') + + # ── Build block and insert into appcast ── + ITEM_FILE="$RUNNER_TEMP/appcast_item.xml" + cat > "$ITEM_FILE" < + Version ${VERSION}${CHANNEL_EL} + ${PUB_DATE} + + + ITEM_EOF + + # Remove any existing appcast entry for the same version (prevents duplicates on beta re-releases) + awk -v ver="$VERSION" ' + // { buf = $0; inside = 1; next } + inside { + buf = buf ORS $0 + if (/<\/item>/) { + inside = 0 + if (buf !~ "sparkle:shortVersionString=\"" ver "\"") print buf + buf = "" + } + next + } + { print } + ' appcast.xml > "$RUNNER_TEMP/appcast_deduped.xml" + mv "$RUNNER_TEMP/appcast_deduped.xml" appcast.xml + + # Insert the item block after AutoPiP Updates + sed -i '' "/AutoPiP Updates<\/title>/r ${ITEM_FILE}" appcast.xml + echo "appcast.xml updated β€” $CHANNEL $VERSION" + + - name: Upload build artifact + uses: actions/upload-artifact@v7 + with: + name: AutoPiP + path: ${{ runner.temp }}/AutoPiP.dmg + + # ── Release ──────────────────────────────────────────────── + - name: Create tag and GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.tag }} + name: ${{ steps.version.outputs.tag }} + files: ${{ runner.temp }}/AutoPiP.dmg + prerelease: ${{ steps.version.outputs.prerelease == 'true' }} + generate_release_notes: true + token: ${{ steps.app-token.outputs.token || github.token }} + body: | + ## Important Note + + This extension is currently not notarized by Apple. You'll need to bypass macOS security settings to initially install it. Updating works through Sparkle and doesn't need these steps again! + + ### Installation Steps + + 1. Download `AutoPiP.dmg` file from the releases page and open it + 2. When trying to open `AutoPiP.dmg`, macOS will show a security warning + 3. To bypass this: + - Open System Settings + - Go to Privacy & Security + - Scroll down and click "Open Anyway" + 4. Open `AutoPiP.dmg` again + 5. Copy the `.app` to your Applications folder + 6. When trying to open the app, macOS will show a security warning + 7. To bypass this: + - Open System Settings + - Go to Privacy & Security + - Scroll down and click "Open Anyway" + 8. Open `AutoPiP.app` again + 9. Follow the instructions + 10. Allow the requested permissions when prompted + + # ── Push appcast ──────────────────────────────────────────── + - name: Push appcast to working branch and main + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + WORKING_BRANCH="${{ github.ref_name }}" + TAG="${{ steps.version.outputs.tag }}" + + # Save updated appcast + cp appcast.xml "$RUNNER_TEMP/appcast.xml" + git checkout -- . + + push_appcast() { + local branch="$1" + git checkout "$branch" + cp "$RUNNER_TEMP/appcast.xml" appcast.xml + git add appcast.xml + if git diff --cached --quiet; then + echo "appcast.xml unchanged on $branch β€” nothing to push" + return 0 + fi + git commit -m "chore: update appcast for ${TAG}" + + set +e + PUSH_ERR=$(git push origin "$branch" 2>&1) + PUSH_RC=$? + set -e + + if [ $PUSH_RC -eq 0 ]; then + echo "βœ… appcast.xml pushed to $branch" + return 0 + fi + + echo "::warning::Direct push to $branch failed β€” $PUSH_ERR" + echo "Creating pull request instead…" + + PR_BRANCH="chore/appcast-${TAG}-${branch}" + git checkout -B "$PR_BRANCH" + git push -f origin "$PR_BRANCH" + gh pr create \ + --base "$branch" \ + --head "$PR_BRANCH" \ + --title "chore: update appcast for ${TAG}" \ + --body "Automated appcast update from CI. Direct push to \`$branch\` was blocked by branch protection. Please merge to complete the release." \ + || echo "::warning::PR may already exist for this appcast update." + + echo "βœ… Appcast PR created for $branch" + } + + # Always push to the triggering branch first + push_appcast "$WORKING_BRANCH" + + # Also push to main if the triggering branch is not main + if [ "$WORKING_BRANCH" != "main" ]; then + git fetch origin main + push_appcast "main" + fi + + # ── Cleanup ──────────────────────────────────────────────── + - name: Cleanup keychain + if: always() + run: | + if [ -n "$KEYCHAIN_PATH" ] && [ -f "$KEYCHAIN_PATH" ]; then + security delete-keychain "$KEYCHAIN_PATH" || true + fi diff --git a/AutoPiP Extension/Resources/content.js b/AutoPiP Extension/Resources/content.js index aa520e9..eadd0d2 100644 --- a/AutoPiP Extension/Resources/content.js +++ b/AutoPiP Extension/Resources/content.js @@ -447,6 +447,59 @@ new MutationObserver(() => { childList: true }); +// Track which video elements already have PiP event listeners attached +const pipListenerVideos = new WeakSet(); + +/** + * Attach event listeners to a video element to resume playback when the + * PiP window is closed via the native macOS close button. + * @param {HTMLVideoElement} video - Video element to attach listeners to + */ +function attachPiPEventListeners(video) { + if (pipListenerVideos.has(video)) return; + + let userPausedInPiP = false; + + // Track if the user explicitly paused the video while in PiP mode + video.addEventListener('pause', () => { + if (isPiPActive(video)) { + userPausedInPiP = true; + debugLog('User paused video while in PiP mode'); + } + }); + + // Clear the flag when the user resumes playback while still in PiP mode + video.addEventListener('play', () => { + if (isPiPActive(video)) { + userPausedInPiP = false; + debugLog('User resumed video while in PiP mode'); + } + }); + + // When PiP is exited (e.g. user clicks the native close button on the PiP + // window), WebKit pauses the video as a side effect. Resume playback + // automatically unless the user intentionally paused the video first. + // Note: JavaScript's single-threaded event loop ensures pause/play/mode-change + // events are processed sequentially, so userPausedInPiP is always consistent. + video.addEventListener('webkitpresentationmodechanged', () => { + debugLog('Presentation mode changed to:', video.webkitPresentationMode); + + if (video.webkitPresentationMode !== 'picture-in-picture') { + if (video.paused && !userPausedInPiP) { + debugLog('PiP window closed, resuming playback'); + video.play().catch(error => { + console.error('[AutoPiP] Failed to resume playback after PiP close:', error); + }); + } + } + // Reset on both enter and exit so subsequent PiP sessions start clean + userPausedInPiP = false; + }); + + pipListenerVideos.add(video); + debugLog('PiP event listeners attached to video element'); +} + // Video selector caching with invalidation let cachedVideo = null; let cacheTimestamp = 0; @@ -473,6 +526,7 @@ function getVideo() { if (video) { cachedVideo = video; cacheTimestamp = now; + attachPiPEventListeners(video); return video; } } diff --git a/AutoPiP Extension/Resources/manifest.json b/AutoPiP Extension/Resources/manifest.json index 3062195..867dd64 100644 --- a/AutoPiP Extension/Resources/manifest.json +++ b/AutoPiP Extension/Resources/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "AutoPiP", - "version": "2.0.0", + "version": "2.1.0", "default_locale": "en", "description": "Automatically enables Picture-in-Picture mode when switching tabs, windows or scrolling down YouTube videos", "icons": { diff --git a/AutoPiP.xcodeproj/project.pbxproj b/AutoPiP.xcodeproj/project.pbxproj index 177ff77..d9586b2 100644 --- a/AutoPiP.xcodeproj/project.pbxproj +++ b/AutoPiP.xcodeproj/project.pbxproj @@ -428,7 +428,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -462,7 +462,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -634,7 +634,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -681,7 +681,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -706,7 +706,7 @@ DEVELOPMENT_TEAM = NRKZZ9TFF7; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.vd.AutoPiPTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -724,7 +724,7 @@ DEVELOPMENT_TEAM = NRKZZ9TFF7; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.vd.AutoPiPTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -741,7 +741,7 @@ DEVELOPMENT_TEAM = NRKZZ9TFF7; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.vd.AutoPiPUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -758,7 +758,7 @@ DEVELOPMENT_TEAM = NRKZZ9TFF7; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = 2.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.vd.AutoPiPUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; diff --git a/AutoPiP/AppDelegate.swift b/AutoPiP/AppDelegate.swift index 3b8e600..261b139 100644 --- a/AutoPiP/AppDelegate.swift +++ b/AutoPiP/AppDelegate.swift @@ -10,7 +10,7 @@ import Cocoa @main class AppDelegate: NSObject, NSApplicationDelegate { - private let updateController = UpdateController() + let updateController = UpdateController() func applicationDidFinishLaunching(_ notification: Notification) { setupMenu() diff --git a/AutoPiP/Info.plist b/AutoPiP/Info.plist index ce061c6..7b099db 100644 --- a/AutoPiP/Info.plist +++ b/AutoPiP/Info.plist @@ -2,6 +2,8 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> + <key>SUEnableAutomaticChecks</key> + <true/> <key>SUEnableInstallerLauncherService</key> <true/> <key>SUFeedURL</key> diff --git a/AutoPiP/Resources/Base.lproj/Main.html b/AutoPiP/Resources/Base.lproj/Main.html index 3410697..dd5a5cf 100644 --- a/AutoPiP/Resources/Base.lproj/Main.html +++ b/AutoPiP/Resources/Base.lproj/Main.html @@ -3,17 +3,119 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'"> - <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> - <link rel="stylesheet" href="../Style.css"> <script src="../Script.js" defer></script> </head> <body> - <img src="../Icon.png" width="128" height="128" alt="AutoPiP Icon"> - <p class="state-unknown">You can turn on AutoPiP’s extension in Safari Extensions preferences.</p> - <p class="state-on">AutoPiP’s extension is currently on. You can turn it off in Safari Extensions preferences.</p> - <p class="state-off">AutoPiP’s extension is currently off. You can turn it on in Safari Extensions preferences.</p> - <button class="open-preferences">Quit and Open Safari Extensions Preferences…</button> + + <!-- ===== ONBOARDING PAGE 1: Welcome ===== --> + <div class="page hidden" id="page-welcome"> + <div class="page-content centered"> + <img src="../Icon.png" width="96" height="96" alt="AutoPiP Icon"> + <h1>Welcome to AutoPiP</h1> + <p class="subtitle">Automatically enables Picture-in-Picture when you switch tabs or windows in Safari.</p> + <div class="support-links"> + <a href="https://github.com/vordenken/AutoPiP" id="link-github">GitHub</a> + <span class="dot">·</span> + <a href="https://ko-fi.com/vordenken" id="link-kofi">Ko-fi</a> + <span class="dot">·</span> + <a href="https://www.buymeacoffee.com/vordenken" id="link-bmc">Buy Me a Coffee</a> + </div> + </div> + <div class="page-footer"> + <span class="version-label" id="version-label"></span> + <button id="welcome-next" class="btn primary">Continue</button> + </div> + </div> + + <!-- ===== ONBOARDING PAGE 2: Features ===== --> + <div class="page hidden" id="page-features"> + <div class="page-content"> + <h2>Features</h2> + <p class="hint">Configure these anytime in the AutoPiP Safari toolbar popup.</p> + <div class="feature-list"> + <div class="feature-item"> + <strong>Automatic PiP</strong> + <span>Switch tabs or windows – your video continues in PiP.</span> + </div> + <div class="feature-item"> + <strong>Scroll PiP</strong> + <span>Scroll past a YouTube video to trigger PiP automatically.</span> + </div> + <div class="feature-item"> + <strong>Keyboard Shortcut</strong> + <span>Press ⌥P to toggle PiP at any time (customizable).</span> + </div> + <div class="feature-item"> + <strong>Site Control</strong> + <span>Blacklist mode (default) works on all sites. Switch to Whitelist to allow only specific ones.</span> + </div> + </div> + </div> + <div class="page-footer"> + <button id="features-back" class="btn">Back</button> + <button id="features-next" class="btn primary">Continue</button> + </div> + </div> + + <!-- ===== ONBOARDING PAGE 3: Updates + Done ===== --> + <div class="page hidden" id="page-updates"> + <div class="page-content"> + <h2>Updates</h2> + <div class="settings-list"> + <div class="setting-row"> + <span>Automatically check for updates</span> + <input type="checkbox" class="toggle" id="onb-auto-check" checked> + </div> + <div class="setting-row sub"> + <span>Automatically download updates</span> + <input type="checkbox" class="toggle" id="onb-auto-download"> + </div> + <div class="setting-row"> + <span>Include beta updates</span> + <input type="checkbox" class="toggle" id="onb-beta"> + </div> + </div> + <button id="onb-check-updates" class="btn">Check for Updates Now</button> + <div class="separator"></div> + <p class="hint permission-hint">When Safari opens, enable the AutoPiP extension and allow it access to <strong>all websites</strong>.</p> + </div> + <div class="page-footer"> + <button id="updates-back" class="btn">Back</button> + <button id="updates-done" class="btn primary">Done – Open Safari</button> + </div> + </div> + + <!-- ===== MAIN VIEW (after onboarding) ===== --> + <div class="page hidden" id="page-main"> + <div class="page-content"> + <img src="../Icon.png" width="64" height="64" alt="AutoPiP Icon"> + <p class="state-info state-unknown">You can turn on AutoPiP’s extension in Safari Extensions preferences.</p> + <p class="state-info state-on">AutoPiP’s extension is currently on. You can turn it off in Safari Extensions preferences.</p> + <p class="state-info state-off">AutoPiP’s extension is currently off. You can turn it on in Safari Extensions preferences.</p> + <div class="separator"></div> + <div class="settings-list"> + <div class="setting-row"> + <span>Automatically check for updates</span> + <input type="checkbox" class="toggle" id="auto-check-toggle"> + </div> + <div class="setting-row sub"> + <span>Automatically download updates</span> + <input type="checkbox" class="toggle" id="auto-download-toggle"> + </div> + <div class="setting-row"> + <span>Include beta updates</span> + <input type="checkbox" class="toggle" id="beta-toggle"> + </div> + </div> + <button id="check-updates-btn" class="btn">Check for Updates Now</button> + </div> + <div class="page-footer"> + <span class="version-label" id="main-version-label"></span> + <button class="open-preferences btn primary">Open Safari Preferences…</button> + </div> + </div> + </body> </html> diff --git a/AutoPiP/Resources/Script.js b/AutoPiP/Resources/Script.js index eaee639..76b9faa 100644 --- a/AutoPiP/Resources/Script.js +++ b/AutoPiP/Resources/Script.js @@ -1,22 +1,122 @@ +/* ===== Onboarding Navigation ===== */ + +function showPage(id) { + document.querySelectorAll('.page').forEach(function(p) { p.classList.add('hidden'); }); + document.getElementById(id).classList.remove('hidden'); +} + +function startOnboarding() { + showPage('page-welcome'); +} + +/* ===== Version (called from Swift) ===== */ + +function setVersion(v) { + var text = 'v' + v; + var el = document.getElementById('version-label'); + if (el) el.textContent = text; + var el2 = document.getElementById('main-version-label'); + if (el2) el2.textContent = text; +} + +/* ===== Main view (called from Swift) ===== */ + function show(enabled, useSettingsInsteadOfPreferences) { + showPage('page-main'); + if (useSettingsInsteadOfPreferences) { - document.getElementsByClassName('state-on')[0].innerText = "AutoPiP’s extension is currently on. You can turn it off in the Extensions section of Safari Settings."; - document.getElementsByClassName('state-off')[0].innerText = "AutoPiP’s extension is currently off. You can turn it on in the Extensions section of Safari Settings."; - document.getElementsByClassName('state-unknown')[0].innerText = "You can turn on AutoPiP’s extension in the Extensions section of Safari Settings."; - document.getElementsByClassName('open-preferences')[0].innerText = "Quit and Open Safari Settings…"; + document.getElementsByClassName('state-on')[0].innerText = "AutoPiP\u2019s extension is currently on. You can turn it off in the Extensions section of Safari Settings."; + document.getElementsByClassName('state-off')[0].innerText = "AutoPiP\u2019s extension is currently off. You can turn it on in the Extensions section of Safari Settings."; + document.getElementsByClassName('state-unknown')[0].innerText = "You can turn on AutoPiP\u2019s extension in the Extensions section of Safari Settings."; + document.getElementsByClassName('open-preferences')[0].innerText = "Open Safari Settings\u2026"; } if (typeof enabled === "boolean") { - document.body.classList.toggle(`state-on`, enabled); - document.body.classList.toggle(`state-off`, !enabled); + document.body.classList.toggle('state-on', enabled); + document.body.classList.toggle('state-off', !enabled); } else { - document.body.classList.remove(`state-on`); - document.body.classList.remove(`state-off`); + document.body.classList.remove('state-on'); + document.body.classList.remove('state-off'); } } -function openPreferences() { - webkit.messageHandlers.controller.postMessage("open-preferences"); +function setUpdateSettings(settings) { + document.getElementById('auto-check-toggle').checked = settings.autoCheck; + document.getElementById('auto-download-toggle').checked = settings.autoDownload; + document.getElementById('auto-download-toggle').disabled = !settings.autoCheck; + document.getElementById('beta-toggle').checked = settings.beta; } -document.querySelector("button.open-preferences").addEventListener("click", openPreferences); +/* ===== Event Listeners ===== */ + +document.addEventListener('DOMContentLoaded', function() { + + /* --- Onboarding Page 1 --- */ + document.getElementById('welcome-next').addEventListener('click', function() { + showPage('page-features'); + }); + + /* --- Onboarding Page 2 (Features) --- */ + document.getElementById('features-back').addEventListener('click', function() { + showPage('page-welcome'); + }); + document.getElementById('features-next').addEventListener('click', function() { + showPage('page-updates'); + }); + + /* --- Onboarding Page 3 (Updates) --- */ + document.getElementById('updates-back').addEventListener('click', function() { + showPage('page-features'); + }); + + document.getElementById('onb-auto-check').addEventListener('change', function() { + var dl = document.getElementById('onb-auto-download'); + dl.disabled = !this.checked; + if (!this.checked) dl.checked = false; + }); + + document.getElementById('onb-check-updates').addEventListener('click', function() { + webkit.messageHandlers.controller.postMessage("check-for-updates"); + }); + + document.getElementById('updates-done').addEventListener('click', function() { + var settings = { + autoCheck: document.getElementById('onb-auto-check').checked, + autoDownload: document.getElementById('onb-auto-download').checked, + beta: document.getElementById('onb-beta').checked + }; + webkit.messageHandlers.controller.postMessage("onboarding-done:" + JSON.stringify(settings)); + }); + + /* --- Support links (open in default browser) --- */ + document.querySelectorAll('.support-links a').forEach(function(link) { + link.addEventListener('click', function(e) { + e.preventDefault(); + webkit.messageHandlers.controller.postMessage("open-url:" + this.href); + }); + }); + + /* --- Main view controls --- */ + document.querySelector("button.open-preferences").addEventListener("click", function() { + webkit.messageHandlers.controller.postMessage("open-preferences"); + }); + + document.getElementById('check-updates-btn').addEventListener('click', function() { + webkit.messageHandlers.controller.postMessage("check-for-updates"); + }); + + document.getElementById('auto-check-toggle').addEventListener('change', function() { + webkit.messageHandlers.controller.postMessage("set-auto-check:" + this.checked); + var dl = document.getElementById('auto-download-toggle'); + dl.disabled = !this.checked; + if (!this.checked) dl.checked = false; + }); + + document.getElementById('auto-download-toggle').addEventListener('change', function() { + webkit.messageHandlers.controller.postMessage("set-auto-download:" + this.checked); + }); + + document.getElementById('beta-toggle').addEventListener('change', function() { + webkit.messageHandlers.controller.postMessage("set-beta:" + this.checked); + }); +}); diff --git a/AutoPiP/Resources/Style.css b/AutoPiP/Resources/Style.css index cbde9e6..4a93697 100644 --- a/AutoPiP/Resources/Style.css +++ b/AutoPiP/Resources/Style.css @@ -6,40 +6,266 @@ :root { color-scheme: light dark; + --accent-color: rgb(0, 122, 255); + --success-color: #34c759; + --border-color: rgba(128, 128, 128, 0.25); +} - --spacing: 20px; +@media (prefers-color-scheme: dark) { + :root { + --accent-color: #0a84ff; + --success-color: #32d74b; + --border-color: rgba(128, 128, 128, 0.35); + } } -html { +html, body { height: 100%; + margin: 0; + overflow: hidden; } body { + font: -apple-system-short-body; + text-align: center; +} + +.hidden { display: none !important; } + +/* ===== Pages (shared) ===== */ + +.page { display: flex; + flex-direction: column; + height: 100%; +} + +.page-content { + flex: 1; + display: flex; + flex-direction: column; align-items: center; justify-content: center; - flex-direction: column; + padding: 20px 30px 0; + gap: 8px; +} - gap: var(--spacing); - margin: 0 calc(var(--spacing) * 2); - height: 100%; +.page-content.centered { + text-align: center; +} - font: -apple-system-short-body; +.page-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px 14px; +} + +/* ===== Typography ===== */ + +h1 { + font-size: 1.3em; + margin: 0; + font-weight: 600; +} + +h2 { + font-size: 1.1em; + margin: 0 0 2px; + font-weight: 600; +} + +.subtitle { + margin: 0; + font-size: 0.85em; + opacity: 0.7; + line-height: 1.4; +} + +.hint { + margin: 0; + font-size: 0.75em; + opacity: 0.55; +} + +.permission-hint { text-align: center; + line-height: 1.4; +} + +/* ===== Buttons ===== */ + +.btn { + font-size: 0.85em; + padding: 5px 14px; + border-radius: 5px; + border: none; + cursor: default; + background: rgba(128, 128, 128, 0.2); +} + +.btn:active { + opacity: 0.7; +} + +.btn.primary { + background-color: var(--accent-color); + color: white; + font-weight: 500; +} + + + +/* ===== Toggle Switch (matches popup) ===== */ + +.toggle { + position: relative; + width: 38px; + height: 22px; + -webkit-appearance: none; + appearance: none; + background-color: rgba(128, 128, 128, 0.3); + border-radius: 11px; + outline: none; + cursor: default; + transition: background-color 0.25s ease; + margin: 0; + flex-shrink: 0; +} + +.toggle:checked { + background-color: var(--success-color); +} + +.toggle::before { + content: ''; + position: absolute; + width: 18px; + height: 18px; + border-radius: 50%; + background-color: white; + top: 2px; + left: 2px; + transition: transform 0.25s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.toggle:checked::before { + transform: translateX(16px); } +.toggle:active::before { + width: 22px; +} + +/* ===== Setting rows ===== */ + +.settings-list { + width: 100%; + display: flex; + flex-direction: column; + gap: 6px; + text-align: left; + font-size: 0.85em; +} + +.setting-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} + +.setting-row.sub { + padding-left: 18px; +} + +.setting-row span { + flex: 1; +} + +/* ===== Feature list (page 2) ===== */ + +.feature-list { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + text-align: left; +} + +.feature-item { + display: flex; + flex-direction: column; + gap: 1px; + font-size: 0.85em; +} + +.feature-item strong { + font-weight: 600; +} + +.feature-item span { + font-size: 0.88em; + opacity: 0.55; + line-height: 1.3; +} + +/* ===== Support links ===== */ + +.support-links { + display: flex; + gap: 6px; + align-items: center; + font-size: 0.75em; + margin-top: 10px; +} + +.support-links a { + color: var(--accent-color); + text-decoration: none; +} + +.support-links a:hover { + text-decoration: underline; +} + +.dot { + opacity: 0.4; +} + +/* ===== Version label ===== */ + +.version-label { + font-size: 0.7em; + opacity: 0.35; +} + +/* ===== Separator ===== */ + +.separator { + width: 100%; + height: 1px; + background: var(--border-color); + margin: 4px 0; +} + +/* ===== State info text (main view) ===== */ + +.state-info { + margin: 0; + font-size: 0.85em; + opacity: 0.7; + line-height: 1.4; +} + +/* State visibility */ body:not(.state-on, .state-off) :is(.state-on, .state-off) { display: none; } - body.state-on :is(.state-off, .state-unknown) { display: none; } - body.state-off :is(.state-on, .state-unknown) { display: none; } - -button { - font-size: 1em; -} diff --git a/AutoPiP/UpdateController.swift b/AutoPiP/UpdateController.swift index f1c0792..302497e 100644 --- a/AutoPiP/UpdateController.swift +++ b/AutoPiP/UpdateController.swift @@ -3,14 +3,22 @@ import Sparkle +private class UpdaterDelegate: NSObject, SPUUpdaterDelegate { + func allowedChannels(for updater: SPUUpdater) -> Set<String> { + UserDefaults.standard.bool(forKey: "BetaUpdatesEnabled") ? Set(["beta"]) : Set() + } +} + class UpdateController { + private let delegate = UpdaterDelegate() private let updaterController: SPUStandardUpdaterController + var updater: SPUUpdater { updaterController.updater } + init() { - // Initialisiere den Updater updaterController = SPUStandardUpdaterController( startingUpdater: true, - updaterDelegate: nil, + updaterDelegate: delegate, userDriverDelegate: nil ) } @@ -18,4 +26,22 @@ class UpdateController { func checkForUpdates() { updaterController.checkForUpdates(nil) } + + var automaticallyChecksForUpdates: Bool { + get { updater.automaticallyChecksForUpdates } + set { updater.automaticallyChecksForUpdates = newValue } + } + + var automaticallyDownloadsUpdates: Bool { + get { updater.automaticallyDownloadsUpdates } + set { updater.automaticallyDownloadsUpdates = newValue } + } + + var isBetaUpdatesEnabled: Bool { + get { UserDefaults.standard.bool(forKey: "BetaUpdatesEnabled") } + set { + UserDefaults.standard.set(newValue, forKey: "BetaUpdatesEnabled") + updater.resetUpdateCycleAfterShortDelay() + } + } } diff --git a/AutoPiP/ViewController.swift b/AutoPiP/ViewController.swift index ff83c74..a5ddfe2 100644 --- a/AutoPiP/ViewController.swift +++ b/AutoPiP/ViewController.swift @@ -26,30 +26,111 @@ class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHan } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in - guard let state = state, error == nil else { - // Insert code to inform the user that something went wrong. - return - } + if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + webView.evaluateJavaScript("setVersion('\(version)')") + } + + let onboardingDone = UserDefaults.standard.bool(forKey: "OnboardingCompleted") + + if !onboardingDone { + webView.evaluateJavaScript("startOnboarding()") + return + } + + showMainView() + } + private func showMainView() { + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in DispatchQueue.main.async { + guard let state = state, error == nil else { + self.webView.evaluateJavaScript("show(null, true)") + return + } + if #available(macOS 13, *) { - webView.evaluateJavaScript("show(\(state.isEnabled), true)") + self.webView.evaluateJavaScript("show(\(state.isEnabled), true)") } else { - webView.evaluateJavaScript("show(\(state.isEnabled), false)") + self.webView.evaluateJavaScript("show(\(state.isEnabled), false)") } } } + + if let appDelegate = NSApp.delegate as? AppDelegate { + let uc = appDelegate.updateController + let json = """ + {autoCheck:\(uc.automaticallyChecksForUpdates),autoDownload:\(uc.automaticallyDownloadsUpdates),beta:\(uc.isBetaUpdatesEnabled)} + """ + webView.evaluateJavaScript("setUpdateSettings(\(json))") + } } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if (message.body as! String != "open-preferences") { - return; + guard let body = message.body as? String else { return } + + if body == "open-preferences" { + openSafariAndQuit() + return + } + + if body.hasPrefix("open-url:") { + let urlString = String(body.dropFirst("open-url:".count)) + if let url = URL(string: urlString) { + NSWorkspace.shared.open(url) + } + return + } + + if body.hasPrefix("onboarding-done:") { + let jsonString = String(body.dropFirst("onboarding-done:".count)) + handleOnboardingDone(jsonString) + return + } + + guard let uc = (NSApp.delegate as? AppDelegate)?.updateController else { return } + + switch body { + case "check-for-updates": + uc.checkForUpdates() + case let s where s.hasPrefix("set-auto-check:"): + uc.automaticallyChecksForUpdates = s.hasSuffix("true") + case let s where s.hasPrefix("set-auto-download:"): + uc.automaticallyDownloadsUpdates = s.hasSuffix("true") + case let s where s.hasPrefix("set-beta:"): + uc.isBetaUpdatesEnabled = s.hasSuffix("true") + default: + break + } + } + + private func handleOnboardingDone(_ jsonString: String) { + guard let data = jsonString.data(using: .utf8), + let settings = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let uc = (NSApp.delegate as? AppDelegate)?.updateController else { return } + + if let autoCheck = settings["autoCheck"] as? Bool { + uc.automaticallyChecksForUpdates = autoCheck + } + if let autoDownload = settings["autoDownload"] as? Bool { + uc.automaticallyDownloadsUpdates = autoDownload + } + if let beta = settings["beta"] as? Bool { + uc.isBetaUpdatesEnabled = beta } + UserDefaults.standard.set(true, forKey: "OnboardingCompleted") + openSafariAndQuit() + } + + private func openSafariAndQuit() { SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in DispatchQueue.main.async { - NSApplication.shared.terminate(nil) + if error != nil, let safariURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Safari") { + NSWorkspace.shared.openApplication(at: safariURL, configuration: NSWorkspace.OpenConfiguration()) + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + NSApplication.shared.terminate(nil) + } } } } diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..efd26e2 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,50 @@ +# Building & Releasing AutoPiP + +## Build from Source + +```bash +git clone https://github.com/vordenken/AutoPiP.git +cd AutoPiP +open AutoPiP.xcodeproj +``` + +Then build and run the `AutoPiP` scheme in Xcode. + +## How to Release + +AutoPiP is built and released automatically via GitHub Actions. The version in **`semver.txt`** is the single source of truth. + +1. Create a `feature/*` branch from `main` +2. Update the version in `semver.txt` (must be valid [semver](https://semver.org/), e.g. `2.1.0`) +3. Commit and push: + - **Push to `feature/*`** β†’ beta release (`v2.1.0-beta`), published to Sparkle beta channel + - **Merge to `main`** β†’ stable release (`v2.1.0`), published to Sparkle + +The workflow automatically: +- Sets the version in the Xcode project and `manifest.json` +- Builds and archives the app +- Creates a DMG with drag-to-Applications installer +- Signs the DMG with Sparkle EdDSA (if configured) +- Creates a git tag and GitHub Release with `AutoPiP.dmg` +- Updates `appcast.xml` on both the triggering branch and `main` for Sparkle auto-updates + +You can also trigger a build manually from the **Actions** tab using "Run workflow". + +## Repository Secrets + +| Secret | Required | Description | +|--------|----------|-------------| +| `BUILD_CERTIFICATE_BASE64` | Optional | Base64-encoded `.p12` signing certificate | +| `P12_PASSWORD` | Optional | Password for the `.p12` file | +| `SPARKLE_PRIVATE_KEY` | Optional | EdDSA private key for Sparkle update signatures | + +**Export your signing certificate:** + +```bash +# Export from Keychain Access as .p12, then encode: +base64 -i certificate.p12 | pbcopy +``` + +**Export your Sparkle key:** The EdDSA private key generated by Sparkle's `generate_keys` tool. Store it as the `SPARKLE_PRIVATE_KEY` secret. + +Without `BUILD_CERTIFICATE_BASE64`, the app is built with ad-hoc signing. Without `SPARKLE_PRIVATE_KEY`, appcast entries will have no EdDSA signature. diff --git a/README.md b/README.md index 255b3ae..6e9428c 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ <p align="center"> <img src="https://img.shields.io/github/downloads/vordenken/AutoPiP/total" alt="Downloads"> <img src="https://img.shields.io/github/license/vordenken/AutoPiP" alt="License"> + <img src="https://img.shields.io/github/actions/workflow/status/vordenken/AutoPiP/build-release.yml" alt="Build"> <a href="https://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"></a> </p> @@ -25,11 +26,18 @@ A Safari extension that automatically enables Picture-in-Picture (PiP) mode for ## πŸš€ Quick Start -1. Download the latest release [here](https://github.com/vordenken/AutoPiP/releases) -2. Install and enable the Safari extension -3. Start watching videos - PiP activates automatically! +> **⚠️ macOS Gatekeeper:** AutoPiP is not notarized by Apple. macOS will block both the DMG and the app with a security warning β€” this is expected. Follow the steps below to allow them. Once installed, **updates via Sparkle work without this workaround**. -> **Updating:** To receive updates, open the AutoPiP app from time to time. Sparkle checks for updates automatically (once per day). +1. Download the latest `AutoPiP.dmg` from [Releases](https://github.com/vordenken/AutoPiP/releases) +2. Try to open the DMG β€” macOS will block it with *"cannot be opened because it is from an unidentified developer"* + - Open **System Settings β†’ Privacy & Security**, scroll down and click **"Open Anyway"** + - Open the DMG again and drag `AutoPiP.app` to your Applications folder +3. Try to open `AutoPiP.app` β€” macOS will block it again with the same warning + - Open **System Settings β†’ Privacy & Security**, scroll down and click **"Open Anyway"** + - Open the app again β€” it will guide you to enable the Safari extension +4. Enable **AutoPiP** in Safari β†’ Settings β†’ Extensions + +> **Updating:** Open the AutoPiP app occasionally β€” Sparkle checks for updates automatically (once per day). ## 🎯 Compatibility @@ -53,6 +61,10 @@ A Safari extension that automatically enables Picture-in-Picture (PiP) mode for > I wanted to add Chrome/Firefox support but Safari is the only browser that allows calling PiP without user-interaction - So unless this changes, AutoPiP will be Safari only +## πŸ”¨ Building & Releasing + +See [BUILD.md](BUILD.md) for instructions on building from source, releasing, and configuring repository secrets. + ## 🀝 Contributing As this is my first Swift/Xcode project, I welcome: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4a4427d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +|---------|:---------:| +| 2.x | βœ… | +| < 2.0 | ❌ | + +## Reporting a Vulnerability + +If you discover a security vulnerability in AutoPiP, please report it responsibly: + +1. **Do not** open a public issue +2. Use [GitHub private vulnerability reporting](https://github.com/vordenken/AutoPiP/security/advisories/new) +3. Include a description of the vulnerability and steps to reproduce + +You can expect an initial response within 48 hours. Confirmed vulnerabilities will be patched as soon as possible and credited in the release notes (unless you prefer to remain anonymous). diff --git a/appcast.xml b/appcast.xml index 7a52e57..25aeb3e 100755 --- a/appcast.xml +++ b/appcast.xml @@ -21,7 +21,8 @@ <pubDate>Fri, 27 Feb 2026 12:00:00 +0000</pubDate> <enclosure url="https://github.com/vordenken/autopip/releases/download/v2.0.0/AutoPiP.dmg" - sparkle:version="2.0.0" + sparkle:version="6" + sparkle:shortVersionString="2.0.0" sparkle:edSignature="R7DiDfc40yk1tpXuU3p3o6WJ0XEiISMQKjSmex8/AZt1jeRHLZcQaX1Nb+7Xn0tEF139DXux0mJIf8LfPdAQBg==" length="2288354" type="application/octet-stream"/> @@ -40,7 +41,8 @@ <pubDate>Fri, 03 Jan 2026 19:00:00 +0000</pubDate> <enclosure url="https://github.com/vordenken/autopip/releases/download/v1.0.1/AutoPiP.dmg" - sparkle:version="1.0.1" + sparkle:version="5" + sparkle:shortVersionString="1.0.1" sparkle:edSignature="jSpqiCQBAyMpPBOY2E+pf4tPDNf4uIuY7U1cTqmiPxI03bv/HZv3KkVP5FpPeHQJxeiGXvpGtrIap3j7MR1jDA==" length="1656450" type="application/octet-stream"/> @@ -57,7 +59,8 @@ <pubDate>Mo, 21 Jul 2025 12:00:00 +0000</pubDate> <enclosure url="https://github.com/vordenken/autopip/releases/download/v1.0/AutoPiP.dmg" - sparkle:version="1.0" + sparkle:version="4" + sparkle:shortVersionString="1.0" sparkle:edSignature="cPoswjxw0WzAFyCgyTl0YfqqPEaD/gAP6Nzjg5GR/A2MMGw9D9fGVi+tYF9B8eqomDQXm4WEwFJSe5XBs89GDg==" length="2188862" type="application/octet-stream"/> @@ -86,7 +89,8 @@ <pubDate>Fr, 06 Dec 2024 18:17:00 +0000</pubDate> <enclosure url="https://github.com/vordenken/autopip/releases/download/v0.4/AutoPiP.dmg" - sparkle:version="0.4" + sparkle:version="3" + sparkle:shortVersionString="0.4" sparkle:edSignature="18gVWGYSAZM4NaEfGlZmWZHowDqhgMEuUvILOF4ScN2GuX2I421fb5lOXIyhHLCdV9QLH9khnxsklS1Gh0iIAg==" length="5537628" type="application/octet-stream"/> @@ -105,7 +109,8 @@ <pubDate>Do, 28 Nov 2024 12:45:00 +0000</pubDate> <enclosure url="https://github.com/vordenken/autopip/releases/download/v0.3/AutoPiP.dmg" - sparkle:version="0.3" + sparkle:version="2" + sparkle:shortVersionString="0.3" sparkle:edSignature="wKXRYhayYGcYFcCeUMNsd21bZwIwxULuWIX67gyV52Ioxnmm1TpT1RrB+0+KhcCuv7JTpTeLRboyBmI47FaYCw==" length="5536506" type="application/octet-stream"/> @@ -122,7 +127,8 @@ <pubDate>Di, 26 Nov 2024 15:00:00 +0000</pubDate> <enclosure url="https://github.com/vordenken/autopip/releases/download/v0.2/AutoPiP.dmg" - sparkle:version="0.2" + sparkle:version="1" + sparkle:shortVersionString="0.2" sparkle:edSignature="2GecN7z1i3agIY2fxcssKG5awCG6bI6anQWUW4cHqrkWOst8a81XdX9cK8KeQfHLMpyy5gg4buCHuwgpd2NDCA==" length="5524923" type="application/octet-stream"/> diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..9995802 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"] +} \ No newline at end of file diff --git a/semver.txt b/semver.txt new file mode 100644 index 0000000..7ec1d6d --- /dev/null +++ b/semver.txt @@ -0,0 +1 @@ +2.1.0 From f5470b02ac9c35348a59004c090537946d96c296 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:16:45 +0000 Subject: [PATCH 02/44] chore: update appcast for v2.1.0-beta --- appcast.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/appcast.xml b/appcast.xml index 25aeb3e..4c5e311 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,17 @@ <rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"> <channel> <title>AutoPiP Updates + + Version 2.1.0 + beta + Tue, 14 Apr 2026 20:16:38 +0000 + + Version 2.0.0 From 38043a5b929cfce7ddd8f70bbfbe5adcfdd86bfb Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:48:34 +0200 Subject: [PATCH 03/44] Update semver.txt with new features and improvements for version 2.1.0 --- .github/workflows/build-release.yml | 356 +++++++++++++++------------- semver.txt | 25 ++ 2 files changed, 218 insertions(+), 163 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 26c43fd..3fc0160 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -3,25 +3,60 @@ name: Build and Release on: push: branches: [main, 'feature/*'] - paths: - [semver.txt] workflow_dispatch: permissions: contents: write - pull-requests: write jobs: + # ── Gate: on main only build when semver.txt changed ───────── + check: + runs-on: ubuntu-latest + outputs: + should-build: ${{ steps.check.outputs.should-build }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Decide whether to build + id: check + run: | + if [ "${{ github.ref_name }}" != "main" ]; then + echo "should-build=true" >> "$GITHUB_OUTPUT" + echo "Feature branch β€” always build" + exit 0 + fi + + BEFORE="${{ github.event.before }}" + if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then + echo "should-build=true" >> "$GITHUB_OUTPUT" + echo "New branch push β€” build" + exit 0 + fi + + if git diff --name-only "$BEFORE" "${{ github.sha }}" | grep -q '^semver\.txt$'; then + echo "should-build=true" >> "$GITHUB_OUTPUT" + echo "semver.txt changed on main β€” build" + else + echo "should-build=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping β€” semver.txt not changed on main" + fi + + # ── Main build job ─────────────────────────────────────────── build-and-release: + needs: check + if: needs.check.outputs.should-build == 'true' runs-on: macos-15 steps: + # ── Setup ────────────────────────────────────────────────── - name: Generate GitHub App token id: app-token - if: vars.APP_ID != '' - uses: actions/create-github-app-token@v1 + if: vars.CLIENT_ID != '' + uses: actions/create-github-app-token@v3 with: - app-id: ${{ vars.APP_ID }} + client-id: ${{ vars.CLIENT_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Checkout @@ -30,46 +65,57 @@ jobs: fetch-depth: 0 token: ${{ steps.app-token.outputs.token || github.token }} - - name: Read version from semver.txt + # ── Version & Changelog ──────────────────────────────────── + - name: Read version and changelog from semver.txt id: version run: | - VERSION=$(tr -d '[:space:]' < semver.txt) + VERSION=$(head -1 semver.txt | tr -d '[:space:]') if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Invalid semver in semver.txt: '$VERSION'" + echo "::error::Invalid semver in semver.txt line 1: '$VERSION'" exit 1 fi + # Extract changelog for current version (between --- and next ## or EOF) + awk '/^---$/{found=1; next} /^## /{exit} found{print}' semver.txt \ + | sed '/^[[:space:]]*$/d' > "$RUNNER_TEMP/changelog.txt" + if [ "${{ github.ref_name }}" = "main" ]; then TAG="v${VERSION}" PRERELEASE=false CHANNEL=stable else - TAG="v${VERSION}-beta" + # Auto-increment beta number from existing tags + BETA_NUM=1 + LATEST=$(git tag -l "v${VERSION}-beta*" \ + | sed "s/v${VERSION}-beta//" \ + | grep -E '^[0-9]+$' \ + | sort -n | tail -1) + if [ -n "$LATEST" ]; then + BETA_NUM=$((LATEST + 1)) + fi + TAG="v${VERSION}-beta${BETA_NUM}" PRERELEASE=true CHANNEL=beta fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" - echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + echo "### $CHANNEL release β€” $TAG" >> "$GITHUB_STEP_SUMMARY" + if [ -s "$RUNNER_TEMP/changelog.txt" ]; then + echo '```' >> "$GITHUB_STEP_SUMMARY" + cat "$RUNNER_TEMP/changelog.txt" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + fi - name: Check for existing tag - id: check_tag run: | TAG="${{ steps.version.outputs.tag }}" - CHANNEL="${{ steps.version.outputs.channel }}" - if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then - if [ "$CHANNEL" = "stable" ]; then - echo "::error::Stable release ${TAG} already exists β€” bump the version in semver.txt to create a new release." - exit 1 - fi - echo "::warning::Beta tag ${TAG} already exists β€” release will be overwritten." - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" + echo "::error::Tag ${TAG} already exists. For betas, numbers auto-increment β€” if this is unexpected, check existing tags with: git tag -l 'v*'" + exit 1 fi # ── Signing ──────────────────────────────────────────────── @@ -79,9 +125,8 @@ jobs: P12_PASSWORD: ${{ secrets.P12_PASSWORD }} run: | if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then - echo "::warning::No signing certificate configured β€” building with ad-hoc signing." - echo "SIGNING_AVAILABLE=false" >> "$GITHUB_ENV" - exit 0 + echo "::error::BUILD_CERTIFICATE_BASE64 secret is not set β€” code signing is required." + exit 1 fi CERTIFICATE_PATH="$RUNNER_TEMP/build_certificate.p12" @@ -98,7 +143,6 @@ jobs: security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security list-keychain -d user -s "$KEYCHAIN_PATH" - echo "SIGNING_AVAILABLE=true" >> "$GITHUB_ENV" echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" # ── Build ────────────────────────────────────────────────── @@ -127,23 +171,11 @@ jobs: run: | ARCHIVE_PATH="$RUNNER_TEMP/AutoPiP.xcarchive" - if [ "$SIGNING_AVAILABLE" = "true" ]; then - xcodebuild archive \ - -project AutoPiP.xcodeproj \ - -scheme AutoPiP \ - -configuration Release \ - -archivePath "$ARCHIVE_PATH" - else - xcodebuild archive \ - -project AutoPiP.xcodeproj \ - -scheme AutoPiP \ - -configuration Release \ - -archivePath "$ARCHIVE_PATH" \ - CODE_SIGN_IDENTITY="-" \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGNING_ALLOWED=NO \ - DEVELOPMENT_TEAM="" - fi + xcodebuild archive \ + -project AutoPiP.xcodeproj \ + -scheme AutoPiP \ + -configuration Release \ + -archivePath "$ARCHIVE_PATH" - name: Create DMG run: | @@ -182,72 +214,75 @@ jobs: FILE_LENGTH=$(stat -f%z "$DMG_PATH") echo "DMG size: $FILE_LENGTH bytes" - # ── Sparkle EdDSA signature (optional) ── - SIG_ATTR="" - if [ -n "$SPARKLE_PRIVATE_KEY" ]; then - echo "Downloading Sparkle tools…" - curl -fSL \ - "https://github.com/sparkle-project/Sparkle/releases/download/2.9.1/Sparkle-2.9.1.tar.xz" \ - -o "$RUNNER_TEMP/sparkle.tar.xz" - mkdir -p "$RUNNER_TEMP/sparkle" - tar -xJf "$RUNNER_TEMP/sparkle.tar.xz" -C "$RUNNER_TEMP/sparkle" - - SIGN_TOOL="$RUNNER_TEMP/sparkle/bin/sign_update" - if [ ! -x "$SIGN_TOOL" ]; then - echo "::error::sign_update not found or not executable at $SIGN_TOOL" - exit 1 - fi + # ── Sparkle EdDSA signature (required) ── + if [ -z "$SPARKLE_PRIVATE_KEY" ]; then + echo "::error::SPARKLE_PRIVATE_KEY secret is not set β€” Sparkle signing is required." + exit 1 + fi - echo "Signing DMG with Sparkle EdDSA…" - set +e - SIGN_OUTPUT=$(echo "$SPARKLE_PRIVATE_KEY" | "$SIGN_TOOL" "$DMG_PATH" --ed-key-file -) - SIGN_RC=$? - set -e - - if [ $SIGN_RC -ne 0 ]; then - echo "::warning::sign_update exited with code $SIGN_RC β€” output was:" - echo "$SIGN_OUTPUT" - echo "Continuing without Sparkle signature." - else - SIGNATURE=$(echo "$SIGN_OUTPUT" | grep -oE 'sparkle:edSignature="[^"]+"' | cut -d'"' -f2) - if [ -n "$SIGNATURE" ]; then - SIG_ATTR=$'\n'" sparkle:edSignature=\"${SIGNATURE}\"" - echo "βœ… Sparkle EdDSA signature generated" - else - echo "::warning::sign_update succeeded but no edSignature found in output:" - echo "$SIGN_OUTPUT" - echo "Continuing without Sparkle signature." - fi - fi - else - echo "::warning::SPARKLE_PRIVATE_KEY not set β€” appcast entry will have no signature." + echo "Downloading Sparkle tools…" + curl -fSL \ + "https://github.com/sparkle-project/Sparkle/releases/download/2.9.1/Sparkle-2.9.1.tar.xz" \ + -o "$RUNNER_TEMP/sparkle.tar.xz" + mkdir -p "$RUNNER_TEMP/sparkle" + tar -xJf "$RUNNER_TEMP/sparkle.tar.xz" -C "$RUNNER_TEMP/sparkle" + + SIGN_TOOL="$RUNNER_TEMP/sparkle/bin/sign_update" + if [ ! -x "$SIGN_TOOL" ]; then + echo "::error::sign_update not found or not executable at $SIGN_TOOL" + exit 1 fi - # ── Channel element (beta only) ── - CHANNEL_EL="" - if [ "$CHANNEL" = "beta" ]; then - CHANNEL_EL=$'\n'" beta" + echo "Signing DMG with Sparkle EdDSA…" + SIGN_OUTPUT=$(echo "$SPARKLE_PRIVATE_KEY" | "$SIGN_TOOL" "$DMG_PATH" --ed-key-file -) + SIGNATURE=$(echo "$SIGN_OUTPUT" | grep -oE 'sparkle:edSignature="[^"]+"' | cut -d'"' -f2) + if [ -z "$SIGNATURE" ]; then + echo "::error::Sparkle signing failed β€” no edSignature found in output:" + echo "$SIGN_OUTPUT" + exit 1 fi + SIG_ATTR=$'\n'" sparkle:edSignature=\"${SIGNATURE}\"" + echo "βœ… Sparkle EdDSA signature generated" + + # ── Build appcast XML ── + { + echo ' ' + echo " Version ${VERSION}" + + if [ "$CHANNEL" = "beta" ]; then + echo ' beta' + fi + + # Description from changelog + if [ -s "$RUNNER_TEMP/changelog.txt" ]; then + echo ' ' + echo ' ' + while IFS= read -r line; do + case "$line" in + "- "*) echo "
  • ${line#- }
  • " ;; + "### "*) echo "

    ${line#\#\#\# }

      " ;; + esac + done < "$RUNNER_TEMP/changelog.txt" + echo '
    ' + echo ' ]]>' + echo '
    ' + fi + + DOWNLOAD_URL="https://github.com/vordenken/AutoPiP/releases/download/${TAG}/AutoPiP.dmg" + PUB_DATE=$(date -u '+%a, %d %b %Y %H:%M:%S %z') + + echo " ${PUB_DATE}" + echo ' ' + echo '
    ' + } > "$RUNNER_TEMP/appcast_item.xml" - DOWNLOAD_URL="https://github.com/vordenken/AutoPiP/releases/download/${TAG}/AutoPiP.dmg" - PUB_DATE=$(date -u '+%a, %d %b %Y %H:%M:%S %z') - - # ── Build block and insert into appcast ── - ITEM_FILE="$RUNNER_TEMP/appcast_item.xml" - cat > "$ITEM_FILE" < - Version ${VERSION}${CHANNEL_EL} - ${PUB_DATE} - - - ITEM_EOF - - # Remove any existing appcast entry for the same version (prevents duplicates on beta re-releases) + # Remove existing entries for the same shortVersionString (dedup) awk -v ver="$VERSION" ' // { buf = $0; inside = 1; next } inside { @@ -263,9 +298,9 @@ jobs: ' appcast.xml > "$RUNNER_TEMP/appcast_deduped.xml" mv "$RUNNER_TEMP/appcast_deduped.xml" appcast.xml - # Insert the item block after AutoPiP Updates - sed -i '' "/AutoPiP Updates<\/title>/r ${ITEM_FILE}" appcast.xml - echo "appcast.xml updated β€” $CHANNEL $VERSION" + # Insert after <title>AutoPiP Updates + sed -i '' "/AutoPiP Updates<\/title>/r $RUNNER_TEMP/appcast_item.xml" appcast.xml + echo "appcast.xml updated β€” $CHANNEL ${TAG}" - name: Upload build artifact uses: actions/upload-artifact@v7 @@ -274,40 +309,60 @@ jobs: path: ${{ runner.temp }}/AutoPiP.dmg # ── Release ──────────────────────────────────────────────── + - name: Prepare release notes + run: | + NOTES="$RUNNER_TEMP/release_notes.md" + + # Prepend changelog if available + if [ -s "$RUNNER_TEMP/changelog.txt" ]; then + { + echo "## Changelog" + echo "" + cat "$RUNNER_TEMP/changelog.txt" + echo "" + echo "---" + echo "" + } > "$NOTES" + else + : > "$NOTES" + fi + + cat >> "$NOTES" <<'INSTALL_EOF' + ## Important Note + + This extension is currently not notarized by Apple. You'll need to bypass macOS security settings to initially install it. Updating works through Sparkle and doesn't need these steps again! + + ### Installation Steps + + 1. Download `AutoPiP.dmg` file from the releases page and open it + 2. When trying to open `AutoPiP.dmg`, macOS will show a security warning + 3. To bypass this: + - Open System Settings + - Go to Privacy & Security + - Scroll down and click "Open Anyway" + 4. Open `AutoPiP.dmg` again + 5. Copy the `.app` to your Applications folder + 6. When trying to open the app, macOS will show a security warning + 7. To bypass this: + - Open System Settings + - Go to Privacy & Security + - Scroll down and click "Open Anyway" + 8. Open `AutoPiP.app` again + 9. Follow the instructions + 10. Allow the requested permissions when prompted + INSTALL_EOF + - name: Create tag and GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.version.outputs.tag }} name: ${{ steps.version.outputs.tag }} files: ${{ runner.temp }}/AutoPiP.dmg prerelease: ${{ steps.version.outputs.prerelease == 'true' }} - generate_release_notes: true + body_path: ${{ runner.temp }}/release_notes.md token: ${{ steps.app-token.outputs.token || github.token }} - body: | - ## Important Note - - This extension is currently not notarized by Apple. You'll need to bypass macOS security settings to initially install it. Updating works through Sparkle and doesn't need these steps again! - - ### Installation Steps - - 1. Download `AutoPiP.dmg` file from the releases page and open it - 2. When trying to open `AutoPiP.dmg`, macOS will show a security warning - 3. To bypass this: - - Open System Settings - - Go to Privacy & Security - - Scroll down and click "Open Anyway" - 4. Open `AutoPiP.dmg` again - 5. Copy the `.app` to your Applications folder - 6. When trying to open the app, macOS will show a security warning - 7. To bypass this: - - Open System Settings - - Go to Privacy & Security - - Scroll down and click "Open Anyway" - 8. Open `AutoPiP.app` again - 9. Follow the instructions - 10. Allow the requested permissions when prompted - - # ── Push appcast ──────────────────────────────────────────── + + # ── Push appcast ─────────────────────────────────────────── - name: Push appcast to working branch and main env: GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} @@ -332,37 +387,12 @@ jobs: return 0 fi git commit -m "chore: update appcast for ${TAG}" - - set +e - PUSH_ERR=$(git push origin "$branch" 2>&1) - PUSH_RC=$? - set -e - - if [ $PUSH_RC -eq 0 ]; then - echo "βœ… appcast.xml pushed to $branch" - return 0 - fi - - echo "::warning::Direct push to $branch failed β€” $PUSH_ERR" - echo "Creating pull request instead…" - - PR_BRANCH="chore/appcast-${TAG}-${branch}" - git checkout -B "$PR_BRANCH" - git push -f origin "$PR_BRANCH" - gh pr create \ - --base "$branch" \ - --head "$PR_BRANCH" \ - --title "chore: update appcast for ${TAG}" \ - --body "Automated appcast update from CI. Direct push to \`$branch\` was blocked by branch protection. Please merge to complete the release." \ - || echo "::warning::PR may already exist for this appcast update." - - echo "βœ… Appcast PR created for $branch" + git push origin "$branch" + echo "βœ… appcast.xml pushed to $branch" } - # Always push to the triggering branch first push_appcast "$WORKING_BRANCH" - # Also push to main if the triggering branch is not main if [ "$WORKING_BRANCH" != "main" ]; then git fetch origin main push_appcast "main" diff --git a/semver.txt b/semver.txt index 7ec1d6d..6bdc14c 100644 --- a/semver.txt +++ b/semver.txt @@ -1 +1,26 @@ 2.1.0 +--- +- New onboarding flow with feature overview, support links, and version display +- Toggle switches matching Safari toolbar popup styling +- Post-onboarding view shows extension status and update settings +- Safari permission hint during setup +- Cleaned up macOS menu bar + +## 2.0.0 +- Global on/off toggle to temporarily disable AutoPiP without removing the extension +- Configurable keyboard shortcut to manually trigger PiP (default: βŒ₯P) +- Blacklist / Whitelist mode to control which sites AutoPiP is active on +- Updated Sparkle to 2.9.0 + +## 1.0.1 +- Fixed issue where clicking into YouTube live chat would incorrectly activate PiP mode +- Improved focus detection to prevent PiP activation on internal page interactions + +## 1.0 +- Bugfix: Twitch.tv + +## 0.4 +- Added Disney+ support +- Added feature for PiP on YouTube scrolling +- Enhanced control over PiP behavior with new settings for tab switching, window switching, and scrolling on YouTube +- Updated popup interface with additional checkboxes for PiP settings From f7cda2d0cdf14ee36160ad31690d48b35c883d8f Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:53:48 +0200 Subject: [PATCH 04/44] Improve DMG signing process by using temporary key file and handling errors --- .github/workflows/build-release.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 3fc0160..81b2dae 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -234,7 +234,15 @@ jobs: fi echo "Signing DMG with Sparkle EdDSA…" - SIGN_OUTPUT=$(echo "$SPARKLE_PRIVATE_KEY" | "$SIGN_TOOL" "$DMG_PATH" --ed-key-file -) + KEY_FILE="$RUNNER_TEMP/sparkle_ed_key" + echo "$SPARKLE_PRIVATE_KEY" > "$KEY_FILE" + SIGN_OUTPUT=$("$SIGN_TOOL" "$DMG_PATH" --ed-key-file "$KEY_FILE" 2>&1) || { + echo "::error::sign_update failed:" + echo "$SIGN_OUTPUT" + rm -f "$KEY_FILE" + exit 1 + } + rm -f "$KEY_FILE" SIGNATURE=$(echo "$SIGN_OUTPUT" | grep -oE 'sparkle:edSignature="[^"]+"' | cut -d'"' -f2) if [ -z "$SIGNATURE" ]; then echo "::error::Sparkle signing failed β€” no edSignature found in output:" From dbf03e6b67516eb7cd2ffc67bfa743dbbb9c5a6b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:18:31 +0000 Subject: [PATCH 05/44] chore: update appcast for v2.1.0-beta1 --- appcast.xml | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/appcast.xml b/appcast.xml index 4c5e311..89c5234 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,17 +2,29 @@ <rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"> <channel> <title>AutoPiP Updates - - Version 2.1.0 + + Version 2.1.0 beta - Tue, 14 Apr 2026 20:16:38 +0000 - - + + +
  • New onboarding flow with feature overview, support links, and version display
  • +
  • Toggle switches matching Safari toolbar popup styling
  • +
  • Post-onboarding view shows extension status and update settings
  • +
  • Safari permission hint during setup
  • +
  • Cleaned up macOS menu bar
  • + + ]]> +
    + Wed, 15 Apr 2026 10:18:25 +0000 + +
    Version 2.0.0 From e7e6d8b8ba8273499743ed8b756f9433f66467ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:20:16 +0000 Subject: [PATCH 06/44] chore: update appcast for v2.1.0-beta2 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index 89c5234..d1905be 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]> - Wed, 15 Apr 2026 10:18:25 +0000 + Wed, 15 Apr 2026 10:20:10 +0000 From e24f17a0b679e417a0b2904ac8ca2ba2d3d977d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:22:21 +0000 Subject: [PATCH 07/44] chore: update appcast for v2.1.0-beta3 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index d1905be..8c5e1c8 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]>
    - Wed, 15 Apr 2026 10:20:10 +0000 + Wed, 15 Apr 2026 10:22:16 +0000
    From 4db0a50cc36f746f8f8cada9ae0193c34fa2e31a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:23:55 +0000 Subject: [PATCH 08/44] chore: update appcast for v2.1.0-beta4 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index 8c5e1c8..d9a7e35 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]> - Wed, 15 Apr 2026 10:22:16 +0000 + Wed, 15 Apr 2026 10:23:50 +0000 From 31c7a8b504fdb483c55a7f9da14becfa6e90a998 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:25:24 +0000 Subject: [PATCH 09/44] chore: update appcast for v2.1.0-beta5 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index d9a7e35..7d8d43f 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]> - Wed, 15 Apr 2026 10:23:50 +0000 + Wed, 15 Apr 2026 10:25:19 +0000 From 568c8993adad21637a31bf2d5ca3e1a584d04c34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:27:27 +0000 Subject: [PATCH 10/44] chore: update appcast for v2.1.0-beta6 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index 7d8d43f..a2c0ff4 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]> - Wed, 15 Apr 2026 10:25:19 +0000 + Wed, 15 Apr 2026 10:27:22 +0000 From 06fef74b78951e1b2872bf776b2f0f945757d835 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:28:55 +0000 Subject: [PATCH 11/44] chore: update appcast for v2.1.0-beta7 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index a2c0ff4..b68ac9d 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]> - Wed, 15 Apr 2026 10:27:22 +0000 + Wed, 15 Apr 2026 10:28:51 +0000 From 8ed8a61c5988a5a6a00382e5dc3d4e14d871dd91 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:31:17 +0000 Subject: [PATCH 12/44] chore: update appcast for v2.1.0-beta8 --- appcast.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appcast.xml b/appcast.xml index b68ac9d..8c72d42 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,12 +16,12 @@ ]]> - Wed, 15 Apr 2026 10:28:51 +0000 + Wed, 15 Apr 2026 10:31:12 +0000 From 4d32963bc5f5837da009b97a9cb6482d11f62e6d Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:32:28 +0200 Subject: [PATCH 13/44] Add event listeners to handle changes for auto-check, auto-download, and beta options --- AutoPiP/Resources/Script.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AutoPiP/Resources/Script.js b/AutoPiP/Resources/Script.js index 76b9faa..4496e91 100644 --- a/AutoPiP/Resources/Script.js +++ b/AutoPiP/Resources/Script.js @@ -73,6 +73,16 @@ document.addEventListener('DOMContentLoaded', function() { var dl = document.getElementById('onb-auto-download'); dl.disabled = !this.checked; if (!this.checked) dl.checked = false; + webkit.messageHandlers.controller.postMessage("set-auto-check:" + this.checked); + if (!this.checked) webkit.messageHandlers.controller.postMessage("set-auto-download:false"); + }); + + document.getElementById('onb-auto-download').addEventListener('change', function() { + webkit.messageHandlers.controller.postMessage("set-auto-download:" + this.checked); + }); + + document.getElementById('onb-beta').addEventListener('change', function() { + webkit.messageHandlers.controller.postMessage("set-beta:" + this.checked); }); document.getElementById('onb-check-updates').addEventListener('click', function() { From 14df1f912147f349eaa247532e332316188e0703 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:32:34 +0200 Subject: [PATCH 14/44] Update BUILD.md with enhanced release workflow and semver.txt format details --- BUILD.md | 62 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/BUILD.md b/BUILD.md index efd26e2..dc47385 100644 --- a/BUILD.md +++ b/BUILD.md @@ -12,31 +12,59 @@ Then build and run the `AutoPiP` scheme in Xcode. ## How to Release -AutoPiP is built and released automatically via GitHub Actions. The version in **`semver.txt`** is the single source of truth. +AutoPiP is built and released automatically via GitHub Actions. **`semver.txt`** is the single source of truth for version and changelog. + +### semver.txt Format + +``` +2.1.0 +--- +- First changelog entry +- Second changelog entry + +## 2.0.0 +- Previous release notes +``` + +- **Line 1:** Current version (valid [semver](https://semver.org/)) +- **`---`:** Separator +- **Lines after `---`:** Changelog for the current version (Markdown list items, optional `### ` headings) +- **`## x.y.z`:** Previous release changelogs (preserved history) + +### Workflow 1. Create a `feature/*` branch from `main` -2. Update the version in `semver.txt` (must be valid [semver](https://semver.org/), e.g. `2.1.0`) -3. Commit and push: - - **Push to `feature/*`** β†’ beta release (`v2.1.0-beta`), published to Sparkle beta channel - - **Merge to `main`** β†’ stable release (`v2.1.0`), published to Sparkle +2. Set the version and changelog in `semver.txt` +3. Push β€” every push to `feature/*` creates a new beta release: + - First push β†’ `v2.1.0-beta1` + - Second push β†’ `v2.1.0-beta2` (auto-incremented from existing tags) + - Beta releases are never overwritten +4. Merge to `main` when ready β€” creates stable release `v2.1.0` + - On `main`, the workflow only runs when `semver.txt` changes The workflow automatically: - Sets the version in the Xcode project and `manifest.json` -- Builds and archives the app -- Creates a DMG with drag-to-Applications installer -- Signs the DMG with Sparkle EdDSA (if configured) -- Creates a git tag and GitHub Release with `AutoPiP.dmg` -- Updates `appcast.xml` on both the triggering branch and `main` for Sparkle auto-updates +- Builds, archives, and creates a DMG +- Signs the DMG with the code signing certificate and Sparkle EdDSA +- Creates a git tag and GitHub Release with changelog + installation instructions +- Updates `appcast.xml` (with changelog as ``) on the working branch and `main` -You can also trigger a build manually from the **Actions** tab using "Run workflow". +### GitHub App for Branch Protection + +If `main` has branch protection, the workflow needs a GitHub App to push the appcast update. Create a GitHub App with **Contents: Read & Write** permission, install it on the repo, then: +- Add variable `CLienT_ID` with the App's client ID +- Add secret `APP_PRIVATE_KEY` with the App's private key +- Add the App to the branch protection bypass list ## Repository Secrets -| Secret | Required | Description | -|--------|----------|-------------| -| `BUILD_CERTIFICATE_BASE64` | Optional | Base64-encoded `.p12` signing certificate | -| `P12_PASSWORD` | Optional | Password for the `.p12` file | -| `SPARKLE_PRIVATE_KEY` | Optional | EdDSA private key for Sparkle update signatures | +| Secret / Variable | Required | Description | +|-------------------|----------|-------------| +| `BUILD_CERTIFICATE_BASE64` | **Yes** | Base64-encoded `.p12` signing certificate | +| `P12_PASSWORD` | **Yes** | Password for the `.p12` file | +| `SPARKLE_PRIVATE_KEY` | **Yes** | EdDSA private key for Sparkle update signatures | +| `CLIENT_ID` (variable) | If branch-protected | GitHub App Client ID for pushing to protected branches | +| `APP_PRIVATE_KEY` | If branch-protected | GitHub App private key | **Export your signing certificate:** @@ -46,5 +74,3 @@ base64 -i certificate.p12 | pbcopy ``` **Export your Sparkle key:** The EdDSA private key generated by Sparkle's `generate_keys` tool. Store it as the `SPARKLE_PRIVATE_KEY` secret. - -Without `BUILD_CERTIFICATE_BASE64`, the app is built with ad-hoc signing. Without `SPARKLE_PRIVATE_KEY`, appcast entries will have no EdDSA signature. From 9195c004db607f9bb9410338a3efade1e8fbe91a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 10:34:06 +0000 Subject: [PATCH 15/44] chore: update appcast for v2.1.0-beta9 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index 8c72d42..2935b18 100755 --- a/appcast.xml +++ b/appcast.xml @@ -16,13 +16,13 @@ ]]> - Wed, 15 Apr 2026 10:31:12 +0000 + Wed, 15 Apr 2026 10:34:02 +0000
    From 774165c9362393719fd6098127ea42c7e92d82cd Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:54:18 +0200 Subject: [PATCH 16/44] Skip builds for commits authored by the GitHub Actions bot --- .github/workflows/build-release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 81b2dae..8b90fe2 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -22,6 +22,14 @@ jobs: - name: Decide whether to build id: check run: | + # Never build on commits pushed by the bot itself (e.g. appcast updates) + COMMITTER="${{ github.event.head_commit.author.name }}" + if [ "$COMMITTER" = "github-actions[bot]" ]; then + echo "should-build=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping β€” commit authored by github-actions[bot]" + exit 0 + fi + if [ "${{ github.ref_name }}" != "main" ]; then echo "should-build=true" >> "$GITHUB_OUTPUT" echo "Feature branch β€” always build" From 141d5c8e0b30b5b0a7fac454cdcc58cb3c90b34e Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:58:36 +0200 Subject: [PATCH 17/44] Add concurrency settings to build-release workflow --- .github/workflows/build-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 8b90fe2..4ec4ae4 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -8,6 +8,10 @@ on: permissions: contents: write +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + jobs: # ── Gate: on main only build when semver.txt changed ───────── check: From 95a58eea8db4cc43577f6a6f21882dd2103a4248 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:58:47 +0200 Subject: [PATCH 18/44] Remove PiP event listener attachment logic from video elements (not a bug) --- AutoPiP Extension/Resources/content.js | 54 -------------------------- 1 file changed, 54 deletions(-) diff --git a/AutoPiP Extension/Resources/content.js b/AutoPiP Extension/Resources/content.js index eadd0d2..aa520e9 100644 --- a/AutoPiP Extension/Resources/content.js +++ b/AutoPiP Extension/Resources/content.js @@ -447,59 +447,6 @@ new MutationObserver(() => { childList: true }); -// Track which video elements already have PiP event listeners attached -const pipListenerVideos = new WeakSet(); - -/** - * Attach event listeners to a video element to resume playback when the - * PiP window is closed via the native macOS close button. - * @param {HTMLVideoElement} video - Video element to attach listeners to - */ -function attachPiPEventListeners(video) { - if (pipListenerVideos.has(video)) return; - - let userPausedInPiP = false; - - // Track if the user explicitly paused the video while in PiP mode - video.addEventListener('pause', () => { - if (isPiPActive(video)) { - userPausedInPiP = true; - debugLog('User paused video while in PiP mode'); - } - }); - - // Clear the flag when the user resumes playback while still in PiP mode - video.addEventListener('play', () => { - if (isPiPActive(video)) { - userPausedInPiP = false; - debugLog('User resumed video while in PiP mode'); - } - }); - - // When PiP is exited (e.g. user clicks the native close button on the PiP - // window), WebKit pauses the video as a side effect. Resume playback - // automatically unless the user intentionally paused the video first. - // Note: JavaScript's single-threaded event loop ensures pause/play/mode-change - // events are processed sequentially, so userPausedInPiP is always consistent. - video.addEventListener('webkitpresentationmodechanged', () => { - debugLog('Presentation mode changed to:', video.webkitPresentationMode); - - if (video.webkitPresentationMode !== 'picture-in-picture') { - if (video.paused && !userPausedInPiP) { - debugLog('PiP window closed, resuming playback'); - video.play().catch(error => { - console.error('[AutoPiP] Failed to resume playback after PiP close:', error); - }); - } - } - // Reset on both enter and exit so subsequent PiP sessions start clean - userPausedInPiP = false; - }); - - pipListenerVideos.add(video); - debugLog('PiP event listeners attached to video element'); -} - // Video selector caching with invalidation let cachedVideo = null; let cacheTimestamp = 0; @@ -526,7 +473,6 @@ function getVideo() { if (video) { cachedVideo = video; cacheTimestamp = now; - attachPiPEventListeners(video); return video; } } From 8779afaaef48c7d8bad5474346592a2639217ec3 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:08:24 +0200 Subject: [PATCH 19/44] Update semver.txt with new onboarding flow and automated build details --- semver.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/semver.txt b/semver.txt index 6bdc14c..c391225 100644 --- a/semver.txt +++ b/semver.txt @@ -1,10 +1,12 @@ 2.1.0 --- -- New onboarding flow with feature overview, support links, and version display -- Toggle switches matching Safari toolbar popup styling -- Post-onboarding view shows extension status and update settings -- Safari permission hint during setup -- Cleaned up macOS menu bar +### What's New +- 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch +- βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup + +### Under the Hood +- πŸ”§ Builds and releases are now fully automated via GitHub Actions +- πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release ## 2.0.0 - Global on/off toggle to temporarily disable AutoPiP without removing the extension From 0dae9d4ca45522db37f475a9dfd429c4e21e5283 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 11:10:34 +0000 Subject: [PATCH 20/44] chore: update appcast for v2.1.0-beta10 --- appcast.xml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/appcast.xml b/appcast.xml index 2935b18..62e1ef9 100755 --- a/appcast.xml +++ b/appcast.xml @@ -8,21 +8,22 @@ -
  • New onboarding flow with feature overview, support links, and version display
  • -
  • Toggle switches matching Safari toolbar popup styling
  • -
  • Post-onboarding view shows extension status and update settings
  • -
  • Safari permission hint during setup
  • -
  • Cleaned up macOS menu bar
  • +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    ]]>
    - Wed, 15 Apr 2026 10:34:02 +0000 + Wed, 15 Apr 2026 11:10:29 +0000
    From 913356332579a2e35dc5f8a9fca898150751a4f9 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:16:52 +0200 Subject: [PATCH 21/44] Move concurrency settings to the main build job --- .github/workflows/build-release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 4ec4ae4..82020f1 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -8,10 +8,6 @@ on: permissions: contents: write -concurrency: - group: build-${{ github.ref }} - cancel-in-progress: true - jobs: # ── Gate: on main only build when semver.txt changed ───────── check: @@ -60,6 +56,9 @@ jobs: needs: check if: needs.check.outputs.should-build == 'true' runs-on: macos-15 + concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true steps: # ── Setup ────────────────────────────────────────────────── From 35916368a9bda31c3505bfb65892b4ab75fb5570 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 11:19:13 +0000 Subject: [PATCH 22/44] chore: update appcast for v2.1.0-beta11 --- appcast.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appcast.xml b/appcast.xml index 62e1ef9..277d80f 100755 --- a/appcast.xml +++ b/appcast.xml @@ -17,13 +17,13 @@ ]]> - Wed, 15 Apr 2026 11:10:29 +0000 + Wed, 15 Apr 2026 11:19:08 +0000 From b039309a6256b5b9ab247677c91822689bf8c598 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:24:28 +0200 Subject: [PATCH 23/44] Add beta release notices to appcast and release notes --- .github/workflows/build-release.yml | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 82020f1..6d6a6fb 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -266,16 +266,26 @@ jobs: # ── Build appcast XML ── { echo ' ' - echo " Version ${VERSION}" if [ "$CHANNEL" = "beta" ]; then + echo " Version ${VERSION} (Beta)" echo ' beta' + else + echo " Version ${VERSION}" fi # Description from changelog if [ -s "$RUNNER_TEMP/changelog.txt" ]; then echo ' ' echo ' ' + echo ' ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early.' + echo '

    ' + fi + echo '
      ' while IFS= read -r line; do case "$line" in @@ -332,7 +342,18 @@ jobs: run: | NOTES="$RUNNER_TEMP/release_notes.md" - # Prepend changelog if available + # Prepend beta notice for pre-releases + if [ "${{ steps.version.outputs.prerelease }}" = "true" ]; then + { + echo "> [!WARNING]" + echo "> **This is a beta release.** It may be unstable or incomplete. Only install if you want to test new features early." + echo "" + } > "$NOTES" + else + : > "$NOTES" + fi + + # Append changelog if available if [ -s "$RUNNER_TEMP/changelog.txt" ]; then { echo "## Changelog" @@ -341,9 +362,7 @@ jobs: echo "" echo "---" echo "" - } > "$NOTES" - else - : > "$NOTES" + } >> "$NOTES" fi cat >> "$NOTES" <<'INSTALL_EOF' From fd8482d469c06b32b590c9cb7776eb3de255cc2d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 11:26:21 +0000 Subject: [PATCH 24/44] chore: update appcast for v2.1.0-beta12 --- appcast.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/appcast.xml b/appcast.xml index 277d80f..026923d 100755 --- a/appcast.xml +++ b/appcast.xml @@ -3,10 +3,13 @@ AutoPiP Updates - Version 2.1.0 + Version 2.1.0 (Beta) beta + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

      What's New

      • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
      • @@ -17,13 +20,13 @@
      ]]>
      - Wed, 15 Apr 2026 11:19:08 +0000 + Wed, 15 Apr 2026 11:26:15 +0000
      From f0abe17f0c3c31ecb4f4d5ed239e1ae7c06f0ef8 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:29:01 +0200 Subject: [PATCH 25/44] Update display version for beta releases in appcast --- .github/workflows/build-release.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 6d6a6fb..72cad37 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -300,12 +300,18 @@ jobs: DOWNLOAD_URL="https://github.com/vordenken/AutoPiP/releases/download/${TAG}/AutoPiP.dmg" PUB_DATE=$(date -u '+%a, %d %b %Y %H:%M:%S %z') + # For betas, use the full tag (e.g. "2.1.0-beta12") as display version + if [ "$CHANNEL" = "beta" ]; then + DISPLAY_VERSION="${TAG#v}" + else + DISPLAY_VERSION="${VERSION}" + fi echo " ${PUB_DATE}" echo ' ' echo ' ' From 559dca85b4eaaf55584110faaaa5121ca351c062 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 11:30:56 +0000 Subject: [PATCH 26/44] chore: update appcast for v2.1.0-beta13 --- appcast.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/appcast.xml b/appcast.xml index 026923d..5688879 100755 --- a/appcast.xml +++ b/appcast.xml @@ -20,13 +20,13 @@
    ]]>
    - Wed, 15 Apr 2026 11:26:15 +0000 + Wed, 15 Apr 2026 11:30:51 +0000
    From 40163a752a933e75c209167725a7d0cdf4726b70 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:29:23 +0200 Subject: [PATCH 27/44] Implement fallback for requestPictureInPicture API in togglePiP function --- AutoPiP Extension/Resources/content.js | 27 +++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/AutoPiP Extension/Resources/content.js b/AutoPiP Extension/Resources/content.js index aa520e9..1ea5c6d 100644 --- a/AutoPiP Extension/Resources/content.js +++ b/AutoPiP Extension/Resources/content.js @@ -574,12 +574,29 @@ function togglePiP() { disablePiP(); } else { debugLog('Keyboard shortcut: enabling PiP (manual override)'); - try { - if (!setWebkitPresentationMode(video, 'picture-in-picture')) { - debugLog('PiP not supported on this video element'); + // Prefer the W3C requestPictureInPicture API here: unlike webkitSetPresentationMode, + // it uses the video's natural (intrinsic) dimensions rather than its CSS-rendered size. + // This prevents the "video small in corner" bug when e.g. YouTube's mini-player is + // active and the video element is currently rendered at a small size. + if (typeof video.requestPictureInPicture === 'function') { + video.requestPictureInPicture().catch(error => { + debugLog('requestPictureInPicture failed, falling back to webkit API:', error.message); + try { + if (!setWebkitPresentationMode(video, 'picture-in-picture')) { + debugLog('PiP not supported on this video element'); + } + } catch (e) { + console.error('[AutoPiP] Keyboard PiP toggle exception:', e.message || e); + } + }); + } else { + try { + if (!setWebkitPresentationMode(video, 'picture-in-picture')) { + debugLog('PiP not supported on this video element'); + } + } catch (error) { + console.error('[AutoPiP] Keyboard PiP toggle exception:', error.message || error); } - } catch (error) { - console.error('[AutoPiP] Keyboard PiP toggle exception:', error.message || error); } } } From 92d9acb9fa072e907fbe664e26f9906ce679c03c Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:29:29 +0200 Subject: [PATCH 28/44] Update Sparkle dependency to version 2.9.3 --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AutoPiP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoPiP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6e450d9..190626a 100644 --- a/AutoPiP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoPiP.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/sparkle-project/Sparkle", "state" : { - "revision" : "21d8df80440b1ca3b65fa82e40782f1e5a9e6ba2", - "version" : "2.9.0" + "revision" : "d46d456107feacc80711b21847b82b07bd9fb46e", + "version" : "2.9.3" } } ], From 1fb2e6f16707119f340828c5d8044da57ff43b96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 19:31:18 +0000 Subject: [PATCH 29/44] chore: update appcast for v2.1.0-beta14 --- appcast.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/appcast.xml b/appcast.xml index 5688879..fd3d2ad 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,33 @@ AutoPiP Updates + + Version 2.1.0 (Beta) + beta + + + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

    +
      +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    • +
    + ]]> +
    + Tue, 16 Jun 2026 19:31:11 +0000 + +
    Version 2.1.0 (Beta) beta From a98fc6355d890fe1c89a9cd51efe3e94ebb1c92c Mon Sep 17 00:00:00 2001 From: JP <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:40:11 +0200 Subject: [PATCH 30/44] Update BUILD.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index dc47385..6e7d406 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,7 +46,7 @@ The workflow automatically: - Sets the version in the Xcode project and `manifest.json` - Builds, archives, and creates a DMG - Signs the DMG with the code signing certificate and Sparkle EdDSA -- Creates a git tag and GitHub Release with changelog + installation instructions +- Creates a Git tag and GitHub Release with changelog + installation instructions - Updates `appcast.xml` (with changelog as ``) on the working branch and `main` ### GitHub App for Branch Protection From 3cf7be88a9a5436069261a24e272721504b48d4a Mon Sep 17 00:00:00 2001 From: JP <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:40:22 +0200 Subject: [PATCH 31/44] Update BUILD.md Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 6e7d406..edd6d56 100644 --- a/BUILD.md +++ b/BUILD.md @@ -52,7 +52,7 @@ The workflow automatically: ### GitHub App for Branch Protection If `main` has branch protection, the workflow needs a GitHub App to push the appcast update. Create a GitHub App with **Contents: Read & Write** permission, install it on the repo, then: -- Add variable `CLienT_ID` with the App's client ID +- Add variable `CLIENT_ID` with the App's client ID - Add secret `APP_PRIVATE_KEY` with the App's private key - Add the App to the branch protection bypass list From 2c2b27365c490c62293d786a97addf50bcbfcc34 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:45:58 +0200 Subject: [PATCH 32/44] Ignore specific paths in build workflow to optimize trigger conditions --- .github/workflows/build-release.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 72cad37..3d8838e 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -3,6 +3,19 @@ name: Build and Release on: push: branches: [main, 'feature/*'] + paths-ignore: + # Skip the workflow entirely when only docs/repo-meta files changed. + # Source, semver.txt, appcast.xml and this workflow itself still trigger a build. + - '**.md' + - 'LICENSE' + - 'PRIVACY.md' + - 'SECURITY.md' + - 'renovate.json' + - '.gitignore' + - '.github/ISSUE_TEMPLATE/**' + - '.github/pull_request_template.md' + - '.github/CODEOWNERS' + - '.github/dependabot.yml' workflow_dispatch: permissions: From 81921bbf086a63c210e46ff665ad39f8b4a89e9d Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:55:04 +0200 Subject: [PATCH 33/44] Refactor update handling to ensure automatic downloads are disabled when checks are turned off --- AutoPiP/UpdateController.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/AutoPiP/UpdateController.swift b/AutoPiP/UpdateController.swift index 302497e..f4800e0 100644 --- a/AutoPiP/UpdateController.swift +++ b/AutoPiP/UpdateController.swift @@ -29,12 +29,18 @@ class UpdateController { var automaticallyChecksForUpdates: Bool { get { updater.automaticallyChecksForUpdates } - set { updater.automaticallyChecksForUpdates = newValue } + set { + updater.automaticallyChecksForUpdates = newValue + if !newValue { updater.automaticallyDownloadsUpdates = false } + } } - + var automaticallyDownloadsUpdates: Bool { get { updater.automaticallyDownloadsUpdates } - set { updater.automaticallyDownloadsUpdates = newValue } + set { + if newValue { updater.automaticallyChecksForUpdates = true } + updater.automaticallyDownloadsUpdates = newValue + } } var isBetaUpdatesEnabled: Bool { From 7f917355e89e832edb83d23cca431c13a7d112ac Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 21:55:11 +0200 Subject: [PATCH 34/44] Update auto-download toggle behavior to ensure it is unchecked and disabled when auto-check is turned off --- AutoPiP/Resources/Script.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AutoPiP/Resources/Script.js b/AutoPiP/Resources/Script.js index 4496e91..83a23ea 100644 --- a/AutoPiP/Resources/Script.js +++ b/AutoPiP/Resources/Script.js @@ -119,7 +119,10 @@ document.addEventListener('DOMContentLoaded', function() { webkit.messageHandlers.controller.postMessage("set-auto-check:" + this.checked); var dl = document.getElementById('auto-download-toggle'); dl.disabled = !this.checked; - if (!this.checked) dl.checked = false; + if (!this.checked) { + dl.checked = false; + webkit.messageHandlers.controller.postMessage("set-auto-download:false"); + } }); document.getElementById('auto-download-toggle').addEventListener('change', function() { From fb2b306eae79cac70a3f897817f3e65c85a80f5c Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:08:36 +0200 Subject: [PATCH 35/44] fix blank issues in issue template configuration --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 6d3dc84..3ba13e0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1 @@ -blank_issues: false +blank_issues_enabled: false From 0584c9a9fea52c910036ba4d6aff702121c93c24 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:08:45 +0200 Subject: [PATCH 36/44] Refactor build conditions to improve handling of bot-triggered commits and enhance versioning logic --- .github/workflows/build-release.yml | 42 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 3d8838e..6ec8ff8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -34,29 +34,33 @@ jobs: - name: Decide whether to build id: check + env: + ACTOR: ${{ github.actor }} + REF_NAME: ${{ github.ref_name }} + BEFORE: ${{ github.event.before }} + HEAD_SHA: ${{ github.sha }} run: | - # Never build on commits pushed by the bot itself (e.g. appcast updates) - COMMITTER="${{ github.event.head_commit.author.name }}" - if [ "$COMMITTER" = "github-actions[bot]" ]; then + # Never build when triggered by the bot itself (e.g. appcast updates). + # Use github.actor β€” github.event.head_commit.author.name is user-spoofable. + if [ "$ACTOR" = "github-actions[bot]" ]; then echo "should-build=false" >> "$GITHUB_OUTPUT" - echo "::notice::Skipping β€” commit authored by github-actions[bot]" + echo "::notice::Skipping β€” actor is github-actions[bot]" exit 0 fi - if [ "${{ github.ref_name }}" != "main" ]; then + if [ "$REF_NAME" != "main" ]; then echo "should-build=true" >> "$GITHUB_OUTPUT" echo "Feature branch β€” always build" exit 0 fi - BEFORE="${{ github.event.before }}" if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then echo "should-build=true" >> "$GITHUB_OUTPUT" echo "New branch push β€” build" exit 0 fi - if git diff --name-only "$BEFORE" "${{ github.sha }}" | grep -q '^semver\.txt$'; then + if git diff --name-only "$BEFORE" "$HEAD_SHA" | grep -q '^semver\.txt$'; then echo "should-build=true" >> "$GITHUB_OUTPUT" echo "semver.txt changed on main β€” build" else @@ -92,6 +96,8 @@ jobs: # ── Version & Changelog ──────────────────────────────────── - name: Read version and changelog from semver.txt id: version + env: + REF_NAME: ${{ github.ref_name }} run: | VERSION=$(head -1 semver.txt | tr -d '[:space:]') if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then @@ -103,7 +109,7 @@ jobs: awk '/^---$/{found=1; next} /^## /{exit} found{print}' semver.txt \ | sed '/^[[:space:]]*$/d' > "$RUNNER_TEMP/changelog.txt" - if [ "${{ github.ref_name }}" = "main" ]; then + if [ "$REF_NAME" = "main" ]; then TAG="v${VERSION}" PRERELEASE=false CHANNEL=stable @@ -138,8 +144,10 @@ jobs: run: | TAG="${{ steps.version.outputs.tag }}" if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then - echo "::error::Tag ${TAG} already exists. For betas, numbers auto-increment β€” if this is unexpected, check existing tags with: git tag -l 'v*'" - exit 1 + echo "::warning::Tag ${TAG} already exists β€” assuming partial release, will reconcile (re-upload asset, refresh appcast)." + echo "tag_exists=true" >> "$GITHUB_OUTPUT" + else + echo "tag_exists=false" >> "$GITHUB_OUTPUT" fi # ── Signing ──────────────────────────────────────────────── @@ -171,9 +179,11 @@ jobs: # ── Build ────────────────────────────────────────────────── - name: Set version in Xcode project + env: + VERSION: ${{ steps.version.outputs.version }} + BUILD: ${{ github.run_number }} run: | - VERSION="${{ steps.version.outputs.version }}" - BUILD=$(( ${{ github.run_number }} + 10 )) + BUILD=$(( BUILD + 10 )) sed -i '' "s/MARKETING_VERSION = .*;/MARKETING_VERSION = ${VERSION};/" \ AutoPiP.xcodeproj/project.pbxproj @@ -228,9 +238,10 @@ jobs: - name: Sign DMG and prepare Sparkle appcast env: SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} + VERSION: ${{ steps.version.outputs.version }} + BUILD: ${{ github.run_number }} run: | - VERSION="${{ steps.version.outputs.version }}" - BUILD=$(( ${{ github.run_number }} + 10 )) + BUILD=$(( BUILD + 10 )) TAG="${{ steps.version.outputs.tag }}" CHANNEL="${{ steps.version.outputs.channel }}" DMG_PATH="$RUNNER_TEMP/AutoPiP.dmg" @@ -423,11 +434,10 @@ jobs: - name: Push appcast to working branch and main env: GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} + WORKING_BRANCH: ${{ github.ref_name }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - WORKING_BRANCH="${{ github.ref_name }}" TAG="${{ steps.version.outputs.tag }}" # Save updated appcast From 38510ba0bacc519af2f6a357dcb1aac0091abd04 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:09:32 +0200 Subject: [PATCH 37/44] Move permissions declaration to the build job for clarity --- .github/workflows/build-release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 6ec8ff8..fd44185 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -18,9 +18,6 @@ on: - '.github/dependabot.yml' workflow_dispatch: -permissions: - contents: write - jobs: # ── Gate: on main only build when semver.txt changed ───────── check: @@ -73,6 +70,8 @@ jobs: needs: check if: needs.check.outputs.should-build == 'true' runs-on: macos-15 + permissions: + contents: write concurrency: group: build-${{ github.ref }} cancel-in-progress: true From 26861b1a0e0131cf99b027f7780ee7a0ac994c79 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 20:11:23 +0000 Subject: [PATCH 38/44] chore: update appcast for v2.1.0-beta15 --- appcast.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/appcast.xml b/appcast.xml index fd3d2ad..c4a3fef 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,33 @@ AutoPiP Updates + + Version 2.1.0 (Beta) + beta + + + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

    +
      +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    • +
    + ]]> +
    + Tue, 16 Jun 2026 20:11:17 +0000 + +
    Version 2.1.0 (Beta) beta From 16caaa285ec6ed4a06ef8c1f9e65c7bbe5ce9b7a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 20:13:26 +0000 Subject: [PATCH 39/44] chore: update appcast for v2.1.0-beta16 --- appcast.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/appcast.xml b/appcast.xml index c4a3fef..8b5b965 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,33 @@ AutoPiP Updates + + Version 2.1.0 (Beta) + beta + + + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

    +
      +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    • +
    + ]]> +
    + Tue, 16 Jun 2026 20:13:20 +0000 + +
    Version 2.1.0 (Beta) beta From f89f1f0ddf9a800dbbd485c8883ff1b56ed557cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 20:15:16 +0000 Subject: [PATCH 40/44] chore: update appcast for v2.1.0-beta17 --- appcast.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/appcast.xml b/appcast.xml index 8b5b965..ced57d9 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,33 @@ AutoPiP Updates + + Version 2.1.0 (Beta) + beta + + + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

    +
      +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    • +
    + ]]> +
    + Tue, 16 Jun 2026 20:15:10 +0000 + +
    Version 2.1.0 (Beta) beta From c3d1d820faf64b09e7668a40c18ec37a6abd92fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 20:16:57 +0000 Subject: [PATCH 41/44] chore: update appcast for v2.1.0-beta18 --- appcast.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/appcast.xml b/appcast.xml index ced57d9..f1387d5 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,33 @@ AutoPiP Updates + + Version 2.1.0 (Beta) + beta + + + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

    +
      +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    • +
    + ]]> +
    + Tue, 16 Jun 2026 20:16:51 +0000 + +
    Version 2.1.0 (Beta) beta From 1dbb36730d53da95555b56beb3c2a226ab9b9187 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:18:15 +0200 Subject: [PATCH 42/44] Refactor workflow to improve bot-triggered build handling and clarify paths-ignore comments --- .github/workflows/build-release.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index fd44185..da1fd23 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -4,8 +4,8 @@ on: push: branches: [main, 'feature/*'] paths-ignore: - # Skip the workflow entirely when only docs/repo-meta files changed. - # Source, semver.txt, appcast.xml and this workflow itself still trigger a build. + # Skip the workflow entirely when only docs/repo-meta/appcast files changed. + # Source, semver.txt and this workflow itself still trigger a build. - '**.md' - 'LICENSE' - 'PRIVACY.md' @@ -16,6 +16,7 @@ on: - '.github/pull_request_template.md' - '.github/CODEOWNERS' - '.github/dependabot.yml' + - 'appcast.xml' workflow_dispatch: jobs: @@ -39,11 +40,14 @@ jobs: run: | # Never build when triggered by the bot itself (e.g. appcast updates). # Use github.actor β€” github.event.head_commit.author.name is user-spoofable. - if [ "$ACTOR" = "github-actions[bot]" ]; then - echo "should-build=false" >> "$GITHUB_OUTPUT" - echo "::notice::Skipping β€” actor is github-actions[bot]" - exit 0 - fi + # The appcast push is also excluded via paths-ignore at the trigger level. + case "$ACTOR" in + "github-actions[bot]"|"autopip-ci-bot"|*[Bb]ot) + echo "should-build=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping β€” actor is $ACTOR (bot)" + exit 0 + ;; + esac if [ "$REF_NAME" != "main" ]; then echo "should-build=true" >> "$GITHUB_OUTPUT" From 44f7d29ed7ecd86cd8e734abc0a909833f7aff3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 20:20:14 +0000 Subject: [PATCH 43/44] chore: update appcast for v2.1.0-beta19 --- appcast.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/appcast.xml b/appcast.xml index f1387d5..17ee56a 100755 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,33 @@ AutoPiP Updates + + Version 2.1.0 (Beta) + beta + + + ⚠️ Beta Release β€” This version may be unstable. Only install if you want to test new features early. +

    +
      +

    What's New

      +
    • 🎨 New onboarding flow β€” guides through extension setup and update settings on first launch
    • +
    • βš™οΈ Update preferences (auto-check, auto-download, beta channel) now configurable during setup
    • +

    Under the Hood

      +
    • πŸ”§ Builds and releases are now fully automated via GitHub Actions
    • +
    • πŸ§ͺ Beta release channel β€” opt in to test upcoming features before they reach the stable release
    • +
    + ]]> +
    + Tue, 16 Jun 2026 20:20:09 +0000 + +
    Version 2.1.0 (Beta) beta From 5a58d08f5c8a4cfff8ba1e304b7280db17e75588 Mon Sep 17 00:00:00 2001 From: vordenken <62395860+vordenken@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:21:16 +0200 Subject: [PATCH 44/44] Update paths-ignore to simplify workflow triggers --- .github/workflows/build-release.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index da1fd23..88546b9 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -12,10 +12,7 @@ on: - 'SECURITY.md' - 'renovate.json' - '.gitignore' - - '.github/ISSUE_TEMPLATE/**' - - '.github/pull_request_template.md' - - '.github/CODEOWNERS' - - '.github/dependabot.yml' + - '.github/**' - 'appcast.xml' workflow_dispatch: