mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Compare commits
15 Commits
17d58ecf1e
...
dc9510ec64
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc9510ec64 | ||
|
|
a98d3b7c64 | ||
|
|
6809c4da1b | ||
|
|
aeec2b8a98 | ||
|
|
d7c7ce4cc4 | ||
|
|
daf23b65ad | ||
|
|
d60826ad7f | ||
|
|
e19e7d2c43 | ||
|
|
abeb231bc3 | ||
|
|
1705c2c94a | ||
|
|
12a623f7d7 | ||
|
|
6eff15c9ec | ||
|
|
41b2a9bb60 | ||
|
|
1a7992cc57 | ||
|
|
7dfeb47212 |
@@ -32,7 +32,9 @@ To configure Auto-Type sequences for your entries, perform the following steps:
|
||||
|
||||
1. Navigate to the entries list and open the desired entry for editing. Click the _Auto-Type_ item from the left-hand menu bar *(1)*. Press the kbd:[+] button *(2)* to add a new sequence entry. Select the desired window using the drop-down menu, or simply type a window title in the box *(3)*.
|
||||
+
|
||||
TIP: You can use an asterisk (`\*`) to match any value (e.g., when a window title contains a dynamic filename or website name). Set the window title to `*` to match all windows. Leave the window title blank to offer additional default Auto-Type sequences, such as custom attributes.
|
||||
TIP: You can use an asterisk (`\*`) as a wildcard (e.g., when a window title contains a dynamic file or website name). Set the window title to `*` to match all windows. Leave the window title blank to offer additional sequences for every matching window. This is useful for typing individual custom attributes, for example.
|
||||
+
|
||||
TIP: To use a standard regular expression for window title matching, the window title must start and end with two forward slashes (e.g., `//^Secure Login - .*$//`).
|
||||
+
|
||||
.Auto-Type entry sequences
|
||||
image::autotype_entry_sequences.png[]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
if [ "$1" == "cli" ] || [ "$(basename "$ARGV0")" == "keepassxc-cli" ] || [ "$(basename "$ARGV0")" == "keepassxc-cli.AppImage" ]; then
|
||||
[ "$1" == "cli" ] && shift
|
||||
exec keepassxc-cli "$@"
|
||||
elif [ "$1" == "proxy" ] || [ "$(basename "$ARGV0")" == "keepassxc-proxy" ] || [ "$(basename "$ARGV0")" == "keepassxc-proxy.AppImage" ] \\
|
||||
elif [ "$1" == "proxy" ] || [ "$(basename "$ARGV0")" == "keepassxc-proxy" ] || [ "$(basename "$ARGV0")" == "keepassxc-proxy.AppImage" ] \
|
||||
|| [ -v CHROME_WRAPPER ] || [ -v MOZ_LAUNCHED_CHILD ]; then
|
||||
[ "$1" == "proxy" ] && shift
|
||||
exec keepassxc-proxy "$@"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@ Exec=keepassxc %f
|
||||
TryExec=keepassxc
|
||||
Icon=@APP_ICON_NAME@
|
||||
StartupWMClass=keepassxc
|
||||
StartupNotify=true
|
||||
StartupNotify=false
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Version=1.5
|
||||
|
||||
@@ -157,6 +157,25 @@
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AppKit</name>
|
||||
<message>
|
||||
<source>Window</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Minimize</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Zoom</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bring All to Front</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ApplicationSettingsWidget</name>
|
||||
<message>
|
||||
@@ -553,10 +572,6 @@
|
||||
<source>Export settings…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open browser on double clicking URL field in entry view</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Font size:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -569,6 +584,26 @@
|
||||
<source>Skip confirmation for main window Auto-Type actions</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Double-click action for URL:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Double-click action for URL field</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Edit entry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open entry URL in browser</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy entry URL to clipboard</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-generate password for new entries</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -876,6 +911,17 @@ Ctrl+Shift+4 - Copy URL<br/>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>BinaryStream</name>
|
||||
<message>
|
||||
<source>Failed to read string data: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>String length exceeds 10 MiB limit (requested %1)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>BrowserAccessControlDialog</name>
|
||||
<message>
|
||||
@@ -3770,14 +3816,6 @@ Supported extensions are: %1.</source>
|
||||
<source>Datetime modified</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Accessed:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Datetime accessed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uuid:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -4151,10 +4189,6 @@ Would you like to overwrite the existing attachment?</source>
|
||||
<source>Modified</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Accessed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attachments</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -4191,10 +4225,6 @@ Would you like to overwrite the existing attachment?</source>
|
||||
<source>Last modification date</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Last access date</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -6403,10 +6433,6 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
|
||||
<source>Found zero keys</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to read public key.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Corrupted key file, reading private key failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -6491,6 +6517,14 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
|
||||
<source>(encrypted)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to read key file: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to read public key: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>AES-256/GCM is currently not supported</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
||||
@@ -372,16 +372,30 @@ bool NativeMessageInstaller::createNativeMessageFile(SupportedBrowsers browser)
|
||||
|
||||
QFile scriptFile(path);
|
||||
if (!scriptFile.open(QIODevice::WriteOnly)) {
|
||||
qWarning() << "Browser Plugin: Failed to open native message file for writing at " << scriptFile.fileName();
|
||||
qWarning() << scriptFile.errorString();
|
||||
return false;
|
||||
if (!scriptFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Browser Plugin: Failed to open native message file at " << scriptFile.fileName();
|
||||
qWarning() << scriptFile.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We failed to write to `scriptFile`, but we can read it, so we assume that it's a read-only file.
|
||||
// Consider success if the read-only file already contains the content we would have written.
|
||||
QJsonDocument expectedDoc(constructFile(browser));
|
||||
QJsonDocument actualDoc = QJsonDocument::fromJson(scriptFile.readAll());
|
||||
|
||||
if (expectedDoc != actualDoc) {
|
||||
qWarning() << "Browser Plugin: Unexpected (read-only) native message file at " << scriptFile.fileName();
|
||||
qWarning() << "Expected contents: " << expectedDoc;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
QJsonDocument doc(constructFile(browser));
|
||||
if (scriptFile.write(doc.toJson()) < 0) {
|
||||
qWarning() << "Browser Plugin: Failed to write native message file at " << scriptFile.fileName();
|
||||
qWarning() << scriptFile.errorString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument doc(constructFile(browser));
|
||||
if (scriptFile.write(doc.toJson()) < 0) {
|
||||
qWarning() << "Browser Plugin: Failed to write native message file at " << scriptFile.fileName();
|
||||
qWarning() << scriptFile.errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,9 @@ namespace Utils
|
||||
SetConsoleMode(hIn, mode);
|
||||
#else
|
||||
struct termios t;
|
||||
tcgetattr(STDIN_FILENO, &t);
|
||||
if (tcgetattr(STDIN_FILENO, &t) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
t.c_lflag |= ECHO;
|
||||
|
||||
@@ -67,6 +67,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::SearchLimitGroup,{QS("SearchLimitGroup"), Roaming, false}},
|
||||
{Config::MinimizeOnOpenUrl,{QS("MinimizeOnOpenUrl"), Roaming, false}},
|
||||
{Config::OpenURLOnDoubleClick, {QS("OpenURLOnDoubleClick"), Roaming, true}},
|
||||
{Config::URLDoubleClickAction, {QS("URLDoubleClickAction"), Roaming, 0}},
|
||||
{Config::HideWindowOnCopy,{QS("HideWindowOnCopy"), Roaming, false}},
|
||||
{Config::MinimizeOnCopy,{QS("MinimizeOnCopy"), Roaming, true}},
|
||||
{Config::AutoGeneratePasswordForNewEntries,{QS("AutoGeneratePasswordForNewEntries"), Roaming, false}},
|
||||
@@ -118,6 +119,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::GUI_CheckForUpdates, {QS("GUI/CheckForUpdates"), Roaming, true}},
|
||||
{Config::GUI_CheckForUpdatesNextCheck, {QS("GUI/CheckForUpdatesNextCheck"), Local, 0}},
|
||||
{Config::GUI_CheckForUpdatesIncludeBetas, {QS("GUI/CheckForUpdatesIncludeBetas"), Roaming, false}},
|
||||
{Config::GUI_SearchWaitForEnter, {QS("GUI/SearchWaitForEnter"), Roaming, false}},
|
||||
{Config::GUI_ShowExpiredEntriesOnDatabaseUnlock, {QS("GUI/ShowExpiredEntriesOnDatabaseUnlock"), Roaming, true}},
|
||||
{Config::GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays, {QS("GUI/ShowExpiredEntriesOnDatabaseUnlockOffsetDays"), Roaming, 3}},
|
||||
{Config::GUI_FontSizeOffset, {QS("GUI/FontSizeOffset"), Local, 0}},
|
||||
@@ -489,6 +491,15 @@ void Config::migrate()
|
||||
remove(GUI_ListViewState);
|
||||
}
|
||||
|
||||
// Migrate from boolean OpenURLOnDoubleClick to enum URLDoubleClickAction
|
||||
if (m_settings->contains(configStrings[OpenURLOnDoubleClick].name)
|
||||
&& !m_settings->contains(configStrings[URLDoubleClickAction].name)) {
|
||||
bool openUrlOnDoubleClick = get(OpenURLOnDoubleClick).toBool();
|
||||
// Convert: true (open browser) -> 0, false (edit entry) -> 2
|
||||
set(URLDoubleClickAction, openUrlOnDoubleClick ? 0 : 2);
|
||||
// Keep the old setting for now for compatibility
|
||||
}
|
||||
|
||||
m_settings->setValue("ConfigVersion", CONFIG_VERSION);
|
||||
sync();
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
SearchLimitGroup,
|
||||
MinimizeOnOpenUrl,
|
||||
OpenURLOnDoubleClick,
|
||||
URLDoubleClickAction,
|
||||
HideWindowOnCopy,
|
||||
MinimizeOnCopy,
|
||||
MinimizeAfterUnlock,
|
||||
@@ -98,7 +99,7 @@ public:
|
||||
GUI_CompactMode,
|
||||
GUI_CheckForUpdates,
|
||||
GUI_CheckForUpdatesIncludeBetas,
|
||||
SearchWaitForEnter,
|
||||
GUI_SearchWaitForEnter,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlock,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays,
|
||||
GUI_FontSizeOffset,
|
||||
|
||||
@@ -334,12 +334,15 @@ QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
|
||||
};
|
||||
|
||||
QList<QString> sequenceList;
|
||||
QList<QString> emptyWindowSequences;
|
||||
|
||||
// Add window association matches
|
||||
const auto assocList = autoTypeAssociations()->getAll();
|
||||
for (const auto& assoc : assocList) {
|
||||
auto window = resolveMultiplePlaceholders(assoc.window);
|
||||
if (!assoc.window.isEmpty() && windowMatches(window)) {
|
||||
if (assoc.window.isEmpty()) {
|
||||
emptyWindowSequences << assoc.sequence;
|
||||
} else if (windowMatches(window)) {
|
||||
if (!assoc.sequence.isEmpty()) {
|
||||
sequenceList << assoc.sequence;
|
||||
} else {
|
||||
@@ -358,6 +361,11 @@ QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
|
||||
sequenceList << effectiveAutoTypeSequence();
|
||||
}
|
||||
|
||||
// If any associations were made, include the empty window associations
|
||||
if (!sequenceList.isEmpty()) {
|
||||
sequenceList.append(emptyWindowSequences);
|
||||
}
|
||||
|
||||
return sequenceList;
|
||||
}
|
||||
|
||||
|
||||
@@ -426,6 +426,29 @@ namespace Tools
|
||||
return filename.trimmed();
|
||||
}
|
||||
|
||||
QString cleanUsername()
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
QString userName = qgetenv("USERNAME");
|
||||
if (userName.isEmpty()) {
|
||||
userName = qgetenv("USER");
|
||||
}
|
||||
#else
|
||||
QString userName = qgetenv("USER");
|
||||
if (userName.isEmpty()) {
|
||||
userName = qgetenv("USERNAME");
|
||||
}
|
||||
#endif
|
||||
// Sanitize username for file safety
|
||||
userName = userName.trimmed();
|
||||
// Replace <>:"/\|?* with _
|
||||
userName.replace(QRegularExpression(R"([<>:\"\/\\|?*])"), "_");
|
||||
// Remove trailing dots and spaces
|
||||
userName.replace(QRegularExpression(R"([.\s]+$)"), "");
|
||||
|
||||
return userName;
|
||||
}
|
||||
|
||||
QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties)
|
||||
{
|
||||
QVariantMap result;
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace Tools
|
||||
QString envSubstitute(const QString& filepath,
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment());
|
||||
QString cleanFilename(QString filename);
|
||||
QString cleanUsername();
|
||||
|
||||
template <class T> QSet<T> asSet(const QList<T>& a)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Application.h"
|
||||
|
||||
#include "core/Bootstrap.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
@@ -31,6 +32,7 @@
|
||||
#include <QLocalSocket>
|
||||
#include <QLockFile>
|
||||
#include <QPixmapCache>
|
||||
#include <QRegularExpression>
|
||||
#include <QSocketNotifier>
|
||||
#include <QStandardPaths>
|
||||
|
||||
@@ -63,20 +65,19 @@ Application::Application(int& argc, char** argv)
|
||||
registerUnixSignals();
|
||||
#endif
|
||||
|
||||
QString userName = qgetenv("USER");
|
||||
if (userName.isEmpty()) {
|
||||
userName = qgetenv("USERNAME");
|
||||
}
|
||||
QString identifier = "keepassxc";
|
||||
if (!userName.isEmpty()) {
|
||||
identifier += "-" + userName;
|
||||
// Build identifier
|
||||
auto identifier = QStringLiteral("keepassxc");
|
||||
auto username = Tools::cleanUsername();
|
||||
if (!username.isEmpty()) {
|
||||
identifier += QChar('-') + username;
|
||||
}
|
||||
#ifdef QT_DEBUG
|
||||
// In DEBUG mode don't interfere with Release instances
|
||||
identifier += "-DEBUG";
|
||||
// In DEBUG mode don’t interfere with Release instances
|
||||
identifier += QStringLiteral("-DEBUG");
|
||||
#endif
|
||||
QString lockName = identifier + ".lock";
|
||||
m_socketName = identifier + ".socket";
|
||||
|
||||
QString lockName = identifier + QStringLiteral(".lock");
|
||||
m_socketName = identifier + QStringLiteral(".socket");
|
||||
|
||||
// According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect
|
||||
// this and creates sockets in TempLocation, so let's be consistent.
|
||||
|
||||
@@ -197,7 +197,7 @@ void ApplicationSettingsWidget::loadSettings()
|
||||
m_generalUi->autoReloadOnChangeCheckBox->setChecked(config()->get(Config::AutoReloadOnChange).toBool());
|
||||
m_generalUi->minimizeAfterUnlockCheckBox->setChecked(config()->get(Config::MinimizeAfterUnlock).toBool());
|
||||
m_generalUi->minimizeOnOpenUrlCheckBox->setChecked(config()->get(Config::MinimizeOnOpenUrl).toBool());
|
||||
m_generalUi->openUrlOnDoubleClick->setChecked(config()->get(Config::OpenURLOnDoubleClick).toBool());
|
||||
m_generalUi->urlDoubleClickComboBox->setCurrentIndex(config()->get(Config::URLDoubleClickAction).toInt());
|
||||
m_generalUi->hideWindowOnCopyCheckBox->setChecked(config()->get(Config::HideWindowOnCopy).toBool());
|
||||
hideWindowOnCopyCheckBoxToggled(m_generalUi->hideWindowOnCopyCheckBox->isChecked());
|
||||
m_generalUi->minimizeOnCopyRadioButton->setChecked(config()->get(Config::MinimizeOnCopy).toBool());
|
||||
@@ -382,7 +382,7 @@ void ApplicationSettingsWidget::saveSettings()
|
||||
config()->set(Config::AutoReloadOnChange, m_generalUi->autoReloadOnChangeCheckBox->isChecked());
|
||||
config()->set(Config::MinimizeAfterUnlock, m_generalUi->minimizeAfterUnlockCheckBox->isChecked());
|
||||
config()->set(Config::MinimizeOnOpenUrl, m_generalUi->minimizeOnOpenUrlCheckBox->isChecked());
|
||||
config()->set(Config::OpenURLOnDoubleClick, m_generalUi->openUrlOnDoubleClick->isChecked());
|
||||
config()->set(Config::URLDoubleClickAction, m_generalUi->urlDoubleClickComboBox->currentIndex());
|
||||
config()->set(Config::HideWindowOnCopy, m_generalUi->hideWindowOnCopyCheckBox->isChecked());
|
||||
config()->set(Config::MinimizeOnCopy, m_generalUi->minimizeOnCopyRadioButton->isChecked());
|
||||
config()->set(Config::DropToBackgroundOnCopy, m_generalUi->dropToBackgroundOnCopyRadioButton->isChecked());
|
||||
|
||||
@@ -554,14 +554,59 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="openUrlOnDoubleClick">
|
||||
<property name="text">
|
||||
<string>Open browser on double clicking URL field in entry view</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="urlDoubleClickLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="urlDoubleClickLabel">
|
||||
<property name="text">
|
||||
<string>Double-click action for URL:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>urlDoubleClickComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="urlDoubleClickComboBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Double-click action for URL field</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Open entry URL in browser</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Copy entry URL to clipboard</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Edit entry</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="urlDoubleClickSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useGroupIconOnEntryCreationCheckBox">
|
||||
@@ -1439,7 +1484,7 @@
|
||||
<tabstop>alternativeSaveComboBox</tabstop>
|
||||
<tabstop>ConfirmMoveEntryToRecycleBinCheckBox</tabstop>
|
||||
<tabstop>EnableCopyOnDoubleClickCheckBox</tabstop>
|
||||
<tabstop>openUrlOnDoubleClick</tabstop>
|
||||
<tabstop>urlDoubleClickComboBox</tabstop>
|
||||
<tabstop>useGroupIconOnEntryCreationCheckBox</tabstop>
|
||||
<tabstop>minimizeOnOpenUrlCheckBox</tabstop>
|
||||
<tabstop>hideWindowOnCopyCheckBox</tabstop>
|
||||
|
||||
@@ -550,6 +550,13 @@ void DatabaseWidget::copyTotp()
|
||||
if (!currentEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the entry has no TOTP set, open the setup dialog first
|
||||
if (!currentEntry->hasValidTotp()) {
|
||||
setupTotp();
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->totp());
|
||||
}
|
||||
|
||||
@@ -1426,12 +1433,21 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
||||
// case EntryModel::Attachments:
|
||||
// break;
|
||||
case EntryModel::Url:
|
||||
if (!entry->url().isEmpty() && config()->get(Config::OpenURLOnDoubleClick).toBool()) {
|
||||
openUrlForEntry(entry);
|
||||
break;
|
||||
if (!entry->url().isEmpty()) {
|
||||
switch (config()->get(Config::URLDoubleClickAction).toInt()) {
|
||||
case 2: // Edit entry
|
||||
switchToEntryEdit(entry);
|
||||
break;
|
||||
case 1: // Copy entry URL to clipboard
|
||||
setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->url()));
|
||||
break;
|
||||
case 0: // Open entry URL in browser (default)
|
||||
default:
|
||||
openUrlForEntry(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Note, order matters here. We want to fall into the default case.
|
||||
[[fallthrough]];
|
||||
break;
|
||||
default:
|
||||
switchToEntryEdit(entry);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ void EditWidgetProperties::setFields(const TimeInfo& timeInfo, const QUuid& uuid
|
||||
static const QString timeFormat("d MMM yyyy HH:mm:ss");
|
||||
m_ui->modifiedEdit->setText(timeInfo.lastModificationTime().toLocalTime().toString(timeFormat));
|
||||
m_ui->createdEdit->setText(timeInfo.creationTime().toLocalTime().toString(timeFormat));
|
||||
m_ui->accessedEdit->setText(timeInfo.lastAccessTime().toLocalTime().toString(timeFormat));
|
||||
m_ui->uuidEdit->setText(uuid.toRfc4122().toHex());
|
||||
}
|
||||
|
||||
|
||||
@@ -83,27 +83,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelAccessed">
|
||||
<property name="text">
|
||||
<string>Accessed:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="accessedEdit">
|
||||
<property name="accessibleName">
|
||||
<string>Datetime accessed</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelUuid">
|
||||
<property name="text">
|
||||
<string>Uuid:</string>
|
||||
@@ -113,7 +93,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="uuidEdit">
|
||||
<property name="accessibleName">
|
||||
<string>Unique ID</string>
|
||||
@@ -204,7 +184,6 @@
|
||||
<tabstops>
|
||||
<tabstop>createdEdit</tabstop>
|
||||
<tabstop>modifiedEdit</tabstop>
|
||||
<tabstop>accessedEdit</tabstop>
|
||||
<tabstop>uuidEdit</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
||||
@@ -90,6 +90,10 @@ MainWindow::MainWindow()
|
||||
|
||||
m_ui->setupUi(this);
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
macUtils()->configureWindowAndHelpMenus(this, m_ui->menuHelp);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS)
|
||||
new MainWindowAdaptor(this);
|
||||
QDBusConnection dbus = QDBusConnection::sessionBus();
|
||||
@@ -1027,7 +1031,7 @@ void MainWindow::updateMenuActionState()
|
||||
m_ui->actionEntryAutoTypeTOTP->setVisible(singleEntrySelected && dbWidget->currentEntryHasTotp());
|
||||
m_ui->actionEntryOpenUrl->setEnabled(singleEntryOrEditing && dbWidget->currentEntryHasUrl());
|
||||
m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
|
||||
m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
|
||||
m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryCopyPasswordTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
|
||||
m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryTotpQRCode->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp());
|
||||
|
||||
@@ -72,10 +72,10 @@ SearchWidget::SearchWidget(QWidget* parent)
|
||||
m_actionLimitGroup->setChecked(config()->get(Config::SearchLimitGroup).toBool());
|
||||
|
||||
m_actionWaitForEnter = m_searchMenu->addAction(
|
||||
tr("Press Enter to search"), this, [](bool state) { config()->set(Config::SearchWaitForEnter, state); });
|
||||
tr("Press Enter to search"), this, [](bool state) { config()->set(Config::GUI_SearchWaitForEnter, state); });
|
||||
m_actionWaitForEnter->setObjectName("actionSearchWaitForEnter");
|
||||
m_actionWaitForEnter->setCheckable(true);
|
||||
m_actionWaitForEnter->setChecked(config()->get(Config::SearchWaitForEnter).toBool());
|
||||
m_actionWaitForEnter->setChecked(config()->get(Config::GUI_SearchWaitForEnter).toBool());
|
||||
|
||||
m_ui->searchIcon->setIcon(icons()->icon("system-search"));
|
||||
m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition);
|
||||
|
||||
@@ -203,9 +203,6 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
case Modified:
|
||||
result = Clock::toString(entry->timeInfo().lastModificationTime().toLocalTime());
|
||||
return result;
|
||||
case Accessed:
|
||||
result = Clock::toString(entry->timeInfo().lastAccessTime().toLocalTime());
|
||||
return result;
|
||||
case Attachments: {
|
||||
// Display comma-separated list of attachments
|
||||
QList<QString> attachments = entry->attachments()->keys();
|
||||
@@ -266,8 +263,6 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
return entry->timeInfo().creationTime();
|
||||
case Modified:
|
||||
return entry->timeInfo().lastModificationTime();
|
||||
case Accessed:
|
||||
return entry->timeInfo().lastAccessTime();
|
||||
case Paperclip:
|
||||
// Display entries with attachments above those without when
|
||||
// sorting ascendingly (and vice versa when sorting descendingly)
|
||||
@@ -400,8 +395,6 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||
return tr("Created");
|
||||
case Modified:
|
||||
return tr("Modified");
|
||||
case Accessed:
|
||||
return tr("Accessed");
|
||||
case Attachments:
|
||||
return tr("Attachments");
|
||||
case Size:
|
||||
@@ -441,8 +434,6 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||
return tr("Creation date");
|
||||
case Modified:
|
||||
return tr("Last modification date");
|
||||
case Accessed:
|
||||
return tr("Last access date");
|
||||
case Attachments:
|
||||
return tr("Attached files");
|
||||
case Size:
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
Expires = 6,
|
||||
Created = 7,
|
||||
Modified = 8,
|
||||
Accessed = 9,
|
||||
Accessed = 9, // Kept for compatibility
|
||||
Paperclip = 10,
|
||||
Attachments = 11,
|
||||
Totp = 12,
|
||||
|
||||
@@ -105,12 +105,16 @@ EntryView::EntryView(QWidget* parent)
|
||||
m_columnActions->setExclusive(false);
|
||||
for (int visualIndex = 0; visualIndex < header()->count(); ++visualIndex) {
|
||||
int logicalIndex = header()->logicalIndex(visualIndex);
|
||||
QString caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
|
||||
if (caption.isEmpty()) {
|
||||
caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::ToolTipRole).toString();
|
||||
auto caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole);
|
||||
if (!caption.isValid()) {
|
||||
caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::ToolTipRole);
|
||||
if (!caption.isValid()) {
|
||||
// Ignored column, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto action = m_headerMenu->addAction(caption);
|
||||
auto action = m_headerMenu->addAction(caption.toString());
|
||||
action->setCheckable(true);
|
||||
action->setData(logicalIndex);
|
||||
m_columnActions->addAction(action);
|
||||
@@ -478,7 +482,6 @@ void EntryView::resetViewToDefaults()
|
||||
header()->hideSection(EntryModel::Password);
|
||||
header()->hideSection(EntryModel::Expires);
|
||||
header()->hideSection(EntryModel::Created);
|
||||
header()->hideSection(EntryModel::Accessed);
|
||||
header()->hideSection(EntryModel::Attachments);
|
||||
header()->hideSection(EntryModel::Size);
|
||||
header()->hideSection(EntryModel::PasswordStrength);
|
||||
@@ -515,6 +518,8 @@ void EntryView::resetViewToDefaults()
|
||||
void EntryView::onHeaderChanged()
|
||||
{
|
||||
m_model->setBackgroundColorVisible(isColumnHidden(EntryModel::Color));
|
||||
// Force hide accessed column
|
||||
header()->hideSection(EntryModel::Accessed);
|
||||
}
|
||||
|
||||
void EntryView::showEvent(QShowEvent* event)
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include <QColor>
|
||||
#include <QObject>
|
||||
#include <QMenu>
|
||||
#include <QMainWindow>
|
||||
#include <unistd.h>
|
||||
|
||||
class QWindow;
|
||||
@@ -45,6 +47,7 @@ public:
|
||||
bool enableScreenRecording();
|
||||
void toggleForegroundApp(bool foreground);
|
||||
void setWindowSecurity(QWindow* window, bool state);
|
||||
void configureWindowAndHelpMenus(QMainWindow* mainWindow, QMenu* helpMenu);
|
||||
|
||||
signals:
|
||||
void userSwitched();
|
||||
|
||||
@@ -42,5 +42,6 @@
|
||||
- (bool) enableScreenRecording;
|
||||
- (void) toggleForegroundApp:(bool) foreground;
|
||||
- (void) setWindowSecurity:(NSWindow*) window state:(bool) state;
|
||||
- (void) configureWindowAndHelpMenus:(QMainWindow*) mainWindow helpMenu:(QMenu*) helpMenu;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#import "AppKitImpl.h"
|
||||
#import <QWindow>
|
||||
#import <QMenu>
|
||||
#import <QMenuBar>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#if __clang_major__ >= 13 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_3
|
||||
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||
@@ -231,8 +233,29 @@
|
||||
[window setSharingType: state ? NSWindowSharingNone : NSWindowSharingReadOnly];
|
||||
}
|
||||
|
||||
- (void) configureWindowAndHelpMenus:(QMainWindow*) mainWindow helpMenu:(QMenu*) helpMenu
|
||||
{
|
||||
QMenu *qtWindowMenu = new QMenu(AppKit::tr("Window"));
|
||||
NSMenu *nsWindowMenu = qtWindowMenu->toNSMenu();
|
||||
|
||||
QString minimizeStr = AppKit::tr("Minimize");
|
||||
[nsWindowMenu addItemWithTitle:minimizeStr.toNSString() action:@selector(performMiniaturize:) keyEquivalent:@""];
|
||||
QString zoomStr = AppKit::tr("Zoom");
|
||||
[nsWindowMenu addItemWithTitle:zoomStr.toNSString() action:@selector(performZoom:) keyEquivalent:@""];
|
||||
[nsWindowMenu addItem:[NSMenuItem separatorItem]];
|
||||
QString bringAllToFrontStr = AppKit::tr("Bring All to Front");
|
||||
[nsWindowMenu addItemWithTitle:bringAllToFrontStr.toNSString() action:@selector(arrangeInFront:) keyEquivalent:@""];
|
||||
|
||||
NSApp.windowsMenu = nsWindowMenu;
|
||||
|
||||
mainWindow->menuBar()->insertMenu(helpMenu->menuAction(), qtWindowMenu);
|
||||
|
||||
NSApp.helpMenu = helpMenu->toNSMenu();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
//
|
||||
// ------------------------- C++ Trampolines -------------------------
|
||||
//
|
||||
@@ -312,3 +335,8 @@ void AppKit::setWindowSecurity(QWindow* window, bool state)
|
||||
auto view = reinterpret_cast<NSView*>(window->winId());
|
||||
[static_cast<id>(self) setWindowSecurity:view.window state:state];
|
||||
}
|
||||
|
||||
void AppKit::configureWindowAndHelpMenus(QMainWindow* window, QMenu* helpMenu)
|
||||
{
|
||||
[static_cast<id>(self) configureWindowAndHelpMenus:window helpMenu:helpMenu];
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
#include <QMenu>
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
@@ -202,6 +203,11 @@ void MacUtils::registerNativeEventFilter()
|
||||
::InstallApplicationEventHandler(MacUtils::hotkeyHandler, 1, &eventSpec, this, nullptr);
|
||||
}
|
||||
|
||||
void MacUtils::configureWindowAndHelpMenus(QMainWindow* mainWindow, QMenu* helpMenu)
|
||||
{
|
||||
return m_appkit->configureWindowAndHelpMenus(mainWindow, helpMenu);
|
||||
}
|
||||
|
||||
bool MacUtils::registerGlobalShortcut(const QString& name, Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error)
|
||||
{
|
||||
auto keycode = qtToNativeKeyCode(key);
|
||||
|
||||
@@ -54,6 +54,8 @@ public:
|
||||
|
||||
void registerNativeEventFilter() override;
|
||||
|
||||
void configureWindowAndHelpMenus(QMainWindow* mainWindow, QMenu* helpMenu);
|
||||
|
||||
bool registerGlobalShortcut(const QString& name,
|
||||
Qt::Key key,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
|
||||
@@ -158,7 +158,7 @@ void NixUtils::setLaunchAtStartup(bool enable)
|
||||
<< QStringLiteral("TryExec=") << executeablePathOrName << '\n'
|
||||
<< QStringLiteral("Icon=") << QApplication::applicationName().toLower() << '\n'
|
||||
<< QStringLiteral("StartupWMClass=keepassxc") << '\n'
|
||||
<< QStringLiteral("StartupNotify=true") << '\n'
|
||||
<< QStringLiteral("StartupNotify=false") << '\n'
|
||||
<< QStringLiteral("Terminal=false") << '\n'
|
||||
<< QStringLiteral("Type=Application") << '\n'
|
||||
<< QStringLiteral("Version=1.0") << '\n'
|
||||
|
||||
@@ -56,9 +56,9 @@ namespace
|
||||
// but those cases with high propability constructed examples and very rare in real usage
|
||||
const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid());
|
||||
const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue);
|
||||
targetEntry->setUpdateTimeinfo(false);
|
||||
targetEntry->beginUpdate();
|
||||
targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute));
|
||||
targetEntry->setUpdateTimeinfo(true);
|
||||
targetEntry->endUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "BinaryStream.h"
|
||||
#include "core/Tools.h"
|
||||
#include <QtEndian>
|
||||
|
||||
BinaryStream::BinaryStream(QIODevice* device)
|
||||
@@ -116,9 +117,16 @@ bool BinaryStream::readString(QByteArray& ba)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't attempt to read strings over 10 MiB
|
||||
if (length > 1024 * 1024 * 10) {
|
||||
m_error = tr("String length exceeds 10 MiB limit (requested %1)").arg(Tools::humanReadableFileSize(length, 0));
|
||||
return false;
|
||||
}
|
||||
|
||||
ba.resize(length);
|
||||
|
||||
if (!read(ba.data(), ba.length())) {
|
||||
m_error = tr("Failed to read string data: %1").arg(m_device->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,9 +247,10 @@ bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.readString(m_cipherName);
|
||||
stream.readString(m_kdfName);
|
||||
stream.readString(m_kdfOptions);
|
||||
if (!stream.readString(m_cipherName) || !stream.readString(m_kdfName) || !stream.readString(m_kdfOptions)) {
|
||||
m_error = tr("Failed to read key file: %1").arg(stream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
quint32 numberOfKeys;
|
||||
stream.read(numberOfKeys);
|
||||
@@ -262,7 +263,7 @@ bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
|
||||
for (quint32 i = 0; i < numberOfKeys; ++i) {
|
||||
QByteArray publicKey;
|
||||
if (!stream.readString(publicKey)) {
|
||||
m_error = tr("Failed to read public key.");
|
||||
m_error = tr("Failed to read public key: %1").arg(stream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,20 @@ void TestAutoType::init()
|
||||
m_entry5->setPassword("example5");
|
||||
m_entry5->setTitle("some title");
|
||||
m_entry5->setUrl("http://example.org");
|
||||
|
||||
m_entry6 = new Entry();
|
||||
m_entry6->setGroup(m_group);
|
||||
m_entry6->setPassword("example6");
|
||||
m_entry6->setTitle("empty window test");
|
||||
association.window = "";
|
||||
association.sequence = "{S:Empty Window}";
|
||||
m_entry6->autoTypeAssociations()->add(association);
|
||||
association.window = "non-matching window";
|
||||
association.sequence = "should not match";
|
||||
m_entry6->autoTypeAssociations()->add(association);
|
||||
association.window = "*notepad*";
|
||||
association.sequence = "{USERNAME}";
|
||||
m_entry6->autoTypeAssociations()->add(association);
|
||||
}
|
||||
|
||||
void TestAutoType::cleanup()
|
||||
@@ -444,3 +458,13 @@ void TestAutoType::testAutoTypeEffectiveSequences()
|
||||
QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan);
|
||||
QCOMPARE(entry6->effectiveAutoTypeSequence(), QString());
|
||||
}
|
||||
|
||||
void TestAutoType::testAutoTypeEmptyWindowAssociation()
|
||||
{
|
||||
auto assoc = m_entry6->autoTypeSequences("Windows Notepad");
|
||||
QCOMPARE(assoc.size(), 2);
|
||||
QVERIFY(assoc.contains("{S:Empty Window}"));
|
||||
|
||||
assoc = m_entry6->autoTypeSequences("Some Other Window");
|
||||
QVERIFY(assoc.isEmpty());
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ private slots:
|
||||
void testAutoTypeResults_data();
|
||||
void testAutoTypeSyntaxChecks();
|
||||
void testAutoTypeEffectiveSequences();
|
||||
void testAutoTypeEmptyWindowAssociation();
|
||||
|
||||
private:
|
||||
AutoTypePlatformInterface* m_platform;
|
||||
@@ -64,6 +65,7 @@ private:
|
||||
Entry* m_entry3;
|
||||
Entry* m_entry4;
|
||||
Entry* m_entry5;
|
||||
Entry* m_entry6;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTAUTOTYPE_H
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "TestConfig.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QTest>
|
||||
|
||||
#include "config-keepassx-tests.h"
|
||||
@@ -40,3 +41,39 @@ void TestConfig::testUpgrade()
|
||||
|
||||
tempFile.remove();
|
||||
}
|
||||
|
||||
void TestConfig::testURLDoubleClickMigration()
|
||||
{
|
||||
// Test migration from OpenURLOnDoubleClick to URLDoubleClickAction
|
||||
TemporaryFile tempFile;
|
||||
tempFile.open();
|
||||
|
||||
// Create a config with old setting = true (open browser)
|
||||
QSettings oldConfig(tempFile.fileName(), QSettings::IniFormat);
|
||||
oldConfig.setValue("OpenURLOnDoubleClick", true);
|
||||
oldConfig.sync();
|
||||
tempFile.close();
|
||||
|
||||
Config::createConfigFromFile(tempFile.fileName());
|
||||
|
||||
// Should migrate to URLDoubleClickAction = 0 (open browser)
|
||||
QCOMPARE(config()->get(Config::URLDoubleClickAction).toInt(), 0);
|
||||
|
||||
tempFile.remove();
|
||||
|
||||
// Test migration from OpenURLOnDoubleClick = false (edit entry)
|
||||
TemporaryFile tempFile2;
|
||||
tempFile2.open();
|
||||
|
||||
QSettings oldConfig2(tempFile2.fileName(), QSettings::IniFormat);
|
||||
oldConfig2.setValue("OpenURLOnDoubleClick", false);
|
||||
oldConfig2.sync();
|
||||
tempFile2.close();
|
||||
|
||||
Config::createConfigFromFile(tempFile2.fileName());
|
||||
|
||||
// Should migrate to URLDoubleClickAction = 2 (edit entry)
|
||||
QCOMPARE(config()->get(Config::URLDoubleClickAction).toInt(), 2);
|
||||
|
||||
tempFile2.remove();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class TestConfig : public QObject
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testUpgrade();
|
||||
void testURLDoubleClickMigration();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTCONFIG_H
|
||||
|
||||
@@ -428,3 +428,26 @@ void TestTools::testIsTextMimeType()
|
||||
QVERIFY(!Tools::isTextMimeType(noText));
|
||||
}
|
||||
}
|
||||
|
||||
// Test sanitization logic for Tools::cleanUsername
|
||||
void TestTools::testCleanUsername()
|
||||
{
|
||||
// Test vars
|
||||
QFETCH(QString, input);
|
||||
QFETCH(QString, expected);
|
||||
|
||||
qputenv("USER", input.toUtf8());
|
||||
qputenv("USERNAME", input.toUtf8());
|
||||
QCOMPARE(Tools::cleanUsername(), expected);
|
||||
}
|
||||
|
||||
void TestTools::testCleanUsername_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<QString>("expected");
|
||||
|
||||
QTest::newRow("Leading and trailing spaces") << " user " << "user";
|
||||
QTest::newRow("Special characters") << R"(user<>:"/\|?*name)" << "user_________name";
|
||||
QTest::newRow("Trailing dots and spaces") << "username... " << "username";
|
||||
QTest::newRow("Combination of issues") << R"( user<>:"/\|?*name... )" << "user_________name";
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ private slots:
|
||||
void testGetMimeType();
|
||||
void testGetMimeTypeByFileInfo();
|
||||
void testIsTextMimeType();
|
||||
void testCleanUsername();
|
||||
void testCleanUsername_data();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTTOOLS_H
|
||||
|
||||
Reference in New Issue
Block a user