2
* Copyright (C) 2010 Canonical, Ltd.
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.
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.
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/>.
17
/* Those have to be included before any QObject-style header to avoid
18
compilation errors. */
21
/* Required otherwise using wnck_set_client_type breaks linking with error:
22
undefined reference to `wnck_set_client_type(WnckClientType)'
25
#include <libwnck/libwnck.h>
26
#include <libwnck/util.h>
29
#include "launcherapplication.h"
30
#include "launchermenu.h"
31
#include "bamf-matcher.h"
37
#include <QDBusInterface>
39
LauncherApplication::LauncherApplication() :
40
m_application(NULL), m_appInfo(NULL), m_sticky(false), m_has_visible_window(false)
42
/* Make sure wnck_set_client_type is called only once */
43
static bool client_type_set = false;
46
/* Critically important to set the client type to pager because wnck
47
will pass that type over to the window manager through XEvents.
48
Window managers tend to respect orders from pagers to the letter by
49
for example bypassing focus stealing prevention.
50
Compiz does exactly that in src/event.c:handleEvent(...) in the
51
ClientMessage case (line 1702).
52
Metacity has a similar policy in src/core/window.c:window_activate(...)
55
wnck_set_client_type(WNCK_CLIENT_TYPE_PAGER);
56
client_type_set = true;
59
QObject::connect(&m_launching_timer, SIGNAL(timeout()), this, SLOT(onLaunchingTimeouted()));
62
LauncherApplication::LauncherApplication(const LauncherApplication& other) :
63
m_application(NULL), m_appInfo(NULL)
65
/* FIXME: a number of members are not copied over */
66
QObject::connect(&m_launching_timer, SIGNAL(timeout()), this, SLOT(onLaunchingTimeouted()));
67
if (other.m_application != NULL)
68
setBamfApplication(other.m_application);
69
m_priority = other.m_priority;
72
LauncherApplication::~LauncherApplication()
74
if(m_application != NULL)
85
LauncherApplication::active() const
87
if(m_application != NULL)
88
return m_application->active();
94
LauncherApplication::running() const
96
if(m_application != NULL)
97
return m_application->running();
103
LauncherApplication::urgent() const
105
if(m_application != NULL)
106
return m_application->urgent();
112
LauncherApplication::sticky() const
118
LauncherApplication::name() const
120
if(m_application != NULL)
121
return m_application->name();
123
if(m_appInfo != NULL)
124
return QString::fromUtf8(g_app_info_get_name((GAppInfo*)m_appInfo));
130
LauncherApplication::icon() const
132
if(m_application != NULL)
133
return m_application->icon();
135
if(m_appInfo != NULL)
136
return QString::fromUtf8(g_icon_to_string(g_app_info_get_icon((GAppInfo*)m_appInfo)));
142
LauncherApplication::application_type() const
144
if(m_application != NULL)
145
return m_application->application_type();
151
LauncherApplication::desktop_file() const
153
if(m_application != NULL)
154
return m_application->desktop_file();
156
if(m_appInfo != NULL)
157
return QString::fromUtf8(g_desktop_app_info_get_filename(m_appInfo));
163
LauncherApplication::setSticky(bool sticky)
165
if (sticky == m_sticky)
169
emit stickyChanged(sticky);
173
LauncherApplication::setDesktopFile(QString desktop_file)
175
QByteArray byte_array = desktop_file.toUtf8();
176
gchar *file = byte_array.data();
178
if(desktop_file.startsWith("/"))
180
/* It looks like a full path to a desktop file */
181
m_appInfo = g_desktop_app_info_new_from_filename(file);
185
/* It might just be a desktop file name; let GIO look for the actual
186
desktop file for us */
187
m_appInfo = g_desktop_app_info_new(file);
190
/* Emit the Changed signal on all properties that can depend on m_appInfo
191
m_application's properties take precedence over m_appInfo's
193
if(m_application == NULL && m_appInfo != NULL)
195
emit desktopFileChanged(desktop_file);
196
emit nameChanged(name());
197
emit iconChanged(icon());
202
LauncherApplication::setBamfApplication(BamfApplication *application)
204
if (application == NULL) {
208
m_application = application;
209
setDesktopFile(application->desktop_file());
211
QObject::connect(application, SIGNAL(ActiveChanged(bool)), this, SIGNAL(activeChanged(bool)));
213
/* FIXME: a bug somewhere makes connecting to the Closed() signal not work even though
214
the emit Closed() in bamf-view.cpp is reached. */
215
/* Connect first the onBamfApplicationClosed slot, then the runningChanged
216
signal, to avoid a race condition when an application is closed.
217
See https://launchpad.net/bugs/634057 */
218
QObject::connect(application, SIGNAL(RunningChanged(bool)), this, SLOT(onBamfApplicationClosed(bool)));
219
QObject::connect(application, SIGNAL(RunningChanged(bool)), this, SIGNAL(runningChanged(bool)));
220
QObject::connect(application, SIGNAL(UrgentChanged(bool)), this, SIGNAL(urgentChanged(bool)));
221
QObject::connect(application, SIGNAL(WindowAdded(BamfWindow*)), this, SLOT(updateHasVisibleWindow()));
222
QObject::connect(application, SIGNAL(WindowRemoved(BamfWindow*)), this, SLOT(updateHasVisibleWindow()));
224
connect(application, SIGNAL(WindowAdded(BamfWindow*)), SLOT(onWindowAdded(BamfWindow*)));
226
updateBamfApplicationDependentProperties();
230
LauncherApplication::updateBamfApplicationDependentProperties()
232
emit activeChanged(active());
233
emit runningChanged(running());
234
emit urgentChanged(urgent());
235
emit nameChanged(name());
236
emit iconChanged(icon());
237
emit applicationTypeChanged(application_type());
238
emit desktopFileChanged(desktop_file());
239
m_launching_timer.stop();
240
emit launchingChanged(launching());
241
updateHasVisibleWindow();
245
LauncherApplication::onBamfApplicationClosed(bool running)
250
m_application->disconnect(this);
251
m_application = NULL;
252
updateBamfApplicationDependentProperties();
257
LauncherApplication::setIconGeometry(int x, int y, int width, int height, uint xid)
259
if (m_application == NULL) {
263
QScopedPointer<BamfUintList> xids;
265
xids.reset(m_application->xids());
269
xids.reset(new BamfUintList(list));
271
int size = xids->size();
276
WnckScreen* screen = wnck_screen_get_default();
277
wnck_screen_force_update(screen);
279
for (int i = 0; i < size; ++i) {
280
WnckWindow* window = wnck_window_get(xids->at(i));
281
wnck_window_set_icon_geometry(window, x, y, width, height);
286
LauncherApplication::onWindowAdded(BamfWindow* window)
288
windowAdded(window->xid());
292
LauncherApplication::priority() const
298
LauncherApplication::launching() const
300
return m_launching_timer.isActive();
304
LauncherApplication::updateHasVisibleWindow()
306
bool prev = m_has_visible_window;
308
if (m_application != NULL) {
309
m_has_visible_window = QScopedPointer<BamfUintList>(m_application->xids())->size() > 0;
311
m_has_visible_window = false;
313
if (m_has_visible_window != prev)
314
emit hasVisibleWindowChanged(m_has_visible_window);
318
LauncherApplication::has_visible_window() const
320
return m_has_visible_window;
324
LauncherApplication::activate()
330
else if (running() && has_visible_window())
341
LauncherApplication::launch()
343
if(m_appInfo == NULL) return false;
345
GError* error = NULL;
346
GdkAppLaunchContext *context;
349
g_get_current_time (&timeval);
350
context = gdk_app_launch_context_new();
351
/* Using GDK_CURRENT_TIME doesn’t seem to work, launched windows
352
sometimes don’t get focus (see https://launchpad.net/bugs/643616). */
353
gdk_app_launch_context_set_timestamp(context, timeval.tv_sec);
355
g_app_info_launch((GAppInfo*)m_appInfo, NULL, (GAppLaunchContext*)context, &error);
356
g_object_unref(context);
360
qWarning() << "Failed to launch application:" << error->message;
365
/* 'launching' property becomes true for a maximum of 8 seconds and becomes
366
false as soon as the application is launched */
367
m_launching_timer.setSingleShot(true);
368
m_launching_timer.start(8000);
369
emit launchingChanged(true);
375
LauncherApplication::onLaunchingTimeouted()
377
emit launchingChanged(false);
381
LauncherApplication::close()
383
if (m_application == NULL)
386
QScopedPointer<BamfUintList> xids(m_application->xids());
387
int size = xids->size();
391
WnckScreen* screen = wnck_screen_get_default();
392
wnck_screen_force_update(screen);
394
for (int i = 0; i < size; ++i) {
395
WnckWindow* window = wnck_window_get(xids->at(i));
396
wnck_window_close(window, CurrentTime);
401
LauncherApplication::show()
403
if(m_application == NULL) {
407
QScopedPointer<BamfWindowList> windows(m_application->windows());
408
int size = windows->size();
413
/* Pick the most important window.
414
The primary criterion to determine the most important window is urgency.
415
The secondary criterion is the last_active timestamp (the last time the
416
window was activated). */
417
BamfWindow* important = windows->at(0);
418
for (int i = 0; i < size; ++i) {
419
BamfWindow* current = windows->at(i);
420
if (current->urgent() && !important->urgent()) {
423
else if (current->urgent() || !important->urgent()) {
424
if (current->last_active() > important->last_active()) {
430
WnckScreen* screen = wnck_screen_get_default();
431
wnck_screen_force_update(screen);
433
WnckWindow* window = wnck_window_get(important->xid());
438
LauncherApplication::showWindow(WnckWindow* window)
440
WnckWorkspace* workspace = wnck_window_get_workspace(window);
442
/* Using X.h's CurrentTime (= 0) */
443
wnck_workspace_activate(workspace, CurrentTime);
445
/* If the workspace contains a viewport then move the viewport so
446
that the window is visible.
447
Compiz for example uses only one workspace with a desktop larger
448
than the screen size which means that a viewport is used to
449
determine what part of the desktop is visible.
452
http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#largedesks
454
if (wnck_workspace_is_virtual(workspace)) {
455
moveViewportToWindow(window);
458
/* Using X.h's CurrentTime (= 0) */
459
wnck_window_activate(window, CurrentTime);
463
LauncherApplication::moveViewportToWindow(WnckWindow* window)
465
WnckWorkspace* workspace = wnck_window_get_workspace(window);
466
WnckScreen* screen = wnck_window_get_screen(window);
468
int screen_width = wnck_screen_get_width(screen);
469
int screen_height = wnck_screen_get_height(screen);
470
int viewport_x = wnck_workspace_get_viewport_x(workspace);
471
int viewport_y = wnck_workspace_get_viewport_y(workspace);
473
int window_x, window_y, window_width, window_height;
474
wnck_window_get_geometry(window, &window_x, &window_y,
475
&window_width, &window_height);
477
/* Compute the row and column of the "virtual workspace" that contains
478
the window. A "virtual workspace" is a portion of the desktop of the
481
int viewport_column = (viewport_x + window_x) / screen_width;
482
int viewport_row = (viewport_y + window_y) / screen_height;
484
wnck_screen_move_viewport(screen, viewport_column * screen_width,
485
viewport_row * screen_height);
489
LauncherApplication::spread()
491
qDebug() << "Triggering spread via DBUS";
492
QDBusInterface iface("com.canonical.Unity2d.Spread", "/Spread",
493
"com.canonical.Unity2d.Spread");
494
iface.call("SpreadApplicationWindows", m_application->xids()->at(0));
498
LauncherApplication::createMenuActions()
500
bool is_running = running();
502
/* Only applications with a corresponding desktop file can be kept in the launcher */
503
if (!desktop_file().isEmpty()) {
504
QAction* keep = new QAction(m_menu);
505
keep->setCheckable(is_running);
506
keep->setChecked(sticky());
507
keep->setText(is_running ? tr("Keep In Launcher") : tr("Remove From Launcher"));
508
m_menu->addAction(keep);
509
QObject::connect(keep, SIGNAL(triggered()), this, SLOT(onKeepTriggered()));
514
QAction* quit = new QAction(m_menu);
515
quit->setText(tr("Quit"));
516
m_menu->addAction(quit);
517
QObject::connect(quit, SIGNAL(triggered()), this, SLOT(onQuitTriggered()));
522
LauncherApplication::onKeepTriggered()
524
QAction* keep = static_cast<QAction*>(sender());
525
bool sticky = keep->isChecked();
531
LauncherApplication::onQuitTriggered()