1
/********************************************************************
2
KWin - the KDE window manager
3
This file is part of the KDE project.
5
Copyright (C) 2009 Martin Gräßlin <kde@martin-graesslin.com>
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2 of the License, or
10
(at your option) any later version.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program. If not, see <http://www.gnu.org/licenses/>.
19
*********************************************************************/
23
#include <QDBusMessage>
24
#include <QDBusConnection>
25
#include <QDBusInterface>
27
#include <KAboutApplicationDialog>
28
#include <KActionCollection>
30
#include <KCModuleProxy>
31
#include <KGlobalAccel>
32
#include <KPluginInfo>
33
#include <KPluginFactory>
34
#include <KConfigGroup>
36
#include <KServiceTypeTrader>
37
#include <KShortcutsEditor>
45
K_PLUGIN_FACTORY(KWinDesktopConfigFactory, registerPlugin<KWin::KWinDesktopConfig>();)
46
K_EXPORT_PLUGIN(KWinDesktopConfigFactory("kcm_kwindesktop"))
51
KWinDesktopConfigForm::KWinDesktopConfigForm(QWidget* parent)
57
KWinDesktopConfig::KWinDesktopConfig(QWidget* parent, const QVariantList& args)
58
: KCModule(KWinDesktopConfigFactory::componentData(), parent, args)
59
, m_config(KSharedConfig::openConfig("kwinrc"))
60
, m_actionCollection(NULL)
61
, m_switchDesktopCollection(NULL)
66
void KWinDesktopConfig::init()
68
m_ui = new KWinDesktopConfigForm(this);
69
// TODO: there has to be a way to add the shortcuts editor to the ui file
70
m_editor = new KShortcutsEditor(m_ui, KShortcutsEditor::GlobalAction);
71
m_ui->editorFrame->setLayout(new QVBoxLayout());
72
m_ui->editorFrame->layout()->setMargin(0);
73
m_ui->editorFrame->layout()->addWidget(m_editor);
75
m_ui->desktopNames->setDesktopConfig(this);
76
m_ui->desktopNames->setMaxDesktops(maxDesktops);
77
m_ui->desktopNames->numberChanged(defaultDesktops);
79
// number of rows are still missing in Plasma - hide them for now
80
// TODO: bring them back when trunk is open and bug Plasma devs ;-)
82
m_ui->rowsSpinBox->hide();
84
QVBoxLayout* layout = new QVBoxLayout(this);
85
layout->addWidget(m_ui);
87
setQuickHelp(i18n("<h1>Multiple Desktops</h1>In this module, you can configure how many virtual desktops you want and how these should be labeled."));
89
// Shortcut config. The shortcut belongs to the component "kwin"!
90
m_actionCollection = new KActionCollection(this, KComponentData("kwin"));
91
m_actionCollection->setConfigGroup("Desktop Switching");
92
m_actionCollection->setConfigGlobal(true);
94
m_switchDesktopCollection = new KActionCollection(this, KComponentData("kwin"));
95
m_switchDesktopCollection->setConfigGroup("Desktop Switching");
96
m_switchDesktopCollection->setConfigGlobal(true);
98
// actions for switch desktop collection - other action is filled dynamically
99
KAction* a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction("Switch to Next Desktop"));
100
a->setProperty("isConfigurationAction", true);
101
a->setText(i18n("Switch to Next Desktop"));
102
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
104
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction("Switch to Previous Desktop"));
105
a->setProperty("isConfigurationAction", true);
106
a->setText(i18n("Switch to Previous Desktop"));
107
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
109
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction("Switch One Desktop to the Right"));
110
a->setProperty("isConfigurationAction", true);
111
a->setText(i18n("Switch One Desktop to the Right"));
112
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
114
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction("Switch One Desktop to the Left"));
115
a->setProperty("isConfigurationAction", true);
116
a->setText(i18n("Switch One Desktop to the Left"));
117
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
119
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction("Switch One Desktop Up"));
120
a->setProperty("isConfigurationAction", true);
121
a->setText(i18n("Switch One Desktop Up"));
122
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
124
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction("Switch One Desktop Down"));
125
a->setProperty("isConfigurationAction", true);
126
a->setText(i18n("Switch One Desktop Down"));
127
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
129
m_editor->addCollection(m_switchDesktopCollection, i18n("Desktop Switching"));
132
// get number of desktops
133
NETRootInfo info(QX11Info::display(), NET::NumberOfDesktops | NET::DesktopNames);
134
int n = info.numberOfDesktops();
136
for (int i = 1; i <= n; ++i) {
137
KAction* a = qobject_cast<KAction*>(m_actionCollection->addAction(QString("Switch to Desktop %1").arg(i)));
138
a->setProperty("isConfigurationAction", true);
139
a->setText(i18n("Switch to Desktop %1", i));
140
a->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
143
// This should be after the "Switch to Desktop %1" loop. It HAS to be
144
// there after numberSpinBox is connected to slotChangeShortcuts. We would
145
// overwrite the users settings if not,
146
m_ui->numberSpinBox->setValue(n);
148
m_editor->addCollection(m_actionCollection, i18n("Desktop Switching"));
151
// search the effect names
152
// TODO: way to recognize if a effect is not found
153
KServiceTypeTrader* trader = KServiceTypeTrader::self();
154
KService::List services;
158
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_slide'");
159
if (!services.isEmpty())
160
slide = services.first()->name();
161
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_cubeslide'");
162
if (!services.isEmpty())
163
cube = services.first()->name();
164
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_fadedesktop'");
165
if (!services.isEmpty())
166
fadedesktop = services.first()->name();
168
m_ui->effectComboBox->addItem(i18n("No Animation"));
169
m_ui->effectComboBox->addItem(slide);
170
m_ui->effectComboBox->addItem(cube);
171
m_ui->effectComboBox->addItem(fadedesktop);
173
// effect config and info button
174
m_ui->effectInfoButton->setIcon(KIcon("dialog-information"));
175
m_ui->effectConfigButton->setIcon(KIcon("configure"));
177
connect(m_ui->rowsSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
178
connect(m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
179
connect(m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(slotChangeShortcuts(int)));
180
connect(m_ui->activityCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
181
connect(m_ui->desktopNames, SIGNAL(changed()), SLOT(changed()));
182
connect(m_ui->popupInfoCheckBox, SIGNAL(toggled(bool)), SLOT(changed()));
183
connect(m_ui->popupHideSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
184
connect(m_ui->desktopLayoutIndicatorCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
185
connect(m_ui->wrapAroundBox, SIGNAL(stateChanged(int)), SLOT(changed()));
186
connect(m_editor, SIGNAL(keyChange()), SLOT(changed()));
187
connect(m_ui->allShortcutsCheckBox, SIGNAL(stateChanged(int)), SLOT(slotShowAllShortcuts()));
188
connect(m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changed()));
189
connect(m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotEffectSelectionChanged(int)));
190
connect(m_ui->effectInfoButton, SIGNAL(clicked()), SLOT(slotAboutEffectClicked()));
191
connect(m_ui->effectConfigButton, SIGNAL(clicked()), SLOT(slotConfigureEffectClicked()));
193
// Begin check for immutable - taken from old desktops kcm
195
int kwin_screen_number = DefaultScreen(QX11Info::display());
197
int kwin_screen_number = 0;
200
m_config = KSharedConfig::openConfig("kwinrc");
202
QByteArray groupname;
203
if (kwin_screen_number == 0)
204
groupname = "Desktops";
206
groupname = "Desktops-screen-" + QByteArray::number(kwin_screen_number);
208
if (m_config->isGroupImmutable(groupname)) {
209
m_ui->nameGroup->setEnabled(false);
210
//number of desktops widgets
211
m_ui->numberLabel->setEnabled(false);
212
m_ui->numberSpinBox->setEnabled(false);
214
KConfigGroup cfgGroup(m_config.data(), groupname.constData());
215
if (cfgGroup.isEntryImmutable("Number")) {
216
//number of desktops widgets
217
m_ui->numberLabel->setEnabled(false);
218
m_ui->numberSpinBox->setEnabled(false);
221
// End check for immutable
224
KWinDesktopConfig::~KWinDesktopConfig()
229
void KWinDesktopConfig::defaults()
231
// TODO: plasma stuff
232
m_ui->numberSpinBox->setValue(defaultDesktops);
233
m_ui->desktopNames->numberChanged(defaultDesktops);
234
for (int i = 1; i <= maxDesktops; i++) {
235
m_desktopNames[i-1] = i18n("Desktop %1", i);
236
if (i <= defaultDesktops)
237
m_ui->desktopNames->setDefaultName(i);
241
m_ui->popupInfoCheckBox->setChecked(false);
242
m_ui->popupHideSpinBox->setValue(1000);
243
m_ui->desktopLayoutIndicatorCheckBox->setChecked(true);
245
m_ui->effectComboBox->setCurrentIndex(1);
247
m_ui->wrapAroundBox->setChecked(true);
249
m_editor->allDefault();
255
void KWinDesktopConfig::load()
257
// This method is called on reset(). So undo all changes.
261
// get number of desktops
262
NETRootInfo info(QX11Info::display(), NET::NumberOfDesktops | NET::DesktopNames);
264
for (int i = 1; i <= maxDesktops; i++) {
265
QString name = QString::fromUtf8(info.desktopName(i));
266
m_desktopNames << name;
267
m_ui->desktopNames->setName(i, name);
272
KConfigGroup popupInfo(m_config, "PopupInfo");
273
m_ui->popupInfoCheckBox->setChecked(popupInfo.readEntry("ShowPopup", false));
274
m_ui->popupHideSpinBox->setValue(popupInfo.readEntry("PopupHideDelay", 1000));
275
m_ui->desktopLayoutIndicatorCheckBox->setChecked(!popupInfo.readEntry("TextOnly", false));
277
// Wrap Around on screen edge
278
KConfigGroup windowConfig(m_config, "Windows");
279
m_ui->wrapAroundBox->setChecked(windowConfig.readEntry<bool>("RollOverDesktops", true));
281
// Effect for desktop switching
282
// Set current option to "none" if no plugin is activated.
283
KConfigGroup effectconfig(m_config, "Plugins");
284
m_ui->effectComboBox->setCurrentIndex(0);
285
if (effectEnabled("slide", effectconfig))
286
m_ui->effectComboBox->setCurrentIndex(1);
287
if (effectEnabled("cubeslide", effectconfig))
288
m_ui->effectComboBox->setCurrentIndex(2);
289
if (effectEnabled("fadedesktop", effectconfig))
290
m_ui->effectComboBox->setCurrentIndex(3);
291
slotEffectSelectionChanged(m_ui->effectComboBox->currentIndex());
292
// TODO: plasma stuff
294
QDBusInterface interface("org.kde.plasma-desktop", "/App");
295
if (interface.isValid()) {
296
bool perVirtualDesktopViews = interface.call("perVirtualDesktopViews").arguments().first().toBool();
297
m_ui->activityCheckBox->setEnabled(true);
298
m_ui->activityCheckBox->setChecked(perVirtualDesktopViews);
300
m_ui->activityCheckBox->setEnabled(false);
305
void KWinDesktopConfig::save()
307
// TODO: plasma stuff
309
NETRootInfo info(QX11Info::display(), NET::NumberOfDesktops | NET::DesktopNames);
311
for (int i = 1; i <= maxDesktops; i++) {
312
QString desktopName = m_desktopNames[ i -1 ];
313
if (i <= m_ui->numberSpinBox->value())
314
desktopName = m_ui->desktopNames->name(i);
315
info.setDesktopName(i, desktopName.toUtf8());
318
// set number of desktops
319
info.setNumberOfDesktops(m_ui->numberSpinBox->value());
322
XSync(QX11Info::display(), false);
326
KConfigGroup popupInfo(m_config, "PopupInfo");
327
popupInfo.writeEntry("ShowPopup", m_ui->popupInfoCheckBox->isChecked());
328
popupInfo.writeEntry("PopupHideDelay", m_ui->popupHideSpinBox->value());
329
popupInfo.writeEntry("TextOnly", !m_ui->desktopLayoutIndicatorCheckBox->isChecked());
331
// Wrap Around on screen edge
332
KConfigGroup windowConfig(m_config, "Windows");
333
windowConfig.writeEntry("RollOverDesktops", m_ui->wrapAroundBox->isChecked());
335
// Effect desktop switching
336
KConfigGroup effectconfig(m_config, "Plugins");
337
int desktopSwitcher = m_ui->effectComboBox->currentIndex();
338
switch(desktopSwitcher) {
341
effectconfig.writeEntry("kwin4_effect_slideEnabled", false);
342
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", false);
343
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", false);
347
effectconfig.writeEntry("kwin4_effect_slideEnabled", true);
348
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", false);
349
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", false);
353
effectconfig.writeEntry("kwin4_effect_slideEnabled", false);
354
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", true);
355
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", false);
359
effectconfig.writeEntry("kwin4_effect_slideEnabled", false);
360
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", false);
361
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", true);
368
// Send signal to all kwin instances
369
QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig");
370
QDBusConnection::sessionBus().send(message);
372
QDBusInterface interface("org.kde.plasma-desktop", "/App");
373
interface.call("setPerVirtualDesktopViews", (m_ui->activityCheckBox->isChecked()));
379
void KWinDesktopConfig::undo()
381
// The global shortcuts editor makes changes active immediately. In case
382
// of undo we have to undo them manually
383
m_editor->undoChanges();
386
QString KWinDesktopConfig::cachedDesktopName(int desktop)
388
if (desktop > m_desktopNames.size())
390
return m_desktopNames[ desktop -1 ];
393
QString KWinDesktopConfig::extrapolatedShortcut(int desktop) const
396
if (!desktop || desktop > m_actionCollection->count())
399
return QString("Ctrl+F1");
401
KAction *beforeAction = qobject_cast<KAction*>(m_actionCollection->actions().at(qMin(9, desktop - 2)));
402
QString before = beforeAction->globalShortcut(KAction::ActiveShortcut).toString();
403
if (before.isEmpty())
404
before = beforeAction->globalShortcut(KAction::DefaultShortcut).toString();
407
if (before.contains(QRegExp("F[0-9]{1,2}"))) {
408
if (desktop < 13) // 10?
409
seq = QString("F%1").arg(desktop);
410
else if (!before.contains("Shift"))
411
seq = "Shift+" + QString("F%1").arg(desktop - 10);
412
} else if (before.contains(QRegExp("[0-9]"))) {
415
else if (desktop > 10) {
416
if (!before.contains("Shift"))
417
seq = "Shift+" + QString::number(desktop == 20 ? 0 : (desktop - 10));
419
seq = QString::number(desktop);
422
if (!seq.isEmpty()) {
423
if (before.contains("Ctrl"))
424
seq.prepend("Ctrl+");
425
if (before.contains("Alt"))
427
if (before.contains("Shift"))
428
seq.prepend("Shift+");
429
if (before.contains("Meta"))
430
seq.prepend("Meta+");
435
void KWinDesktopConfig::slotChangeShortcuts(int number)
437
if ((number < 1) || (number > maxDesktops))
440
if (m_ui->allShortcutsCheckBox->isChecked())
441
number = maxDesktops;
443
while (number != m_actionCollection->count()) {
444
if (number < m_actionCollection->count()) {
445
// Remove the action from the action collection. The action itself
446
// will still exist because that's the way kwin currently works.
447
// No need to remove/forget it. See kwinbindings.
448
KAction *a = qobject_cast<KAction*>(
449
m_actionCollection->takeAction(m_actionCollection->actions().last()));
450
// Remove any associated global shortcut. Set it to ""
451
a->setGlobalShortcut(
453
KAction::ActiveShortcut,
454
KAction::NoAutoloading);
455
m_ui->messageLabel->hide();
459
int desktop = m_actionCollection->count() + 1;
460
KAction* action = qobject_cast<KAction*>(m_actionCollection->addAction(QString("Switch to Desktop %1").arg(desktop)));
461
action->setProperty("isConfigurationAction", true);
462
action->setText(i18n("Switch to Desktop %1", desktop));
463
action->setGlobalShortcut(KShortcut(), KAction::ActiveShortcut);
464
QString shortcutString = extrapolatedShortcut(desktop);
465
if (shortcutString.isEmpty()) {
466
m_ui->messageLabel->setText(i18n("No suitable Shortcut for Desktop %1 found", desktop));
467
m_ui->messageLabel->show();
469
KShortcut shortcut(shortcutString);
470
if (!shortcut.primary().isEmpty() || KGlobalAccel::self()->isGlobalShortcutAvailable(shortcut.primary())) {
471
action->setGlobalShortcut(shortcut, KAction::ActiveShortcut, KAction::NoAutoloading);
472
m_ui->messageLabel->setText(i18n("Assigned global Shortcut \"%1\" to Desktop %2", shortcutString, desktop));
473
m_ui->messageLabel->show();
475
m_ui->messageLabel->setText(i18n("Shortcut conflict: Could not set Shortcut %1 for Desktop %2", shortcutString, desktop));
476
m_ui->messageLabel->show();
481
m_editor->clearCollections();
482
m_editor->addCollection(m_switchDesktopCollection, i18n("Desktop Switching"));
483
m_editor->addCollection(m_actionCollection, i18n("Desktop Switching"));
486
void KWinDesktopConfig::slotShowAllShortcuts()
488
slotChangeShortcuts(m_ui->numberSpinBox->value());
491
void KWinDesktopConfig::slotEffectSelectionChanged(int index)
493
bool enabled = false;
496
m_ui->effectInfoButton->setEnabled(enabled);
497
// only cube has config dialog
500
m_ui->effectConfigButton->setEnabled(enabled);
504
bool KWinDesktopConfig::effectEnabled(const QString& effect, const KConfigGroup& cfg) const
506
KService::List services = KServiceTypeTrader::self()->query(
507
"KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\'');
508
if (services.isEmpty())
510
QVariant v = services.first()->property("X-KDE-PluginInfo-EnabledByDefault");
511
return cfg.readEntry("kwin4_effect_" + effect + "Enabled", v.toBool());
514
void KWinDesktopConfig::slotAboutEffectClicked()
516
KServiceTypeTrader* trader = KServiceTypeTrader::self();
517
KService::List services;
519
switch(m_ui->effectComboBox->currentIndex()) {
524
effect = "cubeslide";
527
effect = "fadedesktop";
532
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\'');
533
if (services.isEmpty())
535
KPluginInfo pluginInfo(services.first());
537
const QString name = pluginInfo.name();
538
const QString comment = pluginInfo.comment();
539
const QString author = pluginInfo.author();
540
const QString email = pluginInfo.email();
541
const QString website = pluginInfo.website();
542
const QString version = pluginInfo.version();
543
const QString license = pluginInfo.license();
544
const QString icon = pluginInfo.icon();
546
KAboutData aboutData(name.toUtf8(), name.toUtf8(), ki18n(name.toUtf8()), version.toUtf8(), ki18n(comment.toUtf8()), KAboutLicense::byKeyword(license).key(), ki18n(QByteArray()), ki18n(QByteArray()), website.toLatin1());
547
aboutData.setProgramIconName(icon);
548
const QStringList authors = author.split(',');
549
const QStringList emails = email.split(',');
551
if (authors.count() == emails.count()) {
552
foreach (const QString & author, authors) {
553
if (!author.isEmpty()) {
554
aboutData.addAuthor(ki18n(author.toUtf8()), ki18n(QByteArray()), emails[i].toUtf8(), 0);
559
QPointer<KAboutApplicationDialog> aboutPlugin = new KAboutApplicationDialog(&aboutData, this);
564
void KWinDesktopConfig::slotConfigureEffectClicked()
567
switch(m_ui->effectComboBox->currentIndex()) {
569
effect = "cubeslide_config";
574
KCModuleProxy* proxy = new KCModuleProxy(effect);
575
QPointer< KDialog > configDialog = new KDialog(this);
576
configDialog->setWindowTitle(m_ui->effectComboBox->currentText());
577
configDialog->setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Default);
578
connect(configDialog, SIGNAL(defaultClicked()), proxy, SLOT(defaults()));
580
QWidget *showWidget = new QWidget(configDialog);
581
QVBoxLayout *layout = new QVBoxLayout;
582
showWidget->setLayout(layout);
583
layout->addWidget(proxy);
584
layout->insertSpacing(-1, KDialog::marginHint());
585
configDialog->setMainWidget(showWidget);
587
if (configDialog->exec() == QDialog::Accepted) {