electrobun 0.11.0-beta.3 → 0.13.0-beta.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/bun.lock CHANGED
@@ -7,6 +7,8 @@
7
7
  "dependencies": {
8
8
  "@oneidentity/zstd-js": "^1.0.3",
9
9
  "archiver": "^7.0.1",
10
+ "png-to-ico": "^2.1.8",
11
+ "rcedit": "^4.0.1",
10
12
  "rpc-anywhere": "1.5.0",
11
13
  "tar": "^6.2.1",
12
14
  },
@@ -19,6 +21,8 @@
19
21
  "packages": {
20
22
  "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
21
23
 
24
+ "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
25
+
22
26
  "@oneidentity/zstd-js": ["@oneidentity/zstd-js@1.0.3", "", { "dependencies": { "@types/emscripten": "^1.39.4" } }, "sha512-Jm6sawqxLzBrjC4sg2BeXToa33yPzUmq20CKsehKY2++D/gHb/oSwVjNgT+RH4vys+r8FynrgcNzGwhZWMLzfQ=="],
23
27
 
24
28
  "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
@@ -35,7 +39,7 @@
35
39
 
36
40
  "@types/har-format": ["@types/har-format@1.2.16", "", {}, "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A=="],
37
41
 
38
- "@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
42
+ "@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
39
43
 
40
44
  "@types/readdir-glob": ["@types/readdir-glob@1.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg=="],
41
45
 
@@ -89,6 +93,8 @@
89
93
 
90
94
  "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
91
95
 
96
+ "cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="],
97
+
92
98
  "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
93
99
 
94
100
  "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
@@ -113,10 +119,14 @@
113
119
 
114
120
  "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
115
121
 
122
+ "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
123
+
116
124
  "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
117
125
 
118
126
  "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
119
127
 
128
+ "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
129
+
120
130
  "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
121
131
 
122
132
  "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
@@ -131,6 +141,8 @@
131
141
 
132
142
  "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
133
143
 
144
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
145
+
134
146
  "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
135
147
 
136
148
  "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
@@ -145,10 +157,16 @@
145
157
 
146
158
  "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
147
159
 
160
+ "png-to-ico": ["png-to-ico@2.1.8", "", { "dependencies": { "@types/node": "^17.0.36", "minimist": "^1.2.6", "pngjs": "^6.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-Nf+IIn/cZ/DIZVdGveJp86NG5uNib1ZXMiDd/8x32HCTeKSvgpyg6D/6tUBn1QO/zybzoMK0/mc3QRgAyXdv9w=="],
161
+
162
+ "pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
163
+
148
164
  "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
149
165
 
150
166
  "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
151
167
 
168
+ "rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="],
169
+
152
170
  "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
153
171
 
154
172
  "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
@@ -195,6 +213,12 @@
195
213
 
196
214
  "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
197
215
 
216
+ "@types/readdir-glob/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
217
+
218
+ "@types/ws/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
219
+
220
+ "bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
221
+
198
222
  "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
199
223
 
200
224
  "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -78,6 +78,15 @@ export type InternalWebviewHandlers = RPCSchema<{
78
78
  id: number;
79
79
  rules: string[];
80
80
  };
81
+ webviewTagFindInPage: {
82
+ id: number;
83
+ searchText: string;
84
+ forward: boolean;
85
+ matchCase: boolean;
86
+ };
87
+ webviewTagStopFind: {
88
+ id: number;
89
+ };
81
90
  };
82
91
  }>;
83
92
 
@@ -572,6 +572,34 @@ const ConfigureWebviewTags = (
572
572
  rules: rules,
573
573
  });
574
574
  }
575
+
576
+ findInPage(searchText: string, options?: {forward?: boolean; matchCase?: boolean}) {
577
+ if (!this.webviewId) {
578
+ console.warn('findInPage called on removed webview');
579
+ return;
580
+ }
581
+
582
+ const forward = options?.forward ?? true;
583
+ const matchCase = options?.matchCase ?? false;
584
+
585
+ this.internalRpc.send.webviewTagFindInPage({
586
+ id: this.webviewId,
587
+ searchText,
588
+ forward,
589
+ matchCase,
590
+ });
591
+ }
592
+
593
+ stopFindInPage() {
594
+ if (!this.webviewId) {
595
+ console.warn('stopFindInPage called on removed webview');
596
+ return;
597
+ }
598
+
599
+ this.internalRpc.send.webviewTagStopFind({
600
+ id: this.webviewId,
601
+ });
602
+ }
575
603
  }
