mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-09-10 03:51:46 +02:00
112 lines
3.7 KiB
Nu
Executable File
112 lines
3.7 KiB
Nu
Executable File
#!/usr/bin/env nu
|
|
|
|
# Sunshine clipboard bridge (host side)
|
|
#
|
|
# Reads raw text from stdin and sets it as the clipboard of the compositor that
|
|
# is currently streaming via Sunshine, so pasting inside the stream is a plain
|
|
# Ctrl+V. Moonlight's built-in "type clipboard text" (Ctrl+Alt+Shift+V) is not
|
|
# usable here: Sunshine re-types the payload as Ctrl+Shift+U hex key events,
|
|
# which only GTK widgets understand, so browsers and terminals garble it.
|
|
#
|
|
# The streamed compositor is discovered through the running `sunshine` process
|
|
# (its WAYLAND_DISPLAY / XDG_RUNTIME_DIR). This works for both a headless
|
|
# greetd-sunshine remote session and a regular desktop session.
|
|
#
|
|
# Usage (input must be a pipe, not a tty):
|
|
#
|
|
# printf 'https://example.com/path?q=1' | nu clipboard.nu
|
|
# wl-paste -n | nu clipboard.nu
|
|
#
|
|
# Usage from a Moonlight client over SSH (see send-clipboard.nu):
|
|
#
|
|
# wl-paste -n | ssh ryan@ai "nu ~/nix-config/modules/nixos/desktop/networking/sunshine/clipboard.nu"
|
|
|
|
def main [
|
|
--user (-u): string = "" # Sunshine session user (default: SUDO_USER or current user)
|
|
--display (-d): string = "" # Override WAYLAND_DISPLAY (auto-detected from the sunshine process)
|
|
--verify (-v) # Read the clipboard back and fail if it differs from the input
|
|
] {
|
|
if (is-terminal --stdin) {
|
|
error make {
|
|
msg: 'no input: pipe text on stdin, e.g. `printf "text" | nu clipboard.nu`'
|
|
}
|
|
}
|
|
|
|
let raw = (open --raw /dev/stdin)
|
|
if ($raw | str trim) == '' {
|
|
error make { msg: 'empty input: nothing to copy' }
|
|
}
|
|
|
|
let user = (effective-user $user)
|
|
let pid = (streaming-sunshine-pid $user)
|
|
if $pid < 0 {
|
|
error make {
|
|
msg: $"no running `sunshine` process for user `($user)`"
|
|
help: 'start the Sunshine session first (headless: `sudo nu ./remote-session.nu`)'
|
|
}
|
|
}
|
|
|
|
let runtime = (proc-env $pid 'XDG_RUNTIME_DIR' $"/run/user/(^id -u $user | str trim)")
|
|
let display = if $display != '' {
|
|
$display
|
|
} else {
|
|
let detected = (proc-env $pid 'WAYLAND_DISPLAY' '')
|
|
if $detected != '' { $detected } else { (single-wayland-display $runtime) }
|
|
}
|
|
|
|
let target_env = {
|
|
WAYLAND_DISPLAY: $display
|
|
XDG_RUNTIME_DIR: $runtime
|
|
}
|
|
|
|
# wl-copy forks a server that must outlive this process and serve the
|
|
# selection; redirect its stdio so the daemon does not hold our pipes open.
|
|
with-env $target_env {
|
|
$raw | ^wl-copy out> /dev/null err> /dev/null
|
|
}
|
|
if $env.LAST_EXIT_CODE != 0 {
|
|
error make { msg: 'wl-copy failed to set the clipboard' }
|
|
}
|
|
|
|
if $verify {
|
|
let pasted = (with-env $target_env { ^wl-paste -n | complete })
|
|
if $pasted.exit_code != 0 or ($pasted.stdout | str trim) != ($raw | str trim) {
|
|
error make { msg: 'verification failed: clipboard content differs from the input' }
|
|
}
|
|
}
|
|
}
|
|
|
|
def effective-user [user: string] {
|
|
if $user != '' { return $user }
|
|
$env.SUDO_USER? | default (whoami)
|
|
}
|
|
|
|
def streaming-sunshine-pid [user: string] {
|
|
let found = (^pgrep -o -u $user -x sunshine | complete)
|
|
if $found.exit_code != 0 { return (-1) }
|
|
($found.stdout | str trim | into int)
|
|
}
|
|
|
|
def proc-env [pid: int, key: string, fallback: string] {
|
|
let environ = (open --raw $"/proc/($pid)/environ")
|
|
for kv in ($environ | split row "\u{0}") {
|
|
if ($kv | str starts-with $"($key)=") {
|
|
return ($kv | str replace $"($key)=" '')
|
|
}
|
|
}
|
|
$fallback
|
|
}
|
|
|
|
def single-wayland-display [runtime: string] {
|
|
let sockets = (ls $runtime
|
|
| where { |it| ($it.name | path basename) =~ '^wayland-[0-9]+$' }
|
|
| get name)
|
|
if ($sockets | length) == 1 {
|
|
return ($sockets.0 | path basename)
|
|
}
|
|
error make {
|
|
msg: $"cannot determine WAYLAND_DISPLAY under ($runtime)"
|
|
help: $"candidates: (($sockets | path basename | str join ', ')) -- pass --display explicitly"
|
|
}
|
|
}
|