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