diff --git a/qt/main.cpp b/qt/main.cpp index 8236ac6..5c3ccaf 100644 --- a/qt/main.cpp +++ b/qt/main.cpp @@ -1,426 +1,427 @@ /* main.cpp - A Qt dialog for PIN entry. * Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB) * Copyright (C) 2003, 2021 g10 Code GmbH * Copyright 2007 Ingo Klöcker * * Written by Steffen Hansen . * Modified by Marcus Brinkmann . * Modified by Marc Mutz * Software engineering by Ingo Klöcker * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-2.0+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "accessibility.h" #include "pinentryconfirm.h" #include "pinentrydialog.h" #include "pinentry.h" #include "util.h" #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include #include #include #include #ifdef FALLBACK_CURSES #include #endif #if QT_VERSION >= 0x050000 && defined(QT_STATIC) #include #ifdef Q_OS_WIN #include #include Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) #elif defined(Q_OS_MAC) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) #else Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) #endif #endif #ifdef Q_OS_WIN #include #endif #include "pinentry_debug.h" static QString escape_accel(const QString &s) { QString result; result.reserve(s.size()); bool afterUnderscore = false; for (unsigned int i = 0, end = s.size() ; i != end ; ++i) { const QChar ch = s[i]; if (ch == QLatin1Char('_')) { if (afterUnderscore) { // escaped _ result += QLatin1Char('_'); afterUnderscore = false; } else { // accel afterUnderscore = true; } } else { if (afterUnderscore || // accel ch == QLatin1Char('&')) { // escape & from being interpreted by Qt result += QLatin1Char('&'); } result += ch; afterUnderscore = false; } } if (afterUnderscore) // trailing single underscore: shouldn't happen, but deal with it robustly: { result += QLatin1Char('_'); } return result; } namespace { class InvalidUtf8 : public std::invalid_argument { public: InvalidUtf8() : std::invalid_argument("invalid utf8") {} ~InvalidUtf8() throw() {} }; } static const bool GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 = false; static QString from_utf8(const char *s) { const QString result = QString::fromUtf8(s); if (result.contains(QChar::ReplacementCharacter)) { if (GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8) { throw InvalidUtf8(); } else { return QString::fromLocal8Bit(s); } } return result; } static void setup_foreground_window(QWidget *widget, WId parentWid) { #if QT_VERSION >= 0x050000 /* For windows set the desktop window as the transient parent */ QWindow *parentWindow = nullptr; if (parentWid) { parentWindow = QWindow::fromWinId(parentWid); } #ifdef Q_OS_WIN if (!parentWindow) { HWND desktop = GetDesktopWindow(); if (desktop) { parentWindow = QWindow::fromWinId((WId) desktop); } } #endif if (parentWindow) { // Ensure that we have a native wid widget->winId(); QWindow *wndHandle = widget->windowHandle(); if (wndHandle) { wndHandle->setTransientParent(parentWindow); } } #endif widget->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint | Qt::WindowMinimizeButtonHint); } static int qt_cmd_handler(pinentry_t pe) { int want_pass = !!pe->pin; const QString ok = pe->ok ? escape_accel(from_utf8(pe->ok)) : pe->default_ok ? escape_accel(from_utf8(pe->default_ok)) : /* else */ QLatin1String("&OK") ; const QString cancel = pe->cancel ? escape_accel(from_utf8(pe->cancel)) : pe->default_cancel ? escape_accel(from_utf8(pe->default_cancel)) : /* else */ QLatin1String("&Cancel") ; unique_malloced_ptr str{pinentry_get_title(pe)}; const QString title = str ? from_utf8(str.get()) : /* else */ QLatin1String("pinentry-qt") ; const QString repeatError = pe->repeat_error_string ? from_utf8(pe->repeat_error_string) : QLatin1String("Passphrases do not match"); const QString repeatString = pe->repeat_passphrase ? from_utf8(pe->repeat_passphrase) : QString(); const QString visibilityTT = pe->default_tt_visi ? from_utf8(pe->default_tt_visi) : QLatin1String("Show passphrase"); const QString hideTT = pe->default_tt_hide ? from_utf8(pe->default_tt_hide) : QLatin1String("Hide passphrase"); const QString capsLockHint = pe->default_capshint ? from_utf8(pe->default_capshint) : QLatin1String("Caps Lock is on"); const QString generateLbl = pe->genpin_label ? from_utf8(pe->genpin_label) : QString(); const QString generateTT = pe->genpin_tt ? from_utf8(pe->genpin_tt) : QString(); if (want_pass) { PinEntryDialog pinentry(nullptr, 0, pe->timeout, true, !!pe->quality_bar, repeatString, visibilityTT, hideTT); setup_foreground_window(&pinentry, pe->parent_wid); pinentry.setPinentryInfo(pe); pinentry.setPrompt(escape_accel(from_utf8(pe->prompt))); pinentry.setDescription(from_utf8(pe->description)); pinentry.setRepeatErrorText(repeatError); pinentry.setGenpinLabel(generateLbl); pinentry.setGenpinTT(generateTT); pinentry.setCapsLockHint(capsLockHint); pinentry.setFormattedPassphrase({ bool(pe->formatted_passphrase), from_utf8(pe->formatted_passphrase_hint)}); pinentry.setConstraintsOptions({ bool(pe->constraints_enforce), from_utf8(pe->constraints_hint_short), from_utf8(pe->constraints_hint_long), from_utf8(pe->constraints_error_title) }); if (!title.isEmpty()) { pinentry.setWindowTitle(title); } /* If we reuse the same dialog window. */ pinentry.setPin(QString()); pinentry.setOkText(ok); pinentry.setCancelText(cancel); if (pe->error) { pinentry.setError(from_utf8(pe->error)); } if (pe->quality_bar) { pinentry.setQualityBar(from_utf8(pe->quality_bar)); } if (pe->quality_bar_tt) { pinentry.setQualityBarTT(from_utf8(pe->quality_bar_tt)); } bool ret = pinentry.exec(); if (!ret) { if (pinentry.timedOut()) pe->specific_err = gpg_error (GPG_ERR_TIMEOUT); return -1; } const QString pinStr = pinentry.pin(); QByteArray pin = pinStr.toUtf8(); if (!!pe->repeat_passphrase) { /* Should not have been possible to accept the dialog in that case but we do a safety check here */ pe->repeat_okay = (pinStr == pinentry.repeatedPin()); } int len = strlen(pin.constData()); if (len >= 0) { pinentry_setbufferlen(pe, len + 1); if (pe->pin) { strcpy(pe->pin, pin.constData()); return len; } } return -1; } else { const QString desc = pe->description ? from_utf8(pe->description) : QString(); const QString notok = pe->notok ? escape_accel(from_utf8(pe->notok)) : QString(); const QMessageBox::StandardButtons buttons = pe->one_button ? QMessageBox::Ok : pe->notok ? QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel : /* else */ QMessageBox::Ok | QMessageBox::Cancel ; - PinentryConfirm box(QMessageBox::Information, pe->timeout, title, desc, buttons, nullptr); + PinentryConfirm box{QMessageBox::Information, title, desc, buttons}; + box.setTimeout(std::chrono::seconds{pe->timeout}); setup_foreground_window(&box, pe->parent_wid); const struct { QMessageBox::StandardButton button; QString label; } buttonLabels[] = { { QMessageBox::Ok, ok }, { QMessageBox::Yes, ok }, { QMessageBox::No, notok }, { QMessageBox::Cancel, cancel }, }; for (size_t i = 0 ; i < sizeof buttonLabels / sizeof * buttonLabels ; ++i) if ((buttons & buttonLabels[i].button) && !buttonLabels[i].label.isEmpty()) { box.button(buttonLabels[i].button)->setText(buttonLabels[i].label); Accessibility::setDescription(box.button(buttonLabels[i].button), buttonLabels[i].label); } box.setIconPixmap(applicationIconPixmap()); if (!pe->one_button) { box.setDefaultButton(QMessageBox::Cancel); } box.show(); raiseWindow(&box); const int rc = box.exec(); if (rc == QMessageBox::Cancel) { pe->canceled = true; } if (box.timedOut()) { pe->specific_err = gpg_error (GPG_ERR_TIMEOUT); } return rc == QMessageBox::Ok || rc == QMessageBox::Yes ; } } static int qt_cmd_handler_ex(pinentry_t pe) { try { return qt_cmd_handler(pe); } catch (const InvalidUtf8 &) { pe->locale_err = true; return pe->pin ? -1 : false ; } catch (...) { pe->canceled = true; return pe->pin ? -1 : false ; } } pinentry_cmd_handler_t pinentry_cmd_handler = qt_cmd_handler_ex; int main(int argc, char *argv[]) { pinentry_init("pinentry-qt"); QApplication *app = NULL; int new_argc = 0; #ifdef FALLBACK_CURSES #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) // check a few environment variables that are usually set on X11 or Wayland sessions const bool hasWaylandDisplay = qEnvironmentVariableIsSet("WAYLAND_DISPLAY"); const bool isWaylandSessionType = qgetenv("XDG_SESSION_TYPE") == "wayland"; const bool hasX11Display = pinentry_have_display(argc, argv); const bool isX11SessionType = qgetenv("XDG_SESSION_TYPE") == "x11"; const bool isGUISession = hasWaylandDisplay || isWaylandSessionType || hasX11Display || isX11SessionType; qCDebug(PINENTRY_LOG) << "hasWaylandDisplay:" << hasWaylandDisplay; qCDebug(PINENTRY_LOG) << "isWaylandSessionType:" << isWaylandSessionType; qCDebug(PINENTRY_LOG) << "hasX11Display:" << hasX11Display; qCDebug(PINENTRY_LOG) << "isX11SessionType:" << isX11SessionType; qCDebug(PINENTRY_LOG) << "isGUISession:" << isGUISession; #else const bool isGUISession = pinentry_have_display(argc, argv); #endif if (!isGUISession) { pinentry_cmd_handler = curses_cmd_handler; pinentry_set_flavor_flag ("curses"); } else #endif { /* Qt does only understand -display but not --display; thus we are fixing that here. The code is pretty simply and may get confused if an argument is called "--display". */ char **new_argv, *p; size_t n; int i, done; for (n = 0, i = 0; i < argc; i++) { n += strlen(argv[i]) + 1; } n++; new_argv = (char **)calloc(argc + 1, sizeof * new_argv); if (new_argv) { *new_argv = (char *)malloc(n); } if (!new_argv || !*new_argv) { fprintf(stderr, "pinentry-qt: can't fixup argument list: %s\n", strerror(errno)); exit(EXIT_FAILURE); } for (done = 0, p = *new_argv, i = 0; i < argc; i++) if (!done && !strcmp(argv[i], "--display")) { new_argv[i] = strcpy(p, argv[i] + 1); p += strlen(argv[i] + 1) + 1; done = 1; } else { new_argv[i] = strcpy(p, argv[i]); p += strlen(argv[i]) + 1; } /* Note: QApplication uses int &argc so argc has to be valid * for the full lifetime of the application. * * As Qt might modify argc / argv we use copies here so that * we do not loose options that are handled in both. e.g. display. */ new_argc = argc; Q_ASSERT (new_argc); app = new QApplication(new_argc, new_argv); app->setWindowIcon(QIcon(QLatin1String(":/icons/document-encrypt.png"))); } pinentry_parse_opts(argc, argv); int rc = pinentry_loop(); delete app; return rc ? EXIT_FAILURE : EXIT_SUCCESS ; } diff --git a/qt/pinentryconfirm.cpp b/qt/pinentryconfirm.cpp index 101e18c..7501b53 100644 --- a/qt/pinentryconfirm.cpp +++ b/qt/pinentryconfirm.cpp @@ -1,78 +1,88 @@ /* pinentryconfirm.cpp - A QMessageBox with a timeout * * Copyright (C) 2011 Ben Kibbey * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-2.0+ */ #include "pinentryconfirm.h" #include "accessibility.h" #include "pinentrydialog.h" #include #include #include #include -PinentryConfirm::PinentryConfirm(Icon icon, int timeout, const QString &title, - const QString &desc, StandardButtons buttons, QWidget *parent) : - QMessageBox(icon, title, desc, buttons, parent) +PinentryConfirm::PinentryConfirm(Icon icon, const QString &title, const QString &text, + StandardButtons buttons, QWidget *parent, Qt::WindowFlags flags) + : QMessageBox{icon, title, text, buttons, parent, flags} { - _timed_out = false; - if (timeout > 0) { - _timer = new QTimer(this); - connect(_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); - _timer->start(timeout * 1000); - } - Accessibility::setDescription(this, desc); + _timer.callOnTimeout(this, &PinentryConfirm::slotTimeout); + Accessibility::setDescription(this, text); Accessibility::setName(this, title); raiseWindow(this); } +void PinentryConfirm::setTimeout(std::chrono::seconds timeout) +{ + _timer.setInterval(timeout); +} + +std::chrono::seconds PinentryConfirm::timeout() const +{ + return std::chrono::duration_cast(_timer.intervalAsDuration()); +} + bool PinentryConfirm::timedOut() const { return _timed_out; } void PinentryConfirm::showEvent(QShowEvent *event) { static bool resized; if (!resized) { QGridLayout* lay = dynamic_cast (layout()); if (lay) { QSize textSize = fontMetrics().size(Qt::TextExpandTabs, text(), fontMetrics().maxWidth()); QSpacerItem* horizontalSpacer = new QSpacerItem(textSize.width() + iconPixmap().width(), 0, QSizePolicy::Minimum, QSizePolicy::Expanding); lay->addItem(horizontalSpacer, lay->rowCount(), 1, 1, lay->columnCount() - 1); } resized = true; } QMessageBox::showEvent(event); raiseWindow(this); + + if (timeout() > std::chrono::milliseconds::zero()) { + _timer.setSingleShot(true); + _timer.start(); + } } void PinentryConfirm::slotTimeout() { QAbstractButton *b = button(QMessageBox::Cancel); _timed_out = true; if (b) { b->animateClick(0); } } #include "pinentryconfirm.moc" diff --git a/qt/pinentryconfirm.h b/qt/pinentryconfirm.h index ec116ac..f734756 100644 --- a/qt/pinentryconfirm.h +++ b/qt/pinentryconfirm.h @@ -1,46 +1,51 @@ /* pinentryconfirm.h - A QMessageBox with a timeout * * Copyright (C) 2011 Ben Kibbey * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-2.0+ */ #ifndef PINENTRYCONFIRM_H #define PINENTRYCONFIRM_H #include #include class PinentryConfirm : public QMessageBox { Q_OBJECT public: - PinentryConfirm(Icon, int timeout, const QString &title, - const QString &desc, StandardButtons buttons, - QWidget *parent); + PinentryConfirm(Icon icon, const QString &title, const QString &text, + StandardButtons buttons = NoButton, QWidget *parent = nullptr, + Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + + void setTimeout(std::chrono::seconds timeout); + std::chrono::seconds timeout() const; + bool timedOut() const; -private slots: +protected: + void showEvent(QShowEvent *event) override; + +private Q_SLOTS: void slotTimeout(); private: - QTimer *_timer; - bool _timed_out; - -protected: - /* reimp */ void showEvent(QShowEvent *event); +private: + QTimer _timer; + bool _timed_out = false; }; #endif