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