diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26da48b7e..80d551535 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,7 @@ env: YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }} YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }} YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }} + GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }} concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' @@ -106,10 +107,19 @@ jobs: - name: 'Check for GitHub Labels' id: github-check - if: github.event_name == 'pull_request' run: | - if test -n "$(curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')"; then - echo "generator=Xcode" >> $GITHUB_OUTPUT + if [[ "${GITHUB_EVENT_NAME}" == 'pull_request' ]]; then + if test -n "$(curl -H "Authorization: Bearer ${{ github.token }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')"; then + echo "generator=Xcode" >> $GITHUB_OUTPUT + else + echo "generator=Ninja" >> $GITHUB_OUTPUT + fi + elif [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then + if [[ ${GITHUB_REF_NAME} =~ [0-9]+.[0-9]+.[0-9]+(-(rc|beta).+)? ]]; then + echo "generator=Xcode" >> $GITHUB_OUTPUT + else + echo "generator=Ninja" >> $GITHUB_OUTPUT + fi else echo "generator=Ninja" >> $GITHUB_OUTPUT fi @@ -119,7 +129,9 @@ jobs: uses: actions/cache@v3 with: path: ${{ github.workspace }}/.ccache - key: ${{ runner.os }}-${{ github.ref_name }}-${{ steps.github-check.outputs.generator }}-ccache-${{ matrix.arch }}-${{ needs.config.outputs.cache_date }} + key: ${{ runner.os }}-ccache-${{ steps.github-check.outputs.generator }}-${{ matrix.arch }}-${{ github.event_name }}-${{ github.head_ref }} + restore-keys: | + ${{ runner.os }}-ccache-${{ steps.github-check.outputs.generator }}-${{ matrix.arch }}-push- - name: 'Setup build environment' id: setup @@ -215,7 +227,9 @@ jobs: CACHE_NAME: 'ccache-cache' with: path: ${{ github.workspace }}/.ccache - key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ matrix.ubuntu }}-${{ needs.config.outputs.cache_date }} + key: ${{ runner.os }}-ccache-${{ matrix.ubuntu }}-${{ github.event_name }}-${{ github.head_ref }} + restore-keys: | + ${{ runner.os }}-ccache-${{ matrix.ubuntu }}-push- - name: 'Restore Chromium Embedded Framework from cache' id: cef-cache diff --git a/CI/windows/02_build_obs.ps1 b/CI/windows/02_build_obs.ps1 index b075e07ec..0114d2763 100644 --- a/CI/windows/02_build_obs.ps1 +++ b/CI/windows/02_build_obs.ps1 @@ -83,6 +83,7 @@ function Configure-OBS { "-DYOUTUBE_CLIENTID_HASH=${Env:YOUTUBE_CLIENTID_HASH}", "-DYOUTUBE_SECRET=${Env:YOUTUBE_SECRET}", "-DYOUTUBE_SECRET_HASH=${Env:YOUTUBE_SECRET_HASH}", + "-DGPU_PRIORITY_VAL=${Env:GPU_PRIORITY_VAL}", "-DCOPIED_DEPENDENCIES=OFF", "-DCOPY_DEPENDENCIES=ON", "-DBUILD_FOR_DISTRIBUTION=$(if (Test-Path Env:BUILD_FOR_DISTRIBUTION) { "ON" } else { "OFF" })", @@ -109,6 +110,7 @@ function Configure-OBS { "-DYOUTUBE_CLIENTID_HASH=`"${Env:YOUTUBE_CLIENTID_HASH}`"", "-DYOUTUBE_SECRET=`"${Env:YOUTUBE_SECRET}`"", "-DYOUTUBE_SECRET_HASH=`"${Env:YOUTUBE_SECRET_HASH}`"", + "-DGPU_PRIORITY_VAL=`"${Env:GPU_PRIORITY_VAL}`"", "-DCOPIED_DEPENDENCIES=OFF", "-DCOPY_DEPENDENCIES=ON", "-DBUILD_FOR_DISTRIBUTION=`"$(if (Test-Path Env:BUILD_FOR_DISTRIBUTION) { "ON" } else { "OFF" })`"", diff --git a/UI/auth-oauth.cpp b/UI/auth-oauth.cpp index c19be67bd..d18923d9d 100644 --- a/UI/auth-oauth.cpp +++ b/UI/auth-oauth.cpp @@ -71,12 +71,7 @@ OAuthLogin::OAuthLogin(QWidget *parent, const std::string &url, bool token) #endif } -OAuthLogin::~OAuthLogin() -{ -#ifdef BROWSER_AVAILABLE - delete cefWidget; -#endif -} +OAuthLogin::~OAuthLogin() {} int OAuthLogin::exec() { @@ -88,6 +83,22 @@ int OAuthLogin::exec() return QDialog::Rejected; } +void OAuthLogin::reject() +{ +#ifdef BROWSER_AVAILABLE + delete cefWidget; +#endif + QDialog::reject(); +} + +void OAuthLogin::accept() +{ +#ifdef BROWSER_AVAILABLE + delete cefWidget; +#endif + QDialog::accept(); +} + void OAuthLogin::urlChanged(const QString &url) { std::string uri = get_token ? "access_token=" : "code="; diff --git a/UI/auth-oauth.hpp b/UI/auth-oauth.hpp index b558692b2..364dccdc4 100644 --- a/UI/auth-oauth.hpp +++ b/UI/auth-oauth.hpp @@ -24,6 +24,8 @@ public: inline bool LoadFail() const { return fail; } virtual int exec() override; + virtual void reject() override; + virtual void accept() override; public slots: void urlChanged(const QString &url); diff --git a/UI/auth-restream.cpp b/UI/auth-restream.cpp index ed4d39c88..9188da118 100644 --- a/UI/auth-restream.cpp +++ b/UI/auth-restream.cpp @@ -282,6 +282,11 @@ static void DeleteCookies() void RegisterRestreamAuth() { +#if !defined(__APPLE__) && !defined(_WIN32) + if (QApplication::platformName().contains("wayland")) + return; +#endif + OAuth::RegisterOAuth(restreamDef, CreateRestreamAuth, RestreamAuth::Login, DeleteCookies); } diff --git a/UI/auth-twitch.cpp b/UI/auth-twitch.cpp index 034c3d82b..386a0e927 100644 --- a/UI/auth-twitch.cpp +++ b/UI/auth-twitch.cpp @@ -512,6 +512,11 @@ static void DeleteCookies() void RegisterTwitchAuth() { +#if !defined(__APPLE__) && !defined(_WIN32) + if (QApplication::platformName().contains("wayland")) + return; +#endif + OAuth::RegisterOAuth(twitchDef, CreateTwitchAuth, TwitchAuth::Login, DeleteCookies); } diff --git a/UI/context-bar-controls.cpp b/UI/context-bar-controls.cpp index 55fe20495..73c37582e 100644 --- a/UI/context-bar-controls.cpp +++ b/UI/context-bar-controls.cpp @@ -728,12 +728,15 @@ void TextSourceToolbar::on_text_textChanged() if (!source) { return; } - + std::string newText = QT_TO_UTF8(ui->text->text()); + OBSDataAutoRelease settings = obs_source_get_settings(source); + if (newText == obs_data_get_string(settings, "text")) { + return; + } SaveOldProperties(source); - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "text", QT_TO_UTF8(ui->text->text())); - obs_source_update(source, settings); + obs_data_set_string(settings, "text", newText.c_str()); + obs_source_update(source, nullptr); SetUndoProperties(source, true); } diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 4c03aa7db..8f11efee1 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -102,7 +102,6 @@ LockVolume="Lock Volume" LogViewer="Log Viewer" ShowOnStartup="Show on startup" OpenFile="Open file" -AddValue="Add %1" AddSource="Add Source" RemoveScene="Remove Selected Scene" RemoveSource="Remove Selected Source(s)" @@ -206,7 +205,6 @@ Basic.AutoConfig.StreamPage.Service.Custom="Custom..." Basic.AutoConfig.StreamPage.Server="Server" Basic.AutoConfig.StreamPage.StreamKey="Stream Key" Basic.AutoConfig.StreamPage.StreamKey.ToolTip="RIST: enter the encryption passphrase.\nRTMP: enter the key provided by the service.\nSRT: enter the streamid if the service uses one." -Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Link)" Basic.AutoConfig.StreamPage.EncoderKey="Encoder Key" Basic.AutoConfig.StreamPage.ConnectedAccount="Connected account" Basic.AutoConfig.StreamPage.PerformBandwidthTest="Estimate bitrate with bandwidth test (may take a few minutes)" @@ -264,8 +262,6 @@ Updater.Text="There is a new update available:" Updater.UpdateNow="Update Now" Updater.RemindMeLater="Remind me Later" Updater.Skip="Skip Version" -Updater.Running.Title="Program currently active" -Updater.Running.Text="Outputs are currently active, please shut down any active outputs before attempting to update" Updater.NoUpdatesAvailable.Title="No updates available" Updater.NoUpdatesAvailable.Text="No updates are currently available" Updater.BranchNotFound.Title="Update Channel Removed" @@ -275,8 +271,6 @@ Updater.RepairButUpdatesAvailable.Text="Checking file integrity is only possible Updater.RepairConfirm.Title="Confirm Integrity Check" Updater.RepairConfirm.Text="Starting the integrity check will scan your OBS installation for corruption and redownload broken/modified files. This may take a moment.\n\nDo you wish to proceed?" Updater.FailedToLaunch="Failed to launch updater" -Updater.GameCaptureActive.Title="Game capture active" -Updater.GameCaptureActive.Text="Game capture hook library is currently in use. Please close any games/programs being captured (or restart Windows) and try again." # quick transitions QuickTransitions.SwapScenes="Swap Preview/Program Scenes After Transitioning" @@ -348,6 +342,7 @@ TransitionNameDlg.Text="Please enter the name of the transition" TransitionNameDlg.Title="Transition Name" # title bar strings +TitleBar.PortableMode="Portable Mode" TitleBar.Profile="Profile" TitleBar.Scenes="Scenes" @@ -459,6 +454,8 @@ Remux.FileExists="The following target files already exist. Do you want to repla Remux.ExitUnfinishedTitle="Remuxing in progress" Remux.ExitUnfinished="Remuxing is not finished, stopping now may render the target file unusable.\nAre you sure you want to stop remuxing?" Remux.HelpText="Drop files in this window to remux, or select an empty \"OBS Recording\" cell to browse for a file." +Remux.NoFilesAddedTitle="No remuxing file added" +Remux.NoFilesAdded="No file is added to remux. Drop a folder containing one or more video files." # missing file dialog MissingFiles="Missing Files" @@ -497,9 +494,9 @@ MacPermissions.Item.Accessibility="Accessibility" MacPermissions.Item.Accessibility.Details="For keyboard shortcuts (hotkeys) to work while other apps are focused, please enable this permission." MacPermissions.Continue="Continue" -# update dialog -UpdateAvailable="New Update Available" -UpdateAvailable.Text="Version %1.%2.%3 is now available. Click here to download" +# Source leak error message +SourceLeak.Title="Source Cleanup Error" +SourceLeak.Text="There was a problem while changing scene collections and some sources could not be unloaded. This issue is typically caused by plugins that are not releasing resources properly. Please ensure that any plugins you are using are up to date.\n\nOBS Studio will now exit to prevent any potential data corruption." # audio device names Basic.DesktopDevice1="Desktop Audio" @@ -791,7 +788,6 @@ Basic.MainMenu.View="&View" Basic.MainMenu.View.Toolbars="&Toolbars" Basic.MainMenu.View.ListboxToolbars="Scene/Source List Buttons" Basic.MainMenu.View.ContextBar="Source Toolbar" -Basic.MainMenu.View.SceneTransitions="S&cene Transitions" Basic.MainMenu.View.SourceIcons="Source &Icons" Basic.MainMenu.View.StatusBar="&Status Bar" Basic.MainMenu.View.Fullscreen.Interface="Fullscreen Interface" @@ -813,7 +809,6 @@ Basic.MainMenu.Profile.Export="Export Profile" Basic.MainMenu.SceneCollection.Import="Import Scene Collection" Basic.MainMenu.SceneCollection.Export="Export Scene Collection" Basic.MainMenu.Profile.Exists="The profile already exists" -Basic.MainMenu.SceneCollection.Exists="The scene collection already exists" # basic mode help menu Basic.MainMenu.Tools="&Tools" @@ -908,7 +903,6 @@ Basic.Settings.General.ChannelDescription.beta="Potentially unstable pre-release # basic mode 'stream' settings Basic.Settings.Stream="Stream" -Basic.Settings.Stream.StreamType="Stream Type" Basic.Settings.Stream.Custom.UseAuthentication="Use authentication" Basic.Settings.Stream.Custom.Username="Username" Basic.Settings.Stream.Custom.Password="Password" @@ -949,7 +943,6 @@ Basic.Settings.Output.Format.TT.fmp4="Fragmented MP4 writes the recording in chu Basic.Settings.Output.Encoder.Video="Video Encoder" Basic.Settings.Output.Encoder.Audio="Audio Encoder" Basic.Settings.Output.SelectDirectory="Select Recording Directory" -Basic.Settings.Output.SelectFile="Select Recording File" Basic.Settings.Output.DynamicBitrate="Dynamically change bitrate to manage congestion" Basic.Settings.Output.DynamicBitrate.Beta="Dynamically change bitrate to manage congestion (Beta)" Basic.Settings.Output.DynamicBitrate.TT="Instead of dropping frames to reduce congestion, dynamically changes bitrate on the fly.\n\nNote that this can increase delay to viewers if there is significant sudden congestion.\nWhen the bitrate drops, it can take up to a few minutes to restore.\n\nCurrently only supported for RTMP." @@ -1147,7 +1140,7 @@ Basic.Settings.Audio.MeterDecayRate.Slow="Slow (Type II PPM)" Basic.Settings.Audio.PeakMeterType="Peak Meter Type" Basic.Settings.Audio.PeakMeterType.SamplePeak="Sample Peak" Basic.Settings.Audio.PeakMeterType.TruePeak="True Peak (Higher CPU usage)" -Basic.Settings.Audio.MultiChannelWarning.Enabled="WARNING: Surround sound audio is enabled." +Basic.Settings.Audio.MultichannelWarning.Enabled="WARNING: Surround sound audio is enabled." Basic.Settings.Audio.MultichannelWarning="If streaming, check to see if your streaming service supports both surround sound ingest and surround sound playback. Facebook 360 Live is one example where surround sound is fully supported. Although Facebook Live and YouTube Live both accept surround ingest, Facebook Live downmixes to stereo, and YouTube Live plays only two channels.\n\nOBS audio filters are compatible with surround sound, though VST plugin support isn't guaranteed." Basic.Settings.Audio.MultichannelWarning.Title="Enable surround sound audio?" Basic.Settings.Audio.MultichannelWarning.Confirm="Are you sure you want to enable surround sound audio?" @@ -1199,7 +1192,6 @@ Basic.Settings.Advanced.General.ProcessPriority.Idle="Idle" Basic.Settings.Advanced.FormatWarning="Warning: Color formats other than NV12/P010 are primarily intended for recording, and are not recommended when streaming. Streaming may incur increased CPU usage due to color format conversion." Basic.Settings.Advanced.FormatWarningPreciseSdr="Warning: High-precision formats are more commonly used with HDR color spaces." Basic.Settings.Advanced.FormatWarning2100="Warning: Rec. 2100 should use a format with more precision." -Basic.Settings.Advanced.Audio.BufferingTime="Audio Buffering Time" Basic.Settings.Advanced.Video.ColorFormat="Color Format" Basic.Settings.Advanced.Video.ColorFormat.NV12="NV12 (8-bit, 4:2:0, 2 planes)" Basic.Settings.Advanced.Video.ColorFormat.I420="I420 (8-bit, 4:2:0, 3 planes)" @@ -1327,6 +1319,8 @@ SceneItemHide="Hide '%1'" # Output warnings OutputWarnings.NoTracksSelected="You must select at least one track" +OutputWarnings.NoTracksSelectedOnExit.Title="Output Settings Error" +OutputWarnings.NoTracksSelectedOnExit.Text="All outputs must have at least one audio track selected." OutputWarnings.MP4Recording="Warning: Recordings saved to MP4/MOV will be unrecoverable if the file cannot be finalized (e.g. as a result of BSODs, power losses, etc.). If you want to record multiple audio tracks consider using MKV and remux the recording to MP4/MOV after it is finished (File → Remux Recordings)" OutputWarnings.CannotPause="Warning: Recordings cannot be paused if the recording encoder is set to \"(Use stream encoder)\"" OutputWarnings.CodecIncompatible="The audio or video encoder selection was reset due to incompatibility. Please select a compatible encoder from the list." @@ -1364,6 +1358,7 @@ About.Donate="Make a Contribution" About.GetInvolved="Get Involved" About.Authors="Authors" About.License="License" +About.Error="Error! File could not be read.\n\nGo to: %1" About.Contribute="Support the OBS Project" # Drag-drop URL @@ -1402,7 +1397,6 @@ LoadProfileNeedsRestart="Profile contains settings that require restarting OBS:\ # Context Bar ContextBar.NoSelectedSource="No source selected" ContextBar.ResetTransform="Reset Transform" -ContextBar.FitToCanvas="Fit to Canvas" # Context Bar Media Controls ContextBar.MediaControls.PlayMedia="Play Media" @@ -1411,7 +1405,6 @@ ContextBar.MediaControls.StopMedia="Stop Media" ContextBar.MediaControls.RestartMedia="Restart Media" ContextBar.MediaControls.PlaylistNext="Next in Playlist" ContextBar.MediaControls.PlaylistPrevious="Previous in Playlist" -ContextBar.MediaControls.MediaProperties="Media Properties" ContextBar.MediaControls.BlindSeek="Media Seek Widget" # YouTube Actions and Auth diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index a525bbebe..3a47002d1 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -36,7 +36,7 @@ OBSTheme { alternateBase: rgb(11,10,11); text: rgb(255,254,255); - + button: rgb(22,36,88); buttonText: rgb(255,254,255); @@ -62,14 +62,14 @@ OBSTheme::disabled { windowText: rgb(153,153,153); text: rgb(153,153,153); button: rgb(27,29,34); - + buttonText: rgb(24,24,25); brightText: rgb(24,24,25); } OBSTheme::inactive { text: rgb(255,254,255); - + highlight: rgb(25,28,34); highlightText: rgb(255,255,255); } @@ -171,11 +171,11 @@ QMenu::right-arrow { /* Top Menu Bar Items */ QMenuBar::item { - background-color: transparent; + background-color: transparent; } QMenuBar::item:selected { - background: rgb(19,26,48); + background: rgb(19,26,48); } /* Item Lists */ @@ -212,7 +212,7 @@ SceneTree::item, SourceTree::item { border-radius: 4px; color: palette(text); - border: 0px solid transparent; + border: 0px solid transparent; } QMenu::item:selected, @@ -420,7 +420,7 @@ QScrollBar::handle:pressed { } QScrollBar::handle:vertical { - min-height: 20px; + min-height: 20px; } QScrollBar::handle:horizontal { @@ -891,7 +891,7 @@ QSlider::handle:vertical:pressed { } QSlider::add-page:vertical { - background-color: palette(highlight); + background-color: palette(highlight); border-radius: 2px; } @@ -929,16 +929,16 @@ QSlider::handle:disabled { } VolumeMeter { - qproperty-backgroundNominalColor: rgb(66,116,12); - qproperty-backgroundWarningColor: rgb(152,143,15); - qproperty-backgroundErrorColor: rgb(128,32,4); - qproperty-foregroundNominalColor: rgb(132,216,43); - qproperty-foregroundWarningColor: rgb(228,215,23); - qproperty-foregroundErrorColor: rgb(215,65,22); - qproperty-magnitudeColor: rgb(49,54,59); - qproperty-majorTickColor: rgb(239,240,241); - qproperty-minorTickColor: rgb(118,121,124); - qproperty-peakDecayRate: 23.4; + qproperty-backgroundNominalColor: rgb(66,116,12); + qproperty-backgroundWarningColor: rgb(152,143,15); + qproperty-backgroundErrorColor: rgb(128,32,4); + qproperty-foregroundNominalColor: rgb(132,216,43); + qproperty-foregroundWarningColor: rgb(228,215,23); + qproperty-foregroundErrorColor: rgb(215,65,22); + qproperty-magnitudeColor: rgb(49,54,59); + qproperty-majorTickColor: rgb(239,240,241); + qproperty-minorTickColor: rgb(118,121,124); + qproperty-peakDecayRate: 23.4; } /* Status Bar */ @@ -1107,44 +1107,44 @@ QCheckBox { QCheckBox::indicator, QGroupBox::indicator { - width: 18px; - height: 18px; + width: 18px; + height: 18px; } QGroupBox::indicator { - margin-left: 2px; + margin-left: 2px; } QCheckBox::indicator:unchecked, QGroupBox::indicator:unchecked { - image: url(./Yami/checkbox_unchecked.svg); + image: url(./Yami/checkbox_unchecked.svg); } QCheckBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border: none; - image: url(./Yami/checkbox_unchecked_focus.svg); + border: none; + image: url(./Yami/checkbox_unchecked_focus.svg); } QCheckBox::indicator:checked, QGroupBox::indicator:checked { - image: url(./Yami/checkbox_checked.svg); + image: url(./Yami/checkbox_checked.svg); } QCheckBox::indicator:checked:hover, QGroupBox::indicator:checked:hover { - border: none; - image: url(./Yami/checkbox_checked_focus.svg); + border: none; + image: url(./Yami/checkbox_checked_focus.svg); } QCheckBox::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(./Yami/checkbox_checked_disabled.svg); + image: url(./Yami/checkbox_checked_disabled.svg); } QCheckBox::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - image: url(./Yami/checkbox_unchecked_disabled.svg); + image: url(./Yami/checkbox_unchecked_disabled.svg); } /* Locked CheckBox */ @@ -1212,7 +1212,7 @@ QPushButton#extraPanelDelete:pressed { /* Mute CheckBox */ MuteCheckBox { - outline: none; + outline: none; } MuteCheckBox::indicator { @@ -1221,35 +1221,35 @@ MuteCheckBox::indicator { } MuteCheckBox::indicator:checked { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:hover { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:focus { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:checked:hover { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:focus { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:disabled { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked:disabled { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } #hotkeyFilterReset { @@ -1266,7 +1266,7 @@ OBSHotkeyLabel { } OBSHotkeyLabel[hotkeyPairHover=true] { - color: rgb(53,82,222); + color: rgb(53,82,222); } OBSHotkeyWidget QPushButton { @@ -1336,17 +1336,21 @@ SceneTree { } *[gridMode="true"] SceneTree::item:selected { - background-color: rgb(51,69,163); + background-color: rgb(88,22,36); } *[gridMode="true"] SceneTree::item:checked { - background-color: rgb(51,69,163); + background-color: rgb(88,22,36); } *[gridMode="true"] SceneTree::item:hover { background-color: rgb(42,58,117); } +*[gridMode="true"] SceneTree::item:selected:hover { + background-color: rgb(116,32,49); +} + /* Save icon */ * [themeID="replayIconSmall"] { @@ -1356,25 +1360,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid #4c4c4c; - height: 5px; - background: palette(dark); + border: 1px solid #4c4c4c; + height: 5px; + background: palette(dark); } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: palette(dark); - border: 1px solid #4c4c4c; + background: palette(dark); + border: 1px solid #4c4c4c; } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: #d2d2d2; - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: #d2d2d2; + width: 12px; + height: 24px; + margin: -24px 0px; } /* Media icons */ @@ -1487,7 +1491,7 @@ QCalendarWidget QToolButton:pressed { /* Month Dropdown Menu */ QCalendarWidget QMenu { - + } /* Year spinbox */ QCalendarWidget QSpinBox { diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index 2b56359d3..fc4883759 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -132,8 +132,8 @@ QMainWindow::separator { /* Dock Widget */ QDockWidget { - titlebar-close-icon: url('./Dark/close.svg'); - titlebar-normal-icon: url('./Dark/popout.svg'); + titlebar-close-icon: url('./Dark/close.svg'); + titlebar-normal-icon: url('./Dark/popout.svg'); } QDockWidget::title { @@ -262,8 +262,8 @@ QToolBar { QPushButton[toolButton="true"], QToolButton { - background: transparent; - border: none; + background: transparent; + border: none; padding: 1px; margin: 1px; } @@ -750,7 +750,7 @@ QLabel#errorLabel { /* Preview background color */ OBSQTDisplay { - qproperty-displayBackgroundColor: rgb(76,76,76); + qproperty-displayBackgroundColor: rgb(76,76,76); } /* Preview/Program labels */ @@ -826,7 +826,7 @@ QPushButton#extraPanelDelete:pressed { } OBSMissingFiles { - qproperty-warningIcon: url(./Dark/alert.svg); + qproperty-warningIcon: url(./Dark/alert.svg); } /* Source Icons */ @@ -854,7 +854,7 @@ OBSBasic { SceneTree { qproperty-gridItemWidth: 150; - qproperty-gridItemHeight: 27; + qproperty-gridItemHeight: 27; } *[gridMode="true"] SceneTree::item { @@ -863,9 +863,9 @@ SceneTree { border: none; border-radius: 3px; padding: 4px; - padding-left: 10px; - padding-right: 10px; - margin: 1px; + padding-left: 10px; + padding-right: 10px; + margin: 1px; } *[gridMode="true"] SceneTree::item:selected { @@ -893,25 +893,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid rgb(76,76,76); - height: 5px; - background: palette(base); + border: 1px solid rgb(76,76,76); + height: 5px; + background: palette(base); } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: palette(base);; - border: 1px solid rgb(76,76,76); + background: palette(base);; + border: 1px solid rgb(76,76,76); } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: rgb(210,210,210); - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: rgb(210,210,210); + width: 12px; + height: 24px; + margin: -24px 0px; } /* Media icons */ diff --git a/UI/data/themes/Grey.qss b/UI/data/themes/Grey.qss index 6c4df993c..93733dd07 100644 --- a/UI/data/themes/Grey.qss +++ b/UI/data/themes/Grey.qss @@ -36,7 +36,7 @@ OBSTheme { alternateBase: rgb(11,10,11); text: rgb(255,254,255); - + button: rgb(67,67,67); buttonText: rgb(255,254,255); @@ -62,14 +62,14 @@ OBSTheme::disabled { windowText: rgb(153,153,153); text: rgb(153,153,153); button: rgb(27,29,34); - + buttonText: rgb(43,46,56); brightText: rgb(43,46,56); } OBSTheme::inactive { text: rgb(255,254,255); - + highlight: rgb(25,28,34); highlightText: rgb(255,255,255); } @@ -171,11 +171,11 @@ QMenu::right-arrow { /* Top Menu Bar Items */ QMenuBar::item { - background-color: transparent; + background-color: transparent; } QMenuBar::item:selected { - background: rgb(40,76,184); + background: rgb(40,76,184); } /* Item Lists */ @@ -212,7 +212,7 @@ SceneTree::item, SourceTree::item { border-radius: 4px; color: palette(text); - border: 0px solid transparent; + border: 0px solid transparent; } QMenu::item:selected, @@ -420,7 +420,7 @@ QScrollBar::handle:pressed { } QScrollBar::handle:vertical { - min-height: 20px; + min-height: 20px; } QScrollBar::handle:horizontal { @@ -880,7 +880,7 @@ QSlider::handle:vertical:pressed { } QSlider::add-page:vertical { - background-color: palette(highlight); + background-color: palette(highlight); border-radius: 2px; } @@ -1095,44 +1095,44 @@ QCheckBox { QCheckBox::indicator, QGroupBox::indicator { - width: 18px; - height: 18px; + width: 18px; + height: 18px; } QGroupBox::indicator { - margin-left: 2px; + margin-left: 2px; } QCheckBox::indicator:unchecked, QGroupBox::indicator:unchecked { - image: url(./Yami/checkbox_unchecked.svg); + image: url(./Yami/checkbox_unchecked.svg); } QCheckBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border: none; - image: url(./Yami/checkbox_unchecked_focus.svg); + border: none; + image: url(./Yami/checkbox_unchecked_focus.svg); } QCheckBox::indicator:checked, QGroupBox::indicator:checked { - image: url(./Yami/checkbox_checked.svg); + image: url(./Yami/checkbox_checked.svg); } QCheckBox::indicator:checked:hover, QGroupBox::indicator:checked:hover { - border: none; - image: url(./Yami/checkbox_checked_focus.svg); + border: none; + image: url(./Yami/checkbox_checked_focus.svg); } QCheckBox::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(./Yami/checkbox_checked_disabled.svg); + image: url(./Yami/checkbox_checked_disabled.svg); } QCheckBox::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - image: url(./Yami/checkbox_unchecked_disabled.svg); + image: url(./Yami/checkbox_unchecked_disabled.svg); } /* Locked CheckBox */ @@ -1200,7 +1200,7 @@ QPushButton#extraPanelDelete:pressed { /* Mute CheckBox */ MuteCheckBox { - outline: none; + outline: none; } MuteCheckBox::indicator { @@ -1209,35 +1209,35 @@ MuteCheckBox::indicator { } MuteCheckBox::indicator:checked { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:hover { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:focus { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:checked:hover { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:focus { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:disabled { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked:disabled { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } #hotkeyFilterReset { @@ -1254,7 +1254,7 @@ OBSHotkeyLabel { } OBSHotkeyLabel[hotkeyPairHover=true] { - color: rgb(53,82,222); + color: rgb(53,82,222); } OBSHotkeyWidget QPushButton { @@ -1324,17 +1324,21 @@ SceneTree { } *[gridMode="true"] SceneTree::item:selected { - background-color: rgb(51,69,163); + background-color: rgb(40,76,184); } *[gridMode="true"] SceneTree::item:checked { - background-color: rgb(51,69,163); + background-color: rgb(40,76,184); } *[gridMode="true"] SceneTree::item:hover { background-color: rgb(89,89,89); } +*[gridMode="true"] SceneTree::item:selected:hover { + background-color: rgb(54,92,192); +} + /* Save icon */ * [themeID="replayIconSmall"] { @@ -1344,25 +1348,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid #4c4c4c; - height: 5px; - background: palette(dark); + border: 1px solid #4c4c4c; + height: 5px; + background: palette(dark); } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: palette(dark); - border: 1px solid #4c4c4c; + background: palette(dark); + border: 1px solid #4c4c4c; } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: #d2d2d2; - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: #d2d2d2; + width: 12px; + height: 24px; + margin: -24px 0px; } /* Media icons */ @@ -1475,7 +1479,7 @@ QCalendarWidget QToolButton:pressed { /* Month Dropdown Menu */ QCalendarWidget QMenu { - + } /* Year spinbox */ QCalendarWidget QSpinBox { diff --git a/UI/data/themes/Light.qss b/UI/data/themes/Light.qss index cb55f2c66..3f8f81781 100644 --- a/UI/data/themes/Light.qss +++ b/UI/data/themes/Light.qss @@ -36,7 +36,7 @@ OBSTheme { alternateBase: rgb(11,10,11); text: rgb(2,2,2); - + button: rgb(243,243,243); buttonText: rgb(2,2,2); @@ -62,14 +62,14 @@ OBSTheme::disabled { windowText: rgb(74,74,74); text: rgb(74,74,74); button: rgb(27,29,34); - + buttonText: rgb(43,46,56); brightText: rgb(43,46,56); } OBSTheme::inactive { text: rgb(255,254,255); - + highlight: rgb(25,28,34); highlightText: rgb(255,255,255); } @@ -171,11 +171,11 @@ QMenu::right-arrow { /* Top Menu Bar Items */ QMenuBar::item { - background-color: transparent; + background-color: transparent; } QMenuBar::item:selected { - background: rgb(140,181,255); + background: rgb(140,181,255); } /* Item Lists */ @@ -212,7 +212,7 @@ SceneTree::item, SourceTree::item { border-radius: 4px; color: palette(text); - border: 0px solid transparent; + border: 0px solid transparent; } QMenu::item:selected, @@ -420,7 +420,7 @@ QScrollBar::handle:pressed { } QScrollBar::handle:vertical { - min-height: 20px; + min-height: 20px; } QScrollBar::handle:horizontal { @@ -880,7 +880,7 @@ QSlider::handle:vertical:pressed { } QSlider::add-page:vertical { - background-color: palette(highlight); + background-color: palette(highlight); border-radius: 2px; } @@ -1095,44 +1095,44 @@ QCheckBox { QCheckBox::indicator, QGroupBox::indicator { - width: 18px; - height: 18px; + width: 18px; + height: 18px; } QGroupBox::indicator { - margin-left: 2px; + margin-left: 2px; } QCheckBox::indicator:unchecked, QGroupBox::indicator:unchecked { - image: url(./Light/checkbox_unchecked.svg); + image: url(./Light/checkbox_unchecked.svg); } QCheckBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border: none; - image: url(./Light/checkbox_unchecked_focus.svg); + border: none; + image: url(./Light/checkbox_unchecked_focus.svg); } QCheckBox::indicator:checked, QGroupBox::indicator:checked { - image: url(./Light/checkbox_checked.svg); + image: url(./Light/checkbox_checked.svg); } QCheckBox::indicator:checked:hover, QGroupBox::indicator:checked:hover { - border: none; - image: url(./Light/checkbox_checked_focus.svg); + border: none; + image: url(./Light/checkbox_checked_focus.svg); } QCheckBox::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(./Light/checkbox_checked_disabled.svg); + image: url(./Light/checkbox_checked_disabled.svg); } QCheckBox::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - image: url(./Light/checkbox_unchecked_disabled.svg); + image: url(./Light/checkbox_unchecked_disabled.svg); } /* Locked CheckBox */ @@ -1200,7 +1200,7 @@ QPushButton#extraPanelDelete:pressed { /* Mute CheckBox */ MuteCheckBox { - outline: none; + outline: none; } MuteCheckBox::indicator { @@ -1209,35 +1209,35 @@ MuteCheckBox::indicator { } MuteCheckBox::indicator:checked { - image: url(./Light/mute.svg); + image: url(./Light/mute.svg); } MuteCheckBox::indicator:unchecked { - image: url(./Light/settings/audio.svg); + image: url(./Light/settings/audio.svg); } MuteCheckBox::indicator:unchecked:hover { - image: url(./Light/settings/audio.svg); + image: url(./Light/settings/audio.svg); } MuteCheckBox::indicator:unchecked:focus { - image: url(./Light/settings/audio.svg); + image: url(./Light/settings/audio.svg); } MuteCheckBox::indicator:checked:hover { - image: url(./Light/mute.svg); + image: url(./Light/mute.svg); } MuteCheckBox::indicator:checked:focus { - image: url(./Light/mute.svg); + image: url(./Light/mute.svg); } MuteCheckBox::indicator:checked:disabled { - image: url(./Light/mute.svg); + image: url(./Light/mute.svg); } MuteCheckBox::indicator:unchecked:disabled { - image: url(./Light/settings/audio.svg); + image: url(./Light/settings/audio.svg); } #hotkeyFilterReset { @@ -1254,7 +1254,7 @@ OBSHotkeyLabel { } OBSHotkeyLabel[hotkeyPairHover=true] { - color: rgb(53,82,222); + color: rgb(53,82,222); } OBSHotkeyWidget QPushButton { @@ -1324,17 +1324,21 @@ SceneTree { } *[gridMode="true"] SceneTree::item:selected { - background-color: rgb(51,69,163); + background-color: rgb(140,181,255); } *[gridMode="true"] SceneTree::item:checked { - background-color: rgb(51,69,163); + background-color: rgb(140,181,255); } *[gridMode="true"] SceneTree::item:hover { background-color: rgb(254,254,255); } +*[gridMode="true"] SceneTree::item:selected:hover { + background-color: rgb(178,207,255); +} + /* Save icon */ * [themeID="replayIconSmall"] { @@ -1350,25 +1354,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid #4c4c4c; - height: 5px; - background: palette(dark); + border: 1px solid #4c4c4c; + height: 5px; + background: palette(dark); } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: palette(dark); - border: 1px solid #4c4c4c; + background: palette(dark); + border: 1px solid #4c4c4c; } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: palette(text); - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: palette(text); + width: 12px; + height: 24px; + margin: -24px 0px; } /* Media icons */ @@ -1481,7 +1485,7 @@ QCalendarWidget QToolButton:pressed { /* Month Dropdown Menu */ QCalendarWidget QMenu { - + } /* Year spinbox */ QCalendarWidget QSpinBox { diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index d376fb109..351e9bef9 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -36,7 +36,7 @@ OBSTheme { alternateBase: rgb(11,10,11); text: rgb(255,254,255); - + button: rgb(0,187,210); buttonText: rgb(255,254,255); @@ -51,7 +51,7 @@ OBSTheme { primaryLight: rgb(0,188,212); primaryDark: rgb(25,27,38); - secondary: rgb(240,96,146); + secondary: rgb(240,96,146); highlight: rgb(42,130,218); highlightText: rgb(255,254,255); @@ -64,14 +64,14 @@ OBSTheme::disabled { windowText: rgb(153,153,153); text: rgb(153,153,153); button: rgb(27,29,34); - + buttonText: rgb(35,38,41); brightText: rgb(35,38,41); } OBSTheme::inactive { text: rgb(255,254,255); - + highlight: rgb(25,28,34); highlightText: rgb(255,255,255); } @@ -173,11 +173,11 @@ QMenu::right-arrow { /* Top Menu Bar Items */ QMenuBar::item { - background-color: transparent; + background-color: transparent; } QMenuBar::item:selected { - background: rgb(0,188,212); + background: rgb(0,188,212); } /* Item Lists */ @@ -214,7 +214,7 @@ SceneTree::item, SourceTree::item { border-radius: 4px; color: palette(text); - border: 0px solid transparent; + border: 0px solid transparent; } QMenu::item:selected, @@ -355,7 +355,7 @@ QGroupBox { } QGroupBox::title { - color: rgb(240,98,146); + color: rgb(240,98,146); subcontrol-origin: margin; left: 8px; top: 8px; @@ -423,7 +423,7 @@ QScrollBar::handle:pressed { } QScrollBar::handle:vertical { - min-height: 20px; + min-height: 20px; } QScrollBar::handle:horizontal { @@ -470,7 +470,7 @@ QToolButton { QPushButton[toolButton="true"]:disabled, QToolButton:disabled { - background-color: transparent; + background-color: transparent; } QPushButton[toolButton="true"]:last-child, @@ -627,7 +627,7 @@ QComboBox:selected, QDateTimeEdit:hover, QDateTimeEdit:selected { background-color: palette(base); - border: 2px solid rgb(0,188,212); + border: 2px solid rgb(0,188,212); } QComboBox::drop-down, @@ -884,7 +884,7 @@ QSlider::handle:vertical:pressed { } QSlider::add-page:vertical { - background-color: palette(highlight); + background-color: palette(highlight); border-radius: 2px; } @@ -922,15 +922,15 @@ QSlider::handle:disabled { } VolumeMeter { - qproperty-backgroundNominalColor: rgb(0, 128, 79); - qproperty-backgroundWarningColor: rgb(128, 57, 0); - qproperty-backgroundErrorColor: rgb(128, 9, 0); - qproperty-foregroundNominalColor: rgb(119, 255, 143); - qproperty-foregroundWarningColor: rgb(255, 157, 76); - qproperty-foregroundErrorColor: rgb(255, 89, 76); - qproperty-magnitudeColor: palette(window); - qproperty-majorTickColor: palette(window-text); - qproperty-minorTickColor: palette(mid); + qproperty-backgroundNominalColor: rgb(0, 128, 79); + qproperty-backgroundWarningColor: rgb(128, 57, 0); + qproperty-backgroundErrorColor: rgb(128, 9, 0); + qproperty-foregroundNominalColor: rgb(119, 255, 143); + qproperty-foregroundWarningColor: rgb(255, 157, 76); + qproperty-foregroundErrorColor: rgb(255, 89, 76); + qproperty-magnitudeColor: palette(window); + qproperty-majorTickColor: palette(window-text); + qproperty-minorTickColor: palette(mid); } /* Status Bar */ @@ -1099,44 +1099,44 @@ QCheckBox { QCheckBox::indicator, QGroupBox::indicator { - width: 18px; - height: 18px; + width: 18px; + height: 18px; } QGroupBox::indicator { - margin-left: 2px; + margin-left: 2px; } QCheckBox::indicator:unchecked, QGroupBox::indicator:unchecked { - image: url(./Yami/checkbox_unchecked.svg); + image: url(./Yami/checkbox_unchecked.svg); } QCheckBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border: none; - image: url(./Yami/checkbox_unchecked_focus.svg); + border: none; + image: url(./Yami/checkbox_unchecked_focus.svg); } QCheckBox::indicator:checked, QGroupBox::indicator:checked { - image: url(./Yami/checkbox_checked.svg); + image: url(./Yami/checkbox_checked.svg); } QCheckBox::indicator:checked:hover, QGroupBox::indicator:checked:hover { - border: none; - image: url(./Yami/checkbox_checked_focus.svg); + border: none; + image: url(./Yami/checkbox_checked_focus.svg); } QCheckBox::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(./Yami/checkbox_checked_disabled.svg); + image: url(./Yami/checkbox_checked_disabled.svg); } QCheckBox::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - image: url(./Yami/checkbox_unchecked_disabled.svg); + image: url(./Yami/checkbox_unchecked_disabled.svg); } /* Locked CheckBox */ @@ -1204,7 +1204,7 @@ QPushButton#extraPanelDelete:pressed { /* Mute CheckBox */ MuteCheckBox { - outline: none; + outline: none; } MuteCheckBox::indicator { @@ -1213,35 +1213,35 @@ MuteCheckBox::indicator { } MuteCheckBox::indicator:checked { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:hover { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:focus { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:checked:hover { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:focus { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:disabled { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked:disabled { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } #hotkeyFilterReset { @@ -1258,7 +1258,7 @@ OBSHotkeyLabel { } OBSHotkeyLabel[hotkeyPairHover=true] { - color: rgb(240,98,146); + color: rgb(240,98,146); } OBSHotkeyWidget QPushButton { @@ -1328,17 +1328,21 @@ SceneTree { } *[gridMode="true"] SceneTree::item:selected { - background-color: rgb(51,69,163); + background-color: rgb(145,76,103); } *[gridMode="true"] SceneTree::item:checked { - background-color: rgb(51,69,163); + background-color: rgb(145,76,103); } *[gridMode="true"] SceneTree::item:hover { background-color: rgb(145,76,103); } +*[gridMode="true"] SceneTree::item:selected:hover { + background-color: rgb(145,76,103); +} + /* Save icon */ * [themeID="replayIconSmall"] { @@ -1348,25 +1352,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid #4c4c4c; - height: 5px; - background: palette(dark); + border: 1px solid #4c4c4c; + height: 5px; + background: palette(dark); } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: palette(dark); - border: 1px solid #4c4c4c; + background: palette(dark); + border: 1px solid #4c4c4c; } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: #d2d2d2; - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: #d2d2d2; + width: 12px; + height: 24px; + margin: -24px 0px; } /* Media icons */ @@ -1479,7 +1483,7 @@ QCalendarWidget QToolButton:pressed { /* Month Dropdown Menu */ QCalendarWidget QMenu { - + } /* Year spinbox */ QCalendarWidget QSpinBox { diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 2a96cacd4..76af193c6 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -174,13 +174,13 @@ QMainWindow::separator { } * [themeID="aboutHLayout"] { - background-color: #DCD9D7; + background-color: #DCD9D7; } /* Preview background color */ OBSQTDisplay { - qproperty-displayBackgroundColor: rgb(76, 76, 76); + qproperty-displayBackgroundColor: rgb(76, 76, 76); } /* Preview/Program labels */ @@ -244,7 +244,7 @@ VisibilityCheckBox::indicator:unchecked { } OBSMissingFiles { - qproperty-warningIcon: url(:res/images/alert.svg); + qproperty-warningIcon: url(:res/images/alert.svg); } /* Source Icons */ @@ -272,7 +272,7 @@ OBSBasic { SceneTree { qproperty-gridItemWidth: 150; - qproperty-gridItemHeight: 24; + qproperty-gridItemHeight: 24; } /* Save icon */ @@ -284,25 +284,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid #4c4c4c; - height: 5px; - background: #DCD9D7; + border: 1px solid #4c4c4c; + height: 5px; + background: #DCD9D7; } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: #DCD9D7; - border: 1px solid #4c4c4c; + background: #DCD9D7; + border: 1px solid #4c4c4c; } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: #4c4c4c; - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: #4c4c4c; + width: 12px; + height: 24px; + margin: -24px 0px; } /* Source Context */ @@ -358,18 +358,18 @@ OBSYoutubeActions { } #ytEventList QLabel { - background-color: #e1e1e1; - border: 1px solid #ddd; + background-color: #e1e1e1; + border: 1px solid #ddd; } #ytEventList QLabel:hover { background-color: #eee; - border: 1px solid #777; + border: 1px solid #777; } #ytEventList QLabel[isSelectedEvent=true] { background-color: #ccc; - border: 1px solid #444; + border: 1px solid #444; } /* Calendar Widget */ diff --git a/UI/data/themes/Yami.qss b/UI/data/themes/Yami.qss index 21eefbd34..54b3c2e6a 100644 --- a/UI/data/themes/Yami.qss +++ b/UI/data/themes/Yami.qss @@ -36,7 +36,7 @@ OBSTheme { alternateBase: rgb(11,10,11); text: rgb(255,254,255); - + button: rgb(60,64,75); buttonText: rgb(255,254,255); @@ -62,14 +62,14 @@ OBSTheme::disabled { windowText: rgb(153,153,153); text: rgb(153,153,153); button: rgb(27,29,34); - + buttonText: rgb(43,46,56); brightText: rgb(43,46,56); } OBSTheme::inactive { text: rgb(255,254,255); - + highlight: rgb(25,28,34); highlightText: rgb(255,255,255); } @@ -171,11 +171,11 @@ QMenu::right-arrow { /* Top Menu Bar Items */ QMenuBar::item { - background-color: transparent; + background-color: transparent; } QMenuBar::item:selected { - background: rgb(40,76,184); + background: rgb(40,76,184); } /* Item Lists */ @@ -212,7 +212,7 @@ SceneTree::item, SourceTree::item { border-radius: 4px; color: palette(text); - border: 0px solid transparent; + border: 0px solid transparent; } QMenu::item:selected, @@ -424,7 +424,7 @@ QScrollBar::handle:pressed { } QScrollBar::handle:vertical { - min-height: 20px; + min-height: 20px; } QScrollBar::handle:horizontal { @@ -884,7 +884,7 @@ QSlider::handle:vertical:pressed { } QSlider::add-page:vertical { - background-color: palette(highlight); + background-color: palette(highlight); border-radius: 2px; } @@ -1099,44 +1099,44 @@ QCheckBox { QCheckBox::indicator, QGroupBox::indicator { - width: 18px; - height: 18px; + width: 18px; + height: 18px; } QGroupBox::indicator { - margin-left: 2px; + margin-left: 2px; } QCheckBox::indicator:unchecked, QGroupBox::indicator:unchecked { - image: url(./Yami/checkbox_unchecked.svg); + image: url(./Yami/checkbox_unchecked.svg); } QCheckBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:hover { - border: none; - image: url(./Yami/checkbox_unchecked_focus.svg); + border: none; + image: url(./Yami/checkbox_unchecked_focus.svg); } QCheckBox::indicator:checked, QGroupBox::indicator:checked { - image: url(./Yami/checkbox_checked.svg); + image: url(./Yami/checkbox_checked.svg); } QCheckBox::indicator:checked:hover, QGroupBox::indicator:checked:hover { - border: none; - image: url(./Yami/checkbox_checked_focus.svg); + border: none; + image: url(./Yami/checkbox_checked_focus.svg); } QCheckBox::indicator:checked:disabled, QGroupBox::indicator:checked:disabled { - image: url(./Yami/checkbox_checked_disabled.svg); + image: url(./Yami/checkbox_checked_disabled.svg); } QCheckBox::indicator:unchecked:disabled, QGroupBox::indicator:unchecked:disabled { - image: url(./Yami/checkbox_unchecked_disabled.svg); + image: url(./Yami/checkbox_unchecked_disabled.svg); } /* Locked CheckBox */ @@ -1204,7 +1204,7 @@ QPushButton#extraPanelDelete:pressed { /* Mute CheckBox */ MuteCheckBox { - outline: none; + outline: none; } MuteCheckBox::indicator { @@ -1213,35 +1213,35 @@ MuteCheckBox::indicator { } MuteCheckBox::indicator:checked { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:hover { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:unchecked:focus { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } MuteCheckBox::indicator:checked:hover { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:focus { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:checked:disabled { - image: url(./Dark/mute.svg); + image: url(./Dark/mute.svg); } MuteCheckBox::indicator:unchecked:disabled { - image: url(./Dark/settings/audio.svg); + image: url(./Dark/settings/audio.svg); } #hotkeyFilterReset { @@ -1258,7 +1258,7 @@ OBSHotkeyLabel { } OBSHotkeyLabel[hotkeyPairHover=true] { - color: rgb(53,82,222); + color: rgb(53,82,222); } OBSHotkeyWidget QPushButton { @@ -1328,17 +1328,21 @@ SceneTree { } *[gridMode="true"] SceneTree::item:selected { - background-color: rgb(51,69,163); + background-color: rgb(40,76,184); } *[gridMode="true"] SceneTree::item:checked { - background-color: rgb(51,69,163); + background-color: rgb(40,76,184); } *[gridMode="true"] SceneTree::item:hover { background-color: rgb(79,83,94); } +*[gridMode="true"] SceneTree::item:selected:hover { + background-color: rgb(54,92,192); +} + /* Save icon */ * [themeID="replayIconSmall"] { @@ -1348,25 +1352,25 @@ SceneTree { /* Studio Mode T-Bar */ QSlider[themeID="tBarSlider"] { - height: 24px; + height: 24px; } QSlider::groove:horizontal[themeID="tBarSlider"] { - border: 1px solid #4c4c4c; - height: 5px; - background: palette(dark); + border: 1px solid #4c4c4c; + height: 5px; + background: palette(dark); } QSlider::sub-page:horizontal[themeID="tBarSlider"] { - background: palette(dark); - border: 1px solid #4c4c4c; + background: palette(dark); + border: 1px solid #4c4c4c; } QSlider::handle:horizontal[themeID="tBarSlider"] { - background-color: #d2d2d2; - width: 12px; - height: 24px; - margin: -24px 0px; + background-color: #d2d2d2; + width: 12px; + height: 24px; + margin: -24px 0px; } /* Media icons */ @@ -1479,7 +1483,7 @@ QCalendarWidget QToolButton:pressed { /* Month Dropdown Menu */ QCalendarWidget QMenu { - + } /* Year spinbox */ QCalendarWidget QSpinBox { diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index 55af294c6..9ec44e77a 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -1683,6 +1683,9 @@ Qt::WidgetWithChildrenShortcut + + false + removeIconSmall @@ -1704,6 +1707,9 @@ Qt::WidgetWithChildrenShortcut + + false + removeIconSmall diff --git a/UI/forms/OBSBasicFilters.ui b/UI/forms/OBSBasicFilters.ui index 8f3c157f6..3594ac174 100644 --- a/UI/forms/OBSBasicFilters.ui +++ b/UI/forms/OBSBasicFilters.ui @@ -597,6 +597,9 @@ Del + + false + @@ -622,6 +625,11 @@ Ctrl+Down + + + Rename + + diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index e0b9d114a..3bf50798c 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -3238,7 +3238,8 @@ void OBSApp::ProcessSigInt(void) recv(sigintFd[1], &tmp, sizeof(tmp), 0); OBSBasic *main = reinterpret_cast(GetMainWindow()); - main->close(); + if (main) + main->close(); #endif } diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index b69f31023..9a4f41538 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -88,19 +88,33 @@ Q_DECLARE_METATYPE(media_frames_per_second); void OBSPropertiesView::ReloadProperties() { + deferUpdate = false; if (weakObj || rawObj) { OBSObject strongObj = GetObject(); void *obj = strongObj ? strongObj.Get() : rawObj; - if (obj) + if (obj) { properties.reset(reloadCallback(obj)); + + if (obs_obj_get_type(obj) == OBS_OBJ_TYPE_SOURCE) { + enum obs_source_type type = obs_source_get_type( + (obs_source_t *)obj); + if (type == OBS_SOURCE_TYPE_INPUT || + type == OBS_SOURCE_TYPE_TRANSITION) { + uint32_t flags = + obs_properties_get_flags( + properties.get()); + deferUpdate = + (flags & + OBS_PROPERTIES_DEFER_UPDATE) != + 0; + } + } + } } else { properties.reset(reloadCallback((void *)type.c_str())); obs_properties_apply_settings(properties.get(), settings); } - uint32_t flags = obs_properties_get_flags(properties.get()); - deferUpdate = (flags & OBS_PROPERTIES_DEFER_UPDATE) != 0; - RefreshProperties(); } @@ -633,7 +647,7 @@ QWidget *OBSPropertiesView::AddList(obs_property_t *prop, bool &warning) children.emplace_back(info); /* trigger a settings update if the index was not found */ - if (idx == -1) + if (count && idx == -1) info->ControlChanged(); return combo; diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index c4741ab0b..0ec2288b5 100644 --- a/UI/volume-control.cpp +++ b/UI/volume-control.cpp @@ -37,8 +37,11 @@ static inline Qt::CheckState GetCheckState(bool muted, bool unassigned) static inline bool IsSourceUnassigned(obs_source_t *source) { - return (obs_source_get_audio_mixers(source) & - ((1 << MAX_AUDIO_MIXES) - 1)) == 0; + uint32_t mixes = (obs_source_get_audio_mixers(source) & + ((1 << MAX_AUDIO_MIXES) - 1)); + obs_monitoring_type mt = obs_source_get_monitoring_type(source); + + return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY; } static void ShowUnassignedWarning(const char *name) @@ -116,19 +119,19 @@ void VolControl::VolumeMuted(bool muted) volMeter->muted = muted || unassigned; } -void VolControl::OBSMixersChanged(void *data, calldata_t *calldata) +void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *) { - VolControl *volControl = static_cast(data); - bool unassigned = (calldata_int(calldata, "mixers") & - ((1 << MAX_AUDIO_MIXES) - 1)) == 0; - QMetaObject::invokeMethod(volControl, "AssignmentChanged", - Q_ARG(bool, unassigned)); + VolControl *volControl = static_cast(data); + QMetaObject::invokeMethod(volControl, "MixersOrMonitoringChanged", + Qt::QueuedConnection); } -void VolControl::AssignmentChanged(bool unassigned) +void VolControl::MixersOrMonitoringChanged() { bool muted = obs_source_muted(source); + bool unassigned = IsSourceUnassigned(source); + auto newState = GetCheckState(muted, unassigned); if (mute->checkState() != newState) mute->setCheckState(newState); @@ -377,7 +380,11 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) signal_handler_connect(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); signal_handler_connect(obs_source_get_signal_handler(source), - "audio_mixers", OBSMixersChanged, this); + "audio_mixers", OBSMixersOrMonitoringChanged, + this); + signal_handler_connect(obs_source_get_signal_handler(source), + "audio_monitoring", OBSMixersOrMonitoringChanged, + this); QWidget::connect(slider, SIGNAL(valueChanged(int)), this, SLOT(SliderChanged(int))); @@ -416,7 +423,11 @@ VolControl::~VolControl() signal_handler_disconnect(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); signal_handler_disconnect(obs_source_get_signal_handler(source), - "audio_mixers", OBSMixersChanged, this); + "audio_mixers", OBSMixersOrMonitoringChanged, + this); + signal_handler_disconnect(obs_source_get_signal_handler(source), + "audio_monitoring", + OBSMixersOrMonitoringChanged, this); obs_fader_destroy(obs_fader); obs_volmeter_destroy(obs_volmeter); diff --git a/UI/volume-control.hpp b/UI/volume-control.hpp index cb31bcb12..b8eaaac05 100644 --- a/UI/volume-control.hpp +++ b/UI/volume-control.hpp @@ -301,14 +301,14 @@ private: const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); static void OBSVolumeMuted(void *data, calldata_t *calldata); - static void OBSMixersChanged(void *data, calldata_t *alldata); + static void OBSMixersOrMonitoringChanged(void *data, calldata_t *); void EmitConfigClicked(); private slots: void VolumeChanged(); void VolumeMuted(bool muted); - void AssignmentChanged(bool unassigned); + void MixersOrMonitoringChanged(); void SetMuted(bool checked); void SliderChanged(int vol); diff --git a/UI/win-dll-blocklist.c b/UI/win-dll-blocklist.c index 9ed19cab6..4cba6d932 100644 --- a/UI/win-dll-blocklist.c +++ b/UI/win-dll-blocklist.c @@ -124,8 +124,9 @@ static blocked_module_t blocked_modules[] = { // Wacom / Other tablet driver, locks up UI {L"\\wintab32.dll", 0, 0, TS_IGNORE}, - // Adobe Dynamic Link (Adobe CC), crashes in its own thread - {L"\\mc_trans_video_imagescaler.dll", 0, 0, TS_IGNORE}, + // MainConcept Image Scaler, crashes in its own thread. Block versions + // older than the one Elgato uses (2016-02-15). + {L"\\mc_trans_video_imagescaler.dll", 0, 1455495131, TS_LESS_THAN}, // Weird Polish banking "security" software, breaks UI {L"\\wslbscr64.dll", 0, 0, TS_IGNORE}, diff --git a/UI/window-basic-about.cpp b/UI/window-basic-about.cpp index 2bbc216aa..90e146d34 100644 --- a/UI/window-basic-about.cpp +++ b/UI/window-basic-about.cpp @@ -133,8 +133,9 @@ void OBSAbout::ShowAbout() void OBSAbout::ShowAuthors() { std::string path; - QString error = "Error! File could not be read.\n\n \ - Go to: https://github.com/obsproject/obs-studio/blob/master/AUTHORS"; + QString error = + QTStr("About.Error") + .arg("https://github.com/obsproject/obs-studio/blob/master/AUTHORS"); #ifdef __APPLE__ if (!GetDataFilePath("AUTHORS", path)) { @@ -160,8 +161,9 @@ void OBSAbout::ShowAuthors() void OBSAbout::ShowLicense() { std::string path; - QString error = "Error! File could not be read.\n\n \ - Go to: https://github.com/obsproject/obs-studio/blob/master/COPYING"; + QString error = + QTStr("About.Error") + .arg("https://github.com/obsproject/obs-studio/blob/master/COPYING"); if (!GetDataFilePath("license/gplv2.txt", path)) { ui->textBrowser->setPlainText(error); diff --git a/UI/window-basic-filters.cpp b/UI/window-basic-filters.cpp index 5a9c023df..92fc385d4 100644 --- a/UI/window-basic-filters.cpp +++ b/UI/window-basic-filters.cpp @@ -81,6 +81,7 @@ OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_) QApplication::translate("OBSBasicFilters", "Del", nullptr)); #endif // QT_NO_SHORTCUT + addAction(ui->actionRenameFilter); addAction(ui->actionRemoveFilter); addAction(ui->actionMoveUp); addAction(ui->actionMoveDown); @@ -154,24 +155,10 @@ OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_) ui->preview->hide(); } - QAction *renameAsync = new QAction(ui->asyncWidget); - renameAsync->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameAsync, SIGNAL(triggered()), this, - SLOT(RenameAsyncFilter())); - ui->asyncWidget->addAction(renameAsync); - - QAction *renameEffect = new QAction(ui->effectWidget); - renameEffect->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameEffect, SIGNAL(triggered()), this, - SLOT(RenameEffectFilter())); - ui->effectWidget->addAction(renameEffect); - #ifdef __APPLE__ - renameAsync->setShortcut({Qt::Key_Return}); - renameEffect->setShortcut({Qt::Key_Return}); + ui->actionRenameFilter->setShortcut({Qt::Key_Return}); #else - renameAsync->setShortcut({Qt::Key_F2}); - renameEffect->setShortcut({Qt::Key_F2}); + ui->actionRenameFilter->setShortcut({Qt::Key_F2}); #endif UpdateFilters(); @@ -932,6 +919,14 @@ void OBSBasicFilters::on_actionMoveDown_triggered() on_moveEffectFilterDown_clicked(); } +void OBSBasicFilters::on_actionRenameFilter_triggered() +{ + if (ui->asyncFilters->hasFocus()) + RenameAsyncFilter(); + else if (ui->effectFilters->hasFocus()) + RenameEffectFilter(); +} + void OBSBasicFilters::CustomContextMenu(const QPoint &pos, bool async) { QListWidget *list = async ? ui->asyncFilters : ui->effectFilters; @@ -948,17 +943,11 @@ void OBSBasicFilters::CustomContextMenu(const QPoint &pos, bool async) async ? SLOT(DuplicateAsyncFilter()) : SLOT(DuplicateEffectFilter()); - const char *renameSlot = async ? SLOT(RenameAsyncFilter()) - : SLOT(RenameEffectFilter()); - const char *removeSlot = - async ? SLOT(on_removeAsyncFilter_clicked()) - : SLOT(on_removeEffectFilter_clicked()); - popup.addSeparator(); popup.addAction(QTStr("Duplicate"), this, dulpicateSlot); popup.addSeparator(); - popup.addAction(QTStr("Rename"), this, renameSlot); - popup.addAction(QTStr("Remove"), this, removeSlot); + popup.addAction(ui->actionRenameFilter); + popup.addAction(ui->actionRemoveFilter); popup.addSeparator(); QAction *copyAction = new QAction(QTStr("Copy")); diff --git a/UI/window-basic-filters.hpp b/UI/window-basic-filters.hpp index 2a14e7931..e2bc8fa2a 100644 --- a/UI/window-basic-filters.hpp +++ b/UI/window-basic-filters.hpp @@ -113,6 +113,8 @@ private slots: void on_actionMoveUp_triggered(); void on_actionMoveDown_triggered(); + void on_actionRenameFilter_triggered(); + void AsyncFilterNameEdited(QWidget *editor); void EffectFilterNameEdited(QWidget *editor); diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 737ab966d..877c559f9 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -348,6 +348,7 @@ void BasicOutputHandler::UpdateVirtualCamOutputSource() switch (main->vcamConfig.type) { case VCamOutputType::InternalOutput: + DestroyVirtualCameraScene(); switch (main->vcamConfig.internal) { case VCamInternalType::Default: source = obs_get_output_source(0); @@ -360,10 +361,11 @@ void BasicOutputHandler::UpdateVirtualCamOutputSource() } break; case VCamOutputType::SceneOutput: + DestroyVirtualCameraScene(); source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); break; case VCamOutputType::SourceOutput: - OBSSource s = + OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str()); if (!vCamSourceScene) @@ -380,7 +382,6 @@ void BasicOutputHandler::UpdateVirtualCamOutputSource() if (!vCamSourceSceneItem) { vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); - obs_source_release(s); obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER); @@ -410,6 +411,11 @@ void BasicOutputHandler::DestroyVirtualCamView() obs_view_destroy(virtualCamView); virtualCamView = nullptr; + DestroyVirtualCameraScene(); +} + +void BasicOutputHandler::DestroyVirtualCameraScene() +{ if (!vCamSourceScene) return; @@ -1786,6 +1792,10 @@ inline void AdvancedOutput::SetupRecording() unsigned int cy = 0; int idx = 0; + /* Hack to allow recordings without any audio tracks selected. It is no + * longer possible to select such a configuration in settings, but legacy + * configurations might still have this configured and we don't want to + * just break them. */ if (tracks == 0) tracks = config_get_int(main->Config(), "AdvOut", "TrackIndex"); diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index a67a563e9..80d580e19 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -64,6 +64,7 @@ struct BasicOutputHandler { virtual void UpdateVirtualCamOutputSource(); virtual void DestroyVirtualCamView(); + virtual void DestroyVirtualCameraScene(); inline bool Active() const { diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 9f2e1354e..ffc19a7cc 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -426,7 +426,8 @@ void OBSBasic::SetTransition(OBSSource transition) ui->transitionRemove->setEnabled(configurable); ui->transitionProps->setEnabled(configurable); - if (vcamEnabled && vcamConfig.internal == VCamInternalType::Default) + if (vcamEnabled && vcamConfig.type == VCamOutputType::InternalOutput && + vcamConfig.internal == VCamInternalType::Default) outputHandler->UpdateVirtualCamOutputSource(); if (api) @@ -1475,6 +1476,7 @@ void OBSBasic::QuickTransitionChange() trIdx); if (tr) { qt->source = tr; + qt->fadeToBlack = fadeToBlack; ResetQuickTransitionText(qt); } } diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 8b0714ad7..ddd2a3983 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -392,12 +392,12 @@ OBSBasic::OBSBasic(QWidget *parent) connect(diskFullTimer, SIGNAL(timeout()), this, SLOT(CheckDiskSpaceRemaining())); - renameScene = new QAction(ui->scenesDock); + renameScene = new QAction(QTStr("Rename"), ui->scenesDock); renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(renameScene, SIGNAL(triggered()), this, SLOT(EditSceneName())); ui->scenesDock->addAction(renameScene); - renameSource = new QAction(ui->sourcesDock); + renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(renameSource, SIGNAL(triggered()), this, SLOT(EditSceneItemName())); @@ -1065,9 +1065,18 @@ static inline void AddMissingFiles(void *data, obs_source_t *source) void OBSBasic::LoadData(obs_data_t *data, const char *file) { ClearSceneData(); - InitDefaultTransitions(); ClearContextBar(); + /* Exit OBS if clearing scene data failed for some reason. */ + if (clearingFailed) { + OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), + QTStr("SourceLeak.Text")); + close(); + return; + } + + InitDefaultTransitions(); + if (devicePropertiesThread && devicePropertiesThread->isRunning()) { devicePropertiesThread->wait(); devicePropertiesThread.reset(); @@ -1271,7 +1280,7 @@ retryScene: disableSaving--; - if (vcamEnabled && vcamConfig.internal == VCamInternalType::Preview) + if (vcamEnabled) outputHandler->UpdateVirtualCamOutputSource(); if (api) { @@ -2784,6 +2793,9 @@ OBSBasic::~OBSBasic() if (updateCheckThread && updateCheckThread->isRunning()) updateCheckThread->wait(); + if (patronJsonThread && patronJsonThread->isRunning()) + patronJsonThread->wait(); + delete screenshotData; delete previewProjector; delete studioProgramProjector; @@ -4848,6 +4860,14 @@ void OBSBasic::ClearSceneData() for (int i = 0; i < MAX_CHANNELS; i++) obs_set_output_source(i, nullptr); + /* Reset VCam to default to clear its private scene and any references + * it holds. It will be reconfigured during loading. */ + if (vcamEnabled) { + vcamConfig.type = VCamOutputType::InternalOutput; + vcamConfig.internal = VCamInternalType::Default; + outputHandler->UpdateVirtualCamOutputSource(); + } + lastScene = nullptr; swapScene = nullptr; programScene = nullptr; @@ -4878,12 +4898,52 @@ void OBSBasic::ClearSceneData() QApplication::sendPostedEvents(nullptr); } while (obs_wait_for_destroy_queue()); + /* Pump Qt events one final time to give remaining signals time to be + * processed (since this happens after the destroy thread finishes and + * the audio/video threads have processed their tasks). */ + QApplication::sendPostedEvents(nullptr); + unsetCursor(); - disableSaving--; + /* If scene data wasn't actually cleared, e.g. faulty plugin holding a + * reference, they will still be in the hash table, enumerate them and + * store the names for logging purposes. */ + auto cb2 = [](void *param, obs_source_t *source) { + auto orphans = static_cast *>(param); + orphans->push_back(obs_source_get_name(source)); + return true; + }; - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); + vector orphan_sources; + obs_enum_sources(cb2, &orphan_sources); + + if (!orphan_sources.empty()) { + /* Avoid logging list twice in case it gets called after + * setting the flag the first time. */ + if (!clearingFailed) { + /* This ugly mess exists to join a vector of strings + * with a user-defined delimiter. */ + string orphan_names = std::accumulate( + orphan_sources.begin(), orphan_sources.end(), + string(""), [](string a, string b) { + return std::move(a) + "\n- " + b; + }); + + blog(LOG_ERROR, + "Not all sources were cleared when clearing scene data:\n%s\n", + orphan_names.c_str()); + } + + /* We do not decrement disableSaving here to avoid OBS + * overwriting user data with garbage. */ + clearingFailed = true; + } else { + disableSaving--; + + blog(LOG_INFO, "All scene data cleared"); + blog(LOG_INFO, + "------------------------------------------------"); + } } void OBSBasic::closeEvent(QCloseEvent *event) @@ -4914,7 +4974,8 @@ void OBSBasic::closeEvent(QCloseEvent *event) bool confirmOnExit = config_get_bool(GetGlobalConfig(), "General", "ConfirmOnExit"); - if (confirmOnExit && outputHandler && outputHandler->Active()) { + if (confirmOnExit && outputHandler && outputHandler->Active() && + !clearingFailed) { SetShowing(true); QMessageBox::StandardButton button = OBSMessageBox::question( @@ -5229,7 +5290,8 @@ void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, SetCurrentScene(source); - if (vcamEnabled && vcamConfig.internal == VCamInternalType::Preview) + if (vcamEnabled && vcamConfig.type == VCamOutputType::InternalOutput && + vcamConfig.internal == VCamInternalType::Preview) outputHandler->UpdateVirtualCamOutputSource(); if (api) @@ -5323,9 +5385,8 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) popup.addAction(copyFilters); popup.addAction(pasteFilters); popup.addSeparator(); - popup.addAction(QTStr("Rename"), this, SLOT(EditSceneName())); - popup.addAction(QTStr("Remove"), this, - SLOT(RemoveSelectedScene())); + popup.addAction(renameScene); + popup.addAction(ui->actionRemoveScene); popup.addSeparator(); order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, @@ -5831,10 +5892,8 @@ void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) colorSelect = new ColorSelect(colorMenu); popup.addMenu(AddBackgroundColorMenu( colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(QTStr("Rename"), this, - SLOT(EditSceneItemName())); - popup.addAction(QTStr("Remove"), this, - SLOT(on_actionRemoveSource_triggered())); + popup.addAction(renameSource); + popup.addAction(ui->actionRemoveSource); popup.addSeparator(); popup.addMenu(ui->orderMenu); @@ -9127,7 +9186,7 @@ void OBSBasic::UpdateTitleBar() name << App()->GetVersionString(false); if (App()->IsPortableMode()) - name << " - Portable Mode"; + name << " - " << Str("TitleBar.PortableMode"); name << " - " << Str("TitleBar.Profile") << ": " << profile; name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; @@ -9306,11 +9365,13 @@ void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) void OBSBasic::ShowContextBar() { on_toggleContextBar_toggled(true); + ui->toggleContextBar->setChecked(true); } void OBSBasic::HideContextBar() { on_toggleContextBar_toggled(false); + ui->toggleContextBar->setChecked(false); } void OBSBasic::on_toggleContextBar_toggled(bool visible) diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index cbce69832..55abea8fe 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -245,6 +245,8 @@ private: OBSWeakSourceAutoRelease copySourceTransition; bool closing = false; + bool clearingFailed = false; + QScopedPointer devicePropertiesThread; QScopedPointer whatsNewInitThread; QScopedPointer updateCheckThread; diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index 7a03f6314..64104c729 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -1492,9 +1492,17 @@ void OBSBasicPreview::StretchItem(const vec2 &pos) obs_source_t *source = obs_sceneitem_get_source(stretchItem); + uint32_t source_cx = obs_source_get_width(source); + uint32_t source_cy = obs_source_get_height(source); + + /* if the source's internal size has been set to 0 for whatever reason + * while resizing, do not update transform, otherwise source will be + * stuck invisible until a complete transform reset */ + if (!source_cx || !source_cy) + return; + vec2 baseSize; - vec2_set(&baseSize, float(obs_source_get_width(source)), - float(obs_source_get_height(source))); + vec2_set(&baseSize, float(source_cx), float(source_cy)); vec2 size; vec2_set(&size, br.x - tl.x, br.y - tl.y); diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index f6e0817a0..e8bb33c5e 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -566,7 +566,10 @@ QString OBSBasicSettings::FindProtocol() obs_properties_destroy(props); - return QT_UTF8(obs_data_get_string(settings, "protocol")); + const char *protocol = + obs_data_get_string(settings, "protocol"); + if (protocol && *protocol) + return QT_UTF8(protocol); } return QString("RTMP"); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 643aa0eac..581493978 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -3853,14 +3853,8 @@ void OBSBasicSettings::SaveOutputSettings() SaveCheckBox(ui->simpleReplayBuf, "SimpleOutput", "RecRB"); SaveSpinBox(ui->simpleRBSecMax, "SimpleOutput", "RecRBTime"); SaveSpinBox(ui->simpleRBMegsMax, "SimpleOutput", "RecRBSize"); - config_set_int( - main->Config(), "SimpleOutput", "RecTracks", - (ui->simpleOutRecTrack1->isChecked() ? (1 << 0) : 0) | - (ui->simpleOutRecTrack2->isChecked() ? (1 << 1) : 0) | - (ui->simpleOutRecTrack3->isChecked() ? (1 << 2) : 0) | - (ui->simpleOutRecTrack4->isChecked() ? (1 << 3) : 0) | - (ui->simpleOutRecTrack5->isChecked() ? (1 << 4) : 0) | - (ui->simpleOutRecTrack6->isChecked() ? (1 << 5) : 0)); + config_set_int(main->Config(), "SimpleOutput", "RecTracks", + SimpleOutGetSelectedAudioTracks()); curAdvStreamEncoder = GetComboData(ui->advOutEncoder); @@ -3892,14 +3886,8 @@ void OBSBasicSettings::SaveOutputSettings() SaveSpinBox(ui->advOutSplitFileTime, "AdvOut", "RecSplitFileTime"); SaveSpinBox(ui->advOutSplitFileSize, "AdvOut", "RecSplitFileSize"); - config_set_int( - main->Config(), "AdvOut", "RecTracks", - (ui->advOutRecTrack1->isChecked() ? (1 << 0) : 0) | - (ui->advOutRecTrack2->isChecked() ? (1 << 1) : 0) | - (ui->advOutRecTrack3->isChecked() ? (1 << 2) : 0) | - (ui->advOutRecTrack4->isChecked() ? (1 << 3) : 0) | - (ui->advOutRecTrack5->isChecked() ? (1 << 4) : 0) | - (ui->advOutRecTrack6->isChecked() ? (1 << 5) : 0)); + config_set_int(main->Config(), "AdvOut", "RecTracks", + AdvOutGetSelectedAudioTracks()); config_set_int(main->Config(), "AdvOut", "FLVTrack", CurrentFLVTrack()); @@ -4219,6 +4207,7 @@ bool OBSBasicSettings::QueryAllowedToClose() bool invalidEncoder = false; bool invalidFormat = false; + bool invalidTracks = false; if (simple) { if (ui->simpleOutRecEncoder->currentIndex() == -1 || ui->simpleOutStrEncoder->currentIndex() == -1 || @@ -4228,12 +4217,24 @@ bool OBSBasicSettings::QueryAllowedToClose() if (ui->simpleOutRecFormat->currentIndex() == -1) invalidFormat = true; + + QString qual = + ui->simpleOutRecQuality->currentData().toString(); + QString format = + ui->simpleOutRecFormat->currentData().toString(); + if (SimpleOutGetSelectedAudioTracks() == 0 && + qual != "Stream" && format != "flv") + invalidTracks = true; } else { if (ui->advOutRecEncoder->currentIndex() == -1 || ui->advOutEncoder->currentIndex() == -1 || ui->advOutRecAEncoder->currentIndex() == -1 || ui->advOutAEncoder->currentIndex() == -1) invalidEncoder = true; + + QString format = ui->advOutRecFormat->currentData().toString(); + if (AdvOutGetSelectedAudioTracks() == 0 && format != "flv") + invalidTracks = true; } if (invalidEncoder) { @@ -4246,6 +4247,12 @@ bool OBSBasicSettings::QueryAllowedToClose() this, QTStr("CodecCompat.ContainerMissingOnExit.Title"), QTStr("CodecCompat.ContainerMissingOnExit.Text")); return false; + } else if (invalidTracks) { + OBSMessageBox::warning( + this, + QTStr("OutputWarnings.NoTracksSelectedOnExit.Title"), + QTStr("OutputWarnings.NoTracksSelectedOnExit.Text")); + return false; } return true; @@ -4636,17 +4643,15 @@ void OBSBasicSettings::AudioChangedRestart() if (currentChannelIndex != channelIndex || currentSampleRateIndex != sampleRateIndex || currentLLAudioBufVal != llBufferingEnabled) { - audioChanged = true; ui->audioMsg->setText( QTStr("Basic.Settings.ProgramRestart")); - sender()->setProperty("changed", QVariant(true)); - EnableApplyButton(true); } else { - audioChanged = false; ui->audioMsg->setText(""); - sender()->setProperty("changed", QVariant(false)); - EnableApplyButton(false); } + + audioChanged = true; + sender()->setProperty("changed", QVariant(true)); + EnableApplyButton(true); } } @@ -4980,8 +4985,8 @@ void OBSBasicSettings::AdvOutSplitFileChanged() } static const unordered_set builtin_codecs = { - "h264", "hevc", "av1", "prores", "aac", - "opus", "alac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}; + "h264", "hevc", "av1", "prores", "aac", "opus", + "alac", "flac", "pcm_s16le", "pcm_s24le", "pcm_f32le"}; static const unordered_map> codec_compat = { // Technically our muxer supports HEVC and AV1 as well, but nothing else does @@ -6159,6 +6164,28 @@ int OBSBasicSettings::CurrentFLVTrack() return 0; } +int OBSBasicSettings::SimpleOutGetSelectedAudioTracks() +{ + int tracks = (ui->simpleOutRecTrack1->isChecked() ? (1 << 0) : 0) | + (ui->simpleOutRecTrack2->isChecked() ? (1 << 1) : 0) | + (ui->simpleOutRecTrack3->isChecked() ? (1 << 2) : 0) | + (ui->simpleOutRecTrack4->isChecked() ? (1 << 3) : 0) | + (ui->simpleOutRecTrack5->isChecked() ? (1 << 4) : 0) | + (ui->simpleOutRecTrack6->isChecked() ? (1 << 5) : 0); + return tracks; +} + +int OBSBasicSettings::AdvOutGetSelectedAudioTracks() +{ + int tracks = (ui->advOutRecTrack1->isChecked() ? (1 << 0) : 0) | + (ui->advOutRecTrack2->isChecked() ? (1 << 1) : 0) | + (ui->advOutRecTrack3->isChecked() ? (1 << 2) : 0) | + (ui->advOutRecTrack4->isChecked() ? (1 << 3) : 0) | + (ui->advOutRecTrack5->isChecked() ? (1 << 4) : 0) | + (ui->advOutRecTrack6->isChecked() ? (1 << 5) : 0); + return tracks; +} + /* Using setEditable(true) on a QComboBox when there's a custom style in use * does not work properly, so instead completely recreate the widget, which * seems to work fine. */ diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 6c91532ae..dd466f373 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -375,6 +375,8 @@ private: QIcon GetAdvancedIcon() const; int CurrentFLVTrack(); + int SimpleOutGetSelectedAudioTracks(); + int AdvOutGetSelectedAudioTracks(); OBSService GetStream1Service(); diff --git a/UI/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp index 38308b5be..85edea1e8 100644 --- a/UI/window-basic-source-select.cpp +++ b/UI/window-basic-source-select.cpp @@ -367,6 +367,8 @@ static inline const char *GetSourceDisplayName(const char *id) { if (strcmp(id, "scene") == 0) return Str("Basic.Scene"); + else if (strcmp(id, "group") == 0) + return Str("Group"); const char *v_id = obs_get_latest_input_type_id(id); return obs_source_get_display_name(v_id); } diff --git a/UI/window-basic-transform.cpp b/UI/window-basic-transform.cpp index c77b93b73..7449e8475 100644 --- a/UI/window-basic-transform.cpp +++ b/UI/window-basic-transform.cpp @@ -246,8 +246,10 @@ void OBSBasicTransform::RefreshControls() obs_sceneitem_get_crop(item, &crop); obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)); - float height = float(obs_source_get_height(source)); + uint32_t source_cx = obs_source_get_width(source); + uint32_t source_cy = obs_source_get_height(source); + float width = float(source_cx); + float height = float(source_cy); int alignIndex = AlignToList(osi.alignment); int boundsAlignIndex = AlignToList(osi.bounds_alignment); @@ -260,6 +262,10 @@ void OBSBasicTransform::RefreshControls() ui->sizeY->setValue(osi.scale.y * height); ui->align->setCurrentIndex(alignIndex); + bool valid_size = source_cx != 0 && source_cy != 0; + ui->sizeX->setEnabled(valid_size); + ui->sizeY->setEnabled(valid_size); + ui->boundsType->setCurrentIndex(int(osi.bounds_type)); ui->boundsAlign->setCurrentIndex(boundsAlignIndex); ui->boundsWidth->setValue(osi.bounds.x); @@ -308,15 +314,23 @@ void OBSBasicTransform::OnControlChanged() return; obs_source_t *source = obs_sceneitem_get_source(item); - double width = double(obs_source_get_width(source)); - double height = double(obs_source_get_height(source)); + uint32_t source_cx = obs_source_get_width(source); + uint32_t source_cy = obs_source_get_height(source); + double width = double(source_cx); + double height = double(source_cy); obs_transform_info oti; + obs_sceneitem_get_info(item, &oti); + + /* do not scale a source if it has 0 width/height */ + if (source_cx != 0 && source_cy != 0) { + oti.scale.x = float(ui->sizeX->value() / width); + oti.scale.y = float(ui->sizeY->value() / height); + } + oti.pos.x = float(ui->positionX->value()); oti.pos.y = float(ui->positionY->value()); oti.rot = float(ui->rotation->value()); - oti.scale.x = float(ui->sizeX->value() / width); - oti.scale.y = float(ui->sizeY->value() / height); oti.alignment = listToAlign[ui->align->currentIndex()]; oti.bounds_type = (obs_bounds_type)ui->boundsType->currentIndex(); diff --git a/UI/window-missing-files.cpp b/UI/window-missing-files.cpp index b676b507e..f42dc07a6 100644 --- a/UI/window-missing-files.cpp +++ b/UI/window-missing-files.cpp @@ -164,6 +164,11 @@ void MissingFilesPathItemDelegate::handleBrowse(QWidget *container) container, QTStr("MissingFiles.SelectFile"), currentPath, nullptr); +#ifdef __APPLE__ + // TODO: Revisit when QTBUG-42661 is fixed + container->window()->raise(); +#endif + if (!newPath.isEmpty()) { container->setProperty(PATH_LIST_PROP, QStringList() << newPath); diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp index 174967467..f2ec7ca99 100644 --- a/UI/window-projector.cpp +++ b/UI/window-projector.cpp @@ -229,6 +229,10 @@ void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event) if (!transitionOnDoubleClick) return; + // Only MultiView projectors handle double click + if (this->type != ProjectorType::Multiview) + return; + OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window(); if (!main->IsPreviewProgramMode()) return; diff --git a/cmake/Modules/CompilerConfig.cmake b/cmake/Modules/CompilerConfig.cmake index 41c56da8b..849d19de7 100644 --- a/cmake/Modules/CompilerConfig.cmake +++ b/cmake/Modules/CompilerConfig.cmake @@ -100,6 +100,13 @@ else() endif() option(CALM_DEPRECATION "Keep deprecated-declarations as warnings" OFF) + #[[ + Note about -Wmaybe-uninitialized on GCC, this warning seems to be subject of various regressions and false positives. This + warning is set to not turn into an error. + + - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105562 for 12.1.0 + - https://github.com/obsproject/obs-studio/issues/8850 for 13.1.1 + ]] add_compile_options( -Werror -Wextra @@ -117,19 +124,13 @@ else() "$<$:-DDEBUG=1;-D_DEBUG=1>" "$<$:-Wnull-conversion;-fcolor-diagnostics;-Wno-error=shorten-64-to-32>" "$<$:-Wnull-conversion;-fcolor-diagnostics;-Wno-error=shorten-64-to-32>" - "$<$:-Wconversion-null>" + "$<$:-Wconversion-null;-Wno-error=maybe-uninitialized>" + "$<$:-Wno-error=maybe-uninitialized>" "$<$:-Wno-error=deprecated-declarations>") - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - # GCC on aarch64 emits type-limits warnings that do not appear on x86_64 - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") - add_compile_options(-Wno-error=type-limits) - endif() - - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105562 - if(CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "12.1.0") - add_compile_options(-Wno-error=maybe-uninitialized) - endif() + # GCC on aarch64 emits type-limits warnings that do not appear on x86_64 + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + add_compile_options(-Wno-error=type-limits) endif() if(OBS_CODESIGN_LINKER) diff --git a/deps/media-playback/media-playback/media.c b/deps/media-playback/media-playback/media.c index 1446643e6..a0d8ebd8e 100644 --- a/deps/media-playback/media-playback/media.c +++ b/deps/media-playback/media-playback/media.c @@ -293,6 +293,14 @@ bool mp_media_prepare_frames(mp_media_t *m) } } + /* kind of a cheap fix, but because a stinger might be + * interrupted and restart playback, the request_preload signal + * might happen when the current frame is invalid, so clear out + * these pointers to signify they're not valid. (the obsframe + * structure is only used in the media thread, so this isn't a + * threading issue) */ + m->obsframe.data[0] = NULL; + if (m->has_video && !mp_decode_frame(&m->v)) return false; if (m->has_audio && !mp_decode_frame(&m->a)) @@ -840,8 +848,11 @@ static inline bool mp_media_thread(mp_media_t *m) if (pause) continue; - if (preload_frame) + /* see note in mp_media_prepare_frames() for context on the + * pointer check */ + if (preload_frame && m->obsframe.data[0] && !is_active) { m->v_preload_cb(m->opaque, &m->obsframe); + } /* frames are ready */ if (is_active && !timeout) { diff --git a/deps/obs-scripting/obs-scripting-lua.c b/deps/obs-scripting/obs-scripting-lua.c index 6f0d7222c..042b9cd7d 100644 --- a/deps/obs-scripting/obs-scripting-lua.c +++ b/deps/obs-scripting/obs-scripting-lua.c @@ -129,7 +129,8 @@ static bool load_lua_script(struct obs_lua_script *data) goto fail; } - if (luaL_loadbuffer(script, file_data, strlen(file_data), NULL) != 0) { + if (luaL_loadbuffer(script, file_data, strlen(file_data), + data->base.path.array) != 0) { script_warn(&data->base, "Error loading file: %s", lua_tostring(script, -1)); bfree(file_data); @@ -1227,10 +1228,13 @@ void obs_lua_script_unload(obs_script_t *s) /* call script_unload */ pthread_mutex_lock(&data->mutex); + current_lua_script = data; lua_getglobal(script, "script_unload"); lua_pcall(script, 0, 0, 0); + current_lua_script = NULL; + /* ---------------------------- */ /* remove all callbacks */ diff --git a/docs/sphinx/reference-libobs-util-darray.rst b/docs/sphinx/reference-libobs-util-darray.rst index 09365b4d2..cebace95a 100644 --- a/docs/sphinx/reference-libobs-util-darray.rst +++ b/docs/sphinx/reference-libobs-util-darray.rst @@ -236,7 +236,8 @@ dynamic array value with a reference (&) operator. For example: .. function:: void da_erase_range(da, size_t start_idx, size_t end_idx) - Erases a range of values. + Erases a range of values, including the element at ``start_idx``, but + not the one at ``end_idx``. :param da: The dynamic array :param start_idx: The starting index diff --git a/docs/sphinx/reference-properties.rst b/docs/sphinx/reference-properties.rst index fc3c3e43c..19a6d1d74 100644 --- a/docs/sphinx/reference-properties.rst +++ b/docs/sphinx/reference-properties.rst @@ -51,7 +51,9 @@ General Functions - OBS_PROPERTIES_DEFER_UPDATE - A hint that tells the front-end to defers updating the settings until the user has finished editing all properties rather than - immediately updating any settings + immediately updating any settings. Currently only + works for properties of input and transition sources, + this flag is a no-op for other properties at this time. --------------------- diff --git a/docs/sphinx/reference-services.rst b/docs/sphinx/reference-services.rst index 192022670..7085742cb 100644 --- a/docs/sphinx/reference-services.rst +++ b/docs/sphinx/reference-services.rst @@ -186,6 +186,8 @@ Service Definition Structure .. member:: bool (*obs_service_info.can_try_to_connect)(void *data) + (Optional) + :return: If the service has all the needed connection info to be able to connect. @@ -380,12 +382,16 @@ General Service Functions :return: The output type that should be preferred with this service +--------------------- + .. function:: const char *obs_service_get_connect_info(const obs_service_t *service, uint32_t type) :param type: Check :c:member:`obs_service_info.get_connect_info` for type values. :return: Connection info related to the type value. +--------------------- + .. function:: bool obs_service_can_try_to_connect(const obs_service_t *service) :return: If the service has all the needed connection info to be diff --git a/libobs-d3d11/d3d11-subsystem.cpp b/libobs-d3d11/d3d11-subsystem.cpp index 6e7e0e334..52518a7f7 100644 --- a/libobs-d3d11/d3d11-subsystem.cpp +++ b/libobs-d3d11/d3d11-subsystem.cpp @@ -26,6 +26,7 @@ #include #include "d3d11-subsystem.hpp" #include +#include struct UnsupportedHWError : HRError { inline UnsupportedHWError(const char *str, HRESULT hr) @@ -472,39 +473,18 @@ static bool increase_maximum_frame_latency(ID3D11Device *device) } #if USE_GPU_PRIORITY -static bool set_priority(ID3D11Device *device) +static bool set_priority(ID3D11Device *device, bool hags_enabled) { - typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { - D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, - D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, - D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME - } D3DKMT_SCHEDULINGPRIORITYCLASS; - ComQIPtr dxgiDevice(device); if (!dxgiDevice) { blog(LOG_DEBUG, "%s: Failed to get IDXGIDevice", __FUNCTION__); return false; } - HMODULE gdi32 = GetModuleHandleW(L"GDI32"); - if (!gdi32) { - blog(LOG_DEBUG, "%s: Failed to get GDI32", __FUNCTION__); - return false; - } - - NTSTATUS(WINAPI * d3dkmt_spspc)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); - d3dkmt_spspc = (decltype(d3dkmt_spspc))GetProcAddress( - gdi32, "D3DKMTSetProcessSchedulingPriorityClass"); - if (!d3dkmt_spspc) { - blog(LOG_DEBUG, "%s: Failed to get d3dkmt_spspc", __FUNCTION__); - return false; - } - - NTSTATUS status = d3dkmt_spspc(GetCurrentProcess(), - D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME); + NTSTATUS status = D3DKMTSetProcessSchedulingPriorityClass( + GetCurrentProcess(), + hags_enabled ? D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH + : D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME); if (status != 0) { blog(LOG_DEBUG, "%s: Failed to set process priority class: %d", __FUNCTION__, (int)status); @@ -521,9 +501,45 @@ static bool set_priority(ID3D11Device *device) blog(LOG_INFO, "D3D11 GPU priority setup success"); return true; } - #endif +static bool adapter_hags_enabled(DXGI_ADAPTER_DESC *desc) +{ + D3DKMT_OPENADAPTERFROMLUID d3dkmt_openluid{}; + bool hags_enabled = false; + NTSTATUS res; + + d3dkmt_openluid.AdapterLuid = desc->AdapterLuid; + + res = D3DKMTOpenAdapterFromLuid(&d3dkmt_openluid); + if (FAILED(res)) { + blog(LOG_DEBUG, "Failed opening D3DKMT adapter: %x", res); + return hags_enabled; + } + + D3DKMT_WDDM_2_7_CAPS caps = {}; + D3DKMT_QUERYADAPTERINFO args = {}; + args.hAdapter = d3dkmt_openluid.hAdapter; + args.Type = KMTQAITYPE_WDDM_2_7_CAPS; + args.pPrivateDriverData = ∩︀ + args.PrivateDriverDataSize = sizeof(caps); + res = D3DKMTQueryAdapterInfo(&args); + + /* On Windows 10 pre-2004 this will fail, but HAGS isn't supported + * there anyway. */ + if (SUCCEEDED(res)) + hags_enabled = caps.HwSchEnabled; + + D3DKMT_CLOSEADAPTER d3dkmt_close = {d3dkmt_openluid.hAdapter}; + res = D3DKMTCloseAdapter(&d3dkmt_close); + if (FAILED(res)) { + blog(LOG_DEBUG, "Failed closing D3DKMT adapter %x: %x", + d3dkmt_openluid.hAdapter, res); + } + + return hags_enabled; +} + static bool CheckFormat(ID3D11Device *device, DXGI_FORMAT format) { constexpr UINT required = D3D11_FORMAT_SUPPORT_TEXTURE2D | @@ -573,9 +589,16 @@ void gs_device::InitDevice(uint32_t adapterIdx) blog(LOG_INFO, "DXGI increase maximum frame latency failed"); } + /* Log HAGS status */ + bool hags_enabled = adapter_hags_enabled(&desc); + if (hags_enabled) { + blog(LOG_WARNING, + "Hardware-Accelerated GPU Scheduling enabled on adapter!"); + } + /* adjust gpu thread priority on non-intel GPUs */ #if USE_GPU_PRIORITY - if (desc.VendorId != 0x8086 && !set_priority(device)) { + if (desc.VendorId != 0x8086 && !set_priority(device, hags_enabled)) { blog(LOG_INFO, "D3D11 GPU priority setup " "failed (not admin?)"); } diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index c5c0aace0..d2e26710d 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -283,6 +283,7 @@ set(public_headers obs-source.h obs-ui.h obs.h + obs.hpp util/base.h util/bmem.h util/c99defs.h diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c index c70ef468f..5293ea142 100644 --- a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c +++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c @@ -54,7 +54,6 @@ void get_default_id(char **id) } else { *id = bzalloc(strlen(pdo->default_sink_name) + 9); strcat(*id, pdo->default_sink_name); - strcat(*id, ".monitor"); bfree(pdo->default_sink_name); } @@ -62,9 +61,14 @@ void get_default_id(char **id) pulseaudio_unref(); } +/** + * Checks whether a sound source (id1) is the .monitor device for the + * selected monitoring output (id2). + */ bool devices_match(const char *id1, const char *id2) { bool match; + char *name_default = NULL; char *name1 = NULL; char *name2 = NULL; @@ -72,18 +76,28 @@ bool devices_match(const char *id1, const char *id2) return false; if (strcmp(id1, "default") == 0) { - get_default_id(&name1); - id1 = name1; + get_default_id(&name_default); + name1 = bzalloc(strlen(name_default) + 9); + strcat(name1, name_default); + strcat(name1, ".monitor"); + } else { + name1 = bstrdup(id1); } + if (strcmp(id2, "default") == 0) { - get_default_id(&name2); + if (!name_default) + get_default_id(&name_default); + name2 = bzalloc(strlen(name_default) + 9); + strcat(name2, name_default); + strcat(name2, ".monitor"); } else { name2 = bzalloc(strlen(id2) + 9); strcat(name2, id2); strcat(name2, ".monitor"); } - match = strcmp(id1, name2) == 0; + match = strcmp(name1, name2) == 0; + bfree(name_default); bfree(name1); bfree(name2); return match; diff --git a/libobs/data/format_conversion.effect b/libobs/data/format_conversion.effect index 40284a7b3..734f14032 100644 --- a/libobs/data/format_conversion.effect +++ b/libobs/data/format_conversion.effect @@ -153,12 +153,12 @@ VertTexTexPos VSPacked422Left_Reverse(uint id : VERTEXID) float x = idHigh * 4. - 1.; float y = idLow * 4. - 1.; - float u = idHigh * 2. + width_x2_i; + float u = idHigh * 2.; float v = idLow * 2.; v = obs_glsl_compile ? v : (1. - v); VertTexTexPos vert_out; - vert_out.uvuv = float4(width_d2 * u, height * v, u, v); + vert_out.uvuv = float4(width_d2 * u, height * v, u + width_x2_i, v); vert_out.pos = float4(x, y, 0., 1.); return vert_out; } diff --git a/libobs/obs-config.h b/libobs/obs-config.h index 63372a2af..4ea959779 100644 --- a/libobs/obs-config.h +++ b/libobs/obs-config.h @@ -41,7 +41,7 @@ * * Reset to zero each major or minor version */ -#define LIBOBS_API_PATCH_VER 0 +#define LIBOBS_API_PATCH_VER 3 #define MAKE_SEMANTIC_VERSION(major, minor, patch) \ ((major << 24) | (minor << 16) | patch) diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 1ea555c3a..ebb56975f 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -419,6 +419,7 @@ struct obs_core_data { volatile bool valid; DARRAY(char *) protocols; + DARRAY(obs_source_t *) sources_to_tick; }; /* user hotkeys */ diff --git a/libobs/obs-properties.c b/libobs/obs-properties.c index ab683ed1d..dc2653dc2 100644 --- a/libobs/obs-properties.c +++ b/libobs/obs-properties.c @@ -363,9 +363,9 @@ void obs_properties_apply_settings_internal(obs_properties_t *props, obs_data_t *settings, obs_properties_t *realprops) { - struct obs_property *p, *tmp; + struct obs_property *p = props->properties; - HASH_ITER (hh, props->properties, p, tmp) { + while (p) { if (p->type == OBS_PROPERTY_GROUP) { obs_properties_apply_settings_internal( obs_property_group_content(p), settings, @@ -375,6 +375,8 @@ void obs_properties_apply_settings_internal(obs_properties_t *props, p->modified(realprops, p, settings); else if (p->modified2) p->modified2(p->priv, realprops, p, settings); + + p = p->hh.next; } } diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 0744374cc..530c2f04a 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -2732,7 +2732,9 @@ void obs_sceneitem_set_info(obs_sceneitem_t *item, if (item && info) { item->pos = info->pos; item->rot = info->rot; - item->scale = info->scale; + if (isfinite(info->scale.x) && isfinite(info->scale.y)) { + item->scale = info->scale; + } item->align = info->alignment; item->bounds_type = info->bounds_type; item->bounds_align = info->bounds_alignment; diff --git a/libobs/obs-video.c b/libobs/obs-video.c index 60acaaf6f..4732d2d0f 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -45,35 +45,42 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) /* ------------------------------------- */ /* call tick callbacks */ - pthread_mutex_lock(&obs->data.draw_callbacks_mutex); + pthread_mutex_lock(&data->draw_callbacks_mutex); - for (size_t i = obs->data.tick_callbacks.num; i > 0; i--) { + for (size_t i = data->tick_callbacks.num; i > 0; i--) { struct tick_callback *callback; - callback = obs->data.tick_callbacks.array + (i - 1); + callback = data->tick_callbacks.array + (i - 1); callback->tick(callback->param, seconds); } - pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); + pthread_mutex_unlock(&data->draw_callbacks_mutex); /* ------------------------------------- */ - /* call the tick function of each source */ + /* get an array of all sources to tick */ + + da_clear(data->sources_to_tick); pthread_mutex_lock(&data->sources_mutex); source = data->sources; while (source) { obs_source_t *s = obs_source_get_ref(source); - - if (s) { - obs_source_video_tick(s, seconds); - obs_source_release(s); - } - + if (s) + da_push_back(data->sources_to_tick, &s); source = (struct obs_source *)source->context.hh_uuid.next; } pthread_mutex_unlock(&data->sources_mutex); + /* ------------------------------------- */ + /* call the tick function of each source */ + + for (size_t i = 0; i < data->sources_to_tick.num; i++) { + obs_source_t *s = data->sources_to_tick.array[i]; + obs_source_video_tick(s, seconds); + obs_source_release(s); + } + return cur_time; } diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index 4d16b011c..373f09dc3 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -180,8 +180,6 @@ static void log_admin_status(void) L"SOFTWARE\\Policies\\Microsoft\\Windows\\GameDVR" #define WIN10_GAME_DVR_REG_KEY L"System\\GameConfigStore" #define WIN10_GAME_MODE_REG_KEY L"Software\\Microsoft\\GameBar" -#define WIN10_HAGS_REG_KEY \ - L"SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers" static void log_gaming_features(void) { @@ -193,7 +191,6 @@ static void log_gaming_features(void) struct reg_dword game_dvr_enabled; struct reg_dword game_dvr_bg_recording; struct reg_dword game_mode_enabled; - struct reg_dword hags_enabled; get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_BAR_REG_KEY, L"AppCaptureEnabled", &game_bar_enabled); @@ -205,8 +202,6 @@ static void log_gaming_features(void) L"HistoricalCaptureEnabled", &game_dvr_bg_recording); get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_MODE_REG_KEY, L"AutoGameModeEnabled", &game_mode_enabled); - get_reg_dword(HKEY_LOCAL_MACHINE, WIN10_HAGS_REG_KEY, L"HwSchMode", - &hags_enabled); if (game_mode_enabled.status != ERROR_SUCCESS) { get_reg_dword(HKEY_CURRENT_USER, WIN10_GAME_MODE_REG_KEY, @@ -241,15 +236,6 @@ static void log_gaming_features(void) // On by default in newer Windows 10 builds (no registry key set) blog(LOG_INFO, "\tGame Mode: Probably On (no reg key set)"); } - - if (hags_enabled.status == ERROR_SUCCESS) { - blog(LOG_INFO, "\tHardware GPU Scheduler: %s", - (hags_enabled.return_value == 2) ? "On" : "Off"); - } else if (win_build >= 22000) { - // On by default in Windows 11 (no registry key set) - blog(LOG_INFO, - "\tHardware GPU Scheduler: Probably On (no reg key set)"); - } } static const char *get_str_for_state(int state) diff --git a/libobs/obs.c b/libobs/obs.c index 1850ecc75..a19b9331b 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -748,7 +748,10 @@ static int obs_init_video(struct obs_video_info *ovi) int errorcode; #ifdef __APPLE__ - errorcode = pthread_create(&video->video_thread, NULL, + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INTERACTIVE, 0); + errorcode = pthread_create(&video->video_thread, &attr, obs_graphics_thread_autorelease, obs); #else errorcode = pthread_create(&video->video_thread, NULL, @@ -1083,6 +1086,7 @@ static void obs_free_data(void) for (size_t i = 0; i < data->protocols.num; i++) bfree(data->protocols.array[i]); da_free(data->protocols); + da_free(data->sources_to_tick); } static const char *obs_signals[] = { diff --git a/libobs/util/darray.h b/libobs/util/darray.h index 2b308eb22..dba5c5d15 100644 --- a/libobs/util/darray.h +++ b/libobs/util/darray.h @@ -121,6 +121,11 @@ static inline void darray_ensure_capacity(const size_t element_size, dst->capacity = new_cap; } +static inline void darray_clear(struct darray *dst) +{ + dst->num = 0; +} + static inline void darray_resize(const size_t element_size, struct darray *dst, const size_t size) { @@ -269,10 +274,10 @@ static inline void *darray_insert_new(const size_t element_size, if (idx == dst->num) return darray_push_back_new(element_size, dst); - item = darray_item(element_size, dst, idx); - move_count = dst->num - idx; darray_ensure_capacity(element_size, dst, ++dst->num); + + item = darray_item(element_size, dst, idx); memmove(darray_item(element_size, dst, idx + 1), item, move_count * element_size); @@ -487,6 +492,8 @@ static inline void darray_swap(const size_t element_size, struct darray *dst, #define da_resize(v, size) darray_resize(sizeof(*v.array), &v.da, size) +#define da_clear(v) darray_clear(&v.da) + #define da_copy(dst, src) darray_copy(sizeof(*dst.array), &dst.da, &src.da) #define da_copy_array(dst, src_array, n) \ diff --git a/plugins/linux-alsa/data/locale/en-US.ini b/plugins/linux-alsa/data/locale/en-US.ini index 45d864717..e8e45f87f 100644 --- a/plugins/linux-alsa/data/locale/en-US.ini +++ b/plugins/linux-alsa/data/locale/en-US.ini @@ -1,2 +1,4 @@ AlsaInput="Audio Capture Device (ALSA)" Device="Device" +PCM="PCM" +Rate="Rate" diff --git a/plugins/linux-capture/data/locale/en-US.ini b/plugins/linux-capture/data/locale/en-US.ini index 1872fdb9d..b4339ef4d 100644 --- a/plugins/linux-capture/data/locale/en-US.ini +++ b/plugins/linux-capture/data/locale/en-US.ini @@ -10,6 +10,5 @@ CropLeft="Crop Left" CropRight="Crop Right" CropBottom="Crop Bottom" SwapRedBlue="Swap red and blue" -LockX="Lock X server when capturing" IncludeXBorder="Include X Border" ExcludeAlpha="Use alpha-less texture format (Mesa workaround)" diff --git a/plugins/linux-pulseaudio/data/locale/en-US.ini b/plugins/linux-pulseaudio/data/locale/en-US.ini index 8b3c57c3c..43718c057 100644 --- a/plugins/linux-pulseaudio/data/locale/en-US.ini +++ b/plugins/linux-pulseaudio/data/locale/en-US.ini @@ -1,3 +1,4 @@ PulseInput="Audio Input Capture (PulseAudio)" PulseOutput="Audio Output Capture (PulseAudio)" Device="Device" +Default="Default" diff --git a/plugins/mac-avcapture/data/locale/en-US.ini b/plugins/mac-avcapture/data/locale/en-US.ini index 2fafea676..d6bd4a419 100644 --- a/plugins/mac-avcapture/data/locale/en-US.ini +++ b/plugins/mac-avcapture/data/locale/en-US.ini @@ -12,3 +12,4 @@ VideoRange.Full="Full" Auto="Auto" Unknown="Unknown (%1)" EnableAudio="Enable audio if supported by device" +Resolution="Resolution" diff --git a/plugins/mac-capture/mac-screen-capture.m b/plugins/mac-capture/mac-screen-capture.m index cc09437fa..615394824 100644 --- a/plugins/mac-capture/mac-screen-capture.m +++ b/plugins/mac-capture/mac-screen-capture.m @@ -297,6 +297,13 @@ static inline void screen_stream_audio_update(struct screen_capture *sc, CMAudioFormatDescriptionGetStreamBasicDescription( format_description); + if (audio_description->mChannelsPerFrame < 1) { + MACCAP_ERR( + "screen_stream_audio_update: Received sample buffer has less than 1 channel per frame (mChannelsPerFrame set to '%d')\n", + audio_description->mChannelsPerFrame); + return; + } + char *_Nullable bytes = NULL; CMBlockBufferRef data_buffer = CMSampleBufferGetDataBuffer(sample_buffer); @@ -448,9 +455,12 @@ static bool init_screen_stream(struct screen_capture *sc) } break; } os_sem_post(sc->shareable_content_available); + + CGColorRef background = CGColorGetConstantColor(kCGColorClear); [sc->stream_properties setQueueDepth:8]; [sc->stream_properties setShowsCursor:!sc->hide_cursor]; [sc->stream_properties setColorSpaceName:kCGColorSpaceDisplayP3]; + [sc->stream_properties setBackgroundColor:background]; FourCharCode l10r_type = 0; l10r_type = ('l' << 24) | ('1' << 16) | ('0' << 8) | 'r'; [sc->stream_properties setPixelFormat:l10r_type]; diff --git a/plugins/mac-syphon/data/locale/en-US.ini b/plugins/mac-syphon/data/locale/en-US.ini index 86550bea9..5ad5336ff 100644 --- a/plugins/mac-syphon/data/locale/en-US.ini +++ b/plugins/mac-syphon/data/locale/en-US.ini @@ -1,8 +1,5 @@ Syphon="Syphon Client" Source="Source" -LaunchSyphonInject="Launch SyphonInject" -Inject="Inject" -Application="Application" SyphonLicense="Syphon License" Crop="Crop" Crop.origin.x="Crop left" diff --git a/plugins/mac-videotoolbox/data/locale/en-US.ini b/plugins/mac-videotoolbox/data/locale/en-US.ini index 0610c325b..174865672 100644 --- a/plugins/mac-videotoolbox/data/locale/en-US.ini +++ b/plugins/mac-videotoolbox/data/locale/en-US.ini @@ -5,7 +5,6 @@ VTHEVCEncT2="Apple VT HEVC T2 Hardware Encoder" VTHEVCEncSW="Apple VT HEVC Software Encoder" VTProResEncHW="Apple VT ProRes Hardware Encoder" VTProResEncSW="Apple VT ProRes Software Encoder" -VTEncoder="VideoToolbox Encoder" Bitrate="Bitrate" Quality="Quality" UseMaxBitrate="Limit bitrate" diff --git a/plugins/mac-videotoolbox/encoder.c b/plugins/mac-videotoolbox/encoder.c index 9aa3e2b05..628408554 100644 --- a/plugins/mac-videotoolbox/encoder.c +++ b/plugins/mac-videotoolbox/encoder.c @@ -626,8 +626,9 @@ static bool create_encoder(struct vt_encoder *enc) kVTCompressionPropertyKey_AllowFrameReordering, kVTCompressionPropertyKey_ProfileLevel}; - float key_frame_interval = - enc->keyint * ((float)enc->fps_num / enc->fps_den); + SInt32 key_frame_interval = + (SInt32)(enc->keyint * + ((float)enc->fps_num / enc->fps_den)); float expected_framerate = (float)enc->fps_num / enc->fps_den; CFNumberRef MaxKeyFrameInterval = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index 0646c5f10..9652be160 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -1,4 +1,8 @@ FFmpegOutput="FFmpeg Output" +FFmpegMuxer="FFmpeg Muxer" +FFmpegHlsMuxer="FFmpeg HLS Muxer" +FFmpegMpegts="FFmpeg MPEG-TS" +FFmpegMpegtsMuxer="FFmpeg MPEG-TS Muxer" FFmpegAAC="FFmpeg AAC" FFmpegOpus="FFmpeg Opus" FFmpegALAC="FFmpeg ALAC (24-bit)" @@ -17,13 +21,14 @@ RateControl="Rate Control" KeyframeIntervalSec="Keyframe Interval (0=auto)" Lossless="Lossless" Level="Level" +FilePath="File Path" AMFOpts="AMF/FFmpeg Options" AMFOpts.ToolTip="Use to specify custom AMF or FFmpeg options. For example, \"level=5.2 profile=main\". Check the AMF encoder docs for more details." +GPU="GPU" BFrames="Max B-frames" -VAAPI.Codec="VAAPI Codec" VAAPI.Device="VAAPI Device" NVENC.LookAhead="Look-ahead" @@ -35,7 +40,7 @@ NVENC.8bitUnsupportedHdr="OBS does not support 8-bit output of Rec. 2100." NVENC.I010Unsupported="NVENC does not support I010. Use P010 instead." NVENC.10bitUnsupported="Cannot perform 10-bit encode on this encoder." NVENC.16bitUnsupported="Cannot perform 16-bit encode on this encoder." -NVENC.TooManyBFrames="Max B-frames setting (%d) is more than encoder supports (%d)." +NVENC.NoAV1FallbackPossible="AV1 encoding is not available with the current settings. Try disabling any re-scaling or GPU options that may be set. Check the log for more details." NVENC.Preset2.p1="P1: Fastest (Lowest Quality)" NVENC.Preset2.p2="P2: Faster (Lower Quality)" NVENC.Preset2.p3="P3: Fast (Low Quality)" @@ -43,6 +48,13 @@ NVENC.Preset2.p4="P4: Medium (Medium Quality)" NVENC.Preset2.p5="P5: Slow (Good Quality)" NVENC.Preset2.p6="P6: Slower (Better Quality)" NVENC.Preset2.p7="P7: Slowest (Best Quality)" +NVENC.Preset2.mq="MQ" +NVENC.Preset2.hq="HQ" +NVENC.Preset2.default="Default" +NVENC.Preset2.hp="HP" +NVENC.Preset2.ll="LL" +NVENC.Preset2.llhq="LLHQ" +NVENC.Preset2.llhp="LLHP" NVENC.Tuning.hq="High Quality" NVENC.Tuning.ll="Low Latency" NVENC.Tuning.ull="Ultra Low Latency" @@ -64,7 +76,6 @@ InputFormat="Input Format" BufferingMB="Network Buffering" HardwareDecode="Use hardware decoding when available" ClearOnMediaEnd="Show nothing when playback ends" -Advanced="Advanced" RestartWhenActivated="Restart playback when source becomes active" CloseFileWhenInactive="Close file when inactive" CloseFileWhenInactive.ToolTip="Closes the file when the source is not being displayed on the stream or\nrecording. This allows the file to be changed when the source isn't active,\nbut there may be some startup delay when the source reactivates." @@ -95,8 +106,6 @@ WarnWindowsDefender="If Windows 10 Ransomware Protection is enabled it can also Encoder.Error="Failed to open %1: %2" Encoder.Timeout="Encoder %1 is taking too long to encode (timeout: %2 seconds)" -AMF.Error="Failed to open AMF codec: %1" -AMF.GenericError="Try installing the latest AMD driver and closing other recording software that might be using the AMD encoder such as the Radeon Software or Windows Game DVR." AMF.8bitUnsupportedHdr="OBS does not support 8-bit output of Rec. 2100." AMF.10bitUnsupportedAvc="Cannot perform 10-bit encode on AMD H.264 encoder." AMF.16bitUnsupported="Cannot perform 16-bit encode on this encoder." diff --git a/plugins/obs-ffmpeg/jim-nvenc.c b/plugins/obs-ffmpeg/jim-nvenc.c index 10a7dd185..ff67ab7e7 100644 --- a/plugins/obs-ffmpeg/jim-nvenc.c +++ b/plugins/obs-ffmpeg/jim-nvenc.c @@ -1165,6 +1165,11 @@ reroute: case CODEC_HEVC: return obs_encoder_create_rerouted(encoder, "ffmpeg_hevc_nvenc"); + case CODEC_AV1: + obs_encoder_set_last_error( + encoder, + obs_module_text("NVENC.NoAV1FallbackPossible")); + break; } return NULL; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c index 79c7f3f4a..134efb5ec 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c @@ -287,6 +287,8 @@ static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) hevc_vaapi_video_info(enc, &info); } else +#else + UNUSED_PARAMETER(hevc); #endif { h264_vaapi_video_info(enc, &info); @@ -564,6 +566,8 @@ static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, &enc->header_size, &enc->sei, &enc->sei_size); } else +#else + UNUSED_PARAMETER(hevc); #endif { obs_extract_avc_headers( @@ -649,6 +653,8 @@ static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) FF_PROFILE_HEVC_MAIN); } else +#else + UNUSED_PARAMETER(hevc); #endif { obs_data_set_default_int(settings, "profile", diff --git a/plugins/obs-ffmpeg/texture-amf.cpp b/plugins/obs-ffmpeg/texture-amf.cpp index abd1d6f92..694156e32 100644 --- a/plugins/obs-ffmpeg/texture-amf.cpp +++ b/plugins/obs-ffmpeg/texture-amf.cpp @@ -104,6 +104,7 @@ struct amf_base { AMF_SURFACE_FORMAT amf_format; amf_int64 max_throughput = 0; + amf_int64 requested_throughput = 0; amf_int64 throughput = 0; int64_t dts_offset = 0; uint32_t cx; @@ -440,6 +441,8 @@ static inline void refresh_throughput_caps(amf_base *enc, const char *&preset) if (res == AMF_OK) { caps->GetProperty(get_opt_name(CAP_MAX_THROUGHPUT), &enc->max_throughput); + caps->GetProperty(get_opt_name(CAP_REQUESTED_THROUGHPUT), + &enc->requested_throughput); } } @@ -455,7 +458,8 @@ static inline void check_preset_compatibility(amf_base *enc, preset = "quality"; set_opt(QUALITY_PRESET, get_preset(enc, preset)); } else { - if (enc->max_throughput < enc->throughput) { + if (enc->max_throughput - enc->requested_throughput < + enc->throughput) { preset = "quality"; refresh_throughput_caps(enc, preset); } @@ -467,7 +471,8 @@ static inline void check_preset_compatibility(amf_base *enc, preset = "balanced"; set_opt(QUALITY_PRESET, get_preset(enc, preset)); } else { - if (enc->max_throughput < enc->throughput) { + if (enc->max_throughput - enc->requested_throughput < + enc->throughput) { preset = "balanced"; refresh_throughput_caps(enc, preset); } @@ -476,7 +481,8 @@ static inline void check_preset_compatibility(amf_base *enc, if (astrcmpi(preset, "balanced") == 0) { if (enc->max_throughput && - enc->max_throughput < enc->throughput) { + enc->max_throughput - enc->requested_throughput < + enc->throughput) { preset = "speed"; refresh_throughput_caps(enc, preset); } @@ -1254,13 +1260,17 @@ try { int64_t qp = obs_data_get_int(settings, "cqp"); const char *rc_str = obs_data_get_string(settings, "rate_control"); int rc = get_avc_rate_control(rc_str); - AMF_RESULT res; + AMF_RESULT res = AMF_OK; amf_avc_update_data(enc, rc, bitrate * 1000, qp); + res = enc->amf_encoder->Flush(); + if (res != AMF_OK) + throw amf_error("AMFComponent::Flush failed", res); + res = enc->amf_encoder->ReInit(enc->cx, enc->cy); if (res != AMF_OK) - throw amf_error("AMFComponent::Init failed", res); + throw amf_error("AMFComponent::ReInit failed", res); return true; @@ -1364,6 +1374,8 @@ static void amf_avc_create_internal(amf_base *enc, obs_data_t *settings) &enc->bframes_supported); caps->GetProperty(AMF_VIDEO_ENCODER_CAP_MAX_THROUGHPUT, &enc->max_throughput); + caps->GetProperty(AMF_VIDEO_ENCODER_CAP_REQUESTED_THROUGHPUT, + &enc->requested_throughput); } const char *preset = obs_data_get_string(settings, "preset"); @@ -1488,8 +1500,7 @@ static void register_avc() amf_encoder_info.get_name = amf_avc_get_name; amf_encoder_info.create = amf_avc_create_texencode; amf_encoder_info.destroy = amf_destroy; - /* FIXME: Figure out why encoder does not survive reconfiguration - amf_encoder_info.update = amf_avc_update; */ + amf_encoder_info.update = amf_avc_update; amf_encoder_info.encode_texture = amf_encode_tex; amf_encoder_info.get_defaults = amf_defaults; amf_encoder_info.get_properties = amf_avc_properties; @@ -1581,13 +1592,17 @@ try { int64_t qp = obs_data_get_int(settings, "cqp"); const char *rc_str = obs_data_get_string(settings, "rate_control"); int rc = get_hevc_rate_control(rc_str); - AMF_RESULT res; + AMF_RESULT res = AMF_OK; amf_hevc_update_data(enc, rc, bitrate * 1000, qp); + res = enc->amf_encoder->Flush(); + if (res != AMF_OK) + throw amf_error("AMFComponent::Flush failed", res); + res = enc->amf_encoder->ReInit(enc->cx, enc->cy); if (res != AMF_OK) - throw amf_error("AMFComponent::Init failed", res); + throw amf_error("AMFComponent::ReInit failed", res); return true; @@ -1693,6 +1708,9 @@ static void amf_hevc_create_internal(amf_base *enc, obs_data_t *settings) if (res == AMF_OK) { caps->GetProperty(AMF_VIDEO_ENCODER_HEVC_CAP_MAX_THROUGHPUT, &enc->max_throughput); + caps->GetProperty( + AMF_VIDEO_ENCODER_HEVC_CAP_REQUESTED_THROUGHPUT, + &enc->requested_throughput); } const bool is10bit = enc->amf_format == AMF_SURFACE_P010; @@ -1835,8 +1853,7 @@ static void register_hevc() amf_encoder_info.get_name = amf_hevc_get_name; amf_encoder_info.create = amf_hevc_create_texencode; amf_encoder_info.destroy = amf_destroy; - /* FIXME: Figure out why encoder does not survive reconfiguration - amf_encoder_info.update = amf_hevc_update; */ + amf_encoder_info.update = amf_hevc_update; amf_encoder_info.encode_texture = amf_encode_tex; amf_encoder_info.get_defaults = amf_defaults; amf_encoder_info.get_properties = amf_hevc_properties; @@ -1947,12 +1964,17 @@ try { int64_t cq_level = obs_data_get_int(settings, "cqp"); const char *rc_str = obs_data_get_string(settings, "rate_control"); int rc = get_av1_rate_control(rc_str); + AMF_RESULT res = AMF_OK; amf_av1_update_data(enc, rc, bitrate * 1000, cq_level); - AMF_RESULT res = enc->amf_encoder->ReInit(enc->cx, enc->cy); + res = enc->amf_encoder->Flush(); if (res != AMF_OK) - throw amf_error("AMFComponent::Init failed", res); + throw amf_error("AMFComponent::Flush failed", res); + + res = enc->amf_encoder->ReInit(enc->cx, enc->cy); + if (res != AMF_OK) + throw amf_error("AMFComponent::ReInit failed", res); return true; @@ -2027,6 +2049,9 @@ static void amf_av1_create_internal(amf_base *enc, obs_data_t *settings) if (res == AMF_OK) { caps->GetProperty(AMF_VIDEO_ENCODER_AV1_CAP_MAX_THROUGHPUT, &enc->max_throughput); + caps->GetProperty( + AMF_VIDEO_ENCODER_AV1_CAP_REQUESTED_THROUGHPUT, + &enc->requested_throughput); } const bool is10bit = enc->amf_format == AMF_SURFACE_P010; @@ -2153,8 +2178,7 @@ static void register_av1() amf_encoder_info.get_name = amf_av1_get_name; amf_encoder_info.create = amf_av1_create_texencode; amf_encoder_info.destroy = amf_destroy; - /* FIXME: Figure out why encoder does not survive reconfiguration - amf_encoder_info.update = amf_av1_update; */ + amf_encoder_info.update = amf_av1_update; amf_encoder_info.encode_texture = amf_encode_tex; amf_encoder_info.get_defaults = amf_av1_defaults; amf_encoder_info.get_properties = amf_av1_properties; diff --git a/plugins/obs-filters/chroma-key-filter.c b/plugins/obs-filters/chroma-key-filter.c index 598a617fd..2a5a6557a 100644 --- a/plugins/obs-filters/chroma-key-filter.c +++ b/plugins/obs-filters/chroma-key-filter.c @@ -447,7 +447,8 @@ static obs_properties_t *chroma_key_properties_v1(void *data) obs_property_list_add_string(p, obs_module_text("Green"), "green"); obs_property_list_add_string(p, obs_module_text("Blue"), "blue"); obs_property_list_add_string(p, obs_module_text("Magenta"), "magenta"); - obs_property_list_add_string(p, obs_module_text("Custom"), "custom"); + obs_property_list_add_string(p, obs_module_text("CustomColor"), + "custom"); obs_property_set_modified_callback(p, key_type_changed); @@ -486,7 +487,8 @@ static obs_properties_t *chroma_key_properties_v2(void *data) obs_property_list_add_string(p, obs_module_text("Green"), "green"); obs_property_list_add_string(p, obs_module_text("Blue"), "blue"); obs_property_list_add_string(p, obs_module_text("Magenta"), "magenta"); - obs_property_list_add_string(p, obs_module_text("Custom"), "custom"); + obs_property_list_add_string(p, obs_module_text("CustomColor"), + "custom"); obs_property_set_modified_callback(p, key_type_changed); diff --git a/plugins/obs-filters/data/locale/en-US.ini b/plugins/obs-filters/data/locale/en-US.ini index a3def5ff5..17ce57827 100644 --- a/plugins/obs-filters/data/locale/en-US.ini +++ b/plugins/obs-filters/data/locale/en-US.ini @@ -111,7 +111,6 @@ Expander.OutputGain="Output Gain" Expander.Detector="Detection" Expander.RMS="RMS" Expander.Peak="Peak" -Expander.None="None" Expander.Presets="Presets" Expander.Presets.Expander="Expander" Expander.Presets.Gate="Gate" diff --git a/plugins/obs-filters/expander-filter.c b/plugins/obs-filters/expander-filter.c index 07d69e6d0..ebfbbdc39 100644 --- a/plugins/obs-filters/expander-filter.c +++ b/plugins/obs-filters/expander-filter.c @@ -45,7 +45,6 @@ #define TEXT_DETECTOR MT_("Expander.Detector") #define TEXT_PEAK MT_("Expander.Peak") #define TEXT_RMS MT_("Expander.RMS") -#define TEXT_NONE MT_("Expander.None") #define TEXT_PRESETS MT_("Expander.Presets") #define TEXT_PRESETS_EXP MT_("Expander.Presets.Expander") #define TEXT_PRESETS_GATE MT_("Expander.Presets.Gate") diff --git a/plugins/obs-outputs/data/locale/en-US.ini b/plugins/obs-outputs/data/locale/en-US.ini index 2e1ec58e7..6ae4d16af 100644 --- a/plugins/obs-outputs/data/locale/en-US.ini +++ b/plugins/obs-outputs/data/locale/en-US.ini @@ -1,5 +1,8 @@ RTMPStream="RTMP Stream" RTMPStream.DropThreshold="Drop Threshold" +RTMPStream.BindIP="Bind IP" +RTMPStream.NewSocketLoop="New Socket Loop" +RTMPStream.LowLatencyMode="Low Latency Mode" FLVOutput="FLV File Output" FLVOutput.FilePath="File Path" Default="Default" @@ -12,3 +15,6 @@ HostNotFound="Hostname not found. Make sure you entered a valid streaming server NoData="Hostname found, but no data of the requested type. This can occur if you have bound to an IPv6 address and your streaming service only has IPv4 addresses (see Settings → Advanced)." AddressNotAvailable="Address not available. You may have tried to bind to an invalid IP address (see Settings → Advanced)." SSLCertVerifyFailed="The RTMP server sent an invalid SSL certificate." + +FTLStream="FTL Stream" +FTLStream.PeakBitrate="Peak Bitrate" diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 899881765..8b20d7904 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -98,11 +98,6 @@ static inline bool disconnected(struct rtmp_stream *stream) return os_atomic_load_bool(&stream->disconnected); } -static inline bool silently_reconnecting(struct rtmp_stream *stream) -{ - return os_atomic_load_bool(&stream->silent_reconnect); -} - static void rtmp_stream_destroy(void *data) { struct rtmp_stream *stream = data; @@ -262,27 +257,6 @@ static inline bool get_next_packet(struct rtmp_stream *stream, return new_packet; } -static inline void peek_next_packet(struct rtmp_stream *stream, - struct encoder_packet *packet) -{ - pthread_mutex_lock(&stream->packets_mutex); - circlebuf_peek_front(&stream->packets, packet, - sizeof(struct encoder_packet)); - pthread_mutex_unlock(&stream->packets_mutex); -} - -static void reinsert_packet_at_front(struct rtmp_stream *stream, - struct encoder_packet *packet) -{ - pthread_mutex_lock(&stream->packets_mutex); - circlebuf_push_front(&stream->packets, packet, - sizeof(struct encoder_packet)); - pthread_mutex_unlock(&stream->packets_mutex); - os_sem_post(stream->send_sem); -} - -#define RTMP_PACKET_TYPE_RECONNECT 0x20 - static bool process_recv_data(struct rtmp_stream *stream, size_t size) { UNUSED_PARAMETER(size); @@ -301,9 +275,7 @@ static bool process_recv_data(struct rtmp_stream *stream, size_t size) } if (packet.m_body) { - if (packet.m_packetType == RTMP_PACKET_TYPE_RECONNECT) { - os_atomic_set_bool(&stream->silent_reconnect, true); - } + /* do processing here */ RTMPPacket_Free(&packet); } return true; @@ -635,7 +607,6 @@ static void dbr_add_frame(struct rtmp_stream *stream, struct dbr_frame *back) } static void dbr_set_bitrate(struct rtmp_stream *stream); -static bool rtmp_stream_start(void *data); #ifdef _WIN32 #define socklen_t int @@ -687,14 +658,6 @@ static void *send_thread(void *data) } } - /* silent reconnect signal received from server, reconnect on - * next keyframe */ - if (silently_reconnecting(stream) && - packet.type == OBS_ENCODER_VIDEO && packet.keyframe) { - reinsert_packet_at_front(stream, &packet); - break; - } - if (stream->dbr_enabled) { dbr_frame.send_beg = os_gettime_ns(); dbr_frame.size = packet.size; @@ -730,8 +693,6 @@ static void *send_thread(void *data) } else if (encode_error) { info("Encoder error, disconnecting"); send_footers(stream); // Y2023 spec - } else if (silently_reconnecting(stream)) { - info("Silent reconnect signal received from server"); } else { info("User stopped the stream"); send_footers(stream); // Y2023 spec @@ -751,14 +712,6 @@ static void *send_thread(void *data) set_output_error(stream); - if (silently_reconnecting(stream)) { - /* manually close the socket to prevent librtmp from sending - * unpublish / deletestream messages when we call RTMP_Close, - * since we want to re-use this stream when we reconnect */ - RTMPSockBuf_Close(&stream->rtmp.m_sb); - stream->rtmp.m_sb.sb_socket = -1; - } - RTMP_Close(&stream->rtmp); /* reset bitrate on stop */ @@ -771,35 +724,18 @@ static void *send_thread(void *data) if (!stopping(stream)) { pthread_detach(stream->send_thread); - if (!silently_reconnecting(stream)) - obs_output_signal_stop(stream->output, - OBS_OUTPUT_DISCONNECTED); + obs_output_signal_stop(stream->output, OBS_OUTPUT_DISCONNECTED); } else if (encode_error) { obs_output_signal_stop(stream->output, OBS_OUTPUT_ENCODE_ERROR); } else { obs_output_end_data_capture(stream->output); } - if (!silently_reconnecting(stream)) { - free_packets(stream); - os_event_reset(stream->stop_event); - os_atomic_set_bool(&stream->active, false); - } - + free_packets(stream); + os_event_reset(stream->stop_event); + os_atomic_set_bool(&stream->active, false); stream->sent_headers = false; - /* reset bitrate on stop */ - if (stream->dbr_enabled) { - if (stream->dbr_cur_bitrate != stream->dbr_orig_bitrate) { - stream->dbr_cur_bitrate = stream->dbr_orig_bitrate; - dbr_set_bitrate(stream); - } - } - - if (silently_reconnecting(stream)) { - rtmp_stream_start(stream); - } - return NULL; } @@ -1020,8 +956,7 @@ static int init_send(struct rtmp_stream *stream) int ret; obs_output_t *context = stream->output; - if (!silently_reconnecting(stream)) - reset_semaphore(stream); + reset_semaphore(stream); ret = pthread_create(&stream->send_thread, NULL, send_thread, stream); if (ret != 0) { @@ -1136,8 +1071,7 @@ static int init_send(struct rtmp_stream *stream) return OBS_OUTPUT_DISCONNECTED; } - if (!silently_reconnecting(stream)) - obs_output_begin_data_capture(stream->output, 0); + obs_output_begin_data_capture(stream->output, 0); return OBS_OUTPUT_SUCCESS; } @@ -1219,12 +1153,6 @@ static void win32_log_interface_type(struct rtmp_stream *stream) } #endif -static void add_connect_data(char **penc, char *pend) -{ - const AVal val = AVC("supportsGoAway"); - *penc = AMF_EncodeNamedBoolean(*penc, pend, &val, true); -} - static int try_connect(struct rtmp_stream *stream) { if (dstr_is_empty(&stream->path)) { @@ -1250,7 +1178,6 @@ static int try_connect(struct rtmp_stream *stream) set_rtmp_dstr(&stream->rtmp.Link.pubPasswd, &stream->password); set_rtmp_dstr(&stream->rtmp.Link.flashVer, &stream->encoder_name); stream->rtmp.Link.swfUrl = stream->rtmp.Link.tcUrl; - stream->rtmp.Link.customConnectEncode = add_connect_data; if (dstr_is_empty(&stream->bind_ip) || dstr_cmp(&stream->bind_ip, "default") == 0) { @@ -1409,17 +1336,9 @@ static void *connect_thread(void *data) os_set_thread_name("rtmp-stream: connect_thread"); - if (!silently_reconnecting(stream)) { - if (!init_connect(stream)) { - obs_output_signal_stop(stream->output, - OBS_OUTPUT_BAD_PATH); - os_atomic_set_bool(&stream->silent_reconnect, false); - return NULL; - } - } else { - struct encoder_packet packet; - peek_next_packet(stream, &packet); - stream->start_dts_offset = get_ms_time(&packet, packet.dts); + if (!init_connect(stream)) { + obs_output_signal_stop(stream->output, OBS_OUTPUT_BAD_PATH); + return NULL; } // HDR streaming disabled for AV1 and HEVC @@ -1446,7 +1365,6 @@ static void *connect_thread(void *data) if (!stopping(stream)) pthread_detach(stream->connect_thread); - os_atomic_set_bool(&stream->silent_reconnect, false); os_atomic_set_bool(&stream->connecting, false); return NULL; } @@ -1455,12 +1373,10 @@ static bool rtmp_stream_start(void *data) { struct rtmp_stream *stream = data; - if (!silently_reconnecting(stream)) { - if (!obs_output_can_begin_data_capture(stream->output, 0)) - return false; - if (!obs_output_initialize_encoders(stream->output, 0)) - return false; - } + if (!obs_output_can_begin_data_capture(stream->output, 0)) + return false; + if (!obs_output_initialize_encoders(stream->output, 0)) + return false; os_atomic_set_bool(&stream->connecting, true); return pthread_create(&stream->connect_thread, NULL, connect_thread, @@ -1708,10 +1624,8 @@ static void check_to_drop_frames(struct rtmp_stream *stream, bool pframes) static bool add_video_packet(struct rtmp_stream *stream, struct encoder_packet *packet) { - if (!silently_reconnecting(stream)) { - check_to_drop_frames(stream, false); - check_to_drop_frames(stream, true); - } + check_to_drop_frames(stream, false); + check_to_drop_frames(stream, true); /* if currently dropping frames, drop packets until it reaches the * desired priority */ diff --git a/plugins/obs-outputs/rtmp-stream.h b/plugins/obs-outputs/rtmp-stream.h index 6a33ff9c9..dd6985468 100644 --- a/plugins/obs-outputs/rtmp-stream.h +++ b/plugins/obs-outputs/rtmp-stream.h @@ -68,7 +68,6 @@ struct rtmp_stream { volatile bool active; volatile bool disconnected; volatile bool encode_error; - volatile bool silent_reconnect; pthread_t send_thread; int max_shutdown_time_sec; diff --git a/plugins/obs-qsv11/data/locale/en-US.ini b/plugins/obs-qsv11/data/locale/en-US.ini index dbd2f410f..30e70710f 100644 --- a/plugins/obs-qsv11/data/locale/en-US.ini +++ b/plugins/obs-qsv11/data/locale/en-US.ini @@ -13,3 +13,4 @@ SubjectiveVideoEnhancements="Subjective Video Enhancements" 8bitUnsupportedHdr="OBS does not support 8-bit output of Rec. 2100." 10bitUnsupportedAvc="Cannot perform 10-bit encode on Intel QSV H.264 encoder." 16bitUnsupported="Cannot perform 16-bit encode on this encoder." +BFrames="B Frames" diff --git a/plugins/obs-qsv11/obs-qsv11.c b/plugins/obs-qsv11/obs-qsv11.c index 4077005ad..1c7d60dfc 100644 --- a/plugins/obs-qsv11/obs-qsv11.c +++ b/plugins/obs-qsv11/obs-qsv11.c @@ -228,7 +228,7 @@ static inline void add_strings(obs_property_t *list, const char *const *strings) #define TEXT_CONVERGENCE obs_module_text("Convergence") #define TEXT_ICQ_QUALITY obs_module_text("ICQQuality") #define TEXT_KEYINT_SEC obs_module_text("KeyframeIntervalSec") -#define TEXT_BFRAMES obs_module_text("B Frames") +#define TEXT_BFRAMES obs_module_text("BFrames") #define TEXT_PERCEPTUAL_ENHANCEMENTS \ obs_module_text("SubjectiveVideoEnhancements") diff --git a/plugins/obs-websocket b/plugins/obs-websocket index 6ef055a36..6fd18a7ef 160000 --- a/plugins/obs-websocket +++ b/plugins/obs-websocket @@ -1 +1 @@ -Subproject commit 6ef055a369249f6d7b008914fe2bc360c96f23dc +Subproject commit 6fd18a7ef1ecb149e8444154af1daab61d4241a9 diff --git a/plugins/text-freetype2/text-freetype2.c b/plugins/text-freetype2/text-freetype2.c index 31bd8f53a..4c7e06987 100644 --- a/plugins/text-freetype2/text-freetype2.c +++ b/plugins/text-freetype2/text-freetype2.c @@ -376,7 +376,10 @@ static void ft2_source_update(void *data, obs_data_t *settings) srcdata->log_lines = log_lines; vbuf_needs_update = true; } - srcdata->log_mode = chat_log_mode; + if (srcdata->log_mode != chat_log_mode) { + srcdata->log_mode = chat_log_mode; + vbuf_needs_update = true; + } if (ft2_lib == NULL) goto error; diff --git a/plugins/text-freetype2/text-functionality.c b/plugins/text-freetype2/text-functionality.c index 7b4839d33..25772c50a 100644 --- a/plugins/text-freetype2/text-functionality.c +++ b/plugins/text-freetype2/text-functionality.c @@ -140,7 +140,8 @@ void set_up_vertex_buffer(struct ft2_source *srcdata) next_char:; glyph_index = FT_Get_Char_Index(srcdata->font_face, srcdata->text[i]); - word_width += src_glyph->xadv; + if (src_glyph) + word_width += src_glyph->xadv; eos_skip:; } diff --git a/plugins/win-capture/compat-helpers.c b/plugins/win-capture/compat-helpers.c index 2ba9ce5c5..f4d5565ec 100644 --- a/plugins/win-capture/compat-helpers.c +++ b/plugins/win-capture/compat-helpers.c @@ -145,16 +145,17 @@ struct compat_result *check_compatibility(const char *win_title, const char *j_title = get_string_val(entry, "window_title"); const char *j_class = get_string_val(entry, "window_class"); - if (win_class && (match_flags & MATCH_CLASS) && - strcmp(win_class, j_class) != 0) + if ((match_flags & MATCH_CLASS) && + (!win_class || strcmp(win_class, j_class) != 0)) continue; - if (exe && (match_flags & MATCH_EXE) && - astrcmpi(exe, j_exe) != 0) + if ((match_flags & MATCH_EXE) && + (!exe || astrcmpi(exe, j_exe) != 0)) continue; /* Title supports partial matches as some games append additional * information after the title, e.g., "Minecraft 1.18". */ - if (win_title && (match_flags & MATCH_TITLE) && - astrcmpi_n(win_title, j_title, strlen(j_title)) != 0) + if ((match_flags & MATCH_TITLE) && + (!win_title || + astrcmpi_n(win_title, j_title, strlen(j_title)) != 0)) continue; /* Attempt to translate and compile message */ diff --git a/plugins/win-capture/data/compatibility.json b/plugins/win-capture/data/compatibility.json index 1a1347106..8f30dbc46 100644 --- a/plugins/win-capture/data/compatibility.json +++ b/plugins/win-capture/data/compatibility.json @@ -211,6 +211,20 @@ "window_capture_wgc": false, "message": "Edge may not be capturable using the selected Capture Method (BitBlt).", "url": "" + }, + { + "name": "Honkai: Star Rail", + "translation_key": "Compatibility.GameCapture.Admin", + "severity": 1, + "executable": "StarRail.exe", + "window_class": "", + "window_title": "", + "match_flags": 1, + "game_capture": true, + "window_capture": false, + "window_capture_wgc": false, + "message": "Honkai: Star Rail may require OBS to be run as admin to use Game Capture.", + "url": "https://obsproject.com/kb/game-capture-troubleshooting" } ] } diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index e91b57ba1..1943768d9 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -18,7 +18,6 @@ AllowTransparency="Allow Transparency" Monitor="Display" PrimaryMonitor="Primary Monitor" Method="Capture Method" -Method.Auto="Automatic" Method.DXGI="DXGI Desktop Duplication" Method.WindowsGraphicsCapture="Windows 10 (1903 and up)" GameCapture="Game Capture" diff --git a/plugins/win-capture/data/package.json b/plugins/win-capture/data/package.json index 4bc99d9db..7737647f9 100644 --- a/plugins/win-capture/data/package.json +++ b/plugins/win-capture/data/package.json @@ -1,11 +1,11 @@ { "$schema": "schema/package-schema.json", "url": "https://obsproject.com/obs2_update/win-capture/v1", - "version": 1, + "version": 2, "files": [ { "name": "compatibility.json", - "version": 1 + "version": 2 } ] } diff --git a/plugins/win-dshow/data/locale/en-US.ini b/plugins/win-dshow/data/locale/en-US.ini index ddc91acdf..22bc67bf4 100644 --- a/plugins/win-dshow/data/locale/en-US.ini +++ b/plugins/win-dshow/data/locale/en-US.ini @@ -11,7 +11,6 @@ ColorRange="Color Range" ColorRange.Default="Default" ColorRange.Partial="Limited" ColorRange.Full="Full" -ConfigureAudio="Configure Audio" ConfigureVideo="Configure Video" ConfigureCrossbar="Configure Crossbar" ResFPSType="Resolution/FPS Type"