Ver código fonte

Added instances

TC 2 dias atrás
pai
commit
29f0c4c318
42 arquivos alterados com 202 adições e 3465 exclusões
  1. 2 0
      .gitignore
  2. 5 0
      dezukvmd/mod/kvmhid/handler.go
  3. 21 0
      dezukvmd/www/forgot-password.html
  4. 130 5
      dezukvmd/www/index.html
  5. 13 8
      dezukvmd/www/js/kvmevt.js
  6. 31 0
      dezukvmd/www/no_session.html
  7. 0 18
      remdeskd/.vscode/c_cpp_properties.json
  8. 0 24
      remdeskd/.vscode/launch.json
  9. 0 59
      remdeskd/.vscode/settings.json
  10. 0 38
      remdeskd/api.go
  11. 0 44
      remdeskd/configure.go
  12. 0 17
      remdeskd/configure_chip.sh
  13. 0 33
      remdeskd/debug.sh
  14. BIN
      remdeskd/dezukvmd
  15. 0 14
      remdeskd/go.mod
  16. 0 11
      remdeskd/go.sum
  17. 0 5
      remdeskd/ipkvm.go
  18. 0 217
      remdeskd/kvmscan.go
  19. 0 106
      remdeskd/main.go
  20. 0 81
      remdeskd/mod/remdesaux/handlers.go
  21. 0 118
      remdeskd/mod/remdesaux/remdesaux.go
  22. 0 247
      remdeskd/mod/remdeshid/ch9329.go
  23. 0 64
      remdeskd/mod/remdeshid/handler.go
  24. 0 319
      remdeskd/mod/remdeshid/keyboard.go
  25. 0 130
      remdeskd/mod/remdeshid/mouse.go
  26. 0 176
      remdeskd/mod/remdeshid/remdeshid.go
  27. 0 65
      remdeskd/mod/remdeshid/typedef.go
  28. 0 319
      remdeskd/mod/usbcapture/audio_device.go
  29. BIN
      remdeskd/mod/usbcapture/stream_takeover.jpg
  30. BIN
      remdeskd/mod/usbcapture/stream_takeover.psd
  31. 0 53
      remdeskd/mod/usbcapture/typedef.go
  32. 0 83
      remdeskd/mod/usbcapture/usbcapture.go
  33. 0 414
      remdeskd/mod/usbcapture/video_device.go
  34. 0 98
      remdeskd/tools.go
  35. 0 175
      remdeskd/usbkvm.go
  36. BIN
      remdeskd/www/img/cursor_overlay.png
  37. BIN
      remdeskd/www/img/cursor_overlay.psd
  38. 0 31
      remdeskd/www/index.html
  39. 0 1
      remdeskd/www/js/jquery-3.7.1.min.js
  40. 0 386
      remdeskd/www/kvmevt.js
  41. 0 34
      remdeskd/www/main.css
  42. 0 72
      remdeskd/www/ui.js

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+remdeskd/remdeskd
+remdeskd/config/*

+ 5 - 0
dezukvmd/mod/kvmhid/handler.go

@@ -44,6 +44,11 @@ func (c *Controller) HIDWebSocketHandler(w http.ResponseWriter, r *http.Request)
 		if err != nil {
 			errmsg := map[string]string{"error": err.Error()}
 			if err := conn.WriteJSON(errmsg); err != nil {
+				// Check for broken pipe error to handle closed websocket
+				if err != nil && (err.Error() == "write: broken pipe" || err.Error() == "broken pipe") {
+					log.Println("WebSocket connection closed (broken pipe), cleaning up")
+					break
+				}
 				log.Println("Error writing message:", err)
 				continue
 			}

+ 21 - 0
dezukvmd/www/forgot-password.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <title>Forgot Password | DezuKVM</title>
+        <meta name="csrf_token" content="">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <meta name="dezukvm.csrf.token" content="{{.csrfToken}}">
+        <link rel="icon" type="image/png" href="/favicon.png">
+        <script src="js/jquery-3.7.1.min.js"></script>
+        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.4/semantic.min.css" integrity="sha512-ySrYzxj+EI1e9xj/kRYqeDL5l1wW0IWY8pzHNTIZ+vc1D3Z14UDNPbwup4yOUmlRemYjgUXsUZ/xvCQU2ThEAw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.4/semantic.min.js" integrity="sha512-Y/wIVu+S+XJsDL7I+nL50kAVFLMqSdvuLqF2vMoRqiMkmvcqFjEpEgeu6Rx8tpZXKp77J8OUpMKy0m3jLYhbbw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+        
+        <body>
+            <div class="ui middle aligned center aligned grid" style="height: 100vh;">
+                <div class="column">
+                    <h2 class="ui header">good luck :)</h2>
+                </div>
+            </div>
+        </body>
+        </html>

+ 130 - 5
dezukvmd/www/index.html

@@ -111,6 +111,21 @@
                     background: #222;
                 }
             }
+
+            #session[contentfor="session"] {
+                height: 100%;
+                width: 100%;
+                padding: 0;
+                margin: 0;
+                display: flex;
+                flex-direction: column;
+            }
+            #sessionContext {
+                border: none;
+                width: 100%;
+                height: 100%;
+                display: block;
+            }
         </style>
     </head>
     <body>
@@ -122,10 +137,10 @@
                             <img src="img/logo.png" alt="Logo">
                         </div>
                         <div class="menu-options">
-                            <div class="item"><i class="ui server icon"></i></div>
-                            <div class="active item"><i class="ui desktop icon"></i></div>
-                            <div class="item"><i class="ui folder icon"></i></div>
-                            <div class="item"><i class="ui plug icon"></i></div>
+                            <div class="active item" menu="instances"><i class="ui server icon"></i></div>
+                            <div class="item" menu="session"><i class="ui desktop icon"></i></div>
+                            <div class="item" menu="files"><i class="ui folder icon"></i></div>
+                            <div class="item" menu="power"><i class="ui plug icon"></i></div>
                         </div>
                     </div>
                 </div>
@@ -135,8 +150,118 @@
                 </div>
             </nav>
             <main class="content">
-              
+            <div contentfor="instances" class="ui container" style="padding: 1rem; height: 100%; overflow-y: auto;">
+                <br>
+                <h2>Instances</h2>
+                <div id="instanceList">
+                    <div class="ui active inverted dimmer">
+                        <div class="ui text loader">Loading Instances...</div>
+                    </div>
+                </div>
+            </div>
+            <div id="session" contentfor="session">
+                <iframe id="sessionContext" src="no_session.html"></iframe>
+            </div>
+            <div id="files" contentfor="files">
+                <h2>File Management</h2>
+            </div>
+            <div id="power" contentfor="power">
+                <h2>Power Controls</h2>
+            </div>
             </main>
         </div>
+        <script>
+            let currentTab = 'instances';
+            $(document).ready(function() {
+                listInstances();
+            });
+
+            $('#sessionContext').on('load', function() {
+                this.contentWindow.focus();
+            });
+
+
+            $('.sidebar .menu-options .item').on('click', function() {
+                $('.sidebar .menu-options .item').removeClass('active');
+                $(this).addClass('active');
+                const menu = $(this).attr('menu');
+                $('.content > [contentfor]').hide();
+                $('.content > [contentfor="' + menu + '"]').show();
+                currentTab = menu;
+                if (menu === 'session') {
+                    $('#sessionContext').focus();
+                }
+            });
+            $('.content > [contentfor]').hide();
+            $('.content > [contentfor="instances"]').show();
+
+            $(window).on('focus', function() {
+                if (currentTab === 'session') {
+                    $('#sessionContext').focus();
+                }
+            });
+
+            function renderInstance(instance) {
+                return `
+                    <div class="ui segment" style="margin-bottom: 1em; position: relative;">
+                        <div class="ui header">
+                            Instance UUID: ${instance.uuid}
+                        </div>
+                        <div class="ui list">
+                            <div class="item"><strong>Video Device:</strong> ${instance.video_capture_dev}</div>
+                            <div class="item"><strong>Resolution:</strong> ${instance.video_resolution_width}x${instance.video_resolution_height}</div>
+                            <div class="item"><strong>Framerate:</strong> ${instance.video_framerate} fps</div>
+                            <div class="item"><strong>Audio Device:</strong> ${instance.audio_capture_dev}</div>
+                            <div class="item"><strong>Audio Channels:</strong> ${instance.audio_channels}</div>
+                            <div class="item"><strong>Audio Sample Rate:</strong> ${instance.audio_sample_rate} Hz</div>
+                            <div class="item"><strong>Aux MCU Device:</strong> ${instance.aux_mcu_device}</div>
+                            <div class="item"><strong>USB KVM Device:</strong> ${instance.usb_kvm_device}</div>
+                            <div class="item"><strong>USB Mass Storage Side:</strong> ${instance.usb_mass_storage_side}</div>
+                            <div class="item"><strong>Stream Info:</strong> ${instance.stream_info}</div>
+                        </div>
+                        <button class="ui primary button" style="position: absolute; bottom: 1em; right: 1em;"
+                            onclick="startSession('${instance.uuid}')">
+                            Launch Viewport
+                        </button>
+                    </div>
+                `;
+            }
+
+            function connectToSession(sessionId, callback=undefined) {
+                $('#sessionContext').attr('src', `/viewport.html#${sessionId}`);
+                if (callback) callback();
+            }
+
+            function startSession(sessionId) {
+                connectToSession(sessionId, function() {
+                    // Switch to session tab
+                    $('.sidebar .menu-options .item').removeClass('active');
+                    $('.sidebar .menu-options .item[menu="session"]').addClass('active');
+                    $('.content > [contentfor]').hide();
+                    $('.content > [contentfor="session"]').show();
+                });
+            }
+
+            function listInstances() {
+                $.get('/api/v1/instances', function(data) {
+                    let instances = [];
+                    try {
+                        instances = typeof data === 'string' ? JSON.parse(data) : data;
+                    } catch (e) {
+                        instances = [];
+                    }
+                    const $list = $('#instanceList');
+                    $list.empty();
+                    if (instances.length === 0) {
+                        $list.append('<div class="ui message">No instances found.</div>');
+                        return;
+                    }
+                    instances.forEach(function(instance) {
+                        $list.append(renderInstance(instance));
+                    });
+                });
+            }
+
+        </script>
     </body>
 </html>

+ 13 - 8
dezukvmd/www/js/kvmevt.js

@@ -163,12 +163,7 @@ function handleMouseScroll(event) {
     }
 }
 
-// Attach mouse event listeners
-let remoteCaptureEle = document.getElementById(cursorCaptureElementId);
-remoteCaptureEle.addEventListener('mousemove', handleMouseMove);
-remoteCaptureEle.addEventListener('mousedown', handleMousePress);
-remoteCaptureEle.addEventListener('mouseup', handleMouseRelease);
-remoteCaptureEle.addEventListener('wheel', handleMouseScroll);
+
 
 /* Keyboard */
 function isNumpadEvent(event) {
@@ -265,10 +260,20 @@ function startHidWebSocket(){
         //console.log('Message from server ', event.data);
     });
 
-    document.addEventListener('keydown', handleKeyDown);
-    document.addEventListener('keyup', handleKeyUp);
+  
 }
 
