#include "printergui.h"
#include "ui_printergui.h"
#include <QMessageBox>
#include <unistd.h>
#include <QTimer>
#include <QStatusBar>
#include <QCloseEvent>
#include <QFileInfo>
#include <pwd.h>
static QStringList knownColorOptions = QStringList() << "ColorModel" << "XRXColor";
static QStringList knownGrayscaleOptions = QStringList() << "HPColorAsGray";
static QStringList knownDuplexOptions = QStringList() << "Duplex";
static QStringList knownPageSizeOptions = QStringList() << "PageSize";
static int isUtf8(const char *s);
static void toLatin1(char *s);
static char* cleanCupsString(const char* in);
// ____________________________________________________________________________
PrinterGui::PrinterGui(char *argv[], QWidget *parent) :
QDialog(parent),
ui(new Ui::PrinterGui),
user(NULL),
statusBar(NULL),
bgTimeout(-1),
jobId(0)
{
// When called it is guaranteed that argv has (at least) 3 elements
// Set username
// Do not use getlogin[_r] as it doesn't fucking work!
struct passwd *pw = getpwuid(getuid());
if (pw != NULL && pw->pw_dir != NULL && pw->pw_dir[0] == '/') {
// Special case - .account file in user home, but only if it is owned by root
QFile file(QString(pw->pw_dir).append("/.account"));
if (file.exists()) {
QFileInfo fi(file);
// If owned by root, not writable by group or other, read user stored inside
if ((fi.permissions() & (QFile::WriteGroup | QFile::WriteOther)) == 0 && fi.owner() == "root" && file.open(QIODevice::ReadOnly)) {
char buffer[20];
qint64 ret = file.readLine(buffer, sizeof(buffer));
if (ret > 0) {
if (buffer[ret - 1] == '\n') {
buffer[ret - 1] = '\0';
}
this->user = strdup(buffer);
}
}
}
}
if (this->user == NULL && pw != NULL && pw->pw_name != NULL && pw->pw_name[0] != '\0') {
// Detect real name
this->user = strdup(pw->pw_name);
}
if (this->user == NULL) {
// Fallback to what command line says
this->user = strdup(argv[1]);
}
// Filename
this->file = new char[strlen(argv[2]) + 10]; // + 10 in case we rename (ghostscript)
strcpy(this->file, argv[2]);
// Initialize cups
num_dests = cupsGetDests(&dests);
// Initialize UI
initializeUI();
// Timer
this->bgTimer = new QTimer(this);
connect(bgTimer, SIGNAL(timeout()), this, SLOT(bgTimer_timeout()));
this->bgTimer->start(1000);
ui->txtUserId->setText(this->user);
}
// ____________________________________________________________________________
PrinterGui::~PrinterGui()
{
QMap<QTreeWidgetItem*, ppd_file_t*>::const_iterator i = ppds.constBegin();
while (i != ppds.constEnd()) {
ppdClose(i.value());
}
ppds.clear();
cupsFreeDests(num_dests, dests);
free(this->user);
delete[] this->file;
delete this->ui;
}
// ____________________________________________________________________________
void PrinterGui::initializeUI()
{
ui->setupUi(this);
this->setWindowModality(Qt::ApplicationModal);
// Put always on top
this->setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);
ui->horizontalLayoutButtons->setAlignment(Qt::AlignRight);
ui->comboBoxColor->setEnabled(false);
ui->comboBoxSides->setEnabled(false);
ui->label_color->setEnabled(false);
ui->label_duplex->setEnabled(false);
// Create a status bar (qt designer doesn't support this for dialogs)
statusBar = new QStatusBar(this);
ui->statusBarLayout->addWidget(statusBar);
/* Initialize Treeview */
ui->printerList->setColumnCount(3);
//ui->printerList->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum);
// Rename headers
QStringList h;
h.append("Drucker");
h.append("Information");
h.append("Standort");
ui->printerList->setHeaderLabels(h);
// Hide trusted print by default
handleTrustedPrint(NULL);
// Fill treewidget with data from cups dests;
cups_dest_t *dest = dests;
for (int i = num_dests; i > 0; ++dest, --i) {
if (dest->instance == NULL) {
char uri[1000];
ipp_t *req = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
"localhost", ippPort(), "/printers/%s", dest->name);
ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
NULL, uri);
ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_NAME,
"requesting-user-name",
NULL, cupsUser());
ipp_t *res = cupsDoRequest(CUPS_HTTP_DEFAULT, req, "/");
ipp_attribute_t *attr = ippFindAttribute(res, "printer-location", IPP_TAG_TEXT);
const char *location = "";
if (attr != NULL) {
location = ippGetString(attr, 0, NULL);
if (location == NULL) {
location = "";
}
}
QTreeWidgetItem *wi = new QTreeWidgetItem();
wi->setText(0, QString::fromUtf8(dest->name));
wi->setText(1, QString::fromUtf8(
cupsGetOption("printer-info", dest->num_options, dest->options)));
wi->setText(2, QString::fromUtf8(location));
ui->printerList->addTopLevelItem(wi);
if (dest->is_default) {
ui->printerList->setCurrentItem(wi);
}
ippDelete(res);
}
}
// Resize columns to contents
for (int i = 0; i < 3; ++i) {
ui->printerList->resizeColumnToContents(i);
}
/* Main Window properties */
// center dialog on screen center
QRect desktopRect = QApplication::desktop()->screenGeometry(this);
this->move( desktopRect.width()/2-this->width()/2,
desktopRect.height()/2-this->height()/2);
const char *docName;
docName = getenv("J");
if (docName == NULL) {
docName = getenv("N");
}
if (docName == NULL) {
docName = "Untitled";
}
this->setWindowTitle(QString::fromUtf8("Drucken - %1 [%2]").arg(this->user, QString::fromUtf8(docName)));
this->show();
this->showNormal();
this->raise();
this->activateWindow();
}
void PrinterGui::handleTrustedPrint(ppd_file_t *ppd)
{
bool supported = false;
bool enabled = false;
QString pwProperty, pwMask;
static QRegExpValidator sevenNum(QRegExp("^[0-9]{1,7}$"));
QRegExpValidator *pwValidator = NULL;
if (ppd != NULL) {
for (ppd_option_t *o = ppdFirstOption(ppd); o != NULL; o = ppdNextOption(ppd)) {
if (strcmp(o->keyword, "UseSecure") == 0) {
supported = true;
enabled = (strcmp(o->defchoice, "On") == 0);
} else if (strcmp(o->keyword, "SecuredPassword") == 0) {
pwProperty = o->keyword;
pwValidator = &sevenNum;
} else if (strcmp(o->keyword, "UserPassword") == 0) {
if (pwProperty.isEmpty()) {
pwProperty = o->keyword;
pwValidator = &sevenNum;
}
}
}
}
ui->txtUserPin->setProperty("key", pwProperty);
ui->txtUserPin->setValidator(pwValidator);
ui->chkSecurePrint->setChecked(enabled);
for (int i = 0; i < ui->gridSecurePrint->count(); ++i) {
QWidget *child = ui->gridSecurePrint->itemAt(i)->widget();
if (child != NULL) {
child->setVisible(supported);
}
}
}
void PrinterGui::on_chkSecurePrint_stateChanged(int state)
{
ui->txtUserId->setEnabled(state == Qt::Checked);
ui->txtUserPin->setEnabled(state == Qt::Checked);
if (state == Qt::Checked) {
ui->txtUserPin->setFocus();
}
}
static bool enableOptionSelection(ppd_file_t *ppd, QStringList &nameList, QComboBox *combo, QLabel *label, bool forceDisabled)
{
// Check for each option if it matches an
QString matchedProperty;
for (ppd_option_t *o = ppdFirstOption(ppd); o != NULL; o = ppdNextOption(ppd)) {
QString option(o->keyword);
for (QStringList::iterator it = nameList.begin(); it != nameList.end(); ++it) {
bool exact = (option == *it);
if((!exact && !matchedProperty.isEmpty()) || !option.contains(*it))
continue;
// Matches, update combo
matchedProperty = option;
combo->setUpdatesEnabled(false);
combo->clear();
for (int i = 0; i < o->num_choices; ++i) {
combo->addItem(QString::fromUtf8(o->choices[i].text), QString::fromLatin1(o->choices[i].choice));
if (strcmp(o->choices[i].choice, o->defchoice) == 0) {
combo->setCurrentIndex(i);
}
}
combo->setUpdatesEnabled(true);
combo->adjustSize();
if (exact) {
break;
}
}
}
combo->setProperty("key", matchedProperty);
const bool selectable = !matchedProperty.isEmpty() && !forceDisabled;
combo->setEnabled(selectable);
label->setEnabled(selectable);
return selectable;
}
// ____________________________________________________________________________
void PrinterGui::on_printerList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{
ui->printerList->setFocus();
cups_dest_t *dest = cupsGetDest(
ui->printerList->currentItem()->text(0).toUtf8().constData(),
NULL,
num_dests,
dests);
/* * Check printer properties (auth, color, duplex, copies) * */
// get printer capabilities
const char *type = cupsGetOption("printer-type", dest->num_options, dest->options);
int res = 0;
if (type != NULL) {
res = ::atoi(type);
}
ppd_file_t *ppd = NULL;
if (ppds.contains(current)) {
ppd = ppds[current];
} else {
const char *ppdFile = cupsGetPPD2(CUPS_HTTP_DEFAULT, dest->name);
if (ppdFile == NULL) {
qDebug() << "cupsGetPPD2 for " << dest->name << " returned NULL: " << cupsLastErrorString();
} else {
ppd = ppdOpenFile(ppdFile);
qDebug() << dest->name << " has ppd " << ppdFile;
}
ppds.insert(current, ppd);
}
if (ppd != NULL) {
// Check color capabilities
//if (res & CUPS_PRINTER_COLOR) // No needed? Should just get disabled either way
if (enableOptionSelection(ppd, knownGrayscaleOptions, ui->comboBoxColor, ui->label_color, false)) {
ui->label_color->setText(trUtf8("Graustufen"));
} else if (enableOptionSelection(ppd, knownColorOptions, ui->comboBoxColor, ui->label_color, false)) {
ui->label_color->setText(trUtf8("Farbe"));
}
enableOptionSelection(ppd, knownDuplexOptions, ui->comboBoxSides, ui->label_duplex, false);
// TODO: Make it so this selection overrides what the document specifies
enableOptionSelection(ppd, knownPageSizeOptions, ui->cboPaperSize, ui->lblPaperSize, true);
handleTrustedPrint(ppd);
} else {
qDebug() << "ppd is null for "<< dest->name << "; cups last error: " << cupsLastErrorString();
}
// Check copy capabilities
if (res & CUPS_PRINTER_COPIES) {
ui->lineEditCopies->setEnabled(true);
ui->labelCopies->setEnabled(true);
} else {
ui->lineEditCopies->setEnabled(false);
ui->lineEditCopies->setText("1");
ui->labelCopies->setEnabled(false);
}
// Check availability
if (res & CUPS_PRINTER_REJECTING) {
ui->buttonPrint->setEnabled(false);
statusBar->showMessage("Dieser Drucker nimmt zur Zeit keine Aufträge an");
} else {
ui->buttonPrint->setEnabled(true);
ui->buttonPrint->setDefault(true);
statusBar->clearMessage();
}
}
void PrinterGui::closeEvent(QCloseEvent * e)
{
if (e->isAccepted()) {
if (jobId != 0) {
cupsCancelJob(ui->printerList->currentItem()->text(0).toUtf8().constData(), jobId);
jobId = 0;
}
QCoreApplication::instance()->exit(0);
}
QDialog::closeEvent(e);
}
void PrinterGui::keyPressEvent(QKeyEvent * e)
{
if(e->key() != Qt::Key_Escape) {
QDialog::keyPressEvent(e);
return;
}
this->close();
}
void PrinterGui::hideEvent(QHideEvent * e)
{
this->close();
}
// ____________________________________________________________________________
void PrinterGui::on_buttonCancel_clicked()
{
this->close();
}
// ____________________________________________________________________________
void PrinterGui::on_buttonPrint_clicked()
{
QString cmd;
/* * Print via cups lib * */
// Destination / Queue
cups_dest_t *dest = cupsGetDest(
ui->printerList->currentItem()->text(0).toUtf8().constData(),
NULL,
num_dests,
dests);
// In dest we have the default options
// Color
QString colorKey = ui->comboBoxColor->property("key").toString();
if (!colorKey.isEmpty()) {
dest->num_options = cupsAddOption(colorKey.toUtf8().constData(),
ui->comboBoxColor->itemData(ui->comboBoxColor->currentIndex()).toString().toUtf8().constData(),
dest->num_options,
&(dest->options));
}
// Duplex
QString duplexKey = ui->comboBoxSides->property("key").toString();
if (!duplexKey.isEmpty()) {
dest->num_options = cupsAddOption(duplexKey.toUtf8().constData(),
ui->comboBoxSides->itemData(ui->comboBoxSides->currentIndex()).toString().toUtf8().constData(),
dest->num_options,
&(dest->options));
}
// Paper size
QString paperKey = ui->cboPaperSize->property("key").toString();
if (!paperKey.isEmpty()) {
dest->num_options = cupsAddOption(paperKey.toUtf8().constData(),
ui->cboPaperSize->itemData(ui->cboPaperSize->currentIndex()).toString().toUtf8().constData(),
dest->num_options,
&(dest->options));
}
// Kopien
if (ui->lineEditCopies->isEnabled()) {
dest->num_options = cupsAddOption("copies",
ui->lineEditCopies->text().toUtf8().constData(),
dest->num_options,
&(dest->options));
}
dest->num_options = cupsRemoveOption("InputSlot", dest->num_options, &(dest->options));
// If we use secured print, allow overriding username
const char *securedMode = "Off";
if (ui->chkSecurePrint->isHidden() || !ui->chkSecurePrint->isChecked()) {
cupsSetUser(this->user);
} else {
securedMode = "On";
if (ui->txtUserId->text().isEmpty()) {
cupsSetUser(this->user);
} else {
cupsSetUser(ui->txtUserId->text().toUtf8().constData());
}
const QString pinKey = ui->txtUserPin->property("key").toString();
dest->num_options = cupsAddOption(pinKey.toUtf8().constData(),
ui->txtUserPin->text().toUtf8().constData(),
dest->num_options,
&(dest->options));
}
dest->num_options = cupsAddOption("UseSecured",
securedMode,
dest->num_options,
&(dest->options));
dest->num_options = cupsAddOption("OptSecured",
securedMode,
dest->num_options,
&(dest->options));
char jobtitle[250];
const char *docName;
docName = getenv("J");
if (docName == NULL) {
docName = getenv("N");
}
if (docName == NULL) {
docName = "Untitled";
}
char *cleanUser = cleanCupsString(this->user);
char *cleanTitle = cleanCupsString(docName);
snprintf(jobtitle, sizeof(jobtitle), "gui-%d-%s (%s)", (int)getpid(), cleanUser, cleanTitle);
free(cleanUser);
free(cleanTitle);
// Drucken
jobId = cupsPrintFile(dest->name,
file,
jobtitle,
dest->num_options,
dest->options);
if (jobId == 0) {
QMessageBox::critical(this, "CUPS Fehler", cupsLastErrorString());
} else {
this->bgTimeout = 0;
statusBar->showMessage("Druckauftrag wird verarbeitet...");
ui->buttonPrint->setEnabled(false);
ui->cboPaperSize->setEnabled(false);
ui->comboBoxColor->setEnabled(false);
ui->comboBoxSides->setEnabled(false);
ui->lineEditCopies->setEnabled(false);
ui->printerList->setEnabled(false);
}
}
void PrinterGui::bgTimer_timeout()
{
if (this->bgTimeout == -1) {
// Could do something here every second....
this->raise();
this->activateWindow();
return;
}
if (++this->bgTimeout > 120) {
// Job was sent, GUI is invisible, quit after a few seconds
QCoreApplication::instance()->exit(0);
}
}
/**
* Checks if the given c string is UTF-8.
* Using as bool-function:
* Return value != 0 means (potentially) UTF-8,
* that is, it can be processed by UTF-8 functions without error.
* Using for in-detail detection:
* Return value 0 = not valid UTF-8
* Return value 1 = valid ASCII (which is a subset of UTF-8)
* Return value 2 = valid UTF-8, but not valid ASCII.
*/
static int isUtf8(const char *s)
{
bool isAscii = true;
while (*s != '\0') {
if ((s[0] & 248) == 240 &&
(s[1] & 192) == 128 &&
(s[2] & 192) == 128 &&
(s[3] & 192) == 128) {
s += 4;
isAscii = false;
} else if ((s[0] & 240) == 224 &&
(s[1] & 192) == 128 &&
(s[2] & 192) == 128) {
s += 3;
isAscii = false;
} else if ((s[0] & 224) == 192 &&
(s[1] & 192) == 128) {
s += 2;
isAscii = false;
} else if ((s[0] & 128) != 0) {
// Invalid UTF-8 sequence, so no ASCII either
return 0;
} else {
s+= 1;
}
}
return isAscii ? 1 : 2;
}
static void toLatin1(char *s)
{
char *d = s;
while (*s != '\0') {
if ((s[0] & 248) == 240 &&
(s[1] & 192) == 128 &&
(s[2] & 192) == 128 &&
(s[3] & 192) == 128) {
d[0] = '_';
s += 4;
} else if ((s[0] & 240) == 224 &&
(s[1] & 192) == 128 &&
(s[2] & 192) == 128) {
d[0] = '_';
s += 3;
} else if ((s[0] & 224) == 192 &&
(s[1] & 192) == 128) {
int c = ((s[0] & 31) << 6) | (s[1] & 63);
if (c > 255) {
d[0] = '_';
} else {
d[0] = (char)c;
}
s += 2;
} else if ((s[0] & 128) != 0) {
// Invalid UTF-8 sequence!?
d[0] = '_';
s+= 1;
} else {
d[0] = s[0];
s+= 1;
}
d += 1;
}
*d = '\0';
}
static char* cleanCupsString(const char* in)
{
int enc = isUtf8(in);
char *out = strdup(in);
if (enc == 2) {
// UTF-8, turn to Latin-1
toLatin1(out);
}
// Try to convert umlauts etc., remove non-printing chars
unsigned char *tmp = (unsigned char*)out;
while (*tmp != '\0') {
if (*tmp < 0x20 || *tmp > 0x7E) {
switch (*tmp) {
case 0x84: // „
case 0x93: // “
case 0x94: // ”
case 0xAB: // «
case 0xBB: // »
*tmp = '"';
break;
case 0x91: // ‘
case 0x92: // ’
case 0xB4: // ´
*tmp = '\'';
break;
case 0xB7: // ·
*tmp = '*';
break;
case 0xF7: // ÷
*tmp = '/';
break;
case 0x82: // ‚
case 0xB8: // ¸
*tmp = ',';
break;
case 0x96: // –
case 0x97: // —
case 0xAC: // ¬
*tmp = '-';
break;
case 0xAF: // ¯
case 0xB0: // °
*tmp = '^';
break;
case 0x98: // ˜
*tmp = '~';
break;
case 0xB9: // ¹
*tmp = '1';
break;
case 0xB2: // ²
*tmp = '2';
break;
case 0xB3: // ³
*tmp = '3';
break;
case 0xC0: // À
case 0xC1: // Á
case 0xC2: // Â
case 0xC3: // Ã
case 0xC4: // Ä
case 0xC5: // Å
case 0xC6: // Æ
*tmp = 'A';
break;
case 0xAA: // ª
case 0xE0: // à
case 0xE1: // á
case 0xE2: // â
case 0xE3: // ã
case 0xE4: // ä
case 0xE5: // å
case 0xE6: // æ
*tmp = 'a';
break;
case 0xA9: // ©
case 0xC7: // Ç
*tmp = 'C';
break;
case 0xA2: // ¢
case 0xE7: // ç
*tmp = 'c';
break;
case 0xD0: // Ð
*tmp = 'D';
break;
case 0xF0: // ð
*tmp = 'd';
break;
case 0xC8: // È
case 0xC9: // É
case 0xCA: // Ê
case 0xCB: // Ë
*tmp = 'E';
break;
case 0xE8: // è
case 0xE9: // é
case 0xEA: // ê
case 0xEB: // ë
*tmp = 'e';
break;
case 0x83: // ƒ
*tmp = 'f';
break;
case 0xCC: // Ì
case 0xCD: // Í
case 0xCE: // Î
case 0xCF: // Ï
*tmp = 'I';
break;
case 0xA1: // ¡
case 0xEC: // ì
case 0xED: // í
case 0xEE: // î
case 0xEF: // ï
*tmp = 'i';
break;
case 0xD1: // Ñ
*tmp = 'N';
break;
case 0xF1: // ñ
*tmp = 'n';
break;
case 0xD2: // Ò
case 0xD3: // Ó
case 0xD4: // Ô
case 0xD5: // Õ
case 0xD6: // Ö
case 0xD8: // Ø
*tmp = 'O';
break;
case 0xF2: // ò
case 0xF3: // ó
case 0xF4: // ô
case 0xF5: // õ
case 0xF6: // ö
case 0xF8: // ø
*tmp ='o';
break;
case 0xA3: // £
case 0xA7: // §
*tmp = 'P';
break;
case 0xAE: // ®
*tmp = 'R';
break;
case 0x8A: // Š
*tmp = 'S';
break;
case 0x9A: // š
case 0xDF: // ß
*tmp = 's';
break;
case 0xDE: // Þ
*tmp = 'T';
break;
case 0xFE: // þ
*tmp = 't';
break;
case 0xD9: // Ù
case 0xDA: // Ú
case 0xDB: // Û
case 0xDC: // Ü
*tmp = 'U';
break;
case 0xB5: // µ
case 0xF9: // ù
case 0xFA: // ú
case 0xFB: // û
case 0xFC: // ü
*tmp = 'u';
break;
case 0xD7: // ×
*tmp = 'x';
break;
case 0xA5: // ¥
case 0xDD: // Ý
*tmp = 'Y';
break;
case 0xFD: // ý
case 0xFF: // ÿ
*tmp = 'y';
break;
default:
*tmp = '.';
break;
}
}
tmp++;
}
return out;
}