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

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

    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 13 14 15 16 17 18
    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 已提交
19
#include <time.h>
J
jp9000 已提交
20
#include <obs.hpp>
J
jp9000 已提交
21
#include <QMessageBox>
22
#include <QShowEvent>
23
#include <QDesktopServices>
J
jp9000 已提交
24
#include <QFileDialog>
J
jp9000 已提交
25 26
#include <QNetworkRequest>
#include <QNetworkReply>
27

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

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

J
jp9000 已提交
47
#include "ui_OBSBasic.h"
48

J
jp9000 已提交
49
#include <fstream>
50 51
#include <sstream>

52 53 54
#include <QScreen>
#include <QWindow>

55 56
#define PREVIEW_EDGE_SIZE 10

57
using namespace std;
J
jp9000 已提交
58

J
jp9000 已提交
59 60
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);
P
Palana 已提交
61
Q_DECLARE_METATYPE(OBSSource);
J
jp9000 已提交
62
Q_DECLARE_METATYPE(obs_order_movement);
J
jp9000 已提交
63

64 65
static void AddExtraModulePaths()
{
66 67 68
	char base_module_dir[512];
	int ret = os_get_config_path(base_module_dir, sizeof(base_module_dir),
			"obs-studio/plugins/%module%");
B
BtbN 已提交
69

70
	if (ret <= 0)
71 72 73 74 75 76
		return;

	string path = (char*)base_module_dir;
	obs_add_module_path((path + "/bin").c_str(), (path + "/data").c_str());
}

77 78
static QList<QKeySequence> DeleteKeys;

79
OBSBasic::OBSBasic(QWidget *parent)
80
	: OBSMainWindow  (parent),
J
jp9000 已提交
81
	  ui             (new Ui::OBSBasic)
82 83
{
	ui->setupUi(this);
84

85 86 87 88
	char styleSheetPath[512];
	int ret = os_get_config_path(styleSheetPath, sizeof(styleSheetPath),
			"obs-studio/basic/stylesheet.qss");
	if (ret > 0) {
H
HomeWorld 已提交
89 90 91 92 93
		if (QFile::exists(styleSheetPath)) {
			QString path = QString("file:///") +
				QT_UTF8(styleSheetPath);
			App()->setStyleSheet(path);
		}
94 95
	}

P
Palana 已提交
96 97 98 99
	qRegisterMetaType<OBSScene>    ("OBSScene");
	qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
	qRegisterMetaType<OBSSource>   ("OBSSource");

100 101 102 103 104 105
	connect(windowHandle(), &QWindow::screenChanged, [this]() {
		struct obs_video_info ovi;

		if (obs_get_video_info(&ovi))
			ResizePreview(ovi.base_width, ovi.base_height);
	});
J
jp9000 已提交
106 107 108 109

	stringstream name;
	name << "OBS " << App()->GetVersionString();

J
jp9000 已提交
110
	blog(LOG_INFO, "%s", name.str().c_str());
J
jp9000 已提交
111
	setWindowTitle(QT_UTF8(name.str().c_str()));
J
jp9000 已提交
112 113 114 115 116 117 118 119 120 121 122 123 124 125

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

	cpuUsageInfo = os_cpu_usage_info_start();
J
jp9000 已提交
128 129 130 131
	cpuUsageTimer = new QTimer(this);
	connect(cpuUsageTimer, SIGNAL(timeout()),
			ui->statusbar, SLOT(UpdateCPUUsage()));
	cpuUsageTimer->start(3000);
132

133 134 135 136 137 138
	DeleteKeys =
#ifdef __APPLE__
		QList<QKeySequence>{{Qt::Key_Backspace}} <<
#endif
		QKeySequence::keyBindings(QKeySequence::Delete);

139
#ifdef __APPLE__
140 141
	ui->actionRemoveSource->setShortcuts(DeleteKeys);
	ui->actionRemoveScene->setShortcuts(DeleteKeys);
142 143 144

	ui->action_Settings->setMenuRole(QAction::PreferencesRole);
	ui->actionE_xit->setMenuRole(QAction::QuitRole);
145
#endif
146 147
}

148
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent)
149
{
150
	obs_source_t *source = obs_get_output_source(channel);
151 152 153
	if (!source)
		return;

154
	obs_data_t *data = obs_save_source(source);
155

J
jp9000 已提交
156
	obs_data_set_obj(parent, name, data);
157 158 159 160 161

	obs_data_release(data);
	obs_source_release(source);
}

162
static obs_data_t *GenerateSaveData()
163
{
164 165 166
	obs_data_t       *saveData     = obs_data_create();
	obs_data_array_t *sourcesArray = obs_save_sources();
	obs_source_t     *currentScene = obs_get_output_source(0);
167
	const char       *sceneName   = obs_source_get_name(currentScene);
168

169 170 171 172 173 174
	SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData);
	SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData);
	SaveAudioDevice(AUX_AUDIO_1,     3, saveData);
	SaveAudioDevice(AUX_AUDIO_2,     4, saveData);
	SaveAudioDevice(AUX_AUDIO_3,     5, saveData);

J
jp9000 已提交
175 176
	obs_data_set_string(saveData, "current_scene", sceneName);
	obs_data_set_array(saveData, "sources", sourcesArray);
177 178 179 180 181 182
	obs_data_array_release(sourcesArray);
	obs_source_release(currentScene);

	return saveData;
}

183 184 185 186 187 188 189 190 191 192 193 194
void OBSBasic::ClearVolumeControls()
{
	VolControl *control;

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

	volumes.clear();
}

195 196
void OBSBasic::Save(const char *file)
{
197
	obs_data_t *saveData  = GenerateSaveData();
J
jp9000 已提交
198
	const char *jsonData = obs_data_get_json(saveData);
199 200 201 202 203 204 205 206

	/* TODO maybe a message box here? */
	if (!os_quick_write_utf8_file(file, jsonData, strlen(jsonData), false))
		blog(LOG_ERROR, "Could not save scene data to %s", file);

	obs_data_release(saveData);
}

207
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
208
{
209
	obs_data_t *data = obs_data_get_obj(parent, name);
210 211 212
	if (!data)
		return;

213
	obs_source_t *source = obs_load_source(data);
214 215 216 217 218 219 220 221 222 223
	if (source) {
		obs_set_output_source(channel, source);
		obs_source_release(source);
	}

	obs_data_release(data);
}

void OBSBasic::CreateDefaultScene()
{
224 225
	obs_scene_t  *scene  = obs_scene_create(Str("Basic.Scene"));
	obs_source_t *source = obs_scene_get_source(scene);
226 227 228 229 230

	obs_add_source(source);

#ifdef __APPLE__
	source = obs_source_create(OBS_SOURCE_TYPE_INPUT, "display_capture",
231
			Str("Basic.DisplayCapture"), NULL);
232 233 234 235 236 237 238 239

	if (source) {
		obs_scene_add(scene, source);
		obs_add_source(source);
		obs_source_release(source);
	}
#endif

240
	obs_set_output_source(0, obs_scene_get_source(scene));
241 242 243
	obs_scene_release(scene);
}

244 245 246 247 248 249 250 251
void OBSBasic::Load(const char *file)
{
	if (!file) {
		blog(LOG_ERROR, "Could not find file %s", file);
		return;
	}

	BPtr<char> jsonData = os_quick_read_utf8_file(file);
252 253
	if (!jsonData) {
		CreateDefaultScene();
254
		return;
255
	}
256

257 258
	obs_data_t       *data       = obs_data_create_from_json(jsonData);
	obs_data_array_t *sources    = obs_data_get_array(data, "sources");
J
jp9000 已提交
259 260
	const char       *sceneName = obs_data_get_string(data,
			"current_scene");
261
	obs_source_t     *curScene;
262

263 264 265 266 267 268
	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);

269 270 271 272 273 274 275 276 277 278
	obs_load_sources(sources);

	curScene = obs_get_source_by_name(sceneName);
	obs_set_output_source(0, curScene);
	obs_source_release(curScene);

	obs_data_array_release(sources);
	obs_data_release(data);
}

279 280 281
static inline bool HasAudioDevices(const char *source_id)
{
	const char *output_id = source_id;
282
	obs_properties_t *props = obs_get_source_properties(
283
			OBS_SOURCE_TYPE_INPUT, output_id);
284 285 286 287 288
	size_t count = 0;

	if (!props)
		return false;

289
	obs_property_t *devices = obs_properties_get(props, "device_id");
290 291 292 293 294 295 296 297
	if (devices)
		count = obs_property_list_item_count(devices);

	obs_properties_destroy(props);

	return count != 0;
}

298 299 300 301 302 303 304
#define SERVICE_PATH "obs-studio/basic/service.json"

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

305 306 307 308
	char serviceJsonPath[512];
	int ret = os_get_config_path(serviceJsonPath, sizeof(serviceJsonPath),
			SERVICE_PATH);
	if (ret <= 0)
309 310
		return;

311 312
	obs_data_t *data     = obs_data_create();
	obs_data_t *settings = obs_service_get_settings(service);
313

J
jp9000 已提交
314 315
	obs_data_set_string(data, "type", obs_service_gettype(service));
	obs_data_set_obj(data, "settings", settings);
316

J
jp9000 已提交
317
	const char *json = obs_data_get_json(data);
318 319 320 321 322 323 324 325 326 327 328

	os_quick_write_utf8_file(serviceJsonPath, json, strlen(json), false);

	obs_data_release(settings);
	obs_data_release(data);
}

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

329 330 331 332 333
	if (service) {
		obs_service_destroy(service);
		service = nullptr;
	}

334 335 336 337
	char serviceJsonPath[512];
	int ret = os_get_config_path(serviceJsonPath, sizeof(serviceJsonPath),
			SERVICE_PATH);
	if (ret <= 0)
338 339 340 341 342 343
		return false;

	BPtr<char> jsonText = os_quick_read_utf8_file(serviceJsonPath);
	if (!jsonText)
		return false;

344
	obs_data_t *data = obs_data_create_from_json(jsonText);
345 346

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

349
	obs_data_t *settings = obs_data_get_obj(data, "settings");
350

351
	service = obs_service_create(type, "default_service", settings);
352 353 354 355 356 357 358 359 360 361 362 363

	obs_data_release(settings);
	obs_data_release(data);

	return !!service;
}

bool OBSBasic::InitService()
{
	if (LoadService())
		return true;

364
	service = obs_service_create("rtmp_common", "default_service", nullptr);
365 366 367 368 369 370
	if (!service)
		return false;

	return true;
}

371 372
bool OBSBasic::InitBasicConfigDefaults()
{
373 374 375
	bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource());
	bool hasInputAudio   = HasAudioDevices(App()->InputAudioSource());

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
	config_set_default_int(basicConfig, "Window", "PosX",  -1);
	config_set_default_int(basicConfig, "Window", "PosY",  -1);
	config_set_default_int(basicConfig, "Window", "SizeX", -1);
	config_set_default_int(basicConfig, "Window", "SizeY", -1);

	vector<MonitorInfo> monitors;
	GetMonitors(monitors);

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

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

J
jp9000 已提交
393 394
	config_set_default_string(basicConfig, "Output", "Type", "Simple");

395 396
	config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
			GetDefaultVideoSavePath().c_str());
397 398 399
	config_set_default_uint  (basicConfig, "SimpleOutput", "VBitrate",
			2500);
	config_set_default_uint  (basicConfig, "SimpleOutput", "ABitrate", 128);
J
jp9000 已提交
400 401 402 403 404
	config_set_default_bool  (basicConfig, "SimpleOutput", "Reconnect",
			true);
	config_set_default_uint  (basicConfig, "SimpleOutput", "RetryDelay", 2);
	config_set_default_uint  (basicConfig, "SimpleOutput", "MaxRetries",
			20);
J
jp9000 已提交
405 406
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseAdvanced",
			false);
407
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseCBR", true);
408 409 410
	config_set_default_bool  (basicConfig, "SimpleOutput", "UseBufsize",
			false);
	config_set_default_int   (basicConfig, "SimpleOutput", "Bufsize", 2500);
J
jp9000 已提交
411 412
	config_set_default_string(basicConfig, "SimpleOutput", "Preset",
			"veryfast");
413

J
jp9000 已提交
414 415 416 417 418 419 420 421 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
	config_set_default_bool  (basicConfig, "AdvOut", "Reconnect", true);
	config_set_default_uint  (basicConfig, "AdvOut", "RetryDelay", 2);
	config_set_default_uint  (basicConfig, "AdvOut", "MaxRetries", 20);
	config_set_default_bool  (basicConfig, "AdvOut", "UseRescale", false);
	config_set_default_bool  (basicConfig, "AdvOut", "Multitrack", false);
	config_set_default_uint  (basicConfig, "AdvOut", "TrackIndex", 1);
	config_set_default_uint  (basicConfig, "AdvOut", "TrackCount", 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());
	config_set_default_bool  (basicConfig, "AdvOut", "RecUseRescale",
			false);
	config_set_default_bool  (basicConfig, "AdvOut", "RecMultitrack",
			false);
	config_set_default_uint  (basicConfig, "AdvOut", "RecTrackIndex", 1);
	config_set_default_uint  (basicConfig, "AdvOut", "RecTrackCount", 1);
	config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
			"none");

	config_set_default_uint  (basicConfig, "AdvOut", "FFVBitrate", 2500);
	config_set_default_bool  (basicConfig, "AdvOut", "FFUseRescale",
			false);
	config_set_default_uint  (basicConfig, "AdvOut", "FFABitrate", 160);
	config_set_default_uint  (basicConfig, "AdvOut", "FFAudioTrack", 1);

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

447 448 449 450 451 452 453 454 455 456 457 458 459
	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);

	cx = cx * 10 / 15;
	cy = cy * 10 / 15;
	config_set_default_uint  (basicConfig, "Video", "OutputCX", cx);
	config_set_default_uint  (basicConfig, "Video", "OutputCY", cy);

	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);
460
	config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
461 462 463 464
	config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
	config_set_default_string(basicConfig, "Video", "ColorSpace", "709");
	config_set_default_string(basicConfig, "Video", "ColorRange",
			"Partial");
465 466 467 468 469 470

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

J
jp9000 已提交
471
	config_set_default_string(basicConfig, "Audio", "DesktopDevice1",
472
			hasDesktopAudio ? "default" : "disabled");
J
jp9000 已提交
473 474 475
	config_set_default_string(basicConfig, "Audio", "DesktopDevice2",
			"disabled");
	config_set_default_string(basicConfig, "Audio", "AuxDevice1",
476
			hasInputAudio ? "default" : "disabled");
J
jp9000 已提交
477 478 479 480 481
	config_set_default_string(basicConfig, "Audio", "AuxDevice2",
			"disabled");
	config_set_default_string(basicConfig, "Audio", "AuxDevice3",
			"disabled");

482 483 484 485 486
	return true;
}

bool OBSBasic::InitBasicConfig()
{
487 488 489 490 491 492 493
	char configPath[512];
	int ret = os_get_config_path(configPath, sizeof(configPath),
			"obs-studio/basic/basic.ini");
	if (ret <= 0) {
		OBSErrorBox(nullptr, "Failed to get base.ini path");
		return false;
	}
494

495 496 497
	int code = basicConfig.Open(configPath, CONFIG_OPEN_ALWAYS);
	if (code != CONFIG_SUCCESS) {
		OBSErrorBox(NULL, "Failed to open basic.ini: %d", code);
498 499 500 501 502 503
		return false;
	}

	return InitBasicConfigDefaults();
}

504 505
void OBSBasic::InitOBSCallbacks()
{
506
	signal_handler_connect(obs_get_signal_handler(), "source_add",
507
			OBSBasic::SourceAdded, this);
508
	signal_handler_connect(obs_get_signal_handler(), "source_remove",
509
			OBSBasic::SourceRemoved, this);
510
	signal_handler_connect(obs_get_signal_handler(), "channel_change",
511
			OBSBasic::ChannelChanged, this);
512
	signal_handler_connect(obs_get_signal_handler(), "source_activate",
513
			OBSBasic::SourceActivated, this);
514
	signal_handler_connect(obs_get_signal_handler(), "source_deactivate",
515
			OBSBasic::SourceDeactivated, this);
516
	signal_handler_connect(obs_get_signal_handler(), "source_rename",
J
jp9000 已提交
517
			OBSBasic::SourceRenamed, this);
518 519
}

J
jp9000 已提交
520 521
void OBSBasic::InitPrimitives()
{
J
jp9000 已提交
522
	obs_enter_graphics();
J
jp9000 已提交
523

524
	gs_render_start(true);
J
jp9000 已提交
525 526 527 528 529
	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);
530
	box = gs_render_save();
J
jp9000 已提交
531

532
	gs_render_start(true);
J
jp9000 已提交
533 534 535 536
	for (int i = 0; i <= 360; i += (360/20)) {
		float pos = RAD(float(i));
		gs_vertex2f(cosf(pos), sinf(pos));
	}
537
	circle = gs_render_save();
J
jp9000 已提交
538

J
jp9000 已提交
539
	obs_leave_graphics();
J
jp9000 已提交
540 541
}

J
jp9000 已提交
542 543
void OBSBasic::ResetOutputs()
{
J
jp9000 已提交
544 545 546
	const char *mode = config_get_string(basicConfig, "Output", "Mode");
	bool advOut = astrcmpi(mode, "Advanced") == 0;

J
jp9000 已提交
547 548
	if (!outputHandler || !outputHandler->Active()) {
		outputHandler.reset();
J
jp9000 已提交
549 550 551
		outputHandler.reset(advOut ?
			CreateAdvancedOutputHandler(this) :
			CreateSimpleOutputHandler(this));
J
jp9000 已提交
552 553 554 555 556
	} else {
		outputHandler->Update();
	}
}

557 558
void OBSBasic::OBSInit()
{
559 560 561 562 563
	char savePath[512];
	int ret = os_get_config_path(savePath, sizeof(savePath),
			"obs-studio/basic/scenes.json");
	if (ret <= 0)
		throw "Failed to get scenes.json file path";
564

565 566 567 568
	/* make sure it's fully displayed before doing any initialization */
	show();
	App()->processEvents();

569
	if (!obs_startup(App()->GetLocale()))
570
		throw "Failed to initialize libobs";
571 572
	if (!InitBasicConfig())
		throw "Failed to load basic.ini";
573
	if (!ResetAudio())
574 575
		throw "Failed to initialize audio";

576
	ret = ResetVideo();
577 578 579 580 581

	switch (ret) {
	case OBS_VIDEO_MODULE_NOT_FOUND:
		throw "Failed to initialize video:  Graphics module not found";
	case OBS_VIDEO_NOT_SUPPORTED:
J
jp9000 已提交
582 583 584
		throw "Failed to initialize video:  Required graphics API "
		      "functionality not found on these drivers or "
		      "unavailable on this equipment";
585 586 587 588 589 590 591
	case OBS_VIDEO_INVALID_PARAM:
		throw "Failed to initialize video:  Invalid parameters";
	default:
		if (ret != OBS_VIDEO_SUCCESS)
			throw "Failed to initialize video:  Unspecified error";
	}

592
	InitOBSCallbacks();
593

594
	AddExtraModulePaths();
J
jp9000 已提交
595
	obs_load_all_modules();
J
jp9000 已提交
596

J
jp9000 已提交
597 598
	ResetOutputs();

599 600 601
	if (!InitService())
		throw "Failed to initialize service";

J
jp9000 已提交
602 603
	InitPrimitives();

604
	Load(savePath);
J
jp9000 已提交
605
	ResetAudioDevices();
606

J
jp9000 已提交
607
	TimedCheckForUpdates();
608
	loaded = true;
J
jp9000 已提交
609 610 611 612

	QTimer *timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), this, SLOT(SaveProject()));
	timer->start(20000);
613 614 615 616
}

OBSBasic::~OBSBasic()
{
J
jp9000 已提交
617
	SaveProject();
618

J
jp9000 已提交
619 620 621 622 623 624
	/* 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 已提交
625
	delete cpuUsageTimer;
626 627
	os_cpu_usage_info_destroy(cpuUsageInfo);

J
jp9000 已提交
628 629
	outputHandler.reset();

J
John Bradley 已提交
630 631 632
	if (interaction)
		delete interaction;

633 634 635 636 637
	if (properties)
		delete properties;

	if (transformWindow)
		delete transformWindow;
638

J
jp9000 已提交
639 640 641
	if (advAudioWindow)
		delete advAudioWindow;

642
	ClearVolumeControls();
643 644
	ui->sources->clear();
	ui->scenes->clear();
J
jp9000 已提交
645

J
jp9000 已提交
646
	obs_enter_graphics();
647 648
	gs_vertexbuffer_destroy(box);
	gs_vertexbuffer_destroy(circle);
J
jp9000 已提交
649
	obs_leave_graphics();
J
jp9000 已提交
650

651
	obs_shutdown();
J
jp9000 已提交
652 653 654 655

	config_set_int(App()->GlobalConfig(), "General", "LastVersion",
			LIBOBS_API_VER);
	config_save(App()->GlobalConfig());
656 657
}

J
jp9000 已提交
658 659
void OBSBasic::SaveProject()
{
660 661 662 663 664 665
	char savePath[512];
	int ret = os_get_config_path(savePath, sizeof(savePath),
			"obs-studio/basic/scenes.json");
	if (ret <= 0)
		return;

J
jp9000 已提交
666 667 668
	Save(savePath);
}

J
jp9000 已提交
669
OBSScene OBSBasic::GetCurrentScene()
670
{
J
jp9000 已提交
671
	QListWidgetItem *item = ui->scenes->currentItem();
J
jp9000 已提交
672
	return item ? item->data(Qt::UserRole).value<OBSScene>() : nullptr;
673 674
}

675
OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
J
jp9000 已提交
676
{
J
jp9000 已提交
677
	return item ? item->data(Qt::UserRole).value<OBSSceneItem>() : nullptr;
J
jp9000 已提交
678 679
}

680 681 682 683 684
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
	return GetSceneItem(ui->sources->currentItem());
}

685 686 687 688 689
void OBSBasic::UpdateSources(OBSScene scene)
{
	ui->sources->clear();

	obs_scene_enum_items(scene,
690
			[] (obs_scene_t *scene, obs_sceneitem_t *item, void *p)
691 692
			{
				OBSBasic *window = static_cast<OBSBasic*>(p);
693
				window->InsertSceneItem(item);
J
jp9000 已提交
694 695

				UNUSED_PARAMETER(scene);
696 697 698 699
				return true;
			}, this);
}

700
void OBSBasic::InsertSceneItem(obs_sceneitem_t *item)
701
{
702
	obs_source_t *source = obs_sceneitem_get_source(item);
703
	const char   *name  = obs_source_get_name(source);
704 705 706 707 708 709

	QListWidgetItem *listItem = new QListWidgetItem(QT_UTF8(name));
	listItem->setData(Qt::UserRole,
			QVariant::fromValue(OBSSceneItem(item)));

	ui->sources->insertItem(0, listItem);
710 711 712
	ui->sources->setCurrentRow(0);

	/* if the source was just created, open properties dialog */
713 714 715 716
	if (sourceSceneRefs[source] == 0 && loaded)
		CreatePropertiesWindow(source);
}

717
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
J
John Bradley 已提交
718 719 720 721 722 723 724 725 726
{
	if (interaction)
		interaction->close();

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

727
void OBSBasic::CreatePropertiesWindow(obs_source_t *source)
728 729 730 731 732 733 734
{
	if (properties)
		properties->close();

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

737 738 739
/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
740
{
741
	const char *name  = obs_source_get_name(source);
742
	obs_scene_t *scene = obs_scene_from_source(source);
J
jp9000 已提交
743 744

	QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name));
J
jp9000 已提交
745
	item->setData(Qt::UserRole, QVariant::fromValue(OBSScene(scene)));
J
jp9000 已提交
746
	ui->scenes->addItem(item);
747

748
	signal_handler_t *handler = obs_source_get_signal_handler(source);
749
	signal_handler_connect(handler, "item_add",
J
jp9000 已提交
750
			OBSBasic::SceneItemAdded, this);
751
	signal_handler_connect(handler, "item_remove",
J
jp9000 已提交
752
			OBSBasic::SceneItemRemoved, this);
753 754 755
	signal_handler_connect(handler, "item_select",
			OBSBasic::SceneItemSelected, this);
	signal_handler_connect(handler, "item_deselect",
J
jp9000 已提交
756
			OBSBasic::SceneItemDeselected, this);
J
jp9000 已提交
757 758 759 760 761 762 763 764
	signal_handler_connect(handler, "item_move_up",
			OBSBasic::SceneItemMoveUp, this);
	signal_handler_connect(handler, "item_move_down",
			OBSBasic::SceneItemMoveDown, this);
	signal_handler_connect(handler, "item_move_top",
			OBSBasic::SceneItemMoveTop, this);
	signal_handler_connect(handler, "item_move_bottom",
			OBSBasic::SceneItemMoveBottom, this);
765 766
}

767
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
768
{
769
	const char *name = obs_source_get_name(source);
J
jp9000 已提交
770

J
jp9000 已提交
771 772 773
	QListWidgetItem *sel = ui->scenes->currentItem();
	QList<QListWidgetItem*> items = ui->scenes->findItems(QT_UTF8(name),
			Qt::MatchExactly);
J
jp9000 已提交
774

J
jp9000 已提交
775 776 777 778
	if (sel != nullptr) {
		if (items.contains(sel))
			ui->sources->clear();
		delete sel;
J
jp9000 已提交
779
	}
780 781
}

782
void OBSBasic::AddSceneItem(OBSSceneItem item)
783
{
784 785
	obs_scene_t  *scene  = obs_sceneitem_get_scene(item);
	obs_source_t *source = obs_sceneitem_get_source(item);
J
jp9000 已提交
786

787 788
	if (GetCurrentScene() == scene)
		InsertSceneItem(item);
J
jp9000 已提交
789 790

	sourceSceneRefs[source] = sourceSceneRefs[source] + 1;
791 792
}

793
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
794
{
795
	obs_scene_t *scene = obs_sceneitem_get_scene(item);
796

J
jp9000 已提交
797
	if (GetCurrentScene() == scene) {
B
BtbN 已提交
798
		for (int i = 0; i < ui->sources->count(); i++) {
J
jp9000 已提交
799 800
			QListWidgetItem *listItem = ui->sources->item(i);
			QVariant userData = listItem->data(Qt::UserRole);
J
jp9000 已提交
801

J
jp9000 已提交
802
			if (userData.value<OBSSceneItem>() == item) {
J
jp9000 已提交
803
				delete listItem;
J
jp9000 已提交
804 805
				break;
			}
806 807
		}
	}
J
jp9000 已提交
808

809
	obs_source_t *source = obs_sceneitem_get_source(item);
J
jp9000 已提交
810 811

	int scenes = sourceSceneRefs[source] - 1;
812 813
	sourceSceneRefs[source] = scenes;

J
jp9000 已提交
814 815 816 817
	if (scenes == 0) {
		obs_source_remove(source);
		sourceSceneRefs.erase(source);
	}
818 819
}

820
void OBSBasic::UpdateSceneSelection(OBSSource source)
821 822
{
	if (source) {
823
		obs_scene_t *scene = obs_scene_from_source(source);
824
		const char *name = obs_source_get_name(source);
J
jp9000 已提交
825

826 827 828
		if (!scene)
			return;

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

832 833 834 835 836
		if (items.count()) {
			sceneChanging = true;
			ui->scenes->setCurrentItem(items.first());
			sceneChanging = false;

J
jp9000 已提交
837
			UpdateSources(scene);
838
		}
J
jp9000 已提交
839
	}
840 841
}

J
jp9000 已提交
842 843 844 845 846 847 848 849 850 851 852 853 854 855
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);
	RenameListValues(ui->sources, newName, prevName);
856 857 858 859 860

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

863 864
void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
{
865
	if (!select || scene != GetCurrentScene())
866 867 868 869 870 871 872 873 874 875 876
		return;

	for (int i = 0; i < ui->sources->count(); i++) {
		QListWidgetItem *witem = ui->sources->item(i);
		QVariant data = witem->data(Qt::UserRole);
		if (!data.canConvert<OBSSceneItem>())
			continue;

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

877
		ui->sources->setCurrentItem(witem);
878 879 880 881
		break;
	}
}

J
jp9000 已提交
882
void OBSBasic::MoveSceneItem(OBSSceneItem item, obs_order_movement movement)
J
jp9000 已提交
883
{
J
jp9000 已提交
884
	OBSScene scene = obs_sceneitem_get_scene(item);
J
jp9000 已提交
885 886 887 888 889 890 891 892 893 894
	if (scene != GetCurrentScene())
		return;

	int curRow = ui->sources->currentRow();
	if (curRow == -1)
		return;

	QListWidgetItem *listItem = ui->sources->takeItem(curRow);

	switch (movement) {
J
jp9000 已提交
895
	case OBS_ORDER_MOVE_UP:
J
jp9000 已提交
896 897 898 899
		if (curRow > 0)
			curRow--;
		break;

J
jp9000 已提交
900
	case OBS_ORDER_MOVE_DOWN:
J
jp9000 已提交
901 902 903 904
		if (curRow < ui->sources->count())
			curRow++;
		break;

J
jp9000 已提交
905
	case OBS_ORDER_MOVE_TOP:
J
jp9000 已提交
906 907 908
		curRow = 0;
		break;

J
jp9000 已提交
909
	case OBS_ORDER_MOVE_BOTTOM:
J
jp9000 已提交
910 911 912 913 914 915 916 917
		curRow = ui->sources->count();
		break;
	}

	ui->sources->insertItem(curRow, listItem);
	ui->sources->setCurrentRow(curRow);
}

918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
void OBSBasic::ActivateAudioSource(OBSSource source)
{
	VolControl *vol = new VolControl(source);

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

937
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
J
jp9000 已提交
938
{
939
	const char *name  = obs_source_get_name(source);
940 941 942

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

944
	QMessageBox remove_source(this);
945 946 947
	remove_source.setText(text);
	QAbstractButton *Yes = remove_source.addButton(QTStr("Yes"),
			QMessageBox::YesRole);
J
Jkoan 已提交
948 949 950 951 952 953
	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();
954
}
J
jp9000 已提交
955

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

P
Palana 已提交
958 959 960 961 962
#ifdef UPDATE_SPARKLE
void init_sparkle_updater(bool update_to_undeployed);
void trigger_sparkle_update();
#endif

J
jp9000 已提交
963 964
void OBSBasic::TimedCheckForUpdates()
{
P
Palana 已提交
965 966 967 968
#ifdef UPDATE_SPARKLE
	init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
				"UpdateToUndeployed"));
#else
J
jp9000 已提交
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
	long long lastUpdate = config_get_int(App()->GlobalConfig(), "General",
			"LastUpdateCheck");
	uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
			"LastVersion");

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

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

	if (secs > UPDATE_CHECK_INTERVAL)
		CheckForUpdates();
P
Palana 已提交
985
#endif
J
jp9000 已提交
986 987 988 989
}

void OBSBasic::CheckForUpdates()
{
P
Palana 已提交
990 991 992
#ifdef UPDATE_SPARKLE
	trigger_sparkle_update();
#else
J
jp9000 已提交
993 994
	ui->actionCheckForUpdates->setEnabled(false);

995 996 997 998 999 1000 1001
	string versionString("obs-basic ");
	versionString += App()->GetVersionString();

	QNetworkRequest request;
	request.setUrl(QUrl("https://obsproject.com/obs2_update/basic.json"));
	request.setRawHeader("User-Agent", versionString.c_str());

F
fryshorts 已提交
1002 1003
	QNetworkReply *reply = networkManager.get(request);
	connect(reply, SIGNAL(finished()),
J
jp9000 已提交
1004
			this, SLOT(updateFileFinished()));
P
Palana 已提交
1005
#endif
J
jp9000 已提交
1006 1007
}

J
jp9000 已提交
1008 1009
#ifdef __APPLE__
#define VERSION_ENTRY "mac"
J
jp9000 已提交
1010 1011
#elif _WIN32
#define VERSION_ENTRY "windows"
J
jp9000 已提交
1012 1013 1014 1015
#else
#define VERSION_ENTRY "other"
#endif

J
jp9000 已提交
1016 1017 1018 1019
void OBSBasic::updateFileFinished()
{
	ui->actionCheckForUpdates->setEnabled(true);

F
fryshorts 已提交
1020 1021
	QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
	if (!reply || reply->error()) {
J
jp9000 已提交
1022
		blog(LOG_WARNING, "Update check failed: %s",
F
fryshorts 已提交
1023
				QT_TO_UTF8(reply->errorString()));
J
jp9000 已提交
1024 1025 1026
		return;
	}

F
fryshorts 已提交
1027 1028
	QByteArray raw = reply->readAll();
	if (!raw.length())
J
jp9000 已提交
1029 1030
		return;

F
fryshorts 已提交
1031 1032
	obs_data_t *returnData  = obs_data_create_from_json(raw.constData());
	obs_data_t *versionData = obs_data_get_obj(returnData, VERSION_ENTRY);
J
jp9000 已提交
1033 1034 1035
	const char *description = obs_data_get_string(returnData,
			"description");
	const char *download    = obs_data_get_string(versionData, "download");
J
jp9000 已提交
1036 1037

	if (returnData && versionData && description && download) {
J
jp9000 已提交
1038 1039 1040
		long major   = obs_data_get_int(versionData, "major");
		long minor   = obs_data_get_int(versionData, "minor");
		long patch   = obs_data_get_int(versionData, "patch");
J
jp9000 已提交
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
		long version = MAKE_SEMANTIC_VERSION(major, minor, patch);

		blog(LOG_INFO, "Update check: latest version is: %ld.%ld.%ld",
				major, minor, patch);

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

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

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

			long long t = (long long)time(nullptr);
			config_set_int(App()->GlobalConfig(), "General",
					"LastUpdateCheck", t);
			config_save(App()->GlobalConfig());
		}
	} else {
		blog(LOG_WARNING, "Bad JSON file received from server");
	}

	obs_data_release(versionData);
	obs_data_release(returnData);
J
jp9000 已提交
1072

F
fryshorts 已提交
1073
	reply->deleteLater();
J
jp9000 已提交
1074 1075
}

1076 1077 1078 1079
void OBSBasic::RemoveSelectedScene()
{
	OBSScene scene = GetCurrentScene();
	if (scene) {
1080
		obs_source_t *source = obs_scene_get_source(scene);
1081 1082 1083 1084 1085 1086 1087 1088 1089
		if (QueryRemoveSource(source))
			obs_source_remove(source);
	}
}

void OBSBasic::RemoveSelectedSceneItem()
{
	OBSSceneItem item = GetCurrentSceneItem();
	if (item) {
1090
		obs_source_t *source = obs_sceneitem_get_source(item);
1091
		if (QueryRemoveSource(source))
J
jp9000 已提交
1092 1093 1094 1095
			obs_sceneitem_remove(item);
	}
}

1096 1097
/* OBS Callbacks */

1098
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
1099 1100 1101
{
	OBSBasic *window = static_cast<OBSBasic*>(data);

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

1104 1105
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
1106 1107
}

1108
void OBSBasic::SceneItemRemoved(void *data, calldata_t *params)
1109
{
1110
	OBSBasic *window = static_cast<OBSBasic*>(data);
1111

1112
	obs_sceneitem_t *item = (obs_sceneitem_t*)calldata_ptr(params, "item");
1113

1114 1115
	QMetaObject::invokeMethod(window, "RemoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
1116 1117
}

1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
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));
}

1142
void OBSBasic::SourceAdded(void *data, calldata_t *params)
1143
{
J
jp9000 已提交
1144
	OBSBasic *window = static_cast<OBSBasic*>(data);
1145
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
1146

1147
	if (obs_scene_from_source(source) != NULL)
J
jp9000 已提交
1148
		QMetaObject::invokeMethod(window,
1149 1150
				"AddScene",
				Q_ARG(OBSSource, OBSSource(source)));
1151 1152
}

1153
void OBSBasic::SourceRemoved(void *data, calldata_t *params)
1154
{
1155
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
1156

1157
	if (obs_scene_from_source(source) != NULL)
1158 1159 1160
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
1161 1162
}

1163
void OBSBasic::SourceActivated(void *data, calldata_t *params)
1164
{
1165
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
1166 1167 1168 1169 1170 1171 1172 1173
	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)));
}

1174
void OBSBasic::SourceDeactivated(void *data, calldata_t *params)
1175
{
1176
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
1177 1178 1179 1180 1181 1182 1183 1184
	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)));
}

1185
void OBSBasic::SourceRenamed(void *data, calldata_t *params)
J
jp9000 已提交
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195
{
	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)));
}

1196
void OBSBasic::ChannelChanged(void *data, calldata_t *params)
1197
{
1198
	obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
1199
	uint32_t channel = (uint32_t)calldata_int(params, "channel");
1200 1201

	if (channel == 0)
1202 1203 1204
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"UpdateSceneSelection",
				Q_ARG(OBSSource, OBSSource(source)));
1205 1206
}

1207 1208 1209 1210 1211
void OBSBasic::DrawBackdrop(float cx, float cy)
{
	if (!box)
		return;

1212 1213 1214
	gs_effect_t    *solid = obs_get_solid_effect();
	gs_eparam_t    *color = gs_effect_get_param_by_name(solid, "color");
	gs_technique_t *tech  = gs_effect_get_technique(solid, "Solid");
1215 1216 1217

	vec4 colorVal;
	vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f);
1218
	gs_effect_set_vec4(color, &colorVal);
1219

1220 1221
	gs_technique_begin(tech);
	gs_technique_begin_pass(tech, 0);
1222 1223 1224 1225 1226 1227 1228 1229
	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();
1230 1231
	gs_technique_end_pass(tech);
	gs_technique_end(tech);
1232 1233 1234 1235

	gs_load_vertexbuffer(nullptr);
}

1236 1237
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
1238
	OBSBasic *window = static_cast<OBSBasic*>(data);
1239 1240 1241 1242
	obs_video_info ovi;

	obs_get_video_info(&ovi);

J
jp9000 已提交
1243 1244
	window->previewCX = int(window->previewScale * float(ovi.base_width));
	window->previewCY = int(window->previewScale * float(ovi.base_height));
1245 1246 1247

	gs_viewport_push();
	gs_projection_push();
1248 1249 1250

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

1251 1252
	gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
			-100.0f, 100.0f);
1253
	gs_set_viewport(window->previewX, window->previewY,
J
jp9000 已提交
1254
			window->previewCX, window->previewCY);
1255

1256 1257
	window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));

J
jp9000 已提交
1258
	obs_render_main_view();
1259
	gs_load_vertexbuffer(nullptr);
1260

1261 1262
	/* --------------------------------------- */

1263 1264 1265
	QSize previewSize = GetPixelSize(window->ui->preview);
	float right  = float(previewSize.width())  - window->previewX;
	float bottom = float(previewSize.height()) - window->previewY;
1266 1267 1268 1269

	gs_ortho(-window->previewX, right,
	         -window->previewY, bottom,
	         -100.0f, 100.0f);
1270
	gs_reset_viewport();
J
jp9000 已提交
1271 1272 1273

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

1274 1275
	/* --------------------------------------- */

1276 1277
	gs_projection_pop();
	gs_viewport_pop();
J
jp9000 已提交
1278 1279 1280

	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
1281 1282
}

1283
void OBSBasic::SceneItemMoveUp(void *data, calldata_t *params)
J
jp9000 已提交
1284
{
1285
	OBSSceneItem item = (obs_sceneitem_t*)calldata_ptr(params, "item");
J
jp9000 已提交
1286 1287 1288
	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"MoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)),
J
jp9000 已提交
1289
			Q_ARG(obs_order_movement, OBS_ORDER_MOVE_UP));
J
jp9000 已提交
1290 1291
}

1292
void OBSBasic::SceneItemMoveDown(void *data, calldata_t *params)
J
jp9000 已提交
1293
{
1294
	OBSSceneItem item = (obs_sceneitem_t*)calldata_ptr(params, "item");
J
jp9000 已提交
1295 1296 1297
	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"MoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)),
J
jp9000 已提交
1298
			Q_ARG(obs_order_movement, OBS_ORDER_MOVE_DOWN));
J
jp9000 已提交
1299 1300
}

1301
void OBSBasic::SceneItemMoveTop(void *data, calldata_t *params)
J
jp9000 已提交
1302
{
1303
	OBSSceneItem item = (obs_sceneitem_t*)calldata_ptr(params, "item");
J
jp9000 已提交
1304 1305 1306
	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"MoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)),
J
jp9000 已提交
1307
			Q_ARG(obs_order_movement, OBS_ORDER_MOVE_TOP));
J
jp9000 已提交
1308 1309
}

1310
void OBSBasic::SceneItemMoveBottom(void *data, calldata_t *params)
J
jp9000 已提交
1311
{
1312
	OBSSceneItem item = (obs_sceneitem_t*)calldata_ptr(params, "item");
J
jp9000 已提交
1313 1314 1315
	QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
			"MoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)),
J
jp9000 已提交
1316
			Q_ARG(obs_order_movement, OBS_ORDER_MOVE_BOTTOM));
J
jp9000 已提交
1317 1318
}

1319 1320
/* Main class functions */

1321
obs_service_t *OBSBasic::GetService()
1322 1323 1324 1325 1326 1327
{
	if (!service)
		service = obs_service_create("rtmp_common", NULL, NULL);
	return service;
}

1328
void OBSBasic::SetService(obs_service_t *newService)
1329 1330 1331 1332 1333 1334 1335 1336
{
	if (newService) {
		if (service)
			obs_service_destroy(service);
		service = newService;
	}
}

1337 1338 1339 1340 1341 1342 1343
bool OBSBasic::StreamingActive()
{
	if (!outputHandler)
		return false;
	return outputHandler->StreamingActive();
}

1344 1345 1346 1347 1348 1349
#ifdef _WIN32
#define IS_WIN32 1
#else
#define IS_WIN32 0
#endif

1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370
static inline int AttemptToResetVideo(struct obs_video_info *ovi)
{
	int ret = obs_reset_video(ovi);
	if (ret == OBS_VIDEO_INVALID_PARAM) {
		struct obs_video_info new_params = *ovi;

		if (new_params.window_width == 0)
			new_params.window_width = 512;
		if (new_params.window_height == 0)
			new_params.window_height = 512;

		new_params.output_width  = new_params.window_width;
		new_params.output_height = new_params.window_height;
		new_params.base_width    = new_params.window_width;
		new_params.base_height   = new_params.window_height;
		ret = obs_reset_video(&new_params);
	}

	return ret;
}

1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
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;
}

1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401
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;
#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
		return VIDEO_FORMAT_BGRA;
}

1402
int OBSBasic::ResetVideo()
J
jp9000 已提交
1403 1404
{
	struct obs_video_info ovi;
1405
	int ret;
J
jp9000 已提交
1406

1407
	GetConfigFPS(ovi.fps_num, ovi.fps_den);
J
jp9000 已提交
1408

1409 1410 1411 1412 1413 1414 1415
	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 已提交
1416
	ovi.graphics_module = App()->GetRenderModule();
1417
	ovi.base_width     = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
1418
			"Video", "BaseCX");
1419
	ovi.base_height    = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
1420
			"Video", "BaseCY");
1421
	ovi.output_width   = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
1422
			"Video", "OutputCX");
1423
	ovi.output_height  = (uint32_t)config_get_uint(basicConfig,
J
jp9000 已提交
1424
			"Video", "OutputCY");
1425 1426 1427 1428 1429
	ovi.output_format  = GetVideoFormatFromName(colorFormat);
	ovi.colorspace     = astrcmpi(colorSpace, "601") == 0 ?
		VIDEO_CS_601 : VIDEO_CS_709;
	ovi.range          = astrcmpi(colorRange, "Full") == 0 ?
		VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
J
jp9000 已提交
1430 1431
	ovi.adapter        = 0;
	ovi.gpu_conversion = true;
1432
	ovi.scale_type     = GetScaleType(basicConfig);
1433

J
jp9000 已提交
1434
	QTToGSWindow(ui->preview->winId(), ovi.window);
J
jp9000 已提交
1435 1436

	//required to make opengl display stuff on osx(?)
J
jp9000 已提交
1437
	ResizePreview(ovi.base_width, ovi.base_height);
J
jp9000 已提交
1438

1439
	QSize size = GetPixelSize(ui->preview);
J
jp9000 已提交
1440 1441
	ovi.window_width  = size.width();
	ovi.window_height = size.height();
J
jp9000 已提交
1442

1443
	ret = AttemptToResetVideo(&ovi);
1444 1445
	if (IS_WIN32 && ret != OBS_VIDEO_SUCCESS) {
		/* Try OpenGL if DirectX fails on windows */
1446
		if (astrcmpi(ovi.graphics_module, DL_OPENGL) != 0) {
1447 1448 1449 1450
			blog(LOG_WARNING, "Failed to initialize obs video (%d) "
					  "with graphics_module='%s', retrying "
					  "with graphics_module='%s'",
					  ret, ovi.graphics_module,
1451 1452
					  DL_OPENGL);
			ovi.graphics_module = DL_OPENGL;
1453 1454 1455 1456
			ret = AttemptToResetVideo(&ovi);
		}
	}

1457 1458
	if (ret == OBS_VIDEO_SUCCESS)
		obs_add_draw_callback(OBSBasic::RenderMain, this);
1459

1460
	return ret;
J
jp9000 已提交
1461
}
J
jp9000 已提交
1462

1463
bool OBSBasic::ResetAudio()
J
jp9000 已提交
1464
{
J
jp9000 已提交
1465
	struct audio_output_info ai;
1466 1467 1468
	ai.name = "Main Audio Track";
	ai.format = AUDIO_FORMAT_FLOAT;

1469
	ai.samples_per_sec = config_get_uint(basicConfig, "Audio",
1470 1471
			"SampleRate");

1472
	const char *channelSetupStr = config_get_string(basicConfig,
1473 1474 1475 1476 1477 1478 1479
			"Audio", "ChannelSetup");

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

1480
	ai.buffer_ms = config_get_uint(basicConfig, "Audio", "BufferingTime");
J
jp9000 已提交
1481 1482

	return obs_reset_audio(&ai);
J
jp9000 已提交
1483 1484
}

J
jp9000 已提交
1485
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceName,
1486
		const char *deviceDesc, int channel)
J
jp9000 已提交
1487 1488 1489
{
	const char *deviceId = config_get_string(basicConfig, "Audio",
			deviceName);
1490 1491
	obs_source_t *source;
	obs_data_t *settings;
J
jp9000 已提交
1492 1493 1494 1495
	bool same = false;

	source = obs_get_output_source(channel);
	if (source) {
1496
		settings = obs_source_get_settings(source);
J
jp9000 已提交
1497
		const char *curId = obs_data_get_string(settings, "device_id");
J
jp9000 已提交
1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508

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

		obs_data_release(settings);
		obs_source_release(source);
	}

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

	if (!same && strcmp(deviceId, "disabled") != 0) {
1509
		obs_data_t *settings = obs_data_create();
J
jp9000 已提交
1510
		obs_data_set_string(settings, "device_id", deviceId);
J
jp9000 已提交
1511
		source = obs_source_create(OBS_SOURCE_TYPE_INPUT,
1512
				sourceId, deviceDesc, settings);
J
jp9000 已提交
1513 1514 1515 1516 1517 1518 1519 1520
		obs_data_release(settings);

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

void OBSBasic::ResetAudioDevices()
J
jp9000 已提交
1521
{
1522 1523 1524 1525 1526 1527 1528 1529 1530 1531
	ResetAudioDevice(App()->OutputAudioSource(), "DesktopDevice1",
			Str("Basic.DesktopDevice1"), 1);
	ResetAudioDevice(App()->OutputAudioSource(), "DesktopDevice2",
			Str("Basic.DesktopDevice2"), 2);
	ResetAudioDevice(App()->InputAudioSource(),  "AuxDevice1",
			Str("Basic.AuxDevice1"), 3);
	ResetAudioDevice(App()->InputAudioSource(),  "AuxDevice2",
			Str("Basic.AuxDevice2"), 4);
	ResetAudioDevice(App()->InputAudioSource(),  "AuxDevice3",
			Str("Basic.AuxDevice3"), 5);
J
jp9000 已提交
1532 1533
}

J
jp9000 已提交
1534
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
1535
{
1536
	QSize  targetSize;
J
jp9000 已提交
1537

1538
	/* resize preview panel to fix to the top section of the window */
1539
	targetSize = GetPixelSize(ui->preview);
1540
	GetScaleAndCenterPos(int(cx), int(cy),
1541 1542
			targetSize.width()  - PREVIEW_EDGE_SIZE * 2,
			targetSize.height() - PREVIEW_EDGE_SIZE * 2,
1543
			previewX, previewY, previewScale);
J
jp9000 已提交
1544

1545 1546 1547
	previewX += float(PREVIEW_EDGE_SIZE);
	previewY += float(PREVIEW_EDGE_SIZE);

J
jp9000 已提交
1548 1549 1550 1551 1552
	if (isVisible()) {
		if (resizeTimer)
			killTimer(resizeTimer);
		resizeTimer = startTimer(100);
	}
J
jp9000 已提交
1553 1554
}

J
jp9000 已提交
1555
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
1556
{
1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567
	if (outputHandler && outputHandler->Active()) {
		QMessageBox::StandardButton button = QMessageBox::question(
				this, QTStr("ConfirmExit.Title"),
				QTStr("ConfirmExit.Text"));

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

1568 1569 1570 1571
	QWidget::closeEvent(event);
	if (!event->isAccepted())
		return;

1572 1573 1574 1575 1576 1577 1578 1579 1580 1581
	/* Check all child dialogs and ensure they run their proper closeEvent
	 * methods before exiting the application.  Otherwise Qt doesn't send
	 * the proper QCloseEvent messages. */
	QList<QDialog*> childDialogs = this->findChildren<QDialog *>();
	if (!childDialogs.isEmpty()) {
		for (int i = 0; i < childDialogs.size(); ++i) {
			childDialogs.at(i)->close();
		}
	}

1582 1583 1584
	// remove draw callback in case our drawable surfaces go away before
	// the destructor gets called
	obs_remove_draw_callback(OBSBasic::RenderMain, this);
1585 1586
}

J
jp9000 已提交
1587
void OBSBasic::changeEvent(QEvent *event)
1588
{
J
jp9000 已提交
1589 1590
	/* TODO */
	UNUSED_PARAMETER(event);
1591 1592
}

J
jp9000 已提交
1593
void OBSBasic::resizeEvent(QResizeEvent *event)
1594
{
J
jp9000 已提交
1595 1596 1597 1598
	struct obs_video_info ovi;

	if (obs_get_video_info(&ovi))
		ResizePreview(ovi.base_width, ovi.base_height);
J
jp9000 已提交
1599 1600

	UNUSED_PARAMETER(event);
1601 1602
}

J
jp9000 已提交
1603 1604 1605 1606 1607 1608
void OBSBasic::timerEvent(QTimerEvent *event)
{
	if (event->timerId() == resizeTimer) {
		killTimer(resizeTimer);
		resizeTimer = 0;

1609
		QSize size = GetPixelSize(ui->preview);
J
jp9000 已提交
1610 1611 1612 1613
		obs_resize(size.width(), size.height());
	}
}

J
jp9000 已提交
1614
void OBSBasic::on_action_New_triggered()
1615
{
J
jp9000 已提交
1616
	/* TODO */
1617 1618
}

J
jp9000 已提交
1619
void OBSBasic::on_action_Open_triggered()
1620
{
J
jp9000 已提交
1621
	/* TODO */
1622 1623
}

J
jp9000 已提交
1624
void OBSBasic::on_action_Save_triggered()
1625
{
J
jp9000 已提交
1626
	/* TODO */
1627 1628
}

1629 1630 1631 1632 1633 1634 1635
void OBSBasic::on_actionShow_Recordings_triggered()
{
	const char *path = config_get_string(basicConfig,
			"SimpleOutput", "FilePath");
	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}

P
Palana 已提交
1636 1637 1638 1639 1640 1641 1642 1643
void OBSBasic::on_actionRemux_triggered()
{
	const char *path = config_get_string(basicConfig,
			"SimpleOutput", "FilePath");
	OBSRemux remux(path, this);
	remux.exec();
}

P
Palana 已提交
1644 1645 1646 1647 1648 1649
void OBSBasic::on_action_Settings_triggered()
{
	OBSBasicSettings settings(this);
	settings.exec();
}

J
jp9000 已提交
1650 1651 1652 1653 1654 1655 1656
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
	advAudioWindow = new OBSBasicAdvAudio(this);
	advAudioWindow->show();
	advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
}

1657 1658 1659 1660 1661
void OBSBasic::on_advAudioProps_clicked()
{
	on_actionAdvAudioProperties_triggered();
}

1662 1663
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
1664
{
1665
	obs_source_t *source = NULL;
J
jp9000 已提交
1666

1667 1668 1669 1670
	if (sceneChanging)
		return;

	if (current) {
1671
		obs_scene_t *scene;
J
jp9000 已提交
1672

1673
		scene = current->data(Qt::UserRole).value<OBSScene>();
1674
		source = obs_scene_get_source(scene);
1675 1676
	}

1677
	/* TODO: allow transitions */
1678
	obs_set_output_source(0, source);
1679 1680

	UNUSED_PARAMETER(prev);
1681 1682
}

J
jp9000 已提交
1683 1684
void OBSBasic::EditSceneName()
{
1685 1686 1687 1688 1689 1690
	QListWidgetItem *item = ui->scenes->currentItem();
	Qt::ItemFlags flags   = item->flags();

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

J
jp9000 已提交
1693
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
1694
{
J
jp9000 已提交
1695 1696
	QListWidgetItem *item = ui->scenes->itemAt(pos);

1697
	QMenu popup(this);
J
jp9000 已提交
1698 1699 1700
	popup.addAction(QTStr("Add"),
			this, SLOT(on_actionAddScene_triggered()));

P
Palana 已提交
1701 1702 1703 1704
	if (item) {
		popup.addSeparator();
		popup.addAction(QTStr("Rename"),
				this, SLOT(EditSceneName()));
J
jp9000 已提交
1705
		popup.addAction(QTStr("Remove"),
1706 1707
				this, SLOT(RemoveSelectedScene()),
				DeleteKeys.front());
P
Palana 已提交
1708
	}
J
jp9000 已提交
1709 1710

	popup.exec(QCursor::pos());
1711 1712
}

J
jp9000 已提交
1713
void OBSBasic::on_actionAddScene_triggered()
1714
{
1715
	string name;
S
Socapex 已提交
1716
	QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
P
Palana 已提交
1717 1718 1719

	int i = 1;
	QString placeHolderText = format.arg(i);
1720
	obs_source_t *source = nullptr;
P
Palana 已提交
1721 1722
	while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
		obs_source_release(source);
P
Palana 已提交
1723
		placeHolderText = format.arg(++i);
P
Palana 已提交
1724
	}
S
Socapex 已提交
1725

J
jp9000 已提交
1726
	bool accepted = NameDialog::AskForName(this,
1727 1728
			QTStr("Basic.Main.AddSceneDlg.Title"),
			QTStr("Basic.Main.AddSceneDlg.Text"),
S
Socapex 已提交
1729 1730
			name,
			placeHolderText);
1731

J
jp9000 已提交
1732
	if (accepted) {
J
jp9000 已提交
1733 1734
		if (name.empty()) {
			QMessageBox::information(this,
1735 1736
					QTStr("NoNameEntered.Title"),
					QTStr("NoNameEntered.Text"));
J
jp9000 已提交
1737 1738 1739 1740
			on_actionAddScene_triggered();
			return;
		}

1741
		obs_source_t *source = obs_get_source_by_name(name.c_str());
1742
		if (source) {
J
jp9000 已提交
1743
			QMessageBox::information(this,
1744 1745
					QTStr("NameExists.Title"),
					QTStr("NameExists.Text"));
1746 1747

			obs_source_release(source);
J
jp9000 已提交
1748
			on_actionAddScene_triggered();
1749 1750 1751
			return;
		}

1752
		obs_scene_t *scene = obs_scene_create(name.c_str());
1753
		source = obs_scene_get_source(scene);
1754
		obs_add_source(source);
1755
		obs_scene_release(scene);
1756 1757

		obs_set_output_source(0, source);
1758
	}
1759 1760
}

J
jp9000 已提交
1761
void OBSBasic::on_actionRemoveScene_triggered()
1762
{
1763
	OBSScene     scene  = GetCurrentScene();
1764
	obs_source_t *source = obs_scene_get_source(scene);
1765 1766 1767

	if (source && QueryRemoveSource(source))
		obs_source_remove(source);
1768 1769
}

J
jp9000 已提交
1770
void OBSBasic::on_actionSceneProperties_triggered()
1771
{
J
jp9000 已提交
1772
	/* TODO */
1773 1774
}

J
jp9000 已提交
1775
void OBSBasic::on_actionSceneUp_triggered()
1776
{
J
jp9000 已提交
1777
	/* TODO */
1778 1779
}

J
jp9000 已提交
1780
void OBSBasic::on_actionSceneDown_triggered()
1781
{
J
jp9000 已提交
1782
	/* TODO */
1783 1784
}

1785 1786
void OBSBasic::on_sources_currentItemChanged(QListWidgetItem *current,
		QListWidgetItem *prev)
1787
{
1788
	auto select_one = [] (obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
1789 1790
			void *param)
	{
1791
		obs_sceneitem_t *selectedItem =
J
jp9000 已提交
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802
			*reinterpret_cast<OBSSceneItem*>(param);
		obs_sceneitem_select(item, (selectedItem == item));

		UNUSED_PARAMETER(scene);
		return true;
	};

	if (!current)
		return;

	OBSSceneItem item = current->data(Qt::UserRole).value<OBSSceneItem>();
1803
	obs_source_t *source = obs_sceneitem_get_source(item);
1804 1805 1806
	if ((obs_source_get_output_flags(source) & OBS_SOURCE_VIDEO) == 0)
		return;

J
jp9000 已提交
1807 1808
	obs_scene_enum_items(GetCurrentScene(), select_one, &item);

1809
	UNUSED_PARAMETER(prev);
1810 1811
}

J
jp9000 已提交
1812 1813
void OBSBasic::EditSceneItemName()
{
1814 1815 1816 1817 1818 1819
	QListWidgetItem *item = ui->sources->currentItem();
	Qt::ItemFlags flags   = item->flags();

	item->setFlags(flags | Qt::ItemIsEditable);
	ui->sources->editItem(item);
	item->setFlags(flags);
J
jp9000 已提交
1820 1821
}

J
jp9000 已提交
1822
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
1823
{
J
jp9000 已提交
1824 1825
	QListWidgetItem *item = ui->sources->itemAt(pos);

1826
	QMenu popup(this);
J
jp9000 已提交
1827 1828 1829 1830 1831 1832 1833 1834
	QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
	if (addSourceMenu)
		popup.addMenu(addSourceMenu);

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

J
John Bradley 已提交
1835
		OBSSceneItem sceneItem = GetSceneItem(item);
1836
		obs_source_t *source = obs_sceneitem_get_source(sceneItem);
J
John Bradley 已提交
1837 1838
		QAction *action;

J
jp9000 已提交
1839 1840
		popup.addAction(QTStr("Rename"), this,
				SLOT(EditSceneItemName()));
1841 1842
		popup.addAction(QTStr("Remove"), this,
				SLOT(on_actionRemoveSource_triggered()),
1843
				DeleteKeys.front());
J
jp9000 已提交
1844 1845
		popup.addSeparator();
		popup.addMenu(ui->orderMenu);
J
jp9000 已提交
1846 1847
		popup.addMenu(ui->transformMenu);
		popup.addSeparator();
J
John Bradley 已提交
1848 1849 1850 1851 1852 1853 1854

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

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

J
jp9000 已提交
1855 1856 1857 1858 1859
		popup.addAction(QTStr("Properties"), this,
				SLOT(on_actionSourceProperties_triggered()));
	}

	popup.exec(QCursor::pos());
1860 1861
}

P
Palana 已提交
1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873
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 已提交
1874
void OBSBasic::AddSource(const char *id)
1875
{
1876 1877 1878 1879
	if (id && *id) {
		OBSBasicSourceSelect sourceSelect(this, id);
		sourceSelect.exec();
	}
1880 1881
}

1882
QMenu *OBSBasic::CreateAddSourcePopupMenu()
1883
{
1884
	const char *type;
J
jp9000 已提交
1885 1886
	bool foundValues = false;
	size_t idx = 0;
1887

1888
	QMenu *popup = new QMenu(QTStr("Add"), this);
J
jp9000 已提交
1889
	while (obs_enum_input_types(idx++, &type)) {
1890
		const char *name = obs_source_get_display_name(
1891
				OBS_SOURCE_TYPE_INPUT, type);
1892

1893 1894 1895
		if (strcmp(type, "scene") == 0)
			continue;

J
jp9000 已提交
1896 1897
		QAction *popupItem = new QAction(QT_UTF8(name), this);
		popupItem->setData(QT_UTF8(type));
1898 1899 1900
		connect(popupItem, SIGNAL(triggered(bool)),
				this, SLOT(AddSourceFromAction()));
		popup->addAction(popupItem);
1901

J
jp9000 已提交
1902
		foundValues = true;
1903 1904
	}

1905 1906 1907
	if (!foundValues) {
		delete popup;
		popup = nullptr;
1908
	}
1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934

	return popup;
}

void OBSBasic::AddSourceFromAction()
{
	QAction *action = qobject_cast<QAction*>(sender());
	if (!action)
		return;

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

void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
{
	if (!GetCurrentScene()) {
		// Tell the user he needs a scene first (help beginners).
		QMessageBox::information(this,
				QTStr("Basic.Main.AddSourceHelp.Title"),
				QTStr("Basic.Main.AddSourceHelp.Text"));
		return;
	}

	QPointer<QMenu> popup = CreateAddSourcePopupMenu();
	if (popup)
		popup->exec(pos);
1935 1936
}

J
jp9000 已提交
1937
void OBSBasic::on_actionAddSource_triggered()
1938
{
J
jp9000 已提交
1939
	AddSourcePopupMenu(QCursor::pos());
1940 1941
}

J
jp9000 已提交
1942
void OBSBasic::on_actionRemoveSource_triggered()
1943
{
1944
	OBSSceneItem item   = GetCurrentSceneItem();
1945
	obs_source_t *source = obs_sceneitem_get_source(item);
1946 1947

	if (source && QueryRemoveSource(source))
J
jp9000 已提交
1948
		obs_sceneitem_remove(item);
1949 1950
}

J
John Bradley 已提交
1951 1952 1953 1954 1955 1956 1957 1958 1959
void OBSBasic::on_actionInteract_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
	OBSSource source = obs_sceneitem_get_source(item);

	if (source)
		CreateInteractionWindow(source);
}

J
jp9000 已提交
1960
void OBSBasic::on_actionSourceProperties_triggered()
1961
{
1962
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
1963
	OBSSource source = obs_sceneitem_get_source(item);
1964

1965 1966
	if (source)
		CreatePropertiesWindow(source);
1967 1968
}

J
jp9000 已提交
1969
void OBSBasic::on_actionSourceUp_triggered()
1970
{
J
jp9000 已提交
1971
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
1972
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
1973
}
J
jp9000 已提交
1974

J
jp9000 已提交
1975
void OBSBasic::on_actionSourceDown_triggered()
1976
{
J
jp9000 已提交
1977
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
1978
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
1979 1980
}

J
jp9000 已提交
1981 1982 1983
void OBSBasic::on_actionMoveUp_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
1984
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
J
jp9000 已提交
1985 1986 1987 1988 1989
}

void OBSBasic::on_actionMoveDown_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
1990
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
J
jp9000 已提交
1991 1992 1993 1994 1995
}

void OBSBasic::on_actionMoveToTop_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
1996
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_TOP);
J
jp9000 已提交
1997 1998 1999 2000 2001
}

void OBSBasic::on_actionMoveToBottom_triggered()
{
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
2002
	obs_sceneitem_set_order(item, OBS_ORDER_MOVE_BOTTOM);
J
jp9000 已提交
2003 2004
}

2005
static BPtr<char> ReadLogFile(const char *log)
J
jp9000 已提交
2006
{
2007 2008 2009
	char logDir[512];
	if (os_get_config_path(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
		return nullptr;
J
jp9000 已提交
2010 2011 2012 2013 2014

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

2015
	BPtr<char> file = os_quick_read_utf8_file(path.c_str());
J
jp9000 已提交
2016 2017 2018 2019 2020 2021 2022 2023
	if (!file)
		blog(LOG_WARNING, "Failed to read log file %s", path.c_str());

	return file;
}

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

2026
	if (!fileString)
J
jp9000 已提交
2027 2028
		return;

2029
	if (!*fileString)
J
jp9000 已提交
2030 2031 2032 2033
		return;

	ui->menuLogFiles->setEnabled(false);

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

2037 2038 2039
	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 已提交
2040

2041
	obs_data_set_string(content.get(), "content", fileString);
J
jp9000 已提交
2042

2043 2044 2045 2046 2047 2048 2049 2050 2051 2052
	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 已提交
2053
	if (!json) {
2054 2055 2056 2057
		blog(LOG_ERROR, "Failed to get JSON data for log upload");
		return;
	}

F
fryshorts 已提交
2058 2059 2060 2061 2062 2063 2064 2065
	QBuffer *postData = new QBuffer();
	postData->setData(json, (int) strlen(json));

	QNetworkRequest postReq(QUrl("https://api.github.com/gists"));
	postReq.setHeader(QNetworkRequest::ContentTypeHeader,
			"application/json");

	QNetworkReply *reply = networkManager.post(postReq, postData);
J
jp9000 已提交
2066

F
fryshorts 已提交
2067 2068 2069 2070
	/* set the reply as parent, so the buffer is deleted with the reply */
	postData->setParent(reply);

	connect(reply, SIGNAL(finished()),
J
jp9000 已提交
2071 2072 2073
			this, SLOT(logUploadFinished()));
}

P
Palana 已提交
2074 2075
void OBSBasic::on_actionShowLogs_triggered()
{
2076 2077 2078 2079
	char logDir[512];
	if (os_get_config_path(logDir, sizeof(logDir), "obs-studio/logs") <= 0)
		return;

P
Palana 已提交
2080 2081 2082 2083
	QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir));
	QDesktopServices::openUrl(url);
}

J
jp9000 已提交
2084 2085 2086 2087 2088 2089 2090 2091 2092 2093
void OBSBasic::on_actionUploadCurrentLog_triggered()
{
	UploadLog(App()->GetCurrentLog());
}

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

J
jp9000 已提交
2094 2095 2096 2097 2098
void OBSBasic::on_actionCheckForUpdates_triggered()
{
	CheckForUpdates();
}

J
jp9000 已提交
2099 2100 2101 2102
void OBSBasic::logUploadFinished()
{
	ui->menuLogFiles->setEnabled(true);

F
fryshorts 已提交
2103 2104
	QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
	if (!reply || reply->error()) {
J
jp9000 已提交
2105 2106
		QMessageBox::information(this,
				QTStr("LogReturnDialog.ErrorUploadingLog"),
F
fryshorts 已提交
2107
				reply->errorString());
J
jp9000 已提交
2108 2109 2110
		return;
	}

F
fryshorts 已提交
2111 2112
	QByteArray raw = reply->readAll();
	if (!raw.length())
J
jp9000 已提交
2113 2114
		return;

F
fryshorts 已提交
2115 2116
	obs_data_t *returnData = obs_data_create_from_json(raw.constData());
	QString logURL         = obs_data_get_string(returnData, "html_url");
J
jp9000 已提交
2117 2118 2119 2120
	obs_data_release(returnData);

	OBSLogReply logDialog(this, logURL);
	logDialog.exec();
J
jp9000 已提交
2121

F
fryshorts 已提交
2122
	reply->deleteLater();
J
jp9000 已提交
2123 2124
}

J
jp9000 已提交
2125
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
2126
		obs_source_t *source, const string &name)
2127
{
2128 2129 2130 2131
	const char *prevName = obs_source_get_name(source);
	if (name == prevName)
		return;

2132
	obs_source_t    *foundSource = obs_get_source_by_name(name.c_str());
2133
	QListWidgetItem *listItem    = listWidget->currentItem();
2134

2135
	if (foundSource || name.empty()) {
2136
		listItem->setText(QT_UTF8(prevName));
2137

2138
		if (foundSource) {
2139 2140 2141 2142 2143 2144 2145 2146 2147
			QMessageBox::information(parent,
				QTStr("NameExists.Title"),
				QTStr("NameExists.Text"));
		} else if (name.empty()) {
			QMessageBox::information(parent,
				QTStr("NoNameEntered.Title"),
				QTStr("NoNameEntered.Text"));
		}

2148 2149 2150
		obs_source_release(foundSource);
	} else {
		listItem->setText(QT_UTF8(name.c_str()));
2151
		obs_source_set_name(source, name.c_str());
2152 2153 2154
	}
}

J
jp9000 已提交
2155 2156 2157 2158 2159
void OBSBasic::SceneNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSScene  scene = GetCurrentScene();
	QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
2160
	string    text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
2161 2162 2163 2164

	if (!scene)
		return;

2165
	obs_source_t *source = obs_scene_get_source(scene);
2166
	RenameListItem(this, ui->scenes, source, text);
J
jp9000 已提交
2167 2168 2169 2170 2171 2172 2173 2174 2175

	UNUSED_PARAMETER(endHint);
}

void OBSBasic::SceneItemNameEdited(QWidget *editor,
		QAbstractItemDelegate::EndEditHint endHint)
{
	OBSSceneItem item  = GetCurrentSceneItem();
	QLineEdit    *edit = qobject_cast<QLineEdit*>(editor);
2176
	string       text  = QT_TO_UTF8(edit->text().trimmed());
J
jp9000 已提交
2177 2178 2179 2180

	if (!item)
		return;

2181
	obs_source_t *source = obs_sceneitem_get_source(item);
2182
	RenameListItem(this, ui->sources, source, text);
J
jp9000 已提交
2183 2184 2185 2186

	UNUSED_PARAMETER(endHint);
}

2187
void OBSBasic::StreamingStart()
2188
{
2189
	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
J
jp9000 已提交
2190
	ui->streamButton->setEnabled(true);
J
jp9000 已提交
2191
	ui->statusbar->StreamStarted(outputHandler->streamOutput);
2192 2193
}

2194
void OBSBasic::StreamingStop(int code)
2195
{
2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210
	const char *errorMessage;

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

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

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

2211
	default:
2212 2213 2214 2215 2216 2217 2218 2219 2220 2221
	case OBS_OUTPUT_ERROR:
		errorMessage = Str("Output.ConnectFail.Error");
		break;

	case OBS_OUTPUT_DISCONNECTED:
		/* doesn't happen if output is set to reconnect.  note that
		 * reconnects are handled in the output, not in the UI */
		errorMessage = Str("Output.ConnectFail.Disconnected");
	}

J
jp9000 已提交
2222
	ui->statusbar->StreamStopped();
2223

2224
	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
J
jp9000 已提交
2225
	ui->streamButton->setEnabled(true);
2226 2227 2228 2229 2230

	if (code != OBS_OUTPUT_SUCCESS)
		QMessageBox::information(this,
				QTStr("Output.ConnectFail.Title"),
				QT_UTF8(errorMessage));
J
jp9000 已提交
2231 2232
}

P
Palana 已提交
2233 2234
void OBSBasic::RecordingStart()
{
J
jp9000 已提交
2235
	ui->statusbar->RecordingStarted(outputHandler->fileOutput);
P
Palana 已提交
2236 2237
}

2238
void OBSBasic::RecordingStop()
2239
{
P
Palana 已提交
2240
	ui->statusbar->RecordingStopped();
2241 2242
	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
}
2243

2244 2245
void OBSBasic::on_streamButton_clicked()
{
J
jp9000 已提交
2246 2247
	SaveProject();

J
jp9000 已提交
2248 2249
	if (outputHandler->StreamingActive()) {
		outputHandler->StopStreaming();
2250
	} else {
J
jp9000 已提交
2251
		if (outputHandler->StartStreaming(service)) {
2252 2253 2254 2255 2256 2257 2258 2259 2260
			ui->streamButton->setEnabled(false);
			ui->streamButton->setText(
					QTStr("Basic.Main.Connecting"));
		}
	}
}

void OBSBasic::on_recordButton_clicked()
{
J
jp9000 已提交
2261 2262
	SaveProject();

J
jp9000 已提交
2263 2264
	if (outputHandler->RecordingActive()) {
		outputHandler->StopRecording();
2265 2266
	} else {

J
jp9000 已提交
2267
		if (outputHandler->StartRecording()) {
2268 2269 2270
			ui->recordButton->setText(
					QTStr("Basic.Main.StopRecording"));
		}
J
jp9000 已提交
2271 2272 2273
	}
}

J
jp9000 已提交
2274
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
2275
{
2276 2277
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
2278
}
2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342

void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const
{
	const char *val = config_get_string(basicConfig, "Video", "FPSCommon");

	if (strcmp(val, "10") == 0) {
		num = 10;
		den = 1;
	} else if (strcmp(val, "20") == 0) {
		num = 20;
		den = 1;
	} else if (strcmp(val, "25") == 0) {
		num = 25;
		den = 1;
	} else if (strcmp(val, "29.97") == 0) {
		num = 30000;
		den = 1001;
	} else if (strcmp(val, "48") == 0) {
		num = 48;
		den = 1;
	} else if (strcmp(val, "59.94") == 0) {
		num = 60000;
		den = 1001;
	} else if (strcmp(val, "60") == 0) {
		num = 60;
		den = 1;
	} else {
		num = 30;
		den = 1;
	}
}

void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const
{
	num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSInt");
	den = 1;
}

void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const
{
	num = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNum");
	den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSDen");
}

void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const
{
	num = 1000000000;
	den = (uint32_t)config_get_uint(basicConfig, "Video", "FPSNS");
}

void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const
{
	uint32_t type = config_get_uint(basicConfig, "Video", "FPSType");

	if (type == 1) //"Integer"
		GetFPSInteger(num, den);
	else if (type == 2) //"Fraction"
		GetFPSFraction(num, den);
	else if (false) //"Nanoseconds", currently not implemented
		GetFPSNanoseconds(num, den);
	else
		GetFPSCommon(num, den);
}

2343
config_t *OBSBasic::Config() const
2344 2345 2346
{
	return basicConfig;
}
J
jp9000 已提交
2347 2348 2349

void OBSBasic::on_actionEditTransform_triggered()
{
2350 2351 2352
	if (transformWindow)
		transformWindow->close();

J
jp9000 已提交
2353 2354
	transformWindow = new OBSBasicTransform(this);
	transformWindow->show();
2355
	transformWindow->setAttribute(Qt::WA_DeleteOnClose, true);
J
jp9000 已提交
2356 2357 2358 2359
}

void OBSBasic::on_actionResetTransform_triggered()
{
2360
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
J
jp9000 已提交
2361 2362 2363 2364
	{
		if (!obs_sceneitem_selected(item))
			return true;

2365
		obs_transform_info info;
J
jp9000 已提交
2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382
		vec2_set(&info.pos, 0.0f, 0.0f);
		vec2_set(&info.scale, 1.0f, 1.0f);
		info.rot = 0.0f;
		info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
		info.bounds_type = OBS_BOUNDS_NONE;
		info.bounds_alignment = OBS_ALIGN_CENTER;
		vec2_set(&info.bounds, 0.0f, 0.0f);
		obs_sceneitem_set_info(item, &info);

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

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

2383
static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
J
jp9000 已提交
2384 2385 2386 2387 2388
{
	matrix4 boxTransform;
	obs_sceneitem_get_box_transform(item, &boxTransform);

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

2391
	auto GetMinPos = [&] (float x, float y)
J
jp9000 已提交
2392 2393 2394 2395
	{
		vec3 pos;
		vec3_set(&pos, x, y, 0.0f);
		vec3_transform(&pos, &pos, &boxTransform);
2396 2397
		vec3_min(&tl, &tl, &pos);
		vec3_max(&br, &br, &pos);
J
jp9000 已提交
2398 2399
	};

2400 2401 2402 2403 2404 2405
	GetMinPos(0.0f, 0.0f);
	GetMinPos(1.0f, 0.0f);
	GetMinPos(0.0f, 1.0f);
	GetMinPos(1.0f, 1.0f);
}

2406
static vec3 GetItemTL(obs_sceneitem_t *item)
2407 2408 2409
{
	vec3 tl, br;
	GetItemBox(item, tl, br);
J
jp9000 已提交
2410 2411 2412
	return tl;
}

2413
static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
J
jp9000 已提交
2414 2415 2416 2417
{
	vec3 newTL;
	vec2 pos;

J
jp9000 已提交
2418
	obs_sceneitem_get_pos(item, &pos);
J
jp9000 已提交
2419 2420 2421
	newTL = GetItemTL(item);
	pos.x += tl.x - newTL.x;
	pos.y += tl.y - newTL.y;
J
jp9000 已提交
2422
	obs_sceneitem_set_pos(item, &pos);
J
jp9000 已提交
2423 2424
}

2425
static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
2426 2427 2428 2429 2430 2431 2432 2433 2434
		void *param)
{
	if (!obs_sceneitem_selected(item))
		return true;

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

	vec3 tl = GetItemTL(item);

J
jp9000 已提交
2435
	rot += obs_sceneitem_get_rot(item);
J
jp9000 已提交
2436 2437
	if (rot >= 360.0f)       rot -= 360.0f;
	else if (rot <= -360.0f) rot += 360.0f;
J
jp9000 已提交
2438
	obs_sceneitem_set_rot(item, rot);
J
jp9000 已提交
2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464

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

2465
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
2466 2467 2468 2469 2470 2471 2472 2473 2474 2475
		void *param)
{
	vec2 &mul = *reinterpret_cast<vec2*>(param);

	if (!obs_sceneitem_selected(item))
		return true;

	vec3 tl = GetItemTL(item);

	vec2 scale;
J
jp9000 已提交
2476
	obs_sceneitem_get_scale(item, &scale);
J
jp9000 已提交
2477
	vec2_mul(&scale, &scale, &mul);
J
jp9000 已提交
2478
	obs_sceneitem_set_scale(item, &scale);
J
jp9000 已提交
2479 2480

	SetItemTL(item, tl);
J
jp9000 已提交
2481 2482

	UNUSED_PARAMETER(scene);
J
jp9000 已提交
2483 2484 2485 2486 2487
	return true;
}

void OBSBasic::on_actionFlipHorizontal_triggered()
{
J
jp9000 已提交
2488 2489
	vec2 scale;
	vec2_set(&scale, -1.0f, 1.0f);
J
jp9000 已提交
2490 2491 2492 2493 2494 2495
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

void OBSBasic::on_actionFlipVertical_triggered()
{
J
jp9000 已提交
2496 2497
	vec2 scale;
	vec2_set(&scale, 1.0f, -1.0f);
J
jp9000 已提交
2498 2499 2500 2501
	obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
			&scale);
}

2502
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
J
jp9000 已提交
2503 2504 2505 2506 2507 2508 2509 2510 2511 2512
		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);

2513
	obs_transform_info itemInfo;
J
jp9000 已提交
2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545
	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()
{
2546
	auto func = [] (obs_scene_t *scene, obs_sceneitem_t *item, void *param)
2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571
	{
		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 已提交
2572
		UNUSED_PARAMETER(param);
2573 2574 2575 2576
		return true;
	};

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