/* * This file is part of qtzeroconf. (c) 2012 Johannes Hilden * https://github.com/johanneshilden/qtzeroconf * * Modified: (C) 2016 Simon Stürz * * 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 2.1 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include "zconfservicebrowser.h" #include "zconfserviceclient.h" class ZConfServiceBrowserPrivate { public: ZConfServiceBrowserPrivate(ZConfServiceClient *client) : client(client), browser(0) { } static void callback(AvahiServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void *userdata) { Q_UNUSED(browser); Q_UNUSED(flags); ZConfServiceBrowser *serviceBrowser = static_cast(userdata); if (serviceBrowser) { switch (event) { case AVAHI_BROWSER_FAILURE: qDebug() << ("Avahi browser error: " % QString(avahi_strerror(avahi_client_errno(serviceBrowser->d_ptr->client->client)))); emit serviceBrowser->serviceBrowsingFinished(); break; case AVAHI_BROWSER_NEW: qDebug() << ("New service '" % QString(name) % "' of type " % QString(type) % " in domain " % QString(domain) % "."); // We ignore the returned resolver object. In the callback // function we free it. If the server is terminated before // the callback function is called the server will free // the resolver for us. if (!(avahi_service_resolver_new(serviceBrowser->d_ptr->client->client, interface, protocol, name, serviceBrowser->d_ptr->type.toLatin1().data(), domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags) 0, ZConfServiceBrowserPrivate::resolve, serviceBrowser))) qDebug() << ("Failed to resolve service '" % QString(name) % "': " % avahi_strerror(avahi_client_errno(serviceBrowser->d_ptr->client->client))); break; case AVAHI_BROWSER_REMOVE: serviceBrowser->d_ptr->entries.remove(name); emit serviceBrowser->serviceEntryRemoved(name); break; case AVAHI_BROWSER_ALL_FOR_NOW: emit serviceBrowser->serviceBrowsingFinished(); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: break; } } } static void resolve(AvahiServiceResolver *resolver, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { Q_UNUSED(interface); Q_UNUSED(type); Q_UNUSED(txt); ZConfServiceBrowser *serviceBrowser = static_cast(userdata); if (serviceBrowser) { switch (event) { case AVAHI_RESOLVER_FAILURE: qDebug() << ("Failed to resolve service '" % QString(name) % "': " % avahi_strerror(avahi_client_errno(serviceBrowser->d_ptr->client->client))); break; case AVAHI_RESOLVER_FOUND: { char a[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(a, sizeof(a), address); // convert protocol QAbstractSocket::NetworkLayerProtocol p; if (protocol == AVAHI_PROTO_INET) { p = QAbstractSocket::IPv4Protocol; } else if (protocol == AVAHI_PROTO_INET6) { p = QAbstractSocket::IPv6Protocol; } else { p = QAbstractSocket::UnknownNetworkLayerProtocol; } QStringList txtList; // TODO: get txt string list // // get txt string list // if (txt) { // AvahiStringList *txtAtt = txt->text; // txtAtt = (txt ? txt->next : 0); // while (txtAtt) { // qDebug() << txt; // txtList.append(QString(txt->)); // } // } AvahiServiceEntry entry = AvahiServiceEntry(name, QHostAddress(QString(a)), QString(domain), QString(host_name), (quint16)port, p, txtList, flags); serviceBrowser->d_ptr->entries.insert(name, entry); emit serviceBrowser->serviceEntryAdded(name); } } avahi_service_resolver_free(resolver); } } typedef QHash ZConfServiceEntryTable; ZConfServiceClient *const client; AvahiServiceBrowser *browser; ZConfServiceEntryTable entries; QString type; }; /*! \class ZConfServiceBrowser \ingroup hardware \inmodule libguh \brief AvahiServiceBrowser wrapper that lets you browse for services available on the local network. This class can be used to handle Zeroconf service discovery in a Qt-based client application. Instantiate a ZConfServiceBrowser object and call browse() with the desired service type as argument (e.g., "_http._tcp" or "_ipp._tcp"). ZConfServiceBrowser will emit serviceEntryAdded() when a new service is discovered and serviceEntryRemoved() when a service is removed from the network. */ /*! \fn void ZConfServiceBrowser::serviceEntryAdded(const QString &name); This signal will be emited when a new service entry with the given \a name was added in the network. */ /*! \fn void ZConfServiceBrowser::serviceEntryRemoved(const QString &name); This signal will be emited when a new service entry with the given \a name was removed from the network. */ /*! Creates a Zeroconf service browser with the given \a parent. Call browse() to start browsing for services. */ ZConfServiceBrowser::ZConfServiceBrowser(QObject *parent) : QObject(parent), d_ptr(new ZConfServiceBrowserPrivate(new ZConfServiceClient(this))) { connect(d_ptr->client, SIGNAL(clientRunning()), this, SLOT(createServiceBrowser())); } /*! Destroys the browser object and releases all resources associated with it. */ ZConfServiceBrowser::~ZConfServiceBrowser() { if (d_ptr->browser) avahi_service_browser_free(d_ptr->browser); delete d_ptr; } /*! Browses for Zeroconf services of the given \a serviceType on the LAN. This is a non-blocking call. ZConfServiceBrowser will emit serviceEntryAdded() when a new service is discovered and serviceEntryRemoved() when a service is removed from the network. */ void ZConfServiceBrowser::browse(QString serviceType) { d_ptr->type = serviceType; if(d_ptr->client) d_ptr->client->run(); } /*! Returns a ZConfServiceEntry struct with detailed information about the Zeroconf service associated with the \a name. */ AvahiServiceEntry ZConfServiceBrowser::serviceEntry(QString name) { return d_ptr->entries.value(name); } void ZConfServiceBrowser::createServiceBrowser() { if (d_ptr->browser) return; d_ptr->browser = avahi_service_browser_new(d_ptr->client->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, d_ptr->type.toLatin1().data(), NULL, (AvahiLookupFlags) 0, ZConfServiceBrowserPrivate::callback, this); }