From b3160a17ea1a8731c4d818ddc711d76b90464f46 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 8 May 2017 23:27:54 +0200 Subject: [PATCH 01/29] enable minimize on close for macOS --- src/gui/MainWindow.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0415f2b4e..c3ab306eb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -660,8 +660,12 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { - bool minimizeOnClose = isTrayIconEnabled() && - config()->get("GUI/MinimizeOnClose").toBool(); + bool minimizeOnClose = config()->get("GUI/MinimizeOnClose").toBool(); +#ifndef Q_OS_MAC + // if we aren't on OS X, check if the tray is enabled. + // on OS X we are using the dock for the minimize action + minimizeOnClose = isTrayIconEnabled() && minimizeOnClose; +#endif if (minimizeOnClose && !appExitCalled) { event->ignore(); From c06e55df811d5c300dca25a7605654aef434d6a9 Mon Sep 17 00:00:00 2001 From: Jan Hellwig Date: Thu, 13 Apr 2017 22:02:09 +0200 Subject: [PATCH 02/29] Use a dedicated tray icon when the database is unlocked. --- COPYING | 2 ++ share/CMakeLists.txt | 16 ++++++++++++++++ .../128x128/apps/keepassxc-unlocked.png | Bin 0 -> 11058 bytes .../16x16/apps/keepassxc-unlocked.png | Bin 0 -> 880 bytes .../24x24/apps/keepassxc-unlocked.png | Bin 0 -> 1402 bytes .../256x256/apps/keepassxc-unlocked.png | Bin 0 -> 23418 bytes .../32x32/apps/keepassxc-unlocked.png | Bin 0 -> 2031 bytes .../48x48/apps/keepassxc-unlocked.png | Bin 0 -> 3317 bytes .../64x64/apps/keepassxc-unlocked.png | Bin 0 -> 4786 bytes .../scalable/apps/keepassxc-unlocked.svgz | Bin 0 -> 2334 bytes src/core/FilePath.cpp | 2 +- 11 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 share/icons/application/128x128/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/16x16/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/24x24/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/256x256/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/32x32/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/48x48/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/64x64/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/scalable/apps/keepassxc-unlocked.svgz diff --git a/COPYING b/COPYING index 4dbaece33..d36384011 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/share/CMakeLists.txt b/share/CMakeLists.txt index 9660d7509..7d01a1c40 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 0000000000000000000000000000000000000000..69b0fe24ee854c6e67af8c70ae0676dd901b9bcb GIT binary patch literal 11058 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2164f0335eff2a7740bcbdb18fb623c4cf808aae GIT binary patch literal 880 zcmV-$1CRWPP)@1BR%VmQqzYi^JN{L8Jf5a#GMKg z&biGP6RN6C27|%<8z#r&@t4b`WikBUS%Y(KbI$Es&u_B$q`LMVzILvhFXe@*s*_)B z5{*W$GRDN|@l!_U`GCmfm#R+4VXm-L?K~e4r^injj4?4ZG;|pNXl-q+cVJ-P$fN87 z^XY6_aY+QR#3j?Rt+fG`E%5jKOQuUAcsiR_9?v{9`}+EN0c@ecV6dm9rKMpabIaZ& z6Ks+R9%U0sEcqvM3jVr((RiFqD2{$FquOn4Z4IGNC`{dMx1Un#TGAJ&OQvwBn=4RX zUpD~YbG1a)Ym53UrIgBUx7$xG%d!DLl0X!h!l8>2Sf~O3PIjGbWdfH3Y;E{S0)Tgg zDdPO^_V26g@&2mBVzJu*auNW{&CNY)Z*M<*{J?LG3;MfC(fA-0*asT$by>^hoVfe; zUzre_{M;C*^aqEP#6C-Y$;q`a>Mu`MZ*sNWK~w85z(tP1|q}Ua!{|iA1hUO-(HrhOu@`hGDQwCNn=c zIC$CT^Lf{2$cC0xYzc)z;rjaeW&kJ@3hz^?lnNmCf8!UnMS-(X?m+|q0000*FP)t?LBrF0M znJ(PmtrxN_OPtAMyV$rfE=#5?H0-Y+_oOl2qtlF8I$Y9h){9idcUP!5GsD70VC z-!3Rn`BC>JZ%)qlKHvMi@ALfR+2PH|m>G6230$^{MERdO*SyEC`(%jwM?PH8Z8Dsi7 z7Jc^YnXjazOQnqNs&S6A2L zbUMERK-Z1#a5zrhxpT+M7}F+a{A%xT?^5=^|J@kgTb--!tP?_rE-9^ZicX-!$Q;o+5Ve5US^zNmY|{fcA_53hvFfBGcq z9UoK}V_I!(ZFfdSM$wWav2TLI;rRO4v18kr!ti~=H`>J9I5#pqq{k;=Gus85*(SvI z4Nbk&$7jd5?;5I9rZ7}iR&LME&i)pFVTr(IvsIiwe|`@D{+IR(k&)mq&v6v(eXRmh z*efLQEh0zs4tJ}-aTLSTL;S_NKSuxvH8nMR?RI;`5`o2HIg*u?WtyH3sCV7B3<5_W za0GK~nx1Vw%@j>pMWSemezy5En~MhNBEH>y%Mc7tt4^oWB#PqUMFLYRuBWe!c(aPsnT`LXveLp&C`Oj>%HWB@{Oaj_jhG7lgvKR-VO z0G^LNC6r&i%0>c%{P@hMLO_Wn$;ySJ^V&#YkY5Ws;CBN63JVKU0HpCe&u?Dbp~1)` z<+-&tx5wYF0DvXQ$`L|fGMcoRa9|a~M94<~KorG|JkM{Y0CZhnDBr+y;CZs95dx4H z_Xfi|^oQ~bdeVj@Z6%HpjbI@t0O(XvlqpG)LI9AGWY#!Jv6c}+K*(}+UVpzTYhk() zm$J#C0e~b)vx=fjQ2_tmzkh!M0Cre&^>r(t0Er2rYO3Dc`x~WHUxnL|x*Gt{+1cp_ zFiHUgT3T8L0m%Ee76}GH0LPJ47B3M;Ir8Cq`RYoHK@hNiYmopT+TPwi0AOK~BuTpY z;>C+8(P-rlzF*4LCRmmz#DZ#5@2?3fF@;$lGqW+3bGzMBV`F1Cf!Id#csy6Ds;Zs< zkdN;EJRv=Ki^B69;>}^+C1jg-3k!=Ok*@S-Bxfo|Klm&GfPCuIsV82q_Zk3ju{l#2 zV`E06F)wf5esNb?9_f=FsS!CuDIuVQK-MDB-1LJ60J!$t6&arH=NEmcN$JXof*-j} zi6&ZKU*Ff**jOD3g*@>Dn4O*V4h#%%IXO8ya5o42Ky&Z{%b7xMBt)iD(8=jY^L zq*r>Rbb6Z;2qh3W9{KiCR=Ka*kd$bm9UUDn&zw1PVKIAP^_5BOcKa7qRaIYBR#rLz z2$SJ)_37xJvM11ksd{Zm_8Ti4gucM-t42P6O> zH8r)UxVX5ftE=lxS0?M~>KZ$A=unf@YAswV*6S{PgWYbg5JmAwX=$mgpr9Z{6vd6P zRX2O@-aUU?Tic+|=er)0B>H~|;#)F+G%P<`{8)Il1l~08KVbfwSeV1>xBvhE07*qo IM6N<$f+;?UzyJUM literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d1c11781338b6541e013b56f0c7e8921baafd7e8 GIT binary patch literal 23418 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:&=`5_=sN1|2^%R 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..de06bf03aea282b8f4d2374417c6d00c9df1287c GIT binary patch literal 2031 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 M07*qoM6N<$g2h`(_5c6? literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..84ce139046b21579c83ba1947304808a485695ec GIT binary patch literal 2334 zcmV+(3E}o1iwFP!000000M%IAj@!r)efL)onwK>qQhmS3BQKH|yU0TjAZsH)-Z&&@ zhHHrwNRH?7^*PnNNg9nkPOuy72nN_y)m>Gmt50?N^2dkWHu_RDZCO{F1y9%_Dypo` z%j#yc`1J9o*e#;AORGHH)>W}tRQ2M=_irxSFE?+ZD8l5bUDxf^W^vnf`}Jyde}A9U z`=V;^(=NNcs_V~5-Q2A1nsOx=^DEdc=9zgmP2Ar#+hCe!t72R1imGc@JmISY^K3TH znj-DWFGW`Gc6HT;nN@pnVBF-_M^W9&V8p%mD<)P##IT6l$Er&o;^V2v?%Y%)z*f-B zmanr~KWxkD^NYa*?V)#|zsJ96!YD}E`mV`}Ys@T?s_0f9K7N>#F-!6;KUfBfwpqF_ zj{J_o9NyG1cD7KXP)d2`#%<1b}#|Cjn3@j0vstI->|Ti_b-|Puun(OR(y)PGB3_4ri>`O$vwm4nysjhdMg-n!}oLPOU&QOBFB+fhK?K36vQ!G(VNzPvg^x+Hyg9@|7 zGlzq2mp+uc@~Oz@t(x85H4T_6-lmU5Gn|8H6)Gk#uG<+78mUl9P=Nr7wE4M7^AeML zqu%UTgIvrohf1)k_oFSa>>jt+auUX&%GMV`m_o>RK{RaodW<*}*!6W=bTbH}Q|iabYKEcGfmK zYfJlb9IWPS-nMUT@09RwQ5~`T!`27qF7bw0j`HBlC%8DNx4$)7vH8Q|HJ&GVaGv-# z0ef9El!-Hm)W&;#oV84Zbk67_gt!0}lF1V^pV>a&290h0&ZBd=qRtw=c14%wX_wAT zca+$%@!|!r{^y6Ermm1>>wneF=b6ySAib*Z!1Gh@pkAJ>X&ch+eYwM(iLU#9!mS(5 zC&iI*_qZ?SMf+u&qQB0ceF5d!uB1t;U%GO;{Tn$s=*GjMWw$L3TJ&m&e(X92$z86- z)Waw%n`}D-MC7K&9!8ut4G6J)+TCiw#-=^=$*~`@#92nSfAJTu}i$n^p#u+_?38f6b{vK(z1XTzn&^@C+M`*;x z8fC?D`ifZiO;$_IzG8;;Jc+b*{3~XY%{Nz@qx*NXi)d`oV5H?y77>s?OKS_JkCaWU)Se{hVoYiRj|Nh6Z31{PHEFN70_utI z&Itz);aMoAu~s5DA|nCd#aUPixPiGCM1UQ5Sc7g#A;Tf}#st`%gpR9(8m;ybNAyN1 zTGS@A0|;V0<`aWR14P7Jw2a1#G54(rF5ipe;=F_51lP&GMbU3v(oc$$E_)^%!7&;A6 zOmgf6u1bUyqa6rZfGqnH0$KF*Rf4{__VoK7I9mMwIhxuKZnq+~GgI#(O=O0n&goR6 z{W|bXHt?DDM}+32>-o&+X-w?V136F{A2$RoM(RFc~ATr1Trg;k1+E`R%X%M3YZgJ!uc>>&a@M`fSM;SD(AxrupxFaIFeRlldzQ6Qmb(0jRT}A$Sk*@3l1|d0udb1jkF_G z8I;UORRb4EYxT@kQs@(|3i`19R7>+R1AU3*c)K&rg-{WhmZjfP6i_;R_K2q z9~0t4>M#p|8vjBY1ZtrRYRdEIdw E0D&rzsQ>@~ literal 0 HcmV?d00001 diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index 0506e3ab7..67ececfbb 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -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) From 8d6db27b3430543e6f262a566be0cbd0331a404e Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 11 May 2017 13:44:08 +0200 Subject: [PATCH 03/29] add tray icon to MacOS X --- src/gui/MainWindow.cpp | 15 +++------------ src/gui/SettingsWidget.cpp | 5 ----- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c3ab306eb..4e25a9426 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -660,13 +660,7 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { - bool minimizeOnClose = config()->get("GUI/MinimizeOnClose").toBool(); -#ifndef Q_OS_MAC - // if we aren't on OS X, check if the tray is enabled. - // on OS X we are using the dock for the minimize action - minimizeOnClose = isTrayIconEnabled() && minimizeOnClose; -#endif - if (minimizeOnClose && !appExitCalled) + if (isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool() && !appExitCalled) { event->ignore(); hideWindow(); @@ -836,7 +830,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()) { @@ -919,13 +915,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) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 716eb14f1..46910a5a3 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())); From 70357be029834710dca3dacede538705470fc210 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 12 May 2017 10:54:01 +0200 Subject: [PATCH 04/29] Update README.md changed reference to new build instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18a025aa8..bf214d3c1 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). From 9a59a124aab4549e6db2c728ab9d515b0277aba7 Mon Sep 17 00:00:00 2001 From: Jens Rutschmann Date: Sun, 14 May 2017 01:02:54 +0200 Subject: [PATCH 05/29] Compare window title with entry URLs during autotype matching. (#556) * Compare window title with entry URLs during autotype matching. * Adapted option label to reflect that both entry title and URL are used for auto-type window matching. --- src/autotype/AutoType.cpp | 24 ++++++++++++++++++++++-- src/autotype/AutoType.h | 2 ++ src/gui/SettingsWidgetGeneral.ui | 2 +- tests/TestAutoType.cpp | 28 ++++++++++++++++++++++++++++ tests/TestAutoType.h | 3 +++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 12367fe95..6a066d4c4 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 311eedaab..ea5c95610 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/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 37a60912c..2fe0f4089 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/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index a73298866..a3ed8cbe2 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 569bc8c70..fb09a2783 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 From c167693ae4918b8cf74d83bec6e923d8db2c5d7b Mon Sep 17 00:00:00 2001 From: Robert van Bregt Date: Fri, 12 May 2017 11:10:43 +0200 Subject: [PATCH 06/29] add fix for mac qt build environment Signed-off-by: Robert van Bregt --- utils/fix_mac.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 utils/fix_mac.sh diff --git a/utils/fix_mac.sh b/utils/fix_mac.sh new file mode 100755 index 000000000..2e4e84e5e --- /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 From 00ae123736a16ab08e03364bb41d0cca70ff37ea Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 11 May 2017 14:36:06 -0400 Subject: [PATCH 07/29] Adding .clang-format file. --- .clang-format | 89 ++++++++++++++++++++++++++++++++++++++++ src/gui/SearchWidget.cpp | 24 +++++------ src/gui/SearchWidget.h | 12 +++--- 3 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..5dc8fc3b0 --- /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/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index e94f838c5..2142a96fe 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 d2b94d979..e87701814 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) }; From 3822625e77a0d1736ab4e355a116ee2829508b05 Mon Sep 17 00:00:00 2001 From: Yen Chi Hsuan Date: Sat, 6 May 2017 01:49:52 +0800 Subject: [PATCH 08/29] Allow disabling .app bundles on Mac --- CMakeLists.txt | 11 ++++++++--- src/CMakeLists.txt | 7 ++++--- src/autotype/mac/CMakeLists.txt | 18 ++++++++++++------ src/core/FilePath.cpp | 6 +++--- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cde795ad2..98ae0eced 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/src/CMakeLists.txt b/src/CMakeLists.txt index 786a03a80..42cbe36bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,14 +216,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() @@ -231,7 +232,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/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index 076dd5038..ac93de0e7 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/core/FilePath.cpp b/src/core/FilePath.cpp index 0506e3ab7..2c1c20e84 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 @@ -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 From 2de5a9d2812c0e4f026453cd98a85cf2b4217eaf Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 24 Dec 2016 01:04:43 +0100 Subject: [PATCH 09/29] Lock database when OS is locked (Windows, DBus, macOS implementations) #134 --- src/CMakeLists.txt | 22 ++++++++ src/core/ScreenLockListener.cpp | 11 ++++ src/core/ScreenLockListener.h | 21 ++++++++ src/core/ScreenLockListenerDBus.cpp | 66 ++++++++++++++++++++++++ src/core/ScreenLockListenerDBus.h | 18 +++++++ src/core/ScreenLockListenerMac.cpp | 41 +++++++++++++++ src/core/ScreenLockListenerMac.h | 24 +++++++++ src/core/ScreenLockListenerPrivate.cpp | 26 ++++++++++ src/core/ScreenLockListenerPrivate.h | 19 +++++++ src/core/ScreenLockListenerWin.cpp | 69 ++++++++++++++++++++++++++ src/core/ScreenLockListenerWin.h | 21 ++++++++ src/gui/MainWindow.cpp | 10 ++++ src/gui/MainWindow.h | 3 ++ src/gui/SettingsWidget.cpp | 4 ++ src/gui/SettingsWidgetGeneral.ui | 7 +++ 15 files changed, 362 insertions(+) create mode 100644 src/core/ScreenLockListener.cpp create mode 100644 src/core/ScreenLockListener.h create mode 100644 src/core/ScreenLockListenerDBus.cpp create mode 100644 src/core/ScreenLockListenerDBus.h create mode 100644 src/core/ScreenLockListenerMac.cpp create mode 100644 src/core/ScreenLockListenerMac.h create mode 100644 src/core/ScreenLockListenerPrivate.cpp create mode 100644 src/core/ScreenLockListenerPrivate.h create mode 100644 src/core/ScreenLockListenerWin.cpp create mode 100644 src/core/ScreenLockListenerWin.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42cbe36bb..dfdf96c8a 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 diff --git a/src/core/ScreenLockListener.cpp b/src/core/ScreenLockListener.cpp new file mode 100644 index 000000000..2c17dfa14 --- /dev/null +++ b/src/core/ScreenLockListener.cpp @@ -0,0 +1,11 @@ +#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 000000000..7339054f9 --- /dev/null +++ b/src/core/ScreenLockListener.h @@ -0,0 +1,21 @@ +#ifndef SCREENLOCKLISTENER_H +#define SCREENLOCKLISTENER_H +#include + +class ScreenLockListenerPrivate; + +class ScreenLockListener : public QObject { + Q_OBJECT + +public: + ScreenLockListener(QWidget* parent=NULL); + ~ScreenLockListener(); + +Q_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 000000000..283b1e27e --- /dev/null +++ b/src/core/ScreenLockListenerDBus.cpp @@ -0,0 +1,66 @@ +#include "ScreenLockListenerDBus.h" + +#include +#include +#include + +ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): + ScreenLockListenerMac(parent) +{ + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + QDBusConnection systemBus = QDBusConnection::systemBus(); + 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())); + + /* Currently unable to get the current user session from login1.Manager + QDBusInterface login1_manager_iface("org.freedesktop.login1", "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", systemBus); + if(login1_manager_iface.isValid()){ + qint64 my_pid = QCoreApplication::applicationPid(); + QDBusReply reply = login1_manager_iface.call("GetSessionByPID",static_cast(my_pid)); + if (reply.isValid()){ + QString current_session = reply.value(); + } + } + */ +} + +void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) +{ + if(status != 0){ + Q_EMIT screenLocked(); + } +} + +void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) +{ + if(beforeSleep){ + Q_EMIT screenLocked(); + } +} + +void ScreenLockListenerDBus::unityLocked() +{ + Q_EMIT screenLocked(); +} diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h new file mode 100644 index 000000000..016932b8a --- /dev/null +++ b/src/core/ScreenLockListenerDBus.h @@ -0,0 +1,18 @@ +#ifndef SCREENLOCKLISTENERDBUS_H +#define SCREENLOCKLISTENERDBUS_H +#include +#include + +class ScreenLockListenerDBus : public ScreenLockListenerPrivate +{ + Q_OBJECT +public: + explicit ScreenLockListenerDBus(QWidget *parent = 0); + +private Q_SLOTS: + void gnomeSessionStatusChanged(uint status); + void logindPrepareForSleep(bool beforeSleep); + void unityLocked(); +}; + +#endif // SCREENLOCKLISTENERDBUS_H diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp new file mode 100644 index 000000000..57dc2de3f --- /dev/null +++ b/src/core/ScreenLockListenerMac.cpp @@ -0,0 +1,41 @@ +#include "ScreenLockListenerMac.h" + +#include +#include + +ScreenLockListenerMac* ScreenLockListenerMac::instance(){ + static QMutex mutex; + QMutexLocker lock(&mutex); + + static ScreenLockListenerMac* m_ptr=NULL; + if (m_ptr==NULL){ + m_ptr = new ScreenLockListenerMac(); + } + return m_ptr; +} + +void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, + CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/){ + instance()->onSignalReception(); +} + +ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent): + ScreenLockListenerPrivate(parent){ + CFNotificationCenterRef distCenter; + CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); + distCenter = CFNotificationCenterGetDistributedCenter(); + if (NULL == distCenter) + return; + + CFNotificationCenterAddObserver( + distCenter, + this, &ScreenLockListenerMac::notificationCenterCallBack, + screenIsLockedSignal, + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); +} + +void ScreenLockListenerMac::onSignalReception() +{ + Q_EMIT screenLocked(); +} diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h new file mode 100644 index 000000000..5824339f1 --- /dev/null +++ b/src/core/ScreenLockListenerMac.h @@ -0,0 +1,24 @@ +#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*/, + CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/); + +private: + ScreenLockListenerMac(QWidget* parent=NULL); + void onSignalReception(); + +}; + +#endif // SCREENLOCKLISTENERMAC_H diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp new file mode 100644 index 000000000..e9355c7d0 --- /dev/null +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -0,0 +1,26 @@ +#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) + return ScreenLockListenerMac::instance(); +#endif +#if defined(Q_OS_LINUX) + return new ScreenLockListenerDBus(parent); +#endif +#if 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 000000000..aef20edcc --- /dev/null +++ b/src/core/ScreenLockListenerPrivate.h @@ -0,0 +1,19 @@ +#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); + +Q_SIGNALS: + void screenLocked(); +}; + +#endif // SCREENLOCKLISTENERPRIVATE_H diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp new file mode 100644 index 000000000..349d5de58 --- /dev/null +++ b/src/core/ScreenLockListenerWin.cpp @@ -0,0 +1,69 @@ +#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!=NULL); + // 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){ + const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); + if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE){ + const DWORD* state = reinterpret_cast(&setting->Data); + if (*state == 0){ + Q_EMIT screenLocked(); + return true; + } + } + } + if(m->message == WM_WTSSESSION_CHANGE){ + if (m->wParam==WTS_CONSOLE_DISCONNECT){ + Q_EMIT screenLocked(); + return true; + } + if (m->wParam==WTS_SESSION_LOCK){ + Q_EMIT screenLocked(); + return true; + } + } + } + return false; +} diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h new file mode 100644 index 000000000..ccf42ad70 --- /dev/null +++ b/src/core/ScreenLockListenerWin.h @@ -0,0 +1,21 @@ +#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 *) Q_DECL_OVERRIDE; + +private: + void * m_powernotificationhandle; +}; + +#endif // SCREENLOCKLISTENERWIN_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0415f2b4e..65062fb79 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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()) { @@ -959,3 +962,10 @@ void MainWindow::hideYubiKeyPopup() hideGlobalMessage(); setEnabled(true); } + +void MainWindow::handleScreenLock() +{ + if (config()->get("AutoCloseOnScreenLock").toBool()){ + lockDatabasesAfterInactivity(); + } +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index f35cd4658..72082e4ed 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" @@ -91,6 +92,7 @@ private slots: void lockDatabasesAfterInactivity(); void repairDatabase(); void hideTabMessage(); + void handleScreenLock(); private: static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); @@ -112,6 +114,7 @@ private: InactivityTimer* m_inactivityTimer; int m_countDefaultAttributes; QSystemTrayIcon* m_trayIcon; + ScreenLockListener* m_screenLockListener; Q_DISABLE_COPY(MainWindow) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 716eb14f1..15f812a0d 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -141,6 +141,8 @@ void SettingsWidget::loadSettings() } } + m_generalUi->autoCloseOnScreenLockCheckBox->setChecked(config()->get("AutoCloseOnScreenLock").toBool()); + m_secUi->clearClipboardCheckBox->setChecked(config()->get("security/clearclipboard").toBool()); m_secUi->clearClipboardSpinBox->setValue(config()->get("security/clearclipboardtimeout").toInt()); @@ -186,6 +188,8 @@ void SettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); + config()->set("AutoCloseOnScreenLock", m_generalUi->autoCloseOnScreenLockCheckBox->isChecked()); + config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked()); diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 2fe0f4089..ef947d3f0 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -367,6 +367,13 @@ + + + + Close database when session is locked or lid is closed + + + From c6ecf48ccdb5d8bc56c96707f39117c43afd45e7 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 24 Dec 2016 01:05:24 +0100 Subject: [PATCH 10/29] enable OS X build with Xcode 8.2 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e24d1d178..f3aeccf72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ services: [docker] os: - linux -# - osx + - osx + +osx_image: xcode8.2 # Define clang compiler without any frills compiler: @@ -26,6 +28,7 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libgcrypt || brew install libgcrypt; fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libmicrohttpd || brew install libmicrohttpd; fi before_script: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGS="-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5"; fi From a3af8fc0ea0de6e3464dd8910d894bfbfade4025 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 31 Dec 2016 01:23:25 +0100 Subject: [PATCH 11/29] Fix Linux ScreenLockListener implementation --- src/core/ScreenLockListenerDBus.cpp | 2 +- src/core/ScreenLockListenerDBus.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 283b1e27e..4ac54a839 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -5,7 +5,7 @@ #include ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): - ScreenLockListenerMac(parent) + ScreenLockListenerPrivate(parent) { QDBusConnection sessionBus = QDBusConnection::sessionBus(); QDBusConnection systemBus = QDBusConnection::systemBus(); diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index 016932b8a..be6fcd5f0 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -2,6 +2,7 @@ #define SCREENLOCKLISTENERDBUS_H #include #include +#include "ScreenLockListenerPrivate.h" class ScreenLockListenerDBus : public ScreenLockListenerPrivate { From 44085df592a7de66d01eb6ea557964905598fc09 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 31 Dec 2016 01:32:29 +0100 Subject: [PATCH 12/29] Avoid warning in MacOS implementation --- src/core/ScreenLockListenerPrivate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index e9355c7d0..6f3f7bbf8 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -15,6 +15,7 @@ ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent): ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent){ #if defined(Q_OS_OSX) + Q_UNUSED(parent); return ScreenLockListenerMac::instance(); #endif #if defined(Q_OS_LINUX) From d1acd750684b808e447c17e4b0b5125ed4a4ba95 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sun, 1 Jan 2017 14:08:14 +0100 Subject: [PATCH 13/29] Moved "Lock databases on screen lock" setting to security settings widget. Changed wording and preference variable name for conformity with existing settings. --- src/gui/MainWindow.cpp | 2 +- src/gui/SettingsWidget.cpp | 4 ++-- src/gui/SettingsWidgetSecurity.ui | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 65062fb79..3d5fad919 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -965,7 +965,7 @@ void MainWindow::hideYubiKeyPopup() void MainWindow::handleScreenLock() { - if (config()->get("AutoCloseOnScreenLock").toBool()){ + if (config()->get("security/lockdatabasescreenlock").toBool()){ lockDatabasesAfterInactivity(); } } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 15f812a0d..75ebf2601 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -141,7 +141,6 @@ void SettingsWidget::loadSettings() } } - m_generalUi->autoCloseOnScreenLockCheckBox->setChecked(config()->get("AutoCloseOnScreenLock").toBool()); m_secUi->clearClipboardCheckBox->setChecked(config()->get("security/clearclipboard").toBool()); m_secUi->clearClipboardSpinBox->setValue(config()->get("security/clearclipboardtimeout").toInt()); @@ -149,6 +148,7 @@ void SettingsWidget::loadSettings() 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()); @@ -188,7 +188,6 @@ void SettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); - config()->set("AutoCloseOnScreenLock", m_generalUi->autoCloseOnScreenLockCheckBox->isChecked()); config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); @@ -210,6 +209,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/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 08fa6f4ea..93e3e9114 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -142,6 +142,13 @@ + + + + Lock databases when session is locked or lid is closed + + + From 289e98ed5b0e542aa3347f8ce2ef440ace6f3a0d Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sun, 1 Jan 2017 14:11:31 +0100 Subject: [PATCH 14/29] remove commented code --- src/core/ScreenLockListenerDBus.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 4ac54a839..3edb59577 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -32,18 +32,6 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): "Locked", // signal name this, //receiver SLOT(unityLocked())); - - /* Currently unable to get the current user session from login1.Manager - QDBusInterface login1_manager_iface("org.freedesktop.login1", "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", systemBus); - if(login1_manager_iface.isValid()){ - qint64 my_pid = QCoreApplication::applicationPid(); - QDBusReply reply = login1_manager_iface.call("GetSessionByPID",static_cast(my_pid)); - if (reply.isValid()){ - QString current_session = reply.value(); - } - } - */ } void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) From 1b7b2ff456dca5b17d6c25ccfc368170950bf43c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 4 May 2017 22:52:10 +0200 Subject: [PATCH 15/29] Added freedesktop DBus, fixed codestyle --- src/CMakeLists.txt | 7 +++++++ src/core/Config.cpp | 1 + src/core/ScreenLockListenerDBus.cpp | 20 ++++++++++++++++++-- src/core/ScreenLockListenerDBus.h | 1 + src/core/ScreenLockListenerMac.cpp | 16 ++++++++++------ src/core/ScreenLockListenerPrivate.cpp | 8 +++++--- src/core/ScreenLockListenerWin.cpp | 26 +++++++++++++------------- src/gui/SettingsWidgetSecurity.ui | 14 +++++++------- 8 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfdf96c8a..9dfa52679 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -222,9 +222,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) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index c0876daa5..f0a369c30 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/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 3edb59577..2b4de9da3 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -9,6 +9,15 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *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 @@ -36,14 +45,14 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) { - if(status != 0){ + if (status != 0) { Q_EMIT screenLocked(); } } void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) { - if(beforeSleep){ + if (beforeSleep) { Q_EMIT screenLocked(); } } @@ -52,3 +61,10 @@ void ScreenLockListenerDBus::unityLocked() { Q_EMIT screenLocked(); } + +void ScreenLockListenerDBus::freedesktopScreenSaver(bool status) +{ + if (status) { + Q_EMIT screenLocked(); + } +} \ No newline at end of file diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index be6fcd5f0..0c49323f7 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -14,6 +14,7 @@ private Q_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 index 57dc2de3f..7be9dd2f2 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -3,29 +3,33 @@ #include #include -ScreenLockListenerMac* ScreenLockListenerMac::instance(){ +ScreenLockListenerMac* ScreenLockListenerMac::instance() +{ static QMutex mutex; QMutexLocker lock(&mutex); static ScreenLockListenerMac* m_ptr=NULL; - if (m_ptr==NULL){ + if (m_ptr == NULL) { m_ptr = new ScreenLockListenerMac(); } return m_ptr; } void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, - CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/){ + CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/) +{ instance()->onSignalReception(); } -ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent): - ScreenLockListenerPrivate(parent){ +ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) + : ScreenLockListenerPrivate(parent) +{ CFNotificationCenterRef distCenter; CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); distCenter = CFNotificationCenterGetDistributedCenter(); - if (NULL == distCenter) + if (NULL == distCenter) { return; + } CFNotificationCenterAddObserver( distCenter, diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index 6f3f7bbf8..e0c6d234c 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -9,11 +9,13 @@ #include "ScreenLockListenerWin.h" #endif -ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent): - QObject(parent){ +ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent) + : QObject(parent) +{ } -ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent){ +ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent) +{ #if defined(Q_OS_OSX) Q_UNUSED(parent); return ScreenLockListenerMac::instance(); diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 349d5de58..21c59c723 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -8,11 +8,11 @@ * 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() +ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) + : ScreenLockListenerPrivate(parent) + , QAbstractNativeEventFilter() { - Q_ASSERT(parent!=NULL); + Q_ASSERT(parent != NULL); // On windows, we need to register for platform specific messages and // install a message handler for them QCoreApplication::instance()->installNativeEventFilter(this); @@ -26,7 +26,7 @@ ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) : // This call requests a notification for session changes if (!WTSRegisterSessionNotification( reinterpret_cast(parent->winId()), - NOTIFY_FOR_THIS_SESSION)){ + NOTIFY_FOR_THIS_SESSION)) { } } @@ -35,31 +35,31 @@ ScreenLockListenerWin::~ScreenLockListenerWin() HWND h= reinterpret_cast(static_cast(parent())->winId()); WTSUnRegisterSessionNotification(h); - if(m_powernotificationhandle){ + 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"){ + if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG* m = static_cast(message); - if(m->message == WM_POWERBROADCAST){ + if (m->message == WM_POWERBROADCAST) { const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); - if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE){ + if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { const DWORD* state = reinterpret_cast(&setting->Data); - if (*state == 0){ + if (*state == 0) { Q_EMIT screenLocked(); return true; } } } - if(m->message == WM_WTSSESSION_CHANGE){ - if (m->wParam==WTS_CONSOLE_DISCONNECT){ + if (m->message == WM_WTSSESSION_CHANGE) { + if (m->wParam == WTS_CONSOLE_DISCONNECT) { Q_EMIT screenLocked(); return true; } - if (m->wParam==WTS_SESSION_LOCK){ + if (m->wParam == WTS_SESSION_LOCK) { Q_EMIT screenLocked(); return true; } diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 93e3e9114..30ab2ecfc 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -123,6 +123,13 @@ + + + + Lock databases when session is locked or lid is closed + + + @@ -142,13 +149,6 @@ - - - - Lock databases when session is locked or lid is closed - - - From 8ddd0b2f6f12e248aa042155bd298bf6c0060901 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 4 May 2017 23:09:08 +0200 Subject: [PATCH 16/29] Revert travis settings --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3aeccf72..e24d1d178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,7 @@ services: [docker] os: - linux - - osx - -osx_image: xcode8.2 +# - osx # Define clang compiler without any frills compiler: @@ -28,7 +26,6 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libgcrypt || brew install libgcrypt; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libmicrohttpd || brew install libmicrohttpd; fi before_script: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGS="-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5"; fi From 147c000ef1478e305ed78e525a1184c2e08910a7 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 6 May 2017 22:14:53 -0400 Subject: [PATCH 17/29] Corrected nullptr crash on Windows when going to sleep --- src/core/ScreenLockListenerWin.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 21c59c723..4aae8a28d 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -45,13 +45,18 @@ bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG* m = static_cast(message); if (m->message == WM_POWERBROADCAST) { - const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); - if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { - const DWORD* state = reinterpret_cast(&setting->Data); - if (*state == 0) { - Q_EMIT screenLocked(); - return true; + 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) { + Q_EMIT screenLocked(); + return true; + } } + } else if (m->wParam == PBT_APMSUSPEND) { + Q_EMIT screenLocked(); + return true; } } if (m->message == WM_WTSSESSION_CHANGE) { From 3218cb9ace1e3fb32e8cc2d74b2b839a920276bc Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 6 May 2017 22:34:12 -0400 Subject: [PATCH 18/29] Moved locking checkboxes into security settings --- src/gui/SettingsWidgetGeneral.ui | 7 ---- src/gui/SettingsWidgetSecurity.ui | 62 +++++++++++++++++-------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index ef947d3f0..2fe0f4089 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -367,13 +367,6 @@ - - - - Close database when session is locked or lid is closed - - - diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 30ab2ecfc..679c470ad 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,18 +107,11 @@ Convenience - + - + - Don't require password repeat when it is visible - - - - - - - Show passwords in cleartext by default + Lock databases when session is locked or lid is closed @@ -124,9 +123,16 @@ - + - Lock databases when session is locked or lid is closed + Don't require password repeat when it is visible + + + + + + + Show passwords in cleartext by default From 533136fb0eb0391a294f742ddfb099adb69cec92 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 8 May 2017 16:03:19 +0200 Subject: [PATCH 19/29] Add file header, use nullptr instead of NULL, fix code style --- src/core/ScreenLockListener.cpp | 17 ++++++++++++++++ src/core/ScreenLockListener.h | 19 +++++++++++++++++- src/core/ScreenLockListenerDBus.cpp | 17 ++++++++++++++++ src/core/ScreenLockListenerDBus.h | 17 ++++++++++++++++ src/core/ScreenLockListenerMac.cpp | 25 ++++++++++++++++++++---- src/core/ScreenLockListenerMac.h | 19 +++++++++++++++++- src/core/ScreenLockListenerPrivate.cpp | 17 ++++++++++++++++ src/core/ScreenLockListenerPrivate.h | 21 ++++++++++++++++++-- src/core/ScreenLockListenerWin.cpp | 27 +++++++++++++++++++++----- src/core/ScreenLockListenerWin.h | 23 +++++++++++++++++++--- 10 files changed, 186 insertions(+), 16 deletions(-) diff --git a/src/core/ScreenLockListener.cpp b/src/core/ScreenLockListener.cpp index 2c17dfa14..eb78cd608 100644 --- a/src/core/ScreenLockListener.cpp +++ b/src/core/ScreenLockListener.cpp @@ -1,3 +1,20 @@ +/* + * 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" diff --git a/src/core/ScreenLockListener.h b/src/core/ScreenLockListener.h index 7339054f9..bc115d19c 100644 --- a/src/core/ScreenLockListener.h +++ b/src/core/ScreenLockListener.h @@ -1,3 +1,20 @@ +/* + * 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 @@ -8,7 +25,7 @@ class ScreenLockListener : public QObject { Q_OBJECT public: - ScreenLockListener(QWidget* parent=NULL); + ScreenLockListener(QWidget* parent = nullptr); ~ScreenLockListener(); Q_SIGNALS: diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 2b4de9da3..e24e388a3 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -1,3 +1,20 @@ +/* + * 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 diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index 0c49323f7..a907f20cc 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -1,3 +1,20 @@ +/* + * 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 diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index 7be9dd2f2..3917a888c 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -1,3 +1,20 @@ +/* + * 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 @@ -8,8 +25,8 @@ ScreenLockListenerMac* ScreenLockListenerMac::instance() static QMutex mutex; QMutexLocker lock(&mutex); - static ScreenLockListenerMac* m_ptr=NULL; - if (m_ptr == NULL) { + static ScreenLockListenerMac* m_ptr = nullptr; + if (m_ptr == nullptr) { m_ptr = new ScreenLockListenerMac(); } return m_ptr; @@ -27,7 +44,7 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) CFNotificationCenterRef distCenter; CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); distCenter = CFNotificationCenterGetDistributedCenter(); - if (NULL == distCenter) { + if (nullptr == distCenter) { return; } @@ -35,7 +52,7 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) distCenter, this, &ScreenLockListenerMac::notificationCenterCallBack, screenIsLockedSignal, - NULL, + nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); } diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h index 5824339f1..733cb3756 100644 --- a/src/core/ScreenLockListenerMac.h +++ b/src/core/ScreenLockListenerMac.h @@ -1,3 +1,20 @@ +/* + * 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 @@ -16,7 +33,7 @@ public: CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/); private: - ScreenLockListenerMac(QWidget* parent=NULL); + ScreenLockListenerMac(QWidget* parent = nullptr); void onSignalReception(); }; diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index e0c6d234c..9a0ebd69b 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -1,3 +1,20 @@ +/* + * 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" diff --git a/src/core/ScreenLockListenerPrivate.h b/src/core/ScreenLockListenerPrivate.h index aef20edcc..781d64527 100644 --- a/src/core/ScreenLockListenerPrivate.h +++ b/src/core/ScreenLockListenerPrivate.h @@ -1,3 +1,20 @@ +/* + * 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 @@ -7,10 +24,10 @@ class ScreenLockListenerPrivate : public QObject { Q_OBJECT public: - static ScreenLockListenerPrivate* instance(QWidget *parent = 0); + static ScreenLockListenerPrivate* instance(QWidget* parent = 0); protected: - ScreenLockListenerPrivate(QWidget *parent = 0); + ScreenLockListenerPrivate(QWidget* parent = 0); Q_SIGNALS: void screenLocked(); diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 4aae8a28d..3e7950abc 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -1,3 +1,20 @@ +/* + * 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 @@ -8,11 +25,11 @@ * 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) +ScreenLockListenerWin::ScreenLockListenerWin(QWidget* parent) : ScreenLockListenerPrivate(parent) , QAbstractNativeEventFilter() { - Q_ASSERT(parent != NULL); + 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); @@ -21,7 +38,7 @@ ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) HPOWERNOTIFY hPnotify = RegisterPowerSettingNotification( reinterpret_cast(parent->winId()), &GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE); - m_powernotificationhandle = reinterpret_cast(hPnotify); + m_powerNotificationHandle = reinterpret_cast(hPnotify); // This call requests a notification for session changes if (!WTSRegisterSessionNotification( @@ -40,10 +57,10 @@ ScreenLockListenerWin::~ScreenLockListenerWin() } } -bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void *message, long *) +bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void* message, long *) { if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { - MSG* m = static_cast(message); + MSG* m = static_cast(message); if (m->message == WM_POWERBROADCAST) { if (m->wParam == PBT_POWERSETTINGCHANGE) { const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h index ccf42ad70..0a1c37f91 100644 --- a/src/core/ScreenLockListenerWin.h +++ b/src/core/ScreenLockListenerWin.h @@ -1,3 +1,20 @@ +/* + * 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 @@ -10,12 +27,12 @@ class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstract { Q_OBJECT public: - explicit ScreenLockListenerWin(QWidget *parent = 0); + explicit ScreenLockListenerWin(QWidget* parent = 0); ~ScreenLockListenerWin(); - virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) Q_DECL_OVERRIDE; + virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long *) override; private: - void * m_powernotificationhandle; + void* m_powerNotificationHandle ; }; #endif // SCREENLOCKLISTENERWIN_H From 970525cfd4c5714ce19516517254138153ca2b59 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 10 May 2017 11:05:17 -0400 Subject: [PATCH 20/29] Styling + CFNotificationName -> CFStringRef --- src/core/ScreenLockListenerMac.cpp | 17 +++++++++-------- src/core/ScreenLockListenerMac.h | 5 +++-- src/core/ScreenLockListenerWin.cpp | 2 +- src/core/ScreenLockListenerWin.h | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index 3917a888c..d919adb6c 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -32,8 +32,9 @@ ScreenLockListenerMac* ScreenLockListenerMac::instance() return m_ptr; } -void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, - CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/) +void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef, void*, + CFStringRef, const void*, + CFDictionaryRef) { instance()->onSignalReception(); } @@ -48,12 +49,12 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) return; } - CFNotificationCenterAddObserver( - distCenter, - this, &ScreenLockListenerMac::notificationCenterCallBack, - screenIsLockedSignal, - nullptr, - CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver(distCenter, + this, + &ScreenLockListenerMac::notificationCenterCallBack, + screenIsLockedSignal, + nullptr, + CFNotificationSuspensionBehaviorDeliverImmediately); } void ScreenLockListenerMac::onSignalReception() diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h index 733cb3756..cd36ce9e6 100644 --- a/src/core/ScreenLockListenerMac.h +++ b/src/core/ScreenLockListenerMac.h @@ -29,8 +29,9 @@ class ScreenLockListenerMac: public ScreenLockListenerPrivate { public: static ScreenLockListenerMac* instance(); - static void notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, - CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/); + static void notificationCenterCallBack(CFNotificationCenterRef center, void* observer, + CFStringRef name, const void* object, + CFDictionaryRef userInfo); private: ScreenLockListenerMac(QWidget* parent = nullptr); diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 3e7950abc..9429393fe 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -57,7 +57,7 @@ ScreenLockListenerWin::~ScreenLockListenerWin() } } -bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void* message, long *) +bool ScreenLockListenerWin::nativeEventFilter(const QByteArray& eventType, void* message, long*) { if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG* m = static_cast(message); diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h index 0a1c37f91..6a8380efe 100644 --- a/src/core/ScreenLockListenerWin.h +++ b/src/core/ScreenLockListenerWin.h @@ -29,7 +29,7 @@ class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstract public: explicit ScreenLockListenerWin(QWidget* parent = 0); ~ScreenLockListenerWin(); - virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long *) override; + virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long*) override; private: void* m_powerNotificationHandle ; From b69b50c6c6f2152cedf4528af24beed6179bcbbe Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 17 May 2017 13:03:51 +0200 Subject: [PATCH 21/29] fix codestyle and use C++11 keywords --- src/core/ScreenLockListener.h | 2 +- src/core/ScreenLockListenerDBus.cpp | 8 ++++---- src/core/ScreenLockListenerDBus.h | 2 +- src/core/ScreenLockListenerMac.cpp | 2 +- src/core/ScreenLockListenerPrivate.cpp | 6 ++---- src/core/ScreenLockListenerPrivate.h | 2 +- src/core/ScreenLockListenerWin.cpp | 8 ++++---- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/core/ScreenLockListener.h b/src/core/ScreenLockListener.h index bc115d19c..b4eb81e04 100644 --- a/src/core/ScreenLockListener.h +++ b/src/core/ScreenLockListener.h @@ -28,7 +28,7 @@ public: ScreenLockListener(QWidget* parent = nullptr); ~ScreenLockListener(); -Q_SIGNALS: +signals: void screenLocked(); private: diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index e24e388a3..1976b47ea 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -63,25 +63,25 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) { if (status != 0) { - Q_EMIT screenLocked(); + emit screenLocked(); } } void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) { if (beforeSleep) { - Q_EMIT screenLocked(); + emit screenLocked(); } } void ScreenLockListenerDBus::unityLocked() { - Q_EMIT screenLocked(); + emit screenLocked(); } void ScreenLockListenerDBus::freedesktopScreenSaver(bool status) { if (status) { - Q_EMIT screenLocked(); + emit screenLocked(); } } \ No newline at end of file diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index a907f20cc..72f308f72 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -27,7 +27,7 @@ class ScreenLockListenerDBus : public ScreenLockListenerPrivate public: explicit ScreenLockListenerDBus(QWidget *parent = 0); -private Q_SLOTS: +private slots: void gnomeSessionStatusChanged(uint status); void logindPrepareForSleep(bool beforeSleep); void unityLocked(); diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index d919adb6c..dce05de3f 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -59,5 +59,5 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) void ScreenLockListenerMac::onSignalReception() { - Q_EMIT screenLocked(); + emit screenLocked(); } diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index 9a0ebd69b..36ee301f2 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -36,11 +36,9 @@ ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent) #if defined(Q_OS_OSX) Q_UNUSED(parent); return ScreenLockListenerMac::instance(); -#endif -#if defined(Q_OS_LINUX) +#elif defined(Q_OS_LINUX) return new ScreenLockListenerDBus(parent); -#endif -#if defined(Q_OS_WIN) +#elif defined(Q_OS_WIN) return new ScreenLockListenerWin(parent); #endif } diff --git a/src/core/ScreenLockListenerPrivate.h b/src/core/ScreenLockListenerPrivate.h index 781d64527..8ecad17d8 100644 --- a/src/core/ScreenLockListenerPrivate.h +++ b/src/core/ScreenLockListenerPrivate.h @@ -29,7 +29,7 @@ public: protected: ScreenLockListenerPrivate(QWidget* parent = 0); -Q_SIGNALS: +signals: void screenLocked(); }; diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 9429393fe..a1bf13d4f 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -67,22 +67,22 @@ bool ScreenLockListenerWin::nativeEventFilter(const QByteArray& eventType, void* if (setting != nullptr && setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { const DWORD* state = reinterpret_cast(&setting->Data); if (*state == 0) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } } } else if (m->wParam == PBT_APMSUSPEND) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } } if (m->message == WM_WTSSESSION_CHANGE) { if (m->wParam == WTS_CONSOLE_DISCONNECT) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } if (m->wParam == WTS_SESSION_LOCK) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } } From 00dc4b9ace3b23cffc09115a9e05c06742dc01f1 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 17 May 2017 10:05:45 +0200 Subject: [PATCH 22/29] Ignore double close event on macOS, resolves #430 --- src/gui/MainWindow.cpp | 15 +++++++++++---- src/gui/MainWindow.h | 4 +++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 3d5fad919..6a4117377 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 @@ -347,7 +347,7 @@ MainWindow::~MainWindow() void MainWindow::appExit() { - appExitCalled = true; + m_appExitCalled = true; close(); } @@ -663,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(); @@ -680,6 +686,7 @@ void MainWindow::closeEvent(QCloseEvent* event) bool accept = saveLastDatabases(); if (accept) { + m_appExiting = true; saveWindowInformation(); event->accept(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 72082e4ed..fffc634a9 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -40,6 +40,7 @@ class MainWindow : public QMainWindow public: MainWindow(); ~MainWindow(); + enum StackedWidgetIndex { DatabaseTabScreen = 0, @@ -118,7 +119,8 @@ private: Q_DISABLE_COPY(MainWindow) - bool appExitCalled; + bool m_appExitCalled; + bool m_appExiting; }; #define KEEPASSXC_MAIN_WINDOW (qobject_cast(qApp) ? \ From 1c54d2496269cf88f9d429d3a3f9671738c6fad9 Mon Sep 17 00:00:00 2001 From: Weslly Date: Fri, 19 May 2017 00:42:31 -0300 Subject: [PATCH 23/29] Fix quit submenu on macOS tray icon --- src/gui/MainWindow.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4e25a9426..a02d75b80 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -747,10 +747,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); From c0640e49eeed5b999739f397dee0389411394d24 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 19 May 2017 14:30:09 +0200 Subject: [PATCH 24/29] revert old if structure --- src/gui/MainWindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index a02d75b80..ce8653e63 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -660,7 +660,9 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { - if (isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool() && !appExitCalled) + bool minimizeOnClose = isTrayIconEnabled() && + config()->get("GUI/MinimizeOnClose").toBool(); + if (minimizeOnClose && !appExitCalled) { event->ignore(); hideWindow(); From a2e82dc88395faa73dcf33471637c9465b6ae336 Mon Sep 17 00:00:00 2001 From: louib Date: Fri, 19 May 2017 14:04:11 -0400 Subject: [PATCH 25/29] Feature : clip command (#578) --- src/cli/CMakeLists.txt | 2 ++ src/cli/Clip.cpp | 73 +++++++++++++++++++++++++++++++++++++++ src/cli/Clip.h | 27 +++++++++++++++ src/cli/Extract.cpp | 9 +++-- src/cli/List.cpp | 11 +++--- src/cli/Merge.cpp | 29 ++++++++-------- src/cli/Show.cpp | 15 ++++---- src/cli/keepassxc-cli.cpp | 10 ++++-- src/core/Database.cpp | 2 +- src/core/Group.cpp | 46 ++++++++++++++++++++++-- src/core/Group.h | 4 ++- src/core/Uuid.cpp | 7 ++++ src/core/Uuid.h | 3 ++ tests/TestGroup.cpp | 52 ++++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 15 files changed, 250 insertions(+), 41 deletions(-) create mode 100644 src/cli/Clip.cpp create mode 100644 src/cli/Clip.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index e090ad1d8..86edb9c01 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 000000000..b3d4fd107 --- /dev/null +++ b/src/cli/Clip.cpp @@ -0,0 +1,73 @@ +/* + * 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 "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "gui/Clipboard.h" +#include "keys/CompositeKey.h" + +int Clip::execute(int argc, char** argv) +{ + QApplication app(argc, argv); + 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.")); + parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to clip.")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 2) { + parser.showHelp(); + return EXIT_FAILURE; + } + + out << "Insert the database password\n> "; + out.flush(); + + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + + Database* db = Database::openDatabaseFile(args.at(0), key); + 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 000000000..944184095 --- /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 81a9ddf07..74fa33da7 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 1e3488106..af3bf0c6d 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -30,7 +30,8 @@ #include "core/Group.h" #include "keys/CompositeKey.h" -void printGroup(Group* group, QString baseName, int depth) { +void printGroup(Group* group, QString baseName, int depth) +{ QTextStream out(stdout); @@ -46,23 +47,21 @@ void printGroup(Group* group, QString baseName, int depth) { } for (Entry* entry : group->entries()) { - out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n"; + 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.")); parser.process(app); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index aa399dd5b..8ff8a7b20 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -37,11 +37,15 @@ int Merge::execute(int argc, char** argv) 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.")); parser.addOption(samePasswordOption); parser.process(app); @@ -54,22 +58,20 @@ int Merge::execute(int argc, char** argv) out << "Insert the first database password\n> "; out.flush(); - + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line1 = inputTextStream.readLine(); CompositeKey key1 = CompositeKey::readFromLine(line1); CompositeKey key2; if (parser.isSet("same-password")) { - key2 = *key1.clone(); + key2 = *key1.clone(); + } else { + out << "Insert the second database password\n> "; + out.flush(); + QString line2 = inputTextStream.readLine(); + key2 = CompositeKey::readFromLine(line2); } - 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) { @@ -104,5 +106,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 9222a093d..59c0219a2 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 b27b7483f..f6b5df8d7 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(); @@ -53,6 +54,7 @@ int main(int argc, char **argv) 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.")); @@ -82,7 +84,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") { @@ -110,5 +115,4 @@ int main(int argc, char **argv) #endif return exitCode; - } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5b5a707f9..b3897efae 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -367,7 +367,7 @@ void Database::startModifiedTimer() m_timer->start(150); } -const CompositeKey & Database::key() const +const CompositeKey& Database::key() const { return m_data.key; } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index bd4f8851b..886e55cae 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -483,7 +483,24 @@ 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; + } + } + } + + return findEntryByPath(entryId); +} + +Entry* Group::findEntryByUuid(const Uuid& uuid) { Q_ASSERT(!uuid.isNull()); for (Entry* entry : asConst(m_entries)) { @@ -495,6 +512,29 @@ 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; +} + QList Group::groupsRecursive(bool includeSelf) const { QList groupList; @@ -551,10 +591,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 e3e5e7554..a1b2bcb46 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); diff --git a/src/core/Uuid.cpp b/src/core/Uuid.cpp index 1b046c5a3..1b531159f 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 4164399d6..ecb20e0c3 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/tests/TestGroup.cpp b/tests/TestGroup.cpp index e87e6cedc..425bc75c7 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -567,3 +567,55 @@ 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")); + + entry = db->rootGroup()->findEntry(QString("invalid/path/to/entry2")); + 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; +} diff --git a/tests/TestGroup.h b/tests/TestGroup.h index c9ed8f087..87795dea2 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -38,6 +38,7 @@ private slots: void testMergeConflict(); void testMergeDatabase(); void testMergeConflictKeepBoth(); + void testFindEntry(); private: Database* createMergeTestDatabase(); From 54ad927044439d5852f524edd7837157f43b43ad Mon Sep 17 00:00:00 2001 From: louib Date: Sun, 21 May 2017 13:05:44 -0400 Subject: [PATCH 26/29] Moving print group in Group class. (#586) --- src/cli/List.cpp | 32 +++++++------------------------- src/core/Group.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/core/Group.h | 1 + tests/TestGroup.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 5 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index af3bf0c6d..8dd250b34 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -30,30 +30,6 @@ #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) { @@ -63,6 +39,11 @@ int List::execute(int argc, char** argv) QCommandLineParser parser; 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(); @@ -83,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/core/Group.cpp b/src/core/Group.cpp index 886e55cae..38acc2d60 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; @@ -535,6 +535,38 @@ Entry* Group::findEntryByPath(QString entryPath, QString basePath) 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; diff --git a/src/core/Group.h b/src/core/Group.h index a1b2bcb46..9b1f0209f 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -123,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/tests/TestGroup.cpp b/tests/TestGroup.cpp index 425bc75c7..4fb5e4b3b 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -619,3 +619,48 @@ void TestGroup::testFindEntry() 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 87795dea2..a0ed9282b 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -39,6 +39,7 @@ private slots: void testMergeDatabase(); void testMergeConflictKeepBoth(); void testFindEntry(); + void testPrint(); private: Database* createMergeTestDatabase(); From eeafe7761479068fda7aec772f0dc36a8d5b1422 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 21 May 2017 13:51:16 -0400 Subject: [PATCH 27/29] Find entry by title. --- src/core/Group.cpp | 13 ++++++++++++- tests/TestGroup.cpp | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 38acc2d60..0c83fa303 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -497,7 +497,18 @@ Entry* Group::findEntry(QString entryId) } } - return findEntryByPath(entryId); + 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) diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 4fb5e4b3b..a706badad 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -606,9 +606,17 @@ void TestGroup::testFindEntry() 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); From c3bd5d21aa239761f6c4998cc79f8846c10d6718 Mon Sep 17 00:00:00 2001 From: louib Date: Mon, 22 May 2017 17:53:41 -0400 Subject: [PATCH 28/29] Adding a GUI prompt for password. (#587) --- src/cli/Clip.cpp | 30 ++++++++++++++++++++---------- src/cli/keepassxc-cli.cpp | 7 ++++--- src/core/Database.cpp | 15 +++++++++++++++ src/core/Database.h | 3 ++- src/gui/UnlockDatabaseDialog.cpp | 26 +++++++++++++++++++++----- src/gui/UnlockDatabaseDialog.h | 3 ++- 6 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index b3d4fd107..f587b3f10 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -26,37 +26,47 @@ #include #include +#include "gui/UnlockDatabaseDialog.h" #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" #include "gui/Clipboard.h" -#include "keys/CompositeKey.h" int Clip::execute(int argc, char** argv) { - QApplication 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", "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(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 database password\n> "; - out.flush(); + 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)); + } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line = inputTextStream.readLine(); - CompositeKey key = CompositeKey::readFromLine(line); - - Database* db = Database::openDatabaseFile(args.at(0), key); if (!db) { return EXIT_FAILURE; } diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index f6b5df8d7..cc09f8a98 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -47,9 +47,6 @@ 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."); @@ -72,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; } @@ -104,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; } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index b3897efae..07bf575a8 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -18,6 +18,7 @@ #include "Database.h" #include +#include #include #include @@ -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 7728d14c8..37745e840 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/gui/UnlockDatabaseDialog.cpp b/src/gui/UnlockDatabaseDialog.cpp index 3d002f756..c3c62c5a8 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 daf8a0f1f..732395eff 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); From dcc8094ce4ae67f6dd48d9c3a4bf8325a416be89 Mon Sep 17 00:00:00 2001 From: louib Date: Thu, 25 May 2017 13:07:24 -0400 Subject: [PATCH 29/29] Add the GUI prompt option to the merge command. (#589) --- src/cli/Merge.cpp | 51 ++++++++++++++++++++++++++----------------- src/core/Database.cpp | 2 +- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 8ff8a7b20..ca2c71013 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -19,6 +19,7 @@ #include "Merge.h" +#include #include #include #include @@ -27,12 +28,15 @@ #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; @@ -47,38 +51,45 @@ int Merge::execute(int argc, char** argv) << "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(); + Database* db1; + Database* db2; - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line1 = inputTextStream.readLine(); - CompositeKey key1 = CompositeKey::readFromLine(line1); - - 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 { - out << "Insert the second database password\n> "; - out.flush(); - QString line2 = inputTextStream.readLine(); - key2 = CompositeKey::readFromLine(line2); + 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())); + } } - - 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; } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 07bf575a8..64fc3469a 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -403,7 +403,7 @@ Database* Database::unlockFromStdin(QString databaseFilename) static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout); - outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n>"); + outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> "); outputTextStream.flush(); QString line = inputTextStream.readLine();