@owear/core 0.1.2 → 0.1.4
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/dist/app.d.ts +128 -0
- package/dist/app.js +285 -0
- package/dist/appicon.d.ts +4 -0
- package/dist/appicon.js +16 -0
- package/dist/bridge.d.ts +79 -0
- package/dist/bridge.js +55 -0
- package/dist/browserwindow.d.ts +115 -0
- package/dist/browserwindow.js +237 -0
- package/dist/channel.d.ts +22 -0
- package/dist/channel.js +114 -0
- package/dist/dialog.d.ts +47 -0
- package/dist/dialog.js +22 -0
- package/dist/index.d.ts +83 -96
- package/dist/index.js +26 -253
- package/dist/installer.d.ts +126 -0
- package/dist/installer.js +84 -0
- package/dist/ipc/node.d.ts +7 -0
- package/dist/ipc/node.js +25 -0
- package/dist/menu.d.ts +56 -0
- package/dist/menu.js +208 -0
- package/dist/modules.d.ts +16 -0
- package/dist/modules.js +13 -0
- package/dist/nativeimage.d.ts +41 -0
- package/dist/nativeimage.js +376 -0
- package/dist/node/worker-runner.d.ts +1 -0
- package/dist/node/worker-runner.js +35 -0
- package/dist/node/worker.d.ts +29 -0
- package/dist/node/worker.js +61 -0
- package/dist/port.d.ts +28 -0
- package/dist/port.js +42 -0
- package/dist/power.d.ts +33 -0
- package/dist/power.js +99 -0
- package/dist/protocol.d.ts +14 -0
- package/dist/protocol.js +46 -0
- package/dist/safestorage.d.ts +16 -0
- package/dist/safestorage.js +21 -0
- package/dist/screen.d.ts +92 -0
- package/dist/screen.js +121 -0
- package/dist/session.d.ts +24 -0
- package/dist/session.js +42 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.js +34 -0
- package/dist/tray.d.ts +30 -0
- package/dist/tray.js +69 -0
- package/dist/updater/boot.d.ts +20 -0
- package/dist/updater/boot.js +49 -0
- package/dist/updater/delta.d.ts +30 -0
- package/dist/updater/delta.js +96 -0
- package/dist/updater/yaml.d.ts +6 -0
- package/dist/updater/yaml.js +160 -0
- package/dist/updater.d.ts +119 -0
- package/dist/updater.js +429 -0
- package/dist/webcontents.d.ts +44 -0
- package/dist/webcontents.js +109 -0
- package/dist/webrequest.d.ts +23 -0
- package/dist/webrequest.js +76 -0
- package/package.json +7 -6
- package/LICENSE +0 -201
package/dist/menu.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Copyright 2026 Owear Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// @owear/core/src/menu.ts — Menu / MenuItem (C5).
|
|
5
|
+
import { channel } from './channel.js';
|
|
6
|
+
import { app } from './app.js';
|
|
7
|
+
import { windowsById } from './browserwindow.js';
|
|
8
|
+
import { screen } from './screen.js';
|
|
9
|
+
import { powerMonitor } from './power.js';
|
|
10
|
+
import { currentTray } from './tray.js';
|
|
11
|
+
const menuItemsById = new Map();
|
|
12
|
+
function formatAccelerator(acc) {
|
|
13
|
+
if (process.platform === 'darwin')
|
|
14
|
+
return acc.replace(/CmdOrCtrl|CommandOrControl|Command|Cmd/g, 'Cmd');
|
|
15
|
+
return acc.replace(/CmdOrCtrl|CommandOrControl|Command|Cmd/g, 'Ctrl');
|
|
16
|
+
}
|
|
17
|
+
export class MenuItem {
|
|
18
|
+
id;
|
|
19
|
+
label;
|
|
20
|
+
role;
|
|
21
|
+
type;
|
|
22
|
+
checked;
|
|
23
|
+
enabled;
|
|
24
|
+
visible;
|
|
25
|
+
accelerator;
|
|
26
|
+
sublabel;
|
|
27
|
+
toolTip;
|
|
28
|
+
submenu;
|
|
29
|
+
click;
|
|
30
|
+
constructor(opts = {}, idHint = 'm') {
|
|
31
|
+
this.type = opts.type ?? (opts.submenu ? 'submenu' : 'normal');
|
|
32
|
+
this.label = opts.label ?? opts.role ?? '';
|
|
33
|
+
this.role = opts.role;
|
|
34
|
+
this.checked = !!opts.checked;
|
|
35
|
+
this.enabled = opts.enabled !== false;
|
|
36
|
+
this.visible = opts.visible !== false;
|
|
37
|
+
this.accelerator = opts.accelerator;
|
|
38
|
+
this.sublabel = opts.sublabel;
|
|
39
|
+
this.toolTip = opts.toolTip;
|
|
40
|
+
this.click = opts.click;
|
|
41
|
+
this.id = opts.id ?? idHint;
|
|
42
|
+
if (opts.submenu instanceof Menu)
|
|
43
|
+
this.submenu = opts.submenu;
|
|
44
|
+
else if (Array.isArray(opts.submenu))
|
|
45
|
+
this.submenu = Menu.buildFromTemplate(opts.submenu, this.id);
|
|
46
|
+
}
|
|
47
|
+
_toJSON() {
|
|
48
|
+
menuItemsById.set(this.id, this);
|
|
49
|
+
const o = {
|
|
50
|
+
id: this.id,
|
|
51
|
+
label: this.label,
|
|
52
|
+
type: this.type,
|
|
53
|
+
enabled: this.enabled,
|
|
54
|
+
visible: this.visible,
|
|
55
|
+
};
|
|
56
|
+
if (this.role)
|
|
57
|
+
o.role = this.role;
|
|
58
|
+
if (this.checked)
|
|
59
|
+
o.checked = true;
|
|
60
|
+
if (this.accelerator)
|
|
61
|
+
o.accelerator = formatAccelerator(this.accelerator);
|
|
62
|
+
if (this.submenu)
|
|
63
|
+
o.submenu = this.submenu._toJSON();
|
|
64
|
+
return o;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Menú (estilo Electron, "Owear"): se construye en el main y los clicks llaman
|
|
69
|
+
* handlers del main.
|
|
70
|
+
* Menu.setApplicationMenu(Menu.buildFromTemplate([...])) // menubar (Windows)
|
|
71
|
+
* menu.popup({ window: win }) // contextual
|
|
72
|
+
*/
|
|
73
|
+
export class Menu {
|
|
74
|
+
items = [];
|
|
75
|
+
static _appMenu = null;
|
|
76
|
+
constructor(items = []) {
|
|
77
|
+
this.items = items;
|
|
78
|
+
}
|
|
79
|
+
static buildFromTemplate(template, idPrefix = 'm') {
|
|
80
|
+
const items = template.map((t, i) => t instanceof MenuItem ? t : new MenuItem(t, `${idPrefix}.${i}`));
|
|
81
|
+
return new Menu(items);
|
|
82
|
+
}
|
|
83
|
+
static setApplicationMenu(menu) {
|
|
84
|
+
Menu._appMenu = menu;
|
|
85
|
+
const items = menu ? menu._toJSON() : [];
|
|
86
|
+
void channel.call('menu.setApplicationMenu', { items }).catch(() => undefined);
|
|
87
|
+
}
|
|
88
|
+
static getApplicationMenu() {
|
|
89
|
+
return Menu._appMenu;
|
|
90
|
+
}
|
|
91
|
+
append(item) {
|
|
92
|
+
this.items.push(item);
|
|
93
|
+
}
|
|
94
|
+
/** JSON del menú (registra los items para el dispatch de clicks). */
|
|
95
|
+
_toJSON() {
|
|
96
|
+
return this.items.map((it) => it._toJSON());
|
|
97
|
+
}
|
|
98
|
+
/** Menú contextual nativo. `window` resuelve el id del click. */
|
|
99
|
+
popup(options = {}) {
|
|
100
|
+
const items = options.items
|
|
101
|
+
? Menu.buildFromTemplate(options.items, 'popup')._toJSON()
|
|
102
|
+
: this._toJSON();
|
|
103
|
+
void channel
|
|
104
|
+
.call('module.invoke', {
|
|
105
|
+
module: 'menu',
|
|
106
|
+
method: 'popup',
|
|
107
|
+
args: [{ items, x: options.x, y: options.y }],
|
|
108
|
+
windowId: options.window?.id ?? 0,
|
|
109
|
+
})
|
|
110
|
+
.catch(() => undefined);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function runMenuItem(item, window) {
|
|
114
|
+
if (item.click) {
|
|
115
|
+
item.click(item, window);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!item.role)
|
|
119
|
+
return;
|
|
120
|
+
switch (item.role) {
|
|
121
|
+
case 'quit':
|
|
122
|
+
void app.quit();
|
|
123
|
+
break;
|
|
124
|
+
case 'minimize':
|
|
125
|
+
void window?.minimize();
|
|
126
|
+
break;
|
|
127
|
+
case 'close':
|
|
128
|
+
void window?.close();
|
|
129
|
+
break;
|
|
130
|
+
case 'reload':
|
|
131
|
+
case 'forceReload':
|
|
132
|
+
void window?.webContents.reload();
|
|
133
|
+
break;
|
|
134
|
+
case 'toggleDevTools':
|
|
135
|
+
void window?.webContents.openDevTools();
|
|
136
|
+
break;
|
|
137
|
+
case 'togglefullscreen':
|
|
138
|
+
void window?.setFullScreen(true);
|
|
139
|
+
break;
|
|
140
|
+
case 'undo':
|
|
141
|
+
void window?.webContents.executeJavaScript("document.execCommand('undo')");
|
|
142
|
+
break;
|
|
143
|
+
case 'redo':
|
|
144
|
+
void window?.webContents.executeJavaScript("document.execCommand('redo')");
|
|
145
|
+
break;
|
|
146
|
+
case 'cut':
|
|
147
|
+
void window?.webContents.executeJavaScript("document.execCommand('cut')");
|
|
148
|
+
break;
|
|
149
|
+
case 'copy':
|
|
150
|
+
void window?.webContents.executeJavaScript("document.execCommand('copy')");
|
|
151
|
+
break;
|
|
152
|
+
case 'paste':
|
|
153
|
+
void window?.webContents.executeJavaScript("document.execCommand('paste')");
|
|
154
|
+
break;
|
|
155
|
+
case 'selectAll':
|
|
156
|
+
void window?.webContents.executeJavaScript("document.execCommand('selectAll')");
|
|
157
|
+
break;
|
|
158
|
+
default: break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function dispatchMenuClick(data) {
|
|
162
|
+
const id = data?.id;
|
|
163
|
+
if (!id)
|
|
164
|
+
return;
|
|
165
|
+
const item = menuItemsById.get(id);
|
|
166
|
+
if (!item)
|
|
167
|
+
return;
|
|
168
|
+
const window = data.windowId ? windowsById.get(data.windowId) : undefined;
|
|
169
|
+
runMenuItem(item, window);
|
|
170
|
+
}
|
|
171
|
+
// Popup del módulo `menu` → module.event; menubar del kernel → menu.click.
|
|
172
|
+
channel.on('menu.click', (params) => dispatchMenuClick(params));
|
|
173
|
+
channel.on('module.event', (params) => {
|
|
174
|
+
const name = params?.name;
|
|
175
|
+
// Re-emisión genérica: `app.__channel.on('screen.added', …)`.
|
|
176
|
+
if (typeof name === 'string')
|
|
177
|
+
channel.emit(name, params?.payload);
|
|
178
|
+
if (name === 'menu.click')
|
|
179
|
+
dispatchMenuClick(params.payload);
|
|
180
|
+
else if (name === 'tray.event' && currentTray)
|
|
181
|
+
currentTray.emit(params.payload?.button ?? 'click', params.payload);
|
|
182
|
+
// C10 — pantallas
|
|
183
|
+
else if (name === 'screen.added')
|
|
184
|
+
screen.emit('added', params.payload?.display);
|
|
185
|
+
else if (name === 'screen.removed')
|
|
186
|
+
screen.emit('removed', params.payload?.display);
|
|
187
|
+
else if (name === 'screen.changed')
|
|
188
|
+
screen.emit('changed', params.payload?.display, params.payload?.metrics ?? []);
|
|
189
|
+
// C10 — energía
|
|
190
|
+
else if (name === 'power.suspend')
|
|
191
|
+
powerMonitor.emit('suspend');
|
|
192
|
+
else if (name === 'power.resume')
|
|
193
|
+
powerMonitor.emit('resume');
|
|
194
|
+
else if (name === 'power.shutdown')
|
|
195
|
+
powerMonitor.emit('shutdown');
|
|
196
|
+
else if (name === 'power.lock')
|
|
197
|
+
powerMonitor.emit('lock');
|
|
198
|
+
else if (name === 'power.unlock')
|
|
199
|
+
powerMonitor.emit('unlock');
|
|
200
|
+
else if (name === 'power.ac') {
|
|
201
|
+
powerMonitor._setOnBattery(false);
|
|
202
|
+
powerMonitor.emit('ac');
|
|
203
|
+
}
|
|
204
|
+
else if (name === 'power.battery') {
|
|
205
|
+
powerMonitor._setOnBattery(true);
|
|
206
|
+
powerMonitor.emit('battery');
|
|
207
|
+
}
|
|
208
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Metadatos de un módulo nativo cargado en el kernel. */
|
|
2
|
+
export interface NativeModuleInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
/** Ruta del .owm o "builtin:<nombre>" para los enlazados al kernel. */
|
|
6
|
+
origin: string;
|
|
7
|
+
builtin: boolean;
|
|
8
|
+
/** Número de funciones (compat). */
|
|
9
|
+
functions: number;
|
|
10
|
+
/** Nombres de las funciones. */
|
|
11
|
+
functionNames: string[];
|
|
12
|
+
}
|
|
13
|
+
/** Módulos nativos cargados, con versión, origen y funciones. */
|
|
14
|
+
export declare function listNativeModules(): Promise<NativeModuleInfo[]>;
|
|
15
|
+
/** Metadatos de un módulo nativo concreto. Rechaza si no existe. */
|
|
16
|
+
export declare function nativeModuleInfo(name: string): Promise<NativeModuleInfo>;
|
package/dist/modules.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright 2026 Owear Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// @owear/core/src/modules.ts — introspección de los módulos nativos cargados.
|
|
5
|
+
import { channel } from './channel.js';
|
|
6
|
+
/** Módulos nativos cargados, con versión, origen y funciones. */
|
|
7
|
+
export function listNativeModules() {
|
|
8
|
+
return channel.call('module.list');
|
|
9
|
+
}
|
|
10
|
+
/** Metadatos de un módulo nativo concreto. Rechaza si no existe. */
|
|
11
|
+
export function nativeModuleInfo(name) {
|
|
12
|
+
return channel.call('module.info', { name });
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface Size {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Rectangle {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ResizeOptions {
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
quality?: 'good' | 'better' | 'best';
|
|
15
|
+
}
|
|
16
|
+
/** Imagen (estilo Electron `nativeImage`). */
|
|
17
|
+
export declare class NativeImage {
|
|
18
|
+
private _rgba;
|
|
19
|
+
private _fallback;
|
|
20
|
+
private _format;
|
|
21
|
+
private constructor();
|
|
22
|
+
static fromBuffer(buf: Buffer): NativeImage;
|
|
23
|
+
static fromDataURL(dataURL: string): NativeImage;
|
|
24
|
+
static fromPath(path: string): NativeImage;
|
|
25
|
+
isEmpty(): boolean;
|
|
26
|
+
getSize(): Size;
|
|
27
|
+
toPNG(): Buffer;
|
|
28
|
+
toJPEG(_quality?: number): Buffer;
|
|
29
|
+
toDataURL(): string;
|
|
30
|
+
resize(options: ResizeOptions): NativeImage;
|
|
31
|
+
crop(rect: Rectangle): NativeImage;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `nativeImage` (sin dependencias nativas). Síncrono como Electron para
|
|
35
|
+
* `createFrom*`/`getSize`/`toPNG`/`resize`/`crop`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const nativeImage: {
|
|
38
|
+
createFromPath(path: string): NativeImage;
|
|
39
|
+
createFromBuffer(buf: Buffer): NativeImage;
|
|
40
|
+
createFromDataURL(dataURL: string): NativeImage;
|
|
41
|
+
};
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// Copyright 2026 Owear Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// packages/core/src/nativeimage.ts — nativeImage sin dependencias nativas.
|
|
5
|
+
//
|
|
6
|
+
// Códec PNG completo (colorType 0/2/3/4/6, bitDepth 1/2/4/8/16, sin interlace)
|
|
7
|
+
// con el zlib de Node + resize bilineal y crop. Idéntico en Linux y Windows.
|
|
8
|
+
// JPEG: `getSize` (cabecera SOF) y `toJPEG` (devuelve los bytes si la fuente ya
|
|
9
|
+
// es JPEG); resize de JPEG no soportado.
|
|
10
|
+
import { inflateSync, deflateSync } from 'node:zlib';
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
const CRC_TABLE = (() => {
|
|
13
|
+
const t = new Uint32Array(256);
|
|
14
|
+
for (let n = 0; n < 256; n++) {
|
|
15
|
+
let c = n;
|
|
16
|
+
for (let k = 0; k < 8; k++)
|
|
17
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
18
|
+
t[n] = c >>> 0;
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
})();
|
|
22
|
+
function crc32(buf) {
|
|
23
|
+
let c = 0xffffffff;
|
|
24
|
+
for (let i = 0; i < buf.length; i++)
|
|
25
|
+
c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
26
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
27
|
+
}
|
|
28
|
+
function paeth(a, b, c) {
|
|
29
|
+
const p = a + b - c;
|
|
30
|
+
const pa = Math.abs(p - a);
|
|
31
|
+
const pb = Math.abs(p - b);
|
|
32
|
+
const pc = Math.abs(p - c);
|
|
33
|
+
return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
34
|
+
}
|
|
35
|
+
/** Tamaño a partir del IHDR (aunque no se pueda decodificar). */
|
|
36
|
+
function pngSize(buf) {
|
|
37
|
+
if (buf.length < 24 || buf[0] !== 0x89 || buf[1] !== 0x50)
|
|
38
|
+
return null;
|
|
39
|
+
const be = (i) => ((buf[i] << 24) | (buf[i + 1] << 16) | (buf[i + 2] << 8) | buf[i + 3]) >>> 0;
|
|
40
|
+
return { width: be(16), height: be(20) };
|
|
41
|
+
}
|
|
42
|
+
/** Decodifica PNG a RGBA (paleta y low bit-depth incluidos). */
|
|
43
|
+
function decodePNG(png) {
|
|
44
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
45
|
+
for (let i = 0; i < 8; i++)
|
|
46
|
+
if (png[i] !== sig[i])
|
|
47
|
+
return null;
|
|
48
|
+
let pos = 8;
|
|
49
|
+
let width = 0;
|
|
50
|
+
let height = 0;
|
|
51
|
+
let bitDepth = 0;
|
|
52
|
+
let colorType = 0;
|
|
53
|
+
let interlace = 0;
|
|
54
|
+
let palette = null;
|
|
55
|
+
let trns = null;
|
|
56
|
+
const idat = [];
|
|
57
|
+
while (pos + 8 <= png.length) {
|
|
58
|
+
const len = (((png[pos] << 24) | (png[pos + 1] << 16) | (png[pos + 2] << 8) | png[pos + 3]) >>> 0);
|
|
59
|
+
const type = String.fromCharCode(png[pos + 4], png[pos + 5], png[pos + 6], png[pos + 7]);
|
|
60
|
+
const data = png.subarray(pos + 8, pos + 8 + len);
|
|
61
|
+
if (type === 'IHDR') {
|
|
62
|
+
width = ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]) >>> 0;
|
|
63
|
+
height = ((data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]) >>> 0;
|
|
64
|
+
bitDepth = data[8];
|
|
65
|
+
colorType = data[9];
|
|
66
|
+
interlace = data[12];
|
|
67
|
+
}
|
|
68
|
+
else if (type === 'PLTE') {
|
|
69
|
+
palette = data;
|
|
70
|
+
}
|
|
71
|
+
else if (type === 'tRNS') {
|
|
72
|
+
trns = data;
|
|
73
|
+
}
|
|
74
|
+
else if (type === 'IDAT') {
|
|
75
|
+
idat.push(data);
|
|
76
|
+
}
|
|
77
|
+
else if (type === 'IEND') {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
pos += 12 + len;
|
|
81
|
+
}
|
|
82
|
+
if (interlace !== 0 || width === 0 || height === 0)
|
|
83
|
+
return null;
|
|
84
|
+
if (![1, 2, 4, 8, 16].includes(bitDepth))
|
|
85
|
+
return null;
|
|
86
|
+
if (colorType !== 0 && colorType !== 2 && colorType !== 3 && colorType !== 4 && colorType !== 6)
|
|
87
|
+
return null;
|
|
88
|
+
if (colorType === 3 && (!palette || bitDepth === 16))
|
|
89
|
+
return null;
|
|
90
|
+
const channels = colorType === 6 ? 4 : colorType === 2 ? 3 : colorType === 4 ? 2 : 1;
|
|
91
|
+
const bpp = Math.max(1, (channels * bitDepth) >> 3);
|
|
92
|
+
const stride = Math.ceil((width * channels * bitDepth) / 8);
|
|
93
|
+
const raw = (() => {
|
|
94
|
+
try {
|
|
95
|
+
return inflateSync(Buffer.concat(idat.map((d) => Buffer.from(d))));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
if (!raw || raw.length < (stride + 1) * height)
|
|
102
|
+
return null;
|
|
103
|
+
const maxVal = (1 << bitDepth) - 1;
|
|
104
|
+
const out = new Uint8Array(width * height * 4);
|
|
105
|
+
const prev = new Uint8Array(stride);
|
|
106
|
+
const cur = new Uint8Array(stride);
|
|
107
|
+
const sample = (row, index) => {
|
|
108
|
+
if (bitDepth === 8)
|
|
109
|
+
return row[index];
|
|
110
|
+
if (bitDepth === 16)
|
|
111
|
+
return row[index * 2]; // byte alto
|
|
112
|
+
const bitPos = index * bitDepth;
|
|
113
|
+
const byte = row[bitPos >> 3];
|
|
114
|
+
const shift = 8 - bitDepth - (bitPos & 7);
|
|
115
|
+
return (byte >> shift) & maxVal;
|
|
116
|
+
};
|
|
117
|
+
for (let y = 0; y < height; y++) {
|
|
118
|
+
const filter = raw[y * (stride + 1)];
|
|
119
|
+
const rowStart = y * (stride + 1) + 1;
|
|
120
|
+
for (let i = 0; i < stride; i++) {
|
|
121
|
+
const x = raw[rowStart + i];
|
|
122
|
+
const a = i >= bpp ? cur[i - bpp] : 0;
|
|
123
|
+
const b = prev[i];
|
|
124
|
+
const c = i >= bpp ? prev[i - bpp] : 0;
|
|
125
|
+
let v;
|
|
126
|
+
switch (filter) {
|
|
127
|
+
case 0:
|
|
128
|
+
v = x;
|
|
129
|
+
break;
|
|
130
|
+
case 1:
|
|
131
|
+
v = x + a;
|
|
132
|
+
break;
|
|
133
|
+
case 2:
|
|
134
|
+
v = x + b;
|
|
135
|
+
break;
|
|
136
|
+
case 3:
|
|
137
|
+
v = x + ((a + b) >> 1);
|
|
138
|
+
break;
|
|
139
|
+
case 4:
|
|
140
|
+
v = x + paeth(a, b, c);
|
|
141
|
+
break;
|
|
142
|
+
default: return null;
|
|
143
|
+
}
|
|
144
|
+
cur[i] = v & 0xff;
|
|
145
|
+
}
|
|
146
|
+
for (let x = 0; x < width; x++) {
|
|
147
|
+
const o = (y * width + x) * 4;
|
|
148
|
+
if (colorType === 3) {
|
|
149
|
+
const idx = sample(cur, x);
|
|
150
|
+
const pi = idx * 3;
|
|
151
|
+
out[o] = palette[pi];
|
|
152
|
+
out[o + 1] = palette[pi + 1];
|
|
153
|
+
out[o + 2] = palette[pi + 2];
|
|
154
|
+
out[o + 3] = trns && idx < trns.length ? trns[idx] : 255;
|
|
155
|
+
}
|
|
156
|
+
else if (colorType === 0 || colorType === 4) {
|
|
157
|
+
const g = sample(cur, x * (colorType === 4 ? 2 : 1));
|
|
158
|
+
const gg = bitDepth < 8 ? Math.round((g * 255) / maxVal) : g;
|
|
159
|
+
out[o] = out[o + 1] = out[o + 2] = gg;
|
|
160
|
+
out[o + 3] = colorType === 4 ? sample(cur, x * 2 + 1) : 255;
|
|
161
|
+
}
|
|
162
|
+
else if (colorType === 2) {
|
|
163
|
+
out[o] = sample(cur, x * 3);
|
|
164
|
+
out[o + 1] = sample(cur, x * 3 + 1);
|
|
165
|
+
out[o + 2] = sample(cur, x * 3 + 2);
|
|
166
|
+
out[o + 3] = 255;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
out[o] = sample(cur, x * 4);
|
|
170
|
+
out[o + 1] = sample(cur, x * 4 + 1);
|
|
171
|
+
out[o + 2] = sample(cur, x * 4 + 2);
|
|
172
|
+
out[o + 3] = sample(cur, x * 4 + 3);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
prev.set(cur);
|
|
176
|
+
}
|
|
177
|
+
return { width, height, data: out };
|
|
178
|
+
}
|
|
179
|
+
/** Codifica RGBA a PNG (colorType 6, bitDepth 8, filtro 0). */
|
|
180
|
+
function encodePNG(img) {
|
|
181
|
+
const { width, height, data } = img;
|
|
182
|
+
const stride = width * 4;
|
|
183
|
+
const raw = Buffer.alloc((stride + 1) * height);
|
|
184
|
+
for (let y = 0; y < height; y++) {
|
|
185
|
+
raw[y * (stride + 1)] = 0;
|
|
186
|
+
Buffer.from(data.buffer, data.byteOffset + y * stride, stride).copy(raw, y * (stride + 1) + 1);
|
|
187
|
+
}
|
|
188
|
+
const idat = deflateSync(raw);
|
|
189
|
+
const chunk = (type, body) => {
|
|
190
|
+
const len = Buffer.alloc(4);
|
|
191
|
+
len.writeUInt32BE(body.length, 0);
|
|
192
|
+
const t = Buffer.from(type, 'ascii');
|
|
193
|
+
const crc = Buffer.alloc(4);
|
|
194
|
+
crc.writeUInt32BE(crc32(Buffer.concat([t, body])), 0);
|
|
195
|
+
return Buffer.concat([len, t, body, crc]);
|
|
196
|
+
};
|
|
197
|
+
const ihdr = Buffer.alloc(13);
|
|
198
|
+
ihdr.writeUInt32BE(width, 0);
|
|
199
|
+
ihdr.writeUInt32BE(height, 4);
|
|
200
|
+
ihdr[8] = 8;
|
|
201
|
+
ihdr[9] = 6;
|
|
202
|
+
return Buffer.concat([
|
|
203
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
204
|
+
chunk('IHDR', ihdr),
|
|
205
|
+
chunk('IDAT', idat),
|
|
206
|
+
chunk('IEND', Buffer.alloc(0)),
|
|
207
|
+
]);
|
|
208
|
+
}
|
|
209
|
+
function resizeBilinear(src, dw, dh) {
|
|
210
|
+
const out = new Uint8Array(dw * dh * 4);
|
|
211
|
+
const sx = src.width / dw;
|
|
212
|
+
const sy = src.height / dh;
|
|
213
|
+
for (let y = 0; y < dh; y++) {
|
|
214
|
+
const fy = Math.min(src.height - 1, (y + 0.5) * sy - 0.5);
|
|
215
|
+
const y0 = Math.max(0, Math.floor(fy));
|
|
216
|
+
const y1 = Math.min(src.height - 1, y0 + 1);
|
|
217
|
+
const wy = fy - y0;
|
|
218
|
+
for (let x = 0; x < dw; x++) {
|
|
219
|
+
const fx = Math.min(src.width - 1, (x + 0.5) * sx - 0.5);
|
|
220
|
+
const x0 = Math.max(0, Math.floor(fx));
|
|
221
|
+
const x1 = Math.min(src.width - 1, x0 + 1);
|
|
222
|
+
const wx = fx - x0;
|
|
223
|
+
const o = (y * dw + x) * 4;
|
|
224
|
+
for (let c = 0; c < 4; c++) {
|
|
225
|
+
const p00 = src.data[(y0 * src.width + x0) * 4 + c];
|
|
226
|
+
const p10 = src.data[(y0 * src.width + x1) * 4 + c];
|
|
227
|
+
const p01 = src.data[(y1 * src.width + x0) * 4 + c];
|
|
228
|
+
const p11 = src.data[(y1 * src.width + x1) * 4 + c];
|
|
229
|
+
out[o + c] = Math.round(p00 * (1 - wx) * (1 - wy) + p10 * wx * (1 - wy) + p01 * (1 - wx) * wy + p11 * wx * wy);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { width: dw, height: dh, data: out };
|
|
234
|
+
}
|
|
235
|
+
/** Tamaño de un JPEG (marcador SOF). */
|
|
236
|
+
function jpegSize(buf) {
|
|
237
|
+
if (buf[0] !== 0xff || buf[1] !== 0xd8)
|
|
238
|
+
return null;
|
|
239
|
+
let i = 2;
|
|
240
|
+
while (i + 9 < buf.length) {
|
|
241
|
+
if (buf[i] !== 0xff) {
|
|
242
|
+
i++;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const marker = buf[i + 1];
|
|
246
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
|
|
247
|
+
i += 2;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const len = (buf[i + 2] << 8) | buf[i + 3];
|
|
251
|
+
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc)
|
|
252
|
+
return { height: (buf[i + 5] << 8) | buf[i + 6], width: (buf[i + 7] << 8) | buf[i + 8] };
|
|
253
|
+
i += 2 + len;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function detectFormat(buf) {
|
|
258
|
+
if (buf.length > 3 && buf[0] === 0x89 && buf[1] === 0x50)
|
|
259
|
+
return 'png';
|
|
260
|
+
if (buf.length > 2 && buf[0] === 0xff && buf[1] === 0xd8)
|
|
261
|
+
return 'jpeg';
|
|
262
|
+
return 'unknown';
|
|
263
|
+
}
|
|
264
|
+
/** Imagen (estilo Electron `nativeImage`). */
|
|
265
|
+
export class NativeImage {
|
|
266
|
+
_rgba;
|
|
267
|
+
_fallback;
|
|
268
|
+
_format;
|
|
269
|
+
constructor(rgba, fallback, format) {
|
|
270
|
+
this._rgba = rgba;
|
|
271
|
+
this._fallback = fallback;
|
|
272
|
+
this._format = format;
|
|
273
|
+
}
|
|
274
|
+
static fromBuffer(buf) {
|
|
275
|
+
const u8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
276
|
+
const format = detectFormat(u8);
|
|
277
|
+
if (format === 'png')
|
|
278
|
+
return new NativeImage(decodePNG(u8), buf, 'png');
|
|
279
|
+
return new NativeImage(null, buf, format);
|
|
280
|
+
}
|
|
281
|
+
static fromDataURL(dataURL) {
|
|
282
|
+
const comma = dataURL.indexOf(',');
|
|
283
|
+
if (comma < 0)
|
|
284
|
+
return new NativeImage(null, null, 'unknown');
|
|
285
|
+
return NativeImage.fromBuffer(Buffer.from(dataURL.slice(comma + 1), 'base64'));
|
|
286
|
+
}
|
|
287
|
+
static fromPath(path) {
|
|
288
|
+
try {
|
|
289
|
+
return NativeImage.fromBuffer(fs.readFileSync(path));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return new NativeImage(null, null, 'unknown');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
isEmpty() {
|
|
296
|
+
return !this._rgba && !this._fallback;
|
|
297
|
+
}
|
|
298
|
+
getSize() {
|
|
299
|
+
if (this._rgba)
|
|
300
|
+
return { width: this._rgba.width, height: this._rgba.height };
|
|
301
|
+
if (this._fallback) {
|
|
302
|
+
const u8 = new Uint8Array(this._fallback.buffer, this._fallback.byteOffset, this._fallback.byteLength);
|
|
303
|
+
const p = pngSize(u8);
|
|
304
|
+
if (p)
|
|
305
|
+
return p;
|
|
306
|
+
const j = jpegSize(u8);
|
|
307
|
+
if (j)
|
|
308
|
+
return j;
|
|
309
|
+
}
|
|
310
|
+
return { width: 0, height: 0 };
|
|
311
|
+
}
|
|
312
|
+
toPNG() {
|
|
313
|
+
if (this._format === 'png' && this._fallback)
|
|
314
|
+
return this._fallback;
|
|
315
|
+
if (!this._rgba)
|
|
316
|
+
throw new Error('nativeImage.toPNG: imagen no decodificable');
|
|
317
|
+
return encodePNG(this._rgba);
|
|
318
|
+
}
|
|
319
|
+
toJPEG(_quality = 80) {
|
|
320
|
+
if (this._format === 'jpeg' && this._fallback)
|
|
321
|
+
return this._fallback;
|
|
322
|
+
throw new Error('nativeImage.toJPEG: solo disponible si la fuente ya es JPEG');
|
|
323
|
+
}
|
|
324
|
+
toDataURL() {
|
|
325
|
+
if (this._format === 'jpeg' && this._fallback)
|
|
326
|
+
return `data:image/jpeg;base64,${this._fallback.toString('base64')}`;
|
|
327
|
+
const png = this._format === 'png' && this._fallback ? this._fallback : this.toPNG();
|
|
328
|
+
return `data:image/png;base64,${png.toString('base64')}`;
|
|
329
|
+
}
|
|
330
|
+
resize(options) {
|
|
331
|
+
if (!this._rgba)
|
|
332
|
+
throw new Error('nativeImage.resize: imagen no decodificable');
|
|
333
|
+
const size = this.getSize();
|
|
334
|
+
let w = options.width ?? 0;
|
|
335
|
+
let h = options.height ?? 0;
|
|
336
|
+
if (!w && !h)
|
|
337
|
+
return this;
|
|
338
|
+
if (!w)
|
|
339
|
+
w = Math.max(1, Math.round((size.width * h) / size.height));
|
|
340
|
+
if (!h)
|
|
341
|
+
h = Math.max(1, Math.round((size.height * w) / size.width));
|
|
342
|
+
return new NativeImage(resizeBilinear(this._rgba, w, h), null, 'png');
|
|
343
|
+
}
|
|
344
|
+
crop(rect) {
|
|
345
|
+
if (!this._rgba)
|
|
346
|
+
throw new Error('nativeImage.crop: imagen no decodificable');
|
|
347
|
+
const { x, y, width, height } = rect;
|
|
348
|
+
const out = new Uint8Array(width * height * 4);
|
|
349
|
+
for (let r = 0; r < height; r++) {
|
|
350
|
+
for (let c = 0; c < width; c++) {
|
|
351
|
+
const so = ((y + r) * this._rgba.width + (x + c)) * 4;
|
|
352
|
+
const dofs = (r * width + c) * 4;
|
|
353
|
+
out[dofs] = this._rgba.data[so];
|
|
354
|
+
out[dofs + 1] = this._rgba.data[so + 1];
|
|
355
|
+
out[dofs + 2] = this._rgba.data[so + 2];
|
|
356
|
+
out[dofs + 3] = this._rgba.data[so + 3];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return new NativeImage({ width, height, data: out }, null, 'png');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* `nativeImage` (sin dependencias nativas). Síncrono como Electron para
|
|
364
|
+
* `createFrom*`/`getSize`/`toPNG`/`resize`/`crop`.
|
|
365
|
+
*/
|
|
366
|
+
export const nativeImage = {
|
|
367
|
+
createFromPath(path) {
|
|
368
|
+
return NativeImage.fromPath(path);
|
|
369
|
+
},
|
|
370
|
+
createFromBuffer(buf) {
|
|
371
|
+
return NativeImage.fromBuffer(buf);
|
|
372
|
+
},
|
|
373
|
+
createFromDataURL(dataURL) {
|
|
374
|
+
return NativeImage.fromDataURL(dataURL);
|
|
375
|
+
},
|
|
376
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright 2026 Owear Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// packages/core/src/node/worker-runner.ts — arranque de un worker Node.
|
|
5
|
+
//
|
|
6
|
+
// Lo lanza `app.forkWorker()` (ver worker.ts). Instala el shim
|
|
7
|
+
// `process.parentPort` (estilo `utilityProcess` de Electron) para que los
|
|
8
|
+
// workers portados desde Electron funcionen SIN cambios, y carga el entry real.
|
|
9
|
+
//
|
|
10
|
+
// Invocación: node [execArgv] worker-runner.js <entry> [...args]
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
const parentPort = {
|
|
13
|
+
postMessage(message) {
|
|
14
|
+
// El canal IPC lo abre `child_process.fork`; si no existe, es un noop.
|
|
15
|
+
process.send?.(message);
|
|
16
|
+
},
|
|
17
|
+
on(event, listener) {
|
|
18
|
+
if (event === 'message') {
|
|
19
|
+
process.on('message', (message) => listener({ data: message }));
|
|
20
|
+
}
|
|
21
|
+
return parentPort;
|
|
22
|
+
},
|
|
23
|
+
start() {
|
|
24
|
+
/* noop: el canal IPC ya está activo */
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
process.parentPort ??= parentPort;
|
|
28
|
+
const entry = process.argv[2];
|
|
29
|
+
if (!entry) {
|
|
30
|
+
console.error('[ow] forkWorker: falta el entry del worker');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// El worker debe ver SU argv, no el del runner.
|
|
34
|
+
process.argv = [process.execPath, entry, ...process.argv.slice(3)];
|
|
35
|
+
await import(pathToFileURL(entry).href);
|