Ein Tap mit mutedWhenTapped schaltet die ursprüngliche Ausgabe eines Programms stumm und liefert das Signal stattdessen bei Onyx ab; ein privates Aggregate-Device aus Tap und echtem Ausgabegerät gibt es nach der Verstärkung wieder aus. Das ist der einzige Weg, den macOS ohne eigenen Treiber anbietet. Die Kennlinie ist hergeleitet, nicht geraten: halbe empfundene Lautstärke sind rund −10 dB, also Faktor 0,316. Aus 0,5^k = 0,316 folgt k ≈ 1,66. Die Skala ist damit symmetrisch — 50 % sind −10 dB, 200 % sind +10 dB. Mein erster Ansatz mit der vierten Potenz ergab bei halbem Regler 6 % statt 32 %; der Test hat das gefangen, bevor es an einen Lautsprecher kam. Über 100 % greift ein weicher Begrenzer. Ohne ihn schneidet die Hardware die Werte hart ab, und das knackt hörbar. Er arbeitet erst ab 0,7, weil eine Begrenzung, die schon bei Zimmerlautstärke einsetzt, matt klingt. Der Mixer ist standardmäßig aus, und auch eingeschaltet greift er nur bei ausdrücklich gewählten Programmen ein. Alles andere läuft unverändert vorbei. Der Notausschalter steht sichtbar in der Ansicht, nicht in einem Menü: wenn der Ton weg ist, ist das der Knopf, den man sucht. Drei Fehlerfälle sind eingeplant, weil jeder von ihnen ein stummes Programm bedeutet. Der Wachhund baut Taps neu auf, die nur noch Stille liefern — der bekannte Fall aus Spike C. Ein Wechsel des Ausgabegeräts baut alle Taps neu auf, weil sie am alten Gerät hängen. Und ein fehlgeschlagener Aufbau räumt sich selbst weg, statt in einem halben Zustand zu verharren. Beim Beenden von Onyx wird der Mixer abgebaut: sonst bleiben die abgegriffenen Programme stumm zurück. 178 Tests grün, App-Signatur gültig.
171 lines
7.3 KiB
YAML
171 lines
7.3 KiB
YAML
name: Onyx
|
|
options:
|
|
bundleIdPrefix: com.scarriffleservices
|
|
deploymentTarget:
|
|
macOS: "26.0"
|
|
createIntermediateGroups: true
|
|
groupSortPosition: top
|
|
|
|
settings:
|
|
base:
|
|
DEVELOPMENT_TEAM: PP34X97WS3
|
|
SWIFT_VERSION: "6.0"
|
|
MARKETING_VERSION: "0.1.0"
|
|
CURRENT_PROJECT_VERSION: "1"
|
|
ENABLE_HARDENED_RUNTIME: YES
|
|
# KEIN --deep.
|
|
#
|
|
# Es überträgt die Entitlements der App auf jede eingebettete Binary. Der
|
|
# privilegierte Helfer bekam dadurch application-identifier, weatherkit und
|
|
# application-groups verpasst — allesamt eingeschränkte Entitlements, die
|
|
# ein Provisioning-Profil verlangen. Ein nacktes Kommandozeilenprogramm kann
|
|
# aber keines einbetten, also lehnte AMFI den Start ab:
|
|
#
|
|
# amfid: OnyxHelper not valid: Code=-413 "No matching profile found"
|
|
# taskgated-helper: Disallowing com.scarriffleservices.onyx.helper
|
|
# because no eligible provisioning profiles found
|
|
#
|
|
# Für das ad-hoc signierte MediaRemoteAdapter.framework wird --deep nicht
|
|
# gebraucht: dafür ist disable-library-validation da.
|
|
SWIFT_STRICT_CONCURRENCY: complete
|
|
# Xcode lagert im Debug-Build den Programmcode in eine eigene dylib aus
|
|
# (für SwiftUI-Vorschauen). Das Hauptprogramm ist dann nur noch ein Rumpf —
|
|
# und TCC ordnet Berechtigungsanfragen dem Rumpf zu, der weder Info.plist
|
|
# noch Entitlements trägt. Ergebnis: requestFullAccessToEvents() liefert
|
|
# kommentarlos false, ohne dass je ein Dialog erscheint.
|
|
ENABLE_DEBUG_DYLIB: NO
|
|
|
|
packages:
|
|
OnyxKit:
|
|
path: Packages/OnyxKit
|
|
|
|
targets:
|
|
# Der privilegierte Helfer. Läuft als root und tut genau eine Sache:
|
|
# Lüfterdrehzahlen setzen. Kein Netzwerk, keine Oberfläche, kein
|
|
# Dateizugriff — je weniger hier steht, desto kleiner die Angriffsfläche.
|
|
OnyxHelper:
|
|
type: tool
|
|
platform: macOS
|
|
sources:
|
|
- path: OnyxHelper
|
|
excludes:
|
|
- "*.plist"
|
|
dependencies:
|
|
- package: OnyxKit
|
|
product: OnyxHelperProtocol
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.onyx.helper
|
|
PRODUCT_NAME: OnyxHelper
|
|
CODE_SIGN_STYLE: Automatic
|
|
SKIP_INSTALL: YES
|
|
# Kommandozeilenprogramme haben kein Bundle. Die Kennung muss deshalb
|
|
# in die Binary selbst — sonst signiert codesign sie unter dem
|
|
# Dateinamen und die Signaturprüfung zwischen App und Helfer scheitert.
|
|
INFOPLIST_FILE: OnyxHelper/Info.plist
|
|
CREATE_INFOPLIST_SECTION_IN_BINARY: YES
|
|
|
|
Onyx:
|
|
type: application
|
|
platform: macOS
|
|
sources:
|
|
- path: Onyx
|
|
dependencies:
|
|
- target: OnyxHelper
|
|
embed: false
|
|
- package: OnyxKit
|
|
product: OnyxCore
|
|
- package: OnyxKit
|
|
product: OnyxHelperProtocol
|
|
- package: OnyxKit
|
|
product: OnyxDesign
|
|
- package: OnyxKit
|
|
product: OnyxNotch
|
|
- package: OnyxKit
|
|
product: OnyxWidgetKit
|
|
- package: OnyxKit
|
|
product: OnyxMenuBar
|
|
- package: OnyxKit
|
|
product: CalendarProvider
|
|
- package: OnyxKit
|
|
product: WeatherProvider
|
|
- package: OnyxKit
|
|
product: MediaProvider
|
|
- package: OnyxKit
|
|
product: MetricsProvider
|
|
- package: OnyxKit
|
|
product: NetworkProvider
|
|
- package: OnyxKit
|
|
product: AudioProvider
|
|
postBuildScripts:
|
|
# Der Helfer und seine Plist müssen an genau diese Stellen: SMAppService
|
|
# sucht die Plist unter Contents/Library/LaunchDaemons und liest von dort
|
|
# den Pfad zum Programm.
|
|
- name: Helfer und Launchd-Plist einbetten
|
|
script: |
|
|
APP="$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH"
|
|
mkdir -p "$APP/MacOS" "$APP/Library/LaunchDaemons"
|
|
cp "$BUILT_PRODUCTS_DIR/OnyxHelper" "$APP/MacOS/OnyxHelper"
|
|
cp "$SRCROOT/OnyxHelper/com.scarriffleservices.onyx.helper.plist" \
|
|
"$APP/Library/LaunchDaemons/"
|
|
# Der Helfer wird eigenständig signiert — die Signatur der App deckt
|
|
# ihn nicht ab, und ohne gültige eigene Signatur lehnt launchd ihn ab.
|
|
#
|
|
# `-i` ist Pflicht: Xcode signiert Kommandozeilenprogramme sonst mit
|
|
# dem Produktnamen ("OnyxHelper") als Bezeichner. Die App prüft aber
|
|
# auf com.scarriffleservices.onyx.helper und würde ihren eigenen
|
|
# Helfer ablehnen.
|
|
codesign --force --options runtime --timestamp=none \
|
|
-i com.scarriffleservices.onyx.helper \
|
|
--sign "$EXPANDED_CODE_SIGN_IDENTITY" "$APP/MacOS/OnyxHelper"
|
|
|
|
# Das Kopieren passiert nach Xcodes Signatur und bricht damit das
|
|
# Siegel des App-Bundles ("nested code is modified or invalid").
|
|
# Deshalb zum Schluss die App selbst neu signieren — mit denselben
|
|
# Entitlements, sonst verliert sie App-Group, WeatherKit und die
|
|
# TCC-Berechtigungen.
|
|
codesign --force --options runtime --timestamp=none \
|
|
--entitlements "$SRCROOT/Onyx/Onyx.entitlements" \
|
|
--sign "$EXPANDED_CODE_SIGN_IDENTITY" \
|
|
"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME"
|
|
# Bewusst ohne outputFiles: mit ihnen überspringt Xcode den Schritt,
|
|
# solange die Plist unverändert ist — auch wenn sich das Programm oder
|
|
# die Signaturvorgaben geändert haben.
|
|
# Der MediaRemote-Adapter wird aus dem mitgelieferten Quellcode gebaut,
|
|
# nicht als fertige Binary eingecheckt: so ist nachvollziehbar, was da
|
|
# läuft, und die Ad-hoc-Signatur passt zum restlichen Bundle.
|
|
#
|
|
# Er darf NICHT gelinkt werden. Geladen wird er von /usr/bin/perl, einem
|
|
# berechtigten Systemprogramm — im eigenen Prozess greift Apples
|
|
# Entitlement-Prüfung und es käme gar nichts an.
|
|
- name: MediaRemote-Adapter bauen und beilegen
|
|
script: |
|
|
OUT="$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/Vendor" \
|
|
"$SRCROOT/Scripts/build-mediaremote-adapter.sh"
|
|
inputFiles:
|
|
- $(SRCROOT)/Scripts/build-mediaremote-adapter.sh
|
|
outputFiles:
|
|
- $(BUILT_PRODUCTS_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/Vendor/mediaremote-adapter.pl
|
|
# Das Menüleistensymbol soll das Glyph aus dem App-Icon zeigen, nicht die
|
|
# abgerundete Kachel mit Verlauf. actool kompiliert das .icon-Bündel zu
|
|
# .icns und Assets.car und legt nichts Lesbares ins Bundle — und eine
|
|
# einzelne Datei daraus lässt sich nicht als Ressource referenzieren.
|
|
# Deshalb kopieren statt duplizieren: es bleibt bei einer Quelldatei.
|
|
outputFiles:
|
|
- $(BUILT_PRODUCTS_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/quartz.svg
|
|
settings:
|
|
base:
|
|
PRODUCT_BUNDLE_IDENTIFIER: com.scarriffleservices.onyx
|
|
INFOPLIST_FILE: Onyx/Info.plist
|
|
CODE_SIGN_ENTITLEMENTS: Onyx/Onyx.entitlements
|
|
CODE_SIGN_STYLE: Automatic
|
|
# Kein App-Sandbox: der privilegierte Helper, IOKit und der
|
|
# MediaRemote-Adapter sind damit unvereinbar. Der App-Group-Container
|
|
# bleibt trotzdem erreichbar — siehe docs/spikes/D-appgroup.md.
|
|
ENABLE_APP_SANDBOX: NO
|
|
ENABLE_USER_SCRIPT_SANDBOXING: NO
|
|
COMBINE_HIDPI_IMAGES: YES
|
|
# Icon-Composer-Bündel (Onyx/AppIcon.icon). actool übernimmt daraus
|
|
# alle Größen und die Transluzenz-Darstellung von macOS 26.
|
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|