Merge "Add icon re-ordering support by hold and move" 3.99.1 dab/3.99.1 dab_3.99.1
authorJan-Simon Moeller <jsmoeller@linuxfoundation.org>
Wed, 24 May 2017 08:50:33 +0000 (08:50 +0000)
committerGerrit Code Review <gerrit@automotivelinux.org>
Wed, 24 May 2017 08:50:33 +0000 (08:50 +0000)
homescreen/homescreen.pro
homescreen/qml/MediaAreaBlank.qml
homescreen/src/main.cpp
homescreen/src/mastervolume.cpp [new file with mode: 0644]
homescreen/src/mastervolume.h [new file with mode: 0644]
homescreen/src/paclient.cpp [new file with mode: 0644]
homescreen/src/paclient.h [new file with mode: 0644]

index 1ca59b6..061d2fb 100644 (file)
@@ -15,7 +15,8 @@
 TEMPLATE = app
 TARGET = HomeScreen
 QT = qml quick dbus
-CONFIG += c++11
+CONFIG += c++11 link_pkgconfig
+PKGCONFIG += libpulse
 
 include(../interfaces/interfaces.pri)
 
@@ -27,7 +28,9 @@ SOURCES += \
     src/appinfo.cpp \
     src/statusbarmodel.cpp \
     src/statusbarserver.cpp \
-    src/applicationlauncher.cpp
+    src/applicationlauncher.cpp \
+    src/mastervolume.cpp \
+    src/paclient.cpp
 
 HEADERS  += \
     src/homescreencontrolinterface.h \
@@ -36,7 +39,9 @@ HEADERS  += \
     src/statusbarserver.h \
     src/applicationlauncher.h \
     src/applicationmodel.h \
-    src/appinfo.h
+    src/appinfo.h \
+    src/mastervolume.h \
+    src/paclient.h
 
 OTHER_FILES += \
     README.md
index 0dde451..51fa657 100644 (file)
  */
 
 import QtQuick 2.2
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.0
+import AGL.Demo.Controls 1.0
+import MasterVolume 1.0
 
 Image {
     width: 1080
     height: 215
     source: './images/Utility_Logo_Background-01.png'
+    property bool displayVolume: false;
+
+    MouseArea {
+        anchors.fill: parent
+        function enableVolumeDisplay() {
+            if (!displayVolume) {
+                displayVolume = true
+                master_volume.visible = true
+                volume_timer.restart()
+            }
+        }
+        onClicked: enableVolumeDisplay()
+    }
 
     Image {
+    id: logo_image
         anchors.centerIn: parent
         source: './images/Utility_Logo_Colour-01.png'
     }
+
+    Timer {
+        id: volume_timer
+        interval: 5000; running: false; repeat: false
+        onTriggered: displayVolume = false
+    }
+
+    states: [
+    State { when: displayVolume;
+    PropertyChanges { target: master_volume; opacity: 1.0 }
+    PropertyChanges { target: slider; enabled: true }
+    PropertyChanges { target: logo_image; opacity: 0.0 }
+    },
+    State { when: !displayVolume;
+    PropertyChanges { target: master_volume; opacity: 0.0 }
+    PropertyChanges { target: slider; enabled: false }
+    PropertyChanges { target: logo_image; opacity: 1.0 }
+    }
+    ]
+
+    transitions: Transition {
+    NumberAnimation { property: "opacity"; duration: 500}
+    }
+
+    MasterVolume {
+        id: mv
+        objectName: "mv"
+        onVolumeChanged: slider.value = volume
+    }
+
+    Item {
+        id: master_volume
+        anchors.fill: parent
+        anchors.centerIn: parent
+        visible: false
+
+        Label {
+            font.pixelSize: 36
+            anchors.horizontalCenter: parent.horizontalCenter
+            color: "white"
+            text: "Master Volume"
+        }
+
+        RowLayout {
+            anchors.fill: parent
+            anchors.centerIn: parent
+            anchors.margins: 20
+            spacing: 20
+            Label {
+                font.pixelSize: 36
+                color: "white"
+                text: "0 %"
+            }
+            Slider {
+                id: slider
+                Layout.fillWidth: true
+                from: 0
+                to: 65536
+                stepSize: 256
+                snapMode: Slider.SnapOnRelease
+                onValueChanged: mv.volume = value
+                Component.onCompleted: value = mv.volume
+                onPressedChanged: {
+                    if (pressed) {volume_timer.stop()}
+                    else {volume_timer.restart()}
+                }
+                background: Rectangle {
+                    id: slider_bg
+                    height: 16
+                    color: "#59FF7F"
+                }
+                handle: Rectangle {
+                    anchors.verticalCenter: slider_bg.verticalCenter
+                    width: 48
+                    height: 48
+                    radius: 24
+                    x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
+                    y: slider.topPadding + slider.availableHeight / 2 - height / 2
+                    color: "white"
+                }
+            }
+            Label {
+                font.pixelSize: 36
+                color: "white"
+                text: "100 %"
+            }
+        }
+    }
 }
index 28a8d38..215e7c6 100644 (file)
@@ -28,6 +28,8 @@
 #include "applicationmodel.h"
 #include "appinfo.h"
 #include "afm_user_daemon_proxy.h"
+#include "mastervolume.h"
+#include "paclient.h"
 
 // XXX: We want this DBus connection to be shared across the different
 // QML objects, is there another way to do this, a nice way, perhaps?
@@ -77,18 +79,26 @@ int main(int argc, char *argv[])
         qInstallMessageHandler(noOutput);
     }
 
+    // Fire up PA client QThread
+    QThread* pat = new QThread;
+    PaClient* client = new PaClient();
+    client->moveToThread(pat);
+    pat->start();
+
     qDBusRegisterMetaType<AppInfo>();
     qDBusRegisterMetaType<QList<AppInfo> >();
 
     qmlRegisterType<ApplicationLauncher>("HomeScreen", 1, 0, "ApplicationLauncher");
     qmlRegisterType<ApplicationModel>("Home", 1, 0, "ApplicationModel");
     qmlRegisterType<StatusBarModel>("HomeScreen", 1, 0, "StatusBarModel");
+    qmlRegisterType<MasterVolume>("MasterVolume", 1, 0, "MasterVolume");
 
     QQmlApplicationEngine engine;
 
     LayoutHandler* layoutHandler = new LayoutHandler();
 
     HomeScreenControlInterface* hsci = new HomeScreenControlInterface();
+
     QObject::connect(hsci, SIGNAL(newRequestGetSurfaceStatus(int)), layoutHandler, SLOT(requestGetSurfaceStatus(int)));
     QObject::connect(hsci, SIGNAL(newRequestsToBeVisibleApp(int)), layoutHandler, SLOT(makeMeVisible(int)));
     QObject::connect(hsci, SIGNAL(newRequestRenderSurfaceToArea(int, int)), layoutHandler, SLOT(requestRenderSurfaceToArea(int,int)));
@@ -99,5 +109,14 @@ int main(int argc, char *argv[])
 
     engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
+    QList<QObject *> mobjs = engine.rootObjects();
+    MasterVolume *mv = mobjs.first()->findChild<MasterVolume *>("mv");
+    engine.rootContext()->setContextProperty("MasterVolume", mv);
+    QObject::connect(mv, SIGNAL(sliderVolumeChanged(int)), client, SLOT(incDecVolume(int)));
+    QObject::connect(client, SIGNAL(volumeExternallyChanged(int)), mv, SLOT(changeExternalVolume(int)));
+
+    // Initalize PA client
+    client->init();
+
     return a.exec();
 }
diff --git a/homescreen/src/mastervolume.cpp b/homescreen/src/mastervolume.cpp
new file mode 100644 (file)
index 0000000..46985ee
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mastervolume.h"
+
+#include <QtCore/QDebug>
+
+void MasterVolume::setVolume(pa_volume_t volume)
+{
+       int volume_delta = volume - m_volume;
+       m_volume = volume;
+       emit sliderVolumeChanged(volume_delta);
+
+}
+
+void MasterVolume::changeExternalVolume(int volume)
+{
+       m_volume = volume;
+       emit volumeChanged();
+}
diff --git a/homescreen/src/mastervolume.h b/homescreen/src/mastervolume.h
new file mode 100644 (file)
index 0000000..edcdd8c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QtCore/QObject>
+#include <QQmlEngine>
+#include <QDebug>
+
+#include <pulse/pulseaudio.h>
+
+class MasterVolume : public QObject
+{
+       Q_OBJECT
+               Q_PROPERTY (uint32_t volume READ getVolume WRITE setVolume NOTIFY volumeChanged)
+
+       public:
+               MasterVolume(QObject *parent = 0)
+                       : QObject(parent), m_volume(32768)
+               {
+               }
+
+               ~MasterVolume() {}
+
+               uint32_t getVolume() const { return m_volume; }
+               void setVolume(pa_volume_t volume);
+
+       public slots:
+               void changeExternalVolume(int volume);
+
+       signals:
+               void volumeChanged(void);
+               void sliderVolumeChanged(int volume_delta);
+               void externalVolumeChanged(uint32_t volume);
+
+       private:
+               uint32_t m_volume;
+};
diff --git a/homescreen/src/paclient.cpp b/homescreen/src/paclient.cpp
new file mode 100644 (file)
index 0000000..59a3742
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016,2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "paclient.h"
+
+#include <QtCore/QDebug>
+
+PaClient::PaClient()
+       : m_init(false), m_ml(nullptr), m_mlapi(nullptr), m_ctx(nullptr)
+{
+}
+
+PaClient::~PaClient()
+{
+       if (m_init)
+               close();
+}
+
+void PaClient::close()
+{
+       if (!m_init) return;
+       pa_threaded_mainloop_stop(m_ml);
+       pa_threaded_mainloop_free(m_ml);
+       m_init = false;
+}
+
+static void set_sink_volume_cb(pa_context *c, int success, void *data)
+{
+       Q_UNUSED(data);
+
+       if (!success)
+               qWarning() << "PaClient: set sink volume: " <<
+                       pa_strerror(pa_context_errno(c));
+}
+
+void PaClient::incDecVolume(const int volume_delta)
+{
+       pa_operation *o;
+       pa_context *c = context();
+       pa_sink_info *i = getDefaultSinkInfo();
+
+       if (volume_delta > 0)
+               pa_cvolume_inc_clamp(&i->volume, volume_delta, 65536);
+       else
+               pa_cvolume_dec(&i->volume, abs(volume_delta));
+
+       o = pa_context_set_sink_volume_by_index(c, i->index, &i->volume, set_sink_volume_cb, NULL);
+       if (!o) {
+               qWarning() << "PaClient: set sink #" << i->index <<
+                       " volume: " << pa_strerror(pa_context_errno(c));
+               return;
+       }
+       pa_operation_unref(o);
+}
+
+void get_sink_info_change_cb(pa_context *c,
+               const pa_sink_info *i,
+               int eol,
+               void *data)
+{
+       Q_UNUSED(c);
+       Q_ASSERT(i);
+       Q_ASSERT(data);
+
+       if (eol) return;
+
+       PaClient *self = reinterpret_cast<PaClient*>(data);
+       pa_sink_info *si = self->getDefaultSinkInfo();
+       if (i->index == si->index) {
+               self->setDefaultSinkInfo(i);
+               pa_cvolume *cvolume = &self->getDefaultSinkInfo()->volume;
+               emit self->volumeExternallyChanged(pa_cvolume_avg(cvolume));
+       }
+}
+
+void get_default_sink_info_cb(pa_context *c,
+               const pa_sink_info *i,
+               int eol,
+               void *data)
+{
+       Q_UNUSED(c);
+       Q_ASSERT(i);
+       Q_ASSERT(data);
+
+       if (eol) return;
+
+       PaClient *self = reinterpret_cast<PaClient*>(data);
+       self->setDefaultSinkInfo(i);
+}
+
+pa_sink_info *PaClient::getDefaultSinkInfo(void)
+{
+       return &m_default_sink_info;
+}
+
+void PaClient::setDefaultSinkInfo(const pa_sink_info *i)
+{
+       m_default_sink_info.index = i->index;
+       m_default_sink_info.channel_map.channels = i->channel_map.channels;
+       pa_cvolume *cvolume = &m_default_sink_info.volume;
+       cvolume->channels = i->volume.channels;
+       for (int chan = 0; chan < i->channel_map.channels; chan++) {
+               cvolume->values[chan] = i->volume.values[chan];
+       }
+}
+
+void get_server_info_cb(pa_context *c,
+               const pa_server_info *i,
+               void *data)
+{
+       pa_operation *o;
+       o = pa_context_get_sink_info_by_name(c, i->default_sink_name, get_default_sink_info_cb, data);
+       if (!o) {
+               qWarning() << "PaClient: get sink info by name: " <<
+                       pa_strerror(pa_context_errno(c));
+               return;
+       }
+}
+
+void subscribe_cb(pa_context *c,
+               pa_subscription_event_type_t type,
+               uint32_t index,
+               void *data)
+{
+       pa_operation *o;
+
+       if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) {
+               qWarning("PaClient: unhandled subscribe event operation");
+               return;
+       }
+
+       switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+               case PA_SUBSCRIPTION_EVENT_SINK:
+                       o = pa_context_get_sink_info_by_index(c, index, get_sink_info_change_cb, data);
+                       if (!o) {
+                               qWarning() << "PaClient: get sink info by index: " <<
+                                       pa_strerror(pa_context_errno(c));
+                               return;
+                       }
+                       break;
+               default:
+                       qWarning("PaClient: unhandled subscribe event facility");
+       }
+}
+
+void context_state_cb(pa_context *c, void *data)
+{
+       pa_operation *o;
+       PaClient *self = reinterpret_cast<PaClient*>(data);
+
+       switch (pa_context_get_state(c)) {
+               case PA_CONTEXT_CONNECTING:
+               case PA_CONTEXT_AUTHORIZING:
+               case PA_CONTEXT_SETTING_NAME:
+                       break;
+               case PA_CONTEXT_READY:
+                       o = pa_context_get_server_info(c, get_server_info_cb, data);
+                       if (!o) {
+                               qWarning() << "PaClient: get server info: " <<
+                                       pa_strerror(pa_context_errno(c));
+                               return;
+                       }
+                       pa_operation_unref(o);
+                       pa_context_set_subscribe_callback(c, subscribe_cb, data);
+                       o = pa_context_subscribe(c, (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK), NULL, NULL);
+                       if (!o) {
+                               qWarning() << "PaClient: subscribe: " <<
+                                       pa_strerror(pa_context_errno(c));
+                               return;
+                       }
+                       break;
+               case PA_CONTEXT_TERMINATED:
+                       self->close();
+                       break;
+
+               case PA_CONTEXT_FAILED:
+               default:
+                       qCritical() << "PaClient: connection failed: " <<
+                               pa_strerror(pa_context_errno(c));
+                       self->close();
+                       break;
+       }
+}
+
+void PaClient::init()
+{
+       m_ml = pa_threaded_mainloop_new();
+       if (!m_ml) {
+               qCritical("PaClient: failed to create mainloop");
+               return;
+       }
+
+       pa_threaded_mainloop_set_name(m_ml, "PaClient mainloop");
+
+       m_mlapi = pa_threaded_mainloop_get_api(m_ml);
+
+       lock();
+
+       m_ctx = pa_context_new(m_mlapi, "HomeScreen");
+       if (!m_ctx) {
+               qCritical("PaClient: failed to create context");
+               unlock();
+               pa_threaded_mainloop_free(m_ml);
+               return;
+       }
+       pa_context_set_state_callback(m_ctx, context_state_cb, this);
+
+       if (pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0) {
+               qCritical("PaClient: failed to connect");
+               pa_context_unref(m_ctx);
+               unlock();
+               pa_threaded_mainloop_free(m_ml);
+               return;
+       }
+
+       if (pa_threaded_mainloop_start(m_ml) != 0) {
+               qCritical("PaClient: failed to start mainloop");
+               pa_context_unref(m_ctx);
+               unlock();
+               pa_threaded_mainloop_free(m_ml);
+               return;
+       }
+
+       unlock();
+
+       m_init = true;
+}
diff --git a/homescreen/src/paclient.h b/homescreen/src/paclient.h
new file mode 100644 (file)
index 0000000..abf178b
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016,2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pulse/pulseaudio.h>
+
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+
+class PaClient : public QObject
+{
+       Q_OBJECT
+       public:
+               PaClient();
+               ~PaClient();
+
+               void init();
+               void close();
+
+               inline pa_context *context() const
+               {
+                       return m_ctx;
+               }
+
+               inline void lock()
+               {
+                       pa_threaded_mainloop_lock(m_ml);
+               }
+
+               inline void unlock()
+               {
+                       pa_threaded_mainloop_unlock(m_ml);
+               }
+
+               pa_sink_info * getDefaultSinkInfo(void);
+               void setDefaultSinkInfo(const pa_sink_info *i);
+               void setMasterVolume(const pa_cvolume *);
+
+       public slots:
+               void incDecVolume(const int volume_delta);
+
+       signals:
+               void volumeExternallyChanged(int volume);
+
+       private:
+               bool m_init;
+               pa_threaded_mainloop *m_ml;
+               pa_mainloop_api *m_mlapi;
+               pa_context *m_ctx;
+               pa_cvolume m_master_cvolume;
+               pa_sink_info m_default_sink_info;
+};