diff --git a/frontend/OBSApp.cpp b/frontend/OBSApp.cpp index 2b95502f48cf79..ac2d1dc1645b1c 100644 --- a/frontend/OBSApp.cpp +++ b/frontend/OBSApp.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) #include @@ -1081,16 +1082,16 @@ void OBSApp::AppInit() QAccessible::installFactory(alignmentSelectorFactory); if (!MakeUserDirs()) { - throw "Failed to create required user directories"; + throw OBSInitException(OBSInitErrorCode::UserDirs, "Failed to create required user directories"); } if (!InitGlobalConfig()) { - throw "Failed to initialize global config"; + throw OBSInitException(OBSInitErrorCode::GlobalConfig, "Failed to initialize global config"); } if (!InitLocale()) { - throw "Failed to load locale"; + throw OBSInitException(OBSInitErrorCode::Locale, "Failed to load locale"); } if (!InitTheme()) { - throw "Failed to load theme"; + throw OBSInitException(OBSInitErrorCode::Theme, "Failed to load theme"); } config_set_default_string(userConfig, "Basic", "Profile", Str("Untitled")); @@ -1132,7 +1133,7 @@ void OBSApp::AppInit() move_basic_to_scene_collections(); if (!MakeUserProfileDirs()) { - throw "Failed to create profile directories"; + throw OBSInitException(OBSInitErrorCode::ProfileDirs, "Failed to create profile directories"); } } @@ -1687,12 +1688,12 @@ vector> GetLocaleNames() { string path; if (!GetDataFilePath("locale.ini", path)) { - throw "Could not find locale.ini path"; + throw OBSInitException(OBSInitErrorCode::LocaleIniPath, "Could not find locale.ini path"); } ConfigFile ini; if (ini.Open(path.c_str(), CONFIG_OPEN_EXISTING) != 0) { - throw "Could not open locale.ini"; + throw OBSInitException(OBSInitErrorCode::LocaleIniOpen, "Could not open locale.ini"); } size_t sections = config_num_sections(ini); diff --git a/frontend/cmake/ui-utility.cmake b/frontend/cmake/ui-utility.cmake index 387385da8d2f0d..ca0d2750b00f0e 100644 --- a/frontend/cmake/ui-utility.cmake +++ b/frontend/cmake/ui-utility.cmake @@ -25,6 +25,8 @@ target_sources( utility/MissingFilesPathItemDelegate.hpp utility/MultitrackVideoError.cpp utility/MultitrackVideoError.hpp + utility/OBSInitException.cpp + utility/OBSInitException.hpp utility/MultitrackVideoOutput.cpp utility/MultitrackVideoOutput.hpp utility/NativeEventFilter.hpp diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index ef69b12d5dae5a..eb5e32ff202e50 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -5,6 +5,16 @@ # Language of this file Language="English" +# Startup / initialization errors +Init.Error.UserDirs="Failed to create required user directories." +Init.Error.GlobalConfig="Failed to initialize the global configuration." +Init.Error.Locale="Failed to load the application language files." +Init.Error.Theme="Failed to load the application theme." +Init.Error.ProfileDirs="Failed to create profile directories." +Init.Error.LocaleIniPath="Could not find the locale.ini path." +Init.Error.LocaleIniOpen="Could not open locale.ini." +Init.Error.Unknown="OBS Studio failed to start." + # commonly shared locale OK="OK" Apply="Apply" diff --git a/frontend/obs-main.cpp b/frontend/obs-main.cpp index 9fa5d191821d72..434363d40964b5 100644 --- a/frontend/obs-main.cpp +++ b/frontend/obs-main.cpp @@ -22,6 +22,7 @@ #include #endif #include +#include #include #include @@ -690,6 +691,9 @@ static int run_program(fstream &logFile, int argc, char *argv[]) ret = program.exec(); + } catch (const OBSInitException &error) { + blog(LOG_ERROR, "OBS init failed (%s): %s", OBSInitException::codeName(error.code()), error.what()); + OBSErrorBox(nullptr, "%s", QT_TO_UTF8(error.localizedMessage())); } catch (const char *error) { blog(LOG_ERROR, "%s", error); OBSErrorBox(nullptr, "%s", error); diff --git a/frontend/utility/OBSInitException.cpp b/frontend/utility/OBSInitException.cpp new file mode 100644 index 00000000000000..2974e9bee0d0fa --- /dev/null +++ b/frontend/utility/OBSInitException.cpp @@ -0,0 +1,42 @@ +/****************************************************************************** + Copyright (C) 2026 by Ray Winkelman + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "OBSInitException.hpp" + +#include "OBSApp.hpp" + +QString OBSInitException::localizedMessage() const +{ + switch (code_) { + case OBSInitErrorCode::UserDirs: + return QTStr("Init.Error.UserDirs"); + case OBSInitErrorCode::GlobalConfig: + return QTStr("Init.Error.GlobalConfig"); + case OBSInitErrorCode::Locale: + return QTStr("Init.Error.Locale"); + case OBSInitErrorCode::Theme: + return QTStr("Init.Error.Theme"); + case OBSInitErrorCode::ProfileDirs: + return QTStr("Init.Error.ProfileDirs"); + case OBSInitErrorCode::LocaleIniPath: + return QTStr("Init.Error.LocaleIniPath"); + case OBSInitErrorCode::LocaleIniOpen: + return QTStr("Init.Error.LocaleIniOpen"); + } + + return QTStr("Init.Error.Unknown"); +} diff --git a/frontend/utility/OBSInitException.hpp b/frontend/utility/OBSInitException.hpp new file mode 100644 index 00000000000000..91036ca1e52ffd --- /dev/null +++ b/frontend/utility/OBSInitException.hpp @@ -0,0 +1,74 @@ +/****************************************************************************** + Copyright (C) 2026 by Ray Winkelman + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +enum class OBSInitErrorCode { + UserDirs, + GlobalConfig, + Locale, + Theme, + ProfileDirs, + LocaleIniPath, + LocaleIniOpen, +}; + +class OBSInitException : public std::exception { +public: + explicit OBSInitException(OBSInitErrorCode code, std::string detail = {}) + : code_(code), + detail_(std::move(detail)) + { + } + + OBSInitErrorCode code() const { return code_; } + + const char *what() const noexcept override { return detail_.empty() ? codeName(code_) : detail_.c_str(); } + + QString localizedMessage() const; + + static const char *codeName(OBSInitErrorCode code) + { + switch (code) { + case OBSInitErrorCode::UserDirs: + return "UserDirs"; + case OBSInitErrorCode::GlobalConfig: + return "GlobalConfig"; + case OBSInitErrorCode::Locale: + return "Locale"; + case OBSInitErrorCode::Theme: + return "Theme"; + case OBSInitErrorCode::ProfileDirs: + return "ProfileDirs"; + case OBSInitErrorCode::LocaleIniPath: + return "LocaleIniPath"; + case OBSInitErrorCode::LocaleIniOpen: + return "LocaleIniOpen"; + } + return "Unknown"; + } + +private: + OBSInitErrorCode code_; + std::string detail_; +};