Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
236cd0a
feat(#58): added `golang.design/x/clipboard` package
AlejandroSuero May 19, 2024
37dafec
feat(#58): added `--copy` flag and config setting
AlejandroSuero May 19, 2024
d4d556c
test(#58): tested `--copy` flag
AlejandroSuero May 19, 2024
4c4f2bd
docs(#58): update `README.md`, golden and `full.json` with `--copy`
AlejandroSuero May 19, 2024
eba42f9
fix(test): writting image to clipboard
AlejandroSuero May 27, 2024
c57172d
feat: added `clipboard.Read` and modified `copy` in case of conflict
AlejandroSuero May 31, 2024
96cb243
test: updated test to follow `TestFreezeConfigurations`
AlejandroSuero Jun 5, 2024
197f8b9
refactor: `copyToClipboard` function
AlejandroSuero Jun 9, 2024
bb68f3b
feat: copy when using `libsvg`
AlejandroSuero Jun 9, 2024
f678cda
test(copy): improved performance using `svg`
AlejandroSuero Jun 9, 2024
153ba1f
feat: copy when output not only `.png`
AlejandroSuero Jun 9, 2024
15accc9
added comment while figuring out a solution
AlejandroSuero Jun 11, 2024
164655c
feat: modified to work on non-png formats as text
AlejandroSuero Jun 12, 2024
9a169d5
refactor(copy): `--copy` -> `--output clipboard`
AlejandroSuero Jun 12, 2024
01d684a
refactor(copy): moved logic to function
AlejandroSuero Jun 12, 2024
457f7d4
fix: unnecessary if statment
AlejandroSuero Jun 12, 2024
b88ebf7
feat: added `copy` output to copy to clipboard
AlejandroSuero Jun 12, 2024
724101b
Merge branch 'main' into feature/add-copy-to-clipboard
AlejandroSuero Sep 20, 2024
6d74d8c
fix: build issues after solving conflicts
AlejandroSuero Sep 20, 2024
87af18f
feat(clipboard): remove copy as output source
bashbunni Dec 5, 2024
66788be
Merge branch 'main' into feature/add-copy-to-clipboard
AlejandroSuero Jan 17, 2025
f2263b6
fix: `go mod tidy` and `make golden`
AlejandroSuero Jan 18, 2025
8233445
fix: refactor to use `config.Output`
AlejandroSuero Jan 18, 2025
74cf67b
Merge branch 'main' into feature/add-copy-to-clipboard
AlejandroSuero Apr 17, 2025
064830d
fix(ci): install dep for clipboard package linux
bashbunni Jun 12, 2025
5052219
fix(ci): add linux dep to lint workflow
bashbunni Jun 12, 2025
f911601
fix(ci): include tests in build
bashbunni Jun 12, 2025
4e89c3b
fix(ci): install dep properly
bashbunni Jun 12, 2025
ac4cc5f
fix(ci): install xvfb for clipboard dep
bashbunni Jun 12, 2025
00fda87
fix(ci): fix order of deps
bashbunni Jun 12, 2025
1165d85
fix(ci): typo
bashbunni Jun 12, 2025
4e6c075
fix: add fatal log for unhandled err
bashbunni Jun 12, 2025
5848393
merge main
bashbunni Aug 28, 2025
9db8cd1
feat: add x11/wayland support for copy/paste
bashbunni Aug 28, 2025
117aa5c
ci: add action for clipboard tests on different display managers
bashbunni Aug 28, 2025
14d364d
fix(ci): add libx11-dev dep for clipboard pkg
bashbunni Aug 28, 2025
c772430
fix(ci): no clipboard available in CI
bashbunni Aug 29, 2025
87d9096
feat: don't handle wayland and x11
bashbunni Aug 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Install deps
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libx11-dev xvfb
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
echo "DISPLAY=:99" >> $GITHUB_ENV
- name: Install Go
uses: actions/setup-go@v5
with:
Expand All @@ -25,8 +32,8 @@ jobs:
- name: Build
run: go build -v ./...

#- name: Test
# run: go test -v -cover -timeout=30s ./...
- name: Test
run: go test -v -cover -timeout=30s ./...
dependabot:
needs: [build]
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- name: Install deps
if: runner.os == 'Linux'
run: sudo apt install libx11-dev
- name: Install Go
uses: actions/setup-go@v5
with:
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,32 @@ freeze main.go --output out.webp
freeze main.go --output out.{svg,png,webp}
```

### Copy

Copy the output image to your clipboard, so you can paste it anywhere. Freeze
uses `golang.design/clipboard` under the hood which supports macOS, Linux (x11),
and Windows.

```bash
freeze main.go --output clipboard
```

#### Copy to clipboard (Wayland, x11)

Since there are many different display servers out there that all manage
clipboards differently, you can pipe freeze images to your clipboard.

> [!TIP]
> Freeze will automatically output images to stdout if it's used in a pipeline.

``` bash
# x11
freeze artichoke.hs | xclip -t image/png -selection clipboard

# Wayland
wl-copy < $(freeze artichoke.hs)
```

### Font

Specify the font family, font size, and font line height of the output image.
Expand Down
5 changes: 3 additions & 2 deletions configurations/full.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
"size": 14,
"ligatures": true
},
"line_height": 1.2
}
"line_height": 1.2,
"copy": false
}
34 changes: 34 additions & 0 deletions freeze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/aymanbagabas/go-udiff"
"golang.design/x/clipboard"
)

const binary = "./test/freeze-test"
Expand Down Expand Up @@ -58,6 +59,30 @@ func TestFreezeOutput(t *testing.T) {
}
}

func TestClipboard(t *testing.T) {
// Clipboard can't be tested in headless CI. Only run this test with a
// real display.
if os.Getenv("CI") != "" {
t.Skip("Clipboard tests require real display server")
}
output := "clipboard"
defer os.Remove(output)

cmd := exec.Command(binary, "test/input/bubbletea.model", "-o", output, "--language", "go", "--height", "800", "--width", "750", "--config", "full", "--window=false", "--show-line-numbers")
err := cmd.Run()
if err != nil {
t.Fatal(err)
}
err = clipboard.Init()
if err != nil {
t.Fatal(err)
}
png := clipboard.Read(clipboard.FmtImage)
if png == nil {
t.Fatal("clipboard is empty")
}
}

func TestFreezeHelp(t *testing.T) {
out := bytes.Buffer{}
cmd := exec.Command(binary)
Expand Down Expand Up @@ -136,6 +161,15 @@ func TestFreezeConfigurations(t *testing.T) {
flags: []string{"--language", "go", "--height", "800", "--width", "750", "--config", "full", "--window=false", "--show-line-numbers"},
output: "bubbletea",
},
{
input: "test/input/bubbletea.model",
flags: []string{"--language", "go", "--height", "800", "--width", "750", "--config", "full", "--window=false", "--show-line-numbers"},
output: "bubbletea-copy",
},
// {
// flags: []string{"--execute", "layout", "--height", "800", "--config", "full", "--margin", "50,10"},
// output: "composite-2",
// },
{
input: "test/input/layout.ansi",
flags: []string{},
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.16
golang.design/x/clipboard v0.7.0
)

require (
Expand Down Expand Up @@ -49,6 +50,9 @@ require (
github.com/tetratelabs/wazero v1.8.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/exp/shiny v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,16 @@ github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmc
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/exp/shiny v0.0.0-20240506185415-9bf2ced13842 h1:kEvPiBVeT1JJGw/3THfe1W1zvTAvU1V6pCFV0icZvQs=
golang.org/x/exp/shiny v0.0.0-20240506185415-9bf2ced13842/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
10 changes: 10 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ func main() {

istty := isatty.IsTerminal(os.Stdout.Fd())

if config.Output == "clipboard" { // convert to png because we can't copy svg to clipboard
config.Output = "clipboard.png"
}
switch {
case strings.HasSuffix(config.Output, ".png"):
// use libsvg conversion.
Expand All @@ -402,6 +405,9 @@ func main() {
printFilenameOutput(config.Output)
break
}
if svgConversionErr != nil {
printErrorFatal("Oops, came across an error", svgConversionErr)
}

// could not convert with libsvg, try resvg
svgConversionErr = resvgConvert(doc, imageWidth, imageHeight, config.Output)
Expand All @@ -413,6 +419,10 @@ func main() {
default:
// output file specified.
if config.Output != "" {
_, err := doc.WriteToBytes()
if err != nil {
printErrorFatal("Unable to write output", err)
}
err = doc.WriteToFile(config.Output)
if err != nil {
printErrorFatal("Unable to write output", err)
Expand Down
35 changes: 31 additions & 4 deletions png.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,34 @@ import (
"context"
"os"
"os/exec"
"strings"

"github.com/beevik/etree"
"github.com/charmbracelet/freeze/font"
"github.com/kanrichan/resvg-go"
"golang.design/x/clipboard"
)

func copyToClipboard(path string) error {
err := clipboard.Init()
if err != nil {
return err
}
// check if WAYLAND_DISPLAY is set
if os.Getenv("XDG_SESSION_TYPE") == "wayland" || os.Getenv("XDG_SESSION_TYPE") == "x11" {
printError("if you're using a display server like wayland or x11, use freeze in a pipeline to copy image contents to your clipboard. See the freeze README to learn more.", nil)
}
png, err := os.ReadFile(path)
defer os.Remove(path) // nolint: errcheck
if err != nil {
return err
}

clipboard.Write(clipboard.FmtImage, png)
clipboard.Read(clipboard.FmtImage)
return err
}

func libsvgConvert(doc *etree.Document, _, _ float64, output string) error {
_, err := exec.LookPath("rsvg-convert")
if err != nil {
Expand All @@ -27,6 +49,12 @@ func libsvgConvert(doc *etree.Document, _, _ float64, output string) error {
rsvgConvert := exec.Command("rsvg-convert", "-o", output)
rsvgConvert.Stdin = bytes.NewReader(svg)
err = rsvgConvert.Run()
if err != nil {
return err
}
if strings.HasPrefix(output, "clipboard") {
return copyToClipboard(output)
}
return err //nolint: wrapcheck
}

Expand Down Expand Up @@ -90,9 +118,8 @@ func resvgConvert(doc *etree.Document, w, h float64, output string) error {
return err //nolint: wrapcheck
}

err = os.WriteFile(output, png, 0o600)
if err != nil {
return err //nolint: wrapcheck
if output == "clipboard.png" {
return copyToClipboard(output)
}
return err //nolint: wrapcheck
return os.WriteFile(output, png, 0o600)
}
49 changes: 49 additions & 0 deletions test/golden/svg/bubbletea-copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading