Files

101 lines
2.2 KiB
Go

// 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
}
}
}
}
}