diff --git a/docs/keybindings/Keybindings_de.md b/docs/keybindings/Keybindings_de.md index f463e21bd..274595c3b 100644 --- a/docs/keybindings/Keybindings_de.md +++ b/docs/keybindings/Keybindings_de.md @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ## Container
+  : copy container id
   d: entfernen
   e: hide/show stopped containers
   p: pause
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index a5a8ff3f0..4c82e9322 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Containers
 
 
+  : copy container id
   d: remove
   e: hide/show stopped containers
   p: pause
diff --git a/docs/keybindings/Keybindings_es.md b/docs/keybindings/Keybindings_es.md
index ff4eee148..ac3ce401e 100644
--- a/docs/keybindings/Keybindings_es.md
+++ b/docs/keybindings/Keybindings_es.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Contenedores
 
 
+  : copy container id
   d: borrar
   e: esconder/mostrar contenedores parados
   p: pausa
diff --git a/docs/keybindings/Keybindings_fr.md b/docs/keybindings/Keybindings_fr.md
index 64fb4197d..e8df6e328 100644
--- a/docs/keybindings/Keybindings_fr.md
+++ b/docs/keybindings/Keybindings_fr.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Conteneurs
 
 
+  : copy container id
   d: supprimer
   e: cacher/montrer les conteneurs arrêtés
   p: pause
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index 3de0c93e8..da6a6dd85 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Containers
 
 
+  : copy container id
   d: verwijder
   e: verberg gestopte containers
   p: pause
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 8d46b0efe..cb261f25d 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Kontenery
 
 
+  : copy container id
   d: usuń
   e: hide/show stopped containers
   p: pause
diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md
index 41604314c..ac958a5dc 100644
--- a/docs/keybindings/Keybindings_pt.md
+++ b/docs/keybindings/Keybindings_pt.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Contêineres
 
 
+  : copy container id
   d: remover
   e: ocultar/mostrar contêineres parados
   p: pausar
diff --git a/docs/keybindings/Keybindings_tr.md b/docs/keybindings/Keybindings_tr.md
index 018f68c28..1617e9c6d 100644
--- a/docs/keybindings/Keybindings_tr.md
+++ b/docs/keybindings/Keybindings_tr.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## Konteynerler
 
 
+  : copy container id
   d: kaldır
   e: hide/show stopped containers
   p: pause
diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md
index 8b1607dde..98543452e 100644
--- a/docs/keybindings/Keybindings_zh.md
+++ b/docs/keybindings/Keybindings_zh.md
@@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
 ## 容器
 
 
+  : copy container id
   d: 移除
   e: 隐藏/显示已停止的容器
   p: 暂停
