ui(launcher): mirror upstream preview and stop disabled media tests
This commit is contained in:
parent
a305746a0e
commit
6d0387ace7
Binary file not shown.
|
Before Width: | Height: | Size: 505 KiB After Width: | Height: | Size: 505 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 496 KiB After Width: | Height: | Size: 496 KiB |
BIN
client/assets/placeholders/webcam_disabled.png
Normal file
BIN
client/assets/placeholders/webcam_disabled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 614 KiB |
@ -74,10 +74,15 @@ impl DeviceTestController {
|
||||
camera_picture: >k::Picture,
|
||||
camera_status: >k::Label,
|
||||
) -> Result<()> {
|
||||
let mirrored = self
|
||||
.camera
|
||||
.as_ref()
|
||||
.is_some_and(LocalCameraPreview::mirrored);
|
||||
if let Some(camera) = self.camera.as_mut() {
|
||||
camera.stop();
|
||||
}
|
||||
let mut preview = LocalCameraPreview::new(camera_picture, camera_status);
|
||||
preview.set_mirrored(mirrored);
|
||||
preview.set_selected(self.selected_camera.as_deref())?;
|
||||
preview.set_selected_mode(self.selected_camera_mode)?;
|
||||
self.camera = Some(preview);
|
||||
@ -129,6 +134,24 @@ impl DeviceTestController {
|
||||
preview.toggle()
|
||||
}
|
||||
|
||||
pub fn stop_camera_preview(&mut self) {
|
||||
if let Some(camera) = self.camera.as_mut() {
|
||||
camera.stop();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_camera_preview_mirrored(&mut self, mirrored: bool) {
|
||||
if let Some(camera) = self.camera.as_mut() {
|
||||
camera.set_mirrored(mirrored);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn camera_preview_mirrored(&self) -> bool {
|
||||
self.camera
|
||||
.as_ref()
|
||||
.is_some_and(LocalCameraPreview::mirrored)
|
||||
}
|
||||
|
||||
pub fn toggle_microphone(&mut self, source: Option<&str>, sink: Option<&str>) -> Result<bool> {
|
||||
self.cleanup_finished();
|
||||
if self.microphone.is_some() {
|
||||
@ -146,6 +169,10 @@ impl DeviceTestController {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn stop_microphone_monitor(&mut self) {
|
||||
self.stop(DeviceTestKind::Microphone);
|
||||
}
|
||||
|
||||
pub fn stop_local_capture_for_relay(&mut self) {
|
||||
if self
|
||||
.camera
|
||||
@ -215,6 +242,12 @@ impl DeviceTestController {
|
||||
self.toggle_child(DeviceTestKind::Speaker, build_speaker_test(sink))
|
||||
}
|
||||
|
||||
pub fn stop_speaker_test(&mut self) {
|
||||
if self.speaker.is_some() {
|
||||
self.stop(DeviceTestKind::Speaker);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_microphone_replay(&mut self, sink: Option<&str>) -> Result<bool> {
|
||||
self.cleanup_finished();
|
||||
if self.microphone_replay.is_some() {
|
||||
@ -231,6 +264,12 @@ impl DeviceTestController {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn stop_microphone_replay(&mut self) {
|
||||
if self.microphone_replay.is_some() {
|
||||
self.stop(DeviceTestKind::MicrophoneReplay);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn microphone_level_fraction(&mut self) -> f64 {
|
||||
self.cleanup_finished();
|
||||
self.microphone_level
|
||||
@ -370,10 +409,12 @@ impl DeviceTestController {
|
||||
}
|
||||
|
||||
struct LocalCameraPreview {
|
||||
picture: gtk::Picture,
|
||||
latest: Arc<Mutex<Option<PreviewFrame>>>,
|
||||
status_text: Arc<Mutex<String>>,
|
||||
generation: Arc<AtomicU64>,
|
||||
running: Arc<AtomicBool>,
|
||||
mirrored: Arc<AtomicBool>,
|
||||
selected_device: Option<String>,
|
||||
selected_mode: Option<CameraMode>,
|
||||
relay_preview_path: Option<PathBuf>,
|
||||
|
||||
@ -4,17 +4,22 @@ impl LocalCameraPreview {
|
||||
let status_text = Arc::new(Mutex::new(CAMERA_PREVIEW_IDLE.to_string()));
|
||||
let generation = Arc::new(AtomicU64::new(0));
|
||||
let running = Arc::new(AtomicBool::new(false));
|
||||
let mirrored = Arc::new(AtomicBool::new(false));
|
||||
|
||||
picture.set_paintable(Some(&blank_camera_preview_texture()));
|
||||
picture.set_paintable(Some(&camera_preview_placeholder_texture()));
|
||||
|
||||
{
|
||||
let picture = picture.clone();
|
||||
let status_label = status_label.clone();
|
||||
let latest = Arc::clone(&latest);
|
||||
let status_text = Arc::clone(&status_text);
|
||||
let mirrored = Arc::clone(&mirrored);
|
||||
glib::timeout_add_local(Duration::from_millis(120), move || {
|
||||
let next = latest.lock().ok().and_then(|mut slot| slot.take());
|
||||
if let Some(frame) = next {
|
||||
if let Some(mut frame) = next {
|
||||
if mirrored.load(Ordering::Acquire) {
|
||||
mirror_preview_frame(&mut frame);
|
||||
}
|
||||
let bytes = glib::Bytes::from_owned(frame.rgba);
|
||||
let texture = gdk::MemoryTexture::new(
|
||||
frame.width,
|
||||
@ -33,10 +38,12 @@ impl LocalCameraPreview {
|
||||
}
|
||||
|
||||
Self {
|
||||
picture: picture.clone(),
|
||||
latest,
|
||||
status_text,
|
||||
generation,
|
||||
running,
|
||||
mirrored,
|
||||
selected_device: None,
|
||||
selected_mode: None,
|
||||
relay_preview_path: None,
|
||||
@ -55,6 +62,14 @@ impl LocalCameraPreview {
|
||||
self.is_running() && self.relay_preview_path.is_some()
|
||||
}
|
||||
|
||||
fn set_mirrored(&mut self, mirrored: bool) {
|
||||
self.mirrored.store(mirrored, Ordering::Release);
|
||||
}
|
||||
|
||||
fn mirrored(&self) -> bool {
|
||||
self.mirrored.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn set_selected(&mut self, camera: Option<&str>) -> Result<()> {
|
||||
self.selected_device = normalize_camera_selection(camera);
|
||||
|
||||
@ -186,6 +201,8 @@ impl LocalCameraPreview {
|
||||
if let Ok(mut latest) = self.latest.lock() {
|
||||
*latest = None;
|
||||
}
|
||||
self.picture
|
||||
.set_paintable(Some(&camera_preview_placeholder_texture()));
|
||||
let message = if was_relay_file {
|
||||
"Relay webcam preview stopped.".to_string()
|
||||
} else {
|
||||
@ -212,17 +229,41 @@ impl LocalCameraPreview {
|
||||
}
|
||||
}
|
||||
|
||||
fn blank_camera_preview_texture() -> gdk::MemoryTexture {
|
||||
let rgba =
|
||||
vec![12_u8; (CAMERA_PREVIEW_DEFAULT_WIDTH * CAMERA_PREVIEW_DEFAULT_HEIGHT * 4) as usize];
|
||||
let bytes = glib::Bytes::from_owned(rgba);
|
||||
gdk::MemoryTexture::new(
|
||||
CAMERA_PREVIEW_DEFAULT_WIDTH,
|
||||
CAMERA_PREVIEW_DEFAULT_HEIGHT,
|
||||
gdk::MemoryFormat::R8g8b8a8,
|
||||
&bytes,
|
||||
(CAMERA_PREVIEW_DEFAULT_WIDTH * 4) as usize,
|
||||
)
|
||||
fn camera_preview_placeholder_texture() -> gdk::Texture {
|
||||
let path = format!(
|
||||
"{}/assets/placeholders/webcam_disabled.png",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
gdk::Texture::from_filename(path).unwrap_or_else(|_| {
|
||||
let rgba = vec![
|
||||
12_u8;
|
||||
(CAMERA_PREVIEW_DEFAULT_WIDTH * CAMERA_PREVIEW_DEFAULT_HEIGHT * 4) as usize
|
||||
];
|
||||
let bytes = glib::Bytes::from_owned(rgba);
|
||||
glib::object::Cast::upcast(gdk::MemoryTexture::new(
|
||||
CAMERA_PREVIEW_DEFAULT_WIDTH,
|
||||
CAMERA_PREVIEW_DEFAULT_HEIGHT,
|
||||
gdk::MemoryFormat::R8g8b8a8,
|
||||
&bytes,
|
||||
(CAMERA_PREVIEW_DEFAULT_WIDTH * 4) as usize,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn mirror_preview_frame(frame: &mut PreviewFrame) {
|
||||
let width = frame.width.max(0) as usize;
|
||||
if width == 0 {
|
||||
return;
|
||||
}
|
||||
for row in frame.rgba.chunks_exact_mut(frame.stride) {
|
||||
let row = &mut row[..width * 4];
|
||||
for left_pixel in 0..(width / 2) {
|
||||
let right_pixel = width - 1 - left_pixel;
|
||||
for channel in 0..4 {
|
||||
row.swap(left_pixel * 4 + channel, right_pixel * 4 + channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalMicrophoneMonitor {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use super::{
|
||||
MIC_REPLAY_MAX_BYTES, build_wav_bytes, camera_preview_mode, camera_preview_pipeline_desc,
|
||||
microphone_monitor_pipeline_desc, normalize_camera_selection, push_recent_audio,
|
||||
read_camera_preview_tap, read_microphone_level_tap, resolve_camera_device,
|
||||
MIC_REPLAY_MAX_BYTES, PreviewFrame, build_wav_bytes, camera_preview_mode,
|
||||
camera_preview_pipeline_desc, microphone_monitor_pipeline_desc, mirror_preview_frame,
|
||||
normalize_camera_selection, push_recent_audio, read_camera_preview_tap,
|
||||
read_microphone_level_tap, resolve_camera_device,
|
||||
};
|
||||
use crate::launcher::devices::CameraMode;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -114,3 +115,15 @@ fn relay_microphone_level_tap_clamps_values() {
|
||||
assert_eq!(read_microphone_level_tap(&path), None);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_preview_frame_flips_each_row_without_changing_channels() {
|
||||
let mut frame = PreviewFrame {
|
||||
width: 2,
|
||||
height: 1,
|
||||
stride: 8,
|
||||
rgba: vec![1, 2, 3, 4, 9, 10, 11, 12],
|
||||
};
|
||||
mirror_preview_frame(&mut frame);
|
||||
assert_eq!(frame.rgba, vec![9, 10, 11, 12, 1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
@ -25,6 +25,29 @@
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let widgets = widgets.clone();
|
||||
let tests = Rc::clone(&tests);
|
||||
let camera_mirror_button = widgets.camera_mirror_button.clone();
|
||||
camera_mirror_button.connect_toggled(move |button| {
|
||||
tests
|
||||
.borrow_mut()
|
||||
.set_camera_preview_mirrored(button.is_active());
|
||||
button.set_tooltip_text(Some(if button.is_active() {
|
||||
"Launcher preview mirrored."
|
||||
} else {
|
||||
"Mirror launcher preview only."
|
||||
}));
|
||||
if tests.borrow_mut().is_running(DeviceTestKind::Camera) {
|
||||
widgets.status_label.set_text(if button.is_active() {
|
||||
"Launcher webcam preview mirrored. The actual uplink stays untouched."
|
||||
} else {
|
||||
"Launcher webcam preview returned to the real uplink orientation."
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let widgets = widgets.clone();
|
||||
let tests = Rc::clone(&tests);
|
||||
|
||||
@ -84,11 +84,18 @@
|
||||
let state = Rc::clone(&state);
|
||||
let widgets = widgets.clone();
|
||||
let child_proc = Rc::clone(&child_proc);
|
||||
let tests = Rc::clone(&tests);
|
||||
let toggle = widgets.camera_channel_toggle.clone();
|
||||
toggle.connect_toggled(move |toggle| {
|
||||
if let Ok(mut state) = state.try_borrow_mut() {
|
||||
state.set_camera_channel_enabled(toggle.is_active());
|
||||
}
|
||||
if !toggle.is_active() {
|
||||
tests.borrow_mut().stop_camera_preview();
|
||||
widgets
|
||||
.status_label
|
||||
.set_text("Camera stream disabled. Webcam preview stopped.");
|
||||
}
|
||||
if let Ok(state_snapshot) = state.try_borrow().map(|state| state.clone()) {
|
||||
refresh_launcher_ui(
|
||||
&widgets,
|
||||
@ -96,6 +103,7 @@
|
||||
child_proc.borrow().is_some(),
|
||||
);
|
||||
}
|
||||
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
|
||||
});
|
||||
}
|
||||
|
||||
@ -103,11 +111,20 @@
|
||||
let state = Rc::clone(&state);
|
||||
let widgets = widgets.clone();
|
||||
let child_proc = Rc::clone(&child_proc);
|
||||
let tests = Rc::clone(&tests);
|
||||
let toggle = widgets.microphone_channel_toggle.clone();
|
||||
toggle.connect_toggled(move |toggle| {
|
||||
if let Ok(mut state) = state.try_borrow_mut() {
|
||||
state.set_microphone_channel_enabled(toggle.is_active());
|
||||
}
|
||||
if !toggle.is_active() {
|
||||
let mut tests = tests.borrow_mut();
|
||||
tests.stop_microphone_monitor();
|
||||
tests.stop_microphone_replay();
|
||||
widgets
|
||||
.status_label
|
||||
.set_text("Mic stream disabled. Mic monitor and replay stopped.");
|
||||
}
|
||||
if let Ok(state_snapshot) = state.try_borrow().map(|state| state.clone()) {
|
||||
refresh_launcher_ui(
|
||||
&widgets,
|
||||
@ -115,6 +132,7 @@
|
||||
child_proc.borrow().is_some(),
|
||||
);
|
||||
}
|
||||
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
|
||||
});
|
||||
}
|
||||
|
||||
@ -122,11 +140,20 @@
|
||||
let state = Rc::clone(&state);
|
||||
let widgets = widgets.clone();
|
||||
let child_proc = Rc::clone(&child_proc);
|
||||
let tests = Rc::clone(&tests);
|
||||
let toggle = widgets.audio_channel_toggle.clone();
|
||||
toggle.connect_toggled(move |toggle| {
|
||||
if let Ok(mut state) = state.try_borrow_mut() {
|
||||
state.set_audio_channel_enabled(toggle.is_active());
|
||||
}
|
||||
if !toggle.is_active() {
|
||||
let mut tests = tests.borrow_mut();
|
||||
tests.stop_speaker_test();
|
||||
tests.stop_microphone_replay();
|
||||
widgets
|
||||
.status_label
|
||||
.set_text("Speaker stream disabled. Local audio playback stopped.");
|
||||
}
|
||||
if let Ok(state_snapshot) = state.try_borrow().map(|state| state.clone()) {
|
||||
refresh_launcher_ui(
|
||||
&widgets,
|
||||
@ -134,6 +161,7 @@
|
||||
child_proc.borrow().is_some(),
|
||||
);
|
||||
}
|
||||
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,8 @@ pub fn build_launcher_view(
|
||||
preview_panel,
|
||||
camera_preview_frame,
|
||||
camera_preview,
|
||||
camera_mirror_button,
|
||||
camera_mirror_revealer,
|
||||
camera_status,
|
||||
camera_test_button,
|
||||
microphone_test_button,
|
||||
|
||||
@ -141,6 +141,8 @@
|
||||
device_refresh_button: device_refresh_button.clone(),
|
||||
swap_key_button: swap_key_button.clone(),
|
||||
camera_test_button: camera_test_button.clone(),
|
||||
camera_mirror_button: camera_mirror_button.clone(),
|
||||
camera_mirror_revealer: camera_mirror_revealer.clone(),
|
||||
microphone_test_button: microphone_test_button.clone(),
|
||||
microphone_replay_button: microphone_replay_button.clone(),
|
||||
speaker_test_button: speaker_test_button.clone(),
|
||||
@ -173,6 +175,7 @@
|
||||
preview_panel,
|
||||
camera_preview_frame,
|
||||
camera_preview,
|
||||
camera_mirror_button,
|
||||
camera_status,
|
||||
},
|
||||
widgets,
|
||||
|
||||
@ -35,6 +35,8 @@ struct DeviceControlsContext {
|
||||
preview_panel: gtk::Box,
|
||||
camera_preview_frame: gtk::AspectFrame,
|
||||
camera_preview: gtk::Picture,
|
||||
camera_mirror_button: gtk::ToggleButton,
|
||||
camera_mirror_revealer: gtk::Revealer,
|
||||
camera_status: gtk::Label,
|
||||
camera_test_button: gtk::Button,
|
||||
microphone_test_button: gtk::Button,
|
||||
|
||||
@ -219,6 +219,25 @@
|
||||
);
|
||||
camera_preview.set_keep_aspect_ratio(true);
|
||||
camera_preview.add_css_class("camera-preview-frame");
|
||||
let camera_mirror_button = gtk::ToggleButton::new();
|
||||
camera_mirror_button.add_css_class("camera-preview-mirror-toggle");
|
||||
camera_mirror_button.add_css_class("flat");
|
||||
camera_mirror_button.set_focus_on_click(false);
|
||||
camera_mirror_button.set_halign(gtk::Align::End);
|
||||
camera_mirror_button.set_valign(gtk::Align::Start);
|
||||
camera_mirror_button.set_margin_top(10);
|
||||
camera_mirror_button.set_margin_end(10);
|
||||
camera_mirror_button.set_tooltip_text(Some("Mirror launcher preview only."));
|
||||
camera_mirror_button.set_visible(false);
|
||||
let camera_mirror_icon = gtk::Image::from_icon_name("object-flip-horizontal-symbolic");
|
||||
camera_mirror_button.set_child(Some(&camera_mirror_icon));
|
||||
let camera_mirror_revealer = gtk::Revealer::new();
|
||||
camera_mirror_revealer.set_transition_type(gtk::RevealerTransitionType::Crossfade);
|
||||
camera_mirror_revealer.set_transition_duration(120);
|
||||
camera_mirror_revealer.set_halign(gtk::Align::End);
|
||||
camera_mirror_revealer.set_valign(gtk::Align::Start);
|
||||
camera_mirror_revealer.set_reveal_child(false);
|
||||
camera_mirror_revealer.set_child(Some(&camera_mirror_button));
|
||||
let camera_status = gtk::Label::new(Some("Select a webcam and click Start Preview."));
|
||||
camera_status.add_css_class("dim-label");
|
||||
camera_status.set_wrap(false);
|
||||
@ -244,7 +263,34 @@
|
||||
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
|
||||
);
|
||||
camera_preview_frame.set_child(Some(&camera_preview));
|
||||
camera_preview_shell.append(&camera_preview_frame);
|
||||
let camera_preview_overlay = gtk::Overlay::new();
|
||||
camera_preview_overlay.set_hexpand(true);
|
||||
camera_preview_overlay.set_vexpand(true);
|
||||
camera_preview_overlay.set_halign(gtk::Align::Fill);
|
||||
camera_preview_overlay.set_valign(gtk::Align::Fill);
|
||||
camera_preview_overlay.set_size_request(
|
||||
CAMERA_PREVIEW_VIEWPORT_WIDTH,
|
||||
CAMERA_PREVIEW_VIEWPORT_HEIGHT,
|
||||
);
|
||||
camera_preview_overlay.set_child(Some(&camera_preview_frame));
|
||||
camera_preview_overlay.add_overlay(&camera_mirror_revealer);
|
||||
let hover_revealer = camera_mirror_revealer.clone();
|
||||
let hover_button = camera_mirror_button.clone();
|
||||
let hover_controller = gtk::EventControllerMotion::new();
|
||||
hover_controller.connect_enter(move |_, _, _| {
|
||||
if hover_button.is_visible() {
|
||||
hover_revealer.set_reveal_child(true);
|
||||
}
|
||||
});
|
||||
let leave_revealer = camera_mirror_revealer.clone();
|
||||
let leave_button = camera_mirror_button.clone();
|
||||
hover_controller.connect_leave(move |_| {
|
||||
if leave_button.is_visible() {
|
||||
leave_revealer.set_reveal_child(false);
|
||||
}
|
||||
});
|
||||
camera_preview_overlay.add_controller(hover_controller);
|
||||
camera_preview_shell.append(&camera_preview_overlay);
|
||||
let webcam_group = build_subgroup("Webcam Preview");
|
||||
webcam_group.set_hexpand(true);
|
||||
webcam_group.set_vexpand(true);
|
||||
@ -299,6 +345,8 @@
|
||||
preview_panel,
|
||||
camera_preview_frame,
|
||||
camera_preview,
|
||||
camera_mirror_button,
|
||||
camera_mirror_revealer,
|
||||
camera_status,
|
||||
camera_test_button,
|
||||
microphone_test_button,
|
||||
|
||||
@ -100,6 +100,23 @@ pub fn install_css(window: >k::ApplicationWindow) {
|
||||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||||
border-radius: 14px;
|
||||
}
|
||||
button.camera-preview-mirror-toggle {
|
||||
min-width: 36px;
|
||||
min-height: 36px;
|
||||
padding: 0;
|
||||
border-radius: 999px;
|
||||
background: rgba(16, 19, 25, 0.28);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
color: rgba(238, 242, 247, 0.92);
|
||||
}
|
||||
button.camera-preview-mirror-toggle:checked {
|
||||
background: rgba(76, 154, 255, 0.26);
|
||||
border-color: rgba(76, 154, 255, 0.58);
|
||||
color: #eef6ff;
|
||||
}
|
||||
button.camera-preview-mirror-toggle image {
|
||||
-gtk-icon-size: 18px;
|
||||
}
|
||||
label.status-line {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@ -145,6 +145,8 @@ pub struct LauncherWidgets {
|
||||
pub device_refresh_button: gtk::Button,
|
||||
pub swap_key_button: gtk::Button,
|
||||
pub camera_test_button: gtk::Button,
|
||||
pub camera_mirror_button: gtk::ToggleButton,
|
||||
pub camera_mirror_revealer: gtk::Revealer,
|
||||
pub microphone_test_button: gtk::Button,
|
||||
pub microphone_replay_button: gtk::Button,
|
||||
pub speaker_test_button: gtk::Button,
|
||||
@ -164,6 +166,7 @@ pub struct DeviceStageWidgets {
|
||||
pub preview_panel: gtk::Box,
|
||||
pub camera_preview_frame: gtk::AspectFrame,
|
||||
pub camera_preview: gtk::Picture,
|
||||
pub camera_mirror_button: gtk::ToggleButton,
|
||||
pub camera_status: gtk::Label,
|
||||
}
|
||||
|
||||
|
||||
@ -211,6 +211,15 @@ pub fn refresh_test_buttons(widgets: &LauncherWidgets, tests: &mut DeviceTestCon
|
||||
} else {
|
||||
"Start Preview"
|
||||
});
|
||||
let camera_mirrored = tests.camera_preview_mirrored();
|
||||
if widgets.camera_mirror_button.is_active() != camera_mirrored {
|
||||
widgets.camera_mirror_button.set_active(camera_mirrored);
|
||||
}
|
||||
widgets.camera_mirror_button.set_visible(camera_running);
|
||||
widgets.camera_mirror_button.set_sensitive(camera_running);
|
||||
if !camera_running {
|
||||
widgets.camera_mirror_revealer.set_reveal_child(false);
|
||||
}
|
||||
widgets
|
||||
.microphone_test_button
|
||||
.set_label(if microphone_running {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user