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

Add blurred background #698

Merged
merged 13 commits into from
Feb 20, 2024
63 changes: 63 additions & 0 deletions compositor/Background/Animation.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io)
* 2014 Tom Beckmann
*/

namespace GreeterCompositor {
public class Animation : Object {
public string filename { get; construct; }
public string[] key_frame_files { get; private set; default = {}; }
public double transition_progress { get; private set; default = 0.0; }
public double transition_duration { get; private set; default = 0.0; }
public bool loaded { get; private set; default = false; }

private Gnome.BGSlideShow? show = null;

public Animation (string filename) {
Object (filename: filename);
}

public async void load () {
show = new Gnome.BGSlideShow (filename);

show.load_async (null, (obj, res) => {
loaded = true;

load.callback ();
});

yield;
}

#if HAS_MUTTER45
public void update (Mtk.Rectangle monitor) {
#else
public void update (Meta.Rectangle monitor) {
#endif
string[] key_frame_files = {};

if (show == null)
return;

if (show.get_num_slides () < 1)
return;

double progress, duration;
bool is_fixed;
string file1, file2;
show.get_current_slide (monitor.width, monitor.height, out progress, out duration, out is_fixed, out file1, out file2);

transition_duration = duration;
transition_progress = progress;

if (file1 != null)
key_frame_files += file1;

if (file2 != null)
key_frame_files += file2;

this.key_frame_files = key_frame_files;
}
}
}
240 changes: 240 additions & 0 deletions compositor/Background/Background.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io)
* 2014 Tom Beckmann
*/

namespace GreeterCompositor {
public class Background : Object {
private const double ANIMATION_OPACITY_STEP_INCREMENT = 4.0;
private const double ANIMATION_MIN_WAKEUP_INTERVAL = 1.0;

public signal void changed ();
public signal void loaded ();

public Meta.Display display { get; construct; }
public int monitor_index { get; construct; }
public BackgroundSource background_source { get; construct; }
public bool is_loaded { get; private set; default = false; }
public GDesktop.BackgroundStyle style { get; construct; }
public string? filename { get; construct; }
public Meta.Background background { get; private set; }

private Animation? animation = null;
private Gee.HashMap<string,ulong> file_watches;
private Cancellable cancellable;
private uint update_animation_timeout_id = 0;

public Background (Meta.Display display, int monitor_index, string? filename,
BackgroundSource background_source, GDesktop.BackgroundStyle style) {
Object (display: display,
monitor_index: monitor_index,
background_source: background_source,
style: style,
filename: filename);
}

construct {
background = new Meta.Background (display);
background.set_data<unowned Background> ("delegate", this);

file_watches = new Gee.HashMap<string,ulong> ();
cancellable = new Cancellable ();

background_source.changed.connect (settings_changed);

load ();
}

public void destroy () {
cancellable.cancel ();
remove_animation_timeout ();

var cache = BackgroundCache.get_default ();

foreach (var watch in file_watches.values) {
cache.disconnect (watch);
}

background_source.changed.disconnect (settings_changed);
}

public void update_resolution () {
if (animation != null) {
remove_animation_timeout ();
update_animation ();
}
}

private void set_loaded () {
if (is_loaded)
return;

is_loaded = true;

Idle.add (() => {
loaded ();
return Source.REMOVE;
});
}

private void load_pattern () {
string color_string;
var settings = background_source.gnome_background_settings;

color_string = settings.get_string ("primary-color");
var color = Clutter.Color.from_string (color_string);

var shading_type = settings.get_enum ("color-shading-type");

if (shading_type == GDesktop.BackgroundShading.SOLID) {
background.set_color (color);
} else {
color_string = settings.get_string ("secondary-color");
var second_color = Clutter.Color.from_string (color_string);
background.set_gradient ((GDesktop.BackgroundShading) shading_type, color, second_color);
}
}

private void watch_file (string filename) {
if (file_watches.has_key (filename))
return;

var cache = BackgroundCache.get_default ();

cache.monitor_file (filename);

file_watches[filename] = cache.file_changed.connect ((changed_file) => {
if (changed_file == filename) {
var image_cache = Meta.BackgroundImageCache.get_default ();
image_cache.purge (File.new_for_path (changed_file));
changed ();
}
});
}

private void remove_animation_timeout () {
if (update_animation_timeout_id != 0) {
Source.remove (update_animation_timeout_id);
update_animation_timeout_id = 0;
}
}

private void finish_animation (string[] files) {
set_loaded ();

if (files.length > 1)
background.set_blend (File.new_for_path (files[0]), File.new_for_path (files[1]), animation.transition_progress, style);
else if (files.length > 0)
background.set_file (File.new_for_path (files[0]), style);
else
background.set_file (null, style);

queue_update_animation ();
}

private void update_animation () {
update_animation_timeout_id = 0;

animation.update (display.get_monitor_geometry (monitor_index));
var files = animation.key_frame_files;

var cache = Meta.BackgroundImageCache.get_default ();
var num_pending_images = files.length;
for (var i = 0; i < files.length; i++) {
watch_file (files[i]);

var image = cache.load (File.new_for_path (files[i]));

if (image.is_loaded ()) {
num_pending_images--;
if (num_pending_images == 0) {
finish_animation (files);
}
} else {
ulong handler = 0;
handler = image.loaded.connect (() => {
image.disconnect (handler);
if (--num_pending_images == 0) {
finish_animation (files);
}
});
}
}
}

private void queue_update_animation () {
if (update_animation_timeout_id != 0)
return;

if (cancellable == null || cancellable.is_cancelled ())
return;

if (animation.transition_duration == 0)
return;

var n_steps = 255.0 / ANIMATION_OPACITY_STEP_INCREMENT;
var time_per_step = (animation.transition_duration * 1000) / n_steps;

var interval = (uint32) Math.fmax (ANIMATION_MIN_WAKEUP_INTERVAL * 1000, time_per_step);

if (interval > uint32.MAX)
return;

update_animation_timeout_id = Timeout.add (interval, () => {
update_animation_timeout_id = 0;
update_animation ();
return Source.REMOVE;
});
}

private async void load_animation (string filename) {
animation = yield BackgroundCache.get_default ().get_animation (filename);

if (animation == null || cancellable.is_cancelled ()) {
set_loaded ();
return;
}

update_animation ();
watch_file (filename);
}

private void load_image (string filename) {
background.set_file (File.new_for_path (filename), style);
watch_file (filename);

var cache = Meta.BackgroundImageCache.get_default ();
var image = cache.load (File.new_for_path (filename));
if (image.is_loaded ())
set_loaded ();
else {
ulong handler = 0;
handler = image.loaded.connect (() => {
set_loaded ();
image.disconnect (handler);
});
}
}

private void load_file (string filename) {
if (filename.has_suffix (".xml"))
load_animation.begin (filename);
else
load_image (filename);
}

private void load () {
load_pattern ();

if (filename == null)
set_loaded ();
else
load_file (filename);
}

private void settings_changed () {
changed ();
}
}
}
90 changes: 90 additions & 0 deletions compositor/Background/BackgroundCache.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io)
* 2014 Tom Beckmann
*/

