diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..5dc8fc3b --- /dev/null +++ b/.clang-format @@ -0,0 +1,89 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: true + AfterFunction: true + AfterControlStatement: false + AfterEnum: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... + diff --git a/CMakeLists.txt b/CMakeLists.txt index 04136751..881e0bdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) option(WITH_ASAN "Enable address sanitizer checks (Linux only)" OFF) option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF) +option(WITH_APP_BUNDLE "Enable Application Bundle for OS X" ON) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF) @@ -77,6 +78,10 @@ endmacro(add_gcc_compiler_flags) add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) +if(WITH_APP_BUNDLE) + add_definitions(-DWITH_APP_BUNDLE) +endif() + add_gcc_compiler_flags("-fno-common") add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long") add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute") @@ -164,13 +169,13 @@ if(MINGW) endif() endif() -if(APPLE OR MINGW) +if(APPLE AND WITH_APP_BUNDLE OR MINGW) set(PROGNAME KeePassXC) else() set(PROGNAME keepassxc) endif() -if(APPLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") +if(APPLE AND WITH_APP_BUNDLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") set(CMAKE_INSTALL_PREFIX "/Applications") endif() @@ -179,7 +184,7 @@ if(MINGW) set(BIN_INSTALL_DIR ".") set(PLUGIN_INSTALL_DIR ".") set(DATA_INSTALL_DIR "share") -elseif(APPLE) +elseif(APPLE AND WITH_APP_BUNDLE) set(CLI_INSTALL_DIR "/usr/local/bin") set(BIN_INSTALL_DIR ".") set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns") diff --git a/COPYING b/COPYING index 4dbaece3..d3638401 100644 --- a/COPYING +++ b/COPYING @@ -41,6 +41,8 @@ Files: share/icons/application/*/apps/keepassxc.png share/icons/application/scalable/apps/keepassxc-dark.svgz share/icons/application/*/apps/keepassxc-locked.png share/icons/application/scalable/apps/keepassxc-locked.svgz + share/icons/application/*/apps/keepassxc-unlocked.png + share/icons/application/scalable/apps/keepassxc-unlocked.svgz share/icons/application/*/mimetypes/application-x-keepassxc.png share/icons/application/scalable/mimetypes/application-x-keepassxc.svgz Copyright: 2016, Lorenzo Stella diff --git a/README.md b/README.md index 18a025aa..bf214d3c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org ### Building KeePassXC -*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).* +*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).* First, you must download the KeePassXC [source tarball](https://keepassxc.org/download#source) or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc). diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 9660d750..7d01a1c4 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -89,6 +89,22 @@ add_custom_target(icons COMMAND inkscape -z -w 256 -h 256 icons/application/scalable/apps/keepassxc-locked.svgz -e icons/application/256x256/apps/keepassxc-locked.png + # SVGZ to PNGs for KeePassXC + COMMAND inkscape -z -w 16 -h 16 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/16x16/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 24 -h 24 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/24x24/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 32 -h 32 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/32x32/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 48 -h 48 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/48x48/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 64 -h 64 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/64x64/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 128 -h 128 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/128x128/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 256 -h 256 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/256x256/apps/keepassxc-unlocked.png + # SVGZ to PNGs for KeePassXC MIME-Type COMMAND inkscape -z -w 16 -h 16 icons/application/scalable/mimetypes/application-x-keepassxc.svgz -e icons/application/16x16/mimetypes/application-x-keepassxc.png diff --git a/share/icons/application/128x128/apps/keepassxc-unlocked.png b/share/icons/application/128x128/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..69b0fe24 Binary files /dev/null and b/share/icons/application/128x128/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/16x16/apps/keepassxc-unlocked.png b/share/icons/application/16x16/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..2164f033 Binary files /dev/null and b/share/icons/application/16x16/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/24x24/apps/keepassxc-unlocked.png b/share/icons/application/24x24/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..68c6cabc Binary files /dev/null and b/share/icons/application/24x24/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/256x256/apps/keepassxc-unlocked.png b/share/icons/application/256x256/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..d1c11781 Binary files /dev/null and b/share/icons/application/256x256/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/32x32/apps/keepassxc-unlocked.png b/share/icons/application/32x32/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..de06bf03 Binary files /dev/null and b/share/icons/application/32x32/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/48x48/apps/keepassxc-unlocked.png b/share/icons/application/48x48/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..06a563d1 Binary files /dev/null and b/share/icons/application/48x48/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/64x64/apps/keepassxc-unlocked.png b/share/icons/application/64x64/apps/keepassxc-unlocked.png new file mode 100644 index 00000000..8ade0e85 Binary files /dev/null and b/share/icons/application/64x64/apps/keepassxc-unlocked.png differ diff --git a/share/icons/application/scalable/apps/keepassxc-unlocked.svgz b/share/icons/application/scalable/apps/keepassxc-unlocked.svgz new file mode 100644 index 00000000..84ce1390 Binary files /dev/null and b/share/icons/application/scalable/apps/keepassxc-unlocked.svgz differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0fb3ab5..1c890e16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,10 @@ set(keepassx_SOURCES core/PasswordGenerator.cpp core/PassphraseGenerator.cpp core/SignalMultiplexer.cpp + core/ScreenLockListener.cpp + core/ScreenLockListener.h + core/ScreenLockListenerPrivate.h + core/ScreenLockListenerPrivate.cpp core/TimeDelta.cpp core/TimeInfo.cpp core/ToDbExporter.cpp @@ -136,6 +140,24 @@ set(keepassx_SOURCES totp/totp.h totp/totp.cpp ) +if(APPLE) + set(keepassx_SOURCES ${keepassx_SOURCES} + core/ScreenLockListenerMac.h + core/ScreenLockListenerMac.cpp + ) +endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(keepassx_SOURCES ${keepassx_SOURCES} + core/ScreenLockListenerDBus.h + core/ScreenLockListenerDBus.cpp + ) +endif() +if(MINGW) + set(keepassx_SOURCES ${keepassx_SOURCES} + core/ScreenLockListenerWin.h + core/ScreenLockListenerWin.cpp + ) +endif() set(keepassx_SOURCES_MAINEXE main.cpp @@ -201,9 +223,16 @@ target_link_libraries(keepassx_core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) + +if(APPLE) + target_link_libraries(keepassx_core "-framework Foundation") +endif() if (UNIX AND NOT APPLE) target_link_libraries(keepassx_core Qt5::DBus) endif() +if(MINGW) + target_link_libraries(keepassx_core Wtsapi32.lib) +endif() if(MINGW) include(GenerateProductVersion) @@ -217,14 +246,15 @@ if(MINGW) ) endif() -add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) +add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) target_link_libraries(${PROGNAME} keepassx_core) set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) -if(APPLE) +if(APPLE AND WITH_APP_BUNDLE) configure_file(${CMAKE_SOURCE_DIR}/share/macosx/Info.plist.cmake ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) set_target_properties(${PROGNAME} PROPERTIES + MACOSX_BUNDLE ON MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) endif() @@ -232,7 +262,7 @@ install(TARGETS ${PROGNAME} BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) -if(APPLE) +if(APPLE AND WITH_APP_BUNDLE) if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib") install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib" DESTINATION "${DATA_INSTALL_DIR}") diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 12367fe9..6a066d4c 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -574,8 +574,9 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->resolvePlaceholder(entry->title()).isEmpty() - && windowTitle.contains(entry->resolvePlaceholder(entry->title()), Qt::CaseInsensitive)) { + if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() + && (windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title())) + || windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url())))) { sequence = entry->defaultAutoTypeSequence(); match = true; } @@ -631,3 +632,22 @@ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPa return WildcardMatcher(windowTitle).match(windowPattern); } } + +bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle) +{ + return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive); +} + +bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl) +{ + if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) { + return true; + } + + QUrl url(resolvedUrl); + if (url.isValid() && !url.host().isEmpty()) { + return windowTitle.contains(url.host(), Qt::CaseInsensitive); + } + + return false; +} diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 311eedaa..ea5c9561 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -66,6 +66,8 @@ private: bool parseActions(const QString& sequence, const Entry* entry, QList& actions); QList createActionFromTemplate(const QString& tmpl, const Entry* entry); QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString()); + bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle); + bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl); bool windowMatches(const QString& windowTitle, const QString& windowPattern); bool m_inAutoType; diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index 076dd503..ac93de0e 100644 --- a/src/autotype/mac/CMakeLists.txt +++ b/src/autotype/mac/CMakeLists.txt @@ -12,9 +12,15 @@ target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets if(NOT DEFINED QT_BINARY_DIR) set(QT_BINARY_DIR "/usr/local/opt/qt5/bin" CACHE PATH "QT binary folder") endif() -add_custom_command(TARGET keepassx-autotype-cocoa - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} - COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src -COMMENT "Deploying autotype plugin") +if(WITH_APP_BUNDLE) + add_custom_command(TARGET keepassx-autotype-cocoa + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} + COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src + COMMENT "Deploying autotype plugin") +else() + install(TARGETS keepassx-autotype-cocoa + BUNDLE DESTINATION . COMPONENT Runtime + LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) +endif() diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index e090ad1d..86edb9c0 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,6 +14,8 @@ # along with this program. If not, see . set(cli_SOURCES + Clip.cpp + Clip.h EntropyMeter.cpp EntropyMeter.h Extract.cpp diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp new file mode 100644 index 00000000..f587b3f1 --- /dev/null +++ b/src/cli/Clip.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "Clip.h" + +#include +#include +#include +#include +#include + +#include "gui/UnlockDatabaseDialog.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "gui/Clipboard.h" + +int Clip::execute(int argc, char** argv) +{ + + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << QString(argv[i]); + } + QTextStream out(stdout); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", "Copy a password to the clipboard")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + QCommandLineOption guiPrompt( + QStringList() << "g" + << "gui-prompt", + QCoreApplication::translate("main", "Use a GUI prompt unlocking the database.")); + parser.addOption(guiPrompt); + parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to clip.")); + parser.process(arguments); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 2) { + QCoreApplication app(argc, argv); + parser.showHelp(); + return EXIT_FAILURE; + } + + Database* db = nullptr; + QApplication app(argc, argv); + if (parser.isSet("gui-prompt")) { + db = UnlockDatabaseDialog::openDatabasePrompt(args.at(0)); + } else { + db = Database::unlockFromStdin(args.at(0)); + } + + if (!db) { + return EXIT_FAILURE; + } + + QString entryId = args.at(1); + Entry* entry = db->rootGroup()->findEntry(entryId); + if (!entry) { + qCritical("Entry %s not found.", qPrintable(entryId)); + return EXIT_FAILURE; + } + + Clipboard::instance()->setText(entry->password()); + return EXIT_SUCCESS; +} diff --git a/src/cli/Clip.h b/src/cli/Clip.h new file mode 100644 index 00000000..94418409 --- /dev/null +++ b/src/cli/Clip.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_CLIP_H +#define KEEPASSXC_CLIP_H + +class Clip +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_CLIP_H diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 81a9ddf0..74fa33da 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -30,14 +30,14 @@ #include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" -int Extract::execute(int argc, char **argv) +int Extract::execute(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", - "Extract and print the content of a database.")); + parser.setApplicationDescription( + QCoreApplication::translate("main", "Extract and print the content of a database.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database to extract.")); parser.process(app); @@ -75,8 +75,7 @@ int Extract::execute(int argc, char **argv) if (reader.hasError()) { if (xmlData.isEmpty()) { qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString())); - } - else { + } else { qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); } return EXIT_FAILURE; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 1e348810..8dd250b3 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -30,40 +30,20 @@ #include "core/Group.h" #include "keys/CompositeKey.h" -void printGroup(Group* group, QString baseName, int depth) { - QTextStream out(stdout); - - QString groupName = baseName + group->name() + "/"; - QString indentation = QString(" ").repeated(depth); - - out << indentation << groupName << " " << group->uuid().toHex() << "\n"; - out.flush(); - - if (group->entries().isEmpty() && group->children().isEmpty()) { - out << indentation << " [empty]\n"; - return; - } - - for (Entry* entry : group->entries()) { - out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n"; - } - - for (Group* innerGroup : group->children()) { - printGroup(innerGroup, groupName, depth + 1); - } - -} - -int List::execute(int argc, char **argv) +int List::execute(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", - "List database entries.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + QCommandLineOption printUuidsOption( + QStringList() << "u" + << "print-uuids", + QCoreApplication::translate("main", "Print the UUIDs of the entries and groups.")); + parser.addOption(printUuidsOption); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -84,6 +64,7 @@ int List::execute(int argc, char **argv) return EXIT_FAILURE; } - printGroup(db->rootGroup(), QString(""), 0); + out << db->rootGroup()->print(parser.isSet("print-uuids")); + out.flush(); return EXIT_SUCCESS; } diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index aa399dd5..ca2c7101 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -19,6 +19,7 @@ #include "Merge.h" +#include #include #include #include @@ -27,56 +28,68 @@ #include "core/Database.h" #include "format/KeePass2Writer.h" -#include "keys/CompositeKey.h" +#include "gui/UnlockDatabaseDialog.h" int Merge::execute(int argc, char** argv) { - QCoreApplication app(argc, argv); + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << QString(argv[i]); + } QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases.")); - parser.addPositionalArgument("database1", QCoreApplication::translate("main", "Path of the database to merge into.")); - parser.addPositionalArgument("database2", QCoreApplication::translate("main", "Path of the database to merge from.")); + parser.addPositionalArgument("database1", + QCoreApplication::translate("main", "Path of the database to merge into.")); + parser.addPositionalArgument("database2", + QCoreApplication::translate("main", "Path of the database to merge from.")); - QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password", - QCoreApplication::translate("main", "Use the same password for both database files.")); + QCommandLineOption samePasswordOption( + QStringList() << "s" + << "same-password", + QCoreApplication::translate("main", "Use the same password for both database files.")); + + QCommandLineOption guiPrompt( + QStringList() << "g" + << "gui-prompt", + QCoreApplication::translate("main", "Use a GUI prompt unlocking the database.")); + parser.addOption(guiPrompt); parser.addOption(samePasswordOption); - parser.process(app); + parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { + QCoreApplication app(argc, argv); parser.showHelp(); return EXIT_FAILURE; } - out << "Insert the first database password\n> "; - out.flush(); - - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line1 = inputTextStream.readLine(); - CompositeKey key1 = CompositeKey::readFromLine(line1); + Database* db1; + Database* db2; - CompositeKey key2; - if (parser.isSet("same-password")) { - key2 = *key1.clone(); + if (parser.isSet("gui-prompt")) { + QApplication app(argc, argv); + db1 = UnlockDatabaseDialog::openDatabasePrompt(args.at(0)); + if (!parser.isSet("same-password")) { + db2 = UnlockDatabaseDialog::openDatabasePrompt(args.at(1)); + } else { + db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone())); + } + } else { + QCoreApplication app(argc, argv); + db1 = Database::unlockFromStdin(args.at(0)); + if (!parser.isSet("same-password")) { + db2 = Database::unlockFromStdin(args.at(1)); + } else { + db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone())); + } } - else { - out << "Insert the second database password\n> "; - out.flush(); - QString line2 = inputTextStream.readLine(); - key2 = CompositeKey::readFromLine(line2); - } - - - Database* db1 = Database::openDatabaseFile(args.at(0), key1); if (db1 == nullptr) { return EXIT_FAILURE; } - - Database* db2 = Database::openDatabaseFile(args.at(1), key2); if (db2 == nullptr) { return EXIT_FAILURE; } @@ -104,5 +117,4 @@ int Merge::execute(int argc, char** argv) out << "Successfully merged the database files.\n"; return EXIT_SUCCESS; - } diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 9222a093..59c0219a 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -30,16 +30,15 @@ #include "core/Group.h" #include "keys/CompositeKey.h" -int Show::execute(int argc, char **argv) +int Show::execute(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", - "Show a password.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "Show a password.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); - parser.addPositionalArgument("uuid", QCoreApplication::translate("main", "Uuid of the entry to show")); + parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to show.")); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -60,10 +59,10 @@ int Show::execute(int argc, char **argv) return EXIT_FAILURE; } - Uuid uuid = Uuid::fromHex(args.at(1)); - Entry* entry = db->resolveEntry(uuid); - if (entry == nullptr) { - qCritical("No entry found with uuid %s", qPrintable(uuid.toHex())); + QString entryId = args.at(1); + Entry* entry = db->rootGroup()->findEntry(entryId); + if (!entry) { + qCritical("Entry %s not found.", qPrintable(entryId)); return EXIT_FAILURE; } diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index b27b7483..cc09f8a9 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -35,7 +36,7 @@ #include #endif -int main(int argc, char **argv) +int main(int argc, char** argv) { #ifdef QT_NO_DEBUG Tools::disableCoreDumps(); @@ -46,13 +47,11 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - QCoreApplication app(argc, argv); - app.setApplicationVersion(KEEPASSX_VERSION); - QCommandLineParser parser; QString description("KeePassXC command line interface."); description = description.append(QString("\n\nAvailable commands:")); + description = description.append(QString("\n clip\t\tCopy a password to the clipboard.")); description = description.append(QString("\n extract\tExtract and print the content of a database.")); description = description.append(QString("\n entropy-meter\tCalculate password entropy.")); description = description.append(QString("\n list\t\tList database entries.")); @@ -70,6 +69,8 @@ int main(int argc, char **argv) // parser.process(app); if (argc < 2) { + QCoreApplication app(argc, argv); + app.setApplicationVersion(KEEPASSX_VERSION); parser.showHelp(); return EXIT_FAILURE; } @@ -82,7 +83,10 @@ int main(int argc, char **argv) int exitCode = EXIT_FAILURE; - if (commandName == "entropy-meter") { + if (commandName == "clip") { + argv[0] = const_cast("keepassxc-cli clip"); + exitCode = Clip::execute(argc, argv); + } else if (commandName == "entropy-meter") { argv[0] = const_cast("keepassxc-cli entropy-meter"); exitCode = EntropyMeter::execute(argc, argv); } else if (commandName == "extract") { @@ -99,6 +103,8 @@ int main(int argc, char **argv) exitCode = Show::execute(argc, argv); } else { qCritical("Invalid command %s.", qPrintable(commandName)); + QCoreApplication app(argc, argv); + app.setApplicationVersion(KEEPASSX_VERSION); parser.showHelp(); exitCode = EXIT_FAILURE; } @@ -110,5 +116,4 @@ int main(int argc, char **argv) #endif return exitCode; - } diff --git a/src/core/Config.cpp b/src/core/Config.cpp index c0876daa..f0a369c3 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -119,6 +119,7 @@ void Config::init(const QString& fileName) m_defaults.insert("security/lockdatabaseidle", false); m_defaults.insert("security/lockdatabaseidlesec", 240); m_defaults.insert("security/lockdatabaseminimize", false); + m_defaults.insert("security/lockdatabasescreenlock", true); m_defaults.insert("security/passwordsrepeat", false); m_defaults.insert("security/passwordscleartext", false); m_defaults.insert("security/autotypeask", true); diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5b5a707f..64fc3469 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -18,6 +18,7 @@ #include "Database.h" #include +#include #include #include @@ -367,7 +368,7 @@ void Database::startModifiedTimer() m_timer->start(150); } -const CompositeKey & Database::key() const +const CompositeKey& Database::key() const { return m_data.key; } @@ -396,3 +397,17 @@ Database* Database::openDatabaseFile(QString fileName, CompositeKey key) return db; } + +Database* Database::unlockFromStdin(QString databaseFilename) +{ + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QTextStream outputTextStream(stdout); + + outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> "); + outputTextStream.flush(); + + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + return Database::openDatabaseFile(databaseFilename, key); + +} diff --git a/src/core/Database.h b/src/core/Database.h index 7728d14c..37745e84 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -90,7 +90,7 @@ public: QByteArray transformSeed() const; quint64 transformRounds() const; QByteArray transformedMasterKey() const; - const CompositeKey & key() const; + const CompositeKey& key() const; QByteArray challengeResponseKey() const; bool challengeMasterSeed(const QByteArray& masterSeed); @@ -120,6 +120,7 @@ public: static Database* databaseByUuid(const Uuid& uuid); static Database* openDatabaseFile(QString fileName, CompositeKey key); + static Database* unlockFromStdin(QString databaseFilename); signals: void groupDataChanged(Group* group); diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index 0506e3ab..132fdc00 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -49,7 +49,7 @@ QString FilePath::pluginPath(const QString& name) // for TestAutoType pluginPaths << QCoreApplication::applicationDirPath() + "/../src/autotype/test"; -#if defined(Q_OS_MAC) +#if defined(Q_OS_MAC) && defined(WITH_APP_BUNDLE) pluginPaths << QCoreApplication::applicationDirPath() + "/../PlugIns"; #endif @@ -101,7 +101,7 @@ QIcon FilePath::trayIconLocked() QIcon FilePath::trayIconUnlocked() { - return applicationIcon(); + return icon("apps", "keepassxc-unlocked"); } QIcon FilePath::icon(const QString& category, const QString& name, bool fromTheme) @@ -195,7 +195,7 @@ FilePath::FilePath() else if (testSetDir(QString(KEEPASSX_SOURCE_DIR) + "/share")) { } #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) +#if defined(Q_OS_UNIX) && !(defined(Q_OS_MAC) && defined(WITH_APP_BUNDLE)) else if (isDataDirAbsolute && testSetDir(KEEPASSX_DATA_DIR)) { } else if (!isDataDirAbsolute && testSetDir(QString("%1/../%2").arg(appDirPath, KEEPASSX_DATA_DIR))) { @@ -203,7 +203,7 @@ FilePath::FilePath() else if (!isDataDirAbsolute && testSetDir(QString("%1/%2").arg(KEEPASSX_PREFIX_DIR, KEEPASSX_DATA_DIR))) { } #endif -#ifdef Q_OS_MAC +#if defined(Q_OS_MAC) && defined(WITH_APP_BUNDLE) else if (testSetDir(appDirPath + "/../Resources")) { } #endif diff --git a/src/core/Group.cpp b/src/core/Group.cpp index bd4f8851..0c83fa30 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -18,8 +18,8 @@ #include "Group.h" #include "core/Config.h" -#include "core/Global.h" #include "core/DatabaseIcons.h" +#include "core/Global.h" #include "core/Metadata.h" const int Group::DefaultIconNumber = 48; @@ -202,7 +202,7 @@ QString Group::effectiveAutoTypeSequence() const } while (group && sequence.isEmpty()); if (sequence.isEmpty()) { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; + sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; } return sequence; @@ -483,7 +483,35 @@ QList Group::entriesRecursive(bool includeHistoryItems) const return entryList; } -Entry* Group::findEntry(const Uuid& uuid) +Entry* Group::findEntry(QString entryId) +{ + Q_ASSERT(!entryId.isEmpty()); + Q_ASSERT(!entryId.isNull()); + + if (Uuid::isUuid(entryId)) { + Uuid entryUuid = Uuid::fromHex(entryId); + for (Entry* entry : entriesRecursive(false)) { + if (entry->uuid() == entryUuid) { + return entry; + } + } + } + + Entry* entry = findEntryByPath(entryId); + if (entry) { + return entry; + } + + for (Entry* entry : entriesRecursive(false)) { + if (entry->title() == entryId) { + return entry; + } + } + + return nullptr; +} + +Entry* Group::findEntryByUuid(const Uuid& uuid) { Q_ASSERT(!uuid.isNull()); for (Entry* entry : asConst(m_entries)) { @@ -495,6 +523,61 @@ Entry* Group::findEntry(const Uuid& uuid) return nullptr; } +Entry* Group::findEntryByPath(QString entryPath, QString basePath) +{ + + Q_ASSERT(!entryPath.isEmpty()); + Q_ASSERT(!entryPath.isNull()); + + for (Entry* entry : asConst(m_entries)) { + QString currentEntryPath = basePath + entry->title(); + if (entryPath == currentEntryPath) { + return entry; + } + } + + for (Group* group : asConst(m_children)) { + Entry* entry = group->findEntryByPath(entryPath, basePath + group->name() + QString("/")); + if (entry != nullptr) { + return entry; + } + } + + return nullptr; +} + +QString Group::print(bool printUuids, QString baseName, int depth) +{ + + QString response; + QString indentation = QString(" ").repeated(depth); + + if (entries().isEmpty() && children().isEmpty()) { + response += indentation + "[empty]\n"; + return response; + } + + for (Entry* entry : entries()) { + response += indentation + entry->title(); + if (printUuids) { + response += " " + entry->uuid().toHex(); + } + response += "\n"; + } + + for (Group* innerGroup : children()) { + QString newBaseName = baseName + innerGroup->name() + "/"; + response += indentation + newBaseName; + if (printUuids) { + response += " " + innerGroup->uuid().toHex(); + } + response += "\n"; + response += innerGroup->print(printUuids, newBaseName, depth + 1); + } + + return response; +} + QList Group::groupsRecursive(bool includeSelf) const { QList groupList; @@ -551,10 +634,10 @@ void Group::merge(const Group* other) const QList dbEntries = other->entries(); for (Entry* entry : dbEntries) { // entries are searched by uuid - if (!findEntry(entry->uuid())) { + if (!findEntryByUuid(entry->uuid())) { entry->clone(Entry::CloneNoFlags)->setGroup(this); } else { - resolveConflict(findEntry(entry->uuid()), entry); + resolveConflict(findEntryByUuid(entry->uuid()), entry); } } diff --git a/src/core/Group.h b/src/core/Group.h index e3e5e755..9b1f0209 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -78,7 +78,9 @@ public: static const int DefaultIconNumber; static const int RecycleBinIconNumber; - Entry* findEntry(const Uuid& uuid); + Entry* findEntry(QString entryId); + Entry* findEntryByUuid(const Uuid& uuid); + Entry* findEntryByPath(QString entryPath, QString basePath = QString("")); Group* findChildByName(const QString& name); void setUuid(const Uuid& uuid); void setName(const QString& name); @@ -121,6 +123,7 @@ public: Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const; void copyDataFrom(const Group* other); void merge(const Group* other); + QString print(bool printUuids = false, QString baseName = QString(""), int depth = 0); signals: void dataChanged(Group* group); diff --git a/src/core/ScreenLockListener.cpp b/src/core/ScreenLockListener.cpp new file mode 100644 index 00000000..eb78cd60 --- /dev/null +++ b/src/core/ScreenLockListener.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenLockListener.h" +#include "ScreenLockListenerPrivate.h" + +ScreenLockListener::ScreenLockListener(QWidget* parent): + QObject(parent){ + m_listener = ScreenLockListenerPrivate::instance(parent); + connect(m_listener,SIGNAL(screenLocked()), this,SIGNAL(screenLocked())); +} + +ScreenLockListener::~ScreenLockListener(){ +} diff --git a/src/core/ScreenLockListener.h b/src/core/ScreenLockListener.h new file mode 100644 index 00000000..b4eb81e0 --- /dev/null +++ b/src/core/ScreenLockListener.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREENLOCKLISTENER_H +#define SCREENLOCKLISTENER_H +#include + +class ScreenLockListenerPrivate; + +class ScreenLockListener : public QObject { + Q_OBJECT + +public: + ScreenLockListener(QWidget* parent = nullptr); + ~ScreenLockListener(); + +signals: + void screenLocked(); + +private: + ScreenLockListenerPrivate* m_listener; +}; + +#endif // SCREENLOCKLISTENER_H diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp new file mode 100644 index 00000000..1976b47e --- /dev/null +++ b/src/core/ScreenLockListenerDBus.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenLockListenerDBus.h" + +#include +#include +#include + +ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): + ScreenLockListenerPrivate(parent) +{ + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + QDBusConnection systemBus = QDBusConnection::systemBus(); + + sessionBus.connect( + "org.freedesktop.ScreenSaver", // service + "/org/freedesktop/ScreenSaver", // path + "org.freedesktop.ScreenSaver", // interface + "ActiveChanged", // signal name + this, //receiver + SLOT(freedesktopScreenSaver(bool))); + + sessionBus.connect( + "org.gnome.SessionManager", // service + "/org/gnome/SessionManager/Presence", // path + "org.gnome.SessionManager.Presence", // interface + "StatusChanged", // signal name + this, //receiver + SLOT(gnomeSessionStatusChanged(uint))); + + systemBus.connect( + "org.freedesktop.login1", // service + "/org/freedesktop/login1", // path + "org.freedesktop.login1.Manager", // interface + "PrepareForSleep", // signal name + this, //receiver + SLOT(logindPrepareForSleep(bool))); + + sessionBus.connect( + "com.canonical.Unity", // service + "/com/canonical/Unity/Session", // path + "com.canonical.Unity.Session", // interface + "Locked", // signal name + this, //receiver + SLOT(unityLocked())); +} + +void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) +{ + if (status != 0) { + emit screenLocked(); + } +} + +void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) +{ + if (beforeSleep) { + emit screenLocked(); + } +} + +void ScreenLockListenerDBus::unityLocked() +{ + emit screenLocked(); +} + +void ScreenLockListenerDBus::freedesktopScreenSaver(bool status) +{ + if (status) { + emit screenLocked(); + } +} \ No newline at end of file diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h new file mode 100644 index 00000000..72f308f7 --- /dev/null +++ b/src/core/ScreenLockListenerDBus.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREENLOCKLISTENERDBUS_H +#define SCREENLOCKLISTENERDBUS_H +#include +#include +#include "ScreenLockListenerPrivate.h" + +class ScreenLockListenerDBus : public ScreenLockListenerPrivate +{ + Q_OBJECT +public: + explicit ScreenLockListenerDBus(QWidget *parent = 0); + +private slots: + void gnomeSessionStatusChanged(uint status); + void logindPrepareForSleep(bool beforeSleep); + void unityLocked(); + void freedesktopScreenSaver(bool status); +}; + +#endif // SCREENLOCKLISTENERDBUS_H diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp new file mode 100644 index 00000000..dce05de3 --- /dev/null +++ b/src/core/ScreenLockListenerMac.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenLockListenerMac.h" + +#include +#include + +ScreenLockListenerMac* ScreenLockListenerMac::instance() +{ + static QMutex mutex; + QMutexLocker lock(&mutex); + + static ScreenLockListenerMac* m_ptr = nullptr; + if (m_ptr == nullptr) { + m_ptr = new ScreenLockListenerMac(); + } + return m_ptr; +} + +void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef, void*, + CFStringRef, const void*, + CFDictionaryRef) +{ + instance()->onSignalReception(); +} + +ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) + : ScreenLockListenerPrivate(parent) +{ + CFNotificationCenterRef distCenter; + CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); + distCenter = CFNotificationCenterGetDistributedCenter(); + if (nullptr == distCenter) { + return; + } + + CFNotificationCenterAddObserver(distCenter, + this, + &ScreenLockListenerMac::notificationCenterCallBack, + screenIsLockedSignal, + nullptr, + CFNotificationSuspensionBehaviorDeliverImmediately); +} + +void ScreenLockListenerMac::onSignalReception() +{ + emit screenLocked(); +} diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h new file mode 100644 index 00000000..cd36ce9e --- /dev/null +++ b/src/core/ScreenLockListenerMac.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREENLOCKLISTENERMAC_H +#define SCREENLOCKLISTENERMAC_H +#include +#include + +#include + +#include "ScreenLockListenerPrivate.h" + +class ScreenLockListenerMac: public ScreenLockListenerPrivate { + Q_OBJECT + +public: + static ScreenLockListenerMac* instance(); + static void notificationCenterCallBack(CFNotificationCenterRef center, void* observer, + CFStringRef name, const void* object, + CFDictionaryRef userInfo); + +private: + ScreenLockListenerMac(QWidget* parent = nullptr); + void onSignalReception(); + +}; + +#endif // SCREENLOCKLISTENERMAC_H diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp new file mode 100644 index 00000000..36ee301f --- /dev/null +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenLockListenerPrivate.h" +#if defined(Q_OS_OSX) +#include "ScreenLockListenerMac.h" +#endif +#if defined(Q_OS_LINUX) +#include "ScreenLockListenerDBus.h" +#endif +#if defined(Q_OS_WIN) +#include "ScreenLockListenerWin.h" +#endif + +ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent) + : QObject(parent) +{ +} + +ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent) +{ +#if defined(Q_OS_OSX) + Q_UNUSED(parent); + return ScreenLockListenerMac::instance(); +#elif defined(Q_OS_LINUX) + return new ScreenLockListenerDBus(parent); +#elif defined(Q_OS_WIN) + return new ScreenLockListenerWin(parent); +#endif +} diff --git a/src/core/ScreenLockListenerPrivate.h b/src/core/ScreenLockListenerPrivate.h new file mode 100644 index 00000000..8ecad17d --- /dev/null +++ b/src/core/ScreenLockListenerPrivate.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREENLOCKLISTENERPRIVATE_H +#define SCREENLOCKLISTENERPRIVATE_H +#include +#include + +class ScreenLockListenerPrivate : public QObject +{ + Q_OBJECT +public: + static ScreenLockListenerPrivate* instance(QWidget* parent = 0); + +protected: + ScreenLockListenerPrivate(QWidget* parent = 0); + +signals: + void screenLocked(); +}; + +#endif // SCREENLOCKLISTENERPRIVATE_H diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp new file mode 100644 index 00000000..a1bf13d4 --- /dev/null +++ b/src/core/ScreenLockListenerWin.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScreenLockListenerWin.h" +#include +#include +#include + +/* + * See https://msdn.microsoft.com/en-us/library/windows/desktop/aa373196(v=vs.85).aspx + * See https://msdn.microsoft.com/en-us/library/aa383841(v=vs.85).aspx + * See https://blogs.msdn.microsoft.com/oldnewthing/20060104-50/?p=32783 + */ +ScreenLockListenerWin::ScreenLockListenerWin(QWidget* parent) + : ScreenLockListenerPrivate(parent) + , QAbstractNativeEventFilter() +{ + Q_ASSERT(parent != nullptr); + // On windows, we need to register for platform specific messages and + // install a message handler for them + QCoreApplication::instance()->installNativeEventFilter(this); + + // This call requests a notification from windows when a laptop is closed + HPOWERNOTIFY hPnotify = RegisterPowerSettingNotification( + reinterpret_cast(parent->winId()), + &GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE); + m_powerNotificationHandle = reinterpret_cast(hPnotify); + + // This call requests a notification for session changes + if (!WTSRegisterSessionNotification( + reinterpret_cast(parent->winId()), + NOTIFY_FOR_THIS_SESSION)) { + } +} + +ScreenLockListenerWin::~ScreenLockListenerWin() +{ + HWND h= reinterpret_cast(static_cast(parent())->winId()); + WTSUnRegisterSessionNotification(h); + + if (m_powernotificationhandle) { + UnregisterPowerSettingNotification(reinterpret_cast(m_powernotificationhandle)); + } +} + +bool ScreenLockListenerWin::nativeEventFilter(const QByteArray& eventType, void* message, long*) +{ + if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { + MSG* m = static_cast(message); + if (m->message == WM_POWERBROADCAST) { + if (m->wParam == PBT_POWERSETTINGCHANGE) { + const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); + if (setting != nullptr && setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { + const DWORD* state = reinterpret_cast(&setting->Data); + if (*state == 0) { + emit screenLocked(); + return true; + } + } + } else if (m->wParam == PBT_APMSUSPEND) { + emit screenLocked(); + return true; + } + } + if (m->message == WM_WTSSESSION_CHANGE) { + if (m->wParam == WTS_CONSOLE_DISCONNECT) { + emit screenLocked(); + return true; + } + if (m->wParam == WTS_SESSION_LOCK) { + emit screenLocked(); + return true; + } + } + } + return false; +} diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h new file mode 100644 index 00000000..6a8380ef --- /dev/null +++ b/src/core/ScreenLockListenerWin.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCREENLOCKLISTENERWIN_H +#define SCREENLOCKLISTENERWIN_H +#include +#include +#include + +#include "ScreenLockListenerPrivate.h" + +class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstractNativeEventFilter +{ + Q_OBJECT +public: + explicit ScreenLockListenerWin(QWidget* parent = 0); + ~ScreenLockListenerWin(); + virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long*) override; + +private: + void* m_powerNotificationHandle ; +}; + +#endif // SCREENLOCKLISTENERWIN_H diff --git a/src/core/Uuid.cpp b/src/core/Uuid.cpp index 1b046c5a..1b531159 100644 --- a/src/core/Uuid.cpp +++ b/src/core/Uuid.cpp @@ -22,6 +22,8 @@ #include "crypto/Random.h" const int Uuid::Length = 16; +const QRegExp Uuid::HexRegExp = QRegExp(QString("^[0-9A-F]{%1}$").arg(QString::number(Uuid::Length * 2)), + Qt::CaseInsensitive); Uuid::Uuid() : m_data(Length, 0) @@ -115,3 +117,8 @@ QDataStream& operator>>(QDataStream& stream, Uuid& uuid) return stream; } + +bool Uuid::isUuid(const QString& uuid) +{ + return Uuid::HexRegExp.exactMatch(uuid); +} diff --git a/src/core/Uuid.h b/src/core/Uuid.h index 4164399d..ecb20e0c 100644 --- a/src/core/Uuid.h +++ b/src/core/Uuid.h @@ -20,6 +20,7 @@ #include #include +#include class Uuid { @@ -36,8 +37,10 @@ public: bool operator==(const Uuid& other) const; bool operator!=(const Uuid& other) const; static const int Length; + static const QRegExp HexRegExp; static Uuid fromBase64(const QString& str); static Uuid fromHex(const QString& str); + static bool isUuid(const QString& str); private: QByteArray m_data; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0415f2b4..51d40dc5 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -103,9 +103,9 @@ const QString MainWindow::BaseWindowTitle = "KeePassXC"; MainWindow::MainWindow() : m_ui(new Ui::MainWindow()) , m_trayIcon(nullptr) + , m_appExitCalled(false) + , m_appExiting(false) { - appExitCalled = false; - m_ui->setupUi(this); // Setup the search widget in the toolbar @@ -329,6 +329,9 @@ MainWindow::MainWindow() connect(m_ui->tabWidget, SIGNAL(messageTab(QString,MessageWidget::MessageType)), this, SLOT(displayTabMessage(QString, MessageWidget::MessageType))); connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage())); + m_screenLockListener = new ScreenLockListener(this); + connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock())); + updateTrayIcon(); if (config()->hasAccessError()) { @@ -344,7 +347,7 @@ MainWindow::~MainWindow() void MainWindow::appExit() { - appExitCalled = true; + m_appExitCalled = true; close(); } @@ -660,9 +663,15 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { + // ignore double close events (happens on macOS when closing from the dock) + if (m_appExiting) { + event->accept(); + return; + } + bool minimizeOnClose = isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool(); - if (minimizeOnClose && !appExitCalled) + if (minimizeOnClose && !m_appExitCalled) { event->ignore(); hideWindow(); @@ -677,6 +686,7 @@ void MainWindow::closeEvent(QCloseEvent* event) bool accept = saveLastDatabases(); if (accept) { + m_appExiting = true; saveWindowInformation(); event->accept(); @@ -749,10 +759,17 @@ void MainWindow::updateTrayIcon() QAction* actionToggle = new QAction(tr("Toggle window"), menu); menu->addAction(actionToggle); +#ifdef Q_OS_MAC + QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu); + menu->addAction(actionQuit); + + connect(actionQuit, SIGNAL(triggered()), SLOT(appExit())); +#else menu->addAction(m_ui->actionQuit); connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason))); +#endif connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow())); m_trayIcon->setContextMenu(menu); @@ -832,7 +849,9 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) void MainWindow::hideWindow() { +#ifndef Q_OS_MAC setWindowState(windowState() | Qt::WindowMinimized); +#endif QTimer::singleShot(0, this, SLOT(hide())); if (config()->get("security/lockdatabaseminimize").toBool()) { @@ -915,13 +934,8 @@ void MainWindow::repairDatabase() bool MainWindow::isTrayIconEnabled() const { -#ifdef Q_OS_MAC - // systray not useful on OS X - return false; -#else return config()->get("GUI/ShowTrayIcon").toBool() && QSystemTrayIcon::isSystemTrayAvailable(); -#endif } void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton) @@ -959,3 +973,10 @@ void MainWindow::hideYubiKeyPopup() hideGlobalMessage(); setEnabled(true); } + +void MainWindow::handleScreenLock() +{ + if (config()->get("security/lockdatabasescreenlock").toBool()){ + lockDatabasesAfterInactivity(); + } +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index f35cd465..fffc634a 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -23,6 +23,7 @@ #include #include "core/SignalMultiplexer.h" +#include "core/ScreenLockListener.h" #include "gui/DatabaseWidget.h" #include "gui/Application.h" @@ -39,6 +40,7 @@ class MainWindow : public QMainWindow public: MainWindow(); ~MainWindow(); + enum StackedWidgetIndex { DatabaseTabScreen = 0, @@ -91,6 +93,7 @@ private slots: void lockDatabasesAfterInactivity(); void repairDatabase(); void hideTabMessage(); + void handleScreenLock(); private: static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); @@ -112,10 +115,12 @@ private: InactivityTimer* m_inactivityTimer; int m_countDefaultAttributes; QSystemTrayIcon* m_trayIcon; + ScreenLockListener* m_screenLockListener; Q_DISABLE_COPY(MainWindow) - bool appExitCalled; + bool m_appExitCalled; + bool m_appExiting; }; #define KEEPASSXC_MAIN_WINDOW (qobject_cast(qApp) ? \ diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index e94f838c..2142a96f 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -25,7 +25,7 @@ #include "core/FilePath.h" -SearchWidget::SearchWidget(QWidget *parent) +SearchWidget::SearchWidget(QWidget* parent) : QWidget(parent) , m_ui(new Ui::SearchWidget()) { @@ -40,11 +40,11 @@ SearchWidget::SearchWidget(QWidget *parent) connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); - new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); + new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); m_ui->searchEdit->installEventFilter(this); - QMenu *searchMenu = new QMenu(); + QMenu* searchMenu = new QMenu(); m_actionCaseSensitive = searchMenu->addAction(tr("Case Sensitive"), this, SLOT(updateCaseSensitive())); m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive"); m_actionCaseSensitive->setCheckable(true); @@ -58,39 +58,35 @@ SearchWidget::SearchWidget(QWidget *parent) m_ui->searchEdit->addAction(m_ui->clearIcon, QLineEdit::TrailingPosition); // Fix initial visibility of actions (bug in Qt) - for (QToolButton * toolButton: m_ui->searchEdit->findChildren()) { + for (QToolButton* toolButton : m_ui->searchEdit->findChildren()) { toolButton->setVisible(toolButton->defaultAction()->isVisible()); } } SearchWidget::~SearchWidget() { - } -bool SearchWidget::eventFilter(QObject *obj, QEvent *event) +bool SearchWidget::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); + QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { emit escapePressed(); return true; - } - else if (keyEvent->matches(QKeySequence::Copy)) { + } else if (keyEvent->matches(QKeySequence::Copy)) { // If Control+C is pressed in the search edit when no text // is selected, copy the password of the current entry if (!m_ui->searchEdit->hasSelectedText()) { emit copyPressed(); return true; } - } - else if (keyEvent->matches(QKeySequence::MoveToNextLine)) { + } else if (keyEvent->matches(QKeySequence::MoveToNextLine)) { if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) { // If down is pressed at EOL, move the focus to the entry view emit downPressed(); return true; - } - else { + } else { // Otherwise move the cursor to EOL m_ui->searchEdit->setCursorPosition(m_ui->searchEdit->text().length()); return true; @@ -110,7 +106,7 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx) mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit())); } -void SearchWidget::databaseChanged(DatabaseWidget *dbWidget) +void SearchWidget::databaseChanged(DatabaseWidget* dbWidget) { if (dbWidget != nullptr) { // Set current search text from this database diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index d2b94d97..e8770181 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -18,11 +18,11 @@ #ifndef KEEPASSX_SEARCHWIDGET_H #define KEEPASSX_SEARCHWIDGET_H -#include #include +#include -#include "gui/DatabaseWidget.h" #include "core/SignalMultiplexer.h" +#include "gui/DatabaseWidget.h" namespace Ui { class SearchWidget; @@ -33,17 +33,17 @@ class SearchWidget : public QWidget Q_OBJECT public: - explicit SearchWidget(QWidget *parent = 0); + explicit SearchWidget(QWidget* parent = 0); ~SearchWidget(); void connectSignals(SignalMultiplexer& mx); void setCaseSensitive(bool state); protected: - bool eventFilter(QObject *obj, QEvent *event); + bool eventFilter(QObject* obj, QEvent* event); signals: - void search(const QString &text); + void search(const QString& text); void caseSensitiveChanged(bool state); void escapePressed(); void copyPressed(); @@ -62,7 +62,7 @@ private slots: private: const QScopedPointer m_ui; QTimer* m_searchTimer; - QAction *m_actionCaseSensitive; + QAction* m_actionCaseSensitive; Q_DISABLE_COPY(SearchWidget) }; diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 716eb14f..d903c2de 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -66,11 +66,6 @@ SettingsWidget::SettingsWidget(QWidget* parent) m_generalUi->generalSettingsTabWidget->removeTab(1); } -#ifdef Q_OS_MAC - // systray not useful on OS X - m_generalUi->systraySettings->setVisible(false); -#endif - connect(this, SIGNAL(accepted()), SLOT(saveSettings())); connect(this, SIGNAL(rejected()), SLOT(reject())); @@ -141,12 +136,14 @@ void SettingsWidget::loadSettings() } } + m_secUi->clearClipboardCheckBox->setChecked(config()->get("security/clearclipboard").toBool()); m_secUi->clearClipboardSpinBox->setValue(config()->get("security/clearclipboardtimeout").toInt()); m_secUi->lockDatabaseIdleCheckBox->setChecked(config()->get("security/lockdatabaseidle").toBool()); m_secUi->lockDatabaseIdleSpinBox->setValue(config()->get("security/lockdatabaseidlesec").toInt()); m_secUi->lockDatabaseMinimizeCheckBox->setChecked(config()->get("security/lockdatabaseminimize").toBool()); + m_secUi->lockDatabaseOnScreenLockCheckBox->setChecked(config()->get("security/lockdatabasescreenlock").toBool()); m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool()); m_secUi->passwordRepeatCheckBox->setChecked(config()->get("security/passwordsrepeat").toBool()); @@ -186,6 +183,7 @@ void SettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); + config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked()); @@ -206,6 +204,7 @@ void SettingsWidget::saveSettings() config()->set("security/lockdatabaseidle", m_secUi->lockDatabaseIdleCheckBox->isChecked()); config()->set("security/lockdatabaseidlesec", m_secUi->lockDatabaseIdleSpinBox->value()); config()->set("security/lockdatabaseminimize", m_secUi->lockDatabaseMinimizeCheckBox->isChecked()); + config()->set("security/lockdatabasescreenlock", m_secUi->lockDatabaseOnScreenLockCheckBox->isChecked()); config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); config()->set("security/passwordsrepeat", m_secUi->passwordRepeatCheckBox->isChecked()); diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 37a60912..2fe0f408 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -303,7 +303,7 @@ - Use entry title to match windows for global Auto-Type + Use entry title and URL to match windows for global Auto-Type diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 08fa6f4e..679c470a 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -28,7 +28,14 @@ Timeouts - + + + + + Clear clipboard after + + + @@ -54,7 +61,20 @@ - + + + + + 0 + 0 + + + + Lock databases after inactivity of + + + + false @@ -79,20 +99,6 @@ - - - - Clear clipboard after - - - - - - - Lock databases after inactivity of - - - @@ -101,7 +107,21 @@ Convenience - + + + + + Lock databases when session is locked or lid is closed + + + + + + + Lock databases after minimizing the window + + + @@ -116,13 +136,6 @@ - - - - Lock databases after minimizing the window - - - diff --git a/src/gui/UnlockDatabaseDialog.cpp b/src/gui/UnlockDatabaseDialog.cpp index 3d002f75..c3c62c5a 100644 --- a/src/gui/UnlockDatabaseDialog.cpp +++ b/src/gui/UnlockDatabaseDialog.cpp @@ -19,18 +19,17 @@ #include "UnlockDatabaseWidget.h" #include "autotype/AutoType.h" -#include "gui/DragTabBar.h" #include "core/Database.h" +#include "gui/DragTabBar.h" - -UnlockDatabaseDialog::UnlockDatabaseDialog(QWidget *parent) +UnlockDatabaseDialog::UnlockDatabaseDialog(QWidget* parent) : QDialog(parent) , m_view(new UnlockDatabaseWidget(this)) { connect(m_view, SIGNAL(editFinished(bool)), this, SLOT(complete(bool))); } -void UnlockDatabaseDialog::setDBFilename(const QString &filename) +void UnlockDatabaseDialog::setDBFilename(const QString& filename) { m_view->load(filename); } @@ -40,7 +39,7 @@ void UnlockDatabaseDialog::clearForms() m_view->clearForms(); } -Database *UnlockDatabaseDialog::database() +Database* UnlockDatabaseDialog::database() { return m_view->database(); } @@ -54,3 +53,20 @@ void UnlockDatabaseDialog::complete(bool r) reject(); } } + +Database* UnlockDatabaseDialog::openDatabasePrompt(QString databaseFilename) +{ + + UnlockDatabaseDialog* unlockDatabaseDialog = new UnlockDatabaseDialog(); + unlockDatabaseDialog->setObjectName("Open database"); + unlockDatabaseDialog->setDBFilename(databaseFilename); + unlockDatabaseDialog->show(); + unlockDatabaseDialog->exec(); + + Database* db = unlockDatabaseDialog->database(); + if (!db) { + qWarning("Could not open database %s.", qPrintable(databaseFilename)); + } + delete unlockDatabaseDialog; + return db; +} diff --git a/src/gui/UnlockDatabaseDialog.h b/src/gui/UnlockDatabaseDialog.h index daf8a0f1..732395ef 100644 --- a/src/gui/UnlockDatabaseDialog.h +++ b/src/gui/UnlockDatabaseDialog.h @@ -31,10 +31,11 @@ class UnlockDatabaseDialog : public QDialog { Q_OBJECT public: - explicit UnlockDatabaseDialog(QWidget *parent = Q_NULLPTR); + explicit UnlockDatabaseDialog(QWidget* parent = Q_NULLPTR); void setDBFilename(const QString& filename); void clearForms(); Database* database(); + static Database* openDatabasePrompt(QString databaseFilename); signals: void unlockDone(bool); diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index a7329886..a3ed8cbe 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -104,6 +104,12 @@ void TestAutoType::init() association.window = "//^CustomAttr3$//"; association.sequence = "{PaSSworD}"; m_entry4->autoTypeAssociations()->add(association); + + m_entry5 = new Entry(); + m_entry5->setGroup(m_group); + m_entry5->setPassword("example5"); + m_entry5->setTitle("some title"); + m_entry5->setUrl("http://example.org"); } void TestAutoType::cleanup() @@ -172,6 +178,28 @@ void TestAutoType::testGlobalAutoTypeTitleMatch() QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); } +void TestAutoType::testGlobalAutoTypeUrlMatch() +{ + config()->set("AutoTypeEntryTitleMatch", true); + + m_test->setActiveWindowTitle("Dummy - http://example.org/ - "); + m_autoType->performGlobalAutoType(m_dbList); + + QCOMPARE(m_test->actionChars(), + QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); +} + +void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch() +{ + config()->set("AutoTypeEntryTitleMatch", true); + + m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - "); + m_autoType->performGlobalAutoType(m_dbList); + + QCOMPARE(m_test->actionChars(), + QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); +} + void TestAutoType::testGlobalAutoTypeTitleMatchDisabled() { m_test->setActiveWindowTitle("An Entry Title!"); diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index 569bc8c7..fb09a278 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -42,6 +42,8 @@ private slots: void testGlobalAutoTypeWithNoMatch(); void testGlobalAutoTypeWithOneMatch(); void testGlobalAutoTypeTitleMatch(); + void testGlobalAutoTypeUrlMatch(); + void testGlobalAutoTypeUrlSubdomainMatch(); void testGlobalAutoTypeTitleMatchDisabled(); void testGlobalAutoTypeRegExp(); @@ -56,6 +58,7 @@ private: Entry* m_entry2; Entry* m_entry3; Entry* m_entry4; + Entry* m_entry5; }; #endif // KEEPASSX_TESTAUTOTYPE_H diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index e87e6ced..a706bada 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -567,3 +567,108 @@ Database* TestGroup::createMergeTestDatabase() return db; } + +void TestGroup::testFindEntry() +{ + Database* db = new Database(); + + Entry* entry1 = new Entry(); + entry1->setTitle(QString("entry1")); + entry1->setGroup(db->rootGroup()); + entry1->setUuid(Uuid::random()); + + Group* group1 = new Group(); + group1->setName("group1"); + + Entry* entry2 = new Entry(); + + entry2->setTitle(QString("entry2")); + entry2->setGroup(group1); + entry2->setUuid(Uuid::random()); + + group1->setParent(db->rootGroup()); + + Entry* entry; + + entry = db->rootGroup()->findEntry(entry1->uuid().toHex()); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry1")); + + entry = db->rootGroup()->findEntry(QString("entry1")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry1")); + + entry = db->rootGroup()->findEntry(entry2->uuid().toHex()); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + + entry = db->rootGroup()->findEntry(QString("group1/entry2")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + + // Should also find the entry only by title. + entry = db->rootGroup()->findEntry(QString("entry2")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + + entry = db->rootGroup()->findEntry(QString("invalid/path/to/entry2")); + QVERIFY(entry == nullptr); + + entry = db->rootGroup()->findEntry(QString("entry27")); + QVERIFY(entry == nullptr); + + // A valid UUID that does not exist in this database. + entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc75281")); + QVERIFY(entry == nullptr); + + // An invalid UUID. + entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc")); + QVERIFY(entry == nullptr); + + delete db; +} + +void TestGroup::testPrint() +{ + Database* db = new Database(); + + QString output = db->rootGroup()->print(); + QCOMPARE(output, QString("[empty]\n")); + + output = db->rootGroup()->print(true); + QCOMPARE(output, QString("[empty]\n")); + + Entry* entry1 = new Entry(); + entry1->setTitle(QString("entry1")); + entry1->setGroup(db->rootGroup()); + entry1->setUuid(Uuid::random()); + + output = db->rootGroup()->print(); + QCOMPARE(output, QString("entry1\n")); + + output = db->rootGroup()->print(true); + QCOMPARE(output, QString("entry1 " + entry1->uuid().toHex() + "\n")); + + + Group* group1 = new Group(); + group1->setName("group1"); + + Entry* entry2 = new Entry(); + + entry2->setTitle(QString("entry2")); + entry2->setGroup(group1); + entry2->setUuid(Uuid::random()); + + group1->setParent(db->rootGroup()); + + output = db->rootGroup()->print(); + QVERIFY(output.contains(QString("entry1\n"))); + QVERIFY(output.contains(QString("group1/\n"))); + QVERIFY(output.contains(QString(" entry2\n"))); + + output = db->rootGroup()->print(true); + QVERIFY(output.contains(QString("entry1 " + entry1->uuid().toHex() + "\n"))); + QVERIFY(output.contains(QString("group1/ " + group1->uuid().toHex() + "\n"))); + QVERIFY(output.contains(QString(" entry2 " + entry2->uuid().toHex() + "\n"))); + delete db; +} diff --git a/tests/TestGroup.h b/tests/TestGroup.h index c9ed8f08..a0ed9282 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -38,6 +38,8 @@ private slots: void testMergeConflict(); void testMergeDatabase(); void testMergeConflictKeepBoth(); + void testFindEntry(); + void testPrint(); private: Database* createMergeTestDatabase(); diff --git a/utils/fix_mac.sh b/utils/fix_mac.sh new file mode 100755 index 00000000..2e4e84e5 --- /dev/null +++ b/utils/fix_mac.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Canonical path to qt5 directory +QT="/usr/local/Cellar/qt" +if [ ! -d "$QT" ]; then + # Alternative (old) path to qt5 directory + QT+="5" + if [ ! -d "$QT" ]; then + echo "Qt/Qt5 not found!" + exit + fi +fi +QT5_DIR="$QT/$(ls $QT | sort -r | head -n1)" +echo $QT5_DIR + +# Change qt5 framework ids +for framework in $(find "$QT5_DIR/lib" -regex ".*/\(Qt[a-zA-Z]*\)\.framework/Versions/5/\1"); do + echo "$framework" + install_name_tool -id "$framework" "$framework" +done