LLMIFY THIS MOFO
CI / Test (push) Successful in 44s

This commit is contained in:
2026-09-01 01:49:40 -04:00
parent dad09d1109
commit 4426af63cb
19 changed files with 388 additions and 56 deletions
+141
View File
@@ -0,0 +1,141 @@
package injection
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"
"time"
)
// clipboardPasteBackend temporarily puts text on the regular clipboard, then
// uses wtype to issue Ctrl+V. This works in applications that do not support
// the primary selection, such as browsers and Electron apps. The previous
// text clipboard is restored after the paste has been delivered.
type clipboardPasteBackend struct {
clipboard *clipboardBackend
wtype *wtypeBackend
ctrlShiftVClasses []string
}
func NewClipboardPasteBackend(ctrlShiftVClasses []string) Backend {
return &clipboardPasteBackend{
clipboard: NewClipboardBackend().(*clipboardBackend),
wtype: NewWtypeBackend().(*wtypeBackend),
ctrlShiftVClasses: ctrlShiftVClasses,
}
}
func (c *clipboardPasteBackend) Name() string {
return "clipboard-paste"
}
func (c *clipboardPasteBackend) Available() error {
if err := c.clipboard.Available(); err != nil {
return err
}
if err := c.wtype.Available(); err != nil {
return err
}
return nil
}
func (c *clipboardPasteBackend) Inject(ctx context.Context, text string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
if err := c.Available(); err != nil {
return err
}
// wl-paste only needs to keep the clipboard owner alive while it reads the
// selection, so this command returns with the exact existing text content.
// --no-newline prevents wl-paste from adding a terminal-friendly newline.
previousClipboard, restore, err := c.readClipboard(ctx)
if err != nil {
return err
}
copyText := exec.CommandContext(ctx, "wl-copy")
copyText.Stdin = strings.NewReader(text)
if err := copyText.Run(); err != nil {
return fmt.Errorf("copy transcription to clipboard: %w", err)
}
// wl-copy returns before every client has observed the new selection. Give
// the compositor a short chance to publish it; this is still perceived as
// an immediate paste and avoids inserting the previous clipboard contents.
select {
case <-time.After(150 * time.Millisecond):
case <-ctx.Done():
return ctx.Err()
}
// wtype resolves the keysym itself, avoiding ydotool's physical-keycode
// layout problem on Dvorak and Colemak setups. Terminal emulators typically
// reserve Ctrl+V, so configured window classes use Ctrl+Shift+V instead.
args := []string{"-M", "ctrl"}
if c.shouldUseCtrlShiftV(ctx) {
args = append(args, "-M", "shift", "-k", "v", "-m", "shift")
} else {
args = append(args, "-k", "v")
}
args = append(args, "-m", "ctrl")
cmd := exec.CommandContext(ctx, "wtype", args...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("paste clipboard with wtype: %w", err)
}
if !restore {
return nil
}
// Receiving a Wayland paste is asynchronous. Leave the transcription
// available briefly, then put the user's prior clipboard back.
select {
case <-time.After(300 * time.Millisecond):
case <-ctx.Done():
return ctx.Err()
}
restoreClipboard := exec.CommandContext(ctx, "wl-copy")
restoreClipboard.Stdin = strings.NewReader(previousClipboard)
if err := restoreClipboard.Run(); err != nil {
return fmt.Errorf("restore previous clipboard: %w", err)
}
return nil
}
func (c *clipboardPasteBackend) shouldUseCtrlShiftV(ctx context.Context) bool {
if len(c.ctrlShiftVClasses) == 0 {
return false
}
cmd := exec.CommandContext(ctx, "hyprctl", "activewindow", "-j")
output, err := cmd.Output()
if err != nil {
return false
}
var activeWindow struct {
Class string `json:"class"`
}
if err := json.Unmarshal(output, &activeWindow); err != nil {
return false
}
class := strings.ToLower(activeWindow.Class)
for _, match := range c.ctrlShiftVClasses {
if match = strings.TrimSpace(strings.ToLower(match)); match != "" && strings.Contains(class, match) {
return true
}
}
return false
}
func (c *clipboardPasteBackend) readClipboard(ctx context.Context) (text string, restore bool, err error) {
cmd := exec.CommandContext(ctx, "wl-paste", "--no-newline")
output, err := cmd.Output()
if err != nil {
// No clipboard owner is normal. There is simply nothing to restore.
if _, ok := err.(*exec.ExitError); ok {
return "", false, nil
}
return "", false, fmt.Errorf("read existing clipboard: %w", err)
}
return string(output), true, nil
}