diff --git a/app/components-react/windows/SourceProperties.tsx b/app/components-react/windows/SourceProperties.tsx index c80dff541d3f..74c5eba223b1 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,7 +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: 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', +]); + +// 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]+)/); + if (!match) return ''; + const [_, vid, pid] = [...match]; + return `0x${vid}-0x${pid}`; +} export default function SourceProperties() { const { @@ -52,6 +81,16 @@ export default function SourceProperties() { ]); } + const videoDevice = + source && + ['dshow_input', 'macos_avcapture'].includes(source.type) && + source.getSettings().video_device_id; + const isSupportedWebcam = SUPPORTED_WEBCAMS.has(parseId(videoDevice)); + + function configureInGHub() { + shell.openExternal(`lghubapp://devices/${parseId(videoDevice)}/default`); + } + // make the URL field debounced for the browser_source const extraProps: IObsFormProps['extraProps'] = {}; if (source && source.type === 'browser_source') { @@ -69,6 +108,11 @@ export default function SourceProperties() { extraProps={extraProps} layout="horizontal" /> + {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')} + + )} ); } 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" } diff --git a/app/services/sources/sources.ts b/app/services/sources/sources.ts index a59cf88feea1..989a5d684fb2 100644 --- a/app/services/sources/sources.ts +++ b/app/services/sources/sources.ts @@ -759,9 +759,8 @@ export class SourcesService extends StatefulService { // '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',