Compare commits

...

11 Commits

Author SHA1 Message Date
Juzu-O
592d553ff8 Add URL double-click action option to Settings (#12322)
* Closes #4717

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com>
Co-authored-by: Jonathan White <support@dmapps.us>
2025-11-02 12:32:11 -05:00
MNarath
a709f14cf3 Fix KeeShare entries with references not updating (#11809)
A Entry that gets shared containing a reference Attribute would not write a history entry upon resolving said Attribute resulting in the import into the target database not beeing triggered despite the changes beeing written to the keeshare db.
2025-11-02 12:29:14 -05:00
Isaac Elliott
9031cb530e Allow read-only native message files (#12236)
* Allow read-only native message files

It's possible[^1] for a native message file to be both correct and read-only.
When current versions of `keepassxc` encounter this, it fails, because
it can't write to the file. In this situation it should only fail if
the read-only file's contents are different to those it's trying to
write.

[^1]: e.g. when using an immutable OS management system like NixOS or
   home-manager.

---------

Co-authored-by: Jonathan White <support@dmapps.us>
2025-11-02 10:17:06 -05:00
Jonathan White
ebf0676661 Fix Auto-Type Empty Window Behavior
* Fixes #9282
* Also improve documentation for window title matching behavior
2025-11-02 10:16:41 -05:00
Sebastian Livoni
6130a64be5 Add Window menu for macOS and specify Help menu to AppKit (#12357)
* Add Window menu for macOS and specify Help menu to AppKit
* Fix potential NSString dangling pointers of temporary QStrings
2025-11-02 10:16:22 -05:00
renner
9814037fd3 feat: refresh appdata.xml
* Adds donation, contact and up-to-date transifex URL
* Add features to appdata.xml for FlatHub
* Remove old releases to reduce file size
* Improve summary and description text
2025-11-02 09:08:07 -05:00
renner
8c8ae49240 chore: reformat xml with GUI tool 2025-11-02 09:08:07 -05:00
Chris Bednarski
1e370b8ab8 Change StartupNotify to false
StartupNotify causes KeepassXC to hang on startup until the notification timeout is reached, making the KeepassXC window unavailable in the application switcher (i.e. alt-tab) on various Linux distros.

Fixes https://github.com/keepassxreboot/keepassxc/issues/6423
Fixes https://github.com/keepassxreboot/keepassxc/issues/11664
2025-11-02 08:58:02 -05:00
Jonathan White
cd9bb483fe Fix saving "Search Wait for Enter" setting 2025-11-02 06:24:36 -05:00
Sertonix
2cc2c905b5 Fix uninitialized memory when --pw-stdin is used with a pipe 2025-11-01 19:56:11 -04:00
Siddhant Shekhar
d9ccf767d0 Sanitize username to prevent single-instance detection failure (#12559)
---------

Co-authored-by: Jonathan White <support@dmapps.us>
2025-11-01 10:25:10 -04:00
30 changed files with 1039 additions and 1305 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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 dont 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.

View File

@@ -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());

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();

View File

@@ -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

View File

@@ -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];
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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'

View File

@@ -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();
}
}

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -25,6 +25,7 @@ class TestConfig : public QObject
Q_OBJECT
private slots:
void testUpgrade();
void testURLDoubleClickMigration();
};
#endif // KEEPASSX_TESTCONFIG_H

View File

@@ -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";
}

View File

@@ -41,6 +41,8 @@ private slots:
void testGetMimeType();
void testGetMimeTypeByFileInfo();
void testIsTextMimeType();
void testCleanUsername();
void testCleanUsername_data();
};
#endif // KEEPASSX_TESTTOOLS_H