Merge pull request #17 from guh/improve-connect-dialog

improve discovery and connecttion
This commit is contained in:
Michael Zanetti 2018-06-04 12:24:12 +02:00 committed by GitHub
commit bd86599c16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 998 additions and 401 deletions

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@guh.io> *
* *
* This file is part of mea. *
* *
@ -22,38 +22,19 @@
#include <QUrl>
DiscoveryDevice::DiscoveryDevice()
DiscoveryDevice::DiscoveryDevice(QObject *parent): QObject(parent)
{
m_portConfigs = new PortConfigs(this);
}
QUrl DiscoveryDevice::location() const
QUuid DiscoveryDevice::uuid() const
{
return m_location;
return m_uuid;
}
void DiscoveryDevice::setLocation(const QUrl &location)
void DiscoveryDevice::setUuid(const QUuid &uuid)
{
m_location = location;
}
QString DiscoveryDevice::webSocketUrl() const
{
return m_webSocketUrl;
}
void DiscoveryDevice::setWebSocketUrl(const QString &webSocketUrl)
{
m_webSocketUrl = webSocketUrl;
}
QString DiscoveryDevice::nymeaRpcUrl() const
{
return m_nymeaRpcUrl;
}
void DiscoveryDevice::setNymeaRpcUrl(const QString &nymeaRpcUrl)
{
m_nymeaRpcUrl = nymeaRpcUrl;
m_uuid = uuid;
}
QHostAddress DiscoveryDevice::hostAddress() const
@ -61,116 +42,156 @@ QHostAddress DiscoveryDevice::hostAddress() const
return m_hostAddress;
}
void DiscoveryDevice::setHostAddress(const QHostAddress &hostAddress)
QString DiscoveryDevice::hostAddressString() const
{
m_hostAddress = hostAddress;
return m_hostAddress.toString();
}
int DiscoveryDevice::port() const
void DiscoveryDevice::setHostAddress(const QHostAddress &hostAddress)
{
if (m_hostAddress != hostAddress) {
m_hostAddress = hostAddress;
emit hostAddressChanged();
}
}
QString DiscoveryDevice::name() const
{
return m_name;
}
void DiscoveryDevice::setName(const QString &name)
{
if (m_name != name) {
m_name = name;
emit nameChanged();
}
}
QString DiscoveryDevice::version() const
{
return m_version;
}
void DiscoveryDevice::setVersion(const QString &version)
{
if (m_version != version) {
m_version = version;
emit versionChanged();
}
}
PortConfigs* DiscoveryDevice::portConfigs() const
{
return m_portConfigs;
}
QString DiscoveryDevice::toUrl(int portConfigIndex)
{
PortConfig *pc = m_portConfigs->get(portConfigIndex);
if (!pc) {
qWarning() << "No portconfig for index" << portConfigIndex;
}
QString ret = pc->protocol() == PortConfig::ProtocolNymeaRpc ? "nymea" : "ws";
ret += pc->sslEnabled() ? "s" : "";
ret += "://";
ret += m_hostAddress.toString();
ret += ":";
ret += QString::number(pc->port());
return ret;
}
PortConfigs::PortConfigs(QObject *parent): QAbstractListModel(parent)
{
}
int PortConfigs::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_portConfigs.count();
}
QVariant PortConfigs::data(const QModelIndex &index, int role) const
{
switch (role) {
case RolePort:
return m_portConfigs.at(index.row())->port();
case RoleProtocol:
return m_portConfigs.at(index.row())->protocol();
case RoleSSLEnabled:
return m_portConfigs.at(index.row())->sslEnabled();
}
return QVariant();
}
PortConfig *PortConfigs::find(int port)
{
foreach (PortConfig* pc, m_portConfigs) {
if (pc->port() == port) {
return pc;
}
}
return nullptr;
}
void PortConfigs::insert(PortConfig *portConfig)
{
portConfig->setParent(this);
beginInsertRows(QModelIndex(), m_portConfigs.count(), m_portConfigs.count());
m_portConfigs.append(portConfig);
endInsertRows();
emit countChanged();
}
PortConfig* PortConfigs::get(int index) const
{
return m_portConfigs.at(index);
}
QHash<int, QByteArray> PortConfigs::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RolePort, "port");
roles.insert(RoleProtocol, "protocol");
roles.insert(RoleSSLEnabled, "sslEnabled");
return roles;
}
PortConfig::PortConfig(int port, QObject *parent):
QObject(parent),
m_port(port)
{
}
int PortConfig::port() const
{
return m_port;
}
void DiscoveryDevice::setPort(const int &port)
PortConfig::Protocol PortConfig::protocol() const
{
m_port = port;
return m_protocol;
}
QString DiscoveryDevice::friendlyName() const
void PortConfig::setProtocol(PortConfig::Protocol protocol)
{
return m_friendlyName;
if (m_protocol != protocol) {
m_protocol = protocol;
emit protocolChanged();
}
}
void DiscoveryDevice::setFriendlyName(const QString &friendlyName)
bool PortConfig::sslEnabled() const
{
m_friendlyName = friendlyName;
return m_sslEnabled;
}
QString DiscoveryDevice::manufacturer() const
void PortConfig::setSslEnabled(bool sslEnabled)
{
return m_manufacturer;
}
void DiscoveryDevice::setManufacturer(const QString &manufacturer)
{
m_manufacturer = manufacturer;
}
QUrl DiscoveryDevice::manufacturerURL() const
{
return m_manufacturerURL;
}
void DiscoveryDevice::setManufacturerURL(const QUrl &manufacturerURL)
{
m_manufacturerURL = manufacturerURL;
}
QString DiscoveryDevice::modelDescription() const
{
return m_modelDescription;
}
void DiscoveryDevice::setModelDescription(const QString &modelDescription)
{
m_modelDescription = modelDescription;
}
QString DiscoveryDevice::modelName() const
{
return m_modelName;
}
void DiscoveryDevice::setModelName(const QString &modelName)
{
m_modelName = modelName;
}
QString DiscoveryDevice::modelNumber() const
{
return m_modelNumber;
}
void DiscoveryDevice::setModelNumber(const QString &modelNumber)
{
m_modelNumber = modelNumber;
}
QUrl DiscoveryDevice::modelURL() const
{
return m_modelURL;
}
void DiscoveryDevice::setModelURL(const QUrl &modelURL)
{
m_modelURL = modelURL;
}
QString DiscoveryDevice::uuid() const
{
return m_uuid;
}
void DiscoveryDevice::setUuid(const QString &uuid)
{
m_uuid = uuid;
}
QDebug operator<<(QDebug debug, const DiscoveryDevice &DiscoveryDevice)
{
debug << "----------------------------------------------\n";
debug << "UPnP device on " << QString("%1:%2").arg(DiscoveryDevice.hostAddress().toString()).arg(DiscoveryDevice.port()) << "\n";
debug << "location | " << DiscoveryDevice.location().toString() << "\n";
debug << "websocket | " << DiscoveryDevice.webSocketUrl() << "\n";
debug << "nymearpc | " << DiscoveryDevice.nymeaRpcUrl() << "\n";
debug << "friendly name | " << DiscoveryDevice.friendlyName() << "\n";
debug << "manufacturer | " << DiscoveryDevice.manufacturer() << "\n";
debug << "manufacturer URL | " << DiscoveryDevice.manufacturerURL().toString() << "\n";
debug << "model name | " << DiscoveryDevice.modelName() << "\n";
debug << "model number | " << DiscoveryDevice.modelNumber() << "\n";
debug << "model description | " << DiscoveryDevice.modelDescription() << "\n";
debug << "model URL | " << DiscoveryDevice.modelURL().toString() << "\n";
debug << "UUID | " << DiscoveryDevice.uuid() << "\n";
return debug;
if (m_sslEnabled != sslEnabled) {
m_sslEnabled = sslEnabled;
emit sslEnabledChanged();
}
}

