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