11
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
)
|
||||
|
||||
// errNoClipboardImage means the system clipboard holds no image data.
|
||||
var errNoClipboardImage = errors.New("剪贴板没有图片")
|
||||
|
||||
// clipboardRunner is the injectable command runner (replaced in tests).
|
||||
var clipboardRunner = func(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).Output()
|
||||
}
|
||||
|
||||
// imageMediaExt maps clipboard image MIME types to file extensions, in
|
||||
// preference order.
|
||||
var imageMediaExt = []struct{ mime, ext string }{
|
||||
{"image/png", "png"},
|
||||
{"image/jpeg", "jpg"},
|
||||
{"image/jpg", "jpg"},
|
||||
{"image/gif", "gif"},
|
||||
{"image/webp", "webp"},
|
||||
}
|
||||
|
||||
func pickImageMime(types string) (mime, ext string) {
|
||||
for _, m := range imageMediaExt {
|
||||
if strings.Contains(types, m.mime) {
|
||||
return m.mime, m.ext
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// readClipboardImage reads an image off the system clipboard via the available
|
||||
// helper (wl-paste on Wayland, xclip on X11). Returns errNoClipboardImage when
|
||||
// the clipboard holds no image.
|
||||
func readClipboardImage() (data []byte, ext string, err error) {
|
||||
if _, e := exec.LookPath("wl-paste"); e == nil {
|
||||
types, _ := clipboardRunner("wl-paste", "--list-types")
|
||||
mime, ext := pickImageMime(string(types))
|
||||
if mime == "" {
|
||||
return nil, "", errNoClipboardImage
|
||||
}
|
||||
data, err := clipboardRunner("wl-paste", "--type", mime)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("wl-paste: %w", err)
|
||||
}
|
||||
return data, ext, nil
|
||||
}
|
||||
if _, e := exec.LookPath("xclip"); e == nil {
|
||||
targets, _ := clipboardRunner("xclip", "-selection", "clipboard", "-t", "TARGETS", "-o")
|
||||
mime, ext := pickImageMime(string(targets))
|
||||
if mime == "" {
|
||||
return nil, "", errNoClipboardImage
|
||||
}
|
||||
data, err := clipboardRunner("xclip", "-selection", "clipboard", "-t", mime, "-o")
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("xclip: %w", err)
|
||||
}
|
||||
return data, ext, nil
|
||||
}
|
||||
return nil, "", errors.New("未找到剪贴板工具 (wl-paste/xclip)")
|
||||
}
|
||||
|
||||
// pasteImageCmd reads a clipboard image and writes it to the inbox as
|
||||
// <index>.<ext>, mirroring the VSCode spx-inbox scheme.
|
||||
func pasteImageCmd(inboxDir string, index int) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
data, ext, err := readClipboardImage()
|
||||
if err != nil {
|
||||
return imagePastedMsg{err: err}
|
||||
}
|
||||
if err := os.MkdirAll(inboxDir, 0o755); err != nil {
|
||||
return imagePastedMsg{err: fmt.Errorf("创建 inbox: %w", err)}
|
||||
}
|
||||
path := filepath.Join(inboxDir, fmt.Sprintf("%d.%s", index, ext))
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return imagePastedMsg{err: fmt.Errorf("写入图片: %w", err)}
|
||||
}
|
||||
return imagePastedMsg{path: path, index: index}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user