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
6 files changed
+34 -29

No files matched your search

+1 -1
View File
@@ -15,7 +15,7 @@ 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/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 (`-`). - `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 (`-`).
+2 -2
View File
@@ -17,7 +17,7 @@ SECOND DISPLAY — INSTALLATION
- 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"
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
+4 -4
View File
@@ -11,7 +11,7 @@ make # builds build/SecondDisplay.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