Merge origin/main (shared clipboard Phase 1) into the local W6.2 + rumble-D line
Reconciles the two diverged mains: the local stack (W6.1/W6.2 crate decomposition, lid-closed fixes, ABR climb gating, rumble root-fix A-D incl. the W7 core module splits) x origin/main (clipboard wire ABI v8 + pf-clipboard + the macOS client). Textual conflicts were trivial (host Cargo.toml dep union; Cargo.lock + the cbindgen header regenerate). The real work was structural: the local line's W7 splits dissolved the two files the clipboard's Phase 0 landed in, so the merge left both layouts side by side. Resolved by re-homing the clipboard delta into the split layout and deleting the stale flat files: - quic/msgs.rs -> quic/clip.rs (the 0x40-0x44 messages, CLIP_* vocabulary, codecs) + HOST_CAP_CLIPBOARD into quic/caps.rs beside GAMEPAD_STATE; mod.rs declares clip and fixes the split-by-concern doc. - client.rs -> the client/ split: CtrlRequest clip variants + Negotiated.host_caps (control.rs), CLIP_EVENT_QUEUE (planes.rs), clip channel ends (worker.rs), NativeClient clip surface + channels (mod.rs), control-task encode/decode arms + the clipboard task spawn (pump.rs). Verified on macOS: core 174/174 (merged rumble + clipboard suites), pf-clipboard clippy -D warnings clean, fmt applied, header regenerated. Host-side compile rides the Linux/Windows legs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,13 +7,14 @@ use super::frame_channel::{
|
||||
use super::worker::reject_from_close;
|
||||
use super::*;
|
||||
use crate::abr::BitrateController;
|
||||
use crate::clipboard::ClipEventCore;
|
||||
use crate::config::{CompositorPref, GamepadPref, Role};
|
||||
use crate::error::PunktfunkError;
|
||||
use crate::packet::FLAG_PROBE;
|
||||
use crate::quic::{
|
||||
accept_resync, endpoint, io, wall_clock_ns, window_loss_ppm, BitrateChanged, ClockEcho,
|
||||
ClockResync, Hello, HidOutput, LossReport, ProbeRequest, ProbeResult, Reconfigure,
|
||||
Reconfigured, RequestKeyframe, ResyncStep, SetBitrate, Start, Welcome,
|
||||
accept_resync, endpoint, io, wall_clock_ns, window_loss_ppm, BitrateChanged, ClipOffer,
|
||||
ClipState, ClockEcho, ClockResync, Hello, HidOutput, LossReport, ProbeRequest, ProbeResult,
|
||||
Reconfigure, Reconfigured, RequestKeyframe, ResyncStep, SetBitrate, Start, Welcome,
|
||||
};
|
||||
use crate::session::Session;
|
||||
use crate::transport::UdpTransport;
|
||||
@@ -49,6 +50,8 @@ pub(super) async fn run_pump(args: WorkerArgs) {
|
||||
mut rich_input_rx,
|
||||
mut ctrl_rx,
|
||||
ctrl_tx,
|
||||
clip_event_tx,
|
||||
clip_cmd_rx,
|
||||
ready_tx,
|
||||
shutdown,
|
||||
quit,
|
||||
@@ -213,6 +216,7 @@ pub(super) async fn run_pump(args: WorkerArgs) {
|
||||
audio_channels: welcome.audio_channels,
|
||||
codec: welcome.codec,
|
||||
shard_payload: welcome.shard_payload,
|
||||
host_caps: welcome.host_caps,
|
||||
},
|
||||
welcome.host_caps,
|
||||
))
|
||||
@@ -413,6 +417,9 @@ pub(super) async fn run_pump(args: WorkerArgs) {
|
||||
let bitrate_ack = bitrate_ack.clone();
|
||||
let clock_offset = clock_offset.clone();
|
||||
let clock_gen = clock_gen.clone();
|
||||
// The control task feeds clipboard metadata events (ClipState/ClipOffer) onto the same event
|
||||
// plane the clipboard task uses for fetch data; the original tx goes to that task below.
|
||||
let clip_event_tx = clip_event_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
// Mid-stream clock re-sync (see [`ClockResync`]): a batch runs every
|
||||
// CLOCK_RESYNC_INTERVAL and whenever the pump asks (CtrlRequest::ClockResync after
|
||||
@@ -442,6 +449,8 @@ pub(super) async fn run_pump(args: WorkerArgs) {
|
||||
}
|
||||
resync.begin(wall_clock_ns()).encode()
|
||||
}
|
||||
CtrlRequest::ClipControl(c) => c.encode(),
|
||||
CtrlRequest::ClipOffer(o) => o.encode(),
|
||||
};
|
||||
if io::write_msg(&mut ctrl_send, &bytes).await.is_err() {
|
||||
break;
|
||||
@@ -523,6 +532,21 @@ pub(super) async fn run_pump(args: WorkerArgs) {
|
||||
}
|
||||
ResyncStep::Idle => {}
|
||||
}
|
||||
} else if let Ok(state) = ClipState::decode(&msg) {
|
||||
// Host ack / policy / backend update for the toggle UI (try_send: a
|
||||
// lagging embedder drops the newest — a stale toggle heals on the next).
|
||||
let _ = clip_event_tx.try_send(ClipEventCore::State {
|
||||
enabled: state.enabled,
|
||||
policy: state.policy,
|
||||
reason: state.reason,
|
||||
});
|
||||
} else if let Ok(offer) = ClipOffer::decode(&msg) {
|
||||
// The host copied something: surface the lazy format list; the embedder
|
||||
// fetches only if a local app pastes.
|
||||
let _ = clip_event_tx.try_send(ClipEventCore::RemoteOffer {
|
||||
seq: offer.seq,
|
||||
kinds: offer.kinds,
|
||||
});
|
||||
} else {
|
||||
tracing::warn!(
|
||||
tag = ?msg.first(),
|
||||
@@ -536,6 +560,17 @@ pub(super) async fn run_pump(args: WorkerArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
// Clipboard task: the fetch-stream accept loop (host pulls what we offered) + outbound fetches
|
||||
// (we pull what the host offered). Metadata (enable/offer/state) rides the control task above;
|
||||
// only bulk bytes flow here. Dies with the connection (accept_bi errors) or when the embedder
|
||||
// drops the command sender. Always spawned — a host without HOST_CAP_CLIPBOARD simply never
|
||||
// opens a clip stream, and our control-plane offers hit its "unknown message" arm harmlessly.
|
||||
tokio::spawn(crate::clipboard::run(
|
||||
conn.clone(),
|
||||
clip_event_tx,
|
||||
clip_cmd_rx,
|
||||
));
|
||||
|
||||
// Datagram demux: host → client audio/rumble (try_send: a lagging embedder drops the
|
||||
// newest packet rather than backing up the QUIC receive path).
|
||||
let dgram_conn = conn.clone();
|
||||
|
||||
Reference in New Issue
Block a user