Browse Source

frontend: Split main application implementation into single files

PatTheMav 11 months ago
parent
commit
2be464a21f

+ 38 - 1125
frontend/OBSApp.cpp

@@ -15,108 +15,59 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include <time.h>
-#include <stdio.h>
-#include <wchar.h>
-#include <chrono>
-#include <ratio>
-#include <string>
-#include <sstream>
-#include <mutex>
-#include <filesystem>
-#include <util/bmem.h>
-#include <util/dstr.hpp>
-#include <util/platform.h>
-#include <util/profiler.hpp>
-#include <util/cf-parser.h>
-#include <obs-config.h>
-#include <obs.hpp>
-#include <qt-wrappers.hpp>
-#include <slider-ignorewheel.hpp>
+#include "OBSApp.hpp"
 
-#include <QDir>
-#include <QFile>
-#include <QGuiApplication>
-#include <QScreen>
-#include <QProcess>
-#include <QAccessible>
-
-#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"
+#include <components/Multiview.hpp>
+#include <utility/OBSEventFilter.hpp>
+#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
+#include <utility/models/branches.hpp>
 #endif
-#include "window-basic-settings.hpp"
-#include "platform.hpp"
-
-#include <fstream>
-
-#include <curl/curl.h>
+#include <widgets/OBSBasic.hpp>
 
-#ifdef _WIN32
-#include <windows.h>
-#include <filesystem>
-#include <util/windows/win-version.h>
-#else
-#include <signal.h>
-#include <pthread.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <unistd.h>
+#if !defined(_WIN32) && !defined(__APPLE__)
+#include <obs-nix-platform.h>
 #endif
+#include <qt-wrappers.hpp>
 
 #if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
-#include "update/models/branches.hpp"
+#include <QFile>
 #endif
 
+#ifdef _WIN32
+#include <QSessionManager>
+#else
+#include <QSocketNotifier>
+#endif
 #if !defined(_WIN32) && !defined(__APPLE__)
-#include <obs-nix-platform.h>
 #include <qpa/qplatformnativeinterface.h>
 #endif
 
-#include <iostream>
+#ifdef _WIN32
+#include <sstream>
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <sys/socket.h>
+#endif
 
-#include "ui-config.h"
+#include "moc_OBSApp.cpp"
 
 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> obsLogViewer;
+string currentLogFile;
+string lastLogFile;
+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;
+
+extern QPointer<OBSLogViewer> 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<system_clock::period, seconds::period>::value && written && (sizeof(buf) - written) > 5) {
-		auto tp_secs = time_point_cast<seconds>(tp);
-		auto millis = duration_cast<milliseconds>(tp - tp_secs).count();
-
-		snprintf(buf + written, sizeof(buf) - written, ".%03u", static_cast<unsigned>(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<mutex> 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<fstream *>(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<mutex> 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<char> 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<char> 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<pair<string, string>> 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<char> path(GetAppConfigPathPtr(dst.str().c_str()));
-
-#ifdef _WIN32
-	BPtr<wchar_t> 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<profiler_name_store_t, decltype(ProfilerNameStoreRelease)>;
-
-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<profiler_snapshot_t, decltype(SnapshotRelease)>;
-
-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<char> 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<const char *>(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<QWidget *>(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<void, decltype(ProfilerFree)> prof_release(static_cast<void *>(&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<char> path(GetAppConfigPathPtr(name.c_str()));
-
-	fstream file;
-
-#ifdef _WIN32
-	BPtr<wchar_t> 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<char[]> 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 <string>: Use specific scene collection."
-				"\n"
-				"--profile <string>: Use specific profile.\n"
-				"--scene <string>: 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;
-}

+ 25 - 68
frontend/OBSApp.hpp

@@ -17,62 +17,29 @@
 
 #pragma once
 
+#include <utility/OBSTheme.hpp>
+#include <widgets/OBSMainWindow.hpp>
+
+#include <obs-frontend-api.h>
+#include <util/platform.h>
+#include <util/profiler.hpp>
+#include <util/util.hpp>
+
 #include <QApplication>
-#include <QTranslator>
+#include <QPalette>
 #include <QPointer>
-#include <QFileSystemWatcher>
 
-#ifndef _WIN32
-#include <QSocketNotifier>
-#else
-#include <QSessionManager>
-#endif
-#include <obs.hpp>
-#include <util/lexer.h>
-#include <util/profiler.h>
-#include <util/util.hpp>
-#include <util/platform.h>
-#include <obs-frontend-api.h>
+#include <deque>
 #include <functional>
 #include <string>
-#include <memory>
 #include <vector>
-#include <deque>
-#include <filesystem>
-
-#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; }
+typedef std::function<void()> VoidFunc;
 
-	virtual QString translate(const char *context, const char *sourceText, const char *disambiguation,
-				  int n) const override;
-};
+Q_DECLARE_METATYPE(VoidFunc)
 
-typedef std::function<void()> VoidFunc;
+class QFileSystemWatcher;
+class QSocketNotifier;
 
 struct UpdateBranch {
 	QString 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<OBSApp *>(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();

+ 10 - 12
frontend/OBSApp_Themes.cpp

@@ -15,25 +15,23 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include <cinttypes>
+#include "OBSApp.hpp"
 
+#include <utility/OBSProxyStyle.hpp>
+#include <utility/OBSThemeVariable.hpp>
+#include <utility/platform.hpp>
+
+#include <qt-wrappers.hpp>
+#include <ui-config.h>
 #include <util/cf-parser.h>
 
 #include <QDir>
+#include <QDirIterator>
 #include <QFile>
-#include <QTimer>
+#include <QFileSystemWatcher>
 #include <QMetaEnum>
-#include <QDirIterator>
-#include <QGuiApplication>
 #include <QRandomGenerator>
-
-#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 <QTimer>
 
 using namespace std;
 

+ 554 - 464
frontend/OBSStudioAPI.cpp

@@ -1,20 +1,9 @@
-#include <obs-frontend-internal.hpp>
-#include <qt-wrappers.hpp>
-#include "obs-app.hpp"
-#include "window-basic-main.hpp"
-#include "window-basic-main-outputs.hpp"
-
-#include <functional>
-
-using namespace std;
+#include "OBSStudioAPI.hpp"
 
-Q_DECLARE_METATYPE(OBSScene);
-Q_DECLARE_METATYPE(OBSSource);
+#include <widgets/OBSBasic.hpp>
+#include <widgets/OBSProjector.hpp>
 
-template<typename T> static T GetOBSRef(QListWidgetItem *item)
-{
-	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
-}
+#include <qt-wrappers.hpp>
 
 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<typename T> struct OBSStudioCallback {
-	T callback;
-	void *private_data;
-
-	inline OBSStudioCallback(T cb, void *p) : callback(cb), private_data(p) {}
-};
-
 template<typename T>
 inline size_t GetCallbackIdx(vector<OBSStudioCallback<T>> &callbacks, T callback, void *private_data)
 {
@@ -43,590 +23,700 @@ inline size_t GetCallbackIdx(vector<OBSStudioCallback<T>> &callbacks, T callback
 	return (size_t)-1;
 }
 
-struct OBSStudioAPI : obs_frontend_callbacks {
-	OBSBasic *main;
-	vector<OBSStudioCallback<obs_frontend_event_cb>> callbacks;
-	vector<OBSStudioCallback<obs_frontend_save_cb>> saveCallbacks;
-	vector<OBSStudioCallback<obs_frontend_save_cb>> preloadCallbacks;
-
-	inline OBSStudioAPI(OBSBasic *main_) : main(main_) {}
-
-	void *obs_frontend_get_main_window(void) override { return (void *)main; }
+void *OBSStudioAPI::obs_frontend_get_main_window()
+{
+	return (void *)main;
+}
 
-	void *obs_frontend_get_main_window_handle(void) override { return (void *)main->winId(); }
+void *OBSStudioAPI::obs_frontend_get_main_window_handle()
+{
+	return (void *)main->winId();
+}
 
-	void *obs_frontend_get_system_tray(void) override { return (void *)main->trayIcon.data(); }
+void *OBSStudioAPI::obs_frontend_get_system_tray()
+{
+	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<OBSScene>(item);
-			obs_source_t *source = obs_scene_get_source(scene);
+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<OBSScene>(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<OBSSource>();
+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<OBSSource>();
 
-			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 obs_frontend_set_current_transition(obs_source_t *transition) override
-	{
-		QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition)));
-	}
+void OBSStudioAPI::obs_frontend_set_current_transition(obs_source_t *transition)
+{
+	QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition)));
+}
 
-	int obs_frontend_get_transition_duration(void) override { return main->ui->transitionDuration->value(); }
+int OBSStudioAPI::obs_frontend_get_transition_duration()
+{
+	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 OBSStudioAPI::obs_frontend_set_transition_duration(int duration)
+{
+	QMetaObject::invokeMethod(main->ui->transitionDuration, "setValue", Q_ARG(int, duration));
+}
 
-	void obs_frontend_release_tbar(void) override { QMetaObject::invokeMethod(main, "TBarReleased"); }
+void OBSStudioAPI::obs_frontend_release_tbar()
+{
+	QMetaObject::invokeMethod(main, "TBarReleased");
+}
 
-	void obs_frontend_set_tbar_position(int position) override
-	{
-		QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position));
-	}
+void OBSStudioAPI::obs_frontend_set_tbar_position(int position)
+{
+	QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position));
+}
 
-	int obs_frontend_get_tbar_position(void) override { return main->tBar->value(); }
+int OBSStudioAPI::obs_frontend_get_tbar_position()
+{
+	return main->tBar->value();
+}
 
-	void obs_frontend_get_scene_collections(std::vector<std::string> &strings) override
-	{
-		for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) {
-			strings.emplace_back(collectionName);
-		}
+void OBSStudioAPI::obs_frontend_get_scene_collections(std::vector<std::string> &strings)
+{
+	for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) {
+		strings.emplace_back(collectionName);
 	}
+}
 
-	char *obs_frontend_get_current_scene_collection(void) override
-	{
-		const OBSSceneCollection &currentCollection = main->GetCurrentSceneCollection();
-		return bstrdup(currentCollection.name.c_str());
-	}
+char *OBSStudioAPI::obs_frontend_get_current_scene_collection()
+{
+	const OBSSceneCollection &currentCollection = main->GetCurrentSceneCollection();
+	return bstrdup(currentCollection.name.c_str());
+}
 
-	void obs_frontend_set_current_scene_collection(const char *collection) override
-	{
-		QList<QAction *> menuActions = main->ui->sceneCollectionMenu->actions();
-		QString qstrCollection = QT_UTF8(collection);
+void OBSStudioAPI::obs_frontend_set_current_scene_collection(const char *collection)
+{
+	QList<QAction *> 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");
+	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 obs_frontend_get_profiles(std::vector<std::string> &strings) override
-	{
-		const OBSProfileCache &profiles = main->GetProfileCache();
+void OBSStudioAPI::obs_frontend_get_profiles(std::vector<std::string> &strings)
+{
+	const OBSProfileCache &profiles = main->GetProfileCache();
 
-		for (auto &[profileName, profile] : profiles) {
-			strings.emplace_back(profileName);
-		}
+	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 *OBSStudioAPI::obs_frontend_get_current_profile()
+{
+	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();
+char *OBSStudioAPI::obs_frontend_get_current_profile_path()
+{
+	const OBSProfile &profile = main->GetCurrentProfile();
 
-		return bstrdup(profile.path.u8string().c_str());
-	}
+	return bstrdup(profile.path.u8string().c_str());
+}
 
-	void obs_frontend_set_current_profile(const char *profile) override
-	{
-		QList<QAction *> menuActions = main->ui->profileMenu->actions();
-		QString qstrProfile = QT_UTF8(profile);
+void OBSStudioAPI::obs_frontend_set_current_profile(const char *profile)
+{
+	QList<QAction *> 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");
+	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 obs_frontend_duplicate_profile(const char *name) override
-	{
-		QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name));
-	}
+void OBSStudioAPI::obs_frontend_create_profile(const char *name)
+{
+	QMetaObject::invokeMethod(main, "CreateNewProfile", 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_duplicate_profile(const char *name)
+{
+	QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name));
+}
 
-	void obs_frontend_streaming_start(void) override { QMetaObject::invokeMethod(main, "StartStreaming"); }
+void OBSStudioAPI::obs_frontend_delete_profile(const char *profile)
+{
+	QMetaObject::invokeMethod(main, "DeleteProfile", Q_ARG(QString, profile));
+}
 
-	void obs_frontend_streaming_stop(void) override { QMetaObject::invokeMethod(main, "StopStreaming"); }
+void OBSStudioAPI::obs_frontend_streaming_start()
+{
+	QMetaObject::invokeMethod(main, "StartStreaming");
+}
 
-	bool obs_frontend_streaming_active(void) override { return os_atomic_load_bool(&streaming_active); }
+void OBSStudioAPI::obs_frontend_streaming_stop()
+{
+	QMetaObject::invokeMethod(main, "StopStreaming");
+}
 
-	void obs_frontend_recording_start(void) override { QMetaObject::invokeMethod(main, "StartRecording"); }
+bool OBSStudioAPI::obs_frontend_streaming_active()
+{
+	return os_atomic_load_bool(&streaming_active);
+}
 
-	void obs_frontend_recording_stop(void) override { QMetaObject::invokeMethod(main, "StopRecording"); }
+void OBSStudioAPI::obs_frontend_recording_start()
+{
+	QMetaObject::invokeMethod(main, "StartRecording");
+}
 
-	bool obs_frontend_recording_active(void) override { return os_atomic_load_bool(&recording_active); }
+void OBSStudioAPI::obs_frontend_recording_stop()
+{
+	QMetaObject::invokeMethod(main, "StopRecording");
+}
 
-	void obs_frontend_recording_pause(bool pause) override
-	{
-		QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording");
-	}
+bool OBSStudioAPI::obs_frontend_recording_active()
+{
+	return os_atomic_load_bool(&recording_active);
+}
 
-	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;
-		}
-	}
+void OBSStudioAPI::obs_frontend_recording_pause(bool pause)
+{
+	QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording");
+}
 
-	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;
 	}
+}
 
-	void obs_frontend_replay_buffer_start(void) override { QMetaObject::invokeMethod(main, "StartReplayBuffer"); }
-
-	void obs_frontend_replay_buffer_save(void) override { QMetaObject::invokeMethod(main, "ReplayBufferSave"); }
+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;
 
-	void obs_frontend_replay_buffer_stop(void) override { QMetaObject::invokeMethod(main, "StopReplayBuffer"); }
+	proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput);
 
-	bool obs_frontend_replay_buffer_active(void) override { return os_atomic_load_bool(&replaybuf_active); }
+	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_add_tools_menu_qaction(const char *name) override
-	{
-		main->ui->menuTools->setEnabled(true);
-		return (void *)main->ui->menuTools->addAction(QT_UTF8(name));
-	}
+void OBSStudioAPI::obs_frontend_replay_buffer_start()
+{
+	QMetaObject::invokeMethod(main, "StartReplayBuffer");
+}
 
-	void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override
-	{
-		main->ui->menuTools->setEnabled(true);
+void OBSStudioAPI::obs_frontend_replay_buffer_save()
+{
+	QMetaObject::invokeMethod(main, "ReplayBufferSave");
+}
 
-		auto func = [private_data, callback]() {
-			callback(private_data);
-		};
+void OBSStudioAPI::obs_frontend_replay_buffer_stop()
+{
+	QMetaObject::invokeMethod(main, "StopReplayBuffer");
+}
 
-		QAction *action = main->ui->menuTools->addAction(QT_UTF8(name));
-		QObject::connect(action, &QAction::triggered, func);
-	}
+bool OBSStudioAPI::obs_frontend_replay_buffer_active()
+{
+	return os_atomic_load_bool(&replaybuf_active);
+}
 
-	void *obs_frontend_add_dock(void *dock) override
-	{
-		QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
+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));
+}
 
