/*
    roxterm - VTE/GTK terminal emulator with tabs
    Copyright (C) 2004-2011 Tony Houghton <h@realh.co.uk>

    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; either version 2 of the License, or
    (at your option) any later version.

    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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "defns.h"

#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <gdk/gdkx.h>

#include "about.h"
#include "boxcompat.h"
#include "colourscheme.h"
#include "dlg.h"
#include "dragrcv.h"
#include "dynopts.h"
#include "encodings.h"
#include "globalopts.h"
#include "logo.h"
#include "optsfile.h"
#include "optsdbus.h"
#include "roxterm.h"
#include "multitab.h"
#include "search.h"
#if ENABLE_SM
#include "session.h"
#endif
#include "session-file.h"
#include "shortcuts.h"
#include "uri.h"
#include "x11support.h"

/* vte_terminal_set_word_chars was removed from vte 0.38 but reinstated
 * in 0.40. Hopefully this will allow the option to be ignored in 0.38
 * but work in 0.40 without recompiling roxterm.
 */
void __attribute__((weak))
vte_terminal_set_word_chars(VteTerminal *vte, const char *chars)
{
    (void) vte;
    (void) chars;
}

typedef enum {
    ROXTerm_Match_Invalid,
    ROXTerm_Match_Complete,
    ROXTerm_Match_WWW,
    ROXTerm_Match_FTP,
    ROXTerm_Match_MailTo,
    ROXTerm_Match_File,
    ROXTerm_Match_SSH_Host,
    ROXTerm_Match_Misc_URI,
} ROXTerm_MatchType;

typedef struct {
    int tag;
    ROXTerm_MatchType type;
} ROXTerm_MatchMap;

struct ROXTermData {
    /* We do not own references to tab or widget */
    MultiTab *tab;
    GtkWidget *widget;            /* VteTerminal */
    GtkWidget *hbox;
    GtkWidget *scrollbar;
    pid_t pid;
    /* We own a reference to colour_scheme */
    Options *colour_scheme;
    char *matched_url;
    ROXTerm_MatchType match_type;
    Options *profile;
    char *special_command;      /* Next window/tab opened from this one should
                                   run this command instead of default */
    char **commandv;            /* Copied from --execute args */
    char **actual_commandv;     /* The actual command used */
    char *directory;            /* Copied from global_options_directory */
    GArray *match_map;
    gboolean no_respawn;
    gboolean running;
    DragReceiveData *drd;

    gboolean hold_over_uri;    /* Whether we've just received a button press
                               over a URI which may turn into a drag or click
                                */
    double hold_x, hold_y;     /* Position of event above */
    gulong hold_handler_id;    /* Signal handler id for the above event */
    char *encoding;
    double target_zoom_factor, current_zoom_factor;
    int zoom_index;
    gboolean post_exit_tag;
    char *display_name;
    const char *status_icon_name;
    gboolean maximise;
    gulong win_state_changed_tag;
    GtkWidget *replace_task_dialog;
    gboolean postponed_free;
    PangoFontDescription *pango_desc;
    gboolean dont_lookup_dimensions;
    char *reply;
    int columns, rows;
    char **env;
    char *search_pattern;
    guint search_flags;
    int file_match_tag[2];
    gboolean from_session;
    GtkStyleContext *style_context;
    int padding_w, padding_h;
};

#define PROFILE_NAME_KEY "roxterm_profile_name"

static GList *roxterm_terms = NULL;

static DynamicOptions *roxterm_profiles = NULL;

static void roxterm_apply_profile(ROXTermData * roxterm, VteTerminal * vte,
        gboolean update_geometry);

inline static MultiWin *roxterm_get_win(ROXTermData *roxterm)
{
    return roxterm->tab ? multi_tab_get_parent(roxterm->tab) : NULL;
}

/********************** Encodings ***************************/

static Encodings *roxterm_encodings = NULL;

inline static char const **roxterm_list_encodings(void)
{
    return encodings_list(roxterm_encodings);
}

static void roxterm_apply_encoding(ROXTermData *roxterm, VteTerminal *vte)
{
    vte_terminal_set_encoding(vte, roxterm->encoding, NULL);
}

void roxterm_encoding_toggled(GtkCheckMenuItem *item, gpointer win)
{
    if (gtk_check_menu_item_get_active(item)
            && !multi_win_get_ignore_toggles(win))
    {
        ROXTermData *roxterm =
            multi_tab_get_user_data(multi_win_get_current_tab(win));
        const char *encoding = menutree_encoding_from_widget(item);

        g_free(roxterm->encoding);
        roxterm->encoding = encoding ? g_strdup(encoding) : NULL;
        roxterm_apply_encoding(roxterm, VTE_TERMINAL(roxterm->widget));
    }
}

static void roxterm_encodings_changed(MenuTree *mtree,
        const char *what_happened,
        const char *current_name, const char *new_name)
{
    if (!strcmp(what_happened, OPTSDBUS_DELETED))
    {
        menutree_remove_encoding(mtree, current_name);
    }
    else if (!strcmp(what_happened, OPTSDBUS_ADDED))
    {
        menutree_add_encoding(mtree, current_name, roxterm_encoding_toggled);
    }
    else if (!strcmp(what_happened, OPTSDBUS_RENAMED))
    {
        menutree_change_encoding(mtree, current_name, new_name,
                roxterm_encoding_toggled);
    }
    else
    {
        g_critical(_("Action '%s' not recognised for encodings"),
                what_happened);
    }
}

/******************** End Encodings *************************/

/*********************** URI handling ***********************/

static int roxterm_match_add(ROXTermData *roxterm, VteTerminal *vte,
        const char *match, ROXTerm_MatchType type)
{
    ROXTerm_MatchMap map;

    map.type = type;
    map.tag = vte_terminal_match_add_gregex(vte,
            g_regex_new(match, G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF,
                0, NULL),
            0);
    vte_terminal_match_set_cursor_type(vte, map.tag, GDK_HAND2);
    g_array_append_val(roxterm->match_map, map);
    return map.tag;
}

static void roxterm_match_remove(ROXTermData *roxterm, VteTerminal *vte,
        int tag)
{
    guint n;

    vte_terminal_match_remove(vte, tag);
    for (n = 0; n < roxterm->match_map->len; ++n)
    {
        if (g_array_index(roxterm->match_map, ROXTerm_MatchMap, n).tag == tag)
        {
            g_array_remove_index_fast(roxterm->match_map, n);
            break;
        }
    }
}

#define URLSTART "\\b"

#define URLMSGIDSET "[-A-Z\\^_a-z{|}~!\"#$%&'()*+,./0-9;:=?`]"
#define URLEND "[^].}<> \t\r\n,\\\"]"
#define URLPARTHOST "[A-Za-z0-9]+(-[A-Za-z0-9]+)*"
#define URLFQDN URLPARTHOST "(\\." URLPARTHOST ")+"
#define URLHOST URLPARTHOST "(\\." URLPARTHOST ")*"
#define URLUSERCHARS "-A-Za-z0-9!#$%&'*+/=?^_`{|}~"
#define URLUSER "[" URLUSERCHARS "]+(\\.[" URLUSERCHARS "]+)*"
#define URLEXTHOST "(" URLUSER "(:[^@]+)?@)?" URLHOST "(:[0-9]+)?"
#define URLFQDNTAIL "\\." URLHOST
#define URLSCHEME "(telnet:|nntp:|https?:|ftps?:|webcal:)"
#define URLPATHCHARS "-A-Za-z0-9_$.+!(),;@&=?/~#%"
#define URLPATH "[/?][" URLPATHCHARS ":'*]*" URLEND
#define URLNEWS URLSTART "news:" URLMSGIDSET "+@" URLHOST

static const char *full_urls[] = {
    URLSTART URLSCHEME "//" URLEXTHOST,
    URLSTART URLSCHEME "//" URLEXTHOST URLPATH
};

static const char *web_urls[] = {
    URLSTART "www[0-9-]*" URLFQDNTAIL,
    URLSTART "www[0-9-]*" URLFQDNTAIL URLPATH
};

static const char *ftp_urls[] = {
    URLSTART "ftp[0-9-]*" URLFQDNTAIL,
    URLSTART "ftp[0-9-]*" URLFQDNTAIL URLPATH
};

static const char *ssh_urls[] = {
    URLSTART "ssh://" URLEXTHOST,
    URLSTART "ssh[0-9-]*" URLFQDNTAIL,
    URLSTART URLHOST "\\.l(ocal|an)"
};

static const char *misc_urls[] = {
    URLSTART "((git\\+)?ssh|svn|bzr|hg)://" URLEXTHOST URLPATH,
};

static const char *mailto_urls[] = {
    URLSTART URLUSER "@" URLHOST,
    URLSTART "mailto:" URLUSER "@" URLHOST "\\?[A-Za-z]+=" URLMSGIDSET "+"
};

/* FIXME: Could support ~user but glib doesn't provide a convenience func.
 * Should also be able to support spaces if escaped or in quotes, but
 * would complicate expression(s).
 */
#define URLFILEBODY "(\\.|\\.\\.|~)?/[" URLPATHCHARS "]*"
#define URLFILE "\\bfile://" URLFILEBODY

static const char *file_urls[] = {
    "\\s" URLFILEBODY,
    "^" URLFILEBODY
};

static void roxterm_add_matches(ROXTermData *roxterm, VteTerminal *vte)
{
    guint n;

    for (n = 0; n < G_N_ELEMENTS(full_urls); ++n)
        roxterm_match_add(roxterm, vte, full_urls[n], ROXTerm_Match_Complete);
    for (n = 0; n < G_N_ELEMENTS(web_urls); ++n)
        roxterm_match_add(roxterm, vte, web_urls[n], ROXTerm_Match_WWW);
    for (n = 0; n < G_N_ELEMENTS(ftp_urls); ++n)
        roxterm_match_add(roxterm, vte, ftp_urls[n], ROXTerm_Match_FTP);
    for (n = 0; n < G_N_ELEMENTS(mailto_urls); ++n)
        roxterm_match_add(roxterm, vte, mailto_urls[n], ROXTerm_Match_MailTo);
    for (n = 0; n < G_N_ELEMENTS(ssh_urls); ++n)
        roxterm_match_add(roxterm, vte, ssh_urls[n], ROXTerm_Match_SSH_Host);
    for (n = 0; n < G_N_ELEMENTS(misc_urls); ++n)
        roxterm_match_add(roxterm, vte, misc_urls[n], ROXTerm_Match_Misc_URI);
    roxterm_match_add(roxterm, vte, URLFILE, ROXTerm_Match_File);
    roxterm_match_add(roxterm, vte, URLNEWS, ROXTerm_Match_Complete);
}

static void roxterm_add_file_matches(ROXTermData *roxterm, VteTerminal *vte)
{
    int n;

    for (n = 0; n < 2; ++n)
    {
        roxterm->file_match_tag[n] =
            roxterm_match_add(roxterm, vte, file_urls[n], ROXTerm_Match_File);
    }
}

static void roxterm_remove_file_matches(ROXTermData *roxterm, VteTerminal *vte)
{
    int n;

    for (n = 0; n < 2; ++n)
    {
        roxterm_match_remove(roxterm, vte, roxterm->file_match_tag[n]);
        roxterm->file_match_tag[n] = -1;
    }
}

static ROXTerm_MatchType roxterm_get_match_type(ROXTermData *roxterm, int tag)
{
    guint n;

    for (n = 0; n < roxterm->match_map->len; ++n)
    {
        ROXTerm_MatchMap *m = &g_array_index(roxterm->match_map,
                ROXTerm_MatchMap, n);

        if (m->tag == tag)
            return m->type;
    }
    return ROXTerm_Match_Invalid;
}

/********************* End URI handling *********************/

inline static DynamicOptions *roxterm_get_profiles(void)
{
    if (!roxterm_profiles)
        roxterm_profiles = dynamic_options_get("Profiles");
    return roxterm_profiles;
}

/* Foreground (and palette?) colour change doesn't cause a redraw so force it */
inline static void roxterm_force_redraw(ROXTermData *roxterm)
{
    if (roxterm->widget)
        gtk_widget_queue_draw(roxterm->widget);
}

char *roxterm_get_cwd(ROXTermData *roxterm)
{
    char *pidfile = NULL;
    char *target = NULL;
    GError *error = NULL;

    if (roxterm->pid < 0)
        return NULL;

    pidfile = g_strdup_printf("/proc/%d/cwd", roxterm->pid);
    target = g_file_read_link(pidfile, &error);
    /* According to a comment in gnome-terminal readlink()
     * returns a NULL/empty string in Solaris but we can still use the
     * /proc link if it exists */
    if (!target || !target[0])
    {
        g_free(target);
        target = NULL;
        if (!error && g_file_test(pidfile, G_FILE_TEST_EXISTS))
            target = pidfile;
        else
            g_free(pidfile);
    }
    else
    {
        g_free(pidfile);
    }
    if (error)
        g_error_free(error);
    return target;
}

static char **roxterm_strv_copy(char **src)
{
    int n;
    char **dest;

    for (n = 0; src[n]; ++n);
    dest = g_new(char *, n + 1);
    for (n = 0; src[n]; ++n)
        dest[n] = g_strdup(src[n]);
    dest[n] = 0;
    return dest;
}

static ROXTermData *roxterm_data_clone(ROXTermData *old_gt)
{
    ROXTermData *new_gt = g_new(ROXTermData, 1);

    *new_gt = *old_gt;
    new_gt->status_icon_name = NULL;
    new_gt->widget = NULL;
    new_gt->tab = NULL;
    new_gt->running = FALSE;
    new_gt->postponed_free = FALSE;
    new_gt->dont_lookup_dimensions = FALSE;
    new_gt->actual_commandv = NULL;
    new_gt->env = roxterm_strv_copy(old_gt->env);

    if (old_gt->colour_scheme)
    {
        new_gt->colour_scheme = colour_scheme_lookup_and_ref
            (options_get_leafname(old_gt->colour_scheme));
    }
    if (old_gt->profile)
    {
        new_gt->profile = dynamic_options_lookup_and_ref(roxterm_profiles,
            options_get_leafname(old_gt->profile), "roxterm profile");
    }
    /* special_command should be transferred to new data and removed from old
     * one */
    old_gt->special_command = NULL;

    /* Only inherit commandv from templates, not from running terminals */
    if (!old_gt->running && old_gt->commandv)
    {
        new_gt->commandv = global_options_copy_strv(old_gt->commandv);
    }
    else
    {
        new_gt->commandv = NULL;
    }
    new_gt->directory = roxterm_get_cwd(old_gt);
    if (!new_gt->directory && old_gt->directory)
    {
        new_gt->directory = g_strdup(old_gt->directory);
    }

    if (old_gt->encoding)
        new_gt->encoding = g_strdup(old_gt->encoding);
    if (old_gt->display_name)
        new_gt->display_name = g_strdup(old_gt->display_name);

    new_gt->match_map = g_array_new(FALSE, FALSE, sizeof(ROXTerm_MatchMap));
    new_gt->matched_url = NULL;

    if (old_gt->pango_desc)
    {
        new_gt->pango_desc = pango_font_description_copy(old_gt->pango_desc);
    }
    if (old_gt->widget && gtk_widget_get_realized(old_gt->widget))
    {
        VteTerminal *vte = VTE_TERMINAL(old_gt->widget);

        new_gt->columns = vte_terminal_get_column_count(vte);
        new_gt->rows = vte_terminal_get_row_count(vte);
    }
    new_gt->file_match_tag[0] = new_gt->file_match_tag[1] = -1;

    new_gt->style_context = NULL;

    return new_gt;
}

/* A new env is allocated and returned; the old one is not freed. replace
 * is a flat array of NAME, value, ..., NULL. Variables in replace replace
 * existing ones in env with the same name or are appended.
 */
static char **roxterm_modify_environment(char const * const *env,
        char const * const *replace)
{
    int env_len, replace_len;
    int i, j, k;
    char **equals;
    char **result;

    for (env_len = 0; env[env_len]; ++env_len);
    for (replace_len = 0; replace[replace_len]; ++replace_len);
    replace_len /= 2;
    equals = g_new(char *, replace_len + 1);
    equals[replace_len] = NULL;
    for (i = 0; i < replace_len; ++i)
    {
        equals[i] = g_strconcat(replace[i * 2], "=", NULL);
    }
    result = g_new(char *, replace_len + env_len + 1);
    k = 0;
    for (j = 0; j < env_len; ++j)
    {
        gboolean skip = FALSE;

        for (i = 0; i < replace_len; ++i)
        {
            if (g_str_has_prefix(env[j], equals[i]))
            {
                skip = TRUE;
                break;
            }
        }
        if (!skip)
        {
            result[k++] = g_strdup(env[j]);
        }
    }
    g_strfreev(equals);
    for (j = 0; j < replace_len; ++j)
    {
        result[k++] = g_strconcat(replace[j * 2], "=", replace[j * 2 + 1],
                NULL);
    }
    result[k] = NULL;
    return result;
}

static char **roxterm_get_environment(ROXTermData *roxterm, const char *term)
{
    /* We still set TERM even though vte is supposed to override it because
     * earlier versions added an additional TERM instead of replacing it, and
     * the one we add should take priority.
     */
    char **result;
    char const *new_env[] = {
        "WINDOWID", NULL,
        "ROXTERM_ID", NULL,
        "ROXTERM_NUM", NULL,
        "ROXTERM_PID", NULL,
        NULL, NULL,
        NULL, NULL,
        NULL, NULL,
        NULL };
    enum {
        EnvWindowId,
        EnvRoxtermId,
        EnvRoxtermNum,
        EnvRoxtermPid
    };
    GList *link;
    int n;
    const char *cterm;

    /* Despite called when idle after showing, widget's GdkWindow can still
     * be NULL at this point (for hidden tabs?) so need to check it's realized.
     */
    if (!gtk_widget_get_realized(roxterm->widget))
        gtk_widget_realize(roxterm->widget);
    new_env[EnvWindowId * 2 + 1] = g_strdup_printf("%ld",
            GDK_WINDOW_XID(gtk_widget_get_window(roxterm->widget)));
    new_env[EnvRoxtermId * 2 + 1] = g_strdup_printf("%p", roxterm);
    for (n = 0, link = roxterm_terms; link; ++n, link = g_list_next(link))
    {
        ++n;
        if (link->data == roxterm)
            break;
    }
    new_env[EnvRoxtermNum * 2 + 1] = g_strdup_printf("%d", n);
    new_env[EnvRoxtermPid * 2 + 1] = g_strdup_printf("%d", (int) getpid());
    n = EnvRoxtermPid + 1;
    if (roxterm->display_name)
    {
        new_env[n * 2] = "DISPLAY";
        new_env[n * 2 + 1] = roxterm->display_name;
        ++n;
    }
    if (term)
    {
        new_env[n * 2] = "TERM";
        new_env[n * 2 + 1] = term;
        ++n;
    }
    cterm = options_lookup_string(roxterm->profile, "color_term");
    if (cterm)
    {
        new_env[n * 2] = "COLORTERM";
        new_env[n * 2 + 1] = cterm;
        ++n;
    }
    result = roxterm_modify_environment((char const * const *) roxterm->env,
            new_env);
    g_free((char *) new_env[EnvWindowId * 2 + 1]);
    g_free((char *) new_env[EnvRoxtermId * 2 + 1]);
    g_free((char *) new_env[EnvRoxtermNum * 2 + 1]);
    g_free((char *) new_env[EnvRoxtermPid * 2 + 1]);
    return result;
}

static GtkWindow *roxterm_get_toplevel(ROXTermData *roxterm)
{
    MultiWin *w;

    if (roxterm && (w = roxterm_get_win(roxterm)) != NULL)
    {
        GtkWidget *tl = multi_win_get_widget(w);
        if (tl && gtk_widget_is_toplevel(tl))
            return GTK_WINDOW(tl);
    }
    return NULL;
}

static char *get_default_command(ROXTermData *roxterm)
{
    char *command = NULL;
    struct passwd *pinfo = getpwuid(getuid());

    if (pinfo)
    {
        command = g_strdup(pinfo->pw_shell);
    }
    else
    {
        dlg_warning(roxterm_get_toplevel(roxterm),
                _("Unable to look up login details: %s"), strerror(errno));
    }
    if (!command || !command[0])
        command = g_strdup("/bin/sh");
    return command;
}

static void roxterm_show_status(ROXTermData *roxterm, const char *name)
{
    if (name && !strcmp(name, "dialog-information") &&
            roxterm->status_icon_name &&
            (!strcmp(roxterm->status_icon_name, "dialog-warning") ||
            !strcmp(roxterm->status_icon_name, "dialog_error")))

    {
        return;
    }
    roxterm->status_icon_name = name;
    if (roxterm->tab && options_lookup_int_with_default(roxterm->profile,
            "show_tab_status", FALSE))
    {
        multi_tab_set_status_icon_name(roxterm->tab, name);
    }
}

static gboolean roxterm_command_failed(ROXTermData *roxterm)
{
    MultiWin *win = roxterm_get_win(roxterm);
    GtkWidget *w = win ? multi_win_get_widget(win) : NULL;

    dlg_critical(w ? GTK_WINDOW(w) : NULL, "%s", roxterm->reply);
    g_free(roxterm->reply);
    multi_tab_delete(roxterm->tab);
    return FALSE;
}

/* Returns NULL if cwd is apparently inaccessible */
static const char *check_cwd(const char *cwd)
{
    return (cwd && g_file_test(cwd, G_FILE_TEST_IS_DIR) &&
            g_file_test(cwd, G_FILE_TEST_IS_EXECUTABLE)) ?
            cwd : NULL;
}

/* Returns home dir if cwd is inaccessible. If home is also inaccessible
 * returns NULL. */
static const char *roxterm_check_cwd(const char *cwd)
{
    return check_cwd(cwd) ? cwd : check_cwd(g_get_home_dir());
}

static char *roxterm_fork_command(VteTerminal *vte,
        char **argv, char **envv,
        const char *working_directory,
        gboolean login, gboolean utmp, gboolean wtmp, pid_t *pid)
{
    GPid *ppid = (GPid *) pid;
    GError *error = NULL;
    VtePty *pty;
    char *filename = argv[0];
    char **new_argv = NULL;
    char *reply = NULL;

    working_directory = roxterm_check_cwd(working_directory);
    if (login)
    {
        /* If login_shell, make sure argv[0] is the
         * shell base name (== leaf name) prepended by "-" */
        guint old_len;
        char *leaf = strrchr(filename, G_DIR_SEPARATOR);

        if (leaf)
            ++leaf;
        else
            leaf = argv[0];

        /* The NULL terminator is not considered by g_strv_length() */
        old_len = g_strv_length(argv);
        new_argv = g_new(char *, old_len + 2);

        new_argv[0] = filename;
        new_argv[1] = g_strconcat("-", leaf, NULL);
        memcpy(new_argv + 2, argv + 1, sizeof(char *) * old_len);
        argv = new_argv;
    }

    pty = vte_terminal_pty_new_sync(vte,
            (login ? 0 : VTE_PTY_NO_LASTLOG) |
            (utmp ? 0 : VTE_PTY_NO_UTMP) |
            (wtmp ? 0 : VTE_PTY_NO_WTMP),
            NULL, &error);
    if (pty)
    {
        vte_terminal_set_pty(vte, pty);
        if (g_spawn_async(working_directory, argv, envv,
                G_SPAWN_DO_NOT_REAP_CHILD |
                    (login ? G_SPAWN_FILE_AND_ARGV_ZERO : G_SPAWN_SEARCH_PATH),
                (GSpawnChildSetupFunc) vte_pty_child_setup, pty,
                ppid, &error))
        {
            vte_terminal_watch_child(vte, *ppid);
        }
        else
        {
            reply = g_strdup_printf(
                    _("The new terminal's command failed to run: %s"),
                    error->message);
        }
        /* The VteTerminal and we both own a reference, so drop our ref to
         * ensure pty is destroyed with terminal.
         */
        g_object_unref(pty);
    }
    else
    {
        reply = g_strdup_printf(_("Failed to create pty: %s"), error->message);
    }
    if (error)
        g_error_free(error);

    if (new_argv)
    {
        g_free(new_argv[1]);
        g_free(new_argv);
    }

    if (reply)
        *pid = -1;
    return reply;
}

static void roxterm_run_command(ROXTermData *roxterm, VteTerminal *vte)
{
    char **commandv = NULL;
    char *command = NULL;
    gboolean special = FALSE; /* special_command and -e override login_shell */
    gboolean login = FALSE;
    const char *term = options_lookup_string(roxterm->profile, "term");
    char **env;
    char *reply = NULL;

    if (!term)
        term = "xterm";
    env = roxterm_get_environment(roxterm, term);
    roxterm->running = TRUE;
    roxterm_show_status(roxterm, "window-close");
    if (roxterm->special_command)
    {
        /* Use special_command, currently single string */
        command = roxterm->special_command;
        special = TRUE;
    }
    else if (roxterm->commandv)
    {
        /* Either restoring a session or -e was given */
        if (!roxterm->from_session)
        {
            special = TRUE;
            /* If commandv is a single string it might be a command + args
             * separated by spaces, so parse it. See
             * <http://sourceforge.net/p/roxterm/bugs/87/>.
             */
            if (!roxterm->commandv[1])
            {
                command = roxterm->commandv[0];
                roxterm->commandv[0] = NULL;
                g_strfreev(roxterm->commandv);
                roxterm->commandv = NULL;
            }
        }
    }
    else
    {
        /* Either use custom command from option or default (as single string)
         */
        if (options_lookup_int_with_default(roxterm->profile,
                    "use_ssh", FALSE))
        {
            const char *host = options_lookup_string_with_default(
                    roxterm->profile, "ssh_address", "localhost");
            const char *user = options_lookup_string(roxterm->profile,
                    "ssh_user");
            const char *ssh_opts = options_lookup_string(roxterm->profile,
                    "ssh_options");
            int port = options_lookup_int_with_default(roxterm->profile,
                    "ssh_port", 22);

            command = g_strdup_printf("ssh%s%s %s -p %d %s",
                    (user && user[0]) ? " -l " : "",
                    (user && user[0]) ? user : "",
                    (ssh_opts && ssh_opts[0]) ? ssh_opts : "",
                    port, host);
        }
        else if (options_lookup_int_with_default(roxterm->profile,
                    "use_custom_command", FALSE))
        {
            command = options_lookup_string(roxterm->profile, "command");
        }
        if (!command || !command[0])
        {
            command = get_default_command(roxterm);
        }
        else
        {
            /* Using custom command disables login_shell */
            special = TRUE;
        }
    }
    if (roxterm->commandv && !roxterm->special_command)
    {
        /* We're using roxterm->commandv, point commandv to it */
        commandv = roxterm->commandv;
    }
    else
    {
        /* Parse our own commandv from single string command */
        int argc;
        GError *error = NULL;

        commandv = NULL;
        if (!g_shell_parse_argv(command, &argc, &commandv, &error))
        {
            reply = g_strdup_printf(_("Unable to parse command %s: %s"),
                    command, error ? error->message : _("unknown reason"));
            g_error_free(error);
            if (commandv)
                g_strfreev(commandv);
            commandv = g_new(char *, 2);
            commandv[0] = get_default_command(roxterm);
            commandv[1] = NULL;
        }
    }
    if (!special && options_lookup_int_with_default(roxterm->profile,
            "login_shell", 0))
    {
        login = TRUE;
    }

    if (commandv && commandv[0])
    {
        gboolean xtmplog = options_lookup_int_with_default(roxterm->profile,
                "update_records", 1);

        reply = roxterm_fork_command(vte, commandv, env,
                roxterm->directory, login, xtmplog, xtmplog, &roxterm->pid);
    }
    else
    {
        reply = g_strdup(_("Trying to run NULL command in new terminal"));
    }

    roxterm->special_command = NULL;
    /* If special_command was set, command points to the same string */
    g_free(command);
    roxterm->actual_commandv = commandv;
    g_strfreev(env);
    if (reply && strcmp(reply, "OK"))
    {
        roxterm->reply = reply;
        g_idle_add((GSourceFunc) roxterm_command_failed, roxterm);
    }
    else
    {
        g_free(reply);
    }
}

static char *roxterm_lookup_uri_handler(ROXTermData *roxterm, const char *tag)
{
    char *filename = options_lookup_string(roxterm->profile, tag);

    if (g_file_test(filename, G_FILE_TEST_IS_DIR))
    {
        char *apprun = g_build_filename(filename, "AppRun", NULL);

        if (g_file_test(apprun, G_FILE_TEST_IS_EXECUTABLE))
        {
            g_free(filename);
            filename = apprun;
        }
        else
        {
            g_free(apprun);
        }
    }
    return filename;
}

/* Converts quotes to % sequences to fix debian bug #696917 */
char *preparse_url(const char *url)
{
    GString *s = g_string_new("");
    size_t n;
    char c;
    for (n = 0; (c = url[n]) != 0; ++n)
    {
        switch (c)
        {
            case '"':
                s = g_string_append(s, "%22");
                break;
            case '\'':
                s = g_string_append(s, "%27");
                break;
            default:
                s = g_string_append_c(s, c);
                break;
        }
    }
    return g_string_free(s, FALSE);
}

static void roxterm_launch_browser(ROXTermData *roxterm, const char *url)
{
    char *url2 = preparse_url(url);
    char *browser_o = roxterm_lookup_uri_handler(roxterm, "browser");
    char *browse = uri_get_browser_command(url2, browser_o);

    if (browser_o)
        g_free(browser_o);
    if (browse)
    {
        roxterm_spawn(roxterm, browse, options_lookup_int_with_default
            (roxterm->profile, "browser_spawn_type", 0));
        g_free(browse);
    }
    g_free(url2);
}

static void roxterm_launch_email(ROXTermData *roxterm, const char *uri)
{
    char *mailer_o = roxterm_lookup_uri_handler(roxterm, "mailer");
    char *mailer = uri_get_mailer_command(uri, mailer_o);

    if (mailer_o)
        g_free(mailer_o);
    if (mailer)
    {
        roxterm_spawn(roxterm, mailer, options_lookup_int_with_default
            (roxterm->profile, "mailer_spawn_type", 0));
        g_free(mailer);
    }
}

static void roxterm_launch_filer(ROXTermData *roxterm, const char *uri)
{
    gboolean is_dir;
    char *dir = NULL;
    char *expu = NULL;
    char *filer_o;
    char *filer;

    if (g_str_has_prefix(uri, "file://"))
        uri += 7;
    if (uri[0] == '~')
    {
        expu = g_build_filename(g_get_home_dir(), uri + 2, NULL);
        uri = expu;
    }
    is_dir = g_file_test(uri, G_FILE_TEST_IS_DIR);
    if (!is_dir && options_lookup_int_with_default(roxterm->profile,
                "file_as_dir", TRUE))
    {
        dir = g_path_get_dirname(uri);
        uri = dir;
        is_dir = TRUE;
    }
    if (is_dir)
    {
        filer_o = roxterm_lookup_uri_handler(roxterm, "dir_filer");
        filer = uri_get_directory_command(uri, filer_o);
    }
    else
    {
        filer_o = roxterm_lookup_uri_handler(roxterm, "filer");
        filer = uri_get_file_command(uri, filer_o);
    }
    if (filer_o)
        g_free(filer_o);
    if (filer)
    {
        roxterm_spawn(roxterm, filer,
            options_lookup_int_with_default(roxterm->profile,
                is_dir ? "dir_spawn_type" : "filer_spawn_type",
                0));
        g_free(filer);
    }
    g_free(dir);
    g_free(expu);
}

static void roxterm_launch_ssh(ROXTermData *roxterm, const char *uri)
{
    char *ssh_o = roxterm_lookup_uri_handler(roxterm, "ssh");
    char *ssh = uri_get_ssh_command(uri, ssh_o);

    if (ssh_o)
        g_free(ssh_o);
    if (ssh)
    {
        roxterm_spawn(roxterm, ssh, options_lookup_int_with_default
            (roxterm->profile, "ssh_spawn_type", ROXTerm_SpawnNewTab));
        g_free(ssh);
    }
}

static void roxterm_launch_uri(ROXTermData *roxterm)
{
    switch (roxterm->match_type)
    {
        case ROXTerm_Match_MailTo:
            roxterm_launch_email(roxterm, roxterm->matched_url);
            break;
        case ROXTerm_Match_File:
            roxterm_launch_filer(roxterm, roxterm->matched_url);
            break;
        case ROXTerm_Match_SSH_Host:
            roxterm_launch_ssh(roxterm, roxterm->matched_url);
            break;
        default:
            roxterm_launch_browser(roxterm, roxterm->matched_url);
            break;
    }
}

static void roxterm_data_delete(ROXTermData *roxterm)
{
    /* This doesn't delete widgets because they're deleted when removed from
     * the parent */
    GtkWindow *gwin;

    g_return_if_fail(roxterm);

    gwin = roxterm_get_toplevel(roxterm);
    if (gwin && roxterm->win_state_changed_tag)
    {
        g_signal_handler_disconnect(gwin, roxterm->win_state_changed_tag);
    }
    if (roxterm->post_exit_tag)
        g_source_remove(roxterm->post_exit_tag);
    if (roxterm->colour_scheme)
    {
        UNREF_LOG(colour_scheme_unref(roxterm->colour_scheme));
    }
    if (roxterm->profile)
    {
        UNREF_LOG(dynamic_options_unref(roxterm_profiles,
                options_get_leafname(roxterm->profile)));
    }
    drag_receive_data_delete(roxterm->drd);
    if (roxterm->actual_commandv != roxterm->commandv)
        g_strfreev(roxterm->actual_commandv);
    if (roxterm->commandv)
        g_strfreev(roxterm->commandv);
    g_free(roxterm->directory);
    g_free(roxterm->encoding);
    g_free(roxterm->display_name);
    g_strfreev(roxterm->env);
    if (roxterm->pango_desc)
        pango_font_description_free(roxterm->pango_desc);
    if (roxterm->replace_task_dialog)
    {
        roxterm->postponed_free = TRUE;
        gtk_widget_destroy(roxterm->replace_task_dialog);
    }
    else
    {
        g_free(roxterm);
    }
}

typedef enum {
    ROXTerm_DontShowURIMenuItems,
    ROXTerm_ShowWebURIMenuItems,
    ROXTerm_ShowMailURIMenuItems,
    ROXTerm_ShowFileURIMenuItems,
    ROXTerm_ShowSSHHostMenuItems,
    ROXTerm_ShowMiscURIMenuItems,
} ROXTerm_URIMenuItemsShowType;

static void
set_show_uri_menu_items(MenuTree *tree, ROXTerm_URIMenuItemsShowType show_type)
{
    g_return_if_fail(tree);
    menutree_set_show_item(tree, MENUTREE_OPEN_IN_BROWSER,
        show_type == ROXTerm_ShowWebURIMenuItems);
    menutree_set_show_item(tree, MENUTREE_OPEN_IN_MAILER,
        show_type == ROXTerm_ShowMailURIMenuItems);
    menutree_set_show_item(tree, MENUTREE_OPEN_IN_FILER,
        show_type == ROXTerm_ShowFileURIMenuItems);
    menutree_set_show_item(tree, MENUTREE_COPY_URI,
        show_type != ROXTerm_DontShowURIMenuItems);
    menutree_set_show_item(tree, MENUTREE_SSH_HOST,
        show_type == ROXTerm_ShowSSHHostMenuItems);
    menutree_set_show_item(tree, MENUTREE_URI_SEPARATOR,
        show_type != ROXTerm_DontShowURIMenuItems);
}

static void
roxterm_set_show_uri_menu_items(ROXTermData * roxterm,
    ROXTerm_URIMenuItemsShowType show_type)
{
    MultiWin *win = roxterm_get_win(roxterm);

    set_show_uri_menu_items(multi_win_get_popup_menu(win), show_type);
    set_show_uri_menu_items(multi_win_get_short_popup_menu(win), show_type);
}

static double roxterm_get_config_saturation(ROXTermData *roxterm)
{
    double saturation = options_lookup_double_with_default(roxterm->profile,
            "saturation", 1.0);

    if (saturation < 0 || saturation > 1)
    {
        g_warning(_("Saturation option %f out of range"), saturation);
        saturation = CLAMP(saturation, 0, 1);
    }
    return saturation;
}

static void
roxterm_update_cursor_colour(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_color_cursor(vte,
            colour_scheme_get_cursor_colour(roxterm->colour_scheme, TRUE));
}

static void
roxterm_update_bold_colour(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_color_bold(vte,
            colour_scheme_get_bold_colour(roxterm->colour_scheme, TRUE));
}

guint16 extrapolate_chroma(guint16 bg, guint16 fg, double factor)
{
    gint32 ext = (gint32) bg +
        (gint32) ((double) ((gint32) fg - (gint32) bg) * factor);

    return (guint16) CLAMP(ext, 0, 0xffff);
}

static const GdkRGBA *extrapolate_colours(const GdkRGBA *bg,
        const GdkRGBA *fg, double factor)
{
    static GdkRGBA ext;

#define EXTRAPOLATE(c) ext. c = \
    extrapolate_chroma(bg ? bg->  c : 0, fg ? fg->  c : 0xffff, factor)
    EXTRAPOLATE(red);
    EXTRAPOLATE(green);
    EXTRAPOLATE(blue);
    return &ext;
}

static void roxterm_get_vte_padding(ROXTermData *roxterm,
        int *pad_w, int *pad_h)
{
    if (!roxterm->style_context)
    {
        roxterm->style_context =
            gtk_widget_get_style_context(roxterm->widget);
        if (roxterm->style_context)
        {
            GtkBorder border;
            GtkStateFlags state = gtk_widget_get_state_flags(roxterm->widget);
            gtk_style_context_get_padding(roxterm->style_context,
                    state, &border);
            roxterm->padding_w = border.left + border.right;
            roxterm->padding_h = border.top + border.bottom;
            gtk_style_context_get_border(roxterm->style_context,
                    state, &border);
            roxterm->padding_w += border.left + border.right;
            roxterm->padding_h += border.top + border.bottom;
            gtk_style_context_get_margin(roxterm->style_context,
                    state, &border);
            roxterm->padding_w += border.left + border.right;
            roxterm->padding_h += border.top + border.bottom;
        }
        else
        {
            g_warning("Unable to get VTE widget's style context");
            /* I think default is 1 pixel each side */
            roxterm->padding_w = roxterm->padding_h = 2;
        }
    }
    if (pad_w)
        *pad_w = roxterm->padding_w;
    if (pad_h)
        *pad_h = roxterm->padding_h;
}

static const GdkRGBA *roxterm_get_background_colour_with_transparency(
        ROXTermData * roxterm)
{
    const GdkRGBA *bgro =
            colour_scheme_get_background_colour(roxterm->colour_scheme, TRUE);
    static GdkRGBA background;
    float alpha = roxterm_get_config_saturation(roxterm);
    
    if (bgro)
    {
        background = *bgro;
    }
    else if (alpha < 1.0f)
    {
        bgro = colour_scheme_get_background_colour(roxterm->colour_scheme,
                FALSE);
        background = *bgro;
        bgro = NULL;
    }
    background.alpha = alpha;
    return (alpha < 1.0f) ? &background : bgro;
}

static void
roxterm_apply_colour_scheme(ROXTermData *roxterm, VteTerminal *vte)
{
    gboolean bold = FALSE;
    const GdkRGBA *bd = NULL;
    int ncolours = 0;
    const GdkRGBA *palette = NULL;
    const GdkRGBA *foreground =
            colour_scheme_get_foreground_colour(roxterm->colour_scheme, TRUE);
    const GdkRGBA *background =
            roxterm_get_background_colour_with_transparency(roxterm);
    
    vte_terminal_set_default_colors(vte);
    ncolours = colour_scheme_get_palette_size(roxterm->colour_scheme);
    if (ncolours)
        palette = colour_scheme_get_palette(roxterm->colour_scheme);
    vte_terminal_set_colors(vte, foreground, background,
            palette, ncolours);
    if (!ncolours && foreground && background)
    {
        vte_terminal_set_color_bold(vte,
                extrapolate_colours(background, foreground, 1.2));
        bold = TRUE;
    }
    bd = colour_scheme_get_bold_colour(roxterm->colour_scheme, TRUE);
    if (bd || !bold)
        vte_terminal_set_color_bold(vte, bd);
    roxterm_update_cursor_colour(roxterm, vte);
    roxterm_force_redraw(roxterm);
}

static void
roxterm_set_vte_size(ROXTermData *roxterm, VteTerminal *vte,
        int columns, int rows)
{
    /* First get current size of vte and parent window so we know difference */
    int cw, ch, ww, wh;
    GtkWidget *vw = GTK_WIDGET(vte);
    GdkWindow *drbl = gtk_widget_get_window(vw);
    MultiWin *win = roxterm_get_win(roxterm);
    GtkWidget *pw = win ? multi_win_get_widget(win) : NULL;
    GdkWindow *pd = pw ? gtk_widget_get_window(pw) : NULL;

    if (drbl && pd)
    {
        cw = gdk_window_get_width(drbl);
        ch = gdk_window_get_height(drbl);
        gtk_window_get_size(GTK_WINDOW(pw), &ww, &wh);
    }
    vte_terminal_set_size(vte, columns, rows);
    /* If we're in a realized window, resize it as required, otherwise the
     * vte's size request won't be honoured.
     */
    if (drbl && pd)
    {
        int px, py;
        int req_w, req_h;

        roxterm_get_vte_padding(roxterm, &px, &py);
        req_w = vte_terminal_get_char_width(vte) * columns + px;
        req_h = vte_terminal_get_char_height(vte) * rows + py;
        /*
        g_debug("Child was %dx%d, window bigger by %dx%d; "
                "resizing for child calc %dx%d",
                cw, ch, ww - cw, wh - ch, req_w, req_h);
        */
        gtk_window_resize(GTK_WINDOW(pw), req_w + ww - cw, req_h + wh - ch);
    }
}

static void roxterm_geometry_func(ROXTermData *roxterm,
        GdkGeometry *geom, GdkWindowHints *hints)
{
    VteTerminal *vte = VTE_TERMINAL(roxterm->widget);

    roxterm_get_vte_padding(roxterm, &geom->base_width, &geom->base_height);
    geom->width_inc = vte_terminal_get_char_width(vte);
    geom->height_inc = vte_terminal_get_char_height(vte);
    geom->min_width = geom->base_width + 4 * geom->width_inc;
    geom->min_height = geom->base_height + 4 * geom->height_inc;
    *hints = GDK_HINT_RESIZE_INC | GDK_HINT_BASE_SIZE | GDK_HINT_MIN_SIZE;
}

static void roxterm_size_func(ROXTermData *roxterm, gboolean pixels,
        int *pwidth, int *pheight)
{
    VteTerminal *vte = VTE_TERMINAL(roxterm->widget);

    if (!pixels || *pwidth == -1)
        *pwidth = vte_terminal_get_column_count(vte);
    if (!pixels || *pheight == -1)
        *pheight = vte_terminal_get_row_count(vte);
    if (pixels)
    {
        int px, py;

        roxterm_get_vte_padding(roxterm, &px, &py);
        *pwidth = *pwidth * vte_terminal_get_char_width(vte) + px;
        *pheight = *pheight * vte_terminal_get_char_height(vte) + py;
    }
}

static void roxterm_default_size_func(ROXTermData *roxterm,
        int *pwidth, int *pheight)
{
    *pwidth = options_lookup_int_with_default(roxterm->profile, "width", 80);
    *pheight = options_lookup_int_with_default(roxterm->profile, "height", 24);
}

static void roxterm_update_size(ROXTermData * roxterm, VteTerminal * vte)
{
    if (multi_win_get_current_tab(roxterm_get_win(roxterm)) == roxterm->tab)
    {
        int w, h;

        roxterm_default_size_func(roxterm, &w, &h);
        roxterm_set_vte_size(roxterm, vte, w, h);
    }
}

static void roxterm_update_geometry(ROXTermData * roxterm, VteTerminal * vte)
{
    (void) vte;
    if (multi_win_get_current_tab(roxterm_get_win(roxterm)) == roxterm->tab)
    {
        GdkGeometry geom;
        GdkWindowHints hints;

        roxterm_geometry_func(roxterm, &geom, &hints);
        multi_win_set_geometry_hints(roxterm_get_win(roxterm), roxterm->widget,
            &geom, hints);
    }
}

static void resize_pango_for_zoom(PangoFontDescription *pango_desc,
        double zf)
{
    int size;
    gboolean abs = FALSE;

    if (zf == 1.0)
        return;
    size = pango_font_description_get_size(pango_desc);
    if (!size)
        size = 10 * PANGO_SCALE;
    else
        abs = pango_font_description_get_size_is_absolute(pango_desc);
    size = (int) ((double) size * zf);
    if (abs)
        pango_font_description_set_absolute_size(pango_desc, size);
    else
        pango_font_description_set_size(pango_desc, size);
}

static void
roxterm_apply_profile_font(ROXTermData *roxterm, VteTerminal *vte,
    gboolean update_geometry)
{
    char *fdesc;
    PangoFontDescription *pango_desc = NULL;
    int w = vte_terminal_get_column_count(vte);
    int h = vte_terminal_get_row_count(vte);

    if (roxterm->pango_desc)
    {
        pango_font_description_free(roxterm->pango_desc);
        roxterm->pango_desc = NULL;
    }
    fdesc = options_lookup_string(roxterm->profile, "font");
    double zf = roxterm->target_zoom_factor;

    if (fdesc && fdesc[0])
    {
        pango_desc = pango_font_description_from_string(fdesc);
        if (!pango_desc)
            g_warning(_("Couldn't create a font from '%s'"), fdesc);
    }
    if (!pango_desc)
    {
        if (zf == roxterm->current_zoom_factor)
        {
            if (fdesc)
                g_free(fdesc);
            return;
        }
        pango_desc = pango_font_description_copy(
                vte_terminal_get_font(vte));
        zf /= roxterm->current_zoom_factor;
    }
    if (!pango_desc)
    {
        g_free(fdesc);
        g_return_if_fail(pango_desc != NULL);
    }
    resize_pango_for_zoom(pango_desc, zf);
    vte_terminal_set_font(vte, pango_desc);
    roxterm->pango_desc = pango_desc;
    g_free(fdesc);
    roxterm->current_zoom_factor = roxterm->target_zoom_factor;
    if (update_geometry)
    {
        roxterm_set_vte_size(roxterm, vte, w, h);
        roxterm_update_geometry(roxterm, vte);
    }
}

static void
roxterm_update_font(ROXTermData *roxterm, VteTerminal *vte,
    gboolean update_geometry)
{
    PangoFontDescription *pango_desc = NULL;
    double zf;
    int w, h;

    if (!roxterm->pango_desc)
    {
        roxterm_apply_profile_font(roxterm, vte, update_geometry);
        return;
    }
    w = vte_terminal_get_column_count(vte);
    h = vte_terminal_get_row_count(vte);
    zf = roxterm->target_zoom_factor /
            (roxterm->current_zoom_factor ?
                    roxterm->current_zoom_factor : 1);
    pango_desc = roxterm->pango_desc;
    resize_pango_for_zoom(pango_desc, zf);
    vte_terminal_set_font(vte, pango_desc);
    roxterm->current_zoom_factor = roxterm->target_zoom_factor;
    if (update_geometry)
    {
        roxterm_set_vte_size(roxterm, vte, w, h);
        roxterm_update_geometry(roxterm, vte);
    }
}

static void roxterm_set_zoom_factor(ROXTermData *roxterm, double factor,
        int index)
{
    roxterm->zoom_index = index;
    roxterm->target_zoom_factor = factor;
    roxterm_update_font(roxterm, VTE_TERMINAL(roxterm->widget), TRUE);
}

/* Change a terminal's font etc so it matches size with another */
static void roxterm_match_text_size(ROXTermData *roxterm, ROXTermData *other)
{
    VteTerminal *vte;
    VteTerminal *other_vte;
    const PangoFontDescription *fd;
    int width, height;

    if (roxterm == other)
        return;
    vte = VTE_TERMINAL(roxterm->widget);
    other_vte = VTE_TERMINAL(other->widget);
    width = vte_terminal_get_column_count(other_vte);
    height = vte_terminal_get_row_count(other_vte);
    fd = vte_terminal_get_font(VTE_TERMINAL(other->widget));
    roxterm->target_zoom_factor = other->current_zoom_factor;
    roxterm->current_zoom_factor = other->current_zoom_factor;
    roxterm->zoom_index = other->zoom_index;
    if (!pango_font_description_equal(
            vte_terminal_get_font(VTE_TERMINAL(roxterm->widget)), fd))
    {
        vte_terminal_set_font(vte, fd);
    }
    if (vte_terminal_get_column_count(vte) != width ||
            vte_terminal_get_row_count(vte) != height)
    {
        roxterm_set_vte_size(roxterm, VTE_TERMINAL(roxterm->widget),
                width, height);
    }
    roxterm_update_geometry(roxterm, vte);
}

static gboolean roxterm_about_uri_hook(GtkAboutDialog *about,
        gchar *link, gpointer data)
{
    (void) about;
    if (g_str_has_prefix(link, "mailto:"))
    {
        roxterm_launch_email(data, link + 7);
    }
    else
    {
        roxterm_launch_browser(data, link);
    }
    return TRUE;
}

static gboolean roxterm_popup_handler(GtkWidget * widget, ROXTermData * roxterm)
{
    (void) widget;
    roxterm_set_show_uri_menu_items(roxterm, ROXTerm_DontShowURIMenuItems);
    multi_tab_popup_menu(roxterm->tab, 0, gtk_get_current_event_time());
    return TRUE;
}

/* Checks whether a button event position is over a matched expression; if so
 * roxterm->matched_url is set and the result is TRUE */
static gboolean roxterm_check_match(ROXTermData *roxterm, VteTerminal *vte,
        int event_x, int event_y)
{
    int xpad, ypad;
    int tag;

    roxterm_get_vte_padding(roxterm, &xpad, &ypad);
    g_free(roxterm->matched_url);
    roxterm->matched_url = vte_terminal_match_check(vte,
        (event_x - ypad) / vte_terminal_get_char_width(vte),
        (event_y - ypad) / vte_terminal_get_char_height(vte), &tag);
    if (roxterm->matched_url)
        roxterm->match_type = roxterm_get_match_type(roxterm, tag);
    return roxterm->matched_url != NULL;
}

static void roxterm_clear_hold_over_uri(ROXTermData *roxterm)
{
    if (roxterm->hold_over_uri)
    {
        g_signal_handler_disconnect(roxterm->widget, roxterm->hold_handler_id);
        roxterm->hold_over_uri = FALSE;
    }
}

inline static void roxterm_clear_drag_url(ROXTermData *roxterm)
{
    roxterm_clear_hold_over_uri(roxterm);
}

static GtkTargetList *roxterm_get_uri_drag_target_list(void)
{
    static const GtkTargetEntry target_table[] = {
        { (char *) "text/uri-list", 0, ROXTERM_DRAG_TARGET_URI_LIST },
        { (char *) "UTF8_STRING", 0, ROXTERM_DRAG_TARGET_UTF8_STRING }
    };

    return gtk_target_list_new(target_table, G_N_ELEMENTS(target_table));
}

static void roxterm_uri_drag_ended(GtkWidget *widget,
        GdkDragContext *drag_context, ROXTermData *roxterm)
{
    (void) widget;
    (void) drag_context;
    roxterm_clear_drag_url(roxterm);
}

static void roxterm_uri_drag_data_get(GtkWidget *widget,
        GdkDragContext *drag_context, GtkSelectionData *selection,
        guint info, guint time, ROXTermData *roxterm)
{
    const char *encoding;
    char *uri_list[2];
    char *utf8 = NULL;
    (void) widget;
    (void) drag_context;
    (void) time;

    g_return_if_fail(roxterm->matched_url);

    encoding = vte_terminal_get_encoding(VTE_TERMINAL(roxterm->widget));
    if (g_ascii_strcasecmp(encoding, "utf-8"))
    {
        GError *err = NULL;

        utf8 = g_convert(roxterm->matched_url, -1, "UTF-8", encoding,
                NULL, NULL, &err);
        if (err)
        {
            dlg_warning(roxterm_get_toplevel(roxterm),
                    _("Unable to convert dragged URI to UTF-8: %s"),
                    err->message);
            g_free(utf8);
            utf8 = NULL;
        }
    }
    if (!utf8)
        utf8 = g_strdup(roxterm->matched_url);

    if (info == ROXTERM_DRAG_TARGET_URI_LIST)
    {
        if (roxterm->match_type == ROXTerm_Match_WWW)
        {
            uri_list[0] = g_strdup_printf("http://%s", utf8);
        }
        else if (roxterm->match_type == ROXTerm_Match_FTP)
        {
            uri_list[0] = g_strdup_printf("ftp://%s", utf8);
        }
        else if (roxterm->match_type == ROXTerm_Match_MailTo
                && strncmp(utf8, "mailto:", 7))
        {
            uri_list[0] = g_strdup_printf("mailto:%s", utf8);
        }
        else if (roxterm->match_type == ROXTerm_Match_SSH_Host)
        {
            if (g_str_has_prefix(utf8, "ssh://"))
                uri_list[0] = g_strdup(utf8);
            else
                uri_list[0] = g_strdup_printf("ssh://%s", utf8);
        }
        else
        {
            uri_list[0] = g_strdup(utf8);
        }
        uri_list[1] = NULL;
        gtk_selection_data_set_uris(selection, uri_list);
        g_free(uri_list[0]);
    }
    else
    {
        gtk_selection_data_set_text(selection, utf8, -1);
    }
    g_free(utf8);
}

/* Starts drag & drop process if user tries to drag a URL */
static gboolean roxterm_motion_handler(GtkWidget *widget, GdkEventButton *event,
        ROXTermData *roxterm)
{
    GtkTargetList *target_list;

    if (!gtk_drag_check_threshold(widget, roxterm->hold_x,
                roxterm->hold_y, event->x, event->y)
            || !roxterm->hold_over_uri)
    {
        return FALSE;
    }

    roxterm_clear_hold_over_uri(roxterm);
    g_return_val_if_fail(roxterm->matched_url, FALSE);


    target_list = roxterm_get_uri_drag_target_list();
    gtk_drag_begin_with_coordinates(roxterm->widget, target_list,
            GDK_ACTION_COPY, 1, (GdkEvent *) event, event->x, event->y);

    return TRUE;
}

static gboolean roxterm_click_handler(GtkWidget *widget,
        GdkEventButton *event, ROXTermData *roxterm)
{
    VteTerminal *vte = VTE_TERMINAL(roxterm->widget);

    (void) widget;

    roxterm_clear_drag_url(roxterm);
    roxterm_check_match(roxterm, vte, event->x, event->y);
    if (event->button == 3 && !(event->state & (GDK_SHIFT_MASK |
                GDK_CONTROL_MASK | GDK_MOD1_MASK)))
    {
        ROXTerm_URIMenuItemsShowType show_type = ROXTerm_DontShowURIMenuItems;

        if (roxterm->matched_url)
        {
            switch (roxterm->match_type)
            {
                case ROXTerm_Match_SSH_Host:
                    show_type = ROXTerm_ShowSSHHostMenuItems;
                    break;
                case ROXTerm_Match_Misc_URI:
                    show_type = ROXTerm_ShowMiscURIMenuItems;
                    break;
                case ROXTerm_Match_MailTo:
                    show_type = ROXTerm_ShowMailURIMenuItems;
                    break;
                case ROXTerm_Match_File:
                    show_type = ROXTerm_ShowFileURIMenuItems;
                    break;
                default:
                    show_type = ROXTerm_ShowWebURIMenuItems;
                    break;
                }
        }
        roxterm_set_show_uri_menu_items(roxterm, show_type);
        multi_tab_popup_menu(roxterm->tab, event->button, event->time);
        return TRUE;
    }
    else if ((event->state & GDK_CONTROL_MASK) && event->button == 1
             && event->type == GDK_BUTTON_PRESS && roxterm->matched_url)
    {
        roxterm->hold_over_uri = TRUE;
        roxterm->hold_x = event->x;
        roxterm->hold_y = event->y;
        roxterm->hold_handler_id = g_signal_connect(roxterm->widget,
                "motion-notify-event",
                G_CALLBACK(roxterm_motion_handler), roxterm);
        return TRUE;
    }
    return FALSE;
}

static gboolean roxterm_release_handler(GtkWidget *widget,
        GdkEventButton *event, ROXTermData *roxterm)
{
    gboolean result = FALSE;
    (void) widget;

    if ((event->state & GDK_CONTROL_MASK) && event->button == 1
            && roxterm->hold_over_uri && roxterm->matched_url)
    {
        roxterm_launch_uri(roxterm);
        result = TRUE;
    }
    roxterm_clear_hold_over_uri(roxterm);
    return result;
}

static void roxterm_window_title_handler(VteTerminal *vte,
        ROXTermData * roxterm)
{
    const char *t = vte_terminal_get_window_title(vte);

    multi_tab_set_window_title(roxterm->tab, t ? t : _("ROXTerm"));
}

static void roxterm_icon_title_handler(VteTerminal *vte,
        ROXTermData *roxterm)
{
    multi_tab_set_icon_title(roxterm->tab, vte_terminal_get_icon_title(vte));
}

/* data is cast to char const **pname - indirect pointer to name to check for -
 * if a match is found, *pname is set to NULL */
static void check_if_name_matches_property(GtkWidget *widget, gpointer data)
{
    const char *profile_name = g_object_get_data(G_OBJECT(widget),
            PROFILE_NAME_KEY);
    char const **pname = data;
    gboolean check = FALSE;

    g_return_if_fail(profile_name);
    /* pname may have been NULLed by an earlier iteration */
    if (!*pname)
        return;
    if (!strcmp(profile_name, *pname))
    {
        check = TRUE;
        *pname = NULL;
    }
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), check);
}

static void check_preferences_submenu(MenuTree *tree, MenuTreeID id,
        const char *current_name)
{
    GtkMenu *submenu = menutree_submenu_from_id(tree, id);
    const char *name = current_name;

    gtk_container_foreach(GTK_CONTAINER(submenu),
            check_if_name_matches_property, &name);
    if (name)
    {
        /* name hasn't been NULLed, no match, try "ghost" */
        char *ghost_name = g_strdup_printf("(%s)", current_name);

        name = ghost_name;
        gtk_container_foreach(GTK_CONTAINER(submenu),
                check_if_name_matches_property, &name);
        g_free(ghost_name);
        if (name)
        {
            g_critical(_("No menu item matches profile/scheme '%s'"),
                    current_name);
        }
    }
}

static void check_preferences_submenu_pair(ROXTermData *roxterm, MenuTreeID id,
        const char *current_name)
{
    MultiWin *win = roxterm_get_win(roxterm);
    MenuTree *tree = multi_win_get_popup_menu(win);

    multi_win_set_ignore_toggles(win, TRUE);
    if (tree)
    {
        check_preferences_submenu(tree, id, current_name);
    }
    tree = multi_win_get_menu_bar(win);
    if (tree)
    {
        check_preferences_submenu(tree, id, current_name);
    }
    multi_win_set_ignore_toggles(win, FALSE);
}

inline static void roxterm_shade_mtree_search_items(MenuTree *mtree,
        gboolean shade)
{
    if (mtree)
    {
        menutree_shade(mtree, MENUTREE_SEARCH_FIND_NEXT, shade);
        menutree_shade(mtree, MENUTREE_SEARCH_FIND_PREVIOUS, shade);
    }
}

static void roxterm_shade_search_menu_items(ROXTermData *roxterm)
{
    MultiWin *win = roxterm_get_win(roxterm);
    gboolean shade = vte_terminal_search_get_gregex(
            VTE_TERMINAL(roxterm->widget)) == NULL;

    roxterm_shade_mtree_search_items(multi_win_get_menu_bar(win), shade);
    roxterm_shade_mtree_search_items(multi_win_get_popup_menu(win), shade);
}

static void roxterm_tab_selection_handler(ROXTermData * roxterm, MultiTab * tab)
{
    MultiWin *win = roxterm_get_win(roxterm);
    MenuTree *menu_bar = multi_win_get_menu_bar(win);
    MenuTree *popup_menu = multi_win_get_popup_menu(win);
    MenuTree *short_popup = multi_win_get_short_popup_menu(win);
    (void) tab;

    roxterm->status_icon_name = NULL;
    check_preferences_submenu_pair(roxterm,
            MENUTREE_PREFERENCES_SELECT_PROFILE,
            options_get_leafname(roxterm->profile));
    check_preferences_submenu_pair(roxterm,
            MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME,
            options_get_leafname(roxterm->colour_scheme));
    check_preferences_submenu_pair(roxterm,
            MENUTREE_PREFERENCES_SELECT_SHORTCUTS,
            options_get_leafname(multi_win_get_shortcut_scheme(win)));
    roxterm_shade_search_menu_items(roxterm);

    multi_win_set_ignore_toggles(win, TRUE);
    if (!popup_menu->encodings)
    {
        char const **encodings = roxterm_list_encodings();

        menutree_build_encodings_menu(popup_menu,
                encodings, roxterm_encoding_toggled);
        menutree_build_encodings_menu(menu_bar,
                encodings, roxterm_encoding_toggled);
        menutree_build_encodings_menu(short_popup,
                encodings, roxterm_encoding_toggled);
        if (encodings)
            g_free(encodings);
    }
    menutree_select_encoding(popup_menu, roxterm->encoding);
    menutree_select_encoding(menu_bar, roxterm->encoding);
    menutree_select_encoding(short_popup, roxterm->encoding);
    multi_win_set_ignore_toggles(win, FALSE);
}

static gboolean run_child_when_idle(ROXTermData *roxterm)
{
    if (!roxterm->running)
        roxterm_run_command(roxterm, VTE_TERMINAL(roxterm->widget));
    return FALSE;
}

static void roxterm_browser_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    if (roxterm->matched_url)
        roxterm_launch_browser(roxterm, roxterm->matched_url);
}

static void roxterm_mailto_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    if (roxterm->matched_url)
        roxterm_launch_email(roxterm, roxterm->matched_url);
}

static void roxterm_open_in_filer_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    if (roxterm->matched_url)
        roxterm_launch_filer(roxterm, roxterm->matched_url);
}

static void roxterm_copy_url_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    if (roxterm->matched_url)
    {
        gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
            roxterm->matched_url, -1);
        gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
            roxterm->matched_url, -1);
    }
}

static void roxterm_ssh_host_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    if (roxterm->matched_url)
        roxterm_launch_ssh(roxterm, roxterm->matched_url);
}

static void roxterm_select_all_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_select_all(VTE_TERMINAL(roxterm->widget));
}

static void roxterm_copy_clipboard_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_copy_clipboard(VTE_TERMINAL(roxterm->widget));
}

static void roxterm_paste_clipboard_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_paste_clipboard(VTE_TERMINAL(roxterm->widget));
}

static void roxterm_copy_and_paste_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_copy_clipboard(VTE_TERMINAL(roxterm->widget));
    vte_terminal_paste_clipboard(VTE_TERMINAL(roxterm->widget));
}

static void roxterm_reset_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_reset(VTE_TERMINAL(roxterm->widget), TRUE, FALSE);
}

static void roxterm_reset_and_clear_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_reset(VTE_TERMINAL(roxterm->widget), TRUE, TRUE);
}

static void roxterm_respawn_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
    int response;
    GtkWidget *w = NULL;

    if (roxterm->post_exit_tag)
    {
        g_source_remove(roxterm->post_exit_tag);
    }
    if (roxterm->running)
    {
        w = roxterm->replace_task_dialog =
                dlg_ok_cancel(GTK_WINDOW(multi_win_get_widget(win)),
                    _("Kill current task?"),
                    _("The current task is still running; "
                    "to restart it the current instance must be killed. "
                    "Do you want to kill the current task to "
                    "replace it with a new instance?"));
        response = gtk_dialog_run(GTK_DIALOG(w));
    }
    else
    {
        response = GTK_RESPONSE_OK;
    }
    switch (response)
    {
        case GTK_RESPONSE_NONE:
        case GTK_RESPONSE_DELETE_EVENT:
            /* Don't destroy */
            break;
        case GTK_RESPONSE_OK:
            roxterm_run_command(roxterm, VTE_TERMINAL(roxterm->widget));
            /* Fall through */
        default:
            if (w)
                gtk_widget_destroy(w);
    }
    if (roxterm->postponed_free)
        g_free(roxterm);
    else
        roxterm->replace_task_dialog = NULL;
}

static void roxterm_edit_profile_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    optsdbus_send_edit_profile_message(options_get_leafname(roxterm->profile),
            roxterm->display_name);
}

static void roxterm_edit_colour_scheme_action(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    optsdbus_send_edit_colour_scheme_message(
            options_get_leafname(roxterm->colour_scheme),
            roxterm->display_name);
}

static void roxterm_edit_shortcuts_scheme_action(MultiWin * win)
{
    shortcuts_edit(GTK_WINDOW(multi_win_get_widget(win)),
            multi_win_get_shortcuts_scheme_name(win));
}


static void roxterm_open_search_action(MultiWin *win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    search_open_dialog(roxterm);
}

static void roxterm_find_next_action(MultiWin *win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_search_find_next(VTE_TERMINAL(roxterm->widget));
}

static void roxterm_find_prev_action(MultiWin *win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    vte_terminal_search_find_previous(VTE_TERMINAL(roxterm->widget));
}

static void roxterm_show_about(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    g_return_if_fail(roxterm);
    /* Presumably the first window argument will force dialog to have the
     * same GdkScreen.
     */
    about_dialog_show(GTK_WINDOW(multi_win_get_widget(win)),
            roxterm_about_uri_hook,
            roxterm);
}

static char *roxterm_get_help_filename(const char *base_dir, const char *lang)
{
    char *filename = g_build_filename(base_dir, lang, "guide.html", NULL);

    if (!g_file_test(filename, G_FILE_TEST_EXISTS))
    {
        g_free(filename);
        filename = NULL;
    }
    return filename;
}

static char *roxterm_get_help_uri(const char *base_dir, const char *lang)
{
    char *short_lang = NULL;
    char *sep;
    char *filename;
    char *uri;

    filename = roxterm_get_help_filename(base_dir, lang);
    if (!filename)
    {
        short_lang = g_strdup(lang);
        sep = strchr(short_lang, '.');
        if (sep)
        {
            *sep = 0;
            filename = roxterm_get_help_filename(base_dir, short_lang);
        }
        if (!filename)
        {
            sep = strchr(short_lang, '_');
            filename = roxterm_get_help_filename(base_dir, short_lang);
        }
        g_free(short_lang);
        if (!filename)
        {
            filename = roxterm_get_help_filename(base_dir, "en");
        }
    }
    uri = g_strconcat("file://", filename, NULL);
    g_free(filename);
    return uri;
}

static void roxterm_show_manual(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);
    char *uri;
    char *dir = NULL;
    const char *lang;

    g_return_if_fail(roxterm);

    if (global_options_appdir)
    {
        dir = g_build_filename(global_options_appdir, "Help", NULL);
    }
    else
    {
        dir = g_build_filename(HTML_DIR, NULL);
    }

    lang = g_getenv("LANG");
    if (!lang || !lang[0])
        lang = "en";
    uri = roxterm_get_help_uri(dir, lang);

    roxterm_launch_browser(roxterm, uri);
    g_free(uri);
    g_free(dir);
}

static void roxterm_open_config_manager(MultiWin * win)
{
    ROXTermData *roxterm = multi_win_get_user_data_for_current_tab(win);

    optsdbus_send_edit_opts_message("Configlet", NULL, roxterm->display_name);
}

static void
roxterm_style_change_handler(GtkWidget * widget, ROXTermData * roxterm)
{
    (void) widget;
    roxterm->style_context = NULL;
    roxterm_update_geometry(roxterm, VTE_TERMINAL(roxterm->widget));
}

static void
roxterm_char_size_changed(GtkSettings * settings, guint arg1, guint arg2,
    ROXTermData * roxterm)
{
    (void) settings;
    (void) arg1;
    (void) arg2;
    roxterm_update_geometry(roxterm, VTE_TERMINAL(roxterm->widget));
}

typedef enum {
    roxterm_ChildExitClose,
    roxterm_ChildExitHold,
    roxterm_ChildExitRespawn,
    roxterm_ChildExitAsk
} roxterm_ChildExitAction;

static void roxterm_hide_menutree(GtkMenuItem *item, gpointer handle)
{
    GtkWidget *submenu = gtk_menu_item_get_submenu(item);
    (void) handle;

    if (submenu)
    {
        gtk_widget_hide(submenu);
        gtk_container_foreach(GTK_CONTAINER(submenu),
                (GtkCallback) roxterm_hide_menutree, NULL);
    }
}

static gboolean roxterm_post_child_exit(ROXTermData *roxterm)
{
    MultiWin *win = roxterm_get_win(roxterm);
    roxterm_ChildExitAction action =
        options_lookup_int_with_default(roxterm->profile, "exit_action",
                roxterm_ChildExitClose);

    if (action == roxterm_ChildExitAsk)
    {
        GtkWidget *dialog = gtk_message_dialog_new(
                roxterm_get_toplevel(roxterm),
                GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                _("The command in this terminal has terminated. "
                "What do you want to do with the terminal now?"));
        GtkWidget *respawn;

        gtk_dialog_add_button(GTK_DIALOG(dialog),
                _("Close"), roxterm_ChildExitClose);
        gtk_dialog_add_button(GTK_DIALOG(dialog),
                _("Leave open"), roxterm_ChildExitHold);
        respawn = gtk_dialog_add_button(GTK_DIALOG(dialog),
                _("Rerun command"), roxterm_ChildExitRespawn);
        if (roxterm->no_respawn)
            gtk_widget_set_sensitive(respawn, FALSE);
        gtk_widget_show_all(dialog);
        action = gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
    }
    roxterm->post_exit_tag = 0;
    if (action == roxterm_ChildExitRespawn && roxterm->no_respawn)
        action = roxterm_ChildExitClose;
    switch (action)
    {
        case roxterm_ChildExitClose:
            gtk_container_foreach(
                    GTK_CONTAINER(
                            multi_win_get_menu_bar(win)->top_level),
                    (GtkCallback) roxterm_hide_menutree, NULL);
            gtk_widget_hide(multi_win_get_popup_menu(win)->top_level);
            gtk_widget_hide(multi_win_get_short_popup_menu(win)->top_level);
            multi_tab_delete(roxterm->tab);
            break;
        case roxterm_ChildExitHold:
            roxterm_show_status(roxterm, "dialog-error");
            break;
        case roxterm_ChildExitRespawn:
            roxterm_run_command(roxterm, VTE_TERMINAL(roxterm->widget));
            break;
        default:
            break;
    }
    return FALSE;
}

static void roxterm_child_exited(VteTerminal *vte, int status,
        ROXTermData *roxterm)
{
    double delay = 0;

    (void) status;

    roxterm->running = FALSE;
    roxterm_show_status(roxterm, "dialog-error");
    if ((options_lookup_int(roxterm->profile, "exit_action") !=
            roxterm_ChildExitAsk) &&
        ((delay = options_lookup_double(roxterm->profile, "exit_pause")) != 0))
    {
        vte_terminal_feed(vte, _("\n.\n"), -1);
        roxterm->post_exit_tag = g_timeout_add((guint) (delay * 1000.0),
                (GSourceFunc) roxterm_post_child_exit, roxterm);
    }
    else
    {
        roxterm_post_child_exit(roxterm);
    }
}

inline static ROXTermData *roxterm_from_menutree(MenuTree *mtree)
{
    return (ROXTermData *) multi_win_get_user_data_for_current_tab(
                     (MultiWin *) mtree->user_data);

}

static void roxterm_change_profile(ROXTermData *roxterm, Options *profile)
{
    if (roxterm->profile != profile)
    {
        if (roxterm->profile)
        {
            dynamic_options_unref(roxterm_profiles,
                    options_get_leafname(roxterm->profile));
        }
        roxterm->profile = profile;
        options_ref(roxterm->profile);
        /* Force profile's font */
        if (roxterm->pango_desc)
        {
            pango_font_description_free(roxterm->pango_desc);
            roxterm->pango_desc = NULL;
        }
        roxterm_apply_profile(roxterm, VTE_TERMINAL(roxterm->widget), FALSE);
        roxterm_update_size(roxterm, VTE_TERMINAL(roxterm->widget));
    }
}

static void roxterm_change_colour_scheme(ROXTermData *roxterm,
        Options *colour_scheme)
{
    if (roxterm->colour_scheme != colour_scheme)
    {
        if (roxterm->colour_scheme)
            colour_scheme_unref(roxterm->colour_scheme);
        roxterm->colour_scheme = colour_scheme;
        options_ref(colour_scheme);
        roxterm_apply_colour_scheme(roxterm, VTE_TERMINAL(roxterm->widget));
    }
}

static void roxterm_change_colour_scheme_by_name(ROXTermData *roxterm,
        const char *name)
{
    Options *scheme = colour_scheme_lookup_and_ref(name);
    if (scheme)
    {
        roxterm_change_colour_scheme(roxterm, scheme);
        colour_scheme_unref(scheme);
    }
    else
    {
        dlg_warning(roxterm_get_toplevel(roxterm),
                _("Unknown colour scheme '%s'"), name);
    }
}

static void match_text_size_foreach_tab(MultiTab *tab, void *data)
{
    ROXTermData *roxterm = multi_tab_get_user_data(tab);

    if (roxterm != data)
        roxterm_match_text_size(roxterm, data);
}

static void roxterm_new_term_with_profile(GtkMenuItem *mitem,
        MenuTree *mtree, gboolean just_tab)
{
    ROXTermData *roxterm = roxterm_from_menutree(mtree);
    MultiWin *win;
    const char *profile_name;
    Options *old_profile;
    Options *new_profile;

    if (!roxterm)
        return;

    win = roxterm_get_win(roxterm);
    profile_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
    if (!profile_name)
    {
        g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
        return;
    }
    old_profile = roxterm->profile;
    new_profile = dynamic_options_lookup_and_ref(roxterm_get_profiles(),
            profile_name, "roxterm profile");
    if (!new_profile)
    {
        dlg_warning(roxterm_get_toplevel(roxterm),
                _("Profile '%s' not found"), profile_name);
        return;
    }
    roxterm->profile = new_profile;
    if (just_tab)
    {
        multi_tab_new(win, roxterm);
    }
    else
    {
        multi_win_clone(win, roxterm,
                options_lookup_int_with_default(new_profile,
                        "always_show_tabs", TRUE));
    }
    dynamic_options_unref(roxterm_profiles, profile_name);
    roxterm->profile = old_profile;
    /* All tabs in the same window must have same size */
    /*
    if (just_tab && strcmp(profile_name, options_get_leafname(old_profile)))
    {
        multi_win_foreach_tab(win, match_text_size_foreach_tab,
                multi_tab_get_user_data(tab));
    }
    */
}

static void roxterm_new_window_with_profile(GtkMenuItem *mitem, MenuTree *mtree)
{
    roxterm_new_term_with_profile(mitem, mtree, FALSE);
}

static void roxterm_new_tab_with_profile(GtkMenuItem *mitem, MenuTree *mtree)
{
    roxterm_new_term_with_profile(mitem, mtree, TRUE);
}

static void roxterm_profile_selected(GtkCheckMenuItem *mitem, MenuTree *mtree)
{
    ROXTermData *roxterm = roxterm_from_menutree(mtree);
    const char *profile_name;
    MultiWin *win = roxterm ? roxterm_get_win(roxterm) : NULL;

    if (!roxterm || multi_win_get_ignore_toggles(win))
        return;

    profile_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
    if (!profile_name)
    {
        g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
        return;
    }
    if (!gtk_check_menu_item_get_active(mitem))
    {
        check_preferences_submenu_pair(roxterm,
                MENUTREE_PREFERENCES_SELECT_PROFILE, profile_name);
    }

    if (strcmp(profile_name, options_get_leafname(roxterm->profile)))
    {
        Options *profile = dynamic_options_lookup_and_ref(roxterm_profiles,
            profile_name, "roxterm profile");

        if (profile)
        {
            roxterm_change_profile(roxterm, profile);
            /* All tabs in the same window must have same size */
            multi_win_foreach_tab(win, match_text_size_foreach_tab,
                    roxterm);
            /* Have one more ref than we need, so decrease it */
            options_unref(profile);
        }
        else
        {
            dlg_warning(roxterm_get_toplevel(roxterm),
                    _("Profile '%s' not found"), profile_name);
        }
    }
}

static void roxterm_colour_scheme_selected(GtkCheckMenuItem *mitem,
        MenuTree *mtree)
{
    ROXTermData *roxterm = roxterm_from_menutree(mtree);
    const char *scheme_name;

    if (!roxterm || multi_win_get_ignore_toggles(roxterm_get_win(roxterm)))
        return;

    scheme_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
    if (!scheme_name)
    {
        g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
        return;
    }
    if (!gtk_check_menu_item_get_active(mitem))
    {
        check_preferences_submenu_pair(roxterm,
                MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME, scheme_name);
    }

    if (strcmp(scheme_name, options_get_leafname(roxterm->colour_scheme)))
    {
        Options *colour_scheme = colour_scheme_lookup_and_ref(scheme_name);

        if (colour_scheme)
        {
            roxterm_change_colour_scheme(roxterm, colour_scheme);
            options_unref(colour_scheme);
        }
        else
        {
            dlg_warning(roxterm_get_toplevel(roxterm),
                    _("Colour scheme '%s' not found"), scheme_name);
        }
    }
}

static void roxterm_shortcuts_selected(GtkCheckMenuItem *mitem,
        MenuTree *mtree)
{
    ROXTermData *roxterm = roxterm_from_menutree(mtree);
    MultiWin *win = roxterm ? roxterm_get_win(roxterm) : NULL;
    char *scheme_name;
    Options *shortcuts;

    if (!roxterm || multi_win_get_ignore_toggles(win))
        return;

    scheme_name = g_object_get_data(G_OBJECT(mitem), PROFILE_NAME_KEY);
    if (!scheme_name)
    {
        g_critical(_("Menu item has no '%s' data"), PROFILE_NAME_KEY);
        return;
    }
    if (!gtk_check_menu_item_get_active(mitem))
    {
        check_preferences_submenu_pair(roxterm,
                MENUTREE_PREFERENCES_SELECT_SHORTCUTS, scheme_name);
    }
    shortcuts = shortcuts_open(scheme_name, TRUE);
    multi_win_set_shortcut_scheme(win, shortcuts);
    shortcuts_unref(shortcuts);
}

static void roxterm_text_changed_handler(VteTerminal *vte, ROXTermData *roxterm)
{
    (void) vte;
    if (roxterm->tab != multi_win_get_current_tab(roxterm_get_win(roxterm)))
    {
        roxterm_show_status(roxterm, "dialog-information");
    }
}

static void roxterm_bell_handler(VteTerminal *vte, ROXTermData *roxterm)
{
    MultiWin *win = roxterm_get_win(roxterm);
    (void) vte;

    if (roxterm->tab != multi_win_get_current_tab(win))
    {
        roxterm_show_status(roxterm, "dialog-warning");
    }
    if (options_lookup_int_with_default(roxterm->profile,
            "bell_highlights_tab", TRUE))
    {
        GtkWindow *gwin = GTK_WINDOW(multi_win_get_widget(win));

        if (roxterm->tab != multi_win_get_current_tab(win))
            multi_tab_draw_attention(roxterm->tab);
        if (!gtk_window_is_active(gwin))
            gtk_window_set_urgency_hint(gwin, TRUE);
    }
}

/* Ignore keys which are shortcuts, otherwise they get sent to terminal
 * when menu item is shaded.
 */
static gboolean roxterm_key_press_handler(GtkWidget *widget,
        GdkEventKey *event, ROXTermData *roxterm)
{
    Options *shortcuts = multi_win_get_shortcut_scheme(
            roxterm_get_win(roxterm));
    (void) widget;

    if (!event->is_modifier &&
            shortcuts_key_is_shortcut(shortcuts, event->keyval,
                    event->state & GDK_MODIFIER_MASK))
    {
        return TRUE;
    }
    return FALSE;
}

static void roxterm_resize_window_handler(VteTerminal *vte,
        guint width, guint height, ROXTermData *roxterm)
{
    MultiWin *win = roxterm_get_win(roxterm);
    int pad_w, pad_h;
    GtkAllocation alloc;
    int columns, rows;

    /* Can't compute size if not realized */
    if (!gtk_widget_get_realized(roxterm->widget))
        return;
    /* Ignore if maximised or full screen */
    if (win && (multi_win_is_maximised(win) || multi_win_is_fullscreen(win)))
        return;
    /* May already be desired size */
    gtk_widget_get_allocation(roxterm->widget, &alloc);
    if (alloc.width == (int) width && alloc.height == (int) height)
        return;
    /* Compute nearest grid size */
    roxterm_get_vte_padding(roxterm, &pad_w, &pad_h);
    columns = (int) (0.5 +
            (double) (width - pad_w) /
            (double) vte_terminal_get_char_width(vte));
    rows = (int) (0.5 +
            (double) (height - pad_h) /
            (double) vte_terminal_get_char_height(vte));
    /* Only resize window now if this is current tab */
    if (roxterm->tab == multi_win_get_current_tab(win))
        roxterm_set_vte_size(roxterm, vte, columns, rows);
    else
        vte_terminal_set_size(vte, columns, rows);
}

static GtkWidget *create_radio_menu_item(MenuTree *mtree,
        const char *name, GSList **group, GCallback handler)
{
    GtkWidget *mitem;

    mitem = gtk_radio_menu_item_new_with_label(*group, name);
    gtk_widget_show(mitem);
    *group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(mitem));
    if (handler)
    {
        g_object_set_data_full(G_OBJECT(mitem), PROFILE_NAME_KEY,
                g_strdup(name), g_free);
        g_signal_connect(mitem, "activate", handler, mtree);
    }
    return mitem;
}

static GtkMenu *radio_menu_from_strv(char **items,
        GCallback handler, MenuTree *mtree)
{
    int n;
    GtkMenu *menu;
    GSList *group = NULL;

    if (!items || !items[0])
        return NULL;

    menu = GTK_MENU(gtk_menu_new());

    for (n = 0; items[n]; ++n)
    {
        GtkWidget *mitem = create_radio_menu_item(mtree, items[n],
                &group, handler);

        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mitem),
                FALSE);
    }
    return menu;
}

static void roxterm_add_pair_of_pref_submenus(MultiWin *win,
        DynamicOptions *family, MenuTreeID id, GCallback handler)
{
    char **items = dynamic_options_list_sorted(family);
    GtkMenu *submenu;
    MenuTree *menutree;

    g_return_if_fail(items);
    multi_win_set_ignore_toggles(win, TRUE);
    menutree = multi_win_get_menu_bar(win);
    if (menutree)
    {
        submenu = radio_menu_from_strv(items, handler, menutree);
        gtk_menu_item_set_submenu(
                GTK_MENU_ITEM(menutree_get_widget_for_id(menutree, id)),
                GTK_WIDGET(submenu));
    }
    menutree = multi_win_get_popup_menu(win);
    if (menutree)
    {
        submenu = radio_menu_from_strv(items, handler, menutree);
        gtk_menu_item_set_submenu(
                GTK_MENU_ITEM(menutree_get_widget_for_id(menutree, id)),
                GTK_WIDGET(submenu));
    }
    multi_win_set_ignore_toggles(win, FALSE);
    g_strfreev(items);
}

