not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / plasma / applets / systemtray / protocols / fdo / fdoselectionmanager.cpp
bloba83049755def4be9414b041d719a14399b77c188
1 /***************************************************************************
2 * fdoselectionmanager.cpp *
3 * *
4 * Copyright (C) 2008 Jason Stubbs <jasonbstubbs@gmail.com> *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
20 ***************************************************************************/
22 #include "fdonotification.h"
23 #include "fdoselectionmanager.h"
24 #include "fdotask.h"
25 #include "x11embedpainter.h"
27 #include <KDebug>
29 #include <QtCore/QCoreApplication>
30 #include <QtCore/QHash>
31 #include <QtCore/QTimer>
33 #include <QtGui/QTextDocument>
34 #include <QtGui/QX11Info>
36 #include <KGlobal>
38 #include <config-X11.h>
40 #include <X11/Xlib.h>
41 #include <X11/Xatom.h>
42 #include <X11/extensions/Xrender.h>
44 #ifdef HAVE_XFIXES
45 # include <X11/extensions/Xfixes.h>
46 #endif
48 #ifdef HAVE_XDAMAGE
49 # include <X11/extensions/Xdamage.h>
50 #endif
52 #ifdef HAVE_XCOMPOSITE
53 # include <X11/extensions/Xcomposite.h>
54 #endif
56 #define SYSTEM_TRAY_REQUEST_DOCK 0
57 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
58 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
61 namespace SystemTray
64 static FdoSelectionManager *s_manager = 0;
65 static X11EmbedPainter *s_painter = 0;
67 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
68 struct DamageWatch
70 QWidget *container;
71 Damage damage;
74 static int damageEventBase = 0;
75 static QMap<WId, DamageWatch*> damageWatches;
76 static QCoreApplication::EventFilter oldEventFilter;
78 // Global event filter for intercepting damage events
79 static bool x11EventFilter(void *message, long int *result)
81 XEvent *event = reinterpret_cast<XEvent*>(message);
82 if (event->type == damageEventBase + XDamageNotify) {
83 XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>(event);
84 if (DamageWatch *damageWatch = damageWatches.value(e->drawable)) {
85 // Create a new region and empty the damage region into it.
86 // The window is small enough that we don't really care about the region;
87 // we'll just throw it away and schedule a full repaint of the container.
88 XserverRegion region = XFixesCreateRegion(e->display, 0, 0);
89 XDamageSubtract(e->display, e->damage, None, region);
90 XFixesDestroyRegion(e->display, region);
91 damageWatch->container->update();
95 if (oldEventFilter && oldEventFilter != x11EventFilter) {
96 return oldEventFilter(message, result);
97 } else {
98 return false;
101 #endif
104 struct MessageRequest
106 long messageId;
107 long timeout;
108 long bytesRemaining;
109 QByteArray message;
113 class FdoSelectionManagerPrivate
115 public:
116 FdoSelectionManagerPrivate(FdoSelectionManager *q)
117 : q(q), haveComposite(false)
119 display = QX11Info::display();
120 selectionAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_S" + QByteArray::number(QX11Info::appScreen()), false);
121 opcodeAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_OPCODE", false);
122 messageAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_MESSAGE_DATA", false);
123 visualAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_VISUAL", false);
125 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
126 int eventBase, errorBase;
127 bool haveXfixes = XFixesQueryExtension(display, &eventBase, &errorBase);
128 bool haveXdamage = XDamageQueryExtension(display, &damageEventBase, &errorBase);
129 bool haveXComposite = XCompositeQueryExtension(display, &eventBase, &errorBase);
131 if (haveXfixes && haveXdamage && haveXComposite) {
132 haveComposite = true;
133 oldEventFilter = QCoreApplication::instance()->setEventFilter(x11EventFilter);
135 #endif
138 void createNotification(WId winId);
140 void handleRequestDock(const XClientMessageEvent &event);
141 void handleBeginMessage(const XClientMessageEvent &event);
142 void handleMessageData(const XClientMessageEvent &event);
143 void handleCancelMessage(const XClientMessageEvent &event);
145 Display *display;
146 Atom selectionAtom;
147 Atom opcodeAtom;
148 Atom messageAtom;
149 Atom visualAtom;
151 QHash<WId, MessageRequest> messageRequests;
152 QHash<WId, FdoTask*> tasks;
153 QHash<WId, FdoNotification*> notifications;
155 FdoSelectionManager *q;
156 bool haveComposite;
159 FdoSelectionManager::FdoSelectionManager()
160 : d(new FdoSelectionManagerPrivate(this))
162 // Init the selection later just to ensure that no signals are sent
163 // until after construction is done and the creating object has a
164 // chance to connect.
165 QTimer::singleShot(0, this, SLOT(initSelection()));
169 FdoSelectionManager::~FdoSelectionManager()
171 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
172 if (d->haveComposite && QCoreApplication::instance()) {
173 QCoreApplication::instance()->setEventFilter(oldEventFilter);
175 #endif
177 if (s_manager == this) {
178 s_manager = 0;
179 delete s_painter;
180 s_painter = 0;
183 delete d;
186 FdoSelectionManager *FdoSelectionManager::manager()
188 return s_manager;
191 X11EmbedPainter *FdoSelectionManager::painter()
193 return s_painter;
196 void FdoSelectionManager::addDamageWatch(QWidget *container, WId client)
198 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
199 DamageWatch *damage = new DamageWatch;
200 damage->container = container;
201 damage->damage = XDamageCreate(QX11Info::display(), client, XDamageReportNonEmpty);
202 damageWatches.insert(client, damage);
203 #endif
206 void FdoSelectionManager::removeDamageWatch(QWidget *container)
208 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
209 for (QMap<WId, DamageWatch*>::Iterator it = damageWatches.begin(); it != damageWatches.end(); ++it)
211 DamageWatch *damage = *(it);
212 if (damage->container == container) {
213 XDamageDestroy(QX11Info::display(), damage->damage);
214 damageWatches.erase(it);
215 delete damage;
216 break;
219 #endif
223 bool FdoSelectionManager::haveComposite() const
225 return d->haveComposite;
229 bool FdoSelectionManager::x11Event(XEvent *event)
231 if (event->type == ClientMessage) {
232 if (event->xclient.message_type == d->opcodeAtom) {
233 switch (event->xclient.data.l[1]) {
234 case SYSTEM_TRAY_REQUEST_DOCK:
235 d->handleRequestDock(event->xclient);
236 return true;
237 case SYSTEM_TRAY_BEGIN_MESSAGE:
238 d->handleBeginMessage(event->xclient);
239 return true;
240 case SYSTEM_TRAY_CANCEL_MESSAGE:
241 d->handleCancelMessage(event->xclient);
242 return true;
244 } else if (event->xclient.message_type == d->messageAtom) {
245 d->handleMessageData(event->xclient);
246 return true;
250 return QWidget::x11Event(event);
254 void FdoSelectionManager::initSelection()
256 XSetSelectionOwner(d->display, d->selectionAtom, winId(), CurrentTime);
258 WId selectionOwner = XGetSelectionOwner(d->display, d->selectionAtom);
259 if (selectionOwner != winId()) {
260 // FIXME: Hmmm... Reading the docs on XSetSelectionOwner,
261 // this should not be possible.
262 kDebug() << "Tried to set selection owner to" << winId() << "but it is set to" << selectionOwner;
263 return;
266 // Prefer the ARGB32 visual if available
267 int nvi;
268 VisualID visual = XVisualIDFromVisual((Visual*)QX11Info::appVisual());
269 XVisualInfo templ;
270 templ.visualid = visual;
271 XVisualInfo *xvi = XGetVisualInfo(d->display, VisualIDMask, &templ, &nvi);
272 if (xvi) {
273 templ.screen = xvi[0].screen;
274 templ.depth = xvi[0].depth;
275 templ.c_class = xvi[0].c_class;
276 XFree(xvi);
277 xvi = XGetVisualInfo(d->display, VisualScreenMask | VisualDepthMask | VisualClassMask,
278 &templ, &nvi);
279 for (int i = 0; i < nvi; i++) {
280 XRenderPictFormat *format = XRenderFindVisualFormat(d->display, xvi[i].visual);
281 if (format->type == PictTypeDirect && format->direct.alphaMask) {
282 visual = xvi[i].visualid;
283 break;
286 XFree(xvi);
288 XChangeProperty(d->display, winId(), d->visualAtom, XA_VISUALID, 32,
289 PropModeReplace, (const unsigned char*)&visual, 1);
291 if (!s_painter) {
292 s_painter = new X11EmbedPainter;
294 s_manager = this;
296 WId root = QX11Info::appRootWindow();
297 XClientMessageEvent xev;
299 xev.type = ClientMessage;
300 xev.window = root;
301 xev.message_type = XInternAtom(d->display, "MANAGER", false);
302 xev.format = 32;
303 xev.data.l[0] = CurrentTime;
304 xev.data.l[1] = d->selectionAtom;
305 xev.data.l[2] = winId();
306 xev.data.l[3] = 0;
307 xev.data.l[4] = 0;
309 XSendEvent(d->display, root, false, StructureNotifyMask, (XEvent*)&xev);
313 void FdoSelectionManagerPrivate::handleRequestDock(const XClientMessageEvent &event)
315 const WId winId = (WId)event.data.l[2];
317 if (tasks.contains(winId)) {
318 kDebug() << "got a dock request from an already existing task";
319 return;
322 FdoTask *task = new FdoTask(winId);
324 tasks[winId] = task;
325 q->connect(task, SIGNAL(taskDeleted(WId)), q, SLOT(cleanupTask(WId)));
327 emit q->taskCreated(task);
331 void FdoSelectionManager::cleanupTask(WId winId)
333 d->tasks.remove(winId);
337 void FdoSelectionManagerPrivate::handleBeginMessage(const XClientMessageEvent &event)
339 const WId winId = event.window;
341 MessageRequest request;
342 request.messageId = event.data.l[4];
343 request.timeout = event.data.l[2];
344 request.bytesRemaining = event.data.l[3];
346 if (request.bytesRemaining) {
347 messageRequests[winId] = request;
352 void FdoSelectionManagerPrivate::handleMessageData(const XClientMessageEvent &event)
354 const WId winId = event.window;
355 const char *messageData = event.data.b;
357 if (!messageRequests.contains(winId)) {
358 kDebug() << "Unexpected message data from" << winId;
359 return;
362 MessageRequest &request = messageRequests[winId];
363 const int messageSize = qMin(request.bytesRemaining, 20l);
364 request.bytesRemaining -= messageSize;
365 request.message += QByteArray(messageData, messageSize);
367 if (request.bytesRemaining == 0) {
368 createNotification(winId);
369 messageRequests.remove(winId);
374 void FdoSelectionManagerPrivate::createNotification(WId winId)
376 if (!tasks.contains(winId)) {
377 kDebug() << "message request from unknown task" << winId;
378 return;
381 MessageRequest &request = messageRequests[winId];
382 Task *task = tasks[winId];
384 QString message = QString::fromUtf8(request.message);
385 message = QTextDocument(message).toHtml();
387 FdoNotification *notification = new FdoNotification(winId, task);
388 notification->setApplicationName(task->name());
389 notification->setApplicationIcon(task->icon());
390 notification->setMessage(message);
391 notification->setTimeout(request.timeout);
393 q->connect(notification, SIGNAL(notificationDeleted(WId)), q, SLOT(cleanupNotification(WId)));
394 emit q->notificationCreated(notification);
398 void FdoSelectionManagerPrivate::handleCancelMessage(const XClientMessageEvent &event)
400 const WId winId = event.window;
401 const long messageId = event.data.l[2];
403 if (messageRequests.contains(winId) && messageRequests[winId].messageId == messageId) {
404 messageRequests.remove(winId);
405 } else if (notifications.contains(winId)) {
406 notifications.take(winId)->deleteLater();
411 void FdoSelectionManager::cleanupNotification(WId winId)
413 d->notifications.remove(winId);