commit 3bcbae6565b4724264dfa7141a01afe1aef21efb
parent f6d427dc04c526bf59c34283c2128854a6a20a75
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 16 Jul 2022 15:12:32 -0400
moving clipboard to platform area
Diffstat:
10 files changed, 302 insertions(+), 222 deletions(-)
diff --git a/cmd/lb-totp/main.go b/cmd/lb-totp/main.go
@@ -11,11 +11,11 @@ import (
"time"
"github.com/enckse/lockbox/internal/cli"
- "github.com/enckse/lockbox/internal/clip"
"github.com/enckse/lockbox/internal/colors"
"github.com/enckse/lockbox/internal/encrypt"
"github.com/enckse/lockbox/internal/inputs"
"github.com/enckse/lockbox/internal/misc"
+ "github.com/enckse/lockbox/internal/platform"
"github.com/enckse/lockbox/internal/store"
otp "github.com/pquerna/otp/totp"
)
@@ -107,9 +107,9 @@ func display(token string, args cli.Arguments) error {
clear()
}
}
- clipboard := clip.Commands{}
+ clipboard := platform.Clipboard{}
if args.Clip {
- clipboard, err = clip.NewCommands()
+ clipboard, err = platform.NewClipboard()
if err != nil {
misc.Die("invalid clipboard", err)
}
diff --git a/cmd/lb/main.go b/cmd/lb/main.go
@@ -11,13 +11,13 @@ import (
"time"
"github.com/enckse/lockbox/internal/cli"
- "github.com/enckse/lockbox/internal/clip"
"github.com/enckse/lockbox/internal/colors"
"github.com/enckse/lockbox/internal/dump"
"github.com/enckse/lockbox/internal/encrypt"
"github.com/enckse/lockbox/internal/hooks"
"github.com/enckse/lockbox/internal/inputs"
"github.com/enckse/lockbox/internal/misc"
+ "github.com/enckse/lockbox/internal/platform"
"github.com/enckse/lockbox/internal/store"
)
@@ -181,9 +181,9 @@ func main() {
misc.Die("unable to get color for terminal", err)
}
dumpData := []dump.ExportEntity{}
- clipboard := clip.Commands{}
+ clipboard := platform.Clipboard{}
if !isShow {
- clipboard, err = clip.NewCommands()
+ clipboard, err = platform.NewClipboard()
if err != nil {
misc.Die("unable to get clipboard", err)
}
@@ -239,7 +239,7 @@ func main() {
if err != nil {
misc.Die("unable to read value to clear", err)
}
- clipboard, err := clip.NewCommands()
+ clipboard, err := platform.NewClipboard()
if err != nil {
misc.Die("unable to get paste command", err)
}
diff --git a/contrib/completions.bash b/contrib/completions.bash
@@ -11,8 +11,8 @@ _is_clip() {
_lb() {
local cur opts clip_enabled needs
clip_enabled=" -c clip"
- if [ -n "$LOCKBOX_CLIPMODE" ]; then
- if [ "$LOCKBOX_CLIPMODE" == "off" ]; then
+ if [ -n "$LOCKBOX_NOCLIP" ]; then
+ if [ "$LOCKBOX_NOCLIP" == "yes" ]; then
clip_enabled=""
fi
fi
diff --git a/internal/clip/clipboard.go b/internal/clip/clipboard.go
@@ -1,142 +0,0 @@
-package clip
-
-import (
- "errors"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/enckse/lockbox/internal/misc"
-)
-
-const (
- maxTime = 45
- pbClipMode = "pb"
- waylandClipMode = "wayland"
- xClipMode = "x11"
- wslMode = "wsl"
-)
-
-type (
- // Commands represent system clipboard operations.
- Commands struct {
- copying []string
- pasting []string
- MaxTime int
- }
-)
-
-// NewCommands will retrieve the commands to use for clipboard operations.
-func NewCommands() (Commands, error) {
- env := strings.TrimSpace(os.Getenv("LOCKBOX_CLIPMODE"))
- if env == "" {
- b, err := exec.Command("uname", "-a").Output()
- if err != nil {
- return Commands{}, err
- }
- raw := strings.TrimSpace(string(b))
- parts := strings.Split(raw, " ")
- switch parts[0] {
- case "Darwin":
- env = pbClipMode
- case "Linux":
- if strings.Contains(raw, "microsoft-standard-WSL2") {
- env = wslMode
- } else {
- if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
- if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
- return Commands{}, errors.New("unable to detect linux clipboard mode")
- }
- env = xClipMode
- } else {
- env = waylandClipMode
- }
- }
- default:
- return Commands{}, errors.New("unable to detect clipboard mode")
- }
- }
- max := maxTime
- useMax := os.Getenv("LOCKBOX_CLIPMAX")
- if useMax != "" {
- i, err := strconv.Atoi(useMax)
- if err != nil {
- return Commands{}, err
- }
- if i < 1 {
- return Commands{}, errors.New("clipboard max time must be greater than 0")
- }
- max = i
- }
- var copying []string
- var pasting []string
- switch env {
- case pbClipMode:
- copying = []string{"pbcopy"}
- pasting = []string{"pbpaste"}
- case xClipMode:
- copying = []string{"xclip"}
- pasting = []string{"xclip", "-o"}
- case waylandClipMode:
- copying = []string{"wl-copy"}
- pasting = []string{"wl-paste"}
- case wslMode:
- copying = []string{"clip.exe"}
- pasting = []string{"powershell.exe", "-command", "Get-Clipboard"}
- default:
- return Commands{}, errors.New("clipboard is unavailable")
- }
- return Commands{copying: copying, pasting: pasting, MaxTime: max}, nil
-}
-
-// CopyTo will copy to clipboard, if non-empty will clear later.
-func (c Commands) CopyTo(value, executable string) {
- cmd, args := c.Args(true)
- pipeTo(cmd, value, true, args...)
- if value != "" {
- fmt.Printf("clipboard will clear in %d seconds\n", c.MaxTime)
- pipeTo(filepath.Join(filepath.Dir(executable), "lb"), value, false, "clear")
- }
-}
-
-// Args returns clipboard args for execution.
-func (c Commands) Args(copying bool) (string, []string) {
- var using []string
- if copying {
- using = c.copying
- } else {
- using = c.pasting
- }
- var args []string
- if len(using) > 1 {
- args = using[1:]
- }
- return using[0], args
-}
-
-func pipeTo(command, value string, wait bool, args ...string) {
- cmd := exec.Command(command, args...)
- stdin, err := cmd.StdinPipe()
- if err != nil {
- misc.Die("unable to get stdin pipe", err)
- }
-
- go func() {
- defer stdin.Close()
- if _, err := stdin.Write([]byte(value)); err != nil {
- fmt.Printf("failed writing to stdin: %v\n", err)
- }
- }()
- var ran error
- if wait {
- ran = cmd.Run()
- } else {
- ran = cmd.Start()
- }
- if ran != nil {
- misc.Die("failed to run command", ran)
- }
-}
diff --git a/internal/clip/clipboard_test.go b/internal/clip/clipboard_test.go
@@ -1,71 +0,0 @@
-package clip
-
-import (
- "os"
- "testing"
-)
-
-func TestNoClipboard(t *testing.T) {
- os.Setenv("LOCKBOX_CLIPMAX", "")
- os.Setenv("LOCKBOX_CLIPMODE", "off")
- _, err := NewCommands()
- if err == nil || err.Error() != "clipboard is unavailable" {
- t.Errorf("invalid error: %v", err)
- }
-}
-
-func TestMaxTime(t *testing.T) {
- os.Setenv("LOCKBOX_CLIPMODE", pbClipMode)
- os.Setenv("LOCKBOX_CLIPMAX", "")
- c, err := NewCommands()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if c.MaxTime != 45 {
- t.Error("invalid default")
- }
- os.Setenv("LOCKBOX_CLIPMAX", "1")
- c, err = NewCommands()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if c.MaxTime != 1 {
- t.Error("invalid default")
- }
- os.Setenv("LOCKBOX_CLIPMAX", "-1")
- _, err = NewCommands()
- if err == nil || err.Error() != "clipboard max time must be greater than 0" {
- t.Errorf("invalid max time error: %v", err)
- }
- os.Setenv("LOCKBOX_CLIPMAX", "$&(+")
- _, err = NewCommands()
- if err == nil || err.Error() != "strconv.Atoi: parsing \"$&(+\": invalid syntax" {
- t.Errorf("invalid max time error: %v", err)
- }
-}
-
-func TestClipboardInstances(t *testing.T) {
- os.Setenv("LOCKBOX_CLIPMAX", "")
- for _, item := range []string{pbClipMode, xClipMode, waylandClipMode, wslMode} {
- os.Setenv("LOCKBOX_CLIPMODE", item)
- c, err := NewCommands()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if len(c.copying) == 0 || len(c.pasting) == 0 {
- t.Error("invalid command retrieved")
- }
- }
-}
-
-func TestArgs(t *testing.T) {
- c := Commands{copying: []string{"cp"}, pasting: []string{"paste", "with", "args"}}
- cmd, args := c.Args(true)
- if cmd != "cp" || len(args) != 0 {
- t.Error("invalid parse")
- }
- cmd, args = c.Args(false)
- if cmd != "paste" || len(args) != 2 || args[0] != "with" || args[1] != "args" {
- t.Error("invalid parse")
- }
-}
diff --git a/internal/inputs/env.go b/internal/inputs/env.go
@@ -20,6 +20,11 @@ func isYesNoEnv(defaultValue bool, env string) (bool, error) {
return false, fmt.Errorf("invalid yes/no env value for %s", env)
}
+// IsNoClipEnabled indicates if clipboard mode is enabled.
+func IsNoClipEnabled() (bool, error) {
+ return isYesNoEnv(false, "LOCKBOX_NOCLIP")
+}
+
// IsNoColorEnabled indicates if the flag is set to disable color.
func IsNoColorEnabled() (bool, error) {
return isYesNoEnv(false, "LOCKBOX_NOCOLOR")
diff --git a/internal/platform/clipboard.go b/internal/platform/clipboard.go
@@ -0,0 +1,121 @@
+package platform
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+
+ "github.com/enckse/lockbox/internal/inputs"
+ "github.com/enckse/lockbox/internal/misc"
+)
+
+const (
+ maxTime = 45
+)
+
+type (
+ // Clipboard represent system clipboard operations.
+ Clipboard struct {
+ copying []string
+ pasting []string
+ MaxTime int
+ }
+)
+
+// NewClipboard will retrieve the commands to use for clipboard operations.
+func NewClipboard() (Clipboard, error) {
+ noClip, err := inputs.IsNoClipEnabled()
+ if err != nil {
+ return Clipboard{}, err
+ }
+ if noClip {
+ return Clipboard{}, errors.New("clipboard is off")
+ }
+ sys, err := NewPlatform()
+ if err != nil {
+ return Clipboard{}, err
+ }
+ max := maxTime
+ useMax := os.Getenv("LOCKBOX_CLIPMAX")
+ if useMax != "" {
+ i, err := strconv.Atoi(useMax)
+ if err != nil {
+ return Clipboard{}, err
+ }
+ if i < 1 {
+ return Clipboard{}, errors.New("clipboard max time must be greater than 0")
+ }
+ max = i
+ }
+ var copying []string
+ var pasting []string
+ switch sys {
+ case MacOS:
+ copying = []string{"pbcopy"}
+ pasting = []string{"pbpaste"}
+ case LinuxX:
+ copying = []string{"xclip"}
+ pasting = []string{"xclip", "-o"}
+ case LinuxWayland:
+ copying = []string{"wl-copy"}
+ pasting = []string{"wl-paste"}
+ case WindowsLinux:
+ copying = []string{"clip.exe"}
+ pasting = []string{"powershell.exe", "-command", "Get-Clipboard"}
+ default:
+ return Clipboard{}, errors.New("clipboard is unavailable")
+ }
+ return Clipboard{copying: copying, pasting: pasting, MaxTime: max}, nil
+}
+
+// CopyTo will copy to clipboard, if non-empty will clear later.
+func (c Clipboard) CopyTo(value, executable string) {
+ cmd, args := c.Args(true)
+ pipeTo(cmd, value, true, args...)
+ if value != "" {
+ fmt.Printf("clipboard will clear in %d seconds\n", c.MaxTime)
+ pipeTo(filepath.Join(filepath.Dir(executable), "lb"), value, false, "clear")
+ }
+}
+
+// Args returns clipboard args for execution.
+func (c Clipboard) Args(copying bool) (string, []string) {
+ var using []string
+ if copying {
+ using = c.copying
+ } else {
+ using = c.pasting
+ }
+ var args []string
+ if len(using) > 1 {
+ args = using[1:]
+ }
+ return using[0], args
+}
+
+func pipeTo(command, value string, wait bool, args ...string) {
+ cmd := exec.Command(command, args...)
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ misc.Die("unable to get stdin pipe", err)
+ }
+
+ go func() {
+ defer stdin.Close()
+ if _, err := stdin.Write([]byte(value)); err != nil {
+ fmt.Printf("failed writing to stdin: %v\n", err)
+ }
+ }()
+ var ran error
+ if wait {
+ ran = cmd.Run()
+ } else {
+ ran = cmd.Start()
+ }
+ if ran != nil {
+ misc.Die("failed to run command", ran)
+ }
+}
diff --git a/internal/platform/clipboard_test.go b/internal/platform/clipboard_test.go
@@ -0,0 +1,73 @@
+package platform
+
+import (
+ "os"
+ "testing"
+)
+
+func TestNoClipboard(t *testing.T) {
+ os.Setenv("LOCKBOX_CLIPMAX", "")
+ os.Setenv("LOCKBOX_NOCLIP", "yes")
+ _, err := NewClipboard()
+ if err == nil || err.Error() != "clipboard is off" {
+ t.Errorf("invalid error: %v", err)
+ }
+}
+
+func TestMaxTime(t *testing.T) {
+ os.Setenv("LOCKBOX_NOCLIP", "no")
+ os.Setenv("LOCKBOX_PLATFORM", string(LinuxWayland))
+ os.Setenv("LOCKBOX_CLIPMAX", "")
+ c, err := NewClipboard()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if c.MaxTime != 45 {
+ t.Error("invalid default")
+ }
+ os.Setenv("LOCKBOX_CLIPMAX", "1")
+ c, err = NewClipboard()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if c.MaxTime != 1 {
+ t.Error("invalid default")
+ }
+ os.Setenv("LOCKBOX_CLIPMAX", "-1")
+ _, err = NewClipboard()
+ if err == nil || err.Error() != "clipboard max time must be greater than 0" {
+ t.Errorf("invalid max time error: %v", err)
+ }
+ os.Setenv("LOCKBOX_CLIPMAX", "$&(+")
+ _, err = NewClipboard()
+ if err == nil || err.Error() != "strconv.Atoi: parsing \"$&(+\": invalid syntax" {
+ t.Errorf("invalid max time error: %v", err)
+ }
+}
+
+func TestClipboardInstances(t *testing.T) {
+ os.Setenv("LOCKBOX_NOCLIP", "no")
+ os.Setenv("LOCKBOX_CLIPMAX", "")
+ for _, item := range []System{MacOS, LinuxWayland, LinuxX, WindowsLinux} {
+ os.Setenv("LOCKBOX_PLATFORM", string(item))
+ c, err := NewClipboard()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if len(c.copying) == 0 || len(c.pasting) == 0 {
+ t.Error("invalid command retrieved")
+ }
+ }
+}
+
+func TestArgs(t *testing.T) {
+ c := Clipboard{copying: []string{"cp"}, pasting: []string{"paste", "with", "args"}}
+ cmd, args := c.Args(true)
+ if cmd != "cp" || len(args) != 0 {
+ t.Error("invalid parse")
+ }
+ cmd, args = c.Args(false)
+ if cmd != "paste" || len(args) != 2 || args[0] != "with" || args[1] != "args" {
+ t.Error("invalid parse")
+ }
+}
diff --git a/internal/platform/core.go b/internal/platform/core.go
@@ -0,0 +1,67 @@
+package platform
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+type (
+ // System represents the platform lockbox is running on.
+ System string
+)
+
+const (
+ // MacOS based systems.
+ MacOS System = "macos"
+ // LinuxWayland running Wayland.
+ LinuxWayland System = "linux-wayland"
+ // LinuxX running X.
+ LinuxX System = "linux-x"
+ // WindowsLinux with WSL.
+ WindowsLinux System = "wsl"
+ // Unknown platform.
+ Unknown = ""
+)
+
+// NewPlatform gets a new system platform.
+func NewPlatform() (System, error) {
+ env := os.Getenv("LOCKBOX_PLATFORM")
+ if env != "" {
+ switch env {
+ case string(MacOS):
+ return MacOS, nil
+ case string(LinuxWayland):
+ return LinuxWayland, nil
+ case string(WindowsLinux):
+ return WindowsLinux, nil
+ case string(LinuxX):
+ return LinuxX, nil
+ default:
+ return Unknown, errors.New("unknown platform mode")
+ }
+ }
+ b, err := exec.Command("uname", "-a").Output()
+ if err != nil {
+ return Unknown, err
+ }
+ raw := strings.TrimSpace(string(b))
+ parts := strings.Split(raw, " ")
+ switch parts[0] {
+ case "Darwin":
+ return MacOS, nil
+ case "Linux":
+ if strings.Contains(raw, "microsoft-standard-WSL2") {
+ return WindowsLinux, nil
+ }
+ if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
+ if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
+ return Unknown, errors.New("unable to detect linux clipboard mode")
+ }
+ return LinuxX, nil
+ }
+ return LinuxWayland, nil
+ }
+ return Unknown, errors.New("unable to detect clipboard mode")
+}
diff --git a/internal/platform/core_test.go b/internal/platform/core_test.go
@@ -0,0 +1,27 @@
+package platform
+
+import (
+ "os"
+ "testing"
+)
+
+func TestNewPlatform(t *testing.T) {
+ for _, item := range []System{MacOS, LinuxWayland, LinuxX, WindowsLinux} {
+ os.Setenv("LOCKBOX_PLATFORM", string(item))
+ s, err := NewPlatform()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if s != item {
+ t.Error("mismatch on input and resulting detection")
+ }
+ }
+}
+
+func TestNewPlatformUnknown(t *testing.T) {
+ os.Setenv("LOCKBOX_PLATFORM", "afleaj")
+ _, err := NewPlatform()
+ if err == nil || err.Error() != "unknown platform mode" {
+ t.Errorf("error expected for platform: %v", err)
+ }
+}