From 9ebfc9eb777f188ce092dfadfaa3457cd290d4d6 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:04:28 +0200 Subject: [PATCH 01/34] Create login screen UI --- qtox.pro | 9 +- src/widget/loginscreen.cpp | 14 + src/widget/loginscreen.h | 22 + src/widget/loginscreen.ui | 1022 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1064 insertions(+), 3 deletions(-) create mode 100644 src/widget/loginscreen.cpp create mode 100644 src/widget/loginscreen.h create mode 100644 src/widget/loginscreen.ui diff --git a/qtox.pro b/qtox.pro index 625de5625..ac8b16260 100644 --- a/qtox.pro +++ b/qtox.pro @@ -32,7 +32,8 @@ FORMS += \ src/widget/form/setpassworddialog.ui \ src/chatlog/content/filetransferwidget.ui \ src/widget/form/settings/advancedsettings.ui \ - src/android.ui + src/android.ui \ + src/widget/loginscreen.ui CONFIG += c++11 @@ -467,7 +468,8 @@ SOURCES += \ src/video/cameradevice.cpp \ src/video/camerasource.cpp \ src/video/corevideosource.cpp \ - src/core/toxid.cpp + src/core/toxid.cpp \ + src/widget/loginscreen.cpp HEADERS += \ src/audio.h \ @@ -500,4 +502,5 @@ HEADERS += \ src/video/camerasource.h \ src/video/corevideosource.h \ src/video/videomode.h \ - src/core/toxid.h + src/core/toxid.h \ + src/widget/loginscreen.h diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp new file mode 100644 index 000000000..215d1752f --- /dev/null +++ b/src/widget/loginscreen.cpp @@ -0,0 +1,14 @@ +#include "loginscreen.h" +#include "ui_loginscreen.h" + +LoginScreen::LoginScreen(QWidget *parent) : + QWidget(parent), + ui(new Ui::LoginScreen) +{ + ui->setupUi(this); +} + +LoginScreen::~LoginScreen() +{ + delete ui; +} diff --git a/src/widget/loginscreen.h b/src/widget/loginscreen.h new file mode 100644 index 000000000..a0745c862 --- /dev/null +++ b/src/widget/loginscreen.h @@ -0,0 +1,22 @@ +#ifndef LOGINSCREEN_H +#define LOGINSCREEN_H + +#include + +namespace Ui { +class LoginScreen; +} + +class LoginScreen : public QWidget +{ + Q_OBJECT + +public: + explicit LoginScreen(QWidget *parent = 0); + ~LoginScreen(); + +private: + Ui::LoginScreen *ui; +}; + +#endif // LOGINSCREEN_H diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui new file mode 100644 index 000000000..743d8e231 --- /dev/null +++ b/src/widget/loginscreen.ui @@ -0,0 +1,1022 @@ + + + LoginScreen + + + + 0 + 0 + 400 + 200 + + + + + 400 + 200 + + + + + 400 + 200 + + + + + + + + + 28 + 28 + 28 + + + + + + + 255 + 255 + 255 + + + + + + + 28 + 28 + 28 + + + + + + + 28 + 28 + 28 + + + + + + + + + 28 + 28 + 28 + + + + + + + 255 + 255 + 255 + + + + + + + 28 + 28 + 28 + + + + + + + 28 + 28 + 28 + + + + + + + + + 28 + 28 + 28 + + + + + + + 128 + 128 + 128 + + + + + + + 28 + 28 + 28 + + + + + + + 28 + 28 + 28 + + + + + + + + Form + + + true + + + + + 150 + 0 + 250 + 200 + + + + + + + + + 255 + 255 + 255 + + + + + + + 214 + 210 + 207 + + + + + + + + + 255 + 255 + 255 + + + + + + + 214 + 210 + 207 + + + + + + + + + 28 + 28 + 28 + + + + + + + 214 + 210 + 207 + + + + + + + + true + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border-style: solid; +border-width: 15px 0 15px 15px; +border-color: #d6d2cf #d6d2cf #d6d2cf #1c1c1c; +margin-top:125px; +margin-bottom:45px; + + + + + + + + + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Username: + + + Qt::AlignCenter + + + + + + + + 0 + 25 + + + + 64 + + + + + + + Password: + + + Qt::AlignCenter + + + + + + + + 0 + 25 + + + + 64 + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 108 + 200 + 101 + + + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 108 + 200 + 101 + + + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 108 + 200 + 101 + + + + + + + + + 75 + true + + + + false + + + border-radius:5px; +padding:5px; +color:white; +background-color:#6cc865; + + + Create Account + + + true + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + border-style: solid; +border-width: 15px 0 15px 15px; +border-color: #d6d2cf #d6d2cf #d6d2cf #1c1c1c; +margin-top:165px; +margin-bottom:5px; + + + + + + + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Username: + + + Qt::AlignCenter + + + + + + + + 0 + 25 + + + + 64 + + + + + + + Password: + + + Qt::AlignCenter + + + + + + + + 0 + 25 + + + + 64 + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 108 + 200 + 101 + + + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 108 + 200 + 101 + + + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 108 + 200 + 101 + + + + + + + 108 + 200 + 101 + + + + + + + + + 75 + true + + + + false + + + border-radius:5px; +padding:5px; +color:white; +background-color:#6cc865; + + + Login + + + true + + + + + + + + + + + + + + + + 10 + 160 + 125 + 1 + + + + + + + + + 117 + 117 + 117 + + + + + + + + + 117 + 117 + 117 + + + + + + + + + 128 + 128 + 128 + + + + + + + + QFrame::Plain + + + Qt::Horizontal + + + + + + 25 + 130 + 100 + 22 + + + + + 75 + true + + + + New Profile + + + true + + + + + + 25 + 170 + 100 + 22 + + + + + 75 + true + + + + Login + + + true + + + + + + From 93df0dec2b27aa49e3ecf9cbd2aed99158e7af52 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:13:41 +0200 Subject: [PATCH 02/34] Add Tox logo at login --- img/login_logo.png | Bin 0 -> 24304 bytes res.qrc | 1 + src/widget/loginscreen.ui | 23 ++++++++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 img/login_logo.png diff --git a/img/login_logo.png b/img/login_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b74304e1e3a66856bdbb50991643eb16eafbcdaa GIT binary patch literal 24304 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

