Implement Caps Lock warning

This commit is contained in:
Janek Bevendorff 2019-10-21 10:49:09 +02:00
parent 596d2cf425
commit d9214db404
12 changed files with 122 additions and 22 deletions

View file

@ -350,7 +350,8 @@ if(HAIKU)
target_link_libraries(keepassx_core network) target_link_libraries(keepassx_core network)
endif() endif()
if(UNIX AND NOT APPLE) 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() endif()
if(MINGW) if(MINGW)
target_link_libraries(keepassx_core Wtsapi32.lib Ws2_32.lib) target_link_libraries(keepassx_core Wtsapi32.lib Ws2_32.lib)

View file

@ -115,11 +115,11 @@ QIcon Resources::trayIconUnlocked()
return useDarkIcon() ? icon("keepassxc-dark", false) : icon("keepassxc-unlocked", false); 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); QIcon icon = m_iconCache.value(name);
if (!icon.isNull()) { if (!icon.isNull() && !overrideColor.isValid()) {
return icon; 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); QImage img = icon.pixmap(128, 128).toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
icon = {}; icon = {};
QPalette palette = getMainWindow()->palette();
QPainter painter(&img); QPainter painter(&img);
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
if (!overrideColor.isValid()) {
QPalette palette = getMainWindow()->palette();
painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Normal, QPalette::WindowText)); painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Normal, QPalette::WindowText));
icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal); icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal);
painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::ButtonText)); painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::ButtonText));
icon.addPixmap(QPixmap::fromImage(img), QIcon::Active); icon.addPixmap(QPixmap::fromImage(img), QIcon::Active);
painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::HighlightedText)); painter.fillRect(
0, 0, img.width(), img.height(), palette.color(QPalette::Active, QPalette::HighlightedText));
icon.addPixmap(QPixmap::fromImage(img), QIcon::Selected); icon.addPixmap(QPixmap::fromImage(img), QIcon::Selected);
painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Disabled, QPalette::WindowText)); painter.fillRect(0, 0, img.width(), img.height(), palette.color(QPalette::Disabled, QPalette::WindowText));
icon.addPixmap(QPixmap::fromImage(img), QIcon::Disabled); 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) #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
icon.setIsMask(true); icon.setIsMask(true);
#endif #endif
} }
if (!overrideColor.isValid()) {
m_iconCache.insert(name, icon); m_iconCache.insert(name, icon);
}
return icon; return icon;
} }

View file

@ -19,6 +19,7 @@
#ifndef KEEPASSX_RESOURCES_H #ifndef KEEPASSX_RESOURCES_H
#define KEEPASSX_RESOURCES_H #define KEEPASSX_RESOURCES_H
#include <QColor>
#include <QHash> #include <QHash>
#include <QIcon> #include <QIcon>
#include <QString> #include <QString>
@ -33,7 +34,7 @@ public:
QIcon trayIcon(); QIcon trayIcon();
QIcon trayIconLocked(); QIcon trayIconLocked();
QIcon trayIconUnlocked(); 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); QIcon onOffIcon(const QString& name, bool recolor = true);
static Resources* instance(); static Resources* instance();

View file

@ -22,16 +22,14 @@
#include "core/Resources.h" #include "core/Resources.h"
#include "gui/Font.h" #include "gui/Font.h"
#include "gui/PasswordGeneratorWidget.h" #include "gui/PasswordGeneratorWidget.h"
#include "gui/osutils/OSUtils.h"
#include "gui/styles/StateColorPalette.h" #include "gui/styles/StateColorPalette.h"
#include <QDialog> #include <QDialog>
#include <QTimer>
#include <QToolTip>
#include <QVBoxLayout> #include <QVBoxLayout>
namespace
{
} // namespace
PasswordEdit::PasswordEdit(QWidget* parent) PasswordEdit::PasswordEdit(QWidget* parent)
: QLineEdit(parent) : QLineEdit(parent)
{ {
@ -70,6 +68,13 @@ PasswordEdit::PasswordEdit(QWidget* parent)
m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut);
addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition);
m_passwordGeneratorAction->setVisible(false); 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) void PasswordEdit::setRepeatPartner(PasswordEdit* repeatEdit)
@ -165,3 +170,34 @@ void PasswordEdit::autocompletePassword(const QString& password)
setText(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()); });
}
}
}

View file

@ -39,20 +39,27 @@ public slots:
void setShowPassword(bool show); void setShowPassword(bool show);
void updateRepeatStatus(); void updateRepeatStatus();
protected:
bool event(QEvent* event) override;
signals:
void capslockToggled(bool capslockOn);
private slots: private slots:
void autocompletePassword(const QString& password); void autocompletePassword(const QString& password);
void popupPasswordGenerator(); void popupPasswordGenerator();
void setParentPasswordEdit(PasswordEdit* parent); void setParentPasswordEdit(PasswordEdit* parent);
void checkCapslockState();
private: private:
QPointer<QAction> m_errorAction; QPointer<QAction> m_errorAction;
QPointer<QAction> m_correctAction; QPointer<QAction> m_correctAction;
QPointer<QAction> m_toggleVisibleAction; QPointer<QAction> m_toggleVisibleAction;
QPointer<QAction> m_passwordGeneratorAction; QPointer<QAction> m_passwordGeneratorAction;
QPointer<QAction> m_capslockAction;
QPointer<PasswordEdit> m_repeatPasswordEdit; QPointer<PasswordEdit> m_repeatPasswordEdit;
QPointer<PasswordEdit> m_parentPasswordEdit; QPointer<PasswordEdit> m_parentPasswordEdit;
bool m_sendGeneratorSignal = false; bool m_capslockState = false;
bool m_isRepeatPartner = false;
}; };
#endif // KEEPASSX_PASSWORDEDIT_H #endif // KEEPASSX_PASSWORDEDIT_H

View file

@ -31,6 +31,7 @@ class OSUtilsBase : public QObject
public: public:
virtual bool isDarkMode() = 0; virtual bool isDarkMode() = 0;
virtual bool isCapslockEnabled() = 0;
protected: protected:
explicit OSUtilsBase(QObject* parent = nullptr); explicit OSUtilsBase(QObject* parent = nullptr);

View file

@ -19,6 +19,9 @@
#include "MacUtils.h" #include "MacUtils.h"
#include <QApplication> #include <QApplication>
#include <CoreGraphics/CGEventSource.h>
QPointer<MacUtils> MacUtils::m_instance = nullptr; QPointer<MacUtils> MacUtils::m_instance = nullptr;
MacUtils::MacUtils(QObject* parent) MacUtils::MacUtils(QObject* parent)
@ -85,3 +88,8 @@ bool MacUtils::enableScreenRecording()
{ {
return m_appkit->enableScreenRecording(); return m_appkit->enableScreenRecording();
} }
bool MacUtils::isCapslockEnabled()
{
return (CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState) & kCGEventFlagMaskAlphaShift) != 0;
}

View file

@ -33,13 +33,15 @@ class MacUtils : public OSUtilsBase
public: public:
static MacUtils* instance(); static MacUtils* instance();
bool isDarkMode() override;
bool isCapslockEnabled() override;
WId activeWindow(); WId activeWindow();
bool raiseWindow(WId pid); bool raiseWindow(WId pid);
bool raiseLastActiveWindow(); bool raiseLastActiveWindow();
bool raiseOwnWindow(); bool raiseOwnWindow();
bool hideOwnWindow(); bool hideOwnWindow();
bool isHidden(); bool isHidden();
bool isDarkMode() override;
bool enableAccessibility(); bool enableAccessibility();
bool enableScreenRecording(); bool enableScreenRecording();

View file

@ -18,9 +18,17 @@
#include "NixUtils.h" #include "NixUtils.h"
#include <QApplication> #include <QApplication>
#include <QColor> #include <QColor>
#include <QGuiApplication>
#include <QPalette> #include <QPalette>
#include <QStyle> #include <QStyle>
#include <qpa/qplatformnativeinterface.h>
// namespace required to avoid name clashes with declarations in XKBlib.h
namespace X11
{
#include <X11/XKBlib.h>
}
QPointer<NixUtils> NixUtils::m_instance = nullptr; QPointer<NixUtils> NixUtils::m_instance = nullptr;
NixUtils* NixUtils::instance() NixUtils* NixUtils::instance()
@ -48,3 +56,24 @@ bool NixUtils::isDarkMode()
} }
return qApp->style()->standardPalette().color(QPalette::Window).toHsl().lightness() < 110; 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<X11::Display*>(display), XkbUseCoreKbd, &state) == Success) {
return ((state & 1u) != 0);
}
}
// TODO: Wayland
return false;
}

View file

@ -29,6 +29,7 @@ public:
static NixUtils* instance(); static NixUtils* instance();
bool isDarkMode() override; bool isDarkMode() override;
bool isCapslockEnabled() override;
private: private:
explicit NixUtils(QObject* parent = nullptr); explicit NixUtils(QObject* parent = nullptr);

View file

@ -83,3 +83,8 @@ bool WinUtils::isDarkMode()
QSettings::NativeFormat); QSettings::NativeFormat);
return settings.value("AppsUseLightTheme", 1).toInt() == 0; return settings.value("AppsUseLightTheme", 1).toInt() == 0;
} }
bool WinUtils::isCapslockEnabled()
{
return GetKeyState(VK_CAPITAL) == 1;
}

View file

@ -33,6 +33,7 @@ public:
static void registerEventFilters(); static void registerEventFilters();
bool isDarkMode() override; bool isDarkMode() override;
bool isCapslockEnabled() override;
protected: protected:
explicit WinUtils(QObject* parent = nullptr); explicit WinUtils(QObject* parent = nullptr);