electrobun 1.17.3-beta.9 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/bin/electrobun.cjs +11 -1
- package/dist/api/bun/ElectrobunConfig.ts +42 -1
- package/dist/api/bun/__tests__/ffi-contract.test.ts +105 -0
- package/dist/api/bun/core/BrowserView.ts +5 -8
- package/dist/api/bun/core/BrowserWindow.ts +34 -2
- package/dist/api/bun/core/GpuWindow.ts +30 -2
- package/dist/api/bun/core/Paths.ts +1 -1
- package/dist/api/bun/core/Updater.ts +19 -12
- package/dist/api/bun/index.ts +2 -0
- package/dist/api/bun/preload/.generated/compiled.ts +1 -1
- package/dist/api/bun/preload/webviewTag.ts +16 -2
- package/dist/api/bun/proc/native.ts +84 -24
- package/dist/api/bun/webGPU.ts +7 -32
- package/dist/api/bun/webgpuAdapter.ts +0 -5
- package/dist/api/shared/bun-version.ts +1 -1
- package/dist/api/shared/cef-version.ts +2 -2
- package/package.json +2 -2
- package/src/cli/index.ts +299 -31
package/README.md
CHANGED
|
@@ -14,16 +14,27 @@
|
|
|
14
14
|
## What is Electrobun?
|
|
15
15
|
|
|
16
16
|
Electrobun aims to be a complete **solution-in-a-box** for building, updating, and shipping ultra fast, tiny, and cross-platform desktop applications written in Typescript.
|
|
17
|
-
Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in <a href="https://ziglang.org/">zig</a>.
|
|
17
|
+
Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in Objc, C++, and several core parts written in <a href="https://ziglang.org/">zig</a>.
|
|
18
18
|
|
|
19
|
-
Visit <a href="https://
|
|
19
|
+
Visit <a href="https://docs.electrobunny.ai/electrobun/">https://docs.electrobunny.ai/electrobun/</a> to see api documentation, guides, and more.
|
|
20
|
+
|
|
21
|
+
You use it via npm.
|
|
22
|
+
|
|
23
|
+
Don't miss our:
|
|
24
|
+
- self-extracting bundles that use ZSTD compression for more compact distributables as small as 16MB
|
|
25
|
+
- zig optimized BSDIFF implementation that lets you ship tiny app updates as small as 4KB
|
|
26
|
+
- `bundleCEF` flag to bundle and pin Chromium for those that want that tradeoff of consistency over file size
|
|
27
|
+
- `bundleWGPU` that lets you use Bun Typescript -> WGPU to control a native GPU surface without a webview
|
|
28
|
+
- Our Three.js and Babylon.js adapters that work right in Bun
|
|
29
|
+
- Our `<electrobun-webview>` and `<electrobun-wpgu>` html elements that let you composit proper OOPIFs and native GPU surfaces into your UIs
|
|
30
|
+
- so much more.
|
|
20
31
|
|
|
21
32
|
**Project Goals**
|
|
22
33
|
|
|
23
34
|
- Write typescript for the main process and webviews without having to think about it.
|
|
24
35
|
- Isolation between main and webview processes with fast, typed, easy to implement RPC between them.
|
|
25
|
-
- Small self-extracting app bundles ~
|
|
26
|
-
- Even smaller app updates as small as
|
|
36
|
+
- Small self-extracting app bundles ~14MB (when using system webview, most of this is the bun runtime)
|
|
37
|
+
- Even smaller app updates as small as 4KB (using bsdiff it only downloads tiny patches between versions)
|
|
27
38
|
- Provide everything you need in one tightly integrated workflow to start writing code in 5 minutes and distribute in 10.
|
|
28
39
|
|
|
29
40
|
## Apps Built with Electrobun
|
|
@@ -42,8 +53,10 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
|
|
|
42
53
|
- [Codex Agents Composer](https://github.com/MrLesk/codex-agents-composer) - desktop app for managing your Codex agents and their skills
|
|
43
54
|
- [codex-devtools](https://github.com/gulivan/codex-devtools) - desktop inspector for Codex session data; browse conversations, search messages, and analyze agent activity
|
|
44
55
|
- [Deskdown](https://github.com/guarana-studio/deskdown) - transform any web address into a desktop app in under 20 seconds
|
|
56
|
+
- [Dictate](https://github.com/siddhantparadox/dictate) - Windows dictation app with local and BYOK cloud transcription
|
|
45
57
|
- [dev-3.0](https://github.com/h0x91b/dev-3.0) - helps you not get lost while managing multiple AI agents across projects
|
|
46
58
|
- [DOOM](https://github.com/blackboardsh/electrobun-doom) - DOOM implemented in 2 ways: bun -> (c doom -> bundled wgpu) and (full ts port bun -> bundled wgpu)
|
|
59
|
+
- [dotlock](https://github.com/tsconfigdotjson/dotlock) - macOS desktop app for managing `.env` files across your projects
|
|
47
60
|
- [electrobun-pdf](https://github.com/GijungKim/electrobun-pdf) - local-first PDF & DOCX editor for opening, annotating, and exporting documents without leaving your machine
|
|
48
61
|
- [electrobun-rms](https://github.com/khanhthanhdev/electrobun-rms) - fast Electrobun desktop app template with React, Tailwind CSS, and Vite
|
|
49
62
|
- [golb](https://github.com/chrisdadev13/golb) - desktop AI coding workspace built with React, Vite, and Tailwind
|
|
@@ -55,6 +68,7 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
|
|
|
55
68
|
- [md-browse](https://github.com/needle-tools/md-browse) - a markdown-first browser that converts web pages to clean markdown
|
|
56
69
|
- [Patchline](https://github.com/adwaithks/Patchline) - lightweight desktop Git client for reading patches and line diffs, then staging and committing changes
|
|
57
70
|
- [peekachu](https://github.com/needle-tools/peekachu) - password manager for AIs; store secrets in your OS keychain and scrub output so AI assistants never see actual values
|
|
71
|
+
- [PiBun](https://github.com/khairold/pibun) - desktop GUI for the Pi coding agent with chat, terminal, git integration, and plugin system
|
|
58
72
|
- [PLEXI](https://github.com/ianjamesburke/PLEXI) - a multi-dimensional terminal multiplexer for the agentic era
|
|
59
73
|
- [Prometheus](https://github.com/opensourcectl/prometheus) - desktop utility toolbox for file cleanup, document manipulation, and image processing
|
|
60
74
|
- [Quiver](https://ataraxy-labs.github.io/quiver/) - desktop app for GitHub PR reviews, merge conflict resolution, and AI commit messages
|
|
@@ -67,6 +81,7 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
|
|
|
67
81
|
- [VibesOS](https://github.com/popmechanic/VibesOS) - A GUI for Claude Code that makes it easy to vibe code simple, un-hackable apps
|
|
68
82
|
- [VoiceVault](https://github.com/PJH720/VoiceVault) - AI-powered voice recorder with transcription, summarization, and RAG search
|
|
69
83
|
- [warren](https://github.com/Loa212/warren) - open-source, peer-to-peer terminal mesh for accessing your machines from any device without SSH keys or config files
|
|
84
|
+
- [whatsapp-reminder](https://github.com/FatahChan/whatsapp-reminder) - managed scheduled WhatsApp messages
|
|
70
85
|
|
|
71
86
|
### Video Demos
|
|
72
87
|
|
|
@@ -81,8 +96,11 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
|
|
|
81
96
|
[](https://www.star-history.com/#blackboardsh/electrobun&type=date&legend=top-left)
|
|
82
97
|
|
|
83
98
|
## Contributing
|
|
99
|
+
Electrobun is one piece of a vision I'm building. I'm optimizing for focus and execution. Issues and PRs can be used to share ideas, but there should be no expectation that I will review, respond to, or merge them.
|
|
100
|
+
|
|
84
101
|
Ways to get involved:
|
|
85
102
|
|
|
103
|
+
- Read the [Contribution guidelines](./CONTRIBUTING.md)
|
|
86
104
|
- Follow us on X for updates <a href="https://twitter.com/BlackboardTech">@BlackboardTech</a> and <a href="https://twitter.com/YoavCodes">@YoavCodes</a> or on bluesky <a href="https://bsky.app/profile/yoav.codes">@yoav.codes</a>
|
|
87
105
|
- Join the conversation on <a href="https://discord.gg/ueKE4tjaCE">Discord</a>
|
|
88
106
|
- Create and participate in Github issues and discussions
|
|
@@ -155,3 +173,4 @@ All commands are run from the `/package` directory:
|
|
|
155
173
|
| Windows 11+ | Official |
|
|
156
174
|
| Ubuntu 22.04+ | Official |
|
|
157
175
|
| Other Linux distros (gtk3, webkit2gtk-4.1) | Community |
|
|
176
|
+
| Raspberry Pi | Unofficial fork: [kortexa-ai/electrobun (linux-wpe)](https://github.com/kortexa-ai/electrobun/tree/kortexa/linux-wpe) — follow the author [@francip](https://x.com/francip/status/2050149256053539059?s=20) |
|
package/bin/electrobun.cjs
CHANGED
|
@@ -32,6 +32,16 @@ const platform = getPlatform();
|
|
|
32
32
|
const arch = platform === 'win' ? 'x64' : getArch();
|
|
33
33
|
const binExt = platform === 'win' ? '.exe' : '';
|
|
34
34
|
|
|
35
|
+
function getTarCommand() {
|
|
36
|
+
if (platform !== 'win') {
|
|
37
|
+
return 'tar';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Git Bash tar can treat C:\... as a remote path. Force the built-in Windows tar.
|
|
41
|
+
const systemTar = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe');
|
|
42
|
+
return existsSync(systemTar) ? `"${systemTar}"` : 'tar';
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// Paths
|
|
36
46
|
const electrobunDir = join(__dirname, '..');
|
|
37
47
|
const cacheDir = join(electrobunDir, '.cache');
|
|
@@ -98,7 +108,7 @@ async function ensureCliBinary() {
|
|
|
98
108
|
await downloadFile(tarballUrl, tarballPath);
|
|
99
109
|
|
|
100
110
|
// Extract using system tar (available on macOS, Linux, and Windows 10+)
|
|
101
|
-
execSync(
|
|
111
|
+
execSync(`${getTarCommand()} -xzf "${tarballPath}"`, { cwd: cacheDir, stdio: 'pipe' });
|
|
102
112
|
|
|
103
113
|
// Clean up tarball
|
|
104
114
|
unlinkSync(tarballPath);
|
|
@@ -56,6 +56,39 @@ export interface ElectrobunConfig {
|
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
58
|
urlSchemes?: string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* File type associations for the application.
|
|
62
|
+
* Registers document types so the OS can open files with your app
|
|
63
|
+
* (e.g., double-click in Finder, "Open With" menu, drag-to-dock).
|
|
64
|
+
*
|
|
65
|
+
* Platform support:
|
|
66
|
+
* - macOS: Fully supported. Generates CFBundleDocumentTypes in Info.plist.
|
|
67
|
+
* - Windows/Linux: Not yet supported.
|
|
68
|
+
*
|
|
69
|
+
* Files arrive as file:// URLs via the existing "open-url" event:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* Electrobun.events.on("open-url", (e) => {
|
|
72
|
+
* if (e.data.url.startsWith("file://")) {
|
|
73
|
+
* console.log("Opened file:", e.data.url);
|
|
74
|
+
* }
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
fileAssociations?: Array<{
|
|
79
|
+
/** File extensions without the leading dot (e.g., ["dotlock", "json"]) */
|
|
80
|
+
ext: string[];
|
|
81
|
+
/** Human-readable name for this file type (e.g., "DotLock Document") */
|
|
82
|
+
name: string;
|
|
83
|
+
/** The app's role for this file type. @default "Viewer" */
|
|
84
|
+
role?: "Editor" | "Viewer" | "Shell" | "None";
|
|
85
|
+
/**
|
|
86
|
+
* Path to an .icns file for this document type (macOS only).
|
|
87
|
+
* The file is automatically copied into the app bundle's Resources folder
|
|
88
|
+
* during the build. Only the filename (without path) is written to Info.plist.
|
|
89
|
+
*/
|
|
90
|
+
icon?: string;
|
|
91
|
+
}>;
|
|
59
92
|
};
|
|
60
93
|
|
|
61
94
|
/**
|
|
@@ -278,7 +311,15 @@ export interface ElectrobunConfig {
|
|
|
278
311
|
entitlements?: Record<string, boolean | string | string[]>;
|
|
279
312
|
|
|
280
313
|
/**
|
|
281
|
-
* Path to .iconset folder
|
|
314
|
+
* Path to .iconset folder or .icon file (from Icon Composer)
|
|
315
|
+
* containing app icons.
|
|
316
|
+
*
|
|
317
|
+
* - `.iconset` folders are converted to .icns via iconutil
|
|
318
|
+
* (requires Command Line Tools)
|
|
319
|
+
* - `.icon` files are compiled via actool, producing Assets.car
|
|
320
|
+
* for Liquid Glass on macOS 26+ and a .icns fallback for older
|
|
321
|
+
* macOS versions (requires Xcode)
|
|
322
|
+
*
|
|
282
323
|
* @default "icon.iconset"
|
|
283
324
|
*/
|
|
284
325
|
icons?: string;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Contract tests for the bun:ffi APIs that electrobun depends on.
|
|
2
|
+
//
|
|
3
|
+
// Bun bumps don't usually break electrobun, but when they do, the breakage
|
|
4
|
+
// almost always lives in this surface — JSCallback marshaling, FFIType
|
|
5
|
+
// encoding, dlopen behavior. These tests are a tripwire: if a new Bun release
|
|
6
|
+
// breaks any of them, we want to know before cutting an electrobun release,
|
|
7
|
+
// not after a user reports a crash.
|
|
8
|
+
//
|
|
9
|
+
// Skipped on Windows for now since the bun-check workflow runs on Linux and
|
|
10
|
+
// the system library paths differ. If we add a Windows runner later, switch
|
|
11
|
+
// the libc path resolution to include msvcrt/ucrtbase.
|
|
12
|
+
|
|
13
|
+
import { describe, expect, it } from "bun:test";
|
|
14
|
+
import {
|
|
15
|
+
CString,
|
|
16
|
+
FFIType,
|
|
17
|
+
JSCallback,
|
|
18
|
+
dlopen,
|
|
19
|
+
ptr,
|
|
20
|
+
toArrayBuffer,
|
|
21
|
+
type Pointer,
|
|
22
|
+
} from "bun:ffi";
|
|
23
|
+
|
|
24
|
+
const isUnix =
|
|
25
|
+
process.platform === "darwin" || process.platform === "linux";
|
|
26
|
+
|
|
27
|
+
const libcPath =
|
|
28
|
+
process.platform === "darwin" ? "libSystem.B.dylib" : "libc.so.6";
|
|
29
|
+
|
|
30
|
+
(isUnix ? describe : describe.skip)(
|
|
31
|
+
"bun:ffi contract used by electrobun",
|
|
32
|
+
() => {
|
|
33
|
+
it("dlopen + FFIType.cstring + FFIType.u64 (strlen)", () => {
|
|
34
|
+
const lib = dlopen(libcPath, {
|
|
35
|
+
strlen: {
|
|
36
|
+
args: [FFIType.cstring],
|
|
37
|
+
returns: FFIType.u64,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const len = lib.symbols.strlen(
|
|
42
|
+
new TextEncoder().encode("hello\0"),
|
|
43
|
+
);
|
|
44
|
+
expect(Number(len)).toBe(5);
|
|
45
|
+
|
|
46
|
+
lib.close();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("ptr + toArrayBuffer round-trip", () => {
|
|
50
|
+
const src = new Uint8Array([1, 2, 3, 4, 5]);
|
|
51
|
+
const back = new Uint8Array(
|
|
52
|
+
toArrayBuffer(ptr(src), 0, src.byteLength),
|
|
53
|
+
);
|
|
54
|
+
expect(Array.from(back)).toEqual([1, 2, 3, 4, 5]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("CString reads null-terminated bytes", () => {
|
|
58
|
+
const buf = new Uint8Array([72, 105, 33, 0]); // "Hi!\0"
|
|
59
|
+
const s = new CString(ptr(buf));
|
|
60
|
+
expect(s.toString()).toBe("Hi!");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("JSCallback: native invokes JS via function pointer (qsort)", () => {
|
|
64
|
+
const lib = dlopen(libcPath, {
|
|
65
|
+
qsort: {
|
|
66
|
+
args: [
|
|
67
|
+
FFIType.ptr,
|
|
68
|
+
FFIType.u64,
|
|
69
|
+
FFIType.u64,
|
|
70
|
+
FFIType.function,
|
|
71
|
+
],
|
|
72
|
+
returns: FFIType.void,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let comparisonCount = 0;
|
|
77
|
+
const compare = new JSCallback(
|
|
78
|
+
(aPtr: Pointer, bPtr: Pointer) => {
|
|
79
|
+
comparisonCount++;
|
|
80
|
+
const a = new Int32Array(toArrayBuffer(aPtr, 0, 4))[0]!;
|
|
81
|
+
const b = new Int32Array(toArrayBuffer(bPtr, 0, 4))[0]!;
|
|
82
|
+
return a - b;
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
86
|
+
returns: FFIType.i32,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const arr = new Int32Array([5, 2, 8, 1, 3]);
|
|
91
|
+
lib.symbols.qsort(
|
|
92
|
+
ptr(arr),
|
|
93
|
+
BigInt(arr.length),
|
|
94
|
+
4n,
|
|
95
|
+
compare.ptr,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(Array.from(arr)).toEqual([1, 2, 3, 5, 8]);
|
|
99
|
+
expect(comparisonCount).toBeGreaterThan(0);
|
|
100
|
+
|
|
101
|
+
compare.close();
|
|
102
|
+
lib.close();
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
);
|
|
@@ -74,7 +74,7 @@ const randomId = Math.random().toString(36).substring(7);
|
|
|
74
74
|
|
|
75
75
|
export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
76
76
|
id: number = nextWebviewId++;
|
|
77
|
-
ptr
|
|
77
|
+
ptr: Pointer | null = null;
|
|
78
78
|
hostWebviewId?: number;
|
|
79
79
|
windowId!: number;
|
|
80
80
|
renderer!: "cef" | "native";
|
|
@@ -223,17 +223,12 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
loadURL(url: string) {
|
|
226
|
-
console.log(`DEBUG: loadURL called for webview ${this.id}: ${url}`);
|
|
227
226
|
this.url = url;
|
|
228
227
|
native!.symbols.loadURLInWebView(this.ptr, toCString(this.url));
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
loadHTML(html: string) {
|
|
232
231
|
this.html = html;
|
|
233
|
-
console.log(
|
|
234
|
-
`DEBUG: Setting HTML content for webview ${this.id}:`,
|
|
235
|
-
html.substring(0, 50) + "...",
|
|
236
|
-
);
|
|
237
232
|
|
|
238
233
|
if (this.renderer === "cef") {
|
|
239
234
|
// For CEF, store HTML content in native map and use scheme handler
|
|
@@ -361,8 +356,10 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
361
356
|
unregisterHandler() {},
|
|
362
357
|
});
|
|
363
358
|
this.rpcHandler = undefined;
|
|
364
|
-
|
|
365
|
-
|
|
359
|
+
|
|
360
|
+
this.rpcHandler = undefined;
|
|
361
|
+
this.ptr = null;
|
|
362
|
+
native!.symbols.webviewRemove(ptr);
|
|
366
363
|
}
|
|
367
364
|
|
|
368
365
|
static getById(id: number) {
|
|
@@ -12,6 +12,11 @@ import { WGPUView } from "./WGPUView";
|
|
|
12
12
|
const buildConfig = await BuildConfig.get();
|
|
13
13
|
|
|
14
14
|
export type WindowOptionsType<T = undefined> = {
|
|
15
|
+
trafficLightOffset?: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
activate?: boolean;
|
|
15
20
|
title: string;
|
|
16
21
|
frame: {
|
|
17
22
|
x: number;
|
|
@@ -123,6 +128,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
123
128
|
transparent: boolean = false;
|
|
124
129
|
passthrough: boolean = false;
|
|
125
130
|
hidden: boolean = false;
|
|
131
|
+
trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
|
|
126
132
|
navigationRules: string | null = null;
|
|
127
133
|
// Sandbox mode disables RPC and only allows event emission (for untrusted content)
|
|
128
134
|
sandbox: boolean = false;
|
|
@@ -153,6 +159,10 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
153
159
|
this.transparent = options.transparent ?? false;
|
|
154
160
|
this.passthrough = options.passthrough ?? false;
|
|
155
161
|
this.hidden = options.hidden ?? false;
|
|
162
|
+
this.trafficLightOffset = {
|
|
163
|
+
x: options.trafficLightOffset?.x ?? 0,
|
|
164
|
+
y: options.trafficLightOffset?.y ?? 0,
|
|
165
|
+
};
|
|
156
166
|
this.navigationRules = options.navigationRules || null;
|
|
157
167
|
this.sandbox = options.sandbox ?? false;
|
|
158
168
|
|
|
@@ -165,6 +175,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
165
175
|
titleBarStyle,
|
|
166
176
|
transparent,
|
|
167
177
|
hidden,
|
|
178
|
+
activate,
|
|
168
179
|
}: Partial<WindowOptionsType<T>>) {
|
|
169
180
|
this.ptr = ffi.request.createWindow({
|
|
170
181
|
id: this.id,
|
|
@@ -208,6 +219,8 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
208
219
|
titleBarStyle: titleBarStyle || "default",
|
|
209
220
|
transparent: transparent ?? false,
|
|
210
221
|
hidden: hidden ?? false,
|
|
222
|
+
activate: activate ?? true,
|
|
223
|
+
trafficLightOffset: this.trafficLightOffset,
|
|
211
224
|
}) as Pointer;
|
|
212
225
|
|
|
213
226
|
BrowserWindowMap[this.id] = this;
|
|
@@ -263,12 +276,27 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
263
276
|
return ffi.request.closeWindow({ winId: this.id });
|
|
264
277
|
}
|
|
265
278
|
|
|
279
|
+
activate() {
|
|
280
|
+
return ffi.request.activateWindow({ winId: this.id });
|
|
281
|
+
}
|
|
282
|
+
|
|
266
283
|
focus() {
|
|
267
|
-
|
|
284
|
+
console.log(
|
|
285
|
+
"[electrobun] BrowserWindow.focus() is deprecated. Use window.activate() instead.",
|
|
286
|
+
);
|
|
287
|
+
return this.activate();
|
|
268
288
|
}
|
|
269
289
|
|
|
270
290
|
show() {
|
|
271
|
-
return ffi.request.
|
|
291
|
+
return ffi.request.showWindow({ winId: this.id, activate: true });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
showInactive() {
|
|
295
|
+
return ffi.request.showWindow({ winId: this.id, activate: false });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
hide() {
|
|
299
|
+
return ffi.request.hideWindow({ winId: this.id });
|
|
272
300
|
}
|
|
273
301
|
|
|
274
302
|
minimize() {
|
|
@@ -325,6 +353,10 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
325
353
|
return ffi.request.setWindowPosition({ winId: this.id, x, y });
|
|
326
354
|
}
|
|
327
355
|
|
|
356
|
+
setWindowButtonPosition(x: number, y: number) {
|
|
357
|
+
return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
|
|
358
|
+
}
|
|
359
|
+
|
|
328
360
|
setSize(width: number, height: number) {
|
|
329
361
|
this.frame.width = width;
|
|
330
362
|
this.frame.height = height;
|
|
@@ -6,6 +6,11 @@ import { getNextWindowId } from "./windowIds";
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
export type GpuWindowOptionsType = {
|
|
9
|
+
trafficLightOffset?: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
};
|
|
13
|
+
activate?: boolean;
|
|
9
14
|
title: string;
|
|
10
15
|
frame: {
|
|
11
16
|
x: number;
|
|
@@ -54,6 +59,7 @@ export class GpuWindow {
|
|
|
54
59
|
title: string = "Electrobun";
|
|
55
60
|
state: "creating" | "created" = "creating";
|
|
56
61
|
transparent: boolean = false;
|
|
62
|
+
trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
|
|
57
63
|
frame: {
|
|
58
64
|
x: number;
|
|
59
65
|
y: number;
|
|
@@ -73,6 +79,10 @@ export class GpuWindow {
|
|
|
73
79
|
? { ...defaultOptions.frame, ...options.frame }
|
|
74
80
|
: { ...defaultOptions.frame };
|
|
75
81
|
this.transparent = options.transparent ?? false;
|
|
82
|
+
this.trafficLightOffset = {
|
|
83
|
+
x: options.trafficLightOffset?.x ?? 0,
|
|
84
|
+
y: options.trafficLightOffset?.y ?? 0,
|
|
85
|
+
};
|
|
76
86
|
|
|
77
87
|
this.init(options);
|
|
78
88
|
}
|
|
@@ -81,6 +91,7 @@ export class GpuWindow {
|
|
|
81
91
|
styleMask,
|
|
82
92
|
titleBarStyle,
|
|
83
93
|
transparent,
|
|
94
|
+
activate,
|
|
84
95
|
}: Partial<GpuWindowOptionsType>) {
|
|
85
96
|
this.ptr = ffi.request.createWindow({
|
|
86
97
|
id: this.id,
|
|
@@ -123,6 +134,8 @@ export class GpuWindow {
|
|
|
123
134
|
},
|
|
124
135
|
titleBarStyle: titleBarStyle || "default",
|
|
125
136
|
transparent: transparent ?? false,
|
|
137
|
+
activate: activate ?? true,
|
|
138
|
+
trafficLightOffset: this.trafficLightOffset,
|
|
126
139
|
}) as Pointer;
|
|
127
140
|
|
|
128
141
|
GpuWindowMap[this.id] = this;
|
|
@@ -160,12 +173,23 @@ export class GpuWindow {
|
|
|
160
173
|
return ffi.request.closeWindow({ winId: this.id });
|
|
161
174
|
}
|
|
162
175
|
|
|
176
|
+
activate() {
|
|
177
|
+
return ffi.request.activateWindow({ winId: this.id });
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
focus() {
|
|
164
|
-
|
|
181
|
+
console.log(
|
|
182
|
+
"[electrobun] GpuWindow.focus() is deprecated. Use window.activate() instead.",
|
|
183
|
+
);
|
|
184
|
+
return this.activate();
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
show() {
|
|
168
|
-
return ffi.request.
|
|
188
|
+
return ffi.request.showWindow({ winId: this.id, activate: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
showInactive() {
|
|
192
|
+
return ffi.request.showWindow({ winId: this.id, activate: false });
|
|
169
193
|
}
|
|
170
194
|
|
|
171
195
|
minimize() {
|
|
@@ -214,6 +238,10 @@ export class GpuWindow {
|
|
|
214
238
|
return ffi.request.setWindowPosition({ winId: this.id, x, y });
|
|
215
239
|
}
|
|
216
240
|
|
|
241
|
+
setWindowButtonPosition(x: number, y: number) {
|
|
242
|
+
return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
|
|
243
|
+
}
|
|
244
|
+
|
|
217
245
|
setSize(width: number, height: number) {
|
|
218
246
|
this.frame.width = width;
|
|
219
247
|
this.frame.height = height;
|
|
@@ -179,7 +179,7 @@ const Updater = {
|
|
|
179
179
|
// todo: allow switching channels, by default will check the current channel
|
|
180
180
|
checkForUpdate: async () => {
|
|
181
181
|
emitStatus("checking", "Checking for updates...");
|
|
182
|
-
const localInfo = await Updater.
|
|
182
|
+
const localInfo = await Updater.getLocalInfo();
|
|
183
183
|
|
|
184
184
|
if (localInfo.channel === "dev") {
|
|
185
185
|
emitStatus("no-update", "Dev channel - updates disabled", {
|
|
@@ -283,7 +283,7 @@ const Updater = {
|
|
|
283
283
|
await Updater.channelBucketUrl(); // Ensure localInfo is loaded
|
|
284
284
|
const appFileName = localInfo.name;
|
|
285
285
|
|
|
286
|
-
let currentHash = (await Updater.
|
|
286
|
+
let currentHash = (await Updater.getLocalInfo()).hash;
|
|
287
287
|
let latestHash = (await Updater.checkForUpdate()).hash;
|
|
288
288
|
|
|
289
289
|
const extractionFolder = join(appDataFolder, "self-extraction");
|
|
@@ -1074,14 +1074,14 @@ del "%~f0"
|
|
|
1074
1074
|
},
|
|
1075
1075
|
|
|
1076
1076
|
channelBucketUrl: async () => {
|
|
1077
|
-
await Updater.
|
|
1077
|
+
await Updater.getLocalInfo();
|
|
1078
1078
|
// With flat prefix-based naming, channelBucketUrl is just the baseUrl
|
|
1079
1079
|
// Users can also use Updater.localInfo.baseUrl() directly
|
|
1080
1080
|
return localInfo.baseUrl;
|
|
1081
1081
|
},
|
|
1082
1082
|
|
|
1083
1083
|
appDataFolder: async () => {
|
|
1084
|
-
await Updater.
|
|
1084
|
+
await Updater.getLocalInfo();
|
|
1085
1085
|
// Use identifier + channel for the app data folder
|
|
1086
1086
|
// e.g., ~/Library/Application Support/sh.blackboard.myapp/canary/
|
|
1087
1087
|
const appDataFolder = join(
|
|
@@ -1096,20 +1096,20 @@ del "%~f0"
|
|
|
1096
1096
|
// TODO: consider moving this from "Updater.localInfo" to "BuildVars"
|
|
1097
1097
|
localInfo: {
|
|
1098
1098
|
version: async () => {
|
|
1099
|
-
return (await Updater.
|
|
1099
|
+
return (await Updater.getLocalInfo()).version;
|
|
1100
1100
|
},
|
|
1101
1101
|
hash: async () => {
|
|
1102
|
-
return (await Updater.
|
|
1102
|
+
return (await Updater.getLocalInfo()).hash;
|
|
1103
1103
|
},
|
|
1104
1104
|
channel: async () => {
|
|
1105
|
-
return (await Updater.
|
|
1105
|
+
return (await Updater.getLocalInfo()).channel;
|
|
1106
1106
|
},
|
|
1107
1107
|
baseUrl: async () => {
|
|
1108
|
-
return (await Updater.
|
|
1108
|
+
return (await Updater.getLocalInfo()).baseUrl;
|
|
1109
1109
|
},
|
|
1110
1110
|
},
|
|
1111
1111
|
|
|
1112
|
-
|
|
1112
|
+
getLocalInfo: async () => {
|
|
1113
1113
|
if (localInfo) {
|
|
1114
1114
|
return localInfo;
|
|
1115
1115
|
}
|
|
@@ -1122,8 +1122,15 @@ del "%~f0"
|
|
|
1122
1122
|
console.error("Failed to read version.json", error);
|
|
1123
1123
|
localInfo = { identifier: "", channel: "", version: "", hash: "", baseUrl: "", name: "" };
|
|
1124
1124
|
return localInfo;
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
getLocallocalInfo: async () => {
|
|
1128
|
+
console.error(
|
|
1129
|
+
"[Electrobun] Updater.getLocallocalInfo() is deprecated. Use Updater.getLocalInfo() instead.",
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
return Updater.getLocalInfo();
|
|
1133
|
+
},
|
|
1134
|
+
};
|
|
1128
1135
|
|
|
1129
1136
|
export { Updater };
|
package/dist/api/bun/index.ts
CHANGED
|
@@ -229,6 +229,7 @@ export {
|
|
|
229
229
|
Screen,
|
|
230
230
|
Session,
|
|
231
231
|
WGPUBridge,
|
|
232
|
+
|
|
232
233
|
BuildConfig,
|
|
233
234
|
};
|
|
234
235
|
|
|
@@ -247,6 +248,7 @@ const Electrobun = {
|
|
|
247
248
|
Screen,
|
|
248
249
|
Session,
|
|
249
250
|
WGPUBridge,
|
|
251
|
+
|
|
250
252
|
BuildConfig,
|
|
251
253
|
events: electobunEventEmmitter,
|
|
252
254
|
PATHS,
|