!Ud+ERtkVY!kSsWO*h`zc?TvBVPlv~RQ ze7gG!a+q1^p>J3~sV~yxl^*&Ge5R24Rj7pjrHHUzi;B8zK5Yg{d0HBfc2Low=Dp!6 zSKv7*b^qO5BcJ`k4A#yES1<>=PH%c`x#ssqYnj2V0v|sdm+(B7U0w^iA6)uck@1nx z+vriLe@8^98~Z$etm^bQTIw0N?%^x@d}#VCY498R%knFcxGJU3O&#AyPIc^gSD3za3Vhq{wK26PpbFb*23lA^U3z&-;U!((mJ7L=Kz!o%O$TH0`(Z z445<9SE+&XA4&Z0uV1G7z{k_oM+FrwP0)8f^#MQ?wJ_wtk0@-OY>>L6qN=dZ{r>Md z?%w55Gb;wdg98Yl>e?drRXoWyB$s8zTBh&1G#jySadQ5P(D7bM7O4CW^KZIqtJ?u? zOnb-?=BSlXSX6W^53;Rv1s(C?GKWlVOSDG22b$S0G_X(G)Ct6?{K!hX2ok;}ldus( zX1Z^dcsC>$BvdR)6D+SJ40>d95fNZ!;bKFFQXqm2j8p>Shn5lu77dPX#}~CU3w1SL zRv+~2qQQ+qVrgz(S!Nu|Xl>j0srHiE3q-_Fz-s)o^M*m*WNqzKxwhEWu#9=VsGz1c zfe3b75kW=3E4Rpl?{b*RMku+_P{9%o4m3Epuof;5v%$py-0=VP>3=^F!KYevqd*kR zVS=D2cE<`-A|GJL7uZoqR>r*Oa2JO4UV3_(f=F=za(}=rnS2Dj}6EtMDC2H0qm}+$P?r{toBnraU}$KVg%H#aKJY^7=lV#TR$5E$e*rVY39d zLgk5qwR!s;(9K{hKweD*lzY`!Ed>OjZ2ftfY?kB;$mQA3l{W)VEa%Ry|5KG}VC?os za!#nrKdY$uvavmE0cX)-4NfaQnN|jJ06V|MaKbh(-4764iWuvc8N#0&8Usj`%>+LN7?Bg0o-|nwF zRQxNE<=>pG?P!pYkU(Mle?R^IKID#~L)MI#Z@e@IN?FR#5FVu$>U$Asil5XdZAz#L<&Z`KVn6Oh)&BYR#}J4g^Y2Ysqzy8O4iQ|u%!Xd}TF_*hi)>2);xcB{8wHMG zrSHQTUGC#1e&-h-kDG_Vne<)ZZ>-f-OX zbHih1=8y^eu1L(iaf|fbjpgh944fW?aA2W2=sz2ej~RT(oNDITPkqbbGqzB>I8fH$ zZM(FQX<74}A#_v04E2kVkPT@rhMAE-8SBS6fm?i+$#!%d`sL<5^DK-b0p~i(i%f6~T zgpkW7(0{A%?Z{%op;rcK?5F|*O|sTM{xvvh%FI&*{aq`Dss%PMffp zRGW2IpKT5D-SXp{EXERYSqpaw+~~}o9N^U~(sufdUz4;f`#v*hSJA0u$ACXiHOMZ) z)M#Q%Rp;d~>J!;{IlE)&cXm5p7{mU}fbeYH8R$6X12Kf(!_mn)m;iWXo9q@i_TG^W#PD(5T~zvt>4qebvMs({|>4 zfp&_af@8X@BeIT@(@7stK5M!MZ+0_2?4JP7;l;8Ls7&lS`v>Kap*=Y=S82fYRG4O3 z+M>_EnDr)edduO#{v;!d)aOrb?Mo4-e6O?y!RD_yt+|I!l`QecVMw+%6`j*fkz0BL}4}(HUGKLDRW16 z8#6ox^&*H8so^|Yza-3}a|N?WePBDdSFGPn!AXoc)pDP24X6hcD2mQo5kApEPG z&TX4mbXZ#ZO4c102&&~_)gek^Z#m6|i*rCI0B37lsA#i4&17+=;n<*Y_+d$vNWM(H zrp(fxQa3E+pyV=95i#-WZ@*t_Pfs_G@xteOJ|DCsBq#udbjfj3YhwKohnZ&F;P6KV zf6n)QFus#i#AjQKYWCgUCPDlo88Vo}5on}FcL(36%S>Z%>))Vt2L5flT{#WVGb1?J zP5JIH8@2l*3Y+8z8_r^kst^PRT@>vYbP&vs=H7l-d7jZy;||geATbSmy!zErvLnO5 z1Ncx;Vd2i&$7z+Tvg6EIR`jh(qCh(N9j}pVIV8F-Jja~cYMa$|fazA5-aPJB08<(( z{^=^vUzO7W2b??c#uFPcMg^in&(G<4ZP7o9G`3oK z>_7NDg{9edemw#85fI^tj>4fSEMHh{X;!|Oz)V7f9 zN`@Jx7gu-pEiJkkw~0TF_PVF=*z6NZ*d}e&uCq$%ozycizTS*xn`254d;5>9?Rhoi zB!(0H$YRNnj-L-&oG&H@=nrdIH7#Oxzd+0|CWn0^^Z@T&u?R6!f;~}&+4}q8OTuZp z*s0u>O1|&WuS*W`VE6HbV0Hd&hjp5=I0m(0iYO zFE7+cTQAEWgAF?WDu86Fme-eQj*C(3^g$#*PH87k0>OME;1y^TTGyS=7lS@D7e8#L ztLwh48wiN%*ij5lMvOltOe}W>KHq$3$~_4+aq4{Z0IJwcX=dHo_orI59pNy#&n_iyKiUA|6( zRA6_~g>J{w(b&J)Uf4?=FIh-g`+cf0e9Es34s4dBQeK>(+yiAyrNeSptav)Xnj)4w94h zo7Kaqsp*R>%fyLNG9*dAL`<~G06}=esy8Qc zv&?n4+|hm*JI2<*jcPv^;LAc@<`CO2^?r}X{6hT3dE9?5({0=h(8P*5-c{F8md{%x z-$h8)a&bx7_Sza8GbXZxZsyX7jX1Wn+`7E;U@Q0C#zlTCI_!Iq@e3O`!Xpg%4-v{C?@j zxafXEEP)F{xUuUnZSmaKA<6yB4;#ng1VnRzp7(VVqC}^}MU~b0a$OAqRUaR;p90#_ z+Ojb+h7qZv2E2#xMYRlh);k3eG2_!ouZ0bZKz)qBQFnOEU2Y0*BUj}?)yOP(G*)1H zco_Q(_Y?Te@+_6B4=EAEE?#Sv3DCS1#u8|#+I~m-jE@9|P9m)TPseuui46%E*@T{% z*=wUKII-|GS~4>#te=?PRc5$f&G;ZcZIlu#j;n;nNtC7{syVXTkPLoR`|;fHmF%`! zDsw-e^K9h2U$m6uEiq$$)1iCpPwl9Iq|vzqt5%Wn{+@@gAo!_aZbn>fI?I1Ou7c$wbJ=|7!LSwRcDmGsVyY z3E0Ky*1zvhJLi2TvPHn&2T6p?A3Q|1Oo-_0+$vJGw)=={CxGY?TQ(UP^x)}e8hs@nT}Vb9HsKDK3bdm%{{421~>_E~hO*_!m0V)%GN@T}Z|u2pi7t9;HZtcZq*kJ`H5fnG6^zfE5b=bKt6QT< z3?=$_In7Gu*F^usV6*8776@uUeR~maLMH!i z_6oc%q4Z5l4ahnp+=HRYrHIbC7BR960 zw`6L~kGc>06Y)oCmiWER??*1$;+{8isyef;ip8FHAd7X*8$Ai_K3D0iRO@9IbbYN% zQV8b#zBg?*qoz-;BP~}%YF)-})#Q~XBsCL}lY87bsVkGo*@YDpViWtIocl{A31bKo z6ez_@#mnE2_{KFgRFFcE^Ab3i9jAX7T=O2Bfd=Cen)&G##w(s!+RzS5m=qnQ7i^ls zGGfICAHb3TyGg*0np(mb!jP&Cqd2kW{Xlm2k>oQn|0Z@N{blNs7TiDDt>2OVc#4MR z_mlU<-AW*IIxXMQEjCn$L38*IFqa#*`uMr~(=D+zx|Y>R#Lpt}7p+s)9>!E**n}ZT znyu`tNo7dz&ukHgXJ9}Clbz?J^;A^& zaV$w7SiIk)xZ*HV`z%|uHf-G-o7;g81V9hUfafu1Z`%1VrDS^hh}+tWr#C}L*T}2V ztb%*`$O$%IwA}4xVB7T?9G|nhZ1%|=*bPQt**Pp%Z^QvGPkT}wCYc4cUQ9YSudrCT z{=ha!v04XImMTk0P4rB9Ekb?)%nM7Vxz9O;nBKrbD!|_E8=MF z-(|Onjg1Jh6A&-1=`DrpJCPx4JWl2;wpn10DF+IW)r^fP+v5?k-{nTc<2#W%9vRJ9 zCsxRPU|%F=Mz}HYbMU7ci@`kqd)1rlvC{JQM?46~w~xLy)G8njYsw^X3MQ;w*NH_>|T>Vz)abcMEK+5 zrQzI)I+iX#aNbd)9n?=WIgR!wB$xMdqgU%?K# zu{D#4ZujKcR}r}1xm4u{dvk)eR3+QS@ui;G4-%Y`d=$$R0uC!XAtwhtp02Qcc`H`F z5t4rnw5*0|ul-;c)3(1o$o$UhPynr)E%8DyfF*nE0g}#O??&Glmy}^D4T+A<3D{iB ztpZbsqbByq9RM~bQ4GlAw(_&qbL8RjANvh_RP)Csgfj8D;{o37u3)naE&eYrtXrM^ z3;89~MyA7XL~Nm1kH>GwqQ-R8;Pw0EAVUFb>ssE!!@B+E5gb+r$j!DEhARET-%N|)bqm))BVb?O zTI9?H(N}38E$nE;5=Q+qw(xWWA=eCEr?$Nv+p0`$GJ48+Xx9tFT=&Ddtuor0XtDDl z1#5tFuG5nJYQc)d=+2#1ELCEF+avzZU<={EOOuX6{evkfEjH+VE5!ZE@85ngJw3hG z$k#QY8ugX3`;nLK?V=h_dbRotVgVGQciTw;m=}Gx_Mrn*EQJQW%|B7oz*Qr1`ts+j zDZcr7N{O|HCe5h!lWjPi#w$HHW}YE|oA#%FV~r0_K_JyByVQm@G;Hfu&lPEkG$Kt? zhmqP0L&@e(>0@c8HYH$PTZ$t~;-UDdk(qvb8aK=U|>g=8=W@j_?dKA2XTbQ4vPMz7zmw76d~?+^R1 zLz{tg35XfS)HH1_AHJIM0LX%CWMt&YH_xWsLTnls?eIhqfx`E~ybcTO{e&~`2ETfB z-L;*Y$peoe&m!ddq5Mer;{Di&PbK;rrza z_v__PL+(K!@-#qW8}AwGxa0@Ija=;}AGB9u2bqlVr}Pz{%-AErcBOJ*4FzH%sqFOh z#e9{+R8(F(u+KC^CK}J(lU7f@Zv*#Y`oHLA;N9U`qEn{Vh5LErJC)aRo1g!^f%?*I z_8Smqn+$7t%w`S>F0PA$QT(A{4PexyPV6Di+r`Q6gxAIsS^z)(rIEZ-v!f}~939R8 zJBQ}C&ba@00k;1A)_dZl@uTqs|D-Y-2q#{yqJu3pYT%-nep1=Yksq(8-*;RGDEIJu zuFdn{669jkupA9-s9t^>gUYpjTUmW3@oRKa$l7TH2oz9TNy%F&G644oq<}uZN6Jg5 z)&eC~JTo%`Pm;tU=Fktifwx*)Y0x_!#IJYKf=Ao8LvCYHv|l~5352}i+4fL#CV+Jh_VR%JD~kG7QkVv^eksYx#C2Vn^a-fF?^l0e5keJBe2HRN z`|xC(C;DkY{rAGNz}EWK&t}?r5GKBBJ?#(@oTU2nX$Vz%8ypEKkIJsJHrTItp`$Hb zSdCKbKo8OJMh*ysV;;TC-tOrtkRBg%SY6+GmrkMVyAvj$J9O$^);OEM#VcSMV?3HU zC~Q&XvRYNNeAqzp6h{`Y@bC+y0YJYiF7Y}~>g1iUq8w)h;(=ToaVZwjiu%+SH&WDj z?*DDUsZ^Zfo^k8j3`NJc00B3WtViZ3CT#SXFwa#38Ok}{)IM|&0RJ!s%5q|&A8dTV z3^xzrNKKBstgz)&S2PY57gr6TAAU0>3lCK_9}V`}Ih1@4fnrTNOpcuZ>RUMIFDx;! zBx!NgE#$Q*HYs0a;(x+YRUbu9v`jqCxBbJW|MZDY%~EB0>Yd(=blfiiCNZF$GOAXF zfAmmL>1y_x36u6{T`6|(o2z&x{m_0jS1~S)c>eXEjQKN)jTP7skIS>;M=$pA{naqB zhPJd5`D{x|)3&s(k76m)<{w63kRqNLc4yRl1L71Y@E}TXJvS?2MG1^!+i8=5n+)FN zL4s0OgaTINVI`fFuMA*{bzY1MQFR*JRZzNGL?|ztXcfkKIc@_ZfE4s1J9jAsi#w5* zn3c=+5GFDBT@S6;rI1O2jhol(Kxq3v9h_xYRBabVDG32-q`QO>X#|Fp?k?#Lr8@*9 zlrAYr=^Bvkk_KT2krZB}V+iT~9=`uv!^}BP?!DIC6hJ}}9yWLOPr0VN6+-w3j$)Vu zB2xT9tZ@DzjXh$A_mAE62<~HI61C;x80Ph1dF!pe6Y7b^0Hh1~4jH9%j#~8N!^3|XZuZs2-gtf} z7E8<&$+`V_APcPIO;y^CwvlAxEr%t*oA(FQ0Oe^5&;jC^HXk`>zOmBY7pYcfFq6?I zeTNqX`S})dmRhlUPo2m7pM&0_FI;*QV~{rj0GdETxt#!6C?tt+ZRx3IS*)1}z%H#9 z+yCGjo0?Xk5ILSwdiAZo{xrIki`EK_iaspxa`ltJgWnzkJqQ@AvPiZronK;bsqqvVkNC>kfT*ii6KB+rEl(JJk3QV>4UMG-Cxe?sPTbuR%u#OX!wmv1ech&d zo>;U7jL*iVKY35WJUfTsQ?S=43j*79$ddc=DCXPZbmt0Bd*PQCV+)-Mp3KAK?pqf#AsP za3W>jX&{wK2T>N@hs$*zPBKk_a25Gz1JhiIkZ2gsD6CIoc`tJVS@|OORR#ZVIYp&6Aq5TnYc?EutLirPiBd5C)hL@&9!@e+WlLMTbPe6i9BSckoqUibG&Q#*!3QsVJNuVzOl2j6L{FVmjj|KWTVWuFu{hv zPr@B-o3{}bS!(ee!cnd(7^a;%HCMwW*|;S=k~GjM4hYy6ZvGqAm8%nI}X7niELQ?7bTP{Da8OX5{1M0P;5?19;9;Zz~7aTyrEG@FS_(^a1^o z4;nwHYAnW;(*^^+y_xes>!JYl0`||g%2{ZhjmDhcYo_K5s`ztynL+9csCq=0TdA~e zq^@}E&Y6HyQ;0h!bD2hFSd{G)?*J4SGZje~t>4ZQt=;@aYs=BXn3C*ySR}ic#c6TS zDLs_Q3etJ3M?7P8a(qgff8}I7S+FV|n}g06_Lb=*v*`BT@dJZ>YuxzZwfz(mn9vcx zhtGxUrDM7}G6duQ_@YB@#nT?~bkZsYU~Ay+dhC-g4b>=vT=b!abT9XQ!V}z1hhF}7 zn2@`2lj1{!shbvWP&XN`eT zL&b!ck|xzsu(M|elNmq{OZVrC&X(X*eB{@WS%k5uu)TJ!B3aARfTd+V2hRZj>%Ye}7CcgQudRj=wze@?h zUKxc=|Fd>#Y6CR$%G~}`(b^&nfJBpx@oP z-)(u*xR{88=GKm*lYy8NKZqw`S-4Qu$*bhu5?kH)`GfXEchnW;4zoY zHmZ>Yy#&LpajXTtw}0V-YG~DsQS|Hb0b%T`t$jxGuxxGP;^L7Yv8q$NKD>d;X`Nnt zm~h#*eH!z#pqXwo?3=Y{;8`tC9ilK^z_f@S2Q8vSx{ADoAuq8dH7u@1^tn&o&=42z z#qvsE(9he5+@_F>XdFA|&Mg;o51Rw$oBPhit(&_l)2{ms_kop#0O#e1SeMg5+T!6q zmSx48WYs#vGW6Ws-2QVDJz}y4s;wR^&p@kQSH#Jv@nyp_QDiq)%p(oF26$H|JL)x+ zA9Dv_JMQfcLCt0D6>tl-)>K*l5svLETQKRk)>-n93#x}Z22uHBF^vq#v|NVce=ZVY z&KwQqD?ns{_su>?w&1m?YsqI6T3ml(U#%_m#$_epg`k$~?BGzgG@vR}U*~3Ba@dg8 zzD1Uq{t0?k0x=Z)PpeGM#Mnn1Ht6{Hh=CC!l$>vG>zA8NzOF!7$;lA*j-Q|3fZt?4 z&kN4i+@A z=iF}IQ;|fh-45KdnqP(z1Q>n7AP(<`?ZwskmI!`bB!sbM zL62F&O!Llq8Sl}U;0lz!gU)3ZOX$fxTa?A?I7e`r^6_(XtBp@=i&mb^{8n0OCp3(9 zK6d(!X!zs%xn)!&d1d8Fqx$V9C8r)vSs$O6)IYD?@}(Ktbbj4M_Pnjco+9{L$a4a= z6h#S4NhKw0@C|uw`g(vN^VI&UZlW2cP+11?%m`%8++%;P#$Yhczhv?vYu*)~oZNRH z#p=Cd;9>TLB$e98b=m@ZB14UAbb zt_KFicSA^nKKrPOCDRNfXG`THUAjT)48)|ia1{tFp*f=dlJ4%&$p+*R$g*WFJP;@T zP-Gf1b%x+=E4%1^6c#Ps%d4_gfTMf@q*$8Su{k&6dH$e^8VFI=nFCq)s68rJR$gj%B_bDq=xp4{_4Pg!WY)?-J2`O+44Axjw zzM>KLOK44dN(>#8GEjDzW5tRtdwu`ZK|0&u$dYGI%D}+zpQ*k5(UkbZ{C_KTTOD&k z{U_}TW!mUT3a4(H&f-Iu>>TW3i*4T3dcS=RmRj&h1?;#}Z{4y~m{#G8VeeMfx703o z<|3cPzYr0@R?T^x`g0Th42t1EB7d}WxVKz;Z-ts6q3ROSs|Gx^7dJ|xepH3I$J1`KI7=g`7|M5mU~&8 zym5*uB5p=OQa3ivHs`Wh`ZZ^02uVmuX_z)w^em-*=^Lp<-21&!lNT^9m-lyv|F%ym z585V>To6f2!oSy>sbZ-{VLzsH$p&NA-6z7b~X3oj~ zl8kf3e6==+ZMBU@)<m;#Sc8a0F08o#t5)4}WN z?5OYPZ3sk>U72|}xV%R_@D--}4o^2(SYF-Dxkue!RDKu_UAz{BEd-xtR1t0;A2t(E z&=IiZr6>LUJLCz_I8(S}D7vKPziEz|NY~y=C{pF;;4FOQEu$A2_CvQ&1hCt)1;+0% zo<7!8C#{@XjT&gX{yLF$_+QWz1^IO@^0bP{o)g0EFD=<#z4HGXJvqUaN_~kyJ4XgO5wmt03W&l z?eOpT_RDZ_T5@uO&GBP^c)!w+vF2|-IbX~L$!3V=H&Mq^JQbQo=Lm^HS;_KH5W1n} z!{|iCfBcFI>;O_z-gMN_{`n!5a_?*OIXBnWsm=zjZ$0+WK-_}pa-#O{^8iu+k5xD5 zJ?^|Fh2Yn%u7Q4=gS~xOk3&uU(G}uHd*Pij!=yAa7T_DPhllT`IN&+r;+`>J8<0&c zO1YtKMt@Fo(Sv#;`);vw+HtSJcbcWN@)j{YzF?_Q7zW%gvD}38ZOiq8eE2l4nep`k z0TL$Kvdus?P9Z|U$+LmNfBdcGniur!oce9(O28g&KWktioA8SkB+!Uz>`FU^(mRl^} z7K-4x*s80la%PDI`kC9-7x%j(CVL;M3fI?>!6cG3Jin$qTE(D6Q(X0FdV^>2??=y3 z@9piae^OHJ`B&LQfVxUb2C~wl3FuAfa5C0GDp~@r{^^GHOfWmzG#HwL%cid?{9dI> z{27UAI@jveWNXkWY^hqV6#ur zbLDUZIgf6}8h5VDM?jMMEn4KO%G&NnySj)CnR%tmaItV>ReqOvh%)lxtSLt)FBj&V zZ}uEo8Z@T)Wb&T;e!oc1%)HV6H|foqOkx*3fZFmf=_#2FP^YjLQp1w+qmH~2BM%BM zs0bpD`Z|wb@YV3-lDNGyzd-F``o#*zI-g_gqyIwjoO&&f3#Am^PLWrqb)RhR@9(GR z%sg)ksKz5r?FLG`Y=Zq4AH0q8;ve?KnDlRzm48mkl_($hMJoSBN##fxUWP*9f<5n5 ziDi1>U0-^BHqUQV=)#^>RkXEJR?J`iNcQq!;-`2~(CAGzG(lj{_0%6;^Jn;^J<^7DdoKRVd?ijYdcIsWcdZHYqSGSCU=Kw<|YBR zl`k^|(Z6P@kl?%#GEZNRNM2>v9Q$%E=tknm{E%Ai4vs8BagKfS(&rEsL)75B=2Dk{ zU0%3!d(GdMm(F)2)zvZ<77S<@7;^4Zvutt}i6^~(n7_LZ*}S(0F%UPc?(y)fE0Rvo zL@~fT7Fddsi<^$?>seDLhMyF#GRhXd@^dnu<*qFdt5cd;AQA~_&!)yK6aPZAe+sw# z@9H_}8&K)&J8t4M0u38U!v!zO*fxsI)uVD%(xd;hBzb;XOscj*G1FGpJ6i7$}$0$f20dtOb z%yQYQwUCg8kwQX}n{-3pkJnbp7HRg-W;`QF>Hd~a*lauY^%CIDWCPGs{IKxwv1hor*;O+Ra!p^! zyUijIE%h_G3~3K@84Q4 zY6fI%tcQoUJT2zPBM5w!Z-MHk#>Oe+HoCvpii6tM*Y~seAp}Dp0cYPSZpz~GxRL88 z3*oa!R*KfJ#A=g{r+^b*ySx4MEv|imRD;+z)tdjAdf1|?87mIqYsWWlP;bT6t#xp< zbvGmruAv}uTEzY4;xM;jdWQ}I!2x-cf^=M5E>aQ_-RZ%>-Jhr^@RWFJ*`hv<*;I6S zA$DuP6Vy68JG`!QcP;FP`g$2&Y&w;~)A&*Th*-om85O;`|< zmUXlUZHpcW5fZ>Fw97kVV`Jr}r>C7;L?5@yP9Yn2v5&vui/acceptCall/acceptCall.svg ui/rejectCall/rejectCall.svg ui/volButton/volButtonDisabled.png + img/login_logo.png diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 743d8e231..9bece81a6 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -1016,7 +1016,28 @@ background-color:#6cc865; true + + + + 25 + 15 + 100 + 100 + + + + + + + :/img/login_logo.png + + + true + + - + + + From f4cad233142b3764936c190c8989e8bc40d16b69 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:19:00 +0200 Subject: [PATCH 03/34] Fix login screen window title --- src/widget/loginscreen.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 9bece81a6..7ace820ca 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -141,7 +141,7 @@ - Form + qTox true From 6b5f0e6ad7e95573f375bdb8fe55221ef49e4bb8 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:19:13 +0200 Subject: [PATCH 04/34] Disable login screen on Android --- qtox.pro | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qtox.pro b/qtox.pro index ac8b16260..01a847d8c 100644 --- a/qtox.pro +++ b/qtox.pro @@ -359,7 +359,8 @@ contains(ENABLE_SYSTRAY_GTK_BACKEND, NO) { src/widget/callconfirmwidget.h \ src/widget/systemtrayicon.h \ src/misc/qrwidget.h \ - src/widget/systemtrayicon_private.h + src/widget/systemtrayicon_private.h \ + src/widget/loginscreen.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -419,7 +420,8 @@ contains(ENABLE_SYSTRAY_GTK_BACKEND, NO) { src/chatlog/pixmapcache.cpp \ src/offlinemsgengine.cpp \ src/misc/qrwidget.cpp \ - src/widget/genericchatroomwidget.cpp + src/widget/genericchatroomwidget.cpp \ + src/widget/loginscreen.cpp } win32 { @@ -468,8 +470,7 @@ SOURCES += \ src/video/cameradevice.cpp \ src/video/camerasource.cpp \ src/video/corevideosource.cpp \ - src/core/toxid.cpp \ - src/widget/loginscreen.cpp + src/core/toxid.cpp HEADERS += \ src/audio.h \ @@ -502,5 +503,4 @@ HEADERS += \ src/video/camerasource.h \ src/video/corevideosource.h \ src/video/videomode.h \ - src/core/toxid.h \ - src/widget/loginscreen.h + src/core/toxid.h From c6268cd604d0b3fc52b494ecb88c71d2ee48972f Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:28:16 +0200 Subject: [PATCH 05/34] Connect LoginScreen signals --- src/widget/loginscreen.cpp | 25 +++++++++++++++++++++++++ src/widget/loginscreen.h | 8 ++++++++ src/widget/loginscreen.ui | 8 ++++---- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 215d1752f..41154371e 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -6,9 +6,34 @@ LoginScreen::LoginScreen(QWidget *parent) : ui(new Ui::LoginScreen) { ui->setupUi(this); + + connect(ui->newProfilePgbtn, &QPushButton::clicked, this, &LoginScreen::onNewProfilePageClicked); + connect(ui->loginPgbtn, &QPushButton::clicked, this, &LoginScreen::onLoginPageClicked); + connect(ui->createAccountButton, &QPushButton::clicked, this, &LoginScreen::onCreateNewProfile); + connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin); } LoginScreen::~LoginScreen() { delete ui; } + +void LoginScreen::onNewProfilePageClicked() +{ + ui->stackedWidget->setCurrentIndex(0); +} + +void LoginScreen::onLoginPageClicked() +{ + ui->stackedWidget->setCurrentIndex(1); +} + +void LoginScreen::onCreateNewProfile() +{ + +} + +void LoginScreen::onLogin() +{ + +} diff --git a/src/widget/loginscreen.h b/src/widget/loginscreen.h index a0745c862..a11ad10ee 100644 --- a/src/widget/loginscreen.h +++ b/src/widget/loginscreen.h @@ -15,6 +15,14 @@ public: explicit LoginScreen(QWidget *parent = 0); ~LoginScreen(); +private slots: + // Buttons to change page + void onNewProfilePageClicked(); + void onLoginPageClicked(); + // Buttons to submit form + void onCreateNewProfile(); + void onLogin(); + private: Ui::LoginScreen *ui; }; diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 7ace820ca..6f89a245f 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -366,7 +366,7 @@ margin-bottom:45px; - + @@ -714,7 +714,7 @@ margin-bottom:5px; - + @@ -972,7 +972,7 @@ background-color:#6cc865; Qt::Horizontal - + 25 @@ -994,7 +994,7 @@ background-color:#6cc865; true - + 25 From 22c8b38fd9c5511bf8ebc46e4ef254b369866261 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:40:21 +0200 Subject: [PATCH 06/34] Update login screen ui --- src/widget/loginscreen.ui | 43 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 6f89a245f..5cb1e1464 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -143,6 +143,10 @@ qTox + + + :/img/icons/qtox.svg:/img/icons/qtox.svg + true @@ -223,7 +227,7 @@ true - 0 + 1 @@ -300,12 +304,6 @@ margin-bottom:45px; - - - 0 - 25 - - 64 @@ -323,12 +321,6 @@ margin-bottom:45px; - - - 0 - 25 - - 64 @@ -337,6 +329,19 @@ margin-bottom:45px; + + + + Confirm password: + + + Qt::AlignCenter + + + + + + @@ -648,12 +653,6 @@ margin-bottom:5px; - - - 0 - 25 - - 64 @@ -671,12 +670,6 @@ margin-bottom:5px; - - - 0 - 25 - - 64 From 68f6a5d03248b847c2e94619db155346a6f23d79 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 19:45:55 +0200 Subject: [PATCH 07/34] Use combobox for login username --- src/widget/loginscreen.ui | 60 +++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 5cb1e1464..3291b7232 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -162,6 +162,24 @@ + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + @@ -182,6 +200,24 @@ + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + @@ -202,6 +238,24 @@ + + + + 255 + 255 + 255 + + + + + + + 128 + 128 + 128 + + + @@ -652,11 +706,7 @@ margin-bottom:5px; - - - 64 - - + From 7d6167d90c0207b50875e2ceaeda398fbfe34429 Mon Sep 17 00:00:00 2001 From: tux3 Date: Wed, 3 Jun 2015 23:20:47 +0200 Subject: [PATCH 08/34] Scan profiles for login screen --- qtox.pro | 6 +++-- src/misc/settings.cpp | 22 +++++++++++++++- src/misc/settings.h | 8 +++--- src/profile.cpp | 54 ++++++++++++++++++++++++++++++++++++++ src/profile.h | 31 ++++++++++++++++++++++ src/widget/loginscreen.cpp | 10 +++++++ src/widget/loginscreen.ui | 10 +++---- 7 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 src/profile.cpp create mode 100644 src/profile.h diff --git a/qtox.pro b/qtox.pro index 01a847d8c..eb944190a 100644 --- a/qtox.pro +++ b/qtox.pro @@ -470,7 +470,8 @@ SOURCES += \ src/video/cameradevice.cpp \ src/video/camerasource.cpp \ src/video/corevideosource.cpp \ - src/core/toxid.cpp + src/core/toxid.cpp \ + src/profile.cpp HEADERS += \ src/audio.h \ @@ -503,4 +504,5 @@ HEADERS += \ src/video/camerasource.h \ src/video/corevideosource.h \ src/video/videomode.h \ - src/core/toxid.h + src/core/toxid.h \ + src/profile.h diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 3b724bb01..076967c2c 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -1246,7 +1246,6 @@ bool Settings::getCompactLayout() const void Settings::setCompactLayout(bool value) { compactLayout = value; - emit compactLayoutChanged(); } bool Settings::getGroupchatPosition() const @@ -1268,3 +1267,24 @@ void Settings::setThemeColor(const int &value) { themeColor = value; } + +void Settings::createPersonal(QString basename) +{ + QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini"; + qDebug() << "Creating new profile settings in " << path; + + QSettings ps(path, QSettings::IniFormat); + ps.beginGroup("Friends"); + ps.beginWriteArray("Friend", 0); + ps.endArray(); + ps.endGroup(); + + ps.beginGroup("Privacy"); + ps.endGroup(); +} + +bool Settings::profileExists(QString basename) +{ + QString path = getSettingsDirPath() + QDir::separator() + basename; + return QFile::exists(path+".tox") && QFile::exists(path+".ini"); +} diff --git a/src/misc/settings.h b/src/misc/settings.h index a56aa2506..807e6bed9 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -30,12 +30,15 @@ class Settings : public QObject { Q_OBJECT public: + ~Settings() = default; static Settings& getInstance(); void switchProfile(const QString& profile); QString detectProfile(); QList searchProfiles(); QString askProfiles(); - ~Settings() = default; + + void createPersonal(QString basename); ///< Write a default personnal settings file for a profile + bool profileExists(QString basename); ///< Return true if the given profile (tox AND ini) exists void executeSettingsDialog(QWidget* parent); @@ -365,12 +368,9 @@ private: int themeColor; signals: - //void dataChanged(); void dhtServerListChanged(); - void logStorageOptsChanged(); void smileyPackChanged(); void emojiFontChanged(); - void compactLayoutChanged(); }; #endif // SETTINGS_HPP diff --git a/src/profile.cpp b/src/profile.cpp new file mode 100644 index 000000000..ffeabb823 --- /dev/null +++ b/src/profile.cpp @@ -0,0 +1,54 @@ +#include "profile.h" +#include "src/misc/settings.h" +#include +#include +#include + +QVector Profile::profiles; + +Profile::Profile() +{ + +} + +Profile::~Profile() +{ + +} + +QVector Profile::getFilesByExt(QString extension) +{ + QDir dir(Settings::getInstance().getSettingsDirPath()); + QVector out; + dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); + dir.setNameFilters(QStringList("*."+extension)); + QFileInfoList list = dir.entryInfoList(); + out.reserve(list.size()); + for (QFileInfo file : list) + out += file.completeBaseName(); + return out; +} + +void Profile::scanProfiles() +{ + profiles.clear(); + QVector toxfiles = getFilesByExt("tox"), inifiles = getFilesByExt("ini"); + for (QString toxfile : toxfiles) + { + if (!inifiles.contains(toxfile)) + importProfile(toxfile); + profiles.append(toxfile); + } +} + +void Profile::importProfile(QString name) +{ + Settings& s = Settings::getInstance(); + assert(!s.profileExists(name)); + s.createPersonal(name); +} + +QVector Profile::getProfiles() +{ + return profiles; +} diff --git a/src/profile.h b/src/profile.h new file mode 100644 index 000000000..1bea521db --- /dev/null +++ b/src/profile.h @@ -0,0 +1,31 @@ +#ifndef PROFILE_H +#define PROFILE_H + +#include +#include + +/// Manages user profiles +class Profile +{ +public: + Profile(); + ~Profile(); + + /// Scan for profile, automatically importing them if needed + /// NOT thread-safe + static void scanProfiles(); + static QVector getProfiles(); + +private: + /// Lists all the files in the config dir with a given extension + /// Pass the raw extension, e.g. "jpeg" not ".jpeg". + static QVector getFilesByExt(QString extension); + /// Creates a .ini file for the given .tox profile + /// Only pass the basename, without extension + static void importProfile(QString name); + +private: + static QVector profiles; +}; + +#endif // PROFILE_H diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 41154371e..411fb2f46 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -1,5 +1,6 @@ #include "loginscreen.h" #include "ui_loginscreen.h" +#include "src/profile.h" LoginScreen::LoginScreen(QWidget *parent) : QWidget(parent), @@ -11,6 +12,15 @@ LoginScreen::LoginScreen(QWidget *parent) : connect(ui->loginPgbtn, &QPushButton::clicked, this, &LoginScreen::onLoginPageClicked); connect(ui->createAccountButton, &QPushButton::clicked, this, &LoginScreen::onCreateNewProfile); connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin); + + QVector profiles = Profile::getProfiles(); + for (QString profile : profiles) + ui->loginUsernames->addItem(profile); + + if (profiles.isEmpty()) + ui->stackedWidget->setCurrentIndex(0); + else + ui->stackedWidget->setCurrentIndex(1); } LoginScreen::~LoginScreen() diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 3291b7232..285e45c99 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -357,7 +357,7 @@ margin-bottom:45px; - + 64 @@ -374,7 +374,7 @@ margin-bottom:45px; - + 64 @@ -394,7 +394,7 @@ margin-bottom:45px; - + @@ -706,7 +706,7 @@ margin-bottom:5px; - + @@ -719,7 +719,7 @@ margin-bottom:5px; - + 64 From 032c561e62bc418f5e618edd5b1283d3ff809c54 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 01:30:17 +0200 Subject: [PATCH 09/34] Make Nexus own and start the LoginScreen And start implementing some of the required methods to make Core, LoginScreen and Nexus use Profile --- src/core/core.cpp | 19 +++--- src/core/core.h | 5 +- src/core/coreencryption.cpp | 4 +- src/main.cpp | 6 +- src/nexus.cpp | 63 +++++++++++++------- src/nexus.h | 18 ++++-- src/profile.cpp | 116 +++++++++++++++++++++++++++++++++++- src/profile.h | 22 ++++++- src/profilelocker.cpp | 13 ++++ src/profilelocker.h | 4 ++ src/widget/loginscreen.cpp | 39 ++++++++++-- src/widget/loginscreen.h | 2 + src/widget/loginscreen.ui | 2 +- 13 files changed, 258 insertions(+), 55 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index ea747635b..5d18a14cf 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -24,6 +24,7 @@ #include "src/audio.h" #include "src/profilelocker.h" #include "src/avatarbroadcaster.h" +#include "src/profile.h" #include "corefile.h" #include @@ -52,11 +53,9 @@ QThread* Core::coreThread{nullptr}; #define MAX_GROUP_MESSAGE_LEN 1024 -Core::Core(QThread *CoreThread, QString loadPath) : - tox(nullptr), toxav(nullptr), loadPath(loadPath), ready{false} +Core::Core(QThread *CoreThread, Profile& profile) : + tox(nullptr), toxav(nullptr), profile(profile), ready{false} { - qDebug() << "loading Tox from" << loadPath; - coreThread = CoreThread; Audio::getInstance(); @@ -239,7 +238,7 @@ void Core::start() { qDebug() << "Starting up"; - QByteArray savedata = loadToxSave(loadPath); + QByteArray savedata = profile.loadToxSave(); make_tox(savedata); @@ -888,7 +887,7 @@ QString Core::sanitize(QString name) QByteArray Core::loadToxSave(QString path) { QByteArray data; - loadPath = ""; // if not empty upon return, then user forgot a password and is switching + //loadPath = ""; // if not empty upon return, then user forgot a password and is switching // If we can't get a lock, then another instance is already using that profile while (!ProfileLocker::lock(QFileInfo(path).baseName())) @@ -1019,10 +1018,10 @@ void Core::switchConfiguration(const QString& _profile) emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg")); emit blockingClearContacts(); // we need this to block, but signals are required for thread safety - if (profile.isEmpty()) - loadPath = ""; - else - loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); + //if (profile.isEmpty()) + //loadPath = ""; + //else + // loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); Settings::getInstance().switchProfile(profile); HistoryKeeper::resetInstance(); diff --git a/src/core/core.h b/src/core/core.h index c1eacaf72..99296494c 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -29,6 +29,7 @@ #include "coredefines.h" #include "toxid.h" +class Profile; template class QList; class QTimer; class QString; @@ -45,7 +46,7 @@ class Core : public QObject public: enum PasswordType {ptMain = 0, ptHistory, ptCounter}; - explicit Core(QThread* coreThread, QString initialLoadPath); + explicit Core(QThread* coreThread, Profile& profile); static Core* getInstance(); ///< Returns the global widget's Core instance ~Core(); @@ -292,7 +293,7 @@ private: Tox* tox; ToxAv* toxav; QTimer *toxTimer, *fileTimer; //, *saveTimer; - QString loadPath; // meaningless after start() is called + Profile& profile; int dhtServerId; static ToxCall calls[TOXAV_MAX_CALLS]; #ifdef QTOX_FILTER_AUDIO diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index d798700d5..323119ce0 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -110,7 +110,7 @@ QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) if (!tox_pass_key_encrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], encrypted, nullptr)) { - qWarning() << "encryptData: encryption failed"; + qWarning() << "Encryption failed"; return QByteArray(); } return QByteArray(reinterpret_cast(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH); @@ -126,7 +126,7 @@ QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) if (!tox_pass_key_decrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], decrypted, nullptr)) { - qWarning() << "decryptData: decryption failed"; + qWarning() << "Decryption failed"; return QByteArray(); } return QByteArray(reinterpret_cast(decrypted), sz); diff --git a/src/main.cpp b/src/main.cpp index 4c24ff7f7..1be0ef81c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ See the COPYING file for more details. */ +#include "toxme.h" #include "widget/widget.h" #include "misc/settings.h" #include "src/nexus.h" @@ -20,6 +21,7 @@ #include "src/widget/toxsave.h" #include "src/autoupdate.h" #include "src/profilelocker.h" +#include "src/widget/loginscreen.h" #include #include #include @@ -31,9 +33,6 @@ #include #include - -#include "toxme.h" - #include #define EXIT_UPDATE_MACX 218 //We track our state using unique exit codes when debugging @@ -292,7 +291,6 @@ int main(int argc, char *argv[]) Nexus::getInstance().start(); // Run - a.setQuitOnLastWindowClosed(false); int errorcode = a.exec(); #ifdef LOG_TO_FILE diff --git a/src/nexus.cpp b/src/nexus.cpp index bb31d627d..b4f86eaa6 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -1,12 +1,16 @@ #include "nexus.h" +#include "profile.h" #include "src/core/core.h" #include "misc/settings.h" #include "video/camerasource.h" #include "widget/gui.h" +#include "widget/loginscreen.h" #include #include #include #include +#include +#include #ifdef Q_OS_ANDROID #include @@ -18,18 +22,14 @@ static Nexus* nexus{nullptr}; Nexus::Nexus(QObject *parent) : QObject(parent), - core{nullptr}, - coreThread{nullptr}, + profile{nullptr}, widget{nullptr}, - androidgui{nullptr}, - started{false} + androidgui{nullptr} { } Nexus::~Nexus() { - delete core; - delete coreThread; #ifdef Q_OS_ANDROID delete androidgui; #else @@ -39,9 +39,6 @@ Nexus::~Nexus() void Nexus::start() { - if (started) - return; - qDebug() << "Starting up"; // Setup the environment @@ -59,19 +56,30 @@ void Nexus::start() qRegisterMetaType("Core::PasswordType"); qRegisterMetaType>("std::shared_ptr"); + // Create and show login screen + loginScreen = new LoginScreen(); + showLogin(); +} + +void Nexus::showLogin() +{ + ((QApplication*)qApp)->setQuitOnLastWindowClosed(true); + loginScreen->reset(); + loginScreen->show(); +} + +void Nexus::showMainGUI() +{ + assert(profile); + + ((QApplication*)qApp)->setQuitOnLastWindowClosed(false); + loginScreen->close(); + // Create GUI #ifndef Q_OS_ANDROID widget = Widget::getInstance(); #endif - // Create Core - QString profilePath = Settings::getInstance().detectProfile(); - coreThread = new QThread(this); - coreThread->setObjectName("qTox Core"); - core = new Core(coreThread, profilePath); - core->moveToThread(coreThread); - connect(coreThread, &QThread::started, core, &Core::start); - // Start GUI #ifdef Q_OS_ANDROID androidgui = new AndroidGUI; @@ -88,6 +96,7 @@ void Nexus::start() GUI::setEnabled(false); // Connections + Core* core = profile->getCore(); #ifdef Q_OS_ANDROID connect(core, &Core::connected, androidgui, &AndroidGUI::onConnected); connect(core, &Core::disconnected, androidgui, &AndroidGUI::onDisconnected); @@ -138,10 +147,7 @@ void Nexus::start() connect(widget, &Widget::changeProfile, core, &Core::switchConfiguration); #endif - // Start Core - coreThread->start(); - - started = true; + profile->startCore(); } Nexus& Nexus::getInstance() @@ -160,7 +166,20 @@ void Nexus::destroyInstance() Core* Nexus::getCore() { - return getInstance().core; + Nexus& nexus = getInstance(); + if (!nexus.profile) + return nullptr; + return nexus.profile->getCore(); +} + +Profile* Nexus::getProfile() +{ + return getInstance().profile; +} + +void Nexus::setProfile(Profile* profile) +{ + getInstance().profile = profile; } AndroidGUI* Nexus::getAndroidGUI() diff --git a/src/nexus.h b/src/nexus.h index a411b1f22..835ebe8b3 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -3,10 +3,11 @@ #include -class QThread; -class Core; class Widget; class AndroidGUI; +class Profile; +class LoginScreen; +class Core; /// This class is in charge of connecting various systems together /// and forwarding signals appropriately to the right objects @@ -15,11 +16,17 @@ class Nexus : public QObject { Q_OBJECT public: - void start(); ///< Will initialise the systems (GUI, Core, ...) + void start(); ///< Sets up invariants and calls showLogin + void showLogin(); ///< Shows the login screen + /// Hides the login screen and shows the GUI for the given profile. + /// Will delete the current GUI, if it exists. + void showMainGUI(); static Nexus& getInstance(); static void destroyInstance(); static Core* getCore(); ///< Will return 0 if not started + static Profile* getProfile(); ///< Will return 0 if not started + static void setProfile(Profile* profile); ///< Delete the current profile, if any, and replaces it static AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started static Widget* getDesktopGUI(); ///< Will return 0 if not started static QString getSupportedImageFilter(); @@ -30,11 +37,10 @@ private: ~Nexus(); private: - Core* core; - QThread* coreThread; + Profile* profile; Widget* widget; AndroidGUI* androidgui; - bool started; + LoginScreen* loginScreen; }; #endif // NEXUS_H diff --git a/src/profile.cpp b/src/profile.cpp index ffeabb823..1b2610964 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -1,19 +1,48 @@ #include "profile.h" +#include "profilelocker.h" #include "src/misc/settings.h" +#include "src/core/core.h" #include #include #include +#include +#include +#include QVector Profile::profiles; -Profile::Profile() +Profile::Profile(QString name, QString password) + : name{name}, password{password} { + coreThread = new QThread(); + coreThread->setObjectName("qTox Core"); + core = new Core(coreThread, *this); + core->moveToThread(coreThread); + QObject::connect(coreThread, &QThread::started, core, &Core::start); +} + +Profile* Profile::loadProfile(QString name, QString password) +{ + if (ProfileLocker::hasLock()) + { + + } + if (!ProfileLocker::lock(name)) + { + qWarning() << "Failed to lock profile "< Profile::getFilesByExt(QString extension) @@ -52,3 +81,86 @@ QVector Profile::getProfiles() { return profiles; } + +Core* Profile::getCore() +{ + return core; +} + +void Profile::startCore() +{ + coreThread->start(); +} + +QByteArray Profile::loadToxSave() +{ + QByteArray data; + QString path = Settings::getSettingsDirPath() + QDir::separator() + name; + + QFile saveFile(path); + qint64 fileSize; + qDebug() << "Loading tox save "<(data.data()), salt); + core->setPassword(password, Core::ptMain, salt); + + data = core->decryptData(data, Core::ptMain); + if (data.isEmpty()) + qCritical() << "Failed to decrypt the tox save file"; + } + else + { + if (!password.isEmpty()) + qWarning() << "We have a password, but the tox save file is not encrypted"; + } + +fail: + saveFile.close(); + return data; +} + +bool Profile::isProfileEncrypted(QString name) +{ + uint8_t data[encryptHeaderSize] = {0}; + QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox"; + QFile saveFile(path); + if (!saveFile.open(QIODevice::ReadOnly)) + { + qWarning() << "Couldn't open tox save "< #include +#include + +class Core; +class QThread; /// Manages user profiles class Profile { public: - Profile(); + /// Locks and loads an existing profile and create the associate Core* instance + /// Returns a nullptr on error, for example if the profile is already in use + Profile* loadProfile(QString name, QString password); ~Profile(); + Core* getCore(); + void startCore(); ///< Starts the Core thread + QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted + /// Scan for profile, automatically importing them if needed /// NOT thread-safe static void scanProfiles(); static QVector getProfiles(); + /// Checks whether a profile is encrypted. Return false on error. + static bool isProfileEncrypted(QString name); + private: + Profile(QString name, QString password); /// Lists all the files in the config dir with a given extension /// Pass the raw extension, e.g. "jpeg" not ".jpeg". static QVector getFilesByExt(QString extension); @@ -25,7 +39,13 @@ private: static void importProfile(QString name); private: + Core* core; + QThread* coreThread; + QString name, password; static QVector profiles; + /// How much data we need to read to check if the file is encrypted + /// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined + static constexpr int encryptHeaderSize = 8; }; #endif // PROFILE_H diff --git a/src/profilelocker.cpp b/src/profilelocker.cpp index e7e758062..93d18c1d8 100644 --- a/src/profilelocker.cpp +++ b/src/profilelocker.cpp @@ -97,3 +97,16 @@ void ProfileLocker::deathByBrokenLock() qCritical() << "Lock is *BROKEN*, exiting immediately"; abort(); } + +bool ProfileLocker::hasLock() +{ + return lockfile.operator bool(); +} + +QString ProfileLocker::getCurLockName() +{ + if (lockfile) + return curLockName; + else + return QString(); +} diff --git a/src/profilelocker.h b/src/profilelocker.h index be59f043f..a3cc071f5 100644 --- a/src/profilelocker.h +++ b/src/profilelocker.h @@ -24,6 +24,10 @@ public: static bool lock(QString profile); /// Releases the lock on the current profile static void unlock(); + /// Returns true if we're currently holding a lock + static bool hasLock(); + /// Return the name of the currently loaded profile, a null string if there is none + static QString getCurLockName(); /// Releases all locks on all profiles /// DO NOT call unless all we're the only qTox instance /// and we don't hold any lock yet. diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 411fb2f46..1ff396fa6 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -12,7 +12,24 @@ LoginScreen::LoginScreen(QWidget *parent) : connect(ui->loginPgbtn, &QPushButton::clicked, this, &LoginScreen::onLoginPageClicked); connect(ui->createAccountButton, &QPushButton::clicked, this, &LoginScreen::onCreateNewProfile); connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin); + connect(ui->loginUsernames, &QComboBox::currentTextChanged, this, &LoginScreen::onLoginUsernameSelected); + reset(); +} + +LoginScreen::~LoginScreen() +{ + delete ui; +} + +void LoginScreen::reset() +{ + ui->newUsername->clear(); + ui->newPass->clear(); + ui->loginPassword->clear(); + + ui->loginUsernames->clear(); + Profile::scanProfiles(); QVector profiles = Profile::getProfiles(); for (QString profile : profiles) ui->loginUsernames->addItem(profile); @@ -23,11 +40,6 @@ LoginScreen::LoginScreen(QWidget *parent) : ui->stackedWidget->setCurrentIndex(1); } -LoginScreen::~LoginScreen() -{ - delete ui; -} - void LoginScreen::onNewProfilePageClicked() { ui->stackedWidget->setCurrentIndex(0); @@ -43,6 +55,23 @@ void LoginScreen::onCreateNewProfile() } +void LoginScreen::onLoginUsernameSelected(const QString &name) +{ + if (name.isEmpty()) + return; + + if (Profile::isProfileEncrypted(name)) + { + ui->loginPasswordLabel->show(); + ui->loginPassword->show(); + } + else + { + ui->loginPasswordLabel->hide(); + ui->loginPassword->hide(); + } +} + void LoginScreen::onLogin() { diff --git a/src/widget/loginscreen.h b/src/widget/loginscreen.h index a11ad10ee..a08be048a 100644 --- a/src/widget/loginscreen.h +++ b/src/widget/loginscreen.h @@ -14,8 +14,10 @@ class LoginScreen : public QWidget public: explicit LoginScreen(QWidget *parent = 0); ~LoginScreen(); + void reset(); ///< Resets the UI, clears all fields private slots: + void onLoginUsernameSelected(const QString& name); // Buttons to change page void onNewProfilePageClicked(); void onLoginPageClicked(); diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 285e45c99..57de30668 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -709,7 +709,7 @@ margin-bottom:5px; - + Password: From bf29d4a6d53e1c29cc3d139723382db995e05532 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 02:10:06 +0200 Subject: [PATCH 10/34] Allow to load/create from login screen Saving is not implemented, so bad things will happen if you let qTox save those profiles --- src/misc/settings.cpp | 6 ---- src/misc/settings.h | 1 - src/nexus.cpp | 4 +++ src/profile.cpp | 50 +++++++++++++++++++++++++++----- src/profile.h | 13 ++++++--- src/widget/loginscreen.cpp | 58 +++++++++++++++++++++++++++++++++++++- 6 files changed, 113 insertions(+), 19 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 076967c2c..1e93431d6 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -1282,9 +1282,3 @@ void Settings::createPersonal(QString basename) ps.beginGroup("Privacy"); ps.endGroup(); } - -bool Settings::profileExists(QString basename) -{ - QString path = getSettingsDirPath() + QDir::separator() + basename; - return QFile::exists(path+".tox") && QFile::exists(path+".ini"); -} diff --git a/src/misc/settings.h b/src/misc/settings.h index 807e6bed9..5ae8e2132 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -38,7 +38,6 @@ public: QString askProfiles(); void createPersonal(QString basename); ///< Write a default personnal settings file for a profile - bool profileExists(QString basename); ///< Return true if the given profile (tox AND ini) exists void executeSettingsDialog(QWidget* parent); diff --git a/src/nexus.cpp b/src/nexus.cpp index b4f86eaa6..c712d2e1c 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -16,6 +16,7 @@ #include #else #include +#include #endif static Nexus* nexus{nullptr}; @@ -65,6 +66,9 @@ void Nexus::showLogin() { ((QApplication*)qApp)->setQuitOnLastWindowClosed(true); loginScreen->reset(); +#ifndef Q_OS_ANDROID + loginScreen->move(QApplication::desktop()->screen()->rect().center() - loginScreen->rect().center()); +#endif loginScreen->show(); } diff --git a/src/profile.cpp b/src/profile.cpp index 1b2610964..d0bfb0ee6 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -11,8 +11,8 @@ QVector Profile::profiles; -Profile::Profile(QString name, QString password) - : name{name}, password{password} +Profile::Profile(QString name, QString password, bool isNewProfile) + : name{name}, password{password}, isNewProfile{isNewProfile} { coreThread = new QThread(); coreThread->setObjectName("qTox Core"); @@ -25,7 +25,31 @@ Profile* Profile::loadProfile(QString name, QString password) { if (ProfileLocker::hasLock()) { + qCritical() << "Tried to load profile "< Profile::getProfiles() @@ -95,8 +119,14 @@ void Profile::startCore() QByteArray Profile::loadToxSave() { QByteArray data; - QString path = Settings::getSettingsDirPath() + QDir::separator() + name; + if (isNewProfile) + { + qDebug() << "Loading empty data for new profile"; + return data; + } + + QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox"; QFile saveFile(path); qint64 fileSize; qDebug() << "Loading tox save "< getProfiles(); - /// Checks whether a profile is encrypted. Return false on error. - static bool isProfileEncrypted(QString name); + static bool profileExists(QString name); + static bool isProfileEncrypted(QString name); ///< Returns false on error. private: - Profile(QString name, QString password); + Profile(QString name, QString password, bool isNewProfile); /// Lists all the files in the config dir with a given extension /// Pass the raw extension, e.g. "jpeg" not ".jpeg". static QVector getFilesByExt(QString extension); @@ -43,6 +47,7 @@ private: QThread* coreThread; QString name, password; static QVector profiles; + bool isNewProfile; /// How much data we need to read to check if the file is encrypted /// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined static constexpr int encryptHeaderSize = 8; diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 1ff396fa6..784bf5004 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -1,6 +1,9 @@ #include "loginscreen.h" #include "ui_loginscreen.h" #include "src/profile.h" +#include "src/profilelocker.h" +#include "src/nexus.h" +#include LoginScreen::LoginScreen(QWidget *parent) : QWidget(parent), @@ -51,8 +54,40 @@ void LoginScreen::onLoginPageClicked() } void LoginScreen::onCreateNewProfile() -{ +{ + QString name = ui->newUsername->text(); + QString pass = ui->newPass->text(); + + if (name.isEmpty()) + { + QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The username must not be empty.")); + return; + } + if (ui->newPassConfirm->text() != pass) + { + QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The passwords are different.")); + return; + } + + if (Profile::profileExists(name)) + { + QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("This profile already exists.")); + return; + } + + Profile* profile = Profile::createProfile(name, pass); + if (!profile) + { + // Unknown error + QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("Couldn't create a new profile.")); + return; + } + + Nexus& nexus = Nexus::getInstance(); + + nexus.setProfile(profile); + nexus.showMainGUI(); } void LoginScreen::onLoginUsernameSelected(const QString &name) @@ -60,6 +95,7 @@ void LoginScreen::onLoginUsernameSelected(const QString &name) if (name.isEmpty()) return; + ui->loginPassword->clear(); if (Profile::isProfileEncrypted(name)) { ui->loginPasswordLabel->show(); @@ -74,5 +110,25 @@ void LoginScreen::onLoginUsernameSelected(const QString &name) void LoginScreen::onLogin() { + QString name = ui->loginUsernames->currentText(); + QString pass = ui->loginPassword->text(); + + if (!ProfileLocker::isLockable(name)) + { + QMessageBox::critical(this, tr("Couldn't load this profile"), tr("This profile is already in use.")); + return; + } + + Profile* profile = Profile::loadProfile(name, pass); + if (!profile) + { + // Unknown error + QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Couldn't load this profile.")); + return; + } + + Nexus& nexus = Nexus::getInstance(); + nexus.setProfile(profile); + nexus.showMainGUI(); } From 840fd7dc40a70f3bcc0fba435699c2e9ebcc7042 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 02:43:07 +0200 Subject: [PATCH 11/34] Cleanly create new profiles --- src/core/core.cpp | 97 ++++++++----------------------------- src/core/core.h | 6 +-- src/core/coreencryption.cpp | 63 +----------------------- src/profile.cpp | 20 +++++--- src/profile.h | 7 ++- src/widget/loginscreen.cpp | 7 +++ 6 files changed, 47 insertions(+), 153 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 5d18a14cf..2cba81039 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -126,7 +126,7 @@ Core* Core::getInstance() return Nexus::getCore(); } -void Core::make_tox(QByteArray savedata) +void Core::makeTox(QByteArray savedata) { // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options. bool enableIPv6 = Settings::getInstance().getEnableIPv6(); @@ -236,19 +236,25 @@ void Core::make_tox(QByteArray savedata) void Core::start() { - qDebug() << "Starting up"; - - QByteArray savedata = profile.loadToxSave(); - - make_tox(savedata); - - // Do we need to create a new save & profile? - if (savedata.isNull()) + bool isNewProfile = profile.isNewProfile(); + if (isNewProfile) { - qDebug() << "Save file not found, creating a new profile"; + qDebug() << "Creating a new profile"; + makeTox(QByteArray()); Settings::getInstance().load(); setStatusMessage(tr("Toxing on qTox")); - setUsername(tr("qTox User")); + setUsername(profile.getName()); + } + else + { + qDebug() << "Loading user profile"; + QByteArray savedata = profile.loadToxSave(); + if (savedata.isEmpty()) + { + emit failedToStart(); + return; + } + makeTox(savedata); } qsrand(time(nullptr)); @@ -323,7 +329,7 @@ void Core::start() } else { - qDebug() << "Error loading self avatar"; + qDebug() << "Self avatar not found"; } ready = true; @@ -331,7 +337,7 @@ void Core::start() // If we created a new profile earlier, // now that we're ready save it and ONLY THEN broadcast the new ID. // This is useful for e.g. the profileForm that searches for saves. - if (savedata.isNull()) + if (isNewProfile) { saveConfiguration(); emit idSet(getSelfId().toString()); @@ -884,71 +890,6 @@ QString Core::sanitize(QString name) return name; } -QByteArray Core::loadToxSave(QString path) -{ - QByteArray data; - //loadPath = ""; // if not empty upon return, then user forgot a password and is switching - - // If we can't get a lock, then another instance is already using that profile - while (!ProfileLocker::lock(QFileInfo(path).baseName())) - { - qWarning() << "Profile "< 0) - { - data = configurationFile.readAll(); - if (tox_is_data_encrypted((uint8_t*)data.data())) - { - if (!loadEncryptedSave(data)) - { - configurationFile.close(); - - QString profile; - do { - profile = Settings::getInstance().askProfiles(); - } while (profile.isEmpty()); - - if (!profile.isEmpty()) - { - Settings::getInstance().switchProfile(profile); - HistoryKeeper::resetInstance(); - return loadToxSave(QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT)); - } - return QByteArray(); - } - } - } - configurationFile.close(); - - return data; -} - void Core::saveConfiguration() { if (QThread::currentThread() != coreThread) diff --git a/src/core/core.h b/src/core/core.h index 99296494c..6b8dcd0d6 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -147,7 +147,7 @@ public slots: static bool isGroupCallMicEnabled(int groupId); static bool isGroupCallVolEnabled(int groupId); - void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr); + void setPassword(const QString &password, PasswordType passtype, uint8_t* salt = nullptr); void useOtherPassword(PasswordType type); void clearPassword(PasswordType passtype); QByteArray encryptData(const QByteArray& data, PasswordType passtype); @@ -279,10 +279,8 @@ private: bool checkConnection(); - QByteArray loadToxSave(QString path); - bool loadEncryptedSave(QByteArray& data); void checkEncryptedHistory(); - void make_tox(QByteArray savedata); + void makeTox(QByteArray savedata); void loadFriends(); void checkLastOnline(uint32_t friendId); diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index 323119ce0..5a7cff718 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -31,7 +31,7 @@ #include #include -void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) +void Core::setPassword(const QString& password, PasswordType passtype, uint8_t* salt) { clearPassword(passtype); if (password.isEmpty()) @@ -44,8 +44,6 @@ void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype], nullptr); else tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype], nullptr); - - password.clear(); } void Core::useOtherPassword(PasswordType type) @@ -163,65 +161,6 @@ QByteArray Core::getSaltFromFile(QString filename) return res; } -bool Core::loadEncryptedSave(QByteArray& data) -{ - if (!Settings::getInstance().getEncryptTox()) - GUI::showWarning(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); - - size_t fileSize = data.size(); - int error = -1; - QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile())); - QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); - QString dialogtxt; - - if (pwsaltedkeys[ptMain]) // password set, try it - { - QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0); - if (tox_pass_key_decrypt((uint8_t*)data.data(), fileSize, pwsaltedkeys[ptMain], - (uint8_t*)newData.data(), nullptr)) - { - data = newData; - Settings::getInstance().setEncryptTox(true); - return true; - } - - dialogtxt = tr("The profile password failed. Please try another?", "used only when pw set before load() doesn't work"); - } - else - { - dialogtxt = a; - } - - uint8_t salt[TOX_PASS_SALT_LENGTH]; - tox_get_salt(reinterpret_cast(data.data()), salt); - - do - { - QString pw = GUI::passwordDialog(tr("Change profile"), dialogtxt); - - if (pw.isEmpty()) - { - clearPassword(ptMain); - return false; - } - else - { - setPassword(pw, ptMain, salt); - } - - QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0); - error = !tox_pass_key_decrypt((uint8_t*)data.data(), data.size(), pwsaltedkeys[ptMain], - (uint8_t*)newData.data(), nullptr); - if (!error) - data = newData; - - dialogtxt = a + "\n" + b; - } while (error != 0); - - Settings::getInstance().setEncryptTox(true); - return true; -} - void Core::checkEncryptedHistory() { QString path = HistoryKeeper::getHistoryPath(); diff --git a/src/profile.cpp b/src/profile.cpp index d0bfb0ee6..db01ae0a3 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -12,7 +12,7 @@ QVector Profile::profiles; Profile::Profile(QString name, QString password, bool isNewProfile) - : name{name}, password{password}, isNewProfile{isNewProfile} + : name{name}, password{password}, newProfile{isNewProfile} { coreThread = new QThread(); coreThread->setObjectName("qTox Core"); @@ -111,21 +111,26 @@ Core* Profile::getCore() return core; } +QString Profile::getName() +{ + return name; +} + void Profile::startCore() { coreThread->start(); } +bool Profile::isNewProfile() +{ + return newProfile; +} + QByteArray Profile::loadToxSave() { + /// TODO: Cache the data, invalidate it only when we save QByteArray data; - if (isNewProfile) - { - qDebug() << "Loading empty data for new profile"; - return data; - } - QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox"; QFile saveFile(path); qint64 fileSize; @@ -156,6 +161,7 @@ QByteArray Profile::loadToxSave() if (password.isEmpty()) { qCritical() << "The tox save file is encrypted, but we don't have a password!"; + data.clear(); goto fail; } diff --git a/src/profile.h b/src/profile.h index e3efe9734..f131edb26 100644 --- a/src/profile.h +++ b/src/profile.h @@ -22,7 +22,10 @@ public: ~Profile(); Core* getCore(); + QString getName(); + void startCore(); ///< Starts the Core thread + bool isNewProfile(); QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted /// Scan for profile, automatically importing them if needed @@ -34,7 +37,7 @@ public: static bool isProfileEncrypted(QString name); ///< Returns false on error. private: - Profile(QString name, QString password, bool isNewProfile); + Profile(QString name, QString password, bool newProfile); /// Lists all the files in the config dir with a given extension /// Pass the raw extension, e.g. "jpeg" not ".jpeg". static QVector getFilesByExt(QString extension); @@ -47,7 +50,7 @@ private: QThread* coreThread; QString name, password; static QVector profiles; - bool isNewProfile; + bool newProfile; ///< True if this is a newly created profile, with no .tox save file yet. /// How much data we need to read to check if the file is encrypted /// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined static constexpr int encryptHeaderSize = 8; diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 784bf5004..49b627d5d 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -126,6 +126,13 @@ void LoginScreen::onLogin() QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Couldn't load this profile.")); return; } + if (profile->loadToxSave().isEmpty()) + { + // Unknown error + QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Wrong password.")); + delete profile; + return; + } Nexus& nexus = Nexus::getInstance(); From 933dce485db4744a7fae1619fc6aac7bcc442f7b Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 10:56:48 +0200 Subject: [PATCH 12/34] Add tox save loading/saving to Profile --- src/profile.cpp | 32 +++++++++++++++++++++++++++++++- src/profile.h | 1 + src/widget/loginscreen.cpp | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/profile.cpp b/src/profile.cpp index db01ae0a3..4ead3976d 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -5,10 +5,10 @@ #include #include #include +#include #include #include #include - QVector Profile::profiles; Profile::Profile(QString name, QString password, bool isNewProfile) @@ -184,6 +184,36 @@ fail: return data; } +void Profile::saveToxSave(QByteArray data) +{ + ProfileLocker::assertLock(); + assert(ProfileLocker::getCurLockName() == name); + + QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox"; + QSaveFile saveFile(path); + if (!saveFile.open(QIODevice::WriteOnly)) + { + qCritical() << "Tox save file " << path << " couldn't be opened"; + return; + } + + if (!password.isEmpty()) + { + core->setPassword(password, Core::ptMain); + data = core->encryptData(data, Core::ptMain); + if (data.isEmpty()) + { + qCritical() << "Failed to encrypt, can't save!"; + saveFile.cancelWriting(); + return; + } + } + + saveFile.write(data); + saveFile.commit(); + newProfile = false; +} + bool Profile::profileExists(QString name) { QString path = Settings::getSettingsDirPath() + QDir::separator() + name; diff --git a/src/profile.h b/src/profile.h index f131edb26..81c426514 100644 --- a/src/profile.h +++ b/src/profile.h @@ -27,6 +27,7 @@ public: void startCore(); ///< Starts the Core thread bool isNewProfile(); QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted + void saveToxSave(QByteArray data); ///< Saves the profile's .tox save, encrypted if needed /// Scan for profile, automatically importing them if needed /// NOT thread-safe diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 49b627d5d..d65488455 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -14,8 +14,12 @@ LoginScreen::LoginScreen(QWidget *parent) : connect(ui->newProfilePgbtn, &QPushButton::clicked, this, &LoginScreen::onNewProfilePageClicked); connect(ui->loginPgbtn, &QPushButton::clicked, this, &LoginScreen::onLoginPageClicked); connect(ui->createAccountButton, &QPushButton::clicked, this, &LoginScreen::onCreateNewProfile); + connect(ui->newUsername, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile); + connect(ui->newPass, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile); + connect(ui->newPassConfirm, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile); connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin); connect(ui->loginUsernames, &QComboBox::currentTextChanged, this, &LoginScreen::onLoginUsernameSelected); + connect(ui->loginPassword, &QLineEdit::returnPressed, this, &LoginScreen::onLogin); reset(); } From abf65a5060031751015536ece3dafe20694a1ae2 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 11:37:54 +0200 Subject: [PATCH 13/34] Remove deprecated profile management functions --- src/core/core.cpp | 99 +++--------------- src/core/core.h | 14 +-- src/core/coreencryption.cpp | 106 ------------------- src/misc/settings.cpp | 8 ++ src/misc/settings.h | 1 + src/nexus.cpp | 1 - src/profile.cpp | 5 + src/profile.h | 3 +- src/widget/form/profileform.cpp | 124 +++-------------------- src/widget/form/profileform.h | 16 +-- src/widget/form/profileform.ui | 86 ++++++---------- src/widget/form/settings/generalform.cpp | 4 +- src/widget/form/settings/privacyform.cpp | 2 +- src/widget/widget.h | 1 - 14 files changed, 81 insertions(+), 389 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 2cba81039..27883c19d 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -103,7 +103,7 @@ Core::~Core() { qDebug() << "Deleting Core"; - saveConfiguration(); + profile.saveToxSave(); toxTimer->stop(); coreThread->exit(0); while (coreThread->isRunning()) @@ -339,7 +339,7 @@ void Core::start() // This is useful for e.g. the profileForm that searches for saves. if (isNewProfile) { - saveConfiguration(); + profile.saveToxSave(); emit idSet(getSelfId().toString()); } @@ -582,7 +582,7 @@ void Core::acceptFriendRequest(const QString& userId) } else { - saveConfiguration(); + profile.saveToxSave(); emit friendAdded(friendId, userId); } } @@ -627,7 +627,7 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag emit friendAdded(friendId, userId); } } - saveConfiguration(); + profile.saveToxSave(); } int Core::sendMessage(uint32_t friendId, const QString& message) @@ -742,7 +742,7 @@ void Core::removeFriend(uint32_t friendId, bool fake) } else { - saveConfiguration(); + profile.saveToxSave(); emit friendRemoved(friendId); } } @@ -780,7 +780,7 @@ void Core::setUsername(const QString& username) else { emit usernameSet(username); - saveConfiguration(); + profile.saveToxSave(); } } @@ -844,7 +844,7 @@ void Core::setStatusMessage(const QString& message) } else { - saveConfiguration(); + profile.saveToxSave(); emit statusMessageSet(message); } } @@ -869,7 +869,7 @@ void Core::setStatus(Status status) } tox_self_set_status(tox, userstatus); - saveConfiguration(); + profile.saveToxSave(); emit statusSet(status); } @@ -890,84 +890,13 @@ QString Core::sanitize(QString name) return name; } -void Core::saveConfiguration() +QByteArray Core::getToxSaveData() { - if (QThread::currentThread() != coreThread) - return (void) QMetaObject::invokeMethod(this, "saveConfiguration"); - - if (!isReady()) - return; - - ProfileLocker::assertLock(); - - QString dir = Settings::getSettingsDirPath(); - QDir directory(dir); - if (!directory.exists() && !directory.mkpath(directory.absolutePath())) - { - qCritical() << "Error while creating directory " << dir; - return; - } - - QString profile = Settings::getInstance().getCurrentProfile(); - - if (profile == "") - { // no profile active; this should only happen on startup, if at all - profile = sanitize(getUsername()); - - if (profile == "") // happens on creation of a new Tox ID - profile = getIDString(); - - Settings::getInstance().switchProfile(profile); - } - - QString path = directory.filePath(profile + TOX_EXT); - - saveConfiguration(path); -} - -void Core::switchConfiguration(const QString& _profile) -{ - QString profile = QFileInfo(_profile).baseName(); - // If we can't get a lock, then another instance is already using that profile - while (!profile.isEmpty() && !ProfileLocker::lock(profile)) - { - qWarning() << "Profile "<stop(); - deadifyTox(); - - emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg")); - emit blockingClearContacts(); // we need this to block, but signals are required for thread safety - - //if (profile.isEmpty()) - //loadPath = ""; - //else - // loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); - - Settings::getInstance().switchProfile(profile); - HistoryKeeper::resetInstance(); - - start(); + uint32_t fileSize = tox_get_savedata_size(tox); + QByteArray data; + data.resize(fileSize); + tox_get_savedata(tox, (uint8_t*)data.data()); + return data; } void Core::loadFriends() diff --git a/src/core/core.h b/src/core/core.h index 6b8dcd0d6..2d22867d9 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -92,9 +92,7 @@ public slots: void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer void bootstrapDht(); ///< Connects us to the Tox network - void saveConfiguration(); - void saveConfiguration(const QString& path); - void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core + QByteArray getToxSaveData(); ///< Returns the unencrypted tox save data void acceptFriendRequest(const QString& userId); void requestFriendship(const QString& friendAddress, const QString& message); @@ -303,16 +301,6 @@ private: TOX_PASS_KEY* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw" - // Hack for reloading current profile if switching to an encrypted one fails. - // Testing the passwords before killing the current profile is perfectly doable, - // however it would require major refactoring; - // the Core class as a whole also requires major refactoring (especially to support multiple IDs at once), - // so I'm punting on this until then, when it would get fixed anyways - TOX_PASS_KEY* backupkeys[PasswordType::ptCounter] = {nullptr}; - QString* backupProfile = nullptr; - void saveCurrentInformation(); - QString loadOldInformation(); - static const int videobufsize; static uint8_t* videobuf; diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index 5a7cff718..2a1a7a0ec 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -62,43 +62,6 @@ void Core::clearPassword(PasswordType passtype) pwsaltedkeys[passtype] = nullptr; } -// part of a hack, see core.h -void Core::saveCurrentInformation() -{ - if (pwsaltedkeys[ptMain]) - { - backupkeys[ptMain] = new TOX_PASS_KEY; - std::copy(pwsaltedkeys[ptMain], pwsaltedkeys[ptMain]+1, backupkeys[ptMain]); - } - if (pwsaltedkeys[ptHistory]) - { - backupkeys[ptHistory] = new TOX_PASS_KEY; - std::copy(pwsaltedkeys[ptHistory], pwsaltedkeys[ptHistory]+1, backupkeys[ptHistory]); - } - backupProfile = new QString(Settings::getInstance().getCurrentProfile()); -} - -QString Core::loadOldInformation() -{ - QString out; - if (backupProfile) - { - out = *backupProfile; - delete backupProfile; - backupProfile = nullptr; - } - backupProfile = nullptr; - clearPassword(ptMain); - clearPassword(ptHistory); - // we can just copy the pointer, as long as we null out backupkeys - // (if backupkeys was null anyways, then this is a null-op) - pwsaltedkeys[ptMain] = backupkeys[ptMain]; - pwsaltedkeys[ptHistory] = backupkeys[ptHistory]; - backupkeys[ptMain] = nullptr; - backupkeys[ptHistory] = nullptr; - return out; -} - QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) { if (!pwsaltedkeys[passtype]) @@ -228,72 +191,3 @@ void Core::checkEncryptedHistory() dialogtxt = a + "\n" + c + "\n" + b; } while (error); } - -void Core::saveConfiguration(const QString& path) -{ - if (QThread::currentThread() != coreThread) - return (void) QMetaObject::invokeMethod(this, "saveConfiguration", Q_ARG(const QString&, path)); - - if (!isReady()) - { - qWarning() << "saveConfiguration: Tox not started, aborting!"; - return; - } - - QSaveFile configurationFile(path); - if (!configurationFile.open(QIODevice::WriteOnly)) - { - qCritical() << "File " << path << " cannot be opened"; - return; - } - - qDebug() << "writing tox_save to " << path; - - uint32_t fileSize = tox_get_savedata_size(tox); - bool encrypt = Settings::getInstance().getEncryptTox(); - - if (fileSize > 0 && fileSize <= std::numeric_limits::max()) - { - uint8_t *data = new uint8_t[fileSize]; - - if (encrypt) - { - if (!pwsaltedkeys[ptMain]) - { - // probably zero chance event - GUI::showWarning(tr("NO Password"), tr("Local file encryption is enabled, but there is no password! It will be disabled.")); - Settings::getInstance().setEncryptTox(false); - tox_get_savedata(tox, data); - } - else - { - tox_get_savedata(tox, data); - uint8_t* newData = new uint8_t[fileSize+TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; - if (tox_pass_key_encrypt(data, fileSize, pwsaltedkeys[ptMain], newData, nullptr)) - { - delete[] data; - data = newData; - fileSize+=TOX_PASS_ENCRYPTION_EXTRA_LENGTH; - } - else - { - delete[] newData; - delete[] data; - qCritical() << "Core::saveConfiguration(QString): Encryption failed, couldn't save"; - configurationFile.cancelWriting(); - return; - } - } - } - else - { - tox_get_savedata(tox, data); - } - - configurationFile.write(reinterpret_cast(data), fileSize); - configurationFile.commit(); - delete[] data; - } - - Settings::getInstance().save(); -} diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 1e93431d6..27586ef0b 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -1282,3 +1282,11 @@ void Settings::createPersonal(QString basename) ps.beginGroup("Privacy"); ps.endGroup(); } + +void Settings::createSettingsDir() +{ + QString dir = Settings::getSettingsDirPath(); + QDir directory(dir); + if (!directory.exists() && !directory.mkpath(directory.absolutePath())) + qCritical() << "Error while creating directory " << dir; +} diff --git a/src/misc/settings.h b/src/misc/settings.h index 5ae8e2132..28feccb80 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -36,6 +36,7 @@ public: QString detectProfile(); QList searchProfiles(); QString askProfiles(); + void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist void createPersonal(QString basename); ///< Write a default personnal settings file for a profile diff --git a/src/nexus.cpp b/src/nexus.cpp index c712d2e1c..1aadc87a8 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -148,7 +148,6 @@ void Nexus::showMainGUI() connect(widget, &Widget::statusSet, core, &Core::setStatus); connect(widget, &Widget::friendRequested, core, &Core::requestFriendship); connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest); - connect(widget, &Widget::changeProfile, core, &Core::switchConfiguration); #endif profile->startCore(); diff --git a/src/profile.cpp b/src/profile.cpp index 4ead3976d..05c78da62 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -184,6 +184,11 @@ fail: return data; } +void Profile::saveToxSave() +{ + saveToxSave(core->getToxSaveData()); +} + void Profile::saveToxSave(QByteArray data) { ProfileLocker::assertLock(); diff --git a/src/profile.h b/src/profile.h index 81c426514..7d3f07861 100644 --- a/src/profile.h +++ b/src/profile.h @@ -27,7 +27,8 @@ public: void startCore(); ///< Starts the Core thread bool isNewProfile(); QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted - void saveToxSave(QByteArray data); ///< Saves the profile's .tox save, encrypted if needed + void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed + void saveToxSave(QByteArray data); ///< Saves the profile's .tox save with this data, encrypted if needed /// Scan for profile, automatically importing them if needed /// NOT thread-safe diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 9e4d273eb..df6495e7c 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -26,6 +26,7 @@ #include "src/historykeeper.h" #include "src/misc/style.h" #include "src/profilelocker.h" +#include "src/profile.h" #include #include #include @@ -35,17 +36,6 @@ #include #include -void ProfileForm::refreshProfiles() -{ - bodyUI->profiles->clear(); - for (QString profile : Settings::getInstance().searchProfiles()) - bodyUI->profiles->addItem(profile); - - QString current = Settings::getInstance().getCurrentProfile(); - if (current != "") - bodyUI->profiles->setCurrentText(current); -} - ProfileForm::ProfileForm(QWidget *parent) : QWidget{parent}, qr{nullptr} { @@ -98,25 +88,12 @@ ProfileForm::ProfileForm(QWidget *parent) : connect(bodyUI->toxIdLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked())); connect(toxId, SIGNAL(clicked()), this, SLOT(copyIdClicked())); connect(core, &Core::idSet, this, &ProfileForm::setToxId); - connect(core, &Core::statusSet, this, &ProfileForm::onStatusSet); connect(bodyUI->userName, SIGNAL(editingFinished()), this, SLOT(onUserNameEdited())); connect(bodyUI->statusMessage, SIGNAL(editingFinished()), this, SLOT(onStatusMessageEdited())); - connect(bodyUI->loadButton, &QPushButton::clicked, this, &ProfileForm::onLoadClicked); connect(bodyUI->renameButton, &QPushButton::clicked, this, &ProfileForm::onRenameClicked); connect(bodyUI->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked); connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked); - connect(bodyUI->importButton, &QPushButton::clicked, this, &ProfileForm::onImportClicked); - connect(bodyUI->newButton, &QPushButton::clicked, this, &ProfileForm::onNewClicked); - - connect(core, &Core::avStart, this, &ProfileForm::disableSwitching); - connect(core, &Core::avStarting, this, &ProfileForm::disableSwitching); - connect(core, &Core::avInvite, this, &ProfileForm::disableSwitching); - connect(core, &Core::avRinging, this, &ProfileForm::disableSwitching); - connect(core, &Core::avCancel, this, &ProfileForm::enableSwitching); - connect(core, &Core::avEnd, this, &ProfileForm::enableSwitching); - connect(core, &Core::avEnding, this, &ProfileForm::enableSwitching); - connect(core, &Core::avPeerTimeout, this, &ProfileForm::enableSwitching); - connect(core, &Core::avRequestTimeout, this, &ProfileForm::enableSwitching); + connect(bodyUI->logoutButton, &QPushButton::clicked, this, &ProfileForm::onLogoutClicked); connect(core, &Core::usernameSet, this, [=](const QString& val) { bodyUI->userName->setText(val); }); connect(core, &Core::statusMessageSet, this, [=](const QString& val) { bodyUI->statusMessage->setText(val); }); @@ -187,7 +164,6 @@ void ProfileForm::setToxId(const QString& id) qr = new QRWidget(); qr->setQRData("tox:"+id); bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150))); - refreshProfiles(); } void ProfileForm::onAvatarClicked() @@ -250,22 +226,9 @@ void ProfileForm::onAvatarClicked() Nexus::getCore()->setAvatar(bytes); } -void ProfileForm::onLoadClicked() -{ - if (bodyUI->profiles->currentText() != Settings::getInstance().getCurrentProfile()) - { - if (Core::getInstance()->anyActiveCalls()) - GUI::showWarning(tr("Call active", "popup title"), - tr("You can't switch profiles while a call is active!", "popup text")); - else - emit Widget::getInstance()->changeProfile(bodyUI->profiles->currentText()); - // I think by directly calling the function, I may have been causing thread issues - } -} - void ProfileForm::onRenameClicked() { - QString cur = bodyUI->profiles->currentText(); + /** TODO: Create a rename (low level) function in Profile, use it QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur); do { @@ -297,11 +260,12 @@ void ProfileForm::onRenameClicked() break; } } while (true); + */ } void ProfileForm::onExportClicked() { - QString current = bodyUI->profiles->currentText() + Core::TOX_EXT; + QString current = Nexus::getProfile()->getName() + Core::TOX_EXT; QString path = QFileDialog::getSaveFileName(0, tr("Export profile", "save dialog title"), QDir::home().filePath(current), tr("Tox save file (*.tox)", "save dialog filter")); @@ -319,6 +283,7 @@ void ProfileForm::onExportClicked() void ProfileForm::onDeleteClicked() { + /** Add a delete function in profile if (Settings::getInstance().getCurrentProfile() == bodyUI->profiles->currentText()) { GUI::showWarning(tr("Profile currently loaded","current profile deletion warning title"), tr("This profile is currently in use. Please load a different profile before deleting this one.","current profile deletion warning text")); @@ -340,76 +305,22 @@ void ProfileForm::onDeleteClicked() bodyUI->profiles->setCurrentText(Settings::getInstance().getCurrentProfile()); } } + */ } -void ProfileForm::onImportClicked() +void ProfileForm::onLogoutClicked() { - QString path = QFileDialog::getOpenFileName(0, - tr("Import profile", "import dialog title"), - QDir::homePath(), - tr("Tox save file (*.tox)", "import dialog filter")); - if (path.isEmpty()) - return; - - QFileInfo info(path); - QString profile = info.completeBaseName(); - - if (info.suffix() != "tox") - { - GUI::showWarning(tr("Ignoring non-Tox file", "popup title"), - tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text")); - return; - } - - QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT); - - if (QFileInfo(profilePath).exists() && !GUI::askQuestion(tr("Profile already exists", "import confirm title"), - tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile))) - return; - - QFile::copy(path, profilePath); - bodyUI->profiles->addItem(profile); + /// TODO: Save and call Nexus show login? } -void ProfileForm::onStatusSet(Status) -{ - refreshProfiles(); -} - -void ProfileForm::onNewClicked() -{ - emit Widget::getInstance()->changeProfile(QString()); -} - -void ProfileForm::disableSwitching() -{ - bodyUI->loadButton->setEnabled(false); - bodyUI->newButton->setEnabled(false); -} - -void ProfileForm::enableSwitching() -{ - if (!core->anyActiveCalls()) - { - bodyUI->loadButton->setEnabled(true); - bodyUI->newButton->setEnabled(true); - } -} - -void ProfileForm::showEvent(QShowEvent *event) -{ - refreshProfiles(); - QWidget::showEvent(event); -} - -void ProfileForm::on_copyQr_clicked() +void ProfileForm::onCopyQrClicked() { QApplication::clipboard()->setImage(*qr->getImage()); } -void ProfileForm::on_saveQr_clicked() +void ProfileForm::onSaveQrClicked() { - QString current = bodyUI->profiles->currentText() + ".png"; + QString current = Nexus::getProfile()->getName() + ".png"; QString path = QFileDialog::getSaveFileName(0, tr("Save", "save qr image"), QDir::home().filePath(current), tr("Save QrCode (*.png)", "save dialog filter")); @@ -424,14 +335,3 @@ void ProfileForm::on_saveQr_clicked() GUI::showWarning(tr("Failed to copy file"), tr("The file you chose could not be written to.")); } } - -bool ProfileForm::eventFilter(QObject *o, QEvent *e) -{ - if ((e->type() == QEvent::Wheel) && - (qobject_cast(o) || qobject_cast(o) )) - { - e->ignore(); - return true; - } - return QWidget::eventFilter(o, e); -} diff --git a/src/widget/form/profileform.h b/src/widget/form/profileform.h index 62e8c2244..e4828b9a2 100644 --- a/src/widget/form/profileform.h +++ b/src/widget/form/profileform.h @@ -56,7 +56,6 @@ signals: public slots: void onSelfAvatarLoaded(const QPixmap &pic); - void onStatusSet(Status status); private slots: void setToxId(const QString& id); @@ -64,21 +63,12 @@ private slots: void onAvatarClicked(); void onUserNameEdited(); void onStatusMessageEdited(); - void onLoadClicked(); void onRenameClicked(); void onExportClicked(); void onDeleteClicked(); - void onImportClicked(); - void onNewClicked(); - void disableSwitching(); - void enableSwitching(); - void on_copyQr_clicked(); - - void on_saveQr_clicked(); - -protected: - virtual void showEvent(QShowEvent *); - bool eventFilter(QObject *o, QEvent *e); + void onLogoutClicked(); + void onCopyQrClicked(); + void onSaveQrClicked(); private: void refreshProfiles(); diff --git a/src/widget/form/profileform.ui b/src/widget/form/profileform.ui index 03c73d16f..f02ae7155 100644 --- a/src/widget/form/profileform.ui +++ b/src/widget/form/profileform.ui @@ -37,10 +37,10 @@ - 0 + -3 0 - 565 - 617 + 630 + 625 @@ -160,52 +160,41 @@ Share it with your friends to communicate. - Profiles + Profile - - - - - Available profiles: - - - + - - - - 0 - 0 - + + + Qt::Horizontal - - Currently selected profile. + + + 40 + 20 + - + - - - - - + - Load selected profile and switch to it. + Rename profile. - Load + Rename - + - Rename selected profile. + Delete profile. - Rename + Delete @@ -221,38 +210,27 @@ Profile does not contain your history. - - - Delete selected profile. - - - Delete - - - - - - - - - + - Import Tox profile from a .tox file. + Go back to the login screen - Import a profile + Logout - - - Create new Tox ID and switch to it. + + + Qt::Horizontal - - New Tox ID + + + 40 + 20 + - + diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 630023ffe..067f5edf6 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -349,9 +349,9 @@ void GeneralForm::onReconnectClicked() { if (Core::getInstance()->anyActiveCalls()) QMessageBox::warning(this, tr("Call active", "popup title"), - tr("You can't disconnect while a call is active!", "popup text")); + tr("You can't disconnect while a call is active!", "popup text")); else - emit Widget::getInstance()->changeProfile(Settings::getInstance().getCurrentProfile()); + ; /// TODO: Add a reset function in Profile to save then restart toxcore } void GeneralForm::reloadSmiles() diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index c5c8d6521..f2d99d347 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -218,7 +218,7 @@ bool PrivacyForm::setToxPassword() core->setPassword(newpw, Core::ptMain); Settings::getInstance().setEncryptTox(true); - core->saveConfiguration(); + Settings::getInstance().save(); return true; } else diff --git a/src/widget/widget.h b/src/widget/widget.h index cb1cd0870..09ec716e2 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -131,7 +131,6 @@ signals: void statusSelected(Status status); void usernameChanged(const QString& username); void statusMessageChanged(const QString& statusMessage); - void changeProfile(const QString& profile); void resized(); private slots: From 05a49e807231eaf50871fe40d1afeb1b5c2621f4 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 11:42:49 +0200 Subject: [PATCH 14/34] Autosave on exit --- src/nexus.cpp | 6 +++++- src/profile.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nexus.cpp b/src/nexus.cpp index 1aadc87a8..f6826ed4a 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -25,7 +25,8 @@ Nexus::Nexus(QObject *parent) : QObject(parent), profile{nullptr}, widget{nullptr}, - androidgui{nullptr} + androidgui{nullptr}, + loginScreen{nullptr} { } @@ -36,6 +37,9 @@ Nexus::~Nexus() #else delete widget; #endif + delete loginScreen; + delete profile; + Settings::getInstance().save(); } void Nexus::start() diff --git a/src/profile.cpp b/src/profile.cpp index 05c78da62..6b71cef9b 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -195,6 +195,7 @@ void Profile::saveToxSave(QByteArray data) assert(ProfileLocker::getCurLockName() == name); QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox"; + qDebug() << "Saving tox save to "< Date: Thu, 4 Jun 2015 11:56:19 +0200 Subject: [PATCH 15/34] Implement logout button --- src/nexus.cpp | 16 ++++++++++++++-- src/nexus.h | 2 +- src/profile.cpp | 1 + src/widget/form/profileform.cpp | 5 ++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/nexus.cpp b/src/nexus.cpp index f6826ed4a..1a768a620 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -39,7 +39,8 @@ Nexus::~Nexus() #endif delete loginScreen; delete profile; - Settings::getInstance().save(); + if (profile) + Settings::getInstance().save(); } void Nexus::start() @@ -68,12 +69,23 @@ void Nexus::start() void Nexus::showLogin() { - ((QApplication*)qApp)->setQuitOnLastWindowClosed(true); +#ifdef Q_OS_ANDROID + delete androidui; + androidgui = nullptr; +#else + delete widget; + widget = nullptr; +#endif + + delete profile; + profile = nullptr; + loginScreen->reset(); #ifndef Q_OS_ANDROID loginScreen->move(QApplication::desktop()->screen()->rect().center() - loginScreen->rect().center()); #endif loginScreen->show(); + ((QApplication*)qApp)->setQuitOnLastWindowClosed(true); } void Nexus::showMainGUI() diff --git a/src/nexus.h b/src/nexus.h index 835ebe8b3..17a636372 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -17,7 +17,7 @@ class Nexus : public QObject Q_OBJECT public: void start(); ///< Sets up invariants and calls showLogin - void showLogin(); ///< Shows the login screen + void showLogin(); ///< Hides the man GUI, delete the profile, and shows the login screen /// Hides the login screen and shows the GUI for the given profile. /// Will delete the current GUI, if it exists. void showMainGUI(); diff --git a/src/profile.cpp b/src/profile.cpp index 6b71cef9b..f8c42d073 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -66,6 +66,7 @@ Profile::~Profile() { delete core; delete coreThread; + ProfileLocker::assertLock(); assert(ProfileLocker::getCurLockName() == name); ProfileLocker::unlock(); } diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index df6495e7c..095e6a1e1 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -310,7 +310,10 @@ void ProfileForm::onDeleteClicked() void ProfileForm::onLogoutClicked() { - /// TODO: Save and call Nexus show login? + Nexus& nexus = Nexus::getInstance(); + nexus.getProfile()->saveToxSave(); + Settings::getInstance().save(); + nexus.showLogin(); } void ProfileForm::onCopyQrClicked() From f7546a731f92fca6f44ee52ee40d3fd3c5fe3191 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 12:14:17 +0200 Subject: [PATCH 16/34] Fix profile saving on exit --- src/core/core.cpp | 11 +++++++++-- src/core/core.h | 6 ++++-- src/profile.cpp | 1 + src/widget/form/profileform.cpp | 1 - 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 27883c19d..0ddce44c9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -103,8 +104,8 @@ Core::~Core() { qDebug() << "Deleting Core"; - profile.saveToxSave(); - toxTimer->stop(); + QMetaObject::invokeMethod(this, "stopTimers", Qt::BlockingQueuedConnection); + delete toxTimer; coreThread->exit(0); while (coreThread->isRunning()) { @@ -1238,3 +1239,9 @@ void Core::resetCallSources() } } } + +void Core::stopTimers() +{ + assert(QThread::currentThread() == coreThread); + toxTimer->stop(); +} diff --git a/src/core/core.h b/src/core/core.h index 2d22867d9..f963d221e 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -285,12 +285,14 @@ private: void deadifyTox(); +private slots: + void stopTimers(); ///< Must only be called from the Core thread + private: Tox* tox; ToxAv* toxav; - QTimer *toxTimer, *fileTimer; //, *saveTimer; + QTimer *toxTimer; Profile& profile; - int dhtServerId; static ToxCall calls[TOXAV_MAX_CALLS]; #ifdef QTOX_FILTER_AUDIO static AudioFilterer * filterer[TOXAV_MAX_CALLS]; diff --git a/src/profile.cpp b/src/profile.cpp index f8c42d073..0b92cd467 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -64,6 +64,7 @@ Profile* Profile::createProfile(QString name, QString password) Profile::~Profile() { + saveToxSave(); delete core; delete coreThread; ProfileLocker::assertLock(); diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 095e6a1e1..9b9eb63fa 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -311,7 +311,6 @@ void ProfileForm::onDeleteClicked() void ProfileForm::onLogoutClicked() { Nexus& nexus = Nexus::getInstance(); - nexus.getProfile()->saveToxSave(); Settings::getInstance().save(); nexus.showLogin(); } From 21db31c21515481d737190697b3e175438ef337b Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 12:43:28 +0200 Subject: [PATCH 17/34] Implement profile deletion --- src/core/core.cpp | 14 +++++++++++--- src/profile.cpp | 34 ++++++++++++++++++++++++++++++--- src/profile.h | 12 +++++++++--- src/widget/form/profileform.cpp | 26 +++++-------------------- src/widget/loginscreen.ui | 8 ++++++-- 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 0ddce44c9..4b6415caf 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -104,7 +104,13 @@ Core::~Core() { qDebug() << "Deleting Core"; - QMetaObject::invokeMethod(this, "stopTimers", Qt::BlockingQueuedConnection); + if (coreThread->isRunning()) + { + if (QThread::currentThread() == coreThread) + stopTimers(); + else + QMetaObject::invokeMethod(this, "stopTimers", Qt::BlockingQueuedConnection); + } delete toxTimer; coreThread->exit(0); while (coreThread->isRunning()) @@ -781,7 +787,8 @@ void Core::setUsername(const QString& username) else { emit usernameSet(username); - profile.saveToxSave(); + if (ready) + profile.saveToxSave(); } } @@ -845,7 +852,8 @@ void Core::setStatusMessage(const QString& message) } else { - profile.saveToxSave(); + if (ready) + profile.saveToxSave(); emit statusMessageSet(message); } } diff --git a/src/profile.cpp b/src/profile.cpp index 0b92cd467..89aa8e8d3 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -2,6 +2,7 @@ #include "profilelocker.h" #include "src/misc/settings.h" #include "src/core/core.h" +#include "src/historykeeper.h" #include #include #include @@ -12,7 +13,8 @@ QVector Profile::profiles; Profile::Profile(QString name, QString password, bool isNewProfile) - : name{name}, password{password}, newProfile{isNewProfile} + : name{name}, password{password}, + newProfile{isNewProfile}, isRemoved{false} { coreThread = new QThread(); coreThread->setObjectName("qTox Core"); @@ -64,7 +66,8 @@ Profile* Profile::createProfile(QString name, QString password) Profile::~Profile() { - saveToxSave(); + if (!isRemoved && core->isReady()) + saveToxSave(); delete core; delete coreThread; ProfileLocker::assertLock(); @@ -130,6 +133,8 @@ bool Profile::isNewProfile() QByteArray Profile::loadToxSave() { + assert(!isRemoved); + /// TODO: Cache the data, invalidate it only when we save QByteArray data; @@ -188,11 +193,15 @@ fail: void Profile::saveToxSave() { - saveToxSave(core->getToxSaveData()); + assert(core->isReady()); + QByteArray data = core->getToxSaveData(); + assert(data.size()); + saveToxSave(data); } void Profile::saveToxSave(QByteArray data) { + assert(!isRemoved); ProfileLocker::assertLock(); assert(ProfileLocker::getCurLockName() == name); @@ -244,3 +253,22 @@ bool Profile::isProfileEncrypted(QString name) return tox_is_data_encrypted(data); } + +void Profile::remove() +{ + if (isRemoved) + { + qWarning() << "Profile "< profiles; bool newProfile; ///< True if this is a newly created profile, with no .tox save file yet. + bool isRemoved; ///< True if the profile has been removed by remove() + static QVector profiles; /// How much data we need to read to check if the file is encrypted /// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined static constexpr int encryptHeaderSize = 8; diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 9b9eb63fa..1ae17fc6a 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -283,29 +283,13 @@ void ProfileForm::onExportClicked() void ProfileForm::onDeleteClicked() { - /** Add a delete function in profile - if (Settings::getInstance().getCurrentProfile() == bodyUI->profiles->currentText()) + if (GUI::askQuestion(tr("Really delete profile?","deletion confirmation title"), + tr("Are you sure you want to delete this profile?","deletion confirmation text"))) { - GUI::showWarning(tr("Profile currently loaded","current profile deletion warning title"), tr("This profile is currently in use. Please load a different profile before deleting this one.","current profile deletion warning text")); + Nexus& nexus = Nexus::getInstance(); + nexus.getProfile()->remove(); + nexus.showLogin(); } - else - { - if (GUI::askQuestion(tr("Deletion imminent!","deletion confirmation title"), - tr("Are you sure you want to delete this profile?","deletion confirmation text"))) - { - QString profile = bodyUI->profiles->currentText(); - QDir dir(Settings::getSettingsDirPath()); - - QFile::remove(dir.filePath(profile + Core::TOX_EXT)); - QFile::remove(dir.filePath(profile + ".ini")); - QFile::remove(HistoryKeeper::getHistoryPath(profile, 0)); - QFile::remove(HistoryKeeper::getHistoryPath(profile, 1)); - - bodyUI->profiles->removeItem(bodyUI->profiles->currentIndex()); - bodyUI->profiles->setCurrentText(Settings::getInstance().getCurrentProfile()); - } - } - */ } void ProfileForm::onLogoutClicked() diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 57de30668..017245aa4 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -281,7 +281,7 @@ true - 1 + 0 @@ -394,7 +394,11 @@ margin-bottom:45px; - + + + QLineEdit::Password + + From 7fc087ea950334b83de3aafaecd05ae066a5dc67 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 13:01:30 +0200 Subject: [PATCH 18/34] Implement profile renaming --- src/profile.cpp | 21 +++++++++++++++++++++ src/profile.h | 3 +++ src/widget/form/profileform.cpp | 31 +++++++++---------------------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/profile.cpp b/src/profile.cpp index 89aa8e8d3..41c5e2f48 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -272,3 +272,24 @@ void Profile::remove() QFile::remove(HistoryKeeper::getHistoryPath(name, 0)); QFile::remove(HistoryKeeper::getHistoryPath(name, 1)); } + +bool Profile::rename(QString newName) +{ + QString path = Settings::getSettingsDirPath() + QDir::separator() + name, + newPath = Settings::getSettingsDirPath() + QDir::separator() + newName; + + if (!ProfileLocker::lock(newName)) + return false; + + QFile::rename(path+".tox", newPath+".tox"); + QFile::rename(path+".ini", newPath+".ini"); + HistoryKeeper::renameHistory(name, newName); + bool resetAutorun = Settings::getInstance().getAutorun(); + Settings::getInstance().setAutorun(false); + Settings::getInstance().setCurrentProfile(newName); + if (resetAutorun) + Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line + + name = newName; + return true; +} diff --git a/src/profile.h b/src/profile.h index e158bfd41..e048b7cc8 100644 --- a/src/profile.h +++ b/src/profile.h @@ -35,6 +35,9 @@ public: /// Updates the profiles vector void remove(); + /// Tries to rename the profile + bool rename(QString newName); + /// Scan for profile, automatically importing them if needed /// NOT thread-safe static void scanProfiles(); diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 1ae17fc6a..aa7f65bb8 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -228,39 +228,26 @@ void ProfileForm::onAvatarClicked() void ProfileForm::onRenameClicked() { - /** TODO: Create a rename (low level) function in Profile, use it + Nexus& nexus = Nexus::getInstance(); + QString cur = nexus.getProfile()->getName(); QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur); do { QString name = QInputDialog::getText(this, title, title+":"); if (name.isEmpty()) break; name = Core::sanitize(name); - QDir dir(Settings::getSettingsDirPath()); - QString file = dir.filePath(name+Core::TOX_EXT); - if (!QFile::exists(file) || GUI::askQuestion(tr("Profile already exists", "rename confirm title"), - tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(cur))) + + if (!Profile::profileExists(name) || GUI::askQuestion(tr("Profile already exists", "rename confirm title"), + tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(name))) { - if (!ProfileLocker::lock(name)) - { - GUI::showWarning(tr("Profile already exists", "rename failed title"), - tr("A profile named \"%1\" already exists and is in use.").arg(cur)); - break; - } - - QFile::rename(dir.filePath(cur+Core::TOX_EXT), file); - QFile::rename(dir.filePath(cur+".ini"), dir.filePath(name+".ini")); - bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name); - HistoryKeeper::renameHistory(cur, name); - bool resetAutorun = Settings::getInstance().getAutorun(); - Settings::getInstance().setAutorun(false); - Settings::getInstance().setCurrentProfile(name); - if (resetAutorun) - Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line + + if (!nexus.getProfile()->rename(name)) + GUI::showError(tr("Failed to rename", "rename failed title"), + tr("Couldn't rename the profile to \"%1\"").arg(cur)); break; } } while (true); - */ } void ProfileForm::onExportClicked() From 1ffb2d4a924eb0b45582b70ce78467ba784db6df Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 13:07:14 +0200 Subject: [PATCH 19/34] Remove some unused Settings methods --- src/misc/settings.cpp | 92 ------------------------------------------- src/misc/settings.h | 8 ---- 2 files changed, 100 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 27586ef0b..698ff5d22 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -72,98 +72,6 @@ void Settings::switchProfile(const QString& profile) load(); } -QString Settings::genRandomProfileName() -{ - QDir dir(getSettingsDirPath()); - QString basename = "imported_"; - QString randname; - do { - randname = QString().setNum(qrand()*qrand()*qrand(), 16); - randname.truncate(6); - randname = basename + randname; - } while (QFile(dir.filePath(randname)).exists()); - return randname; -} - -QString Settings::detectProfile() -{ - QDir dir(getSettingsDirPath()); - QString path, profile = getCurrentProfile(); - path = dir.filePath(profile + Core::TOX_EXT); - QFile file(path); - if (profile.isEmpty() || !file.exists()) - { - setCurrentProfile(""); -#if 1 // deprecation attempt - // if the last profile doesn't exist, fall back to old "data" - path = dir.filePath(Core::CONFIG_FILE_NAME); - QFile file(path); - if (file.exists()) - { - profile = genRandomProfileName(); - setCurrentProfile(profile); - file.rename(profile + Core::TOX_EXT); - return profile; - } - else if (QFile(path = dir.filePath("tox_save")).exists()) // also import tox_save if no data - { - profile = genRandomProfileName(); - setCurrentProfile(profile); - QFile(path).rename(profile + Core::TOX_EXT); - return profile; - } - else -#endif - { - profile = askProfiles(); - if (profile.isEmpty()) - { - return ""; - } - else - { - switchProfile(profile); - return dir.filePath(profile + Core::TOX_EXT); - } - } - } - else - { - return path; - } -} - -QList Settings::searchProfiles() -{ - QList out; - QDir dir(getSettingsDirPath()); - dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); - dir.setNameFilters(QStringList("*.tox")); - for (QFileInfo file : dir.entryInfoList()) - out += file.completeBaseName(); - - return out; -} - -QString Settings::askProfiles() -{ // TODO: allow user to create new Tox ID, even if a profile already exists - QList profiles = searchProfiles(); - if (profiles.empty()) return ""; - bool ok; - QString profile = GUI::itemInputDialog(nullptr, - tr("Choose a profile"), - tr("Please choose which identity to use"), - profiles, - 0, // which slot to start on - false, // if the user can enter their own input - &ok); - if (!ok) // user cancelled - return ""; - else - return profile; -} - - void Settings::load() { if (loaded) diff --git a/src/misc/settings.h b/src/misc/settings.h index 28feccb80..fd2cd4031 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -33,15 +33,10 @@ public: ~Settings() = default; static Settings& getInstance(); void switchProfile(const QString& profile); - QString detectProfile(); - QList searchProfiles(); - QString askProfiles(); void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist void createPersonal(QString basename); ///< Write a default personnal settings file for a profile - void executeSettingsDialog(QWidget* parent); - static QString getSettingsDirPath(); struct DhtServer @@ -262,9 +257,6 @@ public: void save(QString path, bool writePersonal = true); void load(); -private: - static QString genRandomProfileName(); - private: static Settings* settings; From 42a7efb053d1fcd47294ef49a541783995075617 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 14:19:18 +0200 Subject: [PATCH 20/34] Implement the -p option --- src/core/coreencryption.cpp | 4 +++- src/historykeeper.cpp | 4 +++- src/main.cpp | 24 +++++++++++++++----- src/misc/settings.cpp | 45 +++++++++---------------------------- src/misc/settings.h | 3 +-- src/nexus.cpp | 7 ++++-- src/profile.cpp | 10 +++++++++ src/profile.h | 3 ++- src/widget/loginscreen.cpp | 11 +++++++-- 9 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index 2a1a7a0ec..34e7fdfec 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -21,6 +21,8 @@ #include "src/misc/settings.h" #include "src/misc/cstring.h" #include "src/historykeeper.h" +#include "src/nexus.h" +#include "src/profile.h" #include #include #include @@ -139,7 +141,7 @@ void Core::checkEncryptedHistory() return; } - QString a(tr("Please enter the password for the chat history for the %1 profile.", "used in load() when no hist pw set").arg(Settings::getInstance().getCurrentProfile())); + QString a(tr("Please enter the password for the chat history for the profile \"%1\".", "used in load() when no hist pw set").arg(Nexus::getProfile()->getName())); QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); QString c(tr("\nDisabling chat history now will leave the encrypted history intact (but not usable); if you later remember the password, you may re-enable encryption from the Privacy tab with the correct password to use the history.", "part of history password dialog")); QString dialogtxt; diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index 56f3a4bf8..fdf7cc952 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -15,6 +15,8 @@ #include "historykeeper.h" #include "misc/settings.h" #include "src/core/core.h" +#include "src/nexus.h" +#include "src/profile.h" #include #include @@ -74,7 +76,7 @@ bool HistoryKeeper::checkPassword(int encrypted) return true; if ((encrypted == 1) || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) - return EncryptedDb::check(getHistoryPath(Settings::getInstance().getCurrentProfile(), encrypted)); + return EncryptedDb::check(getHistoryPath(Nexus::getProfile()->getName(), encrypted)); return true; } diff --git a/src/main.cpp b/src/main.cpp index 1be0ef81c..515b44938 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #include "src/widget/toxuri.h" #include "src/widget/toxsave.h" #include "src/autoupdate.h" +#include "src/profile.h" #include "src/profilelocker.h" #include "src/widget/loginscreen.h" #include @@ -113,15 +114,28 @@ int main(int argc, char *argv[]) if (parser.isSet("p")) { - QString profile = parser.value("p"); - if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox")) + QString profileName = parser.value("p"); + if (QDir(Settings::getSettingsDirPath()).exists(profileName + ".tox")) { - qDebug() << "Setting profile to" << profile; - Settings::getInstance().switchProfile(profile); + qDebug() << "Setting profile to" << profileName; + if (Profile::isProfileEncrypted(profileName)) + { + Settings::getInstance().setCurrentProfile(profileName); + } + else + { + Profile* profile = Profile::loadProfile(profileName); + if (!profile) + { + qCritical() << "-p profile" << profileName + ".tox" << " couldn't be loaded"; + return EXIT_FAILURE; + } + Nexus::getInstance().setProfile(profile); + } } else { - qCritical() << "-p profile" << profile + ".tox" << "doesn't exist"; + qCritical() << "-p profile" << profileName + ".tox" << "doesn't exist"; return EXIT_FAILURE; } } diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 698ff5d22..052dedebd 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -40,8 +40,7 @@ #define SHOW_SYSTEM_TRAY_DEFAULT (bool) true -const QString Settings::OLDFILENAME = "settings.ini"; -const QString Settings::FILENAME = "qtox.ini"; +const QString Settings::globalSettingsFile = "qtox.ini"; Settings* Settings::settings{nullptr}; bool Settings::makeToxPortable{false}; @@ -59,38 +58,17 @@ Settings& Settings::getInstance() return *settings; } -void Settings::switchProfile(const QString& profile) -{ - // Saves current profile as main profile if this instance is main instance - setCurrentProfile(profile); - save(false); - - // If this instance is not main instance previous save did not happen therefore - // we manually set profile again and load profile settings - setCurrentProfile(profile); - loaded = false; - load(); -} - void Settings::load() { if (loaded) return; + createSettingsDir(); QDir dir(getSettingsDirPath()); - if (!dir.exists()) - dir.mkpath("."); - if (QFile(FILENAME).exists()) - { - QSettings ps(FILENAME, QSettings::IniFormat); - ps.beginGroup("General"); - makeToxPortable = ps.value("makeToxPortable", false).toBool(); - ps.endGroup(); - } - else if (QFile(OLDFILENAME).exists()) + if (QFile(globalSettingsFile).exists()) { - QSettings ps(OLDFILENAME, QSettings::IniFormat); + QSettings ps(globalSettingsFile, QSettings::IniFormat); ps.beginGroup("General"); makeToxPortable = ps.value("makeToxPortable", false).toBool(); ps.endGroup(); @@ -100,16 +78,13 @@ void Settings::load() makeToxPortable = false; } - QString filePath = dir.filePath(FILENAME); + QString filePath = dir.filePath(globalSettingsFile); - //if no settings file exist -- use the default one + // If no settings file exist -- use the default one if (!QFile(filePath).exists()) { - if (!QFile(filePath = dir.filePath(OLDFILENAME)).exists()) - { - qDebug() << "No settings file found, using defaults"; - filePath = ":/conf/" + FILENAME; - } + qDebug() << "No settings file found, using defaults"; + filePath = ":/conf/" + globalSettingsFile; } qDebug() << "Loading settings from " + filePath; @@ -282,7 +257,7 @@ void Settings::load() void Settings::save(bool writePersonal) { - QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME); + QString filePath = QDir(getSettingsDirPath()).filePath(globalSettingsFile); save(filePath, writePersonal); } @@ -533,7 +508,7 @@ bool Settings::getMakeToxPortable() const void Settings::setMakeToxPortable(bool newValue) { makeToxPortable = newValue; - save(FILENAME); // Commit to the portable file that we don't want to use it + save(globalSettingsFile); // Commit to the portable file that we don't want to use it if (!newValue) // Update the new file right now if not already done save(); } diff --git a/src/misc/settings.h b/src/misc/settings.h index fd2cd4031..4341b709d 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -32,7 +32,6 @@ class Settings : public QObject public: ~Settings() = default; static Settings& getInstance(); - void switchProfile(const QString& profile); void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist void createPersonal(QString basename); ///< Write a default personnal settings file for a profile @@ -267,7 +266,7 @@ private: void saveGlobal(QString path); void savePersonal(QString path); - static const QString FILENAME; + static const QString globalSettingsFile; static const QString OLDFILENAME; bool loaded; diff --git a/src/nexus.cpp b/src/nexus.cpp index 1a768a620..e83d2a21d 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -62,9 +62,12 @@ void Nexus::start() qRegisterMetaType("Core::PasswordType"); qRegisterMetaType>("std::shared_ptr"); - // Create and show login screen loginScreen = new LoginScreen(); - showLogin(); + + if (profile) + showMainGUI(); + else + showLogin(); } void Nexus::showLogin() diff --git a/src/profile.cpp b/src/profile.cpp index 41c5e2f48..a830c8c26 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -16,6 +16,8 @@ Profile::Profile(QString name, QString password, bool isNewProfile) : name{name}, password{password}, newProfile{isNewProfile}, isRemoved{false} { + Settings::getInstance().setCurrentProfile(name); + coreThread = new QThread(); coreThread->setObjectName("qTox Core"); core = new Core(coreThread, *this); @@ -293,3 +295,11 @@ bool Profile::rename(QString newName) name = newName; return true; } + +bool Profile::checkPassword() +{ + if (isRemoved) + return false; + + return !loadToxSave().isEmpty(); +} diff --git a/src/profile.h b/src/profile.h index e048b7cc8..f91ca6249 100644 --- a/src/profile.h +++ b/src/profile.h @@ -14,7 +14,7 @@ class Profile public: /// Locks and loads an existing profile and create the associate Core* instance /// Returns a nullptr on error, for example if the profile is already in use - static Profile* loadProfile(QString name, QString password); + static Profile* loadProfile(QString name, QString password = QString()); /// Creates a new profile and the associated Core* instance /// If password is not empty, the profile will be encrypted /// Returns a nullptr on error, for example if the profile already exists @@ -26,6 +26,7 @@ public: void startCore(); ///< Starts the Core thread bool isNewProfile(); + bool checkPassword(); ///< Checks whether the password is valid QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles. void saveToxSave(QByteArray data); ///< Write the .tox save, encrypted if needed. Invalid on deleted profiles. diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index d65488455..971660e7e 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -3,7 +3,9 @@ #include "src/profile.h" #include "src/profilelocker.h" #include "src/nexus.h" +#include "src/misc/settings.h" #include +#include LoginScreen::LoginScreen(QWidget *parent) : QWidget(parent), @@ -37,9 +39,15 @@ void LoginScreen::reset() ui->loginUsernames->clear(); Profile::scanProfiles(); + QString lastUsed = Settings::getInstance().getCurrentProfile(); + qDebug() << "Last used is "< profiles = Profile::getProfiles(); for (QString profile : profiles) + { ui->loginUsernames->addItem(profile); + if (profile == lastUsed) + ui->loginUsernames->setCurrentIndex(ui->loginUsernames->count()-1); + } if (profiles.isEmpty()) ui->stackedWidget->setCurrentIndex(0); @@ -130,9 +138,8 @@ void LoginScreen::onLogin() QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Couldn't load this profile.")); return; } - if (profile->loadToxSave().isEmpty()) + if (!profile->checkPassword()) { - // Unknown error QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Wrong password.")); delete profile; return; From 4e2983817fbf0e6411b39162b925621cd0e8e756 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 14:31:13 +0200 Subject: [PATCH 21/34] Fix crash in ~Core --- src/core/core.cpp | 6 +++--- src/core/core.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 4b6415caf..d870950a9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -107,11 +107,10 @@ Core::~Core() if (coreThread->isRunning()) { if (QThread::currentThread() == coreThread) - stopTimers(); + killTimers(); else QMetaObject::invokeMethod(this, "stopTimers", Qt::BlockingQueuedConnection); } - delete toxTimer; coreThread->exit(0); while (coreThread->isRunning()) { @@ -1248,8 +1247,9 @@ void Core::resetCallSources() } } -void Core::stopTimers() +void Core::killTimers() { assert(QThread::currentThread() == coreThread); toxTimer->stop(); + delete toxTimer; } diff --git a/src/core/core.h b/src/core/core.h index f963d221e..05d044ec7 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -286,7 +286,7 @@ private: void deadifyTox(); private slots: - void stopTimers(); ///< Must only be called from the Core thread + void killTimers(); ///< Must only be called from the Core thread private: Tox* tox; From 5f94f33117a0fdfb89f7f795aa49389c92ad254a Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 15:16:25 +0200 Subject: [PATCH 22/34] Use profile password for all encryption --- src/core/core.cpp | 8 +- src/core/core.h | 15 +- src/core/coreencryption.cpp | 84 +++----- src/historykeeper.cpp | 8 +- src/main.cpp | 2 +- src/misc/db/encrypteddb.cpp | 8 +- src/misc/settings.cpp | 24 --- src/misc/settings.h | 8 - src/nexus.cpp | 1 - src/profile.cpp | 20 +- src/profile.h | 5 +- src/widget/form/settings/privacyform.cpp | 212 -------------------- src/widget/form/settings/privacyform.h | 4 - src/widget/form/settings/privacysettings.ui | 85 ++------ src/widget/loginscreen.cpp | 2 +- 15 files changed, 80 insertions(+), 406 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index d870950a9..2567d82e1 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -62,9 +62,7 @@ Core::Core(QThread *CoreThread, Profile& profile) : Audio::getInstance(); videobuf = nullptr; - - for (int i = 0; i < ptCounter; i++) - pwsaltedkeys[i] = nullptr; + encryptionKey = nullptr; toxTimer = new QTimer(this); toxTimer->setSingleShot(true); @@ -109,7 +107,7 @@ Core::~Core() if (QThread::currentThread() == coreThread) killTimers(); else - QMetaObject::invokeMethod(this, "stopTimers", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection); } coreThread->exit(0); while (coreThread->isRunning()) @@ -286,7 +284,7 @@ void Core::start() emit idSet(id); // tox core is already decrypted - if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs()) + if (Settings::getInstance().getEnableLogging() && Nexus::getProfile()->isEncrypted()) checkEncryptedHistory(); loadFriends(); diff --git a/src/core/core.h b/src/core/core.h index 05d044ec7..9160f08d3 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -44,8 +44,6 @@ class Core : public QObject { Q_OBJECT public: - enum PasswordType {ptMain = 0, ptHistory, ptCounter}; - explicit Core(QThread* coreThread, Profile& profile); static Core* getInstance(); ///< Returns the global widget's Core instance ~Core(); @@ -82,7 +80,7 @@ public: VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source static bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active) - bool isPasswordSet(PasswordType passtype); + bool isPasswordSet(); bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first void resetCallSources(); ///< Forces to regenerate each call's audio sources @@ -145,11 +143,10 @@ public slots: static bool isGroupCallMicEnabled(int groupId); static bool isGroupCallVolEnabled(int groupId); - void setPassword(const QString &password, PasswordType passtype, uint8_t* salt = nullptr); - void useOtherPassword(PasswordType type); - void clearPassword(PasswordType passtype); - QByteArray encryptData(const QByteArray& data, PasswordType passtype); - QByteArray decryptData(const QByteArray& data, PasswordType passtype); + void setPassword(const QString &password, uint8_t* salt = nullptr); + void clearPassword(); + QByteArray encryptData(const QByteArray& data); + QByteArray decryptData(const QByteArray& data); signals: void connected(); @@ -301,7 +298,7 @@ private: QMutex messageSendMutex; bool ready; - TOX_PASS_KEY* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw" + TOX_PASS_KEY* encryptionKey = nullptr; // use the pw's hash as the "pw" static const int videobufsize; static uint8_t* videobuf; diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index 34e7fdfec..199c4ad17 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -33,45 +33,38 @@ #include #include -void Core::setPassword(const QString& password, PasswordType passtype, uint8_t* salt) +void Core::setPassword(const QString& password, uint8_t* salt) { - clearPassword(passtype); + clearPassword(); if (password.isEmpty()) return; - pwsaltedkeys[passtype] = new TOX_PASS_KEY; + encryptionKey = new TOX_PASS_KEY; CString str(password); if (salt) - tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype], nullptr); + tox_derive_key_with_salt(str.data(), str.size(), salt, encryptionKey, nullptr); else - tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype], nullptr); + tox_derive_key_from_pass(str.data(), str.size(), encryptionKey, nullptr); } -void Core::useOtherPassword(PasswordType type) +void Core::clearPassword() { - clearPassword(type); - pwsaltedkeys[type] = new TOX_PASS_KEY; - - PasswordType other = (type == ptMain) ? ptHistory : ptMain; - - std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+1, pwsaltedkeys[type]); -} - -void Core::clearPassword(PasswordType passtype) -{ - delete pwsaltedkeys[passtype]; - pwsaltedkeys[passtype] = nullptr; + delete encryptionKey; + encryptionKey = nullptr; } -QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) +QByteArray Core::encryptData(const QByteArray& data) { - if (!pwsaltedkeys[passtype]) + if (!encryptionKey) + { + qWarning() << "No encryption key set"; return QByteArray(); + } uint8_t encrypted[data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; if (!tox_pass_key_encrypt(reinterpret_cast(data.data()), data.size(), - pwsaltedkeys[passtype], encrypted, nullptr)) + encryptionKey, encrypted, nullptr)) { qWarning() << "Encryption failed"; return QByteArray(); @@ -79,15 +72,18 @@ QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) return QByteArray(reinterpret_cast(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH); } -QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) +QByteArray Core::decryptData(const QByteArray& data) { - if (!pwsaltedkeys[passtype]) + if (!encryptionKey) + { + qWarning() << "No encryption key set"; return QByteArray(); + } int sz = data.size() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; uint8_t decrypted[sz]; if (!tox_pass_key_decrypt(reinterpret_cast(data.data()), data.size(), - pwsaltedkeys[passtype], decrypted, nullptr)) + encryptionKey, decrypted, nullptr)) { qWarning() << "Decryption failed"; return QByteArray(); @@ -95,12 +91,9 @@ QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) return QByteArray(reinterpret_cast(decrypted), sz); } -bool Core::isPasswordSet(PasswordType passtype) +bool Core::isPasswordSet() { - if (pwsaltedkeys[passtype]) - return true; - - return false; + return static_cast(encryptionKey); } QByteArray Core::getSaltFromFile(QString filename) @@ -135,42 +128,24 @@ void Core::checkEncryptedHistory() if (exists && salt.size() == 0) { // maybe we should handle this better GUI::showWarning(tr("Encrypted chat history"), tr("No encrypted chat history file found, or it was corrupted.\nHistory will be disabled!")); - Settings::getInstance().setEncryptLogs(false); - Settings::getInstance().setEnableLogging(false); HistoryKeeper::resetInstance(); return; } + setPassword(Nexus::getProfile()->getPassword(), reinterpret_cast(salt.data())); + QString a(tr("Please enter the password for the chat history for the profile \"%1\".", "used in load() when no hist pw set").arg(Nexus::getProfile()->getName())); QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); QString c(tr("\nDisabling chat history now will leave the encrypted history intact (but not usable); if you later remember the password, you may re-enable encryption from the Privacy tab with the correct password to use the history.", "part of history password dialog")); QString dialogtxt; - if (pwsaltedkeys[ptHistory]) - { - if (!exists || HistoryKeeper::checkPassword()) - return; - dialogtxt = tr("The chat history password failed. Please try another?", "used only when pw set before load() doesn't work"); - } - else - { - dialogtxt = a; - } + if (!exists || HistoryKeeper::checkPassword()) + return; + dialogtxt = tr("The chat history password failed. Please try another?", "used only when pw set before load() doesn't work"); dialogtxt += "\n" + c; - if (pwsaltedkeys[ptMain]) - { - useOtherPassword(ptHistory); - if (!exists || HistoryKeeper::checkPassword()) - { - qDebug() << "using main password for chat history"; - return; - } - clearPassword(ptHistory); - } - bool error = true; do { @@ -178,15 +153,14 @@ void Core::checkEncryptedHistory() if (pw.isEmpty()) { - clearPassword(ptHistory); - Settings::getInstance().setEncryptLogs(false); + clearPassword(); Settings::getInstance().setEnableLogging(false); HistoryKeeper::resetInstance(); return; } else { - setPassword(pw, ptHistory, reinterpret_cast(salt.data())); + setPassword(pw, reinterpret_cast(salt.data())); } error = exists && !HistoryKeeper::checkPassword(); diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index fdf7cc952..82e316aa4 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -47,9 +47,7 @@ HistoryKeeper *HistoryKeeper::getInstance() if (Settings::getInstance().getEnableLogging()) { - bool encrypted = Settings::getInstance().getEncryptLogs(); - - if (encrypted) + if (Nexus::getProfile()->isEncrypted()) { path = getHistoryPath(); dbIntf = new EncryptedDb(path, initLst); @@ -75,7 +73,7 @@ bool HistoryKeeper::checkPassword(int encrypted) if (!Settings::getInstance().getEnableLogging() && (encrypted == -1)) return true; - if ((encrypted == 1) || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) + if ((encrypted == 1) || (encrypted == -1 && Nexus::getProfile()->isEncrypted())) return EncryptedDb::check(getHistoryPath(Nexus::getProfile()->getName(), encrypted)); return true; @@ -365,7 +363,7 @@ QString HistoryKeeper::getHistoryPath(QString currentProfile, int encrypted) if (currentProfile.isEmpty()) currentProfile = Settings::getInstance().getCurrentProfile(); - if (encrypted == 1 || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) + if (encrypted == 1 || (encrypted == -1 && Nexus::getProfile()->isEncrypted())) return baseDir.filePath(currentProfile + ".qtox_history.encrypted"); else return baseDir.filePath(currentProfile + ".qtox_history"); diff --git a/src/main.cpp b/src/main.cpp index 515b44938..0bad8bd05 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,7 +118,7 @@ int main(int argc, char *argv[]) if (QDir(Settings::getSettingsDirPath()).exists(profileName + ".tox")) { qDebug() << "Setting profile to" << profileName; - if (Profile::isProfileEncrypted(profileName)) + if (Profile::isEncrypted(profileName)) { Settings::getInstance().setCurrentProfile(profileName); } diff --git a/src/misc/db/encrypteddb.cpp b/src/misc/db/encrypteddb.cpp index 96f49e285..f215225fe 100644 --- a/src/misc/db/encrypteddb.cpp +++ b/src/misc/db/encrypteddb.cpp @@ -85,7 +85,7 @@ bool EncryptedDb::pullFileContent(const QString &fname, QByteArray &buf) while (!dbFile.atEnd()) { QByteArray encrChunk = dbFile.read(encryptedChunkSize); - buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); + buf = Core::getInstance()->decryptData(encrChunk); if (buf.size() > 0) { fileContent += buf; @@ -130,7 +130,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql) { QByteArray filledChunk = buffer.left(plainChunkSize); encrFile.seek(chunkPosition * encryptedChunkSize); - QByteArray encr = Core::getInstance()->encryptData(filledChunk, Core::ptHistory); + QByteArray encr = Core::getInstance()->encryptData(filledChunk); if (encr.size() > 0) { encrFile.write(encr); @@ -142,7 +142,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql) } encrFile.seek(chunkPosition * encryptedChunkSize); - QByteArray encr = Core::getInstance()->encryptData(buffer, Core::ptHistory); + QByteArray encr = Core::getInstance()->encryptData(buffer); if (encr.size() > 0) encrFile.write(encr); @@ -158,7 +158,7 @@ bool EncryptedDb::check(const QString &fname) if (file.size() > 0) { QByteArray encrChunk = file.read(encryptedChunkSize); - QByteArray buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); + QByteArray buf = Core::getInstance()->decryptData(encrChunk); if (buf.size() == 0) state = false; } diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 052dedebd..e9316920b 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -249,8 +249,6 @@ void Settings::load() ps.beginGroup("Privacy"); typingNotification = ps.value("typingNotification", false).toBool(); enableLogging = ps.value("enableLogging", false).toBool(); - encryptLogs = ps.value("encryptLogs", false).toBool(); - encryptTox = ps.value("encryptTox", false).toBool(); ps.endGroup(); } } @@ -396,8 +394,6 @@ void Settings::savePersonal(QString path) ps.beginGroup("Privacy"); ps.setValue("typingNotification", typingNotification); ps.setValue("enableLogging", enableLogging); - ps.setValue("encryptLogs", encryptLogs); - ps.setValue("encryptTox", encryptTox); ps.endGroup(); } @@ -731,26 +727,6 @@ void Settings::setEnableLogging(bool newValue) enableLogging = newValue; } -bool Settings::getEncryptLogs() const -{ - return encryptLogs; -} - -void Settings::setEncryptLogs(bool newValue) -{ - encryptLogs = newValue; -} - -bool Settings::getEncryptTox() const -{ - return encryptTox; -} - -void Settings::setEncryptTox(bool newValue) -{ - encryptTox = newValue; -} - Db::syncType Settings::getDbSyncType() const { return dbSyncType; diff --git a/src/misc/settings.h b/src/misc/settings.h index 4341b709d..29155aa16 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -104,12 +104,6 @@ public: bool getEnableLogging() const; void setEnableLogging(bool newValue); - bool getEncryptLogs() const; - void setEncryptLogs(bool newValue); - - bool getEncryptTox() const; - void setEncryptTox(bool newValue); - Db::syncType getDbSyncType() const; void setDbSyncType(int newValue); @@ -303,8 +297,6 @@ private: uint32_t currentProfileId; bool enableLogging; - bool encryptLogs; - bool encryptTox = false; int autoAwayTime; diff --git a/src/nexus.cpp b/src/nexus.cpp index e83d2a21d..1a8817397 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -59,7 +59,6 @@ void Nexus::start() qRegisterMetaType("QPixmap"); qRegisterMetaType("ToxFile"); qRegisterMetaType("ToxFile::FileDirection"); - qRegisterMetaType("Core::PasswordType"); qRegisterMetaType>("std::shared_ptr"); loginScreen = new LoginScreen(); diff --git a/src/profile.cpp b/src/profile.cpp index a830c8c26..8d4db4470 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -176,9 +176,9 @@ QByteArray Profile::loadToxSave() uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast(data.data()), salt); - core->setPassword(password, Core::ptMain, salt); + core->setPassword(password, salt); - data = core->decryptData(data, Core::ptMain); + data = core->decryptData(data); if (data.isEmpty()) qCritical() << "Failed to decrypt the tox save file"; } @@ -218,8 +218,8 @@ void Profile::saveToxSave(QByteArray data) if (!password.isEmpty()) { - core->setPassword(password, Core::ptMain); - data = core->encryptData(data, Core::ptMain); + core->setPassword(password); + data = core->encryptData(data); if (data.isEmpty()) { qCritical() << "Failed to encrypt, can't save!"; @@ -239,7 +239,12 @@ bool Profile::profileExists(QString name) return QFile::exists(path+".tox") && QFile::exists(path+".ini"); } -bool Profile::isProfileEncrypted(QString name) +bool Profile::isEncrypted() +{ + return !password.isEmpty(); +} + +bool Profile::isEncrypted(QString name) { uint8_t data[encryptHeaderSize] = {0}; QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox"; @@ -303,3 +308,8 @@ bool Profile::checkPassword() return !loadToxSave().isEmpty(); } + +QString Profile::getPassword() +{ + return password; +} diff --git a/src/profile.h b/src/profile.h index f91ca6249..661b78ea0 100644 --- a/src/profile.h +++ b/src/profile.h @@ -26,7 +26,10 @@ public: void startCore(); ///< Starts the Core thread bool isNewProfile(); + bool isEncrypted(); ///< Returns true if we have a password set (doesn't check the actual file on disk) bool checkPassword(); ///< Checks whether the password is valid + QString getPassword(); + QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles. void saveToxSave(QByteArray data); ///< Write the .tox save, encrypted if needed. Invalid on deleted profiles. @@ -45,7 +48,7 @@ public: static QVector getProfiles(); static bool profileExists(QString name); - static bool isProfileEncrypted(QString name); ///< Returns false on error. + static bool isEncrypted(QString name); ///< Returns false on error. Checks the actual file on disk. private: Profile(QString name, QString password, bool newProfile); diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index f2d99d347..f3d70ae59 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -31,15 +31,8 @@ PrivacyForm::PrivacyForm() : bodyUI = new Ui::PrivacySettings; bodyUI->setupUi(this); - bodyUI->encryptToxHLayout->addStretch(); - bodyUI->encryptLogsHLayout->addStretch(); - connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated())); connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated())); - connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated())); - connect(bodyUI->changeLogsPwButton, &QPushButton::clicked, this, &PrivacyForm::setChatLogsPassword); - connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated())); - connect(bodyUI->changeToxPwButton, &QPushButton::clicked, this, &PrivacyForm::setToxPassword); connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam())); connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam())); connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit())); @@ -53,7 +46,6 @@ PrivacyForm::~PrivacyForm() void PrivacyForm::onEnableLoggingUpdated() { Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked()); - bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked()); HistoryKeeper::resetInstance(); Widget::getInstance()->clearAllReceipts(); } @@ -63,205 +55,6 @@ void PrivacyForm::onTypingNotificationEnabledUpdated() Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked()); } -bool PrivacyForm::setChatLogsPassword() -{ - Core* core = Core::getInstance(); - SetPasswordDialog* dialog; - - // check if an encrypted history exists because it was disabled earlier, and use it if possible - QString path = HistoryKeeper::getHistoryPath(QString(), 1); - QByteArray salt = core->getSaltFromFile(path); - bool haveEncHist = salt.size() > 0; - - QString body = tr("Please set your new chat history password."); - if (haveEncHist) - body += "\n\n" + tr("It appears you have an unused encrypted chat history; if the password matches, it will be added to your current history."); - - if (core->isPasswordSet(Core::ptMain)) - dialog = new SetPasswordDialog(body, tr("Use data file password", "pushbutton text"), 0); - else - dialog = new SetPasswordDialog(body, QString(), 0); - - do { - int r = dialog->exec(); - if (r == QDialog::Rejected) - break; - - QList oldMessages = HistoryKeeper::exportMessagesDeleteFile(); - - QString newpw = dialog->getPassword(); - - if (r == SetPasswordDialog::Tertiary) - core->useOtherPassword(Core::ptHistory); - else if (haveEncHist) - core->setPassword(newpw, Core::ptHistory, reinterpret_cast(salt.data())); - else - core->setPassword(newpw, Core::ptHistory); - - if (!haveEncHist || HistoryKeeper::checkPassword(1)) - { - Settings::getInstance().setEncryptLogs(true); - HistoryKeeper::getInstance()->importMessages(oldMessages); - if (haveEncHist) - { - Widget::getInstance()->reloadHistory(); - GUI::showWarning(tr("Successfully decrypted old chat history","popup title"), tr("You have succesfully decrypted the old chat history, and it has been added to your current history and re-encrypted.", "popup text")); - } - delete dialog; - return true; - } - else - { - if (GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("There is currently an unused encrypted chat history, but the password you just entered doesn't match.\n\nIf you don't care about the old history, you may delete it and use the password you just entered.\nOtherwise, hit Cancel to try again.", "This happens when enabling encryption after previously \"Disabling History\""), tr("Delete"), tr("Cancel"))) - { - if (GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("Are you absolutely sure you want to lose the unused encrypted chat history?", "secondary popup"), tr("Delete"), tr("Cancel"))) - haveEncHist = false; // logically this is really just a `break`, but conceptually this is more accurate - } - } - } while (haveEncHist); - - delete dialog; - return false; -} - -void PrivacyForm::onEncryptLogsUpdated() -{ - Core* core = Core::getInstance(); - - if (bodyUI->cbEncryptHistory->isChecked()) - { - if (!core->isPasswordSet(Core::ptHistory)) - { - if (setChatLogsPassword()) - { - bodyUI->cbEncryptHistory->setChecked(true); - bodyUI->changeLogsPwButton->setEnabled(true); - return; - } - } - } - else - { - QMessageBox box(QMessageBox::Warning, - tr("Old encrypted chat history", "title"), - tr("Would you like to decrypt your chat history?\nOtherwise it will be deleted."), - QMessageBox::NoButton, Widget::getInstance()); - QPushButton* decryptBtn = box.addButton(tr("Decrypt"), QMessageBox::YesRole); - QPushButton* deleteBtn = box.addButton(tr("Delete"), QMessageBox::NoRole); - QPushButton* cancelBtn = box.addButton(tr("Cancel"), QMessageBox::RejectRole); - box.setDefaultButton(cancelBtn); - box.setEscapeButton(cancelBtn); - - box.exec(); - - if (box.clickedButton() == decryptBtn) - { - QList oldMessages = HistoryKeeper::exportMessagesDeleteFile(true); - core->clearPassword(Core::ptHistory); - Settings::getInstance().setEncryptLogs(false); - HistoryKeeper::getInstance()->importMessages(oldMessages); - } - else if (box.clickedButton() == deleteBtn) - { - QMessageBox box2(QMessageBox::Critical, - tr("Old encrypted chat history", "title"), - tr("Are you sure you want to lose your entire chat history?"), - QMessageBox::NoButton, Widget::getInstance()); - QPushButton* deleteBtn2 = box2.addButton(tr("Delete"), QMessageBox::AcceptRole); - QPushButton* cancelBtn2 = box2.addButton(tr("Cancel"), QMessageBox::RejectRole); - box2.setDefaultButton(cancelBtn2); - box2.setEscapeButton(cancelBtn2); - box2.exec(); - - if (box2.clickedButton() == deleteBtn2) - { - HistoryKeeper::removeHistory(true); - } - else - { - bodyUI->cbEncryptHistory->setChecked(true); - return; - } - } - else - { - bodyUI->cbEncryptHistory->setChecked(true); - return; - } - } - - core->clearPassword(Core::ptHistory); - Settings::getInstance().setEncryptLogs(false); - bodyUI->cbEncryptHistory->setChecked(false); - bodyUI->changeLogsPwButton->setEnabled(false); - HistoryKeeper::resetInstance(); -} - -bool PrivacyForm::setToxPassword() -{ - Core* core = Core::getInstance(); - SetPasswordDialog* dialog; - QString body = tr("Please set your new data file password."); - if (core->isPasswordSet(Core::ptHistory)) - dialog = new SetPasswordDialog(body, tr("Use chat history password", "pushbutton text"), 0); - else - dialog = new SetPasswordDialog(body, QString(), 0); - - if (int r = dialog->exec()) - { - QString newpw = dialog->getPassword(); - delete dialog; - - if (r == SetPasswordDialog::Tertiary) - core->useOtherPassword(Core::ptMain); - else - core->setPassword(newpw, Core::ptMain); - - Settings::getInstance().setEncryptTox(true); - Settings::getInstance().save(); - return true; - } - else - { - delete dialog; - return false; - } -} - -void PrivacyForm::onEncryptToxUpdated() -{ - Core* core = Core::getInstance(); - - if (bodyUI->cbEncryptTox->isChecked()) - { - if (!core->isPasswordSet(Core::ptMain)) - { - if (setToxPassword()) - { - bodyUI->cbEncryptTox->setChecked(true); - bodyUI->changeToxPwButton->setEnabled(true); - return; - } - } - } - else - { - if (!GUI::askQuestion(tr("Decrypt your data file", "title"), - tr("Would you like to decrypt your data file?"), - tr("Decrypt"), tr("Cancel"))) - { - bodyUI->cbEncryptTox->setChecked(true); - return; - } - // affirmative answer falls through to the catch all below - } - - bodyUI->cbEncryptTox->setChecked(false); - Settings::getInstance().setEncryptTox(false); - bodyUI->changeToxPwButton->setEnabled(false); - core->clearPassword(Core::ptMain); -} - void PrivacyForm::setNospam() { QString newNospam = bodyUI->nospamLineEdit->text(); @@ -277,11 +70,6 @@ void PrivacyForm::present() bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam); bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled()); bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); - bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs()); - bodyUI->changeLogsPwButton->setEnabled(Settings::getInstance().getEncryptLogs()); - bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging()); - bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox()); - bodyUI->changeToxPwButton->setEnabled(Settings::getInstance().getEncryptTox()); } void PrivacyForm::generateRandomNospam() diff --git a/src/widget/form/settings/privacyform.h b/src/widget/form/settings/privacyform.h index efcb3492b..ab88acd58 100644 --- a/src/widget/form/settings/privacyform.h +++ b/src/widget/form/settings/privacyform.h @@ -36,10 +36,6 @@ private slots: void setNospam(); void generateRandomNospam(); void onNospamEdit(); - void onEncryptLogsUpdated(); - bool setChatLogsPassword(); - void onEncryptToxUpdated(); - bool setToxPassword(); private: Ui::PrivacySettings* bodyUI; diff --git a/src/widget/form/settings/privacysettings.ui b/src/widget/form/settings/privacysettings.ui index d80cc1a78..dc8394921 100644 --- a/src/widget/form/settings/privacysettings.ui +++ b/src/widget/form/settings/privacysettings.ui @@ -37,7 +37,7 @@ 0 0 380 - 391 + 280 @@ -47,7 +47,7 @@ Your friends will be able to see when you are typing. - Send Typing Notifications + Send typing notifications @@ -58,89 +58,32 @@ Save format changes are possible, which may result in data loss. - Keep chat history (mostly stable) + Keep chat history - - - true + + + Nospam is part of your Tox ID. +It is there to help you change your Tox ID when you feel like you are getting too much spam friend requests. +When you change nospam, your current contacts still can communicate with you, +but new contacts need to know your new Tox ID to be able to add you. - Local file encryption + NoSpam - + - + - All Tox communications over the internet are encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files. + The NoSpam is part of your Tox ID, if you are getting spammed with friend requests, change the NoSpam. true - - - - - - true - - - Encrypt Tox data file - - - - - - - Change password - - - - - - - - - - - true - - - Encrypt chat history - - - true - - - - - - - Change password - - - - - - - - - - - - Nospam is part of your Tox ID. -It is there to help you change your Tox ID when you feel like you are getting too much spam friend requests. -When you change nospam, your current contacts still can communicate with you, -but new contacts need to know your new Tox ID to be able to add you. - - - Nospam - - @@ -153,7 +96,7 @@ but new contacts need to know your new Tox ID to be able to add you. - Generate random nospam + Generate random NoSpam diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 971660e7e..7c6e4f143 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -108,7 +108,7 @@ void LoginScreen::onLoginUsernameSelected(const QString &name) return; ui->loginPassword->clear(); - if (Profile::isProfileEncrypted(name)) + if (Profile::isEncrypted(name)) { ui->loginPasswordLabel->show(); ui->loginPassword->show(); From 10ce65c22be84ff40d94e2fc911a4819c0e3b35d Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 15:22:14 +0200 Subject: [PATCH 23/34] Reject passwords shorter than 6 characters --- src/widget/loginscreen.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 7c6e4f143..f94ae86c8 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -76,6 +76,12 @@ void LoginScreen::onCreateNewProfile() return; } + if (pass.size() < 6) + { + QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The password must be at least 6 characters.")); + return; + } + if (ui->newPassConfirm->text() != pass) { QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The passwords are different.")); From a03eea9b5fbd243cab601583bc16d1fc6020ead0 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 15:27:22 +0200 Subject: [PATCH 24/34] Don't allow overwriting on profile rename --- src/widget/form/profileform.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index aa7f65bb8..264fab7e2 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -237,16 +237,14 @@ void ProfileForm::onRenameClicked() if (name.isEmpty()) break; name = Core::sanitize(name); - if (!Profile::profileExists(name) || GUI::askQuestion(tr("Profile already exists", "rename confirm title"), - tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(name))) - { - - - if (!nexus.getProfile()->rename(name)) - GUI::showError(tr("Failed to rename", "rename failed title"), - tr("Couldn't rename the profile to \"%1\"").arg(cur)); + if (Profile::profileExists(name)) + GUI::showError(tr("Profile already exists", "rename failure title"), + tr("A profile named \"%1\" already exists.", "rename confirm text").arg(name)); + else if (!nexus.getProfile()->rename(name)) + GUI::showError(tr("Failed to rename", "rename failed title"), + tr("Couldn't rename the profile to \"%1\"").arg(cur)); + else break; - } } while (true); } From 2dbf2e54b14e26ee812c889c21e8ec5d2182e30a Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 16:36:08 +0200 Subject: [PATCH 25/34] Implement restarting toxcore --- src/core/core.cpp | 27 ++++++++++++++++++++---- src/core/core.h | 4 ++-- src/nexus.cpp | 1 - src/profile.cpp | 10 +++++++++ src/profile.h | 1 + src/widget/form/settings/generalform.cpp | 4 +++- src/widget/gui.cpp | 16 ++++++++++++++ src/widget/gui.h | 3 +++ src/widget/widget.cpp | 2 ++ src/widget/widget.h | 2 +- 10 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 2567d82e1..96b6877cd 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -105,9 +105,10 @@ Core::~Core() if (coreThread->isRunning()) { if (QThread::currentThread() == coreThread) - killTimers(); + killTimers(false); else - QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection, + Q_ARG(bool, false)); } coreThread->exit(0); while (coreThread->isRunning()) @@ -1245,9 +1246,27 @@ void Core::resetCallSources() } } -void Core::killTimers() +void Core::killTimers(bool onlyStop) { assert(QThread::currentThread() == coreThread); toxTimer->stop(); - delete toxTimer; + if (!onlyStop) + { + delete toxTimer; + toxTimer = nullptr; + } +} + +void Core::reset() +{ + assert(QThread::currentThread() == coreThread); + + ready = false; + killTimers(true); + deadifyTox(); + + emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg")); + GUI::clearContacts(); + + start(); } diff --git a/src/core/core.h b/src/core/core.h index 9160f08d3..40450c15a 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -87,6 +87,7 @@ public: public slots: void start(); ///< Initializes the core, must be called before anything else + void reset(); ///< Reinitialized the core. Must be called from the Core thread, with the GUI thread ready to process events. void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer void bootstrapDht(); ///< Connects us to the Tox network @@ -151,7 +152,6 @@ public slots: signals: void connected(); void disconnected(); - void blockingClearContacts(); void friendRequestReceived(const QString& userId, const QString& message); void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction); @@ -283,7 +283,7 @@ private: void deadifyTox(); private slots: - void killTimers(); ///< Must only be called from the Core thread + void killTimers(bool onlyStop); ///< Must only be called from the Core thread private: Tox* tox; diff --git a/src/nexus.cpp b/src/nexus.cpp index 1a8817397..3931a0dc4 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -157,7 +157,6 @@ void Nexus::showMainGUI() connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying); connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated); connect(core, &Core::avInvite, widget, &Widget::playRingtone); - connect(core, &Core::blockingClearContacts, widget, &Widget::clearContactsList, Qt::BlockingQueuedConnection); connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged); connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult); diff --git a/src/profile.cpp b/src/profile.cpp index 8d4db4470..2fa0cf52b 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -3,6 +3,7 @@ #include "src/misc/settings.h" #include "src/core/core.h" #include "src/historykeeper.h" +#include "src/widget/gui.h" #include #include #include @@ -17,6 +18,7 @@ Profile::Profile(QString name, QString password, bool isNewProfile) newProfile{isNewProfile}, isRemoved{false} { Settings::getInstance().setCurrentProfile(name); + HistoryKeeper::resetInstance(); coreThread = new QThread(); coreThread->setObjectName("qTox Core"); @@ -313,3 +315,11 @@ QString Profile::getPassword() { return password; } + +void Profile::restartCore() +{ + GUI::setEnabled(false); // Core::reset re-enables it + if (!isRemoved && core->isReady()) + saveToxSave(); + QMetaObject::invokeMethod(core, "reset"); +} diff --git a/src/profile.h b/src/profile.h index 661b78ea0..75b323bb0 100644 --- a/src/profile.h +++ b/src/profile.h @@ -25,6 +25,7 @@ public: QString getName(); void startCore(); ///< Starts the Core thread + void restartCore(); ///< Delete core and restart a new one bool isNewProfile(); bool isEncrypted(); ///< Returns true if we have a password set (doesn't check the actual file on disk) bool checkPassword(); ///< Checks whether the password is valid diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 067f5edf6..294fd3245 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -20,6 +20,8 @@ #include "src/misc/smileypack.h" #include "src/core/core.h" #include "src/misc/style.h" +#include "src/nexus.h" +#include "src/profile.h" #include #include #include @@ -351,7 +353,7 @@ void GeneralForm::onReconnectClicked() QMessageBox::warning(this, tr("Call active", "popup title"), tr("You can't disconnect while a call is active!", "popup text")); else - ; /// TODO: Add a reset function in Profile to save then restart toxcore + Nexus::getProfile()->restartCore(); } void GeneralForm::reloadSmiles() diff --git a/src/widget/gui.cpp b/src/widget/gui.cpp index 4256d4c59..7c4ccc9bd 100644 --- a/src/widget/gui.cpp +++ b/src/widget/gui.cpp @@ -35,6 +35,14 @@ GUI& GUI::getInstance() // Implementation of the public clean interface +void GUI::clearContacts() +{ + if (QThread::currentThread() == qApp->thread()) + getInstance()._clearContacts(); + else + QMetaObject::invokeMethod(&getInstance(), "_clearContacts", Qt::BlockingQueuedConnection); +} + void GUI::setEnabled(bool state) { if (QThread::currentThread() == qApp->thread()) @@ -192,6 +200,14 @@ QString GUI::passwordDialog(const QString& cancel, const QString& body) // Private implementations +void GUI::_clearContacts() +{ +#ifdef Q_OS_ANDROID +#else + Nexus::getDesktopGUI()->clearContactsList(); +#endif +} + void GUI::_setEnabled(bool state) { #ifdef Q_OS_ANDROID diff --git a/src/widget/gui.h b/src/widget/gui.h index 05cc0d3e6..a7c0d34db 100644 --- a/src/widget/gui.h +++ b/src/widget/gui.h @@ -15,6 +15,8 @@ public: static GUI& getInstance(); /// Returns the main QWidget* of the application static QWidget* getMainWidget(); + /// Clear the GUI's contact list + static void clearContacts(); /// Will enable or disable the GUI. /// A disabled GUI can't be interacted with by the user static void setEnabled(bool state); @@ -64,6 +66,7 @@ private: // Private implementation, those must be called from the GUI thread private slots: + void _clearContacts(); void _setEnabled(bool state); void _setWindowTitle(const QString& title); void _reloadTheme(); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index f1f197b8b..d0924da7f 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -898,6 +898,8 @@ void Widget::removeFriend(int friendId) void Widget::clearContactsList() { + assert(QThread::currentThread() == qApp->thread()); + QList friends = FriendList::getAllFriends(); for (Friend* f : friends) removeFriend(f, true); diff --git a/src/widget/widget.h b/src/widget/widget.h index 09ec716e2..1948a18bc 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -70,9 +70,9 @@ public: void newMessageAlert(GenericChatroomWidget* chat); bool isFriendWidgetCurActiveWidget(Friend* f); bool getIsWindowMinimized(); - void clearContactsList(); void setTranslation(); void updateIcons(); + void clearContactsList(); ~Widget(); virtual void closeEvent(QCloseEvent *event); From 0642c1b0d748a7d813ada969b683c8d24749c57a Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 17:09:13 +0200 Subject: [PATCH 26/34] Add button to delete password --- src/profile.cpp | 15 +++++++++++ src/profile.h | 1 + src/widget/form/profileform.cpp | 23 +++++++++++++++++ src/widget/form/profileform.h | 2 ++ src/widget/form/profileform.ui | 46 ++++++++++++++++++++++++++++++++- 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/profile.cpp b/src/profile.cpp index 2fa0cf52b..6b4f41b15 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -4,6 +4,9 @@ #include "src/core/core.h" #include "src/historykeeper.h" #include "src/widget/gui.h" +#include "src/widget/widget.h" +#include "src/widget/form/setpassworddialog.h" +#include "src/nexus.h" #include #include #include @@ -323,3 +326,15 @@ void Profile::restartCore() saveToxSave(); QMetaObject::invokeMethod(core, "reset"); } + +void Profile::setPassword(QString newPassword) +{ + QList oldMessages = HistoryKeeper::exportMessagesDeleteFile(); + + password = newPassword; + core->setPassword(password); + saveToxSave(); + + HistoryKeeper::getInstance()->importMessages(oldMessages); + Nexus::getDesktopGUI()->reloadHistory(); +} diff --git a/src/profile.h b/src/profile.h index 75b323bb0..5c5f3c8e6 100644 --- a/src/profile.h +++ b/src/profile.h @@ -30,6 +30,7 @@ public: bool isEncrypted(); ///< Returns true if we have a password set (doesn't check the actual file on disk) bool checkPassword(); ///< Checks whether the password is valid QString getPassword(); + void setPassword(QString newPassword); ///< Changes the encryption password and re-saves everything with it QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles. diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 264fab7e2..4709ca2d1 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -94,6 +94,8 @@ ProfileForm::ProfileForm(QWidget *parent) : connect(bodyUI->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked); connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked); connect(bodyUI->logoutButton, &QPushButton::clicked, this, &ProfileForm::onLogoutClicked); + connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::onDeletePassClicked); + connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::onChangePassClicked); connect(core, &Core::usernameSet, this, [=](const QString& val) { bodyUI->userName->setText(val); }); connect(core, &Core::statusMessageSet, this, [=](const QString& val) { bodyUI->statusMessage->setText(val); }); @@ -306,3 +308,24 @@ void ProfileForm::onSaveQrClicked() GUI::showWarning(tr("Failed to copy file"), tr("The file you chose could not be written to.")); } } + +void ProfileForm::onDeletePassClicked() +{ + Profile* pro = Nexus::getProfile(); + if (!pro->isEncrypted()) + { + GUI::showInfo(tr("Nothing to remove"), tr("Your profile does not have a password!")); + return; + } + + if (!GUI::askQuestion(tr("Really delete password?","deletion confirmation title"), + tr("Are you sure you want to delete your password?","deletion confirmation text"))) + return; + + Nexus::getProfile()->setPassword(QString()); +} + +void ProfileForm::onChangePassClicked() +{ + +} diff --git a/src/widget/form/profileform.h b/src/widget/form/profileform.h index e4828b9a2..60d5da37e 100644 --- a/src/widget/form/profileform.h +++ b/src/widget/form/profileform.h @@ -69,6 +69,8 @@ private slots: void onLogoutClicked(); void onCopyQrClicked(); void onSaveQrClicked(); + void onDeletePassClicked(); + void onChangePassClicked(); private: void refreshProfiles(); diff --git a/src/widget/form/profileform.ui b/src/widget/form/profileform.ui index f02ae7155..76eb4dd3d 100644 --- a/src/widget/form/profileform.ui +++ b/src/widget/form/profileform.ui @@ -37,7 +37,7 @@ - -3 + -41 0 630 625 @@ -234,6 +234,50 @@ Profile does not contain your history. + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete password + + + + + + + Change password + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + From 67e85b3ddc6fe08ada413e183a649e094255ae4b Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 17:45:20 +0200 Subject: [PATCH 27/34] Add change password button --- src/profile.cpp | 1 - src/widget/form/profileform.cpp | 7 +++++++ src/widget/form/profileform.ui | 4 ++-- src/widget/form/setpassworddialog.cpp | 16 +++++++++++----- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/profile.cpp b/src/profile.cpp index 6b4f41b15..abc4eef1c 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -5,7 +5,6 @@ #include "src/historykeeper.h" #include "src/widget/gui.h" #include "src/widget/widget.h" -#include "src/widget/form/setpassworddialog.h" #include "src/nexus.h" #include #include diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 4709ca2d1..252980a07 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -19,6 +19,7 @@ #include "ui_mainwindow.h" #include "src/widget/form/settingswidget.h" #include "src/widget/maskablepixmapwidget.h" +#include "src/widget/form/setpassworddialog.h" #include "src/misc/settings.h" #include "src/widget/croppinglabel.h" #include "src/widget/widget.h" @@ -327,5 +328,11 @@ void ProfileForm::onDeletePassClicked() void ProfileForm::onChangePassClicked() { + SetPasswordDialog* dialog = new SetPasswordDialog(tr("Please enter a new password."), QString(), 0); + int r = dialog->exec(); + if (r == QDialog::Rejected) + return; + QString newPass = dialog->getPassword(); + Nexus::getProfile()->setPassword(newPass); } diff --git a/src/widget/form/profileform.ui b/src/widget/form/profileform.ui index 76eb4dd3d..2149f2779 100644 --- a/src/widget/form/profileform.ui +++ b/src/widget/form/profileform.ui @@ -37,7 +37,7 @@ - -41 + 0 0 630 625 @@ -252,7 +252,7 @@ Profile does not contain your history. - Delete password + Remove password diff --git a/src/widget/form/setpassworddialog.cpp b/src/widget/form/setpassworddialog.cpp index da8cc3c22..56b7e853a 100644 --- a/src/widget/form/setpassworddialog.cpp +++ b/src/widget/form/setpassworddialog.cpp @@ -48,15 +48,21 @@ void SetPasswordDialog::onPasswordEdit() { QString pswd = ui->passwordlineEdit->text(); - if (pswd == ui->repasswordlineEdit->text() && pswd.length() > 0) + + if (pswd.length() < 6) { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - ui->body->setText(body); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->body->setText(body + tr("The password is too short")); } - else + else if (pswd != ui->repasswordlineEdit->text()) { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->body->setText(body + tr("The passwords don't match.")); + ui->body->setText(body + tr("The password doesn't match.")); + } + else + { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + ui->body->setText(body); } // Password strength calculator From ea45d4617ffda5c8e492d97de6c4411e73875bdf Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 18:41:58 +0200 Subject: [PATCH 28/34] Allow creating unencrypted accounts again --- src/widget/loginscreen.cpp | 2 +- src/widget/loginscreen.ui | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index f94ae86c8..8f32b2b35 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -76,7 +76,7 @@ void LoginScreen::onCreateNewProfile() return; } - if (pass.size() < 6) + if (pass.size()!=0 && pass.size() < 6) { QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The password must be at least 6 characters.")); return; diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 017245aa4..dbb55724e 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -375,9 +375,6 @@ margin-bottom:45px; - - 64 - QLineEdit::Password From ba0bc12ac2e28e97525952147abc5ddef66ac384 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 18:51:27 +0200 Subject: [PATCH 29/34] Fix loginscreen not resetting pass confirm --- src/widget/loginscreen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 8f32b2b35..8d6191dfb 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -35,6 +35,7 @@ void LoginScreen::reset() { ui->newUsername->clear(); ui->newPass->clear(); + ui->newPassConfirm->clear(); ui->loginPassword->clear(); ui->loginUsernames->clear(); From e5ddd9d0cfee10c4e976c455ef1753ef14b35a08 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 18:51:57 +0200 Subject: [PATCH 30/34] Consider empty encrypted history as nonexistent --- src/core/coreencryption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index 199c4ad17..3e0f4e7bb 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -122,7 +122,7 @@ QByteArray Core::getSaltFromFile(QString filename) void Core::checkEncryptedHistory() { QString path = HistoryKeeper::getHistoryPath(); - bool exists = QFile::exists(path); + bool exists = QFile::exists(path) && QFile(path).size()>0; QByteArray salt = getSaltFromFile(path); if (exists && salt.size() == 0) From aa9e500e229ac5e8fd0caa63a430feaf033b5149 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 18:56:22 +0200 Subject: [PATCH 31/34] Allow long (32k chars) passwords --- src/widget/loginscreen.ui | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index dbb55724e..2e59efe9b 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -721,9 +721,6 @@ margin-bottom:5px; - - 64 - QLineEdit::Password From 0ebaef3c347b0a65539d93161d1cc93eb6c85f5c Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 19:21:22 +0200 Subject: [PATCH 32/34] Password strenght meter on login screen --- src/widget/form/setpassworddialog.cpp | 7 +- src/widget/form/setpassworddialog.h | 1 + src/widget/loginscreen.cpp | 8 +++ src/widget/loginscreen.h | 1 + src/widget/loginscreen.ui | 93 +++++++++++++++++---------- 5 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/widget/form/setpassworddialog.cpp b/src/widget/form/setpassworddialog.cpp index 56b7e853a..5d2f086c1 100644 --- a/src/widget/form/setpassworddialog.cpp +++ b/src/widget/form/setpassworddialog.cpp @@ -65,6 +65,11 @@ void SetPasswordDialog::onPasswordEdit() ui->body->setText(body); } + ui->strengthBar->setValue(getPasswordStrength(pswd)); +} + +int SetPasswordDialog::getPasswordStrength(QString pswd) +{ // Password strength calculator // Based on code in the Master Password dialog in Firefox // (pref-masterpass.js) @@ -98,7 +103,7 @@ void SetPasswordDialog::onPasswordEdit() if (pwstrength > 100) pwstrength = 100; - ui->strengthBar->setValue(pwstrength); + return pwstrength; } QString SetPasswordDialog::getPassword() diff --git a/src/widget/form/setpassworddialog.h b/src/widget/form/setpassworddialog.h index d4d348271..91c34f130 100644 --- a/src/widget/form/setpassworddialog.h +++ b/src/widget/form/setpassworddialog.h @@ -30,6 +30,7 @@ public: explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0); ~SetPasswordDialog(); QString getPassword(); + static int getPasswordStrength(QString password); private slots: void onPasswordEdit(); diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 8d6191dfb..cacfcecc5 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -4,6 +4,7 @@ #include "src/profilelocker.h" #include "src/nexus.h" #include "src/misc/settings.h" +#include "src/widget/form/setpassworddialog.h" #include #include @@ -22,6 +23,8 @@ LoginScreen::LoginScreen(QWidget *parent) : connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin); connect(ui->loginUsernames, &QComboBox::currentTextChanged, this, &LoginScreen::onLoginUsernameSelected); connect(ui->loginPassword, &QLineEdit::returnPressed, this, &LoginScreen::onLogin); + connect(ui->newPass, &QLineEdit::textChanged, this, &LoginScreen::onPasswordEdited); + connect(ui->newPassConfirm, &QLineEdit::textChanged, this, &LoginScreen::onPasswordEdited); reset(); } @@ -157,3 +160,8 @@ void LoginScreen::onLogin() nexus.setProfile(profile); nexus.showMainGUI(); } + +void LoginScreen::onPasswordEdited() +{ + ui->passStrengthMeter->setValue(SetPasswordDialog::getPasswordStrength(ui->newPass->text())); +} diff --git a/src/widget/loginscreen.h b/src/widget/loginscreen.h index a08be048a..3868dd6db 100644 --- a/src/widget/loginscreen.h +++ b/src/widget/loginscreen.h @@ -18,6 +18,7 @@ public: private slots: void onLoginUsernameSelected(const QString& name); + void onPasswordEdited(); // Buttons to change page void onNewProfilePageClicked(); void onLoginPageClicked(); diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 2e59efe9b..229ce8b0c 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -317,9 +317,24 @@ margin-bottom:45px; - - - 6 + + + QLayout::SetMaximumSize + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + Qt::AlignCenter + + + 9 + + + 9 9 @@ -333,20 +348,7 @@ margin-bottom:45px; 9 - - - - Qt::Vertical - - - - 20 - 40 - - - - - + Username: @@ -356,14 +358,14 @@ margin-bottom:45px; - + 64 - + Password: @@ -373,44 +375,41 @@ margin-bottom:45px; - + QLineEdit::Password - + - Confirm password: + Confirm: Qt::AlignCenter - + QLineEdit::Password - - - - Qt::Vertical + + + + 0 - - - 20 - 40 - + + Password strength: %p% - + - + @@ -624,6 +623,32 @@ background-color:#6cc865; + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + From fdcd02980d32f1c152cc393c433916a87c5f7f02 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 19:53:27 +0200 Subject: [PATCH 33/34] New password strengh function --- src/widget/form/setpassworddialog.cpp | 60 +++++++++++---------------- src/widget/form/setpassworddialog.h | 2 +- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/widget/form/setpassworddialog.cpp b/src/widget/form/setpassworddialog.cpp index 5d2f086c1..0de9e99ac 100644 --- a/src/widget/form/setpassworddialog.cpp +++ b/src/widget/form/setpassworddialog.cpp @@ -68,42 +68,32 @@ void SetPasswordDialog::onPasswordEdit() ui->strengthBar->setValue(getPasswordStrength(pswd)); } -int SetPasswordDialog::getPasswordStrength(QString pswd) +int SetPasswordDialog::getPasswordStrength(QString pass) { - // Password strength calculator - // Based on code in the Master Password dialog in Firefox - // (pref-masterpass.js) - // Original code triple-licensed under the MPL, GPL, and LGPL - // so is license-compatible with this file - - const double lengthFactor = reasonablePasswordLength / 8.0; - int pwlength = (int)(pswd.length() / lengthFactor); - if (pwlength > 5) - pwlength = 5; - - const QRegExp numRxp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp); - int numeric = (int)(pswd.count(numRxp) / lengthFactor); - if (numeric > 3) - numeric = 3; - - const QRegExp symbRxp("\\W", Qt::CaseInsensitive, QRegExp::RegExp); - int numsymbols = (int)(pswd.count(symbRxp) / lengthFactor); - if (numsymbols > 3) - numsymbols = 3; - - const QRegExp upperRxp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp); - int upper = (int)(pswd.count(upperRxp) / lengthFactor); - if (upper > 3) - upper = 3; - - int pwstrength=((pwlength*10)-20) + (numeric*10) + (numsymbols*15) + (upper*10); - if (pwstrength < 0) - pwstrength = 0; - - if (pwstrength > 100) - pwstrength = 100; - - return pwstrength; + if (pass.size() < 6) + return 0; + + double fscore = 0; + QHash charCounts; + for (QChar c : pass) + { + charCounts[c]++; + fscore += 5. / charCounts[c]; + } + + int variations = -1; + variations += pass.contains(QRegExp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; + variations += pass.contains(QRegExp("[a-z]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; + variations += pass.contains(QRegExp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; + variations += pass.contains(QRegExp("[\\W]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0; + + int score = fscore; + score += variations * 10; + score -= 20; + score = std::min(score, 100); + score = std::max(score, 0); + + return score; } QString SetPasswordDialog::getPassword() diff --git a/src/widget/form/setpassworddialog.h b/src/widget/form/setpassworddialog.h index 91c34f130..ae73b8879 100644 --- a/src/widget/form/setpassworddialog.h +++ b/src/widget/form/setpassworddialog.h @@ -30,7 +30,7 @@ public: explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0); ~SetPasswordDialog(); QString getPassword(); - static int getPasswordStrength(QString password); + static int getPasswordStrength(QString pass); private slots: void onPasswordEdit(); From b9febaa27da796806044383ba586f6b983ad8067 Mon Sep 17 00:00:00 2001 From: tux3 Date: Thu, 4 Jun 2015 19:56:04 +0200 Subject: [PATCH 34/34] Update login logo image --- img/login_logo.png | Bin 24304 -> 0 bytes img/login_logo.svg | 1 + res.qrc | 2 +- src/widget/loginscreen.ui | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 img/login_logo.png create mode 100644 img/login_logo.svg diff --git a/img/login_logo.png b/img/login_logo.png deleted file mode 100644 index b74304e1e3a66856bdbb50991643eb16eafbcdaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24304 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

!Ud+ERtkVY!kSsWO*h`zc?TvBVPlv~RQ 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 \ No newline at end of file diff --git a/res.qrc b/res.qrc index 7b5968a27..220dd9fd5 100644 --- a/res.qrc +++ b/res.qrc @@ -119,6 +119,6 @@ ui/acceptCall/acceptCall.svg ui/rejectCall/rejectCall.svg ui/volButton/volButtonDisabled.png - img/login_logo.png + img/login_logo.svg diff --git a/src/widget/loginscreen.ui b/src/widget/loginscreen.ui index 229ce8b0c..d4ea6f785 100644 --- a/src/widget/loginscreen.ui +++ b/src/widget/loginscreen.ui @@ -1095,7 +1095,7 @@ background-color:#6cc865; - :/img/login_logo.png + :/img/login_logo.svg true