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
fallback: `systemd-inhibit` (`wake-lock`), `zramctl`/`swapon` (`swapstat`),
`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), and GNU coreutils flags like `stat -c`/`numfmt` (`sudo-toggle`,
`dng2avif`), which differ or don't exist under a BSD userland.
`sbctl` and UEFI Secure Boot state (`sbver`), and GNU coreutils flags like
`stat -c`/`numfmt` (`sudo-toggle`, `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
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
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
clipboard integration has no WSL-specific path here.
use may work; Secure Boot/zram state is still meaningless inside a VM, but
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`,
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. |
| `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. |
| `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
@@ -13,7 +13,7 @@ These features couple the shell to specific external tools. Disabling
spwin Kitty or WezTerm
tab Kitty, WezTerm, or Konsole
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/
upgrade paru or yay (Arch Linux only)
WakaTime hook wakatime CLI and a configured API key
+9 -6
View File
@@ -35,11 +35,14 @@ no fallback:
- `systemd-inhibit` (`wake-lock`)
- `zramctl` / `swapon` (`swapstat`)
- `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`,
`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
that alone does not make the functions above work — they have no macOS
equivalent path today.
@@ -47,10 +50,10 @@ equivalent path today.
**Windows** is not supported. Fish itself has no native Windows build;
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
kernel and can run `systemd`, so basic shell use may work, but `zramctl`,
`sbctl`, and Secure Boot state are meaningless inside a VM, and clipboard
integration would need a WSL-specific path (`clip.exe`, `win32yank`) that
does not exist here.
kernel and can run `systemd`, so basic shell use may work; `zramctl`,
`sbctl`, and Secure Boot state are still meaningless inside a VM, but
clipboard integration works via `win32yank.exe` (see above) once it's
installed on the Windows side and reachable through WSL interop.
**Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`,
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 \
uv cargo fish starship fzf zoxide direnv paru yay \
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 \
rec rec req rec req rec rec rec rec \
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 \
"" "" "" starship "" zoxide "" "" "" \
"" "" \
eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" "" ""
eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" "" "" ""
set -g _fdc_pm \
uv cargo fish starship fzf zoxide direnv "" yay \
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 \
curl-uv rustup-installer git-cargo-fish curl-installer fzf-update "" "" paru-build yay-build \
wakatime-binary "" \
"" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" marktext-release ""
"" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" marktext-release "" win32yank-release
end
# SYNOPSIS
+29
View File
@@ -46,6 +46,14 @@ function _fish_deps_install
set -l i 1
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.
if test "$_fdc_tiers[$i]" = opt; and test $include_optional -eq 0
if not command -q $bin
@@ -140,6 +148,15 @@ function _fish_deps_install
end
set -a methods special-marktext-appimage
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
if type -q go
set -a methods special-go-ov
@@ -296,6 +313,18 @@ function _fish_deps_install
and chmod +x "$_wt_bin"
and ln -sf "$_wt_bin" "$HOME/.local/bin/wakatime"
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
fzf-update
case special-curl
+19
View File
@@ -146,6 +146,25 @@ function _fish_deps_update
continue
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
if test "$special" = pipx
if type -q pipx
+6 -2
View File
@@ -12,7 +12,11 @@
#
# DESCRIPTION
# 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
# 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
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
end
end
+6 -15
View File
@@ -8,8 +8,11 @@
# p [args...]
#
# DESCRIPTION
# Outputs clipboard contents to stdout. Uses wl-paste on Wayland,
# falls back to xclip on X11. Supports -h/--help for usage info.
# Outputs clipboard contents to stdout. Uses wl-paste on Wayland, xclip on
# X11, or win32yank on WSL2. Supports -h/--help for usage info.
#
# DEPENDENCIES
# _fish_clipboard_paste
#
# ARGUMENTS
# -h, --help Show usage help
@@ -41,17 +44,5 @@ function p --description 'Put from clipboard'
return 0
end
# Determine the clipboard provider
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
_fish_clipboard_paste $argv
end
+6 -10
View File
@@ -8,8 +8,11 @@
# paste [args...]
#
# DESCRIPTION
# Outputs clipboard contents to stdout. Uses wl-paste on Wayland,
# falls back to xclip on X11.
# Outputs clipboard contents to stdout. Uses wl-paste on Wayland, xclip on
# X11, or win32yank on WSL2.
#
# DEPENDENCIES
# _fish_clipboard_paste
#
# ARGUMENTS
# args... Arguments forwarded to the clipboard tool
@@ -24,12 +27,5 @@
# EXAMPLE
# paste > file.txt
function paste --description 'Paste from clipboard'
if type -q wl-paste
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
_fish_clipboard_paste $argv
end
+7 -15
View File
@@ -8,8 +8,11 @@
# y [text...]
#
# DESCRIPTION
# Copies text to the system clipboard using wl-copy (Wayland) or xclip (X11).
# Reads from stdin when no arguments are given.
# Copies text to the system clipboard using wl-copy (Wayland), xclip (X11),
# or win32yank (WSL2). Reads from stdin when no arguments are given.
#
# DEPENDENCIES
# _fish_clipboard_copy
#
# ARGUMENTS
# text Text to copy; reads from stdin if omitted
@@ -36,24 +39,13 @@ function y --description 'Yank to clipboard'
return 0
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
if set -q argv[1]
# If arguments are provided, echo them to the clipboard
echo $argv | eval $copy_cmd
echo $argv | _fish_clipboard_copy
else
# If no arguments, read from stdin (pipes/redirects)
eval $copy_cmd
_fish_clipboard_copy
end
end