返回 DeepSeek-Reasonix
clipboard_wsl.go
根目录 / internal / cli / clipboard_wsl.go
1 package cli
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "os/exec"
8 "runtime"
9 "strings"
10 "time"
11
12 "github.com/atotto/clipboard"
13
14 "reasonix/internal/proc"
15 )
16
17 // wslClipboardWriteScript reads stdin as UTF-8 and lets Windows PowerShell
18 // perform the UTF-16 clipboard write. Passing text through clip.exe instead
19 // makes the Windows program decode the raw UTF-8 bytes using the active OEM
20 // code page (CP936 on Chinese systems), producing mojibake such as 中文 -> 涓枃.
21 const wslClipboardWriteScript = `$stdin = [Console]::OpenStandardInput()
22 $reader = [IO.StreamReader]::new($stdin, [Text.UTF8Encoding]::new($false))
23 Set-Clipboard -Value $reader.ReadToEnd()`
24
25 // writeClipboardText uses the normal platform clipboard outside WSL. WSL is a
26 // Linux process: preserve its clipboard utilities, but replace the clip.exe
27 // fallback that would otherwise decode UTF-8 using a Windows code page.
28 func writeClipboardText(text string) error {
29 if isWSL() {
30 return writeWSLClipboardText(text)
31 }
32 return clipboard.WriteAll(text)
33 }
34
35 func isWSL() bool {
36 return isWSLFor(runtime.GOOS, os.Getenv)
37 }
38
39 func isWSLFor(goos string, getenv func(string) string) bool {
40 if goos != "linux" {
41 return false
42 }
43 return getenv("WSL_DISTRO_NAME") != "" || getenv("WSL_INTEROP") != ""
44 }
45
46 func writeWSLClipboardText(text string) error {
47 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
48 defer cancel()
49 cmd := newWSLClipboardCommand(ctx, text)
50 var stderr strings.Builder
51 cmd.Stderr = &stderr
52 if err := cmd.Run(); err != nil {
53 if ctx.Err() != nil {
54 return fmt.Errorf("write WSL clipboard: %w", ctx.Err())
55 }
56 if detail := strings.TrimSpace(stderr.String()); detail != "" {
57 return fmt.Errorf("write WSL clipboard: %w: %s", err, detail)
58 }
59 return fmt.Errorf("write WSL clipboard: %w", err)
60 }
61 return nil
62 }
63
64 func newWSLClipboardCommand(ctx context.Context, text string) *exec.Cmd {
65 args := []string{
66 "powershell.exe",
67 "-NoProfile",
68 "-NonInteractive",
69 "-Command",
70 wslClipboardWriteScript,
71 }
72 // Keep native Linux clipboard support when Windows interop is unavailable.
73 switch {
74 case os.Getenv("WAYLAND_DISPLAY") != "" && clipboardCommandAvailable("wl-copy"):
75 args = []string{"wl-copy"}
76 case clipboardCommandAvailable("xclip"):
77 args = []string{"xclip", "-in", "-selection", "clipboard"}
78 case clipboardCommandAvailable("xsel"):
79 args = []string{"xsel", "--input", "--clipboard"}
80 }
81 cmd := proc.CommandContext(ctx, args[0], args[1:]...)
82 proc.SetCancelKillsTree(cmd)
83 // Bound pipe draining too, if an interop child inherits an output handle.
84 cmd.WaitDelay = time.Second
85 cmd.Stdin = strings.NewReader(text)
86 return cmd
87 }
88
89 func clipboardCommandAvailable(name string) bool {
90 _, err := exec.LookPath(name)
91 return err == nil
92 }
93
93 lines GO