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

« back to all changes in this revision

Viewing changes to .pc/debian-changes-4.12.0-0ubuntu2/libunity-2d-private/src/launcherapplication.cpp

  • Committer: Package Import Robot
  • Author(s): Aurélien Gâteau
  • Date: 2011-11-23 15:05:39 UTC
  • Revision ID: package-import@ubuntu.com-20111123150539-1flcw3o7wxh1ox7n
Tags: 4.12.0-0ubuntu2
Enable accessibility at startup, disable it for started applications
(LP: #877358)

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