From b2bb693d91aafe540d0f6401791e2cb69eaf840b Mon Sep 17 00:00:00 2001 From: Gustavo Marques Date: Sun, 28 Jan 2024 01:46:32 -0300 Subject: [PATCH] Add AccentColor Backend (#128) Move the AccentColorManager from gala to settings-daemon, with the following changes: * fallback to the primary-color if it fail to get a color from the background. * properly listen to changes in the PrefersColorScheme property and don't check for the accent when not required. --- .github/workflows/main.yml | 2 +- README.md | 2 + meson.build | 2 + src/AccountsService.vala | 1 + src/Application.vala | 2 + src/Backends/AccentColorManager.vala | 204 +++++++++++++++++++++++++++ src/meson.build | 3 + 7 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/Backends/AccentColorManager.vala diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7c0577d3..a78cadbb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ jobs: - name: Install Dependencies run: | apt update - apt install -y libaccountsservice-dev libdbus-1-dev libgranite-dev libgeoclue-2-dev libfwupd-dev libpackagekit-glib2-dev meson valac + apt install -y libaccountsservice-dev libdbus-1-dev libgranite-dev libgeoclue-2-dev libfwupd-dev libpackagekit-glib2-dev libgdk-pixbuf2.0-dev libgexiv2-dev meson valac - name: Build env: DESTDIR: out diff --git a/README.md b/README.md index b431dbbc..661b7b8b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ You'll need the following dependencies: * libaccountsservice-dev * libdbus-1-dev * libfwupd-dev +* libgdk-pixbuf2.0-dev * libgeoclue-2-dev +* libgexiv2-dev * libgranite-dev * libpackagekit-glib2-dev * meson diff --git a/meson.build b/meson.build index 28b576b4..595df763 100644 --- a/meson.build +++ b/meson.build @@ -9,6 +9,8 @@ fwupd_dep = dependency('fwupd') gio_dep = dependency ('gio-2.0') glib_dep = dependency('glib-2.0') granite_dep = dependency('granite', version: '>= 5.3.0') +gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0') +gexiv2_dep = dependency('gexiv2') pk_dep = dependency('packagekit-glib2') i18n = import('i18n') gettext_name = meson.project_name() diff --git a/src/AccountsService.vala b/src/AccountsService.vala index f7405b3d..99db8318 100644 --- a/src/AccountsService.vala +++ b/src/AccountsService.vala @@ -80,6 +80,7 @@ public interface SettingsDaemon.AccountsService : Object { [DBus (name = "io.elementary.pantheon.AccountsService")] public interface Pantheon.AccountsService : Object { public abstract int prefers_color_scheme { get; set; } + public abstract int prefers_accent_color { get; set; } } [DBus (name = "org.freedesktop.DisplayManager.AccountsService")] diff --git a/src/Application.vala b/src/Application.vala index 1efb885e..0014d548 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -17,6 +17,7 @@ public sealed class SettingsDaemon.Application : Gtk.Application { private Backends.InterfaceSettings interface_settings; private Backends.NightLightSettings night_light_settings; private Backends.PrefersColorSchemeSettings prefers_color_scheme_settings; + private Backends.AccentColorManager accent_color_manager; private Backends.Housekeeping housekeeping; @@ -117,6 +118,7 @@ public sealed class SettingsDaemon.Application : Gtk.Application { try { pantheon_service = yield connection.get_proxy (FDO_ACCOUNTS_NAME, path, GET_INVALIDATED_PROPERTIES); prefers_color_scheme_settings = new Backends.PrefersColorSchemeSettings (pantheon_service); + accent_color_manager = new Backends.AccentColorManager (pantheon_service); } catch { warning ("Unable to get pantheon's AccountsService proxy, color scheme preference may be incorrect"); } diff --git a/src/Backends/AccentColorManager.vala b/src/Backends/AccentColorManager.vala new file mode 100644 index 00000000..a5d329d3 --- /dev/null +++ b/src/Backends/AccentColorManager.vala @@ -0,0 +1,204 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2021-2024 elementary, Inc. (https://elementary.io) + * Authored by: Marius Meisenzahl + */ + +public class SettingsDaemon.Backends.AccentColorManager : Object { + public unowned Pantheon.AccountsService accounts_service { get; construct; } + + private Settings background_settings; + private Settings interface_settings; + + private enum BackgroundStyle { + NONE; + } + + private struct Theme { + string name; + string stylesheet; + Gdk.RGBA color; + } + + private static inline Gdk.RGBA rgba_from_int (int color) { + return { + ((color >> 16) & 255) / 255.0, + ((color >> 8) & 255) / 255.0, + (color & 255) / 255.0, + 0.0 + }; + } + + private static Theme[] themes = { + { "Blue", "io.elementary.stylesheet.blueberry", rgba_from_int (0x3689e6) }, // vala-lint=double-spaces + { "Mint", "io.elementary.stylesheet.mint", rgba_from_int (0x28bca3) }, // vala-lint=double-spaces + { "Green", "io.elementary.stylesheet.lime", rgba_from_int (0x68b723) }, // vala-lint=double-spaces + { "Yellow", "io.elementary.stylesheet.banana", rgba_from_int (0xf9c440) }, // vala-lint=double-spaces + { "Orange", "io.elementary.stylesheet.orange", rgba_from_int (0xffa154) }, // vala-lint=double-spaces + { "Red", "io.elementary.stylesheet.strawberry", rgba_from_int (0xed5353) }, // vala-lint=double-spaces + { "Pink", "io.elementary.stylesheet.bubblegum", rgba_from_int (0xde3e80) }, // vala-lint=double-spaces + { "Purple", "io.elementary.stylesheet.grape", rgba_from_int (0xa56de2) }, // vala-lint=double-spaces + { "Brown", "io.elementary.stylesheet.cocoa", rgba_from_int (0x8a715e) }, // vala-lint=double-spaces + { "Gray", "io.elementary.stylesheet.slate", rgba_from_int (0x667885) } // vala-lint=double-spaces + }; + + public AccentColorManager (Pantheon.AccountsService accounts_service) { + Object (accounts_service: accounts_service); + } + + construct { + background_settings = new Settings ("org.gnome.desktop.background"); + interface_settings = new Settings ("org.gnome.desktop.interface"); + + ((DBusProxy) accounts_service).g_properties_changed.connect ((props) => { + int accent_color; + + if (!props.lookup ("PrefersAccentColor", "i", out accent_color)) { + return; + }; + + if (accent_color == 0) { + background_settings.changed["picture-options"].connect (update_accent_color); + background_settings.changed["picture-uri"].connect (update_accent_color); + background_settings.changed["primary-color"].connect (update_accent_color); + update_accent_color (); + } else { + background_settings.changed["picture-options"].disconnect (update_accent_color); + background_settings.changed["picture-uri"].disconnect (update_accent_color); + background_settings.changed["primary-color"].disconnect (update_accent_color); + } + }); + + if (accounts_service.prefers_accent_color == 0) { + background_settings.changed["picture-options"].connect (update_accent_color); + background_settings.changed["picture-uri"].connect (update_accent_color); + background_settings.changed["primary-color"].connect (update_accent_color); + update_accent_color (); + } + } + + private void update_accent_color () { + var current_stylesheet = interface_settings.get_string ("gtk-theme"); + debug ("Current stylesheet: %s", current_stylesheet); + Theme? new_theme = null; + + if (background_settings.get_enum ("picture-options") != BackgroundStyle.NONE) { + var picture_uri = background_settings.get_string ("picture-uri"); + debug ("Current wallpaper: %s", picture_uri); + new_theme = get_theme_for_picture (picture_uri); + } + + // we failed to get a theme from the wallpaper, or the user is using a primary color as background + if (new_theme == null) { + var primary_color = background_settings.get_string ("primary-color"); + debug ("Current primary color: %s", primary_color); + new_theme = get_theme_for_primary_color (primary_color); + } + + if (new_theme.stylesheet != current_stylesheet) { + debug ("New stylesheet: %s", new_theme.stylesheet); + interface_settings.set_string ("gtk-theme", new_theme.stylesheet); + } + } + + private Theme? get_theme_for_primary_color (string primary_color) { + var best_match = double.MIN; + var index = 0; + + Gdk.RGBA color = {}; + color.parse (primary_color); + + for (var i = 0; i < themes.length; i++) { + var match = get_match (color, themes[i].color); + if (match > best_match) { + best_match = match; + index = i; + } + } + + return themes[index]; + } + + private Theme? get_theme_for_picture (string picture_uri) { + string path; + try { + path = Filename.from_uri (picture_uri); + } catch (ConvertError e) { + warning ("Failed to convert picture uri to path: '%s'", e.message); + return null; + } + + // try to read a theme name from exif metadata + try { + var metadata = new GExiv2.Metadata (); + metadata.open_path (path); + var accent_name = metadata.try_get_tag_string ("Xmp.xmp.io.elementary.AccentColor"); + + foreach (unowned var theme in themes) { + if (theme.name == accent_name) { + return theme; + } + } + } catch (Error e) { + warning ("Error parsing exif metadata of \"%s\": %s", path, e.message); + } + + // if failed, get a dominant color from the picture + try { + const double PERCENTAGE_SAMPLE_PIXELS = 0.01; + + var pixbuf = new Gdk.Pixbuf.from_file (path); + + var raw_pixels = pixbuf.get_pixels_with_length (); + var factor = pixbuf.has_alpha ? 4 : 3; + var step_size = (int) (raw_pixels.length / factor * PERCENTAGE_SAMPLE_PIXELS); + var pixels = new Array (); + + for (var i = 0; i < raw_pixels.length / factor; i += step_size) { + var offset = i * factor; + pixels.append_val ({ + raw_pixels[offset] / 255.0, + raw_pixels[offset + 1] / 255.0, + raw_pixels[offset + 2] / 255.0, + 0.0 + }); + } + + var best_match = double.MIN; + var index = 0; + + for (var i = 0; i < themes.length; i++) { + var match = 0.0; + + foreach (unowned var pixel in pixels) { + match += get_match (pixel, themes[i].color); + } + + if (match > best_match) { + best_match = match; + index = i; + } + } + + return themes[index]; + } catch (Error e) { + warning (e.message); + } + + return null; + } + + private static inline double get_match (Gdk.RGBA color, Gdk.RGBA other) { + var distance = Math.sqrt ( + Math.pow ((color.red - other.red), 2) + + Math.pow ((color.green - other.green), 2) + + Math.pow ((color.blue - other.blue), 2) + ); + + if (distance > 0.25) { + return 0.0; + } + + return 1.0 - distance; + } +} diff --git a/src/meson.build b/src/meson.build index b35eed56..05f2250f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,7 @@ sources = files( 'AccountsService.vala', 'Application.vala', + 'Backends/AccentColorManager.vala', 'Backends/Housekeeping.vala', 'Backends/InterfaceSettings.vala', 'Backends/KeyboardSettings.vala', @@ -20,6 +21,8 @@ executable( gio_dep, glib_dep, granite_dep, + gdk_pixbuf_dep, + gexiv2_dep, libgeoclue_dep, m_dep, pk_dep