diff options
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 297 |
1 files changed, 153 insertions, 144 deletions
@@ -3,7 +3,6 @@ package main import ( "bufio" "encoding/base64" - "flag" "fmt" "github.com/gdamore/tcell/v2" "github.com/jba/slog/handlers/loghandler" @@ -13,12 +12,16 @@ import ( "os" "strings" "time" + + "github.com/spf13/cobra" ) var ( - oscOpen string = "\x1b]52;c;" - oscClose string = "\a" - isScreen bool + oscOpen string = "\x1b]52;c;" + oscClose string = "\a" + isScreen bool + verboseFlag bool + logfileFlag string ) func encode(fname string, encoder io.WriteCloser) { @@ -54,44 +57,8 @@ func closetty(tty tcell.Tty) { tty.Close() } -func _main() int { - var fnames []string +func initLogging() { var err error - var exitCode int - - flag.Usage = func() { - template := `Reads or writes the system clipboard using the ANSI OSC52 escape sequence. - -Usage: - -COPY mode (default): - - %s [file1 [...fileN]] - -With no arguments, will read from stdin. - -PASTE mode: - - %s --paste - -Outputs clipboard contents to stdout. - -Options: -` - fmt.Fprintf(flag.CommandLine.Output(), template, os.Args[0], os.Args[0]) - flag.PrintDefaults() - } - - var pasteFlag bool - var verboseFlag bool - var logfileFlag string - flag.BoolVar(&pasteFlag, "paste", pasteFlag, "paste operation") - flag.BoolVar(&verboseFlag, "verbose", verboseFlag, "verbose logging") - flag.BoolVar(&verboseFlag, "v", verboseFlag, "verbose logging") - flag.StringVar(&logfileFlag, "logFile", logfileFlag, "redirect logs to file") - - flag.Parse() - logLevel := &slog.LevelVar{} // INFO logOutput := os.Stdout @@ -113,7 +80,9 @@ Options: slog.SetDefault(logger) slog.Debug("logging started") +} +func identifyTerm() { if ti, err := tcell.LookupTerminfo(os.Getenv("TERM")); err != nil { slog.Error(fmt.Sprintf("Failed to lookup terminfo: %v", err)) } else { @@ -128,129 +97,169 @@ Options: oscOpen = "\x1bP" + oscOpen oscClose = oscClose + "\x1b\\" } +} + +func copy(fnames []string) error { + // copy + if len(fnames) == 0 { + fnames = []string{"-"} + } - if !pasteFlag { - // copy - if len(flag.Args()) > 0 { - fnames = flag.Args() - } else { - fnames = []string{"-"} + slog.Debug("Beginning osc52 copy operation") + err := func() error { + tty, err := opentty() + if err != nil { + slog.Error(fmt.Sprintf("opentty: %v", err)) + return err } + defer closetty(tty) - slog.Debug("Beginning osc52 copy operation") - func() { - tty, err := opentty() - if err != nil { - slog.Error(fmt.Sprintf("opentty: %v", err)) - exitCode = 1 - return - } - defer closetty(tty) + // Open buffered output, using default max OSC52 length as buffer size + // TODO limit size + out := bufio.NewWriterSize(tty, 1000000) - // Open buffered output, using default max OSC52 length as buffer size - // TODO limit size - out := bufio.NewWriterSize(tty, 1000000) + // Start OSC52 + fmt.Fprint(out, oscOpen) - // Start OSC52 - fmt.Fprint(out, oscOpen) + b64 := base64.NewEncoder(base64.StdEncoding, out) + for _, fname := range fnames { + encode(fname, b64) + } + b64.Close() - b64 := base64.NewEncoder(base64.StdEncoding, out) - for _, fname := range fnames { - encode(fname, b64) - } - b64.Close() + // End OSC52 + fmt.Fprint(out, oscClose) - // End OSC52 - fmt.Fprint(out, oscClose) + out.Flush() + return nil + }() + slog.Debug("Ended osc52") + return err +} - out.Flush() - }() - slog.Debug("Ended osc52") - } else { - // paste - - slog.Debug("Beginning osc52 paste operation") - data := func() []byte { - tty, err := opentty() - if err != nil { - slog.Error(fmt.Sprintf("opentty: %v", err)) - exitCode = 1 - return nil +func paste() error { + slog.Debug("Beginning osc52 paste operation") + if data, err := func() ([]byte, error) { + tty, err := opentty() + if err != nil { + slog.Error(fmt.Sprintf("opentty: %v", err)) + return nil, err + } + defer closetty(tty) + + // Start OSC52 + fmt.Fprint(tty, oscOpen+"?"+oscClose) + + ttyReader := bufio.NewReader(tty) + buf := make([]byte, 0, 1024) + + // time out intial read in 100 milliseconds + readChan := make(chan byte, 1) + defer close(readChan) + go func() { + if b, e := ttyReader.ReadByte(); e != nil { + slog.Debug(fmt.Sprintf("Initial ReadByte error: %v", e)) + } else { + readChan <- b } - defer closetty(tty) - - // Start OSC52 - fmt.Fprint(tty, oscOpen+"?"+oscClose) - - ttyReader := bufio.NewReader(tty) - buf := make([]byte, 0, 1024) - - // time out intial read in 100 milliseconds - readChan := make(chan byte, 1) - defer close(readChan) - go func() { - if b, e := ttyReader.ReadByte(); e != nil { - slog.Debug(fmt.Sprintf("Initial ReadByte error: %v", e)) - } else { - readChan <- b + }() + select { + case b := <-readChan: + buf = append(buf, b) + case <-time.After(100 * time.Millisecond): + slog.Debug("tty read timeout") + return nil, fmt.Errorf("tty read timeout") + } + + for { + if b, e := ttyReader.ReadByte(); e != nil { + slog.Error(fmt.Sprintf("ReadByte: %v", e)) + return nil, e + } else { + slog.Debug(fmt.Sprintf("Read: %x '%s'", b, string(b))) + // Terminator might be BEL (\a) or ESC-backslash (\x1b\\) + if b == '\a' { + break } - }() - select { - case b := <-readChan: buf = append(buf, b) - case <-time.After(100 * time.Millisecond): - slog.Debug("tty read timeout") - exitCode = 1 - return nil - } - - for { - if b, e := ttyReader.ReadByte(); e != nil { - slog.Error(fmt.Sprintf("ReadByte: %v", e)) - exitCode = 1 - return nil - } else { - slog.Debug(fmt.Sprintf("Read: %x '%s'", b, string(b))) - // Terminator might be BEL (\a) or ESC-backslash (\x1b\\) - if b == '\a' { - break - } - buf = append(buf, b) - // Skip initial 7 bytes of response - if len(buf) > 9 && buf[len(buf)-2] == '\x1b' && buf[len(buf)-1] == '\\' { - buf = buf[:len(buf)-2] - break - } + // Skip initial 7 bytes of response + if len(buf) > 9 && buf[len(buf)-2] == '\x1b' && buf[len(buf)-1] == '\\' { + buf = buf[:len(buf)-2] + break } } + } - slog.Debug(fmt.Sprintf("buf[:7]: %q", buf[:7])) - buf = buf[7:] - - decodedBuf := make([]byte, base64.StdEncoding.DecodedLen(len(buf))) - n, err := base64.StdEncoding.Decode(decodedBuf, []byte(buf)) - if err != nil { - slog.Error(fmt.Sprintf("decode error: %v", err)) - exitCode = 1 - return nil - } - decodedBuf = decodedBuf[:n] + slog.Debug(fmt.Sprintf("buf[:7]: %q", buf[:7])) + buf = buf[7:] - return decodedBuf - }() + decodedBuf := make([]byte, base64.StdEncoding.DecodedLen(len(buf))) + n, err := base64.StdEncoding.Decode(decodedBuf, []byte(buf)) + if err != nil { + slog.Error(fmt.Sprintf("decode error: %v", err)) + return nil, err + } + decodedBuf = decodedBuf[:n] - if data != nil { - if _, err := os.Stdout.Write(data); err != nil { - slog.Error(fmt.Sprintf("Error writing to stdout: %v", err)) - exitCode = 1 - } + return decodedBuf, nil + }(); err != nil { + return err + } else { + if _, err = os.Stdout.Write(data); err != nil { + slog.Error(fmt.Sprintf("Error writing to stdout: %v", err)) + return err } - slog.Debug("Ended osc52") } + slog.Debug("Ended osc52") - return exitCode + return nil +} + +var copyCmd = &cobra.Command{ + Use: "copy", + Short: "Copies input to the system clipboard", + Long: `Copies input to the system clipboard. Usage: + +osc copy [file1 [...fileN]] + +With no arguments, will read from stdin.`, + RunE: func(cmd *cobra.Command, args []string) error { + initLogging() + identifyTerm() + return copy(args) + }, +} + +var pasteCmd = &cobra.Command{ + Use: "paste", + Short: "Outputs system clipboard contents to stdout", + Long: `Outputs system clipboard contents to stdout. Usage: + +osc paste`, + RunE: func(cmd *cobra.Command, args []string) error { + initLogging() + identifyTerm() + return paste() + }, +} + +var rootCmd = &cobra.Command{ + Use: "osc", + Short: "Reads or writes the system clipboard using the ANSI OSC52 escape sequence", + Long: `Reads or writes the system clipboard using the ANSI OSC52 escape sequence.`, +} + +func init() { + rootCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "verbose logging") + rootCmd.PersistentFlags().StringVarP(&logfileFlag, "log", "l", "", "write logs to file") + + rootCmd.AddCommand(copyCmd) + rootCmd.AddCommand(pasteCmd) } func main() { - os.Exit(_main()) + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } } |
