window-basic-main.cpp 13.7 KB
Newer Older
1 2
/******************************************************************************
    Copyright (C) 2013 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 58 59 60 61 62 63 64 65 66
{
	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";

	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");
67
	obs_load_module("obs-ffmpeg");
68

69
	/* HACK: fixes a qt bug with native widgets with native repaint */
70 71 72 73 74 75 76 77 78 79 80 81
	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 已提交
82
OBSScene OBSBasic::GetCurrentScene()
83
{
J
jp9000 已提交
84
	QListWidgetItem *item = ui->scenes->currentItem();
J
jp9000 已提交
85
	return item ? item->data(Qt::UserRole).value<OBSScene>() : nullptr;
86 87
}

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

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
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)
110 111 112
{
	const char *name  = obs_source_getname(source);
	obs_scene_t scene = obs_scene_fromsource(source);
J
jp9000 已提交
113 114

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

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

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

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

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

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

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

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

	sourceSceneRefs[source] = sourceSceneRefs[source] + 1;
154 155
}

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

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

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

	obs_source_t source = obs_sceneitem_getsource(item);

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

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

J
jp9000 已提交
187
		if (type != OBS_SOURCE_TYPE_SCENE)
J
jp9000 已提交
188
			return;
189

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

/* 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 已提交
212

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

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

221 222 223
	obs_scene_t scene = (obs_scene_t)calldata_ptr(params, "scene");
	obs_sceneitem_t item = (obs_sceneitem_t)calldata_ptr(params, "item");

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

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

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

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

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

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

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

254 255 256 257 258 259
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)
260 261 262
		QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
				"UpdateSceneSelection",
				Q_ARG(OBSSource, OBSSource(source)));
263 264 265 266
}

/* Main class functions */

J
jp9000 已提交
267 268 269
bool OBSBasic::InitGraphics()
{
	struct obs_video_info ovi;
J
jp9000 已提交
270 271 272 273

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

	ovi.graphics_module = App()->GetRenderModule();
J
jp9000 已提交
274 275 276 277 278 279 280 281
	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");
282
	ovi.output_format = VIDEO_FORMAT_I420;
J
jp9000 已提交
283
	ovi.adapter       = 0;
284

J
jp9000 已提交
285
	QTToGSWindow(ui->preview, ovi.window);
J
jp9000 已提交
286 287

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

J
jp9000 已提交
290 291 292
	QSize size = ui->preview->size();
	ovi.window_width  = size.width();
	ovi.window_height = size.height();
J
jp9000 已提交
293

J
jp9000 已提交
294 295
	return obs_reset_video(&ovi);
}
J
jp9000 已提交
296

J
jp9000 已提交
297 298 299
bool OBSBasic::InitAudio()
{
	/* TODO: load audio settings from config */
J
jp9000 已提交
300
	struct audio_output_info ai;
J
jp9000 已提交
301 302
	ai.name = "test";
	ai.samples_per_sec = 44100;
303
	ai.format = AUDIO_FORMAT_FLOAT_PLANAR;
J
jp9000 已提交
304 305 306 307
	ai.speakers = SPEAKERS_STEREO;
	ai.buffer_ms = 700;

	return obs_reset_audio(&ai);
J
jp9000 已提交
308 309
}

J
jp9000 已提交
310
void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy)
311
{
J
jp9000 已提交
312
	double targetAspect, baseAspect;
313 314
	QSize  targetSize;
	int x, y;
J
jp9000 已提交
315

316
	/* resize preview panel to fix to the top section of the window */
J
jp9000 已提交
317 318 319
	targetSize   = ui->previewContainer->size();
	targetAspect = double(targetSize.width()) / double(targetSize.height());
	baseAspect   = double(cx) / double(cy);
320

321 322 323 324 325 326 327 328 329 330
	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;
331

332
	ui->preview->setGeometry(x, y, cx, cy);
J
jp9000 已提交
333 334

	graphics_t graphics = obs_graphics();
335
	if (graphics && isVisible()) {
J
jp9000 已提交
336
		gs_entercontext(graphics);
337
		gs_resize(cx, cy);
J
jp9000 已提交
338
		gs_leavecontext();
339
	}
J
jp9000 已提交
340 341
}

J
jp9000 已提交
342
void OBSBasic::closeEvent(QCloseEvent *event)
J
jp9000 已提交
343
{
344 345
}

J
jp9000 已提交
346
void OBSBasic::changeEvent(QEvent *event)
347
{
348 349
}

J
jp9000 已提交
350
void OBSBasic::resizeEvent(QResizeEvent *event)
351
{
J
jp9000 已提交
352 353 354 355
	struct obs_video_info ovi;

	if (obs_get_video_info(&ovi))
		ResizePreview(ovi.base_width, ovi.base_height);
356 357
}

J
jp9000 已提交
358
void OBSBasic::on_action_New_triggered()
359 360 361
{
}

J
jp9000 已提交
362
void OBSBasic::on_action_Open_triggered()
363 364 365
{
}

J
jp9000 已提交
366
void OBSBasic::on_action_Save_triggered()
367 368 369
{
}

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

	if (item) {
		obs_scene_t scene;

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

382
	/* TODO: allow transitions */
383 384 385
	obs_set_output_source(0, source);
}

J
jp9000 已提交
386
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
387 388 389
{
}

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

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

			obs_source_release(source);
J
jp9000 已提交
406
			on_actionAddScene_triggered();
407 408 409
			return;
		}

410
		obs_scene_t scene = obs_scene_create(name.c_str());
411 412
		source = obs_scene_getsource(scene);
		obs_add_source(source);
413
		obs_scene_release(scene);
414 415

		obs_set_output_source(0, source);
416
	}
417 418
}

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

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

J
jp9000 已提交
431
void OBSBasic::on_actionSceneProperties_triggered()
432 433 434
{
}

J
jp9000 已提交
435
void OBSBasic::on_actionSceneUp_triggered()
436 437 438
{
}

J
jp9000 已提交
439
void OBSBasic::on_actionSceneDown_triggered()
440 441 442
{
}

J
jp9000 已提交
443
void OBSBasic::on_sources_itemChanged(QListWidgetItem *item)
444 445 446
{
}

J
jp9000 已提交
447
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
448 449 450
{
}

451 452 453 454 455 456
void OBSBasic::AddSource(obs_scene_t scene, const char *id)
{
	string name;

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

J
jp9000 已提交
462
		if (!accepted)
463 464
			break;

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

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

		sourceSceneRefs[source] = 0;

482 483 484 485 486 487
		obs_add_source(source);
		obs_sceneitem_t item = obs_scene_add(scene, source);
		obs_source_release(source);
	}
}

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

J
jp9000 已提交
495
	if (!scene)
496 497
		return;

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

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

J
jp9000 已提交
508
		foundValues = true;
509 510
	}

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

J
jp9000 已提交
518
void OBSBasic::on_actionAddSource_triggered()
519
{
J
jp9000 已提交
520
	AddSourcePopupMenu(QCursor::pos());
521 522
}

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

J
jp9000 已提交
530
void OBSBasic::on_actionSourceProperties_triggered()
531 532 533
{
}

J
jp9000 已提交
534
void OBSBasic::on_actionSourceUp_triggered()
535 536
{
}
J
jp9000 已提交
537

J
jp9000 已提交
538
void OBSBasic::on_actionSourceDown_triggered()
539 540 541
{
}

J
jp9000 已提交
542 543 544 545 546 547 548 549 550
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 已提交
551
				"Video Files (*.avi)");
J
jp9000 已提交
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571

		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 已提交
572
void OBSBasic::on_settingsButton_clicked()
J
jp9000 已提交
573
{
574 575
	OBSBasicSettings settings(this);
	settings.exec();
J
jp9000 已提交
576
}