/* Copyright (c) 2006 by Arkkra Enterprises */ /* All rights reserved */ // This file includes methods related to the "Run" menu on the main toolbar. #include "Run.H" #include "Preferences.H" #include "globals.H" #include "Config.H" #include "defines.h" #include #include #include #include #include #include #include #include #ifdef OS_LIKE_UNIX #include #include #endif // Message for when Mup fails, but we can't figure out why. const char * Unknown_Mup_failure = "Mup failed. Reason unknown."; // Describe use of numbers passed to -x option #define EXTRACT_NUM_DESCRIPTION "0 is used for pickup measure.\n" \ "Negative numbers are used to specify\n" \ "number of measures from the end.\n" // Tooltip is the same for all macro entries const char * macro_definition_tip = "Your Mup input can use macros to vary characteristics\n" "of the output for different runs.\n" "Enter the name of a macro you want to define,\n" "optionally followed by an equals sign and macro definition.\n" "Macro names must consist of upper case letters,\n" "numbers, and underscores, starting with an upper case letter."; //------ Class for user to set parameters to pass to Mup Run_parameters_dialog::Run_parameters_dialog(void) : Fl_Double_Window(0, 0, 700, 350, "Mup Options") { enable_combine_p = new Fl_Check_Button(20, 20, 165, 30, "Enable Auto Multirest"); enable_combine_p->tooltip("Set whether to automatically combine\n" "consecutive measures of rests into a multirest."); enable_combine_p->callback(combine_cb, this); enable_combine_p->when(FL_WHEN_CHANGED); rest_combine_p = new Positive_Int_Input(370, 20, 40, 30, "Min Measures to Combine"); rest_combine_p->tooltip("Minimum number of consecutive measures\n" "of rest that will be combined into a multirest."); // Only becomes active if auto-multirest combine becomes active rest_combine_p->deactivate(); // -p firstpage first_page_p = new Positive_Int_Input(300, 60, 50, 30, "First Page's Page Number"); first_page_p->tooltip("Set the page number to use\n" "for the first page of music output."); // -o pagelist pages_p = new Fl_Group(30, 100, 380, 70, "Pages to Display"); pages_p->box(FL_ENGRAVED_BOX); pages_p->align(FL_ALIGN_TOP_LEFT | FL_ALIGN_INSIDE); all_p = new Fl_Check_Button(170, 105, 55, 20, "All"); all_p->type(FL_RADIO_BUTTON); all_p->tooltip("You can restrict to displaying only a subset\n" "of the pages of music, but this shows all pages."); all_p->callback(selected_pages_cb, this); all_p->when(FL_WHEN_CHANGED); odd_p = new Fl_Check_Button(245, 105, 60, 20, "Odd"); odd_p->type(FL_RADIO_BUTTON); odd_p->tooltip("This restricts to displaying only\n" "odd numbered pages. This is generally\n" "most useful for printing to a\n" "single-sided printer."); odd_p->callback(selected_pages_cb, this); odd_p->when(FL_WHEN_CHANGED); even_p = new Fl_Check_Button(320, 105, 70, 20, "Even"); even_p->type(FL_RADIO_BUTTON); even_p->tooltip("This restricts to displaying only\n" "even numbered pages. This is generally\n" "most useful for printing to a\n" "single-sided printer."); even_p->callback(selected_pages_cb, this); even_p->when(FL_WHEN_CHANGED); selected_p = new Fl_Check_Button(50, 135, 85, 20, "Selected:"); selected_p->tooltip("This restricts to displaying only\n" "the pages you list in the blank to the right."); selected_p->type(FL_RADIO_BUTTON); selected_p->callback(selected_pages_cb, this); selected_p->when(FL_WHEN_CHANGED); page_list_p = new Fl_Input(150, 130, 230, 30, ""); page_list_p->tooltip("List the specific pages you want displayed.\n" "You can list individual pages separated by commas,\n" "and/or ranges of pages separated by dashes.\n" "Pages may be listed more than once and in any order."); page_list_p->deactivate(); saved_page_list = 0; pages_p->end(); // -s stafflist staff_list_p = new Fl_Input(150, 180, 260, 30, "Staffs to Display/Play"); staff_list_p->tooltip("If you wish to display or play only a subset\n" "of the staffs, or voices on a staff, list them here.\n" "Staffs are specified by comma-separated numbers\n" "or ranges, like 1,4 or 1-3,5-6. Staff numbers can be\n" "followed by v1, v2, or v3 to limit to a single voice.\n"); saved_staff_list = 0; // -x extract list extract_begin_p = new Int_Input(150, 220, 95, 30, "Extract Measures"); extract_begin_p->tooltip("If you wish to display or play only selected\n" "measures, enter the starting measure here.\n" EXTRACT_NUM_DESCRIPTION); extract_begin_p->callback(extract_cb, this); extract_begin_p->when(FL_WHEN_CHANGED); extract_end_p = new Int_Input(315, 220, 95, 30, "through"); extract_end_p->tooltip("If you wish to display or play only selected\n" "measures, enter the ending measure here.\n" EXTRACT_NUM_DESCRIPTION); extract_end_p->deactivate(); // Macros macros_group_p = new Fl_Group(430, 20, 250, 40 + MAX_MACROS * 40, "Macro Definitions"); macros_group_p->box(FL_ENGRAVED_BOX); macros_group_p->align(FL_ALIGN_TOP | FL_ALIGN_INSIDE); int m; for (m = 0; m < MAX_MACROS; m++) { macro_definitions_p[m] = new Fl_Input(450, 50 + m * 40, 210, 30, ""); macro_definitions_p[m]->tooltip(macro_definition_tip); saved_macro_definitions[m] = 0; } macros_group_p->end(); save_p = new Fl_Return_Button(100, h() - 50, 80, 30, "Save"); save_p->callback(Save_cb, this); save_p->when(FL_WHEN_RELEASE); clear_form_p = new Fl_Button((w() - 150) / 2, h() - 50, 150, 30, "Clear Options"); clear_form_p->callback(clear_form_cb, this); clear_form_p->when(FL_WHEN_RELEASE); cancel_p = new Fl_Button(w() - 180, h() - 50, 80, 30, "Cancel"); cancel_p->shortcut(FL_Escape); cancel_p->callback(Cancel_cb, this); cancel_p->when(FL_WHEN_RELEASE); // Set everything to default values clear_form(); // Arrange to clean up all the new-ed widgets in destructor. end(); // Arrange for window manager closes to do Cancel. callback(Cancel_cb, this); when(FL_WHEN_NEVER); } Run_parameters_dialog::~Run_parameters_dialog(void) { int m; for (m = 0; m < MAX_MACROS; m++) { if (saved_macro_definitions[m] != 0) { delete saved_macro_definitions[m]; } } } //---- Callback for when user changes enablement of rest combining, // to gray or ungray the field for how many measures to combine. CALL_BACK(Run_parameters_dialog, combine) { if (enable_combine_p->value()) { rest_combine_p->activate(); } else { rest_combine_p->value(""); rest_combine_p->deactivate(); } } //---- Callback for when user changes setting of start of measure extraction, // to gray or ungray the field for ending measure of extraction. CALL_BACK(Run_parameters_dialog, extract) { if (extract_begin_p->size() > 0) { extract_end_p->activate(); } else { extract_end_p->value(""); extract_end_p->deactivate(); } } //---- Callback for when user changes setting of page selection, // to gray or ungray the field for listing the specific pages to display CALL_BACK(Run_parameters_dialog, selected_pages) { page_list_p->value(""); if (selected_p->value() == 1) { page_list_p->activate(); } else { page_list_p->deactivate(); } } //----Callback for button for clearing the form CALL_BACK(Run_parameters_dialog, clear_form) { // Set everything to default values saved_enable_combine = false; enable_combine_p->value(saved_enable_combine); (void) sprintf(saved_combine_measures, ""); rest_combine_p->value(saved_combine_measures); (void) sprintf(saved_first_page, "%d", MINFIRSTPAGE); first_page_p->value(saved_first_page); if (saved_page_list != 0) { delete saved_page_list; } saved_page_list = new char[1]; saved_page_list[0] = '\0'; page_list_p->value(saved_page_list); all_p->value(1); saved_pages = ALL_PAGES; if (saved_staff_list != 0) { delete saved_staff_list; } saved_staff_list = new char[1]; saved_staff_list[0] = '\0'; staff_list_p->value(saved_staff_list); saved_extract_begin[0] = '\0'; extract_begin_p->value(saved_extract_begin); saved_extract_end[0] = '\0'; extract_end_p->value(saved_extract_end); int m; for (m = 0; m < MAX_MACROS; m++) { macro_definitions_p[m]->value(""); if (saved_macro_definitions[m] != 0) { delete saved_macro_definitions[m]; saved_macro_definitions[m] = 0; } } } //---- callback for when user clicks "Save" on Set Options form CALL_BACK(Run_parameters_dialog, Save) { // -c rest combine option bool error = false; saved_enable_combine = enable_combine_p->value(); int num_meas = (int) atoi(rest_combine_p->value()); if (saved_enable_combine && (num_meas < MINRESTCOMBINE || num_meas > MAXRESTCOMBINE)) { fl_alert("\"Min Measures to Combine\" must be between\n" "%d and %d.", MINRESTCOMBINE, MAXRESTCOMBINE); error = true; } else { (void) strcpy(saved_combine_measures, rest_combine_p->value()); } // -p first page option int page_num = (int) atoi(first_page_p->value()); if (page_num < MINFIRSTPAGE || page_num > MAXFIRSTPAGE) { fl_alert("\"First Page\" number must be between\n" "%d and %d.", MINFIRSTPAGE, MAXFIRSTPAGE); error = true; } else { (void) strcpy(saved_first_page, first_page_p->value()); } // We don't really fully parse and error check the -o argument, // but make sure it at least is made up of only valid characters. if (page_list_p->size() > 0) { if (strspn(page_list_p->value(), "0123456789,- \t") != page_list_p->size()) { fl_alert("\"Pages to Display\" value is not valid."); error = true; } else { // Free existing, if any, and save new value. if (saved_page_list != 0) { delete saved_page_list; } saved_page_list = new char[page_list_p->size() + 1]; strcpy(saved_page_list, page_list_p->value()); } } else if (saved_page_list[0] != '\0') { // Had a list before but not anymore. // Null out the existing list. delete saved_page_list; saved_page_list = new char[1]; saved_page_list[0] = '\0'; } if (all_p->value() == 1) { saved_pages = ALL_PAGES; } if (odd_p->value() == 1) { saved_pages = ODD_PAGES; } if (even_p->value() == 1) { saved_pages = EVEN_PAGES; } if (selected_p->value() == 1) { saved_pages = SELECTED_PAGES; } if (saved_pages == SELECTED_PAGES && page_list_p->size() == 0) { fl_alert("You did not list which selected pages you want."); error = true; } // Similar for staff list (-s option) if (staff_list_p->size() > 0) { if (strspn(staff_list_p->value(), "0123456789,-v \t") != staff_list_p->size()) { fl_alert("\"Staffs to Display/Play\" is not valid"); error = true; } else { if (saved_staff_list != 0) { delete saved_staff_list; } saved_staff_list = new char[staff_list_p->size() + 1]; strcpy(saved_staff_list, staff_list_p->value()); } } else if (saved_staff_list[0] != '\0') { delete saved_staff_list; saved_staff_list = new char[1]; saved_staff_list[0] = '\0'; } // We can't really error check -x option values. // Widget will have already constrained to integer, // and we have no way to know what range is valid, // since we don't know how many measures the song has. // However, to keep things simple, we have fixed-size array of 8 bytes // for saving value, so can only allow up to 6 digits plus sign. if (abs(atoi(extract_begin_p->value())) >= 1000000) { fl_alert("\"Extract Measures\" start value is out of range."); error = true; } else { (void) strcpy(saved_extract_begin, extract_begin_p->value()); } if (abs(atoi(extract_end_p->value())) >= 1000000) { fl_alert("\"Extract Measures\" end value is out of range."); error = true; } else { (void) strcpy(saved_extract_end, extract_end_p->value()); } // Macros int m; int macsize; bool changed; for (m = 0; m < MAX_MACROS; m++) { changed = false; if ((macsize = macro_definitions_p[m]->size()) > 0) { if (macro_error(macro_definitions_p[m]->value())) { fl_alert("Macro %d has invalid format.", m + 1); error = true; } else if (saved_macro_definitions[m] == 0) { // no macro before, but is one now changed = true; } else if (strcmp(macro_definitions_p[m]->value(), saved_macro_definitions[m]) != 0) { // A different macro value than before delete saved_macro_definitions[m]; changed = true; } if (changed) { saved_macro_definitions[m] = new char[macsize + 1]; (void) strcpy(saved_macro_definitions[m], macro_definitions_p[m]->value()); } } else if (macsize == 0 && saved_macro_definitions[m] != 0) { // Used to be a macro value, but not anymore delete saved_macro_definitions[m]; saved_macro_definitions[m] = 0; } } // If there were user errors, we leave the window up for user to // correct the errors. User can, of course, cancel without correcting, // in which case if they try to run, Mup will complain about the // bad arguments. if ( ! error ) { hide(); } } //---- Callback for when user clicks "Cancel" on Set Options form CALL_BACK(Run_parameters_dialog, Cancel) { // Put back all the previous values enable_combine_p->value(saved_enable_combine); rest_combine_p->value(saved_combine_measures); first_page_p->value(saved_first_page); // It seems setting a radio button via value(1) doesn't reset the // others, so clear all, then set the one we want all_p->value(0); odd_p->value(0); even_p->value(0); selected_p->value(0); switch (saved_pages) { default: // default should be impossible case ALL_PAGES: all_p->value(1); break; case ODD_PAGES: odd_p->value(1); break; case EVEN_PAGES: even_p->value(1); break; case SELECTED_PAGES: selected_p->value(1); break; } page_list_p->value(saved_page_list); staff_list_p->value(saved_staff_list); extract_begin_p->value(saved_extract_begin); extract_end_p->value(saved_extract_end); int m; for (m = 0; m < MAX_MACROS; m++) { macro_definitions_p[m]->value( saved_macro_definitions[m] == 0 ? "" : saved_macro_definitions[m]); } hide(); } // Return true if check of macro finds an error. bool Run_parameters_dialog::macro_error(const char * macro) { // First character must be an upper case letter. if ( ! isupper(macro[0])) { return(true); } // Everything up to = or end of string, whichever comes first, // must be upper case letter, digit, or underscore. const char * m_p; for (m_p = macro; *m_p != '\0' && *m_p != '='; m_p++) { if ( ! isupper(*m_p) && ! isdigit(*m_p) && *m_p != '_') { return(true); } } return(false); } //--------class for Run menu ----------------------------------------------- Run::Run() { parameters_p = 0; report_p = 0; #ifdef OS_LIKE_WIN32 display_child.hProcess = 0; display_child.dwProcessId = 0; MIDI_child.hProcess = 0; MIDI_child.dwProcessId = 0; #else display_child = 0; MIDI_child = 0; #endif } Run::~Run() { if (parameters_p != 0) { delete parameters_p; parameters_p = 0; } if (report_p != 0) { delete report_p; report_p = 0; } // Kill off any child processes clean_up(); } //------------callback for Display menu item CALL_BACK(Run, Display) { Run_Mup(false, true); } //-----Callback for menu item to play MIDI file CALL_BACK(Run, Play) { Run_Mup(true, true); } //---------callback for menu item for writing PostScript output CALL_BACK(Run, WritePostScript) { Run_Mup(false, false); } //---------callback for menu item for writing MIDI output CALL_BACK(Run, WriteMIDI) { Run_Mup(true, false); } //---- callback for menu item to collect Mup command line parameters from user CALL_BACK(Run, Options) { if (parameters_p == 0) { parameters_p = new Run_parameters_dialog(); } parameters_p->show(); } //--------- This lets Run class know the file name and editor buffer, // which it needs access to, so it can run Mup on its contents. void Run::set_file(File * file_info_p) { file_p = file_info_p; } // Run Mup commands with user's parameters on the current file void Run::Run_Mup(bool midi, bool show_or_play) { const char * mup_input = file_p->effective_filename(); // Make sure file has been written. // The fiilename == 0 will be hit in the case where // user did New From Template but made no changes. if (file_p->unsaved_changes || file_p->filename == 0) { int auto_save; (void) Preferences_p->get(Auto_save_preference, auto_save, Default_auto_save); if (auto_save) { file_p->Save(false); if (file_p->unsaved_changes || file_p->filename == 0) { // User probably canceled a Save that got // turned into a SaveAs, // or maybe Save failed. If not properly // saved, we shouldn't try to process it. return; } } else { bool hide_the_No; const char * extra_text; // If there is a previous version of the file, // allow user to select "No," which means use that // previous version rather than writing out current. if (access(mup_input, F_OK) == 0) { hide_the_No = false; extra_text = "(If you select \"No,\" " "the most recently saved version " "of the Mup input file will be used.)"; } else { // No previous version hide_the_No = true; extra_text = ""; } switch (file_p->save_changes_check(extra_text, hide_the_No)) { default: // default case should be impossible case Save_confirm_dialog::Cancel: return; case Save_confirm_dialog::No: break; case Save_confirm_dialog::Yes: file_p->Save(false); if (file_p->unsaved_changes || file_p->filename == 0) { // User probably canceled the Save, // or maybe it failed. If not properly // saved, we shouldn't try to process it. return; } break; } } // If was "Untitled" before, it is now saved under a good // name, so get what that is. mup_input = file_p->effective_filename(); } // Get length of file name without the extension int base_length = strlen(mup_input) - strlen(fl_filename_ext(mup_input)); // Create output file name char mup_output[base_length + (midi ? 5 : 4)]; strncpy(mup_output, mup_input, base_length); strcpy(mup_output + base_length, (midi ? ".mid" : ".ps")); // Create error file name char mup_error[base_length + 5]; strncpy(mup_error, mup_input, base_length); strcpy(mup_error + base_length, ".err"); // Get Mup command to use. char * mup_command; (void) Preferences_p->get(Mup_program_location, mup_command, Default_Mup_program_location); char full_location[FL_PATH_MAX]; if ( ! find_executable(mup_command, full_location)) { fl_alert("Mup command not found.\n" "Check Config > File Locations setting."); return; } if (parameters_p == 0) { // User hasn't set any parameters, so we will use all // defaults. Creating the instance lets us deference it // below, so we don't have to care whether user ever // went to the Set Options page or not. parameters_p = new Run_parameters_dialog(); } // Build up list of arguments. // array slots needed for args: // 1 for Mup command itself // 2 for -e and arg // 2 for -f or -m and arg // 2 for -c and arg // 2 for -p and arg // 2 for -o and arg // 2 for -s and arg // 2 for -x and arg // 1 for Mup input file name // 2 for each -D and its macro definition arg // 1 for null terminator const char * command[17 + 2 * MAX_MACROS]; command[0] = full_location; command[1] = "-e"; command[2] = mup_error; command[3] = (midi ? "-m" : "-f"); command[4] = mup_output; int arg_offset = 5; // rest combine if (parameters_p->enable_combine_p->value() && parameters_p->rest_combine_p->size() > 0) { command[arg_offset++] = "-c"; command[arg_offset++] = parameters_p->rest_combine_p->value(); } // first page if (parameters_p->first_page_p->size() > 0) { command[arg_offset++] = "-p"; command[arg_offset++] = parameters_p->first_page_p->value(); } // page list switch (parameters_p->saved_pages) { default: // default should be impossible case Run_parameters_dialog::ALL_PAGES: break; case Run_parameters_dialog::ODD_PAGES: command[arg_offset++] = "-o"; command[arg_offset++] = "odd"; break; case Run_parameters_dialog::EVEN_PAGES: command[arg_offset++] = "-o"; command[arg_offset++] = "even"; case Run_parameters_dialog::SELECTED_PAGES: if (parameters_p->page_list_p->size() > 0) { command[arg_offset++] = "-o"; command[arg_offset++] = parameters_p->page_list_p->value(); } // Else they said selected, but then didn't list // any particular pages. We treat like ALL_PAGES, // since no pages is useless. // We would have already given error message, // but they must have ignored it. break; } // staff list if (parameters_p->staff_list_p->size() > 0) { command[arg_offset++] = "-s"; command[arg_offset++] = parameters_p->staff_list_p->value(); } // extract list char xoption[parameters_p->extract_begin_p->size() + parameters_p->extract_end_p->size() + 2]; if (parameters_p->extract_begin_p->size() > 0) { command[arg_offset++] = "-x"; (void) strcpy(xoption, parameters_p->extract_begin_p->value()); if (parameters_p->extract_end_p->size() > 0) { (void) strcat(xoption, ","); (void) strcat(xoption, parameters_p->extract_end_p->value()); } command[arg_offset++] = xoption; } // -D options int m; for (m = 0; m < MAX_MACROS; m++) { if (parameters_p->saved_macro_definitions[m] != 0) { command[arg_offset++] = "-D"; command[arg_offset++] = parameters_p->saved_macro_definitions[m]; } } // Mup input file name and null terminator command[arg_offset++] = mup_input; command[arg_offset++] = 0; static bool set_mupquiet = false; if ( ! set_mupquiet ) { putenv("MUPQUIET=1"); set_mupquiet = true; } // Look up the right (dis)player program to use. // On Windows we need this even if we are only writing the file, // not actually (dis)playing. To keep the code simpler, // we just always look it up. char * player_command; if (midi) { (void) Preferences_p->get(MIDI_player_location, player_command, Default_MIDI_player_location); } else { (void) Preferences_p->get(Viewer_location, player_command, Default_viewer_location); } #ifdef OS_LIKE_WIN32 // Media player locks the file it plays, so if we had // run it before, we have to make it release the lock // before we run Mup. Also, if it was playing a different file // before, it doesn't seem to want to play ours. // So if the MIDI player of choice is the media player, // we first try to make it close somewhat gracefully, // and wait a second for that to complete. // In any case we try to kill off any child MIDI players we // know of. If the graceful close already worked, // it should find the child already dead. // If they are using some other MIDI player, // we don't know what to do, so we'll just kill any child // MIDI player we know spawned previously, if any. // Would be nice to be able to do something less drastic than // kill the process, but we're not sure what else would be effective. if (midi) { if (is_mplayer(player_command)) { HWND window_handle; if ((window_handle = FindWindow(0, "Windows Media Player")) != 0) { SendMessage(window_handle, WM_CLOSE, 0, 0); } _sleep(1000); } if (has_MIDI_child()) { kill_process(&MIDI_child, "MIDI player"); } } // Somewhat similarly, we kill off any prior displayer, unless // the displayer is GSview, which we know we can pass -e argument // to make it reuse existing instance, if any. else if ( ! is_gsview(player_command) && has_display_child()) { kill_process(&display_child, "display"); } #endif // Run the command. int ret = execute_command(command, 0, true); // Report the errors, if any. struct stat info; if (stat(mup_error, &info) == 0) { if (info.st_size > 0) { if (report_p == 0) { report_p = new Error_report(); } report_p->loadfile(mup_error); report_p->show(); } unlink(mup_error); } else if (ret != 0) { // Exited with error, but left no error file. // Must have died badly (core dumped, execvp failed, etc) #if defined(WIFEXITED) && defined(WIFSIGNALED) if (WIFEXITED(ret)) { // Did exit(), so most likely exec failed // due to bad path to Mup program, // although we should have caught that above. fl_alert("Mup exited with return code %d but no error output.\n" "Check Config > File Locations setting.", WEXITSTATUS(ret)); } else if (WIFSIGNALED(ret)) { // Probably core dump :-( fl_alert("Mup exited due to signal %d.", WTERMSIG(ret)); } else { fl_alert(Unknown_Mup_failure); } #else // WIF... macros not defined if (ret == -1) { #ifdef OS_LIKE_WIN32 // Look up the error reason to include in message. DWORD format_retval; LPVOID error_string = 0; if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, 0, GetLastError(), LANG_NEUTRAL, (LPTSTR)&error_string, 0, 0)) { fl_alert("Failed to execute Mup program:\n%s" "Check settings in Config > File Locations.", (char *) error_string); LocalFree((HLOCAL)error_string); } else { fl_alert(Unknown_Mup_failure); } } #else // not Windows fl_alert(Unknown_Mup_failure); } #endif else { fl_alert(Unknown_Mup_failure); } #endif // WIF... macros } if (ret != 0) { // Something went wrong. We should not go on to // display/play even if user had asked for that. return; } // We wrote the output file successfully. // Hide any previous error window and // check if we need to (dis)play the results. if (report_p != 0) { report_p->hide(); } if ( ! show_or_play ) { // User just asked to generate a file, not (dis)play it. fl_message("%s output file\n\n %s\n\n" "has been successfully created.", (midi ? "MIDI" : "PostScript"), mup_output); return; } if ( ! find_executable(player_command, full_location)) { fl_alert("Unable to run %s command.\n" "Check Config > File Locations setting.", player_command); return; } #ifdef OS_LIKE_UNIX // For MIDI, we try to kill the previous player // if any, since we'll probably need the same // sound card. Note that for Windows, we did this // even before running Mup, since the player may lock // the file so Mup would be unable to write it. if (midi) { kill_process(&MIDI_child, "MIDI player"); } // If using gv as displayer and one already running on // this file, it would be nice to get send it SIGHUP // to make it re-read the file // rather than starting a new one. // But for other displayers, we don't know what signal // might work, if any. And even with gv, if we send // the signal, we have no way of knowing whether it // actually worked. And there are lots of cases to // consider. Suppose the displayer was gv, // and they brought up an instance, // then they changed to some other displayer, // and then back to gv. Should we have killed off // the first gv when bringing up the other viewer, // of should we keep it up and reuse it after they // change back? What if they kill the current // instance just after we decided to re-use it? // So just keep things simple for now. Always // kill off any existing viewer we know of and // start up a new one. else { // displaying kill_process(&display_child, "display"); } #endif // Fill in the argv array for displayer/player command[0] = full_location; #ifdef OS_LIKE_WIN32 // Media player appears to ignore its argument // if it isn't a full path name, including drive. // So ensure we have everything. char fullpath[FL_PATH_MAX]; if (mup_output[1] != ':' ) { // We don't have complete path. // Get current directory including drive. if (getcwd(fullpath, sizeof(fullpath)) == 0) { fl_alert("Unable to determine current folder."); return; } if (mup_output[0] != dir_separator()) { // Relative path (void) sprintf(fullpath + strlen(fullpath), "%c%s", dir_separator(), mup_output); } else { // Was full path except for drive. (void) strcpy(fullpath + 2, mup_output); } } else { // Can use existing path as is. (void) strcpy(fullpath, mup_output); } if ( ! midi && is_gsview(player_command)) { // For gsview, use -e to make it use // existing instance if any. command[1] = "-e"; command[2] = fullpath; command[3] = '\0'; } else { command[1] = fullpath; command[2] = '\0'; } #else command[1] = mup_output; command[2] = '\0'; #endif if (execute_command(command, (midi ? &MIDI_child : &display_child)) != 0) { fl_alert("Unable to run %s command.\n" "Check settings under Config > File Locations.", command[0]); } } // Execute given command with the given argv. // If proc_info_p is zero, wait for the process to complete, // otherwise save information about the spawned process in what it points to, // so that the caller can keep track of it while it runs independently. // The hide_window parameter is only used for Windows and causes the // spawned process to be created with flags to not create a window, // This lets us use a console mode version of Mup, so traditional users // can continue to run Mup in a console without mupmate, but we can // run the same .exe without the annoyance of a console popping up. // Returns 0 on success, -1 on failure to create process, // or what the process returned if it had non-zero exit code, // and was waited for. int Run::execute_command(const char ** argv, Proc_Info *proc_info_p, bool hide_window) { int ret = -1; #ifdef OS_LIKE_UNIX pid_t child; switch (child = fork()) { case 0: execvp(argv[0], (char * const *)argv); // If here, the exec failed. Child must die. exit(1); case -1: // failed if (proc_info_p != 0) { *proc_info_p = 0; } break; default: if (proc_info_p == 0) { // wait for child to complete if (waitpid(child, &ret, 0) < 0) { ret = -1; } } else { *proc_info_p = child; ret = 0; } break; } #else #ifdef OS_LIKE_WIN32 // Convert the argv array into a string with each argument quoted. // First calculate how much space is needed. int a; int length = 0; bool has_quote = false; // to optimize normal case for (a = 0; argv[a] != 0; a++) { length += strlen(argv[a]); // A quoted argv[0] won't work; just quote the other args. if (a > 0) { // Add space before the arg and quotes on each end. length += 3; const char * s; // If embedded quote, add space for escaping it for (s = argv[a]; *s != '\0'; s++) { if (*s == '"') { length++; has_quote = true; } } } } // Get space and fill in all the arguments, properly quoted. char command[length + 1]; (void) strcpy(command, argv[0]); char * dest = command + strlen(command); for (a = 1; argv[a] != 0; a++) { *dest++ = ' '; *dest++ = '"'; if (has_quote) { int i; for (i = 0; argv[a][i] != '\0'; i++) { if (argv[a][i] == '"') { *dest++ = '\\'; } *dest++ = argv[a][i]; } } else { (void) strcpy(dest, argv[a]); dest += strlen(dest); } *dest++ = '"'; } *dest = '\0'; // Fill in information for starting up the process PROCESS_INFORMATION process_info; STARTUPINFO startup_info; memset( &startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(startup_info); DWORD create_flags; // flags to control creation aspects if (hide_window) { startup_info.dwFlags = STARTF_USESHOWWINDOW; startup_info.wShowWindow = SW_HIDE; create_flags = CREATE_NO_WINDOW; } else { create_flags = 0; } // Run the process BOOL proc = CreateProcess(NULL, command, NULL, NULL, TRUE, create_flags, NULL, NULL, &startup_info, &process_info); if (proc) { // It was successfully created. if (proc_info_p == 0) { // wait for child to complete DWORD result = WaitForSingleObject( process_info.hProcess, INFINITE); switch (result) { case WAIT_FAILED: case WAIT_ABANDONED: case WAIT_TIMEOUT: ret = -1; break; default: GetExitCodeProcess(process_info.hProcess, &result); ret = (int) result; } } else { *proc_info_p = process_info; ret = 0; } } else { proc_info_p->hProcess = 0; proc_info_p->dwProcessId = 0; ret = -1; } #else fl_alert("Process execution only implemented\n" "for Linux (and similar) and Windows so far..."); ret = -1; #endif #endif return(ret); } // Kill the specified process, if it exists. // The description is used in error messages void Run::kill_process(const Proc_Info * const proc_info_p, const char * const description) { #ifdef OS_LIKE_UNIX int exitstatus; if (*proc_info_p == 0 || waitpid(*proc_info_p, &exitstatus, WNOHANG) == *proc_info_p) { // No process spawned or the one we had already died return; } // Not clear how hard to try to kill process. // SIGTERM should usually work, and is preferable if it works. // We wait a little while and try SIGKILL if it hasn't died yet. // We don't check for errors, because the // only errors that should be possible are bad signal (should be // impossible, since we hard-code SIGTERM), process doesn't exist // (already dead, so no need to kill it), or bad permission (we // spawned it ourself, so ought to have permission to kill it). (void) kill(*proc_info_p, SIGTERM); // Wait for up to 3 seconds for it to die int w; int ret; for (w = 0; w < 3; w++) { if ((ret = waitpid(*proc_info_p, &exitstatus, WNOHANG)) == *proc_info_p) { // It died. // *** Especially in the case of MIDI, there is a // chance the player has already written stuff out // to the device driver that it won't clear out when // it dies, so that if we try to start a new one, // the device will still be busy. But we have no way // to know how long to wait, if at all, and don't // want to always sleep a long time just to cover // the case of trying to play again while in the middle // of playing. return; } if (ret == -1 && errno == ECHILD) { // Child doesn't exist, so nothing to kill return; } sleep(1); } // Okay. Resort to SIGKILL and wait 1 more second to let it die. kill(*proc_info_p, SIGKILL); sleep(1); #endif #ifdef OS_LIKE_WIN32 if (proc_info_p->hProcess == 0 || WaitForSingleObject( proc_info_p->hProcess, 0) != WAIT_TIMEOUT) { // No process spawned or the one we had must have already died. return; } HANDLE handle; if ( (handle = OpenProcess(PROCESS_TERMINATE, FALSE, proc_info_p->dwProcessId)) == 0 || TerminateProcess(handle, 0) == 0) { // Warn user we were unable to kill the old child. DWORD format_retval; LPVOID error_string = 0; if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, 0, GetLastError(), LANG_NEUTRAL, (LPTSTR)&error_string, 0, 0)) { fl_alert("Unable to terminate %s process.\n%s", description, (char *) error_string); LocalFree((HLOCAL)error_string); } } if (handle != 0) { CloseHandle(handle); } #endif } #ifdef OS_LIKE_WIN32 // Return true if the given command is for Windows Media Player. bool Run::is_mplayer(const char * command) { int length = strlen(command); if (strcasecmp(command + length - 12, "wmplayer.exe") == 0 || strcasecmp(command + length - 11, "mplayer.exe") == 0 || strcasecmp(command + length - 8, "wmplayer") == 0 || strcasecmp(command + length - 7, "mplayer") == 0) { return(true); } return(false); } // Return true if the given command is for GSview. bool Run::is_gsview(const char * command) { int length = strlen(command); if (strcasecmp(command + length - 12, "gsview32.exe") == 0 || strcasecmp(command + length - 10, "gsview.exe") == 0 || strcasecmp(command + length - 8, "gsview32") == 0 || strcasecmp(command + length - 6, "gsview") == 0) { return(true); } return(false); } #endif // Called when user begins a new file, or when exiting, // to kill off child proceses that were spawned. // We start new processes for new file, // even if one already up for current file. // Not sure if that's what user wants, but... void Run::clean_up(void) { kill_process(&display_child, "display"); kill_process(&MIDI_child, "MIDI player"); } // Report whether we spawned a display child and think it is still alive. bool Run::has_display_child(void) { #ifdef OS_LIKE_WIN32 return(display_child.hProcess != 0); #else return(display_child != 0); #endif } // Report whether we spawned a MIDI child and think it is still alive. bool Run::has_MIDI_child(void) { #ifdef OS_LIKE_WIN32 return(MIDI_child.hProcess != 0); #else return(MIDI_child != 0); #endif } //------------ Class for displaying errors from Mup Error_report::Error_report(void) : Fl_Double_Window(Default_width, Default_height, "Error report") { text_p = new Fl_Text_Display(20, 20, w() - 40, h() - 90); resizable((Fl_Widget *) text_p); text_p->buffer( new Fl_Text_Buffer() ); // Set font/size and arrange to get notified of changes in them font_change_reg_p = new Font_change_registration( font_change_cb, (void *) this); ok_p = new Fl_Return_Button(w() / 2 - 40, h() - 50, 80, 30, "OK"); ok_p->callback(OK_cb, this); show(); // Arrange to clean up new-ed widgets in destructor. end(); } Error_report::~Error_report() { } // Load the error file (from -e of Mup) into window to show user. int Error_report::loadfile(const char * filename) { return text_p->buffer()->loadfile(filename); } // Callback for when user clicks OK after reading Mup error report CALL_BACK(Error_report, OK) { hide(); } // Callback for change in font/size void Error_report::font_change_cb(void * data, Fl_Font font, unsigned char size) { ((Error_report *)data)->font_change(font, size); } void Error_report::font_change(Fl_Font font, unsigned char size) { text_p->textfont(font); text_p->textsize(size); text_p->redisplay_range(0, text_p->buffer()->length()); }