summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--qml/cover/CoverPage.qml54
-rw-r--r--qml/pages/FirstPage.qml73
-rw-r--r--qml/pages/SecondPage.qml62
-rw-r--r--qml/salmeta.qml41
-rw-r--r--qml/watch/main.qml8
-rw-r--r--qml/widgetview.qml15
-rw-r--r--rpm/salmeta.changes.in15
-rw-r--r--rpm/salmeta.spec94
-rw-r--r--rpm/salmeta.yaml35
-rw-r--r--salmeta.desktop7
-rw-r--r--salmeta.pro52
-rw-r--r--salmeta.service11
-rw-r--r--src/controller.cpp84
-rw-r--r--src/controller.h53
-rw-r--r--src/metawatch.cpp183
-rw-r--r--src/metawatch.h101
-rw-r--r--src/metawatchbletransport.cpp141
-rw-r--r--src/metawatchbletransport.h46
-rw-r--r--src/metawatchtransport.cpp6
-rw-r--r--src/metawatchtransport.h25
-rw-r--r--src/notificationmonitor.cpp6
-rw-r--r--src/notificationmonitor.h18
-rw-r--r--src/reconnecttimer.cpp64
-rw-r--r--src/reconnecttimer.h35
-rw-r--r--src/salmeta.cpp43
-rw-r--r--src/widgetinfo.cpp77
-rw-r--r--src/widgetinfo.h72
-rw-r--r--src/widgetview.cpp50
-rw-r--r--src/widgetview.h30
-rw-r--r--translations/salmeta.ts37
30 files changed, 1538 insertions, 0 deletions
diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml
new file mode 100644
index 0000000..786b78f
--- /dev/null
+++ b/qml/cover/CoverPage.qml
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2013 Jolla Ltd.
+ Contact: Thomas Perl <thomas.perl@jollamobile.com>
+ All rights reserved.
+
+ You may use this file under the terms of 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 Jolla 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 HOLDERS 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.
+*/
+
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+
+CoverBackground {
+ Label {
+ id: label
+ anchors.centerIn: parent
+ text: qsTr("My Cover")
+ }
+
+ CoverActionList {
+ id: coverAction
+
+ CoverAction {
+ iconSource: "image://theme/icon-cover-next"
+ }
+
+ CoverAction {
+ iconSource: "image://theme/icon-cover-pause"
+ }
+ }
+}
+
+
diff --git a/qml/pages/FirstPage.qml b/qml/pages/FirstPage.qml
new file mode 100644
index 0000000..14d7410
--- /dev/null
+++ b/qml/pages/FirstPage.qml
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2013 Jolla Ltd.
+ Contact: Thomas Perl <thomas.perl@jollamobile.com>
+ All rights reserved.
+
+ You may use this file under the terms of 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 Jolla 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 HOLDERS 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.
+*/
+
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+
+
+Page {
+ id: page
+
+ // To enable PullDownMenu, place our content in a SilicaFlickable
+ SilicaFlickable {
+ anchors.fill: parent
+
+ // PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView
+ PullDownMenu {
+ MenuItem {
+ text: qsTr("Show Page 2")
+ onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml"))
+ }
+ }
+
+ // Tell SilicaFlickable the height of its content.
+ contentHeight: column.height
+
+ // Place our content in a Column. The PageHeader is always placed at the top
+ // of the page, followed by our content.
+ Column {
+ id: column
+
+ width: page.width
+ spacing: Theme.paddingLarge
+ PageHeader {
+ title: qsTr("UI Template")
+ }
+ Label {
+ x: Theme.paddingLarge
+ text: qsTr("Hello Sailors")
+ color: Theme.secondaryHighlightColor
+ font.pixelSize: Theme.fontSizeExtraLarge
+ }
+ }
+ }
+}
+
+
diff --git a/qml/pages/SecondPage.qml b/qml/pages/SecondPage.qml
new file mode 100644
index 0000000..4f37362
--- /dev/null
+++ b/qml/pages/SecondPage.qml
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2013 Jolla Ltd.
+ Contact: Thomas Perl <thomas.perl@jollamobile.com>
+ All rights reserved.
+
+ You may use this file under the terms of 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 Jolla 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 HOLDERS 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.
+*/
+
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+
+
+Page {
+ id: page
+ SilicaListView {
+ id: listView
+ model: 20
+ anchors.fill: parent
+ header: PageHeader {
+ title: qsTr("Nested Page")
+ }
+ delegate: BackgroundItem {
+ id: delegate
+
+ Label {
+ x: Theme.paddingLarge
+ text: qsTr("Item") + " " + index
+ anchors.verticalCenter: parent.verticalCenter
+ color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor
+ }
+ onClicked: console.log("Clicked " + index)
+ }
+ VerticalScrollDecorator {}
+ }
+}
+
+
+
+
+
diff --git a/qml/salmeta.qml b/qml/salmeta.qml
new file mode 100644
index 0000000..bb031a4
--- /dev/null
+++ b/qml/salmeta.qml
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2013 Jolla Ltd.
+ Contact: Thomas Perl <thomas.perl@jollamobile.com>
+ All rights reserved.
+
+ You may use this file under the terms of 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 Jolla 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 HOLDERS 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.
+*/
+
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+import "pages"
+
+ApplicationWindow
+{
+ initialPage: Component { FirstPage { } }
+ cover: Qt.resolvedUrl("cover/CoverPage.qml")
+}
+
+
diff --git a/qml/watch/main.qml b/qml/watch/main.qml
new file mode 100644
index 0000000..200a8b0
--- /dev/null
+++ b/qml/watch/main.qml
@@ -0,0 +1,8 @@
+import QtQuick 2.0
+
+Rectangle {
+ width: 96
+ height: 96
+
+ color: "white"
+}
diff --git a/qml/widgetview.qml b/qml/widgetview.qml
new file mode 100644
index 0000000..077431d
--- /dev/null
+++ b/qml/widgetview.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.0
+
+Rectangle {
+ width: 96*4
+ height: 96
+
+ color: Qt.rgba(0, 0, 0, 1);
+
+ Repeater {
+ model: 16
+ Loader {
+
+ }
+ }
+}
diff --git a/rpm/salmeta.changes.in b/rpm/salmeta.changes.in
new file mode 100644
index 0000000..9661218
--- /dev/null
+++ b/rpm/salmeta.changes.in
@@ -0,0 +1,15 @@
+# Rename this file as salmeta.changes to include changelog
+# entries in your RPM file.
+#
+# Add new changelog entries following the format below.
+# Add newest entries to the top of the list.
+# Separate entries from eachother with a blank line.
+
+# * date Author's Name <author's email> version-release
+# - Summary of changes
+
+* Sun Apr 13 2014 Jack Tar <jack.tar@example.com> 0.0.1-1
+- Scrubbed the deck
+- Hoisted the sails
+
+
diff --git a/rpm/salmeta.spec b/rpm/salmeta.spec
new file mode 100644
index 0000000..dfafb60
--- /dev/null
+++ b/rpm/salmeta.spec
@@ -0,0 +1,94 @@
+#
+# Do NOT Edit the Auto-generated Part!
+# Generated by: spectacle version 0.27
+#
+
+Name: salmeta
+
+# >> macros
+# << macros
+
+%{!?qtc_qmake:%define qtc_qmake %qmake}
+%{!?qtc_qmake5:%define qtc_qmake5 %qmake5}
+%{!?qtc_make:%define qtc_make make}
+%{?qtc_builddir:%define _builddir %qtc_builddir}
+Summary: Salmeta
+Version: 0.1
+Release: 1
+Group: Communications/Bluetooth
+License: GPLv3
+URL: http://javispedro.com
+Source0: %{name}-%{version}.tar.bz2
+Source100: salmeta.yaml
+Requires: sailfishsilica-qt5 >= 0.10.9
+Requires: systemd
+Requires: systemd-user-session-targets
+BuildRequires: pkgconfig(sailfishapp) >= 1.0.2
+BuildRequires: pkgconfig(Qt5Core)
+BuildRequires: pkgconfig(Qt5Qml)
+BuildRequires: pkgconfig(Qt5Quick)
+BuildRequires: pkgconfig(Qt5DBus)
+BuildRequires: pkgconfig(Qt5Bluetooth)
+BuildRequires: pkgconfig(mlite5)
+BuildRequires: pkgconfig(libiphb)
+BuildRequires: desktop-file-utils
+
+%description
+Salmeta is a Metawatch manager program for Sailfish devices.
+
+
+%prep
+%setup -q -n %{name}-%{version}
+
+# >> setup
+# << setup
+
+%build
+# >> build pre
+# << build pre
+
+%qtc_qmake5
+
+%qtc_make %{?_smp_mflags}
+
+# >> build post
+# << build post
+
+%install
+rm -rf %{buildroot}
+# >> install pre
+# << install pre
+%qmake5_install
+
+# >> install post
+# << install post
+
+desktop-file-install --delete-original \
+ --dir %{buildroot}%{_datadir}/applications \
+ %{buildroot}%{_datadir}/applications/*.desktop
+
+%post
+# >> post
+if [ "$1" -ge 1 ]; then
+systemctl-user daemon-reload || :
+systemctl-user restart salmeta.service || :
+fi
+# << post
+
+%postun
+# >> postun
+if [ "$1" -eq 0 ]; then
+systemctl-user stop salmeta.service || :
+systemctl-user daemon-reload || :
+fi
+# << postun
+
+%files
+%defattr(-,root,root,-)
+%{_bindir}
+%{_datadir}/%{name}
+%{_datadir}/applications/%{name}.desktop
+%{_datadir}/icons/hicolor/86x86/apps/%{name}.png
+%{_libdir}/systemd/user/salmeta.service
+# >> files
+# << files
diff --git a/rpm/salmeta.yaml b/rpm/salmeta.yaml
new file mode 100644
index 0000000..14e443f
--- /dev/null
+++ b/rpm/salmeta.yaml
@@ -0,0 +1,35 @@
+Name: salmeta
+Summary: Salmeta
+Version: 0.1
+Release: 1
+Group: Communications/Bluetooth
+URL: http://javispedro.com
+License: GPLv3
+Sources:
+- '%{name}-%{version}.tar.bz2'
+Description: |
+ Salmeta is a Metawatch manager program for Sailfish devices.
+Configure: none
+Builder: qtc5
+
+PkgConfigBR:
+ - sailfishapp >= 1.0.2
+ - Qt5Core
+ - Qt5Qml
+ - Qt5Quick
+ - Qt5DBus
+ - Qt5Bluetooth
+ - mlite5
+ - libiphb
+
+Requires:
+ - sailfishsilica-qt5 >= 0.10.9
+ - systemd
+ - systemd-user-session-targets
+
+Files:
+ - '%{_bindir}'
+ - '%{_datadir}/%{name}'
+ - '%{_datadir}/applications/%{name}.desktop'
+ - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png'
+ - '%{_libdir}/systemd/user/salmeta.service'
diff --git a/salmeta.desktop b/salmeta.desktop
new file mode 100644
index 0000000..7d08500
--- /dev/null
+++ b/salmeta.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Type=Application
+X-Nemo-Application-Type=silica-qt5
+Icon=salmeta
+Exec=salmeta
+Name=Salmeta
+
diff --git a/salmeta.pro b/salmeta.pro
new file mode 100644
index 0000000..7db3699
--- /dev/null
+++ b/salmeta.pro
@@ -0,0 +1,52 @@
+TARGET = salmeta
+
+QT += qml quick dbus bluetooth
+
+CONFIG += link_pkgconfig sailfishapp c++11
+PKGCONFIG += mlite5 libiphb
+LIBS += -lgato
+
+# Remove this if QtCreator can find headers on your machine
+CONFIG(debug, debug|release) : INCLUDEPATH += /usr/include/mlite5
+
+SOURCES += src/salmeta.cpp \
+ src/controller.cpp \
+ src/metawatchtransport.cpp \
+ src/metawatch.cpp \
+ src/metawatchbletransport.cpp \
+ src/reconnecttimer.cpp \
+ src/notificationmonitor.cpp \
+ src/widgetview.cpp \
+ src/widgetinfo.cpp
+
+HEADERS += \
+ src/controller.h \
+ src/metawatchtransport.h \
+ src/metawatch.h \
+ src/metawatchbletransport.h \
+ src/reconnecttimer.h \
+ src/notificationmonitor.h \
+ src/widgetview.h \
+ src/widgetinfo.h
+
+OTHER_FILES += qml/salmeta.qml \
+ qml/cover/CoverPage.qml \
+ qml/pages/FirstPage.qml \
+ qml/pages/SecondPage.qml \
+ rpm/salmeta.changes.in \
+ rpm/salmeta.spec \
+ rpm/salmeta.yaml \
+ translations/*.ts \
+ salmeta.desktop \
+ qml/watch/main.qml \
+ qml/widgetview.qml \
+ salmeta.service
+
+# to disable building translations every time, comment out the
+# following CONFIG line
+CONFIG += sailfishapp_i18n
+#TRANSLATIONS += translations/salmeta-de.ts
+
+unit.path = /usr/lib/systemd/user/
+unit.files = salmeta.service
+INSTALLS += unit
diff --git a/salmeta.service b/salmeta.service
new file mode 100644
index 0000000..70cca61
--- /dev/null
+++ b/salmeta.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Salmeta MetaWatch manager daemon
+Requires=dbus.socket bluetooth.target
+After=lipstick.service dbus.socket bluetooth.target
+
+[Service]
+ExecStart=/usr/bin/salmeta --daemon
+Restart=always
+
+[Install]
+WantedBy=user-session.target
diff --git a/src/controller.cpp b/src/controller.cpp
new file mode 100644
index 0000000..02c8252
--- /dev/null
+++ b/src/controller.cpp
@@ -0,0 +1,84 @@
+#include <QtCore/QDebug>
+#include <QtCore/QDateTime>
+
+#include "controller.h"
+
+static const QLatin1String setting_address("address");
+static const QLatin1String setting_num_pages("num-pages");
+static const QLatin1String setting_page("page%1");
+static const QLatin1String setting_page_widget("widget%1");
+
+Controller::Controller(const QString &settingsPrefix, QQuickView *view, QObject *parent) :
+ QObject(parent),
+ _settings(new MDConfGroup(this)),
+ _metawatch(0),
+ _reconnect(new ReconnectTimer(this)),
+ _view(new WidgetView(settingsPrefix, view, this)),
+ _batteryCharge(0), _batteryCharging(false)
+{
+ _settings->setPath(settingsPrefix);
+ _metawatch = new MetaWatch(_settings->value(setting_address).toString(), this);
+
+ connect(_settings, &MDConfGroup::valueChanged, this, &Controller::handleSettingChanged);
+ connect(_metawatch, &MetaWatch::connected, _reconnect, &ReconnectTimer::stop);
+ connect(_metawatch, &MetaWatch::connected, this, &Controller::handleMetaWatchConnected);
+ connect(_metawatch, &MetaWatch::disconnected, _reconnect, &ReconnectTimer::start);
+ connect(_metawatch, &MetaWatch::batteryStatus, this, &Controller::handleMetaWatchBatteryStatus);
+ connect(_reconnect, &ReconnectTimer::tick, _metawatch, &MetaWatch::connectDevice);
+ _reconnect->start();
+
+ reloadPages();
+}
+
+int Controller::batteryCharge() const
+{
+ return _batteryCharge;
+}
+
+bool Controller::batteryCharging() const
+{
+ return _batteryCharging;
+}
+
+void Controller::reloadPages()
+{
+
+}
+
+void Controller::switchToPage(int page)
+{
+
+}
+
+void Controller::handleSettingChanged(const QString &key)
+{
+ qDebug() << "Setting" << key << "changed";
+ if (key == setting_num_pages) {
+ _settings->value(setting_num_pages, 0);
+ switchToPage(0);
+ }
+}
+
+void Controller::handleMetaWatchConnected()
+{
+ qDebug() << "MetaWatch connected, synchronizing date time";
+
+ _metawatch->updateDeviceType();
+ _metawatch->updateBatteryStatus();
+ _metawatch->setDateTime(QDateTime::currentDateTime());
+ _metawatch->updateLcdDisplay();
+
+ _metawatch->updateWidgetList(_view->widgets());
+}
+
+void Controller::handleMetaWatchBatteryStatus(bool charging, int charge)
+{
+ if (_batteryCharging != charging) {
+ _batteryCharging = charging;
+ emit batteryChargingChanged();
+ }
+ if (_batteryCharge != charge) {
+ _batteryCharge = charge;
+ emit batteryChargeChanged();
+ }
+}
diff --git a/src/controller.h b/src/controller.h
new file mode 100644
index 0000000..d7f2349
--- /dev/null
+++ b/src/controller.h
@@ -0,0 +1,53 @@
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#include <QObject>
+#include <QQuickView>
+#include <MDConfGroup>
+
+#include "metawatch.h"
+#include "widgetview.h"
+#include "reconnecttimer.h"
+
+class Controller : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int batteryCharge READ batteryCharge NOTIFY batteryChargeChanged)
+ Q_PROPERTY(bool batteryCharging READ batteryCharging NOTIFY batteryChargingChanged)
+
+public:
+ Controller(const QString &settingsPrefix, QQuickView *view = 0, QObject *parent = 0);
+
+ int batteryCharge() const;
+ bool batteryCharging() const;
+
+signals:
+ void batteryChargeChanged();
+ void batteryChargingChanged();
+
+public slots:
+ void reloadPages();
+ void switchToPage(int page);
+
+private:
+ static int parseClockUrl(const QUrl &url);
+
+private slots:
+ void handleSettingChanged(const QString &key);
+ void handleMetaWatchConnected();
+ void handleMetaWatchBatteryStatus(bool charging, int charge);
+
+private:
+ MDConfGroup *_settings;
+ MetaWatch *_metawatch;
+ ReconnectTimer *_reconnect;
+
+ WidgetView *_view;
+
+ // Watch status
+ int _batteryCharge;
+ bool _batteryCharging;
+};
+
+#endif // DAEMON_H
diff --git a/src/metawatch.cpp b/src/metawatch.cpp
new file mode 100644
index 0000000..a27d0ec
--- /dev/null
+++ b/src/metawatch.cpp
@@ -0,0 +1,183 @@
+#include <QtCore/QDataStream>
+#include <QtCore/QDateTime>
+#include <QtCore/QDebug>
+
+#include "metawatch.h"
+#include "metawatchbletransport.h"
+
+MetaWatch::MetaWatch(const QString &btAddr, QObject *parent) :
+ QObject(parent)
+{
+ GatoPeripheral *peripheral = new GatoPeripheral(GatoAddress(btAddr), this);
+ _transport = new MetaWatchBLETransport(peripheral, this);
+
+ connect(_transport, &MetaWatchTransport::connected, this, &MetaWatch::connected);
+ connect(_transport, &MetaWatchTransport::disconnected, this, &MetaWatch::disconnected);
+ connect(_transport, &MetaWatchTransport::messageReceived, this, &MetaWatch::handleTransportMessage);
+}
+
+QList<QUrl> MetaWatch::availableClocks()
+{
+ QList<QUrl> clocks;
+
+ for (int i = 0; i < 6; i++) {
+ clocks << QUrl("clock://" + QString::number(i));
+ }
+
+ return clocks;
+}
+
+void MetaWatch::setDateTime(const QDateTime &dt)
+{
+ const QDate &date = dt.date();
+ const QTime &time = dt.time();
+ QByteArray data;
+ QDataStream s(&data, QIODevice::WriteOnly);
+ s.setByteOrder(QDataStream::BigEndian);
+ s << quint16(date.year()) << quint8(date.month()) << quint8(date.day());
+ s << quint8(date.dayOfWeek() % 7);
+ s << quint8(time.hour()) << quint8(time.minute()) << quint8(time.second());
+
+ _transport->sendMessage(MessageSetRealTimeClock, 0, data);
+}
+
+void MetaWatch::configure(WatchProperties props)
+{
+ _transport->sendMessage(MessageWatchPropertyOperation, props | WatchPropertyOperationWrite, QByteArray());
+}
+
+void MetaWatch::updateDeviceType()
+{
+ // Also disables HFP/MAP connection.
+ _transport->sendMessage(MessageGetDeviceType, 0xC0, QByteArray());
+}
+
+void MetaWatch::updateBatteryStatus()
+{
+ _transport->sendMessage(MessageGetBatteryStatus, 0, QByteArray());
+}
+
+void MetaWatch::updateLcdDisplay()
+{
+ // Switches to UiGen2 mode and to page 0.
+ _transport->sendMessage(MessageUpdateLcdDisplay, 0x80, QByteArray());
+}
+
+void MetaWatch::updateWidgetList(const QList<WidgetInfo*> &widgets)
+{
+ int num_widgets = 0;
+
+ // Count valid widgets
+ for (int w = 0; w < widgets.size(); w++) {
+ const WidgetInfo *info = widgets[w];
+ if (info->valid()) num_widgets++;
+ }
+
+ const int max_widgets_in_one_msg = 7;
+ const int num_messages = (num_widgets + (max_widgets_in_one_msg - 1)) / max_widgets_in_one_msg;
+ int num_message = 0;
+ QByteArray msg;
+
+ Q_ASSERT(num_messages < 4);
+
+ msg.reserve(max_widgets_in_one_msg * 2);
+
+ if (num_widgets == 0) {
+ msg.append(static_cast<char>(0xFF));
+ msg.append(static_cast<char>(0x00));
+
+ // Clear all widgets
+ _transport->sendMessage(MessageSetWidgetList, 0x4, msg);
+
+ return;
+ }
+
+ qDebug() << "Msgs" << num_messages << num_message;
+
+ for (int w = 0; w < num_widgets; w++) {
+ WidgetInfo *info = widgets[w];
+ if (!info->valid()) continue; // Skip disabled/empty widget
+
+ quint8 id = w;
+ quint8 options = ((info->page() << 4) & 0x30)
+ | ((info->size() << 2) & 0xC) | ((info->position() << 2) & 0x3);
+
+ if (info->url().scheme() == "clock") {
+ id |= (clockUrlToClockId(info->url()) << 4) & 0xF0;
+ options |= 0x80;
+ }
+
+ if (info->invert()) {
+ options |= 0x40;
+ }
+
+ qDebug() << QString::number(id, 16) << QString::number(options, 16);
+
+ msg.append(id);
+ msg.append(options);
+
+ if (msg.size() >= max_widgets_in_one_msg * 2) {
+ Q_ASSERT(num_message < num_messages);
+ _transport->sendMessage(MessageSetWidgetList,
+ ((num_messages << 2) & 0xC) | (num_message & 0x3),
+ msg);
+
+ msg.clear();
+ msg.reserve(max_widgets_in_one_msg * 2);
+ num_message++;
+ }
+ }
+
+ if (msg.size() > 0) {
+ Q_ASSERT(num_message < num_messages);
+ _transport->sendMessage(MessageSetWidgetList,
+ ((num_messages << 2) & 0xC) | (num_message & 0x3),
+ msg);
+ }
+}
+
+void MetaWatch::connectDevice()
+{
+ _transport->connectDevice();
+}
+
+void MetaWatch::disconnectDevice()
+{
+ _transport->disconnectDevice();
+}
+
+int MetaWatch::clockUrlToClockId(const QUrl &url)
+{
+ if (url.scheme() != "clock")
+ return -1;
+
+ QString host = url.host();
+ if (!host.startsWith("clock"))
+ return -1;
+
+ return host.mid(5).toInt();
+}
+
+void MetaWatch::handleTransportMessage(quint8 type, quint8 options, const QByteArray &payload)
+{
+ switch (type) {
+ case MessageGetDeviceTypeResponse:
+ emit deviceType(static_cast<DeviceType>(options & 0xF));
+ break;
+ case MessageModeChangeIndication:
+ qDebug() << "Got mode change indication";
+ break;
+ case MessageReadBatteryStatusResponse:
+ if (payload.size() < 6) {
+ qWarning() << "Invalid battery status response size";
+ }
+ emit batteryStatus(payload[1], payload[2]);
+ break;
+ case MessageConnectionChange:
+ case MessageIntervalChanged:
+ // No idea what to do with these.
+ break;
+ default:
+ qWarning() << "Unknown message type received:" << QString::number(type, 16);
+ }
+}
diff --git a/src/metawatch.h b/src/metawatch.h
new file mode 100644
index 0000000..dd02bf4
--- /dev/null
+++ b/src/metawatch.h
@@ -0,0 +1,101 @@
+#ifndef METAWATCH_H
+#define METAWATCH_H
+
+#include <QObject>
+#include <QBluetoothAddress>
+
+QT_USE_NAMESPACE_BLUETOOTH
+
+#include "metawatchtransport.h"
+#include "widgetinfo.h"
+
+class MetaWatch : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(MessageTypes WatchProperty UiStyle)
+ Q_FLAGS(WatchProperties)
+
+public:
+ explicit MetaWatch(const QString &btAddr, QObject *parent = 0);
+
+ enum MessageTypes {
+ MessageGetDeviceType = 0x01,
+ MessageGetDeviceTypeResponse = 0x02,
+
+ MessageSetRealTimeClock = 0x26,
+
+ MessageWatchPropertyOperation = 0x30,
+
+ MessageModeChangeIndication = 0x33,
+
+ MessageUpdateLcdDisplay = 0x43,
+
+ MessageGetBatteryStatus = 0x56,
+ MessageReadBatteryStatusResponse = 0x57,
+
+ MessageSetWidgetList = 0xA1,
+
+ MessageSetupAccelerometer = 0xE1,
+ MessageAccelerometerDataResponse = 0xE0,
+
+ // Messages from the propietary BT stack
+ MessageConnectionChange = 0xB9,
+ MessageIntervalChanged = 0xBB
+ };
+
+ enum DeviceType {
+ DeviceMetaWatchDigitalGen1 = 2,
+ DeviceMetaWatchDigitalGen2 = 5
+ };
+
+ enum WatchProperty {
+ WatchPropertyHourFormat12h = 0,
+ WatchPropertyHourFormat24h = 1,
+ WatchPropertyDateFormatMMDD = 0 << 1,
+ WatchPropertyDateFormatDDMM = 1 << 1,
+ WatchPropertyShowSeconds = 1 << 2,
+ WatchPropertyShowSeparationLines = 1 << 3,
+ WatchPropertyAutobacklight = 1 << 4,
+
+ WatchPropertyOperationRead = 0 << 7,
+ WatchPropertyOperationWrite = 1 << 7
+ };
+ Q_DECLARE_FLAGS(WatchProperties, WatchProperty)
+
+ enum UiStyle {
+ UiGen1 = 0,
+ UiGen2 = 1
+ };
+
+ static QList<QUrl> availableClocks();
+
+ void setDateTime(const QDateTime &dt);
+ void configure(WatchProperties props);
+
+ void updateDeviceType();
+ void updateBatteryStatus();
+ void updateLcdDisplay(); // TODO: More overloads
+ void updateWidgetList(const QList<WidgetInfo*>& widgets);
+
+signals:
+ void connected();
+ void disconnected();
+
+ void deviceType(DeviceType type);
+ void batteryStatus(bool charging, int charge);
+
+public slots:
+ void connectDevice();
+ void disconnectDevice();
+
+private:
+ static int clockUrlToClockId(const QUrl &url);
+
+private slots:
+ void handleTransportMessage(quint8 type, quint8 options, const QByteArray &payload);
+
+private:
+ MetaWatchTransport *_transport;
+};
+
+#endif // METAWATCH_H
diff --git a/src/metawatchbletransport.cpp b/src/metawatchbletransport.cpp
new file mode 100644
index 0000000..56039d3
--- /dev/null
+++ b/src/metawatchbletransport.cpp
@@ -0,0 +1,141 @@
+#include "metawatchbletransport.h"
+
+const GatoUUID MetaWatchBLETransport::ServiceUuid(quint16(0x8880));
+const GatoUUID MetaWatchBLETransport::InputCharacteristicUuid(quint16(0x8882));
+const GatoUUID MetaWatchBLETransport::OutputCharacteristicUuid(quint16(0x8881));
+
+MetaWatchBLETransport::MetaWatchBLETransport(GatoPeripheral *peripheral, QObject *parent) :
+ MetaWatchTransport(parent), _dev(peripheral)
+{
+ connect(_dev, SIGNAL(connected()), SLOT(handleDeviceConnected()));
+ connect(_dev, SIGNAL(disconnected()), SLOT(handleDeviceDisconnected()));
+ connect(_dev, SIGNAL(servicesDiscovered()), SLOT(handleDeviceServices()));
+ connect(_dev, SIGNAL(characteristicsDiscovered(GatoService)),
+ SLOT(handleDeviceCharacteristics(GatoService)));
+ connect(_dev, SIGNAL(valueUpdated(GatoCharacteristic,QByteArray)),
+ SLOT(handleDeviceUpdate(GatoCharacteristic,QByteArray)));
+}
+
+MetaWatchBLETransport::~MetaWatchBLETransport()
+{
+ disconnectDevice();
+}
+
+void MetaWatchBLETransport::sendMessage(quint8 type, quint8 options, const QByteArray &payload)
+{
+ QByteArray packet = encode(type, options, payload);
+ qDebug() << "Send:" << packet.toHex();
+ _dev->writeValue(_out, packet);
+}
+
+void MetaWatchBLETransport::connectDevice()
+{
+ qDebug() << "Trying to connect to" << _dev->address();
+ _dev->connectPeripheral();
+}
+
+void MetaWatchBLETransport::disconnectDevice()
+{
+ _dev->disconnectPeripheral();
+}
+
+QByteArray MetaWatchBLETransport::encode(quint8 type, quint8 options, const QByteArray &payload)
+{
+ QByteArray packet;
+
+ quint8 message_size = 6 + payload.size();
+ packet.reserve(message_size);
+
+ packet.append(0x01);
+ packet.append(message_size);
+ packet.append(type);
+ packet.append(options);
+
+ packet.append(payload);
+ packet.append("\0\0", 2); // Empty CRC is OK for BLE
+
+ return packet;
+}
+
+bool MetaWatchBLETransport::decode(const QByteArray &packet, quint8 *type, quint8 *options, QByteArray *payload)
+{
+ if (packet.size() < 6) {
+ qWarning() << "Message too short";
+ }
+
+ if (packet.at(0) != 0x1) {
+ qWarning() << "Invalid packet header";
+ return false;
+ }
+
+ quint8 message_size = packet[1];
+ if (message_size < 6 || message_size > 32 || message_size != packet.size()) {
+ qWarning() << "Invalid message size:" << message_size;
+ }
+
+ *type = packet[2];
+ *options = packet[3];
+
+ *payload = packet.mid(4, message_size - 6);
+
+ // Ignore CRC on BLE: it's set to 0 by firmware.
+
+ return true;
+}
+
+void MetaWatchBLETransport::handleDeviceConnected()
+{
+ qDebug() << "MW connected";
+ if (_dev->services().isEmpty()) {
+ qDebug() << "Trying to discover services";
+ QList<GatoUUID> interesting_services;
+ interesting_services << ServiceUuid;
+ _dev->discoverServices();
+ }
+}
+
+void MetaWatchBLETransport::handleDeviceDisconnected()
+{
+ qDebug() << "MW disconnected";
+ emit disconnected();
+}
+
+void MetaWatchBLETransport::handleDeviceServices()
+{
+ QList<GatoService> services = _dev->services();
+ qDebug() << "Got" << services.size() << "services";
+ foreach (const GatoService &s, services) {
+ if (s.uuid() == ServiceUuid) {
+ _dev->discoverCharacteristics(s);
+ }
+ }
+}
+
+void MetaWatchBLETransport::handleDeviceCharacteristics(const GatoService &service)
+{
+ qDebug() << "Got characteristic";
+ foreach (const GatoCharacteristic &c, service.characteristics()) {
+ if (c.uuid() == InputCharacteristicUuid) {
+ _in = c;
+ _dev->setNotification(_in, true);
+ } else if (c.uuid() == OutputCharacteristicUuid) {
+ _out = c;
+ }
+ if (!_in.isNull() && !_out.isNull()) {
+ emit connected();
+ }
+ }
+}
+
+void MetaWatchBLETransport::handleDeviceUpdate(const GatoCharacteristic &characteristic, const QByteArray &value)
+{
+ qDebug() << "Recv:" << value.toHex();
+ quint8 type, options;
+ QByteArray payload;
+
+ if (decode(value, &type, &options, &payload)) {
+ emit messageReceived(type, options, payload);
+ } else {
+ qWarning() << "Failed to decode message from metawatch:" << value.toHex();
+ }
+}
diff --git a/src/metawatchbletransport.h b/src/metawatchbletransport.h
new file mode 100644
index 0000000..973c4a8
--- /dev/null
+++ b/src/metawatchbletransport.h
@@ -0,0 +1,46 @@
+#ifndef METAWATCHBLETRANSPORT_H
+#define METAWATCHBLETRANSPORT_H
+
+#include <QtCore/QObject>
+
+#include <gato/gatoperipheral.h>
+#include <gato/gatouuid.h>
+#include <gato/gatoservice.h>
+#include <gato/gatocharacteristic.h>
+
+#include "metawatchtransport.h"
+
+class MetaWatchBLETransport : public MetaWatchTransport
+{
+ Q_OBJECT
+public:
+ explicit MetaWatchBLETransport(GatoPeripheral *peripheral, QObject *parent = 0);
+ ~MetaWatchBLETransport();
+
+ static const GatoUUID ServiceUuid;
+ static const GatoUUID InputCharacteristicUuid;
+ static const GatoUUID OutputCharacteristicUuid;
+
+ void sendMessage(quint8 type, quint8 options, const QByteArray &payload);
+
+public slots:
+ void connectDevice();
+ void disconnectDevice();
+
+private:
+ static QByteArray encode(quint8 type, quint8 options, const QByteArray &payload);
+ static bool decode(const QByteArray &msg, quint8 *type, quint8 *options, QByteArray *payload);
+
+private slots:
+ void handleDeviceConnected();
+ void handleDeviceDisconnected();
+ void handleDeviceServices();
+ void handleDeviceCharacteristics(const GatoService &service);
+ void handleDeviceUpdate(const GatoCharacteristic &characteristic, const QByteArray &value);
+
+private:
+ GatoPeripheral *_dev;
+ GatoCharacteristic _in, _out;
+};
+
+#endif // METAWATCHBLETRANSPORT_H
diff --git a/src/metawatchtransport.cpp b/src/metawatchtransport.cpp
new file mode 100644
index 0000000..078d390
--- /dev/null
+++ b/src/metawatchtransport.cpp
@@ -0,0 +1,6 @@
+#include "metawatchtransport.h"
+
+MetaWatchTransport::MetaWatchTransport(QObject *parent) :
+ QObject(parent)
+{
+}
diff --git a/src/metawatchtransport.h b/src/metawatchtransport.h
new file mode 100644
index 0000000..3d78ee2
--- /dev/null
+++ b/src/metawatchtransport.h
@@ -0,0 +1,25 @@
+#ifndef METAWATCHTRANSPORT_H
+#define METAWATCHTRANSPORT_H
+
+#include <QObject>
+
+class MetaWatchTransport : public QObject
+{
+ Q_OBJECT
+public:
+ explicit MetaWatchTransport(QObject *parent = 0);
+
+public:
+ virtual void sendMessage(quint8 type, quint8 options, const QByteArray &payload) = 0;
+
+public slots:
+ virtual void connectDevice() = 0;
+ virtual void disconnectDevice() = 0;
+
+signals:
+ void connected();
+ void disconnected();
+ void messageReceived(quint8 type, quint8 options, const QByteArray &payload);
+};
+
+#endif // METAWATCHTRANSPORT_H
diff --git a/src/notificationmonitor.cpp b/src/notificationmonitor.cpp
new file mode 100644
index 0000000..ca6f713
--- /dev/null
+++ b/src/notificationmonitor.cpp
@@ -0,0 +1,6 @@
+#include "notificationmonitor.h"
+
+NotificationMonitor::NotificationMonitor(QObject *parent) :
+ QObject(parent)
+{
+}
diff --git a/src/notificationmonitor.h b/src/notificationmonitor.h
new file mode 100644
index 0000000..c7b6cb2
--- /dev/null
+++ b/src/notificationmonitor.h
@@ -0,0 +1,18 @@
+#ifndef NOTIFICATIONMONITOR_H
+#define NOTIFICATIONMONITOR_H
+
+#include <QObject>
+
+class NotificationMonitor : public QObject
+{
+ Q_OBJECT
+public:
+ explicit NotificationMonitor(QObject *parent = 0);
+
+signals:
+
+public slots:
+
+};
+
+#endif // NOTIFICATIONMONITOR_H
diff --git a/src/reconnecttimer.cpp b/src/reconnecttimer.cpp
new file mode 100644
index 0000000..eaf6cbb
--- /dev/null
+++ b/src/reconnecttimer.cpp
@@ -0,0 +1,64 @@
+#include <QtCore/QDebug>
+
+extern "C" {
+ #include <iphbd/libiphb.h>
+}
+
+#include "reconnecttimer.h"
+
+static unsigned int num_wait_times = 8;
+static unsigned short wait_times[8] = {
+ 2, 5, 10, 30, 2 * 60, 5 * 60, 10 * 60, 15 * 60
+};
+
+ReconnectTimer::ReconnectTimer(QObject *parent)
+ : QObject(parent),
+ _iphb(iphb_open(0)),
+ _notifier(new QSocketNotifier(iphb_get_fd(_iphb), QSocketNotifier::Read, this)),
+ _active(false)
+{
+ connect(_notifier, &QSocketNotifier::activated, this, &ReconnectTimer::handleIphbActivity);
+}
+
+ReconnectTimer::~ReconnectTimer()
+{
+ iphb_close(_iphb);
+}
+
+void ReconnectTimer::start()
+{
+ _active = true;
+ _counter = 0;
+ setupWait();
+}
+
+void ReconnectTimer::stop()
+{
+ _active = false;
+ _counter = 0;
+ iphb_wait(_iphb, 0, 0, 0);
+}
+
+void ReconnectTimer::setupWait()
+{
+ iphb_wait(_iphb, wait_times[_counter] / 2, wait_times[_counter], 0);
+}
+
+void ReconnectTimer::handleIphbActivity()
+{
+ iphb_discard_wakeups(_iphb);
+
+ qDebug() << "iphb wakeup";
+
+ if (!_active) {
+ // False awakening
+ return;
+ }
+
+ emit tick();
+
+ if (++_counter > num_wait_times)
+ _counter = num_wait_times;
+
+ setupWait();
+}
diff --git a/src/reconnecttimer.h b/src/reconnecttimer.h
new file mode 100644
index 0000000..c8d901f
--- /dev/null
+++ b/src/reconnecttimer.h
@@ -0,0 +1,35 @@
+#ifndef RECONNECTCONTROLLER_H
+#define RECONNECTCONTROLLER_H
+
+#include <QObject>
+#include <QSocketNotifier>
+
+class ReconnectTimer : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ReconnectTimer(QObject *parent = 0);
+ ~ReconnectTimer();
+
+public slots:
+ void start();
+ void stop();
+
+signals:
+ void tick();
+
+private:
+ void setupWait();
+
+private slots:
+ void handleIphbActivity();
+
+private:
+ void *_iphb;
+ QSocketNotifier *_notifier;
+ bool _active;
+ uint _counter;
+};
+
+#endif // RECONNECTCONTROLLER_H
diff --git a/src/salmeta.cpp b/src/salmeta.cpp
new file mode 100644
index 0000000..c4bcb0c
--- /dev/null
+++ b/src/salmeta.cpp
@@ -0,0 +1,43 @@
+#include <QtCore/QDebug>
+#include <sailfishapp.h>
+
+#include "controller.h"
+
+static bool launch_daemon = false;
+static QString settings_key_prefix;
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication *app = SailfishApp::application(argc, argv);
+
+ // TODO: Rudimentary command line parser ahead. Move to QCommandLineParser when it's ready.
+ const QStringList args = app->arguments();
+ auto it = args.begin();
+ while (it != args.end()) {
+ if (*it == "--daemon") {
+ launch_daemon = true;
+ } else if (*it == "--root") {
+ ++it;
+ settings_key_prefix = *it;
+ }
+
+ ++it;
+ }
+
+ if (launch_daemon) {
+ if (settings_key_prefix.isEmpty()) {
+ settings_key_prefix = "/apps/salmeta/watch0";
+ }
+
+ qDebug() << "Starting salmeta (daemon) with settings from" << settings_key_prefix;
+
+ new Controller(settings_key_prefix, SailfishApp::createView());
+ } else {
+ QQuickView *view = SailfishApp::createView();
+ view->setSource(SailfishApp::pathTo("qml/salmeta.qml"));
+ view->show();
+ }
+
+ return app->exec();
+}
+
diff --git a/src/widgetinfo.cpp b/src/widgetinfo.cpp
new file mode 100644
index 0000000..f8cd78a
--- /dev/null
+++ b/src/widgetinfo.cpp
@@ -0,0 +1,77 @@
+#include "widgetinfo.h"
+
+WidgetInfo::WidgetInfo(QObject *parent)
+ : QObject(parent)
+{
+
+}
+
+bool WidgetInfo::valid() const
+{
+ return !_url.isEmpty();
+}
+
+QUrl WidgetInfo::url() const
+{
+ return _url;
+}
+
+void WidgetInfo::setUrl(const QUrl &url)
+{
+ if (url != _url) {
+ _url = url;
+ emit urlChanged();
+ }
+}
+
+bool WidgetInfo::invert() const
+{
+ return _invert;
+}
+
+void WidgetInfo::setInvert(bool invert)
+{
+ if (invert != _invert) {
+ _invert = invert;
+ emit invertChanged();
+ }
+}
+
+int WidgetInfo::page() const
+{
+ return _page;
+}
+
+void WidgetInfo::setPage(int page)
+{
+ if (page != _page) {
+ _page = page;
+ emit pageChanged();
+ }
+}
+
+WidgetInfo::WidgetSize WidgetInfo::size() const
+{
+ return _size;
+}
+
+void WidgetInfo::setSize(const WidgetSize &size)
+{
+ if (size != _size) {
+ _size = size;
+ emit sizeChanged();
+ }
+}
+
+WidgetInfo::WidgetPosition WidgetInfo::position() const
+{
+ return _pos;
+}
+
+void WidgetInfo::setPosition(const WidgetPosition &pos)
+{
+ if (pos != _pos) {
+ _pos = pos;
+ emit positionChanged();
+ }
+}
diff --git a/src/widgetinfo.h b/src/widgetinfo.h
new file mode 100644
index 0000000..06a6149
--- /dev/null
+++ b/src/widgetinfo.h
@@ -0,0 +1,72 @@
+#ifndef WIDGETINFO_H
+#define WIDGETINFO_H
+
+#include <QtCore/QObject>
+#include <QtCore/QUrl>
+
+class WidgetInfo : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(WidgetSize WidgetPosition)
+
+ Q_PROPERTY(bool valid READ valid NOTIFY validChanged)
+ Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(bool invert READ invert WRITE setInvert NOTIFY invertChanged)
+ Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged)
+ Q_PROPERTY(WidgetSize size READ size WRITE setSize NOTIFY sizeChanged)
+ Q_PROPERTY(WidgetPosition position READ position WRITE setPosition NOTIFY positionChanged)
+
+public:
+ explicit WidgetInfo(QObject *parent = 0);
+
+ enum WidgetSize
+ {
+ Size1Q = 0,
+ Size2QHorizontal = 1,
+ Size2QVertical = 2,
+ Size4Q = 3
+ };
+
+ enum WidgetPosition
+ {
+ PosNW = 0,
+ PosNE = 1,
+ PosSW = 2,
+ PosSE = 3
+ };
+
+ bool valid() const;
+
+ QUrl url() const;
+ void setUrl(const QUrl &url);
+
+ bool invert() const;
+ void setInvert(bool invert);
+
+ int page() const;
+ void setPage(int page);
+
+ WidgetSize size() const;
+ void setSize(const WidgetSize &size);
+
+ WidgetPosition position() const;
+ void setPosition(const WidgetPosition &pos);
+
+signals:
+ void validChanged();
+ void urlChanged();
+ void invertChanged();
+ void pageChanged();
+ void sizeChanged();
+ void positionChanged();
+
+private:
+ QUrl _url;
+ bool _invert;
+ short _page;
+ WidgetSize _size;
+ WidgetPosition _pos;
+
+};
+
+#endif // WIDGETINFO_H
diff --git a/src/widgetview.cpp b/src/widgetview.cpp
new file mode 100644
index 0000000..e5bf21e
--- /dev/null
+++ b/src/widgetview.cpp
@@ -0,0 +1,50 @@
+#include <QtQml/QQmlContext>
+#include <sailfishapp.h>
+
+#include "widgetview.h"
+
+#define NUM_WIDGETS 16
+
+WidgetView::WidgetView(const QString &settingsPrefix, QQuickView *view, QObject *parent)
+ : QObject(parent),
+ _settings(new MDConfGroup(this)),
+ _view(view)
+{
+ _settings->setPath(settingsPrefix);
+
+ if (!_view) {
+ _view = new QQuickView;
+ }
+
+ for (int w = 0; w < NUM_WIDGETS; w++) {
+ WidgetInfo *info = new WidgetInfo(this);
+
+ _widgets.append(info);
+ _widgetObjects.append(static_cast<QObject*>(info));
+ }
+
+ reload();
+
+ _view->setResizeMode(QQuickView::SizeViewToRootObject);
+ _view->setSource(SailfishApp::pathTo("qml/widgetview.qml"));
+ _view->rootContext()->setContextProperty("widgets", QVariant::fromValue(_widgetObjects));
+}
+
+QList<WidgetInfo*> WidgetView::widgets()
+{
+ return _widgets;
+}
+
+void WidgetView::reload()
+{
+ for (int w = 0; w < _widgets.size(); w++) {
+ WidgetInfo *widget = _widgets[w];
+ QString base = QString("widget%1/").arg(w);
+
+ widget->setInvert(_settings->value(base + "invert").toBool());
+ widget->setPage(_settings->value(base + "page").toInt());
+ widget->setSize(static_cast<WidgetInfo::WidgetSize>(_settings->value(base + "size").toInt()));
+ widget->setPosition(static_cast<WidgetInfo::WidgetPosition>(_settings->value(base + "position").toInt()));
+ widget->setUrl(_settings->value(base + "url").toUrl());
+ }
+}
diff --git a/src/widgetview.h b/src/widgetview.h
new file mode 100644
index 0000000..6ce294d
--- /dev/null
+++ b/src/widgetview.h
@@ -0,0 +1,30 @@
+#ifndef WIDGETVIEW_H
+#define WIDGETVIEW_H
+
+#include <QtCore/QObject>
+#include <QtCore/QVector>
+#include <QtQuick/QQuickView>
+
+#include <MDConfGroup>
+
+#include "widgetinfo.h"
+
+class WidgetView : public QObject
+{
+ Q_OBJECT
+public:
+ WidgetView(const QString &settingsPrefix, QQuickView *view, QObject *parent = 0);
+
+ QList<WidgetInfo*> widgets();
+
+public slots:
+ void reload();
+
+private:
+ MDConfGroup *_settings;
+ QQuickView *_view;
+ QList<WidgetInfo*> _widgets;
+ QObjectList _widgetObjects;
+};
+
+#endif // WIDGETVIEW_H
diff --git a/translations/salmeta.ts b/translations/salmeta.ts
new file mode 100644
index 0000000..0374c0e
--- /dev/null
+++ b/translations/salmeta.ts
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0">
+<context>
+ <name>CoverPage</name>
+ <message>
+ <source>My Cover</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>FirstPage</name>
+ <message>
+ <source>Show Page 2</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>UI Template</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Hello Sailors</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>SecondPage</name>
+ <message>
+ <source>Nested Page</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Item</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>