This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"errors" | |
"fmt" | |
"io" | |
"log" | |
"net" | |
"os" | |
"os/exec" | |
"runtime" | |
"strconv" | |
"strings" | |
"time" | |
) | |
var ( | |
secondsBetweenChecksForClipChange = 1 | |
helpMsg = `myclip - Shared Clipboard | |
With myclip, you can copy from one device and paste on another. | |
Usage: myclip [--debug/-d] [ <address> | --help/-h ] | |
Examples: | |
myclip # start a new clipboard | |
myclip 192.168.86.24:53701 # join the clipboard at 192.168.86.24:53701 | |
myclip -d # start a new clipboard with debug output | |
myclip -d 192.168.86.24:53701 # join the clipboard with debug output | |
Running just ` + "`myclip`" + ` will start a new clipboard. | |
It will also provide an address with which you can connect to the same clipboard with another device.` | |
listOfClients = make([]*bufio.Writer, 0) | |
localClipboard string | |
printDebugInfo = false | |
version = "v1.0.0" | |
) | |
func main() { | |
if len(os.Args) > 3 { | |
handleError(errors.New("too many arguments")) | |
fmt.Println(helpMsg) | |
return | |
} | |
if hasOption, _ := argsHaveOption("help", "h"); hasOption { | |
fmt.Println(helpMsg) | |
return | |
} | |
if hasOption, i := argsHaveOption("debug", "d"); hasOption { | |
printDebugInfo = true | |
os.Args = removeElemFromSlice(os.Args, i) // delete the debug option and run again | |
main() | |
return | |
} | |
if hasOption, _ := argsHaveOption("version", "v"); hasOption { | |
fmt.Println(version) | |
return | |
} | |
if len(os.Args) == 2 { // has exactly one argument | |
connectToServer(os.Args[1]) | |
return | |
} | |
makeServer() // if there's no arguments we should start a new clipboard | |
} | |
func handleError(err error) { | |
if err == io.EOF { | |
fmt.Println("Disconnected") | |
} else { | |
fmt.Fprintln(os.Stderr, "error: ["+err.Error()+"]") | |
} | |
return | |
} | |
func argsHaveOption(long string, short string) (hasOption bool, foundAt int) { | |
for i, arg := range os.Args { | |
if arg == "--"+long || arg == "-"+short { | |
return true, i | |
} | |
} | |
return false, 0 | |
} | |
func removeElemFromSlice(slice []string, i int) []string { | |
return append(slice[:i], slice[i+1:]...) | |
} | |
func getOutboundIP() net.IP { | |
// https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go/37382208#37382208 | |
conn, err := net.Dial("udp", "8.8.8.8:80") // address can be anything. Doesn't even have to exist | |
if err != nil { | |
handleError(err) | |
return nil | |
} | |
defer conn.Close() | |
localAddr := conn.LocalAddr().(*net.UDPAddr) | |
return localAddr.IP | |
} | |
func makeServer() { | |
fmt.Println("Starting a new clipboard") | |
l, err := net.Listen("tcp4", "0.0.0.0:") | |
if err != nil { | |
handleError(err) | |
return | |
} | |
defer l.Close() | |
port := strconv.Itoa(l.Addr().(*net.TCPAddr).Port) | |
fmt.Println("Run", "`myclip", getOutboundIP().String()+":"+port+"`", "to join this clipboard") | |
fmt.Println() | |
for { | |
c, err := l.Accept() | |
if err != nil { | |
handleError(err) | |
return | |
} | |
fmt.Println("Connected to a device") | |
go handleClient(c) | |
} | |
} | |
func debug(a ...interface{}) { | |
if printDebugInfo { | |
fmt.Println("verbose:", a) | |
} | |
} | |
func handleClient(c net.Conn) { | |
w := bufio.NewWriter(c) | |
listOfClients = append(listOfClients, w) | |
defer c.Close() | |
go monitorSentClips(bufio.NewReader(c)) | |
monitorLocalClip(w) | |
} | |
func connectToServer(address string) { | |
c, err := net.Dial("tcp4", address) | |
if err != nil { | |
handleError(err) | |
return | |
} | |
defer c.Close() | |
fmt.Println("Connected to the clipboard") | |
go monitorSentClips(bufio.NewReader(c)) | |
monitorLocalClip(bufio.NewWriter(c)) | |
} | |
func monitorLocalClip(w *bufio.Writer) { | |
for { | |
localClipboard = getLocalClip() | |
debug("clipboard changed so sending it. localClipboard =", localClipboard) | |
err := sendClipboard(w, localClipboard) | |
if err != nil { | |
handleError(err) | |
return | |
} | |
for localClipboard == getLocalClip() { | |
time.Sleep(time.Second * time.Duration(secondsBetweenChecksForClipChange)) | |
} | |
} | |
} | |
func monitorSentClips(r *bufio.Reader) { | |
var foreignClipboard string | |
for { | |
s, err := r.ReadString('\n') | |
if err != nil { | |
handleError(err) | |
return | |
} | |
if s == "STARTCLIPBOARD\n" { | |
for { | |
s, err = r.ReadString('\n') | |
if err != nil { | |
handleError(err) | |
return | |
} | |
if s == "ENDCLIPBOARD\n" { | |
foreignClipboard = strings.TrimSuffix(foreignClipboard, "\n") | |
break | |
} | |
foreignClipboard += s | |
} | |
setLocalClip(foreignClipboard) | |
localClipboard = foreignClipboard // the local clipboard monitoring thread should still get that localClipboard is the same as the local clipboard. | |
debug("rcvd:", foreignClipboard) | |
for i, w := range listOfClients { | |
if w != nil { | |
debug("Sending received clipboard to", w) | |
err := sendClipboard(w, foreignClipboard) | |
if err != nil { | |
listOfClients[i] = nil | |
fmt.Println("error when trying to send the clipboard to a device. Will not contact that device again.") | |
//handleError(err) | |
} | |
} | |
} | |
foreignClipboard = "" | |
} | |
} | |
} | |
func sendClipboard(w *bufio.Writer, clipboard string) error { | |
var err error | |
clipString := "STARTCLIPBOARD\n" + clipboard + "\nENDCLIPBOARD\n" | |
// If the file doesn't exist, create it, or append to the file | |
f, err := os.OpenFile("clipboard.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
if err != nil { | |
log.Fatal(err) | |
} | |
_, err = f.Write([]byte("\n" + time.Now().Format(time.RFC850) + "\n" + clipboard + "\n \n")) | |
if err != nil { | |
log.Fatal(err) | |
} | |
f.Close() | |
if clipboard == "" { | |
debug("was going to send empty string but skipping") | |
return nil | |
} | |
debug("sent:", clipboard) | |
_, err = w.WriteString(clipString) | |
if err != nil { | |
return err | |
} | |
err = w.Flush() | |
return err | |
} | |
func getLocalClip() string { | |
var out []byte | |
var err error | |
var cmd *exec.Cmd | |
if runtime.GOOS == "darwin" { // darwin means it's macOS | |
cmd = exec.Command("pbpaste") | |
} else if runtime.GOOS == "windows" { | |
cmd = exec.Command("powershell.exe", "-command", "Get-Clipboard") | |
} else { | |
// Unix - check what's available | |
if _, err := exec.LookPath("xclip"); err == nil { | |
cmd = exec.Command("xclip", "-out", "-selection", "clipboard") | |
} else if _, err := exec.LookPath("xsel"); err == nil { | |
cmd = exec.Command("xsel", "--output", "--clipboard") | |
} else if _, err := exec.LookPath("wl-paste"); err == nil { | |
cmd = exec.Command("wl-paste", "--no-newline") | |
} else if _, err := exec.LookPath("termux-clipboard-get"); err == nil { | |
cmd = exec.Command("termux-clipboard-get") | |
} else { | |
handleError(errors.New("sorry, myclip won't work if you don't have xsel, xclip, wayland or Termux installed :(")) | |
os.Exit(2) | |
} | |
} | |
if out, err = cmd.Output(); err != nil { | |
handleError(err) | |
return "An error occurred wile getting the local clipboard" | |
} | |
if runtime.GOOS == "windows" { | |
return strings.TrimSuffix(string(out), "\r\n") // powershell's get-clipboard adds a windows newline to the end for some reason | |
} | |
return string(out) | |
} | |
func setLocalClip(s string) { | |
var copyCmd *exec.Cmd | |
if runtime.GOOS == "darwin" { | |
copyCmd = exec.Command("pbcopy") | |
} else if runtime.GOOS == "windows" { | |
copyCmd = exec.Command("powershell.exe", "-command", "Set-Clipboard -Value "+"\""+s+"\"") | |
} else { | |
if _, err := exec.LookPath("xclip"); err == nil { | |
copyCmd = exec.Command("xclip", "-in", "-selection", "clipboard") | |
} else if _, err := exec.LookPath("xsel"); err == nil { | |
copyCmd = exec.Command("xsel", "--input", "--clipboard") | |
} else if _, err := exec.LookPath("wl-copy"); err == nil { | |
copyCmd = exec.Command("wl-copy") | |
} else if _, err := exec.LookPath("termux-clipboard-set"); err == nil { | |
copyCmd = exec.Command("termux-clipboard-set") | |
} else { | |
handleError(errors.New("sorry, uniclip won't work if you don't have xsel, xclip, wayland or Termux:API installed :(")) | |
os.Exit(2) | |
} | |
} | |
var in io.WriteCloser | |
var err error | |
in, err = copyCmd.StdinPipe() | |
if err != nil { | |
handleError(err) | |
return | |
} | |
if err = copyCmd.Start(); err != nil { | |
handleError(err) | |
return | |
} | |
if _, err = in.Write([]byte(s)); err != nil { | |
handleError(err) | |
return | |
} | |
if err = in.Close(); err != nil { | |
handleError(err) | |
return | |
} | |
if err := copyCmd.Wait(); err != nil { | |
handleError(err) | |
return | |
} | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment