package clipboard import ( "bytes" "fmt" "os" "os/exec" "runtime" ) // Copy places RTF and HTML onto the system clipboard. // HTML is needed for web apps (Slack, Gmail); RTF for native apps (TextEdit, Word). func Copy(rtf, html []byte) error { switch runtime.GOOS { case "darwin": return copyDarwin(rtf, html) case "linux": return copyLinux(rtf, html) default: return fmt.Errorf("unsupported platform: %s", runtime.GOOS) } } func copyDarwin(rtf, html []byte) error { rtfFile, err := writeTempFile("mach-*.rtf", rtf) if err != nil { return err } defer os.Remove(rtfFile) htmlFile, err := writeTempFile("mach-*.html", html) if err != nil { return err } defer os.Remove(htmlFile) // Use Swift to set public.rtf, public.html, and public.utf8-plain-text // on NSPasteboard. All three are needed: // - public.html → web apps (Slack, Gmail, Google Docs) // - public.rtf → native apps (TextEdit, Word, Pages) // - public.string → plain text fallback (Slack/Electron requires this) swift := fmt.Sprintf(` import AppKit let rtfData = try! Data(contentsOf: URL(fileURLWithPath: "%s")) let htmlData = try! Data(contentsOf: URL(fileURLWithPath: "%s")) let htmlString = String(data: htmlData, encoding: .utf8) ?? "" let pb = NSPasteboard.general pb.clearContents() pb.setData(rtfData, forType: .rtf) pb.setData(htmlData, forType: .html) pb.setString(htmlString, forType: .string) `, rtfFile, htmlFile) cmd := exec.Command("swift", "-") cmd.Stdin = bytes.NewReader([]byte(swift)) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("clipboard write failed: %s: %w", string(out), err) } return nil } func copyLinux(rtf, html []byte) error { // xclip can only set one target at a time; prefer HTML for broader compat cmd := exec.Command("xclip", "-selection", "clipboard", "-t", "text/html", "-i") cmd.Stdin = bytes.NewReader(html) return cmd.Run() } func writeTempFile(pattern string, data []byte) (string, error) { f, err := os.CreateTemp("", pattern) if err != nil { return "", fmt.Errorf("failed to create temp file: %w", err) } if _, err := f.Write(data); err != nil { f.Close() os.Remove(f.Name()) return "", fmt.Errorf("failed to write temp file: %w", err) } f.Close() return f.Name(), nil }