namespace GreeterCompositor {
public class BackgroundCache : Object {
private static BackgroundCache? instance = null;

public static unowned BackgroundCache get_default () {
if (instance == null)
instance = new BackgroundCache ();

return instance;
}

public signal void file_changed (string filename);

private Gee.HashMap<string,FileMonitor> file_monitors;
private BackgroundSource background_source;

private Animation animation;

public BackgroundCache () {
Object ();
}

construct {
file_monitors = new Gee.HashMap<string,FileMonitor> ();
}

public void monitor_file (string filename) {
if (file_monitors.has_key (filename))
return;

var file = File.new_for_path (filename);
try {
var monitor = file.monitor (FileMonitorFlags.NONE, null);
monitor.changed.connect (() => {
file_changed (filename);
});

file_monitors[filename] = monitor;
} catch (Error e) {
warning ("Failed to monitor %s: %s", filename, e.message);
}
}

public async Animation get_animation (string filename) {
if (animation != null && animation.filename == filename) {
Idle.add (() => {
get_animation.callback ();
return Source.REMOVE;
});
yield;

return animation;
}

var animation = new Animation (filename);

yield animation.load ();

Idle.add (() => {
get_animation.callback ();
return Source.REMOVE;
});
yield;

return animation;
}

public BackgroundSource get_background_source (Meta.Display display) {
if (background_source == null) {
background_source = new BackgroundSource (display);
background_source.use_count = 1;
} else
background_source.use_count++;

return background_source;
}

public void release_background_source () {
if (--background_source.use_count == 0) {
background_source.destroy ();
}
}
}
}
Loading