File indexing completed on 2024-04-28 17:04:38

0001 /*****************************************************************************
0002  *   Copyright 2007 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
0003  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
0004  *                                                                           *
0005  *   This program is free software; you can redistribute it and/or modify    *
0006  *   it under the terms of the GNU Lesser General Public License as          *
0007  *   published by the Free Software Foundation; either version 2.1 of the    *
0008  *   License, or (at your option) version 3, or any later version accepted   *
0009  *   by the membership of KDE e.V. (or its successor approved by the         *
0010  *   membership of KDE e.V.), which shall act as a proxy defined in          *
0011  *   Section 6 of version 3 of the license.                                  *
0012  *                                                                           *
0013  *   This program is distributed in the hope that it will be useful,         *
0014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
0015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
0016  *   Lesser General Public License for more details.                         *
0017  *                                                                           *
0018  *   You should have received a copy of the GNU Lesser General Public        *
0019  *   License along with this library. If not,                                *
0020  *   see <http://www.gnu.org/licenses/>.                                     *
0021  *****************************************************************************/
0022 
0023 /*
0024   based on the window decoration "Plastik":
0025   Copyright (C) 2003-2005 Sandro Giessl <sandro@giessl.com>
0026 
0027   based on the window decoration "Web":
0028   Copyright (C) 2001 Rik Hemsley (rikkus) <rik@kde.org>
0029  */
0030 
0031 #include <QBitmap>
0032 #include <QPainter>
0033 #include <QImage>
0034 #include <QPixmap>
0035 #include <QStyleFactory>
0036 #include <QStyle>
0037 #include <QDir>
0038 #include <QFile>
0039 #include <QTextStream>
0040 #include <QApplication>
0041 #include <QDBusConnection>
0042 #include <QDBusMessage>
0043 #include "qtcurvehandler.h"
0044 #include "qtcurveclient.h"
0045 #include "qtcurvebutton.h"
0046 #include "qtcurvedbus.h"
0047 #include <KConfig>
0048 #include <KConfigGroup>
0049 #include <KColorUtils>
0050 #include <KColorScheme>
0051 #include <KGlobalSettings>
0052 #include <KSaveFile>
0053 #include <KWindowSystem>
0054 #include <unistd.h>
0055 #include <sys/types.h>
0056 #include <kde_file.h>
0057 #include <common/common.h>
0058 
0059 #include <QX11Info>
0060 #include <qtcurve-utils/x11base.h>
0061 #include <qtcurve-utils/dirs.h>
0062 
0063 static time_t getTimeStamp(const QString &item)
0064 {
0065     KDE_struct_stat info;
0066 
0067     return !item.isEmpty() && 0==KDE_lstat(QFile::encodeName(item), &info) ? info.st_mtime : 0;
0068 }
0069 
0070 namespace QtCurve {
0071 
0072 static const QString&
0073 xdgConfigFolder()
0074 {
0075     static QString xdgDir = QString::fromLocal8Bit(getXDGConfigHome());
0076     return xdgDir;
0077 }
0078 
0079 namespace KWin {
0080 
0081 // make the handler accessible to other classes...
0082 static QtCurveHandler *handler = 0;
0083 QtCurveHandler*
0084 Handler()
0085 {
0086     return handler;
0087 }
0088 
0089 QtCurveHandler::QtCurveHandler() :
0090     m_lastMenuXid(0),
0091     m_lastStatusXid(0),
0092     m_style(nullptr),
0093     m_dBus(nullptr)
0094 {
0095     qtcX11InitXlib(QX11Info::display());
0096     handler = this;
0097     setStyle();
0098     reset(0);
0099 
0100     m_dBus = new QtCurveDBus(this);
0101     QDBusConnection::sessionBus().registerObject("/QtCurve", this);
0102 }
0103 
0104 QtCurveHandler::~QtCurveHandler()
0105 {
0106     handler = 0;
0107     delete m_style;
0108 }
0109 
0110 void QtCurveHandler::setStyle()
0111 {
0112     // Need to use our own style instance, as want to update this when
0113     // settings change...
0114     if (!m_style) {
0115         KConfig kglobals("kdeglobals", KConfig::CascadeConfig);
0116         KConfigGroup general(&kglobals, "General");
0117         QString styleName = general.readEntry("widgetStyle", QString()).toLower();
0118 
0119         m_style = QStyleFactory::create(styleName.isEmpty() ||
0120                                          (styleName != "qtcurve"
0121 #ifdef QTC_QT5_STYLE_SUPPORT
0122                                           && !styleName.startsWith(THEME_PREFIX)
0123 #endif
0124                                              ) ? QString("QtCurve") : styleName);
0125         // Looks wrong with style support
0126         m_timeStamp = getTimeStamp(xdgConfigFolder() + "/qtcurve/stylerc");
0127     }
0128 }
0129 
0130 bool QtCurveHandler::reset(unsigned long changed)
0131 {
0132     bool styleChanged = false;
0133     if (std::abs(m_timeStamp - getTimeStamp(xdgConfigFolder() +
0134                                             "/qtcurve/stylerc")) > 2) {
0135         delete m_style;
0136         m_style = 0L;
0137         setStyle();
0138         styleChanged = true;
0139     }
0140 
0141     // we assume the active font to be the same as the inactive font since the
0142     // control center doesn't offer different settings anyways.
0143     m_titleFont = KDecoration::options()->font(true, false); // not small
0144     m_titleFontTool = KDecoration::options()->font(true, true); // small
0145 
0146     m_hoverCols[0]=KColorScheme(QPalette::Inactive).decoration(KColorScheme::HoverColor).color();
0147     m_hoverCols[1]=KColorScheme(QPalette::Active).decoration(KColorScheme::HoverColor).color();
0148 
0149     // read in the configuration
0150     bool configChanged = readConfig(changed & SettingCompositing);
0151 
0152     setBorderSize();
0153 
0154     for (int t = 0;t < 2;++t) {
0155         for (int i = 0;i < NumButtonIcons;i++) {
0156             m_bitmaps[t][i] = QPixmap();
0157         }
0158     }
0159 
0160     // Do we need to "hit the wooden hammer" ?
0161     bool needHardReset = true;
0162     // TODO: besides the Color and Font settings I can maybe handle more changes
0163     //       without a hard reset. I will do this later...
0164     if (!styleChanged && (changed & ~(SettingColors | SettingFont | SettingButtons)) == 0)
0165        needHardReset = false;
0166 
0167     if (needHardReset || configChanged) {
0168         return true;
0169     } else {
0170         resetDecorations(changed);
0171         return false;
0172     }
0173 }
0174 
0175 void QtCurveHandler::setBorderSize()
0176 {
0177     switch (m_config.borderSize()) {
0178     case QtCurveConfig::BORDER_NONE:
0179     case QtCurveConfig::BORDER_NO_SIDES:
0180         m_borderSize = 1;
0181         break;
0182     case QtCurveConfig::BORDER_TINY:
0183         m_borderSize = 2;
0184         break;
0185     case QtCurveConfig::BORDER_LARGE:
0186         m_borderSize = 8;
0187         break;
0188     case QtCurveConfig::BORDER_VERY_LARGE:
0189         m_borderSize = 12;
0190         break;
0191     case QtCurveConfig::BORDER_HUGE:
0192         m_borderSize = 18;
0193         break;
0194     case QtCurveConfig::BORDER_VERY_HUGE:
0195         m_borderSize = 27;
0196         break;
0197     case QtCurveConfig::BORDER_OVERSIZED:
0198         m_borderSize = 40;
0199         break;
0200     case QtCurveConfig::BORDER_NORMAL:
0201     default:
0202         m_borderSize = 4;
0203     }
0204 
0205     if (!outerBorder() && (m_borderSize == 1 || m_borderSize > 4)) {
0206         m_borderSize--;
0207     } else if (outerBorder() && innerBorder() &&
0208                m_config.borderSize() <= QtCurveConfig::BORDER_NORMAL) {
0209         m_borderSize += 2;
0210     }
0211 }
0212 
0213 KDecoration*
0214 QtCurveHandler::createDecoration(KDecorationBridge *bridge)
0215 {
0216     return (new QtCurveClient(bridge, this))->decoration();
0217 }
0218 
0219 bool QtCurveHandler::supports(Ability ability) const
0220 {
0221     switch (ability) {
0222     // announce
0223     case AbilityAnnounceButtons:
0224     case AbilityAnnounceColors:
0225     // buttons
0226     case AbilityButtonMenu:
0227     case AbilityButtonOnAllDesktops:
0228     case AbilityButtonSpacer:
0229     case AbilityButtonHelp:
0230     case AbilityButtonMinimize:
0231     case AbilityButtonMaximize:
0232     case AbilityButtonClose:
0233     case AbilityButtonAboveOthers:
0234     case AbilityButtonBelowOthers:
0235     case AbilityButtonShade:
0236     // TODO
0237     // case AbilityButtonResize:
0238 #if KDE_IS_VERSION(4, 9, 85)
0239     case AbilityButtonApplicationMenu:
0240 #endif
0241     // colors
0242     case AbilityColorTitleBack:
0243     case AbilityColorTitleFore:
0244     case AbilityColorFrame:
0245         return true;
0246     case AbilityUsesAlphaChannel:
0247         return true; // !Handler()->outerBorder(); ???
0248     case AbilityProvidesShadow:
0249         return customShadows();
0250     case AbilityUsesBlurBehind:
0251         return opacity(true)<100 || opacity(false)<100 || wStyle()->pixelMetric((QStyle::PixelMetric)QtC_CustomBgnd, 0L, 0L);
0252         // TODO's
0253     default:
0254         return false;
0255     }
0256 }
0257 
0258 bool QtCurveHandler::readConfig(bool compositingToggled)
0259 {
0260     QtCurveConfig      oldConfig=m_config;
0261     KConfig            configFile("kwinqtcurverc");
0262     const KConfigGroup config(&configFile, "General");
0263     QFontMetrics       fm(m_titleFont);  // active font = inactive font
0264     int                oldSize=m_titleHeight,
0265                        oldToolSize=m_titleHeightTool;
0266     bool               changedBorder=false;
0267 
0268     // The title should stretch with bigger font sizes!
0269     m_titleHeight = qMax(16, fm.height() + 4); // 4 px for the shadow etc.
0270     // have an even title/button size so the button icons are fully centered...
0271     if (m_titleHeight%2 == 0)
0272         m_titleHeight++;
0273 
0274     fm = QFontMetrics(m_titleFontTool);  // active font = inactive font
0275     // The title should stretch with bigger font sizes!
0276     m_titleHeightTool = qMax(13, fm.height()); // don't care about the shadow etc.
0277     // have an even title/button size so the button icons are fully centered...
0278     if (m_titleHeightTool%2 == 0)
0279         m_titleHeightTool++;
0280 
0281     m_config.load(&configFile);
0282 
0283     static bool borderHack = false;
0284     if (borderHack) {
0285     m_config.setOuterBorder(KWindowSystem::compositingActive() ?
0286                             QtCurveConfig::SHADE_NONE :
0287                             (m_config.customShadows() ?
0288                              QtCurveConfig::SHADE_SHADOW :
0289                              QtCurveConfig::SHADE_DARK));
0290         changedBorder = true;
0291         borderHack = false;
0292     } else if (compositingToggled && !m_config.outerBorder() &&
0293                (m_config.borderSize() < QtCurveConfig::BORDER_TINY ||
0294                 (wStyle()->pixelMetric((QStyle::PixelMetric)QtC_WindowBorder,
0295                                        0L, 0L) &
0296                  WINDOW_BORDER_COLOR_TITLEBAR_ONLY))) {
0297         QDBusConnection::sessionBus().send(
0298             QDBusMessage::createSignal("/KWin", "org.kde.KWin",
0299                                        "reloadConfig"));
0300         borderHack = true;
0301     }
0302 
0303     m_titleHeight += 2 * titleBarPad();
0304 
0305     QFile in(xdgConfigFolder() + "/qtcurve/" BORDER_SIZE_FILE);
0306     int prevSize(-1), prevToolSize(-1), prevSide(-1), prevBot(-1);
0307 
0308     if (in.open(QIODevice::ReadOnly)) {
0309         QTextStream stream(&in);
0310         prevSize=in.readLine().toInt();
0311         prevToolSize=in.readLine().toInt();
0312         prevBot=in.readLine().toInt();
0313         prevSide=in.readLine().toInt();
0314         in.close();
0315     }
0316 
0317     setBorderSize();
0318 
0319     int borderEdge=borderEdgeSize()*2;
0320     bool borderSizesChanged=prevSize!=(m_titleHeight+borderEdge) || prevToolSize!=(m_titleHeightTool+borderEdge) ||
0321                             prevBot!=borderSize(true) || prevSide!=borderSize(false);
0322     if(borderSizesChanged)
0323     {
0324         KSaveFile sizeFile(xdgConfigFolder() + "/qtcurve/" BORDER_SIZE_FILE);
0325 
0326         if (sizeFile.open())
0327         {
0328             QTextStream stream(&sizeFile);
0329             stream << m_titleHeight+borderEdge << endl
0330                    << m_titleHeightTool+borderEdge << endl
0331                    << borderSize(true) << endl
0332                    << borderSize(false) << endl;
0333             stream.flush();
0334             sizeFile.finalize();
0335             sizeFile.close();
0336         }
0337     }
0338     bool shadowChanged(false);
0339 
0340     if (customShadows()) {
0341         ShadowConfig actShadow(QPalette::Active);
0342         ShadowConfig inactShadow(QPalette::Inactive);
0343 
0344         actShadow.load(&configFile);
0345         inactShadow.load(&configFile);
0346 
0347         shadowChanged = (m_shadowCache.shadowConfigChanged(actShadow) ||
0348                          m_shadowCache.shadowConfigChanged(inactShadow));
0349 
0350         m_shadowCache.setShadowConfig(actShadow);
0351         m_shadowCache.setShadowConfig(inactShadow);
0352 
0353         if(shadowChanged || oldConfig.roundBottom()!=roundBottom())
0354             m_shadowCache.reset();
0355     }
0356 
0357     if(m_dBus && (borderSizesChanged || changedBorder))
0358     {
0359         m_dBus->emitBorderSizes(); // KDE4 apps...
0360         borderSizeChanged(); // Gtk2 apps...
0361     }
0362 
0363     return (changedBorder || oldSize != m_titleHeight ||
0364             oldToolSize != m_titleHeightTool || shadowChanged ||
0365             m_config != oldConfig);
0366 }
0367 
0368 const QBitmap & QtCurveHandler::buttonBitmap(ButtonIcon type, const QSize &size, bool toolWindow)
0369 {
0370     int typeIndex(type),
0371         reduceW(size.width()>14 ? static_cast<int>((2.0*(size.width()/3.5))+0.5) : 6),
0372         reduceH(size.height()>14 ? static_cast<int>((2.0*(size.height()/3.5))+0.5) : 6),
0373         w(size.width() - reduceW),
0374         h(size.height() - reduceH);
0375 
0376     if (m_bitmaps[toolWindow][typeIndex].size()!=QSize(w,h))
0377         m_bitmaps[toolWindow][typeIndex] = IconEngine::icon(type /*icon*/, qMin(w, h), wStyle());
0378     return m_bitmaps[toolWindow][typeIndex];
0379 }
0380 
0381 int QtCurveHandler::borderSize(bool bot) const
0382 {
0383     if(bot)
0384     {
0385         if(QtCurveConfig::BORDER_NO_SIDES==m_config.borderSize())
0386             return m_borderSize+5;
0387         else if(QtCurveConfig::BORDER_TINY==m_config.borderSize() && m_config.roundBottom() && m_config.outerBorder())
0388             return m_borderSize+1;
0389     }
0390     return m_borderSize;
0391 }
0392 
0393 void QtCurveHandler::borderSizeChanged()
0394 {
0395     foreach (QtCurveClient *client, m_clients) {
0396         client->informAppOfBorderSizeChanges();
0397     }
0398 }
0399 
0400 void QtCurveHandler::menuBarSize(unsigned int xid, int size)
0401 {
0402     foreach (QtCurveClient *client, m_clients) {
0403         if (client->windowId() == xid) {
0404             client->menuBarSize(size);
0405             break;
0406         }
0407     }
0408     m_lastMenuXid = xid;
0409 }
0410 
0411 void QtCurveHandler::statusBarState(unsigned int xid, bool state)
0412 {
0413     foreach (QtCurveClient *client, m_clients) {
0414         if (client->windowId() == xid) {
0415             client->statusBarState(state);
0416             break;
0417         }
0418     }
0419     m_lastStatusXid = xid;
0420 }
0421 
0422 void QtCurveHandler::emitToggleMenuBar(int xid)
0423 {
0424     m_dBus->emitMbToggle(xid);
0425 }
0426 
0427 void QtCurveHandler::emitToggleStatusBar(int xid)
0428 {
0429     m_dBus->emitSbToggle(xid);
0430 }
0431 
0432 int
0433 QtCurveHandler::borderEdgeSize() const
0434 {
0435     auto edgePad = m_config.edgePad();
0436     if (!outerBorder()) {
0437         return edgePad + 1;
0438     } else if (m_config.borderSize() <= QtCurveConfig::BORDER_NO_SIDES ||
0439                wStyle()->pixelMetric((QStyle::PixelMetric)QtC_Round,
0440                                      nullptr, nullptr) >= ROUND_FULL) {
0441         return edgePad + 3;
0442     } else if (wStyle()->pixelMetric((QStyle::PixelMetric)QtC_WindowBorder,
0443                                      nullptr, nullptr) &
0444                WINDOW_BORDER_ADD_LIGHT_BORDER) {
0445         return edgePad + 2;
0446     } else {
0447         return edgePad + 1;
0448     }
0449 }
0450 
0451 void QtCurveHandler::removeClient(QtCurveClient *c)
0452 {
0453     if(c->windowId()==m_lastMenuXid)
0454         m_lastMenuXid=0;
0455     if(c->windowId()==m_lastStatusXid)
0456         m_lastStatusXid=0;
0457     m_clients.removeAll(c);
0458 }
0459 
0460 }
0461 }
0462 
0463 KWIN_DECORATION(QtCurve::KWin::QtCurveHandler)
0464 
0465 #include "qtcurvedbus.moc"
0466 #include "qtcurvehandler.moc"