Skip to content

Commit 1aea5a5

Browse files
committed
Normalize z-index classes in Dialog/Dropdown
1 parent f09765f commit 1aea5a5

3 files changed

Lines changed: 194 additions & 3 deletions

File tree

apps/desktop/src-tauri/src/recording.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,197 @@ pub enum RecordingAction {
547547
UpgradeRequired,
548548
}
549549

550+
const MICROPHONE_INPUT_PROBE_TIMEOUT: Duration = Duration::from_millis(1500);
551+
const CAMERA_INPUT_PROBE_TIMEOUT: Duration = Duration::from_millis(1500);
552+
553+
fn camera_id_label(id: &camera::DeviceOrModelID) -> String {
554+
match id {
555+
camera::DeviceOrModelID::DeviceID(device_id) => device_id.clone(),
556+
camera::DeviceOrModelID::ModelID(model_id) => format!("{model_id:?}"),
557+
}
558+
}
559+
560+
fn camera_lock_matches_id(
561+
lock: &CameraFeedLock,
562+
selected_id: &camera::DeviceOrModelID,
563+
) -> bool {
564+
let camera_info = lock.camera_info();
565+
match selected_id {
566+
camera::DeviceOrModelID::DeviceID(device_id) => camera_info.device_id() == device_id,
567+
camera::DeviceOrModelID::ModelID(model_id) => camera_info.model_id() == Some(model_id),
568+
}
569+
}
570+
571+
async fn initialize_selected_camera(
572+
camera_feed: &kameo::actor::ActorRef<camera::CameraFeed>,
573+
id: &camera::DeviceOrModelID,
574+
settings: Option<camera::CameraDeviceSettings>,
575+
) -> anyhow::Result<()> {
576+
let label = camera_id_label(id);
577+
let ready = camera_feed
578+
.ask(camera::SetInput {
579+
id: id.clone(),
580+
settings,
581+
})
582+
.await
583+
.map_err(|err| anyhow!("Failed to initialize selected camera '{label}': {err}"))?;
584+
585+
ready.await.map_err(|err| match err {
586+
camera::SetInputError::DeviceNotFound => {
587+
anyhow!("Selected camera '{label}' is no longer available")
588+
}
589+
err => anyhow!("Failed to initialize selected camera '{label}': {err}"),
590+
})
591+
}
592+
593+
async fn lock_initialized_camera(
594+
camera_feed: &kameo::actor::ActorRef<camera::CameraFeed>,
595+
id: &camera::DeviceOrModelID,
596+
) -> anyhow::Result<CameraFeedLock> {
597+
let label = camera_id_label(id);
598+
match camera_feed.ask(camera::Lock).await {
599+
Ok(lock) => Ok(lock),
600+
Err(kameo::error::SendError::HandlerError(
601+
camera::LockFeedError::NoInput,
602+
)) => Err(anyhow!(
603+
"Selected camera '{label}' did not become ready after initialization"
604+
)),
605+
Err(err) => Err(anyhow!("Failed to lock selected camera '{label}': {err}")),
606+
}
607+
}
608+
609+
async fn validate_camera_receiving(
610+
lock: &CameraFeedLock,
611+
id: &camera::DeviceOrModelID,
612+
) -> anyhow::Result<()> {
613+
let label = camera_id_label(id);
614+
let (tx, rx) = flume::bounded(1);
615+
let remove_sender = tx.clone();
616+
617+
lock.ask(camera::AddSender(tx))
618+
.await
619+
.map_err(|err| anyhow!("Failed to probe selected camera '{label}': {err}"))?;
620+
621+
let result = tokio::time::timeout(CAMERA_INPUT_PROBE_TIMEOUT, rx.recv_async()).await;
622+
let _ = lock.ask(camera::RemoveSender(remove_sender)).await;
623+
624+
match result {
625+
Ok(Ok(_)) => Ok(()),
626+
Ok(Err(_)) => Err(anyhow!(
627+
"Selected camera '{label}' stopped before sending a frame"
628+
)),
629+
Err(_) => Err(anyhow!(
630+
"Selected camera '{label}' is not sending video frames"
631+
)),
632+
}
633+
}
634+
635+
async fn lock_selected_camera(
636+
camera_feed: &kameo::actor::ActorRef<camera::CameraFeed>,
637+
selected_id: Option<camera::DeviceOrModelID>,
638+
selected_settings: Option<camera::CameraDeviceSettings>,
639+
capture_target: &ScreenCaptureTarget,
640+
) -> anyhow::Result<Option<Arc<CameraFeedLock>>> {
641+
let Some(id) = selected_id else {
642+
if matches!(capture_target, ScreenCaptureTarget::CameraOnly) {
643+
return Err(anyhow!(
644+
"Camera-only recording requires a selected camera. Please select a camera before starting."
645+
));
646+
}
647+
648+
return Ok(None);
649+
};
650+
651+
let existing_lock = match camera_feed.ask(camera::Lock).await {
652+
Ok(lock) if camera_lock_matches_id(&lock, &id) => Some(lock),
653+
Ok(lock) => {
654+
drop(lock);
655+
tokio::time::sleep(Duration::from_millis(50)).await;
656+
None
657+
}
658+
Err(kameo::error::SendError::HandlerError(
659+
camera::LockFeedError::NoInput,
660+
)) => None,
661+
Err(err) => {
662+
return Err(anyhow!(
663+
"Failed to lock selected camera '{}': {err}",
664+
camera_id_label(&id)
665+
));
666+
}
667+
};
668+
669+
let lock = if let Some(lock) = existing_lock {
670+
lock
671+
} else {
672+
initialize_selected_camera(camera_feed, &id, selected_settings).await?;
673+
lock_initialized_camera(camera_feed, &id).await?
674+
};
675+
676+
validate_camera_receiving(&lock, &id).await?;
677+
Ok(Some(Arc::new(lock)))
678+
}
679+
680+
async fn initialize_selected_microphone(
681+
mic_feed: &kameo::actor::ActorRef<microphone::MicrophoneFeed>,
682+
label: &str,
683+
settings: Option<microphone::MicrophoneDeviceSettings>,
684+
) -> anyhow::Result<()> {
685+
let ready = mic_feed
686+
.ask(microphone::SetInput {
687+
label: label.to_string(),
688+
settings,
689+
})
690+
.await
691+
.map_err(|err| anyhow!("Failed to initialize selected microphone '{label}': {err}"))?;
692+
693+
ready.await.map_err(|err| match err {
694+
microphone::SetInputError::DeviceNotFound => {
695+
anyhow!("Selected microphone '{label}' is no longer available")
696+
}
697+
err => anyhow!("Failed to initialize selected microphone '{label}': {err}"),
698+
})
699+
}
700+
701+
async fn lock_initialized_microphone(
702+
mic_feed: &kameo::actor::ActorRef<microphone::MicrophoneFeed>,
703+
label: &str,
704+
) -> anyhow::Result<microphone::MicrophoneFeedLock> {
705+
match mic_feed.ask(microphone::Lock).await {
706+
Ok(lock) => Ok(lock),
707+
Err(kameo::error::SendError::HandlerError(
708+
microphone::LockFeedError::NoInput,
709+
)) => Err(anyhow!(
710+
"Selected microphone '{label}' did not become ready after initialization"
711+
)),
712+
Err(err) => Err(anyhow!("Failed to lock selected microphone '{label}': {err}")),
713+
}
714+
}
715+
716+
async fn validate_microphone_receiving(
717+
lock: &microphone::MicrophoneFeedLock,
718+
label: &str,
719+
) -> anyhow::Result<()> {
720+
let (tx, rx) = flume::bounded(1);
721+
let remove_sender = tx.clone();
722+
723+
lock.ask(microphone::AddSender(tx))
724+
.await
725+
.map_err(|err| anyhow!("Failed to probe selected microphone '{label}': {err}"))?;
726+
727+
let result = tokio::time::timeout(MICROPHONE_INPUT_PROBE_TIMEOUT, rx.recv_async()).await;
728+
let _ = lock.ask(microphone::RemoveSender(remove_sender)).await;
729+
730+
match result {
731+
Ok(Ok(_)) => Ok(()),
732+
Ok(Err(_)) => Err(anyhow!(
733+
"Selected microphone '{label}' stopped before sending audio"
734+
)),
735+
Err(_) => Err(anyhow!(
736+
"Selected microphone '{label}' is not sending audio"
737+
)),
738+
}
739+
}
740+
550741
pub fn format_project_name<'a>(
551742
template: Option<&str>,
552743
target_name: &'a str,

packages/ui/src/components/Dialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const DialogOverlay = React.forwardRef<
1515
>(({ className, ...props }, ref) => (
1616
<DialogPrimitive.Overlay
1717
ref={ref}
18-
className={classNames("fixed inset-0 z-500 bg-black/60", className)}
18+
className={classNames("fixed inset-0 z-[500] bg-black/60", className)}
1919
{...props}
2020
/>
2121
));
@@ -27,7 +27,7 @@ const DialogContent = React.forwardRef<
2727
>(({ className, children, ...props }, ref) => (
2828
<DialogPortal>
2929
<DialogOverlay className="animate-fadeIn" />
30-
<div className="flex fixed inset-0 z-501 justify-center items-center">
30+
<div className="flex fixed inset-0 z-[501] justify-center items-center">
3131
<DialogPrimitive.Content
3232
ref={ref}
3333
className={classNames(

packages/ui/src/components/Dropdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const DropdownMenuContent = React.forwardRef<
6868
ref={ref}
6969
sideOffset={sideOffset}
7070
className={classNames(
71-
"z-1000000000 bg-gray-1 min-w-32 overflow-hidden rounded-xl border border-gray-3 p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
71+
"z-[1000] bg-gray-1 min-w-32 overflow-hidden rounded-xl border border-gray-3 p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
7272
className,
7373
)}
7474
{...props}

0 commit comments

Comments
 (0)