feat: add stop command to terminate the clipboard watcher daemon

This commit is contained in:
Menno van Leeuwen 2025-05-21 00:07:48 +02:00
parent 2e77ee2333
commit 72705afc16
Signed by: vleeuwenmenno
SSH Key Fingerprint: SHA256:OJFmjANpakwD3F2Rsws4GLtbdz1TJ5tkQF0RZmF0TRE
5 changed files with 102 additions and 13 deletions

View File

@ -1,6 +1,8 @@
package commands package commands
import ( import (
"fmt"
"os"
"os/exec" "os/exec"
"strings" "strings"
@ -14,14 +16,26 @@ func NewStatusCmd() *cobra.Command {
Use: "status", Use: "status",
Short: "Check if the clipboard watcher daemon is running", Short: "Check if the clipboard watcher daemon is running",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Check for the process by name (simple approach) out, err := exec.Command("ps", "-C", "kcm", "-o", "pid=").Output()
out, err := exec.Command("ps", "-C", "kcm").Output()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to check kcm daemon status") log.Fatal().Err(err).Msg("Failed to check kcm daemon status")
} }
lines := strings.Split(string(out), "\n") pids := strings.Fields(string(out))
if len(lines) > 1 { currentPID := os.Getpid()
log.Info().Msg("kcm daemon is running.") filteredPIDs := make([]string, 0, len(pids))
for _, pidStr := range pids {
if pidStr == "" {
continue
}
if pidStr == fmt.Sprintf("%d", currentPID) {
continue
}
filteredPIDs = append(filteredPIDs, pidStr)
}
if len(filteredPIDs) > 0 {
log.Info().
Strs("pids", filteredPIDs).
Msg("kcm daemon is running.")
} else { } else {
log.Warn().Msg("kcm daemon is not running, start a new one using `kcm watch`.") log.Warn().Msg("kcm daemon is not running, start a new one using `kcm watch`.")
} }

54
src/commands/cmd_stop.go Normal file
View File

@ -0,0 +1,54 @@
package commands
import (
"os"
"os/exec"
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
// NewStopCmd creates a new stop command to kill the kcm daemon.
func NewStopCmd() *cobra.Command {
return &cobra.Command{
Use: "stop",
Short: "Stop the clipboard watcher daemon",
Aliases: []string{"--stop", "kill"},
Run: func(cmd *cobra.Command, args []string) {
out, err := exec.Command("ps", "-C", "kcm", "-o", "pid=").Output()
if err != nil {
log.Fatal().Err(err).Msg("Failed to check for running kcm processes")
}
pids := strings.Fields(string(out))
if len(pids) == 0 {
log.Info().Msg("No running kcm daemon found.")
return
}
killed := 0
for _, pidStr := range pids {
pid, err := strconv.Atoi(pidStr)
if err != nil {
continue
}
if pid == os.Getpid() {
continue // Don't kill self
}
proc, err := os.FindProcess(pid)
if err == nil {
err = proc.Kill()
if err == nil {
log.Info().Msgf("Killed kcm daemon with PID %d", pid)
killed++
}
}
}
if killed == 0 {
log.Warn().Msg("No kcm daemons were killed.")
} else {
log.Info().Msgf("Stopped %d kcm daemon(s).", killed)
}
},
}
}

View File

@ -2,6 +2,9 @@ package commands
import ( import (
"os" "os"
"os/exec"
"strconv"
"strings"
"time" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -20,9 +23,22 @@ func NewWatchCmd(history *models.History) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
noDaemon, _ := cmd.Flags().GetBool("no-daemon") noDaemon, _ := cmd.Flags().GetBool("no-daemon")
if !noDaemon { if !noDaemon {
cfg, _ := config.LoadConfig() // Safety net: check if kcm daemon is already running (ignore self)
out, err := exec.Command("ps", "-C", "kcm", "-o", "pid=").Output()
if err == nil {
pids := strings.Fields(string(out))
myPid := os.Getpid()
for _, pidStr := range pids {
pid, err := strconv.Atoi(pidStr)
if err == nil && pid != myPid {
log.Warn().Msg("kcm daemon is already running. Exiting to avoid duplicate daemons.")
os.Exit(1)
}
}
}
// Set log file path since we are daemonizing // Set log file path since we are daemonizing
cfg, _ := config.LoadConfig()
if cfg.Logging.Path != "" { if cfg.Logging.Path != "" {
file, err := os.OpenFile(cfg.Logging.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) file, err := os.OpenFile(cfg.Logging.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil { if err != nil {
@ -46,13 +62,13 @@ func NewWatchCmd(history *models.History) *cobra.Command {
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to daemonize") log.Fatal().Err(err).Msg("Failed to daemonize")
} }
log.Info().Msgf("Daemon started with PID %d", proc.Pid) log.Info().
Str("logging_path", cfg.Logging.Path).
Msgf("Daemon started with PID %d", proc.Pid)
os.Exit(0) os.Exit(0)
} }
} }
log.Info().Msg("Starting clipboard watcher (golang.design/x/clipboard)...")
if err := clipboard.Init(); err != nil { if err := clipboard.Init(); err != nil {
log.Fatal().Err(err).Msg("Failed to initialize clipboard") log.Fatal().Err(err).Msg("Failed to initialize clipboard")
} }
@ -63,7 +79,7 @@ func NewWatchCmd(history *models.History) *cobra.Command {
) )
for { for {
time.Sleep(500 * time.Millisecond) time.Sleep(250 * time.Millisecond)
textOut := clipboard.Read(clipboard.FmtText) textOut := clipboard.Read(clipboard.FmtText)
imgOut := clipboard.Read(clipboard.FmtImage) imgOut := clipboard.Read(clipboard.FmtImage)
@ -79,7 +95,7 @@ func NewWatchCmd(history *models.History) *cobra.Command {
} }
history.Add(item) history.Add(item)
lastText = text lastText = text
log.Info().Str("content", text).Msg("Text clipboard item added to history") log.Info().Str("content", text).Msg("Text item added ")
} }
} }
@ -92,7 +108,7 @@ func NewWatchCmd(history *models.History) *cobra.Command {
} }
history.Add(item) history.Add(item)
lastImage = imgOut lastImage = imgOut
log.Info().Msg("Image clipboard item added to history") log.Info().Msg("Image item added")
} }
} }
}, },

View File

@ -37,11 +37,15 @@ func LoadConfig() (Config, string) {
if err != nil { if err != nil {
continue continue
} }
defer file.Close()
d := yaml.NewDecoder(file) d := yaml.NewDecoder(file)
if err := d.Decode(&cfg); err == nil { if err := d.Decode(&cfg); err == nil {
file.Close()
if cfg.Logging.Path == "" {
cfg.Logging.Path = logFilePath()
}
return cfg, path return cfg, path
} }
file.Close()
} }
cfg.Logging.Format = "console" cfg.Logging.Format = "console"

View File

@ -56,6 +56,7 @@ func main() {
commands.NewSearchCmd(history), commands.NewSearchCmd(history),
commands.NewStatusCmd(), commands.NewStatusCmd(),
commands.NewVersionCmd(), commands.NewVersionCmd(),
commands.NewStopCmd(), // Add the stop command
) )
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {