1 /* 2 * Copyright 2001-2006, Haiku. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 I.R. Adema 7 Stefano Ceccherini (burton666@libero.it) 8 Michael Pfeiffer 9 */ 10 11 // TODO refactor (avoid code duplications, decrease method sizes) 12 13 #include <Alert.h> 14 #include <Application.h> 15 #include <Button.h> 16 #include <Debug.h> 17 #include <Entry.h> 18 #include <File.h> 19 #include <FindDirectory.h> 20 #include <Messenger.h> 21 #include <NodeInfo.h> 22 #include <OS.h> 23 #include <Path.h> 24 #include <PrintJob.h> 25 #include <Roster.h> 26 #include <View.h> 27 28 #include <pr_server.h> 29 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 34 static const int kSemTimeOut = 50000; 35 36 static const char *kPrintServerNotRespondingText = "Print Server is not responding."; 37 static const char *kNoPagesToPrintText = "No Pages to print!"; 38 39 // Summery of spool file format: 40 // See articel "How to Write a BeOS R5 Printer Driver" for description 41 // of spool file format: 42 // http://haiku-os.org/documents/dev/how_to_write_a_printer_driver 43 44 // print_file_header header 45 // On BeOS R5 header.version is 1 << 16 and 46 // header.first_page is -1. 47 // BMessage job_settings 48 // _page_header_ page_header 49 // followed by number_of_pictures: 50 // BPoint where 51 // BRect bounds 52 // BPicture picture 53 // remaining pages start at page_header.next_page of previous page_header 54 55 struct _page_header_ { 56 int32 number_of_pictures; 57 off_t next_page; 58 int32 reserved[10]; 59 }; 60 61 62 static status_t 63 GetPrinterServerMessenger(BMessenger& messenger) 64 { 65 messenger = BMessenger(PSRV_SIGNATURE_TYPE); 66 return messenger.IsValid() ? B_OK : B_ERROR; 67 } 68 69 70 static void 71 ShowError(const char *message) 72 { 73 BAlert* alert = new BAlert("Error", message, "OK"); 74 alert->Go(); 75 } 76 77 78 namespace BPrivate { 79 80 class Configuration { 81 public: 82 Configuration(uint32 what, BMessage *input); 83 ~Configuration(); 84 85 status_t SendRequest(thread_func function); 86 87 BMessage* Request(); 88 89 void SetResult(BMessage* result); 90 BMessage* Result() const { return fResult; } 91 92 private: 93 void RejectUserInput(); 94 void AllowUserInput(); 95 void DeleteSemaphore(); 96 97 uint32 fWhat; 98 BMessage *fInput; 99 BMessage *fRequest; 100 BMessage *fResult; 101 sem_id fThreadCompleted; 102 BAlert *fHiddenApplicationModalWindow; 103 }; 104 105 106 Configuration::Configuration(uint32 what, BMessage *input) 107 : fWhat(what), 108 fInput(input), 109 fRequest(NULL), 110 fResult(NULL), 111 fThreadCompleted(-1), 112 fHiddenApplicationModalWindow(NULL) 113 { 114 RejectUserInput(); 115 } 116 117 118 Configuration::~Configuration() 119 { 120 DeleteSemaphore(); 121 // in case SendRequest could not start the thread 122 delete fRequest; fRequest = NULL; 123 AllowUserInput(); 124 } 125 126 127 void 128 Configuration::RejectUserInput() 129 { 130 BAlert* alert = new BAlert("bogus", "app_modal_dialog", "OK"); 131 fHiddenApplicationModalWindow = alert; 132 alert->DefaultButton()->SetEnabled(false); 133 alert->SetDefaultButton(NULL); 134 alert->MoveTo(-65000, -65000); 135 alert->Go(NULL); 136 } 137 138 139 void 140 Configuration::AllowUserInput() 141 { 142 fHiddenApplicationModalWindow->Lock(); 143 fHiddenApplicationModalWindow->Quit(); 144 } 145 146 147 void 148 Configuration::DeleteSemaphore() 149 { 150 if (fThreadCompleted >= B_OK) { 151 sem_id id = fThreadCompleted; 152 fThreadCompleted = -1; 153 delete_sem(id); 154 } 155 } 156 157 158 status_t 159 Configuration::SendRequest(thread_func function) 160 { 161 fThreadCompleted = create_sem(0, "Configuration"); 162 if (fThreadCompleted < B_OK) { 163 return B_ERROR; 164 } 165 166 thread_id id = spawn_thread(function, "async_request", B_NORMAL_PRIORITY, this); 167 if (id <= 0 || resume_thread(id) != B_OK) { 168 return B_ERROR; 169 } 170 171 // Code copied from BAlert::Go() 172 BWindow* window = dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL))); 173 // Get the originating window, if it exists 174 175 // Heavily modified from TextEntryAlert code; the original didn't let the 176 // blocked window ever draw. 177 if (window != NULL) { 178 status_t err; 179 for (;;) { 180 do { 181 err = acquire_sem_etc(fThreadCompleted, 1, B_RELATIVE_TIMEOUT, 182 kSemTimeOut); 183 // We've (probably) had our time slice taken away from us 184 } while (err == B_INTERRUPTED); 185 186 if (err == B_BAD_SEM_ID) { 187 // Semaphore was finally nuked in SetResult(BMessage *) 188 break; 189 } 190 window->UpdateIfNeeded(); 191 } 192 } else { 193 // No window to update, so just hang out until we're done. 194 while (acquire_sem(fThreadCompleted) == B_INTERRUPTED); 195 } 196 197 status_t status; 198 wait_for_thread(id, &status); 199 200 return Result() != NULL ? B_OK : B_ERROR; 201 } 202 203 204 BMessage * 205 Configuration::Request() 206 { 207 if (fRequest != NULL) 208 return fRequest; 209 210 if (fInput != NULL) { 211 fRequest = new BMessage(*fInput); 212 fRequest->what = fWhat; 213 } else 214 fRequest = new BMessage(fWhat); 215 return fRequest; 216 } 217 218 219 void 220 Configuration::SetResult(BMessage *result) 221 { 222 fResult = result; 223 DeleteSemaphore(); 224 // terminate loop in thread spawned by SendRequest 225 } 226 227 } // BPrivate 228 229 230 BPrintJob::BPrintJob(const char *job_name) 231 : 232 fPrintJobName(NULL), 233 fPageNumber(0), 234 fSpoolFile(NULL), 235 fError(B_ERROR), 236 fSetupMessage(NULL), 237 fDefaultSetupMessage(NULL), 238 fCurrentPageHeader(NULL) 239 { 240 memset(&fCurrentHeader, 0, sizeof(fCurrentHeader)); 241 242 if (job_name != NULL) { 243 fPrintJobName = strdup(job_name); 244 } 245 246 fCurrentPageHeader = new _page_header_; 247 if (fCurrentPageHeader != NULL) { 248 memset(fCurrentPageHeader, 0, sizeof(*fCurrentPageHeader)); 249 } 250 } 251 252 253 BPrintJob::~BPrintJob() 254 { 255 CancelJob(); 256 257 free(fPrintJobName); 258 delete fSetupMessage; 259 delete fDefaultSetupMessage; 260 delete fCurrentPageHeader; 261 } 262 263 264 static status_t 265 ConfigPageThread(void *data) 266 { 267 BPrivate::Configuration* configuration = static_cast<BPrivate::Configuration*>(data); 268 269 BMessenger printServer; 270 if (GetPrinterServerMessenger(printServer) != B_OK) { 271 ShowError(kPrintServerNotRespondingText); 272 configuration->SetResult(NULL); 273 return B_ERROR; 274 } 275 276 BMessage *request = configuration->Request(); 277 if (request == NULL) { 278 configuration->SetResult(NULL); 279 return B_ERROR; 280 } 281 282 283 BMessage reply; 284 if (printServer.SendMessage(request, &reply) != B_OK 285 || reply.what != 'okok') { 286 configuration->SetResult(NULL); 287 return B_ERROR; 288 } 289 290 configuration->SetResult(new BMessage(reply)); 291 return B_OK; 292 } 293 294 295 status_t 296 BPrintJob::ConfigPage() 297 { 298 BPrivate::Configuration configuration(PSRV_SHOW_PAGE_SETUP, fSetupMessage); 299 status_t status = configuration.SendRequest(ConfigPageThread); 300 if (status != B_OK) 301 return status; 302 delete fSetupMessage; 303 fSetupMessage = configuration.Result(); 304 _HandlePageSetup(fSetupMessage); 305 return B_OK; 306 } 307 308 309 static status_t 310 ConfigJobThread(void *data) 311 { 312 BPrivate::Configuration* configuration = static_cast<BPrivate::Configuration*>(data); 313 314 BMessenger printServer; 315 if (GetPrinterServerMessenger(printServer) != B_OK) { 316 ShowError(kPrintServerNotRespondingText); 317 configuration->SetResult(NULL); 318 return B_ERROR; 319 } 320 321 BMessage *request = configuration->Request(); 322 if (request == NULL) { 323 configuration->SetResult(NULL); 324 return B_ERROR; 325 } 326 327 328 BMessage reply; 329 if (printServer.SendMessage(request, &reply) != B_OK 330 || reply.what != 'okok') { 331 configuration->SetResult(NULL); 332 return B_ERROR; 333 } 334 335 configuration->SetResult(new BMessage(reply)); 336 return B_OK; 337 } 338 339 status_t 340 BPrintJob::ConfigJob() 341 { 342 BPrivate::Configuration configuration(PSRV_SHOW_PRINT_SETUP, fSetupMessage); 343 status_t status = configuration.SendRequest(ConfigJobThread); 344 if (status != B_OK) 345 return status; 346 delete fSetupMessage; 347 fSetupMessage = configuration.Result(); 348 _HandlePrintSetup(fSetupMessage); 349 return B_OK; 350 } 351 352 353 void 354 BPrintJob::BeginJob() 355 { 356 if (fSpoolFile != NULL) { 357 // can not start a new job until it has been commited or cancelled 358 return; 359 } 360 if (fCurrentPageHeader == NULL) { 361 return; 362 } 363 364 if (fSetupMessage == NULL) { 365 // TODO show alert, setup message is required 366 return; 367 } 368 369 // create spool file 370 BPath path; 371 status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path); 372 if (status != B_OK) 373 return; 374 375 char *printer = _GetCurrentPrinterName(); 376 if (printer == NULL) 377 return; 378 379 path.Append(printer); 380 free(printer); 381 382 char mangledName[B_FILE_NAME_LENGTH]; 383 _GetMangledName(mangledName, B_FILE_NAME_LENGTH); 384 385 path.Append(mangledName); 386 387 if (path.InitCheck() != B_OK) 388 return; 389 390 // TODO fSpoolFileName should store the name only (not path which can be 1024 bytes long) 391 strncpy(fSpoolFileName, path.Path(), sizeof(fSpoolFileName)); 392 fSpoolFile = new BFile(fSpoolFileName, B_READ_WRITE | B_CREATE_FILE); 393 394 if (fSpoolFile->InitCheck() != B_OK) { 395 CancelJob(); 396 return; 397 } 398 399 // add print_file_header 400 // page_count is updated in CommitJob() 401 fCurrentHeader.version = 1 << 16; 402 fCurrentHeader.page_count = 0; 403 // on BeOS R5 the offset to the first page 404 // was always -1. 405 fCurrentHeader.first_page = (off_t)-1; 406 407 if (fSpoolFile->Write(&fCurrentHeader, sizeof(fCurrentHeader)) != sizeof(fCurrentHeader)) { 408 CancelJob(); 409 return; 410 } 411 412 // add printer settings message 413 if (!fSetupMessage->HasString(PSRV_FIELD_CURRENT_PRINTER)) 414 fSetupMessage->AddString(PSRV_FIELD_CURRENT_PRINTER, printer); 415 _AddSetupSpec(); 416 417 // prepare page header 418 // number_of_pictures is updated in DrawView() 419 // next_page is updated in SpoolPage() 420 fCurrentPageHeaderOffset = fSpoolFile->Position(); 421 fCurrentPageHeader->number_of_pictures = 0; 422 423 // state variables 424 fAbort = 0; 425 fPageNumber = 0; 426 fError = B_OK; 427 return; 428 } 429 430 431 void 432 BPrintJob::CommitJob() 433 { 434 if (fSpoolFile == NULL) { 435 return; 436 } 437 438 if (fPageNumber <= 0) { 439 ShowError(kNoPagesToPrintText); 440 CancelJob(); 441 return; 442 } 443 444 if (fCurrentPageHeader->number_of_pictures > 0) { 445 SpoolPage(); 446 } 447 448 // update spool file 449 _EndLastPage(); 450 451 // set file attributes 452 app_info appInfo; 453 be_app->GetAppInfo(&appInfo); 454 const char* printerName = ""; 455 fSetupMessage->FindString(PSRV_FIELD_CURRENT_PRINTER, &printerName); 456 457 BNodeInfo info(fSpoolFile); 458 info.SetType(PSRV_SPOOL_FILETYPE); 459 460 fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_PAGECOUNT, B_INT32_TYPE, 0, &fPageNumber, sizeof(int32)); 461 fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_DESCRIPTION, B_STRING_TYPE, 0, fPrintJobName, strlen(fPrintJobName) + 1); 462 fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_PRINTER, B_STRING_TYPE, 0, printerName, strlen(printerName) + 1); 463 fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_STATUS, B_STRING_TYPE, 0, PSRV_JOB_STATUS_WAITING, strlen(PSRV_JOB_STATUS_WAITING) + 1); 464 fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_MIMETYPE, B_STRING_TYPE, 0, appInfo.signature, strlen(appInfo.signature) + 1); 465 466 delete fSpoolFile; 467 fSpoolFile = NULL; 468 fError = B_ERROR; 469 470 // notify print server 471 BMessenger printServer; 472 if (GetPrinterServerMessenger(printServer) != B_OK) { 473 return; 474 } 475 476 BMessage request(PSRV_PRINT_SPOOLED_JOB); 477 BMessage reply; 478 479 request.AddString("JobName", fPrintJobName); 480 request.AddString("Spool File", fSpoolFileName); 481 printServer.SendMessage(&request, &reply); 482 } 483 484 void 485 BPrintJob::CancelJob() 486 { 487 if (fSpoolFile == NULL) { 488 return; 489 } 490 491 fAbort = 1; 492 BEntry(fSpoolFileName).Remove(); 493 delete fSpoolFile; 494 fSpoolFile = NULL; 495 } 496 497 498 void 499 BPrintJob::SpoolPage() 500 { 501 if (fSpoolFile == NULL) { 502 return; 503 } 504 505 // update page header 506 fCurrentPageHeader->next_page = fSpoolFile->Position(); 507 fSpoolFile->Seek(fCurrentPageHeaderOffset, SEEK_SET); 508 fSpoolFile->Write(fCurrentPageHeader, sizeof(*fCurrentPageHeader)); 509 fSpoolFile->Seek(0, SEEK_END); 510 511 fCurrentPageHeader->number_of_pictures = 0; 512 } 513 514 515 bool 516 BPrintJob::CanContinue() 517 { 518 // Check if our local error storage is still B_OK 519 return fError == B_OK && !fAbort; 520 } 521 522 523 void 524 BPrintJob::DrawView(BView *view, BRect rect, BPoint where) 525 { 526 if (fSpoolFile == NULL) 527 return; 528 529 if (view == NULL) 530 return; 531 532 if (view->LockLooper()) { 533 BPicture picture; 534 _RecurseView(view, where, &picture, rect); 535 _AddPicture(picture, rect, where); 536 view->UnlockLooper(); 537 } 538 } 539 540 541 BMessage * 542 BPrintJob::Settings() 543 { 544 if (fSetupMessage == NULL) { 545 return NULL; 546 } 547 548 return new BMessage(*fSetupMessage); 549 } 550 551 552 void 553 BPrintJob::SetSettings(BMessage *message) 554 { 555 if (message != NULL) { 556 _HandlePrintSetup(message); 557 } 558 delete fSetupMessage; 559 fSetupMessage = message; 560 } 561 562 563 bool 564 BPrintJob::IsSettingsMessageValid(BMessage *message) const 565 { 566 char *printerName = _GetCurrentPrinterName(); 567 if (printerName == NULL) { 568 return false; 569 } 570 571 const char *name = NULL; 572 // The passed message is valid if it contains the right printer name. 573 bool valid = message != NULL 574 && message->FindString("printer_name", &name) == B_OK 575 && strcmp(printerName, name) == 0; 576 577 free(printerName); 578 579 return valid; 580 } 581 582 583 // Either SetSettings() or ConfigPage() has to be called prior 584 // to any of the getters otherwise they return undefined values. 585 BRect 586 BPrintJob::PaperRect() 587 { 588 if (fDefaultSetupMessage == NULL) 589 _LoadDefaultSettings(); 590 return fPaperSize; 591 } 592 593 594 BRect 595 BPrintJob::PrintableRect() 596 { 597 if (fDefaultSetupMessage == NULL) 598 _LoadDefaultSettings(); 599 return fUsableSize; 600 } 601 602 603 void 604 BPrintJob::GetResolution(int32 *xdpi, int32 *ydpi) 605 { 606 if (fDefaultSetupMessage == NULL) 607 _LoadDefaultSettings(); 608 609 if (xdpi != NULL) 610 *xdpi = fXResolution; 611 if (ydpi != NULL) 612 *ydpi = fYResolution; 613 } 614 615 616 int32 617 BPrintJob::FirstPage() 618 { 619 return fFirstPage; 620 } 621 622 623 int32 624 BPrintJob::LastPage() 625 { 626 return fLastPage; 627 } 628 629 630 int32 631 BPrintJob::PrinterType(void *) const 632 { 633 BMessenger printServer; 634 if (GetPrinterServerMessenger(printServer) != B_OK) { 635 return B_COLOR_PRINTER; // default 636 } 637 638 BMessage message(PSRV_GET_ACTIVE_PRINTER); 639 BMessage reply; 640 641 printServer.SendMessage(&message, &reply); 642 643 int32 type; 644 if (reply.FindInt32("color", &type) != B_OK) { 645 return B_COLOR_PRINTER; // default 646 } 647 return type; 648 } 649 650 651 #if 0 652 #pragma mark ----- PRIVATE ----- 653 #endif 654 655 656 void 657 BPrintJob::_RecurseView(BView *view, BPoint origin, 658 BPicture *picture, BRect rect) 659 { 660 ASSERT(picture != NULL); 661 // TODO: test what happens if views don't have 662 // the B_WILL_DRAW flag or have B_DRAW_ON_CHILDREN 663 664 view->AppendToPicture(picture); 665 view->f_is_printing = true; 666 view->PushState(); 667 view->Draw(rect); 668 view->PopState(); 669 view->f_is_printing = false; 670 view->EndPicture(); 671 672 BView *child = view->ChildAt(0); 673 while (child != NULL) { 674 // TODO: origin and rect should probably 675 // be converted for children views in some way 676 _RecurseView(child, origin, picture, rect); 677 child = child->NextSibling(); 678 } 679 } 680 681 682 void 683 BPrintJob::_GetMangledName(char *buffer, size_t bufferSize) const 684 { 685 snprintf(buffer, bufferSize, "%s@%lld", fPrintJobName, system_time() / 1000); 686 } 687 688 689 void 690 BPrintJob::_HandlePageSetup(BMessage *setup) 691 { 692 setup->FindRect(PSRV_FIELD_PRINTABLE_RECT, &fUsableSize); 693 setup->FindRect(PSRV_FIELD_PAPER_RECT, &fPaperSize); 694 695 // TODO verify data type (taken from libprint) 696 int64 valueInt64; 697 if (setup->FindInt64(PSRV_FIELD_XRES, &valueInt64) == B_OK) { 698 fXResolution = (short)valueInt64; 699 } 700 if (setup->FindInt64(PSRV_FIELD_YRES, &valueInt64) == B_OK) { 701 fYResolution = (short)valueInt64; 702 } 703 } 704 705 706 bool 707 BPrintJob::_HandlePrintSetup(BMessage *message) 708 { 709 _HandlePageSetup(message); 710 711 bool valid = true; 712 if (message->FindInt32(PSRV_FIELD_FIRST_PAGE, &fFirstPage) != B_OK) { 713 valid = false; 714 } 715 if (message->FindInt32(PSRV_FIELD_LAST_PAGE, &fLastPage) != B_OK) { 716 valid = false; 717 } 718 719 return valid; 720 } 721 722 723 void 724 BPrintJob::_NewPage() 725 { 726 // write page header 727 fCurrentPageHeaderOffset = fSpoolFile->Position(); 728 fSpoolFile->Write(fCurrentPageHeader, sizeof(*fCurrentPageHeader)); 729 730 if (fPageNumber == 1) 731 fCurrentHeader.first_page = fCurrentPageHeaderOffset; 732 733 fPageNumber ++; 734 } 735 736 737 void 738 BPrintJob::_EndLastPage() 739 { 740 fSpoolFile->Seek(0, SEEK_SET); 741 fCurrentHeader.page_count = fPageNumber; 742 fSpoolFile->Write(&fCurrentHeader, sizeof(fCurrentHeader));} 743 744 745 void 746 BPrintJob::_AddSetupSpec() 747 { 748 fSetupMessage->Flatten(fSpoolFile); 749 } 750 751 752 void 753 BPrintJob::_AddPicture(BPicture &picture, BRect &rect, BPoint &where) 754 { 755 ASSERT(fSpoolFile != NULL); 756 757 if (fCurrentPageHeader->number_of_pictures == 0) { 758 _NewPage(); 759 } 760 fCurrentPageHeader->number_of_pictures ++; 761 762 fSpoolFile->Write(&where, sizeof(where)); 763 fSpoolFile->Write(&rect, sizeof(rect)); 764 picture.Flatten(fSpoolFile); 765 } 766 767 768 // Returns a copy of the current printer name 769 // or NULL if it ccould not be obtained. 770 // Caller is responsible to free the string using free(). 771 char * 772 BPrintJob::_GetCurrentPrinterName() const 773 { 774 BMessenger printServer; 775 if (GetPrinterServerMessenger(printServer)) { 776 return NULL; 777 } 778 779 BMessage message(PSRV_GET_ACTIVE_PRINTER); 780 BMessage reply; 781 782 const char *printerName = NULL; 783 784 if (printServer.SendMessage(&message, &reply) == B_OK) { 785 reply.FindString("printer_name", &printerName); 786 } 787 if (printerName == NULL) { 788 return NULL; 789 } 790 return strdup(printerName); 791 } 792 793 794 void 795 BPrintJob::_LoadDefaultSettings() 796 { 797 BMessenger printServer; 798 if (GetPrinterServerMessenger(printServer) != B_OK) { 799 return; 800 } 801 802 BMessage message(PSRV_GET_DEFAULT_SETTINGS); 803 BMessage *reply = new BMessage; 804 805 printServer.SendMessage(&message, reply); 806 807 // Only override our settings if we don't have any settings yet 808 if (fSetupMessage == NULL) 809 _HandlePrintSetup(reply); 810 811 delete fDefaultSetupMessage; 812 fDefaultSetupMessage = reply; 813 } 814 815 816 void BPrintJob::_ReservedPrintJob1() {} 817 void BPrintJob::_ReservedPrintJob2() {} 818 void BPrintJob::_ReservedPrintJob3() {} 819 void BPrintJob::_ReservedPrintJob4() {} 820