@ericsanchezok/meta-synergy 1.1.21 → 1.1.23
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/app/darwin/assets/app-icon.png +0 -0
- package/app/darwin/assets/app-icon.svg +14 -0
- package/app/darwin/assets/status-icon.png +0 -0
- package/app/darwin/assets/status-icon.svg +12 -0
- package/{src/menu.swift → app/darwin/main.swift} +109 -52
- package/package.json +1 -1
- package/script/build-app.ts +181 -0
- package/src/cli.ts +1 -1
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" width="286.018" height="285.468" viewBox="0 0 286.018 285.468">
|
|
2
|
+
<defs>
|
|
3
|
+
<clipPath id="clip_0">
|
|
4
|
+
<path transform="matrix(1,0,0,-1,0,285.468)" d="M0 0H286.018V285.468H0Z" clip-rule="evenodd"/>
|
|
5
|
+
</clipPath>
|
|
6
|
+
</defs>
|
|
7
|
+
<g clip-path="url(#clip_0)">
|
|
8
|
+
<path transform="matrix(.1,0,0,-.1,0,285.468)" d="M668.574 1077.45C596.992 1022.7 503.922 907.094 566.523 814.824 626.691 726.133 764.281 785.473 821.723 849.152 907.395 944.133 995.523 1120.89 1045.99 1239.64 1046.98 1241.98 1052.8 1254.61 1051.72 1255.7 916.191 1213.88 782.164 1164.36 668.574 1077.45"/>
|
|
9
|
+
<path transform="matrix(.1,0,0,-.1,0,285.468)" d="M1343.13 409.57C1313.08 409.57 1298.06 390.859 1298.06 353.48 1298.06 335.129 1301.74 321.09 1309.1 311.352 1316.45 301.609 1327.13 296.738 1341.1 296.738 1371.35 296.738 1386.48 315.648 1386.48 353.48 1386.48 390.859 1372.03 409.57 1343.13 409.57"/>
|
|
10
|
+
<path transform="matrix(.1,0,0,-.1,0,285.468)" d="M1617.59 409.57C1587.55 409.57 1572.52 390.859 1572.52 353.48 1572.52 335.129 1576.21 321.09 1583.57 311.352 1590.91 301.609 1601.59 296.738 1615.57 296.738 1645.82 296.738 1660.95 315.648 1660.95 353.48 1660.95 390.859 1646.5 409.57 1617.59 409.57"/>
|
|
11
|
+
<path transform="matrix(.1,0,0,-.1,0,285.468)" d="M2375.91 1799.1C2230.6 1625.99 2021.51 1530.97 1812.66 1456.75 1753.3 1309.8 1696.05 1159.21 1675.72 1000.71 1666.08 925.543 1650.54 772.953 1770.55 791.652 1866.13 806.551 1969.66 929.641 2027.17 1002.49 2048.79 1029.88 2069.83 1064.05 2091.9 1089.57 2132.09 1136.06 2205.48 1098.69 2190.65 1038.91 2185.66 1018.79 2145.83 968.063 2131.54 949.223 2040.22 828.914 1876.8 652.543 1716.83 642.453 1616.27 636.102 1533.28 669.434 1487.84 762.141 1411.94 916.98 1462.83 1133.13 1512.78 1289.92 1522.07 1319.08 1536.68 1351.72 1544.08 1380.37 1544.43 1381.72 1545.7 1384.88 1543.97 1384.98L1321.54 1329.34C1275.68 1215.84 1223.88 1104.2 1159.07 1000.3 1033.16 798.441 836.422 593.023 573.441 645.883 443.293 672.051 370.621 771.543 386.055 904.531 407.914 1092.87 649.063 1228.8 805.711 1296.55 905.621 1339.76 1010.25 1370.82 1114.83 1400.77 1185.18 1556.7 1253.02 1714.2 1341.8 1860.72 1342.28 1862.78 1341.69 1863.55 1339.6 1862.92 1329.25 1859.81 1317.99 1853.92 1306.9 1850.52 1191.84 1815.22 1041.09 1819.91 1007.37 1960.63 999.914 1991.78 1001.29 2056.21 981.121 2078.67 960.082 2102.1 914.652 2080.51 893.434 2065.58 831.055 2021.7 786.414 1946.36 754.332 1878.51 736.652 1841.14 725.824 1786.67 673.633 1786.23 645.074 1785.99 610.363 1809.79 612.844 1841.14 614.895 1867.02 635.902 1915.87 647.613 1940.14 695.203 2038.74 772.773 2139.48 869.824 2192.91 974.012 2250.26 1144.47 2270.65 1200.8 2139.02 1216.45 2102.43 1217.04 2061.12 1229.18 2024.62 1239.48 1993.66 1267.92 1986.87 1297.46 1989.23 1361.92 1994.38 1440.95 2060.04 1489.15 2101.16 1537.36 2142.31 1610.38 2227.15 1670.95 2239.52 1724.02 2250.37 1758.43 2222.56 1752 2167.48 1747.78 2131.27 1703.27 2108.72 1677.81 2086.86 1615.66 2033.53 1578.38 1961.71 1543.45 1889.04 1477.81 1752.47 1427.13 1609.19 1373.4 1467.64L1600.32 1525.54C1649.8 1625.11 1696.93 1723.91 1758.15 1817.13 1855.39 1965.2 2031.4 2181.42 2208.78 2227.77 2322.12 2257.38 2449.36 2238.19 2475.91 2106.46 2498.35 1995.21 2445.84 1882.42 2375.91 1799.1M1823.88 283.301C1813.46 274.652 1798.89 270.332 1780.14 270.332 1760.66 270.332 1746.36 273.391 1737.24 279.504 1728.13 285.613 1722.12 295.461 1719.22 309.031L1749.99 313.852C1751.44 306.703 1754.51 301.57 1759.23 298.473 1763.94 295.352 1770.71 293.801 1779.51 293.801 1787.49 293.801 1793.94 295.504 1798.85 298.934 1803.78 302.344 1806.24 307.883 1806.24 315.563 1806.24 320.211 1805.15 324.082 1802.97 327.133 1800.8 330.191 1797.56 332.82 1793.26 335.063 1788.96 337.273 1780.6 340.063 1768.16 343.363 1756.77 346.371 1748.31 349.762 1742.76 353.543 1737.21 357.332 1732.94 362.094 1729.94 367.844 1726.93 373.602 1725.43 380.664 1725.43 389.051 1725.43 403.863 1730.07 415.391 1739.34 423.621 1748.62 431.863 1762.15 435.98 1779.97 435.98 1796.45 435.98 1809.11 432.941 1817.97 426.902 1826.83 420.844 1832.4 411.543 1834.68 399.012L1803.6 395.73C1802.45 401.844 1799.95 406.141 1796.06 408.641 1792.18 411.121 1786.91 412.363 1780.29 412.363 1765.68 412.363 1758.38 405.941 1758.38 393.102 1758.38 388.961 1759.28 385.531 1761.1 382.832 1762.9 380.152 1765.55 377.852 1769.02 375.922 1772.49 374.004 1780.39 371.293 1792.72 367.781 1806.29 363.922 1815.92 360.07 1821.62 356.203 1827.32 352.301 1831.72 347.41 1834.84 341.504 1837.93 335.594 1839.49 328.203 1839.49 319.281 1839.49 303.961 1834.28 291.961 1823.88 283.301M1676.72 292.402C1662.26 277.684 1641.99 270.332 1615.88 270.332 1590.6 270.332 1570.82 277.793 1556.52 292.711 1542.22 307.633 1535.07 327.891 1535.07 353.48 1535.07 379.461 1542.24 399.723 1556.6 414.242 1570.94 428.742 1591.02 435.98 1616.81 435.98 1643.44 435.98 1663.69 428.914 1677.57 414.773 1691.45 400.633 1698.39 380.191 1698.39 353.48 1698.39 327.473 1691.17 307.102 1676.72 292.402M1496.97 273.434H1461.23V499.371H1496.97ZM1402.25 292.402C1387.8 277.684 1367.52 270.332 1341.41 270.332 1316.13 270.332 1296.35 277.793 1282.05 292.711 1267.75 307.633 1260.6 327.891 1260.6 353.48 1260.6 379.461 1267.78 399.723 1282.13 414.242 1296.48 428.742 1316.55 435.98 1342.34 435.98 1368.97 435.98 1389.23 428.914 1403.1 414.773 1416.98 400.633 1423.92 380.191 1423.92 353.48 1423.92 327.473 1416.71 307.102 1402.25 292.402M1219.9 273.434H1183.54V370.871H1078.18V273.434H1040.42V492.402H1078.18V402.262H1183.54V492.402H1219.9ZM1432.58 2843.11C649.824 2843.11 15.2617 2208.55 15.2617 1425.79 15.2617 643.031 649.824 8.48047 1432.58 8.48047 2215.35 8.48047 2849.91 643.031 2849.91 1425.79 2849.91 2208.55 2215.35 2843.11 1432.58 2843.11"/>
|
|
12
|
+
<path transform="matrix(.1,0,0,-.1,0,285.468)" d="M2225.69 2092.12C2153.21 2059.05 2073.45 1947.94 2031.12 1880.86 1978.69 1797.78 1931.61 1709.42 1888.95 1620.97 1975.49 1652.62 2059.38 1693.64 2133.79 1748.12 2213.71 1806.64 2305.65 1897.29 2333.11 1995.22 2358.83 2086.97 2316.13 2133.38 2225.69 2092.12"/>
|
|
13
|
+
</g>
|
|
14
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
|
2
|
+
<rect width="128" height="128" fill="none" />
|
|
3
|
+
<text
|
|
4
|
+
x="64"
|
|
5
|
+
y="92"
|
|
6
|
+
text-anchor="middle"
|
|
7
|
+
font-family="BM JUA"
|
|
8
|
+
font-size="106"
|
|
9
|
+
letter-spacing="-4"
|
|
10
|
+
fill="#000"
|
|
11
|
+
>S</text>
|
|
12
|
+
</svg>
|
|
@@ -6,15 +6,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
6
6
|
private var menu: NSMenu!
|
|
7
7
|
private var hostProcess: Process?
|
|
8
8
|
private var pollTimer: Timer?
|
|
9
|
-
|
|
10
|
-
private let rootPath: String
|
|
9
|
+
private var statusImage: NSImage?
|
|
11
10
|
|
|
12
11
|
private let agentItem = NSMenuItem(title: "Agent ID: unknown", action: #selector(copyAgentID), keyEquivalent: "")
|
|
13
12
|
private let envItem = NSMenuItem(title: "Env ID: unknown", action: #selector(copyEnvID), keyEquivalent: "")
|
|
14
13
|
private let loginItem = NSMenuItem(title: "Login with Holos", action: #selector(login), keyEquivalent: "")
|
|
15
14
|
private let connectionItem = NSMenuItem(title: "Holos: disconnected", action: #selector(reconnect), keyEquivalent: "")
|
|
16
15
|
private let collaborationItem = NSMenuItem(title: "Collaboration: on", action: #selector(toggleCollaboration), keyEquivalent: "")
|
|
17
|
-
private let terminateItem = NSMenuItem(title: "Terminate Collaboration", action: #selector(
|
|
16
|
+
private let terminateItem = NSMenuItem(title: "Terminate Collaboration", action: #selector(terminateCollaboration), keyEquivalent: "")
|
|
18
17
|
private let refreshItem = NSMenuItem(title: "Refresh", action: #selector(refreshStatus), keyEquivalent: "r")
|
|
19
18
|
private let quitItem = NSMenuItem(title: "Quit", action: #selector(quitApp), keyEquivalent: "q")
|
|
20
19
|
|
|
@@ -22,20 +21,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
22
21
|
private var currentEnvID: String?
|
|
23
22
|
private var hostStarted = false
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
24
|
+
private lazy var runtimeURL: URL? = {
|
|
25
|
+
Bundle.main.resourceURL?.appendingPathComponent("meta-synergy-runtime")
|
|
26
|
+
}()
|
|
29
27
|
|
|
30
28
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
31
29
|
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
32
|
-
|
|
30
|
+
statusImage = loadStatusImage()
|
|
31
|
+
applyStatusAppearance(textFallback: "Meta", toolTip: "MetaSynergy")
|
|
33
32
|
|
|
34
33
|
menu = NSMenu()
|
|
34
|
+
|
|
35
35
|
agentItem.target = self
|
|
36
36
|
envItem.target = self
|
|
37
|
-
configureCopyIcon(for: agentItem)
|
|
38
|
-
configureCopyIcon(for: envItem)
|
|
39
37
|
loginItem.target = self
|
|
40
38
|
connectionItem.target = self
|
|
41
39
|
collaborationItem.target = self
|
|
@@ -43,6 +41,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
43
41
|
refreshItem.target = self
|
|
44
42
|
quitItem.target = self
|
|
45
43
|
|
|
44
|
+
configureCopyIcon(for: agentItem)
|
|
45
|
+
configureCopyIcon(for: envItem)
|
|
46
|
+
|
|
46
47
|
menu.addItem(agentItem)
|
|
47
48
|
menu.addItem(envItem)
|
|
48
49
|
menu.addItem(loginItem)
|
|
@@ -65,23 +66,27 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
@objc private func login() {
|
|
68
|
-
_ =
|
|
69
|
+
_ = runCommand(["login"])
|
|
69
70
|
startHostIfPossible()
|
|
70
71
|
refreshStatus()
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
@objc private func reconnect() {
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
if hostProcess == nil {
|
|
76
|
+
startHostIfPossible()
|
|
77
|
+
} else {
|
|
78
|
+
_ = runCommand(["reconnect"])
|
|
79
|
+
}
|
|
76
80
|
refreshStatus()
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
@objc private func refreshStatus() {
|
|
80
|
-
guard let output =
|
|
84
|
+
guard let output = runCommand(["status"]) else {
|
|
81
85
|
connectionItem.title = "Holos: unavailable"
|
|
82
86
|
connectionItem.isEnabled = false
|
|
83
87
|
collaborationItem.isHidden = true
|
|
84
88
|
terminateItem.isHidden = true
|
|
89
|
+
refreshItem.isHidden = true
|
|
85
90
|
return
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -129,23 +134,25 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
129
134
|
|
|
130
135
|
if let sessionID = session?["sessionID"] as? String,
|
|
131
136
|
let remoteAgentID = session?["remoteAgentID"] as? String {
|
|
132
|
-
terminateItem.title = "Terminate Collaboration (\(remoteAgentID), \(sessionID.prefix(8)))"
|
|
137
|
+
terminateItem.title = "Terminate Collaboration (\(truncateID(remoteAgentID)), \(sessionID.prefix(8)))"
|
|
133
138
|
terminateItem.isEnabled = true
|
|
139
|
+
applyStatusAppearance(textFallback: "Meta Busy", toolTip: "MetaSynergy: busy")
|
|
134
140
|
} else {
|
|
135
141
|
terminateItem.title = "Terminate Collaboration"
|
|
136
142
|
terminateItem.isEnabled = false
|
|
143
|
+
if hasAuth {
|
|
144
|
+
applyStatusAppearance(
|
|
145
|
+
textFallback: collaborationEnabled ? "Meta" : "Meta Off",
|
|
146
|
+
toolTip: collaborationEnabled ? "MetaSynergy: collaboration on" : "MetaSynergy: collaboration off",
|
|
147
|
+
)
|
|
148
|
+
} else {
|
|
149
|
+
applyStatusAppearance(textFallback: "Meta", toolTip: "MetaSynergy")
|
|
150
|
+
}
|
|
137
151
|
}
|
|
138
|
-
|
|
139
|
-
if !hasAuth {
|
|
140
|
-
statusItem.button?.title = "Meta"
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
statusItem.button?.title = session == nil ? (collaborationEnabled ? "Meta" : "Meta Off") : "Meta Busy"
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
@objc private func toggleCollaboration() {
|
|
148
|
-
guard let output =
|
|
155
|
+
guard let output = runCommand(["status"]),
|
|
149
156
|
let data = output.data(using: .utf8),
|
|
150
157
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
151
158
|
let state = json["state"] as? [String: Any],
|
|
@@ -154,12 +161,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
154
161
|
return
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
_ =
|
|
164
|
+
_ = runCommand([enabled ? "disable" : "enable"])
|
|
158
165
|
refreshStatus()
|
|
159
166
|
}
|
|
160
167
|
|
|
161
|
-
@objc private func
|
|
162
|
-
_ =
|
|
168
|
+
@objc private func terminateCollaboration() {
|
|
169
|
+
_ = runCommand(["kick"])
|
|
163
170
|
refreshStatus()
|
|
164
171
|
}
|
|
165
172
|
|
|
@@ -177,27 +184,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
private func startHostIfPossible() {
|
|
180
|
-
guard !hostStarted else { return }
|
|
181
|
-
guard
|
|
182
|
-
|
|
183
|
-
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
184
|
-
let auth = json["auth"] as? [String: Any],
|
|
185
|
-
let agentID = auth["agentID"] as? String,
|
|
186
|
-
let agentSecret = auth["agentSecret"] as? String,
|
|
187
|
-
!agentID.isEmpty,
|
|
188
|
-
!agentSecret.isEmpty
|
|
189
|
-
else {
|
|
190
|
-
return
|
|
191
|
-
}
|
|
187
|
+
guard hostProcess == nil, !hostStarted else { return }
|
|
188
|
+
guard hasAuth() else { return }
|
|
189
|
+
guard let runtimeURL else { return }
|
|
192
190
|
|
|
193
191
|
let process = Process()
|
|
194
|
-
process.executableURL =
|
|
195
|
-
process.arguments = ["
|
|
196
|
-
process.
|
|
197
|
-
process.terminationHandler = { _ in
|
|
192
|
+
process.executableURL = runtimeURL
|
|
193
|
+
process.arguments = ["start"]
|
|
194
|
+
process.terminationHandler = { [weak self] _ in
|
|
198
195
|
DispatchQueue.main.async {
|
|
199
|
-
self
|
|
200
|
-
self
|
|
196
|
+
self?.hostStarted = false
|
|
197
|
+
self?.hostProcess = nil
|
|
201
198
|
}
|
|
202
199
|
}
|
|
203
200
|
|
|
@@ -206,17 +203,29 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
206
203
|
hostProcess = process
|
|
207
204
|
hostStarted = true
|
|
208
205
|
} catch {
|
|
209
|
-
hostStarted = false
|
|
210
206
|
connectionItem.title = "Holos: failed to start host"
|
|
211
207
|
}
|
|
212
208
|
}
|
|
213
209
|
|
|
210
|
+
private func hasAuth() -> Bool {
|
|
211
|
+
guard let output = runCommand(["status"]),
|
|
212
|
+
let data = output.data(using: .utf8),
|
|
213
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
214
|
+
let auth = json["auth"] as? [String: Any],
|
|
215
|
+
let agentID = auth["agentID"] as? String,
|
|
216
|
+
let agentSecret = auth["agentSecret"] as? String
|
|
217
|
+
else {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
return !agentID.isEmpty && !agentSecret.isEmpty
|
|
221
|
+
}
|
|
222
|
+
|
|
214
223
|
@discardableResult
|
|
215
|
-
private func
|
|
224
|
+
private func runCommand(_ args: [String]) -> String? {
|
|
225
|
+
guard let runtimeURL else { return nil }
|
|
216
226
|
let process = Process()
|
|
217
|
-
process.executableURL =
|
|
218
|
-
process.arguments =
|
|
219
|
-
process.currentDirectoryURL = URL(fileURLWithPath: rootPath)
|
|
227
|
+
process.executableURL = runtimeURL
|
|
228
|
+
process.arguments = args
|
|
220
229
|
|
|
221
230
|
let output = Pipe()
|
|
222
231
|
process.standardOutput = output
|
|
@@ -253,9 +262,57 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
253
262
|
return value
|
|
254
263
|
}
|
|
255
264
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
265
|
+
return "\(value.prefix(8))...\(value.suffix(6))"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private func loadStatusImage() -> NSImage? {
|
|
269
|
+
guard let url = Bundle.main.resourceURL?.appendingPathComponent("StatusIcon.png"),
|
|
270
|
+
let source = NSImage(contentsOf: url)
|
|
271
|
+
else {
|
|
272
|
+
return nil
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let canvasSize = NSSize(width: 18, height: 18)
|
|
276
|
+
let image = NSImage(size: canvasSize)
|
|
277
|
+
image.lockFocus()
|
|
278
|
+
NSColor.clear.setFill()
|
|
279
|
+
NSBezierPath(rect: NSRect(origin: .zero, size: canvasSize)).fill()
|
|
280
|
+
|
|
281
|
+
let targetRect = fitRect(
|
|
282
|
+
sourceSize: source.size,
|
|
283
|
+
into: NSRect(x: 1.75, y: 1.75, width: 14.5, height: 14.5),
|
|
284
|
+
)
|
|
285
|
+
source.draw(in: targetRect)
|
|
286
|
+
|
|
287
|
+
image.unlockFocus()
|
|
288
|
+
image.isTemplate = true
|
|
289
|
+
return image
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private func applyStatusAppearance(textFallback: String, toolTip: String) {
|
|
293
|
+
guard let button = statusItem.button else { return }
|
|
294
|
+
if let statusImage {
|
|
295
|
+
button.image = statusImage
|
|
296
|
+
button.imagePosition = .imageOnly
|
|
297
|
+
button.title = ""
|
|
298
|
+
} else {
|
|
299
|
+
button.image = nil
|
|
300
|
+
button.title = textFallback
|
|
301
|
+
}
|
|
302
|
+
button.toolTip = toolTip
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private func fitRect(sourceSize: NSSize, into rect: NSRect) -> NSRect {
|
|
306
|
+
guard sourceSize.width > 0, sourceSize.height > 0 else { return rect }
|
|
307
|
+
let scale = min(rect.width / sourceSize.width, rect.height / sourceSize.height)
|
|
308
|
+
let width = sourceSize.width * scale
|
|
309
|
+
let height = sourceSize.height * scale
|
|
310
|
+
return NSRect(
|
|
311
|
+
x: rect.midX - width / 2,
|
|
312
|
+
y: rect.midY - height / 2,
|
|
313
|
+
width: width,
|
|
314
|
+
height: height,
|
|
315
|
+
)
|
|
259
316
|
}
|
|
260
317
|
}
|
|
261
318
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { $ } from "bun"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import fs from "node:fs/promises"
|
|
6
|
+
|
|
7
|
+
const pkgDir = path.resolve(import.meta.dir, "..")
|
|
8
|
+
const distDir = path.join(pkgDir, "dist", "app")
|
|
9
|
+
const appName = "MetaSynergy"
|
|
10
|
+
const darwinDir = path.join(pkgDir, "app", "darwin")
|
|
11
|
+
const sourcePath = path.join(darwinDir, "main.swift")
|
|
12
|
+
const appIconSourcePath = path.join(darwinDir, "assets", "app-icon.png")
|
|
13
|
+
const statusIconSourcePath = path.join(darwinDir, "assets", "status-icon.png")
|
|
14
|
+
const runtimeEntrypoint = path.join(pkgDir, "src", "cli.ts")
|
|
15
|
+
const signIdentity = process.env.METASYNERGY_SIGN_IDENTITY || "-"
|
|
16
|
+
|
|
17
|
+
const targets = [
|
|
18
|
+
{
|
|
19
|
+
arch: "arm64",
|
|
20
|
+
bunTarget: "bun-darwin-arm64",
|
|
21
|
+
swiftTarget: "arm64-apple-macos12.0",
|
|
22
|
+
suffix: "darwin-arm64",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
arch: "x64",
|
|
26
|
+
bunTarget: "bun-darwin-x64",
|
|
27
|
+
swiftTarget: "x86_64-apple-macos12.0",
|
|
28
|
+
suffix: "darwin-x64",
|
|
29
|
+
},
|
|
30
|
+
] as const
|
|
31
|
+
|
|
32
|
+
await $`rm -rf ${distDir}`
|
|
33
|
+
|
|
34
|
+
for (const target of targets) {
|
|
35
|
+
await buildTarget(target)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (signIdentity === "-") {
|
|
39
|
+
console.log(
|
|
40
|
+
"Note: this build uses ad-hoc signing. Gatekeeper may reject it until you sign/notarize with a Developer ID.",
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function buildTarget(target: (typeof targets)[number]) {
|
|
45
|
+
const targetDir = path.join(distDir, target.suffix)
|
|
46
|
+
const appDir = path.join(targetDir, `${appName}.app`)
|
|
47
|
+
const contentsDir = path.join(appDir, "Contents")
|
|
48
|
+
const macOSDir = path.join(contentsDir, "MacOS")
|
|
49
|
+
const resourcesDir = path.join(contentsDir, "Resources")
|
|
50
|
+
const appExecutable = path.join(macOSDir, appName)
|
|
51
|
+
const runtimeExecutable = path.join(resourcesDir, "meta-synergy-runtime")
|
|
52
|
+
const infoPlistPath = path.join(contentsDir, "Info.plist")
|
|
53
|
+
const dmgPath = path.join(distDir, `${appName}-${target.suffix}.dmg`)
|
|
54
|
+
const tempDir = path.join(targetDir, ".tmp")
|
|
55
|
+
const iconsetDir = path.join(tempDir, "AppIcon.iconset")
|
|
56
|
+
const moduleCacheDir = path.join(tempDir, "swift-module-cache")
|
|
57
|
+
|
|
58
|
+
await fs.mkdir(macOSDir, { recursive: true })
|
|
59
|
+
await fs.mkdir(resourcesDir, { recursive: true })
|
|
60
|
+
await fs.mkdir(tempDir, { recursive: true })
|
|
61
|
+
await fs.mkdir(moduleCacheDir, { recursive: true })
|
|
62
|
+
|
|
63
|
+
console.log(`Building embedded MetaSynergy runtime (${target.suffix})`)
|
|
64
|
+
const result = await Bun.build({
|
|
65
|
+
entrypoints: [runtimeEntrypoint],
|
|
66
|
+
target: "bun",
|
|
67
|
+
sourcemap: "none",
|
|
68
|
+
minify: false,
|
|
69
|
+
compile: {
|
|
70
|
+
target: target.bunTarget,
|
|
71
|
+
outfile: runtimeExecutable,
|
|
72
|
+
execArgv: ["--use-system-ca", "--"],
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Failed to build runtime for ${target.suffix}:\n${result.logs.map((log) => log.message).join("\n")}`,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await $`chmod +x ${runtimeExecutable}`
|
|
83
|
+
|
|
84
|
+
console.log(`Compiling native macOS menu bar host (${target.suffix})`)
|
|
85
|
+
await $`env CLANG_MODULE_CACHE_PATH=${moduleCacheDir} swiftc -target ${target.swiftTarget} -O ${sourcePath} -o ${appExecutable}`
|
|
86
|
+
|
|
87
|
+
const iconBuilt = await buildIcon(iconsetDir, resourcesDir).catch((error) => {
|
|
88
|
+
console.warn(
|
|
89
|
+
`Icon generation skipped for ${target.suffix}: ${error instanceof Error ? error.message : String(error)}`,
|
|
90
|
+
)
|
|
91
|
+
return false
|
|
92
|
+
})
|
|
93
|
+
await $`cp ${statusIconSourcePath} ${path.join(resourcesDir, "StatusIcon.png")}`
|
|
94
|
+
|
|
95
|
+
await fs.writeFile(
|
|
96
|
+
infoPlistPath,
|
|
97
|
+
plist({
|
|
98
|
+
executable: appName,
|
|
99
|
+
iconFile: iconBuilt ? "AppIcon" : undefined,
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
console.log(`Applying code signature (${signIdentity === "-" ? "ad-hoc" : signIdentity}) to ${target.suffix}`)
|
|
104
|
+
await $`codesign --force --deep --sign ${signIdentity} ${appDir}`.quiet().catch((error) => {
|
|
105
|
+
console.warn(`codesign skipped for ${target.suffix}: ${error}`)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
console.log(`Building DMG (${target.suffix})`)
|
|
109
|
+
const dmgStageDir = path.join(tempDir, "dmg")
|
|
110
|
+
await fs.mkdir(dmgStageDir, { recursive: true })
|
|
111
|
+
await $`cp -R ${appDir} ${dmgStageDir}`
|
|
112
|
+
await $`ln -s /Applications ${path.join(dmgStageDir, "Applications")}`
|
|
113
|
+
await $`hdiutil create -volname ${appName} -srcfolder ${dmgStageDir} -ov -format UDZO ${dmgPath}`.quiet()
|
|
114
|
+
|
|
115
|
+
console.log(`Built ${appDir}`)
|
|
116
|
+
console.log(`Built ${dmgPath}`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function buildIcon(iconsetDir: string, resourcesDir: string) {
|
|
120
|
+
await fs.mkdir(iconsetDir, { recursive: true })
|
|
121
|
+
const tempDir = path.dirname(iconsetDir)
|
|
122
|
+
const flattenedJPG = path.join(tempDir, "app-icon-white.jpg")
|
|
123
|
+
const sourcePNG = path.join(tempDir, "app-icon-white.png")
|
|
124
|
+
await $`sips -s format jpeg ${appIconSourcePath} --out ${flattenedJPG}`.quiet()
|
|
125
|
+
await $`sips -s format png ${flattenedJPG} --out ${sourcePNG}`.quiet()
|
|
126
|
+
|
|
127
|
+
const sizes = [
|
|
128
|
+
["16", "icon_16x16.png"],
|
|
129
|
+
["32", "icon_16x16@2x.png"],
|
|
130
|
+
["32", "icon_32x32.png"],
|
|
131
|
+
["64", "icon_32x32@2x.png"],
|
|
132
|
+
["128", "icon_128x128.png"],
|
|
133
|
+
["256", "icon_128x128@2x.png"],
|
|
134
|
+
["256", "icon_256x256.png"],
|
|
135
|
+
["512", "icon_256x256@2x.png"],
|
|
136
|
+
["512", "icon_512x512.png"],
|
|
137
|
+
["1024", "icon_512x512@2x.png"],
|
|
138
|
+
] as const
|
|
139
|
+
|
|
140
|
+
for (const [size, filename] of sizes) {
|
|
141
|
+
await $`sips -z ${size} ${size} ${sourcePNG} --out ${path.join(iconsetDir, filename)}`.quiet()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await $`iconutil -c icns ${iconsetDir} -o ${path.join(resourcesDir, "AppIcon.icns")}`.quiet()
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function plist(input: { executable: string; iconFile?: string }) {
|
|
149
|
+
const iconLine = input.iconFile ? ` <key>CFBundleIconFile</key>\n <string>${input.iconFile}</string>\n` : ""
|
|
150
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
151
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
152
|
+
<plist version="1.0">
|
|
153
|
+
<dict>
|
|
154
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
155
|
+
<string>en</string>
|
|
156
|
+
<key>CFBundleDisplayName</key>
|
|
157
|
+
<string>MetaSynergy</string>
|
|
158
|
+
<key>CFBundleExecutable</key>
|
|
159
|
+
<string>${input.executable}</string>
|
|
160
|
+
<key>CFBundleIdentifier</key>
|
|
161
|
+
<string>io.holos.metasynergy</string>
|
|
162
|
+
${iconLine} <key>CFBundleInfoDictionaryVersion</key>
|
|
163
|
+
<string>6.0</string>
|
|
164
|
+
<key>CFBundleName</key>
|
|
165
|
+
<string>MetaSynergy</string>
|
|
166
|
+
<key>CFBundlePackageType</key>
|
|
167
|
+
<string>APPL</string>
|
|
168
|
+
<key>CFBundleShortVersionString</key>
|
|
169
|
+
<string>0.1.0</string>
|
|
170
|
+
<key>CFBundleVersion</key>
|
|
171
|
+
<string>1</string>
|
|
172
|
+
<key>LSMinimumSystemVersion</key>
|
|
173
|
+
<string>12.0</string>
|
|
174
|
+
<key>LSUIElement</key>
|
|
175
|
+
<true/>
|
|
176
|
+
<key>NSHighResolutionCapable</key>
|
|
177
|
+
<true/>
|
|
178
|
+
</dict>
|
|
179
|
+
</plist>
|
|
180
|
+
`
|
|
181
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -48,7 +48,7 @@ async function main() {
|
|
|
48
48
|
return
|
|
49
49
|
case "menu":
|
|
50
50
|
await new Promise<void>((resolve, reject) => {
|
|
51
|
-
const child = spawn("swift", ["
|
|
51
|
+
const child = spawn("swift", ["app/darwin/main.swift"], { stdio: "inherit" })
|
|
52
52
|
child.once("exit", (code) => {
|
|
53
53
|
if ((code ?? 0) === 0) resolve()
|
|
54
54
|
else reject(new Error(`Menu exited with code ${code ?? 0}`))
|