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   }
226 
227 #endif // HAVE_UNITY
228 
229 #else // _WIN32
230 
231   if(control->progress_system.taskbarlist)
232   {
233     HWND hwnd = GDK_WINDOW_HWND(gtk_widget_get_window(dt_ui_main_window(darktable.gui->ui)));
234     if(control->progress_system.n_progress_bar == 0)
235     {
236       if(ITaskbarList3_SetProgressState(control->progress_system.taskbarlist, hwnd, TBPF_NOPROGRESS) != S_OK)
237         fprintf(stderr, "[progress_create] SetProgressState failed\n");
238     }
239     else
240     {
241       if(ITaskbarList3_SetProgressValue(control->progress_system.taskbarlist, hwnd,
242                                         control->progress_system.global_progress * 100, 100) != S_OK)
243         fprintf(stderr, "[progress_create] SetProgressValue failed\n");
244     }
245   }
246 
247 #endif
248 }
249 
dt_control_progress_init(struct dt_control_t * control)250 void dt_control_progress_init(struct dt_control_t *control)
251 {
252 #ifndef _WIN32
253 
254 #ifdef HAVE_UNITY
255 
256   UnityLauncherEntry *darktable_launcher = unity_launcher_entry_get_for_desktop_id("darktable.desktop");
257   unity_launcher_entry_set_progress_visible(darktable_launcher, FALSE);
258 
259 #else
260 
261   if(darktable.dbus->dbus_connection)
262   {
263     GError *error = NULL;
264 
265     GVariantBuilder builder;
266     g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
267     g_variant_builder_add(&builder, "{sv}", "progress-visible", g_variant_new_boolean(FALSE));
268     GVariant *params = g_variant_new("(sa{sv})", "application://darktable.desktop", &builder);
269 
270     g_dbus_connection_emit_signal(darktable.dbus->dbus_connection,
271                                   "com.canonical.Unity",
272                                   "/darktable",
273                                   "com.canonical.Unity.LauncherEntry",
274                                   "Update",
275                                   params,
276                                   &error);
277     if(error)
278     {
279       fprintf(stderr, "[progress_init] dbus error: %s\n", error->message);
280       g_error_free(error);
281     }
282 
283     g_object_unref(G_OBJECT(darktable.dbus->dbus_connection));
284   }
285 
286 #endif // HAVE_UNITY
287 
288 #else // _WIN32
289 
290   // initializing control->progress_system.taskbarlist in here doesn't work,
291   // it seems to only succeed after dt_gui_gtk_init
292 
293 #endif // _WIN32
294 }
295 
dt_control_progress_create(dt_control_t * control,gboolean has_progress_bar,const gchar * message)296 dt_progress_t *dt_control_progress_create(dt_control_t *control, gboolean has_progress_bar,
297                                           const gchar *message)
298 {
299   // create the object
300   dt_progress_t *progress = (dt_progress_t *)calloc(1, sizeof(dt_progress_t));
301   dt_pthread_mutex_init(&(progress->mutex), NULL);
302 
303   // fill it with values
304   progress->message = g_strdup(message);
305   progress->has_progress_bar = has_progress_bar;
306 
307   dt_pthread_mutex_lock(&control->progress_system.mutex);
308 
309   // add it to the global list
310   control->progress_system.list = g_list_append(control->progress_system.list, progress);
311   control->progress_system.list_length++;
312   if(has_progress_bar) global_progress_start(control, progress);
313 
314   // tell the gui
315   if(control->progress_system.proxy.module != NULL)
316     progress->gui_data = control->progress_system.proxy.added(control->progress_system.proxy.module,
317                                                               has_progress_bar, message);
318 
319   dt_pthread_mutex_unlock(&control->progress_system.mutex);
320 
321   return progress;
322 }
323 
dt_control_progress_destroy(dt_control_t * control,dt_progress_t * progress)324 void dt_control_progress_destroy(dt_control_t *control, dt_progress_t *progress)
325 {
326   dt_pthread_mutex_lock(&control->progress_system.mutex);
327 
328   // tell the gui
329   if(control->progress_system.proxy.module != NULL)
330     control->progress_system.proxy.destroyed(control->progress_system.proxy.module, progress->gui_data);
331 
332   // remove the object from the global list
333   control->progress_system.list = g_list_remove(control->progress_system.list, progress);
334   control->progress_system.list_length--;
335   if(progress->has_progress_bar) global_progress_end(control, progress);
336 
337   dt_pthread_mutex_unlock(&control->progress_system.mutex);
338 
339   // free the object
340   dt_pthread_mutex_destroy(&progress->mutex);
341   g_free(progress->message);
342   free(progress);
343 }
344 
dt_control_progress_make_cancellable(struct dt_control_t * control,dt_progress_t * progress,dt_progress_cancel_callback_t cancel,void * data)345 void dt_control_progress_make_cancellable(struct dt_control_t *control, dt_progress_t *progress,
346                                           dt_progress_cancel_callback_t cancel, void *data)
347 {
348   // set the value
349   dt_pthread_mutex_lock(&progress->mutex);
350   progress->cancel = cancel;
351   progress->cancel_data = data;
352   dt_pthread_mutex_unlock(&progress->mutex);
353 
354   // tell the gui
355   dt_pthread_mutex_lock(&control->progress_system.mutex);
356   if(control->progress_system.proxy.module != NULL)
357     control->progress_system.proxy.cancellable(control->progress_system.proxy.module, progress->gui_data,
358                                                progress);
359   dt_pthread_mutex_unlock(&control->progress_system.mutex);
360 }
361 
dt_control_progress_cancel_callback(dt_progress_t * progress,void * data)362 static void dt_control_progress_cancel_callback(dt_progress_t *progress, void *data)
363 {
364   dt_control_job_cancel((dt_job_t *)data);
365 }
366 
dt_control_progress_attach_job(dt_control_t * control,dt_progress_t * progress,dt_job_t * job)367 void dt_control_progress_attach_job(dt_control_t *control, dt_progress_t *progress, dt_job_t *job)
368 {
369   dt_control_progress_make_cancellable(control, progress, &dt_control_progress_cancel_callback, job);
370 }
371 
dt_control_progress_cancel(dt_control_t * control,dt_progress_t * progress)372 void dt_control_progress_cancel(dt_control_t *control, dt_progress_t *progress)
373 {
374   dt_pthread_mutex_lock(&progress->mutex);
375   if(progress->cancel == NULL)
376   {
377     dt_pthread_mutex_unlock(&progress->mutex);
378     return;
379   }
380 
381   // call the cancel callback
382   progress->cancel(progress, progress->cancel_data);
383 
384   dt_pthread_mutex_unlock(&progress->mutex);
385 
386   // the gui doesn't need to know I guess, it wouldn't to anything with that bit of information
387 }
388 
dt_control_progress_set_progress(dt_control_t * control,dt_progress_t * progress,double value)389 void dt_control_progress_set_progress(dt_control_t *control, dt_progress_t *progress, double value)
390 {
391   // set the value
392   value = CLAMP(value, 0.0, 1.0);
393   dt_pthread_mutex_lock(&progress->mutex);
394   progress->progress = value;
395   dt_pthread_mutex_unlock(&progress->mutex);
396 
397   // tell the gui
398   dt_pthread_mutex_lock(&control->progress_system.mutex);
399   if(control->progress_system.proxy.module != NULL)
400     control->progress_system.proxy.updated(control->progress_system.proxy.module, progress->gui_data, value);
401 
402   if(progress->has_progress_bar) global_progress_set(control, progress, value);
403 
404   dt_pthread_mutex_unlock(&control->progress_system.mutex);
405 }
406 
dt_control_progress_get_progress(dt_progress_t * progress)407 double dt_control_progress_get_progress(dt_progress_t *progress)
408 {
409   dt_pthread_mutex_lock(&progress->mutex);
410   double res = progress->progress;
411   dt_pthread_mutex_unlock(&progress->mutex);
412   return res;
413 }
414 
dt_control_progress_get_message(dt_progress_t * progress)415 const gchar *dt_control_progress_get_message(dt_progress_t *progress)
416 {
417   dt_pthread_mutex_lock(&progress->mutex);
418   const gchar *res = progress->message;
419   dt_pthread_mutex_unlock(&progress->mutex);
420   return res;
421 }
422 
dt_control_progress_set_message(dt_control_t * control,dt_progress_t * progress,const char * message)423 void dt_control_progress_set_message(dt_control_t *control, dt_progress_t *progress, const char *message)
424 {
425   dt_pthread_mutex_lock(&progress->mutex);
426   g_free(progress->message);
427   progress->message = g_strdup(message);
428   dt_pthread_mutex_unlock(&progress->mutex);
429 
430   // tell the gui
431   dt_pthread_mutex_lock(&control->progress_system.mutex);
432   if(control->progress_system.proxy.module != NULL)
433     control->progress_system.proxy.message_updated(control->progress_system.proxy.module, progress->gui_data,
434                                                    message);
435   dt_pthread_mutex_unlock(&control->progress_system.mutex);
436 }
437 
dt_control_progress_set_gui_data(dt_progress_t * progress,void * data)438 void dt_control_progress_set_gui_data(dt_progress_t *progress, void *data)
439 {
440   dt_pthread_mutex_lock(&progress->mutex);
441   progress->gui_data = data;
442   dt_pthread_mutex_unlock(&progress->mutex);
443 }
444 
dt_control_progress_get_gui_data(dt_progress_t * progress)445 void *dt_control_progress_get_gui_data(dt_progress_t *progress)
446 {
447   dt_pthread_mutex_lock(&progress->mutex);
448   void *res = progress->gui_data;
449   dt_pthread_mutex_unlock(&progress->mutex);
450   return res;
451 }
452 
dt_control_progress_has_progress_bar(dt_progress_t * progress)453 gboolean dt_control_progress_has_progress_bar(dt_progress_t *progress)
454 {
455   dt_pthread_mutex_lock(&progress->mutex);
456   gboolean res = progress->has_progress_bar;
457   dt_pthread_mutex_unlock(&progress->mutex);
458   return res;
459 }
460 
dt_control_progress_cancellable(dt_progress_t * progress)461 gboolean dt_control_progress_cancellable(dt_progress_t *progress)
462 {
463   dt_pthread_mutex_lock(&progress->mutex);
464   gboolean res = progress->cancel != NULL;
465   dt_pthread_mutex_unlock(&progress->mutex);
466   return res;
467 }
468 
469 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
470 // vim: shiftwidth=2 expandtab tabstop=2 cindent
471 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
472