1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Research In Motion
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "windowgrabber.h"
41
42 #include <QAbstractEventDispatcher>
43 #include <QDebug>
44 #include <QGuiApplication>
45 #include <QImage>
46 #include <QThread>
47 #include <qpa/qplatformnativeinterface.h>
48
49 #include <QOpenGLContext>
50 #include <QOpenGLFunctions>
51
52 #include <errno.h>
53
54 QT_BEGIN_NAMESPACE
55
56 static PFNEGLCREATEIMAGEKHRPROC s_eglCreateImageKHR;
57 static PFNEGLDESTROYIMAGEKHRPROC s_eglDestroyImageKHR;
58
WindowGrabber(QObject * parent)59 WindowGrabber::WindowGrabber(QObject *parent)
60 : QObject(parent),
61 m_windowParent(nullptr),
62 m_screenContext(0),
63 m_active(false),
64 m_currentFrame(0),
65 m_eglImageSupported(false),
66 m_eglImageCheck(false)
67 {
68 // grab the window frame with 60 frames per second
69 m_timer.setInterval(1000/60);
70
71 connect(&m_timer, SIGNAL(timeout()), SLOT(triggerUpdate()));
72
73 QCoreApplication::eventDispatcher()->installNativeEventFilter(this);
74
75 for ( int i = 0; i < 2; ++i )
76 m_images[i] = 0;
77
78 // Use of EGL images can be disabled by setting QQNX_MM_DISABLE_EGLIMAGE_SUPPORT to something
79 // non-zero. This is probably useful only to test that this path still works since it results
80 // in a high CPU load.
81 if (!s_eglCreateImageKHR && qgetenv("QQNX_MM_DISABLE_EGLIMAGE_SUPPORT").toInt() == 0) {
82 s_eglCreateImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
83 s_eglDestroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
84 }
85
86 QPlatformNativeInterface *const nativeInterface = QGuiApplication::platformNativeInterface();
87 if (nativeInterface) {
88 m_screenContext = static_cast<screen_context_t>(
89 nativeInterface->nativeResourceForIntegration("screenContext"));
90 }
91
92 // Create a parent window for the window whose content will be grabbed. Since the
93 // window is only a buffer conduit, the characteristics of the parent window are
94 // irrelevant. The contents of the window can be grabbed so long as the window
95 // joins the parent window's group and the parent window is in this process.
96 // Using the window that displays this content isn't possible because there's no
97 // way to reliably retrieve it from this code or any calling code.
98 screen_create_window(&m_windowParent, m_screenContext);
99 screen_create_window_group(m_windowParent, nullptr);
100 }
101
~WindowGrabber()102 WindowGrabber::~WindowGrabber()
103 {
104 screen_destroy_window(m_windowParent);
105 QCoreApplication::eventDispatcher()->removeNativeEventFilter(this);
106 cleanup();
107 }
108
setFrameRate(int frameRate)109 void WindowGrabber::setFrameRate(int frameRate)
110 {
111 m_timer.setInterval(1000/frameRate);
112 }
113
114
setWindowId(const QByteArray & windowId)115 void WindowGrabber::setWindowId(const QByteArray &windowId)
116 {
117 m_windowId = windowId;
118 }
119
start()120 void WindowGrabber::start()
121 {
122 if (m_active)
123 return;
124
125 if (!m_screenContext)
126 screen_get_window_property_pv(m_window, SCREEN_PROPERTY_CONTEXT, reinterpret_cast<void**>(&m_screenContext));
127
128 m_timer.start();
129
130 m_active = true;
131 }
132
stop()133 void WindowGrabber::stop()
134 {
135 if (!m_active)
136 return;
137
138 cleanup();
139
140 m_timer.stop();
141
142 m_active = false;
143 }
144
pause()145 void WindowGrabber::pause()
146 {
147 m_timer.stop();
148 }
149
resume()150 void WindowGrabber::resume()
151 {
152 if (!m_active)
153 return;
154
155 m_timer.start();
156 }
157
handleScreenEvent(screen_event_t screen_event)158 bool WindowGrabber::handleScreenEvent(screen_event_t screen_event)
159 {
160
161 int eventType;
162 if (screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &eventType) != 0) {
163 qWarning() << "WindowGrabber: Failed to query screen event type";
164 return false;
165 }
166
167 if (eventType != SCREEN_EVENT_CREATE)
168 return false;
169
170 screen_window_t window = 0;
171 if (screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&window) != 0) {
172 qWarning() << "WindowGrabber: Failed to query window property";
173 return false;
174 }
175
176 const int maxIdStrLength = 128;
177 char idString[maxIdStrLength];
178 if (screen_get_window_property_cv(window, SCREEN_PROPERTY_ID_STRING, maxIdStrLength, idString) != 0) {
179 qWarning() << "WindowGrabber: Failed to query window ID string";
180 return false;
181 }
182
183 // Grab windows that have a non-empty ID string and a matching window id to grab
184 if (idString[0] != '\0' && m_windowId == idString) {
185 m_window = window;
186 start();
187 }
188
189 return false;
190 }
191
nativeEventFilter(const QByteArray & eventType,void * message,long *)192 bool WindowGrabber::nativeEventFilter(const QByteArray &eventType, void *message, long*)
193 {
194 if (eventType == "screen_event_t") {
195 const screen_event_t event = static_cast<screen_event_t>(message);
196 return handleScreenEvent(event);
197 }
198
199 return false;
200 }
201
windowGroupId() const202 QByteArray WindowGrabber::windowGroupId() const
203 {
204 char groupName[256];
205 memset(groupName, 0, sizeof(groupName));
206 screen_get_window_property_cv(m_windowParent,
207 SCREEN_PROPERTY_GROUP,
208 sizeof(groupName) - 1,
209 groupName);
210 return QByteArray(groupName);
211 }
212
eglImageSupported()213 bool WindowGrabber::eglImageSupported()
214 {
215 return m_eglImageSupported;
216 }
217
checkForEglImageExtension()218 void WindowGrabber::checkForEglImageExtension()
219 {
220 QOpenGLContext *m_context = QOpenGLContext::currentContext();
221 if (!m_context) //Should not happen, because we are called from the render thread
222 return;
223
224 QByteArray eglExtensions = QByteArray(eglQueryString(eglGetDisplay(EGL_DEFAULT_DISPLAY),
225 EGL_EXTENSIONS));
226 m_eglImageSupported = m_context->hasExtension(QByteArrayLiteral("GL_OES_EGL_image"))
227 && eglExtensions.contains(QByteArrayLiteral("EGL_KHR_image"))
228 && s_eglCreateImageKHR && s_eglDestroyImageKHR;
229
230 if (strstr(reinterpret_cast<const char*>(glGetString(GL_VENDOR)), "VMware"))
231 m_eglImageSupported = false;
232
233 m_eglImageCheck = true;
234 }
235
triggerUpdate()236 void WindowGrabber::triggerUpdate()
237 {
238 if (!m_eglImageCheck) // We did not check for egl images yet
239 return;
240
241 int size[2] = { 0, 0 };
242
243 int result = screen_get_window_property_iv(m_window, SCREEN_PROPERTY_SOURCE_SIZE, size);
244 if (result != 0) {
245 cleanup();
246 qWarning() << "WindowGrabber: cannot get window size:" << strerror(errno);
247 return;
248 }
249
250 if (m_size.width() != size[0] || m_size.height() != size[1])
251 m_size = QSize(size[0], size[1]);
252
253 emit updateScene(m_size);
254 }
255
selectBuffer()256 bool WindowGrabber::selectBuffer()
257 {
258 // If we're using egl images we need to double buffer since the gpu may still be using the last
259 // video frame. If we're not, it doesn't matter since the data is immediately copied.
260 if (eglImageSupported())
261 m_currentFrame = (m_currentFrame + 1) % 2;
262
263 if (!m_images[m_currentFrame]) {
264 m_images[m_currentFrame] = new WindowGrabberImage();
265 if (!m_images[m_currentFrame]->initialize(m_screenContext)) {
266 delete m_images[m_currentFrame];
267 m_images[m_currentFrame] = 0;
268 return false;
269 }
270 }
271 return true;
272 }
273
getNextTextureId()274 int WindowGrabber::getNextTextureId()
275 {
276 if (!selectBuffer())
277 return 0;
278 return m_images[m_currentFrame]->getTexture(m_window, m_size);
279 }
280
getNextImage()281 QImage WindowGrabber::getNextImage()
282 {
283 if (!selectBuffer())
284 return QImage();
285
286 return m_images[m_currentFrame]->getImage(m_window, m_size);
287 }
288
cleanup()289 void WindowGrabber::cleanup()
290 {
291 for ( int i = 0; i < 2; ++i ) {
292 if (m_images[i]) {
293 m_images[i]->destroy();
294 m_images[i] = 0;
295 }
296 }
297 }
298
299
WindowGrabberImage()300 WindowGrabberImage::WindowGrabberImage()
301 : m_pixmap(0), m_pixmapBuffer(0), m_eglImage(0), m_glTexture(0), m_bufferAddress(0), m_bufferStride(0)
302 {
303 }
304
~WindowGrabberImage()305 WindowGrabberImage::~WindowGrabberImage()
306 {
307 if (m_glTexture)
308 glDeleteTextures(1, &m_glTexture);
309 if (m_eglImage)
310 s_eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage);
311 if (m_pixmap)
312 screen_destroy_pixmap(m_pixmap);
313 }
314
315 bool
initialize(screen_context_t screenContext)316 WindowGrabberImage::initialize(screen_context_t screenContext)
317 {
318 if (screen_create_pixmap(&m_pixmap, screenContext) != 0) {
319 qWarning() << "WindowGrabber: cannot create pixmap:" << strerror(errno);
320 return false;
321 }
322 const int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_READ | SCREEN_USAGE_NATIVE;
323 screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_USAGE, &usage);
324
325 const int format = SCREEN_FORMAT_RGBX8888;
326 screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_FORMAT, &format);
327
328 return true;
329 }
330
331 void
destroy()332 WindowGrabberImage::destroy()
333 {
334 // We want to delete in the thread we were created in since we need the thread that
335 // has called eglMakeCurrent on the right EGL context. This doesn't actually guarantee
336 // this but that would be hard to achieve and in practice it works.
337 if (QThread::currentThread() == thread())
338 delete this;
339 else
340 deleteLater();
341 }
342
343 bool
resize(const QSize & newSize)344 WindowGrabberImage::resize(const QSize &newSize)
345 {
346 if (m_pixmapBuffer) {
347 screen_destroy_pixmap_buffer(m_pixmap);
348 m_pixmapBuffer = 0;
349 m_bufferAddress = 0;
350 m_bufferStride = 0;
351 }
352
353 int size[2] = { newSize.width(), newSize.height() };
354
355 screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_BUFFER_SIZE, size);
356
357 if (screen_create_pixmap_buffer(m_pixmap) == 0) {
358 screen_get_pixmap_property_pv(m_pixmap, SCREEN_PROPERTY_RENDER_BUFFERS,
359 reinterpret_cast<void**>(&m_pixmapBuffer));
360 screen_get_buffer_property_pv(m_pixmapBuffer, SCREEN_PROPERTY_POINTER,
361 reinterpret_cast<void**>(&m_bufferAddress));
362 screen_get_buffer_property_iv(m_pixmapBuffer, SCREEN_PROPERTY_STRIDE, &m_bufferStride);
363 m_size = newSize;
364
365 return true;
366 } else {
367 m_size = QSize();
368 return false;
369 }
370 }
371
372 bool
grab(screen_window_t window)373 WindowGrabberImage::grab(screen_window_t window)
374 {
375 const int rect[] = { 0, 0, m_size.width(), m_size.height() };
376 return screen_read_window(window, m_pixmapBuffer, 1, rect, 0) == 0;
377 }
378
379 QImage
getImage(screen_window_t window,const QSize & size)380 WindowGrabberImage::getImage(screen_window_t window, const QSize &size)
381 {
382 if (size != m_size) {
383 if (!resize(size))
384 return QImage();
385 }
386 if (!m_bufferAddress || !grab(window))
387 return QImage();
388
389 return QImage(m_bufferAddress, m_size.width(), m_size.height(), m_bufferStride, QImage::Format_ARGB32);
390 }
391
392 GLuint
getTexture(screen_window_t window,const QSize & size)393 WindowGrabberImage::getTexture(screen_window_t window, const QSize &size)
394 {
395 if (size != m_size) {
396 if (!m_glTexture)
397 glGenTextures(1, &m_glTexture);
398 glBindTexture(GL_TEXTURE_2D, m_glTexture);
399 if (m_eglImage) {
400 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, 0);
401 s_eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage);
402 m_eglImage = 0;
403 }
404 if (!resize(size))
405 return 0;
406 m_eglImage = s_eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_CONTEXT,
407 EGL_NATIVE_PIXMAP_KHR, m_pixmap, 0);
408 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage);
409 }
410
411 if (!m_pixmap || !grab(window))
412 return 0;
413
414 return m_glTexture;
415 }
416
417
418
419 QT_END_NAMESPACE
420