View File

@ -1,20 +1,20 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@guh.io> *
* *
* This file is part of mea. *
* This file is part of mea. *
* *
* mea is free software: you can redistribute it and/or modify *
* mea is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 3 of the License. *
* *
* mea is distributed in the hope that it will be useful, *
* mea 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with mea. If not, see <http://www.gnu.org/licenses/>. *
* along with mea. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@ -22,72 +22,115 @@
#define DISCOVERYDEVICE_H
#include <QObject>
#include <QUuid>
#include <QUrl>
#include <QHostAddress>
#include <QObject>
#include <QAbstractListModel>
class DiscoveryDevice
class PortConfig: public QObject
{
Q_OBJECT
Q_PROPERTY(int port READ port CONSTANT)
Q_PROPERTY(Protocol protocol READ protocol NOTIFY protocolChanged)
Q_PROPERTY(bool sslEnabled READ sslEnabled NOTIFY sslEnabledChanged)
public:
explicit DiscoveryDevice();
QUrl location() const;
void setLocation(const QUrl &location);
QString webSocketUrl() const;
void setWebSocketUrl(const QString &webSocketUrl);
QString nymeaRpcUrl() const;
void setNymeaRpcUrl(const QString &nymeaRpcUrl);
QHostAddress hostAddress() const;
void setHostAddress(const QHostAddress &hostAddress);
enum Protocol {
ProtocolNymeaRpc,
ProtocolWebSocket
};
Q_ENUM(Protocol)
PortConfig(int port, QObject *parent = nullptr);
int port() const;
void setPort(const int &port);
QString deviceType() const;
void setDeviceType(const QString & deviceType);
Protocol protocol() const;
void setProtocol(Protocol protocol);
QString friendlyName() const;
void setFriendlyName(const QString &friendlyName);
bool sslEnabled() const;
void setSslEnabled(bool sslEnabled);
QString manufacturer() const;
void setManufacturer(const QString &manufacturer);
QUrl manufacturerURL() const;
void setManufacturerURL(const QUrl & manufacturerURL);
QString modelDescription() const;
void setModelDescription(const QString & modelDescription);
QString modelName() const;
void setModelName(const QString & modelName);
QString modelNumber() const;
void setModelNumber(const QString &modelNumber);
QUrl modelURL() const;
void setModelURL(const QUrl &modelURL);
QString uuid() const;
void setUuid(const QString &uuid);
signals:
void protocolChanged();
void sslEnabledChanged();
private:
QUrl m_location;
QString m_webSocketUrl;
QString m_nymeaRpcUrl;
QHostAddress m_hostAddress;
int m_port;
QString m_friendlyName;
QString m_manufacturer;
QUrl m_manufacturerURL;
QString m_modelDescription;
QString m_modelName;
QString m_modelNumber;
QUrl m_modelURL;
QString m_uuid;
int m_port = -1;
Protocol m_protocol = ProtocolNymeaRpc;
bool m_sslEnabled = false;
};
QDebug operator<< (QDebug debug, const DiscoveryDevice &discoveryDevice);
class PortConfigs: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RolePort,
RoleProtocol,
RoleSSLEnabled
};
Q_ENUM(Roles)
PortConfigs(QObject* parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
PortConfig* find(int port);
void insert(PortConfig* portConfig);
Q_INVOKABLE PortConfig *get(int index) const;
signals:
void countChanged();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QList<PortConfig*> m_portConfigs;
};
class DiscoveryDevice: public QObject
{
Q_OBJECT
Q_PROPERTY(QUuid uuid READ uuid CONSTANT)
Q_PROPERTY(QString hostAddress READ hostAddressString NOTIFY hostAddressChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString version READ version NOTIFY versionChanged)
Q_PROPERTY(PortConfigs* portConfigs READ portConfigs CONSTANT)
public:
explicit DiscoveryDevice(QObject *parent = nullptr);
QUuid uuid() const;
void setUuid(const QUuid &uuid);
QHostAddress hostAddress() const;
QString hostAddressString() const;
void setHostAddress(const QHostAddress &hostAddress);
QString name() const;
void setName(const QString &name);
QString version() const;
void setVersion(const QString &version);
PortConfigs *portConfigs() const;
Q_INVOKABLE QString toUrl(int portConfigIndex);
signals:
void nameChanged();
void hostAddressChanged();
void versionChanged();
private:
QUuid m_uuid;
QHostAddress m_hostAddress;
QString m_name;
QString m_version;
PortConfigs *m_portConfigs = nullptr;
};
#endif // DISCOVERYDEVICE_H

View File

@ -25,11 +25,6 @@ DiscoveryModel::DiscoveryModel(QObject *parent) :
{
}
QList<DiscoveryDevice> DiscoveryModel::devices()
{
return m_devices;
}
int DiscoveryModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
@ -41,61 +36,54 @@ QVariant DiscoveryModel::data(const QModelIndex &index, int role) const
if (index.row() < 0 || index.row() >= m_devices.count())
return QVariant();
DiscoveryDevice device = m_devices.at(index.row());
if (role == NameRole) {
return device.friendlyName();
} else if (role == HostAddressRole) {
return device.hostAddress().toString();
} else if (role == WebSocketUrlRole) {
return device.webSocketUrl();
} else if (role == PortRole) {
return device.port();
} else if (role == VersionRole) {
return device.modelNumber();
} else if (role == NymeaRpcUrlRole) {
return device.nymeaRpcUrl();
DiscoveryDevice *device = m_devices.at(index.row());
switch (role) {
case UuidRole:
return device->uuid();
case NameRole:
return device->name();
case HostAddressRole:
return device->hostAddress().toString();
// case WebSocketUrlRole:
// return device.webSocketUrl();
// case PortRole:
// return device.port();
case VersionRole:
return device->version();
// case NymeaRpcUrlRole:
// return device.nymeaRpcUrl();
}
return QVariant();
}
void DiscoveryModel::addDevice(const DiscoveryDevice &device)
void DiscoveryModel::addDevice(DiscoveryDevice *device)
{
for (int i = 0; i < m_devices.count(); i++) {
if (m_devices.at(i).uuid() == device.uuid()) {
m_devices[i] = device;
emit dataChanged(index(i), index(i));
if (m_devices.at(i)->uuid() == device->uuid()) {
qWarning() << "Device already added. Update existing device instead.";
return;
}
}
device->setParent(this);
beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count());
m_devices.append(device);
endInsertRows();
emit countChanged();
}
QString DiscoveryModel::get(int index, DeviceRole role) const
DiscoveryDevice *DiscoveryModel::get(int index) const
{
return data(this->index(index), role).toString();
return m_devices.at(index);
}
bool DiscoveryModel::contains(const QString &uuid) const
DiscoveryDevice *DiscoveryModel::find(const QUuid &uuid)
{
foreach (const DiscoveryDevice &dev, m_devices) {
if (dev.uuid() == uuid) {
return true;
}
}
return false;
}
DiscoveryDevice DiscoveryModel::find(const QHostAddress &address) const
{
foreach (const DiscoveryDevice &dev, m_devices) {
if (dev.hostAddress() == address) {
foreach (DiscoveryDevice *dev, m_devices) {
if (dev->uuid() == uuid) {
return dev;
}
}
return DiscoveryDevice();
return nullptr;
}
void DiscoveryModel::clearModel()
@ -109,11 +97,9 @@ void DiscoveryModel::clearModel()
QHash<int, QByteArray> DiscoveryModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[UuidRole] = "uuid";
roles[NameRole] = "name";
roles[HostAddressRole] = "hostAddress";
roles[WebSocketUrlRole] = "webSocketUrl";
roles[NymeaRpcUrlRole] = "nymeaRpcUrl";
roles[PortRole] = "port";
roles[VersionRole] = "version";
return roles;
}

View File

@ -32,27 +32,22 @@ class DiscoveryModel : public QAbstractListModel
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum DeviceRole {
UuidRole,
NameRole,
HostAddressRole,
WebSocketUrlRole,
NymeaRpcUrlRole,
PortRole,
VersionRole
};
Q_ENUM(DeviceRole)
explicit DiscoveryModel(QObject *parent = 0);
QList<DiscoveryDevice> devices();
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
void addDevice(const DiscoveryDevice &device);
void addDevice(DiscoveryDevice *device);
Q_INVOKABLE QString get(int index, DiscoveryModel::DeviceRole role) const;
bool contains(const QString &uuid) const;
DiscoveryDevice find(const QHostAddress &address) const;
Q_INVOKABLE DiscoveryDevice* get(int index) const;
Q_INVOKABLE DiscoveryDevice* find(const QUuid &uuid);
void clearModel();
@ -63,7 +58,7 @@ protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<DiscoveryDevice> m_devices;
QList<DiscoveryDevice*> m_devices;
};
#endif // DISCOVERYMODEL_H

View File

@ -114,7 +114,7 @@ void UpnpDiscovery::writeDiscoveryPacket()
"MX:2\r\n"
"ST: ssdp:all\r\n\r\n");
qDebug() << "sending discovery packet";
// qDebug() << "sending discovery packet";
writeDatagram(ssdpSearchMessage, m_host, m_port);
}
@ -170,97 +170,104 @@ void UpnpDiscovery::readData()
if (!m_foundDevices.contains(location) && isNymea) {
m_foundDevices.append(location);
DiscoveryDevice discoveryDevice;
discoveryDevice.setHostAddress(hostAddress);
discoveryDevice.setPort(port);
discoveryDevice.setLocation(location.toString());
qDebug() << "Getting server data from:" << location;
QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(location));
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors){
reply->ignoreSslErrors(errors);
});
m_runningReplies.insert(reply, discoveryDevice);
m_runningReplies.insert(reply, hostAddress);
}
}
}
void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
{
reply->deleteLater();
QHostAddress discoveredAddress = m_runningReplies.take(reply);
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() != QNetworkReply::NoError || status != 200) {
qWarning() << "Error fetching UPnP discovery data:" << status << reply->error() << reply->errorString();
return;
}
QByteArray data = reply->readAll();
DiscoveryDevice discoveryDevice = m_runningReplies.take(reply);
switch (status) {
case(200):{
// parse XML data
QXmlStreamReader xml(data);
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
QString name;
QString version;
QUuid uuid;
QList<PortConfig*> portConfigList;
if (xml.isStartDocument())
continue;
// parse XML data
QXmlStreamReader xml(data);
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name().toString() == "websocketURL") {
discoveryDevice.setWebSocketUrl(xml.readElementText());
}
if (xml.isStartDocument())
continue;
if (xml.isStartElement()) {
if (xml.name().toString() == "websocketURL") {
QUrl u(xml.readElementText());
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolWebSocket);
pc->setSslEnabled(u.scheme().endsWith('s'));
portConfigList.append(pc);
}
}
if (xml.isStartElement()) {
if (xml.name().toString() == "nymeaRpcURL") {
discoveryDevice.setNymeaRpcUrl(xml.readElementText());
}
if (xml.isStartElement()) {
if (xml.name().toString() == "nymeaRpcURL") {
QUrl u(xml.readElementText());
qDebug() << "have url" << u << u.scheme();
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolNymeaRpc);
pc->setSslEnabled(u.scheme().endsWith('s'));
portConfigList.append(pc);
}
}
if (xml.isStartElement()) {
if (xml.name().toString() == "device") {
while (!xml.atEnd()) {
if (xml.name() == "friendlyName" && xml.isStartElement()) {
discoveryDevice.setFriendlyName(xml.readElementText());
}
if (xml.name() == "manufacturer" && xml.isStartElement()) {
discoveryDevice.setManufacturer(xml.readElementText());
}
if (xml.name() == "manufacturerURL" && xml.isStartElement()) {
discoveryDevice.setManufacturerURL(QUrl(xml.readElementText()));
}
if (xml.name() == "modelDescription" && xml.isStartElement()) {
discoveryDevice.setModelDescription(xml.readElementText());
}
if (xml.name() == "modelName" && xml.isStartElement()) {
discoveryDevice.setModelName(xml.readElementText());
}
if (xml.name() == "modelNumber" && xml.isStartElement()) {
discoveryDevice.setModelNumber(xml.readElementText());
}
if (xml.name() == "modelURL" && xml.isStartElement()) {
discoveryDevice.setModelURL(QUrl(xml.readElementText()));
}
if (xml.name() == "UDN" && xml.isStartElement()) {
discoveryDevice.setUuid(xml.readElementText());
}
xml.readNext();
if (xml.isStartElement()) {
if (xml.name().toString() == "device") {
while (!xml.atEnd()) {
if (xml.name() == "friendlyName" && xml.isStartElement()) {
name = xml.readElementText();
}
if (xml.name() == "modelNumber" && xml.isStartElement()) {
version = xml.readElementText();
}
if (xml.name() == "UDN" && xml.isStartElement()) {
uuid = xml.readElementText().split(':').last();
}
xml.readNext();
}
xml.readNext();
}
}
}
qDebug() << "discovered device" << discoveryDevice.friendlyName() << discoveryDevice.hostAddress();
qDebug() << "discovered device" << uuid << name << discoveredAddress << version;
if (discoveryDevice.manufacturer().contains("guh")) {
if (!m_discoveryModel->contains(discoveryDevice.uuid())) {
m_discoveryModel->addDevice(discoveryDevice);
}
DiscoveryDevice* device = m_discoveryModel->find(uuid);
if (!device) {
device = new DiscoveryDevice(m_discoveryModel);
device->setUuid(uuid);
qDebug() << "Adding new host to model";
m_discoveryModel->addDevice(device);
}
device->setHostAddress(discoveredAddress);
device->setName(name);
device->setVersion(version);
foreach (PortConfig *pc, portConfigList) {
PortConfig *portConfig = device->portConfigs()->find(pc->port());
if (portConfig) {
qDebug() << "Updating port config" << portConfig->port() << portConfig->sslEnabled() << portConfig->protocol();
portConfig->setProtocol(pc->protocol());
portConfig->setSslEnabled(pc->sslEnabled());
pc->deleteLater();
} else {
qDebug() << "adding new port config" << pc->port() << pc->sslEnabled() << pc->protocol();
device->portConfigs()->insert(pc);
}
break;
}
default:
qWarning() << "HTTP request error " << status;
}
reply->deleteLater();
}

View File

@ -55,7 +55,7 @@ private:
bool m_discovering;
bool m_available;
QHash<QNetworkReply *, DiscoveryDevice> m_runningReplies;
QHash<QNetworkReply *, QHostAddress> m_runningReplies;
QList<QUrl> m_foundDevices;
void setDiscovering(const bool &discovering);

View File

@ -49,6 +49,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
QString uuid;
bool sslEnabled = false;
QString serverName;
QString version;
foreach (const QByteArray &key, entry.txt().keys()) {
QPair<QString, QString> txtRecord = qMakePair<QString, QString>(key, entry.txt().value(key));
if (!sslEnabled && txtRecord.first == "sslEnabled") {
@ -60,34 +61,30 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
if (txtRecord.first == "name") {
serverName = txtRecord.second;
}
if (txtRecord.first == "serverVersion") {
version = txtRecord.second;
}
}
qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
DiscoveryDevice dev = m_discoveryModel->find(entry.ip());
if (dev.uuid() == uuid && dev.nymeaRpcUrl().startsWith("nymeas") && !sslEnabled) {
// We already have this host and with a more secure configuration... skip this one...
return;
}
qDebug() << "Adding new found entry:" << entry.name() << entry.ip();
dev.setUuid(uuid);
dev.setHostAddress(entry.ip());
dev.setPort(entry.port());
dev.setFriendlyName(serverName + " on " + entry.ip().toString());
QHostAddress address = entry.ip();
QString addressString;
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
addressString = "[" + address.toString() + "]";
} else {
addressString = address.toString();
}
if (entry.type() == "_ws._tcp") {
dev.setWebSocketUrl(QString("%1://%2:%3").arg(sslEnabled ? "wss" : "ws").arg(addressString).arg(entry.port()));
} else {
dev.setNymeaRpcUrl(QString("%1://%2:%3").arg(sslEnabled ? "nymeas" : "nymea").arg(addressString).arg(entry.port()));
}
m_discoveryModel->addDevice(dev);
// DiscoveryDevice *dev = new DiscoveryDevice();
// dev->setFriendlyName(entry.hostName());
DiscoveryDevice* device = m_discoveryModel->find(uuid);
if (!device) {
device = new DiscoveryDevice(m_discoveryModel);
device->setUuid(uuid);
qDebug() << "Adding new host to model";
m_discoveryModel->addDevice(device);
}
device->setHostAddress(entry.ip());
device->setName(serverName);
device->setVersion(version);
PortConfig *portConfig = device->portConfigs()->find(entry.port());
if (!portConfig) {
qDebug() << "Adding new port config";
portConfig = new PortConfig(entry.port());
device->portConfigs()->insert(portConfig);
}
portConfig->setProtocol(entry.type() == "_ws._tcp" ? PortConfig::ProtocolWebSocket : PortConfig::ProtocolNymeaRpc);
portConfig->setSslEnabled(sslEnabled);
}
#endif

View File

@ -224,7 +224,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request)
{
QVariantMap newRequest = request;
newRequest.insert("token", m_token);
qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson());
m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson());
}
@ -259,7 +259,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
// qWarning() << "Could not parse json data from mea" << data << error.errorString();
return;
}
qDebug() << "received response" << m_receiveBuffer.left(splitIndex);
// qDebug() << "received response" << m_receiveBuffer.left(splitIndex);
m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1);
if (!m_receiveBuffer.isEmpty()) {
staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray()));

View File

@ -115,6 +115,9 @@ void registerQmlTypes() {
qmlRegisterType<NymeaDiscovery>(uri, 1, 0, "NymeaDiscovery");
qmlRegisterUncreatableType<DiscoveryModel>(uri, 1, 0, "DiscoveryModel", "Get it from NymeaDiscovery");
qmlRegisterUncreatableType<DiscoveryDevice>(uri, 1, 0, "DiscoveryDevice", "Get it from DiscoveryModel");
qmlRegisterUncreatableType<PortConfigs>(uri, 1, 0, "PortConfigs", "Get it from DiscoveryDevice");
qmlRegisterUncreatableType<PortConfig>(uri, 1, 0, "PortConfig", "Get it from DiscoveryDevice");
qmlRegisterType<EventDescriptorParamsFilterModel>(uri, 1, 0, "EventDescriptorParamsFilterModel");

View File

@ -13,7 +13,7 @@ win32:Debug:LIBS += -L$$top_builddir/libmea-core/debug \
win32:Release:LIBS += -L$$top_builddir/libmea-core/release \
-L$$top_builddir/libnymea-common/release
linux:!android:LIBS += -lavahi-client -lavahi-common
PRE_TARGETDEPS += ../libmea-core
HEADERS += \
stylecontroller.h

View File

@ -164,5 +164,7 @@
<file>ui/AppSettingsPage.qml</file>
<file>ui/images/stock_application.svg</file>
<file>ui/delegates/ThingDelegate.qml</file>
<file>ui/images/network-secure.svg</file>
<file>ui/images/lock-broken.svg</file>
</qresource>
</RCC>

View File

@ -1,7 +1,7 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.1
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.3
import Mea 1.0
import "components"
@ -24,9 +24,8 @@ Page {
target: Engine.connection
onVerifyConnectionCertificate: {
print("verify cert!")
certDialog.issuerInfo = issuerInfo
certDialog.fingerprint = fingerprint
certDialog.open();
var popup = certDialogComponent.createObject(app, {issuerInfo: issuerInfo, fingerprint: fingerprint});
popup.open();
}
onConnectionError: {
pageStack.pop(root)
@ -118,34 +117,81 @@ Page {
model: discovery.discoveryModel
clip: true
delegate: ItemDelegate {
delegate: SwipeDelegate {
width: parent.width
height: app.delegateHeight
objectName: "discoveryDelegate" + index
property string hostAddress: model.hostAddress
ColumnLayout {
anchors.fill: parent
anchors.margins: app.margins
Label {
text: model.name
contentItem: RowLayout {
ColumnLayout {
Layout.fillWidth: true
Label {
text: model.name
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
text: model.hostAddress
font.pixelSize: app.smallFont
}
}
Label {
text: model.hostAddress
font.pixelSize: app.smallFont
ColorIcon {
Layout.fillHeight: true
Layout.preferredWidth: height
name: "../images/network-secure.svg"
visible: {
var discoveryDevice = discovery.discoveryModel.get(index);
for (var i = 0; i < discoveryDevice.portConfigs.count; i++) {
if (discoveryDevice.portConfigs.get(i).sslEnabled) {
return true;
}
}
return false;
}
}
}
onClicked: {
var url;
if (model.nymeaRpcUrl) {
url = model.nymeaRpcUrl;
} else {
url = model.webSocketUrl;
}
print("Should connect to", url)
Engine.connection.connect(url)
onClicked: {
var discoveryDevice = discovery.discoveryModel.get(index);
print("discoveryDevice:", discoveryDevice.name, discoveryDevice.uuid, discoveryDevice.hostAddress)
var usedConfigIndex = 0;
for (var i = 1; i < discoveryDevice.portConfigs.count; i++) {
var oldConfig = discoveryDevice.portConfigs.get(usedConfigIndex);
var newConfig = discoveryDevice.portConfigs.get(i);
// prefer secure over insecure
if (!oldConfig.sslEnabled && newConfig.sslEnabled) {
usedConfigIndex = i;
continue;
}
if (oldConfig.sslEnabled && !newConfig.sslEnabled) {
continue; // discard new one as the one we already have is more secure
}
// both options are new either secure or insecure, prefer nymearpc over websocket for less overhead
if (oldConfig.protocol === PortConfig.ProtocolWebSocket && newConfig.protocol === PortConfig.ProtocolNymeaRpc) {
usedConfigIndex = i;
}
}
Engine.connection.connect(discoveryDevice.toUrl(usedConfigIndex))
pageStack.push(connectingPage)
}
swipe.right: MouseArea {
height: parent.height
width: height
anchors.right: parent.right
ColorIcon {
anchors.fill: parent
anchors.margins: app.margins
name: "../images/info.svg"
}
onClicked: {
swipe.close()
var popup = infoDialog.createObject(app,{discoveryDevice: discovery.discoveryModel.get(index)})
popup.open()
}
}
}
Column {
@ -317,82 +363,231 @@ Page {
}
}
Dialog {
id: certDialog
width: Math.min(parent.width * .9, 400)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
standardButtons: Dialog.Yes | Dialog.No
Component {
id: certDialogComponent
property var fingerprint
property var issuerInfo
Dialog {
id: certDialog
width: Math.min(parent.width * .9, 400)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
standardButtons: Dialog.Yes | Dialog.No
ColumnLayout {
anchors { left: parent.left; right: parent.right; top: parent.top }
spacing: app.margins
property var fingerprint
property var issuerInfo
RowLayout {
Layout.fillWidth: true
spacing: app.margins
ColorIcon {
Layout.preferredHeight: app.iconSize * 2
Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg"
color: app.guhAccent
ColumnLayout {
id: certLayout
anchors.fill: parent
// spacing: app.margins
RowLayout {
Layout.fillWidth: true
spacing: app.margins
ColorIcon {
Layout.preferredHeight: app.iconSize * 2
Layout.preferredWidth: height
name: "../images/dialog-warning-symbolic.svg"
color: app.guhAccent
}
Label {
id: titleLabel
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Warning")
color: app.guhAccent
font.pixelSize: app.largeFont
}
}
Label {
id: titleLabel
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Warning")
color: app.guhAccent
font.pixelSize: app.largeFont
text: qsTr("The authenticity of this %1 box cannot be verified.").arg(app.systemName)
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("If this is the first time you connect to this box, this is expected. Once you trust a box, you should never see this message again for that one. If you see this message multiple times for the same box, something suspicious is going on!")
}
ThinDivider {}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: certGridLayout.implicitHeight
Flickable {
anchors.fill: parent
contentHeight: certGridLayout.implicitHeight
clip: true
GridLayout {
id: certGridLayout
columns: 2
width: parent.width
Repeater {
model: certDialog.issuerInfo
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: modelData
}
}
Label {
Layout.fillWidth: true
Layout.columnSpan: 2
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: qsTr("Fingerprint: ") + certDialog.fingerprint
}
}
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Do you want to trust this device?")
font.bold: true
}
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("The authenticity of this %1 box cannot be verified.").arg(app.systemName)
onAccepted: {
Engine.connection.acceptCertificate(certDialog.fingerprint)
}
}
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("If this is the first time you connect to this box, this is expected. Once you trust a box, you should never see this message again for that one. If you see this message multiple times for the same box, something suspicious is going on!")
}
GridLayout {
columns: 2
Component {
id: infoDialog
Dialog {
id: dialog
width: Math.min(parent.width, contentGrid.implicitWidth)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
title: qsTr("Box information")
Repeater {
model: certDialog.issuerInfo
standardButtons: Dialog.Ok
property var discoveryDevice: null
header: Item {
implicitHeight: headerRow.height + app.margins * 2
implicitWidth: parent.width
RowLayout {
id: headerRow
anchors { left: parent.left; right: parent.right; top: parent.top; margins: app.margins }
spacing: app.margins
ColorIcon {
Layout.preferredHeight: app.iconSize * 2
Layout.preferredWidth: height
name: "../images/info.svg"
color: app.guhAccent
}
Label {
id: titleLabel
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: modelData
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: dialog.title
color: app.guhAccent
font.pixelSize: app.largeFont
}
}
}
Label {
Layout.fillWidth: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: qsTr("Fingerprint: ") + certDialog.fingerprint
}
GridLayout {
id: contentGrid
anchors.fill: parent
rowSpacing: app.margins
columns: 2
Label {
text: "Name:"
}
Label {
text: dialog.discoveryDevice.name
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
text: "UUID:"
}
Label {
text: dialog.discoveryDevice.uuid
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
text: "Version:"
}
Label {
text: dialog.discoveryDevice.version
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
text: "IP Address:"
}
Label {
text: dialog.discoveryDevice.hostAddress
Layout.fillWidth: true
elide: Text.ElideRight
}
ThinDivider { Layout.columnSpan: 2 }
Label {
Layout.columnSpan: 2
text: qsTr("Available connections")
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Do you want to trust this device?")
font.bold: true
}
}
Flickable {
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.preferredHeight: 200
contentHeight: contentColumn.implicitHeight
clip: true
ColumnLayout {
id: contentColumn
width: parent.width
Repeater {
model: dialog.discoveryDevice.portConfigs
ItemDelegate {
Layout.fillWidth: true
contentItem: RowLayout {
ColumnLayout {
Layout.fillWidth: true
Label {
text: qsTr("Port: %1").arg(model.port)
}
Label {
text: model.protocol === PortConfig.ProtocolNymeaRpc ? "nymea-rpc" : "websocket"
Layout.fillWidth: true
font.pixelSize: app.smallFont
}
}
onAccepted: {
Engine.connection.acceptCertificate(certDialog.fingerprint)
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: height
name: model.sslEnabled ? "../images/network-secure.svg" : "../images/lock-broken.svg"
color: model.sslEnabled ? app.guhAccent : "red"
}
}
onClicked: {
Engine.connection.connect(dialog.discoveryDevice.toUrl(index))
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="lock-broken.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199993"
inkscape:cx="-18.558726"
inkscape:cy="57.553367"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 393.98438,425.36133 -2,0 -46.01758,0 0,-64 48.01758,0 0,64 z m -4,-4 0,-55.99805 -40.01758,0 0,55.99805 40.01758,0 z"
id="rect4269"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 48 4 C 34.18438 4 22.998047 15.180068 22.998047 28.990234 L 22.998047 45 L 27 45 L 27 28.990234 C 27 17.326469 36.33162 7.9980469 48 7.9980469 C 56.909544 7.9980469 64.43796 13.446423 67.507812 21.210938 L 71.169922 19.642578 C 67.468388 10.473439 58.504779 4 48 4 z M 68.998047 34 L 68.998047 45 L 73 45 L 73 34 L 68.998047 34 z "
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
id="rect4271" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.5999999;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 364.03516,387.09375 -16.96289,8.47852 1.78906,3.58007 15.04883,-7.52148 14.06836,8.03516 14.99804,-8.56641 -1.98437,-3.47461 -13.01367,7.43359 -13.94336,-7.96484 z"
id="path4179"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="lock.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199992"
inkscape:cx="5.4626288"
inkscape:cy="25.355855"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 393.98438,425.36133 -2,0 -46.01758,0 0,-64 48.01758,0 0,64 z m -4,-4 0,-55.99805 -40.01758,0 0,55.99805 40.01758,0 z"
id="rect4269"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 434.00087,393.36222 c 0,13.81562 -11.1845,25.00195 -25.00013,25.00195 l -16.0161,0 0,-4.00195 16.0161,0 c 11.66838,0 21.0005,-9.33162 21.0005,-21 0,-11.66838 -9.33212,-20.99805 -21.0005,-20.99805 l -16.0161,0 0,-4.00195 16.0161,0 c 13.81563,0 25.00013,11.18438 25.00013,25 z"
id="rect4271"
inkscape:connector-curvature="0" />
<path
transform="matrix(0,-1,-1,0,0,0)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
d="m -397.36221,-377.9787 8,-2.00079 0,20.00794 -8,0 z"
id="rect4280"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB