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