src/gui/embedded/qcopchannel_qws.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/embedded/qcopchannel_qws.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,608 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qcopchannel_qws.h"
+
+#ifndef QT_NO_COP
+
+#include "qwsdisplay_qws.h"
+#include "qwscommand_qws_p.h"
+#include "qwindowsystem_qws.h"
+#include "qwindowsystem_p.h"
+#include "qlist.h"
+#include "qmap.h"
+#include "qdatastream.h"
+#include "qpointer.h"
+#include "qmutex.h"
+
+#include "qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+typedef QMap<QString, QList<QWSClient*> > QCopServerMap;
+static QCopServerMap *qcopServerMap = 0;
+
+class QCopServerRegexp
+{
+public:
+    QCopServerRegexp( const QString& channel, QWSClient *client );
+    QCopServerRegexp( const QCopServerRegexp& other );
+
+    QString channel;
+    QWSClient *client;
+    QRegExp regexp;
+};
+
+QCopServerRegexp::QCopServerRegexp( const QString& channel, QWSClient *client )
+{
+    this->channel = channel;
+    this->client = client;
+    this->regexp = QRegExp( channel, Qt::CaseSensitive, QRegExp::Wildcard );
+}
+
+QCopServerRegexp::QCopServerRegexp( const QCopServerRegexp& other )
+{
+    channel = other.channel;
+    client = other.client;
+    regexp = other.regexp;
+}
+
+typedef QList<QCopServerRegexp> QCopServerRegexpList;
+static QCopServerRegexpList *qcopServerRegexpList = 0;
+
+typedef QMap<QString, QList< QPointer<QCopChannel> > > QCopClientMap;
+static QCopClientMap *qcopClientMap = 0;
+
+Q_GLOBAL_STATIC(QMutex, qcopClientMapMutex)
+
+// Determine if a channel name contains wildcard characters.
+static bool containsWildcards( const QString& channel )
+{
+    return channel.contains(QLatin1Char('*'));
+}
+
+class QCopChannelPrivate
+{
+public:
+    QString channel;
+};
+
+/*!
+    \class QCopChannel
+    \ingroup qws
+
+    \brief The QCopChannel class provides communication capabilities
+    between clients in \l{Qt for Embedded Linux}.
+
+    Note that this class is only available in \l{Qt for Embedded Linux}.
+
+    The Qt COmmunication Protocol (QCOP) is a many-to-many protocol
+    for transferring messages across registered channels. A channel is
+    registered by name, and anyone who wants to can listen to the
+    channel as well as send messages through it. The QCOP protocol
+    allows clients to communicate both within the same address space
+    and between different processes.
+
+    To send messages to a given channel, QCopChannel provides the
+    static send() function. Using this function alone, the messages
+    are queued until Qt re-enters the event loop. To immediately flush
+    all queued messages to the registered listeners, call the static
+    flush() function.
+
+    To listen to the traffic on a given channel, you typically
+    instantiate a QCopChannel object for the given channel and connect
+    to its received() signal that is emitted whenever there is
+    incoming data.  Use the static isRegistered() function to query
+    the server for the existence of a given channel. QCopChannel
+    provides the channel() function returning the name of this
+    QCopChannel object's channel.
+
+    In additon, QCopChannel provides the virtual receive() function
+    that can be reimplemented to filter the incoming messages and
+    data. The default implementation simply emits the received()
+    signal.
+
+    \sa QWSServer, QWSClient, {Qt for Embedded Linux Architecture}
+*/
+
+/*!
+    Constructs a QCopChannel object for the specified \a channel, with
+    the given \a parent. Once created, the channel is registered by
+    the server.
+
+    \sa isRegistered(), channel()
+*/
+
+QCopChannel::QCopChannel(const QString& channel, QObject *parent) :
+    QObject(parent)
+{
+    init(channel);
+}
+
+#ifdef QT3_SUPPORT
+/*!
+    Use the two argument overload instead, and call the
+    QObject::setObjectName() function to \a name the instance.
+*/
+QCopChannel::QCopChannel(const QString& channel, QObject *parent, const char *name) :
+    QObject(parent)
+{
+    setObjectName(QString::fromAscii(name));
+    init(channel);
+}
+#endif
+
+void QCopChannel::init(const QString& channel)
+{
+    d = new QCopChannelPrivate;
+    d->channel = channel;
+
+    if (!qt_fbdpy) {
+        qFatal("QCopChannel: Must construct a QApplication "
+                "before QCopChannel");
+        return;
+    }
+
+    {
+	QMutexLocker locker(qcopClientMapMutex());
+
+	if (!qcopClientMap)
+	    qcopClientMap = new QCopClientMap;
+
+	// do we need a new channel list ?
+	QCopClientMap::Iterator it = qcopClientMap->find(channel);
+	if (it != qcopClientMap->end()) {
+	    it.value().append(this);
+	    return;
+	}
+
+	it = qcopClientMap->insert(channel, QList< QPointer<QCopChannel> >());
+	it.value().append(QPointer<QCopChannel>(this));
+    }
+
+    // inform server about this channel
+    qt_fbdpy->registerChannel(channel);
+}
+
+/*!
+  \internal
+
+  Resend all channel registrations
+  */
+void QCopChannel::reregisterAll()
+{
+    if(qcopClientMap)
+        for(QCopClientMap::Iterator iter = qcopClientMap->begin();
+            iter != qcopClientMap->end();
+            ++iter)
+            qt_fbdpy->registerChannel(iter.key());
+}
+
+/*!
+    Destroys this QCopChannel object.
+
+    The server is notified that this particular listener has closed
+    its connection. The server will keep the channel open until the
+    last registered listener detaches.
+
+    \sa isRegistered(), channel()
+*/
+
+QCopChannel::~QCopChannel()
+{
+    QMutexLocker locker(qcopClientMapMutex());
+    QCopClientMap::Iterator it = qcopClientMap->find(d->channel);
+    Q_ASSERT(it != qcopClientMap->end());
+    it.value().removeAll(this);
+    // still any clients connected locally ?
+    if (it.value().isEmpty()) {
+        QByteArray data;
+        QDataStream s(&data, QIODevice::WriteOnly);
+        s << d->channel;
+        if (qt_fbdpy)
+            send(QLatin1String(""), QLatin1String("detach()"), data);
+        qcopClientMap->remove(d->channel);
+    }
+
+    delete d;
+}
+
+/*!
+    Returns the name of this object's channel.
+
+    \sa isRegistered()
+*/
+
+QString QCopChannel::channel() const
+{
+    return d->channel;
+}
+
+/*!
+    \fn void QCopChannel::receive(const QString& message, const QByteArray &data)
+
+    Processes the incoming \a message and \a data.
+
+    This function is called by the server when this object's channel
+    receives new messages. Note that the default implementation simply
+    emits the received() signal; reimplement this function to process
+    the incoming \a message and \a data.
+
+    Note that the format of the given \a data has to be well defined
+    in order to extract the information it contains. In addition, it
+    is recommended to use the DCOP convention. This is not a
+    requirement, but you must ensure that the sender and receiver
+    agree on the argument types. For example:
+
+    \snippet doc/src/snippets/code/src_gui_embedded_qcopchannel_qws.cpp 0
+
+    The above code assumes that the \c message is a DCOP-style
+    function signature and the \c data contains the function's
+    arguments.
+
+    \sa send(), channel(), received()
+ */
+void QCopChannel::receive(const QString& msg, const QByteArray &data)
+{
+    emit received(msg, data);
+}
+
+/*!
+    \fn void QCopChannel::received(const QString& message, const QByteArray &data)
+
+    This signal is emitted whenever this object's channel receives new
+    messages (i.e., it is emitted by the receive() function), passing
+    the incoming \a message and \a data as parameters.
+
+    \sa receive(), channel()
+*/
+
+/*!
+    Queries the server for the existence of the given \a channel. Returns true
+    if the channel is registered; otherwise returns false.
+
+    \sa channel(), send()
+*/
+
+bool QCopChannel::isRegistered(const QString&  channel)
+{
+    QByteArray data;
+    QDataStream s(&data, QIODevice::WriteOnly);
+    s << channel;
+    if (!send(QLatin1String(""), QLatin1String("isRegistered()"), data))
+        return false;
+
+    QWSQCopMessageEvent *e = qt_fbdpy->waitForQCopResponse();
+    bool known = e->message == "known";
+    delete e;
+    return known;
+}
+
+/*!
+    \fn bool QCopChannel::send(const QString& channel, const QString& message)
+    \overload
+*/
+
+bool QCopChannel::send(const QString& channel, const QString& msg)
+{
+    QByteArray data;
+    return send(channel, msg, data);
+}
+
+/*!
+    \fn bool QCopChannel::send(const QString& channel, const QString& message,
+                       const QByteArray &data)
+
+    Sends the given \a message on the specified \a channel with the
+    given \a data. The message will be distributed to all clients
+    subscribed to the channel. Returns true if the message is sent
+    successfully; otherwise returns false.
+
+    It is recommended to use the DCOP convention. This is not a
+    requirement, but you must ensure that the sender and receiver
+    agree on the argument types.
+
+    Note that QDataStream provides a convenient way to fill the byte
+    array with auxiliary data. For example:
+
+    \snippet doc/src/snippets/code/src_gui_embedded_qcopchannel_qws.cpp 1
+
+    In the code above the channel is \c "System/Shell". The \c message
+    is an arbitrary string, but in the example we've used the DCOP
+    convention of passing a function signature. Such a signature is
+    formatted as \c "functionname(types)" where \c types is a list of
+    zero or more comma-separated type names, with no whitespace, no
+    consts and no pointer or reference marks, i.e. no "*" or "&".
+
+    \sa receive(), isRegistered()
+*/
+
+bool QCopChannel::send(const QString& channel, const QString& msg,
+                       const QByteArray &data)
+{
+    if (!qt_fbdpy) {
+        qFatal("QCopChannel::send: Must construct a QApplication "
+                "before using QCopChannel");
+        return false;
+    }
+
+    qt_fbdpy->sendMessage(channel, msg, data);
+
+    return true;
+}
+
+/*!
+    \since 4.2
+
+    Flushes all queued messages to the registered listeners.
+
+    Note that this function returns false if no QApplication has been
+    constructed, otherwise it returns true.
+
+    \sa send()
+
+*/
+bool QCopChannel::flush()
+{
+    if (!qt_fbdpy) {
+        qFatal("QCopChannel::flush: Must construct a QApplication "
+                "before using QCopChannel");
+        return false;
+    }
+
+    qt_fbdpy->flushCommands();
+
+    return true;
+}
+
+class QWSServerSignalBridge : public QObject {
+  Q_OBJECT
+
+public:
+  void emitNewChannel(const QString& channel);
+  void emitRemovedChannel(const QString& channel);
+
+  signals:
+  void newChannel(const QString& channel);
+  void removedChannel(const QString& channel);
+};
+
+void QWSServerSignalBridge::emitNewChannel(const QString& channel){
+  emit newChannel(channel);
+}
+
+void QWSServerSignalBridge::emitRemovedChannel(const QString& channel) {
+  emit removedChannel(channel);
+}
+
+/*!
+    \internal
+    Server side: subscribe client \a cl on channel \a ch.
+*/
+
+void QCopChannel::registerChannel(const QString& ch, QWSClient *cl)
+{
+    if (!qcopServerMap)
+        qcopServerMap = new QCopServerMap;
+
+    // do we need a new channel list ?
+    QCopServerMap::Iterator it = qcopServerMap->find(ch);
+    if (it == qcopServerMap->end())
+      it = qcopServerMap->insert(ch, QList<QWSClient*>());
+
+    // If the channel name contains wildcard characters, then we also
+    // register it on the server regexp matching list.
+    if (containsWildcards( ch )) {
+	QCopServerRegexp item(ch, cl);
+	if (!qcopServerRegexpList)
+	    qcopServerRegexpList = new QCopServerRegexpList;
+	qcopServerRegexpList->append( item );
+    }
+
+    // If this is the first client in the channel, announce the channel as being created.
+    if (it.value().count() == 0) {
+      QWSServerSignalBridge* qwsBridge = new QWSServerSignalBridge();
+      connect(qwsBridge, SIGNAL(newChannel(QString)), qwsServer, SIGNAL(newChannel(QString)));
+      qwsBridge->emitNewChannel(ch);
+      delete qwsBridge;
+    }
+
+    it.value().append(cl);
+}
+
+/*!
+    \internal
+    Server side: unsubscribe \a cl from all channels.
+*/
+
+void QCopChannel::detach(QWSClient *cl)
+{
+    if (!qcopServerMap)
+        return;
+
+    QCopServerMap::Iterator it = qcopServerMap->begin();
+    for (; it != qcopServerMap->end(); ++it) {
+      if (it.value().contains(cl)) {
+        it.value().removeAll(cl);
+        // If this was the last client in the channel, announce the channel as dead.
+        if (it.value().count() == 0) {
+          QWSServerSignalBridge* qwsBridge = new QWSServerSignalBridge();
+          connect(qwsBridge, SIGNAL(removedChannel(QString)), qwsServer, SIGNAL(removedChannel(QString)));
+          qwsBridge->emitRemovedChannel(it.key());
+          delete qwsBridge;
+        }
+      }
+    }
+
+    if (!qcopServerRegexpList)
+	return;
+
+    QCopServerRegexpList::Iterator it2 = qcopServerRegexpList->begin();
+    while(it2 != qcopServerRegexpList->end()) {
+	if ((*it2).client == cl)
+	    it2 = qcopServerRegexpList->erase(it2);
+	else
+	    ++it2;
+    }
+}
+
+/*!
+    \internal
+    Server side: transmit the message to all clients registered to the
+    specified channel.
+*/
+
+void QCopChannel::answer(QWSClient *cl, const QString& ch,
+                          const QString& msg, const QByteArray &data)
+{
+    // internal commands
+    if (ch.isEmpty()) {
+        if (msg == QLatin1String("isRegistered()")) {
+            QString c;
+            QDataStream s(data);
+            s >> c;
+            bool known = qcopServerMap && qcopServerMap->contains(c)
+                        && !((*qcopServerMap)[c]).isEmpty();
+            // Yes, it's a typo, it's not user-visible, and we choose not to fix it for compatibility
+            QLatin1String ans = QLatin1String(known ? "known" : "unkown");
+            QWSServerPrivate::sendQCopEvent(cl, QLatin1String(""),
+                                            ans, data, true);
+            return;
+        } else if (msg == QLatin1String("detach()")) {
+            QString c;
+            QDataStream s(data);
+            s >> c;
+            Q_ASSERT(qcopServerMap);
+            QCopServerMap::Iterator it = qcopServerMap->find(c);
+            if (it != qcopServerMap->end()) {
+                //Q_ASSERT(it.value().contains(cl));
+                it.value().removeAll(cl);
+                if (it.value().isEmpty()) {
+                  // If this was the last client in the channel, announce the channel as dead
+                  QWSServerSignalBridge* qwsBridge = new QWSServerSignalBridge();
+                  connect(qwsBridge, SIGNAL(removedChannel(QString)), qwsServer, SIGNAL(removedChannel(QString)));
+                  qwsBridge->emitRemovedChannel(it.key());
+                  delete qwsBridge;
+                  qcopServerMap->erase(it);
+                }
+            }
+	    if (qcopServerRegexpList && containsWildcards(c)) {
+		// Remove references to a wildcarded channel.
+		QCopServerRegexpList::Iterator it
+		    = qcopServerRegexpList->begin();
+		while(it != qcopServerRegexpList->end()) {
+		    if ((*it).client == cl && (*it).channel == c)
+			it = qcopServerRegexpList->erase(it);
+		    else
+			++it;
+		}
+	    }
+            return;
+        }
+        qWarning("QCopChannel: unknown internal command %s", qPrintable(msg));
+        QWSServerPrivate::sendQCopEvent(cl, QLatin1String(""),
+                                        QLatin1String("bad"), data);
+        return;
+    }
+
+    if (qcopServerMap) {
+        QList<QWSClient*> clist = qcopServerMap->value(ch);
+        for (int i=0; i < clist.size(); ++i) {
+            QWSClient *c = clist.at(i);
+            QWSServerPrivate::sendQCopEvent(c, ch, msg, data);
+        }
+    }
+
+    if(qcopServerRegexpList && !containsWildcards(ch)) {
+	// Search for wildcard matches and forward the message on.
+	QCopServerRegexpList::ConstIterator it = qcopServerRegexpList->constBegin();
+	for (; it != qcopServerRegexpList->constEnd(); ++it) {
+	    if ((*it).regexp.exactMatch(ch)) {
+		QByteArray newData;
+		{
+		    QDataStream stream
+			(&newData, QIODevice::WriteOnly | QIODevice::Append);
+		    stream << ch;
+		    stream << msg;
+		    stream << data;
+		    // Stream is flushed and closed at this point.
+		}
+		QWSServerPrivate::sendQCopEvent
+		    ((*it).client, (*it).channel,
+		     QLatin1String("forwardedMessage(QString,QString,QByteArray)"),
+		     newData);
+	    }
+	}
+    }
+}
+
+/*!
+    \internal
+    Client side: distribute received event to the QCop instance managing the
+    channel.
+*/
+void QCopChannel::sendLocally(const QString& ch, const QString& msg,
+                                const QByteArray &data)
+{
+    Q_ASSERT(qcopClientMap);
+
+    // filter out internal events
+    if (ch.isEmpty())
+        return;
+
+    // feed local clients with received data
+    QList< QPointer<QCopChannel> > clients;
+    {
+	QMutexLocker locker(qcopClientMapMutex());
+	clients = (*qcopClientMap)[ch];
+    }
+    for (int i = 0; i < clients.size(); ++i) {
+	QCopChannel *channel = (QCopChannel *)clients.at(i);
+	if ( channel )
+	    channel->receive(msg, data);
+    }
+}
+
+QT_END_NAMESPACE
+
+#include "qcopchannel_qws.moc"
+
+#endif