mirror of
https://github.com/mmacierzanka/second-display.git
synced 2026-11-06 12:41:45 +01:00
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:
1 parent
8072ac473a
commit
d3609ae29c
8 files changed
+40
-35
No files matched your search
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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.
@@ -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
|
||||||
|
|||||||
@@ -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).
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user