From 11d7d0ecd3b328adb84264be5287fa42808f9749 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Tue, 10 Feb 2026 13:44:20 +0100 Subject: [PATCH 1/2] WIP: neutralino --- .github/workflows/desktop-build.yml | 16 +-- .gitignore | Bin 137 -> 162 bytes js/app.js | 54 +++++----- js/discord-rpc.js | 7 +- js/neutralino-bridge.js | 80 +++++++++++++++ neutralino.config.json | 17 ++-- package.json | 6 +- public/neutralino.js | 1 + public/neutralino_loader.html | 147 ++++++++++++++++++++++++++++ vite.config.js | 4 +- 10 files changed, 285 insertions(+), 47 deletions(-) create mode 100644 js/neutralino-bridge.js create mode 100644 public/neutralino.js create mode 100644 public/neutralino_loader.html diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index ff7a028..3f44b1c 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -37,27 +37,27 @@ jobs: with: fetch-depth: 1 - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v1 with: - node-version: '20' - cache: 'npm' + bun-version: latest - name: Install dependencies - run: npm install + run: bun install - name: Download Neutralino binaries - run: npx neu update + run: bun x neu update - name: Build application - run: npm run build:desktop + run: bun run build - name: Prepare Release run: | mkdir release cp dist/Monochrome/resources.neu release/ cp neutralino.config.json release/ - cp -r extensions release/ + # Extensions are already copied to dist/Monochrome/extensions by postbuild + cp -r dist/Monochrome/extensions release/ cp dist/Monochrome/${{ matrix.binary_source }} release/${{ matrix.binary_dest }} shell: bash diff --git a/.gitignore b/.gitignore index c1b2e4dac6afd184522430e77dacca4aeeffe2e2..db600234084f529ed4f699c72ea247fc410856a2 100644 GIT binary patch delta 40 ucmeBVT*NrRaiX)c92Y|#Ln=clLkU9>Ln1>CLne@)&!ESU#Zb(^%LM?+>Ik6# delta 13 UcmZ3)*vUA-aiYB|GXoa`031RCApigX diff --git a/js/app.js b/js/app.js index bdb80cd..c5635f7 100644 --- a/js/app.js +++ b/js/app.js @@ -1,5 +1,5 @@ //js/app.js -console.log('[App] Script loaded'); +console.log('[App] Script loaded. Query:', window.location.search); import { LosslessAPI } from './api.js'; import { apiSettings, @@ -22,11 +22,12 @@ import { db } from './db.js'; import { syncManager } from './accounts/pocketbase.js'; import { registerSW } from 'virtual:pwa-register'; import { initializeDiscordRPC } from './discord-rpc.js'; -import * as Neutralino from '@neutralinojs/lib'; +import * as Neutralino from './neutralino-bridge.js'; import './smooth-scrolling.js'; // Assign Neutralino to window for global access -if (typeof window !== 'undefined' && window.NL_MODE) { +// Force global assignment for Bridge mode +if (typeof window !== 'undefined') { window.Neutralino = Neutralino; } @@ -238,30 +239,34 @@ async function disablePwaForAuthGate() { } document.addEventListener('DOMContentLoaded', async () => { - // Initialize desktop environment (Neutralino) - const isDesktop = typeof window !== 'undefined' && (window.NL_MODE || window.location.port === '5050'); - if (typeof window !== 'undefined' && window.Neutralino) { - console.log('[App] Neutralino object detected. Environment:', isDesktop ? 'Desktop' : 'Web'); - if (isDesktop) { - console.log('[App] Initializing Neutralino desktop environment...'); - try { - Neutralino.init(); - console.log('[App] Neutralino.init() called successfully.'); + // Delay detection slightly to allow for global injection + await new Promise((resolve) => setTimeout(resolve, 100)); + + const initNeutralino = async () => { + const urlParams = new URLSearchParams(window.location.search); + const isNeutralino = urlParams.get('mode') === 'neutralino'; + + if (isNeutralino) { + try { + // Bridge init is instant and doesn't need tokens/ports + Neutralino.init(); + console.log('[App] Neutralino Bridge initialized.'); - // Register events immediately Neutralino.events.on('windowClose', () => { - console.log('[App] Window close event triggered.'); Neutralino.app.exit(); }); - } catch (error) { - console.error('[App] Failed to initialize desktop environment:', error); + + // Initialize Discord RPC + console.log('[App] Starting Discord RPC...'); + initializeDiscordRPC(player); + + } catch (e) { + console.error('[App] Neutralino init failed:', e); } - } else { - console.log('[App] Skipping Neutralino.init() on regular web environment.'); } - } else { - console.log('[App] Neutralino object NOT detected.'); - } + }; + + const api = new LosslessAPI(apiSettings); @@ -411,10 +416,9 @@ document.addEventListener('DOMContentLoaded', async () => { // Initialize tracker initTracker(player); - if (typeof window !== 'undefined' && window.Neutralino && (window.NL_MODE || window.location.port === '5050')) { - console.log('[App] Starting Discord RPC...'); - initializeDiscordRPC(player); - } + + + initNeutralino(); const castBtn = document.getElementById('cast-btn'); initializeCasting(audioPlayer, castBtn); diff --git a/js/discord-rpc.js b/js/discord-rpc.js index eb2708f..31cdff1 100644 --- a/js/discord-rpc.js +++ b/js/discord-rpc.js @@ -1,4 +1,5 @@ import { getTrackTitle, getTrackArtists } from './utils.js'; +import * as Neutralino from './neutralino-bridge.js'; export function initializeDiscordRPC(player) { console.log('[DiscordRPC] Initializing...'); @@ -51,8 +52,8 @@ export function initializeDiscordRPC(player) { smallImageKey: 'pause', smallImageText: 'Paused', }; - Neutralino.events.broadcast('discord:update', idlingData).catch(() => {}); - Neutralino.extensions.dispatch(EXTENSION_ID, 'discord:update', idlingData).catch(() => {}); + Neutralino.events.broadcast('discord:update', idlingData).catch(() => { }); + Neutralino.extensions.dispatch(EXTENSION_ID, 'discord:update', idlingData).catch(() => { }); } }, 5000); @@ -83,6 +84,6 @@ export function initializeDiscordRPC(player) { smallImageKey: 'pause', smallImageText: 'Paused', }) - .catch(() => {}); + .catch(() => { }); } } diff --git a/js/neutralino-bridge.js b/js/neutralino-bridge.js new file mode 100644 index 0000000..6f459fb --- /dev/null +++ b/js/neutralino-bridge.js @@ -0,0 +1,80 @@ +// js/neutralino-bridge.js + +const listeners = new Map(); + +// Listen for events from the Shell (Parent) +window.addEventListener('message', (event) => { + if (event.data?.type === 'NL_EVENT') { + const { eventName, detail } = event.data; + if (listeners.has(eventName)) { + listeners.get(eventName).forEach((handler) => { + try { + handler(detail); + } catch (e) { + console.error('[Bridge] Error in event handler:', e); + } + }); + } + } +}); + +export const init = async () => { + console.log('[Bridge] Initialized. Mode: Iframe Shell.'); + // Notify Shell we are ready + window.parent.postMessage({ type: 'NL_INIT' }, '*'); +}; + +export const events = { + on: (eventName, handler) => { + if (!listeners.has(eventName)) { + listeners.set(eventName, []); + } + listeners.get(eventName).push(handler); + }, + off: (eventName, handler) => { + if (!listeners.has(eventName)) return; + const handlers = listeners.get(eventName); + const index = handlers.indexOf(handler); + if (index > -1) handlers.splice(index, 1); + }, + broadcast: async (eventName, data) => { + window.parent.postMessage({ type: 'NL_BROADCAST', eventName, data }, '*'); + }, +}; + +export const extensions = { + dispatch: async (extensionId, eventName, data) => { + window.parent.postMessage({ type: 'NL_EXTENSION', extensionId, eventName, data }, '*'); + }, +}; + +export const app = { + exit: async () => { + window.parent.postMessage({ type: 'NL_APP_EXIT' }, '*'); + }, +}; + +const _window = { + minimize: async () => { + window.parent.postMessage({ type: 'NL_WINDOW_MIN' }, '*'); + }, + maximize: async () => { + window.parent.postMessage({ type: 'NL_WINDOW_MAX' }, '*'); + }, + isVisible: async () => { + return true; // Mock response + }, + setTitle: async (title) => { + window.parent.postMessage({ type: 'NL_WINDOW_SET_TITLE', title }, '*'); + } +}; + +// Expose generically for other modules +export { _window as window }; +export default { + init, + events, + extensions, + app, + window: _window +}; diff --git a/neutralino.config.json b/neutralino.config.json index 1fee33d..b717350 100644 --- a/neutralino.config.json +++ b/neutralino.config.json @@ -6,12 +6,12 @@ "description": "Lossless music streaming", "version": "1.0.0", "defaultMode": "window", - "documentRoot": "www/", - "url": "https://monochrome.tf", + "documentRoot": "dist/", + "url": "/neutralino_loader.html", "enableServer": true, "enableNativeAPI": true, "enableExtensions": true, - "tokenSecurity": "one-time", + "tokenSecurity": "none", "modes": { "window": { "title": "Monochrome", @@ -32,7 +32,7 @@ "port": 5050, "cli": { "binaryName": "Monochrome", - "resourcesPath": "www/", + "resourcesPath": "dist/", "binaryVersion": "6.5.0", "clientVersion": "6.5.0" }, @@ -44,5 +44,10 @@ "commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\"" } ], - "nativeAllowList": ["app.exit", "window.*", "extensions.*", "events.*"] -} + "nativeAllowList": [ + "app.exit", + "window.*", + "extensions.*", + "events.*" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 4a7b39b..6f15e0c 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "main": "sw.js", "scripts": { "dev": "vite", - "build": "vite build", - "build:desktop": "vite build --mode neutralino && npx neu build && node -e \"const fs = require('fs'); fs.cpSync('extensions', 'dist/Monochrome/extensions', {recursive: true}); fs.copyFileSync('neutralino.config.json', 'dist/Monochrome/neutralino.config.json')\"", + "build": "vite build --mode neutralino && bun x neu build", + "postbuild": "node -e \"const fs = require('fs'); const path = require('path'); const src = 'extensions'; const dest = path.join('dist', 'Monochrome', 'extensions'); if (fs.existsSync(src)) { fs.mkdirSync(dest, { recursive: true }); fs.cpSync(src, dest, { recursive: true }); console.log('Extensions manually copied to ' + dest); }\"", "preview": "vite preview", "start": "vite preview", "lint:js": "eslint .", @@ -55,4 +55,4 @@ "dashjs": "^5.1.1", "pocketbase": "^0.26.5" } -} +} \ No newline at end of file diff --git a/public/neutralino.js b/public/neutralino.js new file mode 100644 index 0000000..c93e267 --- /dev/null +++ b/public/neutralino.js @@ -0,0 +1 @@ +var Neutralino=function(e){"use strict";function t(e,t){return window.addEventListener(e,t),Promise.resolve({success:!0,message:"Event listener added"})}function n(e,t){const n=new CustomEvent(e,{detail:t});return window.dispatchEvent(n),Promise.resolve({success:!0,message:"Message dispatched"})}function r(e){const t=window.atob(e),n=t.length,r=new Uint8Array(n);for(let e=0;e{if(await f(c),!window.NL_EXTENABLED)return;let e=await l("extensions.getStats");for(let t of e.connected)n("extensionReady",t)}),t("extClientConnect",e=>{n("extensionReady",e.detail)}),!window.NL_EXTENABLED)return;t("extensionReady",async e=>{e.detail in u&&(await f(u[e.detail]),delete u[e.detail])})}(),a.addEventListener("message",e=>{const t=JSON.parse(e.data);t.id&&t.id in s?(t.data?.error?(s[t.id].reject(t.data.error),"NE_RT_INVTOKN"==t.data.error.code&&(a.close(),document.body.innerText="",document.write("NE_RT_INVTOKN: Neutralinojs application cannot execute native methods since NL_TOKEN is invalid."))):t.data?.success&&s[t.id].resolve(t.data.hasOwnProperty("returnValue")?t.data.returnValue:t.data),delete s[t.id]):t.event&&("openedFile"==t.event&&"dataBinary"==t?.data?.action&&(t.data.data=r(t.data.data)),n(t.event,t.data))}),a.addEventListener("open",async e=>{n("ready")}),a.addEventListener("close",async e=>{n("serverOffline",{code:"NE_CL_NSEROFF",message:"Neutralino server is offline. Try restarting the application"})}),a.addEventListener("error",async e=>{document.body.innerText="",document.write("NE_CL_IVCTOKN: Neutralinojs application cannot connect with the framework core using NL_TOKEN.")})}function l(e,t){return new Promise((n,r)=>{if(a?.readyState!=WebSocket.OPEN)return o={method:e,data:t,resolve:n,reject:r},void c.push(o);var o;const i="10000000-1000-4000-8000-100000000000".replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),u=g();s[i]={resolve:n,reject:r},a.send(JSON.stringify({id:i,method:e,data:t,accessToken:u}))})}async function f(e){for(;e.length>0;){const t=e.shift();try{const e=await l(t.method,t.data);t.resolve(e)}catch(e){t.reject(e)}}}function g(){return window.NL_TOKEN||sessionStorage.getItem("NL_TOKEN")||""}function w(e,t){return l("filesystem.writeBinaryFile",{path:e,data:o(t)})}var m={__proto__:null,appendBinaryFile:function(e,t){return l("filesystem.appendBinaryFile",{path:e,data:o(t)})},appendFile:function(e,t){return l("filesystem.appendFile",{path:e,data:t})},copy:function(e,t,n){return l("filesystem.copy",{source:e,destination:t,...n})},createDirectory:function(e){return l("filesystem.createDirectory",{path:e})},createWatcher:function(e){return l("filesystem.createWatcher",{path:e})},getAbsolutePath:function(e){return l("filesystem.getAbsolutePath",{path:e})},getJoinedPath:function(...e){return l("filesystem.getJoinedPath",{paths:e})},getNormalizedPath:function(e){return l("filesystem.getNormalizedPath",{path:e})},getOpenedFileInfo:function(e){return l("filesystem.getOpenedFileInfo",{id:e})},getPathParts:function(e){return l("filesystem.getPathParts",{path:e})},getPermissions:function(e){return l("filesystem.getPermissions",{path:e})},getRelativePath:function(e,t){return l("filesystem.getRelativePath",{path:e,base:t})},getStats:function(e){return l("filesystem.getStats",{path:e})},getUnnormalizedPath:function(e){return l("filesystem.getUnnormalizedPath",{path:e})},getWatchers:function(){return l("filesystem.getWatchers")},move:function(e,t){return l("filesystem.move",{source:e,destination:t})},openFile:function(e){return l("filesystem.openFile",{path:e})},readBinaryFile:function(e,t){return new Promise((n,o)=>{l("filesystem.readBinaryFile",{path:e,...t}).then(e=>{n(r(e))}).catch(e=>{o(e)})})},readDirectory:function(e,t){return l("filesystem.readDirectory",{path:e,...t})},readFile:function(e,t){return l("filesystem.readFile",{path:e,...t})},remove:function(e){return l("filesystem.remove",{path:e})},removeWatcher:function(e){return l("filesystem.removeWatcher",{id:e})},setPermissions:function(e,t,n){return l("filesystem.setPermissions",{path:e,...t,mode:n})},updateOpenedFile:function(e,t,n){return l("filesystem.updateOpenedFile",{id:e,event:t,data:n})},writeBinaryFile:w,writeFile:function(e,t){return l("filesystem.writeFile",{path:e,data:t})}};function p(e,t){return l("os.execCommand",{command:e,...t})}var h={__proto__:null,execCommand:p,getEnv:function(e){return l("os.getEnv",{key:e})},getEnvs:function(){return l("os.getEnvs")},getPath:function(e){return l("os.getPath",{name:e})},getSpawnedProcesses:function(){return l("os.getSpawnedProcesses")},open:function(e){return l("os.open",{url:e})},setTray:function(e){return l("os.setTray",e)},showFolderDialog:function(e,t){return l("os.showFolderDialog",{title:e,...t})},showMessageBox:function(e,t,n,r){return l("os.showMessageBox",{title:e,content:t,choice:n,icon:r})},showNotification:function(e,t,n){return l("os.showNotification",{title:e,content:t,icon:n})},showOpenDialog:function(e,t){return l("os.showOpenDialog",{title:e,...t})},showSaveDialog:function(e,t){return l("os.showSaveDialog",{title:e,...t})},spawnProcess:function(e,t){return l("os.spawnProcess",{command:e,...t})},updateSpawnedProcess:function(e,t,n){return l("os.updateSpawnedProcess",{id:e,event:t,data:n})}};var y={__proto__:null,getArch:function(){return l("computer.getArch")},getCPUInfo:function(){return l("computer.getCPUInfo")},getDisplays:function(){return l("computer.getDisplays")},getKernelInfo:function(){return l("computer.getKernelInfo")},getMemoryInfo:function(){return l("computer.getMemoryInfo")},getMousePosition:function(){return l("computer.getMousePosition")},getOSInfo:function(){return l("computer.getOSInfo")}};var _={__proto__:null,clear:function(){return l("storage.clear")},getData:function(e){return l("storage.getData",{key:e})},getKeys:function(){return l("storage.getKeys")},removeData:function(e){return l("storage.removeData",{key:e})},setData:function(e,t){return l("storage.setData",{key:e,data:t})}};function v(e,t){return l("debug.log",{message:e,type:t})}var N={__proto__:null,log:v};function E(e){return l("app.exit",{code:e})}var P={__proto__:null,broadcast:function(e,t){return l("app.broadcast",{event:e,data:t})},exit:E,getConfig:function(){return l("app.getConfig")},killProcess:function(){return l("app.killProcess")},readProcessInput:function(e){return l("app.readProcessInput",{readAll:e})},restartProcess:function(e){return new Promise(async t=>{let n=window.NL_ARGS.reduce((e,t)=>(t.includes(" ")&&(t=`"${t}"`),e+=" "+t),"");e?.args&&(n+=" "+e.args),await p(n,{background:!0}),E(),t()})},writeProcessError:function(e){return l("app.writeProcessError",{data:e})},writeProcessOutput:function(e){return l("app.writeProcessOutput",{data:e})}};const b=new Set,D=new Map,T=new Map;function O(e=0,t=0){return l("window.beginDrag",{screenX:e,screenY:t})}function S(){return l("window.getSize")}var L={__proto__:null,beginDrag:O,center:function(){return l("window.center")},create:function(e,t){return new Promise((n,r)=>{function o(e){return"string"!=typeof e||(e=e.trim()).includes(" ")&&(e=`"${e}"`),e}t={...t,useSavedState:!1};let i=window.NL_ARGS.reduce((e,t,n)=>((t.includes("--path=")||t.includes("--debug-mode")||t.includes("--load-dir-res")||0==n)&&(e+=" "+o(t)),e),"");i+=" --url="+o(e);for(let e in t){if("processArgs"==e)continue;i+=` --window${"-"+e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}=${o(t[e])}`}t&&t.processArgs&&(i+=" "+t.processArgs),p(i,{background:!0}).then(e=>{n(e)}).catch(e=>{r(e)})})},exitFullScreen:function(){return l("window.exitFullScreen")},focus:function(){return l("window.focus")},getPosition:function(){return l("window.getPosition")},getSize:S,getTitle:function(){return l("window.getTitle")},hide:function(){return l("window.hide")},isFullScreen:function(){return l("window.isFullScreen")},isMaximized:function(){return l("window.isMaximized")},isMinimized:function(){return l("window.isMinimized")},isVisible:function(){return l("window.isVisible")},maximize:function(){return l("window.maximize")},minimize:function(){return l("window.minimize")},move:function(e,t){return l("window.move",{x:e,y:t})},print:function(){return l("window.print")},setAlwaysOnTop:function(e){return l("window.setAlwaysOnTop",{onTop:e})},setBorderless:function(e){return l("window.setBorderless",{borderless:e})},setDraggableRegion:function(e,t){return new Promise((n,r)=>{const o=e instanceof HTMLElement?e:document.getElementById(e);if(!o)return r({code:"NE_WD_DOMNOTF",message:"Unable to find DOM element"});if(b.has(o))return r({code:"NE_WD_ALRDREL",message:"This DOM element is already an active draggable region"});if(t?.exclude?.length){const e=new Set;for(const n of t.exclude){const t=n instanceof HTMLElement?n:document.getElementById(n);t&&e.add(t)}e.size&&D.set(o,e)}const a=(s=o,async function(e){if(0!==e.button)return;const t=D.get(s);if(t)for(const n of t)if(n.contains(e.target))return;await O(e.screenX,e.screenY),e.preventDefault()});var s;o.addEventListener("pointerdown",a),b.add(o),T.set(o,a);n({success:!0,message:"Draggable region was activated",exclusions:{add(...e){if(!b.has(o))throw{code:"NE_WD_NOTDRRE",message:"DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!"};let t=D.get(o);t||(t=new Set,D.set(o,t));const n=i(e);for(const e of n)t.add(e)},remove(...e){if(!b.has(o))throw{code:"NE_WD_NOTDRRE",message:"DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!"};const t=D.get(o);if(!t)return;const n=i(e);for(const e of n)t.delete(e)},removeAll(){if(!b.has(o))throw{code:"NE_WD_NOTDRRE",message:"DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!"};D.delete(o)}}})})},setFullScreen:function(){return l("window.setFullScreen")},setIcon:function(e){return l("window.setIcon",{icon:e})},setMainMenu:function(e){return l("window.setMainMenu",e)},setSize:function(e){return new Promise(async(t,n)=>{let r=await S();l("window.setSize",e={...r,...e}).then(e=>{t(e)}).catch(e=>{n(e)})})},setTitle:function(e){return l("window.setTitle",{title:e})},show:function(){return l("window.show")},snapshot:function(e){return l("window.snapshot",{path:e})},unmaximize:function(){return l("window.unmaximize")},unminimize:function(){return l("window.unminimize")},unsetDraggableRegion:function(e){return new Promise((t,n)=>{const r=e instanceof HTMLElement?e:document.getElementById(e);if(!r)return n({code:"NE_WD_DOMNOTF",message:"Unable to find DOM element"});if(!b.has(r))return n({code:"NE_WD_NOTDRRE",message:"DOM element is not an active draggable region"});const o=T.get(r);o&&(r.removeEventListener("pointerdown",o),T.delete(r)),b.delete(r),D.delete(r),t({success:!0,message:"Draggable region was deactivated"})})}};var M={__proto__:null,broadcast:function(e,t){return l("events.broadcast",{event:e,data:t})},dispatch:n,off:function(e,t){return window.removeEventListener(e,t),Promise.resolve({success:!0,message:"Event listener removed"})},on:t};function x(){return l("extensions.getStats")}var F={__proto__:null,broadcast:function(e,t){return l("extensions.broadcast",{event:e,data:t})},dispatch:function(e,t,n){return new Promise(async(r,o)=>{const i=await x();if(i.loaded.includes(e))if(i.connected.includes(e))try{r(await l("extensions.dispatch",{extensionId:e,event:t,data:n}))}catch(e){o(e)}else!function(e,t){e in u?u[e].push(t):u[e]=[t]}(e,{method:"extensions.dispatch",data:{extensionId:e,event:t,data:n},resolve:r,reject:o});else o({code:"NE_EX_EXTNOTL",message:`${e} is not loaded`})})},getStats:x};let R=null;var A={__proto__:null,checkForUpdates:function(e){return new Promise(async(t,n)=>{if(!e)return n({code:"NE_RT_NATRTER",message:"Missing require parameter: url"});try{const r=await fetch(e);R=JSON.parse(await r.text()),!function(e){return!!(e.applicationId&&e.applicationId==window.NL_APPID&&e.version&&e.resourcesURL)}(R)?n({code:"NE_UP_CUPDMER",message:"Invalid update manifest or mismatching applicationId"}):t(R)}catch(e){n({code:"NE_UP_CUPDERR",message:"Unable to fetch update manifest"})}})},install:function(){return new Promise(async(e,t)=>{if(!R)return t({code:"NE_UP_UPDNOUF",message:"No update manifest loaded. Make sure that updater.checkForUpdates() is called before install()."});try{const t=await fetch(R.resourcesURL),n=await t.arrayBuffer();await w(window.NL_PATH+"/resources.neu",n),e({success:!0,message:"Update installed. Restart the process to see updates"})}catch(e){t({code:"NE_UP_UPDINER",message:"Update installation error"})}})}};var I={__proto__:null,clear:function(){return l("clipboard.clear")},getFormat:function(){return l("clipboard.getFormat")},readHTML:function(){return l("clipboard.readHTML")},readImage:function(e=""){return new Promise((t,n)=>{l("clipboard.readImage").then(n=>{if(n){const r=window.atob(n.data);let o,i,a,s=32==n.bpp?4:3;switch(e.toLowerCase()){case"rgb":o=n.width*n.height*3,i=[0,1,2];break;case"rgba":o=n.width*n.height*4,i=[0,1,2,3];break;case"argb":o=n.width*n.height*4,i=[3,0,1,2];break;case"bgra":o=n.width*n.height*4,i=[2,1,0,3];break;default:o=r.length,a=new Uint8Array(o);for(let e=0;e>>0:(c<<24|u<<16|d<<8|l)>>>0,w=[f>>n.redShift&255,f>>n.greenShift&255,f>>n.blueShift&255,f>>n.alphaShift&255],i.forEach((e,t)=>{a[t+m]=w[e]}),m+=i.length;n.data=a}t(n)}).catch(e=>{n(e)})})},readText:function(){return l("clipboard.readText")},writeHTML:function(e){return l("clipboard.writeHTML",{data:e})},writeImage:function(e){const t={...e};return e?.data&&(t.data=o(e.data)),l("clipboard.writeImage",t)},writeText:function(e){return l("clipboard.writeText",{data:e})}};var C={__proto__:null,extractDirectory:function(e,t){return l("resources.extractDirectory",{path:e,destination:t})},extractFile:function(e,t){return l("resources.extractFile",{path:e,destination:t})},getFiles:function(){return l("resources.getFiles")},getStats:function(e){return l("resources.getStats",{path:e})},readBinaryFile:function(e){return new Promise((t,n)=>{l("resources.readBinaryFile",{path:e}).then(e=>{t(r(e))}).catch(e=>{n(e)})})},readFile:function(e){return l("resources.readFile",{path:e})}};var U={__proto__:null,getMounts:function(){return l("server.getMounts")},mount:function(e,t){return l("server.mount",{path:e,target:t})},unmount:function(e){return l("server.unmount",{path:e})}};var k={__proto__:null,getMethods:function(){return l("custom.getMethods")}};let z=!1;return e.app=P,e.clipboard=I,e.computer=y,e.custom=k,e.debug=N,e.events=M,e.extensions=F,e.filesystem=m,e.init=function(e={}){if(e={exportCustomMethods:!0,...e},!z){if(d(),window.NL_ARGS.find(e=>"--neu-dev-auto-reload"==e)&&t("neuDev_reloadApp",async()=>{await v("Reloading the application..."),location.reload()}),e.exportCustomMethods&&window.NL_CMETHODS&&window.NL_CMETHODS.length>0)for(const e of window.NL_CMETHODS)Neutralino.custom[e]=(...t)=>{let n={};for(const[e,r]of t.entries())n="object"!=typeof r||Array.isArray(r)||null==r?{...n,["arg"+e]:r}:{...n,...r};return l("custom."+e,n)};window.NL_CVERSION="6.5.0",window.NL_CCOMMIT="425c526c318342e0e5d0f17caceef2a53049eda4",z=!0}},e.os=h,e.resources=C,e.server=U,e.storage=_,e.updater=A,e.window=L,e}({}); diff --git a/public/neutralino_loader.html b/public/neutralino_loader.html new file mode 100644 index 0000000..70c3736 --- /dev/null +++ b/public/neutralino_loader.html @@ -0,0 +1,147 @@ + + + + + + Monochrome Shell + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 827ce43..e71e719 100644 --- a/vite.config.js +++ b/vite.config.js @@ -9,8 +9,8 @@ export default defineConfig(({ mode }) => { return { base: './', build: { - outDir: IS_NEUTRALINO ? 'www' : 'dist', - emptyOutDir: IS_NEUTRALINO, + outDir: 'dist', + emptyOutDir: true, }, plugins: [ IS_NEUTRALINO && neutralino(), From 75ae9c23fa4db7d184238f53968334dc13315bc3 Mon Sep 17 00:00:00 2001 From: JulienMaille <182520+JulienMaille@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:00:00 +0000 Subject: [PATCH 2/2] style: auto-fix linting issues --- js/app.js | 5 - js/discord-rpc.js | 6 +- js/neutralino-bridge.js | 4 +- neutralino.config.json | 9 +- package.json | 2 +- public/neutralino.js | 816 +++++++++++++++++++++++++++++++++- public/neutralino_loader.html | 268 +++++------ 7 files changed, 959 insertions(+), 151 deletions(-) diff --git a/js/app.js b/js/app.js index c5635f7..c424e5b 100644 --- a/js/app.js +++ b/js/app.js @@ -259,15 +259,12 @@ document.addEventListener('DOMContentLoaded', async () => { // Initialize Discord RPC console.log('[App] Starting Discord RPC...'); initializeDiscordRPC(player); - } catch (e) { console.error('[App] Neutralino init failed:', e); } } }; - - const api = new LosslessAPI(apiSettings); const audioPlayer = document.getElementById('audio-player'); @@ -416,8 +413,6 @@ document.addEventListener('DOMContentLoaded', async () => { // Initialize tracker initTracker(player); - - initNeutralino(); const castBtn = document.getElementById('cast-btn'); diff --git a/js/discord-rpc.js b/js/discord-rpc.js index 31cdff1..7e64d44 100644 --- a/js/discord-rpc.js +++ b/js/discord-rpc.js @@ -52,8 +52,8 @@ export function initializeDiscordRPC(player) { smallImageKey: 'pause', smallImageText: 'Paused', }; - Neutralino.events.broadcast('discord:update', idlingData).catch(() => { }); - Neutralino.extensions.dispatch(EXTENSION_ID, 'discord:update', idlingData).catch(() => { }); + Neutralino.events.broadcast('discord:update', idlingData).catch(() => {}); + Neutralino.extensions.dispatch(EXTENSION_ID, 'discord:update', idlingData).catch(() => {}); } }, 5000); @@ -84,6 +84,6 @@ export function initializeDiscordRPC(player) { smallImageKey: 'pause', smallImageText: 'Paused', }) - .catch(() => { }); + .catch(() => {}); } } diff --git a/js/neutralino-bridge.js b/js/neutralino-bridge.js index 6f459fb..3444e32 100644 --- a/js/neutralino-bridge.js +++ b/js/neutralino-bridge.js @@ -66,7 +66,7 @@ const _window = { }, setTitle: async (title) => { window.parent.postMessage({ type: 'NL_WINDOW_SET_TITLE', title }, '*'); - } + }, }; // Expose generically for other modules @@ -76,5 +76,5 @@ export default { events, extensions, app, - window: _window + window: _window, }; diff --git a/neutralino.config.json b/neutralino.config.json index b717350..798e0fa 100644 --- a/neutralino.config.json +++ b/neutralino.config.json @@ -44,10 +44,5 @@ "commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\"" } ], - "nativeAllowList": [ - "app.exit", - "window.*", - "extensions.*", - "events.*" - ] -} \ No newline at end of file + "nativeAllowList": ["app.exit", "window.*", "extensions.*", "events.*"] +} diff --git a/package.json b/package.json index 6f15e0c..1e99fe1 100644 --- a/package.json +++ b/package.json @@ -55,4 +55,4 @@ "dashjs": "^5.1.1", "pocketbase": "^0.26.5" } -} \ No newline at end of file +} diff --git a/public/neutralino.js b/public/neutralino.js index c93e267..9c17ed7 100644 --- a/public/neutralino.js +++ b/public/neutralino.js @@ -1 +1,815 @@ -var Neutralino=function(e){"use strict";function t(e,t){return window.addEventListener(e,t),Promise.resolve({success:!0,message:"Event listener added"})}function n(e,t){const n=new CustomEvent(e,{detail:t});return window.dispatchEvent(n),Promise.resolve({success:!0,message:"Message dispatched"})}function r(e){const t=window.atob(e),n=t.length,r=new Uint8Array(n);for(let e=0;e{if(await f(c),!window.NL_EXTENABLED)return;let e=await l("extensions.getStats");for(let t of e.connected)n("extensionReady",t)}),t("extClientConnect",e=>{n("extensionReady",e.detail)}),!window.NL_EXTENABLED)return;t("extensionReady",async e=>{e.detail in u&&(await f(u[e.detail]),delete u[e.detail])})}(),a.addEventListener("message",e=>{const t=JSON.parse(e.data);t.id&&t.id in s?(t.data?.error?(s[t.id].reject(t.data.error),"NE_RT_INVTOKN"==t.data.error.code&&(a.close(),document.body.innerText="",document.write("NE_RT_INVTOKN: Neutralinojs application cannot execute native methods since NL_TOKEN is invalid."))):t.data?.success&&s[t.id].resolve(t.data.hasOwnProperty("returnValue")?t.data.returnValue:t.data),delete s[t.id]):t.event&&("openedFile"==t.event&&"dataBinary"==t?.data?.action&&(t.data.data=r(t.data.data)),n(t.event,t.data))}),a.addEventListener("open",async e=>{n("ready")}),a.addEventListener("close",async e=>{n("serverOffline",{code:"NE_CL_NSEROFF",message:"Neutralino server is offline. Try restarting the application"})}),a.addEventListener("error",async e=>{document.body.innerText="",document.write("NE_CL_IVCTOKN: Neutralinojs application cannot connect with the framework core using NL_TOKEN.")})}function l(e,t){return new Promise((n,r)=>{if(a?.readyState!=WebSocket.OPEN)return o={method:e,data:t,resolve:n,reject:r},void c.push(o);var o;const i="10000000-1000-4000-8000-100000000000".replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),u=g();s[i]={resolve:n,reject:r},a.send(JSON.stringify({id:i,method:e,data:t,accessToken:u}))})}async function f(e){for(;e.length>0;){const t=e.shift();try{const e=await l(t.method,t.data);t.resolve(e)}catch(e){t.reject(e)}}}function g(){return window.NL_TOKEN||sessionStorage.getItem("NL_TOKEN")||""}function w(e,t){return l("filesystem.writeBinaryFile",{path:e,data:o(t)})}var m={__proto__:null,appendBinaryFile:function(e,t){return l("filesystem.appendBinaryFile",{path:e,data:o(t)})},appendFile:function(e,t){return l("filesystem.appendFile",{path:e,data:t})},copy:function(e,t,n){return l("filesystem.copy",{source:e,destination:t,...n})},createDirectory:function(e){return l("filesystem.createDirectory",{path:e})},createWatcher:function(e){return l("filesystem.createWatcher",{path:e})},getAbsolutePath:function(e){return l("filesystem.getAbsolutePath",{path:e})},getJoinedPath:function(...e){return l("filesystem.getJoinedPath",{paths:e})},getNormalizedPath:function(e){return l("filesystem.getNormalizedPath",{path:e})},getOpenedFileInfo:function(e){return l("filesystem.getOpenedFileInfo",{id:e})},getPathParts:function(e){return l("filesystem.getPathParts",{path:e})},getPermissions:function(e){return l("filesystem.getPermissions",{path:e})},getRelativePath:function(e,t){return l("filesystem.getRelativePath",{path:e,base:t})},getStats:function(e){return l("filesystem.getStats",{path:e})},getUnnormalizedPath:function(e){return l("filesystem.getUnnormalizedPath",{path:e})},getWatchers:function(){return l("filesystem.getWatchers")},move:function(e,t){return l("filesystem.move",{source:e,destination:t})},openFile:function(e){return l("filesystem.openFile",{path:e})},readBinaryFile:function(e,t){return new Promise((n,o)=>{l("filesystem.readBinaryFile",{path:e,...t}).then(e=>{n(r(e))}).catch(e=>{o(e)})})},readDirectory:function(e,t){return l("filesystem.readDirectory",{path:e,...t})},readFile:function(e,t){return l("filesystem.readFile",{path:e,...t})},remove:function(e){return l("filesystem.remove",{path:e})},removeWatcher:function(e){return l("filesystem.removeWatcher",{id:e})},setPermissions:function(e,t,n){return l("filesystem.setPermissions",{path:e,...t,mode:n})},updateOpenedFile:function(e,t,n){return l("filesystem.updateOpenedFile",{id:e,event:t,data:n})},writeBinaryFile:w,writeFile:function(e,t){return l("filesystem.writeFile",{path:e,data:t})}};function p(e,t){return l("os.execCommand",{command:e,...t})}var h={__proto__:null,execCommand:p,getEnv:function(e){return l("os.getEnv",{key:e})},getEnvs:function(){return l("os.getEnvs")},getPath:function(e){return l("os.getPath",{name:e})},getSpawnedProcesses:function(){return l("os.getSpawnedProcesses")},open:function(e){return l("os.open",{url:e})},setTray:function(e){return l("os.setTray",e)},showFolderDialog:function(e,t){return l("os.showFolderDialog",{title:e,...t})},showMessageBox:function(e,t,n,r){return l("os.showMessageBox",{title:e,content:t,choice:n,icon:r})},showNotification:function(e,t,n){return l("os.showNotification",{title:e,content:t,icon:n})},showOpenDialog:function(e,t){return l("os.showOpenDialog",{title:e,...t})},showSaveDialog:function(e,t){return l("os.showSaveDialog",{title:e,...t})},spawnProcess:function(e,t){return l("os.spawnProcess",{command:e,...t})},updateSpawnedProcess:function(e,t,n){return l("os.updateSpawnedProcess",{id:e,event:t,data:n})}};var y={__proto__:null,getArch:function(){return l("computer.getArch")},getCPUInfo:function(){return l("computer.getCPUInfo")},getDisplays:function(){return l("computer.getDisplays")},getKernelInfo:function(){return l("computer.getKernelInfo")},getMemoryInfo:function(){return l("computer.getMemoryInfo")},getMousePosition:function(){return l("computer.getMousePosition")},getOSInfo:function(){return l("computer.getOSInfo")}};var _={__proto__:null,clear:function(){return l("storage.clear")},getData:function(e){return l("storage.getData",{key:e})},getKeys:function(){return l("storage.getKeys")},removeData:function(e){return l("storage.removeData",{key:e})},setData:function(e,t){return l("storage.setData",{key:e,data:t})}};function v(e,t){return l("debug.log",{message:e,type:t})}var N={__proto__:null,log:v};function E(e){return l("app.exit",{code:e})}var P={__proto__:null,broadcast:function(e,t){return l("app.broadcast",{event:e,data:t})},exit:E,getConfig:function(){return l("app.getConfig")},killProcess:function(){return l("app.killProcess")},readProcessInput:function(e){return l("app.readProcessInput",{readAll:e})},restartProcess:function(e){return new Promise(async t=>{let n=window.NL_ARGS.reduce((e,t)=>(t.includes(" ")&&(t=`"${t}"`),e+=" "+t),"");e?.args&&(n+=" "+e.args),await p(n,{background:!0}),E(),t()})},writeProcessError:function(e){return l("app.writeProcessError",{data:e})},writeProcessOutput:function(e){return l("app.writeProcessOutput",{data:e})}};const b=new Set,D=new Map,T=new Map;function O(e=0,t=0){return l("window.beginDrag",{screenX:e,screenY:t})}function S(){return l("window.getSize")}var L={__proto__:null,beginDrag:O,center:function(){return l("window.center")},create:function(e,t){return new Promise((n,r)=>{function o(e){return"string"!=typeof e||(e=e.trim()).includes(" ")&&(e=`"${e}"`),e}t={...t,useSavedState:!1};let i=window.NL_ARGS.reduce((e,t,n)=>((t.includes("--path=")||t.includes("--debug-mode")||t.includes("--load-dir-res")||0==n)&&(e+=" "+o(t)),e),"");i+=" --url="+o(e);for(let e in t){if("processArgs"==e)continue;i+=` --window${"-"+e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}=${o(t[e])}`}t&&t.processArgs&&(i+=" "+t.processArgs),p(i,{background:!0}).then(e=>{n(e)}).catch(e=>{r(e)})})},exitFullScreen:function(){return l("window.exitFullScreen")},focus:function(){return l("window.focus")},getPosition:function(){return l("window.getPosition")},getSize:S,getTitle:function(){return l("window.getTitle")},hide:function(){return l("window.hide")},isFullScreen:function(){return l("window.isFullScreen")},isMaximized:function(){return l("window.isMaximized")},isMinimized:function(){return l("window.isMinimized")},isVisible:function(){return l("window.isVisible")},maximize:function(){return l("window.maximize")},minimize:function(){return l("window.minimize")},move:function(e,t){return l("window.move",{x:e,y:t})},print:function(){return l("window.print")},setAlwaysOnTop:function(e){return l("window.setAlwaysOnTop",{onTop:e})},setBorderless:function(e){return l("window.setBorderless",{borderless:e})},setDraggableRegion:function(e,t){return new Promise((n,r)=>{const o=e instanceof HTMLElement?e:document.getElementById(e);if(!o)return r({code:"NE_WD_DOMNOTF",message:"Unable to find DOM element"});if(b.has(o))return r({code:"NE_WD_ALRDREL",message:"This DOM element is already an active draggable region"});if(t?.exclude?.length){const e=new Set;for(const n of t.exclude){const t=n instanceof HTMLElement?n:document.getElementById(n);t&&e.add(t)}e.size&&D.set(o,e)}const a=(s=o,async function(e){if(0!==e.button)return;const t=D.get(s);if(t)for(const n of t)if(n.contains(e.target))return;await O(e.screenX,e.screenY),e.preventDefault()});var s;o.addEventListener("pointerdown",a),b.add(o),T.set(o,a);n({success:!0,message:"Draggable region was activated",exclusions:{add(...e){if(!b.has(o))throw{code:"NE_WD_NOTDRRE",message:"DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!"};let t=D.get(o);t||(t=new Set,D.set(o,t));const n=i(e);for(const e of n)t.add(e)},remove(...e){if(!b.has(o))throw{code:"NE_WD_NOTDRRE",message:"DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!"};const t=D.get(o);if(!t)return;const n=i(e);for(const e of n)t.delete(e)},removeAll(){if(!b.has(o))throw{code:"NE_WD_NOTDRRE",message:"DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!"};D.delete(o)}}})})},setFullScreen:function(){return l("window.setFullScreen")},setIcon:function(e){return l("window.setIcon",{icon:e})},setMainMenu:function(e){return l("window.setMainMenu",e)},setSize:function(e){return new Promise(async(t,n)=>{let r=await S();l("window.setSize",e={...r,...e}).then(e=>{t(e)}).catch(e=>{n(e)})})},setTitle:function(e){return l("window.setTitle",{title:e})},show:function(){return l("window.show")},snapshot:function(e){return l("window.snapshot",{path:e})},unmaximize:function(){return l("window.unmaximize")},unminimize:function(){return l("window.unminimize")},unsetDraggableRegion:function(e){return new Promise((t,n)=>{const r=e instanceof HTMLElement?e:document.getElementById(e);if(!r)return n({code:"NE_WD_DOMNOTF",message:"Unable to find DOM element"});if(!b.has(r))return n({code:"NE_WD_NOTDRRE",message:"DOM element is not an active draggable region"});const o=T.get(r);o&&(r.removeEventListener("pointerdown",o),T.delete(r)),b.delete(r),D.delete(r),t({success:!0,message:"Draggable region was deactivated"})})}};var M={__proto__:null,broadcast:function(e,t){return l("events.broadcast",{event:e,data:t})},dispatch:n,off:function(e,t){return window.removeEventListener(e,t),Promise.resolve({success:!0,message:"Event listener removed"})},on:t};function x(){return l("extensions.getStats")}var F={__proto__:null,broadcast:function(e,t){return l("extensions.broadcast",{event:e,data:t})},dispatch:function(e,t,n){return new Promise(async(r,o)=>{const i=await x();if(i.loaded.includes(e))if(i.connected.includes(e))try{r(await l("extensions.dispatch",{extensionId:e,event:t,data:n}))}catch(e){o(e)}else!function(e,t){e in u?u[e].push(t):u[e]=[t]}(e,{method:"extensions.dispatch",data:{extensionId:e,event:t,data:n},resolve:r,reject:o});else o({code:"NE_EX_EXTNOTL",message:`${e} is not loaded`})})},getStats:x};let R=null;var A={__proto__:null,checkForUpdates:function(e){return new Promise(async(t,n)=>{if(!e)return n({code:"NE_RT_NATRTER",message:"Missing require parameter: url"});try{const r=await fetch(e);R=JSON.parse(await r.text()),!function(e){return!!(e.applicationId&&e.applicationId==window.NL_APPID&&e.version&&e.resourcesURL)}(R)?n({code:"NE_UP_CUPDMER",message:"Invalid update manifest or mismatching applicationId"}):t(R)}catch(e){n({code:"NE_UP_CUPDERR",message:"Unable to fetch update manifest"})}})},install:function(){return new Promise(async(e,t)=>{if(!R)return t({code:"NE_UP_UPDNOUF",message:"No update manifest loaded. Make sure that updater.checkForUpdates() is called before install()."});try{const t=await fetch(R.resourcesURL),n=await t.arrayBuffer();await w(window.NL_PATH+"/resources.neu",n),e({success:!0,message:"Update installed. Restart the process to see updates"})}catch(e){t({code:"NE_UP_UPDINER",message:"Update installation error"})}})}};var I={__proto__:null,clear:function(){return l("clipboard.clear")},getFormat:function(){return l("clipboard.getFormat")},readHTML:function(){return l("clipboard.readHTML")},readImage:function(e=""){return new Promise((t,n)=>{l("clipboard.readImage").then(n=>{if(n){const r=window.atob(n.data);let o,i,a,s=32==n.bpp?4:3;switch(e.toLowerCase()){case"rgb":o=n.width*n.height*3,i=[0,1,2];break;case"rgba":o=n.width*n.height*4,i=[0,1,2,3];break;case"argb":o=n.width*n.height*4,i=[3,0,1,2];break;case"bgra":o=n.width*n.height*4,i=[2,1,0,3];break;default:o=r.length,a=new Uint8Array(o);for(let e=0;e>>0:(c<<24|u<<16|d<<8|l)>>>0,w=[f>>n.redShift&255,f>>n.greenShift&255,f>>n.blueShift&255,f>>n.alphaShift&255],i.forEach((e,t)=>{a[t+m]=w[e]}),m+=i.length;n.data=a}t(n)}).catch(e=>{n(e)})})},readText:function(){return l("clipboard.readText")},writeHTML:function(e){return l("clipboard.writeHTML",{data:e})},writeImage:function(e){const t={...e};return e?.data&&(t.data=o(e.data)),l("clipboard.writeImage",t)},writeText:function(e){return l("clipboard.writeText",{data:e})}};var C={__proto__:null,extractDirectory:function(e,t){return l("resources.extractDirectory",{path:e,destination:t})},extractFile:function(e,t){return l("resources.extractFile",{path:e,destination:t})},getFiles:function(){return l("resources.getFiles")},getStats:function(e){return l("resources.getStats",{path:e})},readBinaryFile:function(e){return new Promise((t,n)=>{l("resources.readBinaryFile",{path:e}).then(e=>{t(r(e))}).catch(e=>{n(e)})})},readFile:function(e){return l("resources.readFile",{path:e})}};var U={__proto__:null,getMounts:function(){return l("server.getMounts")},mount:function(e,t){return l("server.mount",{path:e,target:t})},unmount:function(e){return l("server.unmount",{path:e})}};var k={__proto__:null,getMethods:function(){return l("custom.getMethods")}};let z=!1;return e.app=P,e.clipboard=I,e.computer=y,e.custom=k,e.debug=N,e.events=M,e.extensions=F,e.filesystem=m,e.init=function(e={}){if(e={exportCustomMethods:!0,...e},!z){if(d(),window.NL_ARGS.find(e=>"--neu-dev-auto-reload"==e)&&t("neuDev_reloadApp",async()=>{await v("Reloading the application..."),location.reload()}),e.exportCustomMethods&&window.NL_CMETHODS&&window.NL_CMETHODS.length>0)for(const e of window.NL_CMETHODS)Neutralino.custom[e]=(...t)=>{let n={};for(const[e,r]of t.entries())n="object"!=typeof r||Array.isArray(r)||null==r?{...n,["arg"+e]:r}:{...n,...r};return l("custom."+e,n)};window.NL_CVERSION="6.5.0",window.NL_CCOMMIT="425c526c318342e0e5d0f17caceef2a53049eda4",z=!0}},e.os=h,e.resources=C,e.server=U,e.storage=_,e.updater=A,e.window=L,e}({}); +var Neutralino = (function (e) { + 'use strict'; + function t(e, t) { + return (window.addEventListener(e, t), Promise.resolve({ success: !0, message: 'Event listener added' })); + } + function n(e, t) { + const n = new CustomEvent(e, { detail: t }); + return (window.dispatchEvent(n), Promise.resolve({ success: !0, message: 'Message dispatched' })); + } + function r(e) { + const t = window.atob(e), + n = t.length, + r = new Uint8Array(n); + for (let e = 0; e < n; e++) r[e] = t.charCodeAt(e); + return r.buffer; + } + function o(e) { + let t = new Uint8Array(e), + n = ''; + for (let e of t) n += String.fromCharCode(e); + return window.btoa(n); + } + function i(e) { + const t = []; + for (const n of e) { + const e = Array.isArray(n) ? n : [n]; + for (const n of e) { + const e = n instanceof HTMLElement ? n : document.getElementById(n); + e && t.push(e); + } + } + return t; + } + let a; + const s = {}, + c = [], + u = {}; + function d() { + window.NL_TOKEN && sessionStorage.setItem('NL_TOKEN', window.NL_TOKEN); + const e = g().split('.')[1], + o = window.NL_GINJECTED || window.NL_CINJECTED ? '127.0.0.1' : window.location.hostname; + ((a = new WebSocket(`ws://${o}:${window.NL_PORT}?connectToken=${e}`)), + (function () { + if ( + (t('ready', async () => { + if ((await f(c), !window.NL_EXTENABLED)) return; + let e = await l('extensions.getStats'); + for (let t of e.connected) n('extensionReady', t); + }), + t('extClientConnect', (e) => { + n('extensionReady', e.detail); + }), + !window.NL_EXTENABLED) + ) + return; + t('extensionReady', async (e) => { + e.detail in u && (await f(u[e.detail]), delete u[e.detail]); + }); + })(), + a.addEventListener('message', (e) => { + const t = JSON.parse(e.data); + t.id && t.id in s + ? (t.data?.error + ? (s[t.id].reject(t.data.error), + 'NE_RT_INVTOKN' == t.data.error.code && + (a.close(), + (document.body.innerText = ''), + document.write( + 'NE_RT_INVTOKN: Neutralinojs application cannot execute native methods since NL_TOKEN is invalid.' + ))) + : t.data?.success && + s[t.id].resolve(t.data.hasOwnProperty('returnValue') ? t.data.returnValue : t.data), + delete s[t.id]) + : t.event && + ('openedFile' == t.event && 'dataBinary' == t?.data?.action && (t.data.data = r(t.data.data)), + n(t.event, t.data)); + }), + a.addEventListener('open', async (e) => { + n('ready'); + }), + a.addEventListener('close', async (e) => { + n('serverOffline', { + code: 'NE_CL_NSEROFF', + message: 'Neutralino server is offline. Try restarting the application', + }); + }), + a.addEventListener('error', async (e) => { + ((document.body.innerText = ''), + document.write( + 'NE_CL_IVCTOKN: Neutralinojs application cannot connect with the framework core using NL_TOKEN.' + )); + })); + } + function l(e, t) { + return new Promise((n, r) => { + if (a?.readyState != WebSocket.OPEN) + return ((o = { method: e, data: t, resolve: n, reject: r }), void c.push(o)); + var o; + const i = '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (e) => + (e ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (e / 4)))).toString(16) + ), + u = g(); + ((s[i] = { resolve: n, reject: r }), a.send(JSON.stringify({ id: i, method: e, data: t, accessToken: u }))); + }); + } + async function f(e) { + for (; e.length > 0; ) { + const t = e.shift(); + try { + const e = await l(t.method, t.data); + t.resolve(e); + } catch (e) { + t.reject(e); + } + } + } + function g() { + return window.NL_TOKEN || sessionStorage.getItem('NL_TOKEN') || ''; + } + function w(e, t) { + return l('filesystem.writeBinaryFile', { path: e, data: o(t) }); + } + var m = { + __proto__: null, + appendBinaryFile: function (e, t) { + return l('filesystem.appendBinaryFile', { path: e, data: o(t) }); + }, + appendFile: function (e, t) { + return l('filesystem.appendFile', { path: e, data: t }); + }, + copy: function (e, t, n) { + return l('filesystem.copy', { source: e, destination: t, ...n }); + }, + createDirectory: function (e) { + return l('filesystem.createDirectory', { path: e }); + }, + createWatcher: function (e) { + return l('filesystem.createWatcher', { path: e }); + }, + getAbsolutePath: function (e) { + return l('filesystem.getAbsolutePath', { path: e }); + }, + getJoinedPath: function (...e) { + return l('filesystem.getJoinedPath', { paths: e }); + }, + getNormalizedPath: function (e) { + return l('filesystem.getNormalizedPath', { path: e }); + }, + getOpenedFileInfo: function (e) { + return l('filesystem.getOpenedFileInfo', { id: e }); + }, + getPathParts: function (e) { + return l('filesystem.getPathParts', { path: e }); + }, + getPermissions: function (e) { + return l('filesystem.getPermissions', { path: e }); + }, + getRelativePath: function (e, t) { + return l('filesystem.getRelativePath', { path: e, base: t }); + }, + getStats: function (e) { + return l('filesystem.getStats', { path: e }); + }, + getUnnormalizedPath: function (e) { + return l('filesystem.getUnnormalizedPath', { path: e }); + }, + getWatchers: function () { + return l('filesystem.getWatchers'); + }, + move: function (e, t) { + return l('filesystem.move', { source: e, destination: t }); + }, + openFile: function (e) { + return l('filesystem.openFile', { path: e }); + }, + readBinaryFile: function (e, t) { + return new Promise((n, o) => { + l('filesystem.readBinaryFile', { path: e, ...t }) + .then((e) => { + n(r(e)); + }) + .catch((e) => { + o(e); + }); + }); + }, + readDirectory: function (e, t) { + return l('filesystem.readDirectory', { path: e, ...t }); + }, + readFile: function (e, t) { + return l('filesystem.readFile', { path: e, ...t }); + }, + remove: function (e) { + return l('filesystem.remove', { path: e }); + }, + removeWatcher: function (e) { + return l('filesystem.removeWatcher', { id: e }); + }, + setPermissions: function (e, t, n) { + return l('filesystem.setPermissions', { path: e, ...t, mode: n }); + }, + updateOpenedFile: function (e, t, n) { + return l('filesystem.updateOpenedFile', { id: e, event: t, data: n }); + }, + writeBinaryFile: w, + writeFile: function (e, t) { + return l('filesystem.writeFile', { path: e, data: t }); + }, + }; + function p(e, t) { + return l('os.execCommand', { command: e, ...t }); + } + var h = { + __proto__: null, + execCommand: p, + getEnv: function (e) { + return l('os.getEnv', { key: e }); + }, + getEnvs: function () { + return l('os.getEnvs'); + }, + getPath: function (e) { + return l('os.getPath', { name: e }); + }, + getSpawnedProcesses: function () { + return l('os.getSpawnedProcesses'); + }, + open: function (e) { + return l('os.open', { url: e }); + }, + setTray: function (e) { + return l('os.setTray', e); + }, + showFolderDialog: function (e, t) { + return l('os.showFolderDialog', { title: e, ...t }); + }, + showMessageBox: function (e, t, n, r) { + return l('os.showMessageBox', { title: e, content: t, choice: n, icon: r }); + }, + showNotification: function (e, t, n) { + return l('os.showNotification', { title: e, content: t, icon: n }); + }, + showOpenDialog: function (e, t) { + return l('os.showOpenDialog', { title: e, ...t }); + }, + showSaveDialog: function (e, t) { + return l('os.showSaveDialog', { title: e, ...t }); + }, + spawnProcess: function (e, t) { + return l('os.spawnProcess', { command: e, ...t }); + }, + updateSpawnedProcess: function (e, t, n) { + return l('os.updateSpawnedProcess', { id: e, event: t, data: n }); + }, + }; + var y = { + __proto__: null, + getArch: function () { + return l('computer.getArch'); + }, + getCPUInfo: function () { + return l('computer.getCPUInfo'); + }, + getDisplays: function () { + return l('computer.getDisplays'); + }, + getKernelInfo: function () { + return l('computer.getKernelInfo'); + }, + getMemoryInfo: function () { + return l('computer.getMemoryInfo'); + }, + getMousePosition: function () { + return l('computer.getMousePosition'); + }, + getOSInfo: function () { + return l('computer.getOSInfo'); + }, + }; + var _ = { + __proto__: null, + clear: function () { + return l('storage.clear'); + }, + getData: function (e) { + return l('storage.getData', { key: e }); + }, + getKeys: function () { + return l('storage.getKeys'); + }, + removeData: function (e) { + return l('storage.removeData', { key: e }); + }, + setData: function (e, t) { + return l('storage.setData', { key: e, data: t }); + }, + }; + function v(e, t) { + return l('debug.log', { message: e, type: t }); + } + var N = { __proto__: null, log: v }; + function E(e) { + return l('app.exit', { code: e }); + } + var P = { + __proto__: null, + broadcast: function (e, t) { + return l('app.broadcast', { event: e, data: t }); + }, + exit: E, + getConfig: function () { + return l('app.getConfig'); + }, + killProcess: function () { + return l('app.killProcess'); + }, + readProcessInput: function (e) { + return l('app.readProcessInput', { readAll: e }); + }, + restartProcess: function (e) { + return new Promise(async (t) => { + let n = window.NL_ARGS.reduce((e, t) => (t.includes(' ') && (t = `"${t}"`), (e += ' ' + t)), ''); + (e?.args && (n += ' ' + e.args), await p(n, { background: !0 }), E(), t()); + }); + }, + writeProcessError: function (e) { + return l('app.writeProcessError', { data: e }); + }, + writeProcessOutput: function (e) { + return l('app.writeProcessOutput', { data: e }); + }, + }; + const b = new Set(), + D = new Map(), + T = new Map(); + function O(e = 0, t = 0) { + return l('window.beginDrag', { screenX: e, screenY: t }); + } + function S() { + return l('window.getSize'); + } + var L = { + __proto__: null, + beginDrag: O, + center: function () { + return l('window.center'); + }, + create: function (e, t) { + return new Promise((n, r) => { + function o(e) { + return ('string' != typeof e || ((e = e.trim()).includes(' ') && (e = `"${e}"`)), e); + } + t = { ...t, useSavedState: !1 }; + let i = window.NL_ARGS.reduce( + (e, t, n) => ( + (t.includes('--path=') || + t.includes('--debug-mode') || + t.includes('--load-dir-res') || + 0 == n) && + (e += ' ' + o(t)), + e + ), + '' + ); + i += ' --url=' + o(e); + for (let e in t) { + if ('processArgs' == e) continue; + i += ` --window${'-' + e.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}=${o(t[e])}`; + } + (t && t.processArgs && (i += ' ' + t.processArgs), + p(i, { background: !0 }) + .then((e) => { + n(e); + }) + .catch((e) => { + r(e); + })); + }); + }, + exitFullScreen: function () { + return l('window.exitFullScreen'); + }, + focus: function () { + return l('window.focus'); + }, + getPosition: function () { + return l('window.getPosition'); + }, + getSize: S, + getTitle: function () { + return l('window.getTitle'); + }, + hide: function () { + return l('window.hide'); + }, + isFullScreen: function () { + return l('window.isFullScreen'); + }, + isMaximized: function () { + return l('window.isMaximized'); + }, + isMinimized: function () { + return l('window.isMinimized'); + }, + isVisible: function () { + return l('window.isVisible'); + }, + maximize: function () { + return l('window.maximize'); + }, + minimize: function () { + return l('window.minimize'); + }, + move: function (e, t) { + return l('window.move', { x: e, y: t }); + }, + print: function () { + return l('window.print'); + }, + setAlwaysOnTop: function (e) { + return l('window.setAlwaysOnTop', { onTop: e }); + }, + setBorderless: function (e) { + return l('window.setBorderless', { borderless: e }); + }, + setDraggableRegion: function (e, t) { + return new Promise((n, r) => { + const o = e instanceof HTMLElement ? e : document.getElementById(e); + if (!o) return r({ code: 'NE_WD_DOMNOTF', message: 'Unable to find DOM element' }); + if (b.has(o)) + return r({ + code: 'NE_WD_ALRDREL', + message: 'This DOM element is already an active draggable region', + }); + if (t?.exclude?.length) { + const e = new Set(); + for (const n of t.exclude) { + const t = n instanceof HTMLElement ? n : document.getElementById(n); + t && e.add(t); + } + e.size && D.set(o, e); + } + const a = + ((s = o), + async function (e) { + if (0 !== e.button) return; + const t = D.get(s); + if (t) for (const n of t) if (n.contains(e.target)) return; + (await O(e.screenX, e.screenY), e.preventDefault()); + }); + var s; + (o.addEventListener('pointerdown', a), b.add(o), T.set(o, a)); + n({ + success: !0, + message: 'Draggable region was activated', + exclusions: { + add(...e) { + if (!b.has(o)) + throw { + code: 'NE_WD_NOTDRRE', + message: + 'DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!', + }; + let t = D.get(o); + t || ((t = new Set()), D.set(o, t)); + const n = i(e); + for (const e of n) t.add(e); + }, + remove(...e) { + if (!b.has(o)) + throw { + code: 'NE_WD_NOTDRRE', + message: + 'DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!', + }; + const t = D.get(o); + if (!t) return; + const n = i(e); + for (const e of n) t.delete(e); + }, + removeAll() { + if (!b.has(o)) + throw { + code: 'NE_WD_NOTDRRE', + message: + 'DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!', + }; + D.delete(o); + }, + }, + }); + }); + }, + setFullScreen: function () { + return l('window.setFullScreen'); + }, + setIcon: function (e) { + return l('window.setIcon', { icon: e }); + }, + setMainMenu: function (e) { + return l('window.setMainMenu', e); + }, + setSize: function (e) { + return new Promise(async (t, n) => { + let r = await S(); + l('window.setSize', (e = { ...r, ...e })) + .then((e) => { + t(e); + }) + .catch((e) => { + n(e); + }); + }); + }, + setTitle: function (e) { + return l('window.setTitle', { title: e }); + }, + show: function () { + return l('window.show'); + }, + snapshot: function (e) { + return l('window.snapshot', { path: e }); + }, + unmaximize: function () { + return l('window.unmaximize'); + }, + unminimize: function () { + return l('window.unminimize'); + }, + unsetDraggableRegion: function (e) { + return new Promise((t, n) => { + const r = e instanceof HTMLElement ? e : document.getElementById(e); + if (!r) return n({ code: 'NE_WD_DOMNOTF', message: 'Unable to find DOM element' }); + if (!b.has(r)) + return n({ code: 'NE_WD_NOTDRRE', message: 'DOM element is not an active draggable region' }); + const o = T.get(r); + (o && (r.removeEventListener('pointerdown', o), T.delete(r)), + b.delete(r), + D.delete(r), + t({ success: !0, message: 'Draggable region was deactivated' })); + }); + }, + }; + var M = { + __proto__: null, + broadcast: function (e, t) { + return l('events.broadcast', { event: e, data: t }); + }, + dispatch: n, + off: function (e, t) { + return ( + window.removeEventListener(e, t), + Promise.resolve({ success: !0, message: 'Event listener removed' }) + ); + }, + on: t, + }; + function x() { + return l('extensions.getStats'); + } + var F = { + __proto__: null, + broadcast: function (e, t) { + return l('extensions.broadcast', { event: e, data: t }); + }, + dispatch: function (e, t, n) { + return new Promise(async (r, o) => { + const i = await x(); + if (i.loaded.includes(e)) + if (i.connected.includes(e)) + try { + r(await l('extensions.dispatch', { extensionId: e, event: t, data: n })); + } catch (e) { + o(e); + } + else + !(function (e, t) { + e in u ? u[e].push(t) : (u[e] = [t]); + })(e, { + method: 'extensions.dispatch', + data: { extensionId: e, event: t, data: n }, + resolve: r, + reject: o, + }); + else o({ code: 'NE_EX_EXTNOTL', message: `${e} is not loaded` }); + }); + }, + getStats: x, + }; + let R = null; + var A = { + __proto__: null, + checkForUpdates: function (e) { + return new Promise(async (t, n) => { + if (!e) return n({ code: 'NE_RT_NATRTER', message: 'Missing require parameter: url' }); + try { + const r = await fetch(e); + ((R = JSON.parse(await r.text())), + !(function (e) { + return !!( + e.applicationId && + e.applicationId == window.NL_APPID && + e.version && + e.resourcesURL + ); + })(R) + ? n({ + code: 'NE_UP_CUPDMER', + message: 'Invalid update manifest or mismatching applicationId', + }) + : t(R)); + } catch (e) { + n({ code: 'NE_UP_CUPDERR', message: 'Unable to fetch update manifest' }); + } + }); + }, + install: function () { + return new Promise(async (e, t) => { + if (!R) + return t({ + code: 'NE_UP_UPDNOUF', + message: + 'No update manifest loaded. Make sure that updater.checkForUpdates() is called before install().', + }); + try { + const t = await fetch(R.resourcesURL), + n = await t.arrayBuffer(); + (await w(window.NL_PATH + '/resources.neu', n), + e({ success: !0, message: 'Update installed. Restart the process to see updates' })); + } catch (e) { + t({ code: 'NE_UP_UPDINER', message: 'Update installation error' }); + } + }); + }, + }; + var I = { + __proto__: null, + clear: function () { + return l('clipboard.clear'); + }, + getFormat: function () { + return l('clipboard.getFormat'); + }, + readHTML: function () { + return l('clipboard.readHTML'); + }, + readImage: function (e = '') { + return new Promise((t, n) => { + l('clipboard.readImage') + .then((n) => { + if (n) { + const r = window.atob(n.data); + let o, + i, + a, + s = 32 == n.bpp ? 4 : 3; + switch (e.toLowerCase()) { + case 'rgb': + ((o = n.width * n.height * 3), (i = [0, 1, 2])); + break; + case 'rgba': + ((o = n.width * n.height * 4), (i = [0, 1, 2, 3])); + break; + case 'argb': + ((o = n.width * n.height * 4), (i = [3, 0, 1, 2])); + break; + case 'bgra': + ((o = n.width * n.height * 4), (i = [2, 1, 0, 3])); + break; + default: + ((o = r.length), (a = new Uint8Array(o))); + for (let e = 0; e < o; e++) a[e] = r.charCodeAt(e); + return ((n.data = a), void t(n)); + } + a = new Uint8Array(o); + let c, + u, + d, + l, + f, + g = 255 == new Uint8Array(new Uint32Array([255]).buffer)[0], + w = [], + m = 0; + for (let e = 0; e < r.length; e += s) + ((c = r.charCodeAt(e)), + (u = r.charCodeAt(e + 1)), + (d = r.charCodeAt(e + 2)), + (l = 4 == s ? r.charCodeAt(e + 3) : 255), + (f = g + ? ((l << 24) | (d << 16) | (u << 8) | c) >>> 0 + : ((c << 24) | (u << 16) | (d << 8) | l) >>> 0), + (w = [ + (f >> n.redShift) & 255, + (f >> n.greenShift) & 255, + (f >> n.blueShift) & 255, + (f >> n.alphaShift) & 255, + ]), + i.forEach((e, t) => { + a[t + m] = w[e]; + }), + (m += i.length)); + n.data = a; + } + t(n); + }) + .catch((e) => { + n(e); + }); + }); + }, + readText: function () { + return l('clipboard.readText'); + }, + writeHTML: function (e) { + return l('clipboard.writeHTML', { data: e }); + }, + writeImage: function (e) { + const t = { ...e }; + return (e?.data && (t.data = o(e.data)), l('clipboard.writeImage', t)); + }, + writeText: function (e) { + return l('clipboard.writeText', { data: e }); + }, + }; + var C = { + __proto__: null, + extractDirectory: function (e, t) { + return l('resources.extractDirectory', { path: e, destination: t }); + }, + extractFile: function (e, t) { + return l('resources.extractFile', { path: e, destination: t }); + }, + getFiles: function () { + return l('resources.getFiles'); + }, + getStats: function (e) { + return l('resources.getStats', { path: e }); + }, + readBinaryFile: function (e) { + return new Promise((t, n) => { + l('resources.readBinaryFile', { path: e }) + .then((e) => { + t(r(e)); + }) + .catch((e) => { + n(e); + }); + }); + }, + readFile: function (e) { + return l('resources.readFile', { path: e }); + }, + }; + var U = { + __proto__: null, + getMounts: function () { + return l('server.getMounts'); + }, + mount: function (e, t) { + return l('server.mount', { path: e, target: t }); + }, + unmount: function (e) { + return l('server.unmount', { path: e }); + }, + }; + var k = { + __proto__: null, + getMethods: function () { + return l('custom.getMethods'); + }, + }; + let z = !1; + return ( + (e.app = P), + (e.clipboard = I), + (e.computer = y), + (e.custom = k), + (e.debug = N), + (e.events = M), + (e.extensions = F), + (e.filesystem = m), + (e.init = function (e = {}) { + if (((e = { exportCustomMethods: !0, ...e }), !z)) { + if ( + (d(), + window.NL_ARGS.find((e) => '--neu-dev-auto-reload' == e) && + t('neuDev_reloadApp', async () => { + (await v('Reloading the application...'), location.reload()); + }), + e.exportCustomMethods && window.NL_CMETHODS && window.NL_CMETHODS.length > 0) + ) + for (const e of window.NL_CMETHODS) + Neutralino.custom[e] = (...t) => { + let n = {}; + for (const [e, r] of t.entries()) + n = + 'object' != typeof r || Array.isArray(r) || null == r + ? { ...n, ['arg' + e]: r } + : { ...n, ...r }; + return l('custom.' + e, n); + }; + ((window.NL_CVERSION = '6.5.0'), + (window.NL_CCOMMIT = '425c526c318342e0e5d0f17caceef2a53049eda4'), + (z = !0)); + } + }), + (e.os = h), + (e.resources = C), + (e.server = U), + (e.storage = _), + (e.updater = A), + (e.window = L), + e + ); +})({}); diff --git a/public/neutralino_loader.html b/public/neutralino_loader.html index 70c3736..426de07 100644 --- a/public/neutralino_loader.html +++ b/public/neutralino_loader.html @@ -1,147 +1,151 @@ - + - - - - Monochrome Shell - - - - - - - - - - - + + + + + + - - - \ No newline at end of file + case 'NL_WINDOW_SET_TITLE': + try { + await Neutralino.window.setTitle(event.data.title); + } catch (e) { + console.error('[Shell] Set title failed:', e); + } + break; + } + }); + + +