interactive: Host/View entry menu, clipboard copy, player picker
Bare `pixelpass` now opens a dialoguer-driven Host/View menu instead of going straight to host mode. Host path copies the ticket to the system clipboard via arboard with silent print-only fallback. View path prompts for the ticket, then after the local listener binds prompts mpv-vs-VLC and spawns it detached (setsid + null stdio) so the player survives pixelpass exiting. Headless invocations (`pixelpass <ticket>`, `pixelpass --repair`) unchanged. Per spec at ~/Documents/pixelpass-interactive-mode-spec.md.
This commit is contained in:
Generated
+237
@@ -127,6 +127,24 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arboard"
|
||||||
|
version = "3.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||||
|
dependencies = [
|
||||||
|
"clipboard-win",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-foundation",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
"wl-clipboard-rs",
|
||||||
|
"x11rb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
@@ -583,6 +601,15 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard-win"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||||
|
dependencies = [
|
||||||
|
"error-code",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmov"
|
name = "cmov"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -623,6 +650,18 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.16.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"libc",
|
||||||
|
"unicode-width",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -944,6 +983,16 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dialoguer"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"shell-words",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diatomic-waker"
|
name = "diatomic-waker"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -1026,6 +1075,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@@ -1071,6 +1126,12 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1125,6 +1186,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-code"
|
||||||
|
version = "3.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "5.4.1"
|
version = "5.4.1"
|
||||||
@@ -1164,6 +1231,12 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -2677,6 +2750,18 @@ dependencies = [
|
|||||||
"objc2-encode",
|
"objc2-encode",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-app-kit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-foundation"
|
name = "objc2-core-foundation"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -2690,6 +2775,19 @@ dependencies = [
|
|||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-graphics"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"dispatch2",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-io-surface",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-wlan"
|
name = "objc2-core-wlan"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -2723,6 +2821,17 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-io-surface"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-security"
|
name = "objc2-security"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -2802,6 +2911,16 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_pipe"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "papaya"
|
name = "papaya"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -2862,6 +2981,17 @@ version = "2.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pharos"
|
name = "pharos"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -2942,8 +3072,10 @@ name = "pixelpass"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"arboard",
|
||||||
"ashpd",
|
"ashpd",
|
||||||
"clap",
|
"clap",
|
||||||
|
"dialoguer",
|
||||||
"directories",
|
"directories",
|
||||||
"iroh",
|
"iroh",
|
||||||
"iroh-tickets",
|
"iroh-tickets",
|
||||||
@@ -3637,6 +3769,12 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shell-words"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -4236,6 +4374,17 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree_magic_mini"
|
||||||
|
version = "3.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"nom 8.0.0",
|
||||||
|
"petgraph",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -4535,6 +4684,76 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-backend"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"downcast-rs",
|
||||||
|
"rustix",
|
||||||
|
"smallvec",
|
||||||
|
"wayland-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-client"
|
||||||
|
version = "0.31.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"rustix",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols"
|
||||||
|
version = "0.32.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols-wlr"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-scanner"
|
||||||
|
version = "0.31.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quick-xml",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-sys"
|
||||||
|
version = "0.31.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.98"
|
version = "0.3.98"
|
||||||
@@ -4991,6 +5210,24 @@ dependencies = [
|
|||||||
"wasmparser",
|
"wasmparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wl-clipboard-rs"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"os_pipe",
|
||||||
|
"rustix",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tree_magic_mini",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-wlr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wmi"
|
name = "wmi"
|
||||||
version = "0.18.4"
|
version = "0.18.4"
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ pipewire = "0.9"
|
|||||||
x11rb = { version = "0.13", default-features = false, features = ["allow-unsafe-code"] }
|
x11rb = { version = "0.13", default-features = false, features = ["allow-unsafe-code"] }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
iroh-tickets = "1.0.0-rc.0"
|
iroh-tickets = "1.0.0-rc.0"
|
||||||
|
dialoguer = { version = "0.12", default-features = false }
|
||||||
|
arboard = { version = "3", default-features = false, features = ["wayland-data-control"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|||||||
+8
-5
@@ -5,8 +5,8 @@ use clap::{Parser, ValueEnum};
|
|||||||
name = "pixelpass",
|
name = "pixelpass",
|
||||||
version,
|
version,
|
||||||
about = "P2P screen sharing over iroh + ffmpeg",
|
about = "P2P screen sharing over iroh + ffmpeg",
|
||||||
long_about = "Run with no arguments to host (prints a ticket to share). \
|
long_about = "Run with no arguments for an interactive Host/View menu. \
|
||||||
Pass a ticket to view."
|
Pass a ticket positionally to skip the menu and view headlessly."
|
||||||
)]
|
)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// iroh ticket. If present, runs as viewer. If absent, runs as host.
|
/// iroh ticket. If present, runs as viewer. If absent, runs as host.
|
||||||
@@ -76,15 +76,17 @@ pub struct HostOpts {
|
|||||||
pub framerate: u32,
|
pub framerate: u32,
|
||||||
pub no_hwencode: bool,
|
pub no_hwencode: bool,
|
||||||
pub low_latency: bool,
|
pub low_latency: bool,
|
||||||
|
pub interactive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ViewerOpts {
|
pub struct ViewerOpts {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
pub interactive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn into_host_opts(self) -> HostOpts {
|
pub fn into_host_opts(self, interactive: bool) -> HostOpts {
|
||||||
HostOpts {
|
HostOpts {
|
||||||
window: self.window,
|
window: self.window,
|
||||||
app: self.app,
|
app: self.app,
|
||||||
@@ -94,10 +96,11 @@ impl Cli {
|
|||||||
framerate: self.framerate,
|
framerate: self.framerate,
|
||||||
no_hwencode: self.no_hwencode,
|
no_hwencode: self.no_hwencode,
|
||||||
low_latency: self.low_latency,
|
low_latency: self.low_latency,
|
||||||
|
interactive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_viewer_opts(self) -> ViewerOpts {
|
pub fn into_viewer_opts(self, interactive: bool) -> ViewerOpts {
|
||||||
ViewerOpts { port: self.port }
|
ViewerOpts { port: self.port, interactive }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod alpn;
|
pub mod alpn;
|
||||||
pub mod deps;
|
pub mod deps;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
pub mod process;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
pub mod tunnel;
|
pub mod tunnel;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
/// Spawn a child process fully detached from this process group.
|
||||||
|
///
|
||||||
|
/// The child gets its own session via `setsid(2)` and null stdio, so it
|
||||||
|
/// survives the parent exiting and doesn't take a SIGKILL cascade when
|
||||||
|
/// pixelpass dies. The `Child` is dropped immediately — `std::process::Child::drop`
|
||||||
|
/// does not kill the process on Unix.
|
||||||
|
pub fn spawn_detached(prog: &str, args: &[&str]) -> io::Result<()> {
|
||||||
|
unsafe {
|
||||||
|
Command::new(prog)
|
||||||
|
.args(args)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.pre_exec(|| {
|
||||||
|
nix::unistd::setsid().ok();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.spawn()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+25
-3
@@ -30,7 +30,8 @@ pub async fn run(opts: HostOpts) -> Result<()> {
|
|||||||
|
|
||||||
let addr = endpoint.addr();
|
let addr = endpoint.addr();
|
||||||
let ticket = EndpointTicket::new(addr);
|
let ticket = EndpointTicket::new(addr);
|
||||||
print_host_banner(&ticket, display, &opts);
|
let clipboard_ok = opts.interactive && copy_to_clipboard(&ticket.to_string());
|
||||||
|
print_host_banner(&ticket, display, &opts, clipboard_ok);
|
||||||
|
|
||||||
let result = accept_loop(&endpoint, display, &opts, cancel.clone()).await;
|
let result = accept_loop(&endpoint, display, &opts, cancel.clone()).await;
|
||||||
|
|
||||||
@@ -93,7 +94,12 @@ async fn handle_peer(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_host_banner(ticket: &EndpointTicket, display: DisplayServer, opts: &HostOpts) {
|
fn print_host_banner(
|
||||||
|
ticket: &EndpointTicket,
|
||||||
|
display: DisplayServer,
|
||||||
|
opts: &HostOpts,
|
||||||
|
clipboard_ok: bool,
|
||||||
|
) {
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("┌─ PixelPass · host ─────────────────────────────────────────");
|
eprintln!("┌─ PixelPass · host ─────────────────────────────────────────");
|
||||||
eprintln!("│ display server : {display:?}");
|
eprintln!("│ display server : {display:?}");
|
||||||
@@ -101,7 +107,13 @@ fn print_host_banner(ticket: &EndpointTicket, display: DisplayServer, opts: &Hos
|
|||||||
eprintln!("│ bitrate / fps : {} kbps @ {} fps", opts.bitrate, opts.framerate);
|
eprintln!("│ bitrate / fps : {} kbps @ {} fps", opts.bitrate, opts.framerate);
|
||||||
eprintln!("│ hw encode : {}", if opts.no_hwencode { "off" } else { "auto (VAAPI if available)" });
|
eprintln!("│ hw encode : {}", if opts.no_hwencode { "off" } else { "auto (VAAPI if available)" });
|
||||||
eprintln!("│");
|
eprintln!("│");
|
||||||
eprintln!("│ Share this ticket with your viewer:");
|
if clipboard_ok {
|
||||||
|
eprintln!("│ Your share code has been copied to your clipboard.");
|
||||||
|
eprintln!("│ Send it to your viewer. (If clipboard didn't work, the");
|
||||||
|
eprintln!("│ code is also shown below for manual copy.)");
|
||||||
|
} else {
|
||||||
|
eprintln!("│ Share this ticket with your viewer:");
|
||||||
|
}
|
||||||
eprintln!("│");
|
eprintln!("│");
|
||||||
eprintln!("│ pixelpass {ticket}");
|
eprintln!("│ pixelpass {ticket}");
|
||||||
eprintln!("│");
|
eprintln!("│");
|
||||||
@@ -111,6 +123,16 @@ fn print_host_banner(ticket: &EndpointTicket, display: DisplayServer, opts: &Hos
|
|||||||
eprintln!();
|
eprintln!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_to_clipboard(text: &str) -> bool {
|
||||||
|
match arboard::Clipboard::new().and_then(|mut cb| cb.set_text(text.to_owned())) {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("clipboard copy failed: {e}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn capture_summary(opts: &HostOpts) -> String {
|
fn capture_summary(opts: &HostOpts) -> String {
|
||||||
let mut bits = vec![if opts.window { "window" } else { "fullscreen" }.to_string()];
|
let mut bits = vec![if opts.window { "window" } else { "fullscreen" }.to_string()];
|
||||||
if let Some(app) = &opts.app {
|
if let Some(app) = &opts.app {
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use dialoguer::{Input, Select, theme::ColorfulTheme};
|
||||||
|
use iroh_tickets::endpoint::EndpointTicket;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::cli::Cli;
|
||||||
|
use crate::{host, viewer};
|
||||||
|
|
||||||
|
pub async fn run(cli: Cli) -> Result<()> {
|
||||||
|
print_welcome();
|
||||||
|
|
||||||
|
let theme = ColorfulTheme::default();
|
||||||
|
let choice = Select::with_theme(&theme)
|
||||||
|
.with_prompt("What do you want to do?")
|
||||||
|
.items(&[
|
||||||
|
"Host (share my screen)",
|
||||||
|
"View (watch someone else's screen)",
|
||||||
|
])
|
||||||
|
.default(0)
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
match choice {
|
||||||
|
0 => host::run(cli.into_host_opts(true)).await,
|
||||||
|
_ => {
|
||||||
|
let ticket = prompt_ticket(&theme)?;
|
||||||
|
viewer::run(ticket, cli.into_viewer_opts(true)).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_welcome() {
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Welcome to PixelPass.");
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_ticket(theme: &ColorfulTheme) -> Result<EndpointTicket> {
|
||||||
|
loop {
|
||||||
|
let raw: String = Input::with_theme(theme)
|
||||||
|
.with_prompt("Paste the share code you received")
|
||||||
|
.interact_text()?;
|
||||||
|
match EndpointTicket::from_str(raw.trim()) {
|
||||||
|
Ok(t) => return Ok(t),
|
||||||
|
Err(_) => eprintln!("That doesn't look like a share code. Try again."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Player options the interactive viewer can launch.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Player {
|
||||||
|
Mpv,
|
||||||
|
Vlc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
pub fn spawn(self, url: &str) -> std::io::Result<()> {
|
||||||
|
match self {
|
||||||
|
Player::Mpv => crate::common::process::spawn_detached(
|
||||||
|
"mpv",
|
||||||
|
&[
|
||||||
|
"--profile=low-latency",
|
||||||
|
"--untimed",
|
||||||
|
"--audio-buffer=0.2",
|
||||||
|
"--demuxer-max-bytes=2M",
|
||||||
|
"--demuxer-readahead-secs=0.5",
|
||||||
|
url,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Player::Vlc => crate::common::process::spawn_detached(
|
||||||
|
"vlc",
|
||||||
|
&["--network-caching=200", "--live-caching=200", url],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prompt_player() -> Result<Player> {
|
||||||
|
let theme = ColorfulTheme::default();
|
||||||
|
let choice = Select::with_theme(&theme)
|
||||||
|
.with_prompt("Connected. Pick a player to launch")
|
||||||
|
.items(&["mpv", "VLC"])
|
||||||
|
.default(0)
|
||||||
|
.interact()?;
|
||||||
|
Ok(if choice == 0 { Player::Mpv } else { Player::Vlc })
|
||||||
|
}
|
||||||
+4
-3
@@ -1,6 +1,7 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod common;
|
mod common;
|
||||||
mod host;
|
mod host;
|
||||||
|
mod interactive;
|
||||||
mod repair;
|
mod repair;
|
||||||
mod viewer;
|
mod viewer;
|
||||||
|
|
||||||
@@ -24,12 +25,12 @@ async fn main() -> Result<()> {
|
|||||||
let ticket: EndpointTicket = s.parse().map_err(|e| {
|
let ticket: EndpointTicket = s.parse().map_err(|e| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"argument doesn't look like a pixelpass ticket ({e}).\n\
|
"argument doesn't look like a pixelpass ticket ({e}).\n\
|
||||||
Run with no arguments to host, or pass a ticket to view."
|
Run with no arguments for the interactive menu, or pass a ticket to view."
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
viewer::run(ticket, cli.into_viewer_opts()).await
|
viewer::run(ticket, cli.into_viewer_opts(false)).await
|
||||||
}
|
}
|
||||||
None => host::run(cli.into_host_opts()).await,
|
None => interactive::run(cli).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-4
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use iroh::Endpoint;
|
use iroh::Endpoint;
|
||||||
use iroh::endpoint::presets;
|
use iroh::endpoint::presets;
|
||||||
use iroh_tickets::endpoint::EndpointTicket;
|
use iroh_tickets::endpoint::EndpointTicket;
|
||||||
@@ -22,7 +22,17 @@ pub async fn run(ticket: EndpointTicket, opts: ViewerOpts) -> Result<()> {
|
|||||||
|
|
||||||
let listener = TcpListener::bind(("127.0.0.1", opts.port)).await?;
|
let listener = TcpListener::bind(("127.0.0.1", opts.port)).await?;
|
||||||
let port = listener.local_addr()?.port();
|
let port = listener.local_addr()?.port();
|
||||||
print_viewer_banner(port);
|
let url = format!("http://127.0.0.1:{port}");
|
||||||
|
|
||||||
|
if opts.interactive {
|
||||||
|
let player = crate::interactive::prompt_player()?;
|
||||||
|
player
|
||||||
|
.spawn(&url)
|
||||||
|
.with_context(|| "failed to launch player")?;
|
||||||
|
print_viewer_banner_interactive();
|
||||||
|
} else {
|
||||||
|
print_viewer_banner(&url);
|
||||||
|
}
|
||||||
|
|
||||||
let result = tokio::select! {
|
let result = tokio::select! {
|
||||||
accepted = listener.accept() => {
|
accepted = listener.accept() => {
|
||||||
@@ -40,8 +50,7 @@ pub async fn run(ticket: EndpointTicket, opts: ViewerOpts) -> Result<()> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_viewer_banner(port: u16) {
|
fn print_viewer_banner(url: &str) {
|
||||||
let url = format!("http://127.0.0.1:{port}");
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("┌─ PixelPass · viewer ───────────────────────────────────────");
|
eprintln!("┌─ PixelPass · viewer ───────────────────────────────────────");
|
||||||
eprintln!("│ Connected to host. Open the stream in your player:");
|
eprintln!("│ Connected to host. Open the stream in your player:");
|
||||||
@@ -55,3 +64,12 @@ fn print_viewer_banner(port: u16) {
|
|||||||
eprintln!("└────────────────────────────────────────────────────────────");
|
eprintln!("└────────────────────────────────────────────────────────────");
|
||||||
eprintln!();
|
eprintln!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_viewer_banner_interactive() {
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("┌─ PixelPass · viewer ───────────────────────────────────────");
|
||||||
|
eprintln!("│ Player launched. Close it (or press Ctrl+C here) to");
|
||||||
|
eprintln!("│ disconnect.");
|
||||||
|
eprintln!("└────────────────────────────────────────────────────────────");
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user