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
|
||||
|{CLEARFIELD} |Clear the input field before typing
|
||||
|{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)
|
||||
|===
|
||||
+
|
||||
|
@ -273,6 +273,7 @@ set(autotype_SOURCES
|
||||
autotype/AutoTypeMatchModel.cpp
|
||||
autotype/AutoTypeMatchView.cpp
|
||||
autotype/AutoTypeSelectDialog.cpp
|
||||
autotype/PickcharsDialog.cpp
|
||||
autotype/ShortcutWidget.cpp
|
||||
autotype/WindowSelectComboBox.cpp)
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include "autotype/AutoTypePlatformPlugin.h"
|
||||
#include "autotype/AutoTypeSelectDialog.h"
|
||||
#include "autotype/PickcharsDialog.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
@ -564,6 +565,27 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
|
||||
for (const auto& ch : totp) {
|
||||
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")
|
||||
|| placeholder.startsWith("appactivate")) {
|
||||
// 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