diff --git a/BLEUnlock/AppDelegate.swift b/BLEUnlock/AppDelegate.swift index 353bc5d..6886e04 100644 --- a/BLEUnlock/AppDelegate.swift +++ b/BLEUnlock/AppDelegate.swift @@ -22,6 +22,7 @@ private let pauseNowPlayingNoticeShownKey = "pauseNowPlayingNoticeShown" private let autoCheckUpdatesKey = "autoCheckUpdates" private let legacyBundleIDMigrationKey = "legacyBundleIDMigrationComplete" private let runInBackgroundKey = "runInBackground" +private let statusItemAutosaveName = "BLEUnlockStatusItem" private enum AppNotificationKind: String { case lock @@ -150,7 +151,13 @@ func notifyUpdateAvailable() { @NSApplicationMain #endif class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenuItemValidation, NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate, BLEDelegate { - let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + let statusItem: NSStatusItem = { + let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + // Keep AppKit's persisted visibility state tied to a stable identity + // instead of an automatically assigned Item-N name. + item.autosaveName = statusItemAutosaveName + return item + }() let ble = BLE() let mainMenu = NSMenu() var deviceMenu = NSMenu() diff --git a/CHANGELOG.cn.md b/CHANGELOG.cn.md index c98f0ac..21cbe93 100644 --- a/CHANGELOG.cn.md +++ b/CHANGELOG.cn.md @@ -2,6 +2,7 @@ ## 未发布 +- 为菜单栏项目设置稳定标识,并避免安装脚本在 macOS 26 上自动启动应用,防止 Control Center 把 BLEUnlock 错误归属到终端或安装工具后隐藏图标。 - Release 在更新 Homebrew 前,使用公开脚本安装刚发布的产物,并复验版本、Bundle ID 与代码签名。 ## 1.15.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index bc2fe17..b18491f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Give the status item a stable identity and stop the installer from auto-launching BLEUnlock on macOS 26, preventing Control Center from attributing and hiding it under the terminal or installation tool. - Make Release install the newly published asset through the public script and verify its version, bundle ID, and code signature before updating Homebrew. ## 1.15.1 diff --git a/README.en.md b/README.en.md index 0e97273..ec5293a 100644 --- a/README.en.md +++ b/README.en.md @@ -65,7 +65,7 @@ brew install --cask bifrost-proxy/tap/unlock curl -fsSL https://raw.githubusercontent.com/bifrost-proxy/BLEUnlock/master/install.sh | bash ``` -The script downloads the latest DMG and checksum from this repository, verifies SHA-256, app signature integrity, and the bundle ID, replaces `/Applications/BLEUnlock.app`, and verifies the installed signature again. +The script downloads the latest DMG and checksum from this repository, verifies SHA-256, app signature integrity, and the bundle ID, replaces `/Applications/BLEUnlock.app`, and verifies the installed signature again. Because macOS 26 attributes third-party menu bar items to their launch source, the script does not auto-launch after installation there; open BLEUnlock from Finder's Applications folder instead. ### Manual installation @@ -124,12 +124,25 @@ Launch at Login | Launches BLEUnlock when you login. Run in Background (Hide Menu Bar Icon) | Hides the menu bar icon without showing a Dock icon while BLEUnlock keeps scanning and performing automatic lock/unlock actions. To restore the menu bar icon, open BLEUnlock again from Applications; this also turns off background hiding. Set Minimum RSSI | Devices with RSSI below this value will not be displayed in the device scan list. The default is `-60 dBm`. +On macOS 26 or later, you can also confirm that BLEUnlock is enabled in System Settings > Menu Bar > Allow in the Menu Bar. + ### Background Performance With at least one bound device and background mode enabled, BLEUnlock targets average CPU below 10% and maximum resident memory below 80 MB in steady state. Developers can run `scripts/profile-background.sh 60` for a 60-second sample; the script fails if either limit is exceeded. ## Troubleshooting +### The menu bar icon is missing on macOS 26 and BLEUnlock is not in Settings + +macOS 26 Control Center records which application launched a third-party menu bar item. If a terminal, development tool, or installation script launches BLEUnlock for the first time, Control Center can incorrectly assign the item to that launcher. When the launcher is disabled under Allow in the Menu Bar, BLEUnlock is hidden as well and has no separate Settings row. + +1. Quit BLEUnlock. +2. Open System Settings > Menu Bar, scroll to the bottom, and choose Reset Control Center. This resets the menu bar and Control Center arrangement. +3. In Finder, open Applications and launch BLEUnlock directly. Do not use Terminal or a development tool for this first launch. +4. Return to System Settings > Menu Bar > Allow in the Menu Bar and confirm that BLEUnlock is enabled. + +The current installation script avoids auto-launching BLEUnlock on macOS 26 so the incorrect ownership is not recreated. Pass `--launch` explicitly only when automatic launch is required. + ### Can't find my device in the list If your BLE device is not from Apple, BLEUnlock may not able to find the device name. diff --git a/README.md b/README.md index 36631a2..2891cda 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ brew install --cask bifrost-proxy/tap/unlock curl -fsSL https://raw.githubusercontent.com/bifrost-proxy/BLEUnlock/master/install.sh | bash ``` -脚本会从本仓库下载最新 DMG 和校验文件,校验 SHA-256、App 签名完整性和 Bundle ID 后覆盖安装到 `/Applications/BLEUnlock.app`,并对安装结果再次执行签名校验。 +脚本会从本仓库下载最新 DMG 和校验文件,校验 SHA-256、App 签名完整性和 Bundle ID 后覆盖安装到 `/Applications/BLEUnlock.app`,并对安装结果再次执行签名校验。macOS 26 会按启动来源管理第三方菜单栏项目,因此脚本安装完成后不会自动启动;请从 Finder 的“应用程序”中打开 BLEUnlock。 ### 手动安装 @@ -122,12 +122,25 @@ BLEUnlock 会开始扫描附近的 BLE 设备。选择你的设备后即可开 后台运行(隐藏菜单栏图标) | 隐藏菜单栏图标且不显示 Dock 图标,但 BLEUnlock 仍继续扫描并执行自动锁定/解锁。需要恢复菜单栏图标时,从“应用程序”中再次打开 BLEUnlock;该操作会自动退出后台隐藏模式。 设置最小 RSSI | RSSI 低于该值的设备不会显示在设备扫描列表中,默认值为 `-60 dBm`。 +在 macOS 26 或更高版本中,也可以在“系统设置 → 菜单栏 → 允许在菜单栏显示”中确认 BLEUnlock 已开启。 + ### 后台性能 绑定设备并启用后台运行后,BLEUnlock 的稳态性能目标是平均 CPU 低于 10%、最大常驻内存低于 80 MB。开发者可以运行 `scripts/profile-background.sh 60` 进行 60 秒采样;任一指标超限时脚本会返回失败。 ## 故障排除 +### macOS 26 中菜单栏图标消失,设置中也没有 BLEUnlock + +macOS 26 的 Control Center 会记录第三方菜单栏项目的启动来源。如果 BLEUnlock 首次由终端、开发工具或安装脚本自动启动,系统可能把它错误归属到启动它的应用;当那个应用不允许显示在菜单栏时,BLEUnlock 也会被隐藏,并且不会单独出现在设置列表中。 + +1. 退出 BLEUnlock。 +2. 打开“系统设置 → 菜单栏”,滚动到底部并点击“还原控制中心…”。这会还原菜单栏和控制中心的排列。 +3. 在 Finder 中打开“应用程序”,直接启动 BLEUnlock;不要从终端或开发工具启动第一次运行。 +4. 回到“系统设置 → 菜单栏 → 允许在菜单栏显示”,确认 BLEUnlock 已开启。 + +新版安装脚本在 macOS 26 上默认不再自动启动 BLEUnlock,以避免再次产生错误归属。确实需要脚本启动时,可以显式传入 `--launch`。 + ### 设备列表里找不到我的设备 如果你的 BLE 设备不是 Apple 设备,BLEUnlock 可能无法读取设备名称。 diff --git a/human_tests/README.md b/human_tests/README.md new file mode 100644 index 0000000..4577be9 --- /dev/null +++ b/human_tests/README.md @@ -0,0 +1,3 @@ +# Human Test Index + +- [macOS 26 menu bar registration](menu-bar-registration.md) diff --git a/human_tests/menu-bar-registration.md b/human_tests/menu-bar-registration.md new file mode 100644 index 0000000..c787f53 --- /dev/null +++ b/human_tests/menu-bar-registration.md @@ -0,0 +1,47 @@ +# macOS 26 Menu Bar Registration + +## Observed failure baseline + +On macOS 26.5, a read-only decode of Control Center's `trackedApplications` showed `com.bifrost-proxy.BLEUnlock` inside the `menuItemLocations` array owned by `com.openai.codex`, whose `isAllowed` value was `false`. BLEUnlock had no independent row in System Settings, its process remained running, and its menu bar icon was hidden. This confirms incorrect launch-source attribution rather than an app crash or the `runInBackground` preference. + +## MB-01: First launch registration + +- Goal: Verify that macOS registers BLEUnlock as a named third-party menu bar item. +- Environment: macOS 26.5 on Apple silicon; release-style app installed in `/Applications`. +- Preconditions: Automatic lock/unlock disabled, launch at login disabled, and background hiding disabled. +- Steps: + 1. Reset Control Center to clear stale ownership, then launch the app directly from Finder. + 2. Open System Settings > Menu Bar. + 3. Scroll to Allow in the Menu Bar. + 4. Locate BLEUnlock and verify that its switch is enabled. + 5. Verify that the BLEUnlock status icon is present and opens its menu. +- Expected: BLEUnlock is listed, enabled, and its status icon is usable. +- Actual: Pending user approval because Reset Control Center changes the user's menu bar arrangement. +- Cleanup: Quit the test app and restore the preferred menu bar arrangement. + +## MB-02: Hide and restore + +- Goal: Verify that BLEUnlock can intentionally hide its menu bar icon and restore it by reopening the app. +- Environment: Same as MB-01. +- Preconditions: BLEUnlock is running with its menu bar icon visible. +- Steps: + 1. Enable Run in Background (Hide Menu Bar Icon) and confirm the warning. + 2. Verify that the icon disappears while the process remains running. + 3. Open BLEUnlock Local again. + 4. Verify that the icon returns and the background-hiding option is disabled. +- Expected: Hiding is intentional and reversible; reopening restores the named status item. +- Actual: Pending until MB-01 can be completed. +- Cleanup: Quit BLEUnlock and restore any changed preference. + +## MB-03: Installer ownership guard + +- Goal: Verify that the installation script does not auto-launch BLEUnlock on macOS 26 unless explicitly requested. +- Environment: macOS 26.5 on Apple silicon; installer uses a temporary install directory and does not replace the installed app. +- Preconditions: None. +- Steps: + 1. Run the installer argument/static checks. + 2. Inspect the macOS-major-version branch for default, `--launch`, and `--no-launch` modes. + 3. Confirm release automation continues to pass `--no-launch`. +- Expected: Default is no launch on macOS 26; `--launch` opts in; macOS 25 and earlier retain automatic launch. +- Actual: Passed on macOS 26.5 using `v1.15.1` and a temporary install directory. Download, checksum, signature verification, copy, and post-install verification completed; no test app process launched, and the installer printed the Finder launch instruction. +- Cleanup: None. diff --git a/install.sh b/install.sh index ff1be8f..760dd54 100755 --- a/install.sh +++ b/install.sh @@ -5,14 +5,14 @@ set -euo pipefail REPOSITORY="bifrost-proxy/BLEUnlock" INSTALL_DIR="${BLEUNLOCK_INSTALL_DIR:-/Applications}" RELEASE_TAG="${BLEUNLOCK_VERSION:-}" -LAUNCH_APP=1 +LAUNCH_MODE="auto" usage() { cat <<'EOF' Install the latest BLEUnlock GitHub Release into /Applications. Usage: - install.sh [--version vX.Y.Z] [--install-dir DIR] [--no-launch] + install.sh [--version vX.Y.Z] [--install-dir DIR] [--launch|--no-launch] Environment variables: BLEUNLOCK_VERSION Release tag to install, for example v1.14.3 @@ -38,7 +38,11 @@ while [[ $# -gt 0 ]]; do shift 2 ;; --no-launch) - LAUNCH_APP=0 + LAUNCH_MODE="never" + shift + ;; + --launch) + LAUNCH_MODE="always" shift ;; -h|--help) @@ -53,10 +57,19 @@ done [[ "$(uname -s)" == "Darwin" ]] || die "macOS is required" -for command in curl hdiutil shasum ditto codesign defaults; do +for command in curl hdiutil shasum ditto codesign defaults sw_vers cut open; do command -v "${command}" >/dev/null 2>&1 || die "required command not found: ${command}" done +macos_major="$(sw_vers -productVersion | cut -d. -f1)" +[[ "${macos_major}" =~ ^[0-9]+$ ]] || die "could not determine the macOS major version" + +launch_app=1 +if [[ "${LAUNCH_MODE}" == "never" ]] || + [[ "${LAUNCH_MODE}" == "auto" && "${macos_major}" -ge 26 ]]; then + launch_app=0 +fi + if [[ -z "${RELEASE_TAG}" ]]; then latest_url="$(curl --fail --silent --show-error --location \ --output /dev/null \ @@ -132,6 +145,9 @@ codesign --verify --deep --strict --verbose=2 "${destination_app}" || \ printf 'Installed BLEUnlock %s to %s\n' "${RELEASE_TAG}" "${destination_app}" -if [[ ${LAUNCH_APP} -eq 1 ]]; then +if [[ ${launch_app} -eq 1 ]]; then open "${destination_app}" +elif [[ "${LAUNCH_MODE}" == "auto" && "${macos_major}" -ge 26 ]]; then + printf 'macOS 26 manages third-party menu bar ownership. Open %s from Finder to finish setup.\n' \ + "${destination_app}" fi diff --git a/scripts/build-local.sh b/scripts/build-local.sh index 34b5df6..f8cc4ed 100755 --- a/scripts/build-local.sh +++ b/scripts/build-local.sh @@ -131,6 +131,13 @@ xcrun --sdk macosx swiftc \ -lsqlite3 \ -o "${EXECUTABLE}" +# Recent Command Line Tools can place the dSYM beside the output binary. Keep +# debug symbols outside the app bundle so packaging verification does not treat +# the DWARF image as an unsigned nested executable. +if [[ -d "${EXECUTABLE}.dSYM" ]]; then + mv "${EXECUTABLE}.dSYM" "${BUILD_DIR}/BLEUnlockLocal.dSYM" +fi + cp "${ROOT_DIR}/BLEUnlock/Info.plist" "${CONTENTS_DIR}/Info.plist" plutil -replace CFBundleExecutable -string BLEUnlockLocal "${CONTENTS_DIR}/Info.plist" plutil -replace CFBundleIdentifier -string "${BUNDLE_ID}" "${CONTENTS_DIR}/Info.plist"