Welcome to AutoPiP
+Automatically enables Picture-in-Picture when you switch tabs or windows in Safari.
+ +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..3ba13e0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: 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..88546b9
--- /dev/null
+++ b/.github/workflows/build-release.yml
@@ -0,0 +1,474 @@
+name: Build and Release
+
+on:
+ push:
+ branches: [main, 'feature/*']
+ paths-ignore:
+ # 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'
+ - 'SECURITY.md'
+ - 'renovate.json'
+ - '.gitignore'
+ - '.github/**'
+ - 'appcast.xml'
+ workflow_dispatch:
+
+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
+ env:
+ ACTOR: ${{ github.actor }}
+ REF_NAME: ${{ github.ref_name }}
+ BEFORE: ${{ github.event.before }}
+ HEAD_SHA: ${{ github.sha }}
+ 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.
+ # 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"
+ echo "Feature branch β always build"
+ exit 0
+ fi
+
+ if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
+ echo "should-build=true" >> "$GITHUB_OUTPUT"
+ echo "New branch push β build"
+ exit 0
+ fi
+
+ 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
+ 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
+ permissions:
+ contents: write
+ concurrency:
+ group: build-${{ github.ref }}
+ cancel-in-progress: true
+
+ steps:
+ # ββ Setup ββββββββββββββββββββββββββββββββββββββββββββββββββ
+ - name: Generate GitHub App token
+ id: app-token
+ if: vars.CLIENT_ID != ''
+ uses: actions/create-github-app-token@v3
+ with:
+ client-id: ${{ vars.CLIENT_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 }}
+
+ # ββ 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
+ 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 [ "$REF_NAME" = "main" ]; then
+ TAG="v${VERSION}"
+ PRERELEASE=false
+ CHANNEL=stable
+ else
+ # 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 "prerelease=$PRERELEASE" >> "$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
+ run: |
+ TAG="${{ steps.version.outputs.tag }}"
+ if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then
+ 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 ββββββββββββββββββββββββββββββββββββββββββββββββ
+ - 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 "::error::BUILD_CERTIFICATE_BASE64 secret is not set β code signing is required."
+ exit 1
+ 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 "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
+
+ # ββ Build ββββββββββββββββββββββββββββββββββββββββββββββββββ
+ - name: Set version in Xcode project
+ env:
+ VERSION: ${{ steps.version.outputs.version }}
+ BUILD: ${{ github.run_number }}
+ run: |
+ BUILD=$(( BUILD + 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"
+
+ xcodebuild archive \
+ -project AutoPiP.xcodeproj \
+ -scheme AutoPiP \
+ -configuration Release \
+ -archivePath "$ARCHIVE_PATH"
+
+ - 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 }}
+ VERSION: ${{ steps.version.outputs.version }}
+ BUILD: ${{ github.run_number }}
+ run: |
+ BUILD=$(( BUILD + 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 (required) ββ
+ if [ -z "$SPARKLE_PRIVATE_KEY" ]; then
+ echo "::error::SPARKLE_PRIVATE_KEY secret is not set β Sparkle signing is required."
+ exit 1
+ fi
+
+ 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β¦"
+ 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:"
+ echo "$SIGN_OUTPUT"
+ exit 1
+ fi
+ SIG_ATTR=$'\n'" sparkle:edSignature=\"${SIGNATURE}\""
+ echo "β
Sparkle EdDSA signature generated"
+
+ # ββ Build appcast
You can turn on AutoPiPβs extension in Safari Extensions preferences.
-AutoPiPβs extension is currently on. You can turn it off in Safari Extensions preferences.
-AutoPiPβs extension is currently off. You can turn it on in Safari Extensions preferences.
- + + + + + + + + + + + + +