1 /*
2     This file is part of darktable,
3     Copyright (C) 2014-2020 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "common/dbus.h"
20 #include "control/progress.h"
21 #include "control/control.h"
22 
23 #ifdef HAVE_UNITY
24 #include <unity/unity/unity.h>
25 #endif
26 #ifdef MAC_INTEGRATION
27 #include <gtkosxapplication.h>
28 #endif
29 
30 #ifdef _WIN32
31 #include <gdk/gdkwin32.h>
32 #ifndef ITaskbarList3_SetProgressValue
33   #define ITaskbarList3_SetProgressValue(This,hwnd,ullCompleted,ullTotal) (This)->lpVtbl->SetProgressValue(This,hwnd,ullCompleted,ullTotal)
34 #endif
35 #ifndef ITaskbarList3_SetProgressState
36   #define ITaskbarList3_SetProgressState(This,hwnd,tbpFlags) (This)->lpVtbl->SetProgressState(This,hwnd,tbpFlags)
37 #endif
38 #ifndef ITaskbarList3_HrInit
39   #define ITaskbarList3_HrInit(This) (This)->lpVtbl->HrInit(This)
40 #endif
41 #endif
42 
43 
44 typedef struct _dt_progress_t
45 {
46   double progress;
47   gchar *message;
48   gboolean has_progress_bar;
49   dt_pthread_mutex_t mutex;
50   void *gui_data;
51 
52   // cancel callback and its data
53   dt_progress_cancel_callback_t cancel;
54   void *cancel_data;
55 
56 #ifdef HAVE_UNITY
57   UnityLauncherEntry *darktable_launcher;
58 #endif
59 
60 } _dt_progress_t;
61 
global_progress_start(dt_control_t * control,dt_progress_t * progress)62 static void global_progress_start(dt_control_t *control, dt_progress_t *progress)
63 {
64   control->progress_system.n_progress_bar++;
65 
66 #ifndef _WIN32
67 
68 #ifdef HAVE_UNITY
69 
70   progress->darktable_launcher = unity_launcher_entry_get_for_desktop_id("darktable.desktop");
71   unity_launcher_entry_set_progress(progress->darktable_launcher, 0.0);
72   unity_launcher_entry_set_progress_visible(progress->darktable_launcher, TRUE);
73 
74 #else
75 
76   // this should work for unity as well as kde
77   // https://wiki.ubuntu.com/Unity/LauncherAPI#Low_level_DBus_API:_com.canonical.Unity.LauncherEntry
78   if(darktable.dbus && darktable.dbus->dbus_connection)
79   {
80     GError *error = NULL;
81     g_object_ref(G_OBJECT(darktable.dbus->dbus_connection));
82 
83     GVariantBuilder builder;
84     g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
85     g_variant_builder_add(&builder, "{sv}", "progress", g_variant_new_double(control->progress_system.global_progress));
86     g_variant_builder_add(&builder, "{sv}", "progress-visible", g_variant_new_boolean(TRUE));
87     GVariant *params = g_variant_new("(sa{sv})", "application://darktable.desktop", &builder);
88 
89     g_dbus_connection_emit_signal(darktable.dbus->dbus_connection,
90                                   "com.canonical.Unity",
91                                   "/darktable",
92                                   "com.canonical.Unity.LauncherEntry",
93                                   "Update",
94                                   params,
95                                   &error);
96     if(error)
97     {
98       fprintf(stderr, "[progress_create] dbus error: %s\n", error->message);
99       g_error_free(error);
100     }
101   }
102 
103 #endif // HAVE_UNITY
104 
105 #else // _WIN32
106 
107   // we can't init this in dt_control_progress_init as it's run too early :/
108   if(!control->progress_system.taskbarlist)
109   {
110     void *taskbarlist;
111     if(CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, &IID_ITaskbarList3, (void **)&taskbarlist) == S_OK)
112       if(ITaskbarList3_HrInit((ITaskbarList3 *)taskbarlist) == S_OK)
113         control->progress_system.taskbarlist = taskbarlist;
114   }
115 
116   if(control->progress_system.taskbarlist)
117   {
118     HWND hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(dt_ui_main_window(darktable.gui->ui)));
119     if(ITaskbarList3_SetProgressState(control->progress_system.taskbarlist, hwnd, TBPF_NORMAL) != S_OK)
120       fprintf(stderr, "[progress_create] SetProgressState failed\n");
121     if(ITaskbarList3_SetProgressValue(control->progress_system.taskbarlist, hwnd, control->progress_system.global_progress * 100, 100) != S_OK)
122       fprintf(stderr, "[progress_create] SetProgressValue failed\n");
123   }
124 
125 #endif
126 }
127 
global_progress_set(dt_control_t * control,dt_progress_t * progress,double value)128 static void global_progress_set(dt_control_t *control, dt_progress_t *progress, double value)
129 {
130   control->progress_system.global_progress = MAX(control->progress_system.global_progress, value);
131 
132 #ifndef _WIN32
133 
134 #ifdef HAVE_UNITY
135 
136   unity_launcher_entry_set_progress(progress->darktable_launcher, value);
137 
138 #else
139 
140   if(darktable.dbus && darktable.dbus->dbus_connection)
141   {
142     GError *error = NULL;
143 
144     GVariantBuilder builder;
145     g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
146     g_variant_builder_add(&builder, "{sv}", "progress", g_variant_new_double(control->progress_system.global_progress));
147     GVariant *params = g_variant_new("(sa{sv})", "application://darktable.desktop", &builder);
148 
149     g_dbus_connection_emit_signal(darktable.dbus->dbus_connection,
150                                   "com.canonical.Unity",
151                                   "/darktable",
152                                   "com.canonical.Unity.LauncherEntry",
153                                   "Update",
154                                   params,
155                                   &error);
156     if(error)
157     {
158       fprintf(stderr, "[progress_set] dbus error: %s\n", error->message);
159       g_error_free(error);
160     }
161   }
162 
163 #endif // HAVE_UNITY
164 
165 #else // _WIN32
166 
167   if(control->progress_system.taskbarlist)
168   {
169     HWND hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(dt_ui_main_window(darktable.gui->ui)));
170     if(ITaskbarList3_SetProgressValue(control->progress_system.taskbarlist, hwnd, control->progress_system.global_progress * 100, 100) != S_OK)
171       fprintf(stderr, "[progress_create] SetProgressValue failed\n");
172   }
173 
174 #endif
175 }
176 
global_progress_end(dt_control_t * control,dt_progress_t * progress)177 static void global_progress_end(dt_control_t *control, dt_progress_t *progress)
178 {
179   control->progress_system.n_progress_bar--;
180 
181   // find the biggest progress value among the remaining progress bars
182   control->progress_system.global_progress = 0.0;
183   for(GList *iter = control->progress_system.list; iter; iter = g_list_next(iter))
184   {
185     // this is called after the current progress got removed from the list!
186     dt_progress_t *p = (dt_progress_t *)iter->data;
187     const double value = dt_control_progress_get_progress(p);
188     control->progress_system.global_progress = MAX(control->progress_system.global_progress, value);
189   }
190 
191 #ifndef _WIN32
192 
193 #ifdef HAVE_UNITY
194 
195   unity_launcher_entry_set_progress(progress->darktable_launcher, 1.0);
196   unity_launcher_entry_set_progress_visible(progress->darktable_launcher, FALSE);
197 
198 #else
199 
200   if(darktable.dbus && darktable.dbus->dbus_connection)
201   {
202     GError *error = NULL;
203 
204     GVariantBuilder builder;
205     g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
206     if(control->progress_system.n_progress_bar == 0)
207       g_variant_builder_add(&builder, "{sv}", "progress-visible", g_variant_new_boolean(FALSE));
208     g_variant_builder_add(&builder, "{sv}", "progress", g_variant_new_double(control->progress_system.global_progress));
209     GVariant *params = g_variant_new("(sa{sv})", "application://darktable.desktop", &builder);
210 
211     g_dbus_connection_emit_signal(darktable.dbus->dbus_connection,
212                                   "com.canonical.Unity",
213                                   "/darktable",
214                                   "com.canonical.Unity.LauncherEntry",
215                                   "Update",
216                                   params,
217                                   &error);
218     if(error)
219     {
220       fprintf(stderr, "[progress_destroy] dbus error: %s\n", error->message);
221       g_error_free(error);
222     }
223 
224     g_object_unref(G_OBJECT(darktable.dbus->dbus_connection));
225     darktable.dbus->dbus_connection = NULL;
226   }
227 
228 #endif // HAVE_UNITY
229 
230 #else // _WIN32
231 
232   if(control->progress_system.taskbarlist)
233   {
234     HWND hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(dt_ui_main_window(darktable.gui->ui)));
235     if(control->progress_system.n_progress_bar == 0)
236     {
237       if(ITaskbarList3_SetProgressState(control->progress_system.taskbarlist, hwnd, TBPF_NOPROGRESS) != S_OK)
238         fprintf(stderr, "[progress_create] SetProgressState failed\n");
239     }
240     else
241     {
242       if(ITaskbarList3_SetProgressValue(control->progress_system.taskbarlist, hwnd,
243                                         control->progress_system.global_progress * 100, 100) != S_OK)
244         fprintf(stderr, "[progress_create] SetProgressValue failed\n");
245     }
246   }
247 
248 #endif
249 }
250 
dt_control_progress_init(struct dt_control_t * control)251 void dt_control_progress_init(struct dt_control_t *control)
252 {
253 #ifndef _WIN32
254 
255 #ifdef HAVE_UNITY
256 
257   UnityLauncherEntry *darktable_launcher = unity_launcher_entry_get_for_desktop_id("darktable.desktop");
258   unity_launcher_entry_set_progress_visible(darktable_launcher, FALSE);
259 
260 #else
261 
262   if(darktable.dbus->dbus_connection)
263   {
264     GError *error = NULL;
265 
266     GVariantBuilder builder;
267     g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
268     g_variant_builder_add(&builder, "{sv}", "progress-visible", g_variant_new_boolean(FALSE));
269     GVariant *params = g_variant_new("(sa{sv})", "application://darktable.desktop", &builder);
270 
271     g_dbus_connection_emit_signal(darktable.dbus->dbus_connection,
272                                   "com.canonical.Unity",
273                                   "/darktable",
274                                   "com.canonical.Unity.LauncherEntry",
275                                   "Update",
276                                   params,
277                                   &error);
278     if(error)
279     {
280       fprintf(stderr, "[progress_init] dbus error: %s\n", error->message);
281       g_error_free(error);
282     }
283 
284     g_object_unref(G_OBJECT(darktable.dbus->dbus_connection));
285     darktable.dbus->dbus_connection = NULL;
286   }
287 
288 #endif // HAVE_UNITY
289 
290 #else // _WIN32
291 
292   // initializing control->progress_system.taskbarlist in here doesn't work,
293   // it seems to only succeed after dt_gui_gtk_init
294 
295 #endif // _WIN32
296 }
297 
dt_control_progress_create(dt_control_t * control,gboolean has_progress_bar,const gchar * message)298 dt_progress_t *dt_control_progress_create(dt_control_t *control, gboolean has_progress_bar,
299                                           const gchar *message)
300 {
301   // create the object
302   dt_progress_t *progress = (dt_progress_t *)calloc(1, sizeof(dt_progress_t));
303   dt_pthread_mutex_init(&(progress->mutex), NULL);
304 
305   // fill it with values
306   progress->message = g_strdup(message);
307   progress->has_progress_bar = has_progress_bar;
308 
309   dt_pthread_mutex_lock(&control->progress_system.mutex);
310 
311   // add it to the global list
312   control->progress_system.list = g_list_append(control->progress_system.list, progress);
313   control->progress_system.list_length++;
314   if(has_progress_bar) global_progress_start(control, progress);
315 
316   // tell the gui
317   if(control->progress_system.proxy.module != NULL)
318     progress->gui_data = control->progress_system.proxy.added(control->progress_system.proxy.module,
319                                                               has_progress_bar, message);
320 
321   dt_pthread_mutex_unlock(&control->progress_system.mutex);
322 
323   return progress;
324 }
325 
dt_control_progress_destroy(dt_control_t * control,dt_progress_t * progress)326 void dt_control_progress_destroy(dt_control_t *control, dt_progress_t *progress)
327 {
328   dt_pthread_mutex_lock(&control->progress_system.mutex);
329 
330   // tell the gui
331   if(control->progress_system.proxy.module != NULL)
332     control->progress_system.proxy.destroyed(control->progress_system.proxy.module, progress->gui_data);
333 
334   // remove the object from the global list
335   control->progress_system.list = g_list_remove(control->progress_system.list, progress);
336   control->progress_system.list_length--;
337   if(progress->has_progress_bar) global_progress_end(control, progress);
338 
339   dt_pthread_mutex_unlock(&control->progress_system.mutex);
340 
341   // free the object
342   dt_pthread_mutex_destroy(&progress->mutex);
343   g_free(progress->message);
344   free(progress);
345 }
346 
dt_control_progress_make_cancellable(struct dt_control_t * control,dt_progress_t * progress,dt_progress_cancel_callback_t cancel,void * data)347 void dt_control_progress_make_cancellable(struct dt_control_t *control, dt_progress_t *progress,
348                                           dt_progress_cancel_callback_t cancel, void *data)
349 {
350   // set the value
351   dt_pthread_mutex_lock(&progress->mutex);
352   progress->cancel = cancel;
353   progress->cancel_data = data;
354   dt_pthread_mutex_unlock(&progress->mutex);
355 
356   // tell the gui
357   dt_pthread_mutex_lock(&control->progress_system.mutex);
358   if(control->progress_system.proxy.module != NULL)
359     control->progress_system.proxy.cancellable(control->progress_system.proxy.module, progress->gui_data,
360                                                progress);
361   dt_pthread_mutex_unlock(&control->progress_system.mutex);
362 }
363 
dt_control_progress_cancel_callback(dt_progress_t * progress,void * data)364 static void dt_control_progress_cancel_callback(dt_progress_t *progress, void *data)
365 {
366   dt_control_job_cancel((dt_job_t *)data);
367 }
368 
dt_control_progress_attach_job(dt_control_t * control,dt_progress_t * progress,dt_job_t * job)369 void dt_control_progress_attach_job(dt_control_t *control, dt_progress_t *progress, dt_job_t *job)
370 {
371   dt_control_progress_make_cancellable(control, progress, &dt_control_progress_cancel_callback, job);
372 }
373 
dt_control_progress_cancel(dt_control_t * control,dt_progress_t * progress)374 void dt_control_progress_cancel(dt_control_t *control, dt_progress_t *progress)
375 {
376   dt_pthread_mutex_lock(&progress->mutex);
377   if(progress->cancel == NULL)
378   {
379     dt_pthread_mutex_unlock(&progress->mutex);
380     return;
381   }
382 
383   // call the cancel callback
384   progress->cancel(progress, progress->cancel_data);
385 
386   dt_pthread_mutex_unlock(&progress->mutex);
387 
388   // the gui doesn't need to know I guess, it wouldn't to anything with that bit of information
389 }
390 
dt_control_progress_set_progress(dt_control_t * control,dt_progress_t * progress,double value)391 void dt_control_progress_set_progress(dt_control_t *control, dt_progress_t *progress, double value)
392 {
393   // set the value
394   value = CLAMP(value, 0.0, 1.0);
395   dt_pthread_mutex_lock(&progress->mutex);
396   progress->progress = value;
397   dt_pthread_mutex_unlock(&progress->mutex);
398 
399   // tell the gui
400   dt_pthread_mutex_lock(&control->progress_system.mutex);
401   if(control->progress_system.proxy.module != NULL)
402     control->progress_system.proxy.updated(control->progress_system.proxy.module, progress->gui_data, value);
403 
404   if(progress->has_progress_bar) global_progress_set(control, progress, value);
405 
406   dt_pthread_mutex_unlock(&control->progress_system.mutex);
407 }
408 
dt_control_progress_get_progress(dt_progress_t * progress)409 double dt_control_progress_get_progress(dt_progress_t *progress)
410 {
411   dt_pthread_mutex_lock(&progress->mutex);
412   double res = progress->progress;
413   dt_pthread_mutex_unlock(&progress->mutex);
414   return res;
415 }
416 
dt_control_progress_get_message(dt_progress_t * progress)417 const gchar *dt_control_progress_get_message(dt_progress_t *progress)
418 {
419   dt_pthread_mutex_lock(&progress->mutex);
420   const gchar *res = progress->message;
421   dt_pthread_mutex_unlock(&progress->mutex);
422   return res;
423 }
424 
dt_control_progress_set_message(dt_control_t * control,dt_progress_t * progress,const char * message)425 void dt_control_progress_set_message(dt_control_t *control, dt_progress_t *progress, const char *message)
426 {
427   dt_pthread_mutex_lock(&progress->mutex);
428   g_free(progress->message);
429   progress->message = g_strdup(message);
430   dt_pthread_mutex_unlock(&progress->mutex);
431 
432   // tell the gui
433   dt_pthread_mutex_lock(&control->progress_system.mutex);
434   if(control->progress_system.proxy.module != NULL)
435     control->progress_system.proxy.message_updated(control->progress_system.proxy.module, progress->gui_data,
436                                                    message);
437   dt_pthread_mutex_unlock(&control->progress_system.mutex);
438 }
439 
dt_control_progress_set_gui_data(dt_progress_t * progress,void * data)440 void dt_control_progress_set_gui_data(dt_progress_t *progress, void *data)
441 {
442   dt_pthread_mutex_lock(&progress->mutex);
443   progress->gui_data = data;
444   dt_pthread_mutex_unlock(&progress->mutex);
445 }
446 
dt_control_progress_get_gui_data(dt_progress_t * progress)447 void *dt_control_progress_get_gui_data(dt_progress_t *progress)
448 {
449   dt_pthread_mutex_lock(&progress->mutex);
450   void *res = progress->gui_data;
451   dt_pthread_mutex_unlock(&progress->mutex);
452   return res;
453 }
454 
dt_control_progress_has_progress_bar(dt_progress_t * progress)455 gboolean dt_control_progress_has_progress_bar(dt_progress_t *progress)
456 {
457   dt_pthread_mutex_lock(&progress->mutex);
458   gboolean res = progress->has_progress_bar;
459   dt_pthread_mutex_unlock(&progress->mutex);
460   return res;
461 }
462 
dt_control_progress_cancellable(dt_progress_t * progress)463 gboolean dt_control_progress_cancellable(dt_progress_t *progress)
464 {
465   dt_pthread_mutex_lock(&progress->mutex);
466   gboolean res = progress->cancel != NULL;
467   dt_pthread_mutex_unlock(&progress->mutex);
468   return res;
469 }
470 
471 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
472 // vim: shiftwidth=2 expandtab tabstop=2 cindent
473 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
474