Files
2022-05-10 19:54:49 +08:00

159 lines
3.6 KiB
Go

package vnc
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"errors"
"image"
"image/draw"
"image/jpeg"
"net/http"
"os"
"strings"
"sync"
"github.com/gorilla/websocket"
"github.com/lwch/logging"
"github.com/lwch/natpass/code/client/conn"
"github.com/lwch/natpass/code/network"
"github.com/lwch/natpass/code/utils"
"github.com/lwch/runtime"
)
var upgrader = websocket.Upgrader{}
// WS websocket handler
func (v *VNC) WS(conn *conn.Conn, w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/ws/")
local, err := upgrader.Upgrade(w, r, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer local.Close()
ch := conn.ChanRead(id)
defer conn.SendDisconnect(v.link.target, v.link.id)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer cancel()
defer wg.Done()
v.remoteRead(ctx, ch, local)
}()
go func() {
defer cancel()
defer wg.Done()
v.localRead(ctx, local, conn)
}()
wg.Wait()
}
func (v *VNC) remoteRead(ctx context.Context, ch <-chan *network.Msg, local *websocket.Conn) {
defer utils.Recover("remoteRead")
for {
var msg *network.Msg
select {
case msg = <-ch:
case <-ctx.Done():
return
}
switch msg.GetXType() {
case network.Msg_vnc_image:
data, err := decodeImage(msg.GetVimg())
runtime.Assert(err)
replyImage(local, msg.GetVimg(), data, len(msg.GetVimg().GetData()))
case network.Msg_vnc_clipboard:
v.chClipboard <- msg.GetVclipboard()
default:
logging.Error("on message: %s", msg.GetXType().String())
return
}
}
}
func (v *VNC) localRead(ctx context.Context, local *websocket.Conn, remote *conn.Conn) {
defer utils.Recover("localRead")
for {
select {
case <-ctx.Done():
return
default:
}
_, data, err := local.ReadMessage()
if err != nil {
logging.Error("local read: %v", err)
return
}
var msg struct {
Action string `json:"action"`
}
err = json.Unmarshal(data, &msg)
if err != nil {
logging.Error("unmarshal: %v", err)
return
}
switch msg.Action {
case "mouse":
v.mouseEvent(remote, data)
case "keyboard":
v.keyboardEvent(remote, data)
case "cad":
v.cadEvent(remote)
case "scroll":
v.scrollEvent(remote, data)
}
}
}
func decodeImage(data *network.VncImage) ([]byte, error) {
switch data.GetEncode() {
case network.VncImage_raw:
return data.GetData(), nil
case network.VncImage_jpeg:
img, err := jpeg.Decode(bytes.NewReader(data.GetData()))
if err != nil {
return nil, err
}
// dumpImage(img)
rect := img.Bounds()
raw := image.NewRGBA(rect)
draw.Draw(raw, rect, img, rect.Min, draw.Src)
return raw.Pix, nil
case network.VncImage_png:
// TODO
}
return nil, errors.New("unsupported")
}
func dumpImage(img image.Image) {
f, err := os.Create(`./debug.jpeg`)
if err != nil {
logging.Error("debug: %v", err)
return
}
defer f.Close()
err = jpeg.Encode(f, img, nil)
if err != nil {
logging.Error("encode: %v", err)
return
}
}
func replyImage(conn *websocket.Conn, msg *network.VncImage, data []byte, srcSize int) {
info := msg.GetXInfo()
buf := make([]byte, len(data)+28)
binary.BigEndian.PutUint32(buf, info.GetScreenWidth())
binary.BigEndian.PutUint32(buf[4:], info.GetScreenHeight())
binary.BigEndian.PutUint32(buf[8:], info.GetRectX())
binary.BigEndian.PutUint32(buf[12:], info.GetRectY())
binary.BigEndian.PutUint32(buf[16:], info.GetRectWidth())
binary.BigEndian.PutUint32(buf[20:], info.GetRectHeight())
binary.BigEndian.PutUint32(buf[24:], uint32(srcSize))
copy(buf[28:], data)
conn.WriteMessage(websocket.BinaryMessage, buf)
}