201 lines
5.4 KiB
Go
201 lines
5.4 KiB
Go
package cursor
|
|
|
|
import (
|
|
"arimelody-web/model"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
type CursorClient struct {
|
|
ID int32
|
|
Conn *websocket.Conn
|
|
Route string
|
|
X float32
|
|
Y float32
|
|
Click bool
|
|
Disconnected bool
|
|
}
|
|
|
|
type CursorMessage struct {
|
|
Data []byte
|
|
Route string
|
|
Exclude []*CursorClient
|
|
}
|
|
|
|
func (client *CursorClient) Send(data []byte) {
|
|
err := client.Conn.WriteMessage(websocket.TextMessage, data)
|
|
if err != nil {
|
|
client.Disconnect()
|
|
}
|
|
}
|
|
|
|
func (client *CursorClient) Disconnect() {
|
|
client.Disconnected = true
|
|
broadcast <- CursorMessage{
|
|
[]byte(fmt.Sprintf("leave:%d", client.ID)),
|
|
client.Route,
|
|
[]*CursorClient{},
|
|
}
|
|
}
|
|
|
|
var clients = make(map[int32]*CursorClient)
|
|
var broadcast = make(chan CursorMessage)
|
|
var mutex = &sync.Mutex{}
|
|
|
|
func StartCursor(app *model.AppState) {
|
|
var includes = func (clients []*CursorClient, client *CursorClient) bool {
|
|
for _, c := range clients {
|
|
if c.ID == client.ID { return true }
|
|
}
|
|
return false
|
|
}
|
|
|
|
log("Cursor message handler ready!")
|
|
|
|
for {
|
|
message := <-broadcast
|
|
mutex.Lock()
|
|
for _, client := range clients {
|
|
if client.Route != message.Route { continue }
|
|
if includes(message.Exclude, client) { continue }
|
|
client.Send(message.Data)
|
|
}
|
|
mutex.Unlock()
|
|
}
|
|
}
|
|
|
|
func handleClient(client *CursorClient) {
|
|
msgType, message, err := client.Conn.ReadMessage()
|
|
if err != nil {
|
|
client.Disconnect()
|
|
return
|
|
}
|
|
if msgType != websocket.TextMessage { return }
|
|
|
|
args := strings.Split(string(message), ":")
|
|
if len(args) == 0 { return }
|
|
switch args[0] {
|
|
case "loc":
|
|
if len(args) < 2 { return }
|
|
|
|
client.Route = args[1]
|
|
|
|
mutex.Lock()
|
|
for otherClientID, otherClient := range clients {
|
|
if otherClientID == client.ID || otherClient.Route != client.Route { continue }
|
|
client.Send([]byte(fmt.Sprintf("join:%d", otherClientID)))
|
|
client.Send([]byte(fmt.Sprintf("pos:%d:%f:%f", otherClientID, otherClient.X, otherClient.Y)))
|
|
}
|
|
mutex.Unlock()
|
|
|
|
broadcast <- CursorMessage{
|
|
[]byte(fmt.Sprintf("join:%d", client.ID)),
|
|
client.Route,
|
|
[]*CursorClient{ client },
|
|
}
|
|
case "char":
|
|
if len(args) < 2 { return }
|
|
// haha, turns out using ':' as a separator means you can't type ':'s
|
|
// i should really be writing byte packets, not this nonsense
|
|
msg := byte(':')
|
|
if len(args[1]) > 0 {
|
|
msg = args[1][0]
|
|
}
|
|
broadcast <- CursorMessage{
|
|
[]byte(fmt.Sprintf("char:%d:%c", client.ID, msg)),
|
|
client.Route,
|
|
[]*CursorClient{ client },
|
|
}
|
|
case "nochar":
|
|
broadcast <- CursorMessage{
|
|
[]byte(fmt.Sprintf("nochar:%d", client.ID)),
|
|
client.Route,
|
|
[]*CursorClient{ client },
|
|
}
|
|
case "click":
|
|
if len(args) < 2 { return }
|
|
click := 0
|
|
if args[1][0] == '1' {
|
|
click = 1
|
|
}
|
|
broadcast <- CursorMessage{
|
|
[]byte(fmt.Sprintf("click:%d:%d", client.ID, click)),
|
|
client.Route,
|
|
[]*CursorClient{ client },
|
|
}
|
|
case "pos":
|
|
if len(args) < 3 { return }
|
|
x, err := strconv.ParseFloat(args[1], 32)
|
|
y, err := strconv.ParseFloat(args[2], 32)
|
|
if err != nil { return }
|
|
client.X = float32(x)
|
|
client.Y = float32(y)
|
|
broadcast <- CursorMessage{
|
|
[]byte(fmt.Sprintf("pos:%d:%f:%f", client.ID, client.X, client.Y)),
|
|
client.Route,
|
|
[]*CursorClient{ client },
|
|
}
|
|
}
|
|
}
|
|
|
|
func Handler(app *model.AppState) http.HandlerFunc {
|
|
var upgrader = websocket.Upgrader{
|
|
CheckOrigin: func (r *http.Request) bool {
|
|
origin := r.Header.Get("Origin")
|
|
return origin == app.Config.BaseUrl
|
|
},
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log("Failed to upgrade to WebSocket connection: %v\n", err)
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
client := CursorClient{
|
|
ID: rand.Int31(),
|
|
Conn: conn,
|
|
X: 0.0,
|
|
Y: 0.0,
|
|
Disconnected: false,
|
|
}
|
|
|
|
err = client.Conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("id:%d", client.ID)))
|
|
if err != nil {
|
|
client.Conn.Close()
|
|
return
|
|
}
|
|
|
|
mutex.Lock()
|
|
clients[client.ID] = &client
|
|
mutex.Unlock()
|
|
|
|
// log("Client connected: %s (%s)", fmt.Sprintf("0x%08x", client.ID), client.Conn.RemoteAddr().String())
|
|
|
|
for {
|
|
if client.Disconnected {
|
|
mutex.Lock()
|
|
delete(clients, client.ID)
|
|
client.Conn.Close()
|
|
mutex.Unlock()
|
|
return
|
|
}
|
|
handleClient(&client)
|
|
}
|
|
})
|
|
}
|
|
|
|
func log(format string, args ...any) {
|
|
logString := fmt.Sprintf(format, args...)
|
|
fmt.Printf("[%s] [CURSOR] %s\n", time.Now().Format(time.UnixDate), logString)
|
|
}
|