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