1 /* Copyright (c) 2006 by Arkkra Enterprises */
2 /* All rights reserved */
4 // This file includes methods related to the "Run" menu on the main toolbar.
7 #include "Preferences.H"
11 #include <FL/fl_ask.H>
12 #include <FL/filename.H>
24 // Message for when Mup fails, but we can't figure out why.
25 const char * Unknown_Mup_failure = "Mup failed. Reason unknown.";
27 // Describe use of numbers passed to -x option
28 #define EXTRACT_NUM_DESCRIPTION "0 is used for pickup measure.\n" \
29 "Negative numbers are used to specify\n" \
30 "number of measures from the end.\n"
32 // Tooltip is the same for all macro entries
33 const char * macro_definition_tip =
34 "Your Mup input can use macros to vary characteristics\n"
35 "of the output for different runs.\n"
36 "Enter the name of a macro you want to define,\n"
37 "optionally followed by an equals sign and macro definition.\n"
38 "Macro names must consist of upper case letters,\n"
39 "numbers, and underscores, starting with an upper case letter.";
41 //------ Class for user to set parameters to pass to Mup
43 Run_parameters_dialog::Run_parameters_dialog(void)
44 : Fl_Double_Window(0, 0, 700, 350, "Mup Options")
46 enable_combine_p = new Fl_Check_Button(20, 20, 165, 30,
47 "Enable Auto Multirest");
48 enable_combine_p->tooltip("Set whether to automatically combine\n"
49 "consecutive measures of rests into a multirest.");
50 enable_combine_p->callback(combine_cb, this);
51 enable_combine_p->when(FL_WHEN_CHANGED);
53 rest_combine_p = new Positive_Int_Input(370, 20, 40, 30,
54 "Min Measures to Combine");
55 rest_combine_p->tooltip("Minimum number of consecutive measures\n"
56 "of rest that will be combined into a multirest.");
57 // Only becomes active if auto-multirest combine becomes active
58 rest_combine_p->deactivate();
61 first_page_p = new Positive_Int_Input(300, 60, 50, 30, "First Page's Page Number");
62 first_page_p->tooltip("Set the page number to use\n"
63 "for the first page of music output.");
66 pages_p = new Fl_Group(30, 100, 380, 70, "Pages to Display");
67 pages_p->box(FL_ENGRAVED_BOX);
68 pages_p->align(FL_ALIGN_TOP_LEFT | FL_ALIGN_INSIDE);
70 all_p = new Fl_Check_Button(170, 105, 55, 20, "All");
71 all_p->type(FL_RADIO_BUTTON);
72 all_p->tooltip("You can restrict to displaying only a subset\n"
73 "of the pages of music, but this shows all pages.");
74 all_p->callback(selected_pages_cb, this);
75 all_p->when(FL_WHEN_CHANGED);
77 odd_p = new Fl_Check_Button(245, 105, 60, 20, "Odd");
78 odd_p->type(FL_RADIO_BUTTON);
79 odd_p->tooltip("This restricts to displaying only\n"
80 "odd numbered pages. This is generally\n"
81 "most useful for printing to a\n"
82 "single-sided printer.");
83 odd_p->callback(selected_pages_cb, this);
84 odd_p->when(FL_WHEN_CHANGED);
86 even_p = new Fl_Check_Button(320, 105, 70, 20, "Even");
87 even_p->type(FL_RADIO_BUTTON);
88 even_p->tooltip("This restricts to displaying only\n"
89 "even numbered pages. This is generally\n"
90 "most useful for printing to a\n"
91 "single-sided printer.");
92 even_p->callback(selected_pages_cb, this);
93 even_p->when(FL_WHEN_CHANGED);
95 selected_p = new Fl_Check_Button(50, 135, 85, 20, "Selected:");
96 selected_p->tooltip("This restricts to displaying only\n"
97 "the pages you list in the blank to the right.");
98 selected_p->type(FL_RADIO_BUTTON);
99 selected_p->callback(selected_pages_cb, this);
100 selected_p->when(FL_WHEN_CHANGED);
102 page_list_p = new Fl_Input(150, 130, 230, 30, "");
103 page_list_p->tooltip("List the specific pages you want displayed.\n"
104 "You can list individual pages separated by commas,\n"
105 "and/or ranges of pages separated by dashes.\n"
106 "Pages may be listed more than once and in any order.");
107 page_list_p->deactivate();
112 staff_list_p = new Fl_Input(150, 180, 260, 30, "Staffs to Display/Play");
113 staff_list_p->tooltip("If you wish to display or play only a subset\n"
114 "of the staffs, or voices on a staff, list them here.\n"
115 "Staffs are specified by comma-separated numbers\n"
116 "or ranges, like 1,4 or 1-3,5-6. Staff numbers can be\n"
117 "followed by v1, v2, or v3 to limit to a single voice.\n");
118 saved_staff_list = 0;
121 extract_begin_p = new Int_Input(150, 220, 95, 30, "Extract Measures");
122 extract_begin_p->tooltip("If you wish to display or play only selected\n"
123 "measures, enter the starting measure here.\n"
124 EXTRACT_NUM_DESCRIPTION);
125 extract_begin_p->callback(extract_cb, this);
126 extract_begin_p->when(FL_WHEN_CHANGED);
127 extract_end_p = new Int_Input(315, 220, 95, 30, "through");
128 extract_end_p->tooltip("If you wish to display or play only selected\n"
129 "measures, enter the ending measure here.\n"
130 EXTRACT_NUM_DESCRIPTION);
131 extract_end_p->deactivate();
134 macros_group_p = new Fl_Group(430, 20, 250, 40 + MAX_MACROS * 40, "Macro Definitions");
135 macros_group_p->box(FL_ENGRAVED_BOX);
136 macros_group_p->align(FL_ALIGN_TOP | FL_ALIGN_INSIDE);
138 for (m = 0; m < MAX_MACROS; m++) {
140 macro_definitions_p[m] = new Fl_Input(450, 50 + m * 40, 210, 30, "");
141 macro_definitions_p[m]->tooltip(macro_definition_tip);
143 saved_macro_definitions[m] = 0;
145 macros_group_p->end();
147 save_p = new Fl_Return_Button(100, h() - 50, 80, 30, "Save");
148 save_p->callback(Save_cb, this);
149 save_p->when(FL_WHEN_RELEASE);
151 clear_form_p = new Fl_Button((w() - 150) / 2, h() - 50, 150, 30,
153 clear_form_p->callback(clear_form_cb, this);
154 clear_form_p->when(FL_WHEN_RELEASE);
156 cancel_p = new Fl_Button(w() - 180, h() - 50, 80, 30, "Cancel");
157 cancel_p->shortcut(FL_Escape);
158 cancel_p->callback(Cancel_cb, this);
159 cancel_p->when(FL_WHEN_RELEASE);
161 // Set everything to default values
164 // Arrange to clean up all the new-ed widgets in destructor.
167 // Arrange for window manager closes to do Cancel.
168 callback(Cancel_cb, this);
173 Run_parameters_dialog::~Run_parameters_dialog(void)
176 for (m = 0; m < MAX_MACROS; m++) {
177 if (saved_macro_definitions[m] != 0) {
178 delete saved_macro_definitions[m];
184 //---- Callback for when user changes enablement of rest combining,
185 // to gray or ungray the field for how many measures to combine.
187 CALL_BACK(Run_parameters_dialog, combine)
189 if (enable_combine_p->value()) {
190 rest_combine_p->activate();
193 rest_combine_p->value("");
194 rest_combine_p->deactivate();
199 //---- Callback for when user changes setting of start of measure extraction,
200 // to gray or ungray the field for ending measure of extraction.
202 CALL_BACK(Run_parameters_dialog, extract)
204 if (extract_begin_p->size() > 0) {
205 extract_end_p->activate();
208 extract_end_p->value("");
209 extract_end_p->deactivate();
213 //---- Callback for when user changes setting of page selection,
214 // to gray or ungray the field for listing the specific pages to display
216 CALL_BACK(Run_parameters_dialog, selected_pages)
218 page_list_p->value("");
219 if (selected_p->value() == 1) {
220 page_list_p->activate();
223 page_list_p->deactivate();
228 //----Callback for button for clearing the form
230 CALL_BACK(Run_parameters_dialog, clear_form)
232 // Set everything to default values
233 saved_enable_combine = false;
234 enable_combine_p->value(saved_enable_combine);
236 (void) sprintf(saved_combine_measures, "");
237 rest_combine_p->value(saved_combine_measures);
239 (void) sprintf(saved_first_page, "%d", MINFIRSTPAGE);
240 first_page_p->value(saved_first_page);
242 if (saved_page_list != 0) {
243 delete saved_page_list;
245 saved_page_list = new char[1];
246 saved_page_list[0] = '\0';
247 page_list_p->value(saved_page_list);
249 saved_pages = ALL_PAGES;
251 if (saved_staff_list != 0) {
252 delete saved_staff_list;
254 saved_staff_list = new char[1];
255 saved_staff_list[0] = '\0';
256 staff_list_p->value(saved_staff_list);
258 saved_extract_begin[0] = '\0';
259 extract_begin_p->value(saved_extract_begin);
260 saved_extract_end[0] = '\0';
261 extract_end_p->value(saved_extract_end);
264 for (m = 0; m < MAX_MACROS; m++) {
265 macro_definitions_p[m]->value("");
266 if (saved_macro_definitions[m] != 0) {
267 delete saved_macro_definitions[m];
268 saved_macro_definitions[m] = 0;
274 //---- callback for when user clicks "Save" on Set Options form
276 CALL_BACK(Run_parameters_dialog, Save)
278 // -c rest combine option
280 saved_enable_combine = enable_combine_p->value();
281 int num_meas = (int) atoi(rest_combine_p->value());
282 if (saved_enable_combine && (num_meas < MINRESTCOMBINE || num_meas > MAXRESTCOMBINE)) {
283 fl_alert("\"Min Measures to Combine\" must be between\n"
284 "%d and %d.", MINRESTCOMBINE, MAXRESTCOMBINE);
288 (void) strcpy(saved_combine_measures, rest_combine_p->value());
291 // -p first page option
292 int page_num = (int) atoi(first_page_p->value());
293 if (page_num < MINFIRSTPAGE || page_num > MAXFIRSTPAGE) {
294 fl_alert("\"First Page\" number must be between\n"
295 "%d and %d.", MINFIRSTPAGE, MAXFIRSTPAGE);
299 (void) strcpy(saved_first_page, first_page_p->value());
302 // We don't really fully parse and error check the -o argument,
303 // but make sure it at least is made up of only valid characters.
304 if (page_list_p->size() > 0) {
305 if (strspn(page_list_p->value(), "0123456789,- \t")
306 != page_list_p->size()) {
307 fl_alert("\"Pages to Display\" value is not valid.");
311 // Free existing, if any, and save new value.
312 if (saved_page_list != 0) {
313 delete saved_page_list;
315 saved_page_list = new char[page_list_p->size() + 1];
316 strcpy(saved_page_list, page_list_p->value());
319 else if (saved_page_list[0] != '\0') {
320 // Had a list before but not anymore.
321 // Null out the existing list.
322 delete saved_page_list;
323 saved_page_list = new char[1];
324 saved_page_list[0] = '\0';
326 if (all_p->value() == 1) {
327 saved_pages = ALL_PAGES;
329 if (odd_p->value() == 1) {
330 saved_pages = ODD_PAGES;
332 if (even_p->value() == 1) {
333 saved_pages = EVEN_PAGES;
335 if (selected_p->value() == 1) {
336 saved_pages = SELECTED_PAGES;
338 if (saved_pages == SELECTED_PAGES && page_list_p->size() == 0) {
339 fl_alert("You did not list which selected pages you want.");
343 // Similar for staff list (-s option)
344 if (staff_list_p->size() > 0) {
345 if (strspn(staff_list_p->value(), "0123456789,-v \t")
346 != staff_list_p->size()) {
347 fl_alert("\"Staffs to Display/Play\" is not valid");
351 if (saved_staff_list != 0) {
352 delete saved_staff_list;
354 saved_staff_list = new char[staff_list_p->size() + 1];
355 strcpy(saved_staff_list, staff_list_p->value());
358 else if (saved_staff_list[0] != '\0') {
359 delete saved_staff_list;
360 saved_staff_list = new char[1];
361 saved_staff_list[0] = '\0';
364 // We can't really error check -x option values.
365 // Widget will have already constrained to integer,
366 // and we have no way to know what range is valid,
367 // since we don't know how many measures the song has.
368 // However, to keep things simple, we have fixed-size array of 8 bytes
369 // for saving value, so can only allow up to 6 digits plus sign.
370 if (abs(atoi(extract_begin_p->value())) >= 1000000) {
371 fl_alert("\"Extract Measures\" start value is out of range.");
375 (void) strcpy(saved_extract_begin, extract_begin_p->value());
377 if (abs(atoi(extract_end_p->value())) >= 1000000) {
378 fl_alert("\"Extract Measures\" end value is out of range.");
382 (void) strcpy(saved_extract_end, extract_end_p->value());
389 for (m = 0; m < MAX_MACROS; m++) {
391 if ((macsize = macro_definitions_p[m]->size()) > 0) {
392 if (macro_error(macro_definitions_p[m]->value())) {
393 fl_alert("Macro %d has invalid format.", m + 1);
396 else if (saved_macro_definitions[m] == 0) {
397 // no macro before, but is one now
400 else if (strcmp(macro_definitions_p[m]->value(),
401 saved_macro_definitions[m]) != 0) {
402 // A different macro value than before
403 delete saved_macro_definitions[m];
407 saved_macro_definitions[m] = new char[macsize + 1];
408 (void) strcpy(saved_macro_definitions[m],
409 macro_definitions_p[m]->value());
412 else if (macsize == 0 && saved_macro_definitions[m] != 0) {
413 // Used to be a macro value, but not anymore
414 delete saved_macro_definitions[m];
415 saved_macro_definitions[m] = 0;
420 // If there were user errors, we leave the window up for user to
421 // correct the errors. User can, of course, cancel without correcting,
422 // in which case if they try to run, Mup will complain about the
430 //---- Callback for when user clicks "Cancel" on Set Options form
432 CALL_BACK(Run_parameters_dialog, Cancel)
434 // Put back all the previous values
435 enable_combine_p->value(saved_enable_combine);
436 rest_combine_p->value(saved_combine_measures);
437 first_page_p->value(saved_first_page);
439 // It seems setting a radio button via value(1) doesn't reset the
440 // others, so clear all, then set the one we want
444 selected_p->value(0);
445 switch (saved_pages) {
446 default: // default should be impossible
457 selected_p->value(1);
460 page_list_p->value(saved_page_list);
461 staff_list_p->value(saved_staff_list);
462 extract_begin_p->value(saved_extract_begin);
463 extract_end_p->value(saved_extract_end);
466 for (m = 0; m < MAX_MACROS; m++) {
467 macro_definitions_p[m]->value( saved_macro_definitions[m] == 0
468 ? "" : saved_macro_definitions[m]);
473 // Return true if check of macro finds an error.
476 Run_parameters_dialog::macro_error(const char * macro)
478 // First character must be an upper case letter.
479 if ( ! isupper(macro[0])) {
482 // Everything up to = or end of string, whichever comes first,
483 // must be upper case letter, digit, or underscore.
485 for (m_p = macro; *m_p != '\0' && *m_p != '='; m_p++) {
486 if ( ! isupper(*m_p) && ! isdigit(*m_p) && *m_p != '_') {
494 //--------class for Run menu -----------------------------------------------
501 display_child.hProcess = 0;
502 display_child.dwProcessId = 0;
503 MIDI_child.hProcess = 0;
504 MIDI_child.dwProcessId = 0;
513 if (parameters_p != 0) {
521 // Kill off any child processes
527 //------------callback for Display menu item
529 CALL_BACK(Run, Display)
531 Run_Mup(false, true);
535 //-----Callback for menu item to play MIDI file
543 //---------callback for menu item for writing PostScript output
545 CALL_BACK(Run, WritePostScript)
547 Run_Mup(false, false);
550 //---------callback for menu item for writing MIDI output
552 CALL_BACK(Run, WriteMIDI)
554 Run_Mup(true, false);
557 //---- callback for menu item to collect Mup command line parameters from user
559 CALL_BACK(Run, Options)
561 if (parameters_p == 0) {
562 parameters_p = new Run_parameters_dialog();
564 parameters_p->show();
568 //--------- This lets Run class know the file name and editor buffer,
569 // which it needs access to, so it can run Mup on its contents.
572 Run::set_file(File * file_info_p)
574 file_p = file_info_p;
578 // Run Mup commands with user's parameters on the current file
581 Run::Run_Mup(bool midi, bool show_or_play)
584 const char * mup_input = file_p->effective_filename();
585 // Make sure file has been written.
586 // The fiilename == 0 will be hit in the case where
587 // user did New From Template but made no changes.
588 if (file_p->unsaved_changes || file_p->filename == 0) {
590 (void) Preferences_p->get(Auto_save_preference, auto_save,
594 if (file_p->unsaved_changes || file_p->filename == 0) {
595 // User probably canceled a Save that got
596 // turned into a SaveAs,
597 // or maybe Save failed. If not properly
598 // saved, we shouldn't try to process it.
604 const char * extra_text;
605 // If there is a previous version of the file,
606 // allow user to select "No," which means use that
607 // previous version rather than writing out current.
608 if (access(mup_input, F_OK) == 0) {
610 extra_text = "(If you select \"No,\" "
611 "the most recently saved version "
612 "of the Mup input file will be used.)";
615 // No previous version
619 switch (file_p->save_changes_check(extra_text, hide_the_No)) {
620 default: // default case should be impossible
621 case Save_confirm_dialog::Cancel:
623 case Save_confirm_dialog::No:
625 case Save_confirm_dialog::Yes:
627 if (file_p->unsaved_changes
628 || file_p->filename == 0) {
629 // User probably canceled the Save,
630 // or maybe it failed. If not properly
631 // saved, we shouldn't try to process it.
637 // If was "Untitled" before, it is now saved under a good
638 // name, so get what that is.
639 mup_input = file_p->effective_filename();
642 // Get length of file name without the extension
643 int base_length = strlen(mup_input)
644 - strlen(fl_filename_ext(mup_input));
646 // Create output file name
647 char mup_output[base_length + (midi ? 5 : 4)];
648 strncpy(mup_output, mup_input, base_length);
649 strcpy(mup_output + base_length, (midi ? ".mid" : ".ps"));
651 // Create error file name
652 char mup_error[base_length + 5];
653 strncpy(mup_error, mup_input, base_length);
654 strcpy(mup_error + base_length, ".err");
656 // Get Mup command to use.
658 (void) Preferences_p->get(Mup_program_location, mup_command,
659 Default_Mup_program_location);
660 char full_location[FL_PATH_MAX];
661 if ( ! find_executable(mup_command, full_location)) {
662 fl_alert("Mup command not found.\n"
663 "Check Config > File Locations setting.");
667 if (parameters_p == 0) {
668 // User hasn't set any parameters, so we will use all
669 // defaults. Creating the instance lets us deference it
670 // below, so we don't have to care whether user ever
671 // went to the Set Options page or not.
672 parameters_p = new Run_parameters_dialog();
675 // Build up list of arguments.
676 // array slots needed for args:
677 // 1 for Mup command itself
679 // 2 for -f or -m and arg
685 // 1 for Mup input file name
686 // 2 for each -D and its macro definition arg
687 // 1 for null terminator
688 const char * command[17 + 2 * MAX_MACROS];
689 command[0] = full_location;
691 command[2] = mup_error;
692 command[3] = (midi ? "-m" : "-f");
693 command[4] = mup_output;
697 if (parameters_p->enable_combine_p->value() &&
698 parameters_p->rest_combine_p->size() > 0) {
699 command[arg_offset++] = "-c";
700 command[arg_offset++] = parameters_p->rest_combine_p->value();
704 if (parameters_p->first_page_p->size() > 0) {
705 command[arg_offset++] = "-p";
706 command[arg_offset++] = parameters_p->first_page_p->value();
710 switch (parameters_p->saved_pages) {
711 default: // default should be impossible
712 case Run_parameters_dialog::ALL_PAGES:
714 case Run_parameters_dialog::ODD_PAGES:
715 command[arg_offset++] = "-o";
716 command[arg_offset++] = "odd";
718 case Run_parameters_dialog::EVEN_PAGES:
719 command[arg_offset++] = "-o";
720 command[arg_offset++] = "even";
721 case Run_parameters_dialog::SELECTED_PAGES:
722 if (parameters_p->page_list_p->size() > 0) {
723 command[arg_offset++] = "-o";
724 command[arg_offset++] = parameters_p->page_list_p->value();
726 // Else they said selected, but then didn't list
727 // any particular pages. We treat like ALL_PAGES,
728 // since no pages is useless.
729 // We would have already given error message,
730 // but they must have ignored it.
735 if (parameters_p->staff_list_p->size() > 0) {
736 command[arg_offset++] = "-s";
737 command[arg_offset++] = parameters_p->staff_list_p->value();
741 char xoption[parameters_p->extract_begin_p->size()
742 + parameters_p->extract_end_p->size() + 2];
743 if (parameters_p->extract_begin_p->size() > 0) {
744 command[arg_offset++] = "-x";
745 (void) strcpy(xoption, parameters_p->extract_begin_p->value());
746 if (parameters_p->extract_end_p->size() > 0) {
747 (void) strcat(xoption, ",");
748 (void) strcat(xoption, parameters_p->extract_end_p->value());
750 command[arg_offset++] = xoption;
755 for (m = 0; m < MAX_MACROS; m++) {
756 if (parameters_p->saved_macro_definitions[m] != 0) {
757 command[arg_offset++] = "-D";
758 command[arg_offset++] = parameters_p->saved_macro_definitions[m];
762 // Mup input file name and null terminator
763 command[arg_offset++] = mup_input;
764 command[arg_offset++] = 0;
766 static bool set_mupquiet = false;
767 if ( ! set_mupquiet ) {
768 putenv("MUPQUIET=1");
772 // Look up the right (dis)player program to use.
773 // On Windows we need this even if we are only writing the file,
774 // not actually (dis)playing. To keep the code simpler,
775 // we just always look it up.
776 char * player_command;
778 (void) Preferences_p->get(MIDI_player_location,
780 Default_MIDI_player_location);
783 (void) Preferences_p->get(Viewer_location,
785 Default_viewer_location);
788 // Media player locks the file it plays, so if we had
789 // run it before, we have to make it release the lock
790 // before we run Mup. Also, if it was playing a different file
791 // before, it doesn't seem to want to play ours.
792 // So if the MIDI player of choice is the media player,
793 // we first try to make it close somewhat gracefully,
794 // and wait a second for that to complete.
795 // In any case we try to kill off any child MIDI players we
796 // know of. If the graceful close already worked,
797 // it should find the child already dead.
798 // If they are using some other MIDI player,
799 // we don't know what to do, so we'll just kill any child
800 // MIDI player we know spawned previously, if any.
801 // Would be nice to be able to do something less drastic than
802 // kill the process, but we're not sure what else would be effective.
804 if (is_mplayer(player_command)) {
806 if ((window_handle = FindWindow(0, "Windows Media Player")) != 0) {
807 SendMessage(window_handle, WM_CLOSE, 0, 0);
811 if (has_MIDI_child()) {
812 kill_process(&MIDI_child, "MIDI player");
815 // Somewhat similarly, we kill off any prior displayer, unless
816 // the displayer is GSview, which we know we can pass -e argument
817 // to make it reuse existing instance, if any.
818 else if ( ! is_gsview(player_command) && has_display_child()) {
819 kill_process(&display_child, "display");
824 int ret = execute_command(command, 0, true);
826 // Report the errors, if any.
828 if (stat(mup_error, &info) == 0) {
829 if (info.st_size > 0) {
831 report_p = new Error_report();
833 report_p->loadfile(mup_error);
839 // Exited with error, but left no error file.
840 // Must have died badly (core dumped, execvp failed, etc)
841 #if defined(WIFEXITED) && defined(WIFSIGNALED)
842 if (WIFEXITED(ret)) {
843 // Did exit(), so most likely exec failed
844 // due to bad path to Mup program,
845 // although we should have caught that above.
846 fl_alert("Mup exited with return code %d but no error output.\n"
847 "Check Config > File Locations setting.",
850 else if (WIFSIGNALED(ret)) {
851 // Probably core dump :-(
852 fl_alert("Mup exited due to signal %d.", WTERMSIG(ret));
854 fl_alert(Unknown_Mup_failure);
856 #else // WIF... macros not defined
859 // Look up the error reason to include in message.
861 LPVOID error_string = 0;
863 FORMAT_MESSAGE_ALLOCATE_BUFFER
864 | FORMAT_MESSAGE_FROM_SYSTEM
865 | FORMAT_MESSAGE_ARGUMENT_ARRAY,
867 LANG_NEUTRAL, (LPTSTR)&error_string,
869 fl_alert("Failed to execute Mup program:\n%s"
870 "Check settings in Config > File Locations.",
871 (char *) error_string);
872 LocalFree((HLOCAL)error_string);
875 fl_alert(Unknown_Mup_failure);
879 fl_alert(Unknown_Mup_failure);
883 fl_alert(Unknown_Mup_failure);
885 #endif // WIF... macros
889 // Something went wrong. We should not go on to
890 // display/play even if user had asked for that.
894 // We wrote the output file successfully.
895 // Hide any previous error window and
896 // check if we need to (dis)play the results.
900 if ( ! show_or_play ) {
901 // User just asked to generate a file, not (dis)play it.
902 fl_message("%s output file\n\n %s\n\n"
903 "has been successfully created.",
904 (midi ? "MIDI" : "PostScript"), mup_output);
909 if ( ! find_executable(player_command, full_location)) {
910 fl_alert("Unable to run %s command.\n"
911 "Check Config > File Locations setting.",
917 // For MIDI, we try to kill the previous player
918 // if any, since we'll probably need the same
919 // sound card. Note that for Windows, we did this
920 // even before running Mup, since the player may lock
921 // the file so Mup would be unable to write it.
923 kill_process(&MIDI_child, "MIDI player");
926 // If using gv as displayer and one already running on
927 // this file, it would be nice to get send it SIGHUP
928 // to make it re-read the file
929 // rather than starting a new one.
930 // But for other displayers, we don't know what signal
931 // might work, if any. And even with gv, if we send
932 // the signal, we have no way of knowing whether it
933 // actually worked. And there are lots of cases to
934 // consider. Suppose the displayer was gv,
935 // and they brought up an instance,
936 // then they changed to some other displayer,
937 // and then back to gv. Should we have killed off
938 // the first gv when bringing up the other viewer,
939 // of should we keep it up and reuse it after they
940 // change back? What if they kill the current
941 // instance just after we decided to re-use it?
942 // So just keep things simple for now. Always
943 // kill off any existing viewer we know of and
944 // start up a new one.
946 kill_process(&display_child, "display");
950 // Fill in the argv array for displayer/player
951 command[0] = full_location;
953 // Media player appears to ignore its argument
954 // if it isn't a full path name, including drive.
955 // So ensure we have everything.
956 char fullpath[FL_PATH_MAX];
957 if (mup_output[1] != ':' ) {
958 // We don't have complete path.
959 // Get current directory including drive.
960 if (getcwd(fullpath, sizeof(fullpath)) == 0) {
961 fl_alert("Unable to determine current folder.");
964 if (mup_output[0] != dir_separator()) {
966 (void) sprintf(fullpath + strlen(fullpath),
967 "%c%s", dir_separator(),
971 // Was full path except for drive.
972 (void) strcpy(fullpath + 2, mup_output);
976 // Can use existing path as is.
977 (void) strcpy(fullpath, mup_output);
980 if ( ! midi && is_gsview(player_command)) {
981 // For gsview, use -e to make it use
982 // existing instance if any.
984 command[2] = fullpath;
988 command[1] = fullpath;
993 command[1] = mup_output;
997 if (execute_command(command,
998 (midi ? &MIDI_child : &display_child)) != 0) {
999 fl_alert("Unable to run %s command.\n"
1000 "Check settings under Config > File Locations.",
1006 // Execute given command with the given argv.
1007 // If proc_info_p is zero, wait for the process to complete,
1008 // otherwise save information about the spawned process in what it points to,
1009 // so that the caller can keep track of it while it runs independently.
1010 // The hide_window parameter is only used for Windows and causes the
1011 // spawned process to be created with flags to not create a window,
1012 // This lets us use a console mode version of Mup, so traditional users
1013 // can continue to run Mup in a console without mupmate, but we can
1014 // run the same .exe without the annoyance of a console popping up.
1015 // Returns 0 on success, -1 on failure to create process,
1016 // or what the process returned if it had non-zero exit code,
1017 // and was waited for.
1020 Run::execute_command(const char ** argv, Proc_Info *proc_info_p, bool hide_window)
1026 switch (child = fork()) {
1028 execvp(argv[0], (char * const *)argv);
1029 // If here, the exec failed. Child must die.
1033 if (proc_info_p != 0) {
1038 if (proc_info_p == 0) {
1039 // wait for child to complete
1040 if (waitpid(child, &ret, 0) < 0) {
1045 *proc_info_p = child;
1052 #ifdef OS_LIKE_WIN32
1054 // Convert the argv array into a string with each argument quoted.
1055 // First calculate how much space is needed.
1058 bool has_quote = false; // to optimize normal case
1059 for (a = 0; argv[a] != 0; a++) {
1060 length += strlen(argv[a]);
1061 // A quoted argv[0] won't work; just quote the other args.
1063 // Add space before the arg and quotes on each end.
1066 // If embedded quote, add space for escaping it
1067 for (s = argv[a]; *s != '\0'; s++) {
1076 // Get space and fill in all the arguments, properly quoted.
1077 char command[length + 1];
1078 (void) strcpy(command, argv[0]);
1079 char * dest = command + strlen(command);
1080 for (a = 1; argv[a] != 0; a++) {
1085 for (i = 0; argv[a][i] != '\0'; i++) {
1086 if (argv[a][i] == '"') {
1089 *dest++ = argv[a][i];
1093 (void) strcpy(dest, argv[a]);
1094 dest += strlen(dest);
1100 // Fill in information for starting up the process
1101 PROCESS_INFORMATION process_info;
1102 STARTUPINFO startup_info;
1103 memset( &startup_info, 0, sizeof(startup_info));
1104 startup_info.cb = sizeof(startup_info);
1105 DWORD create_flags; // flags to control creation aspects
1107 startup_info.dwFlags = STARTF_USESHOWWINDOW;
1108 startup_info.wShowWindow = SW_HIDE;
1109 create_flags = CREATE_NO_WINDOW;
1116 BOOL proc = CreateProcess(NULL, command, NULL, NULL,
1117 TRUE, create_flags, NULL, NULL,
1118 &startup_info, &process_info);
1121 // It was successfully created.
1122 if (proc_info_p == 0) {
1123 // wait for child to complete
1124 DWORD result = WaitForSingleObject(
1125 process_info.hProcess, INFINITE);
1128 case WAIT_ABANDONED:
1133 GetExitCodeProcess(process_info.hProcess, &result);
1138 *proc_info_p = process_info;
1143 proc_info_p->hProcess = 0;
1144 proc_info_p->dwProcessId = 0;
1149 fl_alert("Process execution only implemented\n"
1150 "for Linux (and similar) and Windows so far...");
1159 // Kill the specified process, if it exists.
1160 // The description is used in error messages
1163 Run::kill_process(const Proc_Info * const proc_info_p, const char * const description)
1167 if (*proc_info_p == 0 || waitpid(*proc_info_p, &exitstatus, WNOHANG)
1169 // No process spawned or the one we had already died
1172 // Not clear how hard to try to kill process.
1173 // SIGTERM should usually work, and is preferable if it works.
1174 // We wait a little while and try SIGKILL if it hasn't died yet.
1175 // We don't check for errors, because the
1176 // only errors that should be possible are bad signal (should be
1177 // impossible, since we hard-code SIGTERM), process doesn't exist
1178 // (already dead, so no need to kill it), or bad permission (we
1179 // spawned it ourself, so ought to have permission to kill it).
1180 (void) kill(*proc_info_p, SIGTERM);
1181 // Wait for up to 3 seconds for it to die
1184 for (w = 0; w < 3; w++) {
1185 if ((ret = waitpid(*proc_info_p, &exitstatus, WNOHANG))
1188 // *** Especially in the case of MIDI, there is a
1189 // chance the player has already written stuff out
1190 // to the device driver that it won't clear out when
1191 // it dies, so that if we try to start a new one,
1192 // the device will still be busy. But we have no way
1193 // to know how long to wait, if at all, and don't
1194 // want to always sleep a long time just to cover
1195 // the case of trying to play again while in the middle
1199 if (ret == -1 && errno == ECHILD) {
1200 // Child doesn't exist, so nothing to kill
1205 // Okay. Resort to SIGKILL and wait 1 more second to let it die.
1206 kill(*proc_info_p, SIGKILL);
1209 #ifdef OS_LIKE_WIN32
1210 if (proc_info_p->hProcess == 0 || WaitForSingleObject(
1211 proc_info_p->hProcess, 0) != WAIT_TIMEOUT) {
1212 // No process spawned or the one we had must have already died.
1216 if ( (handle = OpenProcess(PROCESS_TERMINATE,
1217 FALSE, proc_info_p->dwProcessId)) == 0 ||
1218 TerminateProcess(handle, 0) == 0) {
1219 // Warn user we were unable to kill the old child.
1220 DWORD format_retval;
1221 LPVOID error_string = 0;
1222 if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER
1223 | FORMAT_MESSAGE_FROM_SYSTEM
1224 | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1226 LANG_NEUTRAL, (LPTSTR)&error_string,
1228 fl_alert("Unable to terminate %s process.\n%s",
1229 description, (char *) error_string);
1230 LocalFree((HLOCAL)error_string);
1234 CloseHandle(handle);
1240 #ifdef OS_LIKE_WIN32
1241 // Return true if the given command is for Windows Media Player.
1244 Run::is_mplayer(const char * command)
1246 int length = strlen(command);
1247 if (strcasecmp(command + length - 12, "wmplayer.exe") == 0 ||
1248 strcasecmp(command + length - 11, "mplayer.exe") == 0 ||
1249 strcasecmp(command + length - 8, "wmplayer") == 0 ||
1250 strcasecmp(command + length - 7, "mplayer") == 0) {
1257 // Return true if the given command is for GSview.
1260 Run::is_gsview(const char * command)
1262 int length = strlen(command);
1263 if (strcasecmp(command + length - 12, "gsview32.exe") == 0 ||
1264 strcasecmp(command + length - 10, "gsview.exe") == 0 ||
1265 strcasecmp(command + length - 8, "gsview32") == 0 ||
1266 strcasecmp(command + length - 6, "gsview") == 0) {
1274 // Called when user begins a new file, or when exiting,
1275 // to kill off child proceses that were spawned.
1276 // We start new processes for new file,
1277 // even if one already up for current file.
1278 // Not sure if that's what user wants, but...
1283 kill_process(&display_child, "display");
1284 kill_process(&MIDI_child, "MIDI player");
1288 // Report whether we spawned a display child and think it is still alive.
1291 Run::has_display_child(void)
1293 #ifdef OS_LIKE_WIN32
1294 return(display_child.hProcess != 0);
1296 return(display_child != 0);
1301 // Report whether we spawned a MIDI child and think it is still alive.
1304 Run::has_MIDI_child(void)
1306 #ifdef OS_LIKE_WIN32
1307 return(MIDI_child.hProcess != 0);
1309 return(MIDI_child != 0);
1314 //------------ Class for displaying errors from Mup
1316 Error_report::Error_report(void)
1317 : Fl_Double_Window(Default_width, Default_height, "Error report")
1319 text_p = new Fl_Text_Display(20, 20, w() - 40, h() - 90);
1320 resizable((Fl_Widget *) text_p);
1321 text_p->buffer( new Fl_Text_Buffer() );
1323 // Set font/size and arrange to get notified of changes in them
1324 font_change_reg_p = new Font_change_registration(
1325 font_change_cb, (void *) this);
1327 ok_p = new Fl_Return_Button(w() / 2 - 40, h() - 50, 80, 30, "OK");
1328 ok_p->callback(OK_cb, this);
1331 // Arrange to clean up new-ed widgets in destructor.
1335 Error_report::~Error_report()
1340 // Load the error file (from -e of Mup) into window to show user.
1343 Error_report::loadfile(const char * filename)
1345 return text_p->buffer()->loadfile(filename);
1349 // Callback for when user clicks OK after reading Mup error report
1351 CALL_BACK(Error_report, OK)
1357 // Callback for change in font/size
1360 Error_report::font_change_cb(void * data, Fl_Font font, unsigned char size)
1362 ((Error_report *)data)->font_change(font, size);
1366 Error_report::font_change(Fl_Font font, unsigned char size)
1368 text_p->textfont(font);
1369 text_p->textsize(size);
1370 text_p->redisplay_range(0, text_p->buffer()->length());