mirror of
https://github.com/ErikReider/SwayNotificationCenter.git
synced 2024-11-22 17:54:08 +03:00
Add MPRIS player background blur (#355)
* Add MPRIS player background blur * Added edge fade * Only blur cover if the size or source changes
This commit is contained in:
parent
1fcef5c966
commit
ff04ca3924
2
.github/workflows/ubuntu-build.yml
vendored
2
.github/workflows/ubuntu-build.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
PACKAGES: meson libwayland-dev libgtk-3-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libhandy-1-dev libgtk-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc
|
||||
PACKAGES: meson libwayland-dev libgtk-3-dev gobject-introspection libgirepository1.0-dev valac libjson-glib-dev libhandy-1-dev libgtk-layer-shell-dev scdoc libgee-0.8-dev libpulse-dev sassc libgranite-dev
|
||||
steps:
|
||||
- name: Install packages
|
||||
run: |
|
||||
|
@ -12,7 +12,7 @@ arch=(
|
||||
'armv7h' # ARM v7 hardfloat
|
||||
)
|
||||
license=('GPL3')
|
||||
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" "gvfs" "libnotify")
|
||||
depends=("gtk3" "gtk-layer-shell" "dbus" "glib2" "gobject-introspection" "libgee" "json-glib" "libhandy" "libpulse" "gvfs" "libnotify" "granite")
|
||||
conflicts=("swaync" "swaync-client")
|
||||
provides=("swaync" "swaync-client" "notification-daemon")
|
||||
makedepends=("vala>=0.56" meson git scdoc sassc)
|
||||
|
@ -29,6 +29,7 @@ BuildRequires: pkgconfig(libpulse)
|
||||
BuildRequires: systemd-devel
|
||||
BuildRequires: systemd
|
||||
BuildRequires: sassc
|
||||
BuildRequires: granite-devel
|
||||
|
||||
Requires: gvfs
|
||||
Requires: libnotify
|
||||
|
@ -19,6 +19,7 @@ assert(sassc.found())
|
||||
style_css = custom_target(
|
||||
'SCSS Compilation',
|
||||
build_by_default: true,
|
||||
build_always_stale: true,
|
||||
input : 'style/style.scss',
|
||||
output : 'style.css',
|
||||
install: true,
|
||||
|
@ -1,14 +1,47 @@
|
||||
@define-color mpris-album-art-overlay rgba(0, 0, 0, 0.55);
|
||||
@define-color mpris-button-hover rgba(0, 0, 0, 0.50);
|
||||
|
||||
$mpris-shadow: 0px 0px 10px rgba(0, 0, 0, 0.75);
|
||||
|
||||
.widget-mpris {
|
||||
/* The parent to all players */
|
||||
}
|
||||
.widget-mpris-player {
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
.widget-mpris-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.widget-mpris-subtitle {
|
||||
font-size: 1.1rem;
|
||||
.widget-mpris-player {
|
||||
padding: 8px;
|
||||
padding: 16px;
|
||||
margin: 16px 20px;
|
||||
background-color: #{"@mpris-album-art-overlay"};
|
||||
border-radius: $border-radius;
|
||||
box-shadow: $mpris-shadow;
|
||||
|
||||
button:hover {
|
||||
/* The media player buttons (play, pause, next, etc...) */
|
||||
background: #{"@noti-bg-hover"};
|
||||
}
|
||||
|
||||
.widget-mpris-album-art {
|
||||
border-radius: $border-radius;
|
||||
box-shadow: $mpris-shadow;
|
||||
}
|
||||
.widget-mpris-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.widget-mpris-subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
& > box > button {
|
||||
/* Change player control buttons */
|
||||
&:hover {
|
||||
background-color: #{"@mpris-button-hover"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > box > button {
|
||||
/* Change player side buttons */
|
||||
}
|
||||
& > box > button:disabled {
|
||||
/* Change player side buttons insensitive */
|
||||
}
|
||||
}
|
||||
|
@ -402,8 +402,14 @@
|
||||
},
|
||||
"image-radius": {
|
||||
"type": "integer",
|
||||
"description": "The border radius of the album art",
|
||||
"description": "The border radius of the album art. Will be overriden by setting the border-radius in the style.css for the \".widget-mpris-album-art\" class",
|
||||
|
||||
"default": 12
|
||||
},
|
||||
"blur": {
|
||||
"type": "bool",
|
||||
"description": "Appy the artwork as the MPRIS background and blur it",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2,6 +2,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
public struct Config {
|
||||
int image_size;
|
||||
int image_radius;
|
||||
bool blur;
|
||||
}
|
||||
|
||||
public class Mpris : BaseWidget {
|
||||
@ -11,6 +12,8 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
}
|
||||
}
|
||||
|
||||
private const int FADE_WIDTH = 20;
|
||||
|
||||
const string MPRIS_PREFIX = "org.mpris.MediaPlayer2.";
|
||||
HashTable<string, MprisPlayer> players = new HashTable<string, MprisPlayer> (str_hash, str_equal);
|
||||
|
||||
@ -26,6 +29,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
Config mpris_config = Config () {
|
||||
image_size = 96,
|
||||
image_radius = 12,
|
||||
blur = true,
|
||||
};
|
||||
|
||||
public Mpris (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) {
|
||||
@ -56,6 +60,7 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
#if HAVE_LATEST_LIBHANDY
|
||||
carousel.allow_scroll_wheel = true;
|
||||
#endif
|
||||
carousel.draw.connect (carousel_draw_cb);
|
||||
carousel.page_changed.connect ((index) => {
|
||||
GLib.List<weak Gtk.Widget> children = carousel.get_children ();
|
||||
int children_length = (int) children.length ();
|
||||
@ -91,6 +96,11 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
// Clamp the radius
|
||||
mpris_config.image_radius = mpris_config.image_radius.clamp (
|
||||
0, (int) (mpris_config.image_size * 0.5));
|
||||
|
||||
// Get blur
|
||||
bool blur_found;
|
||||
bool? blur = get_prop<bool> (config, "blur", out blur_found);
|
||||
if (blur_found) mpris_config.blur = blur;
|
||||
}
|
||||
|
||||
hide ();
|
||||
@ -101,6 +111,51 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
}
|
||||
}
|
||||
|
||||
private bool carousel_draw_cb (Cairo.Context cr) {
|
||||
Gtk.Allocation alloc;
|
||||
carousel.get_allocated_size (out alloc, null);
|
||||
|
||||
Cairo.Pattern left_fade_gradient = new Cairo.Pattern.linear (0, 0, 1, 0);
|
||||
left_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 1);
|
||||
left_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 0);
|
||||
Cairo.Pattern right_fade_gradient = new Cairo.Pattern.linear (0, 0, 1, 0);
|
||||
right_fade_gradient.add_color_stop_rgba (0, 1, 1, 1, 0);
|
||||
right_fade_gradient.add_color_stop_rgba (1, 1, 1, 1, 1);
|
||||
|
||||
cr.save ();
|
||||
cr.push_group ();
|
||||
|
||||
// Draw widgets
|
||||
carousel.draw.disconnect (carousel_draw_cb);
|
||||
carousel.draw (cr);
|
||||
carousel.draw.connect (carousel_draw_cb);
|
||||
|
||||
/// Draw vertical fade
|
||||
|
||||
// Top fade
|
||||
cr.save ();
|
||||
cr.scale (FADE_WIDTH, alloc.height);
|
||||
cr.rectangle (0, 0, FADE_WIDTH, alloc.height);
|
||||
cr.set_source (left_fade_gradient);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
// Bottom fade
|
||||
cr.save ();
|
||||
cr.translate (alloc.width - FADE_WIDTH, 0);
|
||||
cr.scale (FADE_WIDTH, alloc.height);
|
||||
cr.rectangle (0, 0, FADE_WIDTH, alloc.height);
|
||||
cr.set_source (right_fade_gradient);
|
||||
cr.set_operator (Cairo.Operator.DEST_OUT);
|
||||
cr.fill ();
|
||||
cr.restore ();
|
||||
|
||||
cr.pop_group_to_source ();
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the carousel to reload its style_context.
|
||||
* Fixes carousel items not redrawing when window isn't visible.
|
||||
|
@ -22,6 +22,9 @@
|
||||
<property name="icon-name">audio-x-generic-symbolic</property>
|
||||
<property name="use-fallback">True</property>
|
||||
<property name="icon_size">0</property>
|
||||
<style>
|
||||
<class name="widget-mpris-album-art"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -38,6 +38,9 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
private string prev_art_url;
|
||||
private DesktopAppInfo ? desktop_entry = null;
|
||||
|
||||
private Gdk.Pixbuf ? album_art_pixbuf = null;
|
||||
private Granite.Drawing.BufferSurface ? blurred_cover_surface = null;
|
||||
|
||||
private unowned Config mpris_config;
|
||||
|
||||
public MprisPlayer (MprisSource source, Config mpris_config) {
|
||||
@ -99,6 +102,77 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
source.properties_changed.disconnect (properties_changed);
|
||||
}
|
||||
|
||||
public override bool draw (Cairo.Context cr) {
|
||||
unowned Gdk.Window ? window = get_window ();
|
||||
if (!mpris_config.blur || window == null ||
|
||||
!(album_art_pixbuf is Gdk.Pixbuf)) {
|
||||
return base.draw (cr);
|
||||
}
|
||||
|
||||
const double DEGREES = Math.PI / 180.0;
|
||||
unowned Gtk.StyleContext style_ctx = this.get_style_context ();
|
||||
unowned Gtk.StateFlags state = style_ctx.get_state ();
|
||||
Gtk.Border border = style_ctx.get_border (state);
|
||||
Gtk.Border margin = style_ctx.get_margin (state);
|
||||
Value radius_value = style_ctx.get_property (
|
||||
Gtk.STYLE_PROPERTY_BORDER_RADIUS, state);
|
||||
int radius = 0;
|
||||
if (!radius_value.holds (Type.INT) ||
|
||||
(radius = radius_value.get_int ()) == 0) {
|
||||
radius = mpris_config.image_radius;
|
||||
}
|
||||
int scale = style_ctx.get_scale ();
|
||||
int width = get_allocated_width ()
|
||||
- margin.left - margin.right
|
||||
- border.left - border.right;
|
||||
int height = get_allocated_height ()
|
||||
- margin.top - margin.bottom
|
||||
- border.top - border.bottom;
|
||||
|
||||
cr.save ();
|
||||
cr.new_sub_path ();
|
||||
// Top Right
|
||||
cr.arc (width - radius + margin.right,
|
||||
radius + margin.top,
|
||||
radius, -90 * DEGREES, 0 * DEGREES);
|
||||
// Bottom Right
|
||||
cr.arc (width - radius + margin.right,
|
||||
height - radius + margin.bottom,
|
||||
radius, 0 * DEGREES, 90 * DEGREES);
|
||||
// Bottom Left
|
||||
cr.arc (radius + margin.left,
|
||||
height - radius + margin.bottom,
|
||||
radius, 90 * DEGREES, 180 * DEGREES);
|
||||
// Top Left
|
||||
cr.arc (radius + margin.left,
|
||||
radius + margin.top,
|
||||
radius, 180 * DEGREES, 270 * DEGREES);
|
||||
cr.close_path ();
|
||||
|
||||
cr.set_source_rgba (0, 0, 0, 0);
|
||||
cr.clip ();
|
||||
|
||||
// Draw blurred player background
|
||||
if (blurred_cover_surface == null
|
||||
|| blurred_cover_surface.width != width
|
||||
|| blurred_cover_surface.height != height) {
|
||||
var buffer = new Granite.Drawing.BufferSurface (width, height);
|
||||
Cairo.Surface surface = Functions.scale_pixbuf (
|
||||
album_art_pixbuf, width, height, scale);
|
||||
|
||||
buffer.context.set_source_surface (surface, 0, 0);
|
||||
buffer.context.paint ();
|
||||
buffer.fast_blur (8, 2);
|
||||
blurred_cover_surface = buffer;
|
||||
}
|
||||
cr.set_source_surface (blurred_cover_surface.surface, margin.left, margin.top);
|
||||
cr.paint ();
|
||||
|
||||
cr.restore ();
|
||||
|
||||
return base.draw (cr);
|
||||
}
|
||||
|
||||
private void properties_changed (string iface,
|
||||
HashTable<string, Variant> changed,
|
||||
string[] invalid) {
|
||||
@ -246,7 +320,6 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
|
||||
int scale = get_style_context ().get_scale ();
|
||||
|
||||
Gdk.Pixbuf ? pixbuf = null;
|
||||
// Cancel previous download, reset the state and download again
|
||||
album_art_cancellable.cancel ();
|
||||
album_art_cancellable.reset ();
|
||||
@ -255,24 +328,45 @@ namespace SwayNotificationCenter.Widgets.Mpris {
|
||||
InputStream stream = yield file.read_async (Priority.DEFAULT,
|
||||
album_art_cancellable);
|
||||
|
||||
pixbuf = yield new Gdk.Pixbuf.from_stream_async (
|
||||
this.album_art_pixbuf = yield new Gdk.Pixbuf.from_stream_async (
|
||||
stream, album_art_cancellable);
|
||||
} catch (Error e) {
|
||||
debug ("Could not download album art for %s. Using fallback...",
|
||||
source.media_player.identity);
|
||||
}
|
||||
if (pixbuf != null) {
|
||||
var surface = Functions.scale_round_pixbuf (pixbuf,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size,
|
||||
scale,
|
||||
mpris_config.image_radius);
|
||||
pixbuf = Gdk.pixbuf_get_from_surface (surface,
|
||||
0, 0,
|
||||
if (this.album_art_pixbuf != null) {
|
||||
unowned Gtk.StyleContext style_ctx = album_art.get_style_context ();
|
||||
Value br_value =
|
||||
style_ctx.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS,
|
||||
style_ctx.get_state ());
|
||||
int radius = 0;
|
||||
if (!br_value.holds (Type.INT) ||
|
||||
(radius = br_value.get_int ()) == 0) {
|
||||
radius = mpris_config.image_radius;
|
||||
}
|
||||
var surface = Functions.scale_pixbuf (this.album_art_pixbuf,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size);
|
||||
album_art.set_from_pixbuf (pixbuf);
|
||||
mpris_config.image_size,
|
||||
scale);
|
||||
this.album_art_pixbuf = Gdk.pixbuf_get_from_surface (surface,
|
||||
0, 0,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size);
|
||||
this.blurred_cover_surface = null;
|
||||
surface = Functions.round_surface (surface,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size,
|
||||
scale,
|
||||
radius);
|
||||
var pix_buf = Gdk.pixbuf_get_from_surface (surface,
|
||||
0, 0,
|
||||
mpris_config.image_size,
|
||||
mpris_config.image_size);
|
||||
surface.finish ();
|
||||
album_art.set_from_pixbuf (pix_buf);
|
||||
album_art.get_style_context ().set_scale (1);
|
||||
this.queue_draw ();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -209,12 +209,12 @@ namespace SwayNotificationCenter {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Scales the pixbuf to fit the given dimensions */
|
||||
public static Cairo.Surface scale_round_pixbuf (Gdk.Pixbuf pixbuf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale,
|
||||
int radius) {
|
||||
/** Roundes the Cairo Surface to the given radii */
|
||||
public static Cairo.Surface round_surface (Cairo.Surface base_surf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale,
|
||||
int radius) {
|
||||
// Limit radii size
|
||||
radius = int.min (radius, int.min (buffer_width / 2, buffer_height / 2));
|
||||
|
||||
@ -236,7 +236,26 @@ namespace SwayNotificationCenter {
|
||||
cr.paint ();
|
||||
|
||||
cr.save ();
|
||||
Cairo.Surface scale_surf = Gdk.cairo_surface_create_from_pixbuf (pixbuf,
|
||||
cr.set_source_surface (base_surf, 0, 0);
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
/** Scales the pixbuf to fit the given dimensions */
|
||||
public static Cairo.Surface scale_pixbuf (Gdk.Pixbuf pixbuf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale) {
|
||||
|
||||
Cairo.Surface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32,
|
||||
buffer_width,
|
||||
buffer_height);
|
||||
var cr = new Cairo.Context (surface);
|
||||
|
||||
cr.save ();
|
||||
Cairo.Surface base_surf = Gdk.cairo_surface_create_from_pixbuf (pixbuf,
|
||||
img_scale,
|
||||
null);
|
||||
int width = pixbuf.width / img_scale;
|
||||
@ -246,22 +265,40 @@ namespace SwayNotificationCenter {
|
||||
if (window_ratio > bg_ratio) { // Taller wallpaper than monitor
|
||||
double scale = (double) buffer_width / width;
|
||||
if (scale * height < buffer_height) {
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
} else {
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
}
|
||||
} else { // Wider wallpaper than monitor
|
||||
double scale = (double) buffer_height / height;
|
||||
if (scale * width < buffer_width) {
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_tall (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
} else {
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, scale_surf);
|
||||
draw_scale_wide (buffer_width, width, buffer_height, height, cr, base_surf);
|
||||
}
|
||||
}
|
||||
cr.paint ();
|
||||
cr.restore ();
|
||||
|
||||
scale_surf.finish ();
|
||||
base_surf.finish ();
|
||||
return surface;
|
||||
}
|
||||
|
||||
/** Scales the pixbuf to fit the given dimensions */
|
||||
public static Cairo.Surface scale_round_pixbuf (Gdk.Pixbuf pixbuf,
|
||||
int buffer_width,
|
||||
int buffer_height,
|
||||
int img_scale,
|
||||
int radius) {
|
||||
var surface = Functions.scale_pixbuf (pixbuf,
|
||||
buffer_width,
|
||||
buffer_height,
|
||||
img_scale);
|
||||
surface = Functions.round_surface (surface,
|
||||
buffer_width,
|
||||
buffer_height,
|
||||
img_scale,
|
||||
radius);
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,7 @@ app_deps = [
|
||||
dependency('gtk+-3.0', version: '>= 3.22'),
|
||||
dependency('json-glib-1.0', version: '>= 1.0'),
|
||||
dependency('libhandy-1', version: '>= 1.2.3'),
|
||||
dependency('granite', version: '>= 6.2.0'),
|
||||
meson.get_compiler('c').find_library('gtk-layer-shell'),
|
||||
meson.get_compiler('c').find_library('m', required : true),
|
||||
meson.get_compiler('vala').find_library('posix'),
|
||||
|
Loading…
Reference in New Issue
Block a user