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

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
8
    the Free Software Foundation, either version 2 of the License, or
9 10 11
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 14 15 16 17 18 19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

J
jp9000 已提交
20
#include <time.h>
J
jp9000 已提交
21
#include <obs.hpp>
22
#include <QGuiApplication>
J
jp9000 已提交
23
#include <QMessageBox>
24
#include <QShowEvent>
25
#include <QDesktopServices>
J
jp9000 已提交
26
#include <QFileDialog>
27
#include <QDesktopWidget>
28 29
#include <QRect>
#include <QScreen>
30

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

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

J
jp9000 已提交
58 59 60 61
#if defined(_WIN32) && defined(ENABLE_WIN_UPDATER)
#include "win-update/win-update.hpp"
#endif

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

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

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

70
using namespace std;
J
jp9000 已提交
71

J
jp9000 已提交
72 73 74 75 76 77 78 79 80 81
namespace {

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

}

J
jp9000 已提交
82 83
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);
P
Palana 已提交
84
Q_DECLARE_METATYPE(OBSSource);
J
jp9000 已提交
85
Q_DECLARE_METATYPE(obs_order_movement);
J
jp9000 已提交
86
Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
J
jp9000 已提交
87

P
Palana 已提交
88 89 90 91 92 93 94 95 96 97 98 99 100
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));
}

101 102
static void AddExtraModulePaths()
{
103
	char base_module_dir[512];
104 105 106 107
#if defined(_WIN32) || defined(__APPLE__)
	int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir),
			"obs-studio/plugins/%module%");
#else
108
	int ret = GetConfigPath(base_module_dir, sizeof(base_module_dir),
109
			"obs-studio/plugins/%module%");
110
#endif
B
BtbN 已提交
111

112
	if (ret <= 0)
113 114 115
		return;

	string path = (char*)base_module_dir;
116
#if defined(__APPLE__)
117
	obs_add_module_path((path + "/bin").c_str(), (path + "/data").c_str());
118 119 120 121 122

	BPtr<char> config_bin = os_get_config_path_ptr("obs-studio/plugins/%module%/bin");
	BPtr<char> config_data = os_get_config_path_ptr("obs-studio/plugins/%module%/data");
	obs_add_module_path(config_bin, config_data);

123 124 125 126 127 128 129
#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
130 131
}

132 133
static QList<QKeySequence> DeleteKeys;

134
OBSBasic::OBSBasic(QWidget *parent)
135
	: OBSMainWindow  (parent),
J
jp9000 已提交
136
	  ui             (new Ui::OBSBasic)
137
{
138 139
	setAttribute(Qt::WA_NativeWindow);

C
cg2121 已提交
140 141 142
	projectorArray.resize(10, "");
	previewProjectorArray.resize(10, 0);

J
jp9000 已提交
143 144
	setAcceptDrops(true);

145
	ui->setupUi(this);
J
jp9000 已提交
146 147
	ui->previewDisabledLabel->setVisible(false);

J
jp9000 已提交
148 149
	startingDockLayout = saveState();

S
Socapex 已提交
150
	copyActionsDynamicProperties();
151

152 153
	ui->sources->setItemDelegate(new VisibilityItemDelegate(ui->sources));

154
	char styleSheetPath[512];
J
jp9000 已提交
155 156
	int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath),
			"stylesheet.qss");
157
	if (ret > 0) {
H
HomeWorld 已提交
158 159 160 161 162
		if (QFile::exists(styleSheetPath)) {
			QString path = QString("file:///") +
				QT_UTF8(styleSheetPath);
			App()->setStyleSheet(path);
		}
163 164
	}

P
Palana 已提交
165 166 167
	qRegisterMetaType<OBSScene>    ("OBSScene");
	qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
	qRegisterMetaType<OBSSource>   ("OBSSource");
P
Palana 已提交
168
	qRegisterMetaType<obs_hotkey_id>("obs_hotkey_id");
P
Palana 已提交
169

170 171 172
	qRegisterMetaTypeStreamOperators<
		std::vector<std::shared_ptr<OBSSignal>>>(
				"std::vector<std::shared_ptr<OBSSignal>>");
173
	qRegisterMetaTypeStreamOperators<OBSScene>("OBSScene");
174
	qRegisterMetaTypeStreamOperators<OBSSceneItem>("OBSSceneItem");
175

P
Palana 已提交
176 177 178
	ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false);
	ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false);

179
	auto displayResize = [this]() {
180 181 182 183
		struct obs_video_info ovi;

		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
184 185 186 187
	};

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

P
Palana 已提交
189 190
	installEventFilter(CreateShortcutFilter());

191
	stringstream name;
J
jp9000 已提交
192
	name << "OBS " << App()->GetVersionString();
193 194 195
	blog(LOG_INFO, "%s", name.str().c_str());
	blog(LOG_INFO, "---------------------------------");

196
	UpdateTitleBar();
J
jp9000 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210

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

	cpuUsageInfo = os_cpu_usage_info_start();
J
jp9000 已提交
213 214 215 216
	cpuUsageTimer = new QTimer(this);
	connect(cpuUsageTimer, SIGNAL(timeout()),
			ui->statusbar, SLOT(UpdateCPUUsage()));
	cpuUsageTimer->start(3000);
217

218 219 220 221 222 223
	DeleteKeys =
#ifdef __APPLE__
		QList<QKeySequence>{{Qt::Key_Backspace}} <<
#endif
		QKeySequence::keyBindings(QKeySequence::Delete);

224
#ifdef __APPLE__
225 226
	ui->actionRemoveSource->setShortcuts(DeleteKeys);
	ui->actionRemoveScene->setShortcuts(DeleteKeys);
227 228 229

	ui->action_Settings->setMenuRole(QAction::PreferencesRole);
	ui->actionE_xit->setMenuRole(QAction::QuitRole);
230
#endif
231 232 233 234 235 236 237 238 239 240 241 242 243 244

	auto addNudge = [this](const QKeySequence &seq, const char *s)
	{
		QAction *nudge = new QAction(ui->preview);
		nudge->setShortcut(seq);
		nudge->setShortcutContext(Qt::WidgetShortcut);
		ui->preview->addAction(nudge);
		connect(nudge, SIGNAL(triggered()), this, s);
	};

	addNudge(Qt::Key_Up, SLOT(NudgeUp()));
	addNudge(Qt::Key_Down, SLOT(NudgeDown()));
	addNudge(Qt::Key_Left, SLOT(NudgeLeft()));
	addNudge(Qt::Key_Right, SLOT(NudgeRight()));
J
jp9000 已提交
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

	auto assignDockToggle = [this](QDockWidget *dock, QAction *action)
	{
		auto handleWindowToggle = [action] (bool vis)
		{
			action->blockSignals(true);
			action->setChecked(vis);
			action->blockSignals(false);
		};
		auto handleMenuToggle = [dock] (bool check)
		{
			dock->blockSignals(true);
			dock->setVisible(check);
			dock->blockSignals(false);
		};

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

	assignDockToggle(ui->scenesDock, ui->toggleScenes);
	assignDockToggle(ui->sourcesDock, ui->toggleSources);
	assignDockToggle(ui->mixerDock, ui->toggleMixer);
	assignDockToggle(ui->transitionsDock, ui->toggleTransitions);
	assignDockToggle(ui->controlsDock, ui->toggleControls);
S
SuslikV 已提交
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

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

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

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

299 300
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
		vector<OBSSource> &audioSources)
301
{
302
	obs_source_t *source = obs_get_output_source(channel);
303 304 305
	if (!source)
		return;

306 307
	audioSources.push_back(source);

308
	obs_data_t *data = obs_save_source(source);
309

J
jp9000 已提交
310
	obs_data_set_obj(parent, name, data);
311 312 313 314 315

	obs_data_release(data);
	obs_source_release(source);
}

316 317
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
		obs_data_array_t *quickTransitionData, int transitionDuration,
J
jp9000 已提交
318
		obs_data_array_t *transitions,
C
cg2121 已提交
319 320 321
		OBSScene &scene, OBSSource &curProgramScene,
		obs_data_array_t *savedProjectorList,
		obs_data_array_t *savedPreviewProjectorList)
322
{
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	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));

347 348
	obs_source_t *transition = obs_get_output_source(0);
	obs_source_t *currentScene = obs_scene_get_source(scene);
349
	const char   *sceneName   = obs_source_get_name(currentScene);
350
	const char   *programName = obs_source_get_name(curProgramScene);
351

J
jp9000 已提交
352 353 354
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

J
jp9000 已提交
355
	obs_data_set_string(saveData, "current_scene", sceneName);
356
	obs_data_set_string(saveData, "current_program_scene", programName);
J
jp9000 已提交
357
	obs_data_set_array(saveData, "scene_order", sceneOrder);
J
jp9000 已提交
358
	obs_data_set_string(saveData, "name", sceneCollection);
J
jp9000 已提交
359
	obs_data_set_array(saveData, "sources", sourcesArray);
360
	obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
J
jp9000 已提交
361
	obs_data_set_array(saveData, "transitions", transitions);
C
cg2121 已提交
362 363 364
	obs_data_set_array(saveData, "saved_projectors", savedProjectorList);
	obs_data_set_array(saveData, "saved_preview_projectors",
			savedPreviewProjectorList);
365
	obs_data_array_release(sourcesArray);
366 367 368 369 370

	obs_data_set_string(saveData, "current_transition",
			obs_source_get_name(transition));
	obs_data_set_int(saveData, "transition_duration", transitionDuration);
	obs_source_release(transition);
371 372 373 374

	return saveData;
}

S
Socapex 已提交
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
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));
		}
	}
}

395 396 397 398 399 400 401 402 403 404 405 406
void OBSBasic::ClearVolumeControls()
{
	VolControl *control;

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

	volumes.clear();
}

J
jp9000 已提交
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
obs_data_array_t *OBSBasic::SaveSceneListOrder()
{
	obs_data_array_t *sceneOrder = obs_data_array_create();

	for (int i = 0; i < ui->scenes->count(); i++) {
		obs_data_t *data = obs_data_create();
		obs_data_set_string(data, "name",
				QT_TO_UTF8(ui->scenes->item(i)->text()));
		obs_data_array_push_back(sceneOrder, data);
		obs_data_release(data);
	}

	return sceneOrder;
}

C
cg2121 已提交
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
obs_data_array_t *OBSBasic::SaveProjectors()
{
	obs_data_array_t *saveProjector = obs_data_array_create();

	for (size_t i = 0; i < projectorArray.size(); i++) {
		obs_data_t *data = obs_data_create();
		obs_data_set_string(data, "saved_projectors",
			projectorArray.at(i).c_str());
		obs_data_array_push_back(saveProjector, data);
		obs_data_release(data);
	}

	return saveProjector;
}

obs_data_array_t *OBSBasic::SavePreviewProjectors()
{
	obs_data_array_t *saveProjector = obs_data_array_create();

	for (size_t i = 0; i < previewProjectorArray.size(); i++) {
		obs_data_t *data = obs_data_create();
		obs_data_set_int(data, "saved_preview_projectors",
			previewProjectorArray.at(i));
		obs_data_array_push_back(saveProjector, data);
		obs_data_release(data);
	}

	return saveProjector;
}

452 453
void OBSBasic::Save(const char *file)
{
454 455 456 457 458
	OBSScene scene = GetCurrentScene();
	OBSSource curProgramScene = OBSGetStrongRef(programScene);
	if (!curProgramScene)
		curProgramScene = obs_scene_get_source(scene);

J
jp9000 已提交
459
	obs_data_array_t *sceneOrder = SaveSceneListOrder();
J
jp9000 已提交
460
	obs_data_array_t *transitions = SaveTransitions();
461
	obs_data_array_t *quickTrData = SaveQuickTransitions();
C
cg2121 已提交
462 463
	obs_data_array_t *savedProjectorList = SaveProjectors();
	obs_data_array_t *savedPreviewProjectorList = SavePreviewProjectors();
464
	obs_data_t *saveData  = GenerateSaveData(sceneOrder, quickTrData,
J
jp9000 已提交
465
			ui->transitionDuration->value(), transitions,
C
cg2121 已提交
466 467
			scene, curProgramScene, savedProjectorList,
			savedPreviewProjectorList);
468

J
jp9000 已提交
469
	obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked());
470 471 472 473 474 475 476 477
	obs_data_set_bool(saveData, "scaling_enabled",
			ui->preview->IsFixedScaling());
	obs_data_set_int(saveData, "scaling_level",
			ui->preview->GetScalingLevel());
	obs_data_set_double(saveData, "scaling_off_x",
			ui->preview->GetScrollX());
	obs_data_set_double(saveData, "scaling_off_y",
			ui->preview->GetScrollY());
J
jp9000 已提交
478

J
jp9000 已提交
479 480 481 482 483 484 485
	if (api) {
		obs_data_t *moduleObj = obs_data_create();
		api->on_save(moduleObj);
		obs_data_set_obj(saveData, "modules", moduleObj);
		obs_data_release(moduleObj);
	}

486 487
	if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
		blog(LOG_ERROR, "Could not save scene data to %s", file);
488 489

	obs_data_release(saveData);
J
jp9000 已提交
490
	obs_data_array_release(sceneOrder);
491
	obs_data_array_release(quickTrData);
J
jp9000 已提交
492
	obs_data_array_release(transitions);
C
cg2121 已提交
493 494
	obs_data_array_release(savedProjectorList);
	obs_data_array_release(savedPreviewProjectorList);
495 496
}

497
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
498
{
499
	obs_data_t *data = obs_data_get_obj(parent, name);
500 501 502
	if (!data)
		return;

503
	obs_source_t *source = obs_load_source(data);
504 505 506 507 508 509 510 511
	if (source) {
		obs_set_output_source(channel, source);
		obs_source_release(source);
	}

	obs_data_release(data);
}

512 513 514
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
515
	obs_properties_t *props = obs_get_source_properties(output_id);
516 517 518 519 520 521 522 523 524 525 526 527 528 529
	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;
}

530
void OBSBasic::CreateFirstRunSources()
531
{
532 533 534 535 536 537 538 539 540
	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);
541 542
}

543
void OBSBasic::CreateDefaultScene(bool firstStart)
544 545 546 547
{
	disableSaving++;

	ClearSceneData();
548 549 550 551
	InitDefaultTransitions();
	CreateDefaultQuickTransitions();
	ui->transitionDuration->setValue(300);
	SetTransition(fadeTransition);
552 553 554

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

555
	if (firstStart)
556
		CreateFirstRunSources();
557

558
	AddScene(obs_scene_get_source(scene));
559
	SetCurrentScene(scene, true);
560
	obs_scene_release(scene);
J
jp9000 已提交
561 562

	disableSaving--;
563 564
}

J
jp9000 已提交
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex)
{
	for (int i = 0; i < lw->count(); i++) {
		QListWidgetItem *item = lw->item(i);

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

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

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

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

		obs_data_release(data);
	}
}

C
cg2121 已提交
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
void OBSBasic::LoadSavedProjectors(obs_data_array_t *array)
{
	size_t num = obs_data_array_count(array);

	for (size_t i = 0; i < num; i++) {
		obs_data_t *data = obs_data_array_item(array, i);
		projectorArray.at(i) = obs_data_get_string(data,
				"saved_projectors");

		obs_data_release(data);
	}
}

void OBSBasic::LoadSavedPreviewProjectors(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);
		previewProjectorArray.at(i) = obs_data_get_int(data,
				"saved_preview_projectors");

		obs_data_release(data);
	}
}

620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
static void LogFilter(obs_source_t*, obs_source_t *filter, void *v_val)
{
	const char *name = obs_source_get_name(filter);
	const char *id = obs_source_get_id(filter);
	int val = (int)(intptr_t)v_val;
	string indent;

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

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

static bool LogSceneItem(obs_scene_t*, obs_sceneitem_t *item, void*)
{
	obs_source_t *source = obs_sceneitem_get_source(item);
	const char *name = obs_source_get_name(source);
	const char *id = obs_source_get_id(source);

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

641 642 643 644 645 646 647 648 649 650 651 652
	obs_monitoring_type monitoring_type =
		obs_source_get_monitoring_type(source);

	if (monitoring_type != OBS_MONITORING_TYPE_NONE) {
		const char *type =
			(monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY)
			? "monitor only"
			: "monitor and output";

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

653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
	obs_source_enum_filters(source, LogFilter, (void*)(intptr_t)2);
	return true;
}

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

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

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

		blog(LOG_INFO, "- scene '%s':", name);
		obs_scene_enum_items(scene, LogSceneItem, nullptr);
		obs_source_enum_filters(source, LogFilter, (void*)(intptr_t)1);
	}

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

677 678
void OBSBasic::Load(const char *file)
{
679 680 681 682 683
	disableSaving++;

	obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak");
	if (!data) {
		disableSaving--;
684
		blog(LOG_INFO, "No scene file found, creating default scene");
685
		CreateDefaultScene(true);
J
jp9000 已提交
686
		SaveProject();
687
		return;
688
	}
689

690
	ClearSceneData();
691
	InitDefaultTransitions();
692

J
jp9000 已提交
693
	obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
694
	obs_data_array_t *sources    = obs_data_get_array(data, "sources");
J
jp9000 已提交
695
	obs_data_array_t *transitions= obs_data_get_array(data, "transitions");
J
jp9000 已提交
696 697
	const char       *sceneName = obs_data_get_string(data,
			"current_scene");
698 699 700 701 702
	const char       *programSceneName = obs_data_get_string(data,
			"current_program_scene");
	const char       *transitionName = obs_data_get_string(data,
			"current_transition");

703 704 705 706 707 708
	if (!opt_starting_scene.empty()) {
		programSceneName = opt_starting_scene.c_str();
		if (!IsPreviewProgramMode())
			sceneName = opt_starting_scene.c_str();
	}

709 710 711 712 713 714
	int newDuration = obs_data_get_int(data, "transition_duration");
	if (!newDuration)
		newDuration = 300;

	if (!transitionName)
		transitionName = obs_source_get_name(fadeTransition);
J
jp9000 已提交
715 716 717 718 719 720 721

	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");
722
	obs_source_t     *curScene;
723 724
	obs_source_t     *curProgramScene;
	obs_source_t     *curTransition;
725

J
jp9000 已提交
726 727 728
	if (!name || !*name)
		name = curSceneCollection;

729 730 731 732 733 734
	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);

735
	obs_load_sources(sources, OBSBasic::SourceLoaded, this);
736

J
jp9000 已提交
737 738
	if (transitions)
		LoadTransitions(transitions);
J
jp9000 已提交
739 740 741
	if (sceneOrder)
		LoadSceneListOrder(sceneOrder);

J
jp9000 已提交
742 743
	obs_data_array_release(transitions);

744 745 746 747 748 749 750
	curTransition = FindTransition(transitionName);
	if (!curTransition)
		curTransition = fadeTransition;

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

C
cg2121 已提交
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
	obs_data_array_t *savedProjectors = obs_data_get_array(data,
			"saved_projectors");

	if (savedProjectors)
		LoadSavedProjectors(savedProjectors);

	obs_data_array_release(savedProjectors);

	obs_data_array_t *savedPreviewProjectors = obs_data_get_array(data,
			"saved_preview_projectors");

	if (savedPreviewProjectors)
		LoadSavedPreviewProjectors(savedPreviewProjectors);

	obs_data_array_release(savedPreviewProjectors);


768
retryScene:
769
	curScene = obs_get_source_by_name(sceneName);
770
	curProgramScene = obs_get_source_by_name(programSceneName);
771 772 773 774 775 776 777 778 779 780 781 782 783

	/* if the starting scene command line parameter is bad at all,
	 * fall back to original settings */
	if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) {
		sceneName = obs_data_get_string(data, "current_scene");
		programSceneName = obs_data_get_string(data,
				"current_program_scene");
		obs_source_release(curScene);
		obs_source_release(curProgramScene);
		opt_starting_scene.clear();
		goto retryScene;
	}

