window-basic-main.cpp 13.7 KB
Newer Older
1
/******************************************************************************
2
    Copyright (C) 2013-2014 by Hugh Bailey <obs.jim@gmail.com>
3
    Copyright (C) 2014 by 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 <obs.hpp>
J
jp9000 已提交
20
#include <QMessageBox>
21
#include <QShowEvent>
J
jp9000 已提交
22
#include <QFileDialog>
23

24
#include "obs-app.hpp"
25
#include "window-basic-settings.hpp"
26
#include "window-namedialog.hpp"
J
jp9000 已提交
27 28
#include "window-basic-main.hpp"
#include "qt-wrappers.hpp"
29

J
jp9000 已提交
30
#include "ui_OBSBasic.h"
31

32
using namespace std;
J
jp9000 已提交
33

J
jp9000 已提交
34 35 36
Q_DECLARE_METATYPE(OBSScene);
Q_DECLARE_METATYPE(OBSSceneItem);

37 38
OBSBasic::OBSBasic(QWidget *parent)
	: OBSMainWindow (parent),
J
jp9000 已提交
39 40
	  ui            (new Ui::OBSBasic),
	  outputTest    (NULL)
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
{
	ui->setupUi(this);
}

void OBSBasic::OBSInit()
{
	/* make sure it's fully displayed before doing any initialization */
	show();
	App()->processEvents();

	if (!obs_startup())
		throw "Failed to initialize libobs";
	if (!InitGraphics())
		throw "Failed to initialize graphics";
	if (!InitAudio())
		throw "Failed to initialize audio";

58 59
	obs_add_draw_callback(OBSBasic::RenderMain, this);

60 61 62 63 64 65 66 67 68
	signal_handler_connect(obs_signalhandler(), "source-add",
			OBSBasic::SourceAdded, this);
	signal_handler_connect(obs_signalhandler(), "source-remove",
			OBSBasic::SourceRemoved, this);
	signal_handler_connect(obs_signalhandler(), "channel-change",
			OBSBasic::ChannelChanged, this);

	/* TODO: this is a test */
	obs_load_module("test-input");
69
	obs_load_module("obs-ffmpeg");
70

71
	/* HACK: fixes a qt bug with native widgets with native repaint */
72 73 74 75 76 77 78 79 80 81 82 83
	ui->previewContainer->repaint();
}

OBSBasic::~OBSBasic()
{
	/* free the lists before shutting down to remove the scene/item
	 * references */
	ui->sources->clear();
	ui->scenes->clear();
	obs_shutdown();
}

J
jp9000 已提交
84
OBSScene OBSBasic::GetCurrentScene()
85
{
J
jp9000 已提交
86
	QListWidgetItem *item = ui->scenes->currentItem();
J
jp9000 已提交
87
	return item ? item->data(Qt::UserRole).value<OBSScene>() : nullptr;
88 89
}

J
jp9000 已提交
90
OBSSceneItem OBSBasic::GetCurrentSceneItem()
J
jp9000 已提交
91 92
{
	QListWidgetItem *item = ui->sources->currentItem();
J
jp9000 已提交
93
	return item ? item->data(Qt::UserRole).value<OBSSceneItem>() : nullptr;
J
jp9000 已提交
94 95
}

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
void OBSBasic::UpdateSources(OBSScene scene)
{
	ui->sources->clear();

	obs_scene_enum_items(scene,
			[] (obs_scene_t scene, obs_sceneitem_t item, void *p)
			{
				OBSBasic *window = static_cast<OBSBasic*>(p);
				window->AddSceneItem(item);
				return true;
			}, this);
}

/* Qt callbacks for invokeMethod */

void OBSBasic::AddScene(OBSSource source)
112 113 114
{
	const char *name  = obs_source_getname(source);
	obs_scene_t scene = obs_scene_fromsource(source);
J
jp9000 已提交
115 116

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

	signal_handler_t handler = obs_source_signalhandler(source);
J
jp9000 已提交
121
	signal_handler_connect(handler, "add", OBSBasic::SceneItemAdded, this);
122 123
	signal_handler_connect(handler, "remove", OBSBasic::SceneItemRemoved,
			this);
124 125
}

126
void OBSBasic::RemoveScene(OBSSource source)
J
jp9000 已提交
127 128 129
{
	const char *name = obs_source_getname(source);

J
jp9000 已提交
130 131 132
	QListWidgetItem *sel = ui->scenes->currentItem();
	QList<QListWidgetItem*> items = ui->scenes->findItems(QT_UTF8(name),
			Qt::MatchExactly);
J
jp9000 已提交
133

J
jp9000 已提交
134 135 136 137
	if (sel != nullptr) {
		if (items.contains(sel))
			ui->sources->clear();
		delete sel;
J
jp9000 已提交
138
	}
139 140
}

141
void OBSBasic::AddSceneItem(OBSSceneItem item)
142
{
J
jp9000 已提交
143
	obs_scene_t  scene  = obs_sceneitem_getscene(item);
144
	obs_source_t source = obs_sceneitem_getsource(item);
J
jp9000 已提交
145
	const char   *name  = obs_source_getname(source);
J
jp9000 已提交
146

J
jp9000 已提交
147 148
	if (GetCurrentScene() == scene) {
		QListWidgetItem *listItem = new QListWidgetItem(QT_UTF8(name));
J
jp9000 已提交
149 150
		listItem->setData(Qt::UserRole,
				QVariant::fromValue(OBSSceneItem(item)));
J
jp9000 已提交
151 152 153

		ui->sources->insertItem(0, listItem);
	}
J
jp9000 已提交
154 155

	sourceSceneRefs[source] = sourceSceneRefs[source] + 1;
156 157
}

158
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
159
{
J
jp9000 已提交
160
	obs_scene_t scene = obs_sceneitem_getscene(item);
161

J
jp9000 已提交
162
	if (GetCurrentScene() == scene) {
B
BtbN 已提交
163
		for (int i = 0; i < ui->sources->count(); i++) {
J
jp9000 已提交
164 165
			QListWidgetItem *listItem = ui->sources->item(i);
			QVariant userData = listItem->data(Qt::UserRole);
J
jp9000 已提交
166

J
jp9000 已提交
167
			if (userData.value<OBSSceneItem>() == item) {
J
jp9000 已提交
168
				delete listItem;
J
jp9000 已提交
169 170
				break;
			}
171 172
		}
	}
J
jp9000 已提交
173 174 175 176 177 178 179 180

	obs_source_t source = obs_sceneitem_getsource(item);

	int scenes = sourceSceneRefs[source] - 1;
	if (scenes == 0) {
		obs_source_remove(source);
		sourceSceneRefs.erase(source);
	}
181 182
}

183
void OBSBasic::UpdateSceneSelection(OBSSource source)
184 185 186 187 188
{
	if (source) {
		obs_source_type type;
		obs_source_gettype(source, &type, NULL);

J
jp9000 已提交
189
		if (type != OBS_SOURCE_TYPE_SCENE)
J
jp9000 已提交
190
			return;
191

J
jp9000 已提交
192 193 194 195 196 197 198 199 200 201
		obs_scene_t scene = obs_scene_fromsource(source);
		const char *name = obs_source_getname(source);

		QListWidgetItem *sel = ui->scenes->currentItem();
		QList<QListWidgetItem*> items =
			ui->scenes->findItems(QT_UTF8(name), Qt::MatchExactly);

		if (items.contains(sel)) {
			ui->scenes->setCurrentItem(sel);
			UpdateSources(scene);
202
		}
J
jp9000 已提交
203
	}
204 205 206 207 208 209 210 211 212 213
}

/* OBS Callbacks */

void OBSBasic::SceneItemAdded(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");
J
jp9000 已提交
214

215 216
	QMetaObject::invokeMethod(window, "AddSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
J
jp9000 已提交
217 218
}

219
void OBSBasic::SceneItemRemoved(void *data, calldata_t params)
220
{
221
	OBSBasic *window = static_cast<OBSBasic*>(data);
222

223 224 225
	obs_scene_t scene = (obs_scene_t)calldata_ptr(params, "scene");
	obs_sceneitem_t item = (obs_sceneitem_t)calldata_ptr(params, "item");

226 227
	QMetaObject::invokeMethod(window, "RemoveSceneItem",
			Q_ARG(OBSSceneItem, OBSSceneItem(item)));
228 229 230 231 232
}

void OBSBasic::SourceAdded(void *data, calldata_t params)
{
	obs_source_t source = (obs_source_t)calldata_ptr(params, "source");
233 234 235 236

	obs_source_type type;
	obs_source_gettype(source, &type, NULL);

J
jp9000 已提交
237
	if (type == OBS_SOURCE_TYPE_SCENE)
238 239 240
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"AddScene",
				Q_ARG(OBSSource, OBSSource(source)));
241 242
}

J
jp9000 已提交
243
void OBSBasic::SourceRemoved(void *data, calldata_t params)
244
{
245
	obs_source_t source = (obs_source_t)calldata_ptr(params, "source");
246

J
jp9000 已提交
247 248 249
	obs_source_type type;
	obs_source_gettype(source, &type, NULL);

J
jp9000 已提交
250
	if (type == OBS_SOURCE_TYPE_SCENE)
251 252 253
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"RemoveScene",
				Q_ARG(OBSSource, OBSSource(source)));
254 255
}

256 257 258 259 260 261
void OBSBasic::ChannelChanged(void *data, calldata_t params)
{
	obs_source_t source = (obs_source_t)calldata_ptr(params, "source");
	uint32_t channel = calldata_uint32(params, "channel");

	if (channel == 0)
262 263 264
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"UpdateSceneSelection",
				Q_ARG(OBSSource, OBSSource(source)));
265 266
}

267 268
void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
{
J
jp9000 已提交
269
	obs_render_main_view();
270 271
}

272 273
/* Main class functions */

J
jp9000 已提交
274 275 276
bool OBSBasic::InitGraphics()
{
	struct obs_video_info ovi;
J
jp9000 已提交
277 278 279 280

	App()->GetConfigFPS(ovi.fps_num, ovi.fps_den);

	ovi.graphics_module = App()->GetRenderModule();
J
jp9000 已提交
281 282 283 284 285 286 287 288
	ovi.base_width    = (uint32_t)config_get_uint(GetGlobalConfig(),
			"Video", "BaseCX");
	ovi.base_height   = (uint32_t)config_get_uint(GetGlobalConfig(),
			"Video", "BaseCY");
	ovi.output_width  = (uint32_t)config_get_uint(GetGlobalConfig(),
			"Video", "OutputCX");
	ovi.output_height = (uint32_t)config_get_uint(GetGlobalConfig(),
			"Video", "OutputCY");
289
	ovi.output_format = VIDEO_FORMAT_I420;
J
jp9000 已提交
290
	ovi.adapter       = 0;
291

J
jp9000 已提交
292
	QTToGSWindow(ui->preview, ovi.window);
J
jp9000 已提交
293 294

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

J
jp9000 已提交
297 298 299
	QSize size = ui->preview->size();
	ovi.window_width  = size.width();
	ovi.window_height = size.height();
J
jp9000 已提交
300

J
jp9000 已提交
301 302
	return obs_reset_video(&ovi);
}
J
jp9000 已提交
303

J
jp9000 已提交
304 305 306
bool OBSBasic::InitAudio()
{
	/* TODO: load audio settings from config */
J
jp9000 已提交
307
	struct audio_output_info ai;
J
jp9000 已提交
308 309
	ai.name = "test";
	ai.samples_per_sec = 44100;
310
	ai.format = AUDIO_FORMAT_FLOAT_PLANAR;
J
jp9000 已提交
311 312 313 314
	ai.speakers = SPEAKERS_STEREO;
	ai.buffer_ms = 700;

	return obs_reset_audio(&ai);
J
jp9000 已提交
315 316
}

J
jp9000 已提交
317
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
318
{
J
jp9000 已提交
319
	double targetAspect, baseAspect;
320 321
	QSize  targetSize;
	int x, y;
J
jp9000 已提交
322

323
	/* resize preview panel to fix to the top section of the window */
J
jp9000 已提交
324 325 326
	targetSize   = ui->previewContainer->size();
	targetAspect = double(targetSize.width()) / double(targetSize.height());
	baseAspect   = double(cx) / double(cy);
327

328 329 330 331 332 333 334 335 336 337
	if (targetAspect > baseAspect) {
		cx = targetSize.height() * baseAspect;
		cy = targetSize.height();
	} else {
		cx = targetSize.width();
		cy = targetSize.width() / baseAspect;
	}

	x = targetSize.width() /2 - cx/2;
	y = targetSize.height()/2 - cy/2;
338

339
	ui->preview->setGeometry(x, y, cx, cy);
J
jp9000 已提交
340

341 342
	if (isVisible())
		obs_resize(cx, cy);
J
jp9000 已提交
343 344
}