-		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");
+void OBSStudioAPI::obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data)
+{
+	main->ui->menuTools->setEnabled(true);
 
-			char *uuid = os_generate_uuid();
-			name = QT_UTF8(uuid);
-			bfree(uuid);
-			name.append("_oldExtraDock");
+	auto func = [private_data, callback]() {
+		callback(private_data);
+	};
 
-			d->setObjectName(name);
-		}
+	QAction *action = main->ui->menuTools->addAction(QT_UTF8(name));
+	QObject::connect(action, &QAction::triggered, func);
+}
 
-		return (void *)main->AddDockWidget(d);
-	}
+void *OBSStudioAPI::obs_frontend_add_dock(void *dock)
+{
+	QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
 
-	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;
-		}
+	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");
 
-		OBSDock *dock = new OBSDock(main);
-		dock->setWidget((QWidget *)widget);
-		dock->setWindowTitle(QT_UTF8(title));
-		dock->setObjectName(QT_UTF8(id));
+		char *uuid = os_generate_uuid();
+		name = QT_UTF8(uuid);
+		bfree(uuid);
+		name.append("_oldExtraDock");
 
-		main->AddDockWidget(dock, Qt::RightDockWidgetArea);
+		d->setObjectName(name);
+	}
 
-		dock->setVisible(false);
-		dock->setFloating(true);
+	return (void *)main->AddDockWidget(d);
+}
 
