-
-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Set accent color based on wallpaper (#1104)
* Add libgexiv2-dev as dependency * Add classes * Add demo * Fix signal * Cleanup * Use Granite.Settings * Revert "Use Granite.Settings" This reverts commit a2da4bb. * Sort dependencies alphabetically * Cleanup class NamedColor * Read color from Gtk theme * Cleanup
- Loading branch information
1 parent
3d3a69a
commit 5cf9308
Showing
10 changed files
with
327 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
/* | ||
* Copyright 2021 elementary, Inc. (https://elementary.io) | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public | ||
* License along with this program; if not, write to the | ||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
* Boston, MA 02110-1301 USA | ||
* | ||
* Authored by: Marius Meisenzahl <mariusmeisenzahl@gmail.com> | ||
*/ | ||
|
||
public class Gala.AccentColorManager : Object { | ||
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"; | ||
|
||
private Gala.AccountsService? gala_accounts_service = null; | ||
|
||
private Settings background_settings; | ||
private Settings interface_settings; | ||
|
||
private NamedColor[] theme_colors = { | ||
new NamedColor ("Blue", THEME_BLUE), | ||
new NamedColor ("Mint", THEME_MINT), | ||
new NamedColor ("Green", THEME_GREEN), | ||
new NamedColor ("Yellow", THEME_YELLOW), | ||
new NamedColor ("Orange", THEME_ORANGE), | ||
new NamedColor ("Red", THEME_RED), | ||
new NamedColor ("Pink", THEME_PINK), | ||
new NamedColor ("Purple", THEME_PURPLE), | ||
new NamedColor ("Brown", THEME_BROWN), | ||
new NamedColor ("Gray", THEME_GRAY) | ||
}; | ||
|
||
construct { | ||
background_settings = new Settings ("org.gnome.desktop.background"); | ||
interface_settings = new Settings (INTERFACE_SCHEMA); | ||
|
||
string? user_path = null; | ||
try { | ||
FDO.Accounts? accounts_service = GLib.Bus.get_proxy_sync ( | ||
GLib.BusType.SYSTEM, | ||
"org.freedesktop.Accounts", | ||
"/org/freedesktop/Accounts" | ||
); | ||
|
||
user_path = accounts_service.find_user_by_name (GLib.Environment.get_user_name ()); | ||
} catch (Error e) { | ||
critical (e.message); | ||
} | ||
|
||
if (user_path != null) { | ||
try { | ||
gala_accounts_service = GLib.Bus.get_proxy_sync ( | ||
GLib.BusType.SYSTEM, | ||
"org.freedesktop.Accounts", | ||
user_path | ||
); | ||
|
||
((DBusProxy)gala_accounts_service).g_properties_changed.connect (() => { | ||
update_accent_color (); | ||
}); | ||
} catch (Error e) { | ||
warning ("Unable to get AccountsService proxy, accent color preference may be incorrect"); | ||
} | ||
} | ||
|
||
background_settings.changed["picture-uri"].connect (update_accent_color); | ||
|
||
update_accent_color (); | ||
} | ||
|
||
private void update_accent_color () { | ||
bool set_accent_color_based_on_wallpaper = gala_accounts_service.prefers_accent_color == 0; | ||
|
||
if (set_accent_color_based_on_wallpaper) { | ||
var picture_uri = background_settings.get_string ("picture-uri"); | ||
|
||
var current_stylesheet = interface_settings.get_string (STYLESHEET_KEY); | ||
|
||
debug ("Current wallpaper: %s", picture_uri); | ||
debug ("Current stylesheet: %s", current_stylesheet); | ||
|
||
NamedColor? new_color = null; | ||
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); | ||
} catch (Error e) { | ||
warning ("Error parsing exif metadata of \"%s\": %s", path, e.message); | ||
return null; | ||
} | ||
|
||
return metadata.get_tag_string (TAG_ACCENT_COLOR); | ||
} | ||
|
||
public NamedColor? get_accent_color_of_picture_simple (string picture_uri) { | ||
NamedColor new_color = null; | ||
|
||
var file = File.new_for_uri (picture_uri); | ||
|
||
try { | ||
var pixbuf = new Gdk.Pixbuf.from_file (file.get_path ()); | ||
var color_extractor = new ColorExtractor (pixbuf); | ||
|
||
var palette = new Gee.ArrayList<Granite.Drawing.Color> (); | ||
for (int i = 0; i < theme_colors.length; i++) { | ||
palette.add (theme_colors[i].color); | ||
} | ||
|
||
var index = color_extractor.get_dominant_color_index (palette); | ||
new_color = theme_colors[index]; | ||
} catch (Error e) { | ||
warning (e.message); | ||
} | ||
|
||
return new_color; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* | ||
* Copyright 2021 elementary, Inc. (https://elementary.io) | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public | ||
* License along with this program; if not, write to the | ||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
* Boston, MA 02110-1301 USA | ||
* | ||
* Authored by: Marius Meisenzahl <mariusmeisenzahl@gmail.com> | ||
*/ | ||
|
||
public class Gala.ColorExtractor : Object { | ||
private const double PERCENTAGE_SAMPLE_PIXELS = 0.01; | ||
|
||
public Gdk.Pixbuf pixbuf { get; construct set; } | ||
|
||
private Gee.List<Granite.Drawing.Color> pixels; | ||
|
||
public ColorExtractor (Gdk.Pixbuf pixbuf) { | ||
Object (pixbuf: pixbuf); | ||
|
||
pixels = convert_pixels_to_rgb (pixbuf.get_pixels_with_length (), pixbuf.has_alpha); | ||
} | ||
|
||
public int get_dominant_color_index (Gee.List<Granite.Drawing.Color> 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 distance = Math.sqrt ( | ||
Math.pow ((pixel.R - color.R), 2) + | ||
Math.pow ((pixel.G - color.G), 2) + | ||
Math.pow ((pixel.B - color.B), 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<Granite.Drawing.Color> convert_pixels_to_rgb (uint8[] pixels, bool has_alpha) { | ||
var list = new Gee.ArrayList<Granite.Drawing.Color> (); | ||
|
||
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] / 255.0; | ||
double green = pixels[offset + 1] / 255.0; | ||
double blue = pixels[offset + 2] / 255.0; | ||
|
||
list.add (new Granite.Drawing.Color (red, green, blue, 0.0)); | ||
} | ||
|
||
return list; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright 2021 elementary, Inc. (https://elementary.io) | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public | ||
* License along with this program; if not, write to the | ||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
* Boston, MA 02110-1301 USA | ||
* | ||
* Authored by: Marius Meisenzahl <mariusmeisenzahl@gmail.com> | ||
*/ | ||
|
||
public class Gala.NamedColor : Object { | ||
public string name { get; construct set; } | ||
public string theme { get; construct set; } | ||
public Granite.Drawing.Color color { get; construct set; } | ||
|
||
public NamedColor (string name, string theme) { | ||
Object ( | ||
name: name, | ||
theme: theme, | ||
color: InternalUtils.get_accent_color_by_theme_name (theme) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[DBus (name = "io.elementary.pantheon.AccountsService")] | ||
interface Gala.AccountsService : Object { | ||
public abstract int prefers_accent_color { get; } | ||
} | ||
|
||
[DBus (name = "org.freedesktop.Accounts")] | ||
interface Gala.FDO.Accounts : Object { | ||
public abstract string find_user_by_name (string username) throws GLib.Error; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.