+// Attach keyboard event listeners
+const remoteCaptureEle = document.getElementById(cursorCaptureElementId);
+document.addEventListener('keydown', handleKeyDown);
+document.addEventListener('keyup', handleKeyUp);
+
+// Attach mouse event listeners
+remoteCaptureEle.addEventListener('mousemove', handleMouseMove);
+remoteCaptureEle.addEventListener('mousedown', handleMousePress);
+remoteCaptureEle.addEventListener('mouseup', handleMouseRelease);
+remoteCaptureEle.addEventListener('wheel', handleMouseScroll);
+
 function stopWebSocket(){
     if (!hidsocket){
         alert("No ws connection to stop");

+ 31 - 0
dezukvmd/www/no_session.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <title>No Session | DezuKVM</title>
+        <meta name="csrf_token" content="">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <meta name="dezukvm.csrf.token" content="{{.csrfToken}}">
+        <link rel="icon" type="image/png" href="/favicon.png">
+        <script src="js/jquery-3.7.1.min.js"></script>
+        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.4/semantic.min.css" integrity="sha512-ySrYzxj+EI1e9xj/kRYqeDL5l1wW0IWY8pzHNTIZ+vc1D3Z14UDNPbwup4yOUmlRemYjgUXsUZ/xvCQU2ThEAw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.4/semantic.min.js" integrity="sha512-Y/wIVu+S+XJsDL7I+nL50kAVFLMqSdvuLqF2vMoRqiMkmvcqFjEpEgeu6Rx8tpZXKp77J8OUpMKy0m3jLYhbbw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+        <style>
+            body {
+                background: #000 !important;
+                color: #fff !important;
+            }
+            .ui.header, .ui.column {
+                color: #fff !important;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="ui middle aligned center aligned grid" style="height: 100vh;">
+            <div class="column">
+                <h3 class="ui header"><i class="ui green circle check icon"></i> No Connected Session</h3>
+                <p>Please create or connect to a session from the <strong>Instances</strong> tab.</p>
+            </div>
+        </div>
+    </body>
+</html>

+ 0 - 18
remdeskd/.vscode/c_cpp_properties.json

@@ -1,18 +0,0 @@
-{
-  "configurations": [
-    {
-      "name": "windows-gcc-x64",
-      "includePath": [
-        "${workspaceFolder}/**"
-      ],
-      "compilerPath": "C:/TDM-GCC-64/bin/gcc.exe",
-      "cStandard": "${default}",
-      "cppStandard": "${default}",
-      "intelliSenseMode": "windows-gcc-x64",
-      "compilerArgs": [
-        ""
-      ]
-    }
-  ],
-  "version": 4
-}

+ 0 - 24
remdeskd/.vscode/launch.json

@@ -1,24 +0,0 @@
-{
-  "version": "0.2.0",
-  "configurations": [
-    {
-      "name": "C/C++ Runner: Debug Session",
-      "type": "cppdbg",
-      "request": "launch",
-      "args": [],
-      "stopAtEntry": false,
-      "externalConsole": true,
-      "cwd": "d:/Invention/RedesKVM/RemdesKVM/usbkvm/usbkvm_fw/src/remdesHid",
-      "program": "d:/Invention/RedesKVM/RemdesKVM/usbkvm/usbkvm_fw/src/remdesHid/build/Debug/outDebug",
-      "MIMode": "gdb",
-      "miDebuggerPath": "gdb",
-      "setupCommands": [
-        {
-          "description": "Enable pretty-printing for gdb",
-          "text": "-enable-pretty-printing",
-          "ignoreFailures": true
-        }
-      ]
-    }
-  ]
-}

+ 0 - 59
remdeskd/.vscode/settings.json

@@ -1,59 +0,0 @@
-{
-  "C_Cpp_Runner.cCompilerPath": "gcc",
-  "C_Cpp_Runner.cppCompilerPath": "g++",
-  "C_Cpp_Runner.debuggerPath": "gdb",
-  "C_Cpp_Runner.cStandard": "",
-  "C_Cpp_Runner.cppStandard": "",
-  "C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvarsall.bat",
-  "C_Cpp_Runner.useMsvc": false,
-  "C_Cpp_Runner.warnings": [
-    "-Wall",
-    "-Wextra",
-    "-Wpedantic",
-    "-Wshadow",
-    "-Wformat=2",
-    "-Wcast-align",
-    "-Wconversion",
-    "-Wsign-conversion",
-    "-Wnull-dereference"
-  ],
-  "C_Cpp_Runner.msvcWarnings": [
-    "/W4",
-    "/permissive-",
-    "/w14242",
-    "/w14287",
-    "/w14296",
-    "/w14311",
-    "/w14826",
-    "/w44062",
-    "/w44242",
-    "/w14905",
-    "/w14906",
-    "/w14263",
-    "/w44265",
-    "/w14928"
-  ],
-  "C_Cpp_Runner.enableWarnings": true,
-  "C_Cpp_Runner.warningsAsError": false,
-  "C_Cpp_Runner.compilerArgs": [],
-  "C_Cpp_Runner.linkerArgs": [],
-  "C_Cpp_Runner.includePaths": [],
-  "C_Cpp_Runner.includeSearch": [
-    "*",
-    "**/*"
-  ],
-  "C_Cpp_Runner.excludeSearch": [
-    "**/build",
-    "**/build/**",
-    "**/.*",
-    "**/.*/**",
-    "**/.vscode",
-    "**/.vscode/**"
-  ],
-  "C_Cpp_Runner.useAddressSanitizer": false,
-  "C_Cpp_Runner.useUndefinedSanitizer": false,
-  "C_Cpp_Runner.useLeakSanitizer": false,
-  "C_Cpp_Runner.showCompilationTime": false,
-  "C_Cpp_Runner.useLinkTimeOptimization": false,
-  "C_Cpp_Runner.msvcSecureNoWarnings": false
-}

+ 0 - 38
remdeskd/api.go

@@ -1,38 +0,0 @@
-package main
-
-import "net/http"
-
-func registerAPIRoutes() {
-	// Start the web server
-	http.Handle("/", http.FileServer(webfs))
-	http.HandleFunc("/hid", usbKVM.HIDWebSocketHandler)
-	http.HandleFunc("/audio", usbCaptureDevice.AudioStreamingHandler)
-	http.HandleFunc("/stream", usbCaptureDevice.ServeVideoStream)
-}
-
-// Aux APIs for USB KVM mode
-func registerLocalAuxRoutes() {
-	http.HandleFunc("/aux/switchusbkvm", auxMCU.HandleSwitchUSBToKVM)
-	http.HandleFunc("/aux/switchusbremote", auxMCU.HandleSwitchUSBToRemote)
-	http.HandleFunc("/aux/presspower", auxMCU.HandlePressPowerButton)
-	http.HandleFunc("/aux/releasepower", auxMCU.HandleReleasePowerButton)
-	http.HandleFunc("/aux/pressreset", auxMCU.HandlePressResetButton)
-	http.HandleFunc("/aux/releasereset", auxMCU.HandleReleaseResetButton)
-	http.HandleFunc("/aux/getuuid", auxMCU.HandleGetUUID)
-}
-
-// Dummy Aux APIs for setups that do not have an aux MCU
-func registerDummyLocalAuxRoutes() {
-	dummyHandler := func(w http.ResponseWriter, r *http.Request) {
-		w.WriteHeader(http.StatusNotImplemented)
-		w.Write([]byte("Not implemented"))
-	}
-
-	http.HandleFunc("/aux/switchusbkvm", dummyHandler)
-	http.HandleFunc("/aux/switchusbremote", dummyHandler)
-	http.HandleFunc("/aux/presspower", dummyHandler)
-	http.HandleFunc("/aux/releasepower", dummyHandler)
-	http.HandleFunc("/aux/pressreset", dummyHandler)
-	http.HandleFunc("/aux/releasereset", dummyHandler)
-	http.HandleFunc("/aux/getuuid", dummyHandler)
-}

+ 0 - 44
remdeskd/configure.go

@@ -1,44 +0,0 @@
-package main
-
-import (
-	"log"
-	"time"
-
-	"imuslab.com/dezukvm/dezukvmd/mod/remdeshid"
-)
-
-func SetupHIDCommunication(config *UsbKvmConfig) error {
-	// Initiate the HID controller
-	usbKVM = remdeshid.NewHIDController(&remdeshid.Config{
-		PortName:          config.USBKVMDevicePath,
-		BaudRate:          config.USBKVMBaudrate,
-		ScrollSensitivity: 0x01, // Set mouse scroll sensitivity
-	})
-
-	//Start the HID controller
-	err := usbKVM.Connect()
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	time.Sleep(1 * time.Second) // Wait for the controller to initialize
-	log.Println("Updating chip baudrate to 115200...")
-	//Configure the HID controller
-	err = usbKVM.ConfigureChipTo115200()
-	if err != nil {
-		log.Fatalf("Failed to configure chip baudrate: %v", err)
-		return err
-	}
-	time.Sleep(1 * time.Second)
-
-	log.Println("Setting chip USB device properties...")
-	time.Sleep(2 * time.Second) // Wait for the controller to initialize
-	_, err = usbKVM.WriteChipProperties()
-	if err != nil {
-		log.Fatalf("Failed to write chip properties: %v", err)
-		return err
-	}
-
-	log.Println("Configuration command sent. Unplug the device and plug it back in to apply the changes.")
-	return nil
-}

+ 0 - 17
remdeskd/configure_chip.sh

@@ -1,17 +0,0 @@
-#!/bin/bash
-
-# -----------------------------------------------------------------------------
-# Script to start dezukvmd in config chip mode for CH9329 configuration.
-#
-# This script is intended for use with newly soldered CH9329 chips, which have
-# a default baudrate of 9600. It allows setting the chip's default baudrate to
-# 115200, enabling higher speed for keyboard and mouse virtual machine operation.
-#
-# The configuration port used here is shared with the USB KVM mode, as defined
-# in the config/usbkvm.json file.
-#
-# Author: [email protected]
-#
-# License: GPLv3
-# -----------------------------------------------------------------------------
-sudo ./dezukvmd -mode=cfgchip

+ 0 - 33
remdeskd/debug.sh

@@ -1,33 +0,0 @@
-#!/bin/bash
-
-echo "This script helps debug audio and USB KVM devices."
-echo "Make sure you have the necessary permissions to access audio and USB devices."
-echo ""
-echo "------------------"
-echo "Checking for required tools..."
-# Check if 'arecord' (ALSA tool) is installed
-if ! command -v arecord &> /dev/null; then
-    echo "Warning: 'arecord' (ALSA audio recorder) is not installed. Please install it for audio debugging."
-else
-    echo "'arecord' is installed."
-fi
-
-# Check if 'v4l2-ctl' (Video4Linux2 control tool) is installed
-if ! command -v v4l2-ctl &> /dev/null; then
-    echo "Warning: 'v4l2-ctl' (Video4Linux2 control tool) is not installed. Please install it for USB video device debugging."
-else
-    echo "'v4l2-ctl' is installed."
-fi
-
-# List all audio devices
-echo "------------------"
-echo "Listing audio devices"
-sudo ./dezukvmd -mode=debug -tool=audio-devices
-
-# List all USB KVM devices
-echo "------------------"
-echo "Listing USB KVM devices"
-sudo ./dezukvmd -mode=debug -tool=list-usbkvm
-echo "------------------"
-echo "The finalized KVM device group is listed below: "
-sudo ./dezukvmd -mode=debug -tool=list-usbkvm-json

BIN
remdeskd/dezukvmd


+ 0 - 14
remdeskd/go.mod

@@ -1,14 +0,0 @@
-module imuslab.com/dezukvm/dezukvmd
-
-go 1.23.4
-
-require (
-	github.com/gorilla/websocket v1.5.3
-	github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
-	github.com/vladimirvivien/go4vl v0.0.5
-)
-
-require (
-	github.com/pion/opus v0.0.0-20250618074346-646586bb17bf // indirect
-	golang.org/x/sys v0.31.0 // indirect
-)

+ 0 - 11
remdeskd/go.sum

@@ -1,11 +0,0 @@
-github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
-github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/pion/opus v0.0.0-20250618074346-646586bb17bf h1:aVhn89Yu8oTedA+E/Ge3LHk1FvFBThogBP/a0WkhGeI=
-github.com/pion/opus v0.0.0-20250618074346-646586bb17bf/go.mod h1:MF0ECGlX1vw71XHaPvRqZoeFED6QTwvFL71vbsd29yY=
-github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
-github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
-github.com/vladimirvivien/go4vl v0.0.5 h1:jHuo/CZOAzYGzrSMOc7anOMNDr03uWH5c1B5kQ+Chnc=
-github.com/vladimirvivien/go4vl v0.0.5/go.mod h1:FP+/fG/X1DUdbZl9uN+l33vId1QneVn+W80JMc17OL8=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

+ 0 - 5
remdeskd/ipkvm.go

@@ -1,5 +0,0 @@
-package main
-
-func init_ipkvm_mode() error {
-	return nil
-}

+ 0 - 217
remdeskd/kvmscan.go

@@ -1,217 +0,0 @@
-package main
-
-import (
-	"errors"
-	"log"
-	"os/exec"
-	"path/filepath"
-	"regexp"
-	"strings"
-
-	"imuslab.com/dezukvm/dezukvmd/mod/remdesaux"
-)
-
-/*
-Each of the USB-KVM device has the same set of USB devices
-connected under a single USB hub chip. This function
-will scan the USB device tree to find the connected
-USB devices and match them to the configured device paths.
-
-Commonly found devices are:
-- USB hub (the main hub chip)
--- USB UART device (HID KVM)
--- USB CDC ACM device (auxiliary MCU)
--- USB Video Class device (webcam capture)
--- USB Audio Class device (audio capture)
-
-The AuxMCU will provide a UUID to uniquely identify
-the USB KVM device subtree.
-*/
-type UsbKvmDevice struct {
-	UUID               string   // 16 bytes UUID obtained from AuxMCU, might change after power cycle
-	USBKVMDevicePath   string   // e.g. /dev/ttyUSB0
-	AuxMCUDevicePath   string   // e.g. /dev/ttyACM0
-	CaptureDevicePaths []string // e.g. /dev/video0, /dev/video1, etc.
-	AlsaDevicePaths    []string // e.g. /dev/snd/pcmC1D0c, etc.
-}
-
-// populateUsbKvmUUID tries to get the UUID from the AuxMCU device
-func populateUsbKvmUUID(dev *UsbKvmDevice) error {
-	if dev.AuxMCUDevicePath == "" {
-		return nil
-	}
-
-	// The standard baudrate for AuxMCU is 115200
-	aux, err := remdesaux.NewAuxOutbandController(dev.AuxMCUDevicePath, 115200)
-	if err != nil {
-		return err
-	}
-	defer aux.Close()
-
-	uuid, err := aux.GetUUID()
-	if err != nil {
-		return err
-	}
-
-	dev.UUID = uuid
-	return nil
-}
-
-func discoverUsbKvmSubtree() ([]*UsbKvmDevice, error) {
-	// Scan all /dev/tty*, /dev/video*, /dev/snd/pcmC* devices
-	getMatchingDevs := func(pattern string) ([]string, error) {
-		files, err := filepath.Glob(pattern)
-		if err != nil {
-			return nil, err
-		}
-		return files, nil
-	}
-
-	// Get all ttyUSB*, ttyACM*
-	ttyDevs1, _ := getMatchingDevs("/dev/ttyUSB*")
-	ttyDevs2, _ := getMatchingDevs("/dev/ttyACM*")
-	ttyDevs := append(ttyDevs1, ttyDevs2...)
-
-	// Get all video*
-	videoDevs, _ := getMatchingDevs("/dev/video*")
-
-	// Get all ALSA PCM devices (USB audio is usually card > 0)
-	alsaDevs, _ := getMatchingDevs("/dev/snd/pcmC*")
-
-	type devInfo struct {
-		path    string
-		sysPath string
-	}
-
-	getSys := func(devs []string) []devInfo {
-		var out []devInfo
-		for _, d := range devs {
-			sys, err := getDeviceFullPath(d)
-			if err == nil {
-				out = append(out, devInfo{d, sys})
-			}
-		}
-		return out
-	}
-
-	ttys := getSys(ttyDevs)
-	videos := getSys(videoDevs)
-	alsas := getSys(alsaDevs)
-
-	// Find common USB root hub prefix
-	hubPattern := regexp.MustCompile(`^\d+-\d+(\.\d+)*$`)
-	getHub := func(sys string) string {
-		parts := strings.Split(sys, "/")
-		for i := range parts {
-			// Look for USB hub pattern (e.g. 1-2, 2-1, etc.)
-			if hubPattern.MatchString(parts[i]) {
-				return strings.Join(parts[:i+1], "/")
-			}
-		}
-		return ""
-	}
-
-	// Map hub -> device info
-	type hubGroup struct {
-		ttys   []string
-		acms   []string
-		videos []string
-		alsas  []string
-	}
-	hubs := make(map[string]*hubGroup)
-
-	for _, t := range ttys {
-		hub := getHub(t.sysPath)
-		if hub != "" {
-			if hubs[hub] == nil {
-				hubs[hub] = &hubGroup{}
-			}
-			if strings.Contains(t.path, "ACM") {
-				hubs[hub].acms = append(hubs[hub].acms, t.path)
-			} else {
-				hubs[hub].ttys = append(hubs[hub].ttys, t.path)
-			}
-		}
-	}
-	for _, v := range videos {
-		hub := getHub(v.sysPath)
-		if hub != "" {
-			if hubs[hub] == nil {
-				hubs[hub] = &hubGroup{}
-			}
-			hubs[hub].videos = append(hubs[hub].videos, v.path)
-		}
-	}
-	for _, alsa := range alsas {
-		hub := getHub(alsa.sysPath)
-		if hub != "" {
-			if hubs[hub] == nil {
-				hubs[hub] = &hubGroup{}
-			}
-			hubs[hub].alsas = append(hubs[hub].alsas, alsa.path)
-		}
-	}
-
-	var result []*UsbKvmDevice
-	for _, g := range hubs {
-		// At least one tty or acm, one video, optionally alsa
-		if (len(g.ttys) > 0 || len(g.acms) > 0) && len(g.videos) > 0 {
-			// Pick the first tty as USBKVMDevicePath, first acm as AuxMCUDevicePath
-			usbKvm := ""
-			auxMcu := ""
-			if len(g.ttys) > 0 {
-				usbKvm = g.ttys[0]
-			}
-			if len(g.acms) > 0 {
-				auxMcu = g.acms[0]
-			}
-			result = append(result, &UsbKvmDevice{
-				USBKVMDevicePath:   usbKvm,
-				AuxMCUDevicePath:   auxMcu,
-				CaptureDevicePaths: g.videos,
-				AlsaDevicePaths:    g.alsas,
-			})
-		}
-	}
-
-	// Populate UUIDs
-	for _, dev := range result {
-		err := populateUsbKvmUUID(dev)
-		if err != nil {
-			log.Printf("Warning: could not get UUID for AuxMCU %s: %v, is this a third party device?", dev.AuxMCUDevicePath, err)
-		}
-	}
-
-	if len(result) == 0 {
-		return nil, errors.New("no USB KVM device found")
-	}
-	return result, nil
-}
-
-func resolveSymlink(path string) (string, error) {
-	resolved, err := filepath.EvalSymlinks(path)
-	if err != nil {
-		return "", err
-	}
-	return resolved, nil
-}
-
-func getDeviceFullPath(devicePath string) (string, error) {
-	resolvedPath, err := resolveSymlink(devicePath)
-	if err != nil {
-		return "", err
-	}
-
-	// Use udevadm to get the device chain
-	out, err := exec.Command("udevadm", "info", "-q", "path", "-n", resolvedPath).Output()
-	if err != nil {
-		return "", err
-	}
-	sysPath := strings.TrimSpace(string(out))
-	if sysPath == "" {
-		return "", errors.New("could not resolve sysfs path")
-	}
-
-	fullPath := "/sys" + sysPath
-	return fullPath, nil
-}

+ 0 - 106
remdeskd/main.go

@@ -1,106 +0,0 @@
-package main
-
-import (
-	"embed"
-	"flag"
-	"io/fs"
-	"log"
-	"net/http"
-	"os"
-)
-
-const (
-	defaultDevMode   = true
-	configPath       = "./config"
-	usbKvmConfigPath = configPath + "/usbkvm.json"
-)
-
-var (
-	developent = flag.Bool("dev", defaultDevMode, "Enable development mode with local static files")
-	mode       = flag.String("mode", "usbkvm", "Mode of operation: usbkvm, ipkvm or debug")
-	tool       = flag.String("tool", "", "Run debug tool, must be used with -mode=debug")
-)
-
-/* Web Server Static Files */
-//go:embed www
-var embeddedFiles embed.FS
-var webfs http.FileSystem
-
-func init() {
-	// Initiate the web server static files
-	if *developent {
-		webfs = http.Dir("./www")
-	} else {
-		// Embed the ./www folder and trim the prefix
-		subFS, err := fs.Sub(embeddedFiles, "www")
-		if err != nil {
-			log.Fatal(err)
-		}
-		webfs = http.FS(subFS)
-	}
-
-	// Initiate the config folder if not exists
-	err := os.MkdirAll("./config", 0755)
-	if err != nil {
-		log.Fatal("Failed to create config folder:", err)
-	}
-
-}
-
-func main() {
-	flag.Parse()
-
-	switch *mode {
-	case "cfgchip":
-		//Load config file or create default one
-		kvmCfg, err := loadUsbKvmConfig()
-		if err != nil {
-			log.Fatal("Failed to load or create USB KVM config:", err)
-		}
-
-		//Override the baudrate to 9600 for chip configuration
-		kvmCfg.USBKVMBaudrate = 9600
-
-		err = SetupHIDCommunication(kvmCfg)
-		if err != nil {
-			log.Fatal(err)
-		}
-	case "debug":
-		err := handle_debug_tool()
-		if err != nil {
-			log.Fatal(err)
-		}
-	case "ipkvm":
-		//Check runtime dependencies
-		err := run_dependency_precheck()
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		//Start IP-KVM mode
-		err = init_ipkvm_mode()
-		if err != nil {
-			log.Fatal(err)
-		}
-	case "usbkvm":
-		//Check runtime dependencies
-		err := run_dependency_precheck()
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		//Load config file or create default one
-		kvmCfg, err := loadUsbKvmConfig()
-		if err != nil {
-			log.Fatal("Failed to load or create USB KVM config:", err)
-		}
-
-		//Start USB KVM mode
-		err = startUsbKvmMode(kvmCfg)
-		if err != nil {
-			log.Fatal(err)
-		}
-	default:
-		log.Fatalf("Unknown mode: %s. Supported modes are: usbkvm, capture", *mode)
-	}
-}

+ 0 - 81
remdeskd/mod/remdesaux/handlers.go

@@ -1,81 +0,0 @@
-package remdesaux
-
-import (
-	"encoding/json"
-	"log"
-	"net/http"
-)
-
-// Handler for switching USB to KVM side
-func (c *AuxMcu) HandleSwitchUSBToKVM(w http.ResponseWriter, r *http.Request) {
-	if err := c.SwitchUSBToKVM(); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	log.Println("Switched USB mass storage to KVM side")
-	w.WriteHeader(http.StatusOK)
-}
-
-// Handler for switching USB to Remote side
-func (c *AuxMcu) HandleSwitchUSBToRemote(w http.ResponseWriter, r *http.Request) {
-	if err := c.SwitchUSBToRemote(); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	log.Println("Switched USB mass storage to remote side")
-	w.WriteHeader(http.StatusOK)
-}
-
-// Handler for pressing the power button
-func (c *AuxMcu) HandlePressPowerButton(w http.ResponseWriter, r *http.Request) {
-	if err := c.PressPowerButton(); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	w.WriteHeader(http.StatusOK)
-}
-
-// Handler for releasing the power button
-func (c *AuxMcu) HandleReleasePowerButton(w http.ResponseWriter, r *http.Request) {
-	if err := c.ReleasePowerButton(); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	w.WriteHeader(http.StatusOK)
-}
-
-// Handler for pressing the reset button
-func (c *AuxMcu) HandlePressResetButton(w http.ResponseWriter, r *http.Request) {
-	if err := c.PressResetButton(); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	w.WriteHeader(http.StatusOK)
-}
-
-// Handler for releasing the reset button
-func (c *AuxMcu) HandleReleaseResetButton(w http.ResponseWriter, r *http.Request) {
-	if err := c.ReleaseResetButton(); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	w.WriteHeader(http.StatusOK)
-}
-
-// Handler for getting the UUID
-func (c *AuxMcu) HandleGetUUID(w http.ResponseWriter, r *http.Request) {
-	uuid, err := c.GetUUID()
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	w.Header().Set("Content-Type", "application/json")
-	json.NewEncoder(w).Encode(map[string]string{"uuid": uuid})
-}
-
-// Handler for getting the USB mass storage side
-func (c *AuxMcu) HandleGetUSBMassStorageSide(w http.ResponseWriter, r *http.Request) {
-	side := c.GetUSBMassStorageSide()
-	w.Header().Set("Content-Type", "application/json")
-	json.NewEncoder(w).Encode(map[string]interface{}{"usb_mass_storage_side": side})
-}

+ 0 - 118
remdeskd/mod/remdesaux/remdesaux.go

@@ -1,118 +0,0 @@
-package remdesaux
-
-/*
-	RemdesAux - Auxiliary MCU Control for RemdeskVM
-
-	This module provides functions to interact with the auxiliary MCU (CH552G)
-	used in RemdeskVM for managing USB switching and power/reset button simulation.
-*/
-
-import (
-	"bufio"
-	"strings"
-	"sync"
-	"time"
-
-	"github.com/tarm/serial"
-)
-
-type USB_mass_storage_side int
-
-const (
-	USB_MASS_STORAGE_KVM USB_mass_storage_side = iota
-	USB_MASS_STORAGE_REMOTE
-)
-
-type AuxMcu struct {
-	usb_mass_storage_side USB_mass_storage_side
-	port                  *serial.Port
-	reader                *bufio.Reader
-	mu                    sync.Mutex
-}
-
-// NewAuxOutbandController initializes a new AuxMcu instance
-func NewAuxOutbandController(portName string, baudRate int) (*AuxMcu, error) {
-	c := &serial.Config{
-		Name:        portName,
-		Baud:        baudRate,
-		ReadTimeout: time.Second * 2,
-	}
-	port, err := serial.OpenPort(c)
-	if err != nil {
-		return nil, err
-	}
-	return &AuxMcu{
-		usb_mass_storage_side: USB_MASS_STORAGE_KVM, //Default to KVM side, defined in MCU firmware
-		port:                  port,
-		reader:                bufio.NewReader(port),
-	}, nil
-}
-
-func (c *AuxMcu) Close() error {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-	if c.port != nil {
-		return c.port.Close()
-	}
-	return nil
-}
-
-// sendCommand writes a single byte command to the serial port
-func (c *AuxMcu) sendCommand(cmd byte) error {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-	_, err := c.port.Write([]byte{cmd})
-	return err
-}
-
-// SwitchUSBToKVM switches USB mass storage to KVM side
-func (c *AuxMcu) SwitchUSBToKVM() error {
-	c.usb_mass_storage_side = USB_MASS_STORAGE_KVM
-	return c.sendCommand('m')
-}
-
-// SwitchUSBToRemote switches USB mass storage to remote computer
-func (c *AuxMcu) SwitchUSBToRemote() error {
-	c.usb_mass_storage_side = USB_MASS_STORAGE_REMOTE
-	return c.sendCommand('n')
-}
-
-// PressPowerButton simulates pressing the power button
-func (c *AuxMcu) PressPowerButton() error {
-	return c.sendCommand('p')
-}
-
-// ReleasePowerButton simulates releasing the power button
-func (c *AuxMcu) ReleasePowerButton() error {
-	return c.sendCommand('s')
-}
-
-// PressResetButton simulates pressing the reset button
-func (c *AuxMcu) PressResetButton() error {
-	return c.sendCommand('r')
-}
-
-// ReleaseResetButton simulates releasing the reset button
-func (c *AuxMcu) ReleaseResetButton() error {
-	return c.sendCommand('d')
-}
-
-// GetUUID requests the device UUID and returns it as a string
-func (c *AuxMcu) GetUUID() (string, error) {
-	if err := c.sendCommand('u'); err != nil {
-		return "", err
-	}
-	line, err := c.reader.ReadString('\n')
-	if err != nil {
-		return "", err
-	}
-
-	line = strings.TrimSpace(line)
-	return line, nil
-}
-
-func (c *AuxMcu) GetUSBMassStorageSide() USB_mass_storage_side {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-	return c.usb_mass_storage_side
-}

+ 0 - 247
remdeskd/mod/remdeshid/ch9329.go

@@ -1,247 +0,0 @@
-package remdeshid
-
-import (
-	"errors"
-	"fmt"
-	"time"
-)
-
-func (c *Controller) ConfigureChipTo115200() error {
-	// Send the command to get chip configuration and info
-	currentConfig, err := c.GetChipCurrentConfiguration()
-	if err != nil {
-		fmt.Printf("Error getting current configuration: %v\n", err)
-		return errors.New("failed to get current configuration")
-	}
-
-	// Modify baudrate bytes in the response
-	currentConfig[3] = 0x00 // Baudrate byte 1
-	currentConfig[4] = 0x01 // Baudrate byte 2
-	currentConfig[5] = 0xC2 // Baudrate byte 3
-	currentConfig[6] = 0x00 // Baudrate byte 4
-
-	time.Sleep(1 * time.Second) // Wait for a second before sending the command
-	// Prepare the command to set the new configuration
-	setCmd := append([]byte{0x57, 0xAB, 0x00, 0x09, 0x32}, currentConfig[:50]...)
-	setCmd = append(setCmd, calcChecksum(setCmd[:len(setCmd)-1]))
-
-	err = c.Send(setCmd)
-	if err != nil {
-		fmt.Printf("Error sending configuration command: %v\n", err)
-		return errors.New("failed to send configuration command")
-	}
-
-	// Wait for the reply
-	resp, err := c.WaitForReply(0x09)
-	if err != nil {
-		fmt.Printf("Error waiting for reply: %v\n", err)
-		return errors.New("failed to get reply")
-	}
-
-	fmt.Println()
-	fmt.Print("Reply: ")
-	for _, b := range resp {
-		fmt.Printf("0x%02X ", b)
-	}
-	fmt.Println()
-	fmt.Println("Baudrate updated to 115200 successfully")
-	return nil
-}
-
-func (c *Controller) WriteChipProperties() ([]byte, error) {
-	manufacturerString := []byte{
-		0x57, 0xAB, 0x00, 0x0B,
-		0x09, // Length of payload
-		0x00, // Set manufacturer string
-		0x07, // Length of the USB manufacturer string
-		'i', 'm', 'u', 's', 'l', 'a', 'b',
-		0x00, // Checksum placeholder
-	}
-
-	manufacturerString[14] = calcChecksum(manufacturerString[:14])
-	// Send set manufacturer string
-	err := c.Send(manufacturerString)
-	if err != nil {
-		return nil, fmt.Errorf("failed to send manufacturer string: %v", err)
-	}
-	_, err = c.WaitForReply(0x0B)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get manufacturer string response: %v", err)
-	}
-
-	productString := []byte{
-		0x57, 0xAB, 0x00, 0x0B,
-		0x0B, // Length of the payload
-		0x01, // Set product string
-		0x09, // Length of the USB product string
-		'R', 'e', 'm', 'd', 'e', 's', 'K', 'V', 'M',
-		0x00, // Checksum placeholder
-	}
-
-	productString[16] = calcChecksum(productString[:16])
-	// Send set product string
-	err = c.Send(productString)
-	if err != nil {
-		return nil, fmt.Errorf("failed to send product string: %v", err)
-	}
-	_, err = c.WaitForReply(0x0B)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get product string response: %v", err)
-	}
-
-	return []byte("OK"), nil
-}
-
-// GetChipCurrentConfiguration retrieves the current configuration of the chip.
-// It sends a command to the chip and waits for a reply.
-// Note only the data portion of the response is returned, excluding the header and checksum.
-func (c *Controller) GetChipCurrentConfiguration() ([]byte, error) {
-	//Send the command to get chip configuration and info
-	cmd := []byte{0x57, 0xAB,
-		0x00, 0x08, 0x00,
-		0x00, //placeholder for checksum
-	}
-
-	cmd[5] = calcChecksum(cmd[:5])
-	err := c.Send(cmd)
-	if err != nil {
-		fmt.Printf("Error sending command: %v\n", err)
-		return nil, errors.New("failed to send command")
-	}
-
-	resp, err := c.WaitForReply(0x08)
-	if err != nil {
-		fmt.Printf("Error waiting for reply: %v\n", err)
-		return nil, errors.New("failed to get reply")
-	}
-
-	if len(resp) < 50 {
-		fmt.Println("Invalid response length")
-		return nil, errors.New("invalid response length")
-	}
-
-	fmt.Print("Response: ")
-	for _, b := range resp {
-		fmt.Printf("0x%02X ", b)
-	}
-	fmt.Println()
-
-	return resp, nil
-}
-
-func (c *Controller) ChipSoftReset() error {
-	//Send the command to get chip configuration and info
-	cmd := []byte{0x57, 0xAB,
-		0x00, 0x0F,
-		0x00, //placeholder for checksum
-	}
-
-	cmd[4] = calcChecksum(cmd[:4])
-	err := c.Send(cmd)
-	if err != nil {
-		fmt.Printf("Error sending command: %v\n", err)
-		return errors.New("failed to send command")
-	}
-
-	_, err = c.WaitForReply(0x0F)
-	if err != nil {
-		fmt.Printf("Error waiting for reply: %v\n", err)
-		return errors.New("failed to get reply")
-	}
-
-	fmt.Println("Chip soft reset successfully")
-	return nil
-}
-
-func (c *Controller) IsModifierKeys(keycode int) bool {
-	// Modifier keycodes for JavaScript
-	modifierKeys := []int{16, 17, 18, 91} // Shift, Ctrl, Alt, Meta (Windows/Command key)
-	for _, key := range modifierKeys {
-		if keycode == key {
-			return true
-		}
-	}
-	return false
-}
-
-// ConstructAndSendCmd constructs a HID command based on the provided HIDCommand and sends it.
-func (c *Controller) ConstructAndSendCmd(HIDCommand *HIDCommand) ([]byte, error) {
-	switch HIDCommand.Event {
-	case EventTypeKeyPress:
-		if IsModifierKey(uint8(HIDCommand.Keycode)) {
-			//modifier keys
-			return c.SetModifierKey(uint8(HIDCommand.Keycode), HIDCommand.IsRightModKey)
-		} else if HIDCommand.Keycode == 13 && HIDCommand.IsRightModKey {
-			// Numpad enter
-			return c.SendKeyboardPress(uint8(146))
-		}
-		return c.SendKeyboardPress(uint8(HIDCommand.Keycode))
-	case EventTypeKeyRelease:
-		if IsModifierKey(uint8(HIDCommand.Keycode)) {
-			//modifier keys
-			return c.UnsetModifierKey(uint8(HIDCommand.Keycode), HIDCommand.IsRightModKey)
-		} else if HIDCommand.Keycode == 13 && HIDCommand.IsRightModKey {
-			// Numpad enter
-			return c.SendKeyboardRelease(uint8(146))
-		}
-		return c.SendKeyboardRelease(uint8(HIDCommand.Keycode))
-	case EventTypeMouseMove:
-		//Map mouse button state to HID state
-		leftPressed := (HIDCommand.MouseMoveButtonState & 0x01) != 0
-		middlePressed := (HIDCommand.MouseMoveButtonState & 0x02) != 0
-		rightPressed := (HIDCommand.MouseMoveButtonState & 0x04) != 0
-		if leftPressed {
-			c.hidState.MouseButtons |= 0x01
-		} else {
-			c.hidState.MouseButtons &^= 0x01
-		}
-
-		if middlePressed {
-			c.hidState.MouseButtons |= 0x04
-		} else {
-			c.hidState.MouseButtons &^= 0x04
-		}
-
-		if rightPressed {
-			c.hidState.MouseButtons |= 0x02
-		} else {
-			c.hidState.MouseButtons &^= 0x02
-		}
-
-		// Update mouse position
-		c.lastCursorEventTime = time.Now().UnixMilli()
-		if HIDCommand.MouseAbsX != 0 || HIDCommand.MouseAbsY != 0 {
-			xLSB := byte(HIDCommand.MouseAbsX & 0xFF)        // Extract LSB of X
-			xMSB := byte((HIDCommand.MouseAbsX >> 8) & 0xFF) // Extract MSB of X
-			yLSB := byte(HIDCommand.MouseAbsY & 0xFF)        // Extract LSB of Y
-			yMSB := byte((HIDCommand.MouseAbsY >> 8) & 0xFF) // Extract MSB of Y
-			return c.MouseMoveAbsolute(xLSB, xMSB, yLSB, yMSB)
-		} else if HIDCommand.MouseRelX != 0 || HIDCommand.MouseRelY != 0 {
-			//Todo
-		}
-		return []byte{}, nil
-	case EventTypeMousePress:
-		if HIDCommand.MouseButton < 1 || HIDCommand.MouseButton > 3 {
-			return nil, fmt.Errorf("invalid mouse button: %d", HIDCommand.MouseButton)
-		}
-		button := uint8(HIDCommand.MouseButton)
-		return c.MouseButtonPress(button)
-	case EventTypeMouseRelease:
-		if HIDCommand.MouseButton < 1 || HIDCommand.MouseButton > 3 {
-			return nil, fmt.Errorf("invalid mouse button: %d", HIDCommand.MouseButton)
-		}
-		button := uint8(HIDCommand.MouseButton)
-		return c.MouseButtonRelease(button)
-	case EventTypeMouseScroll:
-		if time.Now().UnixMilli()-c.lastCursorEventTime < MinCusorEventInterval {
-			// Ignore mouse move events that are too close together
-			return []byte{}, nil
-		}
-		c.lastCursorEventTime = time.Now().UnixMilli()
-		return c.MouseScroll(HIDCommand.MouseScroll)
-	case EventTypeHIDReset:
-		return []byte{}, c.ChipSoftReset()
-	default:
-		return nil, fmt.Errorf("unsupported HID command event type: %d", HIDCommand.Event)
-	}
-}

+ 0 - 64
remdeskd/mod/remdeshid/handler.go

@@ -1,64 +0,0 @@
-package remdeshid
-
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-	"net/http"
-
-	"github.com/gorilla/websocket"
-)
-
-// upgrader is used to upgrade HTTP connections to WebSocket connections
-var upgrader = websocket.Upgrader{
-	ReadBufferSize:  1024,
-	WriteBufferSize: 1024,
-	CheckOrigin: func(r *http.Request) bool {
-		return true
-	},
-}
-
-// HIDWebSocketHandler handles incoming WebSocket connections for HID commands
-func (c *Controller) HIDWebSocketHandler(w http.ResponseWriter, r *http.Request) {
-	conn, err := upgrader.Upgrade(w, r, nil)
-	if err != nil {
-		log.Println("Failed to upgrade to websocket:", err)
-		return
-	}
-	defer conn.Close()
-	for {
-		_, message, err := conn.ReadMessage()
-		if err != nil {
-			log.Println("Error reading message:", err)
-			break
-		}
-
-		//Try parsing the message as a HIDCommand
-		var hidCmd HIDCommand
-		if err := json.Unmarshal(message, &hidCmd); err != nil {
-			log.Println("Error parsing message:", err)
-			continue
-		}
-
-		bytes, err := c.ConstructAndSendCmd(&hidCmd)
-		if err != nil {
-			errmsg := map[string]string{"error": err.Error()}
-			if err := conn.WriteJSON(errmsg); err != nil {
-				log.Println("Error writing message:", err)
-				continue
-			}
-			log.Println("Error sending command:", err)
-			continue
-		}
-
-		prettyBytes := ""
-		for _, b := range bytes {
-			prettyBytes += fmt.Sprintf("0x%02X ", b)
-		}
-		if err := conn.WriteMessage(websocket.TextMessage, []byte(prettyBytes)); err != nil {
-			log.Println("Error writing message:", err)
-			continue
-		}
-
-	}
-}

