@zakim24/electron-liquid-glass 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Meridius Labs
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the β€œSoftware”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED β€œAS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # electron-liquid-glass
2
+
3
+ <div align="center">
4
+
5
+ <img width="387" alt="image" src="https://github.com/user-attachments/assets/3c3c9ea6-2663-4292-b812-a630c2c3f65b" />
6
+
7
+ ![npm](https://img.shields.io/npm/v/electron-liquid-glass)
8
+ ![npm downloads](https://img.shields.io/npm/dm/electron-liquid-glass)
9
+ ![GitHub](https://img.shields.io/github/license/meridius-labs/electron-liquid-glass)
10
+ ![Platform](https://img.shields.io/badge/platform-macOS-blue)
11
+ ![Node](https://img.shields.io/node/v/electron-liquid-glass)
12
+
13
+ **Modern macOS glass effects for Electron applications**
14
+
15
+ _πŸͺ„ NATIVE `NSGlassEffectView` integration with ZERO CSS hacks_
16
+
17
+ [Installation](#-installation) β€’ [Quick Start](#-quick-start) β€’ [API](#-api-reference) β€’ [Examples](examples/) β€’ [Contributing](#-contributing)
18
+
19
+ </div>
20
+
21
+ ---
22
+
23
+ ## ✨ Features
24
+
25
+ - πŸͺŸ **Native Glass Effects** - Real `NSGlassEffectView` integration, not CSS approximations
26
+ - ⚑ **Zero Configuration** - Works out of the box with any Electron app
27
+ - 🎨 **Fully Customizable** - Corner radius, tint colors, and glass variants
28
+ - πŸ“¦ **Modern Package** - Dual ESM/CommonJS support with TypeScript declarations
29
+ - πŸ”§ **Pre-built Binaries** - No compilation required for standard setups
30
+ - πŸŒ™ **Auto Dark Mode** - Automatically adapts to system appearance changes
31
+
32
+ ## πŸš€ Installation
33
+
34
+ ```bash
35
+ # npm
36
+ npm install electron-liquid-glass
37
+
38
+ # yarn
39
+ yarn add electron-liquid-glass
40
+
41
+ # pnpm
42
+ pnpm add electron-liquid-glass
43
+
44
+ # bun
45
+ bun add electron-liquid-glass
46
+ ```
47
+
48
+ ### Requirements
49
+
50
+ - **macOS 26+** (Tahoe or later)
51
+ - **Electron 30+**
52
+ - **Node.js 22+**
53
+
54
+ > **Note**: This package only works on macOS. On other platforms, it provides safe no-op fallbacks.
55
+
56
+ ## 🎯 Quick Start
57
+
58
+ ### Basic Usage
59
+
60
+ ```javascript
61
+ import { app, BrowserWindow } from "electron";
62
+ import liquidGlass from "electron-liquid-glass";
63
+
64
+ app.whenReady().then(() => {
65
+ const win = new BrowserWindow({
66
+ width: 800,
67
+ height: 600,
68
+
69
+ vibrancy: false, // <-- ❌❌❌ do NOT set vibrancy alongside with liquid glass, it will override and look blurry
70
+
71
+ transparent: true, // <-- This MUST be true
72
+ });
73
+
74
+ win.setWindowButtonVisibility(true); // <-- βœ… This is required to show the window buttons
75
+
76
+ win.loadFile("index.html");
77
+
78
+ /**
79
+ * πŸͺ„ Apply glass effect after content loads πŸͺ„
80
+ */
81
+ win.webContents.once("did-finish-load", () => {
82
+ // πŸͺ„ Apply effect, get handle
83
+ const glassId = liquidGlass.addView(win.getNativeWindowHandle(), {
84
+ /* options */
85
+ });
86
+
87
+ // Experimental, undocumented private APIs
88
+ liquidGlass.unstable_setVariant(glassId, 2);
89
+ });
90
+ });
91
+ ```
92
+
93
+ ### TypeScript Usage
94
+
95
+ ```typescript
96
+ import { BrowserWindow } from "electron";
97
+ import liquidGlass, { GlassOptions } from "electron-liquid-glass";
98
+
99
+ const options: GlassOptions = {
100
+ cornerRadius: 16, // (optional)
101
+ tintColor: "#44000010", // black tint (optional)
102
+ opaque: true, // add opaque background behind glass (optional)
103
+ };
104
+
105
+ liquidGlass.addView(window.getNativeWindowHandle(), options);
106
+ ```
107
+
108
+ ## πŸ“š API Reference
109
+
110
+ ### `liquidGlass.addView(handle, options?)`
111
+
112
+ Applies a glass effect to an Electron window.
113
+
114
+ **Parameters:**
115
+
116
+ - `handle: Buffer` - The native window handle from `BrowserWindow.getNativeWindowHandle()`
117
+ - `options?: GlassOptions` - Configuration options
118
+
119
+ **Returns:** `number` - A unique view ID for future operations
120
+
121
+ ### `GlassOptions`
122
+
123
+ ```typescript
124
+ interface GlassOptions {
125
+ cornerRadius?: number; // Corner radius in pixels (default: 0)
126
+ tintColor?: string; // Hex color with optional alpha (#RRGGBB or #RRGGBBAA)
127
+ opaque?: boolean; // Add opaque background behind glass (default: false)
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ### UNDOCUMENTED EXPERIMENTAL METHODS
134
+
135
+ > ⚠️ **Warning**: DO NOT USE IN PROD. These methods use private macOS APIs and may change in future versions.
136
+
137
+ ```typescript
138
+ // Glass variants (number) (0-15, 19 are functional)
139
+ liquidGlass.unstable_setVariant(glassId, 2);
140
+
141
+ // Scrim overlay (0 = off, 1 = on)
142
+ liquidGlass.unstable_setScrim(glassId, 1);
143
+
144
+ // Subdued state (0 = normal, 1 = subdued)
145
+ liquidGlass.unstable_setSubdued(glassId, 1);
146
+ ```
147
+
148
+ ## πŸ”§ Development
149
+
150
+ ### Building from Source
151
+
152
+ ```bash
153
+ # Clone the repository
154
+ git clone https://github.com/meridius-labs/electron-liquid-glass.git
155
+ cd electron-liquid-glass
156
+
157
+ # Install dependencies
158
+ bun install
159
+
160
+ # Build native module
161
+ bun run build:native
162
+
163
+ # Build TypeScript library
164
+ bun run build
165
+
166
+ # Build everything
167
+ bun run build:all
168
+ ```
169
+
170
+ ### Rebuilding for Custom Electron
171
+
172
+ If you're using a custom Electron version:
173
+
174
+ ```bash
175
+ npx electron-rebuild -f -w electron-liquid-glass
176
+ ```
177
+
178
+ ### Project Structure
179
+
180
+ ```
181
+ electron-liquid-glass/
182
+ β”œβ”€β”€ src/ # Native C++ source code
183
+ β”‚ β”œβ”€β”€ glass_effect.mm # Objective-C++ implementation
184
+ β”‚ └── liquidglass.cc # Node.js addon bindings
185
+ β”œβ”€β”€ js/ # TypeScript source
186
+ β”‚ β”œβ”€β”€ index.ts # Main library code
187
+ β”‚ └── native-loader.ts # Native module loader
188
+ β”œβ”€β”€ dist/ # Built library (generated)
189
+ β”œβ”€β”€ examples/ # Example applications
190
+ └── prebuilds/ # Pre-built binaries
191
+ ```
192
+
193
+ ## πŸ—οΈ How It Works
194
+
195
+ 1. **Native Integration**: Uses Objective-C++ to create `NSGlassEffectView` instances
196
+ 2. **View Hierarchy**: Inserts glass views behind your web content, not over it
197
+ 3. **Automatic Updates**: Listens for system appearance changes to keep effects in sync
198
+ 4. **Memory Management**: Properly manages native view lifecycle
199
+
200
+ ### Technical Details
201
+
202
+ - **Primary**: Uses `NSGlassEffectView` API when available
203
+ - **Fallback**: Falls back to public `NSVisualEffectView` on older systems
204
+ - **Performance**: Minimal overhead, native rendering performance
205
+ - **Compatibility**: Works with all Electron window configurations
206
+
207
+ ## 🀝 Contributing
208
+
209
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
210
+
211
+ ### Development Setup
212
+
213
+ 1. Fork the repository
214
+ 2. Create a feature branch: `git checkout -b feature/amazing-feature`
215
+ 3. Make your changes and test thoroughly
216
+ 4. Commit with conventional commits: `git commit -m "feat: add amazing feature"`
217
+ 5. Push and create a Pull Request
218
+
219
+ ### Reporting Issues
220
+
221
+ - Use the [issue tracker](https://github.com/meridius-labs/electron-liquid-glass/issues)
222
+ - Include your macOS version, Electron version, and Node.js version
223
+ - Provide a minimal reproduction case when possible
224
+
225
+ ## πŸ“‹ Roadmap
226
+
227
+ - [ ] **View Management** - Remove and update existing glass views
228
+
229
+ ## πŸ™ Acknowledgments
230
+
231
+ - Apple's private `NSGlassEffectView` API documentation (reverse-engineered)
232
+ - The Electron team for excellent native integration capabilities
233
+ - Contributors and users who help improve this library
234
+
235
+ ## πŸ“„ License
236
+
237
+ MIT Β© [Meridius Labs](https://github.com/meridius-labs) 2025
238
+
239
+ ---
240
+
241
+ <div align="center">
242
+
243
+ **Made with ❀️ for the Electron community**
244
+
245
+ [⭐ Star on GitHub](https://github.com/meridius-labs/electron-liquid-glass) β€’ [πŸ› Report Bug](https://github.com/meridius-labs/electron-liquid-glass/issues) β€’ [πŸ’‘ Request Feature](https://github.com/meridius-labs/electron-liquid-glass/issues)
246
+
247
+ </div>
Binary file
package/dist/index.cjs ADDED
@@ -0,0 +1,129 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let events = require("events");
25
+ events = __toESM(events);
26
+ let child_process = require("child_process");
27
+ child_process = __toESM(child_process);
28
+ let url = require("url");
29
+ url = __toESM(url);
30
+ let path = require("path");
31
+ path = __toESM(path);
32
+
33
+ //#region js/variants.ts
34
+ const GlassMaterialVariant = {
35
+ regular: 0,
36
+ clear: 1,
37
+ dock: 2,
38
+ appIcons: 3,
39
+ widgets: 4,
40
+ text: 5,
41
+ avplayer: 6,
42
+ facetime: 7,
43
+ controlCenter: 8,
44
+ notificationCenter: 9,
45
+ monogram: 10,
46
+ bubbles: 11,
47
+ identity: 12,
48
+ focusBorder: 13,
49
+ focusPlatter: 14,
50
+ keyboard: 15,
51
+ sidebar: 16,
52
+ abuttedSidebar: 17,
53
+ inspector: 18,
54
+ control: 19,
55
+ loupe: 20,
56
+ slider: 21,
57
+ camera: 22,
58
+ cartouchePopover: 23
59
+ };
60
+
61
+ //#endregion
62
+ //#region js/native-loader.ts
63
+ const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
64
+ const __dirname$1 = (0, path.dirname)(__filename$1);
65
+ const nodeGypBuild = require("node-gyp-build");
66
+ var native_loader_default = nodeGypBuild((0, path.join)(__dirname$1, ".."));
67
+
68
+ //#endregion
69
+ //#region js/index.ts
70
+ var LiquidGlass = class extends events.EventEmitter {
71
+ _addon;
72
+ _isGlassSupported;
73
+ GlassMaterialVariant = GlassMaterialVariant;
74
+ constructor() {
75
+ super();
76
+ try {
77
+ if (!this.isMacOS()) return;
78
+ this._addon = new native_loader_default.LiquidGlassNative();
79
+ } catch (err) {
80
+ console.error("electron-liquid-glass failed to load its native addon – liquid glass functionality will be disabled.", err);
81
+ }
82
+ }
83
+ isMacOS() {
84
+ return process.platform === "darwin";
85
+ }
86
+ /**
87
+ * Check if liquid glass is supported on the current platform
88
+ * @returns true if liquid glass is supported on the current platform
89
+ */
90
+ isGlassSupported() {
91
+ if (this._isGlassSupported !== void 0) return this._isGlassSupported;
92
+ const supported = this.isMacOS() && Number((0, child_process.execSync)("sw_vers -productVersion").toString().trim().split(".")[0]) >= 26;
93
+ this._isGlassSupported = supported;
94
+ return supported;
95
+ }
96
+ /**
97
+ * Wrap the Electron window with a glass / vibrancy view.
98
+ *
99
+ * ⚠️ Will gracefully fall back to legacy macOS blur if liquid glass is not supported.
100
+ * @param handle BrowserWindow.getNativeWindowHandle()
101
+ * @param options Glass effect options
102
+ * @returns id – can be used for future API (remove/update), -1 if not supported
103
+ */
104
+ addView(handle, options = {}) {
105
+ if (!Buffer.isBuffer(handle)) throw new Error("[liquidGlass.addView] handle must be a Buffer");
106
+ if (!this._addon) return -1;
107
+ return this._addon.addView(handle, options);
108
+ }
109
+ setVariant(id, variant) {
110
+ if (!this._addon || typeof this._addon.setVariant !== "function") return;
111
+ this._addon.setVariant(id, variant);
112
+ }
113
+ unstable_setVariant(id, variant) {
114
+ this.setVariant(id, variant);
115
+ }
116
+ unstable_setScrim(id, scrim) {
117
+ if (!this._addon || typeof this._addon.setScrimState !== "function") return;
118
+ this._addon.setScrimState(id, scrim);
119
+ }
120
+ unstable_setSubdued(id, subdued) {
121
+ if (!this._addon || typeof this._addon.setSubduedState !== "function") return;
122
+ this._addon.setSubduedState(id, subdued);
123
+ }
124
+ };
125
+ const liquidGlass = new LiquidGlass();
126
+ var js_default = liquidGlass;
127
+
128
+ //#endregion
129
+ module.exports = js_default;
@@ -0,0 +1,71 @@
1
+ import { EventEmitter } from "events";
2
+
3
+ //#region js/variants.d.ts
4
+ type GlassMaterialVariant = number;
5
+ declare const GlassMaterialVariant: {
6
+ readonly regular: 0;
7
+ readonly clear: 1;
8
+ readonly dock: 2;
9
+ readonly appIcons: 3;
10
+ readonly widgets: 4;
11
+ readonly text: 5;
12
+ readonly avplayer: 6;
13
+ readonly facetime: 7;
14
+ readonly controlCenter: 8;
15
+ readonly notificationCenter: 9;
16
+ readonly monogram: 10;
17
+ readonly bubbles: 11;
18
+ readonly identity: 12;
19
+ readonly focusBorder: 13;
20
+ readonly focusPlatter: 14;
21
+ readonly keyboard: 15;
22
+ readonly sidebar: 16;
23
+ readonly abuttedSidebar: 17;
24
+ readonly inspector: 18;
25
+ readonly control: 19;
26
+ readonly loupe: 20;
27
+ readonly slider: 21;
28
+ readonly camera: 22;
29
+ readonly cartouchePopover: 23;
30
+ };
31
+ //#endregion
32
+ //#region js/index.d.ts
33
+ interface GlassOptions {
34
+ cornerRadius?: number;
35
+ tintColor?: string;
36
+ opaque?: boolean;
37
+ }
38
+ interface LiquidGlassNative {
39
+ addView(handle: Buffer, options: GlassOptions): number;
40
+ setVariant(id: number, variant: GlassMaterialVariant): void;
41
+ setScrimState(id: number, scrim: number): void;
42
+ setSubduedState(id: number, subdued: number): void;
43
+ }
44
+ declare class LiquidGlass extends EventEmitter {
45
+ private _addon?;
46
+ private _isGlassSupported;
47
+ readonly GlassMaterialVariant: typeof GlassMaterialVariant;
48
+ constructor();
49
+ private isMacOS;
50
+ /**
51
+ * Check if liquid glass is supported on the current platform
52
+ * @returns true if liquid glass is supported on the current platform
53
+ */
54
+ isGlassSupported(): boolean;
55
+ /**
56
+ * Wrap the Electron window with a glass / vibrancy view.
57
+ *
58
+ * ⚠️ Will gracefully fall back to legacy macOS blur if liquid glass is not supported.
59
+ * @param handle BrowserWindow.getNativeWindowHandle()
60
+ * @param options Glass effect options
61
+ * @returns id – can be used for future API (remove/update), -1 if not supported
62
+ */
63
+ addView(handle: Buffer, options?: GlassOptions): number;
64
+ private setVariant;
65
+ unstable_setVariant(id: number, variant: GlassMaterialVariant): void;
66
+ unstable_setScrim(id: number, scrim: number): void;
67
+ unstable_setSubdued(id: number, subdued: number): void;
68
+ }
69
+ declare const liquidGlass: LiquidGlass;
70
+ //#endregion
71
+ export { GlassOptions, LiquidGlassNative, liquidGlass as default };
@@ -0,0 +1,71 @@
1
+ import { EventEmitter } from "events";
2
+
3
+ //#region js/variants.d.ts
4
+ type GlassMaterialVariant = number;
5
+ declare const GlassMaterialVariant: {
6
+ readonly regular: 0;
7
+ readonly clear: 1;
8
+ readonly dock: 2;
9
+ readonly appIcons: 3;
10
+ readonly widgets: 4;
11
+ readonly text: 5;
12
+ readonly avplayer: 6;
13
+ readonly facetime: 7;
14
+ readonly controlCenter: 8;
15
+ readonly notificationCenter: 9;
16
+ readonly monogram: 10;
17
+ readonly bubbles: 11;
18
+ readonly identity: 12;
19
+ readonly focusBorder: 13;
20
+ readonly focusPlatter: 14;
21
+ readonly keyboard: 15;
22
+ readonly sidebar: 16;
23
+ readonly abuttedSidebar: 17;
24
+ readonly inspector: 18;
25
+ readonly control: 19;
26
+ readonly loupe: 20;
27
+ readonly slider: 21;
28
+ readonly camera: 22;
29
+ readonly cartouchePopover: 23;
30
+ };
31
+ //#endregion
32
+ //#region js/index.d.ts
33
+ interface GlassOptions {
34
+ cornerRadius?: number;
35
+ tintColor?: string;
36
+ opaque?: boolean;
37
+ }
38
+ interface LiquidGlassNative {
39
+ addView(handle: Buffer, options: GlassOptions): number;
40
+ setVariant(id: number, variant: GlassMaterialVariant): void;
41
+ setScrimState(id: number, scrim: number): void;
42
+ setSubduedState(id: number, subdued: number): void;
43
+ }
44
+ declare class LiquidGlass extends EventEmitter {
45
+ private _addon?;
46
+ private _isGlassSupported;
47
+ readonly GlassMaterialVariant: typeof GlassMaterialVariant;
48
+ constructor();
49
+ private isMacOS;
50
+ /**
51
+ * Check if liquid glass is supported on the current platform
52
+ * @returns true if liquid glass is supported on the current platform
53
+ */
54
+ isGlassSupported(): boolean;
55
+ /**
56
+ * Wrap the Electron window with a glass / vibrancy view.
57
+ *
58
+ * ⚠️ Will gracefully fall back to legacy macOS blur if liquid glass is not supported.
59
+ * @param handle BrowserWindow.getNativeWindowHandle()
60
+ * @param options Glass effect options
61
+ * @returns id – can be used for future API (remove/update), -1 if not supported
62
+ */
63
+ addView(handle: Buffer, options?: GlassOptions): number;
64
+ private setVariant;
65
+ unstable_setVariant(id: number, variant: GlassMaterialVariant): void;
66
+ unstable_setScrim(id: number, scrim: number): void;
67
+ unstable_setSubdued(id: number, subdued: number): void;
68
+ }
69
+ declare const liquidGlass: LiquidGlass;
70
+ //#endregion
71
+ export { GlassOptions, LiquidGlassNative, liquidGlass as default };
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ import { createRequire } from "node:module";
2
+ import { EventEmitter } from "events";
3
+ import { execSync } from "child_process";
4
+ import { fileURLToPath } from "url";
5
+ import { dirname, join } from "path";
6
+
7
+ //#region rolldown:runtime
8
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
9
+
10
+ //#endregion
11
+ //#region js/variants.ts
12
+ const GlassMaterialVariant = {
13
+ regular: 0,
14
+ clear: 1,
15
+ dock: 2,
16
+ appIcons: 3,
17
+ widgets: 4,
18
+ text: 5,
19
+ avplayer: 6,
20
+ facetime: 7,
21
+ controlCenter: 8,
22
+ notificationCenter: 9,
23
+ monogram: 10,
24
+ bubbles: 11,
25
+ identity: 12,
26
+ focusBorder: 13,
27
+ focusPlatter: 14,
28
+ keyboard: 15,
29
+ sidebar: 16,
30
+ abuttedSidebar: 17,
31
+ inspector: 18,
32
+ control: 19,
33
+ loupe: 20,
34
+ slider: 21,
35
+ camera: 22,
36
+ cartouchePopover: 23
37
+ };
38
+
39
+ //#endregion
40
+ //#region js/native-loader.ts
41
+ const __filename = fileURLToPath(import.meta.url);
42
+ const __dirname = dirname(__filename);
43
+ const nodeGypBuild = __require("node-gyp-build");
44
+ var native_loader_default = nodeGypBuild(join(__dirname, ".."));
45
+
46
+ //#endregion
47
+ //#region js/index.ts
48
+ var LiquidGlass = class extends EventEmitter {
49
+ _addon;
50
+ _isGlassSupported;
51
+ GlassMaterialVariant = GlassMaterialVariant;
52
+ constructor() {
53
+ super();
54
+ try {
55
+ if (!this.isMacOS()) return;
56
+ this._addon = new native_loader_default.LiquidGlassNative();
57
+ } catch (err) {
58
+ console.error("electron-liquid-glass failed to load its native addon – liquid glass functionality will be disabled.", err);
59
+ }
60
+ }
61
+ isMacOS() {
62
+ return process.platform === "darwin";
63
+ }
64
+ /**
65
+ * Check if liquid glass is supported on the current platform
66
+ * @returns true if liquid glass is supported on the current platform
67
+ */
68
+ isGlassSupported() {
69
+ if (this._isGlassSupported !== void 0) return this._isGlassSupported;
70
+ const supported = this.isMacOS() && Number(execSync("sw_vers -productVersion").toString().trim().split(".")[0]) >= 26;
71
+ this._isGlassSupported = supported;
72
+ return supported;
73
+ }
74
+ /**
75
+ * Wrap the Electron window with a glass / vibrancy view.
76
+ *
77
+ * ⚠️ Will gracefully fall back to legacy macOS blur if liquid glass is not supported.
78
+ * @param handle BrowserWindow.getNativeWindowHandle()
79
+ * @param options Glass effect options
80
+ * @returns id – can be used for future API (remove/update), -1 if not supported
81
+ */
82
+ addView(handle, options = {}) {
83
+ if (!Buffer.isBuffer(handle)) throw new Error("[liquidGlass.addView] handle must be a Buffer");
84
+ if (!this._addon) return -1;
85
+ return this._addon.addView(handle, options);
86
+ }
87
+ setVariant(id, variant) {
88
+ if (!this._addon || typeof this._addon.setVariant !== "function") return;
89
+ this._addon.setVariant(id, variant);
90
+ }
91
+ unstable_setVariant(id, variant) {
92
+ this.setVariant(id, variant);
93
+ }
94
+ unstable_setScrim(id, scrim) {
95
+ if (!this._addon || typeof this._addon.setScrimState !== "function") return;
96
+ this._addon.setScrimState(id, scrim);
97
+ }
98
+ unstable_setSubdued(id, subdued) {
99
+ if (!this._addon || typeof this._addon.setSubduedState !== "function") return;
100
+ this._addon.setSubduedState(id, subdued);
101
+ }
102
+ };
103
+ const liquidGlass = new LiquidGlass();
104
+ var js_default = liquidGlass;
105
+
106
+ //#endregion
107
+ export { js_default as default };
package/js/index.ts ADDED
@@ -0,0 +1,117 @@
1
+ import { EventEmitter } from "events";
2
+ import { execSync } from "child_process";
3
+ import { GlassMaterialVariant } from "./variants.js";
4
+
5
+ // Load the native addon using the 'bindings' module
6
+ // This will look for the compiled .node file in various places
7
+ import native from "./native-loader.js";
8
+
9
+ export interface GlassOptions {
10
+ cornerRadius?: number;
11
+ tintColor?: string;
12
+ opaque?: boolean;
13
+ }
14
+
15
+ export interface LiquidGlassNative {
16
+ addView(handle: Buffer, options: GlassOptions): number;
17
+ setVariant(id: number, variant: GlassMaterialVariant): void;
18
+ setScrimState(id: number, scrim: number): void;
19
+ setSubduedState(id: number, subdued: number): void;
20
+ }
21
+
22
+ // Create a nice JavaScript wrapper
23
+ class LiquidGlass extends EventEmitter {
24
+ private _addon?: LiquidGlassNative;
25
+ private _isGlassSupported: boolean | undefined;
26
+
27
+ // Instance property for easy access to variants
28
+ readonly GlassMaterialVariant: typeof GlassMaterialVariant =
29
+ GlassMaterialVariant;
30
+
31
+ constructor() {
32
+ super();
33
+
34
+ try {
35
+ if (!this.isMacOS()) {
36
+ return;
37
+ }
38
+
39
+ // Native addon uses liquid glass (macOS 26+)
40
+ // or falls back to legacy blur as needed.
41
+ this._addon = new native.LiquidGlassNative();
42
+ } catch (err) {
43
+ console.error(
44
+ "electron-liquid-glass failed to load its native addon – liquid glass functionality will be disabled.",
45
+ err
46
+ );
47
+ }
48
+ }
49
+
50
+ private isMacOS(): boolean {
51
+ return process.platform === "darwin";
52
+ }
53
+
54
+ /**
55
+ * Check if liquid glass is supported on the current platform
56
+ * @returns true if liquid glass is supported on the current platform
57
+ */
58
+ public isGlassSupported(): boolean {
59
+ if (this._isGlassSupported !== undefined) return this._isGlassSupported;
60
+
61
+ const supported =
62
+ this.isMacOS() &&
63
+ Number(
64
+ execSync("sw_vers -productVersion").toString().trim().split(".")[0]
65
+ ) >= 26;
66
+
67
+ this._isGlassSupported = supported;
68
+ return supported;
69
+ }
70
+
71
+ /**
72
+ * Wrap the Electron window with a glass / vibrancy view.
73
+ *
74
+ * ⚠️ Will gracefully fall back to legacy macOS blur if liquid glass is not supported.
75
+ * @param handle BrowserWindow.getNativeWindowHandle()
76
+ * @param options Glass effect options
77
+ * @returns id – can be used for future API (remove/update), -1 if not supported
78
+ */
79
+ addView(handle: Buffer, options: GlassOptions = {}): number {
80
+ if (!Buffer.isBuffer(handle)) {
81
+ throw new Error("[liquidGlass.addView] handle must be a Buffer");
82
+ }
83
+
84
+ if (!this._addon) {
85
+ // unavailable on this platform
86
+ return -1;
87
+ }
88
+
89
+ return this._addon.addView(handle, options);
90
+ }
91
+
92
+ private setVariant(id: number, variant: GlassMaterialVariant): void {
93
+ if (!this._addon || typeof this._addon.setVariant !== "function") return;
94
+ this._addon.setVariant(id, variant);
95
+ }
96
+
97
+ unstable_setVariant(id: number, variant: GlassMaterialVariant): void {
98
+ this.setVariant(id, variant);
99
+ }
100
+
101
+ unstable_setScrim(id: number, scrim: number): void {
102
+ if (!this._addon || typeof this._addon.setScrimState !== "function") return;
103
+ this._addon.setScrimState(id, scrim);
104
+ }
105
+
106
+ unstable_setSubdued(id: number, subdued: number): void {
107
+ if (!this._addon || typeof this._addon.setSubduedState !== "function")
108
+ return;
109
+ this._addon.setSubduedState(id, subdued);
110
+ }
111
+ }
112
+
113
+ // Create and export the singleton instance
114
+ // The class constructor handles platform checks internally
115
+ const liquidGlass: LiquidGlass = new LiquidGlass();
116
+
117
+ export default liquidGlass;
@@ -0,0 +1,10 @@
1
+ import { fileURLToPath } from "url";
2
+ import { dirname, join } from "path";
3
+
4
+ // ESM-compatible way to get __dirname
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ // node-gyp-build smartly resolves prebuilds as well as local builds.
9
+ const nodeGypBuild = require("node-gyp-build");
10
+ export default nodeGypBuild(join(__dirname, ".."));
package/js/variants.ts ADDED
@@ -0,0 +1,28 @@
1
+ export type GlassMaterialVariant = number;
2
+
3
+ export const GlassMaterialVariant = {
4
+ regular: 0,
5
+ clear: 1,
6
+ dock: 2,
7
+ appIcons: 3,
8
+ widgets: 4,
9
+ text: 5,
10
+ avplayer: 6,
11
+ facetime: 7,
12
+ controlCenter: 8,
13
+ notificationCenter: 9,
14
+ monogram: 10,
15
+ bubbles: 11,
16
+ identity: 12,
17
+ focusBorder: 13,
18
+ focusPlatter: 14,
19
+ keyboard: 15,
20
+ sidebar: 16,
21
+ abuttedSidebar: 17,
22
+ inspector: 18,
23
+ control: 19,
24
+ loupe: 20,
25
+ slider: 21,
26
+ camera: 22,
27
+ cartouchePopover: 23,
28
+ } as const;
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@zakim24/electron-liquid-glass",
3
+ "version": "1.1.1",
4
+ "description": "macOS glass / vibrancy wrapper for Electron BrowserWindow",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Meridius-Labs/electron-liquid-glass.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/Meridius-Labs/electron-liquid-glass/issues"
11
+ },
12
+ "homepage": "https://github.com/Meridius-Labs/electron-liquid-glass#readme",
13
+ "author": "Meridius Labs",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "js",
34
+ "src",
35
+ "build/Release/*.node",
36
+ "prebuilds/**/*",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "build:native": "prebuildify --napi --strip --tag-armv --arch=arm64 && prebuildify --napi --strip --arch=x64",
43
+ "build:all": "bun run build:native && bun run build",
44
+ "clean": "rimraf build prebuilds dist",
45
+ "prepack": "bun run build:all",
46
+ "dev": "bun run build:all && run-p watch-examples start",
47
+ "watch-examples": "tsc -w --project tsconfig.examples.json",
48
+ "start": "bun x electronmon ./dist-examples/electron.js",
49
+ "version:patch": "npm version patch",
50
+ "version:minor": "npm version minor",
51
+ "version:major": "npm version major",
52
+ "version:prerelease": "npm version prerelease",
53
+ "release": "bun run build:all && npm publish",
54
+ "release:dry": "bun run build:all && npm publish --dry-run"
55
+ },
56
+ "keywords": [
57
+ "electron",
58
+ "macos",
59
+ "vibrancy",
60
+ "glass",
61
+ "nsvisualeffectview"
62
+ ],
63
+ "gypfile": false,
64
+ "os": [
65
+ "darwin"
66
+ ],
67
+ "engines": {
68
+ "node": ">=18"
69
+ },
70
+ "dependencies": {
71
+ "bindings": "^1.5.0",
72
+ "node-addon-api": "^8.4.0"
73
+ },
74
+ "optionalDependencies": {
75
+ "node-gyp-build": "^4"
76
+ },
77
+ "devDependencies": {
78
+ "@3c1u/bun-run-all": "^0.1.2",
79
+ "@types/node": "^20",
80
+ "electron": "^38",
81
+ "node-gyp": "^11.2.0",
82
+ "prebuildify": "^5",
83
+ "rimraf": "^5",
84
+ "tsdown": "^0.12.8",
85
+ "typescript": "^5"
86
+ },
87
+ "packageManager": "bun@1.2.23"
88
+ }
@@ -0,0 +1,256 @@
1
+ #include "../include/Common.h"
2
+ #include <napi.h>
3
+ #import <objc/runtime.h>
4
+ #import <objc/message.h>
5
+ #include <string>
6
+ #include <cctype>
7
+
8
+ #ifdef PLATFORM_OSX
9
+ #import <AppKit/AppKit.h>
10
+
11
+ // Simple registry so JS can still address a view by numeric id.
12
+ static std::map<int, NSView *> g_glassViews;
13
+ static int g_nextViewId = 0;
14
+
15
+ // Keys for objc-associated views on a container
16
+ static const void *kGlassEffectKey = &kGlassEffectKey;
17
+ static const void *kBackgroundViewKey = &kBackgroundViewKey;
18
+
19
+ // Utility: convert #RRGGBB or #RRGGBBAA to NSColor* (sRGB)
20
+ static NSColor* ColorFromHexNSString(NSString* hex)
21
+ {
22
+ NSString* cleaned = [[hex stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
23
+ if ([cleaned hasPrefix:@"#"]) cleaned = [cleaned substringFromIndex:1];
24
+ if (cleaned.length != 6 && cleaned.length != 8) return nil;
25
+
26
+ unsigned int rgba = 0;
27
+ NSScanner* scanner = [NSScanner scannerWithString:cleaned];
28
+ if (![scanner scanHexInt:&rgba]) return nil;
29
+
30
+ CGFloat r,g,b,a;
31
+ if (cleaned.length == 6) {
32
+ r = ((rgba & 0xFF0000) >> 16) / 255.0;
33
+ g = ((rgba & 0x00FF00) >> 8) / 255.0;
34
+ b = (rgba & 0x0000FF) / 255.0;
35
+ a = 1.0;
36
+ } else {
37
+ r = ((rgba & 0xFF000000) >> 24) / 255.0;
38
+ g = ((rgba & 0x00FF0000) >> 16) / 255.0;
39
+ b = ((rgba & 0x0000FF00) >> 8) / 255.0;
40
+ a = (rgba & 0x000000FF) / 255.0;
41
+ }
42
+ return [NSColor colorWithRed:r green:g blue:b alpha:a];
43
+ }
44
+
45
+ #define RUN_ON_MAIN(block) \
46
+ if ([NSThread isMainThread]) { \
47
+ block(); \
48
+ } else { \
49
+ dispatch_sync(dispatch_get_main_queue(), block); \
50
+ }
51
+
52
+ /*!
53
+ * AddGlassEffectView
54
+ * -----------------
55
+ * Creates an `NSGlassEffectView` (private) and inserts it behind the contentView
56
+ * of the supplied Electron window. The handle received from JavaScript is the
57
+ * pointer to the Cocoa `NSView` that backs the BrowserWindow. The view is
58
+ * retained in a small registry so that we can manipulate or remove it later if
59
+ * required. The function returns an integer identifier that can be used from
60
+ * JavaScript.
61
+ *
62
+ * Returns –1 on error.
63
+ */
64
+ extern "C" int AddGlassEffectView(unsigned char *buffer, bool opaque) {
65
+ if (!buffer) {
66
+ return -1;
67
+ }
68
+
69
+ __block int resultId = -1;
70
+
71
+ RUN_ON_MAIN(^{
72
+ NSView *rootView = *reinterpret_cast<NSView **>(buffer);
73
+ if (!rootView) return;
74
+
75
+ // Find the proper container - avoid NSThemeFrame
76
+ NSView *container = rootView;
77
+
78
+ // Remove previous glass and background views (if any)
79
+ NSView *oldGlass = objc_getAssociatedObject(container, kGlassEffectKey);
80
+ if (oldGlass) [oldGlass removeFromSuperview];
81
+
82
+ NSView *oldBackground = objc_getAssociatedObject(container, kBackgroundViewKey);
83
+ if (oldBackground) [oldBackground removeFromSuperview];
84
+
85
+ NSRect bounds = container.bounds;
86
+
87
+ NSBox *backgroundView = nil;
88
+
89
+
90
+ NSView *glass = nil;
91
+ Class glassCls = NSClassFromString(@"NSGlassEffectView");
92
+ if (glassCls) {
93
+ /**
94
+ * GLASS VIEW
95
+ */
96
+ glass = [[glassCls alloc] initWithFrame:bounds];
97
+
98
+ if (opaque) {
99
+ // Create a background view behind the glass view using NSBox for proper background color
100
+ backgroundView = [[NSBox alloc] initWithFrame:bounds];
101
+ backgroundView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
102
+ backgroundView.boxType = NSBoxCustom;
103
+ backgroundView.borderType = NSNoBorder;
104
+ backgroundView.fillColor = [NSColor windowBackgroundColor];
105
+ backgroundView.wantsLayer = YES;
106
+
107
+
108
+ // Add the background view first (bottom layer)
109
+ [container addSubview:backgroundView positioned:NSWindowBelow relativeTo:nil];
110
+ }
111
+ } else {
112
+ /**
113
+ * FALLBACK VISUAL EFFECT VIEW
114
+ */
115
+ NSVisualEffectView *visual = [[NSVisualEffectView alloc] initWithFrame:bounds];
116
+ visual.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
117
+ visual.blendingMode = NSVisualEffectBlendingModeBehindWindow;
118
+ visual.material = NSVisualEffectMaterialUnderWindowBackground;
119
+ visual.state = NSVisualEffectStateActive;
120
+ glass = visual;
121
+ }
122
+
123
+ // Ensure autoresize if we created a private glass view too
124
+ glass.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
125
+
126
+ // Add the glass view (positioned relative to background view if opaque, or below everything if not)
127
+ if (opaque && backgroundView) {
128
+ [container addSubview:glass positioned:NSWindowAbove relativeTo:backgroundView];
129
+ } else {
130
+ [container addSubview:glass positioned:NSWindowBelow relativeTo:nil];
131
+ }
132
+
133
+ // Associate views with the container for cleanup
134
+ objc_setAssociatedObject(container, kGlassEffectKey, glass, OBJC_ASSOCIATION_RETAIN);
135
+ if (backgroundView) {
136
+ objc_setAssociatedObject(container, kBackgroundViewKey, backgroundView, OBJC_ASSOCIATION_RETAIN);
137
+ } else {
138
+ objc_setAssociatedObject(container, kBackgroundViewKey, nil, OBJC_ASSOCIATION_ASSIGN);
139
+ }
140
+
141
+
142
+
143
+ int id = g_nextViewId++;
144
+ g_glassViews[id] = glass;
145
+ resultId = id;
146
+ });
147
+
148
+ return resultId;
149
+ }
150
+
151
+ // Configure glass view by id
152
+ extern "C" void ConfigureGlassView(int viewId, double cornerRadius, const char* tintHex) {
153
+ RUN_ON_MAIN(^{
154
+ auto it = g_glassViews.find(viewId);
155
+ if (it == g_glassViews.end()) return;
156
+ NSView* glass = it->second;
157
+
158
+ // Corner radius via CALayer
159
+ glass.wantsLayer = YES;
160
+ glass.layer.cornerRadius = cornerRadius;
161
+ glass.layer.masksToBounds = YES;
162
+
163
+ // corner radius for the background view
164
+ NSView* container = glass.superview;
165
+ NSView* backgroundView = objc_getAssociatedObject(container, kBackgroundViewKey);
166
+ if (backgroundView) {
167
+ backgroundView.wantsLayer = YES;
168
+ backgroundView.layer.cornerRadius = cornerRadius;
169
+ backgroundView.layer.masksToBounds = YES;
170
+ }
171
+
172
+ if (tintHex && strlen(tintHex) > 0) {
173
+ NSString* hex = [NSString stringWithUTF8String:tintHex];
174
+ NSColor* c = ColorFromHexNSString(hex);
175
+ if (c && [glass respondsToSelector:@selector(setTintColor:)]) {
176
+ [(id)glass setTintColor:c];
177
+ } else if (c) {
178
+ glass.layer.backgroundColor = c.CGColor;
179
+ }
180
+ }
181
+ });
182
+ }
183
+
184
+ // -----------------------------------------------------------------------------
185
+ // Dynamically set private properties on a previously created glass view
186
+ // -----------------------------------------------------------------------------
187
+
188
+ // Helper that converts a C-string key (e.g. "variant") into the Objective-C
189
+ // selector for its private setter (e.g. set_variant:). It automatically adds
190
+ // the leading underscore when missing.
191
+ static SEL SetterFromKey(const std::string &key, bool privateVariant) {
192
+ std::string name;
193
+ if (privateVariant) {
194
+ // ensure leading underscore
195
+ if (!key.empty() && key.front() != '_')
196
+ name = "_" + key;
197
+ else
198
+ name = key;
199
+ name = "set" + name;
200
+ } else {
201
+ // camel-case public variant: set + CapitalizedFirst + rest
202
+ if (key.empty()) return nil;
203
+ name = "set";
204
+ name += toupper(key[0]);
205
+ name += key.substr(1);
206
+ }
207
+ name += ":";
208
+ return sel_registerName(name.c_str());
209
+ }
210
+
211
+ static SEL ResolveSetter(id obj, const char* cKey) {
212
+ if (!cKey) return nil;
213
+ std::string key(cKey);
214
+ if (key.empty()) return nil;
215
+ // Try private style first (set_<key>:)
216
+ SEL sel = SetterFromKey(key, true);
217
+ if ([obj respondsToSelector:sel]) return sel;
218
+ // Then try public style (setKey:)
219
+ sel = SetterFromKey(key, false);
220
+ if ([obj respondsToSelector:sel]) return sel;
221
+ return nil;
222
+ }
223
+
224
+ extern "C" void SetGlassViewIntProperty(int viewId, const char* key, long long value) {
225
+ #ifdef PLATFORM_OSX
226
+ RUN_ON_MAIN(^{
227
+ auto it = g_glassViews.find(viewId);
228
+ if (it == g_glassViews.end()) return;
229
+ NSView* glass = it->second;
230
+
231
+ SEL sel = ResolveSetter(glass, key);
232
+ if (!sel) return;
233
+ if ([glass respondsToSelector:sel]) {
234
+ ((void (*)(id, SEL, long long))objc_msgSend)(glass, sel, value);
235
+ }
236
+ });
237
+ #endif
238
+ }
239
+
240
+ extern "C" void SetGlassViewStringProperty(int viewId, const char* key, const char* value) {
241
+ #ifdef PLATFORM_OSX
242
+ RUN_ON_MAIN(^{
243
+ auto it = g_glassViews.find(viewId);
244
+ if (it == g_glassViews.end()) return;
245
+ NSView* glass = it->second;
246
+
247
+ SEL sel = ResolveSetter(glass, key);
248
+ if (!sel) return;
249
+ if ([glass respondsToSelector:sel]) {
250
+ NSString* val = value ? [NSString stringWithUTF8String:value] : @"";
251
+ ((void (*)(id, SEL, id))objc_msgSend)(glass, sel, val);
252
+ }
253
+ });
254
+ #endif
255
+ }
256
+ #endif // PLATFORM_OSX
@@ -0,0 +1,139 @@
1
+ #include <napi.h>
2
+ #include <string>
3
+
4
+ #ifdef __APPLE__
5
+ extern "C" int AddGlassEffectView(unsigned char *buffer, bool opaque);
6
+ extern "C" void ConfigureGlassView(int viewId, double cornerRadius, const char *tintHex);
7
+ extern "C" void SetGlassViewIntProperty(int viewId, const char *key, long long value);
8
+ extern "C" void SetGlassViewStringProperty(int viewId, const char *key, const char *value);
9
+ #endif
10
+
11
+ // Create a class that will be exposed to JavaScript
12
+ class LiquidGlassNative : public Napi::ObjectWrap<LiquidGlassNative>
13
+ {
14
+ public:
15
+ // This static method defines the class for JavaScript
16
+ static Napi::Object Init(Napi::Env env, Napi::Object exports)
17
+ {
18
+ // Define the JavaScript class with method(s)
19
+ Napi::Function func = DefineClass(env, "LiquidGlassNative", {InstanceMethod("addView", &LiquidGlassNative::AddView), InstanceMethod("setVariant", &LiquidGlassNative::SetVariant), InstanceMethod("setScrimState", &LiquidGlassNative::SetScrimState), InstanceMethod("setSubduedState", &LiquidGlassNative::SetSubduedState)});
20
+
21
+ // Create a persistent reference to the constructor
22
+ Napi::FunctionReference *constructor = new Napi::FunctionReference();
23
+ *constructor = Napi::Persistent(func);
24
+ env.SetInstanceData(constructor);
25
+
26
+ // Set the constructor on the exports object
27
+ exports.Set("LiquidGlassNative", func);
28
+ return exports;
29
+ }
30
+
31
+ // Constructor
32
+ LiquidGlassNative(const Napi::CallbackInfo &info)
33
+ : Napi::ObjectWrap<LiquidGlassNative>(info) {}
34
+
35
+ private:
36
+ // New AddView method
37
+ Napi::Value AddView(const Napi::CallbackInfo &info)
38
+ {
39
+ Napi::Env env = info.Env();
40
+
41
+ if (info.Length() < 1 || !info[0].IsBuffer())
42
+ {
43
+ Napi::TypeError::New(env, "Expected first argument to be a Buffer returned by getNativeWindowHandle()").ThrowAsJavaScriptException();
44
+ return env.Null();
45
+ }
46
+
47
+ double radius = 0.0;
48
+ std::string tint;
49
+ bool opaque = false;
50
+ if (info.Length() >= 2 && info[1].IsObject())
51
+ {
52
+ auto obj = info[1].As<Napi::Object>();
53
+ if (obj.Has("cornerRadius") && obj.Get("cornerRadius").IsNumber())
54
+ {
55
+ radius = obj.Get("cornerRadius").As<Napi::Number>().DoubleValue();
56
+ }
57
+ if (obj.Has("tintColor") && obj.Get("tintColor").IsString())
58
+ {
59
+ tint = obj.Get("tintColor").As<Napi::String>().Utf8Value();
60
+ }
61
+ if (obj.Has("opaque") && obj.Get("opaque").IsBoolean())
62
+ {
63
+ opaque = obj.Get("opaque").As<Napi::Boolean>().Value();
64
+ }
65
+ }
66
+
67
+ auto buffer = info[0].As<Napi::Buffer<unsigned char>>();
68
+
69
+ #ifdef __APPLE__
70
+ int viewId = AddGlassEffectView(buffer.Data(), opaque);
71
+ if (viewId >= 0)
72
+ {
73
+ ConfigureGlassView(viewId, radius, tint.c_str());
74
+ }
75
+ return Napi::Number::New(env, viewId);
76
+ #else
77
+ // Not supported on this platform yet
78
+ return Napi::Number::New(env, -1);
79
+ #endif
80
+ }
81
+
82
+ Napi::Value SetVariant(const Napi::CallbackInfo &info)
83
+ {
84
+ Napi::Env env = info.Env();
85
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber())
86
+ {
87
+ Napi::TypeError::New(env, "Expected (id:number, variant:number)").ThrowAsJavaScriptException();
88
+ return env.Null();
89
+ }
90
+ int id = info[0].As<Napi::Number>().Int32Value();
91
+ long long variant = info[1].As<Napi::Number>().Int64Value();
92
+ ApplyIntProp(id, "variant", variant);
93
+ return env.Undefined();
94
+ }
95
+
96
+ Napi::Value SetScrimState(const Napi::CallbackInfo &info)
97
+ {
98
+ Napi::Env env = info.Env();
99
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber())
100
+ {
101
+ Napi::TypeError::New(env, "Expected (id:number, scrim:number)").ThrowAsJavaScriptException();
102
+ return env.Null();
103
+ }
104
+ int id = info[0].As<Napi::Number>().Int32Value();
105
+ long long scrim = info[1].As<Napi::Number>().Int64Value();
106
+ ApplyIntProp(id, "scrimState", scrim);
107
+ return env.Undefined();
108
+ }
109
+
110
+ Napi::Value SetSubduedState(const Napi::CallbackInfo &info)
111
+ {
112
+ Napi::Env env = info.Env();
113
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber())
114
+ {
115
+ Napi::TypeError::New(env, "Expected (id:number, subdued:number)").ThrowAsJavaScriptException();
116
+ return env.Null();
117
+ }
118
+ int id = info[0].As<Napi::Number>().Int32Value();
119
+ long long subd = info[1].As<Napi::Number>().Int64Value();
120
+ ApplyIntProp(id, "subduedState", subd);
121
+ return env.Undefined();
122
+ }
123
+
124
+ static void ApplyIntProp(int id, const char *key, long long v)
125
+ {
126
+ #ifdef __APPLE__
127
+ SetGlassViewIntProperty(id, key, v);
128
+ #endif
129
+ }
130
+ };
131
+
132
+ // Initialize the addon
133
+ Napi::Object Init(Napi::Env env, Napi::Object exports)
134
+ {
135
+ return LiquidGlassNative::Init(env, exports);
136
+ }
137
+
138
+ // Register the initialization function
139
+ NODE_API_MODULE(liquidglass, Init)