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