533 lines
14 KiB
C
533 lines
14 KiB
C
/* $Id$ */
|
|
/* Copyright (c) 2013 Pierre Pronchery <khorben@defora.org> */
|
|
/* This file is part of DeforaOS Desktop Browser */
|
|
/* This program 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, version 3 of the License.
|
|
*
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
#ifdef COMMON_RTRIM
|
|
# include <ctype.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <libintl.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include "Browser.h"
|
|
#define _(string) gettext(string)
|
|
#define N_(string) (string)
|
|
|
|
|
|
/* private */
|
|
/* types */
|
|
typedef struct _CommonTask
|
|
{
|
|
GPid pid;
|
|
guint source;
|
|
|
|
/* stdout */
|
|
gint o_fd;
|
|
GIOChannel * o_channel;
|
|
guint o_source;
|
|
|
|
/* stderr */
|
|
gint e_fd;
|
|
GIOChannel * e_channel;
|
|
guint e_source;
|
|
|
|
/* widgets */
|
|
GtkWidget * window;
|
|
#if GTK_CHECK_VERSION(2, 18, 0)
|
|
GtkWidget * infobar;
|
|
GtkWidget * infobar_label;
|
|
#endif
|
|
GtkWidget * view;
|
|
GtkWidget * statusbar;
|
|
guint statusbar_id;
|
|
} CommonTask;
|
|
|
|
|
|
/* prototypes */
|
|
/* tasks */
|
|
static CommonTask * _common_task_new(BrowserPluginHelper * helper,
|
|
BrowserPluginDefinition * plugin, char const * title,
|
|
char const * directory, char * argv[]);
|
|
static void _common_task_delete(CommonTask * task);
|
|
|
|
static void _common_task_set_status(CommonTask * task, char const * status);
|
|
|
|
static void _common_task_close(CommonTask * task);
|
|
static void _common_task_close_channel(CommonTask * task, GIOChannel * channel);
|
|
|
|
static void _common_task_copy(CommonTask * task);
|
|
|
|
static int _common_task_error(CommonTask * task, char const * message, int ret);
|
|
|
|
static int _common_task_save_buffer_as(CommonTask * task,
|
|
char const * filename);
|
|
static int _common_task_save_buffer_as_dialog(CommonTask * task);
|
|
|
|
/* callbacks */
|
|
static void _common_task_on_close(gpointer data);
|
|
static gboolean _common_task_on_closex(gpointer data);
|
|
static void _common_task_on_child_watch(GPid pid, gint status, gpointer data);
|
|
static void _common_task_on_copy(gpointer data);
|
|
static gboolean _common_task_on_io_can_read(GIOChannel * channel,
|
|
GIOCondition condition, gpointer data);
|
|
static void _common_task_on_save(gpointer data);
|
|
|
|
#ifdef COMMON_RTRIM
|
|
static void _common_rtrim(char * string);
|
|
#endif
|
|
|
|
|
|
/* constants */
|
|
/* tasks */
|
|
static const DesktopAccel _common_task_accel[] =
|
|
{
|
|
{ G_CALLBACK(_common_task_on_close), GDK_CONTROL_MASK, GDK_KEY_W },
|
|
{ NULL, 0, 0 }
|
|
};
|
|
|
|
|
|
/* variables */
|
|
/* tasks */
|
|
static DesktopToolbar _common_task_toolbar[] =
|
|
{
|
|
{ N_("Save as..."), G_CALLBACK(_common_task_on_save), GTK_STOCK_SAVE_AS,
|
|
GDK_CONTROL_MASK, GDK_KEY_S, NULL },
|
|
{ "", NULL, NULL, 0, 0, NULL },
|
|
{ N_("Copy"), G_CALLBACK(_common_task_on_copy), GTK_STOCK_COPY,
|
|
GDK_CONTROL_MASK, GDK_KEY_C, NULL },
|
|
{ NULL, NULL, NULL, 0, 0, NULL }
|
|
};
|
|
|
|
|
|
/* functions */
|
|
/* tasks */
|
|
/* common_task_new */
|
|
static CommonTask * _common_task_new(BrowserPluginHelper * helper,
|
|
BrowserPluginDefinition * plugin, char const * title,
|
|
char const * directory, char * argv[])
|
|
{
|
|
CommonTask * task;
|
|
GSpawnFlags flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD;
|
|
gboolean res;
|
|
GError * error = NULL;
|
|
GtkAccelGroup * group;
|
|
PangoFontDescription * font;
|
|
char buf[256];
|
|
GtkWidget * vbox;
|
|
GtkWidget * widget;
|
|
|
|
if((task = object_new(sizeof(*task))) == NULL)
|
|
return NULL;
|
|
#ifdef DEBUG
|
|
argv[0] = "echo";
|
|
#endif
|
|
res = g_spawn_async_with_pipes(directory, argv, NULL, flags, NULL, NULL,
|
|
&task->pid, NULL, &task->o_fd, &task->e_fd, &error);
|
|
if(res != TRUE)
|
|
{
|
|
helper->error(helper->browser, error->message, 1);
|
|
g_error_free(error);
|
|
object_delete(task);
|
|
return NULL;
|
|
}
|
|
/* widgets */
|
|
font = pango_font_description_new();
|
|
pango_font_description_set_family(font, "monospace");
|
|
group = gtk_accel_group_new();
|
|
desktop_accel_create(_common_task_accel, task, group);
|
|
task->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_add_accel_group(GTK_WINDOW(task->window), group);
|
|
g_object_unref(group);
|
|
gtk_window_set_default_size(GTK_WINDOW(task->window), 600, 400);
|
|
#if GTK_CHECK_VERSION(2, 6, 0)
|
|
if(plugin->icon != NULL)
|
|
gtk_window_set_icon_name(GTK_WINDOW(task->window),
|
|
plugin->icon);
|
|
#endif
|
|
snprintf(buf, sizeof(buf), "%s - %s (%s)", _(plugin->name), title,
|
|
directory);
|
|
gtk_window_set_title(GTK_WINDOW(task->window), buf);
|
|
g_signal_connect_swapped(task->window, "delete-event", G_CALLBACK(
|
|
_common_task_on_closex), task);
|
|
vbox = gtk_vbox_new(FALSE, 0);
|
|
/* toolbar */
|
|
widget = desktop_toolbar_create(_common_task_toolbar, task, group);
|
|
gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
|
|
#if GTK_CHECK_VERSION(2, 18, 0)
|
|
/* infobar */
|
|
task->infobar = gtk_info_bar_new_with_buttons(GTK_STOCK_CLOSE,
|
|
GTK_RESPONSE_CLOSE, NULL);
|
|
gtk_info_bar_set_message_type(GTK_INFO_BAR(task->infobar),
|
|
GTK_MESSAGE_ERROR);
|
|
g_signal_connect(task->infobar, "close", G_CALLBACK(gtk_widget_hide),
|
|
NULL);
|
|
g_signal_connect(task->infobar, "response", G_CALLBACK(
|
|
gtk_widget_hide), NULL);
|
|
widget = gtk_info_bar_get_content_area(GTK_INFO_BAR(task->infobar));
|
|
task->infobar_label = gtk_label_new(NULL);
|
|
gtk_widget_show(task->infobar_label);
|
|
gtk_box_pack_start(GTK_BOX(widget), task->infobar_label, TRUE, TRUE, 0);
|
|
gtk_widget_set_no_show_all(task->infobar, TRUE);
|
|
gtk_box_pack_start(GTK_BOX(vbox), task->infobar, FALSE, TRUE, 0);
|
|
#endif
|
|
/* view */
|
|
widget = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
|
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
|
task->view = gtk_text_view_new();
|
|
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(task->view), FALSE);
|
|
gtk_text_view_set_editable(GTK_TEXT_VIEW(task->view), FALSE);
|
|
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(task->view),
|
|
GTK_WRAP_WORD_CHAR);
|
|
gtk_widget_modify_font(task->view, font);
|
|
gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(widget),
|
|
task->view);
|
|
gtk_box_pack_start(GTK_BOX(vbox), widget, TRUE, TRUE, 0);
|
|
task->statusbar = gtk_statusbar_new();
|
|
task->statusbar_id = 0;
|
|
gtk_box_pack_start(GTK_BOX(vbox), task->statusbar, FALSE, TRUE, 0);
|
|
gtk_container_add(GTK_CONTAINER(task->window), vbox);
|
|
gtk_widget_show_all(task->window);
|
|
pango_font_description_free(font);
|
|
/* events */
|
|
task->source = g_child_watch_add(task->pid, _common_task_on_child_watch,
|
|
task);
|
|
task->o_channel = g_io_channel_unix_new(task->o_fd);
|
|
if((g_io_channel_set_encoding(task->o_channel, NULL, &error))
|
|
!= G_IO_STATUS_NORMAL)
|
|
{
|
|
_common_task_error(task, error->message, 1);
|
|
g_error_free(error);
|
|
}
|
|
task->o_source = g_io_add_watch(task->o_channel, G_IO_IN,
|
|
_common_task_on_io_can_read, task);
|
|
task->e_channel = g_io_channel_unix_new(task->e_fd);
|
|
if((g_io_channel_set_encoding(task->e_channel, NULL, &error))
|
|
!= G_IO_STATUS_NORMAL)
|
|
{
|
|
_common_task_error(task, error->message, 1);
|
|
g_error_free(error);
|
|
}
|
|
task->e_source = g_io_add_watch(task->e_channel, G_IO_IN,
|
|
_common_task_on_io_can_read, task);
|
|
_common_task_set_status(task, _("Running command..."));
|
|
return task;
|
|
}
|
|
|
|
|
|
/* common_task_delete */
|
|
static void _common_task_delete(CommonTask * task)
|
|
{
|
|
_common_task_close(task);
|
|
if(task->source != 0)
|
|
g_source_remove(task->source);
|
|
task->source = 0;
|
|
gtk_widget_destroy(task->window);
|
|
object_delete(task);
|
|
}
|
|
|
|
|
|
/* common_task_set_status */
|
|
static void _common_task_set_status(CommonTask * task, char const * status)
|
|
{
|
|
GtkStatusbar * sb = GTK_STATUSBAR(task->statusbar);
|
|
|
|
if(task->statusbar_id != 0)
|
|
gtk_statusbar_remove(sb, gtk_statusbar_get_context_id(sb, ""),
|
|
task->statusbar_id);
|
|
task->statusbar_id = gtk_statusbar_push(sb,
|
|
gtk_statusbar_get_context_id(sb, ""), status);
|
|
}
|
|
|
|
|
|
/* common_task_close */
|
|
static void _common_task_close(CommonTask * task)
|
|
{
|
|
_common_task_close_channel(task, task->o_channel);
|
|
_common_task_close_channel(task, task->e_channel);
|
|
}
|
|
|
|
|
|
/* common_task_close */
|
|
static void _common_task_close_channel(CommonTask * task, GIOChannel * channel)
|
|
{
|
|
if(channel != NULL && channel == task->o_channel)
|
|
{
|
|
if(task->o_source != 0)
|
|
g_source_remove(task->o_source);
|
|
task->o_source = 0;
|
|
g_io_channel_shutdown(task->o_channel, FALSE, NULL);
|
|
g_io_channel_unref(task->o_channel);
|
|
task->o_channel = NULL;
|
|
}
|
|
if(channel != NULL && task->e_channel != NULL)
|
|
{
|
|
if(task->e_source != 0)
|
|
g_source_remove(task->e_source);
|
|
task->e_source = 0;
|
|
g_io_channel_shutdown(task->e_channel, FALSE, NULL);
|
|
g_io_channel_unref(task->e_channel);
|
|
task->e_channel = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/* common_task_copy */
|
|
static void _common_task_copy(CommonTask * task)
|
|
{
|
|
GtkTextBuffer * tbuf;
|
|
GtkClipboard * clipboard;
|
|
|
|
tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(task->view));
|
|
clipboard = gtk_widget_get_clipboard(task->view,
|
|
GDK_SELECTION_CLIPBOARD);
|
|
gtk_text_buffer_copy_clipboard(tbuf, clipboard);
|
|
}
|
|
|
|
|
|
/* common_task_error */
|
|
static int _common_task_error(CommonTask * task, char const * message, int ret)
|
|
{
|
|
#if GTK_CHECK_VERSION(2, 18, 0)
|
|
gtk_label_set_text(GTK_LABEL(task->infobar_label), message);
|
|
gtk_widget_show(task->infobar);
|
|
#else
|
|
GtkWidget * dialog;
|
|
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(task->window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
# if GTK_CHECK_VERSION(2, 6, 0)
|
|
"%s", _("Error"));
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
# endif
|
|
"%s", message);
|
|
gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
|
|
g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(
|
|
gtk_widget_destroy), NULL);
|
|
gtk_widget_show(dialog);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* common_task_save_buffer_as */
|
|
static int _common_task_save_buffer_as(CommonTask * task, char const * filename)
|
|
{
|
|
struct stat st;
|
|
GtkWidget * dialog;
|
|
gboolean res;
|
|
FILE * fp;
|
|
GtkTextBuffer * tbuf;
|
|
GtkTextIter start;
|
|
GtkTextIter end;
|
|
gchar * buf;
|
|
size_t len;
|
|
|
|
if(filename == NULL)
|
|
return _common_task_save_buffer_as_dialog(task);
|
|
if(stat(filename, &st) == 0)
|
|
{
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(task->window),
|
|
GTK_DIALOG_MODAL
|
|
| GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s",
|
|
#if GTK_CHECK_VERSION(2, 6, 0)
|
|
_("Question"));
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(
|
|
dialog), "%s",
|
|
#endif
|
|
_("This file already exists. Overwrite?"));
|
|
gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
|
|
res = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
if(res == GTK_RESPONSE_NO)
|
|
return -1;
|
|
}
|
|
if((fp = fopen(filename, "w")) == NULL)
|
|
return -_common_task_error(task, strerror(errno), 1);
|
|
tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(task->view));
|
|
/* XXX allocating the complete file is not optimal */
|
|
gtk_text_buffer_get_start_iter(GTK_TEXT_BUFFER(tbuf), &start);
|
|
gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(tbuf), &end);
|
|
buf = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(tbuf), &start, &end,
|
|
FALSE);
|
|
len = strlen(buf);
|
|
if(fwrite(buf, sizeof(char), len, fp) != len)
|
|
{
|
|
g_free(buf);
|
|
fclose(fp);
|
|
unlink(filename);
|
|
return -_common_task_error(task, strerror(errno), 1);
|
|
}
|
|
g_free(buf);
|
|
if(fclose(fp) != 0)
|
|
{
|
|
unlink(filename);
|
|
return -_common_task_error(task, strerror(errno), 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* common_task_save_buffer_as_dialog */
|
|
static int _common_task_save_buffer_as_dialog(CommonTask * task)
|
|
{
|
|
int ret;
|
|
GtkWidget * dialog;
|
|
gchar * filename = NULL;
|
|
|
|
dialog = gtk_file_chooser_dialog_new(_("Save as..."),
|
|
GTK_WINDOW(task->window),
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
|
|
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
|
|
filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(
|
|
dialog));
|
|
gtk_widget_destroy(dialog);
|
|
if(filename == NULL)
|
|
return FALSE;
|
|
ret = _common_task_save_buffer_as(task, filename);
|
|
g_free(filename);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* callbacks */
|
|
/* common_task_on_close */
|
|
static void _common_task_on_close(gpointer data)
|
|
{
|
|
CommonTask * task = data;
|
|
|
|
gtk_widget_hide(task->window);
|
|
_common_task_close(task);
|
|
/* FIXME really implement */
|
|
}
|
|
|
|
|
|
/* common_task_on_closex */
|
|
static gboolean _common_task_on_closex(gpointer data)
|
|
{
|
|
CommonTask * task = data;
|
|
|
|
_common_task_on_close(task);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* common_task_on_child_watch */
|
|
static void _common_task_on_child_watch(GPid pid, gint status, gpointer data)
|
|
{
|
|
CommonTask * task = data;
|
|
char buf[256];
|
|
|
|
task->source = 0;
|
|
if(WIFEXITED(status))
|
|
{
|
|
snprintf(buf, sizeof(buf),
|
|
_("Command exited with error code %d"),
|
|
WEXITSTATUS(status));
|
|
_common_task_set_status(task, buf);
|
|
}
|
|
else if(WIFSIGNALED(status))
|
|
{
|
|
snprintf(buf, sizeof(buf), _("Command exited with signal %d"),
|
|
WTERMSIG(status));
|
|
_common_task_set_status(task, buf);
|
|
}
|
|
g_spawn_close_pid(pid);
|
|
}
|
|
|
|
|
|
/* common_task_on_copy */
|
|
static void _common_task_on_copy(gpointer data)
|
|
{
|
|
CommonTask * task = data;
|
|
|
|
_common_task_copy(task);
|
|
}
|
|
|
|
|
|
/* common_task_on_io_can_read */
|
|
static gboolean _common_task_on_io_can_read(GIOChannel * channel,
|
|
GIOCondition condition, gpointer data)
|
|
{
|
|
CommonTask * task = data;
|
|
char buf[256];
|
|
gsize cnt = 0;
|
|
GError * error = NULL;
|
|
GIOStatus status;
|
|
GtkTextBuffer * tbuf;
|
|
GtkTextIter iter;
|
|
|
|
if(condition != G_IO_IN)
|
|
return FALSE;
|
|
if(channel != task->o_channel && channel != task->e_channel)
|
|
return FALSE;
|
|
status = g_io_channel_read_chars(channel, buf, sizeof(buf), &cnt,
|
|
&error);
|
|
if(cnt > 0)
|
|
{
|
|
tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(task->view));
|
|
gtk_text_buffer_get_end_iter(tbuf, &iter);
|
|
gtk_text_buffer_insert(tbuf, &iter, buf, cnt);
|
|
}
|
|
switch(status)
|
|
{
|
|
case G_IO_STATUS_NORMAL:
|
|
break;
|
|
case G_IO_STATUS_ERROR:
|
|
_common_task_error(task, error->message, 1);
|
|
g_error_free(error);
|
|
case G_IO_STATUS_EOF:
|
|
default: /* should not happen... */
|
|
_common_task_close_channel(task, channel);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* common_task_on_save */
|
|
static void _common_task_on_save(gpointer data)
|
|
{
|
|
CommonTask * task = data;
|
|
|
|
_common_task_save_buffer_as_dialog(task);
|
|
}
|
|
|
|
|
|
#ifdef COMMON_RTRIM
|
|
/* common_rtrim */
|
|
static void _common_rtrim(char * string)
|
|
{
|
|
unsigned char * s = (unsigned char *)string;
|
|
size_t i;
|
|
|
|
if(s == NULL || (i = strlen(string)) == 0)
|
|
return;
|
|
for(i--; s[i] != '\0' && isspace(s[i]); i--)
|
|
{
|
|
string[i] = '\0';
|
|
if(i == 0)
|
|
break;
|
|
}
|
|
}
|
|
#endif
|