1 /*
2 Copyright (c) 2012-2020 Maarten Baert <maarten-baert@hotmail.com>
3 
4 This file is part of SimpleScreenRecorder.
5 
6 SimpleScreenRecorder is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 SimpleScreenRecorder is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with SimpleScreenRecorder.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "PageRecord.h"
21 
22 #include "CommandLineOptions.h"
23 #include "Icons.h"
24 #include "Dialogs.h"
25 #include "EnumStrings.h"
26 #include "MainWindow.h"
27 #include "PageInput.h"
28 #include "PageOutput.h"
29 #include "DialogRecordSchedule.h"
30 
31 #include "HotkeyListener.h"
32 
33 #include "Muxer.h"
34 #include "VideoEncoder.h"
35 #include "AudioEncoder.h"
36 #include "Synchronizer.h"
37 #include "X11Input.h"
38 #if SSR_USE_OPENGL_RECORDING
39 #include "GLInjectInput.h"
40 #endif
41 #if SSR_USE_V4L2
42 #include "V4L2Input.h"
43 #endif
44 #if SSR_USE_ALSA
45 #include "ALSAInput.h"
46 #endif
47 #if SSR_USE_PULSEAUDIO
48 #include "PulseAudioInput.h"
49 #endif
50 #if SSR_USE_JACK
51 #include "JACKInput.h"
52 #endif
53 #include "SimpleSynth.h"
54 #include "VideoPreviewer.h"
55 #include "AudioPreviewer.h"
56 
GetNewSegmentFile(const QString & file,bool add_timestamp)57 static QString GetNewSegmentFile(const QString& file, bool add_timestamp) {
58 	QFileInfo fi(file);
59 	QDateTime now = QDateTime::currentDateTime();
60 	QString newfile;
61 	unsigned int counter = 0;
62 	do {
63 		++counter;
64 		newfile = fi.completeBaseName();
65 		if(add_timestamp) {
66 			if(!newfile.isEmpty())
67 				newfile += "-";
68 			newfile += now.toString("yyyy-MM-dd_hh.mm.ss");
69 		}
70 		if(counter != 1) {
71 			if(!newfile.isEmpty())
72 				newfile += "-";
73 			newfile += "(" + QString::number(counter) + ")";
74 		}
75 		if(!fi.suffix().isEmpty())
76 			newfile += "." + fi.suffix();
77 		newfile = fi.path() + "/" + newfile;
78 	} while(QFileInfo(newfile).exists());
79 	return newfile;
80 }
81 
GetOptionsFromString(const QString & str)82 static std::vector<std::pair<QString, QString> > GetOptionsFromString(const QString& str) {
83 	std::vector<std::pair<QString, QString> > options;
84 	QStringList optionlist = SplitSkipEmptyParts(str, ',');
85 	for(int i = 0; i < optionlist.size(); ++i) {
86 		QString a = optionlist[i];
87 		int p = a.indexOf('=');
88 		if(p < 0) {
89 			options.push_back(std::make_pair(a.trimmed(), QString()));
90 		} else {
91 			options.push_back(std::make_pair(a.mid(0, p).trimmed(), a.mid(p + 1).trimmed()));
92 		}
93 	}
94 	return options;
95 }
96 
ReadableSizeIEC(uint64_t size,const QString & suffix)97 static QString ReadableSizeIEC(uint64_t size, const QString& suffix) {
98 	if(size < (uint64_t) 10 * 1024)
99 		return QString::number(size) + " " + suffix;
100 	if(size < (uint64_t) 10 * 1024 * 1024)
101 		return QString::number((size + 512) / 1024) + " Ki" + suffix;
102 	if(size < (uint64_t) 10 * 1024 * 1024 * 1024)
103 		return QString::number((size / 1024 + 512) / 1024) + " Mi" + suffix;
104 	return QString::number((size / (1024 * 1024) + 512) / 1024) + " Gi" + suffix;
105 }
106 
ReadableSizeSI(uint64_t size,const QString & suffix)107 static QString ReadableSizeSI(uint64_t size, const QString& suffix) {
108 	if(size < (uint64_t) 10 * 1000)
109 		return QString::number(size) + " " + suffix;
110 	if(size < (uint64_t) 10 * 1000 * 1000)
111 		return QString::number((size + 500) / 1000) + " k" + suffix;
112 	if(size < (uint64_t) 10 * 1000 * 1000 * 1000)
113 		return QString::number((size / 1000 + 500) / 1000) + " M" + suffix;
114 	return QString::number((size / (1000 * 1000) + 512) / 1024) + " G" + suffix;
115 }
116 
ReadableTime(int64_t time_micro)117 static QString ReadableTime(int64_t time_micro) {
118 	unsigned int time = (time_micro + 500000) / 1000000;
119 	return QString("%1:%2:%3")
120 			.arg(time / 3600)
121 			.arg((time / 60) % 60, 2, 10, QLatin1Char('0'))
122 			.arg(time % 60, 2, 10, QLatin1Char('0'));
123 }
124 
ReadableWidthHeight(unsigned int width,unsigned int height)125 static QString ReadableWidthHeight(unsigned int width, unsigned int height) {
126 	if(width == 0 && height == 0)
127 		return "?";
128 	return QString::number(width) + "x" + QString::number(height);
129 }
130 
131 class QTextEditSmall : public QTextEdit {
132 
133 public:
QTextEditSmall(QWidget * parent)134 	QTextEditSmall(QWidget *parent) : QTextEdit(parent) {}
QTextEditSmall(const QString & text,QWidget * parent)135 	QTextEditSmall(const QString& text, QWidget *parent) : QTextEdit(text, parent) {}
sizeHint() const136 	virtual QSize sizeHint() const override { return QSize(-1, 100); }
137 
138 };
139 
140 // sound notification sequences
141 #if SSR_USE_ALSA
142 static const std::array<SimpleSynth::Note, 1> SEQUENCE_RECORD_START = {{
143 	{0    , 500, 10000, 440.0f * exp2f( 3.0f / 12.0f), 0.8f}, // C5
144 }};
145 static const std::array<SimpleSynth::Note, 2> SEQUENCE_RECORD_STOP = {{
146 	{0    , 500, 20000, 440.0f * exp2f( 3.0f / 12.0f), 0.8f}, // C5
147 	{10000, 500, 20000, 440.0f * exp2f(-2.0f / 12.0f), 0.8f}, // G4
148 }};
149 static const std::array<SimpleSynth::Note, 4> SEQUENCE_RECORD_ERROR = {{
150 	{0    , 500, 20000, 440.0f * exp2f(-2.0f / 12.0f), 0.8f}, // G4
151 	{10000, 500, 20000, 440.0f * exp2f(-2.0f / 12.0f), 0.8f}, // G4
152 	{20000, 500, 20000, 440.0f * exp2f(-6.0f / 12.0f), 0.4f}, // D#4
153 	{20000, 500, 20000, 440.0f * exp2f(-9.0f / 12.0f), 0.6f}, // C4
154 }};
155 #endif
156 
PageRecord(MainWindow * main_window)157 PageRecord::PageRecord(MainWindow* main_window)
158 	: QWidget(main_window->centralWidget()) {
159 
160 	m_main_window = main_window;
161 
162 	m_page_started = false;
163 	m_input_started = false;
164 	m_output_started = false;
165 	m_previewing = false;
166 
167 	m_schedule_active = false;
168 	m_schedule_time_zone = SCHEDULE_TIME_ZONE_LOCAL;
169 
170 #if SSR_USE_ALSA
171 	m_last_error_sound = std::numeric_limits<int64_t>::min();
172 #endif
173 
174 	m_stdin_reentrant = false;
175 
176 	QGroupBox *groupbox_recording = new QGroupBox(tr("Recording"), this);
177 	{
178 		m_pushbutton_record = new QPushButton(groupbox_recording);
179 
180 		m_label_schedule_status = new QLabel(groupbox_recording);
181 		m_pushbutton_schedule_activate = new QPushButton(groupbox_recording);
182 		m_pushbutton_schedule_edit = new QPushButton(tr("Edit schedule"), groupbox_recording);
183 		// TODO: tooptips
184 
185 		m_checkbox_hotkey_enable = new QCheckBox(tr("Enable recording hotkey"), groupbox_recording);
186 		m_checkbox_hotkey_enable->setToolTip(tr("The recording hotkey is a global keyboard shortcut that can be used to start or pause the recording at any time,\n"
187 												"even when the SimpleScreenRecorder window is not visible. This way you can create recordings without having the\n"
188 												"SimpleScreenRecorder window show up in the final video."));
189 #if SSR_USE_ALSA
190 		m_checkbox_sound_notifications_enable = new QCheckBox(tr("Enable sound notifications"), groupbox_recording);
191 		m_checkbox_sound_notifications_enable->setToolTip(tr("When enabled, a sound will be played when the recording is started or paused, or when an error occurs."));
192 #endif
193 		QLabel *label_hotkey = new QLabel(tr("Hotkey:"), groupbox_recording);
194 		m_checkbox_hotkey_ctrl = new QCheckBox(tr("Ctrl +"), groupbox_recording);
195 		m_checkbox_hotkey_shift = new QCheckBox(tr("Shift +"), groupbox_recording);
196 		m_checkbox_hotkey_alt = new QCheckBox(tr("Alt +"), groupbox_recording);
197 		m_checkbox_hotkey_super = new QCheckBox(tr("Super +"), groupbox_recording);
198 		m_combobox_hotkey_key = new QComboBox(groupbox_recording);
199 		m_combobox_hotkey_key->setToolTip(tr("The key that you have to press (combined with the given modifiers) to start or pause recording.\n"
200 											 "The program that you are recording will not receive the key press."));
201 		// Note: The choice of keys is currently rather limited, because capturing key presses session-wide is a bit harder than it looks.
202 		// For example, applications are not allowed to capture the F1-F12 keys (on Ubuntu at least). The A-Z keys don't have this limitation apparently.
203 		for(unsigned int i = 0; i < 26; ++i) {
204 			m_combobox_hotkey_key->addItem(QString('A' + i));
205 		}
206 
207 		connect(m_pushbutton_record, SIGNAL(clicked()), this, SLOT(OnRecordStartPause()));
208 		connect(m_pushbutton_schedule_activate, SIGNAL(clicked()), this, SLOT(OnScheduleActivateDeactivate()));
209 		connect(m_pushbutton_schedule_edit, SIGNAL(clicked()), this, SLOT(OnScheduleEdit()));
210 		connect(m_checkbox_hotkey_enable, SIGNAL(clicked()), this, SLOT(OnUpdateHotkeyFields()));
211 #if SSR_USE_ALSA
212 		connect(m_checkbox_sound_notifications_enable, SIGNAL(clicked()), this, SLOT(OnUpdateSoundNotifications()));
213 #endif
214 		connect(m_checkbox_hotkey_ctrl, SIGNAL(clicked()), this, SLOT(OnUpdateHotkey()));
215 		connect(m_checkbox_hotkey_shift, SIGNAL(clicked()), this, SLOT(OnUpdateHotkey()));
216 		connect(m_checkbox_hotkey_alt, SIGNAL(clicked()), this, SLOT(OnUpdateHotkey()));
217 		connect(m_checkbox_hotkey_super, SIGNAL(clicked()), this, SLOT(OnUpdateHotkey()));
218 		connect(m_combobox_hotkey_key, SIGNAL(activated(int)), this, SLOT(OnUpdateHotkey()));
219 
220 		QVBoxLayout *layout = new QVBoxLayout(groupbox_recording);
221 		layout->addWidget(m_pushbutton_record);
222 		{
223 			QHBoxLayout *layout2 = new QHBoxLayout();
224 			layout->addLayout(layout2);
225 			layout2->addWidget(m_label_schedule_status, 2);
226 			layout2->addWidget(m_pushbutton_schedule_activate, 1);
227 			layout2->addWidget(m_pushbutton_schedule_edit, 1);
228 		}
229 		{
230 			QHBoxLayout *layout2 = new QHBoxLayout();
231 			layout->addLayout(layout2);
232 			layout2->addWidget(m_checkbox_hotkey_enable);
233 #if SSR_USE_ALSA
234 			layout2->addWidget(m_checkbox_sound_notifications_enable);
235 #endif
236 		}
237 		{
238 			QHBoxLayout *layout2 = new QHBoxLayout();
239 			layout->addLayout(layout2);
240 			layout2->addWidget(label_hotkey);
241 			layout2->addWidget(m_checkbox_hotkey_ctrl);
242 			layout2->addWidget(m_checkbox_hotkey_shift);
243 			layout2->addWidget(m_checkbox_hotkey_alt);
244 			layout2->addWidget(m_checkbox_hotkey_super);
245 			layout2->addWidget(m_combobox_hotkey_key);
246 		}
247 	}
248 	QSplitter *splitter_vertical = new QSplitter(Qt::Vertical, this);
249 	{
250 		QSplitter *splitter_horizontal = new QSplitter(Qt::Horizontal, splitter_vertical);
251 		{
252 			QGroupBox *groupbox_information = new QGroupBox(tr("Information"), splitter_horizontal);
253 			{
254 				QLabel *label_total_time = new QLabel(tr("Total time:"), groupbox_information);
255 				m_label_info_total_time = new QLabel(groupbox_information);
256 				QLabel *label_frame_rate_in = new QLabel(tr("FPS in:"), groupbox_information);
257 				m_label_info_frame_rate_in = new QLabel(groupbox_information);
258 				QLabel *label_frame_rate_out = new QLabel(tr("FPS out:"), groupbox_information);
259 				m_label_info_frame_rate_out = new QLabel(groupbox_information);
260 				QLabel *label_size_in = new QLabel(tr("Size in:"), groupbox_information);
261 				m_label_info_size_in = new QLabel(groupbox_information);
262 				QLabel *label_size_out = new QLabel(tr("Size out:"), groupbox_information);
263 				m_label_info_size_out = new QLabel(groupbox_information);
264 				QLabel *label_file_name = new QLabel(tr("File name:"), groupbox_information);
265 				m_label_info_file_name = new ElidedLabel(QString(), Qt::ElideMiddle, groupbox_information);
266 				m_label_info_file_name->setMinimumWidth(100);
267 				QLabel *label_file_size = new QLabel(tr("File size:"), groupbox_information);
268 				m_label_info_file_size = new QLabel(groupbox_information);
269 				QLabel *label_bit_rate = new QLabel(tr("Bit rate:"), groupbox_information);
270 				m_label_info_bit_rate = new QLabel(groupbox_information);
271 				m_checkbox_show_recording_area = new QCheckBox(tr("Show recording area"), groupbox_information);
272 				m_checkbox_show_recording_area->setToolTip(tr("When enabled, the recorded area is marked on the screen."));
273 
274 				connect(m_checkbox_show_recording_area, SIGNAL(clicked()), this, SLOT(OnUpdateRecordingFrame()));
275 
276 				QGridLayout *layout = new QGridLayout(groupbox_information);
277 				layout->addWidget(label_total_time, 0, 0);
278 				layout->addWidget(m_label_info_total_time, 0, 1);
279 				layout->addWidget(label_frame_rate_in, 1, 0);
280 				layout->addWidget(m_label_info_frame_rate_in, 1, 1);
281 				layout->addWidget(label_frame_rate_out, 2, 0);
282 				layout->addWidget(m_label_info_frame_rate_out, 2, 1);
283 				layout->addWidget(label_size_in, 3, 0);
284 				layout->addWidget(m_label_info_size_in, 3, 1);
285 				layout->addWidget(label_size_out, 4, 0);
286 				layout->addWidget(m_label_info_size_out, 4, 1);
287 				layout->addWidget(label_file_name, 5, 0);
288 				layout->addWidget(m_label_info_file_name, 5, 1);
289 				layout->addWidget(label_file_size, 6, 0);
290 				layout->addWidget(m_label_info_file_size, 6, 1);
291 				layout->addWidget(label_bit_rate, 7, 0);
292 				layout->addWidget(m_label_info_bit_rate, 7, 1);
293 				layout->addWidget(m_checkbox_show_recording_area, 9, 0, 1, 2);
294 				layout->setColumnStretch(1, 1);
295 				layout->setRowStretch(8, 1);
296 			}
297 			QGroupBox *groupbox_preview = new QGroupBox(tr("Preview"), splitter_horizontal);
298 			{
299 				m_preview_page1 = new QWidget(groupbox_preview);
300 				{
301 					QLabel *label_preview_frame_rate = new QLabel(tr("Preview frame rate:"), m_preview_page1);
302 					m_spinbox_preview_frame_rate = new QSpinBox(m_preview_page1);
303 					m_spinbox_preview_frame_rate->setRange(1, 1000);
304 					m_spinbox_preview_frame_rate->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
305 					QLabel *label_preview_note = new QLabel(tr("Note: Previewing requires extra CPU time (especially at high frame rates)."), m_preview_page1);
306 					label_preview_note->setWordWrap(true);
307 					label_preview_note->setAlignment(Qt::AlignLeft | Qt::AlignTop);
308 					label_preview_note->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding);
309 
310 					QGridLayout *layout = new QGridLayout(m_preview_page1);
311 					layout->setMargin(0);
312 					layout->addWidget(label_preview_frame_rate, 0, 0);
313 					layout->addWidget(m_spinbox_preview_frame_rate, 0, 1);
314 					layout->addWidget(label_preview_note, 1, 0, 1, 2);
315 				}
316 				m_preview_page2 = new QWidget(groupbox_preview);
317 				{
318 					m_video_previewer = new VideoPreviewer(m_preview_page2);
319 					m_label_mic_icon = new QLabel(m_preview_page2);
320 					m_label_mic_icon->setPixmap(g_icon_microphone.pixmap(24, 24));
321 					m_audio_previewer = new AudioPreviewer(m_preview_page2);
322 
323 					QVBoxLayout *layout = new QVBoxLayout(m_preview_page2);
324 					layout->setMargin(0);
325 					layout->addWidget(m_video_previewer);
326 					{
327 						QHBoxLayout *layout2 = new QHBoxLayout();
328 						layout->addLayout(layout2);
329 						layout2->addStretch();
330 						layout2->addWidget(m_label_mic_icon);
331 						layout2->addWidget(m_audio_previewer);
332 						layout2->addStretch();
333 					}
334 				}
335 				m_pushbutton_preview_start_stop = new QPushButton(groupbox_preview);
336 
337 				connect(m_pushbutton_preview_start_stop, SIGNAL(clicked()), this, SLOT(OnPreviewStartStop()));
338 
339 				QVBoxLayout *layout = new QVBoxLayout(groupbox_preview);
340 				{
341 					m_stacked_layout_preview = new QStackedLayout();
342 					layout->addLayout(m_stacked_layout_preview);
343 					m_stacked_layout_preview->addWidget(m_preview_page1);
344 					m_stacked_layout_preview->addWidget(m_preview_page2);
345 				}
346 				layout->addWidget(m_pushbutton_preview_start_stop);
347 			}
348 
349 			splitter_horizontal->addWidget(groupbox_information);
350 			splitter_horizontal->addWidget(groupbox_preview);
351 			splitter_horizontal->setStretchFactor(0, 1);
352 			splitter_horizontal->setStretchFactor(1, 3);
353 		}
354 		QGroupBox *groupbox_log = new QGroupBox(tr("Log"), splitter_vertical);
355 		{
356 			m_textedit_log = new QTextEditSmall(groupbox_log);
357 			m_textedit_log->setReadOnly(true);
358 
359 			QVBoxLayout *layout = new QVBoxLayout(groupbox_log);
360 			layout->addWidget(m_textedit_log);
361 		}
362 
363 		splitter_vertical->addWidget(splitter_horizontal);
364 		splitter_vertical->addWidget(groupbox_log);
365 		splitter_vertical->setStretchFactor(0, 3);
366 		splitter_vertical->setStretchFactor(1, 1);
367 	}
368 
369 	QPushButton *button_cancel = new QPushButton(g_icon_cancel, tr("Cancel recording"), this);
370 	QPushButton *button_save = new QPushButton(g_icon_save, tr("Save recording"), this);
371 
372 	if(CommandLineOptions::GetSysTray()) {
373 		m_systray_icon = new QSystemTrayIcon(g_icon_ssr_idle, m_main_window);
374 		QMenu *menu = new QMenu(m_main_window);
375 		m_systray_action_start_pause = menu->addAction(QString(), this, SLOT(OnRecordStartPause()));
376 		m_systray_action_start_pause->setIconVisibleInMenu(true);
377 		m_systray_action_cancel = menu->addAction(g_icon_cancel, tr("Cancel recording"), this, SLOT(OnRecordCancel()));
378 		m_systray_action_cancel->setIconVisibleInMenu(true);
379 		m_systray_action_save = menu->addAction(g_icon_save, tr("Save recording"), this, SLOT(OnRecordSave()));
380 		m_systray_action_save->setIconVisibleInMenu(true);
381 		menu->addSeparator();
382 		m_systray_action_show_hide = menu->addAction(QString(), m_main_window, SLOT(OnShowHide()));
383 		m_systray_action_show_hide->setIconVisibleInMenu(true);
384 		m_systray_action_quit = menu->addAction(g_icon_quit, tr("Quit"), m_main_window, SLOT(close()));
385 		m_systray_action_quit->setIconVisibleInMenu(true);
386 		m_systray_icon->setContextMenu(menu);
387 	} else {
388 		m_systray_icon = NULL;
389 	}
390 
391 	connect(button_cancel, SIGNAL(clicked()), this, SLOT(OnRecordCancel()));
392 	connect(button_save, SIGNAL(clicked()), this, SLOT(OnRecordSave()));
393 	if(m_systray_icon != NULL)
394 		connect(m_systray_icon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), m_main_window, SLOT(OnSysTrayActivated(QSystemTrayIcon::ActivationReason)));
395 
396 	QVBoxLayout *layout = new QVBoxLayout(this);
397 	layout->addWidget(groupbox_recording);
398 	layout->addWidget(splitter_vertical);
399 	{
400 		QHBoxLayout *layout2 = new QHBoxLayout();
401 		layout->addLayout(layout2);
402 		layout2->addWidget(button_cancel);
403 		layout2->addWidget(button_save);
404 	}
405 
406 	m_stdin_notifier = new QSocketNotifier(0, QSocketNotifier::Read, this);
407 	connect(m_stdin_notifier, SIGNAL(activated(int)), this, SLOT(OnStdin()));
408 
409 	m_timer_schedule = new QTimer(this);
410 	m_timer_schedule->setSingleShot(true);
411 	m_timer_update_info = new QTimer(this);
412 	connect(m_timer_schedule, SIGNAL(timeout()), this, SLOT(OnScheduleTimer()));
413 	connect(m_timer_update_info, SIGNAL(timeout()), this, SLOT(OnUpdateInformation()));
414 	connect(&m_hotkey_start_pause, SIGNAL(Triggered()), this, SLOT(OnRecordStartPause()), Qt::QueuedConnection);
415 	connect(Logger::GetInstance(), SIGNAL(NewLine(Logger::enum_type,QString)), this, SLOT(OnNewLogLine(Logger::enum_type,QString)), Qt::QueuedConnection);
416 
417 	UpdateSysTray();
418 	UpdateRecordButton();
419 	UpdateSchedule();
420 	UpdatePreview();
421 	OnUpdateRecordingFrame();
422 
423 	if(m_systray_icon != NULL)
424 		m_systray_icon->show();
425 
426 }
427 
~PageRecord()428 PageRecord::~PageRecord() {
429 	StopPage(false);
430 }
431 
ShouldBlockClose()432 bool PageRecord::ShouldBlockClose() {
433 	if(m_output_manager != NULL) {
434 		if(MessageBox(QMessageBox::Warning, this, MainWindow::WINDOW_CAPTION,
435 					  tr("You have not saved the current recording yet, if you quit now it will be lost.\n"
436 						 "Are you sure that you want to quit?"), BUTTON_YES | BUTTON_NO, BUTTON_YES) != BUTTON_YES) {
437 			return true;
438 		}
439 	}
440 	return false;
441 }
442 
UpdateShowHide()443 void PageRecord::UpdateShowHide() {
444 	if(m_systray_icon == NULL)
445 		return;
446 	if(m_main_window->isVisible()) {
447 		m_systray_action_show_hide->setText(tr("Hide window"));
448 	} else {
449 		m_systray_action_show_hide->setText(tr("Show window"));
450 	}
451 }
452 
LoadSettings(QSettings * settings)453 void PageRecord::LoadSettings(QSettings *settings) {
454 	SetHotkeyEnabled(settings->value("record/hotkey_enable", true).toBool());
455 	SetHotkeyCtrlEnabled(settings->value("record/hotkey_ctrl", false).toBool());
456 	SetHotkeyShiftEnabled(settings->value("record/hotkey_shift", false).toBool());
457 	SetHotkeyAltEnabled(settings->value("record/hotkey_alt", false).toBool());
458 	SetHotkeySuperEnabled(settings->value("record/hotkey_super", true).toBool());
459 	SetHotkeyKey(settings->value("record/hotkey_key", 'r' - 'a').toUInt());
460 #if SSR_USE_ALSA
461 	SetSoundNotificationsEnabled(settings->value("record/sound_notifications_enable", false).toBool());
462 #endif
463 	SetShowRecordingArea(settings->value("record/show_recording_area", false).toBool());
464 	SetPreviewFrameRate(settings->value("record/preview_frame_rate", 10).toUInt());
465 	SetScheduleTimeZone(StringToEnum(settings->value("record/schedule_time_zone", QString()).toString(), SCHEDULE_TIME_ZONE_LOCAL));
466 	unsigned int num_entries = clamp(settings->value("record/schedule_num_entries", 0).toUInt(), 0u, 1000u);
467 	m_schedule_entries.clear();
468 	m_schedule_entries.resize(num_entries);
469 	Qt::TimeSpec spec = SCHEDULE_TIME_ZONE_TIMESPECS[GetScheduleTimeZone()];
470 	for(unsigned int i = 0; i < num_entries; ++i) {
471 		QString timestring = settings->value(QString("record/schedule_entry%1_time").arg(i), QString()).toString();
472 		QString actionstring = settings->value(QString("record/schedule_entry%1_action").arg(i), QString()).toString();
473 		m_schedule_entries[i].time = QDateTime::fromString(timestring, "yyyy-MM-dd hh:mm:ss");
474 		m_schedule_entries[i].time.setTimeSpec(spec);
475 		m_schedule_entries[i].action = StringToEnum(actionstring, SCHEDULE_ACTION_START);
476 	}
477 	OnUpdateHotkeyFields();
478 #if SSR_USE_ALSA
479 	OnUpdateSoundNotifications();
480 #endif
481 	OnUpdateRecordingFrame();
482 }
483 
SaveSettings(QSettings * settings)484 void PageRecord::SaveSettings(QSettings *settings) {
485 	settings->setValue("record/hotkey_enable", IsHotkeyEnabled());
486 	settings->setValue("record/hotkey_ctrl", IsHotkeyCtrlEnabled());
487 	settings->setValue("record/hotkey_shift", IsHotkeyShiftEnabled());
488 	settings->setValue("record/hotkey_alt", IsHotkeyAltEnabled());
489 	settings->setValue("record/hotkey_super", IsHotkeySuperEnabled());
490 	settings->setValue("record/hotkey_key", GetHotkeyKey());
491 #if SSR_USE_ALSA
492 	settings->setValue("record/sound_notifications_enable", AreSoundNotificationsEnabled());
493 #endif
494 	settings->setValue("record/show_recording_area", GetShowRecordingArea());
495 	settings->setValue("record/preview_frame_rate", GetPreviewFrameRate());
496 	settings->setValue("record/schedule_time_zone", EnumToString(GetScheduleTimeZone()));
497 	settings->setValue("record/schedule_num_entries", (unsigned int) m_schedule_entries.size());
498 	for(unsigned int i = 0; i < m_schedule_entries.size(); ++i) {
499 		settings->setValue(QString("record/schedule_entry%1_time").arg(i), m_schedule_entries[i].time.toString("yyyy-MM-dd hh:mm:ss"));
500 		settings->setValue(QString("record/schedule_entry%1_action").arg(i), EnumToString(m_schedule_entries[i].action));
501 	}
502 }
503 
TryStartPage()504 bool PageRecord::TryStartPage() {
505 	if(m_page_started)
506 		return true;
507 	if(!m_main_window->Validate())
508 		return false;
509 	m_main_window->GoPageRecord();
510 	assert(m_page_started);
511 	return true;
512 }
513 
StartPage()514 void PageRecord::StartPage() {
515 
516 	if(m_page_started)
517 		return;
518 
519 	assert(!m_input_started);
520 	assert(!m_output_started);
521 
522 	// save the settings in case libav/ffmpeg decides to kill the process
523 	m_main_window->SaveSettings();
524 
525 	// clear the log
526 	m_textedit_log->clear();
527 
528 	// clear the preview
529 	if(m_previewing) {
530 		m_video_previewer->Reset();
531 		m_audio_previewer->Reset();
532 	}
533 
534 	PageInput *page_input = m_main_window->GetPageInput();
535 	PageOutput *page_output = m_main_window->GetPageOutput();
536 
537 	// get the video input settings
538 	m_video_area = page_input->GetVideoArea();
539 	m_video_area_follow_fullscreen = page_input->GetVideoAreaFollowFullscreen();
540 #if SSR_USE_V4L2
541 	m_v4l2_device = page_input->GetVideoV4L2Device();
542 #endif
543 	m_video_x = page_input->GetVideoX();
544 	m_video_y = page_input->GetVideoY();
545 #if SSR_USE_OPENGL_RECORDING
546 	if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) {
547 		m_video_in_width = 0;
548 		m_video_in_height = 0;
549 	} else {
550 #else
551 	{
552 #endif
553 		m_video_in_width = page_input->GetVideoW();
554 		m_video_in_height = page_input->GetVideoH();
555 	}
556 	m_video_in_width = page_input->GetVideoW();
557 	m_video_in_height = page_input->GetVideoH();
558 	m_video_frame_rate = page_input->GetVideoFrameRate();
559 	m_video_scaling = page_input->GetVideoScalingEnabled();
560 	m_video_scaled_width = page_input->GetVideoScaledW();
561 	m_video_scaled_height = page_input->GetVideoScaledH();
562 	m_video_record_cursor = page_input->GetVideoRecordCursor();
563 
564 	// get the audio input settings
565 	m_audio_enabled = page_input->GetAudioEnabled();
566 	m_audio_channels = 2;
567 	m_audio_sample_rate = 48000;
568 	m_audio_backend = page_input->GetAudioBackend();
569 #if SSR_USE_ALSA
570 	m_alsa_source = page_input->GetALSASourceName();
571 #endif
572 #if SSR_USE_PULSEAUDIO
573 	m_pulseaudio_source = page_input->GetPulseAudioSourceName();
574 #endif
575 #if SSR_USE_JACK
576 	bool jack_connect_system_capture = page_input->GetJackConnectSystemCapture();
577 	bool jack_connect_system_playback = page_input->GetJackConnectSystemPlayback();
578 #endif
579 
580 	// override sample rate for problematic cases (these are hard-coded for now)
581 	if(page_output->GetContainer() == PageOutput::CONTAINER_OTHER && page_output->GetContainerAVName() == "flv") {
582 		m_audio_sample_rate = 44100;
583 	}
584 
585 #if SSR_USE_OPENGL_RECORDING
586 	// get the glinject settings
587 	QString glinject_channel = page_input->GetGLInjectChannel();
588 	bool glinject_relax_permissions = page_input->GetGLInjectRelaxPermissions();
589 	QString glinject_command = page_input->GetGLInjectCommand();
590 	QString glinject_working_directory = page_input->GetGLInjectWorkingDirectory();
591 	bool glinject_auto_launch = page_input->GetGLInjectAutoLaunch();
592 	bool glinject_limit_fps = page_input->GetGLInjectLimitFPS();
593 #endif
594 
595 	// get file settings
596 	m_file_base = page_output->GetFile();
597 	m_file_protocol = page_output->GetFileProtocol();
598 	m_separate_files = page_output->GetSeparateFiles();
599 	m_add_timestamp = page_output->GetAddTimestamp();
600 
601 	// get the output settings
602 	m_output_settings.file = QString(); // will be set later
603 	m_output_settings.container_avname = page_output->GetContainerAVName();
604 
605 	m_output_settings.video_codec_avname = page_output->GetVideoCodecAVName();
606 	m_output_settings.video_kbit_rate = page_output->GetVideoKBitRate();
607 	m_output_settings.video_options.clear();
608 	m_output_settings.video_width = 0;
609 	m_output_settings.video_height = 0;
610 	m_output_settings.video_frame_rate = m_video_frame_rate;
611 	m_output_settings.video_allow_frame_skipping = page_output->GetVideoAllowFrameSkipping();
612 
613 	m_output_settings.audio_codec_avname = (m_audio_enabled)? page_output->GetAudioCodecAVName() : QString();
614 	m_output_settings.audio_kbit_rate = page_output->GetAudioKBitRate();
615 	m_output_settings.audio_options.clear();
616 	m_output_settings.audio_channels = m_audio_channels;
617 	m_output_settings.audio_sample_rate = m_audio_sample_rate;
618 
619 	// some codec-specific things
620 	// you can get more information about all these options by running 'ffmpeg -h' or 'avconv -h' from a terminal
621 	switch(page_output->GetVideoCodec()) {
622 		case PageOutput::VIDEO_CODEC_H264: {
623 			// x264 has a 'constant quality' mode, where the bit rate is simply set to whatever is needed to keep a certain quality. The quality is set
624 			// with the 'crf' option. 'preset' changes the encoding speed (and hence the efficiency of the compression) but doesn't really influence the quality,
625 			// which is great because it means you don't have to experiment with different bit rates and different speeds to get good results.
626 			m_output_settings.video_options.push_back(std::make_pair(QString("crf"), QString::number(page_output->GetH264CRF())));
627 			m_output_settings.video_options.push_back(std::make_pair(QString("preset"), EnumToString(page_output->GetH264Preset())));
628 			break;
629 		}
630 		case PageOutput::VIDEO_CODEC_VP8: {
631 			// The names of there parameters are very unintuitive. The two options we care about (because they change the speed) are 'deadline' and 'cpu-used'.
632 			// 'deadline=best' is unusably slow. 'deadline=good' is the normal setting, it tells the encoder to use the speed set with 'cpu-used'. Higher
633 			// numbers will use *less* CPU, confusingly, so a higher number is faster. I haven't done much testing with 'realtime' so I'm not sure if it's a good idea here.
634 			// It sounds useful, but I think it will use so much CPU that it will slow down the program that is being recorded.
635 			m_output_settings.video_options.push_back(std::make_pair(QString("deadline"), QString("good")));
636 			m_output_settings.video_options.push_back(std::make_pair(QString("cpu-used"), QString::number(page_output->GetVP8CPUUsed())));
637 			break;
638 		}
639 		case PageOutput::VIDEO_CODEC_OTHER: {
640 			m_output_settings.video_options = GetOptionsFromString(page_output->GetVideoOptions());
641 			break;
642 		}
643 		default: break; // to keep GCC happy
644 	}
645 	switch(page_output->GetAudioCodec()) {
646 		case PageOutput::AUDIO_CODEC_OTHER: {
647 			m_output_settings.audio_options = GetOptionsFromString(page_output->GetAudioOptions());
648 			break;
649 		}
650 		default: break; // to keep GCC happy
651 	}
652 
653 	// only show the recording frame option when using a fixed rectangle
654 	GroupVisible({m_checkbox_show_recording_area}, (m_video_area == PageInput::VIDEO_AREA_FIXED));
655 
656 	// hide the audio previewer if there is no audio
657 	GroupVisible({m_label_mic_icon, m_audio_previewer}, m_audio_enabled);
658 
659 	Logger::LogInfo("[PageRecord::StartPage] " + tr("Starting page ..."));
660 
661 
662 	try {
663 
664 #if SSR_USE_OPENGL_RECORDING
665 		// for OpenGL recording, create the input now
666 		if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) {
667 			if(glinject_auto_launch)
668 				GLInjectInput::LaunchApplication(glinject_channel, glinject_relax_permissions, glinject_command, glinject_working_directory);
669 			m_gl_inject_input.reset(new GLInjectInput(glinject_channel, glinject_relax_permissions, m_video_record_cursor, glinject_limit_fps, m_video_frame_rate));
670 		}
671 #endif
672 
673 #if SSR_USE_JACK
674 		if(m_audio_enabled) {
675 			// for JACK, start the input now
676 			if(m_audio_backend == PageInput::AUDIO_BACKEND_JACK)
677 				m_jack_input.reset(new JACKInput(jack_connect_system_capture, jack_connect_system_playback));
678 		}
679 #endif
680 
681 	} catch(...) {
682 		Logger::LogError("[PageRecord::StartPage] " + tr("Error: Something went wrong during initialization."));
683 #if SSR_USE_OPENGL_RECORDING
684 		m_gl_inject_input.reset();
685 #endif
686 #if SSR_USE_JACK
687 		m_jack_input.reset();
688 #endif
689 	}
690 
691 	Logger::LogInfo("[PageRecord::StartPage] " + tr("Started page."));
692 
693 	m_page_started = true;
694 	m_recorded_something = false;
695 	m_wait_saving = false;
696 	m_error_occurred = false;
697 	UpdateSysTray();
698 #if SSR_USE_ALSA
699 	OnUpdateSoundNotifications();
700 #endif
701 
702 	UpdateInput();
703 	OnUpdateRecordingFrame();
704 
705 	OnUpdateInformation();
706 	m_timer_update_info->start(1000);
707 
708 	m_schedule_active = false;
709 	UpdateSchedule();
710 
711 }
712 
713 void PageRecord::StopPage(bool save) {
714 
715 	if(!m_page_started)
716 		return;
717 
718 	m_schedule_active = false;
719 	UpdateSchedule();
720 
721 	StopOutput(true);
722 	StopInput();
723 
724 	Logger::LogInfo("[PageRecord::StopPage] " + tr("Stopping page ..."));
725 
726 	if(m_output_manager != NULL) {
727 
728 		// stop the output
729 		if(save)
730 			FinishOutput();
731 		m_output_manager.reset();
732 
733 		// delete the file if it isn't needed
734 		if(!save && m_file_protocol.isNull()) {
735 			if(QFileInfo(m_output_settings.file).exists())
736 				QFile(m_output_settings.file).remove();
737 		}
738 
739 	}
740 
741 #if SSR_USE_OPENGL_RECORDING
742 	// stop GLInject input
743 	m_gl_inject_input.reset();
744 #endif
745 
746 #if SSR_USE_JACK
747 	// stop JACK input
748 	m_jack_input.reset();
749 #endif
750 
751 	Logger::LogInfo("[PageRecord::StopPage] " + tr("Stopped page."));
752 
753 	m_page_started = false;
754 	UpdateSysTray();
755 #if SSR_USE_ALSA
756 	OnUpdateSoundNotifications();
757 #endif
758 	OnUpdateRecordingFrame();
759 
760 	m_timer_update_info->stop();
761 	OnUpdateInformation();
762 
763 }
764 
765 void PageRecord::StartOutput() {
766 	assert(m_page_started);
767 
768 	if(m_output_started)
769 		return;
770 
771 #if SSR_USE_ALSA
772 	if(m_simple_synth != NULL) {
773 		m_simple_synth->PlaySequence(SEQUENCE_RECORD_START.data(), SEQUENCE_RECORD_START.size());
774 		usleep(200000);
775 	}
776 #endif
777 
778 	try {
779 
780 		Logger::LogInfo("[PageRecord::StartOutput] " + tr("Starting output ..."));
781 
782 		if(m_output_manager == NULL) {
783 
784 			// set the file name
785 			m_output_settings.file = GetNewSegmentFile(m_file_base, m_add_timestamp);
786 
787 			// for X11 recording, update the video size (if possible)
788 			if(m_x11_input != NULL)
789 				m_x11_input->GetCurrentSize(&m_video_in_width, &m_video_in_height);
790 
791 #if SSR_USE_OPENGL_RECORDING
792 			// for OpenGL recording, detect the video size
793 			if(m_video_area == PageInput::VIDEO_AREA_GLINJECT && !m_video_scaling) {
794 				if(m_gl_inject_input == NULL) {
795 					Logger::LogError("[PageRecord::StartOutput] " + tr("Error: Could not get the size of the OpenGL application because the GLInject input has not been created."));
796 					throw GLInjectException();
797 				}
798 				m_gl_inject_input->GetCurrentSize(&m_video_in_width, &m_video_in_height);
799 				if(m_video_in_width == 0 && m_video_in_height == 0) {
800 					Logger::LogError("[PageRecord::StartOutput] " + tr("Error: Could not get the size of the OpenGL application. Either the "
801 									 "application wasn't started correctly, or the application hasn't created an OpenGL window yet. If "
802 									 "you want to start recording before starting the application, you have to enable scaling and enter "
803 									 "the video size manually."));
804 					throw GLInjectException();
805 				}
806 			}
807 #endif
808 
809 			// calculate the output width and height
810 			if(m_video_scaling) {
811 				// Only even width and height is allowed because some pixel formats (e.g. YUV420) require this.
812 				m_output_settings.video_width = m_video_scaled_width / 2 * 2;
813 				m_output_settings.video_height = m_video_scaled_height / 2 * 2;
814 #if SSR_USE_OPENGL_RECORDING
815 			} else if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) {
816 				// The input size is the size of the OpenGL application and can't be changed. The output size is set to the current size of the application.
817 				m_output_settings.video_width = m_video_in_width / 2 * 2;
818 				m_output_settings.video_height = m_video_in_height / 2 * 2;
819 #endif
820 			} else {
821 				// If the user did not explicitly select scaling, then don't force scaling just because the recording area is one pixel too large.
822 				// One missing row/column of pixels is probably better than a blurry video (and scaling is SLOW).
823 				m_video_in_width = m_video_in_width / 2 * 2;
824 				m_video_in_height = m_video_in_height / 2 * 2;
825 				m_output_settings.video_width = m_video_in_width;
826 				m_output_settings.video_height = m_video_in_height;
827 			}
828 
829 			// start the output
830 			m_output_manager.reset(new OutputManager(m_output_settings));
831 
832 		} else {
833 
834 			// start a new segment
835 			m_output_manager->GetSynchronizer()->NewSegment();
836 
837 		}
838 
839 		Logger::LogInfo("[PageRecord::StartOutput] " + tr("Started output."));
840 
841 		m_output_started = true;
842 		m_recorded_something = true;
843 		UpdateSysTray();
844 		UpdateRecordButton();
845 		UpdateInput();
846 		OnUpdateRecordingFrame();
847 
848 	} catch(...) {
849 		Logger::LogError("[PageRecord::StartOutput] " + tr("Error: Something went wrong during initialization."));
850 	}
851 
852 }
853 
854 void PageRecord::StopOutput(bool final) {
855 	assert(m_page_started);
856 
857 	if(!m_output_started)
858 		return;
859 
860 	Logger::LogInfo("[PageRecord::StopOutput] " + tr("Stopping output ..."));
861 
862 	// if final, then StopPage will stop the output (and delete the file if needed)
863 	if(m_separate_files && !final) {
864 
865 		// stop the output
866 		FinishOutput();
867 		m_output_manager.reset();
868 
869 		// change the file name
870 		m_output_settings.file = QString();
871 
872 		// reset the output video size
873 		m_output_settings.video_width = 0;
874 		m_output_settings.video_height = 0;
875 
876 	}
877 
878 	Logger::LogInfo("[PageRecord::StopOutput] " + tr("Stopped output."));
879 
880 #if SSR_USE_ALSA
881 	// if final, don't play the notification (it would get interrupted anyway)
882 	if(m_simple_synth != NULL && !final)
883 		m_simple_synth->PlaySequence(SEQUENCE_RECORD_STOP.data(), SEQUENCE_RECORD_STOP.size());
884 #endif
885 
886 	m_output_started = false;
887 	UpdateSysTray();
888 	UpdateRecordButton();
889 	UpdateInput();
890 	OnUpdateRecordingFrame();
891 
892 }
893 
894 void PageRecord::StartInput() {
895 	assert(m_page_started);
896 
897 	if(m_input_started)
898 		return;
899 
900 	assert(m_x11_input == NULL);
901 #if SSR_USE_ALSA
902 	assert(m_alsa_input == NULL);
903 #endif
904 #if SSR_USE_PULSEAUDIO
905 	assert(m_pulseaudio_input == NULL);
906 #endif
907 
908 	try {
909 
910 		Logger::LogInfo("[PageRecord::StartInput] " + tr("Starting input ..."));
911 
912 		// start the video input
913 		if(m_video_area == PageInput::VIDEO_AREA_SCREEN || m_video_area == PageInput::VIDEO_AREA_FIXED || m_video_area == PageInput::VIDEO_AREA_CURSOR) {
914 			m_x11_input.reset(new X11Input(m_video_x, m_video_y, m_video_in_width, m_video_in_height, m_video_record_cursor,
915 										   m_video_area == PageInput::VIDEO_AREA_CURSOR, m_video_area_follow_fullscreen));
916 			connect(m_x11_input.get(), SIGNAL(CurrentRectangleChanged()), this, SLOT(OnUpdateRecordingFrame()), Qt::QueuedConnection);
917 		}
918 #if SSR_USE_OPENGL_RECORDING
919 		if(m_video_area == PageInput::VIDEO_AREA_GLINJECT) {
920 			if(m_gl_inject_input == NULL) {
921 				Logger::LogError("[PageRecord::StartInput] " + tr("Error: Could not start the GLInject input because it has not been created."));
922 				throw GLInjectException();
923 			}
924 			m_gl_inject_input->SetCapturing(true);
925 		}
926 #endif
927 #if SSR_USE_V4L2
928 		if(m_video_area == PageInput::VIDEO_AREA_V4L2) {
929 			m_v4l2_input.reset(new V4L2Input(m_v4l2_device, m_video_in_width, m_video_in_height));
930 			m_v4l2_input->GetCurrentSize(&m_video_in_width, &m_video_in_height);
931 		}
932 #endif
933 
934 		// start the audio input
935 		if(m_audio_enabled) {
936 #if SSR_USE_ALSA
937 			if(m_audio_backend == PageInput::AUDIO_BACKEND_ALSA)
938 				m_alsa_input.reset(new ALSAInput(m_alsa_source, m_audio_sample_rate));
939 #endif
940 #if SSR_USE_PULSEAUDIO
941 			if(m_audio_backend == PageInput::AUDIO_BACKEND_PULSEAUDIO)
942 				m_pulseaudio_input.reset(new PulseAudioInput(m_pulseaudio_source, m_audio_sample_rate));
943 #endif
944 			// JACK was started when the page was started
945 		}
946 
947 		Logger::LogInfo("[PageRecord::StartInput] " + tr("Started input."));
948 
949 		m_input_started = true;
950 
951 	} catch(...) {
952 		Logger::LogError("[PageRecord::StartInput] " + tr("Error: Something went wrong during initialization."));
953 		m_x11_input.reset();
954 #if SSR_USE_OPENGL_RECORDING
955 		if(m_gl_inject_input != NULL)
956 			m_gl_inject_input->SetCapturing(false);
957 #endif
958 #if SSR_USE_V4L2
959 		m_v4l2_input.reset();
960 #endif
961 #if SSR_USE_ALSA
962 		m_alsa_input.reset();
963 #endif
964 #if SSR_USE_PULSEAUDIO
965 		m_pulseaudio_input.reset();
966 #endif
967 		// JACK shouldn't stop until the page stops
968 		return;
969 	}
970 
971 }
972 
973 void PageRecord::StopInput() {
974 	assert(m_page_started);
975 
976 	if(!m_input_started)
977 		return;
978 
979 	Logger::LogInfo("[PageRecord::StopInput] " + tr("Stopping input ..."));
980 
981 	m_x11_input.reset();
982 #if SSR_USE_OPENGL_RECORDING
983 	if(m_gl_inject_input != NULL)
984 		m_gl_inject_input->SetCapturing(false);
985 #endif
986 #if SSR_USE_V4L2
987 	m_v4l2_input.reset();
988 #endif
989 #if SSR_USE_ALSA
990 	m_alsa_input.reset();
991 #endif
992 #if SSR_USE_PULSEAUDIO
993 	m_pulseaudio_input.reset();
994 #endif
995 	// JACK shouldn't stop until the page stops
996 
997 	Logger::LogInfo("[PageRecord::StopInput] " + tr("Stopped input."));
998 
999 	m_input_started = false;
1000 
1001 }
1002 
1003 void PageRecord::FinishOutput() {
1004 	assert(m_output_manager != NULL);
1005 
1006 	// tell the output manager to finish
1007 	m_output_manager->Finish();
1008 
1009 	// wait until it has actually finished
1010 	m_wait_saving = true;
1011 	//unsigned int frames_left = m_output_manager->GetVideoEncoder()->GetFrameLatency();
1012 	unsigned int frames_done = 0, frames_total = 0;
1013 	QProgressDialog dialog(tr("Encoding remaining data ..."), QString(), 0, frames_total, this);
1014 	dialog.setWindowTitle(MainWindow::WINDOW_CAPTION);
1015 	dialog.setWindowModality(Qt::WindowModal);
1016 	dialog.setCancelButton(NULL);
1017 	dialog.setMinimumDuration(500);
1018 	while(!m_output_manager->IsFinished()) {
1019 		unsigned int frames = m_output_manager->GetTotalQueuedFrameCount();
1020 		if(frames > frames_total)
1021 			frames_total = frames;
1022 		if(frames_total - frames > frames_done)
1023 			frames_done = frames_total - frames;
1024 		//qDebug() << "frames_done" << frames_done << "frames_total" << frames_total << "frames" << frames;
1025 		dialog.setMaximum(frames_total);
1026 		dialog.setValue(frames_done);
1027 		usleep(20000);
1028 	}
1029 	m_wait_saving = false;
1030 
1031 }
1032 
1033 void PageRecord::UpdateInput() {
1034 	assert(m_page_started);
1035 
1036 	if(m_output_started || m_previewing) {
1037 		StartInput();
1038 	} else {
1039 		StopInput();
1040 	}
1041 
1042 	// get sources
1043 	VideoSource *video_source = NULL;
1044 	AudioSource *audio_source = NULL;
1045 	if(m_video_area == PageInput::VIDEO_AREA_SCREEN || m_video_area == PageInput::VIDEO_AREA_FIXED|| m_video_area == PageInput::VIDEO_AREA_CURSOR)
1046 		video_source = m_x11_input.get();
1047 #if SSR_USE_OPENGL_RECORDING
1048 	if(m_video_area == PageInput::VIDEO_AREA_GLINJECT)
1049 		video_source = m_gl_inject_input.get();
1050 #endif
1051 #if SSR_USE_V4L2
1052 	if(m_video_area == PageInput::VIDEO_AREA_V4L2)
1053 		video_source = m_v4l2_input.get();
1054 #endif
1055 	if(m_audio_enabled) {
1056 #if SSR_USE_ALSA
1057 		if(m_audio_backend == PageInput::AUDIO_BACKEND_ALSA)
1058 			audio_source = m_alsa_input.get();
1059 #endif
1060 #if SSR_USE_PULSEAUDIO
1061 		if(m_audio_backend == PageInput::AUDIO_BACKEND_PULSEAUDIO)
1062 			audio_source = m_pulseaudio_input.get();
1063 #endif
1064 #if SSR_USE_JACK
1065 		if(m_audio_backend == PageInput::AUDIO_BACKEND_JACK)
1066 			audio_source = m_jack_input.get();
1067 #endif
1068 	}
1069 
1070 	// connect sinks
1071 	if(m_output_manager != NULL) {
1072 		if(m_output_started) {
1073 			m_output_manager->GetSynchronizer()->ConnectVideoSource(video_source, PRIORITY_RECORD);
1074 			m_output_manager->GetSynchronizer()->ConnectAudioSource(audio_source, PRIORITY_RECORD);
1075 		} else {
1076 			m_output_manager->GetSynchronizer()->ConnectVideoSource(NULL);
1077 			m_output_manager->GetSynchronizer()->ConnectAudioSource(NULL);
1078 		}
1079 	}
1080 	if(m_previewing) {
1081 		m_video_previewer->ConnectVideoSource(video_source, PRIORITY_PREVIEW);
1082 		m_audio_previewer->ConnectAudioSource(audio_source, PRIORITY_PREVIEW);
1083 	} else {
1084 		m_video_previewer->ConnectVideoSource(NULL);
1085 		m_audio_previewer->ConnectAudioSource(NULL);
1086 	}
1087 
1088 }
1089 
1090 void PageRecord::UpdateSysTray() {
1091 	if(m_systray_icon == NULL)
1092 		return;
1093 	GroupEnabled({m_systray_action_cancel, m_systray_action_save}, m_page_started);
1094 	if(m_page_started) {
1095 		if(m_error_occurred) {
1096 			m_systray_icon->setIcon(g_icon_ssr_error);
1097 		} else if(m_output_started) {
1098 			m_systray_icon->setIcon(g_icon_ssr_recording);
1099 		} else {
1100 			m_systray_icon->setIcon(g_icon_ssr_paused);
1101 		}
1102 	} else {
1103 		m_systray_icon->setIcon(g_icon_ssr_idle);
1104 	}
1105 	if(m_page_started && m_output_started) {
1106 		m_systray_action_start_pause->setIcon(g_icon_pause);
1107 		m_systray_action_start_pause->setText(tr("Pause recording"));
1108 	} else {
1109 		m_systray_action_start_pause->setIcon(g_icon_record);
1110 		m_systray_action_start_pause->setText(tr("Start recording"));
1111 	}
1112 }
1113 
1114 void PageRecord::UpdateRecordButton() {
1115 	if(m_output_started) {
1116 		m_pushbutton_record->setIcon(g_icon_pause);
1117 		m_pushbutton_record->setText(tr("Pause recording"));
1118 	} else {
1119 		m_pushbutton_record->setIcon(g_icon_record);
1120 		m_pushbutton_record->setText(tr("Start recording"));
1121 	}
1122 }
1123 
1124 void PageRecord::UpdateSchedule() {
1125 	if(!m_page_started)
1126 		return;
1127 	if(m_schedule_active) {
1128 		m_pushbutton_schedule_activate->setText(tr("Deactivate schedule"));
1129 		m_schedule_position = 0;
1130 		QDateTime now = QDateTime::currentDateTimeUtc();
1131 		while(m_schedule_position < m_schedule_entries.size()) {
1132 			ScheduleEntry &entry = m_schedule_entries[m_schedule_position];
1133 			if(now < entry.time)
1134 				break;
1135 			++m_schedule_position;
1136 		}
1137 	} else {
1138 		m_pushbutton_schedule_activate->setText(tr("Activate schedule"));
1139 	}
1140 	OnScheduleTimer();
1141 }
1142 
1143 void PageRecord::UpdatePreview() {
1144 	if(m_previewing) {
1145 		m_video_previewer->SetFrameRate(GetPreviewFrameRate());
1146 		m_stacked_layout_preview->setCurrentWidget(m_preview_page2);
1147 		m_pushbutton_preview_start_stop->setText(tr("Stop preview"));
1148 	} else {
1149 		m_stacked_layout_preview->setCurrentWidget(m_preview_page1);
1150 		m_pushbutton_preview_start_stop->setText(tr("Start preview"));
1151 	}
1152 }
1153 
1154 QString PageRecord::ReadStdinCommand() {
1155 	for(int i = 0; i < m_stdin_buffer.size(); ++i) {
1156 		if(m_stdin_buffer[i] == '\n') {
1157 			QString command = QString::fromUtf8(m_stdin_buffer.data(), i);
1158 			m_stdin_buffer = QByteArray(m_stdin_buffer.data() + i + 1, m_stdin_buffer.size() - i - 1);
1159 			return command;
1160 		}
1161 	}
1162 	return QString();
1163 }
1164 
1165 void PageRecord::OnUpdateHotkeyFields() {
1166 	bool enabled = IsHotkeyEnabled();
1167 	GroupEnabled({m_checkbox_hotkey_ctrl, m_checkbox_hotkey_shift, m_checkbox_hotkey_alt, m_checkbox_hotkey_super, m_combobox_hotkey_key}, enabled);
1168 	OnUpdateHotkey();
1169 }
1170 
1171 void PageRecord::OnUpdateHotkey() {
1172 	if(IsHotkeyEnabled()) {
1173 		unsigned int modifiers = 0;
1174 		if(IsHotkeyCtrlEnabled()) modifiers |= ControlMask;
1175 		if(IsHotkeyShiftEnabled()) modifiers |= ShiftMask;
1176 		if(IsHotkeyAltEnabled()) modifiers |= Mod1Mask;
1177 		if(IsHotkeySuperEnabled()) modifiers |= Mod4Mask;
1178 		m_hotkey_start_pause.Bind(XK_A + GetHotkeyKey(), modifiers);
1179 	} else {
1180 		m_hotkey_start_pause.Unbind();
1181 	}
1182 }
1183 
1184 #if SSR_USE_ALSA
1185 void PageRecord::OnUpdateSoundNotifications() {
1186 	if(m_page_started && AreSoundNotificationsEnabled()) {
1187 		if(m_simple_synth == NULL) {
1188 			try {
1189 				m_simple_synth.reset(new SimpleSynth("default", 48000));
1190 			} catch(...) {
1191 				Logger::LogError("[PageRecord::OnUpdateSoundNotifications] " + tr("Error: Something went wrong while creating the synth."));
1192 			}
1193 		}
1194 	} else {
1195 		m_simple_synth.reset();
1196 	}
1197 }
1198 #endif
1199 
1200 void PageRecord::OnUpdateRecordingFrame() {
1201 	if(m_page_started && m_video_area == PageInput::VIDEO_AREA_FIXED && GetShowRecordingArea()) {
1202 		if(m_recording_frame == NULL)
1203 			m_recording_frame.reset(new RecordingFrameWindow(this, true));
1204 		if(m_x11_input == NULL) {
1205 			m_recording_frame->SetRectangle(QRect(m_video_x, m_video_y, m_video_in_width, m_video_in_height));
1206 		} else {
1207 			unsigned int x, y, width, height;
1208 			m_x11_input->GetCurrentRectangle(&x, &y, &width, &height);
1209 			m_recording_frame->SetRectangle(QRect(x, y, width, height));
1210 		}
1211 	} else {
1212 		m_recording_frame.reset();
1213 	}
1214 }
1215 
1216 void PageRecord::OnRecordStart() {
1217 	if(m_main_window->IsBusy())
1218 		return;
1219 	if(!TryStartPage())
1220 		return;
1221 	if(m_wait_saving)
1222 		return;
1223 	if(!m_output_started)
1224 		StartOutput();
1225 }
1226 
1227 void PageRecord::OnRecordPause() {
1228 	if(m_main_window->IsBusy())
1229 		return;
1230 	if(!m_page_started)
1231 		return;
1232 	if(m_wait_saving)
1233 		return;
1234 	if(m_output_started)
1235 		StopOutput(false);
1236 }
1237 
1238 void PageRecord::OnRecordStartPause() {
1239 	if(m_page_started && m_output_started) {
1240 		OnRecordPause();
1241 	} else {
1242 		OnRecordStart();
1243 	}
1244 }
1245 
1246 
1247 void PageRecord::OnRecordCancel(bool confirm) {
1248 	if(m_main_window->IsBusy())
1249 		return;
1250 	if(!m_page_started)
1251 		return;
1252 	if(m_wait_saving)
1253 		return;
1254 	if(m_output_manager != NULL && confirm) {
1255 		if(MessageBox(QMessageBox::Warning, this, MainWindow::WINDOW_CAPTION, tr("Are you sure that you want to cancel this recording?"),
1256 					  BUTTON_YES | BUTTON_NO, BUTTON_YES) != BUTTON_YES) {
1257 			return;
1258 		}
1259 	}
1260 	StopPage(false);
1261 	m_main_window->GoPageOutput();
1262 }
1263 
1264 void PageRecord::OnRecordSave(bool confirm) {
1265 	if(m_main_window->IsBusy())
1266 		return;
1267 	if(!m_page_started)
1268 		return;
1269 	if(m_wait_saving)
1270 		return;
1271 	if(!m_recorded_something && confirm) {
1272 		MessageBox(QMessageBox::Information, this, MainWindow::WINDOW_CAPTION, tr("You haven't recorded anything, there is nothing to save."),
1273 				   BUTTON_OK, BUTTON_OK);
1274 		return;
1275 	}
1276 	StopPage(true);
1277 	m_main_window->GoPageDone();
1278 }
1279 
1280 void PageRecord::OnScheduleTimer() {
1281 	if(!m_page_started)
1282 		return;
1283 	if(m_schedule_active) {
1284 		QDateTime now = QDateTime::currentDateTimeUtc();
1285 		while(m_schedule_position < m_schedule_entries.size()) {
1286 			ScheduleEntry &entry = m_schedule_entries[m_schedule_position];
1287 			if(now < entry.time)
1288 				break;
1289 			Logger::LogInfo("[PageRecord::OnScheduleTimer] " + tr("Triggering scheduled action '%1' ...").arg(EnumToString(entry.action)));
1290 			switch(entry.action) {
1291 				case SCHEDULE_ACTION_START: OnRecordStart(); break;
1292 				case SCHEDULE_ACTION_PAUSE: OnRecordPause(); break;
1293 				default: break; // to keep GCC happy
1294 			}
1295 			++m_schedule_position;
1296 		}
1297 		if(m_schedule_position < m_schedule_entries.size()) {
1298 			ScheduleEntry &entry = m_schedule_entries[m_schedule_position];
1299 			int64_t msec = now.msecsTo(entry.time);
1300 			m_label_schedule_status->setText(tr("Schedule: %1 in %2").arg(SCHEDULE_ACTION_TEXT[entry.action]).arg(ReadableTime(msec * 1000)));
1301 			if(msec < 1000) {
1302 				m_timer_schedule->start(msec);
1303 			} else {
1304 				m_timer_schedule->start((msec - 100) % 1000 + 100);
1305 			}
1306 		} else {
1307 			m_label_schedule_status->setText(tr("Schedule: (none)"));
1308 			m_timer_schedule->stop();
1309 		}
1310 	} else {
1311 		m_label_schedule_status->setText(tr("Schedule: (inactive)"));
1312 		m_timer_schedule->stop();
1313 	}
1314 }
1315 
1316 void PageRecord::OnScheduleActivate() {
1317 	if(m_main_window->IsBusy())
1318 		return;
1319 	if(!TryStartPage())
1320 		return;
1321 	if(!m_schedule_active) {
1322 		m_schedule_active = true;
1323 		UpdateSchedule();
1324 	}
1325 }
1326 
1327 void PageRecord::OnScheduleDeactivate() {
1328 	if(m_main_window->IsBusy())
1329 		return;
1330 	if(!m_page_started)
1331 		return;
1332 	if(m_schedule_active) {
1333 		m_schedule_active = false;
1334 		UpdateSchedule();
1335 	}
1336 }
1337 
1338 void PageRecord::OnScheduleActivateDeactivate() {
1339 	if(m_page_started && m_schedule_active) {
1340 		OnScheduleDeactivate();
1341 	} else {
1342 		OnScheduleActivate();
1343 	}
1344 }
1345 
1346 void PageRecord::OnScheduleEdit() {
1347 	DialogRecordSchedule dialog(this);
1348 	dialog.exec();
1349 	UpdateSchedule();
1350 }
1351 
1352 void PageRecord::OnPreviewStartStop() {
1353 	if(!m_page_started)
1354 		return;
1355 	if(m_wait_saving)
1356 		return;
1357 	m_previewing = !m_previewing;
1358 	if(m_previewing) {
1359 		m_video_previewer->Reset();
1360 		m_audio_previewer->Reset();
1361 	}
1362 	UpdatePreview();
1363 	UpdateInput();
1364 	OnUpdateRecordingFrame();
1365 }
1366 
1367 void PageRecord::OnStdin() {
1368 
1369 	// get available length
1370 	int len, res;
1371 	do {
1372 		res = ioctl(0, FIONREAD, &len);
1373 	} while(res == -1 && errno == EINTR);
1374 	if(res == -1) {
1375 		Logger::LogError("[PageRecord::OnStdin] " + tr("Standard input read error (%1).").arg("ioctl"));
1376 		m_stdin_notifier->setEnabled(false);
1377 		return;
1378 	}
1379 	if(len == 0) {
1380 		Logger::LogInfo("[PageRecord::OnStdin] " + tr("Standard input closed (%1).").arg("ioctl"));
1381 		m_stdin_notifier->setEnabled(false);
1382 		return;
1383 	}
1384 
1385 	// read data
1386 	QByteArray buffer(len, 0);
1387 	ssize_t bytes;
1388 	do {
1389 		bytes = read(0, buffer.data(), buffer.size());
1390 	} while(bytes == -1 && errno == EINTR);
1391 	if(bytes == -1) {
1392 		Logger::LogError("[PageRecord::OnStdin] " + tr("Standard input read error (%1).").arg("read"));
1393 		m_stdin_notifier->setEnabled(false);
1394 		return;
1395 	}
1396 	if(bytes == 0) {
1397 		Logger::LogInfo("[PageRecord::OnStdin] " + tr("Standard input closed (%1).").arg("read"));
1398 		m_stdin_notifier->setEnabled(false);
1399 		return;
1400 	}
1401 	m_stdin_buffer.append(buffer.data(), bytes);
1402 
1403 	// process commands
1404 	if(!m_stdin_reentrant) {
1405 		m_stdin_reentrant = true;
1406 		for( ; ; ) {
1407 			QString command = ReadStdinCommand();
1408 			if(command.isNull())
1409 				break;
1410 			Logger::LogInfo("[PageRecord::OnStdin] " + tr("Received command '%1'.").arg(command));
1411 			if(command == "record-start") {
1412 				OnRecordStart();
1413 			} else if(command == "record-pause") {
1414 				OnRecordPause();
1415 			} else if(command == "record-cancel") {
1416 				OnRecordCancel(false);
1417 			} else if(command == "record-save") {
1418 				OnRecordSave(false);
1419 			} else if(command == "schedule-activate") {
1420 				OnScheduleActivate();
1421 			} else if(command == "schedule-deactivate") {
1422 				OnScheduleDeactivate();
1423 			} else if(command == "window-show") {
1424 				m_main_window->OnShow();
1425 			} else if(command == "window-hide") {
1426 				m_main_window->OnHide();
1427 			} else if(command == "quit") {
1428 				m_main_window->Quit();
1429 			} else {
1430 				Logger::LogError("[PageRecord::OnStdin] " + tr("Unknown command."));
1431 			}
1432 		}
1433 		m_stdin_reentrant = false;
1434 	}
1435 
1436 }
1437 
1438 void PageRecord::OnUpdateInformation() {
1439 
1440 	if(m_page_started) {
1441 
1442 		int64_t total_time = 0;
1443 		double fps_in = 0.0;
1444 		double fps_out = 0.0;
1445 		uint64_t bit_rate = 0, total_bytes = 0;
1446 
1447 		if(m_x11_input != NULL)
1448 			fps_in = m_x11_input->GetFPS();
1449 #if SSR_USE_OPENGL_RECORDING
1450 		if(m_gl_inject_input != NULL)
1451 			fps_in = m_gl_inject_input->GetFPS();
1452 #endif
1453 #if SSR_USE_V4L2
1454 		if(m_v4l2_input != NULL)
1455 			fps_in = m_v4l2_input->GetFPS();
1456 #endif
1457 
1458 		if(m_output_manager != NULL) {
1459 			total_time = (m_output_manager->GetSynchronizer() == NULL)? 0 : m_output_manager->GetSynchronizer()->GetTotalTime();
1460 			fps_out = m_output_manager->GetActualFrameRate();
1461 			bit_rate = (uint64_t) (m_output_manager->GetActualBitRate() + 0.5);
1462 			total_bytes = m_output_manager->GetTotalBytes();
1463 		}
1464 
1465 		QString file_name;
1466 		if(m_file_protocol.isNull())
1467 			file_name = (m_output_settings.file.isNull())? "?" : QFileInfo(m_output_settings.file).fileName();
1468 		else
1469 			file_name = "(" + m_file_protocol + ")";
1470 
1471 		// for X11 recording, update the video size
1472 		if(m_x11_input != NULL)
1473 			m_x11_input->GetCurrentSize(&m_video_in_width, &m_video_in_height);
1474 
1475 #if SSR_USE_OPENGL_RECORDING
1476 		// for OpenGL recording, update the video size
1477 		if(m_gl_inject_input != NULL)
1478 			m_gl_inject_input->GetCurrentSize(&m_video_in_width, &m_video_in_height);
1479 #endif
1480 
1481 		m_label_info_total_time->setText(ReadableTime(total_time));
1482 		m_label_info_frame_rate_in->setText(QString::number(fps_in, 'f', 2));
1483 		m_label_info_frame_rate_out->setText(QString::number(fps_out, 'f', 2));
1484 		m_label_info_size_in->setText(ReadableWidthHeight(m_video_in_width, m_video_in_height));
1485 		m_label_info_size_out->setText(ReadableWidthHeight(m_output_settings.video_width, m_output_settings.video_height));
1486 		m_label_info_file_name->setText(file_name);
1487 		m_label_info_file_size->setText(ReadableSizeIEC(total_bytes, "B"));
1488 		m_label_info_bit_rate->setText(ReadableSizeSI(bit_rate, "bit/s"));
1489 
1490 		if(!CommandLineOptions::GetStatsFile().isNull()) {
1491 			QString str = QString() +
1492 					"capturing\t" + ((m_input_started)? "1" : "0") + "\n"
1493 					"recording\t" + ((m_output_started)? "1" : "0") + "\n"
1494 					"total_time\t" + QString::number(total_time) + "\n"
1495 					"frame_rate_in\t" + QString::number(fps_in, 'f', 8) + "\n"
1496 					"frame_rate_out\t" + QString::number(fps_out, 'f', 8) + "\n"
1497 					"size_in_width\t" + QString::number(m_video_in_width) + "\n"
1498 					"size_in_height\t" + QString::number(m_video_in_height) + "\n"
1499 					"size_out_width\t" + QString::number(m_output_settings.video_width) + "\n"
1500 					"size_out_height\t" + QString::number(m_output_settings.video_height) + "\n"
1501 					"file_name\t" + file_name + "\n"
1502 					"file_size\t" + QString::number(total_bytes) + "\n"
1503 					"bit_rate\t" + QString::number(bit_rate) + "\n";
1504 			QByteArray data = str.toUtf8();
1505 			QByteArray old_file = QFile::encodeName(CommandLineOptions::GetStatsFile());
1506 			QByteArray new_file = QFile::encodeName(CommandLineOptions::GetStatsFile() + "-new");
1507 			// Qt doesn't get the permissions right (you can only change the permissions after creating the file, that's too late),
1508 			// and it doesn't allow renaming a file over another file, so don't bother with QFile and just use POSIX and C functions.
1509 			int fd = open(new_file.constData(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
1510 			if(fd != -1) {
1511 				ssize_t b = write(fd, data.constData(), data.size()); Q_UNUSED(b);
1512 				::close(fd);
1513 				rename(new_file.constData(), old_file.constData());
1514 			}
1515 		}
1516 
1517 	} else {
1518 
1519 		m_label_info_total_time->clear();
1520 		m_label_info_frame_rate_in->clear();
1521 		m_label_info_frame_rate_out->clear();
1522 		m_label_info_size_in->clear();
1523 		m_label_info_size_out->clear();
1524 		m_label_info_file_name->clear();
1525 		m_label_info_file_size->clear();
1526 		m_label_info_bit_rate->clear();
1527 
1528 		if(!CommandLineOptions::GetStatsFile().isNull()) {
1529 			QByteArray old_file = QFile::encodeName(CommandLineOptions::GetStatsFile());
1530 			remove(old_file.constData());
1531 		}
1532 
1533 	}
1534 
1535 }
1536 
1537 void PageRecord::OnNewLogLine(Logger::enum_type type, QString string) {
1538 
1539 #if SSR_USE_ALSA
1540 	// play sound for errors
1541 	//TODO// this is an ugly way to detect errors, this should be improved at some point
1542 	int64_t time = hrt_time_micro();
1543 	if(m_simple_synth != NULL && type == Logger::TYPE_ERROR && time > m_last_error_sound + 1000000) {
1544 		m_simple_synth->PlaySequence(SEQUENCE_RECORD_ERROR.data(), SEQUENCE_RECORD_ERROR.size());
1545 		m_last_error_sound = time;
1546 	}
1547 #endif
1548 
1549 	// change system tray icon if an error has occurred
1550 	if(m_page_started && type == Logger::TYPE_ERROR && !m_error_occurred) {
1551 		m_error_occurred = true;
1552 		UpdateSysTray();
1553 	}
1554 
1555 	// add line to log
1556 	QTextCursor cursor = m_textedit_log->textCursor();
1557 	QTextCharFormat format;
1558 	bool should_scroll = (m_textedit_log->verticalScrollBar()->value() >= m_textedit_log->verticalScrollBar()->maximum());
1559 	switch(type) {
1560 		case Logger::TYPE_INFO:     format.setForeground(m_textedit_log->palette().windowText());  break;
1561 		case Logger::TYPE_WARNING:  format.setForeground(Qt::darkYellow);                          break;
1562 		case Logger::TYPE_ERROR:    format.setForeground(Qt::red);                                 break;
1563 		case Logger::TYPE_STDERR:   format.setForeground(Qt::gray);                                break;
1564 	}
1565 	cursor.movePosition(QTextCursor::End);
1566 	if(cursor.position() != 0)
1567 		cursor.insertBlock();
1568 	cursor.insertText(string, format);
1569 	if(should_scroll)
1570 		m_textedit_log->verticalScrollBar()->setValue(m_textedit_log->verticalScrollBar()->maximum());
1571 
1572 }
1573