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) }