-		return true;
+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,
+		     "Dock id '%s' already used!  "
+		     "Duplicate library?",
+		     id);
+		return false;
 	}
 
-	void obs_frontend_remove_dock(const char *id) override { main->RemoveDockWidget(QT_UTF8(id)); }
+	OBSDock *dock = new OBSDock(main);
+	dock->setWidget((QWidget *)widget);
+	dock->setWindowTitle(QT_UTF8(title));
+	dock->setObjectName(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;
-		}
+	main->AddDockWidget(dock, Qt::RightDockWidgetArea);
 
-		QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
-		d->setObjectName(QT_UTF8(id));
+	dock->setVisible(false);
+	dock->setFloating(true);
 
-		main->AddCustomDockWidget(d);
+	return true;
+}
 
-		return true;
-	}
+void OBSStudioAPI::obs_frontend_remove_dock(const char *id)
+{
+	main->RemoveDockWidget(QT_UTF8(id));
+}
 
-	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);
+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_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;
+	QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
+	d->setObjectName(QT_UTF8(id));
 
-		callbacks.erase(callbacks.begin() + idx);
-	}
+	main->AddCustomDockWidget(d);
 
-	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;
+	return true;
+}
 
-		OBSOutput output = main->outputHandler->streamOutput.Get();
-		return obs_output_get_ref(output);
-	}
+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);
+}
 
-	obs_output_t *obs_frontend_get_recording_output(void) override
-	{
-		OBSOutput out = main->outputHandler->fileOutput.Get();
-		return obs_output_get_ref(out);
-	}
+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;
 
-	obs_output_t *obs_frontend_get_replay_buffer_output(void) override
-	{
-		OBSOutput out = main->outputHandler->replayBuffer.Get();
-		return obs_output_get_ref(out);
-	}
+	callbacks.erase(callbacks.begin() + idx);
+}
 
-	config_t *obs_frontend_get_profile_config(void) override { return main->activeConfiguration; }
+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;
 
-	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();
-	}
+	OBSOutput output = main->outputHandler->streamOutput.Get();
+	return obs_output_get_ref(output);
+}
 
-	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));
-	}
+obs_output_t *OBSStudioAPI::obs_frontend_get_recording_output()
+{
+	OBSOutput out = main->outputHandler->fileOutput.Get();
+	return obs_output_get_ref(out);
+}
 
-	void obs_frontend_save(void) override { main->SaveProject(); }
+obs_output_t *OBSStudioAPI::obs_frontend_get_replay_buffer_output()
+{
+	OBSOutput out = main->outputHandler->replayBuffer.Get();
+	return obs_output_get_ref(out);
+}
 
-	void obs_frontend_defer_save_begin(void) override { QMetaObject::invokeMethod(main, "DeferSaveBegin"); }
+config_t *OBSStudioAPI::obs_frontend_get_profile_config()
+{
+	return main->activeConfiguration;
+}
 
-	void obs_frontend_defer_save_end(void) override { QMetaObject::invokeMethod(main, "DeferSaveEnd"); }
+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();
+}
 
-	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);
-	}
+config_t *OBSStudioAPI::obs_frontend_get_app_config()
+{
+	return App()->GetAppConfig();
+}
 
-	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;
+config_t *OBSStudioAPI::obs_frontend_get_user_config()
+{
+	return App()->GetUserConfig();
+}
 
-		saveCallbacks.erase(saveCallbacks.begin() + idx);
-	}
+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 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_save()
+{
+	main->SaveProject();
+}
 
-	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;
+void OBSStudioAPI::obs_frontend_defer_save_begin()
+{
+	QMetaObject::invokeMethod(main, "DeferSaveBegin");
+}
 
-		preloadCallbacks.erase(preloadCallbacks.begin() + idx);
-	}
+void OBSStudioAPI::obs_frontend_defer_save_end()
+{
+	QMetaObject::invokeMethod(main, "DeferSaveEnd");
+}
 
-	void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) override
-	{
-		App()->PushUITranslation(translate);
-	}
+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 obs_frontend_pop_ui_translation(void) override { App()->PopUITranslation(); }
+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;
 
-	void obs_frontend_set_streaming_service(obs_service_t *service) override { main->SetService(service); }
+	saveCallbacks.erase(saveCallbacks.begin() + idx);
+}
 
