From dbfb7f40da8f131b58bebfe123b4839110c06c2f Mon Sep 17 00:00:00 2001 From: Serhii Tereshchenko Date: Sat, 31 May 2025 13:04:02 +0300 Subject: [PATCH] client: Support Dark Mode Refs #714 Co-authored-by: Niels De Graef --- desktop/org.gnome.Geary.gschema.xml | 7 +++ .../accounts/accounts-signature-web-view.vala | 5 -- .../application-configuration.vala | 5 ++ .../components-preferences-window.vala | 17 +++++ .../components/components-web-view.vala | 28 ++++++--- ui/components-web-view.css | 59 ++++++++++++++++++ ui/components-web-view.js | 62 +++++++++++++++++++ ui/composer-web-view.css | 23 +++---- ui/conversation-web-view.css | 40 ++++++------ ui/org.gnome.Geary.gresource.xml | 2 +- ui/signature-web-view.css | 5 -- 11 files changed, 196 insertions(+), 57 deletions(-) create mode 100644 ui/components-web-view.css delete mode 100644 ui/signature-web-view.css diff --git a/desktop/org.gnome.Geary.gschema.xml b/desktop/org.gnome.Geary.gschema.xml index 85742cf0..ba3abb24 100644 --- a/desktop/org.gnome.Geary.gschema.xml +++ b/desktop/org.gnome.Geary.gschema.xml @@ -37,6 +37,13 @@ true Display message previews True if we should display a short preview of each message. + + + + false + Unset colors provided in HTML emails + Override colors in HTML emails + Overrides the original colors in HTML messages to integrate better with the app theme. diff --git a/src/client/accounts/accounts-signature-web-view.vala b/src/client/accounts/accounts-signature-web-view.vala index aa1c3ae8..d424dd64 100644 --- a/src/client/accounts/accounts-signature-web-view.vala +++ b/src/client/accounts/accounts-signature-web-view.vala @@ -12,23 +12,18 @@ public class Accounts.SignatureWebView : Components.WebView { private static WebKit.UserScript? app_script = null; - private static WebKit.UserStyleSheet? app_stylesheet = null; public static new void load_resources() throws GLib.Error { SignatureWebView.app_script = Components.WebView.load_app_script( "signature-web-view.js" ); - SignatureWebView.app_stylesheet = Components.WebView.load_app_stylesheet( - "signature-web-view.css" - ); } public SignatureWebView(Application.Configuration config) { base(config); this.user_content_manager.add_script(SignatureWebView.app_script); - this.user_content_manager.add_style_sheet(SignatureWebView.app_stylesheet); } } diff --git a/src/client/application/application-configuration.vala b/src/client/application/application-configuration.vala index 79b53496..e80fbd3f 100644 --- a/src/client/application/application-configuration.vala +++ b/src/client/application/application-configuration.vala @@ -19,6 +19,7 @@ public class Application.Configuration : Geary.BaseObject { public const string COMPOSE_AS_HTML_KEY = "compose-as-html"; public const string CONVERSATION_VIEWER_ZOOM_KEY = "conversation-viewer-zoom"; public const string DISPLAY_PREVIEW_KEY = "display-preview"; + public const string UNSET_HTML_COLORS = "unset-html-colors"; public const string FORMATTING_TOOLBAR_VISIBLE = "formatting-toolbar-visible"; public const string OPTIONAL_PLUGINS = "optional-plugins"; public const string SEARCH_STRATEGY_KEY = "search-strategy"; @@ -99,6 +100,10 @@ public class Application.Configuration : Geary.BaseObject { get { return settings.get_boolean(DISPLAY_PREVIEW_KEY); } } + public bool unset_html_colors { + get { return settings.get_boolean(UNSET_HTML_COLORS); } + } + public bool single_key_shortcuts { get; set; default = false; } public bool run_in_background { diff --git a/src/client/components/components-preferences-window.vala b/src/client/components/components-preferences-window.vala index 78eec8c1..3eff17db 100644 --- a/src/client/components/components-preferences-window.vala +++ b/src/client/components/components-preferences-window.vala @@ -175,6 +175,17 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow { trust_images_row.activatable_widget = trust_images; trust_images_row.add(trust_images); + var unset_html_colors = new Gtk.Switch(); + unset_html_colors.valign = CENTER; + + var unset_html_colors_row = new Hdy.ActionRow(); + /// Translators: Preferences label + unset_html_colors_row.title = _("_Override the original colors in HTML emails"); + unset_html_colors_row.subtitle = _("Overrides the original colors in HTML messages to integrate better with the app theme. Requires restart."); + unset_html_colors_row.use_underline = true; + unset_html_colors_row.activatable_widget = unset_html_colors; + unset_html_colors_row.add(unset_html_colors); + var group = new Hdy.PreferencesGroup(); /// Translators: Preferences group title //group.title = _("General"); @@ -185,6 +196,7 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow { group.add(single_key_shortucts_row); group.add(startup_notifications_row); group.add(trust_images_row); + group.add(unset_html_colors_row); var page = new Hdy.PreferencesPage(); /// Translators: Preferences page title @@ -229,6 +241,11 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow { (GLib.SettingsBindGetMappingShared) settings_trust_images_getter, (GLib.SettingsBindSetMappingShared) settings_trust_images_setter ); + config.bind( + Application.Configuration.UNSET_HTML_COLORS, + unset_html_colors, + "state" + ); } } diff --git a/src/client/components/components-web-view.vala b/src/client/components/components-web-view.vala index 8d7dab92..47c74883 100644 --- a/src/client/components/components-web-view.vala +++ b/src/client/components/components-web-view.vala @@ -67,9 +67,9 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { private static WebKit.WebContext? default_context = null; - private static WebKit.UserStyleSheet? user_stylesheet = null; + private static List styles = new List(); - private static WebKit.UserScript? script = null; + private static List scripts = new List(); /** @@ -126,14 +126,13 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { */ public static void load_resources(GLib.File user_dir) throws GLib.Error { - WebView.script = load_app_script( - "components-web-view.js" - ); + WebView.scripts.append(load_app_script("components-web-view.js")); + WebView.styles.append(load_app_stylesheet("components-web-view.css")); foreach (string name in new string[] { USER_CSS, USER_CSS_LEGACY }) { GLib.File stylesheet = user_dir.get_child(name); try { - WebView.user_stylesheet = load_user_stylesheet(stylesheet); + WebView.styles.append(load_user_stylesheet(stylesheet)); break; } catch (GLib.IOError.NOT_FOUND err) { // All good, try the next one or just exit @@ -350,11 +349,22 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { WebKit.UserContentManager content_manager = custom_manager ?? new WebKit.UserContentManager(); - content_manager.add_script(WebView.script); - if (WebView.user_stylesheet != null) { - content_manager.add_style_sheet(WebView.user_stylesheet); + + if (config.unset_html_colors) { + WebView.scripts.append( + new WebKit.UserScript( + "window.UNSET_HTML_COLORS = true;", + WebKit.UserContentInjectedFrames.TOP_FRAME, + WebKit.UserScriptInjectionTime.START, + null, + null + ) + ); } + WebView.scripts.foreach(script => content_manager.add_script(script)); + WebView.styles.foreach(style => content_manager.add_style_sheet(style)); + Object( settings: setts, user_content_manager: content_manager, diff --git a/ui/components-web-view.css b/ui/components-web-view.css new file mode 100644 index 00000000..3f13e8ed --- /dev/null +++ b/ui/components-web-view.css @@ -0,0 +1,59 @@ +/* + * Shared styles for Components.WebView and subclasses. + */ + +body { + /* Variables */ + --bg-color: white; + --fg-color: black; + --quote-container-bg: #e8e8e8; + --quote-container-fg: #303030; +/* The following was taken from GTK+4 trunk Adwaita theme: + * gtk/theme/Adwaita/gtk-contained.css */ + --button-color: #2e3436; + --button-outline-color: rgba(46, 52, 54, 0.3); + --button-border-color: #b6b6b3; + --button-border-bottom-color: #91918c; + --button-background-image: linear-gradient(to bottom, #e8e8e7, #dededd 60%, #cfcfcd); + --button-text-shadow-color: rgba(255, 255, 255, 0.76923); + --button-box-shadow-color: rgba(255, 255, 255, 0.8); + + --hover-button-color: #2e3436; + --hover-button-outline-color: rgba(46, 52, 54, 0.3); + --hover-button-border-color: #b6b6b3; + --hover-button-border-bottom-color: #91918c; + --hover-button-background-image: linear-gradient(to bottom, #f7f7f7, #e8e8e7 60%, #dededd); + --hover-button-box-shadow-color: rgba(255, 255, 255, 1); + + /* Adjust default controls and system colors for light and dark mode. + See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */ + color-scheme: light dark; + + color: var(--fg-color); + background-color: var(--bg-color); +} + +@media (prefers-color-scheme: dark) { + body[data-geary-theme=dark] { + --bg-color: #2a2d30; + --fg-color: #eaeaea; + --quote-container-bg: #36383b; + --quote-container-fg: #e3e3e3; + + /* Adwaita Dark inspired button colors */ + --button-color: #eeeeec; + --button-outline-color: rgba(0, 0, 0, 0.3); + --button-border-color: #202020; + --button-border-bottom-color: #151515; + --button-background-image: linear-gradient(to bottom, #4a4a4a, #3c3c3c 60%, #303030); + --button-text-shadow-color: rgba(0, 0, 0, 0.4); + --button-box-shadow-color: rgba(0, 0, 0, 0.3); + + --hover-button-color: #ffffff; + --hover-button-outline-color: rgba(0, 0, 0, 0.4); + --hover-button-border-color: #2c2c2c; + --hover-button-border-bottom-color: #1e1e1e; + --hover-button-background-image: linear-gradient(to bottom, #5a5a5a, #4c4c4c 60%, #404040); + --hover-button-box-shadow-color: rgba(0, 0, 0, 0.4); + } +} diff --git a/ui/components-web-view.js b/ui/components-web-view.js index 8221c231..c926c3cc 100644 --- a/ui/components-web-view.js +++ b/ui/components-web-view.js @@ -71,6 +71,10 @@ PageState.prototype = { // completing. this.updatePreferredHeight(); this._contentLoaded(); + if (window.UNSET_HTML_COLORS) { + document.body.setAttribute("data-geary-theme", "dark") + unsetHTMLColors(document); + } }, loadRemoteResources: function() { const TYPES = "*[src], *[srcset]"; @@ -182,3 +186,61 @@ let MessageSender = function(name) { _GearyWebExtension.send(name, Array.from(arguments)); }; }; + + +/** + * Unsets inline and stylesheet colors from the given document. + * @param {Document} doc The HTML document to process. + * @returns {void} + */ +function unsetHTMLColors(doc) { + // Slightly modified copy from Evolution + // https://gitlab.gnome.org/GNOME/evolution/-/blob/94510bed94e8de641a8d54f2adaec6f02a8972de/data/webkit/e-web-view.js#L1169 + + Array.from(doc.styleSheets).forEach(sheet => { + + Array.from(sheet.cssRules).forEach(rule => { + + if (!rule.style || !rule.selectorText ) + return; + + if (rule.style.color) + rule.style.removeProperty("color"); + + if (rule.style.backgroundColor) + rule.style.removeProperty("background-color"); + }) + }) + + doc.querySelectorAll("[style],[color],[bgcolor]").forEach(elem => { + + if (["HTML", "IFRAME", "INPUT", "BUTTON", "IMG"].includes(elem.tagName)) { + return; + } + + if (elem.style) { + if (elem.style.color) + elem.style.removeProperty("color"); + + if (elem.style.backgroundColor) + elem.style.removeProperty("background-color"); + + if (elem.style.backgroundImage) + elem.style.removeProperty("background-image"); + + if (!elem.style.length) + elem.removeAttribute("style"); + } + + elem.removeAttribute("color"); + elem.removeAttribute("bgcolor"); + }); + + doc.querySelectorAll("body").forEach(elem => { + elem.removeAttribute("bgcolor"); + elem.removeAttribute("text"); + elem.removeAttribute("link"); + elem.removeAttribute("alink"); + elem.removeAttribute("vlink"); + }); +} diff --git a/ui/composer-web-view.css b/ui/composer-web-view.css index 6ea2caef..17b8db0f 100644 --- a/ui/composer-web-view.css +++ b/ui/composer-web-view.css @@ -3,27 +3,20 @@ * Copyright 2017 Michael Gratton */ -:root { - /* Adjust default controls and system colors for light and dark mode. - See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */ - color-scheme: light dark; - - /* Apply canvas background to the root element, so body can darken - it using alpha. */ - background-color: canvas; -} - body { - background-color: rgb(50% 50% 50% / .05); - margin: 0 !important; border: 0 !important; padding: 0 !important; font-size: medium !important; } -body.plain { - font-family: monospace; +body.plain, body.plain * { + font-family: monospace !important; + font-weight: normal; + font-style: normal; + font-size: medium !important; + color: var(--fg-color) !important; + background-color: var(--bg-color) !important; text-decoration: none; } @@ -50,7 +43,7 @@ body > div#geary-quote { } body > div:focus-within { - background-color: canvas; + background-color: white; } body > div#geary-signature:focus-within, diff --git a/ui/conversation-web-view.css b/ui/conversation-web-view.css index baed4bd5..1d997db1 100644 --- a/ui/conversation-web-view.css +++ b/ui/conversation-web-view.css @@ -13,11 +13,7 @@ transition: height 0.25s !important; } -:root { - /* Adjust default controls and system colors for light and dark mode. - See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */ - color-scheme: light dark; - +html { /* Width must always be defined by the viewport so content doesn't overflow inside the WebView, height must always be defined by the content so the WebView can be sized to fit exactly. */ @@ -105,11 +101,11 @@ pre { margin: 0.5em 0; border-radius: 4px; padding: 0.5em 0; - color: #303030; - background-color: #e8e8e8;/* recv-quoted */ + color: var(--quote-container-fg); + background-color: var(--quote-container-bg);/* recv-quoted */ } .geary-sent .geary-quote-container { - background-color: #e8e8e8;/* sent-quoted */ + background-color: var(--quote-container-bg);/* sent-quoted */ } .geary-quote-container > .geary-quote { @@ -172,24 +168,24 @@ pre { border: 1px solid; border-radius: 3px; transition: all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); - color: #2e3436; - outline-color: rgba(46, 52, 54, 0.3); - border-color: #b6b6b3; - border-bottom-color: #91918c; - background-image: linear-gradient(to bottom, #e8e8e7, #dededd 60%, #cfcfcd); - text-shadow: 0 1px rgba(255, 255, 255, 0.76923); - box-shadow: inset 0 1px rgba(255, 255, 255, 0.8); + color: var(--button-color); + outline-color: var(--button-outline-color); + border-color: var(--button-border-color); + border-bottom-color: var(--button-border-bottom-color); + background-image: var(--button-background-image); + text-shadow: 0 1px var(--button-text-shadow-color); + box-shadow: inset 0 1px var(--button-box-shadow-color); } .geary-quote-container .geary-button:hover { /* Likewise the properties below also workaround WK Bug 166648, * and taken from gtk/theme/Adwaita/gtk-contained.css. */ - color: #2e3436; - outline-color: rgba(46, 52, 54, 0.3); - border-color: #b6b6b3; - border-bottom-color: #91918c; - text-shadow: 0 1px rgba(255, 255, 255, 0.76923); - box-shadow: inset 0 1px white; - background-image: linear-gradient(to bottom, #f7f7f7, #e8e8e7 60%, #dededd); + color: var(--hover-button-color); + outline-color: var(--hover-button-outline-color); + border-color: var(--hover-button-border-color); + border-bottom-color: var(--hover-button-border-bottom-color); + text-shadow: 0 1px var(--button-text-shadow-color); + box-shadow: inset 0 1px var(--hover-button-box-shadow-color); + background-image: var(--hover-button-background-image); } /* Highlight search terms */ diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml index 1f9ef0ca..aa56fc8d 100644 --- a/ui/org.gnome.Geary.gresource.xml +++ b/ui/org.gnome.Geary.gresource.xml @@ -9,6 +9,7 @@ application-main-window.ui certificate_warning_dialog.glade components-web-view.js + components-web-view.css components-attachment-pane.ui components-attachment-pane-menus.ui components-attachment-view.ui @@ -49,7 +50,6 @@ gtk/help-overlay.ui password-dialog.glade problem-details-dialog.ui - signature-web-view.css signature-web-view.js geary.css single-key-shortcuts.css diff --git a/ui/signature-web-view.css b/ui/signature-web-view.css deleted file mode 100644 index 53813887..00000000 --- a/ui/signature-web-view.css +++ /dev/null @@ -1,5 +0,0 @@ -:root { - /* Adjust default controls and system colors for light and dark mode. - See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */ - color-scheme: light dark; -}