commit 7b35fc4b5fe720dd3e48acc53140f3eaaa892ecc
parent 7a7be64c4577f260441f4ade1f1b86ffd3b31b5b
Author: Sean Enck <sean@ttypty.com>
Date: Mon, 30 Jun 2025 09:02:44 -0400
clipmgr should really be a utility outside lb
Diffstat:
16 files changed, 63 insertions(+), 661 deletions(-)
diff --git a/cmd/lb/main.go b/cmd/lb/main.go
@@ -43,8 +43,6 @@ func handleEarly(command string, args []string) (bool, error) {
}
fmt.Printf("version: %s\n", vers)
return true, nil
- case commands.ClipManager, commands.ClipManagerDaemon:
- return true, platform.ClipboardManager(args, command == commands.ClipManagerDaemon, platform.DefaultClipboardDaemon{})
}
return false, nil
}
diff --git a/cmd/lb/main_test.go b/cmd/lb/main_test.go
@@ -306,10 +306,7 @@ func test(profile string) error {
r.section("clipboard")
copyFile := filepath.Join(r.testDir, "clip.copy")
- pasteFile := filepath.Join(r.testDir, "clip.paste")
- c["clip.copy_command"] = fmt.Sprintf("[\"touch\", \"%s\"]", copyFile)
- c["clip.paste_command"] = fmt.Sprintf("[\"touch\", \"%s\"]", pasteFile)
- c["clip.timeout"] = "3"
+ c["clip.copy"] = fmt.Sprintf("[\"touch\", \"%s\"]", copyFile)
r.writeConfig(c)
r.run("", "clip test6/multiline/password")
clipPassed := false
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -322,9 +322,7 @@ test6/multiline/notes
test6/multiline/otp
test6/multiline/password
env
-LOCKBOX_CLIP_COPY_COMMAND=[touch testdata/datadir/clip.copy]
-LOCKBOX_CLIP_PASTE_COMMAND=[touch testdata/datadir/clip.paste]
-LOCKBOX_CLIP_TIMEOUT=3
+LOCKBOX_CLIP_COPY=[touch testdata/datadir/clip.copy]
LOCKBOX_JSON_HASH_LENGTH=3
LOCKBOX_JSON_MODE=hash
LOCKBOX_STORE=testdata/datadir/pass.kdbx
diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go
@@ -12,10 +12,6 @@ const (
TOTP = "totp"
// Conv handles text conversion of the data store
Conv = "conv"
- // ClipManager can handle simple clipboard management
- ClipManager = "clipmgr"
- // ClipManagerDaemon is the backing call to perform clipboard management
- ClipManagerDaemon = "clipmgrd"
// Clip will copy values to the clipboard
Clip = "clip"
// Insert adds a value
diff --git a/internal/app/help/core.go b/internal/app/help/core.go
@@ -38,8 +38,6 @@ type (
HelpConfigCommand string
NoColor string
ReadOnlyCommands string
- ClipManager string
- ClipManagerStop string
Config struct {
Env string
Home string
@@ -107,9 +105,6 @@ func Usage(verbose bool, exe string) ([]string, error) {
results = append(results, subCommand(commands.TOTP, commands.TOTPSeed, isEntry, "show the TOTP seed (only)"))
results = append(results, subCommand(commands.TOTP, commands.TOTPShow, isEntry, "show the totp entry"))
results = append(results, command(commands.Version, "", "display version information"))
- results = append(results, command(commands.ClipManager, "", "run the clipboard manager daemon"))
- results = append(results, command(commands.ClipManagerDaemon, "", "clipboard manager daemonized function"))
- results = append(results, subCommand(commands.ClipManager, commands.ClipManagerStop, "", "stop a running clipboard manager daemon"))
sort.Strings(results)
usage := []string{fmt.Sprintf("%s usage:", exe)}
if verbose {
@@ -122,8 +117,6 @@ func Usage(verbose bool, exe string) ([]string, error) {
CompletionsCommand: commands.Completions,
HelpCommand: commands.Help,
HelpConfigCommand: commands.HelpConfig,
- ClipManager: commands.ClipManager,
- ClipManagerStop: commands.ClipManagerStop,
NoColor: config.NoColorFlag,
ReadOnlyCommands: strings.Join(commands.ReadOnly, ", "),
}
diff --git a/internal/app/help/core_test.go b/internal/app/help/core_test.go
@@ -9,11 +9,11 @@ import (
func TestUsage(t *testing.T) {
u, _ := help.Usage(false, "lb")
- if len(u) != 30 {
+ if len(u) != 27 {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = help.Usage(true, "lb")
- if len(u) != 137 {
+ if len(u) != 128 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/help/doc/clipmanager.txt b/internal/app/help/doc/clipmanager.txt
@@ -1,5 +0,0 @@
-The `{{ $.ClipManager }}` functionality allows running a local clipboard
-manager for the system that will monitor the clipboard and clear it when
-it has been idle for a (configured) period of time. This is specifically
-invoked via `{{ $.Executable }} {{ $.ClipManager }}` (and can be killed
-by adding `{{ $.ClipManagerStop }}` as an argument).
diff --git a/internal/app/showclip.go b/internal/app/showclip.go
@@ -19,7 +19,7 @@ func ShowClip(cmd CommandOptions, isShow bool) error {
clipboard := platform.Clipboard{}
if !isShow {
var err error
- clipboard, err = platform.NewClipboard(platform.DefaultClipboardLoader{Full: false})
+ clipboard, err = platform.NewClipboard(platform.DefaultClipboardLoader{})
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 := platform.Clipboard{}
if clipMode {
- clipboard, err = platform.NewClipboard(platform.DefaultClipboardLoader{Full: false})
+ clipboard, err = platform.NewClipboard(platform.DefaultClipboardLoader{})
if err != nil {
return err
}
diff --git a/internal/config/toml_test.go b/internal/config/toml_test.go
@@ -93,7 +93,7 @@ func TestArrayLoad(t *testing.T) {
t.Setenv("TEST", "abc")
data := `store="xyz"
[clip]
-copy_command = ["'xyz/$TEST'", "s", 1]
+copy = ["'xyz/$TEST'", "s", 1]
`
r := strings.NewReader(data)
if err := config.LoadConfig(r, emptyRead); err == nil || err.Error() != "value is not string in array: 1" {
@@ -102,7 +102,7 @@ copy_command = ["'xyz/$TEST'", "s", 1]
data = `include = []
store="xyz"
[clip]
-copy_command = ["'xyz/$TEST'", "s"]
+copy = ["'xyz/$TEST'", "s"]
`
r = strings.NewReader(data)
if err := config.LoadConfig(r, emptyRead); err != nil {
@@ -115,14 +115,14 @@ copy_command = ["'xyz/$TEST'", "s"]
if val != "xyz" || !ok {
t.Errorf("invalid object: %v", val)
}
- a, ok := store.GetArray("LOCKBOX_CLIP_COPY_COMMAND")
+ a, ok := store.GetArray("LOCKBOX_CLIP_COPY")
if fmt.Sprintf("%v", a) != "['xyz/abc' s]" || !ok {
t.Errorf("invalid object: %v", a)
}
data = `include = []
store="xyz"
[clip]
-copy_command = ["'xyz/$TEST'", "s"]
+copy = ["'xyz/$TEST'", "s"]
`
r = strings.NewReader(data)
if err := config.LoadConfig(r, emptyRead); err != nil {
@@ -135,7 +135,7 @@ copy_command = ["'xyz/$TEST'", "s"]
if val != "xyz" || !ok {
t.Errorf("invalid object: %v", val)
}
- a, ok = store.GetArray("LOCKBOX_CLIP_COPY_COMMAND")
+ a, ok = store.GetArray("LOCKBOX_CLIP_COPY")
if fmt.Sprintf("%v", a) != "['xyz/abc' s]" || !ok {
t.Errorf("invalid object: %v", val)
}
@@ -144,16 +144,16 @@ copy_command = ["'xyz/$TEST'", "s"]
func TestReadInt(t *testing.T) {
store.Clear()
data := `
-[clip]
-timeout = true
+[json]
+hash_length = true
`
r := strings.NewReader(data)
if err := config.LoadConfig(r, emptyRead); err == nil || err.Error() != "non-int64 found where expected: true" {
t.Errorf("invalid error: %v", err)
}
data = `include = []
-[clip]
-timeout = 1
+[json]
+hash_length = 1
`
r = strings.NewReader(data)
if err := config.LoadConfig(r, emptyRead); err != nil {
@@ -162,13 +162,13 @@ timeout = 1
if len(store.List()) != 1 {
t.Errorf("invalid store")
}
- val, ok := store.GetInt64("LOCKBOX_CLIP_TIMEOUT")
+ val, ok := store.GetInt64("LOCKBOX_JSON_HASH_LENGTH")
if val != 1 || !ok {
t.Errorf("invalid object: %v", val)
}
data = `include = []
-[clip]
-timeout = -1
+[json]
+hash_length = -1
`
r = strings.NewReader(data)
if err := config.LoadConfig(r, emptyRead); err == nil || err.Error() != "-1 is negative (not allowed here)" {
@@ -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()) != 18 {
+ if len(store.List()) != 15 {
t.Errorf("invalid environment after load: %d", len(store.List()))
}
}
@@ -261,7 +261,7 @@ func TestExpands(t *testing.T) {
t.Setenv("TEST", "1")
data := `include = []
store = "$TEST"
-clip.copy_command = ["$TEST", "$TEST"]
+clip.copy = ["$TEST", "$TEST"]
[totp]
otp_format = "$TEST"
`
@@ -280,7 +280,7 @@ otp_format = "$TEST"
if val != "1" || !ok {
t.Errorf("invalid object: %v", val)
}
- a, ok := store.GetArray("LOCKBOX_CLIP_COPY_COMMAND")
+ a, ok := store.GetArray("LOCKBOX_CLIP_COPY")
if fmt.Sprintf("%v", a) != "[1 1]" || !ok {
t.Errorf("invalid object: %v", a)
}
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -33,27 +33,6 @@ var (
description: "Enable terminal color feature.",
}),
})
- // EnvClipTimeout gets the maximum clipboard time
- EnvClipTimeout = environmentRegister(EnvironmentInt{
- environmentDefault: newDefaultedEnvironment(120,
- environmentBase{
- key: clipCategory + "TIMEOUT",
- description: "Override the amount of time before clearing the clipboard (seconds).",
- }),
- short: "clipboard entry max time",
- })
- // EnvClipProcessFile configures the clip manager daemon pidfile
- EnvClipProcessFile = environmentRegister(EnvironmentString{
- environmentStrings: environmentStrings{
- environmentDefault: newDefaultedEnvironment("",
- environmentBase{
- key: clipCategory + "PIDFILE",
- description: "Set the pidfile for clipboard management",
- }),
- allowed: []string{fileExample},
- flags: []stringsFlags{canExpandFlag},
- },
- })
// EnvJSONHashLength handles the hashing output length
EnvJSONHashLength = environmentRegister(EnvironmentInt{
environmentDefault: newDefaultedEnvironment(0,
@@ -99,23 +78,12 @@ var (
environmentStrings: environmentStrings{
environmentDefault: newDefaultedEnvironment("",
environmentBase{
- key: clipCategory + "COPY_COMMAND",
+ key: clipCategory + "COPY",
description: "Override the detected platform copy command.",
}),
flags: []stringsFlags{isCommandFlag},
},
})
- // EnvClipPaste allows overriding the clipboard paste command
- EnvClipPaste = environmentRegister(EnvironmentArray{
- environmentStrings: environmentStrings{
- environmentDefault: newDefaultedEnvironment("",
- environmentBase{
- key: clipCategory + "PASTE_COMMAND",
- description: "Override the detected platform paste command.",
- }),
- flags: []stringsFlags{isCommandFlag},
- },
- })
// EnvTOTPColorBetween handles terminal coloring for TOTP windows (seconds)
EnvTOTPColorBetween = environmentRegister(EnvironmentArray{
environmentStrings: environmentStrings{
diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go
@@ -61,10 +61,6 @@ func TestFormatTOTP(t *testing.T) {
}
}
-func TestClipboardMax(t *testing.T) {
- checkInt(config.EnvClipTimeout, "LOCKBOX_CLIP_TIMEOUT", "clipboard entry max time", 120, false, t)
-}
-
func TestHashLength(t *testing.T) {
checkInt(config.EnvJSONHashLength, "LOCKBOX_JSON_HASH_LENGTH", "hash length", 0, true, t)
}
@@ -122,7 +118,6 @@ func TestUnsetArrays(t *testing.T) {
store.Clear()
for _, i := range []config.EnvironmentArray{
config.EnvClipCopy,
- config.EnvClipPaste,
} {
val := i.Get()
if len(val) != 0 {
@@ -160,7 +155,6 @@ func TestEmptyStrings(t *testing.T) {
config.EnvStore,
config.EnvKeyFile,
config.EnvDefaultModTime,
- config.EnvClipProcessFile,
} {
val := v.Get()
if val != "" {
diff --git a/internal/platform/clip.go b/internal/platform/clip.go
@@ -14,22 +14,14 @@ import (
type (
// Clipboard represent system clipboard operations.
- Clipboard struct {
- PIDFile string
- copying []string
- pasting []string
- MaxTime int64
- }
+ Clipboard []string
// ClipboardLoader handles how the system is detected
ClipboardLoader interface {
Name() (string, error)
Runtime() string
- Complete() bool
}
// DefaultClipboardLoader is the default system detector
- DefaultClipboardLoader struct {
- Full bool
- }
+ DefaultClipboardLoader struct{}
)
// Name will get the uname results
@@ -46,101 +38,50 @@ func (l DefaultClipboardLoader) Runtime() string {
return runtime.GOOS
}
-// Complete indicates if the loader needs a full complete
-func (l DefaultClipboardLoader) Complete() bool {
- return l.Full
-}
-
-func newBoard(copying, pasting []string) (Clipboard, error) {
- maximum, err := config.EnvClipTimeout.Get()
- if err != nil {
- return Clipboard{}, err
- }
- pid := config.EnvClipProcessFile.Get()
- return Clipboard{copying: copying, pasting: pasting, MaxTime: maximum, PIDFile: pid}, nil
-}
-
// NewClipboard creates a new clipboard
func NewClipboard(loader ClipboardLoader) (Clipboard, error) {
if !config.EnvFeatureClip.Get() {
return Clipboard{}, config.NewFeatureError("clip")
}
- overridePaste := config.EnvClipPaste.Get()
overrideCopy := config.EnvClipCopy.Get()
- setPaste := len(overridePaste) > 0
- setCopy := len(overrideCopy) > 0
- if setPaste && setCopy {
- return newBoard(overrideCopy, overridePaste)
- }
- if setCopy && !loader.Complete() {
- return newBoard(overrideCopy, []string{})
+ if len(overrideCopy) > 0 {
+ return overrideCopy, nil
}
- var copying []string
- var pasting []string
switch loader.Runtime() {
case "darwin":
- copying = []string{"pbcopy"}
- pasting = []string{"pbpaste"}
+ return []string{"pbcopy"}, nil
case "linux":
name, err := loader.Name()
if err != nil {
return Clipboard{}, 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 Clipboard{}, errors.New("unable to detect linux clipboard")
- }
- }
+ return []string{"clip.exe"}, nil
+ }
+ if strings.TrimSpace(os.Getenv("WAYLAND_DISPLAY")) != "" {
+ return []string{"wl-copy"}, nil
}
+ if strings.TrimSpace(os.Getenv("DISPLAY")) != "" {
+ return []string{"xclip"}, nil
+ }
+ return Clipboard{}, errors.New("unable to detect linux clipboard")
default:
return Clipboard{}, errors.New("clipboard is unavailable")
}
- if setPaste {
- pasting = overridePaste
- }
- if setCopy {
- copying = overrideCopy
- }
- return newBoard(copying, pasting)
}
// CopyTo will copy to clipboard, if non-empty will clear later.
func (c Clipboard) CopyTo(value string) error {
- cmd, args, err := c.Args(true)
- if err != nil {
- return err
- }
- pipeTo(cmd, value, args...)
- return nil
-}
-
-// Args returns clipboard args for execution.
-func (c Clipboard) Args(copying bool) (string, []string, error) {
- var using []string
- if copying {
- using = c.copying
- } else {
- using = c.pasting
- }
- if len(using) == 0 {
- return "", nil, fmt.Errorf("command is not set (copying? %v)", copying)
+ if len(c) == 0 {
+ return errors.New("copy command is not set")
}
+ cmd := c[0]
var args []string
- if len(using) > 1 {
- args = using[1:]
+ if len(c) > 1 {
+ args = c[1:]
}
- return using[0], args, nil
+ return pipeTo(cmd, value, args...)
}
func pipeTo(command, value string, args ...string) error {
diff --git a/internal/platform/clip_test.go b/internal/platform/clip_test.go
@@ -12,7 +12,6 @@ type mockLoader struct {
err error
name string
runtime string
- full bool
}
func (m mockLoader) Name() (string, error) {
@@ -23,10 +22,6 @@ 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)
@@ -35,37 +30,10 @@ func TestDisabled(t *testing.T) {
}
}
-func TestMaxTime(t *testing.T) {
- store.Clear()
- defer store.Clear()
- t.Setenv("WAYLAND_DISPLAY", "1")
- loader := mockLoader{name: "linux", runtime: "linux"}
- c, err := platform.NewClipboard(loader)
- if err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if c.MaxTime != 120 {
- t.Error("invalid default")
- }
- store.SetInt64("LOCKBOX_CLIP_TIMEOUT", 1)
- c, err = platform.NewClipboard(loader)
- if err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if c.MaxTime != 1 {
- t.Error("invalid default")
- }
- store.SetInt64("LOCKBOX_CLIP_TIMEOUT", -1)
- c, err = platform.NewClipboard(loader)
- if err == nil || err.Error() != "clipboard entry max time must be > 0" {
- t.Errorf("invalid max time error: %v", err)
- }
-}
-
func TestInstance(t *testing.T) {
store.Clear()
defer store.Clear()
- fxn := func(runtime, name, c, p, e string) {
+ fxn := func(runtime, name, c, e string) {
l := mockLoader{runtime: runtime, name: name}
b, err := platform.NewClipboard(l)
if err != nil {
@@ -74,87 +42,49 @@ func TestInstance(t *testing.T) {
}
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)
+ if fmt.Sprintf("%v", b) != c {
+ t.Errorf("invalid copy: %s != %v", c, b)
}
}
- fxn("darwin", "", "pbcopy ([])", "pbpaste ([])", "")
- fxn("linux", "microsoft", "clip.exe ([])", "powershell.exe ([-command Get-Clipboard])", "")
- fxn("linux", "linux", "", "", "unable to detect linux clipboard")
+ fxn("darwin", "", "[pbcopy]", "")
+ fxn("linux", "microsoft", "[clip.exe]", "")
+ fxn("linux", "linux", "", "unable to detect linux clipboard")
t.Setenv("DISPLAY", "1")
t.Setenv("WAYLAND_DISPLAY", "1")
- fxn("linux", "linux", "wl-copy ([])", "wl-paste ([])", "")
+ fxn("linux", "linux", "[wl-copy]", "")
t.Setenv("WAYLAND_DISPLAY", "")
- fxn("linux", "linux", "xclip ([])", "xclip ([-o])", "")
+ fxn("linux", "linux", "[xclip]", "")
}
-func TestFullPartial(t *testing.T) {
+func TestCopy(t *testing.T) {
store.Clear()
defer store.Clear()
- store.SetArray("LOCKBOX_CLIP_COPY_COMMAND", []string{"abc", "xyz", "111"})
- if _, err := platform.NewClipboard(mockLoader{}); err != nil {
+ store.SetArray("LOCKBOX_CLIP_COPY", []string{})
+ if _, err := platform.NewClipboard(mockLoader{}); err == nil || err.Error() != "clipboard is unavailable" {
t.Errorf("invalid error: %v", err)
}
- if _, err := platform.NewClipboard(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 := platform.NewClipboard(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.SetArray("LOCKBOX_CLIP_COPY", []string{"x"})
c, err := platform.NewClipboard(mockLoader{name: "microsoft", runtime: "linux"})
if err != nil {
t.Errorf("invalid error: %v", err)
}
- cmd, args, err := c.Args(true)
- if cmd != "clip.exe" || len(args) != 0 || err != nil {
- t.Error("invalid parse")
+ if fmt.Sprintf("%v", c) != "[x]" {
+ t.Errorf("invalid override: %v", c)
}
- cmd, args, err = c.Args(false)
- if cmd != "abc" || len(args) != 2 || args[0] != "xyz" || args[1] != "111" || err != nil {
- t.Error("invalid parse")
- }
- store.SetArray("LOCKBOX_CLIP_COPY_COMMAND", []string{"zzz", "lll", "123"})
- c, err = platform.NewClipboard(mockLoader{})
- if err != nil {
- t.Errorf("invalid error: %v", err)
- }
- cmd, args, err = c.Args(true)
- if cmd != "zzz" || len(args) != 2 || args[0] != "lll" || args[1] != "123" || err != nil {
- t.Error("invalid parse")
- }
- cmd, args, err = c.Args(false)
- if cmd != "abc" || len(args) != 2 || args[0] != "xyz" || args[1] != "111" || err != nil {
- t.Error("invalid parse")
- }
- store.Clear()
+ store.SetArray("LOCKBOX_CLIP_COPY", []string{"x", "y", "z"})
c, err = platform.NewClipboard(mockLoader{name: "microsoft", runtime: "linux"})
if err != nil {
t.Errorf("invalid error: %v", err)
}
- cmd, args, err = c.Args(true)
- if cmd != "clip.exe" || len(args) != 0 || err != nil {
- t.Error("invalid parse")
- }
- cmd, args, err = c.Args(false)
- if cmd != "powershell.exe" || len(args) != 2 || args[0] != "-command" || args[1] != "Get-Clipboard" || err != nil {
- t.Errorf("invalid parse %s %v", cmd, args)
+ if fmt.Sprintf("%v", c) != "[x y z]" {
+ t.Errorf("invalid override: %v", c)
}
c = platform.Clipboard{}
- if _, _, err := c.Args(true); err == nil || err.Error() != "command is not set (copying? true)" {
+ if err := c.CopyTo(""); err == nil || err.Error() != "copy command is not set" {
+ t.Errorf("invalid error: %v", err)
+ }
+ c = platform.Clipboard{"echo"}
+ if err := c.CopyTo(""); err != nil {
t.Errorf("invalid error: %v", err)
}
}
diff --git a/internal/platform/clipmanager.go b/internal/platform/clipmanager.go
@@ -1,220 +0,0 @@
-package platform
-
-import (
- "crypto/sha256"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "syscall"
- "time"
-
- "git.sr.ht/~enckse/lockbox/internal/app/commands"
-)
-
-type (
- // ClipboardDaemon is the manager interface
- ClipboardDaemon interface {
- WriteFile(string, string)
- ReadFile(string) ([]byte, error)
- Output(string, ...string) ([]byte, error)
- Start(string, ...string) error
- Getpid() int
- Copy(Clipboard, string)
- Sleep()
- Loader() ClipboardLoader
- Checkpid(int) error
- }
- // DefaultClipboardDaemon is the default functioning daemon
- DefaultClipboardDaemon struct{}
-)
-
-// WriteFile will write the necessary file to backing filesystem
-func (d DefaultClipboardDaemon) WriteFile(file, data string) {
- os.WriteFile(file, []byte(data), 0o644)
-}
-
-// ReadFile will read a file from the filesystem
-func (d DefaultClipboardDaemon) ReadFile(file string) ([]byte, error) {
- return os.ReadFile(file)
-}
-
-// Output will run a command and get output
-func (d DefaultClipboardDaemon) Output(cmd string, args ...string) ([]byte, error) {
- return exec.Command(cmd, args...).Output()
-}
-
-// Start will start an disconnected execution
-func (d DefaultClipboardDaemon) Start(cmd string, args ...string) error {
- return exec.Command(cmd, args...).Start()
-}
-
-// Getpid will return the pid
-func (d DefaultClipboardDaemon) Getpid() int {
- return os.Getpid()
-}
-
-// Copy will copy data to the clipboard
-func (d DefaultClipboardDaemon) Copy(c Clipboard, val string) {
- c.CopyTo(val)
-}
-
-// Sleep will cause a pause/delay/wait
-func (d DefaultClipboardDaemon) Sleep() {
- time.Sleep(1 * time.Second)
-}
-
-// Loader will get the backing loader to use
-func (d DefaultClipboardDaemon) Loader() ClipboardLoader {
- return DefaultClipboardLoader{Full: true}
-}
-
-// Checkpid will check if a pid is still active
-func (d DefaultClipboardDaemon) Checkpid(pid int) error {
- process, err := os.FindProcess(pid)
- if err != nil {
- return err
- }
- return process.Signal(syscall.Signal(0))
-}
-
-// ClipboardManager handles the daemon runner
-func ClipboardManager(args []string, daemon bool, manager ClipboardDaemon) error {
- if manager == nil {
- return errors.New("manager is nil")
- }
- clipboard, err := NewClipboard(manager.Loader())
- if err != nil {
- return err
- }
- if clipboard.PIDFile == "" {
- return errors.New("pidfile is unset")
- }
- getProcess := func() (string, error) {
- b, err := manager.ReadFile(clipboard.PIDFile)
- if err != nil {
- return "", err
- }
- val := strings.TrimSpace(string(b))
- return val, nil
- }
- if !daemon {
- invalid := false
- switch len(args) {
- case 0:
- break
- case 1:
- invalid = args[0] != commands.ClipManagerStop
- if !invalid {
- if PathExists(clipboard.PIDFile) {
- return os.WriteFile(clipboard.PIDFile, []byte("0"), 0o644)
- }
- return nil
- }
- default:
- invalid = true
- }
- if invalid {
- return fmt.Errorf("invalid manager arguments: %v", args)
- }
- if PathExists(clipboard.PIDFile) {
- p, err := getProcess()
- if err != nil {
- return err
- }
- pid, err := strconv.Atoi(p)
- if err != nil {
- return err
- }
- if err := manager.Checkpid(pid); err == nil {
- return nil
- }
- }
- return manager.Start(commands.Executable, commands.ClipManagerDaemon)
- }
- if len(args) > 0 {
- return fmt.Errorf("invalid daemon arguments: %v", args)
- }
- paste, pasteArgs, err := clipboard.Args(false)
- if err != nil {
- return err
- }
- pasteFxn := func() (string, error) {
- b, err := manager.Output(paste, pasteArgs...)
- if err != nil {
- return "", err
- }
- val := strings.TrimSpace(string(b))
- if val == "" {
- return "", nil
- }
- hash := sha256.New()
- if _, err := hash.Write([]byte(val)); err != nil {
- return "", err
- }
- return fmt.Sprintf("%x", hash.Sum(nil)), nil
- }
- pid := strings.TrimSpace(fmt.Sprintf("%d", manager.Getpid()))
- isCurrentProcess := func() (bool, error) {
- val, err := getProcess()
- return val == pid, err
- }
- manager.WriteFile(clipboard.PIDFile, pid)
- var errs []error
- for {
- if len(errs) > 5 {
- return errors.Join(errs...)
- }
- manager.Sleep()
- ok, err := isCurrentProcess()
- if err != nil {
- errs = append(errs, err)
- continue
- }
- if !ok {
- return nil
- }
- current, err := pasteFxn()
- if err != nil {
- errs = append(errs, err)
- continue
- }
- if current != "" {
- ok, err := wait(current, clipboard, manager, isCurrentProcess, pasteFxn)
- if err != nil {
- errs = append(errs, err)
- continue
- }
- if !ok {
- return nil
- }
- }
- errs = []error{}
- }
-}
-
-func wait(val string, clip Clipboard, mgr ClipboardDaemon, isCurrent func() (bool, error), pasteFxn func() (string, error)) (bool, error) {
- var count int64
- for count < clip.MaxTime {
- ok, err := isCurrent()
- if err != nil {
- return false, err
- }
- if !ok {
- return false, nil
- }
- cur, err := pasteFxn()
- if err != nil {
- return false, err
- }
- if cur != val {
- return true, nil
- }
- mgr.Sleep()
- count++
- }
- mgr.Copy(clip, "")
- return true, nil
-}
diff --git a/internal/platform/clipmanager_test.go b/internal/platform/clipmanager_test.go
@@ -1,188 +0,0 @@
-package platform_test
-
-import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- "git.sr.ht/~enckse/lockbox/internal/config/store"
- "git.sr.ht/~enckse/lockbox/internal/platform"
-)
-
-type mock struct {
- err error
- file string
- data string
- cmd string
- args []string
- pid int
- pasted int
-}
-
-func (d *mock) WriteFile(file, data string) {
- d.file = file
- d.data = data
-}
-
-func (d *mock) ReadFile(file string) ([]byte, error) {
- if file == "falsepid" {
- return []byte("1"), nil
- }
- d.file = file
- return []byte(d.data), d.err
-}
-
-func (d *mock) Output(cmd string, args ...string) ([]byte, error) {
- d.pasted++
- d.cmd = cmd
- d.args = args
- val := fmt.Sprintf("%d", min(d.pasted, 100))
- return []byte(val), d.err
-}
-
-func (d *mock) Start(cmd string, args ...string) error {
- d.cmd = cmd
- d.args = args
- return d.err
-}
-
-func (d *mock) Getpid() int {
- return d.pid
-}
-
-func (d *mock) Copy(_ platform.Clipboard, val string) {
- d.err = fmt.Errorf("copied%s: %d", val, d.pasted)
-}
-
-func (d *mock) Sleep() {
-}
-
-func (d *mock) Loader() platform.ClipboardLoader {
- return mockLoader{name: "linux", runtime: "linux"}
-}
-
-func (d *mock) Checkpid(_ int) error {
- return d.err
-}
-
-func TestErrors(t *testing.T) {
- store.Clear()
- defer store.Clear()
- t.Setenv("WAYLAND_DISPLAY", "1")
- if err := platform.ClipboardManager(nil, false, nil); err == nil || err.Error() != "manager is nil" {
- t.Errorf("invalid error: %v", err)
- }
- if err := platform.ClipboardManager(nil, false, &mock{}); err == nil || err.Error() != "pidfile is unset" {
- t.Errorf("invalid error: %v", err)
- }
- store.SetString("LOCKBOX_CLIP_PIDFILE", "a")
- if err := platform.ClipboardManager([]string{"x", "y"}, false, &mock{}); err == nil || err.Error() != "invalid manager arguments: [x y]" {
- t.Errorf("invalid error: %v", err)
- }
- if err := platform.ClipboardManager([]string{"x"}, false, &mock{}); err == nil || err.Error() != "invalid manager arguments: [x]" {
- t.Errorf("invalid error: %v", err)
- }
- m := &mock{}
- m.err = errors.New("xyz")
- if err := platform.ClipboardManager(nil, true, m); err == nil || strings.Count(err.Error(), "xyz") != 6 {
- t.Errorf("invalid error: %v", err)
- }
- if err := platform.ClipboardManager([]string{"x"}, true, m); err == nil || err.Error() != "invalid daemon arguments: [x]" {
- t.Errorf("invalid error: %v", err)
- }
-}
-
-func TestStartKill(t *testing.T) {
- store.Clear()
- defer store.Clear()
- store.SetString("LOCKBOX_CLIP_PIDFILE", "a")
- t.Setenv("WAYLAND_DISPLAY", "1")
- m := &mock{}
- if err := platform.ClipboardManager([]string{"-kill"}, false, m); err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if m.cmd != "" || fmt.Sprintf("%v", m.args) != "[]" {
- t.Errorf("invalid calls: %s %v", m.cmd, m.args)
- }
- pidFile := "testdata"
- os.MkdirAll(pidFile, 0o755)
- pidFile = filepath.Join("testdata", "pidfile")
- store.SetString("LOCKBOX_CLIP_PIDFILE", pidFile)
- os.WriteFile(pidFile, []byte("123"), 0o644)
- defer os.Remove(pidFile)
- m.cmd = ""
- m.args = []string{}
- if err := platform.ClipboardManager([]string{"-kill"}, false, m); err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if m.cmd != "" || fmt.Sprintf("%v", m.args) != "[]" {
- t.Errorf("invalid calls: %s %v", m.cmd, m.args)
- }
- b, err := os.ReadFile(pidFile)
- if err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if strings.TrimSpace(string(b)) != "0" {
- t.Errorf("invalid pid kill: %s", string(b))
- }
-}
-
-func TestStart(t *testing.T) {
- store.Clear()
- defer store.Clear()
- store.SetString("LOCKBOX_CLIP_PIDFILE", "a")
- t.Setenv("WAYLAND_DISPLAY", "1")
- m := &mock{}
- if err := platform.ClipboardManager(nil, false, m); err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if m.cmd != "lb" || fmt.Sprintf("%v", m.args) != "[clipmgrd]" {
- t.Errorf("invalid calls: %s %v", m.cmd, m.args)
- }
- pidFile := "testdata"
- os.MkdirAll(pidFile, 0o755)
- pidFile = filepath.Join("testdata", "pidfile")
- store.SetString("LOCKBOX_CLIP_PIDFILE", pidFile)
- os.WriteFile(pidFile, []byte("123"), 0o644)
- defer os.Remove(pidFile)
- m.cmd = ""
- m.args = []string{}
- if err := platform.ClipboardManager(nil, false, m); err == nil || !strings.Contains(err.Error(), "Atoi") {
- t.Errorf("invalid error: %v", err)
- }
- m.data = "1234"
- m.cmd = ""
- m.args = []string{}
- if err := platform.ClipboardManager(nil, false, m); err != nil {
- t.Errorf("invalid error: %v", err)
- }
- if m.cmd != "" || fmt.Sprintf("%v", m.args) != "[]" {
- t.Errorf("invalid calls: %s %v", m.cmd, m.args)
- }
-}
-
-func TestPIDMismatch(t *testing.T) {
- store.Clear()
- defer store.Clear()
- store.SetString("LOCKBOX_CLIP_PIDFILE", "falsepid")
- t.Setenv("WAYLAND_DISPLAY", "1")
- m := &mock{}
- if err := platform.ClipboardManager(nil, true, m); err != nil {
- t.Errorf("invalid error: %v", err)
- }
-}
-
-func TestChange(t *testing.T) {
- store.Clear()
- defer store.Clear()
- 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 := platform.ClipboardManager(nil, true, m); err == nil || strings.Count(err.Error(), "copied: 221") != 6 {
- t.Errorf("invalid error: %v", err)
- }
-}