commit 7d9e7a76cf679d71242ff2ae4c5f0ef94794b4c8
parent 536918d133e3035fa1f14a7eef378c3c1220cba1
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 7 Dec 2024 14:24:36 -0500
move clipboard under platform
Diffstat:
16 files changed, 414 insertions(+), 389 deletions(-)
diff --git a/cmd/main.go b/cmd/main.go
@@ -12,6 +12,7 @@ import (
"github.com/seanenck/lockbox/internal/app"
"github.com/seanenck/lockbox/internal/config"
"github.com/seanenck/lockbox/internal/platform"
+ "github.com/seanenck/lockbox/internal/platform/clip"
)
var version string
@@ -110,7 +111,7 @@ func clearClipboard() error {
if err != nil {
return err
}
- clipboard, err := platform.NewClipboard()
+ clipboard, err := clip.New()
if err != nil {
return err
}
diff --git a/internal/app/showclip.go b/internal/app/showclip.go
@@ -6,7 +6,7 @@ import (
"fmt"
"github.com/seanenck/lockbox/internal/backend"
- "github.com/seanenck/lockbox/internal/platform"
+ "github.com/seanenck/lockbox/internal/platform/clip"
)
// ShowClip will handle showing/clipping an entry
@@ -16,10 +16,10 @@ func ShowClip(cmd CommandOptions, isShow bool) error {
return errors.New("only one argument supported")
}
entry := args[0]
- clipboard := platform.Clipboard{}
+ clipboard := clip.Board{}
if !isShow {
var err error
- clipboard, err = platform.NewClipboard()
+ clipboard, err = clip.New()
if err != nil {
return fmt.Errorf("unable to get clipboard: %w", err)
}
diff --git a/internal/app/totp.go b/internal/app/totp.go
@@ -13,7 +13,7 @@ import (
"github.com/seanenck/lockbox/internal/backend"
"github.com/seanenck/lockbox/internal/config"
"github.com/seanenck/lockbox/internal/core"
- "github.com/seanenck/lockbox/internal/platform"
+ "github.com/seanenck/lockbox/internal/platform/clip"
)
var (
@@ -97,8 +97,8 @@ func (args *TOTPArguments) display(opts TOTPOptions) error {
interactive = false
}
once := args.Mode == OnceTOTPMode
- clip := args.Mode == ClipTOTPMode
- if !interactive && clip {
+ clipMode := args.Mode == ClipTOTPMode
+ if !interactive && clipMode {
return errors.New("clipboard not available in non-interactive mode")
}
entity, err := opts.app.Transaction().Get(backend.NewPath(args.Entry, args.token), backend.SecretValue)
@@ -131,14 +131,14 @@ func (args *TOTPArguments) display(opts TOTPOptions) error {
first := true
running := 0
lastSecond := -1
- if !clip {
+ if !clipMode {
if !once {
opts.Clear()
}
}
- clipboard := platform.Clipboard{}
- if clip {
- clipboard, err = platform.NewClipboard()
+ clipboard := clip.Board{}
+ if clipMode {
+ clipboard, err = clip.New()
if err != nil {
return err
}
@@ -193,7 +193,7 @@ func (args *TOTPArguments) display(opts TOTPOptions) error {
txt = fmt.Sprintf("\x1b[31m%s\x1b[39m", txt)
}
outputs := []string{txt}
- if !clip {
+ if !clipMode {
outputs = append(outputs, fmt.Sprintf("%s\n %s", args.Entry, code))
if !once {
outputs = append(outputs, "-> CTRL+C to exit")
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/seanenck/lockbox/internal/core"
+ "github.com/seanenck/lockbox/internal/platform"
)
var (
@@ -132,7 +133,7 @@ var (
subKey: "PLATFORM",
desc: "Override the detected platform.",
}),
- allowed: core.Platforms.List(),
+ allowed: platform.Systems.List(),
canDefault: false,
})
// EnvStore is the location of the keepass file/store
diff --git a/internal/core/core.go b/internal/core/core.go
@@ -7,11 +7,12 @@ import (
"sort"
)
-func listFields[T SystemPlatform | JSONOutputMode](p any) []string {
+// ListFields will get the values of strings on an "all string" struct
+func ListFields(p any) []string {
v := reflect.ValueOf(p)
var vals []string
for i := 0; i < v.NumField(); i++ {
- vals = append(vals, fmt.Sprintf("%v", v.Field(i).Interface().(T)))
+ vals = append(vals, fmt.Sprintf("%v", v.Field(i).Interface()))
}
sort.Strings(vals)
return vals
diff --git a/internal/core/core_test.go b/internal/core/core_test.go
@@ -0,0 +1,20 @@
+package core_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/core"
+)
+
+type mock struct {
+ Name string
+ Field string
+}
+
+func TestListFields(t *testing.T) {
+ fields := core.ListFields(mock{"abc", "xyz"})
+ if len(fields) != 2 || fmt.Sprintf("%v", fields) != "[abc xyz]" {
+ t.Errorf("invalid fields: %v", fields)
+ }
+}
diff --git a/internal/core/json.go b/internal/core/json.go
@@ -27,7 +27,7 @@ type (
// List will list the output modes on the struct
func (p JSONOutputTypes) List() []string {
- return listFields[JSONOutputMode](p)
+ return ListFields(p)
}
// ParseJSONOutput handles detecting the JSON output mode
diff --git a/internal/core/json_test.go b/internal/core/json_test.go
@@ -1,14 +1,16 @@
package core_test
import (
+ "fmt"
"testing"
"github.com/seanenck/lockbox/internal/core"
)
func TestJSONList(t *testing.T) {
- if len(core.JSONOutputs.List()) != 3 {
- t.Errorf("invalid list result")
+ list := core.JSONOutputs.List()
+ if len(list) != 3 || fmt.Sprintf("%v", list) != "[empty hash plaintext]" {
+ t.Errorf("invalid list result: %v", list)
}
}
diff --git a/internal/core/platforms.go b/internal/core/platforms.go
@@ -1,72 +0,0 @@
-// Package core defines known platforms
-package core
-
-import (
- "errors"
- "os"
- "os/exec"
- "strings"
-)
-
-// Platforms are the known platforms for lockbox
-var Platforms = PlatformTypes{
- MacOSPlatform: "macos",
- LinuxWaylandPlatform: "linux-wayland",
- LinuxXPlatform: "linux-x",
- WindowsLinuxPlatform: "wsl",
-}
-
-const unknownPlatform = ""
-
-type (
- // SystemPlatform represents the platform lockbox is running on.
- SystemPlatform string
-
- // PlatformTypes defines systems lockbox is known to run on or can run on
- PlatformTypes struct {
- MacOSPlatform SystemPlatform
- LinuxWaylandPlatform SystemPlatform
- LinuxXPlatform SystemPlatform
- WindowsLinuxPlatform SystemPlatform
- }
-)
-
-// List will list the platform types on the struct
-func (p PlatformTypes) List() []string {
- return listFields[SystemPlatform](p)
-}
-
-// NewPlatform gets a new system platform.
-func NewPlatform(candidate string) (SystemPlatform, error) {
- env := candidate
- if env != "" {
- for _, p := range Platforms.List() {
- if p == env {
- return SystemPlatform(p), nil
- }
- }
- return unknownPlatform, errors.New("unknown platform mode")
- }
- b, err := exec.Command("uname", "-a").Output()
- if err != nil {
- return unknownPlatform, err
- }
- raw := strings.ToLower(strings.TrimSpace(string(b)))
- parts := strings.Split(raw, " ")
- switch parts[0] {
- case "darwin":
- return Platforms.MacOSPlatform, nil
- case "linux":
- if strings.Contains(raw, "microsoft-standard-wsl") {
- return Platforms.WindowsLinuxPlatform, nil
- }
- if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
- if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
- return unknownPlatform, errors.New("unable to detect linux clipboard mode")
- }
- return Platforms.LinuxXPlatform, nil
- }
- return Platforms.LinuxWaylandPlatform, nil
- }
- return unknownPlatform, errors.New("unable to detect clipboard mode")
-}
diff --git a/internal/core/platforms_test.go b/internal/core/platforms_test.go
@@ -1,32 +0,0 @@
-package core_test
-
-import (
- "testing"
-
- "github.com/seanenck/lockbox/internal/core"
-)
-
-func TestPlatformList(t *testing.T) {
- if len(core.Platforms.List()) != 4 {
- t.Errorf("invalid list result")
- }
-}
-
-func TestNewPlatform(t *testing.T) {
- for _, item := range core.Platforms.List() {
- s, err := core.NewPlatform(item)
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if s != core.SystemPlatform(item) {
- t.Error("mismatch on input and resulting detection")
- }
- }
-}
-
-func TestNewPlatformUnknown(t *testing.T) {
- _, err := core.NewPlatform("ifjoajei")
- if err == nil || err.Error() != "unknown platform mode" {
- t.Errorf("error expected for platform: %v", err)
- }
-}
diff --git a/internal/platform/clip/core.go b/internal/platform/clip/core.go
@@ -0,0 +1,153 @@
+// Package clip handles platform-specific operations around clipboards.
+package clip
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+
+ osc "github.com/aymanbagabas/go-osc52"
+ "github.com/seanenck/lockbox/internal/config"
+ "github.com/seanenck/lockbox/internal/platform"
+)
+
+type (
+ // Board represent system clipboard operations.
+ Board struct {
+ copying []string
+ pasting []string
+ MaxTime int
+ isOSC52 bool
+ }
+)
+
+func newBoard(copying, pasting []string) (Board, error) {
+ maximum, err := config.EnvClipTimeout.Get()
+ if err != nil {
+ return Board{}, err
+ }
+ return Board{copying: copying, pasting: pasting, MaxTime: maximum, isOSC52: false}, nil
+}
+
+// New will retrieve the commands to use for clipboard operations.
+func New() (Board, error) {
+ canClip, err := config.EnvClipEnabled.Get()
+ if err != nil {
+ return Board{}, err
+ }
+ if !canClip {
+ return Board{}, errors.New("clipboard is off")
+ }
+ overridePaste, err := config.EnvClipPaste.Get()
+ if err != nil {
+ return Board{}, err
+ }
+ overrideCopy, err := config.EnvClipCopy.Get()
+ if err != nil {
+ return Board{}, err
+ }
+ if overrideCopy != nil && overridePaste != nil {
+ return newBoard(overrideCopy, overridePaste)
+ }
+ isOSC, err := config.EnvClipOSC52.Get()
+ if err != nil {
+ return Board{}, err
+ }
+ if isOSC {
+ c := Board{isOSC52: true}
+ return c, nil
+ }
+ sys, err := platform.NewSystem(config.EnvPlatform.Get())
+ if err != nil {
+ return Board{}, err
+ }
+
+ var copying []string
+ var pasting []string
+ switch sys {
+ case platform.Systems.MacOSSystem:
+ copying = []string{"pbcopy"}
+ pasting = []string{"pbpaste"}
+ case platform.Systems.LinuxXSystem:
+ copying = []string{"xclip"}
+ pasting = []string{"xclip", "-o"}
+ case platform.Systems.LinuxWaylandSystem:
+ copying = []string{"wl-copy"}
+ pasting = []string{"wl-paste"}
+ case platform.Systems.WindowsLinuxSystem:
+ copying = []string{"clip.exe"}
+ pasting = []string{"powershell.exe", "-command", "Get-Clipboard"}
+ default:
+ return Board{}, errors.New("clipboard is unavailable")
+ }
+ if overridePaste != nil {
+ pasting = overridePaste
+ }
+ if overrideCopy != nil {
+ copying = overrideCopy
+ }
+ return newBoard(copying, pasting)
+}
+
+// CopyTo will copy to clipboard, if non-empty will clear later.
+func (c Board) CopyTo(value string) error {
+ if c.isOSC52 {
+ osc.Copy(value)
+ return nil
+ }
+ cmd, args, _ := c.Args(true)
+ pipeTo(cmd, value, true, args...)
+ if value != "" {
+ fmt.Printf("clipboard will clear in %d seconds\n", c.MaxTime)
+ exe, err := os.Executable()
+ if err != nil {
+ return err
+ }
+ pipeTo(exe, value, false, "clear")
+ }
+ return nil
+}
+
+// Args returns clipboard args for execution.
+func (c Board) Args(copying bool) (string, []string, bool) {
+ if c.isOSC52 {
+ return "", []string{}, false
+ }
+ 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, true
+}
+
+func pipeTo(command, value string, wait bool, args ...string) error {
+ cmd := exec.Command(command, args...)
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ return 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 {
+ return errors.New("failed to run command")
+ }
+ return nil
+}
diff --git a/internal/platform/clip/core_test.go b/internal/platform/clip/core_test.go
@@ -0,0 +1,112 @@
+package clip_test
+
+import (
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/platform"
+ "github.com/seanenck/lockbox/internal/platform/clip"
+)
+
+func TestNoClipboard(t *testing.T) {
+ t.Setenv("LOCKBOX_CLIP_OSC52", "false")
+ t.Setenv("LOCKBOX_CLIP_TIMEOUT", "")
+ t.Setenv("LOCKBOX_CLIP_ENABLED", "false")
+ _, err := clip.New()
+ if err == nil || err.Error() != "clipboard is off" {
+ t.Errorf("invalid error: %v", err)
+ }
+}
+
+func TestMaxTime(t *testing.T) {
+ t.Setenv("LOCKBOX_CLIP_ENABLED", "true")
+ t.Setenv("LOCKBOX_CLIP_OSC52", "false")
+ t.Setenv("LOCKBOX_PLATFORM", string(platform.Systems.LinuxWaylandSystem))
+ t.Setenv("LOCKBOX_CLIP_TIMEOUT", "")
+ c, err := clip.New()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if c.MaxTime != 45 {
+ t.Error("invalid default")
+ }
+ t.Setenv("LOCKBOX_CLIP_TIMEOUT", "1")
+ c, err = clip.New()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if c.MaxTime != 1 {
+ t.Error("invalid default")
+ }
+ t.Setenv("LOCKBOX_CLIP_TIMEOUT", "-1")
+ _, err = clip.New()
+ if err == nil || err.Error() != "clipboard max time must be > 0" {
+ t.Errorf("invalid max time error: %v", err)
+ }
+ t.Setenv("LOCKBOX_CLIP_TIMEOUT", "$&(+")
+ _, err = clip.New()
+ if err == nil || err.Error() != "strconv.Atoi: parsing \"$&(+\": invalid syntax" {
+ t.Errorf("invalid max time error: %v", err)
+ }
+}
+
+func TestClipboardInstances(t *testing.T) {
+ t.Setenv("LOCKBOX_CLIP_ENABLED", "true")
+ t.Setenv("LOCKBOX_CLIP_TIMEOUT", "")
+ t.Setenv("LOCKBOX_CLIP_OSC52", "false")
+ for _, item := range platform.Systems.List() {
+ t.Setenv("LOCKBOX_PLATFORM", item)
+ _, err := clip.New()
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ }
+}
+
+func TestOSC52(t *testing.T) {
+ t.Setenv("LOCKBOX_CLIP_OSC52", "true")
+ c, _ := clip.New()
+ _, _, ok := c.Args(true)
+ if ok {
+ t.Error("invalid clipboard, should be an internal call")
+ }
+ _, _, ok = c.Args(false)
+ if ok {
+ t.Error("invalid clipboard, should be an internal call")
+ }
+}
+
+func TestArgsOverride(t *testing.T) {
+ t.Setenv("LOCKBOX_CLIP_PASTE_COMMAND", "abc xyz 111")
+ t.Setenv("LOCKBOX_CLIP_OSC52", "false")
+ t.Setenv("LOCKBOX_PLATFORM", string(platform.Systems.WindowsLinuxSystem))
+ c, _ := clip.New()
+ cmd, args, ok := c.Args(true)
+ if cmd != "clip.exe" || len(args) != 0 || !ok {
+ t.Error("invalid parse")
+ }
+ cmd, args, ok = c.Args(false)
+ if cmd != "abc" || len(args) != 2 || args[0] != "xyz" || args[1] != "111" || !ok {
+ t.Error("invalid parse")
+ }
+ t.Setenv("LOCKBOX_CLIP_COPY_COMMAND", "zzz lll 123")
+ c, _ = clip.New()
+ cmd, args, ok = c.Args(true)
+ if cmd != "zzz" || len(args) != 2 || args[0] != "lll" || args[1] != "123" || !ok {
+ t.Error("invalid parse")
+ }
+ cmd, args, ok = c.Args(false)
+ if cmd != "abc" || len(args) != 2 || args[0] != "xyz" || args[1] != "111" || !ok {
+ t.Error("invalid parse")
+ }
+ t.Setenv("LOCKBOX_CLIP_PASTE_COMMAND", "")
+ t.Setenv("LOCKBOX_CLIP_COPY_COMMAND", "")
+ c, _ = clip.New()
+ cmd, args, ok = c.Args(true)
+ if cmd != "clip.exe" || len(args) != 0 || !ok {
+ t.Error("invalid parse")
+ }
+ cmd, args, ok = c.Args(false)
+ if cmd != "powershell.exe" || len(args) != 2 || args[0] != "-command" || args[1] != "Get-Clipboard" || !ok {
+ t.Errorf("invalid parse %s %v", cmd, args)
+ }
+}
diff --git a/internal/platform/clipboard.go b/internal/platform/clipboard.go
@@ -1,153 +0,0 @@
-// Package platform handles platform-specific operations around clipboards.
-package platform
-
-import (
- "errors"
- "fmt"
- "os"
- "os/exec"
-
- osc "github.com/aymanbagabas/go-osc52"
- "github.com/seanenck/lockbox/internal/config"
- "github.com/seanenck/lockbox/internal/core"
-)
-
-type (
- // Clipboard represent system clipboard operations.
- Clipboard struct {
- copying []string
- pasting []string
- MaxTime int
- isOSC52 bool
- }
-)
-
-func newClipboard(copying, pasting []string) (Clipboard, error) {
- maximum, err := config.EnvClipTimeout.Get()
- if err != nil {
- return Clipboard{}, err
- }
- return Clipboard{copying: copying, pasting: pasting, MaxTime: maximum, isOSC52: false}, nil
-}
-
-// NewClipboard will retrieve the commands to use for clipboard operations.
-func NewClipboard() (Clipboard, error) {
- canClip, err := config.EnvClipEnabled.Get()
- if err != nil {
- return Clipboard{}, err
- }
- if !canClip {
- return Clipboard{}, errors.New("clipboard is off")
- }
- overridePaste, err := config.EnvClipPaste.Get()
- if err != nil {
- return Clipboard{}, err
- }
- overrideCopy, err := config.EnvClipCopy.Get()
- if err != nil {
- return Clipboard{}, err
- }
- if overrideCopy != nil && overridePaste != nil {
- return newClipboard(overrideCopy, overridePaste)
- }
- isOSC, err := config.EnvClipOSC52.Get()
- if err != nil {
- return Clipboard{}, err
- }
- if isOSC {
- c := Clipboard{isOSC52: true}
- return c, nil
- }
- sys, err := core.NewPlatform(config.EnvPlatform.Get())
- if err != nil {
- return Clipboard{}, err
- }
-
- var copying []string
- var pasting []string
- switch sys {
- case core.Platforms.MacOSPlatform:
- copying = []string{"pbcopy"}
- pasting = []string{"pbpaste"}
- case core.Platforms.LinuxXPlatform:
- copying = []string{"xclip"}
- pasting = []string{"xclip", "-o"}
- case core.Platforms.LinuxWaylandPlatform:
- copying = []string{"wl-copy"}
- pasting = []string{"wl-paste"}
- case core.Platforms.WindowsLinuxPlatform:
- copying = []string{"clip.exe"}
- pasting = []string{"powershell.exe", "-command", "Get-Clipboard"}
- default:
- return Clipboard{}, errors.New("clipboard is unavailable")
- }
- if overridePaste != nil {
- pasting = overridePaste
- }
- if overrideCopy != nil {
- copying = overrideCopy
- }
- return newClipboard(copying, pasting)
-}
-
-// CopyTo will copy to clipboard, if non-empty will clear later.
-func (c Clipboard) CopyTo(value string) error {
- if c.isOSC52 {
- osc.Copy(value)
- return nil
- }
- cmd, args, _ := c.Args(true)
- pipeTo(cmd, value, true, args...)
- if value != "" {
- fmt.Printf("clipboard will clear in %d seconds\n", c.MaxTime)
- exe, err := os.Executable()
- if err != nil {
- return err
- }
- pipeTo(exe, value, false, "clear")
- }
- return nil
-}
-
-// Args returns clipboard args for execution.
-func (c Clipboard) Args(copying bool) (string, []string, bool) {
- if c.isOSC52 {
- return "", []string{}, false
- }
- 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, true
-}
-
-func pipeTo(command, value string, wait bool, args ...string) error {
- cmd := exec.Command(command, args...)
- stdin, err := cmd.StdinPipe()
- if err != nil {
- return 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 {
- return errors.New("failed to run command")
- }
- return nil
-}
diff --git a/internal/platform/clipboard_test.go b/internal/platform/clipboard_test.go
@@ -1,114 +0,0 @@
-package platform_test
-
-import (
- "fmt"
- "testing"
-
- "github.com/seanenck/lockbox/internal/core"
- "github.com/seanenck/lockbox/internal/platform"
-)
-
-func TestNoClipboard(t *testing.T) {
- t.Setenv("LOCKBOX_CLIP_OSC52", "false")
- t.Setenv("LOCKBOX_CLIP_TIMEOUT", "")
- t.Setenv("LOCKBOX_CLIP_ENABLED", "false")
- _, err := platform.NewClipboard()
- if err == nil || err.Error() != "clipboard is off" {
- t.Errorf("invalid error: %v", err)
- }
-}
-
-func TestMaxTime(t *testing.T) {
- t.Setenv("LOCKBOX_CLIP_ENABLED", "true")
- t.Setenv("LOCKBOX_CLIP_OSC52", "false")
- t.Setenv("LOCKBOX_PLATFORM", string(core.Platforms.LinuxWaylandPlatform))
- t.Setenv("LOCKBOX_CLIP_TIMEOUT", "")
- c, err := platform.NewClipboard()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if c.MaxTime != 45 {
- t.Error("invalid default")
- }
- t.Setenv("LOCKBOX_CLIP_TIMEOUT", "1")
- c, err = platform.NewClipboard()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- if c.MaxTime != 1 {
- t.Error("invalid default")
- }
- t.Setenv("LOCKBOX_CLIP_TIMEOUT", "-1")
- _, err = platform.NewClipboard()
- if err == nil || err.Error() != "clipboard max time must be > 0" {
- t.Errorf("invalid max time error: %v", err)
- }
- t.Setenv("LOCKBOX_CLIP_TIMEOUT", "$&(+")
- _, err = platform.NewClipboard()
- if err == nil || err.Error() != "strconv.Atoi: parsing \"$&(+\": invalid syntax" {
- t.Errorf("invalid max time error: %v", err)
- }
-}
-
-func TestClipboardInstances(t *testing.T) {
- t.Setenv("LOCKBOX_CLIP_ENABLED", "true")
- t.Setenv("LOCKBOX_CLIP_TIMEOUT", "")
- t.Setenv("LOCKBOX_CLIP_OSC52", "false")
- for _, item := range core.Platforms.List() {
- t.Setenv("LOCKBOX_PLATFORM", item)
- _, err := platform.NewClipboard()
- if err != nil {
- t.Errorf("invalid clipboard: %v", err)
- }
- }
-}
-
-func TestOSC52(t *testing.T) {
- t.Setenv("LOCKBOX_CLIP_OSC52", "true")
- c, _ := platform.NewClipboard()
- _, _, ok := c.Args(true)
- if ok {
- t.Error("invalid clipboard, should be an internal call")
- }
- _, _, ok = c.Args(false)
- if ok {
- t.Error("invalid clipboard, should be an internal call")
- }
-}
-
-func TestArgsOverride(t *testing.T) {
- t.Setenv("LOCKBOX_CLIP_PASTE_COMMAND", "abc xyz 111")
- t.Setenv("LOCKBOX_CLIP_OSC52", "false")
- t.Setenv("LOCKBOX_PLATFORM", string(core.Platforms.WindowsLinuxPlatform))
- c, _ := platform.NewClipboard()
- cmd, args, ok := c.Args(true)
- if cmd != "clip.exe" || len(args) != 0 || !ok {
- t.Error("invalid parse")
- }
- cmd, args, ok = c.Args(false)
- if cmd != "abc" || len(args) != 2 || args[0] != "xyz" || args[1] != "111" || !ok {
- t.Error("invalid parse")
- }
- t.Setenv("LOCKBOX_CLIP_COPY_COMMAND", "zzz lll 123")
- c, _ = platform.NewClipboard()
- cmd, args, ok = c.Args(true)
- if cmd != "zzz" || len(args) != 2 || args[0] != "lll" || args[1] != "123" || !ok {
- t.Error("invalid parse")
- }
- cmd, args, ok = c.Args(false)
- if cmd != "abc" || len(args) != 2 || args[0] != "xyz" || args[1] != "111" || !ok {
- t.Error("invalid parse")
- }
- t.Setenv("LOCKBOX_CLIP_PASTE_COMMAND", "")
- t.Setenv("LOCKBOX_CLIP_COPY_COMMAND", "")
- c, _ = platform.NewClipboard()
- cmd, args, ok = c.Args(true)
- if cmd != "clip.exe" || len(args) != 0 || !ok {
- t.Error("invalid parse")
- }
- cmd, args, ok = c.Args(false)
- if cmd != "powershell.exe" || len(args) != 2 || args[0] != "-command" || args[1] != "Get-Clipboard" || !ok {
- fmt.Println(args)
- t.Error("invalid parse")
- }
-}
diff --git a/internal/platform/core.go b/internal/platform/core.go
@@ -0,0 +1,74 @@
+// Package platform defines known platforms
+package platform
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "strings"
+
+ "github.com/seanenck/lockbox/internal/core"
+)
+
+// Systems are the known platforms for lockbox
+var Systems = SystemTypes{
+ MacOSSystem: "macos",
+ LinuxWaylandSystem: "linux-wayland",
+ LinuxXSystem: "linux-x",
+ WindowsLinuxSystem: "wsl",
+}
+
+const unknownSystem = ""
+
+type (
+ // System represents the platform lockbox is running on.
+ System string
+
+ // SystemTypes defines systems lockbox is known to run on or can run on
+ SystemTypes struct {
+ MacOSSystem System
+ LinuxWaylandSystem System
+ LinuxXSystem System
+ WindowsLinuxSystem System
+ }
+)
+
+// List will list the platform types on the struct
+func (p SystemTypes) List() []string {
+ return core.ListFields(p)
+}
+
+// NewSystem gets a new system platform.
+func NewSystem(candidate string) (System, error) {
+ env := candidate
+ if env != "" {
+ for _, p := range Systems.List() {
+ if p == env {
+ return System(p), nil
+ }
+ }
+ return unknownSystem, errors.New("unknown platform mode")
+ }
+ b, err := exec.Command("uname", "-a").Output()
+ if err != nil {
+ return unknownSystem, err
+ }
+ raw := strings.ToLower(strings.TrimSpace(string(b)))
+ parts := strings.Split(raw, " ")
+ switch parts[0] {
+ case "darwin":
+ return Systems.MacOSSystem, nil
+ case "linux":
+ if strings.Contains(raw, "microsoft-standard-wsl") {
+ return Systems.WindowsLinuxSystem, nil
+ }
+ if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) == "" {
+ if strings.TrimSpace(os.Getenv("DISPLAY")) == "" {
+ return unknownSystem, errors.New("unable to detect linux clipboard mode")
+ }
+ return Systems.LinuxXSystem, nil
+ }
+ return Systems.LinuxWaylandSystem, nil
+ }
+ return unknownSystem, errors.New("unable to detect clipboard mode")
+}
diff --git a/internal/platform/core_test.go b/internal/platform/core_test.go
@@ -0,0 +1,32 @@
+package platform_test
+
+import (
+ "testing"
+
+ "github.com/seanenck/lockbox/internal/platform"
+)
+
+func TestPlatformList(t *testing.T) {
+ if len(platform.Systems.List()) != 4 {
+ t.Errorf("invalid list result")
+ }
+}
+
+func TestNewPlatform(t *testing.T) {
+ for _, item := range platform.Systems.List() {
+ s, err := platform.NewSystem(item)
+ if err != nil {
+ t.Errorf("invalid clipboard: %v", err)
+ }
+ if s != platform.System(item) {
+ t.Error("mismatch on input and resulting detection")
+ }
+ }
+}
+
+func TestNewPlatformUnknown(t *testing.T) {
+ _, err := platform.NewSystem("ifjoajei")
+ if err == nil || err.Error() != "unknown platform mode" {
+ t.Errorf("error expected for platform: %v", err)
+ }
+}