From 5372ec5c131e23cccfbabc43d700ffbbbad973ec Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Wed, 7 Sep 2016 10:15:28 +0200 Subject: Make the custom extension example more interesting Define a protocol that does something visible. Change-Id: I29133eeffbb2c98a61ee59b241dcf6a6a0f543cb Reviewed-by: Johan Helsing --- .../client-common/customextension.cpp | 95 ++++++++++++++++-- .../client-common/customextension.h | 26 +++-- .../compositor/customextension.cpp | 55 +++++++++-- .../custom-extension/compositor/customextension.h | 24 +++-- .../wayland/custom-extension/compositor/main.cpp | 2 +- .../custom-extension/compositor/qml/Screen.qml | 77 +++++++++++++-- .../custom-extension/compositor/qml/main.qml | 106 ++++++++++++++++++--- .../custom-extension/cpp-client/cpp-client.pro | 2 +- .../wayland/custom-extension/cpp-client/main.cpp | 71 +++++++++++--- .../wayland/custom-extension/protocol/custom.xml | 75 ++++++++++++--- .../wayland/custom-extension/qml-client/main.qml | 32 +++++-- 11 files changed, 470 insertions(+), 95 deletions(-) diff --git a/examples/wayland/custom-extension/client-common/customextension.cpp b/examples/wayland/custom-extension/client-common/customextension.cpp index 050b0d5c2..c568d753d 100644 --- a/examples/wayland/custom-extension/client-common/customextension.cpp +++ b/examples/wayland/custom-extension/client-common/customextension.cpp @@ -41,30 +41,105 @@ #include "customextension.h" #include #include - +#include +#include +#include +#include #include QT_BEGIN_NAMESPACE CustomExtension::CustomExtension() : QWaylandClientExtensionTemplate(/* Supported protocol version */ 1 ) + , m_activated(false) +{ + connect(this, &CustomExtension::activeChanged, this, &CustomExtension::handleExtensionActive); +} + + +static inline struct ::wl_surface *getWlSurface(QWindow *window) +{ + void *surf = QGuiApplication::platformNativeInterface()->nativeResourceForWindow("surface", window); + return static_cast(surf); +} + +QWindow *CustomExtension::windowForSurface(struct ::wl_surface *surface) +{ + for (QWindow *w : qAsConst(m_windows)) { + if (getWlSurface(w) == surface) + return w; + } + return nullptr; +} + +bool CustomExtension::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::PlatformSurface + && static_cast(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { + QWindow *window = qobject_cast(object); + Q_ASSERT(window); + window->removeEventFilter(this); + QtWayland::qt_example_extension::register_surface(getWlSurface(window)); + } + return false; +} + +void CustomExtension::sendWindowRegistration(QWindow *window) { + if (window->handle()) + QtWayland::qt_example_extension::register_surface(getWlSurface(window)); + else + window->installEventFilter(this); // register when created } -void CustomExtension::sendRequest(const QString &text, int value) +void CustomExtension::registerWindow(QWindow *window) { - qDebug() << "Client-side plugin sending request:" << text << value; - qtrequest(text, value); + m_windows << window; + if (isActive()) + sendWindowRegistration(window); } -void CustomExtension::example_extension_qtevent(struct wl_surface *surface, - uint32_t time, - const QString &text, - uint32_t value) +void CustomExtension::sendBounce(QWindow *window, uint ms) { - qDebug() << "Client-side plugin received an event:" << surface << time << text << value; - emit eventReceived(text, value); + QtWayland::qt_example_extension::bounce(getWlSurface(window), ms); } +void CustomExtension::sendSpin(QWindow *window, uint ms) +{ + QtWayland::qt_example_extension::spin(getWlSurface(window), ms); +} + +void CustomExtension::handleExtensionActive() +{ + if (isActive() && !m_activated) { + for (QWindow *w : qAsConst(m_windows)) + sendWindowRegistration(w); + } +} + +void CustomExtension::example_extension_close(wl_surface *surface) +{ + QWindow *w = windowForSurface(surface); + if (w) + w->close(); +} + +void CustomExtension::example_extension_set_font_size(wl_surface *surface, uint32_t pixel_size) +{ + emit fontSize(windowForSurface(surface), pixel_size); +} + +void CustomExtension::example_extension_set_window_decoration(uint32_t state) +{ + bool shown = state; + for (QWindow *w : qAsConst(m_windows)) { + Qt::WindowFlags f = w->flags(); + if (shown) + f &= ~Qt::FramelessWindowHint; + else + f |= Qt::FramelessWindowHint; + w->setFlags(f); + } +} QT_END_NAMESPACE diff --git a/examples/wayland/custom-extension/client-common/customextension.h b/examples/wayland/custom-extension/client-common/customextension.h index a041d5a7e..3b64fc646 100644 --- a/examples/wayland/custom-extension/client-common/customextension.h +++ b/examples/wayland/custom-extension/client-common/customextension.h @@ -48,24 +48,38 @@ QT_BEGIN_NAMESPACE -class CustomExtension : public QWaylandClientExtensionTemplate, public QtWayland::qt_example_extension +class CustomExtension : public QWaylandClientExtensionTemplate + , public QtWayland::qt_example_extension { Q_OBJECT public: CustomExtension(); + Q_INVOKABLE void registerWindow(QWindow *window); public slots: - void sendRequest(const QString &text, int value); + void sendBounce(QWindow *window, uint ms); + void sendSpin(QWindow *window, uint ms); signals: void eventReceived(const QString &text, uint value); + void fontSize(QWindow *window, uint pixelSize); + void showDecorations(bool); + +private slots: + void handleExtensionActive(); private: - void example_extension_qtevent(struct wl_surface *surface, - uint32_t time, - const QString &text, - uint32_t value) Q_DECL_OVERRIDE; + void example_extension_close(wl_surface *surface) Q_DECL_OVERRIDE; + void example_extension_set_font_size(wl_surface *surface, uint32_t pixel_size) Q_DECL_OVERRIDE; + void example_extension_set_window_decoration(uint32_t state) Q_DECL_OVERRIDE; + + bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + + QWindow *windowForSurface(struct ::wl_surface *); + void sendWindowRegistration(QWindow *); + QList m_windows; + bool m_activated; }; QT_END_NAMESPACE diff --git a/examples/wayland/custom-extension/compositor/customextension.cpp b/examples/wayland/custom-extension/compositor/customextension.cpp index 6c1f05977..7a292a9e6 100644 --- a/examples/wayland/custom-extension/compositor/customextension.cpp +++ b/examples/wayland/custom-extension/compositor/customextension.cpp @@ -44,9 +44,8 @@ #include -namespace QtWayland { - -CustomExtension::CustomExtension() +CustomExtension::CustomExtension(QWaylandCompositor *compositor) + :QWaylandCompositorExtensionTemplate(compositor) { } @@ -57,22 +56,60 @@ void CustomExtension::initialize() init(compositor->display(), 1); } -void CustomExtension::sendEvent(QWaylandSurface *surface, uint time, const QString &text, uint value) +void CustomExtension::setFontSize(QWaylandSurface *surface, uint pixelSize) +{ + if (surface) { + Resource *target = resourceMap().value(surface->waylandClient()); + if (target) { + qDebug() << "Server-side extension sending setFontSize:" << pixelSize; + send_set_font_size(target->handle, surface->resource(), pixelSize); + } + } +} + +void CustomExtension::showDecorations(QWaylandClient *client, bool shown) +{ + if (client) { + Resource *target = resourceMap().value(client->client()); + if (target) { + qDebug() << "Server-side extension sending showDecorations:" << shown; + send_set_window_decoration(target->handle, shown); + } + } + +} + +void CustomExtension::close(QWaylandSurface *surface) { if (surface) { Resource *target = resourceMap().value(surface->waylandClient()); if (target) { - qDebug() << "Server-side extension sending an event:" << text << value; - send_qtevent(target->handle, surface->resource(), time, text, value); + qDebug() << "Server-side extension sending close for" << surface; + send_close(target->handle, surface->resource()); } } } -void CustomExtension::example_extension_qtrequest(QtWaylandServer::qt_example_extension::Resource *resource, const QString &text, int32_t value) +void CustomExtension::example_extension_bounce(QtWaylandServer::qt_example_extension::Resource *resource, wl_resource *wl_surface, uint32_t duration) { Q_UNUSED(resource); - qDebug() << "Server-side extension received a request:" << text << value; - emit requestReceived(text, value); + auto surface = QWaylandSurface::fromResource(wl_surface); + qDebug() << "server received bounce" << surface << duration; + emit bounce(surface, duration); } +void CustomExtension::example_extension_spin(QtWaylandServer::qt_example_extension::Resource *resource, wl_resource *wl_surface, uint32_t duration) +{ + Q_UNUSED(resource); + auto surface = QWaylandSurface::fromResource(wl_surface); + qDebug() << "server received spin" << surface << duration; + emit spin(surface, duration); +} + +void CustomExtension::example_extension_register_surface(QtWaylandServer::qt_example_extension::Resource *resource, wl_resource *wl_surface) +{ + Q_UNUSED(resource); + auto surface = QWaylandSurface::fromResource(wl_surface); + qDebug() << "server received new surface" << surface; + emit surfaceAdded(surface); } diff --git a/examples/wayland/custom-extension/compositor/customextension.h b/examples/wayland/custom-extension/compositor/customextension.h index 8419eeea3..b1b00408b 100644 --- a/examples/wayland/custom-extension/compositor/customextension.h +++ b/examples/wayland/custom-extension/compositor/customextension.h @@ -48,24 +48,30 @@ #include #include "qwayland-server-custom.h" -namespace QtWayland { - -class CustomExtension : public QWaylandCompositorExtensionTemplate, public QtWaylandServer::qt_example_extension +class CustomExtension : public QWaylandCompositorExtensionTemplate + , public QtWaylandServer::qt_example_extension { Q_OBJECT public: - CustomExtension(); + CustomExtension(QWaylandCompositor *compositor = 0); void initialize() Q_DECL_OVERRIDE; - Q_INVOKABLE void sendEvent(QWaylandSurface *surface, uint time, const QString &text, uint value); signals: - void requestReceived(const QString &text, uint value); + void surfaceAdded(QWaylandSurface *surface); + void bounce(QWaylandSurface *surface, uint ms); + void spin(QWaylandSurface *surface, uint ms); + +public slots: + void setFontSize(QWaylandSurface *surface, uint pixelSize); + void showDecorations(QWaylandClient *client, bool); + void close(QWaylandSurface *surface); + protected: - virtual void example_extension_qtrequest(Resource *resource, const QString &text, int32_t value) Q_DECL_OVERRIDE; + void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) Q_DECL_OVERRIDE; + void example_extension_spin(Resource *resource, wl_resource *surface, uint32_t duration) Q_DECL_OVERRIDE; + void example_extension_register_surface(Resource *resource, wl_resource *surface) Q_DECL_OVERRIDE; }; Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(CustomExtension) -} - #endif // CUSTOMEXTENSION_H diff --git a/examples/wayland/custom-extension/compositor/main.cpp b/examples/wayland/custom-extension/compositor/main.cpp index 867e4c0de..7ece7bd0e 100644 --- a/examples/wayland/custom-extension/compositor/main.cpp +++ b/examples/wayland/custom-extension/compositor/main.cpp @@ -50,7 +50,7 @@ static void registerTypes() { - qmlRegisterType("com.theqtcompany.customextension", 1, 0, "CustomExtension"); + qmlRegisterType("com.theqtcompany.customextension", 1, 0, "CustomExtension"); } int main(int argc, char *argv[]) diff --git a/examples/wayland/custom-extension/compositor/qml/Screen.qml b/examples/wayland/custom-extension/compositor/qml/Screen.qml index b6e4e12d0..a6d5fbc7c 100644 --- a/examples/wayland/custom-extension/compositor/qml/Screen.qml +++ b/examples/wayland/custom-extension/compositor/qml/Screen.qml @@ -50,13 +50,65 @@ WaylandOutput { property QtObject output - width: 1024 - height: 768 + width: 1600 + height: 900 visible: true + Rectangle { + id: sidebar + width: 150 + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + color: "lightgray" + Column { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 5 + + Repeater { + model: comp.itemList + Rectangle { + height: 36 + width: sidebar.width - 5 + color: "white" + radius: 5 + Text { + text: "window: " + modelData.shellSurface.title + "[" + modelData.shellSurface.className + + (modelData.isCustom ? "]\nfont size: " + modelData.fontSize :"]\n No extension") + color: modelData.isCustom ? "black" : "darkgray" + } + MouseArea { + enabled: modelData.isCustom + anchors.fill: parent + onWheel: { + if (wheel.angleDelta.y > 0) + modelData.fontSize++ + else if (wheel.angleDelta.y < 0 && modelData.fontSize > 3) + modelData.fontSize-- + } + onDoubleClicked: { + output.compositor.customExtension.close(modelData.surface) + } + } + } + } + Text { + visible: comp.itemList.length > 0 + width: sidebar.width - 5 + text: "Mouse wheel to change font size. Double click to close" + wrapMode: Text.Wrap + } + } + } + WaylandMouseTracker { id: mouseTracker - anchors.fill: parent + anchors.left: sidebar.right + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom windowSystemCursorEnabled: true Image { @@ -74,16 +126,25 @@ WaylandOutput { seat: output.compositor.defaultSeat } + Rectangle { - anchors.bottom: parent.bottom + anchors.top: parent.top anchors.right: parent.right - width: 75 - height: 75 - color: "#BADA55" + width: 100 + height: 100 + property bool on : true + color: on ? "#DEC0DE" : "#FACADE" + Text { + anchors.fill: parent + text: "Toggle window decorations" + wrapMode: Text.WordWrap + } + MouseArea { anchors.fill: parent onClicked: { - comp.sendEvent(); + parent.on = !parent.on + comp.setDecorations(parent.on); } } } diff --git a/examples/wayland/custom-extension/compositor/qml/main.qml b/examples/wayland/custom-extension/compositor/qml/main.qml index a7ca56e03..b5d90bd11 100644 --- a/examples/wayland/custom-extension/compositor/qml/main.qml +++ b/examples/wayland/custom-extension/compositor/qml/main.qml @@ -46,15 +46,14 @@ import com.theqtcompany.customextension 1.0 WaylandCompositor { id: comp - property var lastItem: null + property alias customExtension: custom + property var itemList: [] - property int counter : 0 - - function sendEvent() { - if (lastItem != null) { - console.log("Compositor sending event: " + counter); - custom.sendEvent(lastItem.shellSurface.surface, 0, "test", counter); - counter++; + function itemForSurface(surface) { + var n = itemList.length + for (var i = 0; i < n; i++) { + if (itemList[i].surface === surface) + return itemList[i] } } @@ -66,11 +65,67 @@ WaylandCompositor { id: chromeComponent ShellSurfaceItem { id: chrome + + property bool isCustom + property int fontSize: 12 + onSurfaceDestroyed: { - if (chrome === lastItem) - lastItem = null; + var index = itemList.indexOf(chrome); + if (index > -1) { + var listCopy = itemList + listCopy.splice(index, 1); + itemList = listCopy + } chrome.destroy() } + transform: [ + Rotation { + id: xRot + origin.x: chrome.width/2; origin.y: chrome.height/2; + angle: 0 + axis { x: 1; y: 0; z: 0 } + }, + Rotation { + id: yRot + origin.x: chrome.width/2; origin.y: chrome.height/2; + angle: 0 + axis { x: 0; y: 1; z: 0 } + } + ] + NumberAnimation { + id: spinAnimation + running: false + loops: 2 + target: yRot; + property: "angle"; + from: 0; to: 360; + duration: 400; + } + + function doSpin(ms) { + console.log("spin " + ms) + // using the 'ms' argument is left as an exercise for the reader... + spinAnimation.start() + } + + NumberAnimation { + id: bounceAnimation + running: false + target: chrome + property: "y" + from: 0 + to: output.window.height - chrome.height + easing.type: Easing.OutBounce + duration: 1000 + } + function doBounce(ms) { + console.log("bounce " + ms) + // using the 'ms' argument is left as an exercise for the reader... + bounceAnimation.start() + } + onFontSizeChanged: { + custom.setFontSize(surface, fontSize) + } } } @@ -78,14 +133,39 @@ WaylandCompositor { id: defaultShell onWlShellSurfaceCreated: { var item = chromeComponent.createObject(defaultOutput.surfaceArea, { "shellSurface": shellSurface } ); - lastItem = item; + var w = defaultOutput.surfaceArea.width/2 + var h = defaultOutput.surfaceArea.height/2 + item.x = Math.random()*w + item.y = Math.random()*h + var listCopy = itemList // List properties cannot be modified through Javascript operations + listCopy.push(item) + itemList = listCopy } } CustomExtension { id: custom - onRequestReceived: { - console.log("Compositor received a request: \"" + text + "\", " + value) + + onSurfaceAdded: { + var item = itemForSurface(surface) + item.isCustom = true + } + onBounce: { + var item = itemForSurface(surface) + item.doBounce(ms) + } + onSpin: { + var item = itemForSurface(surface) + item.doSpin(ms) + } + } + + function setDecorations(shown) { + var n = itemList.length + for (var i = 0; i < n; i++) { + // TODO: we only need to do it once for each client + if (itemList[i].isCustom) + custom.showDecorations(itemList[i].surface.client, shown) } } } diff --git a/examples/wayland/custom-extension/cpp-client/cpp-client.pro b/examples/wayland/custom-extension/cpp-client/cpp-client.pro index 9636d1101..54fead078 100644 --- a/examples/wayland/custom-extension/cpp-client/cpp-client.pro +++ b/examples/wayland/custom-extension/cpp-client/cpp-client.pro @@ -1,4 +1,4 @@ -QT += waylandclient-private +QT += waylandclient-private gui-private CONFIG += c++11 CONFIG += wayland-scanner diff --git a/examples/wayland/custom-extension/cpp-client/main.cpp b/examples/wayland/custom-extension/cpp-client/main.cpp index f5894dc81..af8de53b5 100644 --- a/examples/wayland/custom-extension/cpp-client/main.cpp +++ b/examples/wayland/custom-extension/cpp-client/main.cpp @@ -42,9 +42,13 @@ #include #include #include +#include + #include "../client-common/customextension.h" #include +#include +#include class TestWindow : public QRasterWindow { @@ -53,39 +57,78 @@ class TestWindow : public QRasterWindow public: TestWindow(CustomExtension *customExtension) : m_extension(customExtension) + , rect1(50, 50, 100, 100) + , rect2(50, 200, 100, 100) + , rect3(50, 350, 100, 100) { - connect(customExtension, SIGNAL(eventReceived(const QString &, uint)), - this, SLOT(handleEvent(const QString &, uint))); + m_extension->registerWindow(this); + connect(m_extension, &CustomExtension::fontSize, this, &TestWindow::handleSetFontSize); } public slots: - void handleEvent(const QString &text, uint value) + void doSpin() + { + if (!m_extension->isActive()) { + qWarning() << "Extension is not active"; + return; + } + qDebug() << "sending spin..."; + m_extension->sendSpin(this, 500); + } + void doBounce() + { + if (!m_extension->isActive()) { + qWarning() << "Extension is not active"; + return; + } + qDebug() << "sending bounce..."; + m_extension->sendBounce(this, 500); + } + + void newWindow() { - qDebug() << "Client application received event" << text << value; + auto w = new TestWindow(m_extension); + w->show(); + } + + void handleSetFontSize(QWindow *w, uint pixelSize) + { + if (w == this) { + m_font.setPixelSize(pixelSize); + update(); + } } protected: - void paintEvent(QPaintEvent *) + void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE { QPainter p(this); + p.setFont(m_font); p.fillRect(QRect(0,0,width(),height()),Qt::gray); - p.fillRect(50,50,100,100, QColor("#C0FFEE")); + p.fillRect(rect1, QColor("#C0FFEE")); + p.drawText(rect1, Qt::TextWordWrap, "Press here to send spin request."); + p.fillRect(rect2, QColor("#decaff")); + p.drawText(rect2, Qt::TextWordWrap, "Press here to send bounce request."); + p.fillRect(rect3, QColor("#7EA")); + p.drawText(rect3, Qt::TextWordWrap, "Create new window."); } void mousePressEvent(QMouseEvent *ev) Q_DECL_OVERRIDE { - bool insideRect = QRect(50,50,100,100).contains(ev->pos()); - - QString text = insideRect ? "Click inside" : "Click outside"; - int value = ev->pos().x(); - - qDebug() << "Client application sending request:" << text << value; - - m_extension->sendRequest(text, value); + if (rect1.contains(ev->pos())) + doSpin(); + else if (rect2.contains(ev->pos())) + doBounce(); + else if (rect3.contains(ev->pos())) + newWindow(); } private: CustomExtension *m_extension; + QRect rect1; + QRect rect2; + QRect rect3; + QFont m_font; }; int main (int argc, char **argv) diff --git a/examples/wayland/custom-extension/protocol/custom.xml b/examples/wayland/custom-extension/protocol/custom.xml index bda678299..9bbe50d6e 100644 --- a/examples/wayland/custom-extension/protocol/custom.xml +++ b/examples/wayland/custom-extension/protocol/custom.xml @@ -39,24 +39,69 @@ - - - Example event from server to client - - - - - - + + This example shows how to add extra functionality to the Wayland + through an extension. + + + + + Inform the compositor that the client has a new surface that is + covered by the extension. + + + + + + + + The compositor should perform a move animation on the surface. + + + + + + + + The compositor should perform a rotating animation on the surface. + + + + + + + + Ask the client to close the window for the surface. + + + - - - Example request from client to server + + + Ask the client to change the font size on the surface. + + + + + + + + Ask the client to turn window decoration on/off on all surfaces. + + + + + Describes whether window decorations should be shown. + + + + + + - - - + + diff --git a/examples/wayland/custom-extension/qml-client/main.qml b/examples/wayland/custom-extension/qml-client/main.qml index c6496b0bf..41fc02a1d 100644 --- a/examples/wayland/custom-extension/qml-client/main.qml +++ b/examples/wayland/custom-extension/qml-client/main.qml @@ -43,18 +43,23 @@ import QtQuick.Window 2.0 import com.theqtcompany.customextension 1.0 Window { + id: topLevelWindow visible: true Rectangle { anchors.fill: parent - color: "#297A4A" + color: "#f1eece" + } + property alias textItem: bounceText + Text { + id: bounceText + text: "press here to bounce" } MouseArea { anchors.fill: parent onClicked: { - console.log("Clicked outside", mouseX) if (customExtension.active) - customExtension.sendRequest("Clicked outside", mouseX) + customExtension.sendBounce(topLevelWindow, 1000) } } @@ -62,22 +67,31 @@ Window { anchors.centerIn: parent width: 100; height: 100 onClicked: { - var obj = mapToItem(parent, mouse.x, mouse.y) - console.log("Clicked inside", obj.x) if (customExtension.active) - customExtension.sendRequest("Clicked inside", obj.x) + customExtension.sendSpin(topLevelWindow, 500) } Rectangle { anchors.fill: parent - color: "#34FD85" + color: "#fab1ed" + Text { + text: "spin" + } } } CustomExtension { id: customExtension - onActiveChanged: console.log("Custom extension is active:", active) - onEventReceived: console.log("Event received", text, value) + onActiveChanged: { + console.log("Custom extension is active:", active) + registerWindow(topLevelWindow) + } + onFontSize: { + // signal arguments: window and pixelSize + // we are free to interpret the protocol as we want, so + // let's change the font size of just one of the text items + window.textItem.font.pixelSize = pixelSize + } } } -- cgit v1.2.3