window-basic-main.cpp 101.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 250
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
		obs_data_array_t *quickTransitionData, int transitionDuration,
		OBSScene &scene, OBSSource &curProgramScene)
251
{
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
	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));

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

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

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

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

	return saveData;
}

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

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

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

	volumes.clear();
}

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

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

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

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

	obs_data_release(saveData);
J
jp9000 已提交
364
	obs_data_array_release(sceneOrder);
365
	obs_data_array_release(quickTrData);
366 367
}

368
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
369
{
370
	obs_data_t *data = obs_data_get_obj(parent, name);
371 372 373
	if (!data)
		return;

374
	obs_source_t *source = obs_load_source(data);
375 376 377 378 379 380 381 382
	if (source) {
		obs_set_output_source(channel, source);
		obs_source_release(source);
	}

	obs_data_release(data);
}

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

401
void OBSBasic::CreateFirstRunSources()
402
{
403 404 405 406 407 408 409 410 411
	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);
412 413
}

414
void OBSBasic::CreateDefaultScene(bool firstStart)
415 416 417 418
{
	disableSaving++;

	ClearSceneData();
419 420 421 422
	InitDefaultTransitions();
	CreateDefaultQuickTransitions();
	ui->transitionDuration->setValue(300);
	SetTransition(fadeTransition);
423 424 425

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

426
	if (firstStart)
427
		CreateFirstRunSources();
428

429
	AddScene(obs_scene_get_source(scene));
430
	SetCurrentScene(scene, true);
431
	obs_scene_release(scene);
J
jp9000 已提交
432 433

	disableSaving--;
434 435
}

J
jp9000 已提交
436 437 438 439 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
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);
	}
}

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

474 475 476 477 478
	disableSaving++;

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

486
	ClearSceneData();
487
	InitDefaultTransitions();
488

J
jp9000 已提交
489
	obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
490
	obs_data_array_t *sources    = obs_data_get_array(data, "sources");
J
jp9000 已提交
491 492
	const char       *sceneName = obs_data_get_string(data,
			"current_scene");
493 494 495 496 497 498 499 500 501 502 503
	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 已提交
504 505 506 507 508 509 510

	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");
511
	obs_source_t     *curScene;
512 513
	obs_source_t     *curProgramScene;
	obs_source_t     *curTransition;
514

J
jp9000 已提交
515 516 517
	if (!name || !*name)
		name = curSceneCollection;

518 519 520 521 522 523
	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);

524 525
	obs_load_sources(sources);

J
jp9000 已提交
526 527 528
	if (sceneOrder)
		LoadSceneListOrder(sceneOrder);

529 530 531 532 533 534 535
	curTransition = FindTransition(transitionName);
	if (!curTransition)
		curTransition = fadeTransition;

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

536
	curScene = obs_get_source_by_name(sceneName);
537 538 539 540 541 542 543 544 545
	curProgramScene = obs_get_source_by_name(programSceneName);
	if (!curProgramScene) {
		curProgramScene = curScene;
		obs_source_addref(curScene);
	}

	SetCurrentScene(curScene, true);
	if (IsPreviewProgramMode())
		TransitionToScene(curProgramScene, true);
546
	obs_source_release(curScene);
547
	obs_source_release(curProgramScene);
548 549

	obs_data_array_release(sources);
J
jp9000 已提交
550
	obs_data_array_release(sceneOrder);
J
jp9000 已提交
551 552 553 554 555 556 557 558 559

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

560 561 562 563 564 565 566
	obs_data_array_t *quickTransitionData = obs_data_get_array(data,
			"quick_transitions");
	LoadQuickTransitions(quickTransitionData);
	obs_data_array_release(quickTransitionData);

	RefreshQuickTransitions();

567
	obs_data_release(data);
J
jp9000 已提交
568 569

	disableSaving--;
570 571
}

J
jp9000 已提交
572
#define SERVICE_PATH "service.json"
573 574 575 576 577 578

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

579
	char serviceJsonPath[512];
J
jp9000 已提交
580
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
581 582
			SERVICE_PATH);
	if (ret <= 0)
583 584
		return;

585 586
	obs_data_t *data     = obs_data_create();
	obs_data_t *settings = obs_service_get_settings(service);
587

588
	obs_data_set_string(data, "type", obs_service_get_type(service));
J
jp9000 已提交
589
	obs_data_set_obj(data, "settings", settings);
590

591 592
	if (!obs_data_save_json_safe(data, serviceJsonPath, "tmp", "bak"))
		blog(LOG_WARNING, "Failed to save service");
593 594 595 596 597 598 599 600 601

	obs_data_release(settings);
	obs_data_release(data);
}

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

602
	char serviceJsonPath[512];
J
jp9000 已提交
603
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
604 605
			SERVICE_PATH);
	if (ret <= 0)
606 607
		return false;

608 609
	obs_data_t *data = obs_data_create_from_json_file_safe(serviceJsonPath,
			"bak");
610 611

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

614
	obs_data_t *settings = obs_data_get_obj(data, "settings");
P
Palana 已提交
615
	obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys");
616

617
	service = obs_service_create(type, "default_service", settings,
P
Palana 已提交
618
			hotkey_data);
619
	obs_service_release(service);
620

P
Palana 已提交
621
	obs_data_release(hotkey_data);
622 623 624 625 626 627 628 629
	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitService()
{
P
Palana 已提交
630 631
	ProfileScope("OBSBasic::InitService");

632 633 634
	if (LoadService())
		return true;

635 636
	service = obs_service_create("rtmp_common", "default_service", nullptr,
			nullptr);
637 638
	if (!service)
		return false;
639
	obs_service_release(service);
640 641 642 643

	return true;
}

644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
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
};

660 661 662 663 664 665 666 667 668 669 670 671 672 673
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;

674 675 676 677 678 679 680 681 682 683
	/* ----------------------------------------------------- */
	/* 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");
684
		config_save_safe(basicConfig, "tmp", nullptr);
685 686 687 688
	}

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

689
	config_set_default_string(basicConfig, "Output", "Mode", "Simple");
J
jp9000 已提交
690

691 692
	config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
			GetDefaultVideoSavePath().c_str());
693 694
	config_set_default_string(basicConfig, "SimpleOutput", "RecFormat",
			"flv");
695 696
	config_set_default_uint  (basicConfig, "SimpleOutput", "VBitrate",
			2500);
697
	config_set_default_uint  (basicConfig, "SimpleOutput", "ABitrate", 160);
J
jp9000 已提交
698 699 700 701
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseAdvanced",
			false);
	config_set_default_string(basicConfig, "SimpleOutput", "Preset",
			"veryfast");
702 703 704 705
	config_set_default_string(basicConfig, "SimpleOutput", "RecQuality",
			"Stream");
	config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
			SIMPLE_ENCODER_X264);
706

707 708
	config_set_default_bool  (basicConfig, "AdvOut", "ApplyServiceSettings",
			true);
J
jp9000 已提交
709 710 711 712 713 714 715 716
	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());
717
	config_set_default_string(basicConfig, "AdvOut", "RecFormat", "flv");
J
jp9000 已提交
718 719
	config_set_default_bool  (basicConfig, "AdvOut", "RecUseRescale",
			false);
720
	config_set_default_uint  (basicConfig, "AdvOut", "RecTracks", (1<<0));
J
jp9000 已提交
721 722 723
	config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
			"none");

724 725 726 727 728
	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 已提交
729 730 731 732 733 734 735 736 737 738 739
	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);

740 741 742
	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);

743 744 745 746
	config_set_default_bool  (basicConfig, "Output", "DelayEnable", false);
	config_set_default_uint  (basicConfig, "Output", "DelaySec", 20);
	config_set_default_bool  (basicConfig, "Output", "DelayPreserve", true);

747 748 749 750
	config_set_default_bool  (basicConfig, "Output", "Reconnect", true);
	config_set_default_uint  (basicConfig, "Output", "RetryDelay", 10);
	config_set_default_uint  (basicConfig, "Output", "MaxRetries", 20);

751 752 753 754 755 756 757 758 759 760 761 762 763 764
	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);
765 766 767 768 769 770

	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);
771
	config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
772
	config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
773
	config_set_default_string(basicConfig, "Video", "ColorSpace", "601");
774 775
	config_set_default_string(basicConfig, "Video", "ColorRange",
			"Partial");
776 777 778 779 780 781 782 783 784 785

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

	return true;
}

bool OBSBasic::InitBasicConfig()
{
P
Palana 已提交
786 787
	ProfileScope("OBSBasic::InitBasicConfig");

788
	char configPath[512];
J
jp9000 已提交
789 790 791 792 793 794 795 796 797 798 799 800 801

	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");
802 803 804 805
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get base.ini path");
		return false;
	}
806

807 808 809
	int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (code != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", code);
810 811 812
		return false;
	}

J
jp9000 已提交
813 814 815 816 817
	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);
818
		basicConfig.SaveSafe("tmp");
J
jp9000 已提交
819 820
	}

821 822 823
	return InitBasicConfigDefaults();
}

824 825
void OBSBasic::InitOBSCallbacks()
{
P
Palana 已提交
826 827
	ProfileScope("OBSBasic::InitOBSCallbacks");

P
Palana 已提交
828
	signalHandlers.reserve(signalHandlers.size() + 6);
829 830
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_load",
			OBSBasic::SourceLoaded, this);
P
Palana 已提交
831
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove",
832
			OBSBasic::SourceRemoved, this);
P
Palana 已提交
833
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate",
834
			OBSBasic::SourceActivated, this);
P
Palana 已提交
835
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate",
836
			OBSBasic::SourceDeactivated, this);
P
Palana 已提交
837
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename",
J
jp9000 已提交
838
			OBSBasic::SourceRenamed, this);
839 840
}

J
jp9000 已提交
841 842
void OBSBasic::InitPrimitives()
{
P
Palana 已提交
843 844
	ProfileScope("OBSBasic::InitPrimitives");

J
jp9000 已提交
845
	obs_enter_graphics();
J
jp9000 已提交
846

847
	gs_render_start(true);
J
jp9000 已提交
848 849 850 851 852
	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);
853
	box = gs_render_save();
J
jp9000 已提交
854

855
	gs_render_start(true);
J
jp9000 已提交
856 857 858 859
	for (int i = 0; i <= 360; i += (360/20)) {
		float pos = RAD(float(i));
		gs_vertex2f(cosf(pos), sinf(pos));
	}
860
	circle = gs_render_save();
J
jp9000 已提交
861

J
jp9000 已提交
862
	obs_leave_graphics();
J
jp9000 已提交
863 864
}

J
jp9000 已提交
865 866
void OBSBasic::ResetOutputs()
{
P
Palana 已提交
867 868
	ProfileScope("OBSBasic::ResetOutputs");

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

J
jp9000 已提交
872 873
	if (!outputHandler || !outputHandler->Active()) {
		outputHandler.reset();
J
jp9000 已提交
874 875 876
		outputHandler.reset(advOut ?
			CreateAdvancedOutputHandler(this) :
			CreateSimpleOutputHandler(this));
J
jp9000 已提交
877 878 879 880 881
	} else {
		outputHandler->Update();
	}
}

882 883 884
#define MAIN_SEPARATOR \
	"====================================================================="

885 886
void OBSBasic::OBSInit()
{
P
Palana 已提交
887 888
	ProfileScope("OBSBasic::OBSInit");

J
jp9000 已提交
889 890
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
891
	char savePath[512];
J
jp9000 已提交
892 893 894 895 896 897 898 899
	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);
900
	if (ret <= 0)
J
jp9000 已提交
901 902 903 904 905
		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";
906

907 908
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
909
	if (!ResetAudio())
910 911
		throw "Failed to initialize audio";

912
	ret = ResetVideo();
913 914 915 916 917

	switch (ret) {
	case OBS_VIDEO_MODULE_NOT_FOUND:
		throw "Failed to initialize video:  Graphics module not found";
	case OBS_VIDEO_NOT_SUPPORTED:
J
jp9000 已提交
918 919 920
		throw "Failed to initialize video:  Required graphics API "
		      "functionality not found on these drivers or "
		      "unavailable on this equipment";
921 922 923 924 925 926 927
	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";
	}

928
	InitOBSCallbacks();
P
Palana 已提交
929
	InitHotkeys();
930

931
	AddExtraModulePaths();
J
jp9000 已提交
932
	obs_load_all_modules();
J
jp9000 已提交
933

934 935
	blog(LOG_INFO, MAIN_SEPARATOR);

J
jp9000 已提交
936
	ResetOutputs();
937
	CreateHotkeys();
J
jp9000 已提交
938

939 940 941
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
942 943
	InitPrimitives();

944 945 946 947 948 949 950 951 952
	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 已提交
953 954 955 956 957 958
	{
		ProfileScope("OBSBasic::Load");
		disableSaving--;
		Load(savePath);
		disableSaving++;
	}
959

J
jp9000 已提交
960
	TimedCheckForUpdates();
961
	loaded = true;
J
jp9000 已提交
962

963
	previewEnabled = config_get_bool(App()->GlobalConfig(),
J
jp9000 已提交
964
			"BasicWindow", "PreviewEnabled");
965 966 967 968 969

	if (!previewEnabled && !IsPreviewProgramMode())
		QMetaObject::invokeMethod(this, "EnablePreviewDisplay",
				Qt::QueuedConnection,
				Q_ARG(bool, previewEnabled));
970 971 972 973 974 975 976 977 978

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

J
jp9000 已提交
980
	RefreshSceneCollections();
J
jp9000 已提交
981
	RefreshProfiles();
J
jp9000 已提交
982
	disableSaving--;
983 984 985 986 987 988 989 990 991 992 993 994 995

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

996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
#ifdef _WIN32
	show();
#endif

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

#ifndef _WIN32
1008
	show();
1009
#endif
J
jp9000 已提交
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028

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

P
Palana 已提交
1031 1032
void OBSBasic::InitHotkeys()
{
P
Palana 已提交
1033 1034
	ProfileScope("OBSBasic::InitHotkeys");

P
Palana 已提交
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
	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"),
1077
			Str("Push-to-mute"), Str("Push-to-talk"));
P
Palana 已提交
1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097

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

1098 1099
void OBSBasic::CreateHotkeys()
{
P
Palana 已提交
1100 1101
	ProfileScope("OBSBasic::CreateHotkeys");

1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
	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 已提交
1118 1119 1120 1121 1122 1123 1124 1125 1126
	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);
	};

1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163
	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 已提交
1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178
	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");

1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191
	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
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220

	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");
1221 1222
}

J
jp9000 已提交
1223 1224 1225 1226
void OBSBasic::ClearHotkeys()
{
	obs_hotkey_pair_unregister(streamingHotkeys);
	obs_hotkey_pair_unregister(recordingHotkeys);
J
jp9000 已提交
1227
	obs_hotkey_unregister(forceStreamingStopHotkey);
1228 1229
	obs_hotkey_unregister(togglePreviewProgramHotkey);
	obs_hotkey_unregister(transitionHotkey);
J
jp9000 已提交
1230 1231
}

1232 1233
OBSBasic::~OBSBasic()
{
1234 1235
	delete programOptions;
	delete program;
J
jp9000 已提交
1236

J
jp9000 已提交
1237 1238 1239 1240 1241 1242
	/* 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 已提交
1243
	delete cpuUsageTimer;
1244 1245
	os_cpu_usage_info_destroy(cpuUsageInfo);

P
Palana 已提交
1246
	obs_hotkey_set_callback_routing_func(nullptr, nullptr);
J
jp9000 已提交
1247
	ClearHotkeys();
P
Palana 已提交
1248

1249
	service = nullptr;
J
jp9000 已提交
1250 1251
	outputHandler.reset();

J
John Bradley 已提交
1252 1253 1254
	if (interaction)
		delete interaction;

1255 1256 1257
	if (properties)
		delete properties;

J
jp9000 已提交
1258 1259 1260
	if (filters)
		delete filters;

1261 1262
	if (transformWindow)
		delete transformWindow;
1263

J
jp9000 已提交
1264 1265 1266
	if (advAudioWindow)
		delete advAudioWindow;

1267 1268 1269
	obs_display_remove_draw_callback(ui->preview->GetDisplay(),
			OBSBasic::RenderMain, this);

J
jp9000 已提交
1270
	obs_enter_graphics();
1271 1272
	gs_vertexbuffer_destroy(box);
	gs_vertexbuffer_destroy(circle);
J
jp9000 已提交
1273
	obs_leave_graphics();
J
jp9000 已提交
1274

1275 1276 1277 1278 1279 1280 1281 1282 1283
	/* 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 已提交
1284 1285
	config_set_int(App()->GlobalConfig(), "General", "LastVersion",
			LIBOBS_API_VER);
J
jp9000 已提交
1286 1287

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

J
jp9000 已提交
1291
	config_set_int(App()->GlobalConfig(), "BasicWindow", "cx",
J
jp9000 已提交
1292
			lastGeom.width());
J
jp9000 已提交
1293
	config_set_int(App()->GlobalConfig(), "BasicWindow", "cy",
J
jp9000 已提交
1294
			lastGeom.height());
J
jp9000 已提交
1295
	config_set_int(App()->GlobalConfig(), "BasicWindow", "posx",
J
jp9000 已提交
1296
			lastGeom.x());
J
jp9000 已提交
1297
	config_set_int(App()->GlobalConfig(), "BasicWindow", "posy",
J
jp9000 已提交
1298
			lastGeom.y());
J
jp9000 已提交
1299 1300 1301 1302
	config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterTop",
			splitterSizes[0]);
	config_set_int(App()->GlobalConfig(), "BasicWindow", "splitterBottom",
			splitterSizes[1]);
J
jp9000 已提交
1303 1304
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled",
			previewEnabled);
1305 1306
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
			alwaysOnTop);
1307 1308 1309 1310 1311 1312 1313 1314
	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());
1315
	config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326

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

J
jp9000 已提交
1329 1330 1331 1332 1333 1334 1335 1336 1337
void OBSBasic::SaveProjectNow()
{
	if (disableSaving)
		return;

	projectChanged = true;
	SaveProjectDeferred();
}

J
jp9000 已提交
1338 1339
void OBSBasic::SaveProject()
{
J
jp9000 已提交
1340 1341 1342
	if (disableSaving)
		return;

J
jp9000 已提交
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357
	projectChanged = true;
	QMetaObject::invokeMethod(this, "SaveProjectDeferred",
			Qt::QueuedConnection);
}

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

	if (!projectChanged)
		return;

	projectChanged = false;

J
jp9000 已提交
1358 1359
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
1360
	char savePath[512];
J
jp9000 已提交
1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372
	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);
1373 1374 1375
	if (ret <= 0)
		return;

J
jp9000 已提交
1376 1377 1378
	Save(savePath);
}

J
jp9000 已提交
1379
OBSScene OBSBasic::GetCurrentScene()
1380
{
J
jp9000 已提交
1381
	QListWidgetItem *item = ui->scenes->currentItem();
P
Palana 已提交
1382
	return item ? GetOBSRef<OBSScene>(item) : nullptr;
1383 1384
}

1385
OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
J
jp9000 已提交
1386
{
P
Palana 已提交
1387
	return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
J
jp9000 已提交
1388 1389
}

1390 1391
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
1392
	return GetSceneItem(GetTopSelectedSourceItem());
1393 1394
}

1395 1396
void OBSBasic::UpdateSources(OBSScene scene)
{
1397
	ClearListItems(ui->sources);
1398 1399

	obs_scene_enum_items(scene,
1400
			[] (obs_scene_t *scene, obs_sceneitem_t *item, void *p)
1401 1402
			{
				OBSBasic *window = static_cast<OBSBasic*>(p);
1403
				window->InsertSceneItem(item);
J
jp9000 已提交
1404 1405

				UNUSED_PARAMETER(scene);
1406 1407 1408 1409
				return true;
			}, this);
}

1410
void OBSBasic::InsertSceneItem(obs_sceneitem_t *item)
1411
{
1412
	QListWidgetItem *listItem = new QListWidgetItem();
P
Palana 已提交
1413
	SetOBSRef(listItem, OBSSceneItem(item));
1414 1415

	ui->sources->insertItem(0, listItem);
1416
	ui->sources->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
1417

1418
	SetupVisibilityItem(ui->sources, listItem, item);
1419 1420
}

1421
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
J
John Bradley 已提交
1422 1423 1424 1425 1426 1427 1428 1429 1430
{
	if (interaction)
		interaction->close();

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

1431
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
1432 1433 1434 1435 1436 1437 1438
{
	if (properties)
		properties->close();

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

J
jp9000 已提交
1441 1442 1443 1444 1445 1446 1447 1448 1449 1450
void OBSBasic::CreateFiltersWindow(obs_source_t *source)
{
	if (filters)
		filters->close();

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

1451 1452 1453
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
1454
{
1455
	const char *name  = obs_source_get_name(source);
1456
	obs_scene_t *scene = obs_scene_from_source(source);
J
jp9000 已提交
1457 1458

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

P
Palana 已提交
1462 1463 1464 1465 1466
	obs_hotkey_register_source(source, "OBSBasic.SelectScene",
			Str("Basic.Hotkeys.SelectScene"),
			[](void *data,
				obs_hotkey_id, obs_hotkey_t*, bool pressed)
	{
1467 1468 1469
		OBSBasic *main =
			reinterpret_cast<OBSBasic*>(App()->GetMainWindow());

P
Palana 已提交
1470 1471 1472
		auto potential_source = static_cast<obs_source_t*>(data);
		auto source = obs_source_get_ref(potential_source);
		if (source && pressed)
1473
			main->SetCurrentScene(source);
P
Palana 已提交
1474 1475 1476
		obs_source_release(source);
	}, static_cast<obs_source_t*>(source));

1477
	signal_handler_t *handler = obs_source_get_signal_handler(source);
1478

J
jp9000 已提交
1479 1480 1481
	SignalContainer<OBSScene> container;
	container.ref = scene;
	container.handlers.assign({
1482 1483 1484 1485 1486 1487 1488 1489 1490 1491
		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 已提交
1492
	});
1493 1494

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

1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513
	/* 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 已提交
1514
	SaveProject();
1515 1516
}

1517
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
1518
{
P
Palana 已提交
1519 1520 1521 1522 1523 1524 1525 1526 1527
	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 已提交
1528

P
Palana 已提交
1529 1530 1531
		sel = item;
		break;
	}
J
jp9000 已提交
1532

J
jp9000 已提交
1533
	if (sel != nullptr) {
P
Palana 已提交
1534
		if (sel == ui->scenes->currentItem())
1535
			ClearListItems(ui->sources);
J
jp9000 已提交
1536
		delete sel;
J
jp9000 已提交
1537
	}
J
jp9000 已提交
1538 1539

	SaveProject();
1540 1541
}

1542
void OBSBasic::AddSceneItem(OBSSceneItem item)
1543
{
1544
	obs_scene_t  *scene  = obs_sceneitem_get_scene(item);
J
jp9000 已提交
1545

1546 1547
	if (GetCurrentScene() == scene)
		InsertSceneItem(item);
J
jp9000 已提交
1548

J
jp9000 已提交
1549
	SaveProject();
1550 1551
}

1552
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
1553
{
1554
	obs_scene_t *scene = obs_sceneitem_get_scene(item);
1555

J
jp9000 已提交
1556
	if (GetCurrentScene() == scene) {
B
BtbN 已提交
1557
		for (int i = 0; i < ui->sources->count(); i++) {
J
jp9000 已提交
1558
			QListWidgetItem *listItem = ui->sources->item(i);
J
jp9000 已提交
1559

P
Palana 已提交
1560
			if (GetOBSRef<OBSSceneItem>(listItem) == item) {
1561
				DeleteListItem(ui->sources, listItem);
J
jp9000 已提交
1562 1563
				break;
			}
1564 1565
		}
	}
J
jp9000 已提交
1566

J
jp9000 已提交
1567
	SaveProject();
1568 1569
}

1570
void OBSBasic::UpdateSceneSelection(OBSSource source)
1571 1572
{
	if (source) {
1573
		obs_scene_t *scene = obs_scene_from_source(source);
1574
		const char *name = obs_source_get_name(source);
J
jp9000 已提交
1575

1576 1577 1578
		if (!scene)
			return;

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

1582 1583 1584 1585 1586
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

J
jp9000 已提交
1587
			UpdateSources(scene);
1588
		}
J
jp9000 已提交
1589
	}
1590 1591
}

J
jp9000 已提交
1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604
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);
1605 1606 1607 1608 1609

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

	SaveProject();
J
jp9000 已提交
1612 1613
}

1614 1615
void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
{
J
jp9000 已提交
1616 1617
	SignalBlocker sourcesSignalBlocker(ui->sources);

1618
	if (scene != GetCurrentScene() || ignoreSelectionUpdate)
1619 1620 1621 1622
		return;

	for (int i = 0; i < ui->sources->count(); i++) {
		QListWidgetItem *witem = ui->sources->item(i);
P
Palana 已提交
1623 1624
		QVariant data =
			witem->data(static_cast<int>(QtDataRole::OBSRef));
1625 1626 1627 1628 1629 1630
		if (!data.canConvert<OBSSceneItem>())
			continue;

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

J
jp9000 已提交
1631
		witem->setSelected(select);
1632 1633 1634 1635
		break;
	}
}

1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678
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());
}

1679 1680
void OBSBasic::ActivateAudioSource(OBSSource source)
{
1681 1682 1683 1684
	VolControl *vol = new VolControl(source, true);

	connect(vol, &VolControl::ConfigClicked,
			this, &OBSBasic::VolControlContextMenu);
1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700

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

1701
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
J
jp9000 已提交
1702
{
1703
	const char *name  = obs_source_get_name(source);
1704 1705 1706

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

1708
	QMessageBox remove_source(this);
1709 1710 1711
	remove_source.setText(text);
	QAbstractButton *Yes = remove_source.addButton(QTStr("Yes"),
			QMessageBox::YesRole);
J
Jkoan 已提交
1712 1713 1714 1715 1716 1717
	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();
1718
}
J
jp9000 已提交
1719

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

P
Palana 已提交
1722 1723 1724 1725 1726
#ifdef UPDATE_SPARKLE
void init_sparkle_updater(bool update_to_undeployed);
void trigger_sparkle_update();
#endif

J
jp9000 已提交
1727 1728
void OBSBasic::TimedCheckForUpdates()
{
P
Palana 已提交
1729 1730 1731 1732
#ifdef UPDATE_SPARKLE
	init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
				"UpdateToUndeployed"));
#else
J
jp9000 已提交
1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748
	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 已提交
1749
#endif
J
jp9000 已提交
1750 1751 1752 1753
}

void OBSBasic::CheckForUpdates()
{
P
Palana 已提交
1754 1755 1756
#ifdef UPDATE_SPARKLE
	trigger_sparkle_update();
#else
J
jp9000 已提交
1757 1758
	ui->actionCheckForUpdates->setEnabled(false);

1759 1760 1761 1762
	if (updateCheckThread) {
		updateCheckThread->wait();
		delete updateCheckThread;
	}
1763

1764 1765 1766 1767 1768 1769
	RemoteTextThread *thread = new RemoteTextThread(
			"https://obsproject.com/obs2_update/basic.json");
	updateCheckThread = thread;
	connect(thread, &RemoteTextThread::Result,
			this, &OBSBasic::updateFileFinished);
	updateCheckThread->start();
P
Palana 已提交
1770
#endif
J
jp9000 已提交
1771 1772
}

J
jp9000 已提交
1773 1774
#ifdef __APPLE__
#define VERSION_ENTRY "mac"
J
jp9000 已提交
1775 1776
#elif _WIN32
#define VERSION_ENTRY "windows"
J
jp9000 已提交
1777 1778 1779 1780
#else
#define VERSION_ENTRY "other"
#endif

1781
void OBSBasic::updateFileFinished(const QString &text, const QString &error)
J
jp9000 已提交
1782 1783 1784
{
	ui->actionCheckForUpdates->setEnabled(true);

1785 1786
	if (text.isEmpty()) {
		blog(LOG_WARNING, "Update check failed: %s", QT_TO_UTF8(error));
J
jp9000 已提交
1787 1788 1789
		return;
	}

1790
	obs_data_t *returnData  = obs_data_create_from_json(QT_TO_UTF8(text));
F
fryshorts 已提交
1791
	obs_data_t *versionData = obs_data_get_obj(returnData, VERSION_ENTRY);
J
jp9000 已提交
1792 1793 1794
	const char *description = obs_data_get_string(returnData,
			"description");
	const char *download    = obs_data_get_string(versionData, "download");
J
jp9000 已提交
1795 1796

	if (returnData && versionData && description && download) {
J
jp9000 已提交
1797 1798 1799
		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 已提交
1800 1801
		long version = MAKE_SEMANTIC_VERSION(major, minor, patch);

1802 1803
		blog(LOG_INFO, "Update check: last known remote version "
				"is %ld.%ld.%ld",
J
jp9000 已提交
1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823
				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);
1824
			config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
J
jp9000 已提交
1825 1826 1827 1828 1829 1830 1831 1832 1833
		}
	} else {
		blog(LOG_WARNING, "Bad JSON file received from server");
	}

	obs_data_release(versionData);
	obs_data_release(returnData);
}

1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880
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,
1881
				name.c_str(), OBS_SCENE_DUP_REFS);
1882
		source = obs_scene_get_source(scene);
1883 1884
		AddScene(source);
		SetCurrentScene(source, true);
1885
		obs_scene_release(scene);
1886
		break;
1887 1888 1889
	}
}

1890 1891 1892 1893
void OBSBasic::RemoveSelectedScene()
{
	OBSScene scene = GetCurrentScene();
	if (scene) {
1894
		obs_source_t *source = obs_scene_get_source(scene);
1895 1896 1897 1898 1899 1900 1901 1902 1903
		if (QueryRemoveSource(source))
			obs_source_remove(source);
	}
}

void OBSBasic::RemoveSelectedSceneItem()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (item) {
1904
		obs_source_t *source = obs_sceneitem_get_source(item);
1905
		if (QueryRemoveSource(source))
J
jp9000 已提交
1906 1907 1908 1909
			obs_sceneitem_remove(item);
	}
}

1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923
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 已提交
1924
		OBSSceneItem sceneItem = GetOBSRef<OBSSceneItem>(listItem);
1925 1926 1927 1928 1929

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

1930
				listItem = TakeListItem(ui->sources, i);
1931 1932 1933
				if (listItem)  {
					ui->sources->insertItem(idx_inv,
							listItem);
1934 1935 1936
					SetupVisibilityItem(ui->sources,
							listItem, item);

1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951
					if (sel)
						ui->sources->setCurrentRow(
								idx_inv);
				}
			}

			break;
		}
	}
}

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

1952
	if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964
		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 已提交
1965 1966

	SaveProject();
1967 1968
}

1969 1970
/* OBS Callbacks */

1971 1972 1973 1974 1975 1976 1977 1978 1979 1980
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)));
}

1981
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
1982 1983 1984
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

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

1987 1988
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
1989 1990
}

1991
void OBSBasic::SceneItemRemoved(void *data, calldata_t *params)
1992
{
1993
	OBSBasic *window = static_cast<OBSBasic*>(data);
1994

1995
	obs_sceneitem_t *item = (obs_sceneitem_t*)calldata_ptr(params, "item");
1996

1997 1998
	QMetaObject::invokeMethod(window, "RemoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
1999 2000
}

2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
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));
}

2025
void OBSBasic::SourceLoaded(void *data, calldata_t *params)
2026
{
J
jp9000 已提交
2027
	OBSBasic *window = static_cast<OBSBasic*>(data);
2028
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2029

2030
	if (obs_scene_from_source(source) != NULL)
J
jp9000 已提交
2031
		QMetaObject::invokeMethod(window,
2032 2033
				"AddScene",
				Q_ARG(OBSSource, OBSSource(source)));
2034 2035
}

2036
void OBSBasic::SourceRemoved(void *data, calldata_t *params)
2037
{
2038
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2039

2040
	if (obs_scene_from_source(source) != NULL)
2041 2042 2043
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
2044 2045
}

2046
void OBSBasic::SourceActivated(void *data, calldata_t *params)
2047
{
2048
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2049 2050 2051 2052 2053 2054 2055 2056
	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)));
}

2057
void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
2058
{
2059
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2060 2061 2062 2063 2064 2065 2066 2067
	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)));
}

2068
void OBSBasic::SourceRenamed(void *data, calldata_t *params)
J
jp9000 已提交
2069 2070 2071 2072 2073 2074 2075 2076 2077 2078
{
	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)));
}

2079 2080 2081 2082 2083
void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

2084
	gs_effect_t    *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
2085 2086
	gs_eparam_t    *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech  = gs_effect_get_technique(solid, "Solid");
2087 2088 2089

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
2090
	gs_effect_set_vec4(color, &colorVal);
2091

2092 2093
	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
2094 2095 2096 2097 2098 2099 2100 2101
	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();
2102 2103
	gs_technique_end_pass(tech);
	gs_technique_end(tech);
2104 2105 2106 2107

	gs_load_vertexbuffer(nullptr);
}

2108 2109
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
2110
	OBSBasic *window = static_cast<OBSBasic*>(data);
2111 2112 2113 2114
	obs_video_info ovi;

	obs_get_video_info(&ovi);

J
jp9000 已提交
2115 2116
	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));
2117 2118 2119

	gs_viewport_push();
	gs_projection_push();
2120 2121 2122

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

2123 2124
	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
			-100.0f, 100.0f);
2125
	gs_set_viewport(window->previewX, window->previewY,
J
jp9000 已提交
2126
			window->previewCX, window->previewCY);
2127

2128 2129
	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

2130 2131 2132 2133 2134 2135 2136 2137
	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();
	}
2138
	gs_load_vertexbuffer(nullptr);
2139

2140 2141
	/* --------------------------------------- */

2142 2143 2144
	QSize previewSize = GetPixelSize(window->ui->preview);
	float right  = float(previewSize.width())  - window->previewX;
	float bottom = float(previewSize.height()) - window->previewY;
2145 2146 2147 2148

	gs_ortho(-window->previewX, right,
	         -window->previewY, bottom,
	         -100.0f, 100.0f);
2149
	gs_reset_viewport();
J
jp9000 已提交
2150 2151 2152

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

2153 2154
	/* --------------------------------------- */

2155 2156
	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
2157 2158 2159

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
2160 2161
}

2162 2163
/* Main class functions */

2164
obs_service_t *OBSBasic::GetService()
2165
{
2166
	if (!service) {
2167 2168
		service = obs_service_create("rtmp_common", NULL, NULL,
				nullptr);
2169 2170
		obs_service_release(service);
	}
2171 2172 2173
	return service;
}

2174
void OBSBasic::SetService(obs_service_t *newService)
2175
{
2176
	if (newService)
2177 2178 2179
		service = newService;
}

2180 2181 2182 2183 2184 2185 2186
bool OBSBasic::StreamingActive()
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

2187 2188 2189 2190 2191 2192
#ifdef _WIN32
#define IS_WIN32 1
#else
#define IS_WIN32 0
#endif

2193 2194
static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
2195
	return obs_reset_video(ovi);
2196 2197
}

2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210
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;
}

2211 2212 2213 2214 2215 2216
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 已提交
2217 2218
	else if (astrcmpi(name, "I444") == 0)
		return VIDEO_FORMAT_I444;
2219 2220 2221 2222 2223 2224 2225 2226 2227
#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 已提交
2228
		return VIDEO_FORMAT_RGBA;
2229 2230
}

2231
int OBSBasic::ResetVideo()
J
jp9000 已提交
2232
{
P
Palana 已提交
2233 2234
	ProfileScope("OBSBasic::ResetVideo");

J
jp9000 已提交
2235
	struct obs_video_info ovi;
2236
	int ret;
J
jp9000 已提交
2237

2238
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
2239

2240 2241 2242 2243 2244 2245 2246
	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 已提交
2247
	ovi.graphics_module = App()->GetRenderModule();
2248
	ovi.base_width     = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2249
			"Video", "BaseCX");
2250
	ovi.base_height    = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2251
			"Video", "BaseCY");
2252
	ovi.output_width   = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2253
			"Video", "OutputCX");
2254
	ovi.output_height  = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2255
			"Video", "OutputCY");
2256 2257 2258 2259 2260
	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 已提交
2261 2262
	ovi.adapter        = 0;
	ovi.gpu_conversion = true;
2263
	ovi.scale_type     = GetScaleType(basicConfig);
2264

2265
	ret = AttemptToResetVideo(&ovi);
2266 2267
	if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
		/* Try OpenGL if DirectX fails on windows */
2268
		if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
2269 2270 2271 2272
			blog(LOG_WARNING, "Failed to initialize obs video (%d) "
					  "with graphics_module='%s', retrying "
					  "with graphics_module='%s'",
					  ret, ovi.graphics_module,
2273 2274
					  DL_OPENGL);
			ovi.graphics_module = DL_OPENGL;
2275 2276
			ret = AttemptToResetVideo(&ovi);
		}
2277 2278
	} else if (ret == OBS_VIDEO_SUCCESS) {
		ResizePreview(ovi.base_width, ovi.base_height);
2279 2280
		if (program)
			ResizeProgram(ovi.base_width, ovi.base_height);
2281 2282
	}

2283
	return ret;
J
jp9000 已提交
2284
}
J
jp9000 已提交
2285

2286
bool OBSBasic::ResetAudio()
J
jp9000 已提交
2287
{
P
Palana 已提交
2288 2289
	ProfileScope("OBSBasic::ResetAudio");

2290
	struct obs_audio_info ai;
2291
	ai.samples_per_sec = config_get_uint(basicConfig, "Audio",
2292 2293
			"SampleRate");

2294
	const char *channelSetupStr = config_get_string(basicConfig,
2295 2296 2297 2298 2299 2300 2301
			"Audio", "ChannelSetup");

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

J
jp9000 已提交
2302
	return obs_reset_audio(&ai);
J
jp9000 已提交
2303 2304
}

2305
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
2306
		const char *deviceDesc, int channel)
J
jp9000 已提交
2307
{
2308 2309
	obs_source_t *source;
	obs_data_t *settings;
J
jp9000 已提交
2310 2311 2312 2313
	bool same = false;

	source = obs_get_output_source(channel);
	if (source) {
2314
		settings = obs_source_get_settings(source);
J
jp9000 已提交
2315
		const char *curId = obs_data_get_string(settings, "device_id");
J
jp9000 已提交
2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326

		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) {
2327
		obs_data_t *settings = obs_data_create();
J
jp9000 已提交
2328
		obs_data_set_string(settings, "device_id", deviceId);
2329 2330
		source = obs_source_create(sourceId, deviceDesc, settings,
				nullptr);
J
jp9000 已提交
2331 2332 2333 2334 2335 2336 2337
		obs_data_release(settings);

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

J
jp9000 已提交
2338
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
2339
{
2340
	QSize  targetSize;
J
jp9000 已提交
2341

2342
	/* resize preview panel to fix to the top section of the window */
2343
	targetSize = GetPixelSize(ui->preview);
2344
	GetScaleAndCenterPos(int(cx), int(cy),
2345 2346
			targetSize.width()  - PREVIEW_EDGE_SIZE * 2,
			targetSize.height() - PREVIEW_EDGE_SIZE * 2,
2347
			previewX, previewY, previewScale);
J
jp9000 已提交
2348

2349 2350
	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);
J
jp9000 已提交
2351 2352
}

2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369
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 已提交
2370 2371
	disableSaving++;

2372 2373 2374 2375 2376
	CloseDialogs();

	ClearVolumeControls();
	ClearListItems(ui->scenes);
	ClearListItems(ui->sources);
2377 2378
	ClearQuickTransitions();
	ui->transitions->clear();
2379 2380 2381 2382 2383 2384 2385

	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);
2386 2387 2388
	lastScene = nullptr;
	swapScene = nullptr;
	programScene = nullptr;
2389 2390 2391 2392 2393 2394 2395 2396 2397 2398

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

	obs_enum_sources(cb, nullptr);

J
jp9000 已提交
2399
	disableSaving--;
2400 2401 2402

	blog(LOG_INFO, "All scene data cleared");
	blog(LOG_INFO, "------------------------------------------------");
2403 2404
}

J
jp9000 已提交
2405
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
2406
{
2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417
	if (outputHandler && outputHandler->Active()) {
		QMessageBox::StandardButton button = QMessageBox::question(
				this, QTStr("ConfirmExit.Title"),
				QTStr("ConfirmExit.Text"));

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

2418 2419 2420 2421
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

2422 2423 2424 2425 2426
	if (updateCheckThread)
		updateCheckThread->wait();
	if (logUploadThread)
		logUploadThread->wait();

P
Palana 已提交
2427 2428
	signalHandlers.clear();

J
jp9000 已提交
2429
	SaveProjectNow();
J
jp9000 已提交
2430
	disableSaving++;
J
jp9000 已提交
2431

2432 2433 2434
	/* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
	 * sources, etc) so that all references are released before shutdown */
	ClearSceneData();
2435 2436
}

J
jp9000 已提交
2437
void OBSBasic::changeEvent(QEvent *event)
2438
{
J
jp9000 已提交
2439 2440
	/* TODO */
	UNUSED_PARAMETER(event);
2441 2442
}

2443 2444
void OBSBasic::on_actionShow_Recordings_triggered()
{
2445 2446 2447 2448
	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");
2449 2450 2451
	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

P
Palana 已提交
2452 2453
void OBSBasic::on_actionRemux_triggered()
{
2454 2455 2456 2457
	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 已提交
2458 2459 2460 2461
	OBSRemux remux(path, this);
	remux.exec();
}

P
Palana 已提交
2462 2463 2464 2465 2466 2467
void OBSBasic::on_action_Settings_triggered()
{
	OBSBasicSettings settings(this);
	settings.exec();
}

J
jp9000 已提交
2468 2469
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
2470 2471 2472 2473 2474
	if (advAudioWindow != nullptr) {
		advAudioWindow->raise();
		return;
	}

J
jp9000 已提交
2475 2476 2477
	advAudioWindow = new OBSBasicAdvAudio(this);
	advAudioWindow->show();
	advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
2478 2479 2480

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

2483 2484 2485 2486 2487
void OBSBasic::on_advAudioProps_clicked()
{
	on_actionAdvAudioProperties_triggered();
}

2488 2489 2490 2491 2492
void OBSBasic::on_advAudioProps_destroyed()
{
	advAudioWindow = nullptr;
}

2493 2494
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
2495
{
2496
	obs_source_t *source = NULL;
J
jp9000 已提交
2497

2498 2499 2500 2501
	if (sceneChanging)
		return;

	if (current) {
2502
		obs_scene_t *scene;
J
jp9000 已提交
2503

P
Palana 已提交
2504
		scene = GetOBSRef<OBSScene>(current);
2505
		source = obs_scene_get_source(scene);
2506 2507
	}

2508
	SetCurrentScene(source);
2509 2510

	UNUSED_PARAMETER(prev);
2511 2512
}

J
jp9000 已提交
2513 2514
void OBSBasic::EditSceneName()
{
2515 2516 2517 2518 2519 2520
	QListWidgetItem *item = ui->scenes->currentItem();
	Qt::ItemFlags flags   = item->flags();

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

J
jp9000 已提交
2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545
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 已提交
2546
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
2547
{
J
jp9000 已提交
2548
	QListWidgetItem *item = ui->scenes->itemAt(pos);
J
jp9000 已提交
2549
	QPointer<QMenu> sceneProjectorMenu;
J
jp9000 已提交
2550

2551
	QMenu popup(this);
J
jp9000 已提交
2552
	QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
J
jp9000 已提交
2553 2554 2555
	popup.addAction(QTStr("Add"),
			this, SLOT(on_actionAddScene_triggered()));

P
Palana 已提交
2556 2557
	if (item) {
		popup.addSeparator();
2558 2559
		popup.addAction(QTStr("Duplicate"),
				this, SLOT(DuplicateSelectedScene()));
P
Palana 已提交
2560 2561
		popup.addAction(QTStr("Rename"),
				this, SLOT(EditSceneName()));
J
jp9000 已提交
2562
		popup.addAction(QTStr("Remove"),
2563 2564
				this, SLOT(RemoveSelectedScene()),
				DeleteKeys.front());
J
jp9000 已提交
2565
		popup.addSeparator();
J
jp9000 已提交
2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578

		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 已提交
2579 2580 2581 2582 2583
		sceneProjectorMenu = new QMenu(QTStr("SceneProjector"));
		AddProjectorMenuMonitors(sceneProjectorMenu, this,
				SLOT(OpenSceneProjector()));
		popup.addMenu(sceneProjectorMenu);
		popup.addSeparator();
J
jp9000 已提交
2584 2585
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenSceneFilters()));
P
Palana 已提交
2586
	}
J
jp9000 已提交
2587 2588

	popup.exec(QCursor::pos());
2589 2590
}

J
jp9000 已提交
2591
void OBSBasic::on_actionAddScene_triggered()
2592
{
2593
	string name;
S
Socapex 已提交
2594
	QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
P
Palana 已提交
2595 2596 2597

	int i = 1;
	QString placeHolderText = format.arg(i);
2598
	obs_source_t *source = nullptr;
P
Palana 已提交
2599 2600
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
P
Palana 已提交
2601
		placeHolderText = format.arg(++i);
P
Palana 已提交
2602
	}
S
Socapex 已提交
2603

J
jp9000 已提交
2604
	bool accepted = NameDialog::AskForName(this,
2605 2606
			QTStr("Basic.Main.AddSceneDlg.Title"),
			QTStr("Basic.Main.AddSceneDlg.Text"),
S
Socapex 已提交
2607 2608
			name,
			placeHolderText);
2609

J
jp9000 已提交
2610
	if (accepted) {
J
jp9000 已提交
2611 2612
		if (name.empty()) {
			QMessageBox::information(this,
2613 2614
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
J
jp9000 已提交
2615 2616 2617 2618
			on_actionAddScene_triggered();
			return;
		}

2619
		obs_source_t *source = obs_get_source_by_name(name.c_str());
2620
		if (source) {
J
jp9000 已提交
2621
			QMessageBox::information(this,
2622 2623
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));
2624 2625

			obs_source_release(source);
J
jp9000 已提交
2626
			on_actionAddScene_triggered();
2627 2628 2629
			return;
		}

2630
		obs_scene_t *scene = obs_scene_create(name.c_str());
2631
		source = obs_scene_get_source(scene);
2632
		AddScene(source);
2633
		SetCurrentScene(source);
2634
		obs_scene_release(scene);
2635
	}
2636 2637
}

J
jp9000 已提交
2638
void OBSBasic::on_actionRemoveScene_triggered()
2639
{
2640
	OBSScene     scene  = GetCurrentScene();
2641
	obs_source_t *source = obs_scene_get_source(scene);
2642 2643 2644

	if (source && QueryRemoveSource(source))
		obs_source_remove(source);
2645 2646
}

J
jp9000 已提交
2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666
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 已提交
2667
void OBSBasic::on_actionSceneUp_triggered()
2668
{
J
jp9000 已提交
2669
	ChangeSceneIndex(true, -1, 0);
2670 2671
}

J
jp9000 已提交
2672
void OBSBasic::on_actionSceneDown_triggered()
2673
{
J
jp9000 已提交
2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685
	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);
2686 2687
}

J
jp9000 已提交
2688
void OBSBasic::on_sources_itemSelectionChanged()
2689
{
J
jp9000 已提交
2690
	SignalBlocker sourcesSignalBlocker(ui->sources);
2691

J
jp9000 已提交
2692
	auto updateItemSelection = [&]()
2693 2694
	{
		ignoreSelectionUpdate = true;
J
jp9000 已提交
2695 2696 2697 2698 2699 2700 2701
		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());
		}
2702 2703
		ignoreSelectionUpdate = false;
	};
J
jp9000 已提交
2704
	using updateItemSelection_t = decltype(updateItemSelection);
2705 2706

	obs_scene_atomic_update(GetCurrentScene(),
J
jp9000 已提交
2707
			[](void *data, obs_scene_t *)
2708
	{
J
jp9000 已提交
2709 2710
		(*static_cast<updateItemSelection_t*>(data))();
	}, static_cast<void*>(&updateItemSelection));
2711 2712
}

J
jp9000 已提交
2713 2714
void OBSBasic::EditSceneItemName()
{
2715
	QListWidgetItem *item = GetTopSelectedSourceItem();
2716
	Qt::ItemFlags flags   = item->flags();
P
Palana 已提交
2717
	OBSSceneItem sceneItem= GetOBSRef<OBSSceneItem>(item);
2718 2719
	obs_source_t *source  = obs_sceneitem_get_source(sceneItem);
	const char *name      = obs_source_get_name(source);
2720

2721
	item->setText(QT_UTF8(name));
2722
	item->setFlags(flags | Qt::ItemIsEditable);
2723
	ui->sources->removeItemWidget(item);
2724 2725
	ui->sources->editItem(item);
	item->setFlags(flags);
J
jp9000 已提交
2726 2727
}

J
jp9000 已提交
2728
void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
2729
{
2730
	QMenu popup(this);
J
jp9000 已提交
2731 2732
	QPointer<QMenu> previewProjector;
	QPointer<QMenu> sourceProjector;
J
jp9000 已提交
2733 2734 2735 2736 2737 2738

	if (preview) {
		QAction *action = popup.addAction(
				QTStr("Basic.Main.PreviewConextMenu.Enable"),
				this, SLOT(TogglePreview()));
		action->setCheckable(true);
2739 2740
		action->setChecked(
				obs_display_enabled(ui->preview->GetDisplay()));
2741 2742
		if (IsPreviewProgramMode())
			action->setEnabled(false);
J
jp9000 已提交
2743

J
jp9000 已提交
2744 2745 2746 2747 2748 2749
		previewProjector = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjector, this,
				SLOT(OpenPreviewProjector()));

		popup.addMenu(previewProjector);

J
jp9000 已提交
2750 2751 2752
		popup.addSeparator();
	}

J
jp9000 已提交
2753 2754 2755 2756 2757 2758 2759 2760
	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

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

J
John Bradley 已提交
2761
		OBSSceneItem sceneItem = GetSceneItem(item);
2762
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
J
John Bradley 已提交
2763 2764
		QAction *action;

J
jp9000 已提交
2765 2766
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
2767 2768
		popup.addAction(QTStr("Remove"), this,
				SLOT(on_actionRemoveSource_triggered()),
2769
				DeleteKeys.front());
J
jp9000 已提交
2770 2771
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
J
jp9000 已提交
2772
		popup.addMenu(ui->transformMenu);
J
jp9000 已提交
2773 2774 2775 2776 2777 2778 2779

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

		popup.addSeparator();
		popup.addMenu(sourceProjector);
J
jp9000 已提交
2780
		popup.addSeparator();
J
John Bradley 已提交
2781 2782 2783 2784 2785 2786 2787

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

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

J
jp9000 已提交
2788 2789
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenFilters()));
J
jp9000 已提交
2790 2791 2792 2793 2794
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));
	}

	popup.exec(QCursor::pos());
2795 2796
}

J
jp9000 已提交
2797 2798
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
2799 2800
	if (ui->scenes->count())
		CreateSourcePopupMenu(ui->sources->itemAt(pos), false);
J
jp9000 已提交
2801 2802
}

P
Palana 已提交
2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814
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 已提交
2815
void OBSBasic::AddSource(const char *id)
2816
{
2817 2818 2819
	if (id && *id) {
		OBSBasicSourceSelect sourceSelect(this, id);
		sourceSelect.exec();
2820 2821
		if (sourceSelect.newSource)
			CreatePropertiesWindow(sourceSelect.newSource);
2822
	}
2823 2824
}

2825
QMenu *OBSBasic::CreateAddSourcePopupMenu()
2826
{
2827
	const char *type;
J
jp9000 已提交
2828 2829
	bool foundValues = false;
	size_t idx = 0;
2830

2831
	QMenu *popup = new QMenu(QTStr("Add"), this);
J
jp9000 已提交
2832
	while (obs_enum_input_types(idx++, &type)) {
2833
		const char *name = obs_source_get_display_name(type);
2834

2835 2836 2837
		if (strcmp(type, "scene") == 0)
			continue;

J
jp9000 已提交
2838 2839
		QAction *popupItem = new QAction(QT_UTF8(name), this);
		popupItem->setData(QT_UTF8(type));
2840 2841 2842
		connect(popupItem, SIGNAL(triggered(bool)),
				this, SLOT(AddSourceFromAction()));
		popup->addAction(popupItem);
2843

J
jp9000 已提交
2844
		foundValues = true;
2845 2846
	}

2847 2848 2849
	if (!foundValues) {
		delete popup;
		popup = nullptr;
2850
	}
2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876

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

J
jp9000 已提交
2879
void OBSBasic::on_actionAddSource_triggered()
2880
{
J
jp9000 已提交
2881
	AddSourcePopupMenu(QCursor::pos());
2882 2883
}

J
jp9000 已提交
2884
void OBSBasic::on_actionRemoveSource_triggered()
2885
{
2886
	OBSSceneItem item   = GetCurrentSceneItem();
2887
	obs_source_t *source = obs_sceneitem_get_source(item);
2888 2889

	if (source && QueryRemoveSource(source))
J
jp9000 已提交
2890
		obs_sceneitem_remove(item);
2891 2892
}

J
John Bradley 已提交
2893 2894 2895 2896 2897 2898 2899 2900 2901
void OBSBasic::on_actionInteract_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreateInteractionWindow(source);
}

J
jp9000 已提交
2902
void OBSBasic::on_actionSourceProperties_triggered()
2903
{
2904
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2905
	OBSSource source = obs_sceneitem_get_source(item);
2906

2907 2908
	if (source)
		CreatePropertiesWindow(source);
2909 2910
}

J
jp9000 已提交
2911
void OBSBasic::on_actionSourceUp_triggered()
2912
{
J
jp9000 已提交
2913
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2914
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
2915
}
J
jp9000 已提交
2916

J
jp9000 已提交
2917
void OBSBasic::on_actionSourceDown_triggered()
2918
{
J
jp9000 已提交
2919
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2920
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
2921 2922
}

J
jp9000 已提交
2923 2924 2925
void OBSBasic::on_actionMoveUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2926
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
J
jp9000 已提交
2927 2928 2929 2930 2931
}

void OBSBasic::on_actionMoveDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2932
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
J
jp9000 已提交
2933 2934 2935 2936 2937
}

void OBSBasic::on_actionMoveToTop_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2938
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_TOP);
J
jp9000 已提交
2939 2940 2941 2942 2943
}

void OBSBasic::on_actionMoveToBottom_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2944
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_BOTTOM);
J
jp9000 已提交
2945 2946
}

2947
static BPtr<char> ReadLogFile(const char *log)
J
jp9000 已提交
2948
{
2949
	char logDir[512];
2950
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
2951
		return nullptr;
J
jp9000 已提交
2952 2953 2954 2955 2956

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

2957
	BPtr<char> file = os_quick_read_utf8_file(path.c_str());
J
jp9000 已提交
2958 2959 2960 2961 2962 2963 2964 2965
	if (!file)
		blog(LOG_WARNING, "Failed to read log file %s", path.c_str());

	return file;
}

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

2968
	if (!fileString)
J
jp9000 已提交
2969 2970
		return;

2971
	if (!*fileString)
J
jp9000 已提交
2972 2973 2974 2975
		return;

	ui->menuLogFiles->setEnabled(false);

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

2979 2980 2981
	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 已提交
2982

2983
	obs_data_set_string(content.get(), "content", fileString);
J
jp9000 已提交
2984

2985 2986 2987 2988 2989 2990 2991 2992 2993 2994
	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 已提交
2995
	if (!json) {
2996 2997 2998 2999
		blog(LOG_ERROR, "Failed to get JSON data for log upload");
		return;
	}

F
fryshorts 已提交
3000 3001 3002
	QBuffer *postData = new QBuffer();
	postData->setData(json, (int) strlen(json));

3003 3004 3005 3006
	if (logUploadThread) {
		logUploadThread->wait();
		delete logUploadThread;
	}
F
fryshorts 已提交
3007

3008 3009 3010 3011 3012 3013 3014
	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 已提交
3015 3016
}

P
Palana 已提交
3017 3018
void OBSBasic::on_actionShowLogs_triggered()
{
3019
	char logDir[512];
3020
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
3021 3022
		return;

P
Palana 已提交
3023 3024 3025 3026
	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
3027 3028 3029 3030 3031 3032 3033 3034 3035 3036
void OBSBasic::on_actionUploadCurrentLog_triggered()
{
	UploadLog(App()->GetCurrentLog());
}

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

3037 3038 3039
void OBSBasic::on_actionViewCurrentLog_triggered()
{
	char logDir[512];
3040
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052
		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 已提交
3053 3054 3055 3056 3057
void OBSBasic::on_actionCheckForUpdates_triggered()
{
	CheckForUpdates();
}

3058
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
J
jp9000 已提交
3059 3060 3061
{
	ui->menuLogFiles->setEnabled(true);

3062
	if (text.isEmpty()) {
J
jp9000 已提交
3063 3064
		QMessageBox::information(this,
				QTStr("LogReturnDialog.ErrorUploadingLog"),
3065
				error);
J
jp9000 已提交
3066 3067 3068
		return;
	}

3069
	obs_data_t *returnData = obs_data_create_from_json(QT_TO_UTF8(text));
F
fryshorts 已提交
3070
	QString logURL         = obs_data_get_string(returnData, "html_url");
J
jp9000 已提交
3071 3072 3073 3074 3075 3076
	obs_data_release(returnData);

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

J
jp9000 已提交
3077
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
3078
		obs_source_t *source, const string &name)
3079
{
3080 3081 3082 3083
	const char *prevName = obs_source_get_name(source);
	if (name == prevName)
		return;

3084
	obs_source_t    *foundSource = obs_get_source_by_name(name.c_str());
3085
	QListWidgetItem *listItem    = listWidget->currentItem();
3086

3087
	if (foundSource || name.empty()) {
3088
		listItem->setText(QT_UTF8(prevName));
3089

3090
		if (foundSource) {
3091 3092 3093 3094 3095 3096 3097 3098 3099
			QMessageBox::information(parent,
				QTStr("NameExists.Title"),
				QTStr("NameExists.Text"));
		} else if (name.empty()) {
			QMessageBox::information(parent,
				QTStr("NoNameEntered.Title"),
				QTStr("NoNameEntered.Text"));
		}

3100 3101 3102
		obs_source_release(foundSource);
	} else {
		listItem->setText(QT_UTF8(name.c_str()));
3103
		obs_source_set_name(source, name.c_str());
3104 3105 3106
	}
}

J
jp9000 已提交
3107 3108 3109 3110 3111
void OBSBasic::SceneNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSScene  scene = GetCurrentScene();
	QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
3112
	string    text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
3113 3114 3115 3116

	if (!scene)
		return;

3117
	obs_source_t *source = obs_scene_get_source(scene);
3118
	RenameListItem(this, ui->scenes, source, text);
J
jp9000 已提交
3119 3120 3121 3122 3123 3124 3125 3126 3127

	UNUSED_PARAMETER(endHint);
}

void OBSBasic::SceneItemNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSSceneItem item  = GetCurrentSceneItem();
	QLineEdit    *edit = qobject_cast<QLineEdit*>(editor);
3128
	string       text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
3129 3130 3131 3132

	if (!item)
		return;

3133
	obs_source_t *source = obs_sceneitem_get_source(item);
3134
	RenameListItem(this, ui->sources, source, text);
J
jp9000 已提交
3135

3136 3137 3138 3139
	QListWidgetItem *listItem = ui->sources->currentItem();
	listItem->setText(QString());
	SetupVisibilityItem(ui->sources, listItem, item);

J
jp9000 已提交
3140 3141 3142
	UNUSED_PARAMETER(endHint);
}

J
jp9000 已提交
3143 3144 3145 3146 3147 3148 3149 3150
void OBSBasic::OpenFilters()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
3151 3152 3153 3154 3155 3156 3157 3158
void OBSBasic::OpenSceneFilters()
{
	OBSScene scene = GetCurrentScene();
	OBSSource source = obs_scene_get_source(scene);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
3159 3160 3161 3162 3163 3164 3165 3166 3167
#define RECORDING_START \
	"==== Recording Start ==============================================="
#define RECORDING_STOP \
	"==== Recording Stop ================================================"
#define STREAMING_START \
	"==== Streaming Start ==============================================="
#define STREAMING_STOP \
	"==== Streaming Stop ================================================"

3168 3169 3170 3171
void OBSBasic::StartStreaming()
{
	SaveProject();

J
jp9000 已提交
3172 3173
	ui->streamButton->setEnabled(false);
	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
3174

J
jp9000 已提交
3175 3176 3177
	if (!outputHandler->StartStreaming(service)) {
		ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
		ui->streamButton->setEnabled(true);
3178 3179 3180 3181 3182 3183 3184 3185 3186
	}
}

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

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

3188
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3189
		ui->profileMenu->setEnabled(true);
3190
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3191
	}
3192 3193
}

J
jp9000 已提交
3194 3195 3196 3197 3198 3199 3200
void OBSBasic::ForceStopStreaming()
{
	SaveProject();

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

3201
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3202
		ui->profileMenu->setEnabled(true);
3203
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222
	}
}

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);
3223 3224 3225 3226 3227

	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
		App()->IncrementSleepInhibition();
	}
J
jp9000 已提交
3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247
}

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

3248
void OBSBasic::StreamingStart()
3249
{
3250
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
J
jp9000 已提交
3251
	ui->streamButton->setEnabled(true);
J
jp9000 已提交
3252
	ui->statusbar->StreamStarted(outputHandler->streamOutput);
3253 3254 3255 3256 3257 3258

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

3259
	blog(LOG_INFO, STREAMING_START);
3260 3261
}

3262
void OBSBasic::StreamingStop(int code)
3263
{
3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278
	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;

3279
	default:
3280 3281 3282 3283 3284 3285 3286 3287 3288 3289
	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 已提交
3290
	ui->statusbar->StreamStopped();
3291

3292
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
J
jp9000 已提交
3293
	ui->streamButton->setEnabled(true);
3294

3295
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3296
		ui->profileMenu->setEnabled(true);
3297 3298
		App()->DecrementSleepInhibition();
	}
3299 3300

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

3302 3303 3304 3305
	if (code != OBS_OUTPUT_SUCCESS)
		QMessageBox::information(this,
				QTStr("Output.ConnectFail.Title"),
				QT_UTF8(errorMessage));
J
jp9000 已提交
3306 3307 3308 3309 3310 3311

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

3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327
void OBSBasic::StartRecording()
{
	SaveProject();

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

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

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

3329
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3330
		ui->profileMenu->setEnabled(true);
3331
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3332
	}
3333 3334
}

P
Palana 已提交
3335 3336
void OBSBasic::RecordingStart()
{
J
jp9000 已提交
3337
	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
3338
	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
3339 3340 3341 3342 3343 3344

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

3345
	blog(LOG_INFO, RECORDING_START);
P
Palana 已提交
3346 3347
}

3348
void OBSBasic::RecordingStop(int code)
3349
{
P
Palana 已提交
3350
	ui->statusbar->RecordingStopped();
3351
	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
3352
	blog(LOG_INFO, RECORDING_STOP);
3353

J
jp9000 已提交
3354
	if (code == OBS_OUTPUT_UNSUPPORTED) {
3355 3356 3357
		QMessageBox::information(this,
				QTStr("Output.RecordFail.Title"),
				QTStr("Output.RecordFail.Unsupported"));
J
jp9000 已提交
3358

J
jp9000 已提交
3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369
	} 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"));
	}

3370
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
3371
		ui->profileMenu->setEnabled(true);
3372
		App()->DecrementSleepInhibition();
J
jp9000 已提交
3373
	}
3374
}
3375

3376 3377
void OBSBasic::on_streamButton_clicked()
{
J
jp9000 已提交
3378
	if (outputHandler->StreamingActive()) {
3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391
		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;
		}

3392
		StopStreaming();
3393
	} else {
3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406
		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;
		}

3407
		StartStreaming();
3408 3409 3410 3411 3412
	}
}

void OBSBasic::on_recordButton_clicked()
{
3413 3414 3415 3416
	if (outputHandler->RecordingActive())
		StopRecording();
	else
		StartRecording();
J
jp9000 已提交
3417 3418
}

J
jp9000 已提交
3419
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
3420
{
3421 3422
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
3423
}
3424

3425 3426 3427 3428 3429 3430
void OBSBasic::on_actionWebsite_triggered()
{
	QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450
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));
}

3451 3452 3453 3454 3455 3456 3457 3458 3459
QListWidgetItem *OBSBasic::GetTopSelectedSourceItem()
{
	QList<QListWidgetItem*> selectedItems = ui->sources->selectedItems();
	QListWidgetItem *topItem = nullptr;
	if (selectedItems.size() != 0)
		topItem = selectedItems[0];
	return topItem;
}

J
jp9000 已提交
3460 3461
void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos)
{
3462
	CreateSourcePopupMenu(GetTopSelectedSourceItem(), true);
J
jp9000 已提交
3463 3464 3465 3466 3467 3468 3469 3470

	UNUSED_PARAMETER(pos);
}

void OBSBasic::on_previewDisabledLabel_customContextMenuRequested(
		const QPoint &pos)
{
	QMenu popup(this);
J
jp9000 已提交
3471
	QPointer<QMenu> previewProjector;
J
jp9000 已提交
3472 3473 3474 3475 3476

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

J
jp9000 已提交
3479 3480 3481 3482 3483
	previewProjector = new QMenu(QTStr("PreviewProjector"));
	AddProjectorMenuMonitors(previewProjector, this,
			SLOT(OpenPreviewProjector()));

	popup.addMenu(previewProjector);
J
jp9000 已提交
3484 3485 3486 3487 3488
	popup.exec(QCursor::pos());

	UNUSED_PARAMETER(pos);
}

3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510
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();
}

3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573
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);
}

3574
config_t *OBSBasic::Config() const
3575 3576 3577
{
	return basicConfig;
}
J
jp9000 已提交
3578 3579 3580

void OBSBasic::on_actionEditTransform_triggered()
{
3581 3582 3583
	if (transformWindow)
		transformWindow->close();

J
jp9000 已提交
3584 3585
	transformWindow = new OBSBasicTransform(this);
	transformWindow->show();
3586
	transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
J
jp9000 已提交
3587 3588 3589 3590
}

void OBSBasic::on_actionResetTransform_triggered()
{
3591
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
3592 3593 3594 3595
	{
		if (!obs_sceneitem_selected(item))
			return true;

3596
		obs_transform_info info;
J
jp9000 已提交
3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613
		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);

		UNUSED_PARAMETER(scene);
		UNUSED_PARAMETER(param);
		return true;
	};

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

3614
static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
J
jp9000 已提交
3615 3616 3617 3618 3619
{
	matrix4 boxTransform;
	obs_sceneitem_get_box_transform(item, &boxTransform);

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

3622
	auto GetMinPos = [&] (float x, float y)
J
jp9000 已提交
3623 3624 3625 3626
	{
		vec3 pos;
		vec3_set(&pos, x, y, 0.0f);
		vec3_transform(&pos, &pos, &boxTransform);
3627 3628
		vec3_min(&tl, &tl, &pos);
		vec3_max(&br, &br, &pos);
J
jp9000 已提交
3629 3630
	};

3631 3632 3633 3634 3635 3636
	GetMinPos(0.0f, 0.0f);
	GetMinPos(1.0f, 0.0f);
	GetMinPos(0.0f, 1.0f);
	GetMinPos(1.0f, 1.0f);
}

3637
static vec3 GetItemTL(obs_sceneitem_t *item)
3638 3639 3640
{
	vec3 tl, br;
	GetItemBox(item, tl, br);
J
jp9000 已提交
3641 3642 3643
	return tl;
}

3644
static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
J
jp9000 已提交
3645 3646 3647 3648
{
	vec3 newTL;
	vec2 pos;

J
jp9000 已提交
3649
	obs_sceneitem_get_pos(item, &pos);
J
jp9000 已提交
3650 3651 3652
	newTL = GetItemTL(item);
	pos.x += tl.x - newTL.x;
	pos.y += tl.y - newTL.y;
J
jp9000 已提交
3653
	obs_sceneitem_set_pos(item, &pos);
J
jp9000 已提交
3654 3655
}

3656
static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
3657 3658 3659 3660 3661 3662 3663 3664 3665
		void *param)
{
	if (!obs_sceneitem_selected(item))
		return true;

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

	vec3 tl = GetItemTL(item);

J
jp9000 已提交
3666
	rot += obs_sceneitem_get_rot(item);
J
jp9000 已提交
3667 3668
	if (rot >= 360.0f)       rot -= 360.0f;
	else if (rot <= -360.0f) rot += 360.0f;
J
jp9000 已提交
3669
	obs_sceneitem_set_rot(item, rot);
J
jp9000 已提交
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 3695

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

3696
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
3697 3698 3699 3700 3701 3702 3703 3704 3705 3706
		void *param)
{
	vec2 &mul = *reinterpret_cast<vec2*>(param);

	if (!obs_sceneitem_selected(item))
		return true;

	vec3 tl = GetItemTL(item);

	vec2 scale;
J
jp9000 已提交
3707
	obs_sceneitem_get_scale(item, &scale);
J
jp9000 已提交
3708
	vec2_mul(&scale, &scale, &mul);
J
jp9000 已提交
3709
	obs_sceneitem_set_scale(item, &scale);
J
jp9000 已提交
3710 3711

	SetItemTL(item, tl);
J
jp9000 已提交
3712 3713

	UNUSED_PARAMETER(scene);
J
jp9000 已提交
3714 3715 3716 3717 3718
	return true;
}

void OBSBasic::on_actionFlipHorizontal_triggered()
{
J
jp9000 已提交
3719 3720
	vec2 scale;
	vec2_set(&scale, -1.0f, 1.0f);
J
jp9000 已提交
3721 3722 3723 3724 3725 3726
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

void OBSBasic::on_actionFlipVertical_triggered()
{
J
jp9000 已提交
3727 3728
	vec2 scale;
	vec2_set(&scale, 1.0f, -1.0f);
J
jp9000 已提交
3729 3730 3731 3732
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

3733
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
3734 3735 3736 3737 3738 3739 3740 3741 3742 3743
		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);

3744
	obs_transform_info itemInfo;
J
jp9000 已提交
3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776
	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()
{
3777
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802
	{
		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 已提交
3803
		UNUSED_PARAMETER(param);
3804 3805 3806 3807
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
J
jp9000 已提交
3808
}
J
jp9000 已提交
3809

3810 3811 3812 3813 3814 3815 3816
void OBSBasic::EnablePreviewDisplay(bool enable)
{
	obs_display_set_enabled(ui->preview->GetDisplay(), enable);
	ui->preview->setVisible(enable);
	ui->previewDisabledLabel->setVisible(!enable);
}

J
jp9000 已提交
3817 3818
void OBSBasic::TogglePreview()
{
3819 3820
	previewEnabled = !previewEnabled;
	EnablePreviewDisplay(previewEnabled);
J
jp9000 已提交
3821
}
3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832

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 已提交
3833
		struct vec2 dir;
3834 3835
		struct vec2 pos;

J
jp9000 已提交
3836 3837
		vec2_set(&dir, 0.0f, 0.0f);

3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860
		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 已提交
3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901

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);
}
3902 3903 3904 3905 3906

void OBSBasic::UpdateTitleBar()
{
	stringstream name;

J
jp9000 已提交
3907 3908
	const char *profile = config_get_string(App()->GlobalConfig(),
			"Basic", "Profile");
J
jp9000 已提交
3909 3910 3911
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

3912 3913 3914 3915 3916
	name << "OBS ";
	if (previewProgramMode)
		name << "Studio ";

	name << App()->GetVersionString();
J
jp9000 已提交
3917
	name << " - " << Str("TitleBar.Profile") << ": " << profile;
J
jp9000 已提交
3918
	name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;
3919 3920 3921

	setWindowTitle(QT_UTF8(name.str().c_str()));
}
J
jp9000 已提交
3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945

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