window-basic-main.cpp 105.1 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>
J
jp9000 已提交
22
#include <QMessageBox>
23
#include <QShowEvent>
24
#include <QDesktopServices>
J
jp9000 已提交
25
#include <QFileDialog>
26

J
jp9000 已提交
27
#include <util/dstr.h>
28 29
#include <util/util.hpp>
#include <util/platform.h>
P
Palana 已提交
30
#include <util/profiler.hpp>
J
jp9000 已提交
31
#include <graphics/math-defs.h>
32

33
#include "obs-app.hpp"
34
#include "platform.hpp"
35
#include "visibility-item-widget.hpp"
36
#include "item-widget-helpers.hpp"
37
#include "window-basic-settings.hpp"
38
#include "window-namedialog.hpp"
J
jp9000 已提交
39
#include "window-basic-source-select.hpp"
J
jp9000 已提交
40
#include "window-basic-main.hpp"
J
jp9000 已提交
41
#include "window-basic-main-outputs.hpp"
42
#include "window-basic-properties.hpp"
J
jp9000 已提交
43
#include "window-log-reply.hpp"
J
jp9000 已提交
44
#include "window-projector.hpp"
P
Palana 已提交
45
#include "window-remux.hpp"
J
jp9000 已提交
46
#include "qt-wrappers.hpp"
47
#include "display-helpers.hpp"
48
#include "volume-control.hpp"
49
#include "remote-text.hpp"
50

J
jp9000 已提交
51
#include "ui_OBSBasic.h"
52

J
jp9000 已提交
53
#include <fstream>
54 55
#include <sstream>

56 57 58
#include <QScreen>
#include <QWindow>

59
using namespace std;
J
jp9000 已提交
60

J
jp9000 已提交
61 62 63 64 65 66 67 68 69 70
namespace {

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

}

J
jp9000 已提交
71 72
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);
P
Palana 已提交
73
Q_DECLARE_METATYPE(OBSSource);
J
jp9000 已提交
74
Q_DECLARE_METATYPE(obs_order_movement);
J
jp9000 已提交
75
Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
J
jp9000 已提交
76

P
Palana 已提交
77 78 79 80 81 82 83 84 85 86 87 88 89
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));
}

90 91
static void AddExtraModulePaths()
{
92
	char base_module_dir[512];
93
	int ret = GetConfigPath(base_module_dir, sizeof(base_module_dir),
94
			"obs-studio/plugins/%module%");
B
BtbN 已提交
95

96
	if (ret <= 0)
97 98 99
		return;

	string path = (char*)base_module_dir;
100
#if defined(__APPLE__)
101
	obs_add_module_path((path + "/bin").c_str(), (path + "/data").c_str());
102 103 104 105 106 107 108
#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
109 110
}

111 112
static QList<QKeySequence> DeleteKeys;

113
OBSBasic::OBSBasic(QWidget *parent)
114
	: OBSMainWindow  (parent),
J
jp9000 已提交
115
	  ui             (new Ui::OBSBasic)
116 117
{
	ui->setupUi(this);
J
jp9000 已提交
118 119
	ui->previewDisabledLabel->setVisible(false);

S
Socapex 已提交
120
	copyActionsDynamicProperties();
121

122 123
	ui->sources->setItemDelegate(new VisibilityItemDelegate(ui->sources));

J
jp9000 已提交
124
	int width = config_get_int(App()->GlobalConfig(), "BasicWindow", "cx");
125 126 127

	// Check if no values are saved (new installation).
	if (width != 0) {
J
jp9000 已提交
128 129 130
		int height = config_get_int(App()->GlobalConfig(),
				"BasicWindow", "cy");
		int posx = config_get_int(App()->GlobalConfig(), "BasicWindow",
131
				"posx");
J
jp9000 已提交
132
		int posy = config_get_int(App()->GlobalConfig(), "BasicWindow",
133 134
				"posy");

135
		setGeometry(posx, posy, width, height);
136 137
	}

138
	char styleSheetPath[512];
J
jp9000 已提交
139 140
	int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath),
			"stylesheet.qss");
141
	if (ret > 0) {
H
HomeWorld 已提交
142 143 144 145 146
		if (QFile::exists(styleSheetPath)) {
			QString path = QString("file:///") +
				QT_UTF8(styleSheetPath);
			App()->setStyleSheet(path);
		}
147 148
	}

P
Palana 已提交
149 150 151
	qRegisterMetaType<OBSScene>    ("OBSScene");
	qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
	qRegisterMetaType<OBSSource>   ("OBSSource");
P
Palana 已提交
152
	qRegisterMetaType<obs_hotkey_id>("obs_hotkey_id");
P
Palana 已提交
153

154 155 156
	qRegisterMetaTypeStreamOperators<
		std::vector<std::shared_ptr<OBSSignal>>>(
				"std::vector<std::shared_ptr<OBSSignal>>");
157
	qRegisterMetaTypeStreamOperators<OBSScene>("OBSScene");
158
	qRegisterMetaTypeStreamOperators<OBSSceneItem>("OBSSceneItem");
159

P
Palana 已提交
160 161 162
	ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false);
	ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false);

163
	auto displayResize = [this]() {
164 165 166 167
		struct obs_video_info ovi;

		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
168 169 170 171
	};

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

P
Palana 已提交
173 174
	installEventFilter(CreateShortcutFilter());

175 176 177 178 179
	stringstream name;
	name << "OBS " << App()->GetVersionString();	
	blog(LOG_INFO, "%s", name.str().c_str());
	blog(LOG_INFO, "---------------------------------");

180
	UpdateTitleBar();
J
jp9000 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193 194

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

	connect(ui->sources->itemDelegate(),
			SIGNAL(closeEditor(QWidget*,
					QAbstractItemDelegate::EndEditHint)),
			this,
			SLOT(SceneItemNameEdited(QWidget*,
					QAbstractItemDelegate::EndEditHint)));
195 196

	cpuUsageInfo = os_cpu_usage_info_start();
J
jp9000 已提交
197 198 199 200
	cpuUsageTimer = new QTimer(this);
	connect(cpuUsageTimer, SIGNAL(timeout()),
			ui->statusbar, SLOT(UpdateCPUUsage()));
	cpuUsageTimer->start(3000);
201

202 203 204 205 206 207
	DeleteKeys =
#ifdef __APPLE__
		QList<QKeySequence>{{Qt::Key_Backspace}} <<
#endif
		QKeySequence::keyBindings(QKeySequence::Delete);

208
#ifdef __APPLE__
209 210
	ui->actionRemoveSource->setShortcuts(DeleteKeys);
	ui->actionRemoveScene->setShortcuts(DeleteKeys);
211 212 213

	ui->action_Settings->setMenuRole(QAction::PreferencesRole);
	ui->actionE_xit->setMenuRole(QAction::QuitRole);
214
#endif
215 216 217 218 219 220 221 222 223 224 225 226 227 228

	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()));
229 230
}

231 232
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
		vector<OBSSource> &audioSources)
233
{
234
	obs_source_t *source = obs_get_output_source(channel);
235 236 237
	if (!source)
		return;

238 239
	audioSources.push_back(source);

240
	obs_data_t *data = obs_save_source(source);
241

J
jp9000 已提交
242
	obs_data_set_obj(parent, name, data);
243 244 245 246 247

	obs_data_release(data);
	obs_source_release(source);
}

248 249
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
		obs_data_array_t *quickTransitionData, int transitionDuration,
J
jp9000 已提交
250
		obs_data_array_t *transitions,
251
		OBSScene &scene, OBSSource &curProgramScene)
252
{
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
	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);

	auto FilterAudioSources = [&](obs_source_t *source)
	{
		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));

277 278
	obs_source_t *transition = obs_get_output_source(0);
	obs_source_t *currentScene = obs_scene_get_source(scene);
279
	const char   *sceneName   = obs_source_get_name(currentScene);
280
	const char   *programName = obs_source_get_name(curProgramScene);
281

J
jp9000 已提交
282 283 284
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

J
jp9000 已提交
285
	obs_data_set_string(saveData, "current_scene", sceneName);
286
	obs_data_set_string(saveData, "current_program_scene", programName);
J
jp9000 已提交
287
	obs_data_set_array(saveData, "scene_order", sceneOrder);
J
jp9000 已提交
288
	obs_data_set_string(saveData, "name", sceneCollection);
J
jp9000 已提交
289
	obs_data_set_array(saveData, "sources", sourcesArray);
290
	obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
J
jp9000 已提交
291
	obs_data_set_array(saveData, "transitions", transitions);
292
	obs_data_array_release(sourcesArray);
293 294 295 296 297

	obs_data_set_string(saveData, "current_transition",
			obs_source_get_name(transition));
	obs_data_set_int(saveData, "transition_duration", transitionDuration);
	obs_source_release(transition);
298 299 300 301

	return saveData;
}

S
Socapex 已提交
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
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));
		}
	}
}

322 323 324 325 326 327 328 329 330 331 332 333
void OBSBasic::ClearVolumeControls()
{
	VolControl *control;

	for (size_t i = 0; i < volumes.size(); i++) {
		control = volumes[i];
		delete control;
	}

	volumes.clear();
}

J
jp9000 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
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;
}

349 350
void OBSBasic::Save(const char *file)
{
351 352 353 354 355
	OBSScene scene = GetCurrentScene();
	OBSSource curProgramScene = OBSGetStrongRef(programScene);
	if (!curProgramScene)
		curProgramScene = obs_scene_get_source(scene);

J
jp9000 已提交
356
	obs_data_array_t *sceneOrder = SaveSceneListOrder();
J
jp9000 已提交
357
	obs_data_array_t *transitions = SaveTransitions();
358 359
	obs_data_array_t *quickTrData = SaveQuickTransitions();
	obs_data_t *saveData  = GenerateSaveData(sceneOrder, quickTrData,
J
jp9000 已提交
360
			ui->transitionDuration->value(), transitions,
361
			scene, curProgramScene);
362 363 364

	if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
		blog(LOG_ERROR, "Could not save scene data to %s", file);
365 366

	obs_data_release(saveData);
J
jp9000 已提交
367
	obs_data_array_release(sceneOrder);
368
	obs_data_array_release(quickTrData);
J
jp9000 已提交
369
	obs_data_array_release(transitions);
370 371
}

372
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
373
{
374
	obs_data_t *data = obs_data_get_obj(parent, name);
375 376 377
	if (!data)
		return;

378
	obs_source_t *source = obs_load_source(data);
379 380 381 382 383 384 385 386
	if (source) {
		obs_set_output_source(channel, source);
		obs_source_release(source);
	}

	obs_data_release(data);
}

387 388 389
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
390
	obs_properties_t *props = obs_get_source_properties(output_id);
391 392 393 394 395 396 397 398 399 400 401 402 403 404
	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;
}

405
void OBSBasic::CreateFirstRunSources()
406
{
407 408 409 410 411 412 413 414 415
	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);
416 417
}

418
void OBSBasic::CreateDefaultScene(bool firstStart)
419 420 421 422
{
	disableSaving++;

	ClearSceneData();
423 424 425 426
	InitDefaultTransitions();
	CreateDefaultQuickTransitions();
	ui->transitionDuration->setValue(300);
	SetTransition(fadeTransition);
427 428 429

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

430
	if (firstStart)
431
		CreateFirstRunSources();
432

433
	AddScene(obs_scene_get_source(scene));
434
	SetCurrentScene(scene, true);
435
	obs_scene_release(scene);
J
jp9000 已提交
436 437

	disableSaving--;
438 439
}

J
jp9000 已提交
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
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);
	}
}

469 470
void OBSBasic::Load(const char *file)
{
471
	if (!file || !os_file_exists(file)) {
472
		blog(LOG_INFO, "No scene file found, creating default scene");
473
		CreateDefaultScene(true);
474
		SaveProject();
475 476 477
		return;
	}

478 479 480 481 482
	disableSaving++;

	obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak");
	if (!data) {
		disableSaving--;
483 484
		blog(LOG_ERROR, "Failed to load '%s', creating default scene",
				file);
485
		CreateDefaultScene(true);
J
jp9000 已提交
486
		SaveProject();
487
		return;
488
	}
489

490
	ClearSceneData();
491
	InitDefaultTransitions();
492

J
jp9000 已提交
493
	obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
494
	obs_data_array_t *sources    = obs_data_get_array(data, "sources");
J
jp9000 已提交
495
	obs_data_array_t *transitions= obs_data_get_array(data, "transitions");
J
jp9000 已提交
496 497
	const char       *sceneName = obs_data_get_string(data,
			"current_scene");
498 499 500 501 502 503 504 505 506 507 508
	const char       *programSceneName = obs_data_get_string(data,
			"current_program_scene");
	const char       *transitionName = obs_data_get_string(data,
			"current_transition");

	int newDuration = obs_data_get_int(data, "transition_duration");
	if (!newDuration)
		newDuration = 300;

	if (!transitionName)
		transitionName = obs_source_get_name(fadeTransition);
J
jp9000 已提交
509 510 511 512 513 514 515

	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");
516
	obs_source_t     *curScene;
517 518
	obs_source_t     *curProgramScene;
	obs_source_t     *curTransition;
519

J
jp9000 已提交
520 521 522
	if (!name || !*name)
		name = curSceneCollection;

523 524 525 526 527 528
	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);

529
	obs_load_sources(sources, OBSBasic::SourceLoaded, this);
530

J
jp9000 已提交
531 532
	if (transitions)
		LoadTransitions(transitions);
J
jp9000 已提交
533 534 535
	if (sceneOrder)
		LoadSceneListOrder(sceneOrder);

J
jp9000 已提交
536 537
	obs_data_array_release(transitions);

538 539 540 541 542 543 544
	curTransition = FindTransition(transitionName);
	if (!curTransition)
		curTransition = fadeTransition;

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

545
	curScene = obs_get_source_by_name(sceneName);
546 547 548 549 550 551 552 553 554
	curProgramScene = obs_get_source_by_name(programSceneName);
	if (!curProgramScene) {
		curProgramScene = curScene;
		obs_source_addref(curScene);
	}

	SetCurrentScene(curScene, true);
	if (IsPreviewProgramMode())
		TransitionToScene(curProgramScene, true);
555
	obs_source_release(curScene);
556
	obs_source_release(curProgramScene);
557 558

	obs_data_array_release(sources);
J
jp9000 已提交
559
	obs_data_array_release(sceneOrder);
J
jp9000 已提交
560 561 562 563 564 565 566 567 568

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

569 570 571 572 573 574 575
	obs_data_array_t *quickTransitionData = obs_data_get_array(data,
			"quick_transitions");
	LoadQuickTransitions(quickTransitionData);
	obs_data_array_release(quickTransitionData);

	RefreshQuickTransitions();

576
	obs_data_release(data);
J
jp9000 已提交
577 578

	disableSaving--;
579 580
}

J
jp9000 已提交
581
#define SERVICE_PATH "service.json"
582 583 584 585 586 587

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

588
	char serviceJsonPath[512];
J
jp9000 已提交
589
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
590 591
			SERVICE_PATH);
	if (ret <= 0)
592 593
		return;

594 595
	obs_data_t *data     = obs_data_create();
	obs_data_t *settings = obs_service_get_settings(service);
596

597
	obs_data_set_string(data, "type", obs_service_get_type(service));
J
jp9000 已提交
598
	obs_data_set_obj(data, "settings", settings);
599

600 601
	if (!obs_data_save_json_safe(data, serviceJsonPath, "tmp", "bak"))
		blog(LOG_WARNING, "Failed to save service");
602 603 604 605 606 607 608 609 610

	obs_data_release(settings);
	obs_data_release(data);
}

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

611
	char serviceJsonPath[512];
J
jp9000 已提交
612
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
613 614
			SERVICE_PATH);
	if (ret <= 0)
615 616
		return false;

617 618
	obs_data_t *data = obs_data_create_from_json_file_safe(serviceJsonPath,
			"bak");
619 620

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

623
	obs_data_t *settings = obs_data_get_obj(data, "settings");
P
Palana 已提交
624
	obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys");
625

626
	service = obs_service_create(type, "default_service", settings,
P
Palana 已提交
627
			hotkey_data);
628
	obs_service_release(service);
629

P
Palana 已提交
630
	obs_data_release(hotkey_data);
631 632 633 634 635 636 637 638
	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitService()
{
P
Palana 已提交
639 640
	ProfileScope("OBSBasic::InitService");

641 642 643
	if (LoadService())
		return true;

644 645
	service = obs_service_create("rtmp_common", "default_service", nullptr,
			nullptr);
646 647
	if (!service)
		return false;
648
	obs_service_release(service);
649 650 651 652

	return true;
}

653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
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
};

669 670 671 672 673 674 675 676 677 678 679 680 681 682
bool OBSBasic::InitBasicConfigDefaults()
{
	vector<MonitorInfo> monitors;
	GetMonitors(monitors);

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

	uint32_t cx = monitors[0].cx;
	uint32_t cy = monitors[0].cy;

683 684 685 686 687 688 689 690 691 692
	/* ----------------------------------------------------- */
	/* 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");
693
		config_save_safe(basicConfig, "tmp", nullptr);
694 695 696 697
	}

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

698
	config_set_default_string(basicConfig, "Output", "Mode", "Simple");
J
jp9000 已提交
699

700 701
	config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
			GetDefaultVideoSavePath().c_str());
702 703
	config_set_default_string(basicConfig, "SimpleOutput", "RecFormat",
			"flv");
704 705
	config_set_default_uint  (basicConfig, "SimpleOutput", "VBitrate",
			2500);
706
	config_set_default_uint  (basicConfig, "SimpleOutput", "ABitrate", 160);
J
jp9000 已提交
707 708 709 710
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseAdvanced",
			false);
	config_set_default_string(basicConfig, "SimpleOutput", "Preset",
			"veryfast");
711 712 713 714
	config_set_default_string(basicConfig, "SimpleOutput", "RecQuality",
			"Stream");
	config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
			SIMPLE_ENCODER_X264);
715

716 717
	config_set_default_bool  (basicConfig, "AdvOut", "ApplyServiceSettings",
			true);
J
jp9000 已提交
718 719 720 721 722 723 724 725
	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());
726
	config_set_default_string(basicConfig, "AdvOut", "RecFormat", "flv");
J
jp9000 已提交
727 728
	config_set_default_bool  (basicConfig, "AdvOut", "RecUseRescale",
			false);
729
	config_set_default_uint  (basicConfig, "AdvOut", "RecTracks", (1<<0));
J
jp9000 已提交
730 731 732
	config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
			"none");

733 734 735 736 737
	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 已提交
738 739 740 741 742 743 744 745 746 747 748
	config_set_default_uint  (basicConfig, "AdvOut", "FFVBitrate", 2500);
	config_set_default_bool  (basicConfig, "AdvOut", "FFUseRescale",
			false);
	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);

749 750 751
	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);

752
	config_set_default_string(basicConfig, "Output", "FilenameFormatting",
B
bl 已提交
753
			"%CCYY-%MM-%DD %hh-%mm-%ss");
754

755 756 757 758
	config_set_default_bool  (basicConfig, "Output", "DelayEnable", false);
	config_set_default_uint  (basicConfig, "Output", "DelaySec", 20);
	config_set_default_bool  (basicConfig, "Output", "DelayPreserve", true);

759 760 761 762
	config_set_default_bool  (basicConfig, "Output", "Reconnect", true);
	config_set_default_uint  (basicConfig, "Output", "RetryDelay", 10);
	config_set_default_uint  (basicConfig, "Output", "MaxRetries", 20);

763 764 765 766 767 768 769 770 771 772 773 774 775 776
	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);
777 778 779 780 781 782

	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);
783
	config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
784
	config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
785
	config_set_default_string(basicConfig, "Video", "ColorSpace", "601");
786 787
	config_set_default_string(basicConfig, "Video", "ColorRange",
			"Partial");
788 789 790 791 792 793 794 795 796 797

	config_set_default_uint  (basicConfig, "Audio", "SampleRate", 44100);
	config_set_default_string(basicConfig, "Audio", "ChannelSetup",
			"Stereo");

	return true;
}

bool OBSBasic::InitBasicConfig()
{
P
Palana 已提交
798 799
	ProfileScope("OBSBasic::InitBasicConfig");

800
	char configPath[512];
J
jp9000 已提交
801 802 803 804 805 806 807 808 809 810 811 812 813

	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");
814 815 816 817
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get base.ini path");
		return false;
	}
818

819 820 821
	int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (code != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", code);
822 823 824
		return false;
	}

J
jp9000 已提交
825 826 827 828 829
	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);
830
		basicConfig.SaveSafe("tmp");
J
jp9000 已提交
831 832
	}

833 834 835
	return InitBasicConfigDefaults();
}

836 837
void OBSBasic::InitOBSCallbacks()
{
P
Palana 已提交
838 839
	ProfileScope("OBSBasic::InitOBSCallbacks");

P
Palana 已提交
840 841
	signalHandlers.reserve(signalHandlers.size() + 6);
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove",
842
			OBSBasic::SourceRemoved, this);
P
Palana 已提交
843
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate",
844
			OBSBasic::SourceActivated, this);
P
Palana 已提交
845
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate",
846
			OBSBasic::SourceDeactivated, this);
P
Palana 已提交
847
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename",
J
jp9000 已提交
848
			OBSBasic::SourceRenamed, this);
849 850
}

J
jp9000 已提交
851 852
void OBSBasic::InitPrimitives()
{
P
Palana 已提交
853 854
	ProfileScope("OBSBasic::InitPrimitives");

J
jp9000 已提交
855
	obs_enter_graphics();
J
jp9000 已提交
856

857
	gs_render_start(true);
J
jp9000 已提交
858 859 860 861 862
	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);
863
	box = gs_render_save();
J
jp9000 已提交
864

865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
	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();

885
	gs_render_start(true);
J
jp9000 已提交
886 887 888 889
	for (int i = 0; i <= 360; i += (360/20)) {
		float pos = RAD(float(i));
		gs_vertex2f(cosf(pos), sinf(pos));
	}
890
	circle = gs_render_save();
J
jp9000 已提交
891

J
jp9000 已提交
892
	obs_leave_graphics();
J
jp9000 已提交
893 894
}

J
jp9000 已提交
895 896
void OBSBasic::ResetOutputs()
{
P
Palana 已提交
897 898
	ProfileScope("OBSBasic::ResetOutputs");

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

J
jp9000 已提交
902 903
	if (!outputHandler || !outputHandler->Active()) {
		outputHandler.reset();
J
jp9000 已提交
904 905 906
		outputHandler.reset(advOut ?
			CreateAdvancedOutputHandler(this) :
			CreateSimpleOutputHandler(this));
J
jp9000 已提交
907 908 909 910 911
	} else {
		outputHandler->Update();
	}
}

912 913 914
#define MAIN_SEPARATOR \
	"====================================================================="

915 916
void OBSBasic::OBSInit()
{
P
Palana 已提交
917 918
	ProfileScope("OBSBasic::OBSInit");

J
jp9000 已提交
919 920
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
921
	char savePath[512];
J
jp9000 已提交
922 923 924 925 926 927 928 929
	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);
930
	if (ret <= 0)
J
jp9000 已提交
931 932 933 934 935
		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";
936

937 938
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
939
	if (!ResetAudio())
940 941
		throw "Failed to initialize audio";

942
	ret = ResetVideo();
943 944 945 946 947

	switch (ret) {
	case OBS_VIDEO_MODULE_NOT_FOUND:
		throw "Failed to initialize video:  Graphics module not found";
	case OBS_VIDEO_NOT_SUPPORTED:
J
jp9000 已提交
948 949 950
		throw "Failed to initialize video:  Required graphics API "
		      "functionality not found on these drivers or "
		      "unavailable on this equipment";
951 952 953 954 955 956 957
	case OBS_VIDEO_INVALID_PARAM:
		throw "Failed to initialize video:  Invalid parameters";
	default:
		if (ret != OBS_VIDEO_SUCCESS)
			throw "Failed to initialize video:  Unspecified error";
	}

958
	InitOBSCallbacks();
P
Palana 已提交
959
	InitHotkeys();
960

961
	AddExtraModulePaths();
J
jp9000 已提交
962
	obs_load_all_modules();
J
jp9000 已提交
963

964 965
	blog(LOG_INFO, MAIN_SEPARATOR);

J
jp9000 已提交
966
	ResetOutputs();
967
	CreateHotkeys();
J
jp9000 已提交
968

969 970 971
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
972 973
	InitPrimitives();

974 975 976 977 978 979 980 981 982
	sceneDuplicationMode = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "SceneDuplicationMode");
	swapScenesMode = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "SwapScenesMode");
	editPropertiesMode = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "EditPropertiesMode");
	SetPreviewProgramMode(config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "PreviewProgramMode"));

P
Palana 已提交
983 984 985 986 987 988
	{
		ProfileScope("OBSBasic::Load");
		disableSaving--;
		Load(savePath);
		disableSaving++;
	}
989

J
jp9000 已提交
990
	TimedCheckForUpdates();
991
	loaded = true;
J
jp9000 已提交
992

993
	previewEnabled = config_get_bool(App()->GlobalConfig(),
J
jp9000 已提交
994
			"BasicWindow", "PreviewEnabled");
995 996 997 998 999

	if (!previewEnabled && !IsPreviewProgramMode())
		QMetaObject::invokeMethod(this, "EnablePreviewDisplay",
				Qt::QueuedConnection,
				Q_ARG(bool, previewEnabled));
1000 1001 1002 1003 1004 1005 1006 1007 1008

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

J
jp9000 已提交
1010
	RefreshSceneCollections();
J
jp9000 已提交
1011
	RefreshProfiles();
J
jp9000 已提交
1012
	disableSaving--;
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025

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

1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
#ifdef _WIN32
	show();
#endif

	bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow",
			"AlwaysOnTop");
	if (alwaysOnTop) {
		SetAlwaysOnTop(this, true);
		ui->actionAlwaysOnTop->setChecked(true);
	}

#ifndef _WIN32
1038
	show();
1039
#endif
J
jp9000 已提交
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058

	QList<int> defSizes;

	int top = config_get_int(App()->GlobalConfig(), "BasicWindow",
			"splitterTop");
	int bottom = config_get_int(App()->GlobalConfig(), "BasicWindow",
			"splitterBottom");

	if (!top || !bottom) {
		defSizes = ui->mainSplitter->sizes();
		int total = defSizes[0] + defSizes[1];
		defSizes[0] = total * 75 / 100;
		defSizes[1] = total - defSizes[0];
	} else {
		defSizes.push_back(top);
		defSizes.push_back(bottom);
	}

	ui->mainSplitter->setSizes(defSizes);
1059 1060
}

P
Palana 已提交
1061 1062
void OBSBasic::InitHotkeys()
{
P
Palana 已提交
1063 1064
	ProfileScope("OBSBasic::InitHotkeys");

P
Palana 已提交
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
	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"),
1107
			Str("Push-to-mute"), Str("Push-to-talk"));
P
Palana 已提交
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127

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

1128 1129
void OBSBasic::CreateHotkeys()
{
P
Palana 已提交
1130 1131
	ProfileScope("OBSBasic::CreateHotkeys");

1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
	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 已提交
1148 1149 1150 1151 1152 1153 1154 1155 1156
	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);
	};

1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
	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);
	};

#define MAKE_CALLBACK(pred, method) \
	[](void *data, obs_hotkey_pair_id, obs_hotkey_t*, bool pressed) \
	{ \
		OBSBasic &basic = *static_cast<OBSBasic*>(data); \
		if (pred && pressed) { \
			method(); \
			return true; \
		} \
		return false; \
	}

	streamingHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartStreaming",
			Str("Basic.Hotkeys.StartStreaming"),
			"OBSBasic.StopStreaming",
			Str("Basic.Hotkeys.StopStreaming"),
			MAKE_CALLBACK(!basic.outputHandler->StreamingActive(),
				basic.StartStreaming),
			MAKE_CALLBACK(basic.outputHandler->StreamingActive(),
				basic.StopStreaming),
			this, this);
	LoadHotkeyPair(streamingHotkeys,
			"OBSBasic.StartStreaming", "OBSBasic.StopStreaming");

J
jp9000 已提交
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
	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");

1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
	recordingHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartRecording",
			Str("Basic.Hotkeys.StartRecording"),
			"OBSBasic.StopRecording",
			Str("Basic.Hotkeys.StopRecording"),
			MAKE_CALLBACK(!basic.outputHandler->RecordingActive(),
				basic.StartRecording),
			MAKE_CALLBACK(basic.outputHandler->RecordingActive(),
				basic.StopRecording),
			this, this);
	LoadHotkeyPair(recordingHotkeys,
			"OBSBasic.StartRecording", "OBSBasic.StopRecording");
#undef MAKE_CALLBACK
1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250

	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");
1251 1252
}

J
jp9000 已提交
1253 1254 1255 1256
void OBSBasic::ClearHotkeys()
{
	obs_hotkey_pair_unregister(streamingHotkeys);
	obs_hotkey_pair_unregister(recordingHotkeys);
J
jp9000 已提交
1257
	obs_hotkey_unregister(forceStreamingStopHotkey);
1258 1259
	obs_hotkey_unregister(togglePreviewProgramHotkey);
	obs_hotkey_unregister(transitionHotkey);
J
jp9000 已提交
1260 1261
}

1262 1263
OBSBasic::~OBSBasic()
{
1264 1265
	delete programOptions;
	delete program;
J
jp9000 已提交
1266

J
jp9000 已提交
1267 1268 1269 1270 1271 1272
	/* 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 已提交
1273
	delete cpuUsageTimer;
1274 1275
	os_cpu_usage_info_destroy(cpuUsageInfo);

P
Palana 已提交
1276
	obs_hotkey_set_callback_routing_func(nullptr, nullptr);
J
jp9000 已提交
1277
	ClearHotkeys();
P
Palana 已提交
1278

1279
	service = nullptr;
J
jp9000 已提交
1280 1281
	outputHandler.reset();

J
John Bradley 已提交
1282 1283 1284
	if (interaction)
		delete interaction;

1285 1286 1287
	if (properties)
		delete properties;

J
jp9000 已提交
1288 1289 1290
	if (filters)
		delete filters;

1291 1292
	if (transformWindow)
		delete transformWindow;
1293

J
jp9000 已提交
1294 1295 1296
	if (advAudioWindow)
		delete advAudioWindow;

1297 1298 1299
	obs_display_remove_draw_callback(ui->preview->GetDisplay(),
			OBSBasic::RenderMain, this);

J
jp9000 已提交
1300
	obs_enter_graphics();
1301
	gs_vertexbuffer_destroy(box);
1302 1303 1304 1305
	gs_vertexbuffer_destroy(boxLeft);
	gs_vertexbuffer_destroy(boxTop);
	gs_vertexbuffer_destroy(boxRight);
	gs_vertexbuffer_destroy(boxBottom);
1306
	gs_vertexbuffer_destroy(circle);
J
jp9000 已提交
1307
	obs_leave_graphics();
J
jp9000 已提交
1308

1309 1310 1311 1312 1313 1314 1315 1316 1317
	/* 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 已提交
1318 1319
	config_set_int(App()->GlobalConfig(), "General", "LastVersion",
			LIBOBS_API_VER);
J
jp9000 已提交
1320 1321

	QRect lastGeom = normalGeometry();
J
jp9000 已提交
1322
	QList<int> splitterSizes = ui->mainSplitter->sizes();
1323
	bool alwaysOnTop = IsAlwaysOnTop(this);
J
jp9000 已提交
1324

J
jp9000 已提交
1325
	config_set_int(App()->GlobalConfig(), "BasicWindow", "cx",
J
jp9000 已提交
1326
			lastGeom.width());
J
jp9000 已提交
1327
	config_set_int(App()->GlobalConfig(), "BasicWindow", "cy",
J
jp9000 已提交
1328
			lastGeom.height());
J
jp9000 已提交
1329
	config_set_int(App()->GlobalConfig(), "BasicWindow", "posx",
J
jp9000 已提交
1330
			lastGeom.x());
J
jp9000 已提交
1331
	config_set_int(App()->GlobalConfig(), "BasicWindow", "posy",
J
jp9000 已提交
1332
			lastGeom.y());
J
jp9000 已提交
1333 1334 1335 1336
	config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterTop",
			splitterSizes[0]);
	config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterBottom",
			splitterSizes[1]);
J
jp9000 已提交
1337 1338
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled",
			previewEnabled);
1339 1340
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
			alwaysOnTop);
1341 1342 1343 1344 1345 1346 1347 1348
	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());
1349
	config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360

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

J
jp9000 已提交
1363 1364 1365 1366 1367 1368 1369 1370 1371
void OBSBasic::SaveProjectNow()
{
	if (disableSaving)
		return;

	projectChanged = true;
	SaveProjectDeferred();
}

J
jp9000 已提交
1372 1373
void OBSBasic::SaveProject()
{
J
jp9000 已提交
1374 1375 1376
	if (disableSaving)
		return;

J
jp9000 已提交
1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391
	projectChanged = true;
	QMetaObject::invokeMethod(this, "SaveProjectDeferred",
			Qt::QueuedConnection);
}

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

	if (!projectChanged)
		return;

	projectChanged = false;

J
jp9000 已提交
1392 1393
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
1394
	char savePath[512];
J
jp9000 已提交
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406
	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);
1407 1408 1409
	if (ret <= 0)
		return;

J
jp9000 已提交
1410 1411 1412
	Save(savePath);
}

J
jp9000 已提交
1413
OBSScene OBSBasic::GetCurrentScene()
1414
{
J
jp9000 已提交
1415
	QListWidgetItem *item = ui->scenes->currentItem();
P
Palana 已提交
1416
	return item ? GetOBSRef<OBSScene>(item) : nullptr;
1417 1418
}

1419
OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
J
jp9000 已提交
1420
{
P
Palana 已提交
1421
	return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
J
jp9000 已提交
1422 1423
}

1424 1425
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
1426
	return GetSceneItem(GetTopSelectedSourceItem());
1427 1428
}

1429 1430
void OBSBasic::UpdateSources(OBSScene scene)
{
1431
	ClearListItems(ui->sources);
1432 1433

	obs_scene_enum_items(scene,
1434
			[] (obs_scene_t *scene, obs_sceneitem_t *item, void *p)
1435 1436
			{
				OBSBasic *window = static_cast<OBSBasic*>(p);
1437
				window->InsertSceneItem(item);
J
jp9000 已提交
1438 1439

				UNUSED_PARAMETER(scene);
1440 1441 1442 1443
				return true;
			}, this);
}

1444
void OBSBasic::InsertSceneItem(obs_sceneitem_t *item)
1445
{
1446
	QListWidgetItem *listItem = new QListWidgetItem();
P
Palana 已提交
1447
	SetOBSRef(listItem, OBSSceneItem(item));
1448 1449

	ui->sources->insertItem(0, listItem);
1450
	ui->sources->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
1451

1452
	SetupVisibilityItem(ui->sources, listItem, item);
1453 1454
}

1455
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
J
John Bradley 已提交
1456 1457 1458 1459 1460 1461 1462 1463 1464
{
	if (interaction)
		interaction->close();

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

1465
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
1466 1467 1468 1469 1470 1471 1472
{
	if (properties)
		properties->close();

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

J
jp9000 已提交
1475 1476 1477 1478 1479 1480 1481 1482 1483 1484
void OBSBasic::CreateFiltersWindow(obs_source_t *source)
{
	if (filters)
		filters->close();

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

1485 1486 1487
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
1488
{
1489
	const char *name  = obs_source_get_name(source);
1490
	obs_scene_t *scene = obs_scene_from_source(source);
J
jp9000 已提交
1491 1492

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

P
Palana 已提交
1496 1497 1498 1499 1500
	obs_hotkey_register_source(source, "OBSBasic.SelectScene",
			Str("Basic.Hotkeys.SelectScene"),
			[](void *data,
				obs_hotkey_id, obs_hotkey_t*, bool pressed)
	{
1501 1502 1503
		OBSBasic *main =
			reinterpret_cast<OBSBasic*>(App()->GetMainWindow());

P
Palana 已提交
1504 1505 1506
		auto potential_source = static_cast<obs_source_t*>(data);
		auto source = obs_source_get_ref(potential_source);
		if (source && pressed)
1507
			main->SetCurrentScene(source);
P
Palana 已提交
1508 1509 1510
		obs_source_release(source);
	}, static_cast<obs_source_t*>(source));

1511
	signal_handler_t *handler = obs_source_get_signal_handler(source);
1512

J
jp9000 已提交
1513 1514 1515
	SignalContainer<OBSScene> container;
	container.ref = scene;
	container.handlers.assign({
1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
		std::make_shared<OBSSignal>(handler, "item_add",
					OBSBasic::SceneItemAdded, this),
		std::make_shared<OBSSignal>(handler, "item_remove",
					OBSBasic::SceneItemRemoved, 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 已提交
1526
	});
1527 1528

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

1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547
	/* 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 已提交
1548
	SaveProject();
1549 1550
}

1551
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
1552
{
P
Palana 已提交
1553 1554 1555 1556 1557 1558 1559 1560 1561
	obs_scene_t *scene = obs_scene_from_source(source);

	QListWidgetItem *sel = nullptr;
	int count = ui->scenes->count();
	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 已提交
1562

P
Palana 已提交
1563 1564 1565
		sel = item;
		break;
	}
J
jp9000 已提交
1566

J
jp9000 已提交
1567
	if (sel != nullptr) {
P
Palana 已提交
1568
		if (sel == ui->scenes->currentItem())
1569
			ClearListItems(ui->sources);
J
jp9000 已提交
1570
		delete sel;
J
jp9000 已提交
1571
	}
J
jp9000 已提交
1572 1573

	SaveProject();
1574 1575
}

1576
void OBSBasic::AddSceneItem(OBSSceneItem item)
1577
{
1578
	obs_scene_t  *scene  = obs_sceneitem_get_scene(item);
J
jp9000 已提交
1579

1580 1581
	if (GetCurrentScene() == scene)
		InsertSceneItem(item);
J
jp9000 已提交
1582

J
jp9000 已提交
1583
	SaveProject();
1584 1585
}

1586
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
1587
{
1588
	obs_scene_t *scene = obs_sceneitem_get_scene(item);
1589

J
jp9000 已提交
1590
	if (GetCurrentScene() == scene) {
B
BtbN 已提交
1591
		for (int i = 0; i < ui->sources->count(); i++) {
J
jp9000 已提交
1592
			QListWidgetItem *listItem = ui->sources->item(i);
J
jp9000 已提交
1593

P
Palana 已提交
1594
			if (GetOBSRef<OBSSceneItem>(listItem) == item) {
1595
				DeleteListItem(ui->sources, listItem);
J
jp9000 已提交
1596 1597
				break;
			}
1598 1599
		}
	}
J
jp9000 已提交
1600

J
jp9000 已提交
1601
	SaveProject();
1602 1603
}

1604
void OBSBasic::UpdateSceneSelection(OBSSource source)
1605 1606
{
	if (source) {
1607
		obs_scene_t *scene = obs_scene_from_source(source);
1608
		const char *name = obs_source_get_name(source);
J
jp9000 已提交
1609

1610 1611 1612
		if (!scene)
			return;

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

1616 1617 1618 1619 1620
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

J
jp9000 已提交
1621
			UpdateSources(scene);
1622
		}
J
jp9000 已提交
1623
	}
1624 1625
}

J
jp9000 已提交
1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638
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);
}

void OBSBasic::RenameSources(QString newName, QString prevName)
{
	RenameListValues(ui->scenes,  newName, prevName);
1639 1640 1641 1642 1643

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

	SaveProject();
J
jp9000 已提交
1646 1647
}

1648 1649
void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
{
J
jp9000 已提交
1650 1651
	SignalBlocker sourcesSignalBlocker(ui->sources);

1652
	if (scene != GetCurrentScene() || ignoreSelectionUpdate)
1653 1654 1655 1656
		return;

	for (int i = 0; i < ui->sources->count(); i++) {
		QListWidgetItem *witem = ui->sources->item(i);
P
Palana 已提交
1657 1658
		QVariant data =
			witem->data(static_cast<int>(QtDataRole::OBSRef));
1659 1660 1661 1662 1663 1664
		if (!data.canConvert<OBSSceneItem>())
			continue;

		if (item != data.value<OBSSceneItem>())
			continue;

J
jp9000 已提交
1665
		witem->setSelected(select);
1666 1667 1668 1669
		break;
	}
}

1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712
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);
}

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

	QAction filtersAction(QTStr("Filters"), this);
	QAction propertiesAction(QTStr("Properties"), this);

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

	filtersAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));
	propertiesAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));

	QMenu popup(this);
	popup.addAction(&filtersAction);
	popup.addAction(&propertiesAction);
	popup.exec(QCursor::pos());
}

1713 1714
void OBSBasic::ActivateAudioSource(OBSSource source)
{
1715 1716 1717 1718
	VolControl *vol = new VolControl(source, true);

	connect(vol, &VolControl::ConfigClicked,
			this, &OBSBasic::VolControlContextMenu);
1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734

	volumes.push_back(vol);
	ui->volumeWidgets->layout()->addWidget(vol);
}

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

1735
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
J
jp9000 已提交
1736
{
1737
	const char *name  = obs_source_get_name(source);
1738 1739 1740

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

1742
	QMessageBox remove_source(this);
1743 1744 1745
	remove_source.setText(text);
	QAbstractButton *Yes = remove_source.addButton(QTStr("Yes"),
			QMessageBox::YesRole);
J
Jkoan 已提交
1746 1747 1748 1749 1750 1751
	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();
1752
}
J
jp9000 已提交
1753

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

P
Palana 已提交
1756 1757 1758 1759 1760
#ifdef UPDATE_SPARKLE
void init_sparkle_updater(bool update_to_undeployed);
void trigger_sparkle_update();
#endif

J
jp9000 已提交
1761 1762
void OBSBasic::TimedCheckForUpdates()
{
P
Palana 已提交
1763 1764 1765 1766
#ifdef UPDATE_SPARKLE
	init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
				"UpdateToUndeployed"));
#else
J
jp9000 已提交
1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782
	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)
		CheckForUpdates();
P
Palana 已提交
1783
#endif
J
jp9000 已提交
1784 1785 1786 1787
}

void OBSBasic::CheckForUpdates()
{
P
Palana 已提交
1788 1789 1790
#ifdef UPDATE_SPARKLE
	trigger_sparkle_update();
#else
J
jp9000 已提交
1791 1792
	ui->actionCheckForUpdates->setEnabled(false);

1793 1794 1795 1796
	if (updateCheckThread) {
		updateCheckThread->wait();
		delete updateCheckThread;
	}
1797

1798 1799 1800 1801 1802 1803
	RemoteTextThread *thread = new RemoteTextThread(
			"https://obsproject.com/obs2_update/basic.json");
	updateCheckThread = thread;
	connect(thread, &RemoteTextThread::Result,
			this, &OBSBasic::updateFileFinished);
	updateCheckThread->start();
P
Palana 已提交
1804
#endif
J
jp9000 已提交
1805 1806
}

J
jp9000 已提交
1807 1808
#ifdef __APPLE__
#define VERSION_ENTRY "mac"
J
jp9000 已提交
1809 1810
#elif _WIN32
#define VERSION_ENTRY "windows"
J
jp9000 已提交
1811 1812 1813 1814
#else
#define VERSION_ENTRY "other"
#endif

1815
void OBSBasic::updateFileFinished(const QString &text, const QString &error)
J
jp9000 已提交
1816 1817 1818
{
	ui->actionCheckForUpdates->setEnabled(true);

1819 1820
	if (text.isEmpty()) {
		blog(LOG_WARNING, "Update check failed: %s", QT_TO_UTF8(error));
J
jp9000 已提交
1821 1822 1823
		return;
	}

1824
	obs_data_t *returnData  = obs_data_create_from_json(QT_TO_UTF8(text));
F
fryshorts 已提交
1825
	obs_data_t *versionData = obs_data_get_obj(returnData, VERSION_ENTRY);
J
jp9000 已提交
1826 1827 1828
	const char *description = obs_data_get_string(returnData,
			"description");
	const char *download    = obs_data_get_string(versionData, "download");
J
jp9000 已提交
1829 1830

	if (returnData && versionData && description && download) {
J
jp9000 已提交
1831 1832 1833
		long major   = obs_data_get_int(versionData, "major");
		long minor   = obs_data_get_int(versionData, "minor");
		long patch   = obs_data_get_int(versionData, "patch");
J
jp9000 已提交
1834 1835
		long version = MAKE_SEMANTIC_VERSION(major, minor, patch);

1836 1837
		blog(LOG_INFO, "Update check: last known remote version "
				"is %ld.%ld.%ld",
J
jp9000 已提交
1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857
				major, minor, patch);

		if (version > LIBOBS_API_VER) {
			QString     str = QTStr("UpdateAvailable.Text");
			QMessageBox messageBox(this);

			str = str.arg(QString::number(major),
			              QString::number(minor),
			              QString::number(patch),
			              download);

			messageBox.setWindowTitle(QTStr("UpdateAvailable"));
			messageBox.setTextFormat(Qt::RichText);
			messageBox.setText(str);
			messageBox.setInformativeText(QT_UTF8(description));
			messageBox.exec();

			long long t = (long long)time(nullptr);
			config_set_int(App()->GlobalConfig(), "General",
					"LastUpdateCheck", t);
1858
			config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
J
jp9000 已提交
1859 1860 1861 1862 1863 1864 1865 1866 1867
		}
	} else {
		blog(LOG_WARNING, "Bad JSON file received from server");
	}

	obs_data_release(versionData);
	obs_data_release(returnData);
}

1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914
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()) {
			QMessageBox::information(this,
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
			continue;
		}

		obs_source_t *source = obs_get_source_by_name(name.c_str());
		if (source) {
			QMessageBox::information(this,
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));

			obs_source_release(source);
			continue;
		}

		obs_scene_t *scene = obs_scene_duplicate(curScene,
1915
				name.c_str(), OBS_SCENE_DUP_REFS);
1916
		source = obs_scene_get_source(scene);
1917 1918
		AddScene(source);
		SetCurrentScene(source, true);
1919
		obs_scene_release(scene);
1920
		break;
1921 1922 1923
	}
}

1924 1925 1926 1927
void OBSBasic::RemoveSelectedScene()
{
	OBSScene scene = GetCurrentScene();
	if (scene) {
1928
		obs_source_t *source = obs_scene_get_source(scene);
1929 1930 1931 1932 1933 1934 1935 1936 1937
		if (QueryRemoveSource(source))
			obs_source_remove(source);
	}
}

void OBSBasic::RemoveSelectedSceneItem()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (item) {
1938
		obs_source_t *source = obs_sceneitem_get_source(item);
1939
		if (QueryRemoveSource(source))
J
jp9000 已提交
1940 1941 1942 1943
			obs_sceneitem_remove(item);
	}
}

1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957
struct ReorderInfo {
	int idx = 0;
	OBSBasic *window;

	inline ReorderInfo(OBSBasic *window_) : window(window_) {}
};

void OBSBasic::ReorderSceneItem(obs_sceneitem_t *item, size_t idx)
{
	int count = ui->sources->count();
	int idx_inv = count - (int)idx - 1;

	for (int i = 0; i < count; i++) {
		QListWidgetItem *listItem = ui->sources->item(i);
P
Palana 已提交
1958
		OBSSceneItem sceneItem = GetOBSRef<OBSSceneItem>(listItem);
1959 1960 1961 1962 1963

		if (sceneItem == item) {
			if ((int)idx_inv != i) {
				bool sel = (ui->sources->currentRow() == i);

1964
				listItem = TakeListItem(ui->sources, i);
1965 1966 1967
				if (listItem)  {
					ui->sources->insertItem(idx_inv,
							listItem);
1968 1969 1970
					SetupVisibilityItem(ui->sources,
							listItem, item);

1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985
					if (sel)
						ui->sources->setCurrentRow(
								idx_inv);
				}
			}

			break;
		}
	}
}

void OBSBasic::ReorderSources(OBSScene scene)
{
	ReorderInfo info(this);

1986
	if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998
		return;

	obs_scene_enum_items(scene,
			[] (obs_scene_t*, obs_sceneitem_t *item, void *p)
			{
				ReorderInfo *info =
					reinterpret_cast<ReorderInfo*>(p);

				info->window->ReorderSceneItem(item,
					info->idx++);
				return true;
			}, &info);
J
jp9000 已提交
1999 2000

	SaveProject();
2001 2002
}

2003 2004
/* OBS Callbacks */

2005 2006 2007 2008 2009 2010 2011 2012 2013 2014
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)));
}

2015
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
2016 2017 2018
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

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

2021 2022
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
2023 2024
}

2025
void OBSBasic::SceneItemRemoved(void *data, calldata_t *params)
2026
{
2027
	OBSBasic *window = static_cast<OBSBasic*>(data);
2028

2029
	obs_sceneitem_t *item = (obs_sceneitem_t*)calldata_ptr(params, "item");
2030

2031 2032
	QMetaObject::invokeMethod(window, "RemoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
2033 2034
}

2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058
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));
}

2059
void OBSBasic::SourceLoaded(void *data, obs_source_t *source)
2060
{
J
jp9000 已提交
2061
	OBSBasic *window = static_cast<OBSBasic*>(data);
2062

2063
	if (obs_scene_from_source(source) != NULL)
J
jp9000 已提交
2064
		QMetaObject::invokeMethod(window,
2065 2066
				"AddScene",
				Q_ARG(OBSSource, OBSSource(source)));
2067 2068
}

2069
void OBSBasic::SourceRemoved(void *data, calldata_t *params)
2070
{
2071
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2072

2073
	if (obs_scene_from_source(source) != NULL)
2074 2075 2076
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
2077 2078
}

2079
void OBSBasic::SourceActivated(void *data, calldata_t *params)
2080
{
2081
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2082 2083 2084 2085 2086 2087 2088 2089
	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)));
}

2090
void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
2091
{
2092
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2093 2094 2095 2096 2097 2098 2099 2100
	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)));
}

2101
void OBSBasic::SourceRenamed(void *data, calldata_t *params)
J
jp9000 已提交
2102 2103 2104 2105 2106 2107 2108 2109 2110 2111
{
	const char *newName  = calldata_string(params, "new_name");
	const char *prevName = calldata_string(params, "prev_name");

	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"RenameSources",
			Q_ARG(QString, QT_UTF8(newName)),
			Q_ARG(QString, QT_UTF8(prevName)));
}

2112 2113 2114 2115 2116
void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

2117
	gs_effect_t    *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
2118 2119
	gs_eparam_t    *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech  = gs_effect_get_technique(solid, "Solid");
2120 2121 2122

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
2123
	gs_effect_set_vec4(color, &colorVal);
2124

2125 2126
	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
2127 2128 2129 2130 2131 2132 2133 2134
	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();
2135 2136
	gs_technique_end_pass(tech);
	gs_technique_end(tech);
2137 2138 2139 2140

	gs_load_vertexbuffer(nullptr);
}

2141 2142
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
2143
	OBSBasic *window = static_cast<OBSBasic*>(data);
2144 2145 2146 2147
	obs_video_info ovi;

	obs_get_video_info(&ovi);

J
jp9000 已提交
2148 2149
	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));
2150 2151 2152

	gs_viewport_push();
	gs_projection_push();
2153 2154 2155

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

2156 2157
	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
			-100.0f, 100.0f);
2158
	gs_set_viewport(window->previewX, window->previewY,
J
jp9000 已提交
2159
			window->previewCX, window->previewCY);
2160

2161 2162
	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

2163 2164 2165 2166 2167 2168 2169 2170
	if (window->IsPreviewProgramMode()) {
		OBSScene scene = window->GetCurrentScene();
		obs_source_t *source = obs_scene_get_source(scene);
		if (source)
			obs_source_video_render(source);
	} else {
		obs_render_main_view();
	}
2171
	gs_load_vertexbuffer(nullptr);
2172

2173 2174
	/* --------------------------------------- */

2175 2176 2177
	QSize previewSize = GetPixelSize(window->ui->preview);
	float right  = float(previewSize.width())  - window->previewX;
	float bottom = float(previewSize.height()) - window->previewY;
2178 2179 2180 2181

	gs_ortho(-window->previewX, right,
	         -window->previewY, bottom,
	         -100.0f, 100.0f);
2182
	gs_reset_viewport();
J
jp9000 已提交
2183 2184 2185

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

2186 2187
	/* --------------------------------------- */

2188 2189
	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
2190 2191 2192

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
2193 2194
}

2195 2196
/* Main class functions */

2197
obs_service_t *OBSBasic::GetService()
2198
{
2199
	if (!service) {
2200 2201
		service = obs_service_create("rtmp_common", NULL, NULL,
				nullptr);
2202 2203
		obs_service_release(service);
	}
2204 2205 2206
	return service;
}

2207
void OBSBasic::SetService(obs_service_t *newService)
2208
{
2209
	if (newService)
2210 2211 2212
		service = newService;
}

2213 2214 2215 2216 2217 2218 2219
bool OBSBasic::StreamingActive()
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

2220 2221 2222 2223 2224 2225
#ifdef _WIN32
#define IS_WIN32 1
#else
#define IS_WIN32 0
#endif

2226 2227
static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
2228
	return obs_reset_video(ovi);
2229 2230
}

2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243
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;
}

2244 2245 2246 2247 2248 2249
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 已提交
2250 2251
	else if (astrcmpi(name, "I444") == 0)
		return VIDEO_FORMAT_I444;
2252 2253 2254 2255 2256 2257 2258 2259 2260
#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 已提交
2261
		return VIDEO_FORMAT_RGBA;
2262 2263
}

2264
int OBSBasic::ResetVideo()
J
jp9000 已提交
2265
{
P
Palana 已提交
2266 2267
	ProfileScope("OBSBasic::ResetVideo");

J
jp9000 已提交
2268
	struct obs_video_info ovi;
2269
	int ret;
J
jp9000 已提交
2270

2271
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
2272

2273 2274 2275 2276 2277 2278 2279
	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 已提交
2280
	ovi.graphics_module = App()->GetRenderModule();
2281
	ovi.base_width     = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2282
			"Video", "BaseCX");
2283
	ovi.base_height    = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2284
			"Video", "BaseCY");
2285
	ovi.output_width   = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2286
			"Video", "OutputCX");
2287
	ovi.output_height  = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2288
			"Video", "OutputCY");
2289 2290 2291 2292 2293
	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;
J
jp9000 已提交
2294 2295
	ovi.adapter        = 0;
	ovi.gpu_conversion = true;
2296
	ovi.scale_type     = GetScaleType(basicConfig);
2297

2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313
	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);
	}

2314
	ret = AttemptToResetVideo(&ovi);
2315 2316
	if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
		/* Try OpenGL if DirectX fails on windows */
2317
		if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
2318 2319 2320 2321
			blog(LOG_WARNING, "Failed to initialize obs video (%d) "
					  "with graphics_module='%s', retrying "
					  "with graphics_module='%s'",
					  ret, ovi.graphics_module,
2322 2323
					  DL_OPENGL);
			ovi.graphics_module = DL_OPENGL;
2324 2325
			ret = AttemptToResetVideo(&ovi);
		}
2326 2327
	} else if (ret == OBS_VIDEO_SUCCESS) {
		ResizePreview(ovi.base_width, ovi.base_height);
2328 2329
		if (program)
			ResizeProgram(ovi.base_width, ovi.base_height);
2330 2331
	}

2332
	return ret;
J
jp9000 已提交
2333
}
J
jp9000 已提交
2334

2335
bool OBSBasic::ResetAudio()
J
jp9000 已提交
2336
{
P
Palana 已提交
2337 2338
	ProfileScope("OBSBasic::ResetAudio");

2339
	struct obs_audio_info ai;
2340
	ai.samples_per_sec = config_get_uint(basicConfig, "Audio",
2341 2342
			"SampleRate");

2343
	const char *channelSetupStr = config_get_string(basicConfig,
2344 2345 2346 2347 2348 2349 2350
			"Audio", "ChannelSetup");

	if (strcmp(channelSetupStr, "Mono") == 0)
		ai.speakers = SPEAKERS_MONO;
	else
		ai.speakers = SPEAKERS_STEREO;

J
jp9000 已提交
2351
	return obs_reset_audio(&ai);
J
jp9000 已提交
2352 2353
}

2354
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
2355
		const char *deviceDesc, int channel)
J
jp9000 已提交
2356
{
2357 2358
	obs_source_t *source;
	obs_data_t *settings;
J
jp9000 已提交
2359 2360 2361 2362
	bool same = false;

	source = obs_get_output_source(channel);
	if (source) {
2363
		settings = obs_source_get_settings(source);
J
jp9000 已提交
2364
		const char *curId = obs_data_get_string(settings, "device_id");
J
jp9000 已提交
2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375

		same = (strcmp(curId, deviceId) == 0);

		obs_data_release(settings);
		obs_source_release(source);
	}

	if (!same)
		obs_set_output_source(channel, nullptr);

	if (!same && strcmp(deviceId, "disabled") != 0) {
2376
		obs_data_t *settings = obs_data_create();
J
jp9000 已提交
2377
		obs_data_set_string(settings, "device_id", deviceId);
2378 2379
		source = obs_source_create(sourceId, deviceDesc, settings,
				nullptr);
J
jp9000 已提交
2380 2381 2382 2383 2384 2385 2386
		obs_data_release(settings);

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

J
jp9000 已提交
2387
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
2388
{
2389
	QSize  targetSize;
J
jp9000 已提交
2390

2391
	/* resize preview panel to fix to the top section of the window */
2392
	targetSize = GetPixelSize(ui->preview);
2393
	GetScaleAndCenterPos(int(cx), int(cy),
2394 2395
			targetSize.width()  - PREVIEW_EDGE_SIZE * 2,
			targetSize.height() - PREVIEW_EDGE_SIZE * 2,
2396
			previewX, previewY, previewScale);
J
jp9000 已提交
2397

2398 2399
	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);
J
jp9000 已提交
2400 2401
}

2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418
void OBSBasic::CloseDialogs()
{
	QList<QDialog*> childDialogs = this->findChildren<QDialog *>();
	if (!childDialogs.isEmpty()) {
		for (int i = 0; i < childDialogs.size(); ++i) {
			childDialogs.at(i)->close();
		}
	}

	for (QPointer<QWidget> &projector : projectors) {
		delete projector;
		projector.clear();
	}
}

void OBSBasic::ClearSceneData()
{
J
jp9000 已提交
2419 2420
	disableSaving++;

2421 2422 2423 2424 2425
	CloseDialogs();

	ClearVolumeControls();
	ClearListItems(ui->scenes);
	ClearListItems(ui->sources);
2426 2427
	ClearQuickTransitions();
	ui->transitions->clear();
2428 2429 2430 2431 2432 2433 2434

	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);
2435 2436 2437
	lastScene = nullptr;
	swapScene = nullptr;
	programScene = nullptr;
2438 2439 2440 2441 2442 2443 2444 2445 2446 2447

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

	obs_enum_sources(cb, nullptr);

J
jp9000 已提交
2448
	disableSaving--;
2449 2450 2451

	blog(LOG_INFO, "All scene data cleared");
	blog(LOG_INFO, "------------------------------------------------");
2452 2453
}

J
jp9000 已提交
2454
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
2455
{
2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466
	if (outputHandler && outputHandler->Active()) {
		QMessageBox::StandardButton button = QMessageBox::question(
				this, QTStr("ConfirmExit.Title"),
				QTStr("ConfirmExit.Text"));

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

2467 2468 2469 2470
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

2471 2472 2473 2474 2475
	if (updateCheckThread)
		updateCheckThread->wait();
	if (logUploadThread)
		logUploadThread->wait();

P
Palana 已提交
2476 2477
	signalHandlers.clear();

J
jp9000 已提交
2478
	SaveProjectNow();
J
jp9000 已提交
2479
	disableSaving++;
J
jp9000 已提交
2480

2481 2482 2483
	/* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
	 * sources, etc) so that all references are released before shutdown */
	ClearSceneData();
2484 2485
}

J
jp9000 已提交
2486
void OBSBasic::changeEvent(QEvent *event)
2487
{
J
jp9000 已提交
2488 2489
	/* TODO */
	UNUSED_PARAMETER(event);
2490 2491
}

2492 2493
void OBSBasic::on_actionShow_Recordings_triggered()
{
2494 2495 2496 2497
	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");
2498 2499 2500
	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

P
Palana 已提交
2501 2502
void OBSBasic::on_actionRemux_triggered()
{
2503 2504 2505 2506
	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");
P
Palana 已提交
2507 2508 2509 2510
	OBSRemux remux(path, this);
	remux.exec();
}

P
Palana 已提交
2511 2512 2513 2514 2515 2516
void OBSBasic::on_action_Settings_triggered()
{
	OBSBasicSettings settings(this);
	settings.exec();
}

J
jp9000 已提交
2517 2518
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
2519 2520 2521 2522 2523
	if (advAudioWindow != nullptr) {
		advAudioWindow->raise();
		return;
	}

J
jp9000 已提交
2524 2525 2526
	advAudioWindow = new OBSBasicAdvAudio(this);
	advAudioWindow->show();
	advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
2527 2528 2529

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

2532 2533 2534 2535 2536
void OBSBasic::on_advAudioProps_clicked()
{
	on_actionAdvAudioProperties_triggered();
}

2537 2538 2539 2540 2541
void OBSBasic::on_advAudioProps_destroyed()
{
	advAudioWindow = nullptr;
}

2542 2543
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
2544
{
2545
	obs_source_t *source = NULL;
J
jp9000 已提交
2546

2547 2548 2549 2550
	if (sceneChanging)
		return;

	if (current) {
2551
		obs_scene_t *scene;
J
jp9000 已提交
2552

P
Palana 已提交
2553
		scene = GetOBSRef<OBSScene>(current);
2554
		source = obs_scene_get_source(scene);
2555 2556
	}

2557
	SetCurrentScene(source);
2558 2559

	UNUSED_PARAMETER(prev);
2560 2561
}

J
jp9000 已提交
2562 2563
void OBSBasic::EditSceneName()
{
2564 2565 2566 2567 2568 2569
	QListWidgetItem *item = ui->scenes->currentItem();
	Qt::ItemFlags flags   = item->flags();

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

J
jp9000 已提交
2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594
static void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
		const char *slot)
{
	QAction *action;
	std::vector<MonitorInfo> monitors;
	GetMonitors(monitors);

	for (int i = 0; (size_t)i < monitors.size(); i++) {
		const MonitorInfo &monitor = monitors[i];

		QString str = QString("%1 %2: %3x%4 @ %5,%6").
			arg(QTStr("Display"),
			    QString::number(i),
			    QString::number((int)monitor.cx),
			    QString::number((int)monitor.cy),
			    QString::number((int)monitor.x),
			    QString::number((int)monitor.y));

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

J
jp9000 已提交
2595
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
2596
{
J
jp9000 已提交
2597
	QListWidgetItem *item = ui->scenes->itemAt(pos);
J
jp9000 已提交
2598
	QPointer<QMenu> sceneProjectorMenu;
J
jp9000 已提交
2599

2600
	QMenu popup(this);
J
jp9000 已提交
2601
	QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
J
jp9000 已提交
2602 2603 2604
	popup.addAction(QTStr("Add"),
			this, SLOT(on_actionAddScene_triggered()));

P
Palana 已提交
2605 2606
	if (item) {
		popup.addSeparator();
2607 2608
		popup.addAction(QTStr("Duplicate"),
				this, SLOT(DuplicateSelectedScene()));
P
Palana 已提交
2609 2610
		popup.addAction(QTStr("Rename"),
				this, SLOT(EditSceneName()));
J
jp9000 已提交
2611
		popup.addAction(QTStr("Remove"),
2612 2613
				this, SLOT(RemoveSelectedScene()),
				DeleteKeys.front());
J
jp9000 已提交
2614
		popup.addSeparator();
J
jp9000 已提交
2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627

		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();
J
jp9000 已提交
2628 2629 2630 2631 2632
		sceneProjectorMenu = new QMenu(QTStr("SceneProjector"));
		AddProjectorMenuMonitors(sceneProjectorMenu, this,
				SLOT(OpenSceneProjector()));
		popup.addMenu(sceneProjectorMenu);
		popup.addSeparator();
J
jp9000 已提交
2633 2634
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenSceneFilters()));
P
Palana 已提交
2635
	}
J
jp9000 已提交
2636 2637

	popup.exec(QCursor::pos());
2638 2639
}

J
jp9000 已提交
2640
void OBSBasic::on_actionAddScene_triggered()
2641
{
2642
	string name;
S
Socapex 已提交
2643
	QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
P
Palana 已提交
2644 2645 2646

	int i = 1;
	QString placeHolderText = format.arg(i);
2647
	obs_source_t *source = nullptr;
P
Palana 已提交
2648 2649
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
P
Palana 已提交
2650
		placeHolderText = format.arg(++i);
P
Palana 已提交
2651
	}
S
Socapex 已提交
2652

J
jp9000 已提交
2653
	bool accepted = NameDialog::AskForName(this,
2654 2655
			QTStr("Basic.Main.AddSceneDlg.Title"),
			QTStr("Basic.Main.AddSceneDlg.Text"),
S
Socapex 已提交
2656 2657
			name,
			placeHolderText);
2658

J
jp9000 已提交
2659
	if (accepted) {
J
jp9000 已提交
2660 2661
		if (name.empty()) {
			QMessageBox::information(this,
2662 2663
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
J
jp9000 已提交
2664 2665 2666 2667
			on_actionAddScene_triggered();
			return;
		}

2668
		obs_source_t *source = obs_get_source_by_name(name.c_str());
2669
		if (source) {
J
jp9000 已提交
2670
			QMessageBox::information(this,
2671 2672
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));
2673 2674

			obs_source_release(source);
J
jp9000 已提交
2675
			on_actionAddScene_triggered();
2676 2677 2678
			return;
		}

2679
		obs_scene_t *scene = obs_scene_create(name.c_str());
2680
		source = obs_scene_get_source(scene);
2681
		AddScene(source);
2682
		SetCurrentScene(source);
2683
		obs_scene_release(scene);
2684
	}
2685 2686
}

J
jp9000 已提交
2687
void OBSBasic::on_actionRemoveScene_triggered()
2688
{
2689
	OBSScene     scene  = GetCurrentScene();
2690
	obs_source_t *source = obs_scene_get_source(scene);
2691 2692 2693

	if (source && QueryRemoveSource(source))
		obs_source_remove(source);
2694 2695
}

J
jp9000 已提交
2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715
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;
}

J
jp9000 已提交
2716
void OBSBasic::on_actionSceneUp_triggered()
2717
{
J
jp9000 已提交
2718
	ChangeSceneIndex(true, -1, 0);
2719 2720
}

J
jp9000 已提交
2721
void OBSBasic::on_actionSceneDown_triggered()
2722
{
J
jp9000 已提交
2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734
	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);
2735 2736
}

J
jp9000 已提交
2737
void OBSBasic::on_sources_itemSelectionChanged()
2738
{
J
jp9000 已提交
2739
	SignalBlocker sourcesSignalBlocker(ui->sources);
2740

J
jp9000 已提交
2741
	auto updateItemSelection = [&]()
2742 2743
	{
		ignoreSelectionUpdate = true;
J
jp9000 已提交
2744 2745 2746 2747 2748 2749 2750
		for (int i = 0; i < ui->sources->count(); i++)
		{
			QListWidgetItem *wItem = ui->sources->item(i);
			OBSSceneItem item = GetOBSRef<OBSSceneItem>(wItem);

			obs_sceneitem_select(item, wItem->isSelected());
		}
2751 2752
		ignoreSelectionUpdate = false;
	};
J
jp9000 已提交
2753
	using updateItemSelection_t = decltype(updateItemSelection);
2754 2755

	obs_scene_atomic_update(GetCurrentScene(),
J
jp9000 已提交
2756
			[](void *data, obs_scene_t *)
2757
	{
J
jp9000 已提交
2758 2759
		(*static_cast<updateItemSelection_t*>(data))();
	}, static_cast<void*>(&updateItemSelection));
2760 2761
}

J
jp9000 已提交
2762 2763
void OBSBasic::EditSceneItemName()
{
2764
	QListWidgetItem *item = GetTopSelectedSourceItem();
2765
	Qt::ItemFlags flags   = item->flags();
P
Palana 已提交
2766
	OBSSceneItem sceneItem= GetOBSRef<OBSSceneItem>(item);
2767 2768
	obs_source_t *source  = obs_sceneitem_get_source(sceneItem);
	const char *name      = obs_source_get_name(source);
2769

2770
	item->setText(QT_UTF8(name));
2771
	item->setFlags(flags | Qt::ItemIsEditable);
2772
	ui->sources->removeItemWidget(item);
2773 2774
	ui->sources->editItem(item);
	item->setFlags(flags);
J
jp9000 已提交
2775 2776
}

J
jp9000 已提交
2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841
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;
}

J
jp9000 已提交
2842
void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
2843
{
2844
	QMenu popup(this);
J
jp9000 已提交
2845 2846
	QPointer<QMenu> previewProjector;
	QPointer<QMenu> sourceProjector;
J
jp9000 已提交
2847 2848 2849 2850 2851 2852

	if (preview) {
		QAction *action = popup.addAction(
				QTStr("Basic.Main.PreviewConextMenu.Enable"),
				this, SLOT(TogglePreview()));
		action->setCheckable(true);
2853 2854
		action->setChecked(
				obs_display_enabled(ui->preview->GetDisplay()));
2855 2856
		if (IsPreviewProgramMode())
			action->setEnabled(false);
J
jp9000 已提交
2857

J
jp9000 已提交
2858 2859 2860 2861 2862 2863
		previewProjector = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjector, this,
				SLOT(OpenPreviewProjector()));

		popup.addMenu(previewProjector);

J
jp9000 已提交
2864 2865 2866
		popup.addSeparator();
	}

J
jp9000 已提交
2867 2868 2869 2870 2871 2872 2873 2874
	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

	if (item) {
		if (addSourceMenu)
			popup.addSeparator();

J
John Bradley 已提交
2875
		OBSSceneItem sceneItem = GetSceneItem(item);
2876
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
J
jp9000 已提交
2877 2878 2879
		uint32_t flags = obs_source_get_output_flags(source);
		bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) ==
			OBS_SOURCE_ASYNC_VIDEO;
J
John Bradley 已提交
2880 2881
		QAction *action;

J
jp9000 已提交
2882 2883
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
2884 2885
		popup.addAction(QTStr("Remove"), this,
				SLOT(on_actionRemoveSource_triggered()),
2886
				DeleteKeys.front());
J
jp9000 已提交
2887 2888
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
J
jp9000 已提交
2889
		popup.addMenu(ui->transformMenu);
J
jp9000 已提交
2890 2891 2892 2893 2894 2895

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

		popup.addSeparator();
J
jp9000 已提交
2896 2897 2898 2899
		if (isAsyncVideo) {
			popup.addMenu(AddDeinterlacingMenu(source));
			popup.addSeparator();
		}
J
jp9000 已提交
2900
		popup.addMenu(sourceProjector);
J
jp9000 已提交
2901
		popup.addSeparator();
J
John Bradley 已提交
2902 2903 2904 2905 2906 2907 2908

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

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

J
jp9000 已提交
2909 2910
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenFilters()));
J
jp9000 已提交
2911 2912 2913 2914 2915
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));
	}

	popup.exec(QCursor::pos());
2916 2917
}

J
jp9000 已提交
2918 2919
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
2920 2921
	if (ui->scenes->count())
		CreateSourcePopupMenu(ui->sources->itemAt(pos), false);
J
jp9000 已提交
2922 2923
}

P
Palana 已提交
2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935
void OBSBasic::on_sources_itemDoubleClicked(QListWidgetItem *witem)
{
	if (!witem)
		return;

	OBSSceneItem item = GetSceneItem(witem);
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreatePropertiesWindow(source);
}

J
jp9000 已提交
2936
void OBSBasic::AddSource(const char *id)
2937
{
2938 2939 2940
	if (id && *id) {
		OBSBasicSourceSelect sourceSelect(this, id);
		sourceSelect.exec();
2941 2942
		if (sourceSelect.newSource)
			CreatePropertiesWindow(sourceSelect.newSource);
2943
	}
2944 2945
}

2946
QMenu *OBSBasic::CreateAddSourcePopupMenu()
2947
{
2948
	const char *type;
J
jp9000 已提交
2949 2950
	bool foundValues = false;
	size_t idx = 0;
2951

2952
	QMenu *popup = new QMenu(QTStr("Add"), this);
J
jp9000 已提交
2953
	while (obs_enum_input_types(idx++, &type)) {
2954
		const char *name = obs_source_get_display_name(type);
2955

2956 2957 2958
		if (strcmp(type, "scene") == 0)
			continue;

J
jp9000 已提交
2959 2960
		QAction *popupItem = new QAction(QT_UTF8(name), this);
		popupItem->setData(QT_UTF8(type));
2961 2962 2963
		connect(popupItem, SIGNAL(triggered(bool)),
				this, SLOT(AddSourceFromAction()));
		popup->addAction(popupItem);
2964

J
jp9000 已提交
2965
		foundValues = true;
2966 2967
	}

2968 2969 2970
	if (!foundValues) {
		delete popup;
		popup = nullptr;
2971
	}
2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997

	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).
		QMessageBox::information(this,
				QTStr("Basic.Main.AddSourceHelp.Title"),
				QTStr("Basic.Main.AddSourceHelp.Text"));
		return;
	}

	QPointer<QMenu> popup = CreateAddSourcePopupMenu();
	if (popup)
		popup->exec(pos);
2998 2999
}

J
jp9000 已提交
3000
void OBSBasic::on_actionAddSource_triggered()
3001
{
J
jp9000 已提交
3002
	AddSourcePopupMenu(QCursor::pos());
3003 3004
}

J
jp9000 已提交
3005
void OBSBasic::on_actionRemoveSource_triggered()
3006
{
3007
	OBSSceneItem item   = GetCurrentSceneItem();
3008
	obs_source_t *source = obs_sceneitem_get_source(item);
3009 3010

	if (source && QueryRemoveSource(source))
J
jp9000 已提交
3011
		obs_sceneitem_remove(item);
3012 3013
}

J
John Bradley 已提交
3014 3015 3016 3017 3018 3019 3020 3021 3022
void OBSBasic::on_actionInteract_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreateInteractionWindow(source);
}

J
jp9000 已提交
3023
void OBSBasic::on_actionSourceProperties_triggered()
3024
{
3025
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3026
	OBSSource source = obs_sceneitem_get_source(item);
3027

3028 3029
	if (source)
		CreatePropertiesWindow(source);
3030 3031
}

J
jp9000 已提交
3032
void OBSBasic::on_actionSourceUp_triggered()
3033
{
J
jp9000 已提交
3034
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3035
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
3036
}
J
jp9000 已提交
3037

J
jp9000 已提交
3038
void OBSBasic::on_actionSourceDown_triggered()
3039
{
J
jp9000 已提交
3040
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3041
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
3042 3043
}

J
jp9000 已提交
3044 3045 3046
void OBSBasic::on_actionMoveUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3047
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
J
jp9000 已提交
3048 3049 3050 3051 3052
}

void OBSBasic::on_actionMoveDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3053
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
J
jp9000 已提交
3054 3055 3056 3057 3058
}

void OBSBasic::on_actionMoveToTop_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3059
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_TOP);
J
jp9000 已提交
3060 3061 3062 3063 3064
}

void OBSBasic::on_actionMoveToBottom_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3065
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_BOTTOM);
J
jp9000 已提交
3066 3067
}

3068
static BPtr<char> ReadLogFile(const char *log)
J
jp9000 已提交
3069
{
3070
	char logDir[512];
3071
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
3072
		return nullptr;
J
jp9000 已提交
3073 3074 3075 3076 3077

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

3078
	BPtr<char> file = os_quick_read_utf8_file(path.c_str());
J
jp9000 已提交
3079 3080 3081 3082 3083 3084 3085 3086
	if (!file)
		blog(LOG_WARNING, "Failed to read log file %s", path.c_str());

	return file;
}

void OBSBasic::UploadLog(const char *file)
{
3087
	BPtr<char> fileString{ReadLogFile(file)};
J
jp9000 已提交
3088

3089
	if (!fileString)
J
jp9000 已提交
3090 3091
		return;

3092
	if (!*fileString)
J
jp9000 已提交
3093 3094 3095 3096
		return;

	ui->menuLogFiles->setEnabled(false);

3097
	auto data_deleter = [](obs_data_t *d) { obs_data_release(d); };
3098
	using data_t = unique_ptr<struct obs_data, decltype(data_deleter)>;
J
jp9000 已提交
3099

3100 3101 3102
	data_t content{obs_data_create(), data_deleter};
	data_t files{obs_data_create(), data_deleter};
	data_t request{obs_data_create(), data_deleter};
J
jp9000 已提交
3103

3104
	obs_data_set_string(content.get(), "content", fileString);
J
jp9000 已提交
3105

3106 3107 3108 3109 3110 3111 3112 3113 3114 3115
	obs_data_set_obj(files.get(), file, content.get());

	stringstream ss;
	ss << "OBS " << App()->GetVersionString()
	   << " log file uploaded at " << CurrentDateTimeString();
	obs_data_set_string(request.get(), "description", ss.str().c_str());
	obs_data_set_bool(request.get(), "public", false);
	obs_data_set_obj(request.get(), "files", files.get());

	const char *json = obs_data_get_json(request.get());
F
fryshorts 已提交
3116
	if (!json) {
3117 3118 3119 3120
		blog(LOG_ERROR, "Failed to get JSON data for log upload");
		return;
	}

F
fryshorts 已提交
3121 3122 3123
	QBuffer *postData = new QBuffer();
	postData->setData(json, (int) strlen(json));

3124 3125 3126 3127
	if (logUploadThread) {
		logUploadThread->wait();
		delete logUploadThread;
	}
F
fryshorts 已提交
3128

3129 3130 3131 3132 3133 3134 3135
	RemoteTextThread *thread = new RemoteTextThread(
			"https://api.github.com/gists",
			"application/json", json);
	logUploadThread = thread;
	connect(thread, &RemoteTextThread::Result,
			this, &OBSBasic::logUploadFinished);
	logUploadThread->start();
J
jp9000 已提交
3136 3137
}

P
Palana 已提交
3138 3139
void OBSBasic::on_actionShowLogs_triggered()
{
3140
	char logDir[512];
3141
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
3142 3143
		return;

P
Palana 已提交
3144 3145 3146 3147
	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
3148 3149 3150 3151 3152 3153 3154 3155 3156 3157
void OBSBasic::on_actionUploadCurrentLog_triggered()
{
	UploadLog(App()->GetCurrentLog());
}

void OBSBasic::on_actionUploadLastLog_triggered()
{
	UploadLog(App()->GetLastLog());
}

3158 3159 3160
void OBSBasic::on_actionViewCurrentLog_triggered()
{
	char logDir[512];
3161
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173
		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 已提交
3174 3175 3176 3177 3178
void OBSBasic::on_actionCheckForUpdates_triggered()
{
	CheckForUpdates();
}

3179
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
J
jp9000 已提交
3180 3181 3182
{
	ui->menuLogFiles->setEnabled(true);

3183
	if (text.isEmpty()) {
J
jp9000 已提交
3184 3185
		QMessageBox::information(this,
				QTStr("LogReturnDialog.ErrorUploadingLog"),
3186
				error);
J
jp9000 已提交
3187 3188 3189
		return;
	}

3190
	obs_data_t *returnData = obs_data_create_from_json(QT_TO_UTF8(text));
F
fryshorts 已提交
3191
	QString logURL         = obs_data_get_string(returnData, "html_url");
J
jp9000 已提交
3192 3193 3194 3195 3196 3197
	obs_data_release(returnData);

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

J
jp9000 已提交
3198
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
3199
		obs_source_t *source, const string &name)
3200
{
3201 3202 3203 3204
	const char *prevName = obs_source_get_name(source);
	if (name == prevName)
		return;

3205
	obs_source_t    *foundSource = obs_get_source_by_name(name.c_str());
3206
	QListWidgetItem *listItem    = listWidget->currentItem();
3207

3208
	if (foundSource || name.empty()) {
3209
		listItem->setText(QT_UTF8(prevName));
3210

3211
		if (foundSource) {
3212 3213 3214 3215 3216 3217 3218 3219 3220
			QMessageBox::information(parent,
				QTStr("NameExists.Title"),
				QTStr("NameExists.Text"));
		} else if (name.empty()) {
			QMessageBox::information(parent,
				QTStr("NoNameEntered.Title"),
				QTStr("NoNameEntered.Text"));
		}

3221 3222 3223
		obs_source_release(foundSource);
	} else {
		listItem->setText(QT_UTF8(name.c_str()));
3224
		obs_source_set_name(source, name.c_str());
3225 3226 3227
	}
}

J
jp9000 已提交
3228 3229 3230 3231 3232
void OBSBasic::SceneNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSScene  scene = GetCurrentScene();
	QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
3233
	string    text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
3234 3235 3236 3237

	if (!scene)
		return;

3238
	obs_source_t *source = obs_scene_get_source(scene);
3239
	RenameListItem(this, ui->scenes, source, text);
J
jp9000 已提交
3240 3241 3242 3243 3244 3245 3246 3247 3248

	UNUSED_PARAMETER(endHint);
}

void OBSBasic::SceneItemNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSSceneItem item  = GetCurrentSceneItem();
	QLineEdit    *edit = qobject_cast<QLineEdit*>(editor);
3249
	string       text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
3250 3251 3252 3253

	if (!item)
		return;

3254
	obs_source_t *source = obs_sceneitem_get_source(item);
3255
	RenameListItem(this, ui->sources, source, text);
J
jp9000 已提交
3256

3257 3258 3259 3260
	QListWidgetItem *listItem = ui->sources->currentItem();
	listItem->setText(QString());
	SetupVisibilityItem(ui->sources, listItem, item);

J
jp9000 已提交
3261 3262 3263
	UNUSED_PARAMETER(endHint);
}

J
jp9000 已提交
3264 3265 3266 3267 3268 3269 3270 3271
void OBSBasic::OpenFilters()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
3272 3273 3274 3275 3276 3277 3278 3279
void OBSBasic::OpenSceneFilters()
{
	OBSScene scene = GetCurrentScene();
	OBSSource source = obs_scene_get_source(scene);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
3280 3281 3282 3283 3284 3285 3286 3287 3288
#define RECORDING_START \
	"==== Recording Start ==============================================="
#define RECORDING_STOP \
	"==== Recording Stop ================================================"
#define STREAMING_START \
	"==== Streaming Start ==============================================="
#define STREAMING_STOP \
	"==== Streaming Stop ================================================"

3289 3290 3291 3292
void OBSBasic::StartStreaming()
{
	SaveProject();

J
jp9000 已提交
3293 3294
	ui->streamButton->setEnabled(false);
	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
3295

J
jp9000 已提交
3296 3297 3298
	if (!outputHandler->StartStreaming(service)) {
		ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
		ui->streamButton->setEnabled(true);
3299 3300 3301 3302 3303 3304 3305 3306 3307
	}
}

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

	if (outputHandler->StreamingActive())
		outputHandler->StopStreaming();
J
jp9000 已提交
3308

3309
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3310
		ui->profileMenu->setEnabled(true);
3311
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3312
	}
3313 3314
}

J
jp9000 已提交
3315 3316 3317 3318 3319 3320 3321
void OBSBasic::ForceStopStreaming()
{
	SaveProject();

	if (outputHandler->StreamingActive())
		outputHandler->ForceStopStreaming();

3322
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3323
		ui->profileMenu->setEnabled(true);
3324
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343
	}
}

void OBSBasic::StreamDelayStarting(int sec)
{
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
	ui->streamButton->setEnabled(true);

	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);
3344 3345 3346 3347 3348

	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
		App()->IncrementSleepInhibition();
	}
J
jp9000 已提交
3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368
}

void OBSBasic::StreamDelayStopping(int sec)
{
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
	ui->streamButton->setEnabled(true);

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

3369
void OBSBasic::StreamingStart()
3370
{
3371
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
J
jp9000 已提交
3372
	ui->streamButton->setEnabled(true);
J
jp9000 已提交
3373
	ui->statusbar->StreamStarted(outputHandler->streamOutput);
3374 3375 3376 3377 3378 3379

	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
		App()->IncrementSleepInhibition();
	}

3380
	blog(LOG_INFO, STREAMING_START);
3381 3382
}

3383
void OBSBasic::StreamingStop(int code)
3384
{
3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399
	const char *errorMessage;

	switch (code) {
	case OBS_OUTPUT_BAD_PATH:
		errorMessage = Str("Output.ConnectFail.BadPath");
		break;

	case OBS_OUTPUT_CONNECT_FAILED:
		errorMessage = Str("Output.ConnectFail.ConnectFailed");
		break;

	case OBS_OUTPUT_INVALID_STREAM:
		errorMessage = Str("Output.ConnectFail.InvalidStream");
		break;

3400
	default:
3401 3402 3403 3404 3405 3406 3407 3408 3409 3410
	case OBS_OUTPUT_ERROR:
		errorMessage = Str("Output.ConnectFail.Error");
		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 */
		errorMessage = Str("Output.ConnectFail.Disconnected");
	}

J
jp9000 已提交
3411
	ui->statusbar->StreamStopped();
3412

3413
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
J
jp9000 已提交
3414
	ui->streamButton->setEnabled(true);
3415

3416
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3417
		ui->profileMenu->setEnabled(true);
3418 3419
		App()->DecrementSleepInhibition();
	}
3420 3421

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

3423 3424 3425 3426
	if (code != OBS_OUTPUT_SUCCESS)
		QMessageBox::information(this,
				QTStr("Output.ConnectFail.Title"),
				QT_UTF8(errorMessage));
J
jp9000 已提交
3427 3428 3429 3430 3431 3432

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

3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448
void OBSBasic::StartRecording()
{
	SaveProject();

	if (!outputHandler->RecordingActive())
		outputHandler->StartRecording();
}

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

	if (outputHandler->RecordingActive())
		outputHandler->StopRecording();
J
jp9000 已提交
3449

3450
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3451
		ui->profileMenu->setEnabled(true);
3452
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3453
	}
3454 3455
}

P
Palana 已提交
3456 3457
void OBSBasic::RecordingStart()
{
J
jp9000 已提交
3458
	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
3459
	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
3460 3461 3462 3463 3464 3465

	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
		App()->IncrementSleepInhibition();
	}

3466
	blog(LOG_INFO, RECORDING_START);
P
Palana 已提交
3467 3468
}

