Skip to content

Commit f3ee848

Browse files
committed
fix(xpc): make MenuBarItemService talk to ad-hoc-signed parent apps
Upstream PR jordanbaird#903 introduced MenuBarItemService.xpc with this line: listener = try XPCListener(service: name, requirement: .isFromSameTeam()) { ... } `.isFromSameTeam()` requires the listener and every peer to be signed with the same Apple Team Identifier. That works for builds Jordan produces from his Developer ID Application certificate. It does NOT work for any ad-hoc-signed build (TeamIdentifier = empty), because empty-vs-empty is never treated as a match — the listener rejects every check-in attempt with "Bogus check-in attempt. Ignoring." and "Dropping check-in message due to code signing requirement". The visible symptom is the Menu Bar Layout settings pane spinning forever on "Loading menu bar items…" — XPC never returns, so Ice never gets the cached item snapshots. This is the same class of bug reported in upstream issues jordanbaird#744 (46 reactions) and jordanbaird#891 (30 react- ions), and it bites every community fork that ships without an Apple Developer Program account — which is roughly every community fork. This commit reads the running process's actual Team Identifier via SecCodeCopySigningInformation; if it's nil (ad-hoc / unsigned), we fall through to the no-requirement activation path. If it's set (properly Developer-ID-signed build), behaviour is unchanged — the strict same-team requirement still applies. For our fire fork: - 0.11.13-fire.0..fire.1 (ad-hoc, this branch's defaults) → fixed. - 0.11.13-fire.2+ once signed with our Developer ID → unchanged, still uses .isFromSameTeam() because we'll have a team ID. Also bumps MARKETING_VERSION 0.11.13-fire.1 → 0.11.13-fire.2 and CURRENT_PROJECT_VERSION 1123 → 1124 so Sparkle in installed fire.1 recognizes this as a newer build and offers the update.
1 parent ef94a96 commit f3ee848

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

Ice.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@
417417
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
418418
CODE_SIGN_STYLE = Automatic;
419419
COMBINE_HIDPI_IMAGES = YES;
420-
CURRENT_PROJECT_VERSION = 1123;
420+
CURRENT_PROJECT_VERSION = 1124;
421421
DEAD_CODE_STRIPPING = YES;
422422
DEVELOPMENT_ASSET_PATHS = "";
423423
ENABLE_APP_SANDBOX = NO;
@@ -433,7 +433,7 @@
433433
"$(inherited)",
434434
"@executable_path/../Frameworks",
435435
);
436-
MARKETING_VERSION = "0.11.13-fire.1";
436+
MARKETING_VERSION = "0.11.13-fire.2";
437437
PRODUCT_BUNDLE_IDENTIFIER = com.jordanbaird.Ice;
438438
PRODUCT_NAME = "$(TARGET_NAME)";
439439
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -450,7 +450,7 @@
450450
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
451451
CODE_SIGN_STYLE = Automatic;
452452
COMBINE_HIDPI_IMAGES = YES;
453-
CURRENT_PROJECT_VERSION = 1123;
453+
CURRENT_PROJECT_VERSION = 1124;
454454
DEAD_CODE_STRIPPING = YES;
455455
DEVELOPMENT_ASSET_PATHS = "";
456456
ENABLE_APP_SANDBOX = NO;
@@ -466,7 +466,7 @@
466466
"$(inherited)",
467467
"@executable_path/../Frameworks",
468468
);
469-
MARKETING_VERSION = "0.11.13-fire.1";
469+
MARKETING_VERSION = "0.11.13-fire.2";
470470
PRODUCT_BUNDLE_IDENTIFIER = com.jordanbaird.Ice;
471471
PRODUCT_NAME = "$(TARGET_NAME)";
472472
SWIFT_EMIT_LOC_STRINGS = YES;

MenuBarItemService/Listener.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55

66
import OSLog
7+
import Security
78
import XPC
89

910
/// A wrapper around an XPC listener object.
@@ -24,6 +25,38 @@ final class Listener {
2425
cancel()
2526
}
2627

28+
/// Returns the Team Identifier of the currently running process, or
29+
/// `nil` if the binary is unsigned, ad-hoc signed, or the team
30+
/// identifier cannot be read.
31+
///
32+
/// We need this because `.isFromSameTeam()` (used on macOS 26+ to
33+
/// constrain XPC peers) silently rejects every connection when the
34+
/// service binary has no team identifier — which is the case for any
35+
/// ad-hoc-signed build, including community forks that ship without
36+
/// an Apple Developer Program account. Without this check the XPC
37+
/// service rejects its own parent app with "Bogus check-in attempt",
38+
/// and the Menu Bar Layout pane spins forever on
39+
/// "Loading menu bar items…".
40+
private static func ownTeamIdentifier() -> String? {
41+
var staticCode: SecStaticCode?
42+
guard
43+
SecStaticCodeCreateWithPath(Bundle.main.bundleURL as CFURL, [], &staticCode) == errSecSuccess,
44+
let code = staticCode
45+
else {
46+
return nil
47+
}
48+
var info: CFDictionary?
49+
guard
50+
SecCodeCopySigningInformation(code, SecCSFlags(rawValue: 0), &info) == errSecSuccess,
51+
let dict = info as? [String: Any],
52+
let teamID = dict[kSecCodeInfoTeamIdentifier as String] as? String,
53+
!teamID.isEmpty
54+
else {
55+
return nil
56+
}
57+
return teamID
58+
}
59+
2760
/// Handles a received message.
2861
private func handleMessage(_ message: XPCReceivedMessage) -> MenuBarItemService.Response? {
2962
do {
@@ -73,7 +106,13 @@ final class Listener {
73106
Logger.default.debug("Activating listener")
74107

75108
do {
76-
if #available(macOS 26.0, *) {
109+
// On macOS 26+ the listener can constrain peers by team
110+
// identifier, but only when we actually have a team
111+
// identifier to compare against. Ad-hoc-signed builds (every
112+
// community fork without an Apple Developer Program account)
113+
// have no team identifier and would reject every connection
114+
// — including their own parent app — silently.
115+
if #available(macOS 26.0, *), Self.ownTeamIdentifier() != nil {
77116
try uncheckedActivateWithSameTeamRequirement()
78117
} else {
79118
try uncheckedActivate()

0 commit comments

Comments
 (0)