commit 7a7be64c4577f260441f4ade1f1b86ffd3b31b5b
parent 4f250f04cf94416445369fb917ae3b6d38ad2c78
Author: Sean Enck <sean@ttypty.com>
Date: Sun, 29 Jun 2025 23:21:58 -0400
add clipmgr help, add an ability to stop the daemon
Diffstat:
7 files changed, 91 insertions(+), 12 deletions(-)
diff --git a/cmd/lb/main.go b/cmd/lb/main.go
@@ -44,7 +44,7 @@ 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(command == commands.ClipManagerDaemon, platform.DefaultClipboardDaemon{})
+ return true, platform.ClipboardManager(args, command == commands.ClipManagerDaemon, platform.DefaultClipboardDaemon{})
}
return false, nil
}
diff --git a/internal/app/commands/core.go b/internal/app/commands/core.go
@@ -68,6 +68,8 @@ const (
TOTPURL = "url"
// TOTPSeed will display the seed for the TOTP tokens
TOTPSeed = "seed"
+ // ClipManagerStop will stop the clipboard manager
+ ClipManagerStop = "-kill"
)
var (
diff --git a/internal/app/help/core.go b/internal/app/help/core.go
@@ -38,6 +38,8 @@ type (
HelpConfigCommand string
NoColor string
ReadOnlyCommands string
+ ClipManager string
+ ClipManagerStop string
Config struct {
Env string
Home string
@@ -105,6 +107,9 @@ 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 {
@@ -117,6 +122,8 @@ 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) != 27 {
+ if len(u) != 30 {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = help.Usage(true, "lb")
- if len(u) != 128 {
+ if len(u) != 137 {
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
@@ -0,0 +1,5 @@
+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/platform/clipmanager.go b/internal/platform/clipmanager.go
@@ -81,7 +81,7 @@ func (d DefaultClipboardDaemon) Checkpid(pid int) error {
}
// ClipboardManager handles the daemon runner
-func ClipboardManager(daemon bool, manager ClipboardDaemon) error {
+func ClipboardManager(args []string, daemon bool, manager ClipboardDaemon) error {
if manager == nil {
return errors.New("manager is nil")
}
@@ -101,6 +101,24 @@ func ClipboardManager(daemon bool, manager ClipboardDaemon) error {
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 {
@@ -116,6 +134,9 @@ func ClipboardManager(daemon bool, manager ClipboardDaemon) error {
}
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
diff --git a/internal/platform/clipmanager_test.go b/internal/platform/clipmanager_test.go
@@ -72,18 +72,62 @@ func TestErrors(t *testing.T) {
store.Clear()
defer store.Clear()
t.Setenv("WAYLAND_DISPLAY", "1")
- if err := platform.ClipboardManager(false, nil); err == nil || err.Error() != "manager is nil" {
+ if err := platform.ClipboardManager(nil, false, nil); err == nil || err.Error() != "manager is nil" {
t.Errorf("invalid error: %v", err)
}
- if err := platform.ClipboardManager(false, &mock{}); err == nil || err.Error() != "pidfile is unset" {
+ 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(true, m); err == nil || strings.Count(err.Error(), "xyz") != 6 {
+ 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) {
@@ -92,7 +136,7 @@ func TestStart(t *testing.T) {
store.SetString("LOCKBOX_CLIP_PIDFILE", "a")
t.Setenv("WAYLAND_DISPLAY", "1")
m := &mock{}
- if err := platform.ClipboardManager(false, m); err != nil {
+ 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]" {
@@ -106,13 +150,13 @@ func TestStart(t *testing.T) {
defer os.Remove(pidFile)
m.cmd = ""
m.args = []string{}
- if err := platform.ClipboardManager(false, m); err == nil || !strings.Contains(err.Error(), "Atoi") {
+ 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(false, m); err != nil {
+ if err := platform.ClipboardManager(nil, false, m); err != nil {
t.Errorf("invalid error: %v", err)
}
if m.cmd != "" || fmt.Sprintf("%v", m.args) != "[]" {
@@ -126,7 +170,7 @@ func TestPIDMismatch(t *testing.T) {
store.SetString("LOCKBOX_CLIP_PIDFILE", "falsepid")
t.Setenv("WAYLAND_DISPLAY", "1")
m := &mock{}
- if err := platform.ClipboardManager(true, m); err != nil {
+ if err := platform.ClipboardManager(nil, true, m); err != nil {
t.Errorf("invalid error: %v", err)
}
}
@@ -138,7 +182,7 @@ func TestChange(t *testing.T) {
t.Setenv("WAYLAND_DISPLAY", "1")
m := &mock{}
// NOTE: 100 (count before static) + 120 (default timeout) + 1 (caused break of loop)
- if err := platform.ClipboardManager(true, m); err == nil || strings.Count(err.Error(), "copied: 221") != 6 {
+ if err := platform.ClipboardManager(nil, true, m); err == nil || strings.Count(err.Error(), "copied: 221") != 6 {
t.Errorf("invalid error: %v", err)
}
}