diff --git a/qt/Makefile.am b/qt/Makefile.am
index ff92c40..48ebb59 100644
--- a/qt/Makefile.am
+++ b/qt/Makefile.am
@@ -1,75 +1,79 @@
# Makefile.am
# Copyright (C) 2002 g10 Code GmbH, Klarälvdalens Datakonsult AB
# Copyright (C) 2008, 2015 g10 Code GmbH
#
# This file is part of PINENTRY.
#
# PINENTRY 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.
#
# PINENTRY 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+
## Process this file with automake to produce Makefile.in
bin_PROGRAMS = pinentry-qt
EXTRA_DIST = pinentryrc.qrc
SUBDIRS = icons
if FALLBACK_CURSES
ncurses_include = $(NCURSES_INCLUDE)
libcurses = ../pinentry/libpinentry-curses.a $(LIBCURSES) $(LIBICONV)
else
ncurses_include =
libcurses =
endif
AM_CPPFLAGS = $(COMMON_CFLAGS) \
-I$(top_srcdir) -I$(top_srcdir)/secmem \
$(ncurses_include) -I$(top_srcdir)/pinentry
AM_CXXFLAGS = $(PINENTRY_QT5_CFLAGS)
pinentry_qt_LDADD = \
../pinentry/libpinentry.a $(top_builddir)/secmem/libsecmem.a \
$(COMMON_LIBS) $(PINENTRY_QT5_LIBS) $(libcurses)
pinentry_qt_LDFLAGS = $(PINENTRY_QT5_LDFLAGS)
if BUILD_PINENTRY_QT5
BUILT_SOURCES = \
pinentryconfirm.moc pinentrydialog.moc pinlineedit.moc capslock.moc \
+ focusframe.moc \
+ keyboardfocusindication.moc \
pinentryrc.cpp
endif
CLEANFILES = $(BUILT_SOURCES)
if HAVE_W32_SYSTEM
pinentry_qt_platform_SOURCES = capslock_win.cpp
else
pinentry_qt_platform_SOURCES = capslock_unix.cpp
endif
pinentry_qt_SOURCES = pinentrydialog.h pinentrydialog.cpp \
main.cpp pinentryconfirm.cpp pinentryconfirm.h \
pinlineedit.h pinlineedit.cpp capslock.cpp capslock.h capslock_p.h \
pinentry_debug.cpp pinentry_debug.h util.h accessibility.cpp \
accessibility.h qti18n.cpp pinentryrc.qrc \
+ focusframe.h focusframe.cpp \
+ keyboardfocusindication.h keyboardfocusindication.cpp \
$(pinentry_qt_platform_SOURCES)
nodist_pinentry_qt_SOURCES = $(BUILT_SOURCES)
.h.moc:
$(MOC5) `test -f '$<' || echo '$(srcdir)/'`$< -o $@
pinentryrc.cpp: pinentryrc.qrc
$(RCC5) `test -f '$<' || echo '$(srcdir)/'`$< -o $@
diff --git a/qt/focusframe.cpp b/qt/focusframe.cpp
new file mode 100644
index 0000000..c4d4681
--- /dev/null
+++ b/qt/focusframe.cpp
@@ -0,0 +1,74 @@
+/* focusframe.cpp - A focus indicator for labels.
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * 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+
+ */
+
+#include "focusframe.h"
+
+#if QT_CONFIG(graphicseffect)
+#include
+#endif
+#include
+#include
+
+static QRect effectiveWidgetRect(const QWidget *w)
+{
+ // based on QWidgetPrivate::effectiveRectFor
+#if QT_CONFIG(graphicseffect)
+ const auto* const graphicsEffect = w->graphicsEffect();
+ if (graphicsEffect && graphicsEffect->isEnabled())
+ return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect();
+#endif // QT_CONFIG(graphicseffect)
+ return w->rect();
+}
+
+static QRect clipRect(const QWidget *w)
+{
+ // based on QWidgetPrivate::clipRect
+ if (!w->isVisible()) {
+ return QRect();
+ }
+ QRect r = effectiveWidgetRect(w);
+ int ox = 0;
+ int oy = 0;
+ while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) {
+ ox -= w->x();
+ oy -= w->y();
+ w = w->parentWidget();
+ r &= QRect(ox, oy, w->width(), w->height());
+ }
+ return r;
+}
+
+void FocusFrame::paintEvent(QPaintEvent *)
+{
+ if (!widget()) {
+ return;
+ }
+
+ QStylePainter p{this};
+ QStyleOptionFocusRect option;
+ initStyleOption(&option);
+ const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
+ const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
+ const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin*2, vmargin*2);
+ p.setClipRect(rect);
+ p.drawPrimitive(QStyle::PE_FrameFocusRect, option);
+}
+
+#include "focusframe.moc"
diff --git a/qt/focusframe.h b/qt/focusframe.h
new file mode 100644
index 0000000..3d2231e
--- /dev/null
+++ b/qt/focusframe.h
@@ -0,0 +1,36 @@
+/* focusframe.h - A focus indicator for labels.
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * 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+
+ */
+
+#ifndef __FOCUSFRAME_H__
+#define __FOCUSFRAME_H__
+
+#include
+
+class FocusFrame : public QFocusFrame
+{
+ Q_OBJECT
+public:
+ using QFocusFrame::QFocusFrame;
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+};
+
+#endif // __FOCUSFRAME_H__
diff --git a/qt/keyboardfocusindication.cpp b/qt/keyboardfocusindication.cpp
new file mode 100644
index 0000000..27e7231
--- /dev/null
+++ b/qt/keyboardfocusindication.cpp
@@ -0,0 +1,45 @@
+/* keyboardfocusindication.cpp - Helper for extended keyboard focus indication.
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * 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+
+ */
+
+#include "keyboardfocusindication.h"
+
+#include "focusframe.h"
+
+#include
+
+KeyboardFocusIndication::KeyboardFocusIndication(QObject *parent)
+ : QObject{parent}
+{
+ connect(qApp, &QApplication::focusChanged, this, &KeyboardFocusIndication::updateFocusFrame);
+}
+
+void KeyboardFocusIndication::updateFocusFrame(QWidget *, QWidget *focusWidget)
+{
+ if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) {
+ if (!focusFrame) {
+ focusFrame = new FocusFrame{focusWidget};
+ }
+ focusFrame->setWidget(focusWidget);
+ } else if (focusFrame) {
+ focusFrame->setWidget(nullptr);
+ }
+}
+
+#include "keyboardfocusindication.moc"
diff --git a/qt/keyboardfocusindication.h b/qt/keyboardfocusindication.h
new file mode 100644
index 0000000..e86a317
--- /dev/null
+++ b/qt/keyboardfocusindication.h
@@ -0,0 +1,42 @@
+/* keyboardfocusindication.h - Helper for extended keyboard focus indication.
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * 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+
+ */
+
+#ifndef __KEYBOARDFOCUSINDICATION_H__
+#define __KEYBOARDFOCUSINDICATION_H__
+
+#include
+#include
+#include
+
+class FocusFrame;
+
+class KeyboardFocusIndication : public QObject
+{
+ Q_OBJECT
+public:
+ KeyboardFocusIndication(QObject *parent);
+
+private:
+ void updateFocusFrame(QWidget *, QWidget *);
+
+ QPointer focusFrame;
+};
+
+#endif // __KEYBOARDFOCUSINDICATION_H__
diff --git a/qt/main.cpp b/qt/main.cpp
index 47d15d2..8c8ab48 100644
--- a/qt/main.cpp
+++ b/qt/main.cpp
@@ -1,429 +1,431 @@
/* 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 "keyboardfocusindication.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, title, desc, buttons};
box.setTextFormat(Qt::PlainText);
box.setTextInteractionFlags(Qt::TextSelectableByMouse);
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")));
+ (void) new KeyboardFocusIndication{app};
}
pinentry_parse_opts(argc, argv);
int rc = pinentry_loop();
delete app;
return rc ? EXIT_FAILURE : EXIT_SUCCESS ;
}