2013-11-07 15:45:03 -08:00
|
|
|
/******************************************************************************
|
|
|
|
Copyright (C) 2013 by Hugh Bailey <obs.jim@gmail.com>
|
|
|
|
|
|
|
|
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 21:24:38 -08:00
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
2013-11-07 15:45:03 -08: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/>.
|
|
|
|
******************************************************************************/
|
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
#include <time.h>
|
|
|
|
#include <stdio.h>
|
2015-06-23 18:52:26 -07:00
|
|
|
#include <wchar.h>
|
2015-07-18 18:50:02 -07:00
|
|
|
#include <chrono>
|
|
|
|
#include <ratio>
|
2016-12-23 11:08:22 -08:00
|
|
|
#include <string>
|
2013-11-23 22:38:52 -08:00
|
|
|
#include <sstream>
|
2015-09-07 11:20:48 -07:00
|
|
|
#include <mutex>
|
2013-11-22 19:43:48 -08:00
|
|
|
#include <util/bmem.h>
|
2018-07-04 21:01:22 -07:00
|
|
|
#include <util/dstr.hpp>
|
2013-11-23 22:38:52 -08:00
|
|
|
#include <util/platform.h>
|
2015-07-10 23:04:12 -07:00
|
|
|
#include <util/profiler.hpp>
|
2018-07-04 21:01:22 -07:00
|
|
|
#include <util/cf-parser.h>
|
2014-05-15 19:11:33 -07:00
|
|
|
#include <obs-config.h>
|
2013-12-22 16:42:02 -08:00
|
|
|
#include <obs.hpp>
|
|
|
|
|
2016-10-03 23:50:13 -07:00
|
|
|
#include <QGuiApplication>
|
2014-01-25 10:42:55 -08:00
|
|
|
#include <QProxyStyle>
|
2016-10-03 23:50:13 -07:00
|
|
|
#include <QScreen>
|
2014-01-25 10:42:55 -08: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 10:53:55 -08:00
|
|
|
#include "qt-wrappers.hpp"
|
2013-11-22 15:25:38 -08:00
|
|
|
#include "obs-app.hpp"
|
2013-12-28 20:51:18 -08:00
|
|
|
#include "window-basic-main.hpp"
|
2015-02-16 23:45:34 -08:00
|
|
|
#include "window-basic-settings.hpp"
|
2014-07-13 11:36:47 -07:00
|
|
|
#include "window-license-agreement.hpp"
|
2015-01-02 17:11:27 -08:00
|
|
|
#include "crash-report.hpp"
|
2013-12-07 09:22:56 -08:00
|
|
|
#include "platform.hpp"
|
2013-12-06 08:16:33 -08:00
|
|
|
|
2014-04-14 04:02:11 -07:00
|
|
|
#include <fstream>
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2015-08-16 03:51:22 -07:00
|
|
|
#include <curl/curl.h>
|
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
#ifdef _WIN32
|
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 10:53:55 -08:00
|
|
|
#include <windows.h>
|
2014-04-24 14:50:40 -07:00
|
|
|
#else
|
|
|
|
#include <signal.h>
|
2014-01-23 16:00:42 -08:00
|
|
|
#endif
|
2013-11-07 15:45:03 -08:00
|
|
|
|
2017-01-11 11:19:17 -08:00
|
|
|
#include <iostream>
|
|
|
|
|
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 10:53:55 -08:00
|
|
|
using namespace std;
|
2013-11-23 22:38:52 -08:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
static log_handler_t def_log_handler;
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
static string currentLogFile;
|
|
|
|
static string lastLogFile;
|
2018-03-16 16:27:49 -07:00
|
|
|
static string lastCrashLogFile;
|
2014-05-18 17:44:10 -07:00
|
|
|
|
2017-05-14 15:25:34 -07:00
|
|
|
bool portable_mode = false;
|
2017-07-14 08:49:47 -07:00
|
|
|
static bool multi = false;
|
2016-11-03 08:10:44 -07:00
|
|
|
static bool log_verbose = false;
|
|
|
|
static bool unfiltered_log = false;
|
2016-04-13 19:00:12 -07:00
|
|
|
bool opt_start_streaming = false;
|
|
|
|
bool opt_start_recording = false;
|
2017-01-11 11:19:17 -08:00
|
|
|
bool opt_studio_mode = false;
|
|
|
|
bool opt_start_replaybuffer = false;
|
|
|
|
bool opt_minimize_tray = false;
|
2017-04-10 06:52:53 -07:00
|
|
|
bool opt_allow_opengl = false;
|
2017-04-30 07:47:19 -07:00
|
|
|
bool opt_always_on_top = false;
|
2016-04-19 18:10:09 -07:00
|
|
|
string opt_starting_collection;
|
|
|
|
string opt_starting_profile;
|
2016-04-13 18:59:27 -07:00
|
|
|
string opt_starting_scene;
|
2015-06-01 15:52:23 -07:00
|
|
|
|
2016-11-26 22:44:39 -08:00
|
|
|
// AMD PowerXpress High Performance Flags
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|
|
|
#endif
|
|
|
|
|
2014-11-01 13:48:58 -07:00
|
|
|
QObject *CreateShortcutFilter()
|
|
|
|
{
|
2015-06-29 13:26:08 -07:00
|
|
|
return new OBSEventFilter([](QObject *obj, QEvent *event)
|
2014-11-01 13:48:58 -07:00
|
|
|
{
|
|
|
|
auto mouse_event = [](QMouseEvent &event)
|
|
|
|
{
|
|
|
|
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::MidButton:
|
|
|
|
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)
|
2014-11-01 13:48:58 -07:00
|
|
|
{
|
2015-06-29 13:26:08 -07:00
|
|
|
QDialog *dialog = qobject_cast<QDialog*>(obj);
|
|
|
|
|
2014-11-01 13:48:58 -07:00
|
|
|
obs_key_combination_t hotkey = {0, OBS_KEY_NONE};
|
|
|
|
bool pressed = event->type() == QEvent::KeyPress;
|
|
|
|
|
|
|
|
switch (event->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
|
|
|
|
|
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;
|
2017-05-29 17:05:18 -07:00
|
|
|
/* Falls through. */
|
2014-11-01 13:48:58 -07:00
|
|
|
default:
|
|
|
|
hotkey.key = obs_key_from_virtual_key(
|
|
|
|
event->nativeVirtualKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
hotkey.modifiers = TranslateQtKeyboardEventModifiers(
|
|
|
|
event->modifiers());
|
|
|
|
|
|
|
|
obs_hotkey_inject_event(hotkey, pressed);
|
2015-06-29 13:26:08 -07:00
|
|
|
return true;
|
2014-11-01 13:48:58 -07: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 13:48:58 -07:00
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
string CurrentTimeString()
|
|
|
|
{
|
2015-07-18 18:50:02 -07:00
|
|
|
using namespace std::chrono;
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
struct tm tstruct;
|
|
|
|
char buf[80];
|
2015-07-18 18:50:02 -07:00
|
|
|
|
|
|
|
auto tp = system_clock::now();
|
|
|
|
auto now = system_clock::to_time_t(tp);
|
2014-05-18 17:44:10 -07:00
|
|
|
tstruct = *localtime(&now);
|
2015-07-18 18:50:02 -07:00
|
|
|
|
|
|
|
size_t written = strftime(buf, sizeof(buf), "%X", &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));
|
|
|
|
}
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-07-05 23:28:00 -07:00
|
|
|
static inline void LogString(fstream &logFile, const char *timeString,
|
|
|
|
char *str)
|
|
|
|
{
|
|
|
|
logFile << timeString << str << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void LogStringChunk(fstream &logFile, char *str)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
nextLine++;
|
|
|
|
str = nextLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
LogString(logFile, timeString.c_str(), str);
|
|
|
|
}
|
|
|
|
|
2015-09-07 11:20:48 -07:00
|
|
|
#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 char cmp_str[4096];
|
|
|
|
static int rep_count = 0;
|
|
|
|
|
|
|
|
int new_sum = sum_chars(output_str);
|
|
|
|
|
|
|
|
lock_guard<mutex> guard(log_mutex);
|
|
|
|
|
2016-11-03 08:10:44 -07:00
|
|
|
if (unfiltered_log) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-07 11:20:48 -07:00
|
|
|
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;
|
|
|
|
strcpy(cmp_str, output_str);
|
|
|
|
last_char_sum = new_sum;
|
|
|
|
rep_count = 0;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-04-14 04:02:11 -07:00
|
|
|
static void do_log(int log_level, const char *msg, va_list args, void *param)
|
|
|
|
{
|
|
|
|
fstream &logFile = *static_cast<fstream*>(param);
|
2014-05-14 17:47:38 -07:00
|
|
|
char str[4096];
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
#ifndef _WIN32
|
2014-05-22 03:59:51 -07:00
|
|
|
va_list args2;
|
2014-05-14 17:47:38 -07:00
|
|
|
va_copy(args2, args);
|
2014-05-18 17:44:10 -07:00
|
|
|
#endif
|
2013-11-23 22:38:52 -08:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
vsnprintf(str, 4095, msg, args);
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
2016-12-23 11:08:22 -08:00
|
|
|
if (IsDebuggerPresent()) {
|
|
|
|
int wNum = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
|
|
|
|
if (wNum > 1) {
|
2017-01-24 12:49:47 -08:00
|
|
|
static wstring wide_buf;
|
|
|
|
static mutex wide_mutex;
|
|
|
|
|
|
|
|
lock_guard<mutex> lock(wide_mutex);
|
2016-12-23 11:08:22 -08:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
2014-05-14 17:47:38 -07:00
|
|
|
#else
|
|
|
|
def_log_handler(log_level, msg, args2, nullptr);
|
|
|
|
#endif
|
2013-11-23 22:38:52 -08:00
|
|
|
|
2017-05-14 15:24:15 -07:00
|
|
|
if (log_level <= LOG_INFO || log_verbose) {
|
|
|
|
if (too_many_repeated_entries(logFile, msg, str))
|
|
|
|
return;
|
2015-07-05 23:28:00 -07:00
|
|
|
LogStringChunk(logFile, str);
|
2017-05-14 15:24:15 -07:00
|
|
|
}
|
2014-04-14 04:02:11 -07:00
|
|
|
|
2016-04-19 17:56:10 -07:00
|
|
|
#if defined(_WIN32) && defined(OBS_DEBUGBREAK_ON_ERROR)
|
2014-03-10 13:10:35 -07:00
|
|
|
if (log_level <= LOG_ERROR && IsDebuggerPresent())
|
2013-11-23 22:38:52 -08:00
|
|
|
__debugbreak();
|
2014-04-14 04:02:11 -07:00
|
|
|
#endif
|
2014-05-14 17:47:38 -07:00
|
|
|
}
|
2013-11-23 22:38:52 -08:00
|
|
|
|
2014-07-11 11:00:34 -07:00
|
|
|
#define DEFAULT_LANG "en-US"
|
|
|
|
|
2013-12-15 23:07:08 -08:00
|
|
|
bool OBSApp::InitGlobalConfigDefaults()
|
2013-11-23 22:38:52 -08:00
|
|
|
{
|
2014-07-11 11:00:34 -07:00
|
|
|
config_set_default_string(globalConfig, "General", "Language",
|
|
|
|
DEFAULT_LANG);
|
2014-05-14 17:47:38 -07:00
|
|
|
config_set_default_uint(globalConfig, "General", "MaxLogs", 10);
|
2018-07-27 21:35:08 -07:00
|
|
|
config_set_default_int(globalConfig, "General", "InfoIncrement", -1);
|
2016-07-01 10:27:27 -07:00
|
|
|
config_set_default_string(globalConfig, "General", "ProcessPriority",
|
|
|
|
"Normal");
|
2017-02-20 04:46:29 -08:00
|
|
|
config_set_default_bool(globalConfig, "General", "EnableAutoUpdates",
|
|
|
|
true);
|
2013-12-15 23:07:08 -08:00
|
|
|
|
2013-12-17 01:08:41 -08:00
|
|
|
#if _WIN32
|
|
|
|
config_set_default_string(globalConfig, "Video", "Renderer",
|
|
|
|
"Direct3D 11");
|
|
|
|
#else
|
2014-03-06 20:08:12 -08:00
|
|
|
config_set_default_string(globalConfig, "Video", "Renderer", "OpenGL");
|
2013-12-17 01:08:41 -08:00
|
|
|
#endif
|
|
|
|
|
2015-04-02 21:35:46 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow", "PreviewEnabled",
|
|
|
|
true);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"PreviewProgramMode", false);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SceneDuplicationMode", true);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SwapScenesMode", true);
|
2016-04-06 18:03:29 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SnappingEnabled", true);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"ScreenSnapping", true);
|
2016-04-06 18:13:56 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SourceSnapping", true);
|
2016-04-06 18:10:39 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"CenterSnapping", false);
|
2016-04-06 18:03:29 -07:00
|
|
|
config_set_default_double(globalConfig, "BasicWindow",
|
|
|
|
"SnapDistance", 10.0);
|
2016-06-16 10:59:36 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"RecordWhenStreaming", false);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"KeepRecordingWhenStreamStops", false);
|
2016-08-13 07:36:17 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SysTrayEnabled", true);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SysTrayWhenStarted", false);
|
2016-12-29 07:21:53 -08:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"SaveProjectors", false);
|
2016-06-30 14:52:56 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"ShowTransitions", true);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"ShowListboxToolbars", true);
|
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"ShowStatusBar", true);
|
2016-01-25 17:09:59 -08:00
|
|
|
|
2018-01-08 17:40:24 -08:00
|
|
|
if (!config_get_bool(globalConfig, "General", "Pre21Defaults")) {
|
|
|
|
config_set_default_string(globalConfig, "General",
|
|
|
|
"CurrentTheme", "Dark");
|
|
|
|
}
|
|
|
|
|
2018-04-27 19:49:48 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"VerticalVolControl", false);
|
|
|
|
|
2018-03-16 14:55:56 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"MultiviewMouseSwitch", true);
|
|
|
|
|
2018-03-16 19:00:32 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"MultiviewDrawNames", true);
|
|
|
|
|
2018-03-18 19:02:27 -07:00
|
|
|
config_set_default_bool(globalConfig, "BasicWindow",
|
|
|
|
"MultiviewDrawAreas", true);
|
|
|
|
|
2017-04-17 06:38:16 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
config_set_default_bool(globalConfig, "Audio", "DisableAudioDucking",
|
|
|
|
true);
|
|
|
|
#endif
|
|
|
|
|
2016-01-25 17:09:59 -08:00
|
|
|
#ifdef __APPLE__
|
|
|
|
config_set_default_bool(globalConfig, "Video", "DisableOSXVSync", true);
|
|
|
|
config_set_default_bool(globalConfig, "Video", "ResetOSXVSyncOnExit",
|
|
|
|
true);
|
|
|
|
#endif
|
2013-12-15 23:07:08 -08:00
|
|
|
return true;
|
2013-11-23 22:38:52 -08: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 09:22:56 -08:00
|
|
|
OBSErrorBox(NULL, "Failed to create directory %s", path);
|
2013-11-23 22:38:52 -08: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
|
|
|
|
2015-06-01 16:11:57 -07:00
|
|
|
if (GetConfigPath(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 20:08:12 -08:00
|
|
|
return false;
|
|
|
|
|
2015-06-01 16:11:57 -07:00
|
|
|
if (GetConfigPath(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 20:08:12 -08:00
|
|
|
return false;
|
2015-08-01 00:30:02 -07:00
|
|
|
|
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/profiler_data") <= 0)
|
|
|
|
return false;
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
return false;
|
|
|
|
|
2015-03-27 14:29:37 -07:00
|
|
|
#ifdef _WIN32
|
2015-06-01 16:11:57 -07:00
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/crashes") <= 0)
|
2015-03-27 14:29:37 -07:00
|
|
|
return false;
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
return false;
|
2017-02-20 04:46:29 -08:00
|
|
|
|
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/updates") <= 0)
|
|
|
|
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
|
|
|
|
2015-08-09 05:09:07 -07:00
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0)
|
|
|
|
return false;
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
return false;
|
2015-03-27 14:29:37 -07:00
|
|
|
|
2014-03-06 20:08:12 -08:00
|
|
|
return true;
|
2013-11-23 22:38:52 -08:00
|
|
|
}
|
|
|
|
|
2015-06-23 19:29:07 -07:00
|
|
|
static bool MakeUserProfileDirs()
|
|
|
|
{
|
|
|
|
char path[512];
|
|
|
|
|
2015-06-23 19:38:01 -07:00
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0)
|
|
|
|
return false;
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
return false;
|
|
|
|
|
2015-06-23 19:29:07 -07:00
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0)
|
|
|
|
return false;
|
|
|
|
if (!do_mkdir(path))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-19 18:10:09 -07:00
|
|
|
static string GetProfileDirFromName(const char *name)
|
|
|
|
{
|
|
|
|
string outputPath;
|
|
|
|
os_glob_t *glob;
|
|
|
|
char path[512];
|
|
|
|
|
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0)
|
|
|
|
return outputPath;
|
|
|
|
|
2016-10-20 12:41:56 -07:00
|
|
|
strcat(path, "/*");
|
2016-04-19 18:10:09 -07:00
|
|
|
|
|
|
|
if (os_glob(path, 0, &glob) != 0)
|
|
|
|
return outputPath;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < glob->gl_pathc; i++) {
|
|
|
|
struct os_globent ent = glob->gl_pathv[i];
|
|
|
|
if (!ent.directory)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
strcpy(path, ent.path);
|
|
|
|
strcat(path, "/basic.ini");
|
|
|
|
|
|
|
|
ConfigFile config;
|
|
|
|
if (config.Open(path, CONFIG_OPEN_EXISTING) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const char *curName = config_get_string(config, "General",
|
|
|
|
"Name");
|
|
|
|
if (astrcmpi(curName, name) == 0) {
|
|
|
|
outputPath = ent.path;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
os_globfree(glob);
|
|
|
|
|
|
|
|
if (!outputPath.empty()) {
|
|
|
|
replace(outputPath.begin(), outputPath.end(), '\\', '/');
|
|
|
|
const char *start = strrchr(outputPath.c_str(), '/');
|
|
|
|
if (start)
|
|
|
|
outputPath.erase(0, start - outputPath.c_str() + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return outputPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
static string GetSceneCollectionFileFromName(const char *name)
|
|
|
|
{
|
|
|
|
string outputPath;
|
|
|
|
os_glob_t *glob;
|
|
|
|
char path[512];
|
|
|
|
|
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0)
|
|
|
|
return outputPath;
|
|
|
|
|
|
|
|
strcat(path, "/*.json");
|
|
|
|
|
|
|
|
if (os_glob(path, 0, &glob) != 0)
|
|
|
|
return outputPath;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < glob->gl_pathc; i++) {
|
|
|
|
struct os_globent ent = glob->gl_pathv[i];
|
|
|
|
if (ent.directory)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
obs_data_t *data =
|
|
|
|
obs_data_create_from_json_file_safe(ent.path, "bak");
|
|
|
|
const char *curName = obs_data_get_string(data, "name");
|
|
|
|
|
|
|
|
if (astrcmpi(name, curName) == 0) {
|
|
|
|
outputPath = ent.path;
|
|
|
|
obs_data_release(data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
obs_data_release(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
os_globfree(glob);
|
|
|
|
|
|
|
|
if (!outputPath.empty()) {
|
|
|
|
outputPath.resize(outputPath.size() - 5);
|
|
|
|
replace(outputPath.begin(), outputPath.end(), '\\', '/');
|
|
|
|
const char *start = strrchr(outputPath.c_str(), '/');
|
|
|
|
if (start)
|
|
|
|
outputPath.erase(0, start - outputPath.c_str() + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return outputPath;
|
|
|
|
}
|
|
|
|
|
2018-05-09 01:52:56 -07:00
|
|
|
bool OBSApp::UpdatePre22MultiviewLayout(const char *layout)
|
|
|
|
{
|
|
|
|
if (!layout)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "horizontaltop") == 0) {
|
|
|
|
config_set_int(globalConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 17:41:39 -07:00
|
|
|
static_cast<int>(
|
|
|
|
MultiviewLayout::HORIZONTAL_TOP_8_SCENES));
|
2018-05-09 01:52:56 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "horizontalbottom") == 0) {
|
|
|
|
config_set_int(globalConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 17:41:39 -07:00
|
|
|
static_cast<int>(
|
|
|
|
MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES));
|
2018-05-09 01:52:56 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "verticalleft") == 0) {
|
|
|
|
config_set_int(globalConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 17:41:39 -07:00
|
|
|
static_cast<int>(
|
|
|
|
MultiviewLayout::VERTICAL_LEFT_8_SCENES));
|
2018-05-09 01:52:56 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (astrcmpi(layout, "verticalright") == 0) {
|
|
|
|
config_set_int(globalConfig, "BasicWindow", "MultiviewLayout",
|
2018-03-20 17:41:39 -07:00
|
|
|
static_cast<int>(
|
|
|
|
MultiviewLayout::VERTICAL_RIGHT_8_SCENES));
|
2018-05-09 01:52:56 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-11-23 22:38:52 -08:00
|
|
|
bool OBSApp::InitGlobalConfig()
|
|
|
|
{
|
2015-01-15 23:44:38 -08:00
|
|
|
char path[512];
|
2017-04-28 18:02:03 -07:00
|
|
|
bool changed = false;
|
2015-01-15 23:44:38 -08:00
|
|
|
|
2015-06-01 16:11:57 -07:00
|
|
|
int len = GetConfigPath(path, sizeof(path),
|
2015-01-15 23:44:38 -08:00
|
|
|
"obs-studio/global.ini");
|
|
|
|
if (len <= 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-11-23 22:38:52 -08:00
|
|
|
|
2013-12-23 17:59:54 -08:00
|
|
|
int errorcode = globalConfig.Open(path, CONFIG_OPEN_ALWAYS);
|
2013-11-23 22:38:52 -08:00
|
|
|
if (errorcode != CONFIG_SUCCESS) {
|
2013-12-07 09:22:56 -08:00
|
|
|
OBSErrorBox(NULL, "Failed to open global.ini: %d", errorcode);
|
2013-11-23 22:38:52 -08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-19 18:10:09 -07:00
|
|
|
if (!opt_starting_collection.empty()) {
|
|
|
|
string path = GetSceneCollectionFileFromName(
|
|
|
|
opt_starting_collection.c_str());
|
|
|
|
if (!path.empty()) {
|
|
|
|
config_set_string(globalConfig,
|
|
|
|
"Basic", "SceneCollection",
|
|
|
|
opt_starting_collection.c_str());
|
|
|
|
config_set_string(globalConfig,
|
|
|
|
"Basic", "SceneCollectionFile",
|
|
|
|
path.c_str());
|
2017-04-28 18:02:03 -07:00
|
|
|
changed = true;
|
2016-04-19 18:10:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!opt_starting_profile.empty()) {
|
|
|
|
string path = GetProfileDirFromName(
|
|
|
|
opt_starting_profile.c_str());
|
|
|
|
if (!path.empty()) {
|
|
|
|
config_set_string(globalConfig, "Basic", "Profile",
|
|
|
|
opt_starting_profile.c_str());
|
|
|
|
config_set_string(globalConfig, "Basic", "ProfileDir",
|
|
|
|
path.c_str());
|
2017-04-28 18:02:03 -07:00
|
|
|
changed = true;
|
2016-04-19 18:10:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-14 13:12:14 -07:00
|
|
|
if (!config_has_user_value(globalConfig, "General", "Pre19Defaults")) {
|
2017-04-28 18:02:03 -07:00
|
|
|
uint32_t lastVersion = config_get_int(globalConfig, "General",
|
|
|
|
"LastVersion");
|
|
|
|
bool useOldDefaults = lastVersion &&
|
|
|
|
lastVersion < MAKE_SEMANTIC_VERSION(19, 0, 0);
|
|
|
|
|
|
|
|
config_set_bool(globalConfig, "General", "Pre19Defaults",
|
|
|
|
useOldDefaults);
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
2018-01-08 17:40:24 -08:00
|
|
|
if (!config_has_user_value(globalConfig, "General", "Pre21Defaults")) {
|
|
|
|
uint32_t lastVersion = config_get_int(globalConfig, "General",
|
|
|
|
"LastVersion");
|
|
|
|
bool useOldDefaults = lastVersion &&
|
|
|
|
lastVersion < MAKE_SEMANTIC_VERSION(21, 0, 0);
|
|
|
|
|
|
|
|
config_set_bool(globalConfig, "General", "Pre21Defaults",
|
|
|
|
useOldDefaults);
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
2018-05-09 01:52:56 -07:00
|
|
|
if (config_has_user_value(globalConfig, "BasicWindow",
|
|
|
|
"MultiviewLayout")) {
|
|
|
|
const char *layout = config_get_string(globalConfig,
|
|
|
|
"BasicWindow", "MultiviewLayout");
|
|
|
|
changed |= UpdatePre22MultiviewLayout(layout);
|
|
|
|
}
|
|
|
|
|
2017-04-28 18:02:03 -07:00
|
|
|
if (changed)
|
|
|
|
config_save_safe(globalConfig, "tmp", nullptr);
|
|
|
|
|
2013-12-23 17:59:54 -08:00
|
|
|
return InitGlobalConfigDefaults();
|
2013-11-23 22:38:52 -08:00
|
|
|
}
|
2013-11-07 15:45:03 -08:00
|
|
|
|
2013-12-07 09:22:56 -08:00
|
|
|
bool OBSApp::InitLocale()
|
|
|
|
{
|
2015-07-10 23:04:12 -07:00
|
|
|
ProfileScope("OBSApp::InitLocale");
|
2013-12-07 09:22:56 -08:00
|
|
|
const char *lang = config_get_string(globalConfig, "General",
|
|
|
|
"Language");
|
|
|
|
|
2013-12-30 05:56:39 -08:00
|
|
|
locale = lang;
|
|
|
|
|
2013-12-17 12:56:28 -08: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 12:56:28 -08: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 11:33:53 -07:00
|
|
|
bool userLocale = config_has_user_value(globalConfig, "General",
|
|
|
|
"Language");
|
|
|
|
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_;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-12-17 12:56:28 -08:00
|
|
|
return true;
|
2014-07-11 11:33:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
stringstream file;
|
|
|
|
file << "locale/" << lang << ".ini";
|
2013-12-17 12:56:28 -08:00
|
|
|
|
2013-12-07 09:22:56 -08:00
|
|
|
string path;
|
2013-12-17 12:56:28 -08:00
|
|
|
if (GetDataFilePath(file.str().c_str(), path)) {
|
|
|
|
if (!text_lookup_add(textLookup, path.c_str()))
|
2014-02-28 19:02:29 -08:00
|
|
|
blog(LOG_ERROR, "Failed to add locale file '%s'",
|
2013-12-17 12:56:28 -08:00
|
|
|
path.c_str());
|
|
|
|
} else {
|
2014-02-28 19:02:29 -08:00
|
|
|
blog(LOG_ERROR, "Could not find locale file '%s'",
|
2013-12-17 12:56:28 -08:00
|
|
|
file.str().c_str());
|
2013-12-07 09:22:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-04 21:01:22 -07:00
|
|
|
void OBSApp::AddExtraThemeColor(QPalette &pal, int group,
|
|
|
|
const char *name, uint32_t color)
|
|
|
|
{
|
|
|
|
std::function<void(QPalette::ColorGroup)> func;
|
|
|
|
|
|
|
|
#define DEF_PALETTE_ASSIGN(name) \
|
|
|
|
do { \
|
|
|
|
func = [&] (QPalette::ColorGroup group) \
|
|
|
|
{ \
|
|
|
|
pal.setColor(group, QPalette::name, \
|
|
|
|
QColor::fromRgb(color)); \
|
|
|
|
}; \
|
|
|
|
} while (false)
|
|
|
|
|
|
|
|
if (astrcmpi(name, "alternateBase") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(AlternateBase);
|
|
|
|
} else if (astrcmpi(name, "base") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Base);
|
|
|
|
} else if (astrcmpi(name, "brightText") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(BrightText);
|
|
|
|
} else if (astrcmpi(name, "button") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Button);
|
|
|
|
} else if (astrcmpi(name, "buttonText") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(ButtonText);
|
|
|
|
} else if (astrcmpi(name, "brightText") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(BrightText);
|
|
|
|
} else if (astrcmpi(name, "dark") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Dark);
|
|
|
|
} else if (astrcmpi(name, "highlight") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Highlight);
|
|
|
|
} else if (astrcmpi(name, "highlightedText") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(HighlightedText);
|
|
|
|
} else if (astrcmpi(name, "light") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Light);
|
|
|
|
} else if (astrcmpi(name, "link") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Link);
|
|
|
|
} else if (astrcmpi(name, "linkVisited") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(LinkVisited);
|
|
|
|
} else if (astrcmpi(name, "mid") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Mid);
|
|
|
|
} else if (astrcmpi(name, "midlight") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Midlight);
|
|
|
|
} else if (astrcmpi(name, "shadow") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Shadow);
|
|
|
|
} else if (astrcmpi(name, "text") == 0 ||
|
|
|
|
astrcmpi(name, "foreground") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Text);
|
|
|
|
} else if (astrcmpi(name, "toolTipBase") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(ToolTipBase);
|
|
|
|
} else if (astrcmpi(name, "toolTipText") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(ToolTipText);
|
|
|
|
} else if (astrcmpi(name, "windowText") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(WindowText);
|
|
|
|
} else if (astrcmpi(name, "window") == 0 ||
|
|
|
|
astrcmpi(name, "background") == 0) {
|
|
|
|
DEF_PALETTE_ASSIGN(Window);
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef DEF_PALETTE_ASSIGN
|
|
|
|
|
|
|
|
switch (group) {
|
|
|
|
case QPalette::Disabled:
|
|
|
|
case QPalette::Active:
|
|
|
|
case QPalette::Inactive:
|
|
|
|
func((QPalette::ColorGroup)group);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
func((QPalette::ColorGroup)QPalette::Disabled);
|
|
|
|
func((QPalette::ColorGroup)QPalette::Active);
|
|
|
|
func((QPalette::ColorGroup)QPalette::Inactive);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct CFParser {
|
|
|
|
cf_parser cfp = {};
|
|
|
|
inline ~CFParser() {cf_parser_free(&cfp);}
|
|
|
|
inline operator cf_parser*() {return &cfp;}
|
|
|
|
inline cf_parser *operator->() {return &cfp;}
|
|
|
|
};
|
|
|
|
|
|
|
|
void OBSApp::ParseExtraThemeData(const char *path)
|
|
|
|
{
|
|
|
|
BPtr<char> data = os_quick_read_utf8_file(path);
|
|
|
|
QPalette pal = palette();
|
|
|
|
CFParser cfp;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cf_parser_parse(cfp, data, path);
|
|
|
|
|
|
|
|
while (cf_go_to_token(cfp, "OBSTheme", nullptr)) {
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
int group = -1;
|
|
|
|
|
|
|
|
if (cf_token_is(cfp, ":")) {
|
|
|
|
ret = cf_next_token_should_be(cfp, ":", nullptr,
|
|
|
|
nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS) continue;
|
|
|
|
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
if (cf_token_is(cfp, "disabled")) {
|
|
|
|
group = QPalette::Disabled;
|
|
|
|
} else if (cf_token_is(cfp, "active")) {
|
|
|
|
group = QPalette::Active;
|
|
|
|
} else if (cf_token_is(cfp, "inactive")) {
|
|
|
|
group = QPalette::Inactive;
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cf_token_is(cfp, "{")) continue;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
ret = cf_token_is_type(cfp, CFTOKEN_NAME, "name",
|
|
|
|
nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS)
|
|
|
|
break;
|
|
|
|
|
|
|
|
DStr name;
|
|
|
|
dstr_copy_strref(name, &cfp->cur_token->str);
|
|
|
|
|
|
|
|
ret = cf_next_token_should_be(cfp, ":", ";",
|
|
|
|
nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS) continue;
|
|
|
|
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
const char *array;
|
|
|
|
uint32_t color = 0;
|
|
|
|
|
|
|
|
if (cf_token_is(cfp, "#")) {
|
|
|
|
array = cfp->cur_token->str.array;
|
|
|
|
color = strtol(array + 1, nullptr, 16);
|
|
|
|
|
|
|
|
} else if (cf_token_is(cfp, "rgb")) {
|
|
|
|
ret = cf_next_token_should_be(cfp, "(", ";",
|
|
|
|
nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS) continue;
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
array = cfp->cur_token->str.array;
|
|
|
|
color |= strtol(array, nullptr, 10) << 16;
|
|
|
|
|
|
|
|
ret = cf_next_token_should_be(cfp, ",", ";",
|
|
|
|
nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS) continue;
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
array = cfp->cur_token->str.array;
|
|
|
|
color |= strtol(array, nullptr, 10) << 8;
|
|
|
|
|
|
|
|
ret = cf_next_token_should_be(cfp, ",", ";",
|
|
|
|
nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS) continue;
|
|
|
|
if (!cf_next_token(cfp)) return;
|
|
|
|
|
|
|
|
array = cfp->cur_token->str.array;
|
|
|
|
color |= strtol(array, nullptr, 10);
|
|
|
|
|
|
|
|
} else if (cf_token_is(cfp, "white")) {
|
|
|
|
color = 0xFFFFFF;
|
|
|
|
|
|
|
|
} else if (cf_token_is(cfp, "black")) {
|
|
|
|
color = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cf_go_to_token(cfp, ";", nullptr)) return;
|
|
|
|
|
|
|
|
AddExtraThemeColor(pal, group, name->array, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = cf_token_should_be(cfp, "}", "}", nullptr);
|
|
|
|
if (ret != PARSE_SUCCESS) continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
setPalette(pal);
|
|
|
|
}
|
|
|
|
|
2015-02-16 23:45:34 -08:00
|
|
|
bool OBSApp::SetTheme(std::string name, std::string path)
|
|
|
|
{
|
|
|
|
theme = name;
|
|
|
|
|
|
|
|
/* Check user dir first, then preinstalled themes. */
|
|
|
|
if (path == "") {
|
|
|
|
char userDir[512];
|
|
|
|
name = "themes/" + name + ".qss";
|
|
|
|
string temp = "obs-studio/" + name;
|
2015-06-01 16:11:57 -07:00
|
|
|
int ret = GetConfigPath(userDir, sizeof(userDir),
|
2015-02-16 23:45:34 -08:00
|
|
|
temp.c_str());
|
|
|
|
|
|
|
|
if (ret > 0 && QFile::exists(userDir)) {
|
|
|
|
path = string(userDir);
|
|
|
|
} else if (!GetDataFilePath(name.c_str(), path)) {
|
|
|
|
OBSErrorBox(NULL, "Failed to find %s.", name.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString mpath = QString("file:///") + path.c_str();
|
2018-07-04 21:01:22 -07:00
|
|
|
setPalette(defaultPalette);
|
2015-02-16 23:45:34 -08:00
|
|
|
setStyleSheet(mpath);
|
2018-07-04 21:01:22 -07:00
|
|
|
ParseExtraThemeData(path.c_str());
|
|
|
|
|
2018-07-04 21:05:13 -07:00
|
|
|
emit StyleChanged();
|
2015-02-16 23:45:34 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool OBSApp::InitTheme()
|
|
|
|
{
|
2018-07-04 21:01:22 -07:00
|
|
|
defaultPalette = palette();
|
|
|
|
|
2015-02-16 23:45:34 -08:00
|
|
|
const char *themeName = config_get_string(globalConfig, "General",
|
2017-07-02 13:25:20 -07:00
|
|
|
"CurrentTheme");
|
|
|
|
if (!themeName) {
|
|
|
|
/* Use deprecated "Theme" value if available */
|
|
|
|
themeName = config_get_string(globalConfig,
|
|
|
|
"General", "Theme");
|
|
|
|
if (!themeName)
|
|
|
|
themeName = "Default";
|
|
|
|
}
|
2015-02-16 23:45:34 -08:00
|
|
|
|
2017-06-26 23:16:08 -07:00
|
|
|
if (strcmp(themeName, "Default") != 0 && SetTheme(themeName))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return SetTheme("Default");
|
2015-02-16 23:45:34 -08:00
|
|
|
}
|
|
|
|
|
2015-07-10 23:03:31 -07: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
|
|
|
{
|
|
|
|
sleepInhibitor = os_inhibit_sleep_create("OBS Video/audio");
|
|
|
|
}
|
|
|
|
|
|
|
|
OBSApp::~OBSApp()
|
|
|
|
{
|
2017-04-17 06:38:16 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
bool disableAudioDucking = config_get_bool(globalConfig, "Audio",
|
|
|
|
"DisableAudioDucking");
|
|
|
|
if (disableAudioDucking)
|
|
|
|
DisableAudioDucking(false);
|
|
|
|
#endif
|
|
|
|
|
2016-01-25 17:09:59 -08:00
|
|
|
#ifdef __APPLE__
|
|
|
|
bool vsyncDiabled = config_get_bool(globalConfig, "Video",
|
|
|
|
"DisableOSXVSync");
|
|
|
|
bool resetVSync = config_get_bool(globalConfig, "Video",
|
|
|
|
"ResetOSXVSyncOnExit");
|
|
|
|
if (vsyncDiabled && resetVSync)
|
|
|
|
EnableOSXVSync(true);
|
|
|
|
#endif
|
|
|
|
|
2015-11-16 09:08:55 -08:00
|
|
|
os_inhibit_sleep_set_active(sleepInhibitor, false);
|
|
|
|
os_inhibit_sleep_destroy(sleepInhibitor);
|
|
|
|
}
|
2014-07-12 12:24:30 -07:00
|
|
|
|
2015-06-23 19:38:01 -07:00
|
|
|
static void move_basic_to_profiles(void)
|
|
|
|
{
|
|
|
|
char path[512];
|
|
|
|
char new_path[512];
|
|
|
|
os_glob_t *glob;
|
|
|
|
|
|
|
|
/* if not first time use */
|
|
|
|
if (GetConfigPath(path, 512, "obs-studio/basic") <= 0)
|
|
|
|
return;
|
|
|
|
if (!os_file_exists(path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* if the profiles directory doesn't already exist */
|
|
|
|
if (GetConfigPath(new_path, 512, "obs-studio/basic/profiles") <= 0)
|
|
|
|
return;
|
|
|
|
if (os_file_exists(new_path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (os_mkdir(new_path) == MKDIR_ERROR)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strcat(new_path, "/");
|
|
|
|
strcat(new_path, Str("Untitled"));
|
|
|
|
if (os_mkdir(new_path) == MKDIR_ERROR)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strcat(path, "/*.*");
|
|
|
|
if (os_glob(path, 0, &glob) != 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strcpy(path, new_path);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < glob->gl_pathc; i++) {
|
|
|
|
struct os_globent ent = glob->gl_pathv[i];
|
|
|
|
char *file;
|
|
|
|
|
|
|
|
if (ent.directory)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
file = strrchr(ent.path, '/');
|
|
|
|
if (!file++)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (astrcmpi(file, "scenes.json") == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
strcpy(new_path, path);
|
|
|
|
strcat(new_path, "/");
|
|
|
|
strcat(new_path, file);
|
|
|
|
os_rename(ent.path, new_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
os_globfree(glob);
|
|
|
|
}
|
|
|
|
|
2015-06-23 19:29:07 -07:00
|
|
|
static void move_basic_to_scene_collections(void)
|
|
|
|
{
|
|
|
|
char path[512];
|
|
|
|
char new_path[512];
|
|
|
|
|
|
|
|
if (GetConfigPath(path, 512, "obs-studio/basic") <= 0)
|
|
|
|
return;
|
|
|
|
if (!os_file_exists(path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (GetConfigPath(new_path, 512, "obs-studio/basic/scenes") <= 0)
|
|
|
|
return;
|
|
|
|
if (os_file_exists(new_path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (os_mkdir(new_path) == MKDIR_ERROR)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strcat(path, "/scenes.json");
|
|
|
|
strcat(new_path, "/");
|
|
|
|
strcat(new_path, Str("Untitled"));
|
|
|
|
strcat(new_path, ".json");
|
|
|
|
|
|
|
|
os_rename(path, new_path);
|
|
|
|
}
|
|
|
|
|
2014-07-12 12:24:30 -07:00
|
|
|
void OBSApp::AppInit()
|
2013-11-07 15:45:03 -08:00
|
|
|
{
|
2015-07-10 23:04:12 -07:00
|
|
|
ProfileScope("OBSApp::AppInit");
|
|
|
|
|
2014-01-08 16:17:30 -08:00
|
|
|
if (!InitApplicationBundle())
|
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 10:53:55 -08:00
|
|
|
throw "Failed to initialize application bundle";
|
2013-11-23 22:38:52 -08:00
|
|
|
if (!MakeUserDirs())
|
2015-06-23 19:37:24 -07:00
|
|
|
throw "Failed to create required user directories";
|
2013-11-23 22:38:52 -08: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 10:53:55 -08:00
|
|
|
throw "Failed to initialize global config";
|
2013-12-07 09:22:56 -08: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 10:53:55 -08:00
|
|
|
throw "Failed to load locale";
|
2015-02-16 23:45:34 -08:00
|
|
|
if (!InitTheme())
|
|
|
|
throw "Failed to load theme";
|
2015-06-23 19:38:01 -07:00
|
|
|
|
|
|
|
config_set_default_string(globalConfig, "Basic", "Profile",
|
|
|
|
Str("Untitled"));
|
|
|
|
config_set_default_string(globalConfig, "Basic", "ProfileDir",
|
|
|
|
Str("Untitled"));
|
2015-06-23 19:29:07 -07:00
|
|
|
config_set_default_string(globalConfig, "Basic", "SceneCollection",
|
|
|
|
Str("Untitled"));
|
|
|
|
config_set_default_string(globalConfig, "Basic", "SceneCollectionFile",
|
|
|
|
Str("Untitled"));
|
|
|
|
|
2017-04-17 06:38:16 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
bool disableAudioDucking = config_get_bool(globalConfig, "Audio",
|
|
|
|
"DisableAudioDucking");
|
|
|
|
if (disableAudioDucking)
|
|
|
|
DisableAudioDucking(true);
|
|
|
|
#endif
|
|
|
|
|
2016-01-25 17:09:59 -08:00
|
|
|
#ifdef __APPLE__
|
|
|
|
if (config_get_bool(globalConfig, "Video", "DisableOSXVSync"))
|
|
|
|
EnableOSXVSync(false);
|
|
|
|
#endif
|
|
|
|
|
2018-05-06 16:48:27 -07:00
|
|
|
enableHotkeysInFocus = !config_get_bool(globalConfig, "General",
|
|
|
|
"DisableHotkeysInFocus");
|
|
|
|
|
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 22:40:07 -08:00
|
|
|
}
|
2013-11-22 19:43:48 -08:00
|
|
|
|
2013-12-22 22:40:07 -08:00
|
|
|
const char *OBSApp::GetRenderModule() const
|
2013-11-23 22:38:52 -08:00
|
|
|
{
|
2013-12-22 22:40:07 -08:00
|
|
|
const char *renderer = config_get_string(globalConfig, "Video",
|
|
|
|
"Renderer");
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
return (astrcmpi(renderer, "Direct3D 11") == 0) ?
|
2015-01-09 11:19:22 -08:00
|
|
|
DL_D3D11 : DL_OPENGL;
|
2013-11-22 19:43:48 -08: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 10:53:55 -08:00
|
|
|
|
2015-08-09 05:09:07 -07:00
|
|
|
static bool StartupOBS(const char *locale, profiler_name_store_t *store)
|
|
|
|
{
|
|
|
|
char path[512];
|
|
|
|
|
|
|
|
if (GetConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return obs_startup(locale, path, store);
|
|
|
|
}
|
|
|
|
|
2018-05-06 16:48:27 -07:00
|
|
|
inline void OBSApp::ResetHotkeyState(bool inFocus)
|
|
|
|
{
|
|
|
|
obs_hotkey_enable_background_press(
|
|
|
|
inFocus || enableHotkeysInFocus);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OBSApp::EnableInFocusHotkeys(bool enable)
|
|
|
|
{
|
|
|
|
enableHotkeysInFocus = enable;
|
|
|
|
ResetHotkeyState(applicationState() != Qt::ApplicationActive);
|
|
|
|
}
|
|
|
|
|
2014-07-13 11:36:47 -07:00
|
|
|
bool OBSApp::OBSInit()
|
2014-01-25 08:08:56 -08:00
|
|
|
{
|
2015-07-10 23:04:12 -07:00
|
|
|
ProfileScope("OBSApp::OBSInit");
|
|
|
|
|
2014-07-13 11:36:47 -07:00
|
|
|
bool licenseAccepted = config_get_bool(globalConfig, "General",
|
|
|
|
"LicenseAccepted");
|
|
|
|
OBSLicenseAgreement agreement(nullptr);
|
|
|
|
|
|
|
|
if (licenseAccepted || agreement.exec() == QDialog::Accepted) {
|
|
|
|
if (!licenseAccepted) {
|
|
|
|
config_set_bool(globalConfig, "General",
|
|
|
|
"LicenseAccepted", true);
|
|
|
|
config_save(globalConfig);
|
|
|
|
}
|
|
|
|
|
2015-08-09 05:09:07 -07:00
|
|
|
if (!StartupOBS(locale.c_str(), GetProfilerNameStore()))
|
|
|
|
return false;
|
|
|
|
|
2016-10-09 17:53:45 -07:00
|
|
|
blog(LOG_INFO, "Portable mode: %s",
|
|
|
|
portable_mode ? "true" : "false");
|
|
|
|
|
2017-05-14 13:03:46 -07:00
|
|
|
setQuitOnLastWindowClosed(false);
|
|
|
|
|
2014-07-22 11:48:35 -07:00
|
|
|
mainWindow = new OBSBasic();
|
|
|
|
|
|
|
|
mainWindow->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
connect(mainWindow, SIGNAL(destroyed()), this, SLOT(quit()));
|
|
|
|
|
2014-07-13 11:36:47 -07:00
|
|
|
mainWindow->OBSInit();
|
|
|
|
|
2014-11-01 13:48:58 -07:00
|
|
|
connect(this, &QGuiApplication::applicationStateChanged,
|
2018-05-06 16:48:27 -07:00
|
|
|
[this](Qt::ApplicationState state)
|
2014-11-01 13:48:58 -07:00
|
|
|
{
|
2018-05-06 16:48:27 -07:00
|
|
|
ResetHotkeyState(
|
2014-11-01 13:48:58 -07:00
|
|
|
state != Qt::ApplicationActive);
|
|
|
|
});
|
2018-05-06 16:48:27 -07:00
|
|
|
ResetHotkeyState(applicationState() != Qt::ApplicationActive);
|
2014-07-13 11:36:47 -07:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2014-01-25 08:08:56 -08:00
|
|
|
}
|
|
|
|
|
2014-05-15 14:04:18 -07:00
|
|
|
string OBSApp::GetVersionString() const
|
|
|
|
{
|
|
|
|
stringstream ver;
|
2014-05-18 17:44:10 -07:00
|
|
|
|
2014-05-15 19:18:13 -07:00
|
|
|
#ifdef HAVE_OBSCONFIG_H
|
2014-07-14 09:51:21 -07:00
|
|
|
ver << OBS_VERSION;
|
|
|
|
#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
|
2014-07-14 09:51:21 -07:00
|
|
|
ver << " (";
|
2014-05-15 19:18:13 -07:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
2014-05-15 14:04:18 -07:00
|
|
|
if (sizeof(void*) == 8)
|
2014-05-18 17:44:10 -07:00
|
|
|
ver << "64bit, ";
|
2014-05-15 14:04:18 -07:00
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
ver << "windows)";
|
|
|
|
#elif __APPLE__
|
|
|
|
ver << "mac)";
|
2015-05-01 13:05:00 -07:00
|
|
|
#elif __FreeBSD__
|
|
|
|
ver << "freebsd)";
|
2014-05-18 17:44:10 -07:00
|
|
|
#else /* assume linux for the time being */
|
|
|
|
ver << "linux)";
|
|
|
|
#endif
|
2014-05-15 19:14:19 -07:00
|
|
|
|
2014-05-15 14:04:18 -07:00
|
|
|
return ver.str();
|
|
|
|
}
|
|
|
|
|
2016-10-09 17:53:45 -07:00
|
|
|
bool OBSApp::IsPortableMode()
|
|
|
|
{
|
|
|
|
return portable_mode;
|
|
|
|
}
|
|
|
|
|
2014-03-07 16:03:34 -08:00
|
|
|
#ifdef __APPLE__
|
2014-03-07 16:19:26 -08:00
|
|
|
#define INPUT_AUDIO_SOURCE "coreaudio_input_capture"
|
|
|
|
#define OUTPUT_AUDIO_SOURCE "coreaudio_output_capture"
|
2014-03-07 16:03:34 -08:00
|
|
|
#elif _WIN32
|
|
|
|
#define INPUT_AUDIO_SOURCE "wasapi_input_capture"
|
|
|
|
#define OUTPUT_AUDIO_SOURCE "wasapi_output_capture"
|
|
|
|
#else
|
2014-03-10 15:02:37 -07:00
|
|
|
#define INPUT_AUDIO_SOURCE "pulse_input_capture"
|
|
|
|
#define OUTPUT_AUDIO_SOURCE "pulse_output_capture"
|
2014-03-07 16:03:34 -08: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);
|
|
|
|
}
|
|
|
|
|
2014-05-10 18:47:48 -07:00
|
|
|
QString OBSTranslator::translate(const char *context, const char *sourceText,
|
|
|
|
const char *disambiguation, int n) const
|
|
|
|
{
|
|
|
|
const char *out = nullptr;
|
2016-08-28 14:24:14 -07:00
|
|
|
if (!App()->TranslateString(sourceText, &out))
|
|
|
|
return QString(sourceText);
|
2014-05-10 18:47:48 -07:00
|
|
|
|
|
|
|
UNUSED_PARAMETER(context);
|
|
|
|
UNUSED_PARAMETER(disambiguation);
|
|
|
|
UNUSED_PARAMETER(n);
|
|
|
|
return QT_UTF8(out);
|
|
|
|
}
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
static uint64_t convert_log_name(bool has_prefix, const char *name)
|
2014-05-18 17:44:10 -07:00
|
|
|
{
|
|
|
|
BaseLexer lex;
|
|
|
|
string year, month, day, hour, minute, second;
|
|
|
|
|
|
|
|
lexer_start(lex, name);
|
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
if (has_prefix) {
|
|
|
|
string temp;
|
|
|
|
if (!get_token(lex, temp, BASETOKEN_ALPHA)) return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
static void delete_oldest_file(bool has_prefix, const char *location)
|
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 10:53:55 -08:00
|
|
|
{
|
2015-06-01 16:11:57 -07:00
|
|
|
BPtr<char> logDir(GetConfigPathPtr(location));
|
2014-05-18 17:44:10 -07:00
|
|
|
string oldestLog;
|
2014-06-25 01:50:44 -07:00
|
|
|
uint64_t oldest_ts = (uint64_t)-1;
|
2014-05-14 17:47:38 -07:00
|
|
|
struct os_dirent *entry;
|
2014-04-24 14:50:40 -07:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
unsigned int maxLogs = (unsigned int)config_get_uint(
|
|
|
|
App()->GlobalConfig(), "General", "MaxLogs");
|
2014-04-14 04:02:11 -07:00
|
|
|
|
2014-09-25 17:44:05 -07:00
|
|
|
os_dir_t *dir = os_opendir(logDir);
|
2014-05-14 17:47:38 -07:00
|
|
|
if (dir) {
|
|
|
|
unsigned int count = 0;
|
2014-04-14 04:02:11 -07:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
while ((entry = os_readdir(dir)) != NULL) {
|
2014-05-18 17:44:10 -07:00
|
|
|
if (entry->directory || *entry->d_name == '.')
|
2014-05-14 17:47:38 -07:00
|
|
|
continue;
|
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
uint64_t ts = convert_log_name(has_prefix,
|
|
|
|
entry->d_name);
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
if (ts) {
|
|
|
|
if (ts < oldest_ts) {
|
|
|
|
oldestLog = entry->d_name;
|
|
|
|
oldest_ts = ts;
|
|
|
|
}
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
count++;
|
|
|
|
}
|
2014-05-14 17:47:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
os_closedir(dir);
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
if (count > maxLogs) {
|
2014-05-14 17:47:38 -07:00
|
|
|
stringstream delPath;
|
|
|
|
|
2014-05-18 17:44:10 -07:00
|
|
|
delPath << logDir << "/" << oldestLog;
|
2014-05-14 17:47:38 -07:00
|
|
|
os_unlink(delPath.str().c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
static void get_last_log(bool has_prefix, const char *subdir_to_use,
|
|
|
|
std::string &last)
|
2014-05-18 17:44:10 -07:00
|
|
|
{
|
2018-03-16 16:15:44 -07:00
|
|
|
BPtr<char> logDir(GetConfigPathPtr(subdir_to_use));
|
2014-05-18 17:44:10 -07:00
|
|
|
struct os_dirent *entry;
|
2014-09-25 17:44:05 -07:00
|
|
|
os_dir_t *dir = os_opendir(logDir);
|
2014-05-18 17:44:10 -07:00
|
|
|
uint64_t highest_ts = 0;
|
|
|
|
|
|
|
|
if (dir) {
|
|
|
|
while ((entry = os_readdir(dir)) != NULL) {
|
|
|
|
if (entry->directory || *entry->d_name == '.')
|
|
|
|
continue;
|
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
uint64_t ts = convert_log_name(has_prefix,
|
|
|
|
entry->d_name);
|
2014-05-18 17:44:10 -07:00
|
|
|
|
|
|
|
if (ts > highest_ts) {
|
2018-03-16 16:15:44 -07:00
|
|
|
last = entry->d_name;
|
|
|
|
highest_ts = ts;
|
2014-05-18 17:44:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
os_closedir(dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-27 03:14:18 -08: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 03:14:18 -08:00
|
|
|
snprintf(file, sizeof(file), "%d-%02d-%02d%c%02d-%02d-%02d.%s",
|
2014-05-20 23:26:19 -07:00
|
|
|
cur_time->tm_year+1900,
|
|
|
|
cur_time->tm_mon+1,
|
|
|
|
cur_time->tm_mday,
|
2015-11-27 03:14:18 -08:00
|
|
|
noSpace ? '_' : ' ',
|
2014-05-20 23:26:19 -07:00
|
|
|
cur_time->tm_hour,
|
|
|
|
cur_time->tm_min,
|
|
|
|
cur_time->tm_sec,
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-07-11 11:10:35 -07: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;
|
|
|
|
}
|
|
|
|
|
2014-05-20 23:26:19 -07:00
|
|
|
static void create_log_file(fstream &logFile)
|
|
|
|
{
|
|
|
|
stringstream dst;
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
get_last_log(false, "obs-studio/logs", lastLogFile);
|
2018-03-16 16:27:49 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
get_last_log(true, "obs-studio/crashes", lastCrashLogFile);
|
|
|
|
#endif
|
2014-05-18 17:44:10 -07:00
|
|
|
|
2014-05-20 23:26:19 -07:00
|
|
|
currentLogFile = GenerateTimeDateFilename("txt");
|
|
|
|
dst << "obs-studio/logs/" << currentLogFile.c_str();
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2015-06-01 16:11:57 -07:00
|
|
|
BPtr<char> path(GetConfigPathPtr(dst.str().c_str()));
|
2017-05-15 03:38:24 -07:00
|
|
|
|
|
|
|
#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
|
2014-05-20 23:26:19 -07:00
|
|
|
logFile.open(path,
|
|
|
|
ios_base::in | ios_base::out | ios_base::trunc);
|
2017-05-15 03:38:24 -07:00
|
|
|
#endif
|
2014-05-14 17:47:38 -07:00
|
|
|
|
|
|
|
if (logFile.is_open()) {
|
2018-03-16 16:17:56 -07:00
|
|
|
delete_oldest_file(false, "obs-studio/logs");
|
2014-04-14 04:02:11 -07:00
|
|
|
base_set_log_handler(do_log, &logFile);
|
2014-05-14 17:47:38 -07:00
|
|
|
} else {
|
|
|
|
blog(LOG_ERROR, "Failed to open log file");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 23:03:31 -07:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2016-08-21 11:26:15 -07:00
|
|
|
using ProfilerSnapshot =
|
2015-07-10 23:03:31 -07:00
|
|
|
std::unique_ptr<profiler_snapshot_t, decltype(SnapshotRelease)>;
|
|
|
|
|
|
|
|
ProfilerSnapshot GetSnapshot()
|
|
|
|
{
|
|
|
|
return ProfilerSnapshot{profile_snapshot_create(), SnapshotRelease};
|
|
|
|
}
|
|
|
|
|
2015-08-01 00:30:02 -07:00
|
|
|
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 = GetConfigPathPtr(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));
|
|
|
|
}
|
|
|
|
|
2015-07-10 23:03:31 -07:00
|
|
|
static auto ProfilerFree = [](void *)
|
|
|
|
{
|
|
|
|
profiler_stop();
|
|
|
|
|
|
|
|
auto snap = GetSnapshot();
|
|
|
|
|
|
|
|
profiler_print(snap.get());
|
|
|
|
profiler_print_time_between_calls(snap.get());
|
|
|
|
|
2015-08-01 00:30:02 -07:00
|
|
|
SaveProfilerData(snap);
|
|
|
|
|
2015-07-10 23:03:31 -07:00
|
|
|
profiler_free();
|
|
|
|
};
|
|
|
|
|
2015-07-10 23:04:12 -07:00
|
|
|
static const char *run_program_init = "run_program_init";
|
2014-05-14 17:47:38 -07:00
|
|
|
static int run_program(fstream &logFile, int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int ret = -1;
|
2015-07-10 23:03:31 -07:00
|
|
|
|
|
|
|
auto profilerNameStore = CreateNameStore();
|
|
|
|
|
|
|
|
std::unique_ptr<void, decltype(ProfilerFree)>
|
|
|
|
prof_release(static_cast<void*>(&ProfilerFree),
|
|
|
|
ProfilerFree);
|
|
|
|
|
|
|
|
profiler_start();
|
2015-07-10 23:04:12 -07:00
|
|
|
profile_register_root(run_program_init, 0);
|
|
|
|
|
|
|
|
ScopeProfiler prof{run_program_init};
|
2015-07-10 23:03:31 -07:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
QCoreApplication::addLibraryPath(".");
|
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 10:53:55 -08:00
|
|
|
|
2015-07-10 23:03:31 -07:00
|
|
|
OBSApp program(argc, argv, profilerNameStore.get());
|
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 10:53:55 -08:00
|
|
|
try {
|
2018-01-17 19:25:45 -08:00
|
|
|
bool created_log = false;
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
program.AppInit();
|
2018-03-16 16:17:56 -07:00
|
|
|
delete_oldest_file(false, "obs-studio/profiler_data");
|
2014-05-14 17:47:38 -07:00
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
OBSTranslator translator;
|
2014-05-14 17:47:38 -07:00
|
|
|
program.installTranslator(&translator);
|
|
|
|
|
2017-05-14 15:25:34 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
/* --------------------------------------- */
|
|
|
|
/* check and warn if already running */
|
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
bool cancel_launch = false;
|
2017-05-14 15:25:34 -07:00
|
|
|
bool already_running = false;
|
|
|
|
RunOnceMutex rom = GetRunOnceMutex(already_running);
|
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
if (!already_running) {
|
|
|
|
goto run;
|
|
|
|
}
|
2017-05-14 15:25:34 -07:00
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
if (!multi) {
|
2017-05-14 15:25:34 -07:00
|
|
|
QMessageBox::StandardButtons buttons(
|
|
|
|
QMessageBox::Yes | QMessageBox::Cancel);
|
|
|
|
QMessageBox mb(QMessageBox::Question,
|
|
|
|
QTStr("AlreadyRunning.Title"),
|
2018-01-17 19:25:45 -08:00
|
|
|
QTStr("AlreadyRunning.Text"), buttons,
|
2017-05-14 15:25:34 -07:00
|
|
|
nullptr);
|
|
|
|
mb.setButtonText(QMessageBox::Yes,
|
|
|
|
QTStr("AlreadyRunning.LaunchAnyway"));
|
|
|
|
mb.setButtonText(QMessageBox::Cancel, QTStr("Cancel"));
|
|
|
|
mb.setDefaultButton(QMessageBox::Cancel);
|
|
|
|
|
|
|
|
QMessageBox::StandardButton button;
|
|
|
|
button = (QMessageBox::StandardButton)mb.exec();
|
2018-01-17 19:25:45 -08:00
|
|
|
cancel_launch = button == QMessageBox::Cancel;
|
|
|
|
}
|
2017-05-14 15:25:34 -07:00
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
if (cancel_launch)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!created_log) {
|
|
|
|
create_log_file(logFile);
|
|
|
|
created_log = true;
|
|
|
|
}
|
2017-07-14 08:49:47 -07:00
|
|
|
|
2018-01-17 19:25:45 -08:00
|
|
|
if (multi) {
|
2017-07-14 08:49:47 -07:00
|
|
|
blog(LOG_INFO, "User enabled --multi flag and is now "
|
|
|
|
"running multiple instances of OBS.");
|
2018-01-17 19:25:45 -08:00
|
|
|
} 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!");
|
2017-05-14 15:25:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------- */
|
2018-01-17 19:25:45 -08:00
|
|
|
run:
|
2017-05-14 15:25:34 -07:00
|
|
|
#endif
|
2018-01-17 19:25:45 -08:00
|
|
|
|
|
|
|
if (!created_log) {
|
|
|
|
create_log_file(logFile);
|
|
|
|
created_log = true;
|
|
|
|
}
|
|
|
|
|
2018-01-19 18:12:57 -08:00
|
|
|
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());
|
|
|
|
}
|
2017-05-14 15:25:34 -07:00
|
|
|
|
2015-07-10 23:02:41 -07:00
|
|
|
if (!program.OBSInit())
|
|
|
|
return 0;
|
|
|
|
|
2015-07-10 23:04:12 -07:00
|
|
|
prof.Stop();
|
|
|
|
|
2015-07-10 23:02:41 -07:00
|
|
|
return program.exec();
|
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 10:53:55 -08:00
|
|
|
|
|
|
|
} catch (const char *error) {
|
|
|
|
blog(LOG_ERROR, "%s", error);
|
2014-07-12 12:29:14 -07:00
|
|
|
OBSErrorBox(nullptr, "%s", error);
|
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 10:53:55 -08:00
|
|
|
}
|
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-04-11 04:04:30 -07:00
|
|
|
#define MAX_CRASH_REPORT_SIZE (150 * 1024)
|
2015-01-02 17:11:27 -08:00
|
|
|
|
2015-03-27 14:29:37 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
|
|
#define CRASH_MESSAGE \
|
|
|
|
"Woops, OBS has crashed!\n\nWould you like to copy the crash log " \
|
|
|
|
"to the clipboard? (Crash logs will still be saved to the " \
|
|
|
|
"%appdata%\\obs-studio\\crashes directory)"
|
|
|
|
|
2015-01-02 17:11:27 -08:00
|
|
|
static void main_crash_handler(const char *format, va_list args, void *param)
|
|
|
|
{
|
2015-03-27 14:29:37 -07:00
|
|
|
char *text = new char[MAX_CRASH_REPORT_SIZE];
|
|
|
|
|
|
|
|
vsnprintf(text, MAX_CRASH_REPORT_SIZE, format, args);
|
2016-03-31 15:01:41 -07:00
|
|
|
text[MAX_CRASH_REPORT_SIZE - 1] = 0;
|
2015-01-02 17:11:27 -08:00
|
|
|
|
2018-03-16 16:17:56 -07:00
|
|
|
delete_oldest_file(true, "obs-studio/crashes");
|
2015-03-27 14:29:37 -07:00
|
|
|
|
|
|
|
string name = "obs-studio/crashes/Crash ";
|
|
|
|
name += GenerateTimeDateFilename("txt");
|
|
|
|
|
2015-06-01 16:11:57 -07:00
|
|
|
BPtr<char> path(GetConfigPathPtr(name.c_str()));
|
2015-03-27 14:29:37 -07:00
|
|
|
|
|
|
|
fstream file;
|
2017-05-20 18:44:54 -07:00
|
|
|
|
|
|
|
#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 |
|
2016-03-31 15:01:41 -07:00
|
|
|
ios_base::binary);
|
2017-05-20 18:44:54 -07:00
|
|
|
#else
|
|
|
|
file.open(path, ios_base::in | ios_base::out | ios_base::trunc |
|
|
|
|
ios_base::binary);
|
|
|
|
#endif
|
2015-03-27 14:29:37 -07:00
|
|
|
file << text;
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
int ret = MessageBoxA(NULL, CRASH_MESSAGE, "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();
|
|
|
|
}
|
2015-01-02 17:11:27 -08:00
|
|
|
|
|
|
|
exit(-1);
|
|
|
|
|
|
|
|
UNUSED_PARAMETER(param);
|
|
|
|
}
|
|
|
|
|
2015-01-05 00:10:56 -08:00
|
|
|
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;
|
|
|
|
|
2015-02-08 15:05:24 -08:00
|
|
|
AdjustTokenPrivileges(token, false, &tp,
|
2015-01-05 00:10:56 -08:00
|
|
|
sizeof(tp), NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(token);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-06-01 15:52:23 -07:00
|
|
|
#ifdef __APPLE__
|
|
|
|
#define BASE_PATH ".."
|
|
|
|
#else
|
|
|
|
#define BASE_PATH "../.."
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define CONFIG_PATH BASE_PATH "/config"
|
|
|
|
|
|
|
|
#ifndef OBS_UNIX_STRUCTURE
|
|
|
|
#define OBS_UNIX_STRUCTURE 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int GetConfigPath(char *path, size_t size, const char *name)
|
|
|
|
{
|
|
|
|
if (!OBS_UNIX_STRUCTURE && 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
char *GetConfigPathPtr(const char *name)
|
|
|
|
{
|
|
|
|
if (!OBS_UNIX_STRUCTURE && 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
os_utf8_to_wcs(name, base_len, &wfile[0], len);
|
|
|
|
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-01 15:52:23 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-01-29 18:02:46 -08:00
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
|
|
|
#define IS_UNIX 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* if using XDG and was previously using an older build of OBS, move config
|
|
|
|
* files to XDG directory */
|
|
|
|
#if defined(USE_XDG) && defined(IS_UNIX)
|
|
|
|
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, 512, "%s/.obs-studio", home) <= 0)
|
|
|
|
return;
|
2015-07-08 10:03:09 -07:00
|
|
|
|
|
|
|
/* make base xdg path if it doesn't already exist */
|
|
|
|
if (GetConfigPath(new_path, 512, "") <= 0)
|
|
|
|
return;
|
|
|
|
if (os_mkdirs(new_path) == MKDIR_ERROR)
|
|
|
|
return;
|
|
|
|
|
2015-01-29 18:02:46 -08:00
|
|
|
if (GetConfigPath(new_path, 512, "obs-studio") <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (os_file_exists(old_path) && !os_file_exists(new_path)) {
|
|
|
|
rename(old_path, new_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-09-10 19:07:38 -07:00
|
|
|
static bool update_ffmpeg_output(ConfigFile &config)
|
2015-08-18 21:08:50 -07:00
|
|
|
{
|
|
|
|
if (config_has_user_value(config, "AdvOut", "FFOutputToFile"))
|
2015-09-10 19:07:38 -07:00
|
|
|
return false;
|
2015-08-18 21:08:50 -07:00
|
|
|
|
|
|
|
const char *url = config_get_string(config, "AdvOut", "FFURL");
|
|
|
|
if (!url)
|
2015-09-10 19:07:38 -07:00
|
|
|
return false;
|
2015-08-18 21:08:50 -07:00
|
|
|
|
|
|
|
bool isActualURL = strstr(url, "://") != nullptr;
|
|
|
|
if (isActualURL)
|
2015-09-10 19:07:38 -07:00
|
|
|
return false;
|
2015-08-18 21:08:50 -07:00
|
|
|
|
|
|
|
string urlStr = url;
|
|
|
|
string extension;
|
|
|
|
|
|
|
|
for (size_t i = urlStr.length(); i > 0; i--) {
|
|
|
|
size_t idx = i - 1;
|
|
|
|
|
|
|
|
if (urlStr[idx] == '.') {
|
|
|
|
extension = &urlStr[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (urlStr[idx] == '\\' || urlStr[idx] == '/') {
|
|
|
|
urlStr[idx] = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (urlStr.empty() || extension.empty())
|
2015-09-10 19:07:38 -07:00
|
|
|
return false;
|
2015-08-18 21:08:50 -07:00
|
|
|
|
|
|
|
config_remove_value(config, "AdvOut", "FFURL");
|
|
|
|
config_set_string(config, "AdvOut", "FFFilePath", urlStr.c_str());
|
|
|
|
config_set_string(config, "AdvOut", "FFExtension", extension.c_str());
|
|
|
|
config_set_bool(config, "AdvOut", "FFOutputToFile", true);
|
2015-09-10 19:07:38 -07:00
|
|
|
return true;
|
2015-08-18 21:08:50 -07:00
|
|
|
}
|
|
|
|
|
2015-09-10 19:10:40 -07:00
|
|
|
static bool move_reconnect_settings(ConfigFile &config, const char *sec)
|
|
|
|
{
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
if (config_has_user_value(config, sec, "Reconnect")) {
|
|
|
|
bool reconnect = config_get_bool(config, sec, "Reconnect");
|
|
|
|
config_set_bool(config, "Output", "Reconnect", reconnect);
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if (config_has_user_value(config, sec, "RetryDelay")) {
|
|
|
|
int delay = (int)config_get_uint(config, sec, "RetryDelay");
|
|
|
|
config_set_uint(config, "Output", "RetryDelay", delay);
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
if (config_has_user_value(config, sec, "MaxRetries")) {
|
|
|
|
int retries = (int)config_get_uint(config, sec, "MaxRetries");
|
|
|
|
config_set_uint(config, "Output", "MaxRetries", retries);
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool update_reconnect(ConfigFile &config)
|
|
|
|
{
|
|
|
|
if (!config_has_user_value(config, "Output", "Mode"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const char *mode = config_get_string(config, "Output", "Mode");
|
|
|
|
if (!mode)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const char *section = (strcmp(mode, "Advanced") == 0) ?
|
|
|
|
"AdvOut" : "SimpleOutput";
|
|
|
|
|
|
|
|
if (move_reconnect_settings(config, section)) {
|
|
|
|
config_remove_value(config, "SimpleOutput", "Reconnect");
|
|
|
|
config_remove_value(config, "SimpleOutput", "RetryDelay");
|
|
|
|
config_remove_value(config, "SimpleOutput", "MaxRetries");
|
|
|
|
config_remove_value(config, "AdvOut", "Reconnect");
|
|
|
|
config_remove_value(config, "AdvOut", "RetryDelay");
|
|
|
|
config_remove_value(config, "AdvOut", "MaxRetries");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-05-08 19:57:01 -07:00
|
|
|
static void convert_x264_settings(obs_data_t *data)
|
|
|
|
{
|
|
|
|
bool use_bufsize = obs_data_get_bool(data, "use_bufsize");
|
|
|
|
|
|
|
|
if (use_bufsize) {
|
|
|
|
int buffer_size = (int)obs_data_get_int(data, "buffer_size");
|
|
|
|
if (buffer_size == 0)
|
|
|
|
obs_data_set_string(data, "rate_control", "CRF");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void convert_14_2_encoder_setting(const char *encoder, const char *file)
|
|
|
|
{
|
|
|
|
obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak");
|
|
|
|
obs_data_item_t *cbr_item = obs_data_item_byname(data, "cbr");
|
|
|
|
obs_data_item_t *rc_item = obs_data_item_byname(data, "rate_control");
|
2016-05-13 22:03:33 -07:00
|
|
|
bool modified = false;
|
|
|
|
bool cbr = true;
|
2016-05-08 19:57:01 -07:00
|
|
|
|
2016-05-13 22:03:33 -07:00
|
|
|
if (cbr_item) {
|
|
|
|
cbr = obs_data_item_get_bool(cbr_item);
|
|
|
|
obs_data_item_unset_user_value(cbr_item);
|
2016-05-08 19:57:01 -07:00
|
|
|
|
2016-05-13 22:03:33 -07:00
|
|
|
obs_data_set_string(data, "rate_control", cbr ? "CBR" : "VBR");
|
|
|
|
|
|
|
|
modified = true;
|
|
|
|
}
|
2016-05-08 19:57:01 -07:00
|
|
|
|
2016-05-13 22:03:33 -07:00
|
|
|
if (!rc_item && astrcmpi(encoder, "obs_x264") == 0) {
|
|
|
|
if (!cbr_item)
|
2016-05-08 19:57:01 -07:00
|
|
|
obs_data_set_string(data, "rate_control", "CBR");
|
2016-05-13 22:03:33 -07:00
|
|
|
else if (!cbr)
|
2016-05-08 19:57:01 -07:00
|
|
|
convert_x264_settings(data);
|
|
|
|
|
2016-05-13 22:03:33 -07:00
|
|
|
modified = true;
|
2016-05-08 19:57:01 -07:00
|
|
|
}
|
|
|
|
|
2016-05-13 22:03:33 -07:00
|
|
|
if (modified)
|
|
|
|
obs_data_save_json_safe(data, file, "tmp", "bak");
|
|
|
|
|
2016-05-08 19:57:01 -07:00
|
|
|
obs_data_item_release(&rc_item);
|
|
|
|
obs_data_item_release(&cbr_item);
|
|
|
|
obs_data_release(data);
|
|
|
|
}
|
|
|
|
|
2015-09-10 19:06:18 -07:00
|
|
|
static void upgrade_settings(void)
|
2015-08-18 21:08:50 -07:00
|
|
|
{
|
|
|
|
char path[512];
|
|
|
|
int pathlen = GetConfigPath(path, 512, "obs-studio/basic/profiles");
|
|
|
|
|
|
|
|
if (pathlen <= 0)
|
|
|
|
return;
|
|
|
|
if (!os_file_exists(path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
os_dir_t *dir = os_opendir(path);
|
|
|
|
if (!dir)
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct os_dirent *ent = os_readdir(dir);
|
|
|
|
|
|
|
|
while (ent) {
|
2016-05-08 19:57:01 -07:00
|
|
|
if (ent->directory && strcmp(ent->d_name, ".") != 0 &&
|
|
|
|
strcmp(ent->d_name, "..") != 0) {
|
2015-08-18 21:08:50 -07:00
|
|
|
strcat(path, "/");
|
|
|
|
strcat(path, ent->d_name);
|
|
|
|
strcat(path, "/basic.ini");
|
|
|
|
|
2015-09-10 19:07:38 -07:00
|
|
|
ConfigFile config;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = config.Open(path, CONFIG_OPEN_EXISTING);
|
|
|
|
if (ret == CONFIG_SUCCESS) {
|
2015-09-10 19:10:40 -07:00
|
|
|
if (update_ffmpeg_output(config) ||
|
|
|
|
update_reconnect(config)) {
|
2015-09-10 19:07:38 -07:00
|
|
|
config_save_safe(config, "tmp",
|
|
|
|
nullptr);
|
|
|
|
}
|
|
|
|
}
|
2015-08-18 21:08:50 -07:00
|
|
|
|
2016-05-08 19:57:01 -07:00
|
|
|
|
|
|
|
if (config) {
|
|
|
|
const char *sEnc = config_get_string(config,
|
|
|
|
"AdvOut", "Encoder");
|
|
|
|
const char *rEnc = config_get_string(config,
|
|
|
|
"AdvOut", "RecEncoder");
|
|
|
|
|
|
|
|
/* replace "cbr" option with "rate_control" for
|
|
|
|
* each profile's encoder data */
|
|
|
|
path[pathlen] = 0;
|
|
|
|
strcat(path, "/");
|
|
|
|
strcat(path, ent->d_name);
|
|
|
|
strcat(path, "/recordEncoder.json");
|
|
|
|
convert_14_2_encoder_setting(rEnc, path);
|
|
|
|
|
|
|
|
path[pathlen] = 0;
|
|
|
|
strcat(path, "/");
|
|
|
|
strcat(path, ent->d_name);
|
|
|
|
strcat(path, "/streamEncoder.json");
|
|
|
|
convert_14_2_encoder_setting(sEnc, path);
|
|
|
|
}
|
|
|
|
|
2015-08-18 21:08:50 -07:00
|
|
|
path[pathlen] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ent = os_readdir(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
os_closedir(dir);
|
|
|
|
}
|
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2015-02-05 01:38:30 -08:00
|
|
|
#ifndef _WIN32
|
2014-05-14 17:47:38 -07:00
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
#endif
|
|
|
|
|
2015-01-05 00:10:56 -08:00
|
|
|
#ifdef _WIN32
|
2018-04-24 11:58:46 -07:00
|
|
|
obs_init_win32_crash_handler();
|
2017-06-21 08:39:20 -07:00
|
|
|
SetErrorMode(SEM_FAILCRITICALERRORS);
|
2015-01-05 00:10:56 -08:00
|
|
|
load_debug_privilege();
|
2015-03-27 14:29:37 -07:00
|
|
|
base_set_crash_handler(main_crash_handler, nullptr);
|
2015-01-05 00:10:56 -08:00
|
|
|
#endif
|
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
base_get_log_handler(&def_log_handler, nullptr);
|
|
|
|
|
2015-01-29 18:02:46 -08:00
|
|
|
#if defined(USE_XDG) && defined(IS_UNIX)
|
|
|
|
move_to_xdg();
|
|
|
|
#endif
|
|
|
|
|
2015-06-01 15:52:23 -07:00
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
if (arg_is(argv[i], "--portable", "-p")) {
|
|
|
|
portable_mode = true;
|
2016-04-13 18:59:27 -07:00
|
|
|
|
2017-07-14 08:49:47 -07:00
|
|
|
} else if (arg_is(argv[i], "--multi", "-m")) {
|
|
|
|
multi = true;
|
|
|
|
|
2016-11-03 08:10:44 -07:00
|
|
|
} else if (arg_is(argv[i], "--verbose", nullptr)) {
|
|
|
|
log_verbose = true;
|
|
|
|
|
2017-04-30 07:47:19 -07:00
|
|
|
} else if (arg_is(argv[i], "--always-on-top", nullptr)) {
|
|
|
|
opt_always_on_top = true;
|
|
|
|
|
2016-11-03 08:10:44 -07:00
|
|
|
} else if (arg_is(argv[i], "--unfiltered_log", nullptr)) {
|
|
|
|
unfiltered_log = true;
|
|
|
|
|
2016-04-13 19:00:12 -07:00
|
|
|
} else if (arg_is(argv[i], "--startstreaming", nullptr)) {
|
|
|
|
opt_start_streaming = true;
|
|
|
|
|
|
|
|
} else if (arg_is(argv[i], "--startrecording", nullptr)) {
|
|
|
|
opt_start_recording = true;
|
|
|
|
|
2017-01-11 11:19:17 -08:00
|
|
|
} else if (arg_is(argv[i], "--startreplaybuffer", nullptr)) {
|
|
|
|
opt_start_replaybuffer = true;
|
|
|
|
|
2016-04-19 18:10:09 -07:00
|
|
|
} 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];
|
|
|
|
|
2016-04-13 18:59:27 -07:00
|
|
|
} else if (arg_is(argv[i], "--scene", nullptr)) {
|
|
|
|
if (++i < argc) opt_starting_scene = argv[i];
|
2017-01-11 11:19:17 -08:00
|
|
|
|
|
|
|
} 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;
|
|
|
|
|
2017-04-10 06:52:53 -07:00
|
|
|
} else if (arg_is(argv[i], "--allow-opengl", nullptr)) {
|
|
|
|
opt_allow_opengl = true;
|
|
|
|
|
2017-01-11 11:19:17 -08:00
|
|
|
} else if (arg_is(argv[i], "--help", "-h")) {
|
|
|
|
std::cout <<
|
|
|
|
"--help, -h: Get list of available commands.\n\n" <<
|
|
|
|
"--startstreaming: Automatically start streaming.\n" <<
|
|
|
|
"--startrecording: Automatically start recording.\n" <<
|
|
|
|
"--startreplaybuffer: Start replay buffer.\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" <<
|
2017-07-14 08:49:47 -07:00
|
|
|
"--portable, -p: Use portable mode.\n" <<
|
|
|
|
"--multi, -m: Don't warn when launching multiple instances.\n\n" <<
|
2017-01-11 11:19:17 -08:00
|
|
|
"--verbose: Make log more verbose.\n" <<
|
2017-04-30 07:47:19 -07:00
|
|
|
"--always-on-top: Start in 'always on top' mode.\n\n" <<
|
2017-01-11 11:19:17 -08:00
|
|
|
"--unfiltered_log: Make log unfiltered.\n\n" <<
|
2017-04-10 06:52:53 -07:00
|
|
|
"--allow-opengl: Allow OpenGL on Windows.\n\n" <<
|
2017-01-11 11:19:17 -08:00
|
|
|
"--version, -V: Get current version.\n";
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
|
|
|
|
} else if (arg_is(argv[i], "--version", "-V")) {
|
|
|
|
std::cout << "OBS Studio - " <<
|
|
|
|
App()->GetVersionString() << "\n";
|
|
|
|
exit(0);
|
2015-06-01 15:52:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if !OBS_UNIX_STRUCTURE
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-09-10 19:06:18 -07:00
|
|
|
upgrade_settings();
|
2015-08-18 21:08:50 -07:00
|
|
|
|
2014-05-14 17:47:38 -07:00
|
|
|
fstream logFile;
|
|
|
|
|
2015-08-16 03:51:22 -07:00
|
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
2014-05-14 17:47:38 -07:00
|
|
|
int ret = run_program(logFile, argc, argv);
|
|
|
|
|
2014-04-07 01:25:38 -07:00
|
|
|
blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());
|
2014-04-14 04:02:11 -07:00
|
|
|
base_set_log_handler(nullptr, nullptr);
|
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 10:53:55 -08:00
|
|
|
return ret;
|
|
|
|
}
|