466 lines
15 KiB
Rust
466 lines
15 KiB
Rust
impl LauncherState {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn set_routing(&mut self, routing: InputRouting) {
|
|
self.routing = routing;
|
|
}
|
|
|
|
pub fn set_server_available(&mut self, available: bool) {
|
|
self.server_available = available;
|
|
}
|
|
|
|
pub fn set_server_version(&mut self, version: Option<String>) {
|
|
self.server_version = version.and_then(|value| {
|
|
let trimmed = value.trim();
|
|
if trimmed.is_empty() {
|
|
None
|
|
} else {
|
|
Some(trimmed.to_string())
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn set_view_mode(&mut self, view_mode: ViewMode) {
|
|
self.view_mode = view_mode;
|
|
self.displays = match view_mode {
|
|
ViewMode::Unified => [DisplaySurface::Preview, DisplaySurface::Preview],
|
|
ViewMode::Breakout => [DisplaySurface::Window, DisplaySurface::Window],
|
|
};
|
|
}
|
|
|
|
pub fn display_surface(&self, monitor_id: usize) -> DisplaySurface {
|
|
self.displays
|
|
.get(monitor_id)
|
|
.copied()
|
|
.unwrap_or(DisplaySurface::Preview)
|
|
}
|
|
|
|
pub fn feed_source_preset(&self, monitor_id: usize) -> FeedSourcePreset {
|
|
self.feed_sources
|
|
.get(monitor_id)
|
|
.copied()
|
|
.unwrap_or(FeedSourcePreset::ThisEye)
|
|
}
|
|
|
|
pub fn set_feed_source_preset(&mut self, monitor_id: usize, preset: FeedSourcePreset) {
|
|
if let Some(slot) = self.feed_sources.get_mut(monitor_id) {
|
|
*slot = preset;
|
|
}
|
|
}
|
|
|
|
pub fn feed_source_options(&self, monitor_id: usize) -> Vec<FeedSourceChoice> {
|
|
vec![
|
|
FeedSourceChoice {
|
|
preset: FeedSourcePreset::ThisEye,
|
|
label: FeedSourcePreset::ThisEye.label(monitor_id),
|
|
},
|
|
FeedSourceChoice {
|
|
preset: FeedSourcePreset::OtherEye,
|
|
label: FeedSourcePreset::OtherEye.label(monitor_id),
|
|
},
|
|
FeedSourceChoice {
|
|
preset: FeedSourcePreset::Off,
|
|
label: FeedSourcePreset::Off.label(monitor_id),
|
|
},
|
|
]
|
|
}
|
|
|
|
pub fn resolved_feed_monitor_id(&self, monitor_id: usize) -> Option<usize> {
|
|
match self.feed_source_preset(monitor_id) {
|
|
FeedSourcePreset::ThisEye => Some(monitor_id.min(1)),
|
|
FeedSourcePreset::OtherEye => Some(1_usize.saturating_sub(monitor_id.min(1))),
|
|
FeedSourcePreset::Off => None,
|
|
}
|
|
}
|
|
|
|
pub fn set_display_surface(&mut self, monitor_id: usize, surface: DisplaySurface) {
|
|
if let Some(slot) = self.displays.get_mut(monitor_id) {
|
|
*slot = surface;
|
|
self.view_mode = if self
|
|
.displays
|
|
.iter()
|
|
.any(|display| matches!(display, DisplaySurface::Window))
|
|
{
|
|
ViewMode::Breakout
|
|
} else {
|
|
ViewMode::Unified
|
|
};
|
|
}
|
|
}
|
|
|
|
pub fn breakout_count(&self) -> usize {
|
|
self.displays
|
|
.iter()
|
|
.filter(|surface| matches!(surface, DisplaySurface::Window))
|
|
.count()
|
|
}
|
|
|
|
pub fn preview_source_size(&self) -> PreviewSourceSize {
|
|
self.preview_source
|
|
}
|
|
|
|
pub fn set_preview_source_profile(&mut self, width: u32, height: u32, fps: u32) {
|
|
if width == 0 || height == 0 {
|
|
return;
|
|
}
|
|
self.preview_source = PreviewSourceSize {
|
|
width,
|
|
height,
|
|
fps: fps.max(1),
|
|
};
|
|
}
|
|
|
|
pub fn breakout_limit_size(&self) -> PreviewSourceSize {
|
|
self.breakout_limit
|
|
}
|
|
|
|
pub fn set_breakout_limit_size(&mut self, width: u32, height: u32) {
|
|
if width == 0 || height == 0 {
|
|
return;
|
|
}
|
|
self.breakout_limit = PreviewSourceSize {
|
|
width,
|
|
height,
|
|
fps: self.breakout_limit.fps.max(1),
|
|
};
|
|
}
|
|
|
|
pub fn breakout_display_size(&self) -> PreviewSourceSize {
|
|
self.breakout_display
|
|
}
|
|
|
|
pub fn set_breakout_display_size(&mut self, width: u32, height: u32) {
|
|
if width == 0 || height == 0 {
|
|
return;
|
|
}
|
|
self.breakout_display = PreviewSourceSize {
|
|
width,
|
|
height,
|
|
fps: self.breakout_display.fps.max(1),
|
|
};
|
|
}
|
|
|
|
pub fn capture_size_preset(&self, monitor_id: usize) -> CaptureSizePreset {
|
|
normalize_capture_size_preset(
|
|
self.capture_sizes
|
|
.get(monitor_id)
|
|
.copied()
|
|
.unwrap_or(CaptureSizePreset::P1080),
|
|
)
|
|
}
|
|
|
|
pub fn display_capture_size_preset(&self, monitor_id: usize) -> Option<CaptureSizePreset> {
|
|
self.resolved_feed_monitor_id(monitor_id)
|
|
.map(|source_id| self.capture_size_preset(source_id))
|
|
}
|
|
|
|
pub fn set_capture_size_preset(&mut self, monitor_id: usize, preset: CaptureSizePreset) {
|
|
let preset = normalize_capture_size_preset(preset);
|
|
if let Some(slot) = self.capture_sizes.get_mut(monitor_id) {
|
|
*slot = preset;
|
|
}
|
|
let defaults = default_profile_for_preset(self.preview_source, preset);
|
|
self.set_capture_fps(monitor_id, defaults.fps);
|
|
self.set_capture_bitrate_kbit(monitor_id, defaults.max_bitrate_kbit);
|
|
}
|
|
|
|
pub fn capture_fps(&self, monitor_id: usize) -> u32 {
|
|
self.capture_fps
|
|
.get(monitor_id)
|
|
.copied()
|
|
.unwrap_or(default_eye_source_mode().fps)
|
|
.max(1)
|
|
}
|
|
|
|
pub fn display_capture_fps(&self, monitor_id: usize) -> Option<u32> {
|
|
self.resolved_feed_monitor_id(monitor_id)
|
|
.map(|source_id| self.capture_fps(source_id))
|
|
}
|
|
|
|
pub fn set_capture_fps(&mut self, monitor_id: usize, fps: u32) {
|
|
if let Some(slot) = self.capture_fps.get_mut(monitor_id) {
|
|
*slot = fps.max(1);
|
|
}
|
|
}
|
|
|
|
pub fn capture_bitrate_kbit(&self, monitor_id: usize) -> u32 {
|
|
self.capture_bitrates_kbit
|
|
.get(monitor_id)
|
|
.copied()
|
|
.unwrap_or(estimate_source_bitrate_kbit(
|
|
default_eye_source_mode().width as i32,
|
|
default_eye_source_mode().height as i32,
|
|
default_eye_source_mode().fps,
|
|
))
|
|
.max(800)
|
|
}
|
|
|
|
pub fn display_capture_bitrate_kbit(&self, monitor_id: usize) -> Option<u32> {
|
|
self.resolved_feed_monitor_id(monitor_id)
|
|
.map(|source_id| self.capture_bitrate_kbit(source_id))
|
|
}
|
|
|
|
pub fn set_capture_bitrate_kbit(&mut self, monitor_id: usize, max_bitrate_kbit: u32) {
|
|
if let Some(slot) = self.capture_bitrates_kbit.get_mut(monitor_id) {
|
|
*slot = max_bitrate_kbit.max(800);
|
|
}
|
|
}
|
|
|
|
pub fn capture_size_choice(&self, monitor_id: usize) -> CaptureSizeChoice {
|
|
capture_size_choice(
|
|
self.preview_source,
|
|
self.capture_size_preset(monitor_id),
|
|
self.capture_fps(monitor_id),
|
|
self.capture_bitrate_kbit(monitor_id),
|
|
)
|
|
}
|
|
|
|
pub fn display_capture_size_choice(&self, monitor_id: usize) -> Option<CaptureSizeChoice> {
|
|
self.resolved_feed_monitor_id(monitor_id)
|
|
.map(|source_id| self.capture_size_choice(source_id))
|
|
}
|
|
|
|
pub fn effective_preview_source_size(&self, monitor_id: usize) -> PreviewSourceSize {
|
|
let capture = self
|
|
.display_capture_size_choice(monitor_id)
|
|
.unwrap_or_else(|| self.capture_size_choice(monitor_id));
|
|
PreviewSourceSize {
|
|
width: capture.width.max(1) as u32,
|
|
height: capture.height.max(1) as u32,
|
|
fps: capture.fps.max(1),
|
|
}
|
|
}
|
|
|
|
pub fn capture_size_options(&self) -> Vec<CaptureSizeChoice> {
|
|
capture_size_options(self.preview_source)
|
|
}
|
|
|
|
pub fn capture_fps_options(&self) -> Vec<CaptureFpsChoice> {
|
|
capture_fps_options(self.preview_source)
|
|
}
|
|
|
|
pub fn capture_bitrate_options(&self) -> Vec<CaptureBitrateChoice> {
|
|
capture_bitrate_options(self.preview_source)
|
|
}
|
|
|
|
pub fn breakout_size_preset(&self, monitor_id: usize) -> BreakoutSizePreset {
|
|
self.breakout_sizes
|
|
.get(monitor_id)
|
|
.copied()
|
|
.unwrap_or(BreakoutSizePreset::Source)
|
|
}
|
|
|
|
pub fn set_breakout_size_preset(&mut self, monitor_id: usize, preset: BreakoutSizePreset) {
|
|
if let Some(slot) = self.breakout_sizes.get_mut(monitor_id) {
|
|
*slot = preset;
|
|
}
|
|
}
|
|
|
|
pub fn breakout_size_choice(&self, monitor_id: usize) -> BreakoutSizeChoice {
|
|
breakout_size_choice(
|
|
self.breakout_limit,
|
|
self.breakout_display,
|
|
self.effective_preview_source_size(monitor_id),
|
|
self.breakout_size_preset(monitor_id),
|
|
)
|
|
}
|
|
|
|
pub fn breakout_size_options(&self, monitor_id: usize) -> Vec<BreakoutSizeChoice> {
|
|
breakout_size_options(
|
|
self.breakout_limit,
|
|
self.breakout_display,
|
|
self.effective_preview_source_size(monitor_id),
|
|
)
|
|
}
|
|
|
|
pub fn select_camera(&mut self, camera: Option<String>) {
|
|
self.devices.camera = normalize_selection(camera);
|
|
}
|
|
|
|
pub fn select_camera_quality(&mut self, mode: Option<CameraMode>) {
|
|
self.camera_quality = mode;
|
|
}
|
|
|
|
pub fn camera_quality_options(&self, catalog: &DeviceCatalog) -> Vec<CameraMode> {
|
|
self.devices
|
|
.camera
|
|
.as_ref()
|
|
.and_then(|camera| catalog.camera_modes.get(camera))
|
|
.cloned()
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
pub fn selected_camera_quality(&self, catalog: &DeviceCatalog) -> Option<CameraMode> {
|
|
let options = self.camera_quality_options(catalog);
|
|
self.camera_quality
|
|
.filter(|selected| options.contains(selected))
|
|
.or_else(|| options.first().copied())
|
|
}
|
|
|
|
pub fn normalize_camera_quality(&mut self, catalog: &DeviceCatalog) {
|
|
self.camera_quality = self.selected_camera_quality(catalog);
|
|
}
|
|
|
|
pub fn select_microphone(&mut self, microphone: Option<String>) {
|
|
self.devices.microphone = normalize_selection(microphone);
|
|
}
|
|
|
|
pub fn select_speaker(&mut self, speaker: Option<String>) {
|
|
self.devices.speaker = normalize_selection(speaker);
|
|
}
|
|
|
|
pub fn set_camera_channel_enabled(&mut self, enabled: bool) {
|
|
self.channels.camera = enabled;
|
|
}
|
|
|
|
pub fn set_microphone_channel_enabled(&mut self, enabled: bool) {
|
|
self.channels.microphone = enabled;
|
|
}
|
|
|
|
pub fn set_audio_channel_enabled(&mut self, enabled: bool) {
|
|
self.channels.audio = enabled;
|
|
}
|
|
|
|
pub fn set_audio_gain_percent(&mut self, percent: u32) {
|
|
self.audio_gain_percent = normalize_audio_gain_percent(percent);
|
|
}
|
|
|
|
pub fn audio_gain_multiplier(&self) -> f64 {
|
|
self.audio_gain_percent as f64 / 100.0
|
|
}
|
|
|
|
pub fn audio_gain_env_value(&self) -> String {
|
|
format!("{:.3}", self.audio_gain_multiplier())
|
|
}
|
|
|
|
pub fn audio_gain_label(&self) -> String {
|
|
format_audio_gain_percent(self.audio_gain_percent)
|
|
}
|
|
|
|
pub fn set_mic_gain_percent(&mut self, percent: u32) {
|
|
self.mic_gain_percent = normalize_mic_gain_percent(percent);
|
|
}
|
|
|
|
pub fn mic_gain_multiplier(&self) -> f64 {
|
|
self.mic_gain_percent as f64 / 100.0
|
|
}
|
|
|
|
pub fn mic_gain_env_value(&self) -> String {
|
|
format!("{:.3}", self.mic_gain_multiplier())
|
|
}
|
|
|
|
pub fn mic_gain_label(&self) -> String {
|
|
format_mic_gain_percent(self.mic_gain_percent)
|
|
}
|
|
|
|
pub fn select_keyboard(&mut self, keyboard: Option<String>) {
|
|
self.devices.keyboard = normalize_selection(keyboard);
|
|
}
|
|
|
|
pub fn select_mouse(&mut self, mouse: Option<String>) {
|
|
self.devices.mouse = normalize_selection(mouse);
|
|
}
|
|
|
|
pub fn apply_catalog_defaults(&mut self, catalog: &DeviceCatalog) {
|
|
keep_or_select_first(&mut self.devices.camera, &catalog.cameras);
|
|
self.normalize_camera_quality(catalog);
|
|
keep_or_select_first(&mut self.devices.microphone, &catalog.microphones);
|
|
keep_or_select_first(&mut self.devices.speaker, &catalog.speakers);
|
|
}
|
|
|
|
pub fn set_swap_key(&mut self, swap_key: impl Into<String>) {
|
|
self.swap_key = normalize_swap_key(swap_key.into());
|
|
}
|
|
|
|
pub fn begin_swap_key_binding(&mut self) -> u64 {
|
|
self.swap_key_binding = true;
|
|
self.swap_key_binding_token = self.swap_key_binding_token.wrapping_add(1);
|
|
self.swap_key_binding_token
|
|
}
|
|
|
|
pub fn finish_swap_key_binding(&mut self) {
|
|
self.swap_key_binding = false;
|
|
}
|
|
|
|
pub fn cancel_swap_key_binding(&mut self, token: u64) -> bool {
|
|
if self.swap_key_binding && self.swap_key_binding_token == token {
|
|
self.swap_key_binding = false;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn complete_swap_key_binding(&mut self, swap_key: impl Into<String>) {
|
|
self.set_swap_key(swap_key);
|
|
self.finish_swap_key_binding();
|
|
}
|
|
|
|
pub fn start_remote(&mut self) -> bool {
|
|
if self.remote_active {
|
|
return false;
|
|
}
|
|
self.remote_active = true;
|
|
true
|
|
}
|
|
|
|
pub fn stop_remote(&mut self) -> bool {
|
|
if !self.remote_active {
|
|
return false;
|
|
}
|
|
self.remote_active = false;
|
|
true
|
|
}
|
|
|
|
pub fn push_note(&mut self, note: impl Into<String>) {
|
|
self.notes.push(note.into());
|
|
}
|
|
|
|
pub fn set_capture_power(&mut self, power: CapturePowerStatus) {
|
|
self.capture_power = power;
|
|
}
|
|
|
|
pub fn status_line(&self) -> String {
|
|
format!(
|
|
"server={} mode={} view={} active={} power={} source={}x{} d1={} d2={} s1={} s2={} camera={} camera_quality={} mic={} speaker={} channels=cam:{}/mic:{}/audio:{} audio_gain={} mic_gain={} kbd={} mouse={} swap={}",
|
|
self.server_available,
|
|
match self.routing {
|
|
InputRouting::Local => "local",
|
|
InputRouting::Remote => "remote",
|
|
},
|
|
match self.view_mode {
|
|
ViewMode::Unified => "unified",
|
|
ViewMode::Breakout => "breakout",
|
|
},
|
|
self.remote_active,
|
|
if self.capture_power.enabled {
|
|
"on"
|
|
} else {
|
|
"off"
|
|
},
|
|
self.preview_source.width,
|
|
self.preview_source.height,
|
|
self.displays[0].label(),
|
|
self.displays[1].label(),
|
|
self.feed_source_preset(0).as_id(),
|
|
self.feed_source_preset(1).as_id(),
|
|
media_status_label(self.channels.camera, self.devices.camera.as_deref()),
|
|
self.camera_quality
|
|
.map(CameraMode::short_label)
|
|
.unwrap_or_else(|| "default".to_string()),
|
|
media_status_label(self.channels.microphone, self.devices.microphone.as_deref()),
|
|
media_status_label(self.channels.audio, self.devices.speaker.as_deref()),
|
|
self.channels.camera,
|
|
self.channels.microphone,
|
|
self.channels.audio,
|
|
self.audio_gain_label(),
|
|
self.mic_gain_label(),
|
|
self.devices.keyboard.as_deref().unwrap_or("all"),
|
|
self.devices.mouse.as_deref().unwrap_or("all"),
|
|
self.swap_key,
|
|
)
|
|
}
|
|
}
|