-	obs_service_t *obs_frontend_get_streaming_service(void) override { return main->GetService(); }
+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 obs_frontend_save_streaming_service(void) override { main->SaveService(); }
+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);
+}
 
-	bool obs_frontend_preview_program_mode_active(void) override { return main->IsPreviewProgramMode(); }
+void OBSStudioAPI::obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate)
+{
+	App()->PushUITranslation(translate);
+}
 
-	void obs_frontend_set_preview_program_mode(bool enable) override { main->SetPreviewProgramMode(enable); }
+void OBSStudioAPI::obs_frontend_pop_ui_translation()
+{
+	App()->PopUITranslation();
+}
 
-	void obs_frontend_preview_program_trigger_transition(void) override
-	{
-		QMetaObject::invokeMethod(main, "TransitionClicked");
-	}
+void OBSStudioAPI::obs_frontend_set_streaming_service(obs_service_t *service)
+{
+	main->SetService(service);
+}
 
-	bool obs_frontend_preview_enabled(void) override { return main->previewEnabled; }
+obs_service_t *OBSStudioAPI::obs_frontend_get_streaming_service()
+{
+	return main->GetService();
+}
 
-	void obs_frontend_set_preview_enabled(bool enable) override
-	{
-		if (main->previewEnabled != enable)
-			main->EnablePreviewDisplay(enable);
-	}
+void OBSStudioAPI::obs_frontend_save_streaming_service()
+{
+	main->SaveService();
+}
 
-	obs_source_t *obs_frontend_get_current_preview_scene(void) override
-	{
-		if (main->IsPreviewProgramMode()) {
-			OBSSource source = main->GetCurrentSceneSource();
-			return obs_source_get_ref(source);
-		}
+bool OBSStudioAPI::obs_frontend_preview_program_mode_active()
+{
+	return main->IsPreviewProgramMode();
+}
 
-		return nullptr;
-	}
+void OBSStudioAPI::obs_frontend_set_preview_program_mode(bool enable)
+{
+	main->SetPreviewProgramMode(enable);
+}
 
-	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 OBSStudioAPI::obs_frontend_preview_program_trigger_transition()
+{
+	QMetaObject::invokeMethod(main, "TransitionClicked");
+}
 
-	void obs_frontend_take_screenshot(void) override { QMetaObject::invokeMethod(main, "Screenshot"); }
+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);
+}
 
-	void obs_frontend_take_source_screenshot(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source)));
+obs_source_t *OBSStudioAPI::obs_frontend_get_current_preview_scene()
+{
+	if (main->IsPreviewProgramMode()) {
+		OBSSource source = main->GetCurrentSceneSource();
+		return obs_source_get_ref(source);
 	}
 
-	obs_output_t *obs_frontend_get_virtualcam_output(void) override
-	{
-		OBSOutput output = main->outputHandler->virtualCam.Get();
-		return obs_output_get_ref(output);
+	return nullptr;
+}
+
+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_start_virtualcam(void) override { QMetaObject::invokeMethod(main, "StartVirtualCam"); }
+void OBSStudioAPI::obs_frontend_take_screenshot()
+{
+	QMetaObject::invokeMethod(main, "Screenshot");
+}
 
-	void obs_frontend_stop_virtualcam(void) override { QMetaObject::invokeMethod(main, "StopVirtualCam"); }
+void OBSStudioAPI::obs_frontend_take_source_screenshot(obs_source_t *source)
+{
+	QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source)));
+}
 
-	bool obs_frontend_virtualcam_active(void) override { return os_atomic_load_bool(&virtualcam_active); }
+obs_output_t *OBSStudioAPI::obs_frontend_get_virtualcam_output()
+{
+	OBSOutput output = main->outputHandler->virtualCam.Get();
+	return obs_output_get_ref(output);
+}
 
-	void obs_frontend_reset_video(void) override { main->ResetVideo(); }
+void OBSStudioAPI::obs_frontend_start_virtualcam()
+{
+	QMetaObject::invokeMethod(main, "StartVirtualCam");
+}
 
-	void obs_frontend_open_source_properties(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source)));
-	}
+void OBSStudioAPI::obs_frontend_stop_virtualcam()
+{
+	QMetaObject::invokeMethod(main, "StopVirtualCam");
+}
 
-	void obs_frontend_open_source_filters(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source)));
-	}
+bool OBSStudioAPI::obs_frontend_virtualcam_active()
+{
+	return os_atomic_load_bool(&virtualcam_active);
+}
 
-	void obs_frontend_open_source_interaction(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source)));
-	}
+void OBSStudioAPI::obs_frontend_reset_video()
+{
+	main->ResetVideo();
+}
 
-	void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override
-	{
-		QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
-	}
+void OBSStudioAPI::obs_frontend_open_source_properties(obs_source_t *source)
+{
+	QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source)));
+}
 
-	char *obs_frontend_get_current_record_output_path(void) override
-	{
-		const char *recordOutputPath = main->GetCurrentOutputPath();
+void OBSStudioAPI::obs_frontend_open_source_filters(obs_source_t *source)
+{
+	QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source)));
+}
 
-		return bstrdup(recordOutputPath);
-	}
+void OBSStudioAPI::obs_frontend_open_source_interaction(obs_source_t *source)
+{
+	QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source)));
+}
 
-	const char *obs_frontend_get_locale_string(const char *string) override { return Str(string); }
+void OBSStudioAPI::obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item)
+{
+	QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
+}
 
