1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * callbacks.c
4  * Copyright (C) John Stebbins 2008-2021 <stebbins@stebbins>
5  *
6  * callbacks.c is free software.
7  *
8  * You may redistribute it and/or modify it under the terms of the
9  * GNU General Public License version 2, as published by the Free Software
10  * Foundation.
11  *
12  * callbacks.c is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  * See the GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with main.c.  If not, write to:
19  *  The Free Software Foundation, Inc.,
20  *  51 Franklin Street, Fifth Floor
21  *  Boston, MA  02110-1301, USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27 
28 #include <string.h>
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <time.h>
32 
33 #include <glib/gstdio.h>
34 #include <glib/gi18n.h>
35 #include <gio/gio.h>
36 
37 #include "ghbcompat.h"
38 
39 #if !defined(_WIN32)
40 #include <poll.h>
41 #define G_UDEV_API_IS_SUBJECT_TO_CHANGE 1
42 #if defined(__linux__) && defined(_HAVE_GUDEV)
43 #include <gudev/gudev.h>
44 #endif
45 
46 #if defined( __FreeBSD__ )
47 #include <sys/socket.h>
48 #endif
49 #include <netinet/in.h>
50 #include <netdb.h>
51 
52 #if !defined(_NO_UPDATE_CHECK)
53 #if defined(_OLD_WEBKIT)
54 #include <webkit.h>
55 #else
56 #include <webkit/webkit.h>
57 #endif
58 #endif
59 
60 #ifndef NOTIFY_CHECK_VERSION
61 #define NOTIFY_CHECK_VERSION(x,y,z) 0
62 #endif
63 
64 #if !GTK_CHECK_VERSION(3, 4, 0)
65 #include <gdk/gdkx.h>
66 #endif
67 
68 #ifndef NOTIFY_CHECK_VERSION
69 #define NOTIFY_CHECK_VERSION(x,y,z) 0
70 #endif
71 #else
72 #include <winsock2.h>
73 #include <dbt.h>
74 #endif
75 
76 #include "handbrake/handbrake.h"
77 #include "callbacks.h"
78 #include "chapters.h"
79 #include "queuehandler.h"
80 #include "audiohandler.h"
81 #include "subtitlehandler.h"
82 #include "resources.h"
83 #include "settings.h"
84 #include "jobdict.h"
85 #include "presets.h"
86 #include "preview.h"
87 #include "values.h"
88 #include "plist.h"
89 #include "appcast.h"
90 #include "hb-backend.h"
91 #include "ghb-dvd.h"
92 #include "libavutil/parseutils.h"
93 
94 static void update_queue_labels(signal_user_data_t *ud);
95 static void load_all_titles(signal_user_data_t *ud, int titleindex);
96 static GList* dvd_device_list();
97 static void prune_logs(signal_user_data_t *ud);
98 void ghb_notify_done(signal_user_data_t *ud);
99 gpointer ghb_check_update(signal_user_data_t *ud);
100 static gboolean ghb_can_shutdown_gsm();
101 static void ghb_shutdown_gsm();
102 static gboolean ghb_can_suspend_gpm();
103 static void ghb_suspend_gpm();
104 static gboolean appcast_busy = FALSE;
105 
106 #if !defined(_WIN32)
107 #define GPM_DBUS_PM_SERVICE         "org.freedesktop.PowerManagement"
108 #define GPM_DBUS_PM_PATH            "/org/freedesktop/PowerManagement"
109 #define GPM_DBUS_PM_INTERFACE       "org.freedesktop.PowerManagement"
110 #define GPM_DBUS_INHIBIT_PATH       "/org/freedesktop/PowerManagement/Inhibit"
111 #define GPM_DBUS_INHIBIT_INTERFACE  "org.freedesktop.PowerManagement.Inhibit"
112 #endif
113 
114 #if !defined(_WIN32)
115 static GDBusProxy *
ghb_get_session_dbus_proxy(const gchar * name,const gchar * path,const gchar * interface)116 ghb_get_session_dbus_proxy(const gchar *name, const gchar *path, const gchar *interface)
117 {
118     GDBusConnection *conn;
119     GDBusProxy *proxy;
120     GError *error = NULL;
121 
122     g_debug("ghb_get_session_dbus_proxy()");
123     conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
124     if (conn == NULL)
125     {
126         if (error != NULL)
127         {
128             g_warning("DBUS cannot connect: %s", error->message);
129             g_error_free(error);
130         }
131         return NULL;
132     }
133 
134     proxy = g_dbus_proxy_new_sync(conn, G_DBUS_PROXY_FLAGS_NONE, NULL,
135                                   name, path, interface, NULL, NULL);
136     if (proxy == NULL)
137         g_warning("Could not get DBUS proxy: %s", name);
138 
139     g_object_unref(conn);
140     return proxy;
141 }
142 #endif
143 
144 static gboolean
ghb_can_suspend_gpm()145 ghb_can_suspend_gpm()
146 {
147     gboolean can_suspend = FALSE;
148 #if !defined(_WIN32)
149     GDBusProxy  *proxy;
150     GError *error = NULL;
151     GVariant *res;
152 
153 
154     g_debug("ghb_can_suspend_gpm()");
155     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_PM_SERVICE,
156                             GPM_DBUS_PM_PATH, GPM_DBUS_PM_INTERFACE);
157     if (proxy == NULL)
158         return FALSE;
159 
160     res = g_dbus_proxy_call_sync(proxy, "CanSuspend", NULL,
161                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
162     if (!res)
163     {
164         if (error != NULL)
165         {
166             g_warning("CanSuspend failed: %s", error->message);
167             g_error_free(error);
168         }
169         else
170             g_warning("CanSuspend failed");
171         // Try to shutdown anyway
172         can_suspend = TRUE;
173     }
174     else
175     {
176         g_variant_get(res, "(b)", &can_suspend);
177         g_variant_unref(res);
178     }
179     g_object_unref(G_OBJECT(proxy));
180 #endif
181     return can_suspend;
182 }
183 
184 static void
ghb_suspend_gpm()185 ghb_suspend_gpm()
186 {
187 #if !defined(_WIN32)
188     GDBusProxy  *proxy;
189     GError *error = NULL;
190     GVariant *res;
191 
192 
193     g_debug("ghb_suspend_gpm()");
194     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_PM_SERVICE,
195                             GPM_DBUS_PM_PATH, GPM_DBUS_PM_INTERFACE);
196     if (proxy == NULL)
197         return;
198 
199     res = g_dbus_proxy_call_sync(proxy, "Suspend", NULL,
200                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
201     if (!res)
202     {
203         if (error != NULL)
204         {
205             g_warning("Suspend failed: %s", error->message);
206             g_error_free(error);
207         }
208         else
209             g_warning("Suspend failed");
210     }
211     else
212     {
213         g_variant_unref(res);
214     }
215     g_object_unref(G_OBJECT(proxy));
216 #endif
217 }
218 
219 #if !defined(_WIN32)
220 static gboolean
ghb_can_shutdown_gpm()221 ghb_can_shutdown_gpm()
222 {
223     gboolean can_shutdown = FALSE;
224     GDBusProxy  *proxy;
225     GError *error = NULL;
226     GVariant *res;
227 
228 
229     g_debug("ghb_can_shutdown_gpm()");
230     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_PM_SERVICE,
231                             GPM_DBUS_PM_PATH, GPM_DBUS_PM_INTERFACE);
232     if (proxy == NULL)
233         return FALSE;
234 
235     res = g_dbus_proxy_call_sync(proxy, "CanShutdown", NULL,
236                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
237     if (!res)
238     {
239         if (error != NULL)
240         {
241             g_warning("CanShutdown failed: %s", error->message);
242             g_error_free(error);
243         }
244         else
245             g_warning("CanShutdown failed");
246         // Try to shutdown anyway
247         can_shutdown = TRUE;
248     }
249     else
250     {
251         g_variant_get(res, "(b)", &can_shutdown);
252         g_variant_unref(res);
253     }
254     g_object_unref(G_OBJECT(proxy));
255     return can_shutdown;
256 }
257 #endif
258 
259 #if !defined(_WIN32)
260 static void
ghb_shutdown_gpm()261 ghb_shutdown_gpm()
262 {
263     GDBusProxy  *proxy;
264     GError *error = NULL;
265     GVariant *res;
266 
267 
268     g_debug("ghb_shutdown_gpm()");
269     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_PM_SERVICE,
270                             GPM_DBUS_PM_PATH, GPM_DBUS_PM_INTERFACE);
271     if (proxy == NULL)
272         return;
273 
274     res = g_dbus_proxy_call_sync(proxy, "Shutdown", NULL,
275                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
276     if (!res)
277     {
278         if (error != NULL)
279         {
280             g_warning("Shutdown failed: %s", error->message);
281             g_error_free(error);
282         }
283         else
284             g_warning("Shutdown failed");
285     }
286     else
287     {
288         g_variant_unref(res);
289     }
290     g_object_unref(G_OBJECT(proxy));
291 }
292 #endif
293 
294 #if !GTK_CHECK_VERSION(3, 4, 0)
295 guint
ghb_inhibit_gpm()296 ghb_inhibit_gpm()
297 {
298     guint cookie = -1;
299 
300 #if !defined(_WIN32)
301     GDBusProxy  *proxy;
302     GError *error = NULL;
303     GVariant *res;
304 
305     g_debug("ghb_inhibit_gpm()");
306     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_PM_SERVICE,
307                             GPM_DBUS_INHIBIT_PATH, GPM_DBUS_INHIBIT_INTERFACE);
308     if (proxy == NULL)
309         return 0;
310 
311     res = g_dbus_proxy_call_sync(proxy, "Inhibit",
312                                  g_variant_new("(ss)", "ghb", "Encoding"),
313                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
314     if (!res)
315     {
316         if (error != NULL)
317         {
318             g_error_free(error);
319         }
320     }
321     else
322     {
323         g_variant_get(res, "(u)", &cookie);
324         g_variant_unref(res);
325     }
326     g_object_unref(G_OBJECT(proxy));
327 #endif
328 
329     return cookie;
330 }
331 
332 void
ghb_uninhibit_gpm(guint cookie)333 ghb_uninhibit_gpm(guint cookie)
334 {
335 #if !defined(_WIN32)
336     GDBusProxy  *proxy;
337     GError *error = NULL;
338     GVariant *res;
339 
340     g_debug("ghb_uninhibit_gpm() cookie %u", cookie);
341 
342     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_PM_SERVICE,
343                             GPM_DBUS_INHIBIT_PATH, GPM_DBUS_INHIBIT_INTERFACE);
344     if (proxy == NULL)
345         return;
346 
347     res = g_dbus_proxy_call_sync(proxy, "UnInhibit",
348                                  g_variant_new("(u)", cookie),
349                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
350     if (!res)
351     {
352         if (error != NULL)
353         {
354             g_error_free(error);
355         }
356     }
357     else
358     {
359         g_variant_unref(res);
360     }
361     g_object_unref(G_OBJECT(proxy));
362 #endif
363 }
364 #endif
365 
366 #if !defined(_WIN32)
367 // For inhibit and shutdown
368 #define GPM_DBUS_SM_SERVICE         "org.gnome.SessionManager"
369 #define GPM_DBUS_SM_PATH            "/org/gnome/SessionManager"
370 #define GPM_DBUS_SM_INTERFACE       "org.gnome.SessionManager"
371 #endif
372 
373 static gboolean
ghb_can_shutdown_gsm()374 ghb_can_shutdown_gsm()
375 {
376     gboolean can_shutdown = FALSE;
377 #if !defined(_WIN32)
378     GDBusProxy  *proxy;
379     GError *error = NULL;
380     GVariant *res;
381 
382     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_SM_SERVICE,
383                             GPM_DBUS_SM_PATH, GPM_DBUS_SM_INTERFACE);
384     if (proxy == NULL)
385         return FALSE;
386 
387     res = g_dbus_proxy_call_sync(proxy, "CanShutdown", NULL,
388                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
389     g_object_unref(G_OBJECT(proxy));
390     if (!res)
391     {
392         if (error != NULL)
393         {
394             g_error_free(error);
395         }
396         // Try the gpm version
397         can_shutdown = ghb_can_shutdown_gpm();
398     }
399     else
400     {
401         g_variant_get(res, "(b)", &can_shutdown);
402         g_variant_unref(res);
403     }
404 #endif
405     return can_shutdown;
406 }
407 
408 static void
ghb_shutdown_gsm()409 ghb_shutdown_gsm()
410 {
411 #if !defined(_WIN32)
412     GDBusProxy  *proxy;
413     GError *error = NULL;
414     GVariant *res;
415 
416 
417     g_debug("ghb_shutdown_gpm()");
418     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_SM_SERVICE,
419                             GPM_DBUS_SM_PATH, GPM_DBUS_SM_INTERFACE);
420     if (proxy == NULL)
421         return;
422 
423     res = g_dbus_proxy_call_sync(proxy, "Shutdown", NULL,
424                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
425     g_object_unref(G_OBJECT(proxy));
426     if (!res)
427     {
428         if (error != NULL)
429         {
430             g_error_free(error);
431         }
432         // Try the gpm version
433         ghb_shutdown_gpm();
434     }
435     else
436     {
437         g_variant_unref(res);
438     }
439 #endif
440 }
441 
442 #if !GTK_CHECK_VERSION(3, 4, 0)
443 guint
ghb_inhibit_gsm(signal_user_data_t * ud)444 ghb_inhibit_gsm(signal_user_data_t *ud)
445 {
446     guint cookie = -1;
447 
448 #if !defined(_WIN32)
449     GDBusProxy  *proxy;
450     GError *error = NULL;
451     GVariant *res;
452     guint xid;
453     GtkWidget *widget;
454 
455     g_debug("ghb_inhibit_gsm()");
456     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_SM_SERVICE,
457                             GPM_DBUS_SM_PATH, GPM_DBUS_SM_INTERFACE);
458     if (proxy == NULL)
459         return -1;
460 
461     widget = GHB_WIDGET(ud->builder, "hb_window");
462     xid = GDK_WINDOW_XID(gtk_widget_get_window(widget));
463     res = g_dbus_proxy_call_sync(proxy, "Inhibit",
464                                  g_variant_new("(susu)", "ghb", xid, "Encoding", 1 | 4),
465                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
466     g_object_unref(G_OBJECT(proxy));
467     if (!res)
468     {
469         if (error != NULL)
470         {
471             g_error_free(error);
472         }
473     }
474     else
475     {
476         g_variant_get(res, "(u)", &cookie);
477         g_variant_unref(res);
478     }
479 #endif
480 
481     return cookie;
482 }
483 
484 void
ghb_uninhibit_gsm(guint cookie)485 ghb_uninhibit_gsm(guint cookie)
486 {
487 #if !defined(_WIN32)
488     GDBusProxy  *proxy;
489     GError *error = NULL;
490     GVariant *res;
491 
492     g_debug("ghb_uninhibit_gsm() cookie %u", cookie);
493 
494     proxy = ghb_get_session_dbus_proxy(GPM_DBUS_SM_SERVICE,
495                             GPM_DBUS_SM_PATH, GPM_DBUS_SM_INTERFACE);
496     if (proxy == NULL)
497         return;
498 
499     res = g_dbus_proxy_call_sync(proxy, "Uninhibit",
500                                  g_variant_new("(u)", cookie),
501                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
502     if (!res)
503     {
504         if (error != NULL)
505         {
506             g_error_free(error);
507         }
508     }
509     else
510     {
511         g_variant_unref(res);
512     }
513     g_object_unref(G_OBJECT(proxy));
514 #endif
515 }
516 #endif
517 
518 enum {
519     GHB_SUSPEND_UNINHIBITED = 0,
520     GHB_SUSPEND_INHIBITED_GPM,
521     GHB_SUSPEND_INHIBITED_GSM,
522     GHB_SUSPEND_INHIBITED_GTK
523 };
524 static int suspend_inhibited = GHB_SUSPEND_UNINHIBITED;
525 static guint suspend_cookie;
526 
527 void
ghb_inhibit_suspend(signal_user_data_t * ud)528 ghb_inhibit_suspend(signal_user_data_t *ud)
529 {
530     if (suspend_inhibited != GHB_SUSPEND_UNINHIBITED)
531     {
532         // Already inhibited
533         return;
534     }
535 #if GTK_CHECK_VERSION(3, 4, 0)
536     suspend_cookie = gtk_application_inhibit(ud->app, NULL,
537                             GTK_APPLICATION_INHIBIT_SUSPEND, "Encoding");
538     if (suspend_cookie != 0)
539     {
540         suspend_inhibited = GHB_SUSPEND_INHIBITED_GTK;
541         return;
542     }
543 #else
544     suspend_cookie = ghb_inhibit_gsm(ud);
545     if (suspend_cookie != -1)
546     {
547         suspend_inhibited = GHB_SUSPEND_INHIBITED_GSM;
548         return;
549     }
550     suspend_cookie = ghb_inhibit_gpm(ud);
551     if (suspend_cookie != -1)
552     {
553         suspend_inhibited = GHB_SUSPEND_INHIBITED_GPM;
554         return;
555     }
556 #endif
557 }
558 
559 void
ghb_uninhibit_suspend(signal_user_data_t * ud)560 ghb_uninhibit_suspend(signal_user_data_t *ud)
561 {
562     switch (suspend_inhibited)
563     {
564 #if GTK_CHECK_VERSION(3, 4, 0)
565         case GHB_SUSPEND_INHIBITED_GTK:
566             gtk_application_uninhibit(ud->app, suspend_cookie);
567             break;
568 #else
569         case GHB_SUSPEND_INHIBITED_GPM:
570             ghb_uninhibit_gpm(suspend_cookie);
571             break;
572 
573         case GHB_SUSPEND_INHIBITED_GSM:
574             ghb_uninhibit_gsm(suspend_cookie);
575             break;
576 #endif
577         default:
578             break;
579     }
580     suspend_inhibited = GHB_SUSPEND_UNINHIBITED;
581 }
582 
583 // This is a dependency map used for greying widgets
584 // that are dependent on the state of another widget.
585 // The enable_value comes from the values that are
586 // obtained from ghb_widget_value().  For combo boxes
587 // you will have to look further to combo box options
588 // maps in hb-backend.c
589 
590 GhbValue *dep_map;
591 GhbValue *rev_map;
592 
593 void
ghb_init_dep_map()594 ghb_init_dep_map()
595 {
596     dep_map = ghb_resource_get("widget-deps");
597     rev_map = ghb_resource_get("widget-reverse-deps");
598 }
599 
600 static gboolean
dep_check(signal_user_data_t * ud,const gchar * name,gboolean * out_hide)601 dep_check(signal_user_data_t *ud, const gchar *name, gboolean *out_hide)
602 {
603     GtkWidget *widget;
604     GObject *dep_object;
605     gint ii;
606     gint count;
607     gboolean result = TRUE;
608     GhbValue *array, *data;
609     const gchar *widget_name;
610 
611     g_debug("dep_check () %s", name);
612 
613     if (rev_map == NULL) return TRUE;
614     array = ghb_dict_get(rev_map, name);
615     count = ghb_array_len(array);
616     *out_hide = FALSE;
617     for (ii = 0; ii < count; ii++)
618     {
619         data = ghb_array_get(array, ii);
620         widget_name = ghb_value_get_string(ghb_array_get(data, 0));
621         widget = GHB_WIDGET(ud->builder, widget_name);
622         dep_object = gtk_builder_get_object(ud->builder, name);
623         if (widget != NULL && !gtk_widget_is_sensitive(widget))
624         {
625             continue;
626         }
627         if (dep_object == NULL)
628         {
629             g_message("Failed to find widget");
630         }
631         else
632         {
633             gchar *value;
634             gint jj = 0;
635             gchar **values;
636             gboolean sensitive = FALSE;
637             gboolean die, hide;
638 
639             die = ghb_value_get_bool(ghb_array_get(data, 2));
640             hide = ghb_value_get_bool(ghb_array_get(data, 3));
641             const char *tmp = ghb_value_get_string(ghb_array_get(data, 1));
642             values = g_strsplit(tmp, "|", 10);
643 
644             if (widget)
645                 value = ghb_widget_string(widget);
646             else
647                 value = ghb_dict_get_string_xform(ud->settings, widget_name);
648             while (values && values[jj])
649             {
650                 if (values[jj][0] == '>')
651                 {
652                     gdouble dbl = g_strtod (&values[jj][1], NULL);
653                     gdouble dvalue = ghb_widget_double(widget);
654                     if (dvalue > dbl)
655                     {
656                         sensitive = TRUE;
657                         break;
658                     }
659                 }
660                 else if (values[jj][0] == '<')
661                 {
662                     gdouble dbl = g_strtod (&values[jj][1], NULL);
663                     gdouble dvalue = ghb_widget_double(widget);
664                     if (dvalue < dbl)
665                     {
666                         sensitive = TRUE;
667                         break;
668                     }
669                 }
670                 if (strcmp(values[jj], value) == 0)
671                 {
672                     sensitive = TRUE;
673                     break;
674                 }
675                 jj++;
676             }
677             sensitive = die ^ sensitive;
678             if (!sensitive)
679             {
680                 result = FALSE;
681                 *out_hide |= hide;
682             }
683             g_strfreev (values);
684             g_free(value);
685         }
686     }
687     return result;
688 }
689 
690 void
ghb_check_dependency(signal_user_data_t * ud,GtkWidget * widget,const char * alt_name)691 ghb_check_dependency(
692     signal_user_data_t *ud,
693     GtkWidget *widget,
694     const char *alt_name)
695 {
696     GObject *dep_object;
697     const gchar *name;
698     GhbValue *array, *data;
699     gint count, ii;
700     const gchar *dep_name;
701     GType type;
702 
703     if (widget != NULL)
704     {
705         type = G_OBJECT_TYPE(widget);
706         if (type == GTK_TYPE_COMBO_BOX)
707             if (gtk_combo_box_get_active(GTK_COMBO_BOX(widget)) < 0) return;
708         name = ghb_get_setting_key(widget);
709     }
710     else
711         name = alt_name;
712 
713     g_debug("ghb_check_dependency() %s", name);
714 
715     if (dep_map == NULL) return;
716     array = ghb_dict_get(dep_map, name);
717     count = ghb_array_len(array);
718     for (ii = 0; ii < count; ii++)
719     {
720         gboolean sensitive;
721         gboolean hide;
722 
723         data = ghb_array_get(array, ii);
724         dep_name = ghb_value_get_string(data);
725         dep_object = gtk_builder_get_object(ud->builder, dep_name);
726         if (dep_object == NULL)
727         {
728             g_message("Failed to find dependent widget %s", dep_name);
729             continue;
730         }
731         sensitive = dep_check(ud, dep_name, &hide);
732         gtk_widget_set_sensitive(GTK_WIDGET(dep_object), sensitive);
733         gtk_widget_set_can_focus(GTK_WIDGET(dep_object), sensitive);
734         if (!sensitive && hide)
735         {
736             if (gtk_widget_get_visible(GTK_WIDGET(dep_object)))
737             {
738                 gtk_widget_hide(GTK_WIDGET(dep_object));
739             }
740         }
741         else
742         {
743             if (!gtk_widget_get_visible(GTK_WIDGET(dep_object)))
744             {
745                 gtk_widget_show(GTK_WIDGET(dep_object));
746             }
747         }
748     }
749 }
750 
751 void
ghb_check_all_dependencies(signal_user_data_t * ud)752 ghb_check_all_dependencies(signal_user_data_t *ud)
753 {
754     GhbDictIter iter;
755     const gchar *dep_name;
756     GhbValue *value;
757     GObject *dep_object;
758 
759     g_debug("ghb_check_all_dependencies ()");
760     if (rev_map == NULL) return;
761     iter = ghb_dict_iter_init(rev_map);
762     while (ghb_dict_iter_next(rev_map, &iter, &dep_name, &value))
763     {
764         gboolean sensitive;
765         gboolean hide;
766 
767         dep_object = gtk_builder_get_object (ud->builder, dep_name);
768         if (dep_object == NULL)
769         {
770             g_message("Failed to find dependent widget %s", dep_name);
771             continue;
772         }
773         sensitive = dep_check(ud, dep_name, &hide);
774         gtk_widget_set_sensitive(GTK_WIDGET(dep_object), sensitive);
775         gtk_widget_set_can_focus(GTK_WIDGET(dep_object), sensitive);
776         if (!sensitive && hide)
777         {
778             gtk_widget_hide(GTK_WIDGET(dep_object));
779         }
780         else
781         {
782             gtk_widget_show(GTK_WIDGET(dep_object));
783         }
784     }
785 }
786 
787 G_MODULE_EXPORT void
quit_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)788 quit_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud)
789 {
790     gint state = ghb_get_queue_state();
791     if (state & (GHB_STATE_WORKING|GHB_STATE_SEARCHING))
792     {
793         if (ghb_cancel_encode2(ud, _("Closing HandBrake will terminate encoding.\n")))
794         {
795             ghb_hb_cleanup(FALSE);
796             prune_logs(ud);
797             g_application_quit(G_APPLICATION(ud->app));
798             return;
799         }
800         return;
801     }
802     ghb_hb_cleanup(FALSE);
803     prune_logs(ud);
804     g_application_quit(G_APPLICATION(ud->app));
805 }
806 
807 static gboolean
uppers_and_unders(gchar * str)808 uppers_and_unders(gchar *str)
809 {
810     if (str == NULL) return FALSE;
811     str = g_strchomp(g_strchug(str));
812     while (*str)
813     {
814         if (*str == ' ')
815         {
816             return FALSE;
817         }
818         if (*str >= 'a' && *str <= 'z')
819         {
820             return FALSE;
821         }
822         str++;
823     }
824     return TRUE;
825 }
826 
827 enum
828 {
829     CAMEL_FIRST_UPPER,
830     CAMEL_OTHER
831 };
832 
833 static void
camel_convert(gchar * str)834 camel_convert(gchar *str)
835 {
836     gint state = CAMEL_OTHER;
837 
838     if (str == NULL) return;
839     while (*str)
840     {
841         if (*str == '_') *str = ' ';
842         switch (state)
843         {
844             case CAMEL_OTHER:
845             {
846                 if (*str >= 'A' && *str <= 'Z')
847                     state = CAMEL_FIRST_UPPER;
848                 else
849                     state = CAMEL_OTHER;
850 
851             } break;
852             case CAMEL_FIRST_UPPER:
853             {
854                 if (*str >= 'A' && *str <= 'Z')
855                     *str = *str - 'A' + 'a';
856                 else
857                     state = CAMEL_OTHER;
858             } break;
859         }
860         str++;
861     }
862 }
863 
864 #if defined(_WIN32)
865 static gchar*
get_dvd_device_name(gchar * device)866 get_dvd_device_name(gchar *device)
867 {
868     return g_strdup(device);
869 }
870 #else
871 static gchar*
get_dvd_device_name(GDrive * gd)872 get_dvd_device_name(GDrive *gd)
873 {
874     return g_drive_get_identifier(gd, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
875 }
876 #endif
877 
878 static GHashTable *volname_hash = NULL;
879 #if GLIB_CHECK_VERSION(2, 32, 0)
880 static GMutex     volname_mutex_static;
881 #endif
882 static GMutex     *volname_mutex;
883 
884 static void
free_volname_key(gpointer data)885 free_volname_key(gpointer data)
886 {
887     if (data != NULL)
888         g_free(data);
889 }
890 
891 static void
free_volname_value(gpointer data)892 free_volname_value(gpointer data)
893 {
894     if (data != NULL)
895         g_free(data);
896 }
897 
898 #if defined(_WIN32)
899 static gchar*
get_direct_dvd_volume_name(const gchar * drive)900 get_direct_dvd_volume_name(const gchar *drive)
901 {
902     gchar *result = NULL;
903     gchar vname[51], fsname[51];
904 
905     if (GetVolumeInformation(drive, vname, 50, NULL, NULL, NULL, fsname, 50))
906     {
907         result = g_strdup_printf("%s", vname);
908     }
909     return result;
910 }
911 #else
912 static gchar*
get_direct_dvd_volume_name(const gchar * drive)913 get_direct_dvd_volume_name(const gchar *drive)
914 {
915     gchar *result;
916 
917     result = ghb_dvd_volname (drive);
918     return result;
919 }
920 #endif
921 
922 static gchar*
get_dvd_volume_name(gpointer gd)923 get_dvd_volume_name(gpointer gd)
924 {
925     gchar *label = NULL;
926     gchar *result;
927     gchar *drive;
928 
929     drive = get_dvd_device_name(gd);
930     g_mutex_lock(volname_mutex);
931     label = g_strdup(g_hash_table_lookup(volname_hash, drive));
932     g_mutex_unlock(volname_mutex);
933     if (label != NULL)
934     {
935         if (uppers_and_unders(label))
936         {
937             camel_convert(label);
938         }
939 #if defined(_WIN32)
940         result = g_strdup_printf("%s (%s)", label, drive);
941 #else
942         result = g_strdup_printf("%s - %s", drive, label);
943 #endif
944         g_free(label);
945     }
946     else
947     {
948         result = g_strdup_printf("%s", drive);
949     }
950     g_free(drive);
951     return result;
952 }
953 
954 void
ghb_volname_cache_init(void)955 ghb_volname_cache_init(void)
956 {
957 #if GLIB_CHECK_VERSION(2, 32, 0)
958     g_mutex_init(&volname_mutex_static);
959     volname_mutex = &volname_mutex_static;
960 #else
961     volname_mutex = g_mutex_new();
962 #endif
963     volname_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
964                                         free_volname_key, free_volname_value);
965 }
966 
967 static void
free_drive(gpointer drive)968 free_drive(gpointer drive)
969 {
970 #if defined(_WIN32)
971         g_free(drive);
972 #else
973         g_object_unref(drive);
974 #endif
975 }
976 
977 gpointer
ghb_cache_volnames(signal_user_data_t * ud)978 ghb_cache_volnames(signal_user_data_t *ud)
979 {
980     GList *link, *drives;
981 
982     g_debug("ghb_cache_volnames()");
983     link = drives = dvd_device_list();
984     if (drives == NULL)
985         return NULL;
986 
987     g_mutex_lock(volname_mutex);
988     g_hash_table_remove_all(volname_hash);
989     while (link != NULL)
990     {
991         gchar *name, *drive;
992 
993 #if !defined(_WIN32)
994         if (!g_drive_has_media (link->data))
995         {
996             g_object_unref(link->data);
997             link = link->next;
998             continue;
999         }
1000 #endif
1001         drive = get_dvd_device_name(link->data);
1002         name = get_direct_dvd_volume_name(drive);
1003 
1004         if (drive != NULL && name != NULL)
1005         {
1006             g_hash_table_insert(volname_hash, drive, name);
1007         }
1008         else
1009         {
1010             if (drive != NULL)
1011                 g_free(drive);
1012             if (name != NULL)
1013                 g_free(name);
1014         }
1015 
1016         free_drive(link->data);
1017         link = link->next;
1018     }
1019     g_mutex_unlock(volname_mutex);
1020 
1021     g_list_free(drives);
1022 
1023     g_idle_add((GSourceFunc)ghb_file_menu_add_dvd, ud);
1024 
1025     return NULL;
1026 }
1027 
1028 static const gchar*
get_extension(signal_user_data_t * ud,GhbValue * settings)1029 get_extension(signal_user_data_t *ud, GhbValue *settings)
1030 {
1031     const char *mux_id;
1032     const hb_container_t *mux;
1033 
1034     mux_id = ghb_dict_get_string(settings, "FileFormat");
1035     mux = ghb_lookup_container_by_name(mux_id);
1036 
1037     if ((mux->format & HB_MUX_MASK_MP4) &&
1038         ghb_dict_get_bool(ud->prefs, "UseM4v"))
1039     {
1040         return "m4v";
1041     }
1042     return mux->default_extension;
1043 }
1044 
1045 static gboolean
check_name_template(signal_user_data_t * ud,const char * str)1046 check_name_template(signal_user_data_t *ud, const char *str)
1047 {
1048     if (ghb_dict_get_bool(ud->prefs, "auto_name"))
1049     {
1050         const gchar *template;
1051 
1052         template = ghb_dict_get_string(ud->prefs, "auto_name_template");
1053         if (strstr(template, str) != NULL)
1054             return TRUE;
1055     }
1056     return FALSE;
1057 }
1058 
1059 typedef struct {
1060     const char *pattern;
1061     const char *format;
1062 } datemap;
1063 
1064 static int
parse_datestring(const char * src,struct tm * tm)1065 parse_datestring(const char *src, struct tm *tm)
1066 {
1067     datemap ymdThmsZ = {"[0-9]{4}-[0-1]?[0-9]-[0-3]?[0-9]T[0-9]{2}:[0-9]{2}:[0-9]{2}Z", "%Y-%m-%dT%H:%M:%SZ"};
1068 
1069     datemap maps[1] = { ymdThmsZ };
1070 
1071     for (int i = 0; i < sizeof(maps); i++)
1072     {
1073         if (hb_validate_param_string(maps[i].pattern, src))
1074         {
1075             av_small_strptime(src, maps[i].format, tm);
1076             return 1;
1077         }
1078     }
1079     return 0;
1080 }
1081 
1082 static char*
get_creation_date(const char * pattern,const char * metaValue,const char * file)1083 get_creation_date(const char *pattern, const char *metaValue, const char *file)
1084 {
1085     char date[11] = "";
1086     if (metaValue != NULL && strlen(metaValue) > 1)
1087     {
1088         struct tm tm;
1089         if (parse_datestring(metaValue, &tm))
1090         {
1091             strftime(date, 11, pattern, &tm);
1092         }
1093     }
1094     else
1095     {
1096         struct stat stbuf;
1097         if (g_stat(file, &stbuf) == 0){
1098             struct tm *tm;
1099             tm = localtime(&(stbuf.st_mtime));
1100             strftime(date, 11, pattern, tm);
1101         }
1102     }
1103     return strdup(date);
1104 }
1105 
1106 static void
make_unique_dest(const gchar * dest_dir,GString * str,const gchar * extension)1107 make_unique_dest(const gchar *dest_dir, GString *str, const gchar * extension)
1108 {
1109     GString * uniq = g_string_new(str->str);
1110     int       copy = 0;
1111 
1112     g_string_printf(uniq, "%s/%s.%s", dest_dir, str->str, extension);
1113     while (g_file_test(uniq->str, G_FILE_TEST_EXISTS))
1114     {
1115         g_string_printf(uniq, "%s/%s (%d).%s", dest_dir, str->str, ++copy, extension);
1116     }
1117     if (copy)
1118     {
1119         g_string_append_printf(str, " (%d)", copy);
1120     }
1121     g_string_free(uniq, TRUE);
1122 }
1123 
1124 static void
set_destination_settings(signal_user_data_t * ud,GhbValue * settings)1125 set_destination_settings(signal_user_data_t *ud, GhbValue *settings)
1126 {
1127     const gchar *extension, *dest_file, *dest_dir;
1128     gchar *filename;
1129 
1130     extension = get_extension(ud, settings);
1131 
1132     g_debug("set_destination_settings");
1133     dest_file = ghb_dict_get_string(ud->settings, "dest_file");
1134     if (dest_file == NULL)
1135     {
1136         // Initialize destination filename if it has no value yet.
1137         // If auto-naming is disabled, this will be the default filename.
1138         GString *str = g_string_new("");
1139         const gchar *vol_name;
1140         vol_name = ghb_dict_get_string(settings, "volume");
1141         g_string_append_printf(str, "%s", vol_name);
1142         g_string_append_printf(str, ".%s", extension);
1143         filename = g_string_free(str, FALSE);
1144         ghb_dict_set_string(settings, "dest_file", filename);
1145         g_free(filename);
1146     }
1147     ghb_dict_set(settings, "dest_dir", ghb_value_dup(
1148                  ghb_dict_get_value(ud->prefs, "destination_dir")));
1149     if (ghb_dict_get_bool(ud->prefs, "auto_name"))
1150     {
1151         GString *str = g_string_new("");
1152         const gchar *p;
1153 
1154         p = ghb_dict_get_string(ud->prefs, "auto_name_template");
1155         // {source-path} is only allowed as the first element of the
1156         // template since the path must come first in the filename
1157         if (p != NULL &&
1158             (!strncasecmp(p, "{source-path}", strlen("{source-path}")) ||
1159              !strncasecmp(p, "{source_path}", strlen("{source_path}"))))
1160         {
1161             const gchar * source;
1162 
1163             source = ghb_dict_get_string(ud->globals, "scan_source");
1164             if (source != NULL)
1165             {
1166                 char * dirname = g_path_get_dirname(source);
1167                 // if dirname is a directory and it is writable...
1168                 if (dirname != NULL &&
1169                     g_file_test(dirname, G_FILE_TEST_IS_DIR) &&
1170                     access(dirname, W_OK) == 0)
1171                 {
1172                     ghb_dict_set_string(settings, "dest_dir", dirname);
1173                 }
1174                 g_free(dirname);
1175             }
1176             p += strlen("{source-path}");
1177         }
1178         while (*p)
1179         {
1180             if (!strncasecmp(p, "{source}", strlen("{source}")))
1181             {
1182                 const gchar *vol_name;
1183                 vol_name = ghb_dict_get_string(settings, "volume");
1184                 g_string_append_printf(str, "%s", vol_name);
1185                 p += strlen("{source}");
1186             }
1187             else if (!strncasecmp(p, "{title}", strlen("{title}")))
1188             {
1189                 gint title = ghb_dict_get_int(settings, "title");
1190                 if (title >= 0)
1191                     g_string_append_printf(str, "%d", title);
1192                 p += strlen("{title}");
1193             }
1194             else if (!strncasecmp(p, "{preset}", strlen("{preset}")))
1195             {
1196                 const gchar *preset_name;
1197                 preset_name = ghb_dict_get_string(settings, "PresetName");
1198                 g_string_append_printf(str, "%s", preset_name);
1199                 p += strlen("{preset}");
1200             }
1201             else if (!strncasecmp(p, "{chapters}", strlen("{chapters}")))
1202             {
1203                 if (ghb_settings_combo_int(settings, "PtoPType") == 0)
1204                 {
1205                     gint start, end;
1206                     start = ghb_dict_get_int(settings, "start_point");
1207                     end = ghb_dict_get_int(settings, "end_point");
1208                     if (start == end)
1209                         g_string_append_printf(str, "%d", start);
1210                     else
1211                         g_string_append_printf(str, "%d-%d", start, end);
1212                 }
1213                 p += strlen("{chapters}");
1214             }
1215             else if (!strncasecmp(p, "{time}", strlen("{time}")))
1216             {
1217                 char st[6];
1218                 struct tm *lt;
1219                 time_t t = time(NULL);
1220                 lt = localtime(&t);
1221                 st[0] = 0;
1222                 strftime(st, 6, "%H:%M", lt);
1223                 g_string_append_printf(str, "%s", st);
1224                 p += strlen("{time}");
1225             }
1226             else if (!strncasecmp(p, "{date}", strlen("{date}")))
1227             {
1228                 char dt[11];
1229                 struct tm *lt;
1230                 time_t t = time(NULL);
1231                 lt = localtime(&t);
1232                 dt[0] = 0;
1233                 strftime(dt, 11, "%Y-%m-%d", lt);
1234                 g_string_append_printf(str, "%s", dt);
1235                 p += strlen("{date}");
1236             }
1237             else if (!strncasecmp(p, "{creation-date}", strlen("{creation-date}")))
1238             {
1239                 gchar *val;
1240                 const gchar *source = ghb_dict_get_string(ud->globals, "scan_source");
1241                 val = get_creation_date("%Y-%m-%d", ghb_dict_get_string(settings, "MetaReleaseDate"), source);
1242                 g_string_append_printf(str, "%s", val);
1243                 p += strlen("{creation-date}");
1244                 g_free(val);
1245             }
1246             else if (!strncasecmp(p, "{creation-time}", strlen("{creation-time}")))
1247             {
1248                 gchar *val;
1249                 const gchar *source = ghb_dict_get_string(ud->globals, "scan_source");
1250                 val = get_creation_date("%H:%M", ghb_dict_get_string(settings, "MetaReleaseDate"), source);
1251                 g_string_append_printf(str, "%s", val);
1252                 p += strlen("{creation-time}");
1253                 g_free(val);
1254             }
1255             else if (!strncasecmp(p, "{quality}", strlen("{quality}")))
1256             {
1257                 if (ghb_dict_get_bool(settings, "vquality_type_constant"))
1258                 {
1259                     gint vcodec;
1260                     const char *vqname;
1261                     double vquality;
1262                     vcodec = ghb_settings_video_encoder_codec(settings, "VideoEncoder");
1263                     vqname = hb_video_quality_get_name(vcodec);
1264                     vquality = ghb_dict_get_double(settings, "VideoQualitySlider");
1265                     g_string_append_printf(str, "%s%.3g", vqname, vquality);
1266                 }
1267                 p += strlen("{quality}");
1268             }
1269             else if (!strncasecmp(p, "{bitrate}", strlen("{bitrate}")))
1270             {
1271                 if (ghb_dict_get_bool(settings, "vquality_type_bitrate"))
1272                 {
1273                     int vbitrate;
1274                     vbitrate = ghb_dict_get_int(settings, "VideoAvgBitrate");
1275                     g_string_append_printf(str, "%dkbps", vbitrate);
1276                 }
1277                 p += strlen("{bitrate}");
1278             }
1279             else
1280             {
1281                 g_string_append_printf(str, "%c", *p);
1282                 p++;
1283             }
1284         }
1285         dest_dir = ghb_dict_get_string(settings, "dest_dir");
1286         make_unique_dest(dest_dir, str, extension);
1287         g_string_append_printf(str, ".%s", extension);
1288         filename = g_string_free(str, FALSE);
1289         ghb_dict_set_string(settings, "dest_file", filename);
1290         g_free(filename);
1291     }
1292 }
1293 
1294 static void
set_destination(signal_user_data_t * ud)1295 set_destination(signal_user_data_t *ud)
1296 {
1297     set_destination_settings(ud, ud->settings);
1298     ghb_ui_update(ud, "dest_file",
1299         ghb_dict_get_value(ud->settings, "dest_file"));
1300 }
1301 
1302 G_MODULE_EXPORT void
chooser_file_selected_cb(GtkFileChooser * dialog,signal_user_data_t * ud)1303 chooser_file_selected_cb(GtkFileChooser *dialog, signal_user_data_t *ud)
1304 {
1305     gchar *name = gtk_file_chooser_get_filename (dialog);
1306     GtkTreeModel *store;
1307     GtkTreeIter iter;
1308     const gchar *device;
1309     gboolean foundit = FALSE;
1310     GtkComboBox *combo;
1311 
1312     g_debug("chooser_file_selected_cb ()");
1313 
1314     if (name == NULL) return;
1315     combo = GTK_COMBO_BOX(GHB_WIDGET(ud->builder, "source_device"));
1316     store = gtk_combo_box_get_model(combo);
1317     if (gtk_tree_model_get_iter_first(store, &iter))
1318     {
1319         do
1320         {
1321             gtk_tree_model_get(store, &iter, 0, &device, -1);
1322             if (strcmp(name, device) == 0)
1323             {
1324                 foundit = TRUE;
1325                 break;
1326             }
1327         } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter));
1328     }
1329     if (foundit)
1330         gtk_combo_box_set_active_iter (combo, &iter);
1331     else
1332         gtk_combo_box_set_active (combo, 0);
1333 
1334     g_free(name);
1335 }
1336 
1337 G_MODULE_EXPORT void
dvd_device_changed_cb(GtkComboBoxText * combo,signal_user_data_t * ud)1338 dvd_device_changed_cb(GtkComboBoxText *combo, signal_user_data_t *ud)
1339 {
1340     GtkWidget *dialog;
1341     gint ii;
1342 
1343     g_debug("dvd_device_changed_cb ()");
1344     ii = gtk_combo_box_get_active (GTK_COMBO_BOX(combo));
1345     if (ii > 0)
1346     {
1347         const gchar *device;
1348         gchar *name;
1349 
1350         dialog = GHB_WIDGET(ud->builder, "source_dialog");
1351         device = gtk_combo_box_text_get_active_text(combo);
1352         // Protects against unexpected NULL return value
1353         if (device != NULL)
1354         {
1355             name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
1356             if (name == NULL || strcmp(name, device) != 0)
1357                 gtk_file_chooser_select_filename (GTK_FILE_CHOOSER(dialog), device);
1358             if (name != NULL)
1359                 g_free(name);
1360         }
1361     }
1362 }
1363 
1364 static void
source_dialog_extra_widgets(signal_user_data_t * ud,GtkWidget * dialog)1365 source_dialog_extra_widgets(
1366     signal_user_data_t *ud,
1367     GtkWidget *dialog)
1368 {
1369     GtkComboBoxText *combo;
1370     GList *drives, *link;
1371 
1372     g_debug("source_dialog_extra_widgets ()");
1373     combo = GTK_COMBO_BOX_TEXT(GHB_WIDGET(ud->builder, "source_device"));
1374     gtk_list_store_clear(GTK_LIST_STORE(
1375                 gtk_combo_box_get_model(GTK_COMBO_BOX(combo))));
1376 
1377     link = drives = dvd_device_list();
1378     gtk_combo_box_text_append_text (combo, _("Not Selected"));
1379     while (link != NULL)
1380     {
1381         gchar *name = get_dvd_device_name(link->data);
1382         gtk_combo_box_text_append_text(combo, name);
1383         g_free(name);
1384         free_drive(link->data);
1385         link = link->next;
1386     }
1387     g_list_free(drives);
1388     gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1389 }
1390 
ghb_break_pts_duration(gint64 ptsDuration,gint * hh,gint * mm,gdouble * ss)1391 void ghb_break_pts_duration(gint64 ptsDuration, gint *hh, gint *mm, gdouble *ss)
1392 {
1393     *hh = ptsDuration / (90000 * 60 * 60);
1394     ptsDuration -= *hh * 90000 * 60 * 60;
1395     *mm = ptsDuration / (90000 * 60);
1396     ptsDuration -= *mm * 90000 * 60;
1397     *ss = (gdouble)ptsDuration / 90000;
1398 }
1399 
ghb_break_duration(gint64 duration,gint * hh,gint * mm,gint * ss)1400 void ghb_break_duration(gint64 duration, gint *hh, gint *mm, gint *ss)
1401 {
1402     *hh = duration / (60*60);
1403     *mm = (duration / 60) % 60;
1404     *ss = duration % 60;
1405 }
1406 
1407 gint64
ghb_title_range_get_duration(GhbValue * settings,const hb_title_t * title)1408 ghb_title_range_get_duration(GhbValue * settings, const hb_title_t * title)
1409 {
1410     gint64 start, end;
1411 
1412     if (ghb_settings_combo_int(settings, "PtoPType") == 0)
1413     {
1414         start = ghb_dict_get_int(settings, "start_point");
1415         end = ghb_dict_get_int(settings, "end_point");
1416         return ghb_chapter_range_get_duration(title, start, end) / 90000;
1417     }
1418     else if (ghb_settings_combo_int(settings, "PtoPType") == 1)
1419     {
1420         start = ghb_dict_get_int(settings, "start_point");
1421         end = ghb_dict_get_int(settings, "end_point");
1422         return end - start;
1423     }
1424     else if (ghb_settings_combo_int(settings, "PtoPType") == 2)
1425     {
1426         if (title != NULL)
1427         {
1428             gint64 frames;
1429 
1430             start = ghb_dict_get_int(settings, "start_point");
1431             end = ghb_dict_get_int(settings, "end_point");
1432             frames = end - start + 1;
1433             return frames * title->vrate.den / title->vrate.num;
1434         }
1435         else
1436         {
1437             return 0;
1438         }
1439     }
1440     return 0;
1441 }
1442 
1443 static void
update_title_duration(signal_user_data_t * ud)1444 update_title_duration(signal_user_data_t *ud)
1445 {
1446     gint hh, mm, ss;
1447     gint64 duration;
1448     gchar *text;
1449     GtkWidget *widget;
1450     int title_id, titleindex;
1451     const hb_title_t *title;
1452 
1453     title_id = ghb_dict_get_int(ud->settings, "title");
1454     title = ghb_lookup_title(title_id, &titleindex);
1455     widget = GHB_WIDGET (ud->builder, "title_duration");
1456 
1457     duration = ghb_title_range_get_duration(ud->settings, title);
1458     ghb_break_duration(duration, &hh, &mm, &ss);
1459 
1460     text = g_strdup_printf("%02d:%02d:%02d", hh, mm, ss);
1461     gtk_label_set_text(GTK_LABEL(widget), text);
1462     g_free(text);
1463 }
1464 
ghb_show_container_options(signal_user_data_t * ud)1465 void ghb_show_container_options(signal_user_data_t *ud)
1466 {
1467     GtkWidget *w1, *w2, *w3;
1468     w1 = GHB_WIDGET(ud->builder, "AlignAVStart");
1469     w2 = GHB_WIDGET(ud->builder, "Mp4HttpOptimize");
1470     w3 = GHB_WIDGET(ud->builder, "Mp4iPodCompatible");
1471 
1472     const char *mux_id;
1473     const hb_container_t *mux;
1474 
1475     mux_id = ghb_dict_get_string(ud->settings, "FileFormat");
1476     mux = ghb_lookup_container_by_name(mux_id);
1477 
1478     gint enc = ghb_settings_video_encoder_codec(ud->settings, "VideoEncoder");
1479 
1480     gtk_widget_set_visible(w1, (mux->format & HB_MUX_MASK_MP4));
1481     gtk_widget_set_visible(w2, (mux->format & HB_MUX_MASK_MP4));
1482     gtk_widget_set_visible(w3, (mux->format & HB_MUX_MASK_MP4) &&
1483                                (enc == HB_VCODEC_X264_8BIT));
1484 }
1485 
1486 static void
adjustment_configure(GtkAdjustment * adj,double val,double min,double max,double step,double page,double page_sz)1487 adjustment_configure(
1488     GtkAdjustment *adj,
1489     double val,
1490     double min, double max,
1491     double step, double page, double page_sz)
1492 {
1493     gtk_adjustment_configure(adj, val, min, max, step, page, page_sz);
1494 }
1495 
1496 static void
spin_configure(signal_user_data_t * ud,char * name,double val,double min,double max)1497 spin_configure(signal_user_data_t *ud, char *name, double val, double min, double max)
1498 {
1499     GtkSpinButton *spin;
1500     GtkAdjustment *adj;
1501     double step, page, page_sz;
1502 
1503     spin = GTK_SPIN_BUTTON(GHB_WIDGET(ud->builder, name));
1504 
1505     adj = gtk_spin_button_get_adjustment(spin);
1506     step = gtk_adjustment_get_step_increment(adj);
1507     page = gtk_adjustment_get_page_increment(adj);
1508     page_sz = gtk_adjustment_get_page_size(adj);
1509 
1510     adjustment_configure(adj, val, min, max, step, page, page_sz);
1511 }
1512 
1513 void
ghb_scale_configure(signal_user_data_t * ud,char * name,double val,double min,double max,double step,double page,int digits,gboolean inverted)1514 ghb_scale_configure(
1515     signal_user_data_t *ud,
1516     char *name,
1517     double val, double min, double max,
1518     double step, double page,
1519     int digits, gboolean inverted)
1520 {
1521     GtkScale *scale;
1522     GtkAdjustment *adj;
1523     double page_sz;
1524 
1525     scale = GTK_SCALE(GHB_WIDGET(ud->builder, name));
1526 
1527     gtk_scale_set_draw_value(scale, FALSE);
1528     adj = gtk_range_get_adjustment(GTK_RANGE(scale));
1529     page_sz = gtk_adjustment_get_page_size(adj);
1530 
1531     adjustment_configure(adj, val, min, max, step, page, page_sz);
1532 
1533     gtk_scale_set_digits(scale, digits);
1534     gtk_range_set_inverted(GTK_RANGE(scale), inverted);
1535     gtk_scale_set_draw_value(scale, TRUE);
1536 }
1537 
1538 void
ghb_set_widget_ranges(signal_user_data_t * ud,GhbValue * settings)1539 ghb_set_widget_ranges(signal_user_data_t *ud, GhbValue *settings)
1540 {
1541     int title_id, titleindex;
1542     const hb_title_t * title;
1543     double val;
1544 
1545     title_id = ghb_dict_get_int(settings, "title");
1546     title = ghb_lookup_title(title_id, &titleindex);
1547 
1548     // Reconfigure the UI combo boxes
1549     ghb_update_ui_combo_box(ud, "AudioTrack", title, FALSE);
1550     ghb_update_ui_combo_box(ud, "SubtitleTrack", title, FALSE);
1551 
1552     if (title != NULL)
1553     {
1554 
1555         // Set the limits of cropping.  hb_set_anamorphic_size crashes if
1556         // you pass it a cropped width or height == 0.
1557         gint vbound, hbound;
1558         vbound = title->geometry.height;
1559         hbound = title->geometry.width;
1560 
1561         val = ghb_dict_get_int(ud->settings, "PictureTopCrop");
1562         spin_configure(ud, "PictureTopCrop", val, 0, vbound);
1563         val = ghb_dict_get_int(ud->settings, "PictureBottomCrop");
1564         spin_configure(ud, "PictureBottomCrop", val, 0, vbound);
1565         val = ghb_dict_get_int(ud->settings, "PictureLeftCrop");
1566         spin_configure(ud, "PictureLeftCrop", val, 0, hbound);
1567         val = ghb_dict_get_int(ud->settings, "PictureRightCrop");
1568         spin_configure(ud, "PictureRightCrop", val, 0, hbound);
1569 
1570         gint duration = title->duration / 90000;
1571 
1572         if (ghb_settings_combo_int(ud->settings, "PtoPType") == 0)
1573         {
1574             gint num_chapters = hb_list_count(title->list_chapter);
1575 
1576             val = ghb_dict_get_int(ud->settings, "start_point");
1577             spin_configure(ud, "start_point", val, 1, num_chapters);
1578             val = ghb_dict_get_int(ud->settings, "end_point");
1579             spin_configure(ud, "end_point", val, 1, num_chapters);
1580         }
1581         else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 1)
1582         {
1583             val = ghb_dict_get_int(ud->settings, "start_point");
1584             spin_configure(ud, "start_point", val, 0, duration * 2 - 1);
1585             val = ghb_dict_get_int(ud->settings, "end_point");
1586             spin_configure(ud, "end_point", val, 0, duration * 2);
1587         }
1588         else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 2)
1589         {
1590             gdouble max_frames;
1591             max_frames = (gdouble)duration *
1592                          title->vrate.num / title->vrate.den;
1593 
1594             val = ghb_dict_get_int(ud->settings, "start_point");
1595             spin_configure(ud, "start_point", val, 1, max_frames * 2);
1596             val = ghb_dict_get_int(ud->settings, "end_point");
1597             spin_configure(ud, "end_point", val, 1, max_frames * 2);
1598         }
1599 
1600         val = ghb_dict_get_int(ud->settings, "angle");
1601         spin_configure(ud, "angle", val, 1, title->angle_count);
1602     }
1603 
1604     float vqmin, vqmax, step, page;
1605     int inverted, digits;
1606 
1607     ghb_vquality_range(ud, &vqmin, &vqmax, &step, &page, &digits, &inverted);
1608     val = ghb_dict_get_double(ud->settings, "VideoQualitySlider");
1609     ghb_scale_configure(ud, "VideoQualitySlider", val, vqmin, vqmax,
1610                         step, page, digits, inverted);
1611 }
1612 
1613 static void
check_chapter_markers(signal_user_data_t * ud)1614 check_chapter_markers(signal_user_data_t *ud)
1615 {
1616     GtkWidget *widget;
1617     gint start, end;
1618 
1619     if (ghb_settings_combo_int(ud->settings, "PtoPType") == 0)
1620     {
1621         start = ghb_dict_get_int(ud->settings, "start_point");
1622         end = ghb_dict_get_int(ud->settings, "end_point");
1623         widget = GHB_WIDGET (ud->builder, "ChapterMarkers");
1624         gtk_widget_set_sensitive(widget, end > start);
1625     }
1626 }
1627 
1628 void
ghb_load_settings(signal_user_data_t * ud)1629 ghb_load_settings(signal_user_data_t * ud)
1630 {
1631     const char      * fullname;
1632     int               type;
1633     gboolean          preset_modified;
1634     static gboolean   busy = FALSE;
1635 
1636     if (busy)
1637         return;
1638     busy = TRUE;
1639 
1640     fullname        = ghb_dict_get_string(ud->settings, "PresetFullName");
1641     type            = ghb_dict_get_int(ud->settings, "Type");
1642     preset_modified = ghb_dict_get_bool(ud->settings, "preset_modified");
1643     if (preset_modified)
1644     {
1645         ghb_clear_presets_selection(ud);
1646         ghb_preset_menu_button_refresh(ud, fullname, type);
1647     }
1648     else
1649     {
1650         ghb_dict_set_bool(ud->settings, "preset_reload", TRUE);
1651         ghb_select_preset(ud, fullname, type);
1652         ghb_dict_set_bool(ud->settings, "preset_reload", FALSE);
1653     }
1654 
1655     busy = FALSE;
1656 
1657     ghb_load_post_settings(ud);
1658 }
1659 
1660 void
ghb_load_post_settings(signal_user_data_t * ud)1661 ghb_load_post_settings(signal_user_data_t * ud)
1662 {
1663     static gboolean busy = FALSE;
1664     if (busy)
1665         return;
1666     busy = TRUE;
1667 
1668     ud->dont_clear_presets = TRUE;
1669     ud->scale_busy = TRUE;
1670 
1671     ghb_set_widget_ranges(ud, ud->settings);
1672     ghb_check_all_dependencies(ud);
1673     ghb_show_container_options(ud);
1674     check_chapter_markers(ud);
1675 
1676     ghb_clear_audio_selection(ud->builder);
1677     ghb_clear_subtitle_selection(ud->builder);
1678     ghb_settings_to_ui(ud, ud->settings);
1679     ghb_audio_defaults_to_ui(ud);
1680     ghb_subtitle_defaults_to_ui(ud);
1681     ghb_audio_list_refresh_all(ud);
1682     ghb_subtitle_list_refresh_all(ud);
1683     ghb_chapter_list_refresh_all(ud);
1684     update_title_duration(ud);
1685     ghb_update_title_info(ud);
1686 
1687     ud->dont_clear_presets = FALSE;
1688     ud->scale_busy = FALSE;
1689     busy = FALSE;
1690 
1691     ghb_picture_settings_deps(ud);
1692 }
1693 
1694 static void
show_scan_progress(signal_user_data_t * ud)1695 show_scan_progress(signal_user_data_t *ud)
1696 {
1697     GtkWidget      * widget;
1698     GtkProgressBar * progress;
1699     GtkLabel       * label;
1700 
1701     widget = GHB_WIDGET(ud->builder, "SourceInfoBox");
1702     gtk_widget_hide(widget);
1703 
1704     widget = GHB_WIDGET(ud->builder, "SourceScanBox");
1705     gtk_widget_show(widget);
1706 
1707     progress = GTK_PROGRESS_BAR(GHB_WIDGET(ud->builder, "scan_prog"));
1708     gtk_progress_bar_set_fraction(progress, 0);
1709 
1710     label = GTK_LABEL(GHB_WIDGET(ud->builder, "source_scan_label"));
1711     gtk_label_set_text( label, _("Scanning ...") );
1712 
1713 }
1714 
1715 static void
hide_scan_progress(signal_user_data_t * ud)1716 hide_scan_progress(signal_user_data_t *ud)
1717 {
1718     GtkWidget      * widget;
1719     GtkProgressBar * progress;
1720 
1721     progress = GTK_PROGRESS_BAR(GHB_WIDGET(ud->builder, "scan_prog"));
1722     gtk_progress_bar_set_fraction(progress, 1.0);
1723 
1724     widget = GHB_WIDGET(ud->builder, "SourceScanBox");
1725     gtk_widget_hide(widget);
1726 
1727     widget = GHB_WIDGET(ud->builder, "SourceInfoBox");
1728     gtk_widget_show(widget);
1729 }
1730 
1731 static void
start_scan(signal_user_data_t * ud,const gchar * path,gint title_id,gint preview_count)1732 start_scan(
1733     signal_user_data_t *ud,
1734     const gchar *path,
1735     gint title_id,
1736     gint preview_count)
1737 {
1738     GtkWidget *widget;
1739     ghb_status_t status;
1740 
1741     ghb_get_status(&status);
1742     if (status.scan.state != GHB_STATE_IDLE)
1743         return;
1744 
1745     widget = GHB_WIDGET(ud->builder, "sourcetoolbutton");
1746     gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-stop");
1747     gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Stop Scan"));
1748     gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Stop Scan"));
1749 
1750     widget = GHB_WIDGET(ud->builder, "source_open");
1751     gtk_widget_set_sensitive(widget, FALSE);
1752     widget = GHB_WIDGET(ud->builder, "source_title_open");
1753     gtk_widget_set_sensitive(widget, FALSE);
1754     ghb_backend_scan(path, title_id, preview_count,
1755             90000L * ghb_dict_get_int(ud->prefs, "MinTitleDuration"));
1756 }
1757 
1758 gboolean
ghb_idle_scan(signal_user_data_t * ud)1759 ghb_idle_scan(signal_user_data_t *ud)
1760 {
1761     gchar *path;
1762     // ghb_do_scan replaces "scan_source" key in dict, so we must
1763     // make a copy of the string.
1764     path = g_strdup(ghb_dict_get_string(ud->globals, "scan_source"));
1765     ghb_do_scan(ud, path, 0, TRUE);
1766     g_free(path);
1767     return FALSE;
1768 }
1769 
1770 extern GhbValue *ghb_queue_edit_settings;
1771 static gchar *last_scan_file = NULL;
1772 
1773 void
ghb_do_scan(signal_user_data_t * ud,const gchar * filename,gint title_id,gboolean force)1774 ghb_do_scan(
1775     signal_user_data_t *ud,
1776     const gchar *filename,
1777     gint title_id,
1778     gboolean force)
1779 {
1780     int titleindex;
1781     const hb_title_t *title;
1782 
1783     (void)title; // Silence "unused variable" warning
1784 
1785     g_debug("ghb_do_scan()");
1786     if (!force && last_scan_file != NULL &&
1787         strcmp(last_scan_file, filename) == 0)
1788     {
1789         if (ghb_queue_edit_settings != NULL)
1790         {
1791             title_id = ghb_dict_get_int(ghb_queue_edit_settings, "title");
1792             title = ghb_lookup_title(title_id, &titleindex);
1793             ghb_array_replace(ud->settings_array, titleindex,
1794                               ghb_queue_edit_settings);
1795             ud->settings = ghb_queue_edit_settings;
1796             ghb_load_settings(ud);
1797             ghb_queue_edit_settings = NULL;
1798         }
1799         else
1800         {
1801             title = ghb_lookup_title(title_id, &titleindex);
1802             load_all_titles(ud, titleindex);
1803         }
1804         return;
1805     }
1806     if (last_scan_file != NULL)
1807         g_free(last_scan_file);
1808     last_scan_file = NULL;
1809     if (filename != NULL)
1810     {
1811         const gchar *path;
1812         gint preview_count;
1813 
1814         last_scan_file = g_strdup(filename);
1815         ghb_dict_set_string(ud->globals, "scan_source", filename);
1816 
1817         show_scan_progress(ud);
1818         path = ghb_dict_get_string(ud->globals, "scan_source");
1819         prune_logs(ud);
1820 
1821         preview_count = ghb_dict_get_int(ud->prefs, "preview_count");
1822         start_scan(ud, path, title_id, preview_count);
1823     }
1824 }
1825 
1826 static void
do_source_dialog(gboolean single,signal_user_data_t * ud)1827 do_source_dialog(gboolean single, signal_user_data_t *ud)
1828 {
1829     GtkWidget *dialog;
1830     const gchar *sourcename;
1831     gint    response;
1832 
1833     g_debug("source_browse_clicked_cb ()");
1834     sourcename = ghb_dict_get_string(ud->globals, "scan_source");
1835     GtkWidget *widget;
1836     widget = GHB_WIDGET(ud->builder, "single_title_box");
1837     if (single)
1838         gtk_widget_show(widget);
1839     else
1840         gtk_widget_hide(widget);
1841     dialog = GHB_WIDGET(ud->builder, "source_dialog");
1842     source_dialog_extra_widgets(ud, dialog);
1843 
1844     gtk_widget_show(dialog);
1845     gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(dialog), sourcename);
1846 
1847     response = gtk_dialog_run(GTK_DIALOG (dialog));
1848     gtk_widget_hide(dialog);
1849     if (response == GTK_RESPONSE_NO)
1850     {
1851         gchar *filename;
1852 
1853         filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1854         if (filename != NULL)
1855         {
1856             gint title_id;
1857 
1858             if (single)
1859                 title_id = ghb_dict_get_int(ud->settings, "single_title");
1860             else
1861                 title_id = 0;
1862             // ghb_do_scan replaces "scan_source" key in dict, so we must
1863             // be finished with sourcename before calling ghb_do_scan
1864             // since the memory it references will be freed
1865             if (strcmp(sourcename, filename) != 0)
1866             {
1867                 ghb_dict_set_string(ud->prefs, "default_source", filename);
1868                 ghb_pref_save(ud->prefs, "default_source");
1869                 ghb_dvd_set_current(filename, ud);
1870             }
1871             ghb_do_scan(ud, filename, title_id, TRUE);
1872             g_free(filename);
1873         }
1874     }
1875 }
1876 
1877 #if 0
1878 G_MODULE_EXPORT void
1879 source_button_clicked_cb(GtkButton *button, signal_user_data_t *ud)
1880 {
1881     ghb_status_t status;
1882     ghb_get_status(&status);
1883     if (status.scan.state & GHB_STATE_SCANNING)
1884     {
1885         ghb_backend_scan_stop();
1886     }
1887     else
1888     {
1889         do_source_dialog(FALSE, ud);
1890     }
1891 }
1892 #else
1893 G_MODULE_EXPORT void
source_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)1894 source_action_cb(GSimpleAction *action, GVariant *param,
1895                  signal_user_data_t *ud)
1896 {
1897     ghb_status_t status;
1898     ghb_get_status(&status);
1899     if (status.scan.state & GHB_STATE_SCANNING)
1900     {
1901         ghb_backend_scan_stop();
1902     }
1903     else
1904     {
1905         do_source_dialog(FALSE, ud);
1906     }
1907 }
1908 #endif
1909 
1910 G_MODULE_EXPORT void
single_title_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)1911 single_title_action_cb(GSimpleAction *action, GVariant * param,
1912                        signal_user_data_t *ud)
1913 {
1914     do_source_dialog(TRUE, ud);
1915 }
1916 
1917 G_MODULE_EXPORT void
dvd_source_activate_cb(GtkWidget * widget,signal_user_data_t * ud)1918 dvd_source_activate_cb(GtkWidget *widget, signal_user_data_t *ud)
1919 {
1920     const gchar *filename;
1921     const gchar *sourcename;
1922 
1923     // ghb_do_scan replaces "scan_source" key in dict, so we must
1924     // be finished with sourcename before calling ghb_do_scan
1925     // since the memory it references will be freed
1926     sourcename = ghb_dict_get_string(ud->globals, "scan_source");
1927     filename = gtk_buildable_get_name(GTK_BUILDABLE(widget));
1928     if (strcmp(sourcename, filename) != 0)
1929     {
1930         ghb_dict_set_string(ud->prefs, "default_source", filename);
1931         ghb_pref_save(ud->prefs, "default_source");
1932         ghb_dvd_set_current(filename, ud);
1933     }
1934     ghb_do_scan(ud, filename, 0, TRUE);
1935 }
1936 
1937 void
ghb_update_destination_extension(signal_user_data_t * ud)1938 ghb_update_destination_extension(signal_user_data_t *ud)
1939 {
1940     static gchar *containers[] = {".mkv", ".mp4", ".m4v", ".webm", ".error", NULL};
1941     gchar *filename;
1942     const gchar *extension;
1943     gint ii;
1944     GtkEntry *entry;
1945     static gboolean busy = FALSE;
1946 
1947     g_debug("ghb_update_destination_extension ()");
1948     // Since this function modifies the thing that triggers it's
1949     // invocation, check to see if busy to prevent accidental infinite
1950     // recursion.
1951     if (busy)
1952         return;
1953     busy = TRUE;
1954     extension = get_extension(ud, ud->settings);
1955     entry = GTK_ENTRY(GHB_WIDGET(ud->builder, "dest_file"));
1956     filename = g_strdup(ghb_editable_get_text(entry));
1957     for (ii = 0; containers[ii] != NULL; ii++)
1958     {
1959         if (g_str_has_suffix(filename, containers[ii]))
1960         {
1961             gchar *pos;
1962             gchar *new_name;
1963 
1964             pos = g_strrstr( filename, "." );
1965             if (pos == NULL)
1966             {
1967                 // No period? shouldn't happen
1968                 break;
1969             }
1970             *pos = 0;
1971             if (strcmp(extension, &pos[1]) == 0)
1972             {
1973                 // Extension is already correct
1974                 break;
1975             }
1976             new_name = g_strjoin(".", filename, extension, NULL);
1977             ghb_ui_update(ud, "dest_file", ghb_string_value(new_name));
1978             g_free(new_name);
1979             break;
1980         }
1981     }
1982     g_free(filename);
1983     busy = FALSE;
1984 }
1985 
1986 static void
destination_select_title(GtkEntry * entry)1987 destination_select_title(GtkEntry *entry)
1988 {
1989     const gchar *dest;
1990     gint start, end;
1991 
1992     dest = ghb_editable_get_text(entry);
1993     for (end = strlen(dest)-1; end > 0; end--)
1994     {
1995         if (dest[end] == '.')
1996         {
1997             break;
1998         }
1999     }
2000     for (start = end; start >= 0; start--)
2001     {
2002         if (dest[start] == G_DIR_SEPARATOR)
2003         {
2004             start++;
2005             break;
2006         }
2007     }
2008     if (start < 0) start = 0;
2009     if (start < end)
2010     {
2011         gtk_editable_select_region(GTK_EDITABLE(entry), start, end);
2012     }
2013 }
2014 
2015 G_MODULE_EXPORT gboolean
destination_grab_cb(GtkEntry * entry,signal_user_data_t * ud)2016 destination_grab_cb(
2017     GtkEntry *entry,
2018     signal_user_data_t *ud)
2019 {
2020     destination_select_title(entry);
2021     return FALSE;
2022 }
2023 
2024 static void
update_default_destination(signal_user_data_t * ud)2025 update_default_destination(signal_user_data_t *ud)
2026 {
2027     const gchar *dest_dir, *def_dest;
2028 
2029     dest_dir = ghb_dict_get_string(ud->settings, "dest_dir");
2030     def_dest = ghb_dict_get_string(ud->prefs, "destination_dir");
2031     if (dest_dir != NULL && def_dest != NULL && dest_dir[0] != 0 &&
2032         strcmp(dest_dir, def_dest) != 0)
2033     {
2034         ghb_dict_set_string(ud->prefs, "destination_dir", dest_dir);
2035         ghb_pref_save(ud->prefs, "destination_dir");
2036     }
2037 }
2038 
2039 G_MODULE_EXPORT void
dest_dir_set_cb(GtkFileChooserButton * dest_chooser,signal_user_data_t * ud)2040 dest_dir_set_cb(GtkFileChooserButton *dest_chooser, signal_user_data_t *ud)
2041 {
2042     const gchar *dest_file, *dest_dir;
2043     gchar *dest;
2044 
2045     g_debug("dest_dir_set_cb ()");
2046     ghb_widget_to_setting(ud->settings, (GtkWidget*)dest_chooser);
2047     dest_file = ghb_dict_get_string(ud->settings, "dest_file");
2048     dest_dir = ghb_dict_get_string(ud->settings, "dest_dir");
2049     dest = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dest_dir, dest_file);
2050     ghb_dict_set_string(ud->settings, "destination", dest);
2051     GhbValue *dest_dict = ghb_get_job_dest_settings(ud->settings);
2052     ghb_dict_set_string(dest_dict, "File", dest);
2053     g_free(dest);
2054     update_default_destination(ud);
2055 }
2056 
2057 G_MODULE_EXPORT void
dest_file_changed_cb(GtkEntry * entry,signal_user_data_t * ud)2058 dest_file_changed_cb(GtkEntry *entry, signal_user_data_t *ud)
2059 {
2060     const gchar *dest_file, *dest_dir;
2061     gchar *dest;
2062 
2063     g_debug("dest_file_changed_cb ()");
2064     ghb_update_destination_extension(ud);
2065     ghb_widget_to_setting(ud->settings, (GtkWidget*)entry);
2066     // This signal goes off with ever keystroke, so I'm putting this
2067     // update on the timer.
2068     dest_file = ghb_dict_get_string(ud->settings, "dest_file");
2069     dest_dir = ghb_dict_get_string(ud->settings, "dest_dir");
2070     dest = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dest_dir, dest_file);
2071     ghb_dict_set_string(ud->settings, "destination", dest);
2072     GhbValue *dest_dict = ghb_get_job_dest_settings(ud->settings);
2073     ghb_dict_set_string(dest_dict, "File", dest);
2074     g_free(dest);
2075 }
2076 
2077 G_MODULE_EXPORT void
destination_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2078 destination_action_cb(GSimpleAction *action, GVariant *param,
2079                       signal_user_data_t *ud)
2080 {
2081     GtkWidget *dialog;
2082     GtkEntry *entry;
2083     const gchar *destname;
2084     gchar *basename;
2085     GtkWindow *hb_window;
2086 
2087     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
2088     destname = ghb_dict_get_string(ud->settings, "destination");
2089     dialog = gtk_file_chooser_dialog_new("Choose Destination",
2090                       hb_window,
2091                       GTK_FILE_CHOOSER_ACTION_SAVE,
2092                       GHB_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2093                       GHB_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2094                       NULL);
2095     gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), destname);
2096     basename = g_path_get_basename(destname);
2097     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), basename);
2098     g_free(basename);
2099     if (gtk_dialog_run(GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2100     {
2101         char *filename, *dirname;
2102         GtkFileChooser *dest_chooser;
2103 
2104         filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2105         basename = g_path_get_basename(filename);
2106         dirname = g_path_get_dirname(filename);
2107         entry = (GtkEntry*)GHB_WIDGET(ud->builder, "dest_file");
2108         ghb_editable_set_text(entry, basename);
2109         dest_chooser = GTK_FILE_CHOOSER(GHB_WIDGET(ud->builder, "dest_dir"));
2110         gtk_file_chooser_set_filename(dest_chooser, dirname);
2111         g_free (dirname);
2112         g_free (basename);
2113         g_free (filename);
2114     }
2115     gtk_widget_destroy(dialog);
2116 }
2117 
2118 G_MODULE_EXPORT gboolean
window_destroy_event_cb(GtkWidget * widget,GdkEvent * event,signal_user_data_t * ud)2119 window_destroy_event_cb(
2120     GtkWidget *widget,
2121 #if !GTK_CHECK_VERSION(3, 90, 0)
2122     GdkEvent *event,
2123 #endif
2124     signal_user_data_t *ud)
2125 {
2126     ghb_hb_cleanup(FALSE);
2127     prune_logs(ud);
2128     g_application_quit(G_APPLICATION(ud->app));
2129     return FALSE;
2130 }
2131 
2132 G_MODULE_EXPORT gboolean
window_delete_event_cb(GtkWidget * widget,GdkEvent * event,signal_user_data_t * ud)2133 window_delete_event_cb(
2134     GtkWidget *widget,
2135 #if !GTK_CHECK_VERSION(3, 90, 0)
2136     GdkEvent *event,
2137 #endif
2138     signal_user_data_t *ud)
2139 {
2140     gint state = ghb_get_queue_state();
2141     if (state & (GHB_STATE_WORKING|GHB_STATE_SEARCHING))
2142     {
2143         if (ghb_cancel_encode2(ud,
2144             _("Closing HandBrake will terminate encoding.\n")))
2145         {
2146             ghb_hb_cleanup(FALSE);
2147             prune_logs(ud);
2148             g_application_quit(G_APPLICATION(ud->app));
2149             return FALSE;
2150         }
2151         return TRUE;
2152     }
2153     ghb_hb_cleanup(FALSE);
2154     prune_logs(ud);
2155     g_application_quit(G_APPLICATION(ud->app));
2156     return FALSE;
2157 }
2158 
2159 static void
update_acodec(signal_user_data_t * ud)2160 update_acodec(signal_user_data_t *ud)
2161 {
2162     ghb_audio_list_refresh_all(ud);
2163     ghb_grey_combo_options(ud);
2164 }
2165 
2166 G_MODULE_EXPORT void
container_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2167 container_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2168 {
2169     g_debug("container_changed_cb ()");
2170     ghb_widget_to_setting(ud->settings, widget);
2171     const char * mux_id = ghb_dict_get_string(ud->settings, "FileFormat");
2172     GhbValue *dest_dict = ghb_get_job_dest_settings(ud->settings);
2173     ghb_dict_set_string(dest_dict, "Mux", mux_id);
2174 
2175     const hb_container_t *mux = ghb_lookup_container_by_name(mux_id);
2176     if (!(mux->format & HB_MUX_MASK_MP4))
2177     {
2178         ghb_ui_update(ud, "AlignAVStart", ghb_boolean_value(FALSE));
2179     }
2180 
2181     ghb_check_dependency(ud, widget, NULL);
2182     ghb_show_container_options(ud);
2183     update_acodec(ud);
2184     ghb_update_destination_extension(ud);
2185     ghb_clear_presets_selection(ud);
2186     ghb_live_reset(ud);
2187     ghb_subtitle_prune(ud);
2188     ghb_subtitle_list_refresh_all(ud);
2189     ghb_audio_list_refresh_selected(ud);
2190 }
2191 
2192 static gchar*
get_aspect_string(gint aspect_n,gint aspect_d)2193 get_aspect_string(gint aspect_n, gint aspect_d)
2194 {
2195     gchar *aspect;
2196 
2197     if (aspect_d < 10)
2198     {
2199         aspect = g_strdup_printf("%d:%d", aspect_n, aspect_d);
2200     }
2201     else
2202     {
2203         gdouble aspect_nf = (gdouble)aspect_n / aspect_d;
2204         aspect = g_strdup_printf("%.2f:1", aspect_nf);
2205     }
2206     return aspect;
2207 }
2208 
2209 void
ghb_update_title_info(signal_user_data_t * ud)2210 ghb_update_title_info(signal_user_data_t *ud)
2211 {
2212     GtkWidget           * widget;
2213     gchar               * text;
2214     gchar               * aspect;
2215     gchar               * rate;
2216     int                   title_id, titleindex;
2217     int                   audio_count, subtitle_count;
2218     const hb_title_t    * title;
2219     const hb_geometry_t * geo;
2220     gint                  aspect_n, aspect_d;
2221 
2222     title_id = ghb_dict_get_int(ud->settings, "title");
2223     title = ghb_lookup_title(title_id, &titleindex);
2224     if (title == NULL)
2225         return;
2226 
2227     update_title_duration(ud);
2228 
2229     geo = &title->geometry;
2230     hb_reduce(&aspect_n, &aspect_d, geo->width * geo->par.num,
2231               geo->height * geo->par.den);
2232     aspect = get_aspect_string(aspect_n, aspect_d);
2233     rate   = g_strdup_printf("%.6g", (gdouble)title->vrate.num /
2234                                               title->vrate.den);
2235     audio_count = hb_list_count(title->list_audio);
2236     subtitle_count = hb_list_count(title->list_subtitle);
2237 
2238     text = g_strdup_printf(
2239         ", %dx%d (%dx%d), %s, %s FPS, %d Audio Track%s, %d Subtitle Track%s",
2240         geo->width, geo->height,
2241         geo->width * geo->par.num / geo->par.den, geo->height,
2242         aspect, rate,
2243         audio_count, audio_count == 1 ? "" : "s",
2244         subtitle_count, subtitle_count == 1 ? "" : "s");
2245 
2246     widget = GHB_WIDGET(ud->builder, "source_info_label");
2247     gtk_label_set_text(GTK_LABEL(widget), text);
2248     free(text);
2249     free(rate);
2250 
2251     text = g_strdup_printf("%d x %d", geo->width, geo->height);
2252     ghb_ui_update(ud, "source_storage_size", ghb_string_value(text));
2253     g_free(text);
2254 
2255     text = g_strdup_printf("%d x %d", geo->width * geo->par.num / geo->par.den,
2256                            geo->height);
2257     ghb_ui_update(ud, "source_display_size", ghb_string_value(text));
2258     g_free(text);
2259 
2260     ghb_ui_update(ud, "source_aspect_ratio", ghb_string_value(aspect));
2261     free(aspect);
2262 }
2263 
update_meta(GhbValue * settings,const char * name,const char * val)2264 static void update_meta(GhbValue *settings, const char *name, const char *val)
2265 {
2266     GhbValue *metadata = ghb_get_job_metadata_settings(settings);
2267 
2268     if (val == NULL || val[0] == 0)
2269         ghb_dict_remove(metadata, name);
2270     else
2271         ghb_dict_set_string(metadata, name, val);
2272 }
2273 
2274 void
ghb_update_summary_info(signal_user_data_t * ud)2275 ghb_update_summary_info(signal_user_data_t *ud)
2276 {
2277     GString            * str;
2278     char               * text;
2279     int                  title_id;
2280     GtkWidget          * widget;
2281     GhbValue           * titleDict;
2282 
2283     title_id  = ghb_dict_get_int(ud->settings, "title");
2284     titleDict = ghb_get_title_dict(title_id);
2285     if (titleDict == NULL)
2286     {
2287         // No title, clear summary
2288         widget = GHB_WIDGET(ud->builder, "tracks_summary");
2289         gtk_label_set_text(GTK_LABEL(widget), "");
2290         widget = GHB_WIDGET(ud->builder, "filters_summary");
2291         gtk_label_set_text(GTK_LABEL(widget), "");
2292         widget = GHB_WIDGET(ud->builder, "dimensions_summary");
2293         gtk_label_set_text(GTK_LABEL(widget), "--");
2294         widget = GHB_WIDGET(ud->builder, "summary_image");
2295         gtk_widget_show(widget);
2296         widget = GHB_WIDGET(ud->builder, "preview_button_image");
2297         gtk_widget_hide(widget);
2298         return;
2299     }
2300 
2301     widget = GHB_WIDGET(ud->builder, "summary_image");
2302     gtk_widget_hide(widget);
2303     widget = GHB_WIDGET(ud->builder, "preview_button_image");
2304     gtk_widget_show(widget);
2305 
2306     // Video Track
2307     const hb_encoder_t * video_encoder;
2308     const hb_rate_t    * fps;
2309     hb_rational_t        vrate;
2310     char               * rate_str;
2311 
2312     str = g_string_new("");
2313     video_encoder = ghb_settings_video_encoder(ud->settings, "VideoEncoder");
2314     fps = ghb_settings_video_framerate(ud->settings, "VideoFramerate");
2315     if (fps->rate == 0)
2316     {
2317         hb_dict_extract_rational(&vrate, titleDict, "FrameRate");
2318     }
2319     else
2320     {
2321         vrate.num = 27000000;
2322         vrate.den = fps->rate;
2323     }
2324     rate_str = g_strdup_printf("%.6g", (gdouble)vrate.num / vrate.den);
2325     g_string_append_printf(str, "%s, %s FPS", video_encoder->name, rate_str);
2326     g_free(rate_str);
2327     if (ghb_dict_get_bool(ud->settings, "VideoFramerateCFR"))
2328     {
2329         g_string_append_printf(str, " CFR");
2330     }
2331     else if (ghb_dict_get_bool(ud->settings, "VideoFrameratePFR"))
2332     {
2333         g_string_append_printf(str, " PFR");
2334     }
2335     else if (ghb_dict_get_bool(ud->settings, "VideoFramerateVFR"))
2336     {
2337         g_string_append_printf(str, " VFR");
2338     }
2339 
2340     // Audio Tracks (show at most 3 tracks)
2341     GhbValue * audioList;
2342     GhbValue * sourceAudioList;
2343     int        ii, count, show;
2344 
2345     sourceAudioList = ghb_dict_get(titleDict, "AudioList");
2346     audioList    = ghb_get_job_audio_list(ud->settings);
2347     show = count = ghb_array_len(audioList);
2348     if (count > 3)
2349     {
2350         show = 2;
2351     }
2352     for (ii = 0; ii < show; ii++)
2353     {
2354         GhbValue           * asettings, * asource;
2355         const hb_mixdown_t * audio_mix;
2356         const hb_encoder_t * audio_encoder;
2357         const char         * lang;
2358         int                  track;
2359 
2360         asettings     = ghb_array_get(audioList, ii);
2361         track         = ghb_dict_get_int(asettings, "Track");
2362         asource       = ghb_array_get(sourceAudioList, track);
2363         lang          = ghb_dict_get_string(asource, "Language");
2364         audio_encoder = ghb_settings_audio_encoder(asettings, "Encoder");
2365         if (audio_encoder->codec & HB_ACODEC_PASS_FLAG)
2366         {
2367             g_string_append_printf(str, "\n%s, %s", lang, audio_encoder->name);
2368         }
2369         else
2370         {
2371             audio_mix = ghb_settings_mixdown(asettings, "Mixdown");
2372             g_string_append_printf(str, "\n%s, %s, %s", lang,
2373                                    audio_encoder->name, audio_mix->name);
2374         }
2375     }
2376     if (show < count)
2377     {
2378         g_string_append_printf(str, "\n+ %d more audio track%s", count - show,
2379                                count - show > 1 ? "s" : "");
2380     }
2381 
2382     // Subtitle Tracks (show at most 3 tracks)
2383     GhbValue * subtitleDict;
2384     GhbValue * searchDict;
2385     GhbValue * subtitleList;
2386     GhbValue * sourceSubtitleList;
2387     gboolean   search;
2388 
2389     sourceSubtitleList = ghb_dict_get(titleDict, "SubtitleList");
2390     subtitleDict       = ghb_get_job_subtitle_settings(ud->settings);
2391     subtitleList       = ghb_dict_get(subtitleDict, "SubtitleList");
2392     searchDict         = ghb_dict_get(subtitleDict, "Search");
2393     search             = ghb_dict_get_bool(searchDict, "Enable");
2394     show = count       = ghb_array_len(subtitleList) + search;
2395     if (count > 3)
2396     {
2397         show = 2;
2398     }
2399     if (search)
2400     {
2401         gboolean force, burn, def;
2402 
2403         force = ghb_dict_get_bool(searchDict, "Forced");
2404         burn  = ghb_dict_get_bool(searchDict, "Burn");
2405         def   = ghb_dict_get_bool(searchDict, "Default");
2406 
2407         g_string_append_printf(str, "\nForeign Audio Scan");
2408         if (force)
2409         {
2410             g_string_append_printf(str, ", Forced Only");
2411         }
2412         if (burn)
2413         {
2414             g_string_append_printf(str, ", Burned");
2415         }
2416         else if (def)
2417         {
2418             g_string_append_printf(str, ", Default");
2419         }
2420         show--;
2421         count--;
2422     }
2423     for (ii = 0; ii < show; ii++)
2424     {
2425         GhbValue           * subsettings, * subsource;
2426         int                  track;
2427         char               * desc;
2428         gboolean             force, burn, def;
2429 
2430         subsettings = ghb_array_get(subtitleList, ii);
2431         track       = ghb_dict_get_int(subsettings, "Track");
2432         subsource   = ghb_array_get(sourceSubtitleList, track);
2433         desc        = ghb_subtitle_short_description(subsource, subsettings);
2434         force       = ghb_dict_get_bool(subsettings, "Forced");
2435         burn        = ghb_dict_get_bool(subsettings, "Burn");
2436         def         = ghb_dict_get_bool(subsettings, "Default");
2437 
2438         g_string_append_printf(str, "\n%s", desc);
2439         free(desc);
2440         if (force)
2441         {
2442             g_string_append_printf(str, ", Forced Only");
2443         }
2444         if (burn)
2445         {
2446             g_string_append_printf(str, ", Burned");
2447         }
2448         else if (def)
2449         {
2450             g_string_append_printf(str, ", Default");
2451         }
2452     }
2453     if (show < count)
2454     {
2455         g_string_append_printf(str, "\n+ %d more subtitle track%s",
2456                                count - show,
2457                                count - show > 1 ? "s" : "");
2458     }
2459 
2460     if (ghb_dict_get_bool(ud->settings, "ChapterMarkers"))
2461     {
2462         g_string_append_printf(str, "\nChapter Markers");
2463     }
2464 
2465     text = g_string_free(str, FALSE);
2466     widget = GHB_WIDGET(ud->builder, "tracks_summary");
2467     gtk_label_set_text(GTK_LABEL(widget), text);
2468     g_free(text);
2469 
2470     // Filters
2471     gboolean     detel, comb_detect, deint, decomb, deblock, nlmeans, denoise;
2472     gboolean     unsharp, lapsharp, hflip, rot, gray, colorspace, chroma_smooth;
2473     const char * sval;
2474 
2475     sval        = ghb_dict_get_string(ud->settings, "PictureDetelecine");
2476     detel       = sval != NULL && !!strcasecmp(sval, "off");
2477     sval        = ghb_dict_get_string(ud->settings, "PictureCombDetectPreset");
2478     comb_detect = sval != NULL && !!strcasecmp(sval, "off");
2479     sval        = ghb_dict_get_string(ud->settings, "PictureDeinterlaceFilter");
2480     deint       = sval != NULL && !strcasecmp(sval, "deinterlace");
2481     decomb      = sval != NULL && !strcasecmp(sval, "decomb");
2482     sval        = ghb_dict_get_string(ud->settings, "PictureDeblockPreset");
2483     deblock     = sval != NULL && !!strcasecmp(sval, "off");
2484     sval        = ghb_dict_get_string(ud->settings, "PictureDenoiseFilter");
2485     nlmeans     = sval != NULL && !strcasecmp(sval, "nlmeans");
2486     denoise     = sval != NULL && !strcasecmp(sval, "hqdn3d");
2487     sval        = ghb_dict_get_string(ud->settings, "PictureSharpenFilter");
2488     unsharp     = sval != NULL && !strcasecmp(sval, "unsharp");
2489     lapsharp    = sval != NULL && !strcasecmp(sval, "lapsharp");
2490     hflip       = ghb_dict_get_bool(ud->settings, "hflip");
2491     sval        = ghb_dict_get_string(ud->settings, "rotate");
2492     rot         = sval != NULL && !!strcasecmp(sval, "0");
2493     gray        = ghb_dict_get_bool(ud->settings, "VideoGrayScale");
2494     sval        = ghb_dict_get_string(ud->settings, "PictureColorspacePreset");
2495     colorspace  = sval != NULL && !!strcasecmp(sval, "off");
2496     sval        = ghb_dict_get_string(ud->settings, "PictureChromaSmoothPreset");
2497     chroma_smooth  = sval != NULL && !!strcasecmp(sval, "off");
2498 
2499     str = g_string_new("");
2500     sval = "";
2501     if (detel)
2502     {
2503         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DETELECINE);
2504         g_string_append_printf(str, "%s%s", sval, filter->name);
2505         sval = ", ";
2506     }
2507     if (comb_detect)
2508     {
2509         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_COMB_DETECT);
2510         g_string_append_printf(str, "%s%s", sval, filter->name);
2511         sval = ", ";
2512     }
2513     if (deint)
2514     {
2515         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DEINTERLACE);
2516         g_string_append_printf(str, "%s%s", sval, filter->name);
2517         sval = ", ";
2518     }
2519     if (decomb)
2520     {
2521         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DECOMB);
2522         g_string_append_printf(str, "%s%s", sval, filter->name);
2523         sval = ", ";
2524     }
2525     if (deblock)
2526     {
2527         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DEBLOCK);
2528         g_string_append_printf(str, "%s%s", sval, filter->name);
2529         sval = ", ";
2530     }
2531     if (nlmeans)
2532     {
2533         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_NLMEANS);
2534         g_string_append_printf(str, "%s%s", sval, filter->name);
2535         sval = ", ";
2536     }
2537     if (denoise)
2538     {
2539         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_DENOISE);
2540         g_string_append_printf(str, "%s%s", sval, filter->name);
2541         sval = ", ";
2542     }
2543     if (chroma_smooth)
2544     {
2545         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_CHROMA_SMOOTH);
2546         g_string_append_printf(str, "%s%s", sval, filter->name);
2547         sval = ", ";
2548     }
2549     if (unsharp)
2550     {
2551         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_UNSHARP);
2552         g_string_append_printf(str, "%s%s", sval, filter->name);
2553         sval = ", ";
2554     }
2555     if (rot || hflip)
2556     {
2557         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_ROTATE);
2558         g_string_append_printf(str, "%s%s", sval, filter->name);
2559         sval = ", ";
2560     }
2561     if (lapsharp)
2562     {
2563         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_LAPSHARP);
2564         g_string_append_printf(str, "%s%s", sval, filter->name);
2565         sval = ", ";
2566     }
2567     if (gray)
2568     {
2569         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_GRAYSCALE);
2570         g_string_append_printf(str, "%s%s", sval, filter->name);
2571         sval = ", ";
2572     }
2573     if (colorspace)
2574     {
2575         hb_filter_object_t * filter = hb_filter_get(HB_FILTER_COLORSPACE);
2576         g_string_append_printf(str, "%s%s", sval, filter->name);
2577         sval = ", ";
2578     }
2579 
2580     text = g_string_free(str, FALSE);
2581     widget = GHB_WIDGET(ud->builder, "filters_summary");
2582     gtk_label_set_text(GTK_LABEL(widget), text);
2583     g_free(text);
2584 
2585     double display_width;
2586     int    width, height, display_height, par_width, par_height;
2587     char * display_aspect;
2588 
2589     width          = ghb_dict_get_int(ud->settings, "scale_width");
2590     height         = ghb_dict_get_int(ud->settings, "scale_height");
2591     display_width  = ghb_dict_get_int(ud->settings, "PictureDARWidth");
2592     display_height = ghb_dict_get_int(ud->settings, "DisplayHeight");
2593     par_width      = ghb_dict_get_int(ud->settings, "PicturePARWidth");
2594     par_height     = ghb_dict_get_int(ud->settings, "PicturePARHeight");
2595 
2596     display_width = (double)width * par_width / par_height;
2597     display_aspect = ghb_get_display_aspect_string(display_width,
2598                                                    display_height);
2599 
2600     display_width  = ghb_dict_get_int(ud->settings, "PictureDARWidth");
2601     text = g_strdup_printf("%dx%d storage, %dx%d display\n"
2602                            "%d:%d Pixel Aspect Ratio\n"
2603                             "%s Display Aspect Ratio",
2604                            width, height, (int)display_width, display_height,
2605                            par_width, par_height, display_aspect);
2606     widget = GHB_WIDGET(ud->builder, "dimensions_summary");
2607     gtk_label_set_text(GTK_LABEL(widget), text);
2608 
2609     g_free(text);
2610     g_free(display_aspect);
2611 
2612     ghb_value_free(&titleDict);
2613 }
2614 
2615 void
ghb_set_title_settings(signal_user_data_t * ud,GhbValue * settings)2616 ghb_set_title_settings(signal_user_data_t *ud, GhbValue *settings)
2617 {
2618     int title_id, titleindex;
2619     const hb_title_t * title;
2620 
2621     title_id = ghb_dict_get_int(settings, "title");
2622     title = ghb_lookup_title(title_id, &titleindex);
2623 
2624     ghb_subtitle_set_pref_lang(settings);
2625     if (title != NULL)
2626     {
2627         GhbValue * job_dict, * title_dict;
2628         char     * label;
2629 
2630         job_dict = hb_preset_job_init(ghb_scan_handle(), title_id, settings);
2631         ghb_dict_set(settings, "Job", job_dict);
2632         title_dict = hb_title_to_dict(ghb_scan_handle(), title_id);
2633         ghb_dict_set(settings, "Title", title_dict);
2634 
2635         gint num_chapters = hb_list_count(title->list_chapter);
2636 
2637         ghb_dict_set_int(settings, "angle", 1);
2638         ghb_dict_set_string(settings, "PtoPType", "chapter");
2639         ghb_dict_set_int(settings, "start_point", 1);
2640         ghb_dict_set_int(settings, "end_point", num_chapters);
2641         ghb_dict_set_int(settings, "source_width", title->geometry.width);
2642         ghb_dict_set_int(settings, "source_height", title->geometry.height);
2643         ghb_dict_set_string(settings, "source", title->path);
2644         label = ghb_create_source_label(title);
2645         ghb_dict_set_string(settings, "source_label", label);
2646         g_free(label);
2647         label = ghb_create_volume_label(title);
2648         ghb_dict_set_string(settings, "volume", label);
2649         g_free(label);
2650 
2651 
2652         int                angle, hflip;
2653         hb_geometry_crop_t srcGeo;
2654 
2655         srcGeo.geometry = title->geometry;
2656         memcpy(srcGeo.crop, &title->crop, 4 * sizeof(int));
2657         angle = ghb_dict_get_int(settings, "rotate");
2658         hflip = ghb_dict_get_int(settings, "hflip");
2659         hb_rotate_geometry(&srcGeo, &srcGeo, angle, hflip);
2660         ghb_apply_crop(settings, &srcGeo);
2661 
2662         int crop[4];
2663 
2664         crop[0] = ghb_dict_get_int(settings, "PictureTopCrop");
2665         crop[1] = ghb_dict_get_int(settings, "PictureBottomCrop");
2666         crop[2] = ghb_dict_get_int(settings, "PictureLeftCrop");
2667         crop[3] = ghb_dict_get_int(settings, "PictureRightCrop");
2668         ghb_dict_set_int(settings, "scale_width",
2669                  srcGeo.geometry.width - crop[2] - crop[3]);
2670 
2671         // If anamorphic or keep_aspect, the height will
2672         // be automatically calculated
2673         gboolean keep_aspect;
2674         gint pic_par;
2675         keep_aspect = ghb_dict_get_bool(settings, "PictureKeepRatio");
2676         pic_par = ghb_settings_combo_int(settings, "PicturePAR");
2677         if (!keep_aspect ||
2678             pic_par == HB_ANAMORPHIC_NONE ||
2679             pic_par == HB_ANAMORPHIC_AUTO ||
2680             pic_par == HB_ANAMORPHIC_CUSTOM)
2681         {
2682             ghb_dict_set_int(settings, "scale_height",
2683                 srcGeo.geometry.height - crop[0] - crop[1]);
2684         }
2685 
2686         ghb_set_scale_settings(ud, settings, GHB_PIC_USE_MAX);
2687         ghb_dict_set_int(settings, "angle_count", title->angle_count);
2688 
2689 
2690         // Clear UI settings for new title
2691         ghb_dict_set_string(settings, "MetaName", "");
2692         ghb_dict_set_string(settings, "MetaArtist", "");
2693         ghb_dict_set_string(settings, "MetaReleaseDate", "");
2694         ghb_dict_set_string(settings, "MetaComment", "");
2695         ghb_dict_set_string(settings, "MetaAlbumArtist", "");
2696         ghb_dict_set_string(settings, "MetaGenre", "");
2697         ghb_dict_set_string(settings, "MetaDescription", "");
2698         ghb_dict_set_string(settings, "MetaLongDescription", "");
2699 
2700         if (ghb_dict_get_bool(settings, "MetadataPassthrough"))
2701         {
2702             ghb_dict_set_string(settings, "MetaName", title->name);
2703             update_meta(settings, "Name", title->name);
2704             if (title->metadata && title->metadata->dict)
2705             {
2706 
2707                 hb_dict_iter_t iter = hb_dict_iter_init(title->metadata->dict);
2708 
2709                 while (iter != HB_DICT_ITER_DONE)
2710                 {
2711                     const char * key;
2712                     hb_value_t * val;
2713 
2714                     hb_dict_iter_next_ex(title->metadata->dict, &iter, &key, &val);
2715                     if (key != NULL && val != NULL)
2716                     {
2717                         const char * str = hb_value_get_string(val);
2718 
2719                         update_meta(settings, key, str);
2720 
2721                         if (!strcmp(key, "Album"))
2722                         {
2723                             key = "Name";
2724                         }
2725                         char * ui_key = g_strdup_printf("Meta%s", key);
2726                         ghb_dict_set_string(settings, ui_key, str);
2727                         free(ui_key);
2728                     }
2729                 }
2730             }
2731         }
2732         else
2733         {
2734             GhbValue * meta = ghb_get_job_metadata_settings(settings);
2735             hb_dict_clear(meta);
2736         }
2737         ghb_sanitize_audio_track_settings(settings);
2738     }
2739     else
2740     {
2741         ghb_dict_set_string(settings, "source_label", _("No Title Found"));
2742         ghb_dict_set_string(settings, "volume", _("New Video"));
2743         ghb_set_scale_settings(ud, settings, GHB_PIC_USE_MAX);
2744     }
2745 
2746     set_destination_settings(ud, settings);
2747 
2748     const char *dest_file, *dest_dir;
2749     char *dest;
2750     dest_file = ghb_dict_get_string(settings, "dest_file");
2751     dest_dir = ghb_dict_get_string(settings, "dest_dir");
2752     dest = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dest_dir, dest_file);
2753     ghb_dict_set_string(settings, "destination", dest);
2754     GhbValue *dest_dict = ghb_get_job_dest_settings(ud->settings);
2755     ghb_dict_set_string(dest_dict, "File", dest);
2756     g_free(dest);
2757 
2758     ghb_dict_set_int(settings, "preview_frame", 2);
2759 }
2760 
2761 void
ghb_set_current_title_settings(signal_user_data_t * ud)2762 ghb_set_current_title_settings(signal_user_data_t *ud)
2763 {
2764     ghb_set_title_settings(ud, ud->settings);
2765     ghb_update_summary_info(ud);
2766 }
2767 
2768 static void
load_all_titles(signal_user_data_t * ud,int titleindex)2769 load_all_titles(signal_user_data_t *ud, int titleindex)
2770 {
2771     gint ii, count;
2772     GhbValue *preset;
2773     GhbValue *settings_array;
2774     const hb_title_t *title;
2775 
2776     hb_list_t *list = ghb_get_title_list();
2777     count = hb_list_count(list);
2778 
2779     if (count == 0)
2780         count = 1;
2781 
2782     settings_array = ghb_array_new();
2783 
2784     // Start with a clean job
2785     ghb_dict_remove(ud->settings, "Job");
2786 
2787     preset = ghb_get_current_preset(ud);
2788     if (preset != NULL)
2789     {
2790         ghb_preset_to_settings(ud->settings, preset);
2791         ghb_value_free(&preset);
2792     }
2793     for (ii = 0; ii < count; ii++)
2794     {
2795         GhbValue *settings = ghb_value_dup(ud->settings);
2796 
2797         title = hb_list_item(list, ii);
2798         ghb_dict_set_int(settings, "title", title ? title->index : -1);
2799         ghb_set_title_settings(ud, settings);
2800         ghb_array_append(settings_array, settings);
2801     }
2802     if (titleindex < 0 || titleindex >= count)
2803     {
2804         titleindex = 0;
2805     }
2806     ghb_value_free(&ud->settings_array);
2807     ud->settings_array = settings_array;
2808     ud->settings = ghb_array_get(ud->settings_array, titleindex);
2809     ghb_update_summary_info(ud);
2810 }
2811 
2812 static gboolean update_preview = FALSE;
2813 
2814 G_MODULE_EXPORT void
title_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2815 title_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2816 {
2817     gint               title_id, titleindex, count;
2818     const hb_title_t * title;
2819     GtkLabel         * title_label;
2820     char             * label;
2821 
2822     title_id = ghb_widget_int(widget);
2823     title = ghb_lookup_title(title_id, &titleindex);
2824 
2825     label = ghb_create_title_label(title);
2826     title_label = GTK_LABEL(GHB_WIDGET(ud->builder, "title_label"));
2827     gtk_label_set_markup(title_label, label);
2828     g_free(label);
2829 
2830     count = ghb_array_len(ud->settings_array);
2831     int idx = (titleindex >= 0 && titleindex < count) ? titleindex : 0;
2832     if (ghb_dict_get_bool(ud->prefs, "SyncTitleSettings"))
2833     {
2834         GhbValue * preset   = ghb_settings_to_preset(ud->settings);
2835         GhbValue * settings = ghb_array_get(ud->settings_array, idx);
2836         if (preset != NULL)
2837         {
2838             ghb_preset_to_settings(settings, preset);
2839             ghb_set_title_settings(ud, settings);
2840         }
2841         ghb_value_free(&preset);
2842     }
2843     ud->settings = ghb_array_get(ud->settings_array, idx);
2844     ghb_load_settings(ud);
2845 
2846     ghb_audio_title_change(ud, title != NULL);
2847     ghb_subtitle_title_change(ud, title != NULL);
2848     ghb_grey_combo_options(ud);
2849 
2850     if (title != NULL)
2851     {
2852         gint preview_count;
2853         preview_count = title->preview_count;
2854         if (preview_count < 1)
2855         {
2856             preview_count = 1;
2857         }
2858         widget = GHB_WIDGET(ud->builder, "preview_frame");
2859         gtk_range_set_range(GTK_RANGE(widget), 1, preview_count);
2860 
2861         ghb_reset_preview_image(ud);
2862     }
2863     ghb_update_summary_info(ud);
2864 }
2865 
2866 G_MODULE_EXPORT void
ptop_widget_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2867 ptop_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2868 {
2869     gint title_id, titleindex;
2870     const hb_title_t * title;
2871     gboolean numeric = TRUE;
2872     GtkSpinButton *spin;
2873     GhbValue *range;
2874 
2875     ghb_widget_to_setting(ud->settings, widget);
2876     ghb_check_dependency(ud, widget, NULL);
2877     ghb_live_reset(ud);
2878 
2879     // Update type in Job
2880     range = ghb_get_job_range_settings(ud->settings);
2881     ghb_dict_set_string(range, "Type",
2882                         ghb_dict_get_string(ud->settings, "PtoPType"));
2883 
2884     title_id = ghb_dict_get_int(ud->settings, "title");
2885     title = ghb_lookup_title(title_id, &titleindex);
2886     if (title == NULL)
2887         return;
2888 
2889     if (ghb_settings_combo_int(ud->settings, "PtoPType") == 1)
2890         numeric = FALSE;
2891 
2892     spin = GTK_SPIN_BUTTON(GHB_WIDGET(ud->builder, "start_point"));
2893     gtk_spin_button_set_numeric(spin, numeric);
2894     spin = GTK_SPIN_BUTTON(GHB_WIDGET(ud->builder, "end_point"));
2895     gtk_spin_button_set_numeric(spin, numeric);
2896 
2897     gint duration = title->duration / 90000;
2898     if (ghb_settings_combo_int(ud->settings, "PtoPType") == 0)
2899     {
2900         gint num_chapters = hb_list_count(title->list_chapter);
2901         spin_configure(ud, "start_point", 1, 1, num_chapters);
2902         spin_configure(ud, "end_point", num_chapters, 1, num_chapters);
2903     }
2904     else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 1)
2905     {
2906         spin_configure(ud, "start_point", 0, 0, duration * 2 - 1);
2907         spin_configure(ud, "end_point", duration, 0, duration * 2);
2908     }
2909     else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 2)
2910     {
2911         gdouble max_frames = (gdouble)duration *
2912                              title->vrate.num / title->vrate.den;
2913         spin_configure(ud, "start_point", 1, 1, max_frames * 2);
2914         spin_configure(ud, "end_point", max_frames, 1, max_frames * 2);
2915     }
2916 }
2917 
2918 G_MODULE_EXPORT void
setting_widget_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2919 setting_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2920 {
2921     ghb_widget_to_setting(ud->settings, widget);
2922     ghb_check_dependency(ud, widget, NULL);
2923     ghb_update_summary_info(ud);
2924     ghb_clear_presets_selection(ud);
2925     ghb_live_reset(ud);
2926 }
2927 
2928 G_MODULE_EXPORT void
meta_pass_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2929 meta_pass_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2930 {
2931     setting_widget_changed_cb(widget, ud);
2932 
2933     int title_id, titleindex;
2934     const hb_title_t * title;
2935 
2936     title_id = ghb_dict_get_int(ud->settings, "title");
2937     title = ghb_lookup_title(title_id, &titleindex);
2938 
2939     if (title != NULL &&
2940         ghb_dict_get_bool(ud->settings, "MetadataPassthrough"))
2941     {
2942         // Reload metadata from title
2943         ghb_dict_set_string(ud->settings, "MetaName", title->name);
2944         update_meta(ud->settings, "Name", title->name);
2945         if (title->metadata && title->metadata->dict)
2946         {
2947 
2948             hb_dict_iter_t iter = hb_dict_iter_init(title->metadata->dict);
2949 
2950             while (iter != HB_DICT_ITER_DONE)
2951             {
2952                 const char * key;
2953                 hb_value_t * val;
2954 
2955                 hb_dict_iter_next_ex(title->metadata->dict, &iter, &key, &val);
2956                 if (key != NULL && val != NULL)
2957                 {
2958                     const char * str = hb_value_get_string(val);
2959 
2960                     update_meta(ud->settings, key, str);
2961 
2962                     if (!strcmp(key, "Album"))
2963                     {
2964                         key = "Name";
2965                     }
2966                     char * ui_key = g_strdup_printf("Meta%s", key);
2967                     ghb_dict_set_string(ud->settings, ui_key, str);
2968                     free(ui_key);
2969                 }
2970             }
2971         }
2972     }
2973     else
2974     {
2975         // Clear metadata
2976         ghb_dict_set_string(ud->settings, "MetaName", "");
2977         ghb_dict_set_string(ud->settings, "MetaArtist", "");
2978         ghb_dict_set_string(ud->settings, "MetaReleaseDate", "");
2979         ghb_dict_set_string(ud->settings, "MetaComment", "");
2980         ghb_dict_set_string(ud->settings, "MetaAlbumArtist", "");
2981         ghb_dict_set_string(ud->settings, "MetaGenre", "");
2982         ghb_dict_set_string(ud->settings, "MetaDescription", "");
2983         ghb_dict_set_string(ud->settings, "MetaLongDescription", "");
2984 
2985         GhbValue * meta = ghb_get_job_metadata_settings(ud->settings);
2986         hb_dict_clear(meta);
2987     }
2988 
2989     // Update UI
2990     ghb_ui_update_from_settings(ud, "MetaName", ud->settings);
2991     ghb_ui_update_from_settings(ud, "MetaArtist", ud->settings);
2992     ghb_ui_update_from_settings(ud, "MetaReleaseDate", ud->settings);
2993     ghb_ui_update_from_settings(ud, "MetaComment", ud->settings);
2994     ghb_ui_update_from_settings(ud, "MetaAlbumArtist", ud->settings);
2995     ghb_ui_update_from_settings(ud, "MetaGenre", ud->settings);
2996     ghb_ui_update_from_settings(ud, "MetaDescription", ud->settings);
2997     ghb_ui_update_from_settings(ud, "MetaLongDescription", ud->settings);
2998 }
2999 
3000 G_MODULE_EXPORT void
filter_widget_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3001 filter_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3002 {
3003     setting_widget_changed_cb(widget, ud);
3004     update_preview = TRUE;
3005 }
3006 
3007 G_MODULE_EXPORT void
nonsetting_widget_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3008 nonsetting_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3009 {
3010     ghb_widget_to_setting(ud->settings, widget);
3011     ghb_check_dependency(ud, widget, NULL);
3012     ghb_update_summary_info(ud);
3013     ghb_live_reset(ud);
3014 }
3015 
3016 G_MODULE_EXPORT void
comb_detect_widget_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3017 comb_detect_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3018 {
3019     ghb_widget_to_setting(ud->settings, widget);
3020     ghb_check_dependency(ud, widget, NULL);
3021     ghb_clear_presets_selection(ud);
3022     ghb_live_reset(ud);
3023 
3024     const char * comb_detect;
3025     comb_detect = ghb_dict_get_string(ud->settings, "PictureCombDetectPreset");
3026     if (strcasecmp(comb_detect, "off"))
3027     {
3028         const char * deint;
3029         deint = ghb_dict_get_string(ud->settings, "PictureDeinterlaceFilter");
3030         if (!strcasecmp(deint, "off"))
3031         {
3032             ghb_ui_update(ud, "PictureDeinterlaceFilter",
3033                           ghb_string_value("decomb"));
3034         }
3035     }
3036     ghb_update_summary_info(ud);
3037 }
3038 
3039 G_MODULE_EXPORT void
deint_filter_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3040 deint_filter_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3041 {
3042     ghb_widget_to_setting(ud->settings, widget);
3043     ghb_check_dependency(ud, widget, NULL);
3044     ghb_clear_presets_selection(ud);
3045     ghb_live_reset(ud);
3046     ghb_update_ui_combo_box(ud, "PictureDeinterlacePreset", NULL, FALSE);
3047     ghb_ui_update(ud, "PictureDeinterlacePreset",
3048                   ghb_dict_get(ud->settings, "PictureDeinterlacePreset"));
3049 
3050     const char * deint;
3051     deint = ghb_dict_get_string(ud->settings, "PictureDeinterlaceFilter");
3052     if (!strcasecmp(deint, "off"))
3053     {
3054         ghb_ui_update(ud, "PictureCombDetectPreset",
3055                       ghb_string_value("off"));
3056     }
3057     ghb_update_summary_info(ud);
3058 }
3059 
3060 G_MODULE_EXPORT void
denoise_filter_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3061 denoise_filter_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3062 {
3063     ghb_widget_to_setting(ud->settings, widget);
3064     ghb_check_dependency(ud, widget, NULL);
3065     ghb_clear_presets_selection(ud);
3066     ghb_live_reset(ud);
3067     ghb_update_ui_combo_box(ud, "PictureDenoisePreset", NULL, FALSE);
3068     ghb_ui_update(ud, "PictureDenoisePreset",
3069                   ghb_dict_get(ud->settings, "PictureDenoisePreset"));
3070     ghb_update_summary_info(ud);
3071 }
3072 
3073 G_MODULE_EXPORT void
sharpen_filter_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3074 sharpen_filter_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3075 {
3076     ghb_widget_to_setting(ud->settings, widget);
3077     ghb_check_dependency(ud, widget, NULL);
3078     ghb_clear_presets_selection(ud);
3079     ghb_live_reset(ud);
3080     ghb_update_ui_combo_box(ud, "PictureSharpenPreset", NULL, FALSE);
3081     ghb_update_ui_combo_box(ud, "PictureSharpenTune", NULL, FALSE);
3082     ghb_ui_update(ud, "PictureSharpenPreset",
3083                   ghb_dict_get(ud->settings, "PictureSharpenPreset"));
3084     ghb_ui_update(ud, "PictureSharpenTune", ghb_string_value("none"));
3085     ghb_update_summary_info(ud);
3086 }
3087 
3088 G_MODULE_EXPORT void
title_angle_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3089 title_angle_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3090 {
3091     ghb_widget_to_setting(ud->settings, widget);
3092     ghb_check_dependency(ud, widget, NULL);
3093     ghb_live_reset(ud);
3094 
3095     GhbValue *source = ghb_get_job_source_settings(ud->settings);
3096     ghb_dict_set_int(source, "Angle", ghb_dict_get_int(ud->settings, "angle"));
3097 }
3098 
3099 G_MODULE_EXPORT void
meta_name_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3100 meta_name_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3101 {
3102     const char *val;
3103 
3104     ghb_widget_to_setting(ud->settings, widget);
3105     val = ghb_dict_get_string(ud->settings, "MetaName");
3106     update_meta(ud->settings, "Name", val);
3107 }
3108 
3109 G_MODULE_EXPORT void
meta_artist_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3110 meta_artist_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3111 {
3112     const char *val;
3113 
3114     ghb_widget_to_setting(ud->settings, widget);
3115     val = ghb_dict_get_string(ud->settings, "MetaArtist");
3116     update_meta(ud->settings, "Artist", val);
3117 }
3118 
3119 G_MODULE_EXPORT void
meta_album_artist_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3120 meta_album_artist_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3121 {
3122     const char *val;
3123 
3124     ghb_widget_to_setting(ud->settings, widget);
3125     val = ghb_dict_get_string(ud->settings, "MetaAlbumArtist");
3126     update_meta(ud->settings, "AlbumArtist", val);
3127 }
3128 
3129 G_MODULE_EXPORT void
meta_release_date_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3130 meta_release_date_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3131 {
3132     const char *val;
3133 
3134     ghb_widget_to_setting(ud->settings, widget);
3135     val = ghb_dict_get_string(ud->settings, "MetaReleaseDate");
3136     update_meta(ud->settings, "ReleaseDate", val);
3137 }
3138 
3139 G_MODULE_EXPORT void
meta_comment_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3140 meta_comment_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3141 {
3142     const char *val;
3143 
3144     ghb_widget_to_setting(ud->settings, widget);
3145     val = ghb_dict_get_string(ud->settings, "MetaComment");
3146     update_meta(ud->settings, "Comment", val);
3147 }
3148 
3149 G_MODULE_EXPORT void
meta_genre_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3150 meta_genre_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3151 {
3152     const char *val;
3153 
3154     ghb_widget_to_setting(ud->settings, widget);
3155     val = ghb_dict_get_string(ud->settings, "MetaGenre");
3156     update_meta(ud->settings, "Genre", val);
3157 }
3158 
3159 G_MODULE_EXPORT void
meta_description_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3160 meta_description_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3161 {
3162     const char *val;
3163 
3164     ghb_widget_to_setting(ud->settings, widget);
3165     val = ghb_dict_get_string(ud->settings, "MetaDescription");
3166     update_meta(ud->settings, "Description", val);
3167 }
3168 
3169 G_MODULE_EXPORT void
plot_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3170 plot_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3171 {
3172     GtkWidget  * textview;
3173     const char * val;
3174 
3175     textview = GTK_WIDGET(GHB_WIDGET(ud->builder, "MetaLongDescription"));
3176     ghb_widget_to_setting(ud->settings, textview);
3177     val = ghb_dict_get_string(ud->settings, "MetaLongDescription");
3178     update_meta(ud->settings, "LongDescription", val);
3179 }
3180 
3181 G_MODULE_EXPORT void
chapter_markers_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3182 chapter_markers_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3183 {
3184     ghb_widget_to_setting(ud->settings, widget);
3185     ghb_check_dependency(ud, widget, NULL);
3186     ghb_clear_presets_selection(ud);
3187     ghb_live_reset(ud);
3188 
3189     GhbValue *dest;
3190     int start, end;
3191     gboolean markers;
3192     dest     = ghb_get_job_dest_settings(ud->settings);
3193     markers  = ghb_dict_get_bool(ud->settings, "ChapterMarkers");
3194     start    = ghb_dict_get_int(ud->settings, "start_point");
3195     end      = ghb_dict_get_int(ud->settings, "end_point");
3196     markers &= (end > start);
3197     ghb_dict_set_bool(dest, "ChapterMarkers", markers);
3198     ghb_update_summary_info(ud);
3199 }
3200 
3201 G_MODULE_EXPORT void
vquality_type_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3202 vquality_type_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3203 {
3204     ghb_widget_to_setting(ud->settings, widget);
3205     ghb_check_dependency(ud, widget, NULL);
3206     ghb_clear_presets_selection(ud);
3207     ghb_live_reset(ud);
3208     if (check_name_template(ud, "{quality}") ||
3209         check_name_template(ud, "{bitrate}"))
3210         set_destination(ud);
3211 }
3212 
3213 G_MODULE_EXPORT void
vbitrate_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3214 vbitrate_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3215 {
3216     ghb_widget_to_setting(ud->settings, widget);
3217     ghb_check_dependency(ud, widget, NULL);
3218     ghb_clear_presets_selection(ud);
3219     ghb_live_reset(ud);
3220     if (check_name_template(ud, "{bitrate}"))
3221         set_destination(ud);
3222 }
3223 
3224 G_MODULE_EXPORT void
vquality_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3225 vquality_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3226 {
3227     ghb_widget_to_setting(ud->settings, widget);
3228     ghb_check_dependency(ud, widget, NULL);
3229     ghb_clear_presets_selection(ud);
3230     ghb_live_reset(ud);
3231 
3232     gint vcodec;
3233     double vquality;
3234 
3235     vcodec = ghb_settings_video_encoder_codec(ud->settings, "VideoEncoder");
3236     vquality = ghb_dict_get_double(ud->settings, "VideoQualitySlider");
3237     if (vcodec == HB_VCODEC_X264_8BIT && vquality < 1.0)
3238     {
3239         // Set Profile to auto for lossless x264
3240         ghb_ui_update(ud, "VideoProfile", ghb_string_value("auto"));
3241     }
3242 
3243     gdouble step;
3244     float min, max, min_step;
3245     int direction;
3246 
3247     hb_video_quality_get_limits(vcodec, &min, &max, &min_step, &direction);
3248     step = ghb_settings_combo_double(ud->prefs, "VideoQualityGranularity");
3249     if (step < min_step)
3250     {
3251         step = min_step;
3252     }
3253     gdouble val = gtk_range_get_value(GTK_RANGE(widget));
3254     if (val < 0)
3255     {
3256         val = ((int)((val - step / 2) / step)) * step;
3257     }
3258     else
3259     {
3260         val = ((int)((val + step / 2) / step)) * step;
3261     }
3262     if (val < min)
3263     {
3264         val = min;
3265     }
3266     if (val > max)
3267     {
3268         val = max;
3269     }
3270     gtk_range_set_value(GTK_RANGE(widget), val);
3271     if (check_name_template(ud, "{quality}"))
3272         set_destination(ud);
3273 }
3274 
3275 G_MODULE_EXPORT gboolean
ptop_input_cb(GtkWidget * widget,gdouble * val,signal_user_data_t * ud)3276 ptop_input_cb(GtkWidget *widget, gdouble *val, signal_user_data_t *ud)
3277 {
3278     if (ghb_settings_combo_int(ud->settings, "PtoPType") != 1)
3279         return FALSE;
3280 
3281     const gchar *text;
3282     int result;
3283     double ss = 0;
3284     int hh = 0, mm = 0;
3285 
3286     text = ghb_editable_get_text(widget);
3287     result = sscanf(text, "%2d:%2d:%lf", &hh, &mm, &ss);
3288     if (result <= 0)
3289         return FALSE;
3290     if (result == 1)
3291     {
3292         result = sscanf(text, "%lf", val);
3293         return TRUE;
3294     }
3295     *val = hh * 3600 + mm * 60 + ss;
3296     return TRUE;
3297 }
3298 
3299 G_MODULE_EXPORT gboolean
ptop_output_cb(GtkWidget * widget,signal_user_data_t * ud)3300 ptop_output_cb(GtkWidget *widget, signal_user_data_t *ud)
3301 {
3302     if (ghb_settings_combo_int(ud->settings, "PtoPType") != 1)
3303         return FALSE;
3304 
3305     GtkAdjustment *adjustment;
3306     gchar *text;
3307     double value, ss;
3308     int hh, mm;
3309 
3310     adjustment = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(widget));
3311     value = gtk_adjustment_get_value(adjustment);
3312     hh = value / 3600;
3313     value = value - hh * 3600;
3314     mm = value / 60;
3315     value = value - mm * 60;
3316     ss = value;
3317     text = g_strdup_printf ("%02d:%02d:%05.2f", hh, mm, ss);
3318     ghb_editable_set_text(widget, text);
3319     g_free (text);
3320 
3321     return TRUE;
3322 }
3323 
sanitize_ptop(signal_user_data_t * ud,int end_changed)3324 static void sanitize_ptop(signal_user_data_t * ud, int end_changed)
3325 {
3326     int64_t start, end;
3327 
3328     if (ghb_settings_combo_int(ud->settings, "PtoPType") == 0)
3329     {
3330         start = ghb_dict_get_int(ud->settings, "start_point");
3331         end   = ghb_dict_get_int(ud->settings, "end_point");
3332         if (start > end)
3333         {
3334             if (end_changed == 1)
3335                 ghb_ui_update(ud, "start_point", ghb_int_value(end));
3336             else
3337                 ghb_ui_update(ud, "end_point", ghb_int_value(start));
3338         }
3339     }
3340     else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 1)
3341     {
3342         start = ghb_dict_get_double(ud->settings, "start_point") * 90000;
3343         end   = ghb_dict_get_double(ud->settings, "end_point") * 90000;
3344         if (start >= end)
3345         {
3346             if (end_changed == 1)
3347                 ghb_ui_update(ud, "start_point", ghb_int_value(end-1));
3348             else
3349                 ghb_ui_update(ud, "end_point", ghb_int_value(start+1));
3350         }
3351     }
3352     else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 2)
3353     {
3354         start = ghb_dict_get_int(ud->settings, "start_point");
3355         end   = ghb_dict_get_int(ud->settings, "end_point");
3356         if (start > end)
3357         {
3358             if (end_changed == 1)
3359                 ghb_ui_update(ud, "start_point", ghb_int_value(end));
3360             else
3361                 ghb_ui_update(ud, "end_point", ghb_int_value(start));
3362         }
3363     }
3364 }
3365 
update_ptop(signal_user_data_t * ud)3366 static void update_ptop(signal_user_data_t * ud)
3367 {
3368     int64_t start, end;
3369     GhbValue *dest  = ghb_get_job_dest_settings(ud->settings);
3370     GhbValue *range = ghb_get_job_range_settings(ud->settings);
3371 
3372     if (ghb_settings_combo_int(ud->settings, "PtoPType") == 0)
3373     {
3374         start = ghb_dict_get_int(ud->settings, "start_point");
3375         end   = ghb_dict_get_int(ud->settings, "end_point");
3376         if (check_name_template(ud, "{chapters}"))
3377             set_destination(ud);
3378         GtkWidget *widget = GHB_WIDGET (ud->builder, "ChapterMarkers");
3379         gtk_widget_set_sensitive(widget, end > start);
3380         update_title_duration(ud);
3381 
3382         gboolean markers;
3383         markers  = ghb_dict_get_int(ud->settings, "ChapterMarkers");
3384         markers &= (end > start);
3385         ghb_dict_set_bool(dest, "ChapterMarkers", markers);
3386         ghb_dict_set_int(range, "Start", start);
3387         ghb_dict_set_int(range, "End", end);
3388     }
3389     else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 1)
3390     {
3391         start = ghb_dict_get_double(ud->settings, "start_point") * 90000;
3392         end   = ghb_dict_get_double(ud->settings, "end_point") * 90000;
3393         update_title_duration(ud);
3394 
3395         ghb_dict_set_int(range, "Start", start);
3396         ghb_dict_set_int(range, "End", end);
3397     }
3398     else if (ghb_settings_combo_int(ud->settings, "PtoPType") == 2)
3399     {
3400         start = ghb_dict_get_int(ud->settings, "start_point");
3401         end   = ghb_dict_get_int(ud->settings, "end_point");
3402         update_title_duration(ud);
3403 
3404         ghb_dict_set_int(range, "Start", start);
3405         ghb_dict_set_int(range, "End", end);
3406     }
3407 }
3408 
3409 G_MODULE_EXPORT void
ptop_edited_cb(GtkWidget * widget,signal_user_data_t * ud)3410 ptop_edited_cb(GtkWidget *widget, signal_user_data_t *ud)
3411 {
3412     if (ghb_settings_combo_int(ud->settings, "PtoPType") == 1)
3413         return;
3414 
3415     const gchar *text, *key;
3416     int val = 0;
3417 
3418     text = ghb_editable_get_text(widget);
3419     if (text == NULL || text[0] == 0)
3420         return;
3421     val = g_strtod(text, NULL);
3422     key = ghb_get_setting_key(widget);
3423     ghb_dict_set_int(ud->settings, key, val);
3424     update_ptop(ud);
3425 }
3426 
3427 G_MODULE_EXPORT void
start_point_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3428 start_point_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3429 {
3430     ghb_widget_to_setting(ud->settings, widget);
3431     sanitize_ptop(ud, 0);
3432     ghb_check_dependency(ud, widget, NULL);
3433     update_ptop(ud);
3434 }
3435 
3436 G_MODULE_EXPORT void
end_point_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3437 end_point_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3438 {
3439     ghb_widget_to_setting(ud->settings, widget);
3440     sanitize_ptop(ud, 1);
3441     ghb_check_dependency(ud, widget, NULL);
3442     update_ptop(ud);
3443 }
3444 
3445 G_MODULE_EXPORT void
scale_width_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3446 scale_width_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3447 {
3448     g_debug("scale_width_changed_cb ()");
3449     ghb_widget_to_setting(ud->settings, widget);
3450     ghb_check_dependency(ud, widget, NULL);
3451     ghb_clear_presets_selection(ud);
3452     if (gtk_widget_is_sensitive(widget))
3453         ghb_set_scale(ud, GHB_PIC_KEEP_WIDTH);
3454     update_preview = TRUE;
3455     ghb_live_reset(ud);
3456 }
3457 
3458 G_MODULE_EXPORT void
scale_height_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3459 scale_height_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3460 {
3461     g_debug("scale_height_changed_cb ()");
3462     ghb_widget_to_setting(ud->settings, widget);
3463     ghb_check_dependency(ud, widget, NULL);
3464     ghb_clear_presets_selection(ud);
3465     if (gtk_widget_is_sensitive(widget))
3466         ghb_set_scale(ud, GHB_PIC_KEEP_HEIGHT);
3467 
3468     update_preview = TRUE;
3469     ghb_live_reset(ud);
3470 }
3471 
3472 G_MODULE_EXPORT void
crop_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3473 crop_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3474 {
3475     g_debug("crop_changed_cb ()");
3476     ghb_widget_to_setting(ud->settings, widget);
3477     ghb_check_dependency(ud, widget, NULL);
3478     ghb_clear_presets_selection(ud);
3479     if (gtk_widget_is_sensitive(widget))
3480         ghb_set_scale(ud, 0);
3481     update_preview = TRUE;
3482     ghb_live_reset(ud);
3483 }
3484 
3485 G_MODULE_EXPORT void
pad_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3486 pad_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3487 {
3488     g_debug("pad_changed_cb ()");
3489     ghb_widget_to_setting(ud->settings, widget);
3490     ghb_check_dependency(ud, widget, NULL);
3491     ghb_clear_presets_selection(ud);
3492     ghb_live_reset(ud);
3493     if (gtk_widget_is_sensitive(widget))
3494         ghb_set_scale(ud, 0);
3495 
3496     update_preview = TRUE;
3497 }
3498 
3499 G_MODULE_EXPORT void
display_width_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3500 display_width_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3501 {
3502     g_debug("display_width_changed_cb ()");
3503     ghb_widget_to_setting(ud->settings, widget);
3504     ghb_check_dependency(ud, widget, NULL);
3505     ghb_clear_presets_selection(ud);
3506     ghb_live_reset(ud);
3507     if (gtk_widget_is_sensitive(widget))
3508         ghb_set_scale(ud, GHB_PIC_KEEP_DISPLAY_WIDTH);
3509 
3510     update_preview = TRUE;
3511 }
3512 
3513 G_MODULE_EXPORT void
display_height_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3514 display_height_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3515 {
3516     g_debug("display_height_changed_cb ()");
3517     ghb_widget_to_setting(ud->settings, widget);
3518     ghb_check_dependency(ud, widget, NULL);
3519     ghb_clear_presets_selection(ud);
3520     ghb_live_reset(ud);
3521     if (gtk_widget_is_sensitive(widget))
3522         ghb_set_scale(ud, GHB_PIC_KEEP_DISPLAY_HEIGHT);
3523 
3524     update_preview = TRUE;
3525 }
3526 
3527 G_MODULE_EXPORT void
par_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3528 par_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3529 {
3530     g_debug("par_changed_cb ()");
3531     ghb_widget_to_setting(ud->settings, widget);
3532     ghb_check_dependency(ud, widget, NULL);
3533     ghb_clear_presets_selection(ud);
3534     ghb_live_reset(ud);
3535     if (gtk_widget_is_sensitive(widget))
3536         ghb_set_scale(ud, GHB_PIC_KEEP_HEIGHT);
3537 
3538     update_preview = TRUE;
3539 }
3540 
3541 G_MODULE_EXPORT void
scale_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3542 scale_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3543 {
3544     g_debug("scale_changed_cb ()");
3545     ghb_widget_to_setting(ud->settings, widget);
3546     ghb_check_dependency(ud, widget, NULL);
3547     ghb_clear_presets_selection(ud);
3548     ghb_live_reset(ud);
3549     if (gtk_widget_is_sensitive(widget))
3550         ghb_set_scale(ud, 0);
3551     update_preview = TRUE;
3552 }
3553 
3554 G_MODULE_EXPORT void
rotate_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3555 rotate_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3556 {
3557     int angle, hflip, prev_angle, prev_hflip;
3558 
3559     g_debug("rotate_changed_cb ()");
3560     prev_angle = ghb_dict_get_int(ud->settings, "rotate");
3561     prev_hflip = ghb_dict_get_int(ud->settings, "hflip");
3562 
3563     ghb_widget_to_setting(ud->settings, widget);
3564     ghb_check_dependency(ud, widget, NULL);
3565     ghb_clear_presets_selection(ud);
3566     ghb_live_reset(ud);
3567 
3568     hb_geometry_crop_t   geo;
3569 
3570     // First normalize current settings to 0 angle 0 hflip
3571     geo.geometry.width   = ghb_dict_get_int(ud->settings, "scale_width");
3572     geo.geometry.height  = ghb_dict_get_int(ud->settings, "scale_height");
3573     geo.geometry.par.num =
3574         ghb_dict_get_int(ud->settings, "PicturePARWidth");
3575     geo.geometry.par.den =
3576         ghb_dict_get_int(ud->settings, "PicturePARHeight");
3577     geo.crop[0] = ghb_dict_get_int(ud->settings, "PictureTopCrop");
3578     geo.crop[1] = ghb_dict_get_int(ud->settings, "PictureBottomCrop");
3579     geo.crop[2] = ghb_dict_get_int(ud->settings, "PictureLeftCrop");
3580     geo.crop[3] = ghb_dict_get_int(ud->settings, "PictureRightCrop");
3581 
3582     geo.pad[0] = ghb_dict_get_int(ud->settings, "PicturePadTop");
3583     geo.pad[1] = ghb_dict_get_int(ud->settings, "PicturePadBottom");
3584     geo.pad[2] = ghb_dict_get_int(ud->settings, "PicturePadLeft");
3585     geo.pad[3] = ghb_dict_get_int(ud->settings, "PicturePadRight");
3586     // Normally hflip is applied, then rotation.
3587     // To revert, must apply rotation then hflip.
3588     hb_rotate_geometry(&geo, &geo, 360 - prev_angle, 0);
3589     hb_rotate_geometry(&geo, &geo, 0, prev_hflip);
3590 
3591     const hb_title_t   * title;
3592     int                  title_id, titleindex;
3593 
3594     title_id = ghb_dict_get_int(ud->settings, "title");
3595     title = ghb_lookup_title(title_id, &titleindex);
3596     if (title != NULL)
3597     {
3598         // If there is a title, reset to title width/height so that
3599         // dimension limits get properly re-applied
3600         geo.geometry.width = title->geometry.width;
3601         geo.geometry.height = title->geometry.height;
3602     }
3603 
3604     // rotate dimensions to new angle and hflip
3605     angle = ghb_dict_get_int(ud->settings, "rotate");
3606     hflip = ghb_dict_get_int(ud->settings, "hflip");
3607     hb_rotate_geometry(&geo, &geo, angle, hflip);
3608 
3609     ghb_dict_set_int(ud->settings, "scale_width",
3610              geo.geometry.width - geo.crop[2] - geo.crop[3]);
3611     ghb_dict_set_int(ud->settings, "scale_height",
3612              geo.geometry.height - geo.crop[0] - geo.crop[1]);
3613     ghb_dict_set_int(ud->settings, "PicturePARWidth",   geo.geometry.par.num);
3614     ghb_dict_set_int(ud->settings, "PicturePARHeight",  geo.geometry.par.den);
3615     ghb_dict_set_int(ud->settings, "PictureTopCrop",    geo.crop[0]);
3616     ghb_dict_set_int(ud->settings, "PictureBottomCrop", geo.crop[1]);
3617     ghb_dict_set_int(ud->settings, "PictureLeftCrop",   geo.crop[2]);
3618     ghb_dict_set_int(ud->settings, "PictureRightCrop",  geo.crop[3]);
3619 
3620     ghb_dict_set_int(ud->settings, "PicturePadTop",    geo.pad[0]);
3621     ghb_dict_set_int(ud->settings, "PicturePadBottom", geo.pad[1]);
3622     ghb_dict_set_int(ud->settings, "PicturePadLeft",   geo.pad[2]);
3623     ghb_dict_set_int(ud->settings, "PicturePadRight",  geo.pad[3]);
3624 
3625     ghb_set_scale(ud, 0);
3626     update_preview = TRUE;
3627 }
3628 
3629 G_MODULE_EXPORT void
resolution_limit_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3630 resolution_limit_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3631 {
3632     g_debug("resolution_limit_changed_cb ()");
3633     ghb_widget_to_setting(ud->settings, widget);
3634     ghb_check_dependency(ud, widget, NULL);
3635     ghb_clear_presets_selection(ud);
3636     ghb_live_reset(ud);
3637 
3638     const gchar * resolution_limit;
3639     int           width, height;
3640 
3641     resolution_limit = ghb_dict_get_string(ud->settings, "resolution_limit");
3642     ghb_lookup_resolution_limit_dimensions(resolution_limit, &width, &height);
3643     if (width >= 0 && height >= 0)
3644     {
3645         ghb_dict_set_int(ud->settings, "PictureWidth", width);
3646         ghb_dict_set_int(ud->settings, "PictureHeight", height);
3647     }
3648 
3649     ghb_set_scale(ud, GHB_PIC_KEEP_HEIGHT);
3650 
3651     update_preview = TRUE;
3652 }
3653 
3654 G_MODULE_EXPORT void
generic_entry_changed_cb(GtkEntry * entry,signal_user_data_t * ud)3655 generic_entry_changed_cb(GtkEntry *entry, signal_user_data_t *ud)
3656 {
3657     // Normally (due to user input) I only want to process the entry
3658     // when editing is done and the focus-out signal is sent.
3659     // But... there's always a but.
3660     // If the entry is changed by software, the focus-out signal is not sent.
3661     // The changed signal is sent ... so here we are.
3662     // I don't want to process upon every keystroke, so I prevent processing
3663     // while the widget has focus.
3664     g_debug("generic_entry_changed_cb ()");
3665     if (!gtk_widget_has_focus((GtkWidget*)entry))
3666     {
3667         ghb_widget_to_setting(ud->settings, (GtkWidget*)entry);
3668     }
3669 }
3670 
3671 G_MODULE_EXPORT void
preferences_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)3672 preferences_action_cb(GSimpleAction *action, GVariant *param,
3673                       signal_user_data_t *ud)
3674 {
3675     GtkWidget *dialog;
3676 
3677     dialog = GHB_WIDGET(ud->builder, "prefs_dialog");
3678     gtk_dialog_run(GTK_DIALOG(dialog));
3679     gtk_widget_hide(dialog);
3680     ghb_prefs_store();
3681 }
3682 
3683 typedef struct
3684 {
3685     GtkMessageDialog *dlg;
3686     const gchar *msg;
3687     const gchar *action;
3688     gint timeout;
3689     signal_user_data_t *ud;
3690 } countdown_t;
3691 
3692 static gboolean
quit_cb(countdown_t * cd)3693 quit_cb(countdown_t *cd)
3694 {
3695     gchar *str;
3696 
3697     cd->timeout--;
3698     if (cd->timeout == 0)
3699     {
3700         ghb_hb_cleanup(FALSE);
3701         prune_logs(cd->ud);
3702 
3703         gtk_widget_destroy (GTK_WIDGET(cd->dlg));
3704         g_application_quit(G_APPLICATION(cd->ud->app));
3705         return FALSE;
3706     }
3707     str = g_strdup_printf(_("%s\n\n%s in %d seconds ..."),
3708                             cd->msg, cd->action, cd->timeout);
3709     gtk_message_dialog_set_markup(cd->dlg, str);
3710     g_free(str);
3711     return TRUE;
3712 }
3713 
3714 static gboolean
shutdown_cb(countdown_t * cd)3715 shutdown_cb(countdown_t *cd)
3716 {
3717     gchar *str;
3718 
3719     cd->timeout--;
3720     if (cd->timeout == 0)
3721     {
3722         ghb_hb_cleanup(FALSE);
3723         prune_logs(cd->ud);
3724 
3725         ghb_shutdown_gsm();
3726         g_application_quit(G_APPLICATION(cd->ud->app));
3727         return FALSE;
3728     }
3729     str = g_strdup_printf(_("%s\n\n%s in %d seconds ..."),
3730                             cd->msg, cd->action, cd->timeout);
3731     gtk_message_dialog_set_markup(cd->dlg, str);
3732     g_free(str);
3733     return TRUE;
3734 }
3735 
3736 static gboolean
suspend_cb(countdown_t * cd)3737 suspend_cb(countdown_t *cd)
3738 {
3739     gchar *str;
3740 
3741     cd->timeout--;
3742     if (cd->timeout == 0)
3743     {
3744         gtk_widget_destroy (GTK_WIDGET(cd->dlg));
3745         ghb_suspend_gpm();
3746         return FALSE;
3747     }
3748     str = g_strdup_printf(_("%s\n\n%s in %d seconds ..."),
3749                             cd->msg, cd->action, cd->timeout);
3750     gtk_message_dialog_set_markup(cd->dlg, str);
3751     g_free(str);
3752     return TRUE;
3753 }
3754 
3755 void
ghb_countdown_dialog(GtkMessageType type,const gchar * message,const gchar * action,const gchar * cancel,GSourceFunc action_func,signal_user_data_t * ud,gint timeout)3756 ghb_countdown_dialog(
3757     GtkMessageType type,
3758     const gchar *message,
3759     const gchar *action,
3760     const gchar *cancel,
3761     GSourceFunc action_func,
3762     signal_user_data_t *ud,
3763     gint timeout)
3764 {
3765     GtkWindow *hb_window;
3766     GtkWidget *dialog;
3767     GtkResponseType response;
3768     guint timeout_id;
3769     countdown_t cd;
3770 
3771     cd.msg = message;
3772     cd.action = action;
3773     cd.timeout = timeout;
3774     cd.ud = ud;
3775 
3776     // Toss up a warning dialog
3777     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
3778     dialog = gtk_message_dialog_new(hb_window, GTK_DIALOG_MODAL,
3779                             type, GTK_BUTTONS_NONE,
3780                             _("%s\n\n%s in %d seconds ..."),
3781                             message, action, timeout);
3782     gtk_dialog_add_buttons( GTK_DIALOG(dialog),
3783                            cancel, GTK_RESPONSE_CANCEL,
3784                            NULL);
3785 
3786     cd.dlg = GTK_MESSAGE_DIALOG(dialog);
3787     timeout_id = g_timeout_add(1000, action_func, &cd);
3788     response = gtk_dialog_run(GTK_DIALOG(dialog));
3789     gtk_widget_destroy (dialog);
3790     if (response == GTK_RESPONSE_CANCEL)
3791     {
3792         GMainContext *mc;
3793         GSource *source;
3794 
3795         mc = g_main_context_default();
3796         source = g_main_context_find_source_by_id(mc, timeout_id);
3797         if (source != NULL)
3798             g_source_destroy(source);
3799     }
3800 }
3801 
3802 gboolean
ghb_message_dialog(GtkWindow * parent,GtkMessageType type,const gchar * message,const gchar * no,const gchar * yes)3803 ghb_message_dialog(GtkWindow *parent, GtkMessageType type, const gchar *message, const gchar *no, const gchar *yes)
3804 {
3805     GtkWidget *dialog;
3806     GtkResponseType response;
3807 
3808     // Toss up a warning dialog
3809     dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
3810                             type, GTK_BUTTONS_NONE,
3811                             "%s", message);
3812     gtk_dialog_add_buttons( GTK_DIALOG(dialog),
3813                            no, GTK_RESPONSE_NO,
3814                            yes, GTK_RESPONSE_YES, NULL);
3815     response = gtk_dialog_run(GTK_DIALOG(dialog));
3816     gtk_widget_destroy (dialog);
3817     if (response == GTK_RESPONSE_NO)
3818     {
3819         return FALSE;
3820     }
3821     return TRUE;
3822 }
3823 
3824 void
ghb_error_dialog(GtkWindow * parent,GtkMessageType type,const gchar * message,const gchar * cancel)3825 ghb_error_dialog(GtkWindow *parent, GtkMessageType type, const gchar *message, const gchar *cancel)
3826 {
3827     GtkWidget *dialog;
3828 
3829     // Toss up a warning dialog
3830     dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
3831                             type, GTK_BUTTONS_NONE,
3832                             "%s", message);
3833     gtk_dialog_add_buttons( GTK_DIALOG(dialog),
3834                            cancel, GTK_RESPONSE_CANCEL, NULL);
3835     gtk_dialog_run(GTK_DIALOG(dialog));
3836     gtk_widget_destroy (dialog);
3837 }
3838 
3839 void
ghb_cancel_encode(signal_user_data_t * ud,const gchar * extra_msg)3840 ghb_cancel_encode(signal_user_data_t *ud, const gchar *extra_msg)
3841 {
3842     GtkWindow *hb_window;
3843     GtkWidget *dialog;
3844     GtkResponseType response;
3845 
3846     if (extra_msg == NULL) extra_msg = "";
3847     // Toss up a warning dialog
3848     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
3849     dialog = gtk_message_dialog_new(hb_window, GTK_DIALOG_MODAL,
3850                 GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
3851                 _("%sYour movie will be lost if you don't continue encoding."),
3852                 extra_msg);
3853     gtk_dialog_add_buttons( GTK_DIALOG(dialog),
3854                            _("Cancel Current and Stop"), 1,
3855                            _("Cancel Current, Start Next"), 2,
3856                            _("Finish Current, then Stop"), 3,
3857                            _("Continue Encoding"), 4,
3858                            NULL);
3859     response = gtk_dialog_run(GTK_DIALOG(dialog));
3860     gtk_widget_destroy (dialog);
3861     switch ((int)response)
3862     {
3863         case 1:
3864             ghb_stop_queue();
3865             ud->cancel_encode = GHB_CANCEL_ALL;
3866             break;
3867         case 2:
3868             ghb_stop_queue();
3869             ud->cancel_encode = GHB_CANCEL_CURRENT;
3870             break;
3871         case 3:
3872             ud->cancel_encode = GHB_CANCEL_FINISH;
3873             break;
3874         case 4:
3875         default:
3876             ud->cancel_encode = GHB_CANCEL_NONE;
3877             break;
3878     }
3879 }
3880 
3881 gboolean
ghb_cancel_encode2(signal_user_data_t * ud,const gchar * extra_msg)3882 ghb_cancel_encode2(signal_user_data_t *ud, const gchar *extra_msg)
3883 {
3884     GtkWindow *hb_window;
3885     GtkWidget *dialog;
3886     GtkResponseType response;
3887 
3888     if (extra_msg == NULL) extra_msg = "";
3889     // Toss up a warning dialog
3890     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
3891     dialog = gtk_message_dialog_new(hb_window, GTK_DIALOG_MODAL,
3892                 GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE,
3893                 _("%sYour movie will be lost if you don't continue encoding."),
3894                 extra_msg);
3895     gtk_dialog_add_buttons( GTK_DIALOG(dialog),
3896                            _("Cancel Current and Stop"), 1,
3897                            _("Continue Encoding"), 4,
3898                            NULL);
3899     response = gtk_dialog_run(GTK_DIALOG(dialog));
3900     gtk_widget_destroy (dialog);
3901     switch ((int)response)
3902     {
3903         case 1:
3904             ghb_stop_queue();
3905             ud->cancel_encode = GHB_CANCEL_ALL;
3906             return TRUE;
3907         case 4:
3908         default:
3909             break;
3910     }
3911     return FALSE;
3912 }
3913 
3914 static void
start_new_log(signal_user_data_t * ud,GhbValue * uiDict)3915 start_new_log(signal_user_data_t *ud, GhbValue *uiDict)
3916 {
3917     time_t  _now;
3918     struct tm *now;
3919     gchar *log_path, *pos, *basename, *dest_dir;
3920     const gchar *destname;
3921 
3922     // queue_activity_buffer is about to be reused, make sure
3923     // queue is showing the correct buffer
3924     ghb_queue_select_log(ud);
3925     // Erase current contents of queue activity
3926     gtk_text_buffer_set_text(ud->queue_activity_buffer, "", 0);
3927 
3928     _now = time(NULL);
3929     now = localtime(&_now);
3930     destname = ghb_dict_get_string(uiDict, "destination");
3931     basename = g_path_get_basename(destname);
3932     if (ghb_dict_get_bool(ud->prefs, "EncodeLogLocation"))
3933     {
3934         dest_dir = g_path_get_dirname (destname);
3935     }
3936     else
3937     {
3938         dest_dir = ghb_get_user_config_dir("EncodeLogs");
3939     }
3940     pos = g_strrstr( basename, "." );
3941     if (pos != NULL)
3942     {
3943         *pos = 0;
3944     }
3945     log_path = g_strdup_printf("%s/%s %d-%02d-%02d %02d-%02d-%02d.log",
3946         dest_dir,
3947         basename,
3948         now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
3949         now->tm_hour, now->tm_min, now->tm_sec);
3950     g_free(basename);
3951     g_free(dest_dir);
3952     if (ud->job_activity_log)
3953         g_io_channel_unref(ud->job_activity_log);
3954     ud->job_activity_log = g_io_channel_new_file (log_path, "w", NULL);
3955     if (ud->job_activity_log)
3956     {
3957         gchar *ver_str;
3958 
3959         ver_str = g_strdup_printf("Handbrake Version: %s (%d)\n",
3960                                     hb_get_version(NULL), hb_get_build(NULL));
3961         g_io_channel_write_chars (ud->job_activity_log, ver_str,
3962                                     -1, NULL, NULL);
3963         g_free(ver_str);
3964         ghb_dict_set_string(uiDict, "ActivityFilename", log_path);
3965     }
3966     g_free(log_path);
3967 }
3968 
3969 static void
submit_job(signal_user_data_t * ud,GhbValue * queueDict)3970 submit_job(signal_user_data_t *ud, GhbValue *queueDict)
3971 {
3972     gchar *type, *modified;
3973     const char *name;
3974     GhbValue *uiDict;
3975     gboolean preset_modified;
3976 
3977     g_debug("submit_job");
3978     if (queueDict == NULL) return;
3979     uiDict = ghb_dict_get(queueDict, "uiSettings");
3980     preset_modified = ghb_dict_get_bool(uiDict, "preset_modified");
3981     name = ghb_dict_get_string(uiDict, "PresetFullName");
3982     type = ghb_dict_get_int(uiDict, "Type") == 1 ? "Custom " : "";
3983     modified = preset_modified ? "Modified " : "";
3984     ghb_log("%s%sPreset: %s", modified, type, name);
3985 
3986     ghb_dict_set_int(uiDict, "job_status", GHB_QUEUE_RUNNING);
3987     start_new_log(ud, uiDict);
3988     GhbValue *job_dict = ghb_dict_get(queueDict, "Job");
3989     int unique_id = ghb_add_job(ghb_queue_handle(), job_dict);
3990     ghb_dict_set_int(uiDict, "job_unique_id", unique_id);
3991     time_t now = time(NULL);
3992     ghb_dict_set_int(uiDict, "job_start_time", now);
3993     ghb_start_queue();
3994 
3995     // Show queue progress bar
3996     int index = ghb_find_queue_job(ud->queue, unique_id, NULL);
3997     ghb_queue_progress_set_visible(ud, index, 1);
3998 }
3999 
4000 static void
prune_logs(signal_user_data_t * ud)4001 prune_logs(signal_user_data_t *ud)
4002 {
4003     gchar *dest_dir;
4004     gint days;
4005 
4006     // Only prune logs stored in the default config dir location
4007     days = ghb_settings_combo_int(ud->prefs, "LogLongevity");
4008     if (days > 365)
4009         return;
4010 
4011     dest_dir = ghb_get_user_config_dir("EncodeLogs");
4012     if (g_file_test(dest_dir, G_FILE_TEST_IS_DIR))
4013     {
4014         const gchar *file;
4015         gint duration = days * 24 * 60 * 60;
4016 
4017         GDir *gdir = g_dir_open(dest_dir, 0, NULL);
4018         time_t now;
4019 
4020         now = time(NULL);
4021         file = g_dir_read_name(gdir);
4022         while (file)
4023         {
4024             gchar *path;
4025             struct stat stbuf;
4026 
4027             path = g_strdup_printf("%s/%s", dest_dir, file);
4028             g_stat(path, &stbuf);
4029             if (now - stbuf.st_mtime > duration)
4030             {
4031                 g_unlink(path);
4032             }
4033             g_free(path);
4034             file = g_dir_read_name(gdir);
4035         }
4036         g_dir_close(gdir);
4037     }
4038     g_free(dest_dir);
4039     ghb_preview_cleanup(ud);
4040 }
4041 
4042 static gint
queue_pending_count(GhbValue * queue)4043 queue_pending_count(GhbValue *queue)
4044 {
4045     gint nn, ii, count;
4046     GhbValue *queueDict, *uiDict;
4047     gint status;
4048 
4049     nn = 0;
4050     count = ghb_array_len(queue);
4051     for (ii = 0; ii < count; ii++)
4052     {
4053 
4054         queueDict = ghb_array_get(queue, ii);
4055         uiDict = ghb_dict_get(queueDict, "uiSettings");
4056         status = ghb_dict_get_int(uiDict, "job_status");
4057         if (status == GHB_QUEUE_PENDING)
4058         {
4059             nn++;
4060         }
4061     }
4062     return nn;
4063 }
4064 
4065 void
ghb_update_pending(signal_user_data_t * ud)4066 ghb_update_pending(signal_user_data_t *ud)
4067 {
4068     GtkLabel *label;
4069     gint pending;
4070     gchar *str;
4071 
4072     pending = queue_pending_count(ud->queue);
4073     if (pending == 1)
4074     {
4075         str = g_strdup_printf(_("%d encode pending"), pending);
4076     }
4077     else
4078     {
4079         str = g_strdup_printf(_("%d encodes pending"), pending);
4080     }
4081     label = GTK_LABEL(GHB_WIDGET(ud->builder, "pending_status"));
4082     gtk_label_set_text(label, str);
4083     label = GTK_LABEL(GHB_WIDGET(ud->builder, "queue_status_label"));
4084     gtk_label_set_text(label, str);
4085     g_free(str);
4086 
4087     update_queue_labels(ud);
4088 }
4089 
4090 void
ghb_start_next_job(signal_user_data_t * ud)4091 ghb_start_next_job(signal_user_data_t *ud)
4092 {
4093     gint count, ii;
4094     GhbValue *queueDict, *uiDict;
4095     gint status;
4096     GtkWidget *progress;
4097 
4098     g_debug("start_next_job");
4099     progress = GHB_WIDGET(ud->builder, "progressbar");
4100     gtk_widget_show(progress);
4101 
4102     count = ghb_array_len(ud->queue);
4103     for (ii = 0; ii < count; ii++)
4104     {
4105 
4106         queueDict = ghb_array_get(ud->queue, ii);
4107         uiDict = ghb_dict_get(queueDict, "uiSettings");
4108         status = ghb_dict_get_int(uiDict, "job_status");
4109         if (status == GHB_QUEUE_PENDING)
4110         {
4111             ghb_inhibit_suspend(ud);
4112             submit_job(ud, queueDict);
4113             ghb_update_pending(ud);
4114             return;
4115         }
4116     }
4117     // Nothing pending
4118     ghb_uninhibit_suspend(ud);
4119     ghb_notify_done(ud);
4120     ghb_update_pending(ud);
4121     gtk_widget_hide(progress);
4122     ghb_dict_set_bool(ud->globals, "SkipDiskFreeCheck", FALSE);
4123 }
4124 
4125 static gchar*
working_status_string(signal_user_data_t * ud,ghb_instance_status_t * status)4126 working_status_string(signal_user_data_t *ud, ghb_instance_status_t *status)
4127 {
4128     gchar *task_str, *job_str, *status_str;
4129     gint qcount;
4130     gint index;
4131 
4132     qcount = ghb_array_len(ud->queue);
4133     index = ghb_find_queue_job(ud->queue, status->unique_id, NULL);
4134     if (qcount > 1)
4135     {
4136         job_str = g_strdup_printf(_("job %d of %d, "), index+1, qcount);
4137     }
4138     else
4139     {
4140         job_str = g_strdup("");
4141     }
4142     if (status->pass_count > 1)
4143     {
4144         if (status->pass_id == HB_PASS_SUBTITLE)
4145         {
4146             task_str = g_strdup_printf(_("pass %d (subtitle scan) of %d, "),
4147                 status->pass, status->pass_count);
4148         }
4149         else
4150         {
4151             task_str = g_strdup_printf(_("pass %d of %d, "),
4152                 status->pass, status->pass_count);
4153         }
4154     }
4155     else
4156     {
4157         task_str = g_strdup("");
4158     }
4159     if(status->seconds > -1)
4160     {
4161         if (status->rate_cur > 0.0)
4162         {
4163             status_str= g_strdup_printf(
4164                 _("Encoding: %s%s%.2f %%"
4165                 " (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)"),
4166                 job_str, task_str,
4167                 100.0 * status->progress,
4168                 status->rate_cur, status->rate_avg, status->hours,
4169                 status->minutes, status->seconds );
4170         }
4171         else
4172         {
4173             status_str= g_strdup_printf(
4174                 _("Encoding: %s%s%.2f %%"
4175                 " (ETA %02dh%02dm%02ds)"),
4176                 job_str, task_str,
4177                 100.0 * status->progress,
4178                 status->hours, status->minutes, status->seconds );
4179         }
4180     }
4181     else
4182     {
4183         status_str= g_strdup_printf(
4184             _("Encoding: %s%s%.2f %%"),
4185             job_str, task_str,
4186             100.0 * status->progress );
4187     }
4188     g_free(task_str);
4189     g_free(job_str);
4190     return status_str;
4191 }
4192 
4193 static gchar*
searching_status_string(signal_user_data_t * ud,ghb_instance_status_t * status)4194 searching_status_string(signal_user_data_t *ud, ghb_instance_status_t *status)
4195 {
4196     gchar *task_str, *job_str, *status_str;
4197     gint qcount;
4198     gint index;
4199 
4200     qcount = ghb_array_len(ud->queue);
4201     index = ghb_find_queue_job(ud->queue, status->unique_id, NULL);
4202     if (qcount > 1)
4203     {
4204         job_str = g_strdup_printf(_("job %d of %d, "), index+1, qcount);
4205     }
4206     else
4207     {
4208         job_str = g_strdup("");
4209     }
4210     task_str = g_strdup_printf(_("Searching for start time, "));
4211     if(status->seconds > -1)
4212     {
4213         status_str= g_strdup_printf(
4214             _("Encoding: %s%s%.2f %%"
4215             " (ETA %02dh%02dm%02ds)"),
4216             job_str, task_str,
4217             100.0 * status->progress,
4218             status->hours, status->minutes, status->seconds );
4219     }
4220     else
4221     {
4222         status_str= g_strdup_printf(
4223             _("Encoding: %s%s%.2f %%"),
4224             job_str, task_str,
4225             100.0 * status->progress );
4226     }
4227     g_free(task_str);
4228     g_free(job_str);
4229     return status_str;
4230 }
4231 
4232 static void
ghb_backend_events(signal_user_data_t * ud)4233 ghb_backend_events(signal_user_data_t *ud)
4234 {
4235     ghb_status_t     status;
4236     gchar          * status_str;
4237     GtkProgressBar * progress;
4238     GtkLabel       * work_status;
4239     GhbValue       * queueDict = NULL;
4240     gint             index = -1;
4241     static gint      prev_scan_state = -1;
4242     static gint      prev_queue_state = -1;
4243     static gint      event_sequence = 0;
4244 
4245     event_sequence++;
4246     ghb_track_status();
4247     ghb_get_status(&status);
4248     if (prev_scan_state != status.scan.state ||
4249         prev_queue_state != status.queue.state)
4250     {
4251         ghb_queue_buttons_grey(ud);
4252         prev_scan_state = status.scan.state;
4253         prev_queue_state = status.queue.state;
4254     }
4255     progress = GTK_PROGRESS_BAR(GHB_WIDGET (ud->builder, "progressbar"));
4256     work_status = GTK_LABEL(GHB_WIDGET (ud->builder, "work_status"));
4257     if (status.scan.state == GHB_STATE_IDLE &&
4258         status.queue.state == GHB_STATE_IDLE)
4259     {
4260         static gboolean prev_dvdnav;
4261         gboolean dvdnav = ghb_dict_get_bool(ud->prefs, "use_dvdnav");
4262         if (dvdnav != prev_dvdnav)
4263         {
4264             hb_dvd_set_dvdnav(dvdnav);
4265             prev_dvdnav = dvdnav;
4266         }
4267     }
4268     // First handle the status of title scans
4269     // Then handle the status of the queue
4270     if (status.scan.state & GHB_STATE_SCANNING)
4271     {
4272         GtkProgressBar *scan_prog;
4273         GtkLabel *label;
4274 
4275         scan_prog = GTK_PROGRESS_BAR(GHB_WIDGET (ud->builder, "scan_prog"));
4276         label = GTK_LABEL(GHB_WIDGET(ud->builder, "source_scan_label"));
4277 
4278         if (status.scan.title_cur == 0)
4279         {
4280             status_str = g_strdup (_("Scanning..."));
4281         }
4282         else
4283         {
4284             if (status.scan.preview_cur == 0)
4285                 status_str = g_strdup_printf(_("Scanning title %d of %d..."),
4286                               status.scan.title_cur, status.scan.title_count );
4287             else
4288                 status_str = g_strdup_printf(
4289                     _("Scanning title %d of %d preview %d..."),
4290                     status.scan.title_cur, status.scan.title_count,
4291                     status.scan.preview_cur);
4292 
4293         }
4294         gtk_label_set_text(label, status_str);
4295         g_free(status_str);
4296         if (status.scan.title_count > 0)
4297         {
4298             gtk_progress_bar_set_fraction(scan_prog, status.scan.progress);
4299         }
4300     }
4301     else if (status.scan.state & GHB_STATE_SCANDONE)
4302     {
4303         GtkWidget *widget;
4304 
4305         widget = GHB_WIDGET(ud->builder, "sourcetoolbutton");
4306         gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(widget), "hb-source");
4307         gtk_tool_button_set_label(GTK_TOOL_BUTTON(widget), _("Open Source"));
4308         gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(widget), _("Choose Video Source"));
4309 
4310         widget = GHB_WIDGET(ud->builder, "source_open");
4311         gtk_widget_set_sensitive(widget, TRUE);
4312         widget = GHB_WIDGET(ud->builder, "source_title_open");
4313         gtk_widget_set_sensitive(widget, TRUE);
4314 
4315         hide_scan_progress(ud);
4316 
4317         int title_id, titleindex;
4318         const hb_title_t *title;
4319         title_id = ghb_longest_title();
4320         title = ghb_lookup_title(title_id, &titleindex);
4321         ghb_update_ui_combo_box(ud, "title", NULL, FALSE);
4322         load_all_titles(ud, titleindex);
4323 
4324         ghb_clear_scan_state(GHB_STATE_SCANDONE);
4325         // Are there really any titles.
4326         if (title == NULL)
4327         {
4328             ghb_ui_update(ud, "title", ghb_string_value("none"));
4329         }
4330         else
4331         {
4332             ghb_ui_update(ud, "title", ghb_int_value(title->index));
4333         }
4334 
4335         if (ghb_queue_edit_settings != NULL)
4336         {
4337             // Switch to the correct title in the list
4338             ghb_ui_update(ud, "title",
4339                 ghb_dict_get_value(ghb_queue_edit_settings, "title"));
4340 
4341             // The above should cause the current title index to update
4342             title_id = ghb_dict_get_int(ud->settings, "title");
4343             title = ghb_lookup_title(title_id, &titleindex);
4344             ghb_array_replace(ud->settings_array, titleindex,
4345                               ghb_queue_edit_settings);
4346             ud->settings = ghb_queue_edit_settings;
4347             ghb_load_settings(ud);
4348             ghb_queue_edit_settings = NULL;
4349         }
4350     }
4351 
4352     if (status.queue.unique_id != 0)
4353     {
4354         index = ghb_find_queue_job(ud->queue, status.queue.unique_id,
4355                                    &queueDict);
4356         if ((status.queue.state & GHB_STATE_WORKING) &&
4357             (event_sequence % 50 == 0)) // check every 10 seconds
4358         {
4359             ghb_low_disk_check(ud);
4360         }
4361     }
4362 
4363     if (status.queue.state & GHB_STATE_SCANNING)
4364     {
4365         // This needs to be in scanning and working since scanning
4366         // happens fast enough that it can be missed
4367         gtk_label_set_text(work_status, _("Scanning ..."));
4368         gtk_progress_bar_set_fraction(progress, status.queue.progress);
4369         ghb_queue_update_live_stats(ud, index, &status.queue);
4370         ghb_queue_progress_set_fraction(ud, index, status.queue.progress);
4371     }
4372     else if (status.queue.state & GHB_STATE_SCANDONE)
4373     {
4374         ghb_clear_queue_state(GHB_STATE_SCANDONE);
4375     }
4376     else if (status.queue.state & GHB_STATE_PAUSED)
4377     {
4378         gtk_label_set_text (work_status, _("Paused"));
4379         ghb_queue_update_live_stats(ud, index, &status.queue);
4380     }
4381     else if (status.queue.state & GHB_STATE_SEARCHING)
4382     {
4383         gchar *status_str;
4384 
4385         status_str = searching_status_string(ud, &status.queue);
4386         gtk_label_set_text (work_status, status_str);
4387         gtk_progress_bar_set_fraction(progress, status.queue.progress);
4388         ghb_queue_progress_set_fraction(ud, index, status.queue.progress);
4389         g_free(status_str);
4390     }
4391     else if (status.queue.state & GHB_STATE_WORKING)
4392     {
4393         gchar *status_str;
4394 
4395         status_str = working_status_string(ud, &status.queue);
4396         gtk_label_set_text (work_status, status_str);
4397         gtk_progress_bar_set_fraction (progress, status.queue.progress);
4398         ghb_queue_update_live_stats(ud, index, &status.queue);
4399         ghb_queue_progress_set_fraction(ud, index, status.queue.progress);
4400         g_free(status_str);
4401     }
4402     else if (status.queue.state & GHB_STATE_WORKDONE)
4403     {
4404         gint qstatus;
4405 
4406         switch (status.queue.error)
4407         {
4408             case GHB_ERROR_NONE:
4409                 gtk_label_set_text(work_status, _("Encode Done!"));
4410                 qstatus = GHB_QUEUE_DONE;
4411                 break;
4412             case GHB_ERROR_CANCELED:
4413                 gtk_label_set_text(work_status, _("Encode Canceled."));
4414                 qstatus = GHB_QUEUE_CANCELED;
4415                 break;
4416             case GHB_ERROR_FAIL:
4417             default:
4418                 gtk_label_set_text(work_status, _("Encode Failed."));
4419                 qstatus = GHB_QUEUE_FAIL;
4420         }
4421         if (queueDict != NULL)
4422         {
4423             GhbValue *uiDict = ghb_dict_get(queueDict, "uiSettings");
4424             ghb_dict_set_int(uiDict, "job_status", qstatus);
4425             time_t now = time(NULL);
4426             ghb_dict_set_int(uiDict, "job_finish_time", now);
4427             ghb_dict_set_int(uiDict, "job_pause_time_ms", status.queue.paused);
4428         }
4429         ghb_queue_update_status_icon(ud, index);
4430         ghb_queue_update_live_stats(ud, index, &status.queue);
4431         gtk_progress_bar_set_fraction(progress, 1.0);
4432         ghb_queue_progress_set_visible(ud, index, FALSE);
4433         ghb_queue_progress_set_fraction(ud, index, 1.0);
4434 
4435         ghb_clear_queue_state(GHB_STATE_WORKDONE);
4436         if (ud->job_activity_log)
4437             g_io_channel_unref(ud->job_activity_log);
4438         ud->job_activity_log = NULL;
4439         if (ghb_dict_get_bool(ud->prefs, "RemoveFinishedJobs") &&
4440             status.queue.error == GHB_ERROR_NONE)
4441         {
4442             ghb_queue_remove_row(ud, index);
4443         }
4444         if (ud->cancel_encode != GHB_CANCEL_ALL &&
4445             ud->cancel_encode != GHB_CANCEL_FINISH)
4446         {
4447             ghb_start_next_job(ud);
4448         }
4449         else
4450         {
4451             ghb_uninhibit_suspend(ud);
4452             gtk_widget_hide(GTK_WIDGET(progress));
4453             ghb_dict_set_bool(ud->globals, "SkipDiskFreeCheck", FALSE);
4454         }
4455         ghb_save_queue(ud->queue);
4456         ud->cancel_encode = GHB_CANCEL_NONE;
4457     }
4458     else if (status.queue.state & GHB_STATE_MUXING)
4459     {
4460         gtk_label_set_text (work_status, _("Muxing: This may take a while..."));
4461     }
4462 
4463     if (status.live.state & GHB_STATE_WORKING)
4464     {
4465         GtkProgressBar *live_progress;
4466         live_progress = GTK_PROGRESS_BAR(
4467             GHB_WIDGET(ud->builder, "live_encode_progress"));
4468         status_str = working_status_string(ud, &status.live);
4469         gtk_progress_bar_set_text (live_progress, status_str);
4470         gtk_progress_bar_set_fraction (live_progress, status.live.progress);
4471         g_free(status_str);
4472     }
4473     if (status.live.state & GHB_STATE_WORKDONE)
4474     {
4475         switch( status.live.error )
4476         {
4477             case GHB_ERROR_NONE:
4478             {
4479                 ghb_live_encode_done(ud, TRUE);
4480             } break;
4481             default:
4482             {
4483                 ghb_live_encode_done(ud, FALSE);
4484             } break;
4485         }
4486         ghb_clear_live_state(GHB_STATE_WORKDONE);
4487     }
4488 }
4489 
4490 G_MODULE_EXPORT gboolean
ghb_timer_cb(gpointer data)4491 ghb_timer_cb(gpointer data)
4492 {
4493     signal_user_data_t *ud = (signal_user_data_t*)data;
4494 
4495     ghb_live_preview_progress(ud);
4496     ghb_backend_events(ud);
4497     if (update_preview)
4498     {
4499         g_debug("Updating preview\n");
4500         ghb_set_preview_image(ud);
4501         update_preview = FALSE;
4502     }
4503 
4504 #if !defined(_NO_UPDATE_CHECK)
4505     if (!appcast_busy)
4506     {
4507         const gchar *updates;
4508         updates = ghb_dict_get_string(ud->prefs, "check_updates");
4509         gint64 duration = 0;
4510         if (strcmp(updates, "daily") == 0)
4511             duration = 60 * 60 * 24;
4512         else if (strcmp(updates, "weekly") == 0)
4513             duration = 60 * 60 * 24 * 7;
4514         else if (strcmp(updates, "monthly") == 0)
4515             duration = 60 * 60 * 24 * 7;
4516 
4517         if (duration != 0)
4518         {
4519             gint64 last;
4520             time_t tt;
4521 
4522             last = ghb_dict_get_int(ud->prefs, "last_update_check");
4523             time(&tt);
4524             if (last + duration < tt)
4525             {
4526                 ghb_dict_set_int(ud->prefs,
4527                                         "last_update_check", tt);
4528                 ghb_pref_save(ud->prefs, "last_update_check");
4529                 GHB_THREAD_NEW("Update Check", (GThreadFunc)ghb_check_update, ud);
4530             }
4531         }
4532     }
4533 #endif
4534     return TRUE;
4535 }
4536 
4537 static void
append_activity(GtkTextBuffer * tb,const char * text,gsize length)4538 append_activity(GtkTextBuffer * tb, const char * text, gsize length)
4539 {
4540     if (text == NULL || length <= 0)
4541     {
4542         return;
4543     }
4544     GtkTextIter     iter;
4545 
4546     gtk_text_buffer_get_end_iter(tb, &iter);
4547     gtk_text_buffer_insert(tb, &iter, text, -1);
4548 }
4549 
4550 G_MODULE_EXPORT gboolean
ghb_log_cb(GIOChannel * source,GIOCondition cond,gpointer data)4551 ghb_log_cb(GIOChannel *source, GIOCondition cond, gpointer data)
4552 {
4553     gchar *text = NULL;
4554     gsize length, outlength;
4555     GError *gerror = NULL;
4556     GIOStatus status;
4557 
4558     signal_user_data_t *ud = (signal_user_data_t*)data;
4559 
4560     status = g_io_channel_read_line (source, &text, &length, NULL, &gerror);
4561     // Trim nils from end of text, they cause g_io_channel_write_chars to
4562     // fail with an assertion that aborts
4563     while (length > 0 && text[length-1] == 0)
4564         length--;
4565     if (text != NULL && length > 0)
4566     {
4567         gchar *utf8_text;
4568 
4569         // Assume logging is in current locale
4570         utf8_text = g_locale_to_utf8(text, -1, NULL, &length, NULL);
4571         if (utf8_text != NULL)
4572         {
4573             // Write to activity windows
4574             append_activity(ud->activity_buffer, utf8_text, length);
4575             append_activity(ud->queue_activity_buffer, utf8_text, length);
4576 
4577             // Write log files
4578 #if defined(_WIN32)
4579             gsize one = 1;
4580             utf8_text[length-1] = '\r';
4581 #endif
4582             if (ud->activity_log != NULL)
4583             {
4584                 g_io_channel_write_chars (ud->activity_log, utf8_text,
4585                                         length, &outlength, NULL);
4586 #if defined(_WIN32)
4587                 g_io_channel_write_chars (ud->activity_log, "\n",
4588                                         one, &one, NULL);
4589 #endif
4590                 g_io_channel_flush(ud->activity_log, NULL);
4591             }
4592             if (ud->job_activity_log)
4593             {
4594                 g_io_channel_write_chars (ud->job_activity_log, utf8_text,
4595                                         length, &outlength, NULL);
4596 #if defined(_WIN32)
4597                 g_io_channel_write_chars (ud->activity_log, "\n",
4598                                         one, &outlength, NULL);
4599 #endif
4600                 g_io_channel_flush(ud->job_activity_log, NULL);
4601             }
4602             g_free(utf8_text);
4603         }
4604     }
4605     if (text != NULL)
4606         g_free(text);
4607 
4608     if (status != G_IO_STATUS_NORMAL)
4609     {
4610         // This should never happen, but if it does I would get into an
4611         // infinite loop.  Returning false removes this callback.
4612         g_warning("Error while reading activity from pipe");
4613         if (gerror != NULL)
4614         {
4615             g_warning("%s", gerror->message);
4616             g_error_free (gerror);
4617         }
4618         return FALSE;
4619     }
4620     if (gerror != NULL)
4621         g_error_free (gerror);
4622     return TRUE;
4623 }
4624 
4625 static void
update_activity_labels(signal_user_data_t * ud,gboolean active)4626 update_activity_labels(signal_user_data_t *ud, gboolean active)
4627 {
4628     GtkToolButton *button;
4629 
4630     button   = GTK_TOOL_BUTTON(GHB_WIDGET(ud->builder, "show_activity"));
4631     gtk_tool_button_set_label(button, "Activity");
4632 }
4633 
4634 G_MODULE_EXPORT void
show_activity_action_cb(GSimpleAction * action,GVariant * value,signal_user_data_t * ud)4635 show_activity_action_cb(GSimpleAction *action, GVariant *value,
4636                         signal_user_data_t *ud)
4637 {
4638     GtkWidget * activity_window;
4639     gboolean    state = g_variant_get_boolean(value);
4640 
4641     g_simple_action_set_state(action, value);
4642     activity_window = GHB_WIDGET(ud->builder, "activity_window");
4643     gtk_widget_set_visible(activity_window, state);
4644     update_activity_labels(ud, state);
4645 }
4646 
4647 G_MODULE_EXPORT gboolean
activity_window_delete_cb(GtkWidget * xwidget,GdkEvent * event,signal_user_data_t * ud)4648 activity_window_delete_cb(
4649     GtkWidget *xwidget,
4650 #if !GTK_CHECK_VERSION(3, 90, 0)
4651     GdkEvent *event,
4652 #endif
4653     signal_user_data_t *ud)
4654 {
4655     gtk_widget_set_visible(xwidget, FALSE);
4656     GtkWidget *widget = GHB_WIDGET (ud->builder, "show_activity");
4657     gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
4658     return TRUE;
4659 }
4660 
4661 void
ghb_log(gchar * log,...)4662 ghb_log(gchar *log, ...)
4663 {
4664     va_list args;
4665     time_t _now;
4666     struct tm *now;
4667     gchar fmt[362];
4668 
4669     _now = time(NULL);
4670     now = localtime( &_now );
4671     snprintf(fmt, 362, "[%02d:%02d:%02d] gtkgui: %s\n",
4672             now->tm_hour, now->tm_min, now->tm_sec, log);
4673     va_start(args, log);
4674     vfprintf(stderr, fmt, args);
4675     va_end(args);
4676 }
4677 
4678 void
ghb_browse_uri(signal_user_data_t * ud,const gchar * uri)4679 ghb_browse_uri(signal_user_data_t *ud, const gchar *uri)
4680 {
4681 #if defined(_WIN32)
4682     ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
4683 #else
4684     GtkWindow * parent;
4685     gboolean    result;
4686 
4687     parent = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
4688     result = gtk_show_uri_on_window(parent, uri, GDK_CURRENT_TIME, NULL);
4689     if (result) return;
4690     char *argv[] =
4691         {"xdg-open",NULL,NULL,NULL};
4692     argv[1] = (gchar*)uri;
4693     result = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
4694                 NULL, NULL, NULL);
4695     if (result) return;
4696 
4697     argv[0] = "gnome-open";
4698     result = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
4699                 NULL, NULL, NULL);
4700     if (result) return;
4701 
4702     argv[0] = "kfmclient";
4703     argv[1] = "exec";
4704     argv[2] = (gchar*)uri;
4705     result = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
4706                 NULL, NULL, NULL);
4707     if (result) return;
4708 
4709     argv[0] = "firefox";
4710     argv[1] = (gchar*)uri;
4711     argv[2] = NULL;
4712     result = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL,
4713                 NULL, NULL, NULL);
4714 #endif
4715 }
4716 
4717 G_MODULE_EXPORT void
about_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)4718 about_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud)
4719 {
4720     GtkWidget *widget = GHB_WIDGET (ud->builder, "hb_about");
4721     gchar *ver;
4722 
4723     ver = g_strdup_printf("%s (%s)", HB_PROJECT_VERSION, HB_PROJECT_HOST_ARCH);
4724     gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(widget), ver);
4725     g_free(ver);
4726     gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(widget),
4727                                 HB_PROJECT_URL_WEBSITE);
4728     gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(widget),
4729                                         HB_PROJECT_URL_WEBSITE);
4730     gtk_dialog_run(GTK_DIALOG(widget));
4731     gtk_widget_hide(widget);
4732 }
4733 
4734 #define HB_DOCS "https://handbrake.fr/docs/"
4735 
4736 G_MODULE_EXPORT void
guide_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)4737 guide_action_cb(GSimpleAction *action, GVariant *param, signal_user_data_t *ud)
4738 {
4739     ghb_browse_uri(ud, HB_DOCS);
4740 }
4741 
4742 static void
update_queue_labels(signal_user_data_t * ud)4743 update_queue_labels(signal_user_data_t *ud)
4744 {
4745     GtkToolButton *button;
4746     gint           pending;
4747     const gchar   *show_hide;
4748     gchar         *str;
4749 
4750     button  = GTK_TOOL_BUTTON(GHB_WIDGET(ud->builder, "show_queue"));
4751     pending = queue_pending_count(ud->queue);
4752 
4753     show_hide = _("Queue");
4754     if (pending > 0)
4755     {
4756         str = g_strdup_printf("%s (%d)", show_hide, pending);
4757     }
4758     else
4759     {
4760         str = g_strdup_printf("%s", show_hide);
4761     }
4762     gtk_tool_button_set_label(button, str);
4763     g_free(str);
4764 }
4765 
4766 static void
presets_window_set_visible(signal_user_data_t * ud,gboolean visible)4767 presets_window_set_visible(signal_user_data_t *ud, gboolean visible)
4768 {
4769     GtkWidget     * presets_window;
4770     GtkWidget     * hb_window;
4771 
4772     hb_window = GHB_WIDGET(ud->builder, "hb_window");
4773     if (!gtk_widget_is_visible(hb_window))
4774     {
4775         return;
4776     }
4777 
4778     int w, h;
4779     w = ghb_dict_get_int(ud->prefs, "presets_window_width");
4780     h = ghb_dict_get_int(ud->prefs, "presets_window_height");
4781 
4782     presets_window = GHB_WIDGET(ud->builder, "presets_window");
4783     if (w > 200 && h > 200)
4784     {
4785         gtk_window_resize(GTK_WINDOW(presets_window), w, h);
4786     }
4787     gtk_widget_set_visible(presets_window, visible);
4788 
4789 #if !GTK_CHECK_VERSION(3, 90, 0)
4790     // TODO: Is this possible in GTK4?
4791     int             x, y;
4792 
4793     if (visible)
4794     {
4795         gtk_window_get_position(GTK_WINDOW(hb_window), &x, &y);
4796         x -= w + 10;
4797         if (x < 0)
4798         {
4799             gtk_window_move(GTK_WINDOW(hb_window), w + 10, y);
4800             x = 0;
4801         }
4802         gtk_window_move(GTK_WINDOW(presets_window), x, y);
4803     }
4804 #endif
4805 }
4806 
4807 G_MODULE_EXPORT void
show_presets_action_cb(GSimpleAction * action,GVariant * value,signal_user_data_t * ud)4808 show_presets_action_cb(GSimpleAction *action, GVariant *value,
4809                        signal_user_data_t *ud)
4810 {
4811     gboolean state = g_variant_get_boolean(value);
4812 
4813     g_simple_action_set_state(action, value);
4814     presets_window_set_visible(ud, state);
4815 }
4816 
4817 void
debug_log_handler(const gchar * domain,GLogLevelFlags flags,const gchar * msg,gpointer data)4818 debug_log_handler(const gchar *domain, GLogLevelFlags flags, const gchar *msg, gpointer data)
4819 {
4820     signal_user_data_t *ud = (signal_user_data_t*)data;
4821 
4822     if (ud->debug)
4823     {
4824         printf("%s: %s\n", domain, msg);
4825     }
4826 }
4827 
4828 void
warn_log_handler(const gchar * domain,GLogLevelFlags flags,const gchar * msg,gpointer data)4829 warn_log_handler(const gchar *domain, GLogLevelFlags flags, const gchar *msg, gpointer data)
4830 {
4831     printf("%s: %s\n", domain, msg);
4832 }
4833 
4834 void
ghb_hbfd(signal_user_data_t * ud,gboolean hbfd)4835 ghb_hbfd(signal_user_data_t *ud, gboolean hbfd)
4836 {
4837     GtkWidget *widget;
4838     g_debug("ghb_hbfd");
4839     widget = GHB_WIDGET(ud->builder, "queue_pause");
4840     gtk_widget_set_visible(widget, !hbfd);
4841     widget = GHB_WIDGET(ud->builder, "queue_add");
4842     gtk_widget_set_visible(widget, !hbfd);
4843     widget = GHB_WIDGET(ud->builder, "show_queue");
4844     gtk_widget_set_visible(widget, !hbfd);
4845     widget = GHB_WIDGET(ud->builder, "show_activity");
4846     gtk_widget_set_visible(widget, !hbfd);
4847 
4848     widget = GHB_WIDGET(ud->builder, "container_box");
4849     gtk_widget_set_visible(widget, !hbfd);
4850     widget = GHB_WIDGET(ud->builder, "SettingsStackSwitcher");
4851     gtk_widget_set_visible(widget, !hbfd);
4852     widget = GHB_WIDGET(ud->builder, "SettingsStack");
4853     gtk_widget_set_visible(widget, !hbfd);
4854     widget = GHB_WIDGET(ud->builder, "presets_save");
4855     gtk_widget_set_visible(widget, !hbfd);
4856     widget = GHB_WIDGET(ud->builder, "presets_remove");
4857     gtk_widget_set_visible(widget, !hbfd);
4858     widget = GHB_WIDGET (ud->builder, "hb_window");
4859     gtk_window_resize(GTK_WINDOW(widget), 16, 16);
4860 
4861 }
4862 
4863 G_MODULE_EXPORT void
hbfd_action_cb(GSimpleAction * action,GVariant * value,signal_user_data_t * ud)4864 hbfd_action_cb(GSimpleAction *action, GVariant *value, signal_user_data_t *ud)
4865 {
4866     gboolean state = g_variant_get_boolean(value);
4867 
4868     g_simple_action_set_state(action, value);
4869     ghb_dict_set(ud->prefs, "hbfd", ghb_boolean_value(state));
4870     ghb_hbfd(ud, state);
4871     ghb_pref_save(ud->prefs, "hbfd");
4872 }
4873 
4874 G_MODULE_EXPORT void
activity_font_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4875 activity_font_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4876 {
4877 
4878     ghb_widget_to_setting(ud->prefs, widget);
4879     const gchar *name = ghb_get_setting_key(widget);
4880     ghb_pref_set(ud->prefs, name);
4881 
4882     int size = ghb_dict_get_int(ud->prefs, "ActivityFontSize");
4883 
4884 #if GTK_CHECK_VERSION(3, 16, 0)
4885     const gchar *css_template =
4886         "                                   \n\
4887         #activity_view                      \n\
4888         {                                   \n\
4889             font-family: monospace;         \n\
4890             font-size: %dpt;                \n\
4891             font-weight: lighter;           \n\
4892         }                                   \n\
4893         ";
4894     char           * css      = g_strdup_printf(css_template, size);
4895     GtkCssProvider * provider = gtk_css_provider_new();
4896 
4897     ghb_css_provider_load_from_data(provider, css, -1);
4898     GtkWidget * win = GHB_WIDGET(ud->builder, "hb_window");
4899 #if GTK_CHECK_VERSION(3, 90, 0)
4900     GdkDisplay *dd = gtk_widget_get_display(win);
4901     gtk_style_context_add_provider_for_display(dd,
4902                                 GTK_STYLE_PROVIDER(provider),
4903                                 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
4904 #else
4905     GdkScreen *ss = gtk_window_get_screen(GTK_WINDOW(win));
4906     gtk_style_context_add_provider_for_screen(ss,
4907                                 GTK_STYLE_PROVIDER(provider),
4908                                 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
4909 #endif
4910     g_object_unref(provider);
4911     g_free(css);
4912 #else
4913     const gchar          * font_template = "monospace %d";
4914     char                 * font          = g_strdup_printf(font_template, size);
4915     PangoFontDescription * font_desc;
4916     GtkTextView          * textview;
4917 
4918     font_desc = pango_font_description_from_string(font);
4919     textview = GTK_TEXT_VIEW(GHB_WIDGET(ud->builder, "activity_view"));
4920     gtk_widget_override_font(GTK_WIDGET(textview), font_desc);
4921     pango_font_description_free(font_desc);
4922     g_free(font);
4923 #endif
4924 }
4925 
4926 G_MODULE_EXPORT void
when_complete_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4927 when_complete_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4928 {
4929     GhbValue * value = ghb_widget_value(widget);
4930     ghb_ui_update(ud, "QueueWhenComplete", value);
4931     ghb_value_free(&value);
4932 
4933     ghb_widget_to_setting (ud->prefs, widget);
4934 
4935     ghb_check_dependency(ud, widget, NULL);
4936     const gchar *name = ghb_get_setting_key(widget);
4937     ghb_pref_set(ud->prefs, name);
4938     ghb_prefs_store();
4939 }
4940 
4941 G_MODULE_EXPORT void
queue_when_complete_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4942 queue_when_complete_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4943 {
4944     GhbValue * value = ghb_widget_value(widget);
4945     ghb_ui_update(ud, "WhenComplete", value);
4946     ghb_value_free(&value);
4947 }
4948 
4949 G_MODULE_EXPORT void
pref_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4950 pref_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4951 {
4952     ghb_widget_to_setting (ud->prefs, widget);
4953 
4954     ghb_check_dependency(ud, widget, NULL);
4955     const gchar *name = ghb_get_setting_key(widget);
4956     ghb_pref_set(ud->prefs, name);
4957 }
4958 
4959 G_MODULE_EXPORT void
log_level_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4960 log_level_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4961 {
4962     pref_changed_cb(widget, ud);
4963     int level = ghb_dict_get_int(ud->prefs, "LoggingLevel");
4964     ghb_log_level_set(level);
4965 }
4966 
4967 G_MODULE_EXPORT void
use_m4v_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4968 use_m4v_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4969 {
4970     g_debug("use_m4v_changed_cb");
4971     ghb_widget_to_setting (ud->prefs, widget);
4972     ghb_check_dependency(ud, widget, NULL);
4973     const gchar *name = ghb_get_setting_key(widget);
4974     ghb_pref_set(ud->prefs, name);
4975     ghb_update_destination_extension(ud);
4976 }
4977 
4978 G_MODULE_EXPORT void
vqual_granularity_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4979 vqual_granularity_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4980 {
4981     g_debug("vqual_granularity_changed_cb");
4982     ghb_widget_to_setting (ud->prefs, widget);
4983     ghb_check_dependency(ud, widget, NULL);
4984 
4985     const gchar *name = ghb_get_setting_key(widget);
4986     ghb_pref_set(ud->prefs, name);
4987 
4988     float val, vqmin, vqmax, step, page;
4989     int inverted, digits;
4990 
4991     ghb_vquality_range(ud, &vqmin, &vqmax, &step, &page, &digits, &inverted);
4992     val = ghb_dict_get_double(ud->settings, "VideoQualitySlider");
4993     ghb_scale_configure(ud, "VideoQualitySlider", val, vqmin, vqmax,
4994                         step, page, digits, inverted);
4995 }
4996 
4997 G_MODULE_EXPORT void
tweaks_changed_cb(GtkWidget * widget,signal_user_data_t * ud)4998 tweaks_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
4999 {
5000     g_debug("tweaks_changed_cb");
5001     ghb_widget_to_setting (ud->prefs, widget);
5002     const gchar *name = ghb_get_setting_key(widget);
5003     ghb_pref_set(ud->prefs, name);
5004 }
5005 
5006 G_MODULE_EXPORT void
hbfd_feature_changed_cb(GtkWidget * widget,signal_user_data_t * ud)5007 hbfd_feature_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
5008 {
5009     ghb_widget_to_setting (ud->prefs, widget);
5010     const gchar *name = ghb_get_setting_key(widget);
5011     ghb_pref_set(ud->prefs, name);
5012 
5013     gboolean hbfd = ghb_dict_get_bool(ud->prefs, "hbfd_feature");
5014     if (hbfd)
5015     {
5016         const GhbValue *val;
5017         val = ghb_dict_get_value(ud->prefs, "hbfd");
5018         ghb_ui_settings_update(ud, ud->prefs, "hbfd", val);
5019     }
5020     widget = GHB_WIDGET(ud->builder, "hbfd");
5021     gtk_widget_set_visible(widget, hbfd);
5022 }
5023 
5024 gboolean
ghb_file_menu_add_dvd(signal_user_data_t * ud)5025 ghb_file_menu_add_dvd(signal_user_data_t *ud)
5026 {
5027     GList *link, *drives;
5028     static GList *dvd_items = NULL;
5029 
5030     g_debug("ghb_file_menu_add_dvd()");
5031     GtkMenu *menu = GTK_MENU(GHB_WIDGET(ud->builder, "file_submenu"));
5032 
5033     // Clear previous dvd items from list
5034     link = dvd_items;
5035     while (link != NULL)
5036     {
5037         GtkWidget * widget = GTK_WIDGET(link->data);
5038         // widget_destroy automatically removes widget from container.
5039         gtk_widget_destroy(widget);
5040         link = link->next;
5041     }
5042     g_list_free(dvd_items);
5043     dvd_items = NULL;
5044 
5045     int pos = 5;
5046     link = drives = dvd_device_list();
5047     if (drives != NULL)
5048     {
5049         GtkWidget *widget = gtk_separator_menu_item_new();
5050         dvd_items = g_list_append(dvd_items, (gpointer)widget);
5051 
5052         gtk_menu_shell_insert(GTK_MENU_SHELL(menu), widget, pos++);
5053         gtk_widget_set_visible(widget, TRUE);
5054 
5055         while (link != NULL)
5056         {
5057             GtkWidget *widget;
5058             gchar *drive = get_dvd_device_name(link->data);
5059             gchar *name = get_dvd_volume_name(link->data);
5060 
5061             widget = gtk_menu_item_new_with_label(name);
5062             gtk_buildable_set_name(GTK_BUILDABLE(widget), drive);
5063             gtk_widget_set_tooltip_text(widget, _("Scan this DVD source"));
5064 
5065             dvd_items = g_list_append(dvd_items, (gpointer)widget);
5066             gtk_menu_shell_insert(GTK_MENU_SHELL(menu), widget, pos++);
5067 
5068             gtk_widget_set_visible(widget, TRUE);
5069 
5070             // Connect signal to action (menu item)
5071             g_signal_connect(widget, "activate",
5072                 (GCallback)dvd_source_activate_cb, ud);
5073             g_free(name);
5074             g_free(drive);
5075             free_drive(link->data);
5076             link = link->next;
5077         }
5078 
5079         g_list_free(drives);
5080     }
5081 
5082     return FALSE;
5083 }
5084 
5085 gboolean ghb_is_cd(GDrive *gd);
5086 
5087 static GList*
dvd_device_list()5088 dvd_device_list()
5089 {
5090     GList *dvd_devices = NULL;
5091 
5092 #if defined(_WIN32)
5093     gint ii, drives;
5094     gchar drive[5];
5095 
5096     strcpy(drive, "A:" G_DIR_SEPARATOR_S);
5097     drives = GetLogicalDrives();
5098     for (ii = 0; ii < 26; ii++)
5099     {
5100         if (drives & 0x01)
5101         {
5102             guint dtype;
5103 
5104             drive[0] = 'A' + ii;
5105             dtype = GetDriveType(drive);
5106             if (dtype == DRIVE_CDROM)
5107             {
5108                 dvd_devices = g_list_append(dvd_devices,
5109                         (gpointer)g_strdup(drive));
5110             }
5111         }
5112         drives >>= 1;
5113     }
5114 #else
5115     GVolumeMonitor *gvm;
5116     GList *drives, *link;
5117 
5118     gvm = g_volume_monitor_get ();
5119     drives = g_volume_monitor_get_connected_drives (gvm);
5120     link = drives;
5121     while (link != NULL)
5122     {
5123         GDrive *gd;
5124 
5125         gd = (GDrive*)link->data;
5126         if (ghb_is_cd(gd))
5127         {
5128             dvd_devices = g_list_append(dvd_devices, gd);
5129         }
5130         else
5131             g_object_unref (gd);
5132         link = link->next;
5133     }
5134     g_list_free(drives);
5135 #endif
5136 
5137     return dvd_devices;
5138 }
5139 
5140 #if defined(__linux__) && defined(_HAVE_GUDEV)
5141 static GUdevClient *udev_ctx = NULL;
5142 #endif
5143 
5144 gboolean
ghb_is_cd(GDrive * gd)5145 ghb_is_cd(GDrive *gd)
5146 {
5147 #if defined(__linux__) && defined(_HAVE_GUDEV)
5148     gchar *device;
5149     GUdevDevice *udd;
5150 
5151     if (udev_ctx == NULL)
5152         return FALSE;
5153 
5154     device = g_drive_get_identifier(gd, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
5155     if (device == NULL)
5156         return FALSE;
5157 
5158     udd = g_udev_client_query_by_device_file(udev_ctx, device);
5159     if (udd == NULL)
5160     {
5161         g_message("udev: Failed to lookup device %s", device);
5162         g_free(device);
5163         return FALSE;
5164     }
5165     g_free(device);
5166 
5167     gint val;
5168     val = g_udev_device_get_property_as_int(udd, "ID_CDROM_DVD");
5169     g_object_unref(udd);
5170     if (val == 1)
5171         return TRUE;
5172 
5173     return FALSE;
5174 #else
5175     return FALSE;
5176 #endif
5177 }
5178 
5179 void
ghb_udev_init()5180 ghb_udev_init()
5181 {
5182 #if defined(__linux__) && defined(_HAVE_GUDEV)
5183     udev_ctx = g_udev_client_new(NULL);
5184 #endif
5185 }
5186 
5187 #if defined(_WIN32)
5188 static void
handle_media_change(const gchar * device,gboolean insert,signal_user_data_t * ud)5189 handle_media_change(const gchar *device, gboolean insert, signal_user_data_t *ud)
5190 {
5191     guint dtype;
5192     static gint ins_count = 0;
5193     static gint rem_count = 0;
5194 
5195     // The media change event in windows bounces around a bit
5196     // so I debounce it here
5197     // DVD insertion detected.  Scan it.
5198     dtype = GetDriveType(device);
5199     if (dtype != DRIVE_CDROM)
5200         return;
5201     if (insert)
5202     {
5203         rem_count = 0;
5204         ins_count++;
5205         if (ins_count == 2)
5206         {
5207             GHB_THREAD_NEW("Cache Volume Names",
5208                     (GThreadFunc)ghb_cache_volnames, ud);
5209             if (ghb_dict_get_bool(ud->prefs, "AutoScan") &&
5210                 ud->current_dvd_device != NULL &&
5211                 strcmp(device, ud->current_dvd_device) == 0)
5212             {
5213                 show_scan_progress(ud);
5214                 gint preview_count;
5215                 preview_count = ghb_dict_get_int(ud->prefs, "preview_count");
5216                 ghb_dict_set_string(ud->globals, "scan_source", device);
5217                 start_scan(ud, device, 0, preview_count);
5218             }
5219         }
5220     }
5221     else
5222     {
5223         ins_count = 0;
5224         rem_count++;
5225         if (rem_count == 2)
5226         {
5227             GHB_THREAD_NEW("Cache Volume Names",
5228                     (GThreadFunc)ghb_cache_volnames, ud);
5229             if (ud->current_dvd_device != NULL &&
5230                 strcmp(device, ud->current_dvd_device) == 0)
5231             {
5232                 ghb_hb_cleanup(TRUE);
5233                 prune_logs(ud);
5234                 ghb_dict_set_string(ud->globals, "scan_source", "/dev/null");
5235                 start_scan(ud, "/dev/null", 0, 1);
5236             }
5237         }
5238     }
5239 }
5240 
5241 static gchar
FindDriveFromMask(ULONG unitmask)5242 FindDriveFromMask(ULONG unitmask)
5243 {
5244     gchar cc;
5245     for (cc = 0; cc < 26; cc++)
5246     {
5247         if (unitmask & 0x01)
5248             return 'A' + cc;
5249         unitmask >>= 1;
5250     }
5251     return 0;
5252 }
5253 
5254 void
wm_drive_changed(MSG * msg,signal_user_data_t * ud)5255 wm_drive_changed(MSG *msg, signal_user_data_t *ud)
5256 {
5257     PDEV_BROADCAST_HDR bch = (PDEV_BROADCAST_HDR)msg->lParam;
5258     gchar drive[4];
5259 
5260     g_strlcpy(drive, "A:" G_DIR_SEPARATOR_S, 4);
5261     switch (msg->wParam)
5262     {
5263         case DBT_DEVICEARRIVAL:
5264         {
5265             if (bch->dbch_devicetype == DBT_DEVTYP_VOLUME)
5266             {
5267                 PDEV_BROADCAST_VOLUME bcv = (PDEV_BROADCAST_VOLUME)bch;
5268 
5269                 if (bcv->dbcv_flags & DBTF_MEDIA)
5270                 {
5271                     drive[0] = FindDriveFromMask(bcv->dbcv_unitmask);
5272                     handle_media_change(drive, TRUE, ud);
5273                 }
5274             }
5275         } break;
5276 
5277         case DBT_DEVICEREMOVECOMPLETE:
5278         {
5279             if (bch->dbch_devicetype == DBT_DEVTYP_VOLUME)
5280             {
5281                 PDEV_BROADCAST_VOLUME bcv = (PDEV_BROADCAST_VOLUME)bch;
5282 
5283                 if (bcv->dbcv_flags & DBTF_MEDIA)
5284                 {
5285                     drive[0] = FindDriveFromMask(bcv->dbcv_unitmask);
5286                     handle_media_change(drive, FALSE, ud);
5287                 }
5288             }
5289         } break;
5290         default: ;
5291     }
5292 }
5293 
5294 #else
5295 
5296 G_MODULE_EXPORT void
drive_changed_cb(GVolumeMonitor * gvm,GDrive * gd,signal_user_data_t * ud)5297 drive_changed_cb(GVolumeMonitor *gvm, GDrive *gd, signal_user_data_t *ud)
5298 {
5299     gchar *device;
5300     gint state;
5301 
5302     g_debug("drive_changed_cb()");
5303     GHB_THREAD_NEW("Cache Volume Names", (GThreadFunc)ghb_cache_volnames, ud);
5304 
5305     state = ghb_get_scan_state();
5306     device = g_drive_get_identifier(gd, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
5307     if (ud->current_dvd_device == NULL ||
5308         strcmp(device, ud->current_dvd_device) != 0 ||
5309         state != GHB_STATE_IDLE )
5310     {
5311         return;
5312     }
5313     if (g_drive_has_media(gd))
5314     {
5315         if (ghb_dict_get_bool(ud->prefs, "AutoScan"))
5316         {
5317             show_scan_progress(ud);
5318             gint preview_count;
5319             preview_count = ghb_dict_get_int(ud->prefs, "preview_count");
5320             ghb_dict_set_string(ud->globals, "scan_source", device);
5321             start_scan(ud, device, 0, preview_count);
5322         }
5323     }
5324     else
5325     {
5326         ghb_hb_cleanup(TRUE);
5327         prune_logs(ud);
5328         ghb_dict_set_string(ud->globals, "scan_source", "/dev/null");
5329         start_scan(ud, "/dev/null", 0, 1);
5330     }
5331 }
5332 #endif
5333 
5334 typedef struct
5335 {
5336     gint count;
5337     signal_user_data_t *ud;
5338 } button_click_t;
5339 
5340 static gboolean
easter_egg_timeout_cb(button_click_t * bc)5341 easter_egg_timeout_cb(button_click_t *bc)
5342 {
5343     if (bc->count == 1)
5344     {
5345         GtkWidget *widget;
5346         widget = GHB_WIDGET(bc->ud->builder, "allow_tweaks");
5347         gtk_widget_hide(widget);
5348         widget = GHB_WIDGET(bc->ud->builder, "hbfd_feature");
5349         gtk_widget_hide(widget);
5350     }
5351     bc->count = 0;
5352 
5353     return FALSE;
5354 }
5355 
5356 G_MODULE_EXPORT void
easter_egg_multi_cb(GtkGestureMultiPress * gest,gint n_press,gdouble x,gdouble y,signal_user_data_t * ud)5357 easter_egg_multi_cb(
5358     GtkGestureMultiPress * gest,
5359     gint                   n_press,
5360     gdouble                x,
5361     gdouble                y,
5362     signal_user_data_t   * ud)
5363 {
5364     if (n_press == 3)
5365     {
5366         GtkWidget *widget;
5367         widget = GHB_WIDGET(ud->builder, "allow_tweaks");
5368         gtk_widget_show(widget);
5369         widget = GHB_WIDGET(ud->builder, "hbfd_feature");
5370         gtk_widget_show(widget);
5371     }
5372 }
5373 
5374 G_MODULE_EXPORT gboolean
easter_egg_cb(GtkWidget * widget,GdkEvent * event,signal_user_data_t * ud)5375 easter_egg_cb(
5376     GtkWidget *widget,
5377     GdkEvent *event,
5378     signal_user_data_t *ud)
5379 {
5380     GdkEventType type = ghb_event_get_event_type(event);
5381     guint        button;
5382     static button_click_t bc = { .count = 0 };
5383 
5384     bc.ud = ud;
5385     ghb_event_get_button(event, &button);
5386     if (type == GDK_BUTTON_PRESS && button == 1)
5387     {
5388         bc.count++;
5389         switch (bc.count)
5390         {
5391             case 1:
5392             {
5393                 g_timeout_add(500, (GSourceFunc)easter_egg_timeout_cb, &bc);
5394             } break;
5395 
5396             case 3:
5397             {
5398                 // It's a triple left mouse button click
5399                 GtkWidget *widget;
5400                 widget = GHB_WIDGET(ud->builder, "allow_tweaks");
5401                 gtk_widget_show(widget);
5402                 widget = GHB_WIDGET(ud->builder, "hbfd_feature");
5403                 gtk_widget_show(widget);
5404             } break;
5405 
5406             default:
5407                 break;
5408         }
5409     }
5410     return FALSE;
5411 }
5412 
5413 G_MODULE_EXPORT gchar*
format_deblock_cb(GtkScale * scale,gdouble val,signal_user_data_t * ud)5414 format_deblock_cb(GtkScale *scale, gdouble val, signal_user_data_t *ud)
5415 {
5416     if (val < 5.0)
5417     {
5418         return g_strdup_printf(_("Off"));
5419     }
5420     else
5421     {
5422         return g_strdup_printf("%d", (gint)val);
5423     }
5424 }
5425 
5426 static void
process_appcast(signal_user_data_t * ud)5427 process_appcast(signal_user_data_t *ud)
5428 {
5429     gchar *description = NULL, *build = NULL, *version = NULL, *msg;
5430 #if !defined(_WIN32) && !defined(_NO_UPDATE_CHECK)
5431     GtkWidget *window;
5432     static GtkWidget *html = NULL;
5433 #endif
5434     GtkWidget *dialog, *label;
5435     gint    response, ibuild = 0, skip;
5436 
5437     if (ud->appcast == NULL || ud->appcast_len < 15 ||
5438         strncmp(&(ud->appcast[9]), "200 OK", 6))
5439     {
5440         goto done;
5441     }
5442     ghb_appcast_parse(ud->appcast, &description, &build, &version);
5443     if (build)
5444         ibuild = g_strtod(build, NULL);
5445     skip = ghb_dict_get_int(ud->prefs, "update_skip_version");
5446     if (description == NULL || build == NULL || version == NULL
5447         || ibuild <= hb_get_build(NULL) || skip == ibuild)
5448     {
5449         goto done;
5450     }
5451     msg = g_strdup_printf(_("HandBrake %s/%s is now available (you have %s/%d)."),
5452             version, build, hb_get_version(NULL), hb_get_build(NULL));
5453     label = GHB_WIDGET(ud->builder, "update_message");
5454     gtk_label_set_text(GTK_LABEL(label), msg);
5455 
5456 #if !defined(_WIN32) && !defined(_NO_UPDATE_CHECK)
5457     if (html == NULL)
5458     {
5459         html = webkit_web_view_new();
5460         window = GHB_WIDGET(ud->builder, "update_scroll");
5461         gtk_container_add(GTK_CONTAINER(window), html);
5462         // Show it
5463         gtk_widget_set_size_request(html, 420, 240);
5464         gtk_widget_show(html);
5465     }
5466     webkit_web_view_open(WEBKIT_WEB_VIEW(html), description);
5467 #endif
5468     dialog = GHB_WIDGET(ud->builder, "update_dialog");
5469     response = gtk_dialog_run(GTK_DIALOG(dialog));
5470     gtk_widget_hide(dialog);
5471     if (response == GTK_RESPONSE_OK)
5472     {
5473         // Skip
5474         ghb_dict_set_int(ud->prefs, "update_skip_version", ibuild);
5475         ghb_pref_save(ud->prefs, "update_skip_version");
5476     }
5477     g_free(msg);
5478 
5479 done:
5480     if (description) g_free(description);
5481     if (build) g_free(build);
5482     if (version) g_free(version);
5483     g_free(ud->appcast);
5484     ud->appcast_len = 0;
5485     ud->appcast = NULL;
5486     appcast_busy = FALSE;
5487 }
5488 
5489 void
ghb_net_close(GIOChannel * ioc)5490 ghb_net_close(GIOChannel *ioc)
5491 {
5492     gint fd;
5493 
5494     g_debug("ghb_net_close");
5495     if (ioc == NULL) return;
5496     fd = g_io_channel_unix_get_fd(ioc);
5497     close(fd);
5498     g_io_channel_unref(ioc);
5499 }
5500 
5501 G_MODULE_EXPORT gboolean
ghb_net_recv_cb(GIOChannel * ioc,GIOCondition cond,gpointer data)5502 ghb_net_recv_cb(GIOChannel *ioc, GIOCondition cond, gpointer data)
5503 {
5504     gchar buf[2048];
5505     gsize len;
5506     GError *gerror = NULL;
5507     GIOStatus status;
5508 
5509     g_debug("ghb_net_recv_cb");
5510     signal_user_data_t *ud = (signal_user_data_t*)data;
5511 
5512     status = g_io_channel_read_chars (ioc, buf, 2048, &len, &gerror);
5513     if ((status == G_IO_STATUS_NORMAL || status == G_IO_STATUS_EOF) &&
5514         len > 0)
5515     {
5516         gint new_len = ud->appcast_len + len;
5517         ud->appcast = g_realloc(ud->appcast, new_len + 1);
5518         memcpy(&(ud->appcast[ud->appcast_len]), buf, len);
5519         ud->appcast_len = new_len;
5520     }
5521     if (status == G_IO_STATUS_EOF)
5522     {
5523         if ( ud->appcast != NULL )
5524         {
5525             ud->appcast[ud->appcast_len] = 0;
5526         }
5527         ghb_net_close(ioc);
5528         process_appcast(ud);
5529         return FALSE;
5530     }
5531     return TRUE;
5532 }
5533 
5534 GIOChannel*
ghb_net_open(signal_user_data_t * ud,gchar * address,gint port)5535 ghb_net_open(signal_user_data_t *ud, gchar *address, gint port)
5536 {
5537     GIOChannel *ioc;
5538     gint fd;
5539 
5540     struct sockaddr_in   sock;
5541     struct hostent     * host;
5542 
5543     g_debug("ghb_net_open");
5544     if( !( host = gethostbyname( address ) ) )
5545     {
5546         g_warning( "gethostbyname failed (%s)", address );
5547         appcast_busy = FALSE;
5548         return NULL;
5549     }
5550 
5551     memset( &sock, 0, sizeof( struct sockaddr_in ) );
5552     sock.sin_family = host->h_addrtype;
5553     sock.sin_port   = htons( port );
5554     memcpy( &sock.sin_addr, host->h_addr, host->h_length );
5555 
5556     fd = socket(host->h_addrtype, SOCK_STREAM, 0);
5557     if( fd < 0 )
5558     {
5559         g_debug( "socket failed" );
5560         appcast_busy = FALSE;
5561         return NULL;
5562     }
5563 
5564     if(connect(fd, (struct sockaddr*)&sock, sizeof(struct sockaddr_in )) < 0 )
5565     {
5566         g_debug( "connect failed" );
5567         appcast_busy = FALSE;
5568         return NULL;
5569     }
5570     ioc = g_io_channel_unix_new(fd);
5571     g_io_channel_set_encoding (ioc, NULL, NULL);
5572     g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
5573     g_io_add_watch (ioc, G_IO_IN, ghb_net_recv_cb, (gpointer)ud );
5574 
5575     return ioc;
5576 }
5577 
5578 gpointer
ghb_check_update(signal_user_data_t * ud)5579 ghb_check_update(signal_user_data_t *ud)
5580 {
5581     gchar *query;
5582     gsize len;
5583     GIOChannel *ioc;
5584     GError *gerror = NULL;
5585     GRegex *regex;
5586     GMatchInfo *mi;
5587     gchar *host, *appcast;
5588 
5589     g_debug("ghb_check_update");
5590     appcast_busy = TRUE;
5591     regex = g_regex_new("^http://(.+)/(.+)$", 0, 0, NULL);
5592     if (!g_regex_match(regex, HB_PROJECT_URL_APPCAST, 0, &mi))
5593     {
5594         return NULL;
5595     }
5596 
5597     host = g_match_info_fetch(mi, 1);
5598     appcast = g_match_info_fetch(mi, 2);
5599 
5600     if (host == NULL || appcast == NULL)
5601         return NULL;
5602 
5603     query = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
5604                             appcast, host);
5605 
5606     ioc = ghb_net_open(ud, host, 80);
5607     if (ioc == NULL)
5608         goto free_resources;
5609 
5610     g_io_channel_write_chars(ioc, query, strlen(query), &len, &gerror);
5611     g_io_channel_flush(ioc, &gerror);
5612 
5613 free_resources:
5614     g_free(query);
5615     g_free(host);
5616     g_free(appcast);
5617     g_match_info_free(mi);
5618     g_regex_unref(regex);
5619     return NULL;
5620 }
5621 
5622 void
ghb_notify_done(signal_user_data_t * ud)5623 ghb_notify_done(signal_user_data_t *ud)
5624 {
5625 
5626     if (ghb_settings_combo_int(ud->prefs, "WhenComplete") == 0)
5627         return;
5628 
5629     GNotification * notification;
5630     GIcon         * icon;
5631 
5632     notification = g_notification_new(_("Encode Complete"));
5633     g_notification_set_body(notification,
5634         _("Put down that cocktail, Your HandBrake queue is done!"));
5635     icon = g_themed_icon_new("fr.handbrake.ghb");
5636     g_notification_set_icon(notification, icon);
5637 
5638     g_application_send_notification(G_APPLICATION(ud->app), "cocktail", notification);
5639     g_object_unref(G_OBJECT(notification));
5640     g_object_unref(G_OBJECT(icon));
5641 
5642     if (ghb_settings_combo_int(ud->prefs, "WhenComplete") == 3)
5643     {
5644         if (ghb_can_shutdown_gsm())
5645         {
5646             ghb_countdown_dialog(GTK_MESSAGE_WARNING,
5647                 _("Your encode is complete."),
5648                 _("Shutting down the computer"),
5649                 _("Cancel"), (GSourceFunc)shutdown_cb, ud, 60);
5650         }
5651     }
5652     if (ghb_settings_combo_int(ud->prefs, "WhenComplete") == 2)
5653     {
5654         if (ghb_can_suspend_gpm())
5655         {
5656             ghb_countdown_dialog(GTK_MESSAGE_WARNING,
5657                 _("Your encode is complete."),
5658                 _("Putting computer to sleep"),
5659                 _("Cancel"), (GSourceFunc)suspend_cb, ud, 60);
5660         }
5661     }
5662     if (ghb_settings_combo_int(ud->prefs, "WhenComplete") == 4)
5663     {
5664         ghb_countdown_dialog(GTK_MESSAGE_WARNING,
5665                             _("Your encode is complete."),
5666                             _("Quitting Handbrake"),
5667                             _("Cancel"), (GSourceFunc)quit_cb, ud, 60);
5668     }
5669 }
5670 
5671 G_MODULE_EXPORT gboolean
window_map_cb(GtkWidget * widget,GdkEventAny * event,signal_user_data_t * ud)5672 window_map_cb(
5673     GtkWidget *widget,
5674 #if !GTK_CHECK_VERSION(3, 90, 0)
5675     GdkEventAny *event,
5676 #endif
5677     signal_user_data_t *ud)
5678 {
5679     return FALSE;
5680 }
5681 
5682 G_MODULE_EXPORT void
hb_win_sz_alloc_cb(GtkWidget * widget,int width,int height,int baseline,signal_user_data_t * ud)5683 hb_win_sz_alloc_cb(
5684     GtkWidget *widget,
5685 #if GTK_CHECK_VERSION(3, 90, 0)
5686     int width,
5687     int height,
5688     int baseline,
5689 #else
5690     GdkRectangle *rect,
5691 #endif
5692     signal_user_data_t *ud)
5693 {
5694     if (gtk_widget_get_visible(widget))
5695     {
5696         gint w, h, ww, wh;
5697         w = ghb_dict_get_int(ud->prefs, "window_width");
5698         h = ghb_dict_get_int(ud->prefs, "window_height");
5699 
5700         gtk_window_get_size(GTK_WINDOW(widget), &ww, &wh);
5701         if ( w != ww || h != wh )
5702         {
5703             ghb_dict_set_int(ud->prefs, "window_width", ww);
5704             ghb_dict_set_int(ud->prefs, "window_height", wh);
5705             ghb_pref_set(ud->prefs, "window_width");
5706             ghb_pref_set(ud->prefs, "window_height");
5707             ghb_prefs_store();
5708         }
5709     }
5710 }
5711 
container_empty_cb(GtkWidget * widget,gpointer data)5712 static void container_empty_cb(GtkWidget *widget, gpointer data)
5713 {
5714     gtk_widget_destroy(widget);
5715 }
5716 
ghb_container_empty(GtkContainer * c)5717 void ghb_container_empty(GtkContainer *c)
5718 {
5719     gtk_container_foreach(c, container_empty_cb, NULL);
5720 }
5721 
5722 static void
lang_combo_search(GtkComboBox * combo,guint keyval,signal_user_data_t * ud)5723 lang_combo_search(
5724     GtkComboBox * combo,
5725     guint         keyval,
5726     signal_user_data_t *ud)
5727 {
5728     if (combo == NULL)
5729     {
5730         return;
5731     }
5732     GtkTreeModel * model = GTK_TREE_MODEL(gtk_combo_box_get_model(combo));
5733     GtkTreeIter    iter, prev_iter;
5734     gchar        * lang;
5735     gunichar       key_char;
5736     gunichar       first_char;
5737     int            pos = 0, count = 2048;
5738 
5739     key_char = g_unichar_toupper(gdk_keyval_to_unicode(keyval));
5740     if (gtk_combo_box_get_active_iter(combo, &iter))
5741     {
5742         while (pos <= count)
5743         {
5744             pos++;
5745             prev_iter = iter;
5746             if (!gtk_tree_model_iter_next(model, &iter))
5747             {
5748                 GtkTreePath * path = gtk_tree_model_get_path(model, &prev_iter);
5749                 gint        * ind  = gtk_tree_path_get_indices(path);;
5750 
5751                 count = ind[0];
5752                 gtk_tree_path_free(path);
5753                 gtk_tree_model_get_iter_first(model, &iter);
5754             }
5755             gtk_tree_model_get(model, &iter, 0, &lang, -1);
5756             first_char = g_unichar_toupper(g_utf8_get_char(lang));
5757             g_free(lang);
5758             if (first_char == key_char)
5759             {
5760                 gtk_combo_box_set_active_iter(combo, &iter);
5761                 break;
5762             }
5763         }
5764     }
5765 }
5766 
5767 #if GTK_CHECK_VERSION(3, 90, 0)
5768 G_MODULE_EXPORT gboolean
combo_search_key_press_cb(GtkEventControllerKey * keycon,guint keyval,guint keycode,GdkModifierType state,signal_user_data_t * ud)5769 combo_search_key_press_cb(
5770     GtkEventControllerKey * keycon,
5771     guint                   keyval,
5772     guint                   keycode,
5773     GdkModifierType         state,
5774     signal_user_data_t    * ud)
5775 {
5776     GtkComboBox  * combo;
5777 
5778     combo = GTK_COMBO_BOX(gtk_event_controller_get_widget(
5779                             GTK_EVENT_CONTROLLER(keycon)));
5780     lang_combo_search(combo, keyval, ud);
5781     return FALSE;
5782 }
5783 #else
5784 G_MODULE_EXPORT gboolean
combo_search_key_press_cb(GtkWidget * widget,GdkEvent * event,signal_user_data_t * ud)5785 combo_search_key_press_cb(
5786     GtkWidget *widget,
5787     GdkEvent *event,
5788     signal_user_data_t *ud)
5789 {
5790     guint          keyval;
5791 
5792     ghb_event_get_keyval(event, &keyval);
5793     lang_combo_search(GTK_COMBO_BOX(widget), keyval, ud);
5794     return FALSE;
5795 }
5796 #endif
5797