From d3609ae29c0d1db9b4d910987f91f3677ad5036d Mon Sep 17 00:00:00 2001 From: Marcin Macierzanka Date: Tue, 3 Nov 2026 14:01:46 +0100 Subject: [PATCH] Name the app bundle 'Second Display' everywhere; second-display for file artifacts Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 4 +- Config/Info.plist | 2 +- Config/dmg/Read Me First.txt | 8 ++-- Config/dmg/settings.py | 2 +- ...titlements => second-display.entitlements} | 0 Makefile | 47 ++++++++++--------- README.md | 10 ++-- Resources/uninstall.sh | 2 +- 8 files changed, 40 insertions(+), 35 deletions(-) rename Config/{SecondDisplay.entitlements => second-display.entitlements} (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 592ea72..81d95ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,10 +15,10 @@ A native macOS app that shows video from a capture device (camera / capture card ## Structure - `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). -- `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`. - `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 - Settings live in `UserDefaults` (keys: `video`, `audio`, `autostart`, `autostartName`, `keepAwake`, `startFull`, `onTop`, `gravity`, `format.`). diff --git a/Config/Info.plist b/Config/Info.plist index ec833e7..e5e72f2 100644 --- a/Config/Info.plist +++ b/Config/Info.plist @@ -4,7 +4,7 @@ CFBundleDevelopmentRegionen CFBundleLocalizationsenpl - CFBundleExecutableSecondDisplay + CFBundleExecutableSecond Display CFBundleIdentifiercom.mmacierzanka.seconddisplay CFBundleNameSecond Display CFBundlePackageTypeAPPL diff --git a/Config/dmg/Read Me First.txt b/Config/dmg/Read Me First.txt index 0825ff0..3579d2b 100644 --- a/Config/dmg/Read Me First.txt +++ b/Config/dmg/Read Me First.txt @@ -1,7 +1,7 @@ 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. macOS will most likely refuse: this app is not notarized by Apple @@ -13,11 +13,11 @@ SECOND DISPLAY — INSTALLATION A) System Settings - Open System Settings > Privacy & Security. - 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". 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. @@ -29,4 +29,4 @@ REQUIREMENTS UNINSTALL 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" diff --git a/Config/dmg/settings.py b/Config/dmg/settings.py index 51fe336..68f26a2 100644 --- a/Config/dmg/settings.py +++ b/Config/dmg/settings.py @@ -17,7 +17,7 @@ icon_size = 128 text_size = 13 arrange_by = None icon_locations = { - 'SecondDisplay.app': (198, 195), + 'Second Display.app': (198, 195), 'Applications': (529, 195), 'Read Me First.txt': (363, 435), } diff --git a/Config/SecondDisplay.entitlements b/Config/second-display.entitlements similarity index 100% rename from Config/SecondDisplay.entitlements rename to Config/second-display.entitlements diff --git a/Makefile b/Makefile index 10426c5..bd1559a 100644 --- a/Makefile +++ b/Makefile @@ -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) RES := $(shell find Resources -type f) +ENTITLEMENTS := Config/second-display.entitlements # "-" = ad-hoc; set SIGN_ID to a Developer ID identity for distribution SIGN_ID ?= - BUILDNUM := $(shell git rev-list --count HEAD 2>/dev/null || echo 1) VERSION := $(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" Config/Info.plist) -DMG := build/SecondDisplay-$(VERSION).dmg -VOLNAME ?= Second Display +DMG := build/second-display-$(VERSION).dmg +VOLNAME ?= $(NAME) .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 - 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 x86_64-apple-macos26.0 $(SRC) -o $(APP)/Contents/MacOS/SecondDisplay-x86_64 - lipo -create $(APP)/Contents/MacOS/SecondDisplay-* -output $(APP)/Contents/MacOS/SecondDisplay - rm $(APP)/Contents/MacOS/SecondDisplay-* - cp Config/Info.plist $(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/ - xcrun actool Resources/AppIcon.icon --compile $(APP)/Contents/Resources --platform macosx \ +$(STAMP): Makefile $(SRC) $(RES) Config/Info.plist $(ENTITLEMENTS) + rm -rf "$(APP)" && mkdir -p "$(APP)/Contents/MacOS" "$(APP)/Contents/Resources" + swiftc -O -target arm64-apple-macos26.0 $(SRC) -o build/arm64 + swiftc -O -target x86_64-apple-macos26.0 $(SRC) -o build/x86_64 + lipo -create build/arm64 build/x86_64 -output "$(APP)/Contents/MacOS/$(NAME)" + rm build/arm64 build/x86_64 + cp Config/Info.plist "$(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/" + 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 - 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 - open $(APP) + open "$(APP)" clean: ## Remove build output rm -rf build 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 - ~/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 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 - codesign --verify --strict $(APP) + codesign --verify --strict "$(APP)" DMGBUILD := build/venv/bin/dmgbuild @@ -50,7 +55,7 @@ $(DMGBUILD): dmg: build $(DMGBUILD) ## Build a distributable disk image (not notarized) 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 help: ## List targets diff --git a/README.md b/README.md index defcd62..7ecf475 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ macOS 26+, Xcode (Command Line Tools). ## Build and run ``` -make # builds build/SecondDisplay.app +make # builds build/Second Display.app make run # build and launch make check # lint: typecheck, plists, translations, signature make install # copy to ~/Applications -make dmg # build/SecondDisplay-.dmg (universal, not notarized) +make dmg # build/second-display-.dmg (universal, not notarized) 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). @@ -22,11 +22,11 @@ Build from source and install to `~/Applications`: git clone second-display cd second-display 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. -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 `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: `~/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 MIT — see [LICENSE](LICENSE). diff --git a/Resources/uninstall.sh b/Resources/uninstall.sh index aaa18f7..78d23df 100755 --- a/Resources/uninstall.sh +++ b/Resources/uninstall.sh @@ -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; } read -q "?Delete $APP and all its data? [y/N] " || { echo; echo Cancelled; exit 0; } 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 rm -f ~/Library/LaunchAgents/$ID.autostart.plist defaults delete "$ID" 2>/dev/null