mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Compare commits
11 Commits
f927c4c41a
...
592d553ff8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
592d553ff8 | ||
|
|
a709f14cf3 | ||
|
|
9031cb530e | ||
|
|
ebf0676661 | ||
|
|
6130a64be5 | ||
|
|
9814037fd3 | ||
|
|
8c8ae49240 | ||
|
|
1e370b8ab8 | ||
|
|
cd9bb483fe | ||
|
|
2cc2c905b5 | ||
|
|
d9ccf767d0 |
@@ -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[]
|
||||
|
||||
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>
|
||||
@@ -561,10 +580,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>
|
||||
@@ -577,6 +592,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>
|
||||
|
||||
@@ -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}},
|
||||
@@ -491,6 +493,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();
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public:
|
||||
SearchLimitGroup,
|
||||
MinimizeOnOpenUrl,
|
||||
OpenURLOnDoubleClick,
|
||||
URLDoubleClickAction,
|
||||
HideWindowOnCopy,
|
||||
MinimizeOnCopy,
|
||||
MinimizeAfterUnlock,
|
||||
@@ -99,7 +100,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.
|
||||
|
||||
@@ -211,7 +211,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());
|
||||
@@ -389,7 +389,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>
|
||||
|
||||
@@ -1583,12 +1583,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);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -184,7 +186,7 @@
|
||||
//
|
||||
// Check if screen recording is enabled, may show an popup asking for permissions
|
||||
//
|
||||
- (bool) enableScreenRecording
|
||||
- (bool) enableScreenRecording
|
||||
{
|
||||
#if __clang_major__ >= 13 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_3
|
||||
if (@available(macOS 12.3, *)) {
|
||||
@@ -192,7 +194,7 @@
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
// Attempt to use SCShareableContent to check for screen recording permission
|
||||
[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent * _Nullable content,
|
||||
[SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent * _Nullable content,
|
||||
NSError * _Nullable error) {
|
||||
Q_UNUSED(error);
|
||||
if (content) {
|
||||
@@ -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,
|
||||
|
||||
@@ -156,7 +156,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 probability 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,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()
|
||||
@@ -446,3 +460,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