Initial Neutralino.js POC for kokoro-widget

Lightweight alternative to the Electron build (~5MB vs ~300MB).
Uses system webview (WebKit2GTK on Linux, WKWebView on macOS,
WebView2 on Windows). Same UI and feature set as the Electron build:
WebSocket audio client, volume control, host/port settings, tray icon,
hide-to-tray on close, CLI args (--host, --port, --volume).

Note: Linux requires libwebkit2gtk-4.0 or libwebkit2gtk-4.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nonna
2026-03-11 00:44:45 +00:00
commit 2c369d74b1
9 changed files with 1175 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
# Developer tools' files
.lite_workspace.lua
# Neutralinojs binaries and builds
/bin
/dist
# Neutralinojs client (minified)
neutralino.js
# Neutralinojs related files
.storage
*.log

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Neutralinojs
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

6
README.md Normal file
View File

@@ -0,0 +1,6 @@
# neutralinojs-zero
An empty Neutralinojs app, extend as you wish
```
neu create myapp --template neutralinojs/neutralinojs-zero
```

38
neutralino.config.json Normal file
View File

@@ -0,0 +1,38 @@
{
"applicationId": "com.pai.kokoro-widget",
"version": "1.0.0",
"defaultMode": "window",
"documentRoot": "/www/",
"url": "/",
"enableServer": true,
"enableNativeAPI": true,
"nativeAllowList": [
"app.*",
"os.*",
"storage.*",
"window.*",
"events.*",
"debug.*"
],
"modes": {
"window": {
"title": "PAI Voice",
"width": 360,
"height": 540,
"minWidth": 300,
"minHeight": 400,
"icon": "/www/icon.png",
"alwaysOnTop": false,
"borderless": false,
"maximize": false
}
},
"cli": {
"binaryName": "kokoro-widget",
"resourcesPath": "/www/",
"extensionsPath": "/extensions/",
"clientLibrary": "/www/neutralino.js",
"binaryVersion": "6.5.0",
"clientVersion": "6.5.0"
}
}

12
package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "kokoro-widget-neutralino",
"version": "1.0.0",
"description": "Standalone PAI Voice audio widget — lightweight Neutralino.js build",
"scripts": {
"start": "neu run",
"build": "neu build"
},
"dependencies": {
"@neutralinojs/neu": "^11.7.0"
}
}

334
www/app.js Normal file
View File

@@ -0,0 +1,334 @@
'use strict';
// ---------------------------------------------------------------------------
// Config defaults and storage key
// ---------------------------------------------------------------------------
const CONFIG_DEFAULTS = { host: 'localhost', port: 8888, volume: 80 };
const STORAGE_KEY = 'kokoro-config';
// ---------------------------------------------------------------------------
// State
// ---------------------------------------------------------------------------
let config = { ...CONFIG_DEFAULTS };
let ws = null;
let reconnectTimer = null;
const audioQueue = [];
const AUDIO_QUEUE_MAX = 25;
let isPlaying = false;
let consecutiveAudioErrors = 0;
const AUDIO_ERROR_LIMIT = 5;
let volumeSaveTimer = null;
// DOM refs
let statusDot, statusText, speakingIndicator, lastMessage;
let volumeSlider, hostInput, portInput, saveBtn;
// ---------------------------------------------------------------------------
// Config persistence via Neutralino storage
// ---------------------------------------------------------------------------
async function loadConfig() {
try {
const raw = await Neutralino.storage.getData(STORAGE_KEY);
return Object.assign({}, CONFIG_DEFAULTS, JSON.parse(raw));
} catch {
return { ...CONFIG_DEFAULTS };
}
}
async function saveConfig(partial) {
config = Object.assign({}, config, partial);
try {
await Neutralino.storage.setData(STORAGE_KEY, JSON.stringify(config));
} catch (e) {
console.error('Failed to save config:', e);
}
return config;
}
// ---------------------------------------------------------------------------
// CLI argument parsing — Neutralino exposes args via NL_ARGS global
// ---------------------------------------------------------------------------
function parseCliArgs() {
const overrides = {};
// NL_ARGS is a space-joined string of all args passed after --
const args = (typeof NL_ARGS !== 'undefined' ? NL_ARGS : '').split(' ').filter(Boolean);
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--host':
if (args[i + 1]) overrides.host = args[++i];
break;
case '--port': {
const p = parseInt(args[++i], 10);
if (!isNaN(p)) overrides.port = p;
break;
}
case '--volume': {
const v = parseInt(args[++i], 10);
if (!isNaN(v)) overrides.volume = Math.min(100, Math.max(0, v));
break;
}
}
}
return overrides;
}
// ---------------------------------------------------------------------------
// Tray setup
// ---------------------------------------------------------------------------
async function setupTray() {
try {
await Neutralino.os.setTray({
icon: '/www/icon.png',
menuItems: [
{ id: 'tray_show', text: 'Show' },
{ id: 'tray_sep', text: '-' },
{ id: 'tray_quit', text: 'Quit' },
],
});
} catch (e) {
console.warn('Tray setup failed (may not be supported on this platform):', e);
}
}
// Handle tray menu clicks
Neutralino.events.on('trayMenuItemClicked', async (evt) => {
switch (evt.detail.id) {
case 'tray_show':
await Neutralino.window.show();
await Neutralino.window.focus();
break;
case 'tray_quit':
await Neutralino.app.exit();
break;
}
});
// Intercept window close — hide to tray instead of quitting
Neutralino.events.on('windowClose', async () => {
await Neutralino.window.hide();
});
// ---------------------------------------------------------------------------
// Status / speaking display
// ---------------------------------------------------------------------------
function setStatus(state, text) {
statusDot.className = 'status-dot ' + state;
statusText.textContent = text;
}
function setSpeaking(active) {
speakingIndicator.classList.toggle('active', active);
}
// ---------------------------------------------------------------------------
// WebSocket connection
// ---------------------------------------------------------------------------
function getWsUrl() {
return `ws://${config.host}:${config.port}/stream`;
}
function connect() {
if (ws) {
ws.onopen = null;
ws.onmessage = null;
ws.onclose = null;
ws.onerror = null;
ws.close();
ws = null;
}
clearReconnectTimer();
setStatus('connecting', 'Connecting...');
try {
ws = new WebSocket(getWsUrl());
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
setStatus('connected', 'Connected — waiting for audio');
};
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'notification' && msg.message) {
lastMessage.textContent = msg.message;
setStatus('connected', 'Connected');
}
} catch {
// ignore malformed JSON
}
} else if (event.data instanceof ArrayBuffer) {
setStatus('connected', 'Connected');
if (audioQueue.length < AUDIO_QUEUE_MAX) {
audioQueue.push(event.data);
if (!isPlaying) playNext();
} else {
console.warn('Audio queue full — dropping frame');
}
}
};
ws.onclose = () => {
const hadError = ws && ws._hadError;
ws = null;
setStatus(hadError ? 'error' : 'connecting',
hadError ? 'Disconnected — retrying...' : 'Reconnecting...');
scheduleReconnect();
};
ws.onerror = () => {
if (ws) ws._hadError = true;
setStatus('error', 'Connection error');
};
} catch (e) {
console.error('WebSocket connection failed:', e);
setStatus('error', 'Connection error');
scheduleReconnect();
}
}
function scheduleReconnect() {
clearReconnectTimer();
reconnectTimer = setTimeout(() => {
if (ws === null) connect();
}, 3000);
}
function clearReconnectTimer() {
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
}
// ---------------------------------------------------------------------------
// Audio playback queue
// ---------------------------------------------------------------------------
async function playNext() {
if (audioQueue.length === 0) {
isPlaying = false;
setSpeaking(false);
return;
}
isPlaying = true;
setSpeaking(true);
const buffer = audioQueue.shift();
let url = null;
try {
const blob = new Blob([buffer], { type: 'audio/mpeg' });
url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.volume = Math.min(1, Math.max(0, config.volume / 100));
audio.onended = () => {
URL.revokeObjectURL(url);
consecutiveAudioErrors = 0;
playNext();
};
audio.onerror = (e) => {
console.error('Audio error:', e);
URL.revokeObjectURL(url);
consecutiveAudioErrors++;
if (consecutiveAudioErrors >= AUDIO_ERROR_LIMIT) {
isPlaying = false;
setSpeaking(false);
setStatus('error', 'Audio error');
audioQueue.length = 0;
return;
}
playNext();
};
await audio.play();
consecutiveAudioErrors = 0;
} catch (e) {
console.error('Audio playback failed:', e);
if (url) URL.revokeObjectURL(url);
consecutiveAudioErrors++;
if (consecutiveAudioErrors >= AUDIO_ERROR_LIMIT) {
isPlaying = false;
setSpeaking(false);
setStatus('error', 'Audio error');
audioQueue.length = 0;
return;
}
playNext();
}
}
// ---------------------------------------------------------------------------
// Initialization
// ---------------------------------------------------------------------------
async function init() {
// Wire DOM refs
statusDot = document.getElementById('statusDot');
statusText = document.getElementById('statusText');
speakingIndicator = document.getElementById('speakingIndicator');
lastMessage = document.getElementById('lastMessage');
volumeSlider = document.getElementById('volume');
hostInput = document.getElementById('hostInput');
portInput = document.getElementById('portInput');
saveBtn = document.getElementById('saveBtn');
// Load persisted config then apply CLI overrides (CLI does not persist)
config = await loadConfig();
const cliOverrides = parseCliArgs();
if (cliOverrides.host !== undefined) config.host = cliOverrides.host;
if (cliOverrides.port !== undefined) config.port = cliOverrides.port;
if (cliOverrides.volume !== undefined) config.volume = cliOverrides.volume;
// Populate UI
volumeSlider.value = config.volume;
hostInput.value = config.host;
portInput.value = config.port;
// Volume: live update + debounce save
volumeSlider.addEventListener('input', () => {
config.volume = parseInt(volumeSlider.value, 10);
clearTimeout(volumeSaveTimer);
volumeSaveTimer = setTimeout(() => saveConfig({ volume: config.volume }), 500);
});
// Save button
saveBtn.addEventListener('click', async () => {
const newHost = hostInput.value.trim() || 'localhost';
const newPort = parseInt(portInput.value, 10);
const validPort = (!isNaN(newPort) && newPort > 0 && newPort <= 65535) ? newPort : 8888;
hostInput.value = newHost;
portInput.value = validPort;
await saveConfig({ host: newHost, port: validPort });
saveBtn.textContent = 'Saved!';
saveBtn.classList.add('saved');
setTimeout(() => {
saveBtn.textContent = 'Save';
saveBtn.classList.remove('saved');
}, 1500);
connect();
});
// Draggable header
try {
await Neutralino.window.setDraggableRegion('dragRegion');
} catch (e) {
console.warn('setDraggableRegion not supported:', e);
}
// Set up tray
await setupTray();
// Connect to voice server
connect();
}
// Boot after Neutralino is ready
Neutralino.init();
document.addEventListener('DOMContentLoaded', init);

