/* Copyright (C) 2001 to 2006 Chris Vine This program is distributed under the General Public Licence, version 2. For particulars of this and relevant disclaimers see the file COPYING distributed with the source files. */ // something in gtkprintjob.h clashes with the C++ headers with // libstdc++-5 and it needs to be included here to obtain a // clean compile #include #if GTK_CHECK_VERSION(2,10,0) #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_OSTREAM #include #include #endif #ifdef HAVE_ISTREAM #include #include #endif #ifdef HAVE_STREAM_IMBUE #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // the key codes are here #include #include #if GTK_CHECK_VERSION(2,4,0) # define EFAX_GTK_USE_ICON_THEME 1 #else # undef EFAX_GTK_USE_ICON_THEME #endif #ifdef EFAX_GTK_USE_ICON_THEME #include #include "utils/icon_info_handle.h" #endif #include "mainwindow.h" #include "dialogs.h" #include "addressbook.h" #include "file_list.h" #include "settings.h" #include "socket_notify.h" #include "menu_icons.h" #include "utils/shared_handle.h" #include "utils/thread.h" #include "utils/fdstream.h" #ifdef ENABLE_NLS #include #endif #define TIMER_INTERVAL 200 // number of milliseconds between timer events for the main timer #define LOGFILE_TIMER_INTERVAL 60000 // number of milliseconds between flushing of logfile #define LOGFILE_MARK_COUNT 15*60000/LOGFILE_TIMER_INTERVAL // to mark the logfile at 15 minute intervals extern "C" void close_signalhandler(int); static volatile sig_atomic_t close_flag = false; static Thread::Mutex* write_error_mutex_p = 0; bool MainWindow::connected_to_stderr = false; PipeFifo MainWindow::error_pipe(PipeFifo::non_block); namespace { // callbacks for internal use only void MainWindowCB::mainwin_button_clicked(GtkWidget* widget_p, void* data) { MainWindow* instance_p = static_cast(data); if (widget_p == instance_p->file_button_p) { instance_p->set_file_items_sensitive_impl(); } else if (widget_p == instance_p->socket_button_p) { instance_p->set_socket_items_sensitive_impl(); } else if (widget_p == instance_p->single_file_button_p) { instance_p->get_file_impl(); } else if (widget_p == instance_p->multiple_file_button_p) { instance_p->file_list_impl(); } else if (widget_p == instance_p->socket_list_button_p) { instance_p->socket_list_impl(); } else if (widget_p == instance_p->number_button_p) { instance_p->addressbook_impl(); } else if (widget_p == instance_p->send_button_p) { instance_p->sendfax_slot(); } else if (widget_p == instance_p->receive_answer_button_p) { instance_p->receive_impl(EfaxController::receive_answer); } else if (widget_p == instance_p->receive_takeover_button_p) { instance_p->receive_impl(EfaxController::receive_takeover); } else if (widget_p == instance_p->receive_standby_button_p) { instance_p->receive_impl(EfaxController::receive_standby); } else if (widget_p == instance_p->stop_button_p) { instance_p->efax_controller.stop(); } else { write_error("Callback error in MainWindowCB::mainwin_button_clicked()\n"); return; } } void MainWindowCB::mainwin_menuitem_activated(GtkMenuItem* item_p, void* data) { MainWindow* instance_p = static_cast(data); if (item_p == instance_p->list_received_faxes_item_p) { instance_p->fax_list_impl(FaxListEnum::received); } else if (item_p == instance_p->list_sent_faxes_item_p) { instance_p->fax_list_impl(FaxListEnum::sent); } else if (item_p == instance_p->socket_list_item_p) { instance_p->socket_list_impl(); } else if (item_p == instance_p->single_file_item_p) { instance_p->get_file_impl(); } else if (item_p == instance_p->multiple_file_item_p) { instance_p->file_list_impl(); } else if (item_p == instance_p->address_book_item_p) { instance_p->addressbook_impl(); } else if (item_p == instance_p->settings_item_p) { instance_p->settings_impl(); } else if (item_p == instance_p->quit_item_p) { instance_p->close_impl(); } else if (item_p == instance_p->about_efax_gtk_item_p) { instance_p->about_impl(true); } else if (item_p == instance_p->about_efax_item_p) { instance_p->about_impl(false); } else if (item_p == instance_p->translations_item_p) { instance_p->translations_impl(); } else if (item_p == instance_p->help_item_p) { instance_p->helpfile_impl(); } else { write_error("Callback error in MainWindowCB::mainwin_menuitem_activated()\n"); return; } } gboolean MainWindowCB::mainwin_key_press_event(GtkWidget*, GdkEventKey* event_p, void* data) { MainWindow* instance_p = static_cast(data); if (event_p->keyval == GDK_F1) { instance_p->helpfile_impl(); return true; // processing stops here } return false; // carry on processing the key event } gboolean MainWindowCB::mainwin_visibility_notify_event(GtkWidget*, GdkEventVisibility* event_p, void* data) { MainWindow* instance_p = static_cast(data); if(event_p->state == GDK_VISIBILITY_FULLY_OBSCURED) instance_p->obscured = true; else instance_p->obscured = false; return false; // carry on processing the visibility notify event } gboolean MainWindowCB::mainwin_window_state_event(GtkWidget*, GdkEventWindowState* event_p, void* data) { MainWindow* instance_p = static_cast(data); if(event_p->new_window_state == GDK_WINDOW_STATE_ICONIFIED) instance_p->minimised = true; else instance_p->minimised = false; return false; // carry on processing the window state event } void MainWindowCB::mainwin_style_set(GtkWidget*, GtkStyle*, void* data) { static_cast(data)->style_set_impl(); } gboolean MainWindowCB::mainwin_timer_event(void* data) { MainWindow* instance_p = static_cast(data); if (close_flag) { instance_p->close_impl(); // we must have picked up an external kill signal // so we need an orderly close down close_flag = false; } instance_p->efax_controller.timer_event(); return true; // we want a multi-shot timer } gboolean MainWindowCB::mainwin_drawing_area_expose_event(GtkWidget*, GdkEventExpose*, void* data) { return static_cast(data)->draw_fax_from_socket_notifier(); } gboolean MainWindowCB::mainwin_start_hidden_check(void* data) { MainWindow* instance_p = static_cast(data); if (!instance_p->tray_item.is_embedded()) { // the user has goofed - he has set the program to start hidden in the system tray // but has no system tray running! write_error(gettext("The program has been started with the -s option but there is no system tray!\n")); gtk_window_present(instance_p->get_win()); } return false; // this only fires once } } // anonymous namespace MainWindow::MainWindow(const std::string& messages,bool start_hidden, bool start_in_standby, const char* filename): // start_hidden and start_in_standby have a default value of // false and filename has a default value of 0 WinBase(0, prog_config.window_icon_h, false), standard_size(24), obscured(false), minimised(false), status_line(standard_size), tray_item(get_win()) { // catch any relevant Unix signals for an orderly closedown // catch SIGQUIT, SIGTERM SIGINT SIGHUP // it is safe to use signal() for these signal(SIGQUIT, close_signalhandler); signal(SIGTERM, close_signalhandler); signal(SIGINT, close_signalhandler); signal(SIGHUP, close_signalhandler); // ignore SIGPIPE struct sigaction sig_act_pipe; sig_act_pipe.sa_handler = SIG_IGN; // we don't need to mask off any signals sigemptyset(&sig_act_pipe.sa_mask); sig_act_pipe.sa_flags = 0; sigaction(SIGPIPE, &sig_act_pipe, 0); // we don't need to set a child signal handler - the default // (SIG_DFL) is fine - we want to ignore it as we will reap // exit status in EfaxController::timer_event(), which calls // waitpid() write_error_mutex_p = new Thread::Mutex; get_longest_button_text(); GtkWidget* drawing_area_alignment_p = gtk_alignment_new(1, 0.5, 0, 1); drawing_area_p = gtk_drawing_area_new(); gtk_widget_set_size_request(drawing_area_p, standard_size, standard_size); gtk_container_add(GTK_CONTAINER(drawing_area_alignment_p), drawing_area_p); GtkTable* file_entry_table_p = GTK_TABLE(gtk_table_new(3, 3, false)); GtkTable* win_table_p = GTK_TABLE(gtk_table_new(5, 3, false)); file_button_p = gtk_radio_button_new_with_label(0, gettext("File ")); socket_button_p = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(file_button_p), gettext("Socket ")); GtkWidget* fax_method_label_p = gtk_label_new(gettext("Fax entry method: ")); single_file_button_p = gtk_button_new_with_label(gettext("Single file")); multiple_file_button_p = gtk_button_new_with_label(gettext("Multiple files")); socket_list_button_p = gtk_button_new_with_label(gettext("Socket list")); number_button_p = gtk_button_new_with_label(gettext("Tel number: ")); send_button_p = gtk_button_new_with_label(gettext("Send fax")); receive_answer_button_p = gtk_button_new_with_label(gettext("Answer call")); receive_takeover_button_p = gtk_button_new_with_label(gettext("Take over call")); receive_standby_button_p = gtk_button_new_with_label(gettext("Standby")); stop_button_p = gtk_button_new_with_label(gettext("Stop")); file_entry_p = gtk_entry_new(); number_entry_p = gtk_entry_new(); GtkBox* fax_method_radio_box_p = GTK_BOX(gtk_hbox_new(false, 0)); gtk_box_pack_start(fax_method_radio_box_p, file_button_p, false, false, 0); gtk_box_pack_start(fax_method_radio_box_p, socket_button_p, false, false, 0); GtkWidget* fax_method_radio_frame_p = gtk_frame_new(0); gtk_container_add(GTK_CONTAINER(fax_method_radio_frame_p), GTK_WIDGET(fax_method_radio_box_p)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(file_button_p), true); GtkBox* fax_method_box_p = GTK_BOX(gtk_hbox_new(false, 0)); gtk_box_pack_start(fax_method_box_p, fax_method_label_p, false, false, 0); gtk_box_pack_start(fax_method_box_p, fax_method_radio_frame_p, false, false, 0); gtk_box_pack_start(fax_method_box_p, drawing_area_alignment_p, true, true, 0); gtk_table_attach(file_entry_table_p, file_entry_p, 0, 3, 0, 1, GtkAttachOptions(GTK_FILL | GTK_EXPAND), GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(file_entry_table_p, GTK_WIDGET(fax_method_box_p), 0, 3, 1, 2, GtkAttachOptions(GTK_FILL | GTK_EXPAND), GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(file_entry_table_p, single_file_button_p, 0, 1, 2, 3, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(file_entry_table_p, multiple_file_button_p, 1, 2, 2, 3, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(file_entry_table_p, socket_list_button_p, 2, 3, 2, 3, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); GtkWidget* file_entry_frame_p = gtk_frame_new(gettext("Fax to send")); gtk_container_add(GTK_CONTAINER(file_entry_frame_p), GTK_WIDGET(file_entry_table_p)); GtkBox* number_box_p = GTK_BOX(gtk_hbox_new(false, 0)); gtk_box_pack_start(number_box_p, number_button_p, false, false, standard_size/2); gtk_box_pack_start(number_box_p, number_entry_p, true, true, 0); gtk_table_attach(win_table_p, file_entry_frame_p, 0, 3, 0, 1, GtkAttachOptions(GTK_FILL | GTK_EXPAND), GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(win_table_p, GTK_WIDGET(number_box_p), 0, 3, 1, 2, GtkAttachOptions(GTK_FILL | GTK_EXPAND), GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(win_table_p, text_window.get_main_widget(), 0, 3, 2, 3, GtkAttachOptions(GTK_FILL | GTK_EXPAND), GtkAttachOptions(GTK_FILL | GTK_EXPAND), standard_size/3, standard_size/3); gtk_table_attach(win_table_p, send_button_p, 0, 1, 3, 4, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(win_table_p, receive_answer_button_p, 1, 2, 3, 4, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(win_table_p, receive_takeover_button_p, 2, 3, 3, 4, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(win_table_p, receive_standby_button_p, 0, 1, 4, 5, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); gtk_table_attach(win_table_p, stop_button_p, 2, 3, 4, 5, GTK_EXPAND, GTK_SHRINK, standard_size/3, standard_size/3); g_signal_connect(G_OBJECT(file_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(socket_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(single_file_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(multiple_file_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(socket_list_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(number_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(send_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(receive_answer_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(receive_takeover_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(receive_standby_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); g_signal_connect(G_OBJECT(stop_button_p), "clicked", G_CALLBACK(MainWindowCB::mainwin_button_clicked), this); GTK_WIDGET_SET_FLAGS(single_file_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(multiple_file_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(socket_list_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(number_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(send_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(receive_answer_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(receive_takeover_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(receive_standby_button_p, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(stop_button_p, GTK_CAN_DEFAULT); // set up the menu bar // first get the about icon bool have_about_icon = false; GobjHandle about_pixbuf_h; #ifdef EFAX_GTK_USE_ICON_THEME GtkIconTheme* icon_theme_p = gtk_icon_theme_get_default(); IconInfoScopedHandle icon_info_h(gtk_icon_theme_lookup_icon(icon_theme_p, "stock_about", 16, GtkIconLookupFlags(0))); if (icon_info_h.get()) { const gchar* icon_path_p = gtk_icon_info_get_filename(icon_info_h.get()); if (icon_path_p) { GError* error_p = 0; about_pixbuf_h = GobjHandle(gdk_pixbuf_new_from_file(icon_path_p, &error_p)); if (about_pixbuf_h.get()) have_about_icon = true; else write_error("Pixbuf error in MainWindow::MainWindow()\n"); } } #endif if (!have_about_icon) { about_pixbuf_h = GobjHandle(gdk_pixbuf_new_from_xpm_data(about_xpm)); } // create the file menu GtkWidget* file_menu_p = gtk_menu_new(); GtkWidget* image_p; image_p = gtk_image_new_from_stock(GTK_STOCK_INDEX, GTK_ICON_SIZE_MENU); list_received_faxes_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("List _received faxes"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(list_received_faxes_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(list_received_faxes_item_p)); gtk_widget_show(GTK_WIDGET(list_received_faxes_item_p)); image_p = gtk_image_new_from_stock(GTK_STOCK_INDEX, GTK_ICON_SIZE_MENU); list_sent_faxes_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("_List sent faxes"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(list_sent_faxes_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(list_sent_faxes_item_p)); gtk_widget_show(GTK_WIDGET(list_sent_faxes_item_p)); // insert separator GtkWidget* separator_item_p = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), separator_item_p); gtk_widget_show(separator_item_p); socket_list_item_p = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(gettext("Queued _faxes from socket"))); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(socket_list_item_p)); gtk_widget_show(GTK_WIDGET(socket_list_item_p)); image_p = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU); single_file_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("_Enter single file"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(single_file_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(single_file_item_p)); gtk_widget_show(GTK_WIDGET(single_file_item_p)); image_p = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU); multiple_file_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("Enter _multiple files"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(multiple_file_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(multiple_file_item_p)); gtk_widget_show(GTK_WIDGET(multiple_file_item_p)); // insert separator separator_item_p = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), separator_item_p); gtk_widget_show(separator_item_p); address_book_item_p = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(gettext("_Address book"))); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(address_book_item_p)); gtk_widget_show(GTK_WIDGET(address_book_item_p)); // insert separator separator_item_p = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), separator_item_p); gtk_widget_show(separator_item_p); image_p = gtk_image_new_from_stock(GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU); settings_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("_Settings"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(settings_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(settings_item_p)); gtk_widget_show(GTK_WIDGET(settings_item_p)); // insert separator separator_item_p = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), separator_item_p); gtk_widget_show(separator_item_p); image_p = gtk_image_new_from_stock(GTK_STOCK_QUIT, GTK_ICON_SIZE_MENU); quit_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("_Quit"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(quit_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(file_menu_p), GTK_WIDGET(quit_item_p)); gtk_widget_show(GTK_WIDGET(quit_item_p)); // create the help menu GtkWidget* help_menu_p = gtk_menu_new(); image_p = gtk_image_new_from_pixbuf(about_pixbuf_h); about_efax_gtk_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("About efax-_gtk"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(about_efax_gtk_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(help_menu_p), GTK_WIDGET(about_efax_gtk_item_p)); gtk_widget_show(GTK_WIDGET(about_efax_gtk_item_p)); image_p = gtk_image_new_from_pixbuf(about_pixbuf_h); about_efax_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("About _efax"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(about_efax_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(help_menu_p), GTK_WIDGET(about_efax_item_p)); gtk_widget_show(GTK_WIDGET(about_efax_item_p)); translations_item_p = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(gettext("_Translations"))); gtk_menu_shell_append(GTK_MENU_SHELL(help_menu_p), GTK_WIDGET(translations_item_p)); gtk_widget_show(GTK_WIDGET(translations_item_p)); // insert separator separator_item_p = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(help_menu_p), separator_item_p); gtk_widget_show(separator_item_p); image_p = gtk_image_new_from_stock(GTK_STOCK_HELP, GTK_ICON_SIZE_MENU); help_item_p = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(gettext("_Help"))); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(help_item_p), image_p); gtk_menu_shell_append(GTK_MENU_SHELL(help_menu_p), GTK_WIDGET(help_item_p)); gtk_widget_show(GTK_WIDGET(help_item_p)); // now put the two menus into a menubar GtkWidget* root_file_item_p = gtk_menu_item_new_with_mnemonic(gettext("_File")); GtkWidget* root_help_item_p = gtk_menu_item_new_with_mnemonic(gettext("_Help")); gtk_menu_item_set_submenu(GTK_MENU_ITEM(root_file_item_p), file_menu_p); gtk_menu_item_set_submenu(GTK_MENU_ITEM(root_help_item_p), help_menu_p); GtkMenuShell* menu_bar_p = GTK_MENU_SHELL(gtk_menu_bar_new()); gtk_menu_shell_append(menu_bar_p, root_file_item_p); gtk_menu_shell_append(menu_bar_p, root_help_item_p); // connect the activate signals g_signal_connect(G_OBJECT(list_received_faxes_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(list_sent_faxes_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(socket_list_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(single_file_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(multiple_file_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(address_book_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(settings_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(quit_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(about_efax_gtk_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(about_efax_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(translations_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); g_signal_connect(G_OBJECT(help_item_p), "activate", G_CALLBACK(MainWindowCB::mainwin_menuitem_activated), this); // collect up everything into the main window vbox GtkBox* window_box_p = GTK_BOX(gtk_vbox_new(false, 0)); gtk_box_pack_start(window_box_p, GTK_WIDGET(menu_bar_p), false, false, 0); gtk_box_pack_start(window_box_p, GTK_WIDGET(win_table_p), true, true, 0); gtk_box_pack_start(window_box_p, status_line.get_main_widget(), false, false, 0); gtk_container_add(GTK_CONTAINER(get_win()), GTK_WIDGET(window_box_p)); // connect up miscellaneous signals for the GTK+ main window object g_signal_connect(G_OBJECT(get_win()), "key_press_event", G_CALLBACK(MainWindowCB::mainwin_key_press_event), this); g_signal_connect(G_OBJECT(get_win()), "visibility_notify_event", G_CALLBACK(MainWindowCB::mainwin_visibility_notify_event), this); g_signal_connect(G_OBJECT(get_win()), "window_state_event", G_CALLBACK(MainWindowCB::mainwin_window_state_event), this); g_signal_connect(G_OBJECT(get_win()), "style_set", G_CALLBACK(MainWindowCB::mainwin_style_set), this); gtk_container_set_border_width(GTK_CONTAINER(win_table_p), standard_size/3); GList* focus_chain_p = 0; focus_chain_p = g_list_append(focus_chain_p, file_entry_frame_p); focus_chain_p = g_list_append(focus_chain_p, number_box_p); focus_chain_p = g_list_append(focus_chain_p, send_button_p); focus_chain_p = g_list_append(focus_chain_p, receive_answer_button_p); focus_chain_p = g_list_append(focus_chain_p, receive_takeover_button_p); focus_chain_p = g_list_append(focus_chain_p, receive_standby_button_p); focus_chain_p = g_list_append(focus_chain_p, stop_button_p); gtk_container_set_focus_chain(GTK_CONTAINER(win_table_p), focus_chain_p); g_list_free(focus_chain_p); g_timeout_add(TIMER_INTERVAL, MainWindowCB::mainwin_timer_event, this); g_signal_connect(G_OBJECT(drawing_area_p), "expose_event", G_CALLBACK(MainWindowCB::mainwin_drawing_area_expose_event), this); efax_controller.ready_to_quit_notify.connect(sigc::mem_fun(*this, &MainWindow::quit_slot)); efax_controller.stdout_message.connect(sigc::mem_fun(text_window, &MessageText::write_black_slot)); efax_controller.write_state.connect(sigc::mem_fun(status_line, &StatusLine::write_status_slot)); efax_controller.remove_from_socket_server_filelist.connect(sigc::mem_fun(*this, &MainWindow::remove_from_socket_server_filelist)); // connect up our end of the error pipe error_pipe_tag = start_iowatch(error_pipe.get_read_fd(), sigc::mem_fun(*this, &MainWindow::read_error_pipe_slot), G_IO_IN); // we want to minimise the effect on efax, so make writing to the error pipe non-blocking error_pipe.make_write_non_block(); set_file_items_sensitive_impl(); get_window_settings(); if (filename) { // this will also call set_file_items_sensitive_impl() if the call to // get_window_settings() has set socket_button active gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(file_button_p), true); gtk_entry_set_text(GTK_ENTRY(file_entry_p), filename); gtk_window_set_focus(get_win(), number_entry_p); } else gtk_window_set_focus(get_win(), single_file_button_p); // enable visibility events, so that the action when left clicking on the // tray icon is correct GtkWidget* mainwin_p = GTK_WIDGET(get_win()); gtk_widget_add_events(mainwin_p, GDK_VISIBILITY_NOTIFY_MASK); // now we will either show everything, or realise the window // and its children and keep it hidden in the system tray if (start_hidden) { gtk_widget_realize(mainwin_p); gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(get_win()))); gtk_widget_hide(mainwin_p); g_timeout_add(5000, MainWindowCB::mainwin_start_hidden_check, this); } else gtk_widget_show_all(mainwin_p); // now we have shown ourselves (if we are going to show ourselves!) if (!messages.empty()) { text_window.write_red_slot(messages.c_str()); text_window.write_red_slot("\n\n"); } // make sure that we have the faxin, faxout, and faxsent chdir(prog_config.working_dir.c_str()); mkdir("faxin", S_IRUSR | S_IWUSR | S_IXUSR); mkdir("faxout", S_IRUSR | S_IWUSR | S_IXUSR); mkdir("faxsent", S_IRUSR | S_IWUSR | S_IXUSR); // set the working directory for the parent process chdir("faxin"); // if there is no config file installed, then bring up the settings dialog if (!prog_config.found_rcfile) { // we don't want to use MainWindow::settings_impl(), or the absence of // a configuration file will be reported twice -- not a big deal, but ... // so pass true as the last parameter to skip detection of settings file SettingsDialog dialog(standard_size, get_win(), true); dialog.accepted.connect(sigc::mem_fun(*this, &MainWindow::settings_changed_slot)); dialog.exec(); } // this connects the stdout facilities of SocketServer to MessageText socket_server.stdout_message.connect(sigc::mem_fun(text_window, &MessageText::write_black_slot)); // this connects the SigC object in SocketServer to a method in this class // which will update the Socket_list object (if such a dialog is displayed) socket_server.filelist_changed_notify.connect(sigc::mem_fun(*this, &MainWindow::socket_filelist_changed_notify_slot)); // and this connects the SigC object which indicates a fax has been received // from the socket for sending to the method which will pop up a "fax to send" // notify dialog socket_server.fax_to_send_notify.connect(sigc::mem_fun(*this, &MainWindow::fax_to_send_notify_slot)); // this connects the SigC object which indicates that a fax has been received // from the modem by the EfaxController object efax_controller.fax_received_notify.connect(sigc::mem_fun(*this, &MainWindow::fax_received_notify_slot)); // start the socket server if (prog_config.sock_server && !prog_config.sock_server_port.empty()) { socket_server.start(std::string(prog_config.sock_server_port), prog_config.other_sock_client_address); } tray_item.left_button_pressed.connect(sigc::mem_fun(*this, &MainWindow::tray_icon_left_clicked_slot)); tray_item.menu_item_chosen.connect(sigc::mem_fun(*this, &MainWindow::tray_icon_menu_slot)); tray_item.get_state.connect(sigc::mem_fun(efax_controller, &EfaxController::get_state)); tray_item.get_new_fax_count.connect(sigc::mem_fun(efax_controller, &EfaxController::get_count)); efax_controller.write_state.connect(sigc::mem_fun(tray_item, &TrayItem::set_tooltip_slot)); present_window_notify.connect(sigc::mem_fun(*this, &MainWindow::present_window_slot)); start_pipe_thread(); // register our own button icon size for stock icons to match size of externally // defined icons used in this program gtk_icon_size_register("EFAX_GTK_BUTTON_SIZE", 22, 22); // the notifed_fax pair is used by sendfax_slot() if no number is shown // to dial, for use if the option to send on an open connection without // dialling is refused and sendfax_slot() was called by a // SocketNotifyDialog::sendfax_sig signal, so that a SocketNotifyDialog // dialog can be brought up again for the user to have another chance to // choose what he/she wants to do. When the second member of the // notified_fax pair is not 0, then that indicates that sendfax_slot() // was called by a SocketNotifyDialog object (it is normally set to 0 and // only holds another value when set in MainWindow::fax_to_send_dialog()) notified_fax.second = 0; // if the -r option has been chosen, start the program in Receive Standby mode if (start_in_standby) receive_impl(EfaxController::receive_standby); } MainWindow::~MainWindow(void) { save_window_settings(); g_source_remove(error_pipe_tag); delete write_error_mutex_p; } void MainWindow::get_window_settings(void) { int width = 0; int height = 0; int socket_val = 0; int received_list_sort_type = -1; int sent_list_sort_type = -1; std::string file_name(prog_config.working_dir + "/" MAINWIN_SAVE_FILE); std::ifstream file(file_name.c_str(), std::ios::in); if (!file) write_error("Can't get mainwindow settings from file\n"); else { #ifdef HAVE_STREAM_IMBUE file.imbue(std::locale::classic()); #endif // HAVE_STREAM_IMBUE file >> width >> height >> socket_val >> received_list_sort_type >> sent_list_sort_type; if (width > 0 && height > 0) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(socket_button_p), socket_val); gtk_window_set_default_size(get_win(), width, height); if ((received_list_sort_type == GTK_SORT_ASCENDING || received_list_sort_type == GTK_SORT_DESCENDING) && (sent_list_sort_type == GTK_SORT_ASCENDING || sent_list_sort_type == GTK_SORT_DESCENDING)) { FaxListManager::set_received_list_sort_type(GtkSortType(received_list_sort_type)); FaxListManager::set_sent_list_sort_type(GtkSortType(sent_list_sort_type)); } } } } void MainWindow::save_window_settings(void) { std::string file_name(prog_config.working_dir + "/" MAINWIN_SAVE_FILE); std::ofstream file(file_name.c_str(), std::ios::out); if (!file) write_error("Can't save mainwindow settings to file\n"); else { #ifdef HAVE_STREAM_IMBUE file.imbue(std::locale::classic()); #endif // HAVE_STREAM_IMBUE int width = 0; int height = 0; gtk_window_get_size(get_win(), &width, &height); int socket_val = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(socket_button_p)); file << width << ' ' << height << ' ' << socket_val << ' ' << (int)FaxListManager::get_received_list_sort_type() << ' ' << (int)FaxListManager::get_sent_list_sort_type() << ' ' << std::endl; } } void MainWindow::on_delete_event(void) { if (tray_item.is_embedded()) gtk_widget_hide(GTK_WIDGET(get_win())); else close_impl(); } void MainWindow::close_impl(void) { // close down socket server socket_server.stop(); // make sure we close down efax() if it is active // (the EfaxController::ready_to_quit_notify signal is connected to // quit_slot() below and will end the main program loop once any efax // session running has been dealt with) efax_controller.efax_closedown(); } void MainWindow::quit_slot(void) { gtk_main_quit(); } void MainWindow::sendfax_slot(void) { // Fax_item is defined in efax_controller.h Fax_item fax_item; try { fax_item.number = Utf8::locale_from_utf8(gtk_entry_get_text(GTK_ENTRY(number_entry_p))); } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in sendfax_slot()\n"); beep(); return; } // eliminate leading or trailing spaces so that we can check for an empty string strip(fax_item.number); if (fax_item.number.empty()) { PromptDialog dialog(gettext("No fax number specified. Do you want to send the fax " "on an open connection?"), gettext("Telephone number"), standard_size, get_win()); if (!dialog.exec()) { if (notified_fax.second) { // we must have got here by a SocketNotifyDialog::sendfax_sig signal set_files_slot(""); selected_socket_list_file = ""; fax_to_send_dialog(notified_fax); } return; } } notified_fax.second = 0; gtk_window_present(get_win()); std::string files(gtk_entry_get_text(GTK_ENTRY(file_entry_p))); // if this is a fax received from the socket and entered via the socket faxes // list, then "*** " will form the first part of the "file" displayed in the file // entry box - so test for it to see if we are sending a file received via the // socket rather than a regular file if (!files.empty() && files.substr(0,4) == std::string("*** ")) { fax_item.is_socket_file = true; fax_item.file_list.push_back(selected_socket_list_file); } else { fax_item.is_socket_file = false; // convert the contents of files to locale filename codeset if necessary try { files = Utf8::filename_from_utf8(files); } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in sendfax_slot()\n"); beep(); return; } const char* separators = ",;"; // split the files string into separate file names std::string::size_type pos1, pos2; pos1 = files.find_first_not_of(separators); while (pos1 != std::string::npos) { pos2 = files.find_first_of(separators, pos1); if (pos2 != std::string::npos) { std::string file_item(files.substr(pos1, pos2 - pos1)); strip(file_item); fax_item.file_list.push_back(file_item); pos1 = files.find_first_not_of(separators, pos2); } else { std::string file_item(files.substr(pos1)); strip(file_item); fax_item.file_list.push_back(file_item); pos1 = std::string::npos; } } } if (!prog_config.found_rcfile) { text_window.write_red_slot("Can't send fax -- no efax-gtkrc configuration file found\n\n"); beep(); } else if (prog_config.lock_file.empty()) { // if there is no lock file, then it means no // serial device has been specified (the lock // file dir defaults to /var/lock) text_window.write_red_slot("Can't send fax -- no valid serial device specified\n\n"); beep(); } else if (fax_item.file_list.empty()) beep(); else efax_controller.sendfax(fax_item); } void MainWindow::receive_impl(EfaxController::State mode) { if (!prog_config.found_rcfile) { text_window.write_red_slot("Can't receive fax -- no efax-gtkrc configuration file found\n\n"); beep(); } else if (prog_config.lock_file.empty()) { // if there is no lock file, then it means no // serial device has been specified (the lock // file dir defaults to /var/lock) text_window.write_red_slot("Can't receive fax -- no valid serial device specified\n\n"); beep(); } else efax_controller.receive(mode); } void MainWindow::get_file_impl(void) { FileReadSelectDialog file_dialog(standard_size, false, get_win()); file_dialog.exec(); std::vector file_result = file_dialog.get_result(); if (!file_result.empty()) { set_files_slot(file_result[0]); gtk_widget_grab_focus(number_entry_p); } } void MainWindow::set_files_slot(const std::string& files) { gtk_entry_set_text(GTK_ENTRY(file_entry_p), files.c_str()); } void MainWindow::fax_list_impl(FaxListEnum::Mode mode) { if (mode == FaxListEnum::received) { // because FaxListManager::populate_fax_list() calls gtk_main_iteration() // we could get back here again before the constructor of FaxListDialog // has been entered, so check the guard in FaxListManager if (!FaxListManager::is_fax_received_list_main_iteration()) { if (!FaxListDialog::get_is_fax_received_list()) { received_fax_list_p = new FaxListDialog(mode, standard_size); efax_controller.fax_received_notify.connect(sigc::mem_fun(*received_fax_list_p, &FaxListDialog::insert_new_fax_slot)); received_fax_list_p->get_new_fax_count_sig.connect(sigc::mem_fun(efax_controller, &EfaxController::get_count)); received_fax_list_p->reset_sig.connect(sigc::mem_fun(efax_controller, &EfaxController::reset_count)); // now display the correct number of new received faxes received_fax_list_p->display_new_fax_count(); } else gtk_window_present(received_fax_list_p->get_win()); } } else if (mode == FaxListEnum::sent) { // because FaxListManager::populate_fax_list() calls gtk_main_iteration() // we could get back here again before the constructor of FaxListDialog // has been entered, so check the guard in FaxListManager if (!FaxListManager::is_fax_sent_list_main_iteration()) { if (!FaxListDialog::get_is_fax_sent_list()) { sent_fax_list_p = new FaxListDialog(mode, standard_size); efax_controller.fax_sent_notify.connect(sigc::mem_fun(*sent_fax_list_p, &FaxListDialog::insert_new_fax_slot)); } else gtk_window_present(sent_fax_list_p->get_win()); } } // there is no memory leak -- FaxListDialog is modeless and will delete its own memory // when it is closed } void MainWindow::socket_list_impl(void) { // this method will launch the dialog listing queued faxes received from // the socket_server (a SocketListDialog object) if (!SocketListDialog::get_is_socket_list()) { // get the filenames pair - the mutex lock will automatically release when filenames_pair (and // so the shared pointer holding the lock) goes out of scope at the end of the if block (we want to keep // the shared_ptr alive until after we have set up the connection to update the socket list std::pair, SharedPtr > filenames_pair(socket_server.get_filenames()); socket_list_p = new SocketListDialog(filenames_pair, standard_size); // now connect up the dialog to relevant slots and signals update_socket_list_connection = update_socket_list.connect(sigc::mem_fun(*socket_list_p, &SocketListDialog::set_socket_list_rows)); socket_dialog_connection = close_socket_list_dialog.connect(sigc::mem_fun(*socket_list_p, &SocketListDialog::close_slot)); socket_list_p->selected_fax.connect(sigc::mem_fun(*this, &MainWindow::enter_socket_file_slot)); socket_list_p->remove_from_socket_server_filelist.connect(sigc::mem_fun(*this, &MainWindow::remove_from_socket_server_filelist)); socket_list_p->dialog_closed.connect(sigc::mem_fun(*this, &MainWindow::socket_filelist_closed_slot)); // there is no memory leak -- SocketListDialog is modeless and will delete its own memory // when it is closed } else gtk_window_present(socket_list_p->get_win()); } void MainWindow::socket_filelist_changed_notify_slot(void) { /* This slot calls SocketListDialog::set_socket_list_rows() via the MainWindow::update_socket_list signal so updating the socket list dialog if that dialog happens to be displayed, and also calls gtk_widget_queue_draw() on the drawing_area_p object maintained by the MainWindow object to force it to redraw This slot is connected to the SocketServer::filelist_changed_notify Notifier object The SocketServer::filelist_changed_notify signal is emitted by SocketServer:read_socket() when a new fax file to be sent is received via the socket and also by SocketServer::remove_file(). SocketServer::remove_file() is called by MainWindow::remove_from_socket_server_filelist() MainWindow::remove_from_socket_server_filelist() is connected to the EfaxController::remove_from_socket_server_filelist signal and to the SocketListDialog::remove_from_socket_server_filelist signal The EfaxController::remove_from_socket_server_filelist signal is emitted by EfaxController::timer_event() when a fax derived from the socket list is successfully sent The SocketListDialog::remove_from_socket_server_filelist signal is emitted by SocketListDialog::remove_file() SocketListDialog::remove_file() is connected to the PromptDialog::accepted signal (emitted when a user elects to delete a queued fax from the list of files received for faxing from the socket) */ // If a SocketListDialog object exists, cause it to update its display update_socket_list(socket_server.get_filenames()); // trigger an expose event for the drawing area gtk_widget_queue_draw(drawing_area_p); } void MainWindow::socket_filelist_closed_slot(void) { update_socket_list_connection.disconnect(); socket_dialog_connection.disconnect(); } void MainWindow::fax_to_send_notify_slot() { fax_to_send_dialog(socket_server.get_fax_to_send()); } void MainWindow::fax_to_send_dialog(const std::pair& fax_pair) { if (prog_config.sock_popup) { if (GTK_WIDGET_SENSITIVE(GTK_WIDGET(get_win())) && (efax_controller.get_state() == EfaxController::inactive || (efax_controller.get_state() == EfaxController::receive_standby && !efax_controller.is_receiving_fax()))) { notified_fax = fax_pair; SocketNotifyDialog* dialog_p = new SocketNotifyDialog(standard_size, fax_pair); dialog_p->fax_name_sig.connect(sigc::mem_fun(*this, &MainWindow::enter_socket_file_slot)); dialog_p->fax_number_sig.connect(sigc::mem_fun(*this, &MainWindow::set_number_slot)); dialog_p->sendfax_sig.connect(sigc::mem_fun(*this, &MainWindow::sendfax_slot)); gtk_window_present(dialog_p->get_win()); } else { InfoDialog* dialog_p = new InfoDialog(gettext("A print job has been received on socket"), gettext("efax-gtk socket"), GTK_MESSAGE_INFO, get_win(), !(GTK_WIDGET_SENSITIVE(GTK_WIDGET(get_win())))); gtk_window_present(dialog_p->get_win()); } } // there is no memory leak - the exec() method has not been called so the dialog // is self-owning and will delete itself when it is closed } void MainWindow::fax_received_notify_slot(const std::pair& fax_info) { if (prog_config.fax_received_exec && !prog_config.fax_received_prog.empty()) { // get the program name for the exec() call below (because this is a // multi-threaded program, we must do this before fork()ing because // we use functions to get the argument which is not async-signal-safe) std::string cmd; try { cmd.assign(Utf8::filename_from_utf8(prog_config.fax_received_prog)); } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in MainWindow::fax_received_notify_slot()\n"); cmd.assign(prog_config.fax_received_prog); } // we can use the raw output of cmd.c_str() and fax_info.first.c_str() here // - they will remain valid at the execlp() call const char* prog_name = cmd.c_str(); const char* fax_name = fax_info.first.c_str(); pid_t pid = fork(); if (pid == -1) { write_error("Fork error - exiting\n"); std::exit(FORK_ERROR); } if (!pid) { // child process - as soon as everything is set up we are going to do an exec() // now we have forked, we can connect MainWindow::error_pipe to stderr connect_to_stderr(); // this function requires a null pointer as its last argument but as its // latter arguments are passed as an elipsis we need to do an explicit cast // for 64 bit systems execlp(prog_name, prog_name, fax_name, static_cast(0)); // if we reached this point, then the execlp() call must have failed // report error and end process - use _exit() and not exit() write_error("Can't find the program to execute when a fax is received.\n" "Please check your installation and the PATH environmental variable\n"); _exit(EXEC_ERROR); } // end of child process } if (prog_config.fax_received_popup) { InfoDialog* dialog_p = new InfoDialog(gettext("A fax has been received by efax-gtk"), gettext("efax-gtk: fax received"), GTK_MESSAGE_INFO, get_win(), !(GTK_WIDGET_SENSITIVE(GTK_WIDGET(get_win())))); gtk_window_present(dialog_p->get_win()); // there is no memory leak - the exec() method has not been called so the dialog // is self-owning and will delete itself when it is closed } } void MainWindow::enter_socket_file_slot(const std::pair& fax_to_insert) { // this slot is connected to the SocketListDialog::selected_fax signal and is // called (that is, whenever the send fax button is pressed in a dialog object of // that class) // it is also connected to the SocketNotifyDialog::fax_name_sig signal, and is called // when the send fax button is pressed in a dialog object of that class // fax_to_insert.first is the fax label for the fax selected in the SocketListDialog object // and fax_to_insert.second is the real (temporary) file name obtained from mkstemp() // now show the fax label in the "Files to fax" box // in case we are called from SocketNotifyDialog::fax_name_sig (when it // is possible that the file selection buttons will be active) make // sure that the socket button is active // (we do not have to close either file selection dialogs, because if // they were open when fax_to_send_notify_slot() was called then // it would not bring up a SocketNotifyDialog object, and if a file selection // dialog was opened up after a SocketNotifyDialog object was brought up, // it would render the SocketNotifyDialog inoperative by making its parent // (this object) insensitive, so we could not reach here until it is closed) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(socket_button_p), true); // now enter the particulars of the selected file in MainWindow::selected_socket_list_file // (we will use this later to send the fax in MainWindow::sendfax_slot()) selected_socket_list_file = fax_to_insert.second; std::string socket_file_label("*** "); socket_file_label += fax_to_insert.first; socket_file_label += " ***"; set_files_slot(socket_file_label); } void MainWindow::remove_from_socket_server_filelist(const std::string& file) { socket_server.remove_file(file); // if we have stored the file name for sending, delete it // and remove from the "Fax to send" box if (file == selected_socket_list_file) { selected_socket_list_file = ""; set_files_slot(""); } } void MainWindow::set_file_items_sensitive_impl(void) { gtk_widget_set_sensitive(GTK_WIDGET(single_file_item_p), true); gtk_widget_set_sensitive(GTK_WIDGET(multiple_file_item_p), true); gtk_widget_set_sensitive(single_file_button_p, true); gtk_widget_set_sensitive(multiple_file_button_p, true); gtk_editable_set_editable(GTK_EDITABLE(file_entry_p), true); gtk_widget_set_sensitive(GTK_WIDGET(socket_list_item_p), false); gtk_widget_set_sensitive(socket_list_button_p, false); gtk_window_set_focus(get_win(), single_file_button_p); // we now need to close the socket list dialog (if it is open), empty // the std::string object holding particulars of the currently selected // print job received from the socket server and any print job in the // Gtk::Entry object for that print job close_socket_list_dialog(); selected_socket_list_file = ""; set_files_slot(""); } void MainWindow::set_socket_items_sensitive_impl(void) { gtk_widget_set_sensitive(GTK_WIDGET(single_file_item_p), false); gtk_widget_set_sensitive(GTK_WIDGET(multiple_file_item_p), false); gtk_widget_set_sensitive(single_file_button_p, false); gtk_widget_set_sensitive(multiple_file_button_p, false); gtk_editable_set_editable(GTK_EDITABLE(file_entry_p), false); gtk_widget_set_sensitive(GTK_WIDGET(socket_list_item_p), true); gtk_widget_set_sensitive(socket_list_button_p, true); gtk_window_set_focus(get_win(), socket_list_button_p); } void MainWindow::addressbook_impl(void) { if (!AddressBook::get_is_address_list()) { AddressBook* dialog_p = new AddressBook(standard_size, get_win()); dialog_p->accepted.connect(sigc::mem_fun(*this, &MainWindow::set_number_slot)); // there is no memory leak -- AddressBook will delete its own memory // when it is closed } } void MainWindow::file_list_impl(void) { FileListDialog* dialog_p = new FileListDialog(standard_size, get_win()); dialog_p->accepted.connect(sigc::mem_fun(*this, &MainWindow::set_files_slot)); // there is no memory leak -- FileListDialog is modeless and will delete its own memory // when it is closed } void MainWindow::settings_impl(void) { if (efax_controller.get_state() != EfaxController::inactive) { InfoDialog* dialog_p; std::string message(gettext("Can't change settings unless " "the program is inactive\n" "Press the Stop button to make it inactive")); dialog_p = new InfoDialog(message.c_str(), "Change settings", GTK_MESSAGE_WARNING, get_win()); // there is no memory leak -- exec() was not called so InfoDialog // will delete its own memory when it is closed } else { SettingsDialog* dialog_p = new SettingsDialog(standard_size, get_win()); dialog_p->accepted.connect(sigc::mem_fun(*this, &MainWindow::settings_changed_slot)); // there is no memory leak -- SettingsDialog will delete its own memory // when it is closed } } void MainWindow::settings_changed_slot(const std::string& messages) { text_window.reset_logfile(); if (!messages.empty()) { try { text_window.write_red_slot(Utf8::locale_from_utf8(messages).c_str()); text_window.write_red_slot("\n"); } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in MainWindow::settings_changed_slot()\n"); } } if ((!prog_config.sock_server || prog_config.sock_server_port.empty()) && socket_server.is_server_running()) { socket_server.stop(); } else if (prog_config.sock_server && !prog_config.sock_server_port.empty() && !socket_server.is_server_running()) { socket_server.start(std::string(prog_config.sock_server_port), prog_config.other_sock_client_address); } else if (socket_server.get_port() != std::string(prog_config.sock_server_port) || socket_server.get_other_sock_client_address() != prog_config.other_sock_client_address) { socket_server.stop(); socket_server.start(std::string(prog_config.sock_server_port), prog_config.other_sock_client_address); } } void MainWindow::helpfile_impl(void) { if (!HelpDialog::get_is_helpfile()) { helpfile_p = new HelpDialog(standard_size); } else gtk_window_present(helpfile_p->get_win()); // there is no memory leak -- HelpDialog is modeless and will delete its own memory // when it is closed } void MainWindow::set_number_slot(const std::string& number) { gtk_entry_set_text(GTK_ENTRY(number_entry_p), number.c_str()); if (std::strlen(gtk_entry_get_text(GTK_ENTRY(file_entry_p)))) { // reset this window as sensitive to make gtk_widget_grab_focus() work gtk_widget_set_sensitive(GTK_WIDGET(get_win()), true); gtk_widget_grab_focus(send_button_p); } } void MainWindow::about_impl(bool efax_gtk) { InfoDialog* dialog_p; if (efax_gtk) { std::string message("efax-gtk-" VERSION "\n"); message += gettext("Copyright (C) 2001 - 2006 Chris Vine\n\n" "This program is released under the " "GNU General Public License, version 2"); dialog_p = new InfoDialog(message.c_str(), gettext("About efax-gtk"), GTK_MESSAGE_INFO, get_win()); } else { std::string message = gettext("This program is a front end for efax.\n" "efax is a program released under the " "GNU General Public License, version 2 by Ed Casas.\n\n" "The copyright to efax is held by Ed Casas " "(the copyright to this program is held by Chris Vine)"); dialog_p = new InfoDialog(message.c_str(), gettext("About efax"), GTK_MESSAGE_INFO, get_win()); } // there is no memory leak -- exec() was not called so InfoDialog // will delete its own memory when it is closed } void MainWindow::translations_impl(void) { std::string message = gettext("Italian - Luca De Rugeriis\n"); message += gettext("Polish - Pawel Suwinski\n"); message += gettext("Bulgarian - Zdravko Nikolov\n"); message += gettext("Russian - Pavel Vainerman\n"); message += gettext("Hebrew - Assaf Gillat\n"); message += gettext("Greek - Hellenic Linux Users Group\n"); message += gettext("Albanian - Besnik Bleta\n"); message += gettext("Hungarian - Gergely Szakats\n"); message += gettext("Simplified Chinese - Kite Lau\n"); message += gettext("German - Steffen Wagner\n"); message += gettext("Swedish - Daniel Nylander\n"); message += gettext("Catalan - Jordi Sayol Salomo\n"); message += gettext("Traditional Chinese - Wei-Lun Chao\n"); new InfoDialog(message.c_str(), gettext("efax-gtk: Translations"), GTK_MESSAGE_INFO, get_win()); // there is no memory leak -- InfoDialog is modeless and will delete its own memory // when it is closed } void MainWindow::get_longest_button_text(void) { std::vector text_vec; text_vec.push_back(gettext("Single file")); text_vec.push_back(gettext("Multiple files")); text_vec.push_back(gettext("Socket list")); text_vec.push_back(gettext("Tel number: ")); text_vec.push_back(gettext("Send fax")); text_vec.push_back(gettext("Answer call")); text_vec.push_back(gettext("Take over call")); text_vec.push_back(gettext("Standby")); text_vec.push_back(gettext("Stop")); std::vector::const_iterator temp_iter; std::vector::const_iterator max_width_iter; int width; int height; int max_width; // create a button to work on - the GobjHandle object will claim ownership of the button GobjHandle button_h(gtk_button_new()); for (temp_iter = text_vec.begin(), max_width_iter = text_vec.begin(), width = 0, height = 0, max_width = 0; temp_iter != text_vec.end(); ++temp_iter) { GobjHandle pango_layout_h(gtk_widget_create_pango_layout(button_h, temp_iter->c_str())); pango_layout_get_pixel_size(pango_layout_h, &width, &height); if (width > max_width) { max_width = width; max_width_iter = temp_iter; } } max_text = *max_width_iter; } void MainWindow::tray_icon_left_clicked_slot(void) { GtkWidget* mainwin_p = GTK_WIDGET(get_win()); // test GTK_WIDGET_VISIBLE() as well as the 'obscured' variable - if we call // hide() on the window it does not count as a visibility notify event! if (GTK_WIDGET_VISIBLE(mainwin_p) && !obscured && !minimised && GTK_WIDGET_SENSITIVE(mainwin_p)) { gtk_widget_hide(mainwin_p); } else gtk_window_present(get_win()); } void MainWindow::tray_icon_menu_slot(int item) { switch(item) { case TrayItem::list_received_faxes: fax_list_impl(FaxListEnum::received); break; case TrayItem::list_sent_faxes: fax_list_impl(FaxListEnum::sent); break; case TrayItem::receive_answer: receive_impl(EfaxController::receive_answer); break; case TrayItem::receive_takeover: receive_impl(EfaxController::receive_takeover); break; case TrayItem::receive_standby: receive_impl(EfaxController::receive_standby); break; case TrayItem::stop: efax_controller.stop(); break; case TrayItem::quit: close_impl(); break; default: break; } } void MainWindow::style_set_impl(void) { // the main task is to set the buttons in MainWindow int width = 0; int height = 0; // create a button to work on - the GobjHandle object will claim ownership of the button GobjHandle button_h(gtk_button_new()); GtkStyle* style_p = gtk_widget_get_style(GTK_WIDGET(get_win())); gtk_widget_set_style(button_h, style_p); GobjHandle pango_layout_h(gtk_widget_create_pango_layout(button_h, max_text.c_str())); pango_layout_get_pixel_size(pango_layout_h, &width, &height); // add padding width += 18; height += 12; // have some sensible minimum width and height if a very small font chosen const int min_width = standard_size * 4; const int min_height = 30; if (width < min_width) width = min_width; if (height < min_height) height = min_height; gtk_widget_set_size_request(single_file_button_p, width, height); gtk_widget_set_size_request(multiple_file_button_p, width, height); gtk_widget_set_size_request(socket_list_button_p, width, height); gtk_widget_set_size_request(number_button_p, width, height); gtk_widget_set_size_request(send_button_p, width, height); gtk_widget_set_size_request(receive_answer_button_p, width, height); gtk_widget_set_size_request(receive_takeover_button_p, width, height); gtk_widget_set_size_request(receive_standby_button_p, width, height); gtk_widget_set_size_request(stop_button_p, width, height); // now set the status line style status_line.set_status_line(height - 6); } bool MainWindow::draw_fax_from_socket_notifier(void) { /* This method displays the "red circle" indicator which shows a fax file received from the socket is awaiting sending. It is is called in the following circumstances: - it is called in MainWindowCB::mainwin_drawing_area_expose_event(), handling the signal_expose_event signal of the MainWindow::drawing_area_p object - MainWindowCB::mainwin_drawing_area_expose_event() will in turn be called either because of a normal expose event on the desktop, or because MainWindow::socket_filelist_changed_notify_slot() has been invoked which calls gtk_widget_queue_draw() on the drawing_area_p object to force it to redraw (MainWindow::socket_filelist_changed_notify_slot() also calls SocketListDialog::set_socket_list_rows() via the MainWindow::update_socket_list signal so updating the socket list dialog if that dialog happens to be displayed) - MainWindow::socket_filelist_changed_notify_slot() is connected to the SocketServer::filelist_changed_notify Notifier object - the SocketServer::filelist_changed_notify signal is emitted by SocketServer:read_socket() when a new fax file to be sent is received via the socket and also by SocketServer::remove_file(). - SocketServer::remove_file() is called by MainWindow::remove_from_socket_server_filelist() - MainWindow::remove_from_socket_server_filelist() is connected to the EfaxController::remove_from_socket_server_filelist signal and to the SocketListDialog::remove_from_socket_server_filelist signal - the EfaxController::remove_from_socket_server_filelist signal is emitted by EfaxController::timer_event() when a fax derived from the socket list is successfully sent - the SocketListDialog::remove_from_socket_server_filelist signal is emitted by SocketListDialog::remove_file() - SocketListDialog::remove_file() is connected to the PromptDialog::accepted signal (emitted when a user elects to delete a queued fax from the list of files received for faxing from the socket) */ if (GTK_WIDGET_REALIZED(drawing_area_p)) { // if we started hidden in the system tray // the drawing area may not yet be realised GdkDrawable* drawable_p = GDK_DRAWABLE(drawing_area_p->window); if (!socket_server.get_filenames().first->empty()) { GdkColor red; red.red = static_cast(0.7 * 65535.0); red.green = 0; red.blue = 0; GdkColormap* colormap_p = gtk_widget_get_default_colormap(); if (!gdk_colormap_alloc_color(colormap_p, &red, false, true)) { write_error("Error allocating colour in colourmap in " "MainWindow::draw_fax_from_socket_notifier()\n"); } else { GobjHandle red_gc_h(gdk_gc_new(drawable_p)); gdk_gc_set_foreground(red_gc_h, &red); GdkGC* white_gc_p = gtk_widget_get_style(drawing_area_p)->white_gc; gdk_draw_arc(drawable_p, white_gc_p, true, 6, 6, standard_size - 12, standard_size - 12, 0, 64 * 360); gdk_draw_arc(drawable_p, red_gc_h, true, 8, 8, standard_size - 16, standard_size - 16, 0, 64 * 360); } } else { GdkGC* background_gc_p = gtk_widget_get_style(drawing_area_p)->bg_gc[GTK_WIDGET_STATE(drawing_area_p)]; gdk_draw_rectangle(drawable_p, background_gc_p, true, 0, 0, standard_size, standard_size); } } // processing stops here return true; } void MainWindow::strip(std::string& text) { // erase any trailing space or tab while (!text.empty() && text.find_last_of(" \t") == text.size() - 1) { text.resize(text.size() - 1); } // erase any leading space or tab while (!text.empty() && (text[0] == ' ' || text[0] == '\t')) { text.erase(0, 1); } } void MainWindow::start_pipe_thread(void) { // needed for pipe_thread() - we need to get the program PID here, as // under the linuxthreads implementation of pthreads, getpid() will // yield a different value for different threads in the same process // and we need to use the PID yielded by the main (GUI) thread prog_pid = getpid(); // read ProgConfig::working_dir in this thread - if we read it in // MainWindow::pipe_thread() we would have to protect accesses with // the ProgConfig::mutex_p global mutex since not all implementations // of std::string provide concurrent read access, and it is best to // avoid use of the global mutex where that can easily be done prog_fifo_name = prog_config.working_dir; prog_fifo_name += PIPE_NAME; // block off the signals for which we have set handlers so that the pipe // thread does not receive the signals, otherwise we will have memory synchronisation // issues in multi-processor systems - we will unblock in the initial (GUI) thread // as soon as the pipe thread has been launched sigset_t sig_mask; sigemptyset(&sig_mask); sigaddset(&sig_mask, SIGCHLD); sigaddset(&sig_mask, SIGQUIT); sigaddset(&sig_mask, SIGTERM); sigaddset(&sig_mask, SIGINT); sigaddset(&sig_mask, SIGHUP); pthread_sigmask(SIG_BLOCK, &sig_mask, 0); std::auto_ptr res = Thread::Thread::start(sigc::mem_fun(*this, &MainWindow::pipe_thread), false); if (!res.get()) { write_error("Cannot start new pipe thread\n"); } // now unblock the signals so that the initial (GUI) thread can receive them pthread_sigmask(SIG_UNBLOCK, &sig_mask, 0); } void MainWindow::pipe_thread(void) { if(access(prog_fifo_name.c_str(), F_OK) == -1) { // fifo doesn't exit - create it if (mkfifo(prog_fifo_name.c_str(), 0666) != 0) { write_error("Error creating fifo in MainWindow::pipe_thread()\n"); return; } } for (;;) { int read_fd = open(prog_fifo_name.c_str(), O_RDONLY); if(read_fd == -1) { write_error("Error opening fifo in MainWindow::pipe_thread()\n"); return; } Attachable::In pipe_stream; #ifdef HAVE_STREAM_IMBUE pipe_stream.imbue(std::locale::classic()); #endif // HAVE_STREAM_IMBUE pipe_stream.attach(read_fd); pid_t pid = 0; pipe_stream >> pid; // we do not need a mutex for prog_pid as we only use // it in this thread once this thread has been launched if (pid == prog_pid) present_window_notify(); pipe_stream.close(); } } void MainWindow::present_window_slot(void) { gtk_widget_hide(GTK_WIDGET(get_win())); // so window will come up in another // workspace if necessary gtk_window_present(get_win()); } bool MainWindow::read_error_pipe_slot(void) { char pipe_buffer[PIPE_BUF + 1]; ssize_t result; while ((result = MainWindow::error_pipe.read(pipe_buffer, PIPE_BUF)) > 0) { SharedHandle output_h(error_pipe_reassembler(pipe_buffer, result)); if (output_h.get()) text_window.write_red_slot(output_h.get()); else write_error(gettext("Invalid Utf8 received in MainWindow::read_error_pipe_slot()\n")); } return true; // retain connection for further reads } namespace { // callback for internal use only gboolean MessageTextCB::message_text_logfile_timer(void* data) { static_cast(data)->logfile_timer_impl(); return true; // we want a multi-shot timer } } // anonymous namespace MessageText::MessageText(void): MainWidgetBase(gtk_scrolled_window_new(0, 0)), logfile_count(0) { text_view_p = gtk_text_view_new(); gtk_container_add(GTK_CONTAINER(get_main_widget()), text_view_p); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view_p), GTK_WRAP_WORD); gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view_p), false); GTK_WIDGET_UNSET_FLAGS(text_view_p, GTK_CAN_FOCUS); GtkScrolledWindow* scrolled_window_p = GTK_SCROLLED_WINDOW(get_main_widget()); gtk_scrolled_window_set_shadow_type(scrolled_window_p, GTK_SHADOW_IN); gtk_scrolled_window_set_policy(scrolled_window_p, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); GdkColor red; red.red = static_cast(0.8 * 65535.0); red.green = 0; red.blue = 0; // GtkTextTags are not owned by a GTK+ container when passed to it so use a GobjHandle red_tag_h = GobjHandle(gtk_text_tag_new(0)); GdkColormap* colormap_p = gtk_widget_get_default_colormap(); if (!gdk_colormap_alloc_color(colormap_p, &red, false, true)) { write_error("Error allocating colour in colourmap in " "MessageText::MessageText()\n"); } else { g_object_set(red_tag_h.get(), "foreground-gdk", &red, static_cast(0)); } GtkTextBuffer* buffer_p = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view_p)); gtk_text_tag_table_add(gtk_text_buffer_get_tag_table(buffer_p), red_tag_h); // keep an end mark to assist scrolling to the end of buffer GtkTextIter end_iter; gtk_text_buffer_get_end_iter(buffer_p, &end_iter); // despite the name, the caller of gtk_text_buffer_create_mark() does not own // a reference count to the mark - the mark is owned by the text buffer, so // we do not need to put it in a GobjHandle handle end_mark_p = gtk_text_buffer_create_mark(buffer_p, 0, &end_iter, false); // save the current working directory before MainWindow::MainWindow can get // at it, in case it is needed in future by reset_logfile #ifndef PATH_MAX #define PATH_MAX 1023 #endif int count; void* success; for (count = 1, success = 0; !success && count < 10; count++) { ScopedHandle path_buffer_h(new char[(PATH_MAX * count) + 1]); success = getcwd(path_buffer_h.get(), PATH_MAX * count); if (success) { starting_dirname = path_buffer_h.get(); starting_dirname += '/'; } } // now open the log file if required if (!prog_config.logfile_name.empty()) { try { std::string temp(Utf8::filename_from_utf8(prog_config.logfile_name)); if (temp[0] != '/') temp.insert(0, starting_dirname); // provide an absolute path name logfile.open(temp.c_str(), std::ios::app | std::ios::out); if (!logfile) { std::string message("Can't open logfile "); message += temp; message += '\n'; write_red_slot(message.c_str()); // lock the Prog_config object while we modify it Thread::Mutex::Lock lock(*prog_config.mutex_p); prog_config.logfile_name = ""; } else { struct std::tm* time_p; std::time_t time_count; std::time(&time_count); time_p = std::localtime(&time_count); const char date_description_format[] = "%H%M %Z %d %b %Y"; const int max_description_datesize = 126; char date_description[max_description_datesize]; std::strftime(date_description, max_description_datesize, date_description_format, time_p); logfile << "\n\n***********************\n" "Beginning fax log at " << date_description << '\n' << std::endl; // set a timeout to flush every one minute timer_tag = g_timeout_add(LOGFILE_TIMER_INTERVAL, MessageTextCB::message_text_logfile_timer, this); } // save the logfile name in the local version of logfile_name // in case reset_logfile() is called later so we can test whether it has changed logfile_name = Utf8::filename_from_utf8(prog_config.logfile_name); } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in MessageText::MessageText()\n"); beep(); // lock the Prog_config object while we modify it Thread::Mutex::Lock lock(*prog_config.mutex_p); prog_config.logfile_name = ""; } } } MessageText::~MessageText(void) { if (!prog_config.logfile_name.empty()) { g_source_remove(timer_tag); struct std::tm* time_p; std::time_t time_count; std::time(&time_count); time_p = std::localtime(&time_count); const char date_description_format[] = "%H%M %Z %d %b %Y"; const int max_description_datesize = 126; char date_description[max_description_datesize]; std::strftime(date_description, max_description_datesize, date_description_format, time_p); logfile << "\n\nEnding fax log at " << date_description << '\n' << "***********************\n" << std::endl; logfile.close(); logfile.clear(); } } void MessageText::reset_logfile(void) { // check pre-conditions try { if (logfile_name == Utf8::filename_from_utf8(prog_config.logfile_name)) return; // no change! } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in MessageText::reset_logfile()\n"); beep(); // lock the Prog_config object while we modify it Thread::Mutex::Lock lock(*prog_config.mutex_p); prog_config.logfile_name = ""; return; } // proceed // first close the log file if required if (!logfile_name.empty()) { struct std::tm* time_p; std::time_t time_count; std::time(&time_count); time_p = std::localtime(&time_count); const char date_description_format[] = "%H%M %Z %d %b %Y"; const int max_description_datesize = 126; char date_description[max_description_datesize]; std::strftime(date_description, max_description_datesize, date_description_format, time_p); logfile << "\n\nEnding fax log at " << date_description << '\n' << "***********************\n" << std::endl; logfile.close(); logfile.clear(); logfile_name = ""; logfile_count = 0; // and now disconnect the old timer connection g_source_remove(timer_tag); } // now open the new log file if required if (!prog_config.logfile_name.empty()) { try { std::string temp(Utf8::filename_from_utf8(prog_config.logfile_name)); if (temp[0] != '/') temp.insert(0, starting_dirname); // provide an absolute path name logfile.open(temp.c_str(), std::ios::app | std::ios::out); if (!logfile) { std::string message("Can't open logfile "); message += temp; message += '\n'; write_red_slot(message.c_str()); // lock the Prog_config object while we modify it Thread::Mutex::Lock lock(*prog_config.mutex_p); prog_config.logfile_name = ""; } else { struct std::tm* time_p; std::time_t time_count; std::time(&time_count); time_p = std::localtime(&time_count); const char date_description_format[] = "%H%M %Z %d %b %Y"; const int max_description_datesize = 126; char date_description[max_description_datesize]; std::strftime(date_description, max_description_datesize, date_description_format, time_p); logfile << "\n\n***********************\n" "Beginning fax log at " << date_description << '\n' << std::endl; // set a timeout to flush every one minute timer_tag = g_timeout_add(LOGFILE_TIMER_INTERVAL, MessageTextCB::message_text_logfile_timer, this); } logfile_count = 0; } catch (Utf8::ConversionError&) { write_error("UTF-8 conversion error in MessageText::reset_logfile()\n"); beep(); // lock the Prog_config object while we modify it Thread::Mutex::Lock lock(*prog_config.mutex_p); prog_config.logfile_name = ""; } } // save the logfile name in the local version of logfile_name // in case reset_logfile() is called again later // we don't need to check the conversion here, as if it was invalid it // would have thrown above logfile_name = Utf8::filename_from_utf8(prog_config.logfile_name); } void MessageText::logfile_timer_impl(void) { logfile_count++; if (logfile_count >= LOGFILE_MARK_COUNT) { logfile_count = 0; struct std::tm* time_p; std::time_t time_count; std::time(&time_count); time_p = std::localtime(&time_count); const char date_description_format[] = "%H%M %Z %d %b %Y"; const int max_description_datesize = 126; char date_description[max_description_datesize]; std::strftime(date_description, max_description_datesize, date_description_format, time_p); logfile << "\n**** " << date_description << " ****\n" << std::endl; } logfile.flush(); } void MessageText::cleanify(std::string& message) { std::string::size_type chunk_start = 0; std::string::size_type chunk_end; while (chunk_start != std::string::npos) { std::string::size_type chunk_size; chunk_end = message.find('\n', chunk_start); // include in the search chunk any '\n' found if (chunk_end + 1 >= message.size()) chunk_end = std::string::npos; else if (chunk_end != std::string::npos) chunk_end++; // now search the relevant string/substring and erase from message if (chunk_end == std::string::npos) chunk_size = message.size() - chunk_start; else chunk_size = chunk_end - chunk_start; if (message.substr(chunk_start, chunk_size).find(" Copyright ") != std::string::npos || message.substr(chunk_start, chunk_size).find(" compiled ") != std::string::npos || message.substr(chunk_start, chunk_size).find("terminating on signal 15") != std::string::npos) { message.erase(chunk_start, chunk_size); // if we have erased this chunk then we don't want to reset chunk_start // unless we have finished examining message if (chunk_end == std::string::npos) chunk_start = std::string::npos; } // if we didn't erase the last chunk, then we need to reset // chunk_start to beginning of next substring (or to string::npos) else chunk_start = chunk_end; } } void MessageText::write_black_slot(const char* message) { std::string temp(message); cleanify(temp); bool scrolling = false; GtkAdjustment* adj_p = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(get_main_widget())); if (adj_p->value >= (adj_p->upper - adj_p->page_size - 1e-12)) { scrolling = true; } GtkTextBuffer* buffer_p = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view_p)); GtkTextIter end_iter; gtk_text_buffer_get_end_iter(buffer_p, &end_iter); gtk_text_buffer_insert(buffer_p, &end_iter, temp.data(), temp.size()); if (scrolling) { gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text_view_p), end_mark_p, 0.0, false, 0.0, 0.0); } // now relay to standard output ssize_t result; ssize_t written = 0; ssize_t to_write_count = temp.size(); const char* write_text = temp.c_str(); do { result = ::write(1, write_text + written, to_write_count); if (result > 0) { written += result; to_write_count -= result; } } while (to_write_count && (result != -1 || errno == EINTR)); // now log to file if required if (!prog_config.logfile_name.empty()) logfile << temp; } void MessageText::write_red_slot(const char* message) { std::string temp(message); cleanify(temp); bool scrolling = false; GtkAdjustment* adj_p = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(get_main_widget())); if (adj_p->value >= (adj_p->upper - adj_p->page_size - 1e-12)) { scrolling = true; } GtkTextBuffer* buffer_p = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view_p)); GtkTextIter end_iter; gtk_text_buffer_get_end_iter(buffer_p, &end_iter); gtk_text_buffer_insert_with_tags(buffer_p, &end_iter, temp.data(), temp.size(), red_tag_h, static_cast(0)); if (scrolling) { gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text_view_p), end_mark_p, 0.0, false, 0.0, 0.0); } // now relay to standard error ssize_t result; ssize_t written = 0; ssize_t to_write_count = temp.size(); const char* write_text = temp.c_str(); do { result = ::write(2, write_text + written, to_write_count); if (result > 0) { written += result; to_write_count -= result; } } while (to_write_count && (result != -1 || errno == EINTR)); // now log to file if required if (!prog_config.logfile_name.empty()) logfile << temp; } StatusLine::StatusLine(const int size): MainWidgetBase(gtk_hbox_new(false, 0)), standard_size(size) { status_label_p = gtk_label_new(gettext("Inactive")); GtkWidget* help_label_p = gtk_label_new(gettext("Press F1 for help")); GtkWidget* status_frame_p = gtk_frame_new(0); GtkWidget* help_frame_p = gtk_frame_new(0); gtk_frame_set_shadow_type(GTK_FRAME(status_frame_p), GTK_SHADOW_IN); gtk_frame_set_shadow_type(GTK_FRAME(help_frame_p), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(status_frame_p), status_label_p); gtk_container_add(GTK_CONTAINER(help_frame_p), help_label_p); gtk_box_pack_start(GTK_BOX(get_main_widget()), status_frame_p, true, true, 0); gtk_box_pack_end(GTK_BOX(get_main_widget()), help_frame_p, true, true, 0); gtk_widget_set_size_request(status_label_p, standard_size * 7, -1); gtk_widget_set_size_request(help_label_p, standard_size * 6, -1); gtk_container_set_border_width(GTK_CONTAINER(get_main_widget()), 2); gtk_widget_show_all(get_main_widget()); } void StatusLine::set_status_line(int new_height) { // set the height if (new_height < standard_size + 2) new_height = standard_size + 2; gtk_widget_set_size_request(get_main_widget(), -1, new_height); // make the status label display in red GdkColor red; red.red = static_cast(0.9 * 65535.0); red.green = 0; red.blue = 0; // we don't need to allocate the GdkColor red object before it is // used by gtk_widget_modify_fg() - that function will do it itself gtk_widget_modify_fg(status_label_p, GTK_STATE_NORMAL, &red); } void StatusLine::write_status_slot(const char* text) { gtk_label_set_text(GTK_LABEL(status_label_p), text); } int connect_to_stderr(void) { int result = MainWindow::error_pipe.connect_to_stderr(); if (!result) MainWindow::connected_to_stderr = true; return result; } ssize_t write_error(const char* message) { // this writes to a pipe (which will provide asynchronous to // synchronous conversion) so it can be used by different threads // without upsetting GTK+. It also only uses async-signal-safe // system functions if connect_to_stderr() has been called (which // is only in a child process), and in the main process (where // connect_to_stderr() is not called) is thread safe with only a // mutex in this function because (a) although PipeFifo::read() // and PipeFifo::write() check the value of PipeFifo::read_fd and // PipeFifo::write_fd respectively, and PipeFifo::open(), // PipeFifo::make_writeonly(), PipeFifo::make_readonly(), // PipeFifo::close(), PipeFifo::connect_to_stdin(), // PipeFifo::connect_to_stdout() and PipeFifo::connect_to_stderr() // change those values, all but the last one are never called on // MainWindow::error_pipe until (if at all) error_pipe goes out of // scope when global objects are destroyed as the program exits, and // (b) PipeFifo::connect_to_stderr() is only ever called after // fork()ing into a new single-threaded process // The pipe is non-blocking - it will be emptied fairly promptly // but messages should be less than PIPE_BUF in length or any excess // will be lost unless the caller checks the return value and acts // accordingly ssize_t result = 0; ssize_t written = 0; ssize_t to_write_count = std::strlen(message); // if write_error_mutex_p is NULL then this must be the static // pipe being written to before the MainWindow constructor is // entered - we might was well check this condition in this // function although it should not happen if (!MainWindow::connected_to_stderr && write_error_mutex_p) { Thread::Mutex::Lock lock(*write_error_mutex_p); do { result = MainWindow::error_pipe.write(message + written, to_write_count); if (result > 0) { written += result; to_write_count -= result; } } while (to_write_count && result != -1); // the pipe checks for EINTR itself } else { // no mutex is required in this case: connect_to_stderr() is only // called after fork()ing and it is for the new process to synchronise // its writes to standard error (in efax-gtk child processes are all // single-threaded anyway) and as between different processes, POSIX // guarantees that writes not larger than PIPE_BUF in size will not be // interleaved. Since no mutex is used in this case, this function is // async-signal-safe for child processes. It can therefore be used // after fork()ing before exec()ing even though this is a multi-threaded // program do { result = ::write(2, message + written, to_write_count); if (result > 0) { written += result; to_write_count -= result; } } while (to_write_count && (result != -1 || errno == EINTR)); } return written; } void close_signalhandler(int) { close_flag = true; }