positron.js 1.0.4 → 1.0.6
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/bin/positron.js +6 -1
- package/builder.js +25 -2
- package/core/mac/main.swift +104 -21
- package/core/mac/tray.swift +95 -0
- package/core/win/PositronRuntime.csproj +1 -1
- package/core/win/main.cs +176 -22
- package/core/win/tray.cs +142 -0
- package/index.js +165 -42
- package/package.json +9 -2
- package/packager.js +47 -12
- package/tray.js +64 -0
package/bin/positron.js
CHANGED
|
@@ -5,6 +5,7 @@ const performPackager = require("../packager");
|
|
|
5
5
|
const { spawn } = require("child_process");
|
|
6
6
|
const [, , command] = process.argv;
|
|
7
7
|
const { info, success, error } = require("../logs");
|
|
8
|
+
const fs = require("fs");
|
|
8
9
|
|
|
9
10
|
switch (command) {
|
|
10
11
|
case "build":
|
|
@@ -15,7 +16,11 @@ switch (command) {
|
|
|
15
16
|
|
|
16
17
|
case "dev":
|
|
17
18
|
info("Starting Positron in development mode...");
|
|
18
|
-
performNativeBuild();
|
|
19
|
+
const buildSuccess = performNativeBuild();
|
|
20
|
+
if (!buildSuccess) {
|
|
21
|
+
error("Development build failed. Please fix the errors and try again.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
19
24
|
spawn("node", ["."], { stdio: "inherit" });
|
|
20
25
|
break;
|
|
21
26
|
|
package/builder.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const cp = require("child_process");
|
|
4
4
|
const { success, error, info, warn } = require("./logs");
|
|
5
|
+
const semver = require("semver");
|
|
5
6
|
|
|
6
7
|
const arch = process.argv.includes("--x64") ? "x64" : process.argv.includes("--arm64") ? "arm64" : process.arch;
|
|
7
8
|
|
|
@@ -30,6 +31,18 @@ function performNativeBuild() {
|
|
|
30
31
|
if (depPackage.positron) {
|
|
31
32
|
const depDir = path.dirname(depPackagePath);
|
|
32
33
|
|
|
34
|
+
if(depPackage.positron.requiredVersion) {
|
|
35
|
+
const requiredVersion = depPackage.positron.requiredVersion;
|
|
36
|
+
const rootVersion = rootPackage.dependencies["positron.js"];
|
|
37
|
+
if(rootVersion.startsWith("file:")) {
|
|
38
|
+
warn(`[Builder] Dependency "${dep}" specifies a required positron.js version of ${requiredVersion}, but the project is using a local file reference. Skipping version compatibility check for this dependency.`);
|
|
39
|
+
} else {
|
|
40
|
+
if(!semver.satisfies(rootVersion, requiredVersion)) {
|
|
41
|
+
warn(`[Builder] Dependency "${dep}" requires positron.js version ${requiredVersion}, but the project has version ${rootVersion}. This may lead to compatibility issues.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
let missing = [];
|
|
34
47
|
if(!depPackage.positron.className) missing.push("className");
|
|
35
48
|
if(!depPackage.positron.command) missing.push("command");
|
|
@@ -78,7 +91,17 @@ function performNativeBuild() {
|
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
if (buildingForMac) {
|
|
81
|
-
|
|
94
|
+
|
|
95
|
+
const coreMacDir = path.join(__dirname, "core", "mac");
|
|
96
|
+
|
|
97
|
+
nativeExtensionsMac.push({
|
|
98
|
+
command:"createTray",
|
|
99
|
+
className:"TrayExtension",
|
|
100
|
+
sourceFile:path.join(coreMacDir, "tray.swift")
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// -1 to account for the built-in tray extension
|
|
104
|
+
info(`[Builder] Stitching ${nativeExtensionsMac.length-1} native extensions...`);
|
|
82
105
|
|
|
83
106
|
let registryContent = `// Auto-generated by Positron. Do not edit.\n`;
|
|
84
107
|
registryContent += `func getExtensionRegistry() -> [String: (Int, [String]) -> Void] {\n`;
|
|
@@ -99,7 +122,6 @@ function performNativeBuild() {
|
|
|
99
122
|
|
|
100
123
|
registryContent += `}\n`;
|
|
101
124
|
|
|
102
|
-
const coreMacDir = path.join(__dirname, "core", "mac");
|
|
103
125
|
fs.writeFileSync(path.join(coreMacDir, "Registry.swift"), registryContent);
|
|
104
126
|
|
|
105
127
|
info("[Builder] Compiling native binary...");
|
|
@@ -176,6 +198,7 @@ function performNativeBuild() {
|
|
|
176
198
|
const coreWinDir = path.join(__dirname, "core", "win");
|
|
177
199
|
const extensionsDir = path.join(coreWinDir, "extensions");
|
|
178
200
|
|
|
201
|
+
|
|
179
202
|
// 1. Clean and prepare a staging folder for all native extensions
|
|
180
203
|
if (fs.existsSync(extensionsDir)) fs.rmSync(extensionsDir, { recursive: true, force: true });
|
|
181
204
|
fs.mkdirSync(extensionsDir, { recursive: true });
|
package/core/mac/main.swift
CHANGED
|
@@ -3,6 +3,9 @@ import WebKit
|
|
|
3
3
|
import Network
|
|
4
4
|
import Darwin
|
|
5
5
|
import UserNotifications
|
|
6
|
+
import Foundation
|
|
7
|
+
import IOKit.pwr_mgt
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
// MARK: - Globals
|
|
8
11
|
|
|
@@ -20,8 +23,31 @@ let AUTH_TOKEN: String = {
|
|
|
20
23
|
var windowObservations: [Int: NSKeyValueObservation] = [:]
|
|
21
24
|
var navigationDelegates: [Int: WebViewNavigationDelegate] = [:]
|
|
22
25
|
|
|
26
|
+
private var assertionID: IOPMAssertionID?
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
func blockPowerSave() {
|
|
29
|
+
guard assertionID == nil else { return }
|
|
30
|
+
|
|
31
|
+
var id: IOPMAssertionID = 0
|
|
32
|
+
|
|
33
|
+
let result = IOPMAssertionCreateWithName(
|
|
34
|
+
kIOPMAssertionTypePreventUserIdleSystemSleep as CFString,
|
|
35
|
+
IOPMAssertionLevel(kIOPMAssertionLevelOn),
|
|
36
|
+
"Power Save Blocked" as CFString,
|
|
37
|
+
&id
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if result == kIOReturnSuccess {
|
|
41
|
+
assertionID = id
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func unblockPowerSave() {
|
|
46
|
+
guard let id = assertionID else { return }
|
|
47
|
+
|
|
48
|
+
IOPMAssertionRelease(id)
|
|
49
|
+
assertionID = nil
|
|
50
|
+
}
|
|
25
51
|
|
|
26
52
|
final class PositronWebView: WKWebView {
|
|
27
53
|
override func rightMouseDown(with event: NSEvent) {
|
|
@@ -292,7 +318,7 @@ case "isFullscreen":
|
|
|
292
318
|
guard let window = windows[windowId] else { return }
|
|
293
319
|
let isFullscreen = window.styleMask.contains(.fullScreen)
|
|
294
320
|
AppDelegate.shared?.ipcClient.send(
|
|
295
|
-
IPCResponse(windowId: windowId, event: "isFullscreen-reply-\(windowId)", data: ["isFullscreen": isFullscreen ? "true" : "false"])
|
|
321
|
+
IPCResponse(windowId: windowId, event: args.last ?? "isFullscreen-reply-\(windowId)", data: ["isFullscreen": isFullscreen ? "true" : "false"])
|
|
296
322
|
)
|
|
297
323
|
|
|
298
324
|
case "setSwipeNav":
|
|
@@ -305,7 +331,7 @@ case "setSwipeNav":
|
|
|
305
331
|
webView.allowsBackForwardNavigationGestures = enable
|
|
306
332
|
|
|
307
333
|
GetIPCClient().send(
|
|
308
|
-
IPCResponse(windowId: windowId, event: "setSwipeNav-reply-\(windowId)", data: ["enabled": enable ? "true" : "false"])
|
|
334
|
+
IPCResponse(windowId: windowId, event: args.last ?? "setSwipeNav-reply-\(windowId)", data: ["enabled": enable ? "true" : "false"])
|
|
309
335
|
)
|
|
310
336
|
|
|
311
337
|
case "forceCloseWindow":
|
|
@@ -402,7 +428,7 @@ case "forceCloseWindow":
|
|
|
402
428
|
let frame = window.frame
|
|
403
429
|
let bounds = ["x": "\(Int(frame.origin.x))", "y": "\(Int(frame.origin.y))", "width": "\(Int(frame.size.width))", "height": "\(Int(frame.size.height))"]
|
|
404
430
|
AppDelegate.shared?.ipcClient.send(
|
|
405
|
-
IPCResponse(windowId: windowId, event: "getBounds-reply-\(windowId)", data: bounds)
|
|
431
|
+
IPCResponse(windowId: windowId, event: args.last ?? "getBounds-reply-\(windowId)", data: bounds)
|
|
406
432
|
)
|
|
407
433
|
|
|
408
434
|
case "setResizable":
|
|
@@ -461,16 +487,64 @@ case "forceCloseWindow":
|
|
|
461
487
|
guard let window = windows[windowId] else { return }
|
|
462
488
|
let canGoBack = (window.contentView as? WKWebView)?.canGoBack ?? false
|
|
463
489
|
AppDelegate.shared?.ipcClient.send(
|
|
464
|
-
IPCResponse(windowId: windowId, event: "canGoBack-reply-\(windowId)", data: ["canGoBack": canGoBack ? "true" : "false"])
|
|
490
|
+
IPCResponse(windowId: windowId, event: args.last ?? "canGoBack-reply-\(windowId)", data: ["canGoBack": canGoBack ? "true" : "false"])
|
|
465
491
|
)
|
|
466
492
|
|
|
467
493
|
case "canGoForward":
|
|
468
494
|
guard let window = windows[windowId] else { return }
|
|
469
495
|
let canGoForward = (window.contentView as? WKWebView)?.canGoForward ?? false
|
|
470
496
|
AppDelegate.shared?.ipcClient.send(
|
|
471
|
-
IPCResponse(windowId: windowId, event: "canGoForward-reply-\(windowId)", data: ["canGoForward": canGoForward ? "true" : "false"])
|
|
497
|
+
IPCResponse(windowId: windowId, event: args.last ?? "canGoForward-reply-\(windowId)", data: ["canGoForward": canGoForward ? "true" : "false"])
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
case "showFileOpenDialog":
|
|
501
|
+
guard let window = windows[windowId] else { return }
|
|
502
|
+
let panel = NSOpenPanel()
|
|
503
|
+
panel.allowsMultipleSelection = false
|
|
504
|
+
panel.canChooseDirectories = false
|
|
505
|
+
panel.canChooseFiles = true
|
|
506
|
+
panel.beginSheetModal(for: window) { response in
|
|
507
|
+
if response == .OK, let url = panel.url {
|
|
508
|
+
AppDelegate.shared?.ipcClient.send(
|
|
509
|
+
IPCResponse(windowId: windowId, event: args.last ?? "showFileOpenDialog-reply-\(windowId)", data: ["filePath": url.path])
|
|
510
|
+
)
|
|
511
|
+
} else {
|
|
512
|
+
AppDelegate.shared?.ipcClient.send(
|
|
513
|
+
IPCResponse(windowId: windowId, event: args.last ?? "showFileOpenDialog-reply-\(windowId)", data: ["filePath": ""])
|
|
514
|
+
)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
case "readFromClipboard":
|
|
519
|
+
let pasteboard = NSPasteboard.general
|
|
520
|
+
let clipboardText = pasteboard.string(forType: .string) ?? "notext"
|
|
521
|
+
AppDelegate.shared?.ipcClient.send(
|
|
522
|
+
IPCResponse(windowId: windowId, event: args.last ?? "readFromClipboard-reply-\(windowId)", data: ["text": clipboardText])
|
|
472
523
|
)
|
|
473
524
|
|
|
525
|
+
case "blockPowerSave":
|
|
526
|
+
blockPowerSave()
|
|
527
|
+
|
|
528
|
+
case "unblockPowerSave":
|
|
529
|
+
unblockPowerSave()
|
|
530
|
+
|
|
531
|
+
case "isDarkMode":
|
|
532
|
+
guard let window = windows[windowId] else { return }
|
|
533
|
+
let isDarkMode = window.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
|
|
534
|
+
AppDelegate.shared?.ipcClient.send(
|
|
535
|
+
IPCResponse(windowId: windowId, event: args.last ?? "isDarkMode-reply-\(windowId)", data: ["isDarkMode": isDarkMode ? "true" : "false"])
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
case "writeToClipboard":
|
|
540
|
+
guard let text = args.first else {
|
|
541
|
+
printError("writeToClipboard — missing text argument")
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
let pasteboard = NSPasteboard.general
|
|
545
|
+
pasteboard.clearContents()
|
|
546
|
+
pasteboard.setString(text, forType: .string)
|
|
547
|
+
|
|
474
548
|
case "showNotification":
|
|
475
549
|
|
|
476
550
|
UNUserNotificationCenter.current().requestAuthorization(
|
|
@@ -501,14 +575,14 @@ UNUserNotificationCenter.current().requestAuthorization(
|
|
|
501
575
|
guard let window = windows[windowId] else { return }
|
|
502
576
|
let url = (window.contentView as? WKWebView)?.url?.absoluteString ?? ""
|
|
503
577
|
AppDelegate.shared?.ipcClient.send(
|
|
504
|
-
IPCResponse(windowId: windowId, event: "getURL-reply-\(windowId)", data: ["url": url])
|
|
578
|
+
IPCResponse(windowId: windowId, event: args.last ?? "getURL-reply-\(windowId)", data: ["url": url])
|
|
505
579
|
)
|
|
506
580
|
|
|
507
581
|
case "getTitle":
|
|
508
582
|
guard let window = windows[windowId] else { return }
|
|
509
583
|
let title = window.title
|
|
510
584
|
AppDelegate.shared?.ipcClient.send(
|
|
511
|
-
IPCResponse(windowId: windowId, event: "getTitle-reply-\(windowId)", data: ["title": title])
|
|
585
|
+
IPCResponse(windowId: windowId, event: args.last ?? "getTitle-reply-\(windowId)", data: ["title": title])
|
|
512
586
|
)
|
|
513
587
|
|
|
514
588
|
case "executeAppleScript":
|
|
@@ -528,7 +602,7 @@ case "isVisible":
|
|
|
528
602
|
guard let window = windows[windowId] else { return }
|
|
529
603
|
let isVisible = window.isVisible
|
|
530
604
|
AppDelegate.shared?.ipcClient.send(
|
|
531
|
-
IPCResponse(windowId: windowId, event: "isVisible-reply-\(windowId)", data: ["isVisible": isVisible ? "true" : "false"])
|
|
605
|
+
IPCResponse(windowId: windowId, event: args.last ?? "isVisible-reply-\(windowId)", data: ["isVisible": isVisible ? "true" : "false"])
|
|
532
606
|
)
|
|
533
607
|
|
|
534
608
|
case "addToContentBlocker":
|
|
@@ -601,7 +675,7 @@ case "addToContentBlocker":
|
|
|
601
675
|
}
|
|
602
676
|
|
|
603
677
|
GetIPCClient().send(
|
|
604
|
-
IPCResponse(windowId: windowId, event: "addToContentBlocker-reply-\(windowId)", data: ["status": "success"])
|
|
678
|
+
IPCResponse(windowId: windowId, event: args.last ?? "addToContentBlocker-reply-\(windowId)", data: ["status": "success"])
|
|
605
679
|
)
|
|
606
680
|
|
|
607
681
|
case "isSwipeNavEnabled":
|
|
@@ -612,7 +686,7 @@ case "addToContentBlocker":
|
|
|
612
686
|
}
|
|
613
687
|
let enabled = webView.allowsBackForwardNavigationGestures
|
|
614
688
|
AppDelegate.shared?.ipcClient.send(
|
|
615
|
-
IPCResponse(windowId: windowId, event: "isSwipeNavEnabled-reply-\(windowId)", data: ["enabled": enabled ? "true" : "false"])
|
|
689
|
+
IPCResponse(windowId: windowId, event: args.last ?? "isSwipeNavEnabled-reply-\(windowId)", data: ["enabled": enabled ? "true" : "false"])
|
|
616
690
|
)
|
|
617
691
|
|
|
618
692
|
case "forward":
|
|
@@ -698,7 +772,7 @@ case "addToContentBlocker":
|
|
|
698
772
|
resultStr = "null"
|
|
699
773
|
}
|
|
700
774
|
AppDelegate.shared?.ipcClient.send(
|
|
701
|
-
IPCResponse(windowId: windowId, event: "evaluateJS-reply-\(windowId)", data: ["result": resultStr])
|
|
775
|
+
IPCResponse(windowId: windowId, event: args.last ?? "evaluateJS-reply-\(windowId)", data: ["result": resultStr])
|
|
702
776
|
)
|
|
703
777
|
}
|
|
704
778
|
|
|
@@ -724,11 +798,11 @@ case "addToContentBlocker":
|
|
|
724
798
|
if response == .alertFirstButtonReturn {
|
|
725
799
|
let userInput = inputField.stringValue
|
|
726
800
|
AppDelegate.shared?.ipcClient.send(
|
|
727
|
-
IPCResponse(windowId: windowId, event: "prompt-reply-\(windowId)", data: ["input": userInput])
|
|
801
|
+
IPCResponse(windowId: windowId, event: args.last ?? "prompt-reply-\(windowId)", data: ["input": userInput])
|
|
728
802
|
)
|
|
729
803
|
} else {
|
|
730
804
|
AppDelegate.shared?.ipcClient.send(
|
|
731
|
-
IPCResponse(windowId: windowId, event: "prompt-reply-\(windowId)", data: ["input": ""])
|
|
805
|
+
IPCResponse(windowId: windowId, event: args.last ?? "prompt-reply-\(windowId)", data: ["input": ""])
|
|
732
806
|
)
|
|
733
807
|
}
|
|
734
808
|
}
|
|
@@ -747,7 +821,7 @@ case "addToContentBlocker":
|
|
|
747
821
|
alert.beginSheetModal(for: window) { response in
|
|
748
822
|
let confirmed = (response == .alertFirstButtonReturn)
|
|
749
823
|
AppDelegate.shared?.ipcClient.send(
|
|
750
|
-
IPCResponse(windowId: windowId, event: "confirm-reply-\(windowId)", data: ["confirmed": confirmed ? "true" : "false"])
|
|
824
|
+
IPCResponse(windowId: windowId, event: args.last ?? "confirm-reply-\(windowId)", data: ["confirmed": confirmed ? "true" : "false"])
|
|
751
825
|
)
|
|
752
826
|
}
|
|
753
827
|
|
|
@@ -755,7 +829,13 @@ case "addToContentBlocker":
|
|
|
755
829
|
guard let window = windows[windowId] else { return }
|
|
756
830
|
let isFocused = window.isKeyWindow
|
|
757
831
|
AppDelegate.shared?.ipcClient.send(
|
|
758
|
-
IPCResponse(windowId: windowId, event: "isFocused-reply-\(windowId)", data: ["isFocused": isFocused ? "true" : "false"])
|
|
832
|
+
IPCResponse(windowId: windowId, event: args.last ?? "isFocused-reply-\(windowId)", data: ["isFocused": isFocused ? "true" : "false"])
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
case "getFocusedWindowId":
|
|
836
|
+
let focusedWindowId = windows.first(where: { $0.value.isKeyWindow })?.key ?? -1
|
|
837
|
+
AppDelegate.shared?.ipcClient.send(
|
|
838
|
+
IPCResponse(windowId: windowId, event: args.last ?? "getFocusedWindowId-reply-\(windowId)", data: ["focusedWindowId": "\(focusedWindowId)"])
|
|
759
839
|
)
|
|
760
840
|
|
|
761
841
|
case "emitToRenderer":
|
|
@@ -843,7 +923,7 @@ final class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
|
|
|
843
923
|
let isFile = webView.url?.isFileURL ?? false
|
|
844
924
|
let eventName = isFile ? "loadFile-reply-\(windowId)" : "loadURL-reply-\(windowId)"
|
|
845
925
|
AppDelegate.shared?.ipcClient.send(
|
|
846
|
-
IPCResponse(windowId: windowId, event: eventName, data: [:])
|
|
926
|
+
IPCResponse(windowId: windowId, event: eventName, data: ["url": webView.url?.absoluteString ?? "", "title": webView.title ?? "", "canGoBack": (webView.canGoBack ? "true" : "false"), "canGoForward": (webView.canGoForward ? "true" : "false")])
|
|
847
927
|
)
|
|
848
928
|
}
|
|
849
929
|
}
|
|
@@ -931,7 +1011,7 @@ func makePreloadScript(windowId: Int) -> String {
|
|
|
931
1011
|
/** Called internally by Swift's evaluateJS to deliver a push message. */
|
|
932
1012
|
_emit(channel, payload) {
|
|
933
1013
|
(_listeners[channel] || []).forEach(fn => {
|
|
934
|
-
try { fn(payload); } catch(e) { console.
|
|
1014
|
+
try { fn(payload); } catch(e) { console.error('[ipc] listener error:', e); }
|
|
935
1015
|
});
|
|
936
1016
|
},
|
|
937
1017
|
|
|
@@ -1088,8 +1168,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
1088
1168
|
|
|
1089
1169
|
cd "\(resourcePath)"
|
|
1090
1170
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1171
|
+
BACKEND_BIN=$(find . -maxdepth 1 -name "*-backend" | head -n 1)
|
|
1172
|
+
if [ -n "$BACKEND_BIN" ] && [ -f "$BACKEND_BIN" ]; then
|
|
1173
|
+
exec "$BACKEND_BIN"
|
|
1093
1174
|
else
|
|
1094
1175
|
exec "node" "."
|
|
1095
1176
|
fi
|
|
@@ -1311,20 +1392,22 @@ setbuf(__stdoutp, nil)
|
|
|
1311
1392
|
setbuf(__stderrp, nil)
|
|
1312
1393
|
|
|
1313
1394
|
signal(SIGINT) { _ in
|
|
1314
|
-
printError("INFO: Received SIGINT, shutting down…")
|
|
1315
1395
|
AppDelegate.shared?.nodeProcess?.terminate()
|
|
1396
|
+
unblockPowerSave()
|
|
1316
1397
|
exit(0)
|
|
1317
1398
|
}
|
|
1318
1399
|
|
|
1319
1400
|
signal(SIGTERM) { _ in
|
|
1320
1401
|
printError("INFO: Received SIGTERM, shutting down…")
|
|
1321
1402
|
AppDelegate.shared?.nodeProcess?.terminate()
|
|
1403
|
+
unblockPowerSave()
|
|
1322
1404
|
exit(0)
|
|
1323
1405
|
}
|
|
1324
1406
|
|
|
1325
1407
|
signal(SIGSEGV) { _ in
|
|
1326
1408
|
printError("ERROR: Caught SIGSEGV (segmentation fault). This likely indicates a bug in the native code. Attempting to shut down gracefully…")
|
|
1327
1409
|
AppDelegate.shared?.nodeProcess?.terminate()
|
|
1410
|
+
unblockPowerSave()
|
|
1328
1411
|
signal(SIGSEGV, SIG_DFL)
|
|
1329
1412
|
raise(SIGSEGV)
|
|
1330
1413
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import Cocoa
|
|
2
|
+
|
|
3
|
+
class TrayManager {
|
|
4
|
+
static let shared = TrayManager()
|
|
5
|
+
var statusItem: NSStatusItem?
|
|
6
|
+
|
|
7
|
+
func setupTray() {
|
|
8
|
+
if statusItem == nil {
|
|
9
|
+
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
10
|
+
if let button = statusItem?.button {
|
|
11
|
+
button.title = "App"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func setMenu(_ menu: NSMenu) {
|
|
17
|
+
statusItem?.menu = menu
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func setTitle(_ title: String) {
|
|
21
|
+
statusItem?.button?.title = title
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func setIcon(_ iconPath: String) {
|
|
25
|
+
guard let button = statusItem?.button else { return printError("Tray button not initialized") }
|
|
26
|
+
|
|
27
|
+
if iconPath.isEmpty {
|
|
28
|
+
DispatchQueue.main.async {
|
|
29
|
+
button.image = nil
|
|
30
|
+
button.imagePosition = .imageLeft
|
|
31
|
+
}
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
guard FileManager.default.fileExists(atPath: iconPath) else {
|
|
36
|
+
printError("Icon path does not exist: \(iconPath)")
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
DispatchQueue.main.async {
|
|
41
|
+
if let img = NSImage(contentsOfFile: iconPath) {
|
|
42
|
+
img.size = NSSize(width: 18, height: 18)
|
|
43
|
+
img.isTemplate = true
|
|
44
|
+
button.image = img
|
|
45
|
+
button.imagePosition = .imageLeft
|
|
46
|
+
} else {
|
|
47
|
+
button.image = nil
|
|
48
|
+
button.imagePosition = .imageLeft
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public struct TrayExtension {
|
|
55
|
+
public static func handle(windowId: Int, args: [String]) {
|
|
56
|
+
|
|
57
|
+
if(args.last == "setTitle") {
|
|
58
|
+
TrayManager.shared.setTitle(args[0])
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if(args.last == "setIcon") {
|
|
63
|
+
TrayManager.shared.setIcon(args[0])
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
guard let descString = args.first,
|
|
68
|
+
let data = descString.data(using: .utf8),
|
|
69
|
+
let desc = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
70
|
+
printError("tray setMenu — invalid JSON descriptor")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if args.last == "setMenu" {
|
|
75
|
+
TrayManager.shared.setMenu(buildContextMenu(from: desc, windowId: windowId))
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
DispatchQueue.main.async {
|
|
80
|
+
|
|
81
|
+
TrayManager.shared.setupTray()
|
|
82
|
+
|
|
83
|
+
let title = args.count > 1 ? args[1] : ""
|
|
84
|
+
TrayManager.shared.setTitle(title)
|
|
85
|
+
|
|
86
|
+
let imagePath = args.count > 2 ? args[2] : nil
|
|
87
|
+
if let imagePath = imagePath {
|
|
88
|
+
TrayManager.shared.setIcon(imagePath)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let menu = buildContextMenu(from: desc, windowId: windowId)
|
|
92
|
+
TrayManager.shared.setMenu(menu)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
2
|
<PropertyGroup>
|
|
3
3
|
<OutputType>WinExe</OutputType>
|
|
4
|
-
<TargetFramework>
|
|
4
|
+
<TargetFramework>net10.0-windows</TargetFramework>
|
|
5
5
|
<Nullable>enable</Nullable>
|
|
6
6
|
<UseWpf>true</UseWpf>
|
|
7
7
|
<UseWindowsForms>true</UseWindowsForms>
|