lesavka/client/src/launcher/ui/utility_button_bindings.rs

388 lines
17 KiB
Rust
Raw Normal View History

{
{
let child_proc = Rc::clone(&child_proc);
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let clipboard_tx = clipboard_tx.clone();
widgets.clipboard_button.connect_clicked(move |_| {
if child_proc.borrow().is_none() {
widgets
.status_label
.set_text("Start the relay before sending clipboard text.");
return;
}
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
let Some(display) = gtk::gdk::Display::default() else {
widgets
.status_label
.set_text("No desktop clipboard is available in this session.");
return;
};
widgets
.status_label
.set_text("Reading the local clipboard and preparing remote paste...");
let clipboard = display.clipboard();
let clipboard_tx = clipboard_tx.clone();
clipboard.read_text_async(None::<&gtk::gio::Cancellable>, move |result| match result {
Ok(Some(text)) => {
let text = text.trim_end_matches(['\r', '\n']).to_string();
if text.is_empty() {
let _ = clipboard_tx
.send(ClipboardMessage::Finished(Err("clipboard is empty".to_string())));
return;
}
let clipboard_tx = clipboard_tx.clone();
std::thread::spawn(move || {
let result = send_clipboard_text_to_remote(&server_addr, &text)
.map_err(|err| err.to_string());
let _ = clipboard_tx.send(ClipboardMessage::Finished(result));
});
}
Ok(None) => {
let _ = clipboard_tx
.send(ClipboardMessage::Finished(Err("clipboard is empty".to_string())));
}
Err(err) => {
let _ = clipboard_tx.send(ClipboardMessage::Finished(Err(format!(
"clipboard read failed: {err}"
))));
}
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let widgets_for_click = widgets.clone();
widgets.usb_recover_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets_for_click.status_label.set_text(
"Recover USB 1/3: sending gadget reset request to relay host...",
);
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let result = reset_usb_gadget(&server_addr).map_err(|err| format!("{err:#}"));
let _ = tx.send(result);
});
let widgets = widgets_for_click.clone();
glib::timeout_add_local(Duration::from_millis(100), move || match rx.try_recv() {
Ok(Ok(())) => {
widgets.status_label.set_text(
"Recover USB 2/3: relay acknowledged reset. Recover USB 3/3: waiting for USB/UAC/UVC chips to settle.",
);
glib::ControlFlow::Break
}
Ok(Err(err)) => {
widgets
.status_label
.set_text(&format!("Recover USB failed: {err}"));
glib::ControlFlow::Break
}
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
widgets.status_label.set_text(
"Recover USB failed: relay stopped responding before completion.",
);
glib::ControlFlow::Break
}
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let widgets_for_click = widgets.clone();
widgets.uac_recover_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets_for_click
.status_label
.set_text("Recover UAC 1/3: sending gadget reset request to relay host...");
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let result = reset_usb_gadget(&server_addr).map_err(|err| format!("{err:#}"));
let _ = tx.send(result);
});
let widgets = widgets_for_click.clone();
glib::timeout_add_local(Duration::from_millis(100), move || match rx.try_recv() {
Ok(Ok(())) => {
widgets.status_label.set_text(
"Recover UAC 2/3: relay acknowledged reset. Recover UAC 3/3: waiting for UAC chip to settle.",
);
glib::ControlFlow::Break
}
Ok(Err(err)) => {
widgets
.status_label
.set_text(&format!("Recover UAC failed: {err}"));
glib::ControlFlow::Break
}
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
widgets.status_label.set_text(
"Recover UAC failed: relay stopped responding before completion.",
);
glib::ControlFlow::Break
}
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let widgets_for_click = widgets.clone();
widgets.uvc_recover_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets_for_click
.status_label
.set_text("Recover UVC 1/3: sending gadget reset request to relay host...");
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let result = reset_usb_gadget(&server_addr).map_err(|err| format!("{err:#}"));
let _ = tx.send(result);
});
let widgets = widgets_for_click.clone();
glib::timeout_add_local(Duration::from_millis(100), move || match rx.try_recv() {
Ok(Ok(())) => {
widgets.status_label.set_text(
"Recover UVC 2/3: relay acknowledged reset. Recover UVC 3/3: waiting for UVC chip to settle.",
);
glib::ControlFlow::Break
}
Ok(Err(err)) => {
widgets
.status_label
.set_text(&format!("Recover UVC failed: {err}"));
glib::ControlFlow::Break
}
Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
widgets.status_label.set_text(
"Recover UVC failed: relay stopped responding before completion.",
);
glib::ControlFlow::Break
}
});
});
}
2026-04-30 08:16:57 -03:00
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let calibration_tx = calibration_tx.clone();
let calibration_request_in_flight = Rc::clone(&calibration_request_in_flight);
widgets.calibration_default_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets
.status_label
.set_text("Calibration 1/2: restoring saved upstream A/V default...");
calibration_request_in_flight.set(true);
request_calibration_command(calibration_tx.clone(), server_addr, |server_addr| {
restore_default_calibration(server_addr)
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let calibration_tx = calibration_tx.clone();
let calibration_request_in_flight = Rc::clone(&calibration_request_in_flight);
widgets.calibration_factory_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets
.status_label
.set_text("Calibration 1/2: restoring factory MJPEG upstream A/V baseline...");
calibration_request_in_flight.set(true);
request_calibration_command(calibration_tx.clone(), server_addr, |server_addr| {
restore_factory_calibration(server_addr)
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let calibration_tx = calibration_tx.clone();
let calibration_request_in_flight = Rc::clone(&calibration_request_in_flight);
widgets.calibration_minus_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets
.status_label
.set_text("Calibration 1/2: nudging upstream audio 5 ms earlier...");
calibration_request_in_flight.set(true);
request_calibration_command(calibration_tx.clone(), server_addr, |server_addr| {
nudge_audio_calibration(server_addr, -5_000)
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let calibration_tx = calibration_tx.clone();
let calibration_request_in_flight = Rc::clone(&calibration_request_in_flight);
widgets.calibration_plus_button.connect_clicked(move |_| {
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets
.status_label
.set_text("Calibration 1/2: nudging upstream audio 5 ms later...");
calibration_request_in_flight.set(true);
request_calibration_command(calibration_tx.clone(), server_addr, |server_addr| {
nudge_audio_calibration(server_addr, 5_000)
});
});
}
{
let widgets = widgets.clone();
let server_entry = server_entry.clone();
let server_addr_fallback = Rc::clone(&server_addr);
let calibration_tx = calibration_tx.clone();
let calibration_request_in_flight = Rc::clone(&calibration_request_in_flight);
widgets.calibration_blind_button.connect_clicked(move |_| {
let Some(sample) = widgets.diagnostics_log.borrow().latest().cloned() else {
widgets.status_label.set_text(
"Blind calibration needs a live upstream camera and microphone sample first.",
);
return;
};
let camera = sample.upstream_camera;
let microphone = sample.upstream_microphone;
if !camera.connected || !microphone.connected {
widgets.status_label.set_text(
"Blind calibration refused: upstream camera and microphone are not both live.",
);
return;
}
let delivery_delta_ms =
microphone.latest_delivery_age_ms - camera.latest_delivery_age_ms;
let delivery_skew_ms = delivery_delta_ms.abs();
let enqueue_skew_ms =
(microphone.latest_enqueue_age_ms - camera.latest_enqueue_age_ms).abs();
if camera.queue_depth >= 28 || microphone.queue_depth >= 14 || delivery_skew_ms > 80.0
{
widgets.status_label.set_text(
"Blind calibration refused: live queues are too backed up to make a safe timing estimate. Use the test rig or fix queue churn first.",
);
return;
}
let audio_delta_us = (-(delivery_delta_ms as f64) * 500.0)
.round()
.clamp(-10_000.0, 10_000.0) as i64;
let note = format!(
"blind estimate from live telemetry: mic-camera delivery delta {delivery_delta_ms:+.1}ms, enqueue skew {enqueue_skew_ms:.1}ms; applying half-step audio delta {:+.1}ms",
audio_delta_us as f64 / 1000.0
);
let server_addr = selected_server_addr(&server_entry, server_addr_fallback.as_ref());
widgets
.status_label
.set_text("Calibration 1/2: applying blind upstream A/V estimate...");
calibration_request_in_flight.set(true);
request_calibration_command(calibration_tx.clone(), server_addr, move |server_addr| {
blind_calibration_estimate(
server_addr,
audio_delta_us,
delivery_skew_ms,
enqueue_skew_ms,
&note,
)
});
});
}
{
let widgets = widgets.clone();
widgets.calibration_rig_button.connect_clicked(move |_| {
widgets.status_label.set_text(
"Rig calibration wizard is queued for the 0.16.0 test-equipment phase; for now the manual Tethys sync battery remains the measured-default path.",
);
});
}
{
let widgets = widgets.clone();
widgets.diagnostics_copy_button.connect_clicked(move |_| {
if let Err(err) = copy_plain_text(&widgets.diagnostics_rendered_text.borrow()) {
widgets
.status_label
.set_text(&format!("Could not copy the diagnostics report: {err}"));
} else {
widgets
.status_label
.set_text("Diagnostics report copied to the local clipboard.");
}
});
}
{
let app = app.clone();
let widgets = widgets.clone();
let diagnostics_popout = Rc::clone(&diagnostics_popout);
widgets.diagnostics_popout_button.connect_clicked(move |_| {
open_diagnostics_popout(
&app,
&diagnostics_popout,
&widgets.diagnostics_popout_label,
&widgets.diagnostics_popout_scroll,
&widgets.diagnostics_rendered_text,
);
widgets
.status_label
.set_text("Diagnostics report moved into its own window.");
});
}
{
let widgets = widgets.clone();
widgets.console_level_combo.connect_changed(move |combo| {
let level = combo
.active_id()
.as_deref()
.and_then(ConsoleLogLevel::from_id)
.unwrap_or_default();
*widgets.session_log_level.borrow_mut() = level;
widgets.status_label.set_text(&format!(
"Console now shows {} relay logs and higher.",
level.label()
));
});
}
{
let widgets = widgets.clone();
widgets.console_copy_button.connect_clicked(move |_| {
if let Err(err) = copy_session_log(&widgets.session_log_buffer) {
widgets
.status_label
.set_text(&format!("Could not copy the session log: {err}"));
} else {
widgets
.status_label
.set_text("Session log copied to the local clipboard.");
}
});
}
{
let app = app.clone();
let widgets = widgets.clone();
let log_popout = Rc::clone(&log_popout);
widgets.console_popout_button.connect_clicked(move |_| {
open_session_log_popout(&app, &log_popout, &widgets.session_log_buffer);
widgets
.status_label
.set_text("Session log moved into its own window.");
});
}
}