Skip to content

Commit

Permalink
Add AccentColor Backend (#128)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Marukesu authored Jan 28, 2024
1 parent c5d38dd commit b2bb693
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions src/AccountsService.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
2 changes: 2 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
}
Expand Down
204 changes: 204 additions & 0 deletions src/Backends/AccentColorManager.vala
Original file line number Diff line number Diff line change
@@ -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 <mariusmeisenzahl@gmail.com>
*/

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<Gdk.RGBA> ();

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;
}
}
3 changes: 3 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
sources = files(
'AccountsService.vala',
'Application.vala',
'Backends/AccentColorManager.vala',
'Backends/Housekeeping.vala',
'Backends/InterfaceSettings.vala',
'Backends/KeyboardSettings.vala',
Expand All @@ -20,6 +21,8 @@ executable(
gio_dep,
glib_dep,
granite_dep,
gdk_pixbuf_dep,
gexiv2_dep,
libgeoclue_dep,
m_dep,
pk_dep
Expand Down

0 comments on commit b2bb693

Please sign in to comment.