window-basic-main.cpp 197.3 KB
Newer Older
1
/******************************************************************************
2
    Copyright (C) 2013-2015 by Hugh Bailey <obs.jim@gmail.com>
J
jp9000 已提交
3
                               Zachary Lund <admin@computerquip.com>
4
                               Philippe Groarke <philippe.groarke@gmail.com>
5 6 7

    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
8
    the Free Software Foundation, either version 2 of the License, or
9 10 11
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 14 15 16 17 18 19
    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/>.
******************************************************************************/

S
Shaolin 已提交
20
#include <ctime>
J
jp9000 已提交
21
#include <obs.hpp>
22
#include <QGuiApplication>
J
jp9000 已提交
23
#include <QMessageBox>
24
#include <QShowEvent>
25
#include <QDesktopServices>
J
jp9000 已提交
26
#include <QFileDialog>
27
#include <QDesktopWidget>
28
#include <QScreen>
29 30
#include <QColorDialog>
#include <QSizePolicy>
31

J
jp9000 已提交
32
#include <util/dstr.h>
33 34
#include <util/util.hpp>
#include <util/platform.h>
P
Palana 已提交
35
#include <util/profiler.hpp>
36
#include <util/dstr.hpp>
37

38
#include "obs-app.hpp"
39
#include "platform.hpp"
40
#include "visibility-item-widget.hpp"
41
#include "item-widget-helpers.hpp"
42
#include "window-basic-settings.hpp"
43
#include "window-namedialog.hpp"
J
jp9000 已提交
44
#include "window-basic-auto-config.hpp"
J
jp9000 已提交
45
#include "window-basic-source-select.hpp"
J
jp9000 已提交
46
#include "window-basic-main.hpp"
J
jp9000 已提交
47
#include "window-basic-stats.hpp"
J
jp9000 已提交
48
#include "window-basic-main-outputs.hpp"
J
jp9000 已提交
49
#include "window-log-reply.hpp"
J
jp9000 已提交
50
#include "window-projector.hpp"
P
Palana 已提交
51
#include "window-remux.hpp"
J
jp9000 已提交
52
#include "qt-wrappers.hpp"
53
#include "display-helpers.hpp"
54
#include "volume-control.hpp"
55
#include "remote-text.hpp"
S
Shaolin 已提交
56 57
#include <fstream>
#include <sstream>
58

59
#ifdef _WIN32
J
jp9000 已提交
60 61 62
#include "win-update/win-update.hpp"
#endif

J
jp9000 已提交
63
#include "ui_OBSBasic.h"
64
#include "ui_ColorSelect.h"
65

J
jp9000 已提交
66
#include <fstream>
67 68
#include <sstream>

69 70 71
#include <QScreen>
#include <QWindow>

J
jp9000 已提交
72 73 74
#include <json11.hpp>

using namespace json11;
75
using namespace std;
J
jp9000 已提交
76

77
#ifdef BROWSER_AVAILABLE
78
#include <browser-panel.hpp>
J
jp9000 已提交
79 80
#endif

J
jp9000 已提交
81 82
#include "ui-config.h"

J
jp9000 已提交
83
struct QCef;
84 85
struct QCefCookieManager;

J
jp9000 已提交
86
QCef *cef = nullptr;
87 88 89
QCefCookieManager *panel_cookies = nullptr;

void DestroyPanelCookieManager();
J
jp9000 已提交
90

J
jp9000 已提交
91 92
namespace {

J
jp9000 已提交
93
template<typename OBSRef> struct SignalContainer {
J
jp9000 已提交
94 95 96 97 98 99
	OBSRef ref;
	vector<shared_ptr<OBSSignal>> handlers;
};

}

100 101
extern volatile long insideEventLoop;

J
jp9000 已提交
102 103
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);
P
Palana 已提交
104
Q_DECLARE_METATYPE(OBSSource);
J
jp9000 已提交
105
Q_DECLARE_METATYPE(obs_order_movement);
J
jp9000 已提交
106
Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
J
jp9000 已提交
107

J
jp9000 已提交
108
template<typename T> static T GetOBSRef(QListWidgetItem *item)
P
Palana 已提交
109 110 111 112
{
	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
}

J
jp9000 已提交
113
template<typename T> static void SetOBSRef(QListWidgetItem *item, T &&val)
P
Palana 已提交
114 115
{
	item->setData(static_cast<int>(QtDataRole::OBSRef),
J
jp9000 已提交
116
		      QVariant::fromValue(val));
P
Palana 已提交
117 118
}

119 120
static void AddExtraModulePaths()
{
121
	char base_module_dir[512];
122 123
#if defined(_WIN32) || defined(__APPLE__)
	int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir),
J
jp9000 已提交
124
				     "obs-studio/plugins/%module%");
125
#else
126
	int ret = GetConfigPath(base_module_dir, sizeof(base_module_dir),
J
jp9000 已提交
127
				"obs-studio/plugins/%module%");
128
#endif
B
BtbN 已提交
129

130
	if (ret <= 0)
131 132
		return;

J
jp9000 已提交
133
	string path = (char *)base_module_dir;
134
#if defined(__APPLE__)
135
	obs_add_module_path((path + "/bin").c_str(), (path + "/data").c_str());
136

J
jp9000 已提交
137 138 139 140
	BPtr<char> config_bin =
		os_get_config_path_ptr("obs-studio/plugins/%module%/bin");
	BPtr<char> config_data =
		os_get_config_path_ptr("obs-studio/plugins/%module%/data");
141 142
	obs_add_module_path(config_bin, config_data);

143 144
#elif ARCH_BITS == 64
	obs_add_module_path((path + "/bin/64bit").c_str(),
J
jp9000 已提交
145
			    (path + "/data").c_str());
146 147
#else
	obs_add_module_path((path + "/bin/32bit").c_str(),
J
jp9000 已提交
148
			    (path + "/data").c_str());
149
#endif
150 151
}

152 153
extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main);

154 155 156 157
static int CountVideoSources()
{
	int count = 0;

J
jp9000 已提交
158
	auto countSources = [](void *param, obs_source_t *source) {
159 160 161 162 163
		if (!source)
			return true;

		uint32_t flags = obs_source_get_output_flags(source);
		if ((flags & OBS_SOURCE_VIDEO) != 0)
J
jp9000 已提交
164
			(*reinterpret_cast<int *>(param))++;
165 166 167 168 169 170 171 172

		return true;
	};

	obs_enum_sources(countSources, &count);
	return count;
}

173 174
void assignDockToggle(QDockWidget *dock, QAction *action)
{
J
jp9000 已提交
175
	auto handleWindowToggle = [action](bool vis) {
176 177 178 179
		action->blockSignals(true);
		action->setChecked(vis);
		action->blockSignals(false);
	};
J
jp9000 已提交
180
	auto handleMenuToggle = [dock](bool check) {
181 182 183 184 185 186
		dock->blockSignals(true);
		dock->setVisible(check);
		dock->blockSignals(false);
	};

	dock->connect(dock->toggleViewAction(), &QAction::toggled,
J
jp9000 已提交
187 188
		      handleWindowToggle);
	dock->connect(action, &QAction::toggled, handleMenuToggle);
189 190
}

J
jp9000 已提交
191
extern void RegisterTwitchAuth();
J
jp9000 已提交
192
extern void RegisterMixerAuth();
S
SoftArch 已提交
193
extern void RegisterRestreamAuth();
J
jp9000 已提交
194

195
OBSBasic::OBSBasic(QWidget *parent)
J
jp9000 已提交
196
	: OBSMainWindow(parent), ui(new Ui::OBSBasic)
197
{
198 199
	setAttribute(Qt::WA_NativeWindow);

J
jp9000 已提交
200 201 202
#if TWITCH_ENABLED
	RegisterTwitchAuth();
#endif
J
jp9000 已提交
203 204 205
#if MIXER_ENABLED
	RegisterMixerAuth();
#endif
S
SoftArch 已提交
206 207 208
#if RESTREAM_ENABLED
	RegisterRestreamAuth();
#endif
J
jp9000 已提交
209

J
jp9000 已提交
210 211
	setAcceptDrops(true);

212 213
	api = InitializeAPIInterface(this);

214
	ui->setupUi(this);
J
jp9000 已提交
215 216
	ui->previewDisabledLabel->setVisible(false);

J
jp9000 已提交
217 218
	startingDockLayout = saveState();

219
	statsDock = new OBSDock();
220 221 222 223 224 225 226 227
	statsDock->setObjectName(QStringLiteral("statsDock"));
	statsDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
	statsDock->setWindowTitle(QTStr("Basic.Stats"));
	addDockWidget(Qt::BottomDockWidgetArea, statsDock);
	statsDock->setVisible(false);
	statsDock->setFloating(true);
	statsDock->resize(700, 200);

S
Socapex 已提交
228
	copyActionsDynamicProperties();
229

230
	char styleSheetPath[512];
J
jp9000 已提交
231
	int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath),
J
jp9000 已提交
232
				 "stylesheet.qss");
233
	if (ret > 0) {
H
HomeWorld 已提交
234
		if (QFile::exists(styleSheetPath)) {
J
jp9000 已提交
235 236
			QString path =
				QString("file:///") + QT_UTF8(styleSheetPath);
H
HomeWorld 已提交
237 238
			App()->setStyleSheet(path);
		}
239 240
	}

J
jp9000 已提交
241
	qRegisterMetaType<OBSScene>("OBSScene");
P
Palana 已提交
242
	qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
J
jp9000 已提交
243
	qRegisterMetaType<OBSSource>("OBSSource");
P
Palana 已提交
244
	qRegisterMetaType<obs_hotkey_id>("obs_hotkey_id");
P
Palana 已提交
245

J
jp9000 已提交
246 247
	qRegisterMetaTypeStreamOperators<std::vector<std::shared_ptr<OBSSignal>>>(
		"std::vector<std::shared_ptr<OBSSignal>>");
248
	qRegisterMetaTypeStreamOperators<OBSScene>("OBSScene");
249
	qRegisterMetaTypeStreamOperators<OBSSceneItem>("OBSSceneItem");
250

P
Palana 已提交
251 252 253
	ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false);
	ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false);

254 255
	ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes));

256
	auto displayResize = [this]() {
257 258 259 260
		struct obs_video_info ovi;

		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
261 262 263 264
	};

	connect(windowHandle(), &QWindow::screenChanged, displayResize);
	connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize);
J
jp9000 已提交
265

P
pkv 已提交
266 267 268
	delete shortcutFilter;
	shortcutFilter = CreateShortcutFilter();
	installEventFilter(shortcutFilter);
P
Palana 已提交
269

270
	stringstream name;
J
jp9000 已提交
271
	name << "OBS " << App()->GetVersionString();
272 273 274
	blog(LOG_INFO, "%s", name.str().c_str());
	blog(LOG_INFO, "---------------------------------");

275
	UpdateTitleBar();
J
jp9000 已提交
276 277

	connect(ui->scenes->itemDelegate(),
J
jp9000 已提交
278 279 280 281 282
		SIGNAL(closeEditor(QWidget *,
				   QAbstractItemDelegate::EndEditHint)),
		this,
		SLOT(SceneNameEdited(QWidget *,
				     QAbstractItemDelegate::EndEditHint)));
J
jp9000 已提交
283

284
	cpuUsageInfo = os_cpu_usage_info_start();
J
jp9000 已提交
285
	cpuUsageTimer = new QTimer(this);
J
jp9000 已提交
286 287
	connect(cpuUsageTimer.data(), SIGNAL(timeout()), ui->statusbar,
		SLOT(UpdateCPUUsage()));
J
jp9000 已提交
288
	cpuUsageTimer->start(3000);
289

S
Shaolin 已提交
290 291 292 293 294 295 296 297
	QAction *renameScene = new QAction(ui->scenesDock);
	renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut);
	connect(renameScene, SIGNAL(triggered()), this, SLOT(EditSceneName()));
	ui->scenesDock->addAction(renameScene);

	QAction *renameSource = new QAction(ui->sourcesDock);
	renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut);
	connect(renameSource, SIGNAL(triggered()), this,
J
jp9000 已提交
298
		SLOT(EditSceneItemName()));
S
Shaolin 已提交
299 300
	ui->sourcesDock->addAction(renameSource);

301
#ifdef __APPLE__
S
Shaolin 已提交
302 303 304
	renameScene->setShortcut({Qt::Key_Return});
	renameSource->setShortcut({Qt::Key_Return});

305 306
	ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace});
	ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace});
307 308 309

	ui->action_Settings->setMenuRole(QAction::PreferencesRole);
	ui->actionE_xit->setMenuRole(QAction::QuitRole);
S
Shaolin 已提交
310 311 312
#else
	renameScene->setShortcut({Qt::Key_F2});
	renameSource->setShortcut({Qt::Key_F2});
313
#endif
314

J
jp9000 已提交
315
	auto addNudge = [this](const QKeySequence &seq, const char *s) {
316 317 318 319 320 321 322 323 324 325 326
		QAction *nudge = new QAction(ui->preview);
		nudge->setShortcut(seq);
		nudge->setShortcutContext(Qt::WidgetShortcut);
		ui->preview->addAction(nudge);
		connect(nudge, SIGNAL(triggered()), this, s);
	};

	addNudge(Qt::Key_Up, SLOT(NudgeUp()));
	addNudge(Qt::Key_Down, SLOT(NudgeDown()));
	addNudge(Qt::Key_Left, SLOT(NudgeLeft()));
	addNudge(Qt::Key_Right, SLOT(NudgeRight()));
J
jp9000 已提交
327 328 329 330 331 332

	assignDockToggle(ui->scenesDock, ui->toggleScenes);
	assignDockToggle(ui->sourcesDock, ui->toggleSources);
	assignDockToggle(ui->mixerDock, ui->toggleMixer);
	assignDockToggle(ui->transitionsDock, ui->toggleTransitions);
	assignDockToggle(ui->controlsDock, ui->toggleControls);
333
	assignDockToggle(statsDock, ui->toggleStats);
S
SuslikV 已提交
334 335 336 337 338 339 340

	//hide all docking panes
	ui->toggleScenes->setChecked(false);
	ui->toggleSources->setChecked(false);
	ui->toggleMixer->setChecked(false);
	ui->toggleTransitions->setChecked(false);
	ui->toggleControls->setChecked(false);
A
Alex Anderson 已提交
341
	ui->toggleStats->setChecked(false);
S
SuslikV 已提交
342

343 344
	QPoint curPos;

S
SuslikV 已提交
345 346
	//restore parent window geometry
	const char *geometry = config_get_string(App()->GlobalConfig(),
J
jp9000 已提交
347
						 "BasicWindow", "geometry");
S
SuslikV 已提交
348
	if (geometry != NULL) {
J
jp9000 已提交
349 350
		QByteArray byteArray =
			QByteArray::fromBase64(QByteArray(geometry));
S
SuslikV 已提交
351 352 353 354 355
		restoreGeometry(byteArray);

		QRect windowGeometry = normalGeometry();
		if (!WindowPositionValid(windowGeometry)) {
			QRect rect = App()->desktop()->geometry();
J
jp9000 已提交
356 357 358
			setGeometry(QStyle::alignedRect(Qt::LeftToRight,
							Qt::AlignCenter, size(),
							rect));
S
SuslikV 已提交
359
		}
360 361 362

		curPos = pos();
	} else {
J
jp9000 已提交
363 364
		QRect desktopRect =
			QGuiApplication::primaryScreen()->geometry();
365 366
		QSize adjSize = desktopRect.size() / 2 - size() / 2;
		curPos = QPoint(adjSize.width(), adjSize.height());
S
SuslikV 已提交
367
	}
368 369 370 371 372 373

	QPoint curSize(width(), height());
	QPoint statsDockSize(statsDock->width(), statsDock->height());
	QPoint statsDockPos = curSize / 2 - statsDockSize / 2;
	QPoint newPos = curPos + statsDockPos;
	statsDock->move(newPos);
374 375 376

	ui->previewLabel->setProperty("themeID", "previewProgramLabels");

J
jp9000 已提交
377 378
	bool labels = config_get_bool(GetGlobalConfig(), "BasicWindow",
				      "StudioModeLabels");
379 380 381 382 383

	if (!previewProgramMode)
		ui->previewLabel->setHidden(true);
	else
		ui->previewLabel->setHidden(!labels);
384 385
}

386
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
J
jp9000 已提交
387
			    vector<OBSSource> &audioSources)
388
{
389
	obs_source_t *source = obs_get_output_source(channel);
390 391 392
	if (!source)
		return;

393 394
	audioSources.push_back(source);

395
	obs_data_t *data = obs_save_source(source);
396

J
jp9000 已提交
397
	obs_data_set_obj(parent, name, data);
398 399 400 401 402

	obs_data_release(data);
	obs_source_release(source);
}

403
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
J
jp9000 已提交
404 405 406 407 408
				    obs_data_array_t *quickTransitionData,
				    int transitionDuration,
				    obs_data_array_t *transitions,
				    OBSScene &scene, OBSSource &curProgramScene,
				    obs_data_array_t *savedProjectorList)
409
{
410 411 412 413 414 415 416
	obs_data_t *saveData = obs_data_create();

	vector<OBSSource> audioSources;
	audioSources.reserve(5);

	SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources);
	SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources);
J
jp9000 已提交
417 418 419 420
	SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources);
	SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources);
	SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources);
	SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources);
421

422 423 424
	/* -------------------------------- */
	/* save non-group sources           */

J
jp9000 已提交
425
	auto FilterAudioSources = [&](obs_source_t *source) {
426 427 428
		if (obs_source_is_group(source))
			return false;

429
		return find(begin(audioSources), end(audioSources), source) ==
J
jp9000 已提交
430
		       end(audioSources);
431 432 433 434
	};
	using FilterAudioSources_t = decltype(FilterAudioSources);

	obs_data_array_t *sourcesArray = obs_save_sources_filtered(
J
jp9000 已提交
435 436 437 438 439
		[](void *data, obs_source_t *source) {
			return (*static_cast<FilterAudioSources_t *>(data))(
				source);
		},
		static_cast<void *>(&FilterAudioSources));
440

441 442 443 444 445
	/* -------------------------------- */
	/* save group sources separately    */

	/* saving separately ensures they won't be loaded in older versions */
	obs_data_array_t *groupsArray = obs_save_sources_filtered(
J
jp9000 已提交
446 447 448 449
		[](void *, obs_source_t *source) {
			return obs_source_is_group(source);
		},
		nullptr);
450 451 452

	/* -------------------------------- */

453 454
	obs_source_t *transition = obs_get_output_source(0);
	obs_source_t *currentScene = obs_scene_get_source(scene);
J
jp9000 已提交
455 456
	const char *sceneName = obs_source_get_name(currentScene);
	const char *programName = obs_source_get_name(curProgramScene);
457

J
jp9000 已提交
458 459
	const char *sceneCollection = config_get_string(
		App()->GlobalConfig(), "Basic", "SceneCollection");
J
jp9000 已提交
460

J
jp9000 已提交
461
	obs_data_set_string(saveData, "current_scene", sceneName);
462
	obs_data_set_string(saveData, "current_program_scene", programName);
J
jp9000 已提交
463
	obs_data_set_array(saveData, "scene_order", sceneOrder);
J
jp9000 已提交
464
	obs_data_set_string(saveData, "name", sceneCollection);
J
jp9000 已提交
465
	obs_data_set_array(saveData, "sources", sourcesArray);
466
	obs_data_set_array(saveData, "groups", groupsArray);
467
	obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
J
jp9000 已提交
468
	obs_data_set_array(saveData, "transitions", transitions);
C
cg2121 已提交
469
	obs_data_set_array(saveData, "saved_projectors", savedProjectorList);
470
	obs_data_array_release(sourcesArray);
471
	obs_data_array_release(groupsArray);
472 473

	obs_data_set_string(saveData, "current_transition",
J
jp9000 已提交
474
			    obs_source_get_name(transition));
475 476
	obs_data_set_int(saveData, "transition_duration", transitionDuration);
	obs_source_release(transition);
477 478 479 480

	return saveData;
}

S
Socapex 已提交
481 482 483 484
void OBSBasic::copyActionsDynamicProperties()
{
	// Themes need the QAction dynamic properties
	for (QAction *x : ui->scenesToolbar->actions()) {
J
jp9000 已提交
485
		QWidget *temp = ui->scenesToolbar->widgetForAction(x);
S
Socapex 已提交
486 487 488 489 490 491 492

		for (QByteArray &y : x->dynamicPropertyNames()) {
			temp->setProperty(y, x->property(y));
		}
	}

	for (QAction *x : ui->sourcesToolbar->actions()) {
J
jp9000 已提交
493
		QWidget *temp = ui->sourcesToolbar->widgetForAction(x);
S
Socapex 已提交
494 495 496 497 498 499 500

		for (QByteArray &y : x->dynamicPropertyNames()) {
			temp->setProperty(y, x->property(y));
		}
	}
}

S
Shaolin 已提交
501 502
void OBSBasic::UpdateVolumeControlsDecayRate()
{
J
jp9000 已提交
503 504
	double meterDecayRate =
		config_get_double(basicConfig, "Audio", "MeterDecayRate");
S
Shaolin 已提交
505 506 507 508 509 510

	for (size_t i = 0; i < volumes.size(); i++) {
		volumes[i]->SetMeterDecayRate(meterDecayRate);
	}
}

511 512
void OBSBasic::UpdateVolumeControlsPeakMeterType()
{
J
jp9000 已提交
513 514
	uint32_t peakMeterTypeIdx =
		config_get_uint(basicConfig, "Audio", "PeakMeterType");
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533

	enum obs_peak_meter_type peakMeterType;
	switch (peakMeterTypeIdx) {
	case 0:
		peakMeterType = SAMPLE_PEAK_METER;
		break;
	case 1:
		peakMeterType = TRUE_PEAK_METER;
		break;
	default:
		peakMeterType = SAMPLE_PEAK_METER;
		break;
	}

	for (size_t i = 0; i < volumes.size(); i++) {
		volumes[i]->setPeakMeterType(peakMeterType);
	}
}

534 535
void OBSBasic::ClearVolumeControls()
{
C
craftwar 已提交
536 537
	for (VolControl *vol : volumes)
		delete vol;
538 539 540 541

	volumes.clear();
}

J
jp9000 已提交
542 543 544 545 546 547 548
obs_data_array_t *OBSBasic::SaveSceneListOrder()
{
	obs_data_array_t *sceneOrder = obs_data_array_create();

	for (int i = 0; i < ui->scenes->count(); i++) {
		obs_data_t *data = obs_data_create();
		obs_data_set_string(data, "name",
J
jp9000 已提交
549
				    QT_TO_UTF8(ui->scenes->item(i)->text()));
J
jp9000 已提交
550 551 552 553 554 555 556
		obs_data_array_push_back(sceneOrder, data);
		obs_data_release(data);
	}

	return sceneOrder;
}

C
cg2121 已提交
557 558
obs_data_array_t *OBSBasic::SaveProjectors()
{
559
	obs_data_array_t *savedProjectors = obs_data_array_create();
C
cg2121 已提交
560

561 562 563
	auto saveProjector = [savedProjectors](OBSProjector *projector) {
		if (!projector)
			return;
R
Ryan Foster 已提交
564 565

		obs_data_t *data = obs_data_create();
566
		ProjectorType type = projector->GetProjectorType();
S
Shaolin 已提交
567 568 569 570 571 572 573 574 575 576 577
		switch (type) {
		case ProjectorType::Scene:
		case ProjectorType::Source: {
			obs_source_t *source = projector->GetSource();
			const char *name = obs_source_get_name(source);
			obs_data_set_string(data, "name", name);
			break;
		}
		default:
			break;
		}
578 579
		obs_data_set_int(data, "monitor", projector->GetMonitor());
		obs_data_set_int(data, "type", static_cast<int>(type));
J
jp9000 已提交
580 581 582
		obs_data_set_string(
			data, "geometry",
			projector->saveGeometry().toBase64().constData());
583
		obs_data_array_push_back(savedProjectors, data);
R
Ryan Foster 已提交
584
		obs_data_release(data);
585
	};
R
Ryan Foster 已提交
586

587 588
	for (QPointer<QWidget> &proj : projectors)
		saveProjector(static_cast<OBSProjector *>(proj.data()));
S
Shaolin 已提交
589

S
Shaolin 已提交
590 591
	for (QPointer<QWidget> &proj : windowProjectors)
		saveProjector(static_cast<OBSProjector *>(proj.data()));
S
Shaolin 已提交
592

593
	return savedProjectors;
S
Shaolin 已提交
594 595
}

596 597
void OBSBasic::Save(const char *file)
{
598 599 600 601 602
	OBSScene scene = GetCurrentScene();
	OBSSource curProgramScene = OBSGetStrongRef(programScene);
	if (!curProgramScene)
		curProgramScene = obs_scene_get_source(scene);

J
jp9000 已提交
603
	obs_data_array_t *sceneOrder = SaveSceneListOrder();
J
jp9000 已提交
604
	obs_data_array_t *transitions = SaveTransitions();
605
	obs_data_array_t *quickTrData = SaveQuickTransitions();
C
cg2121 已提交
606
	obs_data_array_t *savedProjectorList = SaveProjectors();
J
jp9000 已提交
607 608 609
	obs_data_t *saveData = GenerateSaveData(
		sceneOrder, quickTrData, ui->transitionDuration->value(),
		transitions, scene, curProgramScene, savedProjectorList);
610

J
jp9000 已提交
611
	obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked());
612
	obs_data_set_bool(saveData, "scaling_enabled",
J
jp9000 已提交
613
			  ui->preview->IsFixedScaling());
614
	obs_data_set_int(saveData, "scaling_level",
J
jp9000 已提交
615
			 ui->preview->GetScalingLevel());
616
	obs_data_set_double(saveData, "scaling_off_x",
J
jp9000 已提交
617
			    ui->preview->GetScrollX());
618
	obs_data_set_double(saveData, "scaling_off_y",
J
jp9000 已提交
619
			    ui->preview->GetScrollY());
J
jp9000 已提交
620

J
jp9000 已提交
621 622 623 624 625 626 627
	if (api) {
		obs_data_t *moduleObj = obs_data_create();
		api->on_save(moduleObj);
		obs_data_set_obj(saveData, "modules", moduleObj);
		obs_data_release(moduleObj);
	}

628 629
	if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
		blog(LOG_ERROR, "Could not save scene data to %s", file);
630 631

	obs_data_release(saveData);
J
jp9000 已提交
632
	obs_data_array_release(sceneOrder);
633
	obs_data_array_release(quickTrData);
J
jp9000 已提交
634
	obs_data_array_release(transitions);
C
cg2121 已提交
635
	obs_data_array_release(savedProjectorList);
636 637
}

I
Ilya M 已提交
638 639 640 641 642 643 644 645 646 647 648 649 650
void OBSBasic::DeferSaveBegin()
{
	os_atomic_inc_long(&disableSaving);
}

void OBSBasic::DeferSaveEnd()
{
	long result = os_atomic_dec_long(&disableSaving);
	if (result == 0) {
		SaveProject();
	}
}

651
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
652
{
653
	obs_data_t *data = obs_data_get_obj(parent, name);
654 655 656
	if (!data)
		return;

657
	obs_source_t *source = obs_load_source(data);
658 659 660 661 662 663 664 665
	if (source) {
		obs_set_output_source(channel, source);
		obs_source_release(source);
	}

	obs_data_release(data);
}

666 667 668
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
669
	obs_properties_t *props = obs_get_source_properties(output_id);
670 671 672 673 674 675 676 677 678 679 680 681 682 683
	size_t count = 0;

	if (!props)
		return false;

	obs_property_t *devices = obs_properties_get(props, "device_id");
	if (devices)
		count = obs_property_list_item_count(devices);

	obs_properties_destroy(props);

	return count != 0;
}

684
void OBSBasic::CreateFirstRunSources()
685
{
686
	bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource());
J
jp9000 已提交
687
	bool hasInputAudio = HasAudioDevices(App()->InputAudioSource());
688 689 690

	if (hasDesktopAudio)
		ResetAudioDevice(App()->OutputAudioSource(), "default",
J
jp9000 已提交
691
				 Str("Basic.DesktopDevice1"), 1);
692 693
	if (hasInputAudio)
		ResetAudioDevice(App()->InputAudioSource(), "default",
J
jp9000 已提交
694
				 Str("Basic.AuxDevice1"), 3);
695 696
}

697
void OBSBasic::CreateDefaultScene(bool firstStart)
698 699 700 701
{
	disableSaving++;

	ClearSceneData();
702 703 704 705
	InitDefaultTransitions();
	CreateDefaultQuickTransitions();
	ui->transitionDuration->setValue(300);
	SetTransition(fadeTransition);
706

J
jp9000 已提交
707
	obs_scene_t *scene = obs_scene_create(Str("Basic.Scene"));
708

709
	if (firstStart)
710
		CreateFirstRunSources();
711

712
	SetCurrentScene(scene, true);
713
	obs_scene_release(scene);
J
jp9000 已提交
714 715

	disableSaving--;
716 717
}

J
jp9000 已提交
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex)
{
	for (int i = 0; i < lw->count(); i++) {
		QListWidgetItem *item = lw->item(i);

		if (strcmp(name, QT_TO_UTF8(item->text())) == 0) {
			if (newIndex != i) {
				item = lw->takeItem(i);
				lw->insertItem(newIndex, item);
			}
			break;
		}
	}
}

void OBSBasic::LoadSceneListOrder(obs_data_array_t *array)
{
	size_t num = obs_data_array_count(array);

	for (size_t i = 0; i < num; i++) {
		obs_data_t *data = obs_data_array_item(array, i);
		const char *name = obs_data_get_string(data, "name");

		ReorderItemByName(ui->scenes, name, (int)i);

		obs_data_release(data);
	}
}

C
cg2121 已提交
747 748
void OBSBasic::LoadSavedProjectors(obs_data_array_t *array)
{
749 750 751 752 753
	for (SavedProjectorInfo *info : savedProjectorsArray) {
		delete info;
	}
	savedProjectorsArray.clear();

C
cg2121 已提交
754 755 756 757 758
	size_t num = obs_data_array_count(array);

	for (size_t i = 0; i < num; i++) {
		obs_data_t *data = obs_data_array_item(array, i);

759 760
		SavedProjectorInfo *info = new SavedProjectorInfo();
		info->monitor = obs_data_get_int(data, "monitor");
J
jp9000 已提交
761 762 763 764
		info->type = static_cast<ProjectorType>(
			obs_data_get_int(data, "type"));
		info->geometry =
			std::string(obs_data_get_string(data, "geometry"));
S
Shaolin 已提交
765
		info->name = std::string(obs_data_get_string(data, "name"));
766
		savedProjectorsArray.emplace_back(info);
S
Shaolin 已提交
767 768 769 770 771

		obs_data_release(data);
	}
}

J
jp9000 已提交
772
static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val)
773 774 775 776 777 778 779 780 781 782 783 784
{
	const char *name = obs_source_get_name(filter);
	const char *id = obs_source_get_id(filter);
	int val = (int)(intptr_t)v_val;
	string indent;

	for (int i = 0; i < val; i++)
		indent += "    ";

	blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id);
}

J
jp9000 已提交
785
static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val)
786 787 788 789
{
	obs_source_t *source = obs_sceneitem_get_source(item);
	const char *name = obs_source_get_name(source);
	const char *id = obs_source_get_id(source);
M
Matt Gajownik 已提交
790 791 792 793 794
	int indent_count = (int)(intptr_t)v_val;
	string indent;

	for (int i = 0; i < indent_count; i++)
		indent += "    ";
795

M
Matt Gajownik 已提交
796
	blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id);
797

798 799 800 801 802 803
	obs_monitoring_type monitoring_type =
		obs_source_get_monitoring_type(source);

	if (monitoring_type != OBS_MONITORING_TYPE_NONE) {
		const char *type =
			(monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY)
J
jp9000 已提交
804 805
				? "monitor only"
				: "monitor and output";
806

M
Matt Gajownik 已提交
807
		blog(LOG_INFO, "    %s- monitoring: %s", indent.c_str(), type);
808
	}
M
Matt Gajownik 已提交
809
	int child_indent = 1 + indent_count;
J
jp9000 已提交
810 811
	obs_source_enum_filters(source, LogFilter,
				(void *)(intptr_t)child_indent);
M
Matt Gajownik 已提交
812
	if (obs_sceneitem_is_group(item))
J
jp9000 已提交
813 814
		obs_sceneitem_group_enum_items(item, LogSceneItem,
					       (void *)(intptr_t)child_indent);
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
	return true;
}

void OBSBasic::LogScenes()
{
	blog(LOG_INFO, "------------------------------------------------");
	blog(LOG_INFO, "Loaded scenes:");

	for (int i = 0; i < ui->scenes->count(); i++) {
		QListWidgetItem *item = ui->scenes->item(i);
		OBSScene scene = GetOBSRef<OBSScene>(item);

		obs_source_t *source = obs_scene_get_source(scene);
		const char *name = obs_source_get_name(source);

		blog(LOG_INFO, "- scene '%s':", name);
J
jp9000 已提交
831 832
		obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1);
		obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1);
833 834 835 836 837
	}

	blog(LOG_INFO, "------------------------------------------------");
}

838 839
void OBSBasic::Load(const char *file)
{
840 841 842 843 844
	disableSaving++;

	obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak");
	if (!data) {
		disableSaving--;
845
		blog(LOG_INFO, "No scene file found, creating default scene");
846
		CreateDefaultScene(true);
J
jp9000 已提交
847
		SaveProject();
848
		return;
849
	}
850

851
	ClearSceneData();
852
	InitDefaultTransitions();
853

854 855 856 857
	obs_data_t *modulesObj = obs_data_get_obj(data, "modules");
	if (api)
		api->on_preload(modulesObj);

J
jp9000 已提交
858
	obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
J
jp9000 已提交
859 860 861 862 863 864 865 866
	obs_data_array_t *sources = obs_data_get_array(data, "sources");
	obs_data_array_t *groups = obs_data_get_array(data, "groups");
	obs_data_array_t *transitions = obs_data_get_array(data, "transitions");
	const char *sceneName = obs_data_get_string(data, "current_scene");
	const char *programSceneName =
		obs_data_get_string(data, "current_program_scene");
	const char *transitionName =
		obs_data_get_string(data, "current_transition");
867

868 869 870 871 872 873
	if (!opt_starting_scene.empty()) {
		programSceneName = opt_starting_scene.c_str();
		if (!IsPreviewProgramMode())
			sceneName = opt_starting_scene.c_str();
	}

874 875 876 877 878 879
	int newDuration = obs_data_get_int(data, "transition_duration");
	if (!newDuration)
		newDuration = 300;

	if (!transitionName)
		transitionName = obs_source_get_name(fadeTransition);
J
jp9000 已提交
880 881

	const char *curSceneCollection = config_get_string(
J
jp9000 已提交
882
		App()->GlobalConfig(), "Basic", "SceneCollection");
J
jp9000 已提交
883 884 885

	obs_data_set_default_string(data, "name", curSceneCollection);

J
jp9000 已提交
886 887 888 889
	const char *name = obs_data_get_string(data, "name");
	obs_source_t *curScene;
	obs_source_t *curProgramScene;
	obs_source_t *curTransition;
890

J
jp9000 已提交
891 892 893
	if (!name || !*name)
		name = curSceneCollection;

894 895
	LoadAudioDevice(DESKTOP_AUDIO_1, 1, data);
	LoadAudioDevice(DESKTOP_AUDIO_2, 2, data);
J
jp9000 已提交
896 897 898 899
	LoadAudioDevice(AUX_AUDIO_1, 3, data);
	LoadAudioDevice(AUX_AUDIO_2, 4, data);
	LoadAudioDevice(AUX_AUDIO_3, 5, data);
	LoadAudioDevice(AUX_AUDIO_4, 6, data);
900

901 902 903 904 905 906 907
	if (!sources) {
		sources = groups;
		groups = nullptr;
	} else {
		obs_data_array_push_back_array(sources, groups);
	}

908
	obs_load_sources(sources, nullptr, nullptr);
909

J
jp9000 已提交
910 911
	if (transitions)
		LoadTransitions(transitions);
J
jp9000 已提交
912 913 914
	if (sceneOrder)
		LoadSceneListOrder(sceneOrder);

J
jp9000 已提交
915 916
	obs_data_array_release(transitions);

917 918 919 920 921 922 923
	curTransition = FindTransition(transitionName);
	if (!curTransition)
		curTransition = fadeTransition;

	ui->transitionDuration->setValue(newDuration);
	SetTransition(curTransition);

924
retryScene:
925
	curScene = obs_get_source_by_name(sceneName);
926
	curProgramScene = obs_get_source_by_name(programSceneName);
927 928 929 930 931

	/* if the starting scene command line parameter is bad at all,
	 * fall back to original settings */
	if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) {
		sceneName = obs_data_get_string(data, "current_scene");
J
jp9000 已提交
932 933
		programSceneName =
			obs_data_get_string(data, "current_program_scene");
934 935 936 937 938 939
		obs_source_release(curScene);
		obs_source_release(curProgramScene);
		opt_starting_scene.clear();
		goto retryScene;
	}

940 941 942 943 944 945 946 947
	if (!curProgramScene) {
		curProgramScene = curScene;
		obs_source_addref(curScene);
	}

	SetCurrentScene(curScene, true);
	if (IsPreviewProgramMode())
		TransitionToScene(curProgramScene, true);
948
	obs_source_release(curScene);
949
	obs_source_release(curProgramScene);
950 951

	obs_data_array_release(sources);
952
	obs_data_array_release(groups);
J
jp9000 已提交
953
	obs_data_array_release(sceneOrder);
J
jp9000 已提交
954

955 956 957
	/* ------------------- */

	bool projectorSave = config_get_bool(GetGlobalConfig(), "BasicWindow",
J
jp9000 已提交
958
					     "SaveProjectors");
959 960

	if (projectorSave) {
J
jp9000 已提交
961 962
		obs_data_array_t *savedProjectors =
			obs_data_get_array(data, "saved_projectors");
963

964
		if (savedProjectors) {
965
			LoadSavedProjectors(savedProjectors);
966 967 968
			OpenSavedProjectors();
			activateWindow();
		}
969 970 971 972 973 974

		obs_data_array_release(savedProjectors);
	}

	/* ------------------- */

J
jp9000 已提交
975 976 977 978
	std::string file_base = strrchr(file, '/') + 1;
	file_base.erase(file_base.size() - 5, 5);

	config_set_string(App()->GlobalConfig(), "Basic", "SceneCollection",
J
jp9000 已提交
979
			  name);
J
jp9000 已提交
980
	config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile",
J
jp9000 已提交
981
			  file_base.c_str());
J
jp9000 已提交
982

J
jp9000 已提交
983 984
	obs_data_array_t *quickTransitionData =
		obs_data_get_array(data, "quick_transitions");
985 986 987 988 989
	LoadQuickTransitions(quickTransitionData);
	obs_data_array_release(quickTransitionData);

	RefreshQuickTransitions();

J
jp9000 已提交
990 991 992 993
	bool previewLocked = obs_data_get_bool(data, "preview_locked");
	ui->preview->SetLocked(previewLocked);
	ui->actionLockPreview->setChecked(previewLocked);

994 995 996 997 998 999 1000 1001 1002 1003
	/* ---------------------- */

	bool fixedScaling = obs_data_get_bool(data, "scaling_enabled");
	int scalingLevel = (int)obs_data_get_int(data, "scaling_level");
	float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x");
	float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y");

	if (fixedScaling) {
		ui->preview->SetScalingLevel(scalingLevel);
		ui->preview->SetScrollingOffset(scrollOffX, scrollOffY);
J
Joseph El-Khouri 已提交
1004
	}
1005
	ui->preview->SetFixedScaling(fixedScaling);
J
Joseph El-Khouri 已提交
1006

1007
	/* ---------------------- */
J
Joseph El-Khouri 已提交
1008

1009
	if (api)
J
jp9000 已提交
1010 1011
		api->on_load(modulesObj);

1012
	obs_data_release(modulesObj);
1013
	obs_data_release(data);
J
jp9000 已提交
1014

1015 1016 1017
	if (!opt_starting_scene.empty())
		opt_starting_scene.clear();

1018
	if (opt_start_streaming) {
1019
		blog(LOG_INFO, "Starting stream due to command line parameter");
1020
		QMetaObject::invokeMethod(this, "StartStreaming",
J
jp9000 已提交
1021
					  Qt::QueuedConnection);
1022 1023 1024 1025
		opt_start_streaming = false;
	}

	if (opt_start_recording) {
J
jp9000 已提交
1026 1027
		blog(LOG_INFO,
		     "Starting recording due to command line parameter");
1028
		QMetaObject::invokeMethod(this, "StartRecording",
J
jp9000 已提交
1029
					  Qt::QueuedConnection);
1030 1031 1032
		opt_start_recording = false;
	}

C
cg2121 已提交
1033 1034
	if (opt_start_replaybuffer) {
		QMetaObject::invokeMethod(this, "StartReplayBuffer",
J
jp9000 已提交
1035
					  Qt::QueuedConnection);
C
cg2121 已提交
1036 1037 1038
		opt_start_replaybuffer = false;
	}

1039 1040 1041
	copyString = nullptr;
	copyFiltersString = nullptr;

1042 1043
	LogScenes();

J
jp9000 已提交
1044
	disableSaving--;
1045

1046
	if (api) {
1047
		api->on_event(OBS_FRONTEND_EVENT_SCENE_CHANGED);
1048 1049
		api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
	}
1050 1051
}

J
jp9000 已提交
1052
#define SERVICE_PATH "service.json"
1053 1054 1055 1056 1057 1058

void OBSBasic::SaveService()
{
	if (!service)
		return;

1059
	char serviceJsonPath[512];
J
jp9000 已提交
1060
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
J
jp9000 已提交
1061
				 SERVICE_PATH);
1062
	if (ret <= 0)
1063 1064
		return;

J
jp9000 已提交
1065
	obs_data_t *data = obs_data_create();
1066
	obs_data_t *settings = obs_service_get_settings(service);
1067

1068
	obs_data_set_string(data, "type", obs_service_get_type(service));
J
jp9000 已提交
1069
	obs_data_set_obj(data, "settings", settings);
1070

1071 1072
	if (!obs_data_save_json_safe(data, serviceJsonPath, "tmp", "bak"))
		blog(LOG_WARNING, "Failed to save service");
1073 1074 1075 1076 1077 1078 1079 1080 1081

	obs_data_release(settings);
	obs_data_release(data);
}

bool OBSBasic::LoadService()
{
	const char *type;

1082
	char serviceJsonPath[512];
J
jp9000 已提交
1083
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
J
jp9000 已提交
1084
				 SERVICE_PATH);
1085
	if (ret <= 0)
1086 1087
		return false;

J
jp9000 已提交
1088 1089
	obs_data_t *data =
		obs_data_create_from_json_file_safe(serviceJsonPath, "bak");
1090 1091

	obs_data_set_default_string(data, "type", "rtmp_common");
J
jp9000 已提交
1092
	type = obs_data_get_string(data, "type");
1093

1094
	obs_data_t *settings = obs_data_get_obj(data, "settings");
P
Palana 已提交
1095
	obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys");
1096

1097
	service = obs_service_create(type, "default_service", settings,
J
jp9000 已提交
1098
				     hotkey_data);
1099
	obs_service_release(service);
1100

P
Palana 已提交
1101
	obs_data_release(hotkey_data);
1102 1103 1104 1105 1106 1107 1108 1109
	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitService()
{
P
Palana 已提交
1110 1111
	ProfileScope("OBSBasic::InitService");

1112 1113 1114
	if (LoadService())
		return true;

1115
	service = obs_service_create("rtmp_common", "default_service", nullptr,
J
jp9000 已提交
1116
				     nullptr);
1117 1118
	if (!service)
		return false;
1119
	obs_service_release(service);
1120 1121 1122 1123

	return true;
}

J
jp9000 已提交
1124 1125 1126
static const double scaled_vals[] = {1.0,         1.25, (1.0 / 0.75), 1.5,
				     (1.0 / 0.6), 1.75, 2.0,          2.25,
				     2.5,         2.75, 3.0,          0.0};
1127

1128 1129
extern void CheckExistingCookieId();

1130 1131
bool OBSBasic::InitBasicConfigDefaults()
{
J
jp9000 已提交
1132
	QList<QScreen *> screens = QGuiApplication::screens();
1133

1134
	if (!screens.size()) {
1135
		OBSErrorBox(NULL, "There appears to be no monitors.  Er, this "
J
jp9000 已提交
1136
				  "technically shouldn't be possible.");
1137 1138 1139
		return false;
	}

1140 1141 1142 1143
	QScreen *primaryScreen = QGuiApplication::primaryScreen();

	uint32_t cx = primaryScreen->size().width();
	uint32_t cy = primaryScreen->size().height();
1144

J
jp9000 已提交
1145 1146
	bool oldResolutionDefaults = config_get_bool(
		App()->GlobalConfig(), "General", "Pre19Defaults");
1147 1148 1149 1150 1151 1152 1153 1154 1155

	/* use 1920x1080 for new default base res if main monitor is above
	 * 1920x1080, but don't apply for people from older builds -- only to
	 * new users */
	if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) {
		cx = 1920;
		cy = 1080;
	}

P
pkviet 已提交
1156 1157 1158 1159 1160 1161 1162 1163
	bool changed = false;

	/* ----------------------------------------------------- */
	/* move over old FFmpeg track settings                   */
	if (config_has_user_value(basicConfig, "AdvOut", "FFAudioTrack") &&
	    !config_has_user_value(basicConfig, "AdvOut", "Pre22.1Settings")) {

		int track = (int)config_get_int(basicConfig, "AdvOut",
J
jp9000 已提交
1164
						"FFAudioTrack");
P
pkviet 已提交
1165
		config_set_int(basicConfig, "AdvOut", "FFAudioMixes",
J
jp9000 已提交
1166
			       1LL << (track - 1));
P
pkviet 已提交
1167 1168 1169 1170
		config_set_bool(basicConfig, "AdvOut", "Pre22.1Settings", true);
		changed = true;
	}

1171 1172 1173 1174 1175
	/* ----------------------------------------------------- */
	/* move over mixer values in advanced if older config */
	if (config_has_user_value(basicConfig, "AdvOut", "RecTrackIndex") &&
	    !config_has_user_value(basicConfig, "AdvOut", "RecTracks")) {

J
jp9000 已提交
1176 1177
		uint64_t track =
			config_get_uint(basicConfig, "AdvOut", "RecTrackIndex");
1178 1179 1180
		track = 1ULL << (track - 1);
		config_set_uint(basicConfig, "AdvOut", "RecTracks", track);
		config_remove_value(basicConfig, "AdvOut", "RecTrackIndex");
P
pkviet 已提交
1181
		changed = true;
1182 1183 1184 1185
	}

	/* ----------------------------------------------------- */

P
pkviet 已提交
1186 1187 1188 1189 1190
	if (changed)
		config_save_safe(basicConfig, "tmp", nullptr);

	/* ----------------------------------------------------- */

1191
	config_set_default_string(basicConfig, "Output", "Mode", "Simple");
J
jp9000 已提交
1192

1193
	config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
J
jp9000 已提交
1194
				  GetDefaultVideoSavePath().c_str());
1195
	config_set_default_string(basicConfig, "SimpleOutput", "RecFormat",
J
jp9000 已提交
1196 1197 1198 1199 1200 1201 1202
				  "flv");
	config_set_default_uint(basicConfig, "SimpleOutput", "VBitrate", 2500);
	config_set_default_uint(basicConfig, "SimpleOutput", "ABitrate", 160);
	config_set_default_bool(basicConfig, "SimpleOutput", "UseAdvanced",
				false);
	config_set_default_bool(basicConfig, "SimpleOutput", "EnforceBitrate",
				true);
J
jp9000 已提交
1203
	config_set_default_string(basicConfig, "SimpleOutput", "Preset",
J
jp9000 已提交
1204
				  "veryfast");
1205
	config_set_default_string(basicConfig, "SimpleOutput", "NVENCPreset",
J
jp9000 已提交
1206
				  "hq");
1207
	config_set_default_string(basicConfig, "SimpleOutput", "RecQuality",
J
jp9000 已提交
1208
				  "Stream");
1209 1210 1211
	config_set_default_bool(basicConfig, "SimpleOutput", "RecRB", false);
	config_set_default_int(basicConfig, "SimpleOutput", "RecRBTime", 20);
	config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512);
1212
	config_set_default_string(basicConfig, "SimpleOutput", "RecRBPrefix",
J
jp9000 已提交
1213
				  "Replay");
1214

J
jp9000 已提交
1215 1216 1217 1218
	config_set_default_bool(basicConfig, "AdvOut", "ApplyServiceSettings",
				true);
	config_set_default_bool(basicConfig, "AdvOut", "UseRescale", false);
	config_set_default_uint(basicConfig, "AdvOut", "TrackIndex", 1);
J
jp9000 已提交
1219 1220 1221 1222 1223
	config_set_default_string(basicConfig, "AdvOut", "Encoder", "obs_x264");

	config_set_default_string(basicConfig, "AdvOut", "RecType", "Standard");

	config_set_default_string(basicConfig, "AdvOut", "RecFilePath",
J
jp9000 已提交
1224
				  GetDefaultVideoSavePath().c_str());
1225
	config_set_default_string(basicConfig, "AdvOut", "RecFormat", "flv");
J
jp9000 已提交
1226 1227 1228 1229 1230
	config_set_default_bool(basicConfig, "AdvOut", "RecUseRescale", false);
	config_set_default_uint(basicConfig, "AdvOut", "RecTracks", (1 << 0));
	config_set_default_string(basicConfig, "AdvOut", "RecEncoder", "none");

	config_set_default_bool(basicConfig, "AdvOut", "FFOutputToFile", true);
1231
	config_set_default_string(basicConfig, "AdvOut", "FFFilePath",
J
jp9000 已提交
1232
				  GetDefaultVideoSavePath().c_str());
1233
	config_set_default_string(basicConfig, "AdvOut", "FFExtension", "mp4");
J
jp9000 已提交
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253
	config_set_default_uint(basicConfig, "AdvOut", "FFVBitrate", 2500);
	config_set_default_uint(basicConfig, "AdvOut", "FFVGOPSize", 250);
	config_set_default_bool(basicConfig, "AdvOut", "FFUseRescale", false);
	config_set_default_bool(basicConfig, "AdvOut", "FFIgnoreCompat", false);
	config_set_default_uint(basicConfig, "AdvOut", "FFABitrate", 160);
	config_set_default_uint(basicConfig, "AdvOut", "FFAudioMixes", 1);

	config_set_default_uint(basicConfig, "AdvOut", "Track1Bitrate", 160);
	config_set_default_uint(basicConfig, "AdvOut", "Track2Bitrate", 160);
	config_set_default_uint(basicConfig, "AdvOut", "Track3Bitrate", 160);
	config_set_default_uint(basicConfig, "AdvOut", "Track4Bitrate", 160);
	config_set_default_uint(basicConfig, "AdvOut", "Track5Bitrate", 160);
	config_set_default_uint(basicConfig, "AdvOut", "Track6Bitrate", 160);

	config_set_default_bool(basicConfig, "AdvOut", "RecRB", false);
	config_set_default_uint(basicConfig, "AdvOut", "RecRBTime", 20);
	config_set_default_int(basicConfig, "AdvOut", "RecRBSize", 512);

	config_set_default_uint(basicConfig, "Video", "BaseCX", cx);
	config_set_default_uint(basicConfig, "Video", "BaseCY", cy);
1254

1255 1256 1257 1258 1259 1260 1261 1262
	/* don't allow BaseCX/BaseCY to be susceptible to defaults changing */
	if (!config_has_user_value(basicConfig, "Video", "BaseCX") ||
	    !config_has_user_value(basicConfig, "Video", "BaseCY")) {
		config_set_uint(basicConfig, "Video", "BaseCX", cx);
		config_set_uint(basicConfig, "Video", "BaseCY", cy);
		config_save_safe(basicConfig, "tmp", nullptr);
	}

1263
	config_set_default_string(basicConfig, "Output", "FilenameFormatting",
J
jp9000 已提交
1264
				  "%CCYY-%MM-%DD %hh-%mm-%ss");
1265

J
jp9000 已提交
1266 1267 1268
	config_set_default_bool(basicConfig, "Output", "DelayEnable", false);
	config_set_default_uint(basicConfig, "Output", "DelaySec", 20);
	config_set_default_bool(basicConfig, "Output", "DelayPreserve", true);
1269

J
jp9000 已提交
1270 1271 1272
	config_set_default_bool(basicConfig, "Output", "Reconnect", true);
	config_set_default_uint(basicConfig, "Output", "RetryDelay", 10);
	config_set_default_uint(basicConfig, "Output", "MaxRetries", 20);
1273

1274
	config_set_default_string(basicConfig, "Output", "BindIP", "default");
J
jp9000 已提交
1275 1276 1277 1278
	config_set_default_bool(basicConfig, "Output", "NewSocketLoopEnable",
				false);
	config_set_default_bool(basicConfig, "Output", "LowLatencyEnable",
				false);
1279

1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
	int i = 0;
	uint32_t scale_cx = cx;
	uint32_t scale_cy = cy;

	/* use a default scaled resolution that has a pixel count no higher
	 * than 1280x720 */
	while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) {
		double scale = scaled_vals[i++];
		scale_cx = uint32_t(double(cx) / scale);
		scale_cy = uint32_t(double(cy) / scale);
	}

J
jp9000 已提交
1292 1293
	config_set_default_uint(basicConfig, "Video", "OutputCX", scale_cx);
	config_set_default_uint(basicConfig, "Video", "OutputCY", scale_cy);
1294

1295 1296 1297 1298 1299 1300 1301 1302 1303
	/* don't allow OutputCX/OutputCY to be susceptible to defaults
	 * changing */
	if (!config_has_user_value(basicConfig, "Video", "OutputCX") ||
	    !config_has_user_value(basicConfig, "Video", "OutputCY")) {
		config_set_uint(basicConfig, "Video", "OutputCX", scale_cx);
		config_set_uint(basicConfig, "Video", "OutputCY", scale_cy);
		config_save_safe(basicConfig, "tmp", nullptr);
	}

J
jp9000 已提交
1304
	config_set_default_uint(basicConfig, "Video", "FPSType", 0);
1305
	config_set_default_string(basicConfig, "Video", "FPSCommon", "30");
J
jp9000 已提交
1306 1307 1308
	config_set_default_uint(basicConfig, "Video", "FPSInt", 30);
	config_set_default_uint(basicConfig, "Video", "FPSNum", 30);
	config_set_default_uint(basicConfig, "Video", "FPSDen", 1);
1309
	config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
1310
	config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
1311
	config_set_default_string(basicConfig, "Video", "ColorSpace", "601");
1312
	config_set_default_string(basicConfig, "Video", "ColorRange",
J
jp9000 已提交
1313
				  "Partial");
1314

1315
	config_set_default_string(basicConfig, "Audio", "MonitoringDeviceId",
J
jp9000 已提交
1316 1317 1318 1319 1320 1321
				  "default");
	config_set_default_string(
		basicConfig, "Audio", "MonitoringDeviceName",
		Str("Basic.Settings.Advanced.Audio.MonitoringDevice"
		    ".Default"));
	config_set_default_uint(basicConfig, "Audio", "SampleRate", 44100);
1322
	config_set_default_string(basicConfig, "Audio", "ChannelSetup",
J
jp9000 已提交
1323
				  "Stereo");
S
Shaolin 已提交
1324
	config_set_default_double(basicConfig, "Audio", "MeterDecayRate",
J
jp9000 已提交
1325 1326
				  VOLUME_METER_DECAY_FAST);
	config_set_default_uint(basicConfig, "Audio", "PeakMeterType", 0);
1327

1328 1329
	CheckExistingCookieId();

1330 1331 1332
	return true;
}

1333 1334 1335 1336
extern bool EncoderAvailable(const char *encoder);

void OBSBasic::InitBasicConfigDefaults2()
{
J
jp9000 已提交
1337 1338
	bool oldEncDefaults = config_get_bool(App()->GlobalConfig(), "General",
					      "Pre23Defaults");
1339 1340 1341
	bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults;

	config_set_default_string(basicConfig, "SimpleOutput", "StreamEncoder",
J
jp9000 已提交
1342 1343
				  useNV ? SIMPLE_ENCODER_NVENC
					: SIMPLE_ENCODER_X264);
1344
	config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
J
jp9000 已提交
1345 1346
				  useNV ? SIMPLE_ENCODER_NVENC
					: SIMPLE_ENCODER_X264);
1347 1348
}

1349 1350
bool OBSBasic::InitBasicConfig()
{
P
Palana 已提交
1351 1352
	ProfileScope("OBSBasic::InitBasicConfig");

1353
	char configPath[512];
J
jp9000 已提交
1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366

	int ret = GetProfilePath(configPath, sizeof(configPath), "");
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get profile path");
		return false;
	}

	if (os_mkdir(configPath) == MKDIR_ERROR) {
		OBSErrorBox(nullptr, "Failed to create profile path");
		return false;
	}

	ret = GetProfilePath(configPath, sizeof(configPath), "basic.ini");
1367 1368 1369 1370
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get base.ini path");
		return false;
	}
1371

1372 1373 1374
	int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (code != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", code);
1375 1376 1377
		return false;
	}

J
jp9000 已提交
1378 1379
	if (config_get_string(basicConfig, "General", "Name") == nullptr) {
		const char *curName = config_get_string(App()->GlobalConfig(),
J
jp9000 已提交
1380
							"Basic", "Profile");
J
jp9000 已提交
1381 1382

		config_set_string(basicConfig, "General", "Name", curName);
1383
		basicConfig.SaveSafe("tmp");
J
jp9000 已提交
1384 1385
	}

1386 1387 1388
	return InitBasicConfigDefaults();
}

1389 1390
void OBSBasic::InitOBSCallbacks()
{
P
Palana 已提交
1391 1392
	ProfileScope("OBSBasic::InitOBSCallbacks");

P
Palana 已提交
1393
	signalHandlers.reserve(signalHandlers.size() + 6);
1394
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_create",
J
jp9000 已提交
1395
				    OBSBasic::SourceCreated, this);
P
Palana 已提交
1396
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove",
J
jp9000 已提交
1397
				    OBSBasic::SourceRemoved, this);
P
Palana 已提交
1398
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate",
J
jp9000 已提交
1399 1400 1401 1402
				    OBSBasic::SourceActivated, this);
	signalHandlers.emplace_back(obs_get_signal_handler(),
				    "source_deactivate",
				    OBSBasic::SourceDeactivated, this);
P
Palana 已提交
1403
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename",
J
jp9000 已提交
1404
				    OBSBasic::SourceRenamed, this);
1405 1406
}

J
jp9000 已提交
1407 1408
void OBSBasic::InitPrimitives()
{
P
Palana 已提交
1409 1410
	ProfileScope("OBSBasic::InitPrimitives");

J
jp9000 已提交
1411
	obs_enter_graphics();
J
jp9000 已提交
1412

1413
	gs_render_start(true);
J
jp9000 已提交
1414 1415 1416 1417 1418
	gs_vertex2f(0.0f, 0.0f);
	gs_vertex2f(0.0f, 1.0f);
	gs_vertex2f(1.0f, 1.0f);
	gs_vertex2f(1.0f, 0.0f);
	gs_vertex2f(0.0f, 0.0f);
1419
	box = gs_render_save();
J
jp9000 已提交
1420

1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440
	gs_render_start(true);
	gs_vertex2f(0.0f, 0.0f);
	gs_vertex2f(0.0f, 1.0f);
	boxLeft = gs_render_save();

	gs_render_start(true);
	gs_vertex2f(0.0f, 0.0f);
	gs_vertex2f(1.0f, 0.0f);
	boxTop = gs_render_save();

	gs_render_start(true);
	gs_vertex2f(1.0f, 0.0f);
	gs_vertex2f(1.0f, 1.0f);
	boxRight = gs_render_save();

	gs_render_start(true);
	gs_vertex2f(0.0f, 1.0f);
	gs_vertex2f(1.0f, 1.0f);
	boxBottom = gs_render_save();

1441
	gs_render_start(true);
J
jp9000 已提交
1442
	for (int i = 0; i <= 360; i += (360 / 20)) {
J
jp9000 已提交
1443 1444 1445
		float pos = RAD(float(i));
		gs_vertex2f(cosf(pos), sinf(pos));
	}
1446
	circle = gs_render_save();
J
jp9000 已提交
1447

J
jp9000 已提交
1448
	obs_leave_graphics();
J
jp9000 已提交
1449 1450
}

J
jp9000 已提交
1451 1452 1453 1454 1455 1456 1457 1458
void OBSBasic::ReplayBufferClicked()
{
	if (outputHandler->ReplayBufferActive())
		StopReplayBuffer();
	else
		StartReplayBuffer();
};

J
jp9000 已提交
1459 1460
void OBSBasic::ResetOutputs()
{
P
Palana 已提交
1461 1462
	ProfileScope("OBSBasic::ResetOutputs");

J
jp9000 已提交
1463 1464 1465
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
	bool advOut = astrcmpi(mode, "Advanced") == 0;

J
jp9000 已提交
1466 1467
	if (!outputHandler || !outputHandler->Active()) {
		outputHandler.reset();
J
jp9000 已提交
1468 1469
		outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this)
					   : CreateSimpleOutputHandler(this));
1470

J
jp9000 已提交
1471 1472 1473 1474
		delete replayBufferButton;

		if (outputHandler->replayBuffer) {
			replayBufferButton = new QPushButton(
J
jp9000 已提交
1475
				QTStr("Basic.Main.StartReplayBuffer"), this);
1476
			replayBufferButton->setCheckable(true);
1477
			connect(replayBufferButton.data(),
J
jp9000 已提交
1478 1479
				&QPushButton::clicked, this,
				&OBSBasic::ReplayBufferClicked);
J
jp9000 已提交
1480

J
jp9000 已提交
1481 1482
			replayBufferButton->setProperty("themeID",
							"replayBufferButton");
J
jp9000 已提交
1483 1484
			ui->buttonsVLayout->insertWidget(2, replayBufferButton);
		}
1485

J
jp9000 已提交
1486 1487
		if (sysTrayReplayBuffer)
			sysTrayReplayBuffer->setEnabled(
J
jp9000 已提交
1488
				!!outputHandler->replayBuffer);
J
jp9000 已提交
1489 1490 1491 1492 1493
	} else {
		outputHandler->Update();
	}
}

J
jp9000 已提交
1494
static void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
J
jp9000 已提交
1495
				     const char *slot);
J
jp9000 已提交
1496

1497 1498 1499 1500
#define STARTUP_SEPARATOR \
	"==== Startup complete ==============================================="
#define SHUTDOWN_SEPARATOR \
	"==== Shutting down =================================================="
1501

J
jp9000 已提交
1502
#define UNSUPPORTED_ERROR                                                     \
1503 1504 1505
	"Failed to initialize video:\n\nRequired graphics API functionality " \
	"not found.  Your GPU may not be supported."

J
jp9000 已提交
1506
#define UNKNOWN_ERROR                                                  \
1507 1508
	"Failed to initialize video.  Your GPU may not be supported, " \
	"or your graphics drivers may need to be updated."
1509

1510 1511
void OBSBasic::OBSInit()
{
P
Palana 已提交
1512 1513
	ProfileScope("OBSBasic::OBSInit");

J
jp9000 已提交
1514 1515
	const char *sceneCollection = config_get_string(
		App()->GlobalConfig(), "Basic", "SceneCollectionFile");
1516
	char savePath[512];
J
jp9000 已提交
1517 1518 1519 1520 1521 1522 1523
	char fileName[512];
	int ret;

	if (!sceneCollection)
		throw "Failed to get scene collection name";

	ret = snprintf(fileName, 512, "obs-studio/basic/scenes/%s.json",
J
jp9000 已提交
1524
		       sceneCollection);
1525
	if (ret <= 0)
J
jp9000 已提交
1526 1527 1528 1529 1530
		throw "Failed to create scene collection file name";

	ret = GetConfigPath(savePath, sizeof(savePath), fileName);
	if (ret <= 0)
		throw "Failed to get scene collection json file path";
1531

1532 1533
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
1534
	if (!ResetAudio())
1535 1536
		throw "Failed to initialize audio";

1537
	ret = ResetVideo();
1538 1539 1540 1541 1542

	switch (ret) {
	case OBS_VIDEO_MODULE_NOT_FOUND:
		throw "Failed to initialize video:  Graphics module not found";
	case OBS_VIDEO_NOT_SUPPORTED:
1543
		throw UNSUPPORTED_ERROR;
1544 1545 1546 1547
	case OBS_VIDEO_INVALID_PARAM:
		throw "Failed to initialize video:  Invalid parameters";
	default:
		if (ret != OBS_VIDEO_SUCCESS)
1548
			throw UNKNOWN_ERROR;
1549 1550
	}

1551
	/* load audio monitoring */
1552
#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
J
jp9000 已提交
1553 1554 1555 1556
	const char *device_name =
		config_get_string(basicConfig, "Audio", "MonitoringDeviceName");
	const char *device_id =
		config_get_string(basicConfig, "Audio", "MonitoringDeviceId");
1557 1558

	obs_set_audio_monitoring_device(device_name, device_id);
1559 1560

	blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s",
J
jp9000 已提交
1561
	     device_name, device_id);
1562 1563
#endif

1564
	InitOBSCallbacks();
P
Palana 已提交
1565
	InitHotkeys();
1566

1567
	AddExtraModulePaths();
1568
	blog(LOG_INFO, "---------------------------------");
J
jp9000 已提交
1569
	obs_load_all_modules();
1570 1571
	blog(LOG_INFO, "---------------------------------");
	obs_log_loaded_modules();
1572 1573
	blog(LOG_INFO, "---------------------------------");
	obs_post_load_modules();
J
jp9000 已提交
1574

1575
#ifdef BROWSER_AVAILABLE
J
jp9000 已提交
1576
	cef = obs_browser_init_panel();
J
jp9000 已提交
1577 1578
#endif

1579 1580
	InitBasicConfigDefaults2();

1581 1582
	CheckForSimpleModeX264Fallback();

1583
	blog(LOG_INFO, STARTUP_SEPARATOR);
1584

J
jp9000 已提交
1585
	ResetOutputs();
1586
	CreateHotkeys();
J
jp9000 已提交
1587

1588 1589 1590
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
1591 1592
	InitPrimitives();

J
jp9000 已提交
1593 1594 1595 1596 1597 1598
	sceneDuplicationMode = config_get_bool(
		App()->GlobalConfig(), "BasicWindow", "SceneDuplicationMode");
	swapScenesMode = config_get_bool(App()->GlobalConfig(), "BasicWindow",
					 "SwapScenesMode");
	editPropertiesMode = config_get_bool(
		App()->GlobalConfig(), "BasicWindow", "EditPropertiesMode");
C
cg2121 已提交
1599 1600 1601

	if (!opt_studio_mode) {
		SetPreviewProgramMode(config_get_bool(App()->GlobalConfig(),
J
jp9000 已提交
1602 1603
						      "BasicWindow",
						      "PreviewProgramMode"));
C
cg2121 已提交
1604 1605 1606 1607
	} else {
		SetPreviewProgramMode(true);
		opt_studio_mode = false;
	}
1608

J
jp9000 已提交
1609 1610 1611 1612
#define SET_VISIBILITY(name, control)                                         \
	do {                                                                  \
		if (config_has_user_value(App()->GlobalConfig(),              \
					  "BasicWindow", name)) {             \
1613
			bool visible = config_get_bool(App()->GlobalConfig(), \
J
jp9000 已提交
1614 1615 1616
						       "BasicWindow", name);  \
			ui->control->setChecked(visible);                     \
		}                                                             \
1617 1618 1619 1620 1621 1622
	} while (false)

	SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars);
	SET_VISIBILITY("ShowStatusBar", toggleStatusBar);
#undef SET_VISIBILITY

1623 1624 1625 1626 1627 1628 1629
	{
		ProfileScope("OBSBasic::Load");
		disableSaving--;
		Load(savePath);
		disableSaving++;
	}

J
jp9000 已提交
1630
	TimedCheckForUpdates();
1631
	loaded = true;
J
jp9000 已提交
1632

J
jp9000 已提交
1633 1634
	previewEnabled = config_get_bool(App()->GlobalConfig(), "BasicWindow",
					 "PreviewEnabled");
1635 1636 1637

	if (!previewEnabled && !IsPreviewProgramMode())
		QMetaObject::invokeMethod(this, "EnablePreviewDisplay",
J
jp9000 已提交
1638 1639
					  Qt::QueuedConnection,
					  Q_ARG(bool, previewEnabled));
1640 1641 1642 1643

#ifdef _WIN32
	uint32_t winVer = GetWindowsVersion();
	if (winVer > 0 && winVer < 0x602) {
J
jp9000 已提交
1644 1645
		bool disableAero =
			config_get_bool(basicConfig, "Video", "DisableAero");
1646 1647 1648
		SetAeroEnabled(!disableAero);
	}
#endif
J
jp9000 已提交
1649

1650
	RefreshSceneCollections();
J
jp9000 已提交
1651
	RefreshProfiles();
J
jp9000 已提交
1652
	disableSaving--;
1653

J
jp9000 已提交
1654
	auto addDisplay = [this](OBSQTDisplay *window) {
1655
		obs_display_add_draw_callback(window->GetDisplay(),
J
jp9000 已提交
1656
					      OBSBasic::RenderMain, this);
1657 1658 1659 1660 1661 1662 1663 1664

		struct obs_video_info ovi;
		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
	};

	connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay);

1665
#ifdef _WIN32
J
jp9000 已提交
1666
	SetWin32DropStyle(this);
1667 1668 1669 1670
	show();
#endif

	bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow",
J
jp9000 已提交
1671
					   "AlwaysOnTop");
1672
	if (alwaysOnTop || opt_always_on_top) {
1673 1674 1675 1676 1677
		SetAlwaysOnTop(this, true);
		ui->actionAlwaysOnTop->setChecked(true);
	}

#ifndef _WIN32
1678
	show();
1679
#endif
J
jp9000 已提交
1680

A
Alex Anderson 已提交
1681
	/* setup stats dock */
1682 1683
	OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false);
	statsDock->setWidget(statsDlg);
A
Alex Anderson 已提交
1684

J
jp9000 已提交
1685 1686
	const char *dockStateStr = config_get_string(
		App()->GlobalConfig(), "BasicWindow", "DockState");
J
jp9000 已提交
1687 1688
	if (!dockStateStr) {
		on_resetUI_triggered();
J
jp9000 已提交
1689
	} else {
J
jp9000 已提交
1690 1691 1692 1693
		QByteArray dockState =
			QByteArray::fromBase64(QByteArray(dockStateStr));
		if (!restoreState(dockState))
			on_resetUI_triggered();
J
jp9000 已提交
1694 1695
	}

J
jp9000 已提交
1696 1697
	bool pre23Defaults = config_get_bool(App()->GlobalConfig(), "General",
					     "Pre23Defaults");
1698
	if (pre23Defaults) {
J
jp9000 已提交
1699 1700
		bool resetDockLock23 = config_get_bool(
			App()->GlobalConfig(), "General", "ResetDockLock23");
1701
		if (!resetDockLock23) {
J
jp9000 已提交
1702 1703
			config_set_bool(App()->GlobalConfig(), "General",
					"ResetDockLock23", true);
1704
			config_remove_value(App()->GlobalConfig(),
J
jp9000 已提交
1705
					    "BasicWindow", "DocksLocked");
1706 1707 1708
			config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
		}
	}
J
jp9000 已提交
1709

J
jp9000 已提交
1710 1711
	bool docksLocked = config_get_bool(App()->GlobalConfig(), "BasicWindow",
					   "DocksLocked");
J
jp9000 已提交
1712 1713 1714 1715
	on_lockUI_toggled(docksLocked);
	ui->lockUI->blockSignals(true);
	ui->lockUI->setChecked(docksLocked);
	ui->lockUI->blockSignals(false);
C
cg2121 已提交
1716

1717
#ifndef __APPLE__
C
cg2121 已提交
1718
	SystemTray(true);
1719
#endif
C
cg2121 已提交
1720

1721 1722 1723
	if (windowState().testFlag(Qt::WindowFullScreen))
		fullscreenInterface = true;

J
jp9000 已提交
1724
	bool has_last_version = config_has_user_value(App()->GlobalConfig(),
J
jp9000 已提交
1725 1726 1727
						      "General", "LastVersion");
	bool first_run =
		config_get_bool(App()->GlobalConfig(), "General", "FirstRun");
J
jp9000 已提交
1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738

	if (!first_run) {
		config_set_bool(App()->GlobalConfig(), "General", "FirstRun",
				true);
		config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
	}

	if (!first_run && !has_last_version && !Active()) {
		QString msg;
		msg = QTStr("Basic.FirstStartup.RunWizard");

J
jp9000 已提交
1739 1740
		QMessageBox::StandardButton button = OBSMessageBox::question(
			this, QTStr("Basic.AutoConfig"), msg);
J
jp9000 已提交
1741 1742

		if (button == QMessageBox::Yes) {
J
jp9000 已提交
1743
			QMetaObject::invokeMethod(this,
J
jp9000 已提交
1744 1745
						  "on_autoConfigure_triggered",
						  Qt::QueuedConnection);
J
jp9000 已提交
1746 1747
		} else {
			msg = QTStr("Basic.FirstStartup.RunWizard.NoClicked");
J
jp9000 已提交
1748 1749
			OBSMessageBox::information(
				this, QTStr("Basic.AutoConfig"), msg);
J
jp9000 已提交
1750 1751
		}
	}
1752

1753
	ToggleMixerLayout(config_get_bool(App()->GlobalConfig(), "BasicWindow",
J
jp9000 已提交
1754
					  "VerticalVolControl"));
S
Shaolin 已提交
1755

1756 1757
	if (config_get_bool(basicConfig, "General", "OpenStatsOnStartup"))
		on_stats_triggered();
1758 1759

	OBSBasicStats::InitializeValues();
J
jp9000 已提交
1760 1761 1762 1763 1764 1765

	/* ----------------------- */
	/* Add multiview menu      */

	ui->viewMenu->addSeparator();

1766
	multiviewProjectorMenu = new QMenu(QTStr("MultiviewProjector"));
J
jp9000 已提交
1767
	ui->viewMenu->addMenu(multiviewProjectorMenu);
1768
	AddProjectorMenuMonitors(multiviewProjectorMenu, this,
J
jp9000 已提交
1769
				 SLOT(OpenMultiviewProjector()));
1770
	connect(ui->viewMenu->menuAction(), &QAction::hovered, this,
J
jp9000 已提交
1771 1772 1773
		&OBSBasic::UpdateMultiviewProjectorMenu);
	ui->viewMenu->addAction(QTStr("MultiviewWindowed"), this,
				SLOT(OpenMultiviewWindow()));
C
cg2121 已提交
1774 1775

#if !defined(_WIN32) && !defined(__APPLE__)
J
jp9000 已提交
1776 1777 1778
	delete ui->actionShowCrashLogs;
	delete ui->actionUploadLastCrashLog;
	delete ui->menuCrashLogs;
C
cg2121 已提交
1779
	delete ui->actionCheckForUpdates;
J
jp9000 已提交
1780 1781 1782
	ui->actionShowCrashLogs = nullptr;
	ui->actionUploadLastCrashLog = nullptr;
	ui->menuCrashLogs = nullptr;
C
cg2121 已提交
1783 1784
	ui->actionCheckForUpdates = nullptr;
#endif
1785

1786 1787
	OnFirstLoad();

1788
#ifdef __APPLE__
1789
	QMetaObject::invokeMethod(this, "DeferredSysTrayLoad",
J
jp9000 已提交
1790
				  Qt::QueuedConnection, Q_ARG(int, 10));
1791
#endif
1792 1793
}

1794 1795 1796 1797
void OBSBasic::OnFirstLoad()
{
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_FINISHED_LOADING);
J
jp9000 已提交
1798

1799
#if defined(BROWSER_AVAILABLE) && defined(_WIN32)
J
jp9000 已提交
1800
	/* Attempt to load init screen if available */
J
jp9000 已提交
1801
	if (cef) {
J
jp9000 已提交
1802 1803
		WhatsNewInfoThread *wnit = new WhatsNewInfoThread();
		if (wnit) {
J
jp9000 已提交
1804 1805
			connect(wnit, &WhatsNewInfoThread::Result, this,
				&OBSBasic::ReceivedIntroJson);
J
jp9000 已提交
1806 1807
		}
		if (wnit) {
1808
			introCheckThread.reset(wnit);
J
jp9000 已提交
1809 1810 1811 1812
			introCheckThread->start();
		}
	}
#endif
J
jp9000 已提交
1813 1814

	Auth::Load();
1815 1816
}

1817
void OBSBasic::DeferredSysTrayLoad(int requeueCount)
1818 1819
{
	if (--requeueCount > 0) {
1820
		QMetaObject::invokeMethod(this, "DeferredSysTrayLoad",
J
jp9000 已提交
1821 1822
					  Qt::QueuedConnection,
					  Q_ARG(int, requeueCount));
1823 1824 1825
		return;
	}

1826 1827 1828
	/* Minimizng to tray on initial startup does not work on mac
	 * unless it is done in the deferred load */
	SystemTray(true);
1829 1830
}

J
jp9000 已提交
1831 1832 1833
/* shows a "what's new" page on startup of new versions using CEF */
void OBSBasic::ReceivedIntroJson(const QString &text)
{
1834
#ifdef BROWSER_AVAILABLE
J
jp9000 已提交
1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849
#ifdef _WIN32
	std::string err;
	Json json = Json::parse(QT_TO_UTF8(text), err);
	if (!err.empty())
		return;

	std::string info_url;
	int info_increment = -1;

	/* check to see if there's an info page for this version */
	const Json::array &items = json.array_items();
	for (const Json &item : items) {
		const std::string &version = item["version"].string_value();
		const std::string &url = item["url"].string_value();
		int increment = item["increment"].int_value();
1850
		int rc = item["RC"].int_value();
J
jp9000 已提交
1851 1852 1853 1854 1855

		int major = 0;
		int minor = 0;

		sscanf(version.c_str(), "%d.%d", &major, &minor);
1856 1857 1858 1859 1860
#if OBS_RELEASE_CANDIDATE > 0
		if (major == OBS_RELEASE_CANDIDATE_MAJOR &&
		    minor == OBS_RELEASE_CANDIDATE_MINOR &&
		    rc == OBS_RELEASE_CANDIDATE) {
#else
J
jp9000 已提交
1861
		if (major == LIBOBS_API_MAJOR_VER &&
J
jp9000 已提交
1862
		    minor == LIBOBS_API_MINOR_VER && rc == 0) {
1863
#endif
J
jp9000 已提交
1864 1865 1866 1867 1868 1869 1870 1871 1872 1873
			info_url = url;
			info_increment = increment;
		}
	}

	/* this version was not found, or no info for this version */
	if (info_increment == -1) {
		return;
	}

1874 1875
#if OBS_RELEASE_CANDIDATE > 0
	uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
J
jp9000 已提交
1876
					      "LastRCVersion");
1877
#else
J
jp9000 已提交
1878 1879
	uint32_t lastVersion =
		config_get_int(App()->GlobalConfig(), "General", "LastVersion");
1880
#endif
J
jp9000 已提交
1881 1882 1883

	int current_version_increment = -1;

1884 1885 1886
#if OBS_RELEASE_CANDIDATE > 0
	if (lastVersion < OBS_RELEASE_CANDIDATE_VER) {
#else
1887
	if ((lastVersion & ~0xFFFF) < (LIBOBS_API_VER & ~0xFFFF)) {
1888
#endif
J
jp9000 已提交
1889
		config_set_int(App()->GlobalConfig(), "General",
J
jp9000 已提交
1890
			       "InfoIncrement", -1);
J
jp9000 已提交
1891 1892
	} else {
		current_version_increment = config_get_int(
J
jp9000 已提交
1893
			App()->GlobalConfig(), "General", "InfoIncrement");
J
jp9000 已提交
1894 1895 1896 1897 1898 1899
	}

	if (info_increment <= current_version_increment) {
		return;
	}

J
jp9000 已提交
1900 1901
	config_set_int(App()->GlobalConfig(), "General", "InfoIncrement",
		       info_increment);
J
jp9000 已提交
1902

1903 1904 1905 1906 1907 1908 1909
	/* Don't show What's New dialog for new users */
#if !defined(OBS_RELEASE_CANDIDATE) || OBS_RELEASE_CANDIDATE == 0
	if (!lastVersion) {
		return;
	}
#endif
	cef->init_browser();
J
jp9000 已提交
1910
	ExecuteFuncSafeBlock([] { cef->wait_for_browser_init(); });
1911

J
jp9000 已提交
1912 1913 1914 1915
	QDialog *dlg = new QDialog(this);
	dlg->setAttribute(Qt::WA_DeleteOnClose, true);
	dlg->setWindowTitle("What's New");
	dlg->resize(700, 600);
J
jp9000 已提交
1916

1917 1918 1919 1920
	Qt::WindowFlags flags = dlg->windowFlags();
	Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint;
	dlg->setWindowFlags(flags & (~helpFlag));

J
jp9000 已提交
1921
	QCefWidget *cefWidget = cef->create_widget(nullptr, info_url);
J
jp9000 已提交
1922 1923 1924 1925
	if (!cefWidget) {
		return;
	}

J
jp9000 已提交
1926 1927
	connect(cefWidget, SIGNAL(titleChanged(const QString &)), dlg,
		SLOT(setWindowTitle(const QString &)));
J
jp9000 已提交
1928 1929

	QPushButton *close = new QPushButton(QTStr("Close"));
J
jp9000 已提交
1930
	connect(close, &QAbstractButton::clicked, dlg, &QDialog::accept);
J
jp9000 已提交
1931 1932 1933 1934 1935 1936

	QHBoxLayout *bottomLayout = new QHBoxLayout();
	bottomLayout->addStretch();
	bottomLayout->addWidget(close);
	bottomLayout->addStretch();

J
jp9000 已提交
1937
	QVBoxLayout *topLayout = new QVBoxLayout(dlg);
J
jp9000 已提交
1938 1939 1940
	topLayout->addWidget(cefWidget);
	topLayout->addLayout(bottomLayout);

J
jp9000 已提交
1941
	dlg->show();
J
jp9000 已提交
1942 1943 1944
#else
	UNUSED_PARAMETER(text);
#endif
1945 1946 1947
#else
	UNUSED_PARAMETER(text);
#endif
J
jp9000 已提交
1948 1949
}

1950 1951 1952 1953
void OBSBasic::UpdateMultiviewProjectorMenu()
{
	multiviewProjectorMenu->clear();
	AddProjectorMenuMonitors(multiviewProjectorMenu, this,
J
jp9000 已提交
1954
				 SLOT(OpenMultiviewProjector()));
1955 1956
}

P
Palana 已提交
1957 1958
void OBSBasic::InitHotkeys()
{
P
Palana 已提交
1959 1960
	ProfileScope("OBSBasic::InitHotkeys");

P
Palana 已提交
1961
	struct obs_hotkeys_translations t = {};
J
jp9000 已提交
1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978
	t.insert = Str("Hotkeys.Insert");
	t.del = Str("Hotkeys.Delete");
	t.home = Str("Hotkeys.Home");
	t.end = Str("Hotkeys.End");
	t.page_up = Str("Hotkeys.PageUp");
	t.page_down = Str("Hotkeys.PageDown");
	t.num_lock = Str("Hotkeys.NumLock");
	t.scroll_lock = Str("Hotkeys.ScrollLock");
	t.caps_lock = Str("Hotkeys.CapsLock");
	t.backspace = Str("Hotkeys.Backspace");
	t.tab = Str("Hotkeys.Tab");
	t.print = Str("Hotkeys.Print");
	t.pause = Str("Hotkeys.Pause");
	t.left = Str("Hotkeys.Left");
	t.right = Str("Hotkeys.Right");
	t.up = Str("Hotkeys.Up");
	t.down = Str("Hotkeys.Down");
P
Palana 已提交
1979
#ifdef _WIN32
J
jp9000 已提交
1980
	t.meta = Str("Hotkeys.Windows");
P
Palana 已提交
1981
#else
J
jp9000 已提交
1982
	t.meta = Str("Hotkeys.Super");
P
Palana 已提交
1983
#endif
J
jp9000 已提交
1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000
	t.menu = Str("Hotkeys.Menu");
	t.space = Str("Hotkeys.Space");
	t.numpad_num = Str("Hotkeys.NumpadNum");
	t.numpad_multiply = Str("Hotkeys.NumpadMultiply");
	t.numpad_divide = Str("Hotkeys.NumpadDivide");
	t.numpad_plus = Str("Hotkeys.NumpadAdd");
	t.numpad_minus = Str("Hotkeys.NumpadSubtract");
	t.numpad_decimal = Str("Hotkeys.NumpadDecimal");
	t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum");
	t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply");
	t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide");
	t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd");
	t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract");
	t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal");
	t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual");
	t.mouse_num = Str("Hotkeys.MouseButton");
	t.escape = Str("Hotkeys.Escape");
P
Palana 已提交
2001 2002 2003
	obs_hotkeys_set_translations(&t);

	obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"),
J
jp9000 已提交
2004 2005
						   Str("Push-to-mute"),
						   Str("Push-to-talk"));
P
Palana 已提交
2006

J
jp9000 已提交
2007 2008
	obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"),
						       Str("SceneItemHide"));
P
Palana 已提交
2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020

	obs_hotkey_enable_callback_rerouting(true);
	obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this);
}

void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed)
{
	obs_hotkey_trigger_routed_callback(id, pressed);
}

void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed)
{
J
jp9000 已提交
2021
	OBSBasic &basic = *static_cast<OBSBasic *>(data);
P
Palana 已提交
2022
	QMetaObject::invokeMethod(&basic, "ProcessHotkey",
J
jp9000 已提交
2023 2024
				  Q_ARG(obs_hotkey_id, id),
				  Q_ARG(bool, pressed));
P
Palana 已提交
2025 2026
}

2027 2028
void OBSBasic::CreateHotkeys()
{
P
Palana 已提交
2029 2030
	ProfileScope("OBSBasic::CreateHotkeys");

J
jp9000 已提交
2031 2032 2033
	auto LoadHotkeyData = [&](const char *name) -> OBSData {
		const char *info =
			config_get_string(basicConfig, "Hotkeys", name);
2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045
		if (!info)
			return {};

		obs_data_t *data = obs_data_create_from_json(info);
		if (!data)
			return {};

		OBSData res = data;
		obs_data_release(data);
		return res;
	};

J
jp9000 已提交
2046
	auto LoadHotkey = [&](obs_hotkey_id id, const char *name) {
J
jp9000 已提交
2047 2048 2049 2050 2051 2052 2053
		obs_data_array_t *array =
			obs_data_get_array(LoadHotkeyData(name), "bindings");

		obs_hotkey_load(id, array);
		obs_data_array_release(array);
	};

2054
	auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0,
J
jp9000 已提交
2055
				  const char *name1) {
2056 2057 2058 2059 2060 2061 2062 2063 2064 2065
		obs_data_array_t *array0 =
			obs_data_get_array(LoadHotkeyData(name0), "bindings");
		obs_data_array_t *array1 =
			obs_data_get_array(LoadHotkeyData(name1), "bindings");

		obs_hotkey_pair_load(id, array0, array1);
		obs_data_array_release(array0);
		obs_data_array_release(array1);
	};

J
jp9000 已提交
2066 2067 2068 2069 2070 2071 2072 2073 2074
#define MAKE_CALLBACK(pred, method, log_action)                            \
	[](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \
		OBSBasic &basic = *static_cast<OBSBasic *>(data);          \
		if ((pred) && pressed) {                                   \
			blog(LOG_INFO, log_action " due to hotkey");       \
			method();                                          \
			return true;                                       \
		}                                                          \
		return false;                                              \
2075 2076 2077
	}

	streamingHotkeys = obs_hotkey_pair_register_frontend(
J
jp9000 已提交
2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091
		"OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"),
		"OBSBasic.StopStreaming", Str("Basic.Main.StopStreaming"),
		MAKE_CALLBACK(!basic.outputHandler->StreamingActive() &&
				      basic.ui->streamButton->isEnabled(),
			      basic.StartStreaming, "Starting stream"),
		MAKE_CALLBACK(basic.outputHandler->StreamingActive() &&
				      basic.ui->streamButton->isEnabled(),
			      basic.StopStreaming, "Stopping stream"),
		this, this);
	LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming",
		       "OBSBasic.StopStreaming");

	auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
		OBSBasic &basic = *static_cast<OBSBasic *>(data);
J
jp9000 已提交
2092 2093 2094 2095 2096 2097
		if (basic.outputHandler->StreamingActive() && pressed) {
			basic.ForceStopStreaming();
		}
	};

	forceStreamingStopHotkey = obs_hotkey_register_frontend(
J
jp9000 已提交
2098 2099 2100
		"OBSBasic.ForceStopStreaming",
		Str("Basic.Main.ForceStopStreaming"), cb, this);
	LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming");
J
jp9000 已提交
2101

2102
	recordingHotkeys = obs_hotkey_pair_register_frontend(
J
jp9000 已提交
2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113
		"OBSBasic.StartRecording", Str("Basic.Main.StartRecording"),
		"OBSBasic.StopRecording", Str("Basic.Main.StopRecording"),
		MAKE_CALLBACK(!basic.outputHandler->RecordingActive() &&
				      !basic.ui->recordButton->isChecked(),
			      basic.StartRecording, "Starting recording"),
		MAKE_CALLBACK(basic.outputHandler->RecordingActive() &&
				      basic.ui->recordButton->isChecked(),
			      basic.StopRecording, "Stopping recording"),
		this, this);
	LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording",
		       "OBSBasic.StopRecording");
J
jp9000 已提交
2114

J
jp9000 已提交
2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125
	pauseHotkeys = obs_hotkey_pair_register_frontend(
		"OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"),
		"OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"),
		MAKE_CALLBACK(basic.pause && !basic.pause->isChecked(),
			      basic.PauseRecording, "Pausing recording"),
		MAKE_CALLBACK(basic.pause && basic.pause->isChecked(),
			      basic.UnpauseRecording, "Unpausing recording"),
		this, this);
	LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording",
		       "OBSBasic.UnpauseRecording");

J
jp9000 已提交
2126
	replayBufHotkeys = obs_hotkey_pair_register_frontend(
J
jp9000 已提交
2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137
		"OBSBasic.StartReplayBuffer",
		Str("Basic.Main.StartReplayBuffer"),
		"OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"),
		MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(),
			      basic.StartReplayBuffer,
			      "Starting replay buffer"),
		MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(),
			      basic.StopReplayBuffer, "Stopping replay buffer"),
		this, this);
	LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer",
		       "OBSBasic.StopReplayBuffer");
2138 2139

	togglePreviewHotkeys = obs_hotkey_pair_register_frontend(
J
jp9000 已提交
2140 2141 2142 2143 2144 2145 2146 2147 2148 2149
		"OBSBasic.EnablePreview",
		Str("Basic.Main.PreviewConextMenu.Enable"),
		"OBSBasic.DisablePreview", Str("Basic.Main.Preview.Disable"),
		MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview,
			      "Enabling preview"),
		MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview,
			      "Disabling preview"),
		this, this);
	LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview",
		       "OBSBasic.DisablePreview");
2150
#undef MAKE_CALLBACK
2151

J
jp9000 已提交
2152 2153
	auto togglePreviewProgram = [](void *data, obs_hotkey_id,
				       obs_hotkey_t *, bool pressed) {
2154
		if (pressed)
J
jp9000 已提交
2155 2156 2157
			QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
						  "on_modeSwitch_clicked",
						  Qt::QueuedConnection);
2158 2159 2160
	};

	togglePreviewProgramHotkey = obs_hotkey_register_frontend(
J
jp9000 已提交
2161 2162 2163
		"OBSBasic.TogglePreviewProgram",
		Str("Basic.TogglePreviewProgramMode"), togglePreviewProgram,
		this);
2164 2165
	LoadHotkey(togglePreviewProgramHotkey, "OBSBasic.TogglePreviewProgram");

J
jp9000 已提交
2166 2167
	auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *,
			     bool pressed) {
2168
		if (pressed)
J
jp9000 已提交
2169 2170 2171
			QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
						  "TransitionClicked",
						  Qt::QueuedConnection);
2172 2173 2174
	};

	transitionHotkey = obs_hotkey_register_frontend(
J
jp9000 已提交
2175
		"OBSBasic.Transition", Str("Transition"), transition, this);
2176
	LoadHotkey(transitionHotkey, "OBSBasic.Transition");
2177 2178
}

J
jp9000 已提交
2179 2180 2181 2182
void OBSBasic::ClearHotkeys()
{
	obs_hotkey_pair_unregister(streamingHotkeys);
	obs_hotkey_pair_unregister(recordingHotkeys);
J
jp9000 已提交
2183
	obs_hotkey_pair_unregister(pauseHotkeys);
J
jp9000 已提交
2184
	obs_hotkey_pair_unregister(replayBufHotkeys);
2185
	obs_hotkey_pair_unregister(togglePreviewHotkeys);
J
jp9000 已提交
2186
	obs_hotkey_unregister(forceStreamingStopHotkey);
2187 2188
	obs_hotkey_unregister(togglePreviewProgramHotkey);
	obs_hotkey_unregister(transitionHotkey);
J
jp9000 已提交
2189 2190
}

2191 2192
OBSBasic::~OBSBasic()
{
J
jp9000 已提交
2193 2194 2195
	if (updateCheckThread && updateCheckThread->isRunning())
		updateCheckThread->wait();

2196
	delete multiviewProjectorMenu;
P
pkv 已提交
2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209
	delete previewProjector;
	delete studioProgramProjector;
	delete previewProjectorSource;
	delete previewProjectorMain;
	delete sourceProjector;
	delete sceneProjectorMenu;
	delete scaleFilteringMenu;
	delete colorMenu;
	delete colorWidgetAction;
	delete colorSelect;
	delete deinterlaceMenu;
	delete perSceneTransitionMenu;
	delete shortcutFilter;
P
pkviet 已提交
2210
	delete trayMenu;
2211 2212
	delete programOptions;
	delete program;
J
jp9000 已提交
2213

J
jp9000 已提交
2214 2215 2216 2217 2218 2219
	/* XXX: any obs data must be released before calling obs_shutdown.
	 * currently, we can't automate this with C++ RAII because of the
	 * delicate nature of obs_shutdown needing to be freed before the UI
	 * can be freed, and we have no control over the destruction order of
	 * the Qt UI stuff, so we have to manually clear any references to
	 * libobs. */
J
jp9000 已提交
2220
	delete cpuUsageTimer;
2221 2222
	os_cpu_usage_info_destroy(cpuUsageInfo);

P
Palana 已提交
2223
	obs_hotkey_set_callback_routing_func(nullptr, nullptr);
J
jp9000 已提交
2224
	ClearHotkeys();
P
Palana 已提交
2225

2226
	service = nullptr;
J
jp9000 已提交
2227 2228
	outputHandler.reset();

J
John Bradley 已提交
2229 2230 2231
	if (interaction)
		delete interaction;

2232 2233 2234
	if (properties)
		delete properties;

J
jp9000 已提交
2235 2236 2237
	if (filters)
		delete filters;

2238 2239
	if (transformWindow)
		delete transformWindow;
2240

J
jp9000 已提交
2241 2242 2243
	if (advAudioWindow)
		delete advAudioWindow;

C
cg2121 已提交
2244 2245 2246
	if (about)
		delete about;

2247
	obs_display_remove_draw_callback(ui->preview->GetDisplay(),
J
jp9000 已提交
2248
					 OBSBasic::RenderMain, this);
2249

J
jp9000 已提交
2250
	obs_enter_graphics();
2251
	gs_vertexbuffer_destroy(box);
2252 2253 2254 2255
	gs_vertexbuffer_destroy(boxLeft);
	gs_vertexbuffer_destroy(boxTop);
	gs_vertexbuffer_destroy(boxRight);
	gs_vertexbuffer_destroy(boxBottom);
2256
	gs_vertexbuffer_destroy(circle);
J
jp9000 已提交
2257
	obs_leave_graphics();
J
jp9000 已提交
2258

2259 2260 2261 2262 2263 2264 2265 2266 2267
	/* When shutting down, sometimes source references can get in to the
	 * event queue, and if we don't forcibly process those events they
	 * won't get processed until after obs_shutdown has been called.  I
	 * really wish there were a more elegant way to deal with this via C++,
	 * but Qt doesn't use C++ in a normal way, so you can't really rely on
	 * normal C++ behavior for your data to be freed in the order that you
	 * expect or want it to. */
	QApplication::sendPostedEvents(this);

J
jp9000 已提交
2268
	config_set_int(App()->GlobalConfig(), "General", "LastVersion",
J
jp9000 已提交
2269
		       LIBOBS_API_VER);
2270 2271
#if OBS_RELEASE_CANDIDATE > 0
	config_set_int(App()->GlobalConfig(), "General", "LastRCVersion",
J
jp9000 已提交
2272
		       OBS_RELEASE_CANDIDATE_VER);
2273
#endif
J
jp9000 已提交
2274

2275
	bool alwaysOnTop = IsAlwaysOnTop(this);
J
jp9000 已提交
2276

J
jp9000 已提交
2277 2278
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled",
			previewEnabled);
2279 2280
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
			alwaysOnTop);
2281 2282
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"SceneDuplicationMode", sceneDuplicationMode);
J
jp9000 已提交
2283 2284
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "SwapScenesMode",
			swapScenesMode);
2285 2286 2287 2288
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"EditPropertiesMode", editPropertiesMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"PreviewProgramMode", IsPreviewProgramMode());
J
jp9000 已提交
2289 2290
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "DocksLocked",
			ui->lockUI->isChecked());
2291
	config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
2292 2293 2294 2295

#ifdef _WIN32
	uint32_t winVer = GetWindowsVersion();
	if (winVer > 0 && winVer < 0x602) {
J
jp9000 已提交
2296 2297
		bool disableAero =
			config_get_bool(basicConfig, "Video", "DisableAero");
2298 2299 2300 2301 2302
		if (disableAero) {
			SetAeroEnabled(true);
		}
	}
#endif
J
jp9000 已提交
2303 2304

#ifdef BROWSER_AVAILABLE
2305
	DestroyPanelCookieManager();
J
jp9000 已提交
2306 2307 2308
	delete cef;
	cef = nullptr;
#endif
2309 2310
}

J
jp9000 已提交
2311 2312 2313 2314 2315 2316 2317 2318 2319
void OBSBasic::SaveProjectNow()
{
	if (disableSaving)
		return;

	projectChanged = true;
	SaveProjectDeferred();
}

J
jp9000 已提交
2320 2321
void OBSBasic::SaveProject()
{
J
jp9000 已提交
2322 2323 2324
	if (disableSaving)
		return;

J
jp9000 已提交
2325 2326
	projectChanged = true;
	QMetaObject::invokeMethod(this, "SaveProjectDeferred",
J
jp9000 已提交
2327
				  Qt::QueuedConnection);
J
jp9000 已提交
2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339
}

void OBSBasic::SaveProjectDeferred()
{
	if (disableSaving)
		return;

	if (!projectChanged)
		return;

	projectChanged = false;

J
jp9000 已提交
2340 2341
	const char *sceneCollection = config_get_string(
		App()->GlobalConfig(), "Basic", "SceneCollectionFile");
2342
	char savePath[512];
J
jp9000 已提交
2343 2344 2345 2346 2347 2348 2349
	char fileName[512];
	int ret;

	if (!sceneCollection)
		return;

	ret = snprintf(fileName, 512, "obs-studio/basic/scenes/%s.json",
J
jp9000 已提交
2350
		       sceneCollection);
J
jp9000 已提交
2351 2352 2353 2354
	if (ret <= 0)
		return;

	ret = GetConfigPath(savePath, sizeof(savePath), fileName);
2355 2356 2357
	if (ret <= 0)
		return;

J
jp9000 已提交
2358 2359 2360
	Save(savePath);
}

S
Shaolin 已提交
2361 2362 2363 2364 2365
OBSSource OBSBasic::GetProgramSource()
{
	return OBSGetStrongRef(programScene);
}

J
jp9000 已提交
2366
OBSScene OBSBasic::GetCurrentScene()
2367
{
J
jp9000 已提交
2368
	QListWidgetItem *item = ui->scenes->currentItem();
P
Palana 已提交
2369
	return item ? GetOBSRef<OBSScene>(item) : nullptr;
2370 2371
}

2372
OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
J
jp9000 已提交
2373
{
P
Palana 已提交
2374
	return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
J
jp9000 已提交
2375 2376
}

2377 2378
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
J
jp9000 已提交
2379
	return ui->sources->Get(GetTopSelectedSourceItem());
2380 2381
}

J
Joseph El-Khouri 已提交
2382 2383
void OBSBasic::UpdatePreviewScalingMenu()
{
2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397
	bool fixedScaling = ui->preview->IsFixedScaling();
	float scalingAmount = ui->preview->GetScalingAmount();
	if (!fixedScaling) {
		ui->actionScaleWindow->setChecked(true);
		ui->actionScaleCanvas->setChecked(false);
		ui->actionScaleOutput->setChecked(false);
		return;
	}

	obs_video_info ovi;
	obs_get_video_info(&ovi);

	ui->actionScaleWindow->setChecked(false);
	ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f);
J
jp9000 已提交
2398 2399 2400
	ui->actionScaleOutput->setChecked(scalingAmount ==
					  float(ovi.output_width) /
						  float(ovi.base_width));
J
Joseph El-Khouri 已提交
2401 2402
}

2403
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
J
John Bradley 已提交
2404 2405 2406 2407 2408 2409 2410 2411 2412
{
	if (interaction)
		interaction->close();

	interaction = new OBSBasicInteraction(this, source);
	interaction->Init();
	interaction->setAttribute(Qt::WA_DeleteOnClose, true);
}

2413
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
2414 2415 2416 2417 2418 2419 2420
{
	if (properties)
		properties->close();

	properties = new OBSBasicProperties(this, source);
	properties->Init();
	properties->setAttribute(Qt::WA_DeleteOnClose, true);
2421 2422
}

J
jp9000 已提交
2423 2424 2425 2426 2427 2428 2429 2430 2431 2432
void OBSBasic::CreateFiltersWindow(obs_source_t *source)
{
	if (filters)
		filters->close();

	filters = new OBSBasicFilters(this, source);
	filters->Init();
	filters->setAttribute(Qt::WA_DeleteOnClose, true);
}

2433 2434 2435
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
2436
{
J
jp9000 已提交
2437
	const char *name = obs_source_get_name(source);
2438
	obs_scene_t *scene = obs_scene_from_source(source);
J
jp9000 已提交
2439 2440

	QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name));
P
Palana 已提交
2441
	SetOBSRef(item, OBSScene(scene));
J
jp9000 已提交
2442
	ui->scenes->addItem(item);
2443

J
jp9000 已提交
2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458
	obs_hotkey_register_source(
		source, "OBSBasic.SelectScene",
		Str("Basic.Hotkeys.SelectScene"),
		[](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
			OBSBasic *main = reinterpret_cast<OBSBasic *>(
				App()->GetMainWindow());

			auto potential_source =
				static_cast<obs_source_t *>(data);
			auto source = obs_source_get_ref(potential_source);
			if (source && pressed)
				main->SetCurrentScene(source);
			obs_source_release(source);
		},
		static_cast<obs_source_t *>(source));
P
Palana 已提交
2459

2460
	signal_handler_t *handler = obs_source_get_signal_handler(source);
2461

J
jp9000 已提交
2462 2463 2464
	SignalContainer<OBSScene> container;
	container.ref = scene;
	container.handlers.assign({
2465
		std::make_shared<OBSSignal>(handler, "item_add",
J
jp9000 已提交
2466
					    OBSBasic::SceneItemAdded, this),
2467
		std::make_shared<OBSSignal>(handler, "item_select",
J
jp9000 已提交
2468
					    OBSBasic::SceneItemSelected, this),
2469
		std::make_shared<OBSSignal>(handler, "item_deselect",
J
jp9000 已提交
2470 2471
					    OBSBasic::SceneItemDeselected,
					    this),
2472
		std::make_shared<OBSSignal>(handler, "reorder",
J
jp9000 已提交
2473
					    OBSBasic::SceneReordered, this),
J
jp9000 已提交
2474
	});
2475 2476

	item->setData(static_cast<int>(QtDataRole::OBSSignals),
J
jp9000 已提交
2477
		      QVariant::fromValue(container));
J
jp9000 已提交
2478

2479
	/* if the scene already has items (a duplicated scene) add them */
J
jp9000 已提交
2480
	auto addSceneItem = [this](obs_sceneitem_t *item) {
2481 2482 2483 2484 2485
		AddSceneItem(item);
	};

	using addSceneItem_t = decltype(addSceneItem);

J
jp9000 已提交
2486 2487 2488 2489 2490 2491 2492 2493 2494
	obs_scene_enum_items(
		scene,
		[](obs_scene_t *, obs_sceneitem_t *item, void *param) {
			addSceneItem_t *func;
			func = reinterpret_cast<addSceneItem_t *>(param);
			(*func)(item);
			return true;
		},
		&addSceneItem);
2495

J
jp9000 已提交
2496
	SaveProject();
2497 2498 2499 2500

	if (!disableSaving) {
		obs_source_t *source = obs_scene_get_source(scene);
		blog(LOG_INFO, "User added scene '%s'",
J
jp9000 已提交
2501
		     obs_source_get_name(source));
S
Shaolin 已提交
2502 2503

		OBSProjector::UpdateMultiviewProjectors();
2504
	}
2505 2506 2507

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
2508 2509
}

2510
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
2511
{
P
Palana 已提交
2512 2513 2514 2515
	obs_scene_t *scene = obs_scene_from_source(source);

	QListWidgetItem *sel = nullptr;
	int count = ui->scenes->count();
2516

P
Palana 已提交
2517 2518 2519 2520 2521
	for (int i = 0; i < count; i++) {
		auto item = ui->scenes->item(i);
		auto cur_scene = GetOBSRef<OBSScene>(item);
		if (cur_scene != scene)
			continue;
J
jp9000 已提交
2522

P
Palana 已提交
2523 2524 2525
		sel = item;
		break;
	}
J
jp9000 已提交
2526

J
jp9000 已提交
2527
	if (sel != nullptr) {
P
Palana 已提交
2528
		if (sel == ui->scenes->currentItem())
J
jp9000 已提交
2529
			ui->sources->Clear();
J
jp9000 已提交
2530
		delete sel;
J
jp9000 已提交
2531
	}
J
jp9000 已提交
2532 2533

	SaveProject();
2534 2535 2536

	if (!disableSaving) {
		blog(LOG_INFO, "User Removed scene '%s'",
J
jp9000 已提交
2537
		     obs_source_get_name(source));
S
Shaolin 已提交
2538 2539

		OBSProjector::UpdateMultiviewProjectors();
2540
	}
2541 2542 2543

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
2544 2545
}

J
jp9000 已提交
2546 2547 2548
static bool select_one(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
{
	obs_sceneitem_t *selectedItem =
J
jp9000 已提交
2549
		reinterpret_cast<obs_sceneitem_t *>(param);
J
jp9000 已提交
2550 2551 2552 2553 2554 2555 2556 2557 2558
	if (obs_sceneitem_is_group(item))
		obs_sceneitem_group_enum_items(item, select_one, param);

	obs_sceneitem_select(item, (selectedItem == item));

	UNUSED_PARAMETER(scene);
	return true;
}

2559
void OBSBasic::AddSceneItem(OBSSceneItem item)
2560
{
J
jp9000 已提交
2561
	obs_scene_t *scene = obs_sceneitem_get_scene(item);
J
jp9000 已提交
2562

2563
	if (GetCurrentScene() == scene)
J
jp9000 已提交
2564
		ui->sources->Add(item);
J
jp9000 已提交
2565

J
jp9000 已提交
2566
	SaveProject();
2567 2568 2569 2570 2571

	if (!disableSaving) {
		obs_source_t *sceneSource = obs_scene_get_source(scene);
		obs_source_t *itemSource = obs_sceneitem_get_source(item);
		blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'",
J
jp9000 已提交
2572 2573 2574 2575 2576 2577
		     obs_source_get_name(itemSource),
		     obs_source_get_id(itemSource),
		     obs_source_get_name(sceneSource));

		obs_scene_enum_items(scene, select_one,
				     (obs_sceneitem_t *)item);
2578
	}
2579 2580
}

2581
void OBSBasic::UpdateSceneSelection(OBSSource source)
2582 2583
{
	if (source) {
2584
		obs_scene_t *scene = obs_scene_from_source(source);
2585
		const char *name = obs_source_get_name(source);
J
jp9000 已提交
2586

2587 2588 2589
		if (!scene)
			return;

J
jp9000 已提交
2590
		QList<QListWidgetItem *> items =
J
jp9000 已提交
2591 2592
			ui->scenes->findItems(QT_UTF8(name), Qt::MatchExactly);

2593 2594 2595 2596 2597
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

2598 2599 2600
			OBSScene curScene =
				GetOBSRef<OBSScene>(ui->scenes->currentItem());
			if (api && scene != curScene)
J
jp9000 已提交
2601 2602
				api->on_event(
					OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
2603
		}
J
jp9000 已提交
2604
	}
2605 2606
}

J
jp9000 已提交
2607
static void RenameListValues(QListWidget *listWidget, const QString &newName,
J
jp9000 已提交
2608
			     const QString &prevName)
J
jp9000 已提交
2609
{
J
jp9000 已提交
2610
	QList<QListWidgetItem *> items =
J
jp9000 已提交
2611 2612 2613 2614 2615 2616
		listWidget->findItems(prevName, Qt::MatchExactly);

	for (int i = 0; i < items.count(); i++)
		items[i]->setText(newName);
}

S
Shaolin 已提交
2617
void OBSBasic::RenameSources(OBSSource source, QString newName,
J
jp9000 已提交
2618
			     QString prevName)
J
jp9000 已提交
2619
{
J
jp9000 已提交
2620
	RenameListValues(ui->scenes, newName, prevName);
2621 2622 2623 2624 2625

	for (size_t i = 0; i < volumes.size(); i++) {
		if (volumes[i]->GetName().compare(prevName) == 0)
			volumes[i]->SetName(newName);
	}
J
jp9000 已提交
2626

2627
	OBSProjector::RenameProjector(prevName, newName);
C
cg2121 已提交
2628

J
jp9000 已提交
2629
	SaveProject();
S
Shaolin 已提交
2630 2631 2632 2633

	obs_scene_t *scene = obs_scene_from_source(source);
	if (scene)
		OBSProjector::UpdateMultiviewProjectors();
J
jp9000 已提交
2634 2635
}

2636 2637
void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
{
J
jp9000 已提交
2638 2639
	SignalBlocker sourcesSignalBlocker(ui->sources);

2640
	if (scene != GetCurrentScene() || ignoreSelectionUpdate)
2641 2642
		return;

J
jp9000 已提交
2643
	ui->sources->SelectItem(item, select);
2644 2645
}

2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661
static inline bool SourceMixerHidden(obs_source_t *source)
{
	obs_data_t *priv_settings = obs_source_get_private_settings(source);
	bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden");
	obs_data_release(priv_settings);

	return hidden;
}

static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden)
{
	obs_data_t *priv_settings = obs_source_get_private_settings(source);
	obs_data_set_bool(priv_settings, "mixer_hidden", hidden);
	obs_data_release(priv_settings);
}

2662 2663
void OBSBasic::GetAudioSourceFilters()
{
J
jp9000 已提交
2664 2665
	QAction *action = reinterpret_cast<QAction *>(sender());
	VolControl *vol = action->property("volControl").value<VolControl *>();
2666 2667 2668 2669 2670 2671 2672
	obs_source_t *source = vol->GetSource();

	CreateFiltersWindow(source);
}

void OBSBasic::GetAudioSourceProperties()
{
J
jp9000 已提交
2673 2674
	QAction *action = reinterpret_cast<QAction *>(sender());
	VolControl *vol = action->property("volControl").value<VolControl *>();
2675 2676 2677 2678 2679
	obs_source_t *source = vol->GetSource();

	CreatePropertiesWindow(source);
}

2680 2681
void OBSBasic::HideAudioControl()
{
J
jp9000 已提交
2682 2683
	QAction *action = reinterpret_cast<QAction *>(sender());
	VolControl *vol = action->property("volControl").value<VolControl *>();
2684 2685 2686 2687 2688 2689 2690 2691 2692 2693
	obs_source_t *source = vol->GetSource();

	if (!SourceMixerHidden(source)) {
		SetSourceMixerHidden(source, true);
		DeactivateAudioSource(source);
	}
}

void OBSBasic::UnhideAllAudioControls()
{
J
jp9000 已提交
2694
	auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */
2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707
	{
		if (!obs_source_active(source))
			return true;
		if (!SourceMixerHidden(source))
			return true;

		SetSourceMixerHidden(source, false);
		ActivateAudioSource(source);
		return true;
	};

	using UnhideAudioMixer_t = decltype(UnhideAudioMixer);

J
jp9000 已提交
2708 2709
	auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */
	{ return (*reinterpret_cast<UnhideAudioMixer_t *>(data))(source); };
2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727

	obs_enum_sources(PreEnum, &UnhideAudioMixer);
}

void OBSBasic::ToggleHideMixer()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (!SourceMixerHidden(source)) {
		SetSourceMixerHidden(source, true);
		DeactivateAudioSource(source);
	} else {
		SetSourceMixerHidden(source, false);
		ActivateAudioSource(source);
	}
}

2728 2729
void OBSBasic::MixerRenameSource()
{
J
jp9000 已提交
2730 2731
	QAction *action = reinterpret_cast<QAction *>(sender());
	VolControl *vol = action->property("volControl").value<VolControl *>();
2732 2733 2734 2735 2736 2737
	OBSSource source = vol->GetSource();

	const char *prevName = obs_source_get_name(source);

	for (;;) {
		string name;
J
jp9000 已提交
2738 2739 2740 2741
		bool accepted = NameDialog::AskForName(
			this, QTStr("Basic.Main.MixerRename.Title"),
			QTStr("Basic.Main.MixerRename.Text"), name,
			QT_UTF8(prevName));
2742 2743 2744 2745
		if (!accepted)
			return;

		if (name.empty()) {
2746
			OBSMessageBox::warning(this,
J
jp9000 已提交
2747 2748
					       QTStr("NoNameEntered.Title"),
					       QTStr("NoNameEntered.Text"));
2749 2750 2751
			continue;
		}

2752 2753
		OBSSource sourceTest = obs_get_source_by_name(name.c_str());
		obs_source_release(sourceTest);
2754 2755

		if (sourceTest) {
J
jp9000 已提交
2756 2757
			OBSMessageBox::warning(this, QTStr("NameExists.Title"),
					       QTStr("NameExists.Text"));
2758 2759 2760 2761 2762 2763 2764 2765
			continue;
		}

		obs_source_set_name(source, name.c_str());
		break;
	}
}

2766 2767
void OBSBasic::VolControlContextMenu()
{
J
jp9000 已提交
2768
	VolControl *vol = reinterpret_cast<VolControl *>(sender());
2769

2770 2771 2772 2773
	/* ------------------- */

	QAction hideAction(QTStr("Hide"), this);
	QAction unhideAllAction(QTStr("UnhideAll"), this);
2774
	QAction mixerRenameAction(QTStr("Rename"), this);
2775

2776 2777 2778
	QAction copyFiltersAction(QTStr("Copy.Filters"), this);
	QAction pasteFiltersAction(QTStr("Paste.Filters"), this);

2779 2780
	QAction filtersAction(QTStr("Filters"), this);
	QAction propertiesAction(QTStr("Properties"), this);
2781
	QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this);
2782

S
Shaolin 已提交
2783 2784
	QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
	toggleControlLayoutAction.setCheckable(true);
J
jp9000 已提交
2785 2786
	toggleControlLayoutAction.setChecked(config_get_bool(
		GetGlobalConfig(), "BasicWindow", "VerticalVolControl"));
S
Shaolin 已提交
2787

2788 2789
	/* ------------------- */

J
jp9000 已提交
2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808
	connect(&hideAction, &QAction::triggered, this,
		&OBSBasic::HideAudioControl, Qt::DirectConnection);
	connect(&unhideAllAction, &QAction::triggered, this,
		&OBSBasic::UnhideAllAudioControls, Qt::DirectConnection);
	connect(&mixerRenameAction, &QAction::triggered, this,
		&OBSBasic::MixerRenameSource, Qt::DirectConnection);

	connect(&copyFiltersAction, &QAction::triggered, this,
		&OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection);
	connect(&pasteFiltersAction, &QAction::triggered, this,
		&OBSBasic::AudioMixerPasteFilters, Qt::DirectConnection);

	connect(&filtersAction, &QAction::triggered, this,
		&OBSBasic::GetAudioSourceFilters, Qt::DirectConnection);
	connect(&propertiesAction, &QAction::triggered, this,
		&OBSBasic::GetAudioSourceProperties, Qt::DirectConnection);
	connect(&advPropAction, &QAction::triggered, this,
		&OBSBasic::on_actionAdvAudioProperties_triggered,
		Qt::DirectConnection);
2809

2810 2811
	/* ------------------- */

S
Shaolin 已提交
2812
	connect(&toggleControlLayoutAction, &QAction::changed, this,
J
jp9000 已提交
2813
		&OBSBasic::ToggleVolControlLayout, Qt::DirectConnection);
S
Shaolin 已提交
2814 2815 2816

	/* ------------------- */

2817
	hideAction.setProperty("volControl",
J
jp9000 已提交
2818
			       QVariant::fromValue<VolControl *>(vol));
2819
	mixerRenameAction.setProperty("volControl",
J
jp9000 已提交
2820
				      QVariant::fromValue<VolControl *>(vol));
2821

2822
	copyFiltersAction.setProperty("volControl",
J
jp9000 已提交
2823
				      QVariant::fromValue<VolControl *>(vol));
2824
	pasteFiltersAction.setProperty("volControl",
J
jp9000 已提交
2825
				       QVariant::fromValue<VolControl *>(vol));
2826

2827
	filtersAction.setProperty("volControl",
J
jp9000 已提交
2828
				  QVariant::fromValue<VolControl *>(vol));
2829
	propertiesAction.setProperty("volControl",
J
jp9000 已提交
2830
				     QVariant::fromValue<VolControl *>(vol));
2831

2832 2833
	/* ------------------- */

2834 2835 2836 2837 2838
	if (copyFiltersString == nullptr)
		pasteFiltersAction.setEnabled(false);
	else
		pasteFiltersAction.setEnabled(true);

2839
	QMenu popup;
2840 2841
	popup.addAction(&unhideAllAction);
	popup.addAction(&hideAction);
2842
	popup.addAction(&mixerRenameAction);
2843
	popup.addSeparator();
2844 2845 2846
	popup.addAction(&copyFiltersAction);
	popup.addAction(&pasteFiltersAction);
	popup.addSeparator();
S
Shaolin 已提交
2847 2848
	popup.addAction(&toggleControlLayoutAction);
	popup.addSeparator();
2849 2850
	popup.addAction(&filtersAction);
	popup.addAction(&propertiesAction);
2851
	popup.addAction(&advPropAction);
2852 2853 2854
	popup.exec(QCursor::pos());
}

S
Shaolin 已提交
2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865
void OBSBasic::on_hMixerScrollArea_customContextMenuRequested()
{
	StackedMixerAreaContextMenuRequested();
}

void OBSBasic::on_vMixerScrollArea_customContextMenuRequested()
{
	StackedMixerAreaContextMenuRequested();
}

void OBSBasic::StackedMixerAreaContextMenuRequested()
2866 2867
{
	QAction unhideAllAction(QTStr("UnhideAll"), this);
S
SuslikV 已提交
2868 2869 2870

	QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this);

S
Shaolin 已提交
2871 2872
	QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
	toggleControlLayoutAction.setCheckable(true);
J
jp9000 已提交
2873 2874
	toggleControlLayoutAction.setChecked(config_get_bool(
		GetGlobalConfig(), "BasicWindow", "VerticalVolControl"));
S
Shaolin 已提交
2875

S
SuslikV 已提交
2876 2877
	/* ------------------- */

J
jp9000 已提交
2878 2879
	connect(&unhideAllAction, &QAction::triggered, this,
		&OBSBasic::UnhideAllAudioControls, Qt::DirectConnection);
2880

J
jp9000 已提交
2881 2882 2883
	connect(&advPropAction, &QAction::triggered, this,
		&OBSBasic::on_actionAdvAudioProperties_triggered,
		Qt::DirectConnection);
S
SuslikV 已提交
2884 2885 2886

	/* ------------------- */

S
Shaolin 已提交
2887
	connect(&toggleControlLayoutAction, &QAction::changed, this,
J
jp9000 已提交
2888
		&OBSBasic::ToggleVolControlLayout, Qt::DirectConnection);
S
Shaolin 已提交
2889 2890 2891

	/* ------------------- */

2892
	QMenu popup;
2893
	popup.addAction(&unhideAllAction);
S
SuslikV 已提交
2894
	popup.addSeparator();
S
Shaolin 已提交
2895 2896
	popup.addAction(&toggleControlLayoutAction);
	popup.addSeparator();
S
SuslikV 已提交
2897
	popup.addAction(&advPropAction);
2898 2899 2900
	popup.exec(QCursor::pos());
}

2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911
void OBSBasic::ToggleMixerLayout(bool vertical)
{
	if (vertical) {
		ui->stackedMixerArea->setMinimumSize(180, 220);
		ui->stackedMixerArea->setCurrentIndex(1);
	} else {
		ui->stackedMixerArea->setMinimumSize(220, 0);
		ui->stackedMixerArea->setCurrentIndex(0);
	}
}

S
Shaolin 已提交
2912 2913 2914
void OBSBasic::ToggleVolControlLayout()
{
	bool vertical = !config_get_bool(GetGlobalConfig(), "BasicWindow",
J
jp9000 已提交
2915
					 "VerticalVolControl");
S
Shaolin 已提交
2916 2917
	config_set_bool(GetGlobalConfig(), "BasicWindow", "VerticalVolControl",
			vertical);
2918
	ToggleMixerLayout(vertical);
S
Shaolin 已提交
2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931

	// We need to store it so we can delete current and then add
	// at the right order
	vector<OBSSource> sources;
	for (size_t i = 0; i != volumes.size(); i++)
		sources.emplace_back(volumes[i]->GetSource());

	ClearVolumeControls();

	for (const auto &source : sources)
		ActivateAudioSource(source);
}

2932 2933
void OBSBasic::ActivateAudioSource(OBSSource source)
{
2934 2935 2936
	if (SourceMixerHidden(source))
		return;

S
Shaolin 已提交
2937
	bool vertical = config_get_bool(GetGlobalConfig(), "BasicWindow",
J
jp9000 已提交
2938
					"VerticalVolControl");
S
Shaolin 已提交
2939
	VolControl *vol = new VolControl(source, true, vertical);
2940

J
jp9000 已提交
2941 2942
	double meterDecayRate =
		config_get_double(basicConfig, "Audio", "MeterDecayRate");
S
Shaolin 已提交
2943
	vol->SetMeterDecayRate(meterDecayRate);
2944

J
jp9000 已提交
2945 2946
	uint32_t peakMeterTypeIdx =
		config_get_uint(basicConfig, "Audio", "PeakMeterType");
2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962

	enum obs_peak_meter_type peakMeterType;
	switch (peakMeterTypeIdx) {
	case 0:
		peakMeterType = SAMPLE_PEAK_METER;
		break;
	case 1:
		peakMeterType = TRUE_PEAK_METER;
		break;
	default:
		peakMeterType = SAMPLE_PEAK_METER;
		break;
	}

	vol->setPeakMeterType(peakMeterType);

2963 2964
	vol->setContextMenuPolicy(Qt::CustomContextMenu);

J
jp9000 已提交
2965 2966 2967 2968
	connect(vol, &QWidget::customContextMenuRequested, this,
		&OBSBasic::VolControlContextMenu);
	connect(vol, &VolControl::ConfigClicked, this,
		&OBSBasic::VolControlContextMenu);
2969

2970 2971 2972
	InsertQObjectByName(volumes, vol);

	for (auto volume : volumes) {
S
Shaolin 已提交
2973 2974 2975 2976
		if (vertical)
			ui->vVolControlLayout->addWidget(volume);
		else
			ui->hVolControlLayout->addWidget(volume);
2977
	}
2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990
}

void OBSBasic::DeactivateAudioSource(OBSSource source)
{
	for (size_t i = 0; i < volumes.size(); i++) {
		if (volumes[i]->GetSource() == source) {
			delete volumes[i];
			volumes.erase(volumes.begin() + i);
			break;
		}
	}
}

2991
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
J
jp9000 已提交
2992
{
J
jp9000 已提交
2993
	if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE &&
2994
	    !obs_source_is_group(source)) {
2995 2996 2997
		int count = ui->scenes->count();

		if (count == 1) {
2998
			OBSMessageBox::information(this,
J
jp9000 已提交
2999 3000
						   QTStr("FinalScene.Title"),
						   QTStr("FinalScene.Text"));
3001 3002
			return false;
		}
3003 3004
	}

J
jp9000 已提交
3005
	const char *name = obs_source_get_name(source);
3006 3007 3008

	QString text = QTStr("ConfirmRemove.Text");
	text.replace("$1", QT_UTF8(name));
J
jp9000 已提交
3009

3010
	QMessageBox remove_source(this);
3011
	remove_source.setText(text);
J
jp9000 已提交
3012 3013
	QAbstractButton *Yes =
		remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole);
J
Jkoan 已提交
3014 3015 3016 3017 3018 3019
	remove_source.addButton(QTStr("No"), QMessageBox::NoRole);
	remove_source.setIcon(QMessageBox::Question);
	remove_source.setWindowTitle(QTStr("ConfirmRemove.Title"));
	remove_source.exec();

	return Yes == remove_source.clickedButton();
3020
}
J
jp9000 已提交
3021

J
jp9000 已提交
3022
#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */
J
jp9000 已提交
3023

P
Palana 已提交
3024 3025 3026 3027 3028
#ifdef UPDATE_SPARKLE
void init_sparkle_updater(bool update_to_undeployed);
void trigger_sparkle_update();
#endif

J
jp9000 已提交
3029 3030
void OBSBasic::TimedCheckForUpdates()
{
J
jp9000 已提交
3031
	if (!config_get_bool(App()->GlobalConfig(), "General",
J
jp9000 已提交
3032
			     "EnableAutoUpdates"))
J
jp9000 已提交
3033 3034
		return;

P
Palana 已提交
3035 3036
#ifdef UPDATE_SPARKLE
	init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
J
jp9000 已提交
3037
					     "UpdateToUndeployed"));
3038
#elif _WIN32
J
jp9000 已提交
3039
	long long lastUpdate = config_get_int(App()->GlobalConfig(), "General",
J
jp9000 已提交
3040 3041 3042
					      "LastUpdateCheck");
	uint32_t lastVersion =
		config_get_int(App()->GlobalConfig(), "General", "LastVersion");
J
jp9000 已提交
3043 3044 3045 3046

	if (lastVersion < LIBOBS_API_VER) {
		lastUpdate = 0;
		config_set_int(App()->GlobalConfig(), "General",
J
jp9000 已提交
3047
			       "LastUpdateCheck", 0);
J
jp9000 已提交
3048 3049
	}

J
jp9000 已提交
3050
	long long t = (long long)time(nullptr);
J
jp9000 已提交
3051 3052 3053
	long long secs = t - lastUpdate;

	if (secs > UPDATE_CHECK_INTERVAL)
J
jp9000 已提交
3054
		CheckForUpdates(false);
P
Palana 已提交
3055
#endif
J
jp9000 已提交
3056 3057
}

J
jp9000 已提交
3058
void OBSBasic::CheckForUpdates(bool manualUpdate)
J
jp9000 已提交
3059
{
P
Palana 已提交
3060 3061
#ifdef UPDATE_SPARKLE
	trigger_sparkle_update();
3062
#elif _WIN32
J
jp9000 已提交
3063 3064
	ui->actionCheckForUpdates->setEnabled(false);

J
jp9000 已提交
3065 3066
	if (updateCheckThread && updateCheckThread->isRunning())
		return;
3067

3068
	updateCheckThread.reset(new AutoUpdateThread(manualUpdate));
3069
	updateCheckThread->start();
P
Palana 已提交
3070
#endif
3071 3072

	UNUSED_PARAMETER(manualUpdate);
J
jp9000 已提交
3073 3074
}

J
jp9000 已提交
3075
void OBSBasic::updateCheckFinished()
J
jp9000 已提交
3076 3077 3078 3079
{
	ui->actionCheckForUpdates->setEnabled(true);
}

3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100
void OBSBasic::DuplicateSelectedScene()
{
	OBSScene curScene = GetCurrentScene();

	if (!curScene)
		return;

	OBSSource curSceneSource = obs_scene_get_source(curScene);
	QString format{obs_source_get_name(curSceneSource)};
	format += " %1";

	int i = 2;
	QString placeHolderText = format.arg(i);
	obs_source_t *source = nullptr;
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
		placeHolderText = format.arg(++i);
	}

	for (;;) {
		string name;
J
jp9000 已提交
3101 3102 3103 3104
		bool accepted = NameDialog::AskForName(
			this, QTStr("Basic.Main.AddSceneDlg.Title"),
			QTStr("Basic.Main.AddSceneDlg.Text"), name,
			placeHolderText);
3105 3106 3107 3108
		if (!accepted)
			return;

		if (name.empty()) {
3109
			OBSMessageBox::warning(this,
J
jp9000 已提交
3110 3111
					       QTStr("NoNameEntered.Title"),
					       QTStr("NoNameEntered.Text"));
3112 3113 3114 3115 3116
			continue;
		}

		obs_source_t *source = obs_get_source_by_name(name.c_str());
		if (source) {
J
jp9000 已提交
3117 3118
			OBSMessageBox::warning(this, QTStr("NameExists.Title"),
					       QTStr("NameExists.Text"));
3119 3120 3121 3122 3123

			obs_source_release(source);
			continue;
		}

J
jp9000 已提交
3124 3125
		obs_scene_t *scene = obs_scene_duplicate(curScene, name.c_str(),
							 OBS_SCENE_DUP_REFS);
3126
		source = obs_scene_get_source(scene);
3127
		SetCurrentScene(source, true);
3128
		obs_scene_release(scene);
J
jp9000 已提交
3129

3130
		break;
3131 3132 3133
	}
}

3134 3135 3136 3137
void OBSBasic::RemoveSelectedScene()
{
	OBSScene scene = GetCurrentScene();
	if (scene) {
3138
		obs_source_t *source = obs_scene_get_source(scene);
J
jp9000 已提交
3139
		if (QueryRemoveSource(source)) {
3140
			obs_source_remove(source);
J
jp9000 已提交
3141 3142

			if (api)
J
jp9000 已提交
3143 3144
				api->on_event(
					OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
J
jp9000 已提交
3145
		}
3146 3147 3148 3149 3150 3151 3152
	}
}

void OBSBasic::RemoveSelectedSceneItem()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (item) {
3153
		obs_source_t *source = obs_sceneitem_get_source(item);
3154
		if (QueryRemoveSource(source))
J
jp9000 已提交
3155 3156 3157 3158
			obs_sceneitem_remove(item);
	}
}

3159 3160
void OBSBasic::ReorderSources(OBSScene scene)
{
3161
	if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
3162 3163
		return;

J
jp9000 已提交
3164
	ui->sources->ReorderItems();
J
jp9000 已提交
3165
	SaveProject();
3166 3167
}

3168 3169
/* OBS Callbacks */

3170 3171
void OBSBasic::SceneReordered(void *data, calldata_t *params)
{
J
jp9000 已提交
3172
	OBSBasic *window = static_cast<OBSBasic *>(data);
3173

J
jp9000 已提交
3174
	obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
3175 3176

	QMetaObject::invokeMethod(window, "ReorderSources",
J
jp9000 已提交
3177
				  Q_ARG(OBSScene, OBSScene(scene)));
3178 3179
}

3180
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
3181
{
J
jp9000 已提交
3182
	OBSBasic *window = static_cast<OBSBasic *>(data);
3183

J
jp9000 已提交
3184
	obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item");
J
jp9000 已提交
3185

3186
	QMetaObject::invokeMethod(window, "AddSceneItem",
J
jp9000 已提交
3187
				  Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
3188 3189
}

3190 3191
void OBSBasic::SceneItemSelected(void *data, calldata_t *params)
{
J
jp9000 已提交
3192
	OBSBasic *window = static_cast<OBSBasic *>(data);
3193

J
jp9000 已提交
3194 3195
	obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
	obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item");
3196 3197

	QMetaObject::invokeMethod(window, "SelectSceneItem",
J
jp9000 已提交
3198 3199
				  Q_ARG(OBSScene, scene),
				  Q_ARG(OBSSceneItem, item), Q_ARG(bool, true));
3200 3201 3202 3203
}

void OBSBasic::SceneItemDeselected(void *data, calldata_t *params)
{
J
jp9000 已提交
3204
	OBSBasic *window = static_cast<OBSBasic *>(data);
3205

J
jp9000 已提交
3206 3207
	obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
	obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item");
3208 3209

	QMetaObject::invokeMethod(window, "SelectSceneItem",
J
jp9000 已提交
3210 3211 3212
				  Q_ARG(OBSScene, scene),
				  Q_ARG(OBSSceneItem, item),
				  Q_ARG(bool, false));
3213 3214
}

3215
void OBSBasic::SourceCreated(void *data, calldata_t *params)
3216
{
J
jp9000 已提交
3217
	obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
3218

3219
	if (obs_scene_from_source(source) != NULL)
J
jp9000 已提交
3220 3221 3222
		QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
					  "AddScene", WaitConnection(),
					  Q_ARG(OBSSource, OBSSource(source)));
3223 3224
}

3225
void OBSBasic::SourceRemoved(void *data, calldata_t *params)
3226
{
J
jp9000 已提交
3227
	obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
3228

3229
	if (obs_scene_from_source(source) != NULL)
J
jp9000 已提交
3230 3231 3232
		QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
					  "RemoveScene",
					  Q_ARG(OBSSource, OBSSource(source)));
3233 3234
}

3235
void OBSBasic::SourceActivated(void *data, calldata_t *params)
3236
{
J
jp9000 已提交
3237 3238
	obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
	uint32_t flags = obs_source_get_output_flags(source);
3239 3240

	if (flags & OBS_SOURCE_AUDIO)
J
jp9000 已提交
3241 3242 3243
		QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
					  "ActivateAudioSource",
					  Q_ARG(OBSSource, OBSSource(source)));
3244 3245
}

3246
void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
3247
{
J
jp9000 已提交
3248 3249
	obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
	uint32_t flags = obs_source_get_output_flags(source);
3250 3251

	if (flags & OBS_SOURCE_AUDIO)
J
jp9000 已提交
3252 3253 3254
		QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
					  "DeactivateAudioSource",
					  Q_ARG(OBSSource, OBSSource(source)));
3255 3256
}

3257
void OBSBasic::SourceRenamed(void *data, calldata_t *params)
J
jp9000 已提交
3258
{
J
jp9000 已提交
3259 3260
	obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
	const char *newName = calldata_string(params, "new_name");
J
jp9000 已提交
3261 3262
	const char *prevName = calldata_string(params, "prev_name");

J
jp9000 已提交
3263 3264 3265 3266
	QMetaObject::invokeMethod(static_cast<OBSBasic *>(data),
				  "RenameSources", Q_ARG(OBSSource, source),
				  Q_ARG(QString, QT_UTF8(newName)),
				  Q_ARG(QString, QT_UTF8(prevName)));
3267 3268

	blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName);
J
jp9000 已提交
3269 3270
}

3271 3272 3273 3274 3275
void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

3276 3277
	GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop");

J
jp9000 已提交
3278 3279 3280
	gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
	gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
3281 3282 3283

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
3284
	gs_effect_set_vec4(color, &colorVal);
3285

3286 3287
	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
3288 3289 3290 3291 3292 3293 3294 3295
	gs_matrix_push();
	gs_matrix_identity();
	gs_matrix_scale3f(float(cx), float(cy), 1.0f);

	gs_load_vertexbuffer(box);
	gs_draw(GS_TRISTRIP, 0, 0);

	gs_matrix_pop();
3296 3297
	gs_technique_end_pass(tech);
	gs_technique_end(tech);
3298 3299

	gs_load_vertexbuffer(nullptr);
3300 3301

	GS_DEBUG_MARKER_END();
3302 3303
}

3304 3305
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
3306 3307
	GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain");

J
jp9000 已提交
3308
	OBSBasic *window = static_cast<OBSBasic *>(data);
3309 3310 3311 3312
	obs_video_info ovi;

	obs_get_video_info(&ovi);

J
jp9000 已提交
3313 3314
	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));
3315 3316 3317

	gs_viewport_push();
	gs_projection_push();
3318

3319 3320 3321
	obs_display_t *display = window->ui->preview->GetDisplay();
	uint32_t width, height;
	obs_display_size(display, &width, &height);
J
jp9000 已提交
3322
	float right = float(width) - window->previewX;
3323
	float bottom = float(height) - window->previewY;
3324

J
jp9000 已提交
3325 3326
	gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f,
		 100.0f);
3327 3328 3329

	window->ui->preview->DrawOverflow();

3330 3331
	/* --------------------------------------- */

3332
	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
J
jp9000 已提交
3333 3334 3335
		 -100.0f, 100.0f);
	gs_set_viewport(window->previewX, window->previewY, window->previewCX,
			window->previewCY);
3336

3337 3338
	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

3339 3340 3341 3342 3343 3344
	if (window->IsPreviewProgramMode()) {
		OBSScene scene = window->GetCurrentScene();
		obs_source_t *source = obs_scene_get_source(scene);
		if (source)
			obs_source_video_render(source);
	} else {
J
jp9000 已提交
3345
		obs_render_main_texture();
3346
	}
3347
	gs_load_vertexbuffer(nullptr);
3348

3349 3350
	/* --------------------------------------- */

J
jp9000 已提交
3351 3352
	gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f,
		 100.0f);
3353
	gs_reset_viewport();
J
jp9000 已提交
3354 3355 3356

	window->ui->preview->DrawSceneEditing();

3357 3358
	/* --------------------------------------- */

3359 3360
	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
3361

3362 3363
	GS_DEBUG_MARKER_END();

J
jp9000 已提交
3364 3365
	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
3366 3367
}

3368 3369
/* Main class functions */

3370
obs_service_t *OBSBasic::GetService()
3371
{
3372
	if (!service) {
J
jp9000 已提交
3373 3374
		service =
			obs_service_create("rtmp_common", NULL, NULL, nullptr);
3375 3376
		obs_service_release(service);
	}
3377 3378 3379
	return service;
}

3380
void OBSBasic::SetService(obs_service_t *newService)
3381
{
3382
	if (newService)
3383 3384 3385
		service = newService;
}

V
VodBox 已提交
3386 3387 3388 3389 3390
int OBSBasic::GetTransitionDuration()
{
	return ui->transitionDuration->value();
}

3391
bool OBSBasic::StreamingActive() const
3392 3393 3394 3395 3396 3397
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

3398 3399 3400 3401 3402 3403 3404
bool OBSBasic::Active() const
{
	if (!outputHandler)
		return false;
	return outputHandler->Active();
}

3405 3406 3407 3408 3409 3410
#ifdef _WIN32
#define IS_WIN32 1
#else
#define IS_WIN32 0
#endif

3411 3412
static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
3413
	return obs_reset_video(ovi);
3414 3415
}

3416 3417
static inline enum obs_scale_type GetScaleType(ConfigFile &basicConfig)
{
J
jp9000 已提交
3418 3419
	const char *scaleTypeStr =
		config_get_string(basicConfig, "Video", "ScaleType");
3420 3421 3422 3423 3424 3425 3426 3427 3428

	if (astrcmpi(scaleTypeStr, "bilinear") == 0)
		return OBS_SCALE_BILINEAR;
	else if (astrcmpi(scaleTypeStr, "lanczos") == 0)
		return OBS_SCALE_LANCZOS;
	else
		return OBS_SCALE_BICUBIC;
}

3429 3430 3431 3432 3433 3434
static inline enum video_format GetVideoFormatFromName(const char *name)
{
	if (astrcmpi(name, "I420") == 0)
		return VIDEO_FORMAT_I420;
	else if (astrcmpi(name, "NV12") == 0)
		return VIDEO_FORMAT_NV12;
J
jp9000 已提交
3435 3436
	else if (astrcmpi(name, "I444") == 0)
		return VIDEO_FORMAT_I444;
3437 3438 3439 3440 3441 3442 3443 3444 3445
#if 0 //currently unsupported
	else if (astrcmpi(name, "YVYU") == 0)
		return VIDEO_FORMAT_YVYU;
	else if (astrcmpi(name, "YUY2") == 0)
		return VIDEO_FORMAT_YUY2;
	else if (astrcmpi(name, "UYVY") == 0)
		return VIDEO_FORMAT_UYVY;
#endif
	else
J
jp9000 已提交
3446
		return VIDEO_FORMAT_RGBA;
3447 3448
}

3449 3450
void OBSBasic::ResetUI()
{
J
jp9000 已提交
3451 3452
	bool studioPortraitLayout = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "StudioPortraitLayout");
3453

J
jp9000 已提交
3454 3455
	bool labels = config_get_bool(GetGlobalConfig(), "BasicWindow",
				      "StudioModeLabels");
3456

3457 3458 3459 3460
	if (studioPortraitLayout)
		ui->previewLayout->setDirection(QBoxLayout::TopToBottom);
	else
		ui->previewLayout->setDirection(QBoxLayout::LeftToRight);
3461 3462 3463 3464 3465 3466

	if (previewProgramMode)
		ui->previewLabel->setHidden(!labels);

	if (programLabel)
		programLabel->setHidden(!labels);
3467 3468
}

3469
int OBSBasic::ResetVideo()
J
jp9000 已提交
3470
{
3471 3472 3473
	if (outputHandler && outputHandler->Active())
		return OBS_VIDEO_CURRENTLY_ACTIVE;

P
Palana 已提交
3474 3475
	ProfileScope("OBSBasic::ResetVideo");

J
jp9000 已提交
3476
	struct obs_video_info ovi;
3477
	int ret;
J
jp9000 已提交
3478

3479
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
3480

J
jp9000 已提交
3481 3482 3483 3484 3485 3486
	const char *colorFormat =
		config_get_string(basicConfig, "Video", "ColorFormat");
	const char *colorSpace =
		config_get_string(basicConfig, "Video", "ColorSpace");
	const char *colorRange =
		config_get_string(basicConfig, "Video", "ColorRange");
3487

J
jp9000 已提交
3488
	ovi.graphics_module = App()->GetRenderModule();
J
jp9000 已提交
3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503
	ovi.base_width =
		(uint32_t)config_get_uint(basicConfig, "Video", "BaseCX");
	ovi.base_height =
		(uint32_t)config_get_uint(basicConfig, "Video", "BaseCY");
	ovi.output_width =
		(uint32_t)config_get_uint(basicConfig, "Video", "OutputCX");
	ovi.output_height =
		(uint32_t)config_get_uint(basicConfig, "Video", "OutputCY");
	ovi.output_format = GetVideoFormatFromName(colorFormat);
	ovi.colorspace = astrcmpi(colorSpace, "601") == 0 ? VIDEO_CS_601
							  : VIDEO_CS_709;
	ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL
						      : VIDEO_RANGE_PARTIAL;
	ovi.adapter =
		config_get_uint(App()->GlobalConfig(), "Video", "AdapterIdx");
J
jp9000 已提交
3504
	ovi.gpu_conversion = true;
J
jp9000 已提交
3505
	ovi.scale_type = GetScaleType(basicConfig);
3506

3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522
	if (ovi.base_width == 0 || ovi.base_height == 0) {
		ovi.base_width = 1920;
		ovi.base_height = 1080;
		config_set_uint(basicConfig, "Video", "BaseCX", 1920);
		config_set_uint(basicConfig, "Video", "BaseCY", 1080);
	}

	if (ovi.output_width == 0 || ovi.output_height == 0) {
		ovi.output_width = ovi.base_width;
		ovi.output_height = ovi.base_height;
		config_set_uint(basicConfig, "Video", "OutputCX",
				ovi.base_width);
		config_set_uint(basicConfig, "Video", "OutputCY",
				ovi.base_height);
	}

3523
	ret = AttemptToResetVideo(&ovi);
3524
	if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
3525 3526
		if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
			blog(LOG_WARNING, "Tried to reset when "
J
jp9000 已提交
3527
					  "already active");
3528 3529 3530
			return ret;
		}

3531
		/* Try OpenGL if DirectX fails on windows */
3532
		if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
J
jp9000 已提交
3533 3534 3535 3536 3537
			blog(LOG_WARNING,
			     "Failed to initialize obs video (%d) "
			     "with graphics_module='%s', retrying "
			     "with graphics_module='%s'",
			     ret, ovi.graphics_module, DL_OPENGL);
3538
			ovi.graphics_module = DL_OPENGL;
3539 3540
			ret = AttemptToResetVideo(&ovi);
		}
3541 3542
	} else if (ret == OBS_VIDEO_SUCCESS) {
		ResizePreview(ovi.base_width, ovi.base_height);
3543 3544
		if (program)
			ResizeProgram(ovi.base_width, ovi.base_height);
3545 3546
	}

3547
	if (ret == OBS_VIDEO_SUCCESS) {
3548
		OBSBasicStats::InitializeValues();
3549 3550
		OBSProjector::UpdateMultiviewProjectors();
	}