static void build_new_term_with_profile_submenu(MenuTree *mtree,
        GCallback callback, GtkMenuShell *mshell, char **items)
{
    int n;

    for (n = 0; items[n]; ++n)
    {
        GtkWidget *mitem = gtk_menu_item_new_with_label(items[n]);

        gtk_widget_show(mitem);
        if (callback)
        {
            g_object_set_data_full(G_OBJECT(mitem), PROFILE_NAME_KEY,
                    g_strdup(items[n]), g_free);
            g_signal_connect(mitem, "activate", callback, mtree);
        }
        gtk_menu_shell_append(mshell, mitem);
    }
}

static void roxterm_build_pair_of_profile_submenus(MultiWin *win)
{
    char **items = dynamic_options_list_sorted(dynamic_options_get("Profiles"));
    MenuTree *menutree;

    g_return_if_fail(items);
    multi_win_set_ignore_toggles(win, TRUE);
    menutree = multi_win_get_menu_bar(win);
    if (menutree)
    {
        build_new_term_with_profile_submenu(menutree,
            G_CALLBACK(roxterm_new_window_with_profile),
            GTK_MENU_SHELL(menutree->new_win_profiles_menu), items);
        build_new_term_with_profile_submenu(menutree,
            G_CALLBACK(roxterm_new_tab_with_profile),
            GTK_MENU_SHELL(menutree->new_tab_profiles_menu), items);
    }
    menutree = multi_win_get_popup_menu(win);
    if (menutree)
    {
        build_new_term_with_profile_submenu(menutree,
            G_CALLBACK(roxterm_new_window_with_profile),
            GTK_MENU_SHELL(menutree->new_win_profiles_menu), items);
        build_new_term_with_profile_submenu(menutree,
            G_CALLBACK(roxterm_new_tab_with_profile),
            GTK_MENU_SHELL(menutree->new_tab_profiles_menu), items);
    }
    multi_win_set_ignore_toggles(win, FALSE);
    g_strfreev(items);
}

static void roxterm_add_all_pref_submenus(MultiWin *win)
{
    roxterm_add_pair_of_pref_submenus(win, dynamic_options_get("Profiles"),
            MENUTREE_PREFERENCES_SELECT_PROFILE,
            G_CALLBACK(roxterm_profile_selected));
    roxterm_build_pair_of_profile_submenus(win);
    roxterm_add_pair_of_pref_submenus(win, dynamic_options_get("Colours"),
            MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME,
            G_CALLBACK(roxterm_colour_scheme_selected));
    roxterm_add_pair_of_pref_submenus(win, dynamic_options_get("Shortcuts"),
            MENUTREE_PREFERENCES_SELECT_SHORTCUTS,
            G_CALLBACK(roxterm_shortcuts_selected));
}

static void roxterm_connect_menu_signals(MultiWin * win)
{
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_SELECT_ALL,
        G_CALLBACK(roxterm_select_all_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_COPY,
        G_CALLBACK(roxterm_copy_clipboard_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_PASTE,
        G_CALLBACK(roxterm_paste_clipboard_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_COPY_AND_PASTE,
        G_CALLBACK(roxterm_copy_and_paste_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_RESET,
        G_CALLBACK(roxterm_reset_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_RESET_AND_CLEAR,
        G_CALLBACK(roxterm_reset_and_clear_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_EDIT_RESPAWN,
        G_CALLBACK(roxterm_respawn_action), win, NULL, NULL, NULL);

    multi_win_menu_connect_swapped(win,
            MENUTREE_PREFERENCES_EDIT_CURRENT_PROFILE,
        G_CALLBACK(roxterm_edit_profile_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win,
            MENUTREE_PREFERENCES_EDIT_CURRENT_COLOUR_SCHEME,
        G_CALLBACK(roxterm_edit_colour_scheme_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win,
            MENUTREE_PREFERENCES_EDIT_CURRENT_SHORTCUTS_SCHEME,
        G_CALLBACK(roxterm_edit_shortcuts_scheme_action),
        win, NULL, NULL, NULL);

    multi_win_menu_connect_swapped(win, MENUTREE_HELP_ABOUT,
        G_CALLBACK(roxterm_show_about), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_HELP_SHOW_MANUAL,
        G_CALLBACK(roxterm_show_manual), win, NULL, NULL, NULL);

    multi_win_menu_connect_swapped(win, MENUTREE_PREFERENCES_CONFIG_MANAGER,
        G_CALLBACK(roxterm_open_config_manager), win, NULL, NULL, NULL);

    multi_win_menu_connect_swapped(win, MENUTREE_OPEN_IN_BROWSER,
        G_CALLBACK(roxterm_browser_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_OPEN_IN_MAILER,
        G_CALLBACK(roxterm_mailto_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_OPEN_IN_FILER,
        G_CALLBACK(roxterm_open_in_filer_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_COPY_URI,
        G_CALLBACK(roxterm_copy_url_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_SSH_HOST,
        G_CALLBACK(roxterm_ssh_host_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_SEARCH_FIND,
        G_CALLBACK(roxterm_open_search_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_SEARCH_FIND_NEXT,
        G_CALLBACK(roxterm_find_next_action), win, NULL, NULL, NULL);
    multi_win_menu_connect_swapped(win, MENUTREE_SEARCH_FIND_PREVIOUS,
        G_CALLBACK(roxterm_find_prev_action), win, NULL, NULL, NULL);

    roxterm_add_all_pref_submenus(win);
}

static void roxterm_composited_changed_handler(VteTerminal *vte,
        ROXTermData *roxterm)
{
    roxterm_apply_colour_scheme(roxterm, vte);
}

static void roxterm_connect_misc_signals(ROXTermData * roxterm)
{
    g_signal_connect(roxterm->widget, "child-exited",
            G_CALLBACK(roxterm_child_exited), roxterm);
    g_signal_connect(roxterm->widget, "popup-menu",
            G_CALLBACK(roxterm_popup_handler), roxterm);
    g_signal_connect(roxterm->widget, "button-press-event",
            G_CALLBACK (roxterm_click_handler), roxterm);
    g_signal_connect(roxterm->widget, "button-release-event",
            G_CALLBACK (roxterm_release_handler), roxterm);
    g_signal_connect(roxterm->widget, "icon-title-changed",
        G_CALLBACK(roxterm_icon_title_handler), roxterm);
    g_signal_connect(roxterm->widget, "window-title-changed",
        G_CALLBACK(roxterm_window_title_handler), roxterm);
    g_signal_connect(roxterm->widget, "style-updated",
        G_CALLBACK(roxterm_style_change_handler), roxterm);
    g_signal_connect(roxterm->widget, "char-size-changed",
        G_CALLBACK(roxterm_char_size_changed), roxterm);
    g_signal_connect(roxterm->widget, "drag-end",
        G_CALLBACK(roxterm_uri_drag_ended), roxterm);
    g_signal_connect(roxterm->widget, "drag-data-get",
        G_CALLBACK(roxterm_uri_drag_data_get), roxterm);
    g_signal_connect(roxterm->widget, "bell",
            G_CALLBACK(roxterm_bell_handler), roxterm);
    /* None of these seem to get raised on text output */
    /*
    g_signal_connect(roxterm->widget, "text-modified",
            G_CALLBACK(roxterm_text_changed_handler), roxterm);
    g_signal_connect(roxterm->widget, "text-inserted",
            G_CALLBACK(roxterm_text_changed_handler), roxterm);
    g_signal_connect(roxterm->widget, "text-deleted",
            G_CALLBACK(roxterm_text_changed_handler), roxterm);
    */
    g_signal_connect(roxterm->widget, "cursor-moved",
            G_CALLBACK(roxterm_text_changed_handler), roxterm);
    g_signal_connect(roxterm->widget, "resize-window",
            G_CALLBACK(roxterm_resize_window_handler), roxterm);
    g_signal_connect(roxterm->widget, "composited-changed",
            G_CALLBACK(roxterm_composited_changed_handler), roxterm);
    g_signal_connect(roxterm->widget, "key-press-event",
            G_CALLBACK(roxterm_key_press_handler), roxterm);
}

inline static void
roxterm_set_select_by_word_chars(ROXTermData * roxterm, VteTerminal * vte)
{
    char *wchars = options_lookup_string_with_default
        (roxterm->profile, "sel_by_word", "-A-Za-z0-9,./?%&#:_=+@~");

    vte_terminal_set_word_chars(vte, wchars);
    if (wchars)
        g_free(wchars);
}

inline static void
roxterm_update_audible_bell(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_audible_bell(vte, options_lookup_int
        (roxterm->profile, "audible_bell") != 0);
}

inline static void
roxterm_update_autowrap(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_rewrap_on_resize(vte, options_lookup_int_with_default
        (roxterm->profile, "autowrap", TRUE) != FALSE);
}

inline static void
roxterm_update_allow_bold(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_allow_bold(vte, options_lookup_int
        (roxterm->profile, "allow_bold") != 0);
}

static void
roxterm_update_cursor_blink_mode(ROXTermData * roxterm, VteTerminal * vte)
{
    int o = options_lookup_int(roxterm->profile, "cursor_blink_mode");

    if (o == -1)
    {
        o = options_lookup_int(roxterm->profile, "cursor_blinks") + 1;
        if (o)
            o ^= 3;
    }
    vte_terminal_set_cursor_blink_mode(vte, o);
}

inline static void
roxterm_update_cursor_shape(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_cursor_shape(vte, options_lookup_int_with_default
        (roxterm->profile, "cursor_shape", 0));
}

inline static void
roxterm_update_mouse_autohide(ROXTermData * roxterm, VteTerminal * vte)
{
    vte_terminal_set_mouse_autohide(vte, options_lookup_int(roxterm->profile,
            "mouse_autohide") != 0);
}

static void roxterm_set_scrollback_lines(ROXTermData * roxterm,
        VteTerminal * vte)
{
    int lines = options_lookup_int_with_default(roxterm->profile,
                "limit_scrollback", 0) ?
            options_lookup_int_with_default(roxterm->profile,
                    "scrollback_lines", 1000) :
            -1;
    vte_terminal_set_scrollback_lines(vte, lines);
}

static void roxterm_set_scroll_on_output(ROXTermData * roxterm,
        VteTerminal * vte)
{
    vte_terminal_set_scroll_on_output(vte, options_lookup_int_with_default
        (roxterm->profile, "scroll_on_output", 0));
}

static void roxterm_set_scroll_on_keystroke(ROXTermData * roxterm,
        VteTerminal * vte)
{
    vte_terminal_set_scroll_on_keystroke(vte, options_lookup_int_with_default
        (roxterm->profile, "scroll_on_keystroke", 0));
}

static void roxterm_set_backspace_binding(ROXTermData * roxterm,
        VteTerminal * vte)
{
    vte_terminal_set_backspace_binding(vte, (VteEraseBinding)
        options_lookup_int_with_default(roxterm->profile,
            "backspace_binding", VTE_ERASE_AUTO));
}

static void roxterm_set_delete_binding(ROXTermData * roxterm,
        VteTerminal * vte)
{
    vte_terminal_set_delete_binding(vte, (VteEraseBinding)
        options_lookup_int_with_default(roxterm->profile,
            "delete_binding", VTE_ERASE_AUTO));
}

inline static void roxterm_apply_wrap_switch_tab(ROXTermData *roxterm)
{
    multi_win_set_wrap_switch_tab(roxterm_get_win(roxterm),
        options_lookup_int_with_default(roxterm->profile,
            "wrap_switch_tab", FALSE));
}

inline static void roxterm_apply_always_show_tabs(ROXTermData *roxterm)
{
    multi_win_set_always_show_tabs(roxterm_get_win(roxterm),
        options_lookup_int_with_default(roxterm->profile,
            "always_show_tabs", TRUE));
}

static void roxterm_apply_disable_menu_access(ROXTermData *roxterm)
{
    static char *orig_menu_access = NULL;
    static gboolean disabled = FALSE;
    gboolean disable = options_lookup_int_with_default(roxterm->profile,
            "disable_menu_access", FALSE);
    GtkSettings *settings;
    GtkBindingSet *binding_set;

    if (disable == disabled)
        return;
    settings = gtk_settings_get_default();
    if (!orig_menu_access)
    {
        g_object_get(settings, "gtk-menu-bar-accel", &orig_menu_access, NULL);
    }
    disabled = disable;
    g_type_class_unref(g_type_class_ref(GTK_TYPE_MENU_BAR));
    g_object_set(settings, "gtk-menu-bar-accel",
            disable ? NULL: orig_menu_access,
            NULL);
    binding_set = gtk_binding_set_by_class(
            VTE_TERMINAL_GET_CLASS(roxterm->widget));
    if (disable)
    {
        gtk_binding_entry_skip(binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
    }
    else
    {
        gtk_binding_entry_remove(binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
    }
}

static void roxterm_apply_title_template(ROXTermData *roxterm)
{
    char *win_title = global_options_lookup_string("title");
    gboolean custom_win_title;
    MultiWin *win = roxterm_get_win(roxterm);

    if (win_title)
    {
        custom_win_title = TRUE;
    }
    else
    {
        custom_win_title = FALSE;
        win_title = options_lookup_string_with_default(roxterm->profile,
                    "win_title", "%s");
    }
    multi_win_set_title_template(win, win_title);
    if (custom_win_title)
    {
        global_options_reset_string("title");
        multi_win_set_title_template_locked(win, TRUE);
    }
    g_free(win_title);
}

static void roxterm_apply_show_tab_status(ROXTermData *roxterm)
{
    if (roxterm->tab)
    {
        const char *name;

        if (options_lookup_int_with_default(roxterm->profile,
                "show_tab_status", FALSE))
        {
            name = roxterm->status_icon_name;
        }
        else
        {
            name = NULL;
        }
        multi_tab_set_status_icon_name(roxterm->tab, name);
    }
}

static void roxterm_apply_show_tab_number(ROXTermData *roxterm)
{
    multi_tab_set_show_number(roxterm->tab,
            options_lookup_int_with_default(roxterm->profile,
                    "show_tab_num", TRUE));
}

static void roxterm_apply_match_files(ROXTermData *roxterm, VteTerminal *vte)
{
    if (options_lookup_int_with_default(roxterm->profile,
            "match_plain_files", FALSE))
    {
        if (roxterm->file_match_tag[0] == -1)
            roxterm_add_file_matches(roxterm, vte);
    }
    else
    {
        if (roxterm->file_match_tag[0] != -1)
            roxterm_remove_file_matches(roxterm, vte);
    }

}

inline static void roxterm_apply_middle_click_tab(ROXTermData *roxterm)
{
    multi_tab_set_middle_click_tab_action(roxterm->tab,
            options_lookup_int_with_default(roxterm->profile,
                    "middle_click_tab", 0));
}

static void roxterm_apply_colour_scheme_from_profile(ROXTermData *roxterm)
{
    char *scheme = options_lookup_string(roxterm->profile, "colour_scheme");
    if (scheme && scheme[0])
    {
        roxterm_change_colour_scheme_by_name(roxterm, scheme);
    }
    g_free(scheme);
}

static void roxterm_apply_show_add_tab_btn(ROXTermData *roxterm)
{
    MultiWin *win = roxterm_get_win(roxterm);
    if (win)
    {
        multi_win_set_show_add_tab_button(win,
                options_lookup_int_with_default(roxterm->profile,
                        "show_add_tab_btn", 1));
    }
}

static void roxterm_apply_profile(ROXTermData *roxterm, VteTerminal *vte,
        gboolean update_geometry)
{
    roxterm_set_select_by_word_chars(roxterm, vte);
    roxterm_update_audible_bell(roxterm, vte);
    roxterm_update_allow_bold(roxterm, vte);
    roxterm_update_autowrap(roxterm, vte);
    roxterm_update_cursor_blink_mode(roxterm, vte);
    roxterm_update_cursor_shape(roxterm, vte);

    roxterm_apply_colour_scheme(roxterm, vte);

    roxterm_update_font(roxterm, vte, update_geometry);

    roxterm_set_scrollback_lines(roxterm, vte);
    roxterm_set_scroll_on_output(roxterm, vte);
    roxterm_set_scroll_on_keystroke(roxterm, vte);

    roxterm_set_backspace_binding(roxterm, vte);
    roxterm_set_delete_binding(roxterm, vte);

    roxterm_update_mouse_autohide(roxterm, vte);
    roxterm_apply_encoding(roxterm, vte);

    roxterm_apply_wrap_switch_tab(roxterm);

    roxterm_apply_disable_menu_access(roxterm);

    roxterm_apply_title_template(roxterm);
    roxterm_apply_show_tab_number(roxterm);
    roxterm_apply_show_tab_status(roxterm);
    roxterm_apply_match_files(roxterm, vte);
    roxterm_apply_middle_click_tab(roxterm);

    roxterm_apply_colour_scheme_from_profile(roxterm);

    roxterm_apply_show_add_tab_btn(roxterm);
}

static gboolean
roxterm_drag_data_received(GtkWidget *widget,
        const char *text, gulong len, gpointer data)
{
    char *sep = strstr(text, "\r\n");
    char *rejoined = NULL;
    (void) data;

    if (sep)
    {
        char **uris = g_strsplit(text, "\r\n", 0);
        int n;
        char *tmp;

        for (n = 0; uris[n]; ++n)
        {
            /* Last uri is usually a blank line so skip */
            if (!uris[n][0])
            {
                continue;
            }
            /* escape ' chars */
            if (strchr(uris[n], '\''))
            {
                char **split = g_strsplit(uris[n], "'", 0);

                tmp = g_strjoinv("'\\''", split);
                g_free(uris[n]);
                uris[n] = tmp;
            }
            tmp = g_strdup_printf("%s%s%s", rejoined ? rejoined : "",
                    rejoined ? "' '" : "'", uris[n]);
            g_free(rejoined);
            rejoined = tmp;
        }
        if (rejoined && rejoined[0])
        {
            tmp = g_strdup_printf("%s' ", rejoined);
            g_free(rejoined);
            rejoined = tmp;
        }
        text = rejoined;
        if (text)
            len = strlen(text);
        g_strfreev(uris);
    }
    if (text)
        vte_terminal_feed_child(VTE_TERMINAL(widget), text, len);
    if (rejoined)
        g_free(rejoined);
    return TRUE;
}

static gboolean roxterm_window_state_changed(GtkWidget *win,
        GdkEventWindowState *event, ROXTermData *roxterm)
{
    (void) win;
    roxterm->maximise =
            (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
    return FALSE;
}

inline static void
roxterm_attach_state_changed_handler(ROXTermData *roxterm)
{
    roxterm->win_state_changed_tag =
            g_signal_connect(roxterm_get_toplevel(roxterm),
            "window-state-event",
            G_CALLBACK(roxterm_window_state_changed), roxterm);
}

static void
roxterm_tab_received(GtkWidget *rcvd_widget, ROXTermData *roxterm)
{
    MultiTab *tab = multi_tab_get_from_widget(rcvd_widget);

    if (tab != roxterm->tab)
    {
        MultiWin *win = roxterm_get_win(roxterm);
        int page_num = multi_tab_get_page_num(roxterm->tab);

        if (multi_tab_get_parent(tab) == win)
            multi_tab_move_to_position(tab, page_num, TRUE);
        else
            multi_tab_move_to_new_window(win, tab, page_num);
    }
}

inline static GtkAdjustment *roxterm_get_vte_adjustment(VteTerminal *vte)
{
    return gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte));
}

static GtkWidget *roxterm_multi_tab_filler(MultiWin * win, MultiTab * tab,
    ROXTermData * roxterm_template, ROXTermData ** roxterm_out,
    GtkWidget ** vte_widget, GtkAdjustment **adjustment)
{
    VteTerminal *vte;
    const char *title_orig;
    ROXTermData *roxterm = roxterm_data_clone(roxterm_template);
    int hide_menu_bar;
    MultiWinScrollBar_Position scrollbar_pos;
    char *tab_name;
    gboolean custom_tab_name = FALSE;
    MultiWin *template_win = roxterm_get_win(roxterm_template);

    roxterm_terms = g_list_append(roxterm_terms, roxterm);

    if (template_win)
    {
        hide_menu_bar = !multi_win_get_show_menu_bar(template_win);
    }
    else
    {
        hide_menu_bar = global_options_lookup_int("hide_menubar");
        if (hide_menu_bar == -1)
        {
            hide_menu_bar = options_lookup_int(roxterm_template->profile,
                "hide_menubar") == 1;
        }
    }
    multi_win_set_show_menu_bar(win, !hide_menu_bar);

    roxterm->tab = tab;
    *roxterm_out = roxterm;

    roxterm->widget = vte_terminal_new();
    gtk_widget_grab_focus(roxterm->widget);
    vte = VTE_TERMINAL(roxterm->widget);
    if (vte_widget)
        *vte_widget = roxterm->widget;
    if (adjustment)
        *adjustment = roxterm_get_vte_adjustment(vte);

    scrollbar_pos = multi_win_set_scroll_bar_position(win,
        options_lookup_int_with_default(roxterm_template->profile,
            "scrollbar_pos", MultiWinScrollBar_Right));
    if (scrollbar_pos)
    {
        GtkGrid *grid = GTK_GRID(roxterm->hbox = gtk_grid_new());

        roxterm->scrollbar =
                gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL,
                        roxterm_get_vte_adjustment(vte));
        if (scrollbar_pos == MultiWinScrollBar_Left)
        {
            gtk_grid_attach(grid, roxterm->scrollbar, 0, 0, 1, 1);
            gtk_grid_attach(grid, roxterm->widget, 1, 0, 1, 1);
        }
        else
        {
            gtk_grid_attach(grid, roxterm->widget, 0, 0, 1, 1);
            gtk_grid_attach(grid, roxterm->scrollbar, 1, 0, 1, 1);
        }
        g_object_set(roxterm->widget, "hexpand", TRUE,
                "vexpand", TRUE, NULL);
        g_object_set(roxterm->scrollbar, "hexpand", FALSE,
                "vexpand", TRUE, NULL);
        gtk_widget_show_all(roxterm->hbox);
    }
    else
    {
        gtk_widget_show(roxterm->widget);
    }

    roxterm_add_matches(roxterm, vte);

    roxterm_apply_profile(roxterm, vte, FALSE);
    tab_name = global_options_lookup_string("tab-name");
    if (tab_name)
    {
        custom_tab_name = TRUE;
    }
    else
    {
        custom_tab_name = FALSE;
        tab_name = options_lookup_string_with_default(roxterm->profile,
                "title_string", "%s");
    }
    multi_tab_set_window_title_template(tab, tab_name);
    multi_tab_set_title_template_locked(tab, custom_tab_name);
    if (custom_tab_name)
    {
        global_options_reset_string("tab-name");
    }
    g_free(tab_name);
    title_orig = vte_terminal_get_window_title(vte);
    multi_tab_set_window_title(tab, title_orig ? title_orig : _("ROXTerm"));

    roxterm_set_vte_size(roxterm, vte, roxterm->columns, roxterm->rows);

    roxterm_connect_misc_signals(roxterm);
    multi_tab_connect_tab_selection_handler(win,
        (MultiTabSelectionHandler) roxterm_tab_selection_handler);
    roxterm->drd = drag_receive_setup_dest_widget(roxterm->widget,
            roxterm_drag_data_received,
            (DragReceiveTabHandler) roxterm_tab_received,
            roxterm);

    roxterm_attach_state_changed_handler(roxterm);

    g_idle_add((GSourceFunc) run_child_when_idle, roxterm);

    return scrollbar_pos ? roxterm->hbox : roxterm->widget;
}

static void roxterm_multi_tab_destructor(ROXTermData * roxterm)
{
    roxterm_terms = g_list_remove(roxterm_terms, roxterm);
    roxterm_data_delete(roxterm);
}

/* Returns FALSE if the value hasn't really changed */
static gboolean
roxterm_update_option(Options * opts, const char *key,
    OptsDBusOptType opt_type, OptsDBusValue val)
{
    gboolean result = TRUE;
    char *oldval;

    switch (opt_type)
    {
        case OptsDBus_StringOpt:
            oldval = options_lookup_string(opts, key);
            if (g_strcmp0(oldval, val.s))
                options_set_string(opts, key, val.s);
            else
                result = FALSE;
            if (oldval)
                g_free(oldval);
            return result;
        case OptsDBus_IntOpt:
            if (options_lookup_int(opts, key) == val.i)
                return FALSE;
            options_set_int(opts, key, val.i);
            return TRUE;
        case OptsDBus_FloatOpt:
            if (options_lookup_double(opts, key) == val.f)
                return FALSE;
            options_set_double(opts, key, val.f);
            return TRUE;
        default:
            g_critical(_("Unknown option type (%d)"), opt_type);
    }
    return FALSE;
}

static gboolean roxterm_get_show_tab_close_button(ROXTermData *roxterm)
{
    return options_lookup_int_with_default(roxterm->profile,
            "tab_close_btn", TRUE);
}

static gboolean roxterm_get_new_tab_adjacent(ROXTermData *roxterm)
{
    return options_lookup_int_with_default(roxterm->profile,
            "new_tabs_adjacent", FALSE);
}

static void roxterm_reflect_profile_change(Options * profile, const char *key)
{
    GList *link;

    for (link = roxterm_terms; link; link = g_list_next(link))
    {
        ROXTermData *roxterm = link->data;
        VteTerminal *vte;
        MultiWin *win = roxterm_get_win(roxterm);
        gboolean apply_to_win = FALSE;

        if (roxterm->profile != profile || roxterm->profile->deleted)
            continue;

        vte = VTE_TERMINAL(roxterm->widget);
        if (!strcmp(key, "font"))
        {
            roxterm_apply_profile_font(roxterm, vte, TRUE);
            apply_to_win = TRUE;
        }
        else if (!strcmp(key, "allow_bold"))
        {
            roxterm_update_allow_bold(roxterm, vte);
        }
        else if (!strcmp(key, "hide_menubar") &&
            multi_win_get_current_tab(win) == roxterm->tab)
        {
            multi_win_set_show_menu_bar(win,
                !options_lookup_int(roxterm->profile, "hide_menubar"));
        }
        else if (!strcmp(key, "audible_bell"))
        {
            roxterm_update_audible_bell(roxterm, vte);
        }
        else if (!strcmp(key, "autowrap"))
        {
            roxterm_update_autowrap(roxterm, vte);
        }
        else if (!strcmp(key, "cursor_blink_mode"))
        {
            roxterm_update_cursor_blink_mode(roxterm, vte);
        }
        else if (!strcmp(key, "cursor_shape"))
        {
            roxterm_update_cursor_shape(roxterm, vte);
        }
        else if (!strcmp(key, "mouse_autohide"))
        {
            roxterm_update_mouse_autohide(roxterm, vte);
        }
        else if (!strcmp(key, "sel_by_word"))
        {
            roxterm_set_select_by_word_chars(roxterm, vte);
        }
        else if (!strcmp(key, "width") || !strcmp(key, "height"))
        {
            roxterm_update_size(roxterm, vte);
            apply_to_win = TRUE;
        }
        else if (!strcmp(key, "maximise"))
        {
            roxterm->maximise = options_lookup_int(roxterm->profile,
                    "maximise");
            if (roxterm->maximise)
            {
                gtk_window_maximize(roxterm_get_toplevel(roxterm));
            }
            else
            {
                gtk_window_unmaximize(roxterm_get_toplevel(roxterm));
            }
            apply_to_win = TRUE;
        }
        else if (!strcmp(key, "full_screen"))
        {
            int fs = options_lookup_int(roxterm->profile, "full_screen");
            multi_win_set_fullscreen(win, fs);
            apply_to_win = TRUE;
        }
        else if (!strcmp(key, "saturation"))
        {
            roxterm_apply_colour_scheme(roxterm, vte);
        }
        else if (!strcmp(key, "scrollback_lines") ||
                !strcmp(key, "limit_scrollback"))
        {
            roxterm_set_scrollback_lines(roxterm, vte);
        }
        else if (!strcmp(key, "scroll_on_output"))
        {
            roxterm_set_scroll_on_output(roxterm, vte);
        }
        else if (!strcmp(key, "scroll_on_keystroke"))
        {
            roxterm_set_scroll_on_keystroke(roxterm, vte);
        }
        else if (!strcmp(key, "backspace_binding"))
        {
            roxterm_set_backspace_binding(roxterm, vte);
        }
        else if (!strcmp(key, "delete_binding"))
        {
            roxterm_set_delete_binding(roxterm, vte);
        }
        else if (!strcmp(key, "encoding"))
        {
            char *encoding =
                options_lookup_string(roxterm->profile, "encoding");

            vte_terminal_set_encoding(vte, encoding, NULL);
            if (encoding)
                g_free(encoding);
        }
        else if (!strcmp(key, "wrap_switch_tab"))
        {
            roxterm_apply_wrap_switch_tab(roxterm);
        }
        else if (!strcmp(key, "always_show_tabs"))
        {
            roxterm_apply_always_show_tabs(roxterm);
        }
        else if (!strcmp(key, "show_add_tab_btn"))
        {
            roxterm_apply_show_add_tab_btn(roxterm);
        }
        else if (!strcmp(key, "disable_menu_access"))
        {
            roxterm_apply_disable_menu_access(roxterm);
        }
        else if (!strcmp(key, "disable_menu_shortcuts"))
        {
            gboolean disable = options_lookup_int(roxterm->profile,
                    "disable_menu_shortcuts");
            MenuTree *mtree = multi_win_get_menu_bar(win);

            menutree_disable_shortcuts(mtree, disable);
        }
        else if (!strcmp(key, "disable_tab_menu_shortcuts"))
        {
            gboolean disable = options_lookup_int(roxterm->profile,
                    "disable_tab_menu_shortcuts");
            MenuTree *mtree = multi_win_get_popup_menu(win);

            menutree_disable_tab_shortcuts(mtree, disable);
            mtree = multi_win_get_menu_bar(win);
            menutree_disable_tab_shortcuts(mtree, disable);
        }
        else if (!strcmp(key, "title_string"))
        {
            multi_tab_set_window_title_template(roxterm->tab,
                    options_lookup_string_with_default(roxterm->profile,
                            "title_string", "%s"));
        }
        else if (!strcmp(key, "win_title"))
        {
            multi_win_set_title_template(win,
                    options_lookup_string_with_default(roxterm->profile,
                            "win_title", "%s"));
        }
        else if (!strcmp(key, "tab_close_btn"))
        {
            if (roxterm_get_show_tab_close_button(roxterm))
                multi_tab_add_close_button(roxterm->tab);
            else
                multi_tab_remove_close_button(roxterm->tab);
        }
        else if (!strcmp(key, "show_tab_num"))
        {
            roxterm_apply_show_tab_number(roxterm);
        }
        else if (!strcmp(key, "show_tab_status"))
        {
            roxterm_apply_show_tab_status(roxterm);
        }
        else if (!strcmp(key, "match_plain_files"))
        {
            roxterm_apply_match_files(roxterm, vte);
        }
        else if (!strcmp(key, "middle_click_tab"))
        {
            roxterm_apply_middle_click_tab(roxterm);
        }
        else if (!strcmp(key, "colour_scheme"))
        {
            roxterm_apply_colour_scheme_from_profile(roxterm);
        }
        if (apply_to_win)
        {
            multi_win_foreach_tab(win, match_text_size_foreach_tab, roxterm);
        }
    }
}

static gboolean roxterm_update_colour_option(Options *scheme, const char *key,
        const char *value)
{
    void (*setter)(Options *, const char *) = NULL;
    GdkRGBA *old_colour;
    GdkRGBA *pnew_colour = NULL;
    GdkRGBA  new_colour;

    if (value)
    {
        g_return_val_if_fail(gdk_rgba_parse(&new_colour, value), FALSE);
        pnew_colour = &new_colour;
    }
    if (!strcmp(key, "foreground"))
    {
        old_colour = colour_scheme_get_foreground_colour(scheme, TRUE);
        setter = colour_scheme_set_foreground_colour;
    }
    else if (!strcmp(key, "background"))
    {
        old_colour = colour_scheme_get_background_colour(scheme, TRUE);
        setter = colour_scheme_set_background_colour;
    }
    else if (!strcmp(key, "cursor"))
    {
        old_colour = colour_scheme_get_cursor_colour(scheme, TRUE);
        setter = colour_scheme_set_cursor_colour;
    }
    else if (!strcmp(key, "bold"))
    {
        old_colour = colour_scheme_get_bold_colour(scheme, TRUE);
        setter = colour_scheme_set_bold_colour;
    }
    else
    {
        old_colour = colour_scheme_get_palette(scheme) + atoi(key);
    }
    if (!old_colour && !pnew_colour)
        return FALSE;
    if (old_colour && pnew_colour && gdk_rgba_equal(old_colour, pnew_colour))
        return FALSE;
    if (setter)
        setter(scheme, value);
    else
        colour_scheme_set_palette_entry(scheme, atoi(key), value);
    return TRUE;
}

static gboolean roxterm_update_palette_size(Options *scheme, int size)
{
    if (colour_scheme_get_palette_size(scheme) == size)
        return FALSE;
    colour_scheme_set_palette_size(scheme, size);
    return TRUE;
}

static void roxterm_reflect_colour_change(Options *scheme, const char *key)
{
    GList *link;

    for (link = roxterm_terms; link; link = g_list_next(link))
    {
        ROXTermData *roxterm = link->data;
        VteTerminal *vte;

        if (roxterm->colour_scheme != scheme || roxterm->colour_scheme->deleted)
            continue;

        vte = VTE_TERMINAL(roxterm->widget);

        if (!strcmp(key, "cursor"))
            roxterm_update_cursor_colour(roxterm, vte);
        else if (!strcmp(key, "bold"))
            roxterm_update_bold_colour(roxterm, vte);
        else
            roxterm_apply_colour_scheme(roxterm, vte);
    }
}

static void
roxterm_opt_signal_handler(const char *profile_name, const char *key,
    OptsDBusOptType opt_type, OptsDBusValue val)
{
    if (!strncmp(profile_name, "Profiles/", 9))
    {
        const char *short_profile_name = profile_name + 9;
        Options *profile = dynamic_options_lookup_and_ref(roxterm_profiles,
            short_profile_name, "roxterm profile");

        if (roxterm_update_option(profile, key, opt_type, val))
            roxterm_reflect_profile_change(profile, key);
        dynamic_options_unref(roxterm_profiles, short_profile_name);
    }
    else if (!strncmp(profile_name, "Colours/", 8))
    {
        const char *scheme_name = profile_name + 8;
        Options *scheme = colour_scheme_lookup_and_ref(scheme_name);
        gboolean changed = FALSE;

        if (!strcmp(key, "palette_size"))
            changed = roxterm_update_palette_size(scheme, val.i);
        else
            changed = roxterm_update_colour_option(scheme, key, val.s);
        if (changed)
            roxterm_reflect_colour_change(scheme, key);
        colour_scheme_unref(scheme);
    }
    else if (!strcmp(profile_name, "Global") &&
            (!strcmp(key, "warn_close") ||
            !strcmp(key, "only_warn_running") ||
            !strcmp(key, "prefer_dark_theme")))
    {
        options_set_int(global_options, key, val.i);
        if (!strcmp(key, "prefer_dark_theme"))
            global_options_apply_dark_theme();
    }
    else
    {
        g_critical(_("Profile/key '%s/%s' not understood"), profile_name, key);
    }
}

/* data is cast to char const **pname; if the deleted item is currently
 * selected *pname is changed to NULL */
static void delete_name_from_menu(GtkWidget *widget, gpointer data)
{
    const char *profile_name = g_object_get_data(G_OBJECT(widget),
            PROFILE_NAME_KEY);
    char const **pname = data;

    g_return_if_fail(profile_name);
    /* pname may have been NULLed by an earlier call */
    if (!*pname)
        return;
    if (!strcmp(profile_name, *pname))
    {
        if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
            *pname = NULL;
        gtk_widget_destroy(widget);
    }
}

/* Casts data to GtkWidget ** and sets target to found widget if it finds an
 * entry with no PROFILE_NAME_KEY */
static void find_unhandled_menu_entry(GtkWidget *widget, gpointer data)
{
    if (!g_object_get_data(G_OBJECT(widget), PROFILE_NAME_KEY))
        *((GtkWidget **) data) = widget;
}

static GtkWidget *append_name_to_menu(MenuTree *mtree, GtkMenu *menu,
        const char *name, GCallback handler)
{
    GSList *group = NULL;
    GtkWidget *mitem;

    gtk_container_foreach(GTK_CONTAINER(menu),
            menutree_find_radio_group, &group);
    mitem = create_radio_menu_item(mtree, name, &group, handler);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
    return mitem;
}

/* Sets *pname to NULL if it's currently selected */
inline static void remove_name_from_menu(GtkMenu *menu, char const **pname)
{
    gtk_container_foreach(GTK_CONTAINER(menu),
            delete_name_from_menu, (gpointer) pname);
}

static void stuff_changed_do_menu(MenuTree *mtree,
        const char *what_happened, const char *family_name,
        const char *current_name, const char *new_name)
{
    MenuTreeID id;
    GtkMenu *menu;
    GCallback handler = NULL;

    if (!strcmp(family_name, "Profiles"))
    {
        id = MENUTREE_PREFERENCES_SELECT_PROFILE;
        handler = G_CALLBACK(roxterm_profile_selected);
    }
    else if (!strcmp(family_name, "Colours"))
    {
        id = MENUTREE_PREFERENCES_SELECT_COLOUR_SCHEME;
        handler = G_CALLBACK(roxterm_colour_scheme_selected);
    }
    else if (!strcmp(family_name, "Shortcuts"))
    {
        id = MENUTREE_PREFERENCES_SELECT_SHORTCUTS;
        handler = G_CALLBACK(roxterm_shortcuts_selected);
    }
    else if (!strcmp(family_name, "encodings"))
    {
        roxterm_encodings_changed(mtree, what_happened, current_name, new_name);
        return;
    }
    else
    {
        g_warning(_("Changed options family '%s' not known"), family_name);
        return;
    }
    g_return_if_fail((menu = menutree_submenu_from_id(mtree, id)) != NULL);

    if (!strcmp(what_happened, OPTSDBUS_DELETED))
    {
        GtkWidget *unhandled_mitem = NULL;

        remove_name_from_menu(menu, &current_name);
        gtk_container_foreach(GTK_CONTAINER(menu),
                find_unhandled_menu_entry, &unhandled_mitem);
        if (!unhandled_mitem)
        {
            unhandled_mitem = append_name_to_menu(mtree, menu,
                    _("[Deleted]"), NULL);
            gtk_widget_set_sensitive(unhandled_mitem, FALSE);
        }
        /* If deleted item was selected, select unhandled item in its place
         */
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(unhandled_mitem),
                current_name == NULL);
    }
    else if (!strcmp(what_happened, OPTSDBUS_ADDED))
    {
        append_name_to_menu(mtree, menu, current_name, handler);
    }
    else if (!strcmp(what_happened, OPTSDBUS_RENAMED))
    {
        GtkWidget *mitem;

        remove_name_from_menu(menu, &current_name);
        mitem = append_name_to_menu(mtree, menu, new_name, handler);
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mitem),
                current_name == NULL);
    }
    else
    {
        g_warning(_("Options family change action '%s' not known"),
                what_happened);
    }
}

void roxterm_stuff_changed_handler(const char *what_happened,
        const char *family_name, const char *current_name,
        const char *new_name)
{
    GList *link;
    DynamicOptions *dynopts = NULL;
    Options *options = NULL;

    if (!strcmp(what_happened, OPTSDBUS_CHANGED) &&
            strcmp(family_name, "Shortcuts"))
    {
        for (link = roxterm_terms; link; link = g_list_next(link))
        {
            ROXTermData *roxterm = link->data;

            if (!strcmp(family_name, "Profiles"))
            {
                if (!strcmp(options_get_leafname(roxterm->profile),
                        current_name))
                {
                    roxterm_apply_profile(roxterm,
                            VTE_TERMINAL(roxterm->widget), TRUE);
                }
            }
            else if (!strcmp(family_name, "Colours"))
            {
                if (!strcmp(options_get_leafname(roxterm->colour_scheme),
                        current_name))
                {
                    roxterm_apply_colour_scheme(roxterm,
                            VTE_TERMINAL(roxterm->widget));
                }
            }
        }
        return;
    }

    if (strcmp(family_name, "encodings"))
    {
        dynopts = dynamic_options_get(family_name);
        options = dynamic_options_lookup(dynopts, current_name);
    }

    if (!strcmp(what_happened, OPTSDBUS_DELETED))
    {
        if (options)
        {
            dynamic_options_forget(dynopts, current_name);
            options->deleted = TRUE;
        }
    }
    else if (!strcmp(what_happened, OPTSDBUS_RENAMED))
    {
        if (options)
        {
            options_change_leafname(options, new_name);
            dynamic_options_rename(dynopts, current_name, new_name);
        }
    }

    for (link = multi_win_all; link; link = g_list_next(link))
    {
        MultiWin *win = (MultiWin *) link->data;

        if (strcmp(what_happened, OPTSDBUS_CHANGED))
        {
            MenuTree *mtree = multi_win_get_menu_bar(win);

            multi_win_set_ignore_toggles(win, TRUE);
            if (mtree)
            {
                stuff_changed_do_menu(mtree, what_happened, family_name,
                        current_name, new_name);
            }
            mtree = multi_win_get_popup_menu(win);
            if (mtree)
            {
                stuff_changed_do_menu(mtree, what_happened, family_name,
                        current_name, new_name);
            }
            multi_win_set_ignore_toggles(win, FALSE);
        }
        else if (!strcmp(family_name, "Shortcuts"))
        {
            Options *shortcuts = shortcuts_open(current_name, TRUE);
            multi_win_set_shortcut_scheme(win, shortcuts);
            shortcuts_unref(shortcuts);
        }
    }
}

static gboolean roxterm_verify_id(ROXTermData *roxterm)
{
    GList *link;

    for (link = roxterm_terms; link; link = g_list_next(link))
    {
        if (link->data == roxterm)
            return TRUE;
    }
    g_warning(_("Invalid ROXTERM_ID %p in D-Bus message "
            "(this is expected if you used roxterm's --separate option)"),
            roxterm);
    return FALSE;
}

static void roxterm_set_profile_handler(ROXTermData *roxterm, const char *name)
{
    if (!roxterm_verify_id(roxterm))
        return;

    Options *profile = dynamic_options_lookup_and_ref(roxterm_profiles, name,
            "roxterm profile");

    if (profile)
    {
        roxterm_change_profile(roxterm, profile);
        multi_win_foreach_tab(roxterm_get_win(roxterm),
                match_text_size_foreach_tab, roxterm);
        options_unref(profile);
    }
    else
    {
        dlg_warning(roxterm_get_toplevel(roxterm),
                _("Unknown profile '%s' in D-Bus signal"), name);
    }
}

static void roxterm_set_colour_scheme_handler(ROXTermData *roxterm,
        const char *name)
{
    if (!roxterm_verify_id(roxterm))
        return;
    roxterm_change_colour_scheme_by_name(roxterm, name);
}

static void roxterm_set_shortcut_scheme_handler(ROXTermData *roxterm,
        const char *name)
{
    if (!roxterm_verify_id(roxterm))
        return;

    Options *shortcuts = shortcuts_open(name, TRUE);
    multi_win_set_shortcut_scheme(roxterm_get_win(roxterm), shortcuts);
    shortcuts_unref(shortcuts);
}

/* Parses a number from a geometry string which must be terminated by NULL or
 * one of chars from trmntrs immediately after digits. Returns pointer to that
 * terminator or NULL if invalid. */
static char *parse_geom_n(char *g, int *n, const char *trmntrs)
{
    char *g2 = g;
    size_t l;

    if (*g == '-' || *g == '+')
        ++g2;
    if (!isdigit(*g2))
        return NULL;
    l = strspn(g2, "0123456789");
    /* Check we've reached end of string or one of trmntrs */
    if (g2[l] && (!trmntrs || !strchr(trmntrs, g2[l])))
        return NULL;
    if (n && sscanf(g, "%d", n) != 1)
        return NULL;
    return g2 + l;
}

/* This just checks whether the rest of the string is a valid pair of
 * geometry offsets */
static gboolean check_geom_offsets(char *g)
{
    if ((g = parse_geom_n(g, NULL, "+-")) == NULL)
    {
        return FALSE;
    }
    if ((*g != '+' && *g != '-')
        || (g = parse_geom_n(g, NULL, NULL)) == NULL)
    {
        return FALSE;
    }
    return TRUE;
}

static GtkPositionType get_profile_tab_pos(Options *profile)
{
    switch (options_lookup_int_with_default(profile, "tab_pos", 0))
    {
        case 0:
            return GTK_POS_TOP;
        case 1:
            return GTK_POS_BOTTOM;
        case 2:
            return GTK_POS_LEFT;
        case 3:
            return GTK_POS_RIGHT;
        case 4:
            return -1;
        default:
            return GTK_POS_TOP;
    }
}

inline static GtkPositionType roxterm_get_tab_pos(const ROXTermData *roxterm)
{
    return get_profile_tab_pos(roxterm->profile);
}

static gboolean roxterm_get_always_show_tabs(const ROXTermData *roxterm)
{
    return (gboolean) options_lookup_int_with_default(roxterm->profile,
            "always_show_tabs", TRUE);
}

/* Takes over ownership of profile and non-const strings;
 * on exit size_on_cli indicates whether dimensions were given in *geom;
 * geom is freed and set to NULL if it's invalid
 */
static ROXTermData *roxterm_data_new(const char *display_name,
        double zoom_factor, const char *directory,
        char *profile_name, Options *profile, gboolean maximise,
        const char *colour_scheme_name, char *encoding,
        char **geom, gboolean *size_on_cli, char **env)
{
    ROXTermData *roxterm = g_new0(ROXTermData, 1);
    (void) profile_name;

    roxterm->status_icon_name = NULL;
    roxterm->target_zoom_factor = zoom_factor;
    roxterm->current_zoom_factor = 1.0;
    if (display_name)
        roxterm->display_name = g_strdup(display_name);
    if (!roxterm->target_zoom_factor)
    {
        roxterm->target_zoom_factor = 1.0;
        roxterm->zoom_index = -1;
    }
    else
    {
        roxterm->zoom_index = multi_win_get_nearest_index_for_zoom(
                roxterm->target_zoom_factor);
    }
    roxterm->directory = directory ? g_strdup(directory) : g_get_current_dir();
    roxterm->profile = profile;
    if (size_on_cli)
        *size_on_cli = FALSE;
    if (geom && *geom)
    {
        /* Can't rely on gtk_window_parse_geometry alone because we want to
         * know width and height before creating the window */
        char *g = *geom;

        if (*g == '+' || *g == '-')
        {
            /* Offsets first (and only); we don't actually want to know them
             * here; just check validity of string */
            if (!check_geom_offsets(g))
            {
                g_free(*geom);
                *geom = NULL;
            }
        }
        else
        {
            int width, height;

            g = parse_geom_n(g, &width, "x");
            if (g && *g == 'x')
            {
                ++g;
                g = parse_geom_n(g, &height, "+-");
                if (*g && !check_geom_offsets(g))
                {
                    g_free(*geom);
                    *geom = NULL;
                }
            }
            else
            {
                g_free(*geom);
                *geom = NULL;
            }
            if (*geom)
            {
                roxterm->columns = width;
                roxterm->rows = height;
                if (size_on_cli)
                    *size_on_cli = TRUE;
            }
        }
        if (!geom)
        {
            dlg_warning(NULL, _("Invalid geometry specification"));
        }
    }
    roxterm->maximise = maximise;
    if (colour_scheme_name)
    {
        roxterm->colour_scheme = colour_scheme_lookup_and_ref
            (colour_scheme_name);
    }
    roxterm->encoding = encoding;
    if (encoding && !strcmp(encoding, "Default"))
    {
        g_free(encoding);
        roxterm->encoding = NULL;
    }
    roxterm->pid = -1;
    roxterm->env = global_options_copy_strv(env);
    roxterm->file_match_tag[0] = roxterm->file_match_tag[1] = -1;
    roxterm->style_context = NULL;
    return roxterm;
}

void roxterm_launch(const char *display_name, char **env)
{
    GtkPositionType tab_pos;
    gboolean always_show_tabs;
    char *shortcut_scheme;
    Options *shortcuts;
    char *geom = options_lookup_string(global_options, "geometry");
    gboolean size_on_cli = FALSE;
    char *profile_name =
            global_options_lookup_string_with_default("profile", "Default");
    char *colour_scheme_name =
            global_options_lookup_string_with_default("colour_scheme", "GTK");
    Options *profile = dynamic_options_lookup_and_ref(roxterm_get_profiles(),
            profile_name, "roxterm profile");
    MultiWin *win = NULL;
    ROXTermData *roxterm = roxterm_data_new(display_name,
            global_options_lookup_double("zoom"),
            global_options_directory,
            profile_name, profile,
            global_options_maximise ||
                    options_lookup_int_with_default(profile,
                            "maximise", 0),
            colour_scheme_name,
            global_options_lookup_string("encoding"),
            &geom, &size_on_cli, env);
    int show_add_tab_btn;

    if (!size_on_cli)
    {
        roxterm_default_size_func(roxterm, &roxterm->columns, &roxterm->rows);
    }
    if (global_options_commandv)
    {
        roxterm->commandv = global_options_copy_strv(global_options_commandv);
    }
    tab_pos = roxterm_get_tab_pos(roxterm);
    always_show_tabs = roxterm_get_always_show_tabs(roxterm);
    shortcut_scheme = global_options_lookup_string_with_default
        ("shortcut_scheme", "Default");
    shortcuts = shortcuts_open(shortcut_scheme, FALSE);
    if (global_options_tab)
    {
        ROXTermData *partner;
        char *wtitle = global_options_lookup_string("title");
        MultiWin *best_inactive = NULL;
        MultiWin *best_in_other_ws = NULL;
        GList *link;

        win = NULL;
        for (link = multi_win_all; link; link = g_list_next(link))
        {
            GtkWidget *w;
            guint32 workspace;
            gboolean title_ok;

            win = link->data;
            w = multi_win_get_widget(win);
            title_ok = !wtitle ||
                    !g_strcmp0(multi_win_get_title_template(win), wtitle);
            if (gtk_window_is_active(GTK_WINDOW(w)) && title_ok)
            {
                break;
            }
            if (title_ok)
            {
                if (x11support_get_wm_desktop(gtk_widget_get_window(w),
                    &workspace))
                {
                    if (workspace == global_options_workspace ||
                        workspace == WORKSPACE_ALL)
                    {
                        if (!best_inactive)
                            best_inactive = win;
                    }
                    else if (wtitle)
                    {
                        /* Titles match but window is on wrong workspace */
                        if (!best_in_other_ws)
                            best_in_other_ws = win;
                    }
                }
                else
                {
                    /* There is a window but it hasn't made it onto workspace
                     * yet. This should only happen in things like start-up
                     * scripts so we should treat it as a workspace match. See
       * https://sourceforge.net/p/roxterm/discussion/422639/thread/09ff8e98/
                     * and
       * https://sourceforge.net/p/roxterm/discussion/422638/thread/2cc9a9aa/
                     */
                    if (!best_in_other_ws)
                        best_in_other_ws = win;
                }
            }
            win = NULL;
        }
        if (!win)
        {
            if (best_inactive)
                win = best_inactive;
            else if (best_in_other_ws)
                win = best_in_other_ws;
        }
        partner = win ? multi_win_get_user_data_for_current_tab(win) : NULL;
        if (partner)
        {
            GtkWidget *gwin = multi_win_get_widget(win);

            roxterm->dont_lookup_dimensions = TRUE;
            roxterm->target_zoom_factor = partner->target_zoom_factor;
            roxterm->zoom_index = partner->zoom_index;
            if (partner->pango_desc)
            {
                roxterm->pango_desc =
                        pango_font_description_copy(partner->pango_desc);
                roxterm->current_zoom_factor = partner->current_zoom_factor;
            }
            else
            {
                roxterm->current_zoom_factor = 1.0;
            }
            /* roxterm_data_clone needs to see a widget to get geometry
             * correct */
            roxterm->widget = partner->widget;
            roxterm->tab = partner->tab;
            multi_tab_new(win, roxterm);
            if (gtk_widget_get_visible(gwin))
                gtk_window_present(GTK_WINDOW(gwin));
        }
        else
        {
            global_options_tab = FALSE;
        }
    }

    show_add_tab_btn = options_lookup_int_with_default(roxterm->profile,
            "show_add_tab_btn", 1);
    if (global_options_tab)
    {
        global_options_tab = FALSE;
    }
    else if (global_options_fullscreen ||
            options_lookup_int_with_default(roxterm->profile, "full_screen", 0))
    {
        global_options_fullscreen = FALSE;
        win = multi_win_new_fullscreen(display_name, shortcuts,
                roxterm->zoom_index, roxterm,
                tab_pos, always_show_tabs, show_add_tab_btn);
    }
    else if (roxterm->maximise)
    {
        global_options_maximise = FALSE;
        win = multi_win_new_maximised(display_name, shortcuts,
                roxterm->zoom_index, roxterm,
                tab_pos, always_show_tabs, show_add_tab_btn);
    }
    else
    {
        if (!size_on_cli)
        {
            if (geom)
            {
                char *old_geom = geom;

                geom = g_strdup_printf("%dx%d%s",
                        roxterm->columns, roxterm->rows, old_geom);
                g_free(old_geom);
            }
            else
            {
                geom = g_strdup_printf("%dx%d",
                        roxterm->columns, roxterm->rows);
            }
        }
        win = multi_win_new_with_geom(display_name, shortcuts,
                roxterm->zoom_index, roxterm, geom,
                tab_pos, always_show_tabs, show_add_tab_btn);
    }
    g_free(geom);

    roxterm_data_delete(roxterm);
    g_free(shortcut_scheme);
    g_free(colour_scheme_name);
    g_free(profile_name);
}

static void roxterm_tab_to_new_window(MultiWin *win, MultiTab *tab,
        MultiWin *old_win)
{
    ROXTermData *roxterm = multi_tab_get_user_data(tab);
    ROXTermData *match_roxterm;
    MultiTab *match_tab = multi_win_get_current_tab(win);
    GtkWindow *old_gwin = old_win ?
            GTK_WINDOW(multi_win_get_widget(old_win)) : NULL;

    if (old_gwin)
    {
        g_signal_handler_disconnect(old_gwin, roxterm->win_state_changed_tag);
    }
    roxterm_attach_state_changed_handler(roxterm);
    if (!match_tab)
        return;
    match_roxterm = multi_tab_get_user_data(match_tab);
    if (roxterm == match_roxterm)
    {
        return;
    }
    roxterm_match_text_size(roxterm, match_roxterm);
}

static void roxterm_get_disable_menu_shortcuts(ROXTermData *roxterm,
    gboolean *general, gboolean *tabs)
{
    if (general)
    {
        *general = options_lookup_int_with_default(roxterm->profile,
            "disable_menu_shortcuts", FALSE);
    }
    if (tabs)
    {
        *tabs = options_lookup_int_with_default(roxterm->profile,
            "disable_tab_menu_shortcuts", FALSE);
    }
}

typedef struct {
    guint ntabs;
    int warn;
    gboolean only_running;
} DontShowData;

static void dont_show_again_toggled(GtkToggleButton *button, DontShowData *d)
{
    int val = 0;

    if (gtk_toggle_button_get_active(button))
    {
        if (!d->ntabs)
            val = 2;
        else if (d->ntabs > 1)
            val = 0;
        else
            val = 1;
    }
    else
    {
        val = d->warn;
    }
    options_set_int(global_options, "warn_close", val);
    options_file_save(global_options->kf, "Global");
}

static void only_running_toggled(GtkToggleButton *button, DontShowData *d)
{
    (void) d;
    options_set_int(global_options, "only_warn_running",
            gtk_toggle_button_get_active(button));
    options_file_save(global_options->kf, "Global");
}

static void check_each_tab_running(MultiTab *tab, void *data)
{
    if (((ROXTermData *) multi_tab_get_user_data(tab))->running)
    {
        gboolean *prunning = data;

        *prunning = TRUE;
    }
}

static gboolean roxterm_delete_handler(GtkWindow *gtkwin, GdkEvent *event,
        gpointer data)
{
    GtkWidget *dialog;
    GtkWidget *noshow;
    GtkWidget *only_running;
    gboolean response;
    DontShowData d;
    const char *msg;
    MultiWin *win = event ? data : NULL;
    ROXTermData *roxterm = event ? NULL : data;
    GtkWidget *ca_box;

    d.warn = global_options_lookup_int_with_default("warn_close", 3);
    d.only_running = global_options_lookup_int_with_default("only_warn_running",
            FALSE);
    d.ntabs = win ? multi_win_get_ntabs(win) : 0;
    if ((!win && d.warn < 3) || !d.warn || (d.warn == 1 && d.ntabs <= 1))
        return FALSE;
    if (d.only_running)
    {
        if (win)
        {
            gboolean running = FALSE;

            multi_win_foreach_tab(win, check_each_tab_running, &running);
            if (!running)
                return FALSE;
        }
        else if (!roxterm->running)
        {
            return FALSE;
        }
    }

    switch (d.ntabs)
    {
        case 0:
            msg = _("Closing this ROXTerm tab may cause loss of data. "
                    "Are you sure you want to continue?");
            break;
        case 1:
            msg = _("You are about to close a ROXTerm window; this may cause "
                    "loss of data. Are you sure you want to continue?");
            break;
        default:
            msg = _("You are about to close a window containing multiple "
                    "ROXTerm tabs; this may cause loss of data. Are you sure "
                    "you want to continue?");
            break;
    }
    dialog = gtk_message_dialog_new(gtkwin,
            GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO,
            "%s", msg);
    gtk_window_set_title(GTK_WINDOW(dialog), _("ROXTerm: Confirm close"));

    ca_box = gtk_dialog_get_content_area(GTK_DIALOG(dialog));

    noshow = gtk_check_button_new_with_mnemonic(_("_Don't show this again"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(noshow), FALSE);
    g_signal_connect(noshow, "toggled",
            G_CALLBACK(dont_show_again_toggled), &d);
    box_compat_packv(ca_box, noshow, FALSE, 0);
    gtk_widget_show(noshow);

    only_running = gtk_check_button_new_with_mnemonic(
            _("Only warn if tasks are still _running"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(only_running),
            d.only_running);
    g_signal_connect(only_running, "toggled",
            G_CALLBACK(only_running_toggled), &d);
    box_compat_packv(ca_box, only_running, FALSE, 0);
    gtk_widget_show(only_running);

    response = gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_YES;
    gtk_widget_destroy(dialog);
    return response;
}

void roxterm_init(void)
{
    char *logo_filename = logo_find();

    gtk_window_set_default_icon_from_file(logo_filename, NULL);
    g_free(logo_filename);

    roxterm_encodings = encodings_load();

    optsdbus_listen_for_opt_signals(roxterm_opt_signal_handler);
    optsdbus_listen_for_stuff_changed_signals(roxterm_stuff_changed_handler);
    optsdbus_listen_for_set_profile_signals(
            (OptsDBusSetProfileHandler) roxterm_set_profile_handler);
    optsdbus_listen_for_set_colour_scheme_signals(
            (OptsDBusSetProfileHandler) roxterm_set_colour_scheme_handler);
    optsdbus_listen_for_set_shortcut_scheme_signals(
            (OptsDBusSetProfileHandler) roxterm_set_shortcut_scheme_handler);

    multi_tab_init((MultiTabFiller) roxterm_multi_tab_filler,
        (MultiTabDestructor) roxterm_multi_tab_destructor,
        roxterm_connect_menu_signals,
        (MultiWinGeometryFunc) roxterm_geometry_func,
        (MultiWinSizeFunc) roxterm_size_func,
        (MultiWinDefaultSizeFunc) roxterm_default_size_func,
        roxterm_tab_to_new_window,
        (MultiWinZoomHandler) roxterm_set_zoom_factor,
        (MultiWinGetDisableMenuShortcuts) roxterm_get_disable_menu_shortcuts,
        (MultiWinGetTabPos) roxterm_get_tab_pos,
        (MultiWinDeleteHandler) roxterm_delete_handler,
        (MultiTabGetShowCloseButton) roxterm_get_show_tab_close_button,
        (MultiTabGetNewTabAdjacent) roxterm_get_new_tab_adjacent
        );
}

gboolean roxterm_spawn_command_line(const gchar *command_line,
        const char *display_name, const char *cwd, char **env, GError **error)
{
    char **env2 = NULL;
    int argc;
    char **argv = NULL;
    gboolean result;

    cwd = roxterm_check_cwd(cwd);
    if (display_name)
    {
        char const *disp[3];
        disp[0] = "DISPLAY";
        disp[1] = display_name;
        disp[2] = NULL;
        env2 = roxterm_modify_environment((char const * const *) env, disp);
        env = env2;
    }
    result = g_shell_parse_argv(command_line, &argc, &argv, error);
    if (result)
    {
        result = g_spawn_async(cwd, argv, env, G_SPAWN_SEARCH_PATH,
                NULL, NULL, NULL, error);
    }
    if (env2)
        g_strfreev(env2);
    if (argv)
        g_strfreev(argv);
    return result;
}

void roxterm_spawn(ROXTermData *roxterm, const char *command,
    ROXTerm_SpawnType spawn_type)
{
    GError *error = NULL;
    GtkPositionType tab_pos;
    char *cwd;
    MultiWin *win = roxterm_get_win(roxterm);

    tab_pos = roxterm_get_tab_pos(roxterm);
    switch (spawn_type)
    {
        case ROXTerm_SpawnNewWindow:
            roxterm->special_command = g_strdup(command);
            roxterm->no_respawn = TRUE;
            multi_win_new(roxterm->display_name,
                    multi_win_get_shortcut_scheme(win),
                    roxterm->zoom_index, roxterm, tab_pos,
                    multi_win_get_always_show_tabs(win),
                    options_lookup_int_with_default(roxterm->profile,
                            "show_add_tab_btn", 1));
            break;
        case ROXTerm_SpawnNewTab:
            roxterm->special_command = g_strdup(command);
            roxterm->no_respawn = TRUE;
            multi_tab_new(win, roxterm);
            break;
        default:
            cwd = roxterm_get_cwd(roxterm);
            roxterm_spawn_command_line(command, roxterm->display_name,
                    cwd, roxterm->env, &error);
            if (error)
            {
                dlg_warning(roxterm_get_toplevel(roxterm),
                        _("Unable to spawn command %s: %s"),
                    command, error->message);
                g_error_free(error);
            }
            g_free(cwd);
            break;
    }
}

VteTerminal *roxterm_get_vte_terminal(ROXTermData *roxterm)
{
    return VTE_TERMINAL(roxterm->widget);
}

static const char *get_options_leafname(Options *opts)
{
    char *slash = strrchr(opts->name, G_DIR_SEPARATOR);

    return slash ? slash + 1 : opts->name;
}

const char *roxterm_get_profile_name(ROXTermData *roxterm)
{
    return get_options_leafname(roxterm->profile);
}

const char *roxterm_get_colour_scheme_name(ROXTermData *roxterm)
{
    return get_options_leafname(roxterm->colour_scheme);
}

const char *roxterm_get_shortcuts_scheme_name(ROXTermData *roxterm)
{
    return get_options_leafname(roxterm->profile);
}

void roxterm_get_nonfs_dimensions(ROXTermData *roxterm, int *cols, int *rows)
{
    /* FIXME: This gets current dimensions whether we're full-screen/maximised
     * or not, but this is for session management so presumably manager will
     * remember size anyway.
     */
    VteTerminal *vte = VTE_TERMINAL(roxterm->widget);

    *cols = vte_terminal_get_column_count(vte);
    *rows = vte_terminal_get_row_count(vte);
}

double roxterm_get_zoom_factor(ROXTermData *roxterm)
{
    return roxterm->current_zoom_factor;
}

char const * const *roxterm_get_actual_commandv(ROXTermData *roxterm)
{
    return (char const * const *) roxterm->actual_commandv;
}

#if ENABLE_SM

typedef struct {
    const char *client_id;
    gboolean session_tag_open;
    MultiWin *win;
    MultiTab *tab;
    ROXTermData *roxterm;
    gboolean maximised;
    gboolean fullscreen;
    char *disp;
    char *geom;
    int width, height;
    PangoFontDescription *fdesc;
    char *window_title;
    char *window_title_template;
    double zoom_factor;
    char *title;
    char *role;
    char **commandv;
    int argc;
    int argn;
    char *tab_name;
    char *tab_title_template;
    char *tab_title;
    char *icon_title;
    gboolean win_title_template_locked;
    gboolean tab_title_template_locked;
    gboolean current;
    MultiTab *active_tab;
} _ROXTermParseContext;

static void parse_open_win(_ROXTermParseContext *rctx,
        const char **attribute_names, const char **attribute_values,
        GError **error)
{
    int n;
    const char *disp = NULL;
    const char *geom = NULL;
    const char *role = NULL;
    const char *font = NULL;
    const char *shortcuts_name = NULL;
    const char *title_template = NULL;
    const char *title = NULL;
    gboolean show_mbar = TRUE;
    gboolean show_tabs = FALSE;
    gboolean show_add_tab_btn = TRUE;
    gboolean disable_menu_shortcuts = FALSE;
    gboolean disable_tab_shortcuts = FALSE;
    GtkPositionType tab_pos = GTK_POS_TOP;
    MultiWin *win;
    GtkWindow *gwin;
    Options *shortcuts;

    rctx->fullscreen = FALSE;
    rctx->maximised = FALSE;
    rctx->zoom_factor = 1.0;
    rctx->active_tab = NULL;
    rctx->win_title_template_locked = FALSE;
    for (n = 0; attribute_names[n]; ++n)
    {
        const char *a = attribute_names[n];
        const char *v = attribute_values[n];

        if (!strcmp(a, "disp"))
            rctx->disp = g_strdup(v);
        else if (!strcmp(a, "geometry"))
        {
            rctx->geom = g_strdup(v);
            sscanf(v, "%dx%d+", &rctx->width, &rctx->height);
        }
        else if (!strcmp(a, "title_template"))
            title_template = v;
        else if (!strcmp(a, "font"))
            font = v;
        else if (!strcmp(a, "title"))
            title = v;
        else if (!strcmp(a, "role"))
            role = v;
        /* xclass and xname are obsolete in <window> so silently ignore */
        else if (!strcmp(a, "xclass"))
            continue;
        else if (!strcmp(a, "xname"))
            continue;
        else if (!strcmp(a, "shortcut_scheme"))
            shortcuts_name = v;
        else if (!strcmp(a, "show_menubar"))
            show_mbar = (strcmp(v, "0") != 0);
        else if (!strcmp(a, "always_show_tabs"))
            show_tabs = strcmp(v, "0");
        else if (!strcmp(a, "tab_pos"))
            tab_pos = atoi(v);
        else if (!strcmp(a, "show_add_tab_btn"))
            show_add_tab_btn = strcmp(v, "0");
        else if (!strcmp(a, "disable_menu_shortcuts"))
            disable_menu_shortcuts = (strcmp(v, "0") != 0);
        else if (!strcmp(a, "disable_tab_shortcuts"))
            disable_tab_shortcuts = (strcmp(v, "0") != 0);
        else if (!strcmp(a, "maximised"))
            rctx->maximised = (strcmp(v, "0") != 0);
        else if (!strcmp(a, "fullscreen"))
            rctx->fullscreen = (strcmp(v, "0") != 0);
        else if (!strcmp(a, "zoom"))
            rctx->zoom_factor = atof(v);
        else if (!strcmp(a, "title_template_locked"))
            rctx->win_title_template_locked = atoi(v);
        else
        {
            *error = g_error_new(G_MARKUP_ERROR,
                    G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
                    _("Unknown <window> attribute '%s'"), a);
            return;
        }
    }
    SLOG("Opening window with title %s", title);

    shortcuts = shortcuts_open(shortcuts_name, FALSE);
    rctx->win = win = multi_win_new_blank(disp, shortcuts,
            multi_win_get_nearest_index_for_zoom(rctx->zoom_factor),
            disable_menu_shortcuts, disable_tab_shortcuts, tab_pos, show_tabs,
            show_add_tab_btn);
    shortcuts_unref(shortcuts);
    gwin = GTK_WINDOW(multi_win_get_widget(rctx->win));
    /* Set role and title before and after adding tabs because docs are quite
     * vague about how these are used for restoring the session.
     */
    if (role && role[0])
    {
        rctx->role = g_strdup(role);
        gtk_window_set_role(gwin, role);
    }
    if (title_template && title_template[0])
    {
        rctx->window_title_template = g_strdup(title_template);
        multi_win_set_title_template(win, title_template);
    }
    if (rctx->window_title)
    {
        multi_win_set_title(win, rctx->window_title);
    }
    if (geom && geom[0])
    {
        rctx->geom = g_strdup(geom);
    }
    if (font && font[0])
    {
        rctx->fdesc = pango_font_description_from_string(font);
        resize_pango_for_zoom(rctx->fdesc, rctx->zoom_factor);
    }
    if (disp && disp[0])
        rctx->disp = g_strdup(disp);
    multi_win_set_show_menu_bar(win, show_mbar);
    multi_win_set_always_show_tabs(win, show_tabs);
    if (title && title[0])
        rctx->window_title = g_strdup(title);
}

static void close_win_tag(_ROXTermParseContext *rctx)
{
    if (!multi_win_get_num_tabs(rctx->win))
    {
        g_warning(_("Session contains window with no valid tabs"));
        multi_win_delete(rctx->win);
    }
    else
    {
        GtkWindow *gwin = GTK_WINDOW(multi_win_get_widget(rctx->win));

        if (rctx->fullscreen)
        {
            multi_win_set_fullscreen(rctx->win, TRUE);
        }
        else if (rctx->maximised)
        {
            gtk_window_maximize(gwin);
        }
        else if (rctx->geom)
        {
            /* Need to show children before parsing geom */
            gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(gwin)));
            gtk_window_parse_geometry(gwin, rctx->geom);
        }
        if (rctx->window_title_template)
        {
            multi_win_set_title_template(rctx->win,
                    rctx->window_title_template);
        }
        if (rctx->role)
            gtk_window_set_role(gwin, rctx->role);
        if (rctx->window_title)
            multi_win_set_title(rctx->win, rctx->window_title);
        multi_win_set_title_template_locked(rctx->win,
                rctx->win_title_template_locked);
        multi_win_show(rctx->win);
        if (rctx->active_tab)
        {
            multi_win_select_tab(rctx->win, rctx->active_tab);
            /*
            roxterm_tab_selection_handler(
                    multi_tab_get_user_data(rctx->active_tab),
                    rctx->active_tab);
            */
        }
    }
    rctx->win = NULL;
    g_free(rctx->role);
    rctx->role = NULL;
    g_free(rctx->window_title);
    rctx->window_title = NULL;
    g_free(rctx->window_title_template);
    rctx->window_title_template = NULL;
    if (rctx->fdesc)
    {
        pango_font_description_free(rctx->fdesc);
        rctx->fdesc = NULL;
    }
    g_free(rctx->disp);
    rctx->disp = NULL;
    g_free(rctx->geom);
    rctx->geom = NULL;
}

extern char **environ;

static void parse_open_tab(_ROXTermParseContext *rctx,
        const char **attribute_names, const char **attribute_values,
        GError **error)
{
    const char *profile_name = "Default";
    const char *colours_name = "GTK";
    const char *cwd = NULL;
    const char *encoding = NULL;
    int n;
    ROXTermData *roxterm;
    Options *profile;

    rctx->current = FALSE;
    rctx->tab_title_template_locked = FALSE;
    for (n = 0; attribute_names[n]; ++n)
    {
        const char *a = attribute_names[n];
        const char *v = attribute_values[n];

        if (!strcmp(a, "profile"))
            profile_name = v;
        else if (!strcmp(a, "colour_scheme"))
            colours_name = v;
        else if (!strcmp(a, "cwd"))
            cwd = v;
        else if (!strcmp(a, "title_template"))
            rctx->tab_title_template = g_strdup(v);
        else if (!strcmp(a, "window_title"))
            rctx->tab_title = g_strdup(v);
        else if (!strcmp(a, "icon_title"))
            rctx->icon_title = g_strdup(v);
        else if (!strcmp(a, "encoding"))
            encoding = v;
        else if (!strcmp(a, "current"))
            rctx->current = (strcmp(v, "0") != 0);
        else if (!strcmp(a, "title_template_locked"))
            rctx->tab_title_template_locked = atoi(v);
        else
        {
            *error = g_error_new(G_MARKUP_ERROR,
                    G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
                    _("Unknown <tab> attribute '%s'"), a);
            return;
        }
    }
    SLOG("Loading tab with icon_title %s, cwd %s", rctx->icon_title, cwd);

    profile = dynamic_options_lookup_and_ref(roxterm_get_profiles(),
            profile_name, "roxterm profile");
    roxterm = roxterm_data_new(rctx->disp,
            rctx->zoom_factor, cwd, g_strdup(profile_name), profile,
            rctx->maximised, colours_name, g_strdup(encoding),
            &rctx->geom, NULL, environ);
    roxterm->from_session = TRUE;
    roxterm->dont_lookup_dimensions = TRUE;
    if (rctx->fdesc)
        roxterm->pango_desc = pango_font_description_copy(rctx->fdesc);
    if (encoding && encoding[0])
        roxterm->encoding = g_strdup(encoding);
    rctx->roxterm = roxterm;
}

static void close_tab_tag(_ROXTermParseContext *rctx)
{
    ROXTermData *roxterm = rctx->roxterm;

    if (rctx->commandv)
    {
        if (rctx->commandv[0])
        {
            roxterm->commandv = rctx->commandv;
        }
        else
        {
            g_warning(_("<command> with no arguments"));
        }
    }
    rctx->commandv = NULL;
    rctx->tab = multi_tab_new(rctx->win, roxterm);
    if (rctx->current)
        rctx->active_tab = rctx->tab;
    if (rctx->tab_title_template && rctx->tab_title_template[0])
    {
        multi_tab_set_window_title_template(rctx->tab,
                rctx->tab_title_template);
    }
    if (rctx->tab_title && rctx->tab_title[0])
        multi_tab_set_window_title(rctx->tab, rctx->tab_title);
    if (rctx->icon_title && rctx->icon_title[0])
        multi_tab_set_icon_title(rctx->tab, rctx->icon_title);
    multi_tab_set_title_template_locked(rctx->tab,
            rctx->tab_title_template_locked);
    g_free(rctx->tab_title_template);
    rctx->tab_title_template = NULL;
    g_free(rctx->tab_title);
    rctx->tab_title = NULL;
    g_free(rctx->icon_title);
    rctx->icon_title = NULL;
    roxterm_data_delete(roxterm);
    /*
    roxterm = multi_tab_get_user_data(rctx->tab);
    vte = VTE_TERMINAL(roxterm->widget);
    roxterm_set_vte_size(roxterm, vte, rctx->width, rctx->height);
    */
}

static void parse_open_command(_ROXTermParseContext *rctx,
        const char **attribute_names, const char **attribute_values,
        GError **error)
{
    int n;

    rctx->argn = 0;
    for (n = 0; attribute_names[n]; ++n)
    {
        const char *a = attribute_names[n];
        const char *v = attribute_values[n];

        if (!strcmp(a, "argc"))
            rctx->argc = atoi(v);
        else
        {
            *error = g_error_new(G_MARKUP_ERROR,
                    G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
                    _("Unknown <command> attribute '%s'"), a);
            return;
        }
    }
    if (rctx->argc)
    {
        rctx->commandv = g_new0(char *, rctx->argc + 1);
    }
    else
    {
        *error = g_error_new(G_MARKUP_ERROR,
                G_MARKUP_ERROR_MISSING_ATTRIBUTE,
                _("<command> missing argc attribute"));
    }
}

static void parse_open_arg(_ROXTermParseContext *rctx,
        const char **attribute_names, const char **attribute_values,
        GError **error)
{
    int n;
    const char *arg = NULL;

    if (rctx->argn >= rctx->argc)
    {
        *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                _("Too many <arg> elements for <command argc='%d>"),
                rctx->argc);
        return;
    }
    for (n = 0; attribute_names[n]; ++n)
    {
        const char *a = attribute_names[n];
        const char *v = attribute_values[n];

        if (!strcmp(a, "s"))
        {
            arg = v;
        }
        else
        {
            *error = g_error_new(G_MARKUP_ERROR,
                    G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
                    _("Unknown <arg> attribute '%s'"), a);
            return;
        }
    }
    if (arg)
    {
        rctx->commandv[(rctx->argn)++] = g_strdup(arg);
    }
    else
    {
        *error = g_error_new(G_MARKUP_ERROR,
                G_MARKUP_ERROR_MISSING_ATTRIBUTE,
                _("<arg> missing s attribute"));
    }
}

static void parse_roxterm_session_tag(_ROXTermParseContext *rctx,
        const char **attribute_names, const char **attribute_values,
        GError **error)
{
    int n;
    (void) rctx;
    (void) attribute_values;

    for (n = 0; attribute_names[n]; ++n)
    {
        const char *a = attribute_names[n];

        if (strcmp(a, "id"))
        {
            *error = g_error_new(G_MARKUP_ERROR,
                    G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
                    _("Unknown <roxterm_session> attribute '%s'"), a);
            return;
        }
    }
}

static void parse_start_element(GMarkupParseContext *context,
        const char *element_name,
        const char **attribute_names, const char **attribute_values,
        gpointer handle, GError **error)
{
    _ROXTermParseContext *rctx = handle;
    (void) context;

    if (!strcmp(element_name, "roxterm_session"))
    {
        if (rctx->win)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<roxterm_session> tag nested within <window>"));
        }
        else
        {
            rctx->session_tag_open = TRUE;
            parse_roxterm_session_tag(rctx,
                    attribute_names, attribute_values, error);
        }
    }
    else if (!strcmp(element_name, "window"))
    {
        if (!rctx->session_tag_open)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<window> tag not within <roxterm_session>"));
        }
        else if (rctx->win)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<window> tag nested within another <window>"));
        }
        else
        {
            parse_open_win(rctx, attribute_names, attribute_values, error);
        }
    }
    else if (!strcmp(element_name, "tab"))
    {
        if (!rctx->win)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<tab> tag not within <window>"));
        }
        else if (rctx->roxterm)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<tab> tag nested within another <tab>"));
        }
        else
        {
            parse_open_tab(rctx, attribute_names, attribute_values, error);
        }
    }
    else if (!strcmp(element_name, "command"))
    {
        if (!rctx->roxterm)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<command> tag not within <tab>"));
        }
        else if (rctx->commandv)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<command> tag nested within another <command>"));
        }
        else
        {
            parse_open_command(rctx, attribute_names, attribute_values, error);
        }
    }
    else if (!strcmp(element_name, "arg"))
    {
        if (!rctx->commandv)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("<arg> tag not within <command>"));
        }
        else
        {
            parse_open_arg(rctx, attribute_names, attribute_values, error);
        }
    }
    else
    {
        *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
                _("Unknown element <%s>"), element_name);
    }
}

static void parse_end_element(GMarkupParseContext *context,
        const char *element_name,
        gpointer handle, GError **error)
{
    _ROXTermParseContext *rctx = handle;
    (void) context;

    if (!strcmp(element_name, "roxterm_session"))
    {
        if (!rctx->session_tag_open)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("Unmatched </roxterm_session>"));
        }
        else if (rctx->win)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("Attempt to close <roxterm_session> without "
                    "closing <window>"));
        }
        else
        {
            rctx->session_tag_open = FALSE;
        }
    }
    else if (!strcmp(element_name, "window"))
    {
        if (!rctx->win)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("Unmatched </window>"));
        }
        else if (rctx->roxterm)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("Attempt to close <window> without closing <tab>"));
        }
        else
        {
            close_win_tag(rctx);
        }
    }
    else if (!strcmp(element_name, "tab"))
    {
        if (!rctx->roxterm)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("Unmatched </tab>"));
        }
        else
        {
            close_tab_tag(rctx);
            rctx->tab = NULL;
            rctx->roxterm = NULL;
        }
    }
    else if (!strcmp(element_name, "command"))
    {
        if (!rctx->commandv)
        {
            *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
                    _("Unmatched </command>"));
        }
    }
    else if (strcmp(element_name, "arg"))
    {
        *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
                _("Unknown closing element </%s>"), element_name);
    }
}