+ 0 - 319
remdeskd/mod/remdeshid/keyboard.go

@@ -1,319 +0,0 @@
-package remdeshid
-
-import "errors"
-
-const (
-	MOD_LCTRL  = 0x01
-	MOD_LSHIFT = 0x02
-	MOD_LALT   = 0x04
-	MOD_LGUI   = 0x08
-	MOD_RCTRL  = 0x10
-	MOD_RSHIFT = 0x20
-	MOD_RALT   = 0x40
-	MOD_RGUI   = 0x80
-	MOD_RENTER = 0x58
-)
-
-// IsModifierKey checks if the given JavaScript keycode corresponds to a modifier key
-func IsModifierKey(keycode uint8) bool {
-	switch keycode {
-	case 16: // Shift
-		return true
-	case 17: // Control
-		return true
-	case 18: // Alt
-		return true
-	case 91: // Meta (Windows/Command key)
-		return true
-	default:
-		return false
-	}
-}
-
-func (c *Controller) SetModifierKey(keycode uint8, isRight bool) ([]byte, error) {
-	// Determine the modifier bit based on HID keycode
-	var modifierBit uint8
-	switch keycode {
-	case 17:
-		if isRight {
-			modifierBit = MOD_RCTRL
-		} else {
-			modifierBit = MOD_LCTRL
-		}
-	case 16:
-		if isRight {
-			modifierBit = MOD_RSHIFT
-		} else {
-			modifierBit = MOD_LSHIFT
-		}
-	case 18:
-		if isRight {
-			modifierBit = MOD_RALT
-		} else {
-			modifierBit = MOD_LALT
-		}
-	case 91:
-		if isRight {
-			modifierBit = MOD_RGUI
-		} else {
-			modifierBit = MOD_LGUI
-		}
-	default:
-		// Not a modifier key
-		return nil, errors.ErrUnsupported
-	}
-
-	c.hidState.Modkey |= modifierBit
-
-	return keyboardSendKeyCombinations(c)
-}
-
-func (c *Controller) UnsetModifierKey(keycode uint8, isRight bool) ([]byte, error) {
-	// Determine the modifier bit based on HID keycode
-	var modifierBit uint8
-	switch keycode {
-	case 17:
-		if isRight {
-			modifierBit = MOD_RCTRL
-		} else {
-			modifierBit = MOD_LCTRL
-		}
-	case 16:
-		if isRight {
-			modifierBit = MOD_RSHIFT
-		} else {
-			modifierBit = MOD_LSHIFT
-		}
-	case 18:
-		if isRight {
-			modifierBit = MOD_RALT
-		} else {
-			modifierBit = MOD_LALT
-		}
-	case 91:
-		if isRight {
-			modifierBit = MOD_RGUI
-		} else {
-			modifierBit = MOD_LGUI
-		}
-	default:
-		// Not a modifier key
-		return nil, errors.ErrUnsupported
-	}
-
-	c.hidState.Modkey &^= modifierBit
-	return keyboardSendKeyCombinations(c)
-}
-
-// SendKeyboardPress sends a keyboard press by JavaScript keycode
-func (c *Controller) SendKeyboardPress(keycode uint8) ([]byte, error) {
-	// Convert JavaScript keycode to HID
-	keycode = javaScriptKeycodeToHIDOpcode(keycode)
-	if keycode == 0x00 {
-		// Not supported
-		return nil, errors.New("Unsupported keycode: " + string(keycode))
-	}
-
-	// Already pressed? Skip
-	for i := 0; i < 6; i++ {
-		if c.hidState.KeyboardButtons[i] == keycode {
-			return nil, nil
-		}
-	}
-
-	// Get the empty slot in the current HID list
-	for i := 0; i < 6; i++ {
-		if c.hidState.KeyboardButtons[i] == 0x00 {
-			c.hidState.KeyboardButtons[i] = keycode
-			return keyboardSendKeyCombinations(c)
-		}
-	}
-
-	// No space left
-	return nil, errors.New("No space left in keyboard state to press key: " + string(keycode))
-}
-
-// SendKeyboardRelease sends a keyboard release by JavaScript keycode
-func (c *Controller) SendKeyboardRelease(keycode uint8) ([]byte, error) {
-	// Convert JavaScript keycode to HID
-	keycode = javaScriptKeycodeToHIDOpcode(keycode)
-	if keycode == 0x00 {
-		// Not supported
-		return nil, errors.New("Unsupported keycode: " + string(keycode))
-	}
-
-	// Find the position where the key is pressed
-	for i := 0; i < 6; i++ {
-		if c.hidState.KeyboardButtons[i] == keycode {
-			c.hidState.KeyboardButtons[i] = 0x00
-			return keyboardSendKeyCombinations(c)
-		}
-	}
-
-	// That key is not pressed
-	return nil, nil
-}
-
-// keyboardSendKeyCombinations simulates sending the current key combinations
-func keyboardSendKeyCombinations(c *Controller) ([]byte, error) {
-	// Prepare the packet
-	packet := []uint8{
-		0x57, 0xAB, 0x00, 0x02, 0x08,
-		c.hidState.Modkey, 0x00,
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-		0x00,
-	}
-
-	// Populate the HID keycodes
-	for i := 0; i < len(c.hidState.KeyboardButtons); i++ {
-		packet[7+i] = c.hidState.KeyboardButtons[i]
-	}
-
-	// Calculate checksum
-	packet[13] = calcChecksum(packet[:13])
-
-	err := c.Send(packet)
-	if err != nil {
-		return nil, errors.New("failed to send mouse move command: " + err.Error())
-	}
-
-	// Wait for a reply from the device
-	return c.WaitForReply(0x02)
-}
-
-// JavaScriptKeycodeToHIDOpcode converts JavaScript keycode into HID keycode
-func javaScriptKeycodeToHIDOpcode(keycode uint8) uint8 {
-	// Letters A-Z
-	if keycode >= 65 && keycode <= 90 {
-		return (keycode - 65) + 0x04 // 'A' is 0x04
-	}
-
-	// Numbers 1-9 (top row, not numpad)
-	if keycode >= 49 && keycode <= 57 {
-		return (keycode - 49) + 0x1E // '1' is 0x1E
-	}
-
-	// F1 to F12
-	if keycode >= 112 && keycode <= 123 {
-		return (keycode - 112) + 0x3A // 'F1' is 0x3A
-	}
-
-	switch keycode {
-	case 8:
-		return 0x2A // Backspace
-	case 9:
-		return 0x2B // Tab
-	case 13:
-		return 0x28 // Enter
-	case 16:
-		return 0xE1 // Left shift
-	case 17:
-		return 0xE0 // Left Ctrl
-	case 18:
-		return 0xE6 // Left Alt
-	case 19:
-		return 0x48 // Pause
-	case 20:
-		return 0x39 // Caps Lock
-	case 27:
-		return 0x29 // Escape
-	case 32:
-		return 0x2C // Spacebar
-	case 33:
-		return 0x4B // Page Up
-	case 34:
-		return 0x4E // Page Down
-	case 35:
-		return 0x4D // End
-	case 36:
-		return 0x4A // Home
-	case 37:
-		return 0x50 // Left Arrow
-	case 38:
-		return 0x52 // Up Arrow
-	case 39:
-		return 0x4F // Right Arrow
-	case 40:
-		return 0x51 // Down Arrow
-	case 44:
-		return 0x46 // Print Screen or F13 (Firefox)
-	case 45:
-		return 0x49 // Insert
-	case 46:
-		return 0x4C // Delete
-	case 48:
-		return 0x27 // 0 (not Numpads)
-	case 59:
-		return 0x33 // ';'
-	case 61:
-		return 0x2E // '='
-	case 91:
-		return 0xE3 // Left GUI (Windows)
-	case 92:
-		return 0xE7 // Right GUI
-	case 93:
-		return 0x65 // Menu key
-	case 96:
-		return 0x62 // 0 (Numpads)
-	case 97:
-		return 0x59 // 1 (Numpads)
-	case 98:
-		return 0x5A // 2 (Numpads)
-	case 99:
-		return 0x5B // 3 (Numpads)
-	case 100:
-		return 0x5C // 4 (Numpads)
-	case 101:
-		return 0x5D // 5 (Numpads)
-	case 102:
-		return 0x5E // 6 (Numpads)
-	case 103:
-		return 0x5F // 7 (Numpads)
-	case 104:
-		return 0x60 // 8 (Numpads)
-	case 105:
-		return 0x61 // 9 (Numpads)
-	case 106:
-		return 0x55 // * (Numpads)
-	case 107:
-		return 0x57 // + (Numpads)
-	case 109:
-		return 0x56 // - (Numpads)
-	case 110:
-		return 0x63 // dot (Numpads)
-	case 111:
-		return 0x54 // divide (Numpads)
-	case 144:
-		return 0x53 // Num Lock
-	case 145:
-		return 0x47 // Scroll Lock
-	case 146:
-		return 0x58 // Numpad enter
-	case 173:
-		return 0x2D // -
-	case 186:
-		return 0x33 // ';'
-	case 187:
-		return 0x2E // '='
-	case 188:
-		return 0x36 // ','
-	case 189:
-		return 0x2D // '-'
-	case 190:
-		return 0x37 // '.'
-	case 191:
-		return 0x38 // '/'
-	case 192:
-		return 0x35 // '`'
-	case 219:
-		return 0x2F // '['
-	case 220:
-		return 0x31 // backslash
-	case 221:
-		return 0x30 // ']'
-	case 222:
-		return 0x34 // '\''
-	default:
-		return 0x00 // Unknown / unsupported
-	}
-}

