1 /*
2  * Copyright (C) 2016-2019 Jan Grulich
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  */
19 
20 #ifndef GNOME_HINTS_SETTINGS_H
21 #define GNOME_HINTS_SETTINGS_H
22 
23 #include <QDBusVariant>
24 #include <QFont>
25 #include <QFlags>
26 #include <QObject>
27 #include <QPalette>
28 #include <QVariant>
29 
30 #include <cmath>
31 #include <memory>
32 
33 #undef signals
34 #include <gio/gio.h>
35 #include <gtk-3.0/gtk/gtk.h>
36 #include <gtk-3.0/gtk/gtksettings.h>
37 #define signals Q_SIGNALS
38 
39 #include <qpa/qplatformtheme.h>
40 
41 class GnomeHintsSettings : public QObject
42 {
43     Q_OBJECT
44 public:
45     enum TitlebarButtonsPlacement {
46         LeftPlacement = 0,
47         RightPlacement = 1
48     };
49 
50     enum TitlebarButton {
51         CloseButton = 0x1,
52         MinimizeButton = 0x02,
53         MaximizeButton = 0x04
54     };
55     Q_DECLARE_FLAGS(TitlebarButtons, TitlebarButton);
56 
57     explicit GnomeHintsSettings();
58     virtual ~GnomeHintsSettings();
59 
60     // Borrowed from the KColorUtils code
61     static QColor mix(const QColor &c1, const QColor &c2, qreal bias = 0.5)
62     {
63         auto mixQreal = [](qreal a, qreal b, qreal bias) {
64             return a + (b - a) * bias;
65         };
66 
67         if (bias <= 0.0)
68             return c1;
69         if (bias >= 1.0)
70             return c2;
71         if (std::isnan(bias))
72             return c1;
73 
74         qreal r = mixQreal(c1.redF(),   c2.redF(),   bias);
75         qreal g = mixQreal(c1.greenF(), c2.greenF(), bias);
76         qreal b = mixQreal(c1.blueF(),  c2.blueF(),  bias);
77         qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias);
78 
79         return QColor::fromRgbF(r, g, b, a);
80     }
81 
82     static QColor lighten(const QColor &color, qreal amount = 0.1)
83     {
84         qreal h, s, l, a;
85         color.getHslF(&h, &s, &l, &a);
86 
87         qreal lightness = l + amount;
88         if (lightness > 1)
89             lightness = 1;
90         return QColor::fromHslF(h, s, lightness, a);
91     }
92 
93     static QColor darken(const QColor &color, qreal amount = 0.1)
94     {
95         qreal h, s, l, a;
96         color.getHslF(&h, &s, &l, &a);
97 
98         qreal lightness = l - amount;
99         if (lightness < 0)
100             lightness = 0;
101 
102         return QColor::fromHslF(h, s, lightness, a);
103     }
104 
105     static QColor desaturate(const QColor &color, qreal amount = 0.1)
106     {
107         qreal h, s, l, a;
108         color.getHslF(&h, &s, &l, &a);
109 
110         qreal saturation = s - amount;
111         if (saturation < 0)
112             saturation = 0;
113         return QColor::fromHslF(h, saturation, l, a);
114     }
115 
116     static QColor transparentize(const QColor &color, qreal amount = 0.1)
117     {
118         qreal h, s, l, a;
119         color.getHslF(&h, &s, &l, &a);
120 
121         qreal alpha = a - amount;
122         if (alpha < 0)
123             alpha = 0;
124         return QColor::fromHslF(h, s, l, alpha);
125     }
126 
font(QPlatformTheme::Font type)127     inline QFont * font(QPlatformTheme::Font type) const
128     {
129         if (m_fonts.contains(type)) {
130             return m_fonts[type];
131         } else if (m_fonts.contains(QPlatformTheme::SystemFont)) {
132             return m_fonts[QPlatformTheme::SystemFont];
133         } else {
134             // GTK default font
135             return new QFont(QLatin1String("Sans"), 10);
136         }
137     }
138 
palette()139     inline QPalette * palette() const
140     {
141         QPalette *palette = new QPalette;
142 
143         if (m_gtkThemeDarkVariant) {
144             // Colors defined in GTK adwaita style in _colors.scss
145             QColor base_color = lighten(desaturate(QColor("#241f31"), 1.0), 0.02);
146             QColor text_color = QColor("white");
147             QColor bg_color = darken(desaturate(QColor("#3d3846"), 1.0), 0.04);
148             QColor fg_color = QColor("#eeeeec");
149             QColor selected_bg_color = darken(QColor("#3584e4"), 0.2);
150             QColor selected_fg_color = QColor("white");
151             QColor osd_text_color = QColor("white");
152             QColor osd_bg_color = QColor("black");
153             QColor shadow = transparentize(QColor("black"), 0.9);
154 
155             QColor backdrop_fg_color = mix(fg_color, bg_color);
156             QColor backdrop_base_color = lighten(base_color, 0.01);
157             QColor backdrop_selected_fg_color = mix(text_color, backdrop_base_color, 0.2);
158 
159             // This is the color we use as initial color for the gradient in normal state
160             // Defined in _drawing.scss button(normal)
161             QColor button_base_color = darken(bg_color, 0.01);
162 
163             QColor link_color = lighten(selected_bg_color, 0.2);
164             QColor link_visited_color = lighten(selected_bg_color, 0.1);
165 
166             palette->setColor(QPalette::All,      QPalette::Window,          bg_color);
167             palette->setColor(QPalette::All,      QPalette::WindowText,      fg_color);
168             palette->setColor(QPalette::All,      QPalette::Base,            base_color);
169             palette->setColor(QPalette::All,      QPalette::AlternateBase,   base_color);
170             palette->setColor(QPalette::All,      QPalette::ToolTipBase,     osd_bg_color);
171             palette->setColor(QPalette::All,      QPalette::ToolTipText,     osd_text_color);
172             palette->setColor(QPalette::All,      QPalette::Text,            fg_color);
173             palette->setColor(QPalette::All,      QPalette::Button,          button_base_color);
174             palette->setColor(QPalette::All,      QPalette::ButtonText,      fg_color);
175             palette->setColor(QPalette::All,      QPalette::BrightText,      text_color);
176 
177             palette->setColor(QPalette::All,      QPalette::Light,           lighten(button_base_color));
178             palette->setColor(QPalette::All,      QPalette::Midlight,        mix(lighten(button_base_color), button_base_color));
179             palette->setColor(QPalette::All,      QPalette::Mid,             mix(darken(button_base_color), button_base_color));
180             palette->setColor(QPalette::All,      QPalette::Dark,            darken(button_base_color));
181             palette->setColor(QPalette::All,      QPalette::Shadow,          shadow);
182 
183             palette->setColor(QPalette::All,      QPalette::Highlight,       selected_bg_color);
184             palette->setColor(QPalette::All,      QPalette::HighlightedText, selected_fg_color);
185 
186             palette->setColor(QPalette::All,      QPalette::Link,            link_color);
187             palette->setColor(QPalette::All,      QPalette::LinkVisited,     link_visited_color);
188 
189 
190             QColor insensitive_fg_color = mix(fg_color, bg_color);
191             QColor insensitive_bg_color = mix(bg_color, base_color, 0.4);
192 
193             palette->setColor(QPalette::Disabled, QPalette::Window,          insensitive_bg_color);
194             palette->setColor(QPalette::Disabled, QPalette::WindowText,      insensitive_fg_color);
195             palette->setColor(QPalette::Disabled, QPalette::Base,            base_color);
196             palette->setColor(QPalette::Disabled, QPalette::AlternateBase,   base_color);
197             palette->setColor(QPalette::Disabled, QPalette::Text,            insensitive_fg_color);
198             palette->setColor(QPalette::Disabled, QPalette::Button,          insensitive_bg_color);
199             palette->setColor(QPalette::Disabled, QPalette::ButtonText,      insensitive_fg_color);
200             palette->setColor(QPalette::Disabled, QPalette::BrightText,      text_color);
201 
202             palette->setColor(QPalette::Disabled, QPalette::Light,           lighten(insensitive_bg_color));
203             palette->setColor(QPalette::Disabled, QPalette::Midlight,        mix(lighten(insensitive_bg_color), insensitive_bg_color));
204             palette->setColor(QPalette::Disabled, QPalette::Mid,             mix(darken(insensitive_bg_color), insensitive_bg_color));
205             palette->setColor(QPalette::Disabled, QPalette::Dark,            darken(insensitive_bg_color));
206             palette->setColor(QPalette::Disabled, QPalette::Shadow,          shadow);
207 
208             palette->setColor(QPalette::Disabled, QPalette::Highlight,       selected_bg_color);
209             palette->setColor(QPalette::Disabled, QPalette::HighlightedText, selected_fg_color);
210 
211             palette->setColor(QPalette::Disabled, QPalette::Link,            link_color);
212             palette->setColor(QPalette::Disabled, QPalette::LinkVisited,     link_visited_color);
213 
214 
215             palette->setColor(QPalette::Inactive, QPalette::Window,          bg_color);
216             palette->setColor(QPalette::Inactive, QPalette::WindowText,      backdrop_fg_color);
217             palette->setColor(QPalette::Inactive, QPalette::Base,            backdrop_base_color);
218             palette->setColor(QPalette::Inactive, QPalette::AlternateBase,   backdrop_base_color);
219             palette->setColor(QPalette::Inactive, QPalette::Text,            backdrop_fg_color);
220             palette->setColor(QPalette::Inactive, QPalette::Button,          button_base_color);
221             palette->setColor(QPalette::Inactive, QPalette::ButtonText,      backdrop_fg_color);
222             palette->setColor(QPalette::Inactive, QPalette::BrightText,      text_color);
223 
224             palette->setColor(QPalette::Inactive, QPalette::Light,           lighten(insensitive_bg_color));
225             palette->setColor(QPalette::Inactive, QPalette::Midlight,        mix(lighten(insensitive_bg_color), insensitive_bg_color));
226             palette->setColor(QPalette::Inactive, QPalette::Mid,             mix(darken(insensitive_bg_color), insensitive_bg_color));
227             palette->setColor(QPalette::Inactive, QPalette::Dark,            darken(insensitive_bg_color));
228             palette->setColor(QPalette::Inactive, QPalette::Shadow,          shadow);
229 
230             palette->setColor(QPalette::Inactive, QPalette::Highlight,       selected_bg_color);
231             palette->setColor(QPalette::Inactive, QPalette::HighlightedText, backdrop_selected_fg_color);
232 
233             palette->setColor(QPalette::Inactive, QPalette::Link,            link_color);
234             palette->setColor(QPalette::Inactive, QPalette::LinkVisited,     link_visited_color);
235         } else {
236             // Colors defined in GTK adwaita style in _colors.scss
237             QColor base_color = QColor("white");
238             QColor text_color = QColor("black");
239             QColor bg_color = QColor("#f6f5f4");
240             QColor fg_color = QColor("#2e3436");
241             QColor selected_bg_color = QColor("#3584e4");
242             QColor selected_fg_color = QColor("white");
243             QColor osd_text_color = QColor("white");
244             QColor osd_bg_color = QColor("black");
245             QColor shadow = transparentize(QColor("black"), 0.9);
246 
247             QColor backdrop_fg_color = mix(fg_color, bg_color);
248             QColor backdrop_base_color = darken(base_color, 0.01);
249             QColor backdrop_selected_fg_color = backdrop_base_color;
250 
251             // This is the color we use as initial color for the gradient in normal state
252             // Defined in _drawing.scss button(normal)
253             QColor button_base_color = darken(bg_color, 0.04);
254 
255             QColor link_color = darken(selected_bg_color, 0.1);
256             QColor link_visited_color = darken(selected_bg_color, 0.2);
257 
258             palette->setColor(QPalette::All,      QPalette::Window,          bg_color);
259             palette->setColor(QPalette::All,      QPalette::WindowText,      fg_color);
260             palette->setColor(QPalette::All,      QPalette::Base,            base_color);
261             palette->setColor(QPalette::All,      QPalette::AlternateBase,   base_color);
262             palette->setColor(QPalette::All,      QPalette::ToolTipBase,     osd_bg_color);
263             palette->setColor(QPalette::All,      QPalette::ToolTipText,     osd_text_color);
264             palette->setColor(QPalette::All,      QPalette::Text,            fg_color);
265             palette->setColor(QPalette::All,      QPalette::Button,          button_base_color);
266             palette->setColor(QPalette::All,      QPalette::ButtonText,      fg_color);
267             palette->setColor(QPalette::All,      QPalette::BrightText,      text_color);
268 
269             palette->setColor(QPalette::All,      QPalette::Light,           lighten(button_base_color));
270             palette->setColor(QPalette::All,      QPalette::Midlight,        mix(lighten(button_base_color), button_base_color));
271             palette->setColor(QPalette::All,      QPalette::Mid,             mix(darken(button_base_color), button_base_color));
272             palette->setColor(QPalette::All,      QPalette::Dark,            darken(button_base_color));
273             palette->setColor(QPalette::All,      QPalette::Shadow,          shadow);
274 
275             palette->setColor(QPalette::All,      QPalette::Highlight,       selected_bg_color);
276             palette->setColor(QPalette::All,      QPalette::HighlightedText, selected_fg_color);
277 
278             palette->setColor(QPalette::All,      QPalette::Link,            link_color);
279             palette->setColor(QPalette::All,      QPalette::LinkVisited,     link_visited_color);
280 
281             QColor insensitive_fg_color = mix(fg_color, bg_color);
282             QColor insensitive_bg_color = mix(bg_color, base_color, 0.4);
283 
284             palette->setColor(QPalette::Disabled, QPalette::Window,          insensitive_bg_color);
285             palette->setColor(QPalette::Disabled, QPalette::WindowText,      insensitive_fg_color);
286             palette->setColor(QPalette::Disabled, QPalette::Base,            base_color);
287             palette->setColor(QPalette::Disabled, QPalette::AlternateBase,   base_color);
288             palette->setColor(QPalette::Disabled, QPalette::Text,            insensitive_fg_color);
289             palette->setColor(QPalette::Disabled, QPalette::Button,          insensitive_bg_color);
290             palette->setColor(QPalette::Disabled, QPalette::ButtonText,      insensitive_fg_color);
291             palette->setColor(QPalette::Disabled, QPalette::BrightText,      text_color);
292 
293             palette->setColor(QPalette::Disabled, QPalette::Light,           lighten(insensitive_bg_color));
294             palette->setColor(QPalette::Disabled, QPalette::Midlight,        mix(lighten(insensitive_bg_color), insensitive_bg_color));
295             palette->setColor(QPalette::Disabled, QPalette::Mid,             mix(darken(insensitive_bg_color), insensitive_bg_color));
296             palette->setColor(QPalette::Disabled, QPalette::Dark,            darken(insensitive_bg_color));
297             palette->setColor(QPalette::Disabled, QPalette::Shadow,          shadow);
298 
299             palette->setColor(QPalette::Disabled, QPalette::Highlight,       selected_bg_color);
300             palette->setColor(QPalette::Disabled, QPalette::HighlightedText, selected_fg_color);
301 
302             palette->setColor(QPalette::Disabled, QPalette::Link,            link_color);
303             palette->setColor(QPalette::Disabled, QPalette::LinkVisited,     link_visited_color);
304 
305 
306             palette->setColor(QPalette::Inactive, QPalette::Window,          bg_color);
307             palette->setColor(QPalette::Inactive, QPalette::WindowText,      backdrop_fg_color);
308             palette->setColor(QPalette::Inactive, QPalette::Base,            backdrop_base_color);
309             palette->setColor(QPalette::Inactive, QPalette::AlternateBase,   backdrop_base_color);
310             palette->setColor(QPalette::Inactive, QPalette::Text,            backdrop_fg_color);
311             palette->setColor(QPalette::Inactive, QPalette::Button,          button_base_color);
312             palette->setColor(QPalette::Inactive, QPalette::ButtonText,      backdrop_fg_color);
313             palette->setColor(QPalette::Inactive, QPalette::BrightText,      text_color);
314 
315             palette->setColor(QPalette::Inactive, QPalette::Light,           lighten(insensitive_bg_color));
316             palette->setColor(QPalette::Inactive, QPalette::Midlight,        mix(lighten(insensitive_bg_color), insensitive_bg_color));
317             palette->setColor(QPalette::Inactive, QPalette::Mid,             mix(darken(insensitive_bg_color), insensitive_bg_color));
318             palette->setColor(QPalette::Inactive, QPalette::Dark,            darken(insensitive_bg_color));
319             palette->setColor(QPalette::Inactive, QPalette::Shadow,          shadow);
320 
321             palette->setColor(QPalette::Inactive, QPalette::Highlight,       selected_bg_color);
322             palette->setColor(QPalette::Inactive, QPalette::HighlightedText, backdrop_selected_fg_color);
323 
324             palette->setColor(QPalette::Inactive, QPalette::Link,            link_color);
325             palette->setColor(QPalette::Inactive, QPalette::LinkVisited,     link_visited_color);
326         }
327 
328         return palette;
329     }
330 
gtkThemeDarkVariant()331     inline bool gtkThemeDarkVariant() const
332     {
333         return m_gtkThemeDarkVariant;
334     }
335 
gtkTheme()336     inline QString gtkTheme() const
337     {
338         return QString(m_gtkTheme);
339     }
340 
hint(QPlatformTheme::ThemeHint hint)341     inline QVariant hint(QPlatformTheme::ThemeHint hint) const
342     {
343         return m_hints[hint];
344     }
345 
titlebarButtons()346     inline TitlebarButtons titlebarButtons() const
347     {
348         return m_titlebarButtons;
349     }
350 
titlebarButtonPlacement()351     inline TitlebarButtonsPlacement titlebarButtonPlacement() const
352     {
353         return m_titlebarButtonPlacement;
354     }
355 
356 public Q_SLOTS:
357     void cursorBlinkTimeChanged();
358     void cursorSizeChanged();
359     void fontChanged();
360     void iconsChanged();
361     void themeChanged();
362 
363 private Q_SLOTS:
364     void loadFonts();
365     void loadTheme();
366     void loadTitlebar();
367     void loadStaticHints();
368     void portalSettingChanged(const QString &group, const QString &key, const QDBusVariant &value);
369 
370 protected:
371     static void gsettingPropertyChanged(GSettings *settings, gchar *key, GnomeHintsSettings *gnomeHintsSettings);
372 
373 private:
374     template <typename T> T getSettingsProperty(GSettings *settings, const QString &property, bool *ok = nullptr) {
375         Q_UNUSED(settings); Q_UNUSED(property); Q_UNUSED(ok);
376         return {};
377     }
378     template <typename T>
379     T getSettingsProperty(const QString &property, bool *ok = nullptr) {
380         GSettings *settings = m_settings;
381 
382         // In case of Cinnamon session, we most probably want to return the value from here if possible
383         if (m_cinnamonSettings) {
384             GSettingsSchema *schema;
385             g_object_get(G_OBJECT(m_cinnamonSettings), "settings-schema", &schema, NULL);
386 
387             if (schema) {
388                 if (g_settings_schema_has_key(schema, property.toStdString().c_str())) {
389                     settings = m_cinnamonSettings;
390                 }
391             }
392         }
393 
394         // Use org.gnome.desktop.wm.preferences if the property is there, otherwise it would bail on
395         // non-existent property
396         GSettingsSchema *schema;
397         g_object_get(G_OBJECT(m_gnomeDesktopSettings), "settings-schema", &schema, NULL);
398 
399         if (schema) {
400             if (g_settings_schema_has_key(schema, property.toStdString().c_str())) {
401                 settings = m_gnomeDesktopSettings;
402             }
403         }
404 
405         if (m_usePortal) {
406             QVariant value = m_portalSettings.value(QStringLiteral("org.gnome.desktop.interface")).value(property);
407             if (!value.isNull() && value.canConvert<T>())
408                 return value.value<T>();
409             value = m_portalSettings.value(QStringLiteral("org.gnome.desktop.wm.preferences")).value(property);
410             if (!value.isNull() && value.canConvert<T>())
411                 return value.value<T>();
412         }
413 
414         return getSettingsProperty<T>(settings, property, ok);
415     }
416     QStringList xdgIconThemePaths() const;
417     QString kvantumThemeForGtkTheme() const;
418     void configureKvantum(const QString &theme) const;
419 
420     bool m_usePortal;
421     bool m_gtkThemeDarkVariant = false;
422     TitlebarButtons m_titlebarButtons = TitlebarButton::CloseButton;
423     TitlebarButtonsPlacement m_titlebarButtonPlacement = TitlebarButtonsPlacement::RightPlacement;
424     QString m_gtkTheme = nullptr;
425     GSettings *m_cinnamonSettings = nullptr;
426     GSettings *m_gnomeDesktopSettings = nullptr;
427     GSettings *m_settings = nullptr;
428     QHash<QPlatformTheme::Font, QFont*> m_fonts;
429     QHash<QPlatformTheme::ThemeHint, QVariant> m_hints;
430     QMap<QString, QVariantMap> m_portalSettings;
431 };
432 
getSettingsProperty(GSettings * settings,const QString & property,bool * ok)433 template <> inline int GnomeHintsSettings::getSettingsProperty(GSettings *settings, const QString &property, bool *ok) {
434     if (ok)
435         *ok = true;
436     return g_settings_get_int(settings, property.toStdString().c_str());
437 }
438 
getSettingsProperty(GSettings * settings,const QString & property,bool * ok)439 template <> inline QString GnomeHintsSettings::getSettingsProperty(GSettings *settings, const QString &property, bool *ok) {
440     // be exception and resources safe
441     std::unique_ptr<gchar, void(*)(gpointer)> raw {g_settings_get_string(settings, property.toStdString().c_str()), g_free};
442     if (ok)
443         *ok = !!raw;
444     return QString{raw.get()};
445 }
446 
getSettingsProperty(GSettings * settings,const QString & property,bool * ok)447 template <> inline qreal GnomeHintsSettings::getSettingsProperty(GSettings *settings, const QString &property, bool *ok) {
448     if (ok)
449         *ok = true;
450     return g_settings_get_double(settings, property.toStdString().c_str());
451 }
452 
453 Q_DECLARE_OPERATORS_FOR_FLAGS(GnomeHintsSettings::TitlebarButtons)
454 
455 #endif // GNOME_HINTS_SETTINGS_H
456