Skip to content

Portable Preferences PoC #27

Portable Preferences PoC

Portable Preferences PoC #27

name: Portable Preferences PoC
on:
workflow_dispatch:
permissions:
contents: read
jobs:
poc:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-14, macos-15, macos-26]
steps:
## Environment setup
- uses: actions/checkout@v6
- name: Setup OS for VO automation
uses: guidepup/setup-action@0.21.0
## Example global defaults
- name: Disable Splash
run: |
defaults write com.apple.VoiceOverTraining doNotShowSplashScreen -bool true || true
- name: Enable AppleScript Control
run: |
defaults write com.apple.VoiceOver4/default SCREnableAppleScript -bool true || true
- name: Set Default Greeting
run: |
defaults write com.apple.VoiceOver4/default loginGreeting -string "Default Greeting" || true
- name: Open & Close VoiceOver VoiceOver Utility
run: |
mkdir -p artifacts || true
/usr/bin/osascript start-vo-utility.applescript || true
sleep 5
screencapture artifacts/vo-utility-pre-portable.png || true
/usr/bin/osascript stop-vo-utility.applescript || true
- name: Start & Stop VoiceOver
run: |
/System/Library/CoreServices/VoiceOver.app/Contents/MacOS/VoiceOverStarter &
sleep 5
mkdir -p artifacts || true
screencapture artifacts/started.png || true
kill -15 $(ps aux | egrep "[V]oiceOver.app/Contents/MacOS/VoiceOver launchd -s" | awk '{print $2}') || true
sleep 5
/usr/bin/osascript stop-vo.applescript || true
sleep 5
- name: Group Containers List
run: |
ls -la "$HOME/Library/Group Containers/" || true
ls -laR "$HOME/Library/Group Containers/group.com.apple.VoiceOver/" || true
plutil -convert xml1 -o - "$HOME/Library/Group Containers/group.com.apple.VoiceOver/Library/Preferences/com.apple.VoiceOver4.local.plist" || true
## Automated portable preferences enablement
- name: Convert plist xml to binary
run: |
plutil -convert binary1 "./${{ matrix.os }}/VoiceOver/VoiceOver4.portable/default.plist"
plutil -convert binary1 "./${{ matrix.os }}/VoiceOver/VoiceOver4.portable/master.plist"
- name: Read portable identifier
id: portable
run: |
IDENTIFIER=$(
/usr/libexec/PlistBuddy \
-c "Print :identifier" \
"./${{ matrix.os }}/VoiceOver/VoiceOver4.portable/voidentifier"
)
echo "$IDENTIFIER"
echo "identifier=$IDENTIFIER" >> "$GITHUB_OUTPUT"
- name: Trust portable preferences - group container preferences
env:
IDENTIFIER: ${{ steps.portable.outputs.identifier }}
run: |
PLIST="$HOME/Library/Group Containers/group.com.apple.VoiceOver/Library/Preferences/com.apple.VoiceOver4.local.plist"
# SCRCUserDefaultsAllowAirPlay
/usr/libexec/PlistBuddy -c "Delete :SCRCUserDefaultsAllowAirPlay" "$PLIST" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Add :SCRCUserDefaultsAllowAirPlay bool true" "$PLIST"
# lastTouchedKey
/usr/libexec/PlistBuddy -c "Delete :lastTouchedKey" "$PLIST" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Add :lastTouchedKey string $IDENTIFIER" "$PLIST"
# SCRCUserDefaultsAlwaysUsePortableIDs
/usr/libexec/PlistBuddy -c "Print :SCRCUserDefaultsAlwaysUsePortableIDs" "$PLIST" >/dev/null 2>&1 || {
/usr/libexec/PlistBuddy -c "Add :SCRCUserDefaultsAlwaysUsePortableIDs array" "$PLIST"
}
# portablePreferenceKeys
/usr/libexec/PlistBuddy -c "Print :portablePreferenceKeys" "$PLIST" >/dev/null 2>&1 || {
/usr/libexec/PlistBuddy -c "Add :portablePreferenceKeys array" "$PLIST"
}
# append if missing
for KEY in \
SCRCUserDefaultsAlwaysUsePortableIDs \
portablePreferenceKeys
do
if ! /usr/libexec/PlistBuddy -c "Print :$KEY" "$PLIST" | grep -Fxq "$IDENTIFIER"; then
INDEX=$(
/usr/libexec/PlistBuddy -c "Print :$KEY" "$PLIST" |
tail -n +2 |
wc -l |
tr -d ' '
)
/usr/libexec/PlistBuddy -c "Add :$KEY:$INDEX string $IDENTIFIER" "$PLIST"
fi
done
plutil -convert xml1 -o - "$PLIST" || true
- name: Create portable preferences symlinks
run: |
VO_PORTABLE="/Volumes/GuidepupVoiceOverPreferences/VoiceOver/VoiceOver4.portable"
GROUP_CONTAINER_PREFS="$HOME/Library/Group Containers/group.com.apple.VoiceOver/Library/Preferences"
ln -sf "$VO_PORTABLE" "$GROUP_CONTAINER_PREFS/com.apple.VoiceOver4.portable.scrd"
ln -sf "$VO_PORTABLE" "$GROUP_CONTAINER_PREFS/com.apple.VoiceOver4.portable.scrd.vou"
ln -sf "$VO_PORTABLE" "$GROUP_CONTAINER_PREFS/com.apple.VoiceOver4.portable.scro"
ln -sf "$VO_PORTABLE" "$GROUP_CONTAINER_PREFS/com.apple.VoiceOver4.portable.scui"
- name: Create Volume
run: |
hdiutil create \
-fs APFS \
-volname GuidepupVoiceOverPreferences \
-srcfolder "./${{ matrix.os }}" \
-format UDRW \
-ov \
GuidepupVoiceOverPreferences.dmg
hdiutil attach \
GuidepupVoiceOverPreferences.dmg \
-mountpoint /Volumes/GuidepupVoiceOverPreferences
- name: Verify mount
run: |
ls -R /Volumes/GuidepupVoiceOverPreferences
## Verification Stages
- name: Launch & Verify VoiceOver Welcome Greeting
run: |
mkdir -p artifacts || true
# Start VoiceOver
/System/Library/CoreServices/VoiceOver.app/Contents/MacOS/VoiceOverStarter &
for i in {1..20}; do
echo "Attempt: $i"
screencapture artifacts/vo-$i.png || true
ACTUAL=$(/usr/bin/osascript voiceOverPerform.applescript 2>/dev/null || true)
echo "Spoken Phrase: $ACTUAL"
sleep 0.01
done
kill -15 $(ps aux | egrep "[V]oiceOver.app/Contents/MacOS/VoiceOver launchd -s" | awk '{print $2}') || true
sleep 5
/usr/bin/osascript stop-vo.applescript || true
sleep 5
- name: Open & Close VoiceOver VoiceOver Utility
run: |
mkdir -p artifacts || true
/usr/bin/osascript start-vo-utility.applescript || true
sleep 5
screencapture artifacts/vo-utility-end.png || true
/usr/bin/osascript stop-vo-utility.applescript || true
- name: Group Containers List
run: |
ls -la "$HOME/Library/Group Containers/" || true
ls -laR "$HOME/Library/Group Containers/group.com.apple.VoiceOver/" || true
plutil -convert xml1 -o - "$HOME/Library/Group Containers/group.com.apple.VoiceOver/Library/Preferences/com.apple.VoiceOver4.local.plist" || true
## Cleanup
- name: Cleanup
if: always()
run: |
hdiutil detach /Volumes/GuidepupVoiceOverPreferences || true
rm -f GuidepupVoiceOverPreferences.dmg || true
- uses: actions/upload-artifact@v4
if: always()
continue-on-error: true
with:
name: artifacts-${{ matrix.os }}
path: |
**/artifacts/**/*