+ 0 - 130
remdeskd/mod/remdeshid/mouse.go

@@ -1,130 +0,0 @@
-package remdeshid
-
-import (
-	"bytes"
-	"encoding/binary"
-	"errors"
-)
-
-// calcChecksum calculates the checksum for a given data slice.
-func calcChecksum(data []uint8) uint8 {
-	var sum uint8 = 0
-	for _, value := range data {
-		sum += value
-	}
-	return sum
-}
-
-func (c *Controller) MouseMoveAbsolute(xLSB, xMSB, yLSB, yMSB uint8) ([]byte, error) {
-	packet := []uint8{
-		0x57, 0xAB, 0x00, 0x04, 0x07, 0x02,
-		c.hidState.MouseButtons,
-		xLSB, // X LSB
-		xMSB, // X MSB
-		yLSB, // Y LSB
-		yMSB, // Y MSB
-		0x00, // Scroll
-		0x00, // Checksum placeholder
-	}
-
-	packet[12] = calcChecksum(packet[:12])
-
-	buf := new(bytes.Buffer)
-	if err := binary.Write(buf, binary.LittleEndian, packet); err != nil {
-		return nil, errors.New("failed to write packet to buffer")
-	}
-
-	err := c.Send(buf.Bytes())
-	if err != nil {
-		return nil, errors.New("failed to send mouse move command: " + err.Error())
-	}
-
-	// Wait for a reply from the device
-	return c.WaitForReply(0x04)
-}
-
-func (c *Controller) MouseMoveRelative(dx, dy, wheel uint8) ([]byte, error) {
-	// Ensure 0x80 is not used
-	if dx == 0x80 {
-		dx = 0x81
-	}
-	if dy == 0x80 {
-		dy = 0x81
-	}
-
-	packet := []uint8{
-		0x57, 0xAB, 0x00, 0x05, 0x05, 0x01,
-		c.hidState.MouseButtons,
-		dx,    // Delta X
-		dy,    // Delta Y
-		wheel, // Scroll wheel
-		0x00,  // Checksum placeholder
-	}
-
-	packet[10] = calcChecksum(packet[:10])
-
-	buf := new(bytes.Buffer)
-	if err := binary.Write(buf, binary.LittleEndian, packet); err != nil {
-		return nil, errors.New("failed to write packet to buffer")
-	}
-
-	err := c.Send(buf.Bytes())
-	if err != nil {
-		return nil, errors.New("failed to send mouse move relative command: " + err.Error())
-	}
-
-	return c.WaitForReply(0x05)
-}
-
-// Handle mouse button press events
-func (c *Controller) MouseButtonPress(button uint8) ([]byte, error) {
-	switch button {
-	case 0x01: // Left
-		c.hidState.MouseButtons |= 0x01
-	case 0x02: // Right
-		c.hidState.MouseButtons |= 0x02
-	case 0x03: // Middle
-		c.hidState.MouseButtons |= 0x04
-	default:
-		return nil, errors.New("invalid opcode for mouse button press")
-	}
-
-	// Send updated button state with no movement
-	return c.MouseMoveRelative(0, 0, 0)
-}
-
-// Handle mouse button release events
-func (c *Controller) MouseButtonRelease(button uint8) ([]byte, error) {
-	switch button {
-	case 0x00: // Release all
-		c.hidState.MouseButtons = 0x00
-	case 0x01: // Left
-		c.hidState.MouseButtons &^= 0x01
-	case 0x02: // Right
-		c.hidState.MouseButtons &^= 0x02
-	case 0x03: // Middle
-		c.hidState.MouseButtons &^= 0x04
-	default:
-		return nil, errors.New("invalid opcode for mouse button release")
-	}
-
-	// Send updated button state with no movement
-	return c.MouseMoveRelative(0, 0, 0)
-}
-
-func (c *Controller) MouseScroll(tilt int) ([]byte, error) {
-	if tilt == 0 {
-		// No need to scroll
-		return nil, nil
-	}
-
-	var wheel uint8
-	if tilt < 0 {
-		wheel = uint8(c.Config.ScrollSensitivity)
-	} else {
-		wheel = uint8(0xFF - c.Config.ScrollSensitivity)
-	}
-
-	//fmt.Println(tilt, "-->", wheel)
-	return c.MouseMoveRelative(0, 0, wheel)
-}

