lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 564feb393e606b410d5cc88746c10398976035b3
parent 3daf15afb1cd6fdb99b08b8a46d9f46821070c1b
Author: Sean Enck <sean@ttypty.com>
Date:   Sun, 29 Jun 2025 21:29:36 -0400

platform isn't required, this can all be figured at the clipboard

Diffstat:
Minternal/app/showclip.go | 2+-
Minternal/app/totp.go | 2+-
Minternal/config/toml_test.go | 2+-
Minternal/config/vars.go | 12------------
Minternal/config/vars_test.go | 1-
Minternal/platform/clip/core.go | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Minternal/platform/clip/core_test.go | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Minternal/platform/clip/manager.go | 8+++++++-
Minternal/platform/clip/manager_test.go | 13++++++++-----
Dinternal/platform/core.go | 74--------------------------------------------------------------------------
Dinternal/platform/core_test.go | 32--------------------------------
11 files changed, 149 insertions(+), 162 deletions(-)

diff --git a/internal/app/showclip.go b/internal/app/showclip.go @@ -19,7 +19,7 @@ func ShowClip(cmd CommandOptions, isShow bool) error { clipboard := clip.Board{} if !isShow { var err error - clipboard, err = clip.New() + clipboard, err = clip.New(clip.DefaultLoader{Full: false}) if err != nil { return fmt.Errorf("unable to get clipboard: %w", err) } diff --git a/internal/app/totp.go b/internal/app/totp.go @@ -121,7 +121,7 @@ func (args *TOTPArguments) display(opts TOTPOptions) error { } clipboard := clip.Board{} if clipMode { - clipboard, err = clip.New() + clipboard, err = clip.New(clip.DefaultLoader{Full: false}) if err != nil { return err } diff --git a/internal/config/toml_test.go b/internal/config/toml_test.go @@ -251,7 +251,7 @@ func TestDefaultTOMLToLoadFile(t *testing.T) { if err := config.LoadConfigFile(file); err != nil { t.Errorf("invalid error: %v", err) } - if len(store.List()) != 19 { + if len(store.List()) != 18 { t.Errorf("invalid environment after load: %d", len(store.List())) } } diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -6,7 +6,6 @@ import ( "strings" "git.sr.ht/~enckse/lockbox/internal/output" - "git.sr.ht/~enckse/lockbox/internal/platform" ) var ( @@ -81,17 +80,6 @@ var ( }), short: "max totp time", }) - // EnvPlatform is the platform that the application is running on - EnvPlatform = environmentRegister(EnvironmentString{ - environmentStrings: environmentStrings{ - environmentDefault: newDefaultedEnvironment(detectedValue, - environmentBase{ - key: "PLATFORM", - description: "Override the detected platform.", - }), - allowed: platform.Systems.List(), - }, - }) // EnvStore is the location of the keepass file/store EnvStore = environmentRegister(EnvironmentString{ environmentStrings: environmentStrings{ diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go @@ -157,7 +157,6 @@ func TestDefaultStrings(t *testing.T) { func TestEmptyStrings(t *testing.T) { store.Clear() for _, v := range []config.EnvironmentString{ - config.EnvPlatform, config.EnvStore, config.EnvKeyFile, config.EnvDefaultModTime, diff --git a/internal/platform/clip/core.go b/internal/platform/clip/core.go @@ -4,10 +4,12 @@ package clip import ( "errors" "fmt" + "os" "os/exec" + "runtime" + "strings" "git.sr.ht/~enckse/lockbox/internal/config" - "git.sr.ht/~enckse/lockbox/internal/platform" ) type ( @@ -18,8 +20,37 @@ type ( pasting []string MaxTime int64 } + // Loader handles how the system is detected + Loader interface { + Name() (string, error) + Runtime() string + Complete() bool + } + // DefaultLoader is the default system detector + DefaultLoader struct { + Full bool + } ) +// Name will get the uname results +func (l DefaultLoader) Name() (string, error) { + b, err := exec.Command("uname", "-a").Output() + if err != nil { + return "", err + } + return string(b), nil +} + +// Runtime will return the GOOS runtime +func (l DefaultLoader) Runtime() string { + return runtime.GOOS +} + +// Complete indicates if the loader needs a full complete +func (l DefaultLoader) Complete() bool { + return l.Full +} + func newBoard(copying, pasting []string) (Board, error) { maximum, err := config.EnvClipTimeout.Get() if err != nil { @@ -30,7 +61,7 @@ func newBoard(copying, pasting []string) (Board, error) { } // New creates a new clipboard -func New() (Board, error) { +func New(loader Loader) (Board, error) { if !config.EnvFeatureClip.Get() { return Board{}, config.NewFeatureError("clip") } @@ -41,26 +72,37 @@ func New() (Board, error) { if setPaste && setCopy { return newBoard(overrideCopy, overridePaste) } - sys, err := platform.NewSystem(config.EnvPlatform.Get()) - if err != nil { - return Board{}, err + if setCopy && !loader.Complete() { + return newBoard(overrideCopy, []string{}) } var copying []string var pasting []string - switch sys { - case platform.Systems.MacOSSystem: + switch loader.Runtime() { + case "darwin": 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"} + case "linux": + name, err := loader.Name() + if err != nil { + return Board{}, err + } + if strings.Contains(strings.ToLower(name), "microsoft") { + copying = []string{"clip.exe"} + pasting = []string{"powershell.exe", "-command", "Get-Clipboard"} + } else { + if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) != "" { + copying = []string{"wl-copy"} + pasting = []string{"wl-paste"} + } else { + if strings.TrimSpace(os.Getenv("DISPLAY")) != "" { + copying = []string{"xclip"} + pasting = []string{"xclip", "-o"} + } else { + return Board{}, errors.New("unable to detect linux clipboard") + } + } + } default: return Board{}, errors.New("clipboard is unavailable") } diff --git a/internal/platform/clip/core_test.go b/internal/platform/clip/core_test.go @@ -1,17 +1,36 @@ package clip_test import ( + "fmt" "testing" "git.sr.ht/~enckse/lockbox/internal/config/store" - "git.sr.ht/~enckse/lockbox/internal/platform" "git.sr.ht/~enckse/lockbox/internal/platform/clip" ) +type mockLoader struct { + err error + name string + runtime string + full bool +} + +func (m mockLoader) Name() (string, error) { + return m.name, m.err +} + +func (m mockLoader) Runtime() string { + return m.runtime +} + +func (m mockLoader) Complete() bool { + return m.full +} + func TestDisabled(t *testing.T) { defer store.Clear() store.SetBool("LOCKBOX_FEATURE_CLIP", false) - if _, err := clip.New(); err == nil || err.Error() != "clip feature is disabled" { + if _, err := clip.New(mockLoader{}); err == nil || err.Error() != "clip feature is disabled" { t.Errorf("invalid error: %v", err) } } @@ -19,47 +38,84 @@ func TestDisabled(t *testing.T) { func TestMaxTime(t *testing.T) { store.Clear() defer store.Clear() - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.LinuxWaylandSystem)) - c, err := clip.New() + t.Setenv("WAYLAND_DISPLAY", "1") + loader := mockLoader{name: "linux", runtime: "linux"} + c, err := clip.New(loader) if err != nil { - t.Errorf("invalid clipboard: %v", err) + t.Errorf("invalid error: %v", err) } if c.MaxTime != 120 { t.Error("invalid default") } store.SetInt64("LOCKBOX_CLIP_TIMEOUT", 1) - c, err = clip.New() + c, err = clip.New(loader) if err != nil { - t.Errorf("invalid clipboard: %v", err) + t.Errorf("invalid error: %v", err) } if c.MaxTime != 1 { t.Error("invalid default") } store.SetInt64("LOCKBOX_CLIP_TIMEOUT", -1) - _, err = clip.New() + c, err = clip.New(loader) if err == nil || err.Error() != "clipboard entry max time must be > 0" { t.Errorf("invalid max time error: %v", err) } } -func TestClipboardInstances(t *testing.T) { +func TestInstance(t *testing.T) { store.Clear() defer store.Clear() - for _, item := range platform.Systems.List() { - store.SetString("LOCKBOX_PLATFORM", item) - _, err := clip.New() + fxn := func(runtime, name, c, p, e string) { + l := mockLoader{runtime: runtime, name: name} + b, err := clip.New(l) if err != nil { - t.Errorf("invalid clipboard: %v", err) + if err.Error() != e { + t.Errorf("invalid error: %v", err) + } + return + } + cmd, args, _ := b.Args(true) + copying := fmt.Sprintf("%s (%v)", cmd, args) + cmd, args, _ = b.Args(false) + pasting := fmt.Sprintf("%s (%v)", cmd, args) + if copying != c { + t.Errorf("invalid copy: %s != %s", c, copying) + } + if pasting != p { + t.Errorf("invalid copy: %s != %s", p, pasting) } } + fxn("darwin", "", "pbcopy ([])", "pbpaste ([])", "") + fxn("linux", "microsoft", "clip.exe ([])", "powershell.exe ([-command Get-Clipboard])", "") + fxn("linux", "linux", "", "", "unable to detect linux clipboard") + t.Setenv("DISPLAY", "1") + t.Setenv("WAYLAND_DISPLAY", "1") + fxn("linux", "linux", "wl-copy ([])", "wl-paste ([])", "") + t.Setenv("WAYLAND_DISPLAY", "") + fxn("linux", "linux", "xclip ([])", "xclip ([-o])", "") +} + +func TestFullPartial(t *testing.T) { + store.Clear() + defer store.Clear() + store.SetArray("LOCKBOX_CLIP_COPY_COMMAND", []string{"abc", "xyz", "111"}) + if _, err := clip.New(mockLoader{}); err != nil { + t.Errorf("invalid error: %v", err) + } + if _, err := clip.New(mockLoader{full: true}); err == nil || err.Error() != "clipboard is unavailable" { + t.Errorf("invalid error: %v", err) + } + store.SetArray("LOCKBOX_CLIP_PASTE_COMMAND", []string{"abc", "xyz", "111"}) + if _, err := clip.New(mockLoader{full: true}); err != nil { + t.Errorf("invalid error: %v", err) + } } func TestArgsOverride(t *testing.T) { store.Clear() defer store.Clear() store.SetArray("LOCKBOX_CLIP_PASTE_COMMAND", []string{"abc", "xyz", "111"}) - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.WindowsLinuxSystem)) - c, err := clip.New() + c, err := clip.New(mockLoader{name: "microsoft", runtime: "linux"}) if err != nil { t.Errorf("invalid error: %v", err) } @@ -72,7 +128,7 @@ func TestArgsOverride(t *testing.T) { t.Error("invalid parse") } store.SetArray("LOCKBOX_CLIP_COPY_COMMAND", []string{"zzz", "lll", "123"}) - c, err = clip.New() + c, err = clip.New(mockLoader{}) if err != nil { t.Errorf("invalid error: %v", err) } @@ -85,8 +141,7 @@ func TestArgsOverride(t *testing.T) { t.Error("invalid parse") } store.Clear() - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.WindowsLinuxSystem)) - c, err = clip.New() + c, err = clip.New(mockLoader{name: "microsoft", runtime: "linux"}) if err != nil { t.Errorf("invalid error: %v", err) } diff --git a/internal/platform/clip/manager.go b/internal/platform/clip/manager.go @@ -22,6 +22,7 @@ type ( Getpid() int Copy(Board, string) Sleep() + Loader() Loader } // DefaultDaemon is the default functioning daemon DefaultDaemon struct{} @@ -62,12 +63,17 @@ func (d DefaultDaemon) Sleep() { time.Sleep(1 * time.Second) } +// Loader will get the backing loader to use +func (d DefaultDaemon) Loader() Loader { + return DefaultLoader{Full: true} +} + // Manager handles the daemon runner func Manager(daemon bool, manager Daemon) error { if manager == nil { return errors.New("manager is nil") } - clipboard, err := New() + clipboard, err := New(manager.Loader()) if err != nil { return err } diff --git a/internal/platform/clip/manager_test.go b/internal/platform/clip/manager_test.go @@ -7,7 +7,6 @@ import ( "testing" "git.sr.ht/~enckse/lockbox/internal/config/store" - "git.sr.ht/~enckse/lockbox/internal/platform" "git.sr.ht/~enckse/lockbox/internal/platform/clip" ) @@ -59,10 +58,14 @@ func (d *mock) Copy(_ clip.Board, val string) { func (d *mock) Sleep() { } +func (d *mock) Loader() clip.Loader { + return mockLoader{name: "linux", runtime: "linux"} +} + func TestErrors(t *testing.T) { store.Clear() defer store.Clear() - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.LinuxWaylandSystem)) + t.Setenv("WAYLAND_DISPLAY", "1") if err := clip.Manager(false, nil); err == nil || err.Error() != "manager is nil" { t.Errorf("invalid error: %v", err) } @@ -80,8 +83,8 @@ func TestErrors(t *testing.T) { func TestStart(t *testing.T) { store.Clear() defer store.Clear() - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.LinuxWaylandSystem)) store.SetString("LOCKBOX_CLIP_PIDFILE", "a") + t.Setenv("WAYLAND_DISPLAY", "1") m := &mock{} if err := clip.Manager(false, m); err != nil { t.Errorf("invalid error: %v", err) @@ -94,8 +97,8 @@ func TestStart(t *testing.T) { func TestPIDMismatch(t *testing.T) { store.Clear() defer store.Clear() - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.LinuxWaylandSystem)) store.SetString("LOCKBOX_CLIP_PIDFILE", "falsepid") + t.Setenv("WAYLAND_DISPLAY", "1") m := &mock{} if err := clip.Manager(true, m); err != nil { t.Errorf("invalid error: %v", err) @@ -105,8 +108,8 @@ func TestPIDMismatch(t *testing.T) { func TestChange(t *testing.T) { store.Clear() defer store.Clear() - store.SetString("LOCKBOX_PLATFORM", string(platform.Systems.LinuxWaylandSystem)) store.SetString("LOCKBOX_CLIP_PIDFILE", "a") + t.Setenv("WAYLAND_DISPLAY", "1") m := &mock{} // NOTE: 100 (count before static) + 120 (default timeout) + 1 (caused break of loop) if err := clip.Manager(true, m); err == nil || strings.Count(err.Error(), "copied: 221") != 6 { diff --git a/internal/platform/core.go b/internal/platform/core.go @@ -1,74 +0,0 @@ -// Package platform defines known platforms -package platform - -import ( - "errors" - "os" - "os/exec" - "strings" - - "git.sr.ht/~enckse/lockbox/internal/reflect" -) - -// 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 reflect.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 system") - } - return Systems.LinuxXSystem, nil - } - return Systems.LinuxWaylandSystem, nil - } - return unknownSystem, errors.New("unable to detect system") -} diff --git a/internal/platform/core_test.go b/internal/platform/core_test.go @@ -1,32 +0,0 @@ -package platform_test - -import ( - "testing" - - "git.sr.ht/~enckse/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) - } -}