feat(clipboard): fill the WSL2 clipboard gap with win32yank #161

Merged
rootiest merged 5 commits from feat/win32yank-wsl2-clipboard into main 2026-09-21 06:57:49 +00:00
13 changed files with 159 additions and 60 deletions
+11 -6
View File
@@ -179,10 +179,14 @@ for broader distro support, but coverage outside Arch is thinner.
A number of functions call Linux-specific subsystems directly, with no A number of functions call Linux-specific subsystems directly, with no
fallback: `systemd-inhibit` (`wake-lock`), `zramctl`/`swapon` (`swapstat`), fallback: `systemd-inhibit` (`wake-lock`), `zramctl`/`swapon` (`swapstat`),
`sbctl` and UEFI Secure Boot state (`sbver`), `wl-copy`/`xclip` for clipboard `sbctl` and UEFI Secure Boot state (`sbver`), and GNU coreutils flags like
access (`y`, `p`, `paste`, `hist` — Wayland or X11 only, no `pbcopy`/`pbpaste` `stat -c`/`numfmt` (`sudo-toggle`, `dng2avif`), which differ or don't exist
fallback), and GNU coreutils flags like `stat -c`/`numfmt` (`sudo-toggle`, under a BSD userland.
`dng2avif`), which differ or don't exist under a BSD userland.
Clipboard access (`y`, `p`, `paste`, `hist`) is the exception: it falls back
through `wl-copy`/`wl-paste` (Wayland), `xclip` (X11), and `win32yank.exe`
(WSL2), so it works on all three — there's still no `pbcopy`/`pbpaste`
fallback for macOS.
**macOS** is not supported — `_fish_deps_detect_pm` checks for `brew`, but **macOS** is not supported — `_fish_deps_detect_pm` checks for `brew`, but
that alone doesn't make the functions above work; they have no macOS that alone doesn't make the functions above work; they have no macOS
@@ -190,8 +194,9 @@ equivalent path today.
**Windows** is not supported. Fish has no native Windows build, and this **Windows** is not supported. Fish has no native Windows build, and this
config isn't tested under WSL either. WSL2 can run `systemd`, so basic shell config isn't tested under WSL either. WSL2 can run `systemd`, so basic shell
use may work, but Secure Boot/zram state is meaningless inside a VM and use may work; Secure Boot/zram state is still meaningless inside a VM, but
clipboard integration has no WSL-specific path here. clipboard integration works via `win32yank.exe` once it's reachable through
WSL interop — `fish-deps install` can fetch it for you.
**Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`, **Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`,
and GNU coreutils. These aren't tracked by `fish-deps` — they're base-system and GNU coreutils. These aren't tracked by `fish-deps` — they're base-system
+1
View File
@@ -59,6 +59,7 @@ matter if you already use that specific tool. Skipped by
| `screen` | GNU screen; fallback backend for `jobrunner` when `tmux` is unavailable. | | `screen` | GNU screen; fallback backend for `jobrunner` when `tmux` is unavailable. |
| `marktext` | Markdown editor; backs the `md` wrapper, which is the only thing that references it. No distro packages it under a common name, so `fish-deps` offers the AUR package (`marktext-bin`) on Arch and otherwise installs upstream's AppImage to `~/.local/bin/marktext`. | | `marktext` | Markdown editor; backs the `md` wrapper, which is the only thing that references it. No distro packages it under a common name, so `fish-deps` offers the AUR package (`marktext-bin`) on Arch and otherwise installs upstream's AppImage to `~/.local/bin/marktext`. |
| `firejail` | Sandbox; needed only by `md --read-only`, which uses it to make MarkText unable to save over the file it opened. Every other `md` invocation works without it. | | `firejail` | Sandbox; needed only by `md --read-only`, which uses it to make MarkText unable to save over the file it opened. Every other `md` invocation works without it. |
| `win32yank.exe` | Clipboard bridge for WSL2; backs the `y`/`p`/`paste`/`hist` clipboard fallback chain when neither `wl-copy`/`wl-paste` nor `xclip` are present. `fish-deps` only offers to install it when WSL2 is detected (`microsoft` in `/proc/sys/kernel/osrelease`), downloading the x86_64 binary from GitHub releases to `~/.local/bin`. |
## Terminal Emulators ## Terminal Emulators
@@ -13,7 +13,7 @@ These features couple the shell to specific external tools. Disabling
spwin Kitty or WezTerm spwin Kitty or WezTerm
tab Kitty, WezTerm, or Konsole tab Kitty, WezTerm, or Konsole
split Kitty or WezTerm split Kitty or WezTerm
hist fzf + wl-copy (Wayland clipboard) hist fzf + wl-copy, xclip, or win32yank.exe (WSL2)
logs fzf + ov; reads from ~/.terminal_history/ logs fzf + ov; reads from ~/.terminal_history/
upgrade paru or yay (Arch Linux only) upgrade paru or yay (Arch Linux only)
WakaTime hook wakatime CLI and a configured API key WakaTime hook wakatime CLI and a configured API key
+9 -6
View File
@@ -35,11 +35,14 @@ no fallback:
- `systemd-inhibit` (`wake-lock`) - `systemd-inhibit` (`wake-lock`)
- `zramctl` / `swapon` (`swapstat`) - `zramctl` / `swapon` (`swapstat`)
- `sbctl` and UEFI Secure Boot state (`sbver`) - `sbctl` and UEFI Secure Boot state (`sbver`)
- `wl-copy` / `xclip` for clipboard access (`y`, `p`, `paste`, `hist`) —
Wayland or X11 only, no `pbcopy`/`pbpaste` fallback
- GNU coreutils flags such as `stat -c` and `numfmt` (`sudo-toggle`, - GNU coreutils flags such as `stat -c` and `numfmt` (`sudo-toggle`,
`dng2avif`), which differ or don't exist under a BSD userland `dng2avif`), which differ or don't exist under a BSD userland
Clipboard access (`y`, `p`, `paste`, `hist`) is the exception: it falls back
through `wl-copy`/`wl-paste` (Wayland), `xclip` (X11), and `win32yank.exe`
(WSL2), so it works on all three. There is still no `pbcopy`/`pbpaste`
fallback for macOS.
**macOS** is not supported. `_fish_deps_detect_pm` does check for `brew`, but **macOS** is not supported. `_fish_deps_detect_pm` does check for `brew`, but
that alone does not make the functions above work — they have no macOS that alone does not make the functions above work — they have no macOS
equivalent path today. equivalent path today.
@@ -47,10 +50,10 @@ equivalent path today.
**Windows** is not supported. Fish itself has no native Windows build; **Windows** is not supported. Fish itself has no native Windows build;
upstream's own "Windows" install docs are Cygwin/WSL workarounds, not a real upstream's own "Windows" install docs are Cygwin/WSL workarounds, not a real
port. This config is not tested under WSL either. WSL2 runs a real Linux port. This config is not tested under WSL either. WSL2 runs a real Linux
kernel and can run `systemd`, so basic shell use may work, but `zramctl`, kernel and can run `systemd`, so basic shell use may work; `zramctl`,
`sbctl`, and Secure Boot state are meaningless inside a VM, and clipboard `sbctl`, and Secure Boot state are still meaningless inside a VM, but
integration would need a WSL-specific path (`clip.exe`, `win32yank`) that clipboard integration works via `win32yank.exe` (see above) once it's
does not exist here. installed on the Windows side and reachable through WSL interop.
**Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`, **Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`,
and GNU coreutils (for `stat`, `date`, `numfmt`). These are not tracked by and GNU coreutils (for `stat`, `date`, `numfmt`). These are not tracked by
+28
View File
@@ -0,0 +1,28 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _fish_clipboard_copy
#
# DESCRIPTION
# Copies stdin to the system clipboard. Tries wl-copy (Wayland), then
# xclip (X11), then win32yank.exe (WSL2).
#
# EXIT STATUS
# 0 Text copied to clipboard
# 1 No clipboard provider found
#
# EXAMPLE
# echo "hello" | _fish_clipboard_copy
function _fish_clipboard_copy --description 'Copy stdin to the system clipboard'
if type -q wl-copy
wl-copy
else if type -q xclip
xclip -selection clipboard
else if type -q win32yank.exe
win32yank.exe -i --crlf
else
echo "Error: No clipboard provider (wl-copy, xclip, or win32yank) found." >&2
return 1
end
end
+31
View File
@@ -0,0 +1,31 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _fish_clipboard_paste [args...]
#
# DESCRIPTION
# Prints the system clipboard contents to stdout. Tries wl-paste
# (Wayland), then xclip (X11), then win32yank.exe (WSL2).
#
# ARGUMENTS
# args... Arguments forwarded to the clipboard tool
#
# EXIT STATUS
# 0 Clipboard contents read successfully
# 1 No clipboard provider found
#
# EXAMPLE
# _fish_clipboard_paste
function _fish_clipboard_paste --description 'Print the system clipboard contents'
if type -q wl-paste
wl-paste $argv
else if type -q xclip
xclip -selection clipboard -o $argv
else if type -q win32yank.exe
win32yank.exe -o --lf $argv
else
echo "Error: No clipboard provider (wl-paste, xclip, or win32yank) found." >&2
return 1
end
end
+5 -5
View File
@@ -36,27 +36,27 @@ function _fish_deps_catalog
set -g _fdc_bins \ set -g _fdc_bins \
uv cargo fish starship fzf zoxide direnv paru yay \ uv cargo fish starship fzf zoxide direnv paru yay \
wakatime tailscale \ wakatime tailscale \
eza lsd bat btop dust duf prettyping go ov rg lazygit lazydocker docker trash kitty wezterm python3 yt-dlp screen mpv vlc marktext firejail eza lsd bat btop dust duf prettyping go ov rg lazygit lazydocker docker trash kitty wezterm python3 yt-dlp screen mpv vlc marktext firejail win32yank.exe
set -g _fdc_tiers \ set -g _fdc_tiers \
rec rec req rec req rec rec rec rec \ rec rec req rec req rec rec rec rec \
int int \ int int \
rec rec rec opt opt opt opt opt rec rec opt opt opt rec term term rec opt opt opt opt opt opt rec rec rec opt opt opt opt opt rec rec opt opt opt rec term term rec opt opt opt opt opt opt opt
set -g _fdc_cargo \ set -g _fdc_cargo \
"" "" "" starship "" zoxide "" "" "" \ "" "" "" starship "" zoxide "" "" "" \
"" "" \ "" "" \
eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" "" "" eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" "" "" ""
set -g _fdc_pm \ set -g _fdc_pm \
uv cargo fish starship fzf zoxide direnv "" yay \ uv cargo fish starship fzf zoxide direnv "" yay \
wakatime tailscale \ wakatime tailscale \
eza lsd bat btop dust duf prettyping go ov ripgrep lazygit lazydocker docker trash kitty wezterm python yt-dlp screen mpv vlc "" firejail eza lsd bat btop dust duf prettyping go ov ripgrep lazygit lazydocker docker trash kitty wezterm python yt-dlp screen mpv vlc "" firejail ""
set -g _fdc_special \ set -g _fdc_special \
curl-uv rustup-installer git-cargo-fish curl-installer fzf-update "" "" paru-build yay-build \ curl-uv rustup-installer git-cargo-fish curl-installer fzf-update "" "" paru-build yay-build \
wakatime-binary "" \ wakatime-binary "" \
"" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" marktext-release "" "" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" marktext-release "" win32yank-release
end end
# SYNOPSIS # SYNOPSIS
+29
View File
@@ -46,6 +46,14 @@ function _fish_deps_install
set -l i 1 set -l i 1
for bin in $_fdc_bins for bin in $_fdc_bins
# win32yank only matters under WSL2 — skip the entry entirely
# elsewhere so a plain Linux box never sees it, not even as a
# "no install method available" note.
if test "$bin" = win32yank.exe; and not string match -qi '*microsoft*' (cat /proc/sys/kernel/osrelease 2>/dev/null)
set i (math $i + 1)
continue
end
# Optional-tier deps are opt-in: skip unless --optional/--all was passed. # Optional-tier deps are opt-in: skip unless --optional/--all was passed.
if test "$_fdc_tiers[$i]" = opt; and test $include_optional -eq 0 if test "$_fdc_tiers[$i]" = opt; and test $include_optional -eq 0
if not command -q $bin if not command -q $bin
@@ -140,6 +148,15 @@ function _fish_deps_install
end end
set -a methods special-marktext-appimage set -a methods special-marktext-appimage
set -a method_labels "AppImage download (~/.local/bin/marktext)" set -a method_labels "AppImage download (~/.local/bin/marktext)"
case win32yank-release
if test (uname -m) = x86_64
set -a methods special-win32yank
set -a method_labels "binary download (github releases)"
else
set_color brblack
echo " note: win32yank only ships x86_64 builds (this is "(uname -m)")"
set_color normal
end
case go-ov case go-ov
if type -q go if type -q go
set -a methods special-go-ov set -a methods special-go-ov
@@ -296,6 +313,18 @@ function _fish_deps_install
and chmod +x "$_wt_bin" and chmod +x "$_wt_bin"
and ln -sf "$_wt_bin" "$HOME/.local/bin/wakatime" and ln -sf "$_wt_bin" "$HOME/.local/bin/wakatime"
rm -rf "$_tmpdir" rm -rf "$_tmpdir"
case special-win32yank
set -l _zip win32yank-x64.zip
set -l _tmpdir (mktemp -d)
mkdir -p "$HOME/.local/bin"
and curl -fL "https://github.com/equalsraf/win32yank/releases/latest/download/$_zip" \
-o "$_tmpdir/$_zip"
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
and cp "$_tmpdir/win32yank.exe" "$HOME/.local/bin/win32yank.exe"
and chmod +x "$HOME/.local/bin/win32yank.exe"
set -l _dl_status $status
rm -rf "$_tmpdir"
test $_dl_status -eq 0
case special-fzf case special-fzf
fzf-update fzf-update
case special-curl case special-curl
+19
View File
@@ -146,6 +146,25 @@ function _fish_deps_update
continue continue
end end
# win32yank: re-download the binary from github releases (WSL2 only;
# only reached if a copy is already on PATH, so no WSL check needed)
if test "$special" = win32yank-release
echo "Updating $bin..."
set -l _zip win32yank-x64.zip
set -l _tmpdir (mktemp -d)
curl -fL "https://github.com/equalsraf/win32yank/releases/latest/download/$_zip" \
-o "$_tmpdir/$_zip"
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
and cp "$_tmpdir/win32yank.exe" "$HOME/.local/bin/win32yank.exe"
and chmod +x "$HOME/.local/bin/win32yank.exe"
set -l _up_status $status
rm -rf "$_tmpdir"
test $_up_status -eq 0
and set updated_any 1
set i (math $i + 1)
continue
end
# pipx tools # pipx tools
if test "$special" = pipx if test "$special" = pipx
if type -q pipx if type -q pipx
+6 -2
View File
@@ -12,7 +12,11 @@
# #
# DESCRIPTION # DESCRIPTION
# Searches fish history interactively using fzf, inserts the selected command # Searches fish history interactively using fzf, inserts the selected command
# into the command line, and copies it to the clipboard via wl-copy. # into the command line, and copies it to the clipboard via wl-copy, xclip,
# or win32yank (WSL2).
#
# DEPENDENCIES
# _fish_clipboard_copy
# #
# EXIT STATUS # EXIT STATUS
# 0 Command selected and inserted, or fzf was cancelled # 0 Command selected and inserted, or fzf was cancelled
@@ -36,7 +40,7 @@ function hist --description 'Search fish history and put it in the prompt'
# Strip the timestamp for the final output # Strip the timestamp for the final output
set -l command (echo $selected | string replace -r '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ' '') set -l command (echo $selected | string replace -r '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ' '')
echo $command | wl-copy 2>/dev/null echo $command | _fish_clipboard_copy 2>/dev/null
commandline -r $command commandline -r $command
end end
end end
+6 -15
View File
@@ -8,8 +8,11 @@
# p [args...] # p [args...]
# #
# DESCRIPTION # DESCRIPTION
# Outputs clipboard contents to stdout. Uses wl-paste on Wayland, # Outputs clipboard contents to stdout. Uses wl-paste on Wayland, xclip on
# falls back to xclip on X11. Supports -h/--help for usage info. # X11, or win32yank on WSL2. Supports -h/--help for usage info.
#
# DEPENDENCIES
# _fish_clipboard_paste
# #
# ARGUMENTS # ARGUMENTS
# -h, --help Show usage help # -h, --help Show usage help
@@ -41,17 +44,5 @@ function p --description 'Put from clipboard'
return 0 return 0
end end
# Determine the clipboard provider _fish_clipboard_paste $argv
set -l paste_cmd
if type -q wl-paste
set paste_cmd wl-paste
else if type -q xclip
set paste_cmd xclip -selection clipboard -o
else
echo "Error: No clipboard provider (wl-paste or xclip) found." >&2
return 1
end
# Execute the paste command with any provided arguments
$paste_cmd $argv
end end
+6 -10
View File
@@ -8,8 +8,11 @@
# paste [args...] # paste [args...]
# #
# DESCRIPTION # DESCRIPTION
# Outputs clipboard contents to stdout. Uses wl-paste on Wayland, # Outputs clipboard contents to stdout. Uses wl-paste on Wayland, xclip on
# falls back to xclip on X11. # X11, or win32yank on WSL2.
#
# DEPENDENCIES
# _fish_clipboard_paste
# #
# ARGUMENTS # ARGUMENTS
# args... Arguments forwarded to the clipboard tool # args... Arguments forwarded to the clipboard tool
@@ -24,12 +27,5 @@
# EXAMPLE # EXAMPLE
# paste > file.txt # paste > file.txt
function paste --description 'Paste from clipboard' function paste --description 'Paste from clipboard'
if type -q wl-paste _fish_clipboard_paste $argv
wl-paste $argv
else if type -q xclip
xclip -selection clipboard -o $argv
else
echo "Error: Neither wl-paste nor xclip found." >&2
return 1
end
end end
+7 -15
View File
@@ -8,8 +8,11 @@
# y [text...] # y [text...]
# #
# DESCRIPTION # DESCRIPTION
# Copies text to the system clipboard using wl-copy (Wayland) or xclip (X11). # Copies text to the system clipboard using wl-copy (Wayland), xclip (X11),
# Reads from stdin when no arguments are given. # or win32yank (WSL2). Reads from stdin when no arguments are given.
#
# DEPENDENCIES
# _fish_clipboard_copy
# #
# ARGUMENTS # ARGUMENTS
# text Text to copy; reads from stdin if omitted # text Text to copy; reads from stdin if omitted
@@ -36,24 +39,13 @@ function y --description 'Yank to clipboard'
return 0 return 0
end end
# Determine the clipboard provider
set -l copy_cmd
if type -q wl-copy
set copy_cmd wl-copy
else if type -q xclip
set copy_cmd xclip -selection clipboard
else
echo "Error: No clipboard provider (wl-copy or xclip) found." >&2
return 1
end
# Handle input # Handle input
if set -q argv[1] if set -q argv[1]
# If arguments are provided, echo them to the clipboard # If arguments are provided, echo them to the clipboard
echo $argv | eval $copy_cmd echo $argv | _fish_clipboard_copy
else else
# If no arguments, read from stdin (pipes/redirects) # If no arguments, read from stdin (pipes/redirects)
eval $copy_cmd _fish_clipboard_copy
end end
end end