diff --git a/go.mod b/go.mod
index 0376b2937..e2eb2dab6 100644
--- a/go.mod
+++ b/go.mod
@@ -31,6 +31,7 @@ require (
 )
 
 require (
+	github.com/atotto/clipboard v0.1.4 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/containerd/log v0.1.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index ffacfe7fe..807c47162 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c h1:YDsGA6tou+tAxVe0Dre29iSbQ8TrWdWfwOisKArJT5E=
 github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
+github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
+github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
 github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 h1:1fx+RA5lk1ZkzPAUP7DEgZnVHYxEcHO77vQO/V8z/2Q=
 github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc=
 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index b8dec4a03..5128b1bf2 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/go-errors/errors"
 
+	"github.com/atotto/clipboard"
 	"github.com/jesseduffield/kill"
 	"github.com/jesseduffield/lazydocker/pkg/config"
 	"github.com/jesseduffield/lazydocker/pkg/utils"
@@ -373,3 +374,12 @@ func (c *OSCommand) Kill(cmd *exec.Cmd) error {
 func (c *OSCommand) PrepareForChildren(cmd *exec.Cmd) {
 	kill.PrepareForChildren(cmd)
 }
+
+func (c *OSCommand) CopyToClipboard(str string) error {
+	escaped := strings.Replace(str, "\n", "\\n", -1)
+	truncated := utils.TruncateWithEllipsis(escaped, 40)
+
+	c.Log.Debug(utils.ResolvePlaceholderString("Copying '{{str}}' to clipboard", map[string]string{"str": truncated}))
+
+	return clipboard.WriteAll(str)
+}
diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go
index f806f72c1..f58b69156 100644
--- a/pkg/gui/app_status_manager.go
+++ b/pkg/gui/app_status_manager.go
@@ -1,6 +1,7 @@
 package gui
 
 import (
+	"sync"
 	"time"
 
 	"github.com/jesseduffield/gocui"
@@ -15,10 +16,15 @@ type appStatus struct {
 
 type statusManager struct {
 	statuses []appStatus
+	lock     *sync.Mutex
 }
 
 func (m *statusManager) removeStatus(name string) {
 	newStatuses := []appStatus{}
+
+	m.lock.Lock()
+	defer m.lock.Unlock()
+
 	for _, status := range m.statuses {
 		if status.name != name {
 			newStatuses = append(newStatuses, status)
@@ -28,6 +34,9 @@ func (m *statusManager) removeStatus(name string) {
 }
 
 func (m *statusManager) addWaitingStatus(name string) {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+
 	m.removeStatus(name)
 	newStatus := appStatus{
 		name:       name,
@@ -38,6 +47,9 @@ func (m *statusManager) addWaitingStatus(name string) {
 }
 
 func (m *statusManager) getStatusString() string {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+
 	if len(m.statuses) == 0 {
 		return ""
 	}
@@ -48,6 +60,11 @@ func (m *statusManager) getStatusString() string {
 	return topStatus.name
 }
 
+// WithStaticWaitingStatus shows a waiting status for a specific duration
+func (gui *Gui) WithStaticWaitingStatus(name string, duration time.Duration) error {
+	return gui.WithWaitingStatus(name, func() error { time.Sleep(duration); return nil })
+}
+
 // WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
 func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
 	go func() {
diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go
index 92aa95b37..f8dc9b089 100644
--- a/pkg/gui/containers_panel.go
+++ b/pkg/gui/containers_panel.go
@@ -358,6 +358,20 @@ func (gui *Gui) PauseContainer(container *commands.Container) error {
 	})
 }
 
+func (gui *Gui) handleCopyContainerId(g *gocui.Gui, v *gocui.View) error {
+	ctr, err := gui.Panels.Containers.GetSelectedItem()
+	if err != nil {
+		return nil
+	}
+
+	err = gui.WithStaticWaitingStatus(fmt.Sprintf(gui.Tr.CopyContainerIdStatus, utils.TruncateWithEllipsis(ctr.ID, 10)), time.Second*2)
+	if err != nil {
+		return err
+	}
+
+	return gui.OSCommand.CopyToClipboard(ctr.ID)
+}
+
 func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
 	ctr, err := gui.Panels.Containers.GetSelectedItem()
 	if err != nil {
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index fa6199f31..8bfebf766 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"os"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/docker/docker/api/types/events"
@@ -146,9 +147,11 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
 		State:         initialState,
 		Config:        config,
 		Tr:            tr,
-		statusManager: &statusManager{},
-		taskManager:   tasks.NewTaskManager(log, tr),
-		ErrorChan:     errorChan,
+		statusManager: &statusManager{
+			lock: &sync.Mutex{},
+		},
+		taskManager: tasks.NewTaskManager(log, tr),
+		ErrorChan:   errorChan,
 	}
 
 	deadlock.Opts.Disable = !gui.Config.Debug
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 424f47c56..b51c4c8e4 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -185,6 +185,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
 			Modifier: gocui.ModNone,
 			Handler:  gui.handleDonate,
 		},
+		{
+			ViewName:    "containers",
+			Key:         gocui.KeyCtrlO,
+			Modifier:    gocui.ModNone,
+			Handler:     gui.handleCopyContainerId,
+			Description: gui.Tr.CopyContainerId,
+		},
 		{
 			ViewName:    "containers",
 			Key:         'd',
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 0d4f82222..5637ee734 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -67,6 +67,8 @@ type TranslationSet struct {
 	ViewLogs                    string
 	UpProject                   string
 	DownProject                 string
+	CopyContainerId             string
+	CopyContainerIdStatus       string
 	ServicesTitle               string
 	ContainersTitle             string
 	StandaloneContainersTitle   string
@@ -190,6 +192,8 @@ func englishSet() TranslationSet {
 		ViewLogs:                    "view logs",
 		UpProject:                   "up project",
 		DownProject:                 "down project",
+		CopyContainerId:             "copy container id",
+		CopyContainerIdStatus:       "Copied %s to clipboard",
 		RemoveImage:                 "remove image",
 		RemoveVolume:                "remove volume",
 		RemoveNetwork:               "remove network",
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 0cd9a7cad..33b01e49e 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -11,6 +11,7 @@ import (
 	"sort"
 	"strings"
 	"time"
+	"unicode"
 
 	"github.com/go-errors/errors"
 	"github.com/jesseduffield/gocui"
@@ -410,3 +411,23 @@ func marshalIntoFormat(data interface{}, format string) ([]byte, error) {
 		return nil, errors.New(fmt.Sprintf("Unsupported detailization format: %s", format))
 	}
 }
+
+func StringWidth(s string) int {
+	// We are intentionally not using a range loop here, because that would
+	// convert the characters to runes, which is unnecessary work in this case.
+	for i := 0; i < len(s); i++ {
+		if s[i] > unicode.MaxASCII {
+			return runewidth.StringWidth(s)
+		}
+	}
+
+	return len(s)
+}
+
+// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
+func TruncateWithEllipsis(str string, limit int) string {
+	if StringWidth(str) > limit && limit <= 2 {
+		return strings.Repeat(".", limit)
+	}
+	return runewidth.Truncate(str, limit, "…")
+}
diff --git a/vendor/github.com/atotto/clipboard/.travis.yml b/vendor/github.com/atotto/clipboard/.travis.yml
new file mode 100644
index 000000000..23f21d836
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/.travis.yml
@@ -0,0 +1,22 @@
+language: go
+
+os:
+ - linux
+ - osx
+ - windows
+
+go:
+ - go1.13.x
+ - go1.x
+
+services:
+ - xvfb
+
+before_install:
+ - export DISPLAY=:99.0
+
+script:
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
+ - go test -v .
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
+ - go test -v .
diff --git a/vendor/github.com/atotto/clipboard/LICENSE b/vendor/github.com/atotto/clipboard/LICENSE
new file mode 100644
index 000000000..dee3257b0
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013 Ato Araki. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of @atotto. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/atotto/clipboard/README.md b/vendor/github.com/atotto/clipboard/README.md
new file mode 100644
index 000000000..41fdd57b8
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/README.md
@@ -0,0 +1,48 @@
+[![Build Status](https://travis-ci.org/atotto/clipboard.svg?branch=master)](https://travis-ci.org/atotto/clipboard)
+
+[![GoDoc](https://godoc.org/github.com/atotto/clipboard?status.svg)](http://godoc.org/github.com/atotto/clipboard)
+
+# Clipboard for Go
+
+Provide copying and pasting to the Clipboard for Go.
+
+Build:
+
+    $ go get github.com/atotto/clipboard
+
+Platforms:
+
+* OSX
+* Windows 7 (probably work on other Windows)
+* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
+
+
+Document: 
+
+* http://godoc.org/github.com/atotto/clipboard
+
+Notes:
+
+* Text string only
+* UTF-8 text encoding only (no conversion)
+
+TODO:
+
+* Clipboard watcher(?)
+
+## Commands:
+
+paste shell command:
+
+    $ go get github.com/atotto/clipboard/cmd/gopaste
+    $ # example:
+    $ gopaste > document.txt
+
+copy shell command:
+
+    $ go get github.com/atotto/clipboard/cmd/gocopy
+    $ # example:
+    $ cat document.txt | gocopy
+
+
+
diff --git a/vendor/github.com/atotto/clipboard/clipboard.go b/vendor/github.com/atotto/clipboard/clipboard.go
new file mode 100644
index 000000000..d7907d3a7
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard.go
@@ -0,0 +1,20 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package clipboard read/write on clipboard
+package clipboard
+
+// ReadAll read string from clipboard
+func ReadAll() (string, error) {
+	return readAll()
+}
+
+// WriteAll write string to clipboard
+func WriteAll(text string) error {
+	return writeAll(text)
+}
+
+// Unsupported might be set true during clipboard init, to help callers decide
+// whether or not to offer clipboard options.
+var Unsupported bool
diff --git a/vendor/github.com/atotto/clipboard/clipboard_darwin.go b/vendor/github.com/atotto/clipboard/clipboard_darwin.go
new file mode 100644
index 000000000..6f33078db
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_darwin.go
@@ -0,0 +1,52 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+package clipboard
+
+import (
+	"os/exec"
+)
+
+var (
+	pasteCmdArgs = "pbpaste"
+	copyCmdArgs  = "pbcopy"
+)
+
+func getPasteCommand() *exec.Cmd {
+	return exec.Command(pasteCmdArgs)
+}
+
+func getCopyCommand() *exec.Cmd {
+	return exec.Command(copyCmdArgs)
+}
+
+func readAll() (string, error) {
+	pasteCmd := getPasteCommand()
+	out, err := pasteCmd.Output()
+	if err != nil {
+		return "", err
+	}
+	return string(out), nil
+}
+
+func writeAll(text string) error {
+	copyCmd := getCopyCommand()
+	in, err := copyCmd.StdinPipe()
+	if err != nil {
+		return err
+	}
+
+	if err := copyCmd.Start(); err != nil {
+		return err
+	}
+	if _, err := in.Write([]byte(text)); err != nil {
+		return err
+	}
+	if err := in.Close(); err != nil {
+		return err
+	}
+	return copyCmd.Wait()
+}
diff --git a/vendor/github.com/atotto/clipboard/clipboard_plan9.go b/vendor/github.com/atotto/clipboard/clipboard_plan9.go
new file mode 100644
index 000000000..9d2fef4ef
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_plan9.go
@@ -0,0 +1,42 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build plan9
+
+package clipboard
+
+import (
+	"os"
+	"io/ioutil"
+)
+
+func readAll() (string, error) {
+	f, err := os.Open("/dev/snarf")
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+
+	str, err := ioutil.ReadAll(f)
+	if err != nil {
+		return "", err
+	}
+	
+	return string(str), nil
+}
+
+func writeAll(text string) error {
+	f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	
+	_, err = f.Write([]byte(text))
+	if err != nil {
+		return err
+	}
+	
+	return nil
+}
diff --git a/vendor/github.com/atotto/clipboard/clipboard_unix.go b/vendor/github.com/atotto/clipboard/clipboard_unix.go
new file mode 100644
index 000000000..d9f6a5610
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_unix.go
@@ -0,0 +1,149 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build freebsd linux netbsd openbsd solaris dragonfly
+
+package clipboard
+
+import (
+	"errors"
+	"os"
+	"os/exec"
+)
+
+const (
+	xsel               = "xsel"
+	xclip              = "xclip"
+	powershellExe      = "powershell.exe"
+	clipExe            = "clip.exe"
+	wlcopy             = "wl-copy"
+	wlpaste            = "wl-paste"
+	termuxClipboardGet = "termux-clipboard-get"
+	termuxClipboardSet = "termux-clipboard-set"
+)
+
+var (
+	Primary bool
+	trimDos bool
+
+	pasteCmdArgs []string
+	copyCmdArgs  []string
+
+	xselPasteArgs = []string{xsel, "--output", "--clipboard"}
+	xselCopyArgs  = []string{xsel, "--input", "--clipboard"}
+
+	xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
+	xclipCopyArgs  = []string{xclip, "-in", "-selection", "clipboard"}
+
+	powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
+	clipExeCopyArgs        = []string{clipExe}
+
+	wlpasteArgs = []string{wlpaste, "--no-newline"}
+	wlcopyArgs  = []string{wlcopy}
+
+	termuxPasteArgs = []string{termuxClipboardGet}
+	termuxCopyArgs  = []string{termuxClipboardSet}
+
+	missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
+)
+
+func init() {
+	if os.Getenv("WAYLAND_DISPLAY") != "" {
+		pasteCmdArgs = wlpasteArgs
+		copyCmdArgs = wlcopyArgs
+
+		if _, err := exec.LookPath(wlcopy); err == nil {
+			if _, err := exec.LookPath(wlpaste); err == nil {
+				return
+			}
+		}
+	}
+
+	pasteCmdArgs = xclipPasteArgs
+	copyCmdArgs = xclipCopyArgs
+
+	if _, err := exec.LookPath(xclip); err == nil {
+		return
+	}
+
+	pasteCmdArgs = xselPasteArgs
+	copyCmdArgs = xselCopyArgs
+
+	if _, err := exec.LookPath(xsel); err == nil {
+		return
+	}
+
+	pasteCmdArgs = termuxPasteArgs
+	copyCmdArgs = termuxCopyArgs
+
+	if _, err := exec.LookPath(termuxClipboardSet); err == nil {
+		if _, err := exec.LookPath(termuxClipboardGet); err == nil {
+			return
+		}
+	}
+
+	pasteCmdArgs = powershellExePasteArgs
+	copyCmdArgs = clipExeCopyArgs
+	trimDos = true
+
+	if _, err := exec.LookPath(clipExe); err == nil {
+		if _, err := exec.LookPath(powershellExe); err == nil {
+			return
+		}
+	}
+
+	Unsupported = true
+}
+
+func getPasteCommand() *exec.Cmd {
+	if Primary {
+		pasteCmdArgs = pasteCmdArgs[:1]
+	}
+	return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
+}
+
+func getCopyCommand() *exec.Cmd {
+	if Primary {
+		copyCmdArgs = copyCmdArgs[:1]
+	}
+	return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
+}
+
+func readAll() (string, error) {
+	if Unsupported {
+		return "", missingCommands
+	}
+	pasteCmd := getPasteCommand()
+	out, err := pasteCmd.Output()
+	if err != nil {
+		return "", err
+	}
+	result := string(out)
+	if trimDos && len(result) > 1 {
+		result = result[:len(result)-2]
+	}
+	return result, nil
+}
+
+func writeAll(text string) error {
+	if Unsupported {
+		return missingCommands
+	}
+	copyCmd := getCopyCommand()
+	in, err := copyCmd.StdinPipe()
+	if err != nil {
+		return err
+	}
+
+	if err := copyCmd.Start(); err != nil {
+		return err
+	}
+	if _, err := in.Write([]byte(text)); err != nil {
+		return err
+	}
+	if err := in.Close(); err != nil {
+		return err
+	}
+	return copyCmd.Wait()
+}
diff --git a/vendor/github.com/atotto/clipboard/clipboard_windows.go b/vendor/github.com/atotto/clipboard/clipboard_windows.go
new file mode 100644
index 000000000..253bb9322
--- /dev/null
+++ b/vendor/github.com/atotto/clipboard/clipboard_windows.go
@@ -0,0 +1,157 @@
+// Copyright 2013 @atotto. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package clipboard
+
+import (
+	"runtime"
+	"syscall"
+	"time"
+	"unsafe"
+)
+
+const (
+	cfUnicodetext = 13
+	gmemMoveable  = 0x0002
+)
+
+var (
+	user32                     = syscall.MustLoadDLL("user32")
+	isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
+	openClipboard              = user32.MustFindProc("OpenClipboard")
+	closeClipboard             = user32.MustFindProc("CloseClipboard")
+	emptyClipboard             = user32.MustFindProc("EmptyClipboard")
+	getClipboardData           = user32.MustFindProc("GetClipboardData")
+	setClipboardData           = user32.MustFindProc("SetClipboardData")
+
+	kernel32     = syscall.NewLazyDLL("kernel32")
+	globalAlloc  = kernel32.NewProc("GlobalAlloc")
+	globalFree   = kernel32.NewProc("GlobalFree")
+	globalLock   = kernel32.NewProc("GlobalLock")
+	globalUnlock = kernel32.NewProc("GlobalUnlock")
+	lstrcpy      = kernel32.NewProc("lstrcpyW")
+)
+
+// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
+func waitOpenClipboard() error {
+	started := time.Now()
+	limit := started.Add(time.Second)
+	var r uintptr
+	var err error
+	for time.Now().Before(limit) {
+		r, _, err = openClipboard.Call(0)
+		if r != 0 {
+			return nil
+		}
+		time.Sleep(time.Millisecond)
+	}
+	return err
+}
+
+func readAll() (string, error) {
+	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
+	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+	if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
+		return "", err
+	}
+	err := waitOpenClipboard()
+	if err != nil {
+		return "", err
+	}
+
+	h, _, err := getClipboardData.Call(cfUnicodetext)
+	if h == 0 {
+		_, _, _ = closeClipboard.Call()
+		return "", err
+	}
+
+	l, _, err := globalLock.Call(h)
+	if l == 0 {
+		_, _, _ = closeClipboard.Call()
+		return "", err
+	}
+
+	text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
+
+	r, _, err := globalUnlock.Call(h)
+	if r == 0 {
+		_, _, _ = closeClipboard.Call()
+		return "", err
+	}
+
+	closed, _, err := closeClipboard.Call()
+	if closed == 0 {
+		return "", err
+	}
+	return text, nil
+}
+
+func writeAll(text string) error {
+	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
+	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	err := waitOpenClipboard()
+	if err != nil {
+		return err
+	}
+
+	r, _, err := emptyClipboard.Call(0)
+	if r == 0 {
+		_, _, _ = closeClipboard.Call()
+		return err
+	}
+
+	data := syscall.StringToUTF16(text)
+
+	// "If the hMem parameter identifies a memory object, the object must have
+	// been allocated using the function with the GMEM_MOVEABLE flag."
+	h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
+	if h == 0 {
+		_, _, _ = closeClipboard.Call()
+		return err
+	}
+	defer func() {
+		if h != 0 {
+			globalFree.Call(h)
+		}
+	}()
+
+	l, _, err := globalLock.Call(h)
+	if l == 0 {
+		_, _, _ = closeClipboard.Call()
+		return err
+	}
+
+	r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
+	if r == 0 {
+		_, _, _ = closeClipboard.Call()
+		return err
+	}
+
+	r, _, err = globalUnlock.Call(h)
+	if r == 0 {
+		if err.(syscall.Errno) != 0 {
+			_, _, _ = closeClipboard.Call()
+			return err
+		}
+	}
+
+	r, _, err = setClipboardData.Call(cfUnicodetext, h)
+	if r == 0 {
+		_, _, _ = closeClipboard.Call()
+		return err
+	}
+	h = 0 // suppress deferred cleanup
+	closed, _, err := closeClipboard.Call()
+	if closed == 0 {
+		return err
+	}
+	return nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 448b93b85..e8fa5453d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -8,6 +8,9 @@ github.com/Microsoft/go-winio/pkg/guid
 # github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c
 ## explicit
 github.com/OpenPeeDeeP/xdg
+# github.com/atotto/clipboard v0.1.4
+## explicit
+github.com/atotto/clipboard
 # github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
 ## explicit
 github.com/boz/go-throttle