Fix output geometry in qwindow-compositor
[profile/ivi/qtwayland.git] / examples / qwindow-compositor / qwindowcompositor.cpp
index d4e3f5d..65aafb0 100644 (file)
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the Qt Compositor.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
 #include "qwindowcompositor.h"
+
 #include <QMouseEvent>
 #include <QKeyEvent>
 #include <QTouchEvent>
+#include <QOpenGLFunctions>
+#include <QGuiApplication>
+#include <QCursor>
+#include <QPixmap>
+#include <QLinkedList>
+#include <QScreen>
+
+#include <QtCompositor/waylandinput.h>
 
 QWindowCompositor::QWindowCompositor(QOpenGLWindow *window)
     : WaylandCompositor(window)
     , m_window(window)
+    , m_textureBlitter(0)
+    , m_renderScheduler(this)
+    , m_draggingWindow(0)
+    , m_dragKeyIsPressed(false)
+    , m_modifiers(Qt::NoModifier)
 {
+    enableSubSurfaceExtension();
+    m_window->makeCurrent();
+
+    m_textureCache = new QOpenGLTextureCache(m_window->context());
+    m_textureBlitter = new TextureBlitter();
     m_backgroundImage = QImage(QLatin1String(":/background.jpg"));
-    m_renderer = new SurfaceRenderer(m_window->context(), m_window);
-    m_backgroundTexture = m_renderer->textureFromImage(m_backgroundImage);
+    m_renderScheduler.setSingleShot(true);
+    connect(&m_renderScheduler,SIGNAL(timeout()),this,SLOT(render()));
+
+    QOpenGLFunctions *functions = m_window->context()->functions();
+    functions->glGenFramebuffers(1, &m_surface_fbo);
 
     window->installEventFilter(this);
 
-    render();
+    setRetainedSelectionEnabled(true);
+
+    setOutputGeometry(QRect(QPoint(0, 0), window->size()));
+    setOutputRefreshRate(qGuiApp->primaryScreen()->refreshRate());
+}
+
+QWindowCompositor::~QWindowCompositor()
+{
+    delete m_textureBlitter;
+    delete m_textureCache;
 }
 
 void QWindowCompositor::surfaceDestroyed(QObject *object)
 {
     WaylandSurface *surface = static_cast<WaylandSurface *>(object);
     m_surfaces.removeOne(surface);
-    if (inputFocus() == surface || !inputFocus()) // typically reset to 0 already in Compositor::surfaceDestroyed()
-        setInputFocus(m_surfaces.isEmpty() ? 0 : m_surfaces.last());
-    render();
+    if (defaultInputDevice()->keyboardFocus() == surface || !defaultInputDevice()->keyboardFocus()) // typically reset to 0 already in Compositor::surfaceDestroyed()
+        defaultInputDevice()->setKeyboardFocus(m_surfaces.isEmpty() ? 0 : m_surfaces.last());
+    m_renderScheduler.start(0);
 }
 
-void QWindowCompositor::surfaceMapped(const QSize &size)
+void QWindowCompositor::surfaceMapped()
 {
     WaylandSurface *surface = qobject_cast<WaylandSurface *>(sender());
     QPoint pos;
     if (!m_surfaces.contains(surface)) {
-        uint px = 1 + (qrand() % (m_window->width() - size.width() - 2));
-        uint py = 1 + (qrand() % (m_window->height() - size.height() - 2));
+        uint px = 0;
+        uint py = 0;
+        if (!QCoreApplication::arguments().contains(QLatin1String("-stickytopleft"))) {
+            px = 1 + (qrand() % (m_window->width() - surface->size().width() - 2));
+            py = 1 + (qrand() % (m_window->height() - surface->size().height() - 2));
+        }
         pos = QPoint(px, py);
-        surface->setGeometry(QRect(pos, size));
+        surface->setPos(pos);
     } else {
-        surface->setGeometry(QRect(window()->geometry().topLeft(),size));
         m_surfaces.removeOne(surface);
     }
     m_surfaces.append(surface);
-    setInputFocus(surface);
-    render();
+    defaultInputDevice()->setKeyboardFocus(surface);
+    m_renderScheduler.start(0);
 }
 
 void QWindowCompositor::surfaceDamaged(const QRect &rect)
@@ -53,61 +127,128 @@ void QWindowCompositor::surfaceDamaged(WaylandSurface *surface, const QRect &rec
 {
     Q_UNUSED(surface)
     Q_UNUSED(rect)
-    render();
+    m_renderScheduler.start(0);
 }
 
 void QWindowCompositor::surfaceCreated(WaylandSurface *surface)
 {
     connect(surface, SIGNAL(destroyed(QObject *)), this, SLOT(surfaceDestroyed(QObject *)));
-    connect(surface, SIGNAL(mapped(const QSize &)), this, SLOT(surfaceMapped(const QSize &)));
+    connect(surface, SIGNAL(mapped()), this, SLOT(surfaceMapped()));
     connect(surface, SIGNAL(damaged(const QRect &)), this, SLOT(surfaceDamaged(const QRect &)));
-    render();
+    connect(surface, SIGNAL(extendedSurfaceReady()), this, SLOT(sendExpose()));
+    m_renderScheduler.start(0);
+}
+
+void QWindowCompositor::sendExpose()
+{
+    WaylandSurface *surface = qobject_cast<WaylandSurface *>(sender());
+    surface->sendOnScreenVisibilityChange(true);
 }
 
 QPointF QWindowCompositor::toSurface(WaylandSurface *surface, const QPointF &pos) const
 {
-    return pos - surface->geometry().topLeft();
+    return pos - surface->pos();
+}
+
+void QWindowCompositor::changeCursor(const QImage &image, int hotspotX, int hotspotY)
+{
+    QCursor cursor(QPixmap::fromImage(image),hotspotX,hotspotY);
+    static bool cursroIsSet = false;
+    if (cursroIsSet) {
+        QGuiApplication::changeOverrideCursor(cursor);
+    } else {
+        QGuiApplication::setOverrideCursor(cursor);
+        cursroIsSet = true;
+    }
 }
 
-WaylandSurface *QWindowCompositor::surfaceAt(const QPoint &point, QPoint *local)
+WaylandSurface *QWindowCompositor::surfaceAt(const QPointF &point, QPointF *local)
 {
     for (int i = m_surfaces.size() - 1; i >= 0; --i) {
-        if (m_surfaces.at(i)->geometry().contains(point)) {
+        WaylandSurface *surface = m_surfaces.at(i);
+        QRectF geo(surface->pos(), surface->size());
+        if (geo.contains(point)) {
             if (local)
-                *local = toSurface(m_surfaces.at(i), point).toPoint();
-            return m_surfaces.at(i);
+                *local = toSurface(surface, point);
+            return surface;
         }
     }
     return 0;
 }
 
+GLuint QWindowCompositor::composeSurface(WaylandSurface *surface)
+{
+    GLuint texture = 0;
+
+    QOpenGLFunctions *functions = QOpenGLContext::currentContext()->functions();
+    functions->glBindFramebuffer(GL_FRAMEBUFFER, m_surface_fbo);
+
+    if (surface->type() == WaylandSurface::Shm) {
+        texture = m_textureCache->bindTexture(QOpenGLContext::currentContext(),surface->image());
+    } else if (surface->type() == WaylandSurface::Texture) {
+        texture = surface->texture(QOpenGLContext::currentContext());
+    }
+
+    functions->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                       GL_TEXTURE_2D, texture, 0);
+    paintChildren(surface,surface);
+    functions->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                       GL_TEXTURE_2D,0, 0);
+
+    functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+    return texture;
+}
+
+void QWindowCompositor::paintChildren(WaylandSurface *surface, WaylandSurface *window) {
+
+    if (surface->subSurfaces().size() == 0)
+        return;
+
+    QLinkedListIterator<WaylandSurface *> i(surface->subSurfaces());
+    while (i.hasNext()) {
+        WaylandSurface *subSurface = i.next();
+        QPointF p = subSurface->mapTo(window,QPointF(0,0));
+        if (subSurface->size().isValid()) {
+            GLuint texture = 0;
+            if (subSurface->type() == WaylandSurface::Texture) {
+                texture = subSurface->texture(QOpenGLContext::currentContext());
+            } else if (surface->type() == WaylandSurface::Shm ) {
+                texture = m_textureCache->bindTexture(QOpenGLContext::currentContext(),surface->image());
+            }
+            QRect geo(p.toPoint(),subSurface->size());
+            m_textureBlitter->drawTexture(texture,geo,window->size(),0,window->isYInverted(),subSurface->isYInverted());
+        }
+        paintChildren(subSurface,window);
+    }
+}
+
+
 void QWindowCompositor::render()
 {
     m_window->makeCurrent();
+    m_backgroundTexture = m_textureCache->bindTexture(QOpenGLContext::currentContext(),m_backgroundImage);
 
+    m_textureBlitter->bind();
     //Draw the background Image texture
     int w = m_window->width();
     int h = m_window->height();
-    int iw = m_backgroundImage.width();
-    int ih = m_backgroundImage.height();
-    for (int y = 0; y < h; y += ih) {
-        for (int x = 0; x < w; x += iw) {
-            m_renderer->drawTexture(m_backgroundTexture, QRect(QPoint(x, y), QSize(iw, ih)), 0);
+    QSize imageSize = m_backgroundImage.size();
+    for (int y = 0; y < h; y += imageSize.height()) {
+        for (int x = 0; x < w; x += imageSize.width()) {
+            m_textureBlitter->drawTexture(m_backgroundTexture,QRect(QPoint(x, y),imageSize),window()->size(), 0,true,true);
         }
     }
 
-    //Iterate all surfaces in m_surfaces
-    //If type == WaylandSurface::Texture draw textureId at geometry
     foreach (WaylandSurface *surface, m_surfaces) {
-        if (surface->type() == WaylandSurface::Texture)
-            m_renderer->drawTexture(surface->texture(QOpenGLContext::currentContext()), surface->geometry(), 1); //depth argument should be dynamic (focused should be top).
-        else if (surface->type() == WaylandSurface::Shm)
-            m_renderer->drawImage(surface->image(), surface->geometry());
+        GLuint texture = composeSurface(surface);
+        QRect geo(surface->pos().toPoint(),surface->size());
+        m_textureBlitter->drawTexture(texture,geo,m_window->size(),0,false,surface->isYInverted());
     }
+
+    m_textureBlitter->release();
     frameFinished();
-    glFinish();
+    // N.B. Never call glFinish() here as the busylooping with vsync 'feature' of the nvidia binary driver is not desirable.
     m_window->swapBuffers();
-    m_window->context()->doneCurrent();
 }
 
 bool QWindowCompositor::eventFilter(QObject *obj, QEvent *event)
@@ -115,65 +256,112 @@ bool QWindowCompositor::eventFilter(QObject *obj, QEvent *event)
     if (obj != m_window)
         return false;
 
+    WaylandInputDevice *input = defaultInputDevice();
+
     switch (event->type()) {
     case QEvent::Expose:
-        render();
+        m_renderScheduler.start(0);
+        if (m_window->isExposed()) {
+            // Alt-tabbing away normally results in the alt remaining in
+            // pressed state in the clients xkb state. Prevent this by sending
+            // a release. This is not an issue in a "real" compositor but
+            // is very annoying when running in a regular window on xcb.
+            Qt::KeyboardModifiers mods = QGuiApplication::queryKeyboardModifiers();
+            if (m_modifiers != mods && input->keyboardFocus()) {
+                Qt::KeyboardModifiers stuckMods = m_modifiers ^ mods;
+                if (stuckMods & Qt::AltModifier)
+                    input->sendKeyReleaseEvent(64); // native scancode for left alt
+                m_modifiers = mods;
+            }
+        }
         break;
     case QEvent::MouseButtonPress: {
-        QPoint local;
+        QPointF local;
         QMouseEvent *me = static_cast<QMouseEvent *>(event);
-        WaylandSurface *targetSurface = surfaceAt(me->pos(), &local);
-        if (targetSurface) {
-            if (inputFocus() != targetSurface) {
-                setInputFocus(targetSurface);
+        WaylandSurface *targetSurface = surfaceAt(me->localPos(), &local);
+        if (m_dragKeyIsPressed && targetSurface) {
+            m_draggingWindow = targetSurface;
+            m_drag_diff = local;
+        } else {
+            if (targetSurface && input->keyboardFocus() != targetSurface) {
+                input->setKeyboardFocus(targetSurface);
                 m_surfaces.removeOne(targetSurface);
                 m_surfaces.append(targetSurface);
-                render();
+                m_renderScheduler.start(0);
             }
-            targetSurface->sendMousePressEvent(local, me->button());
+            input->sendMousePressEvent(me->button(), local, me->localPos());
         }
-        break;
+        return true;
     }
     case QEvent::MouseButtonRelease: {
-        WaylandSurface *targetSurface = inputFocus();
-        if (targetSurface) {
+        WaylandSurface *targetSurface = input->mouseFocus();
+        if (m_draggingWindow) {
+            m_draggingWindow = 0;
+            m_drag_diff = QPointF();
+        } else {
             QMouseEvent *me = static_cast<QMouseEvent *>(event);
-            targetSurface->sendMouseReleaseEvent(toSurface(targetSurface, me->pos()).toPoint(), me->button());
+            QPointF localPos;
+            if (targetSurface)
+                localPos = toSurface(targetSurface, me->localPos());
+            input->sendMouseReleaseEvent(me->button(), localPos, me->localPos());
+        }
+        return true;
+    }
+    case QEvent::MouseMove: {
+        QMouseEvent *me = static_cast<QMouseEvent *>(event);
+        if (m_draggingWindow) {
+            m_draggingWindow->setPos(me->localPos() - m_drag_diff);
+            m_renderScheduler.start(0);
+        } else {
+            QPointF local;
+            WaylandSurface *targetSurface = surfaceAt(me->localPos(), &local);
+            input->sendMouseMoveEvent(targetSurface, local, me->localPos());
         }
         break;
     }
+    case QEvent::Wheel: {
+        QWheelEvent *we = static_cast<QWheelEvent *>(event);
+        input->sendMouseWheelEvent(we->orientation(), we->delta());
+        break;
+    }
     case QEvent::KeyPress: {
         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
-        WaylandSurface *targetSurface = inputFocus();
+        if (ke->key() == Qt::Key_Meta || ke->key() == Qt::Key_Super_L) {
+            m_dragKeyIsPressed = true;
+        }
+        m_modifiers = ke->modifiers();
+        WaylandSurface *targetSurface = input->keyboardFocus();
         if (targetSurface)
-            targetSurface->sendKeyPressEvent(ke->nativeScanCode());
+            input->sendKeyPressEvent(ke->nativeScanCode());
         break;
     }
     case QEvent::KeyRelease: {
         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
-        WaylandSurface *targetSurface = inputFocus();
+        if (ke->key() == Qt::Key_Meta || ke->key() == Qt::Key_Super_L) {
+            m_dragKeyIsPressed = false;
+        }
+        m_modifiers = ke->modifiers();
+        WaylandSurface *targetSurface = input->keyboardFocus();
         if (targetSurface)
-            targetSurface->sendKeyReleaseEvent(ke->nativeScanCode());
+            input->sendKeyReleaseEvent(ke->nativeScanCode());
         break;
     }
     case QEvent::TouchBegin:
     case QEvent::TouchUpdate:
     case QEvent::TouchEnd:
     {
-        QSet<WaylandSurface *> targets;
+        WaylandSurface *targetSurface = 0;
         QTouchEvent *te = static_cast<QTouchEvent *>(event);
         QList<QTouchEvent::TouchPoint> points = te->touchPoints();
-        for (int i = 0; i < points.count(); ++i) {
-            const QTouchEvent::TouchPoint &tp(points.at(i));
-            QPoint local;
-            WaylandSurface *targetSurface = surfaceAt(tp.pos().toPoint(), &local);
-            if (targetSurface) {
-                targetSurface->sendTouchPointEvent(tp.id(), local.x(), local.y(), tp.state());
-                targets.insert(targetSurface);
-            }
+        QPoint pointPos;
+        if (!points.isEmpty()) {
+            pointPos = points.at(0).pos().toPoint();
+            targetSurface = surfaceAt(pointPos);
         }
-        foreach (WaylandSurface *surface, targets)
-            surface->sendTouchFrameEvent();
+        if (targetSurface && targetSurface != input->mouseFocus())
+            input->setMouseFocus(targetSurface, pointPos, pointPos);
+        if (input->mouseFocus())
+            input->sendFullTouchEvent(te);
         break;
     }
     default: