mirror of
https://github.com/XZB-1248/Spark
synced 2025-09-26 20:21:11 +08:00
optimize: desktop viewer
This commit is contained in:
@@ -43,12 +43,12 @@ func isPrivateIP(ip _net.IP) bool {
|
||||
func GetLocalIP() (string, error) {
|
||||
ifaces, err := _net.Interfaces()
|
||||
if err != nil {
|
||||
return `Unknown`, err
|
||||
return `<UNKNOWN>`, err
|
||||
}
|
||||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
return `Unknown`, err
|
||||
return `<UNKNOWN>`, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
@@ -68,7 +68,7 @@ func GetLocalIP() (string, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return `Unknown`, errors.New(`no IP address found`)
|
||||
return `<UNKNOWN>`, errors.New(`no IP address found`)
|
||||
}
|
||||
|
||||
func GetMacAddress() (string, error) {
|
||||
@@ -179,16 +179,16 @@ func GetDevice() (*modules.Device, error) {
|
||||
}
|
||||
localIP, err := GetLocalIP()
|
||||
if err != nil {
|
||||
localIP = `<unknown>`
|
||||
localIP = `<UNKNOWN>`
|
||||
}
|
||||
macAddr, err := GetMacAddress()
|
||||
if err != nil {
|
||||
macAddr = `<unknown>`
|
||||
macAddr = `<UNKNOWN>`
|
||||
}
|
||||
cpuInfo, err := GetCPUInfo()
|
||||
if err != nil {
|
||||
cpuInfo = modules.CPU{
|
||||
Model: `<unknown>`,
|
||||
Model: `<UNKNOWN>`,
|
||||
Usage: 0,
|
||||
}
|
||||
}
|
||||
@@ -221,11 +221,11 @@ func GetDevice() (*modules.Device, error) {
|
||||
}
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = `<unknown>`
|
||||
hostname = `<UNKNOWN>`
|
||||
}
|
||||
username, err := user.Current()
|
||||
if err != nil {
|
||||
username = &user.User{Username: `<unknown>`}
|
||||
username = &user.User{Username: `<UNKNOWN>`}
|
||||
} else {
|
||||
slashIndex := strings.Index(username.Username, `\`)
|
||||
if slashIndex > -1 && slashIndex+1 < len(username.Username) {
|
||||
@@ -252,7 +252,7 @@ func GetPartialInfo() (*modules.Device, error) {
|
||||
cpuInfo, err := GetCPUInfo()
|
||||
if err != nil {
|
||||
cpuInfo = modules.CPU{
|
||||
Model: `<unknown>`,
|
||||
Model: `<UNKNOWN>`,
|
||||
Usage: 0,
|
||||
}
|
||||
}
|
||||
|
@@ -33,8 +33,7 @@ type message struct {
|
||||
frame *[]*[]byte
|
||||
}
|
||||
|
||||
// packet explanation:
|
||||
|
||||
// frame packet format:
|
||||
// +---------+---------+----------+-------------+----------+---------+---------+---------+---------+-------+
|
||||
// | magic | op code | event id | body length | img type | x | y | width | height | image |
|
||||
// +---------+---------+----------+-------------+----------+---------+---------+---------+---------+-------+
|
||||
@@ -54,9 +53,9 @@ type message struct {
|
||||
// 0: raw image
|
||||
// 1: compressed image (jpeg)
|
||||
|
||||
const fpsLimit = 10
|
||||
const compress = true
|
||||
const blockSize = 64
|
||||
const compress = 1
|
||||
const fpsLimit = 24
|
||||
const blockSize = 96
|
||||
const frameBuffer = 3
|
||||
const displayIndex = 0
|
||||
const imageQuality = 70
|
||||
@@ -66,7 +65,7 @@ var working = false
|
||||
var sessions = cmap.New()
|
||||
var prevDesktop *image.RGBA
|
||||
var displayBounds image.Rectangle
|
||||
var ErrNoImage = errors.New(`DESKTOP.NO_IMAGE_YET`)
|
||||
var errNoImage = errors.New(`DESKTOP.NO_IMAGE_YET`)
|
||||
|
||||
func init() {
|
||||
go healthCheck()
|
||||
@@ -84,65 +83,68 @@ func worker() {
|
||||
lock.Unlock()
|
||||
|
||||
var (
|
||||
screen screen
|
||||
img *image.RGBA
|
||||
err error
|
||||
errors int
|
||||
numErrors int
|
||||
screen Screen
|
||||
img *image.RGBA
|
||||
err error
|
||||
)
|
||||
screen.init(displayIndex)
|
||||
screen.Init(displayIndex, displayBounds)
|
||||
for working {
|
||||
if sessions.Count() == 0 {
|
||||
lock.Lock()
|
||||
working = false
|
||||
lock.Unlock()
|
||||
break
|
||||
}
|
||||
img = image.NewRGBA(displayBounds)
|
||||
err = screen.capture(img, displayBounds)
|
||||
img, err = screen.Capture()
|
||||
if err != nil {
|
||||
if err == ErrNoImage {
|
||||
return
|
||||
if err == errNoImage {
|
||||
<-time.After(time.Second / fpsLimit)
|
||||
continue
|
||||
}
|
||||
errors++
|
||||
if errors > 10 {
|
||||
numErrors++
|
||||
if numErrors > 10 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
errors = 0
|
||||
numErrors = 0
|
||||
diff := imageCompare(img, prevDesktop, compress)
|
||||
if diff != nil && len(diff) > 0 {
|
||||
prevDesktop = img
|
||||
sessions.IterCb(func(uuid string, t any) bool {
|
||||
desktop := t.(*session)
|
||||
desktop.lock.Lock()
|
||||
if !desktop.escape {
|
||||
if len(desktop.channel) >= frameBuffer {
|
||||
select {
|
||||
case <-desktop.channel:
|
||||
default:
|
||||
}
|
||||
}
|
||||
desktop.channel <- message{t: 0, frame: &diff}
|
||||
}
|
||||
desktop.lock.Unlock()
|
||||
return true
|
||||
})
|
||||
sendImageDiff(diff)
|
||||
}
|
||||
<-time.After(time.Second / fpsLimit)
|
||||
}
|
||||
}
|
||||
img = nil
|
||||
prevDesktop = nil
|
||||
if errors > 10 {
|
||||
quitAll(err.Error())
|
||||
if numErrors > 10 {
|
||||
quitAllDesktop(err.Error())
|
||||
}
|
||||
lock.Lock()
|
||||
working = false
|
||||
lock.Unlock()
|
||||
screen.release()
|
||||
screen.Release()
|
||||
runtime.UnlockOSThread()
|
||||
go runtime.GC()
|
||||
}
|
||||
|
||||
func quitAll(info string) {
|
||||
func sendImageDiff(diff []*[]byte) {
|
||||
sessions.IterCb(func(uuid string, t any) bool {
|
||||
desktop := t.(*session)
|
||||
desktop.lock.Lock()
|
||||
if !desktop.escape {
|
||||
if len(desktop.channel) >= frameBuffer {
|
||||
select {
|
||||
case <-desktop.channel:
|
||||
default:
|
||||
}
|
||||
}
|
||||
desktop.channel <- message{t: 0, frame: &diff}
|
||||
}
|
||||
desktop.lock.Unlock()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func quitAllDesktop(info string) {
|
||||
keys := make([]string, 0)
|
||||
sessions.IterCb(func(uuid string, t any) bool {
|
||||
keys = append(keys, uuid)
|
||||
@@ -157,7 +159,7 @@ func quitAll(info string) {
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func imageCompare(img, prev *image.RGBA, compress bool) []*[]byte {
|
||||
func imageCompare(img, prev *image.RGBA, compress int) []*[]byte {
|
||||
result := make([]*[]byte, 0)
|
||||
if prev == nil {
|
||||
return splitFullImage(img, compress)
|
||||
@@ -168,24 +170,13 @@ func imageCompare(img, prev *image.RGBA, compress bool) []*[]byte {
|
||||
}
|
||||
for _, rect := range diff {
|
||||
block := getImageBlock(img, rect, compress)
|
||||
buf := make([]byte, 12)
|
||||
binary.BigEndian.PutUint16(buf[0:2], uint16(len(block)+10))
|
||||
if compress {
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(1))
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(0))
|
||||
}
|
||||
binary.BigEndian.PutUint16(buf[4:6], uint16(rect.Min.X))
|
||||
binary.BigEndian.PutUint16(buf[6:8], uint16(rect.Min.Y))
|
||||
binary.BigEndian.PutUint16(buf[8:10], uint16(rect.Size().X))
|
||||
binary.BigEndian.PutUint16(buf[10:12], uint16(rect.Size().Y))
|
||||
buf = append(buf, block...)
|
||||
result = append(result, &buf)
|
||||
block = makeImageBlock(block, rect, compress)
|
||||
result = append(result, &block)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func splitFullImage(img *image.RGBA, compress bool) []*[]byte {
|
||||
func splitFullImage(img *image.RGBA, compress int) []*[]byte {
|
||||
if img == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -197,26 +188,16 @@ func splitFullImage(img *image.RGBA, compress bool) []*[]byte {
|
||||
height := utils.If(y+blockSize > imgHeight, imgHeight-y, blockSize)
|
||||
for x := rect.Min.X; x < rect.Max.X; x += blockSize {
|
||||
width := utils.If(x+blockSize > imgWidth, imgWidth-x, blockSize)
|
||||
block := getImageBlock(img, image.Rect(x, y, x+width, y+height), compress)
|
||||
buf := make([]byte, 12)
|
||||
binary.BigEndian.PutUint16(buf[0:2], uint16(len(block)+10))
|
||||
if compress {
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(1))
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(0))
|
||||
}
|
||||
binary.BigEndian.PutUint16(buf[4:6], uint16(x))
|
||||
binary.BigEndian.PutUint16(buf[6:8], uint16(y))
|
||||
binary.BigEndian.PutUint16(buf[8:10], uint16(width))
|
||||
binary.BigEndian.PutUint16(buf[10:12], uint16(height))
|
||||
buf = append(buf, block...)
|
||||
result = append(result, &buf)
|
||||
blockRect := image.Rect(x, y, x+width, y+height)
|
||||
block := getImageBlock(img, blockRect, compress)
|
||||
block = makeImageBlock(block, blockRect, compress)
|
||||
result = append(result, &block)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getImageBlock(img *image.RGBA, rect image.Rectangle, compress bool) []byte {
|
||||
func getImageBlock(img *image.RGBA, rect image.Rectangle, compress int) []byte {
|
||||
width := rect.Dx()
|
||||
height := rect.Dy()
|
||||
buf := make([]byte, width*height*4)
|
||||
@@ -227,17 +208,32 @@ func getImageBlock(img *image.RGBA, rect image.Rectangle, compress bool) []byte
|
||||
bufPos += width * 4
|
||||
imgPos += img.Stride
|
||||
}
|
||||
if !compress {
|
||||
switch compress {
|
||||
case 0:
|
||||
return buf
|
||||
case 1:
|
||||
subImg := &image.RGBA{
|
||||
Pix: buf,
|
||||
Stride: width * 4,
|
||||
Rect: image.Rect(0, 0, width, height),
|
||||
}
|
||||
writer := &bytes.Buffer{}
|
||||
jpeg.Encode(writer, subImg, &jpeg.Options{Quality: imageQuality})
|
||||
return writer.Bytes()
|
||||
}
|
||||
subImg := &image.RGBA{
|
||||
Pix: buf,
|
||||
Stride: width * 4,
|
||||
Rect: image.Rect(0, 0, width, height),
|
||||
}
|
||||
writer := &bytes.Buffer{}
|
||||
jpeg.Encode(writer, subImg, &jpeg.Options{Quality: imageQuality})
|
||||
return writer.Bytes()
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeImageBlock(block []byte, rect image.Rectangle, compress int) []byte {
|
||||
buf := make([]byte, 12)
|
||||
binary.BigEndian.PutUint16(buf[0:2], uint16(len(block)+10))
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(compress))
|
||||
binary.BigEndian.PutUint16(buf[4:6], uint16(rect.Min.X))
|
||||
binary.BigEndian.PutUint16(buf[6:8], uint16(rect.Min.Y))
|
||||
binary.BigEndian.PutUint16(buf[8:10], uint16(rect.Size().X))
|
||||
binary.BigEndian.PutUint16(buf[10:12], uint16(rect.Size().Y))
|
||||
buf = append(buf, block...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func getDiff(img, prev *image.RGBA) []image.Rectangle {
|
||||
@@ -312,7 +308,7 @@ func InitDesktop(pack modules.Packet) error {
|
||||
rawEvent: rawEvent,
|
||||
lastPack: utils.Unix,
|
||||
escape: false,
|
||||
channel: make(chan message, 3),
|
||||
channel: make(chan message, 5),
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
{
|
||||
@@ -350,9 +346,7 @@ func PingDesktop(pack modules.Packet) {
|
||||
} else {
|
||||
uuid = val.(string)
|
||||
}
|
||||
if val, ok := sessions.Get(uuid); !ok {
|
||||
return
|
||||
} else {
|
||||
if val, ok := sessions.Get(uuid); ok {
|
||||
desktop = val.(*session)
|
||||
desktop.lastPack = utils.Unix
|
||||
}
|
||||
@@ -442,11 +436,10 @@ func handleDesktop(pack modules.Packet, uuid string, desktop *session) {
|
||||
binary.BigEndian.PutUint16(data[4:6], uint16(displayBounds.Dy()))
|
||||
buf = append(buf, data...)
|
||||
common.WSConn.SendData(buf)
|
||||
break
|
||||
continue
|
||||
}
|
||||
case <-time.After(time.Second * 5):
|
||||
default:
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
case <-time.After(7 * time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
client/service/desktop/desktop_others.go
Normal file
23
client/service/desktop/desktop_others.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"github.com/kbinani/screenshot"
|
||||
"image"
|
||||
)
|
||||
|
||||
type Screen struct {
|
||||
rect image.Rectangle
|
||||
}
|
||||
|
||||
func (s *Screen) Init(_ uint, rect image.Rectangle) {
|
||||
s.rect = rect
|
||||
}
|
||||
|
||||
func (s *Screen) Capture() (*image.RGBA, error) {
|
||||
return screenshot.CaptureRect(s.rect)
|
||||
}
|
||||
|
||||
func (s *Screen) Release() {}
|
198
client/service/desktop/desktop_windows.go
Normal file
198
client/service/desktop/desktop_windows.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kirides/go-d3d/d3d11"
|
||||
"github.com/kirides/go-d3d/outputduplication"
|
||||
"github.com/kirides/go-d3d/outputduplication/swizzle"
|
||||
winDXGI "github.com/kirides/go-d3d/win"
|
||||
winGDI "github.com/lxn/win"
|
||||
"image"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
libUser32, _ = syscall.LoadLibrary("user32.dll")
|
||||
funcGetDesktopWindow, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "GetDesktopWindow")
|
||||
funcEnumDisplayMonitors, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "EnumDisplayMonitors")
|
||||
funcGetMonitorInfo, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "GetMonitorInfoW")
|
||||
funcEnumDisplaySettings, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "EnumDisplaySettingsW")
|
||||
)
|
||||
|
||||
type Screen struct {
|
||||
screen ScreenCapture
|
||||
}
|
||||
type ScreenCapture interface {
|
||||
Init(uint, image.Rectangle) error
|
||||
Capture() (*image.RGBA, error)
|
||||
Release()
|
||||
}
|
||||
type ScreenDXGI struct {
|
||||
rect image.Rectangle
|
||||
device *d3d11.ID3D11Device
|
||||
deviceCtx *d3d11.ID3D11DeviceContext
|
||||
ddup *outputduplication.OutputDuplicator
|
||||
}
|
||||
type ScreenGDI struct {
|
||||
rect image.Rectangle
|
||||
width int
|
||||
height int
|
||||
hwnd winGDI.HWND
|
||||
hdc winGDI.HDC
|
||||
memoryDevice winGDI.HDC
|
||||
bitmap winGDI.HBITMAP
|
||||
bitmapInfo winGDI.BITMAPINFOHEADER
|
||||
bitmapDataSize uintptr
|
||||
hmem winGDI.HGLOBAL
|
||||
memptr unsafe.Pointer
|
||||
}
|
||||
|
||||
func (s *Screen) Init(displayIndex uint, rect image.Rectangle) {
|
||||
dxgi := ScreenDXGI{}
|
||||
if dxgi.Init(displayIndex, rect) == nil {
|
||||
s.screen = &dxgi
|
||||
} else {
|
||||
gdi := ScreenGDI{}
|
||||
gdi.Init(displayIndex, rect)
|
||||
s.screen = &gdi
|
||||
}
|
||||
}
|
||||
func (s *Screen) Capture() (*image.RGBA, error) {
|
||||
return s.screen.Capture()
|
||||
}
|
||||
func (s *Screen) Release() {
|
||||
s.screen.Release()
|
||||
}
|
||||
|
||||
func (s *ScreenDXGI) Init(displayIndex uint, rect image.Rectangle) error {
|
||||
s.rect = rect
|
||||
var err error
|
||||
if !winDXGI.IsValidDpiAwarenessContext(winDXGI.DpiAwarenessContextPerMonitorAwareV2) {
|
||||
return errors.New("no valid DPI awareness context")
|
||||
}
|
||||
_, err = winDXGI.SetThreadDpiAwarenessContext(winDXGI.DpiAwarenessContextPerMonitorAwareV2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.device, s.deviceCtx, err = d3d11.NewD3D11Device()
|
||||
s.ddup, err = outputduplication.NewIDXGIOutputDuplication(s.device, s.deviceCtx, displayIndex)
|
||||
if err != nil {
|
||||
s.device.Release()
|
||||
s.deviceCtx.Release()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *ScreenDXGI) Capture() (*image.RGBA, error) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, s.rect.Dx(), s.rect.Dy()))
|
||||
err := s.ddup.GetImage(img, 100)
|
||||
if err == outputduplication.ErrNoImageYet {
|
||||
return nil, errNoImage
|
||||
}
|
||||
return img, err
|
||||
}
|
||||
func (s *ScreenDXGI) Release() {
|
||||
if s.ddup != nil {
|
||||
s.ddup.Release()
|
||||
s.ddup = nil
|
||||
}
|
||||
if s.device != nil {
|
||||
s.device.Release()
|
||||
s.device = nil
|
||||
}
|
||||
if s.deviceCtx != nil {
|
||||
s.deviceCtx.Release()
|
||||
s.deviceCtx = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScreenGDI) Init(_ uint, rect image.Rectangle) error {
|
||||
s.rect = rect
|
||||
s.width = rect.Dx()
|
||||
s.height = rect.Dy()
|
||||
|
||||
s.hwnd = getDesktopWindow()
|
||||
s.hdc = winGDI.GetDC(s.hwnd)
|
||||
if s.hdc == 0 {
|
||||
s.Release()
|
||||
return errors.New("GetDC failed")
|
||||
}
|
||||
s.memoryDevice = winGDI.CreateCompatibleDC(s.hdc)
|
||||
if s.memoryDevice == 0 {
|
||||
s.Release()
|
||||
return errors.New("CreateCompatibleDC failed")
|
||||
}
|
||||
s.bitmap = winGDI.CreateCompatibleBitmap(s.hdc, int32(s.width), int32(s.height))
|
||||
if s.bitmap == 0 {
|
||||
s.Release()
|
||||
return errors.New("CreateCompatibleBitmap failed")
|
||||
}
|
||||
|
||||
s.bitmapInfo = winGDI.BITMAPINFOHEADER{}
|
||||
s.bitmapInfo.BiSize = uint32(unsafe.Sizeof(s.bitmapInfo))
|
||||
s.bitmapInfo.BiPlanes = 1
|
||||
s.bitmapInfo.BiBitCount = 32
|
||||
s.bitmapInfo.BiWidth = int32(s.width)
|
||||
s.bitmapInfo.BiHeight = -int32(s.height)
|
||||
s.bitmapInfo.BiCompression = winGDI.BI_RGB
|
||||
s.bitmapInfo.BiSizeImage = uint32(s.width * s.height * 4)
|
||||
|
||||
s.bitmapDataSize = uintptr(((int64(s.width)*int64(s.bitmapInfo.BiBitCount) + 31) / 32) * 4 * int64(s.height))
|
||||
s.hmem = winGDI.GlobalAlloc(winGDI.GMEM_MOVEABLE, s.bitmapDataSize)
|
||||
if s.hmem == 0 {
|
||||
s.Release()
|
||||
return errors.New("GlobalAlloc failed")
|
||||
}
|
||||
s.memptr = winGDI.GlobalLock(s.hmem)
|
||||
if s.memptr == nil {
|
||||
s.Release()
|
||||
return errors.New("GlobalLock failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *ScreenGDI) Capture() (*image.RGBA, error) {
|
||||
old := winGDI.SelectObject(s.memoryDevice, winGDI.HGDIOBJ(s.bitmap))
|
||||
if old == 0 {
|
||||
return nil, errors.New("SelectObject failed")
|
||||
}
|
||||
|
||||
if !winGDI.BitBlt(s.memoryDevice, 0, 0, int32(s.width), int32(s.height), s.hdc, int32(s.rect.Min.X), int32(s.rect.Min.Y), winGDI.SRCCOPY) {
|
||||
return nil, errors.New("BitBlt failed")
|
||||
}
|
||||
|
||||
if winGDI.GetDIBits(s.hdc, s.bitmap, 0, uint32(s.height), (*uint8)(s.memptr), (*winGDI.BITMAPINFO)(unsafe.Pointer(&s.bitmapInfo)), winGDI.DIB_RGB_COLORS) == 0 {
|
||||
return nil, errors.New("GetDIBits failed")
|
||||
}
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, s.width, s.height))
|
||||
imageBytes := ((*[1 << 30]byte)(unsafe.Pointer(s.memptr)))[:s.bitmapDataSize:s.bitmapDataSize]
|
||||
copy(img.Pix[:s.bitmapDataSize], imageBytes)
|
||||
swizzle.BGRA(img.Pix)
|
||||
|
||||
return img, nil
|
||||
}
|
||||
func (s *ScreenGDI) Release() {
|
||||
if s.hdc != 0 {
|
||||
winGDI.ReleaseDC(s.hwnd, s.hdc)
|
||||
s.hdc = 0
|
||||
}
|
||||
if s.memoryDevice != 0 {
|
||||
winGDI.DeleteDC(s.memoryDevice)
|
||||
s.memoryDevice = 0
|
||||
}
|
||||
if s.bitmap != 0 {
|
||||
winGDI.DeleteObject(winGDI.HGDIOBJ(s.bitmap))
|
||||
s.bitmap = 0
|
||||
}
|
||||
if s.hmem != 0 {
|
||||
winGDI.GlobalUnlock(s.hmem)
|
||||
winGDI.GlobalFree(s.hmem)
|
||||
s.hmem = 0
|
||||
}
|
||||
}
|
||||
func getDesktopWindow() winGDI.HWND {
|
||||
ret, _, _ := syscall.SyscallN(funcGetDesktopWindow)
|
||||
return winGDI.HWND(ret)
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"github.com/kbinani/screenshot"
|
||||
"image"
|
||||
)
|
||||
|
||||
type screen struct {
|
||||
displayIndex int
|
||||
}
|
||||
|
||||
func (s *screen) init(displayIndex int) {
|
||||
s.displayIndex = displayIndex
|
||||
}
|
||||
|
||||
func (s *screen) capture(img *image.RGBA, _ image.Rectangle) error {
|
||||
image, err := screenshot.CaptureDisplay(displayIndex)
|
||||
if err == nil {
|
||||
*img = *image
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *screen) release() {
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"github.com/kirides/screencapture/d3d"
|
||||
"github.com/kirides/screencapture/screenshot"
|
||||
"github.com/kirides/screencapture/win"
|
||||
"image"
|
||||
)
|
||||
|
||||
type screen struct {
|
||||
dxgi bool
|
||||
ddup *d3d.OutputDuplicator
|
||||
device *d3d.ID3D11Device
|
||||
deviceCtx *d3d.ID3D11DeviceContext
|
||||
displayIndex int
|
||||
}
|
||||
|
||||
func (s *screen) init(displayIndex int) {
|
||||
var err error
|
||||
s.displayIndex = displayIndex
|
||||
s.dxgi = false
|
||||
return
|
||||
if win.IsValidDpiAwarenessContext(win.DpiAwarenessContextPerMonitorAwareV2) {
|
||||
_, err = win.SetThreadDpiAwarenessContext(win.DpiAwarenessContextPerMonitorAwareV2)
|
||||
s.dxgi = err == nil
|
||||
}
|
||||
if s.dxgi {
|
||||
s.device, s.deviceCtx, err = d3d.NewD3D11Device()
|
||||
s.ddup, err = d3d.NewIDXGIOutputDuplication(s.device, s.deviceCtx, uint(displayIndex))
|
||||
if err != nil {
|
||||
s.dxgi = false
|
||||
s.device.Release()
|
||||
s.deviceCtx.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *screen) capture(img *image.RGBA, bounds image.Rectangle) error {
|
||||
var err error
|
||||
if s.dxgi {
|
||||
err = s.ddup.GetImage(img, 100)
|
||||
if err != nil {
|
||||
if err == d3d.ErrNoImageYet {
|
||||
return ErrNoImage
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return screenshot.CaptureImg(img, 0, 0, bounds.Dx(), bounds.Dy())
|
||||
}
|
||||
|
||||
func (s *screen) release() {
|
||||
if s.ddup != nil {
|
||||
s.ddup.Release()
|
||||
}
|
||||
if s.device != nil {
|
||||
s.device.Release()
|
||||
}
|
||||
if s.deviceCtx != nil {
|
||||
s.deviceCtx.Release()
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@ func ListProcesses() ([]Process, error) {
|
||||
for i := 0; i < len(processes); i++ {
|
||||
name, err := processes[i].Name()
|
||||
if err != nil {
|
||||
name = `<Unknown>`
|
||||
name = `<UNKNOWN>`
|
||||
}
|
||||
result = append(result, Process{Name: name, Pid: processes[i].Pid})
|
||||
}
|
||||
|
13
go.mod
13
go.mod
@@ -11,14 +11,15 @@ require (
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kataras/golog v0.1.7
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
|
||||
github.com/kirides/go-d3d v1.0.0
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e
|
||||
github.com/rakyll/statik v0.1.7
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
)
|
||||
|
||||
require github.com/kirides/screencapture v0.0.0-20211101142135-282f3f7e0f33
|
||||
|
||||
require (
|
||||
github.com/gen2brain/shm v0.0.0-20210511105953-083dbc7d9d83 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20221026125803-c33c9e32b1c8 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/locales v0.13.0 // indirect
|
||||
@@ -27,11 +28,10 @@ require (
|
||||
github.com/golang/protobuf v1.3.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
|
||||
github.com/jezek/xgb v1.1.0 // indirect
|
||||
github.com/kataras/pio v0.0.10 // indirect
|
||||
github.com/leodido/go-urn v1.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -40,9 +40,8 @@ require (
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||
golang.org/x/net v0.0.0-20220111093109-d55c255bac03 // indirect
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
)
|
||||
|
14
go.sum
14
go.sum
@@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gen2brain/shm v0.0.0-20210511105953-083dbc7d9d83 h1:fRNwUddc/xxdx5kQ38X4+q/Grnqlp9zfV/ssKzSzVk0=
|
||||
github.com/gen2brain/shm v0.0.0-20210511105953-083dbc7d9d83/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gen2brain/shm v0.0.0-20221026125803-c33c9e32b1c8 h1:u4/UVF0sNxlqDwCptjIUTUkZW4UoZDrcHzvd2kNnF/k=
|
||||
github.com/gen2brain/shm v0.0.0-20221026125803-c33c9e32b1c8/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
@@ -36,8 +36,9 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/imroc/req/v3 v3.8.2 h1:wFZ7B0dclCQyjClP5GwXRboUGIek5l0mCpodrGgT01c=
|
||||
github.com/imroc/req/v3 v3.8.2/go.mod h1:3JIicOKEDHfCSYYNLb/ObZNpx64EV5y40VlHMwhUCzU=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
@@ -47,8 +48,8 @@ github.com/kataras/pio v0.0.10 h1:b0qtPUqOpM2O+bqa5wr2O6dN4cQNwSmFd6HQqgVae0g=
|
||||
github.com/kataras/pio v0.0.10/go.mod h1:gS3ui9xSD+lAUpbYnjOGiQyY7sUMJO+EHpiRzhtZ5no=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
|
||||
github.com/kirides/screencapture v0.0.0-20211101142135-282f3f7e0f33 h1:DXPbp2f7LBZzkWnpPGUG+H1+82++20vNIpalTamWv6E=
|
||||
github.com/kirides/screencapture v0.0.0-20211101142135-282f3f7e0f33/go.mod h1:fMSGsolzmMhah/U24dXBHSf/6Ue/mXVSIb4wRU7U4Ts=
|
||||
github.com/kirides/go-d3d v1.0.0 h1:i1XycQ+8KCZqToyGq7o6I92EwcJDXgIICSVjLhmHuaM=
|
||||
github.com/kirides/go-d3d v1.0.0/go.mod h1:KUNIIJoB+psyPedDBv8TY4gKXmPJT3K1+F2nL2pKDPQ=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
@@ -100,8 +101,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@@ -359,7 +359,7 @@ func checkAuth() gin.HandlerFunc {
|
||||
|
||||
if ctx.IsAborted() {
|
||||
blocked.Set(addr, now+1)
|
||||
user = utils.If(len(user) == 0, `EMPTY`, user)
|
||||
user = utils.If(len(user) == 0, `<EMPTY>`, user)
|
||||
common.Warn(ctx, `LOGIN_ATTEMPT`, `fail`, ``, map[string]any{
|
||||
`user`: user,
|
||||
})
|
||||
|
@@ -15,9 +15,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
JSON = jsoniter.Config{EscapeHTML: false, SortMapKeys: true, ValidateJsonRawMessage: true}.Froze()
|
||||
|
||||
ErrEntityInvalid = errors.New(`common.ENTITY_INVALID`)
|
||||
ErrFailedVerification = errors.New(`common.ENTITY_CHECK_FAILED`)
|
||||
JSON = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
)
|
||||
|
||||
func If[T any](b bool, t, f T) T {
|
||||
|
@@ -41,6 +41,7 @@ function ScreenModal(props) {
|
||||
function initCanvas() {
|
||||
if (!canvas) return;
|
||||
ctx = canvas.getContext('2d', {alpha: false});
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
}
|
||||
function construct() {
|
||||
if (ctx !== null) {
|
||||
@@ -87,24 +88,11 @@ function ScreenModal(props) {
|
||||
}
|
||||
}
|
||||
function fullScreen() {
|
||||
try {
|
||||
canvas.requestFullscreen();
|
||||
} catch {}
|
||||
try {
|
||||
canvas.webkitRequestFullscreen();
|
||||
} catch {}
|
||||
try {
|
||||
canvas.mozRequestFullScreen();
|
||||
} catch {}
|
||||
try {
|
||||
canvas.msRequestFullscreen();
|
||||
} catch {}
|
||||
canvas.requestFullscreen().catch(console.error);
|
||||
}
|
||||
function refresh() {
|
||||
if (canvas && props.open) {
|
||||
if (!conn) {
|
||||
canvas.width = 1920;
|
||||
canvas.height = 1080;
|
||||
initCanvas(canvas);
|
||||
construct(canvas);
|
||||
} else {
|
||||
@@ -150,13 +138,18 @@ function ScreenModal(props) {
|
||||
dv = null;
|
||||
}
|
||||
function updateImage(ab, it, dx, dy, bw, bh, canvasCtx) {
|
||||
if (it === 0) {
|
||||
canvasCtx.putImageData(new ImageData(new Uint8ClampedArray(ab), bw, bh), dx, dy, 0, 0, bw, bh);
|
||||
} else {
|
||||
createImageBitmap(new Blob([ab]), 0, 0, bw, bh)
|
||||
.then((ib) => {
|
||||
canvasCtx.drawImage(ib, 0, 0, bw, bh, dx, dy, bw, bh);
|
||||
});
|
||||
switch (it) {
|
||||
case 0:
|
||||
canvasCtx.putImageData(new ImageData(new Uint8ClampedArray(ab), bw, bh), dx, dy, 0, 0, bw, bh);
|
||||
break;
|
||||
case 1:
|
||||
createImageBitmap(new Blob([ab]), 0, 0, bw, bh, {
|
||||
premultiplyAlpha: 'none',
|
||||
colorSpaceConversion: 'none'
|
||||
}).then((ib) => {
|
||||
canvasCtx.drawImage(ib, 0, 0, bw, bh, dx, dy, bw, bh);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
function handleJSON(ab) {
|
||||
|
Reference in New Issue
Block a user