diff --git a/package.json b/package.json index 34ea340..4d7a382 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxmox-spice-client", - "version": "1.0.0", + "version": "1.0.1", "description": "VDI SPICE Client f\u00fcr Proxmox", "main": "src/main.js", "scripts": { diff --git a/src/main.js b/src/main.js index 4a82036..79553e0 100644 --- a/src/main.js +++ b/src/main.js @@ -155,6 +155,28 @@ ipcMain.handle('proxmox:connect', async (_e, { node, vmid }) => { } }); +// ---------- IPC: settings ---------- + +ipcMain.handle('settings:platform', () => process.platform); + +ipcMain.handle('settings:getViewerPath', () => store.get('viewerPath', null)); + +ipcMain.handle('settings:clearViewerPath', () => { + store.delete('viewerPath'); +}); + +ipcMain.handle('settings:browseViewerPath', async () => { + const result = await dialog.showOpenDialog(mainWindow, { + title: 'remote-viewer.exe auswählen', + properties: ['openFile'], + filters: [{ name: 'remote-viewer', extensions: ['exe'] }], + }); + if (result.canceled || result.filePaths.length === 0) return null; + const chosen = result.filePaths[0]; + store.set('viewerPath', chosen); + return chosen; +}); + // ---------- SPICE helpers ---------- function buildVVFile(params, vmid) { @@ -187,24 +209,55 @@ function buildVVFile(params, vmid) { return vvPath; } +// VirtViewer's Windows installer names its folder after the bundled version +// (e.g. "VirtViewer v11.0-256"), so a fixed path breaks on every version bump. +// Scan the two Program Files dirs for any "VirtViewer*" folder instead. +function findWindowsViewerCandidates() { + const roots = [ + process.env['ProgramFiles'] || 'C:\\Program Files', + process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', + ]; + const found = []; + for (const root of roots) { + let entries; + try { + entries = fs.readdirSync(root, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + if (!entry.isDirectory() || !/^virtviewer/i.test(entry.name)) continue; + const exe = path.join(root, entry.name, 'bin', 'remote-viewer.exe'); + if (fs.existsSync(exe)) found.push(exe); + } + } + return found; +} + function launchRemoteViewer(vvPath) { - const candidates = - process.platform === 'win32' - ? [ - 'remote-viewer', - path.join('C:', 'Program Files', 'VirtViewer', 'bin', 'remote-viewer.exe'), - path.join('C:', 'Program Files (x86)', 'VirtViewer', 'bin', 'remote-viewer.exe'), - ] - : ['remote-viewer', 'virt-viewer']; + let candidates; + if (process.platform === 'win32') { + const customPath = store.get('viewerPath', null); + candidates = [ + ...(customPath ? [customPath] : []), + 'remote-viewer', + ...findWindowsViewerCandidates(), + ]; + } else { + candidates = ['remote-viewer', 'virt-viewer']; + } function tryNext(i) { if (i >= candidates.length) { + const hint = + process.platform === 'win32' + ? 'Windows: https://virt-manager.org/download/\n\n' + + 'Falls VirtViewer an einem nicht-standardmäßigen Ort installiert ist, ' + + 'trage den Pfad unter „Einstellungen → Viewer-Pfad" ein.' + : 'Linux: sudo apt install virt-viewer'; dialog.showErrorBox( 'remote-viewer nicht gefunden', - 'Bitte installiere virt-viewer:\n\n' + - ' Linux: sudo apt install virt-viewer\n' + - ' Windows: https://virt-manager.org/download/\n\n' + - 'Danach bitte erneut verbinden.' + 'Bitte installiere virt-viewer:\n\n' + hint + '\n\nDanach bitte erneut verbinden.' ); return; } diff --git a/src/preload.js b/src/preload.js index f615c69..1f6114f 100644 --- a/src/preload.js +++ b/src/preload.js @@ -12,4 +12,10 @@ contextBridge.exposeInMainWorld('api', { connect: (vm) => ipcRenderer.invoke('proxmox:connect', vm), logout: () => ipcRenderer.invoke('proxmox:logout'), }, + settings: { + platform: () => ipcRenderer.invoke('settings:platform'), + getViewerPath: () => ipcRenderer.invoke('settings:getViewerPath'), + browseViewerPath: () => ipcRenderer.invoke('settings:browseViewerPath'), + clearViewerPath: () => ipcRenderer.invoke('settings:clearViewerPath'), + }, }); diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 2ecf417..ccc54d3 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -292,3 +292,59 @@ body.vms-page { margin-top: 0.5rem; color: #3a4a60; } + +/* ── Settings modal ───────────────────────────────────────── */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(5, 8, 16, 0.65); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; +} + +.modal-card { + width: 420px; + max-width: 90vw; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 14px; + padding: 1.6rem 1.7rem; + box-shadow: 0 24px 60px rgba(0,0,0,0.55); +} + +.modal-card h3 { + font-size: 1rem; + font-weight: 600; + margin-bottom: 0.6rem; +} + +.modal-card .hint { + font-size: 0.8rem; + color: var(--muted); + margin-bottom: 1.1rem; + line-height: 1.5; +} + +.path-display { + padding: 0.6rem 0.85rem; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: 7px; + font-size: 0.82rem; + color: var(--text); + word-break: break-all; +} + +.modal-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + margin-top: 1.4rem; +} + +.modal-actions .modal-close { + width: auto; + padding: 0.42rem 1rem; +} diff --git a/src/renderer/vms.html b/src/renderer/vms.html index fd85796..c2d2097 100644 --- a/src/renderer/vms.html +++ b/src/renderer/vms.html @@ -14,6 +14,7 @@