From 4e637bc27d948b2d1c685d54a0183149b1b1669f Mon Sep 17 00:00:00 2001 From: Space-Banane <64922620+Space-Banane@users.noreply.github.com> Date: Sat, 17 Jan 2026 15:19:07 +0100 Subject: [PATCH] first commit --- .gitignore | 3 + Readme.md | 65 +++++++++++++++++ build-client.bat | 5 ++ build-server.bat | 5 ++ build.bat | 10 +++ client/embedicon.go | 26 +++++++ client/go.mod | 25 +++++++ client/go.sum | 42 +++++++++++ client/icon.go | 10 +++ client/sync.go | 144 ++++++++++++++++++++++++++++++++++++++ icon.png | Bin 0 -> 1905 bytes server/go.mod | 7 ++ server/go.sum | 4 ++ server/server.go | 165 ++++++++++++++++++++++++++++++++++++++++++++ syncup.ico | Bin 0 -> 4286 bytes 15 files changed, 511 insertions(+) create mode 100644 .gitignore create mode 100644 Readme.md create mode 100644 build-client.bat create mode 100644 build-server.bat create mode 100644 build.bat create mode 100644 client/embedicon.go create mode 100644 client/go.mod create mode 100644 client/go.sum create mode 100644 client/icon.go create mode 100644 client/sync.go create mode 100644 icon.png create mode 100644 server/go.mod create mode 100644 server/go.sum create mode 100644 server/server.go create mode 100644 syncup.ico diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fa83be --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.go +.env +*.exe \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..3f9614d --- /dev/null +++ b/Readme.md @@ -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. \ No newline at end of file diff --git a/build-client.bat b/build-client.bat new file mode 100644 index 0000000..7684b2e --- /dev/null +++ b/build-client.bat @@ -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 \ No newline at end of file diff --git a/build-server.bat b/build-server.bat new file mode 100644 index 0000000..5549f18 --- /dev/null +++ b/build-server.bat @@ -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 \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..0c97b89 --- /dev/null +++ b/build.bat @@ -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. \ No newline at end of file diff --git a/client/embedicon.go b/client/embedicon.go new file mode 100644 index 0000000..c7f060c --- /dev/null +++ b/client/embedicon.go @@ -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}") +} diff --git a/client/go.mod b/client/go.mod new file mode 100644 index 0000000..f89e46c --- /dev/null +++ b/client/go.mod @@ -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 +) diff --git a/client/go.sum b/client/go.sum new file mode 100644 index 0000000..e186dfa --- /dev/null +++ b/client/go.sum @@ -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= diff --git a/client/icon.go b/client/icon.go new file mode 100644 index 0000000..b4eaf74 --- /dev/null +++ b/client/icon.go @@ -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, +} diff --git a/client/sync.go b/client/sync.go new file mode 100644 index 0000000..3417ec9 --- /dev/null +++ b/client/sync.go @@ -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) +} diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0ee40c286032a4446b283c43d64ec1c76fd6d2 GIT binary patch literal 1905 zcmb_dYgiI!8pRu#hKib^v^s;AYPtY{*J;%dQOodBv!#|nG=`T=4c)acKu1l?OJke2 z#@pKJRG^uennIX{GbZ9KSuMO_kdrquMG|qa`7u9s|Ln8#<9*+Ap7TBD$N9eRd6_|Y zUyy;d0RR91`TJo*G?TGa`an(Z^dP>}3@u8CF9uN8w`*2IbT1w`egpt$$Tw6G^)!5Y zyk95<05Iy@Dy{CgA1?p^+kpPqBPWw1=1Vx=L)}a=O43w~c+0fQH!0lnK?XfuRzDTm zg=fNowD0gf9ZE#beIvge96qv_uv12OApN^xYwvxH`N$|scB(7Z2oqtHGX>TWlxd1EY=emuCU9`&;Rx;^aKgrQ=TjD;)l+0RzT}> zL$!gHa@yL`)OlfZ+(ca+${Y+NF_R@shggZKxWbd(_@fv1 z=D5XhN7cr5P~I;mjq_&Ow!wDXEggJbFvpqyq`fxXvp2~{A^cES)wGrbnm8#cf5Zj_ zE#F_1gg~O{UXrqVd%096!7#Ji#71AnOlYPmo z;5g9w>vp5BL4>vXkjSI;T45By7dPe|L`xwo56g+U>wg7sTa=Cur^ljJrRxu#P6q}o ze}o!3h7D}Iyqn+cp~!r-Pc%JKeepf0BfQo*l|3t260H-VjAw)F4%oVq-Q9_jA=yAg z*zut5!FLw$RxK5ZyN%919IFF)JM5f2iKIk%$PRK%%DNNdvT$Z=4eX+a+2W(&g8Kc} z-bXo|5I;-O!F#8t7PqQH&lnO<%@j1OlCsVUVlqZQc*gHUjdTB&z!E4tVWZ{jnm5@m zW++dbsK_xkv0`3L5}N+!*IKQl8CI4*E(wK$aYJkP{iK3TLRsYS(ewdCRVb6=e7Yhu z$&`o=AV*%V*BkjZG8YR0nZb71f=0fJSCO|!bcb6o;ZI@leV^Gc9ans>D;1ON16c@b z$H%%do1IdQt}G9?W$5I(Nk&XKxzf?F!}NarNQ<1edl14BM4_yBPk{bM;UfKpBceuR zw5+qKk{6LXi2)7=4WMZpix@`Gab7#4#I{E3zLHqVP%@&`|8OM4I(TSgF3_1*b%${U zP)ZkpyoPX{8@l}DMFh=D;@i3L)XBWc>@euH&ca?qA2lOEr?hF?o5pZYu>1CY7H5*H zP2#vf;FZDDj%#gAW>vRzbH~gU4j{&1PmT3ANP0`WDEwwz?#hrW*dBi4O zcxgj()T{CLR5~?KlM2y$N#rFlj-q8~f*WzVWS&#se@`r? z8}~tT^{a`P1FpLbSl)(xR-akAq`Kp(NuKRzg1SXL@bDli#{N0ExS!P(0c8nU?o7=) zLfhRF8N5eV{{=SJ!Mc$5y!R^e58oC4lOaY9PjDe4-MuSTG;0#z?}NwIVF+oz0xFVs At^fc4 literal 0 HcmV?d00001 diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..178269a --- /dev/null +++ b/server/go.mod @@ -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 diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..09f4ebf --- /dev/null +++ b/server/go.sum @@ -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= diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..ff599e3 --- /dev/null +++ b/server/server.go @@ -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) + } +} diff --git a/syncup.ico b/syncup.ico new file mode 100644 index 0000000000000000000000000000000000000000..bc3e0484157e9f4e673bfcbd3468ca8070811781 GIT binary patch literal 4286 zcmeHLe@xV67=Q0K$Nd1|F7EU?j=KXnprcxDDlQEu90L8A0;|j-MI97PLP@8tK?4i< zsZIiggTv^bin7((ax=C}w_L2OmQ6E}A8NJM`uo%Ke2*`kmK<4I|J>a^ym#;OJfF|= zJn!>;Z$k(LyM4`OfT&X!>-Y&N_H%IA$7b*qjImpgmo32Bl75zer*r{I z-uIyRUH2?lzxcQZOLp0y&?-?`@b~i*K2z)?gaP)%A*4EfmGr%iUuX>>)$tq5h8=&^ zFH1nl2VO~!H6^^~#_Bg+NG>$P6f1CG^W4uQ*UvsCy^1t8!oBcENtg4Gk>>avafbRE z^z-}_ldgxSYysTV2ZoQ%KKFGeoHN?ZPYFx}BG=D8%^UvA!DTy(#JC2^XS-Cl+3*sb zP0p-7`|mCjSeYsC03nyKEKMLbRspkD2eVND)}AE4TRp$w{bwKc4dtMPR&y`SoDR$B zIpR8V2`lNmSyQfu*UR)AFs}3qeAXhcFhRhe3-QcXTh1dq2SZGVi#eHF){(X47${J-lC)iIZ*Y~z@>8(nyIO(M2G;lSxGD}sBx zINff>r>!P@-0H@~KmuB8d!}VaHb3gXD_c`>vfYV~UUN!! zuFM~4oerun(v*%SieLLs8rrJtI9TUL-TG7v*Xz+%lZt)SK9uBVqNHFcj?}v`yf+2s z`z*LNq$FJ*4%KC#VM``zpY);nF$>Og7^OZ$(q=o~pC2}wPqbD>gjh z$5^*@UgysQ0QJ9nSX}Fy61m zm4FK0cI$AdPbbxJ@rW7|0~-1sgR>oKs$V6|-*`Zei9xM|OZ~Cj>AKWr~3IG5A literal 0 HcmV?d00001