1 /* 2 * Copyright 2001-2008, Haiku. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Ithamar R. Adema 7 * Michael Pfeiffer 8 */ 9 #include "Printer.h" 10 11 #include "pr_server.h" 12 #include "BeUtils.h" 13 #include "PrintServerApp.h" 14 15 // posix 16 #include <limits.h> 17 #include <stdlib.h> 18 #include <string.h> 19 #include <unistd.h> 20 21 // BeOS API 22 #include <Application.h> 23 #include <Autolock.h> 24 #include <Message.h> 25 #include <NodeMonitor.h> 26 #include <String.h> 27 #include <StorageKit.h> 28 #include <SupportDefs.h> 29 30 31 SpoolFolder::SpoolFolder(BLocker*locker, BLooper* looper, const BDirectory& spoolDir) 32 : Folder(locker, looper, spoolDir) 33 { 34 } 35 36 37 // Notify print_server that there is a job file waiting for printing 38 void SpoolFolder::Notify(Job* job, int kind) 39 { 40 if ((kind == kJobAdded || kind == kJobAttrChanged) 41 && job->IsValid() && job->IsWaiting()) { 42 be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB); 43 } 44 } 45 46 47 // --------------------------------------------------------------- 48 typedef BMessage* (*config_func_t)(BNode*, const BMessage*); 49 typedef BMessage* (*take_job_func_t)(BFile*, BNode*, const BMessage*); 50 typedef char* (*add_printer_func_t)(const char* printer_name); 51 typedef BMessage* (*default_settings_t)(BNode*); 52 53 // --------------------------------------------------------------- 54 BObjectList<Printer> Printer::sPrinters; 55 56 57 // --------------------------------------------------------------- 58 // Find [static] 59 // 60 // Searches the static object list for a printer object with the 61 // specified name. 62 // 63 // Parameters: 64 // name - Printer definition name we're looking for. 65 // 66 // Returns: 67 // Pointer to Printer object, or NULL if not found. 68 // --------------------------------------------------------------- 69 Printer* Printer::Find(const BString& name) 70 { 71 // Look in list to find printer definition 72 for (int32 idx=0; idx < sPrinters.CountItems(); idx++) { 73 if (name == sPrinters.ItemAt(idx)->Name()) 74 return sPrinters.ItemAt(idx); 75 } 76 return NULL; 77 } 78 79 80 Printer* Printer::Find(node_ref* node) 81 { 82 node_ref n; 83 // Look in list to find printer definition 84 for (int32 idx = 0; idx < sPrinters.CountItems(); idx++) { 85 Printer* printer = sPrinters.ItemAt(idx); 86 printer->SpoolDir()->GetNodeRef(&n); 87 if (n == *node) 88 return printer; 89 } 90 91 // None found, so return NULL 92 return NULL; 93 } 94 95 96 Printer* Printer::At(int32 idx) 97 { 98 return sPrinters.ItemAt(idx); 99 } 100 101 102 void Printer::Remove(Printer* printer) 103 { 104 sPrinters.RemoveItem(printer); 105 } 106 107 int32 Printer::CountPrinters() 108 { 109 return sPrinters.CountItems(); 110 } 111 112 113 // --------------------------------------------------------------- 114 // Printer [constructor] 115 // 116 // Initializes the printer object with data read from the 117 // attributes attached to the printer definition node. 118 // 119 // Parameters: 120 // node - Printer definition node for this printer. 121 // 122 // Returns: 123 // none. 124 // --------------------------------------------------------------- 125 Printer::Printer(const BDirectory* node, Resource* res) 126 : Inherited(B_EMPTY_STRING), 127 fPrinter(gLock, be_app, *node), 128 fResource(res), 129 fSinglePrintThread(res->NeedsLocking()), 130 fJob(NULL), 131 fProcessing(0), 132 fAbort(false) 133 { 134 BString name; 135 // Set our name to the name of the passed node 136 if (SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_PRT_NAME, &name) == B_OK) 137 SetName(name.String()); 138 139 // Add us to the global list of known printer definitions 140 sPrinters.AddItem(this); 141 142 ResetJobStatus(); 143 } 144 145 146 Printer::~Printer() 147 { 148 ((PrintServerApp*)be_app)->NotifyPrinterDeletion(this); 149 } 150 151 152 void Printer::MessageReceived(BMessage* msg) 153 { 154 switch(msg->what) { 155 case B_GET_PROPERTY: 156 case B_SET_PROPERTY: 157 case B_CREATE_PROPERTY: 158 case B_DELETE_PROPERTY: 159 case B_COUNT_PROPERTIES: 160 case B_EXECUTE_PROPERTY: 161 HandleScriptingCommand(msg); 162 break; 163 164 default: 165 Inherited::MessageReceived(msg); 166 } 167 } 168 169 170 // Remove printer spooler directory 171 status_t Printer::Remove() 172 { 173 status_t rc = B_OK; 174 BPath path; 175 176 if ((rc=::find_directory(B_USER_PRINTERS_DIRECTORY, &path)) == B_OK) { 177 path.Append(Name()); 178 rc = rmdir(path.Path()); 179 } 180 181 return rc; 182 } 183 184 185 // --------------------------------------------------------------- 186 // ConfigurePrinter 187 // 188 // Handles calling the printer addon's add_printer function. 189 // 190 // Parameters: 191 // none. 192 // 193 // Returns: 194 // B_OK if successful or errorcode otherwise. 195 // --------------------------------------------------------------- 196 status_t Printer::ConfigurePrinter() 197 { 198 status_t rc; 199 image_id id; 200 if ((rc = LoadPrinterAddon(id)) == B_OK) { 201 // Addon was loaded, so try and get the add_printer symbol 202 add_printer_func_t func; 203 if (get_image_symbol(id, "add_printer", B_SYMBOL_TYPE_TEXT, 204 (void**)&func) == B_OK) { 205 // call the function and check its result 206 rc = ((*func)(Name()) == NULL) ? B_ERROR : B_OK; 207 } 208 209 ::unload_add_on(id); 210 } 211 212 return rc; 213 } 214 215 216 // --------------------------------------------------------------- 217 // ConfigurePage 218 // 219 // Handles calling the printer addon's config_page function. 220 // 221 // Parameters: 222 // settings - Page settings to display. The contents of this 223 // message will be replaced with the new settings 224 // if the function returns success. 225 // 226 // Returns: 227 // B_OK if successful or errorcode otherwise. 228 // --------------------------------------------------------------- 229 status_t Printer::ConfigurePage(BMessage& settings) 230 { 231 status_t rc; 232 image_id id; 233 if ((rc = LoadPrinterAddon(id)) == B_OK) { 234 // Addon was loaded, so try and get the config_page symbol 235 config_func_t func; 236 if ((rc=get_image_symbol(id, "config_page", B_SYMBOL_TYPE_TEXT, 237 (void**)&func)) == B_OK) { 238 // call the function and check its result 239 BMessage* new_settings = (*func)(SpoolDir(), &settings); 240 if (new_settings != NULL && new_settings->what != 'baad') { 241 settings = *new_settings; 242 settings.what = 'okok'; 243 AddCurrentPrinter(settings); 244 } else { 245 rc = B_ERROR; 246 } 247 delete new_settings; 248 } 249 250 ::unload_add_on(id); 251 } 252 253 return rc; 254 } 255 256 257 // --------------------------------------------------------------- 258 // ConfigureJob 259 // 260 // Handles calling the printer addon's config_job function. 261 // 262 // Parameters: 263 // settings - Job settings to display. The contents of this 264 // message will be replaced with the new settings 265 // if the function returns success. 266 // 267 // Returns: 268 // B_OK if successful or errorcode otherwise. 269 // --------------------------------------------------------------- 270 status_t Printer::ConfigureJob(BMessage& settings) 271 { 272 status_t rc; 273 image_id id; 274 if ((rc = LoadPrinterAddon(id)) == B_OK) { 275 // Addon was loaded, so try and get the config_job symbol 276 config_func_t func; 277 if ((rc = get_image_symbol(id, "config_job", B_SYMBOL_TYPE_TEXT, 278 (void**)&func)) == B_OK) { 279 // call the function and check its result 280 BMessage* new_settings = (*func)(SpoolDir(), &settings); 281 if ((new_settings != NULL) && (new_settings->what != 'baad')) { 282 settings = *new_settings; 283 settings.what = 'okok'; 284 AddCurrentPrinter(settings); 285 } else { 286 rc = B_ERROR; 287 } 288 delete new_settings; 289 } 290 291 ::unload_add_on(id); 292 } 293 294 return rc; 295 } 296 297 298 // --------------------------------------------------------------- 299 // HandleSpooledJobs 300 // 301 // Print spooled jobs in a new thread. 302 // --------------------------------------------------------------- 303 void Printer::HandleSpooledJob() 304 { 305 BAutolock lock(gLock); 306 if (lock.IsLocked() 307 && (!fSinglePrintThread || fProcessing == 0) && FindSpooledJob()) { 308 StartPrintThread(); 309 } 310 } 311 312 313 // --------------------------------------------------------------- 314 // GetDefaultSettings 315 // 316 // Retrieve the default configuration message from printer add-on 317 // 318 // Parameters: 319 // settings, output paramter. 320 // 321 // Returns: 322 // B_OK if successful or errorcode otherwise. 323 // --------------------------------------------------------------- 324 status_t Printer::GetDefaultSettings(BMessage& settings) 325 { 326 status_t rc; 327 image_id id; 328 if ((rc = LoadPrinterAddon(id)) == B_OK) { 329 // Addon was loaded, so try and get the default_settings symbol 330 default_settings_t func; 331 if ((rc = get_image_symbol(id, "default_settings", B_SYMBOL_TYPE_TEXT, 332 (void**)&func)) == B_OK) { 333 // call the function and check its result 334 BMessage* new_settings = (*func)(SpoolDir()); 335 if (new_settings) { 336 settings = *new_settings; 337 settings.what = 'okok'; 338 AddCurrentPrinter(settings); 339 } else { 340 rc = B_ERROR; 341 } 342 delete new_settings; 343 } 344 345 ::unload_add_on(id); 346 } 347 return rc; 348 } 349 350 351 void Printer::AbortPrintThread() 352 { 353 fAbort = true; 354 } 355 356 357 // --------------------------------------------------------------- 358 // LoadPrinterAddon 359 // 360 // Try to load the printer addon into memory. 361 // 362 // Parameters: 363 // id - image_id set to the image id of the loaded addon. 364 // 365 // Returns: 366 // B_OK if successful or errorcode otherwise. 367 // --------------------------------------------------------------- 368 status_t Printer::LoadPrinterAddon(image_id& id) 369 { 370 status_t rc; 371 BString drName; 372 if ((rc = SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_DRV_NAME, &drName)) == B_OK) { 373 // try to locate the driver 374 BPath path; 375 if ((rc= ::TestForAddonExistence(drName.String(), B_USER_ADDONS_DIRECTORY, 376 "Print", path)) != B_OK) { 377 if ((rc = ::TestForAddonExistence(drName.String(), B_COMMON_ADDONS_DIRECTORY, 378 "Print", path)) != B_OK) { 379 rc = ::TestForAddonExistence(drName.String(), B_BEOS_ADDONS_DIRECTORY, 380 "Print", path); 381 } 382 } 383 384 // If the driver was found 385 if (rc == B_OK) { 386 // If we cannot load the addon 387 if ((id=::load_add_on(path.Path())) < 0) 388 rc = id; 389 } 390 } 391 return rc; 392 } 393 394 395 // --------------------------------------------------------------- 396 // AddCurrentPrinter 397 // 398 // Add printer name to message. 399 // 400 // Parameters: 401 // msg - message. 402 // --------------------------------------------------------------- 403 void Printer::AddCurrentPrinter(BMessage& message) 404 { 405 BString name; 406 GetName(name); 407 408 message.RemoveName(PSRV_FIELD_CURRENT_PRINTER); 409 message.AddString(PSRV_FIELD_CURRENT_PRINTER, name.String()); 410 } 411 412 413 // --------------------------------------------------------------- 414 // GetName 415 // 416 // Get the name from spool directory. 417 // 418 // Parameters: 419 // name - the name of the printer. 420 // --------------------------------------------------------------- 421 void Printer::GetName(BString& name) 422 { 423 if (SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_PRT_NAME, &name) != B_OK) 424 name = "Unknown Printer"; 425 } 426 427 428 // --------------------------------------------------------------- 429 // ResetJobStatus 430 // 431 // Reset status of "processing" jobs to "waiting" at print_server start. 432 // --------------------------------------------------------------- 433 void Printer::ResetJobStatus() 434 { 435 if (fPrinter.Lock()) { 436 const int32 n = fPrinter.CountJobs(); 437 for (int32 i = 0; i < n; i ++) { 438 Job* job = fPrinter.JobAt(i); 439 if (job->Status() == kProcessing) 440 job->SetStatus(kWaiting); 441 } 442 fPrinter.Unlock(); 443 } 444 } 445 446 447 // --------------------------------------------------------------- 448 // HasCurrentPrinter 449 // 450 // Try to read the printer name from job file. 451 // 452 // Parameters: 453 // name - the printer name. 454 // 455 // Returns: 456 // true if successful. 457 // --------------------------------------------------------------- 458 bool Printer::HasCurrentPrinter(BString& name) 459 { 460 BMessage settings; 461 // read settings from spool file and get printer name 462 BFile jobFile(&fJob->EntryRef(), B_READ_WRITE); 463 return jobFile.InitCheck() == B_OK 464 && jobFile.Seek(sizeof(print_file_header), SEEK_SET) == sizeof(print_file_header) 465 && settings.Unflatten(&jobFile) == B_OK 466 && settings.FindString(PSRV_FIELD_CURRENT_PRINTER, &name) == B_OK; 467 } 468 469 470 // --------------------------------------------------------------- 471 // MoveJob 472 // 473 // Try to move job to another printer folder. 474 // 475 // Parameters: 476 // name - the printer folder name. 477 // 478 // Returns: 479 // true if successful. 480 // --------------------------------------------------------------- 481 bool Printer::MoveJob(const BString& name) 482 { 483 BPath file(&fJob->EntryRef()); 484 BPath path; 485 file.GetParent(&path); 486 path.Append(".."); 487 path.Append(name.String()); 488 BDirectory dir(path.Path()); 489 BEntry entry(&fJob->EntryRef()); 490 // try to move job file to proper directory 491 return entry.MoveTo(&dir) == B_OK; 492 } 493 494 495 // --------------------------------------------------------------- 496 // FindSpooledJob 497 // 498 // Looks if there is a job waiting to be processed and moves 499 // jobs to the proper printer folder. 500 // 501 // Note: 502 // Our implementation of BPrintJob moves jobs to the 503 // proper printer folder. 504 // 505 // 506 // Returns: 507 // true if there is a job present in fJob. 508 // --------------------------------------------------------------- 509 bool Printer::FindSpooledJob() 510 { 511 BString name2; 512 GetName(name2); 513 do { 514 fJob = fPrinter.GetNextJob(); 515 if (fJob) { 516 BString name; 517 if (HasCurrentPrinter(name) && name != name2 && MoveJob(name)) { 518 // job in wrong printer folder moved to apropriate one 519 fJob->SetStatus(kUnknown, false); // so that fPrinter.GetNextJob skips it 520 fJob->Release(); 521 } else { 522 // job found 523 fJob->SetPrinter(this); 524 return true; 525 } 526 } 527 } while (fJob != NULL); 528 return false; 529 } 530 531 532 // --------------------------------------------------------------- 533 // PrintSpooledJob 534 // 535 // Loads the printer add-on and calls its take_job function with 536 // the spool file as argument. 537 // 538 // Parameters: 539 // spoolFile - the spool file. 540 // 541 // Returns: 542 // B_OK if successful. 543 // --------------------------------------------------------------- 544 status_t Printer::PrintSpooledJob(BFile* spoolFile) 545 { 546 status_t rc; 547 image_id id; 548 if ((rc = LoadPrinterAddon(id)) == B_OK) { 549 take_job_func_t func; 550 // Addon was loaded, so try and get the take_job symbol 551 if ((rc = get_image_symbol(id, "take_job", B_SYMBOL_TYPE_TEXT, 552 (void**)&func)) == B_OK) { 553 // This seems to be required for legacy? 554 // HP PCL3 add-on crashes without it! 555 BMessage params(B_REFS_RECEIVED); 556 params.AddInt32("file", (int32)spoolFile); 557 params.AddInt32("printer", (int32)SpoolDir()); 558 // call the function and check its result 559 BMessage* result = (*func)(spoolFile, SpoolDir(), ¶ms); 560 561 if (result == NULL || result->what != 'okok') 562 rc = B_ERROR; 563 delete result; 564 } 565 566 ::unload_add_on(id); 567 } 568 return rc; 569 } 570 571 572 // --------------------------------------------------------------- 573 // PrintThread 574 // 575 // Loads the printer add-on and calls its take_job function with 576 // the spool file as argument. 577 // 578 // Parameters: 579 // job - the spool job. 580 // --------------------------------------------------------------- 581 void Printer::PrintThread(Job* job) 582 { 583 // Wait until resource is available 584 fResource->Lock(); 585 bool failed = true; 586 // Can we continue? 587 if (!fAbort) { 588 BFile jobFile(&job->EntryRef(), B_READ_WRITE); 589 // Tell the printer to print the spooled job 590 if (jobFile.InitCheck() == B_OK && PrintSpooledJob(&jobFile) == B_OK) { 591 // Remove spool file if printing was successfull. 592 job->Remove(); failed = false; 593 } 594 } 595 // Set status of spooled job on error 596 if (failed) 597 job->SetStatus(kFailed); 598 fResource->Unlock(); 599 job->Release(); 600 atomic_add(&fProcessing, -1); 601 Release(); 602 // Notify print_server to process next spooled job 603 be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB); 604 } 605 606 607 // --------------------------------------------------------------- 608 // print_thread 609 // 610 // Print thread entry, calls PrintThread with spool job. 611 // 612 // Parameters: 613 // data - spool job. 614 // 615 // Returns: 616 // 0 always. 617 // --------------------------------------------------------------- 618 status_t Printer::print_thread(void* data) 619 { 620 Job* job = static_cast<Job*>(data); 621 job->GetPrinter()->PrintThread(job); 622 return 0; 623 } 624 625 626 // --------------------------------------------------------------- 627 // StartPrintThread 628 // 629 // Sets the status of the current spool job to "processing" and 630 // starts the print_thread. 631 // --------------------------------------------------------------- 632 void Printer::StartPrintThread() 633 { 634 Acquire(); 635 thread_id tid = spawn_thread(print_thread, "print", B_NORMAL_PRIORITY, (void*)fJob); 636 if (tid > 0) { 637 fJob->SetStatus(kProcessing); 638 atomic_add(&fProcessing, 1); 639 resume_thread(tid); 640 } else { 641 fJob->Release(); 642 Release(); 643 } 644 } 645 646