positron.js 1.0.3 → 1.0.5
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 +24 -2
- package/core/mac/main.swift +208 -22
- package/core/win/PositronRuntime.csproj +1 -1
- package/core/win/main.cs +196 -19
- package/index.js +239 -42
- package/package.json +9 -2
- package/packager.js +30 -7
package/index.js
CHANGED
|
@@ -267,7 +267,8 @@ class Window extends Events.EventEmitter {
|
|
|
267
267
|
* @param {string} command
|
|
268
268
|
* @param {string[]} args
|
|
269
269
|
*/
|
|
270
|
-
sendCommand(command, args
|
|
270
|
+
sendCommand(command, ...args) {
|
|
271
|
+
if(args[0] instanceof Array) args = args[0].concat(args.slice(1));
|
|
271
272
|
const normalizedArgs = Array.isArray(args) ? args.map(String) : [String(args)];
|
|
272
273
|
|
|
273
274
|
const payload = JSON.stringify({
|
|
@@ -316,7 +317,7 @@ class Window extends Events.EventEmitter {
|
|
|
316
317
|
* @param {string} path The path to the file to load.
|
|
317
318
|
*/
|
|
318
319
|
async loadFile(path) {
|
|
319
|
-
const res = await this.request("loadFile", `loadFile-reply-${this.id}
|
|
320
|
+
const res = await this.request("loadFile", { replyChannel: `loadFile-reply-${this.id}` }, path);
|
|
320
321
|
this.emit("file-loaded", path);
|
|
321
322
|
this.emit("navigated", path);
|
|
322
323
|
return res;
|
|
@@ -327,7 +328,7 @@ class Window extends Events.EventEmitter {
|
|
|
327
328
|
* @param {string} url The URL to load.
|
|
328
329
|
*/
|
|
329
330
|
async loadURL(url) {
|
|
330
|
-
const res = await this.request("loadURL", `loadURL-reply-${this.id}
|
|
331
|
+
const res = await this.request("loadURL", { replyChannel: `loadURL-reply-${this.id}` }, url);
|
|
331
332
|
this.emit("url-loaded", url);
|
|
332
333
|
this.emit("navigated", url);
|
|
333
334
|
return res;
|
|
@@ -338,7 +339,7 @@ class Window extends Events.EventEmitter {
|
|
|
338
339
|
* @param {string} channel The IPC channel to send the message on.
|
|
339
340
|
* @param {string[]} args The arguments to send with the message.
|
|
340
341
|
*/
|
|
341
|
-
|
|
342
|
+
emitToRenderer(channel, args = []) {
|
|
342
343
|
if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
|
|
343
344
|
const payload = JSON.stringify({
|
|
344
345
|
windowId: this.id,
|
|
@@ -348,10 +349,19 @@ sendIpc(channel, args = []) {
|
|
|
348
349
|
activeSocket.send(payload);
|
|
349
350
|
this.emit("ipc-sent", { channel, args });
|
|
350
351
|
} else {
|
|
351
|
-
warn(`Cannot send IPC message, socket not ready
|
|
352
|
+
warn(`Cannot send IPC message on ${channel}, socket not ready.`);
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @deprecated Use emitToRenderer instead.
|
|
359
|
+
*/
|
|
360
|
+
sendIpc = (channel, args = []) => {
|
|
361
|
+
warn("sendIpc is deprecated. Use emitToRenderer instead.");
|
|
362
|
+
this.emitToRenderer(channel, args);
|
|
363
|
+
}
|
|
364
|
+
|
|
355
365
|
#created = false;
|
|
356
366
|
|
|
357
367
|
/**
|
|
@@ -405,6 +415,10 @@ create(width, height, darwinOptions = {
|
|
|
405
415
|
this.sendCommand("forceCloseWindow");
|
|
406
416
|
}
|
|
407
417
|
|
|
418
|
+
isClosed() {
|
|
419
|
+
return !activeWindows.has(this);
|
|
420
|
+
}
|
|
421
|
+
|
|
408
422
|
|
|
409
423
|
/**
|
|
410
424
|
* Sets the application menu for this window.
|
|
@@ -488,10 +502,6 @@ resize(width, height) {
|
|
|
488
502
|
* Opens the developer tools for the window. Emits a "devtools-opened" event when done. Does not work on macOS. For macOS, right-click the window and select "Inspect Element" to open dev tools for that window.
|
|
489
503
|
*/
|
|
490
504
|
openDevTools() {
|
|
491
|
-
if(process.platform === "darwin") {
|
|
492
|
-
warn("The openDevTools command is not supported on macOS due to OS limitations. Please right-click the window and select 'Inspect Element' to access developer tools.");
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
505
|
this.sendCommand("openDevTools");
|
|
496
506
|
this.emit("devtools-opened");
|
|
497
507
|
}
|
|
@@ -560,6 +570,26 @@ focus() {
|
|
|
560
570
|
this.emit("focused");
|
|
561
571
|
}
|
|
562
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Checks if the window is currently visible. Returns a Promise that resolves to true if the window is visible, or false if it is hidden. Emits an "is-visible-checked" event with the result as data when done.
|
|
575
|
+
* @returns {Promise<boolean>} True if the window is visible, false otherwise.
|
|
576
|
+
*/
|
|
577
|
+
async isVisible() {
|
|
578
|
+
const res = await this.request("isVisible");
|
|
579
|
+
return res?.isVisible === "true";
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Checks if the window is currently in fullscreen mode.
|
|
584
|
+
* @returns {Promise<boolean>} True if the window is fullscreen, false otherwise.
|
|
585
|
+
*/
|
|
586
|
+
async isFullscreen() {
|
|
587
|
+
const res = await this.request("isFullscreen");
|
|
588
|
+
return res?.isFullscreen === "true";
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
|
|
563
593
|
/**
|
|
564
594
|
* Reloads the window. Emits a "reloaded" event when done.
|
|
565
595
|
*/
|
|
@@ -573,7 +603,7 @@ reload() {
|
|
|
573
603
|
* @returns {Promise<Buffer|null>} The captured screenshot as a Buffer, or null if the capture failed.
|
|
574
604
|
*/
|
|
575
605
|
async capturePage() {
|
|
576
|
-
const response = await this.request("capturePage", `capture-page-result-${this.id}`);
|
|
606
|
+
const response = await this.request("capturePage", { replyChannel: `capture-page-result-${this.id}` });
|
|
577
607
|
return response.image ? Buffer.from(response.image, "base64") : null;
|
|
578
608
|
}
|
|
579
609
|
|
|
@@ -582,52 +612,94 @@ async capturePage() {
|
|
|
582
612
|
* @returns {Promise<boolean>} True if the window can navigate back, false otherwise.
|
|
583
613
|
*/
|
|
584
614
|
async canGoBack() {
|
|
585
|
-
const response = await this.request("canGoBack"
|
|
615
|
+
const response = await this.request("canGoBack");
|
|
586
616
|
return response === "true";
|
|
587
617
|
}
|
|
588
618
|
|
|
619
|
+
// @ts-check
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* @typedef {Object} RequestOptions
|
|
623
|
+
* @property {number} [timeout]
|
|
624
|
+
* @property {boolean} [noTimeout]
|
|
625
|
+
* @property {string} [replyChannel]
|
|
626
|
+
*/
|
|
627
|
+
|
|
589
628
|
/**
|
|
590
|
-
*
|
|
591
|
-
* @param {string} command
|
|
592
|
-
* @param {
|
|
593
|
-
* @returns {Promise
|
|
629
|
+
* @overload
|
|
630
|
+
* @param {string} command
|
|
631
|
+
* @param {...any} args
|
|
632
|
+
* @returns {Promise<any>}
|
|
594
633
|
*/
|
|
595
|
-
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* @overload
|
|
637
|
+
* @param {string} command
|
|
638
|
+
* @param {RequestOptions} options
|
|
639
|
+
* @param {...any} args
|
|
640
|
+
* @returns {Promise<any>}
|
|
641
|
+
*/
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Send an IPC request.
|
|
645
|
+
* @param {string} command
|
|
646
|
+
* @param {...any} args
|
|
647
|
+
*/
|
|
648
|
+
async request(command, ...args) {
|
|
596
649
|
return new Promise((resolve, reject) => {
|
|
597
650
|
let settled = false;
|
|
598
651
|
|
|
652
|
+
let options = {};
|
|
653
|
+
|
|
654
|
+
if(typeof args[0] === "object" && args[0].constructor === Object) {
|
|
655
|
+
options = args[0];
|
|
656
|
+
args = args.slice(1);
|
|
657
|
+
}
|
|
658
|
+
|
|
599
659
|
if(!command) {
|
|
600
660
|
reject(new Error("Command is required for request"));
|
|
601
661
|
return;
|
|
602
662
|
}
|
|
603
663
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
664
|
+
const reqId = crypto.randomUUID();
|
|
665
|
+
|
|
666
|
+
let replyChannel = `${command}-reply-${reqId}`;
|
|
667
|
+
|
|
668
|
+
if (options.replyChannel) {
|
|
669
|
+
replyChannel = options.replyChannel;
|
|
670
|
+
} else if(args[0] && (args[0].includes("-reply-") || args[0].includes("-result-"))) {
|
|
671
|
+
// TEMP TRANSITIONAL LOGIC TO SUPPORT LEGACY REQUESTS THAT PASS REPLY CHANNEL AS FIRST ARGUMENT. WILL BE REMOVED IN A FUTURE RELEASE.
|
|
672
|
+
replyChannel = args[0];
|
|
673
|
+
}
|
|
607
674
|
|
|
608
675
|
const unsubscribe = ipc.handle(replyChannel, (data) => {
|
|
609
676
|
if (!settled) {
|
|
610
677
|
settled = true;
|
|
611
678
|
clearTimeout(timeout);
|
|
612
679
|
unsubscribe();
|
|
680
|
+
|
|
681
|
+
for (const key in data) {
|
|
682
|
+
if (data[key] === "true") {
|
|
683
|
+
data[key] = true;
|
|
684
|
+
} else if (data[key] === "false") {
|
|
685
|
+
data[key] = false;
|
|
686
|
+
} else if (!isNaN(data[key])) {
|
|
687
|
+
data[key] = Number(data[key]);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
613
691
|
resolve(data);
|
|
614
692
|
}
|
|
615
693
|
});
|
|
616
694
|
|
|
617
695
|
let timeout;
|
|
618
696
|
|
|
619
|
-
if(!
|
|
697
|
+
if(!options.noTimeout) {
|
|
620
698
|
let timeoutDuration = 7000;
|
|
621
|
-
|
|
622
|
-
const timeoutArg = args.find(
|
|
623
|
-
arg => typeof arg === "string" && arg.startsWith("TIMEOUT=")
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
if (timeoutArg) {
|
|
627
|
-
|
|
628
|
-
args = args.filter(arg => arg !== timeoutArg);
|
|
699
|
+
|
|
629
700
|
|
|
630
|
-
|
|
701
|
+
if (options.timeout) {
|
|
702
|
+
timeoutDuration = options.timeout;
|
|
631
703
|
}
|
|
632
704
|
|
|
633
705
|
timeout = setTimeout(() => {
|
|
@@ -650,7 +722,7 @@ if (timeoutArg) {
|
|
|
650
722
|
* @returns {Promise<boolean>} True if the window can navigate forward, false otherwise.
|
|
651
723
|
*/
|
|
652
724
|
async canGoForward() {
|
|
653
|
-
const response = await this.request("canGoForward"
|
|
725
|
+
const response = await this.request("canGoForward");
|
|
654
726
|
return response === "true";
|
|
655
727
|
}
|
|
656
728
|
|
|
@@ -715,7 +787,7 @@ setBounds(x, y, width, height) {
|
|
|
715
787
|
* @returns {Promise<string|null>} The user's input as a string, or null if the user cancelled the prompt.
|
|
716
788
|
*/
|
|
717
789
|
async prompt(message, defaultValue = "") {
|
|
718
|
-
const res = await this.request("prompt",
|
|
790
|
+
const res = await this.request("prompt", message, defaultValue);
|
|
719
791
|
this.emit("prompt", { message, defaultValue });
|
|
720
792
|
return res?.input;
|
|
721
793
|
}
|
|
@@ -747,8 +819,12 @@ setContextMenu(menuTemplate) {
|
|
|
747
819
|
this.emit("context-menu-updated", menuTemplate);
|
|
748
820
|
}
|
|
749
821
|
|
|
822
|
+
/**
|
|
823
|
+
* Checks if the window is currently focused.
|
|
824
|
+
* @returns {Promise<boolean>} True if the window is focused, false otherwise.
|
|
825
|
+
*/
|
|
750
826
|
async isFocused() {
|
|
751
|
-
const res = await this.request("isFocused"
|
|
827
|
+
const res = await this.request("isFocused");
|
|
752
828
|
return res?.isFocused === "true";
|
|
753
829
|
}
|
|
754
830
|
|
|
@@ -757,7 +833,7 @@ async isFocused() {
|
|
|
757
833
|
* @returns {Promise<{x: number, y: number, width: number, height: number}>} An object containing the window's bounds.
|
|
758
834
|
*/
|
|
759
835
|
async getBounds() {
|
|
760
|
-
return await this.request("getBounds"
|
|
836
|
+
return await this.request("getBounds");
|
|
761
837
|
}
|
|
762
838
|
|
|
763
839
|
/**
|
|
@@ -765,7 +841,7 @@ async getBounds() {
|
|
|
765
841
|
* @returns {Promise<string>} The current URL loaded in the window.
|
|
766
842
|
*/
|
|
767
843
|
async getURL() {
|
|
768
|
-
return (await this.request("getURL"
|
|
844
|
+
return (await this.request("getURL"))?.url || "";
|
|
769
845
|
}
|
|
770
846
|
|
|
771
847
|
/**
|
|
@@ -773,7 +849,7 @@ async getURL() {
|
|
|
773
849
|
* @returns {Promise<string>} The current title of the window.
|
|
774
850
|
*/
|
|
775
851
|
async getTitle() {
|
|
776
|
-
return (await this.request("getTitle"
|
|
852
|
+
return (await this.request("getTitle"))?.title || "";
|
|
777
853
|
}
|
|
778
854
|
|
|
779
855
|
/**
|
|
@@ -800,7 +876,7 @@ setTitlebarTransparent(isTransparent) {
|
|
|
800
876
|
* @returns {Promise<*>} A Promise that resolves to the result of the evaluation.
|
|
801
877
|
*/
|
|
802
878
|
async evaluateJavaScript(script) {
|
|
803
|
-
const res = await this.request("evaluateJS",
|
|
879
|
+
const res = await this.request("evaluateJS", script);
|
|
804
880
|
return res.result;
|
|
805
881
|
}
|
|
806
882
|
|
|
@@ -950,7 +1026,7 @@ async removeOnClick(selector) {
|
|
|
950
1026
|
* @returns {Promise<boolean>} True if the user confirmed, false if the user cancelled.
|
|
951
1027
|
*/
|
|
952
1028
|
async confirm(message) {
|
|
953
|
-
const res = await this.request("confirm",
|
|
1029
|
+
const res = await this.request("confirm", message);
|
|
954
1030
|
this.emit("confirm", message);
|
|
955
1031
|
return res?.confirmed === "true";
|
|
956
1032
|
}
|
|
@@ -961,10 +1037,70 @@ async confirm(message) {
|
|
|
961
1037
|
* @returns {Promise<void>} A Promise that resolves when the swipe navigation setting has been updated.
|
|
962
1038
|
*/
|
|
963
1039
|
async setSwipeNavigation(enabled) {
|
|
964
|
-
const res = await this.request("setSwipeNav",
|
|
1040
|
+
const res = await this.request("setSwipeNav", String(enabled));
|
|
965
1041
|
this.emit("swipe-navigation-updated", enabled);
|
|
966
1042
|
}
|
|
967
1043
|
|
|
1044
|
+
/**
|
|
1045
|
+
* Checks if swipe navigation is enabled for the window. Returns a Promise that resolves to true if swipe navigation is enabled, or false if it is disabled. Emits an "is-swipe-navigation-enabled-checked" event with the result as data when done.
|
|
1046
|
+
* @returns {Promise<boolean>} True if swipe navigation is enabled, false otherwise.
|
|
1047
|
+
*/
|
|
1048
|
+
async isSwipeNavigationEnabled() {
|
|
1049
|
+
const res = await this.request("isSwipeNavEnabled");
|
|
1050
|
+
return res?.enabled === "true";
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Adds content blocker rules to the window. The rules can be provided as a JSON object, loaded from a URL that returns JSON, or loaded from a local file.
|
|
1055
|
+
* Once the rules are added, the window will block content according to the specified rules. Emits a "content-blocker-updated" event with the new rules as data when done.
|
|
1056
|
+
* @param {Object} config The configuration for adding content blocker rules.
|
|
1057
|
+
* @param {Object[]} [config.json] An array of content blocker rules as JSON objects. Each rule should follow the format specified by the native layer's content blocking implementation.
|
|
1058
|
+
* @param {string} [config.url] A URL that returns a JSON array of content blocker rules. If provided, the rules will be loaded from this URL instead of using the `json` property.
|
|
1059
|
+
* @param {string} [config.file] A local file path containing the content blocker rules in JSON format. If provided, the rules will be loaded from this file instead of using the `json` or `url` properties.
|
|
1060
|
+
* @param {boolean} [config.reload] Whether to reload the window after adding the content blocker rules. Reloading may be necessary for the new rules to take effect immediately, but it can also be set to false if you want to add rules without interrupting the user's current session.
|
|
1061
|
+
* @param {boolean} [config.clearExisting] Whether to clear existing content blocker rules before adding the new ones. If false, the new rules will be added alongside any existing rules. If true, all existing rules will be removed before adding the new ones.
|
|
1062
|
+
* @platform macOS only
|
|
1063
|
+
* @see https://webkit.org/blog/3476/content-blockers-first-look/
|
|
1064
|
+
*/
|
|
1065
|
+
async addToContentBlocker(config={ json:[], url:"", file:"", reload:true, clearExisting: false }) {
|
|
1066
|
+
|
|
1067
|
+
if(process.platform !== "darwin") return;
|
|
1068
|
+
|
|
1069
|
+
let json = config.json || [];
|
|
1070
|
+
|
|
1071
|
+
if(config.file) {
|
|
1072
|
+
json = config.file
|
|
1073
|
+
} else {
|
|
1074
|
+
if(config.url) {
|
|
1075
|
+
let req = (await fetch(config.url));
|
|
1076
|
+
let _json = await req.json();
|
|
1077
|
+
|
|
1078
|
+
if(json.length) {
|
|
1079
|
+
json = json.concat(_json);
|
|
1080
|
+
} else {
|
|
1081
|
+
json = _json;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
json = JSON.stringify(json);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
const res = await this.request("addToContentBlocker", json, config.reload, config.clearExisting);
|
|
1090
|
+
this.emit("content-blocker-updated", json);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Displays a file open dialog and returns a Promise that resolves to an array of selected file paths, or null if the user cancelled the dialog.
|
|
1095
|
+
* @param {*} options The options for the file open dialog.
|
|
1096
|
+
* @returns
|
|
1097
|
+
*/
|
|
1098
|
+
async showFileOpenDialog(options = {}) {
|
|
1099
|
+
this.emit("show-file-open-dialog", options);
|
|
1100
|
+
return this.request("showFileOpenDialog", options);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
|
|
968
1104
|
}
|
|
969
1105
|
|
|
970
1106
|
const app = {
|
|
@@ -1047,9 +1183,10 @@ const app = {
|
|
|
1047
1183
|
userData: {
|
|
1048
1184
|
/**
|
|
1049
1185
|
* Gets the path to the user data directory for the application. The path is determined based on the operating system and the application name. If the directory does not exist, it will be created. Emits a "user-data-path-retrieved" event with the path as data when done.
|
|
1186
|
+
* @param {string} [append] An optional string to append to the user data path. This can be used to create subdirectories within the user data directory for organizing different types of data. If provided, the appended path will also be created if it does not exist.
|
|
1050
1187
|
* @returns {string} The path to the user data directory.
|
|
1051
1188
|
*/
|
|
1052
|
-
getPath() {
|
|
1189
|
+
getPath(append) {
|
|
1053
1190
|
let userPath = null;
|
|
1054
1191
|
|
|
1055
1192
|
if (process.platform === "win32") {
|
|
@@ -1074,7 +1211,7 @@ userData: {
|
|
|
1074
1211
|
fs.mkdirSync(userPath, { recursive: true });
|
|
1075
1212
|
}
|
|
1076
1213
|
|
|
1077
|
-
return userPath;
|
|
1214
|
+
return append ? path.join(userPath, append) : userPath;
|
|
1078
1215
|
},
|
|
1079
1216
|
|
|
1080
1217
|
/**
|
|
@@ -1108,11 +1245,71 @@ userData: {
|
|
|
1108
1245
|
/**
|
|
1109
1246
|
* Full access to the underlying event emitter for application-level events, allowing for advanced event handling patterns if needed.
|
|
1110
1247
|
*/
|
|
1111
|
-
events: appEvents
|
|
1248
|
+
events: appEvents,
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Sends a command to the native layer. This is a low-level method that can be used to send arbitrary commands, but for most use cases you will want to use the higher-level methods provided by the Window class instead. Emits an "ipc-sent" event with the command and arguments as data when done.
|
|
1252
|
+
* @param {string} command The command to send to the native layer.
|
|
1253
|
+
* @param {any[]} args The arguments to send with the command.
|
|
1254
|
+
*/
|
|
1255
|
+
sendToNative(command, args) {
|
|
1256
|
+
const firstWin = activeWindows.values().next().value;
|
|
1257
|
+
if (firstWin) {
|
|
1258
|
+
firstWin.sendCommand(command, args);
|
|
1259
|
+
} else {
|
|
1260
|
+
error("No active windows to send command to native layer");
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Sends a request to the native layer and returns a Promise that resolves to the response. This is a low-level method that can be used to send arbitrary requests, but for most use cases you will want to use the higher-level methods provided by the Window class instead. Emits an "ipc-request-sent" event with the command and arguments as data when done.
|
|
1266
|
+
* @param {string} command The command to send to the native layer.
|
|
1267
|
+
* @param {any[]} args The arguments to send with the command.
|
|
1268
|
+
* @returns {Promise<any>} A Promise that resolves to the response from the native layer.
|
|
1269
|
+
*/
|
|
1270
|
+
async requestFromNative(command, ...args) {
|
|
1271
|
+
const firstWin = activeWindows.values().next().value;
|
|
1272
|
+
if (firstWin) {
|
|
1273
|
+
return await firstWin.request(command, ...args);
|
|
1274
|
+
} else {
|
|
1275
|
+
error("No active windows to send request to native layer");
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
|
|
1280
|
+
async isDarkMode() {
|
|
1281
|
+
const res = await this.requestFromNative("isDarkMode");
|
|
1282
|
+
return res?.isDarkMode;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const clipboard = {
|
|
1288
|
+
|
|
1289
|
+
async writeText(text) {
|
|
1290
|
+
app.sendToNative("writeToClipboard", [text]);
|
|
1291
|
+
},
|
|
1292
|
+
|
|
1293
|
+
async readText() {
|
|
1294
|
+
const res = await app.requestFromNative("readFromClipboard");
|
|
1295
|
+
return res.text || "";
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const blockPowerSave = {
|
|
1112
1301
|
|
|
1302
|
+
start() {
|
|
1303
|
+
app.sendToNative("blockPowerSave");
|
|
1304
|
+
},
|
|
1305
|
+
|
|
1306
|
+
stop() {
|
|
1307
|
+
app.sendToNative("unblockPowerSave");
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1113
1310
|
}
|
|
1114
1311
|
|
|
1115
|
-
module.exports = { Window, ipc, isPackaged, app, PORT };
|
|
1312
|
+
module.exports = { Window, ipc, isPackaged, app, PORT, clipboard, blockPowerSave };
|
|
1116
1313
|
|
|
1117
1314
|
const findNearestPackageJson = require("./findpackage");
|
|
1118
1315
|
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "https://github.com/systemsoftware/positron.js"
|
|
6
6
|
},
|
|
7
7
|
"homepage": "https://positronjs.gitbook.io",
|
|
8
|
-
"version": "1.0.
|
|
8
|
+
"version": "1.0.5",
|
|
9
9
|
"main": "index.js",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node --test"
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
"keywords": [
|
|
14
14
|
"app",
|
|
15
15
|
"framework",
|
|
16
|
-
"desktop"
|
|
16
|
+
"desktop",
|
|
17
|
+
"cross-platform",
|
|
18
|
+
"native",
|
|
19
|
+
"javascript",
|
|
20
|
+
"node",
|
|
21
|
+
"macos",
|
|
22
|
+
"windows"
|
|
17
23
|
],
|
|
18
24
|
"author": "Bryce",
|
|
19
25
|
"license": "MIT",
|
|
@@ -21,6 +27,7 @@
|
|
|
21
27
|
"dependencies": {
|
|
22
28
|
"@yao-pkg/pkg": "^6.20.0",
|
|
23
29
|
"esbuild": "^0.28.0",
|
|
30
|
+
"semver": "^7.8.1",
|
|
24
31
|
"ws": "^8.20.1"
|
|
25
32
|
},
|
|
26
33
|
"bin": {
|
package/packager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
-
const { execSync } = require("child_process");
|
|
3
|
+
const { execSync, execFileSync } = require("child_process");
|
|
4
4
|
const { info, error, success } = require("./logs");
|
|
5
5
|
const https = require("https");
|
|
6
6
|
const esbuild = require("esbuild");
|
|
@@ -70,9 +70,12 @@ async function packageMacOS(appRoot, distDir, appName) {
|
|
|
70
70
|
error("Fatal: Native compiled binary missing from bin/. Run build first.");
|
|
71
71
|
process.exit(1);
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
fs.
|
|
75
|
-
|
|
73
|
+
const binaryPath = path.join(macosPath, appName);
|
|
74
|
+
fs.copyFileSync(compiledBinary, binaryPath);
|
|
75
|
+
fs.chmodSync(binaryPath, "755");
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
76
79
|
const packageJsonPath = path.join(appRoot, "package.json");
|
|
77
80
|
const package = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
78
81
|
|
|
@@ -105,14 +108,29 @@ async function packageMacOS(appRoot, distDir, appName) {
|
|
|
105
108
|
handleJavaScriptPipeline(appRoot, resourcesPath);
|
|
106
109
|
|
|
107
110
|
const bundledJs = path.join(resourcesPath, "index.js");
|
|
111
|
+
const backendName = appName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase() + '-backend';
|
|
108
112
|
if (fs.existsSync(bundledJs)) {
|
|
109
|
-
await compileWithPkg(bundledJs, "darwin", resourcesPath,
|
|
113
|
+
await compileWithPkg(bundledJs, "darwin", resourcesPath, backendName);
|
|
114
|
+
const binPathEscaped = path.join(resourcesPath, backendName).replace(/"/g, '\\"');
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
let swiftScript = "";
|
|
118
|
+
if(fs.existsSync(path.join(__dirname, ['positronicon', 'png'].join('.')))) {
|
|
119
|
+
const iconPathEscaped = path.join(__dirname, ['positronicon', 'png'].join('.')).replace(/"/g, '\\"');
|
|
120
|
+
swiftScript = `import Cocoa; NSWorkspace.shared.setIcon(NSImage(contentsOfFile: "${iconPathEscaped}"), forFile: "${binPathEscaped}", options: []);`;
|
|
121
|
+
}
|
|
122
|
+
execFileSync("swift", ["-e", swiftScript], { stdio: "ignore" });
|
|
123
|
+
} catch (err) {
|
|
124
|
+
error("Failed to set custom icon on native binary:", err);
|
|
125
|
+
}
|
|
110
126
|
|
|
111
|
-
// fs.renameSync(targetNodePath, path.join(resourcesPath, "positron-backend"));
|
|
112
127
|
}
|
|
113
128
|
|
|
114
129
|
fs.rmSync(path.join(resourcesPath, "icon.ico"), { force: true });
|
|
115
130
|
|
|
131
|
+
if(!process.argv.includes('--keep-package-json') || !process.argv.includes('--kpj')) {
|
|
132
|
+
fs.rmSync(path.join(resourcesPath, "package.json"), { force: true });
|
|
133
|
+
}
|
|
116
134
|
|
|
117
135
|
success(`Successfully packaged macOS app at: ${appBundlePath}`);
|
|
118
136
|
}
|
|
@@ -162,9 +180,10 @@ async function packageWindows(appRoot, distDir, appName) {
|
|
|
162
180
|
}
|
|
163
181
|
|
|
164
182
|
const bundledJs = path.join(resourcesPath, "index.js");
|
|
183
|
+
const backendName = appName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase() + '-backend';
|
|
165
184
|
|
|
166
185
|
if (fs.existsSync(bundledJs)) {
|
|
167
|
-
await compileWithPkg(bundledJs, "win32", resourcesPath,
|
|
186
|
+
await compileWithPkg(bundledJs, "win32", resourcesPath, backendName);
|
|
168
187
|
} else {
|
|
169
188
|
error(`[Packager] Fatal: Bundled JavaScript entry point missing at ${bundledJs}`);
|
|
170
189
|
process.exit(1);
|
|
@@ -173,6 +192,10 @@ async function packageWindows(appRoot, distDir, appName) {
|
|
|
173
192
|
fs.rmSync(path.join(resourcesPath, "icon.icns"), { force: true });
|
|
174
193
|
fs.rmSync(path.join(resourcesPath, "icon.ico"), { force: true });
|
|
175
194
|
|
|
195
|
+
if(!process.argv.includes('--keep-package-json') || !process.argv.includes('--kpj')) {
|
|
196
|
+
fs.rmSync(path.join(resourcesPath, "package.json"), { force: true });
|
|
197
|
+
}
|
|
198
|
+
|
|
176
199
|
const macBinaryPath = path.join(outputFolder, "positron-runtime");
|
|
177
200
|
if (fs.existsSync(macBinaryPath)) {
|
|
178
201
|
fs.rmSync(macBinaryPath);
|