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:
334
www/app.js
Normal file
334
www/app.js
Normal 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
BIN
www/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
220
www/index.html
Normal file
220
www/index.html
Normal 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
531
www/neutralino.d.ts
vendored
Normal 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 {};
|
||||
Reference in New Issue
Block a user