2
* Copyright 2006 Aaron Seigo <aseigo@kde.org>
3
* Copyright 2010 Chani Armitage <chani@kde.org>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU Library General Public License as
7
* published by the Free Software Foundation; either version 2, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details
15
* You should have received a copy of the GNU Library General Public
16
* License along with this program; if not, write to the
17
* Free Software Foundation, Inc.,
18
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
#include "plasmaapp.h"
27
#define _WIN32_WINNT 0x0500
33
#ifndef _SC_PHYS_PAGES
35
#include <sys/types.h>
36
#include <sys/sysctl.h>
40
#include <sys/param.h>
41
#include <sys/sysctl.h>
45
#include <QApplication>
46
#include <QDesktopWidget>
47
#include <QPixmapCache>
48
#include <QtDBus/QtDBus>
50
#include <QVBoxLayout>
53
#include <KAuthorized>
56
#include <KCmdLineArgs>
57
#include <KGlobalAccel>
58
#include <KGlobalSettings>
59
#include <KNotification>
61
#include <KWindowSystem>
63
#include <KIconLoader>
65
#include <ksmserver_interface.h>
67
#include <Plasma/AbstractToolBox>
68
#include <Plasma/AccessAppletJob>
69
#include <Plasma/AccessManager>
70
#include <Plasma/AuthorizationManager>
71
#include <Plasma/Containment>
72
#include <Plasma/Context>
73
#include <Plasma/Dialog>
74
#include <Plasma/Theme>
75
#include <Plasma/Wallpaper>
76
#include <Plasma/WindowEffects>
78
#include <kephal/screens.h>
80
#include <plasmagenericshell/backgrounddialog.h>
81
#include "kactivitycontroller.h"
84
#include "appadaptor.h"
85
#include "controllerwindow.h"
87
#include "desktopcorona.h"
88
#include "desktopview.h"
89
#include "interactiveconsole.h"
90
#include "kactivityinfo.h"
91
#include "panelshadows.h"
92
#include "panelview.h"
93
#include "plasma-shell-desktop.h"
94
#include "toolbutton.h"
95
#include "klistconfirmationdialog.h"
99
#include <X11/extensions/Xrender.h>
102
extern QString plasmaLocale;
104
PlasmaApp* PlasmaApp::self()
107
return new PlasmaApp();
110
return qobject_cast<PlasmaApp*>(kapp);
113
PlasmaApp::PlasmaApp()
114
: KUniqueApplication(),
117
m_mapper(new QSignalMapper(this)),
118
m_startupSuspendWaitCount(0),
119
m_ignoreDashboardClosures(false),
120
m_pendingFixedDashboard(false),
121
m_unlockCorona(false)
123
kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "plasma app ctor start" << "(line:" << __LINE__ << ")";
124
PlasmaApp::suspendStartup(true);
126
if (KGlobalSettings::isMultiHead()) {
127
KGlobal::locale()->setLanguage(plasmaLocale, KGlobal::config().data());
130
KGlobal::locale()->insertCatalog("libplasma");
131
KGlobal::locale()->insertCatalog("plasmagenericshell");
132
KCrash::setFlags(KCrash::AutoRestart);
134
// why is the next line of code here here?
136
// plasma-desktop was once plasma. not a big deal, right?
138
// well, kglobalaccel has a policy of forever
139
// reserving shortcuts. even if the application is not running, it will still
140
// defend that application's right to using that global shortcut. this has,
141
// at least to me, some very obvious negative impacts on usability, such as
142
// making it difficult for the user to switch between applications of the
143
// same type and use the same global shortcuts, or when the component changes
144
// name as in plasma-desktop.
146
// applications can unregister each other's shortcuts, though, and so that's
147
// exactly what we do here.
149
// i'd love to just rely on the kconf_update script, but kglobalaccel doesn't
150
// listen to changes in its config file nor has a dbus interace to tickle it
151
// into re-reading it and it starts too early in the start up sequence for
152
// kconf_update to beat it to the config file.
154
// so we instead deal with a dbus roundtrip with kded
155
// (8 context switches at minimum iirc?)
156
// at every app start for something that really only needs to be done once
157
// but which we can't know for sure when it has been done.
159
// if kglobalaccel ignored non-running apps or could be prompted into
160
// reloading its config, this would be unecessary.
162
// what's kind of funny is that if plasma actually relied on kglobalaccel for
163
// the plasmoid shortcuts (which it can't because layouts change too often and
164
// can be stored/retreived making kglobalaccel management a non-option) this
165
// would be a total disaster. As it is it's "just" potentially losing the user's
166
// customization of the Ctrl+F12 default shortcut to bring up the dashboard.
168
// this line should be removed when we decide that 4.[012]->4.<current> is
169
// no longer a supported upgrade path. sometime after dragons make a comeback
170
// and can be seen circling the skies again.
172
KGlobalAccel::cleanComponent("plasma");
174
m_panelViewCreationTimer.setSingleShot(true);
175
m_panelViewCreationTimer.setInterval(0);
177
m_desktopViewCreationTimer.setSingleShot(true);
178
m_desktopViewCreationTimer.setInterval(0);
180
new PlasmaAppAdaptor(this);
181
QDBusConnection::sessionBus().registerObject("/App", this);
183
// Enlarge application pixmap cache
184
// Calculate the size required to hold background pixmaps for all screens.
185
// Add 10% so that other (smaller) pixmaps can also be cached.
188
if (KGlobalSettings::isMultiHead()) {
191
Display *dpy = XOpenDisplay(NULL);
193
id = DefaultScreen(dpy);
197
const QSize size = Kephal::ScreenUtils::screenSize(id);
198
cacheSize += 4 * size.width() * size.height() / 1024;
200
const int numScreens = Kephal::ScreenUtils::numScreens();
201
for (int i = 0; i < numScreens; i++) {
202
QSize size = Kephal::ScreenUtils::screenSize(i);
203
cacheSize += 4 * size.width() * size.height() / 1024;
206
cacheSize += cacheSize / 10;
208
// Calculate the size of physical system memory; _SC_PHYS_PAGES *
209
// _SC_PAGESIZE is documented to be able to overflow 32-bit integers,
210
// so apply a 10-bit shift. FreeBSD 6-STABLE doesn't have _SC_PHYS_PAGES
211
// (it is documented in FreeBSD 7-STABLE as "Solaris and Linux extension")
212
// so use sysctl in those cases.
213
#if defined(_SC_PHYS_PAGES)
214
int memorySize = sysconf(_SC_PHYS_PAGES);
215
memorySize *= sysconf(_SC_PAGESIZE) / 1024;
219
size_t size = sizeof(sysctlbuf);
221
// This could actually use hw.physmem instead, but I can't find
222
// reliable documentation on how to read the value (which may
223
// not fit in a 32 bit integer).
224
if (!sysctlbyname("vm.stats.vm.v_page_size", sysctlbuf, &size, NULL, 0)) {
225
memorySize = sysctlbuf[0] / 1024;
226
size = sizeof(sysctlbuf);
227
if (!sysctlbyname("vm.stats.vm.v_page_count", sysctlbuf, &size, NULL, 0)) {
228
memorySize *= sysctlbuf[0];
235
static int mib[] = { CTL_HW, HW_PHYSMEM };
237
len = sizeof(memorySize);
238
sysctl(mib, 2, &memorySize, &len, NULL, 0);
244
MEMORYSTATUSEX statex;
245
statex.dwLength = sizeof (statex);
246
GlobalMemoryStatusEx (&statex);
248
memorySize = (statex.ullTotalPhys/1024) + (statex.ullTotalPageFile/1024);
250
// If you have no suitable sysconf() interface and are not FreeBSD,
251
// then you are out of luck and get a compile error.
254
// Increase the pixmap cache size to 1% of system memory if it isn't already
255
// larger so as to maximize cache usage. 1% of 1GB ~= 10MB.
256
if (cacheSize < memorySize / 100) {
257
cacheSize = memorySize / 100;
260
kDebug() << "Setting the pixmap cache size to" << cacheSize << "kilobytes";
261
QPixmapCache::setCacheLimit(cacheSize);
263
KAction *showAction = new KAction(this);
264
showAction->setText(i18n("Show Dashboard"));
265
showAction->setObjectName( QLatin1String("Show Dashboard" )); // NO I18N
266
showAction->setGlobalShortcut(KShortcut(Qt::CTRL + Qt::Key_F12));
267
connect(showAction, SIGNAL(triggered()), this, SLOT(toggleDashboard()));
269
KGlobal::setAllowQuit(true);
272
connect(m_mapper, SIGNAL(mapped(const QString &)),
273
this, SLOT(addRemotePlasmoid(const QString &)));
274
connect(Plasma::AccessManager::self(),
275
SIGNAL(finished(Plasma::AccessAppletJob*)),
276
this, SLOT(plasmoidAccessFinished(Plasma::AccessAppletJob*)));
277
connect(Plasma::AccessManager::self(),
278
SIGNAL(remoteAppletAnnounced(Plasma::PackageMetadata)),
279
this, SLOT(remotePlasmoidAdded(Plasma::PackageMetadata)));
281
Plasma::AuthorizationManager::self()->setAuthorizationPolicy(Plasma::AuthorizationManager::PinPairing);
283
QTimer::singleShot(0, this, SLOT(setupDesktop()));
284
kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "plasma app ctor end" << "(line:" << __LINE__ << ")";
287
PlasmaApp::~PlasmaApp()
291
void PlasmaApp::setupDesktop()
295
const char * const atomNames[] = {"XdndAware", "XdndEnter", "XdndFinished", "XdndPosition", "XdndStatus"};
296
XInternAtoms(QX11Info::display(), const_cast<char **>(atomNames), 5, False, atoms);
297
m_XdndAwareAtom = atoms[0];
298
m_XdndEnterAtom = atoms[1];
299
m_XdndFinishedAtom = atoms[2];
300
m_XdndPositionAtom = atoms[3];
301
m_XdndStatusAtom = atoms[4];
302
const int xdndversion = 5;
303
m_XdndVersionAtom = (Atom)xdndversion;
306
// intialize the default theme and set the font
307
Plasma::Theme *theme = Plasma::Theme::defaultTheme();
308
theme->setFont(AppSettings::desktopFont());
309
m_panelShadows = new PanelShadows();
311
// this line initializes the corona.
314
Kephal::Screens *screens = Kephal::Screens::self();
315
connect(screens, SIGNAL(screenRemoved(int)), SLOT(screenRemoved(int)));
316
connect(screens, SIGNAL(screenAdded(Kephal::Screen*)), SLOT(screenAdded(Kephal::Screen*)));
318
if (AppSettings::perVirtualDesktopViews()) {
319
connect(KWindowSystem::self(), SIGNAL(numberOfDesktopsChanged(int)),
320
this, SLOT(checkVirtualDesktopViews(int)));
323
// free the memory possibly occupied by the background image of the
324
// root window - login managers will typically set one
326
palette.setColor(desktop()->backgroundRole(), Qt::black);
327
desktop()->setPalette(palette);
329
connect(this, SIGNAL(aboutToQuit()), this, SLOT(cleanup()));
330
kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Plasma App SetupDesktop()" << "(line:" << __LINE__ << ")";
332
// now connect up the creation timers and start them to get the views created
333
connect(&m_panelViewCreationTimer, SIGNAL(timeout()), this, SLOT(createWaitingPanels()));
334
connect(&m_desktopViewCreationTimer, SIGNAL(timeout()), this, SLOT(createWaitingDesktops()));
335
m_panelViewCreationTimer.start();
336
m_desktopViewCreationTimer.start();
339
void PlasmaApp::quit()
347
void PlasmaApp::cleanup()
353
m_corona->saveLayout();
355
// save the mapping of Views to Containments at the moment
356
// of application exit so we can restore that when we start again.
357
KConfigGroup viewIds(KGlobal::config(), "ViewIds");
358
viewIds.deleteGroup();
360
foreach (PanelView *v, m_panels) {
361
if (v->containment()) {
362
viewIds.writeEntry(QString::number(v->containment()->id()), v->id());
366
foreach (DesktopView *v, m_desktops) {
367
if (v->containment()) {
368
viewIds.writeEntry(QString::number(v->containment()->id()), v->id());
372
QList<DesktopView*> desktops = m_desktops;
374
qDeleteAll(desktops);
376
QList<PanelView*> panels = m_panels;
380
delete m_console.data();
384
delete m_panelShadows;
387
//TODO: This manual sync() should not be necessary. Remove it when
389
KGlobal::config()->sync();
392
void PlasmaApp::syncConfig()
394
KGlobal::config()->sync();
397
void PlasmaApp::toggleDashboard()
399
// we don't want to listen to dashboard closure signals when toggling
400
// otherwise we get toggleDashboard -> dashboardClosed -> showDashboard
401
// and the wrong state of shown dashboards occurs.
402
m_ignoreDashboardClosures = true;
404
const int currentDesktop = KWindowSystem::currentDesktop() - 1;
405
foreach (DesktopView *view, m_desktops) {
406
if (AppSettings::perVirtualDesktopViews()) {
407
// always hide the dashboard if it isn't on the current desktop
408
if (view->desktop() == currentDesktop) {
409
view->toggleDashboard();
412
view->toggleDashboard();
416
m_ignoreDashboardClosures = false;
419
void PlasmaApp::showDashboard(bool show)
421
// we don't want to listen to dashboard closure signals when showing/hiding
422
// otherwise we get showDashboard -> dashboardClosed -> showDashboard
423
// and that could end up badly :)
424
m_ignoreDashboardClosures = true;
426
const int currentDesktop = KWindowSystem::currentDesktop() - 1;
427
foreach (DesktopView *view, m_desktops) {
428
if (AppSettings::perVirtualDesktopViews()) {
429
// always hide the dashboard if it isn't on the current desktop
430
if (view->desktop() == currentDesktop) {
431
view->showDashboard(show);
434
view->showDashboard(show);
438
m_ignoreDashboardClosures = false;
441
void PlasmaApp::dashboardClosed()
443
if (!m_ignoreDashboardClosures) {
444
showDashboard(false);
448
void PlasmaApp::showInteractiveConsole()
450
if (KGlobal::config()->isImmutable() || !KAuthorized::authorize("plasma-desktop/scripting_console")) {
454
InteractiveConsole *console = m_console.data();
456
m_console = console = new InteractiveConsole(m_corona);
459
KWindowSystem::setOnDesktop(console->winId(), KWindowSystem::currentDesktop());
462
KWindowSystem::forceActiveWindow(console->winId());
465
void PlasmaApp::loadScriptInInteractiveConsole(const QString &script)
467
showInteractiveConsole();
469
m_console.data()->loadScript(script);
473
void PlasmaApp::panelHidden(bool hidden)
477
//kDebug() << "panel hidden" << m_panelHidden;
480
if (m_panelHidden < 0) {
481
kDebug() << "panelHidden(false) called too many times!";
484
//kDebug() << "panel unhidden" << m_panelHidden;
488
QList<PanelView*> PlasmaApp::panelViews() const
493
PanelShadows *PlasmaApp::panelShadows() const
495
return m_panelShadows;
498
ControllerWindow *PlasmaApp::showWidgetExplorer(int screen, Plasma::Containment *containment)
500
return showController(screen, containment, true);
503
void PlasmaApp::toggleActivityManager()
505
const int currentScreen = m_corona->screenId(QCursor::pos());
507
QWeakPointer<ControllerWindow> controllerPtr = m_widgetExplorers.value(currentScreen);
508
ControllerWindow *controller = controllerPtr.data();
510
controller->deleteLater();
514
//try to find the "active" containment
515
int currentDesktop = -1;
516
if (AppSettings::perVirtualDesktopViews()) {
517
currentDesktop = KWindowSystem::currentDesktop() - 1;
520
Plasma::Containment *containment = m_corona->containmentForScreen(currentScreen, currentDesktop);
522
showController(currentScreen, containment, false);
525
ControllerWindow *PlasmaApp::showController(int screen, Plasma::Containment *containment, bool widgetExplorerMode)
528
kDebug() << "no containment";
532
QWeakPointer<ControllerWindow> controllerPtr = m_widgetExplorers.value(screen);
533
ControllerWindow *controller = controllerPtr.data();
536
//kDebug() << "controller not found for screen" << screen;
537
controllerPtr = controller = new ControllerWindow(0);
538
m_widgetExplorers.insert(screen, controllerPtr);
541
controller->setContainment(containment);
542
if (!containment || containment->screen() != screen) {
543
controller->setScreen(screen);
546
controller->setLocation(containment->location());
548
if (widgetExplorerMode) {
549
controller->showWidgetExplorer();
551
controller->showActivityManager();
555
Plasma::WindowEffects::slideWindow(controller, Plasma::BottomEdge);
556
KWindowSystem::setOnAllDesktops(controller->winId(), true);
557
QTimer::singleShot(0, controller, SLOT(activate()));
558
KWindowSystem::setState(controller->winId(), NET::SkipTaskbar | NET::SkipPager | NET::Sticky | NET::KeepAbove);
562
void PlasmaApp::hideController(int screen)
564
QWeakPointer<ControllerWindow> controller = m_widgetExplorers.value(screen);
566
controller.data()->hide();
571
PanelView *PlasmaApp::findPanelForTrigger(WId trigger) const
573
foreach (PanelView *panel, m_panels) {
574
if (panel->unhideTrigger() == trigger) {
582
bool PlasmaApp::x11EventFilter(XEvent *event)
584
if (m_panelHidden > 0 &&
585
(event->type == ClientMessage ||
586
(event->xany.send_event != True && (event->type == EnterNotify ||
587
event->type == MotionNotify)))) {
590
if (event->type == ClientMessage) {
591
kDebug() << "client message with" << event->xclient.message_type << m_XdndEnterAtom << event->xcrossing.window;
595
bool dndEnter = false;
596
bool dndPosition = false;
597
if (event->type == ClientMessage) {
598
dndEnter = event->xclient.message_type == m_XdndEnterAtom;
600
dndPosition = event->xclient.message_type == m_XdndPositionAtom;
602
//kDebug() << "FAIL!";
603
return KUniqueApplication::x11EventFilter(event);
606
//kDebug() << "on enter" << event->xclient.data.l[0];
610
PanelView *panel = findPanelForTrigger(event->xcrossing.window);
611
//kDebug() << "panel?" << panel << ((dndEnter || dndPosition) ? "Drag and drop op" : "Mouse move op");
613
if (dndEnter || dndPosition) {
616
const unsigned long *l = (const unsigned long *)event->xclient.data.l;
618
p = QPoint((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff);
623
XClientMessageEvent response;
624
response.type = ClientMessage;
625
response.window = l[0];
626
response.format = 32;
627
response.data.l[0] = panel->winId(); //event->xcrossing.window;
629
if (panel->hintOrUnhide(p, true)) {
630
response.message_type = m_XdndFinishedAtom;
631
response.data.l[1] = 0; // flags
632
response.data.l[2] = XNone;
634
response.message_type = m_XdndStatusAtom;
635
response.data.l[1] = 0; // flags
636
response.data.l[2] = 0; // x, y
637
response.data.l[3] = 0; // w, h
638
response.data.l[4] = 0; // action
641
XSendEvent(QX11Info::display(), l[0], False, NoEventMask, (XEvent*)&response);
642
} else if (event->type == EnterNotify) {
643
panel->hintOrUnhide(QPoint(-1, -1));
644
//kDebug() << "entry";
645
//FIXME: this if it was possible to avoid the polling
646
/*} else if (event->type == LeaveNotify) {
649
} else if (event->type == MotionNotify) {
650
XMotionEvent *motion = (XMotionEvent*)event;
651
//kDebug() << "motion" << motion->x << motion->y << panel->location();
652
panel->hintOrUnhide(QPoint(motion->x_root, motion->y_root));
657
return KUniqueApplication::x11EventFilter(event);
661
void PlasmaApp::screenRemoved(int id)
663
kDebug() << "@@@@" << id;
664
QMutableListIterator<DesktopView *> it(m_desktops);
665
while (it.hasNext()) {
666
DesktopView *view = it.next();
667
if (view->screen() == id) {
668
// the screen was removed, so we'll destroy the
669
// corresponding view
670
kDebug() << "@@@@removing the view for screen" << id;
671
view->setContainment(0);
677
Kephal::Screen *primary = Kephal::Screens::self()->primaryScreen();
678
QList<Kephal::Screen *> screens = Kephal::Screens::self()->screens();
679
screens.removeAll(primary);
681
// Now we process panels: if there is room on another screen for the panel,
682
// we migrate the panel there, otherwise the view is deleted. The primary
683
// screen is preferred in all cases.
684
QMutableListIterator<PanelView*> pIt(m_panels);
685
while (pIt.hasNext()) {
686
PanelView *panel = pIt.next();
687
if (panel->screen() == id) {
688
Kephal::Screen *moveTo = 0;
689
if (canRelocatePanel(panel, primary)) {
692
foreach (Kephal::Screen *screen, screens) {
693
if (canRelocatePanel(panel, screen)) {
701
panel->migrateTo(moveTo->id());
709
panel->updateStruts();
713
void PlasmaApp::screenAdded(Kephal::Screen *screen)
715
foreach (Plasma::Containment *containment, corona()->containments()) {
716
if (isPanelContainment(containment) && containment->screen() == screen->id()) {
717
m_panelsWaiting << containment;
718
m_panelViewCreationTimer.start();
722
foreach (PanelView *view, m_panels) {
723
if (view->migratedFrom(screen->id())) {
724
view->migrateTo(screen->id());
729
bool PlasmaApp::canRelocatePanel(PanelView * view, Kephal::Screen *screen)
731
if (!screen || !view->containment()) {
735
QRect newGeom = view->geometry();
736
switch (view->location()) {
737
case Plasma::TopEdge:
738
newGeom.setY(screen->geom().y());
739
newGeom.setX(view->offset());
741
case Plasma::BottomEdge:
742
newGeom.setY(screen->geom().bottom() - newGeom.height());
743
newGeom.setX(view->offset());
745
case Plasma::LeftEdge:
746
newGeom.setX(screen->geom().left());
747
newGeom.setY(view->offset());
749
case Plasma::RightEdge:
750
newGeom.setX(screen->geom().right() - newGeom.width());
751
newGeom.setY(view->offset());
757
kDebug() << "testing:" << screen->id() << view << view->geometry() << view->location() << newGeom;
758
foreach (PanelView *pv, m_panels) {
759
kDebug() << pv << pv->screen() << pv->screen() << pv->location() << pv->geometry();
761
pv->screen() == screen->id() &&
762
pv->location() == view->location() &&
763
pv->geometry().intersects(newGeom)) {
773
DesktopView* PlasmaApp::viewForScreen(int screen, int desktop) const
775
foreach (DesktopView *view, m_desktops) {
776
if (view->containment()) {
777
kDebug() << "comparing" << view->containment()->screen() << screen;
779
if (view->containment() && view->containment()->screen() == screen && (desktop < 0 || view->containment()->desktop() == desktop)) {
787
DesktopCorona* PlasmaApp::corona()
792
DesktopCorona *c = new DesktopCorona(this);
793
connect(c, SIGNAL(containmentAdded(Plasma::Containment*)),
794
this, SLOT(containmentAdded(Plasma::Containment*)));
795
connect(c, SIGNAL(configSynced()), this, SLOT(syncConfig()));
796
connect(c, SIGNAL(screenOwnerChanged(int,int,Plasma::Containment*)),
797
this, SLOT(containmentScreenOwnerChanged(int,int,Plasma::Containment*)));
799
foreach (DesktopView *view, m_desktops) {
800
connect(c, SIGNAL(screenOwnerChanged(int,int,Plasma::Containment*)),
801
view, SLOT(screenOwnerChanged(int,int,Plasma::Containment*)));
805
KAction *activityAction = c->addAction("manage activities");
806
connect(activityAction, SIGNAL(triggered()), this, SLOT(toggleActivityManager()));
807
activityAction->setText(i18n("Activities..."));
808
activityAction->setIcon(KIcon("preferences-activities"));
809
activityAction->setData(Plasma::AbstractToolBox::ConfigureTool);
810
activityAction->setShortcut(KShortcut("alt+d, alt+a"));
811
activityAction->setShortcutContext(Qt::ApplicationShortcut);
812
activityAction->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Q));
814
c->updateShortcuts();
817
c->setItemIndexMethod(QGraphicsScene::NoIndex);
818
c->initializeLayout();
819
c->processUpdateScripts();
820
c->checkActivities();
822
foreach (Plasma::Containment *containment, c->containments()) {
823
if (containment->screen() != -1 && containment->wallpaper()) {
824
++m_startupSuspendWaitCount;
825
connect(containment->wallpaper(), SIGNAL(update(QRectF)), this, SLOT(wallpaperCheckedIn()));
829
QTimer::singleShot(5000, this, SLOT(wallpaperCheckInTimeout()));
830
kDebug() << " ------------------------------------------>" << t.elapsed() << m_startupSuspendWaitCount;
836
void PlasmaApp::wallpaperCheckInTimeout()
838
if (m_startupSuspendWaitCount > 0) {
839
m_startupSuspendWaitCount = 0;
840
suspendStartup(false);
844
void PlasmaApp::wallpaperCheckedIn()
846
if (m_startupSuspendWaitCount < 1) {
850
--m_startupSuspendWaitCount;
851
if (m_startupSuspendWaitCount < 1) {
852
m_startupSuspendWaitCount = 0;
853
suspendStartup(false);
857
bool PlasmaApp::hasComposite()
861
return KWindowSystem::compositingActive();
867
void PlasmaApp::suspendStartup(bool suspend)
869
org::kde::KSMServerInterface ksmserver("org.kde.ksmserver", "/KSMServer", QDBusConnection::sessionBus());
871
const QString startupID("workspace desktop");
873
ksmserver.suspendStartup(startupID);
875
ksmserver.resumeStartup(startupID);
879
bool PlasmaApp::isPanelContainment(Plasma::Containment *containment)
885
Plasma::Containment::Type t = containment->containmentType();
887
return t == Plasma::Containment::PanelContainment ||
888
t == Plasma::Containment::CustomPanelContainment;
892
void PlasmaApp::createView(Plasma::Containment *containment)
894
kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Plasma App createView() start" << "(line:" << __LINE__ << ")";
895
kDebug() << "Containment name:" << containment->name()
896
<< "| type" << containment->containmentType()
897
<< "| screen:" << containment->screen()
898
<< "| desktop:" << containment->desktop()
899
<< "| geometry:" << containment->geometry()
900
<< "| zValue:" << containment->zValue();
902
// find the mapping of View to Containment, if any,
903
// so we can restore things on start.
905
if (isPanelContainment(containment)) {
906
m_panelsWaiting << containment;
907
m_panelViewCreationTimer.start();
908
} else if (containment->screen() > -1 &&
909
containment->screen() < m_corona->numScreens()) {
910
if (AppSettings::perVirtualDesktopViews()) {
911
if (containment->desktop() < 0 ||
912
containment->desktop() > KWindowSystem::numberOfDesktops() - 1) {
917
m_desktopsWaiting.append(containment);
918
m_desktopViewCreationTimer.start();
922
void PlasmaApp::setWmClass(WId id)
925
//FIXME: if argb visuals enabled Qt will always set WM_CLASS as "qt-subapplication" no matter what
926
//the application name is we set the proper XClassHint here, hopefully won't be necessary anymore when
927
//qapplication will manage apps with argvisuals in a better way
928
XClassHint classHint;
929
classHint.res_name = const_cast<char*>("Plasma");
930
classHint.res_class = const_cast<char*>("Plasma");
931
XSetClassHint(QX11Info::display(), id, &classHint);
935
void PlasmaApp::createWaitingPanels()
937
if (m_panelsWaiting.isEmpty()) {
941
const QList<QWeakPointer<Plasma::Containment> > containments = m_panelsWaiting;
942
m_panelsWaiting.clear();
944
foreach (QWeakPointer<Plasma::Containment> containmentPtr, containments) {
945
Plasma::Containment *containment = containmentPtr.data();
950
foreach (PanelView *view, m_panels) {
951
if (view->containment() == containment) {
956
if (containment->screen() < 0) {
960
// try to relocate the panel if it is on a now-non-existent screen
961
if (containment->screen() >= m_corona->numScreens()) {
962
m_panelRelocationCandidates << containment;
966
createPanelView(containment);
969
if (!m_panelRelocationCandidates.isEmpty()) {
970
QTimer::singleShot(0, this, SLOT(relocatePanels()));
974
void PlasmaApp::relocatePanels()
976
// we go through relocatables last so that all other panels can be set up first,
977
// preventing panel creation ordering to trip up the canRelocatePanel algorithm
978
Kephal::Screen *primary = Kephal::Screens::self()->primaryScreen();
979
QList<Kephal::Screen *> screens = Kephal::Screens::self()->screens();
980
screens.removeAll(primary);
982
foreach (QWeakPointer<Plasma::Containment> c, m_panelRelocationCandidates) {
983
Plasma::Containment *containment = c.data();
988
Kephal::Screen *moveTo = 0;
989
PanelView *panelView = createPanelView(containment);
990
if (canRelocatePanel(panelView, primary)) {
993
foreach (Kephal::Screen *screen, screens) {
994
if (canRelocatePanel(panelView, screen)) {
1002
panelView->migrateTo(moveTo->id());
1004
m_panels.removeAll(panelView);
1009
m_panelRelocationCandidates.clear();
1012
PanelView *PlasmaApp::createPanelView(Plasma::Containment *containment)
1014
KConfigGroup viewIds(KGlobal::config(), "ViewIds");
1015
const int id = viewIds.readEntry(QString::number(containment->id()), 0);
1016
PanelView *panelView = new PanelView(containment, id);
1018
connect(panelView, SIGNAL(destroyed(QObject*)), this, SLOT(panelRemoved(QObject*)));
1019
m_panels << panelView;
1021
setWmClass(panelView->winId());
1025
void PlasmaApp::createWaitingDesktops()
1027
const QList<QWeakPointer<Plasma::Containment> > containments = m_desktopsWaiting;
1028
m_desktopsWaiting.clear();
1030
foreach (QWeakPointer<Plasma::Containment> weakContainment, containments) {
1031
if (weakContainment) {
1032
Plasma::Containment *containment = weakContainment.data();
1033
KConfigGroup viewIds(KGlobal::config(), "ViewIds");
1034
const int id = viewIds.readEntry(QString::number(containment->id()), 0);
1036
const int desktop = AppSettings::perVirtualDesktopViews() ? containment->desktop() : -1;
1037
if (desktop >= KWindowSystem::numberOfDesktops()) {
1038
kDebug() << "not creating a view on desktop" << desktop << " as it does not exist";
1042
const int screen = containment->screen();
1043
if (screen >= m_corona->numScreens() || screen < 0) {
1044
kDebug() << "not creating a view on screen" << screen << "as it does not exist";
1048
DesktopView *view = viewForScreen(screen, desktop);
1051
kDebug() << "already had a view for" << containment->screen() << containment->desktop();
1052
// we already have a view for this screen
1056
kDebug() << "creating a new view for" << containment->screen() << containment->desktop()
1057
<< "and we have" << m_corona->numScreens() << "screens";
1059
// we have a new screen. neat.
1060
view = new DesktopView(containment, id, 0);
1061
connect(view, SIGNAL(dashboardClosed()), this, SLOT(dashboardClosed()));
1063
connect(m_corona, SIGNAL(screenOwnerChanged(int,int,Plasma::Containment*)),
1064
view, SLOT(screenOwnerChanged(int,int,Plasma::Containment*)));
1067
m_desktops.append(view);
1069
setWmClass(view->winId());
1072
setFixedDashboard(fixedDashboard());
1075
void PlasmaApp::containmentAdded(Plasma::Containment *containment)
1077
if (isPanelContainment(containment)) {
1078
foreach (PanelView * panel, m_panels) {
1079
if (panel->containment() == containment) {
1080
kDebug() << "not creating second PanelView with existing Containment!!";
1086
createView(containment);
1088
disconnect(containment, 0, this, 0);
1089
connect(containment, SIGNAL(configureRequested(Plasma::Containment*)),
1090
this, SLOT(configureContainment(Plasma::Containment*)));
1092
if ((containment->containmentType() == Plasma::Containment::DesktopContainment ||
1093
containment->containmentType() == Plasma::Containment::CustomContainment)) {
1094
QAction *a = containment->action("remove");
1095
delete a; //activities handle removal now
1096
if (!(m_loadingActivity.isEmpty() || m_corona->offscreenWidgets().contains(containment))) {
1097
Plasma::Context *context = containment->context();
1098
if (context->currentActivityId().isEmpty()) {
1099
//kDebug() << "@#$%@#$%@#$%@#$%#@$#@%@$#^%$&^$^$%#%$";
1100
//kDebug() << "script->containment->activity";
1101
Activity *activity = m_corona->activity(m_loadingActivity);
1103
activity->replaceContainment(containment);
1107
if (containment->containmentType() == Plasma::Containment::DesktopContainment) {
1108
foreach (QAction *action, m_corona->actions()) {
1109
containment->addToolBoxAction(action);
1114
if (!isPanelContainment(containment) && !KAuthorized::authorize("editable_desktop_icons")) {
1115
containment->setImmutability(Plasma::SystemImmutable);
1119
void PlasmaApp::containmentScreenOwnerChanged(int wasScreen, int isScreen, Plasma::Containment *containment)
1122
kDebug() << "@@@was" << wasScreen << "is" << isScreen << (QObject*)containment << m_desktops.count();
1125
kDebug() << "@@@screen<0";
1129
if (isPanelContainment(containment)) {
1130
kDebug() << "@@@isPanel";
1134
bool pvd = AppSettings::perVirtualDesktopViews();
1135
foreach (DesktopView *view, m_desktops) {
1136
if (view->screen() == isScreen && (!pvd || view->desktop() == containment->desktop())) {
1137
kDebug() << "@@@@found view" << view;
1142
kDebug() << "@@@@appending";
1143
m_desktopsWaiting.append(containment);
1144
m_desktopViewCreationTimer.start();
1147
void PlasmaApp::configureContainment(Plasma::Containment *containment)
1149
const QString id = "plasma_containment_settings_" + QString::number(containment->id());
1150
BackgroundDialog *configDialog = qobject_cast<BackgroundDialog*>(KConfigDialog::exists(id));
1153
configDialog->reloadConfig();
1155
const QSize resolution = QApplication::desktop()->screenGeometry(containment->screen()).size();
1156
Plasma::View *view = viewForScreen(containment->screen(), containment->desktop());
1159
view = viewForScreen(desktop()->screenNumber(QCursor::pos()), containment->desktop());
1162
if (m_desktops.count() < 1) {
1166
view = m_desktops.at(0);
1171
KConfigSkeleton *nullManager = new KConfigSkeleton(0);
1172
configDialog = new BackgroundDialog(resolution, containment, view, 0, id, nullManager);
1173
configDialog->setAttribute(Qt::WA_DeleteOnClose);
1175
Activity *activity = m_corona->activity(containment->context()->currentActivityId());
1177
connect(configDialog, SIGNAL(containmentPluginChanged(Plasma::Containment*)),
1178
activity, SLOT(replaceContainment(Plasma::Containment*)));
1179
connect(configDialog, SIGNAL(destroyed(QObject*)), nullManager, SLOT(deleteLater()));
1182
configDialog->show();
1183
KWindowSystem::setOnDesktop(configDialog->winId(), KWindowSystem::currentDesktop());
1184
KWindowSystem::activateWindow(configDialog->winId());
1187
void PlasmaApp::cloneCurrentActivity()
1189
KActivityController controller;
1190
//getting the current activity is *so* much easier than the current containment(s) :) :)
1191
QString oldId = controller.currentActivity();
1192
Activity *oldActivity = m_corona->activity(oldId);
1193
QString newId = controller.addActivity(i18nc("%1 is the activity name", "copy of %1", oldActivity->name()));
1195
QString file = "activities/";
1197
KConfig external(file, KConfig::SimpleConfig, "appdata");
1199
//copy the old config to the new location
1200
oldActivity->save(external);
1201
//kDebug() << "saved to" << file;
1204
controller.setCurrentActivity(newId);
1207
//TODO accomodate activities
1208
void PlasmaApp::setPerVirtualDesktopViews(bool perDesktopViews)
1210
if (perDesktopViews == perVirtualDesktopViews()) {
1213
AppSettings::setPerVirtualDesktopViews(perDesktopViews);
1214
AppSettings::self()->writeConfig();
1216
disconnect(KWindowSystem::self(), SIGNAL(numberOfDesktopsChanged(int)),
1217
this, SLOT(checkVirtualDesktopViews(int)));
1219
m_pendingFixedDashboard = fixedDashboard();
1221
if (perDesktopViews) {
1222
connect(KWindowSystem::self(), SIGNAL(numberOfDesktopsChanged(int)),
1223
this, SLOT(checkVirtualDesktopViews(int)));
1224
checkVirtualDesktopViews(KWindowSystem::numberOfDesktops());
1225
setFixedDashboard(m_pendingFixedDashboard);
1227
QList<DesktopView *> perScreenViews;
1228
foreach (DesktopView *view, m_desktops) {
1229
if (view->containment()) {
1230
view->containment()->setScreen(-1, -1);
1237
m_corona->checkScreens(true);
1241
bool PlasmaApp::perVirtualDesktopViews() const
1243
return AppSettings::perVirtualDesktopViews();
1246
void PlasmaApp::checkVirtualDesktopViews(int numDesktops)
1248
kDebug() << numDesktops;
1249
if (AppSettings::perVirtualDesktopViews()) {
1250
QMutableListIterator<DesktopView *> it(m_desktops);
1251
while (it.hasNext()) {
1252
DesktopView *view = it.next();
1253
if (!view->containment() || view->desktop() < 0 || view->desktop() >= numDesktops) {
1260
m_corona->checkScreens(true);
1263
void PlasmaApp::setFixedDashboard(bool fixedDashboard)
1265
//TODO: should probably have one dashboard containment per screen
1266
Plasma::Containment *c = 0;
1267
m_pendingFixedDashboard = fixedDashboard;
1268
if (fixedDashboard) {
1269
foreach (Plasma::Containment *possibility, m_corona->containments()) {
1270
if (possibility->pluginName() == "desktopDashboard") {
1277
//avoid the containmentAdded signal being emitted
1278
c = m_corona->addContainment("desktopDashboard");
1281
//everything failed? probably a badly packaged plasma
1285
m_corona->addOffscreenWidget(c);
1289
foreach (DesktopView *view, m_desktops) {
1290
view->setDashboardContainment(c);
1291
if (view->size().width() > maxViewSize.width() && view->size().height() > maxViewSize.height()) {
1292
maxViewSize = view->size();
1296
if (fixedDashboard) {
1297
c->resize(maxViewSize);
1300
m_corona->requestConfigSync();
1303
bool PlasmaApp::fixedDashboard() const
1305
if (m_desktops.isEmpty()) {
1306
return m_pendingFixedDashboard;
1309
foreach (DesktopView *view, m_desktops) {
1310
if (!view->dashboardFollowsDesktop()) {
1318
void PlasmaApp::panelRemoved(QObject *panel)
1320
m_panels.removeAll((PanelView *)panel);
1323
void PlasmaApp::remotePlasmoidAdded(Plasma::PackageMetadata metadata)
1326
if (m_desktops.isEmpty()) {
1330
if (m_corona->immutability() == Plasma::SystemImmutable) {
1331
kDebug() << "Corona is system locked";
1335
// the notification ptr is automatically delete when the notification is closed
1336
KNotification *notification = new KNotification("newplasmoid", m_desktops.at(0));
1337
notification->setText(i18n("A new widget has become available on the network:<br><b>%1</b> - <i>%2</i>",
1338
metadata.name(), metadata.description()));
1340
// setup widget icon
1341
if (!metadata.icon().isEmpty()) {
1342
notification->setPixmap(KIcon(metadata.icon()).pixmap(IconSize(KIconLoader::Desktop)));
1345
// locked, but the user is able to unlock
1346
if (m_corona->immutability() == Plasma::UserImmutable) {
1347
m_unlockCorona = true;
1348
notification->setActions(QStringList(i18n("Unlock and add to current activity")));
1350
// immutability == Plasma::Mutable
1351
notification->setActions(QStringList(i18n("Add to current activity")));
1354
m_mapper->setMapping(notification, metadata.remoteLocation().prettyUrl());
1355
connect(notification, SIGNAL(action1Activated()), m_mapper, SLOT(map()));
1357
kDebug() << "firing notification";
1358
notification->sendEvent();
1361
void PlasmaApp::addRemotePlasmoid(const QString &location)
1363
if (m_unlockCorona) {
1364
m_unlockCorona = false;
1365
m_corona->setImmutability(Plasma::Mutable);
1368
Plasma::AccessManager::self()->accessRemoteApplet(KUrl(location));
1371
void PlasmaApp::plasmoidAccessFinished(Plasma::AccessAppletJob *job)
1373
if (m_desktops.isEmpty()) {
1377
Plasma::Containment *c = m_desktops.at(0)->containment();
1379
kDebug() << "adding applet";
1380
c->addApplet(job->applet(), QPointF(-1, -1), false);
1384
void PlasmaApp::createActivity(const QString &plugin)
1386
KActivityController controller;
1387
QString id = controller.addActivity(i18nc("Default name for a new activity", "New Activity"));
1389
Activity *a = m_corona->activity(id);
1391
a->setDefaultPlugin(plugin);
1393
controller.setCurrentActivity(id);
1396
void PlasmaApp::createActivityFromScript(const QString &script, const QString &name, const QString &icon, const QStringList &startupApps)
1398
KActivityController controller;
1399
m_loadingActivity = controller.addActivity(name);
1400
Activity *a = m_corona->activity(m_loadingActivity);
1404
//kDebug() << "$$$$$$$$$$$$$$$$ begin script for" << m_loadingActivity;
1405
m_corona->evaluateScripts(QStringList() << script);
1406
//kDebug() << "$$$$$$$$$$$$$$$$ end script for" << m_loadingActivity;
1408
controller.setCurrentActivity(m_loadingActivity);
1409
m_loadingActivity.clear();
1411
KListConfirmationDialog * confirmDialog = new KListConfirmationDialog(
1412
i18n("Run applications"),
1413
i18n("This activity template requests to run the following applications"),
1414
i18n("Run selected"),
1417
connect(confirmDialog, SIGNAL(selected(QList<QVariant>)),
1418
this, SLOT(executeCommands(QList<QVariant>)));
1420
foreach (const QString & exec, startupApps) {
1421
QString realExec = exec;
1423
#define LazyReplace(VAR, VAL) \
1424
if (realExec.contains(VAR)) realExec = realExec.replace(VAR, VAL);
1426
LazyReplace("$desktop", KGlobalSettings::desktopPath());
1427
LazyReplace("$autostart", KGlobalSettings::autostartPath());
1428
LazyReplace("$documents", KGlobalSettings::documentPath());
1429
LazyReplace("$music", KGlobalSettings::musicPath());
1430
LazyReplace("$video", KGlobalSettings::videosPath());
1431
LazyReplace("$downloads", KGlobalSettings::downloadPath());
1432
LazyReplace("$pictures", KGlobalSettings::picturesPath());
1434
QString name = realExec.split(" ")[0];
1436
KService::Ptr service = KService::serviceByDesktopName(name);
1439
confirmDialog->addItem(KIcon(service->icon()), service->name(),
1440
((realExec == name) ? QString() : realExec), realExec, exec.split(" ").size() <= 2);
1442
confirmDialog->addItem(KIcon("dialog-warning"), name,
1443
((realExec == name) ? QString() : realExec), realExec, false);
1449
confirmDialog->exec();
1452
void PlasmaApp::executeCommands(const QList < QVariant > & commands)
1454
foreach (const QVariant & command, commands) {
1455
KRun::runCommand(command.toString(), 0);
1459
#include "plasmaapp.moc"