diff options
Diffstat (limited to 'src/sakura.c')
-rw-r--r-- | src/sakura.c | 3536 |
1 files changed, 3536 insertions, 0 deletions
diff --git a/src/sakura.c b/src/sakura.c new file mode 100644 index 0000000..e72a9aa --- /dev/null +++ b/src/sakura.c @@ -0,0 +1,3536 @@ +/******************************************************************************* + * Filename: sakura.c + * Description: VTE-based terminal emulator + * + * Copyright (C) 2006-2012 David Gómez <david@pleyades.net> + * Copyright (C) 2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <wchar.h> +#include <math.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <locale.h> +#include <libintl.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <pango/pango.h> +#include <vte/vte.h> + +#define _(String) gettext(String) +#define N_(String) (String) +#define GETTEXT_PACKAGE "sakura" + +#define SAY(format,...) do {\ + if (strcmp("Debug", BUILDTYPE)==0) {\ + fprintf(stderr, "[%d] ", getpid());\ + fprintf(stderr, "[%s] ", __FUNCTION__);\ + if (format) fprintf(stderr, format, ##__VA_ARGS__);\ + fputc('\n', stderr);\ + fflush(stderr);\ + }\ +} while (0) + +#define PALETTE_SIZE 16 + +/* 16 color palettes in GdkRGBA format (red, green, blue, alpha) + * Text displayed in the first 8 colors (0-7) is meek (uses thin strokes). + * Text displayed in the second 8 colors (8-15) is bold (uses thick strokes). */ + +const GdkRGBA gruvbox_palette[PALETTE_SIZE] = { + {0.156863, 0.156863, 0.156863, 1.000000}, + {0.800000, 0.141176, 0.113725, 1.000000}, + {0.596078, 0.592157, 0.101961, 1.000000}, + {0.843137, 0.600000, 0.129412, 1.000000}, + {0.270588, 0.521569, 0.533333, 1.000000}, + {0.694118, 0.384314, 0.525490, 1.000000}, + {0.407843, 0.615686, 0.415686, 1.000000}, + {0.658824, 0.600000, 0.517647, 1.000000}, + {0.572549, 0.513725, 0.454902, 1.000000}, + {0.984314, 0.286275, 0.203922, 1.000000}, + {0.721569, 0.733333, 0.149020, 1.000000}, + {0.980392, 0.741176, 0.184314, 1.000000}, + {0.513725, 0.647059, 0.596078, 1.000000}, + {0.827451, 0.525490, 0.607843, 1.000000}, + {0.556863, 0.752941, 0.486275, 1.000000}, + {0.921569, 0.858824, 0.698039, 1.000000}, +}; + +const GdkRGBA tango_palette[PALETTE_SIZE] = { + {0, 0, 0, 1}, + {0.8, 0, 0, 1}, + {0.305882, 0.603922, 0.023529, 1}, + {0.768627, 0.627451, 0, 1}, + {0.203922, 0.396078, 0.643137, 1}, + {0.458824, 0.313725, 0.482353, 1}, + {0.0235294,0.596078, 0.603922, 1}, + {0.827451, 0.843137, 0.811765, 1}, + {0.333333, 0.341176, 0.32549, 1}, + {0.937255, 0.160784, 0.160784, 1}, + {0.541176, 0.886275, 0.203922, 1}, + {0.988235, 0.913725, 0.309804, 1}, + {0.447059, 0.623529, 0.811765, 1}, + {0.678431, 0.498039, 0.658824, 1}, + {0.203922, 0.886275, 0.886275, 1}, + {0.933333, 0.933333, 0.92549, 1} +}; + +const GdkRGBA linux_palette[PALETTE_SIZE] = { + {0, 0, 0, 1}, + {0.666667, 0, 0, 1}, + {0, 0.666667, 0, 1}, + {0.666667, 0.333333, 0, 1}, + {0, 0, 0.666667, 1}, + {0.666667, 0, 0.666667, 1}, + {0, 0.666667, 0.666667, 1}, + {0.666667, 0.666667, 0.666667, 1}, + {0.333333, 0.333333, 0.333333, 1}, + {1, 0.333333, 0.333333, 1}, + {0.333333, 1, 0.333333, 1}, + {1, 1, 0.333333, 1}, + {0.333333, 0.333333, 1, 1}, + {1, 0.333333, 1, 1}, + {0.333333, 1, 1, 1}, + {1, 1, 1, 1} +}; + +const GdkRGBA solarized_dark_palette[PALETTE_SIZE] = { + {0.027451, 0.211765, 0.258824, 1}, + {0.862745, 0.196078, 0.184314, 1}, + {0.521569, 0.600000, 0.000000, 1}, + {0.709804, 0.537255, 0.000000, 1}, + {0.149020, 0.545098, 0.823529, 1}, + {0.827451, 0.211765, 0.509804, 1}, + {0.164706, 0.631373, 0.596078, 1}, + {0.933333, 0.909804, 0.835294, 1}, + {0.000000, 0.168627, 0.211765, 1}, + {0.796078, 0.294118, 0.086275, 1}, + {0.345098, 0.431373, 0.458824, 1}, + {0.396078, 0.482353, 0.513725, 1}, + {0.513725, 0.580392, 0.588235, 1}, + {0.423529, 0.443137, 0.768627, 1}, + {0.576471, 0.631373, 0.631373, 1}, + {0.992157, 0.964706, 0.890196, 1} +#if 0 + { 0, 0x0707, 0x3636, 0x4242 }, // 0 base02 black (used as background color) + { 0, 0xdcdc, 0x3232, 0x2f2f }, // 1 red + { 0, 0x8585, 0x9999, 0x0000 }, // 2 green + { 0, 0xb5b5, 0x8989, 0x0000 }, // 3 yellow + { 0, 0x2626, 0x8b8b, 0xd2d2 }, // 4 blue + { 0, 0xd3d3, 0x3636, 0x8282 }, // 5 magenta + { 0, 0x2a2a, 0xa1a1, 0x9898 }, // 6 cyan + { 0, 0xeeee, 0xe8e8, 0xd5d5 }, // 7 base2 white (used as foreground color) + { 0, 0x0000, 0x2b2b, 0x3636 }, // 8 base3 bright black + { 0, 0xcbcb, 0x4b4B, 0x1616 }, // 9 orange + { 0, 0x5858, 0x6e6e, 0x7575 }, // 10 base01 bright green + { 0, 0x6565, 0x7b7b, 0x8383 }, // 11 base00 bright yellow + { 0, 0x8383, 0x9494, 0x9696 }, // 12 base0 brigth blue + { 0, 0x6c6c, 0x7171, 0xc4c4 }, // 13 violet + { 0, 0x9393, 0xa1a1, 0xa1a1 }, // 14 base1 cyan + { 0, 0xfdfd, 0xf6f6, 0xe3e3 } // 15 base3 white +#endif +}; + +const GdkRGBA solarized_light_palette[PALETTE_SIZE] = { + {0.933333, 0.909804, 0.835294, 1}, + {0.862745, 0.196078, 0.184314, 1}, + {0.521569, 0.600000, 0.000000, 1}, + {0.709804, 0.537255, 0.000000, 1}, + {0.149020, 0.545098, 0.823529, 1}, + {0.827451, 0.211765, 0.509804, 1}, + {0.164706, 0.631373, 0.596078, 1}, + {0.027451, 0.211765, 0.258824, 1}, + {0.992157, 0.964706, 0.890196, 1}, + {0.796078, 0.294118, 0.086275, 1}, + {0.576471, 0.631373, 0.631373, 1}, + {0.513725, 0.580392, 0.588235, 1}, + {0.396078, 0.482353, 0.513725, 1}, + {0.423529, 0.443137, 0.768627, 1}, + {0.345098, 0.431373, 0.458824, 1}, + {0.000000, 0.168627, 0.211765, 1} +#if 0 + { 0, 0xeeee, 0xe8e8, 0xd5d5 }, // 0 S_base2 + { 0, 0xdcdc, 0x3232, 0x2f2f }, // 1 S_red + { 0, 0x8585, 0x9999, 0x0000 }, // 2 S_green + { 0, 0xb5b5, 0x8989, 0x0000 }, // 3 S_yellow + { 0, 0x2626, 0x8b8b, 0xd2d2 }, // 4 S_blue + { 0, 0xd3d3, 0x3636, 0x8282 }, // 5 S_magenta + { 0, 0x2a2a, 0xa1a1, 0x9898 }, // 6 S_cyan + { 0, 0x0707, 0x3636, 0x4242 }, // 7 S_base02 + { 0, 0xfdfd, 0xf6f6, 0xe3e3 }, // 8 S_base3 + { 0, 0xcbcb, 0x4b4B, 0x1616 }, // 9 S_orange + { 0, 0x9393, 0xa1a1, 0xa1a1 }, // 10 S_base1 + { 0, 0x8383, 0x9494, 0x9696 }, // 11 S_base0 + { 0, 0x6565, 0x7b7b, 0x8383 }, // 12 S_base00 + { 0, 0x6c6c, 0x7171, 0xc4c4 }, // 13 S_violet + { 0, 0x5858, 0x6e6e, 0x7575 }, // 14 S_base01 + { 0, 0x0000, 0x2b2b, 0x3636 } // 15 S_base03 +#endif +}; + + +const GdkRGBA xterm_palette[PALETTE_SIZE] = { + {0, 0, 0, 1}, + {0.803922, 0, 0, 1}, + {0, 0.803922, 0, 1}, + {0.803922, 0.803922, 0, 1}, + {0.117647, 0.564706, 1, 1}, + {0.803922, 0, 0.803922, 1}, + {0, 0.803922, 0.803922, 1}, + {0.898039, 0.898039, 0.898039, 1}, + {0.298039, 0.298039, 0.298039, 1}, + {1, 0, 0, 1}, + {0, 1, 0, 1}, + {1, 1, 0, 1}, + {0.27451, 0.509804, 0.705882, 1}, + {1, 0, 1, 1}, + {0, 1, 1, 1}, + {1, 1, 1, 1} +}; + +const GdkRGBA rxvt_palette[PALETTE_SIZE] = { + {0, 0, 0, 1 }, + {0.803921, 0, 0, 1 }, + {0, 0.803921, 0, 1 }, + {0.803921, 0.803921, 0, 1 }, + {0, 0, 0.803921, 1 }, + {0.803921, 0, 0.803921, 1 }, + {0, 0.803921, 0.803921, 1 }, + {0.980392, 0.921568, 0.843137, 1 }, + {0.250980, 0.250980, 0.250980, 1 }, + {1, 0, 0, 1 }, + {0, 1, 0, 1 }, + {1, 1, 0, 1 }, + {0, 0, 1, 1 }, + {1, 0, 1, 1 }, + {0, 1, 1, 1 }, + {1, 1, 1, 1 } +}; + + +#define HIG_DIALOG_CSS "* {\n"\ + "-GtkDialog-action-area-border : 12;\n"\ + "-GtkDialog-button-spacing : 12;\n"\ + "}" + +#define NOTEBOOK_CSS "* {\n"\ + "color : rgba(0,0,0,1.0);\n"\ + "background-color : rgba(0,0,0,1.0);\n"\ + "border-color : rgba(0,0,0,1.0);\n"\ + "}" + +#define TAB_TITLE_CSS "* {\n"\ + "padding : 0px;\n"\ + "}" + +#define NUM_COLORSETS 6 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include <pcre2.h> + +static struct { + GtkWidget *main_window; + GtkWidget *notebook; + GtkWidget *menu; + PangoFontDescription *font; + GdkRGBA forecolors[NUM_COLORSETS]; + GdkRGBA backcolors[NUM_COLORSETS]; + GdkRGBA curscolors[NUM_COLORSETS]; + const GdkRGBA *palette; + char *current_match; + guint width; + guint height; + glong columns; + glong rows; + gint scroll_lines; + gint label_count; + VteCursorShape cursor_type; + bool first_tab; + bool show_scrollbar; + bool show_closebutton; + bool tabs_on_bottom; + bool less_questions; + bool urgent_bell; + bool audible_bell; + bool blinking_cursor; + bool stop_tab_cycling_at_end_tabs; + bool allow_bold; + bool fullscreen; + bool keep_fc; /* Global flag to indicate that we don't want changes in the files and columns values */ + bool config_modified; /* Configuration has been modified */ + bool externally_modified; /* Configuration file has been modified by another process */ + bool resized; + bool disable_numbered_tabswitch; /* For disabling direct tabswitching key */ + bool focused; /* For fading feature */ + bool first_focus; /* First time gtkwindow recieve focus when is created */ + bool faded; /* Fading state */ + bool use_fading; + bool scrollable_tabs; + GtkWidget *item_copy_link; /* We include here only the items which need to be hidden */ + GtkWidget *item_open_link; + GtkWidget *item_open_mail; + GtkWidget *open_link_separator; + GKeyFile *cfg; + GtkCssProvider *provider; + char *configfile; + char *icon; + char *word_chars; /* Exceptions for word selection */ + gchar *tab_default_title; + gint last_colorset; + gint add_tab_accelerator; + gint del_tab_accelerator; + gint switch_tab_accelerator; + gint move_tab_accelerator; + gint copy_accelerator; + gint scrollbar_accelerator; + gint open_url_accelerator; + gint font_size_accelerator; + gint set_tab_name_accelerator; + gint search_accelerator; + gint set_colorset_accelerator; + gint add_tab_key; + gint del_tab_key; + gint prev_tab_key; + gint next_tab_key; + gint copy_key; + gint paste_key; + gint scrollbar_key; + gint set_tab_name_key; + gint search_key; + gint fullscreen_key; + gint increase_font_size_key; + gint decrease_font_size_key; + gint set_colorset_keys[NUM_COLORSETS]; + VteRegex *http_vteregexp, *mail_vteregexp; + char *argv[3]; +} sakura; + +struct terminal { + GtkWidget *hbox; + GtkWidget *vte; /* Reference to VTE terminal */ + GPid pid; /* pid of the forked process */ + GtkWidget *scrollbar; + GtkWidget *label; + gchar *label_text; + bool label_set_byuser; + GtkBorder padding; /* inner-property data */ + int colorset; +}; + + +#define ICON_FILE "terminal-tango.svg" +#define SCROLL_LINES 4096 +#define DEFAULT_SCROLL_LINES 4096 +#define HTTP_REGEXP "(ftp|http)s?://[^ \t\n\b()<>{}«»\\[\\]\'\"]+[^.]" +#define MAIL_REGEXP "[^ \t\n\b]+@([^ \t\n\b]+\\.)+([a-zA-Z]{2,4})" +#define DEFAULT_CONFIGFILE "sakura.conf" +#define DEFAULT_COLUMNS 80 +#define DEFAULT_ROWS 24 +#define DEFAULT_FONT "Ubuntu Mono,monospace 12" +#define FONT_MINIMAL_SIZE (PANGO_SCALE*6) +#define DEFAULT_WORD_CHARS "-,./?%&#_~:" +#define DEFAULT_PALETTE "solarized_dark" +#define TAB_MAX_SIZE 40 +#define TAB_MIN_SIZE 6 +#define FORWARD 1 +#define BACKWARDS 2 +#define FADE_PERCENT 60 +#define DEFAULT_ADD_TAB_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_DEL_TAB_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_SWITCH_TAB_ACCELERATOR (GDK_CONTROL_MASK) +#define DEFAULT_MOVE_TAB_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_COPY_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_SCROLLBAR_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_OPEN_URL_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_FONT_SIZE_ACCELERATOR (GDK_CONTROL_MASK) +#define DEFAULT_SET_TAB_NAME_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_SEARCH_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_SELECT_COLORSET_ACCELERATOR (GDK_CONTROL_MASK|GDK_SHIFT_MASK) +#define DEFAULT_ADD_TAB_KEY GDK_KEY_T +#define DEFAULT_DEL_TAB_KEY GDK_KEY_W +#define DEFAULT_PREV_TAB_KEY GDK_KEY_Left +#define DEFAULT_NEXT_TAB_KEY GDK_KEY_Right +#define DEFAULT_COPY_KEY GDK_KEY_C +#define DEFAULT_PASTE_KEY GDK_KEY_V +#define DEFAULT_SCROLLBAR_KEY GDK_KEY_S +#define DEFAULT_SET_TAB_NAME_KEY GDK_KEY_N +#define DEFAULT_SEARCH_KEY GDK_KEY_F +#define DEFAULT_FULLSCREEN_KEY GDK_KEY_F11 +#define DEFAULT_INCREASE_FONT_SIZE_KEY GDK_KEY_plus +#define DEFAULT_DECREASE_FONT_SIZE_KEY GDK_KEY_minus +#define DEFAULT_SCROLLABLE_TABS TRUE + +/* make this an array instead of #defines to get a compile time + * error instead of a runtime if NUM_COLORSETS changes */ +static int cs_keys[NUM_COLORSETS] = + {GDK_KEY_F1, GDK_KEY_F2, GDK_KEY_F3, GDK_KEY_F4, GDK_KEY_F5, GDK_KEY_F6}; + +#define ERROR_BUFFER_LENGTH 256 +const char cfg_group[] = "sakura"; + +static GQuark term_data_id = 0; +#define sakura_get_page_term( sakura, page_idx ) \ + (struct terminal*)g_object_get_qdata( \ + G_OBJECT( gtk_notebook_get_nth_page( (GtkNotebook*)sakura.notebook, page_idx ) ), term_data_id); + +#define sakura_set_page_term( sakura, page_idx, term ) \ + g_object_set_qdata_full( \ + G_OBJECT( gtk_notebook_get_nth_page( (GtkNotebook*)sakura.notebook, page_idx) ), \ + term_data_id, term, (GDestroyNotify)g_free); + +#define sakura_set_config_integer(key, value) do {\ + g_key_file_set_integer(sakura.cfg, cfg_group, key, value);\ + sakura.config_modified=TRUE;\ + } while(0); + +#define sakura_set_config_string(key, value) do {\ + g_key_file_set_value(sakura.cfg, cfg_group, key, value);\ + sakura.config_modified=TRUE;\ + } while(0); + +#define sakura_set_config_boolean(key, value) do {\ + g_key_file_set_boolean(sakura.cfg, cfg_group, key, value);\ + sakura.config_modified=TRUE;\ + } while(0); + + +/* Spawn callback */ +void sakura_spawm_callback (VteTerminal *, GPid, GError, gpointer); +/* Callbacks */ +static gboolean sakura_key_press (GtkWidget *, GdkEventKey *, gpointer); +static gboolean sakura_button_press (GtkWidget *, GdkEventButton *, gpointer); +static void sakura_beep (GtkWidget *, void *); +static void sakura_increase_font (GtkWidget *, void *); +static void sakura_decrease_font (GtkWidget *, void *); +static void sakura_child_exited (GtkWidget *, void *); +static void sakura_eof (GtkWidget *, void *); +static void sakura_title_changed (GtkWidget *, void *); +static gboolean sakura_delete_event (GtkWidget *, void *); +static void sakura_destroy_window (GtkWidget *, void *); +static gboolean sakura_resized_window( GtkWidget *, GdkEventConfigure *, void *); +static gboolean sakura_focus_in( GtkWidget *, GdkEvent *, void *); +static gboolean sakura_focus_out( GtkWidget *, GdkEvent *, void *); +static void sakura_closebutton_clicked (GtkWidget *, void *); +static void sakura_conf_changed (GtkWidget *, void *); +static void sakura_window_show_event (GtkWidget *, gpointer); +//static gboolean sakura_notebook_focus_in (GtkWidget *, void *); +static gboolean sakura_notebook_scroll (GtkWidget *, GdkEventScroll *); +/* Menuitem callbacks */ +static void sakura_font_dialog (GtkWidget *, void *); +static void sakura_set_name_dialog (GtkWidget *, void *); +static void sakura_color_dialog (GtkWidget *, void *); +static void sakura_set_title_dialog (GtkWidget *, void *); +static void sakura_search_dialog (GtkWidget *, void *); +static void sakura_new_tab (GtkWidget *, void *); +static void sakura_close_tab (GtkWidget *, void *); +static void sakura_fullscreen (GtkWidget *, void *); +static void sakura_open_url (GtkWidget *, void *); +static void sakura_copy (GtkWidget *, void *); +static void sakura_paste (GtkWidget *, void *); +static void sakura_show_first_tab (GtkWidget *widget, void *data); +static void sakura_tabs_on_bottom (GtkWidget *widget, void *data); +static void sakura_less_questions (GtkWidget *widget, void *data); +static void sakura_show_close_button (GtkWidget *widget, void *data); +static void sakura_show_scrollbar(GtkWidget *, void *); +static void sakura_disable_numbered_tabswitch (GtkWidget *, void *); +static void sakura_use_fading (GtkWidget *, void *); +static void sakura_setname_entry_changed(GtkWidget *, void *); + +/* Misc */ +static void sakura_error(const char *, ...); + +/* Functions */ +static void sakura_init(); +static void sakura_init_popup(); +static void sakura_destroy(); +static void sakura_add_tab(); +static void sakura_del_tab(); +static void sakura_move_tab(gint); +static gint sakura_find_tab(VteTerminal *); +static void sakura_set_font(); +static void sakura_set_tab_label_text(const gchar *, gint page); +static void sakura_set_size(void); +static void sakura_set_keybind(const gchar *, guint); +static guint sakura_get_keybind(const gchar *); +static void sakura_config_done(); +static void sakura_set_colorset (int); +static void sakura_set_colors (void); +static guint sakura_tokeycode(guint key); +static void sakura_fade_in(void); +static void sakura_fade_out(void); + +/* Globals for command line parameters */ +static const char *option_font; +static const char *option_workdir; +static const char *option_execute; +static gchar **option_xterm_args; +static gboolean option_xterm_execute=FALSE; +static gboolean option_version=FALSE; +static gint option_ntabs=1; +static gint option_login = FALSE; +static const char *option_title; +static const char *option_icon; +static int option_rows, option_columns; +static gboolean option_hold=FALSE; +static char *option_config_file; +static gboolean option_fullscreen; +static gboolean option_maximize; +static gint option_colorset; + +static GOptionEntry entries[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, N_("Print version number"), NULL }, + { "font", 'f', 0, G_OPTION_ARG_STRING, &option_font, N_("Select initial terminal font"), NULL }, + { "ntabs", 'n', 0, G_OPTION_ARG_INT, &option_ntabs, N_("Select initial number of tabs"), NULL }, + { "working-directory", 'd', 0, G_OPTION_ARG_STRING, &option_workdir, N_("Set working directory"), NULL }, + { "execute", 'x', 0, G_OPTION_ARG_STRING, &option_execute, N_("Execute command"), NULL }, + { "xterm-execute", 'e', 0, G_OPTION_ARG_NONE, &option_xterm_execute, N_("Execute command (last option in the command line)"), NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &option_xterm_args, NULL, NULL }, + { "login", 'l', 0, G_OPTION_ARG_NONE, &option_login, N_("Login shell"), NULL }, + { "title", 't', 0, G_OPTION_ARG_STRING, &option_title, N_("Set window title"), NULL }, + { "icon", 'i', 0, G_OPTION_ARG_STRING, &option_icon, N_("Set window icon"), NULL }, + { "xterm-title", 'T', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &option_title, NULL, NULL }, + { "columns", 'c', 0, G_OPTION_ARG_INT, &option_columns, N_("Set columns number"), NULL }, + { "rows", 'r', 0, G_OPTION_ARG_INT, &option_rows, N_("Set rows number"), NULL }, + { "hold", 'h', 0, G_OPTION_ARG_NONE, &option_hold, N_("Hold window after execute command"), NULL }, + { "maximize", 'm', 0, G_OPTION_ARG_NONE, &option_maximize, N_("Maximize window"), NULL }, + { "fullscreen", 's', 0, G_OPTION_ARG_NONE, &option_fullscreen, N_("Fullscreen mode"), NULL }, + { "config-file", 0, 0, G_OPTION_ARG_FILENAME, &option_config_file, N_("Use alternate configuration file"), NULL }, + { "colorset", 0, 0, G_OPTION_ARG_INT, &option_colorset, N_("Select initial colorset"), NULL }, + { NULL } +}; + + +static guint +sakura_tokeycode (guint key) +{ + GdkKeymap *keymap; + GdkKeymapKey *keys; + gint n_keys; + guint res = 0; + + keymap = gdk_keymap_get_for_display(gdk_display_get_default()); + + if (gdk_keymap_get_entries_for_keyval(keymap, key, &keys, &n_keys)) { + if (n_keys > 0) { + res = keys[0].keycode; + } + g_free(keys); + } + + return res; +} + + +void +search(VteTerminal *vte, const char *pattern, bool reverse) +{ + GError *error=NULL; + VteRegex *regex; + + vte_terminal_search_set_wrap_around(vte, TRUE); + + regex=vte_regex_new_for_search(pattern, (gssize) strlen(pattern), PCRE2_MULTILINE|PCRE2_CASELESS, &error); + if (!regex) { /* Ubuntu-fucking-morons (17.10 and 18.04) package a broken VTE without PCRE2, and search fails */ + sakura_error(error->message); + g_error_free(error); + } else { + vte_terminal_search_set_regex(vte, regex, 0); + + if (!vte_terminal_search_find_next(vte)) { + vte_terminal_unselect_all(vte); + vte_terminal_search_find_next(vte); + } + + if (regex) vte_regex_unref(regex); + } +} + + +static gboolean +sakura_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + if (event->type!=GDK_KEY_PRESS) return FALSE; + + unsigned int topage = 0; + + gint npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* Use keycodes instead of keyvals. With keyvals, key bindings work only in US/ISO8859-1 and similar locales */ + guint keycode = event->hardware_keycode; + + /* Add/delete tab keybinding pressed */ + if ( (event->state & sakura.add_tab_accelerator)==sakura.add_tab_accelerator && + keycode==sakura_tokeycode(sakura.add_tab_key)) { + sakura_add_tab(); + return TRUE; + } else if ( (event->state & sakura.del_tab_accelerator)==sakura.del_tab_accelerator && + keycode==sakura_tokeycode(sakura.del_tab_key) ) { + /* Delete current tab */ + sakura_close_tab(NULL, NULL); + return TRUE; + } + + /* Switch tab keybinding pressed (numbers or next/prev) */ + /* In cases when the user configured accelerators like these ones: + switch_tab_accelerator=4 for ctrl+next[prev]_tab_key + move_tab_accelerator=5 for ctrl+shift+next[prev]_tab_key + move never works, because switch will be processed first, so it needs to be fixed with the following condition */ + if ( ((event->state & sakura.switch_tab_accelerator) == sakura.switch_tab_accelerator) && + ((event->state & sakura.move_tab_accelerator) != sakura.move_tab_accelerator) ) { + + if ((keycode>=sakura_tokeycode(GDK_KEY_1)) && (keycode<=sakura_tokeycode( GDK_KEY_9))) { + + /* User has explicitly disabled this branch, make sure to propagate the event */ + if(sakura.disable_numbered_tabswitch) return FALSE; + + if (sakura_tokeycode(GDK_KEY_1) == keycode) topage = 0; + else if (sakura_tokeycode(GDK_KEY_2) == keycode) topage = 1; + else if (sakura_tokeycode(GDK_KEY_3) == keycode) topage = 2; + else if (sakura_tokeycode(GDK_KEY_4) == keycode) topage = 3; + else if (sakura_tokeycode(GDK_KEY_5) == keycode) topage = 4; + else if (sakura_tokeycode(GDK_KEY_6) == keycode) topage = 5; + else if (sakura_tokeycode(GDK_KEY_7) == keycode) topage = 6; + else if (sakura_tokeycode(GDK_KEY_8) == keycode) topage = 7; + else if (sakura_tokeycode(GDK_KEY_9) == keycode) topage = 8; + if (topage <= npages) + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), topage); + return TRUE; + } else if (keycode==sakura_tokeycode(sakura.prev_tab_key)) { + if (gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook))==0) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), npages-1); + } else { + gtk_notebook_prev_page(GTK_NOTEBOOK(sakura.notebook)); + } + return TRUE; + } else if (keycode==sakura_tokeycode(sakura.next_tab_key)) { + if (gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook))==(npages-1)) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), 0); + } else { + gtk_notebook_next_page(GTK_NOTEBOOK(sakura.notebook)); + } + return TRUE; + } + } + + /* Move tab keybinding pressed */ + if ( ((event->state & sakura.move_tab_accelerator) == sakura.move_tab_accelerator)) { + if (keycode==sakura_tokeycode(sakura.prev_tab_key)) { + sakura_move_tab(BACKWARDS); + return TRUE; + } else if (keycode==sakura_tokeycode(sakura.next_tab_key)) { + sakura_move_tab(FORWARD); + return TRUE; + } + } + + /* Copy/paste keybinding pressed */ + if ( (event->state & sakura.copy_accelerator)==sakura.copy_accelerator ) { + if (keycode==sakura_tokeycode(sakura.copy_key)) { + sakura_copy(NULL, NULL); + return TRUE; + } else if (keycode==sakura_tokeycode(sakura.paste_key)) { + sakura_paste(NULL, NULL); + return TRUE; + } + } + + /* Show scrollbar keybinding pressed */ + if ( (event->state & sakura.scrollbar_accelerator)==sakura.scrollbar_accelerator ) { + if (keycode==sakura_tokeycode(sakura.scrollbar_key)) { + sakura_show_scrollbar(NULL, NULL); + return TRUE; + } + } + + /* Set tab name keybinding pressed */ + if ( (event->state & sakura.set_tab_name_accelerator)==sakura.set_tab_name_accelerator ) { + if (keycode==sakura_tokeycode(sakura.set_tab_name_key)) { + sakura_set_name_dialog(NULL, NULL); + return TRUE; + } + } + + /* Search keybinding pressed */ + if ( (event->state & sakura.search_accelerator)==sakura.search_accelerator ) { + if (keycode==sakura_tokeycode(sakura.search_key)) { + sakura_search_dialog(NULL, NULL); + return TRUE; + } + } + + /* Increase/decrease font size keybinding pressed */ + if ( (event->state & sakura.font_size_accelerator)==sakura.font_size_accelerator ) { + if (keycode==sakura_tokeycode(sakura.increase_font_size_key)) { + sakura_increase_font(NULL, NULL); + return TRUE; + } else if (keycode==sakura_tokeycode(sakura.decrease_font_size_key)) { + sakura_decrease_font(NULL, NULL); + return TRUE; + } + } + + /* F11 (fullscreen) pressed */ + if (keycode==sakura_tokeycode(sakura.fullscreen_key)){ + sakura_fullscreen(NULL, NULL); + return TRUE; + } + + /* Change in colorset */ + if ( (event->state & sakura.set_colorset_accelerator)==sakura.set_colorset_accelerator ) { + int i; + for(i=0; i<NUM_COLORSETS; i++) { + if (keycode==sakura_tokeycode(sakura.set_colorset_keys[i])){ + sakura_set_colorset(i); + return TRUE; + } + } + } + return FALSE; +} + + +static gboolean +sakura_button_press(GtkWidget *widget, GdkEventButton *button_event, gpointer user_data) +{ + struct terminal *term; + gint page, tag; + + if (button_event->type != GDK_BUTTON_PRESS) + return FALSE; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + /* Find out if cursor it's over a matched expression...*/ + sakura.current_match = vte_terminal_match_check_event(VTE_TERMINAL(term->vte), (GdkEvent *) button_event, &tag); + + /* Left button with accelerator: open the URL if any */ + if (button_event->button == 1 && + ((button_event->state & sakura.open_url_accelerator) == sakura.open_url_accelerator) && + sakura.current_match) { + + sakura_open_url(NULL, NULL); + + return TRUE; + } + + /* Right button: show the popup menu */ + if (button_event->button == 3) { + GtkMenu *menu; + menu = GTK_MENU (widget); + + if (sakura.current_match) { + /* Show the extra options in the menu */ + + char *matches; + /* Is it a mail address? */ + if (vte_terminal_event_check_regex_simple(VTE_TERMINAL(term->vte), (GdkEvent *) button_event, + &sakura.mail_vteregexp, 1, 0, &matches)) { + gtk_widget_show(sakura.item_open_mail); + gtk_widget_hide(sakura.item_open_link); + } else { + gtk_widget_show(sakura.item_open_link); + gtk_widget_hide(sakura.item_open_mail); + } + gtk_widget_show(sakura.item_copy_link); + gtk_widget_show(sakura.open_link_separator); + + g_free(matches); + } else { + /* Hide all the options */ + gtk_widget_hide(sakura.item_open_mail); + gtk_widget_hide(sakura.item_open_link); + gtk_widget_hide(sakura.item_copy_link); + gtk_widget_hide(sakura.open_link_separator); + } + + gtk_menu_popup_at_pointer(menu, (GdkEvent *) button_event); + + return TRUE; + } + + return FALSE; +} + + +static gboolean +sakura_focus_in(GtkWidget *widget, GdkEvent *event, void *data) +{ + if (event->type != GDK_FOCUS_CHANGE) return FALSE; + + /* Ignore first focus event */ + if (sakura.first_focus) { + sakura.first_focus=false; return FALSE; + } + + if (!sakura.focused) { + sakura.focused=true; + + if (!sakura.first_focus && sakura.use_fading) { + sakura_fade_in(); + } + + sakura_set_colors(); + return TRUE; + } + + return FALSE; +} + + +static gboolean +sakura_focus_out(GtkWidget *widget, GdkEvent *event, void *data) +{ + if (event->type != GDK_FOCUS_CHANGE) return FALSE; + + if (sakura.focused) { + sakura.focused=false; + + if (!sakura.first_focus && sakura.use_fading) { + sakura_fade_out(); + } + + sakura_set_colors(); + return TRUE; + } + + return FALSE; +} + + +/* Handler for notebook focus-in-event */ +//static gboolean +//sakura_notebook_focus_in(GtkWidget *widget, void *data) +//{ +// struct terminal *term; +// int index; +// +// index = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); +// term = sakura_get_page_term(sakura, index); +// +// /* If term is found stop event propagation */ +// if(term != NULL) { +// gtk_widget_grab_focus(term->vte); +// return TRUE; +// } +// +// return FALSE; +//} + + +/* Handler for notebook scroll-event - switches tabs by scroll direction + TODO: let scroll directions configurable */ +static gboolean +sakura_notebook_scroll(GtkWidget *widget, GdkEventScroll *event) +{ + gint page, npages; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + switch(event->direction) { + case GDK_SCROLL_DOWN: + { + if (sakura.stop_tab_cycling_at_end_tabs == 1) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), --page >= 0 ? page : 0); + } else { + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), --page >= 0 ? page : npages - 1); + } + break; + } + case GDK_SCROLL_UP: + { + if (sakura.stop_tab_cycling_at_end_tabs == 1) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), ++page < npages ? page : npages - 1); + } else { + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), ++page < npages ? page : 0); + } + break; + } + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_SMOOTH: + break; + } + + return FALSE; +} + + +static void +sakura_page_removed (GtkWidget *widget, void *data) +{ + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook))==1) { + /* If the first tab is disabled, window size changes and we need + * to recalculate its size */ + sakura_set_size(); + } +} + + +static void +sakura_beep (GtkWidget *widget, void *data) +{ + // Remove the urgency hint. This is necessary to signal the window manager + // that a new urgent event happened when the urgent hint is set after this. + gtk_window_set_urgency_hint(GTK_WINDOW(sakura.main_window), FALSE); + + if (sakura.urgent_bell) { + gtk_window_set_urgency_hint(GTK_WINDOW(sakura.main_window), TRUE); + } +} + + +static void +sakura_increase_font (GtkWidget *widget, void *data) +{ + gint new_size; + + /* Increment font size one unit */ + new_size=pango_font_description_get_size(sakura.font)+PANGO_SCALE; + + pango_font_description_set_size(sakura.font, new_size); + sakura_set_font(); + sakura_set_size(); + sakura_set_config_string("font", pango_font_description_to_string(sakura.font)); +} + + +static void +sakura_decrease_font (GtkWidget *widget, void *data) +{ + gint new_size; + + /* Decrement font size one unit */ + new_size=pango_font_description_get_size(sakura.font)-PANGO_SCALE; + + /* Set a minimal size */ + if (new_size >= FONT_MINIMAL_SIZE ) { + pango_font_description_set_size(sakura.font, new_size); + sakura_set_font(); + sakura_set_size(); + sakura_set_config_string("font", pango_font_description_to_string(sakura.font)); + } +} + + +static void +sakura_child_exited (GtkWidget *widget, void *data) +{ + gint page, npages; + struct terminal *term; + + page = gtk_notebook_page_num(GTK_NOTEBOOK(sakura.notebook), + gtk_widget_get_parent(widget)); + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + /* Only write configuration to disk if it's the last tab */ + if (npages==1) { + sakura_config_done(); + } + + if (option_hold==TRUE) { + SAY("hold option has been activated"); + return; + } + + /* Child should be automatically reaped because we don't use G_SPAWN_DO_NOT_REAP_CHILD flag */ + g_spawn_close_pid(term->pid); + + sakura_del_tab(page); + + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + if (npages==0) + sakura_destroy(); +} + + +static void +sakura_eof (GtkWidget *widget, void *data) +{ + gint npages; + struct terminal *term; + + SAY("Got EOF signal"); + + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* Only write configuration to disk if it's the last tab */ + if (npages==1) { + sakura_config_done(); + } + + /* Workaround for libvte strange behaviour. There is not child-exited signal for + the last terminal, so we need to kill it here. Check with libvte authors about + child-exited/eof signals */ + if (gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook))==0) { + + term = sakura_get_page_term(sakura, 0); + + if (option_hold==TRUE) { + SAY("hold option has been activated"); + return; + } + + //SAY("waiting for terminal pid (in eof) %d", term->pid); + //waitpid(term->pid, &status, WNOHANG); + /* TODO: check wait return */ + /* Child should be automatically reaped because we don't use G_SPAWN_DO_NOT_REAP_CHILD flag */ + g_spawn_close_pid(term->pid); + + sakura_del_tab(0); + + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + if (npages==0) + sakura_destroy(); + } +} + +/* This handler is called when window title changes, and is used to change window and notebook pages titles */ +static void +sakura_title_changed (GtkWidget *widget, void *data) +{ + struct terminal *term; + const char *title; + gint n_pages; + gint modified_page; + VteTerminal *vte_term=(VteTerminal *)widget; + + modified_page = sakura_find_tab(vte_term); + n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, modified_page); + + title = vte_terminal_get_window_title(VTE_TERMINAL(term->vte)); + + /* User set values overrides any other one, but title should be changed */ + if (!term->label_set_byuser) + sakura_set_tab_label_text(title, modified_page); + + if (option_title == NULL) { + if (n_pages==1) { + /* Beware: It doesn't work in Unity because there is a Compiz bug: #257391 */ + gtk_window_set_title(GTK_WINDOW(sakura.main_window), title); + } else + gtk_window_set_title(GTK_WINDOW(sakura.main_window), "sakura"); + } else { + gtk_window_set_title(GTK_WINDOW(sakura.main_window), option_title); + } + +} + + +/* Save configuration */ +static void +sakura_config_done() +{ + GError *gerror = NULL; + gsize len = 0; + + gchar *cfgdata = g_key_file_to_data(sakura.cfg, &len, &gerror); + if (!cfgdata) { + fprintf(stderr, "%s\n", gerror->message); + exit(EXIT_FAILURE); + } + + /* Write to file IF there's been changes */ + if (sakura.config_modified) { + + bool overwrite=true; + + if (sakura.externally_modified) { + GtkWidget *dialog; + gint response; + + dialog=gtk_message_dialog_new(GTK_WINDOW(sakura.main_window), GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + _("Configuration has been modified by another process. Overwrite?")); + + response=gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response==GTK_RESPONSE_YES) { + overwrite=true; + } else + overwrite=false; + } + + if (overwrite) { + GIOChannel *cfgfile = g_io_channel_new_file(sakura.configfile, "w", &gerror); + if (!cfgfile) { + fprintf(stderr, "%s\n", gerror->message); + g_error_free(gerror); + exit(EXIT_FAILURE); + } + + /* FIXME: if the number of chars written is not "len", something happened. + * Check for errors appropriately...*/ + GIOStatus status = g_io_channel_write_chars(cfgfile, cfgdata, len, NULL, &gerror); + if (status != G_IO_STATUS_NORMAL) { + // FIXME: we should deal with temporary failures (G_IO_STATUS_AGAIN) + fprintf(stderr, "%s\n", gerror->message); + g_error_free(gerror); + exit(EXIT_FAILURE); + } + g_io_channel_shutdown(cfgfile, TRUE, &gerror); + g_io_channel_unref(cfgfile); + } + } +} + + +static gboolean +sakura_delete_event (GtkWidget *widget, void *data) +{ + struct terminal *term; + GtkWidget *dialog; + gint response; + gint npages; + gint i; + pid_t pgid; + + if (!sakura.less_questions) { + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* Check for each tab if there are running processes. Use tcgetpgrp to compare to the shell PGID */ + for (i=0; i < npages; i++) { + + term = sakura_get_page_term(sakura, i); + pgid = tcgetpgrp(vte_pty_get_fd(vte_terminal_get_pty(VTE_TERMINAL(term->vte)))); + + /* If running processes are found, we ask one time and exit */ + if ( (pgid != -1) && (pgid != term->pid)) { + dialog=gtk_message_dialog_new(GTK_WINDOW(sakura.main_window), GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + _("There are running processes.\n\nDo you really want to close Sakura?")); + + response=gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response==GTK_RESPONSE_YES) { + sakura_config_done(); + return FALSE; + } else { + return TRUE; + } + } + + } + } + + sakura_config_done(); + return FALSE; +} + + +static void +sakura_destroy_window (GtkWidget *widget, void *data) +{ + sakura_destroy(); +} + + +static void +sakura_window_show_event(GtkWidget *widget, gpointer data) +{ + // set size when the window is first shown + sakura_set_size(); +} + + +static void +sakura_font_dialog (GtkWidget *widget, void *data) +{ + GtkWidget *font_dialog; + gint response; + + font_dialog=gtk_font_chooser_dialog_new(_("Select font"), GTK_WINDOW(sakura.main_window)); + gtk_font_chooser_set_font_desc(GTK_FONT_CHOOSER(font_dialog), sakura.font); + + response=gtk_dialog_run(GTK_DIALOG(font_dialog)); + + if (response==GTK_RESPONSE_OK) { + pango_font_description_free(sakura.font); + sakura.font=gtk_font_chooser_get_font_desc(GTK_FONT_CHOOSER(font_dialog)); + sakura_set_font(); + sakura_set_size(); + sakura_set_config_string("font", pango_font_description_to_string(sakura.font)); + } + + gtk_widget_destroy(font_dialog); +} + + +static void +sakura_set_name_dialog (GtkWidget *widget, void *data) +{ + GtkWidget *input_dialog, *input_header; + GtkWidget *entry, *label; + GtkWidget *name_hbox; /* We need this for correct spacing */ + gint response; + gint page; + struct terminal *term; + const gchar *text; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + input_dialog=gtk_dialog_new_with_buttons(_("Set tab name"), + GTK_WINDOW(sakura.main_window), + GTK_DIALOG_MODAL|GTK_DIALOG_USE_HEADER_BAR, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Apply"), GTK_RESPONSE_ACCEPT, + NULL); + + /* Configure the new gtk header bar*/ + input_header=gtk_dialog_get_header_bar(GTK_DIALOG(input_dialog)); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(input_header), FALSE); + gtk_dialog_set_default_response(GTK_DIALOG(input_dialog), GTK_RESPONSE_ACCEPT); + + /* Set style */ + gchar *css = g_strdup_printf (HIG_DIALOG_CSS); + gtk_css_provider_load_from_data(sakura.provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context (input_dialog); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (sakura.provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_free(css); + + name_hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + entry=gtk_entry_new(); + label=gtk_label_new(_("New text")); + /* Set tab label as entry default text (when first tab is not displayed, get_tab_label_text + returns a null value, so check accordingly */ + text = gtk_notebook_get_tab_label_text(GTK_NOTEBOOK(sakura.notebook), term->hbox); + if (text) { + gtk_entry_set_text(GTK_ENTRY(entry), text); + } + gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); + gtk_box_pack_start(GTK_BOX(name_hbox), label, TRUE, TRUE, 12); + gtk_box_pack_start(GTK_BOX(name_hbox), entry, TRUE, TRUE, 12); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(input_dialog))), name_hbox, FALSE, FALSE, 12); + + /* Disable accept button until some text is entered */ + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sakura_setname_entry_changed), input_dialog); + gtk_dialog_set_response_sensitive(GTK_DIALOG(input_dialog), GTK_RESPONSE_ACCEPT, FALSE); + + gtk_widget_show_all(name_hbox); + + response=gtk_dialog_run(GTK_DIALOG(input_dialog)); + + if (response==GTK_RESPONSE_ACCEPT) { + sakura_set_tab_label_text(gtk_entry_get_text(GTK_ENTRY(entry)), page); + term->label_set_byuser=true; + } + + gtk_widget_destroy(input_dialog); +} + +static void +sakura_set_colorset (int cs) +{ + gint page; + struct terminal *term; + + if (cs<0 || cs>= NUM_COLORSETS) + return; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + term->colorset=cs; + + sakura_set_config_integer("last_colorset", term->colorset+1); + + sakura_set_colors(); +} + + +/* Set the terminal colors for all notebook tabs */ +static void +sakura_set_colors () +{ + int i; + int n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + struct terminal *term; + + for (i = (n_pages - 1); i >= 0; i--) { + term = sakura_get_page_term(sakura, i); + //SAY("Setting colorset %d", term->colorset+1); + + vte_terminal_set_colors(VTE_TERMINAL(term->vte), + &sakura.forecolors[term->colorset], + &sakura.backcolors[term->colorset], + sakura.palette, PALETTE_SIZE); + vte_terminal_set_color_cursor(VTE_TERMINAL(term->vte), &sakura.curscolors[term->colorset]); + } + + /* Main window opacity must be set. Otherwise vte widget will remain opaque */ + gtk_widget_set_opacity (sakura.main_window, sakura.backcolors[term->colorset].alpha); + +} + + +/* Callback from the color change dialog. Updates the contents of that + * dialog, passed as 'data' from user input. */ +static void +sakura_color_dialog_changed( GtkWidget *widget, void *data) +{ + int selected=-1; + GtkDialog *dialog = (GtkDialog*)data; + GtkColorButton *fore_button = g_object_get_data (G_OBJECT(dialog), "buttonfore"); + GtkColorButton *back_button = g_object_get_data (G_OBJECT(dialog), "buttonback"); + GtkColorButton *curs_button = g_object_get_data (G_OBJECT(dialog), "buttoncurs"); + GtkComboBox *set = g_object_get_data (G_OBJECT(dialog), "set_combo"); + GtkSpinButton *opacity_spin = g_object_get_data( G_OBJECT(dialog), "opacity_spin"); + GdkRGBA *temp_fore_colors = g_object_get_data( G_OBJECT(dialog), "fore"); + GdkRGBA *temp_back_colors = g_object_get_data( G_OBJECT(dialog), "back"); + GdkRGBA *temp_curs_colors = g_object_get_data( G_OBJECT(dialog), "curs"); + selected = gtk_combo_box_get_active( set ); + + /* if we come here as a result of a change in the active colorset, + * load the new colorset to the buttons. + * Else, the colorselect buttons or opacity spin have gotten a new + * value, store that. */ + if( (GtkWidget*)set == widget ) { + /* Spin opacity is a percentage, convert it*/ + gint new_opacity=(int)(temp_back_colors[selected].alpha*100); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(fore_button), &temp_fore_colors[selected]); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(back_button), &temp_back_colors[selected]); + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(curs_button), &temp_curs_colors[selected]); + gtk_spin_button_set_value(opacity_spin, new_opacity); + } else { + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(fore_button), &temp_fore_colors[selected]); + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(back_button), &temp_back_colors[selected]); + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(curs_button), &temp_curs_colors[selected]); + gtk_spin_button_update(opacity_spin); + temp_back_colors[selected].alpha=gtk_spin_button_get_value(opacity_spin)/100; + } + +} + + +static void +sakura_color_dialog (GtkWidget *widget, void *data) +{ + GtkWidget *color_dialog; GtkWidget *color_header; + GtkWidget *label1, *label2, *label3, *set_label, *opacity_label; + GtkWidget *buttonfore, *buttonback, *buttoncurs, *set_combo, *opacity_spin; + GtkAdjustment *spinner_adj; + GtkWidget *hbox_fore, *hbox_back, *hbox_curs, *hbox_sets, *hbox_opacity; + gint response; + struct terminal *term; + gint page; + int cs; + int i; + gchar combo_text[3]; + GdkRGBA temp_fore[NUM_COLORSETS]; + GdkRGBA temp_back[NUM_COLORSETS]; + GdkRGBA temp_curs[NUM_COLORSETS]; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + color_dialog=gtk_dialog_new_with_buttons(_("Select colors"), + GTK_WINDOW(sakura.main_window), + GTK_DIALOG_MODAL|GTK_DIALOG_USE_HEADER_BAR, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Select"), GTK_RESPONSE_ACCEPT, + NULL); + + /* Configure the new gtk header bar*/ + color_header=gtk_dialog_get_header_bar(GTK_DIALOG(color_dialog)); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(color_header), FALSE); + gtk_dialog_set_default_response(GTK_DIALOG(color_dialog), GTK_RESPONSE_ACCEPT); + + /* Set style */ + gchar *css = g_strdup_printf (HIG_DIALOG_CSS); + gtk_css_provider_load_from_data(sakura.provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context (color_dialog); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (sakura.provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_free(css); + + + /* Add the drop-down combobox that selects current colorset to edit. */ + hbox_sets=gtk_box_new(FALSE, 12); + set_label=gtk_label_new(_("Colorset")); + set_combo=gtk_combo_box_text_new(); + for(cs=0; cs<NUM_COLORSETS; cs++){ + g_snprintf(combo_text, 2, "%d", cs+1); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(set_combo), NULL, combo_text); + } + gtk_combo_box_set_active(GTK_COMBO_BOX(set_combo), term->colorset); + + /* Foreground and background and cursor color buttons */ + hbox_fore=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12); + hbox_back=gtk_box_new(FALSE, 12); + hbox_curs=gtk_box_new(FALSE, 12); + label1=gtk_label_new(_("Foreground color")); + label2=gtk_label_new(_("Background color")); + label3=gtk_label_new(_("Cursor color")); + buttonfore=gtk_color_button_new_with_rgba(&sakura.forecolors[term->colorset]); + buttonback=gtk_color_button_new_with_rgba(&sakura.backcolors[term->colorset]); + buttoncurs=gtk_color_button_new_with_rgba(&sakura.curscolors[term->colorset]); + + /* Opacity control */ + hbox_opacity=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12); + spinner_adj = gtk_adjustment_new ((sakura.backcolors[term->colorset].alpha)*100, 0.0, 99.0, 1.0, 5.0, 0); + opacity_spin = gtk_spin_button_new(GTK_ADJUSTMENT(spinner_adj), 1.0, 0); + opacity_label = gtk_label_new(_("Opacity level (%)")); + gtk_box_pack_start(GTK_BOX(hbox_opacity), opacity_label, FALSE, FALSE, 12); + gtk_box_pack_end(GTK_BOX(hbox_opacity), opacity_spin, FALSE, FALSE, 12); + + gtk_box_pack_start(GTK_BOX(hbox_fore), label1, FALSE, FALSE, 12); + gtk_box_pack_end(GTK_BOX(hbox_fore), buttonfore, FALSE, FALSE, 12); + gtk_box_pack_start(GTK_BOX(hbox_back), label2, FALSE, FALSE, 12); + gtk_box_pack_end(GTK_BOX(hbox_back), buttonback, FALSE, FALSE, 12); + gtk_box_pack_start(GTK_BOX(hbox_curs), label3, FALSE, FALSE, 12); + gtk_box_pack_end(GTK_BOX(hbox_curs), buttoncurs, FALSE, FALSE, 12); + gtk_box_pack_start(GTK_BOX(hbox_sets), set_label, FALSE, FALSE, 12); + gtk_box_pack_end(GTK_BOX(hbox_sets), set_combo, FALSE, FALSE, 12); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(color_dialog))), hbox_sets, FALSE, FALSE, 6); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(color_dialog))), hbox_fore, FALSE, FALSE, 6); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(color_dialog))), hbox_back, FALSE, FALSE, 6); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(color_dialog))), hbox_curs, FALSE, FALSE, 6); + gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(color_dialog))), hbox_opacity, FALSE, FALSE, 6); + + gtk_widget_show_all(gtk_dialog_get_content_area(GTK_DIALOG(color_dialog))); + + /* When user switches the colorset to change, the callback needs access + * to these selector widgets */ + g_object_set_data(G_OBJECT(color_dialog), "set_combo", set_combo); + g_object_set_data(G_OBJECT(color_dialog), "buttonfore", buttonfore); + g_object_set_data(G_OBJECT(color_dialog), "buttonback", buttonback); + g_object_set_data(G_OBJECT(color_dialog), "buttoncurs", buttoncurs); + g_object_set_data(G_OBJECT(color_dialog), "opacity_spin", opacity_spin); + g_object_set_data(G_OBJECT(color_dialog), "fore", temp_fore); + g_object_set_data(G_OBJECT(color_dialog), "back", temp_back); + g_object_set_data(G_OBJECT(color_dialog), "curs", temp_curs); + + g_signal_connect(G_OBJECT(buttonfore), "color-set", G_CALLBACK(sakura_color_dialog_changed), color_dialog ); + g_signal_connect(G_OBJECT(buttonback), "color-set", G_CALLBACK(sakura_color_dialog_changed), color_dialog ); + g_signal_connect(G_OBJECT(buttoncurs), "color-set", G_CALLBACK(sakura_color_dialog_changed), color_dialog ); + g_signal_connect(G_OBJECT(set_combo), "changed", G_CALLBACK(sakura_color_dialog_changed), color_dialog ); + g_signal_connect(G_OBJECT(opacity_spin), "changed", G_CALLBACK(sakura_color_dialog_changed), color_dialog ); + + for(i=0; i<NUM_COLORSETS; i++) { + temp_fore[i] = sakura.forecolors[i]; + temp_back[i] = sakura.backcolors[i]; + temp_curs[i] = sakura.curscolors[i]; + } + + response=gtk_dialog_run(GTK_DIALOG(color_dialog)); + + if (response==GTK_RESPONSE_ACCEPT) { + /* Save all colorsets to both the global struct and configuration.*/ + for( i=0; i<NUM_COLORSETS; i++) { + char name[20]; + gchar *cfgtmp; + + sakura.forecolors[i]=temp_fore[i]; + sakura.backcolors[i]=temp_back[i]; + sakura.curscolors[i]=temp_curs[i]; + + sprintf(name, "colorset%d_fore", i+1); + cfgtmp=gdk_rgba_to_string(&sakura.forecolors[i]); + sakura_set_config_string(name, cfgtmp); + g_free(cfgtmp); + + sprintf(name, "colorset%d_back", i+1); + cfgtmp=gdk_rgba_to_string(&sakura.backcolors[i]); + sakura_set_config_string(name, cfgtmp); + g_free(cfgtmp); + + sprintf(name, "colorset%d_curs", i+1); + cfgtmp=gdk_rgba_to_string(&sakura.curscolors[i]); + sakura_set_config_string(name, cfgtmp); + g_free(cfgtmp); + } + + /* Apply the new colorsets to all tabs + * Set the current tab's colorset to the last selected one in the dialog. + * This is probably what the new user expects, and the experienced user + * hopefully will not mind. */ + term->colorset = gtk_combo_box_get_active(GTK_COMBO_BOX(set_combo)); + sakura_set_config_integer("last_colorset", term->colorset+1); + sakura_set_colors(); + } + + gtk_widget_destroy(color_dialog); +} + + +static void +sakura_fade_out() +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + if (!sakura.faded) { + sakura.faded = true; + GdkRGBA x = sakura.forecolors[term->colorset]; + //SAY("fade out red %f to %f", x.red, x.red/100.0*FADE_PERCENT); + x.red = x.red/100.0 * FADE_PERCENT; + x.green = x.green/100.0 * FADE_PERCENT; + x.blue = x.blue/100.0 * FADE_PERCENT; + if ( (x.red >=0 && x.red <=1.0) && (x.green >=0 && x.green <=1.0) && (x.blue >=0 && x.blue <=1.0)) { + sakura.forecolors[term->colorset]=x; + } else { + SAY("Forecolor value out of range"); + } + } +} + + +static void +sakura_fade_in() +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + if (sakura.faded) { + sakura.faded = false; + GdkRGBA x = sakura.forecolors[term->colorset]; + //SAY("fade in red %f to %f", x.red, x.red/FADE_PERCENT*100.0); + x.red = x.red/FADE_PERCENT * 100.0; + x.green = x.green/FADE_PERCENT * 100.0; + x.blue = x.blue/FADE_PERCENT * 100.0; + if ( (x.red >=0 && x.red <=1.0) && (x.green >=0 && x.green <=1.0) && (x.blue >=0 && x.blue <=1.0)) { + sakura.forecolors[term->colorset]=x; + } else { + SAY("Forecolor value out of range"); + } + } +} + + +static void +sakura_search_dialog (GtkWidget *widget, void *data) +{ + GtkWidget *title_dialog, *title_header; + GtkWidget *entry, *label; + GtkWidget *title_hbox; + gint response; + + title_dialog=gtk_dialog_new_with_buttons(_("Search"), + GTK_WINDOW(sakura.main_window), + GTK_DIALOG_MODAL|GTK_DIALOG_USE_HEADER_BAR, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Apply"), GTK_RESPONSE_ACCEPT, + NULL); + + /* Configure the new gtk header bar*/ + title_header=gtk_dialog_get_header_bar(GTK_DIALOG(title_dialog)); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(title_header), FALSE); + gtk_dialog_set_default_response(GTK_DIALOG(title_dialog), GTK_RESPONSE_ACCEPT); + + /* Set style */ + gchar *css = g_strdup_printf (HIG_DIALOG_CSS); + gtk_css_provider_load_from_data(sakura.provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context (title_dialog); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (sakura.provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_free(css); + + entry=gtk_entry_new(); + label=gtk_label_new(_("Search")); + title_hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); + gtk_box_pack_start(GTK_BOX(title_hbox), label, TRUE, TRUE, 12); + gtk_box_pack_start(GTK_BOX(title_hbox), entry, TRUE, TRUE, 12); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(title_dialog))), title_hbox, FALSE, FALSE, 12); + + /* Disable accept button until some text is entered */ + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sakura_setname_entry_changed), title_dialog); + gtk_dialog_set_response_sensitive(GTK_DIALOG(title_dialog), GTK_RESPONSE_ACCEPT, FALSE); + + gtk_widget_show_all(title_hbox); + + response=gtk_dialog_run(GTK_DIALOG(title_dialog)); + if (response==GTK_RESPONSE_ACCEPT) { + gint page; + struct terminal *term; + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + search(VTE_TERMINAL(term->vte), gtk_entry_get_text(GTK_ENTRY(entry)), 0); + } + gtk_widget_destroy(title_dialog); +} + + +static void +sakura_set_title_dialog (GtkWidget *widget, void *data) +{ + GtkWidget *title_dialog, *title_header; + GtkWidget *entry, *label; + GtkWidget *title_hbox; + gint response; + + title_dialog=gtk_dialog_new_with_buttons(_("Set window title"), + GTK_WINDOW(sakura.main_window), + GTK_DIALOG_MODAL|GTK_DIALOG_USE_HEADER_BAR, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Apply"), GTK_RESPONSE_ACCEPT, + NULL); + + /* Configure the new gtk header bar*/ + title_header=gtk_dialog_get_header_bar(GTK_DIALOG(title_dialog)); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(title_header), FALSE); + gtk_dialog_set_default_response(GTK_DIALOG(title_dialog), GTK_RESPONSE_ACCEPT); + + /* Set style */ + gchar *css = g_strdup_printf (HIG_DIALOG_CSS); + gtk_css_provider_load_from_data(sakura.provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context (title_dialog); + gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (sakura.provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_free(css); + + entry=gtk_entry_new(); + label=gtk_label_new(_("New window title")); + title_hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + /* Set window label as entry default text */ + gtk_entry_set_text(GTK_ENTRY(entry), gtk_window_get_title(GTK_WINDOW(sakura.main_window))); + gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); + gtk_box_pack_start(GTK_BOX(title_hbox), label, TRUE, TRUE, 12); + gtk_box_pack_start(GTK_BOX(title_hbox), entry, TRUE, TRUE, 12); + gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(title_dialog))), title_hbox, FALSE, FALSE, 12); + + /* Disable accept button until some text is entered */ + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sakura_setname_entry_changed), title_dialog); + gtk_dialog_set_response_sensitive(GTK_DIALOG(title_dialog), GTK_RESPONSE_ACCEPT, FALSE); + + gtk_widget_show_all(title_hbox); + + response=gtk_dialog_run(GTK_DIALOG(title_dialog)); + if (response==GTK_RESPONSE_ACCEPT) { + /* Bug #257391 shadow reachs here too... */ + gtk_window_set_title(GTK_WINDOW(sakura.main_window), gtk_entry_get_text(GTK_ENTRY(entry))); + } + gtk_widget_destroy(title_dialog); +} + + + +static void +sakura_copy_url (GtkWidget *widget, void *data) +{ + GtkClipboard* clip; + + clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clip, sakura.current_match, -1 ); + clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clip, sakura.current_match, -1 ); + +} + + +static void +sakura_open_url (GtkWidget *widget, void *data) +{ + GError *error=NULL; + gchar *browser=NULL; + + SAY("Opening %s", sakura.current_match); + + browser=g_strdup(g_getenv("BROWSER")); + + if (!browser) { + if ( !(browser = g_find_program_in_path("xdg-open")) ) { + /* TODO: Legacy for systems without xdg-open. This should be removed */ + browser = g_strdup("firefox"); + } + } + + gchar * argv[] = {browser, sakura.current_match, NULL}; + if (!g_spawn_async(".", argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) { + sakura_error("Couldn't exec \"%s %s\": %s", browser, sakura.current_match, error->message); + g_error_free(error); + } + + g_free(browser); +} + + +static void +sakura_open_mail (GtkWidget *widget, void *data) +{ + GError *error = NULL; + gchar *program = NULL; + + if ( (program = g_find_program_in_path("xdg-email")) ) { + gchar * argv[] = { program, sakura.current_match, NULL }; + if (!g_spawn_async(".", argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) { + sakura_error("Couldn't exec \"%s %s\": %s", program, sakura.current_match, error->message); + } + g_free(program); + } +} + + +static void +sakura_show_first_tab (GtkWidget *widget, void *data) +{ + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), TRUE); + sakura_set_config_string("show_always_first_tab", "Yes"); + sakura.first_tab = true; + } else { + /* Only hide tabs if the notebook has one page */ + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)) == 1) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), FALSE); + } + sakura_set_config_string("show_always_first_tab", "No"); + sakura.first_tab = false; + } + sakura_set_size(); +} + +static void +sakura_tabs_on_bottom (GtkWidget *widget, void *data) +{ + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(sakura.notebook), GTK_POS_BOTTOM); + sakura_set_config_boolean("tabs_on_bottom", TRUE); + } else { + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(sakura.notebook), GTK_POS_TOP); + sakura_set_config_boolean("tabs_on_bottom", FALSE); + } +} + +static void +sakura_less_questions (GtkWidget *widget, void *data) +{ + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + sakura.less_questions=TRUE; + sakura_set_config_boolean("less_questions", TRUE); + } else { + sakura.less_questions=FALSE; + sakura_set_config_boolean("less_questions", FALSE); + } +} + +static void +sakura_show_close_button (GtkWidget *widget, void *data) +{ + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + sakura_set_config_boolean("closebutton", TRUE); + } else { + sakura_set_config_boolean("closebutton", FALSE); + } +} + + +static void +sakura_show_scrollbar (GtkWidget *widget, void *data) +{ + gint page; + struct terminal *term; + gint n_pages; + int i; + + sakura.keep_fc=1; + + n_pages=gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + if (!g_key_file_get_boolean(sakura.cfg, cfg_group, "scrollbar", NULL)) { + sakura.show_scrollbar=true; + sakura_set_config_boolean("scrollbar", TRUE); + } else { + sakura.show_scrollbar=false; + sakura_set_config_boolean("scrollbar", FALSE); + } + + /* Toggle/Untoggle the scrollbar for all tabs */ + for (i = (n_pages - 1); i >= 0; i--) { + term = sakura_get_page_term(sakura, i); + if (!sakura.show_scrollbar) + gtk_widget_hide(term->scrollbar); + else + gtk_widget_show(term->scrollbar); + } + sakura_set_size(); +} + + +static void +sakura_urgent_bell (GtkWidget *widget, void *data) +{ + sakura.urgent_bell = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); + if (sakura.urgent_bell) { + sakura_set_config_string("urgent_bell", "Yes"); + } else { + sakura_set_config_string("urgent_bell", "No"); + } +} + + +static void +sakura_audible_bell (GtkWidget *widget, void *data) +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + vte_terminal_set_audible_bell (VTE_TERMINAL(term->vte), TRUE); + sakura_set_config_string("audible_bell", "Yes"); + } else { + vte_terminal_set_audible_bell (VTE_TERMINAL(term->vte), FALSE); + sakura_set_config_string("audible_bell", "No"); + } +} + + + +static void +sakura_blinking_cursor (GtkWidget *widget, void *data) +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + vte_terminal_set_cursor_blink_mode (VTE_TERMINAL(term->vte), VTE_CURSOR_BLINK_ON); + sakura_set_config_string("blinking_cursor", "Yes"); + } else { + vte_terminal_set_cursor_blink_mode (VTE_TERMINAL(term->vte), VTE_CURSOR_BLINK_OFF); + sakura_set_config_string("blinking_cursor", "No"); + } +} + + +static void +sakura_allow_bold (GtkWidget *widget, void *data) +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + vte_terminal_set_allow_bold (VTE_TERMINAL(term->vte), TRUE); + sakura_set_config_string("allow_bold", "Yes"); + } else { + vte_terminal_set_allow_bold (VTE_TERMINAL(term->vte), FALSE); + sakura_set_config_string("allow_bold", "No"); + } +} + +static void +sakura_stop_tab_cycling_at_end_tabs (GtkWidget *widget, void *data) +{ + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + sakura_set_config_string("stop_tab_cycling_at_end_tabs", "Yes"); + sakura.stop_tab_cycling_at_end_tabs = TRUE; + } else { + sakura_set_config_string("stop_tab_cycling_at_end_tabs", "No"); + sakura.stop_tab_cycling_at_end_tabs = FALSE; + } +} + + +static void +sakura_set_cursor(GtkWidget *widget, void *data) +{ + struct terminal *term; + int n_pages, i; + + char *cursor_string = (char *)data; + n_pages=gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + + if (strcmp(cursor_string, "block")==0) { + sakura.cursor_type=VTE_CURSOR_SHAPE_BLOCK; + } else if (strcmp(cursor_string, "underline")==0) { + sakura.cursor_type=VTE_CURSOR_SHAPE_UNDERLINE; + } else if (strcmp(cursor_string, "ibeam")==0) { + sakura.cursor_type=VTE_CURSOR_SHAPE_IBEAM; + } + + for (i = (n_pages - 1); i >= 0; i--) { + term = sakura_get_page_term(sakura, i); + vte_terminal_set_cursor_shape(VTE_TERMINAL(term->vte), sakura.cursor_type); + } + + sakura_set_config_integer("cursor_type", sakura.cursor_type); + } +} + + +static void +sakura_set_palette(GtkWidget *widget, void *data) +{ + char *palette=(char *)data; + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + if (strcmp(palette, "linux")==0) { + sakura.palette=linux_palette; + } else if (strcmp(palette, "gruvbox")==0) { + sakura.palette=gruvbox_palette; + } else if (strcmp(palette, "xterm")==0) { + sakura.palette=xterm_palette; + } else if (strcmp(palette, "rxvt")==0) { + sakura.palette=rxvt_palette; + } else if (strcmp(palette, "tango")==0) { + sakura.palette=tango_palette; + } else if (strcmp(palette, "solarized_dark")==0) { + sakura.palette=solarized_dark_palette; + } else { + sakura.palette=solarized_light_palette; + } + + /* Palette changed so we ¿need? to set colors again */ + sakura_set_colors(); + + sakura_set_config_string("palette", palette); + } +} + + +/* Retrieve the cwd of the specified term page. + * Original function was from terminal-screen.c of gnome-terminal, copyright (C) 2001 Havoc Pennington + * Adapted by Hong Jen Yee, non-linux shit removed by David Gómez */ +static char* +sakura_get_term_cwd(struct terminal* term) +{ + char *cwd = NULL; + + if (term->pid >= 0) { + char *file, *buf; + struct stat sb; + int len; + + file = g_strdup_printf ("/proc/%d/cwd", term->pid); + + if (g_stat(file, &sb) == -1) { + g_free(file); + return cwd; + } + + buf = malloc(sb.st_size + 1); + + if(buf == NULL) { + g_free(file); + return cwd; + } + + len = readlink(file, buf, sb.st_size + 1); + + if (len > 0 && buf[0] == '/') { + buf[len] = '\0'; + cwd = g_strdup(buf); + } + + g_free(buf); + g_free(file); + } + + return cwd; +} + + +static gboolean +sakura_resized_window (GtkWidget *widget, GdkEventConfigure *event, void *data) +{ + if (event->width!=sakura.width || event->height!=sakura.height) { + //SAY("Configure event received. Current w %d h %d ConfigureEvent w %d h %d", + //sakura.width, sakura.height, event->width, event->height); + sakura.resized=TRUE; + } + + return FALSE; +} + + + +static void +sakura_setname_entry_changed (GtkWidget *widget, void *data) +{ + GtkDialog *title_dialog=(GtkDialog *)data; + + if (strcmp(gtk_entry_get_text(GTK_ENTRY(widget)), "")==0) { + gtk_dialog_set_response_sensitive(GTK_DIALOG(title_dialog), GTK_RESPONSE_ACCEPT, FALSE); + } else { + gtk_dialog_set_response_sensitive(GTK_DIALOG(title_dialog), GTK_RESPONSE_ACCEPT, TRUE); + } +} + + +/* Parameters are never used */ +static void +sakura_copy (GtkWidget *widget, void *data) +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + vte_terminal_copy_clipboard_format(VTE_TERMINAL(term->vte), VTE_FORMAT_TEXT); +} + + +/* Parameters are never used */ +static void +sakura_paste (GtkWidget *widget, void *data) +{ + gint page; + struct terminal *term; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + vte_terminal_paste_clipboard(VTE_TERMINAL(term->vte)); +} + + +static void +sakura_new_tab (GtkWidget *widget, void *data) +{ + sakura_add_tab(); +} + + +static void +sakura_close_tab (GtkWidget *widget, void *data) +{ + pid_t pgid; + GtkWidget *dialog; + gint response; + struct terminal *term; + gint page, npages; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + /* Only write configuration to disk if it's the last tab */ + if (npages==1) { + sakura_config_done(); + } + + /* Check if there are running processes for this tab. Use tcgetpgrp to compare to the shell PGID */ + pgid = tcgetpgrp(vte_pty_get_fd(vte_terminal_get_pty(VTE_TERMINAL(term->vte)))); + + if ( (pgid != -1) && (pgid != term->pid) && (!sakura.less_questions) ) { + dialog=gtk_message_dialog_new(GTK_WINDOW(sakura.main_window), GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + _("There is a running process in this terminal.\n\nDo you really want to close it?")); + + response=gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response==GTK_RESPONSE_YES) { + sakura_del_tab(page); + } + } else + sakura_del_tab(page); + + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + if (npages==0) + sakura_destroy(); +} + + +static void +sakura_fullscreen (GtkWidget *widget, void *data) +{ + if (sakura.fullscreen!=TRUE) { + sakura.fullscreen=TRUE; + gtk_window_fullscreen(GTK_WINDOW(sakura.main_window)); + } else { + gtk_window_unfullscreen(GTK_WINDOW(sakura.main_window)); + sakura.fullscreen=FALSE; + } +} + + +/* Callback for the tabs close buttons */ +static void +sakura_closebutton_clicked(GtkWidget *widget, void *data) +{ + gint page; + GtkWidget *hbox=(GtkWidget *)data; + struct terminal *term; + pid_t pgid; + GtkWidget *dialog; + gint npages, response; + + page = gtk_notebook_page_num(GTK_NOTEBOOK(sakura.notebook), hbox); + term = sakura_get_page_term(sakura, page); + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* Only write configuration to disk if it's the last tab */ + if (npages==1) { + sakura_config_done(); + } + + /* Check if there are running processes for this tab. Use tcgetpgrp to compare to the shell PGID */ + pgid = tcgetpgrp(vte_pty_get_fd(vte_terminal_get_pty(VTE_TERMINAL(term->vte)))); + + if ( (pgid != -1) && (pgid != term->pid) && (!sakura.less_questions) ) { + dialog=gtk_message_dialog_new(GTK_WINDOW(sakura.main_window), GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + _("There is a running process in this terminal.\n\nDo you really want to close it?")); + + response=gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response==GTK_RESPONSE_YES) { + sakura_del_tab(page); + + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook))==0) + sakura_destroy(); + } + } else { /* No processes, hell with tab */ + + sakura_del_tab(page); + + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook))==0) + sakura_destroy(); + } +} + +/* Callback called when sakura configuration file is modified by an external process */ +static void +sakura_conf_changed (GtkWidget *widget, void *data) +{ + sakura.externally_modified=true; +} + +static void +sakura_disable_numbered_tabswitch(GtkWidget *widget, void *data) +{ + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + sakura.disable_numbered_tabswitch = true; + sakura_set_config_boolean("disable_numbered_tabswitch", TRUE); + } else { + sakura.disable_numbered_tabswitch = false; + sakura_set_config_boolean("disable_numbered_tabswitch", FALSE); + } +} + +static void +sakura_use_fading(GtkWidget *widget, void *data) +{ + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + sakura.use_fading = true; + sakura_set_config_boolean("use_fading", TRUE); + } else { + sakura.use_fading = false; + sakura_set_config_boolean("use_fading", FALSE); + sakura_fade_in(); + sakura_set_colors(); + } +} + + + +/******* Functions ********/ + + +static void +sakura_init() +{ + char* configdir = NULL; + int i; + + term_data_id = g_quark_from_static_string("sakura_term"); + + /* Config file initialization*/ + sakura.cfg = g_key_file_new(); + sakura.config_modified=false; + + configdir = g_build_filename( g_get_user_config_dir(), "sakura", NULL ); + if( ! g_file_test( g_get_user_config_dir(), G_FILE_TEST_EXISTS) ) + g_mkdir( g_get_user_config_dir(), 0755 ); + if( ! g_file_test( configdir, G_FILE_TEST_EXISTS) ) + g_mkdir( configdir, 0755 ); + if (option_config_file) { + sakura.configfile=g_build_filename(configdir, option_config_file, NULL); + } else { + /* Use more standard-conforming path for config files, if available. */ + sakura.configfile=g_build_filename(configdir, DEFAULT_CONFIGFILE, NULL); + } + g_free(configdir); + + GError *error=NULL; + /* Open config file */ + if (!g_key_file_load_from_file(sakura.cfg, sakura.configfile, 0, &error)) { + /* If there's no file, ignore the error. A new one is created */ + if (error->code==G_KEY_FILE_ERROR_UNKNOWN_ENCODING || error->code==G_KEY_FILE_ERROR_INVALID_VALUE) { + g_error_free(error); + fprintf(stderr, "Not valid config file format\n"); + exit(EXIT_FAILURE); + } + } + + /* Add GFile monitor to control file external changes */ + GFile *cfgfile = g_file_new_for_path(sakura.configfile); + GFileMonitor *mon_cfgfile = g_file_monitor_file (cfgfile, 0, NULL, NULL); + g_signal_connect(G_OBJECT(mon_cfgfile), "changed", G_CALLBACK(sakura_conf_changed), NULL); + + gchar *cfgtmp = NULL; + + /* We can safely ignore errors from g_key_file_get_value(), since if the + * call to g_key_file_has_key() was successful, the key IS there. From the + * glib docs I don't know if we can ignore errors from g_key_file_has_key, + * too. I think we can: the only possible error is that the config file + * doesn't exist, but we have just read it! + */ + + for( i=0; i<NUM_COLORSETS; i++) { + char temp_name[20]; + + sprintf(temp_name, "colorset%d_fore", i+1); + if (!g_key_file_has_key(sakura.cfg, cfg_group, temp_name, NULL)) { + sakura_set_config_string(temp_name, "rgb(192,192,192)"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, temp_name, NULL); + gdk_rgba_parse(&sakura.forecolors[i], cfgtmp); + g_free(cfgtmp); + + sprintf(temp_name, "colorset%d_back", i+1); + if (!g_key_file_has_key(sakura.cfg, cfg_group, temp_name, NULL)) { + sakura_set_config_string(temp_name, "rgba(0,0,0,1)"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, temp_name, NULL); + gdk_rgba_parse(&sakura.backcolors[i], cfgtmp); + g_free(cfgtmp); + + sprintf(temp_name, "colorset%d_curs", i+1); + if (!g_key_file_has_key(sakura.cfg, cfg_group, temp_name, NULL)) { + sakura_set_config_string(temp_name, "rgb(255,255,255)"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, temp_name, NULL); + gdk_rgba_parse(&sakura.curscolors[i], cfgtmp); + g_free(cfgtmp); + + sprintf(temp_name, "colorset%d_key", i+1); + if (!g_key_file_has_key(sakura.cfg, cfg_group, temp_name, NULL)) { + sakura_set_keybind(temp_name, cs_keys[i]); + } + sakura.set_colorset_keys[i]= sakura_get_keybind(temp_name); + } + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "last_colorset", NULL)) { + sakura_set_config_integer("last_colorset", 1); + } + sakura.last_colorset = g_key_file_get_integer(sakura.cfg, cfg_group, "last_colorset", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "scroll_lines", NULL)) { + g_key_file_set_integer(sakura.cfg, cfg_group, "scroll_lines", DEFAULT_SCROLL_LINES); + } + sakura.scroll_lines = g_key_file_get_integer(sakura.cfg, cfg_group, "scroll_lines", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "font", NULL)) { + sakura_set_config_string("font", DEFAULT_FONT); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "font", NULL); + sakura.font = pango_font_description_from_string(cfgtmp); + free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "show_always_first_tab", NULL)) { + sakura_set_config_string("show_always_first_tab", "No"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "show_always_first_tab", NULL); + sakura.first_tab = (strcmp(cfgtmp, "Yes")==0) ? true : false; + free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "scrollbar", NULL)) { + sakura_set_config_boolean("scrollbar", FALSE); + } + sakura.show_scrollbar = g_key_file_get_boolean(sakura.cfg, cfg_group, "scrollbar", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "closebutton", NULL)) { + sakura_set_config_boolean("closebutton", TRUE); + } + sakura.show_closebutton = g_key_file_get_boolean(sakura.cfg, cfg_group, "closebutton", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "tabs_on_bottom", NULL)) { + sakura_set_config_boolean("tabs_on_bottom", FALSE); + } + sakura.tabs_on_bottom = g_key_file_get_boolean(sakura.cfg, cfg_group, "tabs_on_bottom", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "less_questions", NULL)) { + sakura_set_config_boolean("less_questions", FALSE); + } + sakura.less_questions = g_key_file_get_boolean(sakura.cfg, cfg_group, "less_questions", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "disable_numbered_tabswitch", NULL)) { + sakura_set_config_boolean("disable_numbered_tabswitch", FALSE); + } + sakura.disable_numbered_tabswitch = g_key_file_get_boolean(sakura.cfg, cfg_group, "disable_numbered_tabswitch", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "use_fading", NULL)) { + sakura_set_config_boolean("use_fading", FALSE); + } + sakura.use_fading = g_key_file_get_boolean(sakura.cfg, cfg_group, "use_fading", NULL); + + if(!g_key_file_has_key(sakura.cfg, cfg_group, "scrollable_tabs", NULL)) { + sakura_set_config_boolean("scrollable_tabs", TRUE); + } + sakura.scrollable_tabs = g_key_file_get_boolean(sakura.cfg, cfg_group, "scrollable_tabs", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "urgent_bell", NULL)) { + sakura_set_config_string("urgent_bell", "Yes"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "urgent_bell", NULL); + sakura.urgent_bell= (strcmp(cfgtmp, "Yes")==0) ? 1 : 0; + g_free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "audible_bell", NULL)) { + sakura_set_config_string("audible_bell", "Yes"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "audible_bell", NULL); + sakura.audible_bell= (strcmp(cfgtmp, "Yes")==0) ? 1 : 0; + g_free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "blinking_cursor", NULL)) { + sakura_set_config_string("blinking_cursor", "No"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "blinking_cursor", NULL); + sakura.blinking_cursor= (strcmp(cfgtmp, "Yes")==0) ? 1 : 0; + g_free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "stop_tab_cycling_at_end_tabs", NULL)) { + sakura_set_config_string("stop_tab_cycling_at_end_tabs", "No"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "stop_tab_cycling_at_end_tabs", NULL); + sakura.stop_tab_cycling_at_end_tabs= (strcmp(cfgtmp, "Yes")==0) ? 1 : 0; + g_free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "allow_bold", NULL)) { + sakura_set_config_string("allow_bold", "Yes"); + } + cfgtmp = g_key_file_get_value(sakura.cfg, cfg_group, "allow_bold", NULL); + sakura.allow_bold= (strcmp(cfgtmp, "Yes")==0) ? 1 : 0; + g_free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "cursor_type", NULL)) { + sakura_set_config_string("cursor_type", "VTE_CURSOR_SHAPE_BLOCK"); + } + sakura.cursor_type = g_key_file_get_integer(sakura.cfg, cfg_group, "cursor_type", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "word_chars", NULL)) { + sakura_set_config_string("word_chars", DEFAULT_WORD_CHARS); + } + sakura.word_chars = g_key_file_get_value(sakura.cfg, cfg_group, "word_chars", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "palette", NULL)) { + sakura_set_config_string("palette", DEFAULT_PALETTE); + } + cfgtmp = g_key_file_get_string(sakura.cfg, cfg_group, "palette", NULL); + if (strcmp(cfgtmp, "linux")==0) { + sakura.palette=linux_palette; + } else if (strcmp(cfgtmp, "gruvbox")==0) { + sakura.palette=gruvbox_palette; + } else if (strcmp(cfgtmp, "xterm")==0) { + sakura.palette=xterm_palette; + } else if (strcmp(cfgtmp, "rxvt")==0) { + sakura.palette=rxvt_palette; + } else if (strcmp(cfgtmp, "tango")==0) { + sakura.palette=tango_palette; + } else if (strcmp(cfgtmp, "solarized_dark")==0) { + sakura.palette=solarized_dark_palette; + } else { + sakura.palette=solarized_light_palette; + } + g_free(cfgtmp); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "add_tab_accelerator", NULL)) { + sakura_set_config_integer("add_tab_accelerator", DEFAULT_ADD_TAB_ACCELERATOR); + } + sakura.add_tab_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "add_tab_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "del_tab_accelerator", NULL)) { + sakura_set_config_integer("del_tab_accelerator", DEFAULT_DEL_TAB_ACCELERATOR); + } + sakura.del_tab_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "del_tab_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "switch_tab_accelerator", NULL)) { + sakura_set_config_integer("switch_tab_accelerator", DEFAULT_SWITCH_TAB_ACCELERATOR); + } + sakura.switch_tab_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "switch_tab_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "move_tab_accelerator", NULL)) { + sakura_set_config_integer("move_tab_accelerator", DEFAULT_MOVE_TAB_ACCELERATOR); + } + sakura.move_tab_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "move_tab_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "copy_accelerator", NULL)) { + sakura_set_config_integer("copy_accelerator", DEFAULT_COPY_ACCELERATOR); + } + sakura.copy_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "copy_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "scrollbar_accelerator", NULL)) { + sakura_set_config_integer("scrollbar_accelerator", DEFAULT_SCROLLBAR_ACCELERATOR); + } + sakura.scrollbar_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "scrollbar_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "open_url_accelerator", NULL)) { + sakura_set_config_integer("open_url_accelerator", DEFAULT_OPEN_URL_ACCELERATOR); + } + sakura.open_url_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "open_url_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "font_size_accelerator", NULL)) { + sakura_set_config_integer("font_size_accelerator", DEFAULT_FONT_SIZE_ACCELERATOR); + } + sakura.font_size_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "font_size_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "set_tab_name_accelerator", NULL)) { + sakura_set_config_integer("set_tab_name_accelerator", DEFAULT_SET_TAB_NAME_ACCELERATOR); + } + sakura.set_tab_name_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "set_tab_name_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "search_accelerator", NULL)) { + sakura_set_config_integer("search_accelerator", DEFAULT_SEARCH_ACCELERATOR); + } + sakura.search_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "search_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "add_tab_key", NULL)) { + sakura_set_keybind("add_tab_key", DEFAULT_ADD_TAB_KEY); + } + sakura.add_tab_key = sakura_get_keybind("add_tab_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "del_tab_key", NULL)) { + sakura_set_keybind("del_tab_key", DEFAULT_DEL_TAB_KEY); + } + sakura.del_tab_key = sakura_get_keybind("del_tab_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "prev_tab_key", NULL)) { + sakura_set_keybind("prev_tab_key", DEFAULT_PREV_TAB_KEY); + } + sakura.prev_tab_key = sakura_get_keybind("prev_tab_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "next_tab_key", NULL)) { + sakura_set_keybind("next_tab_key", DEFAULT_NEXT_TAB_KEY); + } + sakura.next_tab_key = sakura_get_keybind("next_tab_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "copy_key", NULL)) { + sakura_set_keybind( "copy_key", DEFAULT_COPY_KEY); + } + sakura.copy_key = sakura_get_keybind("copy_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "paste_key", NULL)) { + sakura_set_keybind("paste_key", DEFAULT_PASTE_KEY); + } + sakura.paste_key = sakura_get_keybind("paste_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "scrollbar_key", NULL)) { + sakura_set_keybind("scrollbar_key", DEFAULT_SCROLLBAR_KEY); + } + sakura.scrollbar_key = sakura_get_keybind("scrollbar_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "set_tab_name_key", NULL)) { + sakura_set_keybind("set_tab_name_key", DEFAULT_SET_TAB_NAME_KEY); + } + sakura.set_tab_name_key = sakura_get_keybind("set_tab_name_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "search_key", NULL)) { + sakura_set_keybind("search_key", DEFAULT_SEARCH_KEY); + } + sakura.search_key = sakura_get_keybind("search_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "increase_font_size_key", NULL)) { + sakura_set_keybind("increase_font_size_key", DEFAULT_INCREASE_FONT_SIZE_KEY); + } + sakura.increase_font_size_key = sakura_get_keybind("increase_font_size_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "decrease_font_size_key", NULL)) { + sakura_set_keybind("decrease_font_size_key", DEFAULT_DECREASE_FONT_SIZE_KEY); + } + sakura.decrease_font_size_key = sakura_get_keybind("decrease_font_size_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "fullscreen_key", NULL)) { + sakura_set_keybind("fullscreen_key", DEFAULT_FULLSCREEN_KEY); + } + sakura.fullscreen_key = sakura_get_keybind("fullscreen_key"); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "set_colorset_accelerator", NULL)) { + sakura_set_config_integer("set_colorset_accelerator", DEFAULT_SELECT_COLORSET_ACCELERATOR); + } + sakura.set_colorset_accelerator = g_key_file_get_integer(sakura.cfg, cfg_group, "set_colorset_accelerator", NULL); + + if (!g_key_file_has_key(sakura.cfg, cfg_group, "icon_file", NULL)) { + sakura_set_config_string("icon_file", ICON_FILE); + } + sakura.icon = g_key_file_get_string(sakura.cfg, cfg_group, "icon_file", NULL); + + /* set default title pattern from config or NULL */ + sakura.tab_default_title = g_key_file_get_string(sakura.cfg, cfg_group, "tab_default_title", NULL); + + /* Use always GTK header bar*/ + g_object_set(gtk_settings_get_default(), "gtk-dialogs-use-header", TRUE, NULL); + + sakura.provider = gtk_css_provider_new(); + + sakura.main_window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(sakura.main_window), "sakura"); + + /* Default terminal size*/ + sakura.columns = DEFAULT_COLUMNS; + sakura.rows = DEFAULT_ROWS; + + /* Create notebook and set style */ + sakura.notebook=gtk_notebook_new(); + gtk_notebook_set_scrollable((GtkNotebook*)sakura.notebook, sakura.scrollable_tabs); + + gchar *css = g_strdup_printf(NOTEBOOK_CSS); + gtk_css_provider_load_from_data(sakura.provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context(sakura.notebook); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(sakura.provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_free(css); + + /* Adding mask, for handle scroll events */ + gtk_widget_add_events(sakura.notebook, GDK_SCROLL_MASK); + + /* Figure out if we have rgba capabilities. FIXME: Is this really needed? */ + GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (sakura.main_window)); + GdkVisual *visual = gdk_screen_get_rgba_visual (screen); + if (visual != NULL && gdk_screen_is_composited (screen)) { + gtk_widget_set_visual (GTK_WIDGET (sakura.main_window), visual); + } + + /* Command line options initialization */ + + /* Set argv for forked childs. Real argv vector starts at argv[1] because we're + using G_SPAWN_FILE_AND_ARGV_ZERO to be able to launch login shells */ + sakura.argv[0]=g_strdup(g_getenv("SHELL")); + if (option_login) { + sakura.argv[1]=g_strdup_printf("-%s", g_getenv("SHELL")); + } else { + sakura.argv[1]=g_strdup(g_getenv("SHELL")); + } + sakura.argv[2]=NULL; + + if (option_title) { + gtk_window_set_title(GTK_WINDOW(sakura.main_window), option_title); + } + + if (option_columns) { + sakura.columns = option_columns; + } + + if (option_rows) { + sakura.rows = option_rows; + } + + /* Add datadir path to icon name and set icon */ + gchar *icon_path; error=NULL; + if (option_icon) { + icon_path = g_strdup_printf("%s", option_icon); + } else { + icon_path = g_strdup_printf(DATADIR "/pixmaps/%s", sakura.icon); + } + gtk_window_set_icon_from_file(GTK_WINDOW(sakura.main_window), icon_path, &error); + g_free(icon_path); icon_path=NULL; + if (error) g_error_free(error); + + if (option_font) { + sakura.font=pango_font_description_from_string(option_font); + } + + if (option_colorset && option_colorset>0 && option_colorset <= NUM_COLORSETS) { + sakura.last_colorset=option_colorset; + } + + /* These options are exclusive */ + if (option_fullscreen) { + sakura_fullscreen(NULL, NULL); + } else if (option_maximize) { + gtk_window_maximize(GTK_WINDOW(sakura.main_window)); + } + + sakura.label_count=1; + sakura.fullscreen=FALSE; + sakura.resized=FALSE; + sakura.keep_fc=false; + sakura.externally_modified=false; + + error=NULL; + sakura.http_vteregexp=vte_regex_new_for_match(HTTP_REGEXP, strlen(HTTP_REGEXP), 0, &error); + if (!sakura.http_vteregexp) { + SAY("http_regexp: %s", error->message); + g_error_free(error); + } + error=NULL; + sakura.mail_vteregexp=vte_regex_new_for_match(MAIL_REGEXP, strlen(MAIL_REGEXP), 0, &error); + if (!sakura.mail_vteregexp) { + SAY("mail_regexp: %s", error->message); + g_error_free(error); + } + + gtk_container_add(GTK_CONTAINER(sakura.main_window), sakura.notebook); + + /* Adding mask to see wheter sakura window is focused or not */ + //gtk_widget_add_events(sakura.main_window, GDK_FOCUS_CHANGE_MASK); + sakura.focused = true; + sakura.first_focus = true; + sakura.faded = false; + + sakura_init_popup(); + + g_signal_connect(G_OBJECT(sakura.main_window), "delete_event", G_CALLBACK(sakura_delete_event), NULL); + g_signal_connect(G_OBJECT(sakura.main_window), "destroy", G_CALLBACK(sakura_destroy_window), NULL); + g_signal_connect(G_OBJECT(sakura.main_window), "key-press-event", G_CALLBACK(sakura_key_press), NULL); + g_signal_connect(G_OBJECT(sakura.main_window), "configure-event", G_CALLBACK(sakura_resized_window), NULL); + g_signal_connect(G_OBJECT(sakura.main_window), "focus-out-event", G_CALLBACK(sakura_focus_out), NULL); + g_signal_connect(G_OBJECT(sakura.main_window), "focus-in-event", G_CALLBACK(sakura_focus_in), NULL); + g_signal_connect(G_OBJECT(sakura.main_window), "show", G_CALLBACK(sakura_window_show_event), NULL); + //g_signal_connect(G_OBJECT(sakura.notebook), "focus-in-event", G_CALLBACK(sakura_notebook_focus_in), NULL); + g_signal_connect(sakura.notebook, "scroll-event", G_CALLBACK(sakura_notebook_scroll), NULL); +} + + +static void +sakura_init_popup() +{ + GtkWidget *item_new_tab, *item_set_name, *item_close_tab, *item_copy, + *item_paste, *item_select_font, *item_select_colors, + *item_set_title, *item_fullscreen, + *item_toggle_scrollbar, *item_options, + *item_show_first_tab, *item_urgent_bell, *item_audible_bell, + *item_blinking_cursor, *item_allow_bold, *item_other_options, + *item_cursor, *item_cursor_block, *item_cursor_underline, *item_cursor_ibeam, + *item_palette, *item_palette_tango, *item_palette_linux, *item_palette_xterm, *item_palette_rxvt, + *item_palette_solarized_dark, *item_palette_solarized_light, *item_palette_gruvbox, + *item_show_close_button, *item_tabs_on_bottom, *item_less_questions, + *item_disable_numbered_tabswitch, *item_use_fading, *item_stop_tab_cycling_at_end_tabs; + GtkWidget *options_menu, *other_options_menu, *cursor_menu, *palette_menu; + + sakura.item_open_mail=gtk_menu_item_new_with_label(_("Open mail")); + sakura.item_open_link=gtk_menu_item_new_with_label(_("Open link")); + sakura.item_copy_link=gtk_menu_item_new_with_label(_("Copy link")); + item_new_tab=gtk_menu_item_new_with_label(_("New tab")); + item_set_name=gtk_menu_item_new_with_label(_("Set tab name...")); + item_close_tab=gtk_menu_item_new_with_label(_("Close tab")); + item_fullscreen=gtk_menu_item_new_with_label(_("Full screen")); + item_copy=gtk_menu_item_new_with_label(_("Copy")); + item_paste=gtk_menu_item_new_with_label(_("Paste")); + item_select_font=gtk_menu_item_new_with_label(_("Select font...")); + item_select_colors=gtk_menu_item_new_with_label(_("Select colors...")); + item_set_title=gtk_menu_item_new_with_label(_("Set window title...")); + + item_options=gtk_menu_item_new_with_label(_("Options")); + + item_other_options=gtk_menu_item_new_with_label(_("More")); + item_show_first_tab=gtk_check_menu_item_new_with_label(_("Always show tab bar")); + item_tabs_on_bottom=gtk_check_menu_item_new_with_label(_("Tabs at bottom")); + item_show_close_button=gtk_check_menu_item_new_with_label(_("Show close button on tabs")); + item_toggle_scrollbar=gtk_check_menu_item_new_with_label(_("Show scrollbar")); + item_less_questions=gtk_check_menu_item_new_with_label(_("Don't show exit dialog")); + item_urgent_bell=gtk_check_menu_item_new_with_label(_("Set urgent bell")); + item_audible_bell=gtk_check_menu_item_new_with_label(_("Set audible bell")); + item_blinking_cursor=gtk_check_menu_item_new_with_label(_("Set blinking cursor")); + item_allow_bold=gtk_check_menu_item_new_with_label(_("Enable bold font")); + item_stop_tab_cycling_at_end_tabs=gtk_check_menu_item_new_with_label(_("Stop tab cycling at end tabs")); + item_disable_numbered_tabswitch=gtk_check_menu_item_new_with_label(_("Disable numbered tabswitch")); + item_use_fading=gtk_check_menu_item_new_with_label(_("Enable focus fade")); + item_cursor=gtk_menu_item_new_with_label(_("Set cursor type")); + item_cursor_block=gtk_radio_menu_item_new_with_label(NULL, _("Block")); + item_cursor_underline=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_cursor_block), _("Underline")); + item_cursor_ibeam=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_cursor_block), _("IBeam")); + item_palette=gtk_menu_item_new_with_label(_("Set palette")); + item_palette_tango=gtk_radio_menu_item_new_with_label(NULL, "Tango"); + item_palette_linux=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_palette_tango), "Linux"); + item_palette_gruvbox=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_palette_tango), "Gruvbox"); + item_palette_xterm=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_palette_tango), "Xterm"); + item_palette_rxvt=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_palette_tango), "rxvt"); + item_palette_solarized_dark=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_palette_tango), "Solarized dark"); + item_palette_solarized_light=gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(item_palette_tango), "Solarized light"); + + /* Show defaults in menu items */ + gchar *cfgtmp = NULL; + + if (sakura.first_tab) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_show_first_tab), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_show_first_tab), FALSE); + } + + if (sakura.show_closebutton) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_show_close_button), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_show_close_button), FALSE); + } + + if (sakura.tabs_on_bottom) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_tabs_on_bottom), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_tabs_on_bottom), FALSE); + } + + if (sakura.less_questions) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_less_questions), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_less_questions), FALSE); + } + + if (sakura.show_scrollbar) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_toggle_scrollbar), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_toggle_scrollbar), FALSE); + } + + if (sakura.disable_numbered_tabswitch) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_disable_numbered_tabswitch), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_disable_numbered_tabswitch), FALSE); + } + + if (sakura.use_fading) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_use_fading), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_use_fading), FALSE); + } + + if (sakura.urgent_bell) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_urgent_bell), TRUE); + } + + if (sakura.audible_bell) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_audible_bell), TRUE); + } + + if (sakura.blinking_cursor) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_blinking_cursor), TRUE); + } + + if (sakura.allow_bold) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_allow_bold), TRUE); + } + + if (sakura.stop_tab_cycling_at_end_tabs) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_stop_tab_cycling_at_end_tabs), TRUE); + } + + switch (sakura.cursor_type){ + case VTE_CURSOR_SHAPE_BLOCK: + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_cursor_block), TRUE); + break; + case VTE_CURSOR_SHAPE_UNDERLINE: + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_cursor_underline), TRUE); + break; + case VTE_CURSOR_SHAPE_IBEAM: + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_cursor_ibeam), TRUE); + } + + cfgtmp = g_key_file_get_string(sakura.cfg, cfg_group, "palette", NULL); + if (strcmp(cfgtmp, "linux")==0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_linux), TRUE); + } else if (strcmp(cfgtmp, "gruvbox")==0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_gruvbox), TRUE); + } else if (strcmp(cfgtmp, "tango")==0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_tango), TRUE); + } else if (strcmp(cfgtmp, "xterm")==0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_xterm), TRUE); + } else if (strcmp(cfgtmp, "rxvt")==0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_rxvt), TRUE); + } else if (strcmp(cfgtmp, "solarized_dark")==0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_solarized_dark), TRUE); + } else { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_palette_solarized_light), TRUE); + } + g_free(cfgtmp); + + sakura.open_link_separator=gtk_separator_menu_item_new(); + + sakura.menu=gtk_menu_new(); + //sakura.labels_menu=gtk_menu_new(); + + /* Add items to popup menu */ + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), sakura.item_open_mail); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), sakura.item_open_link); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), sakura.item_copy_link); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), sakura.open_link_separator); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_new_tab); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_set_name); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_close_tab); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_fullscreen); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_copy); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_paste); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(sakura.menu), item_options); + + options_menu=gtk_menu_new(); + other_options_menu=gtk_menu_new(); + cursor_menu=gtk_menu_new(); + palette_menu=gtk_menu_new(); + + gtk_menu_shell_append(GTK_MENU_SHELL(options_menu), item_set_title); + gtk_menu_shell_append(GTK_MENU_SHELL(options_menu), item_select_colors); + gtk_menu_shell_append(GTK_MENU_SHELL(options_menu), item_select_font); + gtk_menu_shell_append(GTK_MENU_SHELL(options_menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(options_menu), item_other_options); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_show_first_tab); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_tabs_on_bottom); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_show_close_button); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), gtk_separator_menu_item_new()); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_toggle_scrollbar); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_less_questions); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_urgent_bell); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_audible_bell); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_disable_numbered_tabswitch); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_use_fading); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_blinking_cursor); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_allow_bold); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_stop_tab_cycling_at_end_tabs); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_cursor); + gtk_menu_shell_append(GTK_MENU_SHELL(cursor_menu), item_cursor_block); + gtk_menu_shell_append(GTK_MENU_SHELL(cursor_menu), item_cursor_underline); + gtk_menu_shell_append(GTK_MENU_SHELL(cursor_menu), item_cursor_ibeam); + gtk_menu_shell_append(GTK_MENU_SHELL(other_options_menu), item_palette); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_tango); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_linux); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_gruvbox); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_xterm); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_rxvt); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_solarized_dark); + gtk_menu_shell_append(GTK_MENU_SHELL(palette_menu), item_palette_solarized_light); + + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item_options), options_menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item_other_options), other_options_menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item_cursor), cursor_menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item_palette), palette_menu); + + /* ... and finally assign callbacks to menuitems */ + g_signal_connect(G_OBJECT(item_new_tab), "activate", G_CALLBACK(sakura_new_tab), NULL); + g_signal_connect(G_OBJECT(item_set_name), "activate", G_CALLBACK(sakura_set_name_dialog), NULL); + g_signal_connect(G_OBJECT(item_close_tab), "activate", G_CALLBACK(sakura_close_tab), NULL); + g_signal_connect(G_OBJECT(item_select_font), "activate", G_CALLBACK(sakura_font_dialog), NULL); + g_signal_connect(G_OBJECT(item_copy), "activate", G_CALLBACK(sakura_copy), NULL); + g_signal_connect(G_OBJECT(item_paste), "activate", G_CALLBACK(sakura_paste), NULL); + g_signal_connect(G_OBJECT(item_select_colors), "activate", G_CALLBACK(sakura_color_dialog), NULL); + + g_signal_connect(G_OBJECT(item_show_first_tab), "activate", G_CALLBACK(sakura_show_first_tab), NULL); + g_signal_connect(G_OBJECT(item_tabs_on_bottom), "activate", G_CALLBACK(sakura_tabs_on_bottom), NULL); + g_signal_connect(G_OBJECT(item_less_questions), "activate", G_CALLBACK(sakura_less_questions), NULL); + g_signal_connect(G_OBJECT(item_show_close_button), "activate", G_CALLBACK(sakura_show_close_button), NULL); + g_signal_connect(G_OBJECT(item_toggle_scrollbar), "activate", G_CALLBACK(sakura_show_scrollbar), NULL); + g_signal_connect(G_OBJECT(item_urgent_bell), "activate", G_CALLBACK(sakura_urgent_bell), NULL); + g_signal_connect(G_OBJECT(item_audible_bell), "activate", G_CALLBACK(sakura_audible_bell), NULL); + g_signal_connect(G_OBJECT(item_blinking_cursor), "activate", G_CALLBACK(sakura_blinking_cursor), NULL); + g_signal_connect(G_OBJECT(item_allow_bold), "activate", G_CALLBACK(sakura_allow_bold), NULL); + g_signal_connect(G_OBJECT(item_stop_tab_cycling_at_end_tabs), "activate", G_CALLBACK(sakura_stop_tab_cycling_at_end_tabs), NULL); + g_signal_connect(G_OBJECT(item_disable_numbered_tabswitch), + "activate", G_CALLBACK(sakura_disable_numbered_tabswitch), NULL); + g_signal_connect(G_OBJECT(item_use_fading), "activate", G_CALLBACK(sakura_use_fading), NULL); + g_signal_connect(G_OBJECT(item_set_title), "activate", G_CALLBACK(sakura_set_title_dialog), NULL); + g_signal_connect(G_OBJECT(item_cursor_block), "activate", G_CALLBACK(sakura_set_cursor), "block"); + g_signal_connect(G_OBJECT(item_cursor_underline), "activate", G_CALLBACK(sakura_set_cursor), "underline"); + g_signal_connect(G_OBJECT(item_cursor_ibeam), "activate", G_CALLBACK(sakura_set_cursor), "ibeam"); + g_signal_connect(G_OBJECT(item_palette_tango), "activate", G_CALLBACK(sakura_set_palette), "tango"); + g_signal_connect(G_OBJECT(item_palette_linux), "activate", G_CALLBACK(sakura_set_palette), "linux"); + g_signal_connect(G_OBJECT(item_palette_gruvbox), "activate", G_CALLBACK(sakura_set_palette), "gruvbox"); + g_signal_connect(G_OBJECT(item_palette_xterm), "activate", G_CALLBACK(sakura_set_palette), "xterm"); + g_signal_connect(G_OBJECT(item_palette_rxvt), "activate", G_CALLBACK(sakura_set_palette), "rxvt"); + g_signal_connect(G_OBJECT(item_palette_solarized_dark), "activate", G_CALLBACK(sakura_set_palette), "solarized_dark"); + g_signal_connect(G_OBJECT(item_palette_solarized_light), "activate", G_CALLBACK(sakura_set_palette), "solarized_light"); + + g_signal_connect(G_OBJECT(sakura.item_open_mail), "activate", G_CALLBACK(sakura_open_mail), NULL); + g_signal_connect(G_OBJECT(sakura.item_open_link), "activate", G_CALLBACK(sakura_open_url), NULL); + g_signal_connect(G_OBJECT(sakura.item_copy_link), "activate", G_CALLBACK(sakura_copy_url), NULL); + g_signal_connect(G_OBJECT(item_fullscreen), "activate", G_CALLBACK(sakura_fullscreen), NULL); + + gtk_widget_show_all(sakura.menu); + +} + + +static void +sakura_destroy() +{ + SAY("Destroying sakura"); + + /* Delete all existing tabs */ + while (gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)) >= 1) { + sakura_del_tab(-1); + } + + g_key_file_free(sakura.cfg); + + pango_font_description_free(sakura.font); + + free(sakura.configfile); + + gtk_main_quit(); + +} + + +static void +sakura_set_size(void) +{ + struct terminal *term; + gint pad_x, pad_y; + gint char_width, char_height; + guint npages; + gint min_width, natural_width; + gint page; + + + term = sakura_get_page_term(sakura, 0); + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* Mayhaps an user resize happened. Check if row and columns have changed */ + if (sakura.resized) { + sakura.columns=vte_terminal_get_column_count(VTE_TERMINAL(term->vte)); + sakura.rows=vte_terminal_get_row_count(VTE_TERMINAL(term->vte)); + SAY("New columns %ld and rows %ld", sakura.columns, sakura.rows); + sakura.resized=FALSE; + } + + gtk_style_context_get_padding(gtk_widget_get_style_context(term->vte), + gtk_widget_get_state_flags(term->vte), + &term->padding); + pad_x = term->padding.left + term->padding.right; + pad_y = term->padding.top + term->padding.bottom; + //SAY("padding x %d y %d", pad_x, pad_y); + char_width = vte_terminal_get_char_width(VTE_TERMINAL(term->vte)); + char_height = vte_terminal_get_char_height(VTE_TERMINAL(term->vte)); + + sakura.width = pad_x + (char_width * sakura.columns); + sakura.height = pad_y + (char_height * sakura.rows); + + if (npages>=2 || sakura.first_tab) { + + /* TODO: Yeah i know, this is utter shit. Remove this ugly hack and set geometry hints*/ + if (!sakura.show_scrollbar) + //sakura.height += min_height - 10; + sakura.height += 10; + else + //sakura.height += min_height - 47; + sakura.height += 47; + + sakura.width += 8; + sakura.width += /* (hb*2)+*/ (pad_x*2); + } + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + + gtk_widget_get_preferred_width(term->scrollbar, &min_width, &natural_width); + //SAY("SCROLLBAR min width %d natural width %d", min_width, natural_width); + if(sakura.show_scrollbar) { + sakura.width += min_width; + } + + /* GTK does not ignore resize for maximized windows on some systems, + so we do need check if it's maximized or not */ + GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(sakura.main_window)); + if(gdk_window != NULL) { + if(gdk_window_get_state(gdk_window) & GDK_WINDOW_STATE_MAXIMIZED) { + SAY("window is maximized, will not resize"); + return; + } + } + + gtk_window_resize(GTK_WINDOW(sakura.main_window), sakura.width, sakura.height); + SAY("Resized to %d %d", sakura.width, sakura.height); +} + + +static void +sakura_set_font() +{ + gint n_pages; + struct terminal *term; + int i; + + n_pages=gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* Set the font for all tabs */ + for (i = (n_pages - 1); i >= 0; i--) { + term = sakura_get_page_term(sakura, i); + vte_terminal_set_font(VTE_TERMINAL(term->vte), sakura.font); + } +} + + +static void +sakura_move_tab(gint direction) +{ + gint page, n_pages; + GtkWidget *child; + + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + n_pages=gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + child=gtk_notebook_get_nth_page(GTK_NOTEBOOK(sakura.notebook), page); + + if (direction==FORWARD) { + if (page!=n_pages-1) + gtk_notebook_reorder_child(GTK_NOTEBOOK(sakura.notebook), child, page+1); + } else { + if (page!=0) + gtk_notebook_reorder_child(GTK_NOTEBOOK(sakura.notebook), child, page-1); + } +} + + +/* Find the notebook page for the vte terminal passed as a parameter */ +static gint +sakura_find_tab(VteTerminal *vte_term) +{ + gint matched_page, page, n_pages; + struct terminal *term; + + n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + matched_page = -1; + page = 0; + + do { + term = sakura_get_page_term(sakura, page); + if ((VteTerminal *)term->vte == vte_term) { + matched_page=page; + } + page++; + } while (page < n_pages); + + return (matched_page); +} + + +static void +sakura_set_tab_label_text(const gchar *title, gint page) +{ + struct terminal *term; + gchar *chopped_title; + + term = sakura_get_page_term(sakura, page); + + if ( (title!=NULL) && (g_strcmp0(title, "") !=0) ) { + /* Chop to max size. TODO: Should it be configurable by the user? */ + chopped_title = g_strndup(title, TAB_MAX_SIZE); + /* Honor the minimum tab label size */ + while (strlen(chopped_title)< TAB_MIN_SIZE) { + char *old_ptr = chopped_title; + chopped_title = g_strconcat(chopped_title, " ", NULL); + free(old_ptr); + } + gtk_label_set_text(GTK_LABEL(term->label), chopped_title); + free(chopped_title); + } else { /* Use the default values */ + gtk_label_set_text(GTK_LABEL(term->label), term->label_text); + } +} + + +/* Callback for vte_terminal_spawn_async */ +void +sakura_spawn_callback (VteTerminal *vte, GPid pid, GError *error, gpointer user_data) +{ + struct terminal *term = (struct terminal *) user_data; + //term = sakura_get_page_term(sakura, page); + if (pid==-1) { /* Fork has failed */ + SAY("Error: %s", error->message); + } else { + term->pid=pid; + } +} + + +static void +sakura_add_tab() +{ + struct terminal *term; + GtkWidget *tab_label_hbox; + GtkWidget *close_button; + int index; + int npages; + gchar *cwd = NULL; + gchar *label_text = _("Terminal %d"); + + term = g_new0( struct terminal, 1 ); + + /* Create label for tabs */ + term->label_set_byuser=false; + + /* appling tab title pattern from config (https://answers.launchpad.net/sakura/+question/267951) */ + if(sakura.tab_default_title != NULL) { + label_text = sakura.tab_default_title; + term->label_set_byuser = true; + } + + term->label_text=g_strdup_printf(label_text, sakura.label_count++); + term->label=gtk_label_new(term->label_text); + + tab_label_hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); + gtk_widget_set_hexpand(tab_label_hbox, TRUE); + gtk_label_set_ellipsize (GTK_LABEL (term->label), PANGO_ELLIPSIZE_END); + gtk_box_pack_start(GTK_BOX(tab_label_hbox), term->label, TRUE, FALSE, 0); + + /* If the tab close button is enabled, create and add it to the tab */ + if (sakura.show_closebutton) { + close_button=gtk_button_new(); + /* Adding scroll-event to button, to propagate it to notebook (fix for scroll event when pointer is above the button) */ + gtk_widget_add_events(close_button, GDK_SCROLL_MASK); + + gtk_widget_set_name(close_button, "closebutton"); + gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); + + GtkWidget *image=gtk_image_new_from_icon_name("window-close", GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (close_button), image); + gtk_box_pack_start(GTK_BOX(tab_label_hbox), close_button, FALSE, FALSE, 0); + } + + if (sakura.tabs_on_bottom) { + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(sakura.notebook), GTK_POS_BOTTOM); + } + + /* Set tab title style */ + gchar *css = g_strdup_printf(TAB_TITLE_CSS); + gtk_css_provider_load_from_data(sakura.provider, css, -1, NULL); + GtkStyleContext *context = gtk_widget_get_style_context(tab_label_hbox); + gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(sakura.provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_free(css); + + gtk_widget_show_all(tab_label_hbox); + + /* Create new vte terminal, scrollbar, and pack it */ + term->vte=vte_terminal_new(); + term->scrollbar=gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(term->vte))); + term->hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(term->hbox), term->vte, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(term->hbox), term->scrollbar, FALSE, FALSE, 0); + + term->colorset=sakura.last_colorset-1; + + /* Select the directory to use for the new tab */ + index = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + if(index >= 0) { + struct terminal *prev_term; + prev_term = sakura_get_page_term( sakura, index ); + cwd = sakura_get_term_cwd( prev_term ); + + term->colorset = prev_term->colorset; + } + if (!cwd) + cwd = g_get_current_dir(); + + /* Keep values when adding tabs */ + sakura.keep_fc=true; + + if ((index=gtk_notebook_append_page(GTK_NOTEBOOK(sakura.notebook), term->hbox, tab_label_hbox))==-1) { + sakura_error("Cannot create a new tab"); + exit(1); + } + + gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(sakura.notebook), term->hbox, TRUE); + // TODO: Set group id to support detached tabs + // gtk_notebook_set_tab_detachable(GTK_NOTEBOOK(sakura.notebook), term->hbox, TRUE); + + sakura_set_page_term(sakura, index, term ); + + /* vte signals */ + g_signal_connect(G_OBJECT(term->vte), "bell", G_CALLBACK(sakura_beep), NULL); + g_signal_connect(G_OBJECT(term->vte), "increase-font-size", G_CALLBACK(sakura_increase_font), NULL); + g_signal_connect(G_OBJECT(term->vte), "decrease-font-size", G_CALLBACK(sakura_decrease_font), NULL); + g_signal_connect(G_OBJECT(term->vte), "child-exited", G_CALLBACK(sakura_child_exited), NULL); + g_signal_connect(G_OBJECT(term->vte), "eof", G_CALLBACK(sakura_eof), NULL); + g_signal_connect(G_OBJECT(term->vte), "window-title-changed", G_CALLBACK(sakura_title_changed), NULL); + g_signal_connect_swapped(G_OBJECT(term->vte), "button-press-event", G_CALLBACK(sakura_button_press), sakura.menu); + + /* Notebook signals */ + g_signal_connect(G_OBJECT(sakura.notebook), "page-removed", G_CALLBACK(sakura_page_removed), NULL); + if (sakura.show_closebutton) { + g_signal_connect(G_OBJECT(close_button), "clicked", G_CALLBACK(sakura_closebutton_clicked), term->hbox); + } + + /* Since vte-2.91 env is properly overwritten */ + char *command_env[2]={"TERM=xterm-256color",0}; + /* First tab */ + npages=gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + if (npages == 1) { + if (sakura.first_tab) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), TRUE); + } else { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), FALSE); + } + + gtk_notebook_set_show_border(GTK_NOTEBOOK(sakura.notebook), FALSE); + sakura_set_font(); + sakura_set_colors(); + /* Set size before showing the widgets but after setting the font */ + sakura_set_size(); + + gtk_widget_show_all(sakura.notebook); + if (!sakura.show_scrollbar) { + gtk_widget_hide(term->scrollbar); + } + + gtk_widget_show(sakura.main_window); + +#ifdef GDK_WINDOWING_X11 + /* Set WINDOWID env variable */ + GdkDisplay *display = gdk_display_get_default(); + + if (GDK_IS_X11_DISPLAY (display)) { + GdkWindow *gwin = gtk_widget_get_window (sakura.main_window); + if (gwin != NULL) { + guint winid = gdk_x11_window_get_xid (gwin); + gchar *winidstr = g_strdup_printf ("%d", winid); + g_setenv ("WINDOWID", winidstr, FALSE); + g_free (winidstr); + } + } +#endif + + int command_argc=0; char **command_argv; + if (option_execute||option_xterm_execute) { + GError *gerror = NULL; + gchar *path; + + if(option_execute) { + /* -x option */ + if (!g_shell_parse_argv(option_execute, &command_argc, &command_argv, &gerror)) { + switch (gerror->code) { + case G_SHELL_ERROR_EMPTY_STRING: + sakura_error("Empty exec string"); + exit(1); + break; + case G_SHELL_ERROR_BAD_QUOTING: + sakura_error("Cannot parse command line arguments: mangled quoting"); + exit(1); + break; + case G_SHELL_ERROR_FAILED: + sakura_error("Error in exec option command line arguments"); + exit(1); + } + g_error_free(gerror); + } + } else { + /* -e option - last in the command line, takes all extra arguments */ + if (option_xterm_args) { + gchar *command_joined; + command_joined = g_strjoinv(" ", option_xterm_args); + if (!g_shell_parse_argv(command_joined, &command_argc, &command_argv, &gerror)) { + switch (gerror->code) { + case G_SHELL_ERROR_EMPTY_STRING: + sakura_error("Empty exec string"); + exit(1); + break; + case G_SHELL_ERROR_BAD_QUOTING: + sakura_error("Cannot parse command line arguments: mangled quoting"); + exit(1); + case G_SHELL_ERROR_FAILED: + sakura_error("Error in exec option command line arguments"); + exit(1); + } + } + if (gerror!=NULL) g_error_free(gerror); + g_free(command_joined); + } + } + + /* Check if the command is valid */ + if (command_argc > 0) { + path=g_find_program_in_path(command_argv[0]); + if (path) { + vte_terminal_spawn_async(VTE_TERMINAL(term->vte), VTE_PTY_NO_HELPER, NULL, command_argv, command_env, + G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, -1, NULL, sakura_spawn_callback, term); + } else { + sakura_error("%s command not found", command_argv[0]); + command_argc=0; + //exit(1); + } + free(path); + g_strfreev(command_argv); g_strfreev(option_xterm_args); + } + } // else { /* No execute option */ + + /* Only fork if there is no execute option or if it has failed */ + if ( (!option_execute && !option_xterm_args) || (command_argc==0)) { + if (option_hold==TRUE) { + sakura_error("Hold option given without any command"); + option_hold=FALSE; + } + vte_terminal_spawn_async(VTE_TERMINAL(term->vte), VTE_PTY_NO_HELPER, cwd, sakura.argv, command_env, + G_SPAWN_SEARCH_PATH|G_SPAWN_FILE_AND_ARGV_ZERO, NULL, NULL, NULL, -1, NULL, sakura_spawn_callback, term); + } + /* Not the first tab */ + } else { + sakura_set_font(); + sakura_set_colors(); + gtk_widget_show_all(term->hbox); + if (!sakura.show_scrollbar) { + gtk_widget_hide(term->scrollbar); + } + + if (npages==2) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), TRUE); + sakura_set_size(); + } + /* Call set_current page after showing the widget: gtk ignores this + * function in the window is not visible *sigh*. Gtk documentation + * says this is for "historical" reasons. Me arse */ + gtk_notebook_set_current_page(GTK_NOTEBOOK(sakura.notebook), index); + vte_terminal_spawn_async(VTE_TERMINAL(term->vte), VTE_PTY_NO_HELPER, cwd, sakura.argv, command_env, + G_SPAWN_SEARCH_PATH|G_SPAWN_FILE_AND_ARGV_ZERO, NULL, NULL, NULL, -1, NULL, sakura_spawn_callback, term); + } + + free(cwd); + + /* Init vte terminal */ + vte_terminal_set_scrollback_lines(VTE_TERMINAL(term->vte), sakura.scroll_lines); + vte_terminal_match_add_regex(VTE_TERMINAL(term->vte), sakura.http_vteregexp, PCRE2_CASELESS); + vte_terminal_match_add_regex(VTE_TERMINAL(term->vte), sakura.mail_vteregexp, PCRE2_CASELESS); + vte_terminal_set_mouse_autohide(VTE_TERMINAL(term->vte), TRUE); + vte_terminal_set_backspace_binding(VTE_TERMINAL(term->vte), VTE_ERASE_ASCII_DELETE); + vte_terminal_set_word_char_exceptions(VTE_TERMINAL(term->vte), sakura.word_chars); + vte_terminal_set_audible_bell (VTE_TERMINAL(term->vte), sakura.audible_bell ? TRUE : FALSE); + vte_terminal_set_cursor_blink_mode (VTE_TERMINAL(term->vte), sakura.blinking_cursor ? VTE_CURSOR_BLINK_ON : VTE_CURSOR_BLINK_OFF); + vte_terminal_set_allow_bold (VTE_TERMINAL(term->vte), sakura.allow_bold ? TRUE : FALSE); + vte_terminal_set_cursor_shape (VTE_TERMINAL(term->vte), sakura.cursor_type); + + //sakura_set_colors(); + + /* FIXME: Possible race here. Find some way to force to process all configure + * events before setting keep_fc again to false */ + sakura.keep_fc=false; +} + + +/* Delete the notebook tab passed as a parameter */ +static void +sakura_del_tab(gint page) +{ + struct terminal *term; + gint npages; + + term = sakura_get_page_term(sakura, page); + npages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)); + + /* When there's only one tab use the shell title, if provided */ + if (npages==2) { + const char *title; + + term = sakura_get_page_term(sakura, 0); + title = vte_terminal_get_window_title(VTE_TERMINAL(term->vte)); + if (title!=NULL) + gtk_window_set_title(GTK_WINDOW(sakura.main_window), title); + } + + term = sakura_get_page_term(sakura, page); + + /* Do the first tab checks BEFORE deleting the tab, to ensure correct + * sizes are calculated when the tab is deleted */ + if ( npages == 2) { + if (sakura.first_tab) { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), TRUE); + } else { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(sakura.notebook), FALSE); + } + sakura.keep_fc=true; + } + + gtk_widget_hide(term->hbox); + gtk_notebook_remove_page(GTK_NOTEBOOK(sakura.notebook), page); + + /* Find the next page, if it exists, and grab focus */ + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(sakura.notebook)) > 0) { + page = gtk_notebook_get_current_page(GTK_NOTEBOOK(sakura.notebook)); + term = sakura_get_page_term(sakura, page); + gtk_widget_grab_focus(term->vte); + } +} + + +static void +sakura_set_keybind(const gchar *key, guint value) +{ + char *valname; + + valname=gdk_keyval_name(value); + g_key_file_set_string(sakura.cfg, cfg_group, key, valname); + sakura.config_modified=TRUE; + //FIXME: free() valname? +} + + +static guint +sakura_get_keybind(const gchar *key) +{ + gchar *value; + guint retval=GDK_KEY_VoidSymbol; + + value=g_key_file_get_string(sakura.cfg, cfg_group, key, NULL); + if (value!=NULL){ + retval=gdk_keyval_from_name(value); + g_free(value); + } + + /* For backwards compatibility with integer values */ + /* If gdk_keyval_from_name fail, it seems to be integer value*/ + if ((retval==GDK_KEY_VoidSymbol)||(retval==0)) { + retval=g_key_file_get_integer(sakura.cfg, cfg_group, key, NULL); + } + + /* Always use uppercase value as keyval */ + return gdk_keyval_to_upper(retval); +} + + +static void +sakura_error(const char *format, ...) +{ + GtkWidget *dialog; + va_list args; + char* buff; + + va_start(args, format); + buff = malloc(sizeof(char)*ERROR_BUFFER_LENGTH); + vsnprintf(buff, sizeof(char)*ERROR_BUFFER_LENGTH, format, args); + va_end(args); + + dialog = gtk_message_dialog_new(GTK_WINDOW(sakura.main_window), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", buff); + gtk_window_set_title(GTK_WINDOW(dialog), _("Error message")); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + free(buff); +} + + +/* This function is used to fix bug #1393939 */ +static void +sakura_sanitize_working_directory() +{ + const gchar *home_directory = g_getenv("HOME"); + if (home_directory == NULL) { + home_directory = g_get_home_dir(); + } + + if (home_directory != NULL) { + if (chdir(home_directory)) { + fprintf(stderr, _("Cannot change working directory\n")); + exit(1); + } + } +} + + +int +main(int argc, char **argv) +{ + gchar *localedir; + int i; int n; + char **nargv; int nargc; + gboolean have_e; + + /* Localization */ + setlocale(LC_ALL, ""); + localedir=g_strdup_printf("%s/locale", DATADIR); + textdomain(GETTEXT_PACKAGE); + bindtextdomain(GETTEXT_PACKAGE, localedir); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + g_free(localedir); + + /* Rewrites argv to include a -- after the -e argument this is required to make + * sure GOption doesn't grab any arguments meant for the command being called */ + + /* Initialize nargv */ + nargv = (char**)calloc((argc+1), sizeof(char*)); + n=0; nargc=argc; + have_e=FALSE; + + for(i=0; i<argc; i++) { + if(!have_e && g_strcmp0(argv[i],"-e") == 0) + { + nargv[n]="-e"; + n++; + nargv[n]="--"; + nargc++; + have_e = TRUE; + } else { + nargv[n]=g_strdup(argv[i]); + } + n++; + } + + /* Options parsing */ + GError *error=NULL; + GOptionContext *context; GOptionGroup *option_group; + + context = g_option_context_new (_("- vte-based terminal emulator")); + option_group = gtk_get_option_group(TRUE); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_group_set_translation_domain(option_group, GETTEXT_PACKAGE); + g_option_context_add_group (context, option_group); + if (!g_option_context_parse (context, &nargc, &nargv, &error)) { + fprintf(stderr, "%s\n", error->message); + g_error_free(error); + exit(1); + } + + g_option_context_free(context); + + if (option_workdir && chdir(option_workdir)) { + fprintf(stderr, _("Cannot change working directory\n")); + exit(1); + } + + if (option_version) { + fprintf(stderr, _("sakura version is %s\n"), VERSION); + exit(1); + } + + if (option_ntabs <= 0) { + option_ntabs=1; + } + + /* Init stuff */ + gtk_init(&nargc, &nargv); g_strfreev(nargv); + sakura_init(); + + /* Add initial tabs (1 by default) */ + for (i=0; i<option_ntabs; i++) + sakura_add_tab(); + + sakura_sanitize_working_directory(); + + gtk_main(); + + return 0; +} |