window-basic-main.cpp 165.0 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/>.
******************************************************************************/

J
jp9000 已提交
20
#include <time.h>
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 29
#include <QRect>
#include <QScreen>
30

J
jp9000 已提交
31
#include <util/dstr.h>
32 33
#include <util/util.hpp>
#include <util/platform.h>
P
Palana 已提交
34
#include <util/profiler.hpp>
35
#include <util/dstr.hpp>
J
jp9000 已提交
36
#include <graphics/math-defs.h>
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"
49
#include "window-basic-properties.hpp"
J
jp9000 已提交
50
#include "window-log-reply.hpp"
J
jp9000 已提交
51
#include "window-projector.hpp"
P
Palana 已提交
52
#include "window-remux.hpp"
J
jp9000 已提交
53
#include "qt-wrappers.hpp"
54
#include "display-helpers.hpp"
55
#include "volume-control.hpp"
56
#include "remote-text.hpp"
J
jp9000 已提交
57
#include "source-tree.hpp"
58

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

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

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

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

J
jp9000 已提交
71 72 73 74 75 76 77
#ifdef _WIN32
#include <browser-panel.hpp>
#endif

#include <json11.hpp>

using namespace json11;
78
using namespace std;
J
jp9000 已提交
79

J
jp9000 已提交
80 81 82 83
#ifdef _WIN32
static CREATE_BROWSER_WIDGET_PROC create_browser_widget = nullptr;
#endif

J
jp9000 已提交
84 85 86 87 88 89 90 91 92 93
namespace {

template <typename OBSRef>
struct SignalContainer {
	OBSRef ref;
	vector<shared_ptr<OBSSignal>> handlers;
};

}

J
jp9000 已提交
94 95
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);
P
Palana 已提交
96
Q_DECLARE_METATYPE(OBSSource);
J
jp9000 已提交
97
Q_DECLARE_METATYPE(obs_order_movement);
J
jp9000 已提交
98
Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
J
jp9000 已提交
99

P
Palana 已提交
100 101 102 103 104 105 106 107 108 109 110 111 112
template <typename T>
static T GetOBSRef(QListWidgetItem *item)
{
	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
}

template <typename T>
static void SetOBSRef(QListWidgetItem *item, T &&val)
{
	item->setData(static_cast<int>(QtDataRole::OBSRef),
			QVariant::fromValue(val));
}

113 114
static void AddExtraModulePaths()
{
115
	char base_module_dir[512];
116 117 118 119
#if defined(_WIN32) || defined(__APPLE__)
	int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir),
			"obs-studio/plugins/%module%");
#else
120
	int ret = GetConfigPath(base_module_dir, sizeof(base_module_dir),
121
			"obs-studio/plugins/%module%");
122
#endif
B
BtbN 已提交
123

124
	if (ret <= 0)
125 126 127
		return;

	string path = (char*)base_module_dir;
128
#if defined(__APPLE__)
129
	obs_add_module_path((path + "/bin").c_str(), (path + "/data").c_str());
130 131 132 133 134

	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");
	obs_add_module_path(config_bin, config_data);

135 136 137 138 139 140 141
#elif ARCH_BITS == 64
	obs_add_module_path((path + "/bin/64bit").c_str(),
			(path + "/data").c_str());
#else
	obs_add_module_path((path + "/bin/32bit").c_str(),
			(path + "/data").c_str());
#endif
142 143
}

144 145
extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main);

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
static int CountVideoSources()
{
	int count = 0;

	auto countSources = [] (void *param, obs_source_t *source)
	{
		if (!source)
			return true;

		uint32_t flags = obs_source_get_output_flags(source);
		if ((flags & OBS_SOURCE_VIDEO) != 0)
			(*reinterpret_cast<int*>(param))++;

		return true;
	};

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

166
OBSBasic::OBSBasic(QWidget *parent)
167
	: OBSMainWindow  (parent),
J
jp9000 已提交
168
	  ui             (new Ui::OBSBasic)
169
{
170 171
	setAttribute(Qt::WA_NativeWindow);

J
jp9000 已提交
172 173
	setAcceptDrops(true);

174 175
	api = InitializeAPIInterface(this);

176
	ui->setupUi(this);
J
jp9000 已提交
177 178
	ui->previewDisabledLabel->setVisible(false);

J
jp9000 已提交
179 180
	startingDockLayout = saveState();

S
Socapex 已提交
181
	copyActionsDynamicProperties();
182

183
	char styleSheetPath[512];
J
jp9000 已提交
184 185
	int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath),
			"stylesheet.qss");
186
	if (ret > 0) {
H
HomeWorld 已提交
187 188 189 190 191
		if (QFile::exists(styleSheetPath)) {
			QString path = QString("file:///") +
				QT_UTF8(styleSheetPath);
			App()->setStyleSheet(path);
		}
192 193
	}

P
Palana 已提交
194 195 196
	qRegisterMetaType<OBSScene>    ("OBSScene");
	qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
	qRegisterMetaType<OBSSource>   ("OBSSource");
P
Palana 已提交
197
	qRegisterMetaType<obs_hotkey_id>("obs_hotkey_id");
P
Palana 已提交
198

199 200 201
	qRegisterMetaTypeStreamOperators<
		std::vector<std::shared_ptr<OBSSignal>>>(
				"std::vector<std::shared_ptr<OBSSignal>>");
202
	qRegisterMetaTypeStreamOperators<OBSScene>("OBSScene");
203
	qRegisterMetaTypeStreamOperators<OBSSceneItem>("OBSSceneItem");
204

P
Palana 已提交
205 206 207
	ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false);
	ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false);

208
	auto displayResize = [this]() {
209 210 211 212
		struct obs_video_info ovi;

		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
213 214 215 216
	};

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

P
Palana 已提交
218 219
	installEventFilter(CreateShortcutFilter());

220
	stringstream name;
J
jp9000 已提交
221
	name << "OBS " << App()->GetVersionString();
222 223 224
	blog(LOG_INFO, "%s", name.str().c_str());
	blog(LOG_INFO, "---------------------------------");

225
	UpdateTitleBar();
J
jp9000 已提交
226 227 228 229 230 231 232 233

	connect(ui->scenes->itemDelegate(),
			SIGNAL(closeEditor(QWidget*,
					QAbstractItemDelegate::EndEditHint)),
			this,
			SLOT(SceneNameEdited(QWidget*,
					QAbstractItemDelegate::EndEditHint)));

234
	cpuUsageInfo = os_cpu_usage_info_start();
J
jp9000 已提交
235
	cpuUsageTimer = new QTimer(this);
236
	connect(cpuUsageTimer.data(), SIGNAL(timeout()),
J
jp9000 已提交
237 238
			ui->statusbar, SLOT(UpdateCPUUsage()));
	cpuUsageTimer->start(3000);
239 240

#ifdef __APPLE__
241 242
	ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace});
	ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace});
243 244 245

	ui->action_Settings->setMenuRole(QAction::PreferencesRole);
	ui->actionE_xit->setMenuRole(QAction::QuitRole);
246
#endif
247 248 249 250 251 252 253 254 255 256 257 258 259 260

	auto addNudge = [this](const QKeySequence &seq, const char *s)
	{
		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 已提交
261

262
	auto assignDockToggle = [] (QDockWidget *dock, QAction *action)
J
jp9000 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
	{
		auto handleWindowToggle = [action] (bool vis)
		{
			action->blockSignals(true);
			action->setChecked(vis);
			action->blockSignals(false);
		};
		auto handleMenuToggle = [dock] (bool check)
		{
			dock->blockSignals(true);
			dock->setVisible(check);
			dock->blockSignals(false);
		};

		dock->connect(dock->toggleViewAction(), &QAction::toggled,
				handleWindowToggle);
		dock->connect(action, &QAction::toggled,
				handleMenuToggle);
	};

	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);
S
SuslikV 已提交
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

	//hide all docking panes
	ui->toggleScenes->setChecked(false);
	ui->toggleSources->setChecked(false);
	ui->toggleMixer->setChecked(false);
	ui->toggleTransitions->setChecked(false);
	ui->toggleControls->setChecked(false);

	//restore parent window geometry
	const char *geometry = config_get_string(App()->GlobalConfig(),
			"BasicWindow", "geometry");
	if (geometry != NULL) {
		QByteArray byteArray = QByteArray::fromBase64(
				QByteArray(geometry));
		restoreGeometry(byteArray);

		QRect windowGeometry = normalGeometry();
		if (!WindowPositionValid(windowGeometry)) {
			QRect rect = App()->desktop()->geometry();
			setGeometry(QStyle::alignedRect(
						Qt::LeftToRight,
						Qt::AlignCenter,
						size(), rect));
		}
	}
313 314
}

315 316
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
		vector<OBSSource> &audioSources)
317
{
318
	obs_source_t *source = obs_get_output_source(channel);
319 320 321
	if (!source)
		return;

322 323
	audioSources.push_back(source);

324
	obs_data_t *data = obs_save_source(source);
325

J
jp9000 已提交
326
	obs_data_set_obj(parent, name, data);
327 328 329 330 331

	obs_data_release(data);
	obs_source_release(source);
}

332 333
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
		obs_data_array_t *quickTransitionData, int transitionDuration,
J
jp9000 已提交
334
		obs_data_array_t *transitions,
C
cg2121 已提交
335
		OBSScene &scene, OBSSource &curProgramScene,
336
		obs_data_array_t *savedProjectorList)
337
{
338 339 340 341 342 343 344 345 346 347 348
	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);
	SaveAudioDevice(AUX_AUDIO_1,     3, saveData, audioSources);
	SaveAudioDevice(AUX_AUDIO_2,     4, saveData, audioSources);
	SaveAudioDevice(AUX_AUDIO_3,     5, saveData, audioSources);

349 350 351
	/* -------------------------------- */
	/* save non-group sources           */

352 353
	auto FilterAudioSources = [&](obs_source_t *source)
	{
354 355 356
		if (obs_source_is_group(source))
			return false;

357 358 359 360 361 362 363 364 365 366 367
		return find(begin(audioSources), end(audioSources), source) ==
				end(audioSources);
	};
	using FilterAudioSources_t = decltype(FilterAudioSources);

	obs_data_array_t *sourcesArray = obs_save_sources_filtered(
			[](void *data, obs_source_t *source)
	{
		return (*static_cast<FilterAudioSources_t*>(data))(source);
	}, static_cast<void*>(&FilterAudioSources));

368 369 370 371 372 373 374 375 376 377 378 379
	/* -------------------------------- */
	/* save group sources separately    */

	/* saving separately ensures they won't be loaded in older versions */
	obs_data_array_t *groupsArray = obs_save_sources_filtered(
			[](void*, obs_source_t *source)
	{
		return obs_source_is_group(source);
	}, nullptr);

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

380 381
	obs_source_t *transition = obs_get_output_source(0);
	obs_source_t *currentScene = obs_scene_get_source(scene);
382
	const char   *sceneName   = obs_source_get_name(currentScene);
383
	const char   *programName = obs_source_get_name(curProgramScene);
384

J
jp9000 已提交
385 386 387
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

J
jp9000 已提交
388
	obs_data_set_string(saveData, "current_scene", sceneName);
389
	obs_data_set_string(saveData, "current_program_scene", programName);
J
jp9000 已提交
390
	obs_data_set_array(saveData, "scene_order", sceneOrder);
J
jp9000 已提交
391
	obs_data_set_string(saveData, "name", sceneCollection);
J
jp9000 已提交
392
	obs_data_set_array(saveData, "sources", sourcesArray);
393
	obs_data_set_array(saveData, "groups", groupsArray);
394
	obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
J
jp9000 已提交
395
	obs_data_set_array(saveData, "transitions", transitions);
C
cg2121 已提交
396
	obs_data_set_array(saveData, "saved_projectors", savedProjectorList);
397
	obs_data_array_release(sourcesArray);
398
	obs_data_array_release(groupsArray);
399 400 401 402 403

	obs_data_set_string(saveData, "current_transition",
			obs_source_get_name(transition));
	obs_data_set_int(saveData, "transition_duration", transitionDuration);
	obs_source_release(transition);
404 405 406 407

	return saveData;
}

S
Socapex 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
void OBSBasic::copyActionsDynamicProperties()
{
	// Themes need the QAction dynamic properties
	for (QAction *x : ui->scenesToolbar->actions()) {
		QWidget* temp = ui->scenesToolbar->widgetForAction(x);

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

	for (QAction *x : ui->sourcesToolbar->actions()) {
		QWidget* temp = ui->sourcesToolbar->widgetForAction(x);

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

S
Shaolin 已提交
428 429 430 431 432 433 434 435 436 437
void OBSBasic::UpdateVolumeControlsDecayRate()
{
	double meterDecayRate = config_get_double(basicConfig, "Audio",
			"MeterDecayRate");

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

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
void OBSBasic::UpdateVolumeControlsPeakMeterType()
{
	uint32_t peakMeterTypeIdx = config_get_uint(basicConfig, "Audio",
			"PeakMeterType");

	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);
	}
}

461 462
void OBSBasic::ClearVolumeControls()
{
C
craftwar 已提交
463 464
	for (VolControl *vol : volumes)
		delete vol;
465 466 467 468

	volumes.clear();
}

J
jp9000 已提交
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
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",
				QT_TO_UTF8(ui->scenes->item(i)->text()));
		obs_data_array_push_back(sceneOrder, data);
		obs_data_release(data);
	}

	return sceneOrder;
}

C
cg2121 已提交
484 485
obs_data_array_t *OBSBasic::SaveProjectors()
{
486
	obs_data_array_t *savedProjectors = obs_data_array_create();
C
cg2121 已提交
487

488 489 490
	auto saveProjector = [savedProjectors](OBSProjector *projector) {
		if (!projector)
			return;
R
Ryan Foster 已提交
491 492

		obs_data_t *data = obs_data_create();
493
		ProjectorType type = projector->GetProjectorType();
S
Shaolin 已提交
494 495 496 497 498 499 500 501 502 503 504
		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;
		}
505 506
		obs_data_set_int(data, "monitor", projector->GetMonitor());
		obs_data_set_int(data, "type", static_cast<int>(type));
S
Shaolin 已提交
507 508 509
		obs_data_set_string(data, "geometry",
				projector->saveGeometry().toBase64()
						.constData());
510
		obs_data_array_push_back(savedProjectors, data);
R
Ryan Foster 已提交
511
		obs_data_release(data);
512
	};
R
Ryan Foster 已提交
513

514 515
	for (QPointer<QWidget> &proj : projectors)
		saveProjector(static_cast<OBSProjector *>(proj.data()));
S
Shaolin 已提交
516

S
Shaolin 已提交
517 518
	for (QPointer<QWidget> &proj : windowProjectors)
		saveProjector(static_cast<OBSProjector *>(proj.data()));
S
Shaolin 已提交
519

520
	return savedProjectors;
S
Shaolin 已提交
521 522
}

523 524
void OBSBasic::Save(const char *file)
{
525 526 527 528 529
	OBSScene scene = GetCurrentScene();
	OBSSource curProgramScene = OBSGetStrongRef(programScene);
	if (!curProgramScene)
		curProgramScene = obs_scene_get_source(scene);

J
jp9000 已提交
530
	obs_data_array_t *sceneOrder = SaveSceneListOrder();
J
jp9000 已提交
531
	obs_data_array_t *transitions = SaveTransitions();
532
	obs_data_array_t *quickTrData = SaveQuickTransitions();
C
cg2121 已提交
533
	obs_data_array_t *savedProjectorList = SaveProjectors();
R
Ryan Foster 已提交
534
	obs_data_t *saveData = GenerateSaveData(sceneOrder, quickTrData,
J
jp9000 已提交
535
			ui->transitionDuration->value(), transitions,
536
			scene, curProgramScene, savedProjectorList);
537

J
jp9000 已提交
538
	obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked());
539 540 541 542 543 544 545 546
	obs_data_set_bool(saveData, "scaling_enabled",
			ui->preview->IsFixedScaling());
	obs_data_set_int(saveData, "scaling_level",
			ui->preview->GetScalingLevel());
	obs_data_set_double(saveData, "scaling_off_x",
			ui->preview->GetScrollX());
	obs_data_set_double(saveData, "scaling_off_y",
			ui->preview->GetScrollY());
J
jp9000 已提交
547

J
jp9000 已提交
548 549 550 551 552 553 554
	if (api) {
		obs_data_t *moduleObj = obs_data_create();
		api->on_save(moduleObj);
		obs_data_set_obj(saveData, "modules", moduleObj);
		obs_data_release(moduleObj);
	}

555 556
	if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
		blog(LOG_ERROR, "Could not save scene data to %s", file);
557 558

	obs_data_release(saveData);
J
jp9000 已提交
559
	obs_data_array_release(sceneOrder);
560
	obs_data_array_release(quickTrData);
J
jp9000 已提交
561
	obs_data_array_release(transitions);
C
cg2121 已提交
562
	obs_data_array_release(savedProjectorList);
563 564
}

I
Ilya M 已提交
565 566 567 568 569 570 571 572 573 574 575 576 577
void OBSBasic::DeferSaveBegin()
{
	os_atomic_inc_long(&disableSaving);
}

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

578
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
579
{
580
	obs_data_t *data = obs_data_get_obj(parent, name);
581 582 583
	if (!data)
		return;

584
	obs_source_t *source = obs_load_source(data);
585 586 587 588 589 590 591 592
	if (source) {
		obs_set_output_source(channel, source);
		obs_source_release(source);
	}

	obs_data_release(data);
}

593 594 595
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
596
	obs_properties_t *props = obs_get_source_properties(output_id);
597 598 599 600 601 602 603 604 605 606 607 608 609 610
	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;
}

611
void OBSBasic::CreateFirstRunSources()
612
{
613 614 615 616 617 618 619 620 621
	bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource());
	bool hasInputAudio   = HasAudioDevices(App()->InputAudioSource());

	if (hasDesktopAudio)
		ResetAudioDevice(App()->OutputAudioSource(), "default",
				Str("Basic.DesktopDevice1"), 1);
	if (hasInputAudio)
		ResetAudioDevice(App()->InputAudioSource(), "default",
				Str("Basic.AuxDevice1"), 3);
622 623
}

624
void OBSBasic::CreateDefaultScene(bool firstStart)
625 626 627 628
{
	disableSaving++;

	ClearSceneData();
629 630 631 632
	InitDefaultTransitions();
	CreateDefaultQuickTransitions();
	ui->transitionDuration->setValue(300);
	SetTransition(fadeTransition);
633 634 635

	obs_scene_t  *scene  = obs_scene_create(Str("Basic.Scene"));

636
	if (firstStart)
637
		CreateFirstRunSources();
638

639
	SetCurrentScene(scene, true);
640
	obs_scene_release(scene);
J
jp9000 已提交
641 642

	disableSaving--;
643 644
}

J
jp9000 已提交
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
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 已提交
674 675 676 677 678 679 680
void OBSBasic::LoadSavedProjectors(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);

681 682 683 684
		SavedProjectorInfo *info = new SavedProjectorInfo();
		info->monitor = obs_data_get_int(data, "monitor");
		info->type = static_cast<ProjectorType>(obs_data_get_int(data,
				"type"));
S
Shaolin 已提交
685 686 687
		info->geometry = std::string(
				obs_data_get_string(data, "geometry"));
		info->name = std::string(obs_data_get_string(data, "name"));
688
		savedProjectorsArray.emplace_back(info);
S
Shaolin 已提交
689 690 691 692 693

		obs_data_release(data);
	}
}

694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
static void LogFilter(obs_source_t*, obs_source_t *filter, void *v_val)
{
	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);
}

static bool LogSceneItem(obs_scene_t*, obs_sceneitem_t *item, void*)
{
	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);

	blog(LOG_INFO, "    - source: '%s' (%s)", name, id);

715 716 717 718 719 720 721 722 723 724 725 726
	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)
			? "monitor only"
			: "monitor and output";

		blog(LOG_INFO, "        - monitoring: %s", type);
	}

727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
	obs_source_enum_filters(source, LogFilter, (void*)(intptr_t)2);
	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);
		obs_scene_enum_items(scene, LogSceneItem, nullptr);
		obs_source_enum_filters(source, LogFilter, (void*)(intptr_t)1);
	}

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

751 752
void OBSBasic::Load(const char *file)
{
753 754 755 756 757
	disableSaving++;

	obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak");
	if (!data) {
		disableSaving--;
758
		blog(LOG_INFO, "No scene file found, creating default scene");
759
		CreateDefaultScene(true);
J
jp9000 已提交
760
		SaveProject();
761
		return;
762
	}
763

764
	ClearSceneData();
765
	InitDefaultTransitions();
766

767 768 769 770
	obs_data_t *modulesObj = obs_data_get_obj(data, "modules");
	if (api)
		api->on_preload(modulesObj);

J
jp9000 已提交
771
	obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
772
	obs_data_array_t *sources    = obs_data_get_array(data, "sources");
773
	obs_data_array_t *groups     = obs_data_get_array(data, "groups");
J
jp9000 已提交
774
	obs_data_array_t *transitions= obs_data_get_array(data, "transitions");
J
jp9000 已提交
775 776
	const char       *sceneName = obs_data_get_string(data,
			"current_scene");
777 778 779 780 781
	const char       *programSceneName = obs_data_get_string(data,
			"current_program_scene");
	const char       *transitionName = obs_data_get_string(data,
			"current_transition");

782 783 784 785 786 787
	if (!opt_starting_scene.empty()) {
		programSceneName = opt_starting_scene.c_str();
		if (!IsPreviewProgramMode())
			sceneName = opt_starting_scene.c_str();
	}

788 789 790 791 792 793
	int newDuration = obs_data_get_int(data, "transition_duration");
	if (!newDuration)
		newDuration = 300;

	if (!transitionName)
		transitionName = obs_source_get_name(fadeTransition);
J
jp9000 已提交
794 795 796 797 798 799 800

	const char *curSceneCollection = config_get_string(
			App()->GlobalConfig(), "Basic", "SceneCollection");

	obs_data_set_default_string(data, "name", curSceneCollection);

	const char       *name = obs_data_get_string(data, "name");
801
	obs_source_t     *curScene;
802 803
	obs_source_t     *curProgramScene;
	obs_source_t     *curTransition;
804

J
jp9000 已提交
805 806 807
	if (!name || !*name)
		name = curSceneCollection;

808 809 810 811 812 813
	LoadAudioDevice(DESKTOP_AUDIO_1, 1, data);
	LoadAudioDevice(DESKTOP_AUDIO_2, 2, data);
	LoadAudioDevice(AUX_AUDIO_1,     3, data);
	LoadAudioDevice(AUX_AUDIO_2,     4, data);
	LoadAudioDevice(AUX_AUDIO_3,     5, data);

814 815 816 817 818 819 820
	if (!sources) {
		sources = groups;
		groups = nullptr;
	} else {
		obs_data_array_push_back_array(sources, groups);
	}

821
	obs_load_sources(sources, nullptr, nullptr);
822

J
jp9000 已提交
823 824
	if (transitions)
		LoadTransitions(transitions);
J
jp9000 已提交
825 826 827
	if (sceneOrder)
		LoadSceneListOrder(sceneOrder);

J
jp9000 已提交
828 829
	obs_data_array_release(transitions);

830 831 832 833 834 835 836
	curTransition = FindTransition(transitionName);
	if (!curTransition)
		curTransition = fadeTransition;

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

837
retryScene:
838
	curScene = obs_get_source_by_name(sceneName);
839
	curProgramScene = obs_get_source_by_name(programSceneName);
840 841 842 843 844 845 846 847 848 849 850 851 852

	/* 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");
		programSceneName = obs_data_get_string(data,
				"current_program_scene");
		obs_source_release(curScene);
		obs_source_release(curProgramScene);
		opt_starting_scene.clear();
		goto retryScene;
	}

853 854 855 856 857 858 859 860
	if (!curProgramScene) {
		curProgramScene = curScene;
		obs_source_addref(curScene);
	}

	SetCurrentScene(curScene, true);
	if (IsPreviewProgramMode())
		TransitionToScene(curProgramScene, true);
861
	obs_source_release(curScene);
862
	obs_source_release(curProgramScene);
863 864

	obs_data_array_release(sources);
865
	obs_data_array_release(groups);
J
jp9000 已提交
866
	obs_data_array_release(sceneOrder);
J
jp9000 已提交
867

868 869 870 871 872 873 874 875 876
	/* ------------------- */

	bool projectorSave = config_get_bool(GetGlobalConfig(), "BasicWindow",
			"SaveProjectors");

	if (projectorSave) {
		obs_data_array_t *savedProjectors = obs_data_get_array(data,
				"saved_projectors");

877
		if (savedProjectors) {
878
			LoadSavedProjectors(savedProjectors);
879 880 881
			OpenSavedProjectors();
			activateWindow();
		}
882 883 884 885 886 887

		obs_data_array_release(savedProjectors);
	}

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

J
jp9000 已提交
888 889 890 891 892 893 894 895
	std::string file_base = strrchr(file, '/') + 1;
	file_base.erase(file_base.size() - 5, 5);

	config_set_string(App()->GlobalConfig(), "Basic", "SceneCollection",
			name);
	config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile",
			file_base.c_str());

896 897 898 899 900 901 902
	obs_data_array_t *quickTransitionData = obs_data_get_array(data,
			"quick_transitions");
	LoadQuickTransitions(quickTransitionData);
	obs_data_array_release(quickTransitionData);

	RefreshQuickTransitions();

J
jp9000 已提交
903 904 905 906
	bool previewLocked = obs_data_get_bool(data, "preview_locked");
	ui->preview->SetLocked(previewLocked);
	ui->actionLockPreview->setChecked(previewLocked);

907 908 909 910 911 912 913 914 915 916
	/* ---------------------- */

	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 已提交
917
	}
918
	ui->preview->SetFixedScaling(fixedScaling);
J
Joseph El-Khouri 已提交
919

920
	/* ---------------------- */
J
Joseph El-Khouri 已提交
921

922
	if (api)
J
jp9000 已提交
923 924
		api->on_load(modulesObj);

925
	obs_data_release(modulesObj);
926
	obs_data_release(data);
J
jp9000 已提交
927

928 929 930
	if (!opt_starting_scene.empty())
		opt_starting_scene.clear();

931
	if (opt_start_streaming) {
932
		blog(LOG_INFO, "Starting stream due to command line parameter");
933 934 935 936 937 938
		QMetaObject::invokeMethod(this, "StartStreaming",
				Qt::QueuedConnection);
		opt_start_streaming = false;
	}

	if (opt_start_recording) {
939
		blog(LOG_INFO, "Starting recording due to command line parameter");
940 941 942 943 944
		QMetaObject::invokeMethod(this, "StartRecording",
				Qt::QueuedConnection);
		opt_start_recording = false;
	}

C
cg2121 已提交
945 946 947 948 949 950
	if (opt_start_replaybuffer) {
		QMetaObject::invokeMethod(this, "StartReplayBuffer",
				Qt::QueuedConnection);
		opt_start_replaybuffer = false;
	}

951 952
	LogScenes();

J
jp9000 已提交
953
	disableSaving--;
954

955
	if (api) {
956
		api->on_event(OBS_FRONTEND_EVENT_SCENE_CHANGED);
957 958
		api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
	}
959 960
}

J
jp9000 已提交
961
#define SERVICE_PATH "service.json"
962 963 964 965 966 967

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

968
	char serviceJsonPath[512];
J
jp9000 已提交
969
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
970 971
			SERVICE_PATH);
	if (ret <= 0)
972 973
		return;

974 975
	obs_data_t *data     = obs_data_create();
	obs_data_t *settings = obs_service_get_settings(service);
976

977
	obs_data_set_string(data, "type", obs_service_get_type(service));
J
jp9000 已提交
978
	obs_data_set_obj(data, "settings", settings);
979

980 981
	if (!obs_data_save_json_safe(data, serviceJsonPath, "tmp", "bak"))
		blog(LOG_WARNING, "Failed to save service");
982 983 984 985 986 987 988 989 990

	obs_data_release(settings);
	obs_data_release(data);
}

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

991
	char serviceJsonPath[512];
J
jp9000 已提交
992
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
993 994
			SERVICE_PATH);
	if (ret <= 0)
995 996
		return false;

997 998
	obs_data_t *data = obs_data_create_from_json_file_safe(serviceJsonPath,
			"bak");
999 1000

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

1003
	obs_data_t *settings = obs_data_get_obj(data, "settings");
P
Palana 已提交
1004
	obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys");
1005

1006
	service = obs_service_create(type, "default_service", settings,
P
Palana 已提交
1007
			hotkey_data);
1008
	obs_service_release(service);
1009

P
Palana 已提交
1010
	obs_data_release(hotkey_data);
1011 1012 1013 1014 1015 1016 1017 1018
	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitService()
{
P
Palana 已提交
1019 1020
	ProfileScope("OBSBasic::InitService");

1021 1022 1023
	if (LoadService())
		return true;

1024 1025
	service = obs_service_create("rtmp_common", "default_service", nullptr,
			nullptr);
1026 1027
	if (!service)
		return false;
1028
	obs_service_release(service);
1029 1030 1031 1032

	return true;
}

1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
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
};

1049 1050
bool OBSBasic::InitBasicConfigDefaults()
{
1051
	QList<QScreen*> screens = QGuiApplication::screens();
1052

1053
	if (!screens.size()) {
1054 1055 1056 1057 1058
		OBSErrorBox(NULL, "There appears to be no monitors.  Er, this "
		                  "technically shouldn't be possible.");
		return false;
	}

1059 1060 1061 1062
	QScreen *primaryScreen = QGuiApplication::primaryScreen();

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

1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
	bool oldResolutionDefaults = config_get_bool(App()->GlobalConfig(),
			"General", "Pre19Defaults");

	/* 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;
	}

1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
	/* ----------------------------------------------------- */
	/* move over mixer values in advanced if older config */
	if (config_has_user_value(basicConfig, "AdvOut", "RecTrackIndex") &&
	    !config_has_user_value(basicConfig, "AdvOut", "RecTracks")) {

		uint64_t track = config_get_uint(basicConfig, "AdvOut",
				"RecTrackIndex");
		track = 1ULL << (track - 1);
		config_set_uint(basicConfig, "AdvOut", "RecTracks", track);
		config_remove_value(basicConfig, "AdvOut", "RecTrackIndex");
1085
		config_save_safe(basicConfig, "tmp", nullptr);
1086 1087 1088 1089
	}

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

1090
	config_set_default_string(basicConfig, "Output", "Mode", "Simple");
J
jp9000 已提交
1091

1092 1093
	config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
			GetDefaultVideoSavePath().c_str());
1094 1095
	config_set_default_string(basicConfig, "SimpleOutput", "RecFormat",
			"flv");
1096 1097
	config_set_default_uint  (basicConfig, "SimpleOutput", "VBitrate",
			2500);
1098 1099
	config_set_default_string(basicConfig, "SimpleOutput", "StreamEncoder",
			SIMPLE_ENCODER_X264);
1100
	config_set_default_uint  (basicConfig, "SimpleOutput", "ABitrate", 160);
J
jp9000 已提交
1101 1102
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseAdvanced",
			false);
1103 1104
	config_set_default_bool  (basicConfig, "SimpleOutput", "EnforceBitrate",
			true);
J
jp9000 已提交
1105 1106
	config_set_default_string(basicConfig, "SimpleOutput", "Preset",
			"veryfast");
1107 1108 1109 1110
	config_set_default_string(basicConfig, "SimpleOutput", "RecQuality",
			"Stream");
	config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
			SIMPLE_ENCODER_X264);
1111 1112 1113
	config_set_default_bool(basicConfig, "SimpleOutput", "RecRB", false);
	config_set_default_int(basicConfig, "SimpleOutput", "RecRBTime", 20);
	config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512);
1114 1115
	config_set_default_string(basicConfig, "SimpleOutput", "RecRBPrefix",
			"Replay");
1116

1117 1118
	config_set_default_bool  (basicConfig, "AdvOut", "ApplyServiceSettings",
			true);
J
jp9000 已提交
1119 1120 1121 1122 1123 1124 1125 1126
	config_set_default_bool  (basicConfig, "AdvOut", "UseRescale", false);
	config_set_default_uint  (basicConfig, "AdvOut", "TrackIndex", 1);
	config_set_default_string(basicConfig, "AdvOut", "Encoder", "obs_x264");

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

	config_set_default_string(basicConfig, "AdvOut", "RecFilePath",
			GetDefaultVideoSavePath().c_str());
1127
	config_set_default_string(basicConfig, "AdvOut", "RecFormat", "flv");
J
jp9000 已提交
1128 1129
	config_set_default_bool  (basicConfig, "AdvOut", "RecUseRescale",
			false);
1130
	config_set_default_uint  (basicConfig, "AdvOut", "RecTracks", (1<<0));
J
jp9000 已提交
1131 1132 1133
	config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
			"none");

1134 1135 1136 1137 1138
	config_set_default_bool  (basicConfig, "AdvOut", "FFOutputToFile",
			true);
	config_set_default_string(basicConfig, "AdvOut", "FFFilePath",
			GetDefaultVideoSavePath().c_str());
	config_set_default_string(basicConfig, "AdvOut", "FFExtension", "mp4");
J
jp9000 已提交
1139
	config_set_default_uint  (basicConfig, "AdvOut", "FFVBitrate", 2500);
1140
	config_set_default_uint  (basicConfig, "AdvOut", "FFVGOPSize", 250);
J
jp9000 已提交
1141 1142
	config_set_default_bool  (basicConfig, "AdvOut", "FFUseRescale",
			false);
1143 1144
	config_set_default_bool  (basicConfig, "AdvOut", "FFIgnoreCompat",
			false);
J
jp9000 已提交
1145 1146 1147 1148 1149 1150 1151
	config_set_default_uint  (basicConfig, "AdvOut", "FFABitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "FFAudioTrack", 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);
J
jp9000 已提交
1152 1153
	config_set_default_uint  (basicConfig, "AdvOut", "Track5Bitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "Track6Bitrate", 160);
J
jp9000 已提交
1154

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

1159 1160 1161
	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);

1162 1163 1164 1165 1166 1167 1168 1169
	/* 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);
	}

1170
	config_set_default_string(basicConfig, "Output", "FilenameFormatting",
B
bl 已提交
1171
			"%CCYY-%MM-%DD %hh-%mm-%ss");
1172

1173 1174 1175 1176
	config_set_default_bool  (basicConfig, "Output", "DelayEnable", false);
	config_set_default_uint  (basicConfig, "Output", "DelaySec", 20);
	config_set_default_bool  (basicConfig, "Output", "DelayPreserve", true);

1177 1178 1179 1180
	config_set_default_bool  (basicConfig, "Output", "Reconnect", true);
	config_set_default_uint  (basicConfig, "Output", "RetryDelay", 10);
	config_set_default_uint  (basicConfig, "Output", "MaxRetries", 20);

1181
	config_set_default_string(basicConfig, "Output", "BindIP", "default");
D
derrod 已提交
1182 1183 1184 1185
	config_set_default_bool  (basicConfig, "Output", "NewSocketLoopEnable",
			false);
	config_set_default_bool  (basicConfig, "Output", "LowLatencyEnable",
			false);
1186

1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
	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);
	}

	config_set_default_uint  (basicConfig, "Video", "OutputCX", scale_cx);
	config_set_default_uint  (basicConfig, "Video", "OutputCY", scale_cy);
1201

1202 1203 1204 1205 1206 1207 1208 1209 1210
	/* 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);
	}

1211 1212 1213 1214 1215
	config_set_default_uint  (basicConfig, "Video", "FPSType", 0);
	config_set_default_string(basicConfig, "Video", "FPSCommon", "30");
	config_set_default_uint  (basicConfig, "Video", "FPSInt", 30);
	config_set_default_uint  (basicConfig, "Video", "FPSNum", 30);
	config_set_default_uint  (basicConfig, "Video", "FPSDen", 1);
1216
	config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
1217
	config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
1218
	config_set_default_string(basicConfig, "Video", "ColorSpace", "601");
1219 1220
	config_set_default_string(basicConfig, "Video", "ColorRange",
			"Partial");
1221

1222 1223 1224 1225 1226
	config_set_default_string(basicConfig, "Audio", "MonitoringDeviceId",
			"default");
	config_set_default_string(basicConfig, "Audio", "MonitoringDeviceName",
			Str("Basic.Settings.Advanced.Audio.MonitoringDevice"
				".Default"));
1227 1228 1229
	config_set_default_uint  (basicConfig, "Audio", "SampleRate", 44100);
	config_set_default_string(basicConfig, "Audio", "ChannelSetup",
			"Stereo");
S
Shaolin 已提交
1230 1231
	config_set_default_double(basicConfig, "Audio", "MeterDecayRate",
			VOLUME_METER_DECAY_FAST);
1232
	config_set_default_uint  (basicConfig, "Audio", "PeakMeterType", 0);
1233 1234 1235 1236 1237 1238

	return true;
}

bool OBSBasic::InitBasicConfig()
{
P
Palana 已提交
1239 1240
	ProfileScope("OBSBasic::InitBasicConfig");

1241
	char configPath[512];
J
jp9000 已提交
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254

	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");
1255 1256 1257 1258
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get base.ini path");
		return false;
	}
1259

1260 1261 1262
	int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (code != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", code);
1263 1264 1265
		return false;
	}

J
jp9000 已提交
1266 1267 1268 1269 1270
	if (config_get_string(basicConfig, "General", "Name") == nullptr) {
		const char *curName = config_get_string(App()->GlobalConfig(),
				"Basic", "Profile");

		config_set_string(basicConfig, "General", "Name", curName);
1271
		basicConfig.SaveSafe("tmp");
J
jp9000 已提交
1272 1273
	}

1274 1275 1276
	return InitBasicConfigDefaults();
}

1277 1278
void OBSBasic::InitOBSCallbacks()
{
P
Palana 已提交
1279 1280
	ProfileScope("OBSBasic::InitOBSCallbacks");

P
Palana 已提交
1281
	signalHandlers.reserve(signalHandlers.size() + 6);
1282 1283
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_create",
			OBSBasic::SourceCreated, this);
P
Palana 已提交
1284
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove",
1285
			OBSBasic::SourceRemoved, this);
P
Palana 已提交
1286
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate",
1287
			OBSBasic::SourceActivated, this);
P
Palana 已提交
1288
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate",
1289
			OBSBasic::SourceDeactivated, this);
P
Palana 已提交
1290
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename",
J
jp9000 已提交
1291
			OBSBasic::SourceRenamed, this);
1292 1293
}

J
jp9000 已提交
1294 1295
void OBSBasic::InitPrimitives()
{
P
Palana 已提交
1296 1297
	ProfileScope("OBSBasic::InitPrimitives");

J
jp9000 已提交
1298
	obs_enter_graphics();
J
jp9000 已提交
1299

1300
	gs_render_start(true);
J
jp9000 已提交
1301 1302 1303 1304 1305
	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);
1306
	box = gs_render_save();
J
jp9000 已提交
1307

1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
	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();

1328
	gs_render_start(true);
J
jp9000 已提交
1329 1330 1331 1332
	for (int i = 0; i <= 360; i += (360/20)) {
		float pos = RAD(float(i));
		gs_vertex2f(cosf(pos), sinf(pos));
	}
1333
	circle = gs_render_save();
J
jp9000 已提交
1334

J
jp9000 已提交
1335
	obs_leave_graphics();
J
jp9000 已提交
1336 1337
}

J
jp9000 已提交
1338 1339 1340 1341 1342 1343 1344 1345
void OBSBasic::ReplayBufferClicked()
{
	if (outputHandler->ReplayBufferActive())
		StopReplayBuffer();
	else
		StartReplayBuffer();
};

J
jp9000 已提交
1346 1347
void OBSBasic::ResetOutputs()
{
P
Palana 已提交
1348 1349
	ProfileScope("OBSBasic::ResetOutputs");

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

J
jp9000 已提交
1353 1354
	if (!outputHandler || !outputHandler->Active()) {
		outputHandler.reset();
J
jp9000 已提交
1355 1356 1357
		outputHandler.reset(advOut ?
			CreateAdvancedOutputHandler(this) :
			CreateSimpleOutputHandler(this));
1358

J
jp9000 已提交
1359 1360 1361 1362 1363 1364
		delete replayBufferButton;

		if (outputHandler->replayBuffer) {
			replayBufferButton = new QPushButton(
					QTStr("Basic.Main.StartReplayBuffer"),
					this);
1365
			connect(replayBufferButton.data(),
J
jp9000 已提交
1366 1367 1368 1369
					&QPushButton::clicked,
					this,
					&OBSBasic::ReplayBufferClicked);

1370
			replayBufferButton->setProperty("themeID", "replayBufferButton");
J
jp9000 已提交
1371 1372
			ui->buttonsVLayout->insertWidget(2, replayBufferButton);
		}
1373

J
jp9000 已提交
1374 1375 1376
		if (sysTrayReplayBuffer)
			sysTrayReplayBuffer->setEnabled(
					!!outputHandler->replayBuffer);
J
jp9000 已提交
1377 1378 1379 1380 1381
	} else {
		outputHandler->Update();
	}
}

J
jp9000 已提交
1382 1383 1384
static void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
		const char *slot);

1385 1386 1387 1388
#define STARTUP_SEPARATOR \
	"==== Startup complete ==============================================="
#define SHUTDOWN_SEPARATOR \
	"==== Shutting down =================================================="
1389

1390 1391 1392 1393 1394
#define UNSUPPORTED_ERROR \
	"Failed to initialize video:\n\nRequired graphics API functionality " \
	"not found.  Your GPU may not be supported."

#define UNKNOWN_ERROR \
1395 1396
	"Failed to initialize video.  Your GPU may not be supported, " \
	"or your graphics drivers may need to be updated."
1397

1398 1399
void OBSBasic::OBSInit()
{
P
Palana 已提交
1400 1401
	ProfileScope("OBSBasic::OBSInit");

J
jp9000 已提交
1402 1403
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
1404
	char savePath[512];
J
jp9000 已提交
1405 1406 1407 1408 1409 1410 1411 1412
	char fileName[512];
	int ret;

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

	ret = snprintf(fileName, 512, "obs-studio/basic/scenes/%s.json",
			sceneCollection);
1413
	if (ret <= 0)
J
jp9000 已提交
1414 1415 1416 1417 1418
		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";
1419

1420 1421
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
1422
	if (!ResetAudio())
1423 1424
		throw "Failed to initialize audio";

1425
	ret = ResetVideo();
1426 1427 1428 1429 1430

	switch (ret) {
	case OBS_VIDEO_MODULE_NOT_FOUND:
		throw "Failed to initialize video:  Graphics module not found";
	case OBS_VIDEO_NOT_SUPPORTED:
1431
		throw UNSUPPORTED_ERROR;
1432 1433 1434 1435
	case OBS_VIDEO_INVALID_PARAM:
		throw "Failed to initialize video:  Invalid parameters";
	default:
		if (ret != OBS_VIDEO_SUCCESS)
1436
			throw UNKNOWN_ERROR;
1437 1438
	}

1439
	/* load audio monitoring */
1440
#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
1441 1442 1443 1444 1445 1446
	const char *device_name = config_get_string(basicConfig, "Audio",
			"MonitoringDeviceName");
	const char *device_id = config_get_string(basicConfig, "Audio",
			"MonitoringDeviceId");

	obs_set_audio_monitoring_device(device_name, device_id);
1447 1448 1449

	blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s",
			device_name, device_id);
1450 1451
#endif

1452
	InitOBSCallbacks();
P
Palana 已提交
1453
	InitHotkeys();
1454

1455
	AddExtraModulePaths();
1456
	blog(LOG_INFO, "---------------------------------");
J
jp9000 已提交
1457
	obs_load_all_modules();
1458 1459
	blog(LOG_INFO, "---------------------------------");
	obs_log_loaded_modules();
1460 1461
	blog(LOG_INFO, "---------------------------------");
	obs_post_load_modules();
J
jp9000 已提交
1462

J
jp9000 已提交
1463 1464 1465 1466
#ifdef _WIN32
	create_browser_widget = obs_browser_init_panel();
#endif

1467 1468
	CheckForSimpleModeX264Fallback();

1469
	blog(LOG_INFO, STARTUP_SEPARATOR);
1470

J
jp9000 已提交
1471
	ResetOutputs();
1472
	CreateHotkeys();
J
jp9000 已提交
1473

1474 1475 1476
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
1477 1478
	InitPrimitives();

1479 1480 1481 1482 1483 1484
	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 已提交
1485 1486 1487 1488 1489 1490 1491 1492

	if (!opt_studio_mode) {
		SetPreviewProgramMode(config_get_bool(App()->GlobalConfig(),
					"BasicWindow", "PreviewProgramMode"));
	} else {
		SetPreviewProgramMode(true);
		opt_studio_mode = false;
	}
1493

1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507
#define SET_VISIBILITY(name, control) \
	do { \
		if (config_has_user_value(App()->GlobalConfig(), \
					"BasicWindow", name)) { \
			bool visible = config_get_bool(App()->GlobalConfig(), \
					"BasicWindow", name); \
			ui->control->setChecked(visible); \
		} \
	} while (false)

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

1508 1509 1510 1511 1512 1513 1514 1515 1516
#ifndef __APPLE__
	{
		ProfileScope("OBSBasic::Load");
		disableSaving--;
		Load(savePath);
		disableSaving++;
	}
#endif

J
jp9000 已提交
1517
	TimedCheckForUpdates();
1518
	loaded = true;
J
jp9000 已提交
1519

1520
	previewEnabled = config_get_bool(App()->GlobalConfig(),
J
jp9000 已提交
1521
			"BasicWindow", "PreviewEnabled");
1522 1523 1524 1525 1526

	if (!previewEnabled && !IsPreviewProgramMode())
		QMetaObject::invokeMethod(this, "EnablePreviewDisplay",
				Qt::QueuedConnection,
				Q_ARG(bool, previewEnabled));
1527 1528 1529 1530 1531 1532 1533 1534 1535

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

1537 1538 1539
#ifndef __APPLE__
	RefreshSceneCollections();
#endif
J
jp9000 已提交
1540
	RefreshProfiles();
J
jp9000 已提交
1541
	disableSaving--;
1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554

	auto addDisplay = [this] (OBSQTDisplay *window)
	{
		obs_display_add_draw_callback(window->GetDisplay(),
				OBSBasic::RenderMain, this);

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

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

1555
#ifdef _WIN32
J
jp9000 已提交
1556
	SetWin32DropStyle(this);
1557 1558 1559 1560 1561
	show();
#endif

	bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow",
			"AlwaysOnTop");
1562
	if (alwaysOnTop || opt_always_on_top) {
1563 1564 1565 1566 1567
		SetAlwaysOnTop(this, true);
		ui->actionAlwaysOnTop->setChecked(true);
	}

#ifndef _WIN32
1568
	show();
1569
#endif
J
jp9000 已提交
1570

J
jp9000 已提交
1571 1572 1573 1574
	const char *dockStateStr = config_get_string(App()->GlobalConfig(),
			"BasicWindow", "DockState");
	if (!dockStateStr) {
		on_resetUI_triggered();
J
jp9000 已提交
1575
	} else {
J
jp9000 已提交
1576 1577 1578 1579
		QByteArray dockState =
			QByteArray::fromBase64(QByteArray(dockStateStr));
		if (!restoreState(dockState))
			on_resetUI_triggered();
J
jp9000 已提交
1580 1581
	}

J
jp9000 已提交
1582 1583 1584 1585 1586 1587 1588 1589 1590
	config_set_default_bool(App()->GlobalConfig(), "BasicWindow",
			"DocksLocked", true);

	bool docksLocked = config_get_bool(App()->GlobalConfig(),
			"BasicWindow", "DocksLocked");
	on_lockUI_toggled(docksLocked);
	ui->lockUI->blockSignals(true);
	ui->lockUI->setChecked(docksLocked);
	ui->lockUI->blockSignals(false);
C
cg2121 已提交
1591 1592

	SystemTray(true);
C
cg2121 已提交
1593

1594 1595 1596
	if (windowState().testFlag(Qt::WindowFullScreen))
		fullscreenInterface = true;

J
jp9000 已提交
1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614
	bool has_last_version = config_has_user_value(App()->GlobalConfig(),
			"General", "LastVersion");
	bool first_run = config_get_bool(App()->GlobalConfig(), "General",
			"FirstRun");

	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");
		msg += "\n\n";
		msg += QTStr("Basic.FirstStartup.RunWizard.BetaWarning");

		QMessageBox::StandardButton button =
1615
			OBSMessageBox::question(this, QTStr("Basic.AutoConfig"),
J
jp9000 已提交
1616 1617 1618 1619 1620 1621
					msg);

		if (button == QMessageBox::Yes) {
			on_autoConfigure_triggered();
		} else {
			msg = QTStr("Basic.FirstStartup.RunWizard.NoClicked");
1622
			OBSMessageBox::information(this,
J
jp9000 已提交
1623 1624 1625
					QTStr("Basic.AutoConfig"), msg);
		}
	}
1626

1627 1628
	ToggleMixerLayout(config_get_bool(App()->GlobalConfig(), "BasicWindow",
			"VerticalVolControl"));
S
Shaolin 已提交
1629

1630 1631
	if (config_get_bool(basicConfig, "General", "OpenStatsOnStartup"))
		on_stats_triggered();
1632 1633

	OBSBasicStats::InitializeValues();
J
jp9000 已提交
1634 1635 1636 1637 1638 1639

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

	ui->viewMenu->addSeparator();

1640
	multiviewProjectorMenu = new QMenu(QTStr("MultiviewProjector"));
J
jp9000 已提交
1641
	ui->viewMenu->addMenu(multiviewProjectorMenu);
1642 1643
	connect(ui->viewMenu->menuAction(), &QAction::hovered, this,
			&OBSBasic::UpdateMultiviewProjectorMenu);
J
jp9000 已提交
1644 1645
	ui->viewMenu->addAction(QTStr("MultiviewWindowed"),
			this, SLOT(OpenMultiviewWindow()));
C
cg2121 已提交
1646 1647

#if !defined(_WIN32) && !defined(__APPLE__)
J
jp9000 已提交
1648 1649 1650
	delete ui->actionShowCrashLogs;
	delete ui->actionUploadLastCrashLog;
	delete ui->menuCrashLogs;
C
cg2121 已提交
1651
	delete ui->actionCheckForUpdates;
J
jp9000 已提交
1652 1653 1654
	ui->actionShowCrashLogs = nullptr;
	ui->actionUploadLastCrashLog = nullptr;
	ui->menuCrashLogs = nullptr;
C
cg2121 已提交
1655 1656
	ui->actionCheckForUpdates = nullptr;
#endif
1657

1658
#ifdef __APPLE__
1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677
	/* This is an incredibly unpleasant hack for macOS to isolate CEF
	 * initialization until after all tasks related to Qt startup and main
	 * window initialization have completed.  There is a macOS-specific bug
	 * within either CEF and/or Qt that can cause a crash if both Qt and
	 * CEF are loading at the same time.
	 *
	 * CEF will typically load fine after about two iterations from this
	 * point, and all Qt tasks are typically fully completed after about
	 * four or five iterations, but to be "ultra" safe, an arbitrarily
	 * large number such as 10 is used.  This hack is extremely unpleasant,
	 * but is worth doing instead of being forced to isolate the entire
	 * browser plugin in to a separate process as before.
	 *
	 * Again, this hack is specific to macOS only.  Fortunately, on other
	 * operating systems, such issues do not occur. */
	QMetaObject::invokeMethod(this, "DeferredLoad",
			Qt::QueuedConnection,
			Q_ARG(QString, QT_UTF8(savePath)),
			Q_ARG(int, 10));
1678 1679
#else
	OnFirstLoad();
1680
#endif
1681 1682
}

1683 1684 1685 1686
void OBSBasic::OnFirstLoad()
{
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_FINISHED_LOADING);
J
jp9000 已提交
1687 1688 1689 1690 1691 1692 1693 1694 1695 1696

#ifdef _WIN32
	/* Attempt to load init screen if available */
	if (create_browser_widget) {
		WhatsNewInfoThread *wnit = new WhatsNewInfoThread();
		if (wnit) {
			connect(wnit, &WhatsNewInfoThread::Result,
					this, &OBSBasic::ReceivedIntroJson);
		}
		if (wnit) {
1697
			introCheckThread.reset(wnit);
J
jp9000 已提交
1698 1699 1700 1701
			introCheckThread->start();
		}
	}
#endif
1702 1703
}

1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714
void OBSBasic::DeferredLoad(const QString &file, int requeueCount)
{
	if (--requeueCount > 0) {
		QMetaObject::invokeMethod(this, "DeferredLoad",
				Qt::QueuedConnection,
				Q_ARG(QString, file),
				Q_ARG(int, requeueCount));
		return;
	}

	Load(QT_TO_UTF8(file));
1715
	RefreshSceneCollections();
1716
	OnFirstLoad();
1717 1718
}

J
jp9000 已提交
1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805
/* shows a "what's new" page on startup of new versions using CEF */
void OBSBasic::ReceivedIntroJson(const QString &text)
{
#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();

		int major = 0;
		int minor = 0;

		sscanf(version.c_str(), "%d.%d", &major, &minor);
		if (major == LIBOBS_API_MAJOR_VER &&
		    minor == LIBOBS_API_MINOR_VER) {
			info_url = url;
			info_increment = increment;
		}
	}

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

	uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
			"LastVersion");

	int current_version_increment = -1;

	if (lastVersion < LIBOBS_API_VER) {
		config_set_int(App()->GlobalConfig(), "General",
				"InfoIncrement", -1);
	} else {
		current_version_increment = config_get_int(
				App()->GlobalConfig(), "General",
				"InfoIncrement");
	}

	if (info_increment <= current_version_increment) {
		return;
	}

	config_set_int(App()->GlobalConfig(), "General",
			"InfoIncrement", info_increment);

	QDialog dlg(this);
	dlg.setWindowTitle("What's New");
	dlg.resize(600, 600);

	QCefWidget *cefWidget = create_browser_widget(nullptr, info_url);
	if (!cefWidget) {
		return;
	}

	connect(cefWidget, SIGNAL(titleChanged(const QString &)),
			&dlg, SLOT(setWindowTitle(const QString &)));

	QPushButton *close = new QPushButton(QTStr("Close"));
	connect(close, &QAbstractButton::clicked,
			&dlg, &QDialog::accept);

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

	QVBoxLayout *topLayout = new QVBoxLayout(&dlg);
	topLayout->addWidget(cefWidget);
	topLayout->addLayout(bottomLayout);

	dlg.exec();
#else
	UNUSED_PARAMETER(text);
#endif
}

1806 1807 1808 1809 1810 1811 1812
void OBSBasic::UpdateMultiviewProjectorMenu()
{
	multiviewProjectorMenu->clear();
	AddProjectorMenuMonitors(multiviewProjectorMenu, this,
			SLOT(OpenMultiviewProjector()));
}

P
Palana 已提交
1813 1814
void OBSBasic::InitHotkeys()
{
P
Palana 已提交
1815 1816
	ProfileScope("OBSBasic::InitHotkeys");

P
Palana 已提交
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858
	struct obs_hotkeys_translations t = {};
	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");
#ifdef _WIN32
	t.meta                         = Str("Hotkeys.Windows");
#else
	t.meta                         = Str("Hotkeys.Super");
#endif
	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");
	obs_hotkeys_set_translations(&t);

	obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"),
1859
			Str("Push-to-mute"), Str("Push-to-talk"));
P
Palana 已提交
1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879

	obs_hotkeys_set_sceneitem_hotkeys_translations(
			Str("SceneItemShow"), Str("SceneItemHide"));

	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)
{
	OBSBasic &basic = *static_cast<OBSBasic*>(data);
	QMetaObject::invokeMethod(&basic, "ProcessHotkey",
			Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed));
}

1880 1881
void OBSBasic::CreateHotkeys()
{
P
Palana 已提交
1882 1883
	ProfileScope("OBSBasic::CreateHotkeys");

1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899
	auto LoadHotkeyData = [&](const char *name) -> OBSData
	{
		const char *info = config_get_string(basicConfig,
				"Hotkeys", name);
		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 已提交
1900 1901 1902 1903 1904 1905 1906 1907 1908
	auto LoadHotkey = [&](obs_hotkey_id id, const char *name)
	{
		obs_data_array_t *array =
			obs_data_get_array(LoadHotkeyData(name), "bindings");

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

1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921
	auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0,
			const char *name1)
	{
		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);
	};

1922
#define MAKE_CALLBACK(pred, method, log_action) \
1923 1924 1925
	[](void *data, obs_hotkey_pair_id, obs_hotkey_t*, bool pressed) \
	{ \
		OBSBasic &basic = *static_cast<OBSBasic*>(data); \
1926
		if ((pred) && pressed) { \
1927
			blog(LOG_INFO, log_action " due to hotkey"); \
1928 1929 1930 1931 1932 1933 1934 1935
			method(); \
			return true; \
		} \
		return false; \
	}

	streamingHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartStreaming",
J
jp9000 已提交
1936
			Str("Basic.Main.StartStreaming"),
1937
			"OBSBasic.StopStreaming",
J
jp9000 已提交
1938
			Str("Basic.Main.StopStreaming"),
1939 1940
			MAKE_CALLBACK(!basic.outputHandler->StreamingActive() &&
				basic.ui->streamButton->isEnabled(),
1941
				basic.StartStreaming, "Starting stream"),
1942 1943
			MAKE_CALLBACK(basic.outputHandler->StreamingActive() &&
				basic.ui->streamButton->isEnabled(),
1944
				basic.StopStreaming, "Stopping stream"),
1945 1946 1947 1948
			this, this);
	LoadHotkeyPair(streamingHotkeys,
			"OBSBasic.StartStreaming", "OBSBasic.StopStreaming");

J
jp9000 已提交
1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963
	auto cb = [] (void *data, obs_hotkey_id, obs_hotkey_t*, bool pressed)
	{
		OBSBasic &basic = *static_cast<OBSBasic*>(data);
		if (basic.outputHandler->StreamingActive() && pressed) {
			basic.ForceStopStreaming();
		}
	};

	forceStreamingStopHotkey = obs_hotkey_register_frontend(
			"OBSBasic.ForceStopStreaming",
			Str("Basic.Main.ForceStopStreaming"),
			cb, this);
	LoadHotkey(forceStreamingStopHotkey,
			"OBSBasic.ForceStopStreaming");

1964 1965
	recordingHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartRecording",
J
jp9000 已提交
1966
			Str("Basic.Main.StartRecording"),
1967
			"OBSBasic.StopRecording",
J
jp9000 已提交
1968
			Str("Basic.Main.StopRecording"),
1969 1970
			MAKE_CALLBACK(!basic.outputHandler->RecordingActive() &&
				!basic.ui->recordButton->isChecked(),
1971
				basic.StartRecording, "Starting recording"),
1972 1973
			MAKE_CALLBACK(basic.outputHandler->RecordingActive() &&
				basic.ui->recordButton->isChecked(),
1974
				basic.StopRecording, "Stopping recording"),
1975 1976 1977
			this, this);
	LoadHotkeyPair(recordingHotkeys,
			"OBSBasic.StartRecording", "OBSBasic.StopRecording");
J
jp9000 已提交
1978 1979 1980 1981 1982 1983 1984

	replayBufHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartReplayBuffer",
			Str("Basic.Main.StartReplayBuffer"),
			"OBSBasic.StopReplayBuffer",
			Str("Basic.Main.StopReplayBuffer"),
			MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(),
1985
				basic.StartReplayBuffer, "Starting replay buffer"),
J
jp9000 已提交
1986
			MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(),
1987
				basic.StopReplayBuffer, "Stopping replay buffer"),
J
jp9000 已提交
1988 1989 1990
			this, this);
	LoadHotkeyPair(replayBufHotkeys,
			"OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer");
1991
#undef MAKE_CALLBACK
1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020

	auto togglePreviewProgram = [] (void *data, obs_hotkey_id,
			obs_hotkey_t*, bool pressed)
	{
		if (pressed)
			QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
					"on_modeSwitch_clicked",
					Qt::QueuedConnection);
	};

	togglePreviewProgramHotkey = obs_hotkey_register_frontend(
			"OBSBasic.TogglePreviewProgram",
			Str("Basic.TogglePreviewProgramMode"),
			togglePreviewProgram, this);
	LoadHotkey(togglePreviewProgramHotkey, "OBSBasic.TogglePreviewProgram");

	auto transition = [] (void *data, obs_hotkey_id, obs_hotkey_t*,
			bool pressed)
	{
		if (pressed)
			QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
					"TransitionClicked",
					Qt::QueuedConnection);
	};

	transitionHotkey = obs_hotkey_register_frontend(
			"OBSBasic.Transition",
			Str("Transition"), transition, this);
	LoadHotkey(transitionHotkey, "OBSBasic.Transition");
2021 2022
}

J
jp9000 已提交
2023 2024 2025 2026
void OBSBasic::ClearHotkeys()
{
	obs_hotkey_pair_unregister(streamingHotkeys);
	obs_hotkey_pair_unregister(recordingHotkeys);
J
jp9000 已提交
2027
	obs_hotkey_pair_unregister(replayBufHotkeys);
J
jp9000 已提交
2028
	obs_hotkey_unregister(forceStreamingStopHotkey);
2029 2030
	obs_hotkey_unregister(togglePreviewProgramHotkey);
	obs_hotkey_unregister(transitionHotkey);
J
jp9000 已提交
2031 2032
}

2033 2034
OBSBasic::~OBSBasic()
{
J
jp9000 已提交
2035 2036 2037
	if (updateCheckThread && updateCheckThread->isRunning())
		updateCheckThread->wait();

2038
	delete multiviewProjectorMenu;
P
pkviet 已提交
2039
	delete trayMenu;
2040 2041
	delete programOptions;
	delete program;
J
jp9000 已提交
2042

J
jp9000 已提交
2043 2044 2045 2046 2047 2048
	/* 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 已提交
2049
	delete cpuUsageTimer;
2050 2051
	os_cpu_usage_info_destroy(cpuUsageInfo);

P
Palana 已提交
2052
	obs_hotkey_set_callback_routing_func(nullptr, nullptr);
J
jp9000 已提交
2053
	ClearHotkeys();
P
Palana 已提交
2054

2055
	service = nullptr;
J
jp9000 已提交
2056 2057
	outputHandler.reset();

J
John Bradley 已提交
2058 2059 2060
	if (interaction)
		delete interaction;

2061 2062 2063
	if (properties)
		delete properties;

J
jp9000 已提交
2064 2065 2066
	if (filters)
		delete filters;

2067 2068
	if (transformWindow)
		delete transformWindow;
2069

J
jp9000 已提交
2070 2071 2072
	if (advAudioWindow)
		delete advAudioWindow;

2073 2074 2075
	obs_display_remove_draw_callback(ui->preview->GetDisplay(),
			OBSBasic::RenderMain, this);

J
jp9000 已提交
2076
	obs_enter_graphics();
2077
	gs_vertexbuffer_destroy(box);
2078 2079 2080 2081
	gs_vertexbuffer_destroy(boxLeft);
	gs_vertexbuffer_destroy(boxTop);
	gs_vertexbuffer_destroy(boxRight);
	gs_vertexbuffer_destroy(boxBottom);
2082
	gs_vertexbuffer_destroy(circle);
J
jp9000 已提交
2083
	obs_leave_graphics();
J
jp9000 已提交
2084

2085 2086 2087 2088 2089 2090 2091 2092 2093
	/* 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 已提交
2094 2095
	config_set_int(App()->GlobalConfig(), "General", "LastVersion",
			LIBOBS_API_VER);
J
jp9000 已提交
2096

2097
	bool alwaysOnTop = IsAlwaysOnTop(this);
J
jp9000 已提交
2098

J
jp9000 已提交
2099 2100
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled",
			previewEnabled);
2101 2102
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
			alwaysOnTop);
2103 2104 2105 2106 2107 2108 2109 2110
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"SceneDuplicationMode", sceneDuplicationMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"SwapScenesMode", swapScenesMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"EditPropertiesMode", editPropertiesMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"PreviewProgramMode", IsPreviewProgramMode());
J
jp9000 已提交
2111 2112
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"DocksLocked", ui->lockUI->isChecked());
2113
	config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124

#ifdef _WIN32
	uint32_t winVer = GetWindowsVersion();
	if (winVer > 0 && winVer < 0x602) {
		bool disableAero = config_get_bool(basicConfig, "Video",
				"DisableAero");
		if (disableAero) {
			SetAeroEnabled(true);
		}
	}
#endif
2125 2126
}

J
jp9000 已提交
2127 2128 2129 2130 2131 2132 2133 2134 2135
void OBSBasic::SaveProjectNow()
{
	if (disableSaving)
		return;

	projectChanged = true;
	SaveProjectDeferred();
}

J
jp9000 已提交
2136 2137
void OBSBasic::SaveProject()
{
J
jp9000 已提交
2138 2139 2140
	if (disableSaving)
		return;

J
jp9000 已提交
2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155
	projectChanged = true;
	QMetaObject::invokeMethod(this, "SaveProjectDeferred",
			Qt::QueuedConnection);
}

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

	if (!projectChanged)
		return;

	projectChanged = false;

J
jp9000 已提交
2156 2157
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
2158
	char savePath[512];
J
jp9000 已提交
2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170
	char fileName[512];
	int ret;

	if (!sceneCollection)
		return;

	ret = snprintf(fileName, 512, "obs-studio/basic/scenes/%s.json",
			sceneCollection);
	if (ret <= 0)
		return;

	ret = GetConfigPath(savePath, sizeof(savePath), fileName);
2171 2172 2173
	if (ret <= 0)
		return;

J
jp9000 已提交
2174 2175 2176
	Save(savePath);
}

S
Shaolin 已提交
2177 2178 2179 2180 2181
OBSSource OBSBasic::GetProgramSource()
{
	return OBSGetStrongRef(programScene);
}

J
jp9000 已提交
2182
OBSScene OBSBasic::GetCurrentScene()
2183
{
J
jp9000 已提交
2184
	QListWidgetItem *item = ui->scenes->currentItem();
P
Palana 已提交
2185
	return item ? GetOBSRef<OBSScene>(item) : nullptr;
2186 2187
}

2188
OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
J
jp9000 已提交
2189
{
P
Palana 已提交
2190
	return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
J
jp9000 已提交
2191 2192
}

2193 2194
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
J
jp9000 已提交
2195
	return ui->sources->Get(GetTopSelectedSourceItem());
2196 2197
}

J
Joseph El-Khouri 已提交
2198 2199
void OBSBasic::UpdatePreviewScalingMenu()
{
2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213
	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
Joseph El-Khouri 已提交
2214
	ui->actionScaleOutput->setChecked(
2215
			scalingAmount == float(ovi.output_width) / float(ovi.base_width));
J
Joseph El-Khouri 已提交
2216 2217
}

2218
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
J
John Bradley 已提交
2219 2220 2221 2222 2223 2224 2225 2226 2227
{
	if (interaction)
		interaction->close();

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

2228
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
2229 2230 2231 2232 2233 2234 2235
{
	if (properties)
		properties->close();

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

J
jp9000 已提交
2238 2239 2240 2241 2242 2243 2244 2245 2246 2247
void OBSBasic::CreateFiltersWindow(obs_source_t *source)
{
	if (filters)
		filters->close();

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

2248 2249 2250
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
2251
{
2252
	const char *name  = obs_source_get_name(source);
2253
	obs_scene_t *scene = obs_scene_from_source(source);
J
jp9000 已提交
2254 2255

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

P
Palana 已提交
2259 2260 2261 2262 2263
	obs_hotkey_register_source(source, "OBSBasic.SelectScene",
			Str("Basic.Hotkeys.SelectScene"),
			[](void *data,
				obs_hotkey_id, obs_hotkey_t*, bool pressed)
	{
2264 2265 2266
		OBSBasic *main =
			reinterpret_cast<OBSBasic*>(App()->GetMainWindow());

P
Palana 已提交
2267 2268 2269
		auto potential_source = static_cast<obs_source_t*>(data);
		auto source = obs_source_get_ref(potential_source);
		if (source && pressed)
2270
			main->SetCurrentScene(source);
P
Palana 已提交
2271 2272 2273
		obs_source_release(source);
	}, static_cast<obs_source_t*>(source));

2274
	signal_handler_t *handler = obs_source_get_signal_handler(source);
2275

J
jp9000 已提交
2276 2277 2278
	SignalContainer<OBSScene> container;
	container.ref = scene;
	container.handlers.assign({
2279 2280 2281 2282 2283 2284 2285 2286
		std::make_shared<OBSSignal>(handler, "item_add",
					OBSBasic::SceneItemAdded, this),
		std::make_shared<OBSSignal>(handler, "item_select",
					OBSBasic::SceneItemSelected, this),
		std::make_shared<OBSSignal>(handler, "item_deselect",
					OBSBasic::SceneItemDeselected, this),
		std::make_shared<OBSSignal>(handler, "reorder",
					OBSBasic::SceneReordered, this),
J
jp9000 已提交
2287
	});
2288 2289

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

2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308
	/* if the scene already has items (a duplicated scene) add them */
	auto addSceneItem = [this] (obs_sceneitem_t *item)
	{
		AddSceneItem(item);
	};

	using addSceneItem_t = decltype(addSceneItem);

	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);

J
jp9000 已提交
2309
	SaveProject();
2310 2311 2312 2313 2314

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

		OBSProjector::UpdateMultiviewProjectors();
2317
	}
2318 2319 2320

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
2321 2322
}

2323
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
2324
{
P
Palana 已提交
2325 2326 2327 2328
	obs_scene_t *scene = obs_scene_from_source(source);

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

P
Palana 已提交
2330 2331 2332 2333 2334
	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 已提交
2335

P
Palana 已提交
2336 2337 2338
		sel = item;
		break;
	}
J
jp9000 已提交
2339

J
jp9000 已提交
2340
	if (sel != nullptr) {
P
Palana 已提交
2341
		if (sel == ui->scenes->currentItem())
J
jp9000 已提交
2342
			ui->sources->Clear();
J
jp9000 已提交
2343
		delete sel;
J
jp9000 已提交
2344
	}
J
jp9000 已提交
2345 2346

	SaveProject();
2347 2348 2349 2350

	if (!disableSaving) {
		blog(LOG_INFO, "User Removed scene '%s'",
				obs_source_get_name(source));
S
Shaolin 已提交
2351 2352

		OBSProjector::UpdateMultiviewProjectors();
2353
	}
2354 2355 2356

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
2357 2358
}

2359
void OBSBasic::AddSceneItem(OBSSceneItem item)
2360
{
2361
	obs_scene_t  *scene  = obs_sceneitem_get_scene(item);
J
jp9000 已提交
2362

2363
	if (GetCurrentScene() == scene)
J
jp9000 已提交
2364
		ui->sources->Add(item);
J
jp9000 已提交
2365

J
jp9000 已提交
2366
	SaveProject();
2367 2368 2369 2370 2371 2372 2373 2374 2375

	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'",
				obs_source_get_name(itemSource),
				obs_source_get_id(itemSource),
				obs_source_get_name(sceneSource));
	}
2376 2377
}

2378
void OBSBasic::UpdateSceneSelection(OBSSource source)
2379 2380
{
	if (source) {
2381
		obs_scene_t *scene = obs_scene_from_source(source);
2382
		const char *name = obs_source_get_name(source);
J
jp9000 已提交
2383

2384 2385 2386
		if (!scene)
			return;

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

2390 2391 2392 2393 2394
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

2395 2396 2397 2398
			OBSScene curScene =
				GetOBSRef<OBSScene>(ui->scenes->currentItem());
			if (api && scene != curScene)
				api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
2399
		}
J
jp9000 已提交
2400
	}
2401 2402
}

J
jp9000 已提交
2403 2404 2405 2406 2407 2408 2409 2410 2411 2412
static void RenameListValues(QListWidget *listWidget, const QString &newName,
		const QString &prevName)
{
	QList<QListWidgetItem*> items =
		listWidget->findItems(prevName, Qt::MatchExactly);

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

S
Shaolin 已提交
2413 2414
void OBSBasic::RenameSources(OBSSource source, QString newName,
		QString prevName)
J
jp9000 已提交
2415 2416
{
	RenameListValues(ui->scenes,  newName, prevName);
2417 2418 2419 2420 2421

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

2423
	OBSProjector::RenameProjector(prevName, newName);
C
cg2121 已提交
2424

J
jp9000 已提交
2425
	SaveProject();
S
Shaolin 已提交
2426 2427 2428 2429

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

2432 2433
void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
{
J
jp9000 已提交
2434 2435
	SignalBlocker sourcesSignalBlocker(ui->sources);

2436
	if (scene != GetCurrentScene() || ignoreSelectionUpdate)
2437 2438
		return;

J
jp9000 已提交
2439
	ui->sources->SelectItem(item, select);
2440 2441
}

2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457
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);
}

2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475
void OBSBasic::GetAudioSourceFilters()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	VolControl *vol = action->property("volControl").value<VolControl*>();
	obs_source_t *source = vol->GetSource();

	CreateFiltersWindow(source);
}

void OBSBasic::GetAudioSourceProperties()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	VolControl *vol = action->property("volControl").value<VolControl*>();
	obs_source_t *source = vol->GetSource();

	CreatePropertiesWindow(source);
}

2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525
void OBSBasic::HideAudioControl()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	VolControl *vol = action->property("volControl").value<VolControl*>();
	obs_source_t *source = vol->GetSource();

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

void OBSBasic::UnhideAllAudioControls()
{
	auto UnhideAudioMixer = [this] (obs_source_t *source) /* -- */
	{
		if (!obs_source_active(source))
			return true;
		if (!SourceMixerHidden(source))
			return true;

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

	using UnhideAudioMixer_t = decltype(UnhideAudioMixer);

	auto PreEnum = [] (void *data, obs_source_t *source) -> bool /* -- */
	{
		return (*reinterpret_cast<UnhideAudioMixer_t *>(data))(source);
	};

	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);
	}
}

2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550
void OBSBasic::MixerRenameSource()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	VolControl *vol = action->property("volControl").value<VolControl*>();
	OBSSource source = vol->GetSource();

	const char *prevName = obs_source_get_name(source);

	for (;;) {
		string name;
		bool accepted = NameDialog::AskForName(this,
				QTStr("Basic.Main.MixerRename.Title"),
				QTStr("Basic.Main.MixerRename.Text"),
				name,
				QT_UTF8(prevName));
		if (!accepted)
			return;

		if (name.empty()) {
			OBSMessageBox::information(this,
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
			continue;
		}

2551 2552
		OBSSource sourceTest = obs_get_source_by_name(name.c_str());
		obs_source_release(sourceTest);
2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565

		if (sourceTest) {
			OBSMessageBox::information(this,
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));
			continue;
		}

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

2566 2567 2568 2569
void OBSBasic::VolControlContextMenu()
{
	VolControl *vol = reinterpret_cast<VolControl*>(sender());

2570 2571 2572 2573
	/* ------------------- */

	QAction hideAction(QTStr("Hide"), this);
	QAction unhideAllAction(QTStr("UnhideAll"), this);
2574
	QAction mixerRenameAction(QTStr("Rename"), this);
2575

2576 2577
	QAction filtersAction(QTStr("Filters"), this);
	QAction propertiesAction(QTStr("Properties"), this);
2578
	QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this);
2579

S
Shaolin 已提交
2580 2581 2582 2583 2584
	QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
	toggleControlLayoutAction.setCheckable(true);
	toggleControlLayoutAction.setChecked(config_get_bool(GetGlobalConfig(),
			"BasicWindow", "VerticalVolControl"));

2585 2586 2587 2588 2589 2590 2591 2592
	/* ------------------- */

	connect(&hideAction, &QAction::triggered,
			this, &OBSBasic::HideAudioControl,
			Qt::DirectConnection);
	connect(&unhideAllAction, &QAction::triggered,
			this, &OBSBasic::UnhideAllAudioControls,
			Qt::DirectConnection);
2593 2594 2595
	connect(&mixerRenameAction, &QAction::triggered,
			this, &OBSBasic::MixerRenameSource,
			Qt::DirectConnection);
2596

2597 2598 2599 2600 2601 2602
	connect(&filtersAction, &QAction::triggered,
			this, &OBSBasic::GetAudioSourceFilters,
			Qt::DirectConnection);
	connect(&propertiesAction, &QAction::triggered,
			this, &OBSBasic::GetAudioSourceProperties,
			Qt::DirectConnection);
2603 2604 2605
	connect(&advPropAction, &QAction::triggered,
			this, &OBSBasic::on_actionAdvAudioProperties_triggered,
			Qt::DirectConnection);
2606

2607 2608
	/* ------------------- */

S
Shaolin 已提交
2609 2610 2611 2612 2613 2614
	connect(&toggleControlLayoutAction, &QAction::changed, this,
			&OBSBasic::ToggleVolControlLayout,
			Qt::DirectConnection);

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

2615 2616
	hideAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));
2617 2618
	mixerRenameAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));
2619

2620 2621 2622 2623 2624
	filtersAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));
	propertiesAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));

2625 2626
	/* ------------------- */

2627
	QMenu popup(this);
2628 2629
	popup.addAction(&unhideAllAction);
	popup.addAction(&hideAction);
2630
	popup.addAction(&mixerRenameAction);
2631
	popup.addSeparator();
S
Shaolin 已提交
2632 2633
	popup.addAction(&toggleControlLayoutAction);
	popup.addSeparator();
2634 2635
	popup.addAction(&filtersAction);
	popup.addAction(&propertiesAction);
2636
	popup.addAction(&advPropAction);
2637 2638 2639
	popup.exec(QCursor::pos());
}

S
Shaolin 已提交
2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650
void OBSBasic::on_hMixerScrollArea_customContextMenuRequested()
{
	StackedMixerAreaContextMenuRequested();
}

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

void OBSBasic::StackedMixerAreaContextMenuRequested()
2651 2652
{
	QAction unhideAllAction(QTStr("UnhideAll"), this);
S
SuslikV 已提交
2653 2654 2655

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

S
Shaolin 已提交
2656 2657 2658 2659 2660
	QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this);
	toggleControlLayoutAction.setCheckable(true);
	toggleControlLayoutAction.setChecked(config_get_bool(GetGlobalConfig(),
			"BasicWindow", "VerticalVolControl"));

S
SuslikV 已提交
2661 2662
	/* ------------------- */

2663 2664 2665 2666
	connect(&unhideAllAction, &QAction::triggered,
			this, &OBSBasic::UnhideAllAudioControls,
			Qt::DirectConnection);

S
SuslikV 已提交
2667 2668 2669 2670 2671 2672
	connect(&advPropAction, &QAction::triggered,
			this, &OBSBasic::on_actionAdvAudioProperties_triggered,
			Qt::DirectConnection);

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

S
Shaolin 已提交
2673 2674 2675 2676 2677 2678
	connect(&toggleControlLayoutAction, &QAction::changed, this,
			&OBSBasic::ToggleVolControlLayout,
			Qt::DirectConnection);

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

2679 2680
	QMenu popup(this);
	popup.addAction(&unhideAllAction);
S
SuslikV 已提交
2681
	popup.addSeparator();
S
Shaolin 已提交
2682 2683
	popup.addAction(&toggleControlLayoutAction);
	popup.addSeparator();
S
SuslikV 已提交
2684
	popup.addAction(&advPropAction);
2685 2686 2687
	popup.exec(QCursor::pos());
}

2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698
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 已提交
2699 2700 2701 2702 2703 2704
void OBSBasic::ToggleVolControlLayout()
{
	bool vertical = !config_get_bool(GetGlobalConfig(), "BasicWindow",
			"VerticalVolControl");
	config_set_bool(GetGlobalConfig(), "BasicWindow", "VerticalVolControl",
			vertical);
2705
	ToggleMixerLayout(vertical);
S
Shaolin 已提交
2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718

	// 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);
}

2719 2720
void OBSBasic::ActivateAudioSource(OBSSource source)
{
2721 2722 2723
	if (SourceMixerHidden(source))
		return;

S
Shaolin 已提交
2724 2725 2726
	bool vertical = config_get_bool(GetGlobalConfig(), "BasicWindow",
			"VerticalVolControl");
	VolControl *vol = new VolControl(source, true, vertical);
2727

S
Shaolin 已提交
2728 2729 2730
	double meterDecayRate = config_get_double(basicConfig, "Audio",
			"MeterDecayRate");
	vol->SetMeterDecayRate(meterDecayRate);
2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749

	uint32_t peakMeterTypeIdx = config_get_uint(basicConfig, "Audio",
			"PeakMeterType");

	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);

2750 2751 2752 2753
	vol->setContextMenuPolicy(Qt::CustomContextMenu);

	connect(vol, &QWidget::customContextMenuRequested,
			this, &OBSBasic::VolControlContextMenu);
2754 2755
	connect(vol, &VolControl::ConfigClicked,
			this, &OBSBasic::VolControlContextMenu);
2756

2757 2758 2759
	InsertQObjectByName(volumes, vol);

	for (auto volume : volumes) {
S
Shaolin 已提交
2760 2761 2762 2763
		if (vertical)
			ui->vVolControlLayout->addWidget(volume);
		else
			ui->hVolControlLayout->addWidget(volume);
2764
	}
2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777
}

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;
		}
	}
}

2778
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
J
jp9000 已提交
2779
{
J
jp9000 已提交
2780
	if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE &&
2781
	    !obs_source_is_group(source)) {
2782 2783 2784
		int count = ui->scenes->count();

		if (count == 1) {
2785
			OBSMessageBox::information(this,
2786 2787 2788 2789
						QTStr("FinalScene.Title"),
						QTStr("FinalScene.Text"));
			return false;
		}
2790 2791
	}

2792
	const char *name  = obs_source_get_name(source);
2793 2794 2795

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

2797
	QMessageBox remove_source(this);
2798 2799 2800
	remove_source.setText(text);
	QAbstractButton *Yes = remove_source.addButton(QTStr("Yes"),
			QMessageBox::YesRole);
J
Jkoan 已提交
2801 2802 2803 2804 2805 2806
	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();
2807
}
J
jp9000 已提交
2808

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

P
Palana 已提交
2811 2812 2813 2814 2815
#ifdef UPDATE_SPARKLE
void init_sparkle_updater(bool update_to_undeployed);
void trigger_sparkle_update();
#endif

J
jp9000 已提交
2816 2817
void OBSBasic::TimedCheckForUpdates()
{
J
jp9000 已提交
2818 2819 2820 2821
	if (!config_get_bool(App()->GlobalConfig(), "General",
				"EnableAutoUpdates"))
		return;

P
Palana 已提交
2822 2823 2824
#ifdef UPDATE_SPARKLE
	init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
				"UpdateToUndeployed"));
2825
#elif _WIN32
J
jp9000 已提交
2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840
	long long lastUpdate = config_get_int(App()->GlobalConfig(), "General",
			"LastUpdateCheck");
	uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
			"LastVersion");

	if (lastVersion < LIBOBS_API_VER) {
		lastUpdate = 0;
		config_set_int(App()->GlobalConfig(), "General",
				"LastUpdateCheck", 0);
	}

	long long t    = (long long)time(nullptr);
	long long secs = t - lastUpdate;

	if (secs > UPDATE_CHECK_INTERVAL)
J
jp9000 已提交
2841
		CheckForUpdates(false);
P
Palana 已提交
2842
#endif
J
jp9000 已提交
2843 2844
}

J
jp9000 已提交
2845
void OBSBasic::CheckForUpdates(bool manualUpdate)
J
jp9000 已提交
2846
{
P
Palana 已提交
2847 2848
#ifdef UPDATE_SPARKLE
	trigger_sparkle_update();
2849
#elif _WIN32
J
jp9000 已提交
2850 2851
	ui->actionCheckForUpdates->setEnabled(false);

J
jp9000 已提交
2852 2853
	if (updateCheckThread && updateCheckThread->isRunning())
		return;
2854

2855
	updateCheckThread.reset(new AutoUpdateThread(manualUpdate));
2856
	updateCheckThread->start();
P
Palana 已提交
2857
#endif
2858 2859

	UNUSED_PARAMETER(manualUpdate);
J
jp9000 已提交
2860 2861
}

J
jp9000 已提交
2862
void OBSBasic::updateCheckFinished()
J
jp9000 已提交
2863 2864 2865 2866
{
	ui->actionCheckForUpdates->setEnabled(true);
}

2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896
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;
		bool accepted = NameDialog::AskForName(this,
				QTStr("Basic.Main.AddSceneDlg.Title"),
				QTStr("Basic.Main.AddSceneDlg.Text"),
				name,
				placeHolderText);
		if (!accepted)
			return;

		if (name.empty()) {
2897
			OBSMessageBox::information(this,
2898 2899 2900 2901 2902 2903 2904
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
			continue;
		}

		obs_source_t *source = obs_get_source_by_name(name.c_str());
		if (source) {
2905
			OBSMessageBox::information(this,
2906 2907 2908 2909 2910 2911 2912 2913
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));

			obs_source_release(source);
			continue;
		}

		obs_scene_t *scene = obs_scene_duplicate(curScene,
2914
				name.c_str(), OBS_SCENE_DUP_REFS);
2915
		source = obs_scene_get_source(scene);
2916
		SetCurrentScene(source, true);
2917
		obs_scene_release(scene);
J
jp9000 已提交
2918

2919
		break;
2920 2921 2922
	}
}

2923 2924 2925 2926
void OBSBasic::RemoveSelectedScene()
{
	OBSScene scene = GetCurrentScene();
	if (scene) {
2927
		obs_source_t *source = obs_scene_get_source(scene);
J
jp9000 已提交
2928
		if (QueryRemoveSource(source)) {
2929
			obs_source_remove(source);
J
jp9000 已提交
2930 2931 2932 2933

			if (api)
				api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
		}
2934 2935 2936 2937 2938 2939 2940
	}
}

void OBSBasic::RemoveSelectedSceneItem()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (item) {
2941
		obs_source_t *source = obs_sceneitem_get_source(item);
2942
		if (QueryRemoveSource(source))
J
jp9000 已提交
2943 2944 2945 2946
			obs_sceneitem_remove(item);
	}
}

2947 2948
void OBSBasic::ReorderSources(OBSScene scene)
{
2949
	if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
2950 2951
		return;

J
jp9000 已提交
2952
	ui->sources->ReorderItems();
J
jp9000 已提交
2953
	SaveProject();
2954 2955
}

2956 2957
/* OBS Callbacks */

2958 2959 2960 2961 2962 2963 2964 2965 2966 2967
void OBSBasic::SceneReordered(void *data, calldata_t *params)
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

	obs_scene_t *scene = (obs_scene_t*)calldata_ptr(params, "scene");

	QMetaObject::invokeMethod(window, "ReorderSources",
			Q_ARG(OBSScene, OBSScene(scene)));
}

2968
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
2969 2970 2971
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

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

2974 2975
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
2976 2977
}

2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999
void OBSBasic::SceneItemSelected(void *data, calldata_t *params)
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

	obs_scene_t     *scene = (obs_scene_t*)calldata_ptr(params, "scene");
	obs_sceneitem_t *item  = (obs_sceneitem_t*)calldata_ptr(params, "item");

	QMetaObject::invokeMethod(window, "SelectSceneItem",
			Q_ARG(OBSScene, scene), Q_ARG(OBSSceneItem, item),
			Q_ARG(bool, true));
}

void OBSBasic::SceneItemDeselected(void *data, calldata_t *params)
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

	obs_scene_t     *scene = (obs_scene_t*)calldata_ptr(params, "scene");
	obs_sceneitem_t *item  = (obs_sceneitem_t*)calldata_ptr(params, "item");

	QMetaObject::invokeMethod(window, "SelectSceneItem",
			Q_ARG(OBSScene, scene), Q_ARG(OBSSceneItem, item),
			Q_ARG(bool, false));
3000

3001 3002
}

3003
void OBSBasic::SourceCreated(void *data, calldata_t *params)
3004
{
3005
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
3006

3007
	if (obs_scene_from_source(source) != NULL)
3008
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
3009
				"AddScene", WaitConnection(),
3010
				Q_ARG(OBSSource, OBSSource(source)));
3011 3012
}

3013
void OBSBasic::SourceRemoved(void *data, calldata_t *params)
3014
{
3015
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
3016

3017
	if (obs_scene_from_source(source) != NULL)
3018 3019 3020
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
3021 3022
}

3023
void OBSBasic::SourceActivated(void *data, calldata_t *params)
3024
{
3025
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
3026 3027 3028 3029 3030 3031 3032 3033
	uint32_t     flags  = obs_source_get_output_flags(source);

	if (flags & OBS_SOURCE_AUDIO)
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"ActivateAudioSource",
				Q_ARG(OBSSource, OBSSource(source)));
}

3034
void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
3035
{
3036
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
3037 3038 3039 3040 3041 3042 3043 3044
	uint32_t     flags  = obs_source_get_output_flags(source);

	if (flags & OBS_SOURCE_AUDIO)
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"DeactivateAudioSource",
				Q_ARG(OBSSource, OBSSource(source)));
}

3045
void OBSBasic::SourceRenamed(void *data, calldata_t *params)
J
jp9000 已提交
3046
{
S
Shaolin 已提交
3047
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
J
jp9000 已提交
3048 3049 3050 3051 3052
	const char *newName  = calldata_string(params, "new_name");
	const char *prevName = calldata_string(params, "prev_name");

	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"RenameSources",
S
Shaolin 已提交
3053
			Q_ARG(OBSSource, source),
J
jp9000 已提交
3054 3055
			Q_ARG(QString, QT_UTF8(newName)),
			Q_ARG(QString, QT_UTF8(prevName)));
3056 3057

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

3060 3061 3062 3063 3064
void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

3065
	gs_effect_t    *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
3066 3067
	gs_eparam_t    *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech  = gs_effect_get_technique(solid, "Solid");
3068 3069 3070

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
3071
	gs_effect_set_vec4(color, &colorVal);
3072

3073 3074
	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
3075 3076 3077 3078 3079 3080 3081 3082
	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();
3083 3084
	gs_technique_end_pass(tech);
	gs_technique_end(tech);
3085 3086 3087 3088

	gs_load_vertexbuffer(nullptr);
}

3089 3090
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
3091
	OBSBasic *window = static_cast<OBSBasic*>(data);
3092 3093 3094 3095
	obs_video_info ovi;

	obs_get_video_info(&ovi);

J
jp9000 已提交
3096 3097
	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));
3098 3099 3100

	gs_viewport_push();
	gs_projection_push();
3101 3102 3103

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

3104 3105
	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
			-100.0f, 100.0f);
3106
	gs_set_viewport(window->previewX, window->previewY,
J
jp9000 已提交
3107
			window->previewCX, window->previewCY);
3108

3109 3110
	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

3111 3112 3113 3114 3115 3116
	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 已提交
3117
		obs_render_main_texture();
3118
	}
3119
	gs_load_vertexbuffer(nullptr);
3120

3121 3122
	/* --------------------------------------- */

3123 3124 3125
	QSize previewSize = GetPixelSize(window->ui->preview);
	float right  = float(previewSize.width())  - window->previewX;
	float bottom = float(previewSize.height()) - window->previewY;
3126 3127 3128 3129

	gs_ortho(-window->previewX, right,
	         -window->previewY, bottom,
	         -100.0f, 100.0f);
3130
	gs_reset_viewport();
J
jp9000 已提交
3131 3132 3133

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

3134 3135
	/* --------------------------------------- */

3136 3137
	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
3138 3139 3140

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
3141 3142
}

3143 3144
/* Main class functions */

3145
obs_service_t *OBSBasic::GetService()
3146
{
3147
	if (!service) {
3148 3149
		service = obs_service_create("rtmp_common", NULL, NULL,
				nullptr);
3150 3151
		obs_service_release(service);
	}
3152 3153 3154
	return service;
}

3155
void OBSBasic::SetService(obs_service_t *newService)
3156
{
3157
	if (newService)
3158 3159 3160
		service = newService;
}

3161
bool OBSBasic::StreamingActive() const
3162 3163 3164 3165 3166 3167
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

3168 3169 3170 3171 3172 3173 3174
bool OBSBasic::Active() const
{
	if (!outputHandler)
		return false;
	return outputHandler->Active();
}

3175 3176 3177 3178 3179 3180
#ifdef _WIN32
#define IS_WIN32 1
#else
#define IS_WIN32 0
#endif

3181 3182
static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
3183
	return obs_reset_video(ovi);
3184 3185
}

3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198
static inline enum obs_scale_type GetScaleType(ConfigFile &basicConfig)
{
	const char *scaleTypeStr = config_get_string(basicConfig,
			"Video", "ScaleType");

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

3199 3200 3201 3202 3203 3204
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 已提交
3205 3206
	else if (astrcmpi(name, "I444") == 0)
		return VIDEO_FORMAT_I444;
3207 3208 3209 3210 3211 3212 3213 3214 3215
#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 已提交
3216
		return VIDEO_FORMAT_RGBA;
3217 3218
}

3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229
void OBSBasic::ResetUI()
{
	bool studioPortraitLayout = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "StudioPortraitLayout");

	if (studioPortraitLayout)
		ui->previewLayout->setDirection(QBoxLayout::TopToBottom);
	else
		ui->previewLayout->setDirection(QBoxLayout::LeftToRight);
}

3230
int OBSBasic::ResetVideo()
J
jp9000 已提交
3231
{
3232 3233 3234
	if (outputHandler && outputHandler->Active())
		return OBS_VIDEO_CURRENTLY_ACTIVE;

P
Palana 已提交
3235 3236
	ProfileScope("OBSBasic::ResetVideo");

J
jp9000 已提交
3237
	struct obs_video_info ovi;
3238
	int ret;
J
jp9000 已提交
3239

3240
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
3241

3242 3243 3244 3245 3246 3247 3248
	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");

J
jp9000 已提交
3249
	ovi.graphics_module = App()->GetRenderModule();
3250
	ovi.base_width     = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
3251
			"Video", "BaseCX");
3252
	ovi.base_height    = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
3253
			"Video", "BaseCY");
3254
	ovi.output_width   = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
3255
			"Video", "OutputCX");
3256
	ovi.output_height  = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
3257
			"Video", "OutputCY");
3258 3259 3260 3261 3262
	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;
3263 3264
	ovi.adapter        = config_get_uint(App()->GlobalConfig(),
			"Video", "AdapterIdx");
J
jp9000 已提交
3265
	ovi.gpu_conversion = true;
3266
	ovi.scale_type     = GetScaleType(basicConfig);
3267

3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283
	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);
	}

3284
	ret = AttemptToResetVideo(&ovi);
3285
	if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
3286 3287 3288 3289 3290 3291
		if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
			blog(LOG_WARNING, "Tried to reset when "
			                  "already active");
			return ret;
		}

3292
		/* Try OpenGL if DirectX fails on windows */
3293
		if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
3294 3295 3296 3297
			blog(LOG_WARNING, "Failed to initialize obs video (%d) "
					  "with graphics_module='%s', retrying "
					  "with graphics_module='%s'",
					  ret, ovi.graphics_module,
3298 3299
					  DL_OPENGL);
			ovi.graphics_module = DL_OPENGL;
3300 3301
			ret = AttemptToResetVideo(&ovi);
		}
3302 3303
	} else if (ret == OBS_VIDEO_SUCCESS) {
		ResizePreview(ovi.base_width, ovi.base_height);
3304 3305
		if (program)
			ResizeProgram(ovi.base_width, ovi.base_height);
3306 3307
	}

3308
	if (ret == OBS_VIDEO_SUCCESS) {
3309
		OBSBasicStats::InitializeValues();
3310 3311
		OBSProjector::UpdateMultiviewProjectors();
	}
3312

3313
	return ret;
J
jp9000 已提交
3314
}
J
jp9000 已提交
3315

3316
bool OBSBasic::ResetAudio()
J
jp9000 已提交
3317
{
P
Palana 已提交
3318 3319
	ProfileScope("OBSBasic::ResetAudio");

3320
	struct obs_audio_info ai;
3321
	ai.samples_per_sec = config_get_uint(basicConfig, "Audio",
3322 3323
			"SampleRate");

3324
	const char *channelSetupStr = config_get_string(basicConfig,
3325 3326 3327 3328
			"Audio", "ChannelSetup");

	if (strcmp(channelSetupStr, "Mono") == 0)
		ai.speakers = SPEAKERS_MONO;
P
pkviet 已提交
3329 3330
	else if (strcmp(channelSetupStr, "2.1") == 0)
		ai.speakers = SPEAKERS_2POINT1;
P
pkviet 已提交
3331 3332
	else if (strcmp(channelSetupStr, "4.0") == 0)
		ai.speakers = SPEAKERS_4POINT0;
P
pkviet 已提交
3333 3334 3335 3336 3337 3338
	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;
3339 3340 3341
	else
		ai.speakers = SPEAKERS_STEREO;

J
jp9000 已提交
3342
	return obs_reset_audio(&ai);
J
jp9000 已提交
3343 3344
}

3345
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
3346
		const char *deviceDesc, int channel)
J
jp9000 已提交
3347
{
3348
	bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
3349 3350
	obs_source_t *source;
	obs_data_t *settings;
J
jp9000 已提交
3351 3352 3353

	source = obs_get_output_source(channel);
	if (source) {
3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366
		if (disable) {
			obs_set_output_source(channel, nullptr);
		} else {
			settings = obs_source_get_settings(source);
			const char *oldId = obs_data_get_string(settings,
					"device_id");
			if (strcmp(oldId, deviceId) != 0) {
				obs_data_set_string(settings, "device_id",
						deviceId);
				obs_source_update(source, settings);
			}
			obs_data_release(settings);
		}
J
jp9000 已提交
3367 3368 3369

		obs_source_release(source);

3370 3371
	} else if (!disable) {
		settings = obs_data_create();
J
jp9000 已提交
3372
		obs_data_set_string(settings, "device_id", deviceId);
3373 3374
		source = obs_source_create(sourceId, deviceDesc, settings,
				nullptr);
J
jp9000 已提交
3375 3376 3377 3378 3379 3380 3381
		obs_data_release(settings);

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

J
jp9000 已提交
3382
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
3383
{
3384
	QSize  targetSize;
3385
	bool isFixedScaling;
J
Joseph El-Khouri 已提交
3386
	obs_video_info ovi;
J
jp9000 已提交
3387

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

3391
	isFixedScaling = ui->preview->IsFixedScaling();
J
Joseph El-Khouri 已提交
3392 3393
	obs_get_video_info(&ovi);

3394 3395
	if (isFixedScaling) {
		previewScale = ui->preview->GetScalingAmount();
J
Joseph El-Khouri 已提交
3396 3397 3398 3399
		GetCenterPosFromFixedScale(int(cx), int(cy),
				targetSize.width() - PREVIEW_EDGE_SIZE * 2,
				targetSize.height() - PREVIEW_EDGE_SIZE * 2,
				previewX, previewY, previewScale);
3400 3401
		previewX += ui->preview->GetScrollX();
		previewY += ui->preview->GetScrollY();
J
Joseph El-Khouri 已提交
3402 3403 3404 3405 3406 3407 3408

	} else {
		GetScaleAndCenterPos(int(cx), int(cy),
				targetSize.width() - PREVIEW_EDGE_SIZE * 2,
				targetSize.height() - PREVIEW_EDGE_SIZE * 2,
				previewX, previewY, previewScale);
	}
J
jp9000 已提交
3409

3410 3411
	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);
J
jp9000 已提交
3412 3413
}

3414 3415 3416 3417 3418 3419 3420 3421 3422
void OBSBasic::CloseDialogs()
{
	QList<QDialog*> childDialogs = this->findChildren<QDialog *>();
	if (!childDialogs.isEmpty()) {
		for (int i = 0; i < childDialogs.size(); ++i) {
			childDialogs.at(i)->close();
		}
	}

C
cg2121 已提交
3423 3424 3425 3426
	for (QPointer<QWidget> &projector : windowProjectors) {
		delete projector;
		projector.clear();
	}
3427 3428 3429 3430
	for (QPointer<QWidget> &projector : projectors) {
		delete projector;
		projector.clear();
	}
J
jp9000 已提交
3431

3432
	if (!stats.isNull()) stats->close(); //call close to save Stats geometry
3433
	if (!remux.isNull()) remux->close();
3434 3435
}

3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458
void OBSBasic::EnumDialogs()
{
	visDialogs.clear();
	modalDialogs.clear();
	visMsgBoxes.clear();

	/* fill list of Visible dialogs and Modal dialogs */
	QList<QDialog*> dialogs = findChildren<QDialog*>();
	for (QDialog *dialog : dialogs) {
		if (dialog->isVisible())
			visDialogs.append(dialog);
		if (dialog->isModal())
			modalDialogs.append(dialog);
	}

	/* fill list of Visible message boxes */
	QList<QMessageBox*> msgBoxes = findChildren<QMessageBox*>();
	for (QMessageBox *msgbox : msgBoxes) {
		if (msgbox->isVisible())
			visMsgBoxes.append(msgbox);
	}
}

3459 3460
void OBSBasic::ClearSceneData()
{
J
jp9000 已提交
3461 3462
	disableSaving++;

3463 3464 3465 3466
	CloseDialogs();

	ClearVolumeControls();
	ClearListItems(ui->scenes);
J
jp9000 已提交
3467
	ui->sources->Clear();
3468 3469
	ClearQuickTransitions();
	ui->transitions->clear();
3470 3471 3472 3473 3474 3475 3476

	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);
3477 3478 3479
	lastScene = nullptr;
	swapScene = nullptr;
	programScene = nullptr;
3480 3481 3482 3483 3484 3485 3486 3487 3488 3489

	auto cb = [](void *unused, obs_source_t *source)
	{
		obs_source_remove(source);
		UNUSED_PARAMETER(unused);
		return true;
	};

	obs_enum_sources(cb, nullptr);

3490 3491 3492
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP);

J
jp9000 已提交
3493
	disableSaving--;
3494 3495 3496

	blog(LOG_INFO, "All scene data cleared");
	blog(LOG_INFO, "------------------------------------------------");
3497 3498
}

J
jp9000 已提交
3499
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
3500
{
3501 3502 3503 3504 3505
	if (isVisible())
		config_set_string(App()->GlobalConfig(),
				"BasicWindow", "geometry",
				saveGeometry().toBase64().constData());

J
jp9000 已提交
3506 3507 3508 3509
	config_set_string(App()->GlobalConfig(),
			"BasicWindow", "DockState",
			saveState().toBase64().constData());

3510
	if (outputHandler && outputHandler->Active()) {
C
cg2121 已提交
3511 3512
		SetShowing(true);

3513
		QMessageBox::StandardButton button = OBSMessageBox::question(
3514 3515 3516 3517 3518 3519 3520 3521 3522
				this, QTStr("ConfirmExit.Title"),
				QTStr("ConfirmExit.Text"));

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

3523 3524 3525 3526
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

3527 3528
	blog(LOG_INFO, SHUTDOWN_SEPARATOR);

J
jp9000 已提交
3529 3530
	if (introCheckThread)
		introCheckThread->wait();
3531 3532 3533 3534 3535
	if (updateCheckThread)
		updateCheckThread->wait();
	if (logUploadThread)
		logUploadThread->wait();

P
Palana 已提交
3536 3537
	signalHandlers.clear();

J
jp9000 已提交
3538
	SaveProjectNow();
J
jp9000 已提交
3539 3540 3541 3542

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

J
jp9000 已提交
3543
	disableSaving++;
J
jp9000 已提交
3544

3545 3546 3547
	/* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
	 * sources, etc) so that all references are released before shutdown */
	ClearSceneData();
3548 3549

	App()->quit();
3550 3551
}

J
jp9000 已提交
3552
void OBSBasic::changeEvent(QEvent *event)
3553
{
3554 3555
	if (event->type() == QEvent::WindowStateChange &&
	    isMinimized() &&
3556
	    trayIcon &&
3557 3558 3559 3560 3561
	    trayIcon->isVisible() &&
	    sysTrayMinimizeToTray()) {

		ToggleShowHide();
	}
3562 3563
}

3564 3565
void OBSBasic::on_actionShow_Recordings_triggered()
{
3566 3567 3568 3569
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
	const char *path = strcmp(mode, "Advanced") ?
		config_get_string(basicConfig, "SimpleOutput", "FilePath") :
		config_get_string(basicConfig, "AdvOut", "RecFilePath");
3570 3571 3572
	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

P
Palana 已提交
3573 3574
void OBSBasic::on_actionRemux_triggered()
{
3575 3576 3577 3578 3579 3580
	if (!remux.isNull()) {
		remux->show();
		remux->raise();
		return;
	}

3581 3582 3583 3584
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
	const char *path = strcmp(mode, "Advanced") ?
		config_get_string(basicConfig, "SimpleOutput", "FilePath") :
		config_get_string(basicConfig, "AdvOut", "RecFilePath");
3585 3586 3587 3588 3589

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

P
Palana 已提交
3592 3593 3594 3595
void OBSBasic::on_action_Settings_triggered()
{
	OBSBasicSettings settings(this);
	settings.exec();
C
cg2121 已提交
3596
	SystemTray(false);
P
Palana 已提交
3597 3598
}

J
jp9000 已提交
3599 3600
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
3601 3602 3603 3604 3605
	if (advAudioWindow != nullptr) {
		advAudioWindow->raise();
		return;
	}

J
jp9000 已提交
3606 3607 3608
	advAudioWindow = new OBSBasicAdvAudio(this);
	advAudioWindow->show();
	advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
3609 3610 3611

	connect(advAudioWindow, SIGNAL(destroyed()),
		this, SLOT(on_advAudioProps_destroyed()));
J
jp9000 已提交
3612 3613
}

3614 3615 3616 3617 3618
void OBSBasic::on_advAudioProps_clicked()
{
	on_actionAdvAudioProperties_triggered();
}

3619 3620 3621 3622 3623
void OBSBasic::on_advAudioProps_destroyed()
{
	advAudioWindow = nullptr;
}

3624 3625
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
3626
{
3627
	obs_source_t *source = NULL;
J
jp9000 已提交
3628

3629 3630 3631 3632
	if (sceneChanging)
		return;

	if (current) {
3633
		obs_scene_t *scene;
J
jp9000 已提交
3634

P
Palana 已提交
3635
		scene = GetOBSRef<OBSScene>(current);
3636
		source = obs_scene_get_source(scene);
3637 3638
	}

3639
	SetCurrentScene(source);
3640

3641 3642 3643
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);

3644
	UNUSED_PARAMETER(prev);
3645 3646
}

J
jp9000 已提交
3647 3648
void OBSBasic::EditSceneName()
{
3649 3650 3651 3652 3653 3654
	QListWidgetItem *item = ui->scenes->currentItem();
	Qt::ItemFlags flags   = item->flags();

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

J
jp9000 已提交
3657 3658 3659 3660
static void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
		const char *slot)
{
	QAction *action;
3661 3662
	QList<QScreen*> screens = QGuiApplication::screens();
	for (int i = 0; i < screens.size(); i++) {
3663
		QRect screenGeometry = screens[i]->geometry();
J
jp9000 已提交
3664 3665 3666
		QString str = QString("%1 %2: %3x%4 @ %5,%6").
			arg(QTStr("Display"),
			    QString::number(i),
S
Shaolin 已提交
3667 3668 3669 3670
			    QString::number(screenGeometry.width()),
			    QString::number(screenGeometry.height()),
			    QString::number(screenGeometry.x()),
			    QString::number(screenGeometry.y()));
J
jp9000 已提交
3671 3672 3673 3674 3675 3676

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

J
jp9000 已提交
3677
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
3678
{
J
jp9000 已提交
3679
	QListWidgetItem *item = ui->scenes->itemAt(pos);
J
jp9000 已提交
3680
	QPointer<QMenu> sceneProjectorMenu;
J
jp9000 已提交
3681

3682
	QMenu popup(this);
J
jp9000 已提交
3683
	QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
J
jp9000 已提交
3684 3685 3686
	popup.addAction(QTStr("Add"),
			this, SLOT(on_actionAddScene_triggered()));

P
Palana 已提交
3687 3688
	if (item) {
		popup.addSeparator();
3689 3690
		popup.addAction(QTStr("Duplicate"),
				this, SLOT(DuplicateSelectedScene()));
P
Palana 已提交
3691 3692
		popup.addAction(QTStr("Rename"),
				this, SLOT(EditSceneName()));
J
jp9000 已提交
3693
		popup.addAction(QTStr("Remove"),
3694
				this, SLOT(RemoveSelectedScene()));
J
jp9000 已提交
3695
		popup.addSeparator();
J
jp9000 已提交
3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708

		order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"),
				this, SLOT(on_actionSceneUp_triggered()));
		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 已提交
3709

J
jp9000 已提交
3710 3711 3712 3713
		sceneProjectorMenu = new QMenu(QTStr("SceneProjector"));
		AddProjectorMenuMonitors(sceneProjectorMenu, this,
				SLOT(OpenSceneProjector()));
		popup.addMenu(sceneProjectorMenu);
C
cg2121 已提交
3714 3715 3716 3717 3718 3719

		QAction *sceneWindow = popup.addAction(
				QTStr("SceneWindow"),
				this, SLOT(OpenSceneWindow()));

		popup.addAction(sceneWindow);
J
jp9000 已提交
3720
		popup.addSeparator();
J
jp9000 已提交
3721 3722
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenSceneFilters()));
3723 3724 3725 3726 3727

		popup.addSeparator();

		QMenu *transitionMenu = CreatePerSceneTransitionMenu();
		popup.addMenu(transitionMenu);
S
Shaolin 已提交
3728 3729 3730

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

J
jp9000 已提交
3731 3732
		QAction *multiviewAction = popup.addAction(
				QTStr("ShowInMultiview"));
S
Shaolin 已提交
3733

J
jp9000 已提交
3734 3735 3736
		OBSSource source = GetCurrentSceneSource();
		OBSData data = obs_source_get_private_settings(source);
		obs_data_release(data);
S
Shaolin 已提交
3737

J
jp9000 已提交
3738 3739 3740
		obs_data_set_default_bool(data, "show_in_multiview",
				true);
		bool show = obs_data_get_bool(data, "show_in_multiview");
S
Shaolin 已提交
3741

J
jp9000 已提交
3742 3743
		multiviewAction->setCheckable(true);
		multiviewAction->setChecked(show);
S
Shaolin 已提交
3744

3745
		auto showInMultiview = [] (OBSData data)
J
jp9000 已提交
3746 3747 3748 3749 3750 3751 3752 3753 3754 3755
		{
			bool show = obs_data_get_bool(data,
					"show_in_multiview");
			obs_data_set_bool(data, "show_in_multiview",
					!show);
			OBSProjector::UpdateMultiviewProjectors();
		};

		connect(multiviewAction, &QAction::triggered,
				std::bind(showInMultiview, data));
P
Palana 已提交
3756
	}
J
jp9000 已提交
3757 3758

	popup.exec(QCursor::pos());
3759 3760
}

J
jp9000 已提交
3761
void OBSBasic::on_actionAddScene_triggered()
3762
{
3763
	string name;
S
Socapex 已提交
3764
	QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
P
Palana 已提交
3765

3766
	int i = 2;
P
Palana 已提交
3767
	QString placeHolderText = format.arg(i);
3768
	obs_source_t *source = nullptr;
P
Palana 已提交
3769 3770
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
P
Palana 已提交
3771
		placeHolderText = format.arg(++i);
P
Palana 已提交
3772
	}
S
Socapex 已提交
3773

J
jp9000 已提交
3774
	bool accepted = NameDialog::AskForName(this,
3775 3776
			QTStr("Basic.Main.AddSceneDlg.Title"),
			QTStr("Basic.Main.AddSceneDlg.Text"),
S
Socapex 已提交
3777 3778
			name,
			placeHolderText);
3779

J
jp9000 已提交
3780
	if (accepted) {
J
jp9000 已提交
3781
		if (name.empty()) {
3782
			OBSMessageBox::information(this,
3783 3784
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
J
jp9000 已提交
3785 3786 3787 3788
			on_actionAddScene_triggered();
			return;
		}

3789
		obs_source_t *source = obs_get_source_by_name(name.c_str());
3790
		if (source) {
3791
			OBSMessageBox::information(this,
3792 3793
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));
3794 3795

			obs_source_release(source);
J
jp9000 已提交
3796
			on_actionAddScene_triggered();
3797 3798 3799
			return;
		}

3800
		obs_scene_t *scene = obs_scene_create(name.c_str());
3801
		source = obs_scene_get_source(scene);
3802
		SetCurrentScene(source);
3803
		obs_scene_release(scene);
3804
	}
3805 3806
}

J
jp9000 已提交
3807
void OBSBasic::on_actionRemoveScene_triggered()
3808
{
3809
	OBSScene     scene  = GetCurrentScene();
3810
	obs_source_t *source = obs_scene_get_source(scene);
3811 3812 3813

	if (source && QueryRemoveSource(source))
		obs_source_remove(source);
3814 3815
}

J
jp9000 已提交
3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833
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;
3834 3835

	OBSProjector::UpdateMultiviewProjectors();
J
jp9000 已提交
3836 3837
}

J
jp9000 已提交
3838
void OBSBasic::on_actionSceneUp_triggered()
3839
{
J
jp9000 已提交
3840
	ChangeSceneIndex(true, -1, 0);
3841 3842
}

J
jp9000 已提交
3843
void OBSBasic::on_actionSceneDown_triggered()
3844
{
J
jp9000 已提交
3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856
	ChangeSceneIndex(true, 1, ui->scenes->count() - 1);
}

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

void OBSBasic::MoveSceneToBottom()
{
	ChangeSceneIndex(false, ui->scenes->count() - 1,
			ui->scenes->count() - 1);
3857 3858
}

J
jp9000 已提交
3859 3860
void OBSBasic::EditSceneItemName()
{
J
jp9000 已提交
3861 3862
	int idx = GetTopSelectedSourceItem();
	ui->sources->Edit(idx);
J
jp9000 已提交
3863 3864
}

J
jp9000 已提交
3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929
void OBSBasic::SetDeinterlacingMode()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	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()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	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);
}

QMenu *OBSBasic::AddDeinterlacingMenu(obs_source_t *source)
{
	QMenu *menu = new QMenu(QTStr("Deinterlacing"));
	obs_deinterlace_mode deinterlaceMode =
		obs_source_get_deinterlace_mode(source);
	obs_deinterlace_field_order deinterlaceOrder =
		obs_source_get_deinterlace_field_order(source);
	QAction *action;

#define ADD_MODE(name, mode) \
	action = menu->addAction(QTStr("" name), this, \
				SLOT(SetDeinterlacingMode())); \
	action->setProperty("mode", (int)mode); \
	action->setCheckable(true); \
	action->setChecked(deinterlaceMode == mode);

	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);
	ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X);
	ADD_MODE("Deinterlacing.Yadif",    OBS_DEINTERLACE_MODE_YADIF);
	ADD_MODE("Deinterlacing.Yadif2x",  OBS_DEINTERLACE_MODE_YADIF_2X);
#undef ADD_MODE

	menu->addSeparator();

#define ADD_ORDER(name, order) \
	action = menu->addAction(QTStr("Deinterlacing." name), this, \
				SLOT(SetDeinterlacingOrder())); \
	action->setProperty("order", (int)order); \
	action->setCheckable(true); \
	action->setChecked(deinterlaceOrder == order);

	ADD_ORDER("TopFieldFirst",    OBS_DEINTERLACE_FIELD_ORDER_TOP);
	ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM);
#undef ADD_ORDER

	return menu;
}

3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961
void OBSBasic::SetScaleFilter()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	obs_scale_type mode = (obs_scale_type)action->property("mode").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();

	obs_sceneitem_set_scale_filter(sceneItem, mode);
}

QMenu *OBSBasic::AddScaleFilteringMenu(obs_sceneitem_t *item)
{
	QMenu *menu = new QMenu(QTStr("ScaleFiltering"));
	obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item);
	QAction *action;

#define ADD_MODE(name, mode) \
	action = menu->addAction(QTStr("" name), this, \
				SLOT(SetScaleFilter())); \
	action->setProperty("mode", (int)mode); \
	action->setCheckable(true); \
	action->setChecked(scaleFilter == mode);

	ADD_MODE("Disable",                 OBS_SCALE_DISABLE);
	ADD_MODE("ScaleFiltering.Point",    OBS_SCALE_POINT);
	ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR);
	ADD_MODE("ScaleFiltering.Bicubic",  OBS_SCALE_BICUBIC);
	ADD_MODE("ScaleFiltering.Lanczos",  OBS_SCALE_LANCZOS);
#undef ADD_MODE

	return menu;
}

J
jp9000 已提交
3962
void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
3963
{
3964
	QMenu popup(this);
J
jp9000 已提交
3965 3966
	QPointer<QMenu> previewProjector;
	QPointer<QMenu> sourceProjector;
J
jp9000 已提交
3967 3968 3969 3970 3971 3972

	if (preview) {
		QAction *action = popup.addAction(
				QTStr("Basic.Main.PreviewConextMenu.Enable"),
				this, SLOT(TogglePreview()));
		action->setCheckable(true);
3973 3974
		action->setChecked(
				obs_display_enabled(ui->preview->GetDisplay()));
3975 3976
		if (IsPreviewProgramMode())
			action->setEnabled(false);
J
jp9000 已提交
3977

J
Joseph El-Khouri 已提交
3978 3979
		popup.addAction(ui->actionLockPreview);
		popup.addMenu(ui->scalingMenu);
J
jp9000 已提交
3980

J
jp9000 已提交
3981 3982
		previewProjector = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjector, this,
3983
				SLOT(OpenPreviewProjector()));
J
jp9000 已提交
3984 3985 3986

		popup.addMenu(previewProjector);

C
cg2121 已提交
3987 3988
		QAction *previewWindow = popup.addAction(
				QTStr("PreviewWindow"),
3989
				this, SLOT(OpenPreviewWindow()));
C
cg2121 已提交
3990 3991 3992

		popup.addAction(previewWindow);

J
jp9000 已提交
3993 3994 3995
		popup.addSeparator();
	}

J
jp9000 已提交
3996 3997 3998 3999
	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

4000
	ui->actionCopyFilters->setEnabled(false);
4001
	ui->actionCopySource->setEnabled(false);
4002

J
jp9000 已提交
4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013
	if (ui->sources->MultipleBaseSelected()) {
		popup.addSeparator();
		popup.addAction(QTStr("Basic.Main.GroupItems"),
				ui->sources, SLOT(GroupSelectedItems()));

	} else if (ui->sources->GroupsSelected()) {
		popup.addSeparator();
		popup.addAction(QTStr("Basic.Main.Ungroup"),
				ui->sources, SLOT(UngroupSelectedGroups()));
	}

4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024
	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 已提交
4025
	if (idx != -1) {
J
jp9000 已提交
4026 4027 4028
		if (addSourceMenu)
			popup.addSeparator();

J
jp9000 已提交
4029
		OBSSceneItem sceneItem = ui->sources->Get(idx);
4030
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
J
jp9000 已提交
4031 4032 4033
		uint32_t flags = obs_source_get_output_flags(source);
		bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) ==
			OBS_SOURCE_ASYNC_VIDEO;
4034 4035
		bool hasAudio = (flags & OBS_SOURCE_AUDIO) ==
			OBS_SOURCE_AUDIO;
J
John Bradley 已提交
4036 4037
		QAction *action;

J
jp9000 已提交
4038 4039
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
4040
		popup.addAction(QTStr("Remove"), this,
4041
				SLOT(on_actionRemoveSource_triggered()));
J
jp9000 已提交
4042 4043
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
J
jp9000 已提交
4044
		popup.addMenu(ui->transformMenu);
J
jp9000 已提交
4045 4046 4047 4048 4049

		sourceProjector = new QMenu(QTStr("SourceProjector"));
		AddProjectorMenuMonitors(sourceProjector, this,
				SLOT(OpenSourceProjector()));

C
cg2121 已提交
4050 4051 4052 4053 4054 4055
		QAction *sourceWindow = popup.addAction(
				QTStr("SourceWindow"),
				this, SLOT(OpenSourceWindow()));

		popup.addAction(sourceWindow);

J
jp9000 已提交
4056
		popup.addSeparator();
4057 4058 4059 4060 4061 4062 4063 4064 4065

		if (hasAudio) {
			QAction *actionHideMixer = popup.addAction(
					QTStr("HideMixer"),
					this, SLOT(ToggleHideMixer()));
			actionHideMixer->setCheckable(true);
			actionHideMixer->setChecked(SourceMixerHidden(source));
		}

J
jp9000 已提交
4066 4067 4068 4069
		if (isAsyncVideo) {
			popup.addMenu(AddDeinterlacingMenu(source));
			popup.addSeparator();
		}
4070 4071 4072 4073

		popup.addMenu(AddScaleFilteringMenu(sceneItem));
		popup.addSeparator();

J
jp9000 已提交
4074
		popup.addMenu(sourceProjector);
C
cg2121 已提交
4075
		popup.addAction(sourceWindow);
J
jp9000 已提交
4076
		popup.addSeparator();
J
John Bradley 已提交
4077 4078 4079 4080 4081 4082 4083

		action = popup.addAction(QTStr("Interact"), this,
				SLOT(on_actionInteract_triggered()));

		action->setEnabled(obs_source_get_output_flags(source) &
				OBS_SOURCE_INTERACTION);

J
jp9000 已提交
4084 4085
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenFilters()));
J
jp9000 已提交
4086 4087
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));
4088 4089

		ui->actionCopyFilters->setEnabled(true);
4090
		ui->actionCopySource->setEnabled(true);
4091 4092
	} else {
		ui->actionPasteFilters->setEnabled(false);
J
jp9000 已提交
4093 4094 4095
	}

	popup.exec(QCursor::pos());
4096 4097
}

J
jp9000 已提交
4098 4099
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
J
jp9000 已提交
4100 4101 4102 4103
	if (ui->scenes->count()) {
		QModelIndex idx = ui->sources->indexAt(pos);
		CreateSourcePopupMenu(idx.row(), false);
	}
P
Palana 已提交
4104 4105
}

4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123
void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
{
	if (!witem)
		return;

	if (IsPreviewProgramMode()) {
		bool doubleClickSwitch = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "TransitionOnDoubleClick");

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

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

J
jp9000 已提交
4124
void OBSBasic::AddSource(const char *id)
4125
{
4126 4127 4128
	if (id && *id) {
		OBSBasicSourceSelect sourceSelect(this, id);
		sourceSelect.exec();
4129
		if (sourceSelect.newSource && strcmp(id, "group") != 0)
4130
			CreatePropertiesWindow(sourceSelect.newSource);
4131
	}
4132 4133
}

4134
QMenu *OBSBasic::CreateAddSourcePopupMenu()
4135
{
4136
	const char *type;
J
jp9000 已提交
4137
	bool foundValues = false;
4138
	bool foundDeprecated = false;
J
jp9000 已提交
4139
	size_t idx = 0;
4140

4141
	QMenu *popup = new QMenu(QTStr("Add"), this);
4142
	QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup);
4143

J
jp9000 已提交
4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160
	auto getActionAfter = [] (QMenu *menu, const QString &name)
	{
		QList<QAction*> actions = menu->actions();

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

		return (QAction*)nullptr;
	};

	auto addSource = [this, getActionAfter] (QMenu *popup,
			const char *type, const char *name)
	{
		QString qname = QT_UTF8(name);
		QAction *popupItem = new QAction(qname, this);
J
jp9000 已提交
4161
		popupItem->setData(QT_UTF8(type));
4162 4163
		connect(popupItem, SIGNAL(triggered(bool)),
				this, SLOT(AddSourceFromAction()));
J
jp9000 已提交
4164 4165 4166

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

J
jp9000 已提交
4169 4170
	while (obs_enum_input_types(idx++, &type)) {
		const char *name = obs_source_get_display_name(type);
4171
		uint32_t caps = obs_get_source_output_flags(type);
J
jp9000 已提交
4172

4173 4174 4175
		if ((caps & OBS_SOURCE_CAP_DISABLED) != 0)
			continue;

4176 4177
		if ((caps & OBS_SOURCE_DEPRECATED) == 0) {
			addSource(popup, type, name);
4178 4179 4180
		} else {
			addSource(deprecated, type, name);
			foundDeprecated = true;
4181
		}
4182
		foundValues = true;
4183 4184
	}

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

J
jp9000 已提交
4187 4188
	popup->addSeparator();
	QAction *addGroup = new QAction(QTStr("Group"), this);
4189
	addGroup->setData(QT_UTF8("group"));
J
jp9000 已提交
4190
	connect(addGroup, SIGNAL(triggered(bool)),
4191
			this, SLOT(AddSourceFromAction()));
J
jp9000 已提交
4192 4193
	popup->addAction(addGroup);

4194 4195 4196 4197 4198
	if (!foundDeprecated) {
		delete deprecated;
		deprecated = nullptr;
	}

4199 4200 4201
	if (!foundValues) {
		delete popup;
		popup = nullptr;
4202 4203

	} else if (foundDeprecated) {
J
jp9000 已提交
4204
		popup->addSeparator();
4205
		popup->addMenu(deprecated);
4206
	}
4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223

	return popup;
}

void OBSBasic::AddSourceFromAction()
{
	QAction *action = qobject_cast<QAction*>(sender());
	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).
4224
		OBSMessageBox::information(this,
4225 4226 4227 4228 4229
				QTStr("Basic.Main.AddSourceHelp.Title"),
				QTStr("Basic.Main.AddSourceHelp.Text"));
		return;
	}

4230
	QScopedPointer<QMenu> popup(CreateAddSourcePopupMenu());
4231 4232
	if (popup)
		popup->exec(pos);
4233 4234
}

J
jp9000 已提交
4235
void OBSBasic::on_actionAddSource_triggered()
4236
{
J
jp9000 已提交
4237
	AddSourcePopupMenu(QCursor::pos());
4238 4239
}

J
jp9000 已提交
4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252
static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
{
	vector<OBSSceneItem> &items =
		*reinterpret_cast<vector<OBSSceneItem>*>(param);

	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 已提交
4253
void OBSBasic::on_actionRemoveSource_triggered()
4254
{
4255
	vector<OBSSceneItem> items;
4256

J
jp9000 已提交
4257
	obs_scene_enum_items(GetCurrentScene(), remove_items, &items);
4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290

	if (!items.size())
		return;

	auto removeMultiple = [this] (size_t count)
	{
		QString text = QTStr("ConfirmRemove.TextMultiple")
			.arg(QString::number(count));

		QMessageBox remove_items(this);
		remove_items.setText(text);
		QAbstractButton *Yes = remove_items.addButton(QTStr("Yes"),
				QMessageBox::YesRole);
		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);
		}
	}
4291 4292
}

J
John Bradley 已提交
4293 4294 4295 4296 4297 4298 4299 4300 4301
void OBSBasic::on_actionInteract_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreateInteractionWindow(source);
}

J
jp9000 已提交
4302
void OBSBasic::on_actionSourceProperties_triggered()
4303
{
4304
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4305
	OBSSource source = obs_sceneitem_get_source(item);
4306

4307 4308
	if (source)
		CreatePropertiesWindow(source);
4309 4310
}

J
jp9000 已提交
4311
void OBSBasic::on_actionSourceUp_triggered()
4312
{
J
jp9000 已提交
4313
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4314
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
4315
}
J
jp9000 已提交
4316

J
jp9000 已提交
4317
void OBSBasic::on_actionSourceDown_triggered()
4318
{
J
jp9000 已提交
4319
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4320
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
4321 4322
}

J
jp9000 已提交
4323 4324 4325
void OBSBasic::on_actionMoveUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4326
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
J
jp9000 已提交
4327 4328 4329 4330 4331
}

void OBSBasic::on_actionMoveDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4332
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
J
jp9000 已提交
4333 4334 4335 4336 4337
}

void OBSBasic::on_actionMoveToTop_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4338
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_TOP);
J
jp9000 已提交
4339 4340 4341 4342 4343
}

void OBSBasic::on_actionMoveToBottom_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
4344
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_BOTTOM);
J
jp9000 已提交
4345 4346
}

4347
static BPtr<char> ReadLogFile(const char *subdir, const char *log)
J
jp9000 已提交
4348
{
4349
	char logDir[512];
4350
	if (GetConfigPath(logDir, sizeof(logDir), subdir) <= 0)
4351
		return nullptr;
J
jp9000 已提交
4352 4353 4354 4355 4356

	string path = (char*)logDir;
	path += "/";
	path += log;

4357
	BPtr<char> file = os_quick_read_utf8_file(path.c_str());
J
jp9000 已提交
4358 4359 4360 4361 4362 4363
	if (!file)
		blog(LOG_WARNING, "Failed to read log file %s", path.c_str());

	return file;
}

4364
void OBSBasic::UploadLog(const char *subdir, const char *file)
J
jp9000 已提交
4365
{
4366
	BPtr<char> fileString{ReadLogFile(subdir, file)};
J
jp9000 已提交
4367

4368
	if (!fileString)
J
jp9000 已提交
4369 4370
		return;

4371
	if (!*fileString)
J
jp9000 已提交
4372 4373 4374 4375
		return;

	ui->menuLogFiles->setEnabled(false);

4376 4377
	stringstream ss;
	ss << "OBS " << App()->GetVersionString()
4378 4379
	   << " log file uploaded at " << CurrentDateTimeString()
	   << "\n\n" << fileString;
4380

F
fryshorts 已提交
4381

4382 4383 4384
	if (logUploadThread) {
		logUploadThread->wait();
	}
F
fryshorts 已提交
4385

4386
	RemoteTextThread *thread = new RemoteTextThread(
4387
			"https://obsproject.com/logs/upload",
4388 4389
			"text/plain", ss.str().c_str());

4390
	logUploadThread.reset(thread);
4391 4392 4393
	connect(thread, &RemoteTextThread::Result,
			this, &OBSBasic::logUploadFinished);
	logUploadThread->start();
J
jp9000 已提交
4394 4395
}

P
Palana 已提交
4396 4397
void OBSBasic::on_actionShowLogs_triggered()
{
4398
	char logDir[512];
4399
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
4400 4401
		return;

P
Palana 已提交
4402 4403 4404 4405
	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
4406 4407
void OBSBasic::on_actionUploadCurrentLog_triggered()
{
4408
	UploadLog("obs-studio/logs", App()->GetCurrentLog());
J
jp9000 已提交
4409 4410 4411 4412
}

void OBSBasic::on_actionUploadLastLog_triggered()
{
4413
	UploadLog("obs-studio/logs", App()->GetLastLog());
J
jp9000 已提交
4414 4415
}

4416 4417 4418
void OBSBasic::on_actionViewCurrentLog_triggered()
{
	char logDir[512];
4419
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431
		return;

	const char* log = App()->GetCurrentLog();

	string path = (char*)logDir;
	path += "/";
	path += log;

	QUrl url = QUrl::fromLocalFile(QT_UTF8(path.c_str()));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446
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 已提交
4447 4448
void OBSBasic::on_actionCheckForUpdates_triggered()
{
J
jp9000 已提交
4449
	CheckForUpdates(true);
J
jp9000 已提交
4450 4451
}

4452
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
J
jp9000 已提交
4453 4454 4455
{
	ui->menuLogFiles->setEnabled(true);

4456
	if (text.isEmpty()) {
4457
		OBSMessageBox::information(this,
J
jp9000 已提交
4458
				QTStr("LogReturnDialog.ErrorUploadingLog"),
4459
				error);
J
jp9000 已提交
4460 4461 4462
		return;
	}

4463
	obs_data_t *returnData = obs_data_create_from_json(QT_TO_UTF8(text));
4464
	string resURL = obs_data_get_string(returnData, "url");
4465
	QString logURL = resURL.c_str();
J
jp9000 已提交
4466 4467 4468 4469 4470 4471
	obs_data_release(returnData);

	OBSLogReply logDialog(this, logURL);
	logDialog.exec();
}

J
jp9000 已提交
4472
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
4473
		obs_source_t *source, const string &name)
4474
{
4475 4476 4477 4478
	const char *prevName = obs_source_get_name(source);
	if (name == prevName)
		return;

4479
	obs_source_t    *foundSource = obs_get_source_by_name(name.c_str());
4480
	QListWidgetItem *listItem    = listWidget->currentItem();
4481

4482
	if (foundSource || name.empty()) {
4483
		listItem->setText(QT_UTF8(prevName));
4484

4485
		if (foundSource) {
4486
			OBSMessageBox::information(parent,
4487 4488 4489
				QTStr("NameExists.Title"),
				QTStr("NameExists.Text"));
		} else if (name.empty()) {
4490
			OBSMessageBox::information(parent,
4491 4492 4493 4494
				QTStr("NoNameEntered.Title"),
				QTStr("NoNameEntered.Text"));
		}

4495 4496 4497
		obs_source_release(foundSource);
	} else {
		listItem->setText(QT_UTF8(name.c_str()));
4498
		obs_source_set_name(source, name.c_str());
4499 4500 4501
	}
}

J
jp9000 已提交
4502 4503 4504 4505 4506
void OBSBasic::SceneNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSScene  scene = GetCurrentScene();
	QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
4507
	string    text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
4508 4509 4510 4511

	if (!scene)
		return;

4512
	obs_source_t *source = obs_scene_get_source(scene);
4513
	RenameListItem(this, ui->scenes, source, text);
J
jp9000 已提交
4514

J
jp9000 已提交
4515 4516 4517
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);

J
jp9000 已提交
4518 4519 4520
	UNUSED_PARAMETER(endHint);
}

J
jp9000 已提交
4521 4522 4523 4524 4525 4526 4527 4528
void OBSBasic::OpenFilters()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
4529 4530 4531 4532 4533 4534 4535 4536
void OBSBasic::OpenSceneFilters()
{
	OBSScene scene = GetCurrentScene();
	OBSSource source = obs_scene_get_source(scene);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
4537 4538 4539 4540
#define RECORDING_START \
	"==== Recording Start ==============================================="
#define RECORDING_STOP \
	"==== Recording Stop ================================================"
J
jp9000 已提交
4541 4542 4543 4544
#define REPLAY_BUFFER_START \
	"==== Replay Buffer Start ==========================================="
#define REPLAY_BUFFER_STOP \
	"==== Replay Buffer Stop ============================================"
J
jp9000 已提交
4545 4546 4547 4548 4549
#define STREAMING_START \
	"==== Streaming Start ==============================================="
#define STREAMING_STOP \
	"==== Streaming Stop ================================================"

4550 4551
void OBSBasic::StartStreaming()
{
J
jp9000 已提交
4552 4553
	if (outputHandler->StreamingActive())
		return;
4554
	if (disableOutputsRef)
4555
		return;
J
jp9000 已提交
4556 4557 4558 4559

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

4560 4561
	SaveProject();

J
jp9000 已提交
4562 4563
	ui->streamButton->setEnabled(false);
	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
4564 4565 4566 4567 4568

	if (sysTrayStream) {
		sysTrayStream->setEnabled(false);
		sysTrayStream->setText(ui->streamButton->text());
	}
4569

J
jp9000 已提交
4570 4571 4572
	if (!outputHandler->StartStreaming(service)) {
		ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
		ui->streamButton->setEnabled(true);
4573
		ui->streamButton->setChecked(false);
4574 4575 4576 4577 4578

		if (sysTrayStream) {
			sysTrayStream->setText(ui->streamButton->text());
			sysTrayStream->setEnabled(true);
		}
4579 4580 4581 4582 4583

		QMessageBox::critical(this,
				QTStr("Output.StartStreamFailed"),
				QTStr("Output.StartFailedGeneric"));
		return;
4584
	}
4585 4586 4587 4588 4589

	bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "RecordWhenStreaming");
	if (recordWhenStreaming)
		StartRecording();
4590 4591 4592 4593 4594

	bool replayBufferWhileStreaming = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "ReplayBufferWhileStreaming");
	if (replayBufferWhileStreaming)
		StartReplayBuffer();
4595 4596
}

4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617
#ifdef _WIN32
static inline void UpdateProcessPriority()
{
	const char *priority = config_get_string(App()->GlobalConfig(),
			"General", "ProcessPriority");
	if (priority && strcmp(priority, "Normal") != 0)
		SetProcessPriority(priority);
}

static inline void ClearProcessPriority()
{
	const char *priority = config_get_string(App()->GlobalConfig(),
			"General", "ProcessPriority");
	if (priority && strcmp(priority, "Normal") != 0)
		SetProcessPriority("Normal");
}
#else
#define UpdateProcessPriority() do {} while(false)
#define ClearProcessPriority() do {} while(false)
#endif

4618
inline void OBSBasic::OnActivate()
4619
{
4620 4621
	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
J
jp9000 已提交
4622
		ui->autoConfigure->setEnabled(false);
4623 4624
		App()->IncrementSleepInhibition();
		UpdateProcessPriority();
C
cg2121 已提交
4625

4626 4627
		if (trayIcon)
			trayIcon->setIcon(QIcon(":/res/images/tray_active.png"));
4628 4629
	}
}
J
jp9000 已提交
4630

4631 4632
inline void OBSBasic::OnDeactivate()
{
4633
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
4634
		ui->profileMenu->setEnabled(true);
J
jp9000 已提交
4635
		ui->autoConfigure->setEnabled(true);
4636
		App()->DecrementSleepInhibition();
4637
		ClearProcessPriority();
C
cg2121 已提交
4638

4639 4640
		if (trayIcon)
			trayIcon->setIcon(QIcon(":/res/images/obs.png"));
J
jp9000 已提交
4641
	}
4642 4643 4644 4645 4646 4647 4648
}

void OBSBasic::StopStreaming()
{
	SaveProject();

	if (outputHandler->StreamingActive())
4649
		outputHandler->StopStreaming(streamingStopping);
4650 4651

	OnDeactivate();
4652 4653 4654 4655 4656 4657 4658

	bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "RecordWhenStreaming");
	bool keepRecordingWhenStreamStops = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "KeepRecordingWhenStreamStops");
	if (recordWhenStreaming && !keepRecordingWhenStreamStops)
		StopRecording();
4659 4660 4661 4662 4663 4664 4665

	bool replayBufferWhileStreaming = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "ReplayBufferWhileStreaming");
	bool keepReplayBufferStreamStops = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "KeepReplayBufferStreamStops");
	if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
		StopReplayBuffer();
4666 4667
}

J
jp9000 已提交
4668 4669 4670 4671 4672
void OBSBasic::ForceStopStreaming()
{
	SaveProject();

	if (outputHandler->StreamingActive())
4673
		outputHandler->StopStreaming(true);
J
jp9000 已提交
4674

4675
	OnDeactivate();
4676 4677 4678 4679 4680 4681 4682

	bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "RecordWhenStreaming");
	bool keepRecordingWhenStreamStops = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "KeepRecordingWhenStreamStops");
	if (recordWhenStreaming && !keepRecordingWhenStreamStops)
		StopRecording();
4683 4684 4685 4686 4687 4688 4689

	bool replayBufferWhileStreaming = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "ReplayBufferWhileStreaming");
	bool keepReplayBufferStreamStops = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "KeepReplayBufferStreamStops");
	if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
		StopReplayBuffer();
J
jp9000 已提交
4690 4691 4692 4693 4694 4695
}

void OBSBasic::StreamDelayStarting(int sec)
{
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
	ui->streamButton->setEnabled(true);
4696
	ui->streamButton->setChecked(true);
4697 4698 4699 4700 4701

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
J
jp9000 已提交
4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713

	if (!startStreamMenu.isNull())
		startStreamMenu->deleteLater();

	startStreamMenu = new QMenu();
	startStreamMenu->addAction(QTStr("Basic.Main.StopStreaming"),
			this, SLOT(StopStreaming()));
	startStreamMenu->addAction(QTStr("Basic.Main.ForceStopStreaming"),
			this, SLOT(ForceStopStreaming()));
	ui->streamButton->setMenu(startStreamMenu);

	ui->statusbar->StreamDelayStarting(sec);
4714

4715
	OnActivate();
J
jp9000 已提交
4716 4717 4718 4719 4720 4721
}

void OBSBasic::StreamDelayStopping(int sec)
{
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
	ui->streamButton->setEnabled(true);
4722
	ui->streamButton->setChecked(false);
4723 4724 4725 4726 4727

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
J
jp9000 已提交
4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741

	if (!startStreamMenu.isNull())
		startStreamMenu->deleteLater();

	startStreamMenu = new QMenu();
	startStreamMenu->addAction(QTStr("Basic.Main.StartStreaming"),
			this, SLOT(StartStreaming()));
	startStreamMenu->addAction(QTStr("Basic.Main.ForceStopStreaming"),
			this, SLOT(ForceStopStreaming()));
	ui->streamButton->setMenu(startStreamMenu);

	ui->statusbar->StreamDelayStopping(sec);
}

4742
void OBSBasic::StreamingStart()
4743
{
4744
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
J
jp9000 已提交
4745
	ui->streamButton->setEnabled(true);
4746
	ui->streamButton->setChecked(true);
J
jp9000 已提交
4747
	ui->statusbar->StreamStarted(outputHandler->streamOutput);
4748 4749 4750 4751 4752

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
4753

J
jp9000 已提交
4754 4755 4756
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTED);

4757
	OnActivate();
4758

4759
	blog(LOG_INFO, STREAMING_START);
4760 4761
}

4762 4763 4764
void OBSBasic::StreamStopping()
{
	ui->streamButton->setText(QTStr("Basic.Main.StoppingStreaming"));
4765 4766 4767

	if (sysTrayStream)
		sysTrayStream->setText(ui->streamButton->text());
J
jp9000 已提交
4768

4769
	streamingStopping = true;
J
jp9000 已提交
4770 4771
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
4772 4773
}

4774
void OBSBasic::StreamingStop(int code, QString last_error)
4775
{
4776 4777 4778
	const char *errorDescription;
	DStr errorMessage;
	bool use_last_error = false;
4779 4780 4781

	switch (code) {
	case OBS_OUTPUT_BAD_PATH:
4782
		errorDescription = Str("Output.ConnectFail.BadPath");
4783 4784 4785
		break;

	case OBS_OUTPUT_CONNECT_FAILED:
4786 4787
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.ConnectFailed");
4788 4789 4790
		break;

	case OBS_OUTPUT_INVALID_STREAM:
4791
		errorDescription = Str("Output.ConnectFail.InvalidStream");
4792 4793
		break;

4794
	default:
4795
	case OBS_OUTPUT_ERROR:
4796 4797
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.Error");
4798 4799 4800 4801 4802
		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 */
4803 4804
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.Disconnected");
4805 4806
	}

4807 4808 4809 4810 4811 4812
	if (use_last_error && !last_error.isEmpty())
		dstr_printf(errorMessage, "%s\n\n%s", errorDescription,
			QT_TO_UTF8(last_error));
	else
		dstr_copy(errorMessage, errorDescription);

J
jp9000 已提交
4813
	ui->statusbar->StreamStopped();
4814

4815
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
J
jp9000 已提交
4816
	ui->streamButton->setEnabled(true);
4817
	ui->streamButton->setChecked(false);
4818 4819 4820 4821 4822

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
4823

4824
	streamingStopping = false;
J
jp9000 已提交
4825 4826 4827
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPED);

4828
	OnDeactivate();
4829 4830

	blog(LOG_INFO, STREAMING_STOP);
J
jp9000 已提交
4831

C
cg2121 已提交
4832
	if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
4833
		OBSMessageBox::information(this,
4834 4835
				QTStr("Output.ConnectFail.Title"),
				QT_UTF8(errorMessage));
C
cg2121 已提交
4836
	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
4837
		SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning);
C
cg2121 已提交
4838
	}
J
jp9000 已提交
4839 4840 4841 4842 4843 4844

	if (!startStreamMenu.isNull()) {
		ui->streamButton->setMenu(nullptr);
		startStreamMenu->deleteLater();
		startStreamMenu = nullptr;
	}
J
jp9000 已提交
4845 4846
}

4847 4848
void OBSBasic::StartRecording()
{
4849 4850
	if (outputHandler->RecordingActive())
		return;
4851
	if (disableOutputsRef)
4852
		return;
4853

J
jp9000 已提交
4854 4855 4856
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING);

4857
	SaveProject();
4858 4859 4860

	if (!outputHandler->StartRecording())
		ui->recordButton->setChecked(false);
4861 4862
}

4863 4864
void OBSBasic::RecordStopping()
{
J
jp9000 已提交
4865
	ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording"));
4866 4867 4868

	if (sysTrayRecord)
		sysTrayRecord->setText(ui->recordButton->text());
J
jp9000 已提交
4869

4870
	recordingStopping = true;
J
jp9000 已提交
4871 4872
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPING);
4873 4874
}

4875 4876 4877 4878 4879
void OBSBasic::StopRecording()
{
	SaveProject();

	if (outputHandler->RecordingActive())
4880
		outputHandler->StopRecording(recordingStopping);
J
jp9000 已提交
4881

4882
	OnDeactivate();
4883 4884
}

P
Palana 已提交
4885 4886
void OBSBasic::RecordingStart()
{
J
jp9000 已提交
4887
	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
J
jp9000 已提交
4888
	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
4889
	ui->recordButton->setChecked(true);
4890 4891 4892

	if (sysTrayRecord)
		sysTrayRecord->setText(ui->recordButton->text());
4893

4894
	recordingStopping = false;
J
jp9000 已提交
4895 4896 4897
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTED);

4898
	OnActivate();
4899

4900
	blog(LOG_INFO, RECORDING_START);
P
Palana 已提交
4901 4902
}

4903
void OBSBasic::RecordingStop(int code)
4904
{
P
Palana 已提交
4905
	ui->statusbar->RecordingStopped();
J
jp9000 已提交
4906
	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
4907
	ui->recordButton->setChecked(false);
4908 4909 4910

	if (sysTrayRecord)
		sysTrayRecord->setText(ui->recordButton->text());
J
jp9000 已提交
4911

4912
	blog(LOG_INFO, RECORDING_STOP);
4913

C
cg2121 已提交
4914
	if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
4915
		OBSMessageBox::information(this,
4916 4917
				QTStr("Output.RecordFail.Title"),
				QTStr("Output.RecordFail.Unsupported"));
J
jp9000 已提交
4918

C
cg2121 已提交
4919
	} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
4920
		OBSMessageBox::information(this,
J
jp9000 已提交
4921 4922 4923
				QTStr("Output.RecordNoSpace.Title"),
				QTStr("Output.RecordNoSpace.Msg"));

C
cg2121 已提交
4924
	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
4925
		OBSMessageBox::information(this,
J
jp9000 已提交
4926 4927
				QTStr("Output.RecordError.Title"),
				QTStr("Output.RecordError.Msg"));
C
cg2121 已提交
4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939

	} else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordFail.Unsupported"),
			QSystemTrayIcon::Warning);

	} else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"),
			QSystemTrayIcon::Warning);

	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordError.Msg"),
			QSystemTrayIcon::Warning);
J
jp9000 已提交
4940 4941
	}

J
jp9000 已提交
4942 4943 4944
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPED);

4945
	OnDeactivate();
4946
}
4947

J
jp9000 已提交
4948 4949 4950 4951 4952 4953 4954 4955 4956
#define RP_NO_HOTKEY_TITLE QTStr("Output.ReplayBuffer.NoHotkey.Title")
#define RP_NO_HOTKEY_TEXT  QTStr("Output.ReplayBuffer.NoHotkey.Msg")

void OBSBasic::StartReplayBuffer()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;
	if (outputHandler->ReplayBufferActive())
		return;
4957
	if (disableOutputsRef)
4958
		return;
J
jp9000 已提交
4959

4960 4961 4962 4963 4964
	if (!NoSourcesConfirmation()) {
		replayBufferButton->setChecked(false);
		return;
	}

J
jp9000 已提交
4965 4966 4967 4968 4969 4970 4971 4972 4973
	obs_output_t *output = outputHandler->replayBuffer;
	obs_data_t *hotkeys = obs_hotkeys_save_output(output);
	obs_data_array_t *bindings = obs_data_get_array(hotkeys,
			"ReplayBuffer.Save");
	size_t count = obs_data_array_count(bindings);
	obs_data_array_release(bindings);
	obs_data_release(hotkeys);

	if (!count) {
4974
		OBSMessageBox::information(this,
J
jp9000 已提交
4975 4976 4977 4978 4979 4980 4981 4982 4983
				RP_NO_HOTKEY_TITLE,
				RP_NO_HOTKEY_TEXT);
		return;
	}

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

	SaveProject();
4984 4985
	if (!outputHandler->StartReplayBuffer())
		replayBufferButton->setChecked(false);
J
jp9000 已提交
4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034
}

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"));

	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);
}

5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048
void OBSBasic::ReplayBufferSave()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;
	if (!outputHandler->ReplayBufferActive())
		return;

	calldata_t cd = {0};
	proc_handler_t *ph = obs_output_get_proc_handler(
			outputHandler->replayBuffer);
	proc_handler_call(ph, "save", &cd);
	calldata_free(&cd);
}

J
jp9000 已提交
5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061
void OBSBasic::ReplayBufferStop(int code)
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

	replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer"));

	if (sysTrayReplayBuffer)
		sysTrayReplayBuffer->setText(replayBufferButton->text());

	blog(LOG_INFO, REPLAY_BUFFER_STOP);

	if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
5062
		OBSMessageBox::information(this,
J
jp9000 已提交
5063 5064 5065 5066
				QTStr("Output.RecordFail.Title"),
				QTStr("Output.RecordFail.Unsupported"));

	} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
5067
		OBSMessageBox::information(this,
J
jp9000 已提交
5068 5069 5070 5071
				QTStr("Output.RecordNoSpace.Title"),
				QTStr("Output.RecordNoSpace.Msg"));

	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
5072
		OBSMessageBox::information(this,
J
jp9000 已提交
5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094
				QTStr("Output.RecordError.Title"),
				QTStr("Output.RecordError.Msg"));

	} else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordFail.Unsupported"),
			QSystemTrayIcon::Warning);

	} else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"),
			QSystemTrayIcon::Warning);

	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
		SysTrayNotify(QTStr("Output.RecordError.Msg"),
			QSystemTrayIcon::Warning);
	}

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

	OnDeactivate();
}

5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116
bool OBSBasic::NoSourcesConfirmation()
{
	if (CountVideoSources() == 0 && isVisible()) {
		QString msg;
		msg = QTStr("NoSources.Text");
		msg += "\n\n";
		msg += QTStr("NoSources.Text.AddSource");

		QMessageBox messageBox(QMessageBox::Question,
				QTStr("NoSources.title"),
				msg,
				QMessageBox::Yes | QMessageBox::No,
				this);
		messageBox.setDefaultButton(QMessageBox::No);

		if (QMessageBox::No == messageBox.exec())
			return false;
	}

	return true;
}

5117 5118
void OBSBasic::on_streamButton_clicked()
{
J
jp9000 已提交
5119
	if (outputHandler->StreamingActive()) {
5120 5121 5122
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
				"WarnBeforeStoppingStream");

C
cg2121 已提交
5123
		if (confirm && isVisible()) {
5124
			QMessageBox::StandardButton button =
5125
				OBSMessageBox::question(this,
5126 5127 5128
						QTStr("ConfirmStop.Title"),
						QTStr("ConfirmStop.Text"));

C
cg2121 已提交
5129 5130
			if (button == QMessageBox::No) {
				ui->streamButton->setChecked(true);
5131
				return;
C
cg2121 已提交
5132
			}
5133 5134
		}

5135
		StopStreaming();
5136
	} else {
5137 5138 5139 5140 5141
		if (!NoSourcesConfirmation()) {
			ui->streamButton->setChecked(false);
			return;
		}

5142 5143 5144
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
				"WarnBeforeStartingStream");

C
cg2121 已提交
5145
		if (confirm && isVisible()) {
5146
			QMessageBox::StandardButton button =
5147
				OBSMessageBox::question(this,
5148 5149 5150
						QTStr("ConfirmStart.Title"),
						QTStr("ConfirmStart.Text"));

C
cg2121 已提交
5151 5152
			if (button == QMessageBox::No) {
				ui->streamButton->setChecked(false);
5153
				return;
C
cg2121 已提交
5154
			}
5155 5156
		}

5157
		StartStreaming();
5158 5159 5160 5161 5162
	}
}

void OBSBasic::on_recordButton_clicked()
{
5163
	if (outputHandler->RecordingActive()) {
5164
		StopRecording();
5165 5166 5167 5168 5169 5170
	} else {
		if (!NoSourcesConfirmation()) {
			ui->recordButton->setChecked(false);
			return;
		}

5171
		StartRecording();
5172
	}
J
jp9000 已提交
5173 5174
}

J
jp9000 已提交
5175
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
5176
{
J
jp9000 已提交
5177
	on_action_Settings_triggered();
J
jp9000 已提交
5178
}
5179

5180 5181 5182 5183 5184 5185
void OBSBasic::on_actionHelpPortal_triggered()
{
	QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

5186 5187 5188 5189 5190 5191
void OBSBasic::on_actionWebsite_triggered()
{
	QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211
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 已提交
5212
int OBSBasic::GetTopSelectedSourceItem()
5213
{
J
jp9000 已提交
5214 5215 5216
	QModelIndexList selectedItems =
		ui->sources->selectionModel()->selectedIndexes();
	return selectedItems.count() ? selectedItems[0].row() : -1;
5217 5218
}

J
jp9000 已提交
5219 5220
void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos)
{
5221
	CreateSourcePopupMenu(GetTopSelectedSourceItem(), true);
J
jp9000 已提交
5222 5223 5224 5225

	UNUSED_PARAMETER(pos);
}

R
Ryan Foster 已提交
5226 5227 5228 5229 5230 5231 5232 5233
void OBSBasic::on_program_customContextMenuRequested(const QPoint&)
{
	QMenu popup(this);
	QPointer<QMenu> studioProgramProjector;

	studioProgramProjector = new QMenu(
			QTStr("StudioProgramProjector"));
	AddProjectorMenuMonitors(studioProgramProjector, this,
5234
			SLOT(OpenStudioProgramProjector()));
R
Ryan Foster 已提交
5235 5236 5237 5238 5239

	popup.addMenu(studioProgramProjector);

	QAction *studioProgramWindow = popup.addAction(
			QTStr("StudioProgramWindow"),
5240
			this, SLOT(OpenStudioProgramWindow()));
R
Ryan Foster 已提交
5241 5242 5243 5244 5245 5246

	popup.addAction(studioProgramWindow);

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

J
jp9000 已提交
5247 5248 5249 5250
void OBSBasic::on_previewDisabledLabel_customContextMenuRequested(
		const QPoint &pos)
{
	QMenu popup(this);
J
jp9000 已提交
5251
	QPointer<QMenu> previewProjector;
J
jp9000 已提交
5252 5253 5254 5255 5256

	QAction *action = popup.addAction(
			QTStr("Basic.Main.PreviewConextMenu.Enable"),
			this, SLOT(TogglePreview()));
	action->setCheckable(true);
5257
	action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
J
jp9000 已提交
5258

J
jp9000 已提交
5259 5260 5261 5262
	previewProjector = new QMenu(QTStr("PreviewProjector"));
	AddProjectorMenuMonitors(previewProjector, this,
			SLOT(OpenPreviewProjector()));

C
cg2121 已提交
5263 5264 5265 5266
	QAction *previewWindow = popup.addAction(
			QTStr("PreviewWindow"),
			this, SLOT(OpenPreviewWindow()));

J
jp9000 已提交
5267
	popup.addMenu(previewProjector);
C
cg2121 已提交
5268
	popup.addAction(previewWindow);
J
jp9000 已提交
5269 5270 5271 5272 5273
	popup.exec(QCursor::pos());

	UNUSED_PARAMETER(pos);
}

5274 5275
void OBSBasic::on_actionAlwaysOnTop_triggered()
{
5276
#ifndef _WIN32
5277 5278 5279 5280
	/* 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 */
5281 5282 5283
	CloseDialogs();
#endif

5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297
	QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop",
			Qt::QueuedConnection);
}

void OBSBasic::ToggleAlwaysOnTop()
{
	bool isAlwaysOnTop = IsAlwaysOnTop(this);

	ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop);
	SetAlwaysOnTop(this, !isAlwaysOnTop);

	show();
}

5298 5299 5300 5301 5302 5303 5304 5305 5306 5307
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 已提交
5308 5309 5310
	} else if (strcmp(val, "24 NTSC") == 0) {
		num = 24000;
		den = 1001;
5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363
	} else if (strcmp(val, "25") == 0) {
		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;
	} 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);
}

5364
config_t *OBSBasic::Config() const
5365 5366 5367
{
	return basicConfig;
}
J
jp9000 已提交
5368 5369 5370

void OBSBasic::on_actionEditTransform_triggered()
{
5371 5372 5373
	if (transformWindow)
		transformWindow->close();

J
jp9000 已提交
5374 5375
	transformWindow = new OBSBasicTransform(this);
	transformWindow->show();
5376
	transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
J
jp9000 已提交
5377 5378
}

5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422
static obs_transform_info copiedTransformInfo;
static obs_sceneitem_crop copiedCropInfo;

void OBSBasic::on_actionCopyTransform_triggered()
{
	auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param)
	{
		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()
{
	auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param)
	{
		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);
}

J
jp9000 已提交
5423 5424
void OBSBasic::on_actionResetTransform_triggered()
{
5425
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
5426 5427 5428 5429
	{
		if (!obs_sceneitem_selected(item))
			return true;

J
jp9000 已提交
5430 5431
		obs_sceneitem_defer_update_begin(item);

5432
		obs_transform_info info;
J
jp9000 已提交
5433 5434 5435 5436 5437 5438 5439 5440 5441
		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 已提交
5442 5443 5444 5445 5446
		obs_sceneitem_crop crop = {};
		obs_sceneitem_set_crop(item, &crop);

		obs_sceneitem_defer_update_end(item);

J
jp9000 已提交
5447 5448 5449 5450 5451 5452 5453 5454
		UNUSED_PARAMETER(scene);
		UNUSED_PARAMETER(param);
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
}

5455
static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
J
jp9000 已提交
5456 5457 5458 5459 5460
{
	matrix4 boxTransform;
	obs_sceneitem_get_box_transform(item, &boxTransform);

	vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
5461
	vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
J
jp9000 已提交
5462

5463
	auto GetMinPos = [&] (float x, float y)
J
jp9000 已提交
5464 5465 5466 5467
	{
		vec3 pos;
		vec3_set(&pos, x, y, 0.0f);
		vec3_transform(&pos, &pos, &boxTransform);
5468 5469
		vec3_min(&tl, &tl, &pos);
		vec3_max(&br, &br, &pos);
J
jp9000 已提交
5470 5471
	};

5472 5473 5474 5475 5476 5477
	GetMinPos(0.0f, 0.0f);
	GetMinPos(1.0f, 0.0f);
	GetMinPos(0.0f, 1.0f);
	GetMinPos(1.0f, 1.0f);
}

5478
static vec3 GetItemTL(obs_sceneitem_t *item)
5479 5480 5481
{
	vec3 tl, br;
	GetItemBox(item, tl, br);
J
jp9000 已提交
5482 5483 5484
	return tl;
}

5485
static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
J
jp9000 已提交
5486 5487 5488 5489
{
	vec3 newTL;
	vec2 pos;

J
jp9000 已提交
5490
	obs_sceneitem_get_pos(item, &pos);
J
jp9000 已提交
5491 5492 5493
	newTL = GetItemTL(item);
	pos.x += tl.x - newTL.x;
	pos.y += tl.y - newTL.y;
J
jp9000 已提交
5494
	obs_sceneitem_set_pos(item, &pos);
J
jp9000 已提交
5495 5496
}

5497
static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
5498 5499 5500 5501 5502 5503 5504 5505 5506
		void *param)
{
	if (!obs_sceneitem_selected(item))
		return true;

	float rot = *reinterpret_cast<float*>(param);

	vec3 tl = GetItemTL(item);

J
jp9000 已提交
5507
	rot += obs_sceneitem_get_rot(item);
J
jp9000 已提交
5508 5509
	if (rot >= 360.0f)       rot -= 360.0f;
	else if (rot <= -360.0f) rot += 360.0f;
J
jp9000 已提交
5510
	obs_sceneitem_set_rot(item, rot);
J
jp9000 已提交
5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536

	SetItemTL(item, tl);

	UNUSED_PARAMETER(scene);
	UNUSED_PARAMETER(param);
	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);
}

5537
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
5538 5539 5540 5541 5542 5543 5544 5545 5546 5547
		void *param)
{
	vec2 &mul = *reinterpret_cast<vec2*>(param);

	if (!obs_sceneitem_selected(item))
		return true;

	vec3 tl = GetItemTL(item);

	vec2 scale;
J
jp9000 已提交
5548
	obs_sceneitem_get_scale(item, &scale);
J
jp9000 已提交
5549
	vec2_mul(&scale, &scale, &mul);
J
jp9000 已提交
5550
	obs_sceneitem_set_scale(item, &scale);
J
jp9000 已提交
5551 5552

	SetItemTL(item, tl);
J
jp9000 已提交
5553 5554

	UNUSED_PARAMETER(scene);
J
jp9000 已提交
5555 5556 5557 5558 5559
	return true;
}

void OBSBasic::on_actionFlipHorizontal_triggered()
{
J
jp9000 已提交
5560 5561
	vec2 scale;
	vec2_set(&scale, -1.0f, 1.0f);
J
jp9000 已提交
5562 5563 5564 5565 5566 5567
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

void OBSBasic::on_actionFlipVertical_triggered()
{
J
jp9000 已提交
5568 5569
	vec2 scale;
	vec2_set(&scale, 1.0f, -1.0f);
J
jp9000 已提交
5570 5571 5572 5573
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

5574
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
5575 5576 5577 5578 5579 5580 5581 5582 5583 5584
		void *param)
{
	obs_bounds_type boundsType = *reinterpret_cast<obs_bounds_type*>(param);

	if (!obs_sceneitem_selected(item))
		return true;

	obs_video_info ovi;
	obs_get_video_info(&ovi);

5585
	obs_transform_info itemInfo;
J
jp9000 已提交
5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617
	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;

	vec2_set(&itemInfo.bounds,
			float(ovi.base_width), float(ovi.base_height));
	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,
			&boundsType);
}

void OBSBasic::on_actionStretchToScreen_triggered()
{
	obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
	obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems,
			&boundsType);
}

void OBSBasic::on_actionCenterToScreen_triggered()
{
5618
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643
	{
		vec3 tl, br, itemCenter, screenCenter, offset;
		obs_video_info ovi;

		if (!obs_sceneitem_selected(item))
			return true;

		obs_get_video_info(&ovi);

		vec3_set(&screenCenter, float(ovi.base_width),
				float(ovi.base_height), 0.0f);
		vec3_mulf(&screenCenter, &screenCenter, 0.5f);

		GetItemBox(item, tl, br);

		vec3_sub(&itemCenter, &br, &tl);
		vec3_mulf(&itemCenter, &itemCenter, 0.5f);
		vec3_add(&itemCenter, &itemCenter, &tl);

		vec3_sub(&offset, &screenCenter, &itemCenter);
		vec3_add(&tl, &tl, &offset);

		SetItemTL(item, tl);

		UNUSED_PARAMETER(scene);
J
jp9000 已提交
5644
		UNUSED_PARAMETER(param);
5645 5646 5647 5648
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
J
jp9000 已提交
5649
}
J
jp9000 已提交
5650

5651 5652 5653 5654 5655 5656 5657
void OBSBasic::EnablePreviewDisplay(bool enable)
{
	obs_display_set_enabled(ui->preview->GetDisplay(), enable);
	ui->preview->setVisible(enable);
	ui->previewDisabledLabel->setVisible(!enable);
}

J
jp9000 已提交
5658 5659
void OBSBasic::TogglePreview()
{
5660 5661
	previewEnabled = !previewEnabled;
	EnablePreviewDisplay(previewEnabled);
J
jp9000 已提交
5662
}
5663

J
jp9000 已提交
5664
static bool nudge_callback(obs_scene_t*, obs_sceneitem_t *item, void *param)
5665
{
J
jp9000 已提交
5666 5667
	if (obs_sceneitem_locked(item))
		return true;
5668

J
jp9000 已提交
5669 5670
	struct vec2 &offset = *reinterpret_cast<struct vec2*>(param);
	struct vec2 pos;
5671

J
jp9000 已提交
5672
	if (!obs_sceneitem_selected(item)) {
J
jp9000 已提交
5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688
		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,
					&new_offset);
		}

J
jp9000 已提交
5689 5690
		return true;
	}
5691

J
jp9000 已提交
5692 5693 5694 5695 5696
	obs_sceneitem_get_pos(item, &pos);
	vec2_add(&pos, &pos, &offset);
	obs_sceneitem_set_pos(item, &pos);
	return true;
}
J
jp9000 已提交
5697

J
jp9000 已提交
5698 5699 5700 5701
void OBSBasic::Nudge(int dist, MoveDir dir)
{
	if (ui->preview->Locked())
		return;
5702

J
jp9000 已提交
5703 5704
	struct vec2 offset;
	vec2_set(&offset, 0.0f, 0.0f);
5705

J
jp9000 已提交
5706 5707 5708 5709 5710 5711
	switch (dir) {
	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;
	}
5712

J
jp9000 已提交
5713
	obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset);
5714 5715 5716 5717 5718 5719
}

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 已提交
5720

S
Shaolin 已提交
5721
OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor,
S
Shaolin 已提交
5722
		QString title, ProjectorType type)
J
jp9000 已提交
5723 5724
{
	/* seriously?  10 monitors? */
C
cg2121 已提交
5725
	if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1)
S
Shaolin 已提交
5726
		return nullptr;
C
cg2121 已提交
5727

S
Shaolin 已提交
5728 5729
	OBSProjector *projector = new OBSProjector(nullptr, source, monitor,
			title, type);
C
cg2121 已提交
5730

S
Shaolin 已提交
5731
	if (monitor < 0) {
C
cg2121 已提交
5732 5733 5734 5735 5736 5737 5738 5739 5740
		for (auto &projPtr : windowProjectors) {
			if (!projPtr) {
				projPtr = projector;
				projector = nullptr;
			}
		}

		if (projector)
			windowProjectors.push_back(projector);
S
Shaolin 已提交
5741
	} else {
C
cg2121 已提交
5742 5743
		delete projectors[monitor];
		projectors[monitor].clear();
C
cg2121 已提交
5744

C
cg2121 已提交
5745 5746
		projectors[monitor] = projector;
	}
S
Shaolin 已提交
5747 5748

	return projector;
J
jp9000 已提交
5749 5750
}

R
Ryan Foster 已提交
5751 5752 5753
void OBSBasic::OpenStudioProgramProjector()
{
	int monitor = sender()->property("monitor").toInt();
5754
	OpenProjector(nullptr, monitor, nullptr, ProjectorType::StudioProgram);
R
Ryan Foster 已提交
5755 5756
}

J
jp9000 已提交
5757 5758 5759
void OBSBasic::OpenPreviewProjector()
{
	int monitor = sender()->property("monitor").toInt();
5760
	OpenProjector(nullptr, monitor, nullptr, ProjectorType::Preview);
J
jp9000 已提交
5761 5762 5763 5764 5765 5766 5767 5768 5769
}

void OBSBasic::OpenSourceProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;

S
Shaolin 已提交
5770 5771
	OpenProjector(obs_sceneitem_get_source(item), monitor, nullptr,
			ProjectorType::Source);
J
jp9000 已提交
5772 5773
}

S
Shaolin 已提交
5774 5775 5776
void OBSBasic::OpenMultiviewProjector()
{
	int monitor = sender()->property("monitor").toInt();
5777
	OpenProjector(nullptr, monitor, nullptr, ProjectorType::Multiview);
S
Shaolin 已提交
5778 5779
}

J
jp9000 已提交
5780 5781 5782 5783 5784 5785 5786
void OBSBasic::OpenSceneProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OBSScene scene = GetCurrentScene();
	if (!scene)
		return;

5787 5788
	OpenProjector(obs_scene_get_source(scene), monitor, nullptr,
			ProjectorType::Scene);
C
cg2121 已提交
5789 5790
}

R
Ryan Foster 已提交
5791 5792
void OBSBasic::OpenStudioProgramWindow()
{
S
Shaolin 已提交
5793
	OpenProjector(nullptr, -1, QTStr("StudioProgramWindow"),
S
Shaolin 已提交
5794
			ProjectorType::StudioProgram);
R
Ryan Foster 已提交
5795 5796
}

C
cg2121 已提交
5797 5798
void OBSBasic::OpenPreviewWindow()
{
S
Shaolin 已提交
5799 5800
	OpenProjector(nullptr, -1, QTStr("PreviewWindow"),
			ProjectorType::Preview);
C
cg2121 已提交
5801 5802 5803 5804 5805 5806 5807 5808 5809
}

void OBSBasic::OpenSourceWindow()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;

	OBSSource source = obs_sceneitem_get_source(item);
5810
	QString title = QString::fromUtf8(obs_source_get_name(source));
S
Shaolin 已提交
5811 5812 5813

	OpenProjector(obs_sceneitem_get_source(item), -1, title,
			ProjectorType::Source);
C
cg2121 已提交
5814 5815
}

S
Shaolin 已提交
5816 5817
void OBSBasic::OpenMultiviewWindow()
{
S
Shaolin 已提交
5818
	OpenProjector(nullptr, -1, QTStr("MultiviewWindowed"),
S
Shaolin 已提交
5819 5820 5821
			ProjectorType::Multiview);
}

C
cg2121 已提交
5822 5823 5824 5825 5826 5827 5828
void OBSBasic::OpenSceneWindow()
{
	OBSScene scene = GetCurrentScene();
	if (!scene)
		return;

	OBSSource source = obs_scene_get_source(scene);
5829
	QString title = QString::fromUtf8(obs_source_get_name(source));
S
Shaolin 已提交
5830 5831 5832

	OpenProjector(obs_scene_get_source(scene), -1, title,
			ProjectorType::Scene);
J
jp9000 已提交
5833
}
5834

C
cg2121 已提交
5835 5836
void OBSBasic::OpenSavedProjectors()
{
5837
	for (SavedProjectorInfo *info : savedProjectorsArray) {
S
Shaolin 已提交
5838
		OBSProjector *projector = nullptr;
5839
		switch (info->type) {
S
Shaolin 已提交
5840
		case ProjectorType::Source:
5841 5842 5843 5844 5845
		case ProjectorType::Scene: {
			OBSSource source = obs_get_source_by_name(
					info->name.c_str());
			if (!source)
				continue;
C
cg2121 已提交
5846

S
Shaolin 已提交
5847 5848 5849 5850
			QString title = nullptr;
			if (info->monitor < 0)
				title = QString::fromUtf8(
						obs_source_get_name(source));
R
Ryan Foster 已提交
5851

S
Shaolin 已提交
5852 5853
			projector = OpenProjector(source, info->monitor, title,
					info->type);
5854 5855 5856

			obs_source_release(source);
			break;
S
Shaolin 已提交
5857
		}
5858
		case ProjectorType::Preview: {
S
Shaolin 已提交
5859
			projector = OpenProjector(nullptr, info->monitor,
5860
					QTStr("PreviewWindow"),
5861
					ProjectorType::Preview);
5862
			break;
S
Shaolin 已提交
5863
		}
5864
		case ProjectorType::StudioProgram: {
S
Shaolin 已提交
5865
			projector = OpenProjector(nullptr, info->monitor,
5866 5867 5868 5869 5870
					QTStr("StudioProgramWindow"),
					ProjectorType::StudioProgram);
			break;
		}
		case ProjectorType::Multiview: {
S
Shaolin 已提交
5871
			projector = OpenProjector(nullptr, info->monitor,
5872
					QTStr("MultiviewWindowed"),
5873
					ProjectorType::Multiview);
5874 5875
			break;
		}
C
cg2121 已提交
5876
		}
S
Shaolin 已提交
5877

5878
		if (projector && !info->geometry.empty()) {
S
Shaolin 已提交
5879 5880 5881
			QByteArray byteArray = QByteArray::fromBase64(
					QByteArray(info->geometry.c_str()));
			projector->restoreGeometry(byteArray);
S
Shaolin 已提交
5882

S
Shaolin 已提交
5883 5884 5885 5886 5887
			if (!WindowPositionValid(projector->normalGeometry())) {
				QRect rect = App()->desktop()->geometry();
				projector->setGeometry(QStyle::alignedRect(
						Qt::LeftToRight,
						Qt::AlignCenter, size(), rect));
C
cg2121 已提交
5888 5889 5890 5891 5892
			}
		}
	}
}

5893 5894 5895 5896 5897 5898 5899 5900 5901 5902
void OBSBasic::on_actionFullscreenInterface_triggered()
{
	if (!fullscreenInterface)
		showFullScreen();
	else
		showNormal();

	fullscreenInterface = !fullscreenInterface;
}

5903 5904 5905 5906
void OBSBasic::UpdateTitleBar()
{
	stringstream name;

J
jp9000 已提交
5907 5908
	const char *profile = config_get_string(App()->GlobalConfig(),
			"Basic", "Profile");
J
jp9000 已提交
5909 5910 5911
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

5912 5913 5914 5915 5916
	name << "OBS ";
	if (previewProgramMode)
		name << "Studio ";

	name << App()->GetVersionString();
5917 5918 5919
	if (App()->IsPortableMode())
		name << " - Portable Mode";

J
jp9000 已提交
5920
	name << " - " << Str("TitleBar.Profile") << ": " << profile;
J
jp9000 已提交
5921
	name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;
5922 5923 5924

	setWindowTitle(QT_UTF8(name.str().c_str()));
}
J
jp9000 已提交
5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948

int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const
{
	char profiles_path[512];
	const char *profile = config_get_string(App()->GlobalConfig(),
			"Basic", "ProfileDir");
	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);
}
5949

J
jp9000 已提交
5950
void OBSBasic::on_resetUI_triggered()
5951
{
J
jp9000 已提交
5952
	restoreState(startingDockLayout);
5953

5954
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
J
jp9000 已提交
5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988
	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);

	QList<QDockWidget*> docks {
		ui->scenesDock,
		ui->sourcesDock,
		ui->mixerDock,
		ui->transitionsDock,
		ui->controlsDock
	};

	QList<int> sizes {
		cx22_5,
		cx22_5,
		mixerSize,
		cx5,
		cx5
	};

	ui->scenesDock->setVisible(true);
	ui->sourcesDock->setVisible(true);
	ui->mixerDock->setVisible(true);
	ui->transitionsDock->setVisible(true);
	ui->controlsDock->setVisible(true);

	resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical);
	resizeDocks(docks, sizes, Qt::Horizontal);
5989
#endif
J
jp9000 已提交
5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002
}

void OBSBasic::on_lockUI_toggled(bool lock)
{
	QDockWidget::DockWidgetFeatures features = lock
		? QDockWidget::NoDockWidgetFeatures
		: QDockWidget::AllDockWidgetFeatures;

	ui->scenesDock->setFeatures(features);
	ui->sourcesDock->setFeatures(features);
	ui->mixerDock->setFeatures(features);
	ui->transitionsDock->setFeatures(features);
	ui->controlsDock->setFeatures(features);
6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020
}

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);

	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"ShowStatusBar", visible);
}
J
jp9000 已提交
6021 6022 6023 6024 6025 6026

void OBSBasic::on_actionLockPreview_triggered()
{
	ui->preview->ToggleLocked();
	ui->actionLockPreview->setChecked(ui->preview->Locked());
}
C
cg2121 已提交
6027

J
Joseph El-Khouri 已提交
6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043
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);
6044 6045
	action->setVisible(!(ovi.output_width == ovi.base_width &&
			ovi.output_height == ovi.base_height));
J
Joseph El-Khouri 已提交
6046 6047 6048 6049 6050 6051

	UpdatePreviewScalingMenu();
}

void OBSBasic::on_actionScaleWindow_triggered()
{
6052
	ui->preview->SetFixedScaling(false);
J
Joseph El-Khouri 已提交
6053 6054 6055 6056 6057 6058
	ui->preview->ResetScrollingOffset();
	emit ui->preview->DisplayResized();
}

void OBSBasic::on_actionScaleCanvas_triggered()
{
6059 6060
	ui->preview->SetFixedScaling(true);
	ui->preview->SetScalingLevel(0);
J
Joseph El-Khouri 已提交
6061 6062 6063 6064 6065
	emit ui->preview->DisplayResized();
}

void OBSBasic::on_actionScaleOutput_triggered()
{
6066 6067 6068 6069 6070 6071 6072 6073 6074 6075
	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)
	int32_t approxScalingLevel = int32_t(
			round(log(scalingAmount) / log(ZOOM_SENSITIVITY)));
	ui->preview->SetScalingLevel(approxScalingLevel);
	ui->preview->SetScalingAmount(scalingAmount);
J
Joseph El-Khouri 已提交
6076 6077 6078
	emit ui->preview->DisplayResized();
}

C
cg2121 已提交
6079 6080 6081 6082 6083 6084 6085
void OBSBasic::SetShowing(bool showing)
{
	if (!showing && isVisible()) {
		config_set_string(App()->GlobalConfig(),
			"BasicWindow", "geometry",
			saveGeometry().toBase64().constData());

6086 6087 6088 6089 6090 6091 6092 6093 6094
		/* hide all visible child dialogs */
		visDlgPositions.clear();
		if (!visDialogs.isEmpty()) {
			for (QDialog *dlg : visDialogs) {
				visDlgPositions.append(dlg->pos());
				dlg->hide();
			}
		}

6095 6096
		if (showHide)
			showHide->setText(QTStr("Basic.SystemTray.Show"));
C
cg2121 已提交
6097 6098 6099 6100 6101 6102 6103 6104
		QTimer::singleShot(250, this, SLOT(hide()));

		if (previewEnabled)
			EnablePreviewDisplay(false);

		setVisible(false);

	} else if (showing && !isVisible()) {
6105 6106
		if (showHide)
			showHide->setText(QTStr("Basic.SystemTray.Hide"));
C
cg2121 已提交
6107 6108 6109 6110 6111 6112
		QTimer::singleShot(250, this, SLOT(show()));

		if (previewEnabled)
			EnablePreviewDisplay(true);

		setVisible(true);
6113

6114 6115 6116 6117 6118 6119 6120 6121 6122
		/* 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();
			}
		}

6123 6124 6125 6126 6127 6128 6129 6130
		/* Unminimize window if it was hidden to tray instead of task
		 * bar. */
		if (sysTrayMinimizeToTray()) {
			Qt::WindowStates state;
			state  = windowState() & ~Qt::WindowMinimized;
			state |= Qt::WindowActive;
			setWindowState(state);
		}
C
cg2121 已提交
6131 6132 6133
	}
}

6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145
void OBSBasic::ToggleShowHide()
{
	bool showing = isVisible();
	if (showing) {
		/* check for modal dialogs */
		EnumDialogs();
		if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty())
			return;
	}
	SetShowing(!showing);
}

6146 6147
void OBSBasic::SystemTrayInit()
{
6148 6149
	trayIcon.reset(new QSystemTrayIcon(QIcon(":/res/images/obs.png"),
			this));
C
cg2121 已提交
6150 6151 6152
	trayIcon->setToolTip("OBS Studio");

	showHide = new QAction(QTStr("Basic.SystemTray.Show"),
6153
			trayIcon.data());
C
cg2121 已提交
6154
	sysTrayStream = new QAction(QTStr("Basic.Main.StartStreaming"),
6155
			trayIcon.data());
C
cg2121 已提交
6156
	sysTrayRecord = new QAction(QTStr("Basic.Main.StartRecording"),
6157
			trayIcon.data());
J
jp9000 已提交
6158
	sysTrayReplayBuffer = new QAction(QTStr("Basic.Main.StartReplayBuffer"),
6159
			trayIcon.data());
C
cg2121 已提交
6160
	exit = new QAction(QTStr("Exit"),
6161
			trayIcon.data());
C
cg2121 已提交
6162

J
jp9000 已提交
6163 6164
	if (outputHandler && !outputHandler->replayBuffer)
		sysTrayReplayBuffer->setEnabled(false);
6165

6166 6167
	connect(trayIcon.data(),
			SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
C
cg2121 已提交
6168 6169 6170 6171 6172 6173 6174 6175
			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()));
6176
	connect(sysTrayReplayBuffer.data(), &QAction::triggered,
J
jp9000 已提交
6177
			this, &OBSBasic::ReplayBufferClicked);
C
cg2121 已提交
6178 6179 6180 6181 6182 6183 6184 6185
	connect(exit, SIGNAL(triggered()),
			this, SLOT(close()));

	trayMenu = new QMenu;
}

void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason)
{
6186
	if (reason == QSystemTrayIcon::Trigger) {
C
cg2121 已提交
6187
		ToggleShowHide();
6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206
	} else if (reason == QSystemTrayIcon::Context) {
		QMenu *previewProjector = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjector, this,
				SLOT(OpenPreviewProjector()));
		QMenu *studioProgramProjector = new QMenu(
				QTStr("StudioProgramProjector"));
		AddProjectorMenuMonitors(studioProgramProjector, this,
				SLOT(OpenStudioProgramProjector()));

		trayMenu->clear();
		trayMenu->addAction(showHide);
		trayMenu->addMenu(previewProjector);
		trayMenu->addMenu(studioProgramProjector);
		trayMenu->addAction(sysTrayStream);
		trayMenu->addAction(sysTrayRecord);
		trayMenu->addAction(sysTrayReplayBuffer);
		trayMenu->addAction(exit);
		trayMenu->popup(QCursor::pos());
	}
C
cg2121 已提交
6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233
}

void OBSBasic::SysTrayNotify(const QString &text,
		QSystemTrayIcon::MessageIcon n)
{
	if (QSystemTrayIcon::supportsMessages()) {
		QSystemTrayIcon::MessageIcon icon =
				QSystemTrayIcon::MessageIcon(n);
		trayIcon->showMessage("OBS Studio", text, icon, 10000);
	}
}

void OBSBasic::SystemTray(bool firstStarted)
{
	if (!QSystemTrayIcon::isSystemTrayAvailable())
		return;

	bool sysTrayWhenStarted = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SysTrayWhenStarted");
	bool sysTrayEnabled = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SysTrayEnabled");

	if (firstStarted)
		SystemTrayInit();

	if (!sysTrayWhenStarted && !sysTrayEnabled) {
		trayIcon->hide();
C
cg2121 已提交
6234 6235
	} else if ((sysTrayWhenStarted && sysTrayEnabled)
			|| opt_minimize_tray) {
C
cg2121 已提交
6236 6237 6238 6239 6240
		trayIcon->show();
		if (firstStarted) {
			QTimer::singleShot(50, this, SLOT(hide()));
			EnablePreviewDisplay(false);
			setVisible(false);
C
cg2121 已提交
6241
			opt_minimize_tray = false;
C
cg2121 已提交
6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255
		}
	} 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"));
}
6256 6257 6258 6259 6260 6261

bool OBSBasic::sysTrayMinimizeToTray()
{
	return config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SysTrayMinimizeToTray");
}
6262 6263 6264 6265 6266 6267

void OBSBasic::on_actionCopySource_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;
J
jp9000 已提交
6268 6269

	on_actionCopyTransform_triggered();
6270 6271 6272 6273 6274 6275 6276

	OBSSource source = obs_sceneitem_get_source(item);

	copyString = obs_source_get_name(source);
	copyVisible = obs_sceneitem_visible(item);

	ui->actionPasteRef->setEnabled(true);
6277 6278 6279 6280 6281 6282

	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);
6283 6284 6285 6286
}

void OBSBasic::on_actionPasteRef_triggered()
{
J
jp9000 已提交
6287 6288 6289 6290 6291
	/* do not allow duplicate refs of the same group in the same scene */
	OBSScene scene = GetCurrentScene();
	if (!!obs_scene_get_group(scene, copyString))
		return;

6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318
	OBSBasicSourceSelect::SourcePaste(copyString, copyVisible, false);
	on_actionPasteTransform_triggered();
}

void OBSBasic::on_actionPasteDup_triggered()
{
	OBSBasicSourceSelect::SourcePaste(copyString, copyVisible, true);
	on_actionPasteTransform_triggered();
}

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);
6319
	obs_source_release(source);
6320

6321
	OBSSceneItem sceneItem = GetCurrentSceneItem();
6322 6323 6324 6325 6326 6327 6328
	OBSSource dstSource = obs_sceneitem_get_source(sceneItem);

	if (source == dstSource)
		return;

	obs_source_copy_filters(dstSource, source);
}
J
jp9000 已提交
6329 6330 6331 6332 6333 6334 6335 6336

void OBSBasic::on_autoConfigure_triggered()
{
	AutoConfig test(this);
	test.setModal(true);
	test.show();
	test.exec();
}
J
jp9000 已提交
6337 6338 6339

void OBSBasic::on_stats_triggered()
{
6340 6341 6342 6343 6344 6345
	if (!stats.isNull()) {
		stats->show();
		stats->raise();
		return;
	}

J
jp9000 已提交
6346 6347 6348 6349 6350
	OBSBasicStats *statsDlg;
	statsDlg = new OBSBasicStats(nullptr);
	statsDlg->show();
	stats = statsDlg;
}