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 "PageInput.h"
21 
22 #include "DialogGLInject.h"
23 #include "Dialogs.h"
24 #include "EnumStrings.h"
25 #include "HiddenScrollArea.h"
26 #include "Icons.h"
27 #include "MainWindow.h"
28 
29 ENUMSTRINGS(PageInput::enum_video_area) = {
30 	{PageInput::VIDEO_AREA_SCREEN, "screen"},
31 	{PageInput::VIDEO_AREA_FIXED, "fixed"},
32 	{PageInput::VIDEO_AREA_CURSOR, "cursor"},
33 #if SSR_USE_OPENGL_RECORDING
34 	{PageInput::VIDEO_AREA_GLINJECT, "glinject"},
35 #endif
36 #if SSR_USE_V4L2
37 	{PageInput::VIDEO_AREA_V4L2, "v4l2"},
38 #endif
39 };
40 
41 ENUMSTRINGS(PageInput::enum_audio_backend) = {
42 #if SSR_USE_ALSA
43 	{PageInput::AUDIO_BACKEND_ALSA, "alsa"},
44 #endif
45 #if SSR_USE_PULSEAUDIO
46 	{PageInput::AUDIO_BACKEND_PULSEAUDIO, "pulseaudio"},
47 #endif
48 #if SSR_USE_JACK
49 	{PageInput::AUDIO_BACKEND_JACK, "jack"},
50 #endif
51 };
52 
GetScreenGeometries()53 static std::vector<QRect> GetScreenGeometries() {
54 	std::vector<QRect> screen_geometries;
55 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
56 	for(QScreen *screen :  QApplication::screens()) {
57 		QRect geometry = screen->geometry();
58 		qreal ratio = screen->devicePixelRatio();
59 		screen_geometries.emplace_back(geometry.x(), geometry.y(), lrint((qreal) geometry.width() * ratio), lrint((qreal) geometry.height() * ratio));
60 	}
61 #else
62 	for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
63 		screen_geometries.push_back(QApplication::desktop()->screenGeometry(i));
64 	}
65 #endif
66 	return screen_geometries;
67 }
68 
CombineScreenGeometries(const std::vector<QRect> & screen_geometries)69 static QRect CombineScreenGeometries(const std::vector<QRect>& screen_geometries) {
70 	QRect combined_geometry;
71 	for(const QRect &geometry : screen_geometries) {
72 		combined_geometry |= geometry;
73 	}
74 	return combined_geometry;
75 }
76 
GetMousePhysicalCoordinates()77 static QPoint GetMousePhysicalCoordinates() {
78 	if(IsPlatformX11()) {
79 		Window root, child;
80 		int root_x, root_y;
81 		int win_x, win_y;
82 		unsigned int mask_return;
83 		XQueryPointer(QX11Info::display(), QX11Info::appRootWindow(), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask_return);
84 		return QPoint(root_x, root_y);
85 	} else {
86 		return QPoint(0, 0); // TODO: implement for wayland
87 	}
88 }
89 
MapToLogicalCoordinates(const QRect & rect)90 static QRect MapToLogicalCoordinates(const QRect& rect) {
91 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
92 	for(QScreen *screen :  QApplication::screens()) {
93 		QRect geometry = screen->geometry();
94 		qreal ratio = screen->devicePixelRatio();
95 		QRect physical_geometry(geometry.x(), geometry.y(), lrint((qreal) geometry.width() * ratio), lrint((qreal) geometry.height() * ratio));
96 		if(physical_geometry.contains(rect.center())) {
97 			return QRect(
98 						geometry.x() + lrint((qreal) (rect.x() - physical_geometry.x()) / ratio - 0.4999),
99 						geometry.y() + lrint((qreal) (rect.y() - physical_geometry.y()) / ratio - 0.4999),
100 						lrint((qreal) rect.width() / ratio - 0.4999),
101 						lrint((qreal) rect.height() / ratio - 0.4999));
102 		}
103 	}
104 #endif
105 	return rect;
106 };
107 
108 // This does some sanity checking on the rubber band rectangle before creating it.
109 // Rubber bands with width or height zero or extremely large appear to cause problems.
ValidateRubberBandRectangle(QRect rect)110 static QRect ValidateRubberBandRectangle(QRect rect) {
111 	std::vector<QRect> screen_geometries = GetScreenGeometries();
112 	QRect combined_geometry = CombineScreenGeometries(screen_geometries);
113 	return rect.normalized() & combined_geometry.adjusted(-10, -10, 10, 10);
114 }
115 
QComboBoxWithSignal(QWidget * parent)116 QComboBoxWithSignal::QComboBoxWithSignal(QWidget* parent)
117 	: QComboBox(parent) {}
118 
showPopup()119 void QComboBoxWithSignal::showPopup() {
120 	emit popupShown();
121 	QComboBox::showPopup();
122 }
123 
hidePopup()124 void QComboBoxWithSignal::hidePopup() {
125 	emit popupHidden();
126 	QComboBox::hidePopup();
127 }
128 
QSpinBoxWithSignal(QWidget * parent)129 QSpinBoxWithSignal::QSpinBoxWithSignal(QWidget* parent)
130 	: QSpinBox(parent) {}
131 
focusInEvent(QFocusEvent * event)132 void QSpinBoxWithSignal::focusInEvent(QFocusEvent* event) {
133 	emit focusIn();
134 	QSpinBox::focusInEvent(event);
135 }
136 
focusOutEvent(QFocusEvent * event)137 void QSpinBoxWithSignal::focusOutEvent(QFocusEvent* event) {
138 	emit focusOut();
139 	QSpinBox::focusOutEvent(event);
140 }
141 
142 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
143 
144 #define TRANSPARENT_WINDOW_FLAGS (Qt::Window | Qt::BypassWindowManagerHint | Qt::FramelessWindowHint | \
145 		Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus)
146 
147 #define TRANSPARENT_WINDOW_ATTRIBUTES() {\
148 }
149 
150 #else
151 
152 #define TRANSPARENT_WINDOW_FLAGS (Qt::Window | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint | \
153 		Qt::WindowStaysOnTopHint)
154 
155 // Replacement for Qt::WindowTransparentForInput based on X11 'shape' extension as described here:
156 // http://shallowsky.com/blog/programming/translucent-window-click-thru.html
157 #define TRANSPARENT_WINDOW_ATTRIBUTES() {\
158 	setAttribute(Qt::WA_X11DoNotAcceptFocus); \
159 	int shape_event_base, shape_error_base; \
160 	if(IsPlatformX11()) { \
161 		if(XShapeQueryExtension(QX11Info::display(), &shape_event_base, &shape_error_base)) { \
162 			Region region = XCreateRegion(); \
163 			XShapeCombineRegion(QX11Info::display(), winId(), ShapeInput, 0, 0, region, ShapeSet); \
164 			XDestroyRegion(region); \
165 		} \
166 	} \
167 }
168 
169 #endif
170 
ScreenLabelWindow(QWidget * parent,const QString & text)171 ScreenLabelWindow::ScreenLabelWindow(QWidget* parent, const QString &text)
172 	: QWidget(parent, TRANSPARENT_WINDOW_FLAGS) {
173 	TRANSPARENT_WINDOW_ATTRIBUTES();
174 	m_text = text;
175 	m_font = QFont("Sans", 18, QFont::Bold);
176 	QFontMetrics fm(m_font);
177 	setFixedSize(fm.size(Qt::TextSingleLine, m_text) + QSize(60, 40));
178 	setWindowOpacity(0.75);
179 }
180 
paintEvent(QPaintEvent * event)181 void ScreenLabelWindow::paintEvent(QPaintEvent* event) {
182 	Q_UNUSED(event);
183 	QPainter painter(this);
184 	painter.setPen(QColor(0, 0, 0));
185 	painter.setBrush(QColor(255, 192, 128));
186 	painter.drawRect(QRectF(0.5, 0.5, (qreal) width() - 1.0, (qreal) height() - 1.0));
187 	painter.setFont(m_font);
188 	painter.drawText(0, 0, width(), height(), Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, m_text);
189 }
190 
RecordingFrameWindow(QWidget * parent,bool outside)191 RecordingFrameWindow::RecordingFrameWindow(QWidget* parent, bool outside)
192 	: QWidget(parent, TRANSPARENT_WINDOW_FLAGS) {
193 	TRANSPARENT_WINDOW_ATTRIBUTES();
194 	m_outside = outside;
195 	QImage image(16, 16, QImage::Format_RGB32);
196 	for(size_t j = 0; j < (size_t) image.height(); ++j) {
197 		uint32_t *row = (uint32_t*) image.scanLine(j);
198 		for(size_t i = 0; i < (size_t) image.width(); ++i) {
199 			//row[i] = ((i + j) % 16 < 8)? 0xffbfbfff : 0xff9f9fdf;
200 			row[i] = ((i + j) % 16 < 8)? 0xffff8080 : 0xff8080ff;
201 		}
202 	}
203 	m_texture = QPixmap::fromImage(image);
204 	UpdateMask();
205 }
206 
SetRectangle(const QRect & r)207 void RecordingFrameWindow::SetRectangle(const QRect& r) {
208 	QRect rect = MapToLogicalCoordinates(ValidateRubberBandRectangle(r));
209 	if(m_outside)
210 		rect.adjust(-RecordingFrameWindow::BORDER_WIDTH, -RecordingFrameWindow::BORDER_WIDTH,
211 					RecordingFrameWindow::BORDER_WIDTH, RecordingFrameWindow::BORDER_WIDTH);
212 	if(rect.isEmpty()) {
213 		hide();
214 	} else {
215 		setGeometry(rect);
216 		show();
217 	}
218 }
219 
UpdateMask()220 void RecordingFrameWindow::UpdateMask() {
221 	if(m_outside) {
222 		setMask(QRegion(0, 0, width(), height()).subtracted(QRegion(BORDER_WIDTH, BORDER_WIDTH, width() - 2 * BORDER_WIDTH, height() - 2 * BORDER_WIDTH)));
223 		setWindowOpacity(0.5);
224 	} else {
225 		if(QX11Info::isCompositingManagerRunning()) {
226 			clearMask();
227 			setWindowOpacity(0.25);
228 		} else {
229 			setMask(QRegion(0, 0, width(), height()).subtracted(QRegion(BORDER_WIDTH, BORDER_WIDTH, width() - 2 * BORDER_WIDTH, height() - 2 * BORDER_WIDTH)));
230 			setWindowOpacity(1.0);
231 		}
232 	}
233 }
234 
resizeEvent(QResizeEvent * event)235 void RecordingFrameWindow::resizeEvent(QResizeEvent *event) {
236 	Q_UNUSED(event);
237 	UpdateMask();
238 }
239 
paintEvent(QPaintEvent * event)240 void RecordingFrameWindow::paintEvent(QPaintEvent* event) {
241 	Q_UNUSED(event);
242 	QPainter painter(this);
243 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
244 	m_texture.setDevicePixelRatio(devicePixelRatioF());
245 #endif
246 	painter.setPen(QColor(0, 0, 0, 128));
247 	painter.setBrush(Qt::NoBrush);
248 	painter.drawTiledPixmap(0, 0, width(), height(), m_texture);
249 	if(m_outside) {
250 		painter.drawRect(QRectF((qreal) BORDER_WIDTH - 0.5,
251 								(qreal) BORDER_WIDTH - 0.5,
252 								(qreal) (width() - 2 * BORDER_WIDTH) + 1.0,
253 								(qreal) (height() - 2 * BORDER_WIDTH) + 1.0));
254 	} else {
255 		painter.drawRect(QRectF(0.5, 0.5, (qreal) width() - 1.0, (qreal) height() - 1.0));
256 	}
257 }
258 
PageInput(MainWindow * main_window)259 PageInput::PageInput(MainWindow* main_window)
260 	: QWidget(main_window->centralWidget()) {
261 
262 	m_main_window = main_window;
263 
264 	m_grabbing = false;
265 	m_selecting_window = false;
266 
267 	HiddenScrollArea *scrollarea = new HiddenScrollArea(this);
268 	QWidget *scrollarea_contents = new QWidget(scrollarea);
269 	scrollarea->setWidget(scrollarea_contents);
270 	{
271 		m_profile_box = new ProfileBox(tr("Input profile"), scrollarea_contents, "input-profiles", &LoadProfileSettingsCallback, &SaveProfileSettingsCallback, this);
272 
273 		QGroupBox *groupbox_video = new QGroupBox(tr("Video input"), scrollarea_contents);
274 		{
275 			m_buttongroup_video_area = new QButtonGroup(groupbox_video);
276 			QRadioButton *radio_area_screen = new QRadioButton(tr("Record the entire screen"), groupbox_video);
277 			QRadioButton *radio_area_fixed = new QRadioButton(tr("Record a fixed rectangle"), groupbox_video);
278 			QRadioButton *radio_area_cursor = new QRadioButton(tr("Follow the cursor"), groupbox_video);
279 #if SSR_USE_OPENGL_RECORDING
280 			QRadioButton *radio_area_glinject = new QRadioButton(tr("Record OpenGL"), groupbox_video);
281 #endif
282 #if SSR_USE_V4L2
283 			QRadioButton *radio_area_v4l2 = new QRadioButton(tr("Record V4L2 device"), groupbox_video);
284 #endif
285 			m_buttongroup_video_area->addButton(radio_area_screen, VIDEO_AREA_SCREEN);
286 			m_buttongroup_video_area->addButton(radio_area_fixed, VIDEO_AREA_FIXED);
287 			m_buttongroup_video_area->addButton(radio_area_cursor, VIDEO_AREA_CURSOR);
288 #if SSR_USE_OPENGL_RECORDING
289 			m_buttongroup_video_area->addButton(radio_area_glinject, VIDEO_AREA_GLINJECT);
290 #endif
291 #if SSR_USE_V4L2
292 			m_buttongroup_video_area->addButton(radio_area_v4l2, VIDEO_AREA_V4L2);
293 #endif
294 			m_combobox_screens = new QComboBoxWithSignal(groupbox_video);
295 			m_combobox_screens->setToolTip(tr("Select what monitor should be recorded in a multi-monitor configuration."));
296 			m_checkbox_follow_fullscreen = new QCheckBox(tr("Record entire screen with cursor"), groupbox_video);
297 			m_checkbox_follow_fullscreen->setToolTip(tr("Record the entire screen on which the cursor is located, rather than following the cursor position."));
298 			m_pushbutton_video_select_rectangle = new QPushButton(tr("Select rectangle..."), groupbox_video);
299 			m_pushbutton_video_select_rectangle->setToolTip(tr("Use the mouse to select the recorded rectangle."));
300 			m_pushbutton_video_select_window = new QPushButton(tr("Select window..."), groupbox_video);
301 			m_pushbutton_video_select_window->setToolTip(tr("Use the mouse to select a window to record.\n"
302 															"Hint: If you click the border of a window, the entire window will be recorded (including the borders). Otherwise only\n"
303 															"the client area of the window will be recorded."));
304 #if SSR_USE_OPENGL_RECORDING
305 			m_pushbutton_video_opengl_settings = new QPushButton(tr("OpenGL settings..."), groupbox_video);
306 			m_pushbutton_video_opengl_settings->setToolTip(tr("Change the settings for OpenGL recording."));
307 #endif
308 #if SSR_USE_V4L2
309 			m_lineedit_v4l2_device = new QLineEdit(groupbox_video);
310 			m_lineedit_v4l2_device->setToolTip(tr("The V4L2 device to record (e.g. /dev/video0)."));
311 #endif
312 			m_label_video_x = new QLabel(tr("Left:"), groupbox_video);
313 			m_spinbox_video_x = new QSpinBoxWithSignal(groupbox_video);
314 			m_spinbox_video_x->setRange(0, SSR_MAX_IMAGE_SIZE);
315 			m_spinbox_video_x->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
316 			m_spinbox_video_x->setToolTip(tr("The x coordinate of the upper-left corner of the recorded rectangle.\n"
317 											 "Hint: You can also change this value with the scroll wheel or the up/down arrows."));
318 			m_label_video_y = new QLabel(tr("Top:"), groupbox_video);
319 			m_spinbox_video_y = new QSpinBoxWithSignal(groupbox_video);
320 			m_spinbox_video_y->setRange(0, SSR_MAX_IMAGE_SIZE);
321 			m_spinbox_video_y->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
322 			m_spinbox_video_y->setToolTip(tr("The y coordinate of the upper-left corner of the recorded rectangle.\n"
323 											 "Hint: You can also change this value with the scroll wheel or the up/down arrows."));
324 			m_label_video_w = new QLabel(tr("Width:"), groupbox_video);
325 			m_spinbox_video_w = new QSpinBoxWithSignal(groupbox_video);
326 			m_spinbox_video_w->setRange(0, SSR_MAX_IMAGE_SIZE);
327 			m_spinbox_video_w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
328 			m_spinbox_video_w->setToolTip(tr("The width of the recorded rectangle.\n"
329 											 "Hint: You can also change this value with the scroll wheel or the up/down arrows."));
330 			m_label_video_h = new QLabel(tr("Height:"), groupbox_video);
331 			m_spinbox_video_h = new QSpinBoxWithSignal(groupbox_video);
332 			m_spinbox_video_h->setRange(0, SSR_MAX_IMAGE_SIZE);
333 			m_spinbox_video_h->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
334 			m_spinbox_video_h->setToolTip(tr("The height of the recorded rectangle.\n"
335 											 "Hint: You can also change this value with the scroll wheel or the up/down arrows."));
336 			QLabel *label_frame_rate = new QLabel(tr("Frame rate:"), groupbox_video);
337 			m_spinbox_video_frame_rate = new QSpinBox(groupbox_video);
338 			m_spinbox_video_frame_rate->setRange(1, 1000);
339 			m_spinbox_video_frame_rate->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
340 			m_spinbox_video_frame_rate->setToolTip(tr("The number of frames per second in the final video. Higher frame rates use more CPU time."));
341 			m_checkbox_scale = new QCheckBox(tr("Scale video"), groupbox_video);
342 			m_checkbox_scale->setToolTip(tr("Enable or disable scaling. Scaling uses more CPU time, but if the scaled video is smaller, it could make the encoding faster."));
343 			m_label_video_scaled_w = new QLabel(tr("Scaled width:"), groupbox_video);
344 			m_spinbox_video_scaled_w = new QSpinBox(groupbox_video);
345 			m_spinbox_video_scaled_w->setRange(0, SSR_MAX_IMAGE_SIZE);
346 			m_spinbox_video_scaled_w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
347 			m_label_video_scaled_h = new QLabel(tr("Scaled height:"), groupbox_video);
348 			m_spinbox_video_scaled_h = new QSpinBox(groupbox_video);
349 			m_spinbox_video_scaled_h->setRange(0, SSR_MAX_IMAGE_SIZE);
350 			m_spinbox_video_scaled_h->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
351 			m_checkbox_record_cursor = new QCheckBox(tr("Record cursor"), groupbox_video);
352 
353 			connect(m_buttongroup_video_area, SIGNAL(buttonClicked(int)), this, SLOT(OnUpdateVideoAreaFields()));
354 			connect(m_combobox_screens, SIGNAL(activated(int)), this, SLOT(OnUpdateVideoAreaFields()));
355 			connect(m_combobox_screens, SIGNAL(popupShown()), this, SLOT(OnIdentifyScreens()));
356 			connect(m_combobox_screens, SIGNAL(popupHidden()), this, SLOT(OnStopIdentifyScreens()));
357 			connect(m_checkbox_follow_fullscreen, SIGNAL(clicked()), this, SLOT(OnUpdateVideoAreaFields()));
358 			connect(m_spinbox_video_x, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame()));
359 			connect(m_spinbox_video_x, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame()));
360 			connect(m_spinbox_video_x, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame()));
361 			connect(m_spinbox_video_y, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame()));
362 			connect(m_spinbox_video_y, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame()));
363 			connect(m_spinbox_video_y, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame()));
364 			connect(m_spinbox_video_w, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame()));
365 			connect(m_spinbox_video_w, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame()));
366 			connect(m_spinbox_video_w, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame()));
367 			connect(m_spinbox_video_h, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame()));
368 			connect(m_spinbox_video_h, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame()));
369 			connect(m_spinbox_video_h, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame()));
370 			connect(m_pushbutton_video_select_rectangle, SIGNAL(clicked()), this, SLOT(OnStartSelectRectangle()));
371 			connect(m_pushbutton_video_select_window, SIGNAL(clicked()), this, SLOT(OnStartSelectWindow()));
372 #if SSR_USE_OPENGL_RECORDING
373 			connect(m_pushbutton_video_opengl_settings, SIGNAL(clicked()), this, SLOT(OnGLInjectDialog()));
374 #endif
375 			connect(m_checkbox_scale, SIGNAL(clicked()), this, SLOT(OnUpdateVideoScaleFields()));
376 
377 			QVBoxLayout *layout = new QVBoxLayout(groupbox_video);
378 			{
379 				QHBoxLayout *layout2 = new QHBoxLayout();
380 				layout->addLayout(layout2);
381 				layout2->addWidget(radio_area_screen);
382 				layout2->addWidget(m_combobox_screens);
383 			}
384 			layout->addWidget(radio_area_fixed);
385 			{
386 				QHBoxLayout *layout2 = new QHBoxLayout();
387 				layout->addLayout(layout2);
388 				layout2->addWidget(radio_area_cursor);
389 				layout2->addWidget(m_checkbox_follow_fullscreen);
390 			}
391 #if SSR_USE_OPENGL_RECORDING
392 			layout->addWidget(radio_area_glinject);
393 #endif
394 #if SSR_USE_V4L2
395 			{
396 				QHBoxLayout *layout2 = new QHBoxLayout();
397 				layout->addLayout(layout2);
398 				layout2->addWidget(radio_area_v4l2);
399 				layout2->addWidget(m_lineedit_v4l2_device);
400 			}
401 #endif
402 			{
403 				QHBoxLayout *layout2 = new QHBoxLayout();
404 				layout->addLayout(layout2);
405 				layout2->addWidget(m_pushbutton_video_select_rectangle);
406 				layout2->addWidget(m_pushbutton_video_select_window);
407 #if SSR_USE_OPENGL_RECORDING
408 				layout2->addWidget(m_pushbutton_video_opengl_settings);
409 #endif
410 				layout2->addStretch();
411 			}
412 			{
413 				QGridLayout *layout2 = new QGridLayout();
414 				layout->addLayout(layout2);
415 				layout2->addWidget(m_label_video_x, 0, 0);
416 				layout2->addWidget(m_spinbox_video_x, 0, 1);
417 				layout2->addWidget(m_label_video_y, 0, 2);
418 				layout2->addWidget(m_spinbox_video_y, 0, 3);
419 				layout2->addWidget(m_label_video_w, 1, 0);
420 				layout2->addWidget(m_spinbox_video_w, 1, 1);
421 				layout2->addWidget(m_label_video_h, 1, 2);
422 				layout2->addWidget(m_spinbox_video_h, 1, 3);
423 			}
424 			{
425 				QGridLayout *layout2 = new QGridLayout();
426 				layout->addLayout(layout2);
427 				layout2->addWidget(label_frame_rate, 0, 0);
428 				layout2->addWidget(m_spinbox_video_frame_rate, 0, 1);
429 			}
430 			layout->addWidget(m_checkbox_scale);
431 			{
432 				QGridLayout *layout2 = new QGridLayout();
433 				layout->addLayout(layout2);
434 				layout2->addWidget(m_label_video_scaled_w, 0, 0);
435 				layout2->addWidget(m_spinbox_video_scaled_w, 0, 1);
436 				layout2->addWidget(m_label_video_scaled_h, 0, 2);
437 				layout2->addWidget(m_spinbox_video_scaled_h, 0, 3);
438 			}
439 			layout->addWidget(m_checkbox_record_cursor);
440 		}
441 		QGroupBox *groupbox_audio = new QGroupBox(tr("Audio input"), scrollarea_contents);
442 		{
443 			m_checkbox_audio_enable = new QCheckBox(tr("Record audio"), groupbox_audio);
444 			m_label_audio_backend = new QLabel(tr("Backend:"), groupbox_audio);
445 			m_combobox_audio_backend = new QComboBox(groupbox_audio);
446 #if SSR_USE_ALSA
447 			m_combobox_audio_backend->addItem("ALSA");
448 #endif
449 #if SSR_USE_PULSEAUDIO
450 			m_combobox_audio_backend->addItem("PulseAudio");
451 #endif
452 #if SSR_USE_JACK
453 			m_combobox_audio_backend->addItem("JACK");
454 #endif
455 #if SSR_USE_ALSA && SSR_USE_PULSEAUDIO
456 			m_combobox_audio_backend->setToolTip(tr("The audio backend that will be used for recording.\n"
457 													"The ALSA backend will also work on systems that use PulseAudio, but it is better to use the PulseAudio backend directly."));
458 #else
459 			m_combobox_audio_backend->setToolTip(tr("The audio backend that will be used for recording."));
460 #endif
461 #if SSR_USE_ALSA
462 			m_label_alsa_source = new QLabel(tr("Source:"), groupbox_audio);
463 			m_combobox_alsa_source = new QComboBox(groupbox_audio);
464 			m_combobox_alsa_source->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
465 			m_combobox_alsa_source->setToolTip(tr("The ALSA source that will be used for recording.\n"
466 												  "The default is usually fine. The 'shared' sources allow multiple programs to record at the same time, but they may be less reliable."));
467 			m_pushbutton_alsa_refresh = new QPushButton(tr("Refresh"), groupbox_audio);
468 			m_pushbutton_alsa_refresh->setToolTip(tr("Refreshes the list of ALSA sources."));
469 #endif
470 #if SSR_USE_PULSEAUDIO
471 			m_label_pulseaudio_source = new QLabel(tr("Source:"), groupbox_audio);
472 			m_combobox_pulseaudio_source = new QComboBox(groupbox_audio);
473 			m_combobox_pulseaudio_source->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
474 			m_combobox_pulseaudio_source->setToolTip(tr("The PulseAudio source that will be used for recording.\n"
475 														"A 'monitor' is a source that records the audio played by other applications.", "Don't translate 'monitor' unless PulseAudio does this as well"));
476 			m_pushbutton_pulseaudio_refresh = new QPushButton(tr("Refresh"), groupbox_audio);
477 			m_pushbutton_pulseaudio_refresh->setToolTip(tr("Refreshes the list of PulseAudio sources."));
478 #endif
479 #if SSR_USE_JACK
480 			m_checkbox_jack_connect_system_capture = new QCheckBox(tr("Record system microphone"));
481 			m_checkbox_jack_connect_system_capture->setToolTip(tr("If checked, the ports will be automatically connected to the system capture ports."));
482 			m_checkbox_jack_connect_system_playback = new QCheckBox(tr("Record system speakers"));
483 			m_checkbox_jack_connect_system_playback->setToolTip(tr("If checked, the ports will be automatically connected to anything that connects to the system playback ports."));
484 #endif
485 
486 			connect(m_checkbox_audio_enable, SIGNAL(clicked()), this, SLOT(OnUpdateAudioFields()));
487 			connect(m_combobox_audio_backend, SIGNAL(activated(int)), this, SLOT(OnUpdateAudioFields()));
488 #if SSR_USE_ALSA
489 			connect(m_pushbutton_alsa_refresh, SIGNAL(clicked()), this, SLOT(OnUpdateALSASources()));
490 #endif
491 #if SSR_USE_PULSEAUDIO
492 			connect(m_pushbutton_pulseaudio_refresh, SIGNAL(clicked()), this, SLOT(OnUpdatePulseAudioSources()));
493 #endif
494 
495 			QVBoxLayout *layout = new QVBoxLayout(groupbox_audio);
496 			layout->addWidget(m_checkbox_audio_enable);
497 			{
498 				QGridLayout *layout2 = new QGridLayout();
499 				layout->addLayout(layout2);
500 				layout2->addWidget(m_label_audio_backend, 0, 0);
501 				layout2->addWidget(m_combobox_audio_backend, 0, 1, 1, 2);
502 #if SSR_USE_ALSA
503 				layout2->addWidget(m_label_alsa_source, 1, 0);
504 				layout2->addWidget(m_combobox_alsa_source, 1, 1);
505 				layout2->addWidget(m_pushbutton_alsa_refresh, 1, 2);
506 #endif
507 #if SSR_USE_PULSEAUDIO
508 				layout2->addWidget(m_label_pulseaudio_source, 2, 0);
509 				layout2->addWidget(m_combobox_pulseaudio_source, 2, 1);
510 				layout2->addWidget(m_pushbutton_pulseaudio_refresh, 2, 2);
511 #endif
512 			}
513 #if SSR_USE_JACK
514 			{
515 				QHBoxLayout *layout2 = new QHBoxLayout();
516 				layout->addLayout(layout2);
517 				layout2->addWidget(m_checkbox_jack_connect_system_capture);
518 				layout2->addWidget(m_checkbox_jack_connect_system_playback);
519 			}
520 #endif
521 		}
522 
523 		QVBoxLayout *layout = new QVBoxLayout(scrollarea_contents);
524 		layout->addWidget(m_profile_box);
525 		layout->addWidget(groupbox_video);
526 		layout->addWidget(groupbox_audio);
527 		layout->addStretch();
528 	}
529 
530 	QPushButton *button_back = new QPushButton(g_icon_go_previous, tr("Back"), this);
531 	QPushButton *button_continue = new QPushButton(g_icon_go_next, tr("Continue"), this);
532 
533 	connect(button_back, SIGNAL(clicked()), m_main_window, SLOT(GoPageWelcome()));
534 	connect(button_continue, SIGNAL(clicked()), this, SLOT(OnContinue()));
535 
536 	QVBoxLayout *layout = new QVBoxLayout(this);
537 	layout->setContentsMargins(0, 0, 0, 0);
538 	layout->addWidget(scrollarea);
539 	{
540 		QHBoxLayout *layout2 = new QHBoxLayout();
541 		layout->addLayout(layout2);
542 		layout2->addSpacing(style()->pixelMetric(QStyle::PM_LayoutLeftMargin));
543 		layout2->addWidget(button_back);
544 		layout2->addWidget(button_continue);
545 		layout2->addSpacing(style()->pixelMetric(QStyle::PM_LayoutRightMargin));
546 	}
547 	layout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
548 
549 	connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(OnFocusChange(QWidget*, QWidget*)));
550 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
551 	connect(qApp, SIGNAL(screenAdded(QScreen*)), this, SLOT(OnScreenAdded(QScreen*)));
552 	connect(qApp, SIGNAL(screenRemoved(QScreen*)), this, SLOT(OnUpdateScreenConfiguration()));
553 #else
554 	connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(OnUpdateScreenConfiguration()));
555 	connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(OnUpdateScreenConfiguration()));
556 #endif
557 
558 	LoadScreenConfigurations();
559 #if SSR_USE_ALSA
560 	LoadALSASources();
561 #endif
562 #if SSR_USE_PULSEAUDIO
563 	LoadPulseAudioSources();
564 #endif
565 
566 	// temporary settings to calculate the worst-case size
567 	SetAudioEnabled(true);
568 #if SSR_USE_ALSA
569 	SetAudioBackend(AUDIO_BACKEND_ALSA);
570 #else
571 #if SSR_USE_PULSEAUDIO
572 	SetAudioBackend(AUDIO_BACKEND_PULSEAUDIO);
573 #else
574 #if SSR_USE_JACK
575 	SetAudioBackend(AUDIO_BACKEND_JACK);
576 #else
577 #error "At least one audio backend must be enabled!"
578 #endif
579 #endif
580 #endif
581 
582 	OnUpdateVideoAreaFields();
583 	OnUpdateVideoScaleFields();
584 	OnUpdateAudioFields();
585 
586 }
587 
LoadSettings(QSettings * settings)588 void PageInput::LoadSettings(QSettings* settings) {
589 	SetProfile(m_profile_box->FindProfile(settings->value("input/profile", QString()).toString()));
590 	LoadProfileSettings(settings);
591 }
592 
SaveSettings(QSettings * settings)593 void PageInput::SaveSettings(QSettings* settings) {
594 	settings->setValue("input/profile", m_profile_box->GetProfileName());
595 	SaveProfileSettings(settings);
596 }
597 
LoadProfileSettingsCallback(QSettings * settings,void * userdata)598 void PageInput::LoadProfileSettingsCallback(QSettings* settings, void* userdata) {
599 	PageInput *page = (PageInput*) userdata;
600 	page->LoadProfileSettings(settings);
601 }
602 
SaveProfileSettingsCallback(QSettings * settings,void * userdata)603 void PageInput::SaveProfileSettingsCallback(QSettings* settings, void* userdata) {
604 	PageInput *page = (PageInput*) userdata;
605 	page->SaveProfileSettings(settings);
606 }
607 
LoadProfileSettings(QSettings * settings)608 void PageInput::LoadProfileSettings(QSettings* settings) {
609 
610 	// choose default audio backend
611 #if SSR_USE_ALSA
612 #if SSR_USE_PULSEAUDIO
613 	enum_audio_backend default_audio_backend = (m_pulseaudio_available)? AUDIO_BACKEND_PULSEAUDIO : AUDIO_BACKEND_ALSA;
614 #else
615 	enum_audio_backend default_audio_backend = AUDIO_BACKEND_ALSA;
616 #endif
617 #else
618 #if SSR_USE_PULSEAUDIO
619 	enum_audio_backend default_audio_backend = AUDIO_BACKEND_PULSEAUDIO;
620 #else
621 #if SSR_USE_JACK
622 	enum_audio_backend default_audio_backend = AUDIO_BACKEND_JACK;
623 #else
624 #error "At least one audio backend must be enabled!"
625 #endif
626 #endif
627 #endif
628 
629 	// load settings
630 	SetVideoArea(StringToEnum(settings->value("input/video_area", QString()).toString(), VIDEO_AREA_SCREEN));
631 	SetVideoAreaScreen(settings->value("input/video_area_screen", 0).toUInt());
632 	SetVideoAreaFollowFullscreen(settings->value("input/video_area_follow_fullscreen", false).toBool());
633 #if SSR_USE_V4L2
634 	SetVideoV4L2Device(settings->value("input/video_v4l2_device", "/dev/video0").toString());
635 #endif
636 	SetVideoX(settings->value("input/video_x", 0).toUInt());
637 	SetVideoY(settings->value("input/video_y", 0).toUInt());
638 	SetVideoW(settings->value("input/video_w", 800).toUInt());
639 	SetVideoH(settings->value("input/video_h", 600).toUInt());
640 	SetVideoFrameRate(settings->value("input/video_frame_rate", 30).toUInt());
641 	SetVideoScalingEnabled(settings->value("input/video_scale", false).toBool());
642 	SetVideoScaledW(settings->value("input/video_scaled_w", 854).toUInt());
643 	SetVideoScaledH(settings->value("input/video_scaled_h", 480).toUInt());
644 	SetVideoRecordCursor(settings->value("input/video_record_cursor", true).toBool());
645 	SetAudioEnabled(settings->value("input/audio_enabled", true).toBool());
646 	SetAudioBackend(StringToEnum(settings->value("input/audio_backend", QString()).toString(), default_audio_backend));
647 #if SSR_USE_ALSA
648 	SetALSASource(FindALSASource(settings->value("input/audio_alsa_source", QString()).toString()));
649 #endif
650 #if SSR_USE_PULSEAUDIO
651 	SetPulseAudioSource(FindPulseAudioSource(settings->value("input/audio_pulseaudio_source", QString()).toString()));
652 #endif
653 #if SSR_USE_JACK
654 	SetJackConnectSystemCapture(settings->value("input/audio_jack_connect_system_capture", true).toBool());
655 	SetJackConnectSystemPlayback(settings->value("input/audio_jack_connect_system_playback", false).toBool());
656 #endif
657 #if SSR_USE_OPENGL_RECORDING
658 	SetGLInjectChannel(settings->value("input/glinject_channel", QString()).toString());
659 	SetGLInjectRelaxPermissions(settings->value("input/glinject_relax_permissions", false).toBool());
660 	SetGLInjectCommand(settings->value("input/glinject_command", "").toString());
661 	SetGLInjectWorkingDirectory(settings->value("input/glinject_working_directory", "").toString());
662 	SetGLInjectAutoLaunch(settings->value("input/glinject_auto_launch", false).toBool());
663 	SetGLInjectLimitFPS(settings->value("input/glinject_limit_fps", false).toBool());
664 #endif
665 
666 	// update things
667 	OnUpdateRecordingFrame();
668 	OnUpdateVideoAreaFields();
669 	OnUpdateVideoScaleFields();
670 	OnUpdateAudioFields();
671 
672 }
673 
SaveProfileSettings(QSettings * settings)674 void PageInput::SaveProfileSettings(QSettings* settings) {
675 	settings->setValue("input/video_area", EnumToString(GetVideoArea()));
676 	settings->setValue("input/video_area_screen", GetVideoAreaScreen());
677 	settings->setValue("input/video_area_follow_fullscreen", GetVideoAreaFollowFullscreen());
678 #if SSR_USE_V4L2
679 	settings->setValue("input/video_v4l2_device", GetVideoV4L2Device());
680 #endif
681 	settings->setValue("input/video_x", GetVideoX());
682 	settings->setValue("input/video_y", GetVideoY());
683 	settings->setValue("input/video_w", GetVideoW());
684 	settings->setValue("input/video_h", GetVideoH());
685 	settings->setValue("input/video_frame_rate", GetVideoFrameRate());
686 	settings->setValue("input/video_scale", GetVideoScalingEnabled());
687 	settings->setValue("input/video_scaled_w", GetVideoScaledW());
688 	settings->setValue("input/video_scaled_h", GetVideoScaledH());
689 	settings->setValue("input/video_record_cursor", GetVideoRecordCursor());
690 	settings->setValue("input/audio_enabled", GetAudioEnabled());
691 	settings->setValue("input/audio_backend", EnumToString(GetAudioBackend()));
692 #if SSR_USE_ALSA
693 	settings->setValue("input/audio_alsa_source", GetALSASourceName());
694 #endif
695 #if SSR_USE_PULSEAUDIO
696 	settings->setValue("input/audio_pulseaudio_source", GetPulseAudioSourceName());
697 #endif
698 #if SSR_USE_JACK
699 	settings->setValue("input/audio_jack_connect_system_capture", GetJackConnectSystemCapture());
700 	settings->setValue("input/audio_jack_connect_system_playback", GetJackConnectSystemPlayback());
701 #endif
702 #if SSR_USE_OPENGL_RECORDING
703 	settings->setValue("input/glinject_channel", GetGLInjectChannel());
704 	settings->setValue("input/glinject_relax_permissions", GetGLInjectRelaxPermissions());
705 	settings->setValue("input/glinject_command", GetGLInjectCommand());
706 	settings->setValue("input/glinject_working_directory", GetGLInjectWorkingDirectory());
707 	settings->setValue("input/glinject_auto_launch", GetGLInjectAutoLaunch());
708 	settings->setValue("input/glinject_limit_fps", GetGLInjectLimitFPS());
709 #endif
710 }
711 
Validate()712 bool PageInput::Validate() {
713 	if(m_grabbing)
714 		return false;
715 	return true;
716 }
717 
718 #if SSR_USE_ALSA
GetALSASourceName()719 QString PageInput::GetALSASourceName() {
720 	return QString::fromStdString(m_alsa_sources[GetALSASource()].m_name);
721 }
722 #endif
723 
724 #if SSR_USE_PULSEAUDIO
GetPulseAudioSourceName()725 QString PageInput::GetPulseAudioSourceName() {
726 	return QString::fromStdString(m_pulseaudio_sources[GetPulseAudioSource()].m_name);
727 }
728 #endif
729 
730 #if SSR_USE_ALSA
FindALSASource(const QString & name)731 unsigned int PageInput::FindALSASource(const QString& name) {
732 	for(unsigned int i = 0; i < m_alsa_sources.size(); ++i) {
733 		if(QString::fromStdString(m_alsa_sources[i].m_name) == name)
734 			return i;
735 	}
736 	return 0;
737 }
738 #endif
739 
740 #if SSR_USE_PULSEAUDIO
FindPulseAudioSource(const QString & name)741 unsigned int PageInput::FindPulseAudioSource(const QString& name) {
742 	for(unsigned int i = 0; i < m_pulseaudio_sources.size(); ++i) {
743 		if(QString::fromStdString(m_pulseaudio_sources[i].m_name) == name)
744 			return i;
745 	}
746 	return 0;
747 }
748 #endif
749 
750 // Tries to find the real window that corresponds to a top-level window (the actual window without window manager decorations).
751 // Returns None if it can't find the window (probably because the window is not handled by the window manager).
752 // Based on the xprop source code (http://cgit.freedesktop.org/xorg/app/xprop/tree/clientwin.c).
X11FindRealWindow(Display * display,Window window)753 static Window X11FindRealWindow(Display* display, Window window) {
754 
755 	// is this the real window?
756 	Atom actual_type;
757 	int actual_format;
758 	unsigned long items, bytes_left;
759 	unsigned char *data = NULL;
760 	XGetWindowProperty(display, window, XInternAtom(display, "WM_STATE", true),
761 					   0, 0, false, AnyPropertyType, &actual_type, &actual_format, &items, &bytes_left, &data);
762 	if(data != NULL)
763 		XFree(data);
764 	if(actual_type != None)
765 		return window;
766 
767 	// get the child windows
768 	Window root, parent, *childs;
769 	unsigned int childcount;
770 	if(!XQueryTree(display, window, &root, &parent, &childs, &childcount)) {
771 		return None;
772 	}
773 
774 	// recursively call this function for all childs
775 	Window real_window = None;
776 	for(unsigned int i = childcount; i > 0; ) {
777 		--i;
778 		Window w = X11FindRealWindow(display, childs[i]);
779 		if(w != None) {
780 			real_window = w;
781 			break;
782 		}
783 	}
784 
785 	// free child window list
786 	if(childs != NULL)
787 		XFree(childs);
788 
789 	return real_window;
790 
791 }
792 
mousePressEvent(QMouseEvent * event)793 void PageInput::mousePressEvent(QMouseEvent* event) {
794 	if(m_grabbing) {
795 		if(event->button() == Qt::LeftButton) {
796 			if(IsPlatformX11()) {
797 				QPoint mouse_physical = GetMousePhysicalCoordinates();
798 				if(m_selecting_window) {
799 					// As expected, Qt does not provide any functions to find the window at a specific position, so I have to use Xlib directly.
800 					// I'm not completely sure whether this is the best way to do this, but it appears to work. XQueryPointer returns the window
801 					// currently below the mouse along with the mouse position, but apparently this may not work correctly when the mouse pointer
802 					// is also grabbed (even though it works fine in my test), so I use XTranslateCoordinates instead. Originally I wanted to
803 					// show the rubber band when the mouse hovers over a window (instead of having to click it), but this doesn't work correctly
804 					// since X will simply return a handle the rubber band itself (even though it should be transparent to mouse events).
805 					Window selected_window;
806 					int x, y;
807 					if(XTranslateCoordinates(QX11Info::display(), QX11Info::appRootWindow(), QX11Info::appRootWindow(), mouse_physical.x(), mouse_physical.y(), &x, &y, &selected_window)) {
808 						XWindowAttributes attributes;
809 						if(selected_window != None && XGetWindowAttributes(QX11Info::display(), selected_window, &attributes)) {
810 
811 							// naive outer/inner rectangle, this won't work for window decorations
812 							m_select_window_outer_rect = QRect(attributes.x, attributes.y, attributes.width + 2 * attributes.border_width, attributes.height + 2 * attributes.border_width);
813 							m_select_window_inner_rect = QRect(attributes.x + attributes.border_width, attributes.y + attributes.border_width, attributes.width, attributes.height);
814 
815 							// try to find the real window (rather than the decorations added by the window manager)
816 							Window real_window = X11FindRealWindow(QX11Info::display(), selected_window);
817 							if(real_window != None) {
818 								Atom actual_type;
819 								int actual_format;
820 								unsigned long items, bytes_left;
821 								long *data = NULL;
822 								int result = XGetWindowProperty(QX11Info::display(), real_window, XInternAtom(QX11Info::display(), "_NET_FRAME_EXTENTS", true),
823 																0, 4, false, AnyPropertyType, &actual_type, &actual_format, &items, &bytes_left, (unsigned char**) &data);
824 								if(result == Success) {
825 									if(items == 4 && bytes_left == 0 && actual_format == 32) { // format 32 means 'long', even if long is 64-bit ...
826 										Window child;
827 										// the attributes of the real window only store the *relative* position which is not what we need, so use XTranslateCoordinates again
828 										if(XTranslateCoordinates(QX11Info::display(), real_window, QX11Info::appRootWindow(), 0, 0, &x, &y, &child)
829 												 && XGetWindowAttributes(QX11Info::display(), real_window, &attributes)) {
830 
831 											// finally!
832 											m_select_window_inner_rect = QRect(x, y, attributes.width, attributes.height);
833 											m_select_window_outer_rect = m_select_window_inner_rect.adjusted(-data[0], -data[2], data[1], data[3]);
834 
835 										} else {
836 
837 											// I doubt this will ever be needed, but do it anyway
838 											m_select_window_inner_rect = m_select_window_outer_rect.adjusted(data[0], data[2], -data[1], -data[3]);
839 
840 										}
841 									}
842 								}
843 								if(data != NULL)
844 									XFree(data);
845 							}
846 
847 							// pick the inner rectangle if the users clicks inside the window, or the outer rectangle otherwise
848 							m_rubber_band_rect = (m_select_window_inner_rect.contains(mouse_physical))? m_select_window_inner_rect : m_select_window_outer_rect;
849 							UpdateRubberBand();
850 
851 						}
852 					}
853 				} else {
854 					m_rubber_band_rect = QRect(mouse_physical, mouse_physical);
855 					UpdateRubberBand();
856 				}
857 			}
858 		} else {
859 			StopGrabbing();
860 		}
861 		event->accept();
862 		return;
863 	}
864 	event->ignore();
865 }
866 
mouseReleaseEvent(QMouseEvent * event)867 void PageInput::mouseReleaseEvent(QMouseEvent* event) {
868 	if(m_grabbing) {
869 		if(event->button() == Qt::LeftButton) {
870 			if(m_rubber_band != NULL) {
871 				SetVideoAreaFromRubberBand();
872 			}
873 		}
874 		StopGrabbing();
875 		event->accept();
876 		return;
877 	}
878 	event->ignore();
879 }
880 
mouseMoveEvent(QMouseEvent * event)881 void PageInput::mouseMoveEvent(QMouseEvent* event) {
882 	if(m_grabbing) {
883 		if(m_rubber_band != NULL && IsPlatformX11()) {
884 			QPoint mouse_physical = GetMousePhysicalCoordinates();
885 			if(m_selecting_window) {
886 				// pick the inner rectangle if the user clicks inside the window, or the outer rectangle otherwise
887 				m_rubber_band_rect = (m_select_window_inner_rect.contains(mouse_physical))? m_select_window_inner_rect : m_select_window_outer_rect;
888 			} else {
889 				m_rubber_band_rect.setBottomRight(mouse_physical);
890 			}
891 			UpdateRubberBand();
892 		}
893 		event->accept();
894 		return;
895 	}
896 	event->ignore();
897 }
898 
keyPressEvent(QKeyEvent * event)899 void PageInput::keyPressEvent(QKeyEvent* event) {
900 	if(m_grabbing) {
901 		if(event->key() == Qt::Key_Escape) {
902 			StopGrabbing();
903 			return;
904 		}
905 		event->accept();
906 		return;
907 	}
908 	event->ignore();
909 }
910 
StartGrabbing()911 void PageInput::StartGrabbing() {
912 	// Grab the mouse and keyboard, and hide the window. Grabbing the keyboard isn't required, but if we don't grab it the user could still alt-tab
913 	// to other windows, switch workspaces, ... which would be very confusing. Grabbing doesn't work if the window is actually hidden or minimized,
914 	// so instead I just lower the window below all other windows (this is probably less confusing too). Grabbing stops as soon as the escape key
915 	// or any mouse button is pressed (or released in the case of the left mouse button).
916 	m_grabbing = true;
917 	if(m_selecting_window)
918 		m_pushbutton_video_select_window->setDown(true);
919 	else
920 		m_pushbutton_video_select_rectangle->setDown(true);
921 	m_main_window->lower();
922 	grabMouse(Qt::CrossCursor);
923 	grabKeyboard();
924 	setMouseTracking(true);
925 }
926 
StopGrabbing()927 void PageInput::StopGrabbing() {
928 	m_rubber_band.reset();
929 	setMouseTracking(false);
930 	releaseKeyboard();
931 	releaseMouse();
932 	m_main_window->raise();
933 	m_main_window->activateWindow();
934 	if(m_selecting_window)
935 		m_pushbutton_video_select_window->setDown(false);
936 	else
937 		m_pushbutton_video_select_rectangle->setDown(false);
938 	m_grabbing = false;
939 }
940 
UpdateRubberBand()941 void PageInput::UpdateRubberBand() {
942 	if(m_rubber_band == NULL)
943 		m_rubber_band.reset(new RecordingFrameWindow(this, false));
944 	m_rubber_band->SetRectangle(m_rubber_band_rect);
945 }
946 
SetVideoAreaFromRubberBand()947 void PageInput::SetVideoAreaFromRubberBand() {
948 	QRect r = m_rubber_band_rect.normalized();
949 	if(GetVideoArea() == VIDEO_AREA_CURSOR) {
950 		SetVideoX(0);
951 		SetVideoY(0);
952 	} else {
953 		SetVideoX(r.x());
954 		SetVideoY(r.y());
955 	}
956 	SetVideoW(r.width());
957 	SetVideoH(r.height());
958 }
959 
LoadScreenConfigurations()960 void PageInput::LoadScreenConfigurations() {
961 	std::vector<QRect> screen_geometries = GetScreenGeometries();
962 	QRect combined_geometry = CombineScreenGeometries(screen_geometries);
963 	m_combobox_screens->clear();
964 	m_combobox_screens->addItem(tr("All screens: %1x%2", "This appears in the screen selection combobox")
965 								.arg(combined_geometry.width()).arg(combined_geometry.height()));
966 	for(size_t i = 0; i < screen_geometries.size(); ++i) {
967 		QRect &geometry = screen_geometries[i];
968 		m_combobox_screens->addItem(tr("Screen %1: %2x%3 at %4,%5", "This appears in the screen selection combobox")
969 									.arg(i + 1).arg(geometry.width()).arg(geometry.height()).arg(geometry.x()).arg(geometry.y()));
970 	}
971 	// update the video x/y/w/h in case the position or size of the selected screen changed
972 	OnUpdateVideoAreaFields();
973 }
974 
975 #if SSR_USE_ALSA
LoadALSASources()976 void PageInput::LoadALSASources() {
977 	m_alsa_sources = ALSAInput::GetSourceList();
978 	if(m_alsa_sources.empty()) {
979 		m_alsa_sources.push_back(ALSAInput::Source("", "(no sources found)"));
980 	}
981 	m_combobox_alsa_source->clear();
982 	for(unsigned int i = 0; i < m_alsa_sources.size(); ++i) {
983 		QString elided = m_combobox_alsa_source->fontMetrics().elidedText("\u200e[" + QString::fromStdString(m_alsa_sources[i].m_name) + "] "
984 																		  + QString::fromStdString(m_alsa_sources[i].m_description), Qt::ElideMiddle, 400) + "\u200e";
985 		m_combobox_alsa_source->addItem(elided);
986 	}
987 }
988 #endif
989 
990 #if SSR_USE_PULSEAUDIO
LoadPulseAudioSources()991 void PageInput::LoadPulseAudioSources() {
992 	m_pulseaudio_sources = PulseAudioInput::GetSourceList();
993 	if(m_pulseaudio_sources.empty()) {
994 		m_pulseaudio_available = false;
995 		m_pulseaudio_sources.push_back(PulseAudioInput::Source("", "(no sources found)"));
996 	} else {
997 		m_pulseaudio_available = true;
998 	}
999 	m_combobox_pulseaudio_source->clear();
1000 	for(unsigned int i = 0; i < m_pulseaudio_sources.size(); ++i) {
1001 		QString elided = m_combobox_pulseaudio_source->fontMetrics().elidedText(QString::fromStdString(m_pulseaudio_sources[i].m_description), Qt::ElideMiddle, 400);
1002 		m_combobox_pulseaudio_source->addItem("\u200e" + elided + "\u200e");
1003 	}
1004 }
1005 #endif
1006 
OnUpdateRecordingFrame()1007 void PageInput::OnUpdateRecordingFrame() {
1008 	if(m_spinbox_video_x->hasFocus() || m_spinbox_video_y->hasFocus() || m_spinbox_video_w->hasFocus() || m_spinbox_video_h->hasFocus()) {
1009 		if(m_recording_frame == NULL)
1010 			m_recording_frame.reset(new RecordingFrameWindow(this, false));
1011 		m_recording_frame->SetRectangle(QRect(GetVideoX(), GetVideoY(), GetVideoW(), GetVideoH()));
1012 	} else {
1013 		m_recording_frame.reset();
1014 	}
1015 }
1016 
OnUpdateVideoAreaFields()1017 void PageInput::OnUpdateVideoAreaFields() {
1018 	switch(GetVideoArea()) {
1019 		case VIDEO_AREA_SCREEN: {
1020 			m_combobox_screens->setEnabled(true);
1021 			m_checkbox_follow_fullscreen->setEnabled(false);
1022 			m_pushbutton_video_select_rectangle->setEnabled(false);
1023 			m_pushbutton_video_select_window->setEnabled(false);
1024 #if SSR_USE_OPENGL_RECORDING
1025 			m_pushbutton_video_opengl_settings->setEnabled(false);
1026 #endif
1027 #if SSR_USE_V4L2
1028 			m_lineedit_v4l2_device->setEnabled(false);
1029 #endif
1030 			m_checkbox_record_cursor->setEnabled(true);
1031 			GroupEnabled({m_label_video_x, m_spinbox_video_x, m_label_video_y, m_spinbox_video_y,
1032 						  m_label_video_w, m_spinbox_video_w, m_label_video_h, m_spinbox_video_h}, false);
1033 			int sc = m_combobox_screens->currentIndex();
1034 			std::vector<QRect> screen_geometries = GetScreenGeometries();
1035 			QRect rect;
1036 			if(sc > 0 && sc <= (int) screen_geometries.size()) {
1037 				rect = screen_geometries[sc - 1];
1038 			} else {
1039 				rect = CombineScreenGeometries(screen_geometries);
1040 			}
1041 			SetVideoX(rect.left());
1042 			SetVideoY(rect.top());
1043 			SetVideoW(rect.width());
1044 			SetVideoH(rect.height());
1045 			break;
1046 		}
1047 		case VIDEO_AREA_FIXED: {
1048 			m_combobox_screens->setEnabled(false);
1049 			m_checkbox_follow_fullscreen->setEnabled(false);
1050 			m_pushbutton_video_select_rectangle->setEnabled(true);
1051 			m_pushbutton_video_select_window->setEnabled(true);
1052 #if SSR_USE_OPENGL_RECORDING
1053 			m_pushbutton_video_opengl_settings->setEnabled(false);
1054 #endif
1055 #if SSR_USE_V4L2
1056 			m_lineedit_v4l2_device->setEnabled(false);
1057 #endif
1058 			m_checkbox_record_cursor->setEnabled(true);
1059 			GroupEnabled({m_label_video_x, m_spinbox_video_x, m_label_video_y, m_spinbox_video_y,
1060 						  m_label_video_w, m_spinbox_video_w, m_label_video_h, m_spinbox_video_h}, true);
1061 			break;
1062 		}
1063 		case VIDEO_AREA_CURSOR: {
1064 			m_combobox_screens->setEnabled(false);
1065 			m_checkbox_follow_fullscreen->setEnabled(true);
1066 #if SSR_USE_OPENGL_RECORDING
1067 			m_pushbutton_video_opengl_settings->setEnabled(false);
1068 #endif
1069 #if SSR_USE_V4L2
1070 			m_lineedit_v4l2_device->setEnabled(false);
1071 #endif
1072 			m_checkbox_record_cursor->setEnabled(true);
1073 			if(m_checkbox_follow_fullscreen->isChecked()) {
1074 				m_pushbutton_video_select_rectangle->setEnabled(false);
1075 				m_pushbutton_video_select_window->setEnabled(false);
1076 				GroupEnabled({m_label_video_x, m_spinbox_video_x, m_label_video_y, m_spinbox_video_y,
1077 							  m_label_video_w, m_spinbox_video_w, m_label_video_h, m_spinbox_video_h}, false);
1078 				std::vector<QRect> screen_geometries = GetScreenGeometries();
1079 				QRect rect = (screen_geometries.size() == 0)? QRect(0, 0, 0, 0) : screen_geometries[0];
1080 				SetVideoX(rect.left());
1081 				SetVideoY(rect.top());
1082 				SetVideoW(rect.width());
1083 				SetVideoH(rect.height());
1084 			} else {
1085 				m_pushbutton_video_select_rectangle->setEnabled(true);
1086 				m_pushbutton_video_select_window->setEnabled(true);
1087 				GroupEnabled({m_label_video_x, m_spinbox_video_x, m_label_video_y, m_spinbox_video_y}, false);
1088 				GroupEnabled({m_label_video_w, m_spinbox_video_w, m_label_video_h, m_spinbox_video_h}, true);
1089 				SetVideoX(0);
1090 				SetVideoY(0);
1091 			}
1092 			break;
1093 		}
1094 #if SSR_USE_OPENGL_RECORDING
1095 		case VIDEO_AREA_GLINJECT: {
1096 			m_combobox_screens->setEnabled(false);
1097 			m_checkbox_follow_fullscreen->setEnabled(false);
1098 			m_pushbutton_video_select_rectangle->setEnabled(false);
1099 			m_pushbutton_video_select_window->setEnabled(false);
1100 			m_pushbutton_video_opengl_settings->setEnabled(true);
1101 #if SSR_USE_V4L2
1102 			m_lineedit_v4l2_device->setEnabled(false);
1103 #endif
1104 			m_checkbox_record_cursor->setEnabled(true);
1105 			GroupEnabled({m_label_video_x, m_spinbox_video_x, m_label_video_y, m_spinbox_video_y,
1106 						  m_label_video_w, m_spinbox_video_w, m_label_video_h, m_spinbox_video_h}, false);
1107 			break;
1108 		}
1109 #endif
1110 #if SSR_USE_V4L2
1111 		case VIDEO_AREA_V4L2: {
1112 			m_combobox_screens->setEnabled(false);
1113 			m_checkbox_follow_fullscreen->setEnabled(false);
1114 			m_pushbutton_video_select_rectangle->setEnabled(false);
1115 			m_pushbutton_video_select_window->setEnabled(false);
1116 #if SSR_USE_OPENGL_RECORDING
1117 			m_pushbutton_video_opengl_settings->setEnabled(false);
1118 #endif
1119 			m_lineedit_v4l2_device->setEnabled(true);
1120 			m_checkbox_record_cursor->setEnabled(false);
1121 			GroupEnabled({m_label_video_x, m_spinbox_video_x, m_label_video_y, m_spinbox_video_y}, false);
1122 			GroupEnabled({m_label_video_w, m_spinbox_video_w, m_label_video_h, m_spinbox_video_h}, true);
1123 			break;
1124 		}
1125 #endif
1126 		default: break;
1127 	}
1128 }
1129 
OnUpdateVideoScaleFields()1130 void PageInput::OnUpdateVideoScaleFields() {
1131 	bool enabled = GetVideoScalingEnabled();
1132 	GroupEnabled({m_label_video_scaled_w, m_spinbox_video_scaled_w, m_label_video_scaled_h, m_spinbox_video_scaled_h}, enabled);
1133 }
1134 
OnUpdateAudioFields()1135 void PageInput::OnUpdateAudioFields() {
1136 	bool enabled = GetAudioEnabled();
1137 	enum_audio_backend backend = GetAudioBackend();
1138 	GroupEnabled({
1139 		m_label_audio_backend, m_combobox_audio_backend,
1140 #if SSR_USE_ALSA
1141 		m_label_alsa_source, m_combobox_alsa_source, m_pushbutton_alsa_refresh,
1142 #endif
1143 #if SSR_USE_PULSEAUDIO
1144 		m_label_pulseaudio_source, m_combobox_pulseaudio_source, m_pushbutton_pulseaudio_refresh,
1145 #endif
1146 #if SSR_USE_JACK
1147 		m_checkbox_jack_connect_system_capture, m_checkbox_jack_connect_system_playback,
1148 #endif
1149 	}, enabled);
1150 	MultiGroupVisible({
1151 #if SSR_USE_ALSA
1152 		{{m_label_alsa_source, m_combobox_alsa_source, m_pushbutton_alsa_refresh}, (backend == AUDIO_BACKEND_ALSA)},
1153 #endif
1154 #if SSR_USE_PULSEAUDIO
1155 		{{m_label_pulseaudio_source, m_combobox_pulseaudio_source, m_pushbutton_pulseaudio_refresh}, (backend == AUDIO_BACKEND_PULSEAUDIO)},
1156 #endif
1157 #if SSR_USE_JACK
1158 		{{m_checkbox_jack_connect_system_capture, m_checkbox_jack_connect_system_playback}, (backend == AUDIO_BACKEND_JACK)},
1159 #endif
1160 	});
1161 }
1162 
OnFocusChange(QWidget * old,QWidget * now)1163 void PageInput::OnFocusChange(QWidget* old, QWidget* now) {
1164 	Q_UNUSED(old);
1165 	if(m_grabbing && now != NULL && now->window() != window()) {
1166 		// workaround to avoid a deadlock situation when a modal dialog appears
1167 		StopGrabbing();
1168 	}
1169 }
1170 
1171 #if QT_VERSION_MAJOR >= 5
OnScreenAdded(QScreen * screen)1172 void PageInput::OnScreenAdded(QScreen* screen) {
1173 	connect(screen, SIGNAL(geometryChanged()), this, SLOT(OnUpdateScreenConfiguration()));
1174 	connect(screen, SIGNAL(physicalDotsPerInchChanged()), this, SLOT(OnUpdateScreenConfiguration()));
1175 	OnUpdateScreenConfiguration();
1176 }
1177 #endif
1178 
OnUpdateScreenConfiguration()1179 void PageInput::OnUpdateScreenConfiguration() {
1180 	unsigned int selected_screen = GetVideoAreaScreen();
1181 	LoadScreenConfigurations();
1182 	SetVideoAreaScreen(selected_screen);
1183 }
1184 
1185 #if SSR_USE_ALSA
OnUpdateALSASources()1186 void PageInput::OnUpdateALSASources() {
1187 	QString selected_source = GetALSASourceName();
1188 	LoadALSASources();
1189 	SetALSASource(FindALSASource(selected_source));
1190 }
1191 #endif
1192 
1193 #if SSR_USE_PULSEAUDIO
OnUpdatePulseAudioSources()1194 void PageInput::OnUpdatePulseAudioSources() {
1195 	QString selected_source = GetPulseAudioSourceName();
1196 	LoadPulseAudioSources();
1197 	SetPulseAudioSource(FindPulseAudioSource(selected_source));
1198 }
1199 #endif
1200 
OnIdentifyScreens()1201 void PageInput::OnIdentifyScreens() {
1202 	OnStopIdentifyScreens();
1203 	std::vector<QRect> screen_geometries = GetScreenGeometries();
1204 	for(size_t i = 0; i < screen_geometries.size(); ++i) {
1205 		QRect &rect = screen_geometries[i];
1206 		ScreenLabelWindow *label = new ScreenLabelWindow(this, tr("Screen %1", "This appears in the screen labels").arg(i + 1));
1207 		label->move(rect.x(), rect.y());
1208 		label->show();
1209 		m_screen_labels.push_back(label);
1210 	}
1211 }
1212 
OnStopIdentifyScreens()1213 void PageInput::OnStopIdentifyScreens() {
1214 	for(unsigned int i = 0; i < m_screen_labels.size(); ++i) {
1215 		delete m_screen_labels[i];
1216 	}
1217 	m_screen_labels.clear();
1218 }
1219 
OnStartSelectRectangle()1220 void PageInput::OnStartSelectRectangle() {
1221 	m_selecting_window = false;
1222 	StartGrabbing();
1223 }
1224 
OnStartSelectWindow()1225 void PageInput::OnStartSelectWindow() {
1226 	m_selecting_window = true;
1227 	StartGrabbing();
1228 }
1229 
1230 #if SSR_USE_OPENGL_RECORDING
OnGLInjectDialog()1231 void PageInput::OnGLInjectDialog() {
1232 	DialogGLInject dialog(this);
1233 	dialog.exec();
1234 }
1235 #endif
1236 
OnContinue()1237 void PageInput::OnContinue() {
1238 	if(!Validate())
1239 		return;
1240 	m_main_window->GoPageOutput();
1241 }
1242