From 2be464a21fde5892236e4b48986f3829c59316f8 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Wed, 11 Dec 2024 19:03:30 +0100 Subject: [PATCH] frontend: Split main application implementation into single files --- frontend/OBSApp.cpp | 1165 +---------- frontend/OBSApp.hpp | 97 +- frontend/OBSApp_Themes.cpp | 24 +- frontend/OBSStudioAPI.cpp | 1160 ++++++----- frontend/OBSStudioAPI.hpp | 782 ++------ frontend/obs-main.cpp | 1616 +-------------- frontend/utility/BaseLexer.hpp | 251 --- frontend/utility/OBSTheme.hpp | 26 +- frontend/utility/OBSThemeVariable.hpp | 26 +- frontend/utility/OBSTranslator.cpp | 2637 +------------------------ frontend/utility/OBSTranslator.hpp | 251 +-- 11 files changed, 940 insertions(+), 7095 deletions(-) diff --git a/frontend/OBSApp.cpp b/frontend/OBSApp.cpp index 883bee21a..36f317728 100644 --- a/frontend/OBSApp.cpp +++ b/frontend/OBSApp.cpp @@ -15,108 +15,59 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "obs-proxy-style.hpp" -#include "log-viewer.hpp" -#include "volume-control.hpp" -#include "window-basic-main.hpp" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-basic-settings.hpp" -#include "platform.hpp" - -#include - -#include - -#ifdef _WIN32 -#include -#include -#include -#else -#include -#include -#include -#include -#include -#endif +#include "OBSApp.hpp" +#include +#include #if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) -#include "update/models/branches.hpp" +#include #endif +#include #if !defined(_WIN32) && !defined(__APPLE__) #include +#endif +#include + +#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) +#include +#endif + +#ifdef _WIN32 +#include +#else +#include +#endif +#if !defined(_WIN32) && !defined(__APPLE__) #include #endif -#include +#ifdef _WIN32 +#include +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif -#include "ui-config.h" +#include "moc_OBSApp.cpp" using namespace std; -static log_handler_t def_log_handler; +string currentLogFile; +string lastLogFile; +string lastCrashLogFile; -static string currentLogFile; -static string lastLogFile; -static string lastCrashLogFile; +extern bool portable_mode; +extern bool safe_mode; +extern bool disable_3p_plugins; +extern bool opt_disable_updater; +extern bool opt_disable_missing_files_check; +extern string opt_starting_collection; +extern string opt_starting_profile; -bool portable_mode = false; -bool steam = false; -bool safe_mode = false; -bool disable_3p_plugins = false; -bool unclean_shutdown = false; -bool disable_shutdown_check = false; -static bool multi = false; -static bool log_verbose = false; -static bool unfiltered_log = false; -bool opt_start_streaming = false; -bool opt_start_recording = false; -bool opt_studio_mode = false; -bool opt_start_replaybuffer = false; -bool opt_start_virtualcam = false; -bool opt_minimize_tray = false; -bool opt_allow_opengl = false; -bool opt_always_on_top = false; -bool opt_disable_updater = false; -bool opt_disable_missing_files_check = false; -string opt_starting_collection; -string opt_starting_profile; -string opt_starting_scene; - -bool restart = false; -bool restart_safe = false; -QStringList arguments; - -QPointer obsLogViewer; +extern QPointer obsLogViewer; #ifndef _WIN32 int OBSApp::sigintFd[2]; @@ -252,28 +203,6 @@ QObject *CreateShortcutFilter() }); } -string CurrentTimeString() -{ - using namespace std::chrono; - - struct tm tstruct; - char buf[80]; - - auto tp = system_clock::now(); - auto now = system_clock::to_time_t(tp); - tstruct = *localtime(&now); - - size_t written = strftime(buf, sizeof(buf), "%T", &tstruct); - if (ratio_less::value && written && (sizeof(buf) - written) > 5) { - auto tp_secs = time_point_cast(tp); - auto millis = duration_cast(tp - tp_secs).count(); - - snprintf(buf + written, sizeof(buf) - written, ".%03u", static_cast(millis)); - } - - return buf; -} - string CurrentDateTimeString() { time_t now = time(0); @@ -284,145 +213,6 @@ string CurrentDateTimeString() return buf; } -static void LogString(fstream &logFile, const char *timeString, char *str, int log_level) -{ - static mutex logfile_mutex; - string msg; - msg += timeString; - msg += str; - - logfile_mutex.lock(); - logFile << msg << endl; - logfile_mutex.unlock(); - - if (!!obsLogViewer) - QMetaObject::invokeMethod(obsLogViewer.data(), "AddLine", Qt::QueuedConnection, Q_ARG(int, log_level), - Q_ARG(QString, QString(msg.c_str()))); -} - -static inline void LogStringChunk(fstream &logFile, char *str, int log_level) -{ - char *nextLine = str; - string timeString = CurrentTimeString(); - timeString += ": "; - - while (*nextLine) { - char *nextLine = strchr(str, '\n'); - if (!nextLine) - break; - - if (nextLine != str && nextLine[-1] == '\r') { - nextLine[-1] = 0; - } else { - nextLine[0] = 0; - } - - LogString(logFile, timeString.c_str(), str, log_level); - nextLine++; - str = nextLine; - } - - LogString(logFile, timeString.c_str(), str, log_level); -} - -#define MAX_REPEATED_LINES 30 -#define MAX_CHAR_VARIATION (255 * 3) - -static inline int sum_chars(const char *str) -{ - int val = 0; - for (; *str != 0; str++) - val += *str; - - return val; -} - -static inline bool too_many_repeated_entries(fstream &logFile, const char *msg, const char *output_str) -{ - static mutex log_mutex; - static const char *last_msg_ptr = nullptr; - static int last_char_sum = 0; - static int rep_count = 0; - - int new_sum = sum_chars(output_str); - - lock_guard guard(log_mutex); - - if (unfiltered_log) { - return false; - } - - if (last_msg_ptr == msg) { - int diff = std::abs(new_sum - last_char_sum); - if (diff < MAX_CHAR_VARIATION) { - return (rep_count++ >= MAX_REPEATED_LINES); - } - } - - if (rep_count > MAX_REPEATED_LINES) { - logFile << CurrentTimeString() << ": Last log entry repeated for " - << to_string(rep_count - MAX_REPEATED_LINES) << " more lines" << endl; - } - - last_msg_ptr = msg; - last_char_sum = new_sum; - rep_count = 0; - - return false; -} - -static void do_log(int log_level, const char *msg, va_list args, void *param) -{ - fstream &logFile = *static_cast(param); - char str[8192]; - -#ifndef _WIN32 - va_list args2; - va_copy(args2, args); -#endif - - vsnprintf(str, sizeof(str), msg, args); - -#ifdef _WIN32 - if (IsDebuggerPresent()) { - int wNum = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - if (wNum > 1) { - static wstring wide_buf; - static mutex wide_mutex; - - lock_guard lock(wide_mutex); - wide_buf.reserve(wNum + 1); - wide_buf.resize(wNum - 1); - MultiByteToWideChar(CP_UTF8, 0, str, -1, &wide_buf[0], wNum); - wide_buf.push_back('\n'); - - OutputDebugStringW(wide_buf.c_str()); - } - } -#endif - -#if !defined(_WIN32) && defined(_DEBUG) - def_log_handler(log_level, msg, args2, nullptr); -#endif - - if (log_level <= LOG_INFO || log_verbose) { -#if !defined(_WIN32) && !defined(_DEBUG) - def_log_handler(log_level, msg, args2, nullptr); -#endif - if (!too_many_repeated_entries(logFile, msg, str)) - LogStringChunk(logFile, str, log_level); - } - -#if defined(_WIN32) && defined(OBS_DEBUGBREAK_ON_ERROR) - if (log_level <= LOG_ERROR && IsDebuggerPresent()) - __debugbreak(); -#endif - -#ifndef _WIN32 - va_end(args2); -#endif -} - #define DEFAULT_LANG "en-US" bool OBSApp::InitGlobalConfigDefaults() @@ -1214,8 +1004,6 @@ void OBSApp::DisableHotkeys() ResetHotkeyState(applicationState() == Qt::ApplicationActive); } -Q_DECLARE_METATYPE(VoidFunc) - void OBSApp::Exec(VoidFunc func) { func(); @@ -1437,172 +1225,6 @@ skip: return QApplication::notify(receiver, e); } -QString OBSTranslator::translate(const char *, const char *sourceText, const char *, int) const -{ - const char *out = nullptr; - QString str(sourceText); - str.replace(" ", ""); - if (!App()->TranslateString(QT_TO_UTF8(str), &out)) - return QString(sourceText); - - return QT_UTF8(out); -} - -static bool get_token(lexer *lex, string &str, base_token_type type) -{ - base_token token; - if (!lexer_getbasetoken(lex, &token, IGNORE_WHITESPACE)) - return false; - if (token.type != type) - return false; - - str.assign(token.text.array, token.text.len); - return true; -} - -static bool expect_token(lexer *lex, const char *str, base_token_type type) -{ - base_token token; - if (!lexer_getbasetoken(lex, &token, IGNORE_WHITESPACE)) - return false; - if (token.type != type) - return false; - - return strref_cmp(&token.text, str) == 0; -} - -static uint64_t convert_log_name(bool has_prefix, const char *name) -{ - BaseLexer lex; - string year, month, day, hour, minute, second; - - lexer_start(lex, name); - - if (has_prefix) { - string temp; - if (!get_token(lex, temp, BASETOKEN_ALPHA)) - return 0; - } - - if (!get_token(lex, year, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, month, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, day, BASETOKEN_DIGIT)) - return 0; - if (!get_token(lex, hour, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, minute, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, second, BASETOKEN_DIGIT)) - return 0; - - stringstream timestring; - timestring << year << month << day << hour << minute << second; - return std::stoull(timestring.str()); -} - -/* If upgrading from an older (non-XDG) build of OBS, move config files to XDG directory. */ -/* TODO: Remove after version 32.0. */ -#if defined(__FreeBSD__) -static void move_to_xdg(void) -{ - char old_path[512]; - char new_path[512]; - char *home = getenv("HOME"); - if (!home) - return; - - if (snprintf(old_path, sizeof(old_path), "%s/.obs-studio", home) <= 0) - return; - - /* make base xdg path if it doesn't already exist */ - if (GetAppConfigPath(new_path, sizeof(new_path), "") <= 0) - return; - if (os_mkdirs(new_path) == MKDIR_ERROR) - return; - - if (GetAppConfigPath(new_path, sizeof(new_path), "obs-studio") <= 0) - return; - - if (os_file_exists(old_path) && !os_file_exists(new_path)) { - rename(old_path, new_path); - } -} -#endif - -static void delete_oldest_file(bool has_prefix, const char *location) -{ - BPtr logDir(GetAppConfigPathPtr(location)); - string oldestLog; - uint64_t oldest_ts = (uint64_t)-1; - struct os_dirent *entry; - - unsigned int maxLogs = (unsigned int)config_get_uint(App()->GetAppConfig(), "General", "MaxLogs"); - - os_dir_t *dir = os_opendir(logDir); - if (dir) { - unsigned int count = 0; - - while ((entry = os_readdir(dir)) != NULL) { - if (entry->directory || *entry->d_name == '.') - continue; - - uint64_t ts = convert_log_name(has_prefix, entry->d_name); - - if (ts) { - if (ts < oldest_ts) { - oldestLog = entry->d_name; - oldest_ts = ts; - } - - count++; - } - } - - os_closedir(dir); - - if (count > maxLogs) { - stringstream delPath; - - delPath << logDir << "/" << oldestLog; - os_unlink(delPath.str().c_str()); - } - } -} - -static void get_last_log(bool has_prefix, const char *subdir_to_use, std::string &last) -{ - BPtr logDir(GetAppConfigPathPtr(subdir_to_use)); - struct os_dirent *entry; - os_dir_t *dir = os_opendir(logDir); - uint64_t highest_ts = 0; - - if (dir) { - while ((entry = os_readdir(dir)) != NULL) { - if (entry->directory || *entry->d_name == '.') - continue; - - uint64_t ts = convert_log_name(has_prefix, entry->d_name); - - if (ts > highest_ts) { - last = entry->d_name; - highest_ts = ts; - } - } - - os_closedir(dir); - } -} - string GenerateTimeDateFilename(const char *extension, bool noSpace) { time_t now = time(0); @@ -1786,444 +1408,7 @@ vector> GetLocaleNames() return names; } -static void create_log_file(fstream &logFile) -{ - stringstream dst; - - get_last_log(false, "obs-studio/logs", lastLogFile); -#ifdef _WIN32 - get_last_log(true, "obs-studio/crashes", lastCrashLogFile); -#endif - - currentLogFile = GenerateTimeDateFilename("txt"); - dst << "obs-studio/logs/" << currentLogFile.c_str(); - - BPtr path(GetAppConfigPathPtr(dst.str().c_str())); - -#ifdef _WIN32 - BPtr wpath; - os_utf8_to_wcs_ptr(path, 0, &wpath); - logFile.open(wpath, ios_base::in | ios_base::out | ios_base::trunc); -#else - logFile.open(path, ios_base::in | ios_base::out | ios_base::trunc); -#endif - - if (logFile.is_open()) { - delete_oldest_file(false, "obs-studio/logs"); - base_set_log_handler(do_log, &logFile); - } else { - blog(LOG_ERROR, "Failed to open log file"); - } -} - -static auto ProfilerNameStoreRelease = [](profiler_name_store_t *store) { - profiler_name_store_free(store); -}; - -using ProfilerNameStore = std::unique_ptr; - -ProfilerNameStore CreateNameStore() -{ - return ProfilerNameStore{profiler_name_store_create(), ProfilerNameStoreRelease}; -} - -static auto SnapshotRelease = [](profiler_snapshot_t *snap) { - profile_snapshot_free(snap); -}; - -using ProfilerSnapshot = std::unique_ptr; - -ProfilerSnapshot GetSnapshot() -{ - return ProfilerSnapshot{profile_snapshot_create(), SnapshotRelease}; -} - -static void SaveProfilerData(const ProfilerSnapshot &snap) -{ - if (currentLogFile.empty()) - return; - - auto pos = currentLogFile.rfind('.'); - if (pos == currentLogFile.npos) - return; - -#define LITERAL_SIZE(x) x, (sizeof(x) - 1) - ostringstream dst; - dst.write(LITERAL_SIZE("obs-studio/profiler_data/")); - dst.write(currentLogFile.c_str(), pos); - dst.write(LITERAL_SIZE(".csv.gz")); -#undef LITERAL_SIZE - - BPtr path = GetAppConfigPathPtr(dst.str().c_str()); - if (!profiler_snapshot_dump_csv_gz(snap.get(), path)) - blog(LOG_WARNING, "Could not save profiler data to '%s'", static_cast(path)); -} - -static auto ProfilerFree = [](void *) { - profiler_stop(); - - auto snap = GetSnapshot(); - - profiler_print(snap.get()); - profiler_print_time_between_calls(snap.get()); - - SaveProfilerData(snap); - - profiler_free(); -}; - -QAccessibleInterface *accessibleFactory(const QString &classname, QObject *object) -{ - if (classname == QLatin1String("VolumeSlider") && object && object->isWidgetType()) - return new VolumeAccessibleInterface(static_cast(object)); - - return nullptr; -} - -static const char *run_program_init = "run_program_init"; -static int run_program(fstream &logFile, int argc, char *argv[]) -{ - int ret = -1; - - auto profilerNameStore = CreateNameStore(); - - std::unique_ptr prof_release(static_cast(&ProfilerFree), ProfilerFree); - - profiler_start(); - profile_register_root(run_program_init, 0); - - ScopeProfiler prof{run_program_init}; - -#ifdef _WIN32 - QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif - - QCoreApplication::addLibraryPath("."); - -#if __APPLE__ - InstallNSApplicationSubclass(); - InstallNSThreadLocks(); - - if (!isInBundle()) { - blog(LOG_ERROR, - "OBS cannot be run as a standalone binary on macOS. Run the Application bundle instead."); - return ret; - } -#endif - -#if !defined(_WIN32) && !defined(__APPLE__) - /* NOTE: Users blindly set this, but this theme is incompatble with Qt6 and - * crashes loading saved geometry. Just turn off this theme and let users complain OBS - * looks ugly instead of crashing. */ - const char *platform_theme = getenv("QT_QPA_PLATFORMTHEME"); - if (platform_theme && strcmp(platform_theme, "qt5ct") == 0) - unsetenv("QT_QPA_PLATFORMTHEME"); -#endif - - /* NOTE: This disables an optimisation in Qt that attempts to determine if - * any "siblings" intersect with a widget when determining the approximate - * visible/unobscured area. However, by Qt's own admission this is slow - * and in the case of OBS it significantly slows down lists with many - * elements (e.g. Hotkeys) and it is actually faster to disable it. */ - qputenv("QT_NO_SUBTRACTOPAQUESIBLINGS", "1"); - - OBSApp program(argc, argv, profilerNameStore.get()); - try { - QAccessible::installFactory(accessibleFactory); - QFontDatabase::addApplicationFont(":/fonts/OpenSans-Regular.ttf"); - QFontDatabase::addApplicationFont(":/fonts/OpenSans-Bold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/OpenSans-Italic.ttf"); - - bool created_log = false; - - program.AppInit(); - delete_oldest_file(false, "obs-studio/profiler_data"); - - OBSTranslator translator; - program.installTranslator(&translator); - - /* --------------------------------------- */ - /* check and warn if already running */ - - bool cancel_launch = false; - bool already_running = false; - -#ifdef _WIN32 - RunOnceMutex rom = -#endif - CheckIfAlreadyRunning(already_running); - - if (!already_running) { - goto run; - } - - if (!multi) { - QMessageBox mb(QMessageBox::Question, QTStr("AlreadyRunning.Title"), - QTStr("AlreadyRunning.Text")); - mb.addButton(QTStr("AlreadyRunning.LaunchAnyway"), QMessageBox::YesRole); - QPushButton *cancelButton = mb.addButton(QTStr("Cancel"), QMessageBox::NoRole); - mb.setDefaultButton(cancelButton); - - mb.exec(); - cancel_launch = mb.clickedButton() == cancelButton; - } - - if (cancel_launch) - return 0; - - if (!created_log) { - create_log_file(logFile); - created_log = true; - } - - if (multi) { - blog(LOG_INFO, "User enabled --multi flag and is now " - "running multiple instances of OBS."); - } else { - blog(LOG_WARNING, "================================"); - blog(LOG_WARNING, "Warning: OBS is already running!"); - blog(LOG_WARNING, "================================"); - blog(LOG_WARNING, "User is now running multiple " - "instances of OBS!"); - /* Clear unclean_shutdown flag as multiple instances - * running from the same config will lead to a - * false-positive detection.*/ - unclean_shutdown = false; - } - - /* --------------------------------------- */ - run: - -#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__FreeBSD__) - // Mounted by termina during chromeOS linux container startup - // https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/master/project-termina/chromeos-base/termina-lxd-scripts/files/lxd_setup.sh - os_dir_t *crosDir = os_opendir("/opt/google/cros-containers"); - if (crosDir) { - QMessageBox::StandardButtons buttons(QMessageBox::Ok); - QMessageBox mb(QMessageBox::Critical, QTStr("ChromeOS.Title"), QTStr("ChromeOS.Text"), buttons, - nullptr); - - mb.exec(); - return 0; - } -#endif - - if (!created_log) - create_log_file(logFile); - - if (unclean_shutdown) { - blog(LOG_WARNING, "[Safe Mode] Unclean shutdown detected!"); - } - - if (unclean_shutdown && !safe_mode) { - QMessageBox mb(QMessageBox::Warning, QTStr("AutoSafeMode.Title"), QTStr("AutoSafeMode.Text")); - QPushButton *launchSafeButton = - mb.addButton(QTStr("AutoSafeMode.LaunchSafe"), QMessageBox::AcceptRole); - QPushButton *launchNormalButton = - mb.addButton(QTStr("AutoSafeMode.LaunchNormal"), QMessageBox::RejectRole); - mb.setDefaultButton(launchNormalButton); - mb.exec(); - - safe_mode = mb.clickedButton() == launchSafeButton; - if (safe_mode) { - blog(LOG_INFO, "[Safe Mode] User has launched in Safe Mode."); - } else { - blog(LOG_WARNING, "[Safe Mode] User elected to launch normally."); - } - } - - qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &, const QString &message) { - switch (type) { -#ifdef _DEBUG - case QtDebugMsg: - blog(LOG_DEBUG, "%s", QT_TO_UTF8(message)); - break; - case QtInfoMsg: - blog(LOG_INFO, "%s", QT_TO_UTF8(message)); - break; -#else - case QtDebugMsg: - case QtInfoMsg: - break; -#endif - case QtWarningMsg: - blog(LOG_WARNING, "%s", QT_TO_UTF8(message)); - break; - case QtCriticalMsg: - case QtFatalMsg: - blog(LOG_ERROR, "%s", QT_TO_UTF8(message)); - break; - } - }); - -#ifdef __APPLE__ - MacPermissionStatus audio_permission = CheckPermission(kAudioDeviceAccess); - MacPermissionStatus video_permission = CheckPermission(kVideoDeviceAccess); - MacPermissionStatus accessibility_permission = CheckPermission(kAccessibility); - MacPermissionStatus screen_permission = CheckPermission(kScreenCapture); - - int permissionsDialogLastShown = - config_get_int(App()->GetAppConfig(), "General", "MacOSPermissionsDialogLastShown"); - if (permissionsDialogLastShown < MACOS_PERMISSIONS_DIALOG_VERSION) { - OBSPermissions check(nullptr, screen_permission, video_permission, audio_permission, - accessibility_permission); - check.exec(); - } -#endif - -#ifdef _WIN32 - if (IsRunningOnWine()) { - QMessageBox mb(QMessageBox::Question, QTStr("Wine.Title"), QTStr("Wine.Text")); - mb.setTextFormat(Qt::RichText); - mb.addButton(QTStr("AlreadyRunning.LaunchAnyway"), QMessageBox::AcceptRole); - QPushButton *closeButton = mb.addButton(QMessageBox::Close); - mb.setDefaultButton(closeButton); - - mb.exec(); - if (mb.clickedButton() == closeButton) - return 0; - } -#endif - - if (argc > 1) { - stringstream stor; - stor << argv[1]; - for (int i = 2; i < argc; ++i) { - stor << " " << argv[i]; - } - blog(LOG_INFO, "Command Line Arguments: %s", stor.str().c_str()); - } - - if (!program.OBSInit()) - return 0; - - prof.Stop(); - - ret = program.exec(); - - } catch (const char *error) { - blog(LOG_ERROR, "%s", error); - OBSErrorBox(nullptr, "%s", error); - } - - if (restart || restart_safe) { - arguments = qApp->arguments(); - - if (restart_safe) { - arguments.append("--safe-mode"); - } else { - arguments.removeAll("--safe-mode"); - } - } - - return ret; -} - -#define MAX_CRASH_REPORT_SIZE (150 * 1024) - -#ifdef _WIN32 - -#define CRASH_MESSAGE \ - "Woops, OBS has crashed!\n\nWould you like to copy the crash log " \ - "to the clipboard? The crash log will still be saved to:\n\n%s" - -static void main_crash_handler(const char *format, va_list args, void * /* param */) -{ - char *text = new char[MAX_CRASH_REPORT_SIZE]; - - vsnprintf(text, MAX_CRASH_REPORT_SIZE, format, args); - text[MAX_CRASH_REPORT_SIZE - 1] = 0; - - string crashFilePath = "obs-studio/crashes"; - - delete_oldest_file(true, crashFilePath.c_str()); - - string name = crashFilePath + "/"; - name += "Crash " + GenerateTimeDateFilename("txt"); - - BPtr path(GetAppConfigPathPtr(name.c_str())); - - fstream file; - -#ifdef _WIN32 - BPtr wpath; - os_utf8_to_wcs_ptr(path, 0, &wpath); - file.open(wpath, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary); -#else - file.open(path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary); -#endif - file << text; - file.close(); - - string pathString(path.Get()); - -#ifdef _WIN32 - std::replace(pathString.begin(), pathString.end(), '/', '\\'); -#endif - - string absolutePath = canonical(filesystem::path(pathString)).u8string(); - - size_t size = snprintf(nullptr, 0, CRASH_MESSAGE, absolutePath.c_str()); - - unique_ptr message_buffer(new char[size + 1]); - - snprintf(message_buffer.get(), size + 1, CRASH_MESSAGE, absolutePath.c_str()); - - string finalMessage = string(message_buffer.get(), message_buffer.get() + size); - - int ret = MessageBoxA(NULL, finalMessage.c_str(), "OBS has crashed!", MB_YESNO | MB_ICONERROR | MB_TASKMODAL); - - if (ret == IDYES) { - size_t len = strlen(text); - - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, len); - memcpy(GlobalLock(mem), text, len); - GlobalUnlock(mem); - - OpenClipboard(0); - EmptyClipboard(); - SetClipboardData(CF_TEXT, mem); - CloseClipboard(); - } - - exit(-1); -} - -static void load_debug_privilege(void) -{ - const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY; - TOKEN_PRIVILEGES tp; - HANDLE token; - LUID val; - - if (!OpenProcessToken(GetCurrentProcess(), flags, &token)) { - return; - } - - if (!!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &val)) { - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = val; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL); - } - - if (!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) { - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = val; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { - blog(LOG_INFO, "Could not set privilege to " - "increase GPU priority"); - } - } - - CloseHandle(token); -} -#endif - -#ifdef __APPLE__ +#if defined(__APPLE__) || defined(__linux__) #define BASE_PATH ".." #else #define BASE_PATH "../.." @@ -2351,41 +1536,6 @@ bool WindowPositionValid(QRect rect) return false; } -static inline bool arg_is(const char *arg, const char *long_form, const char *short_form) -{ - return (long_form && strcmp(arg, long_form) == 0) || (short_form && strcmp(arg, short_form) == 0); -} - -static void check_safe_mode_sentinel(void) -{ -#ifndef NDEBUG - /* Safe Mode detection is disabled in Debug builds to keep developers - * somewhat sane. */ - return; -#else - if (disable_shutdown_check) - return; - - BPtr sentinelPath = GetAppConfigPathPtr("obs-studio/safe_mode"); - if (os_file_exists(sentinelPath)) { - unclean_shutdown = true; - return; - } - - os_quick_write_utf8_file(sentinelPath, nullptr, 0, false); -#endif -} - -static void delete_safe_mode_sentinel(void) -{ -#ifndef NDEBUG - return; -#else - BPtr sentinelPath = GetAppConfigPathPtr("obs-studio/safe_mode"); - os_unlink(sentinelPath); -#endif -} - #ifndef _WIN32 void OBSApp::SigIntSignalHandler(int s) { @@ -2423,240 +1573,3 @@ void OBSApp::commitData(QSessionManager &manager) } } #endif - -#ifdef _WIN32 -static constexpr char vcRunErrorTitle[] = "Outdated Visual C++ Runtime"; -static constexpr char vcRunErrorMsg[] = "OBS Studio requires a newer version of the Microsoft Visual C++ " - "Redistributables.\n\nYou will now be directed to the download page."; -static constexpr char vcRunInstallerUrl[] = "https://obsproject.com/visual-studio-2022-runtimes"; - -static bool vc_runtime_outdated() -{ - win_version_info ver; - if (!get_dll_ver(L"msvcp140.dll", &ver)) - return true; - /* Major is always 14 (hence 140.dll), so we only care about minor. */ - if (ver.minor >= 40) - return false; - - int choice = MessageBoxA(NULL, vcRunErrorMsg, vcRunErrorTitle, MB_OKCANCEL | MB_ICONERROR | MB_TASKMODAL); - if (choice == IDOK) { - /* Open the URL in the default browser. */ - ShellExecuteA(NULL, "open", vcRunInstallerUrl, NULL, NULL, SW_SHOWNORMAL); - } - - return true; -} -#endif - -int main(int argc, char *argv[]) -{ -#ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); - - struct sigaction sig_handler; - - sig_handler.sa_handler = OBSApp::SigIntSignalHandler; - sigemptyset(&sig_handler.sa_mask); - sig_handler.sa_flags = 0; - - sigaction(SIGINT, &sig_handler, NULL); - - /* Block SIGPIPE in all threads, this can happen if a thread calls write on - a closed pipe. */ - sigset_t sigpipe_mask; - sigemptyset(&sigpipe_mask); - sigaddset(&sigpipe_mask, SIGPIPE); - sigset_t saved_mask; - if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &saved_mask) == -1) { - perror("pthread_sigmask"); - exit(1); - } -#endif - -#ifdef _WIN32 - // Abort as early as possible if MSVC runtime is outdated - if (vc_runtime_outdated()) - return 1; - // Try to keep this as early as possible - install_dll_blocklist_hook(); - - obs_init_win32_crash_handler(); - SetErrorMode(SEM_FAILCRITICALERRORS); - load_debug_privilege(); - base_set_crash_handler(main_crash_handler, nullptr); - - const HMODULE hRtwq = LoadLibrary(L"RTWorkQ.dll"); - if (hRtwq) { - typedef HRESULT(STDAPICALLTYPE * PFN_RtwqStartup)(); - PFN_RtwqStartup func = (PFN_RtwqStartup)GetProcAddress(hRtwq, "RtwqStartup"); - func(); - } -#endif - - base_get_log_handler(&def_log_handler, nullptr); - -#if defined(__FreeBSD__) - move_to_xdg(); -#endif - - obs_set_cmdline_args(argc, argv); - - for (int i = 1; i < argc; i++) { - if (arg_is(argv[i], "--multi", "-m")) { - multi = true; - disable_shutdown_check = true; - -#if ALLOW_PORTABLE_MODE - } else if (arg_is(argv[i], "--portable", "-p")) { - portable_mode = true; - -#endif - } else if (arg_is(argv[i], "--verbose", nullptr)) { - log_verbose = true; - - } else if (arg_is(argv[i], "--safe-mode", nullptr)) { - safe_mode = true; - - } else if (arg_is(argv[i], "--only-bundled-plugins", nullptr)) { - disable_3p_plugins = true; - - } else if (arg_is(argv[i], "--disable-shutdown-check", nullptr)) { - /* This exists mostly to bypass the dialog during development. */ - disable_shutdown_check = true; - - } else if (arg_is(argv[i], "--always-on-top", nullptr)) { - opt_always_on_top = true; - - } else if (arg_is(argv[i], "--unfiltered_log", nullptr)) { - unfiltered_log = true; - - } else if (arg_is(argv[i], "--startstreaming", nullptr)) { - opt_start_streaming = true; - - } else if (arg_is(argv[i], "--startrecording", nullptr)) { - opt_start_recording = true; - - } else if (arg_is(argv[i], "--startreplaybuffer", nullptr)) { - opt_start_replaybuffer = true; - - } else if (arg_is(argv[i], "--startvirtualcam", nullptr)) { - opt_start_virtualcam = true; - - } else if (arg_is(argv[i], "--collection", nullptr)) { - if (++i < argc) - opt_starting_collection = argv[i]; - - } else if (arg_is(argv[i], "--profile", nullptr)) { - if (++i < argc) - opt_starting_profile = argv[i]; - - } else if (arg_is(argv[i], "--scene", nullptr)) { - if (++i < argc) - opt_starting_scene = argv[i]; - - } else if (arg_is(argv[i], "--minimize-to-tray", nullptr)) { - opt_minimize_tray = true; - - } else if (arg_is(argv[i], "--studio-mode", nullptr)) { - opt_studio_mode = true; - - } else if (arg_is(argv[i], "--allow-opengl", nullptr)) { - opt_allow_opengl = true; - - } else if (arg_is(argv[i], "--disable-updater", nullptr)) { - opt_disable_updater = true; - - } else if (arg_is(argv[i], "--disable-missing-files-check", nullptr)) { - opt_disable_missing_files_check = true; - - } else if (arg_is(argv[i], "--steam", nullptr)) { - steam = true; - - } else if (arg_is(argv[i], "--help", "-h")) { - std::string help = - "--help, -h: Get list of available commands.\n\n" - "--startstreaming: Automatically start streaming.\n" - "--startrecording: Automatically start recording.\n" - "--startreplaybuffer: Start replay buffer.\n" - "--startvirtualcam: Start virtual camera (if available).\n\n" - "--collection : Use specific scene collection." - "\n" - "--profile : Use specific profile.\n" - "--scene : Start with specific scene.\n\n" - "--studio-mode: Enable studio mode.\n" - "--minimize-to-tray: Minimize to system tray.\n" -#if ALLOW_PORTABLE_MODE - "--portable, -p: Use portable mode.\n" -#endif - "--multi, -m: Don't warn when launching multiple instances.\n\n" - "--safe-mode: Run in Safe Mode (disables third-party plugins, scripting, and WebSockets).\n" - "--only-bundled-plugins: Only load included (first-party) plugins\n" - "--disable-shutdown-check: Disable unclean shutdown detection.\n" - "--verbose: Make log more verbose.\n" - "--always-on-top: Start in 'always on top' mode.\n\n" - "--unfiltered_log: Make log unfiltered.\n\n" - "--disable-updater: Disable built-in updater (Windows/Mac only)\n\n" - "--disable-missing-files-check: Disable the missing files dialog which can appear on startup.\n\n"; - -#ifdef _WIN32 - MessageBoxA(NULL, help.c_str(), "Help", MB_OK | MB_ICONASTERISK); -#else - std::cout << help << "--version, -V: Get current version.\n"; -#endif - exit(0); - - } else if (arg_is(argv[i], "--version", "-V")) { - std::cout << "OBS Studio - " << App()->GetVersionString(false) << "\n"; - exit(0); - } - } - -#if ALLOW_PORTABLE_MODE - if (!portable_mode) { - portable_mode = os_file_exists(BASE_PATH "/portable_mode") || - os_file_exists(BASE_PATH "/obs_portable_mode") || - os_file_exists(BASE_PATH "/portable_mode.txt") || - os_file_exists(BASE_PATH "/obs_portable_mode.txt"); - } - - if (!opt_disable_updater) { - opt_disable_updater = os_file_exists(BASE_PATH "/disable_updater") || - os_file_exists(BASE_PATH "/disable_updater.txt"); - } - - if (!opt_disable_missing_files_check) { - opt_disable_missing_files_check = os_file_exists(BASE_PATH "/disable_missing_files_check") || - os_file_exists(BASE_PATH "/disable_missing_files_check.txt"); - } -#endif - - check_safe_mode_sentinel(); - - fstream logFile; - - curl_global_init(CURL_GLOBAL_ALL); - int ret = run_program(logFile, argc, argv); - -#ifdef _WIN32 - if (hRtwq) { - typedef HRESULT(STDAPICALLTYPE * PFN_RtwqShutdown)(); - PFN_RtwqShutdown func = (PFN_RtwqShutdown)GetProcAddress(hRtwq, "RtwqShutdown"); - func(); - FreeLibrary(hRtwq); - } - - log_blocked_dlls(); -#endif - - delete_safe_mode_sentinel(); - blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs()); - base_set_log_handler(nullptr, nullptr); - - if (restart || restart_safe) { - auto executable = arguments.takeFirst(); - QProcess::startDetached(executable, arguments); - } - - return ret; -} diff --git a/frontend/OBSApp.hpp b/frontend/OBSApp.hpp index dea5e530b..ce0c4fb65 100644 --- a/frontend/OBSApp.hpp +++ b/frontend/OBSApp.hpp @@ -17,63 +17,30 @@ #pragma once -#include -#include -#include -#include +#include +#include -#ifndef _WIN32 -#include -#else -#include -#endif -#include -#include -#include -#include -#include #include +#include +#include +#include + +#include +#include +#include + +#include #include #include -#include #include -#include -#include - -#include "window-main.hpp" -#include "obs-app-theming.hpp" - -std::string CurrentTimeString(); -std::string CurrentDateTimeString(); -std::string GenerateTimeDateFilename(const char *extension, bool noSpace = false); -std::string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format); -std::string GetFormatString(const char *format, const char *prefix, const char *suffix); -std::string GetFormatExt(const char *container); -std::string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, - const char *format); -QObject *CreateShortcutFilter(); - -struct BaseLexer { - lexer lex; - -public: - inline BaseLexer() { lexer_init(&lex); } - inline ~BaseLexer() { lexer_free(&lex); } - operator lexer *() { return &lex; } -}; - -class OBSTranslator : public QTranslator { - Q_OBJECT - -public: - virtual bool isEmpty() const override { return false; } - - virtual QString translate(const char *context, const char *sourceText, const char *disambiguation, - int n) const override; -}; typedef std::function VoidFunc; +Q_DECLARE_METATYPE(VoidFunc) + +class QFileSystemWatcher; +class QSocketNotifier; + struct UpdateBranch { QString name; QString display_name; @@ -233,9 +200,6 @@ signals: int GetAppConfigPath(char *path, size_t size, const char *name); char *GetAppConfigPathPtr(const char *name); -int GetProgramDataPath(char *path, size_t size, const char *name); -char *GetProgramDataPathPtr(const char *name); - inline OBSApp *App() { return static_cast(qApp); @@ -251,30 +215,23 @@ inline QString QTStr(const char *lookupVal) return QString::fromUtf8(Str(lookupVal)); } +int GetProgramDataPath(char *path, size_t size, const char *name); +char *GetProgramDataPathPtr(const char *name); + bool GetFileSafeName(const char *name, std::string &file); bool GetClosestUnusedFileName(std::string &path, const char *extension); -bool GetUnusedSceneCollectionFile(std::string &name, std::string &file); bool WindowPositionValid(QRect rect); -extern bool portable_mode; -extern bool steam; -extern bool safe_mode; -extern bool disable_3p_plugins; - -extern bool opt_start_streaming; -extern bool opt_start_recording; -extern bool opt_start_replaybuffer; -extern bool opt_start_virtualcam; -extern bool opt_minimize_tray; -extern bool opt_studio_mode; -extern bool opt_allow_opengl; -extern bool opt_always_on_top; -extern std::string opt_starting_scene; -extern bool restart; -extern bool restart_safe; - #ifdef _WIN32 extern "C" void install_dll_blocklist_hook(void); extern "C" void log_blocked_dlls(void); #endif + +std::string CurrentDateTimeString(); +std::string GetFormatString(const char *format, const char *prefix, const char *suffix); +std::string GenerateTimeDateFilename(const char *extension, bool noSpace = false); +std::string GetFormatExt(const char *container); +std::string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, + const char *format); +QObject *CreateShortcutFilter(); diff --git a/frontend/OBSApp_Themes.cpp b/frontend/OBSApp_Themes.cpp index 9698ac455..b4206bb66 100644 --- a/frontend/OBSApp_Themes.cpp +++ b/frontend/OBSApp_Themes.cpp @@ -15,25 +15,23 @@ along with this program. If not, see . ******************************************************************************/ -#include +#include "OBSApp.hpp" +#include +#include +#include + +#include +#include #include #include -#include -#include -#include #include -#include +#include +#include +#include #include - -#include "qt-wrappers.hpp" -#include "obs-app.hpp" -#include "obs-app-theming.hpp" -#include "obs-proxy-style.hpp" -#include "platform.hpp" - -#include "ui-config.h" +#include using namespace std; diff --git a/frontend/OBSStudioAPI.cpp b/frontend/OBSStudioAPI.cpp index bb3715017..6e84fdaed 100644 --- a/frontend/OBSStudioAPI.cpp +++ b/frontend/OBSStudioAPI.cpp @@ -1,20 +1,9 @@ -#include +#include "OBSStudioAPI.hpp" + +#include +#include + #include -#include "obs-app.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" - -#include - -using namespace std; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSource); - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} extern volatile bool streaming_active; extern volatile bool recording_active; @@ -22,15 +11,6 @@ extern volatile bool recording_paused; extern volatile bool replaybuf_active; extern volatile bool virtualcam_active; -/* ------------------------------------------------------------------------- */ - -template struct OBSStudioCallback { - T callback; - void *private_data; - - inline OBSStudioCallback(T cb, void *p) : callback(cb), private_data(p) {} -}; - template inline size_t GetCallbackIdx(vector> &callbacks, T callback, void *private_data) { @@ -43,590 +23,700 @@ inline size_t GetCallbackIdx(vector> &callbacks, T callback return (size_t)-1; } -struct OBSStudioAPI : obs_frontend_callbacks { - OBSBasic *main; - vector> callbacks; - vector> saveCallbacks; - vector> preloadCallbacks; +void *OBSStudioAPI::obs_frontend_get_main_window() +{ + return (void *)main; +} - inline OBSStudioAPI(OBSBasic *main_) : main(main_) {} +void *OBSStudioAPI::obs_frontend_get_main_window_handle() +{ + return (void *)main->winId(); +} - void *obs_frontend_get_main_window(void) override { return (void *)main; } +void *OBSStudioAPI::obs_frontend_get_system_tray() +{ + return (void *)main->trayIcon.data(); +} - void *obs_frontend_get_main_window_handle(void) override { return (void *)main->winId(); } +void OBSStudioAPI::obs_frontend_get_scenes(struct obs_frontend_source_list *sources) +{ + for (int i = 0; i < main->ui->scenes->count(); i++) { + QListWidgetItem *item = main->ui->scenes->item(i); + OBSScene scene = GetOBSRef(item); + obs_source_t *source = obs_scene_get_source(scene); - void *obs_frontend_get_system_tray(void) override { return (void *)main->trayIcon.data(); } - - void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) override - { - for (int i = 0; i < main->ui->scenes->count(); i++) { - QListWidgetItem *item = main->ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - obs_source_t *source = obs_scene_get_source(scene); - - if (obs_source_get_ref(source) != nullptr) - da_push_back(sources->sources, &source); - } + if (obs_source_get_ref(source) != nullptr) + da_push_back(sources->sources, &source); } +} - obs_source_t *obs_frontend_get_current_scene(void) override - { - if (main->IsPreviewProgramMode()) { - return obs_weak_source_get_source(main->programScene); - } else { - OBSSource source = main->GetCurrentSceneSource(); - return obs_source_get_ref(source); - } +obs_source_t *OBSStudioAPI::obs_frontend_get_current_scene() +{ + if (main->IsPreviewProgramMode()) { + return obs_weak_source_get_source(main->programScene); + } else { + OBSSource source = main->GetCurrentSceneSource(); + return obs_source_get_ref(source); } +} - void obs_frontend_set_current_scene(obs_source_t *scene) override - { - if (main->IsPreviewProgramMode()) { - QMetaObject::invokeMethod(main, "TransitionToScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(scene))); - } else { - QMetaObject::invokeMethod(main, "SetCurrentScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(scene)), Q_ARG(bool, false)); - } +void OBSStudioAPI::obs_frontend_set_current_scene(obs_source_t *scene) +{ + if (main->IsPreviewProgramMode()) { + QMetaObject::invokeMethod(main, "TransitionToScene", WaitConnection(), + Q_ARG(OBSSource, OBSSource(scene))); + } else { + QMetaObject::invokeMethod(main, "SetCurrentScene", WaitConnection(), Q_ARG(OBSSource, OBSSource(scene)), + Q_ARG(bool, false)); } +} - void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) override - { - for (int i = 0; i < main->ui->transitions->count(); i++) { - OBSSource tr = main->ui->transitions->itemData(i).value(); +void OBSStudioAPI::obs_frontend_get_transitions(struct obs_frontend_source_list *sources) +{ + for (int i = 0; i < main->ui->transitions->count(); i++) { + OBSSource tr = main->ui->transitions->itemData(i).value(); - if (!tr) - continue; + if (!tr) + continue; - if (obs_source_get_ref(tr) != nullptr) - da_push_back(sources->sources, &tr); - } + if (obs_source_get_ref(tr) != nullptr) + da_push_back(sources->sources, &tr); } +} - obs_source_t *obs_frontend_get_current_transition(void) override - { - OBSSource tr = main->GetCurrentTransition(); - return obs_source_get_ref(tr); +obs_source_t *OBSStudioAPI::obs_frontend_get_current_transition() +{ + OBSSource tr = main->GetCurrentTransition(); + return obs_source_get_ref(tr); +} + +void OBSStudioAPI::obs_frontend_set_current_transition(obs_source_t *transition) +{ + QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition))); +} + +int OBSStudioAPI::obs_frontend_get_transition_duration() +{ + return main->ui->transitionDuration->value(); +} + +void OBSStudioAPI::obs_frontend_set_transition_duration(int duration) +{ + QMetaObject::invokeMethod(main->ui->transitionDuration, "setValue", Q_ARG(int, duration)); +} + +void OBSStudioAPI::obs_frontend_release_tbar() +{ + QMetaObject::invokeMethod(main, "TBarReleased"); +} + +void OBSStudioAPI::obs_frontend_set_tbar_position(int position) +{ + QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position)); +} + +int OBSStudioAPI::obs_frontend_get_tbar_position() +{ + return main->tBar->value(); +} + +void OBSStudioAPI::obs_frontend_get_scene_collections(std::vector &strings) +{ + for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) { + strings.emplace_back(collectionName); } +} - void obs_frontend_set_current_transition(obs_source_t *transition) override - { - QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition))); - } +char *OBSStudioAPI::obs_frontend_get_current_scene_collection() +{ + const OBSSceneCollection ¤tCollection = main->GetCurrentSceneCollection(); + return bstrdup(currentCollection.name.c_str()); +} - int obs_frontend_get_transition_duration(void) override { return main->ui->transitionDuration->value(); } +void OBSStudioAPI::obs_frontend_set_current_scene_collection(const char *collection) +{ + QList menuActions = main->ui->sceneCollectionMenu->actions(); + QString qstrCollection = QT_UTF8(collection); - void obs_frontend_set_transition_duration(int duration) override - { - QMetaObject::invokeMethod(main->ui->transitionDuration, "setValue", Q_ARG(int, duration)); - } + for (int i = 0; i < menuActions.count(); i++) { + QAction *action = menuActions[i]; + QVariant v = action->property("file_name"); - void obs_frontend_release_tbar(void) override { QMetaObject::invokeMethod(main, "TBarReleased"); } - - void obs_frontend_set_tbar_position(int position) override - { - QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position)); - } - - int obs_frontend_get_tbar_position(void) override { return main->tBar->value(); } - - void obs_frontend_get_scene_collections(std::vector &strings) override - { - for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) { - strings.emplace_back(collectionName); - } - } - - char *obs_frontend_get_current_scene_collection(void) override - { - const OBSSceneCollection ¤tCollection = main->GetCurrentSceneCollection(); - return bstrdup(currentCollection.name.c_str()); - } - - void obs_frontend_set_current_scene_collection(const char *collection) override - { - QList menuActions = main->ui->sceneCollectionMenu->actions(); - QString qstrCollection = QT_UTF8(collection); - - for (int i = 0; i < menuActions.count(); i++) { - QAction *action = menuActions[i]; - QVariant v = action->property("file_name"); - - if (v.typeName() != nullptr) { - if (action->text() == qstrCollection) { - action->trigger(); - break; - } + if (v.typeName() != nullptr) { + if (action->text() == qstrCollection) { + action->trigger(); + break; } } } +} - bool obs_frontend_add_scene_collection(const char *name) override - { - bool success = false; - QMetaObject::invokeMethod(main, "CreateNewSceneCollection", WaitConnection(), - Q_RETURN_ARG(bool, success), Q_ARG(QString, QT_UTF8(name))); - return success; +bool OBSStudioAPI::obs_frontend_add_scene_collection(const char *name) +{ + bool success = false; + QMetaObject::invokeMethod(main, "CreateNewSceneCollection", WaitConnection(), Q_RETURN_ARG(bool, success), + Q_ARG(QString, QT_UTF8(name))); + return success; +} + +void OBSStudioAPI::obs_frontend_get_profiles(std::vector &strings) +{ + const OBSProfileCache &profiles = main->GetProfileCache(); + + for (auto &[profileName, profile] : profiles) { + strings.emplace_back(profileName); } +} - void obs_frontend_get_profiles(std::vector &strings) override - { - const OBSProfileCache &profiles = main->GetProfileCache(); +char *OBSStudioAPI::obs_frontend_get_current_profile() +{ + const OBSProfile &profile = main->GetCurrentProfile(); + return bstrdup(profile.name.c_str()); +} - for (auto &[profileName, profile] : profiles) { - strings.emplace_back(profileName); - } - } +char *OBSStudioAPI::obs_frontend_get_current_profile_path() +{ + const OBSProfile &profile = main->GetCurrentProfile(); - char *obs_frontend_get_current_profile(void) override - { - const OBSProfile &profile = main->GetCurrentProfile(); - return bstrdup(profile.name.c_str()); - } + return bstrdup(profile.path.u8string().c_str()); +} - char *obs_frontend_get_current_profile_path(void) override - { - const OBSProfile &profile = main->GetCurrentProfile(); +void OBSStudioAPI::obs_frontend_set_current_profile(const char *profile) +{ + QList menuActions = main->ui->profileMenu->actions(); + QString qstrProfile = QT_UTF8(profile); - return bstrdup(profile.path.u8string().c_str()); - } + for (int i = 0; i < menuActions.count(); i++) { + QAction *action = menuActions[i]; + QVariant v = action->property("file_name"); - void obs_frontend_set_current_profile(const char *profile) override - { - QList menuActions = main->ui->profileMenu->actions(); - QString qstrProfile = QT_UTF8(profile); - - for (int i = 0; i < menuActions.count(); i++) { - QAction *action = menuActions[i]; - QVariant v = action->property("file_name"); - - if (v.typeName() != nullptr) { - if (action->text() == qstrProfile) { - action->trigger(); - break; - } + if (v.typeName() != nullptr) { + if (action->text() == qstrProfile) { + action->trigger(); + break; } } } +} - void obs_frontend_create_profile(const char *name) override - { - QMetaObject::invokeMethod(main, "CreateNewProfile", Q_ARG(QString, name)); - } +void OBSStudioAPI::obs_frontend_create_profile(const char *name) +{ + QMetaObject::invokeMethod(main, "CreateNewProfile", Q_ARG(QString, name)); +} - void obs_frontend_duplicate_profile(const char *name) override - { - QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name)); - } +void OBSStudioAPI::obs_frontend_duplicate_profile(const char *name) +{ + QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name)); +} - void obs_frontend_delete_profile(const char *profile) override - { - QMetaObject::invokeMethod(main, "DeleteProfile", Q_ARG(QString, profile)); - } +void OBSStudioAPI::obs_frontend_delete_profile(const char *profile) +{ + QMetaObject::invokeMethod(main, "DeleteProfile", Q_ARG(QString, profile)); +} - void obs_frontend_streaming_start(void) override { QMetaObject::invokeMethod(main, "StartStreaming"); } +void OBSStudioAPI::obs_frontend_streaming_start() +{ + QMetaObject::invokeMethod(main, "StartStreaming"); +} - void obs_frontend_streaming_stop(void) override { QMetaObject::invokeMethod(main, "StopStreaming"); } +void OBSStudioAPI::obs_frontend_streaming_stop() +{ + QMetaObject::invokeMethod(main, "StopStreaming"); +} - bool obs_frontend_streaming_active(void) override { return os_atomic_load_bool(&streaming_active); } +bool OBSStudioAPI::obs_frontend_streaming_active() +{ + return os_atomic_load_bool(&streaming_active); +} - void obs_frontend_recording_start(void) override { QMetaObject::invokeMethod(main, "StartRecording"); } +void OBSStudioAPI::obs_frontend_recording_start() +{ + QMetaObject::invokeMethod(main, "StartRecording"); +} - void obs_frontend_recording_stop(void) override { QMetaObject::invokeMethod(main, "StopRecording"); } +void OBSStudioAPI::obs_frontend_recording_stop() +{ + QMetaObject::invokeMethod(main, "StopRecording"); +} - bool obs_frontend_recording_active(void) override { return os_atomic_load_bool(&recording_active); } +bool OBSStudioAPI::obs_frontend_recording_active() +{ + return os_atomic_load_bool(&recording_active); +} - void obs_frontend_recording_pause(bool pause) override - { - QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording"); - } +void OBSStudioAPI::obs_frontend_recording_pause(bool pause) +{ + QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording"); +} - bool obs_frontend_recording_paused(void) override { return os_atomic_load_bool(&recording_paused); } - - bool obs_frontend_recording_split_file(void) override - { - if (os_atomic_load_bool(&recording_active) && !os_atomic_load_bool(&recording_paused)) { - proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput); - uint8_t stack[128]; - calldata cd; - calldata_init_fixed(&cd, stack, sizeof(stack)); - proc_handler_call(ph, "split_file", &cd); - bool result = calldata_bool(&cd, "split_file_enabled"); - return result; - } else { - return false; - } - } - - bool obs_frontend_recording_add_chapter(const char *name) override - { - if (!os_atomic_load_bool(&recording_active) || os_atomic_load_bool(&recording_paused)) - return false; +bool OBSStudioAPI::obs_frontend_recording_paused() +{ + return os_atomic_load_bool(&recording_paused); +} +bool OBSStudioAPI::obs_frontend_recording_split_file() +{ + if (os_atomic_load_bool(&recording_active) && !os_atomic_load_bool(&recording_paused)) { proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput); - + uint8_t stack[128]; calldata cd; - calldata_init(&cd); - calldata_set_string(&cd, "chapter_name", name); - bool result = proc_handler_call(ph, "add_chapter", &cd); - calldata_free(&cd); + calldata_init_fixed(&cd, stack, sizeof(stack)); + proc_handler_call(ph, "split_file", &cd); + bool result = calldata_bool(&cd, "split_file_enabled"); return result; + } else { + return false; + } +} + +bool OBSStudioAPI::obs_frontend_recording_add_chapter(const char *name) +{ + if (!os_atomic_load_bool(&recording_active) || os_atomic_load_bool(&recording_paused)) + return false; + + proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput); + + calldata cd; + calldata_init(&cd); + calldata_set_string(&cd, "chapter_name", name); + bool result = proc_handler_call(ph, "add_chapter", &cd); + calldata_free(&cd); + return result; +} + +void OBSStudioAPI::obs_frontend_replay_buffer_start() +{ + QMetaObject::invokeMethod(main, "StartReplayBuffer"); +} + +void OBSStudioAPI::obs_frontend_replay_buffer_save() +{ + QMetaObject::invokeMethod(main, "ReplayBufferSave"); +} + +void OBSStudioAPI::obs_frontend_replay_buffer_stop() +{ + QMetaObject::invokeMethod(main, "StopReplayBuffer"); +} + +bool OBSStudioAPI::obs_frontend_replay_buffer_active() +{ + return os_atomic_load_bool(&replaybuf_active); +} + +void *OBSStudioAPI::obs_frontend_add_tools_menu_qaction(const char *name) +{ + main->ui->menuTools->setEnabled(true); + return (void *)main->ui->menuTools->addAction(QT_UTF8(name)); +} + +void OBSStudioAPI::obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) +{ + main->ui->menuTools->setEnabled(true); + + auto func = [private_data, callback]() { + callback(private_data); + }; + + QAction *action = main->ui->menuTools->addAction(QT_UTF8(name)); + QObject::connect(action, &QAction::triggered, func); +} + +void *OBSStudioAPI::obs_frontend_add_dock(void *dock) +{ + QDockWidget *d = reinterpret_cast(dock); + + QString name = d->objectName(); + if (name.isEmpty() || main->IsDockObjectNameUsed(name)) { + blog(LOG_WARNING, "The object name of the added dock is empty or already used," + " a temporary one will be set to avoid conflicts"); + + char *uuid = os_generate_uuid(); + name = QT_UTF8(uuid); + bfree(uuid); + name.append("_oldExtraDock"); + + d->setObjectName(name); } - void obs_frontend_replay_buffer_start(void) override { QMetaObject::invokeMethod(main, "StartReplayBuffer"); } + return (void *)main->AddDockWidget(d); +} - void obs_frontend_replay_buffer_save(void) override { QMetaObject::invokeMethod(main, "ReplayBufferSave"); } - - void obs_frontend_replay_buffer_stop(void) override { QMetaObject::invokeMethod(main, "StopReplayBuffer"); } - - bool obs_frontend_replay_buffer_active(void) override { return os_atomic_load_bool(&replaybuf_active); } - - void *obs_frontend_add_tools_menu_qaction(const char *name) override - { - main->ui->menuTools->setEnabled(true); - return (void *)main->ui->menuTools->addAction(QT_UTF8(name)); - } - - void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override - { - main->ui->menuTools->setEnabled(true); - - auto func = [private_data, callback]() { - callback(private_data); - }; - - QAction *action = main->ui->menuTools->addAction(QT_UTF8(name)); - QObject::connect(action, &QAction::triggered, func); - } - - void *obs_frontend_add_dock(void *dock) override - { - QDockWidget *d = reinterpret_cast(dock); - - QString name = d->objectName(); - if (name.isEmpty() || main->IsDockObjectNameUsed(name)) { - blog(LOG_WARNING, "The object name of the added dock is empty or already used," - " a temporary one will be set to avoid conflicts"); - - char *uuid = os_generate_uuid(); - name = QT_UTF8(uuid); - bfree(uuid); - name.append("_oldExtraDock"); - - d->setObjectName(name); - } - - return (void *)main->AddDockWidget(d); - } - - bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) override - { - if (main->IsDockObjectNameUsed(QT_UTF8(id))) { - blog(LOG_WARNING, - "Dock id '%s' already used! " - "Duplicate library?", - id); - return false; - } - - OBSDock *dock = new OBSDock(main); - dock->setWidget((QWidget *)widget); - dock->setWindowTitle(QT_UTF8(title)); - dock->setObjectName(QT_UTF8(id)); - - main->AddDockWidget(dock, Qt::RightDockWidgetArea); - - dock->setVisible(false); - dock->setFloating(true); - - return true; - } - - void obs_frontend_remove_dock(const char *id) override { main->RemoveDockWidget(QT_UTF8(id)); } - - bool obs_frontend_add_custom_qdock(const char *id, void *dock) override - { - if (main->IsDockObjectNameUsed(QT_UTF8(id))) { - blog(LOG_WARNING, - "Dock id '%s' already used! " - "Duplicate library?", - id); - return false; - } - - QDockWidget *d = reinterpret_cast(dock); - d->setObjectName(QT_UTF8(id)); - - main->AddCustomDockWidget(d); - - return true; - } - - void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(callbacks, callback, private_data); - if (idx == (size_t)-1) - callbacks.emplace_back(callback, private_data); - } - - void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(callbacks, callback, private_data); - if (idx == (size_t)-1) - return; - - callbacks.erase(callbacks.begin() + idx); - } - - obs_output_t *obs_frontend_get_streaming_output(void) override - { - auto multitrackVideo = main->outputHandler->multitrackVideo.get(); - auto mtvOutput = multitrackVideo ? obs_output_get_ref(multitrackVideo->StreamingOutput()) : nullptr; - if (mtvOutput) - return mtvOutput; - - OBSOutput output = main->outputHandler->streamOutput.Get(); - return obs_output_get_ref(output); - } - - obs_output_t *obs_frontend_get_recording_output(void) override - { - OBSOutput out = main->outputHandler->fileOutput.Get(); - return obs_output_get_ref(out); - } - - obs_output_t *obs_frontend_get_replay_buffer_output(void) override - { - OBSOutput out = main->outputHandler->replayBuffer.Get(); - return obs_output_get_ref(out); - } - - config_t *obs_frontend_get_profile_config(void) override { return main->activeConfiguration; } - - config_t *obs_frontend_get_global_config(void) override - { +bool OBSStudioAPI::obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) +{ + if (main->IsDockObjectNameUsed(QT_UTF8(id))) { blog(LOG_WARNING, - "DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead."); - return App()->GetAppConfig(); + "Dock id '%s' already used! " + "Duplicate library?", + id); + return false; } - config_t *obs_frontend_get_app_config(void) override { return App()->GetAppConfig(); } + OBSDock *dock = new OBSDock(main); + dock->setWidget((QWidget *)widget); + dock->setWindowTitle(QT_UTF8(title)); + dock->setObjectName(QT_UTF8(id)); - config_t *obs_frontend_get_user_config(void) override { return App()->GetUserConfig(); } + main->AddDockWidget(dock, Qt::RightDockWidgetArea); - void obs_frontend_open_projector(const char *type, int monitor, const char *geometry, const char *name) override - { - SavedProjectorInfo proj = { - ProjectorType::Preview, - monitor, - geometry ? geometry : "", - name ? name : "", - }; - if (type) { - if (astrcmpi(type, "Source") == 0) - proj.type = ProjectorType::Source; - else if (astrcmpi(type, "Scene") == 0) - proj.type = ProjectorType::Scene; - else if (astrcmpi(type, "StudioProgram") == 0) - proj.type = ProjectorType::StudioProgram; - else if (astrcmpi(type, "Multiview") == 0) - proj.type = ProjectorType::Multiview; - } - QMetaObject::invokeMethod(main, "OpenSavedProjector", WaitConnection(), - Q_ARG(SavedProjectorInfo *, &proj)); + dock->setVisible(false); + dock->setFloating(true); + + return true; +} + +void OBSStudioAPI::obs_frontend_remove_dock(const char *id) +{ + main->RemoveDockWidget(QT_UTF8(id)); +} + +bool OBSStudioAPI::obs_frontend_add_custom_qdock(const char *id, void *dock) +{ + if (main->IsDockObjectNameUsed(QT_UTF8(id))) { + blog(LOG_WARNING, + "Dock id '%s' already used! " + "Duplicate library?", + id); + return false; } - void obs_frontend_save(void) override { main->SaveProject(); } + QDockWidget *d = reinterpret_cast(dock); + d->setObjectName(QT_UTF8(id)); - void obs_frontend_defer_save_begin(void) override { QMetaObject::invokeMethod(main, "DeferSaveBegin"); } + main->AddCustomDockWidget(d); - void obs_frontend_defer_save_end(void) override { QMetaObject::invokeMethod(main, "DeferSaveEnd"); } + return true; +} - void obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data); - if (idx == (size_t)-1) - saveCallbacks.emplace_back(callback, private_data); +void OBSStudioAPI::obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) +{ + size_t idx = GetCallbackIdx(callbacks, callback, private_data); + if (idx == (size_t)-1) + callbacks.emplace_back(callback, private_data); +} + +void OBSStudioAPI::obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) +{ + size_t idx = GetCallbackIdx(callbacks, callback, private_data); + if (idx == (size_t)-1) + return; + + callbacks.erase(callbacks.begin() + idx); +} + +obs_output_t *OBSStudioAPI::obs_frontend_get_streaming_output() +{ + auto multitrackVideo = main->outputHandler->multitrackVideo.get(); + auto mtvOutput = multitrackVideo ? obs_output_get_ref(multitrackVideo->StreamingOutput()) : nullptr; + if (mtvOutput) + return mtvOutput; + + OBSOutput output = main->outputHandler->streamOutput.Get(); + return obs_output_get_ref(output); +} + +obs_output_t *OBSStudioAPI::obs_frontend_get_recording_output() +{ + OBSOutput out = main->outputHandler->fileOutput.Get(); + return obs_output_get_ref(out); +} + +obs_output_t *OBSStudioAPI::obs_frontend_get_replay_buffer_output() +{ + OBSOutput out = main->outputHandler->replayBuffer.Get(); + return obs_output_get_ref(out); +} + +config_t *OBSStudioAPI::obs_frontend_get_profile_config() +{ + return main->activeConfiguration; +} + +config_t *OBSStudioAPI::obs_frontend_get_global_config() +{ + blog(LOG_WARNING, + "DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead."); + return App()->GetAppConfig(); +} + +config_t *OBSStudioAPI::obs_frontend_get_app_config() +{ + return App()->GetAppConfig(); +} + +config_t *OBSStudioAPI::obs_frontend_get_user_config() +{ + return App()->GetUserConfig(); +} + +void OBSStudioAPI::obs_frontend_open_projector(const char *type, int monitor, const char *geometry, const char *name) +{ + SavedProjectorInfo proj = { + ProjectorType::Preview, + monitor, + geometry ? geometry : "", + name ? name : "", + }; + if (type) { + if (astrcmpi(type, "Source") == 0) + proj.type = ProjectorType::Source; + else if (astrcmpi(type, "Scene") == 0) + proj.type = ProjectorType::Scene; + else if (astrcmpi(type, "StudioProgram") == 0) + proj.type = ProjectorType::StudioProgram; + else if (astrcmpi(type, "Multiview") == 0) + proj.type = ProjectorType::Multiview; + } + QMetaObject::invokeMethod(main, "OpenSavedProjector", WaitConnection(), Q_ARG(SavedProjectorInfo *, &proj)); +} + +void OBSStudioAPI::obs_frontend_save() +{ + main->SaveProject(); +} + +void OBSStudioAPI::obs_frontend_defer_save_begin() +{ + QMetaObject::invokeMethod(main, "DeferSaveBegin"); +} + +void OBSStudioAPI::obs_frontend_defer_save_end() +{ + QMetaObject::invokeMethod(main, "DeferSaveEnd"); +} + +void OBSStudioAPI::obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) +{ + size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data); + if (idx == (size_t)-1) + saveCallbacks.emplace_back(callback, private_data); +} + +void OBSStudioAPI::obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) +{ + size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data); + if (idx == (size_t)-1) + return; + + saveCallbacks.erase(saveCallbacks.begin() + idx); +} + +void OBSStudioAPI::obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) +{ + size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data); + if (idx == (size_t)-1) + preloadCallbacks.emplace_back(callback, private_data); +} + +void OBSStudioAPI::obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) +{ + size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data); + if (idx == (size_t)-1) + return; + + preloadCallbacks.erase(preloadCallbacks.begin() + idx); +} + +void OBSStudioAPI::obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) +{ + App()->PushUITranslation(translate); +} + +void OBSStudioAPI::obs_frontend_pop_ui_translation() +{ + App()->PopUITranslation(); +} + +void OBSStudioAPI::obs_frontend_set_streaming_service(obs_service_t *service) +{ + main->SetService(service); +} + +obs_service_t *OBSStudioAPI::obs_frontend_get_streaming_service() +{ + return main->GetService(); +} + +void OBSStudioAPI::obs_frontend_save_streaming_service() +{ + main->SaveService(); +} + +bool OBSStudioAPI::obs_frontend_preview_program_mode_active() +{ + return main->IsPreviewProgramMode(); +} + +void OBSStudioAPI::obs_frontend_set_preview_program_mode(bool enable) +{ + main->SetPreviewProgramMode(enable); +} + +void OBSStudioAPI::obs_frontend_preview_program_trigger_transition() +{ + QMetaObject::invokeMethod(main, "TransitionClicked"); +} + +bool OBSStudioAPI::obs_frontend_preview_enabled() +{ + return main->previewEnabled; +} + +void OBSStudioAPI::obs_frontend_set_preview_enabled(bool enable) +{ + if (main->previewEnabled != enable) + main->EnablePreviewDisplay(enable); +} + +obs_source_t *OBSStudioAPI::obs_frontend_get_current_preview_scene() +{ + if (main->IsPreviewProgramMode()) { + OBSSource source = main->GetCurrentSceneSource(); + return obs_source_get_ref(source); } - void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data); - if (idx == (size_t)-1) - return; + return nullptr; +} - saveCallbacks.erase(saveCallbacks.begin() + idx); +void OBSStudioAPI::obs_frontend_set_current_preview_scene(obs_source_t *scene) +{ + if (main->IsPreviewProgramMode()) { + QMetaObject::invokeMethod(main, "SetCurrentScene", Q_ARG(OBSSource, OBSSource(scene)), + Q_ARG(bool, false)); } +} - void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data); - if (idx == (size_t)-1) - preloadCallbacks.emplace_back(callback, private_data); +void OBSStudioAPI::obs_frontend_take_screenshot() +{ + QMetaObject::invokeMethod(main, "Screenshot"); +} + +void OBSStudioAPI::obs_frontend_take_source_screenshot(obs_source_t *source) +{ + QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source))); +} + +obs_output_t *OBSStudioAPI::obs_frontend_get_virtualcam_output() +{ + OBSOutput output = main->outputHandler->virtualCam.Get(); + return obs_output_get_ref(output); +} + +void OBSStudioAPI::obs_frontend_start_virtualcam() +{ + QMetaObject::invokeMethod(main, "StartVirtualCam"); +} + +void OBSStudioAPI::obs_frontend_stop_virtualcam() +{ + QMetaObject::invokeMethod(main, "StopVirtualCam"); +} + +bool OBSStudioAPI::obs_frontend_virtualcam_active() +{ + return os_atomic_load_bool(&virtualcam_active); +} + +void OBSStudioAPI::obs_frontend_reset_video() +{ + main->ResetVideo(); +} + +void OBSStudioAPI::obs_frontend_open_source_properties(obs_source_t *source) +{ + QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source))); +} + +void OBSStudioAPI::obs_frontend_open_source_filters(obs_source_t *source) +{ + QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source))); +} + +void OBSStudioAPI::obs_frontend_open_source_interaction(obs_source_t *source) +{ + QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source))); +} + +void OBSStudioAPI::obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) +{ + QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item))); +} + +char *OBSStudioAPI::obs_frontend_get_current_record_output_path() +{ + const char *recordOutputPath = main->GetCurrentOutputPath(); + + return bstrdup(recordOutputPath); +} + +const char *OBSStudioAPI::obs_frontend_get_locale_string(const char *string) +{ + return Str(string); +} + +bool OBSStudioAPI::obs_frontend_is_theme_dark() +{ + return App()->IsThemeDark(); +} + +char *OBSStudioAPI::obs_frontend_get_last_recording() +{ + return bstrdup(main->outputHandler->lastRecordingPath.c_str()); +} + +char *OBSStudioAPI::obs_frontend_get_last_screenshot() +{ + return bstrdup(main->lastScreenshot.c_str()); +} + +char *OBSStudioAPI::obs_frontend_get_last_replay() +{ + return bstrdup(main->lastReplay.c_str()); +} + +void OBSStudioAPI::obs_frontend_add_undo_redo_action(const char *name, const undo_redo_cb undo, const undo_redo_cb redo, + const char *undo_data, const char *redo_data, bool repeatable) +{ + main->undo_s.add_action( + name, [undo](const std::string &data) { undo(data.c_str()); }, + [redo](const std::string &data) { redo(data.c_str()); }, undo_data, redo_data, repeatable); +} + +void OBSStudioAPI::on_load(obs_data_t *settings) +{ + for (size_t i = saveCallbacks.size(); i > 0; i--) { + auto cb = saveCallbacks[i - 1]; + cb.callback(settings, false, cb.private_data); } +} - void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data); - if (idx == (size_t)-1) - return; - - preloadCallbacks.erase(preloadCallbacks.begin() + idx); +void OBSStudioAPI::on_preload(obs_data_t *settings) +{ + for (size_t i = preloadCallbacks.size(); i > 0; i--) { + auto cb = preloadCallbacks[i - 1]; + cb.callback(settings, false, cb.private_data); } +} - void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) override - { - App()->PushUITranslation(translate); +void OBSStudioAPI::on_save(obs_data_t *settings) +{ + for (size_t i = saveCallbacks.size(); i > 0; i--) { + auto cb = saveCallbacks[i - 1]; + cb.callback(settings, true, cb.private_data); } +} - void obs_frontend_pop_ui_translation(void) override { App()->PopUITranslation(); } +void OBSStudioAPI::on_event(enum obs_frontend_event event) +{ + if (main->disableSaving && event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP && + event != OBS_FRONTEND_EVENT_EXIT) + return; - void obs_frontend_set_streaming_service(obs_service_t *service) override { main->SetService(service); } - - obs_service_t *obs_frontend_get_streaming_service(void) override { return main->GetService(); } - - void obs_frontend_save_streaming_service(void) override { main->SaveService(); } - - bool obs_frontend_preview_program_mode_active(void) override { return main->IsPreviewProgramMode(); } - - void obs_frontend_set_preview_program_mode(bool enable) override { main->SetPreviewProgramMode(enable); } - - void obs_frontend_preview_program_trigger_transition(void) override - { - QMetaObject::invokeMethod(main, "TransitionClicked"); + for (size_t i = callbacks.size(); i > 0; i--) { + auto cb = callbacks[i - 1]; + cb.callback(event, cb.private_data); } - - bool obs_frontend_preview_enabled(void) override { return main->previewEnabled; } - - void obs_frontend_set_preview_enabled(bool enable) override - { - if (main->previewEnabled != enable) - main->EnablePreviewDisplay(enable); - } - - obs_source_t *obs_frontend_get_current_preview_scene(void) override - { - if (main->IsPreviewProgramMode()) { - OBSSource source = main->GetCurrentSceneSource(); - return obs_source_get_ref(source); - } - - return nullptr; - } - - void obs_frontend_set_current_preview_scene(obs_source_t *scene) override - { - if (main->IsPreviewProgramMode()) { - QMetaObject::invokeMethod(main, "SetCurrentScene", Q_ARG(OBSSource, OBSSource(scene)), - Q_ARG(bool, false)); - } - } - - void obs_frontend_take_screenshot(void) override { QMetaObject::invokeMethod(main, "Screenshot"); } - - void obs_frontend_take_source_screenshot(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source))); - } - - obs_output_t *obs_frontend_get_virtualcam_output(void) override - { - OBSOutput output = main->outputHandler->virtualCam.Get(); - return obs_output_get_ref(output); - } - - void obs_frontend_start_virtualcam(void) override { QMetaObject::invokeMethod(main, "StartVirtualCam"); } - - void obs_frontend_stop_virtualcam(void) override { QMetaObject::invokeMethod(main, "StopVirtualCam"); } - - bool obs_frontend_virtualcam_active(void) override { return os_atomic_load_bool(&virtualcam_active); } - - void obs_frontend_reset_video(void) override { main->ResetVideo(); } - - void obs_frontend_open_source_properties(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source))); - } - - void obs_frontend_open_source_filters(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source))); - } - - void obs_frontend_open_source_interaction(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source))); - } - - void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override - { - QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item))); - } - - char *obs_frontend_get_current_record_output_path(void) override - { - const char *recordOutputPath = main->GetCurrentOutputPath(); - - return bstrdup(recordOutputPath); - } - - const char *obs_frontend_get_locale_string(const char *string) override { return Str(string); } - - bool obs_frontend_is_theme_dark(void) override { return App()->IsThemeDark(); } - - char *obs_frontend_get_last_recording(void) override - { - return bstrdup(main->outputHandler->lastRecordingPath.c_str()); - } - - char *obs_frontend_get_last_screenshot(void) override { return bstrdup(main->lastScreenshot.c_str()); } - - char *obs_frontend_get_last_replay(void) override { return bstrdup(main->lastReplay.c_str()); } - - void obs_frontend_add_undo_redo_action(const char *name, const undo_redo_cb undo, const undo_redo_cb redo, - const char *undo_data, const char *redo_data, bool repeatable) override - { - main->undo_s.add_action( - name, [undo](const std::string &data) { undo(data.c_str()); }, - [redo](const std::string &data) { redo(data.c_str()); }, undo_data, redo_data, repeatable); - } - - void on_load(obs_data_t *settings) override - { - for (size_t i = saveCallbacks.size(); i > 0; i--) { - auto cb = saveCallbacks[i - 1]; - cb.callback(settings, false, cb.private_data); - } - } - - void on_preload(obs_data_t *settings) override - { - for (size_t i = preloadCallbacks.size(); i > 0; i--) { - auto cb = preloadCallbacks[i - 1]; - cb.callback(settings, false, cb.private_data); - } - } - - void on_save(obs_data_t *settings) override - { - for (size_t i = saveCallbacks.size(); i > 0; i--) { - auto cb = saveCallbacks[i - 1]; - cb.callback(settings, true, cb.private_data); - } - } - - void on_event(enum obs_frontend_event event) override - { - if (main->disableSaving && event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP && - event != OBS_FRONTEND_EVENT_EXIT) - return; - - for (size_t i = callbacks.size(); i > 0; i--) { - auto cb = callbacks[i - 1]; - cb.callback(event, cb.private_data); - } - } -}; +} obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main) { diff --git a/frontend/OBSStudioAPI.hpp b/frontend/OBSStudioAPI.hpp index bb3715017..5a1872d85 100644 --- a/frontend/OBSStudioAPI.hpp +++ b/frontend/OBSStudioAPI.hpp @@ -1,29 +1,28 @@ -#include -#include -#include "obs-app.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" +/****************************************************************************** + Copyright (C) 2024 by Patrick Heyer -#include + 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 + +class OBSBasic; using namespace std; -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSource); - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -extern volatile bool streaming_active; -extern volatile bool recording_active; -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; -extern volatile bool virtualcam_active; - -/* ------------------------------------------------------------------------- */ - template struct OBSStudioCallback { T callback; void *private_data; @@ -31,18 +30,6 @@ template struct OBSStudioCallback { inline OBSStudioCallback(T cb, void *p) : callback(cb), private_data(p) {} }; -template -inline size_t GetCallbackIdx(vector> &callbacks, T callback, void *private_data) -{ - for (size_t i = 0; i < callbacks.size(); i++) { - OBSStudioCallback curCB = callbacks[i]; - if (curCB.callback == callback && curCB.private_data == private_data) - return i; - } - - return (size_t)-1; -} - struct OBSStudioAPI : obs_frontend_callbacks { OBSBasic *main; vector> callbacks; @@ -51,586 +38,199 @@ struct OBSStudioAPI : obs_frontend_callbacks { inline OBSStudioAPI(OBSBasic *main_) : main(main_) {} - void *obs_frontend_get_main_window(void) override { return (void *)main; } - - void *obs_frontend_get_main_window_handle(void) override { return (void *)main->winId(); } - - void *obs_frontend_get_system_tray(void) override { return (void *)main->trayIcon.data(); } - - void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) override - { - for (int i = 0; i < main->ui->scenes->count(); i++) { - QListWidgetItem *item = main->ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - obs_source_t *source = obs_scene_get_source(scene); - - if (obs_source_get_ref(source) != nullptr) - da_push_back(sources->sources, &source); - } - } - - obs_source_t *obs_frontend_get_current_scene(void) override - { - if (main->IsPreviewProgramMode()) { - return obs_weak_source_get_source(main->programScene); - } else { - OBSSource source = main->GetCurrentSceneSource(); - return obs_source_get_ref(source); - } - } - - void obs_frontend_set_current_scene(obs_source_t *scene) override - { - if (main->IsPreviewProgramMode()) { - QMetaObject::invokeMethod(main, "TransitionToScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(scene))); - } else { - QMetaObject::invokeMethod(main, "SetCurrentScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(scene)), Q_ARG(bool, false)); - } - } - - void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) override - { - for (int i = 0; i < main->ui->transitions->count(); i++) { - OBSSource tr = main->ui->transitions->itemData(i).value(); - - if (!tr) - continue; - - if (obs_source_get_ref(tr) != nullptr) - da_push_back(sources->sources, &tr); - } - } - - obs_source_t *obs_frontend_get_current_transition(void) override - { - OBSSource tr = main->GetCurrentTransition(); - return obs_source_get_ref(tr); - } - - void obs_frontend_set_current_transition(obs_source_t *transition) override - { - QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition))); - } - - int obs_frontend_get_transition_duration(void) override { return main->ui->transitionDuration->value(); } - - void obs_frontend_set_transition_duration(int duration) override - { - QMetaObject::invokeMethod(main->ui->transitionDuration, "setValue", Q_ARG(int, duration)); - } - - void obs_frontend_release_tbar(void) override { QMetaObject::invokeMethod(main, "TBarReleased"); } - - void obs_frontend_set_tbar_position(int position) override - { - QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position)); - } - - int obs_frontend_get_tbar_position(void) override { return main->tBar->value(); } - - void obs_frontend_get_scene_collections(std::vector &strings) override - { - for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) { - strings.emplace_back(collectionName); - } - } - - char *obs_frontend_get_current_scene_collection(void) override - { - const OBSSceneCollection ¤tCollection = main->GetCurrentSceneCollection(); - return bstrdup(currentCollection.name.c_str()); - } - - void obs_frontend_set_current_scene_collection(const char *collection) override - { - QList menuActions = main->ui->sceneCollectionMenu->actions(); - QString qstrCollection = QT_UTF8(collection); - - for (int i = 0; i < menuActions.count(); i++) { - QAction *action = menuActions[i]; - QVariant v = action->property("file_name"); - - if (v.typeName() != nullptr) { - if (action->text() == qstrCollection) { - action->trigger(); - break; - } - } - } - } - - bool obs_frontend_add_scene_collection(const char *name) override - { - bool success = false; - QMetaObject::invokeMethod(main, "CreateNewSceneCollection", WaitConnection(), - Q_RETURN_ARG(bool, success), Q_ARG(QString, QT_UTF8(name))); - return success; - } - - void obs_frontend_get_profiles(std::vector &strings) override - { - const OBSProfileCache &profiles = main->GetProfileCache(); - - for (auto &[profileName, profile] : profiles) { - strings.emplace_back(profileName); - } - } - - char *obs_frontend_get_current_profile(void) override - { - const OBSProfile &profile = main->GetCurrentProfile(); - return bstrdup(profile.name.c_str()); - } - - char *obs_frontend_get_current_profile_path(void) override - { - const OBSProfile &profile = main->GetCurrentProfile(); - - return bstrdup(profile.path.u8string().c_str()); - } - - void obs_frontend_set_current_profile(const char *profile) override - { - QList menuActions = main->ui->profileMenu->actions(); - QString qstrProfile = QT_UTF8(profile); - - for (int i = 0; i < menuActions.count(); i++) { - QAction *action = menuActions[i]; - QVariant v = action->property("file_name"); - - if (v.typeName() != nullptr) { - if (action->text() == qstrProfile) { - action->trigger(); - break; - } - } - } - } - - void obs_frontend_create_profile(const char *name) override - { - QMetaObject::invokeMethod(main, "CreateNewProfile", Q_ARG(QString, name)); - } - - void obs_frontend_duplicate_profile(const char *name) override - { - QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name)); - } - - void obs_frontend_delete_profile(const char *profile) override - { - QMetaObject::invokeMethod(main, "DeleteProfile", Q_ARG(QString, profile)); - } - - void obs_frontend_streaming_start(void) override { QMetaObject::invokeMethod(main, "StartStreaming"); } - - void obs_frontend_streaming_stop(void) override { QMetaObject::invokeMethod(main, "StopStreaming"); } - - bool obs_frontend_streaming_active(void) override { return os_atomic_load_bool(&streaming_active); } - - void obs_frontend_recording_start(void) override { QMetaObject::invokeMethod(main, "StartRecording"); } - - void obs_frontend_recording_stop(void) override { QMetaObject::invokeMethod(main, "StopRecording"); } - - bool obs_frontend_recording_active(void) override { return os_atomic_load_bool(&recording_active); } - - void obs_frontend_recording_pause(bool pause) override - { - QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording"); - } - - bool obs_frontend_recording_paused(void) override { return os_atomic_load_bool(&recording_paused); } - - bool obs_frontend_recording_split_file(void) override - { - if (os_atomic_load_bool(&recording_active) && !os_atomic_load_bool(&recording_paused)) { - proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput); - uint8_t stack[128]; - calldata cd; - calldata_init_fixed(&cd, stack, sizeof(stack)); - proc_handler_call(ph, "split_file", &cd); - bool result = calldata_bool(&cd, "split_file_enabled"); - return result; - } else { - return false; - } - } - - bool obs_frontend_recording_add_chapter(const char *name) override - { - if (!os_atomic_load_bool(&recording_active) || os_atomic_load_bool(&recording_paused)) - return false; - - proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput); - - calldata cd; - calldata_init(&cd); - calldata_set_string(&cd, "chapter_name", name); - bool result = proc_handler_call(ph, "add_chapter", &cd); - calldata_free(&cd); - return result; - } - - void obs_frontend_replay_buffer_start(void) override { QMetaObject::invokeMethod(main, "StartReplayBuffer"); } - - void obs_frontend_replay_buffer_save(void) override { QMetaObject::invokeMethod(main, "ReplayBufferSave"); } - - void obs_frontend_replay_buffer_stop(void) override { QMetaObject::invokeMethod(main, "StopReplayBuffer"); } - - bool obs_frontend_replay_buffer_active(void) override { return os_atomic_load_bool(&replaybuf_active); } - - void *obs_frontend_add_tools_menu_qaction(const char *name) override - { - main->ui->menuTools->setEnabled(true); - return (void *)main->ui->menuTools->addAction(QT_UTF8(name)); - } - - void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override - { - main->ui->menuTools->setEnabled(true); - - auto func = [private_data, callback]() { - callback(private_data); - }; - - QAction *action = main->ui->menuTools->addAction(QT_UTF8(name)); - QObject::connect(action, &QAction::triggered, func); - } - - void *obs_frontend_add_dock(void *dock) override - { - QDockWidget *d = reinterpret_cast(dock); - - QString name = d->objectName(); - if (name.isEmpty() || main->IsDockObjectNameUsed(name)) { - blog(LOG_WARNING, "The object name of the added dock is empty or already used," - " a temporary one will be set to avoid conflicts"); - - char *uuid = os_generate_uuid(); - name = QT_UTF8(uuid); - bfree(uuid); - name.append("_oldExtraDock"); - - d->setObjectName(name); - } - - return (void *)main->AddDockWidget(d); - } - - bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) override - { - if (main->IsDockObjectNameUsed(QT_UTF8(id))) { - blog(LOG_WARNING, - "Dock id '%s' already used! " - "Duplicate library?", - id); - return false; - } - - OBSDock *dock = new OBSDock(main); - dock->setWidget((QWidget *)widget); - dock->setWindowTitle(QT_UTF8(title)); - dock->setObjectName(QT_UTF8(id)); - - main->AddDockWidget(dock, Qt::RightDockWidgetArea); - - dock->setVisible(false); - dock->setFloating(true); - - return true; - } - - void obs_frontend_remove_dock(const char *id) override { main->RemoveDockWidget(QT_UTF8(id)); } - - bool obs_frontend_add_custom_qdock(const char *id, void *dock) override - { - if (main->IsDockObjectNameUsed(QT_UTF8(id))) { - blog(LOG_WARNING, - "Dock id '%s' already used! " - "Duplicate library?", - id); - return false; - } - - QDockWidget *d = reinterpret_cast(dock); - d->setObjectName(QT_UTF8(id)); - - main->AddCustomDockWidget(d); - - return true; - } - - void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(callbacks, callback, private_data); - if (idx == (size_t)-1) - callbacks.emplace_back(callback, private_data); - } - - void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(callbacks, callback, private_data); - if (idx == (size_t)-1) - return; - - callbacks.erase(callbacks.begin() + idx); - } - - obs_output_t *obs_frontend_get_streaming_output(void) override - { - auto multitrackVideo = main->outputHandler->multitrackVideo.get(); - auto mtvOutput = multitrackVideo ? obs_output_get_ref(multitrackVideo->StreamingOutput()) : nullptr; - if (mtvOutput) - return mtvOutput; - - OBSOutput output = main->outputHandler->streamOutput.Get(); - return obs_output_get_ref(output); - } - - obs_output_t *obs_frontend_get_recording_output(void) override - { - OBSOutput out = main->outputHandler->fileOutput.Get(); - return obs_output_get_ref(out); - } - - obs_output_t *obs_frontend_get_replay_buffer_output(void) override - { - OBSOutput out = main->outputHandler->replayBuffer.Get(); - return obs_output_get_ref(out); - } - - config_t *obs_frontend_get_profile_config(void) override { return main->activeConfiguration; } - - config_t *obs_frontend_get_global_config(void) override - { - blog(LOG_WARNING, - "DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead."); - return App()->GetAppConfig(); - } - - config_t *obs_frontend_get_app_config(void) override { return App()->GetAppConfig(); } - - config_t *obs_frontend_get_user_config(void) override { return App()->GetUserConfig(); } - - void obs_frontend_open_projector(const char *type, int monitor, const char *geometry, const char *name) override - { - SavedProjectorInfo proj = { - ProjectorType::Preview, - monitor, - geometry ? geometry : "", - name ? name : "", - }; - if (type) { - if (astrcmpi(type, "Source") == 0) - proj.type = ProjectorType::Source; - else if (astrcmpi(type, "Scene") == 0) - proj.type = ProjectorType::Scene; - else if (astrcmpi(type, "StudioProgram") == 0) - proj.type = ProjectorType::StudioProgram; - else if (astrcmpi(type, "Multiview") == 0) - proj.type = ProjectorType::Multiview; - } - QMetaObject::invokeMethod(main, "OpenSavedProjector", WaitConnection(), - Q_ARG(SavedProjectorInfo *, &proj)); - } - - void obs_frontend_save(void) override { main->SaveProject(); } - - void obs_frontend_defer_save_begin(void) override { QMetaObject::invokeMethod(main, "DeferSaveBegin"); } - - void obs_frontend_defer_save_end(void) override { QMetaObject::invokeMethod(main, "DeferSaveEnd"); } - - void obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data); - if (idx == (size_t)-1) - saveCallbacks.emplace_back(callback, private_data); - } - - void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data); - if (idx == (size_t)-1) - return; - - saveCallbacks.erase(saveCallbacks.begin() + idx); - } - - void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data); - if (idx == (size_t)-1) - preloadCallbacks.emplace_back(callback, private_data); - } - - void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) override - { - size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data); - if (idx == (size_t)-1) - return; - - preloadCallbacks.erase(preloadCallbacks.begin() + idx); - } - - void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) override - { - App()->PushUITranslation(translate); - } - - void obs_frontend_pop_ui_translation(void) override { App()->PopUITranslation(); } - - void obs_frontend_set_streaming_service(obs_service_t *service) override { main->SetService(service); } - - obs_service_t *obs_frontend_get_streaming_service(void) override { return main->GetService(); } - - void obs_frontend_save_streaming_service(void) override { main->SaveService(); } - - bool obs_frontend_preview_program_mode_active(void) override { return main->IsPreviewProgramMode(); } - - void obs_frontend_set_preview_program_mode(bool enable) override { main->SetPreviewProgramMode(enable); } - - void obs_frontend_preview_program_trigger_transition(void) override - { - QMetaObject::invokeMethod(main, "TransitionClicked"); - } - - bool obs_frontend_preview_enabled(void) override { return main->previewEnabled; } - - void obs_frontend_set_preview_enabled(bool enable) override - { - if (main->previewEnabled != enable) - main->EnablePreviewDisplay(enable); - } - - obs_source_t *obs_frontend_get_current_preview_scene(void) override - { - if (main->IsPreviewProgramMode()) { - OBSSource source = main->GetCurrentSceneSource(); - return obs_source_get_ref(source); - } - - return nullptr; - } - - void obs_frontend_set_current_preview_scene(obs_source_t *scene) override - { - if (main->IsPreviewProgramMode()) { - QMetaObject::invokeMethod(main, "SetCurrentScene", Q_ARG(OBSSource, OBSSource(scene)), - Q_ARG(bool, false)); - } - } + void *obs_frontend_get_main_window(void) override; - void obs_frontend_take_screenshot(void) override { QMetaObject::invokeMethod(main, "Screenshot"); } + void *obs_frontend_get_main_window_handle(void) override; - void obs_frontend_take_source_screenshot(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source))); - } + void *obs_frontend_get_system_tray(void) override; - obs_output_t *obs_frontend_get_virtualcam_output(void) override - { - OBSOutput output = main->outputHandler->virtualCam.Get(); - return obs_output_get_ref(output); - } + void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) override; - void obs_frontend_start_virtualcam(void) override { QMetaObject::invokeMethod(main, "StartVirtualCam"); } + obs_source_t *obs_frontend_get_current_scene(void) override; - void obs_frontend_stop_virtualcam(void) override { QMetaObject::invokeMethod(main, "StopVirtualCam"); } + void obs_frontend_set_current_scene(obs_source_t *scene) override; - bool obs_frontend_virtualcam_active(void) override { return os_atomic_load_bool(&virtualcam_active); } + void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) override; - void obs_frontend_reset_video(void) override { main->ResetVideo(); } + obs_source_t *obs_frontend_get_current_transition(void) override; - void obs_frontend_open_source_properties(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source))); - } + void obs_frontend_set_current_transition(obs_source_t *transition) override; - void obs_frontend_open_source_filters(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source))); - } + int obs_frontend_get_transition_duration(void) override; - void obs_frontend_open_source_interaction(obs_source_t *source) override - { - QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source))); - } + void obs_frontend_set_transition_duration(int duration) override; - void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override - { - QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item))); - } + void obs_frontend_release_tbar(void) override; - char *obs_frontend_get_current_record_output_path(void) override - { - const char *recordOutputPath = main->GetCurrentOutputPath(); + void obs_frontend_set_tbar_position(int position) override; - return bstrdup(recordOutputPath); - } + int obs_frontend_get_tbar_position(void) override; - const char *obs_frontend_get_locale_string(const char *string) override { return Str(string); } + void obs_frontend_get_scene_collections(std::vector &strings) override; - bool obs_frontend_is_theme_dark(void) override { return App()->IsThemeDark(); } + char *obs_frontend_get_current_scene_collection(void) override; - char *obs_frontend_get_last_recording(void) override - { - return bstrdup(main->outputHandler->lastRecordingPath.c_str()); - } + void obs_frontend_set_current_scene_collection(const char *collection) override; - char *obs_frontend_get_last_screenshot(void) override { return bstrdup(main->lastScreenshot.c_str()); } + bool obs_frontend_add_scene_collection(const char *name) override; - char *obs_frontend_get_last_replay(void) override { return bstrdup(main->lastReplay.c_str()); } + void obs_frontend_get_profiles(std::vector &strings) override; + + char *obs_frontend_get_current_profile(void) override; + + char *obs_frontend_get_current_profile_path(void) override; + + void obs_frontend_set_current_profile(const char *profile) override; + + void obs_frontend_create_profile(const char *name) override; + + void obs_frontend_duplicate_profile(const char *name) override; + + void obs_frontend_delete_profile(const char *profile) override; + + void obs_frontend_streaming_start(void) override; + + void obs_frontend_streaming_stop(void) override; + + bool obs_frontend_streaming_active(void) override; + + void obs_frontend_recording_start(void) override; + + void obs_frontend_recording_stop(void) override; + + bool obs_frontend_recording_active(void) override; + + void obs_frontend_recording_pause(bool pause) override; + + bool obs_frontend_recording_paused(void) override; + + bool obs_frontend_recording_split_file(void) override; + + bool obs_frontend_recording_add_chapter(const char *name) override; + + void obs_frontend_replay_buffer_start(void) override; + + void obs_frontend_replay_buffer_save(void) override; + + void obs_frontend_replay_buffer_stop(void) override; + + bool obs_frontend_replay_buffer_active(void) override; + + void *obs_frontend_add_tools_menu_qaction(const char *name) override; + + void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override; + + void *obs_frontend_add_dock(void *dock) override; + + bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) override; + + void obs_frontend_remove_dock(const char *id) override; + + bool obs_frontend_add_custom_qdock(const char *id, void *dock) override; + + void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override; + + void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) override; + + obs_output_t *obs_frontend_get_streaming_output(void) override; + + obs_output_t *obs_frontend_get_recording_output(void) override; + + obs_output_t *obs_frontend_get_replay_buffer_output(void) override; + + config_t *obs_frontend_get_profile_config(void) override; + + config_t *obs_frontend_get_global_config(void) override; + + config_t *obs_frontend_get_app_config(void) override; + + config_t *obs_frontend_get_user_config(void) override; + + void obs_frontend_open_projector(const char *type, int monitor, const char *geometry, + const char *name) override; + + void obs_frontend_save(void) override; + + void obs_frontend_defer_save_begin(void) override; + + void obs_frontend_defer_save_end(void) override; + + void obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) override; + + void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) override; + + void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) override; + + void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) override; + + void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) override; + + void obs_frontend_pop_ui_translation(void) override; + + void obs_frontend_set_streaming_service(obs_service_t *service) override; + + obs_service_t *obs_frontend_get_streaming_service(void) override; + + void obs_frontend_save_streaming_service(void) override; + + bool obs_frontend_preview_program_mode_active(void) override; + + void obs_frontend_set_preview_program_mode(bool enable) override; + + void obs_frontend_preview_program_trigger_transition(void) override; + + bool obs_frontend_preview_enabled(void) override; + + void obs_frontend_set_preview_enabled(bool enable) override; + + obs_source_t *obs_frontend_get_current_preview_scene(void) override; + + void obs_frontend_set_current_preview_scene(obs_source_t *scene) override; + + void obs_frontend_take_screenshot(void) override; + + void obs_frontend_take_source_screenshot(obs_source_t *source) override; + + obs_output_t *obs_frontend_get_virtualcam_output(void) override; + + void obs_frontend_start_virtualcam(void) override; + + void obs_frontend_stop_virtualcam(void) override; + + bool obs_frontend_virtualcam_active(void) override; + + void obs_frontend_reset_video(void) override; + + void obs_frontend_open_source_properties(obs_source_t *source) override; + + void obs_frontend_open_source_filters(obs_source_t *source) override; + + void obs_frontend_open_source_interaction(obs_source_t *source) override; + + void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override; + + char *obs_frontend_get_current_record_output_path(void) override; + + const char *obs_frontend_get_locale_string(const char *string) override; + + bool obs_frontend_is_theme_dark(void) override; + + char *obs_frontend_get_last_recording(void) override; + + char *obs_frontend_get_last_screenshot(void) override; + + char *obs_frontend_get_last_replay(void) override; void obs_frontend_add_undo_redo_action(const char *name, const undo_redo_cb undo, const undo_redo_cb redo, - const char *undo_data, const char *redo_data, bool repeatable) override - { - main->undo_s.add_action( - name, [undo](const std::string &data) { undo(data.c_str()); }, - [redo](const std::string &data) { redo(data.c_str()); }, undo_data, redo_data, repeatable); - } + const char *undo_data, const char *redo_data, bool repeatable) override; - void on_load(obs_data_t *settings) override - { - for (size_t i = saveCallbacks.size(); i > 0; i--) { - auto cb = saveCallbacks[i - 1]; - cb.callback(settings, false, cb.private_data); - } - } + void on_load(obs_data_t *settings) override; - void on_preload(obs_data_t *settings) override - { - for (size_t i = preloadCallbacks.size(); i > 0; i--) { - auto cb = preloadCallbacks[i - 1]; - cb.callback(settings, false, cb.private_data); - } - } + void on_preload(obs_data_t *settings) override; - void on_save(obs_data_t *settings) override - { - for (size_t i = saveCallbacks.size(); i > 0; i--) { - auto cb = saveCallbacks[i - 1]; - cb.callback(settings, true, cb.private_data); - } - } + void on_save(obs_data_t *settings) override; - void on_event(enum obs_frontend_event event) override - { - if (main->disableSaving && event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP && - event != OBS_FRONTEND_EVENT_EXIT) - return; - - for (size_t i = callbacks.size(); i > 0; i--) { - auto cb = callbacks[i - 1]; - cb.callback(event, cb.private_data); - } - } + void on_event(enum obs_frontend_event event) override; }; -obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main) -{ - obs_frontend_callbacks *api = new OBSStudioAPI(main); - obs_frontend_set_callbacks_internal(api); - return api; -} +obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); diff --git a/frontend/obs-main.cpp b/frontend/obs-main.cpp index 883bee21a..987dca5bc 100644 --- a/frontend/obs-main.cpp +++ b/frontend/obs-main.cpp @@ -15,86 +15,52 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "obs-proxy-style.hpp" -#include "log-viewer.hpp" -#include "volume-control.hpp" -#include "window-basic-main.hpp" +#include +#include #ifdef __APPLE__ -#include "window-permissions.hpp" +#include #endif -#include "window-basic-settings.hpp" -#include "platform.hpp" +#include +#include +#include +#include -#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif +#include #include +#include +#include +#include #ifdef _WIN32 +#include +#define WIN32_LEAN_AND_MEAN #include -#include -#include #else #include -#include -#include -#include -#include #endif -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) -#include "update/models/branches.hpp" -#endif - -#if !defined(_WIN32) && !defined(__APPLE__) -#include -#include -#endif - -#include - -#include "ui-config.h" - using namespace std; static log_handler_t def_log_handler; -static string currentLogFile; -static string lastLogFile; -static string lastCrashLogFile; +extern string currentLogFile; +extern string lastLogFile; +extern string lastCrashLogFile; bool portable_mode = false; bool steam = false; bool safe_mode = false; bool disable_3p_plugins = false; -bool unclean_shutdown = false; -bool disable_shutdown_check = false; +static bool unclean_shutdown = false; +static bool disable_shutdown_check = false; static bool multi = false; static bool log_verbose = false; static bool unfiltered_log = false; @@ -114,144 +80,10 @@ string opt_starting_scene; bool restart = false; bool restart_safe = false; -QStringList arguments; +static QStringList arguments; QPointer obsLogViewer; -#ifndef _WIN32 -int OBSApp::sigintFd[2]; -#endif - -// GPU hint exports for AMD/NVIDIA laptops -#ifdef _MSC_VER -extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1; -extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -#endif - -QObject *CreateShortcutFilter() -{ - return new OBSEventFilter([](QObject *obj, QEvent *event) { - auto mouse_event = [](QMouseEvent &event) { - if (!App()->HotkeysEnabledInFocus() && event.button() != Qt::LeftButton) - return true; - - obs_key_combination_t hotkey = {0, OBS_KEY_NONE}; - bool pressed = event.type() == QEvent::MouseButtonPress; - - switch (event.button()) { - case Qt::NoButton: - case Qt::LeftButton: - case Qt::RightButton: - case Qt::AllButtons: - case Qt::MouseButtonMask: - return false; - - case Qt::MiddleButton: - hotkey.key = OBS_KEY_MOUSE3; - break; - -#define MAP_BUTTON(i, j) \ - case Qt::ExtraButton##i: \ - hotkey.key = OBS_KEY_MOUSE##j; \ - break; - MAP_BUTTON(1, 4); - MAP_BUTTON(2, 5); - MAP_BUTTON(3, 6); - MAP_BUTTON(4, 7); - MAP_BUTTON(5, 8); - MAP_BUTTON(6, 9); - MAP_BUTTON(7, 10); - MAP_BUTTON(8, 11); - MAP_BUTTON(9, 12); - MAP_BUTTON(10, 13); - MAP_BUTTON(11, 14); - MAP_BUTTON(12, 15); - MAP_BUTTON(13, 16); - MAP_BUTTON(14, 17); - MAP_BUTTON(15, 18); - MAP_BUTTON(16, 19); - MAP_BUTTON(17, 20); - MAP_BUTTON(18, 21); - MAP_BUTTON(19, 22); - MAP_BUTTON(20, 23); - MAP_BUTTON(21, 24); - MAP_BUTTON(22, 25); - MAP_BUTTON(23, 26); - MAP_BUTTON(24, 27); -#undef MAP_BUTTON - } - - hotkey.modifiers = TranslateQtKeyboardEventModifiers(event.modifiers()); - - obs_hotkey_inject_event(hotkey, pressed); - return true; - }; - - auto key_event = [&](QKeyEvent *event) { - int key = event->key(); - bool enabledInFocus = App()->HotkeysEnabledInFocus(); - - if (key != Qt::Key_Enter && key != Qt::Key_Escape && key != Qt::Key_Return && !enabledInFocus) - return true; - - QDialog *dialog = qobject_cast(obj); - - obs_key_combination_t hotkey = {0, OBS_KEY_NONE}; - bool pressed = event->type() == QEvent::KeyPress; - - switch (key) { - case Qt::Key_Shift: - case Qt::Key_Control: - case Qt::Key_Alt: - case Qt::Key_Meta: - break; - -#ifdef __APPLE__ - case Qt::Key_CapsLock: - // kVK_CapsLock == 57 - hotkey.key = obs_key_from_virtual_key(57); - pressed = true; - break; -#endif - - case Qt::Key_Enter: - case Qt::Key_Escape: - case Qt::Key_Return: - if (dialog && pressed) - return false; - if (!enabledInFocus) - return true; - /* Falls through. */ - default: - hotkey.key = obs_key_from_virtual_key(event->nativeVirtualKey()); - } - - if (event->isAutoRepeat()) - return true; - - hotkey.modifiers = TranslateQtKeyboardEventModifiers(event->modifiers()); - - obs_hotkey_inject_event(hotkey, pressed); - return true; - }; - - switch (event->type()) { - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - return mouse_event(*static_cast(event)); - - /*case QEvent::MouseButtonDblClick: - case QEvent::Wheel:*/ - case QEvent::KeyPress: - case QEvent::KeyRelease: - return key_event(static_cast(event)); - - default: - return false; - } - }); -} - string CurrentTimeString() { using namespace std::chrono; @@ -274,16 +106,6 @@ string CurrentTimeString() return buf; } -string CurrentDateTimeString() -{ - time_t now = time(0); - struct tm tstruct; - char buf[80]; - tstruct = *localtime(&now); - strftime(buf, sizeof(buf), "%Y-%m-%d, %X", &tstruct); - return buf; -} - static void LogString(fstream &logFile, const char *timeString, char *str, int log_level) { static mutex logfile_mutex; @@ -423,1031 +245,6 @@ static void do_log(int log_level, const char *msg, va_list args, void *param) #endif } -#define DEFAULT_LANG "en-US" - -bool OBSApp::InitGlobalConfigDefaults() -{ - config_set_default_uint(appConfig, "General", "MaxLogs", 10); - config_set_default_int(appConfig, "General", "InfoIncrement", -1); - config_set_default_string(appConfig, "General", "ProcessPriority", "Normal"); - config_set_default_bool(appConfig, "General", "EnableAutoUpdates", true); - -#if _WIN32 - config_set_default_string(appConfig, "Video", "Renderer", "Direct3D 11"); -#else - config_set_default_string(appConfig, "Video", "Renderer", "OpenGL"); -#endif - -#ifdef _WIN32 - config_set_default_bool(appConfig, "Audio", "DisableAudioDucking", true); - config_set_default_bool(appConfig, "General", "BrowserHWAccel", true); -#endif - -#ifdef __APPLE__ - config_set_default_bool(appConfig, "General", "BrowserHWAccel", true); - config_set_default_bool(appConfig, "Video", "DisableOSXVSync", true); - config_set_default_bool(appConfig, "Video", "ResetOSXVSyncOnExit", true); -#endif - - return true; -} - -bool OBSApp::InitGlobalLocationDefaults() -{ - char path[512]; - - int len = GetAppConfigPath(path, sizeof(path), nullptr); - if (len <= 0) { - OBSErrorBox(NULL, "Unable to get global configuration path."); - return false; - } - - config_set_default_string(appConfig, "Locations", "Configuration", path); - config_set_default_string(appConfig, "Locations", "SceneCollections", path); - config_set_default_string(appConfig, "Locations", "Profiles", path); - - return true; -} - -void OBSApp::InitUserConfigDefaults() -{ - config_set_default_bool(userConfig, "General", "ConfirmOnExit", true); - - config_set_default_string(userConfig, "General", "HotkeyFocusType", "NeverDisableHotkeys"); - - config_set_default_bool(userConfig, "BasicWindow", "PreviewEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "PreviewProgramMode", false); - config_set_default_bool(userConfig, "BasicWindow", "SceneDuplicationMode", true); - config_set_default_bool(userConfig, "BasicWindow", "SwapScenesMode", true); - config_set_default_bool(userConfig, "BasicWindow", "SnappingEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "ScreenSnapping", true); - config_set_default_bool(userConfig, "BasicWindow", "SourceSnapping", true); - config_set_default_bool(userConfig, "BasicWindow", "CenterSnapping", false); - config_set_default_double(userConfig, "BasicWindow", "SnapDistance", 10.0); - config_set_default_bool(userConfig, "BasicWindow", "SpacingHelpersEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "RecordWhenStreaming", false); - config_set_default_bool(userConfig, "BasicWindow", "KeepRecordingWhenStreamStops", false); - config_set_default_bool(userConfig, "BasicWindow", "SysTrayEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "SysTrayWhenStarted", false); - config_set_default_bool(userConfig, "BasicWindow", "SaveProjectors", false); - config_set_default_bool(userConfig, "BasicWindow", "ShowTransitions", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowListboxToolbars", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowStatusBar", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowSourceIcons", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowContextToolbars", true); - config_set_default_bool(userConfig, "BasicWindow", "StudioModeLabels", true); - - config_set_default_bool(userConfig, "BasicWindow", "VerticalVolControl", false); - - config_set_default_bool(userConfig, "BasicWindow", "MultiviewMouseSwitch", true); - - config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawNames", true); - - config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawAreas", true); - - config_set_default_bool(userConfig, "BasicWindow", "MediaControlsCountdownTimer", true); -} - -static bool do_mkdir(const char *path) -{ - if (os_mkdirs(path) == MKDIR_ERROR) { - OBSErrorBox(NULL, "Failed to create directory %s", path); - return false; - } - - return true; -} - -static bool MakeUserDirs() -{ - char path[512]; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/basic") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/logs") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/profiler_data") <= 0) - return false; - if (!do_mkdir(path)) - return false; - -#ifdef _WIN32 - if (GetAppConfigPath(path, sizeof(path), "obs-studio/crashes") <= 0) - return false; - if (!do_mkdir(path)) - return false; -#endif - -#ifdef WHATSNEW_ENABLED - if (GetAppConfigPath(path, sizeof(path), "obs-studio/updates") <= 0) - return false; - if (!do_mkdir(path)) - return false; -#endif - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - return true; -} - -constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles"; -constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes"; - -static bool MakeUserProfileDirs() -{ - const std::filesystem::path userProfilePath = - App()->userProfilesLocation / std::filesystem::u8path(OBSProfileSubDirectory); - const std::filesystem::path userScenesPath = - App()->userScenesLocation / std::filesystem::u8path(OBSScenesSubDirectory); - - if (!std::filesystem::exists(userProfilePath)) { - try { - std::filesystem::create_directories(userProfilePath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create user profile directory '%s'\n%s", - userProfilePath.u8string().c_str(), error.what()); - return false; - } - } - - if (!std::filesystem::exists(userScenesPath)) { - try { - std::filesystem::create_directories(userScenesPath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create user scene collection directory '%s'\n%s", - userScenesPath.u8string().c_str(), error.what()); - return false; - } - } - - return true; -} - -bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) -{ - if (!layout) - return false; - - if (astrcmpi(layout, "horizontaltop") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::HORIZONTAL_TOP_8_SCENES)); - return true; - } - - if (astrcmpi(layout, "horizontalbottom") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES)); - return true; - } - - if (astrcmpi(layout, "verticalleft") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::VERTICAL_LEFT_8_SCENES)); - return true; - } - - if (astrcmpi(layout, "verticalright") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::VERTICAL_RIGHT_8_SCENES)); - return true; - } - - return false; -} - -bool OBSApp::InitGlobalConfig() -{ - char path[512]; - - int len = GetAppConfigPath(path, sizeof(path), "obs-studio/global.ini"); - if (len <= 0) { - return false; - } - - int errorcode = appConfig.Open(path, CONFIG_OPEN_ALWAYS); - if (errorcode != CONFIG_SUCCESS) { - OBSErrorBox(NULL, "Failed to open global.ini: %d", errorcode); - return false; - } - - uint32_t lastVersion = config_get_int(appConfig, "General", "LastVersion"); - - if (lastVersion < MAKE_SEMANTIC_VERSION(31, 0, 0)) { - bool migratedUserSettings = config_get_bool(appConfig, "General", "Pre31Migrated"); - - if (!migratedUserSettings) { - bool migrated = MigrateGlobalSettings(); - - config_set_bool(appConfig, "General", "Pre31Migrated", migrated); - config_save_safe(appConfig, "tmp", nullptr); - } - } - - InitGlobalConfigDefaults(); - InitGlobalLocationDefaults(); - - if (IsPortableMode()) { - userConfigLocation = - std::filesystem::u8path(config_get_default_string(appConfig, "Locations", "Configuration")); - userScenesLocation = - std::filesystem::u8path(config_get_default_string(appConfig, "Locations", "SceneCollections")); - userProfilesLocation = - std::filesystem::u8path(config_get_default_string(appConfig, "Locations", "Profiles")); - } else { - userConfigLocation = - std::filesystem::u8path(config_get_string(appConfig, "Locations", "Configuration")); - userScenesLocation = - std::filesystem::u8path(config_get_string(appConfig, "Locations", "SceneCollections")); - userProfilesLocation = std::filesystem::u8path(config_get_string(appConfig, "Locations", "Profiles")); - } - - bool userConfigResult = InitUserConfig(userConfigLocation, lastVersion); - - return userConfigResult; -} - -bool OBSApp::InitUserConfig(std::filesystem::path &userConfigLocation, uint32_t lastVersion) -{ - const std::string userConfigFile = userConfigLocation.u8string() + "/obs-studio/user.ini"; - - int errorCode = userConfig.Open(userConfigFile.c_str(), CONFIG_OPEN_ALWAYS); - - if (errorCode != CONFIG_SUCCESS) { - OBSErrorBox(nullptr, "Failed to open user.ini: %d", errorCode); - return false; - } - - MigrateLegacySettings(lastVersion); - InitUserConfigDefaults(); - - return true; -} - -void OBSApp::MigrateLegacySettings(const uint32_t lastVersion) -{ - bool hasChanges = false; - - const uint32_t v19 = MAKE_SEMANTIC_VERSION(19, 0, 0); - const uint32_t v21 = MAKE_SEMANTIC_VERSION(21, 0, 0); - const uint32_t v23 = MAKE_SEMANTIC_VERSION(23, 0, 0); - const uint32_t v24 = MAKE_SEMANTIC_VERSION(24, 0, 0); - const uint32_t v24_1 = MAKE_SEMANTIC_VERSION(24, 1, 0); - - const map defaultsMap{ - {{v19, "Pre19Defaults"}, {v21, "Pre21Defaults"}, {v23, "Pre23Defaults"}, {v24_1, "Pre24.1Defaults"}}}; - - for (auto &[version, configKey] : defaultsMap) { - if (!config_has_user_value(userConfig, "General", configKey.c_str())) { - bool useOldDefaults = lastVersion && lastVersion < version; - config_set_bool(userConfig, "General", configKey.c_str(), useOldDefaults); - - hasChanges = true; - } - } - - if (config_has_user_value(userConfig, "BasicWindow", "MultiviewLayout")) { - const char *layout = config_get_string(userConfig, "BasicWindow", "MultiviewLayout"); - - bool layoutUpdated = UpdatePre22MultiviewLayout(layout); - - hasChanges = hasChanges | layoutUpdated; - } - - if (lastVersion && lastVersion < v24) { - bool disableHotkeysInFocus = config_get_bool(userConfig, "General", "DisableHotkeysInFocus"); - - if (disableHotkeysInFocus) { - config_set_string(userConfig, "General", "HotkeyFocusType", "DisableHotkeysInFocus"); - } - - hasChanges = true; - } - - if (hasChanges) { - userConfig.SaveSafe("tmp"); - } -} - -static constexpr string_view OBSGlobalIniPath = "/obs-studio/global.ini"; -static constexpr string_view OBSUserIniPath = "/obs-studio/user.ini"; - -bool OBSApp::MigrateGlobalSettings() -{ - char path[512]; - - int len = GetAppConfigPath(path, sizeof(path), nullptr); - if (len <= 0) { - OBSErrorBox(nullptr, "Unable to get global configuration path."); - return false; - } - - std::string legacyConfigFileString; - legacyConfigFileString.reserve(strlen(path) + OBSGlobalIniPath.size()); - legacyConfigFileString.append(path).append(OBSGlobalIniPath); - - const std::filesystem::path legacyGlobalConfigFile = std::filesystem::u8path(legacyConfigFileString); - - std::string configFileString; - configFileString.reserve(strlen(path) + OBSUserIniPath.size()); - configFileString.append(path).append(OBSUserIniPath); - - const std::filesystem::path userConfigFile = std::filesystem::u8path(configFileString); - - if (std::filesystem::exists(userConfigFile)) { - OBSErrorBox(nullptr, - "Unable to migrate global configuration - user configuration file already exists."); - return false; - } - - try { - std::filesystem::copy(legacyGlobalConfigFile, userConfigFile); - } catch (const std::filesystem::filesystem_error &) { - OBSErrorBox(nullptr, "Unable to migrate global configuration - copy failed."); - return false; - } - - return true; -} - -bool OBSApp::InitLocale() -{ - ProfileScope("OBSApp::InitLocale"); - - const char *lang = config_get_string(userConfig, "General", "Language"); - bool userLocale = config_has_user_value(userConfig, "General", "Language"); - if (!userLocale || !lang || lang[0] == '\0') - lang = DEFAULT_LANG; - - locale = lang; - - // set basic default application locale - if (!locale.empty()) - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - - string englishPath; - if (!GetDataFilePath("locale/" DEFAULT_LANG ".ini", englishPath)) { - OBSErrorBox(NULL, "Failed to find locale/" DEFAULT_LANG ".ini"); - return false; - } - - textLookup = text_lookup_create(englishPath.c_str()); - if (!textLookup) { - OBSErrorBox(NULL, "Failed to create locale from file '%s'", englishPath.c_str()); - return false; - } - - bool defaultLang = astrcmpi(lang, DEFAULT_LANG) == 0; - - if (userLocale && defaultLang) - return true; - - if (!userLocale && defaultLang) { - for (auto &locale_ : GetPreferredLocales()) { - if (locale_ == lang) - return true; - - stringstream file; - file << "locale/" << locale_ << ".ini"; - - string path; - if (!GetDataFilePath(file.str().c_str(), path)) - continue; - - if (!text_lookup_add(textLookup, path.c_str())) - continue; - - blog(LOG_INFO, "Using preferred locale '%s'", locale_.c_str()); - locale = locale_; - - // set application default locale to the new choosen one - if (!locale.empty()) - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - - return true; - } - - return true; - } - - stringstream file; - file << "locale/" << lang << ".ini"; - - string path; - if (GetDataFilePath(file.str().c_str(), path)) { - if (!text_lookup_add(textLookup, path.c_str())) - blog(LOG_ERROR, "Failed to add locale file '%s'", path.c_str()); - } else { - blog(LOG_ERROR, "Could not find locale file '%s'", file.str().c_str()); - } - - return true; -} - -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) -void ParseBranchesJson(const std::string &jsonString, vector &out, std::string &error) -{ - JsonBranches branches; - - try { - nlohmann::json json = nlohmann::json::parse(jsonString); - branches = json.get(); - } catch (nlohmann::json::exception &e) { - error = e.what(); - return; - } - - for (const JsonBranch &json_branch : branches) { -#ifdef _WIN32 - if (!json_branch.windows) - continue; -#elif defined(__APPLE__) - if (!json_branch.macos) - continue; -#endif - - UpdateBranch branch = { - QString::fromStdString(json_branch.name), - QString::fromStdString(json_branch.display_name), - QString::fromStdString(json_branch.description), - json_branch.enabled, - json_branch.visible, - }; - out.push_back(branch); - } -} - -bool LoadBranchesFile(vector &out) -{ - string error; - string branchesText; - - BPtr branchesFilePath = GetAppConfigPathPtr("obs-studio/updates/branches.json"); - - QFile branchesFile(branchesFilePath.Get()); - if (!branchesFile.open(QIODevice::ReadOnly)) { - error = "Opening file failed."; - goto fail; - } - - branchesText = branchesFile.readAll(); - if (branchesText.empty()) { - error = "File empty."; - goto fail; - } - - ParseBranchesJson(branchesText, out, error); - if (error.empty()) - return !out.empty(); - -fail: - blog(LOG_WARNING, "Loading branches from file failed: %s", error.c_str()); - return false; -} -#endif - -void OBSApp::SetBranchData(const string &data) -{ -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) - string error; - vector result; - - ParseBranchesJson(data, result, error); - - if (!error.empty()) { - blog(LOG_WARNING, "Reading branches JSON response failed: %s", error.c_str()); - return; - } - - if (!result.empty()) - updateBranches = result; - - branches_loaded = true; -#else - UNUSED_PARAMETER(data); -#endif -} - -std::vector OBSApp::GetBranches() -{ - vector out; - /* Always ensure the default branch exists */ - out.push_back(UpdateBranch{"stable", "", "", true, true}); - -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) - if (!branches_loaded) { - vector result; - if (LoadBranchesFile(result)) - updateBranches = result; - - branches_loaded = true; - } -#endif - - /* Copy additional branches to result (if any) */ - if (!updateBranches.empty()) - out.insert(out.end(), updateBranches.begin(), updateBranches.end()); - - return out; -} - -OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store) - : QApplication(argc, argv), - profilerNameStore(store) -{ - /* fix float handling */ -#if defined(Q_OS_UNIX) - if (!setlocale(LC_NUMERIC, "C")) - blog(LOG_WARNING, "Failed to set LC_NUMERIC to C locale"); -#endif - -#ifndef _WIN32 - /* Handle SIGINT properly */ - socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd); - snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this); - connect(snInt, &QSocketNotifier::activated, this, &OBSApp::ProcessSigInt); -#else - connect(qApp, &QGuiApplication::commitDataRequest, this, &OBSApp::commitData); -#endif - - sleepInhibitor = os_inhibit_sleep_create("OBS Video/audio"); - -#ifndef __APPLE__ - setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png"))); -#endif - - setDesktopFileName("com.obsproject.Studio"); -} - -OBSApp::~OBSApp() -{ -#ifdef _WIN32 - bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking"); - if (disableAudioDucking) - DisableAudioDucking(false); -#else - delete snInt; - close(sigintFd[0]); - close(sigintFd[1]); -#endif - -#ifdef __APPLE__ - bool vsyncDisabled = config_get_bool(appConfig, "Video", "DisableOSXVSync"); - bool resetVSync = config_get_bool(appConfig, "Video", "ResetOSXVSyncOnExit"); - if (vsyncDisabled && resetVSync) - EnableOSXVSync(true); -#endif - - os_inhibit_sleep_set_active(sleepInhibitor, false); - os_inhibit_sleep_destroy(sleepInhibitor); - - if (libobs_initialized) - obs_shutdown(); -} - -static void move_basic_to_profiles(void) -{ - char path[512]; - - if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) { - return; - } - - const std::filesystem::path basicPath = std::filesystem::u8path(path); - - if (!std::filesystem::exists(basicPath)) { - return; - } - - const std::filesystem::path profilesPath = - App()->userProfilesLocation / std::filesystem::u8path("obs-studio/basic/profiles"); - - if (std::filesystem::exists(profilesPath)) { - return; - } - - try { - std::filesystem::create_directories(profilesPath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create profiles directory for migration from basic profile\n%s", - error.what()); - return; - } - - const std::filesystem::path newProfilePath = profilesPath / std::filesystem::u8path(Str("Untitled")); - - for (auto &entry : std::filesystem::directory_iterator(basicPath)) { - if (entry.is_directory()) { - continue; - } - - if (entry.path().filename().u8string() == "scenes.json") { - continue; - } - - if (!std::filesystem::exists(newProfilePath)) { - try { - std::filesystem::create_directory(newProfilePath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create profile directory for 'Untitled'\n%s", error.what()); - return; - } - } - - const filesystem::path destinationFile = newProfilePath / entry.path().filename(); - - const auto copyOptions = std::filesystem::copy_options::overwrite_existing; - - try { - std::filesystem::copy(entry.path(), destinationFile, copyOptions); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to copy basic profile file '%s' to new profile 'Untitled'\n%s", - entry.path().filename().u8string().c_str(), error.what()); - - return; - } - } -} - -static void move_basic_to_scene_collections(void) -{ - char path[512]; - - if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) { - return; - } - - const std::filesystem::path basicPath = std::filesystem::u8path(path); - - if (!std::filesystem::exists(basicPath)) { - return; - } - - const std::filesystem::path sceneCollectionPath = - App()->userScenesLocation / std::filesystem::u8path("obs-studio/basic/scenes"); - - if (std::filesystem::exists(sceneCollectionPath)) { - return; - } - - try { - std::filesystem::create_directories(sceneCollectionPath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, - "Failed to create scene collection directory for migration from basic scene collection\n%s", - error.what()); - return; - } - - const std::filesystem::path sourceFile = basicPath / std::filesystem::u8path("scenes.json"); - const std::filesystem::path destinationFile = - (sceneCollectionPath / std::filesystem::u8path(Str("Untitled"))).replace_extension(".json"); - - try { - std::filesystem::rename(sourceFile, destinationFile); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to rename basic scene collection file:\n%s", error.what()); - return; - } -} - -void OBSApp::AppInit() -{ - ProfileScope("OBSApp::AppInit"); - - if (!MakeUserDirs()) - throw "Failed to create required user directories"; - if (!InitGlobalConfig()) - throw "Failed to initialize global config"; - if (!InitLocale()) - throw "Failed to load locale"; - if (!InitTheme()) - throw "Failed to load theme"; - - config_set_default_string(userConfig, "Basic", "Profile", Str("Untitled")); - config_set_default_string(userConfig, "Basic", "ProfileDir", Str("Untitled")); - config_set_default_string(userConfig, "Basic", "SceneCollection", Str("Untitled")); - config_set_default_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled")); - config_set_default_bool(userConfig, "Basic", "ConfigOnNewProfile", true); - - if (!config_has_user_value(userConfig, "Basic", "Profile")) { - config_set_string(userConfig, "Basic", "Profile", Str("Untitled")); - config_set_string(userConfig, "Basic", "ProfileDir", Str("Untitled")); - } - - if (!config_has_user_value(userConfig, "Basic", "SceneCollection")) { - config_set_string(userConfig, "Basic", "SceneCollection", Str("Untitled")); - config_set_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled")); - } - -#ifdef _WIN32 - bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking"); - if (disableAudioDucking) - DisableAudioDucking(true); -#endif - -#ifdef __APPLE__ - if (config_get_bool(appConfig, "Video", "DisableOSXVSync")) - EnableOSXVSync(false); -#endif - - UpdateHotkeyFocusSetting(false); - - move_basic_to_profiles(); - move_basic_to_scene_collections(); - - if (!MakeUserProfileDirs()) - throw "Failed to create profile directories"; -} - -const char *OBSApp::GetRenderModule() const -{ - const char *renderer = config_get_string(appConfig, "Video", "Renderer"); - - return (astrcmpi(renderer, "Direct3D 11") == 0) ? DL_D3D11 : DL_OPENGL; -} - -static bool StartupOBS(const char *locale, profiler_name_store_t *store) -{ - char path[512]; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0) - return false; - - return obs_startup(locale, path, store); -} - -inline void OBSApp::ResetHotkeyState(bool inFocus) -{ - obs_hotkey_enable_background_press((inFocus && enableHotkeysInFocus) || (!inFocus && enableHotkeysOutOfFocus)); -} - -void OBSApp::UpdateHotkeyFocusSetting(bool resetState) -{ - enableHotkeysInFocus = true; - enableHotkeysOutOfFocus = true; - - const char *hotkeyFocusType = config_get_string(userConfig, "General", "HotkeyFocusType"); - - if (astrcmpi(hotkeyFocusType, "DisableHotkeysInFocus") == 0) { - enableHotkeysInFocus = false; - } else if (astrcmpi(hotkeyFocusType, "DisableHotkeysOutOfFocus") == 0) { - enableHotkeysOutOfFocus = false; - } - - if (resetState) - ResetHotkeyState(applicationState() == Qt::ApplicationActive); -} - -void OBSApp::DisableHotkeys() -{ - enableHotkeysInFocus = false; - enableHotkeysOutOfFocus = false; - ResetHotkeyState(applicationState() == Qt::ApplicationActive); -} - -Q_DECLARE_METATYPE(VoidFunc) - -void OBSApp::Exec(VoidFunc func) -{ - func(); -} - -static void ui_task_handler(obs_task_t task, void *param, bool wait) -{ - auto doTask = [=]() { - /* to get clang-format to behave */ - task(param); - }; - QMetaObject::invokeMethod(App(), "Exec", wait ? WaitConnection() : Qt::AutoConnection, Q_ARG(VoidFunc, doTask)); -} - -bool OBSApp::OBSInit() -{ - ProfileScope("OBSApp::OBSInit"); - - qRegisterMetaType("VoidFunc"); - -#if !defined(_WIN32) && !defined(__APPLE__) - if (QApplication::platformName() == "xcb") { - obs_set_nix_platform(OBS_NIX_PLATFORM_X11_EGL); - blog(LOG_INFO, "Using EGL/X11"); - } - -#ifdef ENABLE_WAYLAND - if (QApplication::platformName().contains("wayland")) { - obs_set_nix_platform(OBS_NIX_PLATFORM_WAYLAND); - setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); - blog(LOG_INFO, "Platform: Wayland"); - } -#endif - - QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - obs_set_nix_platform_display(native->nativeResourceForIntegration("display")); -#endif - -#ifdef __APPLE__ - setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); -#endif - - if (!StartupOBS(locale.c_str(), GetProfilerNameStore())) - return false; - - libobs_initialized = true; - - obs_set_ui_task_handler(ui_task_handler); - -#if defined(_WIN32) || defined(__APPLE__) - bool browserHWAccel = config_get_bool(appConfig, "General", "BrowserHWAccel"); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_bool(settings, "BrowserHWAccel", browserHWAccel); - obs_apply_private_data(settings); - - blog(LOG_INFO, "Current Date/Time: %s", CurrentDateTimeString().c_str()); - - blog(LOG_INFO, "Browser Hardware Acceleration: %s", browserHWAccel ? "true" : "false"); -#endif -#ifdef _WIN32 - bool hideFromCapture = config_get_bool(userConfig, "BasicWindow", "HideOBSWindowsFromCapture"); - blog(LOG_INFO, "Hide OBS windows from screen capture: %s", hideFromCapture ? "true" : "false"); -#endif - - blog(LOG_INFO, "Qt Version: %s (runtime), %s (compiled)", qVersion(), QT_VERSION_STR); - blog(LOG_INFO, "Portable mode: %s", portable_mode ? "true" : "false"); - - if (safe_mode) { - blog(LOG_WARNING, "Safe Mode enabled."); - } else if (disable_3p_plugins) { - blog(LOG_WARNING, "Third-party plugins disabled."); - } - - setQuitOnLastWindowClosed(false); - - mainWindow = new OBSBasic(); - - mainWindow->setAttribute(Qt::WA_DeleteOnClose, true); - connect(mainWindow, &OBSBasic::destroyed, this, &OBSApp::quit); - - mainWindow->OBSInit(); - - connect(this, &QGuiApplication::applicationStateChanged, - [this](Qt::ApplicationState state) { ResetHotkeyState(state == Qt::ApplicationActive); }); - ResetHotkeyState(applicationState() == Qt::ApplicationActive); - return true; -} - -string OBSApp::GetVersionString(bool platform) const -{ - stringstream ver; - -#ifdef HAVE_OBSCONFIG_H - ver << obs_get_version_string(); -#else - ver << LIBOBS_API_MAJOR_VER << "." << LIBOBS_API_MINOR_VER << "." << LIBOBS_API_PATCH_VER; - -#endif - - if (platform) { - ver << " ("; -#ifdef _WIN32 - if (sizeof(void *) == 8) - ver << "64-bit, "; - else - ver << "32-bit, "; - - ver << "windows)"; -#elif __APPLE__ - ver << "mac)"; -#elif __OpenBSD__ - ver << "openbsd)"; -#elif __FreeBSD__ - ver << "freebsd)"; -#else /* assume linux for the time being */ - ver << "linux)"; -#endif - } - - return ver.str(); -} - -bool OBSApp::IsPortableMode() -{ - return portable_mode; -} - -bool OBSApp::IsUpdaterDisabled() -{ - return opt_disable_updater; -} - -bool OBSApp::IsMissingFilesCheckDisabled() -{ - return opt_disable_missing_files_check; -} - -#ifdef __APPLE__ -#define INPUT_AUDIO_SOURCE "coreaudio_input_capture" -#define OUTPUT_AUDIO_SOURCE "coreaudio_output_capture" -#elif _WIN32 -#define INPUT_AUDIO_SOURCE "wasapi_input_capture" -#define OUTPUT_AUDIO_SOURCE "wasapi_output_capture" -#else -#define INPUT_AUDIO_SOURCE "pulse_input_capture" -#define OUTPUT_AUDIO_SOURCE "pulse_output_capture" -#endif - -const char *OBSApp::InputAudioSource() const -{ - return INPUT_AUDIO_SOURCE; -} - -const char *OBSApp::OutputAudioSource() const -{ - return OUTPUT_AUDIO_SOURCE; -} - -const char *OBSApp::GetLastLog() const -{ - return lastLogFile.c_str(); -} - -const char *OBSApp::GetCurrentLog() const -{ - return currentLogFile.c_str(); -} - -const char *OBSApp::GetLastCrashLog() const -{ - return lastCrashLogFile.c_str(); -} - -bool OBSApp::TranslateString(const char *lookupVal, const char **out) const -{ - for (obs_frontend_translate_ui_cb cb : translatorHooks) { - if (cb(lookupVal, out)) - return true; - } - - return text_lookup_getstr(App()->GetTextLookup(), lookupVal, out); -} - -// Global handler to receive all QEvent::Show events so we can apply -// display affinity on any newly created windows and dialogs without -// caring where they are coming from (e.g. plugins). -bool OBSApp::notify(QObject *receiver, QEvent *e) -{ - QWidget *w; - QWindow *window; - int windowType; - - if (!receiver->isWidgetType()) - goto skip; - - if (e->type() != QEvent::Show) - goto skip; - - w = qobject_cast(receiver); - - if (!w->isWindow()) - goto skip; - - window = w->windowHandle(); - if (!window) - goto skip; - - windowType = window->flags() & Qt::WindowType::WindowType_Mask; - - if (windowType == Qt::WindowType::Dialog || windowType == Qt::WindowType::Window || - windowType == Qt::WindowType::Tool) { - OBSBasic *main = reinterpret_cast(GetMainWindow()); - if (main) - main->SetDisplayAffinity(window); - } - -skip: - return QApplication::notify(receiver, e); -} - -QString OBSTranslator::translate(const char *, const char *sourceText, const char *, int) const -{ - const char *out = nullptr; - QString str(sourceText); - str.replace(" ", ""); - if (!App()->TranslateString(QT_TO_UTF8(str), &out)) - return QString(sourceText); - - return QT_UTF8(out); -} - static bool get_token(lexer *lex, string &str, base_token_type type) { base_token token; @@ -1603,189 +400,6 @@ static void get_last_log(bool has_prefix, const char *subdir_to_use, std::string } } -string GenerateTimeDateFilename(const char *extension, bool noSpace) -{ - time_t now = time(0); - char file[256] = {}; - struct tm *cur_time; - - cur_time = localtime(&now); - snprintf(file, sizeof(file), "%d-%02d-%02d%c%02d-%02d-%02d.%s", cur_time->tm_year + 1900, cur_time->tm_mon + 1, - cur_time->tm_mday, noSpace ? '_' : ' ', cur_time->tm_hour, cur_time->tm_min, cur_time->tm_sec, - extension); - - return string(file); -} - -string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format) -{ - BPtr filename = os_generate_formatted_filename(extension, !noSpace, format); - return string(filename); -} - -static void FindBestFilename(string &strPath, bool noSpace) -{ - int num = 2; - - if (!os_file_exists(strPath.c_str())) - return; - - const char *ext = strrchr(strPath.c_str(), '.'); - if (!ext) - return; - - int extStart = int(ext - strPath.c_str()); - for (;;) { - string testPath = strPath; - string numStr; - - numStr = noSpace ? "_" : " ("; - numStr += to_string(num++); - if (!noSpace) - numStr += ")"; - - testPath.insert(extStart, numStr); - - if (!os_file_exists(testPath.c_str())) { - strPath = testPath; - break; - } - } -} - -static void ensure_directory_exists(string &path) -{ - replace(path.begin(), path.end(), '\\', '/'); - - size_t last = path.rfind('/'); - if (last == string::npos) - return; - - string directory = path.substr(0, last); - os_mkdirs(directory.c_str()); -} - -static void remove_reserved_file_characters(string &s) -{ - replace(s.begin(), s.end(), '\\', '/'); - replace(s.begin(), s.end(), '*', '_'); - replace(s.begin(), s.end(), '?', '_'); - replace(s.begin(), s.end(), '"', '_'); - replace(s.begin(), s.end(), '|', '_'); - replace(s.begin(), s.end(), ':', '_'); - replace(s.begin(), s.end(), '>', '_'); - replace(s.begin(), s.end(), '<', '_'); -} - -string GetFormatString(const char *format, const char *prefix, const char *suffix) -{ - string f; - - f = format; - - if (prefix && *prefix) { - string str_prefix = prefix; - - if (str_prefix.back() != ' ') - str_prefix += " "; - - size_t insert_pos = 0; - size_t tmp; - - tmp = f.find_last_of('/'); - if (tmp != string::npos && tmp > insert_pos) - insert_pos = tmp + 1; - - tmp = f.find_last_of('\\'); - if (tmp != string::npos && tmp > insert_pos) - insert_pos = tmp + 1; - - f.insert(insert_pos, str_prefix); - } - - if (suffix && *suffix) { - if (*suffix != ' ') - f += " "; - f += suffix; - } - - remove_reserved_file_characters(f); - - return f; -} - -string GetFormatExt(const char *container) -{ - string ext = container; - if (ext == "fragmented_mp4") - ext = "mp4"; - if (ext == "hybrid_mp4") - ext = "mp4"; - else if (ext == "fragmented_mov") - ext = "mov"; - else if (ext == "hls") - ext = "m3u8"; - else if (ext == "mpegts") - ext = "ts"; - - return ext; -} - -string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, const char *format) -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr; - - if (!dir) { - if (main->isVisible()) - OBSMessageBox::warning(main, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); - else - main->SysTrayNotify(QTStr("Output.BadPath.Text"), QSystemTrayIcon::Warning); - return ""; - } - - os_closedir(dir); - - string strPath; - strPath += path; - - char lastChar = strPath.back(); - if (lastChar != '/' && lastChar != '\\') - strPath += "/"; - - string ext = GetFormatExt(container); - strPath += GenerateSpecifiedFilename(ext.c_str(), noSpace, format); - ensure_directory_exists(strPath); - if (!overwrite) - FindBestFilename(strPath, noSpace); - - return strPath; -} - -vector> GetLocaleNames() -{ - string path; - if (!GetDataFilePath("locale.ini", path)) - throw "Could not find locale.ini path"; - - ConfigFile ini; - if (ini.Open(path.c_str(), CONFIG_OPEN_EXISTING) != 0) - throw "Could not open locale.ini"; - - size_t sections = config_num_sections(ini); - - vector> names; - names.reserve(sections); - for (size_t i = 0; i < sections; i++) { - const char *tag = config_get_section(ini, i); - const char *name = config_get_string(ini, tag, "Name"); - names.emplace_back(tag, name); - } - - return names; -} - static void create_log_file(fstream &logFile) { stringstream dst; @@ -2223,134 +837,6 @@ static void load_debug_privilege(void) } #endif -#ifdef __APPLE__ -#define BASE_PATH ".." -#else -#define BASE_PATH "../.." -#endif - -#define CONFIG_PATH BASE_PATH "/config" - -#if defined(ENABLE_PORTABLE_CONFIG) || defined(_WIN32) -#define ALLOW_PORTABLE_MODE 1 -#else -#define ALLOW_PORTABLE_MODE 0 -#endif - -int GetAppConfigPath(char *path, size_t size, const char *name) -{ -#if ALLOW_PORTABLE_MODE - if (portable_mode) { - if (name && *name) { - return snprintf(path, size, CONFIG_PATH "/%s", name); - } else { - return snprintf(path, size, CONFIG_PATH); - } - } else { - return os_get_config_path(path, size, name); - } -#else - return os_get_config_path(path, size, name); -#endif -} - -char *GetAppConfigPathPtr(const char *name) -{ -#if ALLOW_PORTABLE_MODE - if (portable_mode) { - char path[512]; - - if (snprintf(path, sizeof(path), CONFIG_PATH "/%s", name) > 0) { - return bstrdup(path); - } else { - return NULL; - } - } else { - return os_get_config_path_ptr(name); - } -#else - return os_get_config_path_ptr(name); -#endif -} - -int GetProgramDataPath(char *path, size_t size, const char *name) -{ - return os_get_program_data_path(path, size, name); -} - -char *GetProgramDataPathPtr(const char *name) -{ - return os_get_program_data_path_ptr(name); -} - -bool GetFileSafeName(const char *name, std::string &file) -{ - size_t base_len = strlen(name); - size_t len = os_utf8_to_wcs(name, base_len, nullptr, 0); - std::wstring wfile; - - if (!len) - return false; - - wfile.resize(len); - os_utf8_to_wcs(name, base_len, &wfile[0], len + 1); - - for (size_t i = wfile.size(); i > 0; i--) { - size_t im1 = i - 1; - - if (iswspace(wfile[im1])) { - wfile[im1] = '_'; - } else if (wfile[im1] != '_' && !iswalnum(wfile[im1])) { - wfile.erase(im1, 1); - } - } - - if (wfile.size() == 0) - wfile = L"characters_only"; - - len = os_wcs_to_utf8(wfile.c_str(), wfile.size(), nullptr, 0); - if (!len) - return false; - - file.resize(len); - os_wcs_to_utf8(wfile.c_str(), wfile.size(), &file[0], len + 1); - return true; -} - -bool GetClosestUnusedFileName(std::string &path, const char *extension) -{ - size_t len = path.size(); - if (extension) { - path += "."; - path += extension; - } - - if (!os_file_exists(path.c_str())) - return true; - - int index = 1; - - do { - path.resize(len); - path += std::to_string(++index); - if (extension) { - path += "."; - path += extension; - } - } while (os_file_exists(path.c_str())); - - return true; -} - -bool WindowPositionValid(QRect rect) -{ - for (QScreen *screen : QGuiApplication::screens()) { - if (screen->availableGeometry().intersects(rect)) - return true; - } - return false; -} - static inline bool arg_is(const char *arg, const char *long_form, const char *short_form) { return (long_form && strcmp(arg, long_form) == 0) || (short_form && strcmp(arg, short_form) == 0); @@ -2386,44 +872,6 @@ static void delete_safe_mode_sentinel(void) #endif } -#ifndef _WIN32 -void OBSApp::SigIntSignalHandler(int s) -{ - /* Handles SIGINT and writes to a socket. Qt will read - * from the socket in the main thread event loop and trigger - * a call to the ProcessSigInt slot, where we can safely run - * shutdown code without signal safety issues. */ - UNUSED_PARAMETER(s); - - char a = 1; - send(sigintFd[0], &a, sizeof(a), 0); -} -#endif - -void OBSApp::ProcessSigInt(void) -{ - /* This looks weird, but we can't ifdef a Qt slot function so - * the SIGINT handler simply does nothing on Windows. */ -#ifndef _WIN32 - char tmp; - recv(sigintFd[1], &tmp, sizeof(tmp), 0); - - OBSBasic *main = reinterpret_cast(GetMainWindow()); - if (main) - main->close(); -#endif -} - -#ifdef _WIN32 -void OBSApp::commitData(QSessionManager &manager) -{ - if (auto main = App()->GetMainWindow()) { - QMetaObject::invokeMethod(main, "close", Qt::QueuedConnection); - manager.cancel(); - } -} -#endif - #ifdef _WIN32 static constexpr char vcRunErrorTitle[] = "Outdated Visual C++ Runtime"; static constexpr char vcRunErrorMsg[] = "OBS Studio requires a newer version of the Microsoft Visual C++ " @@ -2449,6 +897,18 @@ static bool vc_runtime_outdated() } #endif +#if defined(__APPLE__) || defined(__linux__) +#define BASE_PATH ".." +#else +#define BASE_PATH "../.." +#endif + +#if defined(ENABLE_PORTABLE_CONFIG) || defined(_WIN32) +#define ALLOW_PORTABLE_MODE 1 +#else +#define ALLOW_PORTABLE_MODE 0 +#endif + int main(int argc, char *argv[]) { #ifndef _WIN32 diff --git a/frontend/utility/BaseLexer.hpp b/frontend/utility/BaseLexer.hpp index dea5e530b..c32156b2a 100644 --- a/frontend/utility/BaseLexer.hpp +++ b/frontend/utility/BaseLexer.hpp @@ -17,41 +17,7 @@ #pragma once -#include -#include -#include -#include - -#ifndef _WIN32 -#include -#else -#include -#endif -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "window-main.hpp" -#include "obs-app-theming.hpp" - -std::string CurrentTimeString(); -std::string CurrentDateTimeString(); -std::string GenerateTimeDateFilename(const char *extension, bool noSpace = false); -std::string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format); -std::string GetFormatString(const char *format, const char *prefix, const char *suffix); -std::string GetFormatExt(const char *container); -std::string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, - const char *format); -QObject *CreateShortcutFilter(); struct BaseLexer { lexer lex; @@ -61,220 +27,3 @@ public: inline ~BaseLexer() { lexer_free(&lex); } operator lexer *() { return &lex; } }; - -class OBSTranslator : public QTranslator { - Q_OBJECT - -public: - virtual bool isEmpty() const override { return false; } - - virtual QString translate(const char *context, const char *sourceText, const char *disambiguation, - int n) const override; -}; - -typedef std::function VoidFunc; - -struct UpdateBranch { - QString name; - QString display_name; - QString description; - bool is_enabled; - bool is_visible; -}; - -class OBSApp : public QApplication { - Q_OBJECT - -private: - std::string locale; - - ConfigFile appConfig; - ConfigFile userConfig; - TextLookup textLookup; - QPointer mainWindow; - profiler_name_store_t *profilerNameStore = nullptr; - std::vector updateBranches; - bool branches_loaded = false; - - bool libobs_initialized = false; - - os_inhibit_t *sleepInhibitor = nullptr; - int sleepInhibitRefs = 0; - - bool enableHotkeysInFocus = true; - bool enableHotkeysOutOfFocus = true; - - std::deque translatorHooks; - - bool UpdatePre22MultiviewLayout(const char *layout); - - bool InitGlobalConfig(); - bool InitGlobalConfigDefaults(); - bool InitGlobalLocationDefaults(); - - bool MigrateGlobalSettings(); - void MigrateLegacySettings(uint32_t lastVersion); - - bool InitUserConfig(std::filesystem::path &userConfigLocation, uint32_t lastVersion); - void InitUserConfigDefaults(); - - bool InitLocale(); - bool InitTheme(); - - inline void ResetHotkeyState(bool inFocus); - - QPalette defaultPalette; - OBSTheme *currentTheme = nullptr; - QHash themes; - QPointer themeWatcher; - - void FindThemes(); - - bool notify(QObject *receiver, QEvent *e) override; - -#ifndef _WIN32 - static int sigintFd[2]; - QSocketNotifier *snInt = nullptr; -#else -private slots: - void commitData(QSessionManager &manager); -#endif - -private slots: - void themeFileChanged(const QString &); - -public: - OBSApp(int &argc, char **argv, profiler_name_store_t *store); - ~OBSApp(); - - void AppInit(); - bool OBSInit(); - - void UpdateHotkeyFocusSetting(bool reset = true); - void DisableHotkeys(); - - inline bool HotkeysEnabledInFocus() const { return enableHotkeysInFocus; } - - inline QMainWindow *GetMainWindow() const { return mainWindow.data(); } - - inline config_t *GetAppConfig() const { return appConfig; } - inline config_t *GetUserConfig() const { return userConfig; } - std::filesystem::path userConfigLocation; - std::filesystem::path userScenesLocation; - std::filesystem::path userProfilesLocation; - - inline const char *GetLocale() const { return locale.c_str(); } - - OBSTheme *GetTheme() const { return currentTheme; } - QList GetThemes() const { return themes.values(); } - OBSTheme *GetTheme(const QString &name); - bool SetTheme(const QString &name); - bool IsThemeDark() const { return currentTheme ? currentTheme->isDark : false; } - - void SetBranchData(const std::string &data); - std::vector GetBranches(); - - inline lookup_t *GetTextLookup() const { return textLookup; } - - inline const char *GetString(const char *lookupVal) const { return textLookup.GetString(lookupVal); } - - bool TranslateString(const char *lookupVal, const char **out) const; - - profiler_name_store_t *GetProfilerNameStore() const { return profilerNameStore; } - - const char *GetLastLog() const; - const char *GetCurrentLog() const; - - const char *GetLastCrashLog() const; - - std::string GetVersionString(bool platform = true) const; - bool IsPortableMode(); - bool IsUpdaterDisabled(); - bool IsMissingFilesCheckDisabled(); - - const char *InputAudioSource() const; - const char *OutputAudioSource() const; - - const char *GetRenderModule() const; - - inline void IncrementSleepInhibition() - { - if (!sleepInhibitor) - return; - if (sleepInhibitRefs++ == 0) - os_inhibit_sleep_set_active(sleepInhibitor, true); - } - - inline void DecrementSleepInhibition() - { - if (!sleepInhibitor) - return; - if (sleepInhibitRefs == 0) - return; - if (--sleepInhibitRefs == 0) - os_inhibit_sleep_set_active(sleepInhibitor, false); - } - - inline void PushUITranslation(obs_frontend_translate_ui_cb cb) { translatorHooks.emplace_front(cb); } - - inline void PopUITranslation() { translatorHooks.pop_front(); } -#ifndef _WIN32 - static void SigIntSignalHandler(int); -#endif - -public slots: - void Exec(VoidFunc func); - void ProcessSigInt(); - -signals: - void StyleChanged(); -}; - -int GetAppConfigPath(char *path, size_t size, const char *name); -char *GetAppConfigPathPtr(const char *name); - -int GetProgramDataPath(char *path, size_t size, const char *name); -char *GetProgramDataPathPtr(const char *name); - -inline OBSApp *App() -{ - return static_cast(qApp); -} - -std::vector> GetLocaleNames(); -inline const char *Str(const char *lookup) -{ - return App()->GetString(lookup); -} -inline QString QTStr(const char *lookupVal) -{ - return QString::fromUtf8(Str(lookupVal)); -} - -bool GetFileSafeName(const char *name, std::string &file); -bool GetClosestUnusedFileName(std::string &path, const char *extension); -bool GetUnusedSceneCollectionFile(std::string &name, std::string &file); - -bool WindowPositionValid(QRect rect); - -extern bool portable_mode; -extern bool steam; -extern bool safe_mode; -extern bool disable_3p_plugins; - -extern bool opt_start_streaming; -extern bool opt_start_recording; -extern bool opt_start_replaybuffer; -extern bool opt_start_virtualcam; -extern bool opt_minimize_tray; -extern bool opt_studio_mode; -extern bool opt_allow_opengl; -extern bool opt_always_on_top; -extern std::string opt_starting_scene; -extern bool restart; -extern bool restart_safe; - -#ifdef _WIN32 -extern "C" void install_dll_blocklist_hook(void); -extern "C" void log_blocked_dlls(void); -#endif diff --git a/frontend/utility/OBSTheme.hpp b/frontend/utility/OBSTheme.hpp index cbaed7f24..dbb3b0a6c 100644 --- a/frontend/utility/OBSTheme.hpp +++ b/frontend/utility/OBSTheme.hpp @@ -17,12 +17,11 @@ #pragma once -#include +#include +#include #include -struct OBSThemeVariable; - struct OBSTheme { /* internal name, must be unique */ QString id; @@ -43,24 +42,3 @@ struct OBSTheme { bool isBaseTheme; /* Whether it is a "style" or variant */ bool isHighContrast; /* Whether it is a high-contrast adjustment layer */ }; - -struct OBSThemeVariable { - enum VariableType { - Color, /* RGB color value*/ - Size, /* Number with suffix denoting size (e.g. px, pt, em) */ - Number, /* Number without suffix */ - String, /* Raw string (e.g. color name, border style, etc.) */ - Alias, /* Points at another variable, value will be the key */ - Calc, /* Simple calculation with two operands */ - }; - - /* Whether the variable should be editable in the UI */ - bool editable = false; - /* Used for VariableType::Size only */ - QString suffix; - - VariableType type; - QString name; - QVariant value; - QVariant userValue; /* If overwritten by user, use this value instead */ -}; diff --git a/frontend/utility/OBSThemeVariable.hpp b/frontend/utility/OBSThemeVariable.hpp index cbaed7f24..f04f90cc5 100644 --- a/frontend/utility/OBSThemeVariable.hpp +++ b/frontend/utility/OBSThemeVariable.hpp @@ -17,33 +17,9 @@ #pragma once +#include #include -#include - -struct OBSThemeVariable; - -struct OBSTheme { - /* internal name, must be unique */ - QString id; - QString name; - QString author; - QString extends; - - /* First ancestor base theme */ - QString parent; - /* Dependencies from root to direct ancestor */ - QStringList dependencies; - /* File path */ - std::filesystem::path location; - std::filesystem::path filename; /* Filename without extension */ - - bool isDark; - bool isVisible; /* Whether it should be shown to the user */ - bool isBaseTheme; /* Whether it is a "style" or variant */ - bool isHighContrast; /* Whether it is a high-contrast adjustment layer */ -}; - struct OBSThemeVariable { enum VariableType { Color, /* RGB color value*/ diff --git a/frontend/utility/OBSTranslator.cpp b/frontend/utility/OBSTranslator.cpp index 883bee21a..f61354cce 100644 --- a/frontend/utility/OBSTranslator.cpp +++ b/frontend/utility/OBSTranslator.cpp @@ -15,1427 +15,13 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSTranslator.hpp" + +#include + #include -#include -#include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "obs-proxy-style.hpp" -#include "log-viewer.hpp" -#include "volume-control.hpp" -#include "window-basic-main.hpp" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-basic-settings.hpp" -#include "platform.hpp" - -#include - -#include - -#ifdef _WIN32 -#include -#include -#include -#else -#include -#include -#include -#include -#include -#endif - -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) -#include "update/models/branches.hpp" -#endif - -#if !defined(_WIN32) && !defined(__APPLE__) -#include -#include -#endif - -#include - -#include "ui-config.h" - -using namespace std; - -static log_handler_t def_log_handler; - -static string currentLogFile; -static string lastLogFile; -static string lastCrashLogFile; - -bool portable_mode = false; -bool steam = false; -bool safe_mode = false; -bool disable_3p_plugins = false; -bool unclean_shutdown = false; -bool disable_shutdown_check = false; -static bool multi = false; -static bool log_verbose = false; -static bool unfiltered_log = false; -bool opt_start_streaming = false; -bool opt_start_recording = false; -bool opt_studio_mode = false; -bool opt_start_replaybuffer = false; -bool opt_start_virtualcam = false; -bool opt_minimize_tray = false; -bool opt_allow_opengl = false; -bool opt_always_on_top = false; -bool opt_disable_updater = false; -bool opt_disable_missing_files_check = false; -string opt_starting_collection; -string opt_starting_profile; -string opt_starting_scene; - -bool restart = false; -bool restart_safe = false; -QStringList arguments; - -QPointer obsLogViewer; - -#ifndef _WIN32 -int OBSApp::sigintFd[2]; -#endif - -// GPU hint exports for AMD/NVIDIA laptops -#ifdef _MSC_VER -extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1; -extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -#endif - -QObject *CreateShortcutFilter() -{ - return new OBSEventFilter([](QObject *obj, QEvent *event) { - auto mouse_event = [](QMouseEvent &event) { - if (!App()->HotkeysEnabledInFocus() && event.button() != Qt::LeftButton) - return true; - - obs_key_combination_t hotkey = {0, OBS_KEY_NONE}; - bool pressed = event.type() == QEvent::MouseButtonPress; - - switch (event.button()) { - case Qt::NoButton: - case Qt::LeftButton: - case Qt::RightButton: - case Qt::AllButtons: - case Qt::MouseButtonMask: - return false; - - case Qt::MiddleButton: - hotkey.key = OBS_KEY_MOUSE3; - break; - -#define MAP_BUTTON(i, j) \ - case Qt::ExtraButton##i: \ - hotkey.key = OBS_KEY_MOUSE##j; \ - break; - MAP_BUTTON(1, 4); - MAP_BUTTON(2, 5); - MAP_BUTTON(3, 6); - MAP_BUTTON(4, 7); - MAP_BUTTON(5, 8); - MAP_BUTTON(6, 9); - MAP_BUTTON(7, 10); - MAP_BUTTON(8, 11); - MAP_BUTTON(9, 12); - MAP_BUTTON(10, 13); - MAP_BUTTON(11, 14); - MAP_BUTTON(12, 15); - MAP_BUTTON(13, 16); - MAP_BUTTON(14, 17); - MAP_BUTTON(15, 18); - MAP_BUTTON(16, 19); - MAP_BUTTON(17, 20); - MAP_BUTTON(18, 21); - MAP_BUTTON(19, 22); - MAP_BUTTON(20, 23); - MAP_BUTTON(21, 24); - MAP_BUTTON(22, 25); - MAP_BUTTON(23, 26); - MAP_BUTTON(24, 27); -#undef MAP_BUTTON - } - - hotkey.modifiers = TranslateQtKeyboardEventModifiers(event.modifiers()); - - obs_hotkey_inject_event(hotkey, pressed); - return true; - }; - - auto key_event = [&](QKeyEvent *event) { - int key = event->key(); - bool enabledInFocus = App()->HotkeysEnabledInFocus(); - - if (key != Qt::Key_Enter && key != Qt::Key_Escape && key != Qt::Key_Return && !enabledInFocus) - return true; - - QDialog *dialog = qobject_cast(obj); - - obs_key_combination_t hotkey = {0, OBS_KEY_NONE}; - bool pressed = event->type() == QEvent::KeyPress; - - switch (key) { - case Qt::Key_Shift: - case Qt::Key_Control: - case Qt::Key_Alt: - case Qt::Key_Meta: - break; - -#ifdef __APPLE__ - case Qt::Key_CapsLock: - // kVK_CapsLock == 57 - hotkey.key = obs_key_from_virtual_key(57); - pressed = true; - break; -#endif - - case Qt::Key_Enter: - case Qt::Key_Escape: - case Qt::Key_Return: - if (dialog && pressed) - return false; - if (!enabledInFocus) - return true; - /* Falls through. */ - default: - hotkey.key = obs_key_from_virtual_key(event->nativeVirtualKey()); - } - - if (event->isAutoRepeat()) - return true; - - hotkey.modifiers = TranslateQtKeyboardEventModifiers(event->modifiers()); - - obs_hotkey_inject_event(hotkey, pressed); - return true; - }; - - switch (event->type()) { - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - return mouse_event(*static_cast(event)); - - /*case QEvent::MouseButtonDblClick: - case QEvent::Wheel:*/ - case QEvent::KeyPress: - case QEvent::KeyRelease: - return key_event(static_cast(event)); - - default: - return false; - } - }); -} - -string CurrentTimeString() -{ - using namespace std::chrono; - - struct tm tstruct; - char buf[80]; - - auto tp = system_clock::now(); - auto now = system_clock::to_time_t(tp); - tstruct = *localtime(&now); - - size_t written = strftime(buf, sizeof(buf), "%T", &tstruct); - if (ratio_less::value && written && (sizeof(buf) - written) > 5) { - auto tp_secs = time_point_cast(tp); - auto millis = duration_cast(tp - tp_secs).count(); - - snprintf(buf + written, sizeof(buf) - written, ".%03u", static_cast(millis)); - } - - return buf; -} - -string CurrentDateTimeString() -{ - time_t now = time(0); - struct tm tstruct; - char buf[80]; - tstruct = *localtime(&now); - strftime(buf, sizeof(buf), "%Y-%m-%d, %X", &tstruct); - return buf; -} - -static void LogString(fstream &logFile, const char *timeString, char *str, int log_level) -{ - static mutex logfile_mutex; - string msg; - msg += timeString; - msg += str; - - logfile_mutex.lock(); - logFile << msg << endl; - logfile_mutex.unlock(); - - if (!!obsLogViewer) - QMetaObject::invokeMethod(obsLogViewer.data(), "AddLine", Qt::QueuedConnection, Q_ARG(int, log_level), - Q_ARG(QString, QString(msg.c_str()))); -} - -static inline void LogStringChunk(fstream &logFile, char *str, int log_level) -{ - char *nextLine = str; - string timeString = CurrentTimeString(); - timeString += ": "; - - while (*nextLine) { - char *nextLine = strchr(str, '\n'); - if (!nextLine) - break; - - if (nextLine != str && nextLine[-1] == '\r') { - nextLine[-1] = 0; - } else { - nextLine[0] = 0; - } - - LogString(logFile, timeString.c_str(), str, log_level); - nextLine++; - str = nextLine; - } - - LogString(logFile, timeString.c_str(), str, log_level); -} - -#define MAX_REPEATED_LINES 30 -#define MAX_CHAR_VARIATION (255 * 3) - -static inline int sum_chars(const char *str) -{ - int val = 0; - for (; *str != 0; str++) - val += *str; - - return val; -} - -static inline bool too_many_repeated_entries(fstream &logFile, const char *msg, const char *output_str) -{ - static mutex log_mutex; - static const char *last_msg_ptr = nullptr; - static int last_char_sum = 0; - static int rep_count = 0; - - int new_sum = sum_chars(output_str); - - lock_guard guard(log_mutex); - - if (unfiltered_log) { - return false; - } - - if (last_msg_ptr == msg) { - int diff = std::abs(new_sum - last_char_sum); - if (diff < MAX_CHAR_VARIATION) { - return (rep_count++ >= MAX_REPEATED_LINES); - } - } - - if (rep_count > MAX_REPEATED_LINES) { - logFile << CurrentTimeString() << ": Last log entry repeated for " - << to_string(rep_count - MAX_REPEATED_LINES) << " more lines" << endl; - } - - last_msg_ptr = msg; - last_char_sum = new_sum; - rep_count = 0; - - return false; -} - -static void do_log(int log_level, const char *msg, va_list args, void *param) -{ - fstream &logFile = *static_cast(param); - char str[8192]; - -#ifndef _WIN32 - va_list args2; - va_copy(args2, args); -#endif - - vsnprintf(str, sizeof(str), msg, args); - -#ifdef _WIN32 - if (IsDebuggerPresent()) { - int wNum = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - if (wNum > 1) { - static wstring wide_buf; - static mutex wide_mutex; - - lock_guard lock(wide_mutex); - wide_buf.reserve(wNum + 1); - wide_buf.resize(wNum - 1); - MultiByteToWideChar(CP_UTF8, 0, str, -1, &wide_buf[0], wNum); - wide_buf.push_back('\n'); - - OutputDebugStringW(wide_buf.c_str()); - } - } -#endif - -#if !defined(_WIN32) && defined(_DEBUG) - def_log_handler(log_level, msg, args2, nullptr); -#endif - - if (log_level <= LOG_INFO || log_verbose) { -#if !defined(_WIN32) && !defined(_DEBUG) - def_log_handler(log_level, msg, args2, nullptr); -#endif - if (!too_many_repeated_entries(logFile, msg, str)) - LogStringChunk(logFile, str, log_level); - } - -#if defined(_WIN32) && defined(OBS_DEBUGBREAK_ON_ERROR) - if (log_level <= LOG_ERROR && IsDebuggerPresent()) - __debugbreak(); -#endif - -#ifndef _WIN32 - va_end(args2); -#endif -} - -#define DEFAULT_LANG "en-US" - -bool OBSApp::InitGlobalConfigDefaults() -{ - config_set_default_uint(appConfig, "General", "MaxLogs", 10); - config_set_default_int(appConfig, "General", "InfoIncrement", -1); - config_set_default_string(appConfig, "General", "ProcessPriority", "Normal"); - config_set_default_bool(appConfig, "General", "EnableAutoUpdates", true); - -#if _WIN32 - config_set_default_string(appConfig, "Video", "Renderer", "Direct3D 11"); -#else - config_set_default_string(appConfig, "Video", "Renderer", "OpenGL"); -#endif - -#ifdef _WIN32 - config_set_default_bool(appConfig, "Audio", "DisableAudioDucking", true); - config_set_default_bool(appConfig, "General", "BrowserHWAccel", true); -#endif - -#ifdef __APPLE__ - config_set_default_bool(appConfig, "General", "BrowserHWAccel", true); - config_set_default_bool(appConfig, "Video", "DisableOSXVSync", true); - config_set_default_bool(appConfig, "Video", "ResetOSXVSyncOnExit", true); -#endif - - return true; -} - -bool OBSApp::InitGlobalLocationDefaults() -{ - char path[512]; - - int len = GetAppConfigPath(path, sizeof(path), nullptr); - if (len <= 0) { - OBSErrorBox(NULL, "Unable to get global configuration path."); - return false; - } - - config_set_default_string(appConfig, "Locations", "Configuration", path); - config_set_default_string(appConfig, "Locations", "SceneCollections", path); - config_set_default_string(appConfig, "Locations", "Profiles", path); - - return true; -} - -void OBSApp::InitUserConfigDefaults() -{ - config_set_default_bool(userConfig, "General", "ConfirmOnExit", true); - - config_set_default_string(userConfig, "General", "HotkeyFocusType", "NeverDisableHotkeys"); - - config_set_default_bool(userConfig, "BasicWindow", "PreviewEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "PreviewProgramMode", false); - config_set_default_bool(userConfig, "BasicWindow", "SceneDuplicationMode", true); - config_set_default_bool(userConfig, "BasicWindow", "SwapScenesMode", true); - config_set_default_bool(userConfig, "BasicWindow", "SnappingEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "ScreenSnapping", true); - config_set_default_bool(userConfig, "BasicWindow", "SourceSnapping", true); - config_set_default_bool(userConfig, "BasicWindow", "CenterSnapping", false); - config_set_default_double(userConfig, "BasicWindow", "SnapDistance", 10.0); - config_set_default_bool(userConfig, "BasicWindow", "SpacingHelpersEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "RecordWhenStreaming", false); - config_set_default_bool(userConfig, "BasicWindow", "KeepRecordingWhenStreamStops", false); - config_set_default_bool(userConfig, "BasicWindow", "SysTrayEnabled", true); - config_set_default_bool(userConfig, "BasicWindow", "SysTrayWhenStarted", false); - config_set_default_bool(userConfig, "BasicWindow", "SaveProjectors", false); - config_set_default_bool(userConfig, "BasicWindow", "ShowTransitions", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowListboxToolbars", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowStatusBar", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowSourceIcons", true); - config_set_default_bool(userConfig, "BasicWindow", "ShowContextToolbars", true); - config_set_default_bool(userConfig, "BasicWindow", "StudioModeLabels", true); - - config_set_default_bool(userConfig, "BasicWindow", "VerticalVolControl", false); - - config_set_default_bool(userConfig, "BasicWindow", "MultiviewMouseSwitch", true); - - config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawNames", true); - - config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawAreas", true); - - config_set_default_bool(userConfig, "BasicWindow", "MediaControlsCountdownTimer", true); -} - -static bool do_mkdir(const char *path) -{ - if (os_mkdirs(path) == MKDIR_ERROR) { - OBSErrorBox(NULL, "Failed to create directory %s", path); - return false; - } - - return true; -} - -static bool MakeUserDirs() -{ - char path[512]; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/basic") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/logs") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/profiler_data") <= 0) - return false; - if (!do_mkdir(path)) - return false; - -#ifdef _WIN32 - if (GetAppConfigPath(path, sizeof(path), "obs-studio/crashes") <= 0) - return false; - if (!do_mkdir(path)) - return false; -#endif - -#ifdef WHATSNEW_ENABLED - if (GetAppConfigPath(path, sizeof(path), "obs-studio/updates") <= 0) - return false; - if (!do_mkdir(path)) - return false; -#endif - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0) - return false; - if (!do_mkdir(path)) - return false; - - return true; -} - -constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles"; -constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes"; - -static bool MakeUserProfileDirs() -{ - const std::filesystem::path userProfilePath = - App()->userProfilesLocation / std::filesystem::u8path(OBSProfileSubDirectory); - const std::filesystem::path userScenesPath = - App()->userScenesLocation / std::filesystem::u8path(OBSScenesSubDirectory); - - if (!std::filesystem::exists(userProfilePath)) { - try { - std::filesystem::create_directories(userProfilePath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create user profile directory '%s'\n%s", - userProfilePath.u8string().c_str(), error.what()); - return false; - } - } - - if (!std::filesystem::exists(userScenesPath)) { - try { - std::filesystem::create_directories(userScenesPath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create user scene collection directory '%s'\n%s", - userScenesPath.u8string().c_str(), error.what()); - return false; - } - } - - return true; -} - -bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) -{ - if (!layout) - return false; - - if (astrcmpi(layout, "horizontaltop") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::HORIZONTAL_TOP_8_SCENES)); - return true; - } - - if (astrcmpi(layout, "horizontalbottom") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES)); - return true; - } - - if (astrcmpi(layout, "verticalleft") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::VERTICAL_LEFT_8_SCENES)); - return true; - } - - if (astrcmpi(layout, "verticalright") == 0) { - config_set_int(userConfig, "BasicWindow", "MultiviewLayout", - static_cast(MultiviewLayout::VERTICAL_RIGHT_8_SCENES)); - return true; - } - - return false; -} - -bool OBSApp::InitGlobalConfig() -{ - char path[512]; - - int len = GetAppConfigPath(path, sizeof(path), "obs-studio/global.ini"); - if (len <= 0) { - return false; - } - - int errorcode = appConfig.Open(path, CONFIG_OPEN_ALWAYS); - if (errorcode != CONFIG_SUCCESS) { - OBSErrorBox(NULL, "Failed to open global.ini: %d", errorcode); - return false; - } - - uint32_t lastVersion = config_get_int(appConfig, "General", "LastVersion"); - - if (lastVersion < MAKE_SEMANTIC_VERSION(31, 0, 0)) { - bool migratedUserSettings = config_get_bool(appConfig, "General", "Pre31Migrated"); - - if (!migratedUserSettings) { - bool migrated = MigrateGlobalSettings(); - - config_set_bool(appConfig, "General", "Pre31Migrated", migrated); - config_save_safe(appConfig, "tmp", nullptr); - } - } - - InitGlobalConfigDefaults(); - InitGlobalLocationDefaults(); - - if (IsPortableMode()) { - userConfigLocation = - std::filesystem::u8path(config_get_default_string(appConfig, "Locations", "Configuration")); - userScenesLocation = - std::filesystem::u8path(config_get_default_string(appConfig, "Locations", "SceneCollections")); - userProfilesLocation = - std::filesystem::u8path(config_get_default_string(appConfig, "Locations", "Profiles")); - } else { - userConfigLocation = - std::filesystem::u8path(config_get_string(appConfig, "Locations", "Configuration")); - userScenesLocation = - std::filesystem::u8path(config_get_string(appConfig, "Locations", "SceneCollections")); - userProfilesLocation = std::filesystem::u8path(config_get_string(appConfig, "Locations", "Profiles")); - } - - bool userConfigResult = InitUserConfig(userConfigLocation, lastVersion); - - return userConfigResult; -} - -bool OBSApp::InitUserConfig(std::filesystem::path &userConfigLocation, uint32_t lastVersion) -{ - const std::string userConfigFile = userConfigLocation.u8string() + "/obs-studio/user.ini"; - - int errorCode = userConfig.Open(userConfigFile.c_str(), CONFIG_OPEN_ALWAYS); - - if (errorCode != CONFIG_SUCCESS) { - OBSErrorBox(nullptr, "Failed to open user.ini: %d", errorCode); - return false; - } - - MigrateLegacySettings(lastVersion); - InitUserConfigDefaults(); - - return true; -} - -void OBSApp::MigrateLegacySettings(const uint32_t lastVersion) -{ - bool hasChanges = false; - - const uint32_t v19 = MAKE_SEMANTIC_VERSION(19, 0, 0); - const uint32_t v21 = MAKE_SEMANTIC_VERSION(21, 0, 0); - const uint32_t v23 = MAKE_SEMANTIC_VERSION(23, 0, 0); - const uint32_t v24 = MAKE_SEMANTIC_VERSION(24, 0, 0); - const uint32_t v24_1 = MAKE_SEMANTIC_VERSION(24, 1, 0); - - const map defaultsMap{ - {{v19, "Pre19Defaults"}, {v21, "Pre21Defaults"}, {v23, "Pre23Defaults"}, {v24_1, "Pre24.1Defaults"}}}; - - for (auto &[version, configKey] : defaultsMap) { - if (!config_has_user_value(userConfig, "General", configKey.c_str())) { - bool useOldDefaults = lastVersion && lastVersion < version; - config_set_bool(userConfig, "General", configKey.c_str(), useOldDefaults); - - hasChanges = true; - } - } - - if (config_has_user_value(userConfig, "BasicWindow", "MultiviewLayout")) { - const char *layout = config_get_string(userConfig, "BasicWindow", "MultiviewLayout"); - - bool layoutUpdated = UpdatePre22MultiviewLayout(layout); - - hasChanges = hasChanges | layoutUpdated; - } - - if (lastVersion && lastVersion < v24) { - bool disableHotkeysInFocus = config_get_bool(userConfig, "General", "DisableHotkeysInFocus"); - - if (disableHotkeysInFocus) { - config_set_string(userConfig, "General", "HotkeyFocusType", "DisableHotkeysInFocus"); - } - - hasChanges = true; - } - - if (hasChanges) { - userConfig.SaveSafe("tmp"); - } -} - -static constexpr string_view OBSGlobalIniPath = "/obs-studio/global.ini"; -static constexpr string_view OBSUserIniPath = "/obs-studio/user.ini"; - -bool OBSApp::MigrateGlobalSettings() -{ - char path[512]; - - int len = GetAppConfigPath(path, sizeof(path), nullptr); - if (len <= 0) { - OBSErrorBox(nullptr, "Unable to get global configuration path."); - return false; - } - - std::string legacyConfigFileString; - legacyConfigFileString.reserve(strlen(path) + OBSGlobalIniPath.size()); - legacyConfigFileString.append(path).append(OBSGlobalIniPath); - - const std::filesystem::path legacyGlobalConfigFile = std::filesystem::u8path(legacyConfigFileString); - - std::string configFileString; - configFileString.reserve(strlen(path) + OBSUserIniPath.size()); - configFileString.append(path).append(OBSUserIniPath); - - const std::filesystem::path userConfigFile = std::filesystem::u8path(configFileString); - - if (std::filesystem::exists(userConfigFile)) { - OBSErrorBox(nullptr, - "Unable to migrate global configuration - user configuration file already exists."); - return false; - } - - try { - std::filesystem::copy(legacyGlobalConfigFile, userConfigFile); - } catch (const std::filesystem::filesystem_error &) { - OBSErrorBox(nullptr, "Unable to migrate global configuration - copy failed."); - return false; - } - - return true; -} - -bool OBSApp::InitLocale() -{ - ProfileScope("OBSApp::InitLocale"); - - const char *lang = config_get_string(userConfig, "General", "Language"); - bool userLocale = config_has_user_value(userConfig, "General", "Language"); - if (!userLocale || !lang || lang[0] == '\0') - lang = DEFAULT_LANG; - - locale = lang; - - // set basic default application locale - if (!locale.empty()) - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - - string englishPath; - if (!GetDataFilePath("locale/" DEFAULT_LANG ".ini", englishPath)) { - OBSErrorBox(NULL, "Failed to find locale/" DEFAULT_LANG ".ini"); - return false; - } - - textLookup = text_lookup_create(englishPath.c_str()); - if (!textLookup) { - OBSErrorBox(NULL, "Failed to create locale from file '%s'", englishPath.c_str()); - return false; - } - - bool defaultLang = astrcmpi(lang, DEFAULT_LANG) == 0; - - if (userLocale && defaultLang) - return true; - - if (!userLocale && defaultLang) { - for (auto &locale_ : GetPreferredLocales()) { - if (locale_ == lang) - return true; - - stringstream file; - file << "locale/" << locale_ << ".ini"; - - string path; - if (!GetDataFilePath(file.str().c_str(), path)) - continue; - - if (!text_lookup_add(textLookup, path.c_str())) - continue; - - blog(LOG_INFO, "Using preferred locale '%s'", locale_.c_str()); - locale = locale_; - - // set application default locale to the new choosen one - if (!locale.empty()) - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - - return true; - } - - return true; - } - - stringstream file; - file << "locale/" << lang << ".ini"; - - string path; - if (GetDataFilePath(file.str().c_str(), path)) { - if (!text_lookup_add(textLookup, path.c_str())) - blog(LOG_ERROR, "Failed to add locale file '%s'", path.c_str()); - } else { - blog(LOG_ERROR, "Could not find locale file '%s'", file.str().c_str()); - } - - return true; -} - -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) -void ParseBranchesJson(const std::string &jsonString, vector &out, std::string &error) -{ - JsonBranches branches; - - try { - nlohmann::json json = nlohmann::json::parse(jsonString); - branches = json.get(); - } catch (nlohmann::json::exception &e) { - error = e.what(); - return; - } - - for (const JsonBranch &json_branch : branches) { -#ifdef _WIN32 - if (!json_branch.windows) - continue; -#elif defined(__APPLE__) - if (!json_branch.macos) - continue; -#endif - - UpdateBranch branch = { - QString::fromStdString(json_branch.name), - QString::fromStdString(json_branch.display_name), - QString::fromStdString(json_branch.description), - json_branch.enabled, - json_branch.visible, - }; - out.push_back(branch); - } -} - -bool LoadBranchesFile(vector &out) -{ - string error; - string branchesText; - - BPtr branchesFilePath = GetAppConfigPathPtr("obs-studio/updates/branches.json"); - - QFile branchesFile(branchesFilePath.Get()); - if (!branchesFile.open(QIODevice::ReadOnly)) { - error = "Opening file failed."; - goto fail; - } - - branchesText = branchesFile.readAll(); - if (branchesText.empty()) { - error = "File empty."; - goto fail; - } - - ParseBranchesJson(branchesText, out, error); - if (error.empty()) - return !out.empty(); - -fail: - blog(LOG_WARNING, "Loading branches from file failed: %s", error.c_str()); - return false; -} -#endif - -void OBSApp::SetBranchData(const string &data) -{ -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) - string error; - vector result; - - ParseBranchesJson(data, result, error); - - if (!error.empty()) { - blog(LOG_WARNING, "Reading branches JSON response failed: %s", error.c_str()); - return; - } - - if (!result.empty()) - updateBranches = result; - - branches_loaded = true; -#else - UNUSED_PARAMETER(data); -#endif -} - -std::vector OBSApp::GetBranches() -{ - vector out; - /* Always ensure the default branch exists */ - out.push_back(UpdateBranch{"stable", "", "", true, true}); - -#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER) - if (!branches_loaded) { - vector result; - if (LoadBranchesFile(result)) - updateBranches = result; - - branches_loaded = true; - } -#endif - - /* Copy additional branches to result (if any) */ - if (!updateBranches.empty()) - out.insert(out.end(), updateBranches.begin(), updateBranches.end()); - - return out; -} - -OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store) - : QApplication(argc, argv), - profilerNameStore(store) -{ - /* fix float handling */ -#if defined(Q_OS_UNIX) - if (!setlocale(LC_NUMERIC, "C")) - blog(LOG_WARNING, "Failed to set LC_NUMERIC to C locale"); -#endif - -#ifndef _WIN32 - /* Handle SIGINT properly */ - socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd); - snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this); - connect(snInt, &QSocketNotifier::activated, this, &OBSApp::ProcessSigInt); -#else - connect(qApp, &QGuiApplication::commitDataRequest, this, &OBSApp::commitData); -#endif - - sleepInhibitor = os_inhibit_sleep_create("OBS Video/audio"); - -#ifndef __APPLE__ - setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png"))); -#endif - - setDesktopFileName("com.obsproject.Studio"); -} - -OBSApp::~OBSApp() -{ -#ifdef _WIN32 - bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking"); - if (disableAudioDucking) - DisableAudioDucking(false); -#else - delete snInt; - close(sigintFd[0]); - close(sigintFd[1]); -#endif - -#ifdef __APPLE__ - bool vsyncDisabled = config_get_bool(appConfig, "Video", "DisableOSXVSync"); - bool resetVSync = config_get_bool(appConfig, "Video", "ResetOSXVSyncOnExit"); - if (vsyncDisabled && resetVSync) - EnableOSXVSync(true); -#endif - - os_inhibit_sleep_set_active(sleepInhibitor, false); - os_inhibit_sleep_destroy(sleepInhibitor); - - if (libobs_initialized) - obs_shutdown(); -} - -static void move_basic_to_profiles(void) -{ - char path[512]; - - if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) { - return; - } - - const std::filesystem::path basicPath = std::filesystem::u8path(path); - - if (!std::filesystem::exists(basicPath)) { - return; - } - - const std::filesystem::path profilesPath = - App()->userProfilesLocation / std::filesystem::u8path("obs-studio/basic/profiles"); - - if (std::filesystem::exists(profilesPath)) { - return; - } - - try { - std::filesystem::create_directories(profilesPath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create profiles directory for migration from basic profile\n%s", - error.what()); - return; - } - - const std::filesystem::path newProfilePath = profilesPath / std::filesystem::u8path(Str("Untitled")); - - for (auto &entry : std::filesystem::directory_iterator(basicPath)) { - if (entry.is_directory()) { - continue; - } - - if (entry.path().filename().u8string() == "scenes.json") { - continue; - } - - if (!std::filesystem::exists(newProfilePath)) { - try { - std::filesystem::create_directory(newProfilePath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to create profile directory for 'Untitled'\n%s", error.what()); - return; - } - } - - const filesystem::path destinationFile = newProfilePath / entry.path().filename(); - - const auto copyOptions = std::filesystem::copy_options::overwrite_existing; - - try { - std::filesystem::copy(entry.path(), destinationFile, copyOptions); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to copy basic profile file '%s' to new profile 'Untitled'\n%s", - entry.path().filename().u8string().c_str(), error.what()); - - return; - } - } -} - -static void move_basic_to_scene_collections(void) -{ - char path[512]; - - if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) { - return; - } - - const std::filesystem::path basicPath = std::filesystem::u8path(path); - - if (!std::filesystem::exists(basicPath)) { - return; - } - - const std::filesystem::path sceneCollectionPath = - App()->userScenesLocation / std::filesystem::u8path("obs-studio/basic/scenes"); - - if (std::filesystem::exists(sceneCollectionPath)) { - return; - } - - try { - std::filesystem::create_directories(sceneCollectionPath); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, - "Failed to create scene collection directory for migration from basic scene collection\n%s", - error.what()); - return; - } - - const std::filesystem::path sourceFile = basicPath / std::filesystem::u8path("scenes.json"); - const std::filesystem::path destinationFile = - (sceneCollectionPath / std::filesystem::u8path(Str("Untitled"))).replace_extension(".json"); - - try { - std::filesystem::rename(sourceFile, destinationFile); - } catch (const std::filesystem::filesystem_error &error) { - blog(LOG_ERROR, "Failed to rename basic scene collection file:\n%s", error.what()); - return; - } -} - -void OBSApp::AppInit() -{ - ProfileScope("OBSApp::AppInit"); - - if (!MakeUserDirs()) - throw "Failed to create required user directories"; - if (!InitGlobalConfig()) - throw "Failed to initialize global config"; - if (!InitLocale()) - throw "Failed to load locale"; - if (!InitTheme()) - throw "Failed to load theme"; - - config_set_default_string(userConfig, "Basic", "Profile", Str("Untitled")); - config_set_default_string(userConfig, "Basic", "ProfileDir", Str("Untitled")); - config_set_default_string(userConfig, "Basic", "SceneCollection", Str("Untitled")); - config_set_default_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled")); - config_set_default_bool(userConfig, "Basic", "ConfigOnNewProfile", true); - - if (!config_has_user_value(userConfig, "Basic", "Profile")) { - config_set_string(userConfig, "Basic", "Profile", Str("Untitled")); - config_set_string(userConfig, "Basic", "ProfileDir", Str("Untitled")); - } - - if (!config_has_user_value(userConfig, "Basic", "SceneCollection")) { - config_set_string(userConfig, "Basic", "SceneCollection", Str("Untitled")); - config_set_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled")); - } - -#ifdef _WIN32 - bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking"); - if (disableAudioDucking) - DisableAudioDucking(true); -#endif - -#ifdef __APPLE__ - if (config_get_bool(appConfig, "Video", "DisableOSXVSync")) - EnableOSXVSync(false); -#endif - - UpdateHotkeyFocusSetting(false); - - move_basic_to_profiles(); - move_basic_to_scene_collections(); - - if (!MakeUserProfileDirs()) - throw "Failed to create profile directories"; -} - -const char *OBSApp::GetRenderModule() const -{ - const char *renderer = config_get_string(appConfig, "Video", "Renderer"); - - return (astrcmpi(renderer, "Direct3D 11") == 0) ? DL_D3D11 : DL_OPENGL; -} - -static bool StartupOBS(const char *locale, profiler_name_store_t *store) -{ - char path[512]; - - if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0) - return false; - - return obs_startup(locale, path, store); -} - -inline void OBSApp::ResetHotkeyState(bool inFocus) -{ - obs_hotkey_enable_background_press((inFocus && enableHotkeysInFocus) || (!inFocus && enableHotkeysOutOfFocus)); -} - -void OBSApp::UpdateHotkeyFocusSetting(bool resetState) -{ - enableHotkeysInFocus = true; - enableHotkeysOutOfFocus = true; - - const char *hotkeyFocusType = config_get_string(userConfig, "General", "HotkeyFocusType"); - - if (astrcmpi(hotkeyFocusType, "DisableHotkeysInFocus") == 0) { - enableHotkeysInFocus = false; - } else if (astrcmpi(hotkeyFocusType, "DisableHotkeysOutOfFocus") == 0) { - enableHotkeysOutOfFocus = false; - } - - if (resetState) - ResetHotkeyState(applicationState() == Qt::ApplicationActive); -} - -void OBSApp::DisableHotkeys() -{ - enableHotkeysInFocus = false; - enableHotkeysOutOfFocus = false; - ResetHotkeyState(applicationState() == Qt::ApplicationActive); -} - -Q_DECLARE_METATYPE(VoidFunc) - -void OBSApp::Exec(VoidFunc func) -{ - func(); -} - -static void ui_task_handler(obs_task_t task, void *param, bool wait) -{ - auto doTask = [=]() { - /* to get clang-format to behave */ - task(param); - }; - QMetaObject::invokeMethod(App(), "Exec", wait ? WaitConnection() : Qt::AutoConnection, Q_ARG(VoidFunc, doTask)); -} - -bool OBSApp::OBSInit() -{ - ProfileScope("OBSApp::OBSInit"); - - qRegisterMetaType("VoidFunc"); - -#if !defined(_WIN32) && !defined(__APPLE__) - if (QApplication::platformName() == "xcb") { - obs_set_nix_platform(OBS_NIX_PLATFORM_X11_EGL); - blog(LOG_INFO, "Using EGL/X11"); - } - -#ifdef ENABLE_WAYLAND - if (QApplication::platformName().contains("wayland")) { - obs_set_nix_platform(OBS_NIX_PLATFORM_WAYLAND); - setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); - blog(LOG_INFO, "Platform: Wayland"); - } -#endif - - QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - obs_set_nix_platform_display(native->nativeResourceForIntegration("display")); -#endif - -#ifdef __APPLE__ - setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); -#endif - - if (!StartupOBS(locale.c_str(), GetProfilerNameStore())) - return false; - - libobs_initialized = true; - - obs_set_ui_task_handler(ui_task_handler); - -#if defined(_WIN32) || defined(__APPLE__) - bool browserHWAccel = config_get_bool(appConfig, "General", "BrowserHWAccel"); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_bool(settings, "BrowserHWAccel", browserHWAccel); - obs_apply_private_data(settings); - - blog(LOG_INFO, "Current Date/Time: %s", CurrentDateTimeString().c_str()); - - blog(LOG_INFO, "Browser Hardware Acceleration: %s", browserHWAccel ? "true" : "false"); -#endif -#ifdef _WIN32 - bool hideFromCapture = config_get_bool(userConfig, "BasicWindow", "HideOBSWindowsFromCapture"); - blog(LOG_INFO, "Hide OBS windows from screen capture: %s", hideFromCapture ? "true" : "false"); -#endif - - blog(LOG_INFO, "Qt Version: %s (runtime), %s (compiled)", qVersion(), QT_VERSION_STR); - blog(LOG_INFO, "Portable mode: %s", portable_mode ? "true" : "false"); - - if (safe_mode) { - blog(LOG_WARNING, "Safe Mode enabled."); - } else if (disable_3p_plugins) { - blog(LOG_WARNING, "Third-party plugins disabled."); - } - - setQuitOnLastWindowClosed(false); - - mainWindow = new OBSBasic(); - - mainWindow->setAttribute(Qt::WA_DeleteOnClose, true); - connect(mainWindow, &OBSBasic::destroyed, this, &OBSApp::quit); - - mainWindow->OBSInit(); - - connect(this, &QGuiApplication::applicationStateChanged, - [this](Qt::ApplicationState state) { ResetHotkeyState(state == Qt::ApplicationActive); }); - ResetHotkeyState(applicationState() == Qt::ApplicationActive); - return true; -} - -string OBSApp::GetVersionString(bool platform) const -{ - stringstream ver; - -#ifdef HAVE_OBSCONFIG_H - ver << obs_get_version_string(); -#else - ver << LIBOBS_API_MAJOR_VER << "." << LIBOBS_API_MINOR_VER << "." << LIBOBS_API_PATCH_VER; - -#endif - - if (platform) { - ver << " ("; -#ifdef _WIN32 - if (sizeof(void *) == 8) - ver << "64-bit, "; - else - ver << "32-bit, "; - - ver << "windows)"; -#elif __APPLE__ - ver << "mac)"; -#elif __OpenBSD__ - ver << "openbsd)"; -#elif __FreeBSD__ - ver << "freebsd)"; -#else /* assume linux for the time being */ - ver << "linux)"; -#endif - } - - return ver.str(); -} - -bool OBSApp::IsPortableMode() -{ - return portable_mode; -} - -bool OBSApp::IsUpdaterDisabled() -{ - return opt_disable_updater; -} - -bool OBSApp::IsMissingFilesCheckDisabled() -{ - return opt_disable_missing_files_check; -} - -#ifdef __APPLE__ -#define INPUT_AUDIO_SOURCE "coreaudio_input_capture" -#define OUTPUT_AUDIO_SOURCE "coreaudio_output_capture" -#elif _WIN32 -#define INPUT_AUDIO_SOURCE "wasapi_input_capture" -#define OUTPUT_AUDIO_SOURCE "wasapi_output_capture" -#else -#define INPUT_AUDIO_SOURCE "pulse_input_capture" -#define OUTPUT_AUDIO_SOURCE "pulse_output_capture" -#endif - -const char *OBSApp::InputAudioSource() const -{ - return INPUT_AUDIO_SOURCE; -} - -const char *OBSApp::OutputAudioSource() const -{ - return OUTPUT_AUDIO_SOURCE; -} - -const char *OBSApp::GetLastLog() const -{ - return lastLogFile.c_str(); -} - -const char *OBSApp::GetCurrentLog() const -{ - return currentLogFile.c_str(); -} - -const char *OBSApp::GetLastCrashLog() const -{ - return lastCrashLogFile.c_str(); -} - -bool OBSApp::TranslateString(const char *lookupVal, const char **out) const -{ - for (obs_frontend_translate_ui_cb cb : translatorHooks) { - if (cb(lookupVal, out)) - return true; - } - - return text_lookup_getstr(App()->GetTextLookup(), lookupVal, out); -} - -// Global handler to receive all QEvent::Show events so we can apply -// display affinity on any newly created windows and dialogs without -// caring where they are coming from (e.g. plugins). -bool OBSApp::notify(QObject *receiver, QEvent *e) -{ - QWidget *w; - QWindow *window; - int windowType; - - if (!receiver->isWidgetType()) - goto skip; - - if (e->type() != QEvent::Show) - goto skip; - - w = qobject_cast(receiver); - - if (!w->isWindow()) - goto skip; - - window = w->windowHandle(); - if (!window) - goto skip; - - windowType = window->flags() & Qt::WindowType::WindowType_Mask; - - if (windowType == Qt::WindowType::Dialog || windowType == Qt::WindowType::Window || - windowType == Qt::WindowType::Tool) { - OBSBasic *main = reinterpret_cast(GetMainWindow()); - if (main) - main->SetDisplayAffinity(window); - } - -skip: - return QApplication::notify(receiver, e); -} +#include "moc_OBSTranslator.cpp" QString OBSTranslator::translate(const char *, const char *sourceText, const char *, int) const { @@ -1447,1216 +33,3 @@ QString OBSTranslator::translate(const char *, const char *sourceText, const cha return QT_UTF8(out); } - -static bool get_token(lexer *lex, string &str, base_token_type type) -{ - base_token token; - if (!lexer_getbasetoken(lex, &token, IGNORE_WHITESPACE)) - return false; - if (token.type != type) - return false; - - str.assign(token.text.array, token.text.len); - return true; -} - -static bool expect_token(lexer *lex, const char *str, base_token_type type) -{ - base_token token; - if (!lexer_getbasetoken(lex, &token, IGNORE_WHITESPACE)) - return false; - if (token.type != type) - return false; - - return strref_cmp(&token.text, str) == 0; -} - -static uint64_t convert_log_name(bool has_prefix, const char *name) -{ - BaseLexer lex; - string year, month, day, hour, minute, second; - - lexer_start(lex, name); - - if (has_prefix) { - string temp; - if (!get_token(lex, temp, BASETOKEN_ALPHA)) - return 0; - } - - if (!get_token(lex, year, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, month, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, day, BASETOKEN_DIGIT)) - return 0; - if (!get_token(lex, hour, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, minute, BASETOKEN_DIGIT)) - return 0; - if (!expect_token(lex, "-", BASETOKEN_OTHER)) - return 0; - if (!get_token(lex, second, BASETOKEN_DIGIT)) - return 0; - - stringstream timestring; - timestring << year << month << day << hour << minute << second; - return std::stoull(timestring.str()); -} - -/* If upgrading from an older (non-XDG) build of OBS, move config files to XDG directory. */ -/* TODO: Remove after version 32.0. */ -#if defined(__FreeBSD__) -static void move_to_xdg(void) -{ - char old_path[512]; - char new_path[512]; - char *home = getenv("HOME"); - if (!home) - return; - - if (snprintf(old_path, sizeof(old_path), "%s/.obs-studio", home) <= 0) - return; - - /* make base xdg path if it doesn't already exist */ - if (GetAppConfigPath(new_path, sizeof(new_path), "") <= 0) - return; - if (os_mkdirs(new_path) == MKDIR_ERROR) - return; - - if (GetAppConfigPath(new_path, sizeof(new_path), "obs-studio") <= 0) - return; - - if (os_file_exists(old_path) && !os_file_exists(new_path)) { - rename(old_path, new_path); - } -} -#endif - -static void delete_oldest_file(bool has_prefix, const char *location) -{ - BPtr logDir(GetAppConfigPathPtr(location)); - string oldestLog; - uint64_t oldest_ts = (uint64_t)-1; - struct os_dirent *entry; - - unsigned int maxLogs = (unsigned int)config_get_uint(App()->GetAppConfig(), "General", "MaxLogs"); - - os_dir_t *dir = os_opendir(logDir); - if (dir) { - unsigned int count = 0; - - while ((entry = os_readdir(dir)) != NULL) { - if (entry->directory || *entry->d_name == '.') - continue; - - uint64_t ts = convert_log_name(has_prefix, entry->d_name); - - if (ts) { - if (ts < oldest_ts) { - oldestLog = entry->d_name; - oldest_ts = ts; - } - - count++; - } - } - - os_closedir(dir); - - if (count > maxLogs) { - stringstream delPath; - - delPath << logDir << "/" << oldestLog; - os_unlink(delPath.str().c_str()); - } - } -} - -static void get_last_log(bool has_prefix, const char *subdir_to_use, std::string &last) -{ - BPtr logDir(GetAppConfigPathPtr(subdir_to_use)); - struct os_dirent *entry; - os_dir_t *dir = os_opendir(logDir); - uint64_t highest_ts = 0; - - if (dir) { - while ((entry = os_readdir(dir)) != NULL) { - if (entry->directory || *entry->d_name == '.') - continue; - - uint64_t ts = convert_log_name(has_prefix, entry->d_name); - - if (ts > highest_ts) { - last = entry->d_name; - highest_ts = ts; - } - } - - os_closedir(dir); - } -} - -string GenerateTimeDateFilename(const char *extension, bool noSpace) -{ - time_t now = time(0); - char file[256] = {}; - struct tm *cur_time; - - cur_time = localtime(&now); - snprintf(file, sizeof(file), "%d-%02d-%02d%c%02d-%02d-%02d.%s", cur_time->tm_year + 1900, cur_time->tm_mon + 1, - cur_time->tm_mday, noSpace ? '_' : ' ', cur_time->tm_hour, cur_time->tm_min, cur_time->tm_sec, - extension); - - return string(file); -} - -string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format) -{ - BPtr filename = os_generate_formatted_filename(extension, !noSpace, format); - return string(filename); -} - -static void FindBestFilename(string &strPath, bool noSpace) -{ - int num = 2; - - if (!os_file_exists(strPath.c_str())) - return; - - const char *ext = strrchr(strPath.c_str(), '.'); - if (!ext) - return; - - int extStart = int(ext - strPath.c_str()); - for (;;) { - string testPath = strPath; - string numStr; - - numStr = noSpace ? "_" : " ("; - numStr += to_string(num++); - if (!noSpace) - numStr += ")"; - - testPath.insert(extStart, numStr); - - if (!os_file_exists(testPath.c_str())) { - strPath = testPath; - break; - } - } -} - -static void ensure_directory_exists(string &path) -{ - replace(path.begin(), path.end(), '\\', '/'); - - size_t last = path.rfind('/'); - if (last == string::npos) - return; - - string directory = path.substr(0, last); - os_mkdirs(directory.c_str()); -} - -static void remove_reserved_file_characters(string &s) -{ - replace(s.begin(), s.end(), '\\', '/'); - replace(s.begin(), s.end(), '*', '_'); - replace(s.begin(), s.end(), '?', '_'); - replace(s.begin(), s.end(), '"', '_'); - replace(s.begin(), s.end(), '|', '_'); - replace(s.begin(), s.end(), ':', '_'); - replace(s.begin(), s.end(), '>', '_'); - replace(s.begin(), s.end(), '<', '_'); -} - -string GetFormatString(const char *format, const char *prefix, const char *suffix) -{ - string f; - - f = format; - - if (prefix && *prefix) { - string str_prefix = prefix; - - if (str_prefix.back() != ' ') - str_prefix += " "; - - size_t insert_pos = 0; - size_t tmp; - - tmp = f.find_last_of('/'); - if (tmp != string::npos && tmp > insert_pos) - insert_pos = tmp + 1; - - tmp = f.find_last_of('\\'); - if (tmp != string::npos && tmp > insert_pos) - insert_pos = tmp + 1; - - f.insert(insert_pos, str_prefix); - } - - if (suffix && *suffix) { - if (*suffix != ' ') - f += " "; - f += suffix; - } - - remove_reserved_file_characters(f); - - return f; -} - -string GetFormatExt(const char *container) -{ - string ext = container; - if (ext == "fragmented_mp4") - ext = "mp4"; - if (ext == "hybrid_mp4") - ext = "mp4"; - else if (ext == "fragmented_mov") - ext = "mov"; - else if (ext == "hls") - ext = "m3u8"; - else if (ext == "mpegts") - ext = "ts"; - - return ext; -} - -string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, const char *format) -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr; - - if (!dir) { - if (main->isVisible()) - OBSMessageBox::warning(main, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); - else - main->SysTrayNotify(QTStr("Output.BadPath.Text"), QSystemTrayIcon::Warning); - return ""; - } - - os_closedir(dir); - - string strPath; - strPath += path; - - char lastChar = strPath.back(); - if (lastChar != '/' && lastChar != '\\') - strPath += "/"; - - string ext = GetFormatExt(container); - strPath += GenerateSpecifiedFilename(ext.c_str(), noSpace, format); - ensure_directory_exists(strPath); - if (!overwrite) - FindBestFilename(strPath, noSpace); - - return strPath; -} - -vector> GetLocaleNames() -{ - string path; - if (!GetDataFilePath("locale.ini", path)) - throw "Could not find locale.ini path"; - - ConfigFile ini; - if (ini.Open(path.c_str(), CONFIG_OPEN_EXISTING) != 0) - throw "Could not open locale.ini"; - - size_t sections = config_num_sections(ini); - - vector> names; - names.reserve(sections); - for (size_t i = 0; i < sections; i++) { - const char *tag = config_get_section(ini, i); - const char *name = config_get_string(ini, tag, "Name"); - names.emplace_back(tag, name); - } - - return names; -} - -static void create_log_file(fstream &logFile) -{ - stringstream dst; - - get_last_log(false, "obs-studio/logs", lastLogFile); -#ifdef _WIN32 - get_last_log(true, "obs-studio/crashes", lastCrashLogFile); -#endif - - currentLogFile = GenerateTimeDateFilename("txt"); - dst << "obs-studio/logs/" << currentLogFile.c_str(); - - BPtr path(GetAppConfigPathPtr(dst.str().c_str())); - -#ifdef _WIN32 - BPtr wpath; - os_utf8_to_wcs_ptr(path, 0, &wpath); - logFile.open(wpath, ios_base::in | ios_base::out | ios_base::trunc); -#else - logFile.open(path, ios_base::in | ios_base::out | ios_base::trunc); -#endif - - if (logFile.is_open()) { - delete_oldest_file(false, "obs-studio/logs"); - base_set_log_handler(do_log, &logFile); - } else { - blog(LOG_ERROR, "Failed to open log file"); - } -} - -static auto ProfilerNameStoreRelease = [](profiler_name_store_t *store) { - profiler_name_store_free(store); -}; - -using ProfilerNameStore = std::unique_ptr; - -ProfilerNameStore CreateNameStore() -{ - return ProfilerNameStore{profiler_name_store_create(), ProfilerNameStoreRelease}; -} - -static auto SnapshotRelease = [](profiler_snapshot_t *snap) { - profile_snapshot_free(snap); -}; - -using ProfilerSnapshot = std::unique_ptr; - -ProfilerSnapshot GetSnapshot() -{ - return ProfilerSnapshot{profile_snapshot_create(), SnapshotRelease}; -} - -static void SaveProfilerData(const ProfilerSnapshot &snap) -{ - if (currentLogFile.empty()) - return; - - auto pos = currentLogFile.rfind('.'); - if (pos == currentLogFile.npos) - return; - -#define LITERAL_SIZE(x) x, (sizeof(x) - 1) - ostringstream dst; - dst.write(LITERAL_SIZE("obs-studio/profiler_data/")); - dst.write(currentLogFile.c_str(), pos); - dst.write(LITERAL_SIZE(".csv.gz")); -#undef LITERAL_SIZE - - BPtr path = GetAppConfigPathPtr(dst.str().c_str()); - if (!profiler_snapshot_dump_csv_gz(snap.get(), path)) - blog(LOG_WARNING, "Could not save profiler data to '%s'", static_cast(path)); -} - -static auto ProfilerFree = [](void *) { - profiler_stop(); - - auto snap = GetSnapshot(); - - profiler_print(snap.get()); - profiler_print_time_between_calls(snap.get()); - - SaveProfilerData(snap); - - profiler_free(); -}; - -QAccessibleInterface *accessibleFactory(const QString &classname, QObject *object) -{ - if (classname == QLatin1String("VolumeSlider") && object && object->isWidgetType()) - return new VolumeAccessibleInterface(static_cast(object)); - - return nullptr; -} - -static const char *run_program_init = "run_program_init"; -static int run_program(fstream &logFile, int argc, char *argv[]) -{ - int ret = -1; - - auto profilerNameStore = CreateNameStore(); - - std::unique_ptr prof_release(static_cast(&ProfilerFree), ProfilerFree); - - profiler_start(); - profile_register_root(run_program_init, 0); - - ScopeProfiler prof{run_program_init}; - -#ifdef _WIN32 - QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif - - QCoreApplication::addLibraryPath("."); - -#if __APPLE__ - InstallNSApplicationSubclass(); - InstallNSThreadLocks(); - - if (!isInBundle()) { - blog(LOG_ERROR, - "OBS cannot be run as a standalone binary on macOS. Run the Application bundle instead."); - return ret; - } -#endif - -#if !defined(_WIN32) && !defined(__APPLE__) - /* NOTE: Users blindly set this, but this theme is incompatble with Qt6 and - * crashes loading saved geometry. Just turn off this theme and let users complain OBS - * looks ugly instead of crashing. */ - const char *platform_theme = getenv("QT_QPA_PLATFORMTHEME"); - if (platform_theme && strcmp(platform_theme, "qt5ct") == 0) - unsetenv("QT_QPA_PLATFORMTHEME"); -#endif - - /* NOTE: This disables an optimisation in Qt that attempts to determine if - * any "siblings" intersect with a widget when determining the approximate - * visible/unobscured area. However, by Qt's own admission this is slow - * and in the case of OBS it significantly slows down lists with many - * elements (e.g. Hotkeys) and it is actually faster to disable it. */ - qputenv("QT_NO_SUBTRACTOPAQUESIBLINGS", "1"); - - OBSApp program(argc, argv, profilerNameStore.get()); - try { - QAccessible::installFactory(accessibleFactory); - QFontDatabase::addApplicationFont(":/fonts/OpenSans-Regular.ttf"); - QFontDatabase::addApplicationFont(":/fonts/OpenSans-Bold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/OpenSans-Italic.ttf"); - - bool created_log = false; - - program.AppInit(); - delete_oldest_file(false, "obs-studio/profiler_data"); - - OBSTranslator translator; - program.installTranslator(&translator); - - /* --------------------------------------- */ - /* check and warn if already running */ - - bool cancel_launch = false; - bool already_running = false; - -#ifdef _WIN32 - RunOnceMutex rom = -#endif - CheckIfAlreadyRunning(already_running); - - if (!already_running) { - goto run; - } - - if (!multi) { - QMessageBox mb(QMessageBox::Question, QTStr("AlreadyRunning.Title"), - QTStr("AlreadyRunning.Text")); - mb.addButton(QTStr("AlreadyRunning.LaunchAnyway"), QMessageBox::YesRole); - QPushButton *cancelButton = mb.addButton(QTStr("Cancel"), QMessageBox::NoRole); - mb.setDefaultButton(cancelButton); - - mb.exec(); - cancel_launch = mb.clickedButton() == cancelButton; - } - - if (cancel_launch) - return 0; - - if (!created_log) { - create_log_file(logFile); - created_log = true; - } - - if (multi) { - blog(LOG_INFO, "User enabled --multi flag and is now " - "running multiple instances of OBS."); - } else { - blog(LOG_WARNING, "================================"); - blog(LOG_WARNING, "Warning: OBS is already running!"); - blog(LOG_WARNING, "================================"); - blog(LOG_WARNING, "User is now running multiple " - "instances of OBS!"); - /* Clear unclean_shutdown flag as multiple instances - * running from the same config will lead to a - * false-positive detection.*/ - unclean_shutdown = false; - } - - /* --------------------------------------- */ - run: - -#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__FreeBSD__) - // Mounted by termina during chromeOS linux container startup - // https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/master/project-termina/chromeos-base/termina-lxd-scripts/files/lxd_setup.sh - os_dir_t *crosDir = os_opendir("/opt/google/cros-containers"); - if (crosDir) { - QMessageBox::StandardButtons buttons(QMessageBox::Ok); - QMessageBox mb(QMessageBox::Critical, QTStr("ChromeOS.Title"), QTStr("ChromeOS.Text"), buttons, - nullptr); - - mb.exec(); - return 0; - } -#endif - - if (!created_log) - create_log_file(logFile); - - if (unclean_shutdown) { - blog(LOG_WARNING, "[Safe Mode] Unclean shutdown detected!"); - } - - if (unclean_shutdown && !safe_mode) { - QMessageBox mb(QMessageBox::Warning, QTStr("AutoSafeMode.Title"), QTStr("AutoSafeMode.Text")); - QPushButton *launchSafeButton = - mb.addButton(QTStr("AutoSafeMode.LaunchSafe"), QMessageBox::AcceptRole); - QPushButton *launchNormalButton = - mb.addButton(QTStr("AutoSafeMode.LaunchNormal"), QMessageBox::RejectRole); - mb.setDefaultButton(launchNormalButton); - mb.exec(); - - safe_mode = mb.clickedButton() == launchSafeButton; - if (safe_mode) { - blog(LOG_INFO, "[Safe Mode] User has launched in Safe Mode."); - } else { - blog(LOG_WARNING, "[Safe Mode] User elected to launch normally."); - } - } - - qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &, const QString &message) { - switch (type) { -#ifdef _DEBUG - case QtDebugMsg: - blog(LOG_DEBUG, "%s", QT_TO_UTF8(message)); - break; - case QtInfoMsg: - blog(LOG_INFO, "%s", QT_TO_UTF8(message)); - break; -#else - case QtDebugMsg: - case QtInfoMsg: - break; -#endif - case QtWarningMsg: - blog(LOG_WARNING, "%s", QT_TO_UTF8(message)); - break; - case QtCriticalMsg: - case QtFatalMsg: - blog(LOG_ERROR, "%s", QT_TO_UTF8(message)); - break; - } - }); - -#ifdef __APPLE__ - MacPermissionStatus audio_permission = CheckPermission(kAudioDeviceAccess); - MacPermissionStatus video_permission = CheckPermission(kVideoDeviceAccess); - MacPermissionStatus accessibility_permission = CheckPermission(kAccessibility); - MacPermissionStatus screen_permission = CheckPermission(kScreenCapture); - - int permissionsDialogLastShown = - config_get_int(App()->GetAppConfig(), "General", "MacOSPermissionsDialogLastShown"); - if (permissionsDialogLastShown < MACOS_PERMISSIONS_DIALOG_VERSION) { - OBSPermissions check(nullptr, screen_permission, video_permission, audio_permission, - accessibility_permission); - check.exec(); - } -#endif - -#ifdef _WIN32 - if (IsRunningOnWine()) { - QMessageBox mb(QMessageBox::Question, QTStr("Wine.Title"), QTStr("Wine.Text")); - mb.setTextFormat(Qt::RichText); - mb.addButton(QTStr("AlreadyRunning.LaunchAnyway"), QMessageBox::AcceptRole); - QPushButton *closeButton = mb.addButton(QMessageBox::Close); - mb.setDefaultButton(closeButton); - - mb.exec(); - if (mb.clickedButton() == closeButton) - return 0; - } -#endif - - if (argc > 1) { - stringstream stor; - stor << argv[1]; - for (int i = 2; i < argc; ++i) { - stor << " " << argv[i]; - } - blog(LOG_INFO, "Command Line Arguments: %s", stor.str().c_str()); - } - - if (!program.OBSInit()) - return 0; - - prof.Stop(); - - ret = program.exec(); - - } catch (const char *error) { - blog(LOG_ERROR, "%s", error); - OBSErrorBox(nullptr, "%s", error); - } - - if (restart || restart_safe) { - arguments = qApp->arguments(); - - if (restart_safe) { - arguments.append("--safe-mode"); - } else { - arguments.removeAll("--safe-mode"); - } - } - - return ret; -} - -#define MAX_CRASH_REPORT_SIZE (150 * 1024) - -#ifdef _WIN32 - -#define CRASH_MESSAGE \ - "Woops, OBS has crashed!\n\nWould you like to copy the crash log " \ - "to the clipboard? The crash log will still be saved to:\n\n%s" - -static void main_crash_handler(const char *format, va_list args, void * /* param */) -{ - char *text = new char[MAX_CRASH_REPORT_SIZE]; - - vsnprintf(text, MAX_CRASH_REPORT_SIZE, format, args); - text[MAX_CRASH_REPORT_SIZE - 1] = 0; - - string crashFilePath = "obs-studio/crashes"; - - delete_oldest_file(true, crashFilePath.c_str()); - - string name = crashFilePath + "/"; - name += "Crash " + GenerateTimeDateFilename("txt"); - - BPtr path(GetAppConfigPathPtr(name.c_str())); - - fstream file; - -#ifdef _WIN32 - BPtr wpath; - os_utf8_to_wcs_ptr(path, 0, &wpath); - file.open(wpath, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary); -#else - file.open(path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary); -#endif - file << text; - file.close(); - - string pathString(path.Get()); - -#ifdef _WIN32 - std::replace(pathString.begin(), pathString.end(), '/', '\\'); -#endif - - string absolutePath = canonical(filesystem::path(pathString)).u8string(); - - size_t size = snprintf(nullptr, 0, CRASH_MESSAGE, absolutePath.c_str()); - - unique_ptr message_buffer(new char[size + 1]); - - snprintf(message_buffer.get(), size + 1, CRASH_MESSAGE, absolutePath.c_str()); - - string finalMessage = string(message_buffer.get(), message_buffer.get() + size); - - int ret = MessageBoxA(NULL, finalMessage.c_str(), "OBS has crashed!", MB_YESNO | MB_ICONERROR | MB_TASKMODAL); - - if (ret == IDYES) { - size_t len = strlen(text); - - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, len); - memcpy(GlobalLock(mem), text, len); - GlobalUnlock(mem); - - OpenClipboard(0); - EmptyClipboard(); - SetClipboardData(CF_TEXT, mem); - CloseClipboard(); - } - - exit(-1); -} - -static void load_debug_privilege(void) -{ - const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY; - TOKEN_PRIVILEGES tp; - HANDLE token; - LUID val; - - if (!OpenProcessToken(GetCurrentProcess(), flags, &token)) { - return; - } - - if (!!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &val)) { - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = val; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL); - } - - if (!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) { - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = val; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { - blog(LOG_INFO, "Could not set privilege to " - "increase GPU priority"); - } - } - - CloseHandle(token); -} -#endif - -#ifdef __APPLE__ -#define BASE_PATH ".." -#else -#define BASE_PATH "../.." -#endif - -#define CONFIG_PATH BASE_PATH "/config" - -#if defined(ENABLE_PORTABLE_CONFIG) || defined(_WIN32) -#define ALLOW_PORTABLE_MODE 1 -#else -#define ALLOW_PORTABLE_MODE 0 -#endif - -int GetAppConfigPath(char *path, size_t size, const char *name) -{ -#if ALLOW_PORTABLE_MODE - if (portable_mode) { - if (name && *name) { - return snprintf(path, size, CONFIG_PATH "/%s", name); - } else { - return snprintf(path, size, CONFIG_PATH); - } - } else { - return os_get_config_path(path, size, name); - } -#else - return os_get_config_path(path, size, name); -#endif -} - -char *GetAppConfigPathPtr(const char *name) -{ -#if ALLOW_PORTABLE_MODE - if (portable_mode) { - char path[512]; - - if (snprintf(path, sizeof(path), CONFIG_PATH "/%s", name) > 0) { - return bstrdup(path); - } else { - return NULL; - } - } else { - return os_get_config_path_ptr(name); - } -#else - return os_get_config_path_ptr(name); -#endif -} - -int GetProgramDataPath(char *path, size_t size, const char *name) -{ - return os_get_program_data_path(path, size, name); -} - -char *GetProgramDataPathPtr(const char *name) -{ - return os_get_program_data_path_ptr(name); -} - -bool GetFileSafeName(const char *name, std::string &file) -{ - size_t base_len = strlen(name); - size_t len = os_utf8_to_wcs(name, base_len, nullptr, 0); - std::wstring wfile; - - if (!len) - return false; - - wfile.resize(len); - os_utf8_to_wcs(name, base_len, &wfile[0], len + 1); - - for (size_t i = wfile.size(); i > 0; i--) { - size_t im1 = i - 1; - - if (iswspace(wfile[im1])) { - wfile[im1] = '_'; - } else if (wfile[im1] != '_' && !iswalnum(wfile[im1])) { - wfile.erase(im1, 1); - } - } - - if (wfile.size() == 0) - wfile = L"characters_only"; - - len = os_wcs_to_utf8(wfile.c_str(), wfile.size(), nullptr, 0); - if (!len) - return false; - - file.resize(len); - os_wcs_to_utf8(wfile.c_str(), wfile.size(), &file[0], len + 1); - return true; -} - -bool GetClosestUnusedFileName(std::string &path, const char *extension) -{ - size_t len = path.size(); - if (extension) { - path += "."; - path += extension; - } - - if (!os_file_exists(path.c_str())) - return true; - - int index = 1; - - do { - path.resize(len); - path += std::to_string(++index); - if (extension) { - path += "."; - path += extension; - } - } while (os_file_exists(path.c_str())); - - return true; -} - -bool WindowPositionValid(QRect rect) -{ - for (QScreen *screen : QGuiApplication::screens()) { - if (screen->availableGeometry().intersects(rect)) - return true; - } - return false; -} - -static inline bool arg_is(const char *arg, const char *long_form, const char *short_form) -{ - return (long_form && strcmp(arg, long_form) == 0) || (short_form && strcmp(arg, short_form) == 0); -} - -static void check_safe_mode_sentinel(void) -{ -#ifndef NDEBUG - /* Safe Mode detection is disabled in Debug builds to keep developers - * somewhat sane. */ - return; -#else - if (disable_shutdown_check) - return; - - BPtr sentinelPath = GetAppConfigPathPtr("obs-studio/safe_mode"); - if (os_file_exists(sentinelPath)) { - unclean_shutdown = true; - return; - } - - os_quick_write_utf8_file(sentinelPath, nullptr, 0, false); -#endif -} - -static void delete_safe_mode_sentinel(void) -{ -#ifndef NDEBUG - return; -#else - BPtr sentinelPath = GetAppConfigPathPtr("obs-studio/safe_mode"); - os_unlink(sentinelPath); -#endif -} - -#ifndef _WIN32 -void OBSApp::SigIntSignalHandler(int s) -{ - /* Handles SIGINT and writes to a socket. Qt will read - * from the socket in the main thread event loop and trigger - * a call to the ProcessSigInt slot, where we can safely run - * shutdown code without signal safety issues. */ - UNUSED_PARAMETER(s); - - char a = 1; - send(sigintFd[0], &a, sizeof(a), 0); -} -#endif - -void OBSApp::ProcessSigInt(void) -{ - /* This looks weird, but we can't ifdef a Qt slot function so - * the SIGINT handler simply does nothing on Windows. */ -#ifndef _WIN32 - char tmp; - recv(sigintFd[1], &tmp, sizeof(tmp), 0); - - OBSBasic *main = reinterpret_cast(GetMainWindow()); - if (main) - main->close(); -#endif -} - -#ifdef _WIN32 -void OBSApp::commitData(QSessionManager &manager) -{ - if (auto main = App()->GetMainWindow()) { - QMetaObject::invokeMethod(main, "close", Qt::QueuedConnection); - manager.cancel(); - } -} -#endif - -#ifdef _WIN32 -static constexpr char vcRunErrorTitle[] = "Outdated Visual C++ Runtime"; -static constexpr char vcRunErrorMsg[] = "OBS Studio requires a newer version of the Microsoft Visual C++ " - "Redistributables.\n\nYou will now be directed to the download page."; -static constexpr char vcRunInstallerUrl[] = "https://obsproject.com/visual-studio-2022-runtimes"; - -static bool vc_runtime_outdated() -{ - win_version_info ver; - if (!get_dll_ver(L"msvcp140.dll", &ver)) - return true; - /* Major is always 14 (hence 140.dll), so we only care about minor. */ - if (ver.minor >= 40) - return false; - - int choice = MessageBoxA(NULL, vcRunErrorMsg, vcRunErrorTitle, MB_OKCANCEL | MB_ICONERROR | MB_TASKMODAL); - if (choice == IDOK) { - /* Open the URL in the default browser. */ - ShellExecuteA(NULL, "open", vcRunInstallerUrl, NULL, NULL, SW_SHOWNORMAL); - } - - return true; -} -#endif - -int main(int argc, char *argv[]) -{ -#ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); - - struct sigaction sig_handler; - - sig_handler.sa_handler = OBSApp::SigIntSignalHandler; - sigemptyset(&sig_handler.sa_mask); - sig_handler.sa_flags = 0; - - sigaction(SIGINT, &sig_handler, NULL); - - /* Block SIGPIPE in all threads, this can happen if a thread calls write on - a closed pipe. */ - sigset_t sigpipe_mask; - sigemptyset(&sigpipe_mask); - sigaddset(&sigpipe_mask, SIGPIPE); - sigset_t saved_mask; - if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &saved_mask) == -1) { - perror("pthread_sigmask"); - exit(1); - } -#endif - -#ifdef _WIN32 - // Abort as early as possible if MSVC runtime is outdated - if (vc_runtime_outdated()) - return 1; - // Try to keep this as early as possible - install_dll_blocklist_hook(); - - obs_init_win32_crash_handler(); - SetErrorMode(SEM_FAILCRITICALERRORS); - load_debug_privilege(); - base_set_crash_handler(main_crash_handler, nullptr); - - const HMODULE hRtwq = LoadLibrary(L"RTWorkQ.dll"); - if (hRtwq) { - typedef HRESULT(STDAPICALLTYPE * PFN_RtwqStartup)(); - PFN_RtwqStartup func = (PFN_RtwqStartup)GetProcAddress(hRtwq, "RtwqStartup"); - func(); - } -#endif - - base_get_log_handler(&def_log_handler, nullptr); - -#if defined(__FreeBSD__) - move_to_xdg(); -#endif - - obs_set_cmdline_args(argc, argv); - - for (int i = 1; i < argc; i++) { - if (arg_is(argv[i], "--multi", "-m")) { - multi = true; - disable_shutdown_check = true; - -#if ALLOW_PORTABLE_MODE - } else if (arg_is(argv[i], "--portable", "-p")) { - portable_mode = true; - -#endif - } else if (arg_is(argv[i], "--verbose", nullptr)) { - log_verbose = true; - - } else if (arg_is(argv[i], "--safe-mode", nullptr)) { - safe_mode = true; - - } else if (arg_is(argv[i], "--only-bundled-plugins", nullptr)) { - disable_3p_plugins = true; - - } else if (arg_is(argv[i], "--disable-shutdown-check", nullptr)) { - /* This exists mostly to bypass the dialog during development. */ - disable_shutdown_check = true; - - } else if (arg_is(argv[i], "--always-on-top", nullptr)) { - opt_always_on_top = true; - - } else if (arg_is(argv[i], "--unfiltered_log", nullptr)) { - unfiltered_log = true; - - } else if (arg_is(argv[i], "--startstreaming", nullptr)) { - opt_start_streaming = true; - - } else if (arg_is(argv[i], "--startrecording", nullptr)) { - opt_start_recording = true; - - } else if (arg_is(argv[i], "--startreplaybuffer", nullptr)) { - opt_start_replaybuffer = true; - - } else if (arg_is(argv[i], "--startvirtualcam", nullptr)) { - opt_start_virtualcam = true; - - } else if (arg_is(argv[i], "--collection", nullptr)) { - if (++i < argc) - opt_starting_collection = argv[i]; - - } else if (arg_is(argv[i], "--profile", nullptr)) { - if (++i < argc) - opt_starting_profile = argv[i]; - - } else if (arg_is(argv[i], "--scene", nullptr)) { - if (++i < argc) - opt_starting_scene = argv[i]; - - } else if (arg_is(argv[i], "--minimize-to-tray", nullptr)) { - opt_minimize_tray = true; - - } else if (arg_is(argv[i], "--studio-mode", nullptr)) { - opt_studio_mode = true; - - } else if (arg_is(argv[i], "--allow-opengl", nullptr)) { - opt_allow_opengl = true; - - } else if (arg_is(argv[i], "--disable-updater", nullptr)) { - opt_disable_updater = true; - - } else if (arg_is(argv[i], "--disable-missing-files-check", nullptr)) { - opt_disable_missing_files_check = true; - - } else if (arg_is(argv[i], "--steam", nullptr)) { - steam = true; - - } else if (arg_is(argv[i], "--help", "-h")) { - std::string help = - "--help, -h: Get list of available commands.\n\n" - "--startstreaming: Automatically start streaming.\n" - "--startrecording: Automatically start recording.\n" - "--startreplaybuffer: Start replay buffer.\n" - "--startvirtualcam: Start virtual camera (if available).\n\n" - "--collection : Use specific scene collection." - "\n" - "--profile : Use specific profile.\n" - "--scene : Start with specific scene.\n\n" - "--studio-mode: Enable studio mode.\n" - "--minimize-to-tray: Minimize to system tray.\n" -#if ALLOW_PORTABLE_MODE - "--portable, -p: Use portable mode.\n" -#endif - "--multi, -m: Don't warn when launching multiple instances.\n\n" - "--safe-mode: Run in Safe Mode (disables third-party plugins, scripting, and WebSockets).\n" - "--only-bundled-plugins: Only load included (first-party) plugins\n" - "--disable-shutdown-check: Disable unclean shutdown detection.\n" - "--verbose: Make log more verbose.\n" - "--always-on-top: Start in 'always on top' mode.\n\n" - "--unfiltered_log: Make log unfiltered.\n\n" - "--disable-updater: Disable built-in updater (Windows/Mac only)\n\n" - "--disable-missing-files-check: Disable the missing files dialog which can appear on startup.\n\n"; - -#ifdef _WIN32 - MessageBoxA(NULL, help.c_str(), "Help", MB_OK | MB_ICONASTERISK); -#else - std::cout << help << "--version, -V: Get current version.\n"; -#endif - exit(0); - - } else if (arg_is(argv[i], "--version", "-V")) { - std::cout << "OBS Studio - " << App()->GetVersionString(false) << "\n"; - exit(0); - } - } - -#if ALLOW_PORTABLE_MODE - if (!portable_mode) { - portable_mode = os_file_exists(BASE_PATH "/portable_mode") || - os_file_exists(BASE_PATH "/obs_portable_mode") || - os_file_exists(BASE_PATH "/portable_mode.txt") || - os_file_exists(BASE_PATH "/obs_portable_mode.txt"); - } - - if (!opt_disable_updater) { - opt_disable_updater = os_file_exists(BASE_PATH "/disable_updater") || - os_file_exists(BASE_PATH "/disable_updater.txt"); - } - - if (!opt_disable_missing_files_check) { - opt_disable_missing_files_check = os_file_exists(BASE_PATH "/disable_missing_files_check") || - os_file_exists(BASE_PATH "/disable_missing_files_check.txt"); - } -#endif - - check_safe_mode_sentinel(); - - fstream logFile; - - curl_global_init(CURL_GLOBAL_ALL); - int ret = run_program(logFile, argc, argv); - -#ifdef _WIN32 - if (hRtwq) { - typedef HRESULT(STDAPICALLTYPE * PFN_RtwqShutdown)(); - PFN_RtwqShutdown func = (PFN_RtwqShutdown)GetProcAddress(hRtwq, "RtwqShutdown"); - func(); - FreeLibrary(hRtwq); - } - - log_blocked_dlls(); -#endif - - delete_safe_mode_sentinel(); - blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs()); - base_set_log_handler(nullptr, nullptr); - - if (restart || restart_safe) { - auto executable = arguments.takeFirst(); - QProcess::startDetached(executable, arguments); - } - - return ret; -} diff --git a/frontend/utility/OBSTranslator.hpp b/frontend/utility/OBSTranslator.hpp index dea5e530b..a962306f3 100644 --- a/frontend/utility/OBSTranslator.hpp +++ b/frontend/utility/OBSTranslator.hpp @@ -17,50 +17,8 @@ #pragma once -#include +#include #include -#include -#include - -#ifndef _WIN32 -#include -#else -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "window-main.hpp" -#include "obs-app-theming.hpp" - -std::string CurrentTimeString(); -std::string CurrentDateTimeString(); -std::string GenerateTimeDateFilename(const char *extension, bool noSpace = false); -std::string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format); -std::string GetFormatString(const char *format, const char *prefix, const char *suffix); -std::string GetFormatExt(const char *container); -std::string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite, - const char *format); -QObject *CreateShortcutFilter(); - -struct BaseLexer { - lexer lex; - -public: - inline BaseLexer() { lexer_init(&lex); } - inline ~BaseLexer() { lexer_free(&lex); } - operator lexer *() { return &lex; } -}; class OBSTranslator : public QTranslator { Q_OBJECT @@ -71,210 +29,3 @@ public: virtual QString translate(const char *context, const char *sourceText, const char *disambiguation, int n) const override; }; - -typedef std::function VoidFunc; - -struct UpdateBranch { - QString name; - QString display_name; - QString description; - bool is_enabled; - bool is_visible; -}; - -class OBSApp : public QApplication { - Q_OBJECT - -private: - std::string locale; - - ConfigFile appConfig; - ConfigFile userConfig; - TextLookup textLookup; - QPointer mainWindow; - profiler_name_store_t *profilerNameStore = nullptr; - std::vector updateBranches; - bool branches_loaded = false; - - bool libobs_initialized = false; - - os_inhibit_t *sleepInhibitor = nullptr; - int sleepInhibitRefs = 0; - - bool enableHotkeysInFocus = true; - bool enableHotkeysOutOfFocus = true; - - std::deque translatorHooks; - - bool UpdatePre22MultiviewLayout(const char *layout); - - bool InitGlobalConfig(); - bool InitGlobalConfigDefaults(); - bool InitGlobalLocationDefaults(); - - bool MigrateGlobalSettings(); - void MigrateLegacySettings(uint32_t lastVersion); - - bool InitUserConfig(std::filesystem::path &userConfigLocation, uint32_t lastVersion); - void InitUserConfigDefaults(); - - bool InitLocale(); - bool InitTheme(); - - inline void ResetHotkeyState(bool inFocus); - - QPalette defaultPalette; - OBSTheme *currentTheme = nullptr; - QHash themes; - QPointer themeWatcher; - - void FindThemes(); - - bool notify(QObject *receiver, QEvent *e) override; - -#ifndef _WIN32 - static int sigintFd[2]; - QSocketNotifier *snInt = nullptr; -#else -private slots: - void commitData(QSessionManager &manager); -#endif - -private slots: - void themeFileChanged(const QString &); - -public: - OBSApp(int &argc, char **argv, profiler_name_store_t *store); - ~OBSApp(); - - void AppInit(); - bool OBSInit(); - - void UpdateHotkeyFocusSetting(bool reset = true); - void DisableHotkeys(); - - inline bool HotkeysEnabledInFocus() const { return enableHotkeysInFocus; } - - inline QMainWindow *GetMainWindow() const { return mainWindow.data(); } - - inline config_t *GetAppConfig() const { return appConfig; } - inline config_t *GetUserConfig() const { return userConfig; } - std::filesystem::path userConfigLocation; - std::filesystem::path userScenesLocation; - std::filesystem::path userProfilesLocation; - - inline const char *GetLocale() const { return locale.c_str(); } - - OBSTheme *GetTheme() const { return currentTheme; } - QList GetThemes() const { return themes.values(); } - OBSTheme *GetTheme(const QString &name); - bool SetTheme(const QString &name); - bool IsThemeDark() const { return currentTheme ? currentTheme->isDark : false; } - - void SetBranchData(const std::string &data); - std::vector GetBranches(); - - inline lookup_t *GetTextLookup() const { return textLookup; } - - inline const char *GetString(const char *lookupVal) const { return textLookup.GetString(lookupVal); } - - bool TranslateString(const char *lookupVal, const char **out) const; - - profiler_name_store_t *GetProfilerNameStore() const { return profilerNameStore; } - - const char *GetLastLog() const; - const char *GetCurrentLog() const; - - const char *GetLastCrashLog() const; - - std::string GetVersionString(bool platform = true) const; - bool IsPortableMode(); - bool IsUpdaterDisabled(); - bool IsMissingFilesCheckDisabled(); - - const char *InputAudioSource() const; - const char *OutputAudioSource() const; - - const char *GetRenderModule() const; - - inline void IncrementSleepInhibition() - { - if (!sleepInhibitor) - return; - if (sleepInhibitRefs++ == 0) - os_inhibit_sleep_set_active(sleepInhibitor, true); - } - - inline void DecrementSleepInhibition() - { - if (!sleepInhibitor) - return; - if (sleepInhibitRefs == 0) - return; - if (--sleepInhibitRefs == 0) - os_inhibit_sleep_set_active(sleepInhibitor, false); - } - - inline void PushUITranslation(obs_frontend_translate_ui_cb cb) { translatorHooks.emplace_front(cb); } - - inline void PopUITranslation() { translatorHooks.pop_front(); } -#ifndef _WIN32 - static void SigIntSignalHandler(int); -#endif - -public slots: - void Exec(VoidFunc func); - void ProcessSigInt(); - -signals: - void StyleChanged(); -}; - -int GetAppConfigPath(char *path, size_t size, const char *name); -char *GetAppConfigPathPtr(const char *name); - -int GetProgramDataPath(char *path, size_t size, const char *name); -char *GetProgramDataPathPtr(const char *name); - -inline OBSApp *App() -{ - return static_cast(qApp); -} - -std::vector> GetLocaleNames(); -inline const char *Str(const char *lookup) -{ - return App()->GetString(lookup); -} -inline QString QTStr(const char *lookupVal) -{ - return QString::fromUtf8(Str(lookupVal)); -} - -bool GetFileSafeName(const char *name, std::string &file); -bool GetClosestUnusedFileName(std::string &path, const char *extension); -bool GetUnusedSceneCollectionFile(std::string &name, std::string &file); - -bool WindowPositionValid(QRect rect); - -extern bool portable_mode; -extern bool steam; -extern bool safe_mode; -extern bool disable_3p_plugins; - -extern bool opt_start_streaming; -extern bool opt_start_recording; -extern bool opt_start_replaybuffer; -extern bool opt_start_virtualcam; -extern bool opt_minimize_tray; -extern bool opt_studio_mode; -extern bool opt_allow_opengl; -extern bool opt_always_on_top; -extern std::string opt_starting_scene; -extern bool restart; -extern bool restart_safe; - -#ifdef _WIN32 -extern "C" void install_dll_blocklist_hook(void); -extern "C" void log_blocked_dlls(void); -#endif