settings_codes.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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 "settings/settings_codes.h"
  8. #include "ui/toast/toast.h"
  9. #include "mainwidget.h"
  10. #include "mainwindow.h"
  11. #include "data/data_session.h"
  12. #include "data/data_cloud_themes.h"
  13. #include "history/history_item_components.h"
  14. #include "main/main_session.h"
  15. #include "main/main_account.h"
  16. #include "main/main_domain.h"
  17. #include "ui/boxes/confirm_box.h"
  18. #include "lang/lang_cloud_manager.h"
  19. #include "lang/lang_instance.h"
  20. #include "core/application.h"
  21. #include "mtproto/mtp_instance.h"
  22. #include "mtproto/mtproto_dc_options.h"
  23. #include "core/file_utilities.h"
  24. #include "core/update_checker.h"
  25. #include "window/themes/window_theme.h"
  26. #include "window/themes/window_theme_editor.h"
  27. #include "window/window_session_controller.h"
  28. #include "media/audio/media_audio_track.h"
  29. #include "settings/settings_folders.h"
  30. #include "storage/storage_account.h"
  31. #include "api/api_updates.h"
  32. #include "base/qt/qt_common_adapters.h"
  33. #include "base/custom_app_icon.h"
  34. #include "base/options.h"
  35. #include "boxes/abstract_box.h" // Ui::show().
  36. #include <zlib.h>
  37. namespace Settings {
  38. namespace {
  39. using SessionController = Window::SessionController;
  40. [[nodiscard]] QByteArray UnpackRawGzip(const QByteArray &bytes) {
  41. z_stream stream;
  42. stream.zalloc = nullptr;
  43. stream.zfree = nullptr;
  44. stream.opaque = nullptr;
  45. stream.avail_in = 0;
  46. stream.next_in = nullptr;
  47. int res = inflateInit2(&stream, -MAX_WBITS);
  48. if (res != Z_OK) {
  49. return QByteArray();
  50. }
  51. const auto guard = gsl::finally([&] { inflateEnd(&stream); });
  52. auto result = QByteArray(1024 * 1024, char(0));
  53. stream.avail_in = bytes.size();
  54. stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(bytes.data()));
  55. stream.avail_out = 0;
  56. while (!stream.avail_out) {
  57. stream.avail_out = result.size();
  58. stream.next_out = reinterpret_cast<Bytef*>(result.data());
  59. int res = inflate(&stream, Z_NO_FLUSH);
  60. if (res != Z_OK && res != Z_STREAM_END) {
  61. return QByteArray();
  62. } else if (!stream.avail_out) {
  63. return QByteArray();
  64. }
  65. }
  66. result.resize(result.size() - stream.avail_out);
  67. return result;
  68. }
  69. auto GenerateCodes() {
  70. auto codes = std::map<QString, Fn<void(SessionController*)>>();
  71. codes.emplace(u"debugmode"_q, [](SessionController *window) {
  72. QString text = Logs::DebugEnabled()
  73. ? u"Do you want to disable DEBUG logs?"_q
  74. : u"Do you want to enable DEBUG logs?\n\nAll network events will be logged."_q;
  75. Ui::show(Ui::MakeConfirmBox({ text, [] {
  76. Core::App().switchDebugMode();
  77. } }));
  78. });
  79. codes.emplace(u"viewlogs"_q, [](SessionController *window) {
  80. File::ShowInFolder(cWorkingDir() + "log.txt");
  81. });
  82. if (!Core::UpdaterDisabled()) {
  83. codes.emplace(u"testupdate"_q, [](SessionController *window) {
  84. Core::UpdateChecker().test();
  85. });
  86. }
  87. codes.emplace(u"loadlang"_q, [](SessionController *window) {
  88. Lang::CurrentCloudManager().switchToLanguage({ u"#custom"_q });
  89. });
  90. codes.emplace(u"crashplease"_q, [](SessionController *window) {
  91. Unexpected("Crashed in Settings!");
  92. });
  93. codes.emplace(u"moderate"_q, [](SessionController *window) {
  94. auto text = Core::App().settings().moderateModeEnabled() ? u"Disable moderate mode?"_q : u"Enable moderate mode?"_q;
  95. Ui::show(Ui::MakeConfirmBox({ text, [=] {
  96. Core::App().settings().setModerateModeEnabled(!Core::App().settings().moderateModeEnabled());
  97. Core::App().saveSettingsDelayed();
  98. Ui::hideLayer();
  99. } }));
  100. });
  101. codes.emplace(u"getdifference"_q, [](SessionController *window) {
  102. if (window) {
  103. window->session().updates().getDifference();
  104. }
  105. });
  106. codes.emplace(u"loadcolors"_q, [](SessionController *window) {
  107. FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) {
  108. if (!result.paths.isEmpty()) {
  109. Window::Theme::Apply(result.paths.front());
  110. }
  111. });
  112. });
  113. codes.emplace(u"endpoints"_q, [](SessionController *window) {
  114. if (!Core::App().domain().started()) {
  115. return;
  116. }
  117. const auto weak = window
  118. ? base::make_weak(&window->session().account())
  119. : nullptr;
  120. FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open DC endpoints", "DC Endpoints (*.tdesktop-endpoints)", [weak](const FileDialog::OpenResult &result) {
  121. if (!result.paths.isEmpty()) {
  122. const auto loadFor = [&](not_null<Main::Account*> account) {
  123. if (!account->mtp().dcOptions().loadFromFile(result.paths.front())) {
  124. Ui::show(Ui::MakeInformBox("Could not load endpoints"
  125. " :( Errors in 'log.txt'."));
  126. }
  127. };
  128. if (const auto strong = weak.get()) {
  129. loadFor(strong);
  130. } else {
  131. for (const auto &pair : Core::App().domain().accounts()) {
  132. loadFor(pair.account.get());
  133. }
  134. }
  135. }
  136. });
  137. });
  138. codes.emplace(u"testmode"_q, [](SessionController *window) {
  139. auto &domain = Core::App().domain();
  140. if (domain.started()
  141. && (domain.accounts().size() == 1)
  142. && !domain.active().sessionExists()) {
  143. const auto environment = domain.active().mtp().environment();
  144. domain.addActivated([&] {
  145. return (environment == MTP::Environment::Production)
  146. ? MTP::Environment::Test
  147. : MTP::Environment::Production;
  148. }());
  149. Ui::Toast::Show((environment == MTP::Environment::Production)
  150. ? "Switched to the test environment."
  151. : "Switched to the production environment.");
  152. }
  153. });
  154. codes.emplace(u"folders"_q, [](SessionController *window) {
  155. if (window) {
  156. window->showSettings(Settings::Folders::Id());
  157. }
  158. });
  159. codes.emplace(u"registertg"_q, [](SessionController *window) {
  160. Core::Application::RegisterUrlScheme();
  161. Ui::Toast::Show("Forced custom scheme register.");
  162. });
  163. codes.emplace(u"numberbuttons"_q, [](SessionController *window) {
  164. using namespace base::options;
  165. auto &option = lookup<bool>(kOptionFastButtonsMode);
  166. const auto now = !option.value();
  167. option.set(now);
  168. Ui::Toast::Show(now
  169. ? u"Fast buttons mode enabled."_q
  170. : u"Fast buttons mode disabled."_q);
  171. });
  172. auto audioFilters = u"Audio files (*.wav *.mp3);;"_q + FileDialog::AllFilesFilter();
  173. auto audioKeys = {
  174. u"msg_incoming"_q,
  175. u"call_incoming"_q,
  176. u"call_outgoing"_q,
  177. u"call_busy"_q,
  178. u"call_connect"_q,
  179. u"call_end"_q,
  180. };
  181. for (auto &key : audioKeys) {
  182. codes.emplace(key, [=](SessionController *window) {
  183. FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open audio file", audioFilters, [=](const FileDialog::OpenResult &result) {
  184. if (!result.paths.isEmpty()) {
  185. auto track = Media::Audio::Current().createTrack();
  186. track->fillFromFile(result.paths.front());
  187. if (track->failed()) {
  188. Ui::show(Ui::MakeInformBox(
  189. "Could not audio :( Errors in 'log.txt'."));
  190. } else {
  191. Core::App().settings().setSoundOverride(
  192. key,
  193. result.paths.front());
  194. Core::App().saveSettingsDelayed();
  195. }
  196. }
  197. });
  198. });
  199. }
  200. codes.emplace(u"sounds_reset"_q, [](SessionController *window) {
  201. Core::App().settings().clearSoundOverrides();
  202. Core::App().saveSettingsDelayed();
  203. Ui::show(Ui::MakeInformBox("All sound overrides were reset."));
  204. });
  205. codes.emplace(u"unpacklog"_q, [](SessionController *window) {
  206. FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open crash log file", "Crash dump (*.txt)", [=](const FileDialog::OpenResult &result) {
  207. if (result.paths.isEmpty()) {
  208. return;
  209. }
  210. auto f = QFile(result.paths.front());
  211. if (!f.open(QIODevice::ReadOnly)) {
  212. Ui::Toast::Show("Could not open log :(");
  213. return;
  214. }
  215. const auto all = f.readAll();
  216. const auto log = all.indexOf("Log: ");
  217. if (log < 0) {
  218. Ui::Toast::Show("Could not find log :(");
  219. return;
  220. }
  221. const auto base = all.mid(log + 5);
  222. const auto end = base.indexOf('\n');
  223. if (end <= 0) {
  224. Ui::Toast::Show("Could not find log end :(");
  225. return;
  226. }
  227. const auto based = QByteArray::fromBase64(base.mid(0, end));
  228. const auto uncompressed = UnpackRawGzip(based);
  229. if (uncompressed.isEmpty()) {
  230. Ui::Toast::Show("Could not unpack log :(");
  231. return;
  232. }
  233. FileDialog::GetWritePath(Core::App().getFileDialogParent(), "Save detailed log", "Crash dump (*.txt)", QString(), [=](QString &&result) {
  234. if (result.isEmpty()) {
  235. return;
  236. }
  237. auto f = QFile(result);
  238. if (!f.open(QIODevice::WriteOnly)) {
  239. Ui::Toast::Show("Could not open details :(");
  240. } else if (f.write(uncompressed) != uncompressed.size()) {
  241. Ui::Toast::Show("Could not write details :(");
  242. } else {
  243. f.close();
  244. Ui::Toast::Show("Done!");
  245. }
  246. });
  247. });
  248. });
  249. codes.emplace(u"testchatcolors"_q, [](SessionController *window) {
  250. const auto now = !Data::CloudThemes::TestingColors();
  251. Data::CloudThemes::SetTestingColors(now);
  252. Ui::Toast::Show(now ? "Testing chat theme colors!" : "Not testing..");
  253. });
  254. #ifdef Q_OS_MAC
  255. codes.emplace(u"customicon"_q, [](SessionController *window) {
  256. const auto iconFilters = u"Icon files (*.icns *.png);;"_q + FileDialog::AllFilesFilter();
  257. const auto change = [](const QString &path) {
  258. const auto success = path.isEmpty()
  259. ? base::ClearCustomAppIcon()
  260. : base::SetCustomAppIcon(path);
  261. Ui::Toast::Show(success
  262. ? (path.isEmpty()
  263. ? "Icon cleared. Restarting the Dock."
  264. : "Icon updated. Restarting the Dock.")
  265. : (path.isEmpty()
  266. ? "Icon clear failed. See log.txt for details."
  267. : "Icon update failed. See log.txt for details."));
  268. };
  269. FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Choose custom icon", iconFilters, [=](const FileDialog::OpenResult &result) {
  270. change(result.paths.isEmpty() ? QString() : result.paths.front());
  271. }, [=] {
  272. change(QString());
  273. });
  274. });
  275. #endif // Q_OS_MAC
  276. return codes;
  277. }
  278. } // namespace
  279. void CodesFeedString(SessionController *window, const QString &text) {
  280. static const auto codes = GenerateCodes();
  281. static auto secret = QString();
  282. secret += text.toLower();
  283. int size = secret.size(), from = 0;
  284. while (size > from) {
  285. auto piece = base::StringViewMid(secret,from);
  286. auto found = false;
  287. for (const auto &[key, method] : codes) {
  288. if (piece == key) {
  289. method(window);
  290. from = size;
  291. found = true;
  292. break;
  293. }
  294. }
  295. if (found) break;
  296. found = ranges::any_of(codes, [&](const auto &pair) {
  297. return pair.first.startsWith(piece);
  298. });
  299. if (found) break;
  300. ++from;
  301. }
  302. secret = (size > from) ? secret.mid(from) : QString();
  303. }
  304. } // namespace Settings