| require ( | require ( | ||||
| github.com/BurntSushi/toml v0.4.1 | github.com/BurntSushi/toml v0.4.1 | ||||
| github.com/go-vgo/robotgo v0.99.0 | github.com/go-vgo/robotgo v0.99.0 | ||||
| github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 | |||||
| ) | ) |
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 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 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= | ||||
| github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= | 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 h1:Lp6kvic1/LxkSShf6KNjcrcE21FCFlonrCguQN96Ay4= | ||||
| github.com/go-vgo/robotgo v0.99.0/go.mod h1:0+i2QWRmZtbIF02RwmiGfFj33Judprukd8ls5J6Eajg= | 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 h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= | ||||
| github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= | 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= | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= |
| import "errors" | import "errors" | ||||
| import "log" | import "log" | ||||
| import "os" | import "os" | ||||
| import "fmt" | |||||
| import "path" | import "path" | ||||
| import "net/http" | import "net/http" | ||||
| import "io/ioutil" | import "io/ioutil" | ||||
| import "encoding/json" | import "encoding/json" | ||||
| import "github.com/go-vgo/robotgo" | import "github.com/go-vgo/robotgo" | ||||
| import "github.com/BurntSushi/toml" | import "github.com/BurntSushi/toml" | ||||
| import "coffee.mort.mediator/screencap" | |||||
| type Config struct { | type Config struct { | ||||
| BasePath string `toml:"base_path"` | BasePath string `toml:"base_path"` | ||||
| fs := http.FileServer(http.Dir("./web")) | fs := http.FileServer(http.Dir("./web")) | ||||
| http.Handle("/", fs) | 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" { | if req.Method == "GET" { | ||||
| var size ScreenSizeData | var size ScreenSizeData | ||||
| size.Width, size.Height = robotgo.GetScreenSize() | size.Width, size.Height = robotgo.GetScreenSize() | ||||
| } | } | ||||
| })) | })) | ||||
| 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" { | if req.Method == "GET" { | ||||
| var pos MousePosData | var pos MousePosData | ||||
| pos.X, pos.Y = robotgo.GetMousePos() | pos.X, pos.Y = robotgo.GetMousePos() | ||||
| } | } | ||||
| })) | })) | ||||
| 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 { | http.HandleFunc("/api/dir/", handler(func(w RW, req *Req) error { | ||||
| if req.Method == "GET" { | if req.Method == "GET" { | ||||
| subPath := req.URL.Path[len("/api/dir/"):] | subPath := req.URL.Path[len("/api/dir/"):] | ||||
| } | } | ||||
| })) | })) | ||||
| go screencap.Run() | |||||
| log.Println("Listening on :3000...") | log.Println("Listening on :3000...") | ||||
| err = http.ListenAndServe("localhost:3000", nil) | err = http.ListenAndServe("localhost:3000", nil) | ||||
| if err != nil { | if err != nil { |
| 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() | |||||
| } | |||||
| } |
| <html> | <html> | ||||
| <head> | <head> | ||||
| <meta charset="utf-8"> | <meta charset="utf-8"> | ||||
| <title>Mediator</title> | |||||
| <title>Mediator - Files</title> | |||||
| <link rel="stylesheet" href="style.css"> | <link rel="stylesheet" href="style.css"> | ||||
| <link rel="icon" type="image/png" href="favicon.png"> | <link rel="icon" type="image/png" href="favicon.png"> | ||||
| <!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> |