Files
clipboard-on-the-go/client/sync.go

189 lines
4.3 KiB
Go

package main
import (
"bytes"
"fmt"
"log"
"time"
"github.com/getlantern/systray"
"github.com/gorilla/websocket"
"golang.design/x/clipboard"
)
var (
conn *websocket.Conn
lastClipboard []byte
identification string
isConnected bool
serverURL string
)
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
serverURL = "ws://" + server_ip + ":" + server_port + "/ws"
if identification == "" || server_ip == "" || server_port == "" {
log.Println("Missing configuration")
statusItem.SetTitle("Status: Config error")
return
}
// Reconnection loop with exponential backoff
backoff := 1 * time.Second
maxBackoff := 60 * time.Second
for {
if connectToServer(statusItem) {
// Successfully connected, reset backoff
backoff = 1 * time.Second
// Listen for messages until connection drops
listenForMessages(statusItem)
}
// Connection failed or dropped, wait before retry
isConnected = false
statusItem.SetTitle(fmt.Sprintf("Status: Reconnecting in %ds...", int(backoff.Seconds())))
log.Printf("Reconnecting in %s...", backoff)
time.Sleep(backoff)
// Increase backoff exponentially
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}
}
func connectToServer(statusItem *systray.MenuItem) bool {
statusItem.SetTitle("Status: Connecting...")
var err error
conn, _, err = websocket.DefaultDialer.Dial(serverURL, nil)
if err != nil {
log.Println("Error connecting to server:", err)
statusItem.SetTitle("Status: Connection failed")
return false
}
// Send identification to the server
err = conn.WriteMessage(websocket.TextMessage, []byte(identification))
if err != nil {
log.Println("Error sending identification:", err)
conn.Close()
conn = nil
return false
}
isConnected = true
statusItem.SetTitle(fmt.Sprintf("Status: Connected (%s)", identification))
log.Println("Connected to server")
return true
}
func listenForMessages(statusItem *systray.MenuItem) {
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("Error reading message:", err)
isConnected = false
statusItem.SetTitle("Status: Disconnected")
if conn != nil {
conn.Close()
conn = nil
}
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)
isConnected = false
}
}
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)
}