| 1 | /* Copyright (c) 2006 by Arkkra Enterprises */ |
| 2 | /* All rights reserved */ |
| 3 | |
| 4 | // This file includes methods related to the "Run" menu on the main toolbar. |
| 5 | |
| 6 | #include "Run.H" |
| 7 | #include "Preferences.H" |
| 8 | #include "globals.H" |
| 9 | #include "Config.H" |
| 10 | #include "defines.h" |
| 11 | #include <FL/fl_ask.H> |
| 12 | #include <FL/filename.H> |
| 13 | #include <string.h> |
| 14 | #include <stdlib.h> |
| 15 | #include <unistd.h> |
| 16 | #include <fcntl.h> |
| 17 | #include <ctype.h> |
| 18 | #include <sys/stat.h> |
| 19 | #ifdef OS_LIKE_UNIX |
| 20 | #include <sys/wait.h> |
| 21 | #include <errno.h> |
| 22 | #endif |
| 23 | |
| 24 | // Message for when Mup fails, but we can't figure out why. |
| 25 | const char * Unknown_Mup_failure = "Mup failed. Reason unknown."; |
| 26 | |
| 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" |
| 31 | |
| 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."; |
| 40 | |
| 41 | //------ Class for user to set parameters to pass to Mup |
| 42 | |
| 43 | Run_parameters_dialog::Run_parameters_dialog(void) |
| 44 | : Fl_Double_Window(0, 0, 700, 350, "Mup Options") |
| 45 | { |
| 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); |
| 52 | |
| 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(); |
| 59 | |
| 60 | // -p firstpage |
| 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."); |
| 64 | |
| 65 | // -o pagelist |
| 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); |
| 69 | |
| 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); |
| 76 | |
| 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); |
| 85 | |
| 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); |
| 94 | |
| 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); |
| 101 | |
| 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(); |
| 108 | saved_page_list = 0; |
| 109 | pages_p->end(); |
| 110 | |
| 111 | // -s stafflist |
| 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; |
| 119 | |
| 120 | // -x extract list |
| 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(); |
| 132 | |
| 133 | // Macros |
| 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); |
| 137 | int m; |
| 138 | for (m = 0; m < MAX_MACROS; m++) { |
| 139 | |
| 140 | macro_definitions_p[m] = new Fl_Input(450, 50 + m * 40, 210, 30, ""); |
| 141 | macro_definitions_p[m]->tooltip(macro_definition_tip); |
| 142 | |
| 143 | saved_macro_definitions[m] = 0; |
| 144 | } |
| 145 | macros_group_p->end(); |
| 146 | |
| 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); |
| 150 | |
| 151 | clear_form_p = new Fl_Button((w() - 150) / 2, h() - 50, 150, 30, |
| 152 | "Clear Options"); |
| 153 | clear_form_p->callback(clear_form_cb, this); |
| 154 | clear_form_p->when(FL_WHEN_RELEASE); |
| 155 | |
| 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); |
| 160 | |
| 161 | // Set everything to default values |
| 162 | clear_form(); |
| 163 | |
| 164 | // Arrange to clean up all the new-ed widgets in destructor. |
| 165 | end(); |
| 166 | |
| 167 | // Arrange for window manager closes to do Cancel. |
| 168 | callback(Cancel_cb, this); |
| 169 | when(FL_WHEN_NEVER); |
| 170 | } |
| 171 | |
| 172 | |
| 173 | Run_parameters_dialog::~Run_parameters_dialog(void) |
| 174 | { |
| 175 | int m; |
| 176 | for (m = 0; m < MAX_MACROS; m++) { |
| 177 | if (saved_macro_definitions[m] != 0) { |
| 178 | delete saved_macro_definitions[m]; |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | |
| 184 | //---- Callback for when user changes enablement of rest combining, |
| 185 | // to gray or ungray the field for how many measures to combine. |
| 186 | |
| 187 | CALL_BACK(Run_parameters_dialog, combine) |
| 188 | { |
| 189 | if (enable_combine_p->value()) { |
| 190 | rest_combine_p->activate(); |
| 191 | } |
| 192 | else { |
| 193 | rest_combine_p->value(""); |
| 194 | rest_combine_p->deactivate(); |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | |
| 199 | //---- Callback for when user changes setting of start of measure extraction, |
| 200 | // to gray or ungray the field for ending measure of extraction. |
| 201 | |
| 202 | CALL_BACK(Run_parameters_dialog, extract) |
| 203 | { |
| 204 | if (extract_begin_p->size() > 0) { |
| 205 | extract_end_p->activate(); |
| 206 | } |
| 207 | else { |
| 208 | extract_end_p->value(""); |
| 209 | extract_end_p->deactivate(); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | //---- Callback for when user changes setting of page selection, |
| 214 | // to gray or ungray the field for listing the specific pages to display |
| 215 | |
| 216 | CALL_BACK(Run_parameters_dialog, selected_pages) |
| 217 | { |
| 218 | page_list_p->value(""); |
| 219 | if (selected_p->value() == 1) { |
| 220 | page_list_p->activate(); |
| 221 | } |
| 222 | else { |
| 223 | page_list_p->deactivate(); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | |
| 228 | //----Callback for button for clearing the form |
| 229 | |
| 230 | CALL_BACK(Run_parameters_dialog, clear_form) |
| 231 | { |
| 232 | // Set everything to default values |
| 233 | saved_enable_combine = false; |
| 234 | enable_combine_p->value(saved_enable_combine); |
| 235 | |
| 236 | (void) sprintf(saved_combine_measures, ""); |
| 237 | rest_combine_p->value(saved_combine_measures); |
| 238 | |
| 239 | (void) sprintf(saved_first_page, "%d", MINFIRSTPAGE); |
| 240 | first_page_p->value(saved_first_page); |
| 241 | |
| 242 | if (saved_page_list != 0) { |
| 243 | delete saved_page_list; |
| 244 | } |
| 245 | saved_page_list = new char[1]; |
| 246 | saved_page_list[0] = '\0'; |
| 247 | page_list_p->value(saved_page_list); |
| 248 | all_p->value(1); |
| 249 | saved_pages = ALL_PAGES; |
| 250 | |
| 251 | if (saved_staff_list != 0) { |
| 252 | delete saved_staff_list; |
| 253 | } |
| 254 | saved_staff_list = new char[1]; |
| 255 | saved_staff_list[0] = '\0'; |
| 256 | staff_list_p->value(saved_staff_list); |
| 257 | |
| 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); |
| 262 | |
| 263 | int m; |
| 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; |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | |
| 274 | //---- callback for when user clicks "Save" on Set Options form |
| 275 | |
| 276 | CALL_BACK(Run_parameters_dialog, Save) |
| 277 | { |
| 278 | // -c rest combine option |
| 279 | bool error = false; |
| 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); |
| 285 | error = true; |
| 286 | } |
| 287 | else { |
| 288 | (void) strcpy(saved_combine_measures, rest_combine_p->value()); |
| 289 | } |
| 290 | |
| 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); |
| 296 | error = true; |
| 297 | } |
| 298 | else { |
| 299 | (void) strcpy(saved_first_page, first_page_p->value()); |
| 300 | } |
| 301 | |
| 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."); |
| 308 | error = true; |
| 309 | } |
| 310 | else { |
| 311 | // Free existing, if any, and save new value. |
| 312 | if (saved_page_list != 0) { |
| 313 | delete saved_page_list; |
| 314 | } |
| 315 | saved_page_list = new char[page_list_p->size() + 1]; |
| 316 | strcpy(saved_page_list, page_list_p->value()); |
| 317 | } |
| 318 | } |
| 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'; |
| 325 | } |
| 326 | if (all_p->value() == 1) { |
| 327 | saved_pages = ALL_PAGES; |
| 328 | } |
| 329 | if (odd_p->value() == 1) { |
| 330 | saved_pages = ODD_PAGES; |
| 331 | } |
| 332 | if (even_p->value() == 1) { |
| 333 | saved_pages = EVEN_PAGES; |
| 334 | } |
| 335 | if (selected_p->value() == 1) { |
| 336 | saved_pages = SELECTED_PAGES; |
| 337 | } |
| 338 | if (saved_pages == SELECTED_PAGES && page_list_p->size() == 0) { |
| 339 | fl_alert("You did not list which selected pages you want."); |
| 340 | error = true; |
| 341 | } |
| 342 | |
| 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"); |
| 348 | error = true; |
| 349 | } |
| 350 | else { |
| 351 | if (saved_staff_list != 0) { |
| 352 | delete saved_staff_list; |
| 353 | } |
| 354 | saved_staff_list = new char[staff_list_p->size() + 1]; |
| 355 | strcpy(saved_staff_list, staff_list_p->value()); |
| 356 | } |
| 357 | } |
| 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'; |
| 362 | } |
| 363 | |
| 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."); |
| 372 | error = true; |
| 373 | } |
| 374 | else { |
| 375 | (void) strcpy(saved_extract_begin, extract_begin_p->value()); |
| 376 | } |
| 377 | if (abs(atoi(extract_end_p->value())) >= 1000000) { |
| 378 | fl_alert("\"Extract Measures\" end value is out of range."); |
| 379 | error = true; |
| 380 | } |
| 381 | else { |
| 382 | (void) strcpy(saved_extract_end, extract_end_p->value()); |
| 383 | } |
| 384 | |
| 385 | // Macros |
| 386 | int m; |
| 387 | int macsize; |
| 388 | bool changed; |
| 389 | for (m = 0; m < MAX_MACROS; m++) { |
| 390 | changed = false; |
| 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); |
| 394 | error = true; |
| 395 | } |
| 396 | else if (saved_macro_definitions[m] == 0) { |
| 397 | // no macro before, but is one now |
| 398 | changed = true; |
| 399 | } |
| 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]; |
| 404 | changed = true; |
| 405 | } |
| 406 | if (changed) { |
| 407 | saved_macro_definitions[m] = new char[macsize + 1]; |
| 408 | (void) strcpy(saved_macro_definitions[m], |
| 409 | macro_definitions_p[m]->value()); |
| 410 | } |
| 411 | } |
| 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; |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | |
| 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 |
| 423 | // bad arguments. |
| 424 | if ( ! error ) { |
| 425 | hide(); |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | |
| 430 | //---- Callback for when user clicks "Cancel" on Set Options form |
| 431 | |
| 432 | CALL_BACK(Run_parameters_dialog, Cancel) |
| 433 | { |
| 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); |
| 438 | |
| 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 |
| 441 | all_p->value(0); |
| 442 | odd_p->value(0); |
| 443 | even_p->value(0); |
| 444 | selected_p->value(0); |
| 445 | switch (saved_pages) { |
| 446 | default: // default should be impossible |
| 447 | case ALL_PAGES: |
| 448 | all_p->value(1); |
| 449 | break; |
| 450 | case ODD_PAGES: |
| 451 | odd_p->value(1); |
| 452 | break; |
| 453 | case EVEN_PAGES: |
| 454 | even_p->value(1); |
| 455 | break; |
| 456 | case SELECTED_PAGES: |
| 457 | selected_p->value(1); |
| 458 | break; |
| 459 | } |
| 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); |
| 464 | |
| 465 | int m; |
| 466 | for (m = 0; m < MAX_MACROS; m++) { |
| 467 | macro_definitions_p[m]->value( saved_macro_definitions[m] == 0 |
| 468 | ? "" : saved_macro_definitions[m]); |
| 469 | } |
| 470 | hide(); |
| 471 | } |
| 472 | |
| 473 | // Return true if check of macro finds an error. |
| 474 | |
| 475 | bool |
| 476 | Run_parameters_dialog::macro_error(const char * macro) |
| 477 | { |
| 478 | // First character must be an upper case letter. |
| 479 | if ( ! isupper(macro[0])) { |
| 480 | return(true); |
| 481 | } |
| 482 | // Everything up to = or end of string, whichever comes first, |
| 483 | // must be upper case letter, digit, or underscore. |
| 484 | const char * m_p; |
| 485 | for (m_p = macro; *m_p != '\0' && *m_p != '='; m_p++) { |
| 486 | if ( ! isupper(*m_p) && ! isdigit(*m_p) && *m_p != '_') { |
| 487 | return(true); |
| 488 | } |
| 489 | } |
| 490 | return(false); |
| 491 | } |
| 492 | |
| 493 | |
| 494 | //--------class for Run menu ----------------------------------------------- |
| 495 | |
| 496 | Run::Run() |
| 497 | { |
| 498 | parameters_p = 0; |
| 499 | report_p = 0; |
| 500 | #ifdef OS_LIKE_WIN32 |
| 501 | display_child.hProcess = 0; |
| 502 | display_child.dwProcessId = 0; |
| 503 | MIDI_child.hProcess = 0; |
| 504 | MIDI_child.dwProcessId = 0; |
| 505 | #else |
| 506 | display_child = 0; |
| 507 | MIDI_child = 0; |
| 508 | #endif |
| 509 | } |
| 510 | |
| 511 | Run::~Run() |
| 512 | { |
| 513 | if (parameters_p != 0) { |
| 514 | delete parameters_p; |
| 515 | parameters_p = 0; |
| 516 | } |
| 517 | if (report_p != 0) { |
| 518 | delete report_p; |
| 519 | report_p = 0; |
| 520 | } |
| 521 | // Kill off any child processes |
| 522 | clean_up(); |
| 523 | } |
| 524 | |
| 525 | |
| 526 | |
| 527 | //------------callback for Display menu item |
| 528 | |
| 529 | CALL_BACK(Run, Display) |
| 530 | { |
| 531 | Run_Mup(false, true); |
| 532 | } |
| 533 | |
| 534 | |
| 535 | //-----Callback for menu item to play MIDI file |
| 536 | |
| 537 | CALL_BACK(Run, Play) |
| 538 | { |
| 539 | Run_Mup(true, true); |
| 540 | } |
| 541 | |
| 542 | |
| 543 | //---------callback for menu item for writing PostScript output |
| 544 | |
| 545 | CALL_BACK(Run, WritePostScript) |
| 546 | { |
| 547 | Run_Mup(false, false); |
| 548 | } |
| 549 | |
| 550 | //---------callback for menu item for writing MIDI output |
| 551 | |
| 552 | CALL_BACK(Run, WriteMIDI) |
| 553 | { |
| 554 | Run_Mup(true, false); |
| 555 | } |
| 556 | |
| 557 | //---- callback for menu item to collect Mup command line parameters from user |
| 558 | |
| 559 | CALL_BACK(Run, Options) |
| 560 | { |
| 561 | if (parameters_p == 0) { |
| 562 | parameters_p = new Run_parameters_dialog(); |
| 563 | } |
| 564 | parameters_p->show(); |
| 565 | } |
| 566 | |
| 567 | |
| 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. |
| 570 | |
| 571 | void |
| 572 | Run::set_file(File * file_info_p) |
| 573 | { |
| 574 | file_p = file_info_p; |
| 575 | } |
| 576 | |
| 577 | |
| 578 | // Run Mup commands with user's parameters on the current file |
| 579 | |
| 580 | void |
| 581 | Run::Run_Mup(bool midi, bool show_or_play) |
| 582 | { |
| 583 | |
| 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) { |
| 589 | int auto_save; |
| 590 | (void) Preferences_p->get(Auto_save_preference, auto_save, |
| 591 | Default_auto_save); |
| 592 | if (auto_save) { |
| 593 | file_p->Save(false); |
| 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. |
| 599 | return; |
| 600 | } |
| 601 | } |
| 602 | else { |
| 603 | bool hide_the_No; |
| 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) { |
| 609 | hide_the_No = false; |
| 610 | extra_text = "(If you select \"No,\" " |
| 611 | "the most recently saved version " |
| 612 | "of the Mup input file will be used.)"; |
| 613 | } |
| 614 | else { |
| 615 | // No previous version |
| 616 | hide_the_No = true; |
| 617 | extra_text = ""; |
| 618 | } |
| 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: |
| 622 | return; |
| 623 | case Save_confirm_dialog::No: |
| 624 | break; |
| 625 | case Save_confirm_dialog::Yes: |
| 626 | file_p->Save(false); |
| 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. |
| 632 | return; |
| 633 | } |
| 634 | break; |
| 635 | } |
| 636 | } |
| 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(); |
| 640 | } |
| 641 | |
| 642 | // Get length of file name without the extension |
| 643 | int base_length = strlen(mup_input) |
| 644 | - strlen(fl_filename_ext(mup_input)); |
| 645 | |
| 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")); |
| 650 | |
| 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"); |
| 655 | |
| 656 | // Get Mup command to use. |
| 657 | char * mup_command; |
| 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."); |
| 664 | return; |
| 665 | } |
| 666 | |
| 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(); |
| 673 | } |
| 674 | |
| 675 | // Build up list of arguments. |
| 676 | // array slots needed for args: |
| 677 | // 1 for Mup command itself |
| 678 | // 2 for -e and arg |
| 679 | // 2 for -f or -m and arg |
| 680 | // 2 for -c and arg |
| 681 | // 2 for -p and arg |
| 682 | // 2 for -o and arg |
| 683 | // 2 for -s and arg |
| 684 | // 2 for -x 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; |
| 690 | command[1] = "-e"; |
| 691 | command[2] = mup_error; |
| 692 | command[3] = (midi ? "-m" : "-f"); |
| 693 | command[4] = mup_output; |
| 694 | int arg_offset = 5; |
| 695 | |
| 696 | // rest combine |
| 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(); |
| 701 | } |
| 702 | |
| 703 | // first page |
| 704 | if (parameters_p->first_page_p->size() > 0) { |
| 705 | command[arg_offset++] = "-p"; |
| 706 | command[arg_offset++] = parameters_p->first_page_p->value(); |
| 707 | } |
| 708 | |
| 709 | // page list |
| 710 | switch (parameters_p->saved_pages) { |
| 711 | default: // default should be impossible |
| 712 | case Run_parameters_dialog::ALL_PAGES: |
| 713 | break; |
| 714 | case Run_parameters_dialog::ODD_PAGES: |
| 715 | command[arg_offset++] = "-o"; |
| 716 | command[arg_offset++] = "odd"; |
| 717 | break; |
| 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(); |
| 725 | } |
| 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. |
| 731 | break; |
| 732 | } |
| 733 | |
| 734 | // staff list |
| 735 | if (parameters_p->staff_list_p->size() > 0) { |
| 736 | command[arg_offset++] = "-s"; |
| 737 | command[arg_offset++] = parameters_p->staff_list_p->value(); |
| 738 | } |
| 739 | |
| 740 | // extract list |
| 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()); |
| 749 | } |
| 750 | command[arg_offset++] = xoption; |
| 751 | } |
| 752 | |
| 753 | // -D options |
| 754 | int m; |
| 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]; |
| 759 | } |
| 760 | } |
| 761 | |
| 762 | // Mup input file name and null terminator |
| 763 | command[arg_offset++] = mup_input; |
| 764 | command[arg_offset++] = 0; |
| 765 | |
| 766 | static bool set_mupquiet = false; |
| 767 | if ( ! set_mupquiet ) { |
| 768 | putenv("MUPQUIET=1"); |
| 769 | set_mupquiet = true; |
| 770 | } |
| 771 | |
| 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; |
| 777 | if (midi) { |
| 778 | (void) Preferences_p->get(MIDI_player_location, |
| 779 | player_command, |
| 780 | Default_MIDI_player_location); |
| 781 | } |
| 782 | else { |
| 783 | (void) Preferences_p->get(Viewer_location, |
| 784 | player_command, |
| 785 | Default_viewer_location); |
| 786 | } |
| 787 | #ifdef OS_LIKE_WIN32 |
| 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. |
| 803 | if (midi) { |
| 804 | if (is_mplayer(player_command)) { |
| 805 | HWND window_handle; |
| 806 | if ((window_handle = FindWindow(0, "Windows Media Player")) != 0) { |
| 807 | SendMessage(window_handle, WM_CLOSE, 0, 0); |
| 808 | } |
| 809 | _sleep(1000); |
| 810 | } |
| 811 | if (has_MIDI_child()) { |
| 812 | kill_process(&MIDI_child, "MIDI player"); |
| 813 | } |
| 814 | } |
| 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"); |
| 820 | } |
| 821 | #endif |
| 822 | |
| 823 | // Run the command. |
| 824 | int ret = execute_command(command, 0, true); |
| 825 | |
| 826 | // Report the errors, if any. |
| 827 | struct stat info; |
| 828 | if (stat(mup_error, &info) == 0) { |
| 829 | if (info.st_size > 0) { |
| 830 | if (report_p == 0) { |
| 831 | report_p = new Error_report(); |
| 832 | } |
| 833 | report_p->loadfile(mup_error); |
| 834 | report_p->show(); |
| 835 | } |
| 836 | unlink(mup_error); |
| 837 | } |
| 838 | else if (ret != 0) { |
| 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.", |
| 848 | WEXITSTATUS(ret)); |
| 849 | } |
| 850 | else if (WIFSIGNALED(ret)) { |
| 851 | // Probably core dump :-( |
| 852 | fl_alert("Mup exited due to signal %d.", WTERMSIG(ret)); |
| 853 | } else { |
| 854 | fl_alert(Unknown_Mup_failure); |
| 855 | } |
| 856 | #else // WIF... macros not defined |
| 857 | if (ret == -1) { |
| 858 | #ifdef OS_LIKE_WIN32 |
| 859 | // Look up the error reason to include in message. |
| 860 | DWORD format_retval; |
| 861 | LPVOID error_string = 0; |
| 862 | if (FormatMessage( |
| 863 | FORMAT_MESSAGE_ALLOCATE_BUFFER |
| 864 | | FORMAT_MESSAGE_FROM_SYSTEM |
| 865 | | FORMAT_MESSAGE_ARGUMENT_ARRAY, |
| 866 | 0, GetLastError(), |
| 867 | LANG_NEUTRAL, (LPTSTR)&error_string, |
| 868 | 0, 0)) { |
| 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); |
| 873 | } |
| 874 | else { |
| 875 | fl_alert(Unknown_Mup_failure); |
| 876 | } |
| 877 | } |
| 878 | #else // not Windows |
| 879 | fl_alert(Unknown_Mup_failure); |
| 880 | } |
| 881 | #endif |
| 882 | else { |
| 883 | fl_alert(Unknown_Mup_failure); |
| 884 | } |
| 885 | #endif // WIF... macros |
| 886 | } |
| 887 | |
| 888 | if (ret != 0) { |
| 889 | // Something went wrong. We should not go on to |
| 890 | // display/play even if user had asked for that. |
| 891 | return; |
| 892 | } |
| 893 | |
| 894 | // We wrote the output file successfully. |
| 895 | // Hide any previous error window and |
| 896 | // check if we need to (dis)play the results. |
| 897 | if (report_p != 0) { |
| 898 | report_p->hide(); |
| 899 | } |
| 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); |
| 905 | return; |
| 906 | } |
| 907 | |
| 908 | |
| 909 | if ( ! find_executable(player_command, full_location)) { |
| 910 | fl_alert("Unable to run %s command.\n" |
| 911 | "Check Config > File Locations setting.", |
| 912 | player_command); |
| 913 | return; |
| 914 | } |
| 915 | |
| 916 | #ifdef OS_LIKE_UNIX |
| 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. |
| 922 | if (midi) { |
| 923 | kill_process(&MIDI_child, "MIDI player"); |
| 924 | } |
| 925 | |
| 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. |
| 945 | else { // displaying |
| 946 | kill_process(&display_child, "display"); |
| 947 | } |
| 948 | #endif |
| 949 | |
| 950 | // Fill in the argv array for displayer/player |
| 951 | command[0] = full_location; |
| 952 | #ifdef OS_LIKE_WIN32 |
| 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."); |
| 962 | return; |
| 963 | } |
| 964 | if (mup_output[0] != dir_separator()) { |
| 965 | // Relative path |
| 966 | (void) sprintf(fullpath + strlen(fullpath), |
| 967 | "%c%s", dir_separator(), |
| 968 | mup_output); |
| 969 | } |
| 970 | else { |
| 971 | // Was full path except for drive. |
| 972 | (void) strcpy(fullpath + 2, mup_output); |
| 973 | } |
| 974 | } |
| 975 | else { |
| 976 | // Can use existing path as is. |
| 977 | (void) strcpy(fullpath, mup_output); |
| 978 | } |
| 979 | |
| 980 | if ( ! midi && is_gsview(player_command)) { |
| 981 | // For gsview, use -e to make it use |
| 982 | // existing instance if any. |
| 983 | command[1] = "-e"; |
| 984 | command[2] = fullpath; |
| 985 | command[3] = '\0'; |
| 986 | } |
| 987 | else { |
| 988 | command[1] = fullpath; |
| 989 | command[2] = '\0'; |
| 990 | |
| 991 | } |
| 992 | #else |
| 993 | command[1] = mup_output; |
| 994 | command[2] = '\0'; |
| 995 | #endif |
| 996 | |
| 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.", |
| 1001 | command[0]); |
| 1002 | } |
| 1003 | } |
| 1004 | |
| 1005 | |
| 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. |
| 1018 | |
| 1019 | int |
| 1020 | Run::execute_command(const char ** argv, Proc_Info *proc_info_p, bool hide_window) |
| 1021 | { |
| 1022 | int ret = -1; |
| 1023 | |
| 1024 | #ifdef OS_LIKE_UNIX |
| 1025 | pid_t child; |
| 1026 | switch (child = fork()) { |
| 1027 | case 0: |
| 1028 | execvp(argv[0], (char * const *)argv); |
| 1029 | // If here, the exec failed. Child must die. |
| 1030 | exit(1); |
| 1031 | case -1: |
| 1032 | // failed |
| 1033 | if (proc_info_p != 0) { |
| 1034 | *proc_info_p = 0; |
| 1035 | } |
| 1036 | break; |
| 1037 | default: |
| 1038 | if (proc_info_p == 0) { |
| 1039 | // wait for child to complete |
| 1040 | if (waitpid(child, &ret, 0) < 0) { |
| 1041 | ret = -1; |
| 1042 | } |
| 1043 | } |
| 1044 | else { |
| 1045 | *proc_info_p = child; |
| 1046 | ret = 0; |
| 1047 | } |
| 1048 | break; |
| 1049 | } |
| 1050 | #else |
| 1051 | |
| 1052 | #ifdef OS_LIKE_WIN32 |
| 1053 | |
| 1054 | // Convert the argv array into a string with each argument quoted. |
| 1055 | // First calculate how much space is needed. |
| 1056 | int a; |
| 1057 | int length = 0; |
| 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. |
| 1062 | if (a > 0) { |
| 1063 | // Add space before the arg and quotes on each end. |
| 1064 | length += 3; |
| 1065 | const char * s; |
| 1066 | // If embedded quote, add space for escaping it |
| 1067 | for (s = argv[a]; *s != '\0'; s++) { |
| 1068 | if (*s == '"') { |
| 1069 | length++; |
| 1070 | has_quote = true; |
| 1071 | } |
| 1072 | } |
| 1073 | } |
| 1074 | } |
| 1075 | |
| 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++) { |
| 1081 | *dest++ = ' '; |
| 1082 | *dest++ = '"'; |
| 1083 | if (has_quote) { |
| 1084 | int i; |
| 1085 | for (i = 0; argv[a][i] != '\0'; i++) { |
| 1086 | if (argv[a][i] == '"') { |
| 1087 | *dest++ = '\\'; |
| 1088 | } |
| 1089 | *dest++ = argv[a][i]; |
| 1090 | } |
| 1091 | } |
| 1092 | else { |
| 1093 | (void) strcpy(dest, argv[a]); |
| 1094 | dest += strlen(dest); |
| 1095 | } |
| 1096 | *dest++ = '"'; |
| 1097 | } |
| 1098 | *dest = '\0'; |
| 1099 | |
| 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 |
| 1106 | if (hide_window) { |
| 1107 | startup_info.dwFlags = STARTF_USESHOWWINDOW; |
| 1108 | startup_info.wShowWindow = SW_HIDE; |
| 1109 | create_flags = CREATE_NO_WINDOW; |
| 1110 | } |
| 1111 | else { |
| 1112 | create_flags = 0; |
| 1113 | } |
| 1114 | |
| 1115 | // Run the process |
| 1116 | BOOL proc = CreateProcess(NULL, command, NULL, NULL, |
| 1117 | TRUE, create_flags, NULL, NULL, |
| 1118 | &startup_info, &process_info); |
| 1119 | |
| 1120 | if (proc) { |
| 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); |
| 1126 | switch (result) { |
| 1127 | case WAIT_FAILED: |
| 1128 | case WAIT_ABANDONED: |
| 1129 | case WAIT_TIMEOUT: |
| 1130 | ret = -1; |
| 1131 | break; |
| 1132 | default: |
| 1133 | GetExitCodeProcess(process_info.hProcess, &result); |
| 1134 | ret = (int) result; |
| 1135 | } |
| 1136 | } |
| 1137 | else { |
| 1138 | *proc_info_p = process_info; |
| 1139 | ret = 0; |
| 1140 | } |
| 1141 | } |
| 1142 | else { |
| 1143 | proc_info_p->hProcess = 0; |
| 1144 | proc_info_p->dwProcessId = 0; |
| 1145 | ret = -1; |
| 1146 | } |
| 1147 | |
| 1148 | #else |
| 1149 | fl_alert("Process execution only implemented\n" |
| 1150 | "for Linux (and similar) and Windows so far..."); |
| 1151 | ret = -1; |
| 1152 | #endif |
| 1153 | |
| 1154 | #endif |
| 1155 | return(ret); |
| 1156 | } |
| 1157 | |
| 1158 | |
| 1159 | // Kill the specified process, if it exists. |
| 1160 | // The description is used in error messages |
| 1161 | |
| 1162 | void |
| 1163 | Run::kill_process(const Proc_Info * const proc_info_p, const char * const description) |
| 1164 | { |
| 1165 | #ifdef OS_LIKE_UNIX |
| 1166 | int exitstatus; |
| 1167 | if (*proc_info_p == 0 || waitpid(*proc_info_p, &exitstatus, WNOHANG) |
| 1168 | == *proc_info_p) { |
| 1169 | // No process spawned or the one we had already died |
| 1170 | return; |
| 1171 | } |
| 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 |
| 1182 | int w; |
| 1183 | int ret; |
| 1184 | for (w = 0; w < 3; w++) { |
| 1185 | if ((ret = waitpid(*proc_info_p, &exitstatus, WNOHANG)) |
| 1186 | == *proc_info_p) { |
| 1187 | // It died. |
| 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 |
| 1196 | // of playing. |
| 1197 | return; |
| 1198 | } |
| 1199 | if (ret == -1 && errno == ECHILD) { |
| 1200 | // Child doesn't exist, so nothing to kill |
| 1201 | return; |
| 1202 | } |
| 1203 | sleep(1); |
| 1204 | } |
| 1205 | // Okay. Resort to SIGKILL and wait 1 more second to let it die. |
| 1206 | kill(*proc_info_p, SIGKILL); |
| 1207 | sleep(1); |
| 1208 | #endif |
| 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. |
| 1213 | return; |
| 1214 | } |
| 1215 | HANDLE handle; |
| 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, |
| 1225 | 0, GetLastError(), |
| 1226 | LANG_NEUTRAL, (LPTSTR)&error_string, |
| 1227 | 0, 0)) { |
| 1228 | fl_alert("Unable to terminate %s process.\n%s", |
| 1229 | description, (char *) error_string); |
| 1230 | LocalFree((HLOCAL)error_string); |
| 1231 | } |
| 1232 | } |
| 1233 | if (handle != 0) { |
| 1234 | CloseHandle(handle); |
| 1235 | } |
| 1236 | #endif |
| 1237 | } |
| 1238 | |
| 1239 | |
| 1240 | #ifdef OS_LIKE_WIN32 |
| 1241 | // Return true if the given command is for Windows Media Player. |
| 1242 | |
| 1243 | bool |
| 1244 | Run::is_mplayer(const char * command) |
| 1245 | { |
| 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) { |
| 1251 | return(true); |
| 1252 | } |
| 1253 | return(false); |
| 1254 | } |
| 1255 | |
| 1256 | |
| 1257 | // Return true if the given command is for GSview. |
| 1258 | |
| 1259 | bool |
| 1260 | Run::is_gsview(const char * command) |
| 1261 | { |
| 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) { |
| 1267 | return(true); |
| 1268 | } |
| 1269 | return(false); |
| 1270 | } |
| 1271 | #endif |
| 1272 | |
| 1273 | |
| 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... |
| 1279 | |
| 1280 | void |
| 1281 | Run::clean_up(void) |
| 1282 | { |
| 1283 | kill_process(&display_child, "display"); |
| 1284 | kill_process(&MIDI_child, "MIDI player"); |
| 1285 | } |
| 1286 | |
| 1287 | |
| 1288 | // Report whether we spawned a display child and think it is still alive. |
| 1289 | |
| 1290 | bool |
| 1291 | Run::has_display_child(void) |
| 1292 | { |
| 1293 | #ifdef OS_LIKE_WIN32 |
| 1294 | return(display_child.hProcess != 0); |
| 1295 | #else |
| 1296 | return(display_child != 0); |
| 1297 | #endif |
| 1298 | } |
| 1299 | |
| 1300 | |
| 1301 | // Report whether we spawned a MIDI child and think it is still alive. |
| 1302 | |
| 1303 | bool |
| 1304 | Run::has_MIDI_child(void) |
| 1305 | { |
| 1306 | #ifdef OS_LIKE_WIN32 |
| 1307 | return(MIDI_child.hProcess != 0); |
| 1308 | #else |
| 1309 | return(MIDI_child != 0); |
| 1310 | #endif |
| 1311 | } |
| 1312 | |
| 1313 | |
| 1314 | //------------ Class for displaying errors from Mup |
| 1315 | |
| 1316 | Error_report::Error_report(void) |
| 1317 | : Fl_Double_Window(Default_width, Default_height, "Error report") |
| 1318 | { |
| 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() ); |
| 1322 | |
| 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); |
| 1326 | |
| 1327 | ok_p = new Fl_Return_Button(w() / 2 - 40, h() - 50, 80, 30, "OK"); |
| 1328 | ok_p->callback(OK_cb, this); |
| 1329 | show(); |
| 1330 | |
| 1331 | // Arrange to clean up new-ed widgets in destructor. |
| 1332 | end(); |
| 1333 | } |
| 1334 | |
| 1335 | Error_report::~Error_report() |
| 1336 | { |
| 1337 | } |
| 1338 | |
| 1339 | |
| 1340 | // Load the error file (from -e of Mup) into window to show user. |
| 1341 | |
| 1342 | int |
| 1343 | Error_report::loadfile(const char * filename) |
| 1344 | { |
| 1345 | return text_p->buffer()->loadfile(filename); |
| 1346 | } |
| 1347 | |
| 1348 | |
| 1349 | // Callback for when user clicks OK after reading Mup error report |
| 1350 | |
| 1351 | CALL_BACK(Error_report, OK) |
| 1352 | { |
| 1353 | hide(); |
| 1354 | } |
| 1355 | |
| 1356 | |
| 1357 | // Callback for change in font/size |
| 1358 | |
| 1359 | void |
| 1360 | Error_report::font_change_cb(void * data, Fl_Font font, unsigned char size) |
| 1361 | { |
| 1362 | ((Error_report *)data)->font_change(font, size); |
| 1363 | } |
| 1364 | |
| 1365 | void |
| 1366 | Error_report::font_change(Fl_Font font, unsigned char size) |
| 1367 | { |
| 1368 | text_p->textfont(font); |
| 1369 | text_p->textsize(size); |
| 1370 | text_p->redisplay_range(0, text_p->buffer()->length()); |
| 1371 | } |