on main help sign in
feat(clipboard): migrate clipboard to golang.design/x/clipboard
commit98d79e95a52f31307f6943af3b0cbe7e87daa01d authorKieran Klukas <kieran@dunkirk.sh> dateThu 18 Jun 2026, 19:04 (4 months ago) parentffd4796
8 files changed+39−18
go.mod+5−2 go.sum+8−2 internal/app/app.go+7 internal/cmd/login.go+3−6 internal/ui/common/common.go+2−2 internal/ui/model/clipboard_not_supported.go+1−1 internal/ui/model/clipboard_supported.go+12−4 internal/ui/model/keys.go+1−1
go.mod+5−2
@@ -17,8 +17,6 @@ require (
1717 github.com/Microsoft/go-winio v0.6.2
1818 github.com/PuerkitoBio/goquery v1.12.0
1919 github.com/alecthomas/chroma/v2 v2.26.1
20− github.com/atotto/clipboard v0.1.4
21− github.com/aymanbagabas/go-nativeclipboard v0.1.3
2220 github.com/aymanbagabas/go-udiff v0.4.1
2321 github.com/bmatcuk/doublestar/v4 v4.10.0
2422 github.com/charlievieth/fastwalk v1.0.14
@@ -67,6 +65,7 @@ require (
6765 github.com/tidwall/sjson v1.2.5
6866 github.com/zeebo/xxh3 v1.1.0
6967 go.uber.org/goleak v1.3.0
68+ golang.design/x/clipboard v0.8.0
7069 golang.org/x/net v0.56.0
7170 golang.org/x/sync v0.21.0
7271 golang.org/x/sys v0.46.0
@@ -88,6 +87,7 @@ require (
8887 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
8988 github.com/KyleBanks/depth v1.2.1 // indirect
9089 github.com/andybalholm/cascadia v1.3.3 // indirect
90+ github.com/atotto/clipboard v0.1.4 // indirect
9191 github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
9292 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
9393 github.com/aws/aws-sdk-go-v2/config v1.32.25 // indirect
@@ -195,9 +195,12 @@ require (
195195 go.opentelemetry.io/otel/trace v1.44.0 // indirect
196196 go.uber.org/multierr v1.11.0 // indirect
197197 go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
198+ golang.design/x/x11 v0.2.0 // indirect
198199 golang.org/x/crypto v0.53.0 // indirect
199200 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
201+ golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
200202 golang.org/x/image v0.42.0 // indirect
203+ golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
201204 golang.org/x/mod v0.36.0 // indirect
202205 golang.org/x/oauth2 v0.36.0 // indirect
203206 golang.org/x/term v0.44.0 // indirect
go.sum+8−2
@@ -86,8 +86,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUY
8686github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
8787github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
8888github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
89−github.com/aymanbagabas/go-nativeclipboard v0.1.3 h1:FmAWHPTwneAixu7uGDn3cL42xPlUCdNp2J8egMn3P1k=
90−github.com/aymanbagabas/go-nativeclipboard v0.1.3/go.mod h1:2o7MyZwwi4pmXXpOpvOS5FwaHyoCIUks0ktjUvB0EoE=
9189github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
9290github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
9391github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -450,6 +448,10 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
450448go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
451449go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
452450go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
451+golang.design/x/clipboard v0.8.0 h1:6VEcH28wwcSgKc+vnxHHDWiRjrakTQIAJnPUrt3aOgg=
452+golang.design/x/clipboard v0.8.0/go.mod h1:s0pwrtA3Q9fgnVtGDmP5ZK/pp55cQKB23esKsjwWhWM=
453+golang.design/x/x11 v0.2.0 h1:Uiwu2guGihsJX/ZCzpoDPFz5gR/Qntm08mvoBCmRydo=
454+golang.design/x/x11 v0.2.0/go.mod h1:/5q1mFkdc1rL8mvB7DsQFi6as4tIkBv4FXjcP07mrkE=
453455golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
454456golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
455457golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@@ -461,9 +463,13 @@ golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
461463golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
462464golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
463465golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
466+golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
467+golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
464468golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
465469golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
466470golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
471+golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
472+golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
467473golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
468474golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
469475golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
internal/app/app.go+7
@@ -42,6 +42,7 @@ import (
4242 "github.com/charmbracelet/x/ansi"
4343 "github.com/charmbracelet/x/exp/charmtone"
4444 "github.com/charmbracelet/x/term"
45+ "golang.design/x/clipboard"
4546)
4647
4748// UpdateAvailableMsg is sent when a new version is available.
@@ -122,6 +123,12 @@ func New(ctx context.Context, conn *sql.DB, store *config.ConfigStore, skillsMgr
122123
123124 app.setupEvents()
124125
126+ // Initialize clipboard support. This is best-effort; if it fails
127+ // (e.g., headless environment), clipboard operations will return nil.
128+ if err := clipboard.Init(); err != nil {
129+ slog.Warn("Clipboard initialization failed", "error", err)
130+ }
131+
125132 // Check for updates in the background.
126133 go app.checkForUpdates(ctx)
127134
internal/cmd/login.go+3−6
@@ -8,7 +8,6 @@ import (
88 "os/signal"
99
1010 "charm.land/lipgloss/v2"
11− "github.com/atotto/clipboard"
1211 "github.com/charmbracelet/crush/internal/client"
1312 "github.com/charmbracelet/crush/internal/config"
1413 "github.com/charmbracelet/crush/internal/oauth"
@@ -17,6 +16,7 @@ import (
1716 "github.com/charmbracelet/x/ansi"
1817 "github.com/pkg/browser"
1918 "github.com/spf13/cobra"
19+ "golang.design/x/clipboard"
2020)
2121
2222var loginCmd = &cobra.Command{
@@ -95,11 +95,8 @@ func loginHyper(c *client.Client, wsID string, force bool) error {
9595 return err
9696 }
9797
98− if clipboard.WriteAll(resp.UserCode) == nil {
99− fmt.Println("The following code should be on clipboard already:")
100− } else {
101− fmt.Println("Copy the following code:")
102− }
98+ clipboard.Write(clipboard.FmtText, []byte(resp.UserCode))
99+ fmt.Println("The following code should be on clipboard already:")
103100
104101 fmt.Println()
105102 fmt.Println(lipgloss.NewStyle().Bold(true).Render(resp.UserCode))
internal/ui/common/common.go+2−2
@@ -6,12 +6,12 @@ import (
66 "os"
77
88 tea "charm.land/bubbletea/v2"
9− "github.com/atotto/clipboard"
109 "github.com/charmbracelet/crush/internal/config"
1110 "github.com/charmbracelet/crush/internal/ui/styles"
1211 "github.com/charmbracelet/crush/internal/ui/util"
1312 "github.com/charmbracelet/crush/internal/workspace"
1413 uv "github.com/charmbracelet/ultraviolet"
14+ "golang.design/x/clipboard"
1515)
1616
1717// MaxAttachmentSize defines the maximum allowed size for file attachments (5 MB).
@@ -112,7 +112,7 @@ func CopyToClipboardWithCallback(text, successMessage string, callback tea.Cmd)
112112 return tea.Sequence(
113113 tea.SetClipboard(text),
114114 func() tea.Msg {
115− _ = clipboard.WriteAll(text)
115+ clipboard.Write(clipboard.FmtText, []byte(text))
116116 return nil
117117 },
118118 callback,
internal/ui/model/clipboard_not_supported.go+1−1
@@ -1,4 +1,4 @@
1−//go:build !(darwin || linux || windows) || arm || 386 || ios || android
1+//go:build !((darwin || linux || windows || freebsd || openbsd || netbsd) && !ios && !android)
22
33package model
44
internal/ui/model/clipboard_supported.go+12−4
@@ -1,15 +1,23 @@
1−//go:build (linux || darwin || windows) && !arm && !386 && !ios && !android
1+//go:build (darwin || linux || windows || freebsd || openbsd || netbsd) && !ios && !android
22
33package model
44
5−import "github.com/aymanbagabas/go-nativeclipboard"
5+import "golang.design/x/clipboard"
66
77func readClipboard(f clipboardFormat) ([]byte, error) {
88 switch f {
99 case clipboardFormatText:
10− return nativeclipboard.Text.Read()
10+ data := clipboard.Read(clipboard.FmtText)
11+ if data == nil {
12+ return nil, errClipboardUnknownFormat
13+ }
14+ return data, nil
1115 case clipboardFormatImage:
12− return nativeclipboard.Image.Read()
16+ data := clipboard.Read(clipboard.FmtImage)
17+ if data == nil {
18+ return nil, errClipboardUnknownFormat
19+ }
20+ return data, nil
1321 }
1422 return nil, errClipboardUnknownFormat
1523}
internal/ui/model/keys.go+1−1
@@ -129,7 +129,7 @@ func DefaultKeyMap() KeyMap {
129129 key.WithHelp("ctrl+f", "add image"),
130130 )
131131 km.Editor.PasteImage = key.NewBinding(
132− key.WithKeys("ctrl+v"),
132+ key.WithKeys("ctrl+v", "super+v"),
133133 key.WithHelp("ctrl+v", "paste image from clipboard"),
134134 )
135135 km.Editor.MentionFile = key.NewBinding(