first commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
config.go
|
||||
.env
|
||||
*.exe
|
||||
65
Readme.md
Normal file
65
Readme.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Clipboard on the GO 😉
|
||||
A simple clipboard synchronization tool built with Go. It consists of a server and a client application that work together to keep your clipboard data in sync across multiple devices.
|
||||
|
||||
## Features
|
||||
- Real-time clipboard synchronization
|
||||
- Cross-platform support (Windows, macOS, Linux)
|
||||
- Easy to set up and use
|
||||
|
||||
## Prerequisites
|
||||
- Go programming language installed (version 1.16 or higher)
|
||||
- Git installed
|
||||
|
||||
## Installation
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://gitea.reversed.dev/space/clipboard-on-the-go.git
|
||||
cd clipboard-on-the-go
|
||||
```
|
||||
2. Modify the configuration.
|
||||
2.1 Server Configuration:
|
||||
- Navigate to the `server` directory.
|
||||
- Make a new file called `config.go` and paste/modify the following code:
|
||||
```go
|
||||
package main
|
||||
|
||||
const (
|
||||
// Update these values before building
|
||||
EmbeddedServerIP = "0.0.0.0"
|
||||
EmbeddedServerPort = "8080"
|
||||
)
|
||||
|
||||
```
|
||||
2.2 Client Configuration:
|
||||
- Navigate to the `client` directory.
|
||||
- Make a new file called `config.go` and paste/modify the following code:
|
||||
```go
|
||||
package main
|
||||
|
||||
const (
|
||||
// Update these values before building
|
||||
EmbeddedIdentification = "my-unique-id"
|
||||
EmbeddedServerIP = "100.107.73.38"
|
||||
EmbeddedServerPort = "8080"
|
||||
)
|
||||
```
|
||||
3. Build the programs.
|
||||
- Run the `build.bat` script in the root directory to build both the server and client applications.
|
||||
|
||||
4. Run the server.
|
||||
- Navigate to the `server` directory and execute the `clipboard-sync-server.exe` file.
|
||||
5. Run the client.
|
||||
- Navigate to the `client` directory and execute the `clipboard-sync.exe` file.
|
||||
|
||||
## Usage
|
||||
This is intended to be hosted on a public ish server, preferably in a tailnet or via a VPN, as you never should expose anything to the internet.
|
||||
|
||||
I use this on my home server, which i can connect to with tailscale, so i don't have to worry about exposing it to the internet.
|
||||
|
||||
Clients are identified by the `EmbeddedIdentification` value in the client config file. Make sure to set this to a unique value for each client you want to sync.
|
||||
|
||||
## License
|
||||
This project is licensed under the MIT License.
|
||||
|
||||
## Disclaimer
|
||||
This software is provided "as is", without warranty of any kind. Use at your own risk.
|
||||
5
build-client.bat
Normal file
5
build-client.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
cd client
|
||||
go build -ldflags -H=windowsgui -o clipboard-sync.exe sync.go config.go icon.go
|
||||
echo Build complete: clipboard-sync.exe
|
||||
pause
|
||||
5
build-server.bat
Normal file
5
build-server.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
cd server
|
||||
go build -o clipboard-sync-server.exe server.go config.go
|
||||
echo Build complete: clipboard-sync-server.exe
|
||||
pause
|
||||
10
build.bat
Normal file
10
build.bat
Normal file
@@ -0,0 +1,10 @@
|
||||
@echo off
|
||||
call build-server.bat
|
||||
if errorlevel 1 exit /b 1
|
||||
cd ..
|
||||
|
||||
call build-client.bat
|
||||
if errorlevel 1 exit /b 1
|
||||
cd ..
|
||||
|
||||
echo Build completed successfully.
|
||||
26
client/embedicon.go
Normal file
26
client/embedicon.go
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data, err := os.ReadFile("../syncup.ico")
|
||||
if err != nil {
|
||||
fmt.Println("Error reading icon:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("var EmbeddedIcon = []byte{")
|
||||
for i, b := range data {
|
||||
if i%12 == 0 {
|
||||
fmt.Print("\n\t")
|
||||
}
|
||||
fmt.Printf("0x%02X, ", b)
|
||||
}
|
||||
fmt.Println("\n}")
|
||||
}
|
||||
25
client/go.mod
Normal file
25
client/go.mod
Normal file
@@ -0,0 +1,25 @@
|
||||
module clipboard-on-the-go
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.5.1
|
||||
golang.design/x/clipboard v0.7.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||
github.com/getlantern/systray v1.2.2 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
|
||||
golang.org/x/image v0.28.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
)
|
||||
42
client/go.sum
Normal file
42
client/go.sum
Normal file
@@ -0,0 +1,42 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE=
|
||||
github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
|
||||
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
|
||||
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
|
||||
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
|
||||
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
10
client/icon.go
Normal file
10
client/icon.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
// EmbeddedIcon contains the icon data
|
||||
// To generate this, run: go run embedicon.go
|
||||
var EmbeddedIcon = []byte{
|
||||
// Replace this with your actual icon bytes
|
||||
// You can use a tool or script to convert syncup.ico to byte array
|
||||
// For now, using minimal PNG as placeholder
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||
}
|
||||
144
client/sync.go
Normal file
144
client/sync.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/gorilla/websocket"
|
||||
"golang.design/x/clipboard"
|
||||
)
|
||||
|
||||
var (
|
||||
conn *websocket.Conn
|
||||
lastClipboard []byte
|
||||
identification string
|
||||
isConnected bool
|
||||
)
|
||||
|
||||
func onReady() {
|
||||
// Use embedded icon
|
||||
systray.SetIcon(EmbeddedIcon)
|
||||
|
||||
systray.SetTitle("Clipboard Sync")
|
||||
systray.SetTooltip("Clipboard synchronization active")
|
||||
|
||||
mStatus := systray.AddMenuItem("Status: Connecting...", "Connection status")
|
||||
mStatus.Disable()
|
||||
systray.AddSeparator()
|
||||
mQuit := systray.AddMenuItem("Quit", "Quit the application")
|
||||
|
||||
// Start clipboard monitoring
|
||||
go monitorClipboard()
|
||||
|
||||
// Start client connection
|
||||
go startClient(mStatus)
|
||||
|
||||
// Handle menu interactions
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func onExit() {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
log.Println("Exiting clipboard sync client")
|
||||
}
|
||||
|
||||
func startClient(statusItem *systray.MenuItem) {
|
||||
// Use embedded configuration
|
||||
identification = EmbeddedIdentification
|
||||
server_ip := EmbeddedServerIP
|
||||
server_port := EmbeddedServerPort
|
||||
server_connection := "ws://" + server_ip + ":" + server_port + "/ws"
|
||||
|
||||
if identification == "" || server_ip == "" || server_port == "" {
|
||||
log.Println("Missing configuration")
|
||||
statusItem.SetTitle("Status: Config error")
|
||||
return
|
||||
}
|
||||
|
||||
// Connect to the server
|
||||
var connectErr error
|
||||
conn, _, connectErr = websocket.DefaultDialer.Dial(server_connection, nil)
|
||||
if connectErr != nil {
|
||||
log.Println("Error connecting to server:", connectErr)
|
||||
statusItem.SetTitle("Status: Connection failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Send identification to the server
|
||||
sendMessage(conn, websocket.TextMessage, []byte(identification))
|
||||
|
||||
isConnected = true
|
||||
statusItem.SetTitle(fmt.Sprintf("Status: Connected (%s)", identification))
|
||||
log.Println("Connected to server")
|
||||
|
||||
// Listen for messages from the server
|
||||
for {
|
||||
messageType, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading message:", err)
|
||||
isConnected = false
|
||||
statusItem.SetTitle("Status: Disconnected")
|
||||
return
|
||||
}
|
||||
handleMessage(messageType, message)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(messageType int, message []byte) {
|
||||
log.Printf("Received clipboard update: %d bytes", len(message))
|
||||
// Update local clipboard tracking to avoid re-sending
|
||||
lastClipboard = make([]byte, len(message))
|
||||
copy(lastClipboard, message)
|
||||
// Write to clipboard
|
||||
clipboard.Write(clipboard.FmtText, message)
|
||||
log.Println("Clipboard updated")
|
||||
}
|
||||
|
||||
func sendMessage(conn *websocket.Conn, messageType int, message []byte) {
|
||||
err := conn.WriteMessage(messageType, message)
|
||||
if err != nil {
|
||||
log.Println("Error sending message:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func monitorClipboard() {
|
||||
for {
|
||||
if isConnected && conn != nil {
|
||||
data := clipboard.Read(clipboard.FmtText)
|
||||
|
||||
// Check if clipboard has changed
|
||||
if len(data) > 0 && !bytes.Equal(data, lastClipboard) {
|
||||
log.Printf("Clipboard changed, sending to server: %d bytes", len(data))
|
||||
sendMessage(conn, websocket.TextMessage, data)
|
||||
lastClipboard = make([]byte, len(data))
|
||||
copy(lastClipboard, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultIcon() []byte {
|
||||
return EmbeddedIcon
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize clipboard
|
||||
clip_err := clipboard.Init()
|
||||
if clip_err != nil {
|
||||
log.Fatal("Failed to initialize clipboard:", clip_err)
|
||||
}
|
||||
|
||||
// Run systray application
|
||||
systray.Run(onReady, onExit)
|
||||
}
|
||||
7
server/go.mod
Normal file
7
server/go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module server
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3
|
||||
|
||||
require github.com/joho/godotenv v1.5.1 // indirect
|
||||
4
server/go.sum
Normal file
4
server/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
165
server/server.go
Normal file
165
server/server.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
conn *websocket.Conn
|
||||
identification string
|
||||
send chan []byte
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
clients map[*Client]bool
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
broadcast chan *Message
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
identification string
|
||||
data []byte
|
||||
sender *Client
|
||||
}
|
||||
|
||||
func newServer() *Server {
|
||||
return &Server{
|
||||
clients: make(map[*Client]bool),
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
broadcast: make(chan *Message),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) run() {
|
||||
for {
|
||||
select {
|
||||
case client := <-s.register:
|
||||
s.mu.Lock()
|
||||
s.clients[client] = true
|
||||
s.mu.Unlock()
|
||||
log.Printf("Client registered with identification: %s", client.identification)
|
||||
|
||||
case client := <-s.unregister:
|
||||
s.mu.Lock()
|
||||
if _, ok := s.clients[client]; ok {
|
||||
delete(s.clients, client)
|
||||
close(client.send)
|
||||
log.Printf("Client unregistered: %s", client.identification)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
case message := <-s.broadcast:
|
||||
s.mu.RLock()
|
||||
for client := range s.clients {
|
||||
if client.identification == message.identification && client != message.sender {
|
||||
select {
|
||||
case client.send <- message.data:
|
||||
default:
|
||||
close(client.send)
|
||||
delete(s.clients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) readPump(server *Server) {
|
||||
defer func() {
|
||||
server.unregister <- c
|
||||
c.conn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
_, message, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
log.Printf("error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
log.Printf("Received clipboard data from %s: %d bytes", c.identification, len(message))
|
||||
server.broadcast <- &Message{
|
||||
identification: c.identification,
|
||||
data: message,
|
||||
sender: c,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) writePump() {
|
||||
defer c.conn.Close()
|
||||
|
||||
for message := range c.send {
|
||||
sendMessage(c.conn, websocket.TextMessage, message)
|
||||
log.Printf("Sent clipboard data to %s", c.identification)
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(conn *websocket.Conn, messageType int, message []byte) {
|
||||
err := conn.WriteMessage(messageType, message)
|
||||
if err != nil {
|
||||
log.Println("Error sending message:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleWebSocket(server *Server, w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("Error upgrading connection:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// First message should be identification
|
||||
_, identification, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading identification:", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
identification: string(identification),
|
||||
send: make(chan []byte, 256),
|
||||
}
|
||||
|
||||
server.register <- client
|
||||
|
||||
go client.writePump()
|
||||
go client.readPump(server)
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := EmbeddedServerPort
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
|
||||
server := newServer()
|
||||
go server.run()
|
||||
|
||||
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleWebSocket(server, w, r)
|
||||
})
|
||||
|
||||
log.Printf("Server starting on port %s", port)
|
||||
err := http.ListenAndServe(":"+port, nil)
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe error:", err)
|
||||
}
|
||||
}
|
||||
BIN
syncup.ico
Normal file
BIN
syncup.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Reference in New Issue
Block a user