mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-24 06:49:46 -05:00
Implement Auto-Type {PICKCHARS}
* Closes #725 Support Auto-Type {PICKCHARS} placeholder. Open a dialog that lets you pick characters of an entry's password by their position. Supports typing {TAB} in between characters to move between fields (if necessary). Also supports using arrow keys to quickly navigate around the choice grid.
This commit is contained in:
parent
027ff9f2bf
commit
813ab47e29
@ -47,6 +47,7 @@ image::autotype_entry_sequences.png[]
|
|||||||
|{DELAY X} |Delay typing start by X milliseconds
|
|{DELAY X} |Delay typing start by X milliseconds
|
||||||
|{CLEARFIELD} |Clear the input field before typing
|
|{CLEARFIELD} |Clear the input field before typing
|
||||||
|{TOTP} |Insert calculated TOTP value (if configured)
|
|{TOTP} |Insert calculated TOTP value (if configured)
|
||||||
|
|{PICKCHARS} |Pick specific password characters from a dialog
|
||||||
|{<ACTION> X} |Repeat <ACTION> X times (e.g., {SPACE 5} inserts five spaces)
|
|{<ACTION> X} |Repeat <ACTION> X times (e.g., {SPACE 5} inserts five spaces)
|
||||||
|===
|
|===
|
||||||
+
|
+
|
||||||
|
@ -273,6 +273,7 @@ set(autotype_SOURCES
|
|||||||
autotype/AutoTypeMatchModel.cpp
|
autotype/AutoTypeMatchModel.cpp
|
||||||
autotype/AutoTypeMatchView.cpp
|
autotype/AutoTypeMatchView.cpp
|
||||||
autotype/AutoTypeSelectDialog.cpp
|
autotype/AutoTypeSelectDialog.cpp
|
||||||
|
autotype/PickcharsDialog.cpp
|
||||||
autotype/ShortcutWidget.cpp
|
autotype/ShortcutWidget.cpp
|
||||||
autotype/WindowSelectComboBox.cpp)
|
autotype/WindowSelectComboBox.cpp)
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "autotype/AutoTypePlatformPlugin.h"
|
#include "autotype/AutoTypePlatformPlugin.h"
|
||||||
#include "autotype/AutoTypeSelectDialog.h"
|
#include "autotype/AutoTypeSelectDialog.h"
|
||||||
|
#include "autotype/PickcharsDialog.h"
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
@ -564,6 +565,27 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
|
|||||||
for (const auto& ch : totp) {
|
for (const auto& ch : totp) {
|
||||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||||
}
|
}
|
||||||
|
} else if (placeholder == "pickchars") {
|
||||||
|
if (error) {
|
||||||
|
// Ignore this if we are syntax checking
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Show pickchars dialog for entry's password
|
||||||
|
auto password = entry->resolvePlaceholder(entry->password());
|
||||||
|
if (!password.isEmpty()) {
|
||||||
|
PickcharsDialog pickcharsDialog(password);
|
||||||
|
if (pickcharsDialog.exec() == QDialog::Accepted && !pickcharsDialog.selectedChars().isEmpty()) {
|
||||||
|
auto chars = pickcharsDialog.selectedChars();
|
||||||
|
auto iter = chars.begin();
|
||||||
|
while (iter != chars.end()) {
|
||||||
|
actions << QSharedPointer<AutoTypeKey>::create(*iter);
|
||||||
|
++iter;
|
||||||
|
if (pickcharsDialog.pressTab() && iter != chars.end()) {
|
||||||
|
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["tab"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (placeholder == "beep" || placeholder.startsWith("vkey")
|
} else if (placeholder == "beep" || placeholder.startsWith("vkey")
|
||||||
|| placeholder.startsWith("appactivate")) {
|
|| placeholder.startsWith("appactivate")) {
|
||||||
// Ignore these commands
|
// Ignore these commands
|
||||||
|
172
src/autotype/PickcharsDialog.cpp
Normal file
172
src/autotype/PickcharsDialog.cpp
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* 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 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PickcharsDialog.h"
|
||||||
|
#include "ui_PickcharsDialog.h"
|
||||||
|
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "gui/Icons.h"
|
||||||
|
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QShortcut>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
#include <QScreen>
|
||||||
|
#else
|
||||||
|
#include <QDesktopWidget>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PickcharsDialog::PickcharsDialog(const QString& string, QWidget* parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_ui(new Ui::PickcharsDialog())
|
||||||
|
{
|
||||||
|
if (string.isEmpty()) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Places the window on the active (virtual) desktop instead of where the main window is.
|
||||||
|
setAttribute(Qt::WA_X11BypassTransientForHint);
|
||||||
|
setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint)
|
||||||
|
& ~Qt::WindowContextHelpButtonHint);
|
||||||
|
setWindowIcon(icons()->applicationIcon());
|
||||||
|
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
// Increase max columns with longer passwords for better display
|
||||||
|
int width = 10;
|
||||||
|
if (string.length() >= 100) {
|
||||||
|
width = 20;
|
||||||
|
} else if (string.length() >= 60) {
|
||||||
|
width = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (const auto& ch : string) {
|
||||||
|
auto btn = new QPushButton(QString::number(count + 1));
|
||||||
|
btn->setProperty("char", ch);
|
||||||
|
btn->setProperty("count", count);
|
||||||
|
connect(btn, &QPushButton::clicked, this, &PickcharsDialog::charSelected);
|
||||||
|
m_ui->charsGrid->addWidget(btn, count / width, count % width);
|
||||||
|
m_lastSelected = count;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
// Prevent stretched buttons
|
||||||
|
if (m_ui->charsGrid->rowCount() == 1 && m_ui->charsGrid->columnCount() < 5) {
|
||||||
|
m_ui->charsGrid->addItem(new QSpacerItem(5, 5, QSizePolicy::MinimumExpanding), count / width, count % width);
|
||||||
|
}
|
||||||
|
m_ui->charsGrid->itemAtPosition(0, 0)->widget()->setFocus();
|
||||||
|
|
||||||
|
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
|
// Navigate grid layout using up/down/left/right motion
|
||||||
|
new QShortcut(Qt::Key_Up, this, SLOT(upPressed()));
|
||||||
|
new QShortcut(Qt::Key_Down, this, SLOT(downPressed()));
|
||||||
|
// Remove last selected character
|
||||||
|
auto shortcut = new QShortcut(Qt::Key_Backspace, this);
|
||||||
|
connect(shortcut, &QShortcut::activated, this, [this] {
|
||||||
|
auto text = m_ui->selectedChars->text();
|
||||||
|
m_ui->selectedChars->setText(text.left(text.size() - 1));
|
||||||
|
});
|
||||||
|
// Submit the form
|
||||||
|
shortcut = new QShortcut(Qt::CTRL + Qt::Key_S, this);
|
||||||
|
connect(shortcut, &QShortcut::activated, this, [this] { accept(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::upPressed()
|
||||||
|
{
|
||||||
|
auto focus = focusWidget();
|
||||||
|
if (!focus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = focus->property("count");
|
||||||
|
if (count.isValid()) {
|
||||||
|
// Lower bound not checked by QGridLayout::itemAt https://bugreports.qt.io/browse/QTBUG-91261
|
||||||
|
auto upCount = count.toInt() - m_ui->charsGrid->columnCount();
|
||||||
|
if (upCount >= 0) {
|
||||||
|
m_ui->charsGrid->itemAt(upCount)->widget()->setFocus();
|
||||||
|
}
|
||||||
|
} else if (focus == m_ui->selectedChars) {
|
||||||
|
// Move back to the last selected button
|
||||||
|
auto item = m_ui->charsGrid->itemAt(m_lastSelected);
|
||||||
|
if (item) {
|
||||||
|
item->widget()->setFocus();
|
||||||
|
}
|
||||||
|
} else if (focus == m_ui->pressTab) {
|
||||||
|
m_ui->selectedChars->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::downPressed()
|
||||||
|
{
|
||||||
|
auto focus = focusWidget();
|
||||||
|
if (!focus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = focus->property("count");
|
||||||
|
if (count.isValid()) {
|
||||||
|
auto item = m_ui->charsGrid->itemAt(count.toInt() + m_ui->charsGrid->columnCount());
|
||||||
|
if (item) {
|
||||||
|
item->widget()->setFocus();
|
||||||
|
} else {
|
||||||
|
// Store the currently selected button and move to the line edit
|
||||||
|
m_lastSelected = count.toInt();
|
||||||
|
m_ui->selectedChars->setFocus();
|
||||||
|
}
|
||||||
|
} else if (focus == m_ui->selectedChars) {
|
||||||
|
m_ui->pressTab->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PickcharsDialog::selectedChars()
|
||||||
|
{
|
||||||
|
return m_ui->selectedChars->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PickcharsDialog::pressTab()
|
||||||
|
{
|
||||||
|
return m_ui->pressTab->isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::charSelected()
|
||||||
|
{
|
||||||
|
auto btn = qobject_cast<QPushButton*>(sender());
|
||||||
|
if (!btn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->selectedChars->setText(m_ui->selectedChars->text() + btn->property("char").toChar());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::showEvent(QShowEvent* event)
|
||||||
|
{
|
||||||
|
QDialog::showEvent(event);
|
||||||
|
|
||||||
|
// Center on active screen
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
auto screen = QApplication::screenAt(QCursor::pos());
|
||||||
|
if (!screen) {
|
||||||
|
// screenAt can return a nullptr, default to the primary screen
|
||||||
|
screen = QApplication::primaryScreen();
|
||||||
|
}
|
||||||
|
QRect screenGeometry = screen->availableGeometry();
|
||||||
|
#else
|
||||||
|
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
|
||||||
|
#endif
|
||||||
|
move(screenGeometry.center().x() - (size().width() / 2), screenGeometry.center().y() - (size().height() / 2));
|
||||||
|
}
|
52
src/autotype/PickcharsDialog.h
Normal file
52
src/autotype/PickcharsDialog.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* 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 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSXC_PICKCHARSDIALOG_H
|
||||||
|
#define KEEPASSXC_PICKCHARSDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class PickcharsDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PickcharsDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PickcharsDialog(const QString& string, QWidget* parent = nullptr);
|
||||||
|
QString selectedChars();
|
||||||
|
bool pressTab();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent*) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void charSelected();
|
||||||
|
void upPressed();
|
||||||
|
void downPressed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<Ui::PickcharsDialog> m_ui;
|
||||||
|
int m_lastSelected;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_PICKCHARSDIALOG_H
|
87
src/autotype/PickcharsDialog.ui
Normal file
87
src/autotype/PickcharsDialog.ui
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>PickcharsDialog</class>
|
||||||
|
<widget class="QDialog" name="PickcharsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>418</width>
|
||||||
|
<height>188</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>KeePassXC - Pick Characters</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select characters to type, navigate with arrow keys, Ctrl + S submits.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="charsGrid"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="PasswordEdit" name="selectedChars">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="pressTab">
|
||||||
|
<property name="text">
|
||||||
|
<string>Press &Tab between characters</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>PasswordEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>gui/PasswordEdit.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>selectedChars</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
Loading…
Reference in New Issue
Block a user