@opentray/ext-badge-darwin-arm64 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @opentray/ext-badge-darwin-arm64
2
+
3
+ macOS arm64 Dock helper app package for `@opentray/ext-badge`.
4
+
5
+ This package owns the Dock-facing helper bundle used to present badge state with a real
6
+ application identity. The helper is intentionally separate from the broker process.
7
+ It currently projects badge text, overlay, attention, and click/quit lifecycle only; progress is not a Dock projection on this helper.
8
+
9
+ ## Build
10
+
11
+ ```bash
12
+ bash scripts/release/build-badge-dock-helper.sh /tmp/OpenTrayBadgeHelper.app.zip
13
+ ```
14
+
15
+ ## Runtime knobs
16
+
17
+ - `OPENTRAY_BADGE_DOCK_TITLE`
18
+ - `OPENTRAY_BADGE_DOCK_BADGE`
19
+ - `OPENTRAY_BADGE_DOCK_ICON_NAME`
20
+ - `OPENTRAY_BADGE_DOCK_ICON_PATH`
21
+ - `OPENTRAY_BADGE_DOCK_CLICK_SIGNAL`
22
+ - `OPENTRAY_BADGE_DOCK_LOG`
23
+
24
+ The helper stays source-only in git. The app bundle is staged locally or in release jobs.
package/app/Info.plist ADDED
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>OpenTrayBadgeHelper</string>
9
+ <key>CFBundleIconFile</key>
10
+ <string>AppIcon</string>
11
+ <key>CFBundleIdentifier</key>
12
+ <string>com.opentray.ext-badge.helper</string>
13
+ <key>CFBundleInfoDictionaryVersion</key>
14
+ <string>6.0</string>
15
+ <key>CFBundleName</key>
16
+ <string>OpenTray Badge Dock Helper</string>
17
+ <key>CFBundleDisplayName</key>
18
+ <string>OpenTray Badge</string>
19
+ <key>CFBundlePackageType</key>
20
+ <string>APPL</string>
21
+ <key>CFBundleShortVersionString</key>
22
+ <string>1.0</string>
23
+ <key>CFBundleVersion</key>
24
+ <string>1</string>
25
+ <key>LSMinimumSystemVersion</key>
26
+ <string>13.0</string>
27
+ <key>NSHighResolutionCapable</key>
28
+ <true/>
29
+ <key>NSPrincipalClass</key>
30
+ <string>NSApplication</string>
31
+ </dict>
32
+ </plist>
package/app/main.swift ADDED
@@ -0,0 +1,190 @@
1
+ import AppKit
2
+ import Foundation
3
+
4
+ final class BadgeDockHelper: NSObject, NSApplicationDelegate {
5
+ private let dockTitle = ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_TITLE"]
6
+ ?? "OpenTray Badge"
7
+ private let dockIconName = ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_ICON_NAME"]
8
+ ?? "bell.badge"
9
+ private let dockIconPath = ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_ICON_PATH"]
10
+ private let helperLog = argumentValue("--log-path") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_LOG"]
11
+ private let clickSignalPath = argumentValue("--click-signal") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_CLICK_SIGNAL"]
12
+ private let quitSignalPath = argumentValue("--quit-signal") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_QUIT_SIGNAL"]
13
+ private let quitNotifyPath = argumentValue("--quit-notify") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_QUIT_NOTIFY"]
14
+ private let stateDir = argumentValue("--state-dir") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_STATE_DIR"]
15
+ private let stateBadgePath = argumentValue("--badge-path") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_BADGE_PATH"]
16
+ private let stateTitlePath = argumentValue("--title-path") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_TITLE_PATH"]
17
+ private let stateIconNamePath = argumentValue("--icon-path") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_ICON_NAME_PATH"]
18
+ private let forceMultiOpen = CommandLine.arguments.contains("--force-multi-open")
19
+ private var lifecycleTimer: Timer?
20
+ private var stateTimer: Timer?
21
+ private var cachedDockTitle: String
22
+ private var cachedDockBadge: String?
23
+ private var cachedDockIconName: String
24
+ private var cachedDockIconPath: String?
25
+ private var renderedSignature: String = ""
26
+
27
+ override init() {
28
+ self.cachedDockTitle = argumentValue("--title") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_TITLE"] ?? "OpenTray Badge"
29
+ self.cachedDockBadge = argumentValue("--badge") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_BADGE"]
30
+ self.cachedDockIconName = argumentValue("--icon-name") ?? ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_ICON_NAME"] ?? "bell.badge"
31
+ self.cachedDockIconPath = ProcessInfo.processInfo.environment["OPENTRAY_BADGE_DOCK_ICON_PATH"]
32
+ super.init()
33
+ }
34
+
35
+ func applicationDidFinishLaunching(_ notification: Notification) {
36
+ NSApplication.shared.setActivationPolicy(.regular)
37
+ NSApplication.shared.applicationIconImage = placeholderDockIcon()
38
+ applyDockBadge(cachedDockBadge)
39
+ log("launched title=\(cachedDockTitle)")
40
+ startLifecycleWatch()
41
+ startStateWatch()
42
+ NSApp.activate(ignoringOtherApps: true)
43
+ }
44
+
45
+ func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
46
+ log("dock-reopen")
47
+ signalDockClick()
48
+ sender.activate(ignoringOtherApps: true)
49
+ return true
50
+ }
51
+
52
+ func applicationWillTerminate(_ notification: Notification) {
53
+ signalDockQuitNotify()
54
+ }
55
+
56
+ private func resolveDockIcon() -> NSImage? {
57
+ if let cachedDockIconPath, !cachedDockIconPath.isEmpty {
58
+ return NSImage(contentsOfFile: cachedDockIconPath)
59
+ }
60
+ return NSImage(systemSymbolName: cachedDockIconName, accessibilityDescription: cachedDockTitle)
61
+ }
62
+
63
+ private func applyDockBadge(_ badge: String?) {
64
+ guard let badge, !badge.isEmpty else {
65
+ NSApp.dockTile.badgeLabel = nil
66
+ return
67
+ }
68
+ NSApp.dockTile.badgeLabel = badge
69
+ }
70
+
71
+ private func signalDockClick() {
72
+ guard let clickSignalPath, !clickSignalPath.isEmpty else {
73
+ return
74
+ }
75
+ let payload = "\(Date().timeIntervalSince1970)\n"
76
+ try? payload.write(toFile: clickSignalPath, atomically: true, encoding: .utf8)
77
+ }
78
+
79
+ private func startLifecycleWatch() {
80
+ guard let quitSignalPath, !quitSignalPath.isEmpty else {
81
+ return
82
+ }
83
+ if forceMultiOpen {
84
+ return
85
+ }
86
+ lifecycleTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] timer in
87
+ guard let self else {
88
+ timer.invalidate()
89
+ return
90
+ }
91
+ if FileManager.default.fileExists(atPath: quitSignalPath) {
92
+ timer.invalidate()
93
+ NSApp.terminate(self)
94
+ }
95
+ }
96
+ }
97
+
98
+ private func startStateWatch() {
99
+ guard let stateDir, !stateDir.isEmpty else {
100
+ return
101
+ }
102
+ stateTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in
103
+ self?.refreshDockState()
104
+ }
105
+ }
106
+
107
+ private func refreshDockState() {
108
+ var changed = false
109
+ if let stateBadgePath, let badge = readTrimmedFile(stateBadgePath) {
110
+ cachedDockBadge = badge.isEmpty ? nil : badge
111
+ changed = true
112
+ }
113
+ if let stateTitlePath, let title = readTrimmedFile(stateTitlePath), !title.isEmpty {
114
+ cachedDockTitle = title
115
+ changed = true
116
+ }
117
+ if let stateIconNamePath, let iconName = readTrimmedFile(stateIconNamePath), !iconName.isEmpty {
118
+ cachedDockIconName = iconName
119
+ changed = true
120
+ }
121
+ if changed {
122
+ updateDockTile()
123
+ }
124
+ }
125
+
126
+ private func readTrimmedFile(_ path: String) -> String? {
127
+ guard FileManager.default.fileExists(atPath: path) else {
128
+ return nil
129
+ }
130
+ return (try? String(contentsOfFile: path, encoding: .utf8))?.trimmingCharacters(in: .whitespacesAndNewlines)
131
+ }
132
+
133
+ private func updateDockTile() {
134
+ let signature = [cachedDockTitle, cachedDockBadge ?? "", cachedDockIconName, cachedDockIconPath ?? ""].joined(separator: "|")
135
+ guard signature != renderedSignature else {
136
+ return
137
+ }
138
+ renderedSignature = signature
139
+ applyDockBadge(cachedDockBadge)
140
+ NSApplication.shared.applicationIconImage = resolveDockIcon() ?? placeholderDockIcon()
141
+ NSApplication.shared.dockTile.display()
142
+ }
143
+
144
+ private func placeholderDockIcon() -> NSImage {
145
+ let image = NSImage(size: NSSize(width: 64, height: 64))
146
+ image.lockFocus()
147
+ NSColor.clear.set()
148
+ NSBezierPath(rect: NSRect(x: 0, y: 0, width: 64, height: 64)).fill()
149
+ image.unlockFocus()
150
+ return image
151
+ }
152
+
153
+ private func signalDockQuitNotify() {
154
+ guard let quitNotifyPath, !quitNotifyPath.isEmpty else {
155
+ return
156
+ }
157
+ let payload = "\(Date().timeIntervalSince1970)\n"
158
+ try? payload.write(toFile: quitNotifyPath, atomically: true, encoding: .utf8)
159
+ }
160
+
161
+ private func log(_ message: String) {
162
+ NSLog("OpenTray badge helper: %@", message)
163
+ if let helperLog {
164
+ try? "\(message)\n".appendToFile(atPath: helperLog)
165
+ }
166
+ }
167
+ }
168
+
169
+ private extension String {
170
+ func appendToFile(atPath path: String) throws {
171
+ let url = URL(fileURLWithPath: path)
172
+ let existing = (try? String(contentsOf: url, encoding: .utf8)) ?? ""
173
+ try (existing + self).write(to: url, atomically: true, encoding: .utf8)
174
+ }
175
+ }
176
+
177
+ private func argumentValue(_ name: String) -> String? {
178
+ let arguments = CommandLine.arguments
179
+ for index in 0..<arguments.count {
180
+ if arguments[index] == name, index + 1 < arguments.count {
181
+ return arguments[index + 1]
182
+ }
183
+ }
184
+ return nil
185
+ }
186
+
187
+ let delegate = BadgeDockHelper()
188
+ NSApplication.shared.delegate = delegate
189
+ NSApplication.shared.setActivationPolicy(.regular)
190
+ NSApplication.shared.run()
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@opentray/ext-badge-darwin-arm64",
3
+ "version": "0.1.0",
4
+ "description": "OpenTray badge Dock helper app for macOS arm64.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/jixoai/opentray"
9
+ },
10
+ "files": [
11
+ "README.md",
12
+ "app"
13
+ ],
14
+ "os": [
15
+ "darwin"
16
+ ],
17
+ "cpu": [
18
+ "arm64"
19
+ ],
20
+ "peerDependencies": {
21
+ "opentray": ">=0.0.0"
22
+ }
23
+ }