/* * This is free software; you can redistribute it and/or modify it under * the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #include "config.h" #include "debug.h" #include "tilda.h" #include "configsys.h" #include "tilda_window.h" #include "tilda_terminal.h" #include "key_grabber.h" #include #include #include #include #include #include #include #include #include #include #include #include static tilda_term* tilda_window_get_current_terminal (tilda_window *tw); static gboolean show_confirmation_dialog (tilda_window *tw, const char *message); static void tilda_window_apply_transparency (tilda_window *tw, gboolean status); static gboolean update_tilda_window_size (gpointer user_data); static GdkFilterReturn window_filter_function (GdkXEvent *xevent, GdkEvent *event, gpointer data); static void tilda_window_setup_alpha_mode (tilda_window *tw) { GdkScreen *screen; GdkVisual *visual; screen = gtk_widget_get_screen (GTK_WIDGET (tw->window)); visual = gdk_screen_get_rgba_visual (screen); if (visual == NULL) { visual = gdk_screen_get_system_visual (screen); } if (visual != NULL && gdk_screen_is_composited (screen)) { /* Set RGBA colormap if possible so VTE can use real alpha * channels for transparency. */ gtk_widget_set_visual (GTK_WIDGET (tw->window), visual); tw->have_argb_visual = TRUE; } else { tw->have_argb_visual = FALSE; } } static tilda_term* find_tt_in_g_list (tilda_window *tw, gint pos) { DEBUG_FUNCTION ("find_tt_in_g_list"); DEBUG_ASSERT (tw != NULL); DEBUG_ASSERT (tw->terms != NULL); GtkWidget *page, *current_page; GList *terms = g_list_first (tw->terms); tilda_term *result=NULL; current_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (tw->notebook), pos); do { page = TILDA_TERM(terms->data)->hbox; if (page == current_page) { result = terms->data; break; } } while ((terms = terms->next) != NULL); return result; } void tilda_window_close_current_tab (tilda_window *tw) { DEBUG_FUNCTION ("close_current_tab"); DEBUG_ASSERT (tw != NULL); gboolean can_close = TRUE; if (config_getbool ("confirm_close_tab")) { char * message = _("Are you sure you want to close this tab?"); can_close = show_confirmation_dialog (tw, message); } if (can_close) { gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook)); tilda_window_close_tab (tw, pos, FALSE); } } static gboolean show_confirmation_dialog (tilda_window *tw, const char *message) { gboolean result; GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW (tw->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, NULL); gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG(dialog), message); gtk_window_set_keep_above (GTK_WINDOW (dialog), TRUE); gint response = gtk_dialog_run (GTK_DIALOG (dialog)); switch (response) { case GTK_RESPONSE_OK: result = TRUE; break; default: result = FALSE; break; } gtk_widget_destroy (dialog); return result; } gint tilda_window_set_tab_position (tilda_window *tw, enum notebook_tab_positions pos) { const GtkPositionType gtk_pos[] = {GTK_POS_TOP, GTK_POS_BOTTOM, GTK_POS_LEFT, GTK_POS_RIGHT }; if ((pos < 0) || (pos > 4)) { g_printerr (_("You have a bad tab_pos in your configuration file\n")); pos = NB_TOP; } if(NB_HIDDEN == pos) { gtk_notebook_set_show_tabs (GTK_NOTEBOOK(tw->notebook), FALSE); } else { gtk_notebook_set_tab_pos (GTK_NOTEBOOK (tw->notebook), gtk_pos[pos]); if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) > 1) gtk_notebook_set_show_tabs (GTK_NOTEBOOK(tw->notebook), TRUE); } return 0; } void tilda_window_set_fullscreen(tilda_window *tw) { DEBUG_FUNCTION ("tilda_window_set_fullscreen"); DEBUG_ASSERT (tw != NULL); if (tw->fullscreen == TRUE) { gtk_window_fullscreen (GTK_WINDOW (tw->window)); } else { gtk_window_unfullscreen (GTK_WINDOW (tw->window)); } } gint toggle_fullscreen_cb (tilda_window *tw) { DEBUG_FUNCTION ("toggle_fullscreen_cb"); DEBUG_ASSERT (tw != NULL); tw->fullscreen = !tw->fullscreen; tilda_window_set_fullscreen(tw); // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the // keystroke into the vte terminal widget. return GDK_EVENT_STOP; } static gint toggle_transparency_cb (tilda_window *tw) { DEBUG_FUNCTION ("toggle_transparency"); DEBUG_ASSERT (tw != NULL); tilda_window_toggle_transparency(tw); return GDK_EVENT_STOP; } void tilda_window_toggle_transparency (tilda_window *tw) { gboolean status = !config_getbool ("enable_transparency"); config_setbool ("enable_transparency", status); tilda_window_apply_transparency (tw, status); } void tilda_window_refresh_transparency (tilda_window *tw) { gboolean status = config_getbool ("enable_transparency"); tilda_window_apply_transparency (tw, status); } static void tilda_window_apply_transparency (tilda_window *tw, gboolean status) { tilda_term *tt; guint i; GdkRGBA bg; bg.red = GUINT16_TO_FLOAT(config_getint ("back_red")); bg.green = GUINT16_TO_FLOAT(config_getint ("back_green")); bg.blue = GUINT16_TO_FLOAT(config_getint ("back_blue")); bg.alpha = (status ? GUINT16_TO_FLOAT(config_getint ("back_alpha")) : 1.0); for (i=0; iterms); i++) { tt = g_list_nth_data (tw->terms, i); vte_terminal_set_color_background(VTE_TERMINAL(tt->vte_term), &bg); } } static gboolean search_cb (TildaSearchBox *search, VteRegex *regex, TildaSearchDirection direction, gboolean wrap_on_search, tilda_window *tw) { VteTerminal *vte_terminal; tilda_term *term; term = tilda_window_get_current_terminal (tw); vte_terminal = VTE_TERMINAL (term->vte_term); vte_terminal_search_set_regex (vte_terminal, regex, (GRegexMatchFlags) 0); vte_terminal_search_set_wrap_around (vte_terminal, wrap_on_search); if (direction == SEARCH_BACKWARD) return vte_terminal_search_find_previous (vte_terminal); else return vte_terminal_search_find_next (vte_terminal); } static void search_focus_out_cb (TildaSearchBox *box, tilda_window *tw) { gtk_widget_grab_focus (tilda_window_get_current_terminal (tw)->vte_term); } static gboolean toggle_searchbar_cb (tilda_window *tw) { tilda_window_toggle_searchbar (tw); return GDK_EVENT_STOP; } void tilda_window_toggle_searchbar (tilda_window *tw) { tilda_search_box_toggle (TILDA_SEARCH_BOX (tw->search)); } /* Zoom helpers */ static const double zoom_factors[] = { TERMINAL_SCALE_MINIMUM, TERMINAL_SCALE_XXXXX_SMALL, TERMINAL_SCALE_XXXX_SMALL, TERMINAL_SCALE_XXX_SMALL, PANGO_SCALE_XX_SMALL, PANGO_SCALE_X_SMALL, PANGO_SCALE_SMALL, PANGO_SCALE_MEDIUM, PANGO_SCALE_LARGE, PANGO_SCALE_X_LARGE, PANGO_SCALE_XX_LARGE, TERMINAL_SCALE_XXX_LARGE, TERMINAL_SCALE_XXXX_LARGE, TERMINAL_SCALE_XXXXX_LARGE, TERMINAL_SCALE_MAXIMUM }; static gboolean find_larger_zoom_factor (double current, double *found) { guint i; for (i = 0; i < G_N_ELEMENTS (zoom_factors); ++i) { /* Find a font that's larger than this one */ if ((zoom_factors[i] - current) > 1e-6) { *found = zoom_factors[i]; return TRUE; } } return FALSE; } static gboolean find_smaller_zoom_factor (double current, double *found) { int i; i = (int) G_N_ELEMENTS (zoom_factors) - 1; while (i >= 0) { /* Find a font that's smaller than this one */ if ((current - zoom_factors[i]) > 1e-6) { *found = zoom_factors[i]; return TRUE; } --i; } return FALSE; } /* Increase and Decrease and reset affects all tabs at once */ static gboolean normalize_font_size(tilda_window *tw) { tilda_term *tt; guint i; tw->current_scale_factor = PANGO_SCALE_MEDIUM; for (i=0; iterms); i++) { tt = g_list_nth_data (tw->terms, i); tilda_term_adjust_font_scale(tt, tw->current_scale_factor); } return GDK_EVENT_STOP; } static gboolean increase_font_size (tilda_window *tw) { tilda_term *tt; guint i; if(!find_larger_zoom_factor (tw->current_scale_factor, &tw->current_scale_factor)) { return GDK_EVENT_STOP; } for (i=0; iterms); i++) { tt = g_list_nth_data (tw->terms, i); tilda_term_adjust_font_scale(tt, tw->current_scale_factor); } return GDK_EVENT_STOP; } static gboolean decrease_font_size (tilda_window *tw) { tilda_term *tt; guint i; if(!find_smaller_zoom_factor (tw->current_scale_factor, &tw->current_scale_factor)) { return GDK_EVENT_STOP; } for (i=0; iterms); i++) { tt = g_list_nth_data (tw->terms, i); tilda_term_adjust_font_scale(tt, tw->current_scale_factor); } return GDK_EVENT_STOP; } gint tilda_window_next_tab (tilda_window *tw) { DEBUG_FUNCTION ("next_tab"); DEBUG_ASSERT (tw != NULL); int num_pages; int current_page; GtkNotebook *notebook; notebook = GTK_NOTEBOOK (tw->notebook); /* If we are on the last page, go to first page */ num_pages = gtk_notebook_get_n_pages (notebook); current_page = gtk_notebook_get_current_page (notebook); if ((num_pages - 1) == current_page) gtk_notebook_set_current_page (notebook, 0); else gtk_notebook_next_page (notebook); // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the // keystroke into the vte terminal widget. return GDK_EVENT_STOP; } gint tilda_window_prev_tab (tilda_window *tw) { DEBUG_FUNCTION ("prev_tab"); DEBUG_ASSERT (tw != NULL); int num_pages; int current_page; GtkNotebook *notebook; notebook = GTK_NOTEBOOK (tw->notebook); num_pages = gtk_notebook_get_n_pages (notebook); current_page = gtk_notebook_get_current_page (notebook); if (current_page == 0) gtk_notebook_set_current_page (notebook, (num_pages - 1)); else gtk_notebook_prev_page (notebook); // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the // keystroke into the vte terminal widget. return GDK_EVENT_STOP; } enum tab_direction { TAB_LEFT = -1, TAB_RIGHT = 1 }; static gint move_tab (tilda_window *tw, int direction) { DEBUG_FUNCTION ("move_tab"); DEBUG_ASSERT (tw != NULL); int num_pages; int current_page_index; int new_page_index; GtkWidget* current_page; GtkNotebook* notebook; notebook = GTK_NOTEBOOK (tw->notebook); num_pages = gtk_notebook_get_n_pages (notebook); if (num_pages > 1) { current_page_index = gtk_notebook_get_current_page (notebook); current_page = gtk_notebook_get_nth_page (notebook, current_page_index); /* wrap over if new_page_index over-/underflows */ new_page_index = (current_page_index + direction) % num_pages; gtk_notebook_reorder_child (notebook, current_page, new_page_index); } // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the // keystroke into the vte terminal widget. return GDK_EVENT_STOP; } static gint move_tab_left (tilda_window *tw) { DEBUG_FUNCTION ("move_tab_left"); return move_tab(tw, TAB_LEFT); } static gint move_tab_right (tilda_window *tw) { DEBUG_FUNCTION ("move_tab_right"); return move_tab(tw, TAB_RIGHT); } static gboolean focus_term (GtkWidget *widget, gpointer data) { DEBUG_FUNCTION ("focus_term"); DEBUG_ASSERT (data != NULL); DEBUG_ASSERT (widget != NULL); GList *list; GtkWidget *box; tilda_window *tw = TILDA_WINDOW(data); GtkWidget *n = GTK_WIDGET (tw->notebook); box = gtk_notebook_get_nth_page (GTK_NOTEBOOK(n), gtk_notebook_get_current_page(GTK_NOTEBOOK(n))); if (box != NULL) { list = gtk_container_get_children (GTK_CONTAINER(box)); gtk_widget_grab_focus (list->data); } // It worked. Having this return GDK_EVENT_STOP makes the callback not carry the // keystroke into the vte terminal widget. return GDK_EVENT_STOP; } static gboolean auto_hide_tick(gpointer data) { DEBUG_FUNCTION ("auto_hide_tick"); DEBUG_ASSERT (data != NULL); tilda_window *tw = TILDA_WINDOW(data); tw->auto_hide_current_time += tw->timer_resolution; if ((tw->auto_hide_current_time >= tw->auto_hide_max_time) || tw->current_state == STATE_UP) { pull(tw, PULL_UP, TRUE); tw->auto_hide_tick_handler = 0; return FALSE; } return TRUE; } /* Start auto hide tick */ static void start_auto_hide_tick(tilda_window *tw) { DEBUG_FUNCTION("start_auto_hide_tick"); // If the delay is less or equal to 1000ms then the timer is not precise // enough, because it takes already about 200ms to register it, so we // rather sleep for the given amount of time. const guint32 MAX_SLEEP_TIME = 1000; if ((tw->auto_hide_tick_handler == 0) && (tw->disable_auto_hide == FALSE)) { /* If there is no timer registered yet, then the auto_hide_tick_handler * has a value of zero. Next we need to make sure that the auto hide * max time is greater then zero, or else we can pull up immediately, * without the trouble of registering a timer. */ if (tw->auto_hide_max_time > MAX_SLEEP_TIME) { tw->auto_hide_current_time = 0; tw->auto_hide_tick_handler = g_timeout_add(tw->timer_resolution, auto_hide_tick, tw); } else if (tw->auto_hide_max_time > 0 && tw->auto_hide_max_time <= MAX_SLEEP_TIME) { // auto_hide_max_time is in milli seconds, so we need to convert to // microseconds by multiplying with 1000 g_usleep (tw->auto_hide_max_time * 1000); pull(tw, PULL_UP, TRUE); } else { pull(tw, PULL_UP, TRUE); } } } /* Stop auto hide tick */ static void stop_auto_hide_tick(tilda_window *tw) { if (tw->auto_hide_tick_handler != 0) { g_source_remove(tw->auto_hide_tick_handler); tw->auto_hide_tick_handler = 0; } } static gboolean mouse_enter (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, gpointer data) { DEBUG_FUNCTION ("mouse_enter"); DEBUG_ASSERT (data != NULL); DEBUG_ASSERT (widget != NULL); tilda_window *tw = TILDA_WINDOW(data); stop_auto_hide_tick(tw); return GDK_EVENT_STOP; } static gboolean mouse_leave (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, gpointer data) { DEBUG_FUNCTION ("mouse_leave"); DEBUG_ASSERT (data != NULL); DEBUG_ASSERT (widget != NULL); GdkEventCrossing *ev = (GdkEventCrossing*)event; tilda_window *tw = TILDA_WINDOW(data); if ((ev->mode != GDK_CROSSING_NORMAL) || (tw->auto_hide_on_mouse_leave == FALSE)) return GDK_EVENT_STOP; start_auto_hide_tick(tw); return GDK_EVENT_STOP; } static gboolean focus_out_event_cb (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, gpointer data) { DEBUG_FUNCTION ("focus_out_event_cb"); DEBUG_ASSERT (data != NULL); DEBUG_ASSERT (widget != NULL); tilda_window *tw = TILDA_WINDOW(data); /** * When the tilda 'key' to pull down/up the tilda window is pressed, then tilda will inevitably loose focus. The * problem is that we cannot distinguish whether it was focused before the key press occurred. Checking the xevent * type here allows us to distinguish these two cases: * * * We loose focus because of a KeyPress event * * We loose focus because another window gained focus or some other reason. * * Depending on one of the two cases we set the tw->focus_loss_on_keypress to true or false. We can then * check this flag in the pull() function that shows or hides the tilda window. This helps us to decide if * we just need to focus tilda or if we should hide it. */ XEvent xevent; XPeekEvent(gdk_x11_display_get_xdisplay(gdk_window_get_display(gtk_widget_get_window(tw->window))), &xevent); if(xevent.type == KeyPress) { tw->focus_loss_on_keypress = TRUE; } else { tw->focus_loss_on_keypress = FALSE; } if (tw->auto_hide_on_focus_lost == FALSE) return GDK_EVENT_PROPAGATE; start_auto_hide_tick(tw); return GDK_EVENT_PROPAGATE; } static void goto_tab (tilda_window *tw, guint i) { DEBUG_FUNCTION ("goto_tab"); DEBUG_ASSERT (tw != NULL); gtk_notebook_set_current_page (GTK_NOTEBOOK (tw->notebook), i); } static gboolean goto_tab_generic (tilda_window *tw, guint tab_number) { DEBUG_FUNCTION ("goto_tab_generic"); DEBUG_ASSERT (tw != NULL); if (g_list_length (tw->terms) > (tab_number-1)) { goto_tab (tw, tab_number - 1); } return GDK_EVENT_STOP; } /* These all just call the generic function since they're all basically the same * anyway. Unfortunately, they can't just be macros, since we need to be able to * create a pointer to them for callbacks. */ static gboolean goto_tab_1 (tilda_window *tw) { return goto_tab_generic (tw, 1); } static gboolean goto_tab_2 (tilda_window *tw) { return goto_tab_generic (tw, 2); } static gboolean goto_tab_3 (tilda_window *tw) { return goto_tab_generic (tw, 3); } static gboolean goto_tab_4 (tilda_window *tw) { return goto_tab_generic (tw, 4); } static gboolean goto_tab_5 (tilda_window *tw) { return goto_tab_generic (tw, 5); } static gboolean goto_tab_6 (tilda_window *tw) { return goto_tab_generic (tw, 6); } static gboolean goto_tab_7 (tilda_window *tw) { return goto_tab_generic (tw, 7); } static gboolean goto_tab_8 (tilda_window *tw) { return goto_tab_generic (tw, 8); } static gboolean goto_tab_9 (tilda_window *tw) { return goto_tab_generic (tw, 9); } static gboolean goto_tab_10 (tilda_window *tw) { return goto_tab_generic (tw, 10); } static gint ccopy (tilda_window *tw) { DEBUG_FUNCTION ("ccopy"); DEBUG_ASSERT (tw != NULL); DEBUG_ASSERT (tw->notebook != NULL); GtkWidget *current_page; GList *list; gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook)); current_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (tw->notebook), pos); list = gtk_container_get_children (GTK_CONTAINER(current_page)); if GTK_IS_SCROLLBAR(list->data) { list = list->next; } if ( ! vte_terminal_get_has_selection (VTE_TERMINAL (list->data)) ) { return GDK_EVENT_PROPAGATE; } vte_terminal_copy_clipboard_format (VTE_TERMINAL(list->data), VTE_FORMAT_TEXT); vte_terminal_unselect_all (VTE_TERMINAL(list->data)); return GDK_EVENT_STOP; } static gint cpaste (tilda_window *tw) { DEBUG_FUNCTION ("cpaste"); DEBUG_ASSERT (tw != NULL); DEBUG_ASSERT (tw->notebook != NULL); GtkWidget *current_page; GList *list; gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook)); current_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (tw->notebook), pos); list = gtk_container_get_children (GTK_CONTAINER (current_page)); if GTK_IS_SCROLLBAR(list->data) { list = list->next; } vte_terminal_paste_clipboard (VTE_TERMINAL(list->data)); return GDK_EVENT_STOP; } /* Tie a single keyboard shortcut to a callback function */ static gint tilda_add_config_accelerator_by_path(const gchar* key, const gchar* path, GCallback callback_func, tilda_window *tw) { guint accel_key; GdkModifierType accel_mods; GClosure *temp; if (strcmp(key, "NULL") == 0) return TRUE; gtk_accelerator_parse (config_getstr(key), &accel_key, &accel_mods); if (! ((accel_key == 0) && (accel_mods == 0)) ) // make sure it parsed properly { temp = g_cclosure_new_swap (callback_func, tw, NULL); gtk_accel_map_add_entry(path, accel_key, accel_mods); gtk_accel_group_connect_by_path(tw->accel_group, path, temp); } return 0; } gboolean tilda_window_update_keyboard_accelerators (const gchar* path, const gchar* value) { guint accel_key; GdkModifierType accel_mods; gtk_accelerator_parse (value, &accel_key, &accel_mods); return gtk_accel_map_change_entry(path, accel_key, accel_mods, FALSE); } /* This function does the setup of the keyboard acceleratos. It should only be called once when the tilda window is * initialized. Use tilda_window_update_keyboard_accelerators to update keybindings that have been changed by the user. */ static gint tilda_window_setup_keyboard_accelerators (tilda_window *tw) { /* Create Accel Group to add key codes for quit, next, prev and new tabs */ tw->accel_group = gtk_accel_group_new (); gtk_window_add_accel_group (GTK_WINDOW (tw->window), tw->accel_group); /* Set up keyboard shortcuts for Exit, Next Tab, Previous Tab, Move Tab, Add Tab, Close Tab, Copy, and Paste using key combinations defined in the config. */ tilda_add_config_accelerator_by_path("addtab_key", "/context/New Tab", G_CALLBACK(tilda_window_add_tab), tw); tilda_add_config_accelerator_by_path("closetab_key", "/context/Close Tab", G_CALLBACK(tilda_window_close_current_tab), tw); tilda_add_config_accelerator_by_path("copy_key", "/context/Copy", G_CALLBACK(ccopy), tw); tilda_add_config_accelerator_by_path("paste_key", "/context/Paste", G_CALLBACK(cpaste), tw); tilda_add_config_accelerator_by_path("fullscreen_key", "/context/Toggle Fullscreen", G_CALLBACK(toggle_fullscreen_cb), tw); tilda_add_config_accelerator_by_path("quit_key", "/context/Quit", G_CALLBACK(tilda_window_confirm_quit), tw); tilda_add_config_accelerator_by_path("toggle_transparency_key", "/context/Toggle Transparency", G_CALLBACK(toggle_transparency_cb), tw); tilda_add_config_accelerator_by_path("toggle_searchbar_key", "/context/Toggle Searchbar", G_CALLBACK(toggle_searchbar_cb), tw); tilda_add_config_accelerator_by_path("nexttab_key", "/context/Next Tab", G_CALLBACK(tilda_window_next_tab), tw); tilda_add_config_accelerator_by_path("prevtab_key", "/context/Previous Tab", G_CALLBACK(tilda_window_prev_tab), tw); tilda_add_config_accelerator_by_path("movetableft_key", "/context/Move Tab Left", G_CALLBACK(move_tab_left), tw); tilda_add_config_accelerator_by_path("movetabright_key", "/context/Move Tab Right", G_CALLBACK(move_tab_right), tw); tilda_add_config_accelerator_by_path("increase_font_size_key", "/context/Increase Font Size", G_CALLBACK(increase_font_size), tw); tilda_add_config_accelerator_by_path("decrease_font_size_key", "/context/Decrease Font Size", G_CALLBACK(decrease_font_size), tw); tilda_add_config_accelerator_by_path("normalize_font_size_key", "/context/Normalize Font Size", G_CALLBACK(normalize_font_size), tw); /* Set up keyboard shortcuts for Goto Tab # using key combinations defined in the config*/ /* Know a better way? Then you do. */ tilda_add_config_accelerator_by_path("gototab_1_key", "/context/Goto Tab 1", G_CALLBACK(goto_tab_1), tw); tilda_add_config_accelerator_by_path("gototab_2_key", "/context/Goto Tab 2", G_CALLBACK(goto_tab_2), tw); tilda_add_config_accelerator_by_path("gototab_3_key", "/context/Goto Tab 3", G_CALLBACK(goto_tab_3), tw); tilda_add_config_accelerator_by_path("gototab_4_key", "/context/Goto Tab 4", G_CALLBACK(goto_tab_4), tw); tilda_add_config_accelerator_by_path("gototab_5_key", "/context/Goto Tab 5", G_CALLBACK(goto_tab_5), tw); tilda_add_config_accelerator_by_path("gototab_6_key", "/context/Goto Tab 6", G_CALLBACK(goto_tab_6), tw); tilda_add_config_accelerator_by_path("gototab_7_key", "/context/Goto Tab 7", G_CALLBACK(goto_tab_7), tw); tilda_add_config_accelerator_by_path("gototab_8_key", "/context/Goto Tab 8", G_CALLBACK(goto_tab_8), tw); tilda_add_config_accelerator_by_path("gototab_9_key", "/context/Goto Tab 9", G_CALLBACK(goto_tab_9), tw); tilda_add_config_accelerator_by_path("gototab_10_key", "/context/Goto Tab 10", G_CALLBACK(goto_tab_10), tw); return 0; } static tilda_term* tilda_window_get_current_terminal (tilda_window *tw) { gint pos = gtk_notebook_get_current_page (GTK_NOTEBOOK (tw->notebook)); if (pos >= 0) { GList *found = g_list_nth (tw->terms, (guint) pos); if (found) { return found->data; } } return NULL; } static gint tilda_window_set_icon (tilda_window *tw, gchar *filename) { GdkPixbuf *window_icon = gdk_pixbuf_new_from_file (filename, NULL); if (window_icon == NULL) { TILDA_PERROR (); DEBUG_ERROR ("Cannot open window icon"); g_printerr (_("Unable to set tilda's icon: %s\n"), filename); return 1; } gtk_window_set_icon (GTK_WINDOW(tw->window), window_icon); g_object_unref (window_icon); return 0; } static gboolean delete_event_callback (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, G_GNUC_UNUSED gpointer user_data) { gtk_main_quit (); return FALSE; } /* Detect changes in GtkNotebook tab order and update the tw->terms list to reflect such changes. */ static void page_reordered_cb (GtkNotebook *notebook, GtkWidget *child, guint page_num, tilda_window *tw) { DEBUG_FUNCTION ("page_reordered_cb"); GList *terminals; tilda_term *terminal; guint i; terminals = tw->terms; for (i = 0; i < g_list_length (terminals); i++) { terminal = g_list_nth_data (terminals, i); if (terminal->hbox == child) { terminals = g_list_remove (terminals, terminal); tw->terms = g_list_insert (terminals, terminal, page_num); break; } } } static void switch_page_cb (GtkNotebook *notebook, GtkWidget *page, guint page_num, tilda_window *tw) { DEBUG_FUNCTION ("tilda_terminal_switch_page_cb"); guint counter = 0; tilda_term *term = NULL; for(GList *item=tw->terms; item != NULL; item=item->next) { if(counter == page_num) { term = (tilda_term*) item->data; } counter++; } char * current_title = tilda_terminal_get_title (term); if (current_title != NULL) { gtk_window_set_title (GTK_WINDOW (tw->window), current_title); } g_free (current_title); } gboolean tilda_window_init (const gchar *config_file, const gint instance, tilda_window *tw) { DEBUG_FUNCTION ("tilda_window_init"); DEBUG_ASSERT (instance >= 0); GtkCssProvider *provider; GtkStyleContext *style_context; /* Set the instance number */ tw->instance = instance; /* Set the config file */ tw->config_file = g_strdup (config_file); /* Create the main window */ tw->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); GError* error = NULL; GtkBuilder *gtk_builder = gtk_builder_new (); #if ENABLE_NLS gtk_builder_set_translation_domain (gtk_builder, PACKAGE); #endif if(!gtk_builder_add_from_resource (gtk_builder, "/org/tilda/tilda.ui", &error)) { fprintf (stderr, "Error: %s\n", error->message); g_error_free(error); return FALSE; } tw->gtk_builder = gtk_builder; /* The gdk_x11_get_server_time call will hang if GDK_PROPERTY_CHANGE_MASK is not set */ gdk_window_set_events(gdk_screen_get_root_window (gtk_widget_get_screen (tw->window)), GDK_PROPERTY_CHANGE_MASK); /* Generic timer resolution */ tw->timer_resolution = config_getint("timer_resolution"); /* Auto hide support */ tw->auto_hide_tick_handler = 0; tw->auto_hide_max_time = config_getint("auto_hide_time"); tw->auto_hide_on_mouse_leave = config_getbool("auto_hide_on_mouse_leave"); tw->auto_hide_on_focus_lost = config_getbool("auto_hide_on_focus_lost"); tw->disable_auto_hide = FALSE; tw->focus_loss_on_keypress = FALSE; PangoFontDescription *description = pango_font_description_from_string(config_getstr("font")); gint size = pango_font_description_get_size(description); tw->unscaled_font_size = size; tw->current_scale_factor = PANGO_SCALE_MEDIUM; if(1 == config_getint("non_focus_pull_up_behaviour")) { tw->hide_non_focused = TRUE; } else { tw->hide_non_focused = FALSE; } tw->fullscreen = config_getbool("start_fullscreen"); tilda_window_set_fullscreen(tw); /* Set up all window properties */ if (config_getbool ("pinned")) gtk_window_stick (GTK_WINDOW(tw->window)); if(config_getbool ("set_as_desktop")) gtk_window_set_type_hint(GTK_WINDOW(tw->window), GDK_WINDOW_TYPE_HINT_DESKTOP); gtk_window_set_skip_taskbar_hint (GTK_WINDOW(tw->window), config_getbool ("notaskbar")); gtk_window_set_keep_above (GTK_WINDOW(tw->window), config_getbool ("above")); gtk_window_set_decorated (GTK_WINDOW(tw->window), FALSE); gtk_widget_set_size_request (GTK_WIDGET(tw->window), 0, 0); tilda_window_set_icon (tw, g_build_filename (DATADIR, "pixmaps", "tilda.png", NULL)); tilda_window_setup_alpha_mode (tw); gtk_widget_set_app_paintable (GTK_WIDGET (tw->window), TRUE); /* Add keyboard accelerators */ tw->accel_group = NULL; /* We can redefine the accelerator group from the wizard; this shows that it's our first time defining it. */ tilda_window_setup_keyboard_accelerators (tw); /* Create the notebook */ tw->notebook = gtk_notebook_new (); /* Adding widget title for CSS selection */ gtk_widget_set_name (GTK_WIDGET(tw->window), "Main"); /* Here we setup the CSS settings for the GtkNotebook. * If the option "Show notebook border" in the preferences is not * checked. Then we disable the border. Otherwise nothing is changed. * * Depending on the theme it might be necessary to set different * CSS rules, so it is not possible to find a solution that fits * everyone. The following configuration works well with the GNOME * default theme Adwaita, but for example has problems under Ubuntu. * Note that for bigger modifications the user can create a style.css * file in tildas config directory, which will be parsed by * load_custom_css_file() in tilda.c on start up. */ gtk_notebook_set_show_tabs (GTK_NOTEBOOK(tw->notebook), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (tw->notebook), config_getbool("notebook_border")); gtk_notebook_set_scrollable (GTK_NOTEBOOK(tw->notebook), TRUE); tilda_window_set_tab_position (tw, config_getint ("tab_pos")); provider = gtk_css_provider_new (); style_context = gtk_widget_get_style_context(tw->notebook); gtk_style_context_add_provider (style_context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); if(!config_getbool("notebook_border")) { /** * Calling gtk_notebook_set_show_border is not enough. We need to * disable the border explicitly by using CSS. */ gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), " .notebook {\n" " border: none;\n" "}\n", -1, NULL); } g_object_unref (provider); /* Create the linked list of terminals */ tw->terms = NULL; /* Add the initial terminal */ if (!tilda_window_add_tab (tw)) { return FALSE; } /* This is required in key_grabber.c to get the x11 server time, * since the specification requires this flag to be set when * gdk_x11_get_server_time() is called. **/ gtk_widget_add_events (tw->window, GDK_PROPERTY_CHANGE_MASK ); /* Connect signal handlers */ g_signal_connect (G_OBJECT(tw->window), "delete-event", G_CALLBACK (delete_event_callback), tw); g_signal_connect (G_OBJECT(tw->window), "show", G_CALLBACK (focus_term), tw); g_signal_connect (G_OBJECT(tw->window), "focus-out-event", G_CALLBACK (focus_out_event_cb), tw); g_signal_connect (G_OBJECT(tw->window), "enter-notify-event", G_CALLBACK (mouse_enter), tw); g_signal_connect (G_OBJECT(tw->window), "leave-notify-event", G_CALLBACK (mouse_leave), tw); /* We need this signal to detect changes in the order of tabs so that we can keep the order * of tilda_terms in the tw->terms structure in sync with the order of tabs. */ g_signal_connect (G_OBJECT(tw->notebook), "page-reordered", G_CALLBACK (page_reordered_cb), tw); g_signal_connect (G_OBJECT (tw->notebook), "switch-page", G_CALLBACK (switch_page_cb), tw); /* Setup the tilda window. The tilda window consists of a top level window that contains the following widgets: * * The main_box holds a GtkNotebook with all the terminal tabs * * The search_box holds the TildaSearchBox widget */ GtkWidget *main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); tw->search = tilda_search_box_new (); GtkStyleContext *context = gtk_widget_get_style_context(main_box); gtk_style_context_add_class(context, GTK_STYLE_CLASS_BACKGROUND); gtk_container_add (GTK_CONTAINER(tw->window), main_box); gtk_box_pack_start (GTK_BOX (main_box), tw->notebook, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (main_box), tw->search, FALSE, TRUE, 0); g_signal_connect (tw->search, "search", G_CALLBACK (search_cb), tw); g_signal_connect (tw->search, "focus-out", G_CALLBACK (search_focus_out_cb), tw); /* Show the widgets */ gtk_widget_show_all (main_box); gtk_widget_set_visible(tw->search, FALSE); /* the tw->window widget will be shown later, by pull() */ /* Position the window */ tw->current_state = STATE_UP; GdkRectangle rectangle; config_get_configured_window_size (&rectangle); gint width = rectangle.width; gint height = rectangle.height; gtk_window_set_default_size (GTK_WINDOW(tw->window), width, height); gtk_window_resize (GTK_WINDOW(tw->window), width, height); /* Create GDK resources now, to prevent crashes later on */ gtk_widget_realize (tw->window); generate_animation_positions (tw); /* Initialize wizard window reference to NULL */ tw->wizard_window = NULL; GdkScreen *screen; GdkWindow *root; GdkEventMask mask; screen = gtk_widget_get_screen (GTK_WIDGET (tw->window)); root = gdk_screen_get_root_window (screen); mask = gdk_window_get_events (root); mask |= GDK_PROPERTY_CHANGE_MASK; gdk_window_set_events (root, mask); gdk_window_add_filter (root, window_filter_function, tw); return TRUE; } gint tilda_window_free (tilda_window *tw) { /* Close each tab which still exists. * This will free their data structures automatically. */ if (tw->notebook != NULL) { gint num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK(tw->notebook)); while (num_pages > 0) { /* Close the 0th tab, which should always exist while we have * some pages left in the notebook. */ tilda_window_close_tab (tw, 0, TRUE); num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK(tw->notebook)); } } g_free (tw->config_file); gtk_widget_destroy (tw->search); if (tw->gtk_builder != NULL) { g_object_unref (G_OBJECT(tw->gtk_builder)); } tw->size_update_event_source = 0; return 0; } gint tilda_window_add_tab (tilda_window *tw) { DEBUG_FUNCTION ("tilda_window_add_tab"); DEBUG_ASSERT (tw != NULL); tilda_term *tt; GtkWidget *label; gint index; tt = tilda_term_init (tw); if (tt == NULL) { TILDA_PERROR (); g_printerr (_("Out of memory, cannot create tab\n")); return FALSE; } /* Create page and append to notebook */ label = gtk_label_new (config_getstr("title")); /* Strangely enough, prepend puts pages on the end */ index = gtk_notebook_append_page (GTK_NOTEBOOK(tw->notebook), tt->hbox, label); gtk_notebook_set_current_page (GTK_NOTEBOOK(tw->notebook), index); gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK(tw->notebook), tt->hbox, TRUE); if(config_getbool ("expand_tabs")) { gtk_container_child_set (GTK_CONTAINER(tw->notebook), GTK_WIDGET(tt->hbox), "tab-expand", TRUE, "tab-fill", TRUE, NULL); } /* We should show the tabs if there are more than one tab in the notebook * (or show single tab is set), * and tab position is not set to hidden */ if ((gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) > 1 || config_getbool("show_single_tab")) && config_getint("tab_pos") != NB_HIDDEN) gtk_notebook_set_show_tabs (GTK_NOTEBOOK (tw->notebook), TRUE); /* The new terminal should grab the focus automatically */ gtk_widget_grab_focus (tt->vte_term); return GDK_EVENT_STOP; //index; } gint tilda_window_close_tab (tilda_window *tw, gint tab_index, gboolean force_exit) { DEBUG_FUNCTION ("tilda_window_close_tab"); DEBUG_ASSERT (tw != NULL); DEBUG_ASSERT (tab_index >= 0); tilda_term *tt; GtkWidget *child; child = gtk_notebook_get_nth_page (GTK_NOTEBOOK(tw->notebook), tab_index); if (child == NULL) { DEBUG_ERROR ("Bad tab_index specified"); return -1; } tt = find_tt_in_g_list (tw, tab_index); gtk_notebook_remove_page (GTK_NOTEBOOK (tw->notebook), tab_index); /* We should hide the tabs if there is only one tab left */ if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) == 1 && !config_getbool("show_single_tab")) gtk_notebook_set_show_tabs (GTK_NOTEBOOK (tw->notebook), FALSE); /* With no pages left, either leave the program or create a new * terminal */ if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (tw->notebook)) < 1) { if (force_exit == TRUE) { /** * It is necessary to check the main_level because if CTRL_C was used * or the "Quit" option from the context menu then gtk_main_quit has * already been called and cannot call it again. */ if(gtk_main_level () > 0) { gtk_main_quit (); } } else { /* These can stay here. They don't need to go into a header * because they are only used at this point in the code. */ enum on_last_terminal_exit { EXIT_TILDA, RESTART_TERMINAL, RESTART_TERMINAL_AND_HIDE }; /* Check the user's preference for what to do when the last * terminal is closed. Take the appropriate action */ switch (config_getint ("on_last_terminal_exit")) { case RESTART_TERMINAL: tilda_window_add_tab (tw); break; case RESTART_TERMINAL_AND_HIDE: tilda_window_add_tab (tw); pull (tw, PULL_UP, TRUE); break; case EXIT_TILDA: default: /** * It is necessary to check the main_level because if CTRL_C was used * or the "Quit" option from the context menu then gtk_main_quit has * already been called and cannot call it again. */ if(gtk_main_level () > 0) { gtk_main_quit (); } break; } } } /* Remove the tilda_term from the list of terminals */ tw->terms = g_list_remove (tw->terms, tt); /* Free the terminal, we are done with it */ tilda_term_free (tt); return GDK_EVENT_STOP; } gint tilda_window_confirm_quit (tilda_window *tw) { DEBUG_FUNCTION(__FUNCTION__); gboolean can_quit = TRUE; if(config_getbool("prompt_on_exit")) { char * message = _("Are you sure you want to Quit?"); can_quit = show_confirmation_dialog (tw, message); } if (can_quit) { gtk_main_quit (); } return GDK_EVENT_STOP; } GdkMonitor* tilda_window_find_monitor_number (tilda_window *tw) { DEBUG_FUNCTION ("tilda_window_find_monitor_number"); GdkDisplay *display = gdk_display_get_default (); gint n_monitors = gdk_display_get_n_monitors (gdk_display_get_default ()); gchar *show_on_monitor = config_getstr("show_on_monitor"); for(int i = 0; i < n_monitors; ++i) { GdkMonitor *monitor = gdk_display_get_monitor (display, i); const gchar *monitor_name = gdk_monitor_get_model (monitor); if(0 == g_strcmp0 (show_on_monitor, monitor_name)) { return monitor; } } return gdk_display_get_primary_monitor (display); } gint tilda_window_find_centering_coordinate (tilda_window *tw, enum dimensions dimension) { DEBUG_FUNCTION ("tilda_window_find_centering_coordinate"); gdouble monitor_dimension = 0; gdouble tilda_dimension = 0; GdkMonitor *monitor = tilda_window_find_monitor_number (tw); GdkRectangle rectangle; gdk_monitor_get_workarea (monitor, &rectangle); GdkRectangle tilda_rectangle; config_get_configured_window_size (&tilda_rectangle); if (dimension == HEIGHT) { monitor_dimension = rectangle.height; tilda_dimension = tilda_rectangle.height; } else if (dimension == WIDTH) { monitor_dimension = rectangle.width; tilda_dimension = tilda_rectangle.width; } const gdouble screen_center = monitor_dimension / 2.0; const gdouble tilda_center = tilda_dimension / 2.0; gint center = (int) (screen_center - tilda_center); if(dimension == HEIGHT) { center += rectangle.y; } else if (dimension == WIDTH) { center += rectangle.x; } return center; } void tilda_window_update_window_position (tilda_window *tw) { DEBUG_FUNCTION ("tilda_window_update_window_position"); /** * If the screen size changed we might also need to recenter the * tilda window. */ gint pos_x, pos_y; gboolean centered_horizontally = config_getbool ("centered_horizontally"); gboolean centered_vertically = config_getbool ("centered_vertically"); if (centered_horizontally) { pos_x = tilda_window_find_centering_coordinate (tw, WIDTH); config_setint ("x_pos", pos_x); pos_y = (gint) config_getint ("y_pos"); gtk_window_move (GTK_WINDOW (tw->window), pos_x, pos_y); } if (centered_vertically) { pos_y = tilda_window_find_centering_coordinate (tw, HEIGHT); config_setint ("y_pos", pos_y); pos_x = (gint) config_getint ("x_pos"); gtk_window_move (GTK_WINDOW (tw->window), pos_x, pos_y); } } static gboolean update_tilda_window_size (gpointer user_data) { tilda_window *tw = user_data; g_debug ("Updating tilda window size in idle handler to " "match new size of workarea."); /* 1. Get current tilda window size */ int windowHeight = gtk_widget_get_allocated_height (GTK_WIDGET (tw->window)); int windowWidth = gtk_widget_get_allocated_width (GTK_WIDGET (tw->window)); gint newWidth = windowWidth; gint newHeight = windowHeight; /* 2. Get the desired size and update the tilda window size if necessary. */ GdkRectangle configured_geometry; config_get_configured_window_size (&configured_geometry); if (configured_geometry.width - windowWidth >= 1) { newWidth = configured_geometry.width; } if (configured_geometry.height - windowHeight >= 1) { newHeight = configured_geometry.height; } gtk_window_resize (GTK_WINDOW (tw->window), newWidth, newHeight); tilda_window_update_window_position (tw); /* 3. Returning G_SOURCE_REMOVE below will clear the event source in Gtk. * Thus, we need to reset the ID such that a new event source can be * registered if the workarea changes again. */ tw->size_update_event_source = 0; return G_SOURCE_REMOVE; } /** * This function inspects the incoming XEvents to find property events that * update the workarea and as a result queues an update function which updates * the tilda window size in case the workarea has changed. * * The reason we need this function is that in the signal handlers of * the signals GdkScreen::monitors-changed and GdkScreen::size-changed * we cannot reliably get the updated workarea. This is because the workarea * is computed and update by the window manager only after the monitor * resolution has changed. Thus, if we call gdk_monitor_get_workarea * from the monitors-changed or size-changed signal handlers we will * not get the correct workarea because the window manager has not yet * updated the corresponding XProperty. */ static GdkFilterReturn window_filter_function (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer user_data) { tilda_window *tw = user_data; XEvent *xevent = (XEvent *) gdk_xevent; switch (xevent->type) { case PropertyNotify: { XPropertyEvent *propertyEvent; propertyEvent = (XPropertyEvent *) xevent; const Atom WORKAREA_ATOM = XInternAtom (propertyEvent->display, "_NET_WORKAREA", True); if (propertyEvent->atom != WORKAREA_ATOM) return GDK_FILTER_CONTINUE; if (tw->size_update_event_source != 0) return GDK_FILTER_CONTINUE; tw->size_update_event_source = g_idle_add (update_tilda_window_size, user_data); break; } default:break; } return GDK_FILTER_CONTINUE; }