90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
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}
|
|
}
|
|
}
|