#define NCOLOURS (lenof(((Config *)0)->colours))
+GdkAtom compound_text_atom, utf8_string_atom;
+
+extern char **pty_argv; /* declared in pty.c */
+extern int use_pty_argv;
+
struct gui_data {
GtkWidget *window, *area, *sbar;
GtkBox *hbox;
GtkAdjustment *sbar_adjust;
GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2;
+ GtkWidget *sessionsmenu;
GdkPixmap *pixmap;
GdkFont *fonts[4]; /* normal, bold, wide, widebold */
struct {
int ignore_sbar;
int mouseptr_visible;
guint term_paste_idle_id;
- GdkAtom compound_text_atom, utf8_string_atom;
int alt_keycode;
int alt_digits;
char wintitle[sizeof(((Config *)0)->wintitle)];
struct unicode_data ucsdata;
Config cfg;
void *eventlogstuff;
+ char *progname, **gtkargvstart;
+ int ngtkargs;
};
struct draw_ctx {
void connection_fatal(void *frontend, char *p, ...)
{
- Terminal *term = (Terminal *)frontend;
- struct gui_data *inst = (struct gui_data *)term->frontend;
+ struct gui_data *inst = (struct gui_data *)frontend;
va_list ap;
char *msg;
*/
}
-int askappend(void *frontend, Filename filename)
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
{
- /*
- * Logging in an xterm-alike is liable to be something you only
- * do at serious diagnostic need. Hence, I'm going to take the
- * easy option for now and assume we always want to overwrite
- * log files. I can always make it properly configurable later.
- */
- return 2;
+ struct gui_data *inst = (struct gui_data *)frontend;
+ return term_data(inst->term, is_stderr, data, len);
}
void logevent(void *frontend, char *string)
{
- Terminal *term = (Terminal *)frontend;
- struct gui_data *inst = (struct gui_data *)term->frontend;
+ struct gui_data *inst = (struct gui_data *)frontend;
log_eventlog(inst->logctx, string);
*/
void *get_window(void *frontend)
{
- Terminal *term = (Terminal *)frontend;
- struct gui_data *inst = (struct gui_data *)term->frontend;
+ struct gui_data *inst = (struct gui_data *)frontend;
return inst->window;
}
gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data)
{
- /*
- * We could implement warn-on-close here if we really wanted
- * to.
- */
+ struct gui_data *inst = (struct gui_data *)data;
+ if (!inst->exited && inst->cfg.warn_on_close) {
+ if (!reallyclose(inst))
+ return TRUE;
+ }
return FALSE;
}
{
struct gui_data *inst = (struct gui_data *)data;
int w, h, need_size = 0;
+ GdkGC *gc;
+ /*
+ * See if the terminal size has changed, in which case we must
+ * let the terminal know.
+ */
w = (event->width - 2*inst->cfg.window_border) / inst->font_width;
h = (event->height - 2*inst->cfg.window_border) / inst->font_height;
-
if (w != inst->width || h != inst->height) {
- if (inst->pixmap) {
- gdk_pixmap_unref(inst->pixmap);
- inst->pixmap = NULL;
- }
inst->cfg.width = inst->width = w;
inst->cfg.height = inst->height = h;
need_size = 1;
}
- if (!inst->pixmap) {
- GdkGC *gc;
-
- inst->pixmap = gdk_pixmap_new(widget->window,
- (inst->cfg.width * inst->font_width +
- 2*inst->cfg.window_border),
- (inst->cfg.height * inst->font_height +
- 2*inst->cfg.window_border), -1);
-
- gc = gdk_gc_new(inst->area->window);
- gdk_gc_set_foreground(gc, &inst->cols[18]); /* default background */
- gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
- inst->cfg.width * inst->font_width + 2*inst->cfg.window_border,
- inst->cfg.height * inst->font_height + 2*inst->cfg.window_border);
- gdk_gc_unref(gc);
+
+ if (inst->pixmap) {
+ gdk_pixmap_unref(inst->pixmap);
+ inst->pixmap = NULL;
}
- if (need_size) {
+ inst->pixmap = gdk_pixmap_new(widget->window,
+ (inst->cfg.width * inst->font_width +
+ 2*inst->cfg.window_border),
+ (inst->cfg.height * inst->font_height +
+ 2*inst->cfg.window_border), -1);
+
+ gc = gdk_gc_new(inst->area->window);
+ gdk_gc_set_foreground(gc, &inst->cols[18]); /* default background */
+ gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
+ inst->cfg.width * inst->font_width + 2*inst->cfg.window_border,
+ inst->cfg.height * inst->font_height + 2*inst->cfg.window_border);
+ gdk_gc_unref(gc);
+
+ if (need_size && inst->term) {
term_size(inst->term, h, w, inst->cfg.savelines);
}
+ if (inst->term)
+ term_invalidate(inst->term);
+
return TRUE;
}
{
struct gui_data *inst = (struct gui_data *)data;
char output[32];
- int start, end;
+ int start, end, special;
/* By default, nothing is generated. */
end = start = 0;
return TRUE;
}
+ special = FALSE;
+
/* ALT+things gives leading Escape. */
output[0] = '\033';
strncpy(output+1, event->string, 31);
(event->state & GDK_CONTROL_MASK)) {
output[1] = '\003';
end = 2;
+ special = TRUE;
+ }
+
+ /* We handle Return ourselves, because it needs to be flagged as
+ * special to ldisc. */
+ if (event->keyval == GDK_Return) {
+ output[1] = '\015';
+ end = 2;
+ special = TRUE;
}
/* Control-2, Control-Space and Control-@ are NUL */
!(event->state & GDK_SHIFT_MASK)) {
output[1] = inst->cfg.bksp_is_delete ? '\x7F' : '\x08';
end = 2;
+ special = TRUE;
}
/* For Shift Backspace, do opposite of what is configured. */
if (event->keyval == GDK_BackSpace &&
(event->state & GDK_SHIFT_MASK)) {
output[1] = inst->cfg.bksp_is_delete ? '\x08' : '\x7F';
end = 2;
+ special = TRUE;
}
/* Shift-Tab is ESC [ Z */
printf("\n");
#endif
- if (!inst->direct_to_font) {
+ if (special) {
+ /*
+ * For special control characters, the character set
+ * should never matter.
+ */
+ output[end] = '\0'; /* NUL-terminate */
+ ldisc_send(inst->ldisc, output+start, -2, 1);
+ } else if (!inst->direct_to_font) {
/*
* The stuff we've just generated is assumed to be
* ISO-8859-1! This sounds insane, but `man
gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
GDK_SELECTION_TYPE_STRING, 1);
gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
- inst->compound_text_atom, 1);
+ compound_text_atom, 1);
if (inst->pasteout_data_utf8)
gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
- inst->utf8_string_atom, 1);
+ utf8_string_atom, 1);
}
+
+ if (must_deselect)
+ term_deselect(inst->term);
}
void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
guint info, guint time_stamp, gpointer data)
{
struct gui_data *inst = (struct gui_data *)data;
- if (seldata->target == inst->utf8_string_atom)
+ if (seldata->target == utf8_string_atom)
gtk_selection_data_set(seldata, seldata->target, 8,
inst->pasteout_data_utf8,
inst->pasteout_data_utf8_len);
* fall back to an ordinary string.
*/
gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
- inst->utf8_string_atom, GDK_CURRENT_TIME);
+ utf8_string_atom, GDK_CURRENT_TIME);
} else {
/*
* If we're in direct-to-font mode, we disable UTF-8
{
struct gui_data *inst = (struct gui_data *)data;
- if (seldata->target == inst->utf8_string_atom && seldata->length <= 0) {
+ if (seldata->target == utf8_string_atom && seldata->length <= 0) {
/*
* Failed to get a UTF-8 selection string. Try an ordinary
* string.
*/
if (seldata->length <= 0 ||
(seldata->type != GDK_SELECTION_TYPE_STRING &&
- seldata->type != inst->utf8_string_atom))
+ seldata->type != utf8_string_atom))
return; /* Nothing happens. */
if (inst->pastein_data)
inst->pastein_data = snewn(seldata->length, wchar_t);
inst->pastein_data_len = seldata->length;
inst->pastein_data_len =
- mb_to_wc((seldata->type == inst->utf8_string_atom ?
+ mb_to_wc((seldata->type == utf8_string_atom ?
CS_UTF8 : inst->ucsdata.line_codepage),
0, seldata->data, seldata->length,
inst->pastein_data, inst->pastein_data_len);
long get_windowid(void *frontend)
{
- Terminal *term = (Terminal *)frontend;
- struct gui_data *inst = (struct gui_data *)(term->frontend);
+ struct gui_data *inst = (struct gui_data *)frontend;
return (long)GDK_WINDOW_XWINDOW(inst->area->window);
}
struct gui_data *inst, Config *cfg)
{
int err = 0;
- extern char **pty_argv; /* declared in pty.c */
- extern int use_pty_argv;
+ char *val;
/*
* Macros to make argument handling easier. Note that because
}
#define SECOND_PASS_ONLY { if (!do_everything) continue; }
- char *val;
while (--argc > 0) {
char *p = *++argv;
int ret;
ldisc_send(inst->ldisc, NULL, 0, 0);
}
+void copy_all_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ term_copyall(inst->term);
+}
+
void special_menuitem(GtkMenuItem *item, gpointer data)
{
struct gui_data *inst = (struct gui_data *)data;
oldcfg.window_border != cfg2.window_border || need_size) {
set_geom_hints(inst);
request_resize(inst, cfg2.width, cfg2.height);
- //term_size(inst->term, cfg2.height, cfg2.width, cfg2.savelines);
- // where TF is our configure event going?!
- }
+ } else {
+ /*
+ * The above will have caused a call to term_size() for
+ * us if it happened. If the user has fiddled with only
+ * the scrollback size, the above will not have
+ * happened and we will need an explicit term_size()
+ * here.
+ */
+ if (oldcfg.savelines != cfg2.savelines)
+ term_size(inst->term, inst->term->rows, inst->term->cols,
+ cfg2.savelines);
+ }
term_invalidate(inst->term);
}
sfree(title);
}
+void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...)
+{
+ /*
+ * Re-execing ourself is not an exact science under Unix. I do
+ * the best I can by using /proc/self/exe if available and by
+ * assuming argv[0] can be found on $PATH if not.
+ *
+ * Note that we also have to reconstruct the elements of the
+ * original argv which gtk swallowed, since the user wants the
+ * new session to appear on the same X display as the old one.
+ */
+ char **args;
+ va_list ap;
+ int i, n;
+ int pid;
+
+ /*
+ * Collect the arguments with which to re-exec ourself.
+ */
+ va_start(ap, fd_to_close);
+ n = 2; /* progname and terminating NULL */
+ n += inst->ngtkargs;
+ while (va_arg(ap, char *) != NULL)
+ n++;
+ va_end(ap);
+
+ args = snewn(n, char *);
+ args[0] = inst->progname;
+ args[n-1] = NULL;
+ for (i = 0; i < inst->ngtkargs; i++)
+ args[i+1] = inst->gtkargvstart[i];
+
+ i++;
+ va_start(ap, fd_to_close);
+ while ((args[i++] = va_arg(ap, char *)) != NULL);
+ va_end(ap);
+
+ assert(i == n);
+
+ /*
+ * Do the double fork.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ return;
+ }
+
+ if (pid == 0) {
+ int pid2 = fork();
+ if (pid2 < 0) {
+ perror("fork");
+ _exit(1);
+ } else if (pid2 > 0) {
+ /*
+ * First child has successfully forked second child. My
+ * Work Here Is Done. Note the use of _exit rather than
+ * exit: the latter appears to cause destroy messages
+ * to be sent to the X server. I suspect gtk uses
+ * atexit.
+ */
+ _exit(0);
+ }
+
+ /*
+ * If we reach here, we are the second child, so we now
+ * actually perform the exec.
+ */
+ if (fd_to_close >= 0)
+ close(fd_to_close);
+
+ execv("/proc/self/exe", args);
+ execvp(inst->progname, args);
+ perror("exec");
+ _exit(127);
+
+ } else {
+ int status;
+ waitpid(pid, &status, 0);
+ }
+
+}
+
+void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)
+{
+ struct gui_data *inst = (struct gui_data *)gdata;
+ /*
+ * For this feature we must marshal cfg and (possibly) pty_argv
+ * into a byte stream, create a pipe, and send this byte stream
+ * to the child through the pipe.
+ */
+ int i, ret, size;
+ char *data;
+ char option[80];
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ perror("pipe");
+ return;
+ }
+
+ size = sizeof(inst->cfg);
+ if (use_pty_argv && pty_argv) {
+ for (i = 0; pty_argv[i]; i++)
+ size += strlen(pty_argv[i]) + 1;
+ }
+
+ data = snewn(size, char);
+ memcpy(data, &inst->cfg, sizeof(inst->cfg));
+ if (use_pty_argv && pty_argv) {
+ int p = sizeof(inst->cfg);
+ for (i = 0; pty_argv[i]; i++) {
+ strcpy(data + p, pty_argv[i]);
+ p += strlen(pty_argv[i]) + 1;
+ }
+ assert(p == size);
+ }
+
+ sprintf(option, "---[%d,%d]", pipefd[0], size);
+ fcntl(pipefd[0], F_SETFD, 0);
+ fork_and_exec_self(inst, pipefd[1], option, NULL);
+ close(pipefd[0]);
+
+ i = 0;
+ while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
+ i += ret;
+ if (ret < 0)
+ perror("write to pipe");
+ close(pipefd[1]);
+ sfree(data);
+}
+
+int read_dupsession_data(struct gui_data *inst, Config *cfg, char *arg)
+{
+ int fd, i, ret, size;
+ char *data;
+
+ if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
+ fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
+ exit(1);
+ }
+
+ data = snewn(size, char);
+ i = 0;
+ while (i < size && (ret = read(fd, data + i, size - i)) > 0)
+ i += ret;
+ if (ret < 0) {
+ perror("read from pipe");
+ exit(1);
+ } else if (i < size) {
+ fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
+ appname);
+ exit(1);
+ }
+
+ memcpy(cfg, data, sizeof(Config));
+ if (use_pty_argv && size > sizeof(Config)) {
+ int n = 0;
+ i = sizeof(Config);
+ while (i < size) {
+ while (i < size && data[i]) i++;
+ if (i >= size) {
+ fprintf(stderr, "%s: malformed Duplicate Session data\n",
+ appname);
+ exit(1);
+ }
+ i++;
+ n++;
+ }
+ pty_argv = snewn(n+1, char *);
+ pty_argv[n] = NULL;
+ n = 0;
+ i = sizeof(Config);
+ while (i < size) {
+ char *p = data + i;
+ while (i < size && data[i]) i++;
+ assert(i < size);
+ i++;
+ pty_argv[n++] = dupstr(p);
+ }
+ }
+
+ return 0;
+}
+
+void new_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ fork_and_exec_self(inst, -1, NULL);
+}
+
+void saved_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+ fork_and_exec_self(inst, -1, "-load", str, NULL);
+}
+
+void saved_session_freedata(GtkMenuItem *item, gpointer data)
+{
+ char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+ sfree(str);
+}
+
void update_specials_menu(void *frontend)
{
- Terminal *term = (Terminal *)frontend;
- struct gui_data *inst = (struct gui_data *)term->frontend;
+ struct gui_data *inst = (struct gui_data *)frontend;
const struct telnet_special *specials;
extern int cfgbox(Config *cfg);
struct gui_data *inst;
+ /*
+ * Create an instance structure and initialise to zeroes
+ */
+ inst = snew(struct gui_data);
+ memset(inst, 0, sizeof(*inst));
+ inst->alt_keycode = -1; /* this one needs _not_ to be zero */
+
/* defer any child exit handling until we're ready to deal with
* it */
block_signal(SIGCHLD, 1);
- gtk_init(&argc, &argv);
+ /*
+ * SIGPIPE is not something we want to see terminating the
+ * process.
+ */
+ block_signal(SIGPIPE, 1);
+ inst->progname = argv[0];
/*
- * Create an instance structure and initialise to zeroes
+ * Copy the original argv before letting gtk_init fiddle with
+ * it. It will be required later.
*/
- inst = snew(struct gui_data);
- memset(inst, 0, sizeof(*inst));
- inst->alt_keycode = -1; /* this one needs _not_ to be zero */
+ {
+ int i, oldargc;
+ inst->gtkargvstart = snewn(argc-1, char *);
+ for (i = 1; i < argc; i++)
+ inst->gtkargvstart[i-1] = dupstr(argv[i]);
+ oldargc = argc;
+ gtk_init(&argc, &argv);
+ inst->ngtkargs = oldargc - argc;
+ }
- if (do_cmdline(argc, argv, 0, inst, &inst->cfg))
- exit(1); /* pre-defaults pass to get -class */
- do_defaults(NULL, &inst->cfg);
- if (do_cmdline(argc, argv, 1, inst, &inst->cfg))
- exit(1); /* post-defaults, do everything */
+ if (argc > 1 && !strncmp(argv[1], "---", 3)) {
+ read_dupsession_data(inst, &inst->cfg, argv[1]);
+ /* Splatter this argument so it doesn't clutter a ps listing */
+ memset(argv[1], 0, strlen(argv[1]));
+ } else {
+ if (do_cmdline(argc, argv, 0, inst, &inst->cfg))
+ exit(1); /* pre-defaults pass to get -class */
+ do_defaults(NULL, &inst->cfg);
+ if (do_cmdline(argc, argv, 1, inst, &inst->cfg))
+ exit(1); /* post-defaults, do everything */
- cmdline_run_saved(&inst->cfg);
+ cmdline_run_saved(&inst->cfg);
- if (!*inst->cfg.host && !cfgbox(&inst->cfg))
- exit(0); /* config box hit Cancel */
+ if (!*inst->cfg.host && !cfgbox(&inst->cfg))
+ exit(0); /* config box hit Cancel */
+ }
- inst->compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
- inst->utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+ if (!compound_text_atom)
+ compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
+ if (!utf8_string_atom)
+ utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
setup_fonts_ucs(inst);
{
GtkWidget *menuitem;
char *s;
- extern const int use_event_log;
+ extern const int use_event_log, new_session, saved_sessions;
inst->menu = gtk_menu_new();
gtk_signal_connect(GTK_OBJECT(menuitem), "activate", \
GTK_SIGNAL_FUNC(func), inst); \
} while (0)
+ if (new_session)
+ MKMENUITEM("New Session", new_session_menuitem);
+ MKMENUITEM("Duplicate Session", dup_session_menuitem);
+ if (saved_sessions) {
+ struct sesslist sesslist;
+ int i;
+
+ inst->sessionsmenu = gtk_menu_new();
+
+ get_sesslist(&sesslist, TRUE);
+ for (i = 1; i < sesslist.nsessions; i++) {
+ menuitem = gtk_menu_item_new_with_label(sesslist.sessions[i]);
+ gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
+ gtk_widget_show(menuitem);
+ gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
+ dupstr(sesslist.sessions[i]));
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(saved_session_menuitem),
+ inst);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "destroy",
+ GTK_SIGNAL_FUNC(saved_session_freedata),
+ inst);
+ }
+ get_sesslist(&sesslist, FALSE);
+
+ MKMENUITEM("Saved Sessions", NULL);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
+ inst->sessionsmenu);
+ }
+ MKMENUITEM(NULL, NULL);
MKMENUITEM("Change Settings", change_settings_menuitem);
MKMENUITEM(NULL, NULL);
if (use_event_log)
inst->specialsitem2 = menuitem;
MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
+ MKMENUITEM("Copy All", copy_all_menuitem);
MKMENUITEM(NULL, NULL);
s = dupcat("About ", appname, NULL);
MKMENUITEM(s, about_menuitem);
{
char *realhost, *error;
- error = inst->back->init((void *)inst->term, &inst->backhandle,
+ error = inst->back->init((void *)inst, &inst->backhandle,
&inst->cfg, inst->cfg.host, inst->cfg.port,
&realhost, inst->cfg.tcp_nodelay);
}
}
inst->back->provide_logctx(inst->backhandle, inst->logctx);
- update_specials_menu(inst->term);
+ update_specials_menu(inst);
term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle);