Allow clearing the clipboard instantly; improve code sharing between platforms; facilitate GUI/TUI client development regarding the clipboard

This commit is contained in:
2024-12-21 15:31:14 -05:00
parent 9ed7b8ad9b
commit 3b57d59ddc
10 changed files with 117 additions and 128 deletions
+36 -3
View File
@@ -59,18 +59,51 @@ func CopyArgument(targetLocation string, field int) {
} }
} }
// ClipClearArgument is called to clear the clipboard after 30 seconds if the contents have not been modified. // ClipClearArgument reads the assigned clipboard contents from stdin and passes them to clipClearProcess.
func ClipClearArgument() { func ClipClearArgument() {
// read previous clipboard contents from stdin // read previous clipboard contents from stdin
clipScanner := bufio.NewScanner(os.Stdin) clipScanner := bufio.NewScanner(os.Stdin)
if clipScanner.Scan() { if clipScanner.Scan() {
oldContents := clipScanner.Text() assignedContents := clipScanner.Text()
clipClear(oldContents) clipClearProcess(assignedContents)
} else { } else {
os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment)
} }
} }
// clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed.
// assignedContents can be omitted to clear the clipboard immediately and unconditionally.
func clipClearProcess(assignedContents string) {
cmdPaste, cmdClear := getClipCommands()
clearClipboard := func() {
err := cmdClear.Run()
if err != nil {
fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
Exit(0)
}
// if assignedContents is empty, clear the clipboard immediately and unconditionally
if assignedContents == "" {
clearClipboard()
}
// wait 30 seconds before checking clipboard contents
time.Sleep(30 * time.Second)
newContents, err := cmdPaste.Output()
if err != nil {
fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
if assignedContents == strings.TrimRight(string(newContents), "\r\n") {
clearClipboard()
}
}
// GenTOTP generates a TOTP token from a secret (supports standard and Steam TOTP). // GenTOTP generates a TOTP token from a secret (supports standard and Steam TOTP).
func GenTOTP(secret string, time time.Time, forSteam bool) string { func GenTOTP(secret string, time time.Time, forSteam bool) string {
var totpToken string var totpToken string
+6 -24
View File
@@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strings"
"time"
) )
// copyString copies a string to the clipboard. // copyString copies a string to the clipboard.
@@ -21,29 +19,13 @@ func copyString(continuous bool, copySubject string) {
} }
if !continuous { if !continuous {
launchClipClear(copySubject) launchClipClearProcess(copySubject)
} }
} }
// clipClear is called in a separate process to clear the clipboard after 30 seconds. // getClipCommands returns the commands for pasting and clearing the clipboard contents.
func clipClear(oldContents string) { func getClipCommands() (*exec.Cmd, *exec.Cmd) {
time.Sleep(30 * time.Second) cmdClear := exec.Command("pbcopy")
writeToStdin(cmdClear, "")
cmd := exec.Command("pbpaste") return exec.Command("pbpaste"), cmdClear
newContents, err := cmd.Output()
if err != nil {
fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
if oldContents == strings.TrimRight(string(newContents), "\r\n") {
cmd = exec.Command("pbcopy")
writeToStdin(cmd, "")
err = cmd.Run()
if err != nil {
fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
}
Exit(0)
} }
+7 -25
View File
@@ -1,4 +1,4 @@
//go:build linux && termux //go:build android && termux
package core package core
@@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strings"
"time"
) )
// copyString copies a string to the clipboard. // copyString copies a string to the clipboard.
@@ -21,29 +19,13 @@ func copyString(continuous bool, copySubject string) {
} }
if !continuous { if !continuous {
launchClipClear(copySubject) launchClipClearProcess(copySubject)
} }
} }
// clipClear is called in a separate process to clear the clipboard after 30 seconds. // getClipCommands returns the commands for pasting and clearing the clipboard contents.
func clipClear(oldContents string) { func getClipCommands() (*exec.Cmd, *exec.Cmd) {
time.Sleep(30 * time.Second) cmdClear := exec.Command("termux-clipboard-set")
writeToStdin(cmdClear, "")
cmd := exec.Command("termux-clipboard-get") return exec.Command("termux-clipboard-get"), cmdClear
newContents, err := cmd.Output()
if err != nil {
fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
if oldContents == strings.TrimRight(string(newContents), "\r\n") {
cmd = exec.Command("termux-clipboard-set")
writeToStdin(cmd, "")
err = cmd.Run()
if err != nil {
fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
}
Exit(0)
} }
+13 -42
View File
@@ -6,68 +6,39 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strings"
"time"
) )
// copyString copies a string to the clipboard. // copyString copies a string to the clipboard.
func copyString(continuous bool, copySubject string) { func copyString(continuous bool, copySubject string) {
var envSet bool // track whether environment variables are set var envSet, isWayland bool // track whether environment variables are set
var cmd *exec.Cmd var cmdCopy *exec.Cmd
// determine whether to use wl-copy (Wayland) or xclip (X11) // determine whether to use wl-copy (Wayland) or xclip (X11)
if _, envSet = os.LookupEnv("WAYLAND_DISPLAY"); envSet { if _, envSet = os.LookupEnv("WAYLAND_DISPLAY"); envSet {
cmd = exec.Command("wl-copy", "-t", "text/plain") cmdCopy = exec.Command("wl-copy", "-t", "text/plain")
isWayland = true
} else if _, envSet = os.LookupEnv("DISPLAY"); envSet { } else if _, envSet = os.LookupEnv("DISPLAY"); envSet {
cmd = exec.Command("xclip", "-sel", "c", "-t", "text/plain") cmdCopy = exec.Command("xclip", "-sel", "c", "-t", "text/plain")
} else { } else {
fmt.Println(AnsiError + "Clipboard platform could not be determined - Note that the clipboard does not function in a raw TTY" + AnsiReset) fmt.Println(AnsiError + "Clipboard platform could not be determined - Note that the clipboard does not function in a raw TTY" + AnsiReset)
os.Exit(ErrorClipboard) os.Exit(ErrorClipboard)
} }
writeToStdin(cmd, copySubject) writeToStdin(cmdCopy, copySubject)
err := cmd.Run() err := cmdCopy.Run()
if err != nil { if err != nil {
fmt.Println(AnsiError+"Failed to copy to clipboard:", err.Error()+AnsiReset) fmt.Println(AnsiError+"Failed to copy to clipboard:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard) os.Exit(ErrorClipboard)
} }
if !continuous { if !continuous {
launchClipClear(copySubject) launchClipClearProcess(copySubject, isWayland)
} }
} }
// clipClear is called in a separate process to clear the clipboard after 30 seconds. // getClipCommands returns the commands for pasting and clearing the clipboard contents.
func clipClear(oldContents string) { func getClipCommands() (*exec.Cmd, *exec.Cmd) {
time.Sleep(30 * time.Second) if os.Args[2] == "true" { // wayland
return exec.Command("wl-paste"), exec.Command("wl-copy", "-c")
// determine clipboard tool to use (wl-clipboard VS xclip)
var envSet bool
var cmdClear, cmdPaste *exec.Cmd
if _, envSet = os.LookupEnv("WAYLAND_DISPLAY"); envSet {
cmdClear = exec.Command("wl-copy", "-c")
cmdPaste = exec.Command("wl-paste")
} else if _, envSet = os.LookupEnv("DISPLAY"); envSet {
cmdClear = exec.Command("xclip", "-i", "/dev/null", "-sel", "c")
cmdPaste = exec.Command("xclip", "-o", "-sel", "c")
} else {
fmt.Println(AnsiError + "Clipboard platform could not be determined - Neither $WAYLAND_DISPLAY nor $DISPLAY are set" + AnsiReset)
os.Exit(ErrorClipboard)
} }
return exec.Command("xclip", "-o", "-sel", "c"), exec.Command("xclip", "-i", "/dev/null", "-sel", "c")
// read current clipboard contents
newContents, err := cmdPaste.Output()
if err != nil {
fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
// clear clipboard if contents have not been modified
if oldContents == strings.TrimRight(string(newContents), "\r\n") {
err = cmdClear.Run()
if err != nil {
fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
}
Exit(0)
} }
+4 -22
View File
@@ -7,7 +7,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"time"
) )
// copyString copies a string to the clipboard. // copyString copies a string to the clipboard.
@@ -20,28 +19,11 @@ func copyString(continuous bool, copySubject string) {
} }
if !continuous { if !continuous {
launchClipClear(copySubject) launchClipClearProcess(copySubject)
} }
} }
// clipClear is called in a separate process to clear the clipboard after 30 seconds. // getClipCommands returns the commands for pasting and clearing the clipboard contents.
func clipClear(oldContents string) { func getClipCommands() (*exec.Cmd, *exec.Cmd) {
time.Sleep(30 * time.Second) return exec.Command("powershell.exe", "-c", "Get-Clipboard"), exec.Command("powershell.exe", "-c", "Set-Clipboard")
cmd := exec.Command("powershell.exe", "-c", "Get-Clipboard")
newContents, err := cmd.Output()
if err != nil {
fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
if oldContents == strings.TrimRight(string(newContents), "\r\n") {
cmd = exec.Command("powershell.exe", "-c", "Set-Clipboard")
err = cmd.Run()
if err != nil {
fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset)
os.Exit(ErrorClipboard)
}
}
Exit(0)
} }
@@ -1,4 +1,4 @@
//go:build !returnOnExit //go:build (windows || darwin || android || termux || wsl) && !returnOnExit
package core package core
@@ -8,9 +8,9 @@ import (
"os/exec" "os/exec"
) )
// launchClipClear launches the automated clipboard clearing process. // launchClipClearProcess launches the automated clipboard clearing process.
// For non-interactive CLI implementations, an entirely separate process is created for this purpose. // For non-interactive CLI implementations, an entirely separate process is created for this purpose.
func launchClipClear(copySubject string) { func launchClipClearProcess(copySubject string) {
executableName := os.Args[0] executableName := os.Args[0]
cmd := exec.Command(executableName, "clipclear") cmd := exec.Command(executableName, "clipclear")
writeToStdin(cmd, copySubject) writeToStdin(cmd, copySubject)
+24
View File
@@ -0,0 +1,24 @@
//go:build !windows && !darwin && !android && !termux && !wsl && !returnOnExit
package core
import (
"fmt"
"os"
"os/exec"
"strconv"
)
// launchClipClearProcess launches the automated clipboard clearing process.
// For non-interactive CLI implementations, an entirely separate process is created for this purpose.
func launchClipClearProcess(copySubject string, isWayland bool) {
executableName := os.Args[0]
cmd := exec.Command(executableName, "clipclear", strconv.FormatBool(isWayland))
writeToStdin(cmd, copySubject)
err := cmd.Start()
if err != nil {
fmt.Println(AnsiError + "Failed to launch automated clipboard clearing process - Does this libmutton implementation support the \"clipclear\" argument?" + AnsiReset)
os.Exit(ErrorClipboard)
}
os.Exit(0) // use os.Exit directly since this version of this function is only meant for non-interactive CLI implementations
}
-9
View File
@@ -1,9 +0,0 @@
//go:build returnOnExit
package core
// launchClipClear launches the automated clipboard clearing process.
// For interactive GUI/TUI implementations, the clipboard clearing process is launched as a goroutine.
func launchClipClear(copySubject string) {
go clipClear(copySubject)
}
+9
View File
@@ -0,0 +1,9 @@
//go:build (windows || darwin || android || termux || wsl) && returnOnExit
package core
// launchClipClearProcess launches the automated clipboard clearing process.
// For interactive GUI/TUI implementations, the clipboard clearing process is launched as a goroutine.
func launchClipClearProcess(copySubject string) {
go clipClearProcess(copySubject)
}
+15
View File
@@ -0,0 +1,15 @@
//go:build !windows && !darwin && !android && !termux && !wsl && returnOnExit
package core
import (
"os"
"strconv"
)
// launchClipClearProcess launches the automated clipboard clearing process.
// For interactive GUI/TUI implementations, the clipboard clearing process is launched as a goroutine.
func launchClipClearProcess(copySubject string, isWayland bool) {
os.Args[2] = strconv.FormatBool(isWayland)
go clipClearProcess(copySubject)
}