kcm.cpp 25.8 KB
Newer Older
Marco Martin's avatar
Marco Martin committed
1
2
3
/* This file is part of the KDE Project
   Copyright (c) 2014 Marco Martin <mart@kde.org>
   Copyright (c) 2014 Vishesh Handa <me@vhanda.in>
4
   Copyright (c) 2019 Cyril Rossi <cyril.rossi@enioka.com>
Marco Martin's avatar
Marco Martin committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "kcm.h"
#include "../krdb/krdb.h"
#include "config-kcm.h"
24
#include "config-workspace.h"
Marco Martin's avatar
Marco Martin committed
25
26
27
28
29
#include <klauncher_iface.h>

#include <KAboutData>
#include <KSharedConfig>
#include <KGlobalSettings>
Marco Martin's avatar
Marco Martin committed
30
#include <KIconLoader>
31
32
#include <KAutostart>
#include <KRun>
33
#include <KService>
Marco Martin's avatar
Marco Martin committed
34

35
#include <QDebug>
36
37
#include <QQuickItem>
#include <QQuickWindow>
38
39
#include <QStandardPaths>
#include <QProcess>
Marco Martin's avatar
Marco Martin committed
40
41
#include <QStandardItemModel>
#include <QX11Info>
42
43
#include <QStyle>
#include <QStyleFactory>
Marco Martin's avatar
Marco Martin committed
44
45

#include <KLocalizedString>
46
#include <KPackage/PackageLoader>
Marco Martin's avatar
Marco Martin committed
47
48
49

#include <X11/Xlib.h>

50
51
#include "lookandfeelsettings.h"

52
53
54
55
56
#ifdef HAVE_XCURSOR
#   include "../cursortheme/xcursor/xcursortheme.h"
#   include <X11/Xcursor/Xcursor.h>
#endif

Marco Martin's avatar
Marco Martin committed
57
58
59
60
#ifdef HAVE_XFIXES
#  include <X11/extensions/Xfixes.h>
#endif

61
62
63
KCMLookandFeel::KCMLookandFeel(QObject *parent, const QVariantList &args)
    : KQuickAddons::ManagedConfigModule(parent, args)
    , m_settings(new LookAndFeelSettings(this))
64
    , m_config(QStringLiteral("kdeglobals"))
Marco Martin's avatar
Marco Martin committed
65
66
67
68
69
70
    , m_configGroup(m_config.group("KDE"))
    , m_applyColors(true)
    , m_applyWidgetStyle(true)
    , m_applyIcons(true)
    , m_applyPlasmaTheme(true)
    , m_applyCursors(true)
71
    , m_applyWindowSwitcher(true)
72
    , m_applyDesktopSwitcher(true)
73
    , m_resetDefaultLayout(false)
74
    , m_applyWindowDecoration(true)
Marco Martin's avatar
Marco Martin committed
75
{
76
    qmlRegisterType<LookAndFeelSettings>();
Marco Martin's avatar
Marco Martin committed
77
    qmlRegisterType<QStandardItemModel>();
Marco Martin's avatar
Marco Martin committed
78
    qmlRegisterType<KCMLookandFeel>();
79
80

    KAboutData *about = new KAboutData(QStringLiteral("kcm_lookandfeel"), i18n("Global Theme"),
81
82
                                       QStringLiteral("0.1"), QString(), KAboutLicense::LGPL);
    about->addAuthor(i18n("Marco Martin"), QString(), QStringLiteral("mart@kde.org"));
Marco Martin's avatar
Marco Martin committed
83
    setAboutData(about);
84
    setButtons(Apply | Default);
Marco Martin's avatar
Marco Martin committed
85
86
87
88

    m_model = new QStandardItemModel(this);
    QHash<int, QByteArray> roles = m_model->roleNames();
    roles[PluginNameRole] = "pluginName";
89
    roles[DescriptionRole] = "description";
90
    roles[ScreenshotRole] = "screenshot";
91
    roles[FullScreenPreviewRole] = "fullScreenPreview";
Marco Martin's avatar
Marco Martin committed
92
93
94
95
96
97
98
99
100
101
    roles[HasSplashRole] = "hasSplash";
    roles[HasLockScreenRole] = "hasLockScreen";
    roles[HasRunCommandRole] = "hasRunCommand";
    roles[HasLogoutRole] = "hasLogout";

    roles[HasColorsRole] = "hasColors";
    roles[HasWidgetStyleRole] = "hasWidgetStyle";
    roles[HasIconsRole] = "hasIcons";
    roles[HasPlasmaThemeRole] = "hasPlasmaTheme";
    roles[HasCursorsRole] = "hasCursors";
102
    roles[HasWindowSwitcherRole] = "hasWindowSwitcher";
103
    roles[HasDesktopSwitcherRole] = "hasDesktopSwitcher";
Marco Martin's avatar
Marco Martin committed
104
    m_model->setItemRoleNames(roles);
105
    loadModel();
Marco Martin's avatar
Marco Martin committed
106
107
}

Marco Martin's avatar
Marco Martin committed
108
109
110
111
KCMLookandFeel::~KCMLookandFeel()
{
}

112
void KCMLookandFeel::reloadModel()
Marco Martin's avatar
Marco Martin committed
113
{
114
    loadModel();
Marco Martin's avatar
Marco Martin committed
115
}
Marco Martin's avatar
Marco Martin committed
116

117
QStandardItemModel *KCMLookandFeel::lookAndFeelModel() const
Marco Martin's avatar
Marco Martin committed
118
119
120
121
{
    return m_model;
}

122
int KCMLookandFeel::pluginIndex(const QString &pluginName) const
Marco Martin's avatar
Marco Martin committed
123
{
124
125
126
    const auto results = m_model->match(m_model->index(0, 0), PluginNameRole, pluginName);
    if (results.count() == 1) {
        return results.first().row();
127
    }
Marco Martin's avatar
Marco Martin committed
128

129
130
131
    return -1;
}

132
QList<KPackage::Package> KCMLookandFeel::availablePackages(const QStringList &components)
133
{
134
    QList<KPackage::Package> packages;
135
    QStringList paths;
136
    const QStringList dataPaths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
137

138
    paths.reserve(dataPaths.count());
139
    for (const QString &path : dataPaths) {
140
        QDir dir(path + QStringLiteral("/plasma/look-and-feel"));
141
142
143
144
        paths << dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
    }

    for (const QString &path : paths) {
145
        KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
146
        pkg.setPath(path);
147
        pkg.setFallbackPackage(KPackage::Package());
148
        if (components.isEmpty()) {
149
            packages << pkg;
150
151
152
153
154
155
156
        } else {
            for (const auto &component : components) {
                if (!pkg.filePath(component.toUtf8()).isEmpty()) {
                    packages << pkg;
                    break;
                }
            }
157
158
159
160
161
162
        }
    }

    return packages;
}

163
164
165
166
167
LookAndFeelSettings *KCMLookandFeel::lookAndFeelSettings() const
{
    return m_settings;
}

168
void KCMLookandFeel::loadModel()
Marco Martin's avatar
Marco Martin committed
169
170
171
{
    m_model->clear();

172
173
    const QList<KPackage::Package> pkgs = availablePackages({"defaults", "layouts"});
    for (const KPackage::Package &pkg : pkgs) {
Marco Martin's avatar
Marco Martin committed
174
175
176
        if (!pkg.metadata().isValid()) {
            continue;
        }
177
        QStandardItem *row = new QStandardItem(pkg.metadata().name());
178
179
        row->setData(pkg.metadata().pluginId(), PluginNameRole);
        row->setData(pkg.metadata().description(), DescriptionRole);
180
        row->setData(pkg.filePath("preview"), ScreenshotRole);
181
        row->setData(pkg.filePath("fullscreenpreview"), FullScreenPreviewRole);
Marco Martin's avatar
Marco Martin committed
182
183
184
185
186
187
188
189
190
191

        //What the package provides
        row->setData(!pkg.filePath("splashmainscript").isEmpty(), HasSplashRole);
        row->setData(!pkg.filePath("lockscreenmainscript").isEmpty(), HasLockScreenRole);
        row->setData(!pkg.filePath("runcommandmainscript").isEmpty(), HasRunCommandRole);
        row->setData(!pkg.filePath("logoutmainscript").isEmpty(), HasLogoutRole);

        if (!pkg.filePath("defaults").isEmpty()) {
            KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("defaults"));
            KConfigGroup cg(conf, "kdeglobals");
192
            cg = KConfigGroup(&cg, "General");
Marco Martin's avatar
Marco Martin committed
193
194
195
196
            bool hasColors = !cg.readEntry("ColorScheme", QString()).isEmpty();
            if (!hasColors) {
                hasColors = !pkg.filePath("colors").isEmpty();
            }
197
            row->setData(hasColors, HasColorsRole);
198
            cg = KConfigGroup(&cg, "KDE");
Marco Martin's avatar
Marco Martin committed
199
200
201
202
203
204
205
206
207
208
209
210
            row->setData(!cg.readEntry("widgetStyle", QString()).isEmpty(), HasWidgetStyleRole);
            cg = KConfigGroup(conf, "kdeglobals");
            cg = KConfigGroup(&cg, "Icons");
            row->setData(!cg.readEntry("Theme", QString()).isEmpty(), HasIconsRole);

            cg = KConfigGroup(conf, "kdeglobals");
            cg = KConfigGroup(&cg, "Theme");
            row->setData(!cg.readEntry("name", QString()).isEmpty(), HasPlasmaThemeRole);

            cg = KConfigGroup(conf, "kcminputrc");
            cg = KConfigGroup(&cg, "Mouse");
            row->setData(!cg.readEntry("cursorTheme", QString()).isEmpty(), HasCursorsRole);
211
212
213
214

            cg = KConfigGroup(conf, "kwinrc");
            cg = KConfigGroup(&cg, "WindowSwitcher");
            row->setData(!cg.readEntry("LayoutName", QString()).isEmpty(), HasWindowSwitcherRole);
215
216
217
218

            cg = KConfigGroup(conf, "kwinrc");
            cg = KConfigGroup(&cg, "DesktopSwitcher");
            row->setData(!cg.readEntry("LayoutName", QString()).isEmpty(), HasDesktopSwitcherRole);
Marco Martin's avatar
Marco Martin committed
219
220
221
222
        }

        m_model->appendRow(row);
    }
223
    m_model->sort(0 /*column*/);
224
225
226

    //Model has been cleared so pretend the selected look and fell changed to force view update
    emit m_settings->lookAndFeelPackageChanged();
227
228
229
230
}

void KCMLookandFeel::load()
{
231
    ManagedConfigModule::load();
232

233
234
    m_package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
    m_package.setPath(m_settings->lookAndFeelPackage());
Marco Martin's avatar
Marco Martin committed
235
236
237
238
}

void KCMLookandFeel::save()
{
239
    KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
240
    package.setPath(m_settings->lookAndFeelPackage());
Marco Martin's avatar
Marco Martin committed
241
242
243
244
245

    if (!package.isValid()) {
        return;
    }

246
    ManagedConfigModule::save();
Marco Martin's avatar
Marco Martin committed
247

248
    if (m_resetDefaultLayout) {
249
        QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), QStringLiteral("/PlasmaShell"),
250
251
252
                                                   QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("loadLookAndFeelDefaultLayout"));

        QList<QVariant> args;
253
        args << m_settings->lookAndFeelPackage();
254
255
256
257
258
        message.setArguments(args);

        QDBusConnection::sessionBus().call(message, QDBus::NoBlock);
    }

Marco Martin's avatar
Marco Martin committed
259
260
261
262
263
    if (!package.filePath("defaults").isEmpty()) {
        KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("defaults"));
        KConfigGroup cg(conf, "kdeglobals");
        cg = KConfigGroup(&cg, "KDE");
        if (m_applyWidgetStyle) {
264
265
266
267
268
269
            QString widgetStyle = cg.readEntry("widgetStyle", QString());
            // Some global themes refer to breeze's widgetStyle with a lowercase b.
            if (widgetStyle == QStringLiteral("breeze")) {
                widgetStyle = QStringLiteral("Breeze");
            }
            setWidgetStyle(widgetStyle);
Marco Martin's avatar
Marco Martin committed
270
271
272
273
        }

        if (m_applyColors) {
            QString colorsFile = package.filePath("colors");
Marco Martin's avatar
Marco Martin committed
274
275
            KConfigGroup cg(conf, "kdeglobals");
            cg = KConfigGroup(&cg, "General");
Marco Martin's avatar
Marco Martin committed
276
            QString colorScheme = cg.readEntry("ColorScheme", QString());
Marco Martin's avatar
Marco Martin committed
277

Marco Martin's avatar
Marco Martin committed
278
279
280
281
282
283
284
            if (!colorsFile.isEmpty()) {
                if (!colorScheme.isEmpty()) {
                    setColors(colorScheme, colorsFile);
                } else {
                    setColors(package.metadata().name(), colorsFile);
                }
            } else if (!colorScheme.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
285
                colorScheme.remove(QLatin1Char('\'')); // So Foo's does not become FooS
286
                QRegExp fixer(QStringLiteral("[\\W,.-]+(.?)"));
Marco Martin's avatar
Marco Martin committed
287
                int offset;
Marco Martin's avatar
Marco Martin committed
288
                while ((offset = fixer.indexIn(colorScheme)) >= 0) {
Marco Martin's avatar
Marco Martin committed
289
                    colorScheme.replace(offset, fixer.matchedLength(), fixer.cap(1).toUpper());
Marco Martin's avatar
Marco Martin committed
290
                }
Marco Martin's avatar
Marco Martin committed
291
                colorScheme.replace(0, 1, colorScheme.at(0).toUpper());
292
293
294
295
296
297
298
299
300
301
302

                //NOTE: why this loop trough all the scheme files?
                //the scheme theme name is an heuristic, there is no plugin metadata whatsoever.
                //is based on the file name stripped from weird characters or the
                //eventual id- prefix store.kde.org puts, so we can just find a
                //theme that ends as the specified name
                bool schemeFound = false;
                const QStringList schemeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory);
                for (const QString &dir : schemeDirs) {
                    const QStringList fileNames = QDir(dir).entryList(QStringList()<<QStringLiteral("*.colors"));
                    for (const QString &file : fileNames) {
303
304
                        if (file.endsWith(colorScheme + QStringLiteral(".colors"))) {
                            setColors(colorScheme, dir + QLatin1Char('/') + file);
305
306
307
308
309
310
311
312
                            schemeFound = true;
                            break;
                        }
                    }
                    if (schemeFound) {
                        break;
                    }
                }
Marco Martin's avatar
Marco Martin committed
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
            }
        }

        if (m_applyIcons) {
            cg = KConfigGroup(conf, "kdeglobals");
            cg = KConfigGroup(&cg, "Icons");
            setIcons(cg.readEntry("Theme", QString()));
        }

        if (m_applyPlasmaTheme) {
            cg = KConfigGroup(conf, "plasmarc");
            cg = KConfigGroup(&cg, "Theme");
            setPlasmaTheme(cg.readEntry("name", QString()));
        }

        if (m_applyCursors) {
            cg = KConfigGroup(conf, "kcminputrc");
            cg = KConfigGroup(&cg, "Mouse");
            setCursorTheme(cg.readEntry("cursorTheme", QString()));
        }
333
334
335
336
337
338

        if (m_applyWindowSwitcher) {
            cg = KConfigGroup(conf, "kwinrc");
            cg = KConfigGroup(&cg, "WindowSwitcher");
            setWindowSwitcher(cg.readEntry("LayoutName", QString()));
        }
339
340
341
342
343
344

        if (m_applyDesktopSwitcher) {
            cg = KConfigGroup(conf, "kwinrc");
            cg = KConfigGroup(&cg, "DesktopSwitcher");
            setDesktopSwitcher(cg.readEntry("LayoutName", QString()));
        }
345
346
347
348

        if (m_applyWindowDecoration) {
            cg = KConfigGroup(conf, "kwinrc");
            cg = KConfigGroup(&cg, "org.kde.kdecoration2");
349
#ifdef HAVE_BREEZE_DECO
350
            setWindowDecoration(cg.readEntry("library", QStringLiteral(BREEZE_KDECORATION_PLUGIN_ID)), cg.readEntry("theme", QStringLiteral("Breeze")));
351
#else
352
            setWindowDecoration(cg.readEntry("library", QStringLiteral("org.kde.kwin.aurorae")), cg.readEntry("theme", QStringLiteral("kwin4_decoration_qml_plastik")));
353
354
355
356
357
358
359
360
361
362
#endif
        }

        // Reload KWin if something changed, but only once.
        if (m_applyWindowSwitcher || m_applyDesktopSwitcher || m_applyWindowDecoration) {
            QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"),
                                                    QStringLiteral("org.kde.KWin"),
                                                    QStringLiteral("reloadConfig"));
            QDBusConnection::sessionBus().send(message);
        }
363
364
365
366
367
368
369
370
371

        //autostart
        if (m_resetDefaultLayout) {
            //remove all the old package to autostart
            {
                KSharedConfigPtr oldConf = KSharedConfig::openConfig(m_package.filePath("defaults"));
                cg = KConfigGroup(oldConf, QStringLiteral("Autostart"));
                const QStringList autostartServices = cg.readEntry("Services", QStringList());

372
373
374
375
376
377
378
379
                if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) {
                    for (const QString &serviceFile : autostartServices) {
                        KService service(serviceFile + QStringLiteral(".desktop"));
                        KAutostart as(serviceFile);
                        as.setAutostarts(false);
                        //FIXME: quite ugly way to stop things, and what about non KDE things?
                        QProcess::startDetached(QStringLiteral("kquitapp5"), {QStringLiteral("--service"), service.property(QStringLiteral("X-DBUS-ServiceName")).toString()});
                    }
380
381
382
383
384
385
386
387
388
389
390
391
                }
            }
            //Set all the stuff in the new lnf to autostart
            {
                cg = KConfigGroup(conf, QStringLiteral("Autostart"));
                const QStringList autostartServices = cg.readEntry("Services", QStringList());

                for (const QString &serviceFile : autostartServices) {
                    KService service(serviceFile + QStringLiteral(".desktop"));
                    KAutostart as(serviceFile);
                    as.setCommand(service.exec());
                    as.setAutostarts(true);
392
393
394
                    if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) {
                        KRun::runApplication(service, {}, nullptr);
                    }
395
396
397
                }
            }
        }
Marco Martin's avatar
Marco Martin committed
398
399
    }

400
    //TODO: option to enable/disable apply? they don't seem required by UI design
401
402
403
404
    const auto *item = m_model->item(pluginIndex(m_settings->lookAndFeelPackage()));
    if (item->data(HasSplashRole).toBool()) {
        setSplashScreen(m_settings->lookAndFeelPackage());
    }
405
    setLockScreen(m_settings->lookAndFeelPackage());
406

Marco Martin's avatar
Marco Martin committed
407
    m_configGroup.sync();
408
    m_package.setPath(m_settings->lookAndFeelPackage());
Marco Martin's avatar
Marco Martin committed
409
    runRdb(KRdbExportQtColors | KRdbExportGtkTheme | KRdbExportColors | KRdbExportQtSettings | KRdbExportXftSettings);
Marco Martin's avatar
Marco Martin committed
410
411
412
413
414
415
416
417
}

void KCMLookandFeel::setWidgetStyle(const QString &style)
{
    if (style.isEmpty()) {
        return;
    }

418
419
420
421
422
423
424
425
426
    // Some global themes use styles that may not be installed.
    // Test if style can be installed before updating the config.
    QScopedPointer<QStyle> newStyle(QStyleFactory::create(style));
    if (newStyle) {
        m_configGroup.writeEntry("widgetStyle", style);
        m_configGroup.sync();
        //FIXME: changing style on the fly breaks QQuickWidgets
        KGlobalSettings::self()->emitChange(KGlobalSettings::StyleChanged);
    }
Marco Martin's avatar
Marco Martin committed
427
428
429
430
}

void KCMLookandFeel::setColors(const QString &scheme, const QString &colorFile)
{
Marco Martin's avatar
Marco Martin committed
431
    if (scheme.isEmpty() && colorFile.isEmpty()) {
Marco Martin's avatar
Marco Martin committed
432
433
434
        return;
    }

435
    KSharedConfigPtr conf = KSharedConfig::openConfig(colorFile, KSharedConfig::CascadeConfig);
Marco Martin's avatar
Marco Martin committed
436
    foreach (const QString &grp, conf->groupList()) {
437
438
439
        KConfigGroup cg(conf, grp);
        KConfigGroup cg2(&m_config, grp);
        cg.copyTo(&cg2);
440
    }
441

442
443
444
    KConfigGroup configGroup(&m_config, "General");
    configGroup.writeEntry("ColorScheme", scheme);

445
    configGroup.sync();
446
    KGlobalSettings::self()->emitChange(KGlobalSettings::PaletteChanged);
Marco Martin's avatar
Marco Martin committed
447
448
449
450
451
452
453
454
455
456
}

void KCMLookandFeel::setIcons(const QString &theme)
{
    if (theme.isEmpty()) {
        return;
    }

    KConfigGroup cg(&m_config, "Icons");
    cg.writeEntry("Theme", theme);
Marco Martin's avatar
Marco Martin committed
457
458
459
460
461
    cg.sync();

    for (int i=0; i < KIconLoader::LastGroup; i++) {
        KIconLoader::emitChange(KIconLoader::Group(i));
    }
Marco Martin's avatar
Marco Martin committed
462
463
464
465
466
467
468
469
}

void KCMLookandFeel::setPlasmaTheme(const QString &theme)
{
    if (theme.isEmpty()) {
        return;
    }

470
    KConfig config(QStringLiteral("plasmarc"));
Marco Martin's avatar
Marco Martin committed
471
472
473
474
475
476
477
478
479
480
481
482
    KConfigGroup cg(&config, "Theme");
    cg.writeEntry("name", theme);
    cg.sync();
}

void KCMLookandFeel::setCursorTheme(const QString themeName)
{
    //TODO: use pieces of cursor kcm when moved to plasma-desktop
    if (themeName.isEmpty()) {
        return;
    }

483
    KConfig config(QStringLiteral("kcminputrc"));
Marco Martin's avatar
Marco Martin committed
484
485
486
487
    KConfigGroup cg(&config, "Mouse");
    cg.writeEntry("cursorTheme", themeName);
    cg.sync();

488
#ifdef HAVE_XCURSOR
Marco Martin's avatar
Marco Martin committed
489
490
491
    // Require the Xcursor version that shipped with X11R6.9 or greater, since
    // in previous versions the Xfixes code wasn't enabled due to a bug in the
    // build system (freedesktop bug #975).
492
#if defined(HAVE_XFIXES) && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105
493
494
    const int cursorSize = cg.readEntry("cursorSize", 0);

Marco Martin's avatar
Marco Martin committed
495
    QDir themeDir = cursorThemeDir(themeName, 0);
Marco Martin's avatar
debug--    
Marco Martin committed
496

Marco Martin's avatar
Marco Martin committed
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
    if (!themeDir.exists()) {
        return;
    }

    XCursorTheme theme(themeDir);

    if (!CursorTheme::haveXfixes()) {
        return;
    }

    // Set up the proper launch environment for newly started apps
    OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"),
                                       QStringLiteral("/KLauncher"),
                                       QDBusConnection::sessionBus());
    klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), themeName);

    // Update the Xcursor X resources
    runRdb(0);

    // Notify all applications that the cursor theme has changed
    KGlobalSettings::self()->emitChange(KGlobalSettings::CursorChanged);

    // Reload the standard cursors
    QStringList names;

    // Qt cursors
523
524
525
526
527
    names << QStringLiteral("left_ptr")       << QStringLiteral("up_arrow")      << QStringLiteral("cross")      << QStringLiteral("wait")
          << QStringLiteral("left_ptr_watch") << QStringLiteral("ibeam")         << QStringLiteral("size_ver")   << QStringLiteral("size_hor")
          << QStringLiteral("size_bdiag")     << QStringLiteral("size_fdiag")    << QStringLiteral("size_all")   << QStringLiteral("split_v")
          << QStringLiteral("split_h")        << QStringLiteral("pointing_hand") << QStringLiteral("openhand")
          << QStringLiteral("closedhand")     << QStringLiteral("forbidden")     << QStringLiteral("whats_this") << QStringLiteral("copy") << QStringLiteral("move") << QStringLiteral("link");
Marco Martin's avatar
Marco Martin committed
528
529

    // X core cursors
530
531
532
533
534
535
536
537
    names << QStringLiteral("X_cursor")            << QStringLiteral("right_ptr")           << QStringLiteral("hand1")
          << QStringLiteral("hand2")               << QStringLiteral("watch")               << QStringLiteral("xterm")
          << QStringLiteral("crosshair")           << QStringLiteral("left_ptr_watch")      << QStringLiteral("center_ptr")
          << QStringLiteral("sb_h_double_arrow")   << QStringLiteral("sb_v_double_arrow")   << QStringLiteral("fleur")
          << QStringLiteral("top_left_corner")     << QStringLiteral("top_side")            << QStringLiteral("top_right_corner")
          << QStringLiteral("right_side")          << QStringLiteral("bottom_right_corner") << QStringLiteral("bottom_side")
          << QStringLiteral("bottom_left_corner")  << QStringLiteral("left_side")           << QStringLiteral("question_arrow")
          << QStringLiteral("pirate");
Marco Martin's avatar
Marco Martin committed
538
539

    foreach (const QString &name, names) {
540
        XFixesChangeCursorByName(QX11Info::display(), theme.loadCursor(name, cursorSize), QFile::encodeName(name));
Marco Martin's avatar
Marco Martin committed
541
542
543
544
    }

#else
    KMessageBox::information(this,
545
                                 i18n("You have to restart the Plasma session for these changes to take effect."),
Marco Martin's avatar
Marco Martin committed
546
547
                                 i18n("Cursor Settings Changed"), "CursorSettingsChanged");
#endif
548
#endif
Marco Martin's avatar
Marco Martin committed
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
}

QDir KCMLookandFeel::cursorThemeDir(const QString &theme, const int depth)
{
    // Prevent infinite recursion
    if (depth > 10) {
        return QDir();
    }

    // Search each icon theme directory for 'theme'
    foreach (const QString &baseDir, cursorSearchPaths()) {
        QDir dir(baseDir);
        if (!dir.exists() || !dir.cd(theme)) {
            continue;
        }

        // If there's a cursors subdir, we'll assume this is a cursor theme
566
        if (dir.exists(QStringLiteral("cursors"))) {
Marco Martin's avatar
Marco Martin committed
567
568
569
570
            return dir;
        }

        // If the theme doesn't have an index.theme file, it can't inherit any themes.
571
        if (!dir.exists(QStringLiteral("index.theme"))) {
Marco Martin's avatar
Marco Martin committed
572
573
574
575
            continue;
        }

        // Open the index.theme file, so we can get the list of inherited themes
Laurent Montel's avatar
Laurent Montel committed
576
        KConfig config(dir.path() + QStringLiteral("/index.theme"), KConfig::NoGlobals);
Marco Martin's avatar
Marco Martin committed
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
        KConfigGroup cg(&config, "Icon Theme");

        // Recurse through the list of inherited themes, to check if one of them
        // is a cursor theme.
        QStringList inherits = cg.readEntry("Inherits", QStringList());
        foreach (const QString &inherit, inherits) {
            // Avoid possible DoS
            if (inherit == theme) {
                continue;
            }

            if (cursorThemeDir(inherit, depth + 1).exists()) {
                return dir;
            }
        }
    }

    return QDir();
}

const QStringList KCMLookandFeel::cursorSearchPaths()
{
    if (!m_cursorSearchPaths.isEmpty())
        return m_cursorSearchPaths;

602
#ifdef HAVE_XCURSOR
Marco Martin's avatar
Marco Martin committed
603
604
605
606
607
608
609
610
611
612
613
614
615
616
#if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1
    // These are the default paths Xcursor will scan for cursor themes
    QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons");

    // If XCURSOR_PATH is set, use that instead of the default path
    char *xcursorPath = std::getenv("XCURSOR_PATH");
    if (xcursorPath)
        path = xcursorPath;
#else
    // Get the search path from Xcursor
    QString path = XcursorLibraryPath();
#endif

    // Separate the paths
617
    m_cursorSearchPaths = path.split(QLatin1Char(':'), QString::SkipEmptyParts);
Marco Martin's avatar
Marco Martin committed
618
619
620
621
622
623
624
625
626
627
628
629
630

    // Remove duplicates
    QMutableStringListIterator i(m_cursorSearchPaths);
    while (i.hasNext())
    {
        const QString path = i.next();
        QMutableStringListIterator j(i);
        while (j.hasNext())
            if (j.next() == path)
                j.remove();
    }

    // Expand all occurrences of ~/ to the home dir
631
    m_cursorSearchPaths.replaceInStrings(QRegExp(QStringLiteral("^~\\/")), QDir::home().path() + QLatin1Char('/'));
Marco Martin's avatar
Marco Martin committed
632
    return m_cursorSearchPaths;
633
634
635
#else
    return QStringList();
#endif
Marco Martin's avatar
Marco Martin committed
636
637
}

638
639
640
641
642
643
void KCMLookandFeel::setSplashScreen(const QString &theme)
{
    if (theme.isEmpty()) {
        return;
    }

644
    KConfig config(QStringLiteral("ksplashrc"));
645
646
647
648
649
650
651
652
653
654
655
656
657
    KConfigGroup cg(&config, "KSplash");
    cg.writeEntry("Theme", theme);
    //TODO: a way to set none as spash in the l&f
    cg.writeEntry("Engine", "KSplashQML");
    cg.sync();
}

void KCMLookandFeel::setLockScreen(const QString &theme)
{
    if (theme.isEmpty()) {
        return;
    }

658
    KConfig config(QStringLiteral("kscreenlockerrc"));
659
660
661
662
663
    KConfigGroup cg(&config, "Greeter");
    cg.writeEntry("Theme", theme);
    cg.sync();
}

664
665
666
667
668
void KCMLookandFeel::setWindowSwitcher(const QString &theme)
{
    if (theme.isEmpty()) {
        return;
    }
669

670
    KConfig config(QStringLiteral("kwinrc"));
671
672
673
674
    KConfigGroup cg(&config, "TabBox");
    cg.writeEntry("LayoutName", theme);
    cg.sync();
}
675

676
677
678
679
680
681
void KCMLookandFeel::setDesktopSwitcher(const QString &theme)
{
    if (theme.isEmpty()) {
        return;
    }

682
    KConfig config(QStringLiteral("kwinrc"));
683
684
685
686
    KConfigGroup cg(&config, "TabBox");
    cg.writeEntry("DesktopLayout", theme);
    cg.writeEntry("DesktopListLayout", theme);
    cg.sync();
687
688
689
690
691
692
693
694
695
696
697
698
}

void KCMLookandFeel::setWindowDecoration(const QString &library, const QString &theme)
{
    if (library.isEmpty()) {
        return;
    }

    KConfig config(QStringLiteral("kwinrc"));
    KConfigGroup cg(&config, "org.kde.kdecoration2");
    cg.writeEntry("library", library);
    cg.writeEntry("theme", theme);
699

700
    cg.sync();
701
702
703
704
705
706
707
    // Reload KWin.
    QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"),
                                                      QStringLiteral("org.kde.KWin"),
                                                      QStringLiteral("reloadConfig"));
    QDBusConnection::sessionBus().send(message);
}

708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
void KCMLookandFeel::setResetDefaultLayout(bool reset)
{
    if (m_resetDefaultLayout == reset) {
        return;
    }
    m_resetDefaultLayout = reset;
    emit resetDefaultLayoutChanged();
    if (reset) {
        setNeedsSave(true);
    }
}

bool KCMLookandFeel::resetDefaultLayout() const
{
    return m_resetDefaultLayout;
}