Files
dvdisaster/show-manual.c
2017-12-21 05:31:58 +11:00

316 lines
8.3 KiB
C

/* dvdisaster: Additional error correction for optical media.
* Copyright (C) 2004-2017 Carsten Gnoerlich.
*
* Email: carsten@dvdisaster.org -or- cgnoerlich@fsfe.org
* Project homepage: http://www.dvdisaster.org
*
* This file is part of dvdisaster.
*
* dvdisaster is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* dvdisaster is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with dvdisaster. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dvdisaster.h"
#include <sys/wait.h>
/***
*** Ask user to specify his viewer
***/
#define SEARCH_BUTTON 1
typedef struct
{ GtkWidget *dialog;
GtkWidget *entry;
GtkWidget *search;
GtkWidget *filesel;
GtkWidget *fileok;
GtkWidget *filecancel;
char *path;
} viewer_dialog_info;
static void response_cb(GtkWidget *widget, int response, gpointer data)
{ viewer_dialog_info *bdi = (viewer_dialog_info*)data;
switch(response)
{ case GTK_RESPONSE_ACCEPT:
if(Closure->viewer) g_free(Closure->viewer);
Closure->viewer = g_strdup(gtk_entry_get_text(GTK_ENTRY(bdi->entry)));
ShowPDF(bdi->path);
break;
case GTK_RESPONSE_REJECT:
if(bdi->path) g_free(bdi->path);
break;
}
gtk_widget_destroy(widget);
if(bdi->filesel)
gtk_widget_destroy(bdi->filesel);
g_free(bdi);
}
static void search_cb(GtkWidget *widget, gpointer data)
{ viewer_dialog_info *bdi = (viewer_dialog_info*)data;
if(widget == bdi->search)
{ bdi->filesel = gtk_file_selection_new(_utf("windowtitle|Choose a PDF viewer"));
bdi->fileok = GTK_FILE_SELECTION(bdi->filesel)->ok_button;
bdi->filecancel = GTK_FILE_SELECTION(bdi->filesel)->cancel_button;
ReverseCancelOK(GTK_DIALOG(bdi->filesel));
gtk_file_selection_hide_fileop_buttons(GTK_FILE_SELECTION(bdi->filesel));
g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(bdi->filesel)->ok_button), "clicked",
G_CALLBACK(search_cb), bdi);
g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(bdi->filesel)->cancel_button), "clicked",
G_CALLBACK(search_cb), bdi);
gtk_widget_show(bdi->filesel);
}
if(widget == bdi->fileok)
{
if(Closure->viewer) g_free(Closure->viewer);
Closure->viewer = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(bdi->filesel)));
ShowPDF(bdi->path);
gtk_widget_destroy(bdi->filesel);
gtk_widget_destroy(bdi->dialog);
g_free(bdi);
return;
}
if(widget == bdi->filecancel)
{ gtk_widget_destroy(bdi->filesel);
bdi->filesel = NULL;
}
}
static void viewer_dialog(char *path)
{ GtkWidget *dialog, *vbox, *hbox, *label, *entry, *button;
viewer_dialog_info *bdi = g_malloc0(sizeof(viewer_dialog_info));
/* Create the dialog */
dialog = gtk_dialog_new_with_buttons(_utf("windowtitle|PDF viewer required"),
Closure->window, GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
bdi->dialog = dialog;
if(path)
{ bdi->path = g_strdup(path);
}
vbox = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, FALSE, FALSE, 0);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
/* Insert the contents */
label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), _utf("<b>Could not find a suitable PDF viewer.</b>\n\n"
"Which PDF viewer would you like to use\n"
"for reading the online documentation?\n\n"
"Please enter its name (e.g. xpdf) or\n"
"use the \"Search\" button for a file dialog.\n")),
gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 10);
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 10);
bdi->entry = entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 10);
bdi->search = button = gtk_button_new_with_label(_utf("Search"));
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(search_cb), bdi);
gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 10);
/* Show it */
g_signal_connect(dialog, "response", G_CALLBACK(response_cb), bdi);
gtk_widget_show_all(dialog);
}
/***
*** Show the manual in an external viewer
***/
/*
* Check the child processes exit status
* to find whether the viewer could be invoked.
*/
typedef struct
{ pid_t pid;
char *path;
GtkWidget *msg;
int seconds;
} viewer_info;
static void msg_destroy_cb(GtkWidget *widget, gpointer data)
{ viewer_info *bi = (viewer_info*)data;
bi->msg = NULL;
}
/*
* The following list of viewers
* will be tried one at a time until one entry succeeds by:
* - returning zero
* - not returning within 60 seconds
*/
static int viewer_index;
static void try_viewer(viewer_info*);
static char *viewers[] =
{ "evince",
"xpdf",
"okular",
"gv",
"mupdf",
"pdfcube",
"zathura",
NULL
};
static gboolean viewer_timeout_func(gpointer data)
{ viewer_info *bi = (viewer_info*)data;
int status;
waitpid(bi->pid, &status, WNOHANG);
/* At least mozilla returns random values under FreeBSD on success,
so we can't rely on the return value exept our own 110 one. */
if(WIFEXITED(status))
{
switch(WEXITSTATUS(status))
{ case 110: /* viewer did not execute */
viewer_index++;
if(!viewers[viewer_index]) /* all viewers from the list failed */
{ viewer_dialog(bi->path);
if(bi->msg)
gtk_widget_destroy(bi->msg);
if(bi->path)
g_free(bi->path);
g_free(bi);
}
else /* try next viewer from list */
{ bi->seconds = 0;
try_viewer(bi);
}
return FALSE;
case 0: /* viewer assumed to be successful */
default:
if(bi->msg)
gtk_widget_destroy(bi->msg);
if(bi->path)
g_free(bi->path);
g_free(bi);
return FALSE;
}
}
bi->seconds++;
if(bi->seconds == 10 && bi->msg)
{ gtk_widget_destroy(bi->msg);
bi->msg = NULL;
}
return bi->seconds > 60 ? FALSE : TRUE;
}
/*
* Invoke the viewer
*/
static void try_viewer(viewer_info *bi)
{ pid_t pid;
bi->pid = pid = fork();
if(pid == -1)
{ printf("fork failed\n");
return;
}
/* make the parent remember and wait() for the viewer */
if(pid > 0)
{ g_timeout_add(1000, viewer_timeout_func, (gpointer)bi);
if(viewer_index)
{ g_free(Closure->viewer);
Closure->viewer = g_strdup(viewers[viewer_index]);
}
}
/* try calling the viewer */
if(pid == 0)
{ char *argv[10];
int argc = 0;
argv[argc++] = viewer_index ? viewers[viewer_index] : Closure->viewer;
argv[argc++] = bi->path;
argv[argc++] = NULL;
execvp(argv[0], argv);
_exit(110); /* couldn't execute */
}
}
void ShowPDF(char *target)
{ viewer_info *bi = g_malloc0(sizeof(viewer_info));
guint64 ignore;
if(!Closure->docDir)
{
CreateMessage(_("Documentation not installed."), GTK_MESSAGE_ERROR);
g_free(bi);
return;
}
/* If no target is given, show the manual. */
if(!target)
{ bi->path = g_strdup_printf("%s/manual.pdf",Closure->docDir);
}
else
if(*target != '/') bi->path = g_strdup_printf("%s/%s",Closure->docDir, target);
else bi->path = g_strdup(target);
if(!LargeStat(bi->path, &ignore))
{
CreateMessage(_("Documentation file\n%s\nnot found.\n"), GTK_MESSAGE_ERROR, bi->path);
g_free(bi->path);
g_free(bi);
return;
}
/* Lock the help button and show a message for 10 seconds. */
TimedInsensitive(Closure->helpButton, 10000);
bi->msg = CreateMessage(_("Please hang on until the viewer comes up!"), GTK_MESSAGE_INFO);
g_signal_connect(G_OBJECT(bi->msg), "destroy", G_CALLBACK(msg_destroy_cb), bi);
/* Try the first viwer */
viewer_index = 0;
try_viewer(bi);
}