00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "fdonotification.h"
00023 #include "fdoselectionmanager.h"
00024 #include "fdotask.h"
00025 #include "x11embedpainter.h"
00026
00027 #include <KDebug>
00028
00029 #include <QtCore/QCoreApplication>
00030 #include <QtCore/QHash>
00031 #include <QtCore/QTimer>
00032
00033 #include <QtGui/QTextDocument>
00034 #include <QtGui/QX11Info>
00035
00036 #include <KGlobal>
00037
00038 #include <config-X11.h>
00039
00040 #include <X11/Xlib.h>
00041 #include <X11/Xatom.h>
00042 #include <X11/extensions/Xrender.h>
00043
00044 #ifdef HAVE_XFIXES
00045 # include <X11/extensions/Xfixes.h>
00046 #endif
00047
00048 #ifdef HAVE_XDAMAGE
00049 # include <X11/extensions/Xdamage.h>
00050 #endif
00051
00052 #ifdef HAVE_XCOMPOSITE
00053 # include <X11/extensions/Xcomposite.h>
00054 #endif
00055
00056 #define SYSTEM_TRAY_REQUEST_DOCK 0
00057 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
00058 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
00059
00060
00061 namespace SystemTray
00062 {
00063
00064 static FdoSelectionManager *s_manager = 0;
00065 static X11EmbedPainter *s_painter = 0;
00066
00067 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
00068 struct DamageWatch
00069 {
00070 QWidget *container;
00071 Damage damage;
00072 };
00073
00074 static int damageEventBase = 0;
00075 static QMap<WId, DamageWatch*> damageWatches;
00076 static QCoreApplication::EventFilter oldEventFilter;
00077
00078
00079 static bool x11EventFilter(void *message, long int *result)
00080 {
00081 XEvent *event = reinterpret_cast<XEvent*>(message);
00082 if (event->type == damageEventBase + XDamageNotify) {
00083 XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>(event);
00084 if (DamageWatch *damageWatch = damageWatches.value(e->drawable)) {
00085
00086
00087
00088 XserverRegion region = XFixesCreateRegion(e->display, 0, 0);
00089 XDamageSubtract(e->display, e->damage, None, region);
00090 XFixesDestroyRegion(e->display, region);
00091 damageWatch->container->update();
00092 }
00093 }
00094
00095 if (oldEventFilter && oldEventFilter != x11EventFilter) {
00096 return oldEventFilter(message, result);
00097 } else {
00098 return false;
00099 }
00100 }
00101 #endif
00102
00103
00104 struct MessageRequest
00105 {
00106 long messageId;
00107 long timeout;
00108 long bytesRemaining;
00109 QByteArray message;
00110 };
00111
00112
00113 class FdoSelectionManagerPrivate
00114 {
00115 public:
00116 FdoSelectionManagerPrivate(FdoSelectionManager *q)
00117 : q(q), haveComposite(false)
00118 {
00119 display = QX11Info::display();
00120 selectionAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_S" + QByteArray::number(QX11Info::appScreen()), false);
00121 opcodeAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_OPCODE", false);
00122 messageAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_MESSAGE_DATA", false);
00123 visualAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_VISUAL", false);
00124
00125 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
00126 int eventBase, errorBase;
00127 bool haveXfixes = XFixesQueryExtension(display, &eventBase, &errorBase);
00128 bool haveXdamage = XDamageQueryExtension(display, &damageEventBase, &errorBase);
00129 bool haveXComposite = XCompositeQueryExtension(display, &eventBase, &errorBase);
00130
00131 if (haveXfixes && haveXdamage && haveXComposite) {
00132 haveComposite = true;
00133 oldEventFilter = QCoreApplication::instance()->setEventFilter(x11EventFilter);
00134 }
00135 #endif
00136 }
00137
00138 void createNotification(WId winId);
00139
00140 void handleRequestDock(const XClientMessageEvent &event);
00141 void handleBeginMessage(const XClientMessageEvent &event);
00142 void handleMessageData(const XClientMessageEvent &event);
00143 void handleCancelMessage(const XClientMessageEvent &event);
00144
00145 Display *display;
00146 Atom selectionAtom;
00147 Atom opcodeAtom;
00148 Atom messageAtom;
00149 Atom visualAtom;
00150
00151 QHash<WId, MessageRequest> messageRequests;
00152 QHash<WId, FdoTask*> tasks;
00153 QHash<WId, FdoNotification*> notifications;
00154
00155 FdoSelectionManager *q;
00156 bool haveComposite;
00157 };
00158
00159 FdoSelectionManager::FdoSelectionManager()
00160 : d(new FdoSelectionManagerPrivate(this))
00161 {
00162
00163
00164
00165 QTimer::singleShot(0, this, SLOT(initSelection()));
00166 }
00167
00168
00169 FdoSelectionManager::~FdoSelectionManager()
00170 {
00171 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
00172 if (d->haveComposite && QCoreApplication::instance()) {
00173 QCoreApplication::instance()->setEventFilter(oldEventFilter);
00174 }
00175 #endif
00176
00177 if (s_manager == this) {
00178 s_manager = 0;
00179 delete s_painter;
00180 s_painter = 0;
00181 }
00182
00183 delete d;
00184 }
00185
00186 FdoSelectionManager *FdoSelectionManager::manager()
00187 {
00188 return s_manager;
00189 }
00190
00191 X11EmbedPainter *FdoSelectionManager::painter()
00192 {
00193 return s_painter;
00194 }
00195
00196 void FdoSelectionManager::addDamageWatch(QWidget *container, WId client)
00197 {
00198 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
00199 DamageWatch *damage = new DamageWatch;
00200 damage->container = container;
00201 damage->damage = XDamageCreate(QX11Info::display(), client, XDamageReportNonEmpty);
00202 damageWatches.insert(client, damage);
00203 #endif
00204 }
00205
00206 void FdoSelectionManager::removeDamageWatch(QWidget *container)
00207 {
00208 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
00209 for (QMap<WId, DamageWatch*>::Iterator it = damageWatches.begin(); it != damageWatches.end(); ++it)
00210 {
00211 DamageWatch *damage = *(it);
00212 if (damage->container == container) {
00213 XDamageDestroy(QX11Info::display(), damage->damage);
00214 damageWatches.erase(it);
00215 delete damage;
00216 break;
00217 }
00218 }
00219 #endif
00220 }
00221
00222
00223 bool FdoSelectionManager::haveComposite() const
00224 {
00225 return d->haveComposite;
00226 }
00227
00228
00229 bool FdoSelectionManager::x11Event(XEvent *event)
00230 {
00231 if (event->type == ClientMessage) {
00232 if (event->xclient.message_type == d->opcodeAtom) {
00233 switch (event->xclient.data.l[1]) {
00234 case SYSTEM_TRAY_REQUEST_DOCK:
00235 d->handleRequestDock(event->xclient);
00236 return true;
00237 case SYSTEM_TRAY_BEGIN_MESSAGE:
00238 d->handleBeginMessage(event->xclient);
00239 return true;
00240 case SYSTEM_TRAY_CANCEL_MESSAGE:
00241 d->handleCancelMessage(event->xclient);
00242 return true;
00243 }
00244 } else if (event->xclient.message_type == d->messageAtom) {
00245 d->handleMessageData(event->xclient);
00246 return true;
00247 }
00248 }
00249
00250 return QWidget::x11Event(event);
00251 }
00252
00253
00254 void FdoSelectionManager::initSelection()
00255 {
00256 XSetSelectionOwner(d->display, d->selectionAtom, winId(), CurrentTime);
00257
00258 WId selectionOwner = XGetSelectionOwner(d->display, d->selectionAtom);
00259 if (selectionOwner != winId()) {
00260
00261
00262 kDebug() << "Tried to set selection owner to" << winId() << "but it is set to" << selectionOwner;
00263 return;
00264 }
00265
00266
00267 int nvi;
00268 VisualID visual = XVisualIDFromVisual((Visual*)QX11Info::appVisual());
00269 XVisualInfo templ;
00270 templ.visualid = visual;
00271 XVisualInfo *xvi = XGetVisualInfo(d->display, VisualIDMask, &templ, &nvi);
00272 if (xvi) {
00273 templ.screen = xvi[0].screen;
00274 templ.depth = xvi[0].depth;
00275 templ.c_class = xvi[0].c_class;
00276 XFree(xvi);
00277 xvi = XGetVisualInfo(d->display, VisualScreenMask | VisualDepthMask | VisualClassMask,
00278 &templ, &nvi);
00279 for (int i = 0; i < nvi; i++) {
00280 XRenderPictFormat *format = XRenderFindVisualFormat(d->display, xvi[i].visual);
00281 if (format->type == PictTypeDirect && format->direct.alphaMask) {
00282 visual = xvi[i].visualid;
00283 break;
00284 }
00285 }
00286 XFree(xvi);
00287 }
00288 XChangeProperty(d->display, winId(), d->visualAtom, XA_VISUALID, 32,
00289 PropModeReplace, (const unsigned char*)&visual, 1);
00290
00291 if (!s_painter) {
00292 s_painter = new X11EmbedPainter;
00293 }
00294 s_manager = this;
00295
00296 WId root = QX11Info::appRootWindow();
00297 XClientMessageEvent xev;
00298
00299 xev.type = ClientMessage;
00300 xev.window = root;
00301 xev.message_type = XInternAtom(d->display, "MANAGER", false);
00302 xev.format = 32;
00303 xev.data.l[0] = CurrentTime;
00304 xev.data.l[1] = d->selectionAtom;
00305 xev.data.l[2] = winId();
00306 xev.data.l[3] = 0;
00307 xev.data.l[4] = 0;
00308
00309 XSendEvent(d->display, root, false, StructureNotifyMask, (XEvent*)&xev);
00310 }
00311
00312
00313 void FdoSelectionManagerPrivate::handleRequestDock(const XClientMessageEvent &event)
00314 {
00315 const WId winId = (WId)event.data.l[2];
00316
00317 if (tasks.contains(winId)) {
00318 kDebug() << "got a dock request from an already existing task";
00319 return;
00320 }
00321
00322 FdoTask *task = new FdoTask(winId);
00323
00324 tasks[winId] = task;
00325 q->connect(task, SIGNAL(taskDeleted(WId)), q, SLOT(cleanupTask(WId)));
00326
00327 emit q->taskCreated(task);
00328 }
00329
00330
00331 void FdoSelectionManager::cleanupTask(WId winId)
00332 {
00333 d->tasks.remove(winId);
00334 }
00335
00336
00337 void FdoSelectionManagerPrivate::handleBeginMessage(const XClientMessageEvent &event)
00338 {
00339 const WId winId = event.window;
00340
00341 MessageRequest request;
00342 request.messageId = event.data.l[4];
00343 request.timeout = event.data.l[2];
00344 request.bytesRemaining = event.data.l[3];
00345
00346 if (request.bytesRemaining) {
00347 messageRequests[winId] = request;
00348 }
00349 }
00350
00351
00352 void FdoSelectionManagerPrivate::handleMessageData(const XClientMessageEvent &event)
00353 {
00354 const WId winId = event.window;
00355 const char *messageData = event.data.b;
00356
00357 if (!messageRequests.contains(winId)) {
00358 kDebug() << "Unexpected message data from" << winId;
00359 return;
00360 }
00361
00362 MessageRequest &request = messageRequests[winId];
00363 const int messageSize = qMin(request.bytesRemaining, 20l);
00364 request.bytesRemaining -= messageSize;
00365 request.message += QByteArray(messageData, messageSize);
00366
00367 if (request.bytesRemaining == 0) {
00368 createNotification(winId);
00369 messageRequests.remove(winId);
00370 }
00371 }
00372
00373
00374 void FdoSelectionManagerPrivate::createNotification(WId winId)
00375 {
00376 if (!tasks.contains(winId)) {
00377 kDebug() << "message request from unknown task" << winId;
00378 return;
00379 }
00380
00381 MessageRequest &request = messageRequests[winId];
00382 Task *task = tasks[winId];
00383
00384 QString message = QString::fromUtf8(request.message);
00385 message = QTextDocument(message).toHtml();
00386
00387 FdoNotification *notification = new FdoNotification(winId, task);
00388 notification->setApplicationName(task->name());
00389 notification->setApplicationIcon(task->icon());
00390 notification->setMessage(message);
00391 notification->setTimeout(request.timeout);
00392
00393 q->connect(notification, SIGNAL(notificationDeleted(WId)), q, SLOT(cleanupNotification(WId)));
00394 emit q->notificationCreated(notification);
00395 }
00396
00397
00398 void FdoSelectionManagerPrivate::handleCancelMessage(const XClientMessageEvent &event)
00399 {
00400 const WId winId = event.window;
00401 const long messageId = event.data.l[2];
00402
00403 if (messageRequests.contains(winId) && messageRequests[winId].messageId == messageId) {
00404 messageRequests.remove(winId);
00405 } else if (notifications.contains(winId)) {
00406 notifications.take(winId)->deleteLater();
00407 }
00408 }
00409
00410
00411 void FdoSelectionManager::cleanupNotification(WId winId)
00412 {
00413 d->notifications.remove(winId);
00414 }
00415
00416 }