/******************************************************************************* * Filename: sakura.c * Description: VTE-based terminal emulator * * Copyright (C) 2006-2012 David Gómez * Copyright (C) 2008 Hong Jen Yee (PCMan) * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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; itype != 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; cscolorset); /* 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; icolorset = 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; i0 && 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; imessage); 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