Name the app bundle 'Second Display' everywhere; second-display for file artifacts

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mmacierzankaandClaude Bot committed 2026-11-03 14:01:46 +01:00
1 parent 8072ac473a
commit d3609ae29c
8 files changed
+40 -35

No files matched your search

+2 -2
View File
@@ -15,10 +15,10 @@ A native macOS app that shows video from a capture device (camera / capture card
## Structure ## Structure
- `Sources/` – app code (AppKit + AVFoundation, one `AVCaptureSession`, audio via `AVCaptureAudioPreviewOutput`). The `App` class is split into extensions: - `Sources/` – app code (AppKit + AVFoundation, one `AVCaptureSession`, audio via `AVCaptureAudioPreviewOutput`). The `App` class is split into extensions:
`main.swift` (entry point), `App.swift` (state, window, lifecycle), `Capture.swift` (devices, formats, hot-plug), `Menu.swift` (menus and actions), `Power.swift` (sleep prevention), `Autostart.swift` (launch-on-connect agent), `Preferences.swift` (UserDefaults-backed settings). `main.swift` (entry point), `App.swift` (state, window, lifecycle), `Capture.swift` (devices, formats, hot-plug), `Menu.swift` (menus and actions), `Power.swift` (sleep prevention), `Autostart.swift` (launch-on-connect agent), `Preferences.swift` (UserDefaults-backed settings).
- `Config/Info.plist`, `Config/SecondDisplay.entitlements`, `Config/dmg/` – version, permissions (camera, microphone; hardened runtime). - `Config/Info.plist`, `Config/second-display.entitlements`, `Config/dmg/` – version, permissions (camera, microphone; hardened runtime).
- `Resources/` – `AppIcon.icon` (Icon Composer, Liquid Glass; compiled by `actool`), `{en,pl}.lproj` (`Localizable.strings`, `InfoPlist.strings`), `uninstall.sh` (copied into the bundle; removes the app, preferences, caches, TCC permissions). Bundle ID: `com.mmacierzanka.seconddisplay`. - `Resources/` – `AppIcon.icon` (Icon Composer, Liquid Glass; compiled by `actool`), `{en,pl}.lproj` (`Localizable.strings`, `InfoPlist.strings`), `uninstall.sh` (copied into the bundle; removes the app, preferences, caches, TCC permissions). Bundle ID: `com.mmacierzanka.seconddisplay`.
- `scripts/check-strings.sh` – en/pl translation consistency. `scripts/make-dmg-background.swift` regenerates `Config/dmg/background.png`; keep its size and icon coordinates in sync with `Config/dmg/settings.py` (dmgbuild layout, installed into `build/venv` on first `make dmg`). - `scripts/check-strings.sh` – en/pl translation consistency. `scripts/make-dmg-background.swift` regenerates `Config/dmg/background.png`; keep its size and icon coordinates in sync with `Config/dmg/settings.py` (dmgbuild layout, installed into `build/venv` on first `make dmg`).
- `Makefile` – `make` (builds `build/SecondDisplay.app`), `run`, `check`, `install`, `uninstall`, `dmg`, `clean`, `help`. Builds are universal (arm64 + x86_64, macOS 26+). `make dmg` makes an un-notarized DMG with `Config/dmg/Read Me First.txt`. `SIGN_ID` defaults to ad-hoc (`-`). - `Makefile` – `make` (builds `build/Second Display.app`), `run`, `check`, `install`, `uninstall`, `dmg`, `clean`, `help`. Builds are universal (arm64 + x86_64, macOS 26+). `make dmg` makes an un-notarized DMG with `Config/dmg/Read Me First.txt`. `SIGN_ID` defaults to ad-hoc (`-`).
## Conventions ## Conventions
- Settings live in `UserDefaults` (keys: `video`, `audio`, `autostart`, `autostartName`, `keepAwake`, `startFull`, `onTop`, `gravity`, `format.<id>`). - Settings live in `UserDefaults` (keys: `video`, `audio`, `autostart`, `autostartName`, `keepAwake`, `startFull`, `onTop`, `gravity`, `format.<id>`).
+1 -1
View File
@@ -4,7 +4,7 @@
<dict> <dict>
<key>CFBundleDevelopmentRegion</key><string>en</string> <key>CFBundleDevelopmentRegion</key><string>en</string>
<key>CFBundleLocalizations</key><array><string>en</string><string>pl</string></array> <key>CFBundleLocalizations</key><array><string>en</string><string>pl</string></array>
<key>CFBundleExecutable</key><string>SecondDisplay</string> <key>CFBundleExecutable</key><string>Second Display</string>
<key>CFBundleIdentifier</key><string>com.mmacierzanka.seconddisplay</string> <key>CFBundleIdentifier</key><string>com.mmacierzanka.seconddisplay</string>
<key>CFBundleName</key><string>Second Display</string> <key>CFBundleName</key><string>Second Display</string>
<key>CFBundlePackageType</key><string>APPL</string> <key>CFBundlePackageType</key><string>APPL</string>
+4 -4
View File
@@ -1,7 +1,7 @@
SECOND DISPLAY — INSTALLATION SECOND DISPLAY — INSTALLATION
============================= =============================
1. Drag "SecondDisplay" onto the "Applications" folder in this window. 1. Drag "Second Display" onto the "Applications" folder in this window.
2. Open Second Display from Applications. 2. Open Second Display from Applications.
macOS will most likely refuse: this app is not notarized by Apple macOS will most likely refuse: this app is not notarized by Apple
@@ -13,11 +13,11 @@ SECOND DISPLAY — INSTALLATION
A) System Settings A) System Settings
- Open System Settings > Privacy & Security. - Open System Settings > Privacy & Security.
- Scroll to the Security section; you will see - Scroll to the Security section; you will see
"SecondDisplay was blocked...". Click "Open Anyway". "Second Display was blocked...". Click "Open Anyway".
- Confirm with your password / Touch ID, then click "Open". - Confirm with your password / Touch ID, then click "Open".
B) Terminal B) Terminal
xattr -dr com.apple.quarantine /Applications/SecondDisplay.app xattr -dr com.apple.quarantine "/Applications/Second Display.app"
You only need to do this once per downloaded copy. You only need to do this once per downloaded copy.
@@ -29,4 +29,4 @@ REQUIREMENTS
UNINSTALL UNINSTALL
Run this in Terminal to remove the app and every trace it leaves: Run this in Terminal to remove the app and every trace it leaves:
/Applications/SecondDisplay.app/Contents/Resources/uninstall.sh "/Applications/Second Display.app/Contents/Resources/uninstall.sh"
+1 -1
View File
@@ -17,7 +17,7 @@ icon_size = 128
text_size = 13 text_size = 13
arrange_by = None arrange_by = None
icon_locations = { icon_locations = {
'SecondDisplay.app': (198, 195), 'Second Display.app': (198, 195),
'Applications': (529, 195), 'Applications': (529, 195),
'Read Me First.txt': (363, 435), 'Read Me First.txt': (363, 435),
} }
File renamed without changes.
+26 -21
View File
@@ -1,47 +1,52 @@
APP := build/SecondDisplay.app NAME := Second Display
APP := build/$(NAME).app
# GNU make cannot use paths with spaces as targets, so builds are tracked by a stamp file
STAMP := build/.app-built
SRC := $(wildcard Sources/*.swift) SRC := $(wildcard Sources/*.swift)
RES := $(shell find Resources -type f) RES := $(shell find Resources -type f)
ENTITLEMENTS := Config/second-display.entitlements
# "-" = ad-hoc; set SIGN_ID to a Developer ID identity for distribution # "-" = ad-hoc; set SIGN_ID to a Developer ID identity for distribution
SIGN_ID ?= - SIGN_ID ?= -
BUILDNUM := $(shell git rev-list --count HEAD 2>/dev/null || echo 1) BUILDNUM := $(shell git rev-list --count HEAD 2>/dev/null || echo 1)
VERSION := $(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" Config/Info.plist) VERSION := $(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" Config/Info.plist)
DMG := build/SecondDisplay-$(VERSION).dmg DMG := build/second-display-$(VERSION).dmg
VOLNAME ?= Second Display VOLNAME ?= $(NAME)
.PHONY: build run clean install uninstall check dmg help .PHONY: build run clean install uninstall check dmg help
build: $(APP) ## Build build/SecondDisplay.app build: $(STAMP) ## Build build/Second Display.app
$(APP): Makefile $(SRC) $(RES) Config/Info.plist Config/SecondDisplay.entitlements $(STAMP): Makefile $(SRC) $(RES) Config/Info.plist $(ENTITLEMENTS)
rm -rf $(APP) && mkdir -p $(APP)/Contents/MacOS $(APP)/Contents/Resources rm -rf "$(APP)" && mkdir -p "$(APP)/Contents/MacOS" "$(APP)/Contents/Resources"
swiftc -O -target arm64-apple-macos26.0 $(SRC) -o $(APP)/Contents/MacOS/SecondDisplay-arm64 swiftc -O -target arm64-apple-macos26.0 $(SRC) -o build/arm64
swiftc -O -target x86_64-apple-macos26.0 $(SRC) -o $(APP)/Contents/MacOS/SecondDisplay-x86_64 swiftc -O -target x86_64-apple-macos26.0 $(SRC) -o build/x86_64
lipo -create $(APP)/Contents/MacOS/SecondDisplay-* -output $(APP)/Contents/MacOS/SecondDisplay lipo -create build/arm64 build/x86_64 -output "$(APP)/Contents/MacOS/$(NAME)"
rm $(APP)/Contents/MacOS/SecondDisplay-* rm build/arm64 build/x86_64
cp Config/Info.plist $(APP)/Contents/Info.plist cp Config/Info.plist "$(APP)/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $(BUILDNUM)" $(APP)/Contents/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $(BUILDNUM)" "$(APP)/Contents/Info.plist"
cp -R Resources/en.lproj Resources/pl.lproj Resources/uninstall.sh $(APP)/Contents/Resources/ cp -R Resources/en.lproj Resources/pl.lproj Resources/uninstall.sh "$(APP)/Contents/Resources/"
xcrun actool Resources/AppIcon.icon --compile $(APP)/Contents/Resources --platform macosx \ xcrun actool Resources/AppIcon.icon --compile "$(APP)/Contents/Resources" --platform macosx \
--minimum-deployment-target 26.0 --app-icon AppIcon --output-partial-info-plist /dev/null >/dev/null --minimum-deployment-target 26.0 --app-icon AppIcon --output-partial-info-plist /dev/null >/dev/null
codesign --force --options runtime --entitlements Config/SecondDisplay.entitlements --sign "$(SIGN_ID)" $(APP) codesign --force --options runtime --entitlements $(ENTITLEMENTS) --sign "$(SIGN_ID)" "$(APP)"
touch $(STAMP)
run: build ## Build and launch run: build ## Build and launch
open $(APP) open "$(APP)"
clean: ## Remove build output clean: ## Remove build output
rm -rf build rm -rf build
install: build ## Copy to ~/Applications install: build ## Copy to ~/Applications
mkdir -p ~/Applications && rm -rf ~/Applications/SecondDisplay.app && cp -R $(APP) ~/Applications/ mkdir -p ~/Applications && rm -rf ~/Applications/SecondDisplay.app "$$HOME/Applications/$(NAME).app" && cp -R "$(APP)" ~/Applications/
uninstall: ## Remove the installed app and all its leftovers uninstall: ## Remove the installed app and all its leftovers
~/Applications/SecondDisplay.app/Contents/Resources/uninstall.sh "$$HOME/Applications/$(NAME).app/Contents/Resources/uninstall.sh"
check: build ## Lint sources, plists, strings and the built bundle check: build ## Lint sources, plists, strings and the built bundle
swiftc -typecheck $(SRC) swiftc -typecheck $(SRC)
plutil -lint Config/Info.plist Config/SecondDisplay.entitlements Resources/*.lproj/*.strings plutil -lint Config/Info.plist $(ENTITLEMENTS) Resources/*.lproj/*.strings
scripts/check-strings.sh scripts/check-strings.sh
codesign --verify --strict $(APP) codesign --verify --strict "$(APP)"
DMGBUILD := build/venv/bin/dmgbuild DMGBUILD := build/venv/bin/dmgbuild
@@ -50,7 +55,7 @@ $(DMGBUILD):
dmg: build $(DMGBUILD) ## Build a distributable disk image (not notarized) dmg: build $(DMGBUILD) ## Build a distributable disk image (not notarized)
rm -f $(DMG) rm -f $(DMG)
$(DMGBUILD) -s Config/dmg/settings.py -D app=$(APP) "$(VOLNAME)" $(DMG) $(DMGBUILD) -s Config/dmg/settings.py -D app="$(APP)" "$(VOLNAME)" $(DMG)
hdiutil verify $(DMG) | tail -1 hdiutil verify $(DMG) | tail -1
help: ## List targets help: ## List targets
+5 -5
View File
@@ -7,11 +7,11 @@ macOS 26+, Xcode (Command Line Tools).
## Build and run ## Build and run
``` ```
make # builds build/SecondDisplay.app make # builds build/Second Display.app
make run # build and launch make run # build and launch
make check # lint: typecheck, plists, translations, signature make check # lint: typecheck, plists, translations, signature
make install # copy to ~/Applications make install # copy to ~/Applications
make dmg # build/SecondDisplay-<version>.dmg (universal, not notarized) make dmg # build/second-display-<version>.dmg (universal, not notarized)
make help # list targets make help # list targets
``` ```
The app is signed ad-hoc (works on this Mac). For distribution: `make SIGN_ID="Developer ID Application: ..."` (notarization is still required). The app is signed ad-hoc (works on this Mac). For distribution: `make SIGN_ID="Developer ID Application: ..."` (notarization is still required).
@@ -22,11 +22,11 @@ Build from source and install to `~/Applications`:
git clone <repository-url> second-display git clone <repository-url> second-display
cd second-display cd second-display
make install make install
open ~/Applications/SecondDisplay.app open "$HOME/Applications/Second Display.app"
``` ```
On first launch macOS asks for camera and microphone access — allow both. You can change this later in System Settings → Privacy & Security. On first launch macOS asks for camera and microphone access — allow both. You can change this later in System Settings → Privacy & Security.
If you received a prebuilt copy (the `.dmg`), drag the app to Applications. It is not notarized, so macOS blocks it on first launch: allow it once in System Settings → Privacy & Security → "Open Anyway", or run `xattr -dr com.apple.quarantine /Applications/SecondDisplay.app`. The same steps are in `Read Me First.txt` inside the disk image. If you received a prebuilt copy (the `.dmg`), drag the app to Applications. It is not notarized, so macOS blocks it on first launch: allow it once in System Settings → Privacy & Security → "Open Anyway", or run `xattr -dr com.apple.quarantine "/Applications/Second Display.app"`. The same steps are in `Read Me First.txt` inside the disk image.
## Releasing a DMG ## Releasing a DMG
`make dmg` builds a universal (arm64 + x86_64) app and packs it with an `Applications` shortcut and `Read Me First.txt` (source: `Config/dmg/`) in a styled Finder window: large icons and a background with an arrow (`Config/dmg/background.png`, generated by `scripts/make-dmg-background.swift`). The layout is written directly into the image by [dmgbuild](https://github.com/dmgbuild/dmgbuild) (settings in `Config/dmg/settings.py`), so no Finder automation is needed. The first `make dmg` installs it into `build/venv` (needs `python3` and network access). The disk image is not signed with a Developer ID or notarized; the instructions in it explain how recipients allow it. Bump the version in `Config/Info.plist` before releasing and tag it (`git tag -a vX.Y.Z -m vX.Y.Z`). `make dmg` builds a universal (arm64 + x86_64) app and packs it with an `Applications` shortcut and `Read Me First.txt` (source: `Config/dmg/`) in a styled Finder window: large icons and a background with an arrow (`Config/dmg/background.png`, generated by `scripts/make-dmg-background.swift`). The layout is written directly into the image by [dmgbuild](https://github.com/dmgbuild/dmgbuild) (settings in `Config/dmg/settings.py`), so no Finder automation is needed. The first `make dmg` installs it into `build/venv` (needs `python3` and network access). The disk image is not signed with a Developer ID or notarized; the instructions in it explain how recipients allow it. Bump the version in `Config/Info.plist` before releasing and tag it (`git tag -a vX.Y.Z -m vX.Y.Z`).
@@ -44,7 +44,7 @@ Menus at the top of the screen: **Video** (device, format), **Audio** (source or
## Settings and uninstall ## Settings and uninstall
Settings: `~/Library/Preferences/com.mmacierzanka.seconddisplay.plist`. Settings: `~/Library/Preferences/com.mmacierzanka.seconddisplay.plist`.
To remove the app and all its traces: `make uninstall` or `SecondDisplay.app/Contents/Resources/uninstall.sh`. To remove the app and all its traces: `make uninstall` or `"Second Display.app/Contents/Resources/uninstall.sh"`.
## License ## License
MIT — see [LICENSE](LICENSE). MIT — see [LICENSE](LICENSE).
+1 -1
View File
@@ -5,7 +5,7 @@ APP="${0:A:h:h:h}"
[[ "$APP" == *.app && "$(defaults read "$APP/Contents/Info" CFBundleIdentifier 2>/dev/null)" == "$ID" ]] || { echo "Not a Second Display bundle: $APP"; exit 1; } [[ "$APP" == *.app && "$(defaults read "$APP/Contents/Info" CFBundleIdentifier 2>/dev/null)" == "$ID" ]] || { echo "Not a Second Display bundle: $APP"; exit 1; }
read -q "?Delete $APP and all its data? [y/N] " || { echo; echo Cancelled; exit 0; } read -q "?Delete $APP and all its data? [y/N] " || { echo; echo Cancelled; exit 0; }
echo echo
pkill -x SecondDisplay 2>/dev/null pkill -x "Second Display" 2>/dev/null; pkill -x SecondDisplay 2>/dev/null # current and legacy executable names
launchctl bootout "gui/$(id -u)/$ID.autostart" 2>/dev/null # launch-on-connect agent launchctl bootout "gui/$(id -u)/$ID.autostart" 2>/dev/null # launch-on-connect agent
rm -f ~/Library/LaunchAgents/$ID.autostart.plist rm -f ~/Library/LaunchAgents/$ID.autostart.plist
defaults delete "$ID" 2>/dev/null defaults delete "$ID" 2>/dev/null