+ 0 - 176
remdeskd/mod/remdeshid/remdeshid.go

@@ -1,176 +0,0 @@
-package remdeshid
-
-import (
-	"fmt"
-	"log"
-	"time"
-
-	"github.com/tarm/serial"
-)
-
-func NewHIDController(config *Config) *Controller {
-	// Initialize the HID state with default values
-	defaultHidState := HIDState{
-		Modkey:          0x00,
-		KeyboardButtons: [6]uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
-		Leds:            0x00,
-		MouseButtons:    0x00, // No mouse buttons pressed
-		MouseX:          0,
-		MouseY:          0,
-	}
-
-	return &Controller{
-		Config:            config,
-		serialRunning:     false,
-		hidState:          defaultHidState,
-		writeQueue:        make(chan []byte, 32),
-		incomingDataQueue: make(chan []byte, 1024),
-	}
-}
-
-// Connect opens the serial port and starts reading from it
-func (c *Controller) Connect() error {
-	// Open the serial port
-	config := &serial.Config{
-		Name:   c.Config.PortName,
-		Baud:   c.Config.BaudRate,
-		Size:   8,
-		Parity: serial.ParityNone,
-	}
-
-	port, err := serial.OpenPort(config)
-	if err != nil {
-		return err
-	}
-
-	c.serialPort = port
-	//Start reading from the serial port
-	go func() {
-		buf := make([]byte, 1024)
-		for {
-			n, err := port.Read(buf)
-			if err != nil {
-				log.Println(err.Error())
-				return
-			}
-			if n > 0 {
-				c.incomingDataQueue <- buf[:n]
-				//fmt.Print("Received bytes: ")
-				//for i := 0; i < n; i++ {
-				//	fmt.Printf("0x%02X ", buf[i])
-				//}
-			}
-		}
-	}()
-
-	//Create a loop to write to the serial port
-	c.serialRunning = true
-	go func() {
-		for {
-			data := <-c.writeQueue
-			_, err := port.Write(data)
-			if err != nil {
-				log.Println(err.Error())
-				return
-			}
-		}
-	}()
-
-	//Send over an opr queue reset signal
-	err = c.Send([]byte{0xFF})
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (c *Controller) Send(data []byte) error {
-	if !c.serialRunning {
-		return fmt.Errorf("serial port is not running")
-	}
-	select {
-	case c.writeQueue <- data:
-		return nil
-	case <-time.After(30 * time.Millisecond):
-		return fmt.Errorf("timeout waiting to send data")
-	}
-}
-
-func (c *Controller) ClearReadQueue() {
-	// Clear the incoming data queue
-	for len(c.incomingDataQueue) > 0 {
-		<-c.incomingDataQueue
-	}
-}
-
-func (c *Controller) WaitForReply(cmdByte byte) ([]byte, error) {
-	// Wait for a reply from the device
-	succReplyByte := cmdByte | 0x80
-	errorReplyByte := cmdByte | 0xC0
-	timeout := make(chan bool, 1)
-	go func() {
-		// Timeout after 500ms
-		time.Sleep(500 * time.Millisecond)
-		timeout <- true
-	}()
-
-	var reply []byte
-	for {
-		select {
-		case data := <-c.incomingDataQueue:
-			reply = append(reply, data...)
-			// Check if we have received enough bytes for a complete packet
-			if len(reply) >= 7 {
-				// Validate header
-				if reply[0] == 0x57 && reply[1] == 0xAB {
-					// Extract fields
-					//address := reply[2]
-					replyByte := reply[3]
-					dataLength := reply[4]
-					expectedLength := 5 + int(dataLength) + 1 // Header + address + replyByte + dataLength + data + checksum
-
-					// Check if the full packet is received
-					if len(reply) >= expectedLength {
-						data := reply[5 : 5+dataLength]
-						checksum := reply[5+dataLength]
-
-						// Calculate checksum
-						sum := byte(0)
-						for _, b := range reply[:5+dataLength] {
-							sum += b
-						}
-
-						// Validate checksum
-						if sum == checksum {
-							// Check reply byte for success or error
-							switch replyByte {
-							case succReplyByte:
-								return data, nil
-							case errorReplyByte:
-								fmt.Print("Reply: ")
-								for _, b := range reply {
-									fmt.Printf("0x%02X ", b)
-								}
-								return nil, fmt.Errorf("device returned error reply")
-							}
-						} else {
-							return nil, fmt.Errorf("checksum validation failed")
-						}
-					}
-				} else {
-					// Invalid header, discard data
-					reply = nil
-				}
-			}
-		case <-timeout:
-			return nil, fmt.Errorf("timeout waiting for reply")
-		}
-	}
-}
-
-func (c *Controller) Close() {
-	if c.serialPort != nil {
-		c.serialPort.Close()
-	}
-}

+ 0 - 65
remdeskd/mod/remdeshid/typedef.go

@@ -1,65 +0,0 @@
-package remdeshid
-
-import (
-	"github.com/tarm/serial"
-)
-
-type EventType int
-
-const (
-	EventTypeKeyPress EventType = iota
-	EventTypeKeyRelease
-	EventTypeMouseMove
-	EventTypeMousePress
-	EventTypeMouseRelease
-	EventTypeMouseScroll
-	EventTypeHIDCommand
-	EventTypeHIDReset = 0xFF
-)
-
-const MinCusorEventInterval = 25 // Minimum interval between cursor events in milliseconds
-
-type Config struct {
-	/* Serial port configs */
-	PortName          string
-	BaudRate          int
-	ScrollSensitivity uint8 // Mouse scroll sensitivity, range 0x00 to 0x7E
-}
-
-type HIDState struct {
-	/* Keyboard state */
-	Modkey          uint8    // Modifier key state
-	KeyboardButtons [6]uint8 // Keyboard buttons state
-	Leds            uint8    // LED state
-
-	/* Mouse state */
-	MouseButtons uint8 // Mouse buttons state
-	MouseX       int16 // Mouse X movement
-	MouseY       int16 // Mouse Y movement
-}
-
-// Controller is a struct that represents a HID controller
-type Controller struct {
-	Config *Config
-
-	/* Internal state */
-	serialPort          *serial.Port
-	hidState            HIDState // Current state of the HID device
-	serialRunning       bool
-	writeQueue          chan []byte
-	incomingDataQueue   chan []byte // Queue for incoming data
-	lastCursorEventTime int64
-}
-
-type HIDCommand struct {
-	Event                EventType `json:"event"`
-	Keycode              int       `json:"keycode,omitempty"`
-	IsRightModKey        bool      `json:"is_right_modifier_key,omitempty"`   // true if the key is a right modifier key (Ctrl, Shift, Alt, GUI)
-	MouseAbsX            int       `json:"mouse_x,omitempty"`                 // Absolute mouse position in X direction
-	MouseAbsY            int       `json:"mouse_y,omitempty"`                 // Absolute mouse position in Y direction
-	MouseRelX            int       `json:"mouse_rel_x,omitempty"`             // Relative mouse movement in X direction
-	MouseRelY            int       `json:"mouse_rel_y,omitempty"`             // Relative mouse movement in Y direction
-	MouseMoveButtonState int       `json:"mouse_move_button_state,omitempty"` // Mouse button state during move,
-	MouseButton          int       `json:"mouse_button,omitempty"`            //0x01 for left click, 0x02 for right click, 0x03 for middle clicks
-	MouseScroll          int       `json:"mouse_scroll,omitempty"`            // Positive for scroll up, negative for scroll down, max 127
-}

+ 0 - 319
remdeskd/mod/usbcapture/audio_device.go

@@ -1,319 +0,0 @@
-package usbcapture
-
-import (
-	"bufio"
-	"fmt"
-	"log"
-	"net/http"
-	"os"
-	"os/exec"
-	"regexp"
-	"strings"
-	"syscall"
-	"time"
-
-	"github.com/gorilla/websocket"
-)
-
-// upgrader is used to upgrade HTTP connections to WebSocket connections
-var upgrader = websocket.Upgrader{
-	ReadBufferSize:  1024,
-	WriteBufferSize: 1024,
-	CheckOrigin: func(r *http.Request) bool {
-		return true
-	},
-}
-
-// ListCaptureDevices lists all available audio capture devices in the /dev/snd directory.
-func ListCaptureDevices() ([]string, error) {
-	files, err := os.ReadDir("/dev/snd")
-	if err != nil {
-		return nil, fmt.Errorf("failed to read /dev/snd: %w", err)
-	}
-
-	var captureDevs []string
-	for _, file := range files {
-		name := file.Name()
-		if strings.HasPrefix(name, "pcm") && strings.HasSuffix(name, "c") {
-			fullPath := "/dev/snd/" + name
-			captureDevs = append(captureDevs, fullPath)
-		}
-	}
-
-	return captureDevs, nil
-}
-
-// FindHDMICaptureCard searches for an HDMI capture card using the `arecord -l` command.
-func FindHDMICapturePCMPath() (string, error) {
-	out, err := exec.Command("arecord", "-l").Output()
-	if err != nil {
-		return "", fmt.Errorf("arecord -l failed: %w", err)
-	}
-
-	lines := strings.Split(string(out), "\n")
-	for _, line := range lines {
-		lower := strings.ToLower(line)
-		if strings.Contains(lower, "ms2109") || strings.Contains(lower, "ms2130") {
-			// Example line:
-			// card 1: MS2109 [MS2109], device 0: USB Audio [USB Audio]
-			parts := strings.Fields(line)
-			var cardNum, devNum string
-			for i := range parts {
-				if parts[i] == "card" && i+1 < len(parts) {
-					cardNum = parts[i+1][:1] // "1"
-				}
-				if parts[i] == "device" && i+1 < len(parts) {
-					devNum = strings.TrimSuffix(parts[i+1], ":") // "0"
-				}
-			}
-
-			if cardNum != "" && devNum != "" {
-				return fmt.Sprintf("/dev/snd/pcmC%vD%vc", cardNum, devNum), nil
-			}
-		}
-	}
-
-	return "", fmt.Errorf("no HDMI capture card found")
-}
-
-// Convert a PCM device name to a hardware device name.
-// Example: "pcmC1D0c" -> "hw:1,0"
-func pcmDeviceToHW(dev string) (string, error) {
-	// Regex to extract card and device numbers
-	re := regexp.MustCompile(`pcmC(\d+)D(\d+)[cp]`)
-	matches := re.FindStringSubmatch(dev)
-	if len(matches) < 3 {
-		return "", fmt.Errorf("invalid device format")
-	}
-	card := matches[1]
-	device := matches[2]
-	return fmt.Sprintf("hw:%s,%s", card, device), nil
-}
-
-func GetDefaultAudioConfig() *AudioConfig {
-	return &AudioConfig{
-		SampleRate:     48000,
-		Channels:       2,
-		BytesPerSample: 2,    // 16-bit
-		FrameSize:      1920, // 1920 samples per frame = 40ms @ 48kHz
-	}
-}
-
-func GetDefaultAudioDevice() string {
-	//Check if the default ALSA device exists
-	if _, err := os.Stat("/dev/snd/pcmC0D0c"); err == nil {
-		return "/dev/snd/pcmC0D0c"
-	}
-
-	//If not, list all capture devices and return the first one
-	devs, err := ListCaptureDevices()
-	if err != nil || len(devs) == 0 {
-		return ""
-	}
-
-	return devs[0]
-}
-
-// AudioStreamingHandler handles incoming WebSocket connections for audio streaming.
-func (i *Instance) AudioStreamingHandler(w http.ResponseWriter, r *http.Request) {
-	// Check if the request contains ?quality=low
-	quality := r.URL.Query().Get("quality")
-	qualityKey := []string{"low", "standard", "high"}
-	selectedQuality := "standard"
-	for _, q := range qualityKey {
-		if quality == q {
-			selectedQuality = q
-			break
-		}
-	}
-
-	conn, err := upgrader.Upgrade(w, r, nil)
-	if err != nil {
-		log.Println("Failed to upgrade to websocket:", err)
-		return
-	}
-	defer conn.Close()
-
-	if alsa_device_occupied(i.Config.AudioDeviceName) {
-		//Another instance already running
-		log.Println("Audio pipe already running, stopping previous instance")
-		i.audiostopchan <- true
-		retryCounter := 0
-		for alsa_device_occupied(i.Config.AudioDeviceName) {
-			time.Sleep(500 * time.Millisecond) //Wait a bit for the previous instance to stop
-			retryCounter++
-			if retryCounter > 5 {
-				log.Println("Failed to stop previous audio instance")
-				return
-			}
-		}
-	}
-
-	//Get the capture card audio input
-	pcmdev, err := FindHDMICapturePCMPath()
-	if err != nil {
-		log.Println("Failed to find HDMI capture PCM path:", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-
-	log.Println("Found HDMI capture PCM path:", pcmdev)
-
-	// Convert PCM device to hardware device name
-	hwdev, err := pcmDeviceToHW(pcmdev)
-	if err != nil {
-		log.Println("Failed to convert PCM device to hardware device:", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-
-	log.Println("Using hardware device:", hwdev)
-
-	// Create a buffered reader to read audio data
-	log.Println("Starting audio pipe with arecord...")
-
-	// Start arecord with 48kHz, 16-bit, stereo
-	cmd := exec.Command("arecord",
-		"-f", "S16_LE", // Format: 16-bit little-endian
-		"-r", fmt.Sprint(i.Config.AudioConfig.SampleRate),
-		"-c", fmt.Sprint(i.Config.AudioConfig.Channels),
-		"-D", hwdev, // Use the hardware device
-	)
-
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		log.Println("Failed to get arecord stdout pipe:", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-
-	if err := cmd.Start(); err != nil {
-		log.Println("Failed to start arecord:", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-
-	reader := bufio.NewReader(stdout)
-	bufferSize := i.Config.AudioConfig.FrameSize * i.Config.AudioConfig.Channels * i.Config.AudioConfig.BytesPerSample
-	log.Printf("Buffer size: %d bytes (FrameSize: %d, Channels: %d, BytesPerSample: %d)",
-		bufferSize, i.Config.AudioConfig.FrameSize, i.Config.AudioConfig.Channels, i.Config.AudioConfig.BytesPerSample)
-	buf := make([]byte, bufferSize*2)
-
-	// Start a goroutine to handle WebSocket messages
-	log.Println("Listening for WebSocket messages...")
-	go func() {
-		_, msg, err := conn.ReadMessage()
-		if err == nil {
-			if string(msg) == "exit" {
-				log.Println("Received exit command from client")
-				i.audiostopchan <- true // Signal to stop the audio pipe
-				return
-			}
-		}
-	}()
-
-	log.Println("Starting audio capture loop...")
-	i.isAudioStreaming = true
-	for {
-		select {
-		case <-i.audiostopchan:
-			log.Println("Audio pipe stopped")
-			goto DONE
-		default:
-			n, err := reader.Read(buf)
-			if err != nil {
-				log.Println("Read error:", err)
-				if i.audiostopchan != nil {
-					i.audiostopchan <- true // Signal to stop the audio pipe
-				}
-				goto DONE
-			}
-
-			if n == 0 {
-				continue
-			}
-
-			downsampled := buf[:n] // Default to original buffer if no downsampling
-			switch selectedQuality {
-			case "high":
-				// Keep original 48kHz stereo
-
-			case "standard":
-				// Downsample to 24kHz stereo
-				downsampled = downsample48kTo24kStereo(buf[:n]) // Downsample to 24kHz stereo
-				copy(buf, downsampled)                          // Copy downsampled data back into buf
-				n = len(downsampled)                            // Update n to the new length
-			case "low":
-				downsampled = downsample48kTo16kStereo(buf[:n]) // Downsample to 16kHz stereo
-				copy(buf, downsampled)                          // Copy downsampled data back into buf
-				n = len(downsampled)                            // Update n to the new length
-			}
-
-			//Send only the bytes read to WebSocket
-			err = conn.WriteMessage(websocket.BinaryMessage, downsampled[:n])
-			if err != nil {
-				log.Println("WebSocket send error:", err)
-				goto DONE
-			}
-		}
-	}
-
-DONE:
-	i.isAudioStreaming = false
-	cmd.Process.Kill()
-	log.Println("Audio pipe finished")
-}
-
-// Downsample48kTo24kStereo downsamples a 48kHz stereo audio buffer to 24kHz.
-// It assumes the input buffer is in 16-bit stereo format (2 bytes per channel).
-// The output buffer will also be in 16-bit stereo format.
-func downsample48kTo24kStereo(buf []byte) []byte {
-	const frameSize = 4 // 2 bytes per channel × 2 channels
-	if len(buf)%frameSize != 0 {
-		// Trim incomplete frame (rare case)
-		buf = buf[:len(buf)-len(buf)%frameSize]
-	}
-
-	out := make([]byte, 0, len(buf)/2)
-
-	for i := 0; i < len(buf); i += frameSize * 2 {
-		// Copy every other frame (drop 1 in 2)
-		if i+frameSize <= len(buf) {
-			out = append(out, buf[i:i+frameSize]...)
-		}
-	}
-
-	return out
-}
-
-// Downsample48kTo16kStereo downsamples a 48kHz stereo audio buffer to 16kHz.
-// It assumes the input buffer is in 16-bit stereo format (2 bytes per channel).
-// The output buffer will also be in 16-bit stereo format.
-func downsample48kTo16kStereo(buf []byte) []byte {
-	const frameSize = 4 // 2 bytes per channel × 2 channels
-	if len(buf)%frameSize != 0 {
-		// Trim incomplete frame (rare case)
-		buf = buf[:len(buf)-len(buf)%frameSize]
-	}
-
-	out := make([]byte, 0, len(buf)/3)
-
-	for i := 0; i < len(buf); i += frameSize * 3 {
-		// Copy every third frame (drop 2 in 3)
-		if i+frameSize <= len(buf) {
-			out = append(out, buf[i:i+frameSize]...)
-		}
-	}
-
-	return out
-}
-
-func alsa_device_occupied(dev string) bool {
-	f, err := os.OpenFile(dev, os.O_RDONLY|syscall.O_NONBLOCK, 0)
-	if err != nil {
-		//result <- true // Occupied or cannot open
-		return true
-	}
-	f.Close()
-	return false
-}

BIN
remdeskd/mod/usbcapture/stream_takeover.jpg


BIN
remdeskd/mod/usbcapture/stream_takeover.psd


+ 0 - 53
remdeskd/mod/usbcapture/typedef.go

@@ -1,53 +0,0 @@
-package usbcapture
-
-import (
-	"context"
-
-	"github.com/vladimirvivien/go4vl/device"
-	"github.com/vladimirvivien/go4vl/v4l2"
-)
-
-// The capture resolution to open video device
-type CaptureResolution struct {
-	Width  int
-	Height int
-	FPS    int
-}
-
-type AudioConfig struct {
-	SampleRate     int
-	Channels       int
-	FrameSize      int
-	BytesPerSample int
-}
-
-type Config struct {
-	VideoDeviceName string       // The video device name, e.g., /dev/video0
-	AudioDeviceName string       // The audio device name, e.g., /dev/snd
-	AudioConfig     *AudioConfig // The audio configuration
-}
-
-type Instance struct {
-	/* Runtime configuration */
-	Config               *Config
-	SupportedResolutions []FormatInfo //The supported resolutions of the video device
-	Capturing            bool
-
-	/* Internals */
-	/* Video capture device */
-	camera             *device.Device
-	cameraStartContext context.CancelFunc
-	frames_buff        <-chan []byte
-	pixfmt             v4l2.FourCCType
-	width              int
-	height             int
-	streamInfo         string
-
-	/* audio capture device */
-	isAudioStreaming bool      // Whether audio is currently being captured
-	audiostopchan    chan bool // Channel to stop audio capture
-
-	/* Concurrent access */
-	accessCount       int       // The number of current access, in theory each instance should at most have 1 access
-	videoTakeoverChan chan bool // Channel to signal video takeover request
-}

+ 0 - 83
remdeskd/mod/usbcapture/usbcapture.go

@@ -1,83 +0,0 @@
-package usbcapture
-
-import (
-	"fmt"
-	"os"
-)
-
-// NewInstance creates a new video capture instance
-func NewInstance(config *Config) (*Instance, error) {
-	if config == nil {
-		return nil, fmt.Errorf("config cannot be nil")
-	}
-
-	//Check if the video device exists
-	if _, err := os.Stat(config.VideoDeviceName); os.IsNotExist(err) {
-		return nil, fmt.Errorf("video device %s does not exist", config.VideoDeviceName)
-	} else if err != nil {
-		return nil, fmt.Errorf("failed to check video device: %w", err)
-	}
-
-	//Check if the device file actualy points to a video device
-	isValidDevice, err := checkVideoCaptureDevice(config.VideoDeviceName)
-	if err != nil {
-		return nil, fmt.Errorf("failed to check video device: %w", err)
-	}
-
-	if !isValidDevice {
-		return nil, fmt.Errorf("device %s is not a video capture device", config.VideoDeviceName)
-	}
-
-	//Get the supported resolutions of the video device
-	formatInfo, err := GetV4L2FormatInfo(config.VideoDeviceName)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get video device format info: %w", err)
-	}
-
-	if len(formatInfo) == 0 {
-		return nil, fmt.Errorf("no supported formats found for device %s", config.VideoDeviceName)
-	}
-
-	return &Instance{
-		Config:               config,
-		Capturing:            false,
-		SupportedResolutions: formatInfo,
-
-		// Videos
-		camera:     nil,
-		pixfmt:     0,
-		width:      0,
-		height:     0,
-		streamInfo: "",
-
-		//Audio
-		audiostopchan: make(chan bool, 1),
-
-		// Access control
-		videoTakeoverChan: make(chan bool, 1),
-		accessCount:       0,
-	}, nil
-}
-
-// GetStreamInfo returns the stream information string
-func (i *Instance) GetStreamInfo() string {
-	return i.streamInfo
-}
-
-// IsCapturing checks if the camera is currently capturing video
-func (i *Instance) IsCapturing() bool {
-	return i.Capturing
-}
-
-// IsAudioStreaming checks if the audio is currently being captured
-func (i *Instance) IsAudioStreaming() bool {
-	return i.isAudioStreaming
-}
-
-// Close closes the camera device and releases resources
-func (i *Instance) Close() error {
-	if i.camera != nil {
-		i.StopCapture()
-	}
-	return nil
-}

+ 0 - 414
remdeskd/mod/usbcapture/video_device.go

@@ -1,414 +0,0 @@
-package usbcapture
-
-import (
-	"bufio"
-	"bytes"
-	"context"
-	_ "embed"
-	"errors"
-	"fmt"
-	"log"
-	"mime/multipart"
-	"net/http"
-	"net/textproto"
-	"os/exec"
-	"regexp"
-	"strconv"
-	"strings"
-	"syscall"
-
-	"github.com/vladimirvivien/go4vl/device"
-	"github.com/vladimirvivien/go4vl/v4l2"
-)
-
-/*
-	1920 x 1080 60fps = 55Mbps //Edge not support
-	1920 x 1080 30fps = 50Mbps
-	1920 x 1080 25fps = 40Mbps
-	1920 x 1080 20fps = 30Mbps
-	1920 x 1080 10fps = 15Mbps
-
-	1360 x 768 60fps = 28Mbps
-	1360 x 768 30fps = 25Mbps
-	1360 x 768 25fps = 20Mbps
-	1360 x 768 20fps = 18Mbps
-	1360 x 768 10fps = 10Mbps
-*/
-
-// Struct to store the size and fps info
-type FormatInfo struct {
-	Format string
-	Sizes  []SizeInfo
-}
-
-type SizeInfo struct {
-	Width  int
-	Height int
-	FPS    []int
-}
-
-//go:embed stream_takeover.jpg
-var endOfStreamJPG []byte
-
-// start video capture
-func (i *Instance) StartVideoCapture(openWithResolution *CaptureResolution) error {
-	if i.Capturing {
-		return fmt.Errorf("video capture already started")
-	}
-
-	if openWithResolution.FPS == 0 {
-		openWithResolution.FPS = 25 //Default to 25 FPS
-	}
-
-	devName := i.Config.VideoDeviceName
-	if openWithResolution == nil {
-		return fmt.Errorf("resolution not provided")
-	}
-	frameRate := openWithResolution.FPS
-	buffSize := 8 //No. of frames to buffer
-	//Default to MJPEG
-	//Other formats that are commonly supported are YUYV, H264, MJPEG
-	format := "mjpeg"
-
-	//Check if the video device is a capture device
-	isCaptureDev, err := checkVideoCaptureDevice(devName)
-	if err != nil {
-		return fmt.Errorf("failed to check video device: %w", err)
-	}
-	if !isCaptureDev {
-		return fmt.Errorf("device %s is not a video capture device", devName)
-	}
-
-	//Check if the selected FPS is valid in the provided Resolutions
-	resolutionIsSupported, err := deviceSupportResolution(i.Config.VideoDeviceName, openWithResolution)
-	if err != nil {
-		return err
-	}
-	if !resolutionIsSupported {
-		return errors.New("this device do not support the required resolution settings")
-	}
-
-	//Open the video device
-	camera, err := device.Open(devName,
-		device.WithIOType(v4l2.IOTypeMMAP),
-		device.WithPixFormat(v4l2.PixFormat{
-			PixelFormat: getFormatType(format),
-			Width:       uint32(openWithResolution.Width),
-			Height:      uint32(openWithResolution.Height),
-			Field:       v4l2.FieldAny,
-		}),
-		device.WithFPS(uint32(frameRate)),
-		device.WithBufferSize(uint32(buffSize)),
-	)
-
-	if err != nil {
-		return fmt.Errorf("failed to open video device: %w", err)
-	}
-
-	i.camera = camera
-	caps := camera.Capability()
-	log.Printf("device [%s] opened\n", devName)
-	log.Printf("device info: %s", caps.String())
-	// Should get something like this:
-	//2025/03/16 15:45:25 device info: driver: uvcvideo; card: USB Video: USB Video; bus info: usb-0000:00:14.0-2
-
-	// set device format
-	currFmt, err := camera.GetPixFormat()
-	if err != nil {
-		return fmt.Errorf("failed to get current pixel format: %w", err)
-	}
-	log.Printf("Current format: %s", currFmt)
-	//2025/03/16 15:45:25 Current format: Motion-JPEG [1920x1080]; field=any; bytes per line=0; size image=0; colorspace=Default; YCbCr=Default; Quant=Default; XferFunc=Default
-	i.pixfmt = currFmt.PixelFormat
-	i.width = int(currFmt.Width)
-	i.height = int(currFmt.Height)
-
-	i.streamInfo = fmt.Sprintf("%s - %s [%dx%d] %d fps",
-		caps.Card,
-		v4l2.PixelFormats[currFmt.PixelFormat],
-		currFmt.Width, currFmt.Height, frameRate,
-	)
-
-	// start capture
-	ctx, cancel := context.WithCancel(context.TODO())
-	if err := camera.Start(ctx); err != nil {
-		log.Fatalf("stream capture: %s", err)
-	}
-	i.cameraStartContext = cancel
-
-	// video stream
-	i.frames_buff = camera.GetOutput()
-
-	log.Printf("device capture started (buffer size set %d)", camera.BufferCount())
-	i.Capturing = true
-	return nil
-}
-
-// start http service
-func (i *Instance) ServeVideoStream(w http.ResponseWriter, req *http.Request) {
-	//Check if the access count is already 1, if so, kick out the previous access
-	if i.accessCount >= 1 {
-		log.Println("Another client is already connected, kicking out the previous client...")
-		if i.videoTakeoverChan != nil {
-			i.videoTakeoverChan <- true
-		}
-		log.Println("Previous client kicked out, taking over the stream...")
-	}
-	i.accessCount++
-	defer func() { i.accessCount-- }()
-
-	// Set up the multipart response
-	mimeWriter := multipart.NewWriter(w)
-	w.Header().Set("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=%s", mimeWriter.Boundary()))
-	partHeader := make(textproto.MIMEHeader)
-	partHeader.Add("Content-Type", "image/jpeg")
-
-	var frame []byte
-
-	//Chrome MJPEG decoder cannot decode the first frame from MS2109 capture card for unknown reason
-	//Thus we are discarding the first frame here
-	if i.frames_buff != nil {
-		select {
-		case <-i.frames_buff:
-			// Discard the first frame
-		default:
-			// No frame to discard
-		}
-	}
-
-	// Streaming loop
-	for frame = range i.frames_buff {
-		if len(frame) == 0 {
-			log.Print("skipping empty frame")
-			continue
-		}
-
-		partWriter, err := mimeWriter.CreatePart(partHeader)
-		if err != nil {
-			log.Printf("failed to create multi-part writer: %s", err)
-			return
-		}
-
-		if _, err := partWriter.Write(frame); err != nil {
-			if errors.Is(err, syscall.EPIPE) {
-				//broken pipe, the client browser has exited
-				return
-			}
-			log.Printf("failed to write image: %s", err)
-		}
-
-		select {
-		case <-req.Context().Done():
-			// Client disconnected, exit the loop
-			return
-		case <-i.videoTakeoverChan:
-			// Another client is taking over, exit the loop
-
-			//Send the endofstream.jpg as last frame before exit
-			endFrameHeader := make(textproto.MIMEHeader)
-			endFrameHeader.Add("Content-Type", "image/jpeg")
-			endFrameHeader.Add("Content-Length", fmt.Sprint(len(endOfStreamJPG)))
-			partWriter, err := mimeWriter.CreatePart(endFrameHeader)
-			if err == nil {
-				partWriter.Write(endOfStreamJPG)
-			}
-			log.Println("Video stream taken over by another client, exiting...")
-			return
-		default:
-			// Continue streaming
-		}
-
-	}
-}
-
-// StopCapture stops the video capture and closes the camera device
-func (i *Instance) StopCapture() error {
-	if i.camera != nil {
-		i.cameraStartContext()
-		i.camera.Close()
-		i.camera = nil
-	}
-	i.Capturing = false
-	return nil
-}
-
-// CheckVideoCaptureDevice checks if the given video device is a video capture device
-func checkVideoCaptureDevice(device string) (bool, error) {
-	// Run v4l2-ctl to get device capabilities
-	cmd := exec.Command("v4l2-ctl", "--device", device, "--all")
-	output, err := cmd.CombinedOutput()
-	if err != nil {
-		return false, fmt.Errorf("failed to execute v4l2-ctl: %w", err)
-	}
-
-	// Convert output to string and check for the "Video Capture" capability
-	outputStr := string(output)
-	if strings.Contains(outputStr, "Video Capture") {
-		return true, nil
-	}
-	return false, nil
-}
-
-// GetDefaultVideoDevice returns the first available video capture device, e.g., /dev/video0
-func GetDefaultVideoDevice() (string, error) {
-	// List all /dev/video* devices and return the first one that is a video capture device
-	for i := 0; i < 10; i++ {
-		device := fmt.Sprintf("/dev/video%d", i)
-		isCapture, err := checkVideoCaptureDevice(device)
-		if err != nil {
-			continue
-		}
-		if isCapture {
-			return device, nil
-		}
-	}
-	return "", fmt.Errorf("no video capture device found")
-}
-
-// deviceSupportResolution checks if the given video device supports the specified resolution and frame rate
-func deviceSupportResolution(devicePath string, resolution *CaptureResolution) (bool, error) {
-	formatInfo, err := GetV4L2FormatInfo(devicePath)
-	if err != nil {
-		return false, err
-	}
-
-	// Yes, this is an O(N^3) operation, but a video decices rarely have supported resolution
-	// more than 20 combinations. The compute time should be fine
-	for _, res := range formatInfo {
-		for _, size := range res.Sizes {
-			//Check if there is a matching resolution
-			if size.Height == resolution.Height && size.Width == resolution.Width {
-				//Matching resolution. Check if the required FPS is supported
-				for _, fps := range size.FPS {
-					if fps == resolution.FPS {
-						return true, nil
-					}
-				}
-			}
-		}
-	}
-
-	return false, nil
-}
-
-// PrintV4L2FormatInfo prints the supported formats, resolutions, and frame rates of the given video device
-func PrintV4L2FormatInfo(devicePath string) {
-	// Check if the device is a video capture device
-	isCapture, err := checkVideoCaptureDevice(devicePath)
-	if err != nil {
-		fmt.Printf("Error checking device: %v\n", err)
-		return
-	}
-	if !isCapture {
-		fmt.Printf("Device %s is not a video capture device\n", devicePath)
-		return
-	}
-
-	// Get format info
-	formats, err := GetV4L2FormatInfo(devicePath)
-	if err != nil {
-		fmt.Printf("Error getting format info: %v\n", err)
-		return
-	}
-
-	// Print format info
-	for _, format := range formats {
-		fmt.Printf("Format: %s\n", format.Format)
-		for _, size := range format.Sizes {
-			fmt.Printf("  Size: %dx%d\n", size.Width, size.Height)
-			fmt.Printf("    FPS: %v\n", size.FPS)
-		}
-	}
-}
-
-// Function to run the v4l2-ctl command and parse the output
-func GetV4L2FormatInfo(devicePath string) ([]FormatInfo, error) {
-	// Run the v4l2-ctl command to list formats
-	cmd := exec.Command("v4l2-ctl", "--list-formats-ext", "-d", devicePath)
-	var out bytes.Buffer
-	cmd.Stdout = &out
-	err := cmd.Run()
-	if err != nil {
-		return nil, err
-	}
-
-	// Parse the output
-	var formats []FormatInfo
-	var currentFormat *FormatInfo
-	scanner := bufio.NewScanner(&out)
-
-	formatRegex := regexp.MustCompile(`\[(\d+)\]: '(\S+)'`)
-	sizeRegex := regexp.MustCompile(`Size: Discrete (\d+)x(\d+)`)
-	intervalRegex := regexp.MustCompile(`Interval: Discrete (\d+\.\d+)s \((\d+\.\d+) fps\)`)
-
-	for scanner.Scan() {
-		line := scanner.Text()
-
-		// Match format line
-		if matches := formatRegex.FindStringSubmatch(line); matches != nil {
-			if currentFormat != nil {
-				formats = append(formats, *currentFormat)
-			}
-			// Start a new format entry
-			currentFormat = &FormatInfo{
-				Format: matches[2],
-			}
-		}
-
-		// Match size line
-		if matches := sizeRegex.FindStringSubmatch(line); matches != nil {
-			width, _ := strconv.Atoi(matches[1])
-			height, _ := strconv.Atoi(matches[2])
-
-			// Initialize the size entry
-			sizeInfo := SizeInfo{
-				Width:  width,
-				Height: height,
-			}
-
-			// Match FPS intervals for the current size
-			for scanner.Scan() {
-				line = scanner.Text()
-
-				if fpsMatches := intervalRegex.FindStringSubmatch(line); fpsMatches != nil {
-					fps, _ := strconv.ParseFloat(fpsMatches[2], 32)
-					sizeInfo.FPS = append(sizeInfo.FPS, int(fps))
-				} else {
-					// Stop parsing FPS intervals when no more matches are found
-					break
-				}
-			}
-			// Add the size information to the current format
-			currentFormat.Sizes = append(currentFormat.Sizes, sizeInfo)
-		}
-	}
-
-	// Append the last format if present
-	if currentFormat != nil {
-		formats = append(formats, *currentFormat)
-	}
-
-	if err := scanner.Err(); err != nil {
-		return nil, err
-	}
-
-	return formats, nil
-}
-
-func getFormatType(fmtStr string) v4l2.FourCCType {
-	switch strings.ToLower(fmtStr) {
-	case "jpeg":
-		return v4l2.PixelFmtJPEG
-	case "mpeg":
-		return v4l2.PixelFmtMPEG
-	case "mjpeg":
-		return v4l2.PixelFmtMJPEG
-	case "h264", "h.264":
-		return v4l2.PixelFmtH264
-	case "yuyv":
-		return v4l2.PixelFmtYUYV
-	case "rgb":
-		return v4l2.PixelFmtRGB24
-	}
-	return v4l2.PixelFmtMPEG
-}

+ 0 - 98
remdeskd/tools.go

@@ -1,98 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-	"os/exec"
-
-	"imuslab.com/dezukvm/dezukvmd/mod/usbcapture"
-)
-
-func handle_debug_tool() error {
-	switch *tool {
-	case "dependency-precheck":
-		err := run_dependency_precheck()
-		if err != nil {
-			return err
-		}
-	case "list-usbkvm-json":
-		result, err := discoverUsbKvmSubtree()
-		if err != nil {
-			return err
-		}
-		jsonData, err := json.MarshalIndent(result, "", "  ")
-		if err != nil {
-			return err
-		}
-		fmt.Println(string(jsonData))
-	case "audio-devices":
-		err := list_all_audio_devices()
-		if err != nil {
-			return err
-		}
-	case "list-usbkvm":
-		err := list_usb_kvm_devcies()
-		if err != nil {
-			return err
-		}
-	default:
-		return fmt.Errorf("please specify a valid tool with -tool option")
-	}
-	return nil
-}
-
-// run_dependency_precheck checks if required dependencies are available in the system
-func run_dependency_precheck() error {
-	log.Println("Running precheck...")
-	// Dependencies of USB capture card
-	if _, err := exec.LookPath("v4l2-ctl"); err != nil {
-		return fmt.Errorf("v4l2-ctl not found in PATH")
-	}
-	if _, err := exec.LookPath("arecord"); err != nil {
-		return fmt.Errorf("arecord not found in PATH")
-	}
-	log.Println("v4l2-ctl and arecord found in PATH.")
-	return nil
-}
-
-// list_usb_kvm_devcies lists all discovered USB KVM devices and their associated sub-devices
-func list_usb_kvm_devcies() error {
-	result, err := discoverUsbKvmSubtree()
-	if err != nil {
-		return err
-	}
-	for i, dev := range result {
-		log.Printf("USB KVM Device Tree %d:\n", i)
-		log.Printf(" - USB KVM Device: %s\n", dev.USBKVMDevicePath)
-		log.Printf(" - Aux MCU Device: %s\n", dev.AuxMCUDevicePath)
-		for _, cap := range dev.CaptureDevicePaths {
-			log.Printf(" - Capture Device: %s\n", cap)
-		}
-		for _, snd := range dev.AlsaDevicePaths {
-			log.Printf(" - ALSA Device: %s\n", snd)
-		}
-	}
-	return nil
-}
-
-// list_all_audio_devices lists all available audio capture devices
-func list_all_audio_devices() error {
-	log.Println("Starting in List Audio Devices mode...")
-	// Get the audio devices
-	path, err := usbcapture.FindHDMICapturePCMPath()
-	if err != nil {
-		return err
-	}
-	log.Printf("Found HDMI capture PCM path: %s\n", path)
-	// List all audio capture devices
-	captureDevs, err := usbcapture.ListCaptureDevices()
-	if err != nil {
-		return err
-	}
-	log.Println("Available audio capture devices:")
-	for _, dev := range captureDevs {
-		log.Printf(" - %s\n", dev)
-	}
-	return nil
-}

+ 0 - 175
remdeskd/usbkvm.go

@@ -1,175 +0,0 @@
-package main
-
-/*
-	usbkvm.go
-
-	Handles the USB KVM device connections and auxiliary devices
-	running in USB KVM mode. This mode only support 1 USB KVM device
-	at a time.
-
-	For running multiple USB KVM devices, use the ipkvm mode.
-*/
-import (
-	"encoding/json"
-	"log"
-	"net/http"
-	"os"
-	"os/signal"
-	"syscall"
-
-	"imuslab.com/dezukvm/dezukvmd/mod/remdesaux"
-	"imuslab.com/dezukvm/dezukvmd/mod/remdeshid"
-	"imuslab.com/dezukvm/dezukvmd/mod/usbcapture"
-)
-
-type UsbKvmConfig struct {
-	ListeningAddress        string
-	USBKVMDevicePath        string
-	AuxMCUDevicePath        string
-	VideoCaptureDevicePath  string
-	AudioCaptureDevicePath  string
-	CaptureResolutionWidth  int
-	CaptureResolutionHeight int
-	CaptureResolutionFPS    int
-	USBKVMBaudrate          int
-	AuxMCUBaudrate          int
-}
-
-var (
-	/* Internal variables for USB-KVM mode only */
-	usbKVM              *remdeshid.Controller
-	auxMCU              *remdesaux.AuxMcu
-	usbCaptureDevice    *usbcapture.Instance
-	defaultUsbKvmConfig = &UsbKvmConfig{
-		ListeningAddress:        ":9000",
-		USBKVMDevicePath:        "/dev/ttyUSB0",
-		AuxMCUDevicePath:        "/dev/ttyACM0",
-		VideoCaptureDevicePath:  "/dev/video0",
-		AudioCaptureDevicePath:  "/dev/snd/pcmC1D0c",
-		CaptureResolutionWidth:  1920,
-		CaptureResolutionHeight: 1080,
-		CaptureResolutionFPS:    25,
-		USBKVMBaudrate:          115200,
-		AuxMCUBaudrate:          115200,
-	}
-)
-
-func loadUsbKvmConfig() (*UsbKvmConfig, error) {
-	if _, err := os.Stat(usbKvmConfigPath); os.IsNotExist(err) {
-		file, err := os.OpenFile(usbKvmConfigPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
-		if err != nil {
-			return nil, err
-		}
-
-		// Save default config as JSON
-		enc := json.NewEncoder(file)
-		enc.SetIndent("", "  ")
-		if err := enc.Encode(defaultUsbKvmConfig); err != nil {
-			file.Close()
-			return nil, err
-		}
-		file.Close()
-		return defaultUsbKvmConfig, nil
-	}
-
-	// Load config from file
-	file, err := os.Open(usbKvmConfigPath)
-	if err != nil {
-		return nil, err
-	}
-
-	cfg := &UsbKvmConfig{}
-	dec := json.NewDecoder(file)
-	if err := dec.Decode(cfg); err != nil {
-		file.Close()
-		return nil, err
-	}
-	file.Close()
-	return cfg, nil
-}
-
-func startUsbKvmMode(config *UsbKvmConfig) error {
-	log.Println("Starting in USB KVM mode...")
-	// Initiate the HID controller
-	usbKVM = remdeshid.NewHIDController(&remdeshid.Config{
-		PortName:          config.USBKVMDevicePath,
-		BaudRate:          config.USBKVMBaudrate,
-		ScrollSensitivity: 0x01, // Set mouse scroll sensitivity
-	})
-
-	//Start the HID controller
-	err := usbKVM.Connect()
-	if err != nil {
-		return err
-	}
-
-	//Start auxiliary MCU connections
-	auxMCU, err = remdesaux.NewAuxOutbandController(config.AuxMCUDevicePath, config.AuxMCUBaudrate)
-	if err != nil {
-		return err
-	}
-
-	//Try get the UUID from the auxiliary MCU
-	uuid, err := auxMCU.GetUUID()
-	if err != nil {
-		log.Println("Get UUID failed:", err, " - Auxiliary MCU may not be connected.")
-
-		//Register dummy AUX routes if failed to get UUID
-		registerDummyLocalAuxRoutes()
-	} else {
-		log.Println("Auxiliary MCU found with UUID:", uuid)
-
-		//Register the AUX routes if success
-		registerLocalAuxRoutes()
-	}
-
-	// Initiate the video capture device
-	usbCaptureDevice, err = usbcapture.NewInstance(&usbcapture.Config{
-		VideoDeviceName: config.VideoCaptureDevicePath,
-		AudioDeviceName: config.AudioCaptureDevicePath,
-		AudioConfig:     usbcapture.GetDefaultAudioConfig(),
-	})
-
-	if err != nil {
-		log.Println("Video capture device init failed:", err, " - Video capture device may not be connected.")
-		return err
-	}
-
-	//Get device information for debug
-	usbcapture.PrintV4L2FormatInfo(config.VideoCaptureDevicePath)
-
-	//Start the video capture device
-	err = usbCaptureDevice.StartVideoCapture(&usbcapture.CaptureResolution{
-		Width:  config.CaptureResolutionWidth,
-		Height: config.CaptureResolutionHeight,
-		FPS:    config.CaptureResolutionFPS,
-	})
-	if err != nil {
-		return err
-	}
-
-	// Handle program exit to close the HID controller
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
-	go func() {
-		<-c
-		log.Println("Shutting down usbKVM...")
-
-		if auxMCU != nil {
-			auxMCU.Close()
-		}
-		log.Println("Shutting down capture device...")
-		if usbCaptureDevice != nil {
-			usbCaptureDevice.Close()
-		}
-		os.Exit(0)
-	}()
-
-	// Register the rest of the API routes
-	registerAPIRoutes()
-
-	addr := config.ListeningAddress
-	log.Printf("Serving on%s\n", addr)
-	err = http.ListenAndServe(addr, nil)
-	return err
-}

BIN
remdeskd/www/img/cursor_overlay.png


BIN
remdeskd/www/img/cursor_overlay.psd


+ 0 - 31
remdeskd/www/index.html

@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <meta name="description" content="dezuKVM Management Interface">
-    <meta name="author" content="imuslab">
-    <meta name="csrf-token" content="">
-    <title>Connected | dezuKVM</title>
-    
-    <!-- OpenGraph Metadata -->
-    <meta property="og:title" content="dezuKVM Management Interface">
-    <meta property="og:description" content="A web-based management interface for dezuKVM">
-    <meta property="og:type" content="website">
-    <meta property="og:url" content="https://kvm.aroz.org">
-    <meta property="og:image" content="https://kvm.aroz.org/og.jpg">
-    <script src="js/jquery-3.7.1.min.js"></script>
-    <link rel="stylesheet" href="main.css">
-</head>
-<body>
-    <img id="remoteCapture" src="/stream" oncontextmenu="return false;"></img>
-    <div id="menu">
-        <button id="btnFullScreen" onclick="toggleFullScreen()">Fullscreen</button>
-        <button id="btnCtrlAltDel" onclick="sendCtrlAltDel()">Ctrl+Alt+Del</button>
-        <button id="btnFullScreen" onclick="switchMassStorageToKvm()">Switch Storage to KVM</button>
-        <button id="btnFullScreen" onclick="switchMassStorageToRemote()">Switch Storage to Remote</button>
-    </div>
-    <script src="ui.js"></script>
-    <script src="kvmevt.js"></script>
-</body>
-</html>

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 1
remdeskd/www/js/jquery-3.7.1.min.js


+ 0 - 386
remdeskd/www/kvmevt.js

@@ -1,386 +0,0 @@
-/*
-    kvmevt.js
-
-    Keyboard, Video, Mouse (KVM) over WebSocket client-side event handling.
-    Handles mouse and keyboard events, sending them to the server via WebSocket.
-    Also manages audio streaming from the server.
-*/
-const enableKvmEventDebugPrintout = false; //Set to true to enable debug printout
-const cursorCaptureElementId = "remoteCapture";
-let socket;
-let protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
-let port = window.location.port ? window.location.port : (protocol === 'wss' ? 443 : 80);
-let socketURL = `${protocol}://${window.location.hostname}:${port}/hid`;
-let mouseMoveAbsolute = true; // Set to true for absolute mouse coordinates, false for relative
-let mouseIsOutside = false; //Mouse is outside capture element
-let audioFrontendStarted = false; //Audio frontend has been started
-
-
-/* Mouse events */
-function handleMouseMove(event) {
-    const hidCommand = {
-        event: 2,
-        mouse_x: event.clientX,
-        mouse_y: event.clientY,
-    };
-
-    const rect = event.target.getBoundingClientRect();
-    const relativeX = event.clientX - rect.left;
-    const relativeY = event.clientY - rect.top;
-    
-    if (relativeX < 0 || relativeY < 0 || relativeX > rect.width || relativeY > rect.height) {
-        mouseIsOutside = true;
-        return; // Mouse is outside the client rect
-    }
-    mouseIsOutside = false;
-    const percentageX = (relativeX / rect.width) * 4096;
-    const percentageY = (relativeY / rect.height) * 4096;
-
-    hidCommand.mouse_x = Math.round(percentageX);
-    hidCommand.mouse_y = Math.round(percentageY);
-
-    if (enableKvmEventDebugPrintout) {
-        console.log(`Mouse move: (${event.clientX}, ${event.clientY})`);
-        console.log(`Mouse move relative: (${relativeX}, ${relativeY})`);
-        console.log(`Mouse move percentage: (${hidCommand.mouse_x}, ${hidCommand.mouse_y})`);
-    }
-
-    if (socket && socket.readyState === WebSocket.OPEN) {
-        socket.send(JSON.stringify(hidCommand));
-    } else {
-        console.error("WebSocket is not open.");
-    }
-}
-
-
-function handleMousePress(event) {
-    event.preventDefault();
-    event.stopImmediatePropagation();
-    if (mouseIsOutside) {
-        console.warn("Mouse is outside the capture area, ignoring mouse press.");
-        return;
-    }
-    /* Mouse buttons: 1=left, 2=right, 3=middle */
-    const buttonMap = {
-        0: 1, 
-        1: 3,
-        2: 2
-    }; //Map javascript mouse buttons to HID buttons
-
-    const hidCommand = {
-        event: 3,
-        mouse_button: buttonMap[event.button] || 0
-    };
-
-    // Log the mouse button state
-    if (enableKvmEventDebugPrintout) {
-        console.log(`Mouse down: ${hidCommand.mouse_button}`);
-    }
-
-    if (socket && socket.readyState === WebSocket.OPEN) {
-        socket.send(JSON.stringify(hidCommand));
-    } else {
-        console.error("WebSocket is not open.");
-    }
-
-    if (!audioFrontendStarted){
-        startAudioWebSocket();
-        audioFrontendStarted = true;
-    }
-}
-
-function handleMouseRelease(event) {
-    event.preventDefault();
-    event.stopImmediatePropagation();
-    if (mouseIsOutside) {
-        console.warn("Mouse is outside the capture area, ignoring mouse press.");
-        return;
-    }
-    /* Mouse buttons: 1=left, 2=right, 3=middle */
-    const buttonMap = {
-        0: 1, 
-        1: 3,
-        2: 2
-    }; //Map javascript mouse buttons to HID buttons
-    
-    const hidCommand = {
-        event: 4,
-        mouse_button: buttonMap[event.button] || 0
-    };
-
-    if (enableKvmEventDebugPrintout) {
-        console.log(`Mouse release: ${hidCommand.mouse_button}`);
-    }
-
-    if (socket && socket.readyState === WebSocket.OPEN) {
-        socket.send(JSON.stringify(hidCommand));
-    } else {
-        console.error("WebSocket is not open.");
-    }
-}
-
-function handleMouseScroll(event) {
-    const hidCommand = {
-        event: 5,
-        mouse_scroll: event.deltaY
-    };
-    if (mouseIsOutside) {
-        console.warn("Mouse is outside the capture area, ignoring mouse press.");
-        return;
-    }
-
-    if (enableKvmEventDebugPrintout) {
-        console.log(`Mouse scroll: mouse_scroll=${event.deltaY}`);
-    }
-
-    if (socket && socket.readyState === WebSocket.OPEN) {
-        socket.send(JSON.stringify(hidCommand));
-    } else {
-        console.error("WebSocket is not open.");
-    }
-}
-
-// Attach mouse event listeners
-let remoteCaptureEle = document.getElementById(cursorCaptureElementId);
-remoteCaptureEle.addEventListener('mousemove', handleMouseMove);
-remoteCaptureEle.addEventListener('mousedown', handleMousePress);
-remoteCaptureEle.addEventListener('mouseup', handleMouseRelease);
-remoteCaptureEle.addEventListener('wheel', handleMouseScroll);
-
-/* Keyboard */
-function isNumpadEvent(event) {
-    return event.location === 3;
-}
-
-function handleKeyDown(event) {
-    event.preventDefault();
-    event.stopImmediatePropagation();
-    const key = event.key;
-    let hidCommand = {
-        event: 0,
-        keycode: event.keyCode
-    };
-
-    if (enableKvmEventDebugPrintout) {
-        console.log(`Key down: ${key} (code: ${event.keyCode})`);
-    }
-
-    // Check if the key is a modkey on the right side of the keyboard
-    const rightModKeys = ['Control', 'Alt', 'Shift', 'Meta'];
-    if (rightModKeys.includes(key) && event.location === 2) {
-        hidCommand.is_right_modifier_key = true;
-    }else if (key === 'Enter' && isNumpadEvent(event)) {
-        //Special case for Numpad Enter
-        hidCommand.is_right_modifier_key = true;
-    }else{
-        hidCommand.is_right_modifier_key = false;
-    }
-
-    if (socket && socket.readyState === WebSocket.OPEN) {
-        socket.send(JSON.stringify(hidCommand));
-    } else {
-        console.error("WebSocket is not open.");
-    }
-}
-
-function handleKeyUp(event) {
-    event.preventDefault();
-    event.stopImmediatePropagation();
-    const key = event.key;
-    
-    let hidCommand = {
-        event: 1,
-        keycode: event.keyCode
-    };
-
-    if (enableKvmEventDebugPrintout) {
-        console.log(`Key up: ${key} (code: ${event.keyCode})`);
-    }
-
-    // Check if the key is a modkey on the right side of the keyboard
-    const rightModKeys = ['Control', 'Alt', 'Shift', 'Meta'];
-    if (rightModKeys.includes(key) && event.location === 2) {
-        hidCommand.is_right_modifier_key = true;
-    } else if (key === 'Enter' && isNumpadEvent(event)) {
-        //Special case for Numpad Enter
-        hidCommand.is_right_modifier_key = true;
-    }else{
-        hidCommand.is_right_modifier_key = false;
-    }
-
-
-    if (socket && socket.readyState === WebSocket.OPEN) {
-        socket.send(JSON.stringify(hidCommand));
-    } else {
-        console.error("WebSocket is not open.");
-    }
-}
-
-/* Start and Stop events */
-function startWebSocket(){
-    if (socket){
-        //Already started
-        console.warn("Invalid usage: HID Transport Websocket already started!");
-        return;
-    }
-    const socketUrl = socketURL;
-    socket = new WebSocket(socketUrl);
-
-    socket.addEventListener('open', function(event) {
-        console.log('HID Transport WebSocket is connected.');
-
-        // Send a soft reset command to the server to reset the HID state
-        // that possibly got out of sync from previous session
-        const hidResetCommand = {
-            event: 0xFF
-        };
-        socket.send(JSON.stringify(hidResetCommand));
-    });
-
-    socket.addEventListener('message', function(event) {
-        //Todo: handle control signals from server if needed
-        //console.log('Message from server ', event.data);
-    });
-
-    document.addEventListener('keydown', handleKeyDown);
-    document.addEventListener('keyup', handleKeyUp);
-}
-
-function stopWebSocket(){
-    if (!socket){
-        alert("No ws connection to stop");
-        return;
-    }
-
-    socket.close();
-    console.log('HID Transport WebSocket disconnected.');
-    document.removeEventListener('keydown', handleKeyDown);
-    document.removeEventListener('keyup', handleKeyUp);
-}
-
-/* Audio Streaming Frontend */
-let audioSocket;
-let audioContext;
-let audioQueue = [];
-let audioPlaying = false;
-
-//accept low, standard, high quality audio mode
-function startAudioWebSocket(quality="standard") {
-    if (audioSocket) {
-        console.warn("Audio WebSocket already started");
-        return;
-    }
-    let protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
-    let port = window.location.port ? window.location.port : (protocol === 'wss' ? 443 : 80);
-    let audioSocketURL = `${protocol}://${window.location.hostname}:${port}/audio?quality=${quality}`;
-
-    audioSocket = new WebSocket(audioSocketURL);
-    audioSocket.binaryType = 'arraybuffer';
-
-    audioSocket.onopen = function() {
-        console.log("Audio WebSocket connected");
-        if (!audioContext) {
-            audioContext = new (window.AudioContext || window.webkitAudioContext)({sampleRate: 24000});
-        }
-    };
-
-
-    const MAX_AUDIO_QUEUE = 8;
-    let PCM_SAMPLE_RATE;
-    if (quality == "high"){
-        PCM_SAMPLE_RATE = 48000; // Use 48kHz for high quality
-    } else if (quality == "low") {
-        PCM_SAMPLE_RATE = 16000; // Use 24kHz for low quality
-    } else {
-        PCM_SAMPLE_RATE = 24000; // Default to 24kHz for standard quality
-    }
-    let scheduledTime = 0;
-    audioSocket.onmessage = function(event) {
-        if (!audioContext) return;
-        let pcm = new Int16Array(event.data);
-        if (pcm.length === 0) {
-            console.warn("Received empty PCM data");
-            return;
-        }
-        if (pcm.length % 2 !== 0) {
-            console.warn("Received PCM data with odd length, dropping last sample");
-            pcm = pcm.slice(0, -1);
-        }
-        // Convert Int16 PCM to Float32 [-1, 1]
-        let floatBuf = new Float32Array(pcm.length);
-        for (let i = 0; i < pcm.length; i++) {
-            floatBuf[i] = pcm[i] / 32768;
-        }
-        // Limit queue size to prevent memory overflow
-        if (audioQueue.length >= MAX_AUDIO_QUEUE) {
-            audioQueue.shift();
-        }
-        audioQueue.push(floatBuf);
-        scheduleAudioPlayback();
-    };
-
-    audioSocket.onclose = function() {
-        console.log("Audio WebSocket closed");
-        audioSocket = null;
-        audioPlaying = false;
-        audioQueue = [];
-        scheduledTime = 0;
-    };
-
-    audioSocket.onerror = function(e) {
-        console.error("Audio WebSocket error", e);
-    };
-
-    function scheduleAudioPlayback() {
-        if (!audioContext || audioQueue.length === 0) return;
-
-        // Use audioContext.currentTime to schedule buffers back-to-back
-        if (scheduledTime < audioContext.currentTime) {
-            scheduledTime = audioContext.currentTime;
-        }
-
-        while (audioQueue.length > 0) {
-            let floatBuf = audioQueue.shift();
-            let frameCount = floatBuf.length / 2;
-            let buffer = audioContext.createBuffer(2, frameCount, PCM_SAMPLE_RATE);
-            for (let ch = 0; ch < 2; ch++) {
-                let channelData = buffer.getChannelData(ch);
-                for (let i = 0; i < frameCount; i++) {
-                    channelData[i] = floatBuf[i * 2 + ch];
-                }
-            }
-            let source = audioContext.createBufferSource();
-            source.buffer = buffer;
-            source.connect(audioContext.destination);
-            source.start(scheduledTime);
-            scheduledTime += buffer.duration;
-        }
-    }
-}
-
-function stopAudioWebSocket() {
-    if (!audioSocket) {
-        console.warn("No audio WebSocket to stop");
-        return;
-    }
-
-    if (audioSocket.readyState === WebSocket.OPEN) {
-        audioSocket.send("exit");
-    }
-    audioSocket.onclose = null; // Prevent onclose from being called again
-    audioSocket.onerror = null; // Prevent onerror from being called again
-    audioSocket.close();
-    audioSocket = null;
-    audioPlaying = false;
-    audioQueue = [];
-    if (audioContext) {
-        audioContext.close();
-        audioContext = null;
-    }
-}
-
-startWebSocket();
-
-window.addEventListener('beforeunload', function() {
-    stopAudioWebSocket();
-});

+ 0 - 34
remdeskd/www/main.css

@@ -1,34 +0,0 @@
-body {
-    margin: 0;
-    padding: 0;
-    height: 100vh;
-    width: 100vw;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    box-sizing: border-box;
-    overflow: hidden;
-    background-color: black;
-}
-
-#remoteCapture {
-    max-width: 100vw;
-    max-height: 100vh;
-    width: auto;
-    height: auto;
-    display: block;
-    margin: auto;
-    object-fit: contain;
-    cursor: url('img/cursor_overlay.png') 10 10, pointer;
-}
-
-#menu {
-    position: fixed;
-    top: 0px;
-    left: 50%;
-    transform: translateX(-50%);
-    padding: 10px;
-    display: flex;
-    gap: 10px;
-    z-index: 1000;
-}

+ 0 - 72
remdeskd/www/ui.js

@@ -1,72 +0,0 @@
-/*
-    ui.js
-    
-*/
-
-function cjax(object){
-    let csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
-    $.ajax({
-        url: object.url,
-        type: object.type || 'POST',
-        data: object.data || {},
-        headers: {
-            'X-CSRF-Token': csrf_token
-        },
-        success: object.success,
-        error: object.error
-    });
-}
-
-// Add cjax as a jQuery method
-$.cjax = cjax;
-
-function switchMassStorageToRemote(){
-    $.cjax({
-        url: '/aux/switchusbremote',
-        type: 'GET',
-        success: function(response) {
-            //alert('Mass Storage switched to Remote successfully.');
-        },
-        error: function(xhr, status, error) {
-            alert('Error switching Mass Storage to Remote: ' + error);
-        }
-    });
-}
-
-function switchMassStorageToKvm(){
-    $.cjax({
-        url: '/aux/switchusbkvm',
-        type: 'GET',
-        success: function(response) {
-            //alert('Mass Storage switched to KVM successfully.');
-        },
-        error: function(xhr, status, error) {
-            alert('Error switching Mass Storage to KVM: ' + error);
-        }
-    });
-}
-
-function toggleFullScreen(){
-    let elem = document.documentElement;
-    if (!document.fullscreenElement) {
-        if (elem.requestFullscreen) {
-            elem.requestFullscreen();
-        } else if (elem.mozRequestFullScreen) { // Firefox
-            elem.mozRequestFullScreen();
-        } else if (elem.webkitRequestFullscreen) { // Chrome, Safari, Opera
-            elem.webkitRequestFullscreen();
-        } else if (elem.msRequestFullscreen) { // IE/Edge
-            elem.msRequestFullscreen();
-        }
-    } else {
-        if (document.exitFullscreen) {
-            document.exitFullscreen();
-        } else if (document.mozCancelFullScreen) {
-            document.mozCancelFullScreen();
-        } else if (document.webkitExitFullscreen) {
-            document.webkitExitFullscreen();
-        } else if (document.msExitFullscreen) {
-            document.msExitFullscreen();
-        }
-    }
-}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff