window-basic-main.cpp 14.0 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
	  outputTest    (NULL),
	  ui            (new Ui::OBSBasic)
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
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);
J
jp9000 已提交
105 106

				UNUSED_PARAMETER(scene);
107 108 109 110 111 112 113
				return true;
			}, this);
}

/* Qt callbacks for invokeMethod */

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

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

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

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

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

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

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

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

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

	sourceSceneRefs[source] = sourceSceneRefs[source] + 1;
158 159
}

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

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

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

	obs_source_t source = obs_sceneitem_getsource(item);

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

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

J
jp9000 已提交
191
		if (type != OBS_SOURCE_TYPE_SCENE)
J
jp9000 已提交
192
			return;
193

J
jp9000 已提交
194 195 196 197 198 199 200 201 202 203
		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);
204
		}
J
jp9000 已提交
205
	}
206 207 208 209 210 211 212 213 214
}

/* OBS Callbacks */

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

	obs_sceneitem_t item = (obs_sceneitem_t)calldata_ptr(params, "item");
J
jp9000 已提交
215

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

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

224 225
	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();
J
jp9000 已提交
270 271 272 273

	UNUSED_PARAMETER(data);
	UNUSED_PARAMETER(cx);
	UNUSED_PARAMETER(cy);
274 275
}

276 277
/* Main class functions */

J
jp9000 已提交
278 279 280
bool OBSBasic::InitGraphics()
{
	struct obs_video_info ovi;
J
jp9000 已提交
281 282 283 284

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

	ovi.graphics_module = App()->GetRenderModule();
J
jp9000 已提交
285
	ovi.base_width     = (uint32_t)config_get_uint(GetGlobalConfig(),
J
jp9000 已提交
286
			"Video", "BaseCX");
J
jp9000 已提交
287
	ovi.base_height    = (uint32_t)config_get_uint(GetGlobalConfig(),
J
jp9000 已提交
288
			"Video", "BaseCY");
J
jp9000 已提交
289
	ovi.output_width   = (uint32_t)config_get_uint(GetGlobalConfig(),
J
jp9000 已提交
290
			"Video", "OutputCX");
J
jp9000 已提交
291
	ovi.output_height  = (uint32_t)config_get_uint(GetGlobalConfig(),
J
jp9000 已提交
292
			"Video", "OutputCY");
J
jp9000 已提交
293 294 295
	ovi.output_format  = VIDEO_FORMAT_I420;
	ovi.adapter        = 0;
	ovi.gpu_conversion = true;
296

J
jp9000 已提交
297
	QTToGSWindow(ui->preview, ovi.window);
J
jp9000 已提交
298 299

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

J
jp9000 已提交
302 303 304
	QSize size = ui->preview->size();
	ovi.window_width  = size.width();
	ovi.window_height = size.height();
J
jp9000 已提交
305

J
jp9000 已提交
306 307
	return obs_reset_video(&ovi);
}
J
jp9000 已提交
308

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

	return obs_reset_audio(&ai);
J
jp9000 已提交
320 321
}

J
jp9000 已提交
322
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
323
{
J
jp9000 已提交
324
	double targetAspect, baseAspect;
325 326
	QSize  targetSize;
	int x, y;
J
jp9000 已提交
327

328
	/* resize preview panel to fix to the top section of the window */
J
jp9000 已提交
329 330 331
	targetSize   = ui->previewContainer->size();
	targetAspect = double(targetSize.width()) / double(targetSize.height());
	baseAspect   = double(cx) / double(cy);
332

333 334 335 336 337 338 339 340 341 342
	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;
343

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

346 347
	if (isVisible())
		obs_resize(cx, cy);
J
jp9000 已提交
348 349
}

J
jp9000 已提交
350
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
351
{
J
jp9000 已提交
352 353
	/* TODO */
	UNUSED_PARAMETER(event);
354 355
}

J
jp9000 已提交
356
void OBSBasic::changeEvent(QEvent *event)
357
{
J
jp9000 已提交
358 359
	/* TODO */
	UNUSED_PARAMETER(event);
360 361
}

J
jp9000 已提交
362
void OBSBasic::resizeEvent(QResizeEvent *event)
363
{
J
jp9000 已提交
364 365 366 367
	struct obs_video_info ovi;

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

	UNUSED_PARAMETER(event);
370 371
}

J
jp9000 已提交
372
void OBSBasic::on_action_New_triggered()
373
{
J
jp9000 已提交
374
	/* TODO */
375 376
}

J
jp9000 已提交
377
void OBSBasic::on_action_Open_triggered()
378
{
J
jp9000 已提交
379
	/* TODO */
380 381
}

J
jp9000 已提交
382
void OBSBasic::on_action_Save_triggered()
383
{
J
jp9000 已提交
384
	/* TODO */
385 386
}

J
jp9000 已提交
387
void OBSBasic::on_scenes_itemChanged(QListWidgetItem *item)
388 389
{
	obs_source_t source = NULL;
J
jp9000 已提交
390 391 392 393

	if (item) {
		obs_scene_t scene;

J
jp9000 已提交
394
		scene = item->data(Qt::UserRole).value<OBSScene>();
395
		source = obs_scene_getsource(scene);
396
		UpdateSources(scene);
397 398
	}

399
	/* TODO: allow transitions */
400 401 402
	obs_set_output_source(0, source);
}

J
jp9000 已提交
403
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
404
{
J
jp9000 已提交
405 406
	/* TODO */
	UNUSED_PARAMETER(pos);
407 408
}

J
jp9000 已提交
409
void OBSBasic::on_actionAddScene_triggered()
410
{
411
	string name;
J
jp9000 已提交
412 413 414
	bool accepted = NameDialog::AskForName(this,
			QTStr("MainWindow.AddSceneDlg.Title"),
			QTStr("MainWindow.AddSceneDlg.Text"),
415 416
			name);

J
jp9000 已提交
417
	if (accepted) {
418 419
		obs_source_t source = obs_get_source_by_name(name.c_str());
		if (source) {
J
jp9000 已提交
420 421 422
			QMessageBox::information(this,
					QTStr("MainWindow.NameExists.Title"),
					QTStr("MainWindow.NameExists.Text"));
423 424

			obs_source_release(source);
J
jp9000 已提交
425
			on_actionAddScene_triggered();
426 427 428
			return;
		}

429
		obs_scene_t scene = obs_scene_create(name.c_str());
430 431
		source = obs_scene_getsource(scene);
		obs_add_source(source);
432
		obs_scene_release(scene);
433 434

		obs_set_output_source(0, source);
435
	}
436 437
}

J
jp9000 已提交
438
void OBSBasic::on_actionRemoveScene_triggered()
439
{
J
jp9000 已提交
440 441
	QListWidgetItem *item = ui->scenes->currentItem();
	if (!item)
J
jp9000 已提交
442 443
		return;

J
jp9000 已提交
444
	QVariant userData = item->data(Qt::UserRole);
J
jp9000 已提交
445
	obs_scene_t scene = userData.value<OBSScene>();
J
jp9000 已提交
446 447
	obs_source_t source = obs_scene_getsource(scene);
	obs_source_remove(source);
448 449
}

J
jp9000 已提交
450
void OBSBasic::on_actionSceneProperties_triggered()
451
{
J
jp9000 已提交
452
	/* TODO */
453 454
}

J
jp9000 已提交
455
void OBSBasic::on_actionSceneUp_triggered()
456
{
J
jp9000 已提交
457
	/* TODO */
458 459
}

J
jp9000 已提交
460
void OBSBasic::on_actionSceneDown_triggered()
461
{
J
jp9000 已提交
462
	/* TODO */
463 464
}

J
jp9000 已提交
465
void OBSBasic::on_sources_itemChanged(QListWidgetItem *item)
466
{
J
jp9000 已提交
467 468
	/* TODO */
	UNUSED_PARAMETER(item);
469 470
}

J
jp9000 已提交
471
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
472
{
J
jp9000 已提交
473 474
	/* TODO */
	UNUSED_PARAMETER(pos);
475 476
}

477 478 479 480 481 482
void OBSBasic::AddSource(obs_scene_t scene, const char *id)
{
	string name;

	bool success = false;
	while (!success) {
J
jp9000 已提交
483
		bool accepted = NameDialog::AskForName(this,
484 485 486 487
				Str("MainWindow.AddSourceDlg.Title"),
				Str("MainWindow.AddSourceDlg.Text"),
				name);

J
jp9000 已提交
488
		if (!accepted)
489 490
			break;

J
jp9000 已提交
491
		obs_source_t source = obs_get_source_by_name(name.c_str());
492 493 494
		if (!source) {
			success = true;
		} else {
J
jp9000 已提交
495 496 497
			QMessageBox::information(this,
					QTStr("MainWindow.NameExists.Title"),
					QTStr("MainWindow.NameExists.Text"));
498 499 500 501 502
			obs_source_release(source);
		}
	}

	if (success) {
J
jp9000 已提交
503 504
		obs_source_t source = obs_source_create(OBS_SOURCE_TYPE_INPUT,
				id, name.c_str(), NULL);
J
jp9000 已提交
505 506 507

		sourceSceneRefs[source] = 0;

508
		obs_add_source(source);
J
jp9000 已提交
509
		obs_scene_add(scene, source);
510 511 512 513
		obs_source_release(source);
	}
}

J
jp9000 已提交
514
void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
515
{
516
	OBSScene scene = GetCurrentScene();
517
	const char *type;
J
jp9000 已提交
518 519
	bool foundValues = false;
	size_t idx = 0;
520

J
jp9000 已提交
521
	if (!scene)
522 523
		return;

J
jp9000 已提交
524 525
	QMenu popup;
	while (obs_enum_input_types(idx++, &type)) {
J
jp9000 已提交
526 527
		const char *name = obs_source_getdisplayname(
				OBS_SOURCE_TYPE_INPUT,
J
jp9000 已提交
528
				type, App()->GetLocale());
529

J
jp9000 已提交
530 531 532
		QAction *popupItem = new QAction(QT_UTF8(name), this);
		popupItem->setData(QT_UTF8(type));
		popup.addAction(popupItem);
533

J
jp9000 已提交
534
		foundValues = true;
535 536
	}

J
jp9000 已提交
537 538 539 540
	if (foundValues) {
		QAction *ret = popup.exec(pos);
		if (ret)
			AddSource(scene, ret->data().toString().toUtf8());
541 542 543
	}
}

J
jp9000 已提交
544
void OBSBasic::on_actionAddSource_triggered()
545
{
J
jp9000 已提交
546
	AddSourcePopupMenu(QCursor::pos());
547 548
}

J
jp9000 已提交
549
void OBSBasic::on_actionRemoveSource_triggered()
550
{
551
	OBSSceneItem item = GetCurrentSceneItem();
J
jp9000 已提交
552 553
	if (item)
		obs_sceneitem_remove(item);
554 555
}

J
jp9000 已提交
556
void OBSBasic::on_actionSourceProperties_triggered()
557 558 559
{
}

J
jp9000 已提交
560
void OBSBasic::on_actionSourceUp_triggered()
561 562
{
}
J
jp9000 已提交
563

J
jp9000 已提交
564
void OBSBasic::on_actionSourceDown_triggered()
565 566 567
{
}

J
jp9000 已提交
568 569 570 571 572 573 574 575 576
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 已提交
577
				"Video Files (*.avi)");
J
jp9000 已提交
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597

		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 已提交
598
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
599
{
600 601
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
602
}