package app import ( "bytes" "context" "image" "image/png" "net/url" "os" "os/exec" "path/filepath" "testing" "time" "git.stormux.org/storm/cthulhu/internal/tentaclewire" ) func TestTentacleClipboardRequiresOneLocalFile(t *testing.T) { for _, raw := range []string{"file:///tmp/a%20b\r\n", "copy\nfile:///tmp/a%20b\n", "cut\nfile://localhost/tmp/a%20b\n", "file:///tmp/a%20b\x00", "file:///tmp/a%20b\r\n\x00", "copy\nfile:///tmp/a%20b\n\x00"} { value, err := singleClipboardURI(raw) if err != nil || value.Path != "/tmp/a b" { t.Fatal(value, err) } } for _, raw := range []string{"", "file:///tmp/a\nfile:///tmp/b\n", "file://remote/tmp/a", "https://example.org/a", "file:///tmp/a?query", "file:///tmp/a\x00file:///tmp/b", "file:///tmp/a\x00\x00", "file:///tmp/a%00b", "file:///tmp/a\nfile:///tmp/b\x00"} { if _, err := singleClipboardURI(raw); err == nil { t.Fatal("accepted", raw) } } } func TestNativeTentacleClipboardTargetsAndTemporaryImage(t *testing.T) { if os.Getenv("CTHULHU_NATIVE_INTERNAL_UI") != "1" { t.Skip("requires isolated Xorg clipboard") } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() set := func(target string, data []byte) { t.Helper() command := exec.CommandContext(ctx, "xclip", "-selection", "clipboard", "-in", "-target", target) command.Stdin = bytes.NewReader(data) if err := command.Run(); err != nil { t.Fatal(err) } } path := filepath.Join(t.TempDir(), "one file.txt") if err := os.WriteFile(path, []byte("fixture"), 0600); err != nil { t.Fatal(err) } uri := (&url.URL{Scheme: "file", Path: path}).String() var item tentaclewire.Item var err error for _, selection := range []struct{ target, data string }{ {"text/uri-list", uri + "\r\n"}, {"text/uri-list", uri + "\x00"}, // GTK file-manager selection terminator. {"x-special/gnome-copied-files", "copy\n" + uri + "\n\x00"}, } { set(selection.target, []byte(selection.data)) item, err = readTentacleClipboard(ctx) if err != nil || item.Path != path || item.Temporary { t.Fatal(selection.target, item, err) } } set("text/uri-list", []byte(uri+"\nfile:///tmp/another\n")) if _, err = readTentacleClipboard(ctx); err == nil { t.Fatal("accepted multiple file-manager clipboard items") } text := "one\ntwo é猫" if err = copyTentacleText(ctx, text); err != nil { t.Fatal(err) } item, err = readTentacleClipboard(ctx) if err != nil || item.Text != text || item.Path != "" { t.Fatal(item, err) } var bitmap bytes.Buffer if err = png.Encode(&bitmap, image.NewRGBA(image.Rect(0, 0, 2, 2))); err != nil { t.Fatal(err) } set("image/png", bitmap.Bytes()) item, err = readTentacleClipboard(ctx) if err != nil || !item.Temporary || filepath.Base(item.Path) != "clipboard.png" { t.Fatal(item, err) } defer os.RemoveAll(filepath.Dir(item.Path)) data, err := os.ReadFile(item.Path) if err != nil || !bytes.Equal(data, bitmap.Bytes()) { t.Fatal("temporary clipboard image changed", err) } if info, _ := os.Stat(item.Path); info.Mode().Perm() != 0600 { t.Fatal("temporary image is not private") } }