3469
void OBSBasic::RecordingStop(int code)
3470
{
P
Palana 已提交
3471
	ui->statusbar->RecordingStopped();
3472
	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
3473
	blog(LOG_INFO, RECORDING_STOP);
3474

J
jp9000 已提交
3475
	if (code == OBS_OUTPUT_UNSUPPORTED) {
3476 3477 3478
		QMessageBox::information(this,
				QTStr("Output.RecordFail.Title"),
				QTStr("Output.RecordFail.Unsupported"));
J
jp9000 已提交
3479

J
jp9000 已提交
3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490
	} else if (code == OBS_OUTPUT_NO_SPACE) {
		QMessageBox::information(this,
				QTStr("Output.RecordNoSpace.Title"),
				QTStr("Output.RecordNoSpace.Msg"));

	} else if (code != OBS_OUTPUT_SUCCESS) {
		QMessageBox::information(this,
				QTStr("Output.RecordError.Title"),
				QTStr("Output.RecordError.Msg"));
	}

3491
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3492
		ui->profileMenu->setEnabled(true);
3493
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3494
	}
3495
}
3496

3497 3498
void OBSBasic::on_streamButton_clicked()
{
J
jp9000 已提交
3499
	if (outputHandler->StreamingActive()) {
3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
				"WarnBeforeStoppingStream");

		if (confirm) {
			QMessageBox::StandardButton button =
				QMessageBox::question(this,
						QTStr("ConfirmStop.Title"),
						QTStr("ConfirmStop.Text"));

			if (button == QMessageBox::No)
				return;
		}

3513
		StopStreaming();
3514
	} else {
3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
				"WarnBeforeStartingStream");

		if (confirm) {
			QMessageBox::StandardButton button =
				QMessageBox::question(this,
						QTStr("ConfirmStart.Title"),
						QTStr("ConfirmStart.Text"));

			if (button == QMessageBox::No)
				return;
		}

3528
		StartStreaming();
3529 3530 3531 3532 3533
	}
}

void OBSBasic::on_recordButton_clicked()
{
3534 3535 3536 3537
	if (outputHandler->RecordingActive())
		StopRecording();
	else
		StartRecording();
J
jp9000 已提交
3538 3539
}

J
jp9000 已提交
3540
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
3541
{
3542 3543
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
3544
}
3545

3546 3547 3548 3549 3550 3551
void OBSBasic::on_actionWebsite_triggered()
{
	QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571
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));
}

3572 3573 3574 3575 3576 3577 3578 3579 3580
QListWidgetItem *OBSBasic::GetTopSelectedSourceItem()
{
	QList<QListWidgetItem*> selectedItems = ui->sources->selectedItems();
	QListWidgetItem *topItem = nullptr;
	if (selectedItems.size() != 0)
		topItem = selectedItems[0];
	return topItem;
}

J
jp9000 已提交
3581 3582
void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos)
{
3583
	CreateSourcePopupMenu(GetTopSelectedSourceItem(), true);
J
jp9000 已提交
3584 3585 3586 3587 3588 3589 3590 3591

	UNUSED_PARAMETER(pos);
}

void OBSBasic::on_previewDisabledLabel_customContextMenuRequested(
		const QPoint &pos)
{
	QMenu popup(this);
J
jp9000 已提交
3592
	QPointer<QMenu> previewProjector;
J
jp9000 已提交
3593 3594 3595 3596 3597

	QAction *action = popup.addAction(
			QTStr("Basic.Main.PreviewConextMenu.Enable"),
			this, SLOT(TogglePreview()));
	action->setCheckable(true);
3598
	action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
J
jp9000 已提交
3599

J
jp9000 已提交
3600 3601 3602 3603 3604
	previewProjector = new QMenu(QTStr("PreviewProjector"));
	AddProjectorMenuMonitors(previewProjector, this,
			SLOT(OpenPreviewProjector()));

	popup.addMenu(previewProjector);
J
jp9000 已提交
3605 3606 3607 3608 3609
	popup.exec(QCursor::pos());

	UNUSED_PARAMETER(pos);
}

3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631
void OBSBasic::on_actionAlwaysOnTop_triggered()
{
	CloseDialogs();

	/* 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 */
	QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop",
			Qt::QueuedConnection);
}

void OBSBasic::ToggleAlwaysOnTop()
{
	bool isAlwaysOnTop = IsAlwaysOnTop(this);

	ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop);
	SetAlwaysOnTop(this, !isAlwaysOnTop);

	show();
}

3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694
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;
	} 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);
}

3695
config_t *OBSBasic::Config() const
3696 3697 3698
{
	return basicConfig;
}
J
jp9000 已提交
3699 3700 3701

void OBSBasic::on_actionEditTransform_triggered()
{
3702 3703 3704
	if (transformWindow)
		transformWindow->close();

J
jp9000 已提交
3705 3706
	transformWindow = new OBSBasicTransform(this);
	transformWindow->show();
3707
	transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
J
jp9000 已提交
3708 3709 3710 3711
}

void OBSBasic::on_actionResetTransform_triggered()
{
3712
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
3713 3714 3715 3716
	{
		if (!obs_sceneitem_selected(item))
			return true;

J
jp9000 已提交
3717 3718
		obs_sceneitem_defer_update_begin(item);

3719
		obs_transform_info info;
J
jp9000 已提交
3720 3721 3722 3723 3724 3725 3726 3727 3728
		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 已提交
3729 3730 3731 3732 3733
		obs_sceneitem_crop crop = {};
		obs_sceneitem_set_crop(item, &crop);

		obs_sceneitem_defer_update_end(item);

J
jp9000 已提交
3734 3735 3736 3737 3738 3739 3740 3741
		UNUSED_PARAMETER(scene);
		UNUSED_PARAMETER(param);
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
}

3742
static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
J
jp9000 已提交
3743 3744 3745 3746 3747
{
	matrix4 boxTransform;
	obs_sceneitem_get_box_transform(item, &boxTransform);

	vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
3748
	vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
J
jp9000 已提交
3749

3750
	auto GetMinPos = [&] (float x, float y)
J
jp9000 已提交
3751 3752 3753 3754
	{
		vec3 pos;
		vec3_set(&pos, x, y, 0.0f);
		vec3_transform(&pos, &pos, &boxTransform);
3755 3756
		vec3_min(&tl, &tl, &pos);
		vec3_max(&br, &br, &pos);
J
jp9000 已提交
3757 3758
	};

3759 3760 3761 3762 3763 3764
	GetMinPos(0.0f, 0.0f);
	GetMinPos(1.0f, 0.0f);
	GetMinPos(0.0f, 1.0f);
	GetMinPos(1.0f, 1.0f);
}

3765
static vec3 GetItemTL(obs_sceneitem_t *item)
3766 3767 3768
{
	vec3 tl, br;
	GetItemBox(item, tl, br);
J
jp9000 已提交
3769 3770 3771
	return tl;
}

3772
static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
J
jp9000 已提交
3773 3774 3775 3776
{
	vec3 newTL;
	vec2 pos;

J
jp9000 已提交
3777
	obs_sceneitem_get_pos(item, &pos);
J
jp9000 已提交
3778 3779 3780
	newTL = GetItemTL(item);
	pos.x += tl.x - newTL.x;
	pos.y += tl.y - newTL.y;
J
jp9000 已提交
3781
	obs_sceneitem_set_pos(item, &pos);
J
jp9000 已提交
3782 3783
}

3784
static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
3785 3786 3787 3788 3789 3790 3791 3792 3793
		void *param)
{
	if (!obs_sceneitem_selected(item))
		return true;

	float rot = *reinterpret_cast<float*>(param);

	vec3 tl = GetItemTL(item);

J
jp9000 已提交
3794
	rot += obs_sceneitem_get_rot(item);
J
jp9000 已提交
3795 3796
	if (rot >= 360.0f)       rot -= 360.0f;
	else if (rot <= -360.0f) rot += 360.0f;
J
jp9000 已提交
3797
	obs_sceneitem_set_rot(item, rot);
J
jp9000 已提交
3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823

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

3824
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
3825 3826 3827 3828 3829 3830 3831 3832 3833 3834
		void *param)
{
	vec2 &mul = *reinterpret_cast<vec2*>(param);

	if (!obs_sceneitem_selected(item))
		return true;

	vec3 tl = GetItemTL(item);

	vec2 scale;
J
jp9000 已提交
3835
	obs_sceneitem_get_scale(item, &scale);
J
jp9000 已提交
3836
	vec2_mul(&scale, &scale, &mul);
J
jp9000 已提交
3837
	obs_sceneitem_set_scale(item, &scale);
J
jp9000 已提交
3838 3839

	SetItemTL(item, tl);
J
jp9000 已提交
3840 3841

	UNUSED_PARAMETER(scene);
J
jp9000 已提交
3842 3843 3844 3845 3846
	return true;
}

void OBSBasic::on_actionFlipHorizontal_triggered()
{
J
jp9000 已提交
3847 3848
	vec2 scale;
	vec2_set(&scale, -1.0f, 1.0f);
J
jp9000 已提交
3849 3850 3851 3852 3853 3854
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

void OBSBasic::on_actionFlipVertical_triggered()
{
J
jp9000 已提交
3855 3856
	vec2 scale;
	vec2_set(&scale, 1.0f, -1.0f);
J
jp9000 已提交
3857 3858 3859 3860
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

3861
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
3862 3863 3864 3865 3866 3867 3868 3869 3870 3871
		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);

3872
	obs_transform_info itemInfo;
J
jp9000 已提交
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
	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()
{
3905
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930
	{
		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 已提交
3931
		UNUSED_PARAMETER(param);
3932 3933 3934 3935
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
J
jp9000 已提交
3936
}
J
jp9000 已提交
3937

3938 3939 3940 3941 3942 3943 3944
void OBSBasic::EnablePreviewDisplay(bool enable)
{
	obs_display_set_enabled(ui->preview->GetDisplay(), enable);
	ui->preview->setVisible(enable);
	ui->previewDisabledLabel->setVisible(!enable);
}

J
jp9000 已提交
3945 3946
void OBSBasic::TogglePreview()
{
3947 3948
	previewEnabled = !previewEnabled;
	EnablePreviewDisplay(previewEnabled);
J
jp9000 已提交
3949
}
3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960

void OBSBasic::Nudge(int dist, MoveDir dir)
{
	struct MoveInfo {
		float dist;
		MoveDir dir;
	} info = {(float)dist, dir};

	auto func = [] (obs_scene_t*, obs_sceneitem_t *item, void *param)
	{
		MoveInfo *info = reinterpret_cast<MoveInfo*>(param);
J
jp9000 已提交
3961
		struct vec2 dir;
3962 3963
		struct vec2 pos;

J
jp9000 已提交
3964 3965
		vec2_set(&dir, 0.0f, 0.0f);

3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988
		if (!obs_sceneitem_selected(item))
			return true;

		switch (info->dir) {
		case MoveDir::Up:    dir.y = -info->dist; break;
		case MoveDir::Down:  dir.y =  info->dist; break;
		case MoveDir::Left:  dir.x = -info->dist; break;
		case MoveDir::Right: dir.x =  info->dist; break;
		}

		obs_sceneitem_get_pos(item, &pos);
		vec2_add(&pos, &pos, &dir);
		obs_sceneitem_set_pos(item, &pos);
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, &info);
}

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 已提交
3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029

void OBSBasic::OpenProjector(obs_source_t *source, int monitor)
{
	/* seriously?  10 monitors? */
	if (monitor > 9)
		return;

	delete projectors[monitor];
	projectors[monitor].clear();

	OBSProjector *projector = new OBSProjector(this, source);
	projector->Init(monitor);

	projectors[monitor] = projector;
}

void OBSBasic::OpenPreviewProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OpenProjector(nullptr, monitor);
}

void OBSBasic::OpenSourceProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OBSSceneItem item = GetCurrentSceneItem();
	if (!item)
		return;

	OpenProjector(obs_sceneitem_get_source(item), monitor);
}

void OBSBasic::OpenSceneProjector()
{
	int monitor = sender()->property("monitor").toInt();
	OBSScene scene = GetCurrentScene();
	if (!scene)
		return;

	OpenProjector(obs_scene_get_source(scene), monitor);
}
4030 4031 4032 4033 4034

void OBSBasic::UpdateTitleBar()
{
	stringstream name;

J
jp9000 已提交
4035 4036
	const char *profile = config_get_string(App()->GlobalConfig(),
			"Basic", "Profile");
J
jp9000 已提交
4037 4038 4039
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

4040 4041 4042 4043 4044
	name << "OBS ";
	if (previewProgramMode)
		name << "Studio ";

	name << App()->GetVersionString();
J
jp9000 已提交
4045
	name << " - " << Str("TitleBar.Profile") << ": " << profile;
J
jp9000 已提交
4046
	name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;
4047 4048 4049

	setWindowTitle(QT_UTF8(name.str().c_str()));
}
J
jp9000 已提交
4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073

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