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 "fdoselectionmanager.h"
24
#include "x11embedpainter.h"
28
#include <QtCore/QCoreApplication>
29
#include <QtCore/QHash>
30
#include <QtCore/QTimer>
32
#include <QtGui/QTextDocument>
33
#include <QtGui/QX11Info>
35
#include <KIconLoader>
37
#include <Plasma/DataEngine>
38
#include <Plasma/DataEngineManager>
39
#include <Plasma/ServiceJob>
41
#include <config-X11.h>
44
#include <X11/Xatom.h>
45
#include <X11/extensions/Xrender.h>
48
# include <X11/extensions/Xfixes.h>
52
# include <X11/extensions/Xdamage.h>
55
#ifdef HAVE_XCOMPOSITE
56
# include <X11/extensions/Xcomposite.h>
59
#define SYSTEM_TRAY_REQUEST_DOCK 0
60
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
61
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
67
static FdoSelectionManager *s_manager = 0;
68
static X11EmbedPainter *s_painter = 0;
70
#if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
77
static int damageEventBase = 0;
78
static QMap<WId, DamageWatch*> damageWatches;
79
static QCoreApplication::EventFilter oldEventFilter;
81
// Global event filter for intercepting damage events
82
static bool x11EventFilter(void *message, long int *result)
84
XEvent *event = reinterpret_cast<XEvent*>(message);
85
if (event->type == damageEventBase + XDamageNotify) {
86
XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>(event);
87
if (DamageWatch *damageWatch = damageWatches.value(e->drawable)) {
88
// Create a new region and empty the damage region into it.
89
// The window is small enough that we don't really care about the region;
90
// we'll just throw it away and schedule a full repaint of the container.
91
XserverRegion region = XFixesCreateRegion(e->display, 0, 0);
92
XDamageSubtract(e->display, e->damage, None, region);
93
XFixesDestroyRegion(e->display, region);
94
damageWatch->container->update();
98
if (oldEventFilter && oldEventFilter != x11EventFilter) {
99
return oldEventFilter(message, result);
107
struct MessageRequest
116
class FdoSelectionManagerPrivate
119
FdoSelectionManagerPrivate(FdoSelectionManager *q)
121
notificationsEngine(0),
124
display = QX11Info::display();
125
selectionAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_S" + QByteArray::number(QX11Info::appScreen()), false);
126
opcodeAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_OPCODE", false);
127
messageAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_MESSAGE_DATA", false);
128
visualAtom = XInternAtom(display, "_NET_SYSTEM_TRAY_VISUAL", false);
130
#if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
131
int eventBase, errorBase;
132
bool haveXfixes = XFixesQueryExtension(display, &eventBase, &errorBase);
133
bool haveXdamage = XDamageQueryExtension(display, &damageEventBase, &errorBase);
134
bool haveXComposite = XCompositeQueryExtension(display, &eventBase, &errorBase);
136
if (haveXfixes && haveXdamage && haveXComposite) {
137
haveComposite = true;
138
oldEventFilter = QCoreApplication::instance()->setEventFilter(x11EventFilter);
143
void createNotification(WId winId);
145
void handleRequestDock(const XClientMessageEvent &event);
146
void handleBeginMessage(const XClientMessageEvent &event);
147
void handleMessageData(const XClientMessageEvent &event);
148
void handleCancelMessage(const XClientMessageEvent &event);
156
QHash<WId, MessageRequest> messageRequests;
157
QHash<WId, FdoTask*> tasks;
159
FdoSelectionManager *q;
160
Plasma::DataEngine *notificationsEngine;
165
FdoSelectionManager::FdoSelectionManager()
166
: d(new FdoSelectionManagerPrivate(this))
168
// Init the selection later just to ensure that no signals are sent
169
// until after construction is done and the creating object has a
170
// chance to connect.
171
QTimer::singleShot(0, this, SLOT(initSelection()));
175
FdoSelectionManager::~FdoSelectionManager()
177
#if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
178
if (d->haveComposite && QCoreApplication::instance()) {
179
QCoreApplication::instance()->setEventFilter(oldEventFilter);
183
if (s_manager == this) {
192
FdoSelectionManager *FdoSelectionManager::manager()
197
X11EmbedPainter *FdoSelectionManager::painter()
202
void FdoSelectionManager::addDamageWatch(QWidget *container, WId client)
204
#if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
205
DamageWatch *damage = new DamageWatch;
206
damage->container = container;
207
damage->damage = XDamageCreate(QX11Info::display(), client, XDamageReportNonEmpty);
208
damageWatches.insert(client, damage);
212
void FdoSelectionManager::removeDamageWatch(QWidget *container)
214
#if defined(HAVE_XFIXES) && defined(HAVE_XDAMAGE) && defined(HAVE_XCOMPOSITE)
215
for (QMap<WId, DamageWatch*>::Iterator it = damageWatches.begin(); it != damageWatches.end(); ++it)
217
DamageWatch *damage = *(it);
218
if (damage->container == container) {
219
XDamageDestroy(QX11Info::display(), damage->damage);
220
damageWatches.erase(it);
229
bool FdoSelectionManager::haveComposite() const
231
return d->haveComposite;
235
bool FdoSelectionManager::x11Event(XEvent *event)
237
if (event->type == ClientMessage) {
238
if (event->xclient.message_type == d->opcodeAtom) {
239
switch (event->xclient.data.l[1]) {
240
case SYSTEM_TRAY_REQUEST_DOCK:
241
d->handleRequestDock(event->xclient);
243
case SYSTEM_TRAY_BEGIN_MESSAGE:
244
d->handleBeginMessage(event->xclient);
246
case SYSTEM_TRAY_CANCEL_MESSAGE:
247
d->handleCancelMessage(event->xclient);
250
} else if (event->xclient.message_type == d->messageAtom) {
251
d->handleMessageData(event->xclient);
256
return QWidget::x11Event(event);
260
void FdoSelectionManager::initSelection()
262
XSetSelectionOwner(d->display, d->selectionAtom, winId(), CurrentTime);
264
WId selectionOwner = XGetSelectionOwner(d->display, d->selectionAtom);
265
if (selectionOwner != winId()) {
266
// FIXME: Hmmm... Reading the docs on XSetSelectionOwner,
267
// this should not be possible.
268
kDebug() << "Tried to set selection owner to" << winId() << "but it is set to" << selectionOwner;
272
// Prefer the ARGB32 visual if available
274
VisualID visual = XVisualIDFromVisual((Visual*)QX11Info::appVisual());
276
templ.visualid = visual;
277
XVisualInfo *xvi = XGetVisualInfo(d->display, VisualIDMask, &templ, &nvi);
278
if (xvi && xvi[0].depth > 16) {
279
templ.screen = xvi[0].screen;
281
templ.c_class = TrueColor;
283
xvi = XGetVisualInfo(d->display, VisualScreenMask | VisualDepthMask | VisualClassMask,
285
for (int i = 0; i < nvi; i++) {
286
XRenderPictFormat *format = XRenderFindVisualFormat(d->display, xvi[i].visual);
287
if (format && format->type == PictTypeDirect && format->direct.alphaMask) {
288
visual = xvi[i].visualid;
294
XChangeProperty(d->display, winId(), d->visualAtom, XA_VISUALID, 32,
295
PropModeReplace, (const unsigned char*)&visual, 1);
298
s_painter = new X11EmbedPainter;
302
WId root = QX11Info::appRootWindow();
303
XClientMessageEvent xev;
305
xev.type = ClientMessage;
307
xev.message_type = XInternAtom(d->display, "MANAGER", false);
309
xev.data.l[0] = CurrentTime;
310
xev.data.l[1] = d->selectionAtom;
311
xev.data.l[2] = winId();
315
XSendEvent(d->display, root, false, StructureNotifyMask, (XEvent*)&xev);
319
void FdoSelectionManagerPrivate::handleRequestDock(const XClientMessageEvent &event)
321
const WId winId = (WId)event.data.l[2];
323
if (tasks.contains(winId)) {
324
kDebug() << "got a dock request from an already existing task";
328
FdoTask *task = new FdoTask(winId, q);
331
q->connect(task, SIGNAL(taskDeleted(WId)), q, SLOT(cleanupTask(WId)));
333
emit q->taskCreated(task);
337
void FdoSelectionManager::cleanupTask(WId winId)
339
d->tasks.remove(winId);
343
void FdoSelectionManagerPrivate::handleBeginMessage(const XClientMessageEvent &event)
345
const WId winId = event.window;
347
MessageRequest request;
348
request.messageId = event.data.l[4];
349
request.timeout = event.data.l[2];
350
request.bytesRemaining = event.data.l[3];
352
if (request.bytesRemaining) {
353
messageRequests[winId] = request;
358
void FdoSelectionManagerPrivate::handleMessageData(const XClientMessageEvent &event)
360
const WId winId = event.window;
361
const char *messageData = event.data.b;
363
if (!messageRequests.contains(winId)) {
364
kDebug() << "Unexpected message data from" << winId;
368
MessageRequest &request = messageRequests[winId];
369
const int messageSize = qMin(request.bytesRemaining, 20l);
370
request.bytesRemaining -= messageSize;
371
request.message += QByteArray(messageData, messageSize);
373
if (request.bytesRemaining == 0) {
374
createNotification(winId);
375
messageRequests.remove(winId);
380
void FdoSelectionManagerPrivate::createNotification(WId winId)
382
if (!tasks.contains(winId)) {
383
kDebug() << "message request from unknown task" << winId;
387
MessageRequest &request = messageRequests[winId];
388
Task *task = tasks[winId];
390
QString message = QString::fromUtf8(request.message);
391
message = QTextDocument(message).toHtml();
393
if (!notificationsEngine) {
394
notificationsEngine = Plasma::DataEngineManager::self()->loadEngine("notifications");
396
//FIXME: who is the source in this case?
397
Plasma::Service *service = notificationsEngine->serviceForSource("notification");
398
KConfigGroup op = service->operationDescription("createNotification");
401
op.writeEntry("appName", task->name());
402
//FIXME: find a way to pass icons trough here
403
op.writeEntry("appIcon", task->name());
405
//op.writeEntry("summary", task->name());
406
op.writeEntry("body", message);
407
op.writeEntry("timeout", (int)request.timeout);
408
KJob *job = service->startOperationCall(op);
409
QObject::connect(job, SIGNAL(finished(KJob*)), service, SLOT(deleteLater()));
412
kDebug() << "invalid operation";
417
void FdoSelectionManagerPrivate::handleCancelMessage(const XClientMessageEvent &event)
419
const WId winId = event.window;
420
const long messageId = event.data.l[2];
422
if (messageRequests.contains(winId) && messageRequests[winId].messageId == messageId) {
423
messageRequests.remove(winId);