import { MdOutlineContentPasteGo, MdVolumeOff, MdVolumeUp, MdGraphicEq } from "react-icons/md"; import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu"; import { FaKeyboard } from "react-icons/fa6"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; import { Fragment, useCallback, useRef } from "react"; import { CommandLineIcon } from "@heroicons/react/20/solid"; import { Button } from "@components/Button"; import { useHidStore, useMountMediaStore, useSettingsStore, useUiStore, } from "@/hooks/stores"; import Container from "@components/Container"; import { cx } from "@/cva.config"; import PasteModal from "@/components/popovers/PasteModal"; import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index"; import MountPopopover from "@/components/popovers/MountPopover"; import ExtensionPopover from "@/components/popovers/ExtensionPopover"; import AudioControlPopover from "@/components/popovers/AudioControlPopover"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import { useAudioEvents } from "@/hooks/useAudioEvents"; import { useUsbDeviceConfig } from "@/hooks/useUsbDeviceConfig"; // Type for microphone error interface MicrophoneError { type: 'permission' | 'device' | 'network' | 'unknown'; message: string; } // Type for microphone hook return value interface MicrophoneHookReturn { isMicrophoneActive: boolean; isMicrophoneMuted: boolean; microphoneStream: MediaStream | null; startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: MicrophoneError }>; stopMicrophone: () => Promise<{ success: boolean; error?: MicrophoneError }>; toggleMicrophoneMute: () => Promise<{ success: boolean; error?: MicrophoneError }>; syncMicrophoneState: () => Promise; // Loading states isStarting: boolean; isStopping: boolean; isToggling: boolean; } export default function Actionbar({ requestFullscreen, microphone, }: { requestFullscreen: () => Promise; microphone: MicrophoneHookReturn; }) { const { navigateTo } = useDeviceUiNavigation(); const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled); const setVirtualKeyboard = useHidStore(state => state.setVirtualKeyboardEnabled); const toggleSidebarView = useUiStore(state => state.toggleSidebarView); const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const terminalType = useUiStore(state => state.terminalType); const setTerminalType = useUiStore(state => state.setTerminalType); const remoteVirtualMediaState = useMountMediaStore( state => state.remoteVirtualMediaState, ); const developerMode = useSettingsStore(state => state.developerMode); // This is the only way to get a reliable state change for the popover // at time of writing this there is no mount, or unmount event for the popover const isOpen = useRef(false); const checkIfStateChanged = useCallback( (open: boolean) => { if (open !== isOpen.current) { isOpen.current = open; if (!open) { setTimeout(() => { setDisableFocusTrap(false); console.log("Popover is closing. Returning focus trap to video"); }, 0); } } }, [setDisableFocusTrap], ); // Use WebSocket-based audio events for real-time updates const { audioMuted } = useAudioEvents(); // Use WebSocket data exclusively - no polling fallback const isMuted = audioMuted ?? false; // Default to false if WebSocket data not available yet // Get USB device configuration to check if audio is enabled const { usbDeviceConfig } = useUsbDeviceConfig(); const isAudioEnabledInUsb = usbDeviceConfig?.audio ?? true; // Default to true while loading return (
e.stopPropagation()} onKeyDown={e => e.stopPropagation()} className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2 py-1.5" >
{developerMode && (
{({ open }: { open: boolean }) => { checkIfStateChanged(open); return (
); }}
); }