576
604
 
577
605
  customElements.define("electrobun-webview", WebviewTag);
@@ -129,24 +129,30 @@ export interface ElectrobunConfig {
129
129
  * @default false
130
130
  */
131
131
  codesign?: boolean;
132
-
132
+
133
133
  /**
134
134
  * Enable notarization for macOS builds (requires codesign)
135
135
  * @default false
136
136
  */
137
137
  notarize?: boolean;
138
-
138
+
139
139
  /**
140
140
  * Bundle CEF (Chromium Embedded Framework) instead of using system WebView
141
141
  * @default false
142
142
  */
143
143
  bundleCEF?: boolean;
144
-
144
+
145
+ /**
146
+ * Default renderer for webviews when not explicitly specified
147
+ * @default 'native'
148
+ */
149
+ defaultRenderer?: 'native' | 'cef';
150
+
145
151
  /**
146
152
  * macOS entitlements for code signing
147
153
  */
148
154
  entitlements?: Record<string, boolean | string>;
149
-
155
+
150
156
  /**
151
157
  * Path to .iconset folder containing app icons
152
158
  * @default "icon.iconset"
@@ -163,6 +169,20 @@ export interface ElectrobunConfig {
163
169
  * @default false
164
170
  */
165
171
  bundleCEF?: boolean;
172
+
173
+ /**
174
+ * Default renderer for webviews when not explicitly specified
175
+ * @default 'native'
176
+ */
177
+ defaultRenderer?: 'native' | 'cef';
178
+
179
+ /**
180
+ * Path to application icon (.ico format)
181
+ * Used for the installer/extractor wrapper, desktop shortcuts, and taskbar
182
+ * Should include multiple sizes (16x16, 32x32, 48x48, 256x256) for best results
183
+ * @example "assets/icon.ico"
184
+ */
185
+ icon?: string;
166
186
  };
167
187
 
168
188
  /**
@@ -175,6 +195,20 @@ export interface ElectrobunConfig {
175
195
  * @default false
176
196
  */
177
197
  bundleCEF?: boolean;
198
+
199
+ /**
200
+ * Default renderer for webviews when not explicitly specified
201
+ * @default 'native'
202
+ */
203
+ defaultRenderer?: 'native' | 'cef';
204
+
205
+ /**
206
+ * Path to application icon (PNG format recommended)
207
+ * Used for desktop entries, window icons, and taskbar
208
+ * Should be at least 256x256 pixels for best results
209
+ * @example "assets/icon.png"
210
+ */
211
+ icon?: string;
178
212
  };
179
213
  };
180
214
 
@@ -11,6 +11,7 @@ import {
11
11
  createRPC,
12
12
  } from "rpc-anywhere";
13
13
  import { Updater } from "./Updater";
14
+ import { BuildConfig } from "./BuildConfig";
14
15
  import type { BuiltinBunToWebviewSchema,BuiltinWebviewToBunSchema } from "../../browser/builtinrpcSchema";
15
16
  import { rpcPort, sendMessageToWebviewViaSocket } from "./Socket";
16
17
  import { randomBytes } from "crypto";
@@ -49,11 +50,14 @@ interface ElectrobunWebviewRPCSChema {
49
50
  webview: RPCSchema;
50
51
  }
51
52
 
53
+ const hash = await Updater.localInfo.hash();
54
+ const buildConfig = await BuildConfig.get();
55
+
52
56
  const defaultOptions: Partial<BrowserViewOptions> = {
53
57
  url: null,
54
58
  html: null,
55
59
  preload: null,
56
- renderer: 'native',
60
+ renderer: buildConfig.defaultRenderer,
57
61
  frame: {
58
62
  x: 0,
59
63
  y: 0,
@@ -61,10 +65,6 @@ const defaultOptions: Partial<BrowserViewOptions> = {
61
65
  height: 600,
62
66
  },
63
67
  };
64
-
65
-
66
-
67
- const hash = await Updater.localInfo.hash();
68
68
  // Note: we use the build's hash to separate from different apps and different builds
69
69
  // but we also want a randomId to separate different instances of the same app
70
70
  const randomId = Math.random().toString(36).substring(7);
@@ -136,22 +136,22 @@ export class BrowserView<T> {
136
136
  }
137
137
  }
