1 /*  dvdisaster: Additional error correction for optical media.
2  *  Copyright (C) 2004-2015 Carsten Gnoerlich.
3  *
4  *  Email: carsten@dvdisaster.org  -or-  cgnoerlich@fsfe.org
5  *  Project homepage: http://www.dvdisaster.org
6  *
7  *  This file is part of dvdisaster.
8  *
9  *  dvdisaster is free software: you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation, either version 3 of the License, or
12  *  (at your option) any later version.
13  *
14  *  dvdisaster is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with dvdisaster. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "dvdisaster.h"
24 
25 #include <sys/wait.h>
26 
27 /***
28  *** Ask user to specify his viewer
29  ***/
30 
31 #define SEARCH_BUTTON 1
32 
33 typedef struct
34 {  GtkWidget *dialog;
35    GtkWidget *entry;
36    GtkWidget *search;
37    GtkWidget *filesel;
38    GtkWidget *fileok;
39    GtkWidget *filecancel;
40    char *path;
41 } viewer_dialog_info;
42 
response_cb(GtkWidget * widget,int response,gpointer data)43 static void response_cb(GtkWidget *widget, int response, gpointer data)
44 {  viewer_dialog_info *bdi = (viewer_dialog_info*)data;
45 
46    switch(response)
47    {  case GTK_RESPONSE_ACCEPT:
48 	if(Closure->viewer) g_free(Closure->viewer);
49 	Closure->viewer = g_strdup(gtk_entry_get_text(GTK_ENTRY(bdi->entry)));
50 	ShowPDF(bdi->path);
51 	break;
52 
53       case GTK_RESPONSE_REJECT:
54 	if(bdi->path) g_free(bdi->path);
55         break;
56    }
57    gtk_widget_destroy(widget);
58    if(bdi->filesel)
59      gtk_widget_destroy(bdi->filesel);
60    g_free(bdi);
61 }
62 
search_cb(GtkWidget * widget,gpointer data)63 static void search_cb(GtkWidget *widget, gpointer data)
64 {  viewer_dialog_info *bdi = (viewer_dialog_info*)data;
65 
66    if(widget == bdi->search)
67    {  bdi->filesel = gtk_file_selection_new(_utf("windowtitle|Choose a PDF viewer"));
68       bdi->fileok = GTK_FILE_SELECTION(bdi->filesel)->ok_button;
69       bdi->filecancel = GTK_FILE_SELECTION(bdi->filesel)->cancel_button;
70       ReverseCancelOK(GTK_DIALOG(bdi->filesel));
71       gtk_file_selection_hide_fileop_buttons(GTK_FILE_SELECTION(bdi->filesel));
72       g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(bdi->filesel)->ok_button), "clicked",
73 		       G_CALLBACK(search_cb), bdi);
74 
75       g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(bdi->filesel)->cancel_button), "clicked",
76 		       G_CALLBACK(search_cb), bdi);
77 
78       gtk_widget_show(bdi->filesel);
79    }
80 
81    if(widget == bdi->fileok)
82    {
83       if(Closure->viewer) g_free(Closure->viewer);
84       Closure->viewer = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(bdi->filesel)));
85       ShowPDF(bdi->path);
86       gtk_widget_destroy(bdi->filesel);
87       gtk_widget_destroy(bdi->dialog);
88       g_free(bdi);
89       return;
90    }
91 
92    if(widget == bdi->filecancel)
93    {  gtk_widget_destroy(bdi->filesel);
94       bdi->filesel = NULL;
95    }
96 }
97 
viewer_dialog(char * path)98 static void viewer_dialog(char *path)
99 {  GtkWidget *dialog, *vbox, *hbox, *label, *entry, *button;
100    viewer_dialog_info *bdi = g_malloc0(sizeof(viewer_dialog_info));
101 
102    /* Create the dialog */
103 
104    dialog = gtk_dialog_new_with_buttons(_utf("windowtitle|PDF viewer required"),
105 				       Closure->window, GTK_DIALOG_DESTROY_WITH_PARENT,
106 				       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
107 				       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
108    bdi->dialog = dialog;
109    if(path)
110    {  bdi->path = g_strdup(path);
111    }
112 
113    vbox = gtk_vbox_new(FALSE, 0);
114    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, FALSE, FALSE, 0);
115    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
116 
117    /* Insert the contents */
118 
119    label = gtk_label_new(NULL);
120    gtk_label_set_markup(GTK_LABEL(label), _utf("<b>Could not find a suitable PDF viewer.</b>\n\n"
121                                                "Which PDF viewer would you like to use\n"
122                                                "for reading the online documentation?\n\n"
123 			                       "Please enter its name (e.g. xpdf) or\n"
124 			                       "use the \"Search\" button for a file dialog.\n")),
125 			      gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 10);
126 
127    hbox = gtk_hbox_new(FALSE, 0);
128    gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 10);
129 
130    bdi->entry = entry = gtk_entry_new();
131    gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 10);
132 
133    bdi->search = button = gtk_button_new_with_label(_utf("Search"));
134    g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(search_cb), bdi);
135    gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 10);
136 
137    /* Show it */
138 
139    g_signal_connect(dialog, "response", G_CALLBACK(response_cb), bdi);
140 
141    gtk_widget_show_all(dialog);
142 }
143 
144 /***
145  *** Show the manual in an external viewer
146  ***/
147 
148 /*
149  * Check the child processes exit status
150  * to find whether the viewer could be invoked.
151  */
152 
153 typedef struct
154 {  pid_t pid;
155    char *path;
156    GtkWidget *msg;
157    int seconds;
158 } viewer_info;
159 
160 
msg_destroy_cb(GtkWidget * widget,gpointer data)161 static void msg_destroy_cb(GtkWidget *widget, gpointer data)
162 {  viewer_info *bi = (viewer_info*)data;
163 
164    bi->msg = NULL;
165 }
166 
167 /*
168  * The following list of viewers
169  * will be tried one at a time until one entry succeeds by:
170  * - returning zero
171  * - not returning within 60 seconds
172  */
173 
174 static int viewer_index;
175 static void try_viewer(viewer_info*);
176 
177 static char *viewers[] =
178 {  "evince",
179    "xpdf",
180    "okular",
181    "gv",
182    "mupdf",
183    "pdfcube",
184    "zathura",
185    NULL
186 };
187 
viewer_timeout_func(gpointer data)188 static gboolean viewer_timeout_func(gpointer data)
189 {  viewer_info *bi = (viewer_info*)data;
190    int status;
191 
192    waitpid(bi->pid, &status, WNOHANG);
193 
194    /* At least mozilla returns random values under FreeBSD on success,
195       so we can't rely on the return value exept our own 110 one. */
196 
197    if(WIFEXITED(status))
198    {
199       switch(WEXITSTATUS(status))
200       {  case 110: /* viewer did not execute */
201 	   viewer_index++;
202 	   if(!viewers[viewer_index]) /* all viewers from the list failed */
203 	   {  viewer_dialog(bi->path);
204 
205 	      if(bi->msg)
206 		gtk_widget_destroy(bi->msg);
207 	      if(bi->path)
208 		g_free(bi->path);
209 	      g_free(bi);
210 	   }
211 	   else                        /* try next viewer from list */
212 	   {  bi->seconds = 0;
213 	      try_viewer(bi);
214 	   }
215 	   return FALSE;
216 
217          case 0:  /* viewer assumed to be successful */
218          default:
219 	   if(bi->msg)
220 	     gtk_widget_destroy(bi->msg);
221 	   if(bi->path)
222 	     g_free(bi->path);
223 	   g_free(bi);
224 	   return FALSE;
225       }
226    }
227 
228    bi->seconds++;
229    if(bi->seconds == 10 && bi->msg)
230    {  gtk_widget_destroy(bi->msg);
231       bi->msg = NULL;
232    }
233 
234    return bi->seconds > 60 ? FALSE : TRUE;
235 }
236 
237 /*
238  * Invoke the viewer
239  */
240 
try_viewer(viewer_info * bi)241 static void try_viewer(viewer_info *bi)
242 {  pid_t pid;
243 
244    bi->pid = pid = fork();
245 
246    if(pid == -1)
247    {  printf("fork failed\n");
248       return;
249    }
250 
251    /* make the parent remember and wait() for the viewer */
252 
253    if(pid > 0)
254    {  g_timeout_add(1000, viewer_timeout_func, (gpointer)bi);
255 
256       if(viewer_index)
257       {  g_free(Closure->viewer);
258 	 Closure->viewer = g_strdup(viewers[viewer_index]);
259       }
260    }
261 
262    /* try calling the viewer */
263 
264    if(pid == 0)
265    {  char *argv[10];
266       int argc = 0;
267 
268       argv[argc++] = viewer_index ? viewers[viewer_index] : Closure->viewer;
269       argv[argc++] = bi->path;
270       argv[argc++] = NULL;
271       execvp(argv[0], argv);
272 
273       _exit(110); /* couldn't execute */
274    }
275 }
276 
ShowPDF(char * target)277 void ShowPDF(char *target)
278 {  viewer_info *bi = g_malloc0(sizeof(viewer_info));
279    guint64 ignore;
280 
281    if(!Closure->docDir)
282    {
283       CreateMessage(_("Documentation not installed."), GTK_MESSAGE_ERROR);
284       g_free(bi);
285       return;
286    }
287 
288    /* If no target is given, show the manual. */
289 
290    if(!target)
291    {    bi->path = g_strdup_printf("%s/manual.pdf",Closure->docDir);
292    }
293    else
294       if(*target != '/') bi->path = g_strdup_printf("%s/%s",Closure->docDir, target);
295       else               bi->path = g_strdup(target);
296 
297    if(!LargeStat(bi->path, &ignore))
298    {
299       CreateMessage(_("Documentation file\n%s\nnot found.\n"), GTK_MESSAGE_ERROR, bi->path);
300       g_free(bi->path);
301       g_free(bi);
302       return;
303    }
304 
305    /* Lock the help button and show a message for 10 seconds. */
306 
307    TimedInsensitive(Closure->helpButton, 10000);
308    bi->msg = CreateMessage(_("Please hang on until the viewer comes up!"), GTK_MESSAGE_INFO);
309    g_signal_connect(G_OBJECT(bi->msg), "destroy", G_CALLBACK(msg_destroy_cb), bi);
310 
311    /* Try the first viwer */
312 
313    viewer_index = 0;
314    try_viewer(bi);
315 }
316