1 #include <wordexp.h>
2 #include <glibmm/main.h>
3 #include <gtkmm/drawingarea.h>
4 #include <gtkmm/window.h>
5 #include <gtkmm/image.h>
6 #include <gdkmm/pixbuf.h>
7 #include <gdkmm/general.h>
8 #include <gdk/gdkwayland.h>
9 
10 #include <random>
11 #include <algorithm>
12 
13 #include <iostream>
14 #include <map>
15 
16 #include <gtk-utils.hpp>
17 #include <gtk-layer-shell.h>
18 
19 #include "background.hpp"
20 
21 
show_image(Glib::RefPtr<Gdk::Pixbuf> image,double offset_x,double offset_y)22 void BackgroundDrawingArea::show_image(Glib::RefPtr<Gdk::Pixbuf> image,
23     double offset_x, double offset_y)
24 {
25     if (!image)
26     {
27         to_image.pbuf.clear();
28         from_image.pbuf.clear();
29         return;
30     }
31 
32     from_image = to_image;
33     to_image.pbuf = image;
34     to_image.x = offset_x;
35     to_image.y = offset_y;
36     fade.animate(from_image.pbuf ? 0.0 : 1.0, 1.0);
37 
38     Glib::signal_idle().connect_once([=] () {
39         this->queue_draw();
40     });
41 }
42 
on_draw(const Cairo::RefPtr<Cairo::Context> & cr)43 bool BackgroundDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
44 {
45     if (!to_image.pbuf)
46         return false;
47 
48     if (fade.running())
49         queue_draw();
50 
51     Gdk::Cairo::set_source_pixbuf(cr, to_image.pbuf, to_image.x, to_image.y);
52     cr->rectangle(0, 0, to_image.pbuf->get_width(), to_image.pbuf->get_height());
53     cr->paint_with_alpha(fade);
54 
55     if (!from_image.pbuf)
56         return false;
57 
58     Gdk::Cairo::set_source_pixbuf(cr, from_image.pbuf,
59         from_image.x, from_image.y);
60     cr->rectangle(0, 0, from_image.pbuf->get_width(),
61         from_image.pbuf->get_height());
62     cr->paint_with_alpha(1.0 - fade);
63 
64     return false;
65 }
66 
BackgroundDrawingArea()67 BackgroundDrawingArea::BackgroundDrawingArea()
68 {
69     fade.animate(0, 0);
70 }
71 
72 Glib::RefPtr<Gdk::Pixbuf>
create_from_file_safe(std::string path)73 WayfireBackground::create_from_file_safe(std::string path)
74 {
75     Glib::RefPtr<Gdk::Pixbuf> pbuf;
76     int width = window.get_allocated_width() * scale;
77     int height = window.get_allocated_height() * scale;
78 
79     try {
80         pbuf =
81             Gdk::Pixbuf::create_from_file(path, width, height,
82                 background_preserve_aspect);
83     } catch (...) {
84         return Glib::RefPtr<Gdk::Pixbuf>();
85     }
86 
87     if (background_preserve_aspect)
88     {
89         bool eq_width = (width == pbuf->get_width());
90         offset_x = eq_width ? 0 : (width - pbuf->get_width()) * 0.5;
91         offset_y = eq_width ? (height - pbuf->get_height()) * 0.5 : 0;
92     }
93     else
94     {
95         offset_x = offset_y = 0.0;
96     }
97 
98     return pbuf;
99 }
100 
change_background(int timer)101 bool WayfireBackground::change_background(int timer)
102 {
103     Glib::RefPtr<Gdk::Pixbuf> pbuf;
104     std::string path;
105 
106     if (!load_next_background(pbuf, path))
107         return false;
108 
109     std::cout << "Loaded " << path << std::endl;
110     drawing_area.show_image(pbuf, offset_x, offset_y);
111     return true;
112 }
113 
load_images_from_dir(std::string path)114 bool WayfireBackground::load_images_from_dir(std::string path)
115 {
116     wordexp_t exp;
117 
118     /* Expand path */
119     wordexp(path.c_str(), &exp, 0);
120     if (!exp.we_wordv)
121         return false;
122 
123     auto dir = opendir(exp.we_wordv[0]);
124     if (!dir)
125         return false;
126 
127     /* Iterate over all files in the directory */
128     dirent *file;
129     while ((file = readdir(dir)) != 0)
130     {
131         /* Skip hidden files and folders */
132         if (file->d_name[0] == '.')
133             continue;
134 
135         auto fullpath = std::string(exp.we_wordv[0]) + "/" + file->d_name;
136 
137         struct stat next;
138         if (stat(fullpath.c_str(), &next) == 0)
139         {
140             if (S_ISDIR(next.st_mode)) {
141                 /* Recursive search */
142                 load_images_from_dir(fullpath);
143             } else {
144                 images.push_back(fullpath);
145             }
146         }
147     }
148 
149     if (background_randomize && images.size())
150     {
151         std::random_device random_device;
152         std::mt19937 random_gen(random_device());
153         std::shuffle(images.begin(), images.end(), random_gen);
154     }
155 
156     return true;
157 }
158 
load_next_background(Glib::RefPtr<Gdk::Pixbuf> & pbuf,std::string & path)159 bool WayfireBackground::load_next_background(Glib::RefPtr<Gdk::Pixbuf> &pbuf,
160     std::string &path)
161 {
162     while (!pbuf)
163     {
164         if (!images.size())
165         {
166             std::cerr << "Failed to load background images from "
167                 << (std::string)background_image << std::endl;
168             window.remove();
169             return false;
170         }
171 
172         current_background = (current_background + 1) % images.size();
173 
174         path = images[current_background];
175         pbuf = create_from_file_safe(path);
176 
177         if (!pbuf)
178             images.erase(images.begin() + current_background);
179     }
180 
181     return true;
182 }
183 
reset_background()184 void WayfireBackground::reset_background()
185 {
186     images.clear();
187     current_background = 0;
188     change_bg_conn.disconnect();
189     scale = window.get_scale_factor();
190 }
191 
set_background()192 void WayfireBackground::set_background()
193 {
194     Glib::RefPtr<Gdk::Pixbuf> pbuf;
195 
196     reset_background();
197 
198     std::string path = background_image;
199     try {
200         if (load_images_from_dir(path) && images.size())
201         {
202             if (!load_next_background(pbuf, path))
203                 throw std::exception();
204 
205             std::cout << "Loaded " << path << std::endl;
206         }
207         else
208         {
209             pbuf = create_from_file_safe(path);
210             if (!pbuf)
211                 throw std::exception();
212         }
213     } catch (...)
214     {
215         std::cerr << "Failed to load background image(s) " << path << std::endl;
216     }
217 
218     reset_cycle_timeout();
219     drawing_area.show_image(pbuf, offset_x, offset_y);
220 
221     if (inhibited && output->output)
222     {
223         zwf_output_v2_inhibit_output_done(output->output);
224         inhibited = false;
225     }
226 }
227 
reset_cycle_timeout()228 void WayfireBackground::reset_cycle_timeout()
229 {
230     int cycle_timeout = background_cycle_timeout * 1000;
231     change_bg_conn.disconnect();
232     if (images.size())
233     {
234         change_bg_conn = Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(
235             this, &WayfireBackground::change_background), 0), cycle_timeout);
236     }
237 }
238 
setup_window()239 void WayfireBackground::setup_window()
240 {
241     window.set_decorated(false);
242 
243     gtk_layer_init_for_window(window.gobj());
244     gtk_layer_set_layer(window.gobj(), GTK_LAYER_SHELL_LAYER_BACKGROUND);
245     gtk_layer_set_monitor(window.gobj(), this->output->monitor->gobj());
246 
247     gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_TOP, true);
248     gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, true);
249     gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true);
250     gtk_layer_set_anchor(window.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true);
251 
252     gtk_layer_set_exclusive_zone(window.gobj(), -1);
253     window.add(drawing_area);
254     window.show_all();
255 
256     auto reset_background = [=] () { set_background(); };
257     auto reset_cycle = [=] () { reset_cycle_timeout(); };
258     background_image.set_callback(reset_background);
259     background_randomize.set_callback(reset_background);
260     background_preserve_aspect.set_callback(reset_background);
261     background_cycle_timeout.set_callback(reset_cycle);
262 
263     window.property_scale_factor().signal_changed().connect(
264         sigc::mem_fun(this, &WayfireBackground::set_background));
265 }
266 
WayfireBackground(WayfireShellApp * app,WayfireOutput * output)267 WayfireBackground::WayfireBackground(WayfireShellApp *app, WayfireOutput *output)
268 {
269     this->app = app;
270     this->output = output;
271 
272     if (output->output)
273     {
274         this->inhibited = true;
275         zwf_output_v2_inhibit_output(output->output);
276     }
277 
278     setup_window();
279 
280     this->window.signal_size_allocate().connect_notify(
281         [this, width = 0, height = 0] (Gtk::Allocation& alloc) mutable
282         {
283             if (alloc.get_width() != width || alloc.get_height() != height)
284             {
285                 this->set_background();
286                 width = alloc.get_width();
287                 height = alloc.get_height();
288             }
289         });
290 }
291 
292 class WayfireBackgroundApp : public WayfireShellApp
293 {
294     std::map<WayfireOutput*, std::unique_ptr<WayfireBackground> > backgrounds;
295 
296   public:
297     using WayfireShellApp::WayfireShellApp;
create(int argc,char ** argv)298     static void create(int argc, char **argv)
299     {
300         WayfireShellApp::instance =
301             std::make_unique<WayfireBackgroundApp> (argc, argv);
302         instance->run();
303     }
304 
handle_new_output(WayfireOutput * output)305     void handle_new_output(WayfireOutput *output) override
306     {
307         backgrounds[output] = std::unique_ptr<WayfireBackground> (
308             new WayfireBackground(this, output));
309     }
310 
handle_output_removed(WayfireOutput * output)311     void handle_output_removed(WayfireOutput *output) override
312     {
313         backgrounds.erase(output);
314     }
315 };
316 
main(int argc,char ** argv)317 int main(int argc, char **argv)
318 {
319     WayfireBackgroundApp::create(argc, argv);
320     return 0;
321 }
322