Skip to content

Commit

Permalink
✨ feat(sys/clip): update and enhance clipboard operate logic
Browse files Browse the repository at this point in the history
- fix read and write error on linux
- add more useful method for w/r io stream
  • Loading branch information
inhere committed Mar 2, 2023
1 parent 9374e19 commit fc98d06
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 53 deletions.
142 changes: 96 additions & 46 deletions sysutil/clipboard/clipboard.go
Expand Up @@ -4,67 +4,79 @@ package clipboard
import (
"bytes"
"errors"
"io"
"os/exec"
"strings"

"github.com/gookit/goutil/cliutil"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/sysutil"
)

// Clipboard struct
type Clipboard struct {
// TODO add event on write, read
// buffer for write
buf *bytes.Buffer

// print exec command line on run
verbose bool
// available - bin file exist on the OS.
available bool
writeable, readable bool

readerBin string
readArgs []string
writerBin string
// add slashes. eg: '\' -> '\\'
// addSlashes bool
readArgs []string
writeArgs []string
}

// New instance
func New() *Clipboard {
var readArgs []string

// special handle on Windows
reader := GetReaderBin()
if strings.Contains(reader, " ") {
args := strings.Split(reader, " ")
reader, readArgs = args[0], args[1:]
}
// special handle on with args
reader, readArgs := parseLine(GetReaderBin())
writer, writeArgs := parseLine(GetWriterBin())

return &Clipboard{
readerBin: reader,
readArgs: readArgs,
writerBin: GetWriterBin(),
available: sysutil.HasExecutable(reader),
writerBin: writer,
writeArgs: writeArgs,
readable: sysutil.HasExecutable(reader),
writeable: sysutil.HasExecutable(writer),
}
}

// WithSlashes for the contents
// func (c *Clipboard) WithSlashes() *Clipboard {
// c.addSlashes = true
// return c
// SetReader for handle clip
// func (c *Clipboard) SetReader(line string) {
// }

// SetWriter for handle clip
// func (c *Clipboard) SetWriter(line string) {
// }

// WithVerbose setting
func (c *Clipboard) WithVerbose(yn bool) *Clipboard {
c.verbose = yn
return c
}

// Clean the clipboard
func (c *Clipboard) Clean() error { return c.Reset() }

// Reset and clean the clipboard
func (c *Clipboard) Reset() error {
if c.buf != nil {
c.buf.Reset()
}

// run: echo '' | pbcopy
// echo empty string for clean clipboard.
cmd := exec.Command(c.writerBin)
cmd.Stdin = strings.NewReader("")
return cmd.Run()
// run: echo '' | pbcopy
return c.WriteFrom(strings.NewReader(""))
}

//
// -------------------- write --------------------
// ---------------------------------------- write ----------------------------------------
//

// Write bytes data to clipboard
Expand All @@ -83,46 +95,61 @@ func (c *Clipboard) WriteString(s string) (int, error) {
// Flush buffer contents to clipboard
func (c *Clipboard) Flush() error {
if c.buf == nil || c.buf.Len() == 0 {
return errors.New("not write contents")
return errors.New("clipboard: empty contents for write")
}

// linux:
// # Copy input to clipboard
// echo -n "$input" | xclip -selection c
// Mac:
// echo hello | pbcopy
// pbcopy < tempfile.txt
cmd := exec.Command(c.writerBin)
cmd.Stdin = c.buf

defer c.buf.Reset()
return cmd.Run()
return c.WriteFrom(c.buf)
}

// WriteFromFile contents to clipboard
func (c *Clipboard) WriteFromFile(filepath string) error {
// eg:
// Mac: pbcopy < tempfile.txt
// return exec.Command(c.writerBin, "<", filepath).Run()
file, err := fsutil.OpenReadFile(filepath)
if err != nil {
return err
}

defer file.Close()
return c.WriteFrom(file)
}

// WriteFrom reader data to clipboard
func (c *Clipboard) WriteFrom(r io.Reader) error {
if !c.writeable {
return errorx.Rawf("clipboard: write driver %q not found on OS", c.writerBin)
}

cmd := exec.Command(c.writerBin)
cmd.Stdin = file
cmd := exec.Command(c.writerBin, c.writeArgs...)
cmd.Stdin = r

if c.verbose {
cliutil.Yellowf("clipboard> %s\n", cliutil.BuildLine(c.writerBin, c.writeArgs))
}
return cmd.Run()
}

//
// -------------------- read --------------------
// ---------------------------------------- read ----------------------------------------
//

// Read contents from clipboard
func (c *Clipboard) Read() ([]byte, error) {
return exec.Command(c.readerBin, c.readArgs...).Output()
buf, err := c.ReadToBuffer()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

// ReadToBuffer contents from clipboard
func (c *Clipboard) ReadToBuffer() (*bytes.Buffer, error) {
var buf bytes.Buffer
if err := c.ReadTo(&buf); err != nil {
return nil, err
}
return &buf, nil
}

// ReadString contents as string from clipboard
Expand All @@ -134,31 +161,54 @@ func (c *Clipboard) ReadString() (string, error) {

// fix: at Windows will always return end of the "\r\n"
if sysutil.IsWindows() {
return strings.TrimRight(string(bts), "\r\n"), err
return string(bytes.TrimRight(bts, "\r\n")), nil
}
return string(bts), err
return string(bts), nil
}

// ReadToFile dump clipboard data to file
func (c *Clipboard) ReadToFile(filepath string) error {
// eg:
// Mac: pbpaste >> tasklist.txt
// return exec.Command(c.readerBin, ">>", filepath).Run()
file, err := fsutil.QuickOpenFile(filepath)
if err != nil {
return err
}

defer file.Close()
return c.ReadTo(file)
}

// ReadTo read clipboard contents to writer
func (c *Clipboard) ReadTo(w io.Writer) error {
if !c.readable {
return errorx.Rawf("clipboard: read driver %q not found on OS", c.readerBin)
}

cmd := exec.Command(c.readerBin, c.readArgs...)
cmd.Stdout = file
cmd.Stdout = w

if c.verbose {
cliutil.Yellowf("clipboard> %s\n", cliutil.BuildLine(c.readerBin, c.readArgs))
}
return cmd.Run()
}

//
// ---------------------------------------- help ----------------------------------------
//

// Available check
func (c *Clipboard) Available() bool {
return c.available
return c.writeable && c.readable
}

// Writeable check
func (c *Clipboard) Writeable() bool {
return c.writeable
}

// Readable check
func (c *Clipboard) Readable() bool {
return c.readable
}

func (c *Clipboard) buffer() *bytes.Buffer {
Expand Down
52 changes: 48 additions & 4 deletions sysutil/clipboard/util.go
@@ -1,14 +1,48 @@
package clipboard

import "strings"

// clipboard writer, reader program names
const (
// WriterOnMac driver
//
// Example:
// echo hello | pbcopy
// pbcopy < tempfile.txt
WriterOnMac = "pbcopy"
WriterOnWin = "clip" // clip only support write contents to clipboard.
WriterOnLin = "xsel"

// WriterOnWin driver on Windows
//
// TIP: clip only support write contents to clipboard.
WriterOnWin = "clip"

// WriterOnLin driver name
//
// linux:
// echo "hello-c" | xclip -selection c
WriterOnLin = "xclip -selection clipboard"

// ReaderOnMac driver
//
// Example:
// Mac: pbpaste >> tasklist.txt
ReaderOnMac = "pbpaste"
ReaderOnWin = "powershell get-clipboard" // read should use: powershell get-clipboard
ReaderOnLin = "xclip"

// ReaderOnWin driver on Windows
//
// read clipboard should use: powershell get-clipboard
ReaderOnWin = "powershell get-clipboard"

// ReaderOnLin driver name
//
// Usage:
// xclip -o -selection clipboard
// xclip -o -selection c // can use shorts
ReaderOnLin = "xclip -o -selection clipboard"
)

var (
writerOnLin = []string{"xclip", "xsel"}
)

// std instance
Expand Down Expand Up @@ -36,3 +70,13 @@ func WriteString(s string) error {
}
return std.Flush()
}

// special handle on with args
func parseLine(line string) (bin string, args []string) {
bin = line
if strings.ContainsRune(line, ' ') {
list := strings.Split(line, " ")
bin, args = list[0], list[1:]
}
return
}
1 change: 0 additions & 1 deletion sysutil/clipboard/util_darwin.go
@@ -1,5 +1,4 @@
//go:build darwin
// +build darwin

package clipboard

Expand Down
1 change: 0 additions & 1 deletion sysutil/clipboard/util_unix.go
@@ -1,5 +1,4 @@
//go:build !windows && !darwin
// +build !windows,!darwin

package clipboard

Expand Down
1 change: 0 additions & 1 deletion sysutil/clipboard/util_windows.go
@@ -1,5 +1,4 @@
//go:build windows
// +build windows

package clipboard

Expand Down

0 comments on commit fc98d06

Please sign in to comment.