BIN
www/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

220
www/index.html Normal file
View File

@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PAI Voice</title>
<style>
:root {
--bg-primary: #0d1220;
--bg-secondary: #141c2c;
--bg-tertiary: #1c2638;
--text-primary: #f0f2f5;
--text-secondary: #c0c8d4;
--text-muted: #8a919d;
--cyan: #12c2e9;
--pink: #ff6b9d;
--teal: #2dd4bf;
--orange: #F39C12;
--border: rgba(255, 255, 255, 0.08);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
user-select: none;
}
.voice-panel {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 16px;
padding: 1.5rem 2rem;
width: 300px;
text-align: center;
}
.panel-header {
cursor: move;
margin-bottom: 0.25rem;
padding-bottom: 0.25rem;
}
.voice-panel h1 {
font-size: 1.2rem;
font-weight: 700;
color: var(--cyan);
margin-bottom: 0;
}
.voice-panel .subtitle {
font-size: 0.75rem;
color: var(--text-muted);
margin-bottom: 1.25rem;
}
.status {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--text-muted);
transition: background 0.3s, box-shadow 0.3s;
flex-shrink: 0;
}
.status-dot.connected { background: var(--teal); box-shadow: 0 0 8px rgba(45,212,191,0.4); }
.status-dot.connecting { background: var(--orange); animation: pulse 1.5s infinite; }
.status-dot.error { background: var(--pink); }
.status-text { color: var(--text-secondary); font-size: 0.85rem; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.speaking-indicator {
height: 36px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-bottom: 0.75rem;
opacity: 0;
transition: opacity 0.3s;
}
.speaking-indicator.active { opacity: 1; }
.speaking-bar { width: 4px; height: 8px; background: var(--cyan); border-radius: 2px; }
.speaking-indicator.active .speaking-bar { animation: bars 0.8s ease-in-out infinite; }
.speaking-bar:nth-child(1) { animation-delay: 0s; }
.speaking-bar:nth-child(2) { animation-delay: 0.1s; }
.speaking-bar:nth-child(3) { animation-delay: 0.2s; }
.speaking-bar:nth-child(4) { animation-delay: 0.3s; }
.speaking-bar:nth-child(5) { animation-delay: 0.2s; }
.speaking-bar:nth-child(6) { animation-delay: 0.1s; }
.speaking-bar:nth-child(7) { animation-delay: 0s; }
@keyframes bars {
0%, 100% { height: 8px; }
50% { height: 26px; }
}
.last-message {
font-size: 0.78rem;
color: var(--text-muted);
min-height: 1.4em;
margin-bottom: 1rem;
font-style: italic;
line-height: 1.4;
word-break: break-word;
}
.divider { border: none; border-top: 1px solid var(--border); margin: 0 0 1rem 0; }
.volume-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; }
.volume-row label {
font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase;
letter-spacing: 0.05em; white-space: nowrap; min-width: 3rem; text-align: left;
}
.volume-row input[type="range"] { flex: 1; accent-color: var(--cyan); cursor: pointer; }
.settings-section { border-top: 1px solid var(--border); padding-top: 1rem; text-align: left; }
.settings-section h3 {
font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase;
letter-spacing: 0.08em; margin-bottom: 0.75rem;
}
.settings-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.settings-row label {
font-size: 0.75rem; color: var(--text-secondary); min-width: 2.5rem; text-align: right;
}
.settings-row input {
flex: 1; background: var(--bg-tertiary); border: 1px solid var(--border);
border-radius: 6px; padding: 0.35rem 0.5rem; color: var(--text-primary);
font-size: 0.8rem; font-family: inherit; outline: none; transition: border-color 0.2s;
}
.settings-row input:focus { border-color: rgba(18,194,233,0.5); }
.settings-row input[type="number"]::-webkit-outer-spin-button,
.settings-row input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.save-row { display: flex; justify-content: flex-end; margin-top: 0.75rem; }
#saveBtn {
background: var(--cyan); color: #0d1220; border: none; border-radius: 6px;
padding: 0.4rem 1.1rem; font-size: 0.8rem; font-weight: 600; font-family: inherit;
cursor: pointer; transition: opacity 0.2s;
}
#saveBtn:hover { opacity: 0.85; }
#saveBtn:active { opacity: 0.7; }
#saveBtn.saved { background: var(--teal); }
</style>
</head>
<body>
<div class="voice-panel">
<div class="panel-header" id="dragRegion">
<h1>PAI Voice</h1>
<p class="subtitle">Voice notifications from your AI assistant</p>
</div>
<div class="status">
<span class="status-dot connecting" id="statusDot"></span>
<span class="status-text" id="statusText">Connecting...</span>
</div>
<div class="speaking-indicator" id="speakingIndicator">
<div class="speaking-bar"></div>
<div class="speaking-bar"></div>
<div class="speaking-bar"></div>
<div class="speaking-bar"></div>
<div class="speaking-bar"></div>
<div class="speaking-bar"></div>
<div class="speaking-bar"></div>
</div>
<div class="last-message" id="lastMessage">Waiting for notifications...</div>
<hr class="divider">
<div class="volume-row">
<label for="volume">Volume</label>
<input type="range" id="volume" min="0" max="100" value="80">
</div>
<div class="settings-section">
<h3>Settings</h3>
<div class="settings-row">
<label for="hostInput">Host</label>
<input type="text" id="hostInput" placeholder="localhost" value="localhost">
</div>
<div class="settings-row">
<label for="portInput">Port</label>
<input type="number" id="portInput" placeholder="8888" value="8888" min="1" max="65535">
</div>
<div class="save-row">
<button id="saveBtn">Save</button>
</div>
</div>
</div>
<script src="neutralino.js"></script>
<script src="app.js"></script>
</body>
</html>

531
www/neutralino.d.ts vendored Normal file
View File

@@ -0,0 +1,531 @@
export declare enum LoggerType {
WARNING = "WARNING",
ERROR = "ERROR",
INFO = "INFO"
}
export declare enum Icon {
WARNING = "WARNING",
ERROR = "ERROR",
INFO = "INFO",
QUESTION = "QUESTION"
}
export declare enum MessageBoxChoice {
OK = "OK",
OK_CANCEL = "OK_CANCEL",
YES_NO = "YES_NO",
YES_NO_CANCEL = "YES_NO_CANCEL",
RETRY_CANCEL = "RETRY_CANCEL",
ABORT_RETRY_IGNORE = "ABORT_RETRY_IGNORE"
}
export declare enum ClipboardFormat {
unknown = "unknown",
text = "text",
image = "image"
}
export declare enum Mode {
window = "window",
browser = "browser",
cloud = "cloud",
chrome = "chrome"
}
export declare enum OperatingSystem {
Linux = "Linux",
Windows = "Windows",
Darwin = "Darwin",
FreeBSD = "FreeBSD",
Unknown = "Unknown"
}
export declare enum Architecture {
x64 = "x64",
arm = "arm",
itanium = "itanium",
ia32 = "ia32",
unknown = "unknown"
}
export interface DirectoryEntry {
entry: string;
path: string;
type: string;
}
export interface FileReaderOptions {
pos: number;
size: number;
}
export interface DirectoryReaderOptions {
recursive: boolean;
}
export interface OpenedFile {
id: number;
eof: boolean;
pos: number;
lastRead: number;
}
export interface Stats {
size: number;
isFile: boolean;
isDirectory: boolean;
createdAt: number;
modifiedAt: number;
}
export interface Watcher {
id: number;
path: string;
}
export interface CopyOptions {
recursive: boolean;
overwrite: boolean;
skip: boolean;
}
export interface PathParts {
rootName: string;
rootDirectory: string;
rootPath: string;
relativePath: string;
parentPath: string;
filename: string;
stem: string;
extension: string;
}
interface Permissions$1 {
all: boolean;
ownerAll: boolean;
ownerRead: boolean;
ownerWrite: boolean;
ownerExec: boolean;
groupAll: boolean;
groupRead: boolean;
groupWrite: boolean;
groupExec: boolean;
othersAll: boolean;
othersRead: boolean;
othersWrite: boolean;
othersExec: boolean;
}
export type PermissionsMode = "ADD" | "REPLACE" | "REMOVE";
declare function createDirectory(path: string): Promise<void>;
declare function remove(path: string): Promise<void>;
declare function writeFile(path: string, data: string): Promise<void>;
declare function appendFile(path: string, data: string): Promise<void>;
declare function writeBinaryFile(path: string, data: ArrayBuffer): Promise<void>;
declare function appendBinaryFile(path: string, data: ArrayBuffer): Promise<void>;
declare function readFile(path: string, options?: FileReaderOptions): Promise<string>;
declare function readBinaryFile(path: string, options?: FileReaderOptions): Promise<ArrayBuffer>;
declare function openFile(path: string): Promise<number>;
declare function createWatcher(path: string): Promise<number>;
declare function removeWatcher(id: number): Promise<number>;
declare function getWatchers(): Promise<Watcher[]>;
declare function updateOpenedFile(id: number, event: string, data?: any): Promise<void>;
declare function getOpenedFileInfo(id: number): Promise<OpenedFile>;
declare function readDirectory(path: string, options?: DirectoryReaderOptions): Promise<DirectoryEntry[]>;
declare function copy(source: string, destination: string, options?: CopyOptions): Promise<void>;
declare function move(source: string, destination: string): Promise<void>;
declare function getStats(path: string): Promise<Stats>;
declare function getAbsolutePath(path: string): Promise<string>;
declare function getRelativePath(path: string, base?: string): Promise<string>;
declare function getPathParts(path: string): Promise<PathParts>;
declare function getPermissions(path: string): Promise<Permissions$1>;
declare function setPermissions(path: string, permissions: Permissions$1, mode: PermissionsMode): Promise<void>;
declare function getJoinedPath(...paths: string[]): Promise<string>;
declare function getNormalizedPath(path: string): Promise<string>;
declare function getUnnormalizedPath(path: string): Promise<string>;
export interface ExecCommandOptions {
stdIn?: string;
background?: boolean;
cwd?: string;
}
export interface ExecCommandResult {
pid: number;
stdOut: string;
stdErr: string;
exitCode: number;
}
export interface SpawnedProcess {
id: number;
pid: number;
}
export interface SpawnedProcessOptions {
cwd?: string;
envs?: Record<string, string>;
}
export interface Envs {
[key: string]: string;
}
export interface OpenDialogOptions {
multiSelections?: boolean;
filters?: Filter[];
defaultPath?: string;
}
export interface FolderDialogOptions {
defaultPath?: string;
}
export interface SaveDialogOptions {
forceOverwrite?: boolean;
filters?: Filter[];
defaultPath?: string;
}
export interface Filter {
name: string;
extensions: string[];
}
export interface TrayOptions {
icon: string;
menuItems: TrayMenuItem[];
}
export interface TrayMenuItem {
id?: string;
text: string;
isDisabled?: boolean;
isChecked?: boolean;
}
export type KnownPath = "config" | "data" | "cache" | "documents" | "pictures" | "music" | "video" | "downloads" | "savedGames1" | "savedGames2" | "temp";
declare function execCommand(command: string, options?: ExecCommandOptions): Promise<ExecCommandResult>;
declare function spawnProcess(command: string, options?: SpawnedProcessOptions): Promise<SpawnedProcess>;
declare function updateSpawnedProcess(id: number, event: string, data?: any): Promise<void>;
declare function getSpawnedProcesses(): Promise<SpawnedProcess[]>;
declare function getEnv(key: string): Promise<string>;
declare function getEnvs(): Promise<Envs>;
declare function showOpenDialog(title?: string, options?: OpenDialogOptions): Promise<string[]>;
declare function showFolderDialog(title?: string, options?: FolderDialogOptions): Promise<string>;
declare function showSaveDialog(title?: string, options?: SaveDialogOptions): Promise<string>;
declare function showNotification(title: string, content: string, icon?: Icon): Promise<void>;
declare function showMessageBox(title: string, content: string, choice?: MessageBoxChoice, icon?: Icon): Promise<string>;
declare function setTray(options: TrayOptions): Promise<void>;
declare function open$1(url: string): Promise<void>;
declare function getPath(name: KnownPath): Promise<string>;
export interface MemoryInfo {
physical: {
total: number;
available: number;
};
virtual: {
total: number;
available: number;
};
}
export interface KernelInfo {
variant: string;
version: string;
}
export interface OSInfo {
name: string;
description: string;
version: string;
}
export interface CPUInfo {
vendor: string;
model: string;
frequency: number;
architecture: string;
logicalThreads: number;
physicalCores: number;
physicalUnits: number;
}
export interface Display {
id: number;
resolution: Resolution;
dpi: number;
bpp: number;
refreshRate: number;
}
export interface Resolution {
width: number;
height: number;
}
export interface MousePosition {
x: number;
y: number;
}
declare function getMemoryInfo(): Promise<MemoryInfo>;
declare function getArch(): Promise<string>;
declare function getKernelInfo(): Promise<KernelInfo>;
declare function getOSInfo(): Promise<OSInfo>;
declare function getCPUInfo(): Promise<CPUInfo>;
declare function getDisplays(): Promise<Display[]>;
declare function getMousePosition(): Promise<MousePosition>;
declare function setData(key: string, data: string | null): Promise<void>;
declare function getData(key: string): Promise<string>;
declare function removeData(key: string): Promise<void>;
declare function getKeys(): Promise<string[]>;
declare function clear(): Promise<void>;
declare function log(message: string, type?: LoggerType): Promise<void>;
export interface OpenActionOptions {
url: string;
}
export interface RestartOptions {
args: string;
}
declare function exit(code?: number): Promise<void>;
declare function killProcess(): Promise<void>;
declare function restartProcess(options?: RestartOptions): Promise<void>;
declare function getConfig(): Promise<any>;
declare function broadcast(event: string, data?: any): Promise<void>;
declare function readProcessInput(readAll?: boolean): Promise<string>;
declare function writeProcessOutput(data: string): Promise<void>;
declare function writeProcessError(data: string): Promise<void>;
export interface WindowOptions extends WindowSizeOptions, WindowPosOptions {
title?: string;
icon?: string;
fullScreen?: boolean;
alwaysOnTop?: boolean;
enableInspector?: boolean;
borderless?: boolean;
maximize?: boolean;
hidden?: boolean;
maximizable?: boolean;
useSavedState?: boolean;
exitProcessOnClose?: boolean;
extendUserAgentWith?: string;
injectGlobals?: boolean;
injectClientLibrary?: boolean;
injectScript?: string;
processArgs?: string;
}
export interface WindowSizeOptions {
width?: number;
height?: number;
minWidth?: number;
minHeight?: number;
maxWidth?: number;
maxHeight?: number;
resizable?: boolean;
}
export interface WindowPosOptions {
x?: number;
y?: number;
center?: boolean;
}
export interface WindowMenu extends Array<WindowMenuItem> {
}
export interface WindowMenuItem {
id?: string;
text: string;
action?: string;
shortcut?: string;
isDisabled?: boolean;
isChecked?: boolean;
menuItems?: WindowMenuItem[];
}
declare function setTitle(title: string): Promise<void>;
declare function getTitle(): Promise<string>;
declare function maximize(): Promise<void>;
declare function unmaximize(): Promise<void>;
declare function isMaximized(): Promise<boolean>;
declare function minimize(): Promise<void>;
declare function unminimize(): Promise<void>;
declare function isMinimized(): Promise<boolean>;
declare function setFullScreen(): Promise<void>;
declare function exitFullScreen(): Promise<void>;
declare function isFullScreen(): Promise<boolean>;
declare function show(): Promise<void>;
declare function hide(): Promise<void>;
declare function isVisible(): Promise<boolean>;
declare function focus$1(): Promise<void>;
declare function setIcon(icon: string): Promise<void>;
declare function move$1(x: number, y: number): Promise<void>;
declare function center(): Promise<void>;
declare function beginDrag(screenX?: number, screenY?: number): Promise<void>;
declare function setDraggableRegion(DOMElementOrId: string | HTMLElement, options?: {
exclude?: Array<string | HTMLElement>;
}): Promise<{
success: true;
message: string;
exclusions: {
add(elements: Array<string | HTMLElement>): void;
remove(elements: Array<string | HTMLElement>): void;
removeAll(): void;
};
}>;
declare function unsetDraggableRegion(DOMElementOrId: string | HTMLElement): Promise<{
success: true;
message: string;
}>;
declare function setSize(options: WindowSizeOptions): Promise<void>;
declare function getSize(): Promise<WindowSizeOptions>;
declare function getPosition(): Promise<WindowPosOptions>;
declare function setAlwaysOnTop(onTop: boolean): Promise<void>;
declare function setBorderless(borderless: boolean): Promise<void>;
declare function create(url: string, options?: WindowOptions): Promise<void>;
declare function snapshot(path: string): Promise<void>;
declare function setMainMenu(options: WindowMenu): Promise<void>;
declare function print$1(): Promise<void>;
interface Response$1 {
success: boolean;
message: string;
}
export type Builtin = "ready" | "trayMenuItemClicked" | "windowClose" | "serverOffline" | "clientConnect" | "clientDisconnect" | "appClientConnect" | "appClientDisconnect" | "extClientConnect" | "extClientDisconnect" | "extensionReady" | "neuDev_reloadApp";
declare function on(event: string, handler: (ev: CustomEvent) => void): Promise<Response$1>;
declare function off(event: string, handler: (ev: CustomEvent) => void): Promise<Response$1>;
declare function dispatch(event: string, data?: any): Promise<Response$1>;
declare function broadcast$1(event: string, data?: any): Promise<void>;
export interface ExtensionStats {
loaded: string[];
connected: string[];
}
declare function dispatch$1(extensionId: string, event: string, data?: any): Promise<void>;
declare function broadcast$2(event: string, data?: any): Promise<void>;
declare function getStats$1(): Promise<ExtensionStats>;
export interface Manifest {
applicationId: string;
version: string;
resourcesURL: string;
}
declare function checkForUpdates(url: string): Promise<Manifest>;
declare function install(): Promise<void>;
export interface ClipboardImage {
width: number;
height: number;
bpp: number;
bpr: number;
redMask: number;
greenMask: number;
blueMask: number;
redShift: number;
greenShift: number;
blueShift: number;
data: ArrayBuffer;
}
declare function getFormat(): Promise<ClipboardFormat>;
declare function readText(): Promise<string>;
declare function readImage(format?: string): Promise<ClipboardImage | null>;
declare function writeText(data: string): Promise<void>;
declare function writeImage(image: ClipboardImage): Promise<void>;
declare function readHTML(): Promise<string>;
declare function writeHTML(data: string): Promise<void>;
declare function clear$1(): Promise<void>;
interface Stats$1 {
size: number;
isFile: boolean;
isDirectory: boolean;
}
declare function getFiles(): Promise<string[]>;
declare function getStats$2(path: string): Promise<Stats$1>;
declare function extractFile(path: string, destination: string): Promise<void>;
declare function extractDirectory(path: string, destination: string): Promise<void>;
declare function readFile$1(path: string): Promise<string>;
declare function readBinaryFile$1(path: string): Promise<ArrayBuffer>;
declare function mount(path: string, target: string): Promise<void>;
declare function unmount(path: string): Promise<void>;
declare function getMounts(): Promise<Record<string, string>>;
declare function getMethods(): Promise<string[]>;
export interface InitOptions {
exportCustomMethods?: boolean;
}
export declare function init(options?: InitOptions): void;
export type ErrorCode = "NE_FS_DIRCRER" | "NE_FS_RMDIRER" | "NE_FS_FILRDER" | "NE_FS_FILWRER" | "NE_FS_FILRMER" | "NE_FS_NOPATHE" | "NE_FS_COPYFER" | "NE_FS_MOVEFER" | "NE_OS_INVMSGA" | "NE_OS_INVKNPT" | "NE_ST_INVSTKY" | "NE_ST_STKEYWE" | "NE_RT_INVTOKN" | "NE_RT_NATPRME" | "NE_RT_APIPRME" | "NE_RT_NATRTER" | "NE_RT_NATNTIM" | "NE_CL_NSEROFF" | "NE_EX_EXTNOTC" | "NE_UP_CUPDMER" | "NE_UP_CUPDERR" | "NE_UP_UPDNOUF" | "NE_UP_UPDINER";
interface Error$1 {
code: ErrorCode;
message: string;
}
declare global {
interface Window {
/** Mode of the application: window, browser, cloud, or chrome */
NL_MODE: Mode;
/** Application port */
NL_PORT: number;
/** Command-line arguments */
NL_ARGS: string[];
/** Basic authentication token */
NL_TOKEN: string;
/** Neutralinojs client version */
NL_CVERSION: string;
/** Application identifier */
NL_APPID: string;
/** Application version */
NL_APPVERSION: string;
/** Application path */
NL_PATH: string;
/** Application data path */
NL_DATAPATH: string;
/** Returns true if extensions are enabled */
NL_EXTENABLED: boolean;
/** Returns true if the client library is injected */
NL_GINJECTED: boolean;
/** Returns true if globals are injected */
NL_CINJECTED: boolean;
/** Operating system name: Linux, Windows, Darwin, FreeBSD, or Uknown */
NL_OS: OperatingSystem;
/** CPU architecture: x64, arm, itanium, ia32, or unknown */
NL_ARCH: Architecture;
/** Neutralinojs server version */
NL_VERSION: string;
/** Current working directory */
NL_CWD: string;
/** Identifier of the current process */
NL_PID: string;
/** Source of application resources: bundle or directory */
NL_RESMODE: string;
/** Release commit of the client library */
NL_CCOMMIT: string;
/** An array of custom methods */
NL_CMETHODS: string[];
}
/** Neutralino global object for custom methods **/
const Neutralino: any;
}
declare namespace custom {
export { getMethods };
}
declare namespace filesystem {
export { appendBinaryFile, appendFile, copy, createDirectory, createWatcher, getAbsolutePath, getJoinedPath, getNormalizedPath, getOpenedFileInfo, getPathParts, getPermissions, getRelativePath, getStats, getUnnormalizedPath, getWatchers, move, openFile, readBinaryFile, readDirectory, readFile, remove, removeWatcher, setPermissions, updateOpenedFile, writeBinaryFile, writeFile };
}
declare namespace os {
export { execCommand, getEnv, getEnvs, getPath, getSpawnedProcesses, open$1 as open, setTray, showFolderDialog, showMessageBox, showNotification, showOpenDialog, showSaveDialog, spawnProcess, updateSpawnedProcess };
}
declare namespace computer {
export { getArch, getCPUInfo, getDisplays, getKernelInfo, getMemoryInfo, getMousePosition, getOSInfo };
}
declare namespace storage {
export { clear, getData, getKeys, removeData, setData };
}
declare namespace debug {
export { log };
}
declare namespace app {
export { broadcast, exit, getConfig, killProcess, readProcessInput, restartProcess, writeProcessError, writeProcessOutput };
}
declare namespace window$1 {
export { beginDrag, center, create, exitFullScreen, focus$1 as focus, getPosition, getSize, getTitle, hide, isFullScreen, isMaximized, isMinimized, isVisible, maximize, minimize, move$1 as move, print$1 as print, setAlwaysOnTop, setBorderless, setDraggableRegion, setFullScreen, setIcon, setMainMenu, setSize, setTitle, show, snapshot, unmaximize, unminimize, unsetDraggableRegion };
}
declare namespace events {
export { broadcast$1 as broadcast, dispatch, off, on };
}
declare namespace extensions {
export { broadcast$2 as broadcast, dispatch$1 as dispatch, getStats$1 as getStats };
}
declare namespace updater {
export { checkForUpdates, install };
}
declare namespace clipboard {
export { clear$1 as clear, getFormat, readHTML, readImage, readText, writeHTML, writeImage, writeText };
}
declare namespace resources {
export { extractDirectory, extractFile, getFiles, getStats$2 as getStats, readBinaryFile$1 as readBinaryFile, readFile$1 as readFile };
}
declare namespace server {
export { getMounts, mount, unmount };
}
export {
Error$1 as Error,
Permissions$1 as Permissions,
Response$1 as Response,
app,
clipboard,
computer,
custom,
debug,
events,
extensions,
filesystem,
os,
resources,
server,
storage,
updater,
window$1 as window,
};
export as namespace Neutralino;
export {};