J
jp9000 已提交
345
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
346
{
347 348
}

J
jp9000 已提交
349
void OBSBasic::changeEvent(QEvent *event)
350
{
351 352
}

J
jp9000 已提交
353
void OBSBasic::resizeEvent(QResizeEvent *event)
354
{
J
jp9000 已提交
355 356 357 358
	struct obs_video_info ovi;

	if (obs_get_video_info(&ovi))
		ResizePreview(ovi.base_width, ovi.base_height);
359 360
}

J
jp9000 已提交
361
void OBSBasic::on_action_New_triggered()
362 363 364
{
}

J
jp9000 已提交
365
void OBSBasic::on_action_Open_triggered()
366 367 368
{
}

J
jp9000 已提交
369
void OBSBasic::on_action_Save_triggered()
370 371 372
{
}

J
jp9000 已提交
373
void OBSBasic::on_scenes_itemChanged(QListWidgetItem *item)
374 375
{
	obs_source_t source = NULL;
J
jp9000 已提交
376 377 378 379

	if (item) {
		obs_scene_t scene;

J
jp9000 已提交
380
		scene = item->data(Qt::UserRole).value<OBSScene>();
381
		source = obs_scene_getsource(scene);
382
		UpdateSources(scene);
383 384
	}

385
	/* TODO: allow transitions */
386 387 388
	obs_set_output_source(0, source);
}

J
jp9000 已提交
389
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
390 391 392
{
}

J
jp9000 已提交
393
void OBSBasic::on_actionAddScene_triggered()
394
{
395
	string name;
J
jp9000 已提交
396 397 398
	bool accepted = NameDialog::AskForName(this,
			QTStr("MainWindow.AddSceneDlg.Title"),
			QTStr("MainWindow.AddSceneDlg.Text"),
399 400
			name);

J
jp9000 已提交
401
	if (accepted) {
402 403
		obs_source_t source = obs_get_source_by_name(name.c_str());
		if (source) {
J
jp9000 已提交
404 405 406
			QMessageBox::information(this,
					QTStr("MainWindow.NameExists.Title"),
					QTStr("MainWindow.NameExists.Text"));
407 408

			obs_source_release(source);
J
jp9000 已提交
409
			on_actionAddScene_triggered();
410 411 412
			return;
		}

413
		obs_scene_t scene = obs_scene_create(name.c_str());
414 415
		source = obs_scene_getsource(scene);
		obs_add_source(source);
416
		obs_scene_release(scene);
417 418

		obs_set_output_source(0, source);
419
	}
420 421
}

J
jp9000 已提交
422
void OBSBasic::on_actionRemoveScene_triggered()
423
{
J
jp9000 已提交
424 425
	QListWidgetItem *item = ui->scenes->currentItem();
	if (!item)
J
jp9000 已提交
426 427
		return;

J
jp9000 已提交
428
	QVariant userData = item->data(Qt::UserRole);
J
jp9000 已提交
429
	obs_scene_t scene = userData.value<OBSScene>();
J
jp9000 已提交
430 431
	obs_source_t source = obs_scene_getsource(scene);
	obs_source_remove(source);
432 433
}

J
jp9000 已提交
434
void OBSBasic::on_actionSceneProperties_triggered()
435 436 437
{
}

J
jp9000 已提交
438
void OBSBasic::on_actionSceneUp_triggered()
439 440 441
{
}

J
jp9000 已提交
442
void OBSBasic::on_actionSceneDown_triggered()
443 444 445
{
}

J
jp9000 已提交
446
void OBSBasic::on_sources_itemChanged(QListWidgetItem *item)
447 448 449
{
}

J
jp9000 已提交
450
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
451 452 453
{
}

454 455 456 457 458 459
void OBSBasic::AddSource(obs_scene_t scene, const char *id)
{
	string name;

	bool success = false;
	while (!success) {
J
jp9000 已提交
460
		bool accepted = NameDialog::AskForName(this,
461 462 463 464
				Str("MainWindow.AddSourceDlg.Title"),
				Str("MainWindow.AddSourceDlg.Text"),
				name);

J
jp9000 已提交
465
		if (!accepted)
466 467
			break;

J
jp9000 已提交
468
		obs_source_t source = obs_get_source_by_name(name.c_str());
469 470 471
		if (!source) {
			success = true;
		} else {
J
jp9000 已提交
472 473 474
			QMessageBox::information(this,
					QTStr("MainWindow.NameExists.Title"),
					QTStr("MainWindow.NameExists.Text"));
475 476 477 478 479
			obs_source_release(source);
		}
	}

	if (success) {
J
jp9000 已提交
480 481
		obs_source_t source = obs_source_create(OBS_SOURCE_TYPE_INPUT,
				id, name.c_str(), NULL);
J
jp9000 已提交
482 483 484

		sourceSceneRefs[source] = 0;

485 486 487 488 489 490
		obs_add_source(source);
		obs_sceneitem_t item = obs_scene_add(scene, source);
		obs_source_release(source);
	}
}

J
jp9000 已提交
491
void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
492
{
493
	OBSScene scene = GetCurrentScene();
494
	const char *type;
J
jp9000 已提交
495 496
	bool foundValues = false;
	size_t idx = 0;
497

J
jp9000 已提交
498
	if (!scene)
499 500
		return;

J
jp9000 已提交
501 502
	QMenu popup;
	while (obs_enum_input_types(idx++, &type)) {
J
jp9000 已提交
503 504
		const char *name = obs_source_getdisplayname(
				OBS_SOURCE_TYPE_INPUT,
J
jp9000 已提交
505
				type, App()->GetLocale());
506

J
jp9000 已提交
507 508 509
		QAction *popupItem = new QAction(QT_UTF8(name), this);
		popupItem->setData(QT_UTF8(type));
		popup.addAction(popupItem);
510

J
jp9000 已提交
511
		foundValues = true;
512 513
	}

J
jp9000 已提交
514 515 516 517
	if (foundValues) {
		QAction *ret = popup.exec(pos);
		if (ret)
			AddSource(scene, ret->data().toString().toUtf8());
518 519 520
	}
}

J
jp9000 已提交
521
void OBSBasic::on_actionAddSource_triggered()
522
{
J
jp9000 已提交
523
	AddSourcePopupMenu(QCursor::pos());
524 525
}

J
jp9000 已提交
526
void OBSBasic::on_actionRemoveSource_triggered()
527
{
528
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
529 530
	if (item)
		obs_sceneitem_remove(item);
531 532
}

J
jp9000 已提交
533
void OBSBasic::on_actionSourceProperties_triggered()
534 535 536
{
}

J
jp9000 已提交
537
void OBSBasic::on_actionSourceUp_triggered()
538 539
{
}
J
jp9000 已提交
540

J
jp9000 已提交
541
void OBSBasic::on_actionSourceDown_triggered()
542 543 544
{
}

J
jp9000 已提交
545 546 547 548 549 550 551 552 553
void OBSBasic::on_recordButton_clicked()
{
	if (outputTest) {
		obs_output_destroy(outputTest);
		outputTest = NULL;
		ui->recordButton->setText("Start Recording");
	} else {
		QString path = QFileDialog::getSaveFileName(this,
				"Please enter a file name", QString(),
J
jp9000 已提交
554
				"Video Files (*.avi)");
J
jp9000 已提交
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574

		if (path.isNull() || path.isEmpty())
			return;

		obs_data_t data = obs_data_create();
		obs_data_setstring(data, "filename", QT_TO_UTF8(path));

		outputTest = obs_output_create("ffmpeg_output", "test", data);
		obs_data_release(data);

		if (!obs_output_start(outputTest)) {
			obs_output_destroy(outputTest);
			outputTest = NULL;
			return;
		}

		ui->recordButton->setText("Stop Recording");
	}
}

J
jp9000 已提交
575
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
576
{
577 578
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
579
}