119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
package commands
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/spf13/cobra"
|
|
"github.com/vleeuwenmenno/kcm/src/config"
|
|
"github.com/vleeuwenmenno/kcm/src/models"
|
|
"golang.design/x/clipboard"
|
|
)
|
|
|
|
func NewWatchCmd(history *models.History) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "watch",
|
|
Short: "Watch clipboard and store history",
|
|
Aliases: []string{"--watch"},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
noDaemon, _ := cmd.Flags().GetBool("no-daemon")
|
|
if !noDaemon {
|
|
// 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
|
|
cfg, _ := config.LoadConfig()
|
|
if cfg.Logging.Path != "" {
|
|
file, err := os.OpenFile(cfg.Logging.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
log.Fatal().AnErr("err", err).Msg("Failed to open log file")
|
|
}
|
|
multi := zerolog.MultiLevelWriter(file, os.Stderr)
|
|
log.Logger = log.Output(multi)
|
|
}
|
|
|
|
// Daemonize: fork and detach
|
|
execPath, err := os.Executable()
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Failed to get executable path for daemonizing")
|
|
}
|
|
if os.Getppid() != 1 {
|
|
attr := &os.ProcAttr{
|
|
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
|
|
Env: os.Environ(),
|
|
}
|
|
proc, err := os.StartProcess(execPath, os.Args, attr)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Failed to daemonize")
|
|
}
|
|
log.Info().
|
|
Str("logging_path", cfg.Logging.Path).
|
|
Msgf("Daemon started with PID %d", proc.Pid)
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
if err := clipboard.Init(); err != nil {
|
|
log.Fatal().Err(err).Msg("Failed to initialize clipboard")
|
|
}
|
|
|
|
var (
|
|
lastText string
|
|
lastImage []byte
|
|
)
|
|
|
|
for {
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
textOut := clipboard.Read(clipboard.FmtText)
|
|
imgOut := clipboard.Read(clipboard.FmtImage)
|
|
|
|
if len(textOut) > 0 {
|
|
text := string(textOut)
|
|
if text != lastText {
|
|
item := models.HistoryItem{
|
|
Data: []byte(text),
|
|
DataType: 0, // 0 for text
|
|
Timestamp: time.Now(),
|
|
Pinned: false,
|
|
}
|
|
history.Add(item)
|
|
lastText = text
|
|
log.Info().Str("content", text).Msg("Text item added ")
|
|
}
|
|
}
|
|
|
|
if len(imgOut) > 0 && (lastImage == nil || string(imgOut) != string(lastImage)) {
|
|
item := models.HistoryItem{
|
|
Data: imgOut,
|
|
DataType: 1, // 1 for image/png
|
|
Timestamp: time.Now(),
|
|
Pinned: false,
|
|
}
|
|
history.Add(item)
|
|
lastImage = imgOut
|
|
log.Info().Msg("Image item added")
|
|
}
|
|
}
|
|
},
|
|
}
|
|
cmd.Flags().Bool("no-daemon", false, "Run in foreground (do not daemonize)")
|
|
return cmd
|
|
}
|