From a4851f980d283b09506807ae42dc90b2330ddc59 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Fri, 15 Aug 2025 00:31:18 -0500 Subject: [PATCH] Handles the returned KeysDownState from keyboardReport Now passes all logic through handleKeyPress. If we get a state back from a keyboardReport, use it and also enable keypressReport because we now know it's an upgraded device. --- ui/package-lock.json | 227 +++++++++--------- ui/package.json | 8 +- ui/src/components/ActionBar.tsx | 2 +- ui/src/components/InfoBar.tsx | 10 +- ui/src/components/USBStateStatus.tsx | 2 +- ui/src/components/UsbInfoSetting.tsx | 2 +- ui/src/components/VirtualKeyboard.tsx | 19 +- ui/src/components/WebRTCVideo.tsx | 49 ++-- ui/src/hooks/useKeyboard.ts | 65 +++-- .../routes/devices.$id.settings.network.tsx | 17 +- ui/src/routes/devices.$id.settings.tsx | 2 +- ui/src/routes/devices.$id.tsx | 39 ++- 12 files changed, 251 insertions(+), 191 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index ab6f459..2126943 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "kvm-ui", - "version": "2025.08.07.001", + "version": "2025.08.15.001", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kvm-ui", - "version": "2025.08.07.001", + "version": "2025.08.15.001", "dependencies": { "@headlessui/react": "^2.2.7", "@headlessui/tailwindcss": "^0.2.2", @@ -45,9 +45,9 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.33.0", "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/postcss": "^4.1.11", + "@tailwindcss/postcss": "^4.1.12", "@tailwindcss/typography": "^0.5.16", - "@tailwindcss/vite": "^4.1.11", + "@tailwindcss/vite": "^4.1.12", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@types/semver": "^7.7.0", @@ -66,7 +66,7 @@ "postcss": "^8.5.6", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", - "tailwindcss": "^4.1.11", + "tailwindcss": "^4.1.12", "typescript": "^5.9.2", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4" @@ -88,24 +88,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/runtime": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", - "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -855,6 +841,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1549,25 +1546,25 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" + "tailwindcss": "4.1.12" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1579,24 +1576,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + "@tailwindcss/oxide-android-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-x64": "4.1.12", + "@tailwindcss/oxide-freebsd-x64": "4.1.12", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", + "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", "cpu": [ "arm64" ], @@ -1611,9 +1608,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz", + "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==", "cpu": [ "arm64" ], @@ -1628,9 +1625,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", "cpu": [ "x64" ], @@ -1645,9 +1642,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", + "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", "cpu": [ "x64" ], @@ -1662,9 +1659,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", + "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", "cpu": [ "arm" ], @@ -1679,9 +1676,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", + "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", "cpu": [ "arm64" ], @@ -1696,9 +1693,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", + "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", "cpu": [ "arm64" ], @@ -1713,9 +1710,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", + "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", "cpu": [ "x64" ], @@ -1730,9 +1727,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", + "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", "cpu": [ "x64" ], @@ -1747,9 +1744,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", + "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1765,11 +1762,11 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", "tslib": "^2.8.0" }, "engines": { @@ -1777,9 +1774,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", + "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", "cpu": [ "arm64" ], @@ -1794,9 +1791,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", "cpu": [ "x64" ], @@ -1811,17 +1808,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", - "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.12.tgz", + "integrity": "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", "postcss": "^8.4.41", - "tailwindcss": "4.1.11" + "tailwindcss": "4.1.12" } }, "node_modules/@tailwindcss/typography": { @@ -1841,15 +1838,15 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz", + "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", + "tailwindcss": "4.1.12" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" @@ -2739,9 +2736,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001734", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz", - "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==", + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", "dev": true, "funding": [ { @@ -3159,9 +3156,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.200", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz", - "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==", + "version": "1.5.201", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.201.tgz", + "integrity": "sha512-ZG65vsrLClodGqywuigc+7m0gr4ISoTQttfVh7nfpLv0M7SIwF4WbFNEOywcqTiujs12AUeeXbFyQieDICAIxg==", "dev": true, "license": "ISC" }, @@ -6478,9 +6475,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", "license": "MIT" }, "node_modules/tapable": { @@ -6534,10 +6531,13 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -6939,10 +6939,13 @@ } }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, diff --git a/ui/package.json b/ui/package.json index 4c929f0..f10341f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "kvm-ui", "private": true, - "version": "2025.08.07.001", + "version": "2025.08.15.001", "type": "module", "engines": { "node": "22.15.0" @@ -56,9 +56,9 @@ "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.33.0", "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/postcss": "^4.1.11", + "@tailwindcss/postcss": "^4.1.12", "@tailwindcss/typography": "^0.5.16", - "@tailwindcss/vite": "^4.1.11", + "@tailwindcss/vite": "^4.1.12", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@types/semver": "^7.7.0", @@ -77,7 +77,7 @@ "postcss": "^8.5.6", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", - "tailwindcss": "^4.1.11", + "tailwindcss": "^4.1.12", "typescript": "^5.9.2", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4" diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index 801cc7a..c8a214b 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -48,7 +48,7 @@ export default function Actionbar({ if (!open) { setTimeout(() => { setDisableFocusTrap(false); - console.log("Popover is closing. Returning focus trap to video"); + console.debug("Popover is closing. Returning focus trap to video"); }, 0); } } diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx index bc123ac..eafc380 100644 --- a/ui/src/components/InfoBar.tsx +++ b/ui/src/components/InfoBar.tsx @@ -3,7 +3,6 @@ import { useEffect, useMemo } from "react"; import { cx } from "@/cva.config"; import { HidState, - KeysDownState, MouseState, RTCState, SettingsState, @@ -39,7 +38,7 @@ export default function InfoBar() { if (!rpcDataChannel) return; rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed"); rpcDataChannel.onerror = (e: Event) => - console.log(`Error on DataChannel '${rpcDataChannel.label}': ${e}`); + console.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`); }, [rpcDataChannel]); const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState); @@ -49,12 +48,11 @@ export default function InfoBar() { const hdmiState = useVideoStore((state: VideoState) => state.hdmiState); const displayKeys = useMemo(() => { - if (!showPressedKeys || !keysDownState) + if (!showPressedKeys) return ""; - const state = keysDownState as KeysDownState; - const activeModifierMask = state.modifier || 0; - const keysDown = state.keys || []; + const activeModifierMask = keysDownState.modifier || 0; + const keysDown = keysDownState.keys || []; const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name); const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name); diff --git a/ui/src/components/USBStateStatus.tsx b/ui/src/components/USBStateStatus.tsx index f0b2cb2..618a9c3 100644 --- a/ui/src/components/USBStateStatus.tsx +++ b/ui/src/components/USBStateStatus.tsx @@ -67,7 +67,7 @@ export default function USBStateStatus({ }; const props = StatusCardProps[state]; if (!props) { - console.log("Unsupported USB state: ", state); + console.warn("Unsupported USB state: ", state); return; } diff --git a/ui/src/components/UsbInfoSetting.tsx b/ui/src/components/UsbInfoSetting.tsx index 198335c..4f63697 100644 --- a/ui/src/components/UsbInfoSetting.tsx +++ b/ui/src/components/UsbInfoSetting.tsx @@ -101,8 +101,8 @@ export function UsbInfoSetting() { `Failed to load USB Config: ${resp.error.data || "Unknown error"}`, ); } else { - console.log("syncUsbConfigProduct#getUsbConfig result:", resp.result); const usbConfigState = resp.result as UsbConfigState; + console.log("syncUsbConfigProduct#getUsbConfig result:", usbConfigState); const product = usbConfigs.map(u => u.value).includes(usbConfigState.product) ? usbConfigState.product : "custom"; diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx index e08cee7..fa7857d 100644 --- a/ui/src/components/VirtualKeyboard.tsx +++ b/ui/src/components/VirtualKeyboard.tsx @@ -14,7 +14,7 @@ import DetachIconRaw from "@/assets/detach-icon.svg"; import { cx } from "@/cva.config"; import { HidState, useHidStore, useUiStore } from "@/hooks/stores"; import useKeyboard from "@/hooks/useKeyboard"; -import { keyDisplayMap, keys, modifiers } from "@/keyboardMappings"; +import { keyDisplayMap, keys } from "@/keyboardMappings"; export const DetachIcon = ({ className }: { className?: string }) => { return Detach Icon; @@ -39,7 +39,7 @@ function KeyboardWrapper() { const setVirtualKeyboard = useHidStore(state => state.setVirtualKeyboardEnabled); const keysDownState = useHidStore((state: HidState) => state.keysDownState); - const { handleKeyPress, sendKeyboardEvent, resetKeyboardState } = useKeyboard(); + const { handleKeyPress, executeMacro } = useKeyboard(); const [isDragging, setIsDragging] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); @@ -133,26 +133,25 @@ function KeyboardWrapper() { const latchingKeys = ["CapsLock", "ScrollLock", "NumLock", "Meta", "Compose", "Kana"]; const dynamicKeys = ["ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "MetaLeft", "MetaRight"]; + // handle the fake key-macros we have defined for common combinations if (key === "CtrlAltDelete") { - sendKeyboardEvent({ keys: [keys.Delete], modifier: modifiers.ControlLeft | modifiers.AltLeft }); - setTimeout(resetKeyboardState, 100); + executeMacro([ { keys: ["Delete"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]); return; } if (key === "AltMetaEscape") { - sendKeyboardEvent({ keys: [keys.Escape], modifier: modifiers.AltLeft | modifiers.MetaLeft }); - setTimeout(resetKeyboardState, 100); + executeMacro([ { keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 } ]); return; } if (key === "CtrlAltBackspace") { - sendKeyboardEvent({ keys: [keys.Backspace], modifier: modifiers.ControlLeft | modifiers.AltLeft }); - setTimeout(resetKeyboardState, 100); + executeMacro([ { keys: ["Backspace"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]); return; } // if they press any of the latching keys, we send a keypress down event and the release it automatically (on timer) if (latchingKeys.includes(key)) { + console.debug(`Latching key pressed: ${key} sending down and delayed up pair`); handleKeyPress(keys[key], true) setTimeout(() => handleKeyPress(keys[key], false), 100); return; @@ -161,16 +160,18 @@ function KeyboardWrapper() { // if they press any of the dynamic keys, we send a keypress down event but we don't release it until they click it again if (dynamicKeys.includes(key)) { const currentlyDown = keysDownState.keys.includes(keys[key]); + console.debug(`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`); handleKeyPress(keys[key], !currentlyDown) return; } // otherwise, just treat it as a down+up pair const cleanKey = key.replace(/[()]/g, ""); + console.debug(`Regular key pressed: ${cleanKey} sending down and up pair`); handleKeyPress(keys[cleanKey], true); setTimeout(() => handleKeyPress(keys[cleanKey], false), 50); }, - [handleKeyPress, sendKeyboardEvent, resetKeyboardState, keysDownState], + [executeMacro, handleKeyPress, keysDownState], ); // TODO handle the display of down keys and the layout change for shift/caps lock diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index 33c3dfc..6e273f5 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -35,6 +35,7 @@ export default function WebRTCVideo() { const [isPlaying, setIsPlaying] = useState(false); const peerConnectionState = useRTCStore((state: RTCState) => state.peerConnectionState); const [isPointerLockActive, setIsPointerLockActive] = useState(false); + const [isKeyboardLockActive, setIsKeyboardLockActive] = useState(false); // Store hooks const settings = useSettingsStore(); const { handleKeyPress, resetKeyboardState } = useKeyboard(); @@ -69,16 +70,19 @@ export default function WebRTCVideo() { const [send] = useJsonRpc(); // Video-related + const handleResize = useCallback( + ({ width, height }: { width: number; height: number }) => { + if (!videoElm.current) return; + // Do something with width and height, e.g.: + setVideoClientSize(width, height); + setVideoSize(videoElm.current.videoWidth, videoElm.current.videoHeight); + }, + [setVideoClientSize, setVideoSize] + ); + useResizeObserver({ ref: videoElm as React.RefObject, - onResize: ({ width, height }) => { - // This is actually client size, not videoSize - if (width && height) { - if (!videoElm.current) return; - setVideoClientSize(width, height); - setVideoSize(videoElm.current.videoWidth, videoElm.current.videoHeight); - } - }, + onResize: handleResize, }); const updateVideoSizeStore = useCallback( @@ -107,7 +111,7 @@ export default function WebRTCVideo() { const isFullscreenEnabled = document.fullscreenEnabled; const checkNavigatorPermissions = useCallback(async (permissionName: string) => { - if (!navigator.permissions || !navigator.permissions.query) { + if (!navigator || !navigator.permissions || !navigator.permissions.query) { return false; // if can't query permissions, assume NOT granted } @@ -142,28 +146,30 @@ export default function WebRTCVideo() { const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock"); - if (isKeyboardLockGranted && "keyboard" in navigator) { + if (isKeyboardLockGranted && navigator && "keyboard" in navigator) { try { // @ts-expect-error - keyboard lock is not supported in all browsers await navigator.keyboard.lock(); + setIsKeyboardLockActive(true); } catch { // ignore errors } } - }, [checkNavigatorPermissions]); + }, [checkNavigatorPermissions, setIsKeyboardLockActive]); const releaseKeyboardLock = useCallback(async () => { if (videoElm.current === null || document.fullscreenElement !== videoElm.current) return; - if ("keyboard" in navigator) { + if (navigator && "keyboard" in navigator) { try { // @ts-expect-error - keyboard unlock is not supported in all browsers await navigator.keyboard.unlock(); } catch { // ignore errors } + setIsKeyboardLockActive(false); } - }, []); + }, [setIsKeyboardLockActive]); useEffect(() => { if (!isPointerLockPossible || !videoElm.current) return; @@ -353,12 +359,24 @@ export default function WebRTCVideo() { // https://bugzilla.mozilla.org/show_bug.cgi?id=1299553 if (e.metaKey && hidKey < 0xE0) { setTimeout(() => { + console.debug(`Forcing the meta key release of associated key: ${hidKey}`); handleKeyPress(hidKey, false); }, 10); } + console.debug(`Key down: ${hidKey}`); handleKeyPress(hidKey, true); + + if (!isKeyboardLockActive && hidKey === keys.MetaLeft) { + // If the left meta key was just pressed and we're not keyboard locked + // we'll never see the keyup event because the browser is going to lose + // focus so set a deferred keyup after a short delay + setTimeout(() => { + console.debug(`Forcing the left meta key release`); + handleKeyPress(hidKey, false); + }, 100); + } }, - [handleKeyPress], + [handleKeyPress, isKeyboardLockActive], ); const keyUpHandler = useCallback( @@ -372,6 +390,7 @@ export default function WebRTCVideo() { return; } + console.debug(`Key up: ${hidKey}`); handleKeyPress(hidKey, false); }, [handleKeyPress], @@ -385,7 +404,7 @@ export default function WebRTCVideo() { // Fix only works in chrome based browsers. if (e.code === "Space") { if (videoElm.current.paused) { - console.log("Force playing video"); + console.debug("Force playing video"); videoElm.current.play(); } } diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index f98066d..f9d9b04 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -19,19 +19,33 @@ export default function useKeyboard() { (state: KeysDownState) => { if (rpcDataChannel?.readyState !== "open") return; - //TODO would be nice if the keyboardReport rpc call returned the current state like keypressReport does - send("keyboardReport", { keys: state.keys, modifier: state.modifier }); + console.debug(`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`); + send("keyboardReport", { keys: state.keys, modifier: state.modifier }, (resp: JsonRpcResponse) => { + if ("error" in resp) { + console.error(`Failed to send keyboard report ${state}`, resp.error); + } else { + const keysDownState = resp.result as KeysDownState; - // We do this for the info bar to display the currently pressed keys for the user - setKeysDownState(state); + if (keysDownState) { + // new devices return the keyDownState, so we can use it to update the state + setKeysDownState(keysDownState); + setKeyPressAvailable(true); // if they returned a keysDownState, we know they also support keyPressReport + } else { + // old devices do not return the keyDownState, so we just pretend they accepted what we sent + setKeysDownState(state); + // and we shouldn't set keyPressAvailable here because we don't know if they support it + } + } + }); }, - [rpcDataChannel?.readyState, send, setKeysDownState], + [rpcDataChannel?.readyState, send, setKeyPressAvailable, setKeysDownState], ); const sendKeypressEvent = useCallback( (key: number, press: boolean) => { if (rpcDataChannel?.readyState !== "open") return; + console.debug(`Send keypressEvent key: ${key}, press: ${press}`); send("keypressReport", { key, press }, (resp: JsonRpcResponse) => { if ("error" in resp) { // -32601 means the method is not supported @@ -43,9 +57,12 @@ export default function useKeyboard() { console.error(`Failed to send key ${key} press: ${press}`, resp.error); } } else { - const keyDownState = resp.result as KeysDownState; - // We do this for the info bar to display the currently pressed keys for the user - setKeysDownState(keyDownState); + const keysDownState = resp.result as KeysDownState; + + if (keysDownState) { + setKeysDownState(keysDownState); + // we don't need to set keyPressAvailable here, because it's already true or we never landed here + } } }); }, @@ -53,8 +70,11 @@ export default function useKeyboard() { ); const resetKeyboardState = useCallback(() => { - sendKeyboardEvent({ keys: [], modifier: 0 }); - }, [sendKeyboardEvent]); + console.debug("Resetting keyboard state"); + keysDownState.keys.fill(0); // Reset the keys buffer to zeros + keysDownState.modifier = 0; // Reset the modifier state to zero + sendKeyboardEvent(keysDownState); + }, [keysDownState, sendKeyboardEvent]); const executeMacro = async (steps: { keys: string[] | null; modifiers: string[] | null; delay: number }[]) => { for (const [index, step] of steps.entries()) { @@ -87,6 +107,7 @@ export default function useKeyboard() { const modifierMask = hidKeyToModifierMask[key] || 0; if (modifierMask !== 0) { + console.debug(`Handling modifier key: ${key}, press: ${press}, current modifiers: ${modifiers}, modifier mask: ${modifierMask}`); if (press) { modifiers |= modifierMask; } else { @@ -132,22 +153,24 @@ export default function useKeyboard() { const handleKeyPress = useCallback( (key: number, press: boolean) => { + if (rpcDataChannel?.readyState !== "open") return; + if (keyPressAvailable) { // if the keyPress api is available, we can just send the key press event sendKeypressEvent(key, press); - - // TODO handle the case where the keyPress api is not available and we need to handle the key locally now... - } else { - // if the keyPress api is not available, we need to handle the key locally - const newKeysDownState = handleKeyLocally(keysDownState, key, press); - setKeysDownState(newKeysDownState); - - // then we send the full state - sendKeyboardEvent(newKeysDownState); + // if keyPress api is STILL available, we don't need to handle the key locally + if (keyPressAvailable) return; } + + // if the keyPress api is not available, we need to handle the key locally + const downState = handleKeyLocally(keysDownState, key, press); + setKeysDownState(downState); + + // then we send the full state + sendKeyboardEvent(downState); }, - [keyPressAvailable, keysDownState, sendKeyboardEvent, sendKeypressEvent, setKeysDownState], + [keyPressAvailable, keysDownState, rpcDataChannel?.readyState, sendKeyboardEvent, sendKeypressEvent, setKeysDownState], ); - return { handleKeyPress, sendKeyboardEvent, sendKeypressEvent, resetKeyboardState, executeMacro }; + return { handleKeyPress, resetKeyboardState, executeMacro }; } diff --git a/ui/src/routes/devices.$id.settings.network.tsx b/ui/src/routes/devices.$id.settings.network.tsx index 6fcd588..1df380f 100644 --- a/ui/src/routes/devices.$id.settings.network.tsx +++ b/ui/src/routes/devices.$id.settings.network.tsx @@ -106,11 +106,12 @@ export default function SettingsNetworkRoute() { setNetworkSettingsLoaded(false); send("getNetworkSettings", {}, resp => { if ("error" in resp) return; - console.log(resp.result); - setNetworkSettings(resp.result as NetworkSettings); + const networkSettings = resp.result as NetworkSettings; + console.debug("Network settings: ", networkSettings); + setNetworkSettings(networkSettings); if (!firstNetworkSettings.current) { - firstNetworkSettings.current = resp.result as NetworkSettings; + firstNetworkSettings.current = networkSettings; } setNetworkSettingsLoaded(true); }); @@ -119,8 +120,9 @@ export default function SettingsNetworkRoute() { const getNetworkState = useCallback(() => { send("getNetworkState", {}, resp => { if ("error" in resp) return; - console.log(resp.result); - setNetworkState(resp.result as NetworkState); + const networkState = resp.result as NetworkState; + console.debug("Network state:", networkState); + setNetworkState(networkState); }); }, [send, setNetworkState]); @@ -136,9 +138,10 @@ export default function SettingsNetworkRoute() { setNetworkSettingsLoaded(true); return; } + const networkSettings = resp.result as NetworkSettings; // We need to update the firstNetworkSettings ref to the new settings so we can use it to determine if the settings have changed - firstNetworkSettings.current = resp.result as NetworkSettings; - setNetworkSettings(resp.result as NetworkSettings); + firstNetworkSettings.current = networkSettings; + setNetworkSettings(networkSettings); getNetworkState(); setNetworkSettingsLoaded(true); notifications.success("Network settings saved"); diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx index 09703c3..2a7e190 100644 --- a/ui/src/routes/devices.$id.settings.tsx +++ b/ui/src/routes/devices.$id.settings.tsx @@ -73,7 +73,7 @@ export default function SettingsRoute() { // For some reason, the focus trap is not disabled immediately // so we need to blur the active element (document.activeElement as HTMLElement)?.blur(); - console.log("Just disabled focus trap"); + console.debug("Just disabled focus trap"); }, 300); return () => { diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx index efc52df..81ad5fd 100644 --- a/ui/src/routes/devices.$id.tsx +++ b/ui/src/routes/devices.$id.tsx @@ -216,7 +216,7 @@ export default function KvmIdRoute() { clearInterval(checkInterval); setLoadingMessage("Connection established"); } else if (attempts >= 10) { - console.log( + console.warn( "[setRemoteSessionDescription] Failed to establish connection after 10 attempts", { connectionState: pc.connectionState, @@ -444,7 +444,7 @@ export default function KvmIdRoute() { if (isNewSignalingEnabled) { sendWebRTCSignal("offer", { sd: sd }); } else { - console.log("Legacy signanling. Waiting for ICE Gathering to complete..."); + console.log("Legacy signaling. Waiting for ICE Gathering to complete..."); } } catch (e) { console.error( @@ -511,7 +511,7 @@ export default function KvmIdRoute() { useEffect(() => { if (peerConnectionState === "failed") { - console.log("Connection failed, closing peer connection"); + console.warn("Connection failed, closing peer connection"); cleanupAndStopReconnecting(); } }, [peerConnectionState, cleanupAndStopReconnecting]); @@ -616,20 +616,24 @@ export default function KvmIdRoute() { } if (resp.method === "networkState") { - console.log("Setting network state", resp.params); + console.debug("Setting network state", resp.params); setNetworkState(resp.params as NetworkState); } if (resp.method === "keyboardLedState") { const ledState = resp.params as KeyboardLedState; - console.log("Setting keyboard led state", ledState); + console.debug("Setting keyboard led state", ledState); setKeyboardLedState(ledState); } if (resp.method === "keysDownState") { const downState = resp.params as KeysDownState; - console.log("Setting key down state", downState); - setKeysDownState(downState); + + if (downState) { + console.debug("Setting key down state:", downState); + setKeysDownState(downState); + setKeyPressAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport + } } if (resp.method === "otaState") { @@ -681,8 +685,12 @@ export default function KvmIdRoute() { console.error("Failed to get keyboard led state", resp.error); return; } - console.log("Keyboard led state", resp.result); - setKeyboardLedState(resp.result as KeyboardLedState); + const ledState = resp.result as KeyboardLedState; + + if (ledState) { + console.debug("Keyboard led state: ", resp.result); + setKeyboardLedState(resp.result as KeyboardLedState); + } setNeedLedState(false); }); }, [rpcDataChannel?.readyState, send, setKeyboardLedState, keyboardLedState, needLedState]); @@ -705,10 +713,15 @@ export default function KvmIdRoute() { } else { console.error("Failed to get key down state", resp.error); } - return; + } else { + const downState = resp.result as KeysDownState; + + if (downState) { + console.debug("Keyboard key down state", downState); + setKeysDownState(downState); + setKeyPressAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport + } } - console.log("Keyboard key down state", resp.result); - setKeysDownState(resp.result as KeysDownState); setNeedKeyDownState(false); }); }, [keysDownState, needKeyDownState, rpcDataChannel?.readyState, send, setKeyPressAvailable, setKeysDownState]); @@ -725,7 +738,7 @@ export default function KvmIdRoute() { useEffect(() => { if (!diskChannel || !file) return; diskChannel.onmessage = async e => { - console.log("Received", e.data); + console.debug("Received", e.data); const data = JSON.parse(e.data); const blob = file.slice(data.start, data.end); const buf = await blob.arrayBuffer();