@owear/cli 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/package.json +10 -8
- package/src/ow.js +548 -25
- package/template/README.md +34 -28
- package/template/app/main.ts +273 -4
- package/template/index.html +117 -28
- package/template/package.json +1 -1
- package/template/public/favicon.svg +4 -0
- package/template/src/assets/dancing-script.woff2 +0 -0
- package/template/src/assets/jetbrains-mono.woff2 +0 -0
- package/template/src/ow.d.ts +9 -1
- package/template/src/renderer.ts +489 -1
- package/template/src/style.css +280 -56
- package/template-installer/README.md +38 -0
- package/template-installer/app/main.ts +22 -0
- package/template-installer/index.html +70 -0
- package/template-installer/package.json +22 -0
- package/template-installer/src/assets/dancing-script.woff2 +0 -0
- package/template-installer/src/assets/jetbrains-mono.woff2 +0 -0
- package/template-installer/src/installer.ts +171 -0
- package/template-installer/src/style.css +442 -0
- package/template-installer/tsconfig.json +13 -0
- package/template-installer/uninstaller/app/main.ts +16 -0
- package/template-installer/uninstaller/index.html +54 -0
- package/template-installer/uninstaller/package.json +22 -0
- package/template-installer/uninstaller/src/uninstaller.ts +101 -0
- package/template-installer/uninstaller/vite.config.ts +10 -0
- package/template-installer/vite.config.ts +15 -0
- package/tools/owear-installer.mjs +86 -0
- package/tools/owear-linux.mjs +158 -0
- package/tools/owear-msi.mjs +113 -0
- package/tools/owear-pack.mjs +93 -0
- package/tools/owear-sign.mjs +204 -0
- package/tools/owear-update.mjs +150 -0
- package/LICENSE +0 -201
package/template/README.md
CHANGED
|
@@ -3,54 +3,54 @@
|
|
|
3
3
|
|
|
4
4
|
# __APP_NAME__
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Desktop app built with **[Owear](https://owear.dev)**: the system WebView renders
|
|
7
|
+
your web frontend and the native kernel gives you the operating system.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Get started
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install
|
|
13
13
|
npm run dev # ≡ ow dev
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
hot-reload
|
|
16
|
+
A window with the system WebView opens. Edit `src/renderer.ts` and Vite's
|
|
17
|
+
hot-reload reflects it instantly.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm run build # ≡ ow build → dist/
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Structure
|
|
24
24
|
|
|
25
25
|
```
|
|
26
|
-
├── index.html UI (
|
|
26
|
+
├── index.html UI (custom title bar, hero, cards)
|
|
27
27
|
├── src/
|
|
28
|
-
│ ├── renderer.ts
|
|
29
|
-
│ ├── style.css
|
|
30
|
-
│ └── ow.d.ts
|
|
28
|
+
│ ├── renderer.ts renderer logic + calls to native modules
|
|
29
|
+
│ ├── style.css styles
|
|
30
|
+
│ └── ow.d.ts types for window.ow
|
|
31
31
|
└── app/
|
|
32
|
-
└── main.ts
|
|
32
|
+
└── main.ts main process (Node sidecar): just the window
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
##
|
|
35
|
+
## How it talks to the system
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
No `ipcRenderer` and no `ipcMain`: the renderer calls the kernel's native modules
|
|
38
|
+
directly.
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
|
-
const path = await ow.invoke('dialog', 'open', 'open', '
|
|
41
|
+
const path = await ow.invoke('dialog', 'open', 'open', 'Open a file')
|
|
42
42
|
const text = await ow.invoke('fs', 'readText', path)
|
|
43
|
-
await ow.invoke('notification', 'show', '
|
|
43
|
+
await ow.invoke('notification', 'show', 'Done', `Read ${text.length} bytes`)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
`
|
|
48
|
-
`globalshortcut` · `updater` · `capturer`,
|
|
49
|
-
`window` (extras
|
|
46
|
+
Stock modules: `fs` · `path` · `process` (PTY) · `dialog` · `clipboard` ·
|
|
47
|
+
`shell` · `screen` · `net` · `notification` · `power` · `menu` ·
|
|
48
|
+
`globalshortcut` · `updater` · `capturer`, plus the builtins `ow-window`
|
|
49
|
+
(window), `window` (WebView extras), `session`, `app`, and `crashreporter`.
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## Adding your own native module (C++)
|
|
52
52
|
|
|
53
|
-
1.
|
|
53
|
+
1. Create `native/my_module.cpp`:
|
|
54
54
|
|
|
55
55
|
```cpp
|
|
56
56
|
#include <ow/Json.h>
|
|
@@ -61,17 +61,23 @@ Módulos incluidos (stock): `fs` · `path` · `process` (PTY) · `dialog` ·
|
|
|
61
61
|
ow::Module::RespondOk(res, "\"pong\"");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
OW_MODULE_BEGIN(
|
|
64
|
+
OW_MODULE_BEGIN(my_module, "1.0.0")
|
|
65
65
|
OW_FN(ping)
|
|
66
66
|
OW_MODULE_END()
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
2. `ow dev`
|
|
69
|
+
2. `ow dev` compiles it to `.owm` and generates the binding. Use it in the
|
|
70
|
+
renderer:
|
|
70
71
|
|
|
71
72
|
```ts
|
|
72
|
-
import {
|
|
73
|
-
await
|
|
73
|
+
import { my_module } from '@owear/native'
|
|
74
|
+
await my_module.ping()
|
|
74
75
|
```
|
|
75
76
|
|
|
76
|
-
>
|
|
77
|
-
> `native/`
|
|
77
|
+
> Requires a C++ compiler on the system. If you do not have one, do not add
|
|
78
|
+
> `native/` and the app keeps working with the stock modules.
|
|
79
|
+
|
|
80
|
+
## Learn more
|
|
81
|
+
|
|
82
|
+
The full documentation lives in the Owear repository under
|
|
83
|
+
[`docs/`](https://github.com/owear/owear/tree/main/docs).
|
package/template/app/main.ts
CHANGED
|
@@ -3,21 +3,290 @@
|
|
|
3
3
|
//
|
|
4
4
|
// app/main.ts — proceso principal (sidecar Node), estilo Electron.
|
|
5
5
|
//
|
|
6
|
-
// Sólo gestiona el ciclo de vida de la ventana. La app
|
|
7
|
-
//
|
|
8
|
-
// lógica privilegiada (por ejemplo, exponerla vía un módulo nativo propio).
|
|
6
|
+
// Sólo gestiona el ciclo de vida de la ventana. La app vive en el renderer,
|
|
7
|
+
// que llama a los módulos nativos directo.
|
|
9
8
|
|
|
10
|
-
import { app, BrowserWindow } from '@owear/core'
|
|
9
|
+
import { app, BrowserWindow, Menu, Tray, invokeNative, nativeImage, theme, screen, powerMonitor, powerSaveBlocker } from '@owear/core'
|
|
10
|
+
import * as fs from 'node:fs'
|
|
11
|
+
import * as os from 'node:os'
|
|
12
|
+
import * as path from 'node:path'
|
|
11
13
|
|
|
12
14
|
app.whenReady().then(() => {
|
|
15
|
+
// Icono de la app: `favicon.svg` del proyecto (Vite lo copia a dist/ en build).
|
|
16
|
+
// `app.setIcon` fija el icono por defecto de las ventanas creadas después.
|
|
17
|
+
// (API de icono del kernel: `window.setIcon`.)
|
|
18
|
+
const iconPath = [
|
|
19
|
+
path.join(app.getAppPath(), 'favicon.svg'), // empaquetado: dist/ → app/
|
|
20
|
+
path.join(process.cwd(), 'public', 'favicon.svg'), // dev: public/ del proyecto
|
|
21
|
+
].find((p) => fs.existsSync(p))
|
|
22
|
+
if (iconPath) app.setIcon(iconPath)
|
|
23
|
+
|
|
13
24
|
const win = new BrowserWindow({
|
|
14
25
|
title: '__APP_NAME__',
|
|
15
26
|
width: 1080,
|
|
16
27
|
height: 720,
|
|
17
28
|
titleBarStyle: 'custom', // titlebar propia + regiones [data-ow-drag]
|
|
29
|
+
// Botones de ventana nativos (min/max/close) dentro de la titlebar custom.
|
|
30
|
+
// En Linux son los del TEMA (la distro define forma/tamaño/hover).
|
|
31
|
+
// Opcional: color = fondo de la banda · symbolColor = glifo ·
|
|
32
|
+
// buttonColor = fondo interno del círculo (si no, los del tema).
|
|
33
|
+
titleBarOverlay: { height: 40, color: '#21252b' },
|
|
18
34
|
url: process.env.OW_DEV_SERVER_URL ?? 'app://index.html',
|
|
19
35
|
})
|
|
20
36
|
|
|
21
37
|
win.on('ready-to-show', () => console.log('[main] ventana lista, id =', win.id))
|
|
22
38
|
win.on('closed', () => app.quit())
|
|
39
|
+
|
|
40
|
+
// ── C3: webContents (capturePage, eventos, setWindowOpenHandler) ─────────
|
|
41
|
+
app.handle('starter.capture', async () => {
|
|
42
|
+
const img = await win.webContents.capturePage()
|
|
43
|
+
return { data: img.toPNG().toString('base64') }
|
|
44
|
+
})
|
|
45
|
+
win.webContents.on('did-finish-load', () =>
|
|
46
|
+
void app.send('webContents.event', 'did-finish-load')
|
|
47
|
+
)
|
|
48
|
+
win.webContents.on('before-input-event', (e: { key?: string }) =>
|
|
49
|
+
void app.send('webContents.event', `before-input-event ${e?.key ?? ''}`)
|
|
50
|
+
)
|
|
51
|
+
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
52
|
+
void app.send('webContents.event', `window-open ${url}`)
|
|
53
|
+
return { action: 'deny' }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// ── C4: nativeImage (captura → resize/crop/toPNG/dataURL) ────────────────
|
|
57
|
+
app.handle('starter.nativeImage', async () => {
|
|
58
|
+
const shot = await win.webContents.capturePage() // NativeImage
|
|
59
|
+
const small = shot.resize({ width: 64 })
|
|
60
|
+
const fromBuffer = nativeImage.createFromBuffer(shot.toPNG())
|
|
61
|
+
const fromUrl = nativeImage.createFromDataURL(small.toDataURL())
|
|
62
|
+
const cropped = shot.crop({ x: 0, y: 0, width: 16, height: 16 })
|
|
63
|
+
return {
|
|
64
|
+
original: shot.getSize(),
|
|
65
|
+
resized: small.getSize(),
|
|
66
|
+
cropped: cropped.getSize(),
|
|
67
|
+
pngBytes: small.toPNG().length,
|
|
68
|
+
reDecoded: fromBuffer.getSize(),
|
|
69
|
+
fromDataURL: fromUrl.getSize(),
|
|
70
|
+
empty: small.isEmpty(),
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// ── C5: menu (contextual + menubar) ──────────────────────────────────────
|
|
75
|
+
// Menubar (Windows nativo; noop en Linux/GNOME por diseño).
|
|
76
|
+
Menu.setApplicationMenu(
|
|
77
|
+
Menu.buildFromTemplate([
|
|
78
|
+
{
|
|
79
|
+
label: 'Archivo',
|
|
80
|
+
submenu: [
|
|
81
|
+
{ label: 'Nuevo', accelerator: 'CmdOrCtrl+N', click: () => void app.send('menu.event', 'Nuevo') },
|
|
82
|
+
{ type: 'separator' },
|
|
83
|
+
{ role: 'quit' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: 'Ver',
|
|
88
|
+
submenu: [
|
|
89
|
+
{
|
|
90
|
+
label: 'Barra lateral',
|
|
91
|
+
type: 'checkbox',
|
|
92
|
+
checked: true,
|
|
93
|
+
click: (mi) => void app.send('menu.event', `sidebar=${mi.checked}`),
|
|
94
|
+
},
|
|
95
|
+
{ type: 'separator' },
|
|
96
|
+
{ role: 'toggleDevTools' },
|
|
97
|
+
{ role: 'reload' },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
])
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Menú contextual (popup) disparado por un botón del starter.
|
|
104
|
+
app.handle('starter.menu', () => {
|
|
105
|
+
Menu.buildFromTemplate([
|
|
106
|
+
{ label: 'Nuevo', click: () => void app.send('menu.event', 'Nuevo') },
|
|
107
|
+
{ label: 'Abrir', accelerator: 'CmdOrCtrl+O', click: () => void app.send('menu.event', 'Abrir') },
|
|
108
|
+
{ type: 'separator' },
|
|
109
|
+
{
|
|
110
|
+
label: 'Barra lateral',
|
|
111
|
+
type: 'checkbox',
|
|
112
|
+
checked: true,
|
|
113
|
+
click: (mi) => void app.send('menu.event', `checkbox=${mi.checked}`),
|
|
114
|
+
},
|
|
115
|
+
{ label: 'Zona A', type: 'radio', checked: true, click: () => void app.send('menu.event', 'radio A') },
|
|
116
|
+
{ label: 'Zona B', type: 'radio', click: () => void app.send('menu.event', 'radio B') },
|
|
117
|
+
{ type: 'separator' },
|
|
118
|
+
{ role: 'copy' },
|
|
119
|
+
{ role: 'toggleDevTools' },
|
|
120
|
+
{ role: 'quit' },
|
|
121
|
+
]).popup({ window: win })
|
|
122
|
+
return null
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// ── C6: tray (icono de bandeja) ──────────────────────────────────────────
|
|
126
|
+
let tray: Tray | null = null
|
|
127
|
+
app.handle('starter.tray', async () => {
|
|
128
|
+
if (tray) return { created: false, id: tray.id }
|
|
129
|
+
const icon = (await win.webContents.capturePage()).resize({ width: 16, height: 16 })
|
|
130
|
+
tray = new Tray(icon)
|
|
131
|
+
tray.setToolTip('__APP_NAME__')
|
|
132
|
+
tray.setTitle('__APP_NAME__')
|
|
133
|
+
tray.setContextMenu(
|
|
134
|
+
Menu.buildFromTemplate([
|
|
135
|
+
{ label: 'Mostrar ventana', click: () => void win.show() },
|
|
136
|
+
{ label: 'Capturar página', click: () => void app.send('tray.event', 'capturar') },
|
|
137
|
+
{ type: 'separator' },
|
|
138
|
+
{ role: 'quit' },
|
|
139
|
+
])
|
|
140
|
+
)
|
|
141
|
+
tray.on('click', () => {
|
|
142
|
+
void win.show()
|
|
143
|
+
void app.send('tray.event', 'click')
|
|
144
|
+
})
|
|
145
|
+
tray.on('right-click', () => void app.send('tray.event', 'right-click'))
|
|
146
|
+
tray.on('double-click', () => void app.send('tray.event', 'double-click'))
|
|
147
|
+
return { created: true, id: tray.id }
|
|
148
|
+
})
|
|
149
|
+
app.handle('starter.trayDestroy', () => {
|
|
150
|
+
tray?.destroy()
|
|
151
|
+
tray = null
|
|
152
|
+
return null
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// ── C7: nativeTheme (leer/forzar/escuchar) ───────────────────────────────
|
|
156
|
+
app.handle('starter.themeGet', () => theme.get())
|
|
157
|
+
app.handle('starter.themeSet', async (src: string) => {
|
|
158
|
+
await theme.setSource((src as 'system' | 'light' | 'dark') ?? 'system')
|
|
159
|
+
return theme.get()
|
|
160
|
+
})
|
|
161
|
+
void theme.watch() // cambios del sistema → ow.on('theme.changed')
|
|
162
|
+
|
|
163
|
+
// ── C8: print / printToPDF ───────────────────────────────────────────────
|
|
164
|
+
app.handle('starter.printPdf', async () => {
|
|
165
|
+
try {
|
|
166
|
+
const pdf = await win.webContents.printToPDF()
|
|
167
|
+
const out = path.join(os.tmpdir(), 'owear-starter.pdf')
|
|
168
|
+
fs.writeFileSync(out, pdf)
|
|
169
|
+
void invokeNative('shell', 'showItemInFolder', out)
|
|
170
|
+
return { ok: true, bytes: pdf.length, path: out }
|
|
171
|
+
} catch (e) {
|
|
172
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) }
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
app.handle('starter.print', () => {
|
|
176
|
+
win.webContents.print()
|
|
177
|
+
return null
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// ── C9: BrowserWindow completo ───────────────────────────────────────────
|
|
181
|
+
let child: BrowserWindow | null = null
|
|
182
|
+
app.handle('starter.childWindow', () => {
|
|
183
|
+
if (child) {
|
|
184
|
+
void child.focus()
|
|
185
|
+
return { id: child.id }
|
|
186
|
+
}
|
|
187
|
+
child = new BrowserWindow({
|
|
188
|
+
title: 'Ventana secundaria',
|
|
189
|
+
width: 520,
|
|
190
|
+
height: 360,
|
|
191
|
+
parent: win.id ?? undefined,
|
|
192
|
+
backgroundColor: '#12161c',
|
|
193
|
+
url: process.env.OW_DEV_SERVER_URL ?? 'app://index.html',
|
|
194
|
+
})
|
|
195
|
+
child.on('closed', () => {
|
|
196
|
+
child = null
|
|
197
|
+
})
|
|
198
|
+
return { id: child.id }
|
|
199
|
+
})
|
|
200
|
+
app.handle('starter.toggleOnTop', async () => {
|
|
201
|
+
const on = await win.isAlwaysOnTop()
|
|
202
|
+
await win.setAlwaysOnTop(!on)
|
|
203
|
+
return { on: !on }
|
|
204
|
+
})
|
|
205
|
+
app.handle('starter.progress', async () => {
|
|
206
|
+
await win.setProgressBar(0.5)
|
|
207
|
+
setTimeout(() => void win.setProgressBar(-1, { mode: 'none' }), 4000)
|
|
208
|
+
return null
|
|
209
|
+
})
|
|
210
|
+
app.handle('starter.ignoreMouse', () => {
|
|
211
|
+
void win.setIgnoreMouseEvents(true, { forward: true })
|
|
212
|
+
setTimeout(() => void win.setIgnoreMouseEvents(false), 3000)
|
|
213
|
+
return null
|
|
214
|
+
})
|
|
215
|
+
app.handle('starter.windows', async () => {
|
|
216
|
+
const focus = await BrowserWindow.getFocusedWindow()
|
|
217
|
+
const all = await BrowserWindow.getAllWindows()
|
|
218
|
+
return { focused: focus?.id ?? null, count: all.length, ids: all.map((w) => w.id) }
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// ── C10: pantallas (multi-monitor) y energía ─────────────────────────────
|
|
222
|
+
app.handle('starter.screens', async () => {
|
|
223
|
+
await screen.watch()
|
|
224
|
+
const displays = await screen.getAllDisplays()
|
|
225
|
+
const primary = displays.find((d) => d.primary) ?? displays[0] ?? null
|
|
226
|
+
const cursor = await screen.getCursorScreenPoint()
|
|
227
|
+
return { displays, primaryId: primary?.id ?? null, cursor }
|
|
228
|
+
})
|
|
229
|
+
app.handle('starter.nearest', async () => {
|
|
230
|
+
const cursor = await screen.getCursorScreenPoint()
|
|
231
|
+
const display = await screen.getDisplayNearestPoint(cursor)
|
|
232
|
+
return { cursor, display }
|
|
233
|
+
})
|
|
234
|
+
app.handle('starter.powerOn', async () => {
|
|
235
|
+
await powerMonitor.watch()
|
|
236
|
+
const onBattery = await powerMonitor.isOnBatteryPower()
|
|
237
|
+
const idle = await powerMonitor.getIdleTime()
|
|
238
|
+
return { onBattery, idle }
|
|
239
|
+
})
|
|
240
|
+
app.handle('starter.idle', async () => {
|
|
241
|
+
const idleTime = await powerMonitor.getIdleTime()
|
|
242
|
+
const state = await powerMonitor.getIdleState(30)
|
|
243
|
+
return { idleTime, state }
|
|
244
|
+
})
|
|
245
|
+
let blockerId: number | null = null
|
|
246
|
+
app.handle('starter.blocker', () => {
|
|
247
|
+
if (blockerId != null) {
|
|
248
|
+
const ok = powerSaveBlocker.stop(blockerId)
|
|
249
|
+
blockerId = null
|
|
250
|
+
return { on: false, ok }
|
|
251
|
+
}
|
|
252
|
+
blockerId = powerSaveBlocker.start('prevent-display-sleep')
|
|
253
|
+
return { on: true, id: blockerId }
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// ── C1: exponer `app` al renderer para probarlo en el starter ──────────────
|
|
258
|
+
// El renderer llama `ow.invoke('node','call',{fn:'starter.*'})`.
|
|
259
|
+
app.handle('starter.appInfo', () => ({
|
|
260
|
+
name: app.getName(),
|
|
261
|
+
version: app.getVersion(),
|
|
262
|
+
packaged: app.isPackaged(),
|
|
263
|
+
appPath: app.getAppPath(),
|
|
264
|
+
}))
|
|
265
|
+
|
|
266
|
+
const APP_PATH_NAMES = [
|
|
267
|
+
'home',
|
|
268
|
+
'userData',
|
|
269
|
+
'temp',
|
|
270
|
+
'logs',
|
|
271
|
+
'downloads',
|
|
272
|
+
'documents',
|
|
273
|
+
'exe',
|
|
274
|
+
'appPath',
|
|
275
|
+
] as const
|
|
276
|
+
|
|
277
|
+
app.handle('starter.appPaths', () => {
|
|
278
|
+
const out: Record<string, string> = {}
|
|
279
|
+
for (const name of APP_PATH_NAMES) out[name] = app.getPath(name)
|
|
280
|
+
return out
|
|
23
281
|
})
|
|
282
|
+
|
|
283
|
+
// Ciclo de vida de la app → renderer (app.send → ow.on('app.event')).
|
|
284
|
+
for (const ev of [
|
|
285
|
+
'window-all-closed',
|
|
286
|
+
'before-quit',
|
|
287
|
+
'will-quit',
|
|
288
|
+
'second-instance',
|
|
289
|
+
'child-process-gone',
|
|
290
|
+
] as const) {
|
|
291
|
+
app.on(ev, () => app.send('app.event', ev))
|
|
292
|
+
}
|
package/template/index.html
CHANGED
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
|
|
32
32
|
<main class="page">
|
|
33
33
|
<section class="hero">
|
|
34
|
-
<span class="eyebrow">Owear</span>
|
|
35
34
|
<h1>__APP_NAME__</h1>
|
|
36
35
|
<p class="lead">
|
|
37
36
|
Frontend web, sistema nativo. Estos botones llaman <strong>directo</strong> a los
|
|
@@ -39,6 +38,28 @@
|
|
|
39
38
|
</p>
|
|
40
39
|
<div class="actions">
|
|
41
40
|
<button class="btn btn--primary" data-action="open">Abrir archivo…</button>
|
|
41
|
+
<button class="btn" data-action="multi">Abrir varios…</button>
|
|
42
|
+
<button class="btn" data-action="save">Guardar como…</button>
|
|
43
|
+
<button class="btn" data-action="capture">Capturar página</button>
|
|
44
|
+
<button class="btn" data-action="nimage">nativeImage</button>
|
|
45
|
+
<button class="btn" data-action="menu">Menú contextual</button>
|
|
46
|
+
<button class="btn" data-action="tray">Tray</button>
|
|
47
|
+
<button class="btn btn--ghost" data-action="theme-system">Tema: sistema</button>
|
|
48
|
+
<button class="btn btn--ghost" data-action="theme-light">Claro</button>
|
|
49
|
+
<button class="btn btn--ghost" data-action="theme-dark">Oscuro</button>
|
|
50
|
+
<button class="btn" data-action="pdf">Exportar PDF</button>
|
|
51
|
+
<button class="btn" data-action="print">Imprimir</button>
|
|
52
|
+
<button class="btn" data-action="child">Ventana 2</button>
|
|
53
|
+
<button class="btn" data-action="ontop">AlwaysOnTop</button>
|
|
54
|
+
<button class="btn" data-action="prog">Progreso</button>
|
|
55
|
+
<button class="btn" data-action="ignore">Ignorar ratón</button>
|
|
56
|
+
<button class="btn" data-action="wins">Ventanas</button>
|
|
57
|
+
<button class="btn" data-action="screens">Pantallas</button>
|
|
58
|
+
<button class="btn" data-action="nearest">Monitor del cursor</button>
|
|
59
|
+
<button class="btn btn--ghost" data-action="power-on">Escuchar energía</button>
|
|
60
|
+
<button class="btn btn--ghost" data-action="blocker">Bloqueador ON/OFF</button>
|
|
61
|
+
<button class="btn btn--ghost" data-action="idle">Idle</button>
|
|
62
|
+
<button class="btn" data-action="ask">Diálogo con botones</button>
|
|
42
63
|
<button class="btn" data-action="notify">Notificar</button>
|
|
43
64
|
<button class="btn" data-action="copy">Copiar ruta</button>
|
|
44
65
|
<button class="btn" data-action="reveal">Mostrar en carpeta</button>
|
|
@@ -46,37 +67,105 @@
|
|
|
46
67
|
</div>
|
|
47
68
|
</section>
|
|
48
69
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
<!-- Contenedor con fondo propio que agrupa las cards -->
|
|
71
|
+
<section class="board">
|
|
72
|
+
<section class="grid">
|
|
73
|
+
<article class="card">
|
|
74
|
+
<header class="card__head">
|
|
75
|
+
<h2>Entorno</h2>
|
|
76
|
+
<span class="badge" id="env-window">—</span>
|
|
77
|
+
</header>
|
|
78
|
+
<dl class="kv">
|
|
79
|
+
<div><dt>Plataforma</dt><dd id="env-platform">—</dd></div>
|
|
80
|
+
<div><dt>Motor</dt><dd id="env-engine">—</dd></div>
|
|
81
|
+
<div><dt>Escala</dt><dd id="env-dpr">—</dd></div>
|
|
82
|
+
</dl>
|
|
83
|
+
<p class="ua" id="env-ua">—</p>
|
|
84
|
+
</article>
|
|
62
85
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
<article class="card">
|
|
87
|
+
<header class="card__head">
|
|
88
|
+
<h2>App <span class="hint">C1</span></h2>
|
|
89
|
+
<span class="badge" id="app-version">—</span>
|
|
90
|
+
</header>
|
|
91
|
+
<dl class="kv">
|
|
92
|
+
<div><dt>Nombre</dt><dd id="app-name">—</dd></div>
|
|
93
|
+
<div><dt>Empaquetada</dt><dd id="app-packaged">—</dd></div>
|
|
94
|
+
<div><dt>userData</dt><dd id="app-path-userData" class="mono">—</dd></div>
|
|
95
|
+
<div><dt>exe</dt><dd id="app-path-exe" class="mono">—</dd></div>
|
|
96
|
+
<div><dt>appPath</dt><dd id="app-path-appPath" class="mono">—</dd></div>
|
|
97
|
+
</dl>
|
|
98
|
+
</article>
|
|
71
99
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
<article class="card card--grow">
|
|
101
|
+
<header class="card__head">
|
|
102
|
+
<h2>Consola</h2>
|
|
103
|
+
<button class="linkbtn" data-action="clear">Limpiar</button>
|
|
104
|
+
</header>
|
|
105
|
+
<pre id="console" class="console">Listo. Pulsa «Abrir archivo…» para invocar un módulo nativo.</pre>
|
|
106
|
+
</article>
|
|
107
|
+
</section>
|
|
108
|
+
|
|
109
|
+
<section class="card">
|
|
110
|
+
<header class="card__head"><h2>Cómo funciona</h2></header>
|
|
111
|
+
<p class="hint">
|
|
112
|
+
Cada botón invoca un módulo nativo con <code>ow.invoke()</code>.
|
|
113
|
+
</p>
|
|
114
|
+
<pre class="code"><code>const path = await ow.invoke('dialog', 'open', 'open', 'Abrir archivo')
|
|
78
115
|
const text = await ow.invoke('fs', 'readText', path)
|
|
79
116
|
await ow.invoke('notification', 'show', 'Listo', 'Leídos ' + text.length + ' bytes')</code></pre>
|
|
117
|
+
</section>
|
|
118
|
+
</section>
|
|
119
|
+
|
|
120
|
+
<!-- C10: pantallas (multi-monitor) y energía -->
|
|
121
|
+
<section class="board">
|
|
122
|
+
<header class="card__head">
|
|
123
|
+
<h2>Pantallas y energía <span class="hint">C10</span></h2>
|
|
124
|
+
<span class="badge" id="mon-count">—</span>
|
|
125
|
+
</header>
|
|
126
|
+
<section class="grid">
|
|
127
|
+
<article class="card">
|
|
128
|
+
<header class="card__head">
|
|
129
|
+
<h2>Monitores</h2>
|
|
130
|
+
<span class="badge" id="mon-primary">—</span>
|
|
131
|
+
</header>
|
|
132
|
+
<div id="mon-map" class="monmap">
|
|
133
|
+
<span class="monmap__hint">Pulsa «Pantallas» para mapear los monitores.</span>
|
|
134
|
+
</div>
|
|
135
|
+
<div id="mon-list" class="monlist"></div>
|
|
136
|
+
</article>
|
|
137
|
+
<article class="card card--grow">
|
|
138
|
+
<header class="card__head">
|
|
139
|
+
<h2>Energía</h2>
|
|
140
|
+
<span class="badge" id="power-status">—</span>
|
|
141
|
+
</header>
|
|
142
|
+
<p class="hint">
|
|
143
|
+
Escucha suspend/resume, AC/batería y bloqueo de sesión. El bloqueador
|
|
144
|
+
impide que se apague la pantalla.
|
|
145
|
+
</p>
|
|
146
|
+
<pre id="power-log" class="console console--short">Sin eventos todavía.</pre>
|
|
147
|
+
</article>
|
|
148
|
+
</section>
|
|
149
|
+
</section>
|
|
150
|
+
|
|
151
|
+
<!-- Webview embebida: un navegador nativo hijo de la ventana (proceso
|
|
152
|
+
propio), controlado por la API webview.* y colocado sobre #wv-slot. -->
|
|
153
|
+
<section class="board">
|
|
154
|
+
<header class="card__head">
|
|
155
|
+
<h2>Navegador embebido</h2>
|
|
156
|
+
<span class="badge" id="wv-status">—</span>
|
|
157
|
+
</header>
|
|
158
|
+
<div class="browser">
|
|
159
|
+
<div class="browser__bar">
|
|
160
|
+
<button class="iconbtn" id="wv-back" title="Atrás" aria-label="Atrás">‹</button>
|
|
161
|
+
<button class="iconbtn" id="wv-fwd" title="Adelante" aria-label="Adelante">›</button>
|
|
162
|
+
<button class="iconbtn" id="wv-reload" title="Recargar" aria-label="Recargar">⟳</button>
|
|
163
|
+
<input id="wv-url" class="browser__url" type="text"
|
|
164
|
+
value="https://scrakk.art" spellcheck="false" />
|
|
165
|
+
<button class="btn btn--primary" id="wv-go">Ir</button>
|
|
166
|
+
</div>
|
|
167
|
+
<div id="wv-slot" class="browser__slot"></div>
|
|
168
|
+
</div>
|
|
80
169
|
</section>
|
|
81
170
|
</main>
|
|
82
171
|
|
package/template/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "App de escritorio construida con Owear.",
|
|
6
|
+
"description": "App de escritorio construida con Owear (Starter: titlebar propia, módulos nativos y UI real).",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "ow dev",
|
|
9
9
|
"build": "ow build",
|
|
Binary file
|
|
Binary file
|
package/template/src/ow.d.ts
CHANGED
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
//
|
|
4
4
|
// Tipos de la API que el kernel inyecta en cada documento (`window.ow`).
|
|
5
|
-
//
|
|
5
|
+
// Types maintained by hand against docs/api/renderer.md.
|
|
6
6
|
|
|
7
7
|
export {}
|
|
8
8
|
|
|
9
9
|
declare global {
|
|
10
10
|
interface Window {
|
|
11
11
|
__owWindowId: number
|
|
12
|
+
/** Presente cuando la ventana usa `titleBarOverlay` (botones nativos). */
|
|
13
|
+
__owTitlebarOverlay?: {
|
|
14
|
+
enabled: boolean
|
|
15
|
+
height: number
|
|
16
|
+
width: number
|
|
17
|
+
top?: number
|
|
18
|
+
right?: number
|
|
19
|
+
}
|
|
12
20
|
ow: {
|
|
13
21
|
invoke(module: string, fn: string, ...args: unknown[]): Promise<unknown>
|
|
14
22
|
invokeSync(module: string, fn: string, ...args: unknown[]): unknown
|