diff --git a/QZeroConfNsdManager.java b/QZeroConfNsdManager.java
new file mode 100644
index 0000000..8164bba
--- /dev/null
+++ b/QZeroConfNsdManager.java
@@ -0,0 +1,180 @@
+/**************************************************************************************************
+---------------------------------------------------------------------------------------------------
+ Copyright (C) 2015 Jonathan Bagg
+ This file is part of QtZeroConf.
+
+ QtZeroConf is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QtZeroConf is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with QtZeroConf. If not, see .
+---------------------------------------------------------------------------------------------------
+ Project name : QtZeroConf
+ File name : QZeroConfNsdManager.java
+ Created : 10 Spetember 2021
+ Author(s) : Michael Zanetti
+---------------------------------------------------------------------------------------------------
+ NsdManager wrapper for use on Android devices
+---------------------------------------------------------------------------------------------------
+**************************************************************************************************/
+
+package qtzeroconf;
+
+import java.util.Map;
+
+import android.util.Log;
+
+import android.content.Context;
+
+import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.NsdManager;
+
+public class QZeroConfNsdManager {
+
+ public static native void onServiceResolvedJNI(int id, String name, String type, String hostname, String address, int port, Map txtRecords);
+ public static native void onServiceRemovedJNI(int id, String name);
+ public static native void onBrowserStateChangedJNI(int id, boolean running, boolean error);
+ public static native void onPublisherStateChangedJNI(int id, boolean running, boolean error);
+ public static native void onServiceNameChangedJNI(int id, String newName);
+
+ private static String TAG = "QZeroConfNsdManager";
+ private int id;
+ private Context context;
+ private NsdManager nsdManager;
+ private NsdManager.DiscoveryListener discoveryListener;
+ private NsdManager.RegistrationListener registrationListener;
+ private String registrationName; // The original service name that was given for registration, it might change on collisions
+
+ public QZeroConfNsdManager(int id, Context context) {
+ super();
+ this.id = id;
+ this.context = context;
+
+ nsdManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE);
+ discoveryListener = initializeDiscoveryListener();
+ registrationListener = initializeRegistrationListener();
+ }
+
+ public void registerService(String name, String type, int port, Map txtRecords) {
+ registrationName = name;
+
+ NsdServiceInfo serviceInfo = new NsdServiceInfo();
+ serviceInfo.setServiceName(name);
+ serviceInfo.setServiceType(type);
+ serviceInfo.setPort(port);
+ for (Map.Entry entry: txtRecords.entrySet()) {
+ serviceInfo.setAttribute(entry.getKey(), entry.getValue());
+ }
+
+ try {
+ nsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Error registering service: " + e.toString());
+ onPublisherStateChangedJNI(id, false, true);
+ }
+ }
+
+ public void unregisterService() {
+ nsdManager.unregisterService(registrationListener);
+ }
+
+ public void discoverServices(String serviceType) {
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
+ }
+
+ public void stopServiceDiscovery() {
+ nsdManager.stopServiceDiscovery(discoveryListener);
+ }
+
+ private NsdManager.DiscoveryListener initializeDiscoveryListener() {
+ return new NsdManager.DiscoveryListener() {
+
+ @Override
+ public void onDiscoveryStarted(String regType) {
+ QZeroConfNsdManager.onBrowserStateChangedJNI(id, true, false);
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo service) {
+ nsdManager.resolveService(service, initializeResolveListener());
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ QZeroConfNsdManager.onServiceRemovedJNI(id, serviceInfo.getServiceName());
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ QZeroConfNsdManager.onBrowserStateChangedJNI(id, false, false);
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ QZeroConfNsdManager.onBrowserStateChangedJNI(id, false, true);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ QZeroConfNsdManager.onBrowserStateChangedJNI(id, false, true);
+ }
+ };
+ }
+
+ private NsdManager.ResolveListener initializeResolveListener() {
+ return new NsdManager.ResolveListener() {
+
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ Log.d(TAG, "Resolving failed for: " + serviceInfo.getServiceName() + " " + serviceInfo.getServiceType() + ": " + errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
+ QZeroConfNsdManager.onServiceResolvedJNI(id,
+ serviceInfo.getServiceName(),
+ serviceInfo.getServiceType(),
+ serviceInfo.getHost().getHostName(),
+ serviceInfo.getHost().getHostAddress(),
+ serviceInfo.getPort(),
+ serviceInfo.getAttributes()
+ );
+ }
+ };
+ }
+
+ public NsdManager.RegistrationListener initializeRegistrationListener() {
+ return new NsdManager.RegistrationListener() {
+
+ @Override
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+ QZeroConfNsdManager.onPublisherStateChangedJNI(id, true, false);
+ if (!serviceInfo.getServiceName().equals(registrationName)) {
+ onServiceNameChangedJNI(id, serviceInfo.getServiceName());
+ }
+ }
+
+ @Override
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ QZeroConfNsdManager.onPublisherStateChangedJNI(id, false, true);
+ }
+
+ @Override
+ public void onServiceUnregistered(NsdServiceInfo arg0) {
+ QZeroConfNsdManager.onPublisherStateChangedJNI(id, false, false);
+ }
+
+ @Override
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ QZeroConfNsdManager.onPublisherStateChangedJNI(id, false, true);
+ }
+ };
+ }
+}
diff --git a/androidnsd.cpp b/androidnsd.cpp
new file mode 100644
index 0000000..9f24c38
--- /dev/null
+++ b/androidnsd.cpp
@@ -0,0 +1,322 @@
+/**************************************************************************************************
+---------------------------------------------------------------------------------------------------
+ Copyright (C) 2015-2021 Jonathan Bagg
+ This file is part of QtZeroConf.
+
+ QtZeroConf is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QtZeroConf is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with QtZeroConf. If not, see .
+---------------------------------------------------------------------------------------------------
+ Project name : QtZeroConf
+ File name : androidnsd.cpp
+ Created : 10 Spetember 2021
+ Author(s) : Michael Zanetti
+---------------------------------------------------------------------------------------------------
+ NsdManager wrapper for use on Android devices
+---------------------------------------------------------------------------------------------------
+**************************************************************************************************/
+#include "androidnsd_p.h"
+
+Q_DECLARE_METATYPE(QHostAddress)
+
+static QMutex s_instancesMutex;
+static QList s_instances;
+
+
+QZeroConfPrivate::QZeroConfPrivate(QZeroConf *parent)
+{
+ qRegisterMetaType();
+ qRegisterMetaType("TxtRecordMap");
+
+ pub = parent;
+
+ QAndroidJniEnvironment env;
+
+ JNINativeMethod methods[] {
+ { "onServiceResolvedJNI", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/util/Map;)V", (void*)QZeroConfPrivate::onServiceResolvedJNI },
+ { "onServiceRemovedJNI", "(ILjava/lang/String;)V", (void*)QZeroConfPrivate::onServiceRemovedJNI },
+ { "onBrowserStateChangedJNI", "(IZZ)V", (void*)QZeroConfPrivate::onBrowserStateChangedJNI },
+ { "onPublisherStateChangedJNI", "(IZZ)V", (void*)QZeroConfPrivate::onPublisherStateChangedJNI },
+ { "onServiceNameChangedJNI", "(ILjava/lang/String;)V", (void*)QZeroConfPrivate::onServiceNameChangedJNI }
+ };
+
+ // Passing "this" as ID down to Java so we can access "this" in callbacks.
+ // There seems to be no straight forward way to match the "thiz" pointer from JNI calls to our pointer of the Java class
+ nsdManager = QAndroidJniObject("qtzeroconf/QZeroConfNsdManager", "(ILandroid/content/Context;)V", reinterpret_cast(this), QtAndroid::androidActivity().object());
+ if (nsdManager.isValid()) {
+ jclass objectClass = env->GetObjectClass(nsdManager.object());
+ env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0]));
+ env->DeleteLocalRef(objectClass);
+ }
+
+ QMutexLocker locker(&s_instancesMutex);
+ s_instances.append(this);
+}
+
+QZeroConfPrivate::~QZeroConfPrivate()
+{
+ QMutexLocker locker(&s_instancesMutex);
+ s_instances.removeAll(this);
+}
+
+// In order to not having to pay attention to only use thread safe methods on the java side, we're only running
+// Java calls on the Android thread.
+// To make sure the Java object is not going out of scope and being garbage collected when the QZeroConf object
+// is deleted before the worker thread actually starts, keep a new QAndroidJniObject to nsdManager
+// which will increase the ref counter in the JVM.
+void QZeroConfPrivate::startServicePublish(const char *name, const char *type, quint16 port)
+{
+ QAndroidJniObject ref(nsdManager);
+ QtAndroid::runOnAndroidThread([=](){
+ QAndroidJniObject txtMap("java/util/HashMap");
+ foreach (const QByteArray &key, txtRecords.keys()) {
+ txtMap.callObjectMethod("put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+ QAndroidJniObject::fromString(key).object(),
+ QAndroidJniObject::fromString(txtRecords.value(key)).object());
+ }
+
+ ref.callMethod("registerService", "(Ljava/lang/String;Ljava/lang/String;ILjava/util/Map;)V",
+ QAndroidJniObject::fromString(QString(name)).object(),
+ QAndroidJniObject::fromString(QString(type)).object(),
+ port,
+ txtMap.object());
+ });
+}
+
+void QZeroConfPrivate::stopServicePublish()
+{
+ QAndroidJniObject ref(nsdManager);
+ QtAndroid::runOnAndroidThread([ref]() {
+ ref.callMethod("unregisterService");
+ });
+}
+
+void QZeroConfPrivate::startBrowser(QString type, QAbstractSocket::NetworkLayerProtocol protocol)
+{
+ Q_UNUSED(protocol)
+ QAndroidJniObject ref(nsdManager);
+ QtAndroid::runOnAndroidThread([ref, type]() {
+ ref.callMethod("discoverServices", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(type).object());
+ });
+}
+
+void QZeroConfPrivate::stopBrowser()
+{
+ QAndroidJniObject ref(nsdManager);
+ QtAndroid::runOnAndroidThread([ref]() {
+ ref.callMethod("stopServiceDiscovery");
+ });
+}
+
+// Callbacks will come in from the android thread. So we're never accessing any of our members directly but instead
+// propagate callbacks through Qt::QueuedConnection invokes into the Qt thread. Be sure to check if the instance is still
+// alive by checking s_instances while holding the mutex before scheduling the invokation.
+void QZeroConfPrivate::onServiceResolvedJNI(JNIEnv */*env*/, jobject /*thiz*/, jint id, jstring name, jstring type, jstring hostname, jstring address, jint port, jobject txtRecords)
+{
+ QMap txtMap;
+ QAndroidJniObject txt(txtRecords);
+ QAndroidJniObject txtKeys = txt.callObjectMethod("keySet", "()Ljava/util/Set;").callObjectMethod("toArray", "()[Ljava/lang/Object;");
+
+ QAndroidJniEnvironment env;
+ for (int i = 0; i < txt.callMethod("size"); i++) {
+ QAndroidJniObject key = QAndroidJniObject(env->GetObjectArrayElement(txtKeys.object(), i));
+ QAndroidJniObject valueObj = txt.callObjectMethod("get", "(Ljava/lang/Object;)Ljava/lang/Object;", key.object());
+ if (valueObj.isValid()) {
+ jboolean isCopy;
+ jbyte* b = env->GetByteArrayElements(valueObj.object(), &isCopy);
+ QByteArray value((char *)b, env->GetArrayLength(valueObj.object()));
+ env->ReleaseByteArrayElements(valueObj.object(), b, JNI_ABORT);
+ txtMap.insert(key.toString().toUtf8(), value);
+ } else {
+ txtMap.insert(key.toString().toUtf8(), QByteArray());
+ }
+ }
+
+ QZeroConfPrivate *ref = reinterpret_cast(id);
+ QMutexLocker locker(&s_instancesMutex);
+ if (!s_instances.contains(ref)) {
+ return;
+ }
+ QMetaObject::invokeMethod(ref, "onServiceResolved", Qt::QueuedConnection,
+ Q_ARG(QString, QAndroidJniObject(name).toString()),
+ Q_ARG(QString, QAndroidJniObject(type).toString()),
+ Q_ARG(QString, QAndroidJniObject(hostname).toString()),
+ Q_ARG(QHostAddress, QHostAddress(QAndroidJniObject(address).toString())),
+ Q_ARG(int, port),
+ Q_ARG(TxtRecordMap, txtMap)
+ );
+
+}
+
+void QZeroConfPrivate::onServiceRemovedJNI(JNIEnv */*env*/, jobject /*this*/, jint id, jstring name)
+{
+ QZeroConfPrivate *ref = reinterpret_cast(id);
+ QMutexLocker locker(&s_instancesMutex);
+ if (!s_instances.contains(ref)) {
+ return;
+ }
+ QMetaObject::invokeMethod(ref, "onServiceRemoved", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject(name).toString()));
+}
+
+
+void QZeroConfPrivate::onBrowserStateChangedJNI(JNIEnv */*env*/, jobject /*thiz*/, jint id, jboolean running, jboolean error)
+{
+ QZeroConfPrivate *ref = reinterpret_cast(id);
+ QMutexLocker locker(&s_instancesMutex);
+ if (!s_instances.contains(ref)) {
+ return;
+ }
+ QMetaObject::invokeMethod(ref, "onBrowserStateChanged", Qt::QueuedConnection, Q_ARG(bool, running), Q_ARG(bool, error));
+}
+
+void QZeroConfPrivate::onPublisherStateChangedJNI(JNIEnv */*env*/, jobject /*this*/, jint id, jboolean running, jboolean error)
+{
+ QZeroConfPrivate *ref = reinterpret_cast(id);
+ QMutexLocker locker(&s_instancesMutex);
+ if (!s_instances.contains(ref)) {
+ return;
+ }
+ QMetaObject::invokeMethod(ref, "onPublisherStateChanged", Qt::QueuedConnection, Q_ARG(bool, running), Q_ARG(bool, error));
+}
+
+void QZeroConfPrivate::onServiceNameChangedJNI(JNIEnv */*env*/, jobject /*thiz*/, jint id, jstring newName)
+{
+ QZeroConfPrivate *ref = reinterpret_cast(id);
+ QMutexLocker locker(&s_instancesMutex);
+ if (!s_instances.contains(ref)) {
+ return;
+ }
+ QMetaObject::invokeMethod(ref, "onServiceNameChanged", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject(newName).toString()));
+}
+
+void QZeroConfPrivate::onServiceResolved(const QString &name, const QString &type, const QString &hostname, const QHostAddress &address, int port, const TxtRecordMap &txtRecords)
+{
+ QZeroConfService zcs;
+ bool newRecord = false;
+ if (pub->services.contains(name)) {
+ zcs = pub->services.value(name);
+ } else {
+ zcs = QZeroConfService(new QZeroConfServiceData);
+ newRecord = true;
+ }
+
+ zcs->m_name = name;
+ zcs->m_type = type;
+ // A previous implementation (based on avahi) returned service type as "_http._tcp" but Android API return "._http._tcp"
+ // Stripping leading dot for backwards compatibility. FIXME: Still not in line with bonjour, which adds a trailing dot.
+ zcs->m_type.remove(QRegExp("^."));
+ zcs->m_host = hostname;
+ zcs->m_port = port;
+ zcs->m_ip = address;
+ zcs->m_txt = txtRecords;
+
+ // Those are not available on Androids NsdManager
+ // zcs->m_domain = domain;
+ // zcs->m_interfaceIndex = interface;
+
+ if (newRecord) {
+ pub->services.insert(name, zcs);
+ emit pub->serviceAdded(zcs);
+ } else {
+ emit pub->serviceUpdated(zcs);
+ }
+}
+
+void QZeroConfPrivate::onServiceRemoved(const QString &name)
+{
+ if (pub->services.contains(name)) {
+ QZeroConfService service = pub->services.take(name);
+ emit pub->serviceRemoved(service);
+ }
+}
+
+void QZeroConfPrivate::onBrowserStateChanged(bool running, bool error)
+{
+ browserExists = running;
+ if (error) {
+ emit pub->error(QZeroConf::browserFailed);
+ }
+}
+
+void QZeroConfPrivate::onPublisherStateChanged(bool running, bool error)
+{
+ publisherExists = running;
+ if (running) {
+ emit pub->servicePublished();
+ }
+ if (error) {
+ emit pub->error(QZeroConf::serviceRegistrationFailed);
+ }
+}
+
+void QZeroConfPrivate::onServiceNameChanged(const QString &newName)
+{
+ emit pub->serviceNameChanged(newName);
+}
+
+
+QZeroConf::QZeroConf(QObject *parent) : QObject(parent)
+{
+ pri = new QZeroConfPrivate(this);
+ qRegisterMetaType("QZeroConfService");
+}
+
+QZeroConf::~QZeroConf()
+{
+ delete pri;
+}
+
+void QZeroConf::startServicePublish(const char *name, const char *type, const char *domain, quint16 port)
+{
+ Q_UNUSED(domain) // Not supported on Android API
+ pri->startServicePublish(name, type, port);
+}
+
+void QZeroConf::stopServicePublish(void)
+{
+ pri->stopServicePublish();
+}
+
+bool QZeroConf::publishExists(void)
+{
+ return pri->publisherExists;
+}
+
+void QZeroConf::addServiceTxtRecord(QString nameOnly)
+{
+ pri->txtRecords.insert(nameOnly.toUtf8(), QByteArray());
+}
+
+void QZeroConf::addServiceTxtRecord(QString name, QString value)
+{
+ pri->txtRecords.insert(name.toUtf8(), value.toUtf8());
+}
+
+void QZeroConf::clearServiceTxtRecords()
+{
+ pri->txtRecords.clear();
+}
+
+void QZeroConf::startBrowser(QString type, QAbstractSocket::NetworkLayerProtocol protocol)
+{
+ pri->startBrowser(type, protocol);
+}
+
+void QZeroConf::stopBrowser(void)
+{
+ pri->stopBrowser();
+}
+
+bool QZeroConf::browserExists(void)
+{
+ return pri->browserExists;
+}
diff --git a/androidnsd_p.h b/androidnsd_p.h
new file mode 100644
index 0000000..0e480fb
--- /dev/null
+++ b/androidnsd_p.h
@@ -0,0 +1,65 @@
+/**************************************************************************************************
+---------------------------------------------------------------------------------------------------
+ Copyright (C) 2015-2021 Jonathan Bagg
+ This file is part of QtZeroConf.
+
+ QtZeroConf is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ QtZeroConf is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with QtZeroConf. If not, see .
+---------------------------------------------------------------------------------------------------
+ Project name : QtZeroConf
+ File name : androidnsd_p.h
+ Created : 10 Spetember 2021
+ Author(s) : Michael Zanetti
+---------------------------------------------------------------------------------------------------
+ NsdManager wrapper for use on Android devices
+---------------------------------------------------------------------------------------------------
+**************************************************************************************************/
+#include "qzeroconf.h"
+
+#include
+#include
+#include
+
+class QZeroConfPrivate: QObject
+{
+ Q_OBJECT
+public:
+ typedef QMap TxtRecordMap;
+
+ QZeroConfPrivate(QZeroConf *parent);
+ ~QZeroConfPrivate();
+ void startServicePublish(const char *name, const char *type, quint16 port);
+ void stopServicePublish();
+ void startBrowser(QString type, QAbstractSocket::NetworkLayerProtocol protocol);
+ void stopBrowser();
+ static void onServiceResolvedJNI(JNIEnv */*env*/, jobject /*this*/, jint id, jstring name, jstring type, jstring hostname, jstring address, jint port, jobject txtRecords);
+ static void onServiceRemovedJNI(JNIEnv */*env*/, jobject /*this*/, jint id, jstring name);
+ static void onBrowserStateChangedJNI(JNIEnv */*env*/, jobject /*thiz*/, jint id, jboolean running, jboolean error);
+ static void onPublisherStateChangedJNI(JNIEnv */*env*/, jobject /*thiz*/, jint id, jboolean running, jboolean error);
+ static void onServiceNameChangedJNI(JNIEnv */*env*/, jobject /*thiz*/, jint id, jstring newName);
+
+ QZeroConf *pub;
+ QAndroidJniObject nsdManager;
+
+ bool browserExists = false;
+ bool publisherExists = false;
+ QMap txtRecords;
+
+
+private slots:
+ void onServiceResolved(const QString &name, const QString &type, const QString &hostname, const QHostAddress &address, int port, const TxtRecordMap &txtRecords);
+ void onServiceRemoved(const QString &name);
+ void onBrowserStateChanged(bool running, bool error);
+ void onPublisherStateChanged(bool running, bool error);
+ void onServiceNameChanged(const QString &newName);
+};
diff --git a/qtzeroconf.pri b/qtzeroconf.pri
index ab841b4..19fc37f 100644
--- a/qtzeroconf.pri
+++ b/qtzeroconf.pri
@@ -57,7 +57,7 @@ ios {
QMAKE_CXXFLAGS+= -I$$PWD
}
-ubports|android: {
+ubports: {
QMAKE_CXXFLAGS+= -I$$PWD
QMAKE_CFLAGS+= -I$$PWD
ACM = $$PWD/avahi-common
@@ -121,6 +121,13 @@ ubports|android: {
#avahi-core/iface-none.c avahi-core/iface-pfroute.c avahi-core/avahi-reflector.c
}
+android: {
+ QT += androidextras
+ HEADERS += $$PWD/qzeroconf.h $$PWD/androidnsd_p.h
+ SOURCES += $$PWD/androidnsd.cpp
+ DISTFILES += $$PWD/QZeroConfNsdManager.java
+}
+
HEADERS+= $$PWD/qzeroconfservice.h $$PWD/qzeroconfglobal.h
SOURCES+= $$PWD/qzeroconfservice.cpp
diff --git a/qzeroconf.h b/qzeroconf.h
index cbbbb0d..d252f29 100644
--- a/qzeroconf.h
+++ b/qzeroconf.h
@@ -66,6 +66,7 @@ public:
Q_SIGNALS:
void servicePublished(void);
+ void serviceNameChanged(const QString &newName);
void error(QZeroConf::error_t);
void serviceAdded(QZeroConfService);
void serviceUpdated(QZeroConfService);