From d9214db404571044ae94f92c9aba0963a0243832 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 21 Oct 2019 10:49:09 +0200 Subject: [PATCH] Implement Caps Lock warning --- src/CMakeLists.txt | 3 +- src/core/Resources.cpp | 32 ++++++++++++------- src/core/Resources.h | 3 +- src/gui/PasswordEdit.cpp | 46 ++++++++++++++++++++++++--- src/gui/PasswordEdit.h | 11 +++++-- src/gui/osutils/OSUtilsBase.h | 1 + src/gui/osutils/macutils/MacUtils.cpp | 8 +++++ src/gui/osutils/macutils/MacUtils.h | 4 ++- src/gui/osutils/nixutils/NixUtils.cpp | 29 +++++++++++++++++ src/gui/osutils/nixutils/NixUtils.h | 1 + src/gui/osutils/winutils/WinUtils.cpp | 5 +++ src/gui/osutils/winutils/WinUtils.h | 1 + 12 files changed, 122 insertions(+), 22 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a147b9d36..02f313fca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -350,7 +350,8 @@ if(HAIKU) target_link_libraries(keepassx_core network) endif() if(UNIX AND NOT APPLE) - target_link_libraries(keepassx_core Qt5::DBus) + target_link_libraries(keepassx_core Qt5::DBus X11) + include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() if(MINGW) target_link_libraries(keepassx_core Wtsapi32.lib Ws2_32.lib) diff --git a/src/core/Resources.cpp b/src/core/Resources.cpp index 5f13a7cdf..c4f747232 100644 --- a/src/core/Resources.cpp +++ b/src/core/Resources.cpp @@ -115,11 +115,11 @@ QIcon Resources::trayIconUnlocked() return useDarkIcon() ? icon("keepassxc-dark", false) : icon("keepassxc-unlocked", false); } -QIcon Resources::icon(const QString& name, bool recolor) +QIcon Resources::icon(const QString& name, bool recolor, const QColor& overrideColor) { QIcon icon = m_iconCache.value(name); - if (!icon.isNull()) { + if (!icon.isNull() && !overrideColor.isValid()) { return icon; } @@ -128,28 +128,36 @@ QIcon Resources::icon(const QString& name, bool recolor) QImage img = icon.pixmap(128, 128).toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); icon = {}; - QPalette palette = getMainWindow()->palette(); QPainter painter(&img); painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); - painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Normal, QPalette::WindowText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal); + if (!overrideColor.isValid()) { + QPalette palette = getMainWindow()->palette(); + painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Normal, QPalette::WindowText)); + icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal); - painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::ButtonText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Active); + painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::ButtonText)); + icon.addPixmap(QPixmap::fromImage(img), QIcon::Active); - painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::HighlightedText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Selected); + painter.fillRect( + 0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::HighlightedText)); + icon.addPixmap(QPixmap::fromImage(img), QIcon::Selected); - painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Disabled, QPalette::WindowText)); - icon.addPixmap(QPixmap::fromImage(img), QIcon::Disabled); + painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Disabled, QPalette::WindowText)); + icon.addPixmap(QPixmap::fromImage(img), QIcon::Disabled); + } else { + painter.fillRect(0, 0, img.width(), img.height(), overrideColor); + icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal); + } #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) icon.setIsMask(true); #endif } - m_iconCache.insert(name, icon); + if (!overrideColor.isValid()) { + m_iconCache.insert(name, icon); + } return icon; } diff --git a/src/core/Resources.h b/src/core/Resources.h index 1f506b78e..97a994aa1 100644 --- a/src/core/Resources.h +++ b/src/core/Resources.h @@ -19,6 +19,7 @@ #ifndef KEEPASSX_RESOURCES_H #define KEEPASSX_RESOURCES_H +#include #include #include #include @@ -33,7 +34,7 @@ public: QIcon trayIcon(); QIcon trayIconLocked(); QIcon trayIconUnlocked(); - QIcon icon(const QString& name, bool recolor = true); + QIcon icon(const QString& name, bool recolor = true, const QColor& overrideColor = QColor::Invalid); QIcon onOffIcon(const QString& name, bool recolor = true); static Resources* instance(); diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index 89c4fef51..75423fc89 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -22,16 +22,14 @@ #include "core/Resources.h" #include "gui/Font.h" #include "gui/PasswordGeneratorWidget.h" +#include "gui/osutils/OSUtils.h" #include "gui/styles/StateColorPalette.h" #include +#include +#include #include -namespace -{ - -} // namespace - PasswordEdit::PasswordEdit(QWidget* parent) : QLineEdit(parent) { @@ -70,6 +68,13 @@ PasswordEdit::PasswordEdit(QWidget* parent) m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); m_passwordGeneratorAction->setVisible(false); + + m_capslockAction = + new QAction(resources()->icon("dialog-warning", true, StateColorPalette().color(StateColorPalette::Error)), + tr("Warning: Caps Lock enabled!"), + nullptr); + addAction(m_capslockAction, QLineEdit::LeadingPosition); + m_capslockAction->setVisible(false); } void PasswordEdit::setRepeatPartner(PasswordEdit* repeatEdit) @@ -165,3 +170,34 @@ void PasswordEdit::autocompletePassword(const QString& password) setText(password); } } + +bool PasswordEdit::event(QEvent* event) +{ + if (isVisible()) { + checkCapslockState(); + } + return QLineEdit::event(event); +} + +void PasswordEdit::checkCapslockState() +{ + if (m_parentPasswordEdit) { + return; + } + + bool newCapslockState = osUtils->isCapslockEnabled(); + if (newCapslockState != m_capslockState) { + m_capslockState = newCapslockState; + m_capslockAction->setVisible(newCapslockState); + + // Force repaint to avoid rendering glitches of QLineEdit contents + repaint(); + + emit capslockToggled(m_capslockState); + + if (newCapslockState) { + QTimer::singleShot( + 150, [this]() { QToolTip::showText(mapToGlobal(rect().bottomLeft()), m_capslockAction->text()); }); + } + } +} diff --git a/src/gui/PasswordEdit.h b/src/gui/PasswordEdit.h index 3ebc5d975..559394bd0 100644 --- a/src/gui/PasswordEdit.h +++ b/src/gui/PasswordEdit.h @@ -39,20 +39,27 @@ public slots: void setShowPassword(bool show); void updateRepeatStatus(); +protected: + bool event(QEvent* event) override; + +signals: + void capslockToggled(bool capslockOn); + private slots: void autocompletePassword(const QString& password); void popupPasswordGenerator(); void setParentPasswordEdit(PasswordEdit* parent); + void checkCapslockState(); private: QPointer m_errorAction; QPointer m_correctAction; QPointer m_toggleVisibleAction; QPointer m_passwordGeneratorAction; + QPointer m_capslockAction; QPointer m_repeatPasswordEdit; QPointer m_parentPasswordEdit; - bool m_sendGeneratorSignal = false; - bool m_isRepeatPartner = false; + bool m_capslockState = false; }; #endif // KEEPASSX_PASSWORDEDIT_H diff --git a/src/gui/osutils/OSUtilsBase.h b/src/gui/osutils/OSUtilsBase.h index 9467fca09..0606d3226 100644 --- a/src/gui/osutils/OSUtilsBase.h +++ b/src/gui/osutils/OSUtilsBase.h @@ -31,6 +31,7 @@ class OSUtilsBase : public QObject public: virtual bool isDarkMode() = 0; + virtual bool isCapslockEnabled() = 0; protected: explicit OSUtilsBase(QObject* parent = nullptr); diff --git a/src/gui/osutils/macutils/MacUtils.cpp b/src/gui/osutils/macutils/MacUtils.cpp index 44e5dbee4..b888b18e1 100644 --- a/src/gui/osutils/macutils/MacUtils.cpp +++ b/src/gui/osutils/macutils/MacUtils.cpp @@ -19,6 +19,9 @@ #include "MacUtils.h" #include +#include + + QPointer MacUtils::m_instance = nullptr; MacUtils::MacUtils(QObject* parent) @@ -85,3 +88,8 @@ bool MacUtils::enableScreenRecording() { return m_appkit->enableScreenRecording(); } + +bool MacUtils::isCapslockEnabled() +{ + return (CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState) & kCGEventFlagMaskAlphaShift) != 0; +} diff --git a/src/gui/osutils/macutils/MacUtils.h b/src/gui/osutils/macutils/MacUtils.h index 2146cdd3b..b2180c30a 100644 --- a/src/gui/osutils/macutils/MacUtils.h +++ b/src/gui/osutils/macutils/MacUtils.h @@ -33,13 +33,15 @@ class MacUtils : public OSUtilsBase public: static MacUtils* instance(); + bool isDarkMode() override; + bool isCapslockEnabled() override; + WId activeWindow(); bool raiseWindow(WId pid); bool raiseLastActiveWindow(); bool raiseOwnWindow(); bool hideOwnWindow(); bool isHidden(); - bool isDarkMode() override; bool enableAccessibility(); bool enableScreenRecording(); diff --git a/src/gui/osutils/nixutils/NixUtils.cpp b/src/gui/osutils/nixutils/NixUtils.cpp index 229d6b519..e4de7c9c3 100644 --- a/src/gui/osutils/nixutils/NixUtils.cpp +++ b/src/gui/osutils/nixutils/NixUtils.cpp @@ -18,9 +18,17 @@ #include "NixUtils.h" #include #include +#include #include #include +#include +// namespace required to avoid name clashes with declarations in XKBlib.h +namespace X11 +{ +#include +} + QPointer NixUtils::m_instance = nullptr; NixUtils* NixUtils::instance() @@ -48,3 +56,24 @@ bool NixUtils::isDarkMode() } return qApp->style()->standardPalette().color(QPalette::Window).toHsl().lightness() < 110; } + +bool NixUtils::isCapslockEnabled() +{ + QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface(); + auto* display = native->nativeResourceForWindow("display", nullptr); + if (!display) { + return false; + } + + QString platform = QGuiApplication::platformName(); + if (platform == "xcb") { + unsigned state = 0; + if (X11::XkbGetIndicatorState(reinterpret_cast(display), XkbUseCoreKbd, &state) == Success) { + return ((state & 1u) != 0); + } + } + + // TODO: Wayland + + return false; +} diff --git a/src/gui/osutils/nixutils/NixUtils.h b/src/gui/osutils/nixutils/NixUtils.h index bf236cdc3..b23cf143d 100644 --- a/src/gui/osutils/nixutils/NixUtils.h +++ b/src/gui/osutils/nixutils/NixUtils.h @@ -29,6 +29,7 @@ public: static NixUtils* instance(); bool isDarkMode() override; + bool isCapslockEnabled() override; private: explicit NixUtils(QObject* parent = nullptr); diff --git a/src/gui/osutils/winutils/WinUtils.cpp b/src/gui/osutils/winutils/WinUtils.cpp index f92192f18..f3cd9e8a8 100644 --- a/src/gui/osutils/winutils/WinUtils.cpp +++ b/src/gui/osutils/winutils/WinUtils.cpp @@ -83,3 +83,8 @@ bool WinUtils::isDarkMode() QSettings::NativeFormat); return settings.value("AppsUseLightTheme", 1).toInt() == 0; } + +bool WinUtils::isCapslockEnabled() +{ + return GetKeyState(VK_CAPITAL) == 1; +} diff --git a/src/gui/osutils/winutils/WinUtils.h b/src/gui/osutils/winutils/WinUtils.h index 1a95716e1..0965074fa 100644 --- a/src/gui/osutils/winutils/WinUtils.h +++ b/src/gui/osutils/winutils/WinUtils.h @@ -33,6 +33,7 @@ public: static void registerEventFilters(); bool isDarkMode() override; + bool isCapslockEnabled() override; protected: explicit WinUtils(QObject* parent = nullptr);