static void parse_error(GMarkupParseContext *context, GError *error,
        gpointer handle)
{
    _ROXTermParseContext *rctx = handle;
    (void) context;

    g_warning(_("Unable to parse data for session '%s': %s"),
            rctx->client_id, error->message);
}

gboolean roxterm_load_session(const char *xml, gssize len,
        const char *client_id)
{
    GMarkupParser pvtbl = { parse_start_element, parse_end_element,
            NULL, NULL, parse_error };
    _ROXTermParseContext *rctx = g_new0(_ROXTermParseContext, 1);
    GMarkupParseContext *pctx = g_markup_parse_context_new(&pvtbl,
            G_MARKUP_PREFIX_ERROR_POSITION, rctx, NULL);
    gboolean result;
    GError *error = NULL;

    rctx->client_id = client_id;
    result = g_markup_parse_context_parse(pctx, xml, len, &error);
    if (!error)
        result = g_markup_parse_context_end_parse(pctx, &error) & result;
    if (error)
    {
        g_error_free(error);
        error = NULL;
    }
    SLOG("Session loaded, result %d", result);
    return result;
}
#endif /* ENABLE_SM */

MultiWin *roxterm_get_multi_win(ROXTermData *roxterm)
{
    return roxterm_get_win(roxterm);
}

VteTerminal *roxterm_get_vte(ROXTermData *roxterm)
{
    return VTE_TERMINAL(roxterm->widget);
}

gboolean roxterm_set_search(ROXTermData *roxterm,
        const char *pattern, guint flags, GError **error)
{
    GRegex *regex = NULL;
    char *cooked_pattern = NULL;

    roxterm->search_flags = flags;

    if (roxterm->search_pattern)
    {
        g_free(roxterm->search_pattern);
        roxterm->search_pattern = NULL;
    }

    if (pattern && pattern[0])
    {
        roxterm->search_pattern = g_strdup(pattern);
        if (!(flags & ROXTERM_SEARCH_AS_REGEX))
        {
            cooked_pattern = g_regex_escape_string(pattern, -1);
            pattern = cooked_pattern;
        }
        if (flags & ROXTERM_SEARCH_ENTIRE_WORD)
        {
            char *tmp = cooked_pattern;

            cooked_pattern = g_strdup_printf("\\<%s\\>", pattern);
            pattern = cooked_pattern;
            g_free(tmp);
        }

        regex = g_regex_new(pattern,
                ((flags & ROXTERM_SEARCH_MATCH_CASE) ? 0 : G_REGEX_CASELESS) |
                        G_REGEX_OPTIMIZE,
                G_REGEX_MATCH_NOTEMPTY,
                error);
        g_free(cooked_pattern);
        if (!regex)
            return FALSE;
    }

    vte_terminal_search_set_gregex(VTE_TERMINAL(roxterm->widget), regex, 0);
    vte_terminal_search_set_wrap_around(VTE_TERMINAL(roxterm->widget),
            flags & ROXTERM_SEARCH_WRAP);
    roxterm_shade_search_menu_items(roxterm);
    return TRUE;
}

const char *roxterm_get_search_pattern(ROXTermData *roxterm)
{
    return roxterm->search_pattern;
}

guint roxterm_get_search_flags(ROXTermData *roxterm)
{
    return roxterm->search_flags;
}

/* vi:set sw=4 ts=4 et cindent cino= */
