First commit
[hidecallerid] / qmaemo5homescreenadaptor / qmaemo5homescreenadaptor.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the examples of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qmaemo5homescreenadaptor.h"
43
44 #include <QtCore/qsocketnotifier.h>
45 #include <QtCore/qpointer.h>
46
47 #include <QtGui/qapplication.h>
48 #include <QtGui/qx11info_x11.h>
49 #include <QtGui/qwidget.h>
50 #include <QtGui/qevent.h>
51
52 #include <X11/Xlib.h>
53 #include <X11/Xatom.h>
54 #include <X11/Xutil.h>
55
56 static QCoreApplication::EventFilter oldEventFilter;
57 static QList<QMaemo5HomescreenAdaptor *> allDesktopItems;
58
59 static Atom atomByName(const char *name)
60 {
61     Atom atom = XInternAtom(QX11Info::display(), name, False);
62     if (!atom)
63         qWarning("Unable to obtain %s atom. This class requires a running Hildon session.", name);
64
65     return atom;
66 }
67
68 enum HomescreenAtoms
69 {
70     HildonAppletId               = 0,
71     NetWmWindowType              = 1,
72     Utf8String                   = 2,
73     HildonTypeHomeApplet         = 3,
74     HildonAppletSettings         = 4,
75     HildonAppletShowSettings     = 5,
76     HildonAppletOnCurrentDesktop = 6,
77     EnumCount                    = 7
78 };
79
80 static Atom hsAtoms[EnumCount] = { 0, 0, 0, 0, 0, 0, 0 };
81
82 static void initAtoms()
83 {
84     hsAtoms[HildonAppletId] = atomByName("_HILDON_APPLET_ID");
85     hsAtoms[NetWmWindowType] = atomByName("_NET_WM_WINDOW_TYPE");
86     hsAtoms[Utf8String] = atomByName("UTF8_STRING");
87     hsAtoms[HildonTypeHomeApplet] = atomByName("_HILDON_WM_WINDOW_TYPE_HOME_APPLET");
88     hsAtoms[HildonAppletSettings] = atomByName("_HILDON_APPLET_SETTINGS");
89     hsAtoms[HildonAppletShowSettings] = atomByName("_HILDON_APPLET_SHOW_SETTINGS");
90     hsAtoms[HildonAppletOnCurrentDesktop] = atomByName("_HILDON_APPLET_ON_CURRENT_DESKTOP");
91 }
92
93 /*! \class QMaemo5HomescreenAdaptor
94
95     \brief The QMaemo5HomescreenAdaptor flags a top-level QWidget as homescreen widget
96
97     QMaemo5HomescreenAdaptor is used in conjunction with the Qt for Maemo homescreen
98     loader. It evaluates the two command line arguments "-plugin-id" and "-write-pipe"
99     to set up a Qt top-level widget as Maemo 5 homescreen widget.
100
101     Note: By default, the widget will have a black background. In order to make the
102     widget transparent, set the Qt::WA_TranslucentBackground widget attribute.
103
104     Example:
105
106     \code
107     QLabel *label = new QLabel("Hello Homescreen");
108     new QMaemo5HomescreenAdaptor(label);
109     label->show();
110     \endcode
111
112     Maemo 5 supports homescreen widgets with settings dialogs. To use it, set
113     the settingsAvailable() property and show a settings dialog when the
114     settingsRequested() signal is emitted.
115
116     Maemo 5 supports more than one homescreen. In order to determine whether
117     the homescreen widget is on the currently visible homescreen, connect to
118     the homescreenChanged() signal.
119 */
120
121 /*! \property QMaemo5HomescreenAdaptor::settingsAvailable
122
123     Set this property to true if the widget can make use of a settings dialog,
124     otherwise to false. When this property is set to true, the Maemo 5 homescreen
125     renders a small settings icon on top of the homescreen widget when the
126     user enters the desktop menu. When the user clicks that settings icon, the
127     settingsRequested() signal is emitted.
128
129     The default is false.
130
131     \sa settingsRequested()
132  */
133
134 /*! \fn void settingsRequested()
135
136     This signal is emitted every time the homescreen widget's settings icon is
137     invoked by the user. Note that this icon is only visible when the settingsAvailable()
138     property is set.
139
140     \sa settingsAvailable()
141  */
142
143 /*! \fn void homescreenChanged(bool isOnCurrentHomescreen)
144
145     This is signal is emitted when current homescreen changes and the homescreen
146     widget becomes visible or invisible. The \a isOnCurrentHomescreen argument
147     indicates whether the homescreen widget is on the current homescreen or not.
148
149     This signal can be used to start/stop background processing in order to save
150     battery life.
151  */
152
153 /*!
154     Constructs a new QMaemo5HomescreenAdaptor for the given \a widget.
155
156     Note: The widget must be a top-level widget, and must not be reparented
157     during the lifetime of this adaptor.
158
159     Note: \a widget is also the parent of this class, if the widget is destroyed,
160     so is this adaptor.
161  */
162 QMaemo5HomescreenAdaptor::QMaemo5HomescreenAdaptor(QWidget *widget)
163     : QObject(widget),
164       hasSettings(false)
165 {
166     Q_ASSERT(widget->isWindow());
167
168     if (!hsAtoms[0])
169         initAtoms();
170
171     Display *display = QX11Info::display();
172
173     const QStringList args = QApplication::arguments();
174
175     // parse the command line arguments.
176     int idx;
177     if ((idx = args.indexOf(QLatin1String("-plugin-id"))) != -1) {
178         appletId = args.value(idx + 1);
179         const QByteArray pluginId = appletId.toUtf8();
180         if (!pluginId.isEmpty()) {
181             XChangeProperty(display,
182                     widget->winId(),
183                     hsAtoms[HildonAppletId],
184                     hsAtoms[Utf8String], 8, PropModeReplace,
185                     reinterpret_cast<const unsigned char *>(pluginId.constData()),
186                     pluginId.length());
187         }
188     }
189     if ((idx = args.indexOf(QLatin1String("-write-pipe"))) != -1) {
190         bool ok;
191         int sockId = args.value(idx + 1).toInt(&ok);
192         if (ok) {
193             socketNotifier = new QSocketNotifier(sockId, QSocketNotifier::Exception, this);
194             connect(socketNotifier, SIGNAL(activated(int)), this, SLOT(socketException()));
195         }
196     }
197
198     // set the X11 atoms to flag our widget as homescreen widget
199     if (!appletId.isEmpty()) {
200         XChangeProperty(display,
201                 widget->winId(),
202                 hsAtoms[NetWmWindowType],
203                 XA_ATOM, 32, PropModeReplace,
204                 reinterpret_cast<const unsigned char *>(&hsAtoms[HildonTypeHomeApplet]),
205                 1);
206
207         updateStatus();
208
209         // --- make this window a child of root
210         XSetTransientForHint(display, widget->winId(),
211                              RootWindow(display, widget->x11Info().screen()));
212
213         // --- add an x11 event filter
214         if (!oldEventFilter)
215             oldEventFilter = QCoreApplication::instance()->setEventFilter(applicationEventFilter);
216
217         allDesktopItems.append(this);
218
219         // --- set WM input hints indicating that we don't want focus events
220         XWMHints *h = XGetWMHints(display, widget->winId());
221         XWMHints wm_hints;
222         if (!h) {
223             memset(&wm_hints, 0, sizeof(wm_hints)); // make valgrind happy
224             h = &wm_hints;
225         }
226         h->flags |= InputHint;
227         h->input = False;
228
229         XSetWMHints(display, widget->winId(), h);
230         if (h != &wm_hints)
231             XFree(h);
232
233         widget->setMouseTracking(true);
234     }
235 }
236
237 QMaemo5HomescreenAdaptor::~QMaemo5HomescreenAdaptor()
238 {
239     allDesktopItems.removeOne(this);
240 }
241
242 /*! \internal */
243 void QMaemo5HomescreenAdaptor::updateStatus()
244 {
245     if (appletId.isEmpty())
246         return;
247
248     Display *display = QX11Info::display();
249
250     // Set or remove settings property
251     if (hasSettings)
252         XChangeProperty(display,
253                 appletWidget()->winId(),
254                 hsAtoms[HildonAppletSettings],
255                 XA_CARDINAL, 32, PropModeReplace,
256                 (const unsigned char *) &(hasSettings), 1);
257     else
258         XDeleteProperty(display,
259                 appletWidget()->winId(),
260                 hsAtoms[HildonAppletSettings]);
261 }
262
263 /*! \internal */
264 void QMaemo5HomescreenAdaptor::socketException()
265 {
266     socketNotifier->setEnabled(false);
267     appletWidget()->close();
268 }
269
270 bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event)
271 {
272     return QCoreApplication::sendSpontaneousEvent(receiver, event);
273 }
274
275
276
277 /*! \internal */
278 bool QMaemo5HomescreenAdaptor::applicationEventFilter(void *message, long *result)
279 {
280     static QPointer<QWidget> lastMouseWidget;
281     bool retval = false;
282
283     if (oldEventFilter)
284         retval = oldEventFilter(message, result);
285
286     if (allDesktopItems.isEmpty())
287         return retval;
288
289     XEvent *ev = reinterpret_cast<XEvent *>(message);
290
291     // Generate a mouse release for a leave Notify (as we don't get the mouse release from X11)
292     if (ev->type == ButtonPress) {
293         QPoint globalPos( ev->xbutton.x_root, ev->xbutton.y_root);
294         QWidget *widget = QWidget::find((WId)ev->xany.window);
295         if (widget) {
296             lastMouseWidget = widget->childAt(widget->mapFromGlobal(globalPos));
297             if (!lastMouseWidget)
298                 lastMouseWidget = widget;
299         }
300
301     } else if (ev->type == ButtonRelease) {
302         lastMouseWidget = 0;
303
304     } else if (ev->type == LeaveNotify) {
305         if (lastMouseWidget) {
306             // create a mouse up event that lies in Nirvana.
307             QPoint pos(-1000, -1000);
308             QMouseEvent e(QEvent::MouseButtonRelease, pos, pos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
309             qt_sendSpontaneousEvent(lastMouseWidget, &e);
310             lastMouseWidget = 0;
311        }
312
313     } else if (ev->type == ClientMessage) {
314         XClientMessageEvent *cm = (XClientMessageEvent *)message;
315         if (cm->message_type == hsAtoms[HildonAppletShowSettings]) {
316             for (int i = 0; i < allDesktopItems.count(); ++i) {
317                 if (allDesktopItems.at(i)->appletWidget()->winId() == ev->xproperty.window) {
318                     emit allDesktopItems.at(i)->settingsRequested();
319                     retval = true;
320                 }
321             }
322         }
323     } else if (ev->type == PropertyNotify) {
324         if (ev->xproperty.atom == hsAtoms[HildonAppletOnCurrentDesktop]) {
325             for (int i = 0; i < allDesktopItems.count(); ++i) {
326                 if (allDesktopItems.at(i)->appletWidget()->winId() == ev->xproperty.window) {
327                     emit allDesktopItems.at(i)->homescreenChanged(ev->xproperty.window == 0);
328                     retval = true;
329                 }
330             }
331         }
332     }
333
334     return retval;
335 }
336