/*************************************************************************** * fdoselectionmanager.cpp * * * * Copyright (C) 2008 Jason Stubbs * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "fdoselectionmanager.h" #include "fdotask.h" #include "x11embedpainter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_XFIXES # include #endif #ifdef HAVE_XDAMAGE # include #endif #ifdef HAVE_XCOMPOSITE # include #endif #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 namespace SystemTray { static FdoSelectionManager *s_manager = 0; static X11EmbedPainter *s_painter = 0; #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE) struct DamageWatch { QWidget *container; Damage damage; }; static int damageEventBase = 0; static QMap damageWatches; static QCoreApplication::EventFilter oldEventFilter; // Global event filter for intercepting damage events static bool x11EventFilter(void *message, long int *result) { XEvent *event = reinterpret_cast(message); if (event->type == damageEventBase + XDamageNotify) { XDamageNotifyEvent *e = reinterpret_cast(event); if (DamageWatch *damageWatch = damageWatches.value(e->drawable)) { // Create a new region and empty the damage region into it. // The window is small enough that we don't really care about the region; // we'll just throw it away and schedule a full repaint of the container. XserverRegion region = XFixesCreateRegion(e->display, 0, 0); XDamageSubtract(e->display, e->damage, None, region); XFixesDestroyRegion(e->display, region); damageWatch->container->update(); } } if (oldEventFilter && oldEventFilter != x11EventFilter) { return oldEventFilter(message, result); } else { return false; } } #endif struct MessageRequest { long messageId; long timeout; long bytesRemaining; QByteArray message; }; class FdoSelectionManagerPrivate { public: FdoSelectionManagerPrivate(FdoSelectionManager *q) : q(q), notificationsEngine(0), haveComposite(false) { display = QX11Info::display(); QByteArray selectiondata = "_NET_SYSTEM_TRAY_S" + QByteArray::number(QX11Info::appScreen()); selectionAtom = XInternAtom(display, selectiondata.data(), false); opcodeAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_OPCODE", false); messageAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_MESSAGE_DATA", false); visualAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_VISUAL", false); #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE) int eventBase, errorBase; bool haveXfixes = XFixesQueryExtension(display, &eventBase, &errorBase); bool haveXdamage = XDamageQueryExtension(display, &damageEventBase, &errorBase); bool haveXComposite = XCompositeQueryExtension(display, &eventBase, &errorBase); if (haveXfixes && haveXdamage && haveXComposite) { haveComposite = true; oldEventFilter = QCoreApplication::instance()->setEventFilter(x11EventFilter); } #endif } void createNotification(WId winId); void handleRequestDock(const XClientMessageEvent &event); void handleBeginMessage(const XClientMessageEvent &event); void handleMessageData(const XClientMessageEvent &event); void handleCancelMessage(const XClientMessageEvent &event); Display *display; Atom selectionAtom; Atom opcodeAtom; Atom messageAtom; Atom visualAtom; QHash messageRequests; QHash tasks; FdoSelectionManager *q; Plasma::DataEngine *notificationsEngine; bool haveComposite; }; FdoSelectionManager::FdoSelectionManager() : d(new FdoSelectionManagerPrivate(this)) { // Init the selection later just to ensure that no signals are sent // until after construction is done and the creating object has a // chance to connect. QTimer::singleShot(0, this, SLOT(initSelection())); } FdoSelectionManager::~FdoSelectionManager() { #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE) if (d->haveComposite && QCoreApplication::instance()) { QCoreApplication::instance()->setEventFilter(oldEventFilter); } #endif if (s_manager == this) { s_manager = 0; delete s_painter; s_painter = 0; } delete d; } FdoSelectionManager *FdoSelectionManager::manager() { return s_manager; } X11EmbedPainter *FdoSelectionManager::painter() { return s_painter; } void FdoSelectionManager::addDamageWatch(QWidget *container, WId client) { #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE) DamageWatch *damage = new DamageWatch; damage->container = container; damage->damage = XDamageCreate(QX11Info::display(), client, XDamageReportNonEmpty); damageWatches.insert(client, damage); #endif } void FdoSelectionManager::removeDamageWatch(QWidget *container) { #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE) for (QMap::Iterator it = damageWatches.begin(); it != damageWatches.end(); ++it) { DamageWatch *damage = *(it); if (damage->container == container) { XDamageDestroy(QX11Info::display(), damage->damage); damageWatches.erase(it); delete damage; break; } } #endif } bool FdoSelectionManager::haveComposite() const { return d->haveComposite; } bool FdoSelectionManager::x11Event(XEvent *event) { if (event->type == ClientMessage) { if (event->xclient.message_type == d->opcodeAtom) { switch (event->xclient.data.l[1]) { case SYSTEM_TRAY_REQUEST_DOCK: d->handleRequestDock(event->xclient); return true; case SYSTEM_TRAY_BEGIN_MESSAGE: d->handleBeginMessage(event->xclient); return true; case SYSTEM_TRAY_CANCEL_MESSAGE: d->handleCancelMessage(event->xclient); return true; } } else if (event->xclient.message_type == d->messageAtom) { d->handleMessageData(event->xclient); return true; } } return QWidget::x11Event(event); } void FdoSelectionManager::initSelection() { XSetSelectionOwner(d->display, d->selectionAtom, winId(), CurrentTime); WId selectionOwner = XGetSelectionOwner(d->display, d->selectionAtom); if (selectionOwner != winId()) { // FIXME: Hmmm... Reading the docs on XSetSelectionOwner, // this should not be possible. kDebug() << "Tried to set selection owner to" << winId() << "but it is set to" << selectionOwner; return; } // Prefer the ARGB32 visual if available int nvi; VisualID visual = XVisualIDFromVisual((Visual*)QX11Info::appVisual()); XVisualInfo templ; templ.visualid = visual; XVisualInfo *xvi = XGetVisualInfo(d->display, VisualIDMask, &templ, &nvi); if (xvi && xvi[0].depth > 16) { templ.screen = xvi[0].screen; templ.depth = 32; templ.c_class = TrueColor; XFree(xvi); xvi = XGetVisualInfo(d->display, VisualScreenMask | VisualDepthMask | VisualClassMask, &templ, &nvi); for (int i = 0; i < nvi; i++) { XRenderPictFormat *format = XRenderFindVisualFormat(d->display, xvi[i].visual); if (format && format->type == PictTypeDirect && format->direct.alphaMask) { visual = xvi[i].visualid; break; } } XFree(xvi); } XChangeProperty(d->display, winId(), d->visualAtom, XA_VISUALID, 32, PropModeReplace, (const unsigned char*)&visual, 1); if (!s_painter) { s_painter = new X11EmbedPainter; } s_manager = this; WId root = QX11Info::appRootWindow(); XClientMessageEvent xev; xev.type = ClientMessage; xev.window = root; xev.message_type = XInternAtom(d->display, "MANAGER", false); xev.format = 32; xev.data.l[0] = CurrentTime; xev.data.l[1] = d->selectionAtom; xev.data.l[2] = winId(); xev.data.l[3] = 0; xev.data.l[4] = 0; XSendEvent(d->display, root, false, StructureNotifyMask, (XEvent*)&xev); } void FdoSelectionManagerPrivate::handleRequestDock(const XClientMessageEvent &event) { const WId winId = (WId)event.data.l[2]; if (tasks.contains(winId)) { kDebug() << "got a dock request from an already existing task"; return; } FdoTask *task = new FdoTask(winId, q); tasks[winId] = task; q->connect(task, SIGNAL(taskDeleted(WId)), q, SLOT(cleanupTask(WId))); emit q->taskCreated(task); } void FdoSelectionManager::cleanupTask(WId winId) { d->tasks.remove(winId); } void FdoSelectionManagerPrivate::handleBeginMessage(const XClientMessageEvent &event) { const WId winId = event.window; MessageRequest request; request.messageId = event.data.l[4]; request.timeout = event.data.l[2]; request.bytesRemaining = event.data.l[3]; if (request.bytesRemaining) { messageRequests[winId] = request; } } void FdoSelectionManagerPrivate::handleMessageData(const XClientMessageEvent &event) { const WId winId = event.window; const char *messageData = event.data.b; if (!messageRequests.contains(winId)) { kDebug() << "Unexpected message data from" << winId; return; } MessageRequest &request = messageRequests[winId]; const int messageSize = qMin(request.bytesRemaining, 20l); request.bytesRemaining -= messageSize; request.message += QByteArray(messageData, messageSize); if (request.bytesRemaining == 0) { createNotification(winId); messageRequests.remove(winId); } } void FdoSelectionManagerPrivate::createNotification(WId winId) { if (!tasks.contains(winId)) { kDebug() << "message request from unknown task" << winId; return; } MessageRequest &request = messageRequests[winId]; Task *task = tasks[winId]; QString message = QString::fromUtf8(request.message); message = QTextDocument(message).toHtml(); if (!notificationsEngine) { notificationsEngine = Plasma::DataEngineManager::self()->loadEngine("notifications"); } Plasma::Service *service = notificationsEngine->serviceForSource("notification"); if (!service) { kDebug() << "invalid service for notification"; } else if (service->isOperationEnabled("createNotification")) { QMap params = service->operationParameters("createNotification"); params["appName"] = task->name(); //FIXME: find a way to pass icons trough here params["appIcon"] = task->name(); //params["summary"] = task->name(); params["body"] = message; params["timeout"] = (int)request.timeout; KJob *job = service->startOperationCall("createNotification", params); QObject::connect(job, SIGNAL(finished(KJob*)), service, SLOT(deleteLater())); } else { delete service; kDebug() << "invalid operation or disabled"; } } void FdoSelectionManagerPrivate::handleCancelMessage(const XClientMessageEvent &event) { const WId winId = event.window; const long messageId = event.data.l[2]; if (messageRequests.contains(winId) && messageRequests[winId].messageId == messageId) { messageRequests.remove(winId); } } }