3551

3552
	return ret;
J
jp9000 已提交
3553
}
J
jp9000 已提交
3554

3555
bool OBSBasic::ResetAudio()
J
jp9000 已提交
3556
{
P
Palana 已提交
3557 3558
	ProfileScope("OBSBasic::ResetAudio");

3559
	struct obs_audio_info ai;
J
jp9000 已提交
3560 3561
	ai.samples_per_sec =
		config_get_uint(basicConfig, "Audio", "SampleRate");
3562

J
jp9000 已提交
3563 3564
	const char *channelSetupStr =
		config_get_string(basicConfig, "Audio", "ChannelSetup");
3565 3566 3567

	if (strcmp(channelSetupStr, "Mono") == 0)
		ai.speakers = SPEAKERS_MONO;
P
pkviet 已提交
3568 3569
	else if (strcmp(channelSetupStr, "2.1") == 0)
		ai.speakers = SPEAKERS_2POINT1;
P
pkviet 已提交
3570 3571
	else if (strcmp(channelSetupStr, "4.0") == 0)
		ai.speakers = SPEAKERS_4POINT0;
P
pkviet 已提交
3572 3573 3574 3575 3576 3577
	else if (strcmp(channelSetupStr, "4.1") == 0)
		ai.speakers = SPEAKERS_4POINT1;
	else if (strcmp(channelSetupStr, "5.1") == 0)
		ai.speakers = SPEAKERS_5POINT1;
	else if (strcmp(channelSetupStr, "7.1") == 0)
		ai.speakers = SPEAKERS_7POINT1;
3578 3579 3580
	else
		ai.speakers = SPEAKERS_STEREO;

J
jp9000 已提交
3581
	return obs_reset_audio(&ai);
J
jp9000 已提交
3582 3583
}

3584
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
J
jp9000 已提交
3585
				const char *deviceDesc, int channel)
J
jp9000 已提交
3586
{
3587
	bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
3588 3589
	obs_source_t *source;
	obs_data_t *settings;
J
jp9000 已提交
3590 3591 3592

	source = obs_get_output_source(channel);
	if (source) {
3593 3594 3595 3596
		if (disable) {
			obs_set_output_source(channel, nullptr);
		} else {
			settings = obs_source_get_settings(source);
J
jp9000 已提交
3597 3598
			const char *oldId =
				obs_data_get_string(settings, "device_id");
3599 3600
			if (strcmp(oldId, deviceId) != 0) {
				obs_data_set_string(settings, "device_id",
J
jp9000 已提交
3601
						    deviceId);
3602 3603 3604 3605
				obs_source_update(source, settings);
			}
			obs_data_release(settings);
		}
J
jp9000 已提交
3606 3607 3608

		obs_source_release(source);

3609 3610
	} else if (!disable) {
		settings = obs_data_create();
J
jp9000 已提交
3611
		obs_data_set_string(settings, "device_id", deviceId);
3612
		source = obs_source_create(sourceId, deviceDesc, settings,
J
jp9000 已提交
3613
					   nullptr);
J
jp9000 已提交
3614 3615 3616 3617 3618 3619 3620
		obs_data_release(settings);

		obs_set_output_source(channel, source);
		obs_source_release(source);
	}
}

J
jp9000 已提交
3621
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
3622
{
J
jp9000 已提交
3623
	QSize targetSize;
3624
	bool isFixedScaling;
J
Joseph El-Khouri 已提交
3625
	obs_video_info ovi;
J
jp9000 已提交
3626

3627
	/* resize preview panel to fix to the top section of the window */
3628
	targetSize = GetPixelSize(ui->preview);
J
Joseph El-Khouri 已提交
3629

3630
	isFixedScaling = ui->preview->IsFixedScaling();
J
Joseph El-Khouri 已提交
3631 3632
	obs_get_video_info(&ovi);

3633 3634
	if (isFixedScaling) {
		previewScale = ui->preview->GetScalingAmount();
J
jp9000 已提交
3635 3636 3637 3638 3639
		GetCenterPosFromFixedScale(
			int(cx), int(cy),
			targetSize.width() - PREVIEW_EDGE_SIZE * 2,
			targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX,
			previewY, previewScale);
3640 3641
		previewX += ui->preview->GetScrollX();
		previewY += ui->preview->GetScrollY();
J
Joseph El-Khouri 已提交
3642 3643 3644

	} else {
		GetScaleAndCenterPos(int(cx), int(cy),
J
jp9000 已提交
3645 3646 3647 3648
				     targetSize.width() - PREVIEW_EDGE_SIZE * 2,
				     targetSize.height() -
					     PREVIEW_EDGE_SIZE * 2,
				     previewX, previewY, previewScale);
J
Joseph El-Khouri 已提交
3649
	}
J
jp9000 已提交
3650

3651 3652
	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);
J
jp9000 已提交
3653 3654
}

3655 3656
void OBSBasic::CloseDialogs()
{
J
jp9000 已提交
3657
	QList<QDialog *> childDialogs = this->findChildren<QDialog *>();
3658 3659 3660 3661 3662 3663
	if (!childDialogs.isEmpty()) {
		for (int i = 0; i < childDialogs.size(); ++i) {
			childDialogs.at(i)->close();
		}
	}

C
cg2121 已提交
3664 3665 3666 3667
	for (QPointer<QWidget> &projector : windowProjectors) {
		delete projector;
		projector.clear();
	}
3668 3669 3670 3671
	for (QPointer<QWidget> &projector : projectors) {
		delete projector;
		projector.clear();
	}
J
jp9000 已提交
3672

J
jp9000 已提交
3673 3674 3675 3676
	if (!stats.isNull())
		stats->close(); //call close to save Stats geometry
	if (!remux.isNull())
		remux->close();
3677 3678
}

3679 3680 3681 3682 3683 3684 3685
void OBSBasic::EnumDialogs()
{
	visDialogs.clear();
	modalDialogs.clear();
	visMsgBoxes.clear();

	/* fill list of Visible dialogs and Modal dialogs */
J
jp9000 已提交
3686
	QList<QDialog *> dialogs = findChildren<QDialog *>();
3687 3688 3689 3690 3691 3692 3693 3694
	for (QDialog *dialog : dialogs) {
		if (dialog->isVisible())
			visDialogs.append(dialog);
		if (dialog->isModal())
			modalDialogs.append(dialog);
	}

	/* fill list of Visible message boxes */
J
jp9000 已提交
3695
	QList<QMessageBox *> msgBoxes = findChildren<QMessageBox *>();
3696 3697 3698 3699 3700 3701
	for (QMessageBox *msgbox : msgBoxes) {
		if (msgbox->isVisible())
			visMsgBoxes.append(msgbox);
	}
}

3702 3703
void OBSBasic::ClearSceneData()
{
J
jp9000 已提交
3704 3705
	disableSaving++;

3706 3707 3708 3709
	CloseDialogs();

	ClearVolumeControls();
	ClearListItems(ui->scenes);
J
jp9000 已提交
3710
	ui->sources->Clear();
3711 3712
	ClearQuickTransitions();
	ui->transitions->clear();
3713 3714 3715 3716 3717 3718 3719

	obs_set_output_source(0, nullptr);
	obs_set_output_source(1, nullptr);
	obs_set_output_source(2, nullptr);
	obs_set_output_source(3, nullptr);
	obs_set_output_source(4, nullptr);
	obs_set_output_source(5, nullptr);
3720 3721 3722
	lastScene = nullptr;
	swapScene = nullptr;
	programScene = nullptr;
3723

J
jp9000 已提交
3724
	auto cb = [](void *unused, obs_source_t *source) {
3725 3726 3727 3728 3729 3730 3731
		obs_source_remove(source);
		UNUSED_PARAMETER(unused);
		return true;
	};

	obs_enum_sources(cb, nullptr);

3732 3733 3734
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP);

J
jp9000 已提交
3735
	disableSaving--;
3736 3737 3738

	blog(LOG_INFO, "All scene data cleared");
	blog(LOG_INFO, "------------------------------------------------");
3739 3740
}

J
jp9000 已提交
3741
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
3742
{
3743 3744 3745 3746 3747 3748 3749 3750 3751
	/* Do not close window if inside of a temporary event loop because we
	 * could be inside of an Auth::LoadUI call.  Keep trying once per
	 * second until we've exit any known sub-loops. */
	if (os_atomic_load_long(&insideEventLoop) != 0) {
		QTimer::singleShot(1000, this, SLOT(close()));
		event->ignore();
		return;
	}

3752
	if (isVisible())
J
jp9000 已提交
3753 3754 3755
		config_set_string(App()->GlobalConfig(), "BasicWindow",
				  "geometry",
				  saveGeometry().toBase64().constData());
3756

3757
	if (outputHandler && outputHandler->Active()) {
C
cg2121 已提交
3758 3759
		SetShowing(true);

3760
		QMessageBox::StandardButton button = OBSMessageBox::question(
J
jp9000 已提交
3761 3762
			this, QTStr("ConfirmExit.Title"),
			QTStr("ConfirmExit.Text"));
3763 3764 3765 3766 3767 3768 3769

		if (button == QMessageBox::No) {
			event->ignore();
			return;
		}
	}

3770 3771 3772 3773
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

3774 3775
	blog(LOG_INFO, SHUTDOWN_SEPARATOR);

J
jp9000 已提交
3776 3777
	if (introCheckThread)
		introCheckThread->wait();
3778 3779 3780 3781 3782
	if (updateCheckThread)
		updateCheckThread->wait();
	if (logUploadThread)
		logUploadThread->wait();

P
Palana 已提交
3783 3784
	signalHandlers.clear();

J
jp9000 已提交
3785
	Auth::Save();
J
jp9000 已提交
3786
	SaveProjectNow();
J
jp9000 已提交
3787 3788
	auth.reset();

J
jp9000 已提交
3789 3790
	config_set_string(App()->GlobalConfig(), "BasicWindow", "DockState",
			  saveState().toBase64().constData());
J
jp9000 已提交
3791 3792 3793 3794

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_EXIT);

J
jp9000 已提交
3795
	disableSaving++;
J
jp9000 已提交
3796

3797 3798 3799
	/* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
	 * sources, etc) so that all references are released before shutdown */
	ClearSceneData();
3800 3801

	App()->quit();
3802 3803
}

J
jp9000 已提交
3804
void OBSBasic::changeEvent(QEvent *event)
3805
{
J
jp9000 已提交
3806 3807
	if (event->type() == QEvent::WindowStateChange && isMinimized() &&
	    trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) {
3808 3809 3810

		ToggleShowHide();
	}
3811 3812
}

3813 3814
void OBSBasic::on_actionShow_Recordings_triggered()
{
3815
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
P
pkviet 已提交
3816
	const char *type = config_get_string(basicConfig, "AdvOut", "RecType");
J
jp9000 已提交
3817 3818 3819 3820 3821 3822 3823 3824 3825 3826
	const char *adv_path =
		strcmp(type, "Standard")
			? config_get_string(basicConfig, "AdvOut", "FFFilePath")
			: config_get_string(basicConfig, "AdvOut",
					    "RecFilePath");
	const char *path = strcmp(mode, "Advanced")
				   ? config_get_string(basicConfig,
						       "SimpleOutput",
						       "FilePath")
				   : adv_path;
3827 3828 3829
	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

P
Palana 已提交
3830 3831
void OBSBasic::on_actionRemux_triggered()
{
3832 3833 3834 3835 3836 3837
	if (!remux.isNull()) {
		remux->show();
		remux->raise();
		return;
	}

3838
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
J
jp9000 已提交
3839 3840 3841 3842 3843 3844
	const char *path = strcmp(mode, "Advanced")
				   ? config_get_string(basicConfig,
						       "SimpleOutput",
						       "FilePath")
				   : config_get_string(basicConfig, "AdvOut",
						       "RecFilePath");
3845 3846 3847 3848 3849

	OBSRemux *remuxDlg;
	remuxDlg = new OBSRemux(path, this);
	remuxDlg->show();
	remux = remuxDlg;
P
Palana 已提交
3850 3851
}

P
Palana 已提交
3852 3853
void OBSBasic::on_action_Settings_triggered()
{
3854 3855 3856 3857 3858 3859 3860
	static bool settings_already_executing = false;

	/* Do not load settings window if inside of a temporary event loop
	 * because we could be inside of an Auth::LoadUI call.  Keep trying
	 * once per second until we've exit any known sub-loops. */
	if (os_atomic_load_long(&insideEventLoop) != 0) {
		QTimer::singleShot(1000, this,
J
jp9000 已提交
3861
				   SLOT(on_action_Settings_triggered()));
3862 3863 3864 3865 3866 3867 3868 3869 3870
		return;
	}

	if (settings_already_executing) {
		return;
	}

	settings_already_executing = true;

P
Palana 已提交
3871 3872
	OBSBasicSettings settings(this);
	settings.exec();
C
cg2121 已提交
3873
	SystemTray(false);
3874 3875

	settings_already_executing = false;
P
Palana 已提交
3876 3877
}

J
jp9000 已提交
3878 3879
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
3880 3881 3882 3883 3884
	if (advAudioWindow != nullptr) {
		advAudioWindow->raise();
		return;
	}

J
jp9000 已提交
3885 3886 3887
	advAudioWindow = new OBSBasicAdvAudio(this);
	advAudioWindow->show();
	advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
3888

J
jp9000 已提交
3889 3890
	connect(advAudioWindow, SIGNAL(destroyed()), this,
		SLOT(on_advAudioProps_destroyed()));
J
jp9000 已提交
3891 3892
}

3893 3894 3895 3896 3897
void OBSBasic::on_advAudioProps_clicked()
{
	on_actionAdvAudioProperties_triggered();
}

3898 3899 3900 3901 3902
void OBSBasic::on_advAudioProps_destroyed()
{
	advAudioWindow = nullptr;
}

3903
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
J
jp9000 已提交
3904
					    QListWidgetItem *prev)
3905
{
3906
	obs_source_t *source = NULL;
J
jp9000 已提交
3907

3908 3909 3910 3911
	if (sceneChanging)
		return;

	if (current) {
3912
		obs_scene_t *scene;
J
jp9000 已提交
3913

P
Palana 已提交
3914
		scene = GetOBSRef<OBSScene>(current);
3915
		source = obs_scene_get_source(scene);
3916 3917
	}

3918
	SetCurrentScene(source);
3919

3920 3921 3922
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);

3923
	UNUSED_PARAMETER(prev);
3924 3925
}

J
jp9000 已提交
3926 3927
void OBSBasic::EditSceneName()
{
3928
	QListWidgetItem *item = ui->scenes->currentItem();
J
jp9000 已提交
3929
	Qt::ItemFlags flags = item->flags();
3930 3931 3932 3933

	item->setFlags(flags | Qt::ItemIsEditable);
	ui->scenes->editItem(item);
	item->setFlags(flags);
J
jp9000 已提交
3934 3935
}

J
jp9000 已提交
3936
static void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
J
jp9000 已提交
3937
				     const char *slot)
J
jp9000 已提交
3938 3939
{
	QAction *action;
J
jp9000 已提交
3940
	QList<QScreen *> screens = QGuiApplication::screens();
3941
	for (int i = 0; i < screens.size(); i++) {
3942
		QRect screenGeometry = screens[i]->geometry();
J
jp9000 已提交
3943 3944 3945 3946 3947 3948 3949
		QString str =
			QString("%1 %2: %3x%4 @ %5,%6")
				.arg(QTStr("Display"), QString::number(i + 1),
				     QString::number(screenGeometry.width()),
				     QString::number(screenGeometry.height()),
				     QString::number(screenGeometry.x()),
				     QString::number(screenGeometry.y()));
J
jp9000 已提交
3950 3951 3952 3953 3954 3955

		action = parent->addAction(str, target, slot);
		action->setProperty("monitor", i);
	}
}

J
jp9000 已提交
3956
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
3957
{
J
jp9000 已提交
3958 3959
	QListWidgetItem *item = ui->scenes->itemAt(pos);

3960
	QMenu popup(this);
J
jp9000 已提交
3961
	QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
J
jp9000 已提交
3962 3963
	popup.addAction(QTStr("Add"), this,
			SLOT(on_actionAddScene_triggered()));
J
jp9000 已提交
3964

P
Palana 已提交
3965
	if (item) {
J
jp9000 已提交
3966 3967
		QAction *pasteFilters =
			new QAction(QTStr("Paste.Filters"), this);
3968 3969
		pasteFilters->setEnabled(copyFiltersString);
		connect(pasteFilters, SIGNAL(triggered()), this,
J
jp9000 已提交
3970
			SLOT(ScenePasteFilters()));
3971

P
Palana 已提交
3972
		popup.addSeparator();
J
jp9000 已提交
3973 3974 3975 3976
		popup.addAction(QTStr("Duplicate"), this,
				SLOT(DuplicateSelectedScene()));
		popup.addAction(QTStr("Copy.Filters"), this,
				SLOT(SceneCopyFilters()));
3977 3978
		popup.addAction(pasteFilters);
		popup.addSeparator();
J
jp9000 已提交
3979 3980 3981
		popup.addAction(QTStr("Rename"), this, SLOT(EditSceneName()));
		popup.addAction(QTStr("Remove"), this,
				SLOT(RemoveSelectedScene()));
J
jp9000 已提交
3982
		popup.addSeparator();
J
jp9000 已提交
3983

J
jp9000 已提交
3984 3985
		order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this,
				SLOT(on_actionSceneUp_triggered()));
J
jp9000 已提交
3986 3987 3988 3989 3990 3991 3992 3993 3994 3995
		order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"),
				this, SLOT(on_actionSceneDown_triggered()));
		order.addSeparator();
		order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"),
				this, SLOT(MoveSceneToTop()));
		order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"),
				this, SLOT(MoveSceneToBottom()));
		popup.addMenu(&order);

		popup.addSeparator();
S
Shaolin 已提交
3996

P
pkv 已提交
3997
		delete sceneProjectorMenu;
J
jp9000 已提交
3998 3999
		sceneProjectorMenu = new QMenu(QTStr("SceneProjector"));
		AddProjectorMenuMonitors(sceneProjectorMenu, this,
J
jp9000 已提交
4000
					 SLOT(OpenSceneProjector()));
J
jp9000 已提交
4001
		popup.addMenu(sceneProjectorMenu);
C
cg2121 已提交
4002 4003

		QAction *sceneWindow = popup.addAction(
J
jp9000 已提交
4004
			QTStr("SceneWindow"), this, SLOT(OpenSceneWindow()));
C
cg2121 已提交
4005 4006

		popup.addAction(sceneWindow);
J
jp9000 已提交
4007
		popup.addSeparator();
J
jp9000 已提交
4008 4009
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenSceneFilters()));
4010 4011 4012

		popup.addSeparator();

P
pkv 已提交
4013 4014 4015
		delete perSceneTransitionMenu;
		perSceneTransitionMenu = CreatePerSceneTransitionMenu();
		popup.addMenu(perSceneTransitionMenu);
S
Shaolin 已提交
4016 4017 4018

		/* ---------------------- */

J
jp9000 已提交
4019 4020
		QAction *multiviewAction =
			popup.addAction(QTStr("ShowInMultiview"));
S
Shaolin 已提交
4021

J
jp9000 已提交
4022 4023 4024
		OBSSource source = GetCurrentSceneSource();
		OBSData data = obs_source_get_private_settings(source);
		obs_data_release(data);
S
Shaolin 已提交
4025

J
jp9000 已提交
4026
		obs_data_set_default_bool(data, "show_in_multiview", true);
J
jp9000 已提交
4027
		bool show = obs_data_get_bool(data, "show_in_multiview");
S
Shaolin 已提交
4028

J
jp9000 已提交
4029 4030
		multiviewAction->setCheckable(true);
		multiviewAction->setChecked(show);
S
Shaolin 已提交
4031

J
jp9000 已提交
4032 4033 4034 4035
		auto showInMultiview = [](OBSData data) {
			bool show =
				obs_data_get_bool(data, "show_in_multiview");
			obs_data_set_bool(data, "show_in_multiview", !show);
J
jp9000 已提交
4036 4037 4038 4039
			OBSProjector::UpdateMultiviewProjectors();
		};

		connect(multiviewAction, &QAction::triggered,
J
jp9000 已提交
4040
			std::bind(showInMultiview, data));
P
Palana 已提交
4041
	}
J
jp9000 已提交
4042 4043

	popup.exec(QCursor::pos());
4044 4045
}

J
jp9000 已提交
4046
void OBSBasic::on_actionAddScene_triggered()
4047
{
4048
	string name;
S
Socapex 已提交
4049
	QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
P
Palana 已提交
4050

4051
	int i = 2;
P
Palana 已提交
4052
	QString placeHolderText = format.arg(i);
4053
	obs_source_t *source = nullptr;
P
Palana 已提交
4054 4055
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
P
Palana 已提交
4056
		placeHolderText = format.arg(++i);
P
Palana 已提交
4057
	}
S
Socapex 已提交
4058

J
jp9000 已提交
4059 4060 4061
	bool accepted = NameDialog::AskForName(
		this, QTStr("Basic.Main.AddSceneDlg.Title"),
		QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
4062

J
jp9000 已提交
4063
	if (accepted) {
J
jp9000 已提交
4064
		if (name.empty()) {
4065
			OBSMessageBox::warning(this,
J
jp9000 已提交
4066 4067
					       QTStr("NoNameEntered.Title"),
					       QTStr("NoNameEntered.Text"));
J
jp9000 已提交
4068 4069 4070 4071
			on_actionAddScene_triggered();
			return;
		}

4072
		obs_source_t *source = obs_get_source_by_name(name.c_str());
4073
		if (source) {
J
jp9000 已提交
4074 4075
			OBSMessageBox::warning(this, QTStr("NameExists.Title"),
					       QTStr("NameExists.Text"));
4076 4077

			obs_source_release(source);
J
jp9000 已提交
4078
			on_actionAddScene_triggered();
4079 4080 4081
			return;
		}

4082
		obs_scene_t *scene = obs_scene_create(name.c_str());
4083
		source = obs_scene_get_source(scene);
4084
		SetCurrentScene(source);
4085
		obs_scene_release(scene);
4086
	}
4087 4088
}

J
jp9000 已提交
4089
void OBSBasic::on_actionRemoveScene_triggered()
4090
{
J
jp9000 已提交
4091
	OBSScene scene = GetCurrentScene();
4092
	obs_source_t *source = obs_scene_get_source(scene);
4093 4094 4095

	if (source && QueryRemoveSource(source))
		obs_source_remove(source);
4096 4097
}

J
jp9000 已提交
4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115
void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
{
	int idx = ui->scenes->currentRow();
	if (idx == -1 || idx == invalidIdx)
		return;

	sceneChanging = true;

	QListWidgetItem *item = ui->scenes->takeItem(idx);

	if (!relative)
		idx = 0;

	ui->scenes->insertItem(idx + offset, item);
	ui->scenes->setCurrentRow(idx + offset);
	item->setSelected(true);

	sceneChanging = false;
4116 4117

	OBSProjector::UpdateMultiviewProjectors();
J
jp9000 已提交
4118 4119
}

J
jp9000 已提交
4120
void OBSBasic::on_actionSceneUp_triggered()
4121
{
J
jp9000 已提交
4122
	ChangeSceneIndex(true, -1, 0);
4123 4124
}

J
jp9000 已提交
4125
void OBSBasic::on_actionSceneDown_triggered()
4126
{
J
jp9000 已提交
4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137
	ChangeSceneIndex(true, 1, ui->scenes->count() - 1);
}

void OBSBasic::MoveSceneToTop()
{
	ChangeSceneIndex(false, 0, 0);
}

void OBSBasic::MoveSceneToBottom()
{
	ChangeSceneIndex(false, ui->scenes->count() - 1,
J
jp9000 已提交
4138
			 ui->scenes->count() - 1);
4139 4140
}

J
jp9000 已提交
4141 4142
void OBSBasic::EditSceneItemName()
{
J
jp9000 已提交
4143 4144
	int idx = GetTopSelectedSourceItem();
	ui->sources->Edit(idx);
J
jp9000 已提交
4145 4146
}

J
jp9000 已提交
4147 4148
void OBSBasic::SetDeinterlacingMode()
{
J
jp9000 已提交
4149
	QAction *action = reinterpret_cast<QAction *>(sender());
J
jp9000 已提交
4150 4151 4152 4153 4154 4155 4156 4157 4158 4159
	obs_deinterlace_mode mode =
		(obs_deinterlace_mode)action->property("mode").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();
	obs_source_t *source = obs_sceneitem_get_source(sceneItem);

	obs_source_set_deinterlace_mode(source, mode);
}

void OBSBasic::SetDeinterlacingOrder()
{
J
jp9000 已提交
4160
	QAction *action = reinterpret_cast<QAction *>(sender());
J
jp9000 已提交
4161 4162 4163 4164 4165 4166 4167 4168
	obs_deinterlace_field_order order =
		(obs_deinterlace_field_order)action->property("order").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();
	obs_source_t *source = obs_sceneitem_get_source(sceneItem);

	obs_source_set_deinterlace_field_order(source, order);
}

P
pkv 已提交
4169
QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source)
J
jp9000 已提交
4170 4171 4172 4173 4174 4175 4176
{
	obs_deinterlace_mode deinterlaceMode =
		obs_source_get_deinterlace_mode(source);
	obs_deinterlace_field_order deinterlaceOrder =
		obs_source_get_deinterlace_field_order(source);
	QAction *action;

J
jp9000 已提交
4177 4178 4179 4180 4181
#define ADD_MODE(name, mode)                                    \
	action = menu->addAction(QTStr("" name), this,          \
				 SLOT(SetDeinterlacingMode())); \
	action->setProperty("mode", (int)mode);                 \
	action->setCheckable(true);                             \
J
jp9000 已提交
4182 4183
	action->setChecked(deinterlaceMode == mode);

J
jp9000 已提交
4184 4185 4186 4187 4188 4189
	ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE);
	ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD);
	ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO);
	ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND);
	ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X);
	ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR);
J
jp9000 已提交
4190
	ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X);
J
jp9000 已提交
4191 4192
	ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF);
	ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X);
J
jp9000 已提交
4193 4194 4195 4196
#undef ADD_MODE

	menu->addSeparator();

J
jp9000 已提交
4197
#define ADD_ORDER(name, order)                                       \
J
jp9000 已提交
4198
	action = menu->addAction(QTStr("Deinterlacing." name), this, \
J
jp9000 已提交
4199 4200 4201
				 SLOT(SetDeinterlacingOrder()));     \
	action->setProperty("order", (int)order);                    \
	action->setCheckable(true);                                  \
J
jp9000 已提交
4202 4203
	action->setChecked(deinterlaceOrder == order);

J
jp9000 已提交
4204
	ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP);
J
jp9000 已提交
4205 4206 4207 4208 4209 4210
	ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM);
#undef ADD_ORDER

	return menu;
}

4211 4212
void OBSBasic::SetScaleFilter()
{
J
jp9000 已提交
4213
	QAction *action = reinterpret_cast<QAction *>(sender());
4214 4215 4216 4217 4218 4219
	obs_scale_type mode = (obs_scale_type)action->property("mode").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();

	obs_sceneitem_set_scale_filter(sceneItem, mode);
}

P
pkv 已提交
4220
QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item)
4221 4222 4223 4224
{
	obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item);
	QAction *action;

J
jp9000 已提交
4225 4226 4227 4228 4229
#define ADD_MODE(name, mode)                                                   \
	action =                                                               \
		menu->addAction(QTStr("" name), this, SLOT(SetScaleFilter())); \
	action->setProperty("mode", (int)mode);                                \
	action->setCheckable(true);                                            \
4230 4231
	action->setChecked(scaleFilter == mode);

J
jp9000 已提交
4232 4233
	ADD_MODE("Disable", OBS_SCALE_DISABLE);
	ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT);
4234
	ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR);
J
jp9000 已提交
4235 4236 4237
	ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC);
	ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS);
	ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA);
4238 4239 4240 4241 4242
#undef ADD_MODE

	return menu;
}

J
jp9000 已提交
4243 4244 4245 4246
QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu,
					QWidgetAction *widgetAction,
					ColorSelect *select,
					obs_sceneitem_t *item)
4247 4248 4249 4250
{
	QAction *action;

	menu->setStyleSheet(QString(
J
jp9000 已提交
4251 4252 4253 4254 4255 4256 4257
		"*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}"
		"*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}"
		"*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}"
		"*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}"
		"*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}"
		"*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}"
		"*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}"
4258 4259 4260 4261 4262 4263 4264 4265
		"*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}"));

	obs_data_t *privData = obs_sceneitem_get_private_settings(item);
	obs_data_release(privData);

	obs_data_set_default_int(privData, "color-preset", 0);
	int preset = obs_data_get_int(privData, "color-preset");

J
jp9000 已提交
4266
	action = menu->addAction(QTStr("Clear"), this, +SLOT(ColorChange()));
4267 4268 4269 4270 4271
	action->setCheckable(true);
	action->setProperty("bgColor", 0);
	action->setChecked(preset == 0);

	action = menu->addAction(QTStr("CustomColor"), this,
J
jp9000 已提交
4272
				 +SLOT(ColorChange()));
4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283
	action->setCheckable(true);
	action->setProperty("bgColor", 1);
	action->setChecked(preset == 1);

	menu->addSeparator();

	widgetAction->setDefaultWidget(select);

	for (int i = 1; i < 9; i++) {
		stringstream button;
		button << "preset" << i;
J
jp9000 已提交
4284 4285
		QPushButton *colorButton =
			select->findChild<QPushButton *>(button.str().c_str());
4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298
		if (preset == i + 1)
			colorButton->setStyleSheet("border: 2px solid black");

		colorButton->setProperty("bgColor", i);
		select->connect(colorButton, SIGNAL(released()), this,
				SLOT(ColorChange()));
	}

	menu->addAction(widgetAction);

	return menu;
}

P
pkv 已提交
4299
ColorSelect::ColorSelect(QWidget *parent)
J
jp9000 已提交
4300
	: QWidget(parent), ui(new Ui::ColorSelect)
P
pkv 已提交
4301 4302 4303 4304
{
	ui->setupUi(this);
}

J
jp9000 已提交
4305
void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
4306
{
4307
	QMenu popup(this);
P
pkv 已提交
4308 4309 4310 4311 4312 4313 4314
	delete previewProjectorSource;
	delete sourceProjector;
	delete scaleFilteringMenu;
	delete colorMenu;
	delete colorWidgetAction;
	delete colorSelect;
	delete deinterlaceMenu;
J
jp9000 已提交
4315 4316 4317

	if (preview) {
		QAction *action = popup.addAction(
J
jp9000 已提交
4318 4319
			QTStr("Basic.Main.PreviewConextMenu.Enable"), this,
			SLOT(TogglePreview()));
J
jp9000 已提交
4320
		action->setCheckable(true);
4321
		action->setChecked(
J
jp9000 已提交
4322
			obs_display_enabled(ui->preview->GetDisplay()));
4323 4324
		if (IsPreviewProgramMode())
			action->setEnabled(false);
J
jp9000 已提交
4325

J
Joseph El-Khouri 已提交
4326 4327
		popup.addAction(ui->actionLockPreview);
		popup.addMenu(ui->scalingMenu);
J
jp9000 已提交
4328

P
pkv 已提交
4329 4330
		previewProjectorSource = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjectorSource, this,
J
jp9000 已提交
4331
					 SLOT(OpenPreviewProjector()));
J
jp9000 已提交
4332

P
pkv 已提交
4333
		popup.addMenu(previewProjectorSource);
J
jp9000 已提交
4334

J
jp9000 已提交
4335 4336 4337
		QAction *previewWindow =
			popup.addAction(QTStr("PreviewWindow"), this,
					SLOT(OpenPreviewWindow()));
C
cg2121 已提交
4338 4339 4340

		popup.addAction(previewWindow);

J
jp9000 已提交
4341 4342 4343
		popup.addSeparator();
	}

J
jp9000 已提交
4344 4345 4346 4347
	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

4348
	ui->actionCopyFilters->setEnabled(false);
4349
	ui->actionCopySource->setEnabled(false);
4350

J
jp9000 已提交
4351 4352
	if (ui->sources->MultipleBaseSelected()) {
		popup.addSeparator();
J
jp9000 已提交
4353 4354
		popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources,
				SLOT(GroupSelectedItems()));
J
jp9000 已提交
4355 4356 4357

	} else if (ui->sources->GroupsSelected()) {
		popup.addSeparator();
J
jp9000 已提交
4358 4359
		popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources,
				SLOT(UngroupSelectedGroups()));
J
jp9000 已提交
4360 4361
	}

4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372
	popup.addSeparator();
	popup.addAction(ui->actionCopySource);
	popup.addAction(ui->actionPasteRef);
	popup.addAction(ui->actionPasteDup);
	popup.addSeparator();

	popup.addSeparator();
	popup.addAction(ui->actionCopyFilters);
	popup.addAction(ui->actionPasteFilters);
	popup.addSeparator();

J
jp9000 已提交
4373
	if (idx != -1) {
J
jp9000 已提交
4374 4375 4376
		if (addSourceMenu)
			popup.addSeparator();

J
jp9000 已提交
4377
		OBSSceneItem sceneItem = ui->sources->Get(idx);
4378
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
J
jp9000 已提交
4379 4380
		uint32_t flags = obs_source_get_output_flags(source);
		bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) ==
J
jp9000 已提交
4381 4382
				    OBS_SOURCE_ASYNC_VIDEO;
		bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO;
J
John Bradley 已提交
4383 4384
		QAction *action;

P
pkv 已提交
4385 4386 4387
		colorMenu = new QMenu(QTStr("ChangeBG"));
		colorWidgetAction = new QWidgetAction(colorMenu);
		colorSelect = new ColorSelect(colorMenu);
J
jp9000 已提交
4388 4389
		popup.addMenu(AddBackgroundColorMenu(
			colorMenu, colorWidgetAction, colorSelect, sceneItem));
J
jp9000 已提交
4390 4391
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
4392
		popup.addAction(QTStr("Remove"), this,
4393
				SLOT(on_actionRemoveSource_triggered()));
J
jp9000 已提交
4394 4395
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
J
jp9000 已提交
4396
		popup.addMenu(ui->transformMenu);
J
jp9000 已提交
4397 4398 4399

		sourceProjector = new QMenu(QTStr("SourceProjector"));
		AddProjectorMenuMonitors(sourceProjector, this,
J
jp9000 已提交
4400
					 SLOT(OpenSourceProjector()));
J
jp9000 已提交
4401

C
cg2121 已提交
4402
		QAction *sourceWindow = popup.addAction(
J
jp9000 已提交
4403
			QTStr("SourceWindow"), this, SLOT(OpenSourceWindow()));
C
cg2121 已提交
4404 4405 4406

		popup.addAction(sourceWindow);

J
jp9000 已提交
4407
		popup.addSeparator();
4408 4409

		if (hasAudio) {
J
jp9000 已提交
4410 4411 4412
			QAction *actionHideMixer =
				popup.addAction(QTStr("HideMixer"), this,
						SLOT(ToggleHideMixer()));
4413 4414 4415 4416
			actionHideMixer->setCheckable(true);
			actionHideMixer->setChecked(SourceMixerHidden(source));
		}

J
jp9000 已提交
4417
		if (isAsyncVideo) {
P
pkv 已提交
4418
			deinterlaceMenu = new QMenu(QTStr("Deinterlacing"));
J
jp9000 已提交
4419 4420
			popup.addMenu(
				AddDeinterlacingMenu(deinterlaceMenu, source));
J
jp9000 已提交
4421 4422
			popup.addSeparator();
		}
4423

J
jp9000 已提交
4424 4425 4426
		QAction *resizeOutput =
			popup.addAction(QTStr("ResizeOutputSizeOfSource"), this,
					SLOT(ResizeOutputSizeOfSource()));
4427 4428 4429 4430

		int width = obs_source_get_width(source);
		int height = obs_source_get_height(source);

4431
		resizeOutput->setEnabled(!obs_video_active());
4432 4433 4434 4435

		if (width == 0 || height == 0)
			resizeOutput->setEnabled(false);

P
pkv 已提交
4436
		scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering"));
J
jp9000 已提交
4437 4438
		popup.addMenu(
			AddScaleFilteringMenu(scaleFilteringMenu, sceneItem));
4439 4440
		popup.addSeparator();

J
jp9000 已提交
4441
		popup.addMenu(sourceProjector);
C
cg2121 已提交
4442
		popup.addAction(sourceWindow);
J
jp9000 已提交
4443
		popup.addSeparator();
J
John Bradley 已提交
4444 4445

		action = popup.addAction(QTStr("Interact"), this,
J
jp9000 已提交
4446
					 SLOT(on_actionInteract_triggered()));
J
John Bradley 已提交
4447 4448

		action->setEnabled(obs_source_get_output_flags(source) &
J
jp9000 已提交
4449
				   OBS_SOURCE_INTERACTION);
J
John Bradley 已提交
4450

J
jp9000 已提交
4451
		popup.addAction(QTStr("Filters"), this, SLOT(OpenFilters()));
J
jp9000 已提交
4452 4453
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));
4454 4455

		ui->actionCopyFilters->setEnabled(true);
4456
		ui->actionCopySource->setEnabled(true);
4457 4458
	} else {
		ui->actionPasteFilters->setEnabled(false);
J
jp9000 已提交
4459 4460 4461
	}

	popup.exec(QCursor::pos());
4462 4463
}

J
jp9000 已提交
4464 4465
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
J
jp9000 已提交
4466 4467 4468 4469
	if (ui->scenes->count()) {
		QModelIndex idx = ui->sources->indexAt(pos);
		CreateSourcePopupMenu(idx.row(), false);
	}
P
Palana 已提交
4470 4471
}

4472 4473 4474 4475 4476 4477
void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
{
	if (!witem)
		return;

	if (IsPreviewProgramMode()) {
J
jp9000 已提交
4478 4479 4480
		bool doubleClickSwitch =
			config_get_bool(App()->GlobalConfig(), "BasicWindow",
					"TransitionOnDoubleClick");
4481 4482 4483 4484 4485 4486 4487 4488 4489 4490

		if (doubleClickSwitch) {
			OBSScene scene = GetCurrentScene();

			if (scene)
				SetCurrentScene(scene, false, true);
		}
	}
}

J
jp9000 已提交
4491
void OBSBasic::AddSource(const char *id)
4492
{
4493 4494 4495
	if (id && *id) {
		OBSBasicSourceSelect sourceSelect(this, id);
		sourceSelect.exec();
4496
		if (sourceSelect.newSource && strcmp(id, "group") != 0)
4497
			CreatePropertiesWindow(sourceSelect.newSource);
4498
	}
4499 4500
}

4501
QMenu *OBSBasic::CreateAddSourcePopupMenu()
4502
{
4503
	const char *type;
J
jp9000 已提交
4504
	bool foundValues = false;
4505
	bool foundDeprecated = false;
J
jp9000 已提交
4506
	size_t idx = 0;
4507

4508
	QMenu *popup = new QMenu(QTStr("Add"), this);
4509
	QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup);
4510

J
jp9000 已提交
4511 4512
	auto getActionAfter = [](QMenu *menu, const QString &name) {
		QList<QAction *> actions = menu->actions();
J
jp9000 已提交
4513 4514 4515 4516 4517 4518

		for (QAction *menuAction : actions) {
			if (menuAction->text().compare(name) >= 0)
				return menuAction;
		}

J
jp9000 已提交
4519
		return (QAction *)nullptr;
J
jp9000 已提交
4520 4521
	};

J
jp9000 已提交
4522 4523
	auto addSource = [this, getActionAfter](QMenu *popup, const char *type,
						const char *name) {
J
jp9000 已提交
4524 4525
		QString qname = QT_UTF8(name);
		QAction *popupItem = new QAction(qname, this);
J
jp9000 已提交
4526
		popupItem->setData(QT_UTF8(type));
J
jp9000 已提交
4527 4528
		connect(popupItem, SIGNAL(triggered(bool)), this,
			SLOT(AddSourceFromAction()));
J
jp9000 已提交
4529 4530 4531

		QAction *after = getActionAfter(popup, qname);
		popup->insertAction(after, popupItem);
J
jp9000 已提交
4532
	};
4533

J
jp9000 已提交
4534 4535
	while (obs_enum_input_types(idx++, &type)) {
		const char *name = obs_source_get_display_name(type);
4536
		uint32_t caps = obs_get_source_output_flags(type);
J
jp9000 已提交
4537

4538 4539 4540
		if ((caps & OBS_SOURCE_CAP_DISABLED) != 0)
			continue;

4541 4542
		if ((caps & OBS_SOURCE_DEPRECATED) == 0) {
			addSource(popup, type, name);
4543 4544 4545
		} else {
			addSource(deprecated, type, name);
			foundDeprecated = true;
4546
		}
4547
		foundValues = true;
4548 4549
	}

J
jp9000 已提交
4550
	addSource(popup, "scene", Str("Basic.Scene"));
J
jp9000 已提交
4551

J
jp9000 已提交
4552 4553
	popup->addSeparator();
	QAction *addGroup = new QAction(QTStr("Group"), this);
4554
	addGroup->setData(QT_UTF8("group"));
J
jp9000 已提交
4555 4556
	connect(addGroup, SIGNAL(triggered(bool)), this,
		SLOT(AddSourceFromAction()));
J
jp9000 已提交
4557 4558
	popup->addAction(addGroup);

4559 4560 4561 4562 4563
	if (!foundDeprecated) {
		delete deprecated;
		deprecated = nullptr;
	}

4564 4565 4566
	if (!foundValues) {
		delete popup;
		popup = nullptr;
4567 4568

	} else if (foundDeprecated) {
J
jp9000 已提交
4569
		popup->addSeparator();
4570
		popup->addMenu(deprecated);
4571
	}
4572 4573 4574 4575 4576 4577

	return popup;
}

void OBSBasic::AddSourceFromAction()
{
J
jp9000 已提交
4578
	QAction *action = qobject_cast<QAction *>(sender());
4579 4580 4581 4582 4583 4584 4585 4586 4587 4588
	if (!action)
		return;

	AddSource(QT_TO_UTF8(action->data().toString()));
}

void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
{
	if (!GetCurrentScene()) {
		// Tell the user he needs a scene first (help beginners).
J
jp9000 已提交
4589 4590 4591
		OBSMessageBox::information(
			this, QTStr("Basic.Main.AddSourceHelp.Title"),
			QTStr("Basic.Main.AddSourceHelp.Text"));
4592 4593 4594
		return;
	}

4595
	QScopedPointer<QMenu> popup(CreateAddSourcePopupMenu());
4596 4597
	if (popup)
		popup->exec(pos);
4598 4599
}

J
jp9000 已提交
4600
void OBSBasic::on_actionAddSource_triggered()
4601
{
J
jp9000 已提交
4602
	AddSourcePopupMenu(QCursor::pos());
4603 4604
}

J
jp9000 已提交
4605 4606 4607
static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
{
	vector<OBSSceneItem> &items =
J
jp9000 已提交
4608
		*reinterpret_cast<vector<OBSSceneItem> *>(param);
J
jp9000 已提交
4609 4610 4611 4612 4613 4614 4615 4616 4617

	if (obs_sceneitem_selected(item)) {
		items.emplace_back(item);
	} else if (obs_sceneitem_is_group(item)) {
		obs_sceneitem_group_enum_items(item, remove_items, &items);
	}
	return true;
};

J
jp9000 已提交
4618
void OBSBasic::on_actionRemoveSource_triggered()
4619
{
4620
	vector<OBSSceneItem> items;
4621

J
jp9000 已提交
4622
	obs_scene_enum_items(GetCurrentScene(), remove_items, &items);
4623 4624 4625 4626

	if (!items.size())
		return;

J
jp9000 已提交
4627
	auto removeMultiple = [this](size_t count) {
4628
		QString text = QTStr("ConfirmRemove.TextMultiple")
J
jp9000 已提交
4629
				       .arg(QString::number(count));
4630 4631 4632

		QMessageBox remove_items(this);
		remove_items.setText(text);
J
jp9000 已提交
4633 4634
		QAbstractButton *Yes = remove_items.addButton(
			QTStr("Yes"), QMessageBox::YesRole);
4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654
		remove_items.addButton(QTStr("No"), QMessageBox::NoRole);
		remove_items.setIcon(QMessageBox::Question);
		remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
		remove_items.exec();

		return Yes == remove_items.clickedButton();
	};

	if (items.size() == 1) {
		OBSSceneItem &item = items[0];
		obs_source_t *source = obs_sceneitem_get_source(item);

		if (source && QueryRemoveSource(source))
			obs_sceneitem_remove(item);
	} else {
		if (removeMultiple(items.size())) {
			for (auto &item : items)
				obs_sceneitem_remove(item);
		}
	}
4655 4656
}

J
John Bradley 已提交
4657 4658 4659 4660 4661 4662 4663 4664 4665
void OBSBasic::on_actionInteract_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreateInteractionWindow(source);
}

J
jp9000 已提交
4666
void OBSBasic::on_actionSourceProperties_triggered()
4667
{
4668
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4669
	OBSSource source = obs_sceneitem_get_source(item);
4670

4671 4672
	if (source)
		CreatePropertiesWindow(source);
4673 4674
}

J
jp9000 已提交
4675
void OBSBasic::on_actionSourceUp_triggered()
4676
{
J
jp9000 已提交
4677
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4678
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
4679
}
J
jp9000 已提交
4680

J
jp9000 已提交
4681
void OBSBasic::on_actionSourceDown_triggered()
4682
{
J
jp9000 已提交
4683
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4684
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
4685 4686
}

J
jp9000 已提交
4687 4688 4689
void OBSBasic::on_actionMoveUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4690
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
J
jp9000 已提交
4691 4692 4693 4694 4695
}

void OBSBasic::on_actionMoveDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4696
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
J
jp9000 已提交
4697 4698 4699 4700 4701
}

void OBSBasic::on_actionMoveToTop_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4702
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_TOP);
J
jp9000 已提交
4703 4704 4705 4706 4707
}

void OBSBasic::on_actionMoveToBottom_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4708
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_BOTTOM);
J
jp9000 已提交
4709 4710
}

4711
static BPtr<char> ReadLogFile(const char *subdir, const char *log)
J
jp9000 已提交
4712
{
4713
	char logDir[512];
4714
	if (GetConfigPath(logDir, sizeof(logDir), subdir) <= 0)
4715
		return nullptr;
J
jp9000 已提交
4716

J
jp9000 已提交
4717
	string path = (char *)logDir;
J
jp9000 已提交
4718 4719 4720
	path += "/";
	path += log;

4721
	BPtr<char> file = os_quick_read_utf8_file(path.c_str());
J
jp9000 已提交
4722 4723 4724 4725 4726 4727
	if (!file)
		blog(LOG_WARNING, "Failed to read log file %s", path.c_str());

	return file;
}

4728
void OBSBasic::UploadLog(const char *subdir, const char *file)
J
jp9000 已提交
4729
{
4730
	BPtr<char> fileString{ReadLogFile(subdir, file)};
J
jp9000 已提交
4731

4732
	if (!fileString)
J
jp9000 已提交
4733 4734
		return;

4735
	if (!*fileString)
J
jp9000 已提交
4736 4737 4738 4739
		return;

	ui->menuLogFiles->setEnabled(false);

4740
	stringstream ss;
J
jp9000 已提交
4741 4742 4743
	ss << "OBS " << App()->GetVersionString() << " log file uploaded at "
	   << CurrentDateTimeString() << "\n\n"
	   << fileString;
F
fryshorts 已提交
4744

4745 4746 4747
	if (logUploadThread) {
		logUploadThread->wait();
	}
F
fryshorts 已提交
4748

J
jp9000 已提交
4749 4750 4751
	RemoteTextThread *thread =
		new RemoteTextThread("https://obsproject.com/logs/upload",
				     "text/plain", ss.str().c_str());
4752

4753
	logUploadThread.reset(thread);
J
jp9000 已提交
4754 4755
	connect(thread, &RemoteTextThread::Result, this,
		&OBSBasic::logUploadFinished);
4756
	logUploadThread->start();
J
jp9000 已提交
4757 4758
}

P
Palana 已提交
4759 4760
void OBSBasic::on_actionShowLogs_triggered()
{
4761
	char logDir[512];
4762
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
4763 4764
		return;

P
Palana 已提交
4765 4766 4767 4768
	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
4769 4770
void OBSBasic::on_actionUploadCurrentLog_triggered()
{
4771
	UploadLog("obs-studio/logs", App()->GetCurrentLog());
J
jp9000 已提交
4772 4773 4774 4775
}

void OBSBasic::on_actionUploadLastLog_triggered()
{
4776
	UploadLog("obs-studio/logs", App()->GetLastLog());
J
jp9000 已提交
4777 4778
}

4779 4780 4781
void OBSBasic::on_actionViewCurrentLog_triggered()
{
	char logDir[512];
4782
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
4783 4784
		return;

J
jp9000 已提交
4785
	const char *log = App()->GetCurrentLog();
4786

J
jp9000 已提交
4787
	string path = (char *)logDir;
4788 4789 4790 4791 4792 4793 4794
	path += "/";
	path += log;

	QUrl url = QUrl::fromLocalFile(QT_UTF8(path.c_str()));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809
void OBSBasic::on_actionShowCrashLogs_triggered()
{
	char logDir[512];
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0)
		return;

	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

void OBSBasic::on_actionUploadLastCrashLog_triggered()
{
	UploadLog("obs-studio/crashes", App()->GetLastCrashLog());
}

J
jp9000 已提交
4810 4811
void OBSBasic::on_actionCheckForUpdates_triggered()
{
J
jp9000 已提交
4812
	CheckForUpdates(true);
J
jp9000 已提交
4813 4814
}

4815
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
J
jp9000 已提交
4816 4817 4818
{
	ui->menuLogFiles->setEnabled(true);

4819
	if (text.isEmpty()) {
J
jp9000 已提交
4820 4821 4822
		OBSMessageBox::critical(
			this, QTStr("LogReturnDialog.ErrorUploadingLog"),
			error);
J
jp9000 已提交
4823 4824 4825
		return;
	}

4826
	obs_data_t *returnData = obs_data_create_from_json(QT_TO_UTF8(text));
4827
	string resURL = obs_data_get_string(returnData, "url");
4828
	QString logURL = resURL.c_str();
J
jp9000 已提交
4829 4830 4831 4832 4833 4834
	obs_data_release(returnData);

	OBSLogReply logDialog(this, logURL);
	logDialog.exec();
}

J
jp9000 已提交
4835
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
J
jp9000 已提交
4836
			   obs_source_t *source, const string &name)
4837
{
4838 4839 4840 4841
	const char *prevName = obs_source_get_name(source);
	if (name == prevName)
		return;

J
jp9000 已提交
4842 4843
	obs_source_t *foundSource = obs_get_source_by_name(name.c_str());
	QListWidgetItem *listItem = listWidget->currentItem();
4844

4845
	if (foundSource || name.empty()) {
4846
		listItem->setText(QT_UTF8(prevName));
4847

4848
		if (foundSource) {
4849
			OBSMessageBox::warning(parent,
J
jp9000 已提交
4850 4851
					       QTStr("NameExists.Title"),
					       QTStr("NameExists.Text"));
4852
		} else if (name.empty()) {
4853
			OBSMessageBox::warning(parent,
J
jp9000 已提交
4854 4855
					       QTStr("NoNameEntered.Title"),
					       QTStr("NoNameEntered.Text"));
4856 4857
		}

4858 4859 4860
		obs_source_release(foundSource);
	} else {
		listItem->setText(QT_UTF8(name.c_str()));
4861
		obs_source_set_name(source, name.c_str());
4862 4863 4864
	}
}

J
jp9000 已提交
4865
void OBSBasic::SceneNameEdited(QWidget *editor,
J
jp9000 已提交
4866
			       QAbstractItemDelegate::EndEditHint endHint)
J
jp9000 已提交
4867
{
J
jp9000 已提交
4868 4869 4870
	OBSScene scene = GetCurrentScene();
	QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
	string text = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
4871 4872 4873 4874

	if (!scene)
		return;

4875
	obs_source_t *source = obs_scene_get_source(scene);
4876
	RenameListItem(this, ui->scenes, source, text);
J
jp9000 已提交
4877

J
jp9000 已提交
4878 4879 4880
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);

J
jp9000 已提交
4881 4882 4883
	UNUSED_PARAMETER(endHint);
}

J
jp9000 已提交
4884 4885 4886 4887 4888 4889 4890 4891
void OBSBasic::OpenFilters()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
4892 4893 4894 4895 4896 4897 4898 4899
void OBSBasic::OpenSceneFilters()
{
	OBSScene scene = GetCurrentScene();
	OBSSource source = obs_scene_get_source(scene);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
4900 4901 4902 4903
#define RECORDING_START \
	"==== Recording Start ==============================================="
#define RECORDING_STOP \
	"==== Recording Stop ================================================"
J
jp9000 已提交
4904 4905 4906 4907
#define REPLAY_BUFFER_START \
	"==== Replay Buffer Start ==========================================="
#define REPLAY_BUFFER_STOP \
	"==== Replay Buffer Stop ============================================"
J
jp9000 已提交
4908 4909 4910 4911 4912
#define STREAMING_START \
	"==== Streaming Start ==============================================="
#define STREAMING_STOP \
	"==== Streaming Stop ================================================"

4913 4914
void OBSBasic::StartStreaming()
{
J
jp9000 已提交
4915 4916
	if (outputHandler->StreamingActive())
		return;
4917
	if (disableOutputsRef)
4918
		return;
J
jp9000 已提交
4919 4920 4921 4922

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTING);

4923 4924
	SaveProject();

J
jp9000 已提交
4925 4926
	ui->streamButton->setEnabled(false);
	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
4927 4928 4929 4930 4931

	if (sysTrayStream) {
		sysTrayStream->setEnabled(false);
		sysTrayStream->setText(ui->streamButton->text());
	}
4932

J
jp9000 已提交
4933
	if (!outputHandler->StartStreaming(service)) {
J
jp9000 已提交
4934 4935 4936 4937
		QString message =
			!outputHandler->lastError.empty()
				? QTStr(outputHandler->lastError.c_str())
				: QTStr("Output.StartFailedGeneric");
J
jp9000 已提交
4938 4939
		ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
		ui->streamButton->setEnabled(true);
4940
		ui->streamButton->setChecked(false);
4941 4942 4943 4944 4945

		if (sysTrayStream) {
			sysTrayStream->setText(ui->streamButton->text());
			sysTrayStream->setEnabled(true);
		}
4946

4947
		QMessageBox::critical(this, QTStr("Output.StartStreamFailed"),
J
jp9000 已提交
4948
				      message);
4949
		return;
4950
	}
4951

J
jp9000 已提交
4952 4953
	bool recordWhenStreaming = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming");
4954 4955
	if (recordWhenStreaming)
		StartRecording();
4956

J
jp9000 已提交
4957 4958
	bool replayBufferWhileStreaming = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
4959 4960
	if (replayBufferWhileStreaming)
		StartReplayBuffer();
4961 4962
}

4963 4964 4965 4966
#ifdef _WIN32
static inline void UpdateProcessPriority()
{
	const char *priority = config_get_string(App()->GlobalConfig(),
J
jp9000 已提交
4967
						 "General", "ProcessPriority");
4968 4969 4970 4971 4972 4973 4974
	if (priority && strcmp(priority, "Normal") != 0)
		SetProcessPriority(priority);
}

static inline void ClearProcessPriority()
{
	const char *priority = config_get_string(App()->GlobalConfig(),
J
jp9000 已提交
4975
						 "General", "ProcessPriority");
4976 4977 4978 4979
	if (priority && strcmp(priority, "Normal") != 0)
		SetProcessPriority("Normal");
}
#else
J
jp9000 已提交
4980 4981 4982 4983 4984 4985
#define UpdateProcessPriority() \
	do {                    \
	} while (false)
#define ClearProcessPriority() \
	do {                   \
	} while (false)
4986 4987
#endif

4988
inline void OBSBasic::OnActivate()
4989
{
4990 4991
	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
J
jp9000 已提交
4992
		ui->autoConfigure->setEnabled(false);
4993 4994
		App()->IncrementSleepInhibition();
		UpdateProcessPriority();
C
cg2121 已提交
4995

4996
		if (trayIcon)
J
jp9000 已提交
4997 4998 4999
			trayIcon->setIcon(QIcon::fromTheme(
				"obs-tray-active",
				QIcon(":/res/images/tray_active.png")));
5000 5001
	}
}
J
jp9000 已提交
5002

5003 5004
inline void OBSBasic::OnDeactivate()
{
5005
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
5006
		ui->profileMenu->setEnabled(true);
J
jp9000 已提交
5007
		ui->autoConfigure->setEnabled(true);
5008
		App()->DecrementSleepInhibition();
5009
		ClearProcessPriority();
C
cg2121 已提交
5010

5011
		if (trayIcon)
J
jp9000 已提交
5012 5013
			trayIcon->setIcon(QIcon::fromTheme(
				"obs-tray", QIcon(":/res/images/obs.png")));
J
jp9000 已提交
5014
	}
5015 5016 5017 5018 5019 5020 5021
}

void OBSBasic::StopStreaming()
{
	SaveProject();

	if (outputHandler->StreamingActive())
5022
		outputHandler->StopStreaming(streamingStopping);
5023 5024

	OnDeactivate();
5025

J
jp9000 已提交
5026 5027 5028 5029 5030
	bool recordWhenStreaming = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming");
	bool keepRecordingWhenStreamStops =
		config_get_bool(GetGlobalConfig(), "BasicWindow",
				"KeepRecordingWhenStreamStops");
5031 5032
	if (recordWhenStreaming && !keepRecordingWhenStreamStops)
		StopRecording();
5033

J
jp9000 已提交
5034 5035 5036 5037 5038
	bool replayBufferWhileStreaming = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
	bool keepReplayBufferStreamStops =
		config_get_bool(GetGlobalConfig(), "BasicWindow",
				"KeepReplayBufferStreamStops");
5039 5040
	if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
		StopReplayBuffer();
5041 5042
}

J
jp9000 已提交
5043 5044 5045 5046 5047
void OBSBasic::ForceStopStreaming()
{
	SaveProject();

	if (outputHandler->StreamingActive())
5048
		outputHandler->StopStreaming(true);
J
jp9000 已提交
5049

5050
	OnDeactivate();
5051

J
jp9000 已提交
5052 5053 5054 5055 5056
	bool recordWhenStreaming = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "RecordWhenStreaming");
	bool keepRecordingWhenStreamStops =
		config_get_bool(GetGlobalConfig(), "BasicWindow",
				"KeepRecordingWhenStreamStops");
5057 5058
	if (recordWhenStreaming && !keepRecordingWhenStreamStops)
		StopRecording();
5059

J
jp9000 已提交
5060 5061 5062 5063 5064
	bool replayBufferWhileStreaming = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "ReplayBufferWhileStreaming");
	bool keepReplayBufferStreamStops =
		config_get_bool(GetGlobalConfig(), "BasicWindow",
				"KeepReplayBufferStreamStops");
5065 5066
	if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
		StopReplayBuffer();
J
jp9000 已提交
5067 5068 5069 5070 5071 5072
}

void OBSBasic::StreamDelayStarting(int sec)
{
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
	ui->streamButton->setEnabled(true);
5073
	ui->streamButton->setChecked(true);
5074 5075 5076 5077 5078

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
J
jp9000 已提交
5079 5080 5081 5082 5083

	if (!startStreamMenu.isNull())
		startStreamMenu->deleteLater();

	startStreamMenu = new QMenu();
J
jp9000 已提交
5084 5085 5086 5087
	startStreamMenu->addAction(QTStr("Basic.Main.StopStreaming"), this,
				   SLOT(StopStreaming()));
	startStreamMenu->addAction(QTStr("Basic.Main.ForceStopStreaming"), this,
				   SLOT(ForceStopStreaming()));
J
jp9000 已提交
5088 5089 5090
	ui->streamButton->setMenu(startStreamMenu);

	ui->statusbar->StreamDelayStarting(sec);
5091

5092
	OnActivate();
J
jp9000 已提交
5093 5094 5095 5096 5097 5098
}

void OBSBasic::StreamDelayStopping(int sec)
{
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
	ui->streamButton->setEnabled(true);
5099
	ui->streamButton->setChecked(false);
5100 5101 5102 5103 5104

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
J
jp9000 已提交
5105 5106 5107 5108 5109

	if (!startStreamMenu.isNull())
		startStreamMenu->deleteLater();

	startStreamMenu = new QMenu();
J
jp9000 已提交
5110 5111 5112 5113
	startStreamMenu->addAction(QTStr("Basic.Main.StartStreaming"), this,
				   SLOT(StartStreaming()));
	startStreamMenu->addAction(QTStr("Basic.Main.ForceStopStreaming"), this,
				   SLOT(ForceStopStreaming()));
J
jp9000 已提交
5114 5115 5116
	ui->streamButton->setMenu(startStreamMenu);

	ui->statusbar->StreamDelayStopping(sec);
5117 5118 5119

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
J
jp9000 已提交
5120 5121
}

5122
void OBSBasic::StreamingStart()
5123
{
5124
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
J
jp9000 已提交
5125
	ui->streamButton->setEnabled(true);
5126
	ui->streamButton->setChecked(true);
J
jp9000 已提交
5127
	ui->statusbar->StreamStarted(outputHandler->streamOutput);
5128 5129 5130 5131 5132

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
5133

J
jp9000 已提交
5134 5135 5136
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTED);

5137
	OnActivate();
5138

5139
	blog(LOG_INFO, STREAMING_START);
5140 5141
}

5142 5143 5144
void OBSBasic::StreamStopping()
{
	ui->streamButton->setText(QTStr("Basic.Main.StoppingStreaming"));
5145 5146 5147

	if (sysTrayStream)
		sysTrayStream->setText(ui->streamButton->text());
J
jp9000 已提交
5148

5149
	streamingStopping = true;
J
jp9000 已提交
5150 5151
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
5152 5153
}

5154
void OBSBasic::StreamingStop(int code, QString last_error)
5155
{
5156
	const char *errorDescription = "";
5157 5158
	DStr errorMessage;
	bool use_last_error = false;
5159
	bool encode_error = false;
5160 5161 5162

	switch (code) {
	case OBS_OUTPUT_BAD_PATH:
5163
		errorDescription = Str("Output.ConnectFail.BadPath");
5164 5165 5166
		break;

	case OBS_OUTPUT_CONNECT_FAILED:
5167 5168
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.ConnectFailed");
5169 5170 5171
		break;

	case OBS_OUTPUT_INVALID_STREAM:
5172
		errorDescription = Str("Output.ConnectFail.InvalidStream");
5173 5174
		break;

5175 5176 5177 5178
	case OBS_OUTPUT_ENCODE_ERROR:
		encode_error = true;
		break;

5179
	default:
5180
	case OBS_OUTPUT_ERROR:
5181 5182
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.Error");
5183 5184 5185 5186 5187
		break;

	case OBS_OUTPUT_DISCONNECTED:
		/* doesn't happen if output is set to reconnect.  note that
		 * reconnects are handled in the output, not in the UI */
5188 5189
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.Disconnected");
5190 5191
	}

5192 5193
	if (use_last_error && !last_error.isEmpty())
		dstr_printf(errorMessage, "%s\n\n%s", errorDescription,
J
jp9000 已提交
5194
			    QT_TO_UTF8(last_error));
5195 5196 5197
	else
		dstr_copy(errorMessage, errorDescription);

J
jp9000 已提交
5198
	ui->statusbar->StreamStopped();
5199

5200
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
J
jp9000 已提交
5201
	ui->streamButton->setEnabled(true);
5202
	ui->streamButton->setChecked(false);
5203 5204 5205 5206 5207

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
5208

5209
	streamingStopping = false;
J
jp9000 已提交
5210 5211 5212
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPED);

5213
	OnDeactivate();
5214 5215

	blog(LOG_INFO, STREAMING_STOP);
J
jp9000 已提交
5216

5217
	if (encode_error) {
J
jp9000 已提交
5218 5219 5220
		OBSMessageBox::information(
			this, QTStr("Output.StreamEncodeError.Title"),
			QTStr("Output.StreamEncodeError.Msg"));
5221 5222

	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
5223
		OBSMessageBox::information(this,
J
jp9000 已提交
5224 5225
					   QTStr("Output.ConnectFail.Title"),
					   QT_UTF8(errorMessage));
5226

C
cg2121 已提交
5227
	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
J
jp9000 已提交
5228 5229
		SysTrayNotify(QT_UTF8(errorDescription),
			      QSystemTrayIcon::Warning);
C
cg2121 已提交
5230
	}
J
jp9000 已提交
5231 5232 5233 5234 5235 5236

	if (!startStreamMenu.isNull()) {
		ui->streamButton->setMenu(nullptr);
		startStreamMenu->deleteLater();
		startStreamMenu = nullptr;
	}
J
jp9000 已提交
5237 5238
}

C
cg2121 已提交
5239 5240 5241
void OBSBasic::AutoRemux()
{
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
5242 5243
	bool advanced = astrcmpi(mode, "Advanced") == 0;

J
jp9000 已提交
5244 5245 5246 5247 5248
	const char *path = !advanced ? config_get_string(basicConfig,
							 "SimpleOutput",
							 "FilePath")
				     : config_get_string(basicConfig, "AdvOut",
							 "RecFilePath");
5249

5250 5251
	/* do not save if using FFmpeg output in advanced output mode */
	if (advanced) {
J
jp9000 已提交
5252 5253
		const char *type =
			config_get_string(basicConfig, "AdvOut", "RecType");
5254 5255 5256 5257 5258
		if (astrcmpi(type, "FFmpeg") == 0) {
			return;
		}
	}

J
jp9000 已提交
5259 5260 5261 5262 5263 5264 5265
	QString input;
	input += path;
	input += "/";
	input += remuxFilename.c_str();

	QFileInfo fi(remuxFilename.c_str());

5266 5267 5268 5269 5270
	/* do not remux if lossless */
	if (fi.suffix().compare("avi", Qt::CaseInsensitive) == 0) {
		return;
	}

J
jp9000 已提交
5271 5272 5273 5274 5275
	QString output;
	output += path;
	output += "/";
	output += fi.completeBaseName();
	output += ".mp4";
C
cg2121 已提交
5276 5277 5278

	OBSRemux *remux = new OBSRemux(path, this, true);
	remux->show();
J
jp9000 已提交
5279
	remux->AutoRemux(input, output);
C
cg2121 已提交
5280 5281
}

5282 5283
void OBSBasic::StartRecording()
{
5284 5285
	if (outputHandler->RecordingActive())
		return;
5286
	if (disableOutputsRef)
5287
		return;
5288

J
jp9000 已提交
5289 5290 5291
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING);

5292
	SaveProject();
5293 5294 5295

	if (!outputHandler->StartRecording())
		ui->recordButton->setChecked(false);
5296 5297
}

5298 5299
void OBSBasic::RecordStopping()
{
J
jp9000 已提交
5300
	ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording"));
5301 5302 5303

	if (sysTrayRecord)
		sysTrayRecord->setText(ui->recordButton->text());
J
jp9000 已提交
5304

5305
	recordingStopping = true;
J
jp9000 已提交
5306 5307
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPING);
5308 5309
}

5310 5311 5312 5313 5314
void OBSBasic::StopRecording()
{
	SaveProject();

	if (outputHandler->RecordingActive())
5315
		outputHandler->StopRecording(recordingStopping);
J
jp9000 已提交
5316

5317
	OnDeactivate();
5318 5319
}

P
Palana 已提交
5320 5321
void OBSBasic::RecordingStart()
{
J
jp9000 已提交
5322
	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
J
jp9000 已提交
5323
	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
5324
	ui->recordButton->setChecked(true);
5325 5326 5327

	if (sysTrayRecord)
		sysTrayRecord->setText(ui->recordButton->text());
5328

5329
	recordingStopping = false;
J
jp9000 已提交
5330 5331 5332
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTED);

5333
	OnActivate();
J
jp9000 已提交
5334
	UpdatePause();
5335

5336
	blog(LOG_INFO, RECORDING_START);
P
Palana 已提交
5337 5338
}

5339
void OBSBasic::RecordingStop(int code, QString last_error)
5340
{
P
Palana 已提交
5341
	ui->statusbar->RecordingStopped();
J
jp9000 已提交
5342
	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
5343
	ui->recordButton->setChecked(false);
5344 5345 5346

	if (sysTrayRecord)
		sysTrayRecord->setText(ui->recordButton->text());
J
jp9000 已提交
5347

5348
	blog(LOG_INFO, RECORDING_STOP);
5349

C
cg2121 已提交
5350
	if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
J
jp9000 已提交
5351 5352
		OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"),
					QTStr("Output.RecordFail.Unsupported"));
J
jp9000 已提交
5353

5354
	} else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) {
J
jp9000 已提交
5355 5356 5357
		OBSMessageBox::warning(
			this, QTStr("Output.RecordError.Title"),
			QTStr("Output.RecordError.EncodeErrorMsg"));
5358

C
cg2121 已提交
5359
	} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
5360
		OBSMessageBox::warning(this,
J
jp9000 已提交
5361 5362
				       QTStr("Output.RecordNoSpace.Title"),
				       QTStr("Output.RecordNoSpace.Msg"));
J
jp9000 已提交
5363

C
cg2121 已提交
5364
	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
5365 5366 5367 5368 5369 5370 5371 5372 5373

		const char *errorDescription;
		DStr errorMessage;
		bool use_last_error = true;

		errorDescription = Str("Output.RecordError.Msg");

		if (use_last_error && !last_error.isEmpty())
			dstr_printf(errorMessage, "%s\n\n%s", errorDescription,
J
jp9000 已提交
5374
				    QT_TO_UTF8(last_error));
5375 5376 5377
		else
			dstr_copy(errorMessage, errorDescription);

J
jp9000 已提交
5378 5379
		OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"),
					QT_UTF8(errorMessage));
C
cg2121 已提交
5380 5381 5382

	} else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordFail.Unsupported"),
J
jp9000 已提交
5383
			      QSystemTrayIcon::Warning);
C
cg2121 已提交
5384 5385 5386

	} else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"),
J
jp9000 已提交
5387
			      QSystemTrayIcon::Warning);
C
cg2121 已提交
5388 5389 5390

	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordError.Msg"),
J
jp9000 已提交
5391
			      QSystemTrayIcon::Warning);
J
jp9000 已提交
5392 5393
	}

J
jp9000 已提交
5394 5395 5396
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPED);

C
cg2121 已提交
5397 5398 5399
	if (remuxAfterRecord)
		AutoRemux();

5400
	OnDeactivate();
J
jp9000 已提交
5401
	UpdatePause(false);
5402
}
5403

J
jp9000 已提交
5404
#define RP_NO_HOTKEY_TITLE QTStr("Output.ReplayBuffer.NoHotkey.Title")
J
jp9000 已提交
5405
#define RP_NO_HOTKEY_TEXT QTStr("Output.ReplayBuffer.NoHotkey.Msg")
J
jp9000 已提交
5406

J
jp9000 已提交
5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440
extern volatile bool recording_paused;
extern volatile bool replaybuf_active;

void OBSBasic::ShowReplayBufferPauseWarning()
{
	auto msgBox = []() {
		QMessageBox msgbox(App()->GetMainWindow());
		msgbox.setWindowTitle(QTStr("Output.ReplayBuffer."
					    "PauseWarning.Title"));
		msgbox.setText(QTStr("Output.ReplayBuffer."
				     "PauseWarning.Text"));
		msgbox.setIcon(QMessageBox::Icon::Information);
		msgbox.addButton(QMessageBox::Ok);

		QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain"));
		msgbox.setCheckBox(cb);

		msgbox.exec();

		if (cb->isChecked()) {
			config_set_bool(App()->GlobalConfig(), "General",
					"WarnedAboutReplayBufferPausing", true);
			config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
		}
	};

	bool warned = config_get_bool(App()->GlobalConfig(), "General",
				      "WarnedAboutReplayBufferPausing");
	if (!warned) {
		QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection,
					  Q_ARG(VoidFunc, msgBox));
	}
}

J
jp9000 已提交
5441 5442 5443 5444 5445 5446
void OBSBasic::StartReplayBuffer()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;
	if (outputHandler->ReplayBufferActive())
		return;
5447
	if (disableOutputsRef)
5448
		return;
J
jp9000 已提交
5449

5450 5451 5452 5453 5454
	if (!NoSourcesConfirmation()) {
		replayBufferButton->setChecked(false);
		return;
	}

J
jp9000 已提交
5455 5456
	obs_output_t *output = outputHandler->replayBuffer;
	obs_data_t *hotkeys = obs_hotkeys_save_output(output);
J
jp9000 已提交
5457 5458
	obs_data_array_t *bindings =
		obs_data_get_array(hotkeys, "ReplayBuffer.Save");
J
jp9000 已提交
5459 5460 5461 5462 5463
	size_t count = obs_data_array_count(bindings);
	obs_data_array_release(bindings);
	obs_data_release(hotkeys);

	if (!count) {
J
jp9000 已提交
5464 5465
		OBSMessageBox::information(this, RP_NO_HOTKEY_TITLE,
					   RP_NO_HOTKEY_TEXT);
5466
		replayBufferButton->setChecked(false);
J
jp9000 已提交
5467 5468 5469 5470 5471 5472 5473
		return;
	}

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING);

	SaveProject();
J
jp9000 已提交
5474 5475

	if (!outputHandler->StartReplayBuffer()) {
5476
		replayBufferButton->setChecked(false);
J
jp9000 已提交
5477 5478 5479
	} else if (os_atomic_load_bool(&recording_paused)) {
		ShowReplayBufferPauseWarning();
	}
J
jp9000 已提交
5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515
}

void OBSBasic::ReplayBufferStopping()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

	replayBufferButton->setText(QTStr("Basic.Main.StoppingReplayBuffer"));

	if (sysTrayReplayBuffer)
		sysTrayReplayBuffer->setText(replayBufferButton->text());

	replayBufferStopping = true;
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING);
}

void OBSBasic::StopReplayBuffer()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

	SaveProject();

	if (outputHandler->ReplayBufferActive())
		outputHandler->StopReplayBuffer(replayBufferStopping);

	OnDeactivate();
}

void OBSBasic::ReplayBufferStart()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

	replayBufferButton->setText(QTStr("Basic.Main.StopReplayBuffer"));
5516
	replayBufferButton->setChecked(true);
J
jp9000 已提交
5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529

	if (sysTrayReplayBuffer)
		sysTrayReplayBuffer->setText(replayBufferButton->text());

	replayBufferStopping = false;
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED);

	OnActivate();

	blog(LOG_INFO, REPLAY_BUFFER_START);
}

5530 5531 5532 5533 5534 5535 5536 5537
void OBSBasic::ReplayBufferSave()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;
	if (!outputHandler->ReplayBufferActive())
		return;

	calldata_t cd = {0};
J
jp9000 已提交
5538 5539
	proc_handler_t *ph =
		obs_output_get_proc_handler(outputHandler->replayBuffer);
5540 5541 5542 5543
	proc_handler_call(ph, "save", &cd);
	calldata_free(&cd);
}

J
jp9000 已提交
5544 5545 5546 5547 5548 5549
void OBSBasic::ReplayBufferStop(int code)
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

	replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer"));
5550
	replayBufferButton->setChecked(false);
J
jp9000 已提交
5551 5552 5553 5554 5555 5556 5557

	if (sysTrayReplayBuffer)
		sysTrayReplayBuffer->setText(replayBufferButton->text());

	blog(LOG_INFO, REPLAY_BUFFER_STOP);

	if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
J
jp9000 已提交
5558 5559
		OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"),
					QTStr("Output.RecordFail.Unsupported"));
J
jp9000 已提交
5560 5561

	} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
5562
		OBSMessageBox::warning(this,
J
jp9000 已提交
5563 5564
				       QTStr("Output.RecordNoSpace.Title"),
				       QTStr("Output.RecordNoSpace.Msg"));
J
jp9000 已提交
5565 5566

	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
J
jp9000 已提交
5567 5568
		OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"),
					QTStr("Output.RecordError.Msg"));
J
jp9000 已提交
5569 5570 5571

	} else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordFail.Unsupported"),
J
jp9000 已提交
5572
			      QSystemTrayIcon::Warning);
J
jp9000 已提交
5573 5574 5575

	} else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"),
J
jp9000 已提交
5576
			      QSystemTrayIcon::Warning);
J
jp9000 已提交
5577 5578 5579

	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordError.Msg"),
J
jp9000 已提交
5580
			      QSystemTrayIcon::Warning);
J
jp9000 已提交
5581 5582 5583 5584 5585 5586 5587 5588
	}

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED);

	OnDeactivate();
}

5589 5590 5591 5592 5593 5594 5595 5596
bool OBSBasic::NoSourcesConfirmation()
{
	if (CountVideoSources() == 0 && isVisible()) {
		QString msg;
		msg = QTStr("NoSources.Text");
		msg += "\n\n";
		msg += QTStr("NoSources.Text.AddSource");

5597 5598 5599
		QMessageBox messageBox(this);
		messageBox.setWindowTitle(QTStr("NoSources.Title"));
		messageBox.setText(msg);
J
jp9000 已提交
5600 5601
		QAbstractButton *Yes = messageBox.addButton(
			QTStr("Yes"), QMessageBox::YesRole);
5602 5603 5604 5605 5606
		messageBox.addButton(QTStr("No"), QMessageBox::NoRole);
		messageBox.setIcon(QMessageBox::Question);
		messageBox.exec();

		if (messageBox.clickedButton() != Yes)
5607 5608 5609 5610 5611 5612
			return false;
	}

	return true;
}

5613 5614
void OBSBasic::on_streamButton_clicked()
{
J
jp9000 已提交
5615
	if (outputHandler->StreamingActive()) {
5616
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
J
jp9000 已提交
5617
					       "WarnBeforeStoppingStream");
5618

C
cg2121 已提交
5619
		if (confirm && isVisible()) {
5620
			QMessageBox::StandardButton button =
J
jp9000 已提交
5621 5622 5623
				OBSMessageBox::question(
					this, QTStr("ConfirmStop.Title"),
					QTStr("ConfirmStop.Text"));
5624

C
cg2121 已提交
5625 5626
			if (button == QMessageBox::No) {
				ui->streamButton->setChecked(true);
5627
				return;
C
cg2121 已提交
5628
			}
5629 5630
		}

5631
		StopStreaming();
5632
	} else {
5633 5634 5635 5636 5637
		if (!NoSourcesConfirmation()) {
			ui->streamButton->setChecked(false);
			return;
		}

5638
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
J
jp9000 已提交
5639
					       "WarnBeforeStartingStream");
5640

5641 5642 5643 5644 5645 5646
		obs_data_t *settings = obs_service_get_settings(service);
		bool bwtest = obs_data_get_bool(settings, "bwtest");
		obs_data_release(settings);

		if (bwtest && isVisible()) {
			QMessageBox::StandardButton button =
J
jp9000 已提交
5647 5648 5649
				OBSMessageBox::question(
					this, QTStr("ConfirmBWTest.Title"),
					QTStr("ConfirmBWTest.Text"));
5650 5651 5652 5653 5654 5655

			if (button == QMessageBox::No) {
				ui->streamButton->setChecked(false);
				return;
			}
		} else if (confirm && isVisible()) {
5656
			QMessageBox::StandardButton button =
J
jp9000 已提交
5657 5658 5659
				OBSMessageBox::question(
					this, QTStr("ConfirmStart.Title"),
					QTStr("ConfirmStart.Text"));
5660

C
cg2121 已提交
5661 5662
			if (button == QMessageBox::No) {
				ui->streamButton->setChecked(false);
5663
				return;
C
cg2121 已提交
5664
			}
5665 5666
		}

5667
		StartStreaming();
5668 5669 5670 5671 5672
	}
}

void OBSBasic::on_recordButton_clicked()
{
5673
	if (outputHandler->RecordingActive()) {
5674
		StopRecording();
5675 5676 5677 5678 5679 5680
	} else {
		if (!NoSourcesConfirmation()) {
			ui->recordButton->setChecked(false);
			return;
		}

5681
		StartRecording();
5682
	}
J
jp9000 已提交
5683 5684
}

J
jp9000 已提交
5685
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
5686
{
J
jp9000 已提交
5687
	on_action_Settings_triggered();
J
jp9000 已提交
5688
}
5689

5690 5691 5692 5693 5694 5695
void OBSBasic::on_actionHelpPortal_triggered()
{
	QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

5696 5697 5698 5699 5700 5701
void OBSBasic::on_actionWebsite_triggered()
{
	QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

5702 5703
void OBSBasic::on_actionDiscord_triggered()
{
5704
	QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode);
5705 5706 5707
	QDesktopServices::openUrl(url);
}

5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727
void OBSBasic::on_actionShowSettingsFolder_triggered()
{
	char path[512];
	int ret = GetConfigPath(path, 512, "obs-studio");
	if (ret <= 0)
		return;

	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

void OBSBasic::on_actionShowProfileFolder_triggered()
{
	char path[512];
	int ret = GetProfilePath(path, 512, "");
	if (ret <= 0)
		return;

	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

J
jp9000 已提交
5728
int OBSBasic::GetTopSelectedSourceItem()
5729
{
J
jp9000 已提交
5730 5731 5732
	QModelIndexList selectedItems =
		ui->sources->selectionModel()->selectedIndexes();
	return selectedItems.count() ? selectedItems[0].row() : -1;
5733 5734
}

J
jp9000 已提交
5735 5736
void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos)
{
5737
	CreateSourcePopupMenu(GetTopSelectedSourceItem(), true);
J
jp9000 已提交
5738 5739 5740 5741

	UNUSED_PARAMETER(pos);
}

J
jp9000 已提交
5742
void OBSBasic::on_program_customContextMenuRequested(const QPoint &)
R
Ryan Foster 已提交
5743 5744 5745 5746
{
	QMenu popup(this);
	QPointer<QMenu> studioProgramProjector;

J
jp9000 已提交
5747
	studioProgramProjector = new QMenu(QTStr("StudioProgramProjector"));
R
Ryan Foster 已提交
5748
	AddProjectorMenuMonitors(studioProgramProjector, this,
J
jp9000 已提交
5749
				 SLOT(OpenStudioProgramProjector()));
R
Ryan Foster 已提交
5750 5751 5752

	popup.addMenu(studioProgramProjector);

J
jp9000 已提交
5753 5754 5755
	QAction *studioProgramWindow =
		popup.addAction(QTStr("StudioProgramWindow"), this,
				SLOT(OpenStudioProgramWindow()));
R
Ryan Foster 已提交
5756 5757 5758 5759 5760 5761

	popup.addAction(studioProgramWindow);

	popup.exec(QCursor::pos());
}

J
jp9000 已提交
5762
void OBSBasic::on_previewDisabledLabel_customContextMenuRequested(
J
jp9000 已提交
5763
	const QPoint &pos)
J
jp9000 已提交
5764 5765
{
	QMenu popup(this);
P
pkv 已提交
5766
	delete previewProjectorMain;
J
jp9000 已提交
5767

J
jp9000 已提交
5768 5769 5770
	QAction *action =
		popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"),
				this, SLOT(TogglePreview()));
J
jp9000 已提交
5771
	action->setCheckable(true);
5772
	action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
J
jp9000 已提交
5773

P
pkv 已提交
5774 5775
	previewProjectorMain = new QMenu(QTStr("PreviewProjector"));
	AddProjectorMenuMonitors(previewProjectorMain, this,
J
jp9000 已提交
5776
				 SLOT(OpenPreviewProjector()));
J
jp9000 已提交
5777

J
jp9000 已提交
5778 5779
	QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this,
						 SLOT(OpenPreviewWindow()));
C
cg2121 已提交
5780

P
pkv 已提交
5781
	popup.addMenu(previewProjectorMain);
C
cg2121 已提交
5782
	popup.addAction(previewWindow);
J
jp9000 已提交
5783 5784 5785 5786 5787
	popup.exec(QCursor::pos());

	UNUSED_PARAMETER(pos);
}

5788 5789
void OBSBasic::on_actionAlwaysOnTop_triggered()
{
5790
#ifndef _WIN32
5791 5792 5793 5794
	/* Make sure all dialogs are safely and successfully closed before
	 * switching the always on top mode due to the fact that windows all
	 * have to be recreated, so queue the actual toggle to happen after
	 * all events related to closing the dialogs have finished */
5795 5796 5797
	CloseDialogs();
#endif

5798
	QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop",
J
jp9000 已提交
5799
				  Qt::QueuedConnection);
5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811
}

void OBSBasic::ToggleAlwaysOnTop()
{
	bool isAlwaysOnTop = IsAlwaysOnTop(this);

	ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop);
	SetAlwaysOnTop(this, !isAlwaysOnTop);

	show();
}

5812 5813 5814 5815 5816 5817 5818 5819 5820 5821
void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const
{
	const char *val = config_get_string(basicConfig, "Video", "FPSCommon");

	if (strcmp(val, "10") == 0) {
		num = 10;
		den = 1;
	} else if (strcmp(val, "20") == 0) {
		num = 20;
		den = 1;
J
jp9000 已提交
5822 5823 5824
	} else if (strcmp(val, "24 NTSC") == 0) {
		num = 24000;
		den = 1001;
5825
	} else if (strcmp(val, "25 PAL") == 0) {
5826 5827 5828 5829 5830 5831 5832 5833
		num = 25;
		den = 1;
	} else if (strcmp(val, "29.97") == 0) {
		num = 30000;
		den = 1001;
	} else if (strcmp(val, "48") == 0) {
		num = 48;
		den = 1;
5834 5835 5836
	} else if (strcmp(val, "50 PAL") == 0) {
		num = 50;
		den = 1;
5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880
	} else if (strcmp(val, "59.94") == 0) {
		num = 60000;
		den = 1001;
	} else if (strcmp(val, "60") == 0) {
		num = 60;
		den = 1;
	} else {
		num = 30;
		den = 1;
	}
}

void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const
{
	num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSInt");
	den = 1;
}

void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const
{
	num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNum");
	den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSDen");
}

void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const
{
	num = 1000000000;
	den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNS");
}

void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const
{
	uint32_t type = config_get_uint(basicConfig, "Video", "FPSType");

	if (type == 1) //"Integer"
		GetFPSInteger(num, den);
	else if (type == 2) //"Fraction"
		GetFPSFraction(num, den);
	else if (false) //"Nanoseconds", currently not implemented
		GetFPSNanoseconds(num, den);
	else
		GetFPSCommon(num, den);
}

5881
config_t *OBSBasic::Config() const
5882 5883 5884
{
	return basicConfig;
}
J
jp9000 已提交
5885 5886 5887

void OBSBasic::on_actionEditTransform_triggered()
{
5888 5889 5890
	if (transformWindow)
		transformWindow->close();

J
jp9000 已提交
5891 5892
	transformWindow = new OBSBasicTransform(this);
	transformWindow->show();
5893
	transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
J
jp9000 已提交
5894 5895
}

5896 5897 5898 5899 5900
static obs_transform_info copiedTransformInfo;
static obs_sceneitem_crop copiedCropInfo;

void OBSBasic::on_actionCopyTransform_triggered()
{
J
jp9000 已提交
5901
	auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param) {
5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920
		if (!obs_sceneitem_selected(item))
			return true;

		obs_sceneitem_defer_update_begin(item);
		obs_sceneitem_get_info(item, &copiedTransformInfo);
		obs_sceneitem_get_crop(item, &copiedCropInfo);
		obs_sceneitem_defer_update_end(item);

		UNUSED_PARAMETER(scene);
		UNUSED_PARAMETER(param);
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
	ui->actionPasteTransform->setEnabled(true);
}

void OBSBasic::on_actionPasteTransform_triggered()
{
J
jp9000 已提交
5921
	auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param) {
5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937
		if (!obs_sceneitem_selected(item))
			return true;

		obs_sceneitem_defer_update_begin(item);
		obs_sceneitem_set_info(item, &copiedTransformInfo);
		obs_sceneitem_set_crop(item, &copiedCropInfo);
		obs_sceneitem_defer_update_end(item);

		UNUSED_PARAMETER(scene);
		UNUSED_PARAMETER(param);
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
}

5938
static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
5939
{
5940 5941 5942 5943
	if (obs_sceneitem_is_group(item))
		obs_sceneitem_group_enum_items(item, reset_tr, nullptr);
	if (!obs_sceneitem_selected(item))
		return true;
J
jp9000 已提交
5944

5945
	obs_sceneitem_defer_update_begin(item);
J
jp9000 已提交
5946

5947 5948 5949 5950 5951 5952 5953 5954 5955
	obs_transform_info info;
	vec2_set(&info.pos, 0.0f, 0.0f);
	vec2_set(&info.scale, 1.0f, 1.0f);
	info.rot = 0.0f;
	info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
	info.bounds_type = OBS_BOUNDS_NONE;
	info.bounds_alignment = OBS_ALIGN_CENTER;
	vec2_set(&info.bounds, 0.0f, 0.0f);
	obs_sceneitem_set_info(item, &info);
J
jp9000 已提交
5956

5957 5958
	obs_sceneitem_crop crop = {};
	obs_sceneitem_set_crop(item, &crop);
J
jp9000 已提交
5959

5960
	obs_sceneitem_defer_update_end(item);
J
jp9000 已提交
5961

5962 5963 5964 5965
	UNUSED_PARAMETER(scene);
	UNUSED_PARAMETER(param);
	return true;
}
J
jp9000 已提交
5966

5967 5968 5969
void OBSBasic::on_actionResetTransform_triggered()
{
	obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr);
J
jp9000 已提交
5970 5971
}

5972
static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
J
jp9000 已提交
5973 5974 5975 5976 5977
{
	matrix4 boxTransform;
	obs_sceneitem_get_box_transform(item, &boxTransform);

	vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
5978
	vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
J
jp9000 已提交
5979

J
jp9000 已提交
5980
	auto GetMinPos = [&](float x, float y) {
J
jp9000 已提交
5981 5982 5983
		vec3 pos;
		vec3_set(&pos, x, y, 0.0f);
		vec3_transform(&pos, &pos, &boxTransform);
5984 5985
		vec3_min(&tl, &tl, &pos);
		vec3_max(&br, &br, &pos);
J
jp9000 已提交
5986 5987
	};

5988 5989 5990 5991 5992 5993
	GetMinPos(0.0f, 0.0f);
	GetMinPos(1.0f, 0.0f);
	GetMinPos(0.0f, 1.0f);
	GetMinPos(1.0f, 1.0f);
}

5994
static vec3 GetItemTL(obs_sceneitem_t *item)
5995 5996 5997
{
	vec3 tl, br;
	GetItemBox(item, tl, br);
J
jp9000 已提交
5998 5999 6000
	return tl;
}

6001
static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
J
jp9000 已提交
6002 6003 6004 6005
{
	vec3 newTL;
	vec2 pos;

J
jp9000 已提交
6006
	obs_sceneitem_get_pos(item, &pos);
J
jp9000 已提交
6007 6008 6009
	newTL = GetItemTL(item);
	pos.x += tl.x - newTL.x;
	pos.y += tl.y - newTL.y;
J
jp9000 已提交
6010
	obs_sceneitem_set_pos(item, &pos);
J
jp9000 已提交
6011 6012
}

6013
static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
6014
				  void *param)
J
jp9000 已提交
6015
{
6016 6017
	if (obs_sceneitem_is_group(item))
		obs_sceneitem_group_enum_items(item, RotateSelectedSources,
J
jp9000 已提交
6018
					       param);
J
jp9000 已提交
6019 6020 6021
	if (!obs_sceneitem_selected(item))
		return true;

J
jp9000 已提交
6022
	float rot = *reinterpret_cast<float *>(param);
J
jp9000 已提交
6023 6024 6025

	vec3 tl = GetItemTL(item);

J
jp9000 已提交
6026
	rot += obs_sceneitem_get_rot(item);
J
jp9000 已提交
6027 6028 6029 6030
	if (rot >= 360.0f)
		rot -= 360.0f;
	else if (rot <= -360.0f)
		rot += 360.0f;
J
jp9000 已提交
6031
	obs_sceneitem_set_rot(item, rot);
J
jp9000 已提交
6032

6033 6034
	obs_sceneitem_force_update_transform(item);

J
jp9000 已提交
6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058
	SetItemTL(item, tl);

	UNUSED_PARAMETER(scene);
	return true;
};

void OBSBasic::on_actionRotate90CW_triggered()
{
	float f90CW = 90.0f;
	obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW);
}

void OBSBasic::on_actionRotate90CCW_triggered()
{
	float f90CCW = -90.0f;
	obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW);
}

void OBSBasic::on_actionRotate180_triggered()
{
	float f180 = 180.0f;
	obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180);
}

6059
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
6060
				      void *param)
J
jp9000 已提交
6061
{
J
jp9000 已提交
6062
	vec2 &mul = *reinterpret_cast<vec2 *>(param);
J
jp9000 已提交
6063

6064 6065
	if (obs_sceneitem_is_group(item))
		obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale,
J
jp9000 已提交
6066
					       param);
J
jp9000 已提交
6067 6068 6069 6070 6071 6072
	if (!obs_sceneitem_selected(item))
		return true;

	vec3 tl = GetItemTL(item);

	vec2 scale;
J
jp9000 已提交
6073
	obs_sceneitem_get_scale(item, &scale);
J
jp9000 已提交
6074
	vec2_mul(&scale, &scale, &mul);
J
jp9000 已提交
6075
	obs_sceneitem_set_scale(item, &scale);
J
jp9000 已提交
6076

6077 6078
	obs_sceneitem_force_update_transform(item);

J
jp9000 已提交
6079
	SetItemTL(item, tl);
J
jp9000 已提交
6080 6081

	UNUSED_PARAMETER(scene);
J
jp9000 已提交
6082 6083 6084 6085 6086
	return true;
}

void OBSBasic::on_actionFlipHorizontal_triggered()
{
J
jp9000 已提交
6087 6088
	vec2 scale;
	vec2_set(&scale, -1.0f, 1.0f);
J
jp9000 已提交
6089
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
J
jp9000 已提交
6090
			     &scale);
J
jp9000 已提交
6091 6092 6093 6094
}

void OBSBasic::on_actionFlipVertical_triggered()
{
J
jp9000 已提交
6095 6096
	vec2 scale;
	vec2_set(&scale, 1.0f, -1.0f);
J
jp9000 已提交
6097
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
J
jp9000 已提交
6098
			     &scale);
J
jp9000 已提交
6099 6100
}

6101
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
6102
				     void *param)
J
jp9000 已提交
6103
{
J
jp9000 已提交
6104 6105
	obs_bounds_type boundsType =
		*reinterpret_cast<obs_bounds_type *>(param);
J
jp9000 已提交
6106

6107 6108
	if (obs_sceneitem_is_group(item))
		obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems,
J
jp9000 已提交
6109
					       param);
J
jp9000 已提交
6110 6111 6112 6113 6114 6115
	if (!obs_sceneitem_selected(item))
		return true;

	obs_video_info ovi;
	obs_get_video_info(&ovi);

6116
	obs_transform_info itemInfo;
J
jp9000 已提交
6117 6118 6119 6120 6121
	vec2_set(&itemInfo.pos, 0.0f, 0.0f);
	vec2_set(&itemInfo.scale, 1.0f, 1.0f);
	itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
	itemInfo.rot = 0.0f;

J
jp9000 已提交
6122 6123
	vec2_set(&itemInfo.bounds, float(ovi.base_width),
		 float(ovi.base_height));
J
jp9000 已提交
6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136
	itemInfo.bounds_type = boundsType;
	itemInfo.bounds_alignment = OBS_ALIGN_CENTER;

	obs_sceneitem_set_info(item, &itemInfo);

	UNUSED_PARAMETER(scene);
	return true;
}

void OBSBasic::on_actionFitToScreen_triggered()
{
	obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER;
	obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems,
J
jp9000 已提交
6137
			     &boundsType);
J
jp9000 已提交
6138 6139 6140 6141 6142 6143
}

void OBSBasic::on_actionStretchToScreen_triggered()
{
	obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
	obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems,
J
jp9000 已提交
6144
			     &boundsType);
J
jp9000 已提交
6145 6146
}

6147 6148 6149
enum class CenterType {
	Scene,
	Vertical,
J
jp9000 已提交
6150
	Horizontal,
6151 6152 6153
};

static bool center_to_scene(obs_scene_t *, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
6154
{
J
jp9000 已提交
6155
	CenterType centerType = *reinterpret_cast<CenterType *>(param);
6156

6157 6158
	vec3 tl, br, itemCenter, screenCenter, offset;
	obs_video_info ovi;
6159
	obs_transform_info oti;
6160

6161
	if (obs_sceneitem_is_group(item))
6162
		obs_sceneitem_group_enum_items(item, center_to_scene,
J
jp9000 已提交
6163
					       &centerType);
6164 6165
	if (!obs_sceneitem_selected(item))
		return true;
6166

6167
	obs_get_video_info(&ovi);
6168 6169 6170 6171
	obs_sceneitem_get_info(item, &oti);

	if (centerType == CenterType::Scene)
		vec3_set(&screenCenter, float(ovi.base_width),
J
jp9000 已提交
6172
			 float(ovi.base_height), 0.0f);
6173 6174
	else if (centerType == CenterType::Vertical)
		vec3_set(&screenCenter, float(oti.bounds.x),
J
jp9000 已提交
6175
			 float(ovi.base_height), 0.0f);
6176 6177
	else if (centerType == CenterType::Horizontal)
		vec3_set(&screenCenter, float(ovi.base_width),
J
jp9000 已提交
6178
			 float(oti.bounds.y), 0.0f);
6179

6180
	vec3_mulf(&screenCenter, &screenCenter, 0.5f);
6181

6182
	GetItemBox(item, tl, br);
6183

6184 6185 6186
	vec3_sub(&itemCenter, &br, &tl);
	vec3_mulf(&itemCenter, &itemCenter, 0.5f);
	vec3_add(&itemCenter, &itemCenter, &tl);
6187

6188 6189
	vec3_sub(&offset, &screenCenter, &itemCenter);
	vec3_add(&tl, &tl, &offset);
6190

6191 6192 6193 6194 6195
	if (centerType == CenterType::Vertical)
		tl.x = oti.pos.x;
	else if (centerType == CenterType::Horizontal)
		tl.y = oti.pos.y;

6196 6197 6198
	SetItemTL(item, tl);
	return true;
};
6199

6200 6201
void OBSBasic::on_actionCenterToScreen_triggered()
{
6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215
	CenterType centerType = CenterType::Scene;
	obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType);
}

void OBSBasic::on_actionVerticalCenter_triggered()
{
	CenterType centerType = CenterType::Vertical;
	obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType);
}

void OBSBasic::on_actionHorizontalCenter_triggered()
{
	CenterType centerType = CenterType::Horizontal;
	obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType);
J
jp9000 已提交
6216
}
J
jp9000 已提交
6217

6218 6219 6220 6221 6222 6223 6224
void OBSBasic::EnablePreviewDisplay(bool enable)
{
	obs_display_set_enabled(ui->preview->GetDisplay(), enable);
	ui->preview->setVisible(enable);
	ui->previewDisabledLabel->setVisible(!enable);
}

J
jp9000 已提交
6225 6226
void OBSBasic::TogglePreview()
{
6227 6228
	previewEnabled = !previewEnabled;
	EnablePreviewDisplay(previewEnabled);
J
jp9000 已提交
6229
}
6230

6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248
void OBSBasic::EnablePreview()
{
	if (previewProgramMode)
		return;

	previewEnabled = true;
	EnablePreviewDisplay(true);
}

void OBSBasic::DisablePreview()
{
	if (previewProgramMode)
		return;

	previewEnabled = false;
	EnablePreviewDisplay(false);
}

J
jp9000 已提交
6249
static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param)
6250
{
J
jp9000 已提交
6251 6252
	if (obs_sceneitem_locked(item))
		return true;
6253

J
jp9000 已提交
6254
	struct vec2 &offset = *reinterpret_cast<struct vec2 *>(param);
J
jp9000 已提交
6255
	struct vec2 pos;
6256

J
jp9000 已提交
6257
	if (!obs_sceneitem_selected(item)) {
J
jp9000 已提交
6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270
		if (obs_sceneitem_is_group(item)) {
			struct vec3 offset3;
			vec3_set(&offset3, offset.x, offset.y, 0.0f);

			struct matrix4 matrix;
			obs_sceneitem_get_draw_transform(item, &matrix);
			vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f);
			matrix4_inv(&matrix, &matrix);
			vec3_transform(&offset3, &offset3, &matrix);

			struct vec2 new_offset;
			vec2_set(&new_offset, offset3.x, offset3.y);
			obs_sceneitem_group_enum_items(item, nudge_callback,
J
jp9000 已提交
6271
						       &new_offset);
J
jp9000 已提交
6272 6273
		}

J
jp9000 已提交
6274 6275
		return true;
	}
6276

J
jp9000 已提交
6277 6278 6279 6280 6281
	obs_sceneitem_get_pos(item, &pos);
	vec2_add(&pos, &pos, &offset);
	obs_sceneitem_set_pos(item, &pos);
	return true;
}
J
jp9000 已提交
6282

J
jp9000 已提交
6283 6284 6285 6286
void OBSBasic::Nudge(int dist, MoveDir dir)
{
	if (ui->preview->Locked())
		return;
6287

J
jp9000 已提交
6288 6289
	struct vec2 offset;
	vec2_set(&offset, 0.0f, 0.0f);
6290

J
jp9000 已提交
6291
	switch (dir) {
J
jp9000 已提交
6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303
	case MoveDir::Up:
		offset.y = (float)-dist;
		break;
	case MoveDir::Down:
		offset.y = (float)dist;
		break;
	case MoveDir::Left:
		offset.x = (float)-dist;
		break;
	case MoveDir::Right:
		offset.x = (float)dist;
		break;
J
jp9000 已提交
6304
	}
6305

J
jp9000 已提交
6306
	obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset);
6307 6308
}

J
jp9000 已提交
6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324
void OBSBasic::NudgeUp()
{
	Nudge(1, MoveDir::Up);
}
void OBSBasic::NudgeDown()
{
	Nudge(1, MoveDir::Down);
}
void OBSBasic::NudgeLeft()
{
	Nudge(1, MoveDir::Left);
}
void OBSBasic::NudgeRight()
{
	Nudge(1, MoveDir::Right);
}
J
jp9000 已提交
6325

S
Shaolin 已提交
6326
OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor,
J
jp9000 已提交
6327
				      QString title, ProjectorType type)
J
jp9000 已提交
6328 6329
{
	/* seriously?  10 monitors? */
C
cg2121 已提交
6330
	if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1)
S
Shaolin 已提交
6331
		return nullptr;
C
cg2121 已提交
6332

J
jp9000 已提交
6333 6334
	OBSProjector *projector =
		new OBSProjector(nullptr, source, monitor, title, type);
C
cg2121 已提交
6335

S
Shaolin 已提交
6336
	if (monitor < 0) {
C
cg2121 已提交
6337 6338 6339 6340 6341 6342 6343 6344 6345
		for (auto &projPtr : windowProjectors) {
			if (!projPtr) {
				projPtr = projector;
				projector = nullptr;
			}
		}

		if (projector)
			windowProjectors.push_back(projector);
S
Shaolin 已提交
6346
	} else {
C
cg2121 已提交
6347 6348
		delete projectors[monitor];
		projectors[monitor].clear();
C
cg2121 已提交
6349

C
cg2121 已提交
6350 6351
		projectors[monitor] = projector;
	}
S
Shaolin 已提交
6352 6353

	return projector;
J
jp9000 已提交
6354 6355
}

R
Ryan Foster 已提交
6356 6357 6358
void OBSBasic::OpenStudioProgramProjector()
{
	int monitor = sender()->property("monitor").toInt();
6359
	OpenProjector(nullptr, monitor, nullptr, ProjectorType::StudioProgram);
R
Ryan Foster 已提交
6360 6361
}

J
jp9000 已提交
6362 6363 6364
void OBSBasic::OpenPreviewProjector()
{
	int monitor = sender()->property("monitor").toInt();
6365
	OpenProjector(nullptr, monitor, nullptr, ProjectorType::Preview);
J
jp9000 已提交
6366 6367 6368 6369 6370 6371 6372 6373 6374
}

void OBSBasic::OpenSourceProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;

S
Shaolin 已提交
6375
	OpenProjector(obs_sceneitem_get_source(item), monitor, nullptr,
J
jp9000 已提交
6376
		      ProjectorType::Source);
J
jp9000 已提交
6377 6378
}

S
Shaolin 已提交
6379 6380 6381
void OBSBasic::OpenMultiviewProjector()
{
	int monitor = sender()->property("monitor").toInt();
6382
	OpenProjector(nullptr, monitor, nullptr, ProjectorType::Multiview);
S
Shaolin 已提交
6383 6384
}

J
jp9000 已提交
6385 6386 6387 6388 6389 6390 6391
void OBSBasic::OpenSceneProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OBSScene scene = GetCurrentScene();
	if (!scene)
		return;

6392
	OpenProjector(obs_scene_get_source(scene), monitor, nullptr,
J
jp9000 已提交
6393
		      ProjectorType::Scene);
C
cg2121 已提交
6394 6395
}

R
Ryan Foster 已提交
6396 6397
void OBSBasic::OpenStudioProgramWindow()
{
S
Shaolin 已提交
6398
	OpenProjector(nullptr, -1, QTStr("StudioProgramWindow"),
J
jp9000 已提交
6399
		      ProjectorType::StudioProgram);
R
Ryan Foster 已提交
6400 6401
}

C
cg2121 已提交
6402 6403
void OBSBasic::OpenPreviewWindow()
{
S
Shaolin 已提交
6404
	OpenProjector(nullptr, -1, QTStr("PreviewWindow"),
J
jp9000 已提交
6405
		      ProjectorType::Preview);
C
cg2121 已提交
6406 6407 6408 6409 6410 6411 6412 6413 6414
}

void OBSBasic::OpenSourceWindow()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;

	OBSSource source = obs_sceneitem_get_source(item);
6415
	QString title = QString::fromUtf8(obs_source_get_name(source));
S
Shaolin 已提交
6416 6417

	OpenProjector(obs_sceneitem_get_source(item), -1, title,
J
jp9000 已提交
6418
		      ProjectorType::Source);
C
cg2121 已提交
6419 6420
}

S
Shaolin 已提交
6421 6422
void OBSBasic::OpenMultiviewWindow()
{
S
Shaolin 已提交
6423
	OpenProjector(nullptr, -1, QTStr("MultiviewWindowed"),
J
jp9000 已提交
6424
		      ProjectorType::Multiview);
S
Shaolin 已提交
6425 6426
}

C
cg2121 已提交
6427 6428 6429 6430 6431 6432 6433
void OBSBasic::OpenSceneWindow()
{
	OBSScene scene = GetCurrentScene();
	if (!scene)
		return;

	OBSSource source = obs_scene_get_source(scene);
6434
	QString title = QString::fromUtf8(obs_source_get_name(source));
S
Shaolin 已提交
6435 6436

	OpenProjector(obs_scene_get_source(scene), -1, title,
J
jp9000 已提交
6437
		      ProjectorType::Scene);
J
jp9000 已提交
6438
}
6439

C
cg2121 已提交
6440 6441
void OBSBasic::OpenSavedProjectors()
{
6442
	for (SavedProjectorInfo *info : savedProjectorsArray) {
S
Shaolin 已提交
6443
		OBSProjector *projector = nullptr;
6444
		switch (info->type) {
S
Shaolin 已提交
6445
		case ProjectorType::Source:
6446
		case ProjectorType::Scene: {
J
jp9000 已提交
6447 6448
			OBSSource source =
				obs_get_source_by_name(info->name.c_str());
6449 6450
			if (!source)
				continue;
C
cg2121 已提交
6451

S
Shaolin 已提交
6452 6453 6454
			QString title = nullptr;
			if (info->monitor < 0)
				title = QString::fromUtf8(
J
jp9000 已提交
6455
					obs_source_get_name(source));
R
Ryan Foster 已提交
6456

S
Shaolin 已提交
6457
			projector = OpenProjector(source, info->monitor, title,
J
jp9000 已提交
6458
						  info->type);
6459 6460 6461

			obs_source_release(source);
			break;
S
Shaolin 已提交
6462
		}
6463
		case ProjectorType::Preview: {
S
Shaolin 已提交
6464
			projector = OpenProjector(nullptr, info->monitor,
J
jp9000 已提交
6465 6466
						  QTStr("PreviewWindow"),
						  ProjectorType::Preview);
6467
			break;
S
Shaolin 已提交
6468
		}
6469
		case ProjectorType::StudioProgram: {
S
Shaolin 已提交
6470
			projector = OpenProjector(nullptr, info->monitor,
J
jp9000 已提交
6471 6472
						  QTStr("StudioProgramWindow"),
						  ProjectorType::StudioProgram);
6473 6474 6475
			break;
		}
		case ProjectorType::Multiview: {
S
Shaolin 已提交
6476
			projector = OpenProjector(nullptr, info->monitor,
J
jp9000 已提交
6477 6478
						  QTStr("MultiviewWindowed"),
						  ProjectorType::Multiview);
6479 6480
			break;
		}
C
cg2121 已提交
6481
		}
S
Shaolin 已提交
6482

6483
		if (projector && !info->geometry.empty() && info->monitor < 0) {
S
Shaolin 已提交
6484
			QByteArray byteArray = QByteArray::fromBase64(
J
jp9000 已提交
6485
				QByteArray(info->geometry.c_str()));
S
Shaolin 已提交
6486
			projector->restoreGeometry(byteArray);
S
Shaolin 已提交
6487

S
Shaolin 已提交
6488 6489 6490
			if (!WindowPositionValid(projector->normalGeometry())) {
				QRect rect = App()->desktop()->geometry();
				projector->setGeometry(QStyle::alignedRect(
J
jp9000 已提交
6491 6492
					Qt::LeftToRight, Qt::AlignCenter,
					size(), rect));
C
cg2121 已提交
6493 6494 6495 6496 6497
			}
		}
	}
}

