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

222 lines
7.1 KiB
Rust

fn largest_monitor_size() -> (u32, u32) {
let (width, height) = enumerate_monitors()
.into_iter()
.max_by_key(|monitor| {
effective_monitor_width(monitor) as u64 * effective_monitor_height(monitor) as u64
})
.map(|monitor| {
(
effective_monitor_width(&monitor),
effective_monitor_height(&monitor),
)
})
.unwrap_or((1920, 1080));
(width.max(2), height.max(2))
}
#[cfg(not(coverage))]
fn largest_monitor_physical_size() -> (u32, u32) {
if let Some((width, height)) = probe_kscreen_display_size() {
return (width, height);
}
normalize_breakout_limit(largest_monitor_size().0, largest_monitor_size().1)
}
#[cfg(not(coverage))]
fn probe_kscreen_display_size() -> Option<(u32, u32)> {
let output = Command::new("kscreen-doctor").arg("-o").output().ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8(output.stdout).ok()?;
let mut best = None;
for line in text.lines() {
if !line.contains("Modes:") {
continue;
}
let active = line
.split_whitespace()
.find(|token| token.contains('*') && token.contains('x'))?;
let dims = active
.trim_matches(|ch: char| ch == '*' || ch == '!')
.split('@')
.next()?;
let (width, height) = dims.split_once('x')?;
let width = width.parse::<u32>().ok()?;
let height = height.parse::<u32>().ok()?;
if best
.map(|(best_w, best_h)| width as u64 * height as u64 > best_w as u64 * best_h as u64)
.unwrap_or(true)
{
best = Some((width, height));
}
}
best
}
#[cfg(not(coverage))]
fn effective_monitor_width(monitor: &crate::output::display::MonitorInfo) -> u32 {
let scale = monitor.scale_factor.max(1) as u32;
(monitor.geometry.width().max(1) as u32).saturating_mul(scale)
}
#[cfg(not(coverage))]
fn effective_monitor_height(monitor: &crate::output::display::MonitorInfo) -> u32 {
let scale = monitor.scale_factor.max(1) as u32;
(monitor.geometry.height().max(1) as u32).saturating_mul(scale)
}
#[cfg(not(coverage))]
fn normalize_breakout_limit(width: u32, height: u32) -> (u32, u32) {
const STANDARD_SIZES: &[(u32, u32)] = &[
(3840, 2160),
(2560, 1440),
(1920, 1080),
(1600, 900),
(1366, 768),
(1280, 720),
(960, 540),
];
STANDARD_SIZES
.iter()
.copied()
.find(|(candidate_w, candidate_h)| *candidate_w <= width && *candidate_h <= height)
.unwrap_or((width.max(2), height.max(2)))
}
#[cfg(not(coverage))]
fn launcher_default_size(width: u32, height: u32) -> (i32, i32) {
let max_width = width.saturating_sub(48).max(640) as i32;
let max_height = height.saturating_sub(72).max(520) as i32;
(1380.min(max_width), 860.min(max_height))
}
#[cfg(not(coverage))]
fn rebind_inline_preview(
preview: &super::preview::LauncherPreview,
widgets: &super::ui_components::LauncherWidgets,
state: &LauncherState,
monitor_id: usize,
) {
if let Some(binding) = widgets.display_panes[monitor_id]
.preview_binding
.borrow_mut()
.take()
{
binding.close();
}
if state.feed_source_preset(monitor_id) == FeedSourcePreset::Off {
widgets.display_panes[monitor_id]
.picture
.set_paintable(Option::<&gtk::gdk::Paintable>::None);
widgets.display_panes[monitor_id]
.stream_status
.set_text("Feed disabled.");
return;
}
let binding = preview.install_on_picture(
monitor_id,
super::preview::PreviewSurface::Inline,
&widgets.display_panes[monitor_id].picture,
&widgets.display_panes[monitor_id].stream_status,
);
*widgets.display_panes[monitor_id]
.preview_binding
.borrow_mut() = binding;
}
#[cfg(not(coverage))]
fn rebind_popout_preview(
preview: &super::preview::LauncherPreview,
popouts: &Rc<RefCell<[Option<super::ui_components::PopoutWindowHandle>; 2]>>,
state: &LauncherState,
monitor_id: usize,
) {
let mut popouts = popouts.borrow_mut();
let Some(handle) = popouts.get_mut(monitor_id).and_then(|slot| slot.as_mut()) else {
return;
};
handle.binding.close();
if state.feed_source_preset(monitor_id) == FeedSourcePreset::Off {
handle
.picture
.set_paintable(Option::<&gtk::gdk::Paintable>::None);
handle.status_label.set_text("Feed disabled.");
return;
}
if let Some(binding) = preview.install_on_picture(
monitor_id,
super::preview::PreviewSurface::Window,
&handle.picture,
&handle.status_label,
) {
handle.binding = binding;
}
let capture = state
.display_capture_size_choice(monitor_id)
.unwrap_or_else(|| state.capture_size_choice(monitor_id));
handle
.frame
.set_ratio(capture.preset.display_aspect_ratio());
}
#[cfg(not(coverage))]
fn apply_preview_profiles(preview: &super::preview::LauncherPreview, state: &LauncherState) {
for monitor_id in 0..2 {
let enabled = state.feed_source_preset(monitor_id) != FeedSourcePreset::Off;
preview.set_monitor_enabled(monitor_id, enabled);
let capture = state
.display_capture_size_choice(monitor_id)
.unwrap_or_else(|| state.capture_size_choice(monitor_id));
let source_monitor_id = state
.resolved_feed_monitor_id(monitor_id)
.unwrap_or(monitor_id);
let breakout = state.breakout_size_choice(monitor_id);
preview.set_capture_profile(
monitor_id,
source_monitor_id,
capture.width,
capture.height,
capture.fps,
capture.max_bitrate_kbit,
);
preview.set_breakout_profile(monitor_id, breakout.width, breakout.height);
}
}
#[cfg(not(coverage))]
fn sync_preview_profiles(
preview: &super::preview::LauncherPreview,
widgets: &super::ui_components::LauncherWidgets,
popouts: &Rc<RefCell<[Option<super::ui_components::PopoutWindowHandle>; 2]>>,
state: &LauncherState,
) {
apply_preview_profiles(preview, state);
for monitor_id in 0..2 {
rebind_inline_preview(preview, widgets, state, monitor_id);
rebind_popout_preview(preview, popouts, state, monitor_id);
}
}
#[cfg(not(coverage))]
fn disconnected_capture_note(mode: &str) -> &'static str {
match mode {
"forced-on" => "Relay disconnected. Capture is still forced on for staging.",
"forced-off" => {
"Relay disconnected. Capture stays intentionally dark until you return to Auto or Force On."
}
_ => {
"Relay disconnected. The server will hold capture briefly, then let it return to standby."
}
}
}
/// Keeps remote eye previews tied to a live session while respecting forced-off staging.
fn session_preview_active(
state: &crate::launcher::state::LauncherState,
child_running: bool,
) -> bool {
(child_running || state.remote_active) && state.capture_power.mode != "forced-off"
}