#include "printergui.h" #include "ui_printergui.h" #include #include #include #include #include #include #include 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::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); 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; }