Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7f2bf3c
Lower deployment target to macOS 14.5
nickmorozov Oct 13, 2026
02aa808
Match Direct Monitor checkbox by prefix
nickmorozov Oct 13, 2026
91286dd
Fix shortcut recorder lifecycle and protect navigation keys
nickmorozov Oct 13, 2026
54772bd
Control FC2 in-process via Accessibility instead of osascript
nickmorozov Oct 13, 2026
aa45fb3
Sign and notarize releases when Developer ID secrets are set
nickmorozov Oct 13, 2026
f167759
Add Sparkle 2 updates with an appcast on GitHub Pages
nickmorozov Oct 13, 2026
78ad044
Update README and site: compatibility, permissions, org links
nickmorozov Oct 13, 2026
80751e9
Document the FC2 remote-protocol prototype
nickmorozov Oct 13, 2026
dcbafb0
Add brew trust step to Homebrew install instructions
nickmorozov Oct 13, 2026
d3d783a
Link William Chan's shortcut postmortem from the README credit
nickmorozov Oct 13, 2026
bfa4751
Install via the fully qualified cask name
nickmorozov Oct 13, 2026
61756d4
CI: report test failures and crash logs
nickmorozov Oct 13, 2026
64ba719
Run shortcut recorder tests on the main actor
nickmorozov Oct 13, 2026
5ddf712
CI: print crash reasons from .ips reports
nickmorozov Oct 13, 2026
61f9976
Make ShortcutManager and HotkeyManager deinits nonisolated
nickmorozov Oct 13, 2026
1371cf4
Use enum.dev URLs for the appcast and cask homepage
nickmorozov Oct 13, 2026
75da192
Rename the app to "Volume Control" (display name only)
nickmorozov Oct 13, 2026
8089e48
Release: prerelease path for test builds
nickmorozov Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

env:
# Override deployment target for CI runner compatibility
MACOSX_DEPLOYMENT_TARGET: '15.0'
MACOSX_DEPLOYMENT_TARGET: '14.5'
# Skip code signing on CI
CODE_SIGN_IDENTITY: ''
CODE_SIGNING_REQUIRED: 'NO'
Expand All @@ -20,15 +20,67 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Select Xcode 26
run: |
XCODE=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -V | tail -1)
if [ -z "$XCODE" ]; then
echo "::error::No Xcode 26 on this runner. The project relies on Xcode 26 build settings (SWIFT_DEFAULT_ACTOR_ISOLATION)."
exit 1
fi
sudo xcode-select -s "$XCODE"
xcodebuild -version

- name: Build
run: >
xcodebuild -scheme "Focusrite Volume Control" -configuration Debug
MACOSX_DEPLOYMENT_TARGET=15.0
MACOSX_DEPLOYMENT_TARGET=14.5
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
build

- name: Test
run: >
xcodebuild test -scheme "Focusrite Volume Control" -configuration Debug
MACOSX_DEPLOYMENT_TARGET=15.0
-resultBundlePath build/TestResults.xcresult
MACOSX_DEPLOYMENT_TARGET=14.5
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

# Parallel test runners don't print why a test host crashed; surface it here
- name: Show test failures and crash reports
if: failure()
run: |
if [ -d build/TestResults.xcresult ]; then
xcrun xcresulttool get test-results tests --path build/TestResults.xcresult \
| python3 -c 'import json,sys
def walk(n, path=""):
name = n.get("name", "")
if n.get("nodeType") == "Failure Message":
print(f"{path}: {name}")
for c in n.get("children", []):
walk(c, name if n.get("nodeType") == "Test Case" else path)
for t in json.load(sys.stdin).get("testNodes", []):
walk(t)' || true
fi
# Report names contain spaces ("Volume Control-<date>.ips")
find ~/Library/Logs/DiagnosticReports -name 'Volume Control*' -mmin -60 -print0 2>/dev/null |
while IFS= read -r -d '' f; do
echo "=== $f"
# .ips files are one JSON header line plus a JSON body; print the abort reason and crashing frames
tail -n +2 "$f" | python3 -c 'import json,sys
r = json.load(sys.stdin)
print("termination:", r.get("termination", {}).get("indicator"))
print("asi:", json.dumps(r.get("asi"), indent=1))
t = next((t for t in r.get("threads", []) if t.get("triggered")), None)
imgs = r.get("usedImages", [])
for fr in (t or {}).get("frames", [])[:25]:
img = imgs[fr["imageIndex"]].get("name", "?") if fr.get("imageIndex") is not None and fr["imageIndex"] < len(imgs) else "?"
sym = fr.get("symbol") or hex(fr.get("imageOffset", 0))
print(" " + img + ": " + sym)' || head -60 "$f"
done

- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: TestResults
path: build/TestResults.xcresult
retention-days: 7
292 changes: 262 additions & 30 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions ExportOptions.plist
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
<string>developer-id</string>
<key>destination</key>
<string>export</string>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Developer ID Application</string>
<!-- teamID is added at build time from the APPLE_TEAM_ID secret (see release.yml) -->
</dict>
</plist>
58 changes: 45 additions & 13 deletions Focusrite Volume Control.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
objectVersion = 77;
objects = {

/* Begin PBXBuildFile section */
01A89DA32F2A00000084DBD8 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 01A89DA22F2A00000084DBD8 /* Sparkle */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
01A89D6E2F280BA90084DBD8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
Expand All @@ -17,7 +21,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
01A89D572F280BA90084DBD8 /* Focusrite Volume Control.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Focusrite Volume Control.app"; sourceTree = BUILT_PRODUCTS_DIR; };
01A89D572F280BA90084DBD8 /* Volume Control.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Volume Control.app"; sourceTree = BUILT_PRODUCTS_DIR; };
01A89D652F280BA90084DBD8 /* Focusrite Volume ControlTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Focusrite Volume ControlTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

Expand All @@ -39,6 +43,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
01A89DA32F2A00000084DBD8 /* Sparkle in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -64,7 +69,7 @@
01A89D582F280BA90084DBD8 /* Products */ = {
isa = PBXGroup;
children = (
01A89D572F280BA90084DBD8 /* Focusrite Volume Control.app */,
01A89D572F280BA90084DBD8 /* Volume Control.app */,
01A89D652F280BA90084DBD8 /* Focusrite Volume ControlTests.xctest */,
);
name = Products;
Expand All @@ -90,9 +95,10 @@
);
name = "Focusrite Volume Control";
packageProductDependencies = (
01A89DA22F2A00000084DBD8 /* Sparkle */,
);
productName = "Focusrite Volume Control";
productReference = 01A89D572F280BA90084DBD8 /* Focusrite Volume Control.app */;
productReference = 01A89D572F280BA90084DBD8 /* Volume Control.app */;
productType = "com.apple.product-type.application";
};
01A89D6A2F280BA90084DBD8 /* Focusrite Volume ControlTests */ = {
Expand Down Expand Up @@ -146,6 +152,9 @@
);
mainGroup = 01A89D4E2F280BA90084DBD8;
minimizedProjectReferenceProxies = 1;
packageReferences = (
01A89DA12F2A00000084DBD8 /* XCRemoteSwiftPackageReference "Sparkle" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 01A89D582F280BA90084DBD8 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -254,7 +263,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MACOSX_DEPLOYMENT_TARGET = 14.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -312,7 +321,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MACOSX_DEPLOYMENT_TARGET = 14.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
Expand All @@ -333,16 +342,18 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Focusrite-Volume-Control-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Volume Control";
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Focusrite Volume Control needs to send Apple events to Focusrite Control 2 to adjust volume and mute settings.";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = solutions.enum.FocusriteVolumeControl;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_MODULE_NAME = Focusrite_Volume_Control;
PRODUCT_NAME = "Volume Control";
REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand All @@ -366,16 +377,18 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Focusrite-Volume-Control-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Volume Control";
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Focusrite Volume Control needs to send Apple events to Focusrite Control 2 to adjust volume and mute settings.";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = solutions.enum.FocusriteVolumeControl;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_MODULE_NAME = Focusrite_Volume_Control;
PRODUCT_NAME = "Volume Control";
REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
Expand All @@ -394,7 +407,7 @@
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = 5FNR593BWG;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = solutions.enum.FocusriteVolumeControlTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -403,7 +416,7 @@
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Focusrite Volume Control.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Focusrite Volume Control";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Volume Control.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Volume Control";
};
name = Debug;
};
Expand All @@ -415,7 +428,7 @@
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = 5FNR593BWG;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 26.2;
MACOSX_DEPLOYMENT_TARGET = 14.5;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = solutions.enum.FocusriteVolumeControlTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -424,7 +437,7 @@
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Focusrite Volume Control.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Focusrite Volume Control";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Volume Control.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Volume Control";
};
name = Release;
};
Expand Down Expand Up @@ -459,6 +472,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
01A89DA12F2A00000084DBD8 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = exactVersion;
version = 2.10.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
01A89DA22F2A00000084DBD8 /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = 01A89DA12F2A00000084DBD8 /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 01A89D4F2F280BA90084DBD8 /* Project object */;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "01A89D562F280BA90084DBD8"
BuildableName = "Focusrite Volume Control.app"
BuildableName = "Volume Control.app"
BlueprintName = "Focusrite Volume Control"
ReferencedContainer = "container:Focusrite Volume Control.xcodeproj">
</BuildableReference>
Expand Down Expand Up @@ -58,7 +58,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "01A89D562F280BA90084DBD8"
BuildableName = "Focusrite Volume Control.app"
BuildableName = "Volume Control.app"
BlueprintName = "Focusrite Volume Control"
ReferencedContainer = "container:Focusrite Volume Control.xcodeproj">
</BuildableReference>
Expand All @@ -75,7 +75,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "01A89D562F280BA90084DBD8"
BuildableName = "Focusrite Volume Control.app"
BuildableName = "Volume Control.app"
BlueprintName = "Focusrite Volume Control"
ReferencedContainer = "container:Focusrite Volume Control.xcodeproj">
</BuildableReference>
Expand Down
Loading
Loading