-	bool obs_frontend_is_theme_dark(void) override { return App()->IsThemeDark(); }
+char *OBSStudioAPI::obs_frontend_get_current_record_output_path()
+{
+	const char *recordOutputPath = main->GetCurrentOutputPath();
 
-	char *obs_frontend_get_last_recording(void) override
-	{
-		return bstrdup(main->outputHandler->lastRecordingPath.c_str());
-	}
+	return bstrdup(recordOutputPath);
+}
 
-	char *obs_frontend_get_last_screenshot(void) override { return bstrdup(main->lastScreenshot.c_str()); }
+const char *OBSStudioAPI::obs_frontend_get_locale_string(const char *string)
+{
+	return Str(string);
+}
 
-	char *obs_frontend_get_last_replay(void) override { return bstrdup(main->lastReplay.c_str()); }
+bool OBSStudioAPI::obs_frontend_is_theme_dark()
+{
+	return App()->IsThemeDark();
+}
 
-	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);
-	}
+char *OBSStudioAPI::obs_frontend_get_last_recording()
+{
+	return bstrdup(main->outputHandler->lastRecordingPath.c_str());
+}
 
-	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);
-		}
+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 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 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 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 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 on_event(enum obs_frontend_event event) override
-	{
-		if (main->disableSaving && event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
-		    event != OBS_FRONTEND_EVENT_EXIT)
-			return;
+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;
 
-		for (size_t i = callbacks.size(); i > 0; i--) {
-			auto cb = callbacks[i - 1];
-			cb.callback(event, cb.private_data);
-		}
+	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)
 {

+ 187 - 587
frontend/OBSStudioAPI.hpp

@@ -1,28 +1,27 @@
-#include <obs-frontend-internal.hpp>
-#include <qt-wrappers.hpp>
-#include "obs-app.hpp"
-#include "window-basic-main.hpp"
-#include "window-basic-main-outputs.hpp"
+/******************************************************************************
+ Copyright (C) 2024 by Patrick Heyer <[email protected]>
 
-#include <functional>
+ 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.
 
-using namespace std;
+ 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.
 
-Q_DECLARE_METATYPE(OBSScene);
-Q_DECLARE_METATYPE(OBSSource);
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ******************************************************************************/
 
-template<typename T> static T GetOBSRef(QListWidgetItem *item)
-{
-	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
-}
+#pragma once
 
-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;
+#include <obs-frontend-internal.hpp>
 
-/* ------------------------------------------------------------------------- */
+class OBSBasic;
+
+using namespace std;
 
 template<typename T> struct OBSStudioCallback {
 	T callback;
@@ -31,18 +30,6 @@ template<typename T> struct OBSStudioCallback {
 	inline OBSStudioCallback(T cb, void *p) : callback(cb), private_data(p) {}
 };
 
-template<typename T>
-inline size_t GetCallbackIdx(vector<OBSStudioCallback<T>> &callbacks, T callback, void *private_data)
-{
-	for (size_t i = 0; i < callbacks.size(); i++) {
-		OBSStudioCallback<T> 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<OBSStudioCallback<obs_frontend_event_cb>> 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<OBSScene>(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<OBSSource>();
-
-			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<std::string> &strings) override
-	{
-		for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) {
-			strings.emplace_back(collectionName);
-		}
-	}
-
-	char *obs_frontend_get_current_scene_collection(void) override
-	{
-		const OBSSceneCollection &currentCollection = main->GetCurrentSceneCollection();
-		return bstrdup(currentCollection.name.c_str());
-	}
-
-	void obs_frontend_set_current_scene_collection(const char *collection) override
-	{
-		QList<QAction *> 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<std::string> &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<QAction *> 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<QDockWidget *>(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<QDockWidget *>(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_get_main_window(void) override;
+
+	void *obs_frontend_get_main_window_handle(void) override;
+
+	void *obs_frontend_get_system_tray(void) override;
+
+	void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) override;
+
+	obs_source_t *obs_frontend_get_current_scene(void) override;
+
+	void obs_frontend_set_current_scene(obs_source_t *scene) override;
+
+	void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) override;
+
+	obs_source_t *obs_frontend_get_current_transition(void) override;
+
+	void obs_frontend_set_current_transition(obs_source_t *transition) override;
+
+	int obs_frontend_get_transition_duration(void) override;
+
+	void obs_frontend_set_transition_duration(int duration) override;
+
+	void obs_frontend_release_tbar(void) override;
+
+	void obs_frontend_set_tbar_position(int position) override;
+
+	int obs_frontend_get_tbar_position(void) override;
+
+	void obs_frontend_get_scene_collections(std::vector<std::string> &strings) override;
+
+	char *obs_frontend_get_current_scene_collection(void) override;
+
+	void obs_frontend_set_current_scene_collection(const char *collection) override;
+
+	bool obs_frontend_add_scene_collection(const char *name) override;
+
+	void obs_frontend_get_profiles(std::vector<std::string> &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;
 
-	void obs_frontend_save_streaming_service(void) override { main->SaveService(); }
-
-	bool obs_frontend_preview_program_mode_active(void) override { return main->IsPreviewProgramMode(); }
+	bool obs_frontend_recording_split_file(void) override;
 
-	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; }
+	bool obs_frontend_recording_add_chapter(const char *name) override;
 
-	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);
-		}
+	void obs_frontend_replay_buffer_start(void) override;
 
-		return nullptr;
-	}
+	void obs_frontend_replay_buffer_save(void) override;
 
-	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_replay_buffer_stop(void) override;
 
-	void obs_frontend_take_screenshot(void) override { QMetaObject::invokeMethod(main, "Screenshot"); }
+	bool obs_frontend_replay_buffer_active(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_add_tools_menu_qaction(const char *name) 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_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override;
 
-	void obs_frontend_start_virtualcam(void) override { QMetaObject::invokeMethod(main, "StartVirtualCam"); }
+	void *obs_frontend_add_dock(void *dock) override;
 
-	void obs_frontend_stop_virtualcam(void) override { QMetaObject::invokeMethod(main, "StopVirtualCam"); }
+	bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) override;
 
-	bool obs_frontend_virtualcam_active(void) override { return os_atomic_load_bool(&virtualcam_active); }
+	void obs_frontend_remove_dock(const char *id) override;
 
-	void obs_frontend_reset_video(void) override { main->ResetVideo(); }
+	bool obs_frontend_add_custom_qdock(const char *id, void *dock) override;
 
-	void obs_frontend_open_source_properties(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source)));
-	}
+	void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override;
 
