Files
mach/clipboard/clipboard.go
Yehoshua Adam Sandler e70b629b4a init commit
2026-02-05 15:33:07 -06:00

82 lines
2.2 KiB
Go

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
}