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

« back to all changes in this revision

Viewing changes to libunity-2d-private/src/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
 
 
1033
#include "launcherapplication.moc"