connection_box.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/connection_box.h"
  8. #include "base/call_delayed.h"
  9. #include "base/qthelp_regex.h"
  10. #include "base/qthelp_url.h"
  11. #include "core/application.h"
  12. #include "core/core_settings.h"
  13. #include "core/local_url_handlers.h"
  14. #include "lang/lang_keys.h"
  15. #include "main/main_account.h"
  16. #include "mtproto/facade.h"
  17. #include "storage/localstorage.h"
  18. #include "ui/basic_click_handlers.h"
  19. #include "ui/boxes/confirm_box.h"
  20. #include "ui/effects/animations.h"
  21. #include "ui/effects/radial_animation.h"
  22. #include "ui/painter.h"
  23. #include "ui/text/text_options.h"
  24. #include "ui/text/text_utilities.h"
  25. #include "ui/toast/toast.h"
  26. #include "ui/widgets/buttons.h"
  27. #include "ui/widgets/checkbox.h"
  28. #include "ui/widgets/dropdown_menu.h"
  29. #include "ui/widgets/fields/input_field.h"
  30. #include "ui/widgets/fields/number_input.h"
  31. #include "ui/widgets/fields/password_input.h"
  32. #include "ui/widgets/labels.h"
  33. #include "ui/widgets/popup_menu.h"
  34. #include "ui/wrap/slide_wrap.h"
  35. #include "ui/wrap/vertical_layout.h"
  36. #include "ui/vertical_list.h"
  37. #include "ui/ui_utility.h"
  38. #include "boxes/abstract_box.h" // Ui::show().
  39. #include "window/window_session_controller.h"
  40. #include "styles/style_layers.h"
  41. #include "styles/style_boxes.h"
  42. #include "styles/style_chat_helpers.h"
  43. #include "styles/style_info.h"
  44. #include "styles/style_menu_icons.h"
  45. #include <QtGui/QGuiApplication>
  46. #include <QtGui/QClipboard>
  47. namespace {
  48. constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
  49. using ProxyData = MTP::ProxyData;
  50. [[nodiscard]] ProxyData ProxyDataFromFields(
  51. ProxyData::Type type,
  52. const QMap<QString, QString> &fields) {
  53. auto proxy = ProxyData();
  54. proxy.type = type;
  55. proxy.host = fields.value(u"server"_q);
  56. proxy.port = fields.value(u"port"_q).toUInt();
  57. if (type == ProxyData::Type::Socks5) {
  58. proxy.user = fields.value(u"user"_q);
  59. proxy.password = fields.value(u"pass"_q);
  60. } else if (type == ProxyData::Type::Mtproto) {
  61. proxy.password = fields.value(u"secret"_q);
  62. }
  63. return proxy;
  64. };
  65. class HostInput : public Ui::MaskedInputField {
  66. public:
  67. HostInput(
  68. QWidget *parent,
  69. const style::InputField &st,
  70. rpl::producer<QString> placeholder,
  71. const QString &val);
  72. protected:
  73. void correctValue(
  74. const QString &was,
  75. int wasCursor,
  76. QString &now,
  77. int &nowCursor) override;
  78. };
  79. HostInput::HostInput(
  80. QWidget *parent,
  81. const style::InputField &st,
  82. rpl::producer<QString> placeholder,
  83. const QString &val)
  84. : MaskedInputField(parent, st, std::move(placeholder), val) {
  85. }
  86. void HostInput::correctValue(
  87. const QString &was,
  88. int wasCursor,
  89. QString &now,
  90. int &nowCursor) {
  91. QString newText;
  92. int newCursor = nowCursor;
  93. newText.reserve(now.size());
  94. for (auto i = 0, l = int(now.size()); i < l; ++i) {
  95. if (now[i] == ',') {
  96. newText.append('.');
  97. } else {
  98. newText.append(now[i]);
  99. }
  100. }
  101. setCorrectedText(now, nowCursor, newText, newCursor);
  102. }
  103. class Base64UrlInput : public Ui::MaskedInputField {
  104. public:
  105. Base64UrlInput(
  106. QWidget *parent,
  107. const style::InputField &st,
  108. rpl::producer<QString> placeholder,
  109. const QString &val);
  110. protected:
  111. void correctValue(
  112. const QString &was,
  113. int wasCursor,
  114. QString &now,
  115. int &nowCursor) override;
  116. };
  117. Base64UrlInput::Base64UrlInput(
  118. QWidget *parent,
  119. const style::InputField &st,
  120. rpl::producer<QString> placeholder,
  121. const QString &val)
  122. : MaskedInputField(parent, st, std::move(placeholder), val) {
  123. static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$");
  124. if (!RegExp.match(val).hasMatch()) {
  125. setText(QString());
  126. }
  127. }
  128. void Base64UrlInput::correctValue(
  129. const QString &was,
  130. int wasCursor,
  131. QString &now,
  132. int &nowCursor) {
  133. QString newText;
  134. newText.reserve(now.size());
  135. auto newPos = nowCursor;
  136. for (auto i = 0, l = int(now.size()); i < l; ++i) {
  137. const auto ch = now[i];
  138. if ((ch >= '0' && ch <= '9')
  139. || (ch >= 'a' && ch <= 'z')
  140. || (ch >= 'A' && ch <= 'Z')
  141. || (ch == '-')
  142. || (ch == '_')) {
  143. newText.append(ch);
  144. } else if (i < nowCursor) {
  145. --newPos;
  146. }
  147. }
  148. setCorrectedText(now, nowCursor, newText, newPos);
  149. }
  150. class ProxyRow : public Ui::RippleButton {
  151. public:
  152. using View = ProxiesBoxController::ItemView;
  153. using State = ProxiesBoxController::ItemState;
  154. ProxyRow(QWidget *parent, View &&view);
  155. void updateFields(View &&view);
  156. rpl::producer<> deleteClicks() const;
  157. rpl::producer<> restoreClicks() const;
  158. rpl::producer<> editClicks() const;
  159. rpl::producer<> shareClicks() const;
  160. protected:
  161. int resizeGetHeight(int newWidth) override;
  162. void paintEvent(QPaintEvent *e) override;
  163. private:
  164. void setupControls(View &&view);
  165. int countAvailableWidth() const;
  166. void radialAnimationCallback();
  167. void paintCheck(Painter &p);
  168. void showMenu();
  169. View _view;
  170. Ui::Text::String _title;
  171. object_ptr<Ui::IconButton> _menuToggle;
  172. rpl::event_stream<> _deleteClicks;
  173. rpl::event_stream<> _restoreClicks;
  174. rpl::event_stream<> _editClicks;
  175. rpl::event_stream<> _shareClicks;
  176. base::unique_qptr<Ui::DropdownMenu> _menu;
  177. bool _set = false;
  178. Ui::Animations::Simple _toggled;
  179. Ui::Animations::Simple _setAnimation;
  180. std::unique_ptr<Ui::InfiniteRadialAnimation> _progress;
  181. std::unique_ptr<Ui::InfiniteRadialAnimation> _checking;
  182. int _skipLeft = 0;
  183. int _skipRight = 0;
  184. };
  185. class ProxiesBox : public Ui::BoxContent {
  186. public:
  187. using View = ProxiesBoxController::ItemView;
  188. ProxiesBox(
  189. QWidget*,
  190. not_null<ProxiesBoxController*> controller,
  191. Core::SettingsProxy &settings);
  192. protected:
  193. void prepare() override;
  194. private:
  195. void setupContent();
  196. void setupTopButton();
  197. void createNoRowsLabel();
  198. void addNewProxy();
  199. void applyView(View &&view);
  200. void setupButtons(int id, not_null<ProxyRow*> button);
  201. int rowHeight() const;
  202. void refreshProxyForCalls();
  203. not_null<ProxiesBoxController*> _controller;
  204. Core::SettingsProxy &_settings;
  205. QPointer<Ui::Checkbox> _tryIPv6;
  206. std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings;
  207. QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls;
  208. QPointer<Ui::DividerLabel> _about;
  209. base::unique_qptr<Ui::RpWidget> _noRows;
  210. object_ptr<Ui::VerticalLayout> _initialWrap;
  211. QPointer<Ui::VerticalLayout> _wrap;
  212. int _currentProxySupportsCallsId = 0;
  213. base::flat_map<int, base::unique_qptr<ProxyRow>> _rows;
  214. };
  215. class ProxyBox final : public Ui::BoxContent {
  216. public:
  217. ProxyBox(
  218. QWidget*,
  219. const ProxyData &data,
  220. Fn<void(ProxyData)> callback,
  221. Fn<void(ProxyData)> shareCallback);
  222. private:
  223. using Type = ProxyData::Type;
  224. void prepare() override;
  225. void setInnerFocus() override {
  226. _host->setFocusFast();
  227. }
  228. void refreshButtons();
  229. ProxyData collectData();
  230. void save();
  231. void share();
  232. void setupControls(const ProxyData &data);
  233. void setupTypes();
  234. void setupSocketAddress(const ProxyData &data);
  235. void setupCredentials(const ProxyData &data);
  236. void setupMtprotoCredentials(const ProxyData &data);
  237. void addLabel(
  238. not_null<Ui::VerticalLayout*> parent,
  239. const QString &text) const;
  240. Fn<void(ProxyData)> _callback;
  241. Fn<void(ProxyData)> _shareCallback;
  242. object_ptr<Ui::VerticalLayout> _content;
  243. std::shared_ptr<Ui::RadioenumGroup<Type>> _type;
  244. QPointer<Ui::SlideWrap<>> _aboutSponsored;
  245. QPointer<HostInput> _host;
  246. QPointer<Ui::NumberInput> _port;
  247. QPointer<Ui::InputField> _user;
  248. QPointer<Ui::PasswordInput> _password;
  249. QPointer<Base64UrlInput> _secret;
  250. QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _credentials;
  251. QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _mtprotoCredentials;
  252. };
  253. ProxyRow::ProxyRow(QWidget *parent, View &&view)
  254. : RippleButton(parent, st::proxyRowRipple)
  255. , _menuToggle(this, st::topBarMenuToggle) {
  256. setupControls(std::move(view));
  257. }
  258. rpl::producer<> ProxyRow::deleteClicks() const {
  259. return _deleteClicks.events();
  260. }
  261. rpl::producer<> ProxyRow::restoreClicks() const {
  262. return _restoreClicks.events();
  263. }
  264. rpl::producer<> ProxyRow::editClicks() const {
  265. return _editClicks.events();
  266. }
  267. rpl::producer<> ProxyRow::shareClicks() const {
  268. return _shareClicks.events();
  269. }
  270. void ProxyRow::setupControls(View &&view) {
  271. updateFields(std::move(view));
  272. _toggled.stop();
  273. _setAnimation.stop();
  274. _menuToggle->addClickHandler([=] { showMenu(); });
  275. }
  276. int ProxyRow::countAvailableWidth() const {
  277. return width() - _skipLeft - _skipRight;
  278. }
  279. void ProxyRow::updateFields(View &&view) {
  280. if (_view.selected != view.selected) {
  281. _toggled.start(
  282. [=] { update(); },
  283. view.selected ? 0. : 1.,
  284. view.selected ? 1. : 0.,
  285. st::defaultRadio.duration);
  286. }
  287. _view = std::move(view);
  288. const auto endpoint = _view.host + ':' + QString::number(_view.port);
  289. _title.setMarkedText(
  290. st::proxyRowTitleStyle,
  291. TextWithEntities()
  292. .append(_view.type)
  293. .append(' ')
  294. .append(Ui::Text::Link(endpoint, QString())),
  295. Ui::ItemTextDefaultOptions());
  296. const auto state = _view.state;
  297. if (state == State::Connecting) {
  298. if (!_progress) {
  299. _progress = std::make_unique<Ui::InfiniteRadialAnimation>(
  300. [=] { radialAnimationCallback(); },
  301. st::proxyCheckingAnimation);
  302. }
  303. _progress->start();
  304. } else if (_progress) {
  305. _progress->stop();
  306. }
  307. if (state == State::Checking) {
  308. if (!_checking) {
  309. _checking = std::make_unique<Ui::InfiniteRadialAnimation>(
  310. [=] { radialAnimationCallback(); },
  311. st::proxyCheckingAnimation);
  312. _checking->start();
  313. }
  314. } else {
  315. _checking = nullptr;
  316. }
  317. const auto set = (state == State::Connecting || state == State::Online);
  318. if (_set != set) {
  319. _set = set;
  320. _setAnimation.start(
  321. [=] { update(); },
  322. _set ? 0. : 1.,
  323. _set ? 1. : 0.,
  324. st::defaultRadio.duration);
  325. }
  326. setPointerCursor(!_view.deleted);
  327. update();
  328. }
  329. void ProxyRow::radialAnimationCallback() {
  330. if (!anim::Disabled()) {
  331. update();
  332. }
  333. }
  334. int ProxyRow::resizeGetHeight(int newWidth) {
  335. const auto result = st::proxyRowPadding.top()
  336. + st::semiboldFont->height
  337. + st::proxyRowSkip
  338. + st::normalFont->height
  339. + st::proxyRowPadding.bottom();
  340. auto right = st::proxyRowPadding.right();
  341. _menuToggle->moveToRight(
  342. right,
  343. (result - _menuToggle->height()) / 2,
  344. newWidth);
  345. right += _menuToggle->width();
  346. _skipRight = right;
  347. _skipLeft = st::proxyRowPadding.left()
  348. + st::proxyRowIconSkip;
  349. return result;
  350. }
  351. void ProxyRow::paintEvent(QPaintEvent *e) {
  352. Painter p(this);
  353. if (!_view.deleted) {
  354. paintRipple(p, 0, 0);
  355. }
  356. const auto left = _skipLeft;
  357. const auto availableWidth = countAvailableWidth();
  358. auto top = st::proxyRowPadding.top();
  359. if (_view.deleted) {
  360. p.setOpacity(st::stickersRowDisabledOpacity);
  361. }
  362. paintCheck(p);
  363. p.setPen(st::proxyRowTitleFg);
  364. p.setFont(st::semiboldFont);
  365. p.setTextPalette(st::proxyRowTitlePalette);
  366. _title.drawLeftElided(p, left, top, availableWidth, width());
  367. top += st::semiboldFont->height + st::proxyRowSkip;
  368. const auto statusFg = [&] {
  369. switch (_view.state) {
  370. case State::Online:
  371. return st::proxyRowStatusFgOnline;
  372. case State::Unavailable:
  373. return st::proxyRowStatusFgOffline;
  374. case State::Available:
  375. return st::proxyRowStatusFgAvailable;
  376. default:
  377. return st::proxyRowStatusFg;
  378. }
  379. }();
  380. const auto status = [&] {
  381. switch (_view.state) {
  382. case State::Available:
  383. return tr::lng_proxy_available(
  384. tr::now,
  385. lt_ping,
  386. QString::number(_view.ping));
  387. case State::Checking:
  388. return tr::lng_proxy_checking(tr::now);
  389. case State::Connecting:
  390. return tr::lng_proxy_connecting(tr::now);
  391. case State::Online:
  392. return tr::lng_proxy_online(tr::now);
  393. case State::Unavailable:
  394. return tr::lng_proxy_unavailable(tr::now);
  395. }
  396. Unexpected("State in ProxyRow::paintEvent.");
  397. }();
  398. p.setPen(_view.deleted ? st::proxyRowStatusFg : statusFg);
  399. p.setFont(st::normalFont);
  400. auto statusLeft = left;
  401. if (_checking) {
  402. _checking->draw(
  403. p,
  404. {
  405. st::proxyCheckingPosition.x() + statusLeft,
  406. st::proxyCheckingPosition.y() + top
  407. },
  408. width());
  409. statusLeft += st::proxyCheckingPosition.x()
  410. + st::proxyCheckingAnimation.size.width()
  411. + st::proxyCheckingSkip;
  412. }
  413. p.drawTextLeft(statusLeft, top, width(), status);
  414. top += st::normalFont->height + st::proxyRowPadding.bottom();
  415. }
  416. void ProxyRow::paintCheck(Painter &p) {
  417. const auto loading = _progress
  418. ? _progress->computeState()
  419. : Ui::RadialState{ 0., 0, arc::kFullLength };
  420. const auto toggled = _toggled.value(_view.selected ? 1. : 0.)
  421. * (1. - loading.shown);
  422. const auto _st = &st::defaultRadio;
  423. const auto set = _setAnimation.value(_set ? 1. : 0.);
  424. PainterHighQualityEnabler hq(p);
  425. const auto left = st::proxyRowPadding.left();
  426. const auto top = (height() - _st->diameter - _st->thickness) / 2;
  427. const auto outerWidth = width();
  428. auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled * set);
  429. pen.setWidth(_st->thickness);
  430. pen.setCapStyle(Qt::RoundCap);
  431. p.setPen(pen);
  432. p.setBrush(_st->bg);
  433. const auto rect = style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth);
  434. if (_progress && loading.shown > 0 && anim::Disabled()) {
  435. anim::DrawStaticLoading(
  436. p,
  437. rect,
  438. _st->thickness,
  439. pen.color(),
  440. _st->bg);
  441. } else if (loading.arcLength < arc::kFullLength) {
  442. p.drawArc(rect, loading.arcFrom, loading.arcLength);
  443. } else {
  444. p.drawEllipse(rect);
  445. }
  446. if (toggled > 0 && (!_progress || !anim::Disabled())) {
  447. p.setPen(Qt::NoPen);
  448. p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled * set));
  449. auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
  450. p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth));
  451. }
  452. }
  453. void ProxyRow::showMenu() {
  454. if (_menu) {
  455. return;
  456. }
  457. _menu = base::make_unique_q<Ui::DropdownMenu>(
  458. window(),
  459. st::dropdownMenuWithIcons);
  460. const auto weak = _menu.get();
  461. _menu->setHiddenCallback([=] {
  462. weak->deleteLater();
  463. if (_menu == weak) {
  464. _menuToggle->setForceRippled(false);
  465. }
  466. });
  467. _menu->setShowStartCallback([=] {
  468. if (_menu == weak) {
  469. _menuToggle->setForceRippled(true);
  470. }
  471. });
  472. _menu->setHideStartCallback([=] {
  473. if (_menu == weak) {
  474. _menuToggle->setForceRippled(false);
  475. }
  476. });
  477. _menuToggle->installEventFilter(_menu);
  478. const auto addAction = [&](
  479. const QString &text,
  480. Fn<void()> callback,
  481. const style::icon *icon) {
  482. return _menu->addAction(text, std::move(callback), icon);
  483. };
  484. addAction(tr::lng_proxy_menu_edit(tr::now), [=] {
  485. _editClicks.fire({});
  486. }, &st::menuIconEdit);
  487. if (_view.supportsShare) {
  488. addAction(tr::lng_proxy_edit_share(tr::now), [=] {
  489. _shareClicks.fire({});
  490. }, &st::menuIconShare);
  491. }
  492. if (_view.deleted) {
  493. addAction(tr::lng_proxy_menu_restore(tr::now), [=] {
  494. _restoreClicks.fire({});
  495. }, &st::menuIconRestore);
  496. } else {
  497. addAction(tr::lng_proxy_menu_delete(tr::now), [=] {
  498. _deleteClicks.fire({});
  499. }, &st::menuIconDelete);
  500. }
  501. const auto parentTopLeft = window()->mapToGlobal(QPoint());
  502. const auto buttonTopLeft = _menuToggle->mapToGlobal(QPoint());
  503. const auto parent = QRect(parentTopLeft, window()->size());
  504. const auto button = QRect(buttonTopLeft, _menuToggle->size());
  505. const auto bottom = button.y()
  506. + st::proxyDropdownDownPosition.y()
  507. + _menu->height()
  508. - parent.y();
  509. const auto top = button.y()
  510. + st::proxyDropdownUpPosition.y()
  511. - _menu->height()
  512. - parent.y();
  513. if (bottom > parent.height() && top >= 0) {
  514. const auto left = button.x()
  515. + button.width()
  516. + st::proxyDropdownUpPosition.x()
  517. - _menu->width()
  518. - parent.x();
  519. _menu->move(left, top);
  520. _menu->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
  521. } else {
  522. const auto left = button.x()
  523. + button.width()
  524. + st::proxyDropdownDownPosition.x()
  525. - _menu->width()
  526. - parent.x();
  527. _menu->move(left, bottom - _menu->height());
  528. _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
  529. }
  530. }
  531. ProxiesBox::ProxiesBox(
  532. QWidget*,
  533. not_null<ProxiesBoxController*> controller,
  534. Core::SettingsProxy &settings)
  535. : _controller(controller)
  536. , _settings(settings)
  537. , _initialWrap(this) {
  538. _controller->views(
  539. ) | rpl::start_with_next([=](View &&view) {
  540. applyView(std::move(view));
  541. }, lifetime());
  542. }
  543. void ProxiesBox::prepare() {
  544. setTitle(tr::lng_proxy_settings());
  545. addButton(tr::lng_proxy_add(), [=] { addNewProxy(); });
  546. addButton(tr::lng_close(), [=] { closeBox(); });
  547. setupTopButton();
  548. setupContent();
  549. }
  550. void ProxiesBox::setupTopButton() {
  551. const auto top = addTopButton(st::infoTopBarMenu);
  552. const auto menu
  553. = top->lifetime().make_state<base::unique_qptr<Ui::PopupMenu>>();
  554. const auto callback = [=] {
  555. const auto maybeUrl = QGuiApplication::clipboard()->text();
  556. const auto local = Core::TryConvertUrlToLocal(maybeUrl);
  557. const auto proxyString = u"proxy"_q;
  558. const auto socksString = u"socks"_q;
  559. const auto protocol = u"tg://"_q;
  560. const auto command = base::StringViewMid(
  561. local,
  562. protocol.size(),
  563. 8192);
  564. if (local.startsWith(protocol + proxyString)
  565. || local.startsWith(protocol + socksString)) {
  566. using namespace qthelp;
  567. const auto options = RegExOption::CaseInsensitive;
  568. for (const auto &[expression, _] : Core::LocalUrlHandlers()) {
  569. const auto midExpression = base::StringViewMid(
  570. expression,
  571. 1);
  572. const auto isSocks = midExpression.startsWith(
  573. socksString);
  574. if (!midExpression.startsWith(proxyString)
  575. && !isSocks) {
  576. continue;
  577. }
  578. const auto match = regex_match(
  579. expression,
  580. command,
  581. options);
  582. if (!match) {
  583. continue;
  584. }
  585. const auto type = isSocks
  586. ? ProxyData::Type::Socks5
  587. : ProxyData::Type::Mtproto;
  588. const auto fields = url_parse_params(
  589. match->captured(1),
  590. qthelp::UrlParamNameTransform::ToLower);
  591. const auto proxy = ProxyDataFromFields(type, fields);
  592. const auto contains = _controller->contains(proxy);
  593. const auto toast = (contains
  594. ? tr::lng_proxy_add_from_clipboard_existing_toast
  595. : tr::lng_proxy_add_from_clipboard_good_toast)(tr::now);
  596. uiShow()->showToast(toast);
  597. if (!contains) {
  598. _controller->addNewItem(proxy);
  599. }
  600. break;
  601. }
  602. } else {
  603. uiShow()->showToast(
  604. tr::lng_proxy_add_from_clipboard_failed_toast(tr::now));
  605. }
  606. };
  607. top->setClickedCallback([=] {
  608. *menu = base::make_unique_q<Ui::PopupMenu>(top, st::defaultPopupMenu);
  609. (*menu)->addAction(
  610. tr::lng_proxy_add_from_clipboard(tr::now),
  611. callback);
  612. (*menu)->popup(QCursor::pos());
  613. return true;
  614. });
  615. }
  616. void ProxiesBox::setupContent() {
  617. const auto inner = setInnerWidget(object_ptr<Ui::VerticalLayout>(this));
  618. _tryIPv6 = inner->add(
  619. object_ptr<Ui::Checkbox>(
  620. inner,
  621. tr::lng_connection_try_ipv6(tr::now),
  622. _settings.tryIPv6()),
  623. st::proxyTryIPv6Padding);
  624. _proxySettings
  625. = std::make_shared<Ui::RadioenumGroup<ProxyData::Settings>>(
  626. _settings.settings());
  627. inner->add(
  628. object_ptr<Ui::Radioenum<ProxyData::Settings>>(
  629. inner,
  630. _proxySettings,
  631. ProxyData::Settings::Disabled,
  632. tr::lng_proxy_disable(tr::now)),
  633. st::proxyUsePadding);
  634. inner->add(
  635. object_ptr<Ui::Radioenum<ProxyData::Settings>>(
  636. inner,
  637. _proxySettings,
  638. ProxyData::Settings::System,
  639. tr::lng_proxy_use_system_settings(tr::now)),
  640. st::proxyUsePadding);
  641. inner->add(
  642. object_ptr<Ui::Radioenum<ProxyData::Settings>>(
  643. inner,
  644. _proxySettings,
  645. ProxyData::Settings::Enabled,
  646. tr::lng_proxy_use_custom(tr::now)),
  647. st::proxyUsePadding);
  648. _proxyForCalls = inner->add(
  649. object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
  650. inner,
  651. object_ptr<Ui::Checkbox>(
  652. inner,
  653. tr::lng_proxy_use_for_calls(tr::now),
  654. _settings.useProxyForCalls()),
  655. style::margins(
  656. 0,
  657. st::proxyUsePadding.top(),
  658. 0,
  659. st::proxyUsePadding.bottom())),
  660. style::margins(
  661. st::proxyTryIPv6Padding.left(),
  662. 0,
  663. st::proxyTryIPv6Padding.right(),
  664. st::proxyTryIPv6Padding.top()));
  665. _about = inner->add(
  666. object_ptr<Ui::DividerLabel>(
  667. inner,
  668. object_ptr<Ui::FlatLabel>(
  669. inner,
  670. tr::lng_proxy_about(tr::now),
  671. st::boxDividerLabel),
  672. st::proxyAboutPadding),
  673. style::margins(0, 0, 0, st::proxyRowPadding.top()));
  674. _wrap = inner->add(std::move(_initialWrap));
  675. inner->add(object_ptr<Ui::FixedHeightWidget>(
  676. inner,
  677. st::proxyRowPadding.bottom()));
  678. _proxySettings->setChangedCallback([=](ProxyData::Settings value) {
  679. if (!_controller->setProxySettings(value)) {
  680. _proxySettings->setValue(_settings.settings());
  681. addNewProxy();
  682. }
  683. refreshProxyForCalls();
  684. });
  685. _tryIPv6->checkedChanges(
  686. ) | rpl::start_with_next([=](bool checked) {
  687. _controller->setTryIPv6(checked);
  688. }, _tryIPv6->lifetime());
  689. _controller->proxySettingsValue(
  690. ) | rpl::start_with_next([=](ProxyData::Settings value) {
  691. _proxySettings->setValue(value);
  692. }, inner->lifetime());
  693. _proxyForCalls->entity()->checkedChanges(
  694. ) | rpl::start_with_next([=](bool checked) {
  695. _controller->setProxyForCalls(checked);
  696. }, _proxyForCalls->lifetime());
  697. if (_rows.empty()) {
  698. createNoRowsLabel();
  699. }
  700. refreshProxyForCalls();
  701. _proxyForCalls->finishAnimating();
  702. inner->resizeToWidth(st::boxWideWidth);
  703. inner->heightValue(
  704. ) | rpl::map([=](int height) {
  705. return std::min(
  706. std::max(height, _about->y()
  707. + _about->height()
  708. + 3 * rowHeight()),
  709. st::boxMaxListHeight);
  710. }) | rpl::distinct_until_changed(
  711. ) | rpl::start_with_next([=](int height) {
  712. setDimensions(st::boxWideWidth, height);
  713. }, inner->lifetime());
  714. }
  715. void ProxiesBox::refreshProxyForCalls() {
  716. if (!_proxyForCalls) {
  717. return;
  718. }
  719. _proxyForCalls->toggle(
  720. (_proxySettings->current() == ProxyData::Settings::Enabled
  721. && _currentProxySupportsCallsId != 0),
  722. anim::type::normal);
  723. }
  724. int ProxiesBox::rowHeight() const {
  725. return st::proxyRowPadding.top()
  726. + st::semiboldFont->height
  727. + st::proxyRowSkip
  728. + st::normalFont->height
  729. + st::proxyRowPadding.bottom();
  730. }
  731. void ProxiesBox::addNewProxy() {
  732. getDelegate()->show(_controller->addNewItemBox());
  733. }
  734. void ProxiesBox::applyView(View &&view) {
  735. if (view.selected) {
  736. _currentProxySupportsCallsId = view.supportsCalls ? view.id : 0;
  737. } else if (view.id == _currentProxySupportsCallsId) {
  738. _currentProxySupportsCallsId = 0;
  739. }
  740. refreshProxyForCalls();
  741. const auto id = view.id;
  742. const auto i = _rows.find(id);
  743. if (i == _rows.end()) {
  744. const auto wrap = _wrap
  745. ? _wrap.data()
  746. : _initialWrap.data();
  747. const auto &[i, ok] = _rows.emplace(id, nullptr);
  748. i->second.reset(wrap->insert(
  749. 0,
  750. object_ptr<ProxyRow>(
  751. wrap,
  752. std::move(view))));
  753. setupButtons(id, i->second.get());
  754. if (_noRows) {
  755. _noRows.reset();
  756. }
  757. wrap->resizeToWidth(width());
  758. } else if (view.host.isEmpty()) {
  759. _rows.erase(i);
  760. } else {
  761. i->second->updateFields(std::move(view));
  762. }
  763. }
  764. void ProxiesBox::createNoRowsLabel() {
  765. _noRows.reset(_wrap->add(
  766. object_ptr<Ui::FixedHeightWidget>(
  767. _wrap,
  768. rowHeight()),
  769. st::proxyEmptyListPadding));
  770. _noRows->resize(
  771. (st::boxWideWidth
  772. - st::proxyEmptyListPadding.left()
  773. - st::proxyEmptyListPadding.right()),
  774. _noRows->height());
  775. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  776. _noRows.get(),
  777. tr::lng_proxy_description(tr::now),
  778. st::proxyEmptyListLabel);
  779. _noRows->widthValue(
  780. ) | rpl::start_with_next([=](int width) {
  781. label->resizeToWidth(width);
  782. label->moveToLeft(0, 0);
  783. }, label->lifetime());
  784. }
  785. void ProxiesBox::setupButtons(int id, not_null<ProxyRow*> button) {
  786. button->deleteClicks(
  787. ) | rpl::start_with_next([=] {
  788. _controller->deleteItem(id);
  789. }, button->lifetime());
  790. button->restoreClicks(
  791. ) | rpl::start_with_next([=] {
  792. _controller->restoreItem(id);
  793. }, button->lifetime());
  794. button->editClicks(
  795. ) | rpl::start_with_next([=] {
  796. getDelegate()->show(_controller->editItemBox(id));
  797. }, button->lifetime());
  798. button->shareClicks(
  799. ) | rpl::start_with_next([=] {
  800. _controller->shareItem(id);
  801. }, button->lifetime());
  802. button->clicks(
  803. ) | rpl::start_with_next([=] {
  804. _controller->applyItem(id);
  805. }, button->lifetime());
  806. }
  807. ProxyBox::ProxyBox(
  808. QWidget*,
  809. const ProxyData &data,
  810. Fn<void(ProxyData)> callback,
  811. Fn<void(ProxyData)> shareCallback)
  812. : _callback(std::move(callback))
  813. , _shareCallback(std::move(shareCallback))
  814. , _content(this) {
  815. setupControls(data);
  816. }
  817. void ProxyBox::prepare() {
  818. setTitle(tr::lng_proxy_edit());
  819. connect(_host.data(), &HostInput::changed, [=] {
  820. Ui::PostponeCall(_host, [=] {
  821. const auto host = _host->getLastText().trimmed();
  822. static const auto mask = QRegularExpression(
  823. u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q);
  824. const auto match = mask.match(host);
  825. if (_host->cursorPosition() == host.size()
  826. && match.hasMatch()) {
  827. const auto port = match.captured(1);
  828. _port->setText(port);
  829. _port->setCursorPosition(port.size());
  830. _port->setFocus();
  831. _host->setText(host.mid(0, host.size() - port.size() - 1));
  832. }
  833. });
  834. });
  835. _port.data()->events(
  836. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  837. if (e->type() == QEvent::KeyPress
  838. && (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Backspace)
  839. && _port->cursorPosition() == 0) {
  840. _host->setCursorPosition(_host->getLastText().size());
  841. _host->setFocus();
  842. }
  843. }, _port->lifetime());
  844. refreshButtons();
  845. setDimensionsToContent(st::boxWideWidth, _content);
  846. }
  847. void ProxyBox::refreshButtons() {
  848. clearButtons();
  849. addButton(tr::lng_settings_save(), [=] { save(); });
  850. addButton(tr::lng_cancel(), [=] { closeBox(); });
  851. const auto type = _type->current();
  852. if (type == Type::Socks5 || type == Type::Mtproto) {
  853. addLeftButton(tr::lng_proxy_share(), [=] { share(); });
  854. }
  855. }
  856. void ProxyBox::save() {
  857. if (const auto data = collectData()) {
  858. _callback(data);
  859. closeBox();
  860. }
  861. }
  862. void ProxyBox::share() {
  863. if (const auto data = collectData()) {
  864. _shareCallback(data);
  865. }
  866. }
  867. ProxyData ProxyBox::collectData() {
  868. auto result = ProxyData();
  869. result.type = _type->current();
  870. result.host = _host->getLastText().trimmed();
  871. result.port = _port->getLastText().trimmed().toInt();
  872. result.user = (result.type == Type::Mtproto)
  873. ? QString()
  874. : _user->getLastText();
  875. result.password = (result.type == Type::Mtproto)
  876. ? _secret->getLastText()
  877. : _password->getLastText();
  878. if (result.host.isEmpty()) {
  879. _host->showError();
  880. } else if (!result.port) {
  881. _port->showError();
  882. } else if ((result.type == Type::Http || result.type == Type::Socks5)
  883. && !result.password.isEmpty() && result.user.isEmpty()) {
  884. _user->showError();
  885. } else if (result.type == Type::Mtproto && !result.valid()) {
  886. _secret->showError();
  887. } else if (!result) {
  888. _host->showError();
  889. } else {
  890. return result;
  891. }
  892. return ProxyData();
  893. }
  894. void ProxyBox::setupTypes() {
  895. const auto types = std::map<Type, QString>{
  896. { Type::Http, "HTTP" },
  897. { Type::Socks5, "SOCKS5" },
  898. { Type::Mtproto, "MTPROTO" },
  899. };
  900. for (const auto &[type, label] : types) {
  901. _content->add(
  902. object_ptr<Ui::Radioenum<Type>>(
  903. _content,
  904. _type,
  905. type,
  906. label),
  907. st::proxyEditTypePadding);
  908. }
  909. _aboutSponsored = _content->add(object_ptr<Ui::SlideWrap<>>(
  910. _content,
  911. object_ptr<Ui::PaddingWrap<>>(
  912. _content,
  913. object_ptr<Ui::FlatLabel>(
  914. _content,
  915. tr::lng_proxy_sponsor_warning(tr::now),
  916. st::boxDividerLabel),
  917. st::proxyAboutSponsorPadding)));
  918. }
  919. void ProxyBox::setupSocketAddress(const ProxyData &data) {
  920. addLabel(_content, tr::lng_proxy_address_label(tr::now));
  921. const auto address = _content->add(
  922. object_ptr<Ui::FixedHeightWidget>(
  923. _content,
  924. st::connectionHostInputField.heightMin),
  925. st::proxyEditInputPadding);
  926. _host = Ui::CreateChild<HostInput>(
  927. address,
  928. st::connectionHostInputField,
  929. tr::lng_connection_host_ph(),
  930. data.host);
  931. _port = Ui::CreateChild<Ui::NumberInput>(
  932. address,
  933. st::connectionPortInputField,
  934. tr::lng_connection_port_ph(),
  935. data.port ? QString::number(data.port) : QString(),
  936. 65535);
  937. address->widthValue(
  938. ) | rpl::start_with_next([=](int width) {
  939. _port->moveToRight(0, 0);
  940. _host->resize(
  941. width - _port->width() - st::proxyEditSkip,
  942. _host->height());
  943. _host->moveToLeft(0, 0);
  944. }, address->lifetime());
  945. }
  946. void ProxyBox::setupCredentials(const ProxyData &data) {
  947. _credentials = _content->add(
  948. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  949. _content,
  950. object_ptr<Ui::VerticalLayout>(_content)));
  951. const auto credentials = _credentials->entity();
  952. addLabel(credentials, tr::lng_proxy_credentials_optional(tr::now));
  953. _user = credentials->add(
  954. object_ptr<Ui::InputField>(
  955. credentials,
  956. st::connectionUserInputField,
  957. tr::lng_connection_user_ph(),
  958. data.user),
  959. st::proxyEditInputPadding);
  960. auto passwordWrap = object_ptr<Ui::RpWidget>(credentials);
  961. _password = Ui::CreateChild<Ui::PasswordInput>(
  962. passwordWrap.data(),
  963. st::connectionPasswordInputField,
  964. tr::lng_connection_password_ph(),
  965. (data.type == Type::Mtproto) ? QString() : data.password);
  966. _password->move(0, 0);
  967. _password->heightValue(
  968. ) | rpl::start_with_next([=, wrap = passwordWrap.data()](int height) {
  969. wrap->resize(wrap->width(), height);
  970. }, _password->lifetime());
  971. passwordWrap->widthValue(
  972. ) | rpl::start_with_next([=](int width) {
  973. _password->resize(width, _password->height());
  974. }, _password->lifetime());
  975. credentials->add(std::move(passwordWrap), st::proxyEditInputPadding);
  976. }
  977. void ProxyBox::setupMtprotoCredentials(const ProxyData &data) {
  978. _mtprotoCredentials = _content->add(
  979. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  980. _content,
  981. object_ptr<Ui::VerticalLayout>(_content)));
  982. const auto mtproto = _mtprotoCredentials->entity();
  983. addLabel(mtproto, tr::lng_proxy_credentials(tr::now));
  984. auto secretWrap = object_ptr<Ui::RpWidget>(mtproto);
  985. _secret = Ui::CreateChild<Base64UrlInput>(
  986. secretWrap.data(),
  987. st::connectionUserInputField,
  988. tr::lng_connection_proxy_secret_ph(),
  989. (data.type == Type::Mtproto) ? data.password : QString());
  990. _secret->move(0, 0);
  991. _secret->heightValue(
  992. ) | rpl::start_with_next([=, wrap = secretWrap.data()](int height) {
  993. wrap->resize(wrap->width(), height);
  994. }, _secret->lifetime());
  995. secretWrap->widthValue(
  996. ) | rpl::start_with_next([=](int width) {
  997. _secret->resize(width, _secret->height());
  998. }, _secret->lifetime());
  999. mtproto->add(std::move(secretWrap), st::proxyEditInputPadding);
  1000. }
  1001. void ProxyBox::setupControls(const ProxyData &data) {
  1002. _type = std::make_shared<Ui::RadioenumGroup<Type>>(
  1003. (data.type == Type::None
  1004. ? Type::Socks5
  1005. : data.type));
  1006. _content.create(this);
  1007. _content->resizeToWidth(st::boxWideWidth);
  1008. _content->moveToLeft(0, 0);
  1009. setupTypes();
  1010. setupSocketAddress(data);
  1011. setupCredentials(data);
  1012. setupMtprotoCredentials(data);
  1013. const auto handleType = [=](Type type) {
  1014. _credentials->toggle(
  1015. type == Type::Http || type == Type::Socks5,
  1016. anim::type::instant);
  1017. _mtprotoCredentials->toggle(
  1018. type == Type::Mtproto,
  1019. anim::type::instant);
  1020. _aboutSponsored->toggle(
  1021. type == Type::Mtproto,
  1022. anim::type::instant);
  1023. };
  1024. _type->setChangedCallback([=](Type type) {
  1025. handleType(type);
  1026. refreshButtons();
  1027. });
  1028. handleType(_type->current());
  1029. }
  1030. void ProxyBox::addLabel(
  1031. not_null<Ui::VerticalLayout*> parent,
  1032. const QString &text) const {
  1033. parent->add(
  1034. object_ptr<Ui::FlatLabel>(
  1035. parent,
  1036. text,
  1037. st::proxyEditTitle),
  1038. st::proxyEditTitlePadding);
  1039. }
  1040. } // namespace
  1041. ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
  1042. : _account(account)
  1043. , _settings(Core::App().settings().proxy())
  1044. , _saveTimer([] { Local::writeSettings(); }) {
  1045. _list = ranges::views::all(
  1046. _settings.list()
  1047. ) | ranges::views::transform([&](const ProxyData &proxy) {
  1048. return Item{ ++_idCounter, proxy };
  1049. }) | ranges::to_vector;
  1050. _settings.connectionTypeChanges(
  1051. ) | rpl::start_with_next([=] {
  1052. _proxySettingsChanges.fire_copy(_settings.settings());
  1053. const auto i = findByProxy(_settings.selected());
  1054. if (i != end(_list)) {
  1055. updateView(*i);
  1056. }
  1057. }, _lifetime);
  1058. for (auto &item : _list) {
  1059. refreshChecker(item);
  1060. }
  1061. }
  1062. void ProxiesBoxController::ShowApplyConfirmation(
  1063. Window::SessionController *controller,
  1064. Type type,
  1065. const QMap<QString, QString> &fields) {
  1066. const auto proxy = ProxyDataFromFields(type, fields);
  1067. if (!proxy) {
  1068. auto box = Ui::MakeInformBox(
  1069. (proxy.status() == ProxyData::Status::Unsupported
  1070. ? tr::lng_proxy_unsupported(tr::now)
  1071. : tr::lng_proxy_invalid(tr::now)));
  1072. if (controller) {
  1073. controller->uiShow()->showBox(std::move(box));
  1074. } else {
  1075. Ui::show(std::move(box));
  1076. }
  1077. return;
  1078. }
  1079. static const auto UrlStartRegExp = QRegularExpression(
  1080. "^https://",
  1081. QRegularExpression::CaseInsensitiveOption);
  1082. static const auto UrlEndRegExp = QRegularExpression("/$");
  1083. const auto displayed = "https://" + proxy.host + "/";
  1084. const auto parsed = QUrl::fromUserInput(displayed);
  1085. const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
  1086. ? displayed
  1087. : parsed.isValid()
  1088. ? QString::fromUtf8(parsed.toEncoded())
  1089. : UrlClickHandler::ShowEncoded(displayed);
  1090. const auto displayServer = QString(
  1091. displayUrl
  1092. ).replace(
  1093. UrlStartRegExp,
  1094. QString()
  1095. ).replace(UrlEndRegExp, QString());
  1096. const auto box = [=](not_null<Ui::GenericBox*> box) {
  1097. box->setTitle(tr::lng_proxy_box_title());
  1098. if (type == Type::Mtproto) {
  1099. box->addRow(object_ptr<Ui::FlatLabel>(
  1100. box,
  1101. tr::lng_proxy_sponsor_warning(),
  1102. st::boxDividerLabel));
  1103. Ui::AddSkip(box->verticalLayout());
  1104. Ui::AddSkip(box->verticalLayout());
  1105. }
  1106. const auto &stL = st::proxyApplyBoxLabel;
  1107. const auto &stSubL = st::boxDividerLabel;
  1108. const auto add = [&](const QString &s, tr::phrase<> phrase) {
  1109. if (!s.isEmpty()) {
  1110. box->addRow(object_ptr<Ui::FlatLabel>(box, s, stL));
  1111. box->addRow(object_ptr<Ui::FlatLabel>(box, phrase(), stSubL));
  1112. Ui::AddSkip(box->verticalLayout());
  1113. Ui::AddSkip(box->verticalLayout());
  1114. }
  1115. };
  1116. if (!displayServer.isEmpty()) {
  1117. add(displayServer, tr::lng_proxy_box_server);
  1118. }
  1119. add(QString::number(proxy.port), tr::lng_proxy_box_port);
  1120. if (type == Type::Socks5) {
  1121. add(proxy.user, tr::lng_proxy_box_username);
  1122. add(proxy.password, tr::lng_proxy_box_password);
  1123. } else if (type == Type::Mtproto) {
  1124. add(proxy.password, tr::lng_proxy_box_secret);
  1125. }
  1126. box->addButton(tr::lng_sure_enable(), [=] {
  1127. auto &proxies = Core::App().settings().proxy().list();
  1128. if (!ranges::contains(proxies, proxy)) {
  1129. proxies.push_back(proxy);
  1130. }
  1131. Core::App().setCurrentProxy(proxy, ProxyData::Settings::Enabled);
  1132. Local::writeSettings();
  1133. box->closeBox();
  1134. });
  1135. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  1136. };
  1137. if (controller) {
  1138. controller->uiShow()->showBox(Box(box));
  1139. } else {
  1140. Ui::show(Box(box));
  1141. }
  1142. }
  1143. auto ProxiesBoxController::proxySettingsValue() const
  1144. -> rpl::producer<ProxyData::Settings> {
  1145. return _proxySettingsChanges.events_starting_with_copy(
  1146. _settings.settings()
  1147. ) | rpl::distinct_until_changed();
  1148. }
  1149. void ProxiesBoxController::refreshChecker(Item &item) {
  1150. using Variants = MTP::DcOptions::Variants;
  1151. const auto type = (item.data.type == Type::Http)
  1152. ? Variants::Http
  1153. : Variants::Tcp;
  1154. const auto mtproto = &_account->mtp();
  1155. const auto dcId = mtproto->mainDcId();
  1156. const auto forFiles = false;
  1157. item.state = ItemState::Checking;
  1158. const auto setup = [&](Checker &checker, const bytes::vector &secret) {
  1159. checker = MTP::details::AbstractConnection::Create(
  1160. mtproto,
  1161. type,
  1162. QThread::currentThread(),
  1163. secret,
  1164. item.data);
  1165. setupChecker(item.id, checker);
  1166. };
  1167. if (item.data.type == Type::Mtproto) {
  1168. const auto secret = item.data.secretFromMtprotoPassword();
  1169. setup(item.checker, secret);
  1170. item.checker->connectToServer(
  1171. item.data.host,
  1172. item.data.port,
  1173. secret,
  1174. dcId,
  1175. forFiles);
  1176. item.checkerv6 = nullptr;
  1177. } else {
  1178. const auto options = mtproto->dcOptions().lookup(
  1179. dcId,
  1180. MTP::DcType::Regular,
  1181. true);
  1182. const auto connect = [&](
  1183. Checker &checker,
  1184. Variants::Address address) {
  1185. const auto &list = options.data[address][type];
  1186. if (list.empty()
  1187. || ((address == Variants::IPv6)
  1188. && !Core::App().settings().proxy().tryIPv6())) {
  1189. checker = nullptr;
  1190. return;
  1191. }
  1192. const auto &endpoint = list.front();
  1193. setup(checker, endpoint.secret);
  1194. checker->connectToServer(
  1195. QString::fromStdString(endpoint.ip),
  1196. endpoint.port,
  1197. endpoint.secret,
  1198. dcId,
  1199. forFiles);
  1200. };
  1201. connect(item.checker, Variants::IPv4);
  1202. connect(item.checkerv6, Variants::IPv6);
  1203. if (!item.checker && !item.checkerv6) {
  1204. item.state = ItemState::Unavailable;
  1205. }
  1206. }
  1207. }
  1208. void ProxiesBoxController::setupChecker(int id, const Checker &checker) {
  1209. using Connection = MTP::details::AbstractConnection;
  1210. const auto pointer = checker.get();
  1211. pointer->connect(pointer, &Connection::connected, [=] {
  1212. const auto item = findById(id);
  1213. const auto pingTime = pointer->pingTime();
  1214. item->checker = nullptr;
  1215. item->checkerv6 = nullptr;
  1216. if (item->state == ItemState::Checking) {
  1217. item->state = ItemState::Available;
  1218. item->ping = pingTime;
  1219. updateView(*item);
  1220. }
  1221. });
  1222. const auto failed = [=] {
  1223. const auto item = findById(id);
  1224. if (item->checker == pointer) {
  1225. item->checker = nullptr;
  1226. } else if (item->checkerv6 == pointer) {
  1227. item->checkerv6 = nullptr;
  1228. }
  1229. if (!item->checker
  1230. && !item->checkerv6
  1231. && item->state == ItemState::Checking) {
  1232. item->state = ItemState::Unavailable;
  1233. updateView(*item);
  1234. }
  1235. };
  1236. pointer->connect(pointer, &Connection::disconnected, failed);
  1237. pointer->connect(pointer, &Connection::error, failed);
  1238. }
  1239. object_ptr<Ui::BoxContent> ProxiesBoxController::CreateOwningBox(
  1240. not_null<Main::Account*> account) {
  1241. auto controller = std::make_unique<ProxiesBoxController>(account);
  1242. auto box = controller->create();
  1243. Ui::AttachAsChild(box, std::move(controller));
  1244. return box;
  1245. }
  1246. object_ptr<Ui::BoxContent> ProxiesBoxController::create() {
  1247. auto result = Box<ProxiesBox>(this, _settings);
  1248. _show = result->uiShow();
  1249. for (const auto &item : _list) {
  1250. updateView(item);
  1251. }
  1252. return result;
  1253. }
  1254. auto ProxiesBoxController::findById(int id) -> std::vector<Item>::iterator {
  1255. const auto result = ranges::find(
  1256. _list,
  1257. id,
  1258. [](const Item &item) { return item.id; });
  1259. Assert(result != end(_list));
  1260. return result;
  1261. }
  1262. auto ProxiesBoxController::findByProxy(const ProxyData &proxy)
  1263. ->std::vector<Item>::iterator {
  1264. return ranges::find(
  1265. _list,
  1266. proxy,
  1267. [](const Item &item) { return item.data; });
  1268. }
  1269. void ProxiesBoxController::deleteItem(int id) {
  1270. setDeleted(id, true);
  1271. }
  1272. void ProxiesBoxController::restoreItem(int id) {
  1273. setDeleted(id, false);
  1274. }
  1275. void ProxiesBoxController::shareItem(int id) {
  1276. share(findById(id)->data);
  1277. }
  1278. void ProxiesBoxController::applyItem(int id) {
  1279. auto item = findById(id);
  1280. if (_settings.isEnabled() && (_settings.selected() == item->data)) {
  1281. return;
  1282. } else if (item->deleted) {
  1283. return;
  1284. }
  1285. auto j = findByProxy(_settings.selected());
  1286. Core::App().setCurrentProxy(
  1287. item->data,
  1288. ProxyData::Settings::Enabled);
  1289. saveDelayed();
  1290. if (j != end(_list)) {
  1291. updateView(*j);
  1292. }
  1293. updateView(*item);
  1294. }
  1295. void ProxiesBoxController::setDeleted(int id, bool deleted) {
  1296. auto item = findById(id);
  1297. item->deleted = deleted;
  1298. if (deleted) {
  1299. auto &proxies = _settings.list();
  1300. proxies.erase(ranges::remove(proxies, item->data), end(proxies));
  1301. if (item->data == _settings.selected()) {
  1302. _lastSelectedProxy = _settings.selected();
  1303. _settings.setSelected(MTP::ProxyData());
  1304. if (_settings.isEnabled()) {
  1305. _lastSelectedProxyUsed = true;
  1306. Core::App().setCurrentProxy(
  1307. ProxyData(),
  1308. ProxyData::Settings::System);
  1309. saveDelayed();
  1310. } else {
  1311. _lastSelectedProxyUsed = false;
  1312. }
  1313. }
  1314. } else {
  1315. auto &proxies = _settings.list();
  1316. if (ranges::find(proxies, item->data) == end(proxies)) {
  1317. auto insertBefore = item + 1;
  1318. while (insertBefore != end(_list) && insertBefore->deleted) {
  1319. ++insertBefore;
  1320. }
  1321. auto insertBeforeIt = (insertBefore == end(_list))
  1322. ? end(proxies)
  1323. : ranges::find(proxies, insertBefore->data);
  1324. proxies.insert(insertBeforeIt, item->data);
  1325. }
  1326. if (!_settings.selected() && _lastSelectedProxy == item->data) {
  1327. Assert(!_settings.isEnabled());
  1328. if (base::take(_lastSelectedProxyUsed)) {
  1329. Core::App().setCurrentProxy(
  1330. base::take(_lastSelectedProxy),
  1331. ProxyData::Settings::Enabled);
  1332. } else {
  1333. _settings.setSelected(base::take(_lastSelectedProxy));
  1334. }
  1335. }
  1336. }
  1337. saveDelayed();
  1338. updateView(*item);
  1339. }
  1340. object_ptr<Ui::BoxContent> ProxiesBoxController::editItemBox(int id) {
  1341. return Box<ProxyBox>(findById(id)->data, [=](const ProxyData &result) {
  1342. auto i = findById(id);
  1343. auto j = ranges::find(
  1344. _list,
  1345. result,
  1346. [](const Item &item) { return item.data; });
  1347. if (j != end(_list) && j != i) {
  1348. replaceItemWith(i, j);
  1349. } else {
  1350. replaceItemValue(i, result);
  1351. }
  1352. }, [=](const ProxyData &proxy) {
  1353. share(proxy);
  1354. });
  1355. }
  1356. void ProxiesBoxController::replaceItemWith(
  1357. std::vector<Item>::iterator which,
  1358. std::vector<Item>::iterator with) {
  1359. auto &proxies = _settings.list();
  1360. proxies.erase(ranges::remove(proxies, which->data), end(proxies));
  1361. _views.fire({ which->id });
  1362. _list.erase(which);
  1363. if (with->deleted) {
  1364. restoreItem(with->id);
  1365. }
  1366. applyItem(with->id);
  1367. saveDelayed();
  1368. }
  1369. void ProxiesBoxController::replaceItemValue(
  1370. std::vector<Item>::iterator which,
  1371. const ProxyData &proxy) {
  1372. if (which->deleted) {
  1373. restoreItem(which->id);
  1374. }
  1375. auto &proxies = _settings.list();
  1376. const auto i = ranges::find(proxies, which->data);
  1377. Assert(i != end(proxies));
  1378. *i = proxy;
  1379. which->data = proxy;
  1380. refreshChecker(*which);
  1381. applyItem(which->id);
  1382. saveDelayed();
  1383. }
  1384. object_ptr<Ui::BoxContent> ProxiesBoxController::addNewItemBox() {
  1385. return Box<ProxyBox>(ProxyData(), [=](const ProxyData &result) {
  1386. auto j = ranges::find(
  1387. _list,
  1388. result,
  1389. [](const Item &item) { return item.data; });
  1390. if (j != end(_list)) {
  1391. if (j->deleted) {
  1392. restoreItem(j->id);
  1393. }
  1394. applyItem(j->id);
  1395. } else {
  1396. addNewItem(result);
  1397. }
  1398. }, [=](const ProxyData &proxy) {
  1399. share(proxy);
  1400. });
  1401. }
  1402. bool ProxiesBoxController::contains(const ProxyData &proxy) const {
  1403. const auto j = ranges::find(
  1404. _list,
  1405. proxy,
  1406. [](const Item &item) { return item.data; });
  1407. return (j != end(_list));
  1408. }
  1409. void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
  1410. auto &proxies = _settings.list();
  1411. proxies.push_back(proxy);
  1412. _list.push_back({ ++_idCounter, proxy });
  1413. refreshChecker(_list.back());
  1414. applyItem(_list.back().id);
  1415. }
  1416. bool ProxiesBoxController::setProxySettings(ProxyData::Settings value) {
  1417. if (_settings.settings() == value) {
  1418. return true;
  1419. } else if (value == ProxyData::Settings::Enabled) {
  1420. if (_settings.list().empty()) {
  1421. return false;
  1422. } else if (!_settings.selected()) {
  1423. _settings.setSelected(_settings.list().back());
  1424. auto j = findByProxy(_settings.selected());
  1425. if (j != end(_list)) {
  1426. updateView(*j);
  1427. }
  1428. }
  1429. }
  1430. Core::App().setCurrentProxy(_settings.selected(), value);
  1431. saveDelayed();
  1432. return true;
  1433. }
  1434. void ProxiesBoxController::setProxyForCalls(bool enabled) {
  1435. if (_settings.useProxyForCalls() == enabled) {
  1436. return;
  1437. }
  1438. _settings.setUseProxyForCalls(enabled);
  1439. if (_settings.isEnabled() && _settings.selected().supportsCalls()) {
  1440. _settings.connectionTypeChangesNotify();
  1441. }
  1442. saveDelayed();
  1443. }
  1444. void ProxiesBoxController::setTryIPv6(bool enabled) {
  1445. if (Core::App().settings().proxy().tryIPv6() == enabled) {
  1446. return;
  1447. }
  1448. Core::App().settings().proxy().setTryIPv6(enabled);
  1449. _account->mtp().restart();
  1450. _settings.connectionTypeChangesNotify();
  1451. saveDelayed();
  1452. }
  1453. void ProxiesBoxController::saveDelayed() {
  1454. _saveTimer.callOnce(kSaveSettingsDelayedTimeout);
  1455. }
  1456. auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
  1457. return _views.events();
  1458. }
  1459. void ProxiesBoxController::updateView(const Item &item) {
  1460. const auto selected = (_settings.selected() == item.data);
  1461. const auto deleted = item.deleted;
  1462. const auto type = [&] {
  1463. switch (item.data.type) {
  1464. case Type::Http: return u"HTTP"_q;
  1465. case Type::Socks5: return u"SOCKS5"_q;
  1466. case Type::Mtproto: return u"MTPROTO"_q;
  1467. }
  1468. Unexpected("Proxy type in ProxiesBoxController::updateView.");
  1469. }();
  1470. const auto state = [&] {
  1471. if (!selected || !_settings.isEnabled()) {
  1472. return item.state;
  1473. } else if (_account->mtp().dcstate() == MTP::ConnectedState) {
  1474. return ItemState::Online;
  1475. }
  1476. return ItemState::Connecting;
  1477. }();
  1478. const auto supportsShare = (item.data.type == Type::Socks5)
  1479. || (item.data.type == Type::Mtproto);
  1480. const auto supportsCalls = item.data.supportsCalls();
  1481. _views.fire({
  1482. item.id,
  1483. type,
  1484. item.data.host,
  1485. item.data.port,
  1486. item.ping,
  1487. !deleted && selected,
  1488. deleted,
  1489. !deleted && supportsShare,
  1490. supportsCalls,
  1491. state });
  1492. }
  1493. void ProxiesBoxController::share(const ProxyData &proxy) {
  1494. if (proxy.type == Type::Http) {
  1495. return;
  1496. }
  1497. const auto link = u"https://t.me/"_q
  1498. + (proxy.type == Type::Socks5 ? "socks" : "proxy")
  1499. + "?server=" + proxy.host + "&port=" + QString::number(proxy.port)
  1500. + ((proxy.type == Type::Socks5 && !proxy.user.isEmpty())
  1501. ? "&user=" + qthelp::url_encode(proxy.user) : "")
  1502. + ((proxy.type == Type::Socks5 && !proxy.password.isEmpty())
  1503. ? "&pass=" + qthelp::url_encode(proxy.password) : "")
  1504. + ((proxy.type == Type::Mtproto && !proxy.password.isEmpty())
  1505. ? "&secret=" + proxy.password : "");
  1506. QGuiApplication::clipboard()->setText(link);
  1507. _show->showToast(tr::lng_username_copied(tr::now));
  1508. }
  1509. ProxiesBoxController::~ProxiesBoxController() {
  1510. if (_saveTimer.isActive()) {
  1511. base::call_delayed(
  1512. kSaveSettingsDelayedTimeout,
  1513. QCoreApplication::instance(),
  1514. [] { Local::writeSettings(); });
  1515. }
  1516. }