diff options
-rw-r--r-- | app/CMakeLists.txt | 35 | ||||
-rw-r--r-- | app/main.cpp | 220 | ||||
-rw-r--r-- | app/protocol/agl-shell.xml | 117 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 1 |
4 files changed, 305 insertions, 68 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3911e6a..e99daa7 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -24,22 +24,51 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(OE_QMAKE_PATH_EXTERNAL_HOST_BINS $ENV{OE_QMAKE_PATH_HOST_BINS}) find_package(Qt5 COMPONENTS Core Gui QuickControls2 QuickWidgets REQUIRED) +find_package(Qt5Gui ${QT_MIN_VERSION} CONFIG REQUIRED Private) find_package(PkgConfig REQUIRED) +find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) qt5_add_resources(RESOURCES cluster-gauges.qrc images/images.qrc) PROJECT_TARGET_ADD(cluster-gauges) +add_custom_command( + OUTPUT agl-shell-client-protocol.h + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header + < ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell.xml + > ${CMAKE_SOURCE_DIR}/app/agl-shell-client-protocol.h + DEPENDS ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell.xml +) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/app/agl-shell-client-protocol.h + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header + < ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell.xml + > ${CMAKE_SOURCE_DIR}/app/agl-shell-client-protocol.h + DEPENDS ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell.xml +) + +add_custom_command( + OUTPUT agl-shell-protocol.c + COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code + < ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell.xml + > ${CMAKE_BINARY_DIR}/app/agl-shell-protocol.c + DEPENDS ${CMAKE_SOURCE_DIR}/app/protocol/agl-shell.xml +) + add_executable(${TARGET_NAME} main.cpp + agl-shell-protocol.c + agl-shell-client-protocol.h ${RESOURCES} ) -pkg_check_modules(QLIBWINMGR REQUIRED qlibwindowmanager) pkg_check_modules(QTAPPFW REQUIRED qtappfw-signal-composer) pkg_check_modules(GLIB REQUIRED glib-2.0) +pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) include_directories( + include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) ${QTAPPFW_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ) @@ -47,7 +76,7 @@ include_directories( set_target_properties(${TARGET_NAME} PROPERTIES LABELS "EXECUTABLE" PREFIX "" - COMPILE_FLAGS "${QLIBWINMGR_FLAGS} ${QTAPPFW_FLAGS} ${GLIB_FLAGS} ${EXTRAS_CFLAGS} -DFOR_AFB_BINDING" + COMPILE_FLAGS "${QTAPPFW_FLAGS} ${GLIB_FLAGS} ${EXTRAS_CFLAGS} -DFOR_AFB_BINDING" LINK_FLAGS "${BINDINGS_LINK_FLAG}" LINK_LIBRARIES "${EXTRAS_LIBRARIES}" OUTPUT_NAME "${TARGET_NAME}" @@ -56,7 +85,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES target_link_libraries(${TARGET_NAME} Qt5::QuickControls2 Qt5::QuickWidgets - ${QLIBWINMGR_LIBRARIES} ${QTAPPFW_LIBRARIES} ${GLIB_LDFLAGS} + ${WAYLAND_CLIENT_LIBRARIES} ) diff --git a/app/main.cpp b/app/main.cpp index a5b9ece..3c460d5 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -16,22 +16,85 @@ */ #include <QtCore/QDebug> +#include <QGuiApplication> #include <QtCore/QCommandLineParser> #include <QtCore/QUrlQuery> #include <QtGui/QGuiApplication> #include <QtQml/QQmlApplicationEngine> #include <QtQml/QQmlContext> +#include <QtQml/QQmlComponent> #include <QtQml/qqml.h> #include <QQuickWindow> #include <QtQuickControls2/QQuickStyle> +#include <qpa/qplatformnativeinterface.h> +#include <QTimer> #include <glib.h> +#include <QDebug> +#include <QScreen> -#include <qlibwindowmanager.h> #include <signalcomposer.h> +#include <wayland-client.h> +#include "agl-shell-client-protocol.h" // Global indicating whether canned animation should run bool runAnimation = true; +static void +global_add(void *data, struct wl_registry *reg, uint32_t name, + const char *interface, uint32_t version) +{ + struct agl_shell **shell = static_cast<struct agl_shell **>(data); + if (strcmp(interface, agl_shell_interface.name) == 0) { + *shell = static_cast<struct agl_shell *>(wl_registry_bind(reg, + name, &agl_shell_interface, version) + ); + } +} + +static void +global_remove(void *data, struct wl_registry *reg, uint32_t id) +{ + (void) data; + (void) reg; + (void) id; +} + +static const struct wl_registry_listener registry_listener = { + global_add, + global_remove, +}; + +static struct agl_shell * +register_agl_shell(QPlatformNativeInterface *native) +{ + struct wl_display *wl; + struct wl_registry *registry; + struct agl_shell *shell = nullptr; + + wl = static_cast<struct wl_display *>(native->nativeResourceForIntegration("display")); + registry = wl_display_get_registry(wl); + + wl_registry_add_listener(registry, ®istry_listener, &shell); + wl_display_roundtrip(wl); + wl_registry_destroy(registry); + + return shell; +} + +static struct wl_surface * +getWlSurface(QPlatformNativeInterface *native, QWindow *window) +{ + void *surf = native->nativeResourceForWindow("surface", window); + return static_cast<struct ::wl_surface *>(surf); +} + +static struct wl_output * +getWlOutput(QPlatformNativeInterface *native, QScreen *screen) +{ + void *output = native->nativeResourceForScreen("output", screen); + return static_cast<struct ::wl_output*>(output); +} + void read_config(void) { GKeyFile* conf_file; @@ -64,69 +127,98 @@ void read_config(void) } +static struct wl_surface * +create_component(QPlatformNativeInterface *native, QQmlComponent *comp, + QScreen *screen, QObject **qobj) +{ + QObject *obj = comp->create(); + //QObject *screen_obj = new QScreen(screen); + obj->setParent(screen); + + QWindow *win = qobject_cast<QWindow *>(obj); + *qobj = obj; + + return getWlSurface(native, win); +} + + int main(int argc, char *argv[]) { - // Slight hack, using the homescreen role greatly simplifies things wrt - // the windowmanager - QString myname = QString("homescreen"); - - QGuiApplication app(argc, argv); - - QCommandLineParser parser; - parser.addPositionalArgument("port", app.translate("main", "port for binding")); - parser.addPositionalArgument("secret", app.translate("main", "secret for binding")); - parser.addHelpOption(); - parser.addVersionOption(); - parser.process(app); - QStringList positionalArguments = parser.positionalArguments(); - - QQmlApplicationEngine engine; - - if (positionalArguments.length() == 2) { - int port = positionalArguments.takeFirst().toInt(); - QString secret = positionalArguments.takeFirst(); - QUrl bindingAddress; - bindingAddress.setScheme(QStringLiteral("ws")); - bindingAddress.setHost(QStringLiteral("localhost")); - bindingAddress.setPort(port); - bindingAddress.setPath(QStringLiteral("/api")); - QUrlQuery query; - query.addQueryItem(QStringLiteral("token"), secret); - bindingAddress.setQuery(query); - QQmlContext *context = engine.rootContext(); - context->setContextProperty(QStringLiteral("bindingAddress"), bindingAddress); - - std::string token = secret.toStdString(); - QLibWindowmanager* qwm = new QLibWindowmanager(); - - // WindowManager - if(qwm->init(port, secret) != 0){ - exit(EXIT_FAILURE); - } - - // Request a surface as described in layers.json windowmanager’s file - if (qwm->requestSurface(myname) != 0) { - exit(EXIT_FAILURE); - } - - // Create an event callback against an event type. Here a lambda is called when SyncDraw event occurs - qwm->set_event_handler(QLibWindowmanager::Event_SyncDraw, [qwm, myname](json_object*) { - fprintf(stderr, "Surface got syncDraw!\n"); - qwm->endDraw(myname); - }); - - context->setContextProperty("SignalComposer", new SignalComposer(bindingAddress, context)); - read_config(); - context->setContextProperty("runAnimation", runAnimation); - - engine.load(QUrl(QStringLiteral("qrc:/cluster-gauges.qml"))); - - // Find the instantiated model QObject and connect the signals/slots - QList<QObject *> mobjs = engine.rootObjects(); - - QQuickWindow *window = qobject_cast<QQuickWindow *>(mobjs.first()); - QObject::connect(window, SIGNAL(frameSwapped()), qwm, SLOT(slotActivateSurface())); - } - - return app.exec(); + QString myname = QString("cluster-gauges"); + struct agl_shell *agl_shell; + struct wl_output *output; + + QObject *qobj_bg; + QScreen *screen; + + QGuiApplication app(argc, argv); + app.setDesktopFileName(myname); + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + + agl_shell = register_agl_shell(native); + if (!agl_shell) { + exit(EXIT_FAILURE); + } + + std::shared_ptr<struct agl_shell> shell{agl_shell, agl_shell_destroy}; + + screen = qApp->primaryScreen(); + output = getWlOutput(native, screen); + + QCommandLineParser parser; + parser.addPositionalArgument("port", app.translate("main", "port for binding")); + parser.addPositionalArgument("secret", app.translate("main", "secret for binding")); + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + + QStringList positionalArguments = parser.positionalArguments(); + + QQmlApplicationEngine engine; + QQmlContext *context = engine.rootContext(); + + if (positionalArguments.length() == 2) { + int port = positionalArguments.takeFirst().toInt(); + QString secret = positionalArguments.takeFirst(); + + QUrl bindingAddress; + QUrlQuery query; + + struct wl_surface *bg; + + bindingAddress.setScheme(QStringLiteral("ws")); + bindingAddress.setHost(QStringLiteral("localhost")); + bindingAddress.setPort(port); + bindingAddress.setPath(QStringLiteral("/api")); + + query.addQueryItem(QStringLiteral("token"), secret); + bindingAddress.setQuery(query); + + read_config(); + + context->setContextProperty(QStringLiteral("bindingAddress"), + bindingAddress); + + context->setContextProperty("SignalComposer", + new SignalComposer(bindingAddress, + context)); + context->setContextProperty("runAnimation", runAnimation); + + QQmlComponent bg_comp(&engine, QUrl("qrc:/cluster-gauges.qml")); + qDebug() << bg_comp.errors(); + + bg = create_component(native, &bg_comp, screen, &qobj_bg); + + // set the surface as the background + agl_shell_set_background(agl_shell, bg, output); + + // instruct the compositor it can display after Qt has a chance + // to load everything + QTimer::singleShot(500, [agl_shell](){ + qDebug() << "agl_shell ready!"; + agl_shell_ready(agl_shell); + }); + } + + return app.exec(); } diff --git a/app/protocol/agl-shell.xml b/app/protocol/agl-shell.xml new file mode 100644 index 0000000..1096c64 --- /dev/null +++ b/app/protocol/agl-shell.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="agl_shell"> + <copyright> + Copyright © 2019 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + <interface name="agl_shell" version="1"> + <description summary="user interface for weston-ivi"> + </description> + + <enum name="error"> + <entry name="invalid_argument" value="0"/> + <entry name="background_exists" value="1"/> + <entry name="panel_exists" value="2"/> + </enum> + + <enum name="edge"> + <entry name="top" value="0"/> + <entry name="bottom" value="1"/> + <entry name="left" value="2"/> + <entry name="right" value="3"/> + </enum> + + <request name="ready"> + <description summary="client is ready to be shown"> + Tell the server that this client is ready to be shown. The server + will delay presentation during start-up until all shell clients are + ready to be shown, and will display a black screen instead. + This gives the client an oppurtunity to set up and configure several + surfaces into a coherent interface. + + The client that binds to this interface must send this request, otherwise + they may stall the compositor unnecessarily. + + If this request is called after the compositor has already finished + start-up, no operation is performed. + </description> + </request> + + <request name="set_background"> + <description summary="set surface as output's background"> + Set the surface to act as the background of an output. After this + request, the server will immediately send a configure event with + the dimensions the client should use to cover the entire output. + + The surface must have a "desktop" surface role, as supported by + libweston-desktop. + + Only a single surface may be the background for any output. If a + background surface already exists, a protocol error is raised. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + + <request name="set_panel"> + <description summary="set surface as panel"> + Set the surface to act as a panel of an output. The 'edge' argument + says what edge of the output the surface will be anchored to. + After this request, the server will send a configure event with the + correponding width/height that the client should use, and 0 for the + other dimension. E.g. if the edge is 'top', the width will be the + output's width, and the height will be 0. + + The surface must have a "desktop" surface role, as supported by + libweston-desktop. + + The compositor will take the panel's window geometry into account when + positioning other windows, so the panels are not covered. + + XXX: What happens if e.g. both top and left are used at the same time? + Who gets to have the corner? + + Only a single surface may be the panel for an output's edge. If a + surface already exists on an edge, a protocol error is raised. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output"/> + <arg name="edge" type="uint" enum="edge"/> + </request> + + <request name="activate_app"> + <description summary="make client current window"> + Ask the compositor to make a toplevel to become the current/focused + window for window management purposes. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + + If multiple toplevels have the same app_id, the result is unspecified. + + XXX: Do we need feedback to say it didn't work? (e.g. client does + not exist) + </description> + <arg name="app_id" type="string"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + </interface> +</protocol> diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in index 40796be..f8e02c8 100644 --- a/conf.d/wgt/config.xml.in +++ b/conf.d/wgt/config.xml.in @@ -7,7 +7,6 @@ <author>@PROJECT_AUTHOR@ <@PROJECT_AUTHOR_MAIL@></author> <license>@PROJECT_LICENSE@</license> <feature name="urn:AGL:widget:required-api"> - <param name="windowmanager" value="ws" /> <param name="signal-composer" value="ws" /> </feature> <feature name="urn:AGL:widget:required-permission"> |