2013-11-07 16:45:03 -07:00
|
|
|
/******************************************************************************
|
2023-05-18 17:37:26 -07:00
|
|
|
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
|
2013-11-07 16:45:03 -07:00
|
|
|
|
|
|
|
|
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
|
2013-12-02 22:24:38 -07:00
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
2013-11-07 16:45:03 -07:00
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#include "OBSApp.hpp"
|
2013-12-22 17:42:02 -07:00
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#include <components/Multiview.hpp>
|
|
|
|
|
#include <utility/OBSEventFilter.hpp>
|
|
|
|
|
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
|
|
|
|
|
#include <utility/models/branches.hpp>
|
2022-07-26 01:31:12 +02:00
|
|
|
#endif
|
2024-12-11 19:03:30 +01:00
|
|
|
#include <widgets/OBSBasic.hpp>
|
2015-08-16 03:51:22 -07:00
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
|
|
|
|
#include <obs-nix-platform.h>
|
2014-01-23 17:00:42 -07:00
|
|
|
#endif
|
2024-12-11 19:03:30 +01:00
|
|
|
#include <qt-wrappers.hpp>
|
2013-11-07 16:45:03 -07:00
|
|
|
|
2022-11-05 22:32:43 +01:00
|
|
|
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
|
2024-12-11 19:03:30 +01:00
|
|
|
#include <QFile>
|
2022-11-05 22:32:43 +01:00
|
|
|
#endif
|
|
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
#include <QSessionManager>
|
|
|
|
|
#else
|
|
|
|
|
#include <QSocketNotifier>
|
|
|
|
|
#endif
|
2020-03-06 17:50:41 -03:00
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
2020-03-06 18:53:42 -03:00
|
|
|
#include <qpa/qplatformnativeinterface.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#else
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#endif
|
2017-01-11 13:19:17 -06:00
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#include "moc_OBSApp.cpp"
|
2019-03-29 04:16:12 -05:00
|
|
|
|
Change the UI to Qt (work in progress)
--------------------------------------------------
Notes and details
--------------------------------------------------
Why was this done? Because wxWidgets was just lacking in many areas. I
know wxWidgets is designed to be used with native controls, and that's
great, but wxWidgets just is not a feature-complete toolkit for
multiplatform applications. It lacks in dialog editors, its code is
archaic and outdated, and I just feel frustrated every time I try to do
things with it.
Qt on the other hand.. I had to actually try Qt to realize how much
better it was as a toolkit. They've got everything from dialog editors,
to an IDE, a debugger, build tools, just everything, and it's all
top-notch and highly maintained. The focus of the toolkit is
application development, and they spend their time trying to help
people do exactly that: make programs. Great support, great tools,
and because of that, great toolkit. I just didn't want to alienate any
developers by being stubborn about native widgets.
There *are* some things that are rather lackluster about it and design
choices I disagree with though. For example, I realize that to have an
easy to use toolkit you have to have some level of code generation.
However, in my personal and humble opinion, moc just feels like a
terrible way to approach the problem. Even now I feel like there are a
variety of ways you could handle code generation and automatic
management of things like that. I don't like the idea of circumventing
the language itself like that. It feels like one giant massive hack.
--------------------------------------------------
Things that aren't working properly:
--------------------------------------------------
- Settings dialog is not implemented. The dialog is complete but the
code to handle the dialog hasn't been constructed yet.
- There is a problem with using Qt widgets as a device target on
windows, with at least OpenGL: if I have the preview widget
automatically resize itself, it seems to cause some sort of video
card failure that I don't understand.
- Because of the above, resizing the preview widget has been disabled
until I can figure out what's going on, so it's currently only a
32x32 area.
- Direct3D doesn't seem to render correctly either, seems that the
viewport is messed up or something. I'm sort of confused about
what's going on with it.
- The new main window seems to be triggering more race conditions than
the wxWidgets main window dialog did. I'm not entirely sure what's
going on here, but this may just be existing race conditions within
libobs itself that I just never spotted before (even though I tend to
be very thorough with race conditions any time I use variables
cross-thread)
2014-01-23 11:53:55 -07:00
|
|
|
using namespace std;
|
2013-11-23 23:38:52 -07:00
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
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;
|
2020-05-15 07:34:54 -05:00
|
|
|
|
2023-03-22 00:07:15 +01:00
|
|
|
#ifndef _WIN32
|
|
|
|
|
int OBSApp::sigintFd[2];
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-08-10 06:04:35 -07:00
|
|
|
// GPU hint exports for AMD/NVIDIA laptops
|
2016-11-27 07:44:39 +01:00
|
|
|
#ifdef _MSC_VER
|
2018-08-10 06:04:35 -07:00
|
|
|
extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1;
|
2016-11-27 07:44:39 +01:00
|
|
|
extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|
|
|
|
#endif
|
|
|
|
|
|
2014-11-01 21:48:58 +01:00
|
|
|
QObject *CreateShortcutFilter()
|
|
|
|
|
{
|
2015-06-29 13:26:08 -07:00
|
|
|
return new OBSEventFilter([](QObject *obj, QEvent *event) {
|
2014-11-01 21:48:58 +01:00
|
|
|
auto mouse_event = [](QMouseEvent &event) {
|
2020-02-13 12:28:48 +11:00
|
|
|
if (!App()->HotkeysEnabledInFocus() && event.button() != Qt::LeftButton)
|
2019-07-22 01:11:34 -07:00
|
|
|
return true;
|
|
|
|
|
|
2014-11-01 21:48:58 +01:00
|
|
|
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;
|
|
|
|
|
|
2020-12-01 10:54:21 -05:00
|
|
|
case Qt::MiddleButton:
|
2014-11-01 21:48:58 +01:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-29 13:26:08 -07:00
|
|
|
auto key_event = [&](QKeyEvent *event) {
|
2021-09-20 08:16:16 -07:00
|
|
|
int key = event->key();
|
|
|
|
|
bool enabledInFocus = App()->HotkeysEnabledInFocus();
|
|
|
|
|
|
|
|
|
|
if (key != Qt::Key_Enter && key != Qt::Key_Escape && key != Qt::Key_Return && !enabledInFocus)
|
2019-07-22 01:11:34 -07:00
|
|
|
return true;
|
|
|
|
|
|
2015-06-29 13:26:08 -07:00
|
|
|
QDialog *dialog = qobject_cast<QDialog *>(obj);
|
|
|
|
|
|
2014-11-01 21:48:58 +01:00
|
|
|
obs_key_combination_t hotkey = {0, OBS_KEY_NONE};
|
|
|
|
|
bool pressed = event->type() == QEvent::KeyPress;
|
|
|
|
|
|
2021-09-20 08:16:16 -07:00
|
|
|
switch (key) {
|
2014-11-01 21:48:58 +01:00
|
|
|
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
|
|
|
|
|
|
2015-06-29 13:26:08 -07:00
|
|
|
case Qt::Key_Enter:
|
|
|
|
|
case Qt::Key_Escape:
|
|
|
|
|
case Qt::Key_Return:
|
|
|
|
|
if (dialog && pressed)
|
|
|
|
|
return false;
|
2021-09-20 08:16:16 -07:00
|
|
|
if (!enabledInFocus)
|
|
|
|
|
return true;
|
2017-05-29 21:05:18 -03:00
|
|
|
/* Falls through. */
|
2014-11-01 21:48:58 +01:00
|
|
|
default:
|
|
|
|
|
hotkey.key = obs_key_from_virtual_key(event->nativeVirtualKey());
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-23 04:15:44 -05:00
|
|
|
if (event->isAutoRepeat())
|
|
|
|
|
return true;
|
|
|
|
|
|
2014-11-01 21:48:58 +01:00
|
|
|
hotkey.modifiers = TranslateQtKeyboardEventModifiers(event->modifiers());
|
|
|
|
|
|
|
|
|
|
obs_hotkey_inject_event(hotkey, pressed);
|
2015-06-29 13:26:08 -07:00
|
|
|
return true;
|
2014-11-01 21:48:58 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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:
|
2015-06-29 13:26:08 -07:00
|
|
|
return key_event(static_cast<QKeyEvent *>(event));
|
2014-11-01 21:48:58 +01:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 20:00:34 +02:00
|
|
|
#define DEFAULT_LANG "en-US"
|
|
|
|
|
|
2013-12-16 00:07:08 -07:00
|
|
|
bool OBSApp::InitGlobalConfigDefaults()
|
2013-11-23 23:38:52 -07:00
|
|
|
{
|
2024-09-03 16:28:36 +02:00
|
|
|
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);
|
2013-12-16 00:07:08 -07:00
|
|
|
|
2013-12-17 02:08:41 -07:00
|
|
|
#if _WIN32
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_string(appConfig, "Video", "Renderer", "Direct3D 11");
|
2013-12-17 02:08:41 -07:00
|
|
|
#else
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_string(appConfig, "Video", "Renderer", "OpenGL");
|
2013-12-17 02:08:41 -07:00
|
|
|
#endif
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
#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");
|
2024-10-04 17:33:58 -04:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
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);
|
2024-10-04 17:33:58 -04:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_bool(userConfig, "BasicWindow", "VerticalVolControl", false);
|
2024-10-04 17:33:58 -04:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_bool(userConfig, "BasicWindow", "MultiviewMouseSwitch", true);
|
2024-10-04 17:33:58 -04:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawNames", true);
|
2024-10-04 17:33:58 -04:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_bool(userConfig, "BasicWindow", "MultiviewDrawAreas", true);
|
2024-10-04 17:33:58 -04:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_default_bool(userConfig, "BasicWindow", "MediaControlsCountdownTimer", true);
|
2013-11-23 23:38:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool do_mkdir(const char *path)
|
|
|
|
|
{
|
2015-07-08 10:03:39 -07:00
|
|
|
if (os_mkdirs(path) == MKDIR_ERROR) {
|
2013-12-07 10:22:56 -07:00
|
|
|
OBSErrorBox(NULL, "Failed to create directory %s", path);
|
2013-11-23 23:38:52 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool MakeUserDirs()
|
|
|
|
|
{
|
2015-01-15 23:44:38 -08:00
|
|
|
char path[512];
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/basic") <= 0)
|
2015-01-15 23:44:38 -08:00
|
|
|
return false;
|
2014-05-14 17:47:38 -07:00
|
|
|
if (!do_mkdir(path))
|
2014-03-06 21:08:12 -07:00
|
|
|
return false;
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/logs") <= 0)
|
2015-01-15 23:44:38 -08:00
|
|
|
return false;
|
2014-05-14 17:47:38 -07:00
|
|
|
if (!do_mkdir(path))
|
2014-03-06 21:08:12 -07:00
|
|
|
return false;
|
2015-08-01 09:30:02 +02:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/profiler_data") <= 0)
|
2015-08-01 09:30:02 +02:00
|
|
|
return false;
|
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
|
return false;
|
|
|
|
|
|
2015-03-27 14:29:37 -07:00
|
|
|
#ifdef _WIN32
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/crashes") <= 0)
|
2015-03-27 14:29:37 -07:00
|
|
|
return false;
|
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
|
return false;
|
2022-07-31 18:04:55 +02:00
|
|
|
#endif
|
2017-02-20 04:46:29 -08:00
|
|
|
|
2022-07-31 18:04:55 +02:00
|
|
|
#ifdef WHATSNEW_ENABLED
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/updates") <= 0)
|
2017-02-20 04:46:29 -08:00
|
|
|
return false;
|
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
|
return false;
|
2015-03-27 14:29:37 -07:00
|
|
|
#endif
|
2017-02-20 04:46:29 -08:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0)
|
2015-08-09 05:09:07 -07:00
|
|
|
return false;
|
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
|
return false;
|
2015-03-27 14:29:37 -07:00
|
|
|
|
2014-03-06 21:08:12 -07:00
|
|
|
return true;
|
2013-11-23 23:38:52 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles";
|
|
|
|
|
constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes";
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
static bool MakeUserProfileDirs()
|
2016-04-19 18:10:09 -07:00
|
|
|
{
|
2024-09-03 16:29:30 +02:00
|
|
|
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;
|
2016-04-19 18:10:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
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;
|
2016-04-19 18:10:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
return true;
|
2016-04-19 18:10:09 -07:00
|
|
|
}
|
|
|
|
|
|
2018-05-09 05:52:56 -03:00
|
|
|
bool OBSApp::UpdatePre22MultiviewLayout(const char *layout)
|
|
|
|
|
{
|
|
|
|
|
if (!layout)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "horizontaltop") == 0) {
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 21:41:39 -03:00
|
|
|
static_cast<int>(MultiviewLayout::HORIZONTAL_TOP_8_SCENES));
|
2018-05-09 05:52:56 -03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "horizontalbottom") == 0) {
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 21:41:39 -03:00
|
|
|
static_cast<int>(MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES));
|
2018-05-09 05:52:56 -03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "verticalleft") == 0) {
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 21:41:39 -03:00
|
|
|
static_cast<int>(MultiviewLayout::VERTICAL_LEFT_8_SCENES));
|
2018-05-09 05:52:56 -03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "verticalright") == 0) {
|
2024-09-03 16:28:36 +02:00
|
|
|
config_set_int(userConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 21:41:39 -03:00
|
|
|
static_cast<int>(MultiviewLayout::VERTICAL_RIGHT_8_SCENES));
|
2018-05-09 05:52:56 -03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-23 23:38:52 -07:00
|
|
|
bool OBSApp::InitGlobalConfig()
|
|
|
|
|
{
|
2015-01-15 23:44:38 -08:00
|
|
|
char path[512];
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
int len = GetAppConfigPath(path, sizeof(path), "obs-studio/global.ini");
|
2015-01-15 23:44:38 -08:00
|
|
|
if (len <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-11-23 23:38:52 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
int errorcode = appConfig.Open(path, CONFIG_OPEN_ALWAYS);
|
2013-11-23 23:38:52 -07:00
|
|
|
if (errorcode != CONFIG_SUCCESS) {
|
2013-12-07 10:22:56 -07:00
|
|
|
OBSErrorBox(NULL, "Failed to open global.ini: %d", errorcode);
|
2013-11-23 23:38:52 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
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();
|
|
|
|
|
|
2024-09-18 23:02:25 +02:00
|
|
|
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"));
|
|
|
|
|
}
|
2024-09-03 16:28:36 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 14:02:54 +02:00
|
|
|
MigrateLegacySettings(lastVersion);
|
2024-09-03 16:28:36 +02:00
|
|
|
InitUserConfigDefaults();
|
2019-02-05 18:05:19 -08:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2019-02-05 18:05:19 -08:00
|
|
|
|
2024-10-10 14:02:54 +02:00
|
|
|
void OBSApp::MigrateLegacySettings(const uint32_t lastVersion)
|
2024-09-03 16:28:36 +02:00
|
|
|
{
|
|
|
|
|
bool hasChanges = false;
|
2019-10-10 03:29:09 +02:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2019-10-10 03:29:09 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (config_has_user_value(userConfig, "BasicWindow", "MultiviewLayout")) {
|
|
|
|
|
const char *layout = config_get_string(userConfig, "BasicWindow", "MultiviewLayout");
|
|
|
|
|
|
|
|
|
|
bool layoutUpdated = UpdatePre22MultiviewLayout(layout);
|
|
|
|
|
|
|
|
|
|
hasChanges = hasChanges | layoutUpdated;
|
2018-05-09 05:52:56 -03:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (lastVersion && lastVersion < v24) {
|
|
|
|
|
bool disableHotkeysInFocus = config_get_bool(userConfig, "General", "DisableHotkeysInFocus");
|
|
|
|
|
|
|
|
|
|
if (disableHotkeysInFocus) {
|
|
|
|
|
config_set_string(userConfig, "General", "HotkeyFocusType", "DisableHotkeysInFocus");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hasChanges = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 14:02:54 +02:00
|
|
|
if (hasChanges) {
|
|
|
|
|
userConfig.SaveSafe("tmp");
|
|
|
|
|
}
|
2024-09-03 16:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2019-07-22 01:20:12 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
std::string legacyConfigFileString;
|
|
|
|
|
legacyConfigFileString.reserve(strlen(path) + OBSGlobalIniPath.size());
|
|
|
|
|
legacyConfigFileString.append(path).append(OBSGlobalIniPath);
|
2017-04-28 18:02:03 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-13 21:57:33 -04:00
|
|
|
try {
|
|
|
|
|
std::filesystem::copy(legacyGlobalConfigFile, userConfigFile);
|
|
|
|
|
} catch (const std::filesystem::filesystem_error &) {
|
|
|
|
|
OBSErrorBox(nullptr, "Unable to migrate global configuration - copy failed.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-09-03 16:28:36 +02:00
|
|
|
|
|
|
|
|
return true;
|
2013-11-23 23:38:52 -07:00
|
|
|
}
|
2013-11-07 16:45:03 -07:00
|
|
|
|
2013-12-07 10:22:56 -07:00
|
|
|
bool OBSApp::InitLocale()
|
|
|
|
|
{
|
2015-07-11 08:04:12 +02:00
|
|
|
ProfileScope("OBSApp::InitLocale");
|
2023-03-08 23:08:55 +01:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
const char *lang = config_get_string(userConfig, "General", "Language");
|
|
|
|
|
bool userLocale = config_has_user_value(userConfig, "General", "Language");
|
2023-03-08 23:08:55 +01:00
|
|
|
if (!userLocale || !lang || lang[0] == '\0')
|
|
|
|
|
lang = DEFAULT_LANG;
|
2013-12-07 10:22:56 -07:00
|
|
|
|
2013-12-30 06:56:39 -07:00
|
|
|
locale = lang;
|
|
|
|
|
|
2021-06-27 03:55:22 -07:00
|
|
|
// set basic default application locale
|
|
|
|
|
if (!locale.empty())
|
2021-09-07 12:01:58 -04:00
|
|
|
QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_')));
|
2021-06-27 03:55:22 -07:00
|
|
|
|
2013-12-17 13:56:28 -07:00
|
|
|
string englishPath;
|
2014-07-07 21:49:36 -07:00
|
|
|
if (!GetDataFilePath("locale/" DEFAULT_LANG ".ini", englishPath)) {
|
|
|
|
|
OBSErrorBox(NULL, "Failed to find locale/" DEFAULT_LANG ".ini");
|
2013-12-17 13:56:28 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 20:33:53 +02:00
|
|
|
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_;
|
2021-06-27 03:55:22 -07:00
|
|
|
|
|
|
|
|
// set application default locale to the new choosen one
|
|
|
|
|
if (!locale.empty())
|
|
|
|
|
QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_')));
|
|
|
|
|
|
2014-07-11 20:33:53 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-17 13:56:28 -07:00
|
|
|
return true;
|
2014-07-11 20:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stringstream file;
|
|
|
|
|
file << "locale/" << lang << ".ini";
|
2013-12-17 13:56:28 -07:00
|
|
|
|
2013-12-07 10:22:56 -07:00
|
|
|
string path;
|
2013-12-17 13:56:28 -07:00
|
|
|
if (GetDataFilePath(file.str().c_str(), path)) {
|
|
|
|
|
if (!text_lookup_add(textLookup, path.c_str()))
|
2014-02-28 20:02:29 -07:00
|
|
|
blog(LOG_ERROR, "Failed to add locale file '%s'", path.c_str());
|
2013-12-17 13:56:28 -07:00
|
|
|
} else {
|
2014-02-28 20:02:29 -07:00
|
|
|
blog(LOG_ERROR, "Could not find locale file '%s'", file.str().c_str());
|
2013-12-07 10:22:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-05 22:32:43 +01:00
|
|
|
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
|
2022-10-01 14:29:48 +02:00
|
|
|
void ParseBranchesJson(const std::string &jsonString, vector<UpdateBranch> &out, std::string &error)
|
|
|
|
|
{
|
2023-07-23 07:59:37 +02:00
|
|
|
JsonBranches branches;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
nlohmann::json json = nlohmann::json::parse(jsonString);
|
|
|
|
|
branches = json.get<JsonBranches>();
|
|
|
|
|
} catch (nlohmann::json::exception &e) {
|
|
|
|
|
error = e.what();
|
2022-10-01 14:29:48 +02:00
|
|
|
return;
|
2023-07-23 07:59:37 +02:00
|
|
|
}
|
2022-10-01 14:29:48 +02:00
|
|
|
|
2023-07-23 07:59:37 +02:00
|
|
|
for (const JsonBranch &json_branch : branches) {
|
2022-10-01 14:29:48 +02:00
|
|
|
#ifdef _WIN32
|
2023-07-23 07:59:37 +02:00
|
|
|
if (!json_branch.windows)
|
2022-10-01 14:29:48 +02:00
|
|
|
continue;
|
2022-11-05 22:32:43 +01:00
|
|
|
#elif defined(__APPLE__)
|
2023-07-23 07:59:37 +02:00
|
|
|
if (!json_branch.macos)
|
2022-11-05 22:32:43 +01:00
|
|
|
continue;
|
2022-10-01 14:29:48 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
UpdateBranch branch = {
|
2023-07-23 07:59:37 +02:00
|
|
|
QString::fromStdString(json_branch.name),
|
|
|
|
|
QString::fromStdString(json_branch.display_name),
|
|
|
|
|
QString::fromStdString(json_branch.description),
|
|
|
|
|
json_branch.enabled,
|
|
|
|
|
json_branch.visible,
|
2022-10-01 14:29:48 +02:00
|
|
|
};
|
|
|
|
|
out.push_back(branch);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LoadBranchesFile(vector<UpdateBranch> &out)
|
|
|
|
|
{
|
|
|
|
|
string error;
|
|
|
|
|
string branchesText;
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
BPtr<char> branchesFilePath = GetAppConfigPathPtr("obs-studio/updates/branches.json");
|
2022-10-01 14:29:48 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2022-11-05 22:32:43 +01:00
|
|
|
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
|
2022-10-01 14:29:48 +02:00
|
|
|
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});
|
|
|
|
|
|
2022-11-05 22:32:43 +01:00
|
|
|
#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
|
2022-10-01 14:29:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-11 08:03:31 +02:00
|
|
|
OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
|
|
|
|
|
: QApplication(argc, argv),
|
|
|
|
|
profilerNameStore(store)
|
2015-11-16 09:08:55 -08:00
|
|
|
{
|
2022-02-22 19:06:31 -08:00
|
|
|
/* 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
|
|
|
|
|
|
2023-03-22 00:07:15 +01:00
|
|
|
#ifndef _WIN32
|
|
|
|
|
/* Handle SIGINT properly */
|
|
|
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd);
|
|
|
|
|
snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
|
2023-03-12 03:01:31 +01:00
|
|
|
connect(snInt, &QSocketNotifier::activated, this, &OBSApp::ProcessSigInt);
|
2023-11-28 00:02:20 +01:00
|
|
|
#else
|
|
|
|
|
connect(qApp, &QGuiApplication::commitDataRequest, this, &OBSApp::commitData);
|
2023-03-22 00:07:15 +01:00
|
|
|
#endif
|
|
|
|
|
|
2015-11-16 09:08:55 -08:00
|
|
|
sleepInhibitor = os_inhibit_sleep_create("OBS Video/audio");
|
2019-03-11 10:52:01 -05:00
|
|
|
|
2022-02-22 15:37:48 -06:00
|
|
|
#ifndef __APPLE__
|
2019-03-11 10:52:01 -05:00
|
|
|
setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png")));
|
2020-11-12 23:41:30 +01:00
|
|
|
#endif
|
2022-01-12 02:09:53 +01:00
|
|
|
|
|
|
|
|
setDesktopFileName("com.obsproject.Studio");
|
2015-11-16 09:08:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OBSApp::~OBSApp()
|
|
|
|
|
{
|
2017-04-17 22:38:16 +09:00
|
|
|
#ifdef _WIN32
|
2024-09-16 20:28:35 +02:00
|
|
|
bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking");
|
2017-04-17 22:38:16 +09:00
|
|
|
if (disableAudioDucking)
|
|
|
|
|
DisableAudioDucking(false);
|
2023-03-22 00:07:15 +01:00
|
|
|
#else
|
|
|
|
|
delete snInt;
|
|
|
|
|
close(sigintFd[0]);
|
|
|
|
|
close(sigintFd[1]);
|
2017-04-17 22:38:16 +09:00
|
|
|
#endif
|
|
|
|
|
|
2016-01-25 17:09:59 -08:00
|
|
|
#ifdef __APPLE__
|
2024-09-16 20:28:35 +02:00
|
|
|
bool vsyncDisabled = config_get_bool(appConfig, "Video", "DisableOSXVSync");
|
|
|
|
|
bool resetVSync = config_get_bool(appConfig, "Video", "ResetOSXVSyncOnExit");
|
2022-01-21 15:47:05 +01:00
|
|
|
if (vsyncDisabled && resetVSync)
|
2016-01-25 17:09:59 -08:00
|
|
|
EnableOSXVSync(true);
|
|
|
|
|
#endif
|
|
|
|
|
|
2015-11-16 09:08:55 -08:00
|
|
|
os_inhibit_sleep_set_active(sleepInhibitor, false);
|
|
|
|
|
os_inhibit_sleep_destroy(sleepInhibitor);
|
2020-06-28 01:49:57 +02:00
|
|
|
|
|
|
|
|
if (libobs_initialized)
|
|
|
|
|
obs_shutdown();
|
2015-11-16 09:08:55 -08:00
|
|
|
}
|
2014-07-12 21:24:30 +02:00
|
|
|
|
2015-06-23 19:38:01 -07:00
|
|
|
static void move_basic_to_profiles(void)
|
|
|
|
|
{
|
|
|
|
|
char path[512];
|
|
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) {
|
2015-06-23 19:38:01 -07:00
|
|
|
return;
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
const std::filesystem::path basicPath = std::filesystem::u8path(path);
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
if (!std::filesystem::exists(basicPath)) {
|
2015-06-23 19:38:01 -07:00
|
|
|
return;
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
const std::filesystem::path profilesPath =
|
|
|
|
|
App()->userProfilesLocation / std::filesystem::u8path("obs-studio/basic/profiles");
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
if (std::filesystem::exists(profilesPath)) {
|
2015-06-23 19:38:01 -07:00
|
|
|
return;
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
const std::filesystem::path newProfilePath = profilesPath / std::filesystem::u8path(Str("Untitled"));
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
for (auto &entry : std::filesystem::directory_iterator(basicPath)) {
|
|
|
|
|
if (entry.is_directory()) {
|
2015-06-23 19:38:01 -07:00
|
|
|
continue;
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
if (entry.path().filename().u8string() == "scenes.json") {
|
2015-06-23 19:38:01 -07:00
|
|
|
continue;
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
const filesystem::path destinationFile = newProfilePath / entry.path().filename();
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:29:30 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-23 19:38:01 -07:00
|
|
|
}
|
|
|
|
|
|
2015-06-23 19:29:07 -07:00
|
|
|
static void move_basic_to_scene_collections(void)
|
|
|
|
|
{
|
|
|
|
|
char path[512];
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) {
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::filesystem::path basicPath = std::filesystem::u8path(path);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (!std::filesystem::exists(basicPath)) {
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::filesystem::path sceneCollectionPath =
|
|
|
|
|
App()->userScenesLocation / std::filesystem::u8path("obs-studio/basic/scenes");
|
|
|
|
|
|
|
|
|
|
if (std::filesystem::exists(sceneCollectionPath)) {
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
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());
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
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");
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2014-07-12 21:24:30 +02:00
|
|
|
void OBSApp::AppInit()
|
2013-11-07 16:45:03 -07:00
|
|
|
{
|
2015-07-11 08:04:12 +02:00
|
|
|
ProfileScope("OBSApp::AppInit");
|
|
|
|
|
|
2013-11-23 23:38:52 -07:00
|
|
|
if (!MakeUserDirs())
|
2015-06-23 19:37:24 -07:00
|
|
|
throw "Failed to create required user directories";
|
2013-11-23 23:38:52 -07:00
|
|
|
if (!InitGlobalConfig())
|
Change the UI to Qt (work in progress)
--------------------------------------------------
Notes and details
--------------------------------------------------
Why was this done? Because wxWidgets was just lacking in many areas. I
know wxWidgets is designed to be used with native controls, and that's
great, but wxWidgets just is not a feature-complete toolkit for
multiplatform applications. It lacks in dialog editors, its code is
archaic and outdated, and I just feel frustrated every time I try to do
things with it.
Qt on the other hand.. I had to actually try Qt to realize how much
better it was as a toolkit. They've got everything from dialog editors,
to an IDE, a debugger, build tools, just everything, and it's all
top-notch and highly maintained. The focus of the toolkit is
application development, and they spend their time trying to help
people do exactly that: make programs. Great support, great tools,
and because of that, great toolkit. I just didn't want to alienate any
developers by being stubborn about native widgets.
There *are* some things that are rather lackluster about it and design
choices I disagree with though. For example, I realize that to have an
easy to use toolkit you have to have some level of code generation.
However, in my personal and humble opinion, moc just feels like a
terrible way to approach the problem. Even now I feel like there are a
variety of ways you could handle code generation and automatic
management of things like that. I don't like the idea of circumventing
the language itself like that. It feels like one giant massive hack.
--------------------------------------------------
Things that aren't working properly:
--------------------------------------------------
- Settings dialog is not implemented. The dialog is complete but the
code to handle the dialog hasn't been constructed yet.
- There is a problem with using Qt widgets as a device target on
windows, with at least OpenGL: if I have the preview widget
automatically resize itself, it seems to cause some sort of video
card failure that I don't understand.
- Because of the above, resizing the preview widget has been disabled
until I can figure out what's going on, so it's currently only a
32x32 area.
- Direct3D doesn't seem to render correctly either, seems that the
viewport is messed up or something. I'm sort of confused about
what's going on with it.
- The new main window seems to be triggering more race conditions than
the wxWidgets main window dialog did. I'm not entirely sure what's
going on here, but this may just be existing race conditions within
libobs itself that I just never spotted before (even though I tend to
be very thorough with race conditions any time I use variables
cross-thread)
2014-01-23 11:53:55 -07:00
|
|
|
throw "Failed to initialize global config";
|
2013-12-07 10:22:56 -07:00
|
|
|
if (!InitLocale())
|
Change the UI to Qt (work in progress)
--------------------------------------------------
Notes and details
--------------------------------------------------
Why was this done? Because wxWidgets was just lacking in many areas. I
know wxWidgets is designed to be used with native controls, and that's
great, but wxWidgets just is not a feature-complete toolkit for
multiplatform applications. It lacks in dialog editors, its code is
archaic and outdated, and I just feel frustrated every time I try to do
things with it.
Qt on the other hand.. I had to actually try Qt to realize how much
better it was as a toolkit. They've got everything from dialog editors,
to an IDE, a debugger, build tools, just everything, and it's all
top-notch and highly maintained. The focus of the toolkit is
application development, and they spend their time trying to help
people do exactly that: make programs. Great support, great tools,
and because of that, great toolkit. I just didn't want to alienate any
developers by being stubborn about native widgets.
There *are* some things that are rather lackluster about it and design
choices I disagree with though. For example, I realize that to have an
easy to use toolkit you have to have some level of code generation.
However, in my personal and humble opinion, moc just feels like a
terrible way to approach the problem. Even now I feel like there are a
variety of ways you could handle code generation and automatic
management of things like that. I don't like the idea of circumventing
the language itself like that. It feels like one giant massive hack.
--------------------------------------------------
Things that aren't working properly:
--------------------------------------------------
- Settings dialog is not implemented. The dialog is complete but the
code to handle the dialog hasn't been constructed yet.
- There is a problem with using Qt widgets as a device target on
windows, with at least OpenGL: if I have the preview widget
automatically resize itself, it seems to cause some sort of video
card failure that I don't understand.
- Because of the above, resizing the preview widget has been disabled
until I can figure out what's going on, so it's currently only a
32x32 area.
- Direct3D doesn't seem to render correctly either, seems that the
viewport is messed up or something. I'm sort of confused about
what's going on with it.
- The new main window seems to be triggering more race conditions than
the wxWidgets main window dialog did. I'm not entirely sure what's
going on here, but this may just be existing race conditions within
libobs itself that I just never spotted before (even though I tend to
be very thorough with race conditions any time I use variables
cross-thread)
2014-01-23 11:53:55 -07:00
|
|
|
throw "Failed to load locale";
|
2015-02-17 02:45:34 -05:00
|
|
|
if (!InitTheme())
|
|
|
|
|
throw "Failed to load theme";
|
2015-06-23 19:38:01 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
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);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (!config_has_user_value(userConfig, "Basic", "Profile")) {
|
|
|
|
|
config_set_string(userConfig, "Basic", "Profile", Str("Untitled"));
|
|
|
|
|
config_set_string(userConfig, "Basic", "ProfileDir", Str("Untitled"));
|
2019-03-05 12:50:04 -08:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (!config_has_user_value(userConfig, "Basic", "SceneCollection")) {
|
|
|
|
|
config_set_string(userConfig, "Basic", "SceneCollection", Str("Untitled"));
|
|
|
|
|
config_set_string(userConfig, "Basic", "SceneCollectionFile", Str("Untitled"));
|
2019-03-05 12:50:04 -08:00
|
|
|
}
|
|
|
|
|
|
2017-04-17 22:38:16 +09:00
|
|
|
#ifdef _WIN32
|
2024-09-16 20:28:35 +02:00
|
|
|
bool disableAudioDucking = config_get_bool(appConfig, "Audio", "DisableAudioDucking");
|
2017-04-17 22:38:16 +09:00
|
|
|
if (disableAudioDucking)
|
|
|
|
|
DisableAudioDucking(true);
|
|
|
|
|
#endif
|
|
|
|
|
|
2016-01-25 17:09:59 -08:00
|
|
|
#ifdef __APPLE__
|
2024-09-16 20:28:35 +02:00
|
|
|
if (config_get_bool(appConfig, "Video", "DisableOSXVSync"))
|
2016-01-25 17:09:59 -08:00
|
|
|
EnableOSXVSync(false);
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-07-22 01:20:12 -07:00
|
|
|
UpdateHotkeyFocusSetting(false);
|
2018-05-06 16:48:27 -07:00
|
|
|
|
2015-06-23 19:38:01 -07:00
|
|
|
move_basic_to_profiles();
|
2015-06-23 19:29:07 -07:00
|
|
|
move_basic_to_scene_collections();
|
|
|
|
|
|
|
|
|
|
if (!MakeUserProfileDirs())
|
|
|
|
|
throw "Failed to create profile directories";
|
2013-12-22 23:40:07 -07:00
|
|
|
}
|
2013-11-22 20:43:48 -07:00
|
|
|
|
2013-12-22 23:40:07 -07:00
|
|
|
const char *OBSApp::GetRenderModule() const
|
2013-11-23 23:38:52 -07:00
|
|
|
{
|
2024-09-16 20:28:35 +02:00
|
|
|
const char *renderer = config_get_string(appConfig, "Video", "Renderer");
|
2013-12-22 23:40:07 -07:00
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
return (astrcmpi(renderer, "Direct3D 11") == 0) ? DL_D3D11 : DL_OPENGL;
|
2013-11-22 20:43:48 -07:00
|
|
|
}
|
Change the UI to Qt (work in progress)
--------------------------------------------------
Notes and details
--------------------------------------------------
Why was this done? Because wxWidgets was just lacking in many areas. I
know wxWidgets is designed to be used with native controls, and that's
great, but wxWidgets just is not a feature-complete toolkit for
multiplatform applications. It lacks in dialog editors, its code is
archaic and outdated, and I just feel frustrated every time I try to do
things with it.
Qt on the other hand.. I had to actually try Qt to realize how much
better it was as a toolkit. They've got everything from dialog editors,
to an IDE, a debugger, build tools, just everything, and it's all
top-notch and highly maintained. The focus of the toolkit is
application development, and they spend their time trying to help
people do exactly that: make programs. Great support, great tools,
and because of that, great toolkit. I just didn't want to alienate any
developers by being stubborn about native widgets.
There *are* some things that are rather lackluster about it and design
choices I disagree with though. For example, I realize that to have an
easy to use toolkit you have to have some level of code generation.
However, in my personal and humble opinion, moc just feels like a
terrible way to approach the problem. Even now I feel like there are a
variety of ways you could handle code generation and automatic
management of things like that. I don't like the idea of circumventing
the language itself like that. It feels like one giant massive hack.
--------------------------------------------------
Things that aren't working properly:
--------------------------------------------------
- Settings dialog is not implemented. The dialog is complete but the
code to handle the dialog hasn't been constructed yet.
- There is a problem with using Qt widgets as a device target on
windows, with at least OpenGL: if I have the preview widget
automatically resize itself, it seems to cause some sort of video
card failure that I don't understand.
- Because of the above, resizing the preview widget has been disabled
until I can figure out what's going on, so it's currently only a
32x32 area.
- Direct3D doesn't seem to render correctly either, seems that the
viewport is messed up or something. I'm sort of confused about
what's going on with it.
- The new main window seems to be triggering more race conditions than
the wxWidgets main window dialog did. I'm not entirely sure what's
going on here, but this may just be existing race conditions within
libobs itself that I just never spotted before (even though I tend to
be very thorough with race conditions any time I use variables
cross-thread)
2014-01-23 11:53:55 -07:00
|
|
|
|
2015-08-09 05:09:07 -07:00
|
|
|
static bool StartupOBS(const char *locale, profiler_name_store_t *store)
|
|
|
|
|
{
|
|
|
|
|
char path[512];
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
if (GetAppConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0)
|
2015-08-09 05:09:07 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return obs_startup(locale, path, store);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-06 16:48:27 -07:00
|
|
|
inline void OBSApp::ResetHotkeyState(bool inFocus)
|
|
|
|
|
{
|
2019-07-22 01:20:12 -07:00
|
|
|
obs_hotkey_enable_background_press((inFocus && enableHotkeysInFocus) || (!inFocus && enableHotkeysOutOfFocus));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBSApp::UpdateHotkeyFocusSetting(bool resetState)
|
|
|
|
|
{
|
|
|
|
|
enableHotkeysInFocus = true;
|
|
|
|
|
enableHotkeysOutOfFocus = true;
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
const char *hotkeyFocusType = config_get_string(userConfig, "General", "HotkeyFocusType");
|
2019-07-22 01:20:12 -07:00
|
|
|
|
|
|
|
|
if (astrcmpi(hotkeyFocusType, "DisableHotkeysInFocus") == 0) {
|
|
|
|
|
enableHotkeysInFocus = false;
|
|
|
|
|
} else if (astrcmpi(hotkeyFocusType, "DisableHotkeysOutOfFocus") == 0) {
|
|
|
|
|
enableHotkeysOutOfFocus = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (resetState)
|
|
|
|
|
ResetHotkeyState(applicationState() == Qt::ApplicationActive);
|
2018-05-06 16:48:27 -07:00
|
|
|
}
|
|
|
|
|
|
2019-07-22 01:20:12 -07:00
|
|
|
void OBSApp::DisableHotkeys()
|
2018-05-06 16:48:27 -07:00
|
|
|
{
|
2019-07-22 01:20:12 -07:00
|
|
|
enableHotkeysInFocus = false;
|
|
|
|
|
enableHotkeysOutOfFocus = false;
|
2019-07-22 01:17:29 -07:00
|
|
|
ResetHotkeyState(applicationState() == Qt::ApplicationActive);
|
2018-05-06 16:48:27 -07:00
|
|
|
}
|
|
|
|
|
|
2019-03-05 14:34:40 -08:00
|
|
|
void OBSApp::Exec(VoidFunc func)
|
|
|
|
|
{
|
|
|
|
|
func();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-14 10:39:24 -07:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-13 11:36:47 -07:00
|
|
|
bool OBSApp::OBSInit()
|
2014-01-25 09:08:56 -07:00
|
|
|
{
|
2015-07-11 08:04:12 +02:00
|
|
|
ProfileScope("OBSApp::OBSInit");
|
|
|
|
|
|
2022-08-20 16:51:17 +02:00
|
|
|
qRegisterMetaType<VoidFunc>("VoidFunc");
|
2019-03-05 14:34:40 -08:00
|
|
|
|
2020-03-06 17:50:41 -03:00
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
2022-04-24 12:58:49 -03:00
|
|
|
if (QApplication::platformName() == "xcb") {
|
2021-03-15 22:47:51 -07:00
|
|
|
obs_set_nix_platform(OBS_NIX_PLATFORM_X11_EGL);
|
|
|
|
|
blog(LOG_INFO, "Using EGL/X11");
|
2020-03-06 17:50:41 -03:00
|
|
|
}
|
2020-03-06 18:53:42 -03:00
|
|
|
|
|
|
|
|
#ifdef ENABLE_WAYLAND
|
|
|
|
|
if (QApplication::platformName().contains("wayland")) {
|
|
|
|
|
obs_set_nix_platform(OBS_NIX_PLATFORM_WAYLAND);
|
2021-03-15 19:40:55 -03:00
|
|
|
setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
2020-03-06 18:53:42 -03:00
|
|
|
blog(LOG_INFO, "Platform: Wayland");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-03-15 22:47:51 -07:00
|
|
|
|
|
|
|
|
QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface();
|
|
|
|
|
obs_set_nix_platform_display(native->nativeResourceForIntegration("display"));
|
2020-03-06 17:50:41 -03:00
|
|
|
#endif
|
|
|
|
|
|
2022-09-11 10:58:58 +10:00
|
|
|
#ifdef __APPLE__
|
|
|
|
|
setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
if (!StartupOBS(locale.c_str(), GetProfilerNameStore()))
|
|
|
|
|
return false;
|
2015-08-09 05:09:07 -07:00
|
|
|
|
2020-06-28 01:49:57 +02:00
|
|
|
libobs_initialized = true;
|
|
|
|
|
|
2020-03-14 10:39:24 -07:00
|
|
|
obs_set_ui_task_handler(ui_task_handler);
|
|
|
|
|
|
2020-12-11 19:09:57 +01:00
|
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
2024-10-05 23:13:09 +09:00
|
|
|
bool browserHWAccel = config_get_bool(appConfig, "General", "BrowserHWAccel");
|
2018-08-10 06:12:16 -07:00
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSDataAutoRelease settings = obs_data_create();
|
2018-09-02 04:35:12 -05:00
|
|
|
obs_data_set_bool(settings, "BrowserHWAccel", browserHWAccel);
|
|
|
|
|
obs_apply_private_data(settings);
|
2018-08-10 06:12:16 -07:00
|
|
|
|
2019-01-12 16:35:43 -08:00
|
|
|
blog(LOG_INFO, "Current Date/Time: %s", CurrentDateTimeString().c_str());
|
|
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
blog(LOG_INFO, "Browser Hardware Acceleration: %s", browserHWAccel ? "true" : "false");
|
2018-08-10 06:12:16 -07:00
|
|
|
#endif
|
2022-02-20 13:38:17 +11:00
|
|
|
#ifdef _WIN32
|
2024-09-03 16:28:36 +02:00
|
|
|
bool hideFromCapture = config_get_bool(userConfig, "BasicWindow", "HideOBSWindowsFromCapture");
|
2022-02-20 13:38:17 +11:00
|
|
|
blog(LOG_INFO, "Hide OBS windows from screen capture: %s", hideFromCapture ? "true" : "false");
|
|
|
|
|
#endif
|
2018-08-10 06:12:16 -07:00
|
|
|
|
2021-08-24 21:53:29 +10:00
|
|
|
blog(LOG_INFO, "Qt Version: %s (runtime), %s (compiled)", qVersion(), QT_VERSION_STR);
|
2018-09-02 04:35:12 -05:00
|
|
|
blog(LOG_INFO, "Portable mode: %s", portable_mode ? "true" : "false");
|
2016-10-09 20:53:45 -04:00
|
|
|
|
2023-03-13 03:09:52 +01:00
|
|
|
if (safe_mode) {
|
|
|
|
|
blog(LOG_WARNING, "Safe Mode enabled.");
|
|
|
|
|
} else if (disable_3p_plugins) {
|
|
|
|
|
blog(LOG_WARNING, "Third-party plugins disabled.");
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
setQuitOnLastWindowClosed(false);
|
2017-05-14 13:03:46 -07:00
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
mainWindow = new OBSBasic();
|
2014-07-22 20:48:35 +02:00
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
mainWindow->setAttribute(Qt::WA_DeleteOnClose, true);
|
2023-03-12 03:01:31 +01:00
|
|
|
connect(mainWindow, &OBSBasic::destroyed, this, &OBSApp::quit);
|
2014-07-22 20:48:35 +02:00
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
mainWindow->OBSInit();
|
2014-07-13 11:36:47 -07:00
|
|
|
|
2018-09-02 04:35:12 -05:00
|
|
|
connect(this, &QGuiApplication::applicationStateChanged,
|
2019-07-22 01:17:29 -07:00
|
|
|
[this](Qt::ApplicationState state) { ResetHotkeyState(state == Qt::ApplicationActive); });
|
|
|
|
|
ResetHotkeyState(applicationState() == Qt::ApplicationActive);
|
2018-09-02 04:35:12 -05:00
|
|
|
return true;
|
2014-01-25 09:08:56 -07:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 06:38:40 -06:00
|
|
|
string OBSApp::GetVersionString(bool platform) const
|
2014-05-15 14:04:18 -07:00
|
|
|
{
|
|
|
|
|
stringstream ver;
|
2014-05-18 17:44:10 -07:00
|
|
|
|
2014-05-15 19:18:13 -07:00
|
|
|
#ifdef HAVE_OBSCONFIG_H
|
2023-05-19 15:05:07 +02:00
|
|
|
ver << obs_get_version_string();
|
2014-07-14 09:51:21 -07:00
|
|
|
#else
|
|
|
|
|
ver << LIBOBS_API_MAJOR_VER << "." << LIBOBS_API_MINOR_VER << "." << LIBOBS_API_PATCH_VER;
|
2014-07-14 09:04:05 -07:00
|
|
|
|
2014-05-15 19:18:13 -07:00
|
|
|
#endif
|
|
|
|
|
|
2023-03-05 06:38:40 -06:00
|
|
|
if (platform) {
|
|
|
|
|
ver << " (";
|
2014-05-15 19:18:13 -07:00
|
|
|
#ifdef _WIN32
|
2023-03-30 17:08:30 -04:00
|
|
|
if (sizeof(void *) == 8)
|
|
|
|
|
ver << "64-bit, ";
|
|
|
|
|
else
|
|
|
|
|
ver << "32-bit, ";
|
|
|
|
|
|
2023-03-05 06:38:40 -06:00
|
|
|
ver << "windows)";
|
2014-05-18 17:44:10 -07:00
|
|
|
#elif __APPLE__
|
2023-03-05 06:38:40 -06:00
|
|
|
ver << "mac)";
|
2020-11-10 03:01:55 +03:00
|
|
|
#elif __OpenBSD__
|
2023-03-05 06:38:40 -06:00
|
|
|
ver << "openbsd)";
|
2015-05-01 22:05:00 +02:00
|
|
|
#elif __FreeBSD__
|
2023-03-05 06:38:40 -06:00
|
|
|
ver << "freebsd)";
|
2014-05-18 17:44:10 -07:00
|
|
|
#else /* assume linux for the time being */
|
2023-03-05 06:38:40 -06:00
|
|
|
ver << "linux)";
|
2014-05-18 17:44:10 -07:00
|
|
|
#endif
|
2023-03-05 06:38:40 -06:00
|
|
|
}
|
2014-05-15 19:14:19 -07:00
|
|
|
|
2014-05-15 14:04:18 -07:00
|
|
|
return ver.str();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-09 20:53:45 -04:00
|
|
|
bool OBSApp::IsPortableMode()
|
|
|
|
|
{
|
|
|
|
|
return portable_mode;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-18 21:33:41 +02:00
|
|
|
bool OBSApp::IsUpdaterDisabled()
|
|
|
|
|
{
|
|
|
|
|
return opt_disable_updater;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-13 00:00:13 -07:00
|
|
|
bool OBSApp::IsMissingFilesCheckDisabled()
|
|
|
|
|
{
|
|
|
|
|
return opt_disable_missing_files_check;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-07 17:03:34 -07:00
|
|
|
#ifdef __APPLE__
|
2014-03-07 17:19:26 -07:00
|
|
|
#define INPUT_AUDIO_SOURCE "coreaudio_input_capture"
|
|
|
|
|
#define OUTPUT_AUDIO_SOURCE "coreaudio_output_capture"
|
2014-03-07 17:03:34 -07:00
|
|
|
#elif _WIN32
|
|
|
|
|
#define INPUT_AUDIO_SOURCE "wasapi_input_capture"
|
|
|
|
|
#define OUTPUT_AUDIO_SOURCE "wasapi_output_capture"
|
|
|
|
|
#else
|
2014-03-10 23:02:37 +01:00
|
|
|
#define INPUT_AUDIO_SOURCE "pulse_input_capture"
|
|
|
|
|
#define OUTPUT_AUDIO_SOURCE "pulse_output_capture"
|
2014-03-07 17:03:34 -07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
const char *OBSApp::InputAudioSource() const
|
|
|
|
|
{
|
|
|
|
|
return INPUT_AUDIO_SOURCE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *OBSApp::OutputAudioSource() const
|
|
|
|
|
{
|
|
|
|
|
return OUTPUT_AUDIO_SOURCE;
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
const char *OBSApp::GetLastLog() const
|
|
|
|
|
{
|
|
|
|
|
return lastLogFile.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *OBSApp::GetCurrentLog() const
|
|
|
|
|
{
|
|
|
|
|
return currentLogFile.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-16 16:27:49 -07:00
|
|
|
const char *OBSApp::GetLastCrashLog() const
|
|
|
|
|
{
|
|
|
|
|
return lastCrashLogFile.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-28 14:24:14 -07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-27 02:31:08 +02:00
|
|
|
// 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) {
|
2025-01-25 20:21:42 -06:00
|
|
|
OBSBasic *main = OBSBasic::Get();
|
2020-06-27 02:31:08 +02:00
|
|
|
if (main)
|
|
|
|
|
main->SetDisplayAffinity(window);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
skip:
|
|
|
|
|
return QApplication::notify(receiver, e);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-27 12:14:18 +01:00
|
|
|
string GenerateTimeDateFilename(const char *extension, bool noSpace)
|
2014-05-14 17:47:38 -07:00
|
|
|
{
|
2014-05-20 23:26:19 -07:00
|
|
|
time_t now = time(0);
|
|
|
|
|
char file[256] = {};
|
|
|
|
|
struct tm *cur_time;
|
2014-05-18 17:44:10 -07:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
cur_time = localtime(&now);
|
2015-11-27 12:14:18 +01:00
|
|
|
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,
|
2014-05-20 23:26:19 -07:00
|
|
|
extension);
|
|
|
|
|
|
|
|
|
|
return string(file);
|
|
|
|
|
}
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2016-02-17 20:06:11 -08:00
|
|
|
string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format)
|
|
|
|
|
{
|
2016-12-05 04:32:37 -08:00
|
|
|
BPtr<char> filename = os_generate_formatted_filename(extension, !noSpace, format);
|
|
|
|
|
return string(filename);
|
2016-02-17 20:06:11 -08:00
|
|
|
}
|
|
|
|
|
|
2020-07-23 08:45:03 -05:00
|
|
|
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;
|
|
|
|
|
|
2021-05-28 22:32:46 +02:00
|
|
|
f = format;
|
|
|
|
|
|
2020-07-23 08:45:03 -05:00
|
|
|
if (prefix && *prefix) {
|
2021-05-28 22:32:46 +02:00
|
|
|
string str_prefix = prefix;
|
|
|
|
|
|
|
|
|
|
if (str_prefix.back() != ' ')
|
|
|
|
|
str_prefix += " ";
|
2020-07-23 08:45:03 -05:00
|
|
|
|
2021-05-28 22:32:46 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2020-07-23 08:45:03 -05:00
|
|
|
|
|
|
|
|
if (suffix && *suffix) {
|
|
|
|
|
if (*suffix != ' ')
|
|
|
|
|
f += " ";
|
|
|
|
|
f += suffix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remove_reserved_file_characters(f);
|
|
|
|
|
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-03 15:48:55 +02:00
|
|
|
string GetFormatExt(const char *container)
|
|
|
|
|
{
|
|
|
|
|
string ext = container;
|
|
|
|
|
if (ext == "fragmented_mp4")
|
|
|
|
|
ext = "mp4";
|
2024-04-27 03:55:47 +02:00
|
|
|
if (ext == "hybrid_mp4")
|
|
|
|
|
ext = "mp4";
|
2023-04-03 15:48:55 +02:00
|
|
|
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)
|
2020-07-23 08:45:03 -05:00
|
|
|
{
|
2025-01-25 20:21:42 -06:00
|
|
|
OBSBasic *main = OBSBasic::Get();
|
2020-07-23 08:45:03 -05:00
|
|
|
|
|
|
|
|
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 += "/";
|
|
|
|
|
|
2023-04-03 15:48:55 +02:00
|
|
|
string ext = GetFormatExt(container);
|
|
|
|
|
strPath += GenerateSpecifiedFilename(ext.c_str(), noSpace, format);
|
2020-07-23 08:45:03 -05:00
|
|
|
ensure_directory_exists(strPath);
|
|
|
|
|
if (!overwrite)
|
|
|
|
|
FindBestFilename(strPath, noSpace);
|
|
|
|
|
|
|
|
|
|
return strPath;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 20:10:35 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 19:03:30 +01:00
|
|
|
#if defined(__APPLE__) || defined(__linux__)
|
2015-06-01 15:52:23 -07:00
|
|
|
#define BASE_PATH ".."
|
|
|
|
|
#else
|
|
|
|
|
#define BASE_PATH "../.."
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define CONFIG_PATH BASE_PATH "/config"
|
|
|
|
|
|
2024-09-18 23:02:25 +02:00
|
|
|
#if defined(ENABLE_PORTABLE_CONFIG) || defined(_WIN32)
|
2022-05-07 21:17:39 -07:00
|
|
|
#define ALLOW_PORTABLE_MODE 1
|
|
|
|
|
#else
|
|
|
|
|
#define ALLOW_PORTABLE_MODE 0
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
int GetAppConfigPath(char *path, size_t size, const char *name)
|
2015-06-01 15:52:23 -07:00
|
|
|
{
|
2022-05-07 21:17:39 -07:00
|
|
|
#if ALLOW_PORTABLE_MODE
|
2022-04-29 20:51:42 +02:00
|
|
|
if (portable_mode) {
|
2015-06-01 15:52:23 -07:00
|
|
|
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);
|
|
|
|
|
}
|
2022-04-29 20:51:42 +02:00
|
|
|
#else
|
|
|
|
|
return os_get_config_path(path, size, name);
|
|
|
|
|
#endif
|
2015-06-01 15:52:23 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
char *GetAppConfigPathPtr(const char *name)
|
2015-06-01 15:52:23 -07:00
|
|
|
{
|
2022-05-07 21:17:39 -07:00
|
|
|
#if ALLOW_PORTABLE_MODE
|
2022-04-29 20:51:42 +02:00
|
|
|
if (portable_mode) {
|
2015-06-01 15:52:23 -07:00
|
|
|
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);
|
|
|
|
|
}
|
2022-04-29 20:51:42 +02:00
|
|
|
#else
|
|
|
|
|
return os_get_config_path_ptr(name);
|
|
|
|
|
#endif
|
2015-06-01 15:52:23 -07:00
|
|
|
}
|
|
|
|
|
|
2016-07-06 01:34:16 -07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-23 18:52:26 -07:00
|
|
|
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);
|
2020-02-01 21:49:17 -08:00
|
|
|
os_utf8_to_wcs(name, base_len, &wfile[0], len + 1);
|
2015-06-23 18:52:26 -07:00
|
|
|
|
|
|
|
|
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);
|
2020-02-01 22:10:32 -08:00
|
|
|
os_wcs_to_utf8(wfile.c_str(), wfile.size(), &file[0], len + 1);
|
2015-06-23 18:52:26 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-28 22:26:28 -07:00
|
|
|
bool WindowPositionValid(QRect rect)
|
2016-05-05 15:31:35 -07:00
|
|
|
{
|
2016-10-03 23:50:13 -07:00
|
|
|
for (QScreen *screen : QGuiApplication::screens()) {
|
|
|
|
|
if (screen->availableGeometry().intersects(rect))
|
2016-05-05 15:31:35 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-22 00:07:15 +01:00
|
|
|
#ifndef _WIN32
|
|
|
|
|
void OBSApp::SigIntSignalHandler(int s)
|
2019-03-31 00:59:32 -05:00
|
|
|
{
|
2023-03-22 00:07:15 +01:00
|
|
|
/* 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. */
|
2019-03-31 00:59:32 -05:00
|
|
|
UNUSED_PARAMETER(s);
|
|
|
|
|
|
2023-03-22 00:07:15 +01:00
|
|
|
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);
|
|
|
|
|
|
2025-01-25 20:21:42 -06:00
|
|
|
OBSBasic *main = OBSBasic::Get();
|
2023-05-21 19:29:49 +09:00
|
|
|
if (main)
|
|
|
|
|
main->close();
|
2023-03-22 00:07:15 +01:00
|
|
|
#endif
|
2019-03-31 00:59:32 -05:00
|
|
|
}
|
|
|
|
|
|
2023-11-28 00:02:20 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
void OBSApp::commitData(QSessionManager &manager)
|
|
|
|
|
{
|
|
|
|
|
if (auto main = App()->GetMainWindow()) {
|
|
|
|
|
QMetaObject::invokeMethod(main, "close", Qt::QueuedConnection);
|
|
|
|
|
manager.cancel();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|