6498 6499 6500 6501 6502 6503 6504 6505 6506 6507
void OBSBasic::on_actionFullscreenInterface_triggered()
{
	if (!fullscreenInterface)
		showFullScreen();
	else
		showNormal();

	fullscreenInterface = !fullscreenInterface;
}

6508 6509 6510 6511
void OBSBasic::UpdateTitleBar()
{
	stringstream name;

J
jp9000 已提交
6512 6513 6514 6515
	const char *profile =
		config_get_string(App()->GlobalConfig(), "Basic", "Profile");
	const char *sceneCollection = config_get_string(
		App()->GlobalConfig(), "Basic", "SceneCollection");
J
jp9000 已提交
6516

6517 6518 6519 6520 6521
	name << "OBS ";
	if (previewProgramMode)
		name << "Studio ";

	name << App()->GetVersionString();
6522 6523 6524
	if (App()->IsPortableMode())
		name << " - Portable Mode";

J
jp9000 已提交
6525
	name << " - " << Str("TitleBar.Profile") << ": " << profile;
J
jp9000 已提交
6526
	name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;
6527 6528 6529

	setWindowTitle(QT_UTF8(name.str().c_str()));
}
J
jp9000 已提交
6530 6531 6532 6533

int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const
{
	char profiles_path[512];
J
jp9000 已提交
6534 6535
	const char *profile =
		config_get_string(App()->GlobalConfig(), "Basic", "ProfileDir");
J
jp9000 已提交
6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553
	int ret;

	if (!profile)
		return -1;
	if (!path)
		return -1;
	if (!file)
		file = "";

	ret = GetConfigPath(profiles_path, 512, "obs-studio/basic/profiles");
	if (ret <= 0)
		return ret;

	if (!*file)
		return snprintf(path, size, "%s/%s", profiles_path, profile);

	return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file);
}
6554

J
jp9000 已提交
6555
void OBSBasic::on_resetUI_triggered()
6556
{
6557
	/* prune deleted extra docks */
J
jp9000 已提交
6558
	for (int i = extraDocks.size() - 1; i >= 0; i--) {
6559 6560 6561 6562 6563 6564 6565
		if (!extraDocks[i]) {
			extraDocks.removeAt(i);
		}
	}

	if (extraDocks.size()) {
		QMessageBox::StandardButton button = QMessageBox::question(
J
jp9000 已提交
6566 6567
			this, QTStr("ResetUIWarning.Title"),
			QTStr("ResetUIWarning.Text"));
6568 6569 6570 6571 6572 6573

		if (button == QMessageBox::No)
			return;
	}

	/* undock/hide/center extra docks */
J
jp9000 已提交
6574
	for (int i = extraDocks.size() - 1; i >= 0; i--) {
6575 6576 6577
		if (extraDocks[i]) {
			extraDocks[i]->setVisible(true);
			extraDocks[i]->setFloating(true);
J
jp9000 已提交
6578 6579 6580
			extraDocks[i]->move(frameGeometry().topLeft() +
					    rect().center() -
					    extraDocks[i]->rect().center());
6581 6582 6583 6584
			extraDocks[i]->setVisible(false);
		}
	}

J
jp9000 已提交
6585
	restoreState(startingDockLayout);
6586

6587
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
J
jp9000 已提交
6588 6589 6590 6591 6592 6593 6594 6595 6596 6597
	int cx = width();
	int cy = height();

	int cx22_5 = cx * 225 / 1000;
	int cx5 = cx * 5 / 100;

	cy = cy * 225 / 1000;

	int mixerSize = cx - (cx22_5 * 2 + cx5 * 2);

J
jp9000 已提交
6598 6599 6600
	QList<QDockWidget *> docks{ui->scenesDock, ui->sourcesDock,
				   ui->mixerDock, ui->transitionsDock,
				   ui->controlsDock};
J
jp9000 已提交
6601

J
jp9000 已提交
6602
	QList<int> sizes{cx22_5, cx22_5, mixerSize, cx5, cx5};
J
jp9000 已提交
6603 6604 6605 6606 6607 6608

	ui->scenesDock->setVisible(true);
	ui->sourcesDock->setVisible(true);
	ui->mixerDock->setVisible(true);
	ui->transitionsDock->setVisible(true);
	ui->controlsDock->setVisible(true);
6609 6610
	statsDock->setVisible(false);
	statsDock->setFloating(true);
J
jp9000 已提交
6611 6612 6613

	resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical);
	resizeDocks(docks, sizes, Qt::Horizontal);
6614
#endif
J
jp9000 已提交
6615 6616 6617 6618
}

void OBSBasic::on_lockUI_toggled(bool lock)
{
J
jp9000 已提交
6619 6620 6621
	QDockWidget::DockWidgetFeatures features =
		lock ? QDockWidget::NoDockWidgetFeatures
		     : QDockWidget::AllDockWidgetFeatures;
J
jp9000 已提交
6622

6623 6624 6625 6626 6627 6628 6629 6630
	QDockWidget::DockWidgetFeatures mainFeatures = features;
	mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable;

	ui->scenesDock->setFeatures(mainFeatures);
	ui->sourcesDock->setFeatures(mainFeatures);
	ui->mixerDock->setFeatures(mainFeatures);
	ui->transitionsDock->setFeatures(mainFeatures);
	ui->controlsDock->setFeatures(mainFeatures);
6631
	statsDock->setFeatures(features);
6632

J
jp9000 已提交
6633
	for (int i = extraDocks.size() - 1; i >= 0; i--) {
6634 6635 6636 6637 6638 6639
		if (!extraDocks[i]) {
			extraDocks.removeAt(i);
		} else {
			extraDocks[i]->setFeatures(features);
		}
	}
6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654
}

void OBSBasic::on_toggleListboxToolbars_toggled(bool visible)
{
	ui->sourcesToolbar->setVisible(visible);
	ui->scenesToolbar->setVisible(visible);

	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"ShowListboxToolbars", visible);
}

void OBSBasic::on_toggleStatusBar_toggled(bool visible)
{
	ui->statusbar->setVisible(visible);

J
jp9000 已提交
6655 6656
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "ShowStatusBar",
			visible);
6657
}
J
jp9000 已提交
6658 6659 6660 6661 6662 6663

void OBSBasic::on_actionLockPreview_triggered()
{
	ui->preview->ToggleLocked();
	ui->actionLockPreview->setChecked(ui->preview->Locked());
}
C
cg2121 已提交
6664

J
Joseph El-Khouri 已提交
6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680
void OBSBasic::on_scalingMenu_aboutToShow()
{
	obs_video_info ovi;
	obs_get_video_info(&ovi);

	QAction *action = ui->actionScaleCanvas;
	QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas");
	text = text.arg(QString::number(ovi.base_width),
			QString::number(ovi.base_height));
	action->setText(text);

	action = ui->actionScaleOutput;
	text = QTStr("Basic.MainMenu.Edit.Scale.Output");
	text = text.arg(QString::number(ovi.output_width),
			QString::number(ovi.output_height));
	action->setText(text);
6681
	action->setVisible(!(ovi.output_width == ovi.base_width &&
J
jp9000 已提交
6682
			     ovi.output_height == ovi.base_height));
J
Joseph El-Khouri 已提交
6683 6684 6685 6686 6687 6688

	UpdatePreviewScalingMenu();
}

void OBSBasic::on_actionScaleWindow_triggered()
{
6689
	ui->preview->SetFixedScaling(false);
J
Joseph El-Khouri 已提交
6690 6691 6692 6693 6694 6695
	ui->preview->ResetScrollingOffset();
	emit ui->preview->DisplayResized();
}

void OBSBasic::on_actionScaleCanvas_triggered()
{
6696 6697
	ui->preview->SetFixedScaling(true);
	ui->preview->SetScalingLevel(0);
J
Joseph El-Khouri 已提交
6698 6699 6700 6701 6702
	emit ui->preview->DisplayResized();
}

void OBSBasic::on_actionScaleOutput_triggered()
{
6703 6704 6705 6706 6707 6708
	obs_video_info ovi;
	obs_get_video_info(&ovi);

	ui->preview->SetFixedScaling(true);
	float scalingAmount = float(ovi.output_width) / float(ovi.base_width);
	// log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY)
J
jp9000 已提交
6709 6710
	int32_t approxScalingLevel =
		int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY)));
6711 6712
	ui->preview->SetScalingLevel(approxScalingLevel);
	ui->preview->SetScalingAmount(scalingAmount);
J
Joseph El-Khouri 已提交
6713 6714 6715
	emit ui->preview->DisplayResized();
}

C
cg2121 已提交
6716 6717 6718
void OBSBasic::SetShowing(bool showing)
{
	if (!showing && isVisible()) {
J
jp9000 已提交
6719 6720 6721
		config_set_string(App()->GlobalConfig(), "BasicWindow",
				  "geometry",
				  saveGeometry().toBase64().constData());
C
cg2121 已提交
6722

6723 6724 6725 6726 6727 6728 6729 6730 6731
		/* hide all visible child dialogs */
		visDlgPositions.clear();
		if (!visDialogs.isEmpty()) {
			for (QDialog *dlg : visDialogs) {
				visDlgPositions.append(dlg->pos());
				dlg->hide();
			}
		}

6732 6733
		if (showHide)
			showHide->setText(QTStr("Basic.SystemTray.Show"));
C
cg2121 已提交
6734 6735 6736 6737 6738 6739 6740
		QTimer::singleShot(250, this, SLOT(hide()));

		if (previewEnabled)
			EnablePreviewDisplay(false);

		setVisible(false);

6741 6742 6743 6744
#ifdef __APPLE__
		EnableOSXDockIcon(false);
#endif

C
cg2121 已提交
6745
	} else if (showing && !isVisible()) {
6746 6747
		if (showHide)
			showHide->setText(QTStr("Basic.SystemTray.Hide"));
C
cg2121 已提交
6748 6749 6750 6751 6752 6753
		QTimer::singleShot(250, this, SLOT(show()));

		if (previewEnabled)
			EnablePreviewDisplay(true);

		setVisible(true);
6754

6755 6756 6757 6758 6759 6760 6761 6762
#ifdef __APPLE__
		EnableOSXDockIcon(true);
#endif

		/* raise and activate window to ensure it is on top */
		raise();
		activateWindow();

6763 6764 6765 6766 6767 6768 6769 6770 6771
		/* show all child dialogs that was visible earlier */
		if (!visDialogs.isEmpty()) {
			for (int i = 0; i < visDialogs.size(); ++i) {
				QDialog *dlg = visDialogs[i];
				dlg->move(visDlgPositions[i]);
				dlg->show();
			}
		}

6772 6773 6774 6775
		/* Unminimize window if it was hidden to tray instead of task
		 * bar. */
		if (sysTrayMinimizeToTray()) {
			Qt::WindowStates state;
J
jp9000 已提交
6776
			state = windowState() & ~Qt::WindowMinimized;
6777 6778 6779
			state |= Qt::WindowActive;
			setWindowState(state);
		}
C
cg2121 已提交
6780 6781 6782
	}
}

6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794
void OBSBasic::ToggleShowHide()
{
	bool showing = isVisible();
	if (showing) {
		/* check for modal dialogs */
		EnumDialogs();
		if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty())
			return;
	}
	SetShowing(!showing);
}

6795 6796
void OBSBasic::SystemTrayInit()
{
J
jp9000 已提交
6797 6798 6799
	trayIcon.reset(new QSystemTrayIcon(
		QIcon::fromTheme("obs-tray", QIcon(":/res/images/obs.png")),
		this));
C
cg2121 已提交
6800 6801
	trayIcon->setToolTip("OBS Studio");

J
jp9000 已提交
6802
	showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data());
C
cg2121 已提交
6803
	sysTrayStream = new QAction(QTStr("Basic.Main.StartStreaming"),
J
jp9000 已提交
6804
				    trayIcon.data());
C
cg2121 已提交
6805
	sysTrayRecord = new QAction(QTStr("Basic.Main.StartRecording"),
J
jp9000 已提交
6806
				    trayIcon.data());
J
jp9000 已提交
6807
	sysTrayReplayBuffer = new QAction(QTStr("Basic.Main.StartReplayBuffer"),
J
jp9000 已提交
6808 6809
					  trayIcon.data());
	exit = new QAction(QTStr("Exit"), trayIcon.data());
C
cg2121 已提交
6810

6811 6812 6813 6814
	trayMenu = new QMenu;
	previewProjector = new QMenu(QTStr("PreviewProjector"));
	studioProgramProjector = new QMenu(QTStr("StudioProgramProjector"));
	AddProjectorMenuMonitors(previewProjector, this,
J
jp9000 已提交
6815
				 SLOT(OpenPreviewProjector()));
6816
	AddProjectorMenuMonitors(studioProgramProjector, this,
J
jp9000 已提交
6817
				 SLOT(OpenStudioProgramProjector()));
6818 6819 6820 6821 6822 6823 6824 6825
	trayMenu->addAction(showHide);
	trayMenu->addMenu(previewProjector);
	trayMenu->addMenu(studioProgramProjector);
	trayMenu->addAction(sysTrayStream);
	trayMenu->addAction(sysTrayRecord);
	trayMenu->addAction(sysTrayReplayBuffer);
	trayMenu->addAction(exit);
	trayIcon->setContextMenu(trayMenu);
6826
	trayIcon->show();
6827

J
jp9000 已提交
6828 6829
	if (outputHandler && !outputHandler->replayBuffer)
		sysTrayReplayBuffer->setEnabled(false);
6830

6831
	connect(trayIcon.data(),
J
jp9000 已提交
6832 6833 6834 6835 6836 6837 6838 6839 6840 6841
		SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this,
		SLOT(IconActivated(QSystemTrayIcon::ActivationReason)));
	connect(showHide, SIGNAL(triggered()), this, SLOT(ToggleShowHide()));
	connect(sysTrayStream, SIGNAL(triggered()), this,
		SLOT(on_streamButton_clicked()));
	connect(sysTrayRecord, SIGNAL(triggered()), this,
		SLOT(on_recordButton_clicked()));
	connect(sysTrayReplayBuffer.data(), &QAction::triggered, this,
		&OBSBasic::ReplayBufferClicked);
	connect(exit, SIGNAL(triggered()), this, SLOT(close()));
C
cg2121 已提交
6842 6843 6844 6845
}

void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason)
{
6846 6847 6848 6849
	// Refresh projector list
	previewProjector->clear();
	studioProgramProjector->clear();
	AddProjectorMenuMonitors(previewProjector, this,
J
jp9000 已提交
6850
				 SLOT(OpenPreviewProjector()));
6851
	AddProjectorMenuMonitors(studioProgramProjector, this,
J
jp9000 已提交
6852
				 SLOT(OpenStudioProgramProjector()));
6853 6854

	if (reason == QSystemTrayIcon::Trigger)
C
cg2121 已提交
6855 6856 6857 6858
		ToggleShowHide();
}

void OBSBasic::SysTrayNotify(const QString &text,
J
jp9000 已提交
6859
			     QSystemTrayIcon::MessageIcon n)
C
cg2121 已提交
6860
{
6861
	if (trayIcon && QSystemTrayIcon::supportsMessages()) {
C
cg2121 已提交
6862
		QSystemTrayIcon::MessageIcon icon =
J
jp9000 已提交
6863
			QSystemTrayIcon::MessageIcon(n);
C
cg2121 已提交
6864 6865 6866 6867 6868 6869 6870 6871
		trayIcon->showMessage("OBS Studio", text, icon, 10000);
	}
}

void OBSBasic::SystemTray(bool firstStarted)
{
	if (!QSystemTrayIcon::isSystemTrayAvailable())
		return;
6872
	if (!trayIcon && !firstStarted)
J
jp9000 已提交
6873
		return;
C
cg2121 已提交
6874

J
jp9000 已提交
6875 6876 6877 6878
	bool sysTrayWhenStarted = config_get_bool(
		GetGlobalConfig(), "BasicWindow", "SysTrayWhenStarted");
	bool sysTrayEnabled = config_get_bool(GetGlobalConfig(), "BasicWindow",
					      "SysTrayEnabled");
C
cg2121 已提交
6879 6880 6881 6882 6883 6884

	if (firstStarted)
		SystemTrayInit();

	if (!sysTrayWhenStarted && !sysTrayEnabled) {
		trayIcon->hide();
J
jp9000 已提交
6885 6886
	} else if ((sysTrayWhenStarted && sysTrayEnabled) ||
		   opt_minimize_tray) {
C
cg2121 已提交
6887 6888 6889 6890 6891
		trayIcon->show();
		if (firstStarted) {
			QTimer::singleShot(50, this, SLOT(hide()));
			EnablePreviewDisplay(false);
			setVisible(false);
6892 6893 6894
#ifdef __APPLE__
			EnableOSXDockIcon(false);
#endif
C
cg2121 已提交
6895
			opt_minimize_tray = false;
C
cg2121 已提交
6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909
		}
	} else if (sysTrayEnabled) {
		trayIcon->show();
	} else if (!sysTrayEnabled) {
		trayIcon->hide();
	} else if (!sysTrayWhenStarted && sysTrayEnabled) {
		trayIcon->hide();
	}

	if (isVisible())
		showHide->setText(QTStr("Basic.SystemTray.Hide"));
	else
		showHide->setText(QTStr("Basic.SystemTray.Show"));
}
6910 6911 6912

bool OBSBasic::sysTrayMinimizeToTray()
{
J
jp9000 已提交
6913 6914
	return config_get_bool(GetGlobalConfig(), "BasicWindow",
			       "SysTrayMinimizeToTray");
6915
}
6916 6917 6918 6919 6920 6921

void OBSBasic::on_actionCopySource_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;
J
jp9000 已提交
6922 6923

	on_actionCopyTransform_triggered();
6924 6925 6926 6927 6928 6929 6930

	OBSSource source = obs_sceneitem_get_source(item);

	copyString = obs_source_get_name(source);
	copyVisible = obs_sceneitem_visible(item);

	ui->actionPasteRef->setEnabled(true);
6931 6932 6933 6934 6935 6936

	uint32_t output_flags = obs_source_get_output_flags(source);
	if ((output_flags & OBS_SOURCE_DO_NOT_DUPLICATE) == 0)
		ui->actionPasteDup->setEnabled(true);
	else
		ui->actionPasteDup->setEnabled(false);
6937 6938 6939 6940
}

void OBSBasic::on_actionPasteRef_triggered()
{
J
jp9000 已提交
6941 6942 6943 6944 6945
	/* do not allow duplicate refs of the same group in the same scene */
	OBSScene scene = GetCurrentScene();
	if (!!obs_scene_get_group(scene, copyString))
		return;

6946 6947 6948 6949 6950 6951 6952 6953 6954 6955
	OBSBasicSourceSelect::SourcePaste(copyString, copyVisible, false);
	on_actionPasteTransform_triggered();
}

void OBSBasic::on_actionPasteDup_triggered()
{
	OBSBasicSourceSelect::SourcePaste(copyString, copyVisible, true);
	on_actionPasteTransform_triggered();
}

6956 6957
void OBSBasic::AudioMixerCopyFilters()
{
J
jp9000 已提交
6958 6959
	QAction *action = reinterpret_cast<QAction *>(sender());
	VolControl *vol = action->property("volControl").value<VolControl *>();
6960 6961 6962 6963 6964 6965 6966
	obs_source_t *source = vol->GetSource();

	copyFiltersString = obs_source_get_name(source);
}

void OBSBasic::AudioMixerPasteFilters()
{
J
jp9000 已提交
6967 6968
	QAction *action = reinterpret_cast<QAction *>(sender());
	VolControl *vol = action->property("volControl").value<VolControl *>();
6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979
	obs_source_t *dstSource = vol->GetSource();

	OBSSource source = obs_get_source_by_name(copyFiltersString);
	obs_source_release(source);

	if (source == dstSource)
		return;

	obs_source_copy_filters(dstSource, source);
}

6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997
void OBSBasic::SceneCopyFilters()
{
	copyFiltersString = obs_source_get_name(GetCurrentSceneSource());
}

void OBSBasic::ScenePasteFilters()
{
	OBSSource source = obs_get_source_by_name(copyFiltersString);
	obs_source_release(source);

	OBSSource dstSource = GetCurrentSceneSource();

	if (source == dstSource)
		return;

	obs_source_copy_filters(dstSource, source);
}

6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014
void OBSBasic::on_actionCopyFilters_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();

	if (!item)
		return;

	OBSSource source = obs_sceneitem_get_source(item);

	copyFiltersString = obs_source_get_name(source);

	ui->actionPasteFilters->setEnabled(true);
}

void OBSBasic::on_actionPasteFilters_triggered()
{
	OBSSource source = obs_get_source_by_name(copyFiltersString);
7015
	obs_source_release(source);
7016

7017
	OBSSceneItem sceneItem = GetCurrentSceneItem();
7018 7019 7020 7021 7022 7023 7024
	OBSSource dstSource = obs_sceneitem_get_source(sceneItem);

	if (source == dstSource)
		return;

	obs_source_copy_filters(dstSource, source);
}
J
jp9000 已提交
7025

7026
static void ConfirmColor(SourceTree *sources, const QColor &color,
J
jp9000 已提交
7027
			 QModelIndexList selectedItems)
7028 7029
{
	for (int x = 0; x < selectedItems.count(); x++) {
J
jp9000 已提交
7030 7031 7032 7033
		SourceTreeItem *treeItem =
			sources->GetItemWidget(selectedItems[x].row());
		treeItem->setStyleSheet("background: " +
					color.name(QColor::HexArgb));
7034 7035 7036
		treeItem->style()->unpolish(treeItem);
		treeItem->style()->polish(treeItem);

J
jp9000 已提交
7037
		OBSSceneItem sceneItem = sources->Get(selectedItems[x].row());
7038 7039 7040 7041
		obs_data_t *privData =
			obs_sceneitem_get_private_settings(sceneItem);
		obs_data_set_int(privData, "color-preset", 1);
		obs_data_set_string(privData, "color",
J
jp9000 已提交
7042
				    QT_TO_UTF8(color.name(QColor::HexArgb)));
7043 7044 7045 7046 7047 7048 7049 7050
		obs_data_release(privData);
	}
}

void OBSBasic::ColorChange()
{
	QModelIndexList selectedItems =
		ui->sources->selectionModel()->selectedIndexes();
J
jp9000 已提交
7051 7052
	QAction *action = qobject_cast<QAction *>(sender());
	QPushButton *colorButton = qobject_cast<QPushButton *>(sender());
7053 7054 7055 7056 7057 7058 7059 7060

	if (selectedItems.count() == 0)
		return;

	if (colorButton) {
		int preset = colorButton->property("bgColor").value<int>();

		for (int x = 0; x < selectedItems.count(); x++) {
J
jp9000 已提交
7061 7062
			SourceTreeItem *treeItem = ui->sources->GetItemWidget(
				selectedItems[x].row());
7063 7064 7065 7066 7067
			treeItem->setStyleSheet("");
			treeItem->setProperty("bgColor", preset);
			treeItem->style()->unpolish(treeItem);
			treeItem->style()->polish(treeItem);

J
jp9000 已提交
7068 7069
			OBSSceneItem sceneItem =
				ui->sources->Get(selectedItems[x].row());
7070 7071 7072 7073 7074 7075 7076 7077 7078 7079
			obs_data_t *privData =
				obs_sceneitem_get_private_settings(sceneItem);
			obs_data_set_int(privData, "color-preset", preset + 1);
			obs_data_set_string(privData, "color", "");
			obs_data_release(privData);
		}

		for (int i = 1; i < 9; i++) {
			stringstream button;
			button << "preset" << i;
J
jp9000 已提交
7080 7081 7082 7083
			QPushButton *cButton =
				colorButton->parentWidget()
					->findChild<QPushButton *>(
						button.str().c_str());
7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095
			cButton->setStyleSheet("border: 1px solid black");
		}

		colorButton->setStyleSheet("border: 2px solid black");
	} else if (action) {
		int preset = action->property("bgColor").value<int>();

		if (preset == 1) {
			OBSSceneItem curSceneItem = GetCurrentSceneItem();
			SourceTreeItem *curTreeItem =
				GetItemWidgetFromSceneItem(curSceneItem);
			obs_data_t *curPrivData =
J
jp9000 已提交
7096 7097
				obs_sceneitem_get_private_settings(
					curSceneItem);
7098

J
jp9000 已提交
7099 7100
			int oldPreset =
				obs_data_get_int(curPrivData, "color-preset");
7101 7102 7103 7104 7105
			const QString oldSheet = curTreeItem->styleSheet();

			auto liveChangeColor = [=](const QColor &color) {
				if (color.isValid()) {
					curTreeItem->setStyleSheet(
J
jp9000 已提交
7106 7107
						"background: " +
						color.name(QColor::HexArgb));
7108 7109 7110 7111 7112 7113
				}
			};

			auto changedColor = [=](const QColor &color) {
				if (color.isValid()) {
					ConfirmColor(ui->sources, color,
J
jp9000 已提交
7114
						     selectedItems);
7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128
				}
			};

			auto rejected = [=]() {
				if (oldPreset == 1) {
					curTreeItem->setStyleSheet(oldSheet);
					curTreeItem->setProperty("bgColor", 0);
				} else if (oldPreset == 0) {
					curTreeItem->setStyleSheet(
						"background: none");
					curTreeItem->setProperty("bgColor", 0);
				} else {
					curTreeItem->setStyleSheet("");
					curTreeItem->setProperty("bgColor",
J
jp9000 已提交
7129
								 oldPreset - 1);
7130 7131 7132 7133 7134 7135 7136 7137 7138
				}

				curTreeItem->style()->unpolish(curTreeItem);
				curTreeItem->style()->polish(curTreeItem);
			};

			QColorDialog::ColorDialogOptions options =
				QColorDialog::ShowAlphaChannel;

J
jp9000 已提交
7139 7140
			const char *oldColor =
				obs_data_get_string(curPrivData, "color");
7141
			const char *customColor = *oldColor != 0 ? oldColor
J
jp9000 已提交
7142
								 : "#55FF0000";
7143 7144 7145 7146 7147 7148
#ifdef __APPLE__
			options |= QColorDialog::DontUseNativeDialog;
#endif

			QColorDialog *colorDialog = new QColorDialog(this);
			colorDialog->setOptions(options);
J
jp9000 已提交
7149
			colorDialog->setCurrentColor(QColor(customColor));
7150
			connect(colorDialog, &QColorDialog::currentColorChanged,
J
jp9000 已提交
7151
				liveChangeColor);
7152
			connect(colorDialog, &QColorDialog::colorSelected,
J
jp9000 已提交
7153 7154
				changedColor);
			connect(colorDialog, &QColorDialog::rejected, rejected);
7155 7156 7157 7158 7159
			colorDialog->open();

			obs_data_release(curPrivData);
		} else {
			for (int x = 0; x < selectedItems.count(); x++) {
J
jp9000 已提交
7160 7161 7162
				SourceTreeItem *treeItem =
					ui->sources->GetItemWidget(
						selectedItems[x].row());
7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173
				treeItem->setStyleSheet("background: none");
				treeItem->setProperty("bgColor", preset);
				treeItem->style()->unpolish(treeItem);
				treeItem->style()->polish(treeItem);

				OBSSceneItem sceneItem = ui->sources->Get(
					selectedItems[x].row());
				obs_data_t *privData =
					obs_sceneitem_get_private_settings(
						sceneItem);
				obs_data_set_int(privData, "color-preset",
J
jp9000 已提交
7174
						 preset);
7175 7176 7177 7178 7179 7180 7181
				obs_data_set_string(privData, "color", "");
				obs_data_release(privData);
			}
		}
	}
}

J
jp9000 已提交
7182
SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem)
7183 7184 7185 7186 7187 7188 7189 7190 7191 7192
{
	int i = 0;
	SourceTreeItem *treeItem = ui->sources->GetItemWidget(i);
	OBSSceneItem item = ui->sources->Get(i);
	int64_t id = obs_sceneitem_get_id(sceneItem);
	while (treeItem && obs_sceneitem_get_id(item) != id) {
		i++;
		treeItem = ui->sources->GetItemWidget(i);
		item = ui->sources->Get(i);
	}
J
jp9000 已提交
7193
	if (treeItem)
7194 7195 7196 7197 7198
		return treeItem;

	return nullptr;
}

J
jp9000 已提交
7199 7200 7201 7202 7203 7204 7205
void OBSBasic::on_autoConfigure_triggered()
{
	AutoConfig test(this);
	test.setModal(true);
	test.show();
	test.exec();
}
J
jp9000 已提交
7206 7207 7208

void OBSBasic::on_stats_triggered()
{
7209 7210 7211 7212 7213 7214
	if (!stats.isNull()) {
		stats->show();
		stats->raise();
		return;
	}

J
jp9000 已提交
7215 7216 7217 7218 7219
	OBSBasicStats *statsDlg;
	statsDlg = new OBSBasicStats(nullptr);
	statsDlg->show();
	stats = statsDlg;
}
7220

C
cg2121 已提交
7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231
void OBSBasic::on_actionShowAbout_triggered()
{
	if (about)
		about->close();

	about = new OBSAbout(this);
	about->show();

	about->setAttribute(Qt::WA_DeleteOnClose, true);
}

7232 7233 7234
void OBSBasic::ResizeOutputSizeOfSource()
{
	if (ui->streamButton->isChecked() || ui->recordButton->isChecked() ||
J
jp9000 已提交
7235
	    (replayBufferButton && replayBufferButton->isChecked()))
7236 7237 7238
		return;

	QMessageBox resize_output(this);
J
jp9000 已提交
7239 7240 7241 7242
	resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" +
			      QTStr("ResizeOutputSizeOfSource.Continue"));
	QAbstractButton *Yes =
		resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole);
7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263
	resize_output.addButton(QTStr("No"), QMessageBox::NoRole);
	resize_output.setIcon(QMessageBox::Warning);
	resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource"));
	resize_output.exec();

	if (resize_output.clickedButton() != Yes)
		return;

	OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem());

	int width = obs_source_get_width(source);
	int height = obs_source_get_height(source);

	config_set_uint(basicConfig, "Video", "BaseCX", width);
	config_set_uint(basicConfig, "Video", "BaseCY", height);
	config_set_uint(basicConfig, "Video", "OutputCX", width);
	config_set_uint(basicConfig, "Video", "OutputCY", height);

	ResetVideo();
	on_actionFitToScreen_triggered();
}
7264

7265 7266 7267 7268 7269 7270 7271 7272
QAction *OBSBasic::AddDockWidget(QDockWidget *dock)
{
	QAction *action = ui->viewMenuDocks->addAction(dock->windowTitle());
	action->setCheckable(true);
	assignDockToggle(dock, action);
	extraDocks.push_back(dock);

	bool lock = ui->lockUI->isChecked();
J
jp9000 已提交
7273 7274 7275
	QDockWidget::DockWidgetFeatures features =
		lock ? QDockWidget::NoDockWidgetFeatures
		     : QDockWidget::AllDockWidgetFeatures;
7276 7277 7278 7279

	dock->setFeatures(features);

	/* prune deleted docks */
J
jp9000 已提交
7280
	for (int i = extraDocks.size() - 1; i >= 0; i--) {
7281 7282 7283 7284 7285 7286 7287 7288
		if (!extraDocks[i]) {
			extraDocks.removeAt(i);
		}
	}

	return action;
}

7289 7290
OBSBasic *OBSBasic::Get()
{
J
jp9000 已提交
7291
	return reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
7292
}
7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313

bool OBSBasic::StreamingActive()
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

bool OBSBasic::RecordingActive()
{
	if (!outputHandler)
		return false;
	return outputHandler->RecordingActive();
}

bool OBSBasic::ReplayBufferActive()
{
	if (!outputHandler)
		return false;
	return outputHandler->ReplayBufferActive();
}
7314 7315 7316 7317 7318 7319 7320

SceneRenameDelegate::SceneRenameDelegate(QObject *parent)
	: QStyledItemDelegate(parent)
{
}

void SceneRenameDelegate::setEditorData(QWidget *editor,
J
jp9000 已提交
7321
					const QModelIndex &index) const
7322 7323
{
	QStyledItemDelegate::setEditorData(editor, index);
J
jp9000 已提交
7324
	QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
7325 7326 7327
	if (lineEdit)
		lineEdit->selectAll();
}
7328 7329 7330 7331 7332 7333

bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event)
{
	if (event->type() == QEvent::KeyPress) {
		QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
		if (keyEvent->key() == Qt::Key_Escape) {
J
jp9000 已提交
7334
			QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
7335 7336 7337 7338 7339 7340 7341
			if (lineEdit)
				lineEdit->undo();
		}
	}

	return QStyledItemDelegate::eventFilter(editor, event);
}
7342 7343 7344 7345 7346 7347 7348 7349

void OBSBasic::UpdatePatronJson(const QString &text, const QString &error)
{
	if (!error.isEmpty())
		return;

	patronJson = QT_TO_UTF8(text);
}
J
jp9000 已提交
7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452

void OBSBasic::PauseRecording()
{
	if (!pause || !outputHandler || !outputHandler->fileOutput)
		return;

	obs_output_t *output = outputHandler->fileOutput;

	if (obs_output_pause(output, true)) {
		pause->setChecked(true);
		os_atomic_set_bool(&recording_paused, true);

		if (api)
			api->on_event(OBS_FRONTEND_EVENT_RECORDING_PAUSED);

		if (os_atomic_load_bool(&replaybuf_active))
			ShowReplayBufferPauseWarning();
	}
}

void OBSBasic::UnpauseRecording()
{
	if (!pause || !outputHandler || !outputHandler->fileOutput)
		return;

	obs_output_t *output = outputHandler->fileOutput;

	if (obs_output_pause(output, false)) {
		pause->setChecked(false);
		os_atomic_set_bool(&recording_paused, false);

		if (api)
			api->on_event(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED);
	}
}

void OBSBasic::PauseToggled()
{
	if (!pause || !outputHandler || !outputHandler->fileOutput)
		return;

	obs_output_t *output = outputHandler->fileOutput;
	bool enable = !obs_output_paused(output);

	if (obs_output_pause(output, enable)) {
		os_atomic_set_bool(&recording_paused, enable);

		if (api)
			api->on_event(
				enable ? OBS_FRONTEND_EVENT_RECORDING_PAUSED
				       : OBS_FRONTEND_EVENT_RECORDING_UNPAUSED);

		if (enable && os_atomic_load_bool(&replaybuf_active))
			ShowReplayBufferPauseWarning();
	} else {
		pause->setChecked(!enable);
	}
}

void OBSBasic::UpdatePause(bool activate)
{
	if (!activate || !outputHandler || !outputHandler->RecordingActive()) {
		pause.reset();
		return;
	}

	const char *mode = config_get_string(basicConfig, "Output", "Mode");
	bool adv = astrcmpi(mode, "Advanced") == 0;
	bool shared;

	if (adv) {
		const char *recType =
			config_get_string(basicConfig, "AdvOut", "RecType");

		if (astrcmpi(recType, "FFmpeg") == 0) {
			shared = config_get_bool(basicConfig, "AdvOut",
						 "FFOutputToFile");
		} else {
			const char *recordEncoder = config_get_string(
				basicConfig, "AdvOut", "RecEncoder");
			shared = astrcmpi(recordEncoder, "none") == 0;
		}
	} else {
		const char *quality = config_get_string(
			basicConfig, "SimpleOutput", "RecQuality");
		shared = strcmp(quality, "Stream") == 0;
	}

	if (!shared) {
		pause.reset(new QPushButton());
		pause->setAccessibleName(QTStr("Basic.Main.PauseRecording"));
		pause->setToolTip(QTStr("Basic.Main.PauseRecording"));
		pause->setCheckable(true);
		pause->setChecked(false);
		pause->setProperty("themeID",
				   QVariant(QStringLiteral("pauseIconSmall")));
		connect(pause.data(), &QAbstractButton::clicked, this,
			&OBSBasic::PauseToggled);
		ui->recordingLayout->addWidget(pause.data());
	} else {
		pause.reset();
	}
}