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