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