← Back to team overview

ubuntu-sdk-team team mailing list archive

[Merge] lp:~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus into lp:ubuntu-ui-toolkit/staging

 

Arthur Mello has proposed merging lp:~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus into lp:ubuntu-ui-toolkit/staging.

Commit message:
Add support for interacting with Content Hub Clipboard UI via DBus calls

Requested reviews:
  Ubuntu SDK team (ubuntu-sdk-team)

For more details, see:
https://code.launchpad.net/~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus/+merge/314072

Add support for interacting with Content Hub Clipboard UI via DBus calls
-- 
Your team Ubuntu SDK team is requested to review the proposed merge of lp:~artmello/ubuntu-ui-toolkit/ubuntu-ui-toolkit-clipboard-dbus into lp:ubuntu-ui-toolkit/staging.
=== modified file 'src/UbuntuToolkit/UbuntuToolkit.pro'
--- src/UbuntuToolkit/UbuntuToolkit.pro	2016-11-04 09:37:32 +0000
+++ src/UbuntuToolkit/UbuntuToolkit.pro	2017-01-04 13:10:50 +0000
@@ -52,6 +52,7 @@
     $$PWD/privates/listviewextensions_p.h \
     $$PWD/privates/splitviewhandler_p.h \
     $$PWD/privates/threelabelsslot_p.h \
+    $$PWD/privates/uccontenthub_p.h \
     $$PWD/privates/ucpagewrapper_p.h \
     $$PWD/privates/ucpagewrapper_p_p.h \
     $$PWD/privates/ucpagewrapperincubator_p.h \
@@ -163,6 +164,7 @@
     $$PWD/privates/listviewextensions.cpp \
     $$PWD/privates/splitviewhandler.cpp \
     $$PWD/privates/threelabelsslot_p.cpp \
+    $$PWD/privates/uccontenthub.cpp \
     $$PWD/privates/ucpagewrapper.cpp \
     $$PWD/privates/ucpagewrapperincubator.cpp \
     $$PWD/privates/ucscrollbarutils.cpp \

=== added file 'src/UbuntuToolkit/privates/uccontenthub.cpp'
--- src/UbuntuToolkit/privates/uccontenthub.cpp	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/privates/uccontenthub.cpp	2017-01-04 13:10:50 +0000
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Arthur Mello <arthur.mello@xxxxxxxxxxxxx>
+ */
+
+#include "privates/uccontenthub_p.h"
+
+#include <QtCore/QLoggingCategory>
+#include <QtDBus/QDBusInterface>
+
+Q_LOGGING_CATEGORY(ucContentHub, "ubuntu.components.PrivateContentHub", QtMsgType::QtWarningMsg)
+
+#define CONTHUB_TRACE(params) qCDebug(ucContentHub) << params
+
+static const QString contentHubService = QStringLiteral("com.ubuntu.content.dbus.Service");
+static const QString contentHubObjectPath = QStringLiteral("/");
+static const QString contentHubInterface = QStringLiteral("com.ubuntu.content.dbus.Service");
+
+static const QString dbusService = QStringLiteral("org.freedesktop.DBus");
+static const QString dbusObjectPath = QStringLiteral("/org/freedesktop/DBus");
+static const QString dbusInterface = QStringLiteral("org.freedesktop.DBus");
+
+UT_NAMESPACE_BEGIN
+
+UCContentHub::UCContentHub(QObject *parent)
+    : QObject(parent),
+      m_dbusIface(0),
+      m_contentHubIface(0),
+      m_canPaste(false)
+{
+    m_dbusIface = new QDBusInterface(dbusService,
+                                     dbusObjectPath,
+                                     dbusInterface,
+                                     QDBusConnection::sessionBus(),
+                                     this);
+
+    m_contentHubIface = new QDBusInterface(contentHubService,
+                                           contentHubObjectPath,
+                                           contentHubInterface,
+                                           QDBusConnection::sessionBus(),
+                                           this);
+    if (m_contentHubIface->isValid()) {
+        m_contentHubIface->connection().connect(
+            contentHubService,
+            contentHubObjectPath,
+            contentHubInterface,
+            QStringLiteral("PasteSelected"),
+            this,
+            SLOT(onPasteSelected(QString, QByteArray))
+        );
+
+        m_contentHubIface->connection().connect(
+            contentHubService,
+            contentHubObjectPath,
+            contentHubInterface,
+            QStringLiteral("PasteboardChanged"),
+            this,
+            SLOT(onPasteboardChanged())
+        );
+
+        m_canPaste = checkPasteFormats();
+    }
+}
+
+UCContentHub::~UCContentHub()
+{
+    if (m_dbusIface) {
+        delete m_dbusIface;
+    }
+
+    if (m_contentHubIface) {
+        delete m_contentHubIface;
+    }
+}
+
+void UCContentHub::requestPaste()
+{
+    if (!m_contentHubIface->isValid()) {
+        CONTHUB_TRACE("Invalid Content Hub DBusInterface");
+        return;
+    }
+
+    QString appProfile = getAppProfile();
+    qDebug() << "[UITK ContentHub] AppArmor profile:" << appProfile;
+
+    m_contentHubIface->call(QStringLiteral("RequestPasteByAppId"), appProfile);
+}
+
+bool UCContentHub::canPaste()
+{
+    return m_canPaste;
+}
+
+void UCContentHub::onPasteSelected(QString appId, QByteArray mimedata)
+{
+    if (getAppProfile() != appId) {
+        return;
+    }
+
+    if (mimedata.isNull()) {
+        CONTHUB_TRACE("onPasteSelected: Invalid MimeData received");
+        return;
+    }
+
+    QMimeData* deserialized = deserializeMimeData(mimedata);
+    Q_EMIT pasteSelected(deserialized->text());
+}
+
+void UCContentHub::onPasteboardChanged()
+{
+    if (checkPasteFormats() != m_canPaste) {
+        m_canPaste = !m_canPaste;
+        Q_EMIT canPasteChanged();
+    }
+}
+
+QString UCContentHub::getAppProfile()
+{
+    if (!m_dbusIface->isValid()) {
+        CONTHUB_TRACE("Invalid DBus DBusInterface");
+        return QString();
+    }
+
+    QDBusReply<QString> reply = m_dbusIface->call("GetConnectionAppArmorSecurityContext", QDBusConnection::sessionBus().baseService());
+    if (reply.isValid()) {
+        return reply.value();
+    }
+
+    return QString();
+}
+
+QMimeData* UCContentHub::deserializeMimeData(const QByteArray &serializedMimeData)
+{
+    int maxFormatsCount = 16;
+
+    if (static_cast<std::size_t>(serializedMimeData.size()) < sizeof(int)) {
+        // Data is invalid
+        return nullptr;
+    }
+
+    QMimeData *mimeData = new QMimeData;
+
+    const char* const buffer = serializedMimeData.constData();
+    const int* const header = reinterpret_cast<const int*>(serializedMimeData.constData());
+
+    const int count = qMin(header[0], maxFormatsCount);
+
+    for (int i = 0; i < count; i++) {
+        const int formatOffset = header[i*4+1];
+        const int formatSize = header[i*4+2];
+        const int dataOffset = header[i*4+3];
+        const int dataSize = header[i*4+4];
+
+        if (formatOffset + formatSize <= serializedMimeData.size()
+            && dataOffset + dataSize <= serializedMimeData.size()) {
+
+            QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize);
+            QByteArray mimeDataBytes(&buffer[dataOffset], dataSize);
+
+            mimeData->setData(mimeType, mimeDataBytes);
+        }
+    }
+
+    return mimeData;
+}
+
+bool UCContentHub::checkPasteFormats()
+{
+    if (!m_contentHubIface->isValid()) {
+        CONTHUB_TRACE("Invalid Content Hub DBusInterface");
+        return false;
+    }
+
+    QDBusReply<QStringList> reply = m_contentHubIface->call(QStringLiteral("PasteFormats"));
+    if (reply.isValid()) {
+        // TODO: In  the future check if Paste Formats list has an interesting format
+        return !reply.value().isEmpty();
+    } else {
+        CONTHUB_TRACE("Invalid return from DBus call PasteFormats");
+    }
+
+    return false;
+}
+
+UT_NAMESPACE_END

=== added file 'src/UbuntuToolkit/privates/uccontenthub_p.h'
--- src/UbuntuToolkit/privates/uccontenthub_p.h	1970-01-01 00:00:00 +0000
+++ src/UbuntuToolkit/privates/uccontenthub_p.h	2017-01-04 13:10:50 +0000
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Arthur Mello <arthur.mello@xxxxxxxxxxxxx>
+ */
+
+#ifndef UCCONTENTHUB_P_H
+#define UCCONTENTHUB_P_H
+
+#include <QtCore/QObject>
+#include <QtCore/QMimeData>
+#include <QtDBus/QDBusInterface>
+#include <QtDBus/QDBusConnection>
+
+#include <UbuntuToolkit/ubuntutoolkitglobal.h>
+
+UT_NAMESPACE_BEGIN
+
+class UBUNTUTOOLKIT_EXPORT UCContentHub : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged)
+
+public:
+    UCContentHub(QObject* parent = 0);
+    ~UCContentHub();
+
+    Q_INVOKABLE void requestPaste();
+
+    bool canPaste();
+
+Q_SIGNALS:
+    void pasteSelected(const QString &data);
+    void canPasteChanged();
+
+private Q_SLOTS:
+    void onPasteSelected(QString appId, QByteArray mimedata);
+    void onPasteboardChanged();
+
+private:
+    QString getAppProfile();
+    QMimeData* deserializeMimeData(const QByteArray &serializedMimeData);
+    bool checkPasteFormats();
+
+    QDBusInterface *m_dbusIface;
+    QDBusInterface *m_contentHubIface;
+
+    bool m_canPaste;
+};
+
+UT_NAMESPACE_END
+
+#endif  // UCCONTENTHUB_P_H

=== modified file 'src/UbuntuToolkit/ubuntutoolkitmodule.cpp'
--- src/UbuntuToolkit/ubuntutoolkitmodule.cpp	2016-09-29 10:19:06 +0000
+++ src/UbuntuToolkit/ubuntutoolkitmodule.cpp	2017-01-04 13:10:50 +0000
@@ -44,6 +44,7 @@
 #include "menugroup_p.h"
 #include "privates/appheaderbase_p.h"
 #include "privates/frame_p.h"
+#include "privates/uccontenthub_p.h"
 #include "privates/ucpagewrapper_p.h"
 #include "privates/ucscrollbarutils_p.h"
 #include "qquickclipboard_p.h"
@@ -262,6 +263,8 @@
     qmlRegisterType<UCAppHeaderBase>(privateUri, 1, 3, "AppHeaderBase");
     qmlRegisterType<Tree>(privateUri, 1, 3, "Tree");
 
+    qmlRegisterSimpleSingletonType<UCContentHub>(privateUri, 1, 3, "UCContentHub");
+
     //FIXME: move to a more generic location, i.e StyledItem or QuickUtils
     qmlRegisterSimpleSingletonType<UCScrollbarUtils>(privateUri, 1, 3, "PrivateScrollbarUtils");
 

=== modified file 'src/imports/Components/1.3/TextArea.qml'
--- src/imports/Components/1.3/TextArea.qml	2016-11-10 15:36:49 +0000
+++ src/imports/Components/1.3/TextArea.qml	2017-01-04 13:10:50 +0000
@@ -17,6 +17,7 @@
 import QtQuick 2.4
 import Ubuntu.Components 1.3 as Ubuntu
 import Ubuntu.Components.Popups 1.3
+import Ubuntu.Components.Private 1.3 as Private
 
 /*!
     \qmltype TextArea
@@ -736,6 +737,15 @@
         editor.linkActivated.connect(control.linkActivated);
     }
 
+    Connections {
+        target: Private.UCContentHub
+        onPasteSelected: {
+            if (control.activeFocus) {
+                control.paste(data)
+            }
+        }
+    }
+
     // activation area on mouse click
     // the editor activates automatically when pressed in the editor control,
     // however that one can be slightly spaced to the main control area
@@ -806,6 +816,11 @@
                 control.paste("\n");
             }
             event.accepted = true;
+        } else if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)){
+            if (Private.UCContentHub.canPaste) {
+                Private.UCContentHub.requestPaste();
+                event.accepted = true;
+            }
         } else {
             event.accepted = false;
         }

=== modified file 'src/imports/Components/1.3/TextField.qml'
--- src/imports/Components/1.3/TextField.qml	2016-09-20 07:23:24 +0000
+++ src/imports/Components/1.3/TextField.qml	2017-01-04 13:10:50 +0000
@@ -17,6 +17,7 @@
 import QtQuick 2.4
 import Ubuntu.Components 1.3 as Ubuntu
 import Ubuntu.Components.Popups 1.3
+import Ubuntu.Components.Private 1.3 as Private
 
 /*!
     \qmltype TextField
@@ -637,7 +638,6 @@
         control.triggered(control.text)
     }
 
-
     /*!
       Copies the currently selected text to the system clipboard.
     */
@@ -834,6 +834,28 @@
 
     // internals
 
+    Connections {
+        target: Private.UCContentHub
+        onPasteSelected: {
+            if (control.activeFocus) {
+                control.paste(data)
+            }
+        }
+    }
+
+    Keys.onPressed: {
+        if (readOnly)
+            return;
+        if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)){
+            if (Private.UCContentHub.canPaste) {
+                Private.UCContentHub.requestPaste();
+                event.accepted = true;
+            }
+        } else {
+            event.accepted = false;
+        }
+    }
+
     // Overload focus mechanics to avoid bubbling up of focus from children
     activeFocusOnPress: true
 

=== modified file 'src/imports/Components/1.3/TextInputPopover.qml'
--- src/imports/Components/1.3/TextInputPopover.qml	2015-12-14 15:15:46 +0000
+++ src/imports/Components/1.3/TextInputPopover.qml	2017-01-04 13:10:50 +0000
@@ -17,6 +17,7 @@
 import QtQuick 2.4
 import Ubuntu.Components 1.3
 import Ubuntu.Components.Popups 1.3
+import Ubuntu.Components.Private 1.3 as Private
 
 Popover {
     id: popover
@@ -54,12 +55,12 @@
             }
         },
         Action {
-            text: i18n.dtr('ubuntu-ui-toolkit', "Paste")
+            text: i18n.dtr('ubuntu-ui-toolkit', "Paste...")
             iconName: "edit-paste"
-            enabled: target && target.canPaste
+            enabled: target && Private.UCContentHub.canPaste
             onTriggered: {
                 PopupUtils.close(popover);
-                target.paste();
+                Private.UCContentHub.requestPaste();
             }
         }
     ]


Follow ups