/*
* Copyright 2001-2009, Haiku.
* Distributed under the terms of the MIT license.
*
* Authors:
* I.R. Adema
* Stefano Ceccherini (burton666@libero.it)
* Michael Pfeiffer
* julun <host.haiku@gmx.de>
*/
#include <PrintJob.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Alert.h>
#include <Application.h>
#include <Button.h>
#include <Debug.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Messenger.h>
#include <NodeInfo.h>
#include <OS.h>
#include <Path.h>
#include <Region.h>
#include <Roster.h>
#include <SystemCatalog.h>
#include <View.h>
#include <AutoDeleter.h>
#include <pr_server.h>
#include <ViewPrivate.h>
using BPrivate::gSystemCatalog;
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PrintJob"
#undef B_TRANSLATE
#define B_TRANSLATE(str) \
gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "PrintJob")
/*! Summary of spool file:
|-----------------------------------|
| print_file_header |
|-----------------------------------|
| BMessage print_job_settings |
|-----------------------------------|
| |
| ********** (first page) ********* |
| * * |
| * _page_header_ * |
| * ----------------------------- * |
| * |---------------------------| * |
| * | BPoint where | * |
| * | BRect bounds | * |
| * | BPicture pic | * |
| * |---------------------------| * |
| * |---------------------------| * |
| * | BPoint where | * |
| * | BRect bounds | * |
| * | BPicture pic | * |
| * |---------------------------| * |
| ********************************* |
| |
| ********* (second page) ********* |
| * * |
| * _page_header_ * |
| * ----------------------------- * |
| * |---------------------------| * |
| * | BPoint where | * |
| * | BRect bounds | * |
| * | BPicture pic | * |
| * |---------------------------| * |
| ********************************* |
|-----------------------------------|
BeOS R5 print_file_header.version is 1 << 16
BeOS R5 print_file_header.first_page is -1
each page can consist of a collection of picture structures
remaining pages start at _page_header_.next_page of previous _page_header_
See also: "How to Write a BeOS R5 Printer Driver" for description of spool
file format: http://haiku-os.org/documents/dev/how_to_write_a_printer_driver
*/
struct _page_header_ {
int32 number_of_pictures;
off_t next_page;
int32 reserved[10];
} _PACKED;
static void
ShowError(const char* message)
{
BAlert* alert = new BAlert(B_TRANSLATE("Error"), message, B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
// #pragma mark -- PrintServerMessenger
namespace BPrivate {
class PrintServerMessenger {
public:
PrintServerMessenger(uint32 what, BMessage* input);
~PrintServerMessenger();
BMessage* Request();
status_t SendRequest();
void SetResult(BMessage* result);
BMessage* Result() const { return fResult; }
static status_t GetPrintServerMessenger(BMessenger& messenger);
private:
void RejectUserInput();
void AllowUserInput();
void DeleteSemaphore();
static status_t MessengerThread(void* data);
uint32 fWhat;
BMessage* fInput;
BMessage* fRequest;
BMessage* fResult;
sem_id fThreadCompleted;
BAlert* fHiddenApplicationModalWindow;
};
} // namespace BPrivate
using namespace BPrivate;
// #pragma mark -- BPrintJob
BPrintJob::BPrintJob(const char* jobName)
:
fPrintJobName(NULL),
fSpoolFile(NULL),
fError(B_NO_INIT),
fSetupMessage(NULL),
fDefaultSetupMessage(NULL),
fAbort(0),
fCurrentPageHeader(NULL)
{
memset(&fSpoolFileHeader, 0, sizeof(print_file_header));
if (jobName != NULL && jobName[0])
fPrintJobName = strdup(jobName);
fCurrentPageHeader = new _page_header_;
if (fCurrentPageHeader != NULL)
memset(fCurrentPageHeader, 0, sizeof(_page_header_));
}
BPrintJob::~BPrintJob()
{
CancelJob();
free(fPrintJobName);
delete fSetupMessage;
delete fDefaultSetupMessage;
delete fCurrentPageHeader;
}
status_t
BPrintJob::ConfigPage()
{
PrintServerMessenger messenger(PSRV_SHOW_PAGE_SETUP, fSetupMessage);
status_t status = messenger.SendRequest();
if (status != B_OK)
return status;
delete fSetupMessage;
fSetupMessage = messenger.Result();
_HandlePageSetup(fSetupMessage);
return B_OK;
}
status_t
BPrintJob::ConfigJob()
{
PrintServerMessenger messenger(PSRV_SHOW_PRINT_SETUP, fSetupMessage);
status_t status = messenger.SendRequest();
if (status != B_OK)
return status;
delete fSetupMessage;
fSetupMessage = messenger.Result();
if (!_HandlePrintSetup(fSetupMessage))
return B_ERROR;
fError = B_OK;
return B_OK;
}
void
BPrintJob::BeginJob()
{
fError = B_ERROR;
// can not start a new job until it has been commited or cancelled
if (fSpoolFile != NULL || fCurrentPageHeader == NULL)
return;
// TODO show alert, setup message is required
if (fSetupMessage == NULL)
return;
// create spool file
BPath path;
status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path);
if (status != B_OK)
return;
char *printer = _GetCurrentPrinterName();
if (printer == NULL)
return;
MemoryDeleter _(printer);
path.Append(printer);
char mangledName[B_FILE_NAME_LENGTH];
_GetMangledName(mangledName, B_FILE_NAME_LENGTH);
path.Append(mangledName);
if (path.InitCheck() != B_OK)
return;
// TODO: fSpoolFileName should store the name only (not path which can be
// 1024 bytes long)
strlcpy(fSpoolFileName, path.Path(), sizeof(fSpoolFileName));
fSpoolFile = new BFile(fSpoolFileName, B_READ_WRITE | B_CREATE_FILE);
if (fSpoolFile->InitCheck() != B_OK) {
CancelJob();
return;
}
// add print_file_header
// page_count is updated in CommitJob()
// on BeOS R5 the offset to the first page was always -1
fSpoolFileHeader.version = 1 << 16;
fSpoolFileHeader.page_count = 0;
fSpoolFileHeader.first_page = (off_t)-1;
if (fSpoolFile->Write(&fSpoolFileHeader, sizeof(print_file_header))
!= sizeof(print_file_header)) {
CancelJob();
return;
}
// add printer settings message
if (!fSetupMessage->HasString(PSRV_FIELD_CURRENT_PRINTER))
fSetupMessage->AddString(PSRV_FIELD_CURRENT_PRINTER, printer);
_AddSetupSpec();
_NewPage();
// state variables
fAbort = 0;
fError = B_OK;
}
void
BPrintJob::CommitJob()
{
if (fSpoolFile == NULL)
return;
if (fSpoolFileHeader.page_count == 0) {
ShowError(B_TRANSLATE("No pages to print!"));
CancelJob();
return;
}
// update spool file
_EndLastPage();
// write spool file header
fSpoolFile->Seek(0, SEEK_SET);
fSpoolFile->Write(&fSpoolFileHeader, sizeof(print_file_header));
// set file attributes
app_info appInfo;
be_app->GetAppInfo(&appInfo);
const char* printerName = "";
fSetupMessage->FindString(PSRV_FIELD_CURRENT_PRINTER, &printerName);
BNodeInfo info(fSpoolFile);
info.SetType(PSRV_SPOOL_FILETYPE);
fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_PAGECOUNT, B_INT32_TYPE, 0,
&fSpoolFileHeader.page_count, sizeof(int32));
fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_DESCRIPTION, B_STRING_TYPE, 0,
fPrintJobName, strlen(fPrintJobName) + 1);
fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_PRINTER, B_STRING_TYPE, 0,
printerName, strlen(printerName) + 1);
fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_STATUS, B_STRING_TYPE, 0,
PSRV_JOB_STATUS_WAITING, strlen(PSRV_JOB_STATUS_WAITING) + 1);
fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_MIMETYPE, B_STRING_TYPE, 0,
appInfo.signature, strlen(appInfo.signature) + 1);
delete fSpoolFile;
fSpoolFile = NULL;
fError = B_ERROR;
// notify print server
BMessenger printServer;
if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
return;
BMessage request(PSRV_PRINT_SPOOLED_JOB);
request.AddString("JobName", fPrintJobName);
request.AddString("Spool File", fSpoolFileName);
BMessage reply;
printServer.SendMessage(&request, &reply);
}
void
BPrintJob::CancelJob()
{
if (fSpoolFile == NULL)
return;
fAbort = 1;
BEntry(fSpoolFileName).Remove();
delete fSpoolFile;
fSpoolFile = NULL;
}
void
BPrintJob::SpoolPage()
{
if (fSpoolFile == NULL)
return;
if (fCurrentPageHeader->number_of_pictures == 0)
return;
fSpoolFileHeader.page_count++;
fSpoolFile->Seek(0, SEEK_END);
if (fCurrentPageHeaderOffset) {
// update last written page_header
fCurrentPageHeader->next_page = fSpoolFile->Position();
fSpoolFile->Seek(fCurrentPageHeaderOffset, SEEK_SET);
fSpoolFile->Write(fCurrentPageHeader, sizeof(_page_header_));
fSpoolFile->Seek(fCurrentPageHeader->next_page, SEEK_SET);
}
_NewPage();
}
bool
BPrintJob::CanContinue()
{
// Check if our local error storage is still B_OK
return fError == B_OK && !fAbort;
}
void
BPrintJob::DrawView(BView* view, BRect rect, BPoint where)
{
if (fSpoolFile == NULL)
return;
if (view == NULL)
return;
if (view->LockLooper()) {
BPicture picture;
_RecurseView(view, B_ORIGIN - rect.LeftTop(), &picture, rect);
_AddPicture(picture, rect, where);
view->UnlockLooper();
}
}
BMessage*
BPrintJob::Settings()
{
if (fSetupMessage == NULL)
return NULL;
return new BMessage(*fSetupMessage);
}
void
BPrintJob::SetSettings(BMessage* message)
{
if (message != NULL)
_HandlePrintSetup(message);
delete fSetupMessage;
fSetupMessage = message;
}
bool
BPrintJob::IsSettingsMessageValid(BMessage* message) const
{
char* printerName = _GetCurrentPrinterName();
if (printerName == NULL)
return false;
const char* name = NULL;
// The passed message is valid if it contains the right printer name.
bool valid = message != NULL
&& message->FindString("printer_name", &name) == B_OK
&& strcmp(printerName, name) == 0;
free(printerName);
return valid;
}
// Either SetSettings() or ConfigPage() has to be called prior
// to any of the getters otherwise they return undefined values.
BRect
BPrintJob::PaperRect()
{
if (fDefaultSetupMessage == NULL)
_LoadDefaultSettings();
return fPaperSize;
}
BRect
BPrintJob::PrintableRect()
{
if (fDefaultSetupMessage == NULL)
_LoadDefaultSettings();
return fUsableSize;
}
void
BPrintJob::GetResolution(int32* xdpi, int32* ydpi)
{
if (fDefaultSetupMessage == NULL)
_LoadDefaultSettings();
if (xdpi != NULL)
*xdpi = fXResolution;
if (ydpi != NULL)
*ydpi = fYResolution;
}
int32
BPrintJob::FirstPage()
{
return fFirstPage;
}
int32
BPrintJob::LastPage()
{
return fLastPage;
}
int32
BPrintJob::PrinterType(void*) const
{
BMessenger printServer;
if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
return B_COLOR_PRINTER; // default
BMessage reply;
BMessage message(PSRV_GET_ACTIVE_PRINTER);
printServer.SendMessage(&message, &reply);
int32 type;
if (reply.FindInt32("color", &type) != B_OK)
return B_COLOR_PRINTER; // default
return type;
}
// #pragma mark - private
void
BPrintJob::_RecurseView(BView* view, BPoint origin, BPicture* picture,
BRect rect)
{
ASSERT(picture != NULL);
BRegion region;
region.Set(BRect(rect.left, rect.top, rect.right, rect.bottom));
view->fState->print_rect = rect;
view->AppendToPicture(picture);
view->PushState();
view->SetOrigin(origin);
view->ConstrainClippingRegion(®ion);
if (view->ViewColor() != B_TRANSPARENT_COLOR) {
rgb_color highColor = view->HighColor();
view->SetHighColor(view->ViewColor());
view->FillRect(rect);
view->SetHighColor(highColor);
}
if ((view->Flags() & B_WILL_DRAW) != 0) {
view->fIsPrinting = true;
view->Draw(rect);
view->fIsPrinting = false;
}
view->PopState();
view->EndPicture();
BView* child = view->ChildAt(0);
while (child != NULL) {
if (!child->IsHidden()) {
BPoint leftTop(view->Bounds().LeftTop() + child->Frame().LeftTop());
BRect printRect(rect.OffsetToCopy(rect.LeftTop() - leftTop)
& child->Bounds());
if (printRect.IsValid())
_RecurseView(child, origin + leftTop, picture, printRect);
}
child = child->NextSibling();
}
if ((view->Flags() & B_DRAW_ON_CHILDREN) != 0) {
view->AppendToPicture(picture);
view->PushState();
view->SetOrigin(origin);
view->ConstrainClippingRegion(®ion);
view->fIsPrinting = true;
view->DrawAfterChildren(rect);
view->fIsPrinting = false;
view->PopState();
view->EndPicture();
}
}
void
BPrintJob::_GetMangledName(char* buffer, size_t bufferSize) const
{
snprintf(buffer, bufferSize, "%s@%" B_PRId64, fPrintJobName,
system_time() / 1000);
}
void
BPrintJob::_HandlePageSetup(BMessage* setup)
{
setup->FindRect(PSRV_FIELD_PRINTABLE_RECT, &fUsableSize);
setup->FindRect(PSRV_FIELD_PAPER_RECT, &fPaperSize);
// TODO verify data type (taken from libprint)
int64 valueInt64;
if (setup->FindInt64(PSRV_FIELD_XRES, &valueInt64) == B_OK)
fXResolution = (short)valueInt64;
if (setup->FindInt64(PSRV_FIELD_YRES, &valueInt64) == B_OK)
fYResolution = (short)valueInt64;
}
bool
BPrintJob::_HandlePrintSetup(BMessage* message)
{
_HandlePageSetup(message);
bool valid = true;
if (message->FindInt32(PSRV_FIELD_FIRST_PAGE, &fFirstPage) != B_OK)
valid = false;
if (message->FindInt32(PSRV_FIELD_LAST_PAGE, &fLastPage) != B_OK)
valid = false;
return valid;
}
void
BPrintJob::_NewPage()
{
// init, write new page_header
fCurrentPageHeader->next_page = 0;
fCurrentPageHeader->number_of_pictures = 0;
fCurrentPageHeaderOffset = fSpoolFile->Position();
fSpoolFile->Write(fCurrentPageHeader, sizeof(_page_header_));
}
void
BPrintJob::_EndLastPage()
{
if (!fSpoolFile)
return;
if (fCurrentPageHeader->number_of_pictures == 0)
return;
fSpoolFileHeader.page_count++;
fSpoolFile->Seek(0, SEEK_END);
if (fCurrentPageHeaderOffset) {
fCurrentPageHeader->next_page = 0;
fSpoolFile->Seek(fCurrentPageHeaderOffset, SEEK_SET);
fSpoolFile->Write(fCurrentPageHeader, sizeof(_page_header_));
fSpoolFile->Seek(0, SEEK_END);
}
}
void
BPrintJob::_AddSetupSpec()
{
fSetupMessage->Flatten(fSpoolFile);
}
void
BPrintJob::_AddPicture(BPicture& picture, BRect& rect, BPoint& where)
{
ASSERT(fSpoolFile != NULL);
fCurrentPageHeader->number_of_pictures++;
fSpoolFile->Write(&where, sizeof(BRect));
fSpoolFile->Write(&rect, sizeof(BPoint));
picture.Flatten(fSpoolFile);
}
/*! Returns a copy of the applications default printer name or NULL if it
could not be obtained. Caller is responsible to free the string using
free().
*/
char*
BPrintJob::_GetCurrentPrinterName() const
{
BMessenger printServer;
if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
return NULL;
const char* printerName = NULL;
BMessage reply;
BMessage message(PSRV_GET_ACTIVE_PRINTER);
if (printServer.SendMessage(&message, &reply) == B_OK)
reply.FindString("printer_name", &printerName);
if (printerName == NULL)
return NULL;
return strdup(printerName);
}
void
BPrintJob::_LoadDefaultSettings()
{
BMessenger printServer;
if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
return;
BMessage message(PSRV_GET_DEFAULT_SETTINGS);
BMessage* reply = new BMessage;
printServer.SendMessage(&message, reply);
// Only override our settings if we don't have any settings yet
if (fSetupMessage == NULL)
_HandlePrintSetup(reply);
delete fDefaultSetupMessage;
fDefaultSetupMessage = reply;
}
void BPrintJob::_ReservedPrintJob1() {}
void BPrintJob::_ReservedPrintJob2() {}
void BPrintJob::_ReservedPrintJob3() {}
void BPrintJob::_ReservedPrintJob4() {}
// #pragma mark -- PrintServerMessenger
namespace BPrivate {
PrintServerMessenger::PrintServerMessenger(uint32 what, BMessage *input)
:
fWhat(what),
fInput(input),
fRequest(NULL),
fResult(NULL),
fThreadCompleted(-1),
fHiddenApplicationModalWindow(NULL)
{
RejectUserInput();
}
PrintServerMessenger::~PrintServerMessenger()
{
DeleteSemaphore();
// in case SendRequest could not start the thread
delete fRequest; fRequest = NULL;
AllowUserInput();
}
void
PrintServerMessenger::RejectUserInput()
{
fHiddenApplicationModalWindow = new BAlert("bogus", "app_modal", "OK");
fHiddenApplicationModalWindow->DefaultButton()->SetEnabled(false);
fHiddenApplicationModalWindow->SetDefaultButton(NULL);
fHiddenApplicationModalWindow->SetFlags(fHiddenApplicationModalWindow->Flags() | B_CLOSE_ON_ESCAPE);
fHiddenApplicationModalWindow->MoveTo(-65000, -65000);
fHiddenApplicationModalWindow->Go(NULL);
}
void
PrintServerMessenger::AllowUserInput()
{
fHiddenApplicationModalWindow->Lock();
fHiddenApplicationModalWindow->Quit();
}
void
PrintServerMessenger::DeleteSemaphore()
{
if (fThreadCompleted >= B_OK) {
sem_id id = fThreadCompleted;
fThreadCompleted = -1;
delete_sem(id);
}
}
status_t
PrintServerMessenger::SendRequest()
{
fThreadCompleted = create_sem(0, "print_server_messenger_sem");
if (fThreadCompleted < B_OK)
return B_ERROR;
thread_id id = spawn_thread(MessengerThread, "async_request",
B_NORMAL_PRIORITY, this);
if (id <= 0 || resume_thread(id) != B_OK)
return B_ERROR;
// Get the originating window, if it exists
BWindow* window = dynamic_cast<BWindow*>(
BLooper::LooperForThread(find_thread(NULL)));
if (window != NULL) {
status_t err;
while (true) {
do {
err = acquire_sem_etc(fThreadCompleted, 1, B_RELATIVE_TIMEOUT,
50000);
// We've (probably) had our time slice taken away from us
} while (err == B_INTERRUPTED);
// Semaphore was finally nuked in SetResult(BMessage *)
if (err == B_BAD_SEM_ID)
break;
window->UpdateIfNeeded();
}
} else {
// No window to update, so just hang out until we're done.
while (acquire_sem(fThreadCompleted) == B_INTERRUPTED);
}
status_t status;
wait_for_thread(id, &status);
return Result() != NULL ? B_OK : B_ERROR;
}
BMessage*
PrintServerMessenger::Request()
{
if (fRequest != NULL)
return fRequest;
if (fInput != NULL) {
fRequest = new BMessage(*fInput);
fRequest->what = fWhat;
} else
fRequest = new BMessage(fWhat);
return fRequest;
}
void
PrintServerMessenger::SetResult(BMessage* result)
{
fResult = result;
DeleteSemaphore();
// terminate loop in thread spawned by SendRequest
}
status_t
PrintServerMessenger::GetPrintServerMessenger(BMessenger& messenger)
{
messenger = BMessenger(PSRV_SIGNATURE_TYPE);
return messenger.IsValid() ? B_OK : B_ERROR;
}
status_t
PrintServerMessenger::MessengerThread(void* data)
{
PrintServerMessenger* messenger = static_cast<PrintServerMessenger*>(data);
BMessenger printServer;
if (messenger->GetPrintServerMessenger(printServer) != B_OK) {
ShowError(B_TRANSLATE("Print Server is not responding."));
messenger->SetResult(NULL);
return B_ERROR;
}
BMessage* request = messenger->Request();
if (request == NULL) {
messenger->SetResult(NULL);
return B_ERROR;
}
BMessage reply;
if (printServer.SendMessage(request, &reply) != B_OK
|| reply.what != 'okok' ) {
messenger->SetResult(NULL);
return B_ERROR;
}
messenger->SetResult(new BMessage(reply));
return B_OK;
}
} // namespace BPrivate
↑ V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.