138
138
 
139
- init() {
139
+ init() {
140
140
  this.createStreams();
141
-
141
+
142
142
  // TODO: add a then to this that fires an onReady event
143
143
  return ffi.request.createWebview({
144
144
  id: this.id,
145
145
  windowId: this.windowId,
146
- renderer: this.renderer,
146
+ renderer: this.renderer,
147
147
  rpcPort: rpcPort,
148
148
  // todo: consider sending secretKey as base64
149
149
  secretKey: this.secretKey.toString(),
150
150
  hostWebviewId: this.hostWebviewId || null,
151
151
  pipePrefix: this.pipePrefix,
152
- partition: this.partition,
152
+ partition: this.partition,
153
153
  // Only pass URL if no HTML content is provided to avoid conflicts
154
- url: this.html ? null : this.url,
154
+ url: this.html ? null : this.url,
155
155
  html: this.html,
156
156
  preload: this.preload,
157
157
  frame: {
@@ -162,6 +162,7 @@ export class BrowserView<T> {
162
162
  },
163
163
  autoResize: this.autoResize,
164
164
  navigationRules: this.navigationRules,
165
+ // transparent is looked up from parent window in native.ts
165
166
  });
166
167
 
167
168
 
@@ -3,6 +3,9 @@ import electrobunEventEmitter from "../events/eventEmitter";
3
3
  import { BrowserView } from "./BrowserView";
4
4
  import { type RPC } from "rpc-anywhere";
5
5
  import {FFIType} from 'bun:ffi'
6
+ import { BuildConfig } from "./BuildConfig";
7
+
8
+ const buildConfig = await BuildConfig.get();
6
9
 
7
10
  let nextWindowId = 1;
8
11
 
@@ -18,15 +21,20 @@ type WindowOptionsType<T = undefined> = {
18
21
  html: string | null;
19
22
  preload: string | null;
20
23
  renderer: 'native' | 'cef';
21
- rpc?: T;
24
+ rpc?: T;
22
25
  styleMask?: {};
23
- // TODO: implement all of them
24
- titleBarStyle: "hiddenInset" | "default";
26
+ // titleBarStyle options:
27
+ // - 'default': normal titlebar with native window controls
28
+ // - 'hidden': no titlebar, no native window controls (for fully custom chrome)
29
+ // - 'hiddenInset': transparent titlebar with inset native controls
30
+ titleBarStyle: "hidden" | "hiddenInset" | "default";
31
+ // transparent: when true, window background is transparent (see-through)
32
+ transparent: boolean;
25
33
  navigationRules: string | null;
26
34
  };
27
35
 
28
36
  const defaultOptions: WindowOptionsType = {
29
- title: "Electrobun",
37
+ title: "Electrobun",
30
38
  frame: {
31
39
  x: 0,
32
40
  y: 0,
@@ -36,8 +44,9 @@ const defaultOptions: WindowOptionsType = {
36
44
  url: "https://electrobun.dev",
37
45
  html: null,
38
46
  preload: null,
39
- renderer: 'native',
47
+ renderer: buildConfig.defaultRenderer,
40
48
  titleBarStyle: "default",
49
+ transparent: false,
41
50
  navigationRules: null,
42
51
  };
43
52
 
@@ -52,6 +61,7 @@ export class BrowserWindow<T> {
52
61
  html: string | null = null;
53
62
  preload: string | null = null;
54
63
  renderer: 'native' | 'cef';
64
+ transparent: boolean = false;
55
65
  frame: {
56
66
  x: number;
57
67
  y: number;
@@ -74,22 +84,24 @@ export class BrowserWindow<T> {
74
84
  this.url = options.url || null;
75
85
  this.html = options.html || null;
76
86
  this.preload = options.preload || null;
77
- this.renderer = options.renderer === 'cef' ? 'cef' : 'native';
87
+ this.renderer = options.renderer || defaultOptions.renderer;
88
+ this.transparent = options.transparent ?? false;
78
89
  this.navigationRules = options.navigationRules || null;
79
-
90
+
80
91
  this.init(options);
81
92
  }
82
93
 
83
94
  init({
84
- rpc,
95
+ rpc,
85
96
  styleMask,
86
97
  titleBarStyle,
98
+ transparent,
87
99
  }: Partial<WindowOptionsType<T>>) {
88
-
100
+
89
101
  this.ptr = ffi.request.createWindow({
90
102
  id: this.id,
91
103
  title: this.title,
92
- url: this.url || "",
104
+ url: this.url || "",
93
105
  frame: {
94
106
  width: this.frame.width,
95
107
  height: this.frame.height,
@@ -110,14 +122,23 @@ export class BrowserWindow<T> {
110
122
  NonactivatingPanel: false,
111
123
  HUDWindow: false,
112
124
  ...(styleMask || {}),
125
+ // hiddenInset: transparent titlebar with inset native controls
113
126
  ...(titleBarStyle === "hiddenInset"
114
127
  ? {
115
128
  Titled: true,
116
129
  FullSizeContentView: true,
117
130
  }
118
131
  : {}),
132
+ // hidden: no titlebar, no native controls (for fully custom chrome)
133
+ ...(titleBarStyle === "hidden"
134
+ ? {
135
+ Titled: false,
136
+ FullSizeContentView: true,
137
+ }
138
+ : {}),
119
139
  },
120
140
  titleBarStyle: titleBarStyle || "default",
141
+ transparent: transparent ?? false,
121
142
  });
122
143
 
123
144
  BrowserWindowMap[this.id] = this;
@@ -153,8 +174,6 @@ export class BrowserWindow<T> {
153
174
  console.log('setting webviewId: ', webview.id)
154
175
 
155
176
  this.webviewId = webview.id;
156
-
157
-
158
177
  }
159
178
 
160
179
  get webview() {
@@ -180,12 +199,16 @@ export class BrowserWindow<T> {
180
199
  return ffi.request.focusWindow({ winId: this.id });
181
200
  }
182
201
 
202
+ show() {
203
+ return ffi.request.focusWindow({ winId: this.id });
204
+ }
205
+
183
206
  minimize() {
184
207
  return ffi.request.minimizeWindow({ winId: this.id });
185
208
  }
186
209
 
187
210
  unminimize() {
188
- return ffi.request.unminimizeWindow({ winId: this.id });
211
+ return ffi.request.restoreWindow({ winId: this.id });
189
212
  }
190
213
 
191
214
  isMinimized(): boolean {
@@ -220,6 +243,41 @@ export class BrowserWindow<T> {
220
243
  return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
221
244
  }
222
245
 
246
+ setPosition(x: number, y: number) {
247
+ this.frame.x = x;
248
+ this.frame.y = y;
249
+ return ffi.request.setWindowPosition({ winId: this.id, x, y });
250
+ }
251
+
252
+ setSize(width: number, height: number) {
253
+ this.frame.width = width;
254
+ this.frame.height = height;
255
+ return ffi.request.setWindowSize({ winId: this.id, width, height });
256
+ }
257
+
258
+ setFrame(x: number, y: number, width: number, height: number) {
259
+ this.frame = { x, y, width, height };
260
+ return ffi.request.setWindowFrame({ winId: this.id, x, y, width, height });
261
+ }
262
+
263
+ getFrame(): { x: number; y: number; width: number; height: number } {
264
+ const frame = ffi.request.getWindowFrame({ winId: this.id });
265
+ // Update internal state
266
+ this.frame = frame;
267
+ return frame;
268
+ }
269
+
270
+ getPosition(): { x: number; y: number } {
271
+ const frame = this.getFrame();
272
+ return { x: frame.x, y: frame.y };
273
+ }
274
+
275
+ getSize(): { width: number; height: number } {
276
+ const frame = this.getFrame();
277
+ return { width: frame.width, height: frame.height };
278
+ }
279
+
280
+
223
281
  // todo (yoav): move this to a class that also has off, append, prepend, etc.
224
282
  // name should only allow browserWindow events
225
283
  on(name, handler) {
@@ -0,0 +1,38 @@
1
+ export type BuildConfigType = {
2
+ defaultRenderer: 'native' | 'cef';
3
+ availableRenderers: ('native' | 'cef')[];
4
+ };
5
+
6
+ let buildConfig: BuildConfigType | null = null;
7
+
8
+ const BuildConfig = {
9
+ /**
10
+ * Get the build configuration. Loads from build.json on first call, then returns cached value.
11
+ */
12
+ get: async (): Promise<BuildConfigType> => {
13
+ if (buildConfig) {
14
+ return buildConfig;
15
+ }
16
+
17
+ try {
18
+ const resourcesDir = 'Resources';
19
+ buildConfig = await Bun.file(`../${resourcesDir}/build.json`).json();
20
+ return buildConfig!;
21
+ } catch (error) {
22
+ // Fallback for dev mode or missing file
23
+ buildConfig = {
24
+ defaultRenderer: 'native',
25
+ availableRenderers: ['native'],
26
+ };
27
+ return buildConfig;
28
+ }
29
+ },
30
+
31
+ /**
32
+ * Get the cached build configuration synchronously.
33
+ * Returns null if config hasn't been loaded yet.
34
+ */
35
+ getCached: (): BuildConfigType | null => buildConfig,
36
+ };
37
+
38
+ export { BuildConfig };
@@ -101,7 +101,7 @@ const startRPCServer = () => {
101
101
  return;
102
102
  }
103
103
  const { webviewId } = ws.data;
104
- console.log("Closed:", webviewId, code, reason);
104
+ // console.log("Closed:", webviewId, code, reason);
105
105
  if (socketMap[webviewId]) {
106
106
  socketMap[webviewId].socket = null;
107
107
  }
@@ -57,6 +57,34 @@ exec ./launcher "$@"
57
57
  console.log(`Created/updated Linux launcher script: ${launcherPath}`);
58
58
  }
59
59
 
60
+ // Create launcher script for AppImage
61
+ async function createLinuxAppImageLauncherScript(appImagePath: string): Promise<void> {
62
+ const parentDir = dirname(appImagePath);
63
+ const launcherPath = join(parentDir, "run.sh");
64
+
65
+ const launcherContent = `#!/bin/bash
66
+ # Electrobun AppImage Launcher
67
+ # This script launches the AppImage
68
+
69
+ # Get the directory where this script is located
70
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
71
+ APPIMAGE_PATH="$SCRIPT_DIR/$(basename "${appImagePath}")"
72
+
73
+ # Force X11 backend for compatibility
74
+ export GDK_BACKEND=x11
75
+
76
+ # Launch the AppImage
77
+ exec "$APPIMAGE_PATH" "$@"
78
+ `;
79
+
80
+ await Bun.write(launcherPath, launcherContent);
81
+
82
+ // Make it executable
83
+ execSync(`chmod +x "${launcherPath}"`);
84
+
85
+ console.log(`Created/updated Linux AppImage launcher script: ${launcherPath}`);
86
+ }
87
+
60
88
  // Cross-platform app data directory
61
89
  function getAppDataDir(): string {
62
90
  switch (currentOS) {
@@ -450,10 +478,28 @@ const Updater = {
450
478
 
451
479
  // Platform-specific path handling
452
480
  let newAppBundlePath: string;
453
- if (currentOS === 'linux' || currentOS === 'win') {
454
- // On Linux/Windows, the actual app is inside a subdirectory
481
+ if (currentOS === 'linux') {
482
+ // On Linux, look for the .AppImage file in the extraction directory
483
+ const appImageName = `${localInfo.name.replace(/ /g, "").replace(/\./g, "-")}.AppImage`;
484
+ newAppBundlePath = join(extractionDir, appImageName);
485
+
486
+ // Verify the AppImage exists
487
+ if (!statSync(newAppBundlePath, { throwIfNoEntry: false })) {
488
+ console.error(`AppImage not found at: ${newAppBundlePath}`);
489
+ console.log("Contents of extraction directory:");
490
+ try {
491
+ const files = readdirSync(extractionDir);
492
+ for (const file of files) {
493
+ console.log(` - ${file}`);
494
+ }
495
+ } catch (e) {
496
+ console.log("Could not list directory contents:", e);
497
+ }
498
+ return;
499
+ }
500
+ } else if (currentOS === 'win') {
501
+ // On Windows, the actual app is inside a subdirectory
455
502
  // Use same sanitization as extractor: remove spaces and dots
456
- // Note: localInfo.name already includes the channel (e.g., "test1-canary")
457
503
  const appBundleName = localInfo.name.replace(/ /g, "").replace(/\./g, "-");
458
504
  newAppBundlePath = join(extractionDir, appBundleName);
459
505
 
@@ -485,10 +531,12 @@ const Updater = {
485
531
  ".."
486
532
  );
487
533
  } else {
488
- // On Linux/Windows, calculate app path using app data directory structure
534
+ // Platform-specific app path calculation
489
535
  const appDataFolder = await Updater.appDataFolder();
490
536
  if (currentOS === 'linux') {
491
- runningAppBundlePath = join(appDataFolder, "app");
537
+ // On Linux, store AppImage as a single file
538
+ const appImageName = `${localInfo.name.replace(/ /g, "").replace(/\./g, "-")}.AppImage`;
539
+ runningAppBundlePath = join(appDataFolder, appImageName);
492
540
  } else {
493
541
  // On Windows, use versioned app folders
494
542
  const currentHash = (await Updater.getLocallocalInfo()).hash;
@@ -519,29 +567,25 @@ const Updater = {
519
567
  // Move new app to running location
520
568
  renameSync(newAppBundlePath, runningAppBundlePath);
521
569
  } else if (currentOS === 'linux') {
522
- // On Linux, create tar backup and replace
523
- // Remove existing backup.tar if it exists
570
+ // On Linux, backup and replace AppImage file
571
+ // Remove existing backup if it exists
524
572
  if (statSync(backupPath, { throwIfNoEntry: false })) {
525
573
  unlinkSync(backupPath);
526
574
  }
527
575
 
528
- // Create tar backup of current app
529
- await tar.c(
530
- {
531
- file: backupPath,
532
- cwd: dirname(runningAppBundlePath),
533
- },
534
- [basename(runningAppBundlePath)]
535
- );
536
-
537
- // Remove current app
538
- rmdirSync(runningAppBundlePath, { recursive: true });
576
+ // Create backup of current AppImage (if it exists)
577
+ if (statSync(runningAppBundlePath, { throwIfNoEntry: false })) {
578
+ renameSync(runningAppBundlePath, backupPath);
579
+ }
539
580
 
540
- // Move new app to app location
581
+ // Move new AppImage to app location
541
582
  renameSync(newAppBundlePath, runningAppBundlePath);
542
583
 
543
- // Recreate run.sh launcher script
544
- await createLinuxLauncherScript(runningAppBundlePath);
584
+ // Make AppImage executable
585
+ execSync(`chmod +x "${runningAppBundlePath}"`);
586
+
587
+ // Create/update launcher script that points to the AppImage
588
+ await createLinuxAppImageLauncherScript(runningAppBundlePath);
545
589
  } else {
546
590
  // On Windows, use versioned app folders
547
591
  const parentDir = dirname(runningAppBundlePath);
@@ -630,9 +674,8 @@ start "" launcher.exe
630
674
  await Bun.spawn(["cmd", "/c", runBatPath], { detached: true });
631
675
  break;
632
676
  case 'linux':
633
- // On Linux, use shell backgrounding to detach the process
634
- const linuxLauncher = join(runningAppBundlePath, "bin", "launcher");
635
- Bun.spawn(["sh", "-c", `${linuxLauncher} &`], { detached: true});
677
+ // On Linux, launch the AppImage directly
678
+ Bun.spawn(["sh", "-c", `"${runningAppBundlePath}" &`], { detached: true});
636
679
  break;
637
680
  }
638
681
  // Use native killApp to properly clean up all resources