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