summaryrefslogtreecommitdiffstats
path: root/examples/wayland/qwindow-compositor/compositor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/wayland/qwindow-compositor/compositor.cpp')
-rw-r--r--examples/wayland/qwindow-compositor/compositor.cpp494
1 files changed, 494 insertions, 0 deletions
diff --git a/examples/wayland/qwindow-compositor/compositor.cpp b/examples/wayland/qwindow-compositor/compositor.cpp
new file mode 100644
index 000000000..065a6bfae
--- /dev/null
+++ b/examples/wayland/qwindow-compositor/compositor.cpp
@@ -0,0 +1,494 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Wayland module
+**
+** $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 The Qt Company Ltd 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 "compositor.h"
+
+#include <QMouseEvent>
+#include <QKeyEvent>
+#include <QTouchEvent>
+
+#include <QtWaylandCompositor/QWaylandXdgShell>
+#include <QtWaylandCompositor/QWaylandWlShellSurface>
+#include <QtWaylandCompositor/qwaylandinput.h>
+#include <QtWaylandCompositor/qwaylanddrag.h>
+
+#include <QDebug>
+#include <QOpenGLContext>
+
+#ifndef GL_TEXTURE_EXTERNAL_OES
+#define GL_TEXTURE_EXTERNAL_OES 0x8D65
+#endif
+
+View::View()
+ : m_textureTarget(GL_TEXTURE_2D)
+ , m_texture(0)
+ , m_wlShellSurface(nullptr)
+ , m_xdgSurface(nullptr)
+ , m_xdgPopup(nullptr)
+ , m_parentView(nullptr)
+{}
+
+GLuint View::getTexture(GLenum *target)
+{
+ QWaylandBufferRef buf = currentBuffer();
+ GLuint streamingTexture = buf.textureForPlane(0);
+ if (streamingTexture)
+ m_texture = streamingTexture;
+
+ if (!buf.isShm() && buf.bufferFormatEgl() == QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES)
+ m_textureTarget = GL_TEXTURE_EXTERNAL_OES;
+
+ if (advance()) {
+ buf = currentBuffer();
+ if (!m_texture)
+ glGenTextures(1, &m_texture);
+
+ glBindTexture(m_textureTarget, m_texture);
+ if (buf.isShm())
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ buf.bindToTexture();
+ }
+
+ buf.updateTexture();
+
+ if (target)
+ *target = m_textureTarget;
+
+ return m_texture;
+}
+
+bool View::isCursor() const
+{
+ return surface()->isCursorSurface();
+}
+
+void View::onXdgSetMaximized()
+{
+ m_xdgSurface->requestMaximized(output()->geometry().size());
+
+ // An improvement here, would have been to wait for the commit after the ack_configure for the
+ // request above before moving the window. This would have prevented the window from being
+ // moved until the contents of the window had actually updated. This improvement is left as an
+ // exercise for the reader.
+ setPosition(QPoint(0, 0));
+}
+
+void View::onXdgUnsetMaximized()
+{
+ m_xdgSurface->requestUnMaximized();
+}
+
+void View::onXdgSetFullscreen(QWaylandOutput* clientPreferredOutput)
+{
+ QWaylandOutput *outputToFullscreen = clientPreferredOutput
+ ? clientPreferredOutput
+ : output();
+
+ m_xdgSurface->requestFullscreen(outputToFullscreen->geometry().size());
+
+ // An improvement here, would have been to wait for the commit after the ack_configure for the
+ // request above before moving the window. This would have prevented the window from being
+ // moved until the contents of the window had actually updated. This improvement is left as an
+ // exercise for the reader.
+ setPosition(outputToFullscreen->position());
+}
+
+void View::onOffsetForNextFrame(const QPoint &offset)
+{
+ m_offset = offset;
+ setPosition(position() + offset);
+}
+
+void View::onXdgUnsetFullscreen()
+{
+ onXdgUnsetMaximized();
+}
+
+Compositor::Compositor(QWindow *window)
+ : QWaylandCompositor()
+ , m_window(window)
+ , m_wlShell(new QWaylandWlShell(this))
+ , m_xdgShell(new QWaylandXdgShell(this))
+{
+ connect(m_wlShell, &QWaylandWlShell::shellSurfaceCreated, this, &Compositor::onWlShellSurfaceCreated);
+ connect(m_xdgShell, &QWaylandXdgShell::xdgSurfaceCreated, this, &Compositor::onXdgSurfaceCreated);
+ connect(m_xdgShell, &QWaylandXdgShell::createXdgPopup, this, &Compositor::onCreateXdgPopup);
+}
+
+Compositor::~Compositor()
+{
+}
+
+void Compositor::create()
+{
+ new QWaylandOutput(this, m_window);
+ QWaylandCompositor::create();
+
+ connect(this, &QWaylandCompositor::surfaceCreated, this, &Compositor::onSurfaceCreated);
+ connect(defaultInputDevice(), &QWaylandInputDevice::cursorSurfaceRequest, this, &Compositor::adjustCursorSurface);
+ connect(defaultInputDevice()->drag(), &QWaylandDrag::dragStarted, this, &Compositor::startDrag);
+
+ connect(this, &QWaylandCompositor::subsurfaceChanged, this, &Compositor::onSubsurfaceChanged);
+}
+
+void Compositor::onSurfaceCreated(QWaylandSurface *surface)
+{
+ connect(surface, &QWaylandSurface::surfaceDestroyed, this, &Compositor::surfaceDestroyed);
+ connect(surface, &QWaylandSurface::mappedChanged, this, &Compositor::surfaceMappedChanged);
+ connect(surface, &QWaylandSurface::redraw, this, &Compositor::triggerRender);
+
+ connect(surface, &QWaylandSurface::subsurfacePositionChanged, this, &Compositor::onSubsurfacePositionChanged);
+
+ View *view = new View;
+ view->setSurface(surface);
+ view->setOutput(outputFor(m_window));
+ m_views << view;
+ connect(view, &QWaylandView::surfaceDestroyed, this, &Compositor::viewSurfaceDestroyed);
+ connect(surface, &QWaylandSurface::offsetForNextFrame, view, &View::onOffsetForNextFrame);
+}
+
+void Compositor::surfaceMappedChanged()
+{
+ QWaylandSurface *surface = qobject_cast<QWaylandSurface *>(sender());
+ if (surface->isMapped()) {
+ if (surface->role() == QWaylandWlShellSurface::role()
+ || surface->role() == QWaylandXdgSurface::role()
+ || surface->role() == QWaylandXdgPopup::role()) {
+ defaultInputDevice()->setKeyboardFocus(surface);
+ }
+ } else if (popupActive()) {
+ for (int i = 0; i < m_popupViews.count(); i++) {
+ if (m_popupViews.at(i)->surface() == surface) {
+ m_popupViews.removeAt(i);
+ break;
+ }
+ }
+ }
+ triggerRender();
+}
+
+void Compositor::surfaceDestroyed()
+{
+ triggerRender();
+}
+
+void Compositor::viewSurfaceDestroyed()
+{
+ View *view = qobject_cast<View*>(sender());
+ m_views.removeAll(view);
+ delete view;
+}
+
+View * Compositor::findView(const QWaylandSurface *s) const
+{
+ Q_FOREACH (View* view, m_views) {
+ if (view->surface() == s)
+ return view;
+ }
+ return Q_NULLPTR;
+}
+
+void Compositor::onWlShellSurfaceCreated(QWaylandWlShellSurface *wlShellSurface)
+{
+ connect(wlShellSurface, &QWaylandWlShellSurface::startMove, this, &Compositor::onStartMove);
+ connect(wlShellSurface, &QWaylandWlShellSurface::startResize, this, &Compositor::onWlStartResize);
+ connect(wlShellSurface, &QWaylandWlShellSurface::setTransient, this, &Compositor::onSetTransient);
+ connect(wlShellSurface, &QWaylandWlShellSurface::setPopup, this, &Compositor::onSetPopup);
+
+ View *view = findView(wlShellSurface->surface());
+ Q_ASSERT(view);
+ view->m_wlShellSurface = wlShellSurface;
+}
+
+void Compositor::onXdgSurfaceCreated(QWaylandXdgSurface *xdgSurface)
+{
+ connect(xdgSurface, &QWaylandXdgSurface::startMove, this, &Compositor::onStartMove);
+ connect(xdgSurface, &QWaylandXdgSurface::startResize, this, &Compositor::onXdgStartResize);
+
+ View *view = findView(xdgSurface->surface());
+ Q_ASSERT(view);
+ view->m_xdgSurface = xdgSurface;
+
+ connect(xdgSurface, &QWaylandXdgSurface::setMaximized, view, &View::onXdgSetMaximized);
+ connect(xdgSurface, &QWaylandXdgSurface::setFullscreen, view, &View::onXdgSetFullscreen);
+ connect(xdgSurface, &QWaylandXdgSurface::unsetMaximized, view, &View::onXdgUnsetMaximized);
+ connect(xdgSurface, &QWaylandXdgSurface::unsetFullscreen, view, &View::onXdgUnsetFullscreen);
+}
+
+void Compositor::onCreateXdgPopup(QWaylandSurface *surface, QWaylandSurface *parent,
+ QWaylandInputDevice *inputDevice, const QPoint &position,
+ const QWaylandResource &resource)
+{
+ Q_UNUSED(inputDevice);
+
+ QWaylandXdgPopup *xdgPopup = new QWaylandXdgPopup(m_xdgShell, surface, parent, resource);
+
+ View *view = findView(surface);
+ Q_ASSERT(view);
+
+ View *parentView = findView(parent);
+ Q_ASSERT(parentView);
+
+ view->setPosition(parentView->position() + position);
+ view->m_xdgPopup = xdgPopup;
+}
+
+void Compositor::onStartMove()
+{
+ closePopups();
+ emit startMove();
+}
+
+void Compositor::onWlStartResize(QWaylandInputDevice *, QWaylandWlShellSurface::ResizeEdge edges)
+{
+ closePopups();
+ emit startResize(int(edges), false);
+}
+
+void Compositor::onXdgStartResize(QWaylandInputDevice *inputDevice,
+ QWaylandXdgSurface::ResizeEdge edges)
+{
+ Q_UNUSED(inputDevice);
+ emit startResize(int(edges), true);
+}
+
+void Compositor::onSetTransient(QWaylandSurface *parent, const QPoint &relativeToParent, QWaylandWlShellSurface::FocusPolicy focusPolicy)
+{
+ Q_UNUSED(focusPolicy);
+ QWaylandWlShellSurface *wlShellSurface = qobject_cast<QWaylandWlShellSurface*>(sender());
+ View *view = findView(wlShellSurface->surface());
+
+ if (view) {
+ raise(view);
+ View *parentView = findView(parent);
+ if (parentView)
+ view->setPosition(parentView->position() + relativeToParent);
+ }
+}
+
+void Compositor::onSetPopup(QWaylandInputDevice *inputDevice, QWaylandSurface *parent, const QPoint &relativeToParent)
+{
+ Q_UNUSED(inputDevice);
+ QWaylandWlShellSurface *surface = qobject_cast<QWaylandWlShellSurface*>(sender());
+ View *view = findView(surface->surface());
+ m_popupViews << view;
+ if (view) {
+ raise(view);
+ View *parentView = findView(parent);
+ if (parentView)
+ view->setPosition(parentView->position() + relativeToParent);
+ }
+}
+
+void Compositor::onSubsurfaceChanged(QWaylandSurface *child, QWaylandSurface *parent)
+{
+ View *view = findView(child);
+ View *parentView = findView(parent);
+ view->setParentView(parentView);
+}
+
+void Compositor::onSubsurfacePositionChanged(const QPoint &position)
+{
+ QWaylandSurface *surface = qobject_cast<QWaylandSurface*>(sender());
+ if (!surface)
+ return;
+ View *view = findView(surface);
+ view->setPosition(position);
+ triggerRender();
+}
+
+void Compositor::triggerRender()
+{
+ m_window->requestUpdate();
+}
+
+void Compositor::startRender()
+{
+ QWaylandOutput *out = defaultOutput();
+ if (out)
+ out->frameStarted();
+}
+
+void Compositor::endRender()
+{
+ QWaylandOutput *out = defaultOutput();
+ if (out)
+ out->sendFrameCallbacks();
+}
+
+void Compositor::updateCursor()
+{
+ m_cursorView.advance();
+ QImage image = m_cursorView.currentBuffer().image();
+ if (!image.isNull())
+ m_window->setCursor(QCursor(QPixmap::fromImage(image), m_cursorHotspotX, m_cursorHotspotY));
+}
+
+void Compositor::adjustCursorSurface(QWaylandSurface *surface, int hotspotX, int hotspotY)
+{
+ if ((m_cursorView.surface() != surface)) {
+ if (m_cursorView.surface())
+ disconnect(m_cursorView.surface(), &QWaylandSurface::redraw, this, &Compositor::updateCursor);
+ if (surface)
+ connect(surface, &QWaylandSurface::redraw, this, &Compositor::updateCursor);
+ }
+
+ m_cursorView.setSurface(surface);
+ m_cursorHotspotX = hotspotX;
+ m_cursorHotspotY = hotspotY;
+
+ if (surface && surface->isMapped())
+ updateCursor();
+}
+
+void Compositor::closePopups()
+{
+ Q_FOREACH (View *view, m_popupViews) {
+ if (view->m_wlShellSurface)
+ view->m_wlShellSurface->sendPopupDone();
+ }
+ m_popupViews.clear();
+
+ m_xdgShell->closeAllPopups();
+}
+
+void Compositor::handleMouseEvent(QWaylandView *target, QMouseEvent *me)
+{
+ if (target && popupActive() && me->type() == QEvent::MouseButtonPress
+ && target->surface()->client() != m_popupViews.first()->surface()->client()) {
+ closePopups();
+ }
+ QWaylandInputDevice *input = defaultInputDevice();
+ QWaylandSurface *surface = target ? target->surface() : nullptr;
+ switch (me->type()) {
+ case QEvent::MouseButtonPress:
+ input->sendMousePressEvent(me->button());
+ if (surface != input->keyboardFocus()) {
+ if (surface == nullptr
+ || surface->role() == QWaylandWlShellSurface::role()
+ || surface->role() == QWaylandXdgSurface::role()
+ || surface->role() == QWaylandXdgPopup::role()) {
+ input->setKeyboardFocus(surface);
+ }
+ }
+ break;
+ case QEvent::MouseButtonRelease:
+ input->sendMouseReleaseEvent(me->button());
+ break;
+ case QEvent::MouseMove:
+ input->sendMouseMoveEvent(target, me->localPos(), me->globalPos());
+ default:
+ break;
+ }
+}
+
+void Compositor::handleResize(View *target, const QSize &initialSize, const QPoint &delta, int edge)
+{
+ QWaylandWlShellSurface *wlShellSurface = target->m_wlShellSurface;
+ if (wlShellSurface) {
+ QWaylandWlShellSurface::ResizeEdge edges = QWaylandWlShellSurface::ResizeEdge(edge);
+ QSize newSize = wlShellSurface->sizeForResize(initialSize, delta, edges);
+ wlShellSurface->sendConfigure(newSize, edges);
+ }
+
+ QWaylandXdgSurface *xdgSurface = target->m_xdgSurface;
+ if (xdgSurface) {
+ QWaylandXdgSurface::ResizeEdge edges = static_cast<QWaylandXdgSurface::ResizeEdge>(edge);
+ QSize newSize = xdgSurface->sizeForResize(initialSize, delta, edges);
+ xdgSurface->requestResizing(newSize);
+ }
+}
+
+void Compositor::startDrag()
+{
+ QWaylandDrag *currentDrag = defaultInputDevice()->drag();
+ Q_ASSERT(currentDrag);
+ View *iconView = findView(currentDrag->icon());
+ iconView->setPosition(m_window->mapFromGlobal(QCursor::pos()));
+
+ emit dragStarted(iconView);
+}
+
+void Compositor::handleDrag(View *target, QMouseEvent *me)
+{
+ QPointF pos = me->localPos();
+ QWaylandSurface *surface = 0;
+ if (target) {
+ pos -= target->position();
+ surface = target->surface();
+ }
+ QWaylandDrag *currentDrag = defaultInputDevice()->drag();
+ currentDrag->dragMove(surface, pos);
+ if (me->buttons() == Qt::NoButton)
+ currentDrag->drop();
+}
+
+// We only have a flat list of views, plus pointers from child to parent,
+// so maintaining a stacking order gets a bit complex. A better data
+// structure is left as an exercise for the reader.
+
+static int findEndOfChildTree(const QList<View*> &list, int index)
+{
+ int n = list.count();
+ View *parent = list.at(index);
+ while (index + 1 < n) {
+ if (list.at(index+1)->parentView() != parent)
+ break;
+ index = findEndOfChildTree(list, index + 1);
+ }
+ return index;
+}
+
+void Compositor::raise(View *view)
+{
+ int startPos = m_views.indexOf(view);
+ int endPos = findEndOfChildTree(m_views, startPos);
+
+ int n = m_views.count();
+ int tail = n - endPos - 1;
+
+ //bubble sort: move the child tree to the end of the list
+ for (int i = 0; i < tail; i++) {
+ int source = endPos + 1 + i;
+ int dest = startPos + i;
+ for (int j = source; j > dest; j--)
+ m_views.swap(j, j-1);
+ }
+}