File indexing completed on 2024-04-21 16:30:33

0001 // SPDX-License-Identifier: GPL-3.0-or-later
0002 /*
0003   Copyright 2017 - 2023 Martin Koller, kollix@aon.at
0004 
0005   This file is part of liquidshell.
0006 
0007   liquidshell is free software: you can redistribute it and/or modify
0008   it under the terms of the GNU General Public License as published by
0009   the Free Software Foundation, either version 3 of the License, or
0010   (at your option) any later version.
0011 
0012   liquidshell is distributed in the hope that it will be useful,
0013   but WITHOUT ANY WARRANTY; without even the implied warranty of
0014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015   GNU General Public License for more details.
0016 
0017   You should have received a copy of the GNU General Public License
0018   along with liquidshell.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include <DesktopWidget.hxx>
0022 #include <DesktopPanel.hxx>
0023 #include <WeatherApplet.hxx>
0024 #include <DiskUsageApplet.hxx>
0025 #include <PictureFrameApplet.hxx>
0026 #include <ConfigureDesktopDialog.hxx>
0027 #include <OnScreenVolume.hxx>
0028 #include <OnScreenBrightness.hxx>
0029 #include <KWinCompat.hxx>
0030 
0031 #include <QApplication>
0032 #include <QScreen>
0033 #include <QPainter>
0034 #include <QAction>
0035 #include <QIcon>
0036 #include <QMenu>
0037 #include <QCursor>
0038 #include <QDBusConnection>
0039 #include <QDBusMessage>
0040 
0041 #include <KConfig>
0042 #include <KConfigGroup>
0043 #include <KLocalizedString>
0044 #include <KCMultiDialog>
0045 #include <KHelpMenu>
0046 #include <kcmutils_version.h>
0047 
0048 //--------------------------------------------------------------------------------
0049 
0050 int DesktopWidget::appletNum = 0;
0051 DesktopWidget *DesktopWidget::instance = nullptr;
0052 
0053 //--------------------------------------------------------------------------------
0054 
0055 DesktopWidget::DesktopWidget()
0056   : QWidget(nullptr)
0057   // would love to add Qt::WindowDoesNotAcceptFocus, but this leads to global shortcuts
0058   // no longer working when last maximized firefox window is closed on an empty desktop
0059 {
0060   instance = this;
0061 
0062   setAttribute(Qt::WA_AlwaysShowToolTips);
0063   setAutoFillBackground(true);
0064   setFixedSize(QApplication::primaryScreen()->virtualSize());
0065   setWindowIcon(QIcon::fromTheme("liquidshell"));
0066 
0067   KWindowSystem::setType(winId(), NET::Desktop);
0068 
0069   panel = new DesktopPanel(nullptr);
0070   panel->show();
0071   connect(panel, &DesktopPanel::resized, this, &DesktopWidget::placePanel);
0072   placePanel();
0073 
0074   connect(KWinCompat::self(), &KWinCompat::currentDesktopChanged, this, &DesktopWidget::desktopChanged);
0075   connect(KWinCompat::self(), &KWinCompat::numberOfDesktopsChanged, this, &DesktopWidget::loadSettings);
0076 
0077   setContextMenuPolicy(Qt::ActionsContextMenu);
0078   QAction *action = new QAction(QIcon::fromTheme("configure"), i18n("Configure Wallpaper..."), this);
0079   connect(action, &QAction::triggered, this, &DesktopWidget::configureWallpaper);
0080   addAction(action);
0081 
0082   action = new QAction(i18n("Add Applet"), this);
0083   addAction(action);
0084   QMenu *menu = new QMenu;
0085   action->setMenu(menu);
0086   action = menu->addAction(QIcon::fromTheme("weather-clouds"), i18n("Weather"));
0087   connect(action, &QAction::triggered, [this]() { addApplet("Weather"); });
0088 
0089   action = menu->addAction(QIcon::fromTheme("drive-harddisk"), i18n("Disk Usage"));
0090   connect(action, &QAction::triggered, [this]() { addApplet("DiskUsage"); });
0091 
0092   action = menu->addAction(QIcon::fromTheme("image-x-generix"), i18n("Picture Frame"));
0093   connect(action, &QAction::triggered, [this]() { addApplet("PictureFrame"); });
0094 
0095   action = new QAction(QIcon::fromTheme("preferences-desktop-display"), i18n("Configure Display..."), this);
0096   connect(action, &QAction::triggered, this, &DesktopWidget::configureDisplay);
0097   addAction(action);
0098 
0099   action = new QAction(QIcon::fromTheme("system-run"), i18n("Run Command..."), this);
0100   connect(action, &QAction::triggered,
0101           []()
0102           {
0103             QDBusConnection::sessionBus().send(
0104                 QDBusMessage::createMethodCall("org.kde.krunner", "/App",
0105                                                "org.kde.krunner.App", "display"));
0106           });
0107   addAction(action);
0108 
0109   action = new QAction(i18n("Help"), this);
0110   KHelpMenu *helpMenu = new KHelpMenu(this);
0111   helpMenu->action(KHelpMenu::menuHelpContents)->setVisible(false); // no handbook
0112   helpMenu->action(KHelpMenu::menuWhatsThis)->setVisible(false);
0113   action->setMenu(helpMenu->menu());
0114   addAction(action);
0115 
0116   // restore applets
0117   KConfig config;
0118   KConfigGroup group = config.group("DesktopApplets");
0119   QStringList appletNames = group.readEntry("applets", QStringList());
0120   for (const QString &appletName : appletNames)
0121   {
0122     DesktopApplet *applet = nullptr;
0123 
0124     if ( appletName.startsWith("Weather_") )
0125       applet = new WeatherApplet(this, appletName);
0126     else if ( appletName.startsWith("DiskUsage_") )
0127       applet = new DiskUsageApplet(this, appletName);
0128     else if ( appletName.startsWith("PictureFrame_") )
0129       applet = new PictureFrameApplet(this, appletName);
0130 
0131     if ( applet )
0132     {
0133       int num = appletName.mid(appletName.indexOf('_') + 1).toInt();
0134       if ( num > appletNum )
0135         appletNum = num;
0136 
0137       applet->loadConfig();
0138       applets.append(applet);
0139       connect(applet, &DesktopApplet::removeThis, this, &DesktopWidget::removeApplet);
0140     }
0141   }
0142 
0143   loadSettings();
0144 
0145   connect(qApp, &QApplication::primaryScreenChanged, this, &DesktopWidget::placePanel);
0146 
0147   connect(qApp, &QApplication::screenAdded,
0148           [this]() { setFixedSize(QApplication::primaryScreen()->virtualSize()); desktopChanged(); });
0149 
0150   connect(qApp, &QApplication::screenRemoved,
0151           [this]() { setFixedSize(QApplication::primaryScreen()->virtualSize()); desktopChanged(); });
0152 
0153   connect(QApplication::primaryScreen(), &QScreen::virtualGeometryChanged,
0154           [this]() { setFixedSize(QApplication::primaryScreen()->virtualSize()); placePanel(); desktopChanged(); });
0155 
0156   new OnScreenVolume(this);
0157   new OnScreenBrightness(this);
0158 }
0159 
0160 //--------------------------------------------------------------------------------
0161 
0162 void DesktopWidget::loadSettings()
0163 {
0164   // simple check for default files matching current desktop size
0165   QStringList dirNames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
0166                                                    "wallpapers", QStandardPaths::LocateDirectory);
0167 
0168   QStringList defaultFiles;
0169   const QString geometryString = QString("%1x%2").arg(width()).arg(height());
0170   for (const QString &dirName : dirNames)
0171   {
0172     for (const QString &subdir : QDir(dirName).entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable))
0173     {
0174       QDir dir(dirName + '/' + subdir + "/contents/images");
0175       for (const QString &fileName : dir.entryList(QDir::Files | QDir::Readable))
0176       {
0177         if ( fileName.startsWith(geometryString) )
0178           defaultFiles.append(dir.absoluteFilePath(fileName));
0179       }
0180     }
0181   }
0182 
0183   KConfig config;
0184   for (int i = wallpapers.count() + 1; i <= KWinCompat::numberOfDesktops(); i++)
0185   {
0186     KConfigGroup group = config.group(QString("Desktop_%1").arg(i));
0187 
0188     Wallpaper wallpaper;
0189 
0190     wallpaper.color = group.readEntry("color", QColor(Qt::black));
0191     wallpaper.mode = group.readEntry("wallpaperMode", QString());
0192 
0193     int idx = defaultFiles.count() ? ((i - 1) % defaultFiles.count()) : -1;
0194     wallpaper.fileName = group.readEntry("wallpaper", (idx != -1) ? defaultFiles[idx] : QString());
0195 
0196     wallpapers.append(wallpaper);
0197   }
0198 
0199   wallpapers.resize(KWinCompat::numberOfDesktops());  // if we had more before, reduce size
0200   wallpapers.squeeze();
0201 
0202   desktopChanged();
0203 }
0204 
0205 //--------------------------------------------------------------------------------
0206 
0207 void DesktopWidget::configureWallpaper()
0208 {
0209   if ( dialog )  // already open, do nothing
0210     return;
0211 
0212   bool showingDesktop = KWindowSystem::showingDesktop();
0213   KWindowSystem::setShowingDesktop(true);
0214 
0215   int desktopNum = currentDesktop;
0216   Wallpaper origWallpaper = wallpapers[desktopNum];
0217 
0218   dialog = new ConfigureDesktopDialog(nullptr, wallpapers[desktopNum]);
0219 
0220   connect(dialog, &ConfigureDesktopDialog::changed,
0221           [=]() { wallpapers[desktopNum] = dialog->getWallpaper(); desktopChanged(); });
0222 
0223   connect(dialog, &QDialog::finished,
0224           [=](int result)
0225           {
0226             if ( result == QDialog::Rejected )
0227             {
0228               wallpapers[desktopNum] = origWallpaper;
0229               desktopChanged();
0230             }
0231             else  // store in config file
0232             {
0233               const Wallpaper &wallpaper = wallpapers[desktopNum];
0234               KConfig config;
0235               KConfigGroup group = config.group(QString("Desktop_%1").arg(desktopNum + 1));
0236               group.writeEntry("color", wallpaper.color);
0237               group.writeEntry("wallpaper", wallpaper.fileName);
0238               group.writeEntry("wallpaperMode", wallpaper.mode);
0239             }
0240             KWindowSystem::setShowingDesktop(showingDesktop);  // restore
0241             dialog->deleteLater();
0242             dialog = nullptr;
0243           });
0244 
0245   dialog->show(); // not modal
0246 }
0247 
0248 //--------------------------------------------------------------------------------
0249 
0250 void DesktopWidget::configureDisplay()
0251 {
0252   KCMultiDialog *dialog = new KCMultiDialog(this);
0253   dialog->setAttribute(Qt::WA_DeleteOnClose);
0254 
0255   // different KDE versions need different ways ...
0256   KPluginMetaData module("plasma/kcms/systemsettings/kcm_kscreen");
0257   if ( module.isValid() )
0258     dialog->addModule(module);
0259   else
0260     dialog->addModule("kcm_kscreen");
0261 
0262   dialog->adjustSize();
0263   dialog->setWindowTitle(i18n("Configure Display"));
0264   dialog->show();  // not modal
0265 }
0266 
0267 //--------------------------------------------------------------------------------
0268 
0269 void DesktopWidget::placePanel()
0270 {
0271   int panelHeight = qMin(panel->sizeHint().height(), panel->height());
0272   QRect r = QApplication::primaryScreen()->geometry();
0273   QSize vs = QApplication::primaryScreen()->virtualSize();
0274   panel->setGeometry(r.x(), r.y() + r.height() - panelHeight, r.width(), panelHeight);
0275 
0276   // struts are to the combined screen geoms, not the single screen
0277   KWinCompat::setStrut(panel->winId(), 0, 0, 0, panelHeight + vs.height() - r.bottom());
0278 }
0279 
0280 //--------------------------------------------------------------------------------
0281 
0282 QRect DesktopWidget::availableGeometry()
0283 {
0284   QRect geom = QApplication::primaryScreen()->geometry();
0285   geom.setHeight(geom.height() - instance->panel->height());
0286   return geom;
0287 }
0288 
0289 //--------------------------------------------------------------------------------
0290 
0291 QSize DesktopWidget::availableSize()
0292 {
0293   return availableGeometry().size();
0294 }
0295 
0296 //--------------------------------------------------------------------------------
0297 
0298 void DesktopWidget::desktopChanged()
0299 {
0300   // free memory in previous shown desktop
0301   if ( (currentDesktop >= 0) && (currentDesktop < wallpapers.count()) )
0302     wallpapers[currentDesktop].pixmaps.clear();
0303 
0304   currentDesktop = KWinCompat::currentDesktop() - 1;  // num to index
0305 
0306   if ( currentDesktop >= wallpapers.count() )
0307     return;
0308 
0309   Wallpaper &wallpaper = wallpapers[currentDesktop];
0310 
0311   if ( wallpaper.color.isValid() )
0312   {
0313     QPalette pal = palette();
0314     pal.setColor(backgroundRole(), wallpaper.color);
0315     setPalette(pal);
0316   }
0317 
0318   if ( !wallpaper.fileName.isEmpty() )
0319   {
0320     QPixmap pixmap(wallpaper.fileName);
0321 
0322     if ( !pixmap.isNull() )
0323     {
0324       for (int i = 0; i < QApplication::screens().count(); i++)
0325       {
0326         QRect r = QApplication::screens().at(i)->geometry();
0327         QPixmap scaledPixmap = pixmap;
0328 
0329         if ( wallpaper.mode == "Scaled" )
0330           scaledPixmap = pixmap.scaled(r.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0331         else if ( wallpaper.mode == "ScaledKeepRatio" )
0332           scaledPixmap = pixmap.scaled(r.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
0333         else if ( wallpaper.mode == "ScaledKeepRatioExpand" )
0334           scaledPixmap = pixmap.scaled(r.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
0335 
0336         wallpaper.pixmaps.append(scaledPixmap);
0337       }
0338     }
0339   }
0340 
0341   update();
0342 
0343   // show applets for new desktop
0344   for (DesktopApplet *applet : applets)
0345   {
0346     if ( !applet->isConfiguring() )
0347       applet->setVisible(applet->isOnDesktop(currentDesktop + 1));
0348   }
0349 }
0350 
0351 //--------------------------------------------------------------------------------
0352 
0353 void DesktopWidget::paintEvent(QPaintEvent *event)
0354 {
0355   Q_UNUSED(event)
0356 
0357   if ( currentDesktop < wallpapers.count() )
0358   {
0359     Wallpaper &wallpaper = wallpapers[currentDesktop];
0360 
0361     QPainter painter(this);
0362 
0363     for (int i = 0; i < QApplication::screens().count(); i++)
0364     {
0365       if ( i < wallpaper.pixmaps.count() )
0366       {
0367         QRect r = QApplication::screens().at(i)->geometry();
0368         painter.setClipRect(r);
0369 
0370         painter.drawPixmap(r.x() + (r.width() - wallpaper.pixmaps[i].width()) / 2,
0371                            r.y() + (r.height() - wallpaper.pixmaps[i].height()) / 2,
0372                            wallpaper.pixmaps[i]);
0373       }
0374     }
0375   }
0376 }
0377 
0378 //--------------------------------------------------------------------------------
0379 
0380 void DesktopWidget::addApplet(const QString &type)
0381 {
0382   DesktopApplet *applet = nullptr;
0383 
0384   if ( type == "Weather" )
0385   {
0386     applet = new WeatherApplet(this, QString("%1_%2").arg(type).arg(++appletNum));
0387   }
0388   else if ( type == "DiskUsage" )
0389   {
0390     applet = new DiskUsageApplet(this, QString("%1_%2").arg(type).arg(++appletNum));
0391   }
0392   else if ( type == "PictureFrame" )
0393   {
0394     applet = new PictureFrameApplet(this, QString("%1_%2").arg(type).arg(++appletNum));
0395   }
0396 
0397   if ( applet )
0398   {
0399     applets.append(applet);
0400     applet->loadConfig();  // defaults + size
0401     applet->move(QCursor::pos());
0402     applet->saveConfig();
0403     applet->show();
0404     connect(applet, &DesktopApplet::removeThis, this, &DesktopWidget::removeApplet);
0405   }
0406 
0407   saveAppletsList();
0408 }
0409 
0410 //--------------------------------------------------------------------------------
0411 
0412 void DesktopWidget::removeApplet(DesktopApplet *applet)
0413 {
0414   applets.removeOne(applet);
0415   applet->deleteLater();
0416   saveAppletsList();
0417 }
0418 
0419 //--------------------------------------------------------------------------------
0420 
0421 void DesktopWidget::saveAppletsList()
0422 {
0423   KConfig config;
0424   KConfigGroup group = config.group("DesktopApplets");
0425 
0426   QStringList appletNames;
0427   for (DesktopApplet *applet : applets)
0428     appletNames.append(applet->getId());
0429 
0430   group.writeEntry("applets", appletNames);
0431 }
0432 
0433 //--------------------------------------------------------------------------------