lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 7d9e7a76cf679d71242ff2ae4c5f0ef94794b4c8
parent 536918d133e3035fa1f14a7eef378c3c1220cba1
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  7 Dec 2024 14:24:36 -0500

move clipboard under platform

Diffstat:
Mcmd/main.go | 3++-
Minternal/app/showclip.go | 6+++---
Minternal/app/totp.go | 16++++++++--------
Minternal/config/vars.go | 3++-
Minternal/core/core.go | 5+++--
Ainternal/core/core_test.go | 20++++++++++++++++++++
Minternal/core/json.go | 2+-
Minternal/core/json_test.go | 6++++--
Dinternal/core/platforms.go | 72------------------------------------------------------------------------
Dinternal/core/platforms_test.go | 32--------------------------------
Ainternal/platform/clip/core.go | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/platform/clip/core_test.go | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dinternal/platform/clipboard.go | 153-------------------------------------------------------------------------------
Dinternal/platform/clipboard_test.go | 114-------------------------------------------------------------------------------
Ainternal/platform/core.go | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainternal/platform/core_test.go | 32++++++++++++++++++++++++++++++++
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) + } +}