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