-	void obs_frontend_open_source_filters(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source)));
-	}
+	void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) override;
 
-	void obs_frontend_open_source_interaction(obs_source_t *source) override
-	{
-		QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source)));
-	}
+	obs_output_t *obs_frontend_get_streaming_output(void) override;
 
-	void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override
-	{
-		QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
-	}
+	obs_output_t *obs_frontend_get_recording_output(void) override;
 
-	char *obs_frontend_get_current_record_output_path(void) override
-	{
-		const char *recordOutputPath = main->GetCurrentOutputPath();
+	obs_output_t *obs_frontend_get_replay_buffer_output(void) override;
 
-		return bstrdup(recordOutputPath);
-	}
+	config_t *obs_frontend_get_profile_config(void) override;
 
-	const char *obs_frontend_get_locale_string(const char *string) override { return Str(string); }
+	config_t *obs_frontend_get_global_config(void) override;
 
-	bool obs_frontend_is_theme_dark(void) override { return App()->IsThemeDark(); }
+	config_t *obs_frontend_get_app_config(void) override;
 
-	char *obs_frontend_get_last_recording(void) override
-	{
-		return bstrdup(main->outputHandler->lastRecordingPath.c_str());
-	}
+	config_t *obs_frontend_get_user_config(void) override;
 
-	char *obs_frontend_get_last_screenshot(void) override { return bstrdup(main->lastScreenshot.c_str()); }
+	void obs_frontend_open_projector(const char *type, int monitor, const char *geometry,
+					 const char *name) override;
 
-	char *obs_frontend_get_last_replay(void) override { return bstrdup(main->lastReplay.c_str()); }
+	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);
-	}
-
-	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);
-		}
-	}
+					       const char *undo_data, const char *redo_data, bool repeatable) override;
+
+	void on_load(obs_data_t *settings) override;
+
+	void on_preload(obs_data_t *settings) override;
+
+	void on_save(obs_data_t *settings) override;
+
+	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);

File diff suppressed because it is too large
+ 97 - 1300
frontend/obs-main.cpp


+ 0 - 251
frontend/utility/BaseLexer.hpp

@@ -17,41 +17,7 @@
 
 #pragma once
 
-#include <QApplication>
-#include <QTranslator>
-#include <QPointer>
-#include <QFileSystemWatcher>
-
-#ifndef _WIN32
-#include <QSocketNotifier>
-#else
-#include <QSessionManager>
-#endif
-#include <obs.hpp>
 #include <util/lexer.h>
-#include <util/profiler.h>
-#include <util/util.hpp>
-#include <util/platform.h>
-#include <obs-frontend-api.h>
-#include <functional>
-#include <string>
-#include <memory>
-#include <vector>
-#include <deque>
-#include <filesystem>
-
-#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<void()> 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<OBSMainWindow> mainWindow;
-	profiler_name_store_t *profilerNameStore = nullptr;
-	std::vector<UpdateBranch> 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<obs_frontend_translate_ui_cb> 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<QString, OBSTheme> themes;
-	QPointer<QFileSystemWatcher> 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<OBSTheme> 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<UpdateBranch> 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<OBSApp *>(qApp);
-}
-
-std::vector<std::pair<std::string, std::string>> 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

+ 2 - 24
frontend/utility/OBSTheme.hpp

@@ -17,12 +17,11 @@
 
 #pragma once
 
-#include <QVariant>
+#include <QString>
+#include <QStringList>
 
 #include <filesystem>
 
-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 */
-};

+ 1 - 25
frontend/utility/OBSThemeVariable.hpp

@@ -17,33 +17,9 @@
 
 #pragma once
 
+#include <QString>
 #include <QVariant>
 
-#include <filesystem>
-
-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*/

+ 11 - 2638
frontend/utility/OBSTranslator.cpp

@@ -15,2648 +15,21 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include <time.h>
-#include <stdio.h>
-#include <wchar.h>
-#include <chrono>
-#include <ratio>
-#include <string>
-#include <sstream>
-#include <mutex>
-#include <filesystem>
-#include <util/bmem.h>
-#include <util/dstr.hpp>
-#include <util/platform.h>
-#include <util/profiler.hpp>
-#include <util/cf-parser.h>
-#include <obs-config.h>
-#include <obs.hpp>
-#include <qt-wrappers.hpp>
-#include <slider-ignorewheel.hpp>
-
-#include <QDir>
-#include <QFile>
-#include <QGuiApplication>
-#include <QScreen>
-#include <QProcess>
-#include <QAccessible>
-
-#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 <fstream>
-
-#include <curl/curl.h>
-
-#ifdef _WIN32
-#include <windows.h>
-#include <filesystem>
-#include <util/windows/win-version.h>
-#else
-#include <signal.h>
-#include <pthread.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#endif
-
-#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
-#include "update/models/branches.hpp"
-#endif
-
-#if !defined(_WIN32) && !defined(__APPLE__)
-#include <obs-nix-platform.h>
-#include <qpa/qplatformnativeinterface.h>
-#endif
-
-#include <iostream>
-
-#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> 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<QDialog *>(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<QMouseEvent *>(event));
-
-		/*case QEvent::MouseButtonDblClick:
-		case QEvent::Wheel:*/
-		case QEvent::KeyPress:
-		case QEvent::KeyRelease:
-			return key_event(static_cast<QKeyEvent *>(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<system_clock::period, seconds::period>::value && written && (sizeof(buf) - written) > 5) {
-		auto tp_secs = time_point_cast<seconds>(tp);
-		auto millis = duration_cast<milliseconds>(tp - tp_secs).count();
-
-		snprintf(buf + written, sizeof(buf) - written, ".%03u", static_cast<unsigned>(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<mutex> 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<fstream *>(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<mutex> 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<int>(MultiviewLayout::HORIZONTAL_TOP_8_SCENES));
-		return true;
-	}
-
-	if (astrcmpi(layout, "horizontalbottom") == 0) {
-		config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
-			       static_cast<int>(MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES));
-		return true;
-	}
-
-	if (astrcmpi(layout, "verticalleft") == 0) {
-		config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
-			       static_cast<int>(MultiviewLayout::VERTICAL_LEFT_8_SCENES));
-		return true;
-	}
-
-	if (astrcmpi(layout, "verticalright") == 0) {
-		config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
-			       static_cast<int>(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<uint32_t, string> 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<UpdateBranch> &out, std::string &error)
-{
-	JsonBranches branches;
-
-	try {
-		nlohmann::json json = nlohmann::json::parse(jsonString);
-		branches = json.get<JsonBranches>();
-	} 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<UpdateBranch> &out)
-{
-	string error;
-	string branchesText;
-
-	BPtr<char> 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<UpdateBranch> 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<UpdateBranch> OBSApp::GetBranches()
-{
-	vector<UpdateBranch> 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<UpdateBranch> 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>("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<QWidget *>(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<OBSBasic *>(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;
-	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<char> 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<char> 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<char> 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;
+#include "OBSTranslator.hpp"
 
-		if (str_prefix.back() != ' ')
-			str_prefix += " ";
+#include <OBSApp.hpp>
 
-		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<OBSBasic *>(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<pair<string, string>> 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<pair<string, string>> 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<char> path(GetAppConfigPathPtr(dst.str().c_str()));
-
-#ifdef _WIN32
-	BPtr<wchar_t> 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<profiler_name_store_t, decltype(ProfilerNameStoreRelease)>;
-
-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<profiler_snapshot_t, decltype(SnapshotRelease)>;
-
-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<char> 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<const char *>(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<QWidget *>(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<void, decltype(ProfilerFree)> prof_release(static_cast<void *>(&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<char> path(GetAppConfigPathPtr(name.c_str()));
-
-	fstream file;
-
-#ifdef _WIN32
-	BPtr<wchar_t> 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<char[]> 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<OBSBasic *>(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);
-	}
+#include <qt-wrappers.hpp>
 
-	return true;
-}
-#endif
+#include "moc_OBSTranslator.cpp"
 
-int main(int argc, char *argv[])
+QString OBSTranslator::translate(const char *, const char *sourceText, const char *, int) const
 {
-#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 <string>: Use specific scene collection."
-				"\n"
-				"--profile <string>: Use specific profile.\n"
-				"--scene <string>: 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);
-	}
+	const char *out = nullptr;
+	QString str(sourceText);
+	str.replace(" ", "");
+	if (!App()->TranslateString(QT_TO_UTF8(str), &out))
+		return QString(sourceText);
 
-	return ret;
+	return QT_UTF8(out);
 }

+ 1 - 250
frontend/utility/OBSTranslator.hpp

@@ -17,50 +17,8 @@
 
 #pragma once
 
-#include <QApplication>
+#include <QString>
 #include <QTranslator>
-#include <QPointer>
-#include <QFileSystemWatcher>
-
-#ifndef _WIN32
-#include <QSocketNotifier>
-#else
-#include <QSessionManager>
-#endif
-#include <obs.hpp>
-#include <util/lexer.h>
-#include <util/profiler.h>
-#include <util/util.hpp>
-#include <util/platform.h>
-#include <obs-frontend-api.h>
-#include <functional>
-#include <string>
-#include <memory>
-#include <vector>
-#include <deque>
-#include <filesystem>
-
-#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<void()> 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<OBSMainWindow> mainWindow;
-	profiler_name_store_t *profilerNameStore = nullptr;
-	std::vector<UpdateBranch> 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<obs_frontend_translate_ui_cb> 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<QString, OBSTheme> themes;
-	QPointer<QFileSystemWatcher> 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<OBSTheme> 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<UpdateBranch> 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<OBSApp *>(qApp);
-}
-
-std::vector<std::pair<std::string, std::string>> 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

Some files were not shown because too many files changed in this diff