784 785 786 787 788 789 790 791
	if (!curProgramScene) {
		curProgramScene = curScene;
		obs_source_addref(curScene);
	}

	SetCurrentScene(curScene, true);
	if (IsPreviewProgramMode())
		TransitionToScene(curProgramScene, true);
792
	obs_source_release(curScene);
793
	obs_source_release(curProgramScene);
794 795

	obs_data_array_release(sources);
J
jp9000 已提交
796
	obs_data_array_release(sceneOrder);
J
jp9000 已提交
797 798 799 800 801 802 803 804 805

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

806 807 808 809 810 811 812
	obs_data_array_t *quickTransitionData = obs_data_get_array(data,
			"quick_transitions");
	LoadQuickTransitions(quickTransitionData);
	obs_data_array_release(quickTransitionData);

	RefreshQuickTransitions();

J
jp9000 已提交
813 814 815 816
	bool previewLocked = obs_data_get_bool(data, "preview_locked");
	ui->preview->SetLocked(previewLocked);
	ui->actionLockPreview->setChecked(previewLocked);

817 818 819 820 821 822 823 824 825 826
	/* ---------------------- */

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

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

830
	/* ---------------------- */
J
Joseph El-Khouri 已提交
831

J
jp9000 已提交
832 833 834 835 836 837
	if (api) {
		obs_data_t *modulesObj = obs_data_get_obj(data, "modules");
		api->on_load(modulesObj);
		obs_data_release(modulesObj);
	}

838
	obs_data_release(data);
J
jp9000 已提交
839

840 841 842
	if (!opt_starting_scene.empty())
		opt_starting_scene.clear();

843
	if (opt_start_streaming) {
844
		blog(LOG_INFO, "Starting stream due to command line parameter");
845 846 847 848 849 850
		QMetaObject::invokeMethod(this, "StartStreaming",
				Qt::QueuedConnection);
		opt_start_streaming = false;
	}

	if (opt_start_recording) {
851
		blog(LOG_INFO, "Starting recording due to command line parameter");
852 853 854 855 856
		QMetaObject::invokeMethod(this, "StartRecording",
				Qt::QueuedConnection);
		opt_start_recording = false;
	}

C
cg2121 已提交
857 858 859 860 861 862
	if (opt_start_replaybuffer) {
		QMetaObject::invokeMethod(this, "StartReplayBuffer",
				Qt::QueuedConnection);
		opt_start_replaybuffer = false;
	}

863 864
	LogScenes();

J
jp9000 已提交
865
	disableSaving--;
866 867
}

J
jp9000 已提交
868
#define SERVICE_PATH "service.json"
869 870 871 872 873 874

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

875
	char serviceJsonPath[512];
J
jp9000 已提交
876
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
877 878
			SERVICE_PATH);
	if (ret <= 0)
879 880
		return;

881 882
	obs_data_t *data     = obs_data_create();
	obs_data_t *settings = obs_service_get_settings(service);
883

884
	obs_data_set_string(data, "type", obs_service_get_type(service));
J
jp9000 已提交
885
	obs_data_set_obj(data, "settings", settings);
886

887 888
	if (!obs_data_save_json_safe(data, serviceJsonPath, "tmp", "bak"))
		blog(LOG_WARNING, "Failed to save service");
889 890 891 892 893 894 895 896 897

	obs_data_release(settings);
	obs_data_release(data);
}

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

898
	char serviceJsonPath[512];
J
jp9000 已提交
899
	int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
900 901
			SERVICE_PATH);
	if (ret <= 0)
902 903
		return false;

904 905
	obs_data_t *data = obs_data_create_from_json_file_safe(serviceJsonPath,
			"bak");
906 907

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

910
	obs_data_t *settings = obs_data_get_obj(data, "settings");
P
Palana 已提交
911
	obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys");
912

913
	service = obs_service_create(type, "default_service", settings,
P
Palana 已提交
914
			hotkey_data);
915
	obs_service_release(service);
916

P
Palana 已提交
917
	obs_data_release(hotkey_data);
918 919 920 921 922 923 924 925
	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitService()
{
P
Palana 已提交
926 927
	ProfileScope("OBSBasic::InitService");

928 929 930
	if (LoadService())
		return true;

931 932
	service = obs_service_create("rtmp_common", "default_service", nullptr,
			nullptr);
933 934
	if (!service)
		return false;
935
	obs_service_release(service);
936 937 938 939

	return true;
}

940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
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
};

956 957
bool OBSBasic::InitBasicConfigDefaults()
{
958
	QList<QScreen*> screens = QGuiApplication::screens();
959

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

966 967 968 969
	QScreen *primaryScreen = QGuiApplication::primaryScreen();

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

971 972 973 974 975 976 977 978 979 980 981
	bool oldResolutionDefaults = config_get_bool(App()->GlobalConfig(),
			"General", "Pre19Defaults");

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

982 983 984 985 986 987 988 989 990 991
	/* ----------------------------------------------------- */
	/* 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");
992
		config_save_safe(basicConfig, "tmp", nullptr);
993 994 995 996
	}

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

997
	config_set_default_string(basicConfig, "Output", "Mode", "Simple");
J
jp9000 已提交
998

999 1000
	config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
			GetDefaultVideoSavePath().c_str());
1001 1002
	config_set_default_string(basicConfig, "SimpleOutput", "RecFormat",
			"flv");
1003 1004
	config_set_default_uint  (basicConfig, "SimpleOutput", "VBitrate",
			2500);
1005 1006
	config_set_default_string(basicConfig, "SimpleOutput", "StreamEncoder",
			SIMPLE_ENCODER_X264);
1007
	config_set_default_uint  (basicConfig, "SimpleOutput", "ABitrate", 160);
J
jp9000 已提交
1008 1009
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseAdvanced",
			false);
1010 1011
	config_set_default_bool  (basicConfig, "SimpleOutput", "EnforceBitrate",
			true);
J
jp9000 已提交
1012 1013
	config_set_default_string(basicConfig, "SimpleOutput", "Preset",
			"veryfast");
1014 1015 1016 1017
	config_set_default_string(basicConfig, "SimpleOutput", "RecQuality",
			"Stream");
	config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
			SIMPLE_ENCODER_X264);
1018 1019 1020
	config_set_default_bool(basicConfig, "SimpleOutput", "RecRB", false);
	config_set_default_int(basicConfig, "SimpleOutput", "RecRBTime", 20);
	config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512);
1021 1022
	config_set_default_string(basicConfig, "SimpleOutput", "RecRBPrefix",
			"Replay");
1023

1024 1025
	config_set_default_bool  (basicConfig, "AdvOut", "ApplyServiceSettings",
			true);
J
jp9000 已提交
1026 1027 1028 1029 1030 1031 1032 1033
	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());
1034
	config_set_default_string(basicConfig, "AdvOut", "RecFormat", "flv");
J
jp9000 已提交
1035 1036
	config_set_default_bool  (basicConfig, "AdvOut", "RecUseRescale",
			false);
1037
	config_set_default_uint  (basicConfig, "AdvOut", "RecTracks", (1<<0));
J
jp9000 已提交
1038 1039 1040
	config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
			"none");

1041 1042 1043 1044 1045
	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 已提交
1046
	config_set_default_uint  (basicConfig, "AdvOut", "FFVBitrate", 2500);
1047
	config_set_default_uint  (basicConfig, "AdvOut", "FFVGOPSize", 250);
J
jp9000 已提交
1048 1049
	config_set_default_bool  (basicConfig, "AdvOut", "FFUseRescale",
			false);
1050 1051
	config_set_default_bool  (basicConfig, "AdvOut", "FFIgnoreCompat",
			false);
J
jp9000 已提交
1052 1053 1054 1055 1056 1057 1058
	config_set_default_uint  (basicConfig, "AdvOut", "FFABitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "FFAudioTrack", 1);

	config_set_default_uint  (basicConfig, "AdvOut", "Track1Bitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "Track2Bitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "Track3Bitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "Track4Bitrate", 160);
J
jp9000 已提交
1059 1060
	config_set_default_uint  (basicConfig, "AdvOut", "Track5Bitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "Track6Bitrate", 160);
J
jp9000 已提交
1061

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

1066 1067 1068
	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);

1069 1070 1071 1072 1073 1074 1075 1076
	/* don't allow BaseCX/BaseCY to be susceptible to defaults changing */
	if (!config_has_user_value(basicConfig, "Video", "BaseCX") ||
	    !config_has_user_value(basicConfig, "Video", "BaseCY")) {
		config_set_uint(basicConfig, "Video", "BaseCX", cx);
		config_set_uint(basicConfig, "Video", "BaseCY", cy);
		config_save_safe(basicConfig, "tmp", nullptr);
	}

1077
	config_set_default_string(basicConfig, "Output", "FilenameFormatting",
B
bl 已提交
1078
			"%CCYY-%MM-%DD %hh-%mm-%ss");
1079

1080 1081 1082 1083
	config_set_default_bool  (basicConfig, "Output", "DelayEnable", false);
	config_set_default_uint  (basicConfig, "Output", "DelaySec", 20);
	config_set_default_bool  (basicConfig, "Output", "DelayPreserve", true);

1084 1085 1086 1087
	config_set_default_bool  (basicConfig, "Output", "Reconnect", true);
	config_set_default_uint  (basicConfig, "Output", "RetryDelay", 10);
	config_set_default_uint  (basicConfig, "Output", "MaxRetries", 20);

1088
	config_set_default_string(basicConfig, "Output", "BindIP", "default");
D
derrod 已提交
1089 1090 1091 1092
	config_set_default_bool  (basicConfig, "Output", "NewSocketLoopEnable",
			false);
	config_set_default_bool  (basicConfig, "Output", "LowLatencyEnable",
			false);
1093

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
	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);
1108

1109 1110 1111 1112 1113 1114 1115 1116 1117
	/* don't allow OutputCX/OutputCY to be susceptible to defaults
	 * changing */
	if (!config_has_user_value(basicConfig, "Video", "OutputCX") ||
	    !config_has_user_value(basicConfig, "Video", "OutputCY")) {
		config_set_uint(basicConfig, "Video", "OutputCX", scale_cx);
		config_set_uint(basicConfig, "Video", "OutputCY", scale_cy);
		config_save_safe(basicConfig, "tmp", nullptr);
	}

1118 1119 1120 1121 1122
	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);
1123
	config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
1124
	config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
1125
	config_set_default_string(basicConfig, "Video", "ColorSpace", "601");
1126 1127
	config_set_default_string(basicConfig, "Video", "ColorRange",
			"Partial");
1128

1129 1130 1131 1132 1133
	config_set_default_string(basicConfig, "Audio", "MonitoringDeviceId",
			"default");
	config_set_default_string(basicConfig, "Audio", "MonitoringDeviceName",
			Str("Basic.Settings.Advanced.Audio.MonitoringDevice"
				".Default"));
1134 1135 1136 1137 1138 1139 1140 1141 1142
	config_set_default_uint  (basicConfig, "Audio", "SampleRate", 44100);
	config_set_default_string(basicConfig, "Audio", "ChannelSetup",
			"Stereo");

	return true;
}

bool OBSBasic::InitBasicConfig()
{
P
Palana 已提交
1143 1144
	ProfileScope("OBSBasic::InitBasicConfig");

1145
	char configPath[512];
J
jp9000 已提交
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158

	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");
1159 1160 1161 1162
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get base.ini path");
		return false;
	}
1163

1164 1165 1166
	int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (code != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", code);
1167 1168 1169
		return false;
	}

J
jp9000 已提交
1170 1171 1172 1173 1174
	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);
1175
		basicConfig.SaveSafe("tmp");
J
jp9000 已提交
1176 1177
	}

1178 1179 1180
	return InitBasicConfigDefaults();
}

1181 1182
void OBSBasic::InitOBSCallbacks()
{
P
Palana 已提交
1183 1184
	ProfileScope("OBSBasic::InitOBSCallbacks");

P
Palana 已提交
1185 1186
	signalHandlers.reserve(signalHandlers.size() + 6);
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove",
1187
			OBSBasic::SourceRemoved, this);
P
Palana 已提交
1188
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate",
1189
			OBSBasic::SourceActivated, this);
P
Palana 已提交
1190
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate",
1191
			OBSBasic::SourceDeactivated, this);
P
Palana 已提交
1192
	signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename",
J
jp9000 已提交
1193
			OBSBasic::SourceRenamed, this);
1194 1195
}

J
jp9000 已提交
1196 1197
void OBSBasic::InitPrimitives()
{
P
Palana 已提交
1198 1199
	ProfileScope("OBSBasic::InitPrimitives");

J
jp9000 已提交
1200
	obs_enter_graphics();
J
jp9000 已提交
1201

1202
	gs_render_start(true);
J
jp9000 已提交
1203 1204 1205 1206 1207
	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);
1208
	box = gs_render_save();
J
jp9000 已提交
1209

1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
	gs_render_start(true);
	gs_vertex2f(0.0f, 0.0f);
	gs_vertex2f(0.0f, 1.0f);
	boxLeft = gs_render_save();

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

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

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

1230
	gs_render_start(true);
J
jp9000 已提交
1231 1232 1233 1234
	for (int i = 0; i <= 360; i += (360/20)) {
		float pos = RAD(float(i));
		gs_vertex2f(cosf(pos), sinf(pos));
	}
1235
	circle = gs_render_save();
J
jp9000 已提交
1236

J
jp9000 已提交
1237
	obs_leave_graphics();
J
jp9000 已提交
1238 1239
}

J
jp9000 已提交
1240 1241 1242 1243 1244 1245 1246 1247
void OBSBasic::ReplayBufferClicked()
{
	if (outputHandler->ReplayBufferActive())
		StopReplayBuffer();
	else
		StartReplayBuffer();
};

J
jp9000 已提交
1248 1249
void OBSBasic::ResetOutputs()
{
P
Palana 已提交
1250 1251
	ProfileScope("OBSBasic::ResetOutputs");

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

J
jp9000 已提交
1255 1256
	if (!outputHandler || !outputHandler->Active()) {
		outputHandler.reset();
J
jp9000 已提交
1257 1258 1259
		outputHandler.reset(advOut ?
			CreateAdvancedOutputHandler(this) :
			CreateSimpleOutputHandler(this));
1260

J
jp9000 已提交
1261 1262 1263 1264 1265 1266
		delete replayBufferButton;

		if (outputHandler->replayBuffer) {
			replayBufferButton = new QPushButton(
					QTStr("Basic.Main.StartReplayBuffer"),
					this);
1267
			connect(replayBufferButton.data(),
J
jp9000 已提交
1268 1269 1270 1271
					&QPushButton::clicked,
					this,
					&OBSBasic::ReplayBufferClicked);

1272
			replayBufferButton->setProperty("themeID", "replayBufferButton");
J
jp9000 已提交
1273 1274
			ui->buttonsVLayout->insertWidget(2, replayBufferButton);
		}
1275

J
jp9000 已提交
1276 1277 1278
		if (sysTrayReplayBuffer)
			sysTrayReplayBuffer->setEnabled(
					!!outputHandler->replayBuffer);
J
jp9000 已提交
1279 1280 1281 1282 1283
	} else {
		outputHandler->Update();
	}
}

1284 1285 1286 1287
#define STARTUP_SEPARATOR \
	"==== Startup complete ==============================================="
#define SHUTDOWN_SEPARATOR \
	"==== Shutting down =================================================="
1288

J
jp9000 已提交
1289 1290
extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main);

1291 1292 1293 1294 1295
#define UNSUPPORTED_ERROR \
	"Failed to initialize video:\n\nRequired graphics API functionality " \
	"not found.  Your GPU may not be supported."

#define UNKNOWN_ERROR \
1296 1297
	"Failed to initialize video.  Your GPU may not be supported, " \
	"or your graphics drivers may need to be updated."
1298

1299 1300
void OBSBasic::OBSInit()
{
P
Palana 已提交
1301 1302
	ProfileScope("OBSBasic::OBSInit");

J
jp9000 已提交
1303 1304
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
1305
	char savePath[512];
J
jp9000 已提交
1306 1307 1308 1309 1310 1311 1312 1313
	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);
1314
	if (ret <= 0)
J
jp9000 已提交
1315 1316 1317 1318 1319
		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";
1320

1321 1322
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
1323
	if (!ResetAudio())
1324 1325
		throw "Failed to initialize audio";

1326
	ret = ResetVideo();
1327 1328 1329 1330 1331

	switch (ret) {
	case OBS_VIDEO_MODULE_NOT_FOUND:
		throw "Failed to initialize video:  Graphics module not found";
	case OBS_VIDEO_NOT_SUPPORTED:
1332
		throw UNSUPPORTED_ERROR;
1333 1334 1335 1336
	case OBS_VIDEO_INVALID_PARAM:
		throw "Failed to initialize video:  Invalid parameters";
	default:
		if (ret != OBS_VIDEO_SUCCESS)
1337
			throw UNKNOWN_ERROR;
1338 1339
	}

1340
	/* load audio monitoring */
1341
#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
1342 1343 1344 1345 1346 1347
	const char *device_name = config_get_string(basicConfig, "Audio",
			"MonitoringDeviceName");
	const char *device_id = config_get_string(basicConfig, "Audio",
			"MonitoringDeviceId");

	obs_set_audio_monitoring_device(device_name, device_id);
1348 1349 1350

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

1353
	InitOBSCallbacks();
P
Palana 已提交
1354
	InitHotkeys();
1355

J
jp9000 已提交
1356 1357
	api = InitializeAPIInterface(this);

1358
	AddExtraModulePaths();
1359
	blog(LOG_INFO, "---------------------------------");
J
jp9000 已提交
1360
	obs_load_all_modules();
1361 1362
	blog(LOG_INFO, "---------------------------------");
	obs_log_loaded_modules();
1363 1364
	blog(LOG_INFO, "---------------------------------");
	obs_post_load_modules();
J
jp9000 已提交
1365

1366
	blog(LOG_INFO, STARTUP_SEPARATOR);
1367

J
jp9000 已提交
1368
	ResetOutputs();
1369
	CreateHotkeys();
J
jp9000 已提交
1370

1371 1372 1373
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
1374 1375
	InitPrimitives();

1376 1377 1378 1379 1380 1381
	sceneDuplicationMode = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "SceneDuplicationMode");
	swapScenesMode = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "SwapScenesMode");
	editPropertiesMode = config_get_bool(App()->GlobalConfig(),
				"BasicWindow", "EditPropertiesMode");
C
cg2121 已提交
1382 1383 1384 1385 1386 1387 1388 1389

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

1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404
#define SET_VISIBILITY(name, control) \
	do { \
		if (config_has_user_value(App()->GlobalConfig(), \
					"BasicWindow", name)) { \
			bool visible = config_get_bool(App()->GlobalConfig(), \
					"BasicWindow", name); \
			ui->control->setChecked(visible); \
		} \
	} while (false)

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

P
Palana 已提交
1405 1406 1407 1408 1409 1410
	{
		ProfileScope("OBSBasic::Load");
		disableSaving--;
		Load(savePath);
		disableSaving++;
	}
1411

J
jp9000 已提交
1412
	TimedCheckForUpdates();
1413
	loaded = true;
J
jp9000 已提交
1414

1415
	previewEnabled = config_get_bool(App()->GlobalConfig(),
J
jp9000 已提交
1416
			"BasicWindow", "PreviewEnabled");
1417 1418 1419 1420 1421

	if (!previewEnabled && !IsPreviewProgramMode())
		QMetaObject::invokeMethod(this, "EnablePreviewDisplay",
				Qt::QueuedConnection,
				Q_ARG(bool, previewEnabled));
1422 1423 1424 1425 1426 1427 1428 1429 1430

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

J
jp9000 已提交
1432
	RefreshSceneCollections();
J
jp9000 已提交
1433
	RefreshProfiles();
J
jp9000 已提交
1434
	disableSaving--;
1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447

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

1448
#ifdef _WIN32
J
jp9000 已提交
1449
	SetWin32DropStyle(this);
1450 1451 1452 1453 1454
	show();
#endif

	bool alwaysOnTop = config_get_bool(App()->GlobalConfig(), "BasicWindow",
			"AlwaysOnTop");
1455
	if (alwaysOnTop || opt_always_on_top) {
1456 1457 1458 1459 1460
		SetAlwaysOnTop(this, true);
		ui->actionAlwaysOnTop->setChecked(true);
	}

#ifndef _WIN32
1461
	show();
1462
#endif
J
jp9000 已提交
1463

J
jp9000 已提交
1464 1465 1466 1467
	const char *dockStateStr = config_get_string(App()->GlobalConfig(),
			"BasicWindow", "DockState");
	if (!dockStateStr) {
		on_resetUI_triggered();
J
jp9000 已提交
1468
	} else {
J
jp9000 已提交
1469 1470 1471 1472
		QByteArray dockState =
			QByteArray::fromBase64(QByteArray(dockStateStr));
		if (!restoreState(dockState))
			on_resetUI_triggered();
J
jp9000 已提交
1473 1474
	}

J
jp9000 已提交
1475 1476 1477 1478 1479 1480 1481 1482 1483
	config_set_default_bool(App()->GlobalConfig(), "BasicWindow",
			"DocksLocked", true);

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

	SystemTray(true);
C
cg2121 已提交
1486 1487

	OpenSavedProjectors();
J
jp9000 已提交
1488

1489 1490 1491
	if (windowState().testFlag(Qt::WindowFullScreen))
		fullscreenInterface = true;

J
jp9000 已提交
1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
	bool has_last_version = config_has_user_value(App()->GlobalConfig(),
			"General", "LastVersion");
	bool first_run = config_get_bool(App()->GlobalConfig(), "General",
			"FirstRun");

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

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

		QMessageBox::StandardButton button =
1510
			OBSMessageBox::question(this, QTStr("Basic.AutoConfig"),
J
jp9000 已提交
1511 1512 1513 1514 1515 1516
					msg);

		if (button == QMessageBox::Yes) {
			on_autoConfigure_triggered();
		} else {
			msg = QTStr("Basic.FirstStartup.RunWizard.NoClicked");
1517
			OBSMessageBox::information(this,
J
jp9000 已提交
1518 1519 1520
					QTStr("Basic.AutoConfig"), msg);
		}
	}
1521 1522 1523

	if (config_get_bool(basicConfig, "General", "OpenStatsOnStartup"))
		on_stats_triggered();
1524 1525

	OBSBasicStats::InitializeValues();
1526 1527
}

P
Palana 已提交
1528 1529
void OBSBasic::InitHotkeys()
{
P
Palana 已提交
1530 1531
	ProfileScope("OBSBasic::InitHotkeys");

P
Palana 已提交
1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573
	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"),
1574
			Str("Push-to-mute"), Str("Push-to-talk"));
P
Palana 已提交
1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594

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

1595 1596
void OBSBasic::CreateHotkeys()
{
P
Palana 已提交
1597 1598
	ProfileScope("OBSBasic::CreateHotkeys");

1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614
	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 已提交
1615 1616 1617 1618 1619 1620 1621 1622 1623
	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);
	};

1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636
	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);
	};

1637
#define MAKE_CALLBACK(pred, method, log_action) \
1638 1639 1640 1641
	[](void *data, obs_hotkey_pair_id, obs_hotkey_t*, bool pressed) \
	{ \
		OBSBasic &basic = *static_cast<OBSBasic*>(data); \
		if (pred && pressed) { \
1642
			blog(LOG_INFO, log_action " due to hotkey"); \
1643 1644 1645 1646 1647 1648 1649 1650
			method(); \
			return true; \
		} \
		return false; \
	}

	streamingHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartStreaming",
J
jp9000 已提交
1651
			Str("Basic.Main.StartStreaming"),
1652
			"OBSBasic.StopStreaming",
J
jp9000 已提交
1653
			Str("Basic.Main.StopStreaming"),
1654
			MAKE_CALLBACK(!basic.outputHandler->StreamingActive(),
1655
				basic.StartStreaming, "Starting stream"),
1656
			MAKE_CALLBACK(basic.outputHandler->StreamingActive(),
1657
				basic.StopStreaming, "Stopping stream"),
1658 1659 1660 1661
			this, this);
	LoadHotkeyPair(streamingHotkeys,
			"OBSBasic.StartStreaming", "OBSBasic.StopStreaming");

J
jp9000 已提交
1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676
	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");

1677 1678
	recordingHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartRecording",
J
jp9000 已提交
1679
			Str("Basic.Main.StartRecording"),
1680
			"OBSBasic.StopRecording",
J
jp9000 已提交
1681
			Str("Basic.Main.StopRecording"),
1682
			MAKE_CALLBACK(!basic.outputHandler->RecordingActive(),
1683
				basic.StartRecording, "Starting recording"),
1684
			MAKE_CALLBACK(basic.outputHandler->RecordingActive(),
1685
				basic.StopRecording, "Stopping recording"),
1686 1687 1688
			this, this);
	LoadHotkeyPair(recordingHotkeys,
			"OBSBasic.StartRecording", "OBSBasic.StopRecording");
J
jp9000 已提交
1689 1690 1691 1692 1693 1694 1695

	replayBufHotkeys = obs_hotkey_pair_register_frontend(
			"OBSBasic.StartReplayBuffer",
			Str("Basic.Main.StartReplayBuffer"),
			"OBSBasic.StopReplayBuffer",
			Str("Basic.Main.StopReplayBuffer"),
			MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(),
1696
				basic.StartReplayBuffer, "Starting replay buffer"),
J
jp9000 已提交
1697
			MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(),
1698
				basic.StopReplayBuffer, "Stopping replay buffer"),
J
jp9000 已提交
1699 1700 1701
			this, this);
	LoadHotkeyPair(replayBufHotkeys,
			"OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer");
1702
#undef MAKE_CALLBACK
1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731

	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");
1732 1733
}

J
jp9000 已提交
1734 1735 1736 1737
void OBSBasic::ClearHotkeys()
{
	obs_hotkey_pair_unregister(streamingHotkeys);
	obs_hotkey_pair_unregister(recordingHotkeys);
J
jp9000 已提交
1738
	obs_hotkey_pair_unregister(replayBufHotkeys);
J
jp9000 已提交
1739
	obs_hotkey_unregister(forceStreamingStopHotkey);
1740 1741
	obs_hotkey_unregister(togglePreviewProgramHotkey);
	obs_hotkey_unregister(transitionHotkey);
J
jp9000 已提交
1742 1743
}

1744 1745
OBSBasic::~OBSBasic()
{
J
jp9000 已提交
1746 1747 1748
	if (updateCheckThread && updateCheckThread->isRunning())
		updateCheckThread->wait();

1749 1750
	delete programOptions;
	delete program;
J
jp9000 已提交
1751

J
jp9000 已提交
1752 1753 1754 1755 1756 1757
	/* 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 已提交
1758
	delete cpuUsageTimer;
1759 1760
	os_cpu_usage_info_destroy(cpuUsageInfo);

P
Palana 已提交
1761
	obs_hotkey_set_callback_routing_func(nullptr, nullptr);
J
jp9000 已提交
1762
	ClearHotkeys();
P
Palana 已提交
1763

1764
	service = nullptr;
J
jp9000 已提交
1765 1766
	outputHandler.reset();

J
John Bradley 已提交
1767 1768 1769
	if (interaction)
		delete interaction;

1770 1771 1772
	if (properties)
		delete properties;

J
jp9000 已提交
1773 1774 1775
	if (filters)
		delete filters;

1776 1777
	if (transformWindow)
		delete transformWindow;
1778

J
jp9000 已提交
1779 1780 1781
	if (advAudioWindow)
		delete advAudioWindow;

1782 1783 1784
	obs_display_remove_draw_callback(ui->preview->GetDisplay(),
			OBSBasic::RenderMain, this);

J
jp9000 已提交
1785
	obs_enter_graphics();
1786
	gs_vertexbuffer_destroy(box);
1787 1788 1789 1790
	gs_vertexbuffer_destroy(boxLeft);
	gs_vertexbuffer_destroy(boxTop);
	gs_vertexbuffer_destroy(boxRight);
	gs_vertexbuffer_destroy(boxBottom);
1791
	gs_vertexbuffer_destroy(circle);
J
jp9000 已提交
1792
	obs_leave_graphics();
J
jp9000 已提交
1793

1794 1795 1796 1797 1798 1799 1800 1801 1802
	/* 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 已提交
1803 1804
	config_set_int(App()->GlobalConfig(), "General", "LastVersion",
			LIBOBS_API_VER);
J
jp9000 已提交
1805

1806
	bool alwaysOnTop = IsAlwaysOnTop(this);
J
jp9000 已提交
1807

J
jp9000 已提交
1808 1809
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "PreviewEnabled",
			previewEnabled);
1810 1811
	config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
			alwaysOnTop);
1812 1813 1814 1815 1816 1817 1818 1819
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"SceneDuplicationMode", sceneDuplicationMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"SwapScenesMode", swapScenesMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"EditPropertiesMode", editPropertiesMode);
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"PreviewProgramMode", IsPreviewProgramMode());
J
jp9000 已提交
1820 1821
	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"DocksLocked", ui->lockUI->isChecked());
1822
	config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833

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

J
jp9000 已提交
1836 1837 1838 1839 1840 1841 1842 1843 1844
void OBSBasic::SaveProjectNow()
{
	if (disableSaving)
		return;

	projectChanged = true;
	SaveProjectDeferred();
}

J
jp9000 已提交
1845 1846
void OBSBasic::SaveProject()
{
J
jp9000 已提交
1847 1848 1849
	if (disableSaving)
		return;

J
jp9000 已提交
1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864
	projectChanged = true;
	QMetaObject::invokeMethod(this, "SaveProjectDeferred",
			Qt::QueuedConnection);
}

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

	if (!projectChanged)
		return;

	projectChanged = false;

J
jp9000 已提交
1865 1866
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollectionFile");
1867
	char savePath[512];
J
jp9000 已提交
1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879
	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);
1880 1881 1882
	if (ret <= 0)
		return;

J
jp9000 已提交
1883 1884 1885
	Save(savePath);
}

J
jp9000 已提交
1886
OBSScene OBSBasic::GetCurrentScene()
1887
{
J
jp9000 已提交
1888
	QListWidgetItem *item = ui->scenes->currentItem();
P
Palana 已提交
1889
	return item ? GetOBSRef<OBSScene>(item) : nullptr;
1890 1891
}

1892
OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
J
jp9000 已提交
1893
{
P
Palana 已提交
1894
	return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
J
jp9000 已提交
1895 1896
}

1897 1898
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
1899
	return GetSceneItem(GetTopSelectedSourceItem());
1900 1901
}

J
Joseph El-Khouri 已提交
1902 1903
void OBSBasic::UpdatePreviewScalingMenu()
{
1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917
	bool fixedScaling = ui->preview->IsFixedScaling();
	float scalingAmount = ui->preview->GetScalingAmount();
	if (!fixedScaling) {
		ui->actionScaleWindow->setChecked(true);
		ui->actionScaleCanvas->setChecked(false);
		ui->actionScaleOutput->setChecked(false);
		return;
	}

	obs_video_info ovi;
	obs_get_video_info(&ovi);

	ui->actionScaleWindow->setChecked(false);
	ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f);
J
Joseph El-Khouri 已提交
1918
	ui->actionScaleOutput->setChecked(
1919
			scalingAmount == float(ovi.output_width) / float(ovi.base_width));
J
Joseph El-Khouri 已提交
1920 1921
}

1922 1923
void OBSBasic::UpdateSources(OBSScene scene)
{
1924
	ClearListItems(ui->sources);
1925 1926

	obs_scene_enum_items(scene,
1927
			[] (obs_scene_t *scene, obs_sceneitem_t *item, void *p)
1928 1929
			{
				OBSBasic *window = static_cast<OBSBasic*>(p);
1930
				window->InsertSceneItem(item);
J
jp9000 已提交
1931 1932

				UNUSED_PARAMETER(scene);
1933 1934 1935 1936
				return true;
			}, this);
}

1937
void OBSBasic::InsertSceneItem(obs_sceneitem_t *item)
1938
{
1939
	QListWidgetItem *listItem = new QListWidgetItem();
P
Palana 已提交
1940
	SetOBSRef(listItem, OBSSceneItem(item));
1941 1942

	ui->sources->insertItem(0, listItem);
1943
	ui->sources->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
1944

1945
	SetupVisibilityItem(ui->sources, listItem, item);
1946 1947
}

1948
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
J
John Bradley 已提交
1949 1950 1951 1952 1953 1954 1955 1956 1957
{
	if (interaction)
		interaction->close();

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

1958
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
1959 1960 1961 1962 1963 1964 1965
{
	if (properties)
		properties->close();

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

J
jp9000 已提交
1968 1969 1970 1971 1972 1973 1974 1975 1976 1977
void OBSBasic::CreateFiltersWindow(obs_source_t *source)
{
	if (filters)
		filters->close();

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

1978 1979 1980
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
1981
{
1982
	const char *name  = obs_source_get_name(source);
1983
	obs_scene_t *scene = obs_scene_from_source(source);
J
jp9000 已提交
1984 1985

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

P
Palana 已提交
1989 1990 1991 1992 1993
	obs_hotkey_register_source(source, "OBSBasic.SelectScene",
			Str("Basic.Hotkeys.SelectScene"),
			[](void *data,
				obs_hotkey_id, obs_hotkey_t*, bool pressed)
	{
1994 1995 1996
		OBSBasic *main =
			reinterpret_cast<OBSBasic*>(App()->GetMainWindow());

P
Palana 已提交
1997 1998 1999
		auto potential_source = static_cast<obs_source_t*>(data);
		auto source = obs_source_get_ref(potential_source);
		if (source && pressed)
2000
			main->SetCurrentScene(source);
P
Palana 已提交
2001 2002 2003
		obs_source_release(source);
	}, static_cast<obs_source_t*>(source));

2004
	signal_handler_t *handler = obs_source_get_signal_handler(source);
2005

J
jp9000 已提交
2006 2007 2008
	SignalContainer<OBSScene> container;
	container.ref = scene;
	container.handlers.assign({
2009 2010 2011 2012 2013 2014 2015 2016 2017 2018
		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 已提交
2019
	});
2020 2021

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

2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040
	/* 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 已提交
2041
	SaveProject();
2042 2043 2044 2045 2046 2047

	if (!disableSaving) {
		obs_source_t *source = obs_scene_get_source(scene);
		blog(LOG_INFO, "User added scene '%s'",
				obs_source_get_name(source));
	}
2048 2049 2050

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
2051 2052
}

2053
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
2054
{
P
Palana 已提交
2055 2056 2057 2058
	obs_scene_t *scene = obs_scene_from_source(source);

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

P
Palana 已提交
2060 2061 2062 2063 2064
	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 已提交
2065

P
Palana 已提交
2066 2067 2068
		sel = item;
		break;
	}
J
jp9000 已提交
2069

J
jp9000 已提交
2070
	if (sel != nullptr) {
P
Palana 已提交
2071
		if (sel == ui->scenes->currentItem())
2072
			ClearListItems(ui->sources);
J
jp9000 已提交
2073
		delete sel;
J
jp9000 已提交
2074
	}
J
jp9000 已提交
2075 2076

	SaveProject();
2077 2078 2079 2080 2081

	if (!disableSaving) {
		blog(LOG_INFO, "User Removed scene '%s'",
				obs_source_get_name(source));
	}
2082 2083 2084

	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
2085 2086
}

2087
void OBSBasic::AddSceneItem(OBSSceneItem item)
2088
{
2089
	obs_scene_t  *scene  = obs_sceneitem_get_scene(item);
J
jp9000 已提交
2090

2091 2092
	if (GetCurrentScene() == scene)
		InsertSceneItem(item);
J
jp9000 已提交
2093

J
jp9000 已提交
2094
	SaveProject();
2095 2096 2097 2098 2099 2100 2101 2102 2103

	if (!disableSaving) {
		obs_source_t *sceneSource = obs_scene_get_source(scene);
		obs_source_t *itemSource = obs_sceneitem_get_source(item);
		blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'",
				obs_source_get_name(itemSource),
				obs_source_get_id(itemSource),
				obs_source_get_name(sceneSource));
	}
2104 2105
}

2106
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
2107
{
2108 2109
	for (int i = 0; i < ui->sources->count(); i++) {
		QListWidgetItem *listItem = ui->sources->item(i);
2110

2111 2112 2113
		if (GetOBSRef<OBSSceneItem>(listItem) == item) {
			DeleteListItem(ui->sources, listItem);
			break;
2114 2115
		}
	}
J
jp9000 已提交
2116

J
jp9000 已提交
2117
	SaveProject();
2118 2119 2120 2121 2122 2123 2124 2125 2126 2127

	if (!disableSaving) {
		obs_scene_t *scene = obs_sceneitem_get_scene(item);
		obs_source_t *sceneSource = obs_scene_get_source(scene);
		obs_source_t *itemSource = obs_sceneitem_get_source(item);
		blog(LOG_INFO, "User Removed source '%s' (%s) from scene '%s'",
				obs_source_get_name(itemSource),
				obs_source_get_id(itemSource),
				obs_source_get_name(sceneSource));
	}
2128 2129
}

2130
void OBSBasic::UpdateSceneSelection(OBSSource source)
2131 2132
{
	if (source) {
2133
		obs_scene_t *scene = obs_scene_from_source(source);
2134
		const char *name = obs_source_get_name(source);
J
jp9000 已提交
2135

2136 2137 2138
		if (!scene)
			return;

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

2142 2143 2144 2145 2146
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

J
jp9000 已提交
2147
			UpdateSources(scene);
2148
		}
J
jp9000 已提交
2149
	}
2150 2151
}

J
jp9000 已提交
2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164
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);
2165 2166 2167 2168 2169

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

C
cg2121 已提交
2171 2172 2173 2174 2175 2176 2177 2178
	std::string newText = newName.toUtf8().constData();
	std::string prevText = prevName.toUtf8().constData();

	for (size_t j = 0; j < projectorArray.size(); j++) {
		if (projectorArray.at(j) == prevText)
			projectorArray.at(j) = newText;
	}

J
jp9000 已提交
2179
	SaveProject();
J
jp9000 已提交
2180 2181
}

2182 2183
void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
{
J
jp9000 已提交
2184 2185
	SignalBlocker sourcesSignalBlocker(ui->sources);

2186
	if (scene != GetCurrentScene() || ignoreSelectionUpdate)
2187 2188 2189 2190
		return;

	for (int i = 0; i < ui->sources->count(); i++) {
		QListWidgetItem *witem = ui->sources->item(i);
P
Palana 已提交
2191 2192
		QVariant data =
			witem->data(static_cast<int>(QtDataRole::OBSRef));
2193 2194 2195 2196 2197 2198
		if (!data.canConvert<OBSSceneItem>())
			continue;

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

J
jp9000 已提交
2199
		witem->setSelected(select);
2200 2201 2202 2203
		break;
	}
}

2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219
static inline bool SourceMixerHidden(obs_source_t *source)
{
	obs_data_t *priv_settings = obs_source_get_private_settings(source);
	bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden");
	obs_data_release(priv_settings);

	return hidden;
}

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

2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237
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);
}

2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287
void OBSBasic::HideAudioControl()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	VolControl *vol = action->property("volControl").value<VolControl*>();
	obs_source_t *source = vol->GetSource();

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

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

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

	using UnhideAudioMixer_t = decltype(UnhideAudioMixer);

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

	obs_enum_sources(PreEnum, &UnhideAudioMixer);
}

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

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

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

2292 2293 2294 2295 2296
	/* ------------------- */

	QAction hideAction(QTStr("Hide"), this);
	QAction unhideAllAction(QTStr("UnhideAll"), this);

2297 2298
	QAction filtersAction(QTStr("Filters"), this);
	QAction propertiesAction(QTStr("Properties"), this);
2299
	QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this);
2300

2301 2302 2303 2304 2305 2306 2307 2308 2309
	/* ------------------- */

	connect(&hideAction, &QAction::triggered,
			this, &OBSBasic::HideAudioControl,
			Qt::DirectConnection);
	connect(&unhideAllAction, &QAction::triggered,
			this, &OBSBasic::UnhideAllAudioControls,
			Qt::DirectConnection);

2310 2311 2312 2313 2314 2315
	connect(&filtersAction, &QAction::triggered,
			this, &OBSBasic::GetAudioSourceFilters,
			Qt::DirectConnection);
	connect(&propertiesAction, &QAction::triggered,
			this, &OBSBasic::GetAudioSourceProperties,
			Qt::DirectConnection);
2316 2317 2318
	connect(&advPropAction, &QAction::triggered,
			this, &OBSBasic::on_actionAdvAudioProperties_triggered,
			Qt::DirectConnection);
2319

2320 2321 2322 2323 2324
	/* ------------------- */

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

2325 2326 2327 2328 2329
	filtersAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));
	propertiesAction.setProperty("volControl",
			QVariant::fromValue<VolControl*>(vol));

2330 2331
	/* ------------------- */

2332
	QMenu popup(this);
2333 2334 2335
	popup.addAction(&unhideAllAction);
	popup.addAction(&hideAction);
	popup.addSeparator();
2336 2337
	popup.addAction(&filtersAction);
	popup.addAction(&propertiesAction);
2338
	popup.addAction(&advPropAction);
2339 2340 2341
	popup.exec(QCursor::pos());
}

2342 2343 2344
void OBSBasic::on_mixerScrollArea_customContextMenuRequested()
{
	QAction unhideAllAction(QTStr("UnhideAll"), this);
S
SuslikV 已提交
2345 2346 2347 2348 2349

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

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

2350 2351 2352 2353
	connect(&unhideAllAction, &QAction::triggered,
			this, &OBSBasic::UnhideAllAudioControls,
			Qt::DirectConnection);

S
SuslikV 已提交
2354 2355 2356 2357 2358 2359
	connect(&advPropAction, &QAction::triggered,
			this, &OBSBasic::on_actionAdvAudioProperties_triggered,
			Qt::DirectConnection);

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

2360 2361
	QMenu popup(this);
	popup.addAction(&unhideAllAction);
S
SuslikV 已提交
2362 2363
	popup.addSeparator();
	popup.addAction(&advPropAction);
2364 2365 2366
	popup.exec(QCursor::pos());
}

2367 2368
void OBSBasic::ActivateAudioSource(OBSSource source)
{
2369 2370 2371
	if (SourceMixerHidden(source))
		return;

2372 2373
	VolControl *vol = new VolControl(source, true);

2374 2375 2376 2377
	vol->setContextMenuPolicy(Qt::CustomContextMenu);

	connect(vol, &QWidget::customContextMenuRequested,
			this, &OBSBasic::VolControlContextMenu);
2378 2379
	connect(vol, &VolControl::ConfigClicked,
			this, &OBSBasic::VolControlContextMenu);
2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395

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

2396
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
J
jp9000 已提交
2397
{
2398 2399 2400 2401
	if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE) {
		int count = ui->scenes->count();

		if (count == 1) {
2402
			OBSMessageBox::information(this,
2403 2404 2405 2406
						QTStr("FinalScene.Title"),
						QTStr("FinalScene.Text"));
			return false;
		}
2407 2408
	}

2409
	const char *name  = obs_source_get_name(source);
2410 2411 2412

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

2414
	QMessageBox remove_source(this);
2415 2416 2417
	remove_source.setText(text);
	QAbstractButton *Yes = remove_source.addButton(QTStr("Yes"),
			QMessageBox::YesRole);
J
Jkoan 已提交
2418 2419 2420 2421 2422 2423
	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();
2424
}
J
jp9000 已提交
2425

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

P
Palana 已提交
2428 2429 2430 2431 2432
#ifdef UPDATE_SPARKLE
void init_sparkle_updater(bool update_to_undeployed);
void trigger_sparkle_update();
#endif

J
jp9000 已提交
2433 2434
void OBSBasic::TimedCheckForUpdates()
{
J
jp9000 已提交
2435 2436 2437 2438
	if (!config_get_bool(App()->GlobalConfig(), "General",
				"EnableAutoUpdates"))
		return;

P
Palana 已提交
2439 2440 2441
#ifdef UPDATE_SPARKLE
	init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
				"UpdateToUndeployed"));
J
jp9000 已提交
2442
#elif ENABLE_WIN_UPDATER
J
jp9000 已提交
2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457
	long long lastUpdate = config_get_int(App()->GlobalConfig(), "General",
			"LastUpdateCheck");
	uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
			"LastVersion");

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

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

	if (secs > UPDATE_CHECK_INTERVAL)
J
jp9000 已提交
2458
		CheckForUpdates(false);
P
Palana 已提交
2459
#endif
J
jp9000 已提交
2460 2461
}

J
jp9000 已提交
2462
void OBSBasic::CheckForUpdates(bool manualUpdate)
J
jp9000 已提交
2463
{
P
Palana 已提交
2464 2465
#ifdef UPDATE_SPARKLE
	trigger_sparkle_update();
J
jp9000 已提交
2466
#elif ENABLE_WIN_UPDATER
J
jp9000 已提交
2467 2468
	ui->actionCheckForUpdates->setEnabled(false);

J
jp9000 已提交
2469 2470
	if (updateCheckThread && updateCheckThread->isRunning())
		return;
2471

J
jp9000 已提交
2472
	updateCheckThread = new AutoUpdateThread(manualUpdate);
2473
	updateCheckThread->start();
P
Palana 已提交
2474
#endif
2475 2476

	UNUSED_PARAMETER(manualUpdate);
J
jp9000 已提交
2477 2478
}

J
jp9000 已提交
2479
void OBSBasic::updateCheckFinished()
J
jp9000 已提交
2480 2481 2482 2483
{
	ui->actionCheckForUpdates->setEnabled(true);
}

2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513
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()) {
2514
			OBSMessageBox::information(this,
2515 2516 2517 2518 2519 2520 2521
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
			continue;
		}

		obs_source_t *source = obs_get_source_by_name(name.c_str());
		if (source) {
2522
			OBSMessageBox::information(this,
2523 2524 2525 2526 2527 2528 2529 2530
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));

			obs_source_release(source);
			continue;
		}

		obs_scene_t *scene = obs_scene_duplicate(curScene,
2531
				name.c_str(), OBS_SCENE_DUP_REFS);
2532
		source = obs_scene_get_source(scene);
2533 2534
		AddScene(source);
		SetCurrentScene(source, true);
2535
		obs_scene_release(scene);
J
jp9000 已提交
2536

2537
		break;
2538 2539 2540
	}
}

2541 2542 2543 2544
void OBSBasic::RemoveSelectedScene()
{
	OBSScene scene = GetCurrentScene();
	if (scene) {
2545
		obs_source_t *source = obs_scene_get_source(scene);
J
jp9000 已提交
2546
		if (QueryRemoveSource(source)) {
2547
			obs_source_remove(source);
J
jp9000 已提交
2548 2549 2550 2551

			if (api)
				api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
		}
2552 2553 2554 2555 2556 2557 2558
	}
}

void OBSBasic::RemoveSelectedSceneItem()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (item) {
2559
		obs_source_t *source = obs_sceneitem_get_source(item);
2560
		if (QueryRemoveSource(source))
J
jp9000 已提交
2561 2562 2563 2564
			obs_sceneitem_remove(item);
	}
}

2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578
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 已提交
2579
		OBSSceneItem sceneItem = GetOBSRef<OBSSceneItem>(listItem);
2580 2581 2582 2583 2584

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

2585
				listItem = TakeListItem(ui->sources, i);
2586 2587 2588
				if (listItem)  {
					ui->sources->insertItem(idx_inv,
							listItem);
2589 2590 2591
					SetupVisibilityItem(ui->sources,
							listItem, item);

2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606
					if (sel)
						ui->sources->setCurrentRow(
								idx_inv);
				}
			}

			break;
		}
	}
}

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

2607
	if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619
		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 已提交
2620 2621

	SaveProject();
2622 2623
}

2624 2625
/* OBS Callbacks */

2626 2627 2628 2629 2630 2631 2632 2633 2634 2635
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)));
}

2636
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
2637 2638 2639
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

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

2642 2643
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
2644 2645
}

2646
void OBSBasic::SceneItemRemoved(void *data, calldata_t *params)
2647
{
2648
	OBSBasic *window = static_cast<OBSBasic*>(data);
2649

2650
	obs_sceneitem_t *item = (obs_sceneitem_t*)calldata_ptr(params, "item");
2651

2652 2653
	QMetaObject::invokeMethod(window, "RemoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
2654 2655
}

2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679
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));
}

2680
void OBSBasic::SourceLoaded(void *data, obs_source_t *source)
2681
{
J
jp9000 已提交
2682
	OBSBasic *window = static_cast<OBSBasic*>(data);
2683

2684
	if (obs_scene_from_source(source) != NULL)
J
jp9000 已提交
2685
		QMetaObject::invokeMethod(window,
2686 2687
				"AddScene",
				Q_ARG(OBSSource, OBSSource(source)));
2688 2689
}

2690
void OBSBasic::SourceRemoved(void *data, calldata_t *params)
2691
{
2692
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2693

2694
	if (obs_scene_from_source(source) != NULL)
2695 2696 2697
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
2698 2699
}

2700
void OBSBasic::SourceActivated(void *data, calldata_t *params)
2701
{
2702
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2703 2704 2705 2706 2707 2708 2709 2710
	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)));
}

2711
void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
2712
{
2713
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
2714 2715 2716 2717 2718 2719 2720 2721
	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)));
}

2722
void OBSBasic::SourceRenamed(void *data, calldata_t *params)
J
jp9000 已提交
2723 2724 2725 2726 2727 2728 2729 2730
{
	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)));
2731 2732

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

2735 2736 2737 2738 2739
void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

2740
	gs_effect_t    *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
2741 2742
	gs_eparam_t    *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech  = gs_effect_get_technique(solid, "Solid");
2743 2744 2745

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
2746
	gs_effect_set_vec4(color, &colorVal);
2747

2748 2749
	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
2750 2751 2752 2753 2754 2755 2756 2757
	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();
2758 2759
	gs_technique_end_pass(tech);
	gs_technique_end(tech);
2760 2761 2762 2763

	gs_load_vertexbuffer(nullptr);
}

2764 2765
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
2766
	OBSBasic *window = static_cast<OBSBasic*>(data);
2767 2768 2769 2770
	obs_video_info ovi;

	obs_get_video_info(&ovi);

J
jp9000 已提交
2771 2772
	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));
2773 2774 2775

	gs_viewport_push();
	gs_projection_push();
2776 2777 2778

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

2779 2780
	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
			-100.0f, 100.0f);
2781
	gs_set_viewport(window->previewX, window->previewY,
J
jp9000 已提交
2782
			window->previewCX, window->previewCY);
2783

2784 2785
	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

2786 2787 2788 2789 2790 2791 2792 2793
	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();
	}
2794
	gs_load_vertexbuffer(nullptr);
2795

2796 2797
	/* --------------------------------------- */

2798 2799 2800
	QSize previewSize = GetPixelSize(window->ui->preview);
	float right  = float(previewSize.width())  - window->previewX;
	float bottom = float(previewSize.height()) - window->previewY;
2801 2802 2803 2804

	gs_ortho(-window->previewX, right,
	         -window->previewY, bottom,
	         -100.0f, 100.0f);
2805
	gs_reset_viewport();
J
jp9000 已提交
2806 2807 2808

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

2809 2810
	/* --------------------------------------- */

2811 2812
	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
2813 2814 2815

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
2816 2817
}

2818 2819
/* Main class functions */

2820
obs_service_t *OBSBasic::GetService()
2821
{
2822
	if (!service) {
2823 2824
		service = obs_service_create("rtmp_common", NULL, NULL,
				nullptr);
2825 2826
		obs_service_release(service);
	}
2827 2828 2829
	return service;
}

2830
void OBSBasic::SetService(obs_service_t *newService)
2831
{
2832
	if (newService)
2833 2834 2835
		service = newService;
}

2836
bool OBSBasic::StreamingActive() const
2837 2838 2839 2840 2841 2842
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

2843 2844 2845 2846 2847 2848 2849
bool OBSBasic::Active() const
{
	if (!outputHandler)
		return false;
	return outputHandler->Active();
}

2850 2851 2852 2853 2854 2855
#ifdef _WIN32
#define IS_WIN32 1
#else
#define IS_WIN32 0
#endif

2856 2857
static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
2858
	return obs_reset_video(ovi);
2859 2860
}

2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873
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;
}

2874 2875 2876 2877 2878 2879
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 已提交
2880 2881
	else if (astrcmpi(name, "I444") == 0)
		return VIDEO_FORMAT_I444;
2882 2883 2884 2885 2886 2887 2888 2889 2890
#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 已提交
2891
		return VIDEO_FORMAT_RGBA;
2892 2893
}

2894
int OBSBasic::ResetVideo()
J
jp9000 已提交
2895
{
2896 2897 2898
	if (outputHandler && outputHandler->Active())
		return OBS_VIDEO_CURRENTLY_ACTIVE;

P
Palana 已提交
2899 2900
	ProfileScope("OBSBasic::ResetVideo");

J
jp9000 已提交
2901
	struct obs_video_info ovi;
2902
	int ret;
J
jp9000 已提交
2903

2904
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
2905

2906 2907 2908 2909 2910 2911 2912
	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 已提交
2913
	ovi.graphics_module = App()->GetRenderModule();
2914
	ovi.base_width     = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2915
			"Video", "BaseCX");
2916
	ovi.base_height    = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2917
			"Video", "BaseCY");
2918
	ovi.output_width   = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2919
			"Video", "OutputCX");
2920
	ovi.output_height  = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
2921
			"Video", "OutputCY");
2922 2923 2924 2925 2926
	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;
2927 2928
	ovi.adapter        = config_get_uint(App()->GlobalConfig(),
			"Video", "AdapterIdx");
J
jp9000 已提交
2929
	ovi.gpu_conversion = true;
2930
	ovi.scale_type     = GetScaleType(basicConfig);
2931

2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947
	if (ovi.base_width == 0 || ovi.base_height == 0) {
		ovi.base_width = 1920;
		ovi.base_height = 1080;
		config_set_uint(basicConfig, "Video", "BaseCX", 1920);
		config_set_uint(basicConfig, "Video", "BaseCY", 1080);
	}

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

2948
	ret = AttemptToResetVideo(&ovi);
2949
	if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
2950 2951 2952 2953 2954 2955
		if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
			blog(LOG_WARNING, "Tried to reset when "
			                  "already active");
			return ret;
		}

2956
		/* Try OpenGL if DirectX fails on windows */
2957
		if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
2958 2959 2960 2961
			blog(LOG_WARNING, "Failed to initialize obs video (%d) "
					  "with graphics_module='%s', retrying "
					  "with graphics_module='%s'",
					  ret, ovi.graphics_module,
2962 2963
					  DL_OPENGL);
			ovi.graphics_module = DL_OPENGL;
2964 2965
			ret = AttemptToResetVideo(&ovi);
		}
2966 2967
	} else if (ret == OBS_VIDEO_SUCCESS) {
		ResizePreview(ovi.base_width, ovi.base_height);
2968 2969
		if (program)
			ResizeProgram(ovi.base_width, ovi.base_height);
2970 2971
	}

2972 2973
	if (ret == OBS_VIDEO_SUCCESS)
		OBSBasicStats::InitializeValues();
2974

2975
	return ret;
J
jp9000 已提交
2976
}
J
jp9000 已提交
2977

2978
bool OBSBasic::ResetAudio()
J
jp9000 已提交
2979
{
P
Palana 已提交
2980 2981
	ProfileScope("OBSBasic::ResetAudio");

2982
	struct obs_audio_info ai;
2983
	ai.samples_per_sec = config_get_uint(basicConfig, "Audio",
2984 2985
			"SampleRate");

2986
	const char *channelSetupStr = config_get_string(basicConfig,
2987 2988 2989 2990 2991 2992 2993
			"Audio", "ChannelSetup");

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

J
jp9000 已提交
2994
	return obs_reset_audio(&ai);
J
jp9000 已提交
2995 2996
}

2997
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
2998
		const char *deviceDesc, int channel)
J
jp9000 已提交
2999
{
3000
	bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
3001 3002
	obs_source_t *source;
	obs_data_t *settings;
J
jp9000 已提交
3003 3004 3005

	source = obs_get_output_source(channel);
	if (source) {
3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018
		if (disable) {
			obs_set_output_source(channel, nullptr);
		} else {
			settings = obs_source_get_settings(source);
			const char *oldId = obs_data_get_string(settings,
					"device_id");
			if (strcmp(oldId, deviceId) != 0) {
				obs_data_set_string(settings, "device_id",
						deviceId);
				obs_source_update(source, settings);
			}
			obs_data_release(settings);
		}
J
jp9000 已提交
3019 3020 3021

		obs_source_release(source);

3022 3023
	} else if (!disable) {
		settings = obs_data_create();
J
jp9000 已提交
3024
		obs_data_set_string(settings, "device_id", deviceId);
3025 3026
		source = obs_source_create(sourceId, deviceDesc, settings,
				nullptr);
J
jp9000 已提交
3027 3028 3029 3030 3031 3032 3033
		obs_data_release(settings);

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

J
jp9000 已提交
3034
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
3035
{
3036
	QSize  targetSize;
3037
	bool isFixedScaling;
J
Joseph El-Khouri 已提交
3038
	obs_video_info ovi;
J
jp9000 已提交
3039

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

3043
	isFixedScaling = ui->preview->IsFixedScaling();
J
Joseph El-Khouri 已提交
3044 3045
	obs_get_video_info(&ovi);

3046 3047
	if (isFixedScaling) {
		previewScale = ui->preview->GetScalingAmount();
J
Joseph El-Khouri 已提交
3048 3049 3050 3051
		GetCenterPosFromFixedScale(int(cx), int(cy),
				targetSize.width() - PREVIEW_EDGE_SIZE * 2,
				targetSize.height() - PREVIEW_EDGE_SIZE * 2,
				previewX, previewY, previewScale);
3052 3053
		previewX += ui->preview->GetScrollX();
		previewY += ui->preview->GetScrollY();
J
Joseph El-Khouri 已提交
3054 3055 3056 3057 3058 3059 3060

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

3062 3063
	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);
J
jp9000 已提交
3064 3065
}

3066 3067 3068 3069 3070 3071 3072 3073 3074
void OBSBasic::CloseDialogs()
{
	QList<QDialog*> childDialogs = this->findChildren<QDialog *>();
	if (!childDialogs.isEmpty()) {
		for (int i = 0; i < childDialogs.size(); ++i) {
			childDialogs.at(i)->close();
		}
	}

C
cg2121 已提交
3075 3076 3077 3078
	for (QPointer<QWidget> &projector : windowProjectors) {
		delete projector;
		projector.clear();
	}
3079 3080 3081 3082
	for (QPointer<QWidget> &projector : projectors) {
		delete projector;
		projector.clear();
	}
J
jp9000 已提交
3083

3084
	if (!stats.isNull()) stats->close(); //call close to save Stats geometry
3085 3086
}

3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109
void OBSBasic::EnumDialogs()
{
	visDialogs.clear();
	modalDialogs.clear();
	visMsgBoxes.clear();

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

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

3110 3111
void OBSBasic::ClearSceneData()
{
J
jp9000 已提交
3112 3113
	disableSaving++;

3114 3115 3116 3117 3118
	CloseDialogs();

	ClearVolumeControls();
	ClearListItems(ui->scenes);
	ClearListItems(ui->sources);
3119 3120
	ClearQuickTransitions();
	ui->transitions->clear();
3121 3122 3123 3124 3125 3126 3127

	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);
3128 3129 3130
	lastScene = nullptr;
	swapScene = nullptr;
	programScene = nullptr;
3131 3132 3133 3134 3135 3136 3137 3138 3139 3140

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

	obs_enum_sources(cb, nullptr);

J
jp9000 已提交
3141
	disableSaving--;
3142 3143 3144

	blog(LOG_INFO, "All scene data cleared");
	blog(LOG_INFO, "------------------------------------------------");
3145 3146
}

J
jp9000 已提交
3147
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
3148
{
3149 3150 3151 3152 3153
	if (isVisible())
		config_set_string(App()->GlobalConfig(),
				"BasicWindow", "geometry",
				saveGeometry().toBase64().constData());

J
jp9000 已提交
3154 3155 3156 3157
	config_set_string(App()->GlobalConfig(),
			"BasicWindow", "DockState",
			saveState().toBase64().constData());

3158
	if (outputHandler && outputHandler->Active()) {
C
cg2121 已提交
3159 3160
		SetShowing(true);

3161
		QMessageBox::StandardButton button = OBSMessageBox::question(
3162 3163 3164 3165 3166 3167 3168 3169 3170
				this, QTStr("ConfirmExit.Title"),
				QTStr("ConfirmExit.Text"));

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

3171 3172 3173 3174
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

3175 3176
	blog(LOG_INFO, SHUTDOWN_SEPARATOR);

3177 3178 3179 3180 3181
	if (updateCheckThread)
		updateCheckThread->wait();
	if (logUploadThread)
		logUploadThread->wait();

P
Palana 已提交
3182 3183
	signalHandlers.clear();

J
jp9000 已提交
3184
	SaveProjectNow();
J
jp9000 已提交
3185 3186 3187 3188

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

J
jp9000 已提交
3189
	disableSaving++;
J
jp9000 已提交
3190

3191 3192 3193
	/* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
	 * sources, etc) so that all references are released before shutdown */
	ClearSceneData();
3194 3195

	App()->quit();
3196 3197
}

J
jp9000 已提交
3198
void OBSBasic::changeEvent(QEvent *event)
3199
{
3200 3201
	if (event->type() == QEvent::WindowStateChange &&
	    isMinimized() &&
3202
	    trayIcon &&
3203 3204 3205 3206 3207
	    trayIcon->isVisible() &&
	    sysTrayMinimizeToTray()) {

		ToggleShowHide();
	}
3208 3209
}

3210 3211
void OBSBasic::on_actionShow_Recordings_triggered()
{
3212 3213 3214 3215
	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");
3216 3217 3218
	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

P
Palana 已提交
3219 3220
void OBSBasic::on_actionRemux_triggered()
{
3221 3222 3223 3224
	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 已提交
3225 3226 3227 3228
	OBSRemux remux(path, this);
	remux.exec();
}

P
Palana 已提交
3229 3230 3231 3232
void OBSBasic::on_action_Settings_triggered()
{
	OBSBasicSettings settings(this);
	settings.exec();
C
cg2121 已提交
3233
	SystemTray(false);
P
Palana 已提交
3234 3235
}

J
jp9000 已提交
3236 3237
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
3238 3239 3240 3241 3242
	if (advAudioWindow != nullptr) {
		advAudioWindow->raise();
		return;
	}

J
jp9000 已提交
3243 3244 3245
	advAudioWindow = new OBSBasicAdvAudio(this);
	advAudioWindow->show();
	advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
3246 3247 3248

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

3251 3252 3253 3254 3255
void OBSBasic::on_advAudioProps_clicked()
{
	on_actionAdvAudioProperties_triggered();
}

3256 3257 3258 3259 3260
void OBSBasic::on_advAudioProps_destroyed()
{
	advAudioWindow = nullptr;
}

3261 3262
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
3263
{
3264
	obs_source_t *source = NULL;
J
jp9000 已提交
3265

3266 3267 3268 3269
	if (sceneChanging)
		return;

	if (current) {
3270
		obs_scene_t *scene;
J
jp9000 已提交
3271

P
Palana 已提交
3272
		scene = GetOBSRef<OBSScene>(current);
3273
		source = obs_scene_get_source(scene);
3274 3275
	}

3276
	SetCurrentScene(source);
3277 3278

	UNUSED_PARAMETER(prev);
3279 3280
}

J
jp9000 已提交
3281 3282
void OBSBasic::EditSceneName()
{
3283 3284 3285 3286 3287 3288
	QListWidgetItem *item = ui->scenes->currentItem();
	Qt::ItemFlags flags   = item->flags();

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

J
jp9000 已提交
3291 3292 3293 3294
static void AddProjectorMenuMonitors(QMenu *parent, QObject *target,
		const char *slot)
{
	QAction *action;
3295 3296
	QList<QScreen*> screens = QGuiApplication::screens();
	for (int i = 0; i < screens.size(); i++) {
3297
		QRect screenGeometry = screens[i]->geometry();
J
jp9000 已提交
3298 3299 3300
		QString str = QString("%1 %2: %3x%4 @ %5,%6").
			arg(QTStr("Display"),
			    QString::number(i),
3301 3302 3303 3304
			    QString::number((int)screenGeometry.width()),
			    QString::number((int)screenGeometry.height()),
			    QString::number((int)screenGeometry.x()),
			    QString::number((int)screenGeometry.y()));
J
jp9000 已提交
3305 3306 3307 3308 3309 3310

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

J
jp9000 已提交
3311
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
3312
{
J
jp9000 已提交
3313
	QListWidgetItem *item = ui->scenes->itemAt(pos);
J
jp9000 已提交
3314
	QPointer<QMenu> sceneProjectorMenu;
J
jp9000 已提交
3315

3316
	QMenu popup(this);
J
jp9000 已提交
3317
	QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
J
jp9000 已提交
3318 3319 3320
	popup.addAction(QTStr("Add"),
			this, SLOT(on_actionAddScene_triggered()));

P
Palana 已提交
3321 3322
	if (item) {
		popup.addSeparator();
3323 3324
		popup.addAction(QTStr("Duplicate"),
				this, SLOT(DuplicateSelectedScene()));
P
Palana 已提交
3325 3326
		popup.addAction(QTStr("Rename"),
				this, SLOT(EditSceneName()));
J
jp9000 已提交
3327
		popup.addAction(QTStr("Remove"),
3328 3329
				this, SLOT(RemoveSelectedScene()),
				DeleteKeys.front());
J
jp9000 已提交
3330
		popup.addSeparator();
J
jp9000 已提交
3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343

		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 已提交
3344 3345 3346 3347
		sceneProjectorMenu = new QMenu(QTStr("SceneProjector"));
		AddProjectorMenuMonitors(sceneProjectorMenu, this,
				SLOT(OpenSceneProjector()));
		popup.addMenu(sceneProjectorMenu);
C
cg2121 已提交
3348 3349 3350 3351 3352 3353

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

		popup.addAction(sceneWindow);
J
jp9000 已提交
3354
		popup.addSeparator();
J
jp9000 已提交
3355 3356
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenSceneFilters()));
P
Palana 已提交
3357
	}
J
jp9000 已提交
3358 3359

	popup.exec(QCursor::pos());
3360 3361
}

J
jp9000 已提交
3362
void OBSBasic::on_actionAddScene_triggered()
3363
{
3364
	string name;
S
Socapex 已提交
3365
	QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
P
Palana 已提交
3366

3367
	int i = 2;
P
Palana 已提交
3368
	QString placeHolderText = format.arg(i);
3369
	obs_source_t *source = nullptr;
P
Palana 已提交
3370 3371
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
P
Palana 已提交
3372
		placeHolderText = format.arg(++i);
P
Palana 已提交
3373
	}
S
Socapex 已提交
3374

J
jp9000 已提交
3375
	bool accepted = NameDialog::AskForName(this,
3376 3377
			QTStr("Basic.Main.AddSceneDlg.Title"),
			QTStr("Basic.Main.AddSceneDlg.Text"),
S
Socapex 已提交
3378 3379
			name,
			placeHolderText);
3380

J
jp9000 已提交
3381
	if (accepted) {
J
jp9000 已提交
3382
		if (name.empty()) {
3383
			OBSMessageBox::information(this,
3384 3385
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
J
jp9000 已提交
3386 3387 3388 3389
			on_actionAddScene_triggered();
			return;
		}

3390
		obs_source_t *source = obs_get_source_by_name(name.c_str());
3391
		if (source) {
3392
			OBSMessageBox::information(this,
3393 3394
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));
3395 3396

			obs_source_release(source);
J
jp9000 已提交
3397
			on_actionAddScene_triggered();
3398 3399 3400
			return;
		}

3401
		obs_scene_t *scene = obs_scene_create(name.c_str());
3402
		source = obs_scene_get_source(scene);
3403
		AddScene(source);
3404
		SetCurrentScene(source);
3405
		obs_scene_release(scene);
3406
	}
3407 3408
}

J
jp9000 已提交
3409
void OBSBasic::on_actionRemoveScene_triggered()
3410
{
3411
	OBSScene     scene  = GetCurrentScene();
3412
	obs_source_t *source = obs_scene_get_source(scene);
3413 3414 3415

	if (source && QueryRemoveSource(source))
		obs_source_remove(source);
3416 3417
}

J
jp9000 已提交
3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437
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 已提交
3438
void OBSBasic::on_actionSceneUp_triggered()
3439
{
J
jp9000 已提交
3440
	ChangeSceneIndex(true, -1, 0);
3441 3442
}

J
jp9000 已提交
3443
void OBSBasic::on_actionSceneDown_triggered()
3444
{
J
jp9000 已提交
3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456
	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);
3457 3458
}

J
jp9000 已提交
3459
void OBSBasic::on_sources_itemSelectionChanged()
3460
{
J
jp9000 已提交
3461
	SignalBlocker sourcesSignalBlocker(ui->sources);
3462

J
jp9000 已提交
3463
	auto updateItemSelection = [&]()
3464 3465
	{
		ignoreSelectionUpdate = true;
J
jp9000 已提交
3466 3467 3468 3469 3470 3471 3472
		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());
		}
3473 3474
		ignoreSelectionUpdate = false;
	};
J
jp9000 已提交
3475
	using updateItemSelection_t = decltype(updateItemSelection);
3476 3477

	obs_scene_atomic_update(GetCurrentScene(),
J
jp9000 已提交
3478
			[](void *data, obs_scene_t *)
3479
	{
J
jp9000 已提交
3480 3481
		(*static_cast<updateItemSelection_t*>(data))();
	}, static_cast<void*>(&updateItemSelection));
3482 3483
}

J
jp9000 已提交
3484 3485
void OBSBasic::EditSceneItemName()
{
3486
	QListWidgetItem *item = GetTopSelectedSourceItem();
3487
	Qt::ItemFlags flags   = item->flags();
P
Palana 已提交
3488
	OBSSceneItem sceneItem= GetOBSRef<OBSSceneItem>(item);
3489 3490
	obs_source_t *source  = obs_sceneitem_get_source(sceneItem);
	const char *name      = obs_source_get_name(source);
3491

3492
	item->setText(QT_UTF8(name));
3493
	item->setFlags(flags | Qt::ItemIsEditable);
3494
	ui->sources->removeItemWidget(item);
3495 3496
	ui->sources->editItem(item);
	item->setFlags(flags);
J
jp9000 已提交
3497 3498
}

J
jp9000 已提交
3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 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
void OBSBasic::SetDeinterlacingMode()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	obs_deinterlace_mode mode =
		(obs_deinterlace_mode)action->property("mode").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();
	obs_source_t *source = obs_sceneitem_get_source(sceneItem);

	obs_source_set_deinterlace_mode(source, mode);
}

void OBSBasic::SetDeinterlacingOrder()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	obs_deinterlace_field_order order =
		(obs_deinterlace_field_order)action->property("order").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();
	obs_source_t *source = obs_sceneitem_get_source(sceneItem);

	obs_source_set_deinterlace_field_order(source, order);
}

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

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

	ADD_MODE("Disable",                OBS_DEINTERLACE_MODE_DISABLE);
	ADD_MODE("Deinterlacing.Discard",  OBS_DEINTERLACE_MODE_DISCARD);
	ADD_MODE("Deinterlacing.Retro",    OBS_DEINTERLACE_MODE_RETRO);
	ADD_MODE("Deinterlacing.Blend",    OBS_DEINTERLACE_MODE_BLEND);
	ADD_MODE("Deinterlacing.Blend2x",  OBS_DEINTERLACE_MODE_BLEND_2X);
	ADD_MODE("Deinterlacing.Linear",   OBS_DEINTERLACE_MODE_LINEAR);
	ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X);
	ADD_MODE("Deinterlacing.Yadif",    OBS_DEINTERLACE_MODE_YADIF);
	ADD_MODE("Deinterlacing.Yadif2x",  OBS_DEINTERLACE_MODE_YADIF_2X);
#undef ADD_MODE

	menu->addSeparator();

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

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

	return menu;
}

3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595
void OBSBasic::SetScaleFilter()
{
	QAction *action = reinterpret_cast<QAction*>(sender());
	obs_scale_type mode = (obs_scale_type)action->property("mode").toInt();
	OBSSceneItem sceneItem = GetCurrentSceneItem();

	obs_sceneitem_set_scale_filter(sceneItem, mode);
}

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

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

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

	return menu;
}

J
jp9000 已提交
3596
void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
3597
{
3598
	QMenu popup(this);
J
jp9000 已提交
3599 3600
	QPointer<QMenu> previewProjector;
	QPointer<QMenu> sourceProjector;
J
jp9000 已提交
3601 3602 3603 3604 3605 3606

	if (preview) {
		QAction *action = popup.addAction(
				QTStr("Basic.Main.PreviewConextMenu.Enable"),
				this, SLOT(TogglePreview()));
		action->setCheckable(true);
3607 3608
		action->setChecked(
				obs_display_enabled(ui->preview->GetDisplay()));
3609 3610
		if (IsPreviewProgramMode())
			action->setEnabled(false);
J
jp9000 已提交
3611

J
Joseph El-Khouri 已提交
3612 3613
		popup.addAction(ui->actionLockPreview);
		popup.addMenu(ui->scalingMenu);
J
jp9000 已提交
3614

J
jp9000 已提交
3615 3616 3617 3618 3619 3620
		previewProjector = new QMenu(QTStr("PreviewProjector"));
		AddProjectorMenuMonitors(previewProjector, this,
				SLOT(OpenPreviewProjector()));

		popup.addMenu(previewProjector);

C
cg2121 已提交
3621 3622 3623 3624 3625 3626
		QAction *previewWindow = popup.addAction(
				QTStr("PreviewWindow"),
				this, SLOT(OpenPreviewWindow()));

		popup.addAction(previewWindow);

J
jp9000 已提交
3627 3628 3629
		popup.addSeparator();
	}

J
jp9000 已提交
3630 3631 3632 3633
	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646
	ui->actionCopyFilters->setEnabled(false);

	popup.addSeparator();
	popup.addAction(ui->actionCopySource);
	popup.addAction(ui->actionPasteRef);
	popup.addAction(ui->actionPasteDup);
	popup.addSeparator();

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

J
jp9000 已提交
3647 3648 3649 3650
	if (item) {
		if (addSourceMenu)
			popup.addSeparator();

J
John Bradley 已提交
3651
		OBSSceneItem sceneItem = GetSceneItem(item);
3652
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
J
jp9000 已提交
3653 3654 3655
		uint32_t flags = obs_source_get_output_flags(source);
		bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) ==
			OBS_SOURCE_ASYNC_VIDEO;
3656 3657
		bool hasAudio = (flags & OBS_SOURCE_AUDIO) ==
			OBS_SOURCE_AUDIO;
J
John Bradley 已提交
3658 3659
		QAction *action;

J
jp9000 已提交
3660 3661
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
3662 3663
		popup.addAction(QTStr("Remove"), this,
				SLOT(on_actionRemoveSource_triggered()),
3664
				DeleteKeys.front());
J
jp9000 已提交
3665 3666
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
J
jp9000 已提交
3667
		popup.addMenu(ui->transformMenu);
J
jp9000 已提交
3668 3669 3670 3671 3672

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

C
cg2121 已提交
3673 3674 3675 3676 3677 3678
		QAction *sourceWindow = popup.addAction(
				QTStr("SourceWindow"),
				this, SLOT(OpenSourceWindow()));

		popup.addAction(sourceWindow);

J
jp9000 已提交
3679
		popup.addSeparator();
3680 3681 3682 3683 3684 3685 3686 3687 3688

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

J
jp9000 已提交
3689 3690 3691 3692
		if (isAsyncVideo) {
			popup.addMenu(AddDeinterlacingMenu(source));
			popup.addSeparator();
		}
3693 3694 3695 3696

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

J
jp9000 已提交
3697
		popup.addMenu(sourceProjector);
C
cg2121 已提交
3698
		popup.addAction(sourceWindow);
J
jp9000 已提交
3699
		popup.addSeparator();
J
John Bradley 已提交
3700 3701 3702 3703 3704 3705 3706

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

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

J
jp9000 已提交
3707 3708
		popup.addAction(QTStr("Filters"), this,
				SLOT(OpenFilters()));
J
jp9000 已提交
3709 3710
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));
3711 3712

		ui->actionCopyFilters->setEnabled(true);
J
jp9000 已提交
3713 3714 3715
	}

	popup.exec(QCursor::pos());
3716 3717
}

J
jp9000 已提交
3718 3719
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
3720 3721
	if (ui->scenes->count())
		CreateSourcePopupMenu(ui->sources->itemAt(pos), false);
J
jp9000 已提交
3722 3723
}

P
Palana 已提交
3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735
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 已提交
3736
void OBSBasic::AddSource(const char *id)
3737
{
3738 3739 3740
	if (id && *id) {
		OBSBasicSourceSelect sourceSelect(this, id);
		sourceSelect.exec();
3741 3742
		if (sourceSelect.newSource)
			CreatePropertiesWindow(sourceSelect.newSource);
3743
	}
3744 3745
}

3746
QMenu *OBSBasic::CreateAddSourcePopupMenu()
3747
{
3748
	const char *type;
J
jp9000 已提交
3749
	bool foundValues = false;
3750
	bool foundDeprecated = false;
J
jp9000 已提交
3751
	size_t idx = 0;
3752

3753
	QMenu *popup = new QMenu(QTStr("Add"), this);
3754
	QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup);
3755

J
jp9000 已提交
3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772
	auto getActionAfter = [] (QMenu *menu, const QString &name)
	{
		QList<QAction*> actions = menu->actions();

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

		return (QAction*)nullptr;
	};

	auto addSource = [this, getActionAfter] (QMenu *popup,
			const char *type, const char *name)
	{
		QString qname = QT_UTF8(name);
		QAction *popupItem = new QAction(qname, this);
J
jp9000 已提交
3773
		popupItem->setData(QT_UTF8(type));
3774 3775
		connect(popupItem, SIGNAL(triggered(bool)),
				this, SLOT(AddSourceFromAction()));
J
jp9000 已提交
3776 3777 3778

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

J
jp9000 已提交
3781 3782
	while (obs_enum_input_types(idx++, &type)) {
		const char *name = obs_source_get_display_name(type);
3783
		uint32_t caps = obs_get_source_output_flags(type);
J
jp9000 已提交
3784

3785 3786
		if ((caps & OBS_SOURCE_DEPRECATED) == 0) {
			addSource(popup, type, name);
3787 3788 3789
		} else {
			addSource(deprecated, type, name);
			foundDeprecated = true;
3790
		}
3791
		foundValues = true;
3792 3793
	}

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

3796 3797 3798 3799 3800
	if (!foundDeprecated) {
		delete deprecated;
		deprecated = nullptr;
	}

3801 3802 3803
	if (!foundValues) {
		delete popup;
		popup = nullptr;
3804 3805 3806

	} else if (foundDeprecated) {
		popup->addMenu(deprecated);
3807
	}
3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824

	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).
3825
		OBSMessageBox::information(this,
3826 3827 3828 3829 3830 3831 3832 3833
				QTStr("Basic.Main.AddSourceHelp.Title"),
				QTStr("Basic.Main.AddSourceHelp.Text"));
		return;
	}

	QPointer<QMenu> popup = CreateAddSourcePopupMenu();
	if (popup)
		popup->exec(pos);
3834 3835
}

J
jp9000 已提交
3836
void OBSBasic::on_actionAddSource_triggered()
3837
{
J
jp9000 已提交
3838
	AddSourcePopupMenu(QCursor::pos());
3839 3840
}

J
jp9000 已提交
3841
void OBSBasic::on_actionRemoveSource_triggered()
3842
{
3843
	vector<OBSSceneItem> items;
3844

3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 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
	auto func = [] (obs_scene_t *, obs_sceneitem_t *item, void *param)
	{
		vector<OBSSceneItem> &items =
			*reinterpret_cast<vector<OBSSceneItem>*>(param);
		if (obs_sceneitem_selected(item))
			items.emplace_back(item);
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, &items);

	if (!items.size())
		return;

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

		QMessageBox remove_items(this);
		remove_items.setText(text);
		QAbstractButton *Yes = remove_items.addButton(QTStr("Yes"),
				QMessageBox::YesRole);
		remove_items.addButton(QTStr("No"), QMessageBox::NoRole);
		remove_items.setIcon(QMessageBox::Question);
		remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
		remove_items.exec();

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

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

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

J
John Bradley 已提交
3890 3891 3892 3893 3894 3895 3896 3897 3898
void OBSBasic::on_actionInteract_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreateInteractionWindow(source);
}

J
jp9000 已提交
3899
void OBSBasic::on_actionSourceProperties_triggered()
3900
{
3901
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3902
	OBSSource source = obs_sceneitem_get_source(item);
3903

3904 3905
	if (source)
		CreatePropertiesWindow(source);
3906 3907
}

J
jp9000 已提交
3908
void OBSBasic::on_actionSourceUp_triggered()
3909
{
J
jp9000 已提交
3910
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3911
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
3912
}
J
jp9000 已提交
3913

J
jp9000 已提交
3914
void OBSBasic::on_actionSourceDown_triggered()
3915
{
J
jp9000 已提交
3916
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3917
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
3918 3919
}

J
jp9000 已提交
3920 3921 3922
void OBSBasic::on_actionMoveUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3923
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
J
jp9000 已提交
3924 3925 3926 3927 3928
}

void OBSBasic::on_actionMoveDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3929
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
J
jp9000 已提交
3930 3931 3932 3933 3934
}

void OBSBasic::on_actionMoveToTop_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3935
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_TOP);
J
jp9000 已提交
3936 3937 3938 3939 3940
}

void OBSBasic::on_actionMoveToBottom_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
3941
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_BOTTOM);
J
jp9000 已提交
3942 3943
}

3944
static BPtr<char> ReadLogFile(const char *log)
J
jp9000 已提交
3945
{
3946
	char logDir[512];
3947
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
3948
		return nullptr;
J
jp9000 已提交
3949 3950 3951 3952 3953

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

3954
	BPtr<char> file = os_quick_read_utf8_file(path.c_str());
J
jp9000 已提交
3955 3956 3957 3958 3959 3960 3961 3962
	if (!file)
		blog(LOG_WARNING, "Failed to read log file %s", path.c_str());

	return file;
}

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

3965
	if (!fileString)
J
jp9000 已提交
3966 3967
		return;

3968
	if (!*fileString)
J
jp9000 已提交
3969 3970 3971 3972
		return;

	ui->menuLogFiles->setEnabled(false);

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

3976 3977 3978
	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 已提交
3979

3980
	obs_data_set_string(content.get(), "content", fileString);
J
jp9000 已提交
3981

3982 3983 3984 3985 3986 3987 3988 3989 3990 3991
	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 已提交
3992
	if (!json) {
3993 3994 3995 3996
		blog(LOG_ERROR, "Failed to get JSON data for log upload");
		return;
	}

F
fryshorts 已提交
3997 3998 3999
	QBuffer *postData = new QBuffer();
	postData->setData(json, (int) strlen(json));

4000 4001 4002 4003
	if (logUploadThread) {
		logUploadThread->wait();
		delete logUploadThread;
	}
F
fryshorts 已提交
4004

4005 4006 4007 4008 4009 4010 4011
	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 已提交
4012 4013
}

P
Palana 已提交
4014 4015
void OBSBasic::on_actionShowLogs_triggered()
{
4016
	char logDir[512];
4017
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
4018 4019
		return;

P
Palana 已提交
4020 4021 4022 4023
	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
4024 4025 4026 4027 4028 4029 4030 4031 4032 4033
void OBSBasic::on_actionUploadCurrentLog_triggered()
{
	UploadLog(App()->GetCurrentLog());
}

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

4034 4035 4036
void OBSBasic::on_actionViewCurrentLog_triggered()
{
	char logDir[512];
4037
	if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049
		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 已提交
4050 4051
void OBSBasic::on_actionCheckForUpdates_triggered()
{
J
jp9000 已提交
4052
	CheckForUpdates(true);
J
jp9000 已提交
4053 4054
}

4055
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
J
jp9000 已提交
4056 4057 4058
{
	ui->menuLogFiles->setEnabled(true);

4059
	if (text.isEmpty()) {
4060
		OBSMessageBox::information(this,
J
jp9000 已提交
4061
				QTStr("LogReturnDialog.ErrorUploadingLog"),
4062
				error);
J
jp9000 已提交
4063 4064 4065
		return;
	}

4066
	obs_data_t *returnData = obs_data_create_from_json(QT_TO_UTF8(text));
F
fryshorts 已提交
4067
	QString logURL         = obs_data_get_string(returnData, "html_url");
J
jp9000 已提交
4068 4069 4070 4071 4072 4073
	obs_data_release(returnData);

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

J
jp9000 已提交
4074
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
4075
		obs_source_t *source, const string &name)
4076
{
4077 4078 4079 4080
	const char *prevName = obs_source_get_name(source);
	if (name == prevName)
		return;

4081
	obs_source_t    *foundSource = obs_get_source_by_name(name.c_str());
4082
	QListWidgetItem *listItem    = listWidget->currentItem();
4083

4084
	if (foundSource || name.empty()) {
4085
		listItem->setText(QT_UTF8(prevName));
4086

4087
		if (foundSource) {
4088
			OBSMessageBox::information(parent,
4089 4090 4091
				QTStr("NameExists.Title"),
				QTStr("NameExists.Text"));
		} else if (name.empty()) {
4092
			OBSMessageBox::information(parent,
4093 4094 4095 4096
				QTStr("NoNameEntered.Title"),
				QTStr("NoNameEntered.Text"));
		}

4097 4098 4099
		obs_source_release(foundSource);
	} else {
		listItem->setText(QT_UTF8(name.c_str()));
4100
		obs_source_set_name(source, name.c_str());
4101 4102 4103
	}
}

J
jp9000 已提交
4104 4105 4106 4107 4108
void OBSBasic::SceneNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSScene  scene = GetCurrentScene();
	QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
4109
	string    text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
4110 4111 4112 4113

	if (!scene)
		return;

4114
	obs_source_t *source = obs_scene_get_source(scene);
4115
	RenameListItem(this, ui->scenes, source, text);
J
jp9000 已提交
4116

J
jp9000 已提交
4117 4118 4119
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);

J
jp9000 已提交
4120 4121 4122 4123 4124 4125 4126 4127
	UNUSED_PARAMETER(endHint);
}

void OBSBasic::SceneItemNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSSceneItem item  = GetCurrentSceneItem();
	QLineEdit    *edit = qobject_cast<QLineEdit*>(editor);
4128
	string       text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
4129 4130 4131 4132

	if (!item)
		return;

4133
	obs_source_t *source = obs_sceneitem_get_source(item);
4134
	RenameListItem(this, ui->sources, source, text);
J
jp9000 已提交
4135

4136 4137 4138 4139
	QListWidgetItem *listItem = ui->sources->currentItem();
	listItem->setText(QString());
	SetupVisibilityItem(ui->sources, listItem, item);

J
jp9000 已提交
4140 4141 4142
	UNUSED_PARAMETER(endHint);
}

J
jp9000 已提交
4143 4144 4145 4146 4147 4148 4149 4150
void OBSBasic::OpenFilters()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
4151 4152 4153 4154 4155 4156 4157 4158
void OBSBasic::OpenSceneFilters()
{
	OBSScene scene = GetCurrentScene();
	OBSSource source = obs_scene_get_source(scene);

	CreateFiltersWindow(source);
}

J
jp9000 已提交
4159 4160 4161 4162
#define RECORDING_START \
	"==== Recording Start ==============================================="
#define RECORDING_STOP \
	"==== Recording Stop ================================================"
J
jp9000 已提交
4163 4164 4165 4166
#define REPLAY_BUFFER_START \
	"==== Replay Buffer Start ==========================================="
#define REPLAY_BUFFER_STOP \
	"==== Replay Buffer Stop ============================================"
J
jp9000 已提交
4167 4168 4169 4170 4171
#define STREAMING_START \
	"==== Streaming Start ==============================================="
#define STREAMING_STOP \
	"==== Streaming Stop ================================================"

4172 4173
void OBSBasic::StartStreaming()
{
J
jp9000 已提交
4174 4175
	if (outputHandler->StreamingActive())
		return;
4176
	if (disableOutputsRef)
4177
		return;
J
jp9000 已提交
4178 4179 4180 4181

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

4182 4183
	SaveProject();

J
jp9000 已提交
4184 4185
	ui->streamButton->setEnabled(false);
	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
4186 4187 4188 4189 4190

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

J
jp9000 已提交
4192 4193 4194
	if (!outputHandler->StartStreaming(service)) {
		ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
		ui->streamButton->setEnabled(true);
4195 4196 4197 4198 4199

		if (sysTrayStream) {
			sysTrayStream->setText(ui->streamButton->text());
			sysTrayStream->setEnabled(true);
		}
4200 4201 4202 4203 4204

		QMessageBox::critical(this,
				QTStr("Output.StartStreamFailed"),
				QTStr("Output.StartFailedGeneric"));
		return;
4205
	}
4206 4207 4208 4209 4210

	bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "RecordWhenStreaming");
	if (recordWhenStreaming)
		StartRecording();
4211 4212 4213 4214 4215

	bool replayBufferWhileStreaming = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "ReplayBufferWhileStreaming");
	if (replayBufferWhileStreaming)
		StartReplayBuffer();
4216 4217
}

4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238
#ifdef _WIN32
static inline void UpdateProcessPriority()
{
	const char *priority = config_get_string(App()->GlobalConfig(),
			"General", "ProcessPriority");
	if (priority && strcmp(priority, "Normal") != 0)
		SetProcessPriority(priority);
}

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

4239
inline void OBSBasic::OnActivate()
4240
{
4241 4242
	if (ui->profileMenu->isEnabled()) {
		ui->profileMenu->setEnabled(false);
J
jp9000 已提交
4243
		ui->autoConfigure->setEnabled(false);
4244 4245
		App()->IncrementSleepInhibition();
		UpdateProcessPriority();
C
cg2121 已提交
4246

4247 4248
		if (trayIcon)
			trayIcon->setIcon(QIcon(":/res/images/tray_active.png"));
4249 4250
	}
}
J
jp9000 已提交
4251

4252 4253
inline void OBSBasic::OnDeactivate()
{
4254
	if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
J
jp9000 已提交
4255
		ui->profileMenu->setEnabled(true);
J
jp9000 已提交
4256
		ui->autoConfigure->setEnabled(true);
4257
		App()->DecrementSleepInhibition();
4258
		ClearProcessPriority();
C
cg2121 已提交
4259

4260 4261
		if (trayIcon)
			trayIcon->setIcon(QIcon(":/res/images/obs.png"));
J
jp9000 已提交
4262
	}
4263 4264 4265 4266 4267 4268 4269
}

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

	if (outputHandler->StreamingActive())
4270
		outputHandler->StopStreaming(streamingStopping);
4271 4272

	OnDeactivate();
4273 4274 4275 4276 4277 4278 4279

	bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "RecordWhenStreaming");
	bool keepRecordingWhenStreamStops = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "KeepRecordingWhenStreamStops");
	if (recordWhenStreaming && !keepRecordingWhenStreamStops)
		StopRecording();
4280 4281 4282 4283 4284 4285 4286

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

J
jp9000 已提交
4289 4290 4291 4292 4293
void OBSBasic::ForceStopStreaming()
{
	SaveProject();

	if (outputHandler->StreamingActive())
4294
		outputHandler->StopStreaming(true);
J
jp9000 已提交
4295

4296
	OnDeactivate();
4297 4298 4299 4300 4301 4302 4303

	bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "RecordWhenStreaming");
	bool keepRecordingWhenStreamStops = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "KeepRecordingWhenStreamStops");
	if (recordWhenStreaming && !keepRecordingWhenStreamStops)
		StopRecording();
4304 4305 4306 4307 4308 4309 4310

	bool replayBufferWhileStreaming = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "ReplayBufferWhileStreaming");
	bool keepReplayBufferStreamStops = config_get_bool(GetGlobalConfig(),
		"BasicWindow", "KeepReplayBufferStreamStops");
	if (replayBufferWhileStreaming && !keepReplayBufferStreamStops)
		StopReplayBuffer();
J
jp9000 已提交
4311 4312 4313 4314 4315 4316
}

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

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
J
jp9000 已提交
4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333

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

4335
	OnActivate();
J
jp9000 已提交
4336 4337 4338 4339 4340 4341
}

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

	if (sysTrayStream) {
		sysTrayStream->setText(ui->streamButton->text());
		sysTrayStream->setEnabled(true);
	}
J
jp9000 已提交
4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360

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

4361
void OBSBasic::StreamingStart()
4362
{
4363
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
J
jp9000 已提交
4364
	ui->streamButton->setEnabled(true);
J
jp9000 已提交
4365
	ui->statusbar->StreamStarted(outputHandler->streamOutput);
4366 4367 4368 4369 4370

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

J
jp9000 已提交
4372 4373 4374
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STARTED);

4375
	OnActivate();
4376

4377
	blog(LOG_INFO, STREAMING_START);
4378 4379
}

4380 4381 4382
void OBSBasic::StreamStopping()
{
	ui->streamButton->setText(QTStr("Basic.Main.StoppingStreaming"));
4383 4384 4385

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

4387
	streamingStopping = true;
J
jp9000 已提交
4388 4389
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPING);
4390 4391
}

4392
void OBSBasic::StreamingStop(int code, QString last_error)
4393
{
4394 4395 4396
	const char *errorDescription;
	DStr errorMessage;
	bool use_last_error = false;
4397 4398 4399

	switch (code) {
	case OBS_OUTPUT_BAD_PATH:
4400
		errorDescription = Str("Output.ConnectFail.BadPath");
4401 4402 4403
		break;

	case OBS_OUTPUT_CONNECT_FAILED:
4404 4405
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.ConnectFailed");
4406 4407 4408
		break;

	case OBS_OUTPUT_INVALID_STREAM:
4409
		errorDescription = Str("Output.ConnectFail.InvalidStream");
4410 4411
		break;

4412
	default:
4413
	case OBS_OUTPUT_ERROR:
4414 4415
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.Error");
4416 4417 4418 4419 4420
		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 */
4421 4422
		use_last_error = true;
		errorDescription = Str("Output.ConnectFail.Disconnected");
4423 4424
	}

4425 4426 4427 4428 4429 4430
	if (use_last_error && !last_error.isEmpty())
		dstr_printf(errorMessage, "%s\n\n%s", errorDescription,
			QT_TO_UTF8(last_error));
	else
		dstr_copy(errorMessage, errorDescription);

J
jp9000 已提交
4431
	ui->statusbar->StreamStopped();
4432

4433
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
J
jp9000 已提交
4434
	ui->streamButton->setEnabled(true);
4435 4436 4437 4438 4439

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

4441
	streamingStopping = false;
J
jp9000 已提交
4442 4443 4444
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_STREAMING_STOPPED);

4445
	OnDeactivate();
4446 4447

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

C
cg2121 已提交
4449
	if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
4450
		OBSMessageBox::information(this,
4451 4452
				QTStr("Output.ConnectFail.Title"),
				QT_UTF8(errorMessage));
C
cg2121 已提交
4453
	} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
4454
		SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning);
C
cg2121 已提交
4455
	}
J
jp9000 已提交
4456 4457 4458 4459 4460 4461

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

4464 4465
void OBSBasic::StartRecording()
{
4466 4467
	if (outputHandler->RecordingActive())
		return;
4468
	if (disableOutputsRef)
4469
		return;
4470

J
jp9000 已提交
4471 4472 4473
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING);

4474 4475
	SaveProject();
	outputHandler->StartRecording();
4476 4477
}

4478 4479
void OBSBasic::RecordStopping()
{
J
jp9000 已提交
4480
	ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording"));
4481 4482 4483

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

4485
	recordingStopping = true;
J
jp9000 已提交
4486 4487
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPING);
4488 4489
}

4490 4491 4492 4493 4494
void OBSBasic::StopRecording()
{
	SaveProject();

	if (outputHandler->RecordingActive())
4495
		outputHandler->StopRecording(recordingStopping);
J
jp9000 已提交
4496

4497
	OnDeactivate();
4498 4499
}

P
Palana 已提交
4500 4501
void OBSBasic::RecordingStart()
{
J
jp9000 已提交
4502
	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
J
jp9000 已提交
4503
	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
4504 4505 4506

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

4508
	recordingStopping = false;
J
jp9000 已提交
4509 4510 4511
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTED);

4512
	OnActivate();
4513

4514
	blog(LOG_INFO, RECORDING_START);
P
Palana 已提交
4515 4516
}

4517
void OBSBasic::RecordingStop(int code)
4518
{
P
Palana 已提交
4519
	ui->statusbar->RecordingStopped();
J
jp9000 已提交
4520
	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
4521 4522 4523

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

4525
	blog(LOG_INFO, RECORDING_STOP);
4526

C
cg2121 已提交
4527
	if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
4528
		OBSMessageBox::information(this,
4529 4530
				QTStr("Output.RecordFail.Title"),
				QTStr("Output.RecordFail.Unsupported"));
J
jp9000 已提交
4531

C
cg2121 已提交
4532
	} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
4533
		OBSMessageBox::information(this,
J
jp9000 已提交
4534 4535 4536
				QTStr("Output.RecordNoSpace.Title"),
				QTStr("Output.RecordNoSpace.Msg"));

C
cg2121 已提交
4537
	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
4538
		OBSMessageBox::information(this,
J
jp9000 已提交
4539 4540
				QTStr("Output.RecordError.Title"),
				QTStr("Output.RecordError.Msg"));
C
cg2121 已提交
4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552

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

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

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

J
jp9000 已提交
4555 4556 4557
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPED);

4558
	OnDeactivate();
4559
}
4560

J
jp9000 已提交
4561 4562 4563 4564 4565 4566 4567 4568 4569
#define RP_NO_HOTKEY_TITLE QTStr("Output.ReplayBuffer.NoHotkey.Title")
#define RP_NO_HOTKEY_TEXT  QTStr("Output.ReplayBuffer.NoHotkey.Msg")

void OBSBasic::StartReplayBuffer()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;
	if (outputHandler->ReplayBufferActive())
		return;
4570
	if (disableOutputsRef)
4571
		return;
J
jp9000 已提交
4572 4573 4574 4575 4576 4577 4578 4579 4580 4581

	obs_output_t *output = outputHandler->replayBuffer;
	obs_data_t *hotkeys = obs_hotkeys_save_output(output);
	obs_data_array_t *bindings = obs_data_get_array(hotkeys,
			"ReplayBuffer.Save");
	size_t count = obs_data_array_count(bindings);
	obs_data_array_release(bindings);
	obs_data_release(hotkeys);

	if (!count) {
4582
		OBSMessageBox::information(this,
J
jp9000 已提交
4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654
				RP_NO_HOTKEY_TITLE,
				RP_NO_HOTKEY_TEXT);
		return;
	}

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

	SaveProject();
	outputHandler->StartReplayBuffer();
}

void OBSBasic::ReplayBufferStopping()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

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

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

	replayBufferStopping = true;
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING);
}

void OBSBasic::StopReplayBuffer()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

	SaveProject();

	if (outputHandler->ReplayBufferActive())
		outputHandler->StopReplayBuffer(replayBufferStopping);

	OnDeactivate();
}

void OBSBasic::ReplayBufferStart()
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

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

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

	replayBufferStopping = false;
	if (api)
		api->on_event(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED);

	OnActivate();

	blog(LOG_INFO, REPLAY_BUFFER_START);
}

void OBSBasic::ReplayBufferStop(int code)
{
	if (!outputHandler || !outputHandler->replayBuffer)
		return;

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

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

	blog(LOG_INFO, REPLAY_BUFFER_STOP);

	if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
4655
		OBSMessageBox::information(this,
J
jp9000 已提交
4656 4657 4658 4659
				QTStr("Output.RecordFail.Title"),
				QTStr("Output.RecordFail.Unsupported"));

	} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
4660
		OBSMessageBox::information(this,
J
jp9000 已提交
4661 4662 4663 4664
				QTStr("Output.RecordNoSpace.Title"),
				QTStr("Output.RecordNoSpace.Msg"));

	} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
4665
		OBSMessageBox::information(this,
J
jp9000 已提交
4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687
				QTStr("Output.RecordError.Title"),
				QTStr("Output.RecordError.Msg"));

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

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

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

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

	OnDeactivate();
}

4688 4689
void OBSBasic::on_streamButton_clicked()
{
J
jp9000 已提交
4690
	if (outputHandler->StreamingActive()) {
4691 4692 4693
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
				"WarnBeforeStoppingStream");

C
cg2121 已提交
4694
		if (confirm && isVisible()) {
4695
			QMessageBox::StandardButton button =
4696
				OBSMessageBox::question(this,
4697 4698 4699 4700 4701 4702 4703
						QTStr("ConfirmStop.Title"),
						QTStr("ConfirmStop.Text"));

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

4704
		StopStreaming();
4705
	} else {
4706 4707 4708
		bool confirm = config_get_bool(GetGlobalConfig(), "BasicWindow",
				"WarnBeforeStartingStream");

C
cg2121 已提交
4709
		if (confirm && isVisible()) {
4710
			QMessageBox::StandardButton button =
4711
				OBSMessageBox::question(this,
4712 4713 4714 4715 4716 4717 4718
						QTStr("ConfirmStart.Title"),
						QTStr("ConfirmStart.Text"));

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

4719
		StartStreaming();
4720 4721 4722 4723 4724
	}
}

void OBSBasic::on_recordButton_clicked()
{
4725 4726 4727 4728
	if (outputHandler->RecordingActive())
		StopRecording();
	else
		StartRecording();
J
jp9000 已提交
4729 4730
}

J
jp9000 已提交
4731
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
4732
{
J
jp9000 已提交
4733
	on_action_Settings_triggered();
J
jp9000 已提交
4734
}
4735

4736 4737 4738 4739 4740 4741
void OBSBasic::on_actionWebsite_triggered()
{
	QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode);
	QDesktopServices::openUrl(url);
}

4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761
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));
}

4762 4763 4764 4765 4766 4767 4768 4769 4770
QListWidgetItem *OBSBasic::GetTopSelectedSourceItem()
{
	QList<QListWidgetItem*> selectedItems = ui->sources->selectedItems();
	QListWidgetItem *topItem = nullptr;
	if (selectedItems.size() != 0)
		topItem = selectedItems[0];
	return topItem;
}

J
jp9000 已提交
4771 4772
void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos)
{
4773
	CreateSourcePopupMenu(GetTopSelectedSourceItem(), true);
J
jp9000 已提交
4774 4775 4776 4777 4778 4779 4780 4781

	UNUSED_PARAMETER(pos);
}

void OBSBasic::on_previewDisabledLabel_customContextMenuRequested(
		const QPoint &pos)
{
	QMenu popup(this);
J
jp9000 已提交
4782
	QPointer<QMenu> previewProjector;
J
jp9000 已提交
4783 4784 4785 4786 4787

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

J
jp9000 已提交
4790 4791 4792 4793
	previewProjector = new QMenu(QTStr("PreviewProjector"));
	AddProjectorMenuMonitors(previewProjector, this,
			SLOT(OpenPreviewProjector()));

C
cg2121 已提交
4794 4795 4796 4797
	QAction *previewWindow = popup.addAction(
			QTStr("PreviewWindow"),
			this, SLOT(OpenPreviewWindow()));

J
jp9000 已提交
4798
	popup.addMenu(previewProjector);
C
cg2121 已提交
4799
	popup.addAction(previewWindow);
J
jp9000 已提交
4800 4801 4802 4803 4804
	popup.exec(QCursor::pos());

	UNUSED_PARAMETER(pos);
}

4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826
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();
}

4827 4828 4829 4830 4831 4832 4833 4834 4835 4836
void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const
{
	const char *val = config_get_string(basicConfig, "Video", "FPSCommon");

	if (strcmp(val, "10") == 0) {
		num = 10;
		den = 1;
	} else if (strcmp(val, "20") == 0) {
		num = 20;
		den = 1;
J
jp9000 已提交
4837 4838 4839
	} else if (strcmp(val, "24 NTSC") == 0) {
		num = 24000;
		den = 1001;
4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892
	} 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);
}

4893
config_t *OBSBasic::Config() const
4894 4895 4896
{
	return basicConfig;
}
J
jp9000 已提交
4897 4898 4899

void OBSBasic::on_actionEditTransform_triggered()
{
4900 4901 4902
	if (transformWindow)
		transformWindow->close();

J
jp9000 已提交
4903 4904
	transformWindow = new OBSBasicTransform(this);
	transformWindow->show();
4905
	transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
J
jp9000 已提交
4906 4907
}

4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951
static obs_transform_info copiedTransformInfo;
static obs_sceneitem_crop copiedCropInfo;

void OBSBasic::on_actionCopyTransform_triggered()
{
	auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param)
	{
		if (!obs_sceneitem_selected(item))
			return true;

		obs_sceneitem_defer_update_begin(item);
		obs_sceneitem_get_info(item, &copiedTransformInfo);
		obs_sceneitem_get_crop(item, &copiedCropInfo);
		obs_sceneitem_defer_update_end(item);

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

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
	ui->actionPasteTransform->setEnabled(true);
}

void OBSBasic::on_actionPasteTransform_triggered()
{
	auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param)
	{
		if (!obs_sceneitem_selected(item))
			return true;

		obs_sceneitem_defer_update_begin(item);
		obs_sceneitem_set_info(item, &copiedTransformInfo);
		obs_sceneitem_set_crop(item, &copiedCropInfo);
		obs_sceneitem_defer_update_end(item);

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

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

J
jp9000 已提交
4952 4953
void OBSBasic::on_actionResetTransform_triggered()
{
4954
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
4955 4956 4957 4958
	{
		if (!obs_sceneitem_selected(item))
			return true;

J
jp9000 已提交
4959 4960
		obs_sceneitem_defer_update_begin(item);

4961
		obs_transform_info info;
J
jp9000 已提交
4962 4963 4964 4965 4966 4967 4968 4969 4970
		vec2_set(&info.pos, 0.0f, 0.0f);
		vec2_set(&info.scale, 1.0f, 1.0f);
		info.rot = 0.0f;
		info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
		info.bounds_type = OBS_BOUNDS_NONE;
		info.bounds_alignment = OBS_ALIGN_CENTER;
		vec2_set(&info.bounds, 0.0f, 0.0f);
		obs_sceneitem_set_info(item, &info);

J
jp9000 已提交
4971 4972 4973 4974 4975
		obs_sceneitem_crop crop = {};
		obs_sceneitem_set_crop(item, &crop);

		obs_sceneitem_defer_update_end(item);

J
jp9000 已提交
4976 4977 4978 4979 4980 4981 4982 4983
		UNUSED_PARAMETER(scene);
		UNUSED_PARAMETER(param);
		return true;
	};

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

4984
static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
J
jp9000 已提交
4985 4986 4987 4988 4989
{
	matrix4 boxTransform;
	obs_sceneitem_get_box_transform(item, &boxTransform);

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

4992
	auto GetMinPos = [&] (float x, float y)
J
jp9000 已提交
4993 4994 4995 4996
	{
		vec3 pos;
		vec3_set(&pos, x, y, 0.0f);
		vec3_transform(&pos, &pos, &boxTransform);
4997 4998
		vec3_min(&tl, &tl, &pos);
		vec3_max(&br, &br, &pos);
J
jp9000 已提交
4999 5000
	};

5001 5002 5003 5004 5005 5006
	GetMinPos(0.0f, 0.0f);
	GetMinPos(1.0f, 0.0f);
	GetMinPos(0.0f, 1.0f);
	GetMinPos(1.0f, 1.0f);
}

5007
static vec3 GetItemTL(obs_sceneitem_t *item)
5008 5009 5010
{
	vec3 tl, br;
	GetItemBox(item, tl, br);
J
jp9000 已提交
5011 5012 5013
	return tl;
}

5014
static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
J
jp9000 已提交
5015 5016 5017 5018
{
	vec3 newTL;
	vec2 pos;

J
jp9000 已提交
5019
	obs_sceneitem_get_pos(item, &pos);
J
jp9000 已提交
5020 5021 5022
	newTL = GetItemTL(item);
	pos.x += tl.x - newTL.x;
	pos.y += tl.y - newTL.y;
J
jp9000 已提交
5023
	obs_sceneitem_set_pos(item, &pos);
J
jp9000 已提交
5024 5025
}

5026
static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
5027 5028 5029 5030 5031 5032 5033 5034 5035
		void *param)
{
	if (!obs_sceneitem_selected(item))
		return true;

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

	vec3 tl = GetItemTL(item);

J
jp9000 已提交
5036
	rot += obs_sceneitem_get_rot(item);
J
jp9000 已提交
5037 5038
	if (rot >= 360.0f)       rot -= 360.0f;
	else if (rot <= -360.0f) rot += 360.0f;
J
jp9000 已提交
5039
	obs_sceneitem_set_rot(item, rot);
J
jp9000 已提交
5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065

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

5066
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
5067 5068 5069 5070 5071 5072 5073 5074 5075 5076
		void *param)
{
	vec2 &mul = *reinterpret_cast<vec2*>(param);

	if (!obs_sceneitem_selected(item))
		return true;

	vec3 tl = GetItemTL(item);

	vec2 scale;
J
jp9000 已提交
5077
	obs_sceneitem_get_scale(item, &scale);
J
jp9000 已提交
5078
	vec2_mul(&scale, &scale, &mul);
J
jp9000 已提交
5079
	obs_sceneitem_set_scale(item, &scale);
J
jp9000 已提交
5080 5081

	SetItemTL(item, tl);
J
jp9000 已提交
5082 5083

	UNUSED_PARAMETER(scene);
J
jp9000 已提交
5084 5085 5086 5087 5088
	return true;
}

void OBSBasic::on_actionFlipHorizontal_triggered()
{
J
jp9000 已提交
5089 5090
	vec2 scale;
	vec2_set(&scale, -1.0f, 1.0f);
J
jp9000 已提交
5091 5092 5093 5094 5095 5096
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

void OBSBasic::on_actionFlipVertical_triggered()
{
J
jp9000 已提交
5097 5098
	vec2 scale;
	vec2_set(&scale, 1.0f, -1.0f);
J
jp9000 已提交
5099 5100 5101 5102
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

5103
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
5104 5105 5106 5107 5108 5109 5110 5111 5112 5113
		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);

5114
	obs_transform_info itemInfo;
J
jp9000 已提交
5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146
	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()
{
5147
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172
	{
		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 已提交
5173
		UNUSED_PARAMETER(param);
5174 5175 5176 5177
		return true;
	};

	obs_scene_enum_items(GetCurrentScene(), func, nullptr);
J
jp9000 已提交
5178
}
J
jp9000 已提交
5179

5180 5181 5182 5183 5184 5185 5186
void OBSBasic::EnablePreviewDisplay(bool enable)
{
	obs_display_set_enabled(ui->preview->GetDisplay(), enable);
	ui->preview->setVisible(enable);
	ui->previewDisabledLabel->setVisible(!enable);
}

J
jp9000 已提交
5187 5188
void OBSBasic::TogglePreview()
{
5189 5190
	previewEnabled = !previewEnabled;
	EnablePreviewDisplay(previewEnabled);
J
jp9000 已提交
5191
}
5192 5193 5194

void OBSBasic::Nudge(int dist, MoveDir dir)
{
5195 5196 5197
	if (ui->preview->Locked())
		return;

5198 5199 5200 5201 5202 5203 5204
	struct MoveInfo {
		float dist;
		MoveDir dir;
	} info = {(float)dist, dir};

	auto func = [] (obs_scene_t*, obs_sceneitem_t *item, void *param)
	{
5205 5206 5207
		if (obs_sceneitem_locked(item))
			return true;

5208
		MoveInfo *info = reinterpret_cast<MoveInfo*>(param);
J
jp9000 已提交
5209
		struct vec2 dir;
5210 5211
		struct vec2 pos;

J
jp9000 已提交
5212 5213
		vec2_set(&dir, 0.0f, 0.0f);

5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236
		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 已提交
5237

C
cg2121 已提交
5238 5239
void OBSBasic::OpenProjector(obs_source_t *source, int monitor, bool window,
		QString title)
J
jp9000 已提交
5240 5241
{
	/* seriously?  10 monitors? */
C
cg2121 已提交
5242
	if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1)
J
jp9000 已提交
5243 5244
		return;

C
cg2121 已提交
5245 5246 5247 5248 5249
	bool isPreview = false;

	if (source == nullptr)
		isPreview = true;

C
cg2121 已提交
5250 5251 5252 5253 5254
	if (!window) {
		delete projectors[monitor];
		projectors[monitor].clear();
		RemoveSavedProjectors(monitor);
	}
J
jp9000 已提交
5255

C
cg2121 已提交
5256
	OBSProjector *projector = new OBSProjector(nullptr, source, !!window);
C
cg2121 已提交
5257 5258
	const char *name = obs_source_get_name(source);

C
cg2121 已提交
5259 5260 5261 5262 5263 5264
	if (!window) {
		if (isPreview) {
			previewProjectorArray.at((size_t)monitor) = 1;
		} else {
			projectorArray.at((size_t)monitor) = name;
		}
C
cg2121 已提交
5265 5266
	}

C
cg2121 已提交
5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282
	if (!window) {
		projector->Init(monitor, false, nullptr);
		projectors[monitor] = projector;
	} else {
		projector->Init(monitor, true, title);

		for (auto &projPtr : windowProjectors) {
			if (!projPtr) {
				projPtr = projector;
				projector = nullptr;
			}
		}

		if (projector)
			windowProjectors.push_back(projector);
	}
J
jp9000 已提交
5283 5284 5285 5286 5287
}

void OBSBasic::OpenPreviewProjector()
{
	int monitor = sender()->property("monitor").toInt();
C
cg2121 已提交
5288
	OpenProjector(nullptr, monitor, false);
J
jp9000 已提交
5289 5290 5291 5292 5293 5294 5295 5296 5297
}

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

C
cg2121 已提交
5298
	OpenProjector(obs_sceneitem_get_source(item), monitor, false);
J
jp9000 已提交
5299 5300 5301 5302 5303 5304 5305 5306 5307
}

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

C
cg2121 已提交
5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345
	OpenProjector(obs_scene_get_source(scene), monitor, false);
}

void OBSBasic::OpenPreviewWindow()
{
	int monitor = sender()->property("monitor").toInt();
	QString title = QTStr("PreviewWindow");
	OpenProjector(nullptr, monitor, true, title);
}

void OBSBasic::OpenSourceWindow()
{
	int monitor = sender()->property("monitor").toInt();
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);
	QString text = QString::fromUtf8(obs_source_get_name(source));

	QString title = QTStr("SourceWindow") + " - " + text;

	if (!item)
		return;

	OpenProjector(obs_sceneitem_get_source(item), monitor, true, title);
}

void OBSBasic::OpenSceneWindow()
{
	int monitor = sender()->property("monitor").toInt();
	OBSScene scene = GetCurrentScene();
	OBSSource source = obs_scene_get_source(scene);
	QString text = QString::fromUtf8(obs_source_get_name(source));

	QString title = QTStr("SceneWindow") + " - " + text;

	if (!scene)
		return;

	OpenProjector(obs_scene_get_source(scene), monitor, true, title);
J
jp9000 已提交
5346
}
5347

C
cg2121 已提交
5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364
void OBSBasic::OpenSavedProjectors()
{
	bool projectorSave = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SaveProjectors");

	if (projectorSave) {
		for (size_t i = 0; i < projectorArray.size(); i++) {
			if (projectorArray.at(i).empty() == false) {
				OBSSource source = obs_get_source_by_name(
					projectorArray.at(i).c_str());

				if (!source) {
					RemoveSavedProjectors((int)i);
					obs_source_release(source);
					continue;
				}

C
cg2121 已提交
5365
				OpenProjector(source, (int)i, false);
C
cg2121 已提交
5366 5367 5368 5369 5370 5371
				obs_source_release(source);
			}
		}

		for (size_t i = 0; i < previewProjectorArray.size(); i++) {
			if (previewProjectorArray.at(i) == 1) {
C
cg2121 已提交
5372
				OpenProjector(nullptr, (int)i, false);
C
cg2121 已提交
5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383
			}
		}
	}
}

void OBSBasic::RemoveSavedProjectors(int monitor)
{
	previewProjectorArray.at((size_t)monitor) = 0;
	projectorArray.at((size_t)monitor) = "";
}

5384 5385 5386 5387 5388 5389 5390 5391 5392 5393
void OBSBasic::on_actionFullscreenInterface_triggered()
{
	if (!fullscreenInterface)
		showFullScreen();
	else
		showNormal();

	fullscreenInterface = !fullscreenInterface;
}

5394 5395 5396 5397
void OBSBasic::UpdateTitleBar()
{
	stringstream name;

J
jp9000 已提交
5398 5399
	const char *profile = config_get_string(App()->GlobalConfig(),
			"Basic", "Profile");
J
jp9000 已提交
5400 5401 5402
	const char *sceneCollection = config_get_string(App()->GlobalConfig(),
			"Basic", "SceneCollection");

5403 5404 5405 5406 5407
	name << "OBS ";
	if (previewProgramMode)
		name << "Studio ";

	name << App()->GetVersionString();
5408 5409 5410
	if (App()->IsPortableMode())
		name << " - Portable Mode";

J
jp9000 已提交
5411
	name << " - " << Str("TitleBar.Profile") << ": " << profile;
J
jp9000 已提交
5412
	name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;
5413 5414 5415

	setWindowTitle(QT_UTF8(name.str().c_str()));
}
J
jp9000 已提交
5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439

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

J
jp9000 已提交
5441
void OBSBasic::on_resetUI_triggered()
5442
{
J
jp9000 已提交
5443
	restoreState(startingDockLayout);
5444

5445
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
J
jp9000 已提交
5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479
	int cx = width();
	int cy = height();

	int cx22_5 = cx * 225 / 1000;
	int cx5 = cx * 5 / 100;

	cy = cy * 225 / 1000;

	int mixerSize = cx - (cx22_5 * 2 + cx5 * 2);

	QList<QDockWidget*> docks {
		ui->scenesDock,
		ui->sourcesDock,
		ui->mixerDock,
		ui->transitionsDock,
		ui->controlsDock
	};

	QList<int> sizes {
		cx22_5,
		cx22_5,
		mixerSize,
		cx5,
		cx5
	};

	ui->scenesDock->setVisible(true);
	ui->sourcesDock->setVisible(true);
	ui->mixerDock->setVisible(true);
	ui->transitionsDock->setVisible(true);
	ui->controlsDock->setVisible(true);

	resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical);
	resizeDocks(docks, sizes, Qt::Horizontal);
5480
#endif
J
jp9000 已提交
5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493
}

void OBSBasic::on_lockUI_toggled(bool lock)
{
	QDockWidget::DockWidgetFeatures features = lock
		? QDockWidget::NoDockWidgetFeatures
		: QDockWidget::AllDockWidgetFeatures;

	ui->scenesDock->setFeatures(features);
	ui->sourcesDock->setFeatures(features);
	ui->mixerDock->setFeatures(features);
	ui->transitionsDock->setFeatures(features);
	ui->controlsDock->setFeatures(features);
5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511
}

void OBSBasic::on_toggleListboxToolbars_toggled(bool visible)
{
	ui->sourcesToolbar->setVisible(visible);
	ui->scenesToolbar->setVisible(visible);

	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"ShowListboxToolbars", visible);
}

void OBSBasic::on_toggleStatusBar_toggled(bool visible)
{
	ui->statusbar->setVisible(visible);

	config_set_bool(App()->GlobalConfig(), "BasicWindow",
			"ShowStatusBar", visible);
}
J
jp9000 已提交
5512 5513 5514 5515 5516 5517

void OBSBasic::on_actionLockPreview_triggered()
{
	ui->preview->ToggleLocked();
	ui->actionLockPreview->setChecked(ui->preview->Locked());
}
C
cg2121 已提交
5518

J
Joseph El-Khouri 已提交
5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534
void OBSBasic::on_scalingMenu_aboutToShow()
{
	obs_video_info ovi;
	obs_get_video_info(&ovi);

	QAction *action = ui->actionScaleCanvas;
	QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas");
	text = text.arg(QString::number(ovi.base_width),
			QString::number(ovi.base_height));
	action->setText(text);

	action = ui->actionScaleOutput;
	text = QTStr("Basic.MainMenu.Edit.Scale.Output");
	text = text.arg(QString::number(ovi.output_width),
			QString::number(ovi.output_height));
	action->setText(text);
5535 5536
	action->setVisible(!(ovi.output_width == ovi.base_width &&
			ovi.output_height == ovi.base_height));
J
Joseph El-Khouri 已提交
5537 5538 5539 5540 5541 5542

	UpdatePreviewScalingMenu();
}

void OBSBasic::on_actionScaleWindow_triggered()
{
5543
	ui->preview->SetFixedScaling(false);
J
Joseph El-Khouri 已提交
5544 5545 5546 5547 5548 5549
	ui->preview->ResetScrollingOffset();
	emit ui->preview->DisplayResized();
}

void OBSBasic::on_actionScaleCanvas_triggered()
{
5550 5551
	ui->preview->SetFixedScaling(true);
	ui->preview->SetScalingLevel(0);
J
Joseph El-Khouri 已提交
5552 5553 5554 5555 5556
	emit ui->preview->DisplayResized();
}

void OBSBasic::on_actionScaleOutput_triggered()
{
5557 5558 5559 5560 5561 5562 5563 5564 5565 5566
	obs_video_info ovi;
	obs_get_video_info(&ovi);

	ui->preview->SetFixedScaling(true);
	float scalingAmount = float(ovi.output_width) / float(ovi.base_width);
	// log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY)
	int32_t approxScalingLevel = int32_t(
			round(log(scalingAmount) / log(ZOOM_SENSITIVITY)));
	ui->preview->SetScalingLevel(approxScalingLevel);
	ui->preview->SetScalingAmount(scalingAmount);
J
Joseph El-Khouri 已提交
5567 5568 5569
	emit ui->preview->DisplayResized();
}

C
cg2121 已提交
5570 5571 5572 5573 5574 5575 5576
void OBSBasic::SetShowing(bool showing)
{
	if (!showing && isVisible()) {
		config_set_string(App()->GlobalConfig(),
			"BasicWindow", "geometry",
			saveGeometry().toBase64().constData());

5577 5578 5579 5580 5581 5582 5583 5584 5585
		/* hide all visible child dialogs */
		visDlgPositions.clear();
		if (!visDialogs.isEmpty()) {
			for (QDialog *dlg : visDialogs) {
				visDlgPositions.append(dlg->pos());
				dlg->hide();
			}
		}

5586 5587
		if (showHide)
			showHide->setText(QTStr("Basic.SystemTray.Show"));
C
cg2121 已提交
5588 5589 5590 5591 5592 5593 5594 5595
		QTimer::singleShot(250, this, SLOT(hide()));

		if (previewEnabled)
			EnablePreviewDisplay(false);

		setVisible(false);

	} else if (showing && !isVisible()) {
5596 5597
		if (showHide)
			showHide->setText(QTStr("Basic.SystemTray.Hide"));
C
cg2121 已提交
5598 5599 5600 5601 5602 5603
		QTimer::singleShot(250, this, SLOT(show()));

		if (previewEnabled)
			EnablePreviewDisplay(true);

		setVisible(true);
5604

5605 5606 5607 5608 5609 5610 5611 5612 5613
		/* show all child dialogs that was visible earlier */
		if (!visDialogs.isEmpty()) {
			for (int i = 0; i < visDialogs.size(); ++i) {
				QDialog *dlg = visDialogs[i];
				dlg->move(visDlgPositions[i]);
				dlg->show();
			}
		}

5614 5615 5616 5617 5618 5619 5620 5621
		/* Unminimize window if it was hidden to tray instead of task
		 * bar. */
		if (sysTrayMinimizeToTray()) {
			Qt::WindowStates state;
			state  = windowState() & ~Qt::WindowMinimized;
			state |= Qt::WindowActive;
			setWindowState(state);
		}
C
cg2121 已提交
5622 5623 5624
	}
}

5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636
void OBSBasic::ToggleShowHide()
{
	bool showing = isVisible();
	if (showing) {
		/* check for modal dialogs */
		EnumDialogs();
		if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty())
			return;
	}
	SetShowing(!showing);
}

5637 5638
void OBSBasic::SystemTrayInit()
{
C
cg2121 已提交
5639 5640 5641 5642 5643 5644 5645 5646 5647 5648
	trayIcon = new QSystemTrayIcon(QIcon(":/res/images/obs.png"),
			this);
	trayIcon->setToolTip("OBS Studio");

	showHide = new QAction(QTStr("Basic.SystemTray.Show"),
			trayIcon);
	sysTrayStream = new QAction(QTStr("Basic.Main.StartStreaming"),
			trayIcon);
	sysTrayRecord = new QAction(QTStr("Basic.Main.StartRecording"),
			trayIcon);
J
jp9000 已提交
5649 5650
	sysTrayReplayBuffer = new QAction(QTStr("Basic.Main.StartReplayBuffer"),
			trayIcon);
C
cg2121 已提交
5651 5652 5653
	exit = new QAction(QTStr("Exit"),
			trayIcon);

J
jp9000 已提交
5654 5655
	if (outputHandler && !outputHandler->replayBuffer)
		sysTrayReplayBuffer->setEnabled(false);
5656

C
cg2121 已提交
5657 5658 5659 5660 5661 5662 5663 5664 5665
	connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
			this,
			SLOT(IconActivated(QSystemTrayIcon::ActivationReason)));
	connect(showHide, SIGNAL(triggered()),
			this, SLOT(ToggleShowHide()));
	connect(sysTrayStream, SIGNAL(triggered()),
			this, SLOT(on_streamButton_clicked()));
	connect(sysTrayRecord, SIGNAL(triggered()),
			this, SLOT(on_recordButton_clicked()));
5666
	connect(sysTrayReplayBuffer.data(), &QAction::triggered,
J
jp9000 已提交
5667
			this, &OBSBasic::ReplayBufferClicked);
C
cg2121 已提交
5668 5669 5670
	connect(exit, SIGNAL(triggered()),
			this, SLOT(close()));

5671 5672 5673 5674
	QMenu *previewProjector = new QMenu(QTStr("PreviewProjector"));
	AddProjectorMenuMonitors(previewProjector, this,
			SLOT(OpenPreviewProjector()));

C
cg2121 已提交
5675 5676
	trayMenu = new QMenu;
	trayMenu->addAction(showHide);
5677
	trayMenu->addMenu(previewProjector);
C
cg2121 已提交
5678 5679
	trayMenu->addAction(sysTrayStream);
	trayMenu->addAction(sysTrayRecord);
J
jp9000 已提交
5680
	trayMenu->addAction(sysTrayReplayBuffer);
C
cg2121 已提交
5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715
	trayMenu->addAction(exit);
	trayIcon->setContextMenu(trayMenu);
}

void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason)
{
	if (reason == QSystemTrayIcon::Trigger)
		ToggleShowHide();
}

void OBSBasic::SysTrayNotify(const QString &text,
		QSystemTrayIcon::MessageIcon n)
{
	if (QSystemTrayIcon::supportsMessages()) {
		QSystemTrayIcon::MessageIcon icon =
				QSystemTrayIcon::MessageIcon(n);
		trayIcon->showMessage("OBS Studio", text, icon, 10000);
	}
}

void OBSBasic::SystemTray(bool firstStarted)
{
	if (!QSystemTrayIcon::isSystemTrayAvailable())
		return;

	bool sysTrayWhenStarted = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SysTrayWhenStarted");
	bool sysTrayEnabled = config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SysTrayEnabled");

	if (firstStarted)
		SystemTrayInit();

	if (!sysTrayWhenStarted && !sysTrayEnabled) {
		trayIcon->hide();
C
cg2121 已提交
5716 5717
	} else if ((sysTrayWhenStarted && sysTrayEnabled)
			|| opt_minimize_tray) {
C
cg2121 已提交
5718 5719 5720 5721 5722
		trayIcon->show();
		if (firstStarted) {
			QTimer::singleShot(50, this, SLOT(hide()));
			EnablePreviewDisplay(false);
			setVisible(false);
C
cg2121 已提交
5723
			opt_minimize_tray = false;
C
cg2121 已提交
5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737
		}
	} else if (sysTrayEnabled) {
		trayIcon->show();
	} else if (!sysTrayEnabled) {
		trayIcon->hide();
	} else if (!sysTrayWhenStarted && sysTrayEnabled) {
		trayIcon->hide();
	}

	if (isVisible())
		showHide->setText(QTStr("Basic.SystemTray.Hide"));
	else
		showHide->setText(QTStr("Basic.SystemTray.Show"));
}
5738 5739 5740 5741 5742 5743

bool OBSBasic::sysTrayMinimizeToTray()
{
	return config_get_bool(GetGlobalConfig(),
			"BasicWindow", "SysTrayMinimizeToTray");
}
5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759

void OBSBasic::on_actionCopySource_triggered()
{
	on_actionCopyTransform_triggered();

	OBSSceneItem item = GetCurrentSceneItem();

	if (!item)
		return;

	OBSSource source = obs_sceneitem_get_source(item);

	copyString = obs_source_get_name(source);
	copyVisible = obs_sceneitem_visible(item);

	ui->actionPasteRef->setEnabled(true);
5760 5761 5762 5763 5764 5765

	uint32_t output_flags = obs_source_get_output_flags(source);
	if ((output_flags & OBS_SOURCE_DO_NOT_DUPLICATE) == 0)
		ui->actionPasteDup->setEnabled(true);
	else
		ui->actionPasteDup->setEnabled(false);
5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805
}

void OBSBasic::on_actionPasteRef_triggered()
{
	OBSBasicSourceSelect::SourcePaste(copyString, copyVisible, false);
	on_actionPasteTransform_triggered();
}

void OBSBasic::on_actionPasteDup_triggered()
{
	OBSBasicSourceSelect::SourcePaste(copyString, copyVisible, true);
	on_actionPasteTransform_triggered();
}

void OBSBasic::on_actionCopyFilters_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();

	if (!item)
		return;

	OBSSource source = obs_sceneitem_get_source(item);

	copyFiltersString = obs_source_get_name(source);

	ui->actionPasteFilters->setEnabled(true);
}

void OBSBasic::on_actionPasteFilters_triggered()
{
	OBSSource source = obs_get_source_by_name(copyFiltersString);
	OBSSceneItem sceneItem = GetCurrentSceneItem();

	OBSSource dstSource = obs_sceneitem_get_source(sceneItem);

	if (source == dstSource)
		return;

	obs_source_copy_filters(dstSource, source);
}
J
jp9000 已提交
5806 5807 5808 5809 5810 5811 5812 5813

void OBSBasic::on_autoConfigure_triggered()
{
	AutoConfig test(this);
	test.setModal(true);
	test.show();
	test.exec();
}
J
jp9000 已提交
5814 5815 5816

void OBSBasic::on_stats_triggered()
{
5817 5818 5819 5820 5821 5822
	if (!stats.isNull()) {
		stats->show();
		stats->raise();
		return;
	}

J
jp9000 已提交
5823 5824 5825 5826 5827
	OBSBasicStats *statsDlg;
	statsDlg = new OBSBasicStats(nullptr);
	statsDlg->show();
	stats = statsDlg;
}