// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package clipboard /* #include "watch.h" */ import "C" import ( "bufio" "context" "encoding/json" "errors" "io" "runtime" "time" ) type WatchSettings struct { Enabled bool Epoch uint64 } type TextChange struct { Text string Epoch uint64 Timestamp uint32 Ready bool } // WatchText keeps a GTK selection connection warm so successive clipboard // changes are captured asynchronously and emitted in arrival order. Disabled // applications never request clipboard targets or contents. func WatchText(ctx context.Context, input io.Reader, output io.Writer) error { runtime.LockOSThread() defer runtime.UnlockOSThread() ctx, cancel := context.WithCancel(ctx) defer cancel() settings := make(chan WatchSettings, 1) inputDone := make(chan error, 1) go func() { scanner := bufio.NewScanner(input) scanner.Buffer(make([]byte, 1024), 4096) for scanner.Scan() { var request WatchSettings if err := json.Unmarshal(scanner.Bytes(), &request); err != nil { inputDone <- err return } select { case settings <- request: case <-ctx.Done(): return } } inputDone <- scanner.Err() }() if C.clipboard_watch_init() == 0 { return errors.New("cannot watch clipboard on this display") } encoder := json.NewEncoder(output) if err := encoder.Encode(TextChange{Ready: true}); err != nil { return err } ticker := time.NewTicker(time.Millisecond) defer ticker.Stop() for { select { case <-ctx.Done(): return ctx.Err() case err := <-inputDone: return err case request := <-settings: enabled := C.int(0) if request.Enabled { enabled = 1 } C.clipboard_watch_enable(enabled, C.ulonglong(request.Epoch)) if err := encoder.Encode(TextChange{Ready: true, Epoch: request.Epoch}); err != nil { return err } case <-ticker.C: C.clipboard_watch_iterate() for { change := C.clipboard_watch_next() if change == nil { break } value := TextChange{Text: C.GoString(change.text), Epoch: uint64(change.epoch), Timestamp: uint32(change.timestamp)} C.clipboard_watch_free(change) if err := encoder.Encode(value); err != nil { return err } } } } }