package injection import ( "context" "fmt" "os" "os/exec" "strings" "time" ) // clipboardPasteBackend copies text to the primary selection and pastes it // with ydotool's layout-independent Shift+Insert key codes. // // This preserves the regular clipboard, is independent of the active keyboard // layout, and delivers the whole string at once. type clipboardPasteBackend struct { clipboard *clipboardBackend ydotool *ydotoolBackend } func NewClipboardPasteBackend() Backend { return &clipboardPasteBackend{ clipboard: NewClipboardBackend().(*clipboardBackend), ydotool: NewYdotoolBackend().(*ydotoolBackend), } } 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.ydotool.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 } // Shift+Insert pastes the primary selection, leaving the user's regular // clipboard untouched. primaryCopy := exec.CommandContext(ctx, "wl-copy", "--primary") primaryCopy.Stdin = strings.NewReader(text) if err := primaryCopy.Run(); err != nil { return fmt.Errorf("copy transcription to primary selection: %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() } // Linux input event codes: KEY_LEFTSHIFT=42, KEY_INSERT=110. These keys do // not depend on the alphabetic layout, and Shift+Insert is widely supported. cmd := exec.CommandContext(ctx, "ydotool", "key", "42:1", "110:1", "110:0", "42:0") if socketPath := c.ydotool.getSocketPath(); socketPath != "" { cmd.Env = append(os.Environ(), "YDOTOOL_SOCKET="+socketPath) } if err := cmd.Run(); err != nil { return fmt.Errorf("paste primary selection with ydotool: %w", err) } return nil }