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

« back to all changes in this revision

Viewing changes to libunity-2d-private/Unity2d/launcherapplication.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Didier Roche
  • Date: 2011-08-01 19:53:40 UTC
  • mfrom: (1.1.16 upstream)
  • Revision ID: james.westby@ubuntu.com-20110801195340-se0ukbsb0z3b6an1
Tags: 3.8.14-0ubuntu1
* New upstream release:
  - [launcher] Need to press Alt+F1 twice to show launcher (LP: #812787)
* debian/control:
  - build on latest libunity-core-4.0-dev and latest libnux-1.0-dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (C) 2010 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
 
/* Note regarding the use of wnck: it is critically important that the client
18
 
   type be set to pager because wnck will pass that type over to the window
19
 
   manager through XEvents.
20
 
   Window managers tend to respect orders from pagers to the letter by for
21
 
   example bypassing focus stealing prevention. Compiz does exactly that in
22
 
   src/event.c:handleEvent(…) in the ClientMessage case (line 1702). Metacity
23
 
   has a similar policy in src/core/window.c:window_activate(…) (line 2951).
24
 
 
25
 
   The client type has already been set in Unity2dPlugin::initializeEngine(…),
26
 
   and the corresponding shared library (libunity-2d-private-qml.so) is loaded
27
 
   via QML’s import statement (`import Unity2d 1.0`), so there is no need to
28
 
   set it again here.
29
 
*/
30
 
 
31
 
#include "launcherapplication.h"
32
 
#include "launchermenu.h"
33
 
#include "launcherutility.h"
34
 
#include "bamf-matcher.h"
35
 
#include "bamf-indicator.h"
36
 
 
37
 
#include "dbusmenuimporter.h"
38
 
 
39
 
#include <X11/X.h>
40
 
 
41
 
// libunity-2d
42
 
#include <unity2dtr.h>
43
 
#include <debug_p.h>
44
 
 
45
 
// Qt
46
 
#include <Qt>
47
 
#include <QDebug>
48
 
#include <QAction>
49
 
#include <QDBusInterface>
50
 
#include <QDBusReply>
51
 
#include <QDBusServiceWatcher>
52
 
#include <QFile>
53
 
#include <QFileSystemWatcher>
54
 
#include <QScopedPointer>
55
 
#include <QX11Info>
56
 
 
57
 
extern "C" {
58
 
#include <gdk/gdk.h>
59
 
#include <libsn/sn.h>
60
 
}
61
 
 
62
 
const char* SHORTCUT_NICK_PROPERTY = "nick";
63
 
 
64
 
LauncherApplication::LauncherApplication()
65
 
    : m_application(NULL)
66
 
    , m_desktopFileWatcher(NULL)
67
 
    , m_sticky(false)
68
 
    , m_has_visible_window(false)
69
 
    , m_progress(0), m_progressBarVisible(false)
70
 
    , m_counter(0), m_counterVisible(false)
71
 
    , m_emblem(QString()), m_emblemVisible(false)
72
 
    , m_forceUrgent(false)
73
 
    , m_dynamicQuicklistServiceWatcher(NULL)
74
 
{
75
 
    m_launching_timer.setSingleShot(true);
76
 
    m_launching_timer.setInterval(8000);
77
 
    QObject::connect(&m_launching_timer, SIGNAL(timeout()), this, SLOT(onLaunchingTimeouted()));
78
 
}
79
 
 
80
 
LauncherApplication::LauncherApplication(const LauncherApplication& other)
81
 
{
82
 
    /* FIXME: a number of members are not copied over */
83
 
    QObject::connect(&m_launching_timer, SIGNAL(timeout()), this, SLOT(onLaunchingTimeouted()));
84
 
    if (other.m_application != NULL) {
85
 
        setBamfApplication(other.m_application);
86
 
    }
87
 
}
88
 
 
89
 
LauncherApplication::~LauncherApplication()
90
 
{
91
 
}
92
 
 
93
 
bool
94
 
LauncherApplication::active() const
95
 
{
96
 
    if (m_application != NULL) {
97
 
        return m_application->active();
98
 
    }
99
 
 
100
 
    return false;
101
 
}
102
 
 
103
 
bool
104
 
LauncherApplication::running() const
105
 
{
106
 
    if (m_application != NULL) {
107
 
        return m_application->running();
108
 
    }
109
 
 
110
 
    return false;
111
 
}
112
 
 
113
 
int
114
 
LauncherApplication::windowCount() const
115
 
{
116
 
    if (m_application == NULL) {
117
 
        return 0;
118
 
    }
119
 
 
120
 
    QScopedPointer<BamfWindowList> windows(m_application->windows());
121
 
    return windows->size();
122
 
}
123
 
 
124
 
bool
125
 
LauncherApplication::urgent() const
126
 
{
127
 
    if (m_forceUrgent) {
128
 
        return true;
129
 
    }
130
 
 
131
 
    if (m_application != NULL) {
132
 
        return m_application->urgent();
133
 
    }
134
 
 
135
 
    return false;
136
 
}
137
 
 
138
 
void
139
 
LauncherApplication::beginForceUrgent(int duration)
140
 
{
141
 
    bool wasUrgent = urgent();
142
 
    m_forceUrgent = true;
143
 
    if (wasUrgent != urgent()) {
144
 
        Q_EMIT urgentChanged(urgent());
145
 
    }
146
 
    QTimer::singleShot(duration, this, SLOT(endForceUrgent()));
147
 
}
148
 
 
149
 
void
150
 
LauncherApplication::endForceUrgent()
151
 
{
152
 
    bool wasUrgent = urgent();
153
 
    m_forceUrgent = false;
154
 
    if (wasUrgent != urgent()) {
155
 
        Q_EMIT urgentChanged(urgent());
156
 
    }
157
 
}
158
 
 
159
 
bool
160
 
LauncherApplication::sticky() const
161
 
{
162
 
    return m_sticky;
163
 
}
164
 
 
165
 
QString
166
 
LauncherApplication::name() const
167
 
{
168
 
    if (sticky() && (m_appInfo != NULL)) {
169
 
        return QString::fromUtf8(g_app_info_get_name(m_appInfo.data()));
170
 
    }
171
 
 
172
 
    if (m_application != NULL) {
173
 
        return m_application->name();
174
 
    }
175
 
 
176
 
    if (m_appInfo != NULL) {
177
 
        return QString::fromUtf8(g_app_info_get_name(m_appInfo.data()));
178
 
    }
179
 
 
180
 
    if (m_snStartupSequence != NULL) {
181
 
        return QString::fromUtf8(sn_startup_sequence_get_name(m_snStartupSequence.data()));
182
 
    }
183
 
 
184
 
    return QString("");
185
 
}
186
 
 
187
 
QString
188
 
LauncherApplication::icon() const
189
 
{
190
 
    if (sticky() && (m_appInfo != NULL)) {
191
 
        GCharPointer ptr(g_icon_to_string(g_app_info_get_icon(m_appInfo.data())));
192
 
        return QString::fromUtf8(ptr.data());
193
 
    }
194
 
 
195
 
    if (m_application != NULL) {
196
 
        return m_application->icon();
197
 
    }
198
 
 
199
 
    if (m_appInfo != NULL) {
200
 
        GCharPointer ptr(g_icon_to_string(g_app_info_get_icon(m_appInfo.data())));
201
 
        return QString::fromUtf8(ptr.data());
202
 
    }
203
 
 
204
 
    if (m_snStartupSequence != NULL) {
205
 
        return QString::fromUtf8(sn_startup_sequence_get_icon_name(m_snStartupSequence.data()));
206
 
    }
207
 
 
208
 
    return QString("");
209
 
}
210
 
 
211
 
QString
212
 
LauncherApplication::application_type() const
213
 
{
214
 
    if (m_application != NULL) {
215
 
        return m_application->application_type();
216
 
    }
217
 
 
218
 
    return QString("");
219
 
}
220
 
 
221
 
QString
222
 
LauncherApplication::desktop_file() const
223
 
{
224
 
    if (m_application != NULL) {
225
 
        return m_application->desktop_file();
226
 
    }
227
 
 
228
 
    if (m_appInfo != NULL) {
229
 
        return QString::fromUtf8(g_desktop_app_info_get_filename((GDesktopAppInfo*)m_appInfo.data()));
230
 
    }
231
 
 
232
 
    return QString("");
233
 
}
234
 
 
235
 
QString
236
 
LauncherApplication::executable() const
237
 
{
238
 
    if (m_appInfo != NULL) {
239
 
        return QString::fromUtf8(g_app_info_get_executable(m_appInfo.data()));
240
 
    }
241
 
 
242
 
    if (m_snStartupSequence != NULL) {
243
 
        return QString::fromUtf8(sn_startup_sequence_get_binary_name(m_snStartupSequence.data()));
244
 
    }
245
 
 
246
 
    return QString("");
247
 
}
248
 
 
249
 
void
250
 
LauncherApplication::setSticky(bool sticky)
251
 
{
252
 
    if (sticky == m_sticky) {
253
 
        return;
254
 
    }
255
 
 
256
 
    m_sticky = sticky;
257
 
    stickyChanged(sticky);
258
 
}
259
 
 
260
 
void
261
 
LauncherApplication::setDesktopFile(const QString& desktop_file)
262
 
{
263
 
    QString oldDesktopFile = this->desktop_file();
264
 
 
265
 
    QByteArray byte_array = desktop_file.toUtf8();
266
 
    gchar *file = byte_array.data();
267
 
 
268
 
    if(desktop_file.startsWith("/")) {
269
 
        /* It looks like a full path to a desktop file */
270
 
        m_appInfo.reset((GAppInfo*)g_desktop_app_info_new_from_filename(file));
271
 
    } else {
272
 
        /* It might just be a desktop file name; let GIO look for the actual
273
 
           desktop file for us */
274
 
        /* The docs for g_desktop_app_info_new() says it respects "-" to "/"
275
 
           substitution as per XDG Menu Spec, but it only seems to work for
276
 
           exactly 1 substitution where as Wine programs often require many.
277
 
           Bottom line: We must do some manual trial and error to find desktop
278
 
           files in deeply nested directories.
279
 
 
280
 
           Same workaround is implemented in Unity: plugins/unityshell/src/PlacesView.cpp:731
281
 
           References:
282
 
           https://bugzilla.gnome.org/show_bug.cgi?id=654566
283
 
           https://bugs.launchpad.net/unity-2d/+bug/794471
284
 
        */
285
 
        int slash_index;
286
 
        do {
287
 
            m_appInfo.reset((GAppInfo*)g_desktop_app_info_new(file));
288
 
            slash_index = byte_array.indexOf("-");
289
 
            if (slash_index == -1) {
290
 
                break;
291
 
            }
292
 
            byte_array.replace(slash_index, 1, "/");
293
 
            file = byte_array.data();
294
 
        } while (m_appInfo.isNull());
295
 
    }
296
 
 
297
 
    /* setDesktopFile(…) may be called with the same desktop file, when e.g. the
298
 
       contents of the file have changed. Some properties may have changed. */
299
 
    QString newDesktopFile = this->desktop_file();
300
 
    if (newDesktopFile != oldDesktopFile) {
301
 
        Q_EMIT desktopFileChanged(newDesktopFile);
302
 
    }
303
 
    /* Emit the Changed signal on all properties that can depend on m_appInfo
304
 
       m_application's properties take precedence over m_appInfo's
305
 
    */
306
 
    if (m_appInfo != NULL) {
307
 
        if (m_application == NULL) {
308
 
            Q_EMIT nameChanged(name());
309
 
            Q_EMIT iconChanged(icon());
310
 
        }
311
 
        Q_EMIT executableChanged(executable());
312
 
    }
313
 
 
314
 
    /* Update the list of static shortcuts
315
 
       (quicklist entries defined in the desktop file). */
316
 
    m_staticShortcuts.reset(indicator_desktop_shortcuts_new(newDesktopFile.toUtf8().constData(), "Unity"));
317
 
 
318
 
    monitorDesktopFile(newDesktopFile);
319
 
}
320
 
 
321
 
void
322
 
LauncherApplication::monitorDesktopFile(const QString& path)
323
 
{
324
 
    /* Monitor the desktop file for live changes */
325
 
    if (m_desktopFileWatcher == NULL) {
326
 
        /* FIXME: deleting a QFileSystemWatcher can be quite slow (sometimes
327
 
           around 100ms on a powerful computer) and can provoke visual glitches
328
 
           where the user interface is blocked for a short moment.
329
 
         */
330
 
        m_desktopFileWatcher = new QFileSystemWatcher(this);
331
 
        connect(m_desktopFileWatcher, SIGNAL(fileChanged(const QString&)),
332
 
                SLOT(onDesktopFileChanged(const QString&)));
333
 
    }
334
 
 
335
 
    /* If the file is already being monitored, we shouldn’t need to do anything.
336
 
       However it seems that in some cases, a change to the file will stop
337
 
       emiting further fileChanged signals, despite the file still being in the
338
 
       list of monitored files. This is the case when the desktop file is being
339
 
       edited in gedit for example. This may be a bug in QT itself.
340
 
       To work around this issue, remove the path and add it again. */
341
 
    if (m_desktopFileWatcher->files().contains(path)) {
342
 
        m_desktopFileWatcher->removePath(path);
343
 
    }
344
 
 
345
 
    if (!path.isEmpty()) {
346
 
        m_desktopFileWatcher->addPath(path);
347
 
    }
348
 
}
349
 
 
350
 
void
351
 
LauncherApplication::onDesktopFileChanged(const QString& path)
352
 
{
353
 
    if (m_desktopFileWatcher->files().contains(path) || QFile::exists(path)) {
354
 
        /* The contents of the file have changed. */
355
 
        setDesktopFile(path);
356
 
    } else {
357
 
        /* The desktop file has been deleted.
358
 
           This can happen in a number of cases:
359
 
            - the package it belongs to has been uninstalled
360
 
            - the package it belongs to has been upgraded, in which case it is
361
 
              likely that the desktop file has been removed and a new version of
362
 
              it has been installed in place of the old version
363
 
            - the file has been written to using an editor that first saves to a
364
 
              temporary file and then moves this temporary file to the
365
 
              destination file, which effectively results in the file being
366
 
              temporarily deleted (vi for example does that, whereas gedit
367
 
              doesn’t)
368
 
           In the first case, we want to remove the application from the
369
 
           launcher. In the last two cases, we need to consider that the desktop
370
 
           file’s contents have changed. At this point there is no way to be
371
 
           sure that the file has been permanently removed, so we want to give
372
 
           the application a grace period before checking for real deletion. */
373
 
        QTimer::singleShot(1000, this, SLOT(checkDesktopFileReallyRemoved()));
374
 
    }
375
 
}
376
 
 
377
 
void
378
 
LauncherApplication::checkDesktopFileReallyRemoved()
379
 
{
380
 
    QString path = desktop_file();
381
 
    if (QFile::exists(path)) {
382
 
        /* The desktop file hasn’t really been removed, it was only temporarily
383
 
           deleted. */
384
 
        setDesktopFile(path);
385
 
    } else {
386
 
        /* The desktop file has really been removed. */
387
 
        setSticky(false);
388
 
    }
389
 
}
390
 
 
391
 
void
392
 
LauncherApplication::setBamfApplication(BamfApplication *application)
393
 
{
394
 
    if (application == NULL) {
395
 
        return;
396
 
    }
397
 
 
398
 
    m_application = application;
399
 
    if (!sticky()) {
400
 
        setDesktopFile(application->desktop_file());
401
 
    }
402
 
 
403
 
    QObject::connect(application, SIGNAL(ActiveChanged(bool)), this, SIGNAL(activeChanged(bool)));
404
 
 
405
 
    /* FIXME: a bug somewhere makes connecting to the Closed() signal not work even though
406
 
              the emit Closed() in bamf-view.cpp is reached. */
407
 
    /* Connect first the onBamfApplicationClosed slot, then the runningChanged
408
 
       signal, to avoid a race condition when an application is closed.
409
 
       See https://launchpad.net/bugs/634057 */
410
 
    QObject::connect(application, SIGNAL(RunningChanged(bool)), this, SLOT(onBamfApplicationClosed(bool)));
411
 
    QObject::connect(application, SIGNAL(RunningChanged(bool)), this, SIGNAL(runningChanged(bool)));
412
 
    QObject::connect(application, SIGNAL(UrgentChanged(bool)), this, SIGNAL(urgentChanged(bool)));
413
 
    QObject::connect(application, SIGNAL(WindowAdded(BamfWindow*)), this, SLOT(updateHasVisibleWindow()));
414
 
    QObject::connect(application, SIGNAL(WindowRemoved(BamfWindow*)), this, SLOT(updateHasVisibleWindow()));
415
 
    QObject::connect(application, SIGNAL(WindowAdded(BamfWindow*)), this, SLOT(updateWindowCount()));
416
 
    QObject::connect(application, SIGNAL(WindowRemoved(BamfWindow*)), this, SLOT(updateWindowCount()));
417
 
    connect(application, SIGNAL(ChildAdded(BamfView*)), SLOT(slotChildAdded(BamfView*)));
418
 
    connect(application, SIGNAL(ChildRemoved(BamfView*)), SLOT(slotChildRemoved(BamfView*)));
419
 
 
420
 
    connect(application, SIGNAL(WindowAdded(BamfWindow*)), SLOT(onWindowAdded(BamfWindow*)));
421
 
 
422
 
    updateBamfApplicationDependentProperties();
423
 
}
424
 
 
425
 
void
426
 
LauncherApplication::updateBamfApplicationDependentProperties()
427
 
{
428
 
    activeChanged(active());
429
 
    runningChanged(running());
430
 
    urgentChanged(urgent());
431
 
    nameChanged(name());
432
 
    iconChanged(icon());
433
 
    applicationTypeChanged(application_type());
434
 
    desktopFileChanged(desktop_file());
435
 
    m_launching_timer.stop();
436
 
    launchingChanged(launching());
437
 
    updateHasVisibleWindow();
438
 
    updateWindowCount();
439
 
    fetchIndicatorMenus();
440
 
}
441
 
 
442
 
void
443
 
LauncherApplication::onBamfApplicationClosed(bool running)
444
 
{
445
 
    if(running)
446
 
       return;
447
 
 
448
 
    m_application->disconnect(this);
449
 
    m_application = NULL;
450
 
    updateBamfApplicationDependentProperties();
451
 
    closed();
452
 
}
453
 
 
454
 
void
455
 
LauncherApplication::setSnStartupSequence(SnStartupSequence* sequence)
456
 
{
457
 
    if (sequence != NULL) {
458
 
        if (!sn_startup_sequence_get_completed(sequence)) {
459
 
            /* 'launching' property becomes true for a few seconds */
460
 
            m_launching_timer.start();
461
 
        } else {
462
 
            m_launching_timer.stop();
463
 
        }
464
 
        sn_startup_sequence_ref(sequence);
465
 
    }
466
 
 
467
 
    m_snStartupSequence.reset(sequence);
468
 
 
469
 
    nameChanged(name());
470
 
    iconChanged(icon());
471
 
    executableChanged(executable());
472
 
    launchingChanged(launching());
473
 
}
474
 
 
475
 
void
476
 
LauncherApplication::setIconGeometry(int x, int y, int width, int height, uint xid)
477
 
{
478
 
    if (m_application == NULL) {
479
 
        return;
480
 
    }
481
 
 
482
 
    QScopedPointer<BamfUintList> xids;
483
 
    if (xid == 0) {
484
 
        xids.reset(m_application->xids());
485
 
    } else {
486
 
        QList<uint> list;
487
 
        list.append(xid);
488
 
        xids.reset(new BamfUintList(list));
489
 
    }
490
 
    int size = xids->size();
491
 
    if (size < 1) {
492
 
        return;
493
 
    }
494
 
 
495
 
    WnckScreen* screen = wnck_screen_get_default();
496
 
    wnck_screen_force_update(screen);
497
 
 
498
 
    for (int i = 0; i < size; ++i) {
499
 
        WnckWindow* window = wnck_window_get(xids->at(i));
500
 
        wnck_window_set_icon_geometry(window, x, y, width, height);
501
 
    }
502
 
}
503
 
 
504
 
void
505
 
LauncherApplication::onWindowAdded(BamfWindow* window)
506
 
{
507
 
    if (window != NULL) {
508
 
        windowAdded(window->xid());
509
 
    }
510
 
}
511
 
 
512
 
bool
513
 
LauncherApplication::launching() const
514
 
{
515
 
    return m_launching_timer.isActive();
516
 
}
517
 
 
518
 
void
519
 
LauncherApplication::updateHasVisibleWindow()
520
 
{
521
 
    bool prev = m_has_visible_window;
522
 
 
523
 
    if (m_application != NULL) {
524
 
        m_has_visible_window = QScopedPointer<BamfUintList>(m_application->xids())->size() > 0;
525
 
    } else {
526
 
        m_has_visible_window = false;
527
 
    }
528
 
 
529
 
    if (m_has_visible_window != prev) {
530
 
        hasVisibleWindowChanged(m_has_visible_window);
531
 
    }
532
 
}
533
 
 
534
 
void
535
 
LauncherApplication::updateWindowCount()
536
 
{
537
 
    Q_EMIT windowCountChanged(windowCount());
538
 
}
539
 
 
540
 
bool
541
 
LauncherApplication::has_visible_window() const
542
 
{
543
 
    return m_has_visible_window;
544
 
}
545
 
 
546
 
float
547
 
LauncherApplication::progress() const
548
 
{
549
 
    return m_progress;
550
 
}
551
 
 
552
 
int
553
 
LauncherApplication::counter() const
554
 
{
555
 
    return m_counter;
556
 
}
557
 
 
558
 
QString
559
 
LauncherApplication::emblem() const
560
 
{
561
 
    return m_emblem;
562
 
}
563
 
 
564
 
bool
565
 
LauncherApplication::progressBarVisible() const
566
 
{
567
 
    return m_progressBarVisible;
568
 
}
569
 
 
570
 
bool
571
 
LauncherApplication::counterVisible() const
572
 
{
573
 
    return m_counterVisible;
574
 
}
575
 
 
576
 
bool
577
 
LauncherApplication::emblemVisible() const
578
 
{
579
 
    return m_emblemVisible;
580
 
}
581
 
 
582
 
/* Returns the number of window for this application that reside on the
583
 
   current workspace */
584
 
int
585
 
LauncherApplication::windowCountOnCurrentWorkspace()
586
 
{
587
 
    int windowCount = 0;
588
 
    WnckWorkspace *current = wnck_screen_get_active_workspace(wnck_screen_get_default());
589
 
 
590
 
    for (int i = 0; i < m_application->windows()->size(); i++) {
591
 
        BamfWindow *window = m_application->windows()->at(i);
592
 
        if (window == NULL) {
593
 
            continue;
594
 
        }
595
 
 
596
 
        /* When geting the wnck window, it's possible we get a NULL
597
 
           return value because wnck hasn't updated its internal list yet,
598
 
           so we need to force it once to be sure */
599
 
        WnckWindow *wnck_window = wnck_window_get(window->xid());
600
 
        if (wnck_window == NULL) {
601
 
            wnck_screen_force_update(wnck_screen_get_default());
602
 
            wnck_window = wnck_window_get(window->xid());
603
 
            if (wnck_window == NULL) {
604
 
                continue;
605
 
            }
606
 
        }
607
 
 
608
 
        WnckWorkspace *workspace = wnck_window_get_workspace(wnck_window);
609
 
        if (workspace == current) {
610
 
            windowCount++;
611
 
        }
612
 
    }
613
 
    return windowCount;
614
 
}
615
 
 
616
 
void
617
 
LauncherApplication::activate()
618
 
{
619
 
    if (urgent()) {
620
 
        show();
621
 
    } else if (active()) {
622
 
        if (windowCountOnCurrentWorkspace() > 0 && windowCount() > 1) {
623
 
            spread(windowCount() > windowCountOnCurrentWorkspace());
624
 
        }
625
 
    } else if (running() && has_visible_window()) {
626
 
        show();
627
 
    } else {
628
 
        launch();
629
 
    }
630
 
}
631
 
 
632
 
void
633
 
LauncherApplication::launchNewInstance()
634
 
{
635
 
    launch();
636
 
}
637
 
 
638
 
bool
639
 
LauncherApplication::launch()
640
 
{
641
 
    if (m_appInfo == NULL) {
642
 
        return false;
643
 
    }
644
 
 
645
 
    GError* error = NULL;
646
 
    GTimeVal timeval;
647
 
 
648
 
    g_get_current_time (&timeval);
649
 
    GdkDisplay* display = gdk_display_get_default();
650
 
    GObjectScopedPointer<GdkAppLaunchContext> context(gdk_display_get_app_launch_context(display));
651
 
    /* Using GDK_CURRENT_TIME doesn’t seem to work, launched windows
652
 
       sometimes don’t get focus (see https://launchpad.net/bugs/643616). */
653
 
    gdk_app_launch_context_set_timestamp(context.data(), timeval.tv_sec);
654
 
 
655
 
    g_app_info_launch(m_appInfo.data(), NULL, (GAppLaunchContext*)context.data(), &error);
656
 
 
657
 
    if (error != NULL) {
658
 
        UQ_WARNING << "Failed to launch application:" << error->message;
659
 
        g_error_free(error);
660
 
        return false;
661
 
    }
662
 
 
663
 
    /* 'launching' property becomes true for a few seconds and becomes
664
 
       false as soon as the application is launched */
665
 
    m_launching_timer.start();
666
 
    launchingChanged(true);
667
 
 
668
 
    return true;
669
 
}
670
 
 
671
 
void
672
 
LauncherApplication::onLaunchingTimeouted()
673
 
{
674
 
    launchingChanged(false);
675
 
}
676
 
 
677
 
void
678
 
LauncherApplication::close()
679
 
{
680
 
    if (m_application == NULL) {
681
 
        return;
682
 
    }
683
 
 
684
 
    QScopedPointer<BamfUintList> xids(m_application->xids());
685
 
    int size = xids->size();
686
 
    if (size < 1) {
687
 
        return;
688
 
    }
689
 
 
690
 
    WnckScreen* screen = wnck_screen_get_default();
691
 
    wnck_screen_force_update(screen);
692
 
 
693
 
    for (int i = 0; i < size; ++i) {
694
 
        WnckWindow* window = wnck_window_get(xids->at(i));
695
 
        wnck_window_close(window, CurrentTime);
696
 
    }
697
 
}
698
 
 
699
 
void
700
 
LauncherApplication::show()
701
 
{
702
 
    if (m_application == NULL) {
703
 
        return;
704
 
    }
705
 
 
706
 
    QScopedPointer<BamfWindowList> windows(m_application->windows());
707
 
    int size = windows->size();
708
 
    if (size < 1) {
709
 
        return;
710
 
    }
711
 
 
712
 
    /* Pick the most important window.
713
 
       The primary criterion to determine the most important window is urgency.
714
 
       The secondary criterion is the last_active timestamp (the last time the
715
 
       window was activated). */
716
 
    BamfWindow* important = windows->at(0);
717
 
    for (int i = 0; i < size; ++i) {
718
 
        BamfWindow* current = windows->at(i);
719
 
        if (current->urgent() && !important->urgent()) {
720
 
            important = current;
721
 
        } else if (current->urgent() || !important->urgent()) {
722
 
            if (current->last_active() > important->last_active()) {
723
 
                important = current;
724
 
            }
725
 
        }
726
 
    }
727
 
 
728
 
    WnckScreen* screen = wnck_screen_get_default();
729
 
    wnck_screen_force_update(screen);
730
 
 
731
 
    WnckWindow* window = wnck_window_get(important->xid());
732
 
    LauncherUtility::showWindow(window);
733
 
}
734
 
 
735
 
void
736
 
LauncherApplication::spread(bool showAllWorkspaces)
737
 
{
738
 
    QDBusInterface compiz("org.freedesktop.compiz",
739
 
                          "/org/freedesktop/compiz/scale/screen0/initiate_all_key",
740
 
                          "org.freedesktop.compiz");
741
 
 
742
 
    if (compiz.isValid()) {
743
 
        Qt::HANDLE root = QX11Info::appRootWindow();
744
 
        BamfUintList* xids = m_application->xids();
745
 
        QStringList fragments;
746
 
        for (int i = 0; i < xids->size(); i++) {
747
 
            uint xid = xids->at(i);
748
 
            fragments.append("xid=" + QString::number(xid));
749
 
        }
750
 
 
751
 
        compiz.asyncCall("activate", "root", static_cast<int>(root), "match", fragments.join(" | "));
752
 
    } else {
753
 
        QDBusInterface spread("com.canonical.Unity2d.Spread", "/Spread",
754
 
                              "com.canonical.Unity2d.Spread");
755
 
        QDBusReply<bool> isShown = spread.call("IsShown");
756
 
        if (isShown.isValid()) {
757
 
            if (isShown.value() == true) {
758
 
                spread.asyncCall("FilterByApplication", m_application->desktop_file());
759
 
            } else {
760
 
                if (showAllWorkspaces) {
761
 
                    spread.asyncCall("ShowAllWorkspaces", m_application->desktop_file());
762
 
                } else {
763
 
                    spread.asyncCall("ShowCurrentWorkspace", m_application->desktop_file());
764
 
                }
765
 
            }
766
 
        } else {
767
 
            UQ_WARNING << "Failed to get property IsShown on com.canonical.Unity2d.Spread";
768
 
        }
769
 
    }
770
 
}
771
 
 
772
 
void
773
 
LauncherApplication::slotChildAdded(BamfView* child)
774
 
{
775
 
    BamfIndicator* indicator = qobject_cast<BamfIndicator*>(child);
776
 
    if (indicator != NULL) {
777
 
        QString path = indicator->dbus_menu_path();
778
 
        if (!m_indicatorMenus.contains(path)) {
779
 
            DBusMenuImporter* importer = new DBusMenuImporter(indicator->address(), path, this);
780
 
            connect(importer, SIGNAL(menuUpdated()), SLOT(onIndicatorMenuUpdated()));
781
 
            m_indicatorMenus[path] = importer;
782
 
        }
783
 
    }
784
 
}
785
 
 
786
 
void
787
 
LauncherApplication::slotChildRemoved(BamfView* child)
788
 
{
789
 
    BamfIndicator* indicator = qobject_cast<BamfIndicator*>(child);
790
 
    if (indicator != NULL) {
791
 
        QString path = indicator->dbus_menu_path();
792
 
        if (m_indicatorMenus.contains(path)) {
793
 
            m_indicatorMenus.take(path)->deleteLater();
794
 
        }
795
 
    }
796
 
}
797
 
 
798
 
void
799
 
LauncherApplication::fetchIndicatorMenus()
800
 
{
801
 
    Q_FOREACH(QString path, m_indicatorMenus.keys()) {
802
 
        m_indicatorMenus.take(path)->deleteLater();
803
 
    }
804
 
 
805
 
    if (m_application != NULL) {
806
 
        QScopedPointer<BamfViewList> children(m_application->children());
807
 
        for (int i = 0; i < children->size(); ++i) {
808
 
            slotChildAdded(children->at(i));
809
 
        }
810
 
    }
811
 
}
812
 
 
813
 
void
814
 
LauncherApplication::createMenuActions()
815
 
{
816
 
    if (m_application != NULL && !m_indicatorMenus.isEmpty()) {
817
 
        /* Request indicator menus to be updated: this is asynchronous
818
 
           and the corresponding actions are added to the menu in
819
 
           SLOT(onIndicatorMenuUpdated()).
820
 
           Static menu actions are appended after all indicator menus
821
 
           have been updated.*/
822
 
        m_indicatorMenusReady = 0;
823
 
        Q_FOREACH(DBusMenuImporter* importer, m_indicatorMenus) {
824
 
            importer->updateMenu();
825
 
        }
826
 
    } else {
827
 
        createDynamicMenuActions();
828
 
        createStaticMenuActions();
829
 
    }
830
 
}
831
 
 
832
 
void
833
 
LauncherApplication::createDynamicMenuActions()
834
 
{
835
 
    if (!m_dynamicQuicklistImporter.isNull()) {
836
 
        /* FIXME: the menu is only partially updated while visible: stale
837
 
           actions will correctly be removed from the menu, but new actions
838
 
           will not be added until the menu is closed and opened again.
839
 
           This is an acceptable limitation for now. */
840
 
        QList<QAction*> actions = m_dynamicQuicklistImporter->menu()->actions();
841
 
        Q_FOREACH(QAction* action, actions) {
842
 
            if (action->isSeparator()) {
843
 
                m_menu->insertSeparatorBeforeTitle();
844
 
            } else {
845
 
                connect(action, SIGNAL(triggered()), m_menu, SLOT(hide()));
846
 
                m_menu->insertActionBeforeTitle(action);
847
 
            }
848
 
        }
849
 
    }
850
 
}
851
 
 
852
 
void
853
 
LauncherApplication::createStaticMenuActions()
854
 
{
855
 
    /* Custom menu actions from the desktop file. */
856
 
    if (!m_staticShortcuts.isNull()) {
857
 
        const gchar** nicks = indicator_desktop_shortcuts_get_nicks(m_staticShortcuts.data());
858
 
        if (nicks) {
859
 
            int i = 0;
860
 
            while (((gpointer*) nicks)[i]) {
861
 
                const gchar* nick = nicks[i];
862
 
                QAction* action = new QAction(m_menu);
863
 
                action->setText(QString::fromUtf8(indicator_desktop_shortcuts_nick_get_name(m_staticShortcuts.data(), nick)));
864
 
                action->setProperty(SHORTCUT_NICK_PROPERTY, QVariant(nick));
865
 
                m_menu->insertActionBeforeTitle(action);
866
 
                connect(action, SIGNAL(triggered()), SLOT(onStaticShortcutTriggered()));
867
 
                ++i;
868
 
            }
869
 
        }
870
 
    }
871
 
    m_menu->insertSeparatorBeforeTitle();
872
 
 
873
 
    QList<QAction*> actions;
874
 
    bool is_running = running();
875
 
 
876
 
    /* Only applications with a corresponding desktop file can be kept in the launcher */
877
 
    if (QFile::exists(desktop_file())) {
878
 
        QAction* keep = new QAction(m_menu);
879
 
        keep->setCheckable(is_running);
880
 
        keep->setChecked(sticky());
881
 
        keep->setText(is_running ? u2dTr("Keep In Launcher") : u2dTr("Remove From Launcher"));
882
 
        actions.append(keep);
883
 
        QObject::connect(keep, SIGNAL(triggered()), this, SLOT(onKeepTriggered()));
884
 
    }
885
 
 
886
 
    if (is_running) {
887
 
        QAction* quit = new QAction(m_menu);
888
 
        quit->setText(u2dTr("Quit"));
889
 
        actions.append(quit);
890
 
        QObject::connect(quit, SIGNAL(triggered()), this, SLOT(onQuitTriggered()));
891
 
    }
892
 
 
893
 
    /* Filter out duplicate actions. This typically happens with indicator
894
 
       menus that contain a "Quit" action: we don’t want two "Quit" actions in
895
 
       our menu. */
896
 
    Q_FOREACH(QAction* pending, actions) {
897
 
        bool duplicate = false;
898
 
        Q_FOREACH(QAction* action, m_menu->actions()) {
899
 
            /* The filtering is done on the text of the action. This will
900
 
               obviously break if for example only one of the two actions is
901
 
               localized ("Quit" != "Quitter"), but we don’t have a better way
902
 
               to identify duplicate actions. */
903
 
            if (pending->text() == action->text()) {
904
 
                duplicate = true;
905
 
                break;
906
 
            }
907
 
        }
908
 
 
909
 
        if (!duplicate) {
910
 
            m_menu->addAction(pending);
911
 
        } else {
912
 
            delete pending;
913
 
        }
914
 
    } 
915
 
}
916
 
 
917
 
void
918
 
LauncherApplication::onIndicatorMenuUpdated()
919
 
{
920
 
    if (!m_menu->isVisible()) {
921
 
        return;
922
 
    }
923
 
 
924
 
    DBusMenuImporter* importer = static_cast<DBusMenuImporter*>(sender());
925
 
    QList<QAction*> actions = importer->menu()->actions();
926
 
    Q_FOREACH(QAction* action, actions) {
927
 
        if (action->isSeparator()) {
928
 
            m_menu->insertSeparatorBeforeTitle();
929
 
        } else {
930
 
            connect(action, SIGNAL(triggered()), m_menu, SLOT(hide()));
931
 
            m_menu->insertActionBeforeTitle(action);
932
 
        }
933
 
    }
934
 
 
935
 
    if (++m_indicatorMenusReady == m_indicatorMenus.size()) {
936
 
        /* All indicator menus have been updated. */
937
 
        createDynamicMenuActions();
938
 
        createStaticMenuActions();
939
 
    }
940
 
}
941
 
 
942
 
void
943
 
LauncherApplication::onStaticShortcutTriggered()
944
 
{
945
 
    QAction* action = static_cast<QAction*>(sender());
946
 
    QString nick = action->property(SHORTCUT_NICK_PROPERTY).toString();
947
 
    m_menu->hide();
948
 
    indicator_desktop_shortcuts_nick_exec(m_staticShortcuts.data(), nick.toUtf8().constData());
949
 
}
950
 
 
951
 
void
952
 
LauncherApplication::onKeepTriggered()
953
 
{
954
 
    QAction* keep = static_cast<QAction*>(sender());
955
 
    bool sticky = keep->isChecked();
956
 
    m_menu->hide();
957
 
    setSticky(sticky);
958
 
}
959
 
 
960
 
void
961
 
LauncherApplication::onQuitTriggered()
962
 
{
963
 
    m_menu->hide();
964
 
    close();
965
 
}
966
 
 
967
 
template<typename T>
968
 
bool LauncherApplication::updateOverlayState(QMap<QString, QVariant> properties,
969
 
                                             QString propertyName, T* member)
970
 
{
971
 
    if (properties.contains(propertyName)) {
972
 
        T value = properties.value(propertyName).value<T>();
973
 
        if (value != *member) {
974
 
            *member = value;
975
 
            return true;
976
 
        }
977
 
    }
978
 
    return false;
979
 
}
980
 
 
981
 
void
982
 
LauncherApplication::updateOverlaysState(const QString& sender, QMap<QString, QVariant> properties)
983
 
{
984
 
    if (updateOverlayState(properties, "progress", &m_progress)) {
985
 
        Q_EMIT progressChanged(m_progress);
986
 
    }
987
 
    if (updateOverlayState(properties, "progress-visible", &m_progressBarVisible)) {
988
 
        Q_EMIT progressBarVisibleChanged(m_progressBarVisible);
989
 
    }
990
 
    if (updateOverlayState(properties, "count", &m_counter)) {
991
 
        Q_EMIT counterChanged(m_counter);
992
 
    }
993
 
    if (updateOverlayState(properties, "count-visible", &m_counterVisible)) {
994
 
        Q_EMIT counterVisibleChanged(m_counterVisible);
995
 
    }
996
 
    if (updateOverlayState(properties, "emblem", &m_emblem)) {
997
 
        Q_EMIT emblemChanged(m_emblem);
998
 
    }
999
 
    if (updateOverlayState(properties, "emblem-visible", &m_emblemVisible)) {
1000
 
        Q_EMIT emblemVisibleChanged(m_emblemVisible);
1001
 
    }
1002
 
    if (updateOverlayState(properties, "quicklist", &m_dynamicQuicklistPath)) {
1003
 
        setDynamicQuicklistImporter(sender);
1004
 
    }
1005
 
}
1006
 
 
1007
 
void
1008
 
LauncherApplication::setDynamicQuicklistImporter(const QString& service)
1009
 
{
1010
 
    if (m_dynamicQuicklistPath.isEmpty() || service.isEmpty()) {
1011
 
        m_dynamicQuicklistImporter.reset();
1012
 
    } else {
1013
 
        m_dynamicQuicklistImporter.reset(new DBusMenuImporter(service, m_dynamicQuicklistPath));
1014
 
        m_dynamicQuicklistImporter->updateMenu();
1015
 
        if (m_dynamicQuicklistServiceWatcher == NULL) {
1016
 
            m_dynamicQuicklistServiceWatcher = new QDBusServiceWatcher(this);
1017
 
            m_dynamicQuicklistServiceWatcher->setConnection(QDBusConnection::sessionBus());
1018
 
            connect(m_dynamicQuicklistServiceWatcher,
1019
 
                    SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
1020
 
                    SLOT(dynamicQuicklistImporterServiceOwnerChanged(const QString&, const QString&, const QString&)));
1021
 
        }
1022
 
        m_dynamicQuicklistServiceWatcher->addWatchedService(service);
1023
 
    }
1024
 
}
1025
 
 
1026
 
void
1027
 
LauncherApplication::dynamicQuicklistImporterServiceOwnerChanged(const QString& serviceName, const QString& oldOwner, const QString& newOwner)
1028
 
{
1029
 
    m_dynamicQuicklistServiceWatcher->removeWatchedService(oldOwner);
1030
 
    setDynamicQuicklistImporter(newOwner);
1031
 
}
1032