1 /*****************************************************************************
2  *   Copyright 2007 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
3  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
4  *                                                                           *
5  *   This program is free software; you can redistribute it and/or modify    *
6  *   it under the terms of the GNU Lesser General Public License as          *
7  *   published by the Free Software Foundation; either version 2.1 of the    *
8  *   License, or (at your option) version 3, or any later version accepted   *
9  *   by the membership of KDE e.V. (or its successor approved by the         *
10  *   membership of KDE e.V.), which shall act as a proxy defined in          *
11  *   Section 6 of version 3 of the license.                                  *
12  *                                                                           *
13  *   This program is distributed in the hope that it will be useful,         *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
16  *   Lesser General Public License for more details.                         *
17  *                                                                           *
18  *   You should have received a copy of the GNU Lesser General Public        *
19  *   License along with this library. If not,                                *
20  *   see <http://www.gnu.org/licenses/>.                                     *
21  *****************************************************************************/
22 
23 /*
24   based on the window decoration "Plastik":
25   Copyright (C) 2003-2005 Sandro Giessl <sandro@giessl.com>
26 
27   based on the window decoration "Web":
28   Copyright (C) 2001 Rik Hemsley (rikkus) <rik@kde.org>
29  */
30 
31 #include <QBitmap>
32 #include <QPainter>
33 #include <QImage>
34 #include <QPixmap>
35 #include <QStyleFactory>
36 #include <QStyle>
37 #include <QDir>
38 #include <QFile>
39 #include <QTextStream>
40 #include <QApplication>
41 #include <QDBusConnection>
42 #include <QDBusMessage>
43 #include "qtcurvehandler.h"
44 #include "qtcurveclient.h"
45 #include "qtcurvebutton.h"
46 #include "qtcurvedbus.h"
47 #include <KConfig>
48 #include <KConfigGroup>
49 #include <KColorUtils>
50 #include <KColorScheme>
51 #include <KGlobalSettings>
52 #include <KSaveFile>
53 #include <KWindowSystem>
54 #include <unistd.h>
55 #include <sys/types.h>
56 #include <kde_file.h>
57 #include <common/common.h>
58 
59 #include <QX11Info>
60 #include <qtcurve-utils/x11base.h>
61 #include <qtcurve-utils/dirs.h>
62 
getTimeStamp(const QString & item)63 static time_t getTimeStamp(const QString &item)
64 {
65     KDE_struct_stat info;
66 
67     return !item.isEmpty() && 0==KDE_lstat(QFile::encodeName(item), &info) ? info.st_mtime : 0;
68 }
69 
70 namespace QtCurve {
71 
72 static const QString&
xdgConfigFolder()73 xdgConfigFolder()
74 {
75     static QString xdgDir = QString::fromLocal8Bit(getXDGConfigHome());
76     return xdgDir;
77 }
78 
79 namespace KWin {
80 
81 // make the handler accessible to other classes...
82 static QtCurveHandler *handler = 0;
83 QtCurveHandler*
Handler()84 Handler()
85 {
86     return handler;
87 }
88 
QtCurveHandler()89 QtCurveHandler::QtCurveHandler() :
90     m_lastMenuXid(0),
91     m_lastStatusXid(0),
92     m_style(nullptr),
93     m_dBus(nullptr)
94 {
95     qtcX11InitXlib(QX11Info::display());
96     handler = this;
97     setStyle();
98     reset(0);
99 
100     m_dBus = new QtCurveDBus(this);
101     QDBusConnection::sessionBus().registerObject("/QtCurve", this);
102 }
103 
~QtCurveHandler()104 QtCurveHandler::~QtCurveHandler()
105 {
106     handler = 0;
107     delete m_style;
108 }
109 
setStyle()110 void QtCurveHandler::setStyle()
111 {
112     // Need to use our own style instance, as want to update this when
113     // settings change...
114     if (!m_style) {
115         KConfig kglobals("kdeglobals", KConfig::CascadeConfig);
116         KConfigGroup general(&kglobals, "General");
117         QString styleName = general.readEntry("widgetStyle", QString()).toLower();
118 
119         m_style = QStyleFactory::create(styleName.isEmpty() ||
120                                          (styleName != "qtcurve"
121 #ifdef QTC_QT5_STYLE_SUPPORT
122                                           && !styleName.startsWith(THEME_PREFIX)
123 #endif
124                                              ) ? QString("QtCurve") : styleName);
125         // Looks wrong with style support
126         m_timeStamp = getTimeStamp(xdgConfigFolder() + "/qtcurve/stylerc");
127     }
128 }
129 
reset(unsigned long changed)130 bool QtCurveHandler::reset(unsigned long changed)
131 {
132     bool styleChanged = false;
133     if (std::abs(m_timeStamp - getTimeStamp(xdgConfigFolder() +
134                                             "/qtcurve/stylerc")) > 2) {
135         delete m_style;
136         m_style = 0L;
137         setStyle();
138         styleChanged = true;
139     }
140 
141     // we assume the active font to be the same as the inactive font since the
142     // control center doesn't offer different settings anyways.
143     m_titleFont = KDecoration::options()->font(true, false); // not small
144     m_titleFontTool = KDecoration::options()->font(true, true); // small
145 
146     m_hoverCols[0]=KColorScheme(QPalette::Inactive).decoration(KColorScheme::HoverColor).color();
147     m_hoverCols[1]=KColorScheme(QPalette::Active).decoration(KColorScheme::HoverColor).color();
148 
149     // read in the configuration
150     bool configChanged = readConfig(changed & SettingCompositing);
151 
152     setBorderSize();
153 
154     for (int t = 0;t < 2;++t) {
155         for (int i = 0;i < NumButtonIcons;i++) {
156             m_bitmaps[t][i] = QPixmap();
157         }
158     }
159 
160     // Do we need to "hit the wooden hammer" ?
161     bool needHardReset = true;
162     // TODO: besides the Color and Font settings I can maybe handle more changes
163     //       without a hard reset. I will do this later...
164     if (!styleChanged && (changed & ~(SettingColors | SettingFont | SettingButtons)) == 0)
165        needHardReset = false;
166 
167     if (needHardReset || configChanged) {
168         return true;
169     } else {
170         resetDecorations(changed);
171         return false;
172     }
173 }
174 
setBorderSize()175 void QtCurveHandler::setBorderSize()
176 {
177     switch (m_config.borderSize()) {
178     case QtCurveConfig::BORDER_NONE:
179     case QtCurveConfig::BORDER_NO_SIDES:
180         m_borderSize = 1;
181         break;
182     case QtCurveConfig::BORDER_TINY:
183         m_borderSize = 2;
184         break;
185     case QtCurveConfig::BORDER_LARGE:
186         m_borderSize = 8;
187         break;
188     case QtCurveConfig::BORDER_VERY_LARGE:
189         m_borderSize = 12;
190         break;
191     case QtCurveConfig::BORDER_HUGE:
192         m_borderSize = 18;
193         break;
194     case QtCurveConfig::BORDER_VERY_HUGE:
195         m_borderSize = 27;
196         break;
197     case QtCurveConfig::BORDER_OVERSIZED:
198         m_borderSize = 40;
199         break;
200     case QtCurveConfig::BORDER_NORMAL:
201     default:
202         m_borderSize = 4;
203     }
204 
205     if (!outerBorder() && (m_borderSize == 1 || m_borderSize > 4)) {
206         m_borderSize--;
207     } else if (outerBorder() && innerBorder() &&
208                m_config.borderSize() <= QtCurveConfig::BORDER_NORMAL) {
209         m_borderSize += 2;
210     }
211 }
212 
213 KDecoration*
createDecoration(KDecorationBridge * bridge)214 QtCurveHandler::createDecoration(KDecorationBridge *bridge)
215 {
216     return (new QtCurveClient(bridge, this))->decoration();
217 }
218 
supports(Ability ability) const219 bool QtCurveHandler::supports(Ability ability) const
220 {
221     switch (ability) {
222     // announce
223     case AbilityAnnounceButtons:
224     case AbilityAnnounceColors:
225     // buttons
226     case AbilityButtonMenu:
227     case AbilityButtonOnAllDesktops:
228     case AbilityButtonSpacer:
229     case AbilityButtonHelp:
230     case AbilityButtonMinimize:
231     case AbilityButtonMaximize:
232     case AbilityButtonClose:
233     case AbilityButtonAboveOthers:
234     case AbilityButtonBelowOthers:
235     case AbilityButtonShade:
236     // TODO
237     // case AbilityButtonResize:
238 #if KDE_IS_VERSION(4, 9, 85)
239     case AbilityButtonApplicationMenu:
240 #endif
241     // colors
242     case AbilityColorTitleBack:
243     case AbilityColorTitleFore:
244     case AbilityColorFrame:
245         return true;
246     case AbilityUsesAlphaChannel:
247         return true; // !Handler()->outerBorder(); ???
248     case AbilityProvidesShadow:
249         return customShadows();
250     case AbilityUsesBlurBehind:
251         return opacity(true)<100 || opacity(false)<100 || wStyle()->pixelMetric((QStyle::PixelMetric)QtC_CustomBgnd, 0L, 0L);
252         // TODO's
253     default:
254         return false;
255     }
256 }
257 
readConfig(bool compositingToggled)258 bool QtCurveHandler::readConfig(bool compositingToggled)
259 {
260     QtCurveConfig      oldConfig=m_config;
261     KConfig            configFile("kwinqtcurverc");
262     const KConfigGroup config(&configFile, "General");
263     QFontMetrics       fm(m_titleFont);  // active font = inactive font
264     int                oldSize=m_titleHeight,
265                        oldToolSize=m_titleHeightTool;
266     bool               changedBorder=false;
267 
268     // The title should stretch with bigger font sizes!
269     m_titleHeight = qMax(16, fm.height() + 4); // 4 px for the shadow etc.
270     // have an even title/button size so the button icons are fully centered...
271     if (m_titleHeight%2 == 0)
272         m_titleHeight++;
273 
274     fm = QFontMetrics(m_titleFontTool);  // active font = inactive font
275     // The title should stretch with bigger font sizes!
276     m_titleHeightTool = qMax(13, fm.height()); // don't care about the shadow etc.
277     // have an even title/button size so the button icons are fully centered...
278     if (m_titleHeightTool%2 == 0)
279         m_titleHeightTool++;
280 
281     m_config.load(&configFile);
282 
283     static bool borderHack = false;
284     if (borderHack) {
285     m_config.setOuterBorder(KWindowSystem::compositingActive() ?
286                             QtCurveConfig::SHADE_NONE :
287                             (m_config.customShadows() ?
288                              QtCurveConfig::SHADE_SHADOW :
289                              QtCurveConfig::SHADE_DARK));
290         changedBorder = true;
291         borderHack = false;
292     } else if (compositingToggled && !m_config.outerBorder() &&
293                (m_config.borderSize() < QtCurveConfig::BORDER_TINY ||
294                 (wStyle()->pixelMetric((QStyle::PixelMetric)QtC_WindowBorder,
295                                        0L, 0L) &
296                  WINDOW_BORDER_COLOR_TITLEBAR_ONLY))) {
297         QDBusConnection::sessionBus().send(
298             QDBusMessage::createSignal("/KWin", "org.kde.KWin",
299                                        "reloadConfig"));
300         borderHack = true;
301     }
302 
303     m_titleHeight += 2 * titleBarPad();
304 
305     QFile in(xdgConfigFolder() + "/qtcurve/" BORDER_SIZE_FILE);
306     int prevSize(-1), prevToolSize(-1), prevSide(-1), prevBot(-1);
307 
308     if (in.open(QIODevice::ReadOnly)) {
309         QTextStream stream(&in);
310         prevSize=in.readLine().toInt();
311         prevToolSize=in.readLine().toInt();
312         prevBot=in.readLine().toInt();
313         prevSide=in.readLine().toInt();
314         in.close();
315     }
316 
317     setBorderSize();
318 
319     int borderEdge=borderEdgeSize()*2;
320     bool borderSizesChanged=prevSize!=(m_titleHeight+borderEdge) || prevToolSize!=(m_titleHeightTool+borderEdge) ||
321                             prevBot!=borderSize(true) || prevSide!=borderSize(false);
322     if(borderSizesChanged)
323     {
324         KSaveFile sizeFile(xdgConfigFolder() + "/qtcurve/" BORDER_SIZE_FILE);
325 
326         if (sizeFile.open())
327         {
328             QTextStream stream(&sizeFile);
329             stream << m_titleHeight+borderEdge << endl
330                    << m_titleHeightTool+borderEdge << endl
331                    << borderSize(true) << endl
332                    << borderSize(false) << endl;
333             stream.flush();
334             sizeFile.finalize();
335             sizeFile.close();
336         }
337     }
338     bool shadowChanged(false);
339 
340     if (customShadows()) {
341         ShadowConfig actShadow(QPalette::Active);
342         ShadowConfig inactShadow(QPalette::Inactive);
343 
344         actShadow.load(&configFile);
345         inactShadow.load(&configFile);
346 
347         shadowChanged = (m_shadowCache.shadowConfigChanged(actShadow) ||
348                          m_shadowCache.shadowConfigChanged(inactShadow));
349 
350         m_shadowCache.setShadowConfig(actShadow);
351         m_shadowCache.setShadowConfig(inactShadow);
352 
353         if(shadowChanged || oldConfig.roundBottom()!=roundBottom())
354             m_shadowCache.reset();
355     }
356 
357     if(m_dBus && (borderSizesChanged || changedBorder))
358     {
359         m_dBus->emitBorderSizes(); // KDE4 apps...
360         borderSizeChanged(); // Gtk2 apps...
361     }
362 
363     return (changedBorder || oldSize != m_titleHeight ||
364             oldToolSize != m_titleHeightTool || shadowChanged ||
365             m_config != oldConfig);
366 }
367 
buttonBitmap(ButtonIcon type,const QSize & size,bool toolWindow)368 const QBitmap & QtCurveHandler::buttonBitmap(ButtonIcon type, const QSize &size, bool toolWindow)
369 {
370     int typeIndex(type),
371         reduceW(size.width()>14 ? static_cast<int>((2.0*(size.width()/3.5))+0.5) : 6),
372         reduceH(size.height()>14 ? static_cast<int>((2.0*(size.height()/3.5))+0.5) : 6),
373         w(size.width() - reduceW),
374         h(size.height() - reduceH);
375 
376     if (m_bitmaps[toolWindow][typeIndex].size()!=QSize(w,h))
377         m_bitmaps[toolWindow][typeIndex] = IconEngine::icon(type /*icon*/, qMin(w, h), wStyle());
378     return m_bitmaps[toolWindow][typeIndex];
379 }
380 
borderSize(bool bot) const381 int QtCurveHandler::borderSize(bool bot) const
382 {
383     if(bot)
384     {
385         if(QtCurveConfig::BORDER_NO_SIDES==m_config.borderSize())
386             return m_borderSize+5;
387         else if(QtCurveConfig::BORDER_TINY==m_config.borderSize() && m_config.roundBottom() && m_config.outerBorder())
388             return m_borderSize+1;
389     }
390     return m_borderSize;
391 }
392 
borderSizeChanged()393 void QtCurveHandler::borderSizeChanged()
394 {
395     foreach (QtCurveClient *client, m_clients) {
396         client->informAppOfBorderSizeChanges();
397     }
398 }
399 
menuBarSize(unsigned int xid,int size)400 void QtCurveHandler::menuBarSize(unsigned int xid, int size)
401 {
402     foreach (QtCurveClient *client, m_clients) {
403         if (client->windowId() == xid) {
404             client->menuBarSize(size);
405             break;
406         }
407     }
408     m_lastMenuXid = xid;
409 }
410 
statusBarState(unsigned int xid,bool state)411 void QtCurveHandler::statusBarState(unsigned int xid, bool state)
412 {
413     foreach (QtCurveClient *client, m_clients) {
414         if (client->windowId() == xid) {
415             client->statusBarState(state);
416             break;
417         }
418     }
419     m_lastStatusXid = xid;
420 }
421 
emitToggleMenuBar(int xid)422 void QtCurveHandler::emitToggleMenuBar(int xid)
423 {
424     m_dBus->emitMbToggle(xid);
425 }
426 
emitToggleStatusBar(int xid)427 void QtCurveHandler::emitToggleStatusBar(int xid)
428 {
429     m_dBus->emitSbToggle(xid);
430 }
431 
432 int
borderEdgeSize() const433 QtCurveHandler::borderEdgeSize() const
434 {
435     auto edgePad = m_config.edgePad();
436     if (!outerBorder()) {
437         return edgePad + 1;
438     } else if (m_config.borderSize() <= QtCurveConfig::BORDER_NO_SIDES ||
439                wStyle()->pixelMetric((QStyle::PixelMetric)QtC_Round,
440                                      nullptr, nullptr) >= ROUND_FULL) {
441         return edgePad + 3;
442     } else if (wStyle()->pixelMetric((QStyle::PixelMetric)QtC_WindowBorder,
443                                      nullptr, nullptr) &
444                WINDOW_BORDER_ADD_LIGHT_BORDER) {
445         return edgePad + 2;
446     } else {
447         return edgePad + 1;
448     }
449 }
450 
removeClient(QtCurveClient * c)451 void QtCurveHandler::removeClient(QtCurveClient *c)
452 {
453     if(c->windowId()==m_lastMenuXid)
454         m_lastMenuXid=0;
455     if(c->windowId()==m_lastStatusXid)
456         m_lastStatusXid=0;
457     m_clients.removeAll(c);
458 }
459 
460 }
461 }
462 
463 KWIN_DECORATION(QtCurve::KWin::QtCurveHandler)
464 
465 #include "qtcurvedbus.moc"
466 #include "qtcurvehandler.moc"
467