~ubuntu-branches/ubuntu/precise/unity-2d/precise-security

« back to all changes in this revision

Viewing changes to libunity-2d-private/src/applicationslist.cpp

  • Committer: Package Import Robot
  • Author(s): Didier Roche
  • Date: 2012-03-05 09:52:30 UTC
  • mfrom: (1.1.28)
  • Revision ID: package-import@ubuntu.com-20120305095230-alpmktmmfnjs2t4b
Tags: 5.6.0-0ubuntu1
* New upstream release
  - [dash] [hud] If no active window, shell processes crashes (LP: #944724)
  - Long delay on session logout (LP: #812104)
  - apps/docs are not launched when performing a search on the home lens
    (LP: #932092)
  - [unity-2d] FF exception to add HUD to Unity2d (LP: #942045)
  - There's a gap between the launcher and the upper panel (LP: #942031)
  - Dash - Genre filter category in the Music Lens should use a 3 column
    layout (LP: #841902)
  - selected lens arrow should be on top instead of bottom (LP: #932291)
  - [launcher] pixel-perfection fixes, new assets, tile & pip position,
    border line & context menu position (LP: #936881)
  - Workspace switcher icon should not generate background from icon
    (LP: #939586)
  - No glow assets still needed X Y Z (LP: #934059)
* Revert an upstream commit to ensure that there is not regression with
  libunity-core 5.4 (change will be introduced again when unity 5.6 released)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2010-2011 Canonical, Ltd.
 
3
 *
 
4
 * This program is free software; you can redistribute it and/or modify
 
5
 * it under the terms of the GNU General Public License as published by
 
6
 * the Free Software Foundation; version 3.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful,
 
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
 * GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License
 
14
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 */
 
16
 
 
17
#include "application.h"
 
18
#include "applicationslist.h"
 
19
#include "webfavorite.h"
 
20
#include "applicationslistdbus.h"
 
21
 
 
22
#include "bamf-matcher.h"
 
23
#include "bamf-application.h"
 
24
#include "gconfitem-qml-wrapper.h"
 
25
 
 
26
// unity-2d
 
27
#include "config.h"
 
28
#include <debug_p.h>
 
29
 
 
30
#include <QStringList>
 
31
#include <QDir>
 
32
#include <QDBusConnection>
 
33
#include <QDBusMessage>
 
34
#include <QFileInfo>
 
35
#include <QProcess>
 
36
#include <QX11Info>
 
37
 
 
38
#include <debug_p.h>
 
39
 
 
40
extern "C" {
 
41
#include <libsn/sn.h>
 
42
}
 
43
 
 
44
 
 
45
#define DBUS_SERVICE_UNITY "com.canonical.Unity"
 
46
#define DBUS_SERVICE_LAUNCHER_ENTRY "com.canonical.Unity.LauncherEntry"
 
47
#define DBUS_SERVICE_LAUNCHER "com.canonical.Unity.Launcher"
 
48
#define DBUS_OBJECT_LAUNCHER "/com/canonical/Unity/Launcher"
 
49
 
 
50
/* List of executables that are too generic to be matched against a single application. */
 
51
static const QStringList EXECUTABLES_BLACKLIST = (QStringList() << "xdg-open");
 
52
static const QByteArray LATEST_SETTINGS_MIGRATION = "3.2.10";
 
53
 
 
54
ApplicationsList::ApplicationsList(QObject *parent) :
 
55
    QAbstractListModel(parent)
 
56
{
 
57
    QDBusConnection session = QDBusConnection::sessionBus();
 
58
    /* FIXME: libunity will send out the Update signal for LauncherEntries
 
59
       only if it finds com.canonical.Unity on the bus, so let's just quickly
 
60
       register ourselves as Unity here. Should be moved somewhere else more proper */
 
61
    if (!session.registerService(DBUS_SERVICE_UNITY)) {
 
62
        UQ_WARNING << "The name" << DBUS_SERVICE_UNITY << "is already taken on DBUS";
 
63
    } else {
 
64
        /* Set ourselves up to receive any Update signal coming from any
 
65
           LauncherEntry */
 
66
        session.connect(QString(), QString(),
 
67
                        DBUS_SERVICE_LAUNCHER_ENTRY, "Update",
 
68
                        this, SLOT(onRemoteEntryUpdated(QString,QMap<QString,QVariant>)));
 
69
    }
 
70
 
 
71
    if (!session.registerService(DBUS_SERVICE_LAUNCHER)) {
 
72
        UQ_WARNING << "The name" << DBUS_SERVICE_LAUNCHER << "is already taken on DBUS";
 
73
    } else {
 
74
        /* Set ourselves up to receive a method call from Software Center asking us to add
 
75
           to favorites an application that is being installed and that the user requested
 
76
           to be added. */
 
77
        ApplicationsListDBUS *dbusAdapter = new ApplicationsListDBUS(this);
 
78
        if (!session.registerObject(DBUS_OBJECT_LAUNCHER, dbusAdapter,
 
79
                                    QDBusConnection::ExportAllSlots)) {
 
80
            UQ_WARNING << "The object" << DBUS_OBJECT_LAUNCHER << "on" << DBUS_SERVICE_LAUNCHER
 
81
                       << "is already present on DBUS.";
 
82
        }
 
83
    }
 
84
 
 
85
    /* Register the display to receive startup notifications */
 
86
    Display *xdisplay = QX11Info::display();
 
87
    m_snDisplay = sn_display_new(xdisplay, NULL, NULL);
 
88
    m_snContext = sn_monitor_context_new(m_snDisplay, QX11Info::appScreen(),
 
89
                                          ApplicationsList::snEventHandler,
 
90
                                          this, NULL);
 
91
    Unity2dApplication* application = Unity2dApplication::instance();
 
92
    if (application == NULL) {
 
93
        /* This can happen for example when using qmlviewer to run the launcher */
 
94
        UQ_WARNING << "The application is not an Unity2dApplication."
 
95
                      "Applications startup notifications will be ignored.";
 
96
    } else {
 
97
        application->installX11EventFilter(this);
 
98
    }
 
99
 
 
100
    /* Get the system applications data dirs and be flexible if / is not at the
 
101
       end of each path. */
 
102
    QString xdgDataDir = QFile::decodeName(getenv("XDG_DATA_DIRS"));
 
103
    if (xdgDataDir.isEmpty()) {
 
104
        xdgDataDir = "/usr/local/share/:/usr/share/";
 
105
    }
 
106
    Q_FOREACH(const QString& dirName, xdgDataDir.split(':')) {
 
107
      m_xdgApplicationDirs << QDir::cleanPath(dirName + "/applications") + "/";
 
108
    }
 
109
 
 
110
    load();
 
111
}
 
112
 
 
113
void
 
114
ApplicationsList::snEventHandler(SnMonitorEvent *event, void *user_data)
 
115
{
 
116
    /* This method is static and only forwards the event to a non static method. */
 
117
    ((ApplicationsList*)user_data)->onSnMonitorEventReceived(event);
 
118
}
 
119
 
 
120
void
 
121
ApplicationsList::onSnMonitorEventReceived(SnMonitorEvent *event)
 
122
{
 
123
    SnStartupSequence *sequence = sn_monitor_event_get_startup_sequence(event);
 
124
 
 
125
    switch (sn_monitor_event_get_type (event)) {
 
126
        case SN_MONITOR_EVENT_INITIATED:
 
127
            insertSnStartupSequence(sequence);
 
128
            break;
 
129
        case SN_MONITOR_EVENT_CHANGED:
 
130
        case SN_MONITOR_EVENT_COMPLETED:
 
131
        case SN_MONITOR_EVENT_CANCELED:
 
132
            /* These events are ignored for now. This is acceptable since the
 
133
               case of a failed application startup is handled by
 
134
               Application::launching being automatically reset to
 
135
               false after a timeout. */
 
136
            break;
 
137
    }
 
138
}
 
139
 
 
140
 
 
141
bool
 
142
ApplicationsList::x11EventFilter(XEvent* xevent)
 
143
{
 
144
    /* libsn specifies that all events need to be forwarded to
 
145
       sn_display_process_event but it is not actually necessary.
 
146
       Forwarding only the events of type ClientMessage.
 
147
     */
 
148
    if (xevent->type == ClientMessage) {
 
149
        sn_display_process_event(m_snDisplay, xevent);
 
150
    }
 
151
    return false;
 
152
}
 
153
 
 
154
void
 
155
ApplicationsList::onRemoteEntryUpdated(QString applicationURI, QMap<QString, QVariant> properties)
 
156
{
 
157
    UQ_RETURN_IF_FAIL(calledFromDBus());
 
158
    QString sender = message().service();
 
159
    QString desktopFile;
 
160
    if (applicationURI.indexOf("application://") == 0) {
 
161
        desktopFile = applicationURI.mid(14);
 
162
    } else {
 
163
        UQ_WARNING << "Ignoring update that didn't come from an application:// URI but from:" << applicationURI;
 
164
        return;
 
165
    }
 
166
 
 
167
    Q_FOREACH(Application *application, m_applications) {
 
168
        if (QFileInfo(application->desktop_file()).fileName() == desktopFile) {
 
169
            application->updateOverlaysState(sender, properties);
 
170
            return;
 
171
        }
 
172
    }
 
173
 
 
174
    UQ_WARNING << "Application sent an update but we don't seem to have it in the launcher:" << applicationURI;
 
175
}
 
176
 
 
177
ApplicationsList::~ApplicationsList()
 
178
{
 
179
    sn_monitor_context_unref(m_snContext);
 
180
    sn_display_unref(m_snDisplay);
 
181
 
 
182
    qDeleteAll(m_applications);
 
183
}
 
184
 
 
185
QString
 
186
ApplicationsList::favoriteFromDesktopFilePath(const QString& _desktopFile) const
 
187
{
 
188
    QString desktopFile(_desktopFile);
 
189
    Q_FOREACH(const QString& applicationDir, m_xdgApplicationDirs) {
 
190
        if (_desktopFile.startsWith(applicationDir)) {
 
191
            desktopFile.remove(applicationDir);
 
192
            desktopFile.replace("/", "-");
 
193
            break;
 
194
        }
 
195
    }
 
196
    return desktopFile;
 
197
}
 
198
 
 
199
void
 
200
ApplicationsList::insertApplication(Application* application)
 
201
{
 
202
    /* Insert at the end of the list. */
 
203
    int index = m_applications.size();
 
204
 
 
205
    beginInsertRows(QModelIndex(), index, index);
 
206
    m_applications.insert(index, application);
 
207
 
 
208
    if (!application->desktop_file().isEmpty()) {
 
209
        m_applicationForDesktopFile.insert(application->desktop_file(), application);
 
210
    }
 
211
    QString executable = application->executable();
 
212
    if (!executable.isEmpty() && !EXECUTABLES_BLACKLIST.contains(executable)) {
 
213
        m_applicationForExecutable.insert(executable, application);
 
214
    }
 
215
    endInsertRows();
 
216
 
 
217
    QObject::connect(application, SIGNAL(closed()), this, SLOT(onApplicationClosed()));
 
218
    QObject::connect(application, SIGNAL(stickyChanged(bool)), this, SLOT(onApplicationStickyChanged(bool)));
 
219
    QObject::connect(application, SIGNAL(launchingChanged(bool)), this, SLOT(onApplicationLaunchingChanged(bool)));
 
220
    QObject::connect(application, SIGNAL(urgentChanged(bool)), this, SLOT(onApplicationUrgentChanged(bool)));
 
221
}
 
222
 
 
223
void
 
224
ApplicationsList::removeApplication(Application* application)
 
225
{
 
226
    int index = m_applications.indexOf(application);
 
227
 
 
228
    if (index == -1) {
 
229
        /* application is not present in m_applications */
 
230
        return;
 
231
    }
 
232
 
 
233
    beginRemoveRows(QModelIndex(), index, index);
 
234
    m_applications.removeAt(index);
 
235
    m_applicationForDesktopFile.remove(application->desktop_file());
 
236
    m_applicationForExecutable.remove(application->executable());
 
237
    endRemoveRows();
 
238
 
 
239
    /* ApplicationsList::removeApplication might have been called in
 
240
       response to a signal emitted by application itself. Do not delete
 
241
       immediately to cater for this case.
 
242
    */
 
243
    application->deleteLater();
 
244
}
 
245
 
 
246
void
 
247
ApplicationsList::onApplicationUserVisibleChanged(bool user_visible)
 
248
{
 
249
    BamfApplication* bamf_application = qobject_cast<BamfApplication*>(sender());
 
250
    if (user_visible) {
 
251
        insertBamfApplication(bamf_application);
 
252
    } else {
 
253
        /* FIXME: this case has not been implemented yet but it has not been
 
254
           affecting anybody so far. */
 
255
    }
 
256
}
 
257
 
 
258
void ApplicationsList::insertBamfApplication(BamfApplication* bamf_application)
 
259
{
 
260
    /* Only insert BamfApplications for which the user_visible property is true.
 
261
       Monitor that property so that they are inserted/removed dynamically when it changes.
 
262
 
 
263
       Not doing it led to KDE3 applications not showing up in the launcher.
 
264
       Ref.: https://bugs.launchpad.net/unity-2d/+bug/719983
 
265
    */
 
266
    QObject::connect(bamf_application, SIGNAL(UserVisibleChanged(bool)), this, SLOT(onApplicationUserVisibleChanged(bool)), Qt::UniqueConnection);
 
267
 
 
268
    if (!bamf_application->user_visible()) {
 
269
        return;
 
270
    }
 
271
 
 
272
    Application* matchingApplication = NULL;
 
273
    Application* newApplication = new Application;
 
274
    newApplication->setBamfApplication(bamf_application);
 
275
 
 
276
    QString executable = newApplication->executable();
 
277
    QString desktop_file = newApplication->desktop_file();
 
278
    if (m_applicationForDesktopFile.contains(desktop_file)) {
 
279
        /* A Application with the same desktop file already exists */
 
280
        matchingApplication = m_applicationForDesktopFile[desktop_file];
 
281
    } else if (m_applicationForExecutable.contains(executable)) {
 
282
        /* A Application with the same executable already exists */
 
283
        matchingApplication = m_applicationForExecutable[executable];
 
284
        /* If the application already registered for that executable has a
 
285
           desktop file assigned then make sure that the one to be inserted
 
286
           has the same desktop file.
 
287
        */
 
288
        QString matchingDesktopFile = matchingApplication->desktop_file();
 
289
        if (!matchingDesktopFile.isEmpty() && !desktop_file.isEmpty() &&
 
290
            matchingDesktopFile != desktop_file) {
 
291
                matchingApplication = NULL;
 
292
        }
 
293
    }
 
294
 
 
295
    if (matchingApplication != NULL) {
 
296
        /* A Application that corresponds to bamf_application already exists */
 
297
        /* FIXME: this deletion blocks for a long time (around 100ms here) and
 
298
           leads to a visual glitch in the launcher when an application finished
 
299
           starting up. This is due to the deletion of the QFileSystemWatcher
 
300
           belonging to the Application. */
 
301
        delete newApplication;
 
302
        matchingApplication->setBamfApplication(bamf_application);
 
303
    } else {
 
304
        insertApplication(newApplication);
 
305
    }
 
306
}
 
307
 
 
308
void
 
309
ApplicationsList::insertFavoriteApplication(const QString& desktop_file)
 
310
{
 
311
    if (m_applicationForDesktopFile.contains(desktop_file)) {
 
312
        return;
 
313
    }
 
314
 
 
315
    /* Create a new Application */
 
316
    Application* application = new Application;
 
317
    application->setDesktopFile(desktop_file);
 
318
 
 
319
    /* If the desktop_file property is empty after setting it, it
 
320
       means glib couldn't load the desktop file (probably corrupted) */
 
321
    if (application->desktop_file().isEmpty()) {
 
322
        UQ_WARNING << "Favorite application not added due to desktop file missing or corrupted ("
 
323
                   << desktop_file << ")";
 
324
        delete application;
 
325
    } else {
 
326
        /* Register favorite desktop file into BAMF: applications with the same
 
327
           executable file will match with the given desktop file. This replicates
 
328
           the behaviour of Unity that does it automatically when calling libbamf's
 
329
           bamf_matcher_get_application_for_desktop_file.
 
330
           It fixes bug https://bugs.launchpad.net/unity-2d/+bug/739454
 
331
           The need for that API call is odd and causes at least one bug:
 
332
           https://bugs.launchpad.net/unity/+bug/762898
 
333
        */
 
334
        BamfMatcher& matcher = BamfMatcher::get_default();
 
335
        matcher.register_favorites(QStringList(application->desktop_file()));
 
336
 
 
337
        insertApplication(application);
 
338
        application->setSticky(true);
 
339
    }
 
340
}
 
341
 
 
342
void
 
343
ApplicationsList::insertWebFavorite(const QUrl& url)
 
344
{
 
345
    if (!url.isValid() || url.isRelative()) {
 
346
        UQ_WARNING << "Invalid URL:" << url;
 
347
        return;
 
348
    }
 
349
 
 
350
    Application* application = new Application;
 
351
    WebFavorite* webfav = new WebFavorite(url, application);
 
352
 
 
353
    application->setDesktopFile(webfav->desktopFile());
 
354
    insertApplication(application);
 
355
    application->setSticky(true);
 
356
}
 
357
 
 
358
void
 
359
ApplicationsList::insertSnStartupSequence(SnStartupSequence* sequence)
 
360
{
 
361
    if (sequence == NULL) {
 
362
        return;
 
363
    }
 
364
 
 
365
    QString executable = sn_startup_sequence_get_binary_name(sequence);
 
366
    if (EXECUTABLES_BLACKLIST.contains(executable)) {
 
367
        return;
 
368
    }
 
369
 
 
370
    if (m_applicationForExecutable.contains(executable)) {
 
371
        /* A Application with the same executable already exists */
 
372
        m_applicationForExecutable[executable]->setSnStartupSequence(sequence);
 
373
    } else {
 
374
        /* Create a new Application and append it to the list */
 
375
        Application* newApplication = new Application;
 
376
        newApplication->setSnStartupSequence(sequence);
 
377
        insertApplication(newApplication);
 
378
    }
 
379
}
 
380
 
 
381
void
 
382
ApplicationsList::load()
 
383
{
 
384
    /* Migrate the favorites if needed and ignore errors */
 
385
    QByteArray latest_migration = launcherConfiguration().property("favoriteMigration").toString().toAscii();
 
386
    if (latest_migration < LATEST_SETTINGS_MIGRATION) {
 
387
        if(QProcess::execute(INSTALL_PREFIX "/lib/unity/migrate_favorites.py") != 0) {
 
388
            UQ_WARNING << "Unable to run the migrate favorites tool successfully";
 
389
        }
 
390
    }
 
391
 
 
392
    /* Insert favorites */
 
393
    QString desktop_file;
 
394
    QStringList favorites = launcherConfiguration().property("favorites").toStringList();
 
395
 
 
396
    Q_FOREACH(const QString& favorite, favorites) {
 
397
       insertFavoriteApplication(favorite);
 
398
    }
 
399
 
 
400
    /* Insert running applications from Bamf */
 
401
    BamfMatcher& matcher = BamfMatcher::get_default();
 
402
    QScopedPointer<BamfApplicationList> running_applications(matcher.running_applications());
 
403
    BamfApplication* bamf_application;
 
404
 
 
405
    for(int i=0; i<running_applications->size(); i++) {
 
406
        bamf_application = running_applications->at(i);
 
407
        insertBamfApplication(bamf_application);
 
408
    }
 
409
 
 
410
    QObject::connect(&matcher, SIGNAL(ViewOpened(BamfView*)), SLOT(onBamfViewOpened(BamfView*)));
 
411
}
 
412
 
 
413
void
 
414
ApplicationsList::onBamfViewOpened(BamfView* bamf_view)
 
415
{
 
416
    /* Make sure bamf_view is in fact a BamfApplication */
 
417
    BamfApplication* bamf_application;
 
418
    bamf_application = dynamic_cast<BamfApplication*>(bamf_view);
 
419
 
 
420
    if (bamf_application == NULL) {
 
421
        return;
 
422
    }
 
423
 
 
424
    insertBamfApplication(bamf_application);
 
425
}
 
426
 
 
427
void ApplicationsList::onApplicationClosed()
 
428
{
 
429
    Application* application = static_cast<Application*>(sender());
 
430
 
 
431
    if (!application->sticky() && !application->running()) {
 
432
        removeApplication(application);
 
433
    }
 
434
}
 
435
 
 
436
void
 
437
ApplicationsList::onApplicationStickyChanged(bool sticky)
 
438
{
 
439
    Application* application = static_cast<Application*>(sender());
 
440
 
 
441
    writeFavoritesToGConf();
 
442
 
 
443
    if (!sticky && !application->running()) {
 
444
        removeApplication(application);
 
445
    }
 
446
}
 
447
 
 
448
void
 
449
ApplicationsList::onApplicationLaunchingChanged(bool launching)
 
450
{
 
451
    Application* application = static_cast<Application*>(sender());
 
452
 
 
453
    if (!application->sticky() && !application->running() && !application->launching()) {
 
454
        removeApplication(application);
 
455
    }
 
456
}
 
457
 
 
458
void
 
459
ApplicationsList::onApplicationUrgentChanged(bool urgent)
 
460
{
 
461
    Application* application = static_cast<Application*>(sender());
 
462
    if (urgent) {
 
463
        Q_EMIT applicationBecameUrgent(m_applications.indexOf(application));
 
464
    }
 
465
}
 
466
 
 
467
void
 
468
ApplicationsList::writeFavoritesToGConf()
 
469
{
 
470
    QStringList favorites;
 
471
 
 
472
    Q_FOREACH(Application *application, m_applications) {
 
473
        QString desktop_file = application->desktop_file();
 
474
        if (application->sticky()) {
 
475
            favorites.append(favoriteFromDesktopFilePath(desktop_file));
 
476
        }
 
477
    }
 
478
 
 
479
    launcherConfiguration().blockSignals(true);
 
480
    launcherConfiguration().setProperty("favorites", QVariant(favorites));
 
481
    launcherConfiguration().blockSignals(false);
 
482
}
 
483
 
 
484
int
 
485
ApplicationsList::rowCount(const QModelIndex &parent) const
 
486
{
 
487
    Q_UNUSED(parent)
 
488
 
 
489
    return m_applications.size();
 
490
}
 
491
 
 
492
QVariant
 
493
ApplicationsList::data(const QModelIndex &index, int role) const
 
494
{
 
495
    Q_UNUSED(role);
 
496
 
 
497
    if (!index.isValid()) {
 
498
        return QVariant();
 
499
    }
 
500
 
 
501
    return QVariant::fromValue(m_applications.at(index.row()));
 
502
}
 
503
 
 
504
void
 
505
ApplicationsList::move(int from, int to)
 
506
{
 
507
    QModelIndex parent;
 
508
    /* When moving an item down, the destination index needs to be incremented
 
509
       by one, as explained in the documentation:
 
510
       http://doc.qt.nokia.com/qabstractitemmodel.html#beginMoveRows */
 
511
    beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0));
 
512
    m_applications.move(from, to);
 
513
    endMoveRows();
 
514
 
 
515
    if (m_applications[from]->sticky() || m_applications[to]->sticky()) {
 
516
        /* Update favorites only if at least one of the applications is a favorite */
 
517
        writeFavoritesToGConf();
 
518
    }
 
519
}
 
520
 
 
521
#include "applicationslist.moc"