1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "main/main_session_settings.h"
9
10 #include "chat_helpers/tabbed_selector.h"
11 #include "ui/widgets/input_fields.h"
12 #include "ui/chat/attach/attach_send_files_way.h"
13 #include "window/section_widget.h"
14 #include "support/support_common.h"
15 #include "storage/serialize_common.h"
16 #include "boxes/send_files_box.h"
17 #include "core/application.h"
18 #include "core/core_settings.h"
19
20 namespace Main {
21 namespace {
22
23 constexpr auto kLegacyCallsPeerToPeerNobody = 4;
24 constexpr auto kVersionTag = -1;
25 constexpr auto kVersion = 2;
26 constexpr auto kMaxSavedPlaybackPositions = 16;
27
28 } // namespace
29
SessionSettings()30 SessionSettings::SessionSettings()
31 : _selectorTab(ChatHelpers::SelectorTab::Emoji)
32 , _supportSwitch(Support::SwitchSettings::Next) {
33 }
34
serialize() const35 QByteArray SessionSettings::serialize() const {
36 const auto autoDownload = _autoDownload.serialize();
37 auto size = sizeof(qint32) * 38;
38 size += _groupStickersSectionHidden.size() * sizeof(quint64);
39 size += _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64);
40 size += Serialize::bytearraySize(autoDownload);
41 size += sizeof(qint32) + _hiddenPinnedMessages.size() * (sizeof(quint64) + sizeof(qint32));
42
43 auto result = QByteArray();
44 result.reserve(size);
45 {
46 QDataStream stream(&result, QIODevice::WriteOnly);
47 stream.setVersion(QDataStream::Qt_5_1);
48 stream << qint32(kVersionTag) << qint32(kVersion);
49 stream << static_cast<qint32>(_selectorTab);
50 stream << qint32(_groupStickersSectionHidden.size());
51 for (const auto &peerId : _groupStickersSectionHidden) {
52 stream << SerializePeerId(peerId);
53 }
54 stream << qint32(_supportSwitch);
55 stream << qint32(_supportFixChatsOrder ? 1 : 0);
56 stream << qint32(_supportTemplatesAutocomplete ? 1 : 0);
57 stream << qint32(_supportChatsTimeSlice.current());
58 stream << autoDownload;
59 stream << qint32(_supportAllSearchResults.current() ? 1 : 0);
60 stream << qint32(_archiveCollapsed.current() ? 1 : 0);
61 stream << qint32(_archiveInMainMenu.current() ? 1 : 0);
62 stream << qint32(_skipArchiveInSearch.current() ? 1 : 0);
63 stream << qint32(_mediaLastPlaybackPosition.size());
64 for (const auto &[id, time] : _mediaLastPlaybackPosition) {
65 stream << quint64(id) << qint64(time);
66 }
67 stream << qint32(0);
68 stream << qint32(_dialogsFiltersEnabled ? 1 : 0);
69 stream << qint32(_supportAllSilent ? 1 : 0);
70 stream << qint32(_photoEditorHintShowsCount);
71 stream << qint32(_hiddenPinnedMessages.size());
72 for (const auto &[key, value] : _hiddenPinnedMessages) {
73 stream << SerializePeerId(key) << qint64(value.bare);
74 }
75 }
76 return result;
77 }
78
addFromSerialized(const QByteArray & serialized)79 void SessionSettings::addFromSerialized(const QByteArray &serialized) {
80 if (serialized.isEmpty()) {
81 return;
82 }
83
84 auto &app = Core::App().settings();
85
86 QDataStream stream(serialized);
87 stream.setVersion(QDataStream::Qt_5_1);
88 qint32 versionTag = 0;
89 qint32 version = 0;
90 qint32 selectorTab = static_cast<qint32>(ChatHelpers::SelectorTab::Emoji);
91 qint32 appLastSeenWarningSeen = app.lastSeenWarningSeen() ? 1 : 0;
92 qint32 appTabbedSelectorSectionEnabled = 1;
93 qint32 legacyTabbedSelectorSectionTooltipShown = 0;
94 qint32 appFloatPlayerColumn = static_cast<qint32>(Window::Column::Second);
95 qint32 appFloatPlayerCorner = static_cast<qint32>(RectPart::TopRight);
96 base::flat_map<QString, QString> appSoundOverrides;
97 base::flat_set<PeerId> groupStickersSectionHidden;
98 qint32 appThirdSectionInfoEnabled = 0;
99 qint32 legacySmallDialogsList = 0;
100 float64 appDialogsWidthRatio = app.dialogsWidthRatio();
101 int appThirdColumnWidth = app.thirdColumnWidth();
102 int appThirdSectionExtendedBy = app.thirdSectionExtendedBy();
103 qint32 appSendFilesWay = app.sendFilesWay().serialize();
104 qint32 legacyCallsPeerToPeer = qint32(0);
105 qint32 appSendSubmitWay = static_cast<qint32>(app.sendSubmitWay());
106 qint32 supportSwitch = static_cast<qint32>(_supportSwitch);
107 qint32 supportFixChatsOrder = _supportFixChatsOrder ? 1 : 0;
108 qint32 supportTemplatesAutocomplete = _supportTemplatesAutocomplete ? 1 : 0;
109 qint32 supportChatsTimeSlice = _supportChatsTimeSlice.current();
110 qint32 appIncludeMutedCounter = app.includeMutedCounter() ? 1 : 0;
111 qint32 appCountUnreadMessages = app.countUnreadMessages() ? 1 : 0;
112 qint32 appExeLaunchWarning = app.exeLaunchWarning() ? 1 : 0;
113 QByteArray autoDownload;
114 qint32 supportAllSearchResults = _supportAllSearchResults.current() ? 1 : 0;
115 qint32 archiveCollapsed = _archiveCollapsed.current() ? 1 : 0;
116 qint32 appNotifyAboutPinned = app.notifyAboutPinned() ? 1 : 0;
117 qint32 archiveInMainMenu = _archiveInMainMenu.current() ? 1 : 0;
118 qint32 skipArchiveInSearch = _skipArchiveInSearch.current() ? 1 : 0;
119 qint32 legacyAutoplayGifs = 1;
120 qint32 appLoopAnimatedStickers = app.loopAnimatedStickers() ? 1 : 0;
121 qint32 appLargeEmoji = app.largeEmoji() ? 1 : 0;
122 qint32 appReplaceEmoji = app.replaceEmoji() ? 1 : 0;
123 qint32 appSuggestEmoji = app.suggestEmoji() ? 1 : 0;
124 qint32 appSuggestStickersByEmoji = app.suggestStickersByEmoji() ? 1 : 0;
125 qint32 appSpellcheckerEnabled = app.spellcheckerEnabled() ? 1 : 0;
126 std::vector<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
127 qint32 appVideoPlaybackSpeed = Core::Settings::SerializePlaybackSpeed(app.videoPlaybackSpeed());
128 QByteArray appVideoPipGeometry = app.videoPipGeometry();
129 std::vector<int> appDictionariesEnabled;
130 qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0;
131 base::flat_map<PeerId, MsgId> hiddenPinnedMessages;
132 qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0;
133 qint32 supportAllSilent = _supportAllSilent ? 1 : 0;
134 qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount;
135
136 stream >> versionTag;
137 if (versionTag == kVersionTag) {
138 stream >> version;
139 stream >> selectorTab;
140 } else {
141 selectorTab = versionTag;
142 }
143 if (version < 2) {
144 stream >> appLastSeenWarningSeen;
145 if (!stream.atEnd()) {
146 stream >> appTabbedSelectorSectionEnabled;
147 }
148 if (!stream.atEnd()) {
149 auto count = qint32(0);
150 stream >> count;
151 if (stream.status() == QDataStream::Ok) {
152 for (auto i = 0; i != count; ++i) {
153 QString key, value;
154 stream >> key >> value;
155 if (stream.status() != QDataStream::Ok) {
156 LOG(("App Error: "
157 "Bad data for SessionSettings::addFromSerialized()"));
158 return;
159 }
160 appSoundOverrides.emplace(key, value);
161 }
162 }
163 }
164 if (!stream.atEnd()) {
165 stream >> legacyTabbedSelectorSectionTooltipShown;
166 }
167 if (!stream.atEnd()) {
168 stream >> appFloatPlayerColumn >> appFloatPlayerCorner;
169 }
170 }
171 if (!stream.atEnd()) {
172 auto count = qint32(0);
173 stream >> count;
174 if (stream.status() == QDataStream::Ok) {
175 for (auto i = 0; i != count; ++i) {
176 quint64 peerId;
177 stream >> peerId;
178 if (stream.status() != QDataStream::Ok) {
179 LOG(("App Error: "
180 "Bad data for SessionSettings::addFromSerialized()"));
181 return;
182 }
183 groupStickersSectionHidden.emplace(
184 DeserializePeerId(peerId));
185 }
186 }
187 }
188 if (version < 2) {
189 if (!stream.atEnd()) {
190 stream >> appThirdSectionInfoEnabled;
191 stream >> legacySmallDialogsList;
192 }
193 if (!stream.atEnd()) {
194 qint32 value = 0;
195 stream >> value;
196 appDialogsWidthRatio = std::clamp(value / 1000000., 0., 1.);
197
198 stream >> value;
199 appThirdColumnWidth = value;
200
201 stream >> value;
202 appThirdSectionExtendedBy = value;
203 }
204 if (!stream.atEnd()) {
205 stream >> appSendFilesWay;
206 }
207 if (!stream.atEnd()) {
208 stream >> legacyCallsPeerToPeer;
209 }
210 }
211 if (!stream.atEnd()) {
212 if (version < 2) {
213 stream >> appSendSubmitWay;
214 }
215 stream >> supportSwitch;
216 stream >> supportFixChatsOrder;
217 }
218 if (!stream.atEnd()) {
219 stream >> supportTemplatesAutocomplete;
220 }
221 if (!stream.atEnd()) {
222 stream >> supportChatsTimeSlice;
223 }
224 if (version < 2) {
225 if (!stream.atEnd()) {
226 stream >> appIncludeMutedCounter;
227 stream >> appCountUnreadMessages;
228 }
229 if (!stream.atEnd()) {
230 stream >> appExeLaunchWarning;
231 }
232 }
233 if (!stream.atEnd()) {
234 stream >> autoDownload;
235 }
236 if (!stream.atEnd()) {
237 stream >> supportAllSearchResults;
238 }
239 if (!stream.atEnd()) {
240 stream >> archiveCollapsed;
241 }
242 if (version < 2) {
243 if (!stream.atEnd()) {
244 stream >> appNotifyAboutPinned;
245 }
246 }
247 if (!stream.atEnd()) {
248 stream >> archiveInMainMenu;
249 }
250 if (!stream.atEnd()) {
251 stream >> skipArchiveInSearch;
252 }
253 if (version < 2) {
254 if (!stream.atEnd()) {
255 stream >> legacyAutoplayGifs;
256 stream >> appLoopAnimatedStickers;
257 stream >> appLargeEmoji;
258 stream >> appReplaceEmoji;
259 stream >> appSuggestEmoji;
260 stream >> appSuggestStickersByEmoji;
261 }
262 if (!stream.atEnd()) {
263 stream >> appSpellcheckerEnabled;
264 }
265 }
266 if (!stream.atEnd()) {
267 auto count = qint32(0);
268 stream >> count;
269 if (stream.status() == QDataStream::Ok) {
270 for (auto i = 0; i != count; ++i) {
271 quint64 documentId;
272 qint64 time;
273 stream >> documentId >> time;
274 if (stream.status() != QDataStream::Ok) {
275 LOG(("App Error: "
276 "Bad data for SessionSettings::addFromSerialized()"));
277 return;
278 }
279 mediaLastPlaybackPosition.emplace_back(documentId, time);
280 }
281 }
282 }
283 if (version < 2) {
284 if (!stream.atEnd()) {
285 stream >> appVideoPlaybackSpeed;
286 }
287 if (!stream.atEnd()) {
288 stream >> appVideoPipGeometry;
289 }
290 if (!stream.atEnd()) {
291 auto count = qint32(0);
292 stream >> count;
293 if (stream.status() == QDataStream::Ok) {
294 for (auto i = 0; i != count; ++i) {
295 qint64 langId;
296 stream >> langId;
297 if (stream.status() != QDataStream::Ok) {
298 LOG(("App Error: "
299 "Bad data for SessionSettings::addFromSerialized()"));
300 return;
301 }
302 appDictionariesEnabled.emplace_back(langId);
303 }
304 }
305 }
306 if (!stream.atEnd()) {
307 stream >> appAutoDownloadDictionaries;
308 }
309 }
310 if (!stream.atEnd()) {
311 auto count = qint32(0);
312 stream >> count;
313 if (stream.status() == QDataStream::Ok) {
314 // Legacy.
315 for (auto i = 0; i != count; ++i) {
316 auto key = quint64();
317 auto value = qint32();
318 stream >> key >> value;
319 if (stream.status() != QDataStream::Ok) {
320 LOG(("App Error: "
321 "Bad data for SessionSettings::addFromSerialized()"));
322 return;
323 }
324 hiddenPinnedMessages.emplace(DeserializePeerId(key), value);
325 }
326 }
327 }
328 if (!stream.atEnd()) {
329 stream >> dialogsFiltersEnabled;
330 }
331 if (!stream.atEnd()) {
332 stream >> supportAllSilent;
333 }
334 if (!stream.atEnd()) {
335 stream >> photoEditorHintShowsCount;
336 }
337 if (!stream.atEnd()) {
338 auto count = qint32(0);
339 stream >> count;
340 if (stream.status() == QDataStream::Ok) {
341 for (auto i = 0; i != count; ++i) {
342 auto key = quint64();
343 auto value = qint64();
344 stream >> key >> value;
345 if (stream.status() != QDataStream::Ok) {
346 LOG(("App Error: "
347 "Bad data for SessionSettings::addFromSerialized()"));
348 return;
349 }
350 hiddenPinnedMessages.emplace(DeserializePeerId(key), value);
351 }
352 }
353 }
354 if (stream.status() != QDataStream::Ok) {
355 LOG(("App Error: "
356 "Bad data for SessionSettings::addFromSerialized()"));
357 return;
358 }
359 if (!autoDownload.isEmpty()
360 && !_autoDownload.setFromSerialized(autoDownload)) {
361 return;
362 }
363 if (!version) {
364 if (!legacyAutoplayGifs) {
365 using namespace Data::AutoDownload;
366 _autoDownload = WithDisabledAutoPlay(_autoDownload);
367 }
368 }
369
370 auto uncheckedTab = static_cast<ChatHelpers::SelectorTab>(selectorTab);
371 switch (uncheckedTab) {
372 case ChatHelpers::SelectorTab::Emoji:
373 case ChatHelpers::SelectorTab::Stickers:
374 case ChatHelpers::SelectorTab::Gifs: _selectorTab = uncheckedTab; break;
375 }
376 _groupStickersSectionHidden = std::move(groupStickersSectionHidden);
377 auto uncheckedSupportSwitch = static_cast<Support::SwitchSettings>(
378 supportSwitch);
379 switch (uncheckedSupportSwitch) {
380 case Support::SwitchSettings::None:
381 case Support::SwitchSettings::Next:
382 case Support::SwitchSettings::Previous: _supportSwitch = uncheckedSupportSwitch; break;
383 }
384 _supportFixChatsOrder = (supportFixChatsOrder == 1);
385 _supportTemplatesAutocomplete = (supportTemplatesAutocomplete == 1);
386 _supportChatsTimeSlice = supportChatsTimeSlice;
387 _hadLegacyCallsPeerToPeerNobody = (legacyCallsPeerToPeer == kLegacyCallsPeerToPeerNobody);
388 _supportAllSearchResults = (supportAllSearchResults == 1);
389 _archiveCollapsed = (archiveCollapsed == 1);
390 _archiveInMainMenu = (archiveInMainMenu == 1);
391 _skipArchiveInSearch = (skipArchiveInSearch == 1);
392 _mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition);
393 _hiddenPinnedMessages = std::move(hiddenPinnedMessages);
394 _dialogsFiltersEnabled = (dialogsFiltersEnabled == 1);
395 _supportAllSilent = (supportAllSilent == 1);
396 _photoEditorHintShowsCount = std::move(photoEditorHintShowsCount);
397
398 if (version < 2) {
399 app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1);
400 for (const auto &[key, value] : appSoundOverrides) {
401 app.setSoundOverride(key, value);
402 }
403 if (const auto sendFilesWay = Ui::SendFilesWay::FromSerialized(appSendFilesWay)) {
404 app.setSendFilesWay(*sendFilesWay);
405 }
406 auto uncheckedSendSubmitWay = static_cast<Ui::InputSubmitSettings>(
407 appSendSubmitWay);
408 switch (uncheckedSendSubmitWay) {
409 case Ui::InputSubmitSettings::Enter:
410 case Ui::InputSubmitSettings::CtrlEnter: app.setSendSubmitWay(uncheckedSendSubmitWay); break;
411 }
412 app.setIncludeMutedCounter(appIncludeMutedCounter == 1);
413 app.setCountUnreadMessages(appCountUnreadMessages == 1);
414 app.setExeLaunchWarning(appExeLaunchWarning == 1);
415 app.setNotifyAboutPinned(appNotifyAboutPinned == 1);
416 app.setLoopAnimatedStickers(appLoopAnimatedStickers == 1);
417 app.setLargeEmoji(appLargeEmoji == 1);
418 app.setReplaceEmoji(appReplaceEmoji == 1);
419 app.setSuggestEmoji(appSuggestEmoji == 1);
420 app.setSuggestStickersByEmoji(appSuggestStickersByEmoji == 1);
421 app.setSpellcheckerEnabled(appSpellcheckerEnabled == 1);
422 app.setVideoPlaybackSpeed(Core::Settings::DeserializePlaybackSpeed(appVideoPlaybackSpeed));
423 app.setVideoPipGeometry(appVideoPipGeometry);
424 app.setDictionariesEnabled(std::move(appDictionariesEnabled));
425 app.setAutoDownloadDictionaries(appAutoDownloadDictionaries == 1);
426 app.setTabbedSelectorSectionEnabled(appTabbedSelectorSectionEnabled == 1);
427 auto uncheckedColumn = static_cast<Window::Column>(appFloatPlayerColumn);
428 switch (uncheckedColumn) {
429 case Window::Column::First:
430 case Window::Column::Second:
431 case Window::Column::Third: app.setFloatPlayerColumn(uncheckedColumn); break;
432 }
433 auto uncheckedCorner = static_cast<RectPart>(appFloatPlayerCorner);
434 switch (uncheckedCorner) {
435 case RectPart::TopLeft:
436 case RectPart::TopRight:
437 case RectPart::BottomLeft:
438 case RectPart::BottomRight: app.setFloatPlayerCorner(uncheckedCorner); break;
439 }
440 app.setThirdSectionInfoEnabled(appThirdSectionInfoEnabled);
441 app.setDialogsWidthRatio(appDialogsWidthRatio);
442 app.setThirdColumnWidth(appThirdColumnWidth);
443 app.setThirdSectionExtendedBy(appThirdSectionExtendedBy);
444 }
445 }
446
setSupportChatsTimeSlice(int slice)447 void SessionSettings::setSupportChatsTimeSlice(int slice) {
448 _supportChatsTimeSlice = slice;
449 }
450
supportChatsTimeSlice() const451 int SessionSettings::supportChatsTimeSlice() const {
452 return _supportChatsTimeSlice.current();
453 }
454
supportChatsTimeSliceValue() const455 rpl::producer<int> SessionSettings::supportChatsTimeSliceValue() const {
456 return _supportChatsTimeSlice.value();
457 }
458
setSupportAllSearchResults(bool all)459 void SessionSettings::setSupportAllSearchResults(bool all) {
460 _supportAllSearchResults = all;
461 }
462
supportAllSearchResults() const463 bool SessionSettings::supportAllSearchResults() const {
464 return _supportAllSearchResults.current();
465 }
466
supportAllSearchResultsValue() const467 rpl::producer<bool> SessionSettings::supportAllSearchResultsValue() const {
468 return _supportAllSearchResults.value();
469 }
470
setMediaLastPlaybackPosition(DocumentId id,crl::time time)471 void SessionSettings::setMediaLastPlaybackPosition(DocumentId id, crl::time time) {
472 auto &map = _mediaLastPlaybackPosition;
473 const auto i = ranges::find(
474 map,
475 id,
476 &std::pair<DocumentId, crl::time>::first);
477 if (i != map.end()) {
478 if (time > 0) {
479 i->second = time;
480 } else {
481 map.erase(i);
482 }
483 } else if (time > 0) {
484 if (map.size() >= kMaxSavedPlaybackPositions) {
485 map.erase(map.begin());
486 }
487 map.emplace_back(id, time);
488 }
489 }
490
mediaLastPlaybackPosition(DocumentId id) const491 crl::time SessionSettings::mediaLastPlaybackPosition(DocumentId id) const {
492 const auto i = ranges::find(
493 _mediaLastPlaybackPosition,
494 id,
495 &std::pair<DocumentId, crl::time>::first);
496 return (i != _mediaLastPlaybackPosition.end()) ? i->second : 0;
497 }
498
setArchiveCollapsed(bool collapsed)499 void SessionSettings::setArchiveCollapsed(bool collapsed) {
500 _archiveCollapsed = collapsed;
501 }
502
archiveCollapsed() const503 bool SessionSettings::archiveCollapsed() const {
504 return _archiveCollapsed.current();
505 }
506
archiveCollapsedChanges() const507 rpl::producer<bool> SessionSettings::archiveCollapsedChanges() const {
508 return _archiveCollapsed.changes();
509 }
510
setArchiveInMainMenu(bool inMainMenu)511 void SessionSettings::setArchiveInMainMenu(bool inMainMenu) {
512 _archiveInMainMenu = inMainMenu;
513 }
514
archiveInMainMenu() const515 bool SessionSettings::archiveInMainMenu() const {
516 return _archiveInMainMenu.current();
517 }
518
archiveInMainMenuChanges() const519 rpl::producer<bool> SessionSettings::archiveInMainMenuChanges() const {
520 return _archiveInMainMenu.changes();
521 }
522
setSkipArchiveInSearch(bool skip)523 void SessionSettings::setSkipArchiveInSearch(bool skip) {
524 _skipArchiveInSearch = skip;
525 }
526
skipArchiveInSearch() const527 bool SessionSettings::skipArchiveInSearch() const {
528 return _skipArchiveInSearch.current();
529 }
530
skipArchiveInSearchChanges() const531 rpl::producer<bool> SessionSettings::skipArchiveInSearchChanges() const {
532 return _skipArchiveInSearch.changes();
533 }
534
photoEditorHintShown() const535 bool SessionSettings::photoEditorHintShown() const {
536 return _photoEditorHintShowsCount < kPhotoEditorHintMaxShowsCount;
537 }
538
incrementPhotoEditorHintShown()539 void SessionSettings::incrementPhotoEditorHintShown() {
540 if (photoEditorHintShown()) {
541 _photoEditorHintShowsCount++;
542 }
543 }
544
545 } // namespace Main
546