Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move AccentColorManager here from gala #127

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 libgexiv2-dev meson valac
- name: Build
env:
DESTDIR: out
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ gio_dep = dependency ('gio-2.0')
glib_dep = dependency('glib-2.0')
granite_dep = dependency('granite', version: '>= 5.3.0')
pk_dep = dependency('packagekit-glib2')
gexiv2_dep = dependency('gexiv2')
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 @@ -19,6 +19,7 @@ public sealed class SettingsDaemon.Application : Gtk.Application {
private Backends.PrefersColorSchemeSettings prefers_color_scheme_settings;

private Backends.Housekeeping housekeeping;
private Backends.AccentColorManager accent_color_manager;

private const string FDO_ACCOUNTS_NAME = "org.freedesktop.Accounts";
private const string FDO_ACCOUNTS_PATH = "/org/freedesktop/Accounts";
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
172 changes: 172 additions & 0 deletions src/Backends/AccentColor/AccentColorManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2021-2024 elementary, Inc. <https://elementary.io>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored by: Marius Meisenzahl <[email protected]>
*/

public class SettingsDaemon.Backends.AccentColorManager : GLib.Object {
private class NamedColor : GLib.Object {
public string name { get; construct set; }
public string theme { get; construct set; }
public uint32 color { get; construct set; }

public NamedColor (string name, string theme, uint32 color) {
Object (
name: name,
theme: theme,
color: color
);
}
}

private const string INTERFACE_SCHEMA = "org.gnome.desktop.interface";
private const string STYLESHEET_KEY = "gtk-theme";
private const string TAG_ACCENT_COLOR = "Xmp.xmp.io.elementary.AccentColor";

private const string THEME_BLUE = "io.elementary.stylesheet.blueberry";
private const string THEME_MINT = "io.elementary.stylesheet.mint";
private const string THEME_GREEN = "io.elementary.stylesheet.lime";
private const string THEME_YELLOW = "io.elementary.stylesheet.banana";
private const string THEME_ORANGE = "io.elementary.stylesheet.orange";
private const string THEME_RED = "io.elementary.stylesheet.strawberry";
private const string THEME_PINK = "io.elementary.stylesheet.bubblegum";
private const string THEME_PURPLE = "io.elementary.stylesheet.grape";
private const string THEME_BROWN = "io.elementary.stylesheet.cocoa";
private const string THEME_GRAY = "io.elementary.stylesheet.slate";

public Pantheon.AccountsService accounts_service { get; construct; }

private GLib.Settings background_settings;
private GLib.Settings interface_settings;

private NamedColor[] theme_colors = {
new NamedColor ("Blue", THEME_BLUE, 0x3689e6),
new NamedColor ("Mint", THEME_MINT, 0x28bca3),
new NamedColor ("Green", THEME_GREEN, 0x68b723),
new NamedColor ("Yellow", THEME_YELLOW, 0xf9c440),
new NamedColor ("Orange", THEME_ORANGE, 0xffa154),
new NamedColor ("Red", THEME_RED, 0xed5353),
new NamedColor ("Pink", THEME_PINK, 0xde3e80),
new NamedColor ("Purple", THEME_PURPLE, 0xa56de2),
new NamedColor ("Brown", THEME_BROWN, 0x8a715e),
new NamedColor ("Gray", THEME_GRAY, 0x667885)
};

public AccentColorManager (Pantheon.AccountsService accounts_service) {
Object (accounts_service: accounts_service);
}

construct {
background_settings = new GLib.Settings ("org.gnome.desktop.background");
interface_settings = new GLib.Settings (INTERFACE_SCHEMA);

((DBusProxy) accounts_service).g_properties_changed.connect (() => {
update_accent_color ();
});

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 () {
bool set_accent_color_auto = accounts_service.prefers_accent_color == 0;

if (!set_accent_color_auto) {
return;
}

bool set_accent_color_based_on_primary_color = background_settings.get_enum ("picture-options") == 0;

var current_stylesheet = interface_settings.get_string (STYLESHEET_KEY);

debug ("Current stylesheet: %s", current_stylesheet);

NamedColor? new_color = null;
if (set_accent_color_based_on_primary_color) {
var primary_color = background_settings.get_string ("primary-color");
debug ("Current primary color: %s", primary_color);

new_color = get_accent_color_based_on_primary_color (primary_color);
} else {
var picture_uri = background_settings.get_string ("picture-uri");
debug ("Current wallpaper: %s", picture_uri);

var accent_color_name = read_accent_color_name_from_exif (picture_uri);
if (accent_color_name != null) {
for (int i = 0; i < theme_colors.length; i++) {
if (theme_colors[i].name == accent_color_name) {
new_color = theme_colors[i];
break;
}
}
} else {
new_color = get_accent_color_of_picture_simple (picture_uri);
}
}

if (new_color != null && new_color.theme != current_stylesheet) {
debug ("New stylesheet: %s", new_color.theme);

interface_settings.set_string (
STYLESHEET_KEY,
new_color.theme
);
}
}

private string? read_accent_color_name_from_exif (string picture_uri) {
string path = "";
GExiv2.Metadata metadata;
try {
path = Filename.from_uri (picture_uri);
metadata = new GExiv2.Metadata ();
metadata.open_path (path);

return metadata.try_get_tag_string (TAG_ACCENT_COLOR);
} catch (Error e) {
warning ("Error parsing exif metadata of \"%s\": %s", path, e.message);
return null;
}
}

private NamedColor? get_accent_color (ColorExtractor color_extractor) {
var palette = new Gee.ArrayList<uint32> ();
for (int i = 0; i < theme_colors.length; i++) {
palette.add (theme_colors[i].color);
}

var index = color_extractor.get_dominant_color_index (palette);
return theme_colors[index];
}

private NamedColor? get_accent_color_of_picture_simple (string picture_uri) {
var file = File.new_for_uri (picture_uri);

try {
var pixbuf = new Gdk.Pixbuf.from_file (file.get_path ());
var color_extractor = new ColorExtractor.from_pixbuf (pixbuf);

return get_accent_color (color_extractor);
} catch (Error e) {
warning (e.message);
}

return null;
}

private NamedColor? get_accent_color_based_on_primary_color (string primary_color) {
Gdk.RGBA color = {};
color.parse (primary_color);

var r = (uint32) (color.red * 255);
var g = (uint32) (color.green * 255);
var b = (uint32) (color.blue * 255);

var color_extractor = new ColorExtractor.from_primary_color (r * 65536 + g * 256 + b);

return get_accent_color (color_extractor);
}
}
88 changes: 88 additions & 0 deletions src/Backends/AccentColor/ColorExtractor.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2021-2024 elementary, Inc. <https://elementary.io>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored by: Marius Meisenzahl <[email protected]>
*/

public class SettingsDaemon.ColorExtractor : Object {
private const double PERCENTAGE_SAMPLE_PIXELS = 0.01;

public Gdk.Pixbuf? pixbuf { get; construct set; }
public uint32 primary_color { get; construct set; }

private Gee.List<uint32> pixels;

public ColorExtractor.from_pixbuf (Gdk.Pixbuf pixbuf) {
Object (pixbuf: pixbuf);

pixels = convert_pixels_to_rgb (pixbuf.get_pixels_with_length (), pixbuf.has_alpha);
}

public ColorExtractor.from_primary_color (uint32 primary_color) {
Object (primary_color: primary_color);

pixels = new Gee.ArrayList<uint32> ();
pixels.add (primary_color);
}

public int get_dominant_color_index (Gee.List<uint32> palette) {
int index = 0;
var matches = new double[palette.size];

pixels.foreach ((pixel) => {
for (int i = 0; i < palette.size; i++) {
var color = palette.get (i);

var pixel_r = (int) (pixel / 65536);
var pixel_g = (int) ((pixel - pixel_r * 65536) / 256);
var pixel_b = (int) (pixel - pixel_r * 65536 - pixel_g * 255);

var color_r = (int) (color / 65536);
var color_g = (int) ((color - color_r * 65536) / 256);
var color_b = (int) (color - color_r * 65536 - color_g * 255);

var distance = Math.sqrt (
Math.pow (((pixel_r - color_r) / 255.0), 2) +
Math.pow (((pixel_g - color_g) / 255.0), 2) +
Math.pow (((pixel_b - color_b) / 255.0), 2)
);

if (distance > 0.25) {
continue;
}

matches[i] += 1.0 - distance;
}

return true;
});

double best_match = double.MIN;
for (int i = 0; i < matches.length; i++) {
if (matches[i] > best_match) {
best_match = matches[i];
index = i;
}
}

return index;
}

private Gee.ArrayList<uint32> convert_pixels_to_rgb (uint8[] pixels, bool has_alpha) {
var list = new Gee.ArrayList<uint32> ();

int factor = 3 + (int) has_alpha;
int step_size = (int) (pixels.length / factor * PERCENTAGE_SAMPLE_PIXELS);

for (int i = 0; i < pixels.length / factor; i += step_size) {
int offset = i * factor;
double red = pixels[offset];
double green = pixels[offset + 1];
double blue = pixels[offset + 2];

list.add ((uint32) (red * 65536 + green * 256 + blue));
}

return list;
}
}
5 changes: 4 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
sources = files(
'AccountsService.vala',
'Application.vala',
'Backends/AccentColor/AccentColorManager.vala',
'Backends/AccentColor/ColorExtractor.vala',
'Backends/Housekeeping.vala',
'Backends/InterfaceSettings.vala',
'Backends/KeyboardSettings.vala',
Expand All @@ -22,7 +24,8 @@ executable(
granite_dep,
libgeoclue_dep,
m_dep,
pk_dep
pk_dep,
gexiv2_dep
],
install: true,
)