From 4f974fd480f67e7acafd17a57816624136d7eb35 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 5 Feb 2026 12:51:02 -0800 Subject: [PATCH 1/5] Add check --- app/components-react/windows/SourceProperties.tsx | 8 ++++++++ app/services/sources/sources.ts | 7 +++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/components-react/windows/SourceProperties.tsx b/app/components-react/windows/SourceProperties.tsx index c80dff541d3f..d585f2731c67 100644 --- a/app/components-react/windows/SourceProperties.tsx +++ b/app/components-react/windows/SourceProperties.tsx @@ -8,6 +8,9 @@ import { assertIsDefined } from '../../util/properties-type-guards'; import { useSubscription } from '../hooks/useSubscription'; import { useVuex } from '../hooks'; +const SUPPORTED_WEBCAMS: string[] = []; +const SUPPORTED_MICS: string[] = []; + export default function SourceProperties() { const { WindowsService, @@ -58,6 +61,11 @@ export default function SourceProperties() { extraProps['url'] = { debounce: 1000 }; } + const isSupportedWebcam = + source?.type === 'dshow_input' && SUPPORTED_WEBCAMS.includes(source?.sourceId); + const isSupportedMic = + source?.type === 'wasapi_input_capture' && SUPPORTED_MICS.includes(source?.sourceId); + return ( { // 'monitor_capture', // 'window_capture', 'game_capture', - // 'dshow_input', 'dshow_input', - // 'wasapi_input_capture', + 'wasapi_input_capture', // 'wasapi_output_capture', // 'decklink-input', // 'scene', @@ -771,9 +770,9 @@ export class SourcesService extends StatefulService { // 'liv_capture', // 'ovrstream_dc_source', // 'vlc_source', - // 'coreaudio_input_capture', + 'coreaudio_input_capture', // 'coreaudio_output_capture', - // 'macos_avcapture', + 'macos_avcapture', // 'display_capture', // 'audio_line', // 'syphon-input', From f7615979a9cf2de4f2ef47661227b9629a783c3d Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 5 Feb 2026 13:50:39 -0800 Subject: [PATCH 2/5] Add deep link --- .../windows/SourceProperties.tsx | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/app/components-react/windows/SourceProperties.tsx b/app/components-react/windows/SourceProperties.tsx index d585f2731c67..f72ce11adbd2 100644 --- a/app/components-react/windows/SourceProperties.tsx +++ b/app/components-react/windows/SourceProperties.tsx @@ -1,4 +1,5 @@ import React, { useMemo, useState } from 'react'; +import { shell } from '@electron/remote'; import { Services } from '../service-provider'; import { IObsFormProps, ObsForm } from '../obs/ObsForm'; import { TObsFormData } from '../../components/obs/inputs/ObsInput'; @@ -6,10 +7,35 @@ import { ModalLayout } from '../shared/ModalLayout'; import Display from '../shared/Display'; import { assertIsDefined } from '../../util/properties-type-guards'; import { useSubscription } from '../hooks/useSubscription'; -import { useVuex } from '../hooks'; +import { $t } from 'services/i18n'; -const SUPPORTED_WEBCAMS: string[] = []; -const SUPPORTED_MICS: string[] = []; +const SUPPORTED_WEBCAMS: Set = new Set([ + '0x046d-0x0943', + '0x046d-0x0946', + '0x046d-0x0919', + '0x046d-0x0944', + '0x046d-0x091d', + '0x046d-0x085e', + '0x046d-0x086b', + '0x046d-0x082d', + '0x046d-0x0892', + '0x046d-0x08e5', + '0x046d-0x085c', + '0x046d-0x0883', + '0x046d-0x0894', + '0x046d-0x091b', + '0x046d-0x091c', +]); +const SUPPORTED_MICS: Set = new Set(['0x046d-0x0afc']); + +function parseId(id?: string) { + if (!id) return ''; + //Id strings have a lot of elements but we want to pull the vid and pid + const match = id.match(/vid_([\w\d]+)&pid_([\w\d]+)/); + if (!match) return ''; + const [_, vid, pid] = [...match]; + return `0x${vid}-0x${pid}`; +} export default function SourceProperties() { const { @@ -55,17 +81,26 @@ export default function SourceProperties() { ]); } + const videoDevice = source?.type === 'dshow_input' && source?.getSettings().video_device_id; + const audioDevice = + source?.type === 'wasapi_input_capture' && source?.getSettings().audio_device_id; + const isSupportedWebcam = SUPPORTED_WEBCAMS.has(parseId(videoDevice)); + const isSupportedMic = SUPPORTED_MICS.has(parseId(audioDevice)); + + function configureInGHub() { + if (isSupportedWebcam) { + shell.openExternal(`lghubapp://devices/${parseId(videoDevice)}/default`); + } else if (isSupportedMic) { + shell.openExternal(`lghubapp://devices/${parseId(audioDevice)}/default`); + } + } + // make the URL field debounced for the browser_source const extraProps: IObsFormProps['extraProps'] = {}; if (source && source.type === 'browser_source') { extraProps['url'] = { debounce: 1000 }; } - const isSupportedWebcam = - source?.type === 'dshow_input' && SUPPORTED_WEBCAMS.includes(source?.sourceId); - const isSupportedMic = - source?.type === 'wasapi_input_capture' && SUPPORTED_MICS.includes(source?.sourceId); - return ( + {(isSupportedWebcam || isSupportedMic) && ( + + {$t('Configure on G HUB')} + + )} ); } From 1dae1f754c3baf26e717c8b812370c24bf489713 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 5 Feb 2026 13:52:18 -0800 Subject: [PATCH 3/5] Add translation --- app/i18n/en-US/sources.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/i18n/en-US/sources.json b/app/i18n/en-US/sources.json index 12dfa64050a5..8d3394488452 100644 --- a/app/i18n/en-US/sources.json +++ b/app/i18n/en-US/sources.json @@ -441,5 +441,6 @@ "Health changes": "Health changes", "Search...": "Search...", "Virtual Cam": "Virtual Cam", - "Virtual Cam Error": "Virtual Cam Error" + "Virtual Cam Error": "Virtual Cam Error", + "Configure on G HUB": "Configure on G HUB" } From adfc1fd4e14bd08fa0b753c4ce7e4218d1f69a99 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 5 Feb 2026 13:57:45 -0800 Subject: [PATCH 4/5] Add mac source types --- app/components-react/windows/SourceProperties.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/components-react/windows/SourceProperties.tsx b/app/components-react/windows/SourceProperties.tsx index f72ce11adbd2..7be3cdbec619 100644 --- a/app/components-react/windows/SourceProperties.tsx +++ b/app/components-react/windows/SourceProperties.tsx @@ -81,9 +81,14 @@ export default function SourceProperties() { ]); } - const videoDevice = source?.type === 'dshow_input' && source?.getSettings().video_device_id; + const videoDevice = + source && + ['dshow_input', 'macos_avcapture'].includes(source.type) && + source.getSettings().video_device_id; const audioDevice = - source?.type === 'wasapi_input_capture' && source?.getSettings().audio_device_id; + source && + ['wasapi_input_capture', 'coreaudio_input_capture'].includes(source.type) && + source.getSettings().audio_device_id; const isSupportedWebcam = SUPPORTED_WEBCAMS.has(parseId(videoDevice)); const isSupportedMic = SUPPORTED_MICS.has(parseId(audioDevice)); From 334dae89e5b9ff9578a7cd3de6ad821cb13448d2 Mon Sep 17 00:00:00 2001 From: Sean Beyer Date: Thu, 5 Feb 2026 14:06:14 -0800 Subject: [PATCH 5/5] Move audio to correct window --- .../windows/SourceProperties.tsx | 17 ++--- .../windows/advanced-audio/SourceSettings.tsx | 71 ++++++++++++------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/app/components-react/windows/SourceProperties.tsx b/app/components-react/windows/SourceProperties.tsx index 7be3cdbec619..74c5eba223b1 100644 --- a/app/components-react/windows/SourceProperties.tsx +++ b/app/components-react/windows/SourceProperties.tsx @@ -26,9 +26,9 @@ const SUPPORTED_WEBCAMS: Set = new Set([ '0x046d-0x091b', '0x046d-0x091c', ]); -const SUPPORTED_MICS: Set = new Set(['0x046d-0x0afc']); -function parseId(id?: string) { +// Returns the vid and pid from a logitech device id +export function parseId(id?: string) { if (!id) return ''; //Id strings have a lot of elements but we want to pull the vid and pid const match = id.match(/vid_([\w\d]+)&pid_([\w\d]+)/); @@ -85,19 +85,10 @@ export default function SourceProperties() { source && ['dshow_input', 'macos_avcapture'].includes(source.type) && source.getSettings().video_device_id; - const audioDevice = - source && - ['wasapi_input_capture', 'coreaudio_input_capture'].includes(source.type) && - source.getSettings().audio_device_id; const isSupportedWebcam = SUPPORTED_WEBCAMS.has(parseId(videoDevice)); - const isSupportedMic = SUPPORTED_MICS.has(parseId(audioDevice)); function configureInGHub() { - if (isSupportedWebcam) { - shell.openExternal(`lghubapp://devices/${parseId(videoDevice)}/default`); - } else if (isSupportedMic) { - shell.openExternal(`lghubapp://devices/${parseId(audioDevice)}/default`); - } + shell.openExternal(`lghubapp://devices/${parseId(videoDevice)}/default`); } // make the URL field debounced for the browser_source @@ -117,7 +108,7 @@ export default function SourceProperties() { extraProps={extraProps} layout="horizontal" /> - {(isSupportedWebcam || isSupportedMic) && ( + {isSupportedWebcam && ( {$t('Configure on G HUB')} diff --git a/app/components-react/windows/advanced-audio/SourceSettings.tsx b/app/components-react/windows/advanced-audio/SourceSettings.tsx index ce5dbef98ce4..c522b3f82f47 100644 --- a/app/components-react/windows/advanced-audio/SourceSettings.tsx +++ b/app/components-react/windows/advanced-audio/SourceSettings.tsx @@ -1,4 +1,5 @@ import React, { useState, useRef, useMemo, useEffect } from 'react'; +import { shell } from '@electron/remote'; import { Button, Collapse, Tooltip } from 'antd'; import { SliderInput, @@ -17,6 +18,9 @@ import { Source } from 'services/sources'; import { $t } from 'services/i18n'; import Utils from 'services/utils'; import styles from './AdvancedAudio.m.less'; +import { parseId } from 'components-react/windows/SourceProperties'; + +const SUPPORTED_MICS: Set = new Set(['0x046d-0x0afc']); const { Panel } = Collapse; @@ -247,13 +251,17 @@ function PanelForm(p: { source: AudioSource }) { max={5000} uncontrolled={false} /> - {!isProcessCapture && handleSettingsChange('forceMono', value)} - tooltip={$t('Route audio to the central channel instead of left or right stereo channels')} - />} + {!isProcessCapture && ( + handleSettingsChange('forceMono', value)} + tooltip={$t( + 'Route audio to the central channel instead of left or right stereo channels', + )} + /> + )} prop.name === 'priority'); - windowMatchPriorityOptions = (priorityProperty as IObsListInput | undefined)?.options.map(option => ({ + windowMatchPriorityOptions = (priorityProperty as + | IObsListInput + | undefined)?.options.map(option => ({ label: option.description, value: option.value, })); @@ -312,9 +323,14 @@ function DeviceInputs(p: { source: Source }) { const inputLabel = isOutputCapture ? 'Window' : 'Device'; const foundDevice: boolean = deviceOptions.some(option => option.value === settingId); + const isSupportedMic = SUPPORTED_MICS.has(parseId(settingId)); + + function configureInGHub() { + shell.openExternal(`lghubapp://devices/${parseId(settingId)}/default`); + } + // Ensure the input is still valid. If not, reset to Default device which should be at index 0. - const inputId = - foundDevice || deviceOptions.length === 0 ? settingId : deviceOptions[0].value; + const inputId = foundDevice || deviceOptions.length === 0 ? settingId : deviceOptions[0].value; if (!foundDevice && deviceOptions.length > 0) { handleInput(inputField, deviceOptions[0].value); } @@ -329,21 +345,26 @@ function DeviceInputs(p: { source: Source }) { onChange={value => handleInput(inputField, value)} /> } - { - windowMatchPriorityOptions && ( - handleInput('priority', value)} - /> - ) - } - {!isOutputCapture && handleInput('use_device_timing', value)} - />} + {windowMatchPriorityOptions && ( + handleInput('priority', value)} + /> + )} + {!isOutputCapture && ( + handleInput('use_device_timing', value)} + /> + )} + {isSupportedMic && ( + + {$t('Configure on G HUB')} + + )} ); }