diff --git a/go.mod b/go.mod index 79f7c11..b3c4458 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,10 @@ require ( github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.11.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect - golang.org/x/tools v0.10.0 // indirect + golang.org/x/tools v0.11.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4bc4997..1e1690e 100644 --- a/go.sum +++ b/go.sum @@ -62,13 +62,13 @@ golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g= golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -96,8 +96,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 7acd45b..d97e448 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -40,6 +40,9 @@ import ( // version string will be injected by automation var version string +// saveToClipboard function will be implemented by OS specific code +var saveToClipboard func(img.Scaffold) error + var rootCmd = &cobra.Command{ Use: fmt.Sprintf("%s [%s flags] [--] command [command flags] [command arguments] [...]", executableName(), executableName()), Short: "Creates a screenshot of terminal command output", @@ -118,8 +121,16 @@ window including all terminal colors and text decorations. return err } - filename, runErr := cmd.Flags().GetString("filename") - if filename == "" || runErr != nil { + // save image to clipboard + // + if toClipboard, err := cmd.Flags().GetBool("clipboard"); err == nil && toClipboard { + return saveToClipboard(scaffold) + } + + // save image to file + // + filename, err := cmd.Flags().GetString("filename") + if filename == "" || err != nil { fmt.Fprintf(os.Stderr, "failed to read filename from command-line, defaulting to out.png") filename = "out.png" } @@ -128,7 +139,13 @@ window including all terminal colors and text decorations. return fmt.Errorf("file extension %q of filename %q is not supported, only png is supported", extension, filename) } - return scaffold.SavePNG(filename) + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + + defer file.Close() + return scaffold.Write(file) }, } diff --git a/internal/cmd/root_darwin.go b/internal/cmd/root_darwin.go new file mode 100644 index 0000000..3a9ece7 --- /dev/null +++ b/internal/cmd/root_darwin.go @@ -0,0 +1,75 @@ +// Copyright © 2023 The Homeport Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package cmd + +import ( + "bytes" + "encoding/hex" + "fmt" + "os" + "os/exec" + + "github.com/homeport/termshot/internal/img" +) + +const osascript = "/usr/bin/osascript" + +// hasOsascript checks if /usr/bin/osascript exists and is executable +func hasOsascript() bool { + if fi, err := os.Stat(osascript); err == nil { + return fi.Mode()&0111 != 0 + } + + return false +} + +func init() { + if hasOsascript() { + // register tool flag to enable clipboard option + rootCmd.Flags().BoolP("clipboard", "b", false, "copy termshot to clipboard, overrules filename option") + + // register function to copy image into the clipboard + saveToClipboard = func(scaffold img.Scaffold) error { + var buf bytes.Buffer + + if _, err := buf.WriteString("set the clipboard to «data PICT"); err != nil { + return err + } + + if err := scaffold.Write(hex.NewEncoder(&buf)); err != nil { + return err + } + + if _, err := buf.WriteString("»"); err != nil { + return err + } + + cmd := exec.Command(osascript, "-e", buf.String()) + out, err := cmd.CombinedOutput() + if err != nil { + fmt.Fprint(os.Stderr, string(out)) + return err + } + + return nil + } + } +} diff --git a/internal/img/output.go b/internal/img/output.go index 82a5be7..b124e0b 100644 --- a/internal/img/output.go +++ b/internal/img/output.go @@ -21,7 +21,10 @@ package img import ( + "bytes" + "image" "image/color" + "image/png" "io" "math" "strings" @@ -156,7 +159,7 @@ func (s *Scaffold) measureContent() (width float64, height float64) { return width, height } -func (s *Scaffold) SavePNG(path string) error { +func (s *Scaffold) image() (image.Image, error) { var f = func(value float64) float64 { return s.factor * value } var ( @@ -196,7 +199,7 @@ func (s *Scaffold) SavePNG(path string) error { shadow, err := stackblur.Process(bc.Image(), uint32(s.shadowRadius)) if err != nil { - return err + return nil, err } dc.DrawImage(shadow, 0, 0) @@ -294,5 +297,23 @@ func (s *Scaffold) SavePNG(path string) error { x += w } - return dc.SavePNG(path) + return dc.Image(), nil +} + +func (s *Scaffold) Write(w io.Writer) error { + image, err := s.image() + if err != nil { + return err + } + + return png.Encode(w, image) +} + +func (s *Scaffold) Bytes() ([]byte, error) { + var buf bytes.Buffer + if err := s.Write(&buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil } diff --git a/internal/img/output_test.go b/internal/img/output_test.go index b801b7d..fcefa25 100644 --- a/internal/img/output_test.go +++ b/internal/img/output_test.go @@ -22,6 +22,7 @@ package img_test import ( "bytes" + "io" "os" "strings" @@ -52,7 +53,7 @@ var _ = Describe("Creating images", func() { err := scaffold.AddContent(strings.NewReader("foobar")) Expect(err).ToNot(HaveOccurred()) - err = scaffold.SavePNG(name) + err = scaffold.Write(io.Discard) Expect(err).ToNot(HaveOccurred()) }) }) @@ -72,7 +73,7 @@ var _ = Describe("Creating images", func() { err := scaffold.AddContent(&buf) Expect(err).ToNot(HaveOccurred()) - err = scaffold.SavePNG(name) + err = scaffold.Write(io.Discard) Expect(err).ToNot(HaveOccurred()) }) })