@@ -5,4 +5,5 @@ go 1.16 | |||
require ( | |||
github.com/BurntSushi/toml v0.4.1 | |||
github.com/go-vgo/robotgo v0.99.0 | |||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 | |||
) |
@@ -7,10 +7,16 @@ github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrU | |||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY= | |||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo= | |||
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= | |||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= | |||
github.com/go-vgo/robotgo v0.99.0 h1:Lp6kvic1/LxkSShf6KNjcrcE21FCFlonrCguQN96Ay4= | |||
github.com/go-vgo/robotgo v0.99.0/go.mod h1:0+i2QWRmZtbIF02RwmiGfFj33Judprukd8ls5J6Eajg= | |||
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/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/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= | |||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= | |||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= |
@@ -3,12 +3,14 @@ package main | |||
import "errors" | |||
import "log" | |||
import "os" | |||
import "fmt" | |||
import "path" | |||
import "net/http" | |||
import "io/ioutil" | |||
import "encoding/json" | |||
import "github.com/go-vgo/robotgo" | |||
import "github.com/BurntSushi/toml" | |||
import "coffee.mort.mediator/screencap" | |||
type Config struct { | |||
BasePath string `toml:"base_path"` | |||
@@ -81,7 +83,7 @@ func main() { | |||
fs := http.FileServer(http.Dir("./web")) | |||
http.Handle("/", fs) | |||
http.HandleFunc("/api/screen-size", handler(func(w RW, req *Req) error { | |||
http.HandleFunc("/api/remote/screen-size", handler(func(w RW, req *Req) error { | |||
if req.Method == "GET" { | |||
var size ScreenSizeData | |||
size.Width, size.Height = robotgo.GetScreenSize() | |||
@@ -91,7 +93,7 @@ func main() { | |||
} | |||
})) | |||
http.HandleFunc("/api/mouse-pos", handler(func(w RW, req *Req) error { | |||
http.HandleFunc("/api/remote/mouse-pos", handler(func(w RW, req *Req) error { | |||
if req.Method == "GET" { | |||
var pos MousePosData | |||
pos.X, pos.Y = robotgo.GetMousePos() | |||
@@ -110,6 +112,43 @@ func main() { | |||
} | |||
})) | |||
http.HandleFunc("/api/remote/screencast", handler(func(w RW, req *Req) error { | |||
if req.Method == "GET" { | |||
w.Header().Add("Content-Type", "multipart/x-mixed-replace;boundary=MEDIATOR_FRAME_BOUNDARY") | |||
w.WriteHeader(200) | |||
for { | |||
img := <-screencap.Capture() | |||
log.Printf("Got image, %v bytes", img.Length) | |||
var err error | |||
_, err = w.Write([]byte(fmt.Sprintf( | |||
"--MEDIATOR_FRAME_BOUNDARY\r\n" + | |||
"Content-Type: image/jpeg\r\n" + | |||
"Content-Length: %d\r\n" + | |||
"\r\n", img.Length))) | |||
if err != nil { | |||
log.Printf("Write error: %v", err) | |||
return nil | |||
} | |||
_, err = w.Write(img.Data[0:img.Length]) | |||
if err != nil { | |||
log.Printf("Write error: %v", err) | |||
return nil | |||
} | |||
_, err = w.Write([]byte("\r\n")) | |||
if err != nil { | |||
log.Printf("Write error: %v", err) | |||
return nil | |||
} | |||
} | |||
} else { | |||
return errors.New("Invalid method: " + req.Method) | |||
} | |||
})); | |||
http.HandleFunc("/api/dir/", handler(func(w RW, req *Req) error { | |||
if req.Method == "GET" { | |||
subPath := req.URL.Path[len("/api/dir/"):] | |||
@@ -140,6 +179,8 @@ func main() { | |||
} | |||
})) | |||
go screencap.Run() | |||
log.Println("Listening on :3000...") | |||
err = http.ListenAndServe("localhost:3000", nil) | |||
if err != nil { |
@@ -0,0 +1,88 @@ | |||
package screencap | |||
import "github.com/kbinani/screenshot" | |||
import "image/jpeg" | |||
import "sync" | |||
import "time" | |||
import "log" | |||
type Buffer struct { | |||
Data []byte | |||
Length int | |||
} | |||
func (buf *Buffer) Write(data []byte) (int, error) { | |||
if len(buf.Data) == 0 { | |||
l := len(data) | |||
if l < 1024 { | |||
l = 2024 | |||
} | |||
buf.Data = make([]byte, l) | |||
} else if buf.Length + len(data) > len(buf.Data) { | |||
newSize := len(buf.Data) * 2 | |||
for buf.Length + len(data) > newSize { | |||
newSize *= 2 | |||
} | |||
newBuf := make([]byte, newSize) | |||
copy(newBuf, buf.Data[0:buf.Length]) | |||
buf.Data = newBuf | |||
} | |||
copy(buf.Data[buf.Length:], data) | |||
buf.Length += len(data) | |||
return len(data), nil | |||
} | |||
var ( | |||
mut = sync.Mutex{} | |||
chans = make([]chan *Buffer, 0) | |||
startChan = make(chan struct{}, 1) | |||
buffers = make([]Buffer, 4) | |||
currentBuffer = 0 | |||
) | |||
func Capture() chan *Buffer { | |||
ch := make(chan *Buffer) | |||
mut.Lock() | |||
chans = append(chans, ch) | |||
mut.Unlock() | |||
select { | |||
case startChan <- struct{}{}: | |||
default: | |||
} | |||
return ch | |||
} | |||
func Run() { | |||
for { | |||
<-startChan | |||
img, err := screenshot.CaptureDisplay(0) | |||
if err != nil { | |||
log.Printf("Failed to capture screenshot: %v", err) | |||
time.Sleep(2 * time.Second) | |||
continue | |||
} | |||
buf := &buffers[currentBuffer] | |||
currentBuffer = (currentBuffer + 1) % len(buffers) | |||
buf.Length = 0 | |||
err = jpeg.Encode(buf, img, &jpeg.Options{Quality: 80}) | |||
if err != nil { | |||
log.Printf("Failed to encode jpeg: %v", err) | |||
time.Sleep(2 * time.Second) | |||
continue | |||
} | |||
mut.Lock() | |||
for _, ch := range chans { | |||
ch <- buf | |||
} | |||
chans = make([]chan *Buffer, 0) | |||
mut.Unlock() | |||
} | |||
} |
@@ -2,7 +2,7 @@ | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>Mediator</title> | |||
<title>Mediator - Files</title> | |||
<link rel="stylesheet" href="style.css"> | |||
<link rel="icon" type="image/png" href="favicon.png"> | |||
@@ -0,0 +1,23 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>Mediator - Remote</title> | |||
<link rel="stylesheet" href="style.css"> | |||
<link rel="icon" type="image/png" href="favicon.png"> | |||
</head> | |||
<body> | |||
<img src="/api/remote/screencast"> | |||
<script src="util.js"></script> | |||
<script> | |||
async function main() { | |||
let screenSize = api("GET", "remote/screen-size"); | |||
let mousePos = api("GET", "remote/mouse-pos"); | |||
} | |||
main(); | |||
</script> | |||
</body> | |||
</html> |