Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
Simon Vieille | 7cf77935b3 | ||
Simon Vieille | 451717ddac | ||
Simon Vieille | ae53fe1ab7 | ||
Simon Vieille | 5aab502a76 | ||
Simon Vieille | e3cbf3eda2 | ||
Simon Vieille | ff5a66ad07 | ||
Simon Vieille | a07f14765a | ||
Simon Vieille | 233d1a4d96 | ||
Simon Vieille | a9601c479d | ||
Simon Vieille | e65fccee03 | ||
Simon Vieille | 73b9f98297 | ||
Simon Vieille | fc8e74c772 | ||
Simon Vieille | 253073f15f | ||
Simon Vieille | c865080df1 | ||
Simon Vieille | 5f18c04d4d | ||
Simon Vieille | e6934dd9aa | ||
Simon Vieille | 71ce3bb90d | ||
Simon Vieille | 3533ea4dfe | ||
dd24fda35b | |||
5e76b76c31 | |||
d80395fd6e | |||
676e11db1c | |||
6d6bfee742 | |||
f335eaf792 | |||
6a2670b39c |
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,7 +1,32 @@
|
|||
## [Unreleased]
|
||||
|
||||
## v1.0.0
|
||||
### Added
|
||||
* add live on pointer block
|
||||
* allow to show pointer
|
||||
|
||||
- rewrite of https://gitnet.fr/deblan/remote-i3wm-ws
|
||||
- add configuration file
|
||||
- add authentication
|
||||
## v2.0.1
|
||||
### Fixed
|
||||
* remove padding on pointer buttons
|
||||
* fix live and screenshot render
|
||||
* fix route of manifest
|
||||
|
||||
## v2.0.0
|
||||
### Added
|
||||
* add an option to start the web server with TLS (HTTPS)
|
||||
### Changed
|
||||
* remove jquery
|
||||
* upgrade Bootstrap
|
||||
|
||||
## v1.0.2
|
||||
### Fixed
|
||||
* close connection on ws error
|
||||
|
||||
## v1.0.1
|
||||
### Fixed
|
||||
* fix process overload: replace golang.org/x/net/websocket with github.com/gorilla/websocket
|
||||
|
||||
## v1.0.0
|
||||
### Added
|
||||
* rewrite of https://gitnet.fr/deblan/remote-i3wm-ws
|
||||
* add configuration file
|
||||
* add authentication
|
||||
|
|
|
@ -25,6 +25,6 @@ Download the latest binary from [releases](https://gitnet.fr/deblan/remote-i3wm-
|
|||
|
||||
Create a YAML file from [the model](https://gitnet.fr/deblan/remote-i3wm-go/src/branch/main/config.yaml) and then customize it!
|
||||
|
||||
Run the server: `/path/to/app-latest-linux-amd64 /path/to/config.yaml`.
|
||||
Run the server: `./app-linux-amd64 ./config.yaml`.
|
||||
|
||||
Now you can access the web interface on http://127.0.0.1:4000.
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/net/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Actions struct {
|
||||
Functions map[string]func(ws *websocket.Conn, msg string) error
|
||||
Functions map[string]func(ws *websocket.Conn, msg []byte) error
|
||||
}
|
||||
|
||||
func (actions Actions) add(name string, callback func(ws *websocket.Conn, msg string) error) {
|
||||
func (actions Actions) add(name string, callback func(ws *websocket.Conn, msg []byte) error) {
|
||||
actions.Functions[name] = callback
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,6 @@ func (actions Actions) has(name string) bool {
|
|||
return exists
|
||||
}
|
||||
|
||||
func (actions Actions) exec(name string, ws *websocket.Conn, msg string) error {
|
||||
func (actions Actions) exec(name string, ws *websocket.Conn, msg []byte) error {
|
||||
return actions.Functions[name](ws, msg)
|
||||
}
|
||||
|
|
10
config.go
10
config.go
|
@ -1,10 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type TlsConfig struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
CertFile string `yaml:"certificate"`
|
||||
CertKeyFile string `yaml:"certificate_key"`
|
||||
}
|
||||
|
||||
type ServerAuthConfig struct {
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
|
@ -13,6 +20,7 @@ type ServerAuthConfig struct {
|
|||
type ServerConfig struct {
|
||||
Listen string `yaml:"listen"`
|
||||
Auth ServerAuthConfig `yaml:"auth"`
|
||||
Tls TlsConfig `yaml:tls`
|
||||
}
|
||||
|
||||
type RemoteItemConfigItem struct {
|
||||
|
|
|
@ -3,6 +3,10 @@ server:
|
|||
username: admin
|
||||
password: admin
|
||||
listen: 0.0.0.0:4000
|
||||
tls:
|
||||
enable: false
|
||||
certificate: /path/to/server.crt
|
||||
certificate_key: /path/to/server.key
|
||||
|
||||
remote:
|
||||
- label: Keyboard
|
||||
|
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/daaku/go.zipexe v1.0.2 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jezek/xgb v1.1.0 // indirect
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 // indirect
|
||||
github.com/labstack/echo/v4 v4.11.1 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -10,6 +10,8 @@ github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/3
|
|||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
|
|
17
main.go
17
main.go
|
@ -17,8 +17,9 @@ var (
|
|||
//go:embed static
|
||||
staticFiles embed.FS
|
||||
//go:embed views/layout views/page
|
||||
views embed.FS
|
||||
config Config
|
||||
views embed.FS
|
||||
config Config
|
||||
actions Actions
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -55,10 +56,20 @@ func main() {
|
|||
}))
|
||||
|
||||
assetHandler := http.FileServer(rice.MustFindBox("static").HTTPBox())
|
||||
actions = createActions()
|
||||
|
||||
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler)))
|
||||
e.GET("/manifest.webmanifest", manifestController)
|
||||
e.GET("/", homeController)
|
||||
e.GET("/ws", wsController)
|
||||
|
||||
e.Logger.Fatal(e.Start(config.Server.Listen))
|
||||
if config.Server.Tls.Enable == false {
|
||||
e.Logger.Fatal(e.Start(config.Server.Listen))
|
||||
} else {
|
||||
e.Logger.Fatal(e.StartTLS(
|
||||
config.Server.Listen,
|
||||
config.Server.Tls.CertFile,
|
||||
config.Server.Tls.CertKeyFile,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
46
manifest_controller.go
Normal file
46
manifest_controller.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ManifestIcon struct {
|
||||
Src string `json:"src"`
|
||||
Type string `json:"type"`
|
||||
Sizes string `json:"sizes"`
|
||||
}
|
||||
|
||||
type Manifest struct {
|
||||
ShortName string `json:"short_name"`
|
||||
Name string `json:"name"`
|
||||
ThemeColor string `json:"theme_color"`
|
||||
BackgroundColor string `json:"background_color"`
|
||||
Display string `json:"display"`
|
||||
Orientation string `json:"orientation"`
|
||||
Scope string `json:"scope"`
|
||||
StartUrl string `json:"start_url"`
|
||||
Icons []ManifestIcon `json:"icons"`
|
||||
}
|
||||
|
||||
func manifestController(c echo.Context) error {
|
||||
manifest := &Manifest{
|
||||
ShortName: "RWM",
|
||||
Name: "Remote i3WM",
|
||||
ThemeColor: "#1e3650",
|
||||
BackgroundColor: "#ffffff",
|
||||
Display: "standalone",
|
||||
Orientation: "portrait-primary",
|
||||
Scope: "/",
|
||||
StartUrl: "/",
|
||||
Icons: []ManifestIcon{
|
||||
ManifestIcon{
|
||||
Src: "/static/img/icon.png",
|
||||
Type: "image/png",
|
||||
Sizes: "96x96",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return c.JSONPretty(http.StatusOK, manifest, " ")
|
||||
}
|
104
rice-box.go
104
rice-box.go
File diff suppressed because one or more lines are too long
7
static/bootstrap.bundle.min.js
vendored
7
static/bootstrap.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
7
static/bootstrap.min.css
vendored
7
static/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
7
static/bootstrap.min.js
vendored
7
static/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
6
static/css/bootstrap.min.css
vendored
Normal file
6
static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,10 @@
|
|||
:root {
|
||||
--link-color: #1e3650;
|
||||
--bs-link-color: var(--link-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1e3650;
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
|
@ -62,8 +67,10 @@ a {
|
|||
top: calc(33px + 38px);
|
||||
margin: auto;
|
||||
background: #ccc;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
width: calc(100% - 50px);
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
|
@ -92,6 +99,8 @@ a {
|
|||
z-index: 110;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#pointer-buttons .btn {
|
||||
|
@ -135,3 +144,14 @@ a {
|
|||
margin-top: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mouse-screenshot-live {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
#mouse-text-live {
|
||||
display: inline-block;
|
||||
width: calc(100% - 100px);
|
||||
}
|
BIN
static/img/icon.png
Normal file
BIN
static/img/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
4
static/jquery.min.js
vendored
4
static/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
353
static/js/main.js
Normal file
353
static/js/main.js
Normal file
|
@ -0,0 +1,353 @@
|
|||
let ws
|
||||
let pointer, scroller, response, screenshotImg
|
||||
let scrollLastTimestamp, scrollLastValue
|
||||
let mousePosX, mousePosY, mouseInitPosX, mouseInitPosY
|
||||
let isLive = false
|
||||
let isPointerLive = false
|
||||
let isScreenshotWaiting = false
|
||||
let isPointerScreenshotWaiting = false
|
||||
|
||||
function createWebSocketConnection() {
|
||||
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
|
||||
ws = new WebSocket(`${protocol}://${window.location.hostname}:${window.location.port}/ws`)
|
||||
|
||||
ws.onopen = function(event) {
|
||||
document.querySelector('#disconneced').style.display = 'none'
|
||||
}
|
||||
|
||||
ws.onclose = function(event) {
|
||||
document.querySelector('#disconneced').style.display = 'block'
|
||||
|
||||
window.setTimeout(createWebSocketConnection, 5000)
|
||||
}
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
let data = JSON.parse(event.data)
|
||||
|
||||
if (data.type === 'response') {
|
||||
response.innerText = data.value
|
||||
response.style.display = 'block'
|
||||
|
||||
window.setTimeout(function() {
|
||||
response.style.display = 'none'
|
||||
}, 2500)
|
||||
} else if (data.type === 'screenshot') {
|
||||
if (isScreenshotWaiting) {
|
||||
isScreenshotWaiting = false
|
||||
screenshotImg.setAttribute('src', 'data:image/png;base64, ' + data.value)
|
||||
}
|
||||
|
||||
let pointer = document.querySelector('#pointer')
|
||||
|
||||
if (isPointerScreenshotWaiting) {
|
||||
pointer.style.backgroundImage = `url('data:image/png;base64, ${data.value}')`
|
||||
isPointerScreenshotWaiting = false
|
||||
} else {
|
||||
pointer.style.backgroundImage = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function navigationClickHandler(e) {
|
||||
if (e.target.getAttribute('href') === '#') {
|
||||
return
|
||||
}
|
||||
|
||||
Array.from(document.querySelectorAll('.pane')).forEach((item) => {
|
||||
item.style.display = 'none'
|
||||
})
|
||||
|
||||
document.querySelector(e.target.getAttribute('href')).style.display = 'block'
|
||||
|
||||
Array.from(document.querySelectorAll('#nav a')).forEach((item) => {
|
||||
item.classList.remove('active')
|
||||
})
|
||||
|
||||
e.target.classList.add('active')
|
||||
}
|
||||
|
||||
function buttonClickHandler(e) {
|
||||
ws.send(e.target.getAttribute('data-msg'))
|
||||
}
|
||||
|
||||
function shortcutClearClickHandler(e) {
|
||||
document.querySelector('#shortcut-key').value = ''
|
||||
|
||||
Array.from(document.querySelectorAll('#shortcuts_special_keys input:checked')).forEach((item) => {
|
||||
item.checked = false
|
||||
item.change()
|
||||
})
|
||||
}
|
||||
|
||||
function shortcutSendClickHandler(e) {
|
||||
let keys = []
|
||||
let key = document.querySelector('#shortcut-key').value
|
||||
|
||||
Array.from(document.querySelectorAll('#shortcuts_special_keys input:checked')).forEach((item) => {
|
||||
keys.push(item.value)
|
||||
})
|
||||
|
||||
if (keys.length) {
|
||||
if (key) {
|
||||
keys.push(key)
|
||||
}
|
||||
|
||||
ws.send('{"type":"keys","value": "' + (keys.join(',').replace('"', '\\"')) + '"}')
|
||||
}
|
||||
}
|
||||
|
||||
function textClearClickHandler(e) {
|
||||
document.querySelector('#text').value = ''
|
||||
}
|
||||
|
||||
function textSendClickHandler(e) {
|
||||
const keys = document.querySelector('#text').value
|
||||
|
||||
if (keys.length) {
|
||||
ws.send('{"type":"text","value": "' + (keys.replace('"', '\\"')) + '"}')
|
||||
}
|
||||
}
|
||||
|
||||
function textKeyUpHandler(e) {
|
||||
const keys = document.querySelector('#text').value
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
ws.send('{"type":"text","value": "' + (keys.replace('"', '\\"')) + '"}')
|
||||
}
|
||||
}
|
||||
|
||||
function liveTextKeyUpHandler(e) {
|
||||
const value = e.target.value
|
||||
|
||||
if (e.keyCode === 8) {
|
||||
ws.send('{"type":"key","value": "backspace"}')
|
||||
} else if (e.keyCode === 13) {
|
||||
ws.send('{"type":"key","value": "enter"}')
|
||||
} else if (value.length) {
|
||||
if (value === ' ') {
|
||||
ws.send('{"type":"key","value": "space"}')
|
||||
} else {
|
||||
ws.send('{"type":"text","value": "' + (value.replace('"', '\\"')) + '"}')
|
||||
}
|
||||
|
||||
e.target.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function shortcutsSpecialKeysOnChangeHandler(e) {
|
||||
Array.from(document.querySelectorAll('#shortcuts_special_keys input:checked')).forEach((item) => {
|
||||
item.parentNode.classList.add('btn-primary')
|
||||
item.parentNode.classList.remove('btn-secondary')
|
||||
})
|
||||
|
||||
Array.from(document.querySelectorAll('#shortcuts_special_keys input:not(:checked)')).forEach((item) => {
|
||||
item.parentNode.classList.add('btn-secondary')
|
||||
item.parentNode.classList.remove('btn-primary')
|
||||
})
|
||||
}
|
||||
|
||||
function pointerClickHandler(e) {
|
||||
ws.send('{"type":"pointer","click":"left"}')
|
||||
}
|
||||
|
||||
function scrollerTouchStartHandler(e) {
|
||||
mouseInitPosY = e.targetTouches[0].pageY
|
||||
}
|
||||
|
||||
function scrollerTouchMoveHandler(e) {
|
||||
let touch = e.changedTouches[0]
|
||||
let value = ((touch.pageY - mouseInitPosY > 0) ? 'down' : 'up')
|
||||
let now = new Date().getTime()
|
||||
|
||||
if (touch.pageY === mouseInitPosY || value === scrollLastValue && scrollLastTimestamp !== null && now - scrollLastTimestamp < 200) {
|
||||
return
|
||||
}
|
||||
|
||||
scrollLastTimestamp = now
|
||||
scrollLastValue = value
|
||||
mouseInitPosY = touch.pageY
|
||||
|
||||
ws.send('{"type":"scroll","value": "' + value + '"}')
|
||||
}
|
||||
|
||||
function pointerTouchStartHandler(e) {
|
||||
const touch = e.targetTouches[0]
|
||||
mouseInitPosX = touch.pageX
|
||||
mouseInitPosY = touch.pageY
|
||||
}
|
||||
|
||||
function pointerLiveHandler(e) {
|
||||
if (!e.target.checked) {
|
||||
isPointerLive = false
|
||||
isPointerScreenshotWaiting = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
isPointerLive = true
|
||||
|
||||
let doScreenshot = function() {
|
||||
if (isPointerLive) {
|
||||
if (!isPointerScreenshotWaiting) {
|
||||
isPointerScreenshotWaiting = true
|
||||
ws.send(`{"type":"screenshot","quality":"lq","pointer":true}`)
|
||||
}
|
||||
|
||||
window.setTimeout(doScreenshot, 300)
|
||||
}
|
||||
}
|
||||
|
||||
doScreenshot()
|
||||
}
|
||||
|
||||
function pointerTouchMoveHandler(e) {
|
||||
if (e.changedTouches.length === 2) {
|
||||
return scrollerTouchMoveHandler(e)
|
||||
}
|
||||
|
||||
const touch = e.changedTouches[0]
|
||||
mousePosX = touch.pageX
|
||||
mousePosY = touch.pageY
|
||||
|
||||
const newX = mousePosX - mouseInitPosX
|
||||
const newY = mousePosY - mouseInitPosY
|
||||
|
||||
mouseInitPosX = mousePosX
|
||||
mouseInitPosY = mousePosY
|
||||
|
||||
let msg = '{"type":"pointer","x": "' + newX + '","y": "' + newY + '"}'
|
||||
|
||||
ws.send(msg)
|
||||
}
|
||||
|
||||
function liveHqClickHandler(e) {
|
||||
return liveClickHandler(e, 'hq')
|
||||
}
|
||||
|
||||
function liveLqClickHandler(e) {
|
||||
return liveClickHandler(e, 'lq')
|
||||
}
|
||||
|
||||
function liveClickHandler(e, quality) {
|
||||
if (isLive) {
|
||||
isLive = false
|
||||
isScreenshotWaiting = false
|
||||
|
||||
document.querySelector('#live-hq').innerText = 'Live HQ'
|
||||
document.querySelector('#live-lq').innerText = 'Live LQ'
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
isLive = true
|
||||
|
||||
e.target.innerText = 'Stop live'
|
||||
|
||||
let doScreenshot = function() {
|
||||
if (isLive) {
|
||||
if (!isScreenshotWaiting) {
|
||||
isScreenshotWaiting = true
|
||||
ws.send(`{"type":"screenshot","quality":"${quality}"}`)
|
||||
}
|
||||
|
||||
window.setTimeout(doScreenshot, 100)
|
||||
}
|
||||
}
|
||||
|
||||
doScreenshot()
|
||||
}
|
||||
|
||||
function fullscreenHandler(e) {
|
||||
let element = document.querySelector(e.target.getAttribute('data-target'))
|
||||
let isFullscreen = parseInt(e.target.getAttribute('data-fullscreen'))
|
||||
|
||||
document.querySelector('body').classList.toggle('fullscreen', isFullscreen)
|
||||
|
||||
if (isFullscreen) {
|
||||
e.target.setAttribute('data-fullscreen', '0')
|
||||
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
}
|
||||
} else {
|
||||
e.target.setAttribute('data-fullscreen', '1')
|
||||
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen()
|
||||
} else if (element.webkitRequestFullscreen) {
|
||||
element.webkitRequestFullscreen()
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function documentHashHandler() {
|
||||
const hash = window.location.hash
|
||||
|
||||
if (hash) {
|
||||
document.querySelector('a[href="' + hash + '"]').click()
|
||||
} else {
|
||||
document.querySelector('#nav > li:first-child a').click()
|
||||
}
|
||||
}
|
||||
|
||||
function addEventListenerOn(selector, eventName, listener) {
|
||||
if (typeof selector === 'string') {
|
||||
Array.from(document.querySelectorAll(selector)).forEach((element) => {
|
||||
element.addEventListener(eventName, listener)
|
||||
})
|
||||
} else {
|
||||
selector.addEventListener(eventName, listener)
|
||||
}
|
||||
}
|
||||
|
||||
function addListeners() {
|
||||
addEventListenerOn('#nav a', 'click', navigationClickHandler)
|
||||
addEventListenerOn('button[data-msg]', 'click', buttonClickHandler)
|
||||
|
||||
addEventListenerOn('#shortcut-clear', 'click', shortcutClearClickHandler)
|
||||
addEventListenerOn('#shortcuts_special_keys input', 'change', shortcutsSpecialKeysOnChangeHandler)
|
||||
addEventListenerOn('#shortcut-send', 'click', shortcutSendClickHandler)
|
||||
|
||||
addEventListenerOn('#text-clear', 'click', textClearClickHandler)
|
||||
addEventListenerOn('#text-send', 'click', textSendClickHandler)
|
||||
addEventListenerOn('#text', 'keyup', textKeyUpHandler)
|
||||
addEventListenerOn('.live-text', 'keyup', liveTextKeyUpHandler)
|
||||
|
||||
addEventListenerOn(scroller, 'touchstart', scrollerTouchStartHandler)
|
||||
addEventListenerOn(scroller, 'touchmove', scrollerTouchMoveHandler)
|
||||
|
||||
addEventListenerOn(pointer, 'click', pointerClickHandler)
|
||||
addEventListenerOn(pointer, 'touchstart', pointerTouchStartHandler)
|
||||
addEventListenerOn(pointer, 'touchmove', pointerTouchMoveHandler)
|
||||
addEventListenerOn('#mouse-screenshot-live input', 'change', pointerLiveHandler)
|
||||
|
||||
addEventListenerOn('#live-hq', 'click', liveHqClickHandler)
|
||||
addEventListenerOn('#live-lq', 'click', liveLqClickHandler)
|
||||
addEventListenerOn('.btn-fullscreen', 'click', fullscreenHandler)
|
||||
}
|
||||
|
||||
function bootstrap() {
|
||||
pointer = document.querySelector('#pointer')
|
||||
scroller = document.querySelector('#scrollbar')
|
||||
response = document.querySelector('#response')
|
||||
screenshotImg = document.querySelector('#screenshot img')
|
||||
|
||||
shortcutsSpecialKeysOnChangeHandler()
|
||||
createWebSocketConnection()
|
||||
addListeners()
|
||||
documentHashHandler()
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/static/js/service_worker.js')
|
||||
}
|
||||
}
|
||||
|
||||
addEventListenerOn(window, 'DOMContentLoaded', bootstrap)
|
3
static/js/service_worker.js
Normal file
3
static/js/service_worker.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
self.addEventListener("install", (e) => {
|
||||
console.log("[Service Worker] Install");
|
||||
});
|
308
static/main.js
308
static/main.js
|
@ -1,308 +0,0 @@
|
|||
var ws;
|
||||
var $pointer, $scroller, $response, $screenshotImg;
|
||||
var scrollLastTimestamp, scrollLastValue;
|
||||
var mousePosX, mousePosY, mouseInitPosX, mouseInitPosY;
|
||||
var isLive = false;
|
||||
var isScreenshotWaiting = false;
|
||||
|
||||
var createWebSocketConnection = function() {
|
||||
ws = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/ws`);
|
||||
|
||||
ws.onopen = function(event) {
|
||||
$('#disconneced').fadeOut();
|
||||
}
|
||||
|
||||
ws.onclose = function(event) {
|
||||
$('#disconneced').fadeIn();
|
||||
|
||||
window.setTimeout(createWebSocketConnection, 5000);
|
||||
}
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
var data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'response') {
|
||||
$response.text(data.value);
|
||||
$response.fadeIn();
|
||||
|
||||
window.setTimeout(function() {
|
||||
$response.fadeOut();
|
||||
}, 2500);
|
||||
} else if (data.type === 'screenshot') {
|
||||
isScreenshotWaiting = false
|
||||
$screenshotImg.attr('src', 'data:image/png;base64, ' + data.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var navigationClickHandler = function(e) {
|
||||
if ($(this).attr('href') === '#') {
|
||||
return
|
||||
}
|
||||
|
||||
$('.pane').hide();
|
||||
|
||||
var target = $(this).attr('href');
|
||||
$(target).show();
|
||||
|
||||
$('#nav a').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
}
|
||||
|
||||
var buttonClickHandler = function(e) {
|
||||
var msg = $(this).attr('data-msg');
|
||||
ws.send(msg);
|
||||
}
|
||||
|
||||
var shortcutClearClickHandler = function(e) {
|
||||
$('#shortcut-key').val('');
|
||||
$('#shortcuts_special_keys input:checked').each(function() {
|
||||
$(this).prop('checked', false).trigger('change');
|
||||
});
|
||||
}
|
||||
|
||||
var shortcutSendClickHandler = function(e) {
|
||||
var keys = [];
|
||||
|
||||
$('#shortcuts_special_keys input:checked').each(function() {
|
||||
keys.push($(this).val());
|
||||
});
|
||||
|
||||
var key = $('#shortcut-key').val();
|
||||
|
||||
if (keys.length) {
|
||||
if (key) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
var msg = '{"type":"keys","value": "' + (keys.join(',').replace('"', '\\"')) + '"}';
|
||||
ws.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
var textClearClickHandler = function(e) {
|
||||
$('#text').val('');
|
||||
}
|
||||
|
||||
var textSendClickHandler = function(e) {
|
||||
var keys = $('#text').val();
|
||||
|
||||
if (keys.length) {
|
||||
var msg = '{"type":"text","value": "' + (keys.replace('"', '\\"')) + '"}';
|
||||
ws.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
var textKeyUpHandler = function(e) {
|
||||
var keys = $('#text').val();
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
var msg = '{"type":"text","value": "' + (keys.replace('"', '\\"')) + '"}';
|
||||
ws.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
var liveTextKeyUpHandler = function(e) {
|
||||
var value = $(this).val();
|
||||
var live = false;
|
||||
|
||||
if (e.keyCode === 8) {
|
||||
var msg = '{"type":"key","value": "backspace"}';
|
||||
ws.send(msg);
|
||||
} else if (e.keyCode === 13) {
|
||||
var msg = '{"type":"key","value": "enter"}';
|
||||
ws.send(msg);
|
||||
} else if (value.length) {
|
||||
if (value === ' ') {
|
||||
var msg = '{"type":"key","value": "space"}';
|
||||
ws.send(msg);
|
||||
} else {
|
||||
var msg = '{"type":"text","value": "' + (value.replace('"', '\\"')) + '"}';
|
||||
ws.send(msg);
|
||||
}
|
||||
|
||||
$(this).val('');
|
||||
}
|
||||
}
|
||||
|
||||
var shortcutsSpecialKeysOnChangeHandler = function(e) {
|
||||
$('#shortcuts_special_keys input:checked').each(function() {
|
||||
$(this).parent().addClass('btn-primary').removeClass('btn-secondary');
|
||||
})
|
||||
|
||||
$('#shortcuts_special_keys input:not(:checked)').each(function() {
|
||||
$(this).parent().addClass('btn-secondary').removeClass('btn-primary');
|
||||
})
|
||||
}
|
||||
|
||||
var pointerClickHandler = function(e) {
|
||||
var msg = '{"type":"pointer","click":"left"}';
|
||||
ws.send(msg);
|
||||
}
|
||||
|
||||
var scrollerTouchStartHandler = function(e) {
|
||||
var touch = e.targetTouches[0];
|
||||
mouseInitPosY = touch.pageY;
|
||||
}
|
||||
|
||||
var scrollerTouchMoveHandler = function(e) {
|
||||
var touch = e.changedTouches[0];
|
||||
var value = ((touch.pageY - mouseInitPosY > 0) ? 'down' : 'up');
|
||||
var now = new Date().getTime();
|
||||
|
||||
if (touch.pageY === mouseInitPosY || value === scrollLastValue && scrollLastTimestamp !== null && now - scrollLastTimestamp < 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollLastTimestamp = now;
|
||||
scrollLastValue = value;
|
||||
|
||||
var msg = '{"type":"scroll","value": "' + value + '"}';
|
||||
|
||||
mouseInitPosY = touch.pageY;
|
||||
ws.send(msg);
|
||||
}
|
||||
|
||||
var pointerTouchStartHandler = function(e) {
|
||||
var touch = e.targetTouches[0];
|
||||
mouseInitPosX = touch.pageX;
|
||||
mouseInitPosY = touch.pageY;
|
||||
}
|
||||
|
||||
var pointerTouchMoveHandler = function(e) {
|
||||
if (e.changedTouches.length === 2) {
|
||||
return scrollerTouchMoveHandler(e);
|
||||
}
|
||||
|
||||
var touch = e.changedTouches[0];
|
||||
mousePosX = touch.pageX;
|
||||
mousePosY = touch.pageY;
|
||||
|
||||
var newX = mousePosX - mouseInitPosX;
|
||||
var newY = mousePosY - mouseInitPosY;
|
||||
|
||||
mouseInitPosX = mousePosX;
|
||||
mouseInitPosY = mousePosY;
|
||||
|
||||
var msg = '{"type":"pointer","x": "' + newX + '","y": "' + newY + '"}';
|
||||
|
||||
ws.send(msg);
|
||||
}
|
||||
|
||||
var liveHqClickHandler = function(e) {
|
||||
return liveClickHandler(e, 'hq')
|
||||
}
|
||||
|
||||
var liveLqClickHandler = function(e) {
|
||||
return liveClickHandler(e, 'lq')
|
||||
}
|
||||
|
||||
var liveClickHandler = function(e, quality) {
|
||||
if (isLive) {
|
||||
isLive = false;
|
||||
isScreenshotWaiting = false;
|
||||
$('#live-hq').text(`Live HQ`);
|
||||
$('#live-lq').text(`Live LQ`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
isLive = true;
|
||||
$(e.target).text('Stop live');
|
||||
|
||||
var doScreenshot = function() {
|
||||
if (isLive) {
|
||||
if (!isScreenshotWaiting) {
|
||||
isScreenshotWaiting = true
|
||||
ws.send(`{"type":"screenshot","quality":"${quality}"}`);
|
||||
}
|
||||
|
||||
window.setTimeout(doScreenshot, 100);
|
||||
}
|
||||
}
|
||||
|
||||
doScreenshot();
|
||||
}
|
||||
|
||||
var fullscreenHandler = function(e) {
|
||||
var element = $(e.target.getAttribute('data-target'));
|
||||
var isFullscreen = parseInt($(e.target).attr('data-fullscreen'));
|
||||
|
||||
$('body').toggleClass('fullscreen', isFullscreen)
|
||||
|
||||
if (isFullscreen) {
|
||||
element.attr('data-fullscreen', '0');
|
||||
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
}
|
||||
} else {
|
||||
$(e.target).attr('data-fullscreen', '1');
|
||||
|
||||
if (element.get(0).requestFullscreen) {
|
||||
element.get(0).requestFullscreen();
|
||||
} else if (element.get(0).webkitRequestFullscreen) {
|
||||
element.get(0).webkitRequestFullscreen();
|
||||
} else if (element.get(0).mozRequestFullScreen) {
|
||||
element.get(0).mozRequestFullScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var documentHashHandler = function() {
|
||||
var hash = window.location.hash;
|
||||
|
||||
if (hash) {
|
||||
$('a[href="' + hash + '"]').click();
|
||||
} else {
|
||||
$('#nav > li:first-child a').click();
|
||||
}
|
||||
}
|
||||
|
||||
var addListeners = function() {
|
||||
$('#nav a').click(navigationClickHandler);
|
||||
$('button[data-msg]').click(buttonClickHandler);
|
||||
|
||||
$('#shortcut-clear').click(shortcutClearClickHandler);
|
||||
$('#shortcuts_special_keys input').change(shortcutsSpecialKeysOnChangeHandler);
|
||||
$('#shortcut-send').click(shortcutSendClickHandler);
|
||||
|
||||
$('#text-clear').click(textClearClickHandler);
|
||||
$('#text-send').click(textSendClickHandler);
|
||||
$('#text').on('keyup', textKeyUpHandler);
|
||||
$('.live-text').on('keyup', liveTextKeyUpHandler);
|
||||
|
||||
$scroller
|
||||
.on('touchstart', scrollerTouchStartHandler)
|
||||
.on('touchmove', scrollerTouchMoveHandler);
|
||||
|
||||
$pointer
|
||||
.on('click', pointerClickHandler)
|
||||
.on('touchstart', pointerTouchStartHandler)
|
||||
.on('touchmove', pointerTouchMoveHandler);
|
||||
|
||||
$('#live-hq').click(liveHqClickHandler);
|
||||
$('#live-lq').click(liveLqClickHandler);
|
||||
|
||||
$('.btn-fullscreen').click(fullscreenHandler)
|
||||
}
|
||||
|
||||
var bootstrap = function() {
|
||||
shortcutsSpecialKeysOnChangeHandler();
|
||||
createWebSocketConnection();
|
||||
addListeners();
|
||||
documentHashHandler();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$pointer = $('#pointer');
|
||||
$scroller = $('#scrollbar');
|
||||
$response = $('#response');
|
||||
$screenshotImg = $('#screenshot img');
|
||||
|
||||
bootstrap();
|
||||
});
|
|
@ -4,8 +4,10 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||
<link rel="stylesheet" href="/static/bootstrap.min.css" type="text/css">
|
||||
<link rel="stylesheet" href="/static/main.css" type="text/css">
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css" type="text/css">
|
||||
<link rel="stylesheet" href="/static/css/main.css" type="text/css">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="icon" type="image/png" href="/static/img/icon.png">
|
||||
<title>Remote i3-wm</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -17,10 +19,7 @@
|
|||
|
||||
<div id="response"></div>
|
||||
|
||||
<script src="/static/jquery.min.js"></script>
|
||||
<script src="/static/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/bootstrap.min.js"></script>
|
||||
<script src="/static/main.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
|
|
@ -62,19 +62,23 @@
|
|||
|
||||
{{if eq $value.Type "shortcuts"}}
|
||||
<div class="col-9" id="shortcuts_special_keys">
|
||||
<label class="btn btn-secondary" for="shortcuts_special_key_ctrl">
|
||||
<label class="btn btn-secondary mb-1" for="shortcuts_special_key_ctrl">
|
||||
<input type="checkbox" value="ctrl" id="shortcuts_special_key_ctrl">
|
||||
ctrl
|
||||
</label>
|
||||
<label class="btn btn-secondary" for="shortcuts_special_key_shift">
|
||||
<label class="btn btn-secondary mb-1" for="shortcuts_special_key_shift">
|
||||
<input type="checkbox" value="shift" id="shortcuts_special_key_shift">
|
||||
shift
|
||||
</label>
|
||||
<label class="btn btn-secondary" for="shortcuts_special_key_alt">
|
||||
<label class="btn btn-secondary mb-1" for="shortcuts_special_key_alt">
|
||||
<input type="checkbox" value="alt" id="shortcuts_special_key_alt">
|
||||
alt
|
||||
</label>
|
||||
<label class="btn btn-secondary" for="shortcuts_special_key_win">
|
||||
<label class="btn btn-secondary mb-1" for="shortcuts_special_key_tab">
|
||||
<input type="checkbox" value="tab" id="shortcuts_special_key_tab">
|
||||
tab
|
||||
</label>
|
||||
<label class="btn btn-secondary mb-1" for="shortcuts_special_key_win">
|
||||
<input type="checkbox" value="win" id="shortcuts_special_key_win">
|
||||
win
|
||||
</label>
|
||||
|
@ -112,8 +116,13 @@
|
|||
{{end}}
|
||||
|
||||
{{if eq $value.Type "mouse"}}
|
||||
<div class="form-group col-12">
|
||||
<input type="text" class="form-control live-text" placeholder="Live text" name="text">
|
||||
<div class="form-group col-12" id="mouse">
|
||||
<div id="mouse-screenshot-live">
|
||||
<label>
|
||||
<input type="checkbox"> Screen
|
||||
</label>
|
||||
</div>
|
||||
<input type="text" id="mouse-text-live" class="form-control live-text" placeholder="Live text">
|
||||
</div/>
|
||||
<div id="scrollbar"></div>
|
||||
<div id="pointer"></div>
|
||||
|
|
128
ws_controller.go
128
ws_controller.go
|
@ -5,10 +5,12 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/kbinani/screenshot"
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/net/websocket"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"math"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -37,6 +39,7 @@ type MessagesData struct {
|
|||
|
||||
type ScreenshotMessageData struct {
|
||||
Quality string `json:quality`
|
||||
Pointer bool `json:pointer`
|
||||
}
|
||||
|
||||
type MessageResponse struct {
|
||||
|
@ -44,7 +47,7 @@ type MessageResponse struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func getSimpleMessageValue(msg string) string {
|
||||
func getSimpleMessageValue(msg []byte) string {
|
||||
data := SimpleMessageData{}
|
||||
json.Unmarshal([]byte(msg), &data)
|
||||
|
||||
|
@ -53,15 +56,33 @@ func getSimpleMessageValue(msg string) string {
|
|||
|
||||
func sendMessageResponse(ws *websocket.Conn, r MessageResponse) {
|
||||
value, _ := json.Marshal(r)
|
||||
websocket.Message.Send(ws, string(value))
|
||||
ws.WriteMessage(websocket.TextMessage, value)
|
||||
}
|
||||
|
||||
func wsController(c echo.Context) error {
|
||||
var actions = Actions{
|
||||
Functions: make(map[string]func(ws *websocket.Conn, msg string) error),
|
||||
func getPointerPosition() (float64, float64) {
|
||||
location := exec.Command("xdotool", "getmouselocation")
|
||||
output, _ := location.Output()
|
||||
position := string(output)
|
||||
currentX := 0.0
|
||||
currentY := 0.0
|
||||
|
||||
for key, value := range strings.Split(position, " ") {
|
||||
if key == 0 {
|
||||
currentX, _ = strconv.ParseFloat(strings.Replace(value, "x:", "", 1), 32)
|
||||
} else if key == 1 {
|
||||
currentY, _ = strconv.ParseFloat(strings.Replace(value, "y:", "", 1), 32)
|
||||
}
|
||||
}
|
||||
|
||||
actions.add("pointer", func(ws *websocket.Conn, msg string) error {
|
||||
return currentX, currentY
|
||||
}
|
||||
|
||||
func createActions() Actions {
|
||||
actions := Actions{
|
||||
Functions: make(map[string]func(ws *websocket.Conn, msg []byte) error),
|
||||
}
|
||||
|
||||
actions.add("pointer", func(ws *websocket.Conn, msg []byte) error {
|
||||
data := PointerMessageData{}
|
||||
json.Unmarshal([]byte(msg), &data)
|
||||
|
||||
|
@ -83,19 +104,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
}
|
||||
|
||||
location := exec.Command("xdotool", "getmouselocation")
|
||||
output, _ := location.Output()
|
||||
position := string(output)
|
||||
currentX := 0.0
|
||||
currentY := 0.0
|
||||
|
||||
for key, value := range strings.Split(position, " ") {
|
||||
if key == 0 {
|
||||
currentX, _ = strconv.ParseFloat(strings.Replace(value, "x:", "", 1), 32)
|
||||
} else if key == 1 {
|
||||
currentY, _ = strconv.ParseFloat(strings.Replace(value, "y:", "", 1), 32)
|
||||
}
|
||||
}
|
||||
currentX, currentY := getPointerPosition()
|
||||
|
||||
newX, _ := strconv.ParseFloat(data.X, 32)
|
||||
newY, _ := strconv.ParseFloat(data.Y, 32)
|
||||
|
@ -108,7 +117,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
})
|
||||
|
||||
actions.add("scroll", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("scroll", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := getSimpleMessageValue(msg)
|
||||
key := ""
|
||||
|
||||
|
@ -128,7 +137,7 @@ func wsController(c echo.Context) error {
|
|||
return nil
|
||||
})
|
||||
|
||||
actions.add("workspace", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("workspace", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := getSimpleMessageValue(msg)
|
||||
|
||||
if value == "" {
|
||||
|
@ -140,7 +149,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
})
|
||||
|
||||
actions.add("volume", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("volume", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := getSimpleMessageValue(msg)
|
||||
|
||||
if value == "" {
|
||||
|
@ -177,7 +186,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
})
|
||||
|
||||
actions.add("media", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("media", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := getSimpleMessageValue(msg)
|
||||
|
||||
if value == "" {
|
||||
|
@ -237,7 +246,7 @@ func wsController(c echo.Context) error {
|
|||
return nil
|
||||
})
|
||||
|
||||
actions.add("keys", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("keys", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := strings.TrimSpace(getSimpleMessageValue(msg))
|
||||
|
||||
if value == "" {
|
||||
|
@ -253,6 +262,8 @@ func wsController(c echo.Context) error {
|
|||
key = "Control_L"
|
||||
} else if key == "alt" {
|
||||
key = "Alt_L"
|
||||
} else if key == "tab" {
|
||||
key = "Tab"
|
||||
}
|
||||
|
||||
if key != "" {
|
||||
|
@ -269,7 +280,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
})
|
||||
|
||||
actions.add("key", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("key", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := strings.TrimSpace(getSimpleMessageValue(msg))
|
||||
keys := make(map[string]string)
|
||||
|
||||
|
@ -294,7 +305,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
})
|
||||
|
||||
actions.add("text", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("text", func(ws *websocket.Conn, msg []byte) error {
|
||||
value := strings.TrimSpace(getSimpleMessageValue(msg))
|
||||
|
||||
if value == "" {
|
||||
|
@ -306,7 +317,7 @@ func wsController(c echo.Context) error {
|
|||
return cmd.Run()
|
||||
})
|
||||
|
||||
actions.add("screenshot", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("screenshot", func(ws *websocket.Conn, msg []byte) error {
|
||||
data := ScreenshotMessageData{}
|
||||
json.Unmarshal([]byte(msg), &data)
|
||||
|
||||
|
@ -325,6 +336,24 @@ func wsController(c echo.Context) error {
|
|||
quality = 90
|
||||
}
|
||||
|
||||
if data.Pointer {
|
||||
currentX, currentY := getPointerPosition()
|
||||
pointerSize := 2 * 16.0
|
||||
|
||||
pixelColor := color.RGBA{
|
||||
R: 255,
|
||||
G: 0,
|
||||
B: 0,
|
||||
A: 255,
|
||||
}
|
||||
|
||||
for x := math.Max(0.0, currentX-pointerSize/2); x <= currentX+3; x++ {
|
||||
for y := math.Max(0.0, currentY-pointerSize/2); y < currentY+3; y++ {
|
||||
img.SetRGBA(int(x), int(y), pixelColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
jpeg.Encode(buff, img, &jpeg.Options{Quality: quality})
|
||||
|
||||
|
@ -336,7 +365,7 @@ func wsController(c echo.Context) error {
|
|||
return nil
|
||||
})
|
||||
|
||||
actions.add("messages", func(ws *websocket.Conn, msg string) error {
|
||||
actions.add("messages", func(ws *websocket.Conn, msg []byte) error {
|
||||
data := MessagesData{}
|
||||
json.Unmarshal([]byte(msg), &data)
|
||||
|
||||
|
@ -344,7 +373,7 @@ func wsController(c echo.Context) error {
|
|||
msg, _ := json.Marshal(value)
|
||||
|
||||
if actions.has(value.Type) {
|
||||
actions.exec(value.Type, ws, string(msg))
|
||||
actions.exec(value.Type, ws, msg)
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
@ -352,20 +381,37 @@ func wsController(c echo.Context) error {
|
|||
return nil
|
||||
})
|
||||
|
||||
websocket.Handler(func(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
for {
|
||||
msg := ""
|
||||
websocket.Message.Receive(ws, &msg)
|
||||
return actions
|
||||
}
|
||||
|
||||
message := Message{}
|
||||
json.Unmarshal([]byte(msg), &message)
|
||||
var (
|
||||
upgrader = websocket.Upgrader{}
|
||||
)
|
||||
|
||||
if message.Type != "" && actions.has(message.Type) {
|
||||
actions.exec(message.Type, ws, msg)
|
||||
}
|
||||
func wsController(c echo.Context) error {
|
||||
ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
for {
|
||||
_, msg, err := ws.ReadMessage()
|
||||
|
||||
if err != nil {
|
||||
ws.Close()
|
||||
fmt.Printf("%+v\n", "Connection closed")
|
||||
return err
|
||||
}
|
||||
}).ServeHTTP(c.Response(), c.Request())
|
||||
|
||||
message := Message{}
|
||||
json.Unmarshal([]byte(msg), &message)
|
||||
|
||||
if message.Type != "" && actions.has(message.Type) {
|
||||
actions.exec(message.Type, ws, msg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue