2
* Copyright (C) 2017 Canonical, Ltd.
3
* Copyright (C) 2014 John Layt <jlayt@kde.org>
4
* Copyright (C) 2002, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014 Red Hat, Inc.
5
* Copyright (C) 2008 Novell, Inc.
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU Lesser General Public License as published by
9
* the Free Software Foundation; version 3.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU Lesser General Public License for more details.
16
* You should have received a copy of the GNU Lesser General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20
#include "cups/ippclient.h"
31
IppClient::IppClient()
32
: m_connection(httpConnectEncrypt(cupsServer(),
37
qCritical("Failed to connect to cupsd");
39
qDebug("Successfully connected to cupsd.");
43
IppClient::~IppClient()
46
httpClose(m_connection);
49
bool IppClient::printerDelete(const QString &printerName)
51
return sendNewSimpleRequest(CUPS_DELETE_PRINTER, printerName.toUtf8(),
52
CupsResource::CupsResourceAdmin);
55
bool IppClient::printerAdd(const QString &printerName,
56
const QString &printerUri,
57
const QString &ppdFile,
59
const QString &location)
63
if (!isPrinterNameValid(printerName)) {
64
setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
68
if (!isStringValid(info)) {
69
setInternalStatus(QString("%1 is not a valid description.").arg(info));
73
if (!isStringValid(location)) {
74
setInternalStatus(QString("%1 is not a valid location.").arg(location));
78
if (!isStringValid(ppdFile)) {
79
setInternalStatus(QString("%1 is not a valid ppd file.").arg(ppdFile));
83
if (!isStringValid(printerUri)) {
84
setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
89
request = ippNewRequest (CUPS_ADD_MODIFY_PRINTER);
90
addPrinterUri(request, printerName);
91
addRequestingUsername(request, NULL);
93
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
94
"printer-name", NULL, printerName.toUtf8());
96
if (!ppdFile.isEmpty()) {
97
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
98
"ppd-name", NULL, ppdFile.toUtf8());
100
if (!printerUri.isEmpty()) {
101
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
102
"device-uri", NULL, printerUri.toUtf8());
104
if (!info.isEmpty()) {
105
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
106
"printer-info", NULL, info.toUtf8());
108
if (!location.isEmpty()) {
109
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
110
"printer-location", NULL, location.toUtf8());
113
return sendRequest(request, CupsResourceAdmin);
116
bool IppClient::printerAddWithPpdFile(const QString &printerName,
117
const QString &printerUri,
118
const QString &ppdFileName,
120
const QString &location)
124
if (!isPrinterNameValid(printerName)) {
125
setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
129
if (!isStringValid(info)) {
130
setInternalStatus(QString("%1 is not a valid description.").arg(info));
134
if (!isStringValid(location)) {
135
setInternalStatus(QString("%1 is not a valid location.").arg(location));
139
if (!isStringValid(ppdFileName)) {
140
setInternalStatus(QString("%1 is not a valid ppd file name.").arg(ppdFileName));
144
if (!isStringValid(printerUri)) {
145
setInternalStatus(QString("%1 is not a valid printer uri.").arg(printerUri));
149
request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
150
addPrinterUri(request, printerName);
151
addRequestingUsername(request, NULL);
153
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
154
"printer-name", NULL, printerName.toUtf8());
156
/* In this specific case of ADD_MODIFY, the URI can be NULL/empty since
157
* we provide a complete PPD. And cups fails if we pass an empty
159
if (!printerUri.isEmpty()) {
160
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI,
161
"device-uri", NULL, printerUri.toUtf8());
164
if (!info.isEmpty()) {
165
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
166
"printer-info", NULL, info.toUtf8());
168
if (!location.isEmpty()) {
169
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT,
170
"printer-location", NULL, location.toUtf8());
173
return postRequest(request, ppdFileName.toUtf8(), CupsResourceAdmin);
176
bool IppClient::printerSetDefault(const QString &printerName)
178
return sendNewSimpleRequest(CUPS_SET_DEFAULT, printerName.toUtf8(),
179
CupsResource::CupsResourceAdmin);
182
bool IppClient::printerSetEnabled(const QString &printerName,
186
op = enabled ? IPP_RESUME_PRINTER : IPP_PAUSE_PRINTER;
187
return sendNewSimpleRequest(op, printerName, CupsResourceAdmin);
190
/* reason must be empty if accept is true */
191
bool IppClient::printerSetAcceptJobs(const QString &printerName,
193
const QString &reason)
197
if (accept && !reason.isEmpty()) {
198
setInternalStatus("Accepting jobs does not take a reason.");
202
if (!isPrinterNameValid(printerName)) {
203
setInternalStatus(QString("%1 is not a valid printer name.").arg(printerName));
207
if (!isStringValid(reason)) {
208
setInternalStatus(QString("%1 is not a valid reason.").arg(reason));
213
return sendNewSimpleRequest(CUPS_ACCEPT_JOBS, printerName.toUtf8(),
216
request = ippNewRequest(CUPS_REJECT_JOBS);
217
addPrinterUri(request, printerName);
218
addRequestingUsername(request, NULL);
220
if (!reason.isEmpty())
221
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
222
"printer-state-message", NULL, reason.toUtf8());
224
return sendRequest(request, CupsResourceAdmin);
229
bool IppClient::printerClassSetInfo(const QString &name,
232
if (!isPrinterNameValid(name)) {
233
setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
237
if (!isStringValid(info)) {
238
setInternalStatus(QString("%1 is not a valid description.").arg(info));
242
return sendNewPrinterClassRequest(name, IPP_TAG_PRINTER, IPP_TAG_TEXT,
243
"printer-info", info);
246
bool IppClient::printerClassSetOption(const QString &name,
247
const QString &option,
248
const QStringList &values)
253
ipp_attribute_t *attr;
257
if (!isPrinterNameValid(name)) {
258
setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
262
if (!isStringValid(option)) {
263
setInternalStatus(QString("%1 is not a valid option.").arg(option));
267
Q_FOREACH(const QString &val, values) {
268
if (!isStringValid(val)) {
269
setInternalStatus(QString("%1 is not a valid value.").arg(val));
276
setInternalStatus("No valid values.");
280
isClass = printerIsClass(name);
282
/* We permit only one value to change in PPD file because we are setting
283
* default value in it. */
284
if (!isClass && length == 1) {
285
cups_option_t *options = NULL;
289
numOptions = cupsAddOption(option.toUtf8(),
291
numOptions, &options);
293
ppdfile = QString(cupsGetPPD(name.toUtf8()));
295
newPpdFile = preparePpdForOptions(ppdfile.toUtf8(),
296
options, numOptions).toLatin1().data();
298
unlink(ppdfile.toUtf8());
299
cupsFreeOptions(numOptions, options);
303
request = ippNewRequest(CUPS_ADD_MODIFY_CLASS);
304
addClassUri(request, name);
306
request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
307
addPrinterUri(request, name);
310
addRequestingUsername(request, NULL);
313
ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
320
attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
321
option.toUtf8(), length, NULL, NULL);
323
for (i = 0; i < length; i++)
324
ippSetString(request, &attr, i, values[i].toUtf8());
327
if (!newPpdFile.isEmpty()) {
328
retval = postRequest(request, newPpdFile, CupsResourceAdmin);
330
unlink(newPpdFile.toUtf8());
331
// TODO: fix leak here.
333
retval = sendRequest(request, CupsResourceAdmin);
339
QMap<QString, QVariant> IppClient::printerGetJobAttributes(const int jobId)
342
QMap<QString, QVariant> map;
345
request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);
346
QString uri = QStringLiteral("ipp://localhost/jobs/") + QString::number(jobId);
347
qDebug() << "URI:" << uri;
349
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri.toStdString().data());
352
// Send request and construct reply
354
const QString resourceChar = getResource(CupsResourceRoot);
355
reply = cupsDoRequest(m_connection, request,
356
resourceChar.toUtf8());
358
// Check if the reply is OK
359
if (isReplyOk(reply, false)) {
360
// Loop through the attributes
361
ipp_attribute_t *attr;
363
for (attr = ippFirstAttribute(reply); attr; attr = ippNextAttribute(reply)) {
364
QVariant value = getAttributeValue(attr);
365
map.insert(ippGetName(attr), value);
368
qWarning() << "Not able to get attributes of job:" << jobId;
371
// Destruct the reply if valid
380
/* This function sets given options to specified values in file 'ppdfile'.
381
* This needs to be done because of applications which use content of PPD files
382
* instead of IPP attributes.
383
* CUPS doesn't do this automatically (but hopefully will starting with 1.6) */
384
QString IppClient::preparePpdForOptions(const QString &ppdfile,
385
cups_option_t *options, int numOptions)
387
auto ppdfile_c = ppdfile.toUtf8();
389
bool ppdchanged = false;
392
char newppdfile[PATH_MAX];
393
cups_file_t *in = NULL;
394
cups_file_t *out = NULL;
395
char line[CPH_STR_MAXLEN];
396
char keyword[CPH_STR_MAXLEN];
398
ppd_choice_t *choice;
400
QLatin1String defaultStr("*Default");
402
ppd = ppdOpenFile(ppdfile_c);
404
error = QString("Unable to open PPD file \"%1\": %2")
405
.arg(ppdfile).arg(strerror(errno));
406
setInternalStatus(error);
410
in = cupsFileOpen(ppdfile_c, "r");
412
error = QString("Unable to open PPD file \"%1\": %2")
413
.arg(ppdfile).arg(strerror(errno));
414
setInternalStatus(error);
418
out = cupsTempFile2(newppdfile, sizeof(newppdfile));
420
setInternalStatus("Unable to create temporary file");
424
/* Mark default values and values of options we are changing. */
425
ppdMarkDefaults(ppd);
426
cupsMarkOptions(ppd, numOptions, options);
428
while (cupsFileGets(in, line, sizeof(line))) {
429
QString line_qs(line);
430
if (!line_qs.startsWith(defaultStr)) {
431
cupsFilePrintf(out, "%s\n", line);
433
/* This part parses lines with *Default on their
434
* beginning. For instance:
435
* "*DefaultResolution: 1200dpi" becomes:
436
* - keyword: Resolution
439
strncpy(keyword, line + defaultStr.size(), sizeof(keyword));
441
for (keyptr = keyword; *keyptr; keyptr++)
442
if (*keyptr == ':' || isspace (*keyptr & 255))
446
while (isspace (*keyptr & 255))
449
QString keyword_sq(keyword);
450
QString keyptr_qs(keyptr);
452
/* We have to change PageSize if any of PageRegion,
453
* PageSize, PaperDimension or ImageableArea changes.
454
* We change PageRegion if PageSize is not available. */
455
if (keyword_sq == "PageRegion" ||
456
keyword_sq == "PageSize" ||
457
keyword_sq == "PaperDimension" ||
458
keyword_sq == "ImageableArea") {
460
choice = ppdFindMarkedChoice(ppd, "PageSize");
462
choice = ppdFindMarkedChoice(ppd, "PageRegion");
464
choice = ppdFindMarkedChoice(ppd, keyword);
470
choice_qs = choice->choice;
473
if (choice && choice_qs != keyptr_qs) {
474
/* We have to set the value in PPD manually if
475
* a custom value was passed in:
476
* cupsMarkOptions() marks the choice as
477
* "Custom". We want to set this value with our
479
if (choice_qs != "Custom") {
486
value = cupsGetOption(keyword, numOptions, options);
487
if (!value.isEmpty()) {
491
value.toStdString().c_str());
494
cupsFilePrintf(out, "%s\n", line);
498
cupsFilePrintf(out, "%s\n", line);
504
result = QString::fromUtf8(newppdfile);
520
bool IppClient::sendNewPrinterClassRequest(const QString &printerName,
521
ipp_tag_t group, ipp_tag_t type,
523
const QString &value)
527
request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER);
528
addPrinterUri(request, printerName);
529
addRequestingUsername(request, QString());
530
ippAddString(request, group, type, name.toUtf8(), NULL,
533
if (sendRequest(request, CupsResource::CupsResourceAdmin))
536
// it failed, maybe it was a class?
537
if (m_lastStatus != IPP_NOT_POSSIBLE) {
541
// TODO: implement class modification <here>.
545
void IppClient::addPrinterUri(ipp_t *request, const QString &name)
547
QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
548
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
549
"printer-uri", NULL, uri.toEncoded().data());
552
void IppClient::addRequestingUsername(ipp_t *request, const QString &username)
554
if (!username.isEmpty())
555
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
556
"requesting-user-name", NULL,
559
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
560
"requesting-user-name", NULL, cupsUser());
563
QString IppClient::getLastError() const
565
return m_internalStatus;
568
const QString IppClient::getResource(const IppClient::CupsResource &resource)
571
case CupsResourceRoot:
573
case CupsResourceAdmin:
575
case CupsResourceJobs:
578
qCritical("Asking for a resource with no match.");
583
bool IppClient::isPrinterNameValid(const QString &name)
588
/* Quoting the lpadmin man page:
589
* CUPS allows printer names to contain any printable character
590
* except SPACE, TAB, "/", or "#".
591
* On top of that, validate_name() in lpadmin.c (from cups) checks that
592
* the string is 127 characters long, or shorter. */
594
/* no empty string */
599
/* no string that is too long; see comment at the beginning of the
600
* validation code block */
604
/* only printable characters, no space, no /, no # */
605
for (i = 0; i < len; i++) {
606
const QChar c = name.at(i);
611
if (c == '/' || c == '#')
617
bool IppClient::isStringValid(const QString &string, const bool checkNull,
620
if (isStringPrintable(string, checkNull, maxLength))
625
bool IppClient::isStringPrintable(const QString &string, const bool checkNull,
636
if (maxLength > 0 && len > maxLength)
639
/* only printable characters */
640
for (i = 0; i < len; i++) {
641
const QChar c = string.at(i);
648
void IppClient::setInternalStatus(const QString &status)
650
if (!m_internalStatus.isNull()) {
651
m_internalStatus = QString::null;
654
if (status.isNull()) {
655
m_internalStatus = QString::null;
657
m_internalStatus = status;
659
// Only used for errors for now.
660
qCritical() << status;
664
bool IppClient::postRequest(ipp_t *request, const QString &file,
665
const CupsResource &resource)
668
QString resourceChar;
670
resourceChar = getResource(resource);
673
reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
676
reply = cupsDoFileRequest(m_connection, request, resourceChar.toUtf8(),
679
return handleReply(reply);
683
bool IppClient::sendRequest(ipp_t *request, const CupsResource &resource)
686
const QString resourceChar = getResource(resource);
687
reply = cupsDoRequest(m_connection, request,
688
resourceChar.toUtf8());
689
return handleReply(reply);
692
bool IppClient::sendNewSimpleRequest(ipp_op_t op, const QString &printerName,
693
const IppClient::CupsResource &resource)
697
if (!isPrinterNameValid(printerName))
700
request = ippNewRequest(op);
701
addPrinterUri(request, printerName);
702
addRequestingUsername(request, NULL);
704
return sendRequest(request, resource);
707
bool IppClient::handleReply(ipp_t *reply)
710
retval = isReplyOk(reply, false);
717
bool IppClient::isReplyOk(ipp_t *reply, bool deleteIfReplyNotOk)
719
/* reset the internal status: we'll use the cups status */
720
m_lastStatus = IPP_STATUS_CUPS_INVALID;
722
if (reply && ippGetStatusCode(reply) <= IPP_OK_CONFLICT) {
723
m_lastStatus = IPP_OK;
726
setErrorFromReply(reply);
727
qWarning() << Q_FUNC_INFO << "Cups HTTP error:" << cupsLastErrorString();
729
if (deleteIfReplyNotOk && reply)
736
void IppClient::setErrorFromReply(ipp_t *reply)
739
m_lastStatus = ippGetStatusCode(reply);
741
m_lastStatus = cupsLastError();
744
bool IppClient::printerIsClass(const QString &name)
746
const char * const attrs[1] = { "member-names" };
752
// Class/Printer name validation is equal.
753
if (!isPrinterNameValid(name)) {
754
setInternalStatus(QString("%1 is not a valid printer name.").arg(name));
758
request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
759
addClassUri(request, name);
760
addRequestingUsername(request, QString());
761
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
762
"requested-attributes", 1, NULL, attrs);
764
resource = getResource(CupsResource::CupsResourceRoot);
765
reply = cupsDoRequest(m_connection, request, resource.toUtf8());
767
if (!isReplyOk(reply, true))
770
/* Note: we need to look if the attribute is there, since we get a
771
* reply if the name is a printer name and not a class name. The
772
* attribute is the only way to distinguish the two cases. */
773
retval = ippFindAttribute(reply, attrs[0], IPP_TAG_NAME) != NULL;
781
void IppClient::addClassUri(ipp_t *request, const QString &name)
783
QUrl uri(QString("ipp://localhost/printers/%1").arg(name));
784
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
785
"printer-uri", NULL, uri.toEncoded().data());
788
ppd_file_t* IppClient::getPpdFile(const QString &name,
789
const QString &instance) const
793
ppd_file_t* file = 0;
794
const char *ppdFile = cupsGetPPD(name.toUtf8());
796
file = ppdOpenFile(ppdFile);
800
ppdMarkDefaults(file);
808
cups_dest_t* IppClient::getDest(const QString &name,
809
const QString &instance) const
811
cups_dest_t *dest = 0;
813
if (instance.isEmpty()) {
814
dest = cupsGetNamedDest(m_connection, name.toUtf8(), NULL);
816
dest = cupsGetNamedDest(m_connection, name.toUtf8(), instance.toUtf8());
821
ipp_t* IppClient::createPrinterDriversRequest(
822
const QString &deviceId, const QString &language, const QString &makeModel,
823
const QString &product, const QStringList &includeSchemes,
824
const QStringList &excludeSchemes
827
Q_UNUSED(includeSchemes);
828
Q_UNUSED(excludeSchemes);
832
request = ippNewRequest(CUPS_GET_PPDS);
834
if (!deviceId.isEmpty())
835
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-device-id",
836
NULL, deviceId.toUtf8());
837
if (!language.isEmpty())
838
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "ppd-language",
839
NULL, language.toUtf8());
840
if (!makeModel.isEmpty())
841
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-make-and-model",
842
NULL, makeModel.toUtf8());
843
if (!product.isEmpty())
844
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT, "ppd-product",
845
NULL, product.toUtf8());
847
// Do the request and get return the response.
848
const QString resourceChar = getResource(CupsResourceRoot);
849
return cupsDoRequest(m_connection, request,
850
resourceChar.toUtf8());
853
int IppClient::createSubscription()
857
ipp_attribute_t *attr;
858
int subscriptionId = -1;
860
req = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION);
861
ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
862
"printer-uri", NULL, "/");
863
ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
864
"notify-events", NULL, "all");
865
ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI,
866
"notify-recipient-uri", NULL, "dbus://");
867
ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
868
"notify-lease-duration", 0);
870
resp = cupsDoRequest(m_connection, req,
871
getResource(CupsResourceRoot).toUtf8());
872
if (!isReplyOk(resp, true)) {
873
return subscriptionId;
876
attr = ippFindAttribute(resp, "notify-subscription-id", IPP_TAG_INTEGER);
879
qWarning() << "ipp-create-printer-subscription response doesn't"
880
" contain subscription id.";
882
subscriptionId = ippGetInteger(attr, 0);
887
return subscriptionId;
890
void IppClient::cancelSubscription(const int &subscriptionId)
895
if (subscriptionId <= 0) {
899
req = ippNewRequest(IPP_CANCEL_SUBSCRIPTION);
900
ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI,
901
"printer-uri", NULL, "/");
902
ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
903
"notify-subscription-id", subscriptionId);
905
resp = cupsDoRequest(m_connection, req,
906
getResource(CupsResourceRoot).toUtf8());
907
if (!isReplyOk(resp, true)) {
914
QVariant IppClient::getAttributeValue(ipp_attribute_t *attr, int index) const
918
if (ippGetCount(attr) > 1 && index < 0) {
919
QList<QVariant> list;
921
for (int i=0; i < ippGetCount(attr); i++) {
922
list.append(getAttributeValue(attr, i));
925
var = QVariant::fromValue<QList<QVariant>>(list);
931
switch (ippGetValueTag(attr)) {
934
case IPP_TAG_KEYWORD:
936
case IPP_TAG_CHARSET:
937
case IPP_TAG_MIMETYPE:
938
case IPP_TAG_LANGUAGE:
939
var = QVariant::fromValue<QString>(ippGetString(attr, index, NULL));
941
case IPP_TAG_INTEGER:
943
var = QVariant::fromValue<int>(ippGetInteger(attr, index));
945
case IPP_TAG_BOOLEAN:
946
var = QVariant::fromValue<bool>(ippGetBoolean(attr, index));
948
case IPP_TAG_RANGE: {
951
int lower = ippGetRange(attr, index, &upper);
953
// Build a string similar to "1-3" "5-" "8" "-4"
954
if (lower != INT_MIN) {
955
range += QString::number(lower);
958
if (lower != upper) {
959
range += QStringLiteral("-");
961
if (upper != INT_MAX) {
962
range += QString::number(upper);
966
var = QVariant(range);
969
case IPP_TAG_NOVALUE:
973
time_t time = ippDateToTime(ippGetDate(attr, index));
975
datetime.setTimeZone(QTimeZone::systemTimeZone());
976
datetime.setTime_t(time);
978
var = QVariant::fromValue<QDateTime>(datetime);
982
qWarning() << "Unknown IPP value tab 0x" << ippGetValueTag(attr);