1 /***************************************************************************
2 * fdoselectionmanager.cpp *
4 * Copyright (C) 2008 Jason Stubbs <jasonbstubbs@gmail.com> *
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. *
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. *
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"
25 #include "x11embedpainter.h"
29 #include <QtCore/QCoreApplication>
30 #include <QtCore/QHash>
31 #include <QtCore/QTimer>
33 #include <QtGui/QTextDocument>
34 #include <QtGui/QX11Info>
38 #include <config-X11.h>
41 #include <X11/Xatom.h>
42 #include <X11/extensions/Xrender.h>
45 # include <X11/extensions/Xfixes.h>
49 # include <X11/extensions/Xdamage.h>
52 #ifdef HAVE_XCOMPOSITE
53 # include <X11/extensions/Xcomposite.h>
56 #define SYSTEM_TRAY_REQUEST_DOCK 0
57 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
58 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
64 static FdoSelectionManager
*s_manager
= 0;
65 static X11EmbedPainter
*s_painter
= 0;
67 #if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
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
);
104 struct MessageRequest
113 class FdoSelectionManagerPrivate
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
);
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
);
151 QHash
<WId
, MessageRequest
> messageRequests
;
152 QHash
<WId
, FdoTask
*> tasks
;
153 QHash
<WId
, FdoNotification
*> notifications
;
155 FdoSelectionManager
*q
;
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
);
177 if (s_manager
== this) {
186 FdoSelectionManager
*FdoSelectionManager::manager()
191 X11EmbedPainter
*FdoSelectionManager::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
);
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
);
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
);
237 case SYSTEM_TRAY_BEGIN_MESSAGE
:
238 d
->handleBeginMessage(event
->xclient
);
240 case SYSTEM_TRAY_CANCEL_MESSAGE
:
241 d
->handleCancelMessage(event
->xclient
);
244 } else if (event
->xclient
.message_type
== d
->messageAtom
) {
245 d
->handleMessageData(event
->xclient
);
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
;
266 // Prefer the ARGB32 visual if available
268 VisualID visual
= XVisualIDFromVisual((Visual
*)QX11Info::appVisual());
270 templ
.visualid
= visual
;
271 XVisualInfo
*xvi
= XGetVisualInfo(d
->display
, VisualIDMask
, &templ
, &nvi
);
273 templ
.screen
= xvi
[0].screen
;
274 templ
.depth
= xvi
[0].depth
;
275 templ
.c_class
= xvi
[0].c_class
;
277 xvi
= XGetVisualInfo(d
->display
, VisualScreenMask
| VisualDepthMask
| VisualClassMask
,
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
;
288 XChangeProperty(d
->display
, winId(), d
->visualAtom
, XA_VISUALID
, 32,
289 PropModeReplace
, (const unsigned char*)&visual
, 1);
292 s_painter
= new X11EmbedPainter
;
296 WId root
= QX11Info::appRootWindow();
297 XClientMessageEvent xev
;
299 xev
.type
= ClientMessage
;
301 xev
.message_type
= XInternAtom(d
->display
, "MANAGER", false);
303 xev
.data
.l
[0] = CurrentTime
;
304 xev
.data
.l
[1] = d
->selectionAtom
;
305 xev
.data
.l
[2] = winId();
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";
322 FdoTask
*task
= new FdoTask(winId
);
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
;
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
;
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
);