Merge remote-tracking branch 'tdesktop/dev' into dev

This commit is contained in:
Eric Kotato 2020-02-26 02:22:54 +03:00
commit 6aa631cba9
132 changed files with 4587 additions and 2228 deletions

View file

@ -31,8 +31,9 @@ jobs:
CMAKE_VER: "3.16.3"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
MANUAL_CACHING: "2"
MANUAL_CACHING: "6"
DOC_PATH: "docs/building-cmake.md"
AUTO_CACHING: "1"
steps:
- name: Get repository name.
@ -43,7 +44,20 @@ jobs:
with:
submodules: recursive
- name: Disable man for further package installs.
run: |
cfgFile="/etc/dpkg/dpkg.cfg.d/no_man"
sudo touch $cfgFile
p() {
sudo echo "path-exclude=/usr/share/$1/*" >> $cfgFile
}
p man
p locale
p doc
- name: First set up.
shell: bash
run: |
cd ..
mv $REPO_NAME temp
@ -55,13 +69,13 @@ jobs:
sudo apt-get install software-properties-common -y && \
sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \
libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \
autoconf automake build-essential libass-dev libfreetype6-dev \
autoconf automake build-essential libxml2-dev libass-dev libfreetype6-dev \
libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \
libvorbis-dev libenchant-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \
libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libatspi2.0-dev \
libxcb-render-util0-dev libxcb-util0-dev libxcb-xkb-dev libxrender-dev \
libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev bison \
libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html yasm \
libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libegl1-mesa-dev \
libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html bison yasm \
zlib1g-dev xutils-dev python-xcbgen chrpath gperf wget -y --force-yes && \
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
sudo apt-get update && \
@ -75,6 +89,10 @@ jobs:
gcc --version > CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/linux.yml
echo `md5sum $thisFile | cut -c -32` >> CACHE_KEY.txt
fi
md5cache=$(md5sum CACHE_KEY.txt | cut -c -32)
echo ::set-env name=CACHE_KEY::$md5cache
@ -111,16 +129,6 @@ jobs:
cmake --version
- name: Zlib.
run: |
cd $LibrariesPath
git clone $GIT/madler/zlib.git
cd zlib
./configure
make -j$(nproc)
sudo make install
- name: Opus cache.
id: cache-opus
uses: actions/cache@v1
@ -132,9 +140,8 @@ jobs:
run: |
cd $LibrariesPath
git clone $GIT/xiph/opus
git clone -b v1.3 --depth=1 $GIT/xiph/opus
cd opus
git checkout v1.3
./autogen.sh
./configure
make -j$(nproc)
@ -152,17 +159,20 @@ jobs:
./autogen.sh --enable-static
make -j$(nproc)
sudo make install
cd ..
rm -rf libva
- name: Libvdpau.
run: |
cd $LibrariesPath
git clone git://anongit.freedesktop.org/vdpau/libvdpau
git clone -b libvdpau-1.2 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git
cd libvdpau
git checkout libvdpau-1.2
./autogen.sh --enable-static
make -j$(nproc)
sudo make install
cd ..
rm -rf libvdpau
- name: FFmpeg cache.
id: cache-ffmpeg
@ -278,6 +288,8 @@ jobs:
make -j$(nproc)
sudo make install
cd ..
rm -rf ffmpeg
- name: FFmpeg install.
run: |
cd $LibrariesPath
@ -304,18 +316,20 @@ jobs:
./configure
make -j$(nproc)
sudo make install
cd ..
rm -rf portaudio
- name: OpenAL Soft.
run: |
cd $LibrariesPath
git clone $GIT/kcat/openal-soft.git
cd openal-soft
git checkout openal-soft-1.19.1
cd build
git clone -b openal-soft-1.19.1 --depth=1 $GIT/kcat/openal-soft.git
cd openal-soft/build
cmake -D LIBTYPE:STRING=STATIC ..
make -j$(nproc)
sudo make install
cd -
rm -rf openal-soft
- name: OpenSSL cache.
id: cache-openssl
@ -328,12 +342,14 @@ jobs:
run: |
cd $LibrariesPath
git clone $GIT/openssl/openssl openssl_$OPENSSL_VER
cd openssl_$OPENSSL_VER
git checkout OpenSSL_1_1_1-stable
git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \
$GIT/openssl/openssl openssl_${OPENSSL_VER}
cd openssl_${OPENSSL_VER}
./config --prefix=$LibrariesPath/openssl-cache
make -j$(nproc)
sudo make install
cd ..
rm -rf openssl_${OPENSSL_VER}
- name: OpenSSL install.
run: |
cd $LibrariesPath
@ -344,12 +360,25 @@ jobs:
run: |
cd $LibrariesPath
git clone $GIT/xkbcommon/libxkbcommon.git
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
cd libxkbcommon
git checkout xkbcommon-0.8.4
./autogen.sh
make -j$(nproc)
sudo make install
cd ..
rm -rf libxkbcommon
- name: Libwayland.
run: |
cd $LibrariesPath
git clone -b 1.16 https://gitlab.freedesktop.org/wayland/wayland
cd wayland
./autogen.sh --enable-static --disable-documentation
make -j$(nproc)
sudo make install
cd ..
rm -rf wayland
- name: Qt 5.12.5 cache.
id: cache-qt
@ -362,14 +391,12 @@ jobs:
run: |
cd $LibrariesPath
git clone git://code.qt.io/qt/qt5.git qt_$QT
cd qt_$QT
perl init-repository --module-subset=qtbase,qtimageformats
git checkout v5.12.5
git submodule update qtbase
git submodule update qtimageformats
git clone -b v5.12.5 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT}
cd qt_${QT}
perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg
git submodule update qtbase qtwayland qtimageformats qtsvg
cd qtbase
git apply ../../patches/qtbase_$QT.diff
git apply ../../patches/qtbase_${QT}.diff
cd src/plugins/platforminputcontexts
git clone $GIT/desktop-app/fcitx.git
git clone $GIT/desktop-app/hime.git
@ -389,10 +416,9 @@ jobs:
-qt-xcb \
-system-freetype \
-fontconfig \
-no-opengl \
-no-glib \
-no-gtk \
-static \
-dbus-runtime \
-openssl-linked \
-I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -ldl -lpthread" \
-nomake examples \
@ -400,6 +426,8 @@ jobs:
make -j$(nproc)
sudo make install
cd ..
rm -rf qt_${QT}
- name: Qt 5.12.5 install.
run: |
cd $LibrariesPath
@ -450,6 +478,8 @@ jobs:
make -j$(nproc) dump_syms
mv dump_syms $BreakpadCache/
cd ..
rm -rf gyp breakpad
- name: Breakpad install.
run: |
cd $LibrariesPath

View file

@ -35,6 +35,8 @@ jobs:
ONLY_CACHE: "false"
MANUAL_CACHING: "2"
DOC_PATH: "docs/building-xcode.md"
AUTO_CACHING: "1"
steps:
- name: Get repository name.
run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/}
@ -61,6 +63,10 @@ jobs:
echo $MIN_MAC >> CACHE_KEY.txt
echo $PREFIX >> CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/mac.yml
echo `md5 -q $thisFile` >> CACHE_KEY.txt
fi
echo ::set-env name=CACHE_KEY::`md5 -q CACHE_KEY.txt`
echo ::add-path::$PWD/Libraries/depot_tools

View file

@ -47,10 +47,6 @@ jobs:
md5cache=$(md5sum CMAKE_CACHE_KEY.txt | cut -c -32)
echo ::set-env name=CMAKE_CACHE_KEY::$md5cache
awk -v RS="" -v ORS="\n\n" '/^ enchant:/' snap/snapcraft.yaml > ENCHANT_CACHE_KEY.txt
md5cache=$(md5sum ENCHANT_CACHE_KEY.txt | cut -c -32)
echo ::set-env name=ENCHANT_CACHE_KEY::$md5cache
- name: CMake cache.
id: cache-cmake
uses: actions/cache@v1
@ -62,17 +58,6 @@ jobs:
if: steps.cache-cmake.outputs.cache-hit != 'true'
run: snapcraft build --destructive-mode cmake
- name: Enchant cache.
id: cache-enchant
uses: actions/cache@v1
with:
path: parts/enchant
key: ${{ runner.OS }}-enchant-${{ env.CACHE_KEY }}-${{ env.ENCHANT_CACHE_KEY }}
- name: Enchant build.
if: steps.cache-enchant.outputs.cache-hit != 'true'
run: snapcraft build --destructive-mode enchant
- name: Kotatogram Desktop snap build.
if: env.ONLY_CACHE == 'false'
run: snapcraft --destructive-mode
@ -95,5 +80,5 @@ jobs:
- name: Remove unneeded directories for cache.
run: |
rm -rf parts/{cmake,enchant}/{build,src,ubuntu}
rm -rf parts/{cmake,enchant}/state/{stage,prime}
rm -rf parts/cmake/{build,src,ubuntu}
rm -rf parts/cmake/state/{stage,prime}

View file

@ -30,6 +30,8 @@ jobs:
ONLY_CACHE: "false"
MANUAL_CACHING: "2"
DOC_PATH: "docs/building-msvc.md"
AUTO_CACHING: "1"
steps:
- name: Get repository name.
shell: bash
@ -69,6 +71,10 @@ jobs:
- name: Generate cache key.
shell: bash
run: |
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/win.yml
echo `md5sum $thisFile | awk '{ print $1 }'` >> CACHE_KEY.txt
fi
echo ::set-env name=CACHE_KEY::`md5sum CACHE_KEY.txt | awk '{ print $1 }'`
- name: Choco installs.

3
.gitmodules vendored
View file

@ -64,3 +64,6 @@
[submodule "Telegram/ThirdParty/libdbusmenu-qt"]
path = Telegram/ThirdParty/libdbusmenu-qt
url = https://github.com/desktop-app/libdbusmenu-qt.git
[submodule "Telegram/ThirdParty/hunspell"]
path = Telegram/ThirdParty/hunspell
url = https://github.com/hunspell/hunspell

View file

@ -66,18 +66,7 @@ generate_numbers(Telegram ${res_loc}/numbers.txt)
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
if (DESKTOP_APP_USE_PACKAGED)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads)
target_link_libraries(Telegram
PRIVATE
${CMAKE_DL_LIBS}
Threads::Threads
)
endif()
if (LINUX AND NOT TDESKTOP_DISABLE_DBUS_INTEGRATION)
if (LINUX AND NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_statusnotifieritem
@ -85,6 +74,10 @@ if (LINUX AND NOT TDESKTOP_DISABLE_DBUS_INTEGRATION)
)
endif()
if (add_hunspell_library)
target_link_libraries(Telegram PRIVATE desktop-app::external_hunspell)
endif()
target_link_libraries(Telegram
PRIVATE
tdesktop::lib_mtproto
@ -124,6 +117,17 @@ if (NOT HAVE_LONG_ATOMIC_WITHOUT_LIB)
target_link_libraries(Telegram PRIVATE atomic)
endif()
if (DESKTOP_APP_USE_PACKAGED)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads)
target_link_libraries(Telegram
PRIVATE
${CMAKE_DL_LIBS}
Threads::Threads
)
endif()
target_precompile_headers(Telegram PRIVATE ${src_loc}/stdafx.h)
nice_target_sources(Telegram ${src_loc}
PRIVATE
@ -185,6 +189,8 @@ PRIVATE
boxes/connection_box.h
boxes/create_poll_box.cpp
boxes/create_poll_box.h
boxes/dictionaries_manager.cpp
boxes/dictionaries_manager.h
boxes/download_path_box.cpp
boxes/download_path_box.h
boxes/edit_caption_box.cpp
@ -261,6 +267,8 @@ PRIVATE
chat_helpers/gifs_list_widget.h
chat_helpers/message_field.cpp
chat_helpers/message_field.h
chat_helpers/spellchecker_common.cpp
chat_helpers/spellchecker_common.h
chat_helpers/stickers.cpp
chat_helpers/stickers.h
chat_helpers/stickers_emoji_pack.cpp
@ -341,6 +349,8 @@ PRIVATE
data/data_game.h
data/data_groups.cpp
data/data_groups.h
data/data_histories.cpp
data/data_histories.h
data/data_location.cpp
data/data_location.h
data/data_media_rotation.cpp
@ -854,6 +864,8 @@ PRIVATE
storage/serialize_common.h
storage/serialize_document.cpp
storage/serialize_document.h
storage/storage_cloud_blob.cpp
storage/storage_cloud_blob.h
storage/storage_facade.cpp
storage/storage_facade.h
# storage/storage_feed_messages.cpp
@ -1084,8 +1096,8 @@ endif()
if (build_macstore)
set(bundle_identifier "io.github.kotatogram")
set(bundle_entitlements "Telegram Desktop.entitlements")
set(output_name "Kotatogram Desktop")
set(bundle_entitlements "Telegram Lite.entitlements")
set(output_name "Kotatogram Lite")
set_target_properties(Telegram PROPERTIES
XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${libs_loc}/breakpad/src/client/mac/build/Release
)
@ -1129,7 +1141,7 @@ set_target_properties(Telegram PROPERTIES
)
set(entitlement_sources
"${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram.entitlements"
"${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram Desktop.entitlements"
"${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram Lite.entitlements"
)
target_sources(Telegram PRIVATE ${entitlement_sources})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Telegram PREFIX Resources FILES ${entitlement_sources})

View file

@ -422,6 +422,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_spellchecker" = "Spell checker";
"lng_settings_system_spellchecker" = "Use system spell checker";
"lng_settings_custom_spellchecker" = "Use spell checker";
"lng_settings_auto_download_dictionaries" = "Automatic dictionaries download";
"lng_settings_manage_dictionaries" = "Manage dictionaries";
"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled";
"lng_settings_manage_remove_dictionary" = "Remove Dictionary";
"lng_backgrounds_header" = "Choose your new chat background";
"lng_theme_sure_keep" = "Keep this theme?";
@ -1369,7 +1374,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_user_action_upload_file" = "{user} is sending a file";
"lng_unread_bar#one" = "{count} unread message";
"lng_unread_bar#other" = "{count} unread messages";
//"lng_unread_bar_some" = "Unread messages";
"lng_unread_bar_some" = "Unread messages";
"lng_maps_point" = "Location";
"lng_save_photo" = "Save image";
@ -1581,6 +1586,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mediaview_video_loading" = "Loading - {percent}";
"lng_mediaview_playback_speed" = "Playback speed";
"lng_mediaview_playback_speed_normal" = "Normal";
"lng_mediaview_rotate_video" = "Rotate video";
"lng_theme_preview_title" = "Theme Preview";
"lng_theme_preview_generating" = "Generating color theme preview...";
@ -1610,6 +1616,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_formatting_link_url" = "URL";
"lng_formatting_link_create" = "Create";
"lng_spellchecker_submenu" = "Spelling";
"lng_spellchecker_add" = "Add to Dictionary";
"lng_spellchecker_remove" = "Remove from Dictionary";
"lng_spellchecker_ignore" = "Ignore word";

View file

@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="1.9.10.0" />
Version="1.9.19.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h" // UserData::name
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "history/history.h"
#include "history/history_message.h" // NewMessageFlags.
#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities.
@ -110,23 +111,30 @@ void SendExistingMedia(
auto failHandler = std::make_shared<Fn<void(const RPCError&, QByteArray)>>();
auto performRequest = [=] {
const auto usedFileReference = media->fileReference();
history->sendRequestId = api->request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
inputMedia(),
MTP_string(captionText),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled)
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
}).fail([=](const RPCError &error) {
(*failHandler)(error, usedFileReference);
}).afterRequest(history->sendRequestId
).send();
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto usedFileReference = media->fileReference();
history->sendRequestId = api->request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
inputMedia(),
MTP_string(captionText),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled)
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
finish();
}).fail([=](const RPCError &error) {
(*failHandler)(error, usedFileReference);
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
};
*failHandler = [=](const RPCError &error, QByteArray usedFileReference) {
if (error.code() == 400
@ -169,7 +177,7 @@ void SendExistingDocument(
if (document->sticker()) {
if (const auto main = App::main()) {
main->incrementSticker(document);
document->session().data().notifyRecentStickersUpdated();
document->owner().notifyRecentStickersUpdated();
}
}
}

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_cloud_themes.h"
#include "data/data_histories.h"
#include "dialogs/dialogs_key.h"
#include "core/core_cloud_password.h"
#include "core/application.h"
@ -343,7 +344,7 @@ void ApiWrap::proxyPromotionDone(const MTPhelp_ProxyData &proxy) {
const auto peer = _session->data().peer(peerId);
_session->data().setProxyPromoted(peer);
if (const auto history = _session->data().historyLoaded(peer)) {
requestDialogEntry(history);
history->owner().histories().requestDialogEntry(history);
}
});
}
@ -1011,162 +1012,6 @@ rpl::producer<bool> ApiWrap::dialogsLoadBlockedByDate() const {
return _dialogsLoadBlockedByDate.value();
}
void ApiWrap::requestDialogEntry(not_null<Data::Folder*> folder) {
if (_dialogFolderRequests.contains(folder)) {
return;
}
_dialogFolderRequests.emplace(folder);
auto peers = QVector<MTPInputDialogPeer>(
1,
MTP_inputDialogPeerFolder(MTP_int(folder->id())));
request(MTPmessages_GetPeerDialogs(
MTP_vector(std::move(peers))
)).done([=](const MTPmessages_PeerDialogs &result) {
applyPeerDialogs(result);
_dialogFolderRequests.remove(folder);
}).fail([=](const RPCError &error) {
_dialogFolderRequests.remove(folder);
}).send();
}
void ApiWrap::requestDialogEntry(
not_null<History*> history,
Fn<void()> callback) {
const auto i = _dialogRequests.find(history);
if (i != end(_dialogRequests)) {
if (callback) {
i->second.push_back(std::move(callback));
}
return;
}
const auto [j, ok] = _dialogRequestsPending.try_emplace(history);
if (callback) {
j->second.push_back(std::move(callback));
}
if (!ok) {
return;
}
if (_dialogRequestsPending.size() > 1) {
return;
}
Core::App().postponeCall(crl::guard(_session, [=] {
sendDialogRequests();
}));
}
void ApiWrap::sendDialogRequests() {
if (_dialogRequestsPending.empty()) {
return;
}
auto histories = std::vector<not_null<History*>>();
ranges::transform(
_dialogRequestsPending,
ranges::back_inserter(histories),
[](const auto &pair) { return pair.first; });
auto peers = QVector<MTPInputDialogPeer>();
const auto dialogPeer = [](not_null<History*> history) {
return MTP_inputDialogPeer(history->peer->input);
};
ranges::transform(
histories,
ranges::back_inserter(peers),
dialogPeer);
for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) {
_dialogRequests.emplace(history, std::move(callbacks));
}
const auto finalize = [=] {
for (const auto history : histories) {
dialogEntryApplied(history);
history->updateChatListExistence();
}
};
request(MTPmessages_GetPeerDialogs(
MTP_vector(std::move(peers))
)).done([=](const MTPmessages_PeerDialogs &result) {
applyPeerDialogs(result);
finalize();
}).fail([=](const RPCError &error) {
finalize();
}).send();
}
void ApiWrap::dialogEntryApplied(not_null<History*> history) {
history->dialogEntryApplied();
if (const auto callbacks = _dialogRequestsPending.take(history)) {
for (const auto &callback : *callbacks) {
callback();
}
}
if (const auto callbacks = _dialogRequests.take(history)) {
for (const auto &callback : *callbacks) {
callback();
}
}
}
void ApiWrap::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) {
Expects(dialogs.type() == mtpc_messages_peerDialogs);
const auto &data = dialogs.c_messages_peerDialogs();
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
_session->data().processMessages(data.vmessages(), NewMessageType::Last);
for (const auto &dialog : data.vdialogs().v) {
dialog.match([&](const MTPDdialog &data) {
if (const auto peerId = peerFromMTP(data.vpeer())) {
_session->data().history(peerId)->applyDialog(nullptr, data);
}
}, [&](const MTPDdialogFolder &data) {
const auto folder = _session->data().processFolder(data.vfolder());
folder->applyDialog(data);
});
}
_session->data().sendHistoryChangeNotifications();
}
void ApiWrap::changeDialogUnreadMark(
not_null<History*> history,
bool unread) {
history->setUnreadMark(unread);
using Flag = MTPmessages_MarkDialogUnread::Flag;
request(MTPmessages_MarkDialogUnread(
MTP_flags(unread ? Flag::f_unread : Flag(0)),
MTP_inputDialogPeer(history->peer->input)
)).send();
}
void ApiWrap::requestFakeChatListMessage(
not_null<History*> history) {
if (_fakeChatListRequests.contains(history)) {
return;
}
_fakeChatListRequests.emplace(history);
request(MTPmessages_GetHistory(
history->peer->input,
MTP_int(0), // offset_id
MTP_int(0), // offset_date
MTP_int(0), // add_offset
MTP_int(2), // limit
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
_fakeChatListRequests.erase(history);
history->setFakeChatListMessageFrom(result);
}).fail([=](const RPCError &error) {
_fakeChatListRequests.erase(history);
history->setFakeChatListMessageFrom(MTP_messages_messages(
MTP_vector<MTPMessage>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0)));
}).send();
}
void ApiWrap::requestWallPaper(
const QString &slug,
Fn<void(const Data::WallPaper &)> done,
@ -1786,7 +1631,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
history->checkLocalMessages();
history->owner().sendHistoryChangeNotifications();
} else {
requestDialogEntry(history);
history->owner().histories().requestDialogEntry(history);
}
}
};
@ -2422,44 +2267,7 @@ int ApiWrap::OnlineTillFromStatus(
}
void ApiWrap::clearHistory(not_null<PeerData*> peer, bool revoke) {
auto deleteTillId = MsgId(0);
if (const auto history = _session->data().historyLoaded(peer)) {
while (history->lastMessageKnown()) {
const auto last = history->lastMessage();
if (!last) {
// History is empty.
return;
} else if (!IsServerMsgId(last->id)) {
// Destroy client-side message locally.
last->destroy();
} else {
break;
}
}
if (!history->lastMessageKnown()) {
requestDialogEntry(history, [=] {
Expects(history->lastMessageKnown());
clearHistory(peer, revoke);
});
return;
}
deleteTillId = history->lastMessage()->id;
history->clear(History::ClearType::ClearHistory);
}
if (const auto channel = peer->asChannel()) {
if (const auto migrated = peer->migrateFrom()) {
clearHistory(migrated, revoke);
}
if (IsServerMsgId(deleteTillId)) {
request(MTPchannels_DeleteHistory(
channel->inputChannel,
MTP_int(deleteTillId)
)).send();
}
} else {
deleteHistory(peer, true, revoke);
}
deleteHistory(peer, true, revoke);
}
void ApiWrap::deleteConversation(not_null<PeerData*> peer, bool revoke) {
@ -2473,30 +2281,69 @@ void ApiWrap::deleteConversation(not_null<PeerData*> peer, bool revoke) {
}).fail([=](const RPCError &error) {
deleteHistory(peer, false, revoke);
}).send();
} else if (const auto channel = peer->asChannel()) {
channel->ptsWaitingForShortPoll(-1);
leaveChannel(channel);
} else {
deleteHistory(peer, false, revoke);
}
_session->data().deleteConversationLocally(peer);
}
void ApiWrap::deleteHistory(not_null<PeerData*> peer, bool justClear, bool revoke) {
using Flag = MTPmessages_DeleteHistory::Flag;
const auto flags = Flag(0)
| (justClear ? Flag::f_just_clear : Flag(0))
| ((peer->isUser() && revoke) ? Flag::f_revoke : Flag(0));
request(MTPmessages_DeleteHistory(
MTP_flags(flags),
peer->input,
MTP_int(0)
)).done([=](const MTPmessages_AffectedHistory &result) {
const auto offset = applyAffectedHistory(peer, result);
if (offset > 0) {
deleteHistory(peer, justClear, revoke);
void ApiWrap::deleteHistory(
not_null<PeerData*> peer,
bool justClear,
bool revoke) {
auto deleteTillId = MsgId(0);
const auto history = _session->data().history(peer);
if (justClear) {
// In case of clear history we need to know the last server message.
while (history->lastMessageKnown()) {
const auto last = history->lastMessage();
if (!last) {
// History is empty.
return;
} else if (!IsServerMsgId(last->id)) {
// Destroy client-side message locally.
last->destroy();
} else {
break;
}
}
}).send();
if (!history->lastMessageKnown()) {
history->owner().histories().requestDialogEntry(history, [=] {
Expects(history->lastMessageKnown());
deleteHistory(peer, justClear, revoke);
});
return;
}
deleteTillId = history->lastMessage()->id;
}
if (const auto channel = peer->asChannel()) {
if (!justClear) {
channel->ptsWaitingForShortPoll(-1);
leaveChannel(channel);
} else {
if (const auto migrated = peer->migrateFrom()) {
deleteHistory(migrated, justClear, revoke);
}
if (IsServerMsgId(deleteTillId)) {
history->owner().histories().deleteAllMessages(
history,
deleteTillId,
justClear,
revoke);
}
}
} else {
history->owner().histories().deleteAllMessages(
history,
deleteTillId,
justClear,
revoke);
}
if (!justClear) {
_session->data().deleteConversationLocally(peer);
} else if (history) {
history->clear(History::ClearType::ClearHistory);
}
}
int ApiWrap::applyAffectedHistory(
@ -2528,30 +2375,6 @@ void ApiWrap::applyAffectedMessages(
App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v);
}
void ApiWrap::deleteMessages(
not_null<PeerData*> peer,
const QVector<MTPint> &ids,
bool revoke) {
const auto done = [=](const MTPmessages_AffectedMessages & result) {
applyAffectedMessages(peer, result);
if (const auto history = peer->owner().historyLoaded(peer)) {
history->requestChatListMessage();
}
};
if (const auto channel = peer->asChannel()) {
request(MTPchannels_DeleteMessages(
channel->inputChannel,
MTP_vector<MTPint>(ids)
)).done(done).send();
} else {
using Flag = MTPmessages_DeleteMessages::Flag;
request(MTPmessages_DeleteMessages(
MTP_flags(revoke ? Flag::f_revoke : Flag(0)),
MTP_vector<MTPint>(ids)
)).done(done).send();
}
}
void ApiWrap::saveDraftsToCloud() {
for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) {
if (i->second) continue; // sent already
@ -4057,12 +3880,12 @@ void ApiWrap::requestSharedMedia(
SharedMediaType type,
MsgId messageId,
SliceType slice) {
auto key = std::make_tuple(peer, type, messageId, slice);
const auto key = std::make_tuple(peer, type, messageId, slice);
if (_sharedMediaRequests.contains(key)) {
return;
}
auto prepared = Api::PrepareSearchRequest(
const auto prepared = Api::PrepareSearchRequest(
peer,
type,
QString(),
@ -4072,17 +3895,23 @@ void ApiWrap::requestSharedMedia(
return;
}
auto requestId = request(
std::move(*prepared)
).done([this, peer, type, messageId, slice](
const MTPmessages_Messages &result) {
auto key = std::make_tuple(peer, type, messageId, slice);
_sharedMediaRequests.remove(key);
sharedMediaDone(peer, type, messageId, slice, result);
}).fail([this, key](const RPCError &error) {
_sharedMediaRequests.remove(key);
}).send();
_sharedMediaRequests.emplace(key, requestId);
const auto history = session().data().history(peer);
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::History;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
return request(
std::move(*prepared)
).done([=](const MTPmessages_Messages &result) {
const auto key = std::make_tuple(peer, type, messageId, slice);
_sharedMediaRequests.remove(key);
sharedMediaDone(peer, type, messageId, slice, result);
finish();
}).fail([=](const RPCError &error) {
_sharedMediaRequests.remove(key);
finish();
}).send();
});
_sharedMediaRequests.emplace(key);
}
void ApiWrap::sharedMediaDone(
@ -4432,7 +4261,7 @@ void ApiWrap::userPhotosDone(
//}
void ApiWrap::sendAction(const SendAction &action) {
readServerHistory(action.history);
session().data().histories().readInbox(action.history);
action.history->getReadyFor(ShowAtTheEndMsgId);
_sendActions.fire_copy(action);
}
@ -4443,6 +4272,8 @@ void ApiWrap::forwardMessages(
FnMut<void()> &&successCallback) {
Expects(!items.empty());
auto &histories = session().data().histories();
struct SharedCallback {
int requestsLeft = 0;
FnMut<void()> callback;
@ -4459,7 +4290,7 @@ void ApiWrap::forwardMessages(
const auto history = action.history;
const auto peer = history->peer;
readServerHistory(history);
histories.readInbox(history);
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = action.options.silent
@ -4491,7 +4322,7 @@ void ApiWrap::forwardMessages(
auto currentGroupId = items.front()->groupId();
auto ids = QVector<MTPint>();
auto randomIds = QVector<MTPlong>();
auto localIds = std::unique_ptr<base::flat_map<uint64, FullMsgId>>();
auto localIds = std::shared_ptr<base::flat_map<uint64, FullMsgId>>();
const auto sendAccumulated = [&] {
if (shared) {
@ -4501,30 +4332,36 @@ void ApiWrap::forwardMessages(
| (currentGroupId == MessageGroupId()
? MTPmessages_ForwardMessages::Flag(0)
: MTPmessages_ForwardMessages::Flag::f_grouped);
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(finalFlags),
forwardFrom->input,
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
peer->input,
MTP_int(action.options.scheduled)
)).done([=, callback = std::move(successCallback)](
const MTPUpdates &updates) {
applyUpdates(updates);
if (shared && !--shared->requestsLeft) {
shared->callback();
}
}).fail([=, ids = std::move(localIds)](const RPCError &error) {
if (ids) {
for (const auto &[randomId, itemId] : *ids) {
sendMessageFail(error, peer, randomId, itemId);
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(finalFlags),
forwardFrom->input,
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
peer->input,
MTP_int(action.options.scheduled)
)).done([=](
const MTPUpdates &updates) {
applyUpdates(updates);
if (shared && !--shared->requestsLeft) {
shared->callback();
}
} else {
sendMessageFail(error, peer);
}
}).afterRequest(
history->sendRequestId
).send();
finish();
}).fail([=, ids = localIds](const RPCError &error) {
if (ids) {
for (const auto &[randomId, itemId] : *ids) {
sendMessageFail(error, peer, randomId, itemId);
}
} else {
sendMessageFail(error, peer);
}
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
ids.resize(0);
randomIds.resize(0);
@ -4557,7 +4394,7 @@ void ApiWrap::forwardMessages(
message);
_session->data().registerMessageRandomId(randomId, newId);
if (!localIds) {
localIds = std::make_unique<base::flat_map<uint64, FullMsgId>>();
localIds = std::make_shared<base::flat_map<uint64, FullMsgId>>();
}
localIds->emplace(randomId, newId);
}
@ -4972,6 +4809,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
HistoryItem *lastMessage = nullptr;
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
auto newId = FullMsgId(
peerToChannel(peer->id),
@ -5062,27 +4902,32 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTPVector<MTPRestrictionReason>()),
clientFlags,
NewMessageType::Unread);
history->sendRequestId = request(MTPmessages_SendMessage(
MTP_flags(sendFlags),
peer->input,
MTP_int(action.replyTo),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result, randomId);
history->clearSentDraftText(QString());
}).fail([=](const RPCError &error) {
if (error.type() == qstr("MESSAGE_EMPTY")) {
lastMessage->destroy();
} else {
sendMessageFail(error, peer, randomId, newId);
}
history->clearSentDraftText(QString());
}).afterRequest(history->sendRequestId
).send();
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendMessage(
MTP_flags(sendFlags),
peer->input,
MTP_int(action.replyTo),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result, randomId);
history->clearSentDraftText(QString());
finish();
}).fail([=](const RPCError &error) {
if (error.type() == qstr("MESSAGE_EMPTY")) {
lastMessage->destroy();
} else {
sendMessageFail(error, peer, randomId, newId);
}
history->clearSentDraftText(QString());
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
}
if (const auto main = App::main()) {
@ -5188,23 +5033,29 @@ void ApiWrap::sendInlineResult(
history->clearCloudDraft();
history->setSentDraftText(QString());
history->sendRequestId = request(MTPmessages_SendInlineBotResult(
MTP_flags(sendFlags),
peer->input,
MTP_int(action.replyTo),
MTP_long(randomId),
MTP_long(data->getQueryId()),
MTP_string(data->getId()),
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result, randomId);
history->clearSentDraftText(QString());
}).fail([=](const RPCError &error) {
sendMessageFail(error, peer, randomId, newId);
history->clearSentDraftText(QString());
}).afterRequest(history->sendRequestId
).send();
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendInlineBotResult(
MTP_flags(sendFlags),
peer->input,
MTP_int(action.replyTo),
MTP_long(randomId),
MTP_long(data->getQueryId()),
MTP_string(data->getId()),
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result, randomId);
history->clearSentDraftText(QString());
finish();
}).fail([=](const RPCError &error) {
sendMessageFail(error, peer, randomId, newId);
history->clearSentDraftText(QString());
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
if (const auto main = App::main()) {
main->finishForwarding(action);
}
@ -5323,25 +5174,32 @@ void ApiWrap::sendMediaWithRandomId(
? MTPmessages_SendMedia::Flag::f_schedule_date
: MTPmessages_SendMedia::Flag(0));
const auto peer = history->peer;
const auto itemId = item->fullId();
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
media,
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).fail([=](const RPCError &error) {
sendMessageFail(error, peer, randomId, itemId);
}).afterRequest(
history->sendRequestId
).send();
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto peer = history->peer;
const auto itemId = item->fullId();
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
media,
MTP_string(caption.text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
finish();
}).fail([=](const RPCError &error) {
sendMessageFail(error, peer, randomId, itemId);
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
}
void ApiWrap::sendAlbumWithUploaded(
@ -5419,27 +5277,34 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
| (album->options.scheduled
? MTPmessages_SendMultiMedia::Flag::f_schedule_date
: MTPmessages_SendMultiMedia::Flag(0));
const auto peer = history->peer;
history->sendRequestId = request(MTPmessages_SendMultiMedia(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(album->options.scheduled)
)).done([=](const MTPUpdates &result) {
_sendingAlbums.remove(groupId);
applyUpdates(result);
}).fail([=](const RPCError &error) {
if (const auto album = _sendingAlbums.take(groupId)) {
for (const auto &item : (*album)->items) {
sendMessageFail(error, peer, item.randomId, item.msgId);
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto peer = history->peer;
history->sendRequestId = request(MTPmessages_SendMultiMedia(
MTP_flags(flags),
peer->input,
MTP_int(replyTo),
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(album->options.scheduled)
)).done([=](const MTPUpdates &result) {
_sendingAlbums.remove(groupId);
applyUpdates(result);
finish();
}).fail([=](const RPCError &error) {
if (const auto album = _sendingAlbums.take(groupId)) {
for (const auto &item : (*album)->items) {
sendMessageFail(error, peer, item.randomId, item.msgId);
}
} else {
sendMessageFail(error, peer);
}
} else {
sendMessageFail(error, peer);
}
}).afterRequest(
history->sendRequestId
).send();
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
}
FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
@ -5836,8 +5701,8 @@ Api::SensitiveContent &ApiWrap::sensitiveContent() {
void ApiWrap::createPoll(
const PollData &data,
const SendAction &action,
FnMut<void()> done,
FnMut<void(const RPCError &error)> fail) {
Fn<void()> done,
Fn<void(const RPCError &error)> fail) {
sendAction(action);
const auto history = action.history;
@ -5870,27 +5735,34 @@ void ApiWrap::createPoll(
correct.push_back(MTP_bytes(answer.option));
}
}
const auto replyTo = action.replyTo;
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
MTP_inputMediaPoll(
MTP_flags(inputFlags),
PollDataToMTP(&data),
MTP_vector<MTPbytes>(correct)),
MTP_string(),
MTP_long(rand_value<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled)
)).done([=, done = std::move(done)](const MTPUpdates &result) mutable {
applyUpdates(result);
done();
}).fail([=, fail = std::move(fail)](const RPCError &error) mutable {
fail(error);
}).afterRequest(history->sendRequestId
).send();
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto replyTo = action.replyTo;
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
MTP_inputMediaPoll(
MTP_flags(inputFlags),
PollDataToMTP(&data),
MTP_vector<MTPbytes>(correct)),
MTP_string(),
MTP_long(rand_value<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) mutable {
applyUpdates(result);
done();
finish();
}).fail([=](const RPCError &error) mutable {
fail(error);
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
}
void ApiWrap::sendPollVotes(
@ -6000,43 +5872,6 @@ void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
_pollReloadRequestIds.emplace(itemId, requestId);
}
void ApiWrap::readServerHistory(not_null<History*> history) {
if (history->unreadCount()) {
readServerHistoryForce(history);
}
if (history->unreadMark()) {
changeDialogUnreadMark(history, false);
}
}
void ApiWrap::readServerHistoryForce(not_null<History*> history) {
const auto peer = history->peer;
const auto upTo = history->readInbox();
if (!upTo) {
return;
}
if (const auto channel = peer->asChannel()) {
if (!channel->amIn()) {
return; // no read request for channels that I didn't join
} else if (const auto migrateFrom = channel->migrateFrom()) {
if (const auto migrated = _session->data().historyLoaded(migrateFrom)) {
readServerHistory(migrated);
}
}
}
if (_readRequests.contains(peer)) {
const auto i = _readRequestsPending.find(peer);
if (i == _readRequestsPending.cend()) {
_readRequestsPending.emplace(peer, upTo);
} else if (i->second < upTo) {
i->second = upTo;
}
} else {
sendReadRequest(peer, upTo);
}
}
// // #feed
//void ApiWrap::readFeed(
// not_null<Data::Feed*> feed,
@ -6090,39 +5925,3 @@ void ApiWrap::readServerHistoryForce(not_null<History*> history) {
// _feedReadTimer.callOnce(delay);
// }
//}
void ApiWrap::sendReadRequest(not_null<PeerData*> peer, MsgId upTo) {
const auto requestId = [&] {
const auto finished = [=] {
_readRequests.remove(peer);
if (const auto next = _readRequestsPending.take(peer)) {
sendReadRequest(peer, *next);
} else if (const auto history
= _session->data().historyLoaded(peer)) {
if (!history->unreadCountKnown()) {
requestDialogEntry(history);
}
}
};
if (const auto channel = peer->asChannel()) {
return request(MTPchannels_ReadHistory(
channel->inputChannel,
MTP_int(upTo)
)).done([=](const MTPBool &result) {
finished();
}).fail([=](const RPCError &error) {
finished();
}).send();
}
return request(MTPmessages_ReadHistory(
peer->input,
MTP_int(upTo)
)).done([=](const MTPmessages_AffectedMessages &result) {
applyAffectedMessages(peer, result);
finished();
}).fail([=](const RPCError &error) {
finished();
}).send();
}();
_readRequests.emplace(peer, requestId, upTo);
}

View file

@ -141,6 +141,9 @@ public:
void applyUpdates(
const MTPUpdates &updates,
uint64 sentMessageRandomId = 0);
int applyAffectedHistory(
not_null<PeerData*> peer,
const MTPmessages_AffectedHistory &result);
void registerModifyRequest(const QString &key, mtpRequestId requestId);
void clearModifyRequest(const QString &key);
@ -170,18 +173,10 @@ public:
rpl::producer<bool> dialogsLoadMayBlockByDate() const;
rpl::producer<bool> dialogsLoadBlockedByDate() const;
void requestDialogEntry(not_null<Data::Folder*> folder);
void requestDialogEntry(
not_null<History*> history,
Fn<void()> callback = nullptr);
void dialogEntryApplied(not_null<History*> history);
//void applyFeedSources(const MTPDchannels_feedSources &data); // #feed
//void setFeedChannels(
// not_null<Data::Feed*> feed,
// const std::vector<not_null<ChannelData*>> &channels);
void changeDialogUnreadMark(not_null<History*> history, bool unread);
//void changeDialogUnreadMark(not_null<Data::Feed*> feed, bool unread); // #feed
void requestFakeChatListMessage(not_null<History*> history);
void requestWallPaper(
const QString &slug,
@ -305,10 +300,6 @@ public:
void clearHistory(not_null<PeerData*> peer, bool revoke);
void deleteConversation(not_null<PeerData*> peer, bool revoke);
void deleteMessages(
not_null<PeerData*> peer,
const QVector<MTPint> &ids,
bool revoke);
base::Observable<PeerData*> &fullPeerUpdated() {
return _fullPeerUpdated;
@ -389,11 +380,12 @@ public:
const QString &lastName,
const SendAction &action);
void shareContact(not_null<UserData*> user, const SendAction &action);
void readServerHistory(not_null<History*> history);
void readServerHistoryForce(not_null<History*> history);
//void readFeed( // #feed
// not_null<Data::Feed*> feed,
// Data::MessagePosition position);
void applyAffectedMessages(
not_null<PeerData*> peer,
const MTPmessages_AffectedMessages &result);
void sendVoiceMessage(
QByteArray result,
@ -475,8 +467,8 @@ public:
void createPoll(
const PollData &data,
const SendAction &action,
FnMut<void()> done,
FnMut<void(const RPCError &error)> fail);
Fn<void()> done,
Fn<void(const RPCError &error)> fail);
void sendPollVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options);
@ -531,7 +523,6 @@ private:
QVector<MTPInputMessage> collectMessageIds(const MessageDataRequests &requests);
MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false);
void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs);
void gotChatFull(
not_null<PeerData*> peer,
@ -623,14 +614,7 @@ private:
not_null<PeerData*> peer,
bool justClear,
bool revoke);
void sendReadRequest(not_null<PeerData*> peer, MsgId upTo);
int applyAffectedHistory(
not_null<PeerData*> peer,
const MTPmessages_AffectedHistory &result);
void applyAffectedMessages(const MTPmessages_AffectedMessages &result);
void applyAffectedMessages(
not_null<PeerData*> peer,
const MTPmessages_AffectedMessages &result);
void deleteAllFromUserSend(
not_null<ChannelData*> channel,
@ -685,8 +669,6 @@ private:
not_null<ChannelData*> channel);
void migrateFail(not_null<PeerData*> peer, const RPCError &error);
void sendDialogRequests();
not_null<Main::Session*> _session;
base::flat_map<QString, int> _modifyRequests;
@ -756,22 +738,14 @@ private:
mtpRequestId _contactsRequestId = 0;
mtpRequestId _contactsStatusesRequestId = 0;
base::flat_set<not_null<Data::Folder*>> _dialogFolderRequests;
base::flat_map<
not_null<History*>,
std::vector<Fn<void()>>> _dialogRequests;
base::flat_map<
not_null<History*>,
std::vector<Fn<void()>>> _dialogRequestsPending;
base::flat_set<not_null<History*>> _fakeChatListRequests;
base::flat_map<not_null<History*>, mtpRequestId> _unreadMentionsRequests;
base::flat_map<std::tuple<
base::flat_set<std::tuple<
not_null<PeerData*>,
SharedMediaType,
MsgId,
SliceType>, mtpRequestId> _sharedMediaRequests;
SliceType>> _sharedMediaRequests;
base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests;
@ -800,18 +774,6 @@ private:
rpl::event_stream<SendAction> _sendActions;
struct ReadRequest {
ReadRequest(mtpRequestId requestId, MsgId upTo)
: requestId(requestId)
, upTo(upTo) {
}
mtpRequestId requestId = 0;
MsgId upTo = 0;
};
base::flat_map<not_null<PeerData*>, ReadRequest> _readRequests;
base::flat_map<not_null<PeerData*>, MsgId> _readRequestsPending;
std::unique_ptr<TaskQueue> _fileLoader;
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;

View file

@ -494,6 +494,7 @@ void GroupInfoBox::prepare() {
_description->setInstantReplaces(Ui::InstantReplaces::Default());
_description->setInstantReplacesEnabled(
_navigation->session().settings().replaceEmojiValue());
_description->setSubmitSettings(_navigation->session().settings().sendSubmitWay());
connect(_description, &Ui::InputField::resized, [=] { descriptionResized(); });
connect(_description, &Ui::InputField::submitted, [=] { submit(); });

View file

@ -293,8 +293,7 @@ AdminLog::OwnedItem GenerateTextItem(
const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item;
const auto replyTo = 0;
const auto viaBotId = 0;
const auto item = history->owner().makeMessage(
history,
const auto item = history->makeMessage(
++id,
flags,
clientFlags,

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "base/unixtime.h"
#include "main/main_session.h"
#include "observer_peer.h"
@ -785,7 +786,9 @@ void DeleteMessagesBox::deleteAndClear() {
_deleteConfirmedCallback();
}
base::flat_map<not_null<PeerData*>, QVector<MTPint>> idsByPeer;
auto remove = std::vector<not_null<HistoryItem*>>();
remove.reserve(_ids.size());
base::flat_map<not_null<History*>, QVector<MTPint>> idsByPeer;
base::flat_map<not_null<PeerData*>, QVector<MTPint>> scheduledIdsByPeer;
for (const auto itemId : _ids) {
if (const auto item = _session->data().message(itemId)) {
@ -801,21 +804,15 @@ void DeleteMessagesBox::deleteAndClear() {
}
continue;
}
const auto wasOnServer = IsServerMsgId(item->id);
const auto wasLast = (history->lastMessage() == item);
const auto wasInChats = (history->chatListMessage() == item);
item->destroy();
if (wasOnServer) {
idsByPeer[history->peer].push_back(MTP_int(itemId.msg));
} else if (wasLast || wasInChats) {
history->requestChatListMessage();
remove.push_back(item);
if (IsServerMsgId(item->id)) {
idsByPeer[history].push_back(MTP_int(itemId.msg));
}
}
}
for (const auto &[peer, ids] : idsByPeer) {
peer->session().api().deleteMessages(peer, ids, revoke);
for (const auto &[history, ids] : idsByPeer) {
history->owner().histories().deleteMessages(history, ids, revoke);
}
for (const auto &[peer, ids] : scheduledIdsByPeer) {
peer->session().api().request(MTPmessages_DeleteScheduledMessages(
@ -826,6 +823,17 @@ void DeleteMessagesBox::deleteAndClear() {
}).send();
}
for (const auto item : remove) {
const auto history = item->history();
const auto wasLast = (history->lastMessage() == item);
const auto wasInChats = (history->chatListMessage() == item);
item->destroy();
if (wasLast || wasInChats) {
history->requestChatListMessage();
}
}
const auto session = _session;
Ui::hideLayer();
session->data().sendHistoryChangeNotifications();

View file

@ -767,6 +767,7 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
st::createPollFieldPadding);
InitField(getDelegate()->outerContainer(), question, _session);
question->setMaxLength(kQuestionLimit + kErrorLimit);
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
const auto warning = CreateWarningLabel(
container,

View file

@ -0,0 +1,442 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/dictionaries_manager.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "base/event_filter.h"
#include "chat_helpers/spellchecker_common.h"
#include "core/application.h"
#include "main/main_account.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mtproto/dedicated_file_loader.h"
#include "spellcheck/spellcheck_utils.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/effects/animations.h"
namespace Ui {
namespace {
using Dictionaries = std::vector<int>;
using namespace Storage::CloudBlob;
using Loading = MTP::DedicatedLoader::Progress;
using DictState = BlobState;
using QueryCallback = Fn<void(const QString &)>;
constexpr auto kMaxQueryLength = 15;
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
#define OLD_QT
using QStringView = QString;
#endif
class Inner : public Ui::RpWidget {
public:
Inner(QWidget *parent, Dictionaries enabledDictionaries);
Dictionaries enabledRows() const;
QueryCallback queryCallback() const;
private:
void setupContent(Dictionaries enabledDictionaries);
Dictionaries _enabledRows;
QueryCallback _queryCallback;
};
inline auto DictExists(int langId) {
return Spellchecker::DictionaryExists(langId);
}
inline auto FilterEnabledDict(Dictionaries dicts) {
return dicts | ranges::views::filter(
DictExists
) | ranges::to_vector;
}
DictState ComputeState(int id, bool enabled) {
const auto result = enabled ? DictState(Active()) : DictState(Ready());
if (DictExists(id)) {
return result;
}
return Available{ Spellchecker::GetDownloadSize(id) };
}
QString StateDescription(const DictState &state) {
return StateDescription(
state,
tr::lng_settings_manage_enabled_dictionary);
}
auto CreateMultiSelect(QWidget *parent) {
const auto result = Ui::CreateChild<Ui::MultiSelect>(
parent,
st::contactsMultiSelect,
tr::lng_participant_filter());
result->resizeToWidth(st::boxWidth);
result->moveToLeft(0, 0);
return result;
}
Inner::Inner(
QWidget *parent,
Dictionaries enabledDictionaries) : RpWidget(parent) {
setupContent(std::move(enabledDictionaries));
}
QueryCallback Inner::queryCallback() const {
return _queryCallback;
}
Dictionaries Inner::enabledRows() const {
return _enabledRows;
}
auto AddButtonWithLoader(
not_null<Ui::VerticalLayout*> content,
const Spellchecker::Dict &dict,
bool buttonEnabled,
rpl::producer<QStringView> query) {
const auto id = dict.id;
buttonEnabled &= DictExists(id);
const auto locale = Spellchecker::LocaleFromLangId(id);
const std::vector<QString> indexList = {
dict.name,
QLocale::languageToString(locale.language()),
QLocale::countryToString(locale.country())
};
const auto wrap = content->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
content,
object_ptr<Ui::SettingsButton>(
content,
rpl::single(dict.name),
st::dictionariesSectionButton
)
)
);
const auto button = wrap->entity();
std::move(
query
) | rpl::start_with_next([=](auto string) {
wrap->toggle(
ranges::any_of(indexList, [&](const QString &s) {
return s.startsWith(string, Qt::CaseInsensitive);
}),
anim::type::instant);
}, button->lifetime());
using Loader = Spellchecker::DictLoader;
using GlobalLoaderPtr = std::shared_ptr<base::unique_qptr<Loader>>;
const auto localLoader = button->lifetime()
.make_state<base::unique_qptr<Loader>>();
const auto localLoaderValues = button->lifetime()
.make_state<rpl::event_stream<Loader*>>();
const auto setLocalLoader = [=](base::unique_qptr<Loader> loader) {
*localLoader = std::move(loader);
localLoaderValues->fire(localLoader->get());
};
const auto destroyLocalLoader = [=] {
setLocalLoader(nullptr);
};
const auto buttonState = button->lifetime()
.make_state<rpl::variable<DictState>>();
const auto dictionaryRemoved = button->lifetime()
.make_state<rpl::event_stream<>>();
const auto dictionaryFromGlobalLoader = button->lifetime()
.make_state<rpl::event_stream<>>();
const auto globalLoader = button->lifetime()
.make_state<GlobalLoaderPtr>();
const auto rawGlobalLoaderPtr = [=]() -> Loader* {
if (!globalLoader || !*globalLoader || !*globalLoader->get()) {
return nullptr;
}
return globalLoader->get()->get();
};
const auto setGlobalLoaderPtr = [=](GlobalLoaderPtr loader) {
if (localLoader->get()) {
if (loader && loader->get()) {
loader->get()->destroy();
}
return;
}
*globalLoader = std::move(loader);
localLoaderValues->fire(rawGlobalLoaderPtr());
if (rawGlobalLoaderPtr()) {
dictionaryFromGlobalLoader->fire({});
}
};
Spellchecker::GlobalLoaderChanged(
) | rpl::start_with_next([=](int langId) {
if (!langId && rawGlobalLoaderPtr()) {
setGlobalLoaderPtr(nullptr);
} else if (langId == id) {
setGlobalLoaderPtr(Spellchecker::GlobalLoader());
}
}, button->lifetime());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
buttonState->value() | rpl::map(StateDescription),
st::settingsUpdateState);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
rpl::combine(
button->widthValue(),
label->widthValue()
) | rpl::start_with_next([=] {
label->moveToLeft(
st::settingsUpdateStatePosition.x(),
st::settingsUpdateStatePosition.y());
}, label->lifetime());
buttonState->value(
) | rpl::start_with_next([=](const DictState &state) {
const auto isToggledSet = state.is<Active>();
const auto toggled = isToggledSet ? 1. : 0.;
const auto over = !button->isDisabled()
&& (button->isDown() || button->isOver());
if (toggled == 0. && !over) {
label->setTextColorOverride(std::nullopt);
} else {
label->setTextColorOverride(anim::color(
over ? st::contactsStatusFgOver : st::contactsStatusFg,
st::contactsStatusFgOnline,
toggled));
}
}, label->lifetime());
button->toggleOn(
rpl::single(
buttonEnabled
) | rpl::then(
rpl::merge(
// Events to toggle on.
dictionaryFromGlobalLoader->events(
) | rpl::map([] {
return true;
}),
// Events to toggle off.
rpl::merge(
dictionaryRemoved->events(),
buttonState->value(
) | rpl::filter([](const DictState &state) {
return state.is<Failed>();
}) | rpl::map([] {
return rpl::empty_value();
})
) | rpl::map([] {
return false;
})
)
)
);
*buttonState = localLoaderValues->events_starting_with(
rawGlobalLoaderPtr() ? rawGlobalLoaderPtr() : localLoader->get()
) | rpl::map([=](Loader *loader) {
return (loader && loader->id() == id)
? loader->state()
: rpl::single(
buttonEnabled
) | rpl::then(
rpl::merge(
dictionaryRemoved->events(
) | rpl::map([] {
return false;
}),
button->toggledValue()
)
) | rpl::map([=](auto enabled) {
return ComputeState(id, enabled);
});
}) | rpl::flatten_latest(
) | rpl::filter([=](const DictState &state) {
return !buttonState->current().is<Failed>() || !state.is<Available>();
});
button->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
const auto &state = buttonState->current();
if (toggled && (state.is<Available>() || state.is<Failed>())) {
const auto weak = Ui::MakeWeak(button);
setLocalLoader(base::make_unique_q<Loader>(
App::main(),
id,
Spellchecker::GetDownloadLocation(id),
Spellchecker::DictPathByLangId(id),
Spellchecker::GetDownloadSize(id),
crl::guard(weak, destroyLocalLoader)));
} else if (!toggled && state.is<Loading>()) {
if (const auto g = rawGlobalLoaderPtr()) {
g->destroy();
return;
}
if (localLoader && localLoader->get()->id() == id) {
destroyLocalLoader();
}
}
}, button->lifetime());
const auto contextMenu = button->lifetime()
.make_state<base::unique_qptr<Ui::PopupMenu>>();
const auto showMenu = [=] {
if (!DictExists(id)) {
return false;
}
*contextMenu = base::make_unique_q<Ui::PopupMenu>(button);
contextMenu->get()->addAction(
tr::lng_settings_manage_remove_dictionary(tr::now), [=] {
Spellchecker::RemoveDictionary(id);
dictionaryRemoved->fire({});
});
contextMenu->get()->popup(QCursor::pos());
return true;
};
base::install_event_filter(button, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::ContextMenu && showMenu()) {
return base::EventFilterResult::Cancel;
}
return base::EventFilterResult::Continue;
});
if (const auto g = Spellchecker::GlobalLoader()) {
if (g.get() && g->get()->id() == id) {
setGlobalLoaderPtr(g);
}
}
return button;
}
void Inner::setupContent(Dictionaries enabledDictionaries) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto queryStream = content->lifetime()
.make_state<rpl::event_stream<QStringView>>();
for (const auto &dict : Spellchecker::Dictionaries()) {
const auto id = dict.id;
const auto row = AddButtonWithLoader(
content,
dict,
ranges::contains(enabledDictionaries, id),
queryStream->events());
row->toggledValue(
) | rpl::start_with_next([=](auto enabled) {
if (enabled) {
_enabledRows.push_back(id);
} else {
auto &rows = _enabledRows;
rows.erase(ranges::remove(rows, id), end(rows));
}
}, row->lifetime());
}
_queryCallback = [=](const QString &query) {
if (query.size() >= kMaxQueryLength) {
return;
}
queryStream->fire_copy(query);
};
content->resizeToWidth(st::boxWidth);
Ui::ResizeFitChild(this, content);
}
} // namespace
ManageDictionariesBox::ManageDictionariesBox(
QWidget*,
not_null<Main::Session*> session)
: _session(session) {
}
void ManageDictionariesBox::setInnerFocus() {
_setInnerFocus();
}
void ManageDictionariesBox::prepare() {
const auto multiSelect = CreateMultiSelect(this);
const auto inner = setInnerWidget(
object_ptr<Inner>(
this,
_session->settings().dictionariesEnabled()),
st::boxScroll,
multiSelect->height()
);
multiSelect->setQueryChangedCallback(inner->queryCallback());
_setInnerFocus = [=] {
multiSelect->setInnerFocus();
};
// The initial list of enabled rows may differ from the list of languages
// in settings, so we should store it when box opens
// and save it when box closes (don't do it when "Save" was pressed).
const auto initialEnabledRows = inner->enabledRows();
setTitle(tr::lng_settings_manage_dictionaries());
addButton(tr::lng_settings_save(), [=] {
_session->settings().setDictionariesEnabled(
FilterEnabledDict(inner->enabledRows()));
_session->saveSettingsDelayed();
// Ignore boxClosing() when the Save button was pressed.
lifetime().destroy();
closeBox();
});
addButton(tr::lng_close(), [=] { closeBox(); });
boxClosing() | rpl::start_with_next([=] {
_session->settings().setDictionariesEnabled(
FilterEnabledDict(initialEnabledRows));
_session->saveSettingsDelayed();
}, lifetime());
setDimensionsToContent(st::boxWidth, inner);
using namespace rpl::mappers;
const auto max = lifetime().make_state<int>(0);
rpl::combine(
inner->heightValue(),
multiSelect->heightValue(),
_1 + _2
) | rpl::start_with_next([=](int height) {
using std::min;
accumulate_max(*max, height);
setDimensions(st::boxWidth, min(*max, st::boxMaxListHeight), true);
}, inner->lifetime());
}
} // namespace Ui
#endif // !TDESKTOP_DISABLE_SPELLCHECK

View file

@ -0,0 +1,38 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "boxes/abstract_box.h"
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class ManageDictionariesBox : public Ui::BoxContent {
public:
ManageDictionariesBox(
QWidget*,
not_null<Main::Session*> session);
protected:
void prepare() override;
void setInnerFocus() override;
private:
const not_null<Main::Session*> _session;
Fn<void()> _setInnerFocus;
};
} // namespace Ui
#endif // !TDESKTOP_DISABLE_SPELLCHECK

View file

@ -260,7 +260,7 @@ EditCaptionBox::EditCaptionBox(
tr::lng_photo_caption(),
editData);
_field->setMaxLength(Global::CaptionLengthMax());
_field->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_field->setSubmitSettings(_controller->session().settings().sendSubmitWay());
_field->setInstantReplaces(Ui::InstantReplaces::Default());
_field->setInstantReplacesEnabled(
_controller->session().settings().replaceEmojiValue());

View file

@ -76,7 +76,7 @@ void MuteSettingsBox::prepare() {
_save = [=] {
const auto muteForSeconds = group->value() * 3600;
_peer->session().data().updateNotifySettings(
_peer->owner().updateNotifySettings(
_peer,
muteForSeconds);
closeBox();

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_folder.h"
#include "data/data_histories.h"
#include "apiwrap.h"
#include "mainwidget.h"
#include "lang/lang_keys.h"
@ -30,33 +31,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
void ShareBotGame(not_null<UserData*> bot, not_null<PeerData*> chat) {
const auto history = chat->owner().historyLoaded(chat);
const auto randomId = rand_value<uint64>();
const auto api = &chat->session().api();
const auto requestId = api->request(MTPmessages_SendMedia(
MTP_flags(0),
chat->input,
MTP_int(0),
MTP_inputMediaGame(
MTP_inputGameShortName(
bot->inputUser,
MTP_string(bot->botInfo->shareGameShortName))),
MTP_string(),
MTP_long(randomId),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(0) // schedule_date
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
}).fail([=](const RPCError &error) {
api->sendMessageFail(error, chat);
}).afterRequest(
history ? history->sendRequestId : 0
).send();
if (history) {
history->sendRequestId = requestId;
}
const auto history = chat->owner().history(chat);
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto randomId = rand_value<uint64>();
const auto api = &chat->session().api();
history->sendRequestId = api->request(MTPmessages_SendMedia(
MTP_flags(0),
chat->input,
MTP_int(0),
MTP_inputMediaGame(
MTP_inputGameShortName(
bot->inputUser,
MTP_string(bot->botInfo->shareGameShortName))),
MTP_string(),
MTP_long(randomId),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(0) // schedule_date
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
finish();
}).fail([=](const RPCError &error) {
api->sendMessageFail(error, chat);
finish();
}).afterRequest(
history->sendRequestId
).send();
return history->sendRequestId;
});
Ui::hideLayer();
Ui::showPeerHistory(chat, ShowAtUnreadMsgId);
}

View file

@ -500,6 +500,7 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
_peer->session().settings().replaceEmojiValue());
result->entity()->setSubmitSettings(_peer->session().settings().sendSubmitWay());
Ui::Emoji::SuggestionsController::Init(
_wrap->window(),
result->entity(),

View file

@ -85,7 +85,7 @@ void RateCallBox::ratingChanged(int value) {
Ui::InputField::Mode::MultiLine,
tr::lng_call_rate_comment());
_comment->show();
_comment->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_comment->setSubmitSettings(_session->settings().sendSubmitWay());
_comment->setMaxLength(kRateCallCommentLengthMax);
_comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height());

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "data/data_peer.h"
#include "main/main_session.h"
#include "boxes/confirm_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
@ -106,7 +107,7 @@ void ReportBox::reasonChanged(Reason reason) {
Ui::InputField::Mode::MultiLine,
tr::lng_report_reason_description());
_reasonOtherText->show();
_reasonOtherText->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_reasonOtherText->setSubmitSettings(_peer->session().settings().sendSubmitWay());
_reasonOtherText->setMaxLength(kReportReasonLengthMax);
_reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height());

View file

@ -2046,7 +2046,7 @@ void SendFilesBox::applyAlbumOrder() {
void SendFilesBox::setupCaption() {
_caption->setMaxLength(Global::CaptionLengthMax());
_caption->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_caption->setSubmitSettings(_controller->session().settings().sendSubmitWay());
connect(_caption, &Ui::InputField::resized, [=] {
captionResized();
});

View file

@ -206,6 +206,7 @@ void ShareBox::prepareCommentField() {
field->setMarkdownReplacesEnabled(rpl::single(true));
field->setEditLinkCallback(
DefaultEditLinkCallback(&_navigation->session(), field));
field->setSubmitSettings(_navigation->session().settings().sendSubmitWay());
InitSpellchecker(&_navigation->session(), field);
Ui::SendPendingMoveResizeEvents(_comment);

View file

@ -44,7 +44,7 @@ private:
MTP::Sender _api;
MsgId _offsetId = 0;
mtpRequestId _loadRequestId = 0;
int _loadRequestId = 0; // Not a real mtpRequestId.
bool _allLoaded = false;
};

View file

@ -15,13 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/effects/radial_animation.h"
#include "ui/emoji_config.h"
#include "lang/lang_keys.h"
#include "base/zlib_help.h"
#include "layout.h"
#include "core/application.h"
#include "main/main_account.h"
#include "mainwidget.h"
#include "app.h"
#include "storage/storage_cloud_blob.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
@ -30,69 +28,37 @@ namespace Ui {
namespace Emoji {
namespace {
struct Available {
int size = 0;
using namespace Storage::CloudBlob;
inline bool operator<(const Available &other) const {
return size < other.size;
}
inline bool operator==(const Available &other) const {
return size == other.size;
}
struct Set : public Blob {
QString previewPath;
};
struct Ready {
inline bool operator<(const Ready &other) const {
return false;
}
inline bool operator==(const Ready &other) const {
return true;
}
};
struct Active {
inline bool operator<(const Active &other) const {
return false;
}
inline bool operator==(const Active &other) const {
return true;
}
inline auto PreviewPath(int i) {
return qsl(":/gui/emoji/set%1_preview.webp").arg(i);
}
const auto kSets = {
Set{ {0, 0, 0, "Mac"}, PreviewPath(0) },
Set{ {1, 246, 7'336'383, "Android"}, PreviewPath(1) },
Set{ {2, 206, 5'038'738, "Twemoji"}, PreviewPath(2) },
Set{ {3, 238, 6'992'260, "JoyPixels"}, PreviewPath(3) },
};
using Loading = MTP::DedicatedLoader::Progress;
struct Failed {
inline bool operator<(const Failed &other) const {
return false;
}
inline bool operator==(const Failed &other) const {
return true;
}
};
using SetState = base::variant<
Available,
Ready,
Active,
Loading,
Failed>;
using SetState = BlobState;
class Loader : public QObject {
class Loader : public BlobLoader {
public:
Loader(QObject *parent, int id);
Loader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size);
int id() const;
rpl::producer<SetState> state() const;
void destroy();
private:
void setImplementation(std::unique_ptr<MTP::DedicatedLoader> loader);
void unpack(const QString &path);
void finalize(const QString &path);
void fail();
int _id = 0;
int _size = 0;
rpl::variable<SetState> _state;
MTP::WeakInstance _mtproto;
std::unique_ptr<MTP::DedicatedLoader> _implementation;
void destroy() override;
void unpack(const QString &path) override;
};
@ -127,6 +93,7 @@ private:
void setupHandler();
void load();
void radialAnimationCallback(crl::time now);
void updateLoadingToFinished();
int _id = 0;
bool _switching = false;
@ -148,15 +115,19 @@ void SetGlobalLoader(base::unique_qptr<Loader> loader) {
}
int GetDownloadSize(int id) {
const auto sets = Sets();
return ranges::find(sets, id, &Set::id)->size;
return ranges::find(kSets, id, &Set::id)->size;
}
[[nodiscard]] float64 CountProgress(not_null<const Loading*> loading) {
return (loading->size > 0)
? (loading->already / float64(loading->size))
: 0.;
}
MTP::DedicatedLoader::Location GetDownloadLocation(int id) {
constexpr auto kUsername = "tdhbcfiles";
const auto sets = Sets();
const auto i = ranges::find(sets, id, &Set::id);
return MTP::DedicatedLoader::Location{ kUsername, i->postId };
const auto username = kCloudLocationUsername.utf16();
const auto i = ranges::find(kSets, id, &Set::id);
return MTP::DedicatedLoader::Location{ username, i->postId };
}
SetState ComputeState(int id) {
@ -169,45 +140,9 @@ SetState ComputeState(int id) {
}
QString StateDescription(const SetState &state) {
return state.match([](const Available &data) {
return tr::lng_emoji_set_download(tr::now, lt_size, formatSizeText(data.size));
}, [](const Ready &data) -> QString {
return tr::lng_emoji_set_ready(tr::now);
}, [](const Active &data) -> QString {
return tr::lng_emoji_set_active(tr::now);
}, [](const Loading &data) {
const auto percent = (data.size > 0)
? snap((data.already * 100) / float64(data.size), 0., 100.)
: 0.;
return tr::lng_emoji_set_loading(
tr::now,
lt_percent,
QString::number(int(std::round(percent))) + '%',
lt_progress,
formatDownloadText(data.already, data.size));
}, [](const Failed &data) {
return tr::lng_attach_failed(tr::now);
});
}
QByteArray ReadFinalFile(const QString &path) {
constexpr auto kMaxZipSize = 10 * 1024 * 1024;
auto file = QFile(path);
if (file.size() > kMaxZipSize || !file.open(QIODevice::ReadOnly)) {
return QByteArray();
}
return file.readAll();
}
bool ExtractZipFile(zlib::FileToRead &zip, const QString path) {
constexpr auto kMaxSize = 10 * 1024 * 1024;
const auto content = zip.readCurrentFileContent(kMaxSize);
if (content.isEmpty() || zip.error() != UNZ_OK) {
return false;
}
auto file = QFile(path);
return file.open(QIODevice::WriteOnly)
&& (file.write(content) == content.size());
return StateDescription(
state,
tr::lng_emoji_set_active);
}
bool GoodSetPartName(const QString &name) {
@ -217,89 +152,24 @@ bool GoodSetPartName(const QString &name) {
}
bool UnpackSet(const QString &path, const QString &folder) {
const auto bytes = ReadFinalFile(path);
if (bytes.isEmpty()) {
return false;
}
auto zip = zlib::FileToRead(bytes);
if (zip.goToFirstFile() != UNZ_OK) {
return false;
}
do {
const auto name = zip.getCurrentFileName();
const auto path = folder + '/' + name;
if (GoodSetPartName(name) && !ExtractZipFile(zip, path)) {
return false;
}
const auto jump = zip.goToNextFile();
if (jump == UNZ_END_OF_LIST_OF_FILE) {
break;
} else if (jump != UNZ_OK) {
return false;
}
} while (true);
return true;
return UnpackBlob(path, folder, GoodSetPartName);
}
Loader::Loader(QObject *parent, int id)
: QObject(parent)
, _id(id)
, _size(GetDownloadSize(_id))
, _state(Loading{ 0, _size })
, _mtproto(Core::App().activeAccount().mtp()) {
const auto ready = [=](std::unique_ptr<MTP::DedicatedLoader> loader) {
if (loader) {
setImplementation(std::move(loader));
} else {
fail();
}
};
const auto location = GetDownloadLocation(id);
const auto folder = internal::SetDataPath(id);
MTP::StartDedicatedLoader(&_mtproto, location, folder, ready);
}
int Loader::id() const {
return _id;
}
rpl::producer<SetState> Loader::state() const {
return _state.value();
}
void Loader::setImplementation(
std::unique_ptr<MTP::DedicatedLoader> loader) {
_implementation = std::move(loader);
auto convert = [](auto value) {
return SetState(value);
};
_state = _implementation->progress(
) | rpl::map([](const Loading &state) {
return SetState(state);
});
_implementation->failed(
) | rpl::start_with_next([=] {
fail();
}, _implementation->lifetime());
_implementation->ready(
) | rpl::start_with_next([=](const QString &filepath) {
unpack(filepath);
}, _implementation->lifetime());
QDir(internal::SetDataPath(_id)).removeRecursively();
_implementation->start();
Loader::Loader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size) : BlobLoader(parent, id, location, folder, size) {
}
void Loader::unpack(const QString &path) {
const auto folder = internal::SetDataPath(_id);
const auto folder = internal::SetDataPath(id());
const auto weak = Ui::MakeWeak(this);
crl::async([=] {
if (UnpackSet(path, folder)) {
QFile(path).remove();
SwitchToSet(_id, crl::guard(weak, [=](bool success) {
SwitchToSet(id(), crl::guard(weak, [=](bool success) {
if (success) {
destroy();
} else {
@ -314,13 +184,6 @@ void Loader::unpack(const QString &path) {
});
}
void Loader::finalize(const QString &path) {
}
void Loader::fail() {
_state = Failed();
}
void Loader::destroy() {
Expects(GlobalLoader == this);
@ -334,8 +197,7 @@ Inner::Inner(QWidget *parent) : RpWidget(parent) {
void Inner::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto sets = Sets();
for (const auto &set : sets) {
for (const auto &set : kSets) {
content->add(object_ptr<Row>(content, set));
}
@ -379,6 +241,9 @@ void Row::paintPreview(Painter &p) const {
}
void Row::paintRadio(Painter &p) {
if (_loading && !_loading->animating()) {
_loading = nullptr;
}
const auto loading = _loading
? _loading->computeState()
: Ui::RadialState{ 0., 0, FullArcLength };
@ -536,7 +401,12 @@ void Row::setupHandler() {
}
void Row::load() {
SetGlobalLoader(base::make_unique_q<Loader>(App::main(), _id));
SetGlobalLoader(base::make_unique_q<Loader>(
App::main(),
_id,
GetDownloadLocation(_id),
internal::SetDataPath(_id),
GetDownloadSize(_id)));
}
void Row::setupLabels(const Set &set) {
@ -580,14 +450,20 @@ void Row::setupPreview(const Set &set) {
}
}
void Row::updateLoadingToFinished() {
_loading->update(
_state.current().is<Failed>() ? 0. : 1.,
true,
crl::now());
}
void Row::radialAnimationCallback(crl::time now) {
const auto updated = [&] {
const auto state = _state.current();
if (const auto loading = base::get_if<Loading>(&state)) {
const auto progress = (loading->size > 0)
? (loading->already / float64(loading->size))
: 0.;
return _loading->update(progress, false, now);
return _loading->update(CountProgress(loading), false, now);
} else {
updateLoadingToFinished();
}
return false;
}();
@ -636,15 +512,9 @@ void Row::setupAnimation() {
if (loading && !_loading) {
_loading = std::make_unique<Ui::RadialAnimation>(
[=](crl::time now) { radialAnimationCallback(now); });
const auto progress = (loading->size > 0)
? (loading->already / float64(loading->size))
: 0.;
_loading->start(progress);
_loading->start(CountProgress(loading));
} else if (!loading && _loading) {
_loading->update(
_state.current().is<Failed>() ? 0. : 1.,
true,
crl::now());
updateLoadingToFinished();
}
}, lifetime());

View file

@ -280,17 +280,13 @@ void InitSpellchecker(
not_null<Main::Session*> session,
not_null<Ui::InputField*> field) {
#ifndef TDESKTOP_DISABLE_SPELLCHECK
if (!Platform::Spellchecker::IsAvailable()) {
return;
}
const auto s = Ui::CreateChild<Spellchecker::SpellingHighlighter>(
field.get(),
session->settings().spellcheckerEnabledValue());
Spellchecker::SetPhrases({ {
{ &ph::lng_spellchecker_add, tr::lng_spellchecker_add() },
{ &ph::lng_spellchecker_remove, tr::lng_spellchecker_remove() },
{ &ph::lng_spellchecker_ignore, tr::lng_spellchecker_ignore() },
} });
session->settings().spellcheckerEnabledValue(),
Spellchecker::SpellingHighlighter::CustomContextMenuItem{
tr::lng_settings_manage_dictionaries(tr::now),
[=] { Ui::show(Box<Ui::ManageDictionariesBox>(session)); }
});
field->setExtendedContextMenu(s->contextMenuCreated());
#endif // TDESKTOP_DISABLE_SPELLCHECK
}

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qt_connection.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "boxes/dictionaries_manager.h"
#include "spellcheck/spelling_highlighter.h"
#endif // TDESKTOP_DISABLE_SPELLCHECK

View file

@ -0,0 +1,447 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/spellchecker_common.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "base/platform/base_platform_info.h"
#include "base/zlib_help.h"
#include "data/data_session.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "spellcheck/platform/platform_spellcheck.h"
#include "spellcheck/spellcheck_utils.h"
#include "spellcheck/spellcheck_value.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QInputMethod>
namespace Spellchecker {
namespace {
using namespace Storage::CloudBlob;
constexpr auto kDictExtensions = { "dic", "aff" };
// 31 - QLocale::English, 91 - QLocale::Portuguese.
constexpr auto kLangsForLWC = { 31, 91 };
// 225 - QLocale::UnitesStates, 30 - QLocale::Brazil.
constexpr auto kDefaultCountries = { 225, 30 };
// Language With Country.
inline auto LWC(QLocale::Country country) {
const auto l = QLocale::matchingLocales(
QLocale::AnyLanguage,
QLocale::AnyScript,
country)[0];
if (ranges::contains(kDefaultCountries, country)) {
return int(l.language());
}
return (l.language() * 1000) + country;
}
inline auto LanguageFromLocale(QLocale loc) {
const auto locLang = int(loc.language());
return (ranges::contains(kLangsForLWC, locLang)
&& (loc.country() != QLocale::AnyCountry))
? LWC(loc.country())
: locLang;
}
const auto kDictionaries = {
Dict{{ QLocale::English, 649, 174'516, "English" }}, // en_US
Dict{{ QLocale::Bulgarian, 594, 229'658, "\xd0\x91\xd1\x8a\xd0\xbb\xd0\xb3\xd0\xb0\xd1\x80\xd1\x81\xd0\xba\xd0\xb8" }}, // bg_BG
Dict{{ QLocale::Catalan, 595, 417'611, "\x43\x61\x74\x61\x6c\xc3\xa0" }}, // ca_ES
Dict{{ QLocale::Czech, 596, 860'286, "\xc4\x8c\x65\xc5\xa1\x74\x69\x6e\x61" }}, // cs_CZ
Dict{{ QLocale::Welsh, 597, 177'305, "\x43\x79\x6d\x72\x61\x65\x67" }}, // cy_GB
Dict{{ QLocale::Danish, 598, 345'874, "\x44\x61\x6e\x73\x6b" }}, // da_DK
Dict{{ QLocale::German, 599, 2'412'780, "\x44\x65\x75\x74\x73\x63\x68" }}, // de_DE
Dict{{ QLocale::Greek, 600, 1'389'160, "\xce\x95\xce\xbb\xce\xbb\xce\xb7\xce\xbd\xce\xb9\xce\xba\xce\xac" }}, // el_GR
Dict{{ LWC(QLocale::Australia), 601, 175'266, "English (Australia)" }}, // en_AU
Dict{{ LWC(QLocale::Canada), 602, 174'295, "English (Canada)" }}, // en_CA
Dict{{ LWC(QLocale::UnitedKingdom), 603, 174'433, "English (United Kingdom)" }}, // en_GB
Dict{{ QLocale::Spanish, 604, 264'717, "\x45\x73\x70\x61\xc3\xb1\x6f\x6c" }}, // es_ES
Dict{{ QLocale::Estonian, 605, 757'394, "\x45\x65\x73\x74\x69" }}, // et_EE
Dict{{ QLocale::Persian, 606, 333'911, "\xd9\x81\xd8\xa7\xd8\xb1\xd8\xb3\xdb\x8c" }}, // fa_IR
Dict{{ QLocale::French, 607, 321'391, "\x46\x72\x61\x6e\xc3\xa7\x61\x69\x73" }}, // fr_FR
Dict{{ QLocale::Hebrew, 608, 622'550, "\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa" }}, // he_IL
Dict{{ QLocale::Hindi, 609, 56'105, "\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80" }}, // hi_IN
Dict{{ QLocale::Croatian, 610, 668'876, "\x48\x72\x76\x61\x74\x73\x6b\x69" }}, // hr_HR
Dict{{ QLocale::Hungarian, 611, 660'402, "\x4d\x61\x67\x79\x61\x72" }}, // hu_HU
Dict{{ QLocale::Armenian, 612, 928'746, "\xd5\x80\xd5\xa1\xd5\xb5\xd5\xa5\xd6\x80\xd5\xa5\xd5\xb6" }}, // hy_AM
Dict{{ QLocale::Indonesian, 613, 100'134, "\x49\x6e\x64\x6f\x6e\x65\x73\x69\x61" }}, // id_ID
Dict{{ QLocale::Italian, 614, 324'613, "\x49\x74\x61\x6c\x69\x61\x6e\x6f" }}, // it_IT
Dict{{ QLocale::Korean, 615, 1'256'987, "\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4" }}, // ko_KR
Dict{{ QLocale::Lithuanian, 616, 267'427, "\x4c\x69\x65\x74\x75\x76\x69\xc5\xb3" }}, // lt_LT
Dict{{ QLocale::Latvian, 617, 641'602, "\x4c\x61\x74\x76\x69\x65\xc5\xa1\x75" }}, // lv_LV
Dict{{ QLocale::Norwegian, 618, 588'650, "\x4e\x6f\x72\x73\x6b" }}, // nb_NO
Dict{{ QLocale::Dutch, 619, 743'406, "\x4e\x65\x64\x65\x72\x6c\x61\x6e\x64\x73" }}, // nl_NL
Dict{{ QLocale::Polish, 620, 1'015'747, "\x50\x6f\x6c\x73\x6b\x69" }}, // pl_PL
Dict{{ QLocale::Portuguese, 621, 1'231'999, "\x50\x6f\x72\x74\x75\x67\x75\xc3\xaa\x73 (Brazil)" }}, // pt_BR
Dict{{ LWC(QLocale::Portugal), 622, 138'571, "\x50\x6f\x72\x74\x75\x67\x75\xc3\xaa\x73" }}, // pt_PT
Dict{{ QLocale::Romanian, 623, 455'643, "\x52\x6f\x6d\xc3\xa2\x6e\xc4\x83" }}, // ro_RO
Dict{{ QLocale::Russian, 624, 463'194, "\xd0\xa0\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9" }}, // ru_RU
Dict{{ QLocale::Slovak, 625, 525'328, "\x53\x6c\x6f\x76\x65\x6e\xc4\x8d\x69\x6e\x61" }}, // sk_SK
Dict{{ QLocale::Slovenian, 626, 1'143'710, "\x53\x6c\x6f\x76\x65\x6e\xc5\xa1\xc4\x8d\x69\x6e\x61" }}, // sl_SI
Dict{{ QLocale::Albanian, 627, 583'412, "\x53\x68\x71\x69\x70" }}, // sq_AL
Dict{{ QLocale::Swedish, 628, 593'877, "\x53\x76\x65\x6e\x73\x6b\x61" }}, // sv_SE
Dict{{ QLocale::Tamil, 629, 323'193, "\xe0\xae\xa4\xe0\xae\xae\xe0\xae\xbf\xe0\xae\xb4\xe0\xaf\x8d" }}, // ta_IN
Dict{{ QLocale::Tajik, 630, 369'931, "\xd0\xa2\xd0\xbe\xd2\xb7\xd0\xb8\xd0\xba\xd3\xa3" }}, // tg_TG
Dict{{ QLocale::Turkish, 631, 4'301'099, "\x54\xc3\xbc\x72\x6b\xc3\xa7\x65" }}, // tr_TR
Dict{{ QLocale::Ukrainian, 632, 445'711, "\xd0\xa3\xd0\xba\xd1\x80\xd0\xb0\xd1\x97\xd0\xbd\xd1\x81\xd1\x8c\xd0\xba\xd0\xb0" }}, // uk_UA
Dict{{ QLocale::Vietnamese, 633, 12'949, "\x54\x69\xe1\xba\xbf\x6e\x67\x20\x56\x69\xe1\xbb\x87\x74" }}, // vi_VN
// The Tajik code is 'tg_TG' in Chromium, but QT has only 'tg_TJ'.
};
inline auto IsSupportedLang(int lang) {
return ranges::contains(kDictionaries, lang, &Dict::id);
}
void EnsurePath() {
if (!QDir::current().mkpath(Spellchecker::DictionariesPath())) {
LOG(("App Error: Could not create dictionaries path."));
}
}
bool IsGoodPartName(const QString &name) {
return ranges::find_if(kDictExtensions, [&](const auto &ext) {
return name.endsWith(ext);
}) != end(kDictExtensions);
}
using DictLoaderPtr = std::shared_ptr<base::unique_qptr<DictLoader>>;
DictLoaderPtr BackgroundLoader;
rpl::event_stream<int> BackgroundLoaderChanged;
void SetBackgroundLoader(DictLoaderPtr loader) {
BackgroundLoader = std::move(loader);
}
void DownloadDictionaryInBackground(
not_null<Main::Session*> session,
int counter,
std::vector<int> langs) {
const auto id = langs[counter];
counter++;
const auto destroyer = [=] {
// This is a temporary workaround.
const auto copyId = id;
const auto copyLangs = langs;
const auto copySession = session;
const auto copyCounter = counter;
BackgroundLoader = nullptr;
BackgroundLoaderChanged.fire(0);
if (DictionaryExists(copyId)) {
auto dicts = copySession->settings().dictionariesEnabled();
if (!ranges::contains(dicts, copyId)) {
dicts.push_back(copyId);
copySession->settings().setDictionariesEnabled(std::move(dicts));
copySession->saveSettingsDelayed();
}
}
if (copyCounter >= copyLangs.size()) {
return;
}
DownloadDictionaryInBackground(copySession, copyCounter, copyLangs);
};
if (DictionaryExists(id)) {
destroyer();
return;
}
auto sharedLoader = std::make_shared<base::unique_qptr<DictLoader>>();
*sharedLoader = base::make_unique_q<DictLoader>(
App::main(),
id,
GetDownloadLocation(id),
DictPathByLangId(id),
GetDownloadSize(id),
crl::guard(session, destroyer));
SetBackgroundLoader(std::move(sharedLoader));
BackgroundLoaderChanged.fire_copy(id);
}
} // namespace
DictLoaderPtr GlobalLoader() {
return BackgroundLoader;
}
rpl::producer<int> GlobalLoaderChanged() {
return BackgroundLoaderChanged.events();
}
DictLoader::DictLoader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size,
Fn<void()> destroyCallback)
: BlobLoader(parent, id, location, folder, size)
, _destroyCallback(std::move(destroyCallback)) {
}
void DictLoader::unpack(const QString &path) {
Expects(_destroyCallback);
crl::async([=] {
const auto success = Spellchecker::UnpackDictionary(path, id());
if (success) {
QFile(path).remove();
}
crl::on_main(success ? _destroyCallback : [=] { fail(); });
});
}
void DictLoader::destroy() {
Expects(_destroyCallback);
_destroyCallback();
}
void DictLoader::fail() {
BlobLoader::fail();
destroy();
}
std::vector<Dict> Dictionaries() {
return kDictionaries | ranges::to_vector;
}
int GetDownloadSize(int id) {
return ranges::find(kDictionaries, id, &Spellchecker::Dict::id)->size;
}
MTP::DedicatedLoader::Location GetDownloadLocation(int id) {
const auto username = kCloudLocationUsername.utf16();
const auto i = ranges::find(kDictionaries, id, &Spellchecker::Dict::id);
return MTP::DedicatedLoader::Location{ username, i->postId };
}
QString DictPathByLangId(int langId) {
EnsurePath();
return qsl("%1/%2")
.arg(DictionariesPath())
.arg(Spellchecker::LocaleFromLangId(langId).name());
}
QString DictionariesPath() {
return cWorkingDir() + qsl("tdata/dictionaries");
}
bool UnpackDictionary(const QString &path, int langId) {
const auto folder = DictPathByLangId(langId);
return UnpackBlob(path, folder, IsGoodPartName);
}
bool DictionaryExists(int langId) {
if (!langId) {
return true;
}
const auto folder = DictPathByLangId(langId) + '/';
const auto bad = ranges::find_if(kDictExtensions, [&](const auto &ext) {
const auto name = Spellchecker::LocaleFromLangId(langId).name();
return !QFile(folder + name + '.' + ext).exists();
});
return (bad == end(kDictExtensions));
}
bool RemoveDictionary(int langId) {
if (!langId) {
return true;
}
const auto fileName = Spellchecker::LocaleFromLangId(langId).name();
const auto folder = qsl("%1/%2/")
.arg(DictionariesPath())
.arg(fileName);
return QDir(folder).removeRecursively();
}
bool WriteDefaultDictionary() {
// This is an unused function.
const auto en = QLocale::English;
if (DictionaryExists(en)) {
return false;
}
const auto fileName = QLocale(en).name();
const auto folder = qsl("%1/%2/")
.arg(DictionariesPath())
.arg(fileName);
QDir(folder).removeRecursively();
const auto path = folder + fileName;
QDir().mkpath(folder);
auto input = QFile(qsl(":/misc/en_US_dictionary"));
auto output = QFile(path);
if (input.open(QIODevice::ReadOnly)
&& output.open(QIODevice::WriteOnly)) {
output.write(input.readAll());
const auto result = Spellchecker::UnpackDictionary(path, en);
output.remove();
return result;
}
return false;
}
rpl::producer<QString> ButtonManageDictsState(
not_null<Main::Session*> session) {
if (Platform::Spellchecker::IsSystemSpellchecker()) {
return rpl::single(QString());
}
const auto computeString = [=] {
if (!session->settings().spellcheckerEnabled()) {
return QString();
}
if (!session->settings().dictionariesEnabled().size()) {
return QString();
}
const auto dicts = session->settings().dictionariesEnabled();
const auto filtered = ranges::view::all(
dicts
) | ranges::views::filter(
DictionaryExists
) | ranges::to_vector;
const auto active = Platform::Spellchecker::ActiveLanguages();
return (active.size() == filtered.size())
? QString::number(filtered.size())
: tr::lng_contacts_loading(tr::now);
};
const auto emptyValue = [] { return rpl::empty_value(); };
return rpl::single(
computeString()
) | rpl::then(
rpl::merge(
Spellchecker::SupportedScriptsChanged(),
session->settings().dictionariesEnabledChanges(
) | rpl::map(emptyValue),
session->settings().spellcheckerEnabledChanges(
) | rpl::map(emptyValue)
) | rpl::map(computeString)
);
}
std::vector<int> DefaultLanguages() {
std::vector<int> langs;
const auto append = [&](const auto loc) {
const auto l = LanguageFromLocale(loc);
if (!ranges::contains(langs, l) && IsSupportedLang(l)) {
langs.push_back(l);
}
};
const auto method = QGuiApplication::inputMethod();
langs.reserve(method ? 3 : 2);
if (method) {
append(method->locale());
}
append(QLocale(Platform::SystemLanguage()));
append(QLocale(Lang::LanguageIdOrDefault(Lang::Current().id())));
return langs;
}
void Start(not_null<Main::Session*> session) {
Spellchecker::SetPhrases({ {
{ &ph::lng_spellchecker_submenu, tr::lng_spellchecker_submenu() },
{ &ph::lng_spellchecker_add, tr::lng_spellchecker_add() },
{ &ph::lng_spellchecker_remove, tr::lng_spellchecker_remove() },
{ &ph::lng_spellchecker_ignore, tr::lng_spellchecker_ignore() },
} });
const auto settings = &session->settings();
const auto guard = gsl::finally([=]{
if (settings->spellcheckerEnabled()) {
Platform::Spellchecker::UpdateLanguages(
settings->dictionariesEnabled());
}
});
if (Platform::Spellchecker::IsSystemSpellchecker()) {
return;
}
Spellchecker::SetWorkingDirPath(DictionariesPath());
settings->dictionariesEnabledChanges(
) | rpl::start_with_next([](auto dictionaries) {
Platform::Spellchecker::UpdateLanguages(dictionaries);
}, session->lifetime());
settings->spellcheckerEnabledChanges(
) | rpl::start_with_next([=](auto enabled) {
Platform::Spellchecker::UpdateLanguages(
enabled
? settings->dictionariesEnabled()
: std::vector<int>());
}, session->lifetime());
const auto method = QGuiApplication::inputMethod();
const auto connectInput = [=] {
if (!method || !settings->spellcheckerEnabled()) {
return;
}
auto callback = [=] {
if (BackgroundLoader) {
return;
}
const auto l = LanguageFromLocale(method->locale());
if (!IsSupportedLang(l) || DictionaryExists(l)) {
return;
}
crl::on_main(session, [=] {
DownloadDictionaryInBackground(session, 0, { l });
});
};
QObject::connect(
method,
&QInputMethod::localeChanged,
std::move(callback));
};
if (settings->autoDownloadDictionaries()) {
session->data().contactsLoaded().changes(
) | rpl::start_with_next([=](bool loaded) {
if (!loaded) {
return;
}
DownloadDictionaryInBackground(session, 0, DefaultLanguages());
}, session->lifetime());
connectInput();
}
rpl::combine(
settings->spellcheckerEnabledValue(),
settings->autoDownloadDictionariesValue()
) | rpl::start_with_next([=](bool spell, bool download) {
if (spell && download) {
connectInput();
return;
}
QObject::disconnect(
method,
&QInputMethod::localeChanged,
nullptr,
nullptr);
}, session->lifetime());
}
} // namespace Spellchecker
#endif // !TDESKTOP_DISABLE_SPELLCHECK

View file

@ -0,0 +1,74 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "storage/storage_cloud_blob.h"
#include "base/unique_qptr.h"
namespace Main {
class Session;
} // namespace Main
namespace Spellchecker {
struct Dict : public Storage::CloudBlob::Blob {
};
int GetDownloadSize(int id);
MTP::DedicatedLoader::Location GetDownloadLocation(int id);
[[nodiscard]] QString DictionariesPath();
[[nodiscard]] QString DictPathByLangId(int langId);
bool UnpackDictionary(const QString &path, int langId);
[[nodiscard]] bool DictionaryExists(int langId);
bool RemoveDictionary(int langId);
[[nodiscard]] bool IsEn(int langId);
bool WriteDefaultDictionary();
std::vector<Dict> Dictionaries();
void Start(not_null<Main::Session*> session);
[[nodiscard]] rpl::producer<QString> ButtonManageDictsState(
not_null<Main::Session*> session);
std::vector<int> DefaultLanguages();
class DictLoader : public Storage::CloudBlob::BlobLoader {
public:
DictLoader(
QObject *parent,
int id,
MTP::DedicatedLoader::Location location,
const QString &folder,
int size,
Fn<void()> destroyCallback);
void destroy() override;
rpl::lifetime &lifetime() {
return _lifetime;
}
private:
void unpack(const QString &path) override;
void fail() override;
Fn<void()> _destroyCallback;
rpl::lifetime _lifetime;
};
std::shared_ptr<base::unique_qptr<DictLoader>> GlobalLoader();
rpl::producer<int> GlobalLoaderChanged();
} // namespace Spellchecker
#endif // !TDESKTOP_DISABLE_SPELLCHECK

View file

@ -230,10 +230,6 @@ void Application::run() {
_window = std::make_unique<Window::Controller>(&activeAccount());
const auto currentGeometry = _window->widget()->geometry();
_mediaView = std::make_unique<Media::View::OverlayWidget>();
_window->widget()->setGeometry(currentGeometry);
QCoreApplication::instance()->installEventFilter(this);
connect(
static_cast<QGuiApplication*>(QCoreApplication::instance()),
@ -262,8 +258,15 @@ void Application::run() {
_window->setupIntro();
}
}
_window->widget()->show();
const auto currentGeometry = _window->widget()->geometry();
_mediaView = std::make_unique<Media::View::OverlayWidget>();
_window->widget()->setGeometry(currentGeometry);
DEBUG_LOG(("Application Info: showing."));
_window->firstShow();
_window->finishFirstShow();
if (!locked() && cStartToSettings()) {
_window->showSettings();
@ -297,6 +300,8 @@ void Application::showPhoto(not_null<const PhotoOpenClickHandler*> link) {
}
void Application::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
Expects(_mediaView != nullptr);
_mediaView->showPhoto(photo, item);
_mediaView->activateWindow();
_mediaView->setFocus();
@ -305,12 +310,16 @@ void Application::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
void Application::showPhoto(
not_null<PhotoData*> photo,
not_null<PeerData*> peer) {
Expects(_mediaView != nullptr);
_mediaView->showPhoto(photo, peer);
_mediaView->activateWindow();
_mediaView->setFocus();
}
void Application::showDocument(not_null<DocumentData*> document, HistoryItem *item) {
Expects(_mediaView != nullptr);
if (cUseExternalVideoPlayer()
&& document->isVideoFile()
&& document->loaded()) {
@ -325,6 +334,8 @@ void Application::showDocument(not_null<DocumentData*> document, HistoryItem *it
void Application::showTheme(
not_null<DocumentData*> document,
const Data::CloudTheme &cloud) {
Expects(_mediaView != nullptr);
_mediaView->showTheme(document, cloud);
_mediaView->activateWindow();
_mediaView->setFocus();

View file

@ -55,6 +55,20 @@ std::map<int, const char*> BetaLogs() {
"\xE2\x80\xA2 Rotate photos and videos in the media viewer "
"using the rotate button in the bottom right corner.\n"
},
{
1009015,
"\xE2\x80\xA2 Mark new messages as read "
"while scrolling down through them.\n"
"\xE2\x80\xA2 Bug fixes and other minor improvements."
},
{
1009017,
"\xE2\x80\xA2 Spell checker on Windows 7.\n"
"\xE2\x80\xA2 Bug fixes and other minor improvements."
}
};
};

View file

@ -22,8 +22,8 @@ constexpr auto AppId = "{C4A4AE8F-B9F7-4CC7-8A6C-BF7EEE87ACA5}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Kotatogram Desktop"_cs;
constexpr auto AppFile = "Kotatogram"_cs;
constexpr auto AppVersion = 1009010;
constexpr auto AppVersionStr = "1.9.10";
constexpr auto AppVersion = 1009019;
constexpr auto AppVersionStr = "1.9.19";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
constexpr auto AppKotatoVersion = 1001007;

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_folder.h"
#include "data/data_location.h"
#include "data/data_histories.h"
#include "lang/lang_keys.h"
#include "base/unixtime.h"
#include "history/history.h"
@ -709,13 +710,14 @@ void ApplyChannelUpdate(
const auto folder = folderId
? channel->owner().folderLoaded(folderId)
: nullptr;
auto &histories = channel->owner().histories();
if (folder && history->folder() != folder) {
// If history folder is unknown or not synced, request both.
channel->session().api().requestDialogEntry(history);
channel->session().api().requestDialogEntry(folder);
histories.requestDialogEntry(history);
histories.requestDialogEntry(folder);
} else if (!history->folderKnown()
|| channel->pts() != update.vpts().v) {
channel->session().api().requestDialogEntry(history);
histories.requestDialogEntry(history);
} else {
history->applyDialogFields(
history->folder(),
@ -761,6 +763,9 @@ void ApplyChannelUpdate(
channel->session().api().applyNotifySettings(
MTP_inputNotifyPeer(channel->input),
update.vnotify_settings());
// For clearUpTill() call.
channel->owner().sendHistoryChangeNotifications();
}
void ApplyMegagroupAdmins(

View file

@ -569,7 +569,7 @@ void DocumentData::validateLottieSticker() {
void DocumentData::setDataAndCache(const QByteArray &data) {
setData(data);
if (saveToCache() && data.size() <= Storage::kMaxFileInMemory) {
session().data().cache().put(
owner().cache().put(
cacheKey(),
Storage::Cache::Database::TaggedValue(
base::duplicate(data),

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_histories.h"
#include "dialogs/dialogs_key.h"
#include "history/history.h"
#include "history/history_item.h"
@ -108,7 +109,7 @@ void Folder::registerOne(not_null<History*> history) {
if (_chatsList.indexed()->size() == 1) {
updateChatListSortPosition();
if (!_cloudUnread.known) {
session().api().requestDialogEntry(this);
owner().histories().requestDialogEntry(this);
}
} else {
updateChatListEntry();
@ -323,7 +324,7 @@ uint32 Folder::chatListViewVersion() const {
void Folder::requestChatListMessage() {
if (!chatListMessageKnown()) {
session().api().requestDialogEntry(this);
owner().histories().requestDialogEntry(this);
}
}

View file

@ -0,0 +1,683 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_folder.h"
#include "main/main_session.h"
#include "window/notifications_manager.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "core/application.h"
#include "apiwrap.h"
namespace Data {
namespace {
constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
} // namespace
Histories::Histories(not_null<Session*> owner)
: _owner(owner)
, _readRequestsTimer([=] { sendReadRequests(); }) {
}
Session &Histories::owner() const {
return *_owner;
}
Main::Session &Histories::session() const {
return _owner->session();
}
History *Histories::find(PeerId peerId) {
const auto i = peerId ? _map.find(peerId) : end(_map);
return (i != end(_map)) ? i->second.get() : nullptr;
}
not_null<History*> Histories::findOrCreate(PeerId peerId) {
Expects(peerId != 0);
if (const auto result = find(peerId)) {
return result;
}
const auto [i, ok] = _map.emplace(
peerId,
std::make_unique<History>(&owner(), peerId));
return i->second.get();
}
void Histories::unloadAll() {
for (const auto &[peerId, history] : _map) {
history->clear(History::ClearType::Unload);
}
}
void Histories::clearAll() {
_map.clear();
}
void Histories::readInbox(not_null<History*> history) {
if (history->lastServerMessageKnown()) {
const auto last = history->lastServerMessage();
readInboxTill(history, last ? last->id : 0);
return;
} else if (history->loadedAtBottom()) {
if (const auto lastId = history->maxMsgId()) {
readInboxTill(history, lastId);
return;
} else if (history->loadedAtTop()) {
readInboxTill(history, 0);
return;
}
}
requestDialogEntry(history, [=] {
Expects(history->lastServerMessageKnown());
const auto last = history->lastServerMessage();
readInboxTill(history, last ? last->id : 0);
});
}
void Histories::readInboxTill(not_null<HistoryItem*> item) {
const auto history = item->history();
if (!IsServerMsgId(item->id)) {
readClientSideMessage(item);
auto view = item->mainView();
if (!view) {
return;
}
auto block = view->block();
auto blockIndex = block->indexInHistory();
auto itemIndex = view->indexInBlock();
while (blockIndex > 0 || itemIndex > 0) {
if (itemIndex > 0) {
view = block->messages[--itemIndex].get();
} else {
while (blockIndex > 0) {
block = history->blocks[--blockIndex].get();
itemIndex = block->messages.size();
if (itemIndex > 0) {
view = block->messages[--itemIndex].get();
break;
}
}
}
item = view->data();
if (IsServerMsgId(item->id)) {
break;
}
}
if (!IsServerMsgId(item->id)) {
LOG(("App Error: "
"Can't read history till unknown local message."));
return;
}
}
readInboxTill(history, item->id);
}
void Histories::readInboxTill(not_null<History*> history, MsgId tillId) {
readInboxTill(history, tillId, false);
}
void Histories::readInboxTill(
not_null<History*> history,
MsgId tillId,
bool force) {
Expects(IsServerMsgId(tillId) || (!tillId && !force));
const auto syncGuard = gsl::finally([&] {
if (history->unreadCount() > 0) {
if (const auto last = history->lastServerMessage()) {
if (last->id == tillId) {
history->setUnreadCount(0);
history->updateChatListEntry();
}
}
}
});
history->session().notifications().clearIncomingFromHistory(history);
const auto needsRequest = history->readInboxTillNeedsRequest(tillId);
if (!needsRequest && !force) {
return;
} else if (!history->trackUnreadMessages()) {
return;
}
const auto maybeState = lookup(history);
if (maybeState && maybeState->sentReadTill >= tillId) {
return;
} else if (maybeState && maybeState->willReadTill >= tillId) {
if (force) {
sendPendingReadInbox(history);
}
return;
} else if (!needsRequest
&& (!maybeState || !maybeState->willReadTill)) {
return;
}
const auto stillUnread = history->countStillUnreadLocal(tillId);
if (!force
&& stillUnread
&& history->unreadCountKnown()
&& *stillUnread == history->unreadCount()) {
history->setInboxReadTill(tillId);
return;
}
auto &state = maybeState ? *maybeState : _states[history];
state.willReadTill = tillId;
if (force || !stillUnread || !*stillUnread) {
state.willReadWhen = 0;
sendReadRequests();
if (!stillUnread) {
return;
}
} else if (!state.willReadWhen) {
state.willReadWhen = crl::now() + kReadRequestTimeout;
if (!_readRequestsTimer.isActive()) {
_readRequestsTimer.callOnce(kReadRequestTimeout);
}
}
history->setInboxReadTill(tillId);
history->setUnreadCount(*stillUnread);
history->updateChatListEntry();
}
void Histories::readInboxOnNewMessage(not_null<HistoryItem*> item) {
if (!IsServerMsgId(item->id)) {
readClientSideMessage(item);
} else {
readInboxTill(item->history(), item->id, true);
}
}
void Histories::readClientSideMessage(not_null<HistoryItem*> item) {
if (item->out() || !item->unread()) {
return;
}
const auto history = item->history();
item->markClientSideAsRead();
if (const auto unread = history->unreadCount()) {
history->setUnreadCount(unread - 1);
}
}
void Histories::requestDialogEntry(not_null<Data::Folder*> folder) {
if (_dialogFolderRequests.contains(folder)) {
return;
}
_dialogFolderRequests.emplace(folder);
auto peers = QVector<MTPInputDialogPeer>(
1,
MTP_inputDialogPeerFolder(MTP_int(folder->id())));
session().api().request(MTPmessages_GetPeerDialogs(
MTP_vector(std::move(peers))
)).done([=](const MTPmessages_PeerDialogs &result) {
applyPeerDialogs(result);
_dialogFolderRequests.remove(folder);
}).fail([=](const RPCError &error) {
_dialogFolderRequests.remove(folder);
}).send();
}
void Histories::requestDialogEntry(
not_null<History*> history,
Fn<void()> callback) {
const auto i = _dialogRequests.find(history);
if (i != end(_dialogRequests)) {
if (callback) {
i->second.push_back(std::move(callback));
}
return;
}
const auto [j, ok] = _dialogRequestsPending.try_emplace(history);
if (callback) {
j->second.push_back(std::move(callback));
}
if (!ok) {
return;
}
postponeRequestDialogEntries();
}
void Histories::postponeRequestDialogEntries() {
if (_dialogRequestsPending.size() > 1) {
return;
}
Core::App().postponeCall(crl::guard(&session(), [=] {
sendDialogRequests();
}));
}
void Histories::sendDialogRequests() {
if (_dialogRequestsPending.empty()) {
return;
}
const auto histories = ranges::view::all(
_dialogRequestsPending
) | ranges::view::transform([](const auto &pair) {
return pair.first;
}) | ranges::view::filter([&](not_null<History*> history) {
const auto state = lookup(history);
if (!state) {
return true;
} else if (!postponeEntryRequest(*state)) {
return true;
}
state->postponedRequestEntry = true;
return false;
}) | ranges::to_vector;
auto peers = QVector<MTPInputDialogPeer>();
const auto dialogPeer = [](not_null<History*> history) {
return MTP_inputDialogPeer(history->peer->input);
};
ranges::transform(
histories,
ranges::back_inserter(peers),
dialogPeer);
for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) {
_dialogRequests.emplace(history, std::move(callbacks));
}
const auto finalize = [=] {
for (const auto history : histories) {
const auto state = lookup(history);
if (!state || !state->postponedRequestEntry) {
dialogEntryApplied(history);
history->updateChatListExistence();
}
}
};
session().api().request(MTPmessages_GetPeerDialogs(
MTP_vector(std::move(peers))
)).done([=](const MTPmessages_PeerDialogs &result) {
applyPeerDialogs(result);
finalize();
}).fail([=](const RPCError &error) {
finalize();
}).send();
}
void Histories::dialogEntryApplied(not_null<History*> history) {
const auto state = lookup(history);
if (state && state->postponedRequestEntry) {
return;
}
history->dialogEntryApplied();
if (const auto callbacks = _dialogRequestsPending.take(history)) {
for (const auto &callback : *callbacks) {
callback();
}
}
if (const auto callbacks = _dialogRequests.take(history)) {
for (const auto &callback : *callbacks) {
callback();
}
}
if (state && state->sentReadTill && state->sentReadDone) {
history->setInboxReadTill(base::take(state->sentReadTill));
checkEmptyState(history);
}
}
void Histories::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) {
Expects(dialogs.type() == mtpc_messages_peerDialogs);
const auto &data = dialogs.c_messages_peerDialogs();
_owner->processUsers(data.vusers());
_owner->processChats(data.vchats());
_owner->processMessages(data.vmessages(), NewMessageType::Last);
for (const auto &dialog : data.vdialogs().v) {
dialog.match([&](const MTPDdialog &data) {
if (const auto peerId = peerFromMTP(data.vpeer())) {
_owner->history(peerId)->applyDialog(nullptr, data);
}
}, [&](const MTPDdialogFolder &data) {
const auto folder = _owner->processFolder(data.vfolder());
folder->applyDialog(data);
});
}
_owner->sendHistoryChangeNotifications();
}
void Histories::changeDialogUnreadMark(
not_null<History*> history,
bool unread) {
history->setUnreadMark(unread);
using Flag = MTPmessages_MarkDialogUnread::Flag;
session().api().request(MTPmessages_MarkDialogUnread(
MTP_flags(unread ? Flag::f_unread : Flag(0)),
MTP_inputDialogPeer(history->peer->input)
)).send();
}
void Histories::requestFakeChatListMessage(
not_null<History*> history) {
if (_fakeChatListRequests.contains(history)) {
return;
}
_fakeChatListRequests.emplace(history);
sendRequest(history, RequestType::History, [=](Fn<void()> finish) {
return session().api().request(MTPmessages_GetHistory(
history->peer->input,
MTP_int(0), // offset_id
MTP_int(0), // offset_date
MTP_int(0), // add_offset
MTP_int(2), // limit
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
_fakeChatListRequests.erase(history);
history->setFakeChatListMessageFrom(result);
finish();
}).fail([=](const RPCError &error) {
_fakeChatListRequests.erase(history);
history->setFakeChatListMessageFrom(MTP_messages_messages(
MTP_vector<MTPMessage>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0)));
finish();
}).send();
});
}
void Histories::sendPendingReadInbox(not_null<History*> history) {
if (const auto state = lookup(history)) {
if (state->willReadTill && state->willReadWhen) {
state->willReadWhen = 0;
sendReadRequests();
}
}
}
void Histories::sendReadRequests() {
if (_states.empty()) {
return;
}
const auto now = crl::now();
auto next = std::optional<crl::time>();
for (auto &[history, state] : _states) {
if (!state.willReadTill) {
continue;
} else if (state.willReadWhen <= now) {
sendReadRequest(history, state);
} else if (!next || *next > state.willReadWhen) {
next = state.willReadWhen;
}
}
if (next.has_value()) {
_readRequestsTimer.callOnce(*next - now);
} else {
_readRequestsTimer.cancel();
}
}
void Histories::sendReadRequest(not_null<History*> history, State &state) {
Expects(state.willReadTill > state.sentReadTill);
const auto tillId = state.sentReadTill = base::take(state.willReadTill);
state.willReadWhen = 0;
state.sentReadDone = false;
sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
const auto finished = [=] {
const auto state = lookup(history);
Assert(state != nullptr);
if (state->sentReadTill == tillId) {
state->sentReadDone = true;
if (history->unreadCountRefreshNeeded(tillId)) {
requestDialogEntry(history);
} else {
state->sentReadTill = 0;
}
} else {
Assert(!state->sentReadTill || state->sentReadTill > tillId);
}
sendReadRequests();
finish();
};
if (const auto channel = history->peer->asChannel()) {
return session().api().request(MTPchannels_ReadHistory(
channel->inputChannel,
MTP_int(tillId)
)).done([=](const MTPBool &result) {
finished();
}).fail([=](const RPCError &error) {
finished();
}).send();
} else {
return session().api().request(MTPmessages_ReadHistory(
history->peer->input,
MTP_int(tillId)
)).done([=](const MTPmessages_AffectedMessages &result) {
session().api().applyAffectedMessages(history->peer, result);
finished();
}).fail([=](const RPCError &error) {
finished();
}).send();
}
});
}
void Histories::checkEmptyState(not_null<History*> history) {
const auto empty = [](const State &state) {
return state.postponed.empty()
&& !state.postponedRequestEntry
&& state.sent.empty()
&& (state.willReadTill == 0)
&& (state.sentReadTill == 0);
};
const auto i = _states.find(history);
if (i != end(_states) && empty(i->second)) {
_states.erase(i);
}
}
bool Histories::postponeHistoryRequest(const State &state) const {
const auto proj = [](const auto &pair) {
return pair.second.type;
};
const auto i = ranges::find(state.sent, RequestType::Delete, proj);
return (i != end(state.sent));
}
bool Histories::postponeEntryRequest(const State &state) const {
const auto i = ranges::find_if(state.sent, [](const auto &pair) {
return pair.second.type != RequestType::History;
});
return (i != end(state.sent));
}
void Histories::deleteMessages(
not_null<History*> history,
const QVector<MTPint> &ids,
bool revoke) {
sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
const auto done = [=](const MTPmessages_AffectedMessages &result) {
session().api().applyAffectedMessages(history->peer, result);
finish();
history->requestChatListMessage();
};
const auto fail = [=](const RPCError &error) {
finish();
};
if (const auto channel = history->peer->asChannel()) {
return session().api().request(MTPchannels_DeleteMessages(
channel->inputChannel,
MTP_vector<MTPint>(ids)
)).done(done).fail(fail).send();
} else {
using Flag = MTPmessages_DeleteMessages::Flag;
return session().api().request(MTPmessages_DeleteMessages(
MTP_flags(revoke ? Flag::f_revoke : Flag(0)),
MTP_vector<MTPint>(ids)
)).done(done).fail(fail).send();
}
});
}
void Histories::deleteAllMessages(
not_null<History*> history,
MsgId deleteTillId,
bool justClear,
bool revoke) {
sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
const auto peer = history->peer;
const auto fail = [=](const RPCError &error) {
finish();
};
if (const auto channel = peer->asChannel()) {
return session().api().request(MTPchannels_DeleteHistory(
channel->inputChannel,
MTP_int(deleteTillId)
)).done([=](const MTPBool &result) {
finish();
}).fail(fail).send();
} else {
using Flag = MTPmessages_DeleteHistory::Flag;
const auto flags = Flag(0)
| (justClear ? Flag::f_just_clear : Flag(0))
| ((peer->isUser() && revoke) ? Flag::f_revoke : Flag(0));
return session().api().request(MTPmessages_DeleteHistory(
MTP_flags(flags),
peer->input,
MTP_int(0)
)).done([=](const MTPmessages_AffectedHistory &result) {
const auto offset = session().api().applyAffectedHistory(
peer,
result);
if (offset > 0) {
deleteAllMessages(
history,
deleteTillId,
justClear,
revoke);
}
finish();
}).fail(fail).send();
}
});
}
int Histories::sendRequest(
not_null<History*> history,
RequestType type,
Fn<mtpRequestId(Fn<void()> finish)> generator) {
Expects(type != RequestType::None);
auto &state = _states[history];
const auto id = ++_requestAutoincrement;
_historyByRequest.emplace(id, history);
if (type == RequestType::History && postponeHistoryRequest(state)) {
state.postponed.emplace(
id,
PostponedHistoryRequest{ std::move(generator) });
return id;
}
const auto requestId = generator([=] { checkPostponed(history, id); });
state.sent.emplace(id, SentRequest{
std::move(generator),
requestId,
type
});
if (!state.postponedRequestEntry
&& postponeEntryRequest(state)
&& _dialogRequests.contains(history)) {
state.postponedRequestEntry = true;
}
if (postponeHistoryRequest(state)) {
const auto resendHistoryRequest = [&](auto &pair) {
auto &[id, sent] = pair;
if (sent.type != RequestType::History) {
return false;
}
state.postponed.emplace(
id,
PostponedHistoryRequest{ std::move(sent.generator) });
session().api().request(sent.id).cancel();
return true;
};
state.sent.erase(
ranges::remove_if(state.sent, resendHistoryRequest),
end(state.sent));
}
return id;
}
void Histories::checkPostponed(not_null<History*> history, int id) {
if (const auto state = lookup(history)) {
finishSentRequest(history, state, id);
}
}
void Histories::cancelRequest(int id) {
const auto history = _historyByRequest.take(id);
if (!history) {
return;
}
const auto state = lookup(*history);
if (!state) {
return;
}
state->postponed.remove(id);
finishSentRequest(*history, state, id);
}
void Histories::finishSentRequest(
not_null<History*> history,
not_null<State*> state,
int id) {
_historyByRequest.remove(id);
const auto i = state->sent.find(id);
if (i != end(state->sent)) {
session().api().request(i->second.id).cancel();
state->sent.erase(i);
}
if (!state->postponed.empty() && !postponeHistoryRequest(*state)) {
for (auto &[id, postponed] : base::take(state->postponed)) {
const auto requestId = postponed.generator([=, id=id] {
checkPostponed(history, id);
});
state->sent.emplace(id, SentRequest{
std::move(postponed.generator),
requestId,
RequestType::History
});
}
}
if (state->postponedRequestEntry && !postponeEntryRequest(*state)) {
const auto i = _dialogRequests.find(history);
Assert(i != end(_dialogRequests));
const auto [j, ok] = _dialogRequestsPending.emplace(
history,
std::move(i->second));
Assert(ok);
_dialogRequests.erase(i);
state->postponedRequestEntry = false;
postponeRequestDialogEntries();
}
checkEmptyState(history);
}
Histories::State *Histories::lookup(not_null<History*> history) {
const auto i = _states.find(history);
return (i != end(_states)) ? &i->second : nullptr;
}
} // namespace Data

View file

@ -0,0 +1,133 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
class History;
class HistoryItem;
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
class Folder;
class Histories final {
public:
enum class RequestType : uchar {
None,
History,
ReadInbox,
Delete,
Send,
};
explicit Histories(not_null<Session*> owner);
[[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] History *find(PeerId peerId);
[[nodiscard]] not_null<History*> findOrCreate(PeerId peerId);
void unloadAll();
void clearAll();
void readInbox(not_null<History*> history);
void readInboxTill(not_null<HistoryItem*> item);
void readInboxTill(not_null<History*> history, MsgId tillId);
void readInboxOnNewMessage(not_null<HistoryItem*> item);
void readClientSideMessage(not_null<HistoryItem*> item);
void sendPendingReadInbox(not_null<History*> history);
void requestDialogEntry(not_null<Data::Folder*> folder);
void requestDialogEntry(
not_null<History*> history,
Fn<void()> callback = nullptr);
void dialogEntryApplied(not_null<History*> history);
void changeDialogUnreadMark(not_null<History*> history, bool unread);
//void changeDialogUnreadMark(not_null<Data::Feed*> feed, bool unread); // #feed
void requestFakeChatListMessage(not_null<History*> history);
void deleteMessages(
not_null<History*> history,
const QVector<MTPint> &ids,
bool revoke);
void deleteAllMessages(
not_null<History*> history,
MsgId deleteTillId,
bool justClear,
bool revoke);
int sendRequest(
not_null<History*> history,
RequestType type,
Fn<mtpRequestId(Fn<void()> finish)> generator);
void cancelRequest(int id);
private:
struct PostponedHistoryRequest {
Fn<mtpRequestId(Fn<void()> finish)> generator;
};
struct SentRequest {
Fn<mtpRequestId(Fn<void()> finish)> generator;
mtpRequestId id = 0;
RequestType type = RequestType::None;
};
struct State {
base::flat_map<int, PostponedHistoryRequest> postponed;
base::flat_map<int, SentRequest> sent;
MsgId willReadTill = 0;
MsgId sentReadTill = 0;
crl::time willReadWhen = 0;
bool sentReadDone = false;
bool postponedRequestEntry = false;
};
void readInboxTill(not_null<History*> history, MsgId tillId, bool force);
void sendReadRequests();
void sendReadRequest(not_null<History*> history, State &state);
[[nodiscard]] State *lookup(not_null<History*> history);
void checkEmptyState(not_null<History*> history);
void checkPostponed(not_null<History*> history, int id);
void finishSentRequest(
not_null<History*> history,
not_null<State*> state,
int id);
[[nodiscard]] bool postponeHistoryRequest(const State &state) const;
[[nodiscard]] bool postponeEntryRequest(const State &state) const;
void postponeRequestDialogEntries();
void sendDialogRequests();
void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs);
const not_null<Session*> _owner;
std::unordered_map<PeerId, std::unique_ptr<History>> _map;
base::flat_map<not_null<History*>, State> _states;
base::flat_map<int, not_null<History*>> _historyByRequest;
int _requestAutoincrement = 0;
base::Timer _readRequestsTimer;
base::flat_set<not_null<Data::Folder*>> _dialogFolderRequests;
base::flat_map<
not_null<History*>,
std::vector<Fn<void()>>> _dialogRequests;
base::flat_map<
not_null<History*>,
std::vector<Fn<void()>>> _dialogRequestsPending;
base::flat_set<not_null<History*>> _fakeChatListRequests;
};
} // namespace Data

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "base/unixtime.h"
#include "base/crc32hash.h"
#include "lang/lang_keys.h"
@ -457,7 +458,7 @@ void PeerData::checkFolder(FolderId folderId) {
: nullptr;
if (const auto history = owner().historyLoaded(this)) {
if (folder && history->folder() != folder) {
session().api().requestDialogEntry(history);
owner().histories().requestDialogEntry(history);
}
}
}

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_messages.h"
#include "data/data_channel.h"
#include "data/data_histories.h"
#include "history/history.h"
#include "history/history_item.h"
#include "apiwrap.h"
@ -193,7 +194,7 @@ SearchController::CacheEntry::CacheEntry(const Query &query)
}
SearchController::SearchController(not_null<Main::Session*> session)
: _api(session->api().instance()) {
: _session(session) {
}
bool SearchController::hasInCache(const Query &query) const {
@ -366,23 +367,31 @@ void SearchController::requestMore(
if (!prepared) {
return;
}
auto requestId = _api.request(
std::move(*prepared)
).done([=](const MTPmessages_Messages &result) {
listData->requests.remove(key);
auto parsed = ParseSearchResult(
listData->peer,
query.type,
key.aroundId,
key.direction,
result);
listData->list.addSlice(
std::move(parsed.messageIds),
parsed.noSkipRange,
parsed.fullCount);
}).send();
auto &histories = _session->data().histories();
const auto type = ::Data::Histories::RequestType::History;
const auto history = _session->data().history(listData->peer);
auto requestId = histories.sendRequest(history, type, [=](Fn<void()> finish) {
return _session->api().request(
std::move(*prepared)
).done([=](const MTPmessages_Messages &result) {
listData->requests.remove(key);
auto parsed = ParseSearchResult(
listData->peer,
query.type,
key.aroundId,
key.direction,
result);
listData->list.addSlice(
std::move(parsed.messageIds),
parsed.noSkipRange,
parsed.fullCount);
finish();
}).fail([=](const RPCError &error) {
finish();
}).send();
});
listData->requests.emplace(key, [=] {
_api.request(requestId).cancel();
_session->data().histories().cancelRequest(requestId);
});
}

View file

@ -129,7 +129,7 @@ private:
const Query &query,
Data *listData);
MTP::Sender _api;
const not_null<Main::Session*> _session;
Cache _cache;
Cache::iterator _current = _cache.end();

View file

@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_cloud_themes.h"
#include "data/data_streaming.h"
#include "data/data_media_rotation.h"
#include "data/data_histories.h"
#include "base/platform/base_platform_info.h"
#include "base/unixtime.h"
#include "base/call_delayed.h"
@ -194,7 +195,8 @@ Session::Session(not_null<Main::Session*> session)
, _scheduledMessages(std::make_unique<ScheduledMessages>(this))
, _cloudThemes(std::make_unique<CloudThemes>(session))
, _streaming(std::make_unique<Streaming>(this))
, _mediaRotation(std::make_unique<MediaRotation>()) {
, _mediaRotation(std::make_unique<MediaRotation>())
, _histories(std::make_unique<Histories>(this)) {
_cache->open(Local::cacheKey());
_bigFileCache->open(Local::cacheBigFileKey());
@ -215,9 +217,7 @@ Session::Session(not_null<Main::Session*> session)
void Session::clear() {
_sendActions.clear();
for (const auto &[peerId, history] : _histories) {
history->clear(History::ClearType::Unload);
}
_histories->unloadAll();
_scheduledMessages = nullptr;
_dependentMessages.clear();
base::take(_messages);
@ -227,7 +227,7 @@ void Session::clear() {
cSetRecentInlineBots(RecentInlineBots());
cSetRecentStickers(RecentStickerPack());
App::clearMousedItems();
_histories.clear();
_histories->clearAll();
}
not_null<PeerData*> Session::peer(PeerId id) {
@ -768,20 +768,11 @@ void Session::enumerateChannels(
}
not_null<History*> Session::history(PeerId peerId) {
Expects(peerId != 0);
if (const auto result = historyLoaded(peerId)) {
return result;
}
const auto [i, ok] = _histories.emplace(
peerId,
std::make_unique<History>(this, peerId));
return i->second.get();
return _histories->findOrCreate(peerId);
}
History *Session::historyLoaded(PeerId peerId) const {
const auto i = peerId ? _histories.find(peerId) : end(_histories);
return (i != end(_histories)) ? i->second.get() : nullptr;
return _histories->find(peerId);
}
not_null<History*> Session::history(not_null<const PeerData*> peer) {
@ -1539,7 +1530,7 @@ void Session::applyDialog(
return;
}
const auto history = session().data().history(peerId);
const auto history = this->history(peerId);
history->applyDialog(requestFolder, data);
setPinnedFromDialog(history, data.is_pinned());
@ -1733,18 +1724,15 @@ auto Session::messagesListForInsert(ChannelId channelId)
: &_channelMessages[channelId];
}
HistoryItem *Session::registerMessage(std::unique_ptr<HistoryItem> item) {
Expects(item != nullptr);
const auto result = item.get();
const auto list = messagesListForInsert(result->channelId());
const auto i = list->find(result->id);
void Session::registerMessage(not_null<HistoryItem*> item) {
const auto list = messagesListForInsert(item->channelId());
const auto itemId = item->id;
const auto i = list->find(itemId);
if (i != list->end()) {
LOG(("App Error: Trying to re-registerMessage()."));
i->second->destroy();
}
list->emplace(result->id, std::move(item));
return result;
list->emplace(itemId, item);
}
void Session::processMessagesDeleted(
@ -1763,7 +1751,7 @@ void Session::processMessagesDeleted(
const auto i = list ? list->find(messageId.v) : Messages::iterator();
if (list && i != list->end()) {
const auto history = i->second->history();
destroyMessage(i->second.get());
i->second->destroy();
if (!history->chatListMessageKnown()) {
historiesToCheck.emplace(history);
}
@ -1789,32 +1777,12 @@ void Session::removeDependencyMessage(not_null<HistoryItem*> item) {
}
}
void Session::destroyMessage(not_null<HistoryItem*> item) {
Expects(item->isHistoryEntry() || !item->mainView());
void Session::unregisterMessage(not_null<HistoryItem*> item) {
const auto peerId = item->history()->peer->id;
if (item->isHistoryEntry()) {
// All this must be done for all items manually in History::clear()!
item->eraseFromUnreadMentions();
if (IsServerMsgId(item->id)) {
if (const auto types = item->sharedMediaTypes()) {
session().storage().remove(Storage::SharedMediaRemoveOne(
peerId,
types,
item->id));
}
} else {
session().api().cancelLocalItem(item);
}
item->history()->itemRemoved(item);
}
_itemRemoved.fire_copy(item);
groups().unregisterMessage(item);
removeDependencyMessage(item);
session().notifications().clearFromItem(item);
const auto list = messagesListForInsert(peerToChannel(peerId));
list->erase(item->id);
messagesListForInsert(peerToChannel(peerId))->erase(item->id);
}
MsgId Session::nextLocalMessageId() {
@ -3629,7 +3597,7 @@ void Session::serviceNotification(
}
const auto history = this->history(PeerData::kServiceNotificationsId);
if (!history->folderKnown()) {
_session->api().requestDialogEntry(history, [=] {
histories().requestDialogEntry(history, [=] {
insertCheckedServiceNotification(message, media, date);
});
} else {

View file

@ -61,6 +61,7 @@ class ScheduledMessages;
class CloudThemes;
class Streaming;
class MediaRotation;
class Histories;
class Session final {
public:
@ -96,6 +97,9 @@ public:
[[nodiscard]] MediaRotation &mediaRotation() const {
return *_mediaRotation;
}
[[nodiscard]] Histories &histories() const {
return *_histories;
}
[[nodiscard]] MsgId nextNonHistoryEntryId() {
return ++_nonHistoryEntryId;
}
@ -367,22 +371,8 @@ public:
const Dialogs::Key &key1,
const Dialogs::Key &key2);
template <typename ...Args>
not_null<HistoryMessage*> makeMessage(Args &&...args) {
return static_cast<HistoryMessage*>(
registerMessage(
std::make_unique<HistoryMessage>(
std::forward<Args>(args)...)));
}
template <typename ...Args>
not_null<HistoryService*> makeServiceMessage(Args &&...args) {
return static_cast<HistoryService*>(
registerMessage(
std::make_unique<HistoryService>(
std::forward<Args>(args)...)));
}
void destroyMessage(not_null<HistoryItem*> item);
void registerMessage(not_null<HistoryItem*> item);
void unregisterMessage(not_null<HistoryItem*> item);
// Returns true if item found and it is not detached.
bool checkEntitiesAndViewsUpdate(const MTPDmessage &data);
@ -694,7 +684,7 @@ public:
void clearLocalStorage();
private:
using Messages = std::unordered_map<MsgId, std::unique_ptr<HistoryItem>>;
using Messages = std::unordered_map<MsgId, not_null<HistoryItem*>>;
void suggestStartExport();
@ -715,7 +705,8 @@ private:
const Messages *messagesList(ChannelId channelId) const;
not_null<Messages*> messagesListForInsert(ChannelId channelId);
HistoryItem *registerMessage(std::unique_ptr<HistoryItem> item);
not_null<HistoryItem*> registerMessage(
std::unique_ptr<HistoryItem> item);
void changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId);
void removeDependencyMessage(not_null<HistoryItem*> item);
@ -968,7 +959,6 @@ private:
base::Timer _unmuteByFinishedTimer;
std::unordered_map<PeerId, std::unique_ptr<PeerData>> _peers;
std::unordered_map<PeerId, std::unique_ptr<History>> _histories;
MessageIdsList _mimeForwardIds;
@ -989,6 +979,7 @@ private:
std::unique_ptr<CloudThemes> _cloudThemes;
std::unique_ptr<Streaming> _streaming;
std::unique_ptr<MediaRotation> _mediaRotation;
std::unique_ptr<Histories> _histories;
MsgId _nonHistoryEntryId = ServerMaxMsgId;
rpl::lifetime _lifetime;

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_peer_values.h"
#include "data/data_histories.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
@ -2071,7 +2072,7 @@ bool InnerWidget::searchReceived(
_searchInChat,
item));
if (uniquePeers && !history->unreadCountKnown()) {
history->session().api().requestDialogEntry(history);
history->owner().histories().requestDialogEntry(history);
}
}
lastDateFound = lastDate;

View file

@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_folder.h"
#include "data/data_histories.h"
#include "facades.h"
#include "app.h"
#include "styles/style_dialogs.h"
@ -392,6 +393,7 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) {
_searchQueries.clear();
_searchQuery = QString();
_scroll->scrollToY(0);
cancelSearchRequest();
onSearchMessages();
}, lifetime());
}
@ -736,7 +738,7 @@ bool Widget::onSearchMessages(bool searchCache) {
auto result = false;
auto q = _filter->getLastText().trimmed();
if (q.isEmpty() && !_searchFromUser) {
MTP::cancel(base::take(_searchRequest));
cancelSearchRequest();
MTP::cancel(base::take(_peerSearchRequest));
return true;
}
@ -753,7 +755,7 @@ bool Widget::onSearchMessages(bool searchCache) {
_searchQueryFrom = _searchFromUser;
_searchNextRate = 0;
_searchFull = _searchFullMigrated = false;
MTP::cancel(base::take(_searchRequest));
cancelSearchRequest();
searchReceived(
_searchInChat
? SearchRequestType::PeerFromStart
@ -767,19 +769,23 @@ bool Widget::onSearchMessages(bool searchCache) {
_searchQueryFrom = _searchFromUser;
_searchNextRate = 0;
_searchFull = _searchFullMigrated = false;
MTP::cancel(base::take(_searchRequest));
cancelSearchRequest();
if (const auto peer = _searchInChat.peer()) {
const auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = MTP::send(
MTPmessages_Search(
auto &histories = session().data().histories();
const auto type = Data::Histories::RequestType::History;
const auto history = session().data().history(peer);
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
const auto type = SearchRequestType::PeerFromStart;
const auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = session().api().request(MTPmessages_Search(
flags,
peer->input,
MTP_string(_searchQuery),
_searchQueryFrom
(_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty(),
: MTP_inputUserEmpty()),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
@ -788,9 +794,19 @@ bool Widget::onSearchMessages(bool searchCache) {
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)),
rpcDone(&Widget::searchReceived, SearchRequestType::PeerFromStart),
rpcFail(&Widget::searchFailed, SearchRequestType::PeerFromStart));
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).fail([=](const RPCError &error) {
searchFailed(type, error, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).send();
_searchQueries.insert(_searchRequest, _searchQuery);
return _searchRequest;
});
//} else if (const auto feed = _searchInChat.feed()) { // #feed
// _searchRequest = MTP::send(
// MTPchannels_SearchFeed(
@ -802,6 +818,7 @@ bool Widget::onSearchMessages(bool searchCache) {
// MTP_int(SearchPerPage)),
// rpcDone(&Widget::searchReceived, SearchRequestType::FromStart),
// rpcFail(&Widget::searchFailed, SearchRequestType::FromStart));
// _searchQueries.insert(_searchRequest, _searchQuery);
} else {
const auto flags = session().settings().skipArchiveInSearch()
? MTPmessages_SearchGlobal::Flag::f_folder_id
@ -818,8 +835,8 @@ bool Widget::onSearchMessages(bool searchCache) {
MTP_int(SearchPerPage)),
rpcDone(&Widget::searchReceived, SearchRequestType::FromStart),
rpcFail(&Widget::searchFailed, SearchRequestType::FromStart));
_searchQueries.insert(_searchRequest, _searchQuery);
}
_searchQueries.insert(_searchRequest, _searchQuery);
}
const auto query = Api::ConvertPeerSearchQuery(q);
if (searchForPeersRequired(query)) {
@ -906,93 +923,130 @@ void Widget::searchMessages(
}
void Widget::onSearchMore() {
if (!_searchRequest) {
if (!_searchFull) {
auto offsetPeer = _inner->lastSearchPeer();
auto offsetId = _inner->lastSearchId();
if (const auto peer = _searchInChat.peer()) {
if (_searchRequest || _searchInHistoryRequest) {
return;
}
if (!_searchFull) {
auto offsetPeer = _inner->lastSearchPeer();
auto offsetId = _inner->lastSearchId();
if (const auto peer = _searchInChat.peer()) {
auto &histories = session().data().histories();
const auto type = Data::Histories::RequestType::History;
const auto history = session().data().history(peer);
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
const auto type = offsetId
? SearchRequestType::PeerFromOffset
: SearchRequestType::PeerFromStart;
auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = MTP::send(
MTPmessages_Search(
flags,
peer->input,
MTP_string(_searchQuery),
_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty(),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
MTP_int(offsetId),
MTP_int(0),
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)),
rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::PeerFromOffset : SearchRequestType::PeerFromStart),
rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::PeerFromOffset : SearchRequestType::PeerFromStart));
//} else if (const auto feed = _searchInChat.feed()) { // #feed
// _searchRequest = MTP::send(
// MTPchannels_SearchFeed(
// MTP_int(feed->id()),
// MTP_string(_searchQuery),
// MTP_int(offsetDate),
// offsetPeer
// ? offsetPeer->input
// : MTP_inputPeerEmpty(),
// MTP_int(offsetId),
// MTP_int(SearchPerPage)),
// rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart),
// rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart));
} else {
const auto flags = session().settings().skipArchiveInSearch()
? MTPmessages_SearchGlobal::Flag::f_folder_id
: MTPmessages_SearchGlobal::Flag(0);
const auto folderId = 0;
_searchRequest = MTP::send(
MTPmessages_SearchGlobal(
MTP_flags(flags),
MTP_int(folderId),
MTP_string(_searchQuery),
MTP_int(_searchNextRate),
offsetPeer
? offsetPeer->input
: MTP_inputPeerEmpty(),
MTP_int(offsetId),
MTP_int(SearchPerPage)),
rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart),
rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart));
}
if (!offsetId) {
_searchQueries.insert(_searchRequest, _searchQuery);
}
} else if (_searchInMigrated && !_searchFullMigrated) {
auto offsetMigratedId = _inner->lastSearchMigratedId();
auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = MTP::send(
MTPmessages_Search(
_searchRequest = session().api().request(MTPmessages_Search(
flags,
_searchInMigrated->peer->input,
peer->input,
MTP_string(_searchQuery),
_searchQueryFrom
(_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty(),
: MTP_inputUserEmpty()),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
MTP_int(offsetMigratedId),
MTP_int(offsetId),
MTP_int(0),
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)),
rpcDone(&Widget::searchReceived, offsetMigratedId ? SearchRequestType::MigratedFromOffset : SearchRequestType::MigratedFromStart),
rpcFail(&Widget::searchFailed, offsetMigratedId ? SearchRequestType::MigratedFromOffset : SearchRequestType::MigratedFromStart));
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).fail([=](const RPCError &error) {
searchFailed(type, error, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).send();
if (!offsetId) {
_searchQueries.insert(_searchRequest, _searchQuery);
}
return _searchRequest;
});
//} else if (const auto feed = _searchInChat.feed()) { // #feed
// _searchRequest = MTP::send(
// MTPchannels_SearchFeed(
// MTP_int(feed->id()),
// MTP_string(_searchQuery),
// MTP_int(offsetDate),
// offsetPeer
// ? offsetPeer->input
// : MTP_inputPeerEmpty(),
// MTP_int(offsetId),
// MTP_int(SearchPerPage)),
// rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart),
// rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart));
// if (!offsetId) {
// _searchQueries.insert(_searchRequest, _searchQuery);
// }
} else {
const auto flags = session().settings().skipArchiveInSearch()
? MTPmessages_SearchGlobal::Flag::f_folder_id
: MTPmessages_SearchGlobal::Flag(0);
const auto folderId = 0;
_searchRequest = MTP::send(
MTPmessages_SearchGlobal(
MTP_flags(flags),
MTP_int(folderId),
MTP_string(_searchQuery),
MTP_int(_searchNextRate),
offsetPeer
? offsetPeer->input
: MTP_inputPeerEmpty(),
MTP_int(offsetId),
MTP_int(SearchPerPage)),
rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart),
rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart));
if (!offsetId) {
_searchQueries.insert(_searchRequest, _searchQuery);
}
}
} else if (_searchInMigrated && !_searchFullMigrated) {
auto offsetMigratedId = _inner->lastSearchMigratedId();
auto &histories = session().data().histories();
const auto type = Data::Histories::RequestType::History;
const auto history = _searchInMigrated;
_searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn<void()> finish) {
const auto type = offsetMigratedId
? SearchRequestType::MigratedFromOffset
: SearchRequestType::MigratedFromStart;
const auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = session().api().request(MTPmessages_Search(
flags,
_searchInMigrated->peer->input,
MTP_string(_searchQuery),
(_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty()),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
MTP_int(offsetMigratedId),
MTP_int(0),
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
searchReceived(type, result, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).fail([=](const RPCError &error) {
searchFailed(type, error, _searchRequest);
_searchInHistoryRequest = 0;
finish();
}).send();
return _searchRequest;
});
}
}
@ -1015,94 +1069,95 @@ void Widget::searchReceived(
? *_singleMessageSearch.lookup(_searchQuery)
: nullptr;
if (_searchRequest == requestId) {
switch (result.type()) {
case mtpc_messages_messages: {
auto &d = result.c_messages_messages();
if (_searchRequest != 0) {
// Don't apply cached data!
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
}
auto &msgs = d.vmessages().v;
_inner->searchReceived(msgs, inject, type, msgs.size());
if (_searchRequest != requestId) {
return;
}
switch (result.type()) {
case mtpc_messages_messages: {
auto &d = result.c_messages_messages();
if (_searchRequest != 0) {
// Don't apply cached data!
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
}
auto &msgs = d.vmessages().v;
_inner->searchReceived(msgs, inject, type, msgs.size());
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} break;
case mtpc_messages_messagesSlice: {
auto &d = result.c_messages_messagesSlice();
if (_searchRequest != 0) {
// Don't apply cached data!
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
}
auto &msgs = d.vmessages().v;
const auto someAdded = _inner->searchReceived(msgs, inject, type, d.vcount().v);
const auto nextRate = d.vnext_rate();
const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate);
const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset)
? !rateUpdated
: !someAdded;
if (rateUpdated) {
_searchNextRate = nextRate->v;
}
if (finished) {
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} break;
}
} break;
case mtpc_messages_messagesSlice: {
auto &d = result.c_messages_messagesSlice();
if (_searchRequest != 0) {
// Don't apply cached data!
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
}
auto &msgs = d.vmessages().v;
const auto someAdded = _inner->searchReceived(msgs, inject, type, d.vcount().v);
const auto nextRate = d.vnext_rate();
const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate);
const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset)
? !rateUpdated
: !someAdded;
if (rateUpdated) {
_searchNextRate = nextRate->v;
}
if (finished) {
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
}
} break;
case mtpc_messages_channelMessages: {
auto &d = result.c_messages_channelMessages();
if (const auto peer = _searchInChat.peer()) {
if (const auto channel = peer->asChannel()) {
channel->ptsReceived(d.vpts().v);
} else {
LOG(("API Error: "
"received messages.channelMessages when no channel "
"was passed! (Widget::searchReceived)"));
}
case mtpc_messages_channelMessages: {
auto &d = result.c_messages_channelMessages();
if (const auto peer = _searchInChat.peer()) {
if (const auto channel = peer->asChannel()) {
channel->ptsReceived(d.vpts().v);
} else {
LOG(("API Error: "
"received messages.channelMessages when no channel "
"was passed! (Widget::searchReceived)"));
}
if (_searchRequest != 0) {
// Don't apply cached data!
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
}
auto &msgs = d.vmessages().v;
if (!_inner->searchReceived(msgs, inject, type, d.vcount().v)) {
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
}
} break;
case mtpc_messages_messagesNotModified: {
LOG(("API Error: received messages.messagesNotModified! (Widget::searchReceived)"));
} else {
LOG(("API Error: "
"received messages.channelMessages when no channel "
"was passed! (Widget::searchReceived)"));
}
if (_searchRequest != 0) {
// Don't apply cached data!
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
}
auto &msgs = d.vmessages().v;
if (!_inner->searchReceived(msgs, inject, type, d.vcount().v)) {
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} break;
}
} break;
_searchRequest = 0;
onListScroll();
update();
case mtpc_messages_messagesNotModified: {
LOG(("API Error: received messages.messagesNotModified! (Widget::searchReceived)"));
if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} break;
}
_searchRequest = 0;
onListScroll();
update();
}
void Widget::peerSearchReceived(
@ -1318,7 +1373,7 @@ void Widget::clearSearchCache() {
_searchQueries.clear();
_searchQuery = QString();
_searchQueryFrom = nullptr;
MTP::cancel(base::take(_searchRequest));
cancelSearchRequest();
}
void Widget::showJumpToDate() {
@ -1614,12 +1669,20 @@ void Widget::removeDialog(Key key) {
_inner->removeDialog(key);
}
bool Widget::onCancelSearch() {
bool clearing = !_filter->getLastText().isEmpty();
void Widget::cancelSearchRequest() {
if (_searchRequest) {
MTP::cancel(_searchRequest);
_searchRequest = 0;
}
if (_searchInHistoryRequest) {
session().data().histories().cancelRequest(_searchInHistoryRequest);
_searchInHistoryRequest = 0;
}
}
bool Widget::onCancelSearch() {
bool clearing = !_filter->getLastText().isEmpty();
cancelSearchRequest();
if (_searchInChat && !clearing) {
if (Adaptive::OneColumn()) {
if (const auto peer = _searchInChat.peer()) {
@ -1641,10 +1704,7 @@ bool Widget::onCancelSearch() {
}
void Widget::onCancelSearchInChat() {
if (_searchRequest) {
MTP::cancel(_searchRequest);
_searchRequest = 0;
}
cancelSearchRequest();
if (_searchInChat) {
if (Adaptive::OneColumn() && !App::main()->selectingPeer()) {
if (const auto peer = _searchInChat.peer()) {

View file

@ -136,6 +136,7 @@ private:
const MTPcontacts_Found &result,
mtpRequestId requestId);
void escape();
void cancelSearchRequest();
void setupSupportMode();
void setupConnectingWidget();
@ -219,6 +220,7 @@ private:
int32 _searchNextRate = 0;
bool _searchFull = false;
bool _searchFullMigrated = false;
int _searchInHistoryRequest = 0; // Not real mtpRequestId.
mtpRequestId _searchRequest = 0;
QMap<QString, MTPmessages_Messages> _searchCache;

View file

@ -23,7 +23,8 @@ extern "C" {
namespace FFmpeg {
namespace {
constexpr auto kAlignImageBy = 16;
// See https://github.com/telegramdesktop/tdesktop/issues/7225
constexpr auto kAlignImageBy = 64;
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
constexpr auto kMaxScaleByAspectRatio = 16;
constexpr auto kAvioBlockSize = 4096;

View file

@ -392,10 +392,9 @@ void GenerateItems(
auto addSimpleServiceMessage = [&](const QString &text, PhotoData *photo = nullptr) {
auto message = HistoryService::PreparedText { text };
message.links.push_back(fromLink);
addPart(history->owner().makeServiceMessage(
history,
MTPDmessage_ClientFlag::f_admin_log_entry,
addPart(history->makeServiceMessage(
history->nextNonHistoryEntryId(),
MTPDmessage_ClientFlag::f_admin_log_entry,
date,
message,
MTPDmessage::Flags(0),
@ -433,8 +432,7 @@ void GenerateItems(
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
auto newDescription = PrepareText(newValue, QString());
auto body = history->owner().makeMessage(
history,
auto body = history->makeMessage(
history->nextNonHistoryEntryId(),
bodyFlags,
bodyClientFlags,
@ -469,8 +467,7 @@ void GenerateItems(
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
auto newLink = newValue.isEmpty() ? TextWithEntities() : PrepareText(Core::App().createInternalLinkFull(newValue), QString());
auto body = history->owner().makeMessage(
history,
auto body = history->makeMessage(
history->nextNonHistoryEntryId(),
bodyFlags,
bodyClientFlags,
@ -609,8 +606,7 @@ void GenerateItems(
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
auto bodyText = GenerateParticipantChangeText(channel, action.vparticipant());
addPart(history->owner().makeMessage(
history,
addPart(history->makeMessage(
history->nextNonHistoryEntryId(),
bodyFlags,
bodyClientFlags,
@ -628,8 +624,7 @@ void GenerateItems(
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant());
addPart(history->owner().makeMessage(
history,
addPart(history->makeMessage(
history->nextNonHistoryEntryId(),
bodyFlags,
bodyClientFlags,
@ -653,8 +648,7 @@ void GenerateItems(
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant());
addPart(history->owner().makeMessage(
history,
addPart(history->makeMessage(
history->nextNonHistoryEntryId(),
bodyFlags,
bodyClientFlags,
@ -687,10 +681,9 @@ void GenerateItems(
auto message = HistoryService::PreparedText { text };
message.links.push_back(fromLink);
message.links.push_back(setLink);
addPart(history->owner().makeServiceMessage(
history,
MTPDmessage_ClientFlag::f_admin_log_entry,
addPart(history->makeServiceMessage(
history->nextNonHistoryEntryId(),
MTPDmessage_ClientFlag::f_admin_log_entry,
date,
message,
MTPDmessage::Flags(0),
@ -712,8 +705,7 @@ void GenerateItems(
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
auto bodyText = GenerateDefaultBannedRightsChangeText(channel, action.vnew_banned_rights(), action.vprev_banned_rights());
addPart(history->owner().makeMessage(
history,
addPart(history->makeMessage(
history->nextNonHistoryEntryId(),
bodyFlags,
bodyClientFlags,
@ -763,10 +755,9 @@ void GenerateItems(
auto message = HistoryService::PreparedText{ text };
message.links.push_back(fromLink);
message.links.push_back(chatLink);
addPart(history->owner().makeServiceMessage(
history,
MTPDmessage_ClientFlag::f_admin_log_entry,
addPart(history->makeServiceMessage(
history->nextNonHistoryEntryId(),
MTPDmessage_ClientFlag::f_admin_log_entry,
date,
message,
MTPDmessage::Flags(0),

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_histories.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
#include "mainwidget.h"
@ -135,6 +136,9 @@ void History::itemRemoved(not_null<HistoryItem*> item) {
_joinedMessage = nullptr;
}
item->removeMainView();
if (_lastServerMessage == item) {
_lastServerMessage = std::nullopt;
}
if (lastMessage() == item) {
_lastMessage = std::nullopt;
if (loadedAtBottom()) {
@ -633,11 +637,55 @@ HistoryItem *History::addNewMessage(
return addNewItem(item, unread);
}
not_null<HistoryItem*> History::insertItem(
std::unique_ptr<HistoryItem> item) {
Expects(item != nullptr);
const auto [i, ok] = _messages.insert(std::move(item));
const auto result = i->get();
owner().registerMessage(result);
Ensures(ok);
return result;
}
void History::destroyMessage(not_null<HistoryItem*> item) {
Expects(item->isHistoryEntry() || !item->mainView());
const auto peerId = peer->id;
if (item->isHistoryEntry()) {
// All this must be done for all items manually in History::clear()!
item->eraseFromUnreadMentions();
if (IsServerMsgId(item->id)) {
if (const auto types = item->sharedMediaTypes()) {
session().storage().remove(Storage::SharedMediaRemoveOne(
peerId,
types,
item->id));
}
} else {
session().api().cancelLocalItem(item);
}
itemRemoved(item);
}
owner().unregisterMessage(item);
session().notifications().clearFromItem(item);
auto hack = std::unique_ptr<HistoryItem>(item.get());
const auto i = _messages.find(hack);
hack.release();
Assert(i != end(_messages));
_messages.erase(i);
}
not_null<HistoryItem*> History::addNewItem(
not_null<HistoryItem*> item,
bool unread) {
if (item->isScheduled()) {
session().data().scheduledMessages().appendSending(item);
owner().scheduledMessages().appendSending(item);
return item;
} else if (!item->isHistoryEntry()) {
return item;
@ -689,8 +737,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
const QString &postAuthor,
not_null<HistoryMessage*> forwardOriginal) {
return addNewItem(
owner().makeMessage(
this,
makeMessage(
id,
flags,
clientFlags,
@ -714,8 +761,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
const TextWithEntities &caption,
const MTPReplyMarkup &markup) {
return addNewItem(
owner().makeMessage(
this,
makeMessage(
id,
flags,
clientFlags,
@ -743,8 +789,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
const TextWithEntities &caption,
const MTPReplyMarkup &markup) {
return addNewItem(
owner().makeMessage(
this,
makeMessage(
id,
flags,
clientFlags,
@ -771,8 +816,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
not_null<GameData*> game,
const MTPReplyMarkup &markup) {
return addNewItem(
owner().makeMessage(
this,
makeMessage(
id,
flags,
clientFlags,
@ -1241,7 +1285,7 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (unreadCountKnown()) {
setUnreadCount(unreadCount() + 1);
} else {
session().api().requestDialogEntry(this);
owner().histories().requestDialogEntry(this);
}
}
}
@ -1254,7 +1298,7 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
outboxRead(item);
}
if (!folderKnown()) {
session().api().requestDialogEntry(this);
owner().histories().requestDialogEntry(this);
}
}
@ -1323,8 +1367,8 @@ HistoryBlock *History::prepareBlockForAddingItem() {
}
void History::viewReplaced(not_null<const Element*> was, Element *now) {
if (scrollTopItem == was) scrollTopItem= now;
if (_firstUnreadView == was) _firstUnreadView= now;
if (scrollTopItem == was) scrollTopItem = now;
if (_firstUnreadView == was) _firstUnreadView = now;
if (_unreadBarView == was) _unreadBarView = now;
}
@ -1545,44 +1589,23 @@ void History::addToSharedMedia(
}
}
std::optional<int> History::countUnread(MsgId upTo) const {
if (!folderKnown() || !loadedAtBottom()) {
return std::nullopt;
}
auto result = 0;
for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) {
--i;
const auto &messages = (*i)->messages;
for (auto j = messages.cend(), en = messages.cbegin(); j != en;) {
--j;
const auto item = (*j)->data();
if (item->id > 0 && item->id <= upTo) {
return result;
} else if (!item->out() && item->unread()) {
++result;
}
}
}
return std::nullopt;
}
void History::calculateFirstUnreadMessage() {
if (_firstUnreadView || !_inboxReadBefore) {
if (!_inboxReadBefore) {
return;
}
for (auto i = blocks.cend(); i != blocks.cbegin();) {
--i;
const auto &messages = (*i)->messages;
for (auto j = messages.cend(); j != messages.cbegin();) {
--j;
const auto view = j->get();
const auto item = view->data();
_firstUnreadView = nullptr;
if (!unreadCount() || !trackUnreadMessages()) {
return;
}
for (const auto &block : ranges::view::reverse(blocks)) {
for (const auto &message : ranges::view::reverse(block->messages)) {
const auto item = message->data();
if (!IsServerMsgId(item->id)) {
continue;
} else if (!item->out() || !_firstUnreadView) {
} else if (!item->out()) {
if (item->id >= *_inboxReadBefore) {
_firstUnreadView = view;
_firstUnreadView = message.get();
} else {
return;
}
@ -1591,27 +1614,73 @@ void History::calculateFirstUnreadMessage() {
}
}
bool History::readInboxTillNeedsRequest(MsgId tillId) {
Expects(!tillId || IsServerMsgId(tillId));
readClientSideMessages();
if (unreadMark()) {
owner().histories().changeDialogUnreadMark(this, false);
}
return IsServerMsgId(tillId) && (_inboxReadBefore.value_or(1) <= tillId);
}
void History::readClientSideMessages() {
for (const auto &block : blocks) {
for (const auto &view : block->messages) {
const auto item = view->data();
if (!item->out()) {
item->markClientSideAsRead();
}
}
auto &histories = owner().histories();
for (const auto item : _localMessages) {
histories.readClientSideMessage(item);
}
}
MsgId History::readInbox() {
const auto upTo = msgIdForRead();
if (unreadCountKnown()) {
setUnreadCount(0);
bool History::unreadCountRefreshNeeded(MsgId readTillId) const {
return !unreadCountKnown()
|| ((readTillId + 1) > _inboxReadBefore.value_or(0));
}
std::optional<int> History::countStillUnreadLocal(MsgId readTillId) const {
if (isEmpty() || !folderKnown()) {
return std::nullopt;
}
readClientSideMessages();
if (upTo) {
inboxRead(upTo);
if (_inboxReadBefore) {
const auto before = *_inboxReadBefore;
if (minMsgId() <= before && maxMsgId() >= readTillId) {
auto result = 0;
for (const auto &block : blocks) {
for (const auto &message : block->messages) {
const auto item = message->data();
if (item->out() || !IsServerMsgId(item->id)) {
continue;
} else if (item->id > readTillId) {
break;
} else if (item->id >= before) {
++result;
}
}
}
if (_unreadCount) {
return std::max(*_unreadCount - result, 0);
}
}
}
return upTo;
const auto minimalServerId = minMsgId();
if (!loadedAtBottom()
|| (!loadedAtTop() && !minimalServerId)
|| minimalServerId > readTillId) {
return std::nullopt;
}
auto result = 0;
for (const auto &block : ranges::view::reverse(blocks)) {
for (const auto &message : ranges::view::reverse(block->messages)) {
const auto item = message->data();
if (IsServerMsgId(item->id)) {
if (item->id <= readTillId) {
return result;
} else if (!item->out()) {
++result;
}
}
}
}
return result;
}
void History::applyInboxReadUpdate(
@ -1622,13 +1691,15 @@ void History::applyInboxReadUpdate(
const auto folder = folderId ? owner().folderLoaded(folderId) : nullptr;
if (folder && this->folder() != folder) {
// If history folder is unknown or not synced, request both.
session().api().requestDialogEntry(this);
session().api().requestDialogEntry(folder);
owner().histories().requestDialogEntry(this);
owner().histories().requestDialogEntry(folder);
}
if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) {
inboxRead(upTo, stillUnread);
} else {
inboxRead(upTo);
if (_inboxReadBefore.value_or(1) <= upTo) {
if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) {
inboxRead(upTo, stillUnread);
} else {
inboxRead(upTo);
}
}
}
@ -1638,16 +1709,16 @@ void History::inboxRead(MsgId upTo, std::optional<int> stillUnread) {
}
if (stillUnread.has_value() && folderKnown()) {
setUnreadCount(*stillUnread);
} else if (const auto still = countUnread(upTo)) {
} else if (const auto still = countStillUnreadLocal(upTo)) {
setUnreadCount(*still);
} else {
session().api().requestDialogEntry(this);
owner().histories().requestDialogEntry(this);
}
setInboxReadTill(upTo);
updateChatListEntry();
if (peer->migrateTo()) {
if (auto migrateTo = peer->owner().historyLoaded(peer->migrateTo()->id)) {
migrateTo->updateChatListEntry();
if (const auto to = peer->migrateTo()) {
if (const auto migrated = peer->owner().historyLoaded(to->id)) {
migrated->updateChatListEntry();
}
}
@ -1727,18 +1798,8 @@ void History::setUnreadCount(int newUnreadCount) {
if (const auto last = msgIdForRead()) {
setInboxReadTill(last);
}
} else {
if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) {
calculateFirstUnreadMessage();
}
}
if (_unreadBarView) {
const auto count = chatListUnreadCount();
if (count > 0) {
_unreadBarView->setUnreadBarCount(count);
} else {
_unreadBarView->setUnreadBarFreezed();
}
} else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) {
calculateFirstUnreadMessage();
}
Notify::peerUpdatedDelayed(
peer,
@ -1841,8 +1902,8 @@ void History::setFolder(
not_null<Data::Folder*> folder,
HistoryItem *folderDialogItem) {
setFolderPointer(folder);
if (folderDialogItem && _lastMessage != folderDialogItem) {
setLastMessage(folderDialogItem);
if (folderDialogItem) {
setLastServerMessage(folderDialogItem);
}
}
@ -1993,7 +2054,7 @@ void History::addUnreadBar() {
}
if (const auto count = chatListUnreadCount()) {
_unreadBarView = _firstUnreadView;
_unreadBarView->setUnreadBarCount(count);
_unreadBarView->createUnreadBar();
}
}
@ -2003,17 +2064,6 @@ void History::destroyUnreadBar() {
}
}
bool History::hasNotFreezedUnreadBar() const {
if (_firstUnreadView) {
if (const auto view = _unreadBarView) {
if (const auto bar = view->Get<HistoryView::UnreadBar>()) {
return !bar->freezed;
}
}
}
return false;
}
void History::unsetFirstUnreadMessage() {
_firstUnreadView = nullptr;
}
@ -2273,18 +2323,25 @@ void History::clearSharedMedia() {
//}
}
void History::setLastServerMessage(HistoryItem *item) {
_lastServerMessage = item;
if (_lastMessage
&& *_lastMessage
&& !IsServerMsgId((*_lastMessage)->id)
&& (!item || (*_lastMessage)->date() > item->date())) {
return;
}
setLastMessage(item);
}
void History::setLastMessage(HistoryItem *item) {
if (_lastMessage) {
if (*_lastMessage == item) {
return;
} else if (*_lastMessage
&& item
&& !IsServerMsgId((*_lastMessage)->id)
&& (*_lastMessage)->date() > item->date()) {
return;
}
if (_lastMessage && *_lastMessage == item) {
return;
}
_lastMessage = item;
if (!item || IsServerMsgId(item->id)) {
_lastServerMessage = item;
}
if (peer->migrateTo()) {
// We don't want to request last message for all deactivated chats.
// This is a heavy request for them, because we need to get last
@ -2406,7 +2463,7 @@ void History::setChatListMessageUnknown() {
void History::requestChatListMessage() {
if (!lastMessageKnown()) {
session().api().requestDialogEntry(this, [=] {
owner().histories().requestDialogEntry(this, [=] {
requestChatListMessage();
});
return;
@ -2423,7 +2480,7 @@ void History::setFakeChatListMessage() {
if (const auto chat = peer->asChat()) {
// In chats we try to take the item before the 'last', which
// is the empty-displayed migration message.
session().api().requestFakeChatListMessage(this);
owner().histories().requestFakeChatListMessage(this);
} else if (const auto from = migrateFrom()) {
// In megagroups we just try to use
// the message from the original group.
@ -2484,6 +2541,14 @@ bool History::lastMessageKnown() const {
return _lastMessage.has_value();
}
HistoryItem *History::lastServerMessage() const {
return _lastServerMessage.value_or(nullptr);
}
bool History::lastServerMessageKnown() const {
return _lastServerMessage.has_value();
}
void History::updateChatListExistence() {
Entry::updateChatListExistence();
//if (const auto channel = peer->asChannel()) { // #feed
@ -2491,7 +2556,7 @@ void History::updateChatListExistence() {
// // After ungrouping from a feed we need to load dialog.
// requestChatListMessage();
// if (!unreadCountKnown()) {
// session().api().requestDialogEntry(this);
// owner().histories().requestDialogEntry(this);
// }
// }
//}
@ -2510,6 +2575,13 @@ int History::fixedOnTopIndex() const {
return useProxyPromotion() ? kProxyPromotionFixOnTopIndex : 0;
}
bool History::trackUnreadMessages() const {
if (const auto channel = peer->asChannel()) {
return channel->amIn();
}
return true;
}
bool History::shouldBeInChatList() const {
if (peer->migrateTo() || !folderKnown()) {
return false;
@ -2536,7 +2608,7 @@ bool History::toImportant() const {
void History::unknownMessageDeleted(MsgId messageId) {
if (_inboxReadBefore && messageId >= *_inboxReadBefore) {
session().api().requestDialogEntry(this);
owner().histories().requestDialogEntry(this);
}
}
@ -2588,11 +2660,13 @@ void History::applyDialog(
if (draft && draft->type() == mtpc_draftMessage) {
Data::applyPeerCloudDraft(peer->id, draft->c_draftMessage());
}
session().api().dialogEntryApplied(this);
owner().histories().dialogEntryApplied(this);
}
void History::dialogEntryApplied() {
if (!lastMessageKnown()) {
if (!lastServerMessageKnown()) {
setLastServerMessage(nullptr);
} else if (!lastMessageKnown()) {
setLastMessage(nullptr);
}
if (peer->migrateTo()) {
@ -2656,7 +2730,8 @@ void History::applyDialogFields(
} else {
clearFolder();
}
if (!skipUnreadUpdate()) {
if (!skipUnreadUpdate()
&& maxInboxRead + 1 >= _inboxReadBefore.value_or(1)) {
setUnreadCount(unreadCount);
setInboxReadTill(maxInboxRead);
}
@ -2669,12 +2744,12 @@ void History::applyDialogTopMessage(MsgId topMessageId) {
channelId(),
topMessageId);
if (const auto item = owner().message(itemId)) {
setLastMessage(item);
setLastServerMessage(item);
} else {
setLastMessage(nullptr);
setLastServerMessage(nullptr);
}
} else {
setLastMessage(nullptr);
setLastServerMessage(nullptr);
}
if (clearUnreadOnClientSide()) {
setUnreadCount(0);
@ -3048,10 +3123,10 @@ void History::clear(ClearType type) {
removeJoinedMessage();
forgetScrollState();
blocks.clear();
owner().notifyHistoryUnloaded(this);
lastKeyboardInited = false;
if (type == ClearType::Unload) {
blocks.clear();
owner().notifyHistoryUnloaded(this);
lastKeyboardInited = false;
_loadedAtTop = _loadedAtBottom = false;
} else {
// Leave the 'sending' messages in local messages.
@ -3070,7 +3145,7 @@ void History::clear(ClearType type) {
setUnreadCount(0);
}
if (type == ClearType::DeleteChat) {
setLastMessage(nullptr);
setLastServerMessage(nullptr);
} else if (_lastMessage && *_lastMessage) {
if (IsServerMsgId((*_lastMessage)->id)) {
(*_lastMessage)->applyEditionToHistoryCleared();
@ -3096,7 +3171,6 @@ void History::clear(ClearType type) {
//}
}
}
owner().notifyHistoryChangeDelayed(this);
if (const auto chat = peer->asChat()) {
chat->lastAuthors.clear();
@ -3104,27 +3178,28 @@ void History::clear(ClearType type) {
} else if (const auto channel = peer->asMegagroup()) {
channel->mgInfo->markupSenders.clear();
}
owner().notifyHistoryChangeDelayed(this);
owner().sendHistoryChangeNotifications();
}
void History::clearUpTill(MsgId availableMinId) {
auto minId = minMsgId();
if (!minId || minId > availableMinId) {
return;
}
do {
const auto item = blocks.front()->messages.front()->data();
auto remove = std::vector<not_null<HistoryItem*>>();
remove.reserve(_messages.size());
for (const auto &item : _messages) {
const auto itemId = item->id;
if (IsServerMsgId(itemId) && itemId >= availableMinId) {
if (itemId == availableMinId) {
item->applyEditionToHistoryCleared();
}
break;
if (!IsServerMsgId(itemId)) {
continue;
} else if (itemId == availableMinId) {
item->applyEditionToHistoryCleared();
} else if (itemId < availableMinId) {
remove.push_back(item.get());
}
}
for (const auto item : remove) {
item->destroy();
} while (!isEmpty());
}
requestChatListMessage();
owner().sendHistoryChangeNotifications();
}
void History::applyGroupAdminChanges(const base::flat_set<UserId> &changes) {

View file

@ -90,6 +90,25 @@ public:
void applyGroupAdminChanges(const base::flat_set<UserId> &changes);
template <typename ...Args>
not_null<HistoryMessage*> makeMessage(Args &&...args) {
return static_cast<HistoryMessage*>(
insertItem(
std::make_unique<HistoryMessage>(
this,
std::forward<Args>(args)...)).get());
}
template <typename ...Args>
not_null<HistoryService*> makeServiceMessage(Args &&...args) {
return static_cast<HistoryService*>(
insertItem(
std::make_unique<HistoryService>(
this,
std::forward<Args>(args)...)).get());
}
void destroyMessage(not_null<HistoryItem*> item);
HistoryItem *addNewMessage(
const MTPMessage &msg,
MTPDmessage_ClientFlags clientFlags,
@ -158,7 +177,7 @@ public:
void unregisterLocalMessage(not_null<HistoryItem*> item);
[[nodiscard]] HistoryItem *latestSendingMessage() const;
MsgId readInbox();
[[nodiscard]] bool readInboxTillNeedsRequest(MsgId tillId);
void applyInboxReadUpdate(
FolderId folderId,
MsgId upTo,
@ -172,8 +191,13 @@ public:
not_null<const HistoryItem*> item) const;
[[nodiscard]] MsgId loadAroundId() const;
[[nodiscard]] bool trackUnreadMessages() const;
[[nodiscard]] int unreadCount() const;
[[nodiscard]] bool unreadCountKnown() const;
// Some old unread count is known, but we read history till some place.
[[nodiscard]] bool unreadCountRefreshNeeded(MsgId readTillId) const;
void setUnreadCount(int newUnreadCount);
void setUnreadMark(bool unread);
[[nodiscard]] bool unreadMark() const;
@ -182,7 +206,6 @@ public:
bool changeMute(bool newMute);
void addUnreadBar();
void destroyUnreadBar();
[[nodiscard]] bool hasNotFreezedUnreadBar() const;
[[nodiscard]] Element *unreadBar() const;
void calculateFirstUnreadMessage();
void unsetFirstUnreadMessage();
@ -197,7 +220,9 @@ public:
void getReadyFor(MsgId msgId);
[[nodiscard]] HistoryItem *lastMessage() const;
[[nodiscard]] HistoryItem *lastServerMessage() const;
[[nodiscard]] bool lastMessageKnown() const;
[[nodiscard]] bool lastServerMessageKnown() const;
void unknownMessageDeleted(MsgId messageId);
void applyDialogTopMessage(MsgId topMessageId);
void applyDialog(Data::Folder *requestFolder, const MTPDdialog &data);
@ -344,6 +369,10 @@ public:
HistoryItem *folderDialogItem = nullptr);
void clearFolder();
// Interface for Data::Histories.
void setInboxReadTill(MsgId upTo);
std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
// Still public data.
std::deque<std::unique_ptr<HistoryBlock>> blocks;
@ -395,6 +424,7 @@ private:
void removeBlock(not_null<HistoryBlock*> block);
void clearSharedMedia();
not_null<HistoryItem*> insertItem(std::unique_ptr<HistoryItem> item);
not_null<HistoryItem*> addNewItem(
not_null<HistoryItem*> item,
bool unread);
@ -432,7 +462,6 @@ private:
TimeId adjustedChatListTimeId() const override;
void changedChatListPinHook() override;
void setInboxReadTill(MsgId upTo);
void setOutboxReadTill(MsgId upTo);
void readClientSideMessages();
@ -446,6 +475,7 @@ private:
// After adding a new history slice check lastMessage / loadedAtBottom.
void checkLastMessage();
void setLastMessage(HistoryItem *item);
void setLastServerMessage(HistoryItem *item);
void refreshChatListMessage();
void setChatListMessage(HistoryItem *item);
@ -468,7 +498,6 @@ private:
HistoryItem *lastAvailableMessage() const;
void getNextFirstUnreadMessage();
bool nonEmptyCountMoreThan(int count) const;
std::optional<int> countUnread(MsgId upTo) const;
// Creates if necessary a new block for adding item.
// Depending on isBuildingFrontBlock() gets front or back block.
@ -501,7 +530,9 @@ private:
std::optional<int> _unreadMentionsCount;
base::flat_set<MsgId> _unreadMentions;
std::optional<HistoryItem*> _lastMessage;
std::optional<HistoryItem*> _lastServerMessage;
base::flat_set<not_null<HistoryItem*>> _localMessages;
std::unordered_set<std::unique_ptr<HistoryItem>> _messages;
// This almost always is equal to _lastMessage. The only difference is
// for a group that migrated to a supergroup. Then _lastMessage can

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "window/window_controller.h"
#include "window/notifications_manager.h"
#include "boxes/confirm_box.h"
#include "boxes/report_box.h"
#include "boxes/sticker_set_box.h"
@ -53,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "facades.h"
#include "app.h"
@ -655,13 +657,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto iItem = (_curHistory == _history ? _curItem : 0);
auto view = block->messages[iItem].get();
auto item = view->data();
auto readTill = (HistoryItem*)nullptr;
auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto y = htop + block->y() + view->y();
p.save();
p.translate(0, y);
while (y < drawToY) {
auto h = view->height();
const auto h = view->height();
if (hclip.y() < y + h && hdrawtop < y + h) {
const auto selection = itemRenderSelection(
view,
@ -669,12 +671,23 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
seltoy - htop);
view->draw(p, hclip.translated(0, -y), selection, ms);
if (item->hasViews()) {
App::main()->scheduleViewIncrement(item);
const auto middle = y + h / 2;
const auto bottom = y + h;
if (_visibleAreaBottom >= bottom) {
const auto item = view->data();
if (!item->out() && item->unread()) {
readTill = item;
}
}
if (item->isUnreadMention() && !item->isUnreadMedia()) {
readMentions.insert(item);
_widget->enqueueMessageHighlight(view);
if (_visibleAreaBottom >= middle
&& _visibleAreaTop <= middle) {
if (item->hasViews()) {
App::main()->scheduleViewIncrement(item);
}
if (item->isUnreadMention() && !item->isUnreadMedia()) {
readMentions.insert(item);
_widget->enqueueMessageHighlight(view);
}
}
}
p.translate(0, h);
@ -693,9 +706,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
item = view->data();
}
p.restore();
if (readTill && _widget->doWeReadServerHistory()) {
session().data().histories().readInboxTill(readTill);
}
}
if (!readMentions.empty() && App::wnd()->doWeReadMentions()) {
if (!readMentions.empty() && _widget->doWeReadMentions()) {
session().api().markMediaRead(readMentions);
}
@ -2013,6 +2030,42 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
}
}
void HistoryInner::checkHistoryActivation() {
if (!_widget->doWeReadServerHistory()) {
return;
}
adjustCurrent(_visibleAreaBottom);
if (_history->loadedAtBottom() && _visibleAreaBottom >= height()) {
// Clear possible scheduled messages notifications.
session().notifications().clearFromHistory(_history);
}
if (_curHistory != _history || _history->isEmpty()) {
return;
}
auto block = _history->blocks[_curBlock].get();
auto view = block->messages[_curItem].get();
while (_curBlock > 0 || _curItem > 0) {
const auto top = itemTop(view);
const auto bottom = itemTop(view) + view->height();
if (_visibleAreaBottom >= bottom) {
break;
}
if (_curItem > 0) {
view = block->messages[--_curItem].get();
} else {
while (_curBlock > 0) {
block = _history->blocks[--_curBlock].get();
_curItem = block->messages.size();
if (_curItem > 0) {
view = block->messages[--_curItem].get();
break;
}
}
}
}
session().data().histories().readInboxTill(view->data());
}
void HistoryInner::recountHistoryGeometry() {
_contentWidth = _scroll->width();
@ -2178,6 +2231,7 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
const auto from = _visibleAreaTop - pages * visibleAreaHeight;
const auto till = _visibleAreaBottom + pages * visibleAreaHeight;
session().data().unloadHeavyViewParts(ElementDelegate(), from, till);
checkHistoryActivation();
}
bool HistoryInner::displayScrollDate() const {
@ -2326,7 +2380,8 @@ void HistoryInner::adjustCurrent(int32 y) const {
}
void HistoryInner::adjustCurrent(int32 y, History *history) const {
Assert(!history->isEmpty());
Expects(!history->isEmpty());
_curHistory = history;
if (_curBlock >= history->blocks.size()) {
_curBlock = history->blocks.size() - 1;

View file

@ -62,6 +62,7 @@ public:
void touchScrollUpdated(const QPoint &screenPos);
void checkHistoryActivation();
void recountHistoryGeometry();
void updateSize();

View file

@ -75,8 +75,7 @@ not_null<HistoryItem*> CreateUnsupportedMessage(
EntityInText(EntityType::Italic, 0, text.text.size()));
flags &= ~MTPDmessage::Flag::f_post_author;
flags |= MTPDmessage::Flag::f_legacy;
return history->owner().makeMessage(
history,
return history->makeMessage(
msgId,
flags,
clientFlags,
@ -413,7 +412,7 @@ bool HistoryItem::isScheduled() const {
}
void HistoryItem::destroy() {
_history->owner().destroyMessage(this);
_history->destroyMessage(this);
}
void HistoryItem::refreshMainView() {
@ -929,37 +928,29 @@ not_null<HistoryItem*> HistoryItem::Create(
const auto text = HistoryService::PreparedText {
tr::lng_message_empty(tr::now)
};
return history->owner().makeServiceMessage(
history,
clientFlags,
return history->makeServiceMessage(
data.vid().v,
clientFlags,
data.vdate().v,
text,
data.vflags().v,
data.vfrom_id().value_or_empty());
} else if (checked == MediaCheckResult::HasTimeToLive) {
return history->owner().makeServiceMessage(
history,
data,
clientFlags);
return history->makeServiceMessage(data, clientFlags);
}
return history->owner().makeMessage(history, data, clientFlags);
return history->makeMessage(data, clientFlags);
}, [&](const MTPDmessageService &data) -> HistoryItem* {
if (data.vaction().type() == mtpc_messageActionPhoneCall) {
return history->owner().makeMessage(history, data, clientFlags);
return history->makeMessage(data, clientFlags);
}
return history->owner().makeServiceMessage(
history,
data,
clientFlags);
return history->makeServiceMessage(data, clientFlags);
}, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
const auto text = HistoryService::PreparedText{
tr::lng_message_empty(tr::now)
};
return history->owner().makeServiceMessage(
history,
clientFlags,
return history->makeServiceMessage(
data.vid().v,
clientFlags,
TimeId(0),
text);
});

View file

@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_histories.h"
#include "facades.h"
#include "app.h"
#include "styles/style_dialogs.h"
@ -265,15 +266,6 @@ void FastShareMessage(not_null<HistoryItem*> item) {
return;
}
auto doneCallback = [=](const MTPUpdates &updates, mtpRequestId requestId) {
history->session().api().applyUpdates(updates);
data->requests.remove(requestId);
if (data->requests.empty()) {
Ui::Toast::Show(tr::lng_share_done(tr::now));
Ui::hideLayer();
}
};
const auto sendFlags = MTPmessages_ForwardMessages::Flag(0)
| MTPmessages_ForwardMessages::Flag::f_with_my_score
| (isGroup
@ -297,28 +289,40 @@ void FastShareMessage(not_null<HistoryItem*> item) {
}
return result;
};
auto &api = owner->session().api();
auto &histories = owner->histories();
const auto requestType = Data::Histories::RequestType::Send;
for (const auto peer : result) {
const auto history = peer->owner().history(peer);
const auto history = owner->history(peer);
if (!comment.text.isEmpty()) {
auto message = ApiWrap::MessageToSend(history);
message.textWithTags = comment;
message.action.options = options;
message.action.clearDraft = false;
history->session().api().sendMessage(std::move(message));
api.sendMessage(std::move(message));
}
history->sendRequestId = MTP::send(
MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
data->peer->input,
MTP_vector<MTPint>(msgIds),
MTP_vector<MTPlong>(generateRandom()),
peer->input,
MTP_int(options.scheduled)),
rpcDone(base::duplicate(doneCallback)),
nullptr,
0,
0,
history->sendRequestId);
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
auto &api = history->session().api();
history->sendRequestId = api.request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
data->peer->input,
MTP_vector<MTPint>(msgIds),
MTP_vector<MTPlong>(generateRandom()),
peer->input,
MTP_int(options.scheduled)
)).done([=](const MTPUpdates &updates, mtpRequestId requestId) {
history->session().api().applyUpdates(updates);
data->requests.remove(requestId);
if (data->requests.empty()) {
Ui::Toast::Show(tr::lng_share_done(tr::now));
Ui::hideLayer();
}
finish();
}).fail([=](const RPCError &error) {
finish();
}).afterRequest(history->sendRequestId).send();
return history->sendRequestId;
});
data->requests.insert(history->sendRequestId);
}
};

View file

@ -525,8 +525,8 @@ HistoryService::HistoryService(
HistoryService::HistoryService(
not_null<History*> history,
MTPDmessage_ClientFlags clientFlags,
MsgId id,
MTPDmessage_ClientFlags clientFlags,
TimeId date,
const PreparedText &message,
MTPDmessage::Flags flags,
@ -797,10 +797,9 @@ not_null<HistoryService*> GenerateJoinedMessage(
TimeId inviteDate,
not_null<UserData*> inviter,
MTPDmessage::Flags flags) {
return new HistoryService(
history,
MTPDmessage_ClientFlag::f_local_history_entry,
return history->makeServiceMessage(
history->owner().nextLocalMessageId(),
MTPDmessage_ClientFlag::f_local_history_entry,
inviteDate,
GenerateJoinedText(history, inviter),
flags);

View file

@ -68,8 +68,8 @@ public:
MTPDmessage_ClientFlags clientFlags);
HistoryService(
not_null<History*> history,
MTPDmessage_ClientFlags clientFlags,
MsgId id,
MTPDmessage_ClientFlags clientFlags,
TimeId date,
const PreparedText &message,
MTPDmessage::Flags flags = 0,

File diff suppressed because it is too large Load diff

View file

@ -105,13 +105,13 @@ public:
void start();
void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId);
void historyLoaded();
void windowShown();
bool doWeReadServerHistory() const;
bool doWeReadMentions() const;
[[nodiscard]] bool doWeReadServerHistory() const;
[[nodiscard]] bool doWeReadMentions() const;
bool skipItemRepaint();
void checkHistoryActivation();
void leaveToChildEvent(QEvent *e, QWidget *child) override;
void dragEnterEvent(QDragEnterEvent *e) override;
@ -216,8 +216,9 @@ public:
void applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
void clearDelayedShowAt();
void clearAllLoadRequests();
void clearDelayedShowAtRequest();
void clearDelayedShowAt();
void saveFieldToHistoryLocalDraft();
void applyCloudDraft(History *history);
@ -560,6 +561,10 @@ private:
// destroys _history and _migrated unread bars
void destroyUnreadBar();
void destroyUnreadBarOnClose();
void createUnreadBarIfBelowVisibleArea(int withScrollTop);
[[nodiscard]] bool insideJumpToEndInsteadOfToUnread() const;
void createUnreadBarAndResize();
void saveEditMsg();
void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req);
@ -568,7 +573,8 @@ private:
void checkPreview();
void requestPreview();
void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req);
bool messagesFailed(const RPCError &error, mtpRequestId requestId);
void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, int requestId);
bool messagesFailed(const RPCError &error, int requestId);
void addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages);
void addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages);
@ -586,15 +592,12 @@ private:
std::optional<int> unreadBarTop() const;
int itemTopForHighlight(not_null<HistoryView::Element*> view) const;
void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId);
HistoryView::Element *firstUnreadMessage() const;
// Scroll to current y without updating the _lastUserScrolled time.
// Used to distinguish between user scrolls and syntetic scrolls.
// This one is syntetic.
void synteticScrollToY(int y);
void countHistoryShowFrom();
void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft);
void writeDrafts(History *history);
void setFieldText(
@ -683,12 +686,12 @@ private:
bool _canSendMessages = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId;
mtpRequestId _firstLoadRequest = 0;
mtpRequestId _preloadRequest = 0;
mtpRequestId _preloadDownRequest = 0;
int _firstLoadRequest = 0; // Not real mtpRequestId.
int _preloadRequest = 0; // Not real mtpRequestId.
int _preloadDownRequest = 0; // Not real mtpRequestId.
MsgId _delayedShowAtMsgId = -1;
mtpRequestId _delayedShowAtRequest = 0;
int _delayedShowAtRequest = 0; // Not real mtpRequestId.
object_ptr<HistoryView::TopBarWidget> _topBar;
object_ptr<Ui::ScrollArea> _scroll;

View file

@ -201,6 +201,7 @@ void ComposeControls::init() {
void ComposeControls::initField() {
_field->setMaxHeight(st::historyComposeFieldMaxHeight);
_field->setSubmitSettings(_window->session().settings().sendSubmitWay());
//Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); });
Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); });
//Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });

View file

@ -127,14 +127,8 @@ TextSelection ShiftItemSelection(
return ShiftItemSelection(selection, byText.length());
}
void UnreadBar::init(int newCount) {
if (freezed) {
return;
}
count = newCount;
text = /*(count == kCountUnknown) // #feed
? tr::lng_unread_bar_some(tr::now)
: */tr::lng_unread_bar(tr::now, lt_count, count);
void UnreadBar::init() {
text = tr::lng_unread_bar_some(tr::now);
width = st::semiboldFont->width(text);
}
@ -420,6 +414,18 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
return false;
}
void Element::createUnreadBar() {
if (!AddComponents(UnreadBar::Bit())) {
return;
}
const auto bar = Get<UnreadBar>();
bar->init();
if (data()->mainView() == this) {
recountAttachToPreviousInBlocks();
}
history()->owner().requestViewResize(this);
}
void Element::destroyUnreadBar() {
if (!Has<UnreadBar>()) {
return;
@ -431,29 +437,6 @@ void Element::destroyUnreadBar() {
}
}
void Element::setUnreadBarCount(int count) {
const auto changed = AddComponents(UnreadBar::Bit());
const auto bar = Get<UnreadBar>();
if (bar->freezed) {
return;
}
bar->init(count);
if (changed) {
if (data()->mainView() == this) {
recountAttachToPreviousInBlocks();
}
history()->owner().requestViewResize(this);
} else {
history()->owner().requestViewRepaint(this);
}
}
void Element::setUnreadBarFreezed() {
if (const auto bar = Get<UnreadBar>()) {
bar->freezed = true;
}
}
int Element::displayedDateHeight() const {
if (auto date = Get<DateBadge>()) {
return date->height();

View file

@ -96,26 +96,15 @@ TextSelection ShiftItemSelection(
// Any HistoryView::Element can have this Component for
// displaying the unread messages bar above the message.
struct UnreadBar : public RuntimeComponent<UnreadBar, Element> {
void init(int newCount);
void init();
static int height();
static int marginTop();
void paint(Painter &p, int y, int w) const;
static constexpr auto kCountUnknown = std::numeric_limits<int>::max();
QString text;
int width = 0;
int count = 0;
// If unread bar is freezed the new messages do not
// increment the counter displayed by this bar.
//
// It happens when we've opened the conversation and
// we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history.
bool freezed = false;
};
@ -193,14 +182,9 @@ public:
bool computeIsAttachToPrevious(not_null<Element*> previous);
void setUnreadBarCount(int count);
void createUnreadBar();
void destroyUnreadBar();
// marks the unread bar as freezed so that unread
// messages count will not change for this bar
// when the new messages arrive in this chat history
void setUnreadBarFreezed();
int displayedDateHeight() const;
bool displayDate() const;
bool isInOneDayWithPrevious() const;

View file

@ -462,7 +462,7 @@ void ListWidget::checkUnreadBarCreation() {
if (!_unreadBarElement) {
if (const auto index = _delegate->listUnreadBarView(_items)) {
_unreadBarElement = _items[*index].get();
_unreadBarElement->setUnreadBarCount(UnreadBar::kCountUnknown);
_unreadBarElement->createUnreadBar();
refreshAttachmentsAtIndex(*index);
}
}
@ -2497,14 +2497,9 @@ void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
if (_overElement == was) _overElement = now;
if (_unreadBarElement == was) {
const auto bar = _unreadBarElement->Get<UnreadBar>();
const auto count = bar ? bar->count : 0;
const auto freezed = bar ? bar->freezed : false;
_unreadBarElement = now;
if (now && count) {
_unreadBarElement->setUnreadBarCount(count);
if (freezed) {
_unreadBarElement->setUnreadBarFreezed();
}
if (now && bar) {
_unreadBarElement->createUnreadBar();
}
}
}

View file

@ -1170,6 +1170,9 @@ void Gif::validateGroupedCache(
&& thumb->height() < kUseNonBlurredThreshold));
if (good && !useGood) {
good->load({});
if (!useThumb) {
_data->loadThumbnail(_realParent->fullId());
}
}
const auto loadLevel = useGood ? 3 : useThumb ? 2 : image ? 1 : 0;

View file

@ -482,8 +482,9 @@ void Sticker::setupLottie(not_null<DocumentData*> document) const {
void Sticker::prepareThumbnail() const {
if (const auto document = getShownDocument()) {
if (document->sticker()->animated
&& !_lottie
if (!_lottie
&& document->sticker()
&& document->sticker()->animated
&& document->loaded()) {
setupLottie(document);
}

View file

@ -70,9 +70,11 @@ void PhoneWidget::setupQrLogin() {
) | rpl::then(
account().appConfig().refreshed()
) | rpl::map([=] {
return account().appConfig().get<QString>(
const auto result = account().appConfig().get<QString>(
"qr_login_code",
"disabled");
"[not-set]");
DEBUG_LOG(("PhoneWidget.qr_login_code: %1").arg(result));
return result;
}) | rpl::filter([](const QString &value) {
return (value != "disabled");
}) | rpl::take(1) | rpl::start_with_next([=] {
@ -81,6 +83,8 @@ void PhoneWidget::setupQrLogin() {
tr::lng_phone_to_qr(tr::now));
qrLogin->show();
DEBUG_LOG(("PhoneWidget.qrLogin link created and shown."));
rpl::combine(
sizeValue(),
qrLogin->widthValue()

View file

@ -33,7 +33,8 @@ void StartWidget::submit() {
account().destroyStaleAuthorizationKeys();
const auto qrLogin = account().appConfig().get<QString>(
"qr_login_code",
"disabled");
"[not-set]");
DEBUG_LOG(("qr_login_code: %1").arg(qrLogin));
if (qrLogin == "primary") {
goNext<QrWidget>();
} else {

View file

@ -47,6 +47,7 @@ void AppConfig::refresh() {
_data.emplace_or_assign(qs(data.vkey()), data.vvalue());
});
}
DEBUG_LOG(("getAppConfig result handled."));
}
_refreshed.fire({});
}).fail([=](const RPCError &error) {

View file

@ -27,6 +27,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "observer_peer.h"
#include "facades.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "chat_helpers/spellchecker_common.h"
#endif // TDESKTOP_DISABLE_SPELLCHECK
namespace Main {
namespace {
@ -97,6 +101,10 @@ Session::Session(
});
Window::Theme::Background()->start();
#ifndef TDESKTOP_DISABLE_SPELLCHECK
Spellchecker::Start(this);
#endif // TDESKTOP_DISABLE_SPELLCHECK
}
Session::~Session() {

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "support/support_common.h"
#include "storage/serialize_common.h"
#include "boxes/send_files_box.h"
#include "base/platform/base_platform_info.h"
namespace Main {
namespace {
@ -38,10 +39,17 @@ Settings::Variables::Variables()
, selectorTab(ChatHelpers::SelectorTab::Emoji)
, floatPlayerColumn(Window::Column::Second)
, floatPlayerCorner(RectPart::TopRight)
, dialogsWidthRatio(ThirdColumnByDefault()
? kDefaultBigDialogsWidthRatio
: kDefaultDialogsWidthRatio)
, sendSubmitWay(Ui::InputSubmitSettings::Enter)
, supportSwitch(Support::SwitchSettings::Next) {
}
bool Settings::ThirdColumnByDefault() {
return Platform::IsMacStoreBuild();
}
QByteArray Settings::serialize() const {
const auto autoDownload = _variables.autoDownload.serialize();
auto size = sizeof(qint32) * 38;
@ -110,6 +118,11 @@ QByteArray Settings::serialize() const {
}
stream << qint32(SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current()));
stream << _variables.videoPipGeometry;
stream << qint32(_variables.dictionariesEnabled.current().size());
for (const auto i : _variables.dictionariesEnabled.current()) {
stream << quint64(i);
}
stream << qint32(_variables.autoDownloadDictionaries.current() ? 1 : 0);
}
return result;
}
@ -162,6 +175,8 @@ void Settings::constructFromSerialized(const QByteArray &serialized) {
std::vector<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
qint32 videoPlaybackSpeed = SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current());
QByteArray videoPipGeometry = _variables.videoPipGeometry;
std::vector<int> dictionariesEnabled;
qint32 autoDownloadDictionaries = _variables.autoDownloadDictionaries.current() ? 1 : 0;
stream >> versionTag;
if (versionTag == kVersionTag) {
@ -288,6 +303,20 @@ void Settings::constructFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> videoPipGeometry;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
qint64 langId;
stream >> langId;
dictionariesEnabled.emplace_back(langId);
}
}
}
if (!stream.atEnd()) {
stream >> autoDownloadDictionaries;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Main::Settings::constructFromSerialized()"));
@ -377,6 +406,8 @@ void Settings::constructFromSerialized(const QByteArray &serialized) {
_variables.mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition);
_variables.videoPlaybackSpeed = DeserializePlaybackSpeed(videoPlaybackSpeed);
_variables.videoPipGeometry = videoPipGeometry;
_variables.dictionariesEnabled = std::move(dictionariesEnabled);
_variables.autoDownloadDictionaries = (autoDownloadDictionaries == 1);
}
void Settings::setSupportChatsTimeSlice(int slice) {

View file

@ -241,6 +241,31 @@ public:
return _variables.spellcheckerEnabled.changes();
}
void setDictionariesEnabled(std::vector<int> dictionaries) {
_variables.dictionariesEnabled = std::move(dictionaries);
}
std::vector<int> dictionariesEnabled() const {
return _variables.dictionariesEnabled.current();
}
rpl::producer<std::vector<int>> dictionariesEnabledChanges() const {
return _variables.dictionariesEnabled.changes();
}
void setAutoDownloadDictionaries(bool value) {
_variables.autoDownloadDictionaries = value;
}
bool autoDownloadDictionaries() const {
return _variables.autoDownloadDictionaries.current();
}
rpl::producer<bool> autoDownloadDictionariesValue() const {
return _variables.autoDownloadDictionaries.value();
}
rpl::producer<bool> autoDownloadDictionariesChanges() const {
return _variables.autoDownloadDictionaries.changes();
}
[[nodiscard]] float64 videoPlaybackSpeed() const {
return _variables.videoPlaybackSpeed.current();
}
@ -254,11 +279,14 @@ public:
_variables.videoPipGeometry = geometry;
}
[[nodiscard]] static bool ThirdColumnByDefault();
private:
struct Variables {
Variables();
static constexpr auto kDefaultDialogsWidthRatio = 5. / 14;
static constexpr auto kDefaultBigDialogsWidthRatio = 0.275;
static constexpr auto kDefaultThirdColumnWidth = 0;
bool lastSeenWarningSeen = false;
@ -273,8 +301,7 @@ private:
bool thirdSectionInfoEnabled = true; // per-window
bool smallDialogsList = false; // per-window
int thirdSectionExtendedBy = -1; // per-window
rpl::variable<float64> dialogsWidthRatio
= kDefaultDialogsWidthRatio; // per-window
rpl::variable<float64> dialogsWidthRatio; // per-window
rpl::variable<int> thirdColumnWidth
= kDefaultThirdColumnWidth; // per-window
Ui::InputSubmitSettings sendSubmitWay;
@ -296,6 +323,8 @@ private:
std::vector<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
rpl::variable<float64> videoPlaybackSpeed = 1.;
QByteArray videoPipGeometry;
rpl::variable<std::vector<int>> dictionariesEnabled;
rpl::variable<bool> autoDownloadDictionaries = true;
static constexpr auto kDefaultSupportChatsLimitSlice
= 7 * 24 * 60 * 60;

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_scheduled_messages.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "api/api_text_entities.h"
#include "ui/special_buttons.h"
#include "ui/widgets/buttons.h"
@ -2225,10 +2226,8 @@ void MainWidget::dialogsToUp() {
_dialogs->jumpToTop();
}
void MainWidget::markActiveHistoryAsRead() {
if (const auto activeHistory = _history->history()) {
session().api().readServerHistory(activeHistory);
}
void MainWidget::checkHistoryActivation() {
_history->checkHistoryActivation();
}
void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) {
@ -3527,15 +3526,8 @@ bool MainWidget::isActive() const {
return !_isIdle && isVisible() && !_a_show.animating();
}
bool MainWidget::doWeReadServerHistory() const {
return isActive()
&& !session().supportMode()
&& !_mainSection
&& _history->doWeReadServerHistory();
}
bool MainWidget::doWeReadMentions() const {
return isActive() && !_mainSection && _history->doWeReadMentions();
bool MainWidget::doWeMarkAsRead() const {
return isActive() && !_mainSection;
}
bool MainWidget::lastWasOnline() const {
@ -3815,8 +3807,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
: nullptr;
};
if (const auto id = owner.messageIdByRandomId(randomId)) {
if (const auto local = owner.message(id);
local->isScheduled()) {
const auto local = owner.message(id);
if (local && local->isScheduled()) {
owner.scheduledMessages().sendNowSimpleMessage(d, local);
}
}
@ -4044,7 +4036,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
// d.vunread_count()->v,
// d.vunread_muted_count()->v);
// } else {
// session().api().requestDialogEntry(feed);
// session().data().histories().requestDialogEntry(feed);
// }
// }
//} break;
@ -4420,7 +4412,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
session().api().requestPinnedDialogs(folder);
}
if (!loaded) {
session().api().requestDialogEntry(folder);
session().data().histories().requestDialogEntry(folder);
}
} break;
@ -4478,12 +4470,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
//if (const auto feed = channel->feed()) { // #feed
// feed->requestChatListMessage();
// if (!feed->unreadCountKnown()) {
// feed->session().api().requestDialogEntry(feed);
// feed->owner().histories().requestDialogEntry(feed);
// }
//} else {
history->requestChatListMessage();
if (!history->unreadCountKnown()) {
history->session().api().requestDialogEntry(history);
history->owner().histories().requestDialogEntry(history);
}
//}
if (!channel->amCreator()) {

View file

@ -148,7 +148,7 @@ public:
bool deleteChannelFailed(const RPCError &error);
void historyToDown(History *hist);
void dialogsToUp();
void markActiveHistoryAsRead();
void checkHistoryActivation();
PeerData *peer();
@ -173,8 +173,7 @@ public:
void updateOnlineDisplayIn(int32 msecs);
bool isActive() const;
bool doWeReadServerHistory() const;
bool doWeReadMentions() const;
[[nodiscard]] bool doWeMarkAsRead() const;
bool lastWasOnline() const;
crl::time lastSetOnline() const;

View file

@ -130,7 +130,7 @@ void MainWindow::initHook() {
Qt::QueuedConnection);
}
void MainWindow::firstShow() {
void MainWindow::createTrayIconMenu() {
#ifdef Q_OS_WIN
trayIconMenu = new Ui::PopupMenu(nullptr);
trayIconMenu->deleteOnHide(false);
@ -148,9 +148,48 @@ void MainWindow::firstShow() {
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray()));
trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray()));
trayIconMenu->addAction(tr::ktg_quit_from_tray(tr::now), this, SLOT(quitFromTray()));
initTrayMenuHook();
}
void MainWindow::applyInitialWorkMode() {
Global::RefWorkMode().setForced(Global::WorkMode().value(), true);
psFirstShow();
if (cWindowPos().maximized) {
DEBUG_LOG(("Window Pos: First show, setting maximized."));
setWindowState(Qt::WindowMaximized);
}
if (cStartInTray()
|| (cLaunchMode() == LaunchModeAutoStart
&& cStartMinimized()
&& !Core::App().passcodeLocked())) {
const auto minimizeAndHide = [=] {
DEBUG_LOG(("Window Pos: First show, setting minimized after."));
setWindowState(windowState() | Qt::WindowMinimized);
if (Global::WorkMode().value() == dbiwmTrayOnly
|| Global::WorkMode().value() == dbiwmWindowAndTray) {
hide();
}
};
if (Platform::IsLinux()) {
// If I call hide() synchronously here after show() then on Ubuntu 14.04
// it will show a window frame with transparent window body, without content.
// And to be able to "Show from tray" one more hide() will be required.
crl::on_main(this, minimizeAndHide);
} else {
minimizeAndHide();
}
}
setPositionInited();
}
void MainWindow::finishFirstShow() {
createTrayIconMenu();
initShadows();
applyInitialWorkMode();
createGlobalMenu();
firstShadowsUpdate();
updateTrayMenu();
windowDeactivateEvents(
@ -194,6 +233,7 @@ void MainWindow::setupPasscodeLock() {
if (animated) {
_passcodeLock->showAnimated(bg);
} else {
_passcodeLock->showFinished();
setInnerFocus();
}
}
@ -463,23 +503,17 @@ void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
}
}
bool MainWindow::doWeReadServerHistory() {
bool MainWindow::doWeMarkAsRead() {
if (!_main || Ui::isLayerShown()) {
return false;
}
updateIsActive(0);
return isActive()
&& !Ui::isLayerShown()
&& (_main ? _main->doWeReadServerHistory() : false);
}
bool MainWindow::doWeReadMentions() {
updateIsActive(0);
return isActive()
&& !Ui::isLayerShown()
&& (_main ? _main->doWeReadMentions() : false);
return isActive() && _main->doWeMarkAsRead();
}
void MainWindow::checkHistoryActivation() {
if (doWeReadServerHistory()) {
_main->markActiveHistoryAsRead();
if (_main) {
_main->checkHistoryActivation();
}
}
@ -515,10 +549,12 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) {
} break;
case QEvent::MouseMove: {
if (_main && _main->isIdle()) {
const auto position = static_cast<QMouseEvent*>(e)->globalPos();
if (_main && _main->isIdle() && _lastMousePosition != position) {
Core::App().updateNonIdle();
_main->checkIdleFinish();
}
_lastMousePosition = position;
} break;
case QEvent::MouseButtonRelease: {
@ -556,8 +592,7 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) {
void MainWindow::updateTrayMenu(bool force) {
if (!trayIconMenu || (Platform::IsWindows() && !force)) return;
auto iconMenu = trayIconMenu;
auto actions = iconMenu->actions();
auto actions = trayIconMenu->actions();
if (Platform::IsLinux()) {
auto minimizeAction = actions.at(1);
minimizeAction->setEnabled(isVisible());
@ -571,12 +606,6 @@ void MainWindow::updateTrayMenu(bool force) {
toggleAction->setText(active
? tr::lng_minimize_to_tray(tr::now)
: tr::ktg_open_from_tray(tr::now));
// On macOS just remove trayIcon menu if the window is not active.
// So we will activate the window on click instead of showing the menu.
if (!active && Platform::IsMac()) {
iconMenu = nullptr;
}
}
auto notificationAction = actions.at(Platform::IsLinux() ? 2 : 1);
auto notificationActionText = Global::DesktopNotify()

View file

@ -49,7 +49,7 @@ public:
explicit MainWindow(not_null<Window::Controller*> controller);
~MainWindow();
void firstShow();
void finishFirstShow();
void setupPasscodeLock();
void clearPasscodeLock();
@ -62,8 +62,7 @@ public:
MainWidget *mainWidget();
bool doWeReadServerHistory();
bool doWeReadMentions();
[[nodiscard]] bool doWeMarkAsRead();
void activate();
@ -152,9 +151,11 @@ signals:
private:
[[nodiscard]] bool skipTrayClick() const;
void createTrayIconMenu();
void handleTrayIconActication(
QSystemTrayIcon::ActivationReason reason) override;
void applyInitialWorkMode();
void ensureLayerCreated();
void destroyLayer();
@ -166,6 +167,7 @@ private:
QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64;
crl::time _lastTrayClickTime = 0;
QPoint _lastMousePosition;
object_ptr<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
object_ptr<Intro::Widget> _intro = { nullptr };

View file

@ -16,8 +16,10 @@ namespace Clip {
namespace internal {
namespace {
constexpr int kSkipInvalidDataPackets = 10;
constexpr int kAlignImageBy = 16;
constexpr auto kSkipInvalidDataPackets = 10;
// See https://github.com/telegramdesktop/tdesktop/issues/7225
constexpr auto kAlignImageBy = 64;
void alignedImageBufferCleanupHandler(void *data) {
auto buffer = static_cast<uchar*>(data);

View file

@ -189,14 +189,16 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
}
}
void Instance::clearStreamed(not_null<Data*> data) {
void Instance::clearStreamed(not_null<Data*> data, bool savePosition) {
if (!data->streamed || data->streamed->clearing) {
return;
}
data->streamed->clearing = true;
SaveLastPlaybackPosition(
data->current.audio(),
data->streamed->instance.player().prepareLegacyState());
if (savePosition) {
SaveLastPlaybackPosition(
data->current.audio(),
data->streamed->instance.player().prepareLegacyState());
}
data->streamed->instance.stop();
data->isPlaying = false;
requestRoundVideoResize();
@ -414,7 +416,7 @@ void Instance::playStreamed(
const auto data = getData(audioId.type());
Assert(data != nullptr);
clearStreamed(data);
clearStreamed(data, data->current.audio() != audioId.audio());
data->streamed = std::make_unique<Streamed>(
audioId,
std::move(shared));

View file

@ -225,7 +225,7 @@ private:
not_null<Data*> data,
Streaming::Error &&error);
void clearStreamed(not_null<Data *> data);
void clearStreamed(not_null<Data*> data, bool savePosition = true);
void emitUpdate(AudioMsgId::Type type);
template <typename CheckCallback>
void emitUpdate(AudioMsgId::Type type, CheckCallback check);

View file

@ -827,7 +827,6 @@ float64 Player::speed() const {
}
void Player::setSpeed(float64 speed) {
Expects(active());
Expects(speed >= 0.5 && speed <= 2.);
if (!Media::Audio::SupportsSpeedControl()) {
@ -835,11 +834,13 @@ void Player::setSpeed(float64 speed) {
}
if (_options.speed != speed) {
_options.speed = speed;
if (_audio) {
_audio->setSpeed(speed);
}
if (_video) {
_video->setSpeed(speed);
if (active()) {
if (_audio) {
_audio->setSpeed(speed);
}
if (_video) {
_video->setSpeed(speed);
}
}
}
}

View file

@ -212,6 +212,7 @@ void PaintFrameInner(
QPainter &p,
QRect to,
const QImage &original,
bool alpha,
int rotation) {
const auto rotated = [](QRect rect, int rotation) {
switch (rotation) {
@ -239,22 +240,32 @@ void PaintFrameInner(
if (rotation) {
p.rotate(rotation);
}
p.drawImage(rotated(to, rotation), original);
const auto rect = rotated(to, rotation);
if (alpha) {
p.fillRect(rect, Qt::white);
}
p.drawImage(rect, original);
}
void PaintFrameContent(
QPainter &p,
const QImage &original,
bool alpha,
int rotation,
const FrameRequest &request) {
const auto full = request.outer;
const auto full = request.outer.isEmpty()
? original.size()
: request.outer;
const auto size = request.resize.isEmpty()
? original.size()
: request.resize;
const auto to = QRect(
(full.width() - request.resize.width()) / 2,
(full.height() - request.resize.height()) / 2,
request.resize.width(),
request.resize.height());
(full.width() - size.width()) / 2,
(full.height() - size.height()) / 2,
size.width(),
size.height());
PaintFrameOuter(p, to, full);
PaintFrameInner(p, to, original, rotation);
PaintFrameInner(p, to, original, alpha, rotation);
}
void ApplyFrameRounding(QImage &storage, const FrameRequest &request) {
@ -267,17 +278,21 @@ void ApplyFrameRounding(QImage &storage, const FrameRequest &request) {
QImage PrepareByRequest(
const QImage &original,
bool alpha,
int rotation,
const FrameRequest &request,
QImage storage) {
Expects(!request.outer.isEmpty());
Expects(!request.outer.isEmpty() || alpha);
if (!FFmpeg::GoodStorageForFrame(storage, request.outer)) {
storage = FFmpeg::CreateFrameStorage(request.outer);
const auto outer = request.outer.isEmpty()
? original.size()
: request.outer;
if (!FFmpeg::GoodStorageForFrame(storage, outer)) {
storage = FFmpeg::CreateFrameStorage(outer);
}
QPainter p(&storage);
PaintFrameContent(p, original, rotation, request);
PaintFrameContent(p, original, alpha, rotation, request);
p.end();
ApplyFrameRounding(storage, request);

View file

@ -60,6 +60,7 @@ struct Stream {
QImage storage);
[[nodiscard]] QImage PrepareByRequest(
const QImage &original,
bool alpha,
int rotation,
const FrameRequest &request,
QImage storage);

View file

@ -361,6 +361,7 @@ void VideoTrackObject::presentFrameIfNeeded() {
Expects(frame->position != kFinishedPosition);
fillRequests(frame);
frame->alpha = (frame->decoded->format == AV_PIX_FMT_BGRA);
frame->original = ConvertFrame(
_stream,
frame->decoded.get(),
@ -982,7 +983,8 @@ QImage VideoTrack::frame(
unwrapped.updateFrameRequest(instance, useRequest);
});
}
if (GoodForRequest(frame->original, _streamRotation, useRequest)) {
if (!frame->alpha
&& GoodForRequest(frame->original, _streamRotation, useRequest)) {
return frame->original;
} else if (changed || none || i->second.image.isNull()) {
const auto j = none
@ -1002,6 +1004,7 @@ QImage VideoTrack::frame(
}
j->second.image = PrepareByRequest(
frame->original,
frame->alpha,
_streamRotation,
useRequest,
std::move(j->second.image));
@ -1025,7 +1028,8 @@ void VideoTrack::PrepareFrameByRequests(
const auto end = frame->prepared.end();
for (auto i = begin; i != end; ++i) {
auto &prepared = i->second;
if (!GoodForRequest(frame->original, rotation, prepared.request)) {
if (frame->alpha
|| !GoodForRequest(frame->original, rotation, prepared.request)) {
auto j = begin;
for (; j != i; ++j) {
if (j->second.request == prepared.request) {
@ -1036,6 +1040,7 @@ void VideoTrack::PrepareFrameByRequests(
if (j == i) {
prepared.image = PrepareByRequest(
frame->original,
frame->alpha,
rotation,
prepared.request,
std::move(prepared.image));

View file

@ -83,6 +83,8 @@ private:
crl::time display = kTimeUnknown;
base::flat_map<const Instance*, Prepared> prepared;
bool alpha = false;
};
class Shared {

View file

@ -920,8 +920,8 @@ void OverlayWidget::resizeContentByScreenSize() {
_w = _width;
_h = _height;
}
_x = skipWidth + (usew - _w) / 2;
_y = skipHeight + (useh - _h) / 2;
_x = (width() - _w) / 2;
_y = (height() - _h) / 2;
}
float64 OverlayWidget::radialProgress() const {
@ -2573,17 +2573,26 @@ void OverlayWidget::playbackControlsVolumeChangeFinished() {
}
void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
DEBUG_LOG(("Media playback speed: change to %1.").arg(speed));
if (_doc) {
DEBUG_LOG(("Media playback speed: %1 to settings.").arg(speed));
_doc->session().settings().setVideoPlaybackSpeed(speed);
_doc->session().saveSettingsDelayed();
}
if (_streamed && !videoIsGifv()) {
DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
_streamed->instance.setSpeed(speed);
}
}
float64 OverlayWidget::playbackControlsCurrentSpeed() {
return _doc ? _doc->session().settings().videoPlaybackSpeed() : 1.;
const auto result = _doc
? _doc->session().settings().videoPlaybackSpeed()
: 1.;
DEBUG_LOG(("Media playback speed: now %1 (doc %2)."
).arg(result
).arg(Logs::b(_doc != nullptr)));
return result;
}
void OverlayWidget::switchToPip() {
@ -2682,12 +2691,12 @@ void OverlayWidget::validatePhotoImage(Image *image, bool blurred) {
} else if (!_staticContent.isNull() && (blurred || !_blurred)) {
return;
}
const auto w = _width * cIntRetinaFactor();
const auto h = _height * cIntRetinaFactor();
const auto use = flipSizeByRotation({ _width, _height })
* cIntRetinaFactor();
_staticContent = image->pixNoCache(
fileOrigin(),
w,
h,
use.width(),
use.height(),
Images::Option::Smooth
| (blurred ? Images::Option::Blurred : Images::Option(0)));
_staticContent.setDevicePixelRatio(cRetinaFactor());
@ -3681,7 +3690,7 @@ void OverlayWidget::updateOver(QPoint pos) {
} else if (documentContentShown() && contentRect().contains(pos)) {
if ((_doc->isVideoFile() || _doc->isVideoMessage()) && _streamed) {
updateOverState(OverVideo);
} else if (!_doc->loaded()) {
} else if (!_streamed && !_doc->loaded()) {
updateOverState(OverIcon);
} else if (_over != OverNone) {
updateOverState(OverNone);

View file

@ -47,7 +47,7 @@ constexpr auto kMsInSecond = 1000;
[[nodiscard]] QRect ScreenFromPosition(QPoint point) {
const auto screen = [&]() -> QScreen* {
for (const auto screen : QGuiApplication::screens()) {
if (screen->virtualGeometry().contains(point)) {
if (screen->geometry().contains(point)) {
return screen;
}
}
@ -1391,6 +1391,7 @@ QImage Pip::videoFrame(const FrameRequest &request) const {
if (state == ThumbState::Cover) {
_preparedCoverStorage = Streaming::PrepareByRequest(
_instance.info().video.cover,
false,
_instance.info().video.rotation,
request,
std::move(_preparedCoverStorage));

View file

@ -223,7 +223,7 @@ void PlaybackControls::showMenu() {
addSpeed(1.75);
addSpeed(2.);
_menu.emplace(this, st::mediaviewControlsPopupMenu);
_menu->addAction("Rotate video", [=] {
_menu->addAction(tr::lng_mediaview_rotate_video(tr::now), [=] {
_delegate->playbackControlsRotate();
});
_menu->addSeparator();
@ -235,6 +235,7 @@ void PlaybackControls::showMenu() {
}
void PlaybackControls::updatePlaybackSpeed(float64 speed) {
DEBUG_LOG(("Media playback speed: update to %1.").arg(speed));
_delegate->playbackControlsSpeedChanged(speed);
resizeEvent(nullptr);
}

View file

@ -189,8 +189,9 @@ void SessionPrivate::appendTestConnection(
});
});
const auto protocolDcId = getProtocolDcId();
InvokeQueued(_testConnections.back().data, [=] {
weak->connectToServer(ip, port, protocolSecret, getProtocolDcId());
weak->connectToServer(ip, port, protocolSecret, protocolDcId);
});
}

View file

@ -87,7 +87,7 @@ bool NativeSupported() {
#ifndef TDESKTOP_FORCE_GTK_FILE_DIALOG
return false;
#endif // TDESKTOP_FORCE_GTK_FILE_DIALOG
return !Platform::IsXDGDesktopPortalPresent()
return !Platform::UseXDGDesktopPortal()
&& Platform::internal::GdkHelperLoaded()
&& (Libs::gtk_widget_hide_on_delete != nullptr)
&& (Libs::gtk_clipboard_store != nullptr)

View file

@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "facades.h"
#include "app.h"
#include <QtCore/QSize>
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
#include <QtDBus>
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
@ -36,24 +38,13 @@ constexpr auto kAttentionPanelTrayIconName = "kotatogram-attention-panel"_cs;
constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs;
constexpr auto kTrayIconFilename = "kdesktop-trayicon-XXXXXX.png"_cs;
int32 _trayIconSize = 48;
bool _trayIconMuted = true;
int32 _trayIconCount = 0;
QImage _trayIconImageBack, _trayIconImage;
QString _trayIconThemeName, _trayIconName;
bool TrayIconMuted = true;
int32 TrayIconCount = 0;
base::flat_map<int, QImage> TrayIconImageBack;
QIcon TrayIcon;
QString TrayIconThemeName, TrayIconName;
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
bool UseUnityCount = false;
QString UnityCountDesktopFile;
QString UnityCountDBusPath = "/";
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
#define QT_RED 0
#define QT_GREEN 1
#define QT_BLUE 2
#define QT_ALPHA 3
QString GetTrayIconName() {
QString GetPanelIconName() {
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
@ -64,108 +55,175 @@ QString GetTrayIconName() {
: kPanelTrayIconName.utf16();
}
QImage TrayIconImageGen() {
QString GetTrayIconName() {
const auto panelIconName = GetPanelIconName();
if (QIcon::hasThemeIcon(panelIconName)) {
return panelIconName;
} else if (InSandbox()) {
const auto launcherBasename = GetLauncherBasename();
if (QIcon::hasThemeIcon(launcherBasename)) {
return launcherBasename;
}
} else if (QIcon::hasThemeIcon(kTrayIconName.utf16())) {
return kTrayIconName.utf16();
}
return QString();
}
QIcon TrayIconGen() {
const auto iconThemeName = QIcon::themeName();
const auto iconName = GetTrayIconName();
if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())
&& !iconName.isEmpty()) {
if (TrayIcon.isNull()
|| iconThemeName != TrayIconThemeName
|| iconName != TrayIconName) {
TrayIcon = QIcon::fromTheme(iconName);
TrayIconThemeName = iconThemeName;
TrayIconName = iconName;
}
return TrayIcon;
}
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
const auto counterSlice = (counter >= 1000)
? (1000 + (counter % 100))
: counter;
const auto iconThemeName = QIcon::themeName();
const auto iconName = GetTrayIconName();
if (TrayIcon.isNull()
|| iconThemeName != TrayIconThemeName
|| iconName != TrayIconName
|| muted != TrayIconMuted
|| counterSlice != TrayIconCount) {
QIcon result;
QIcon systemIcon;
if (_trayIconImage.isNull()
|| _trayIconImage.width() != _trayIconSize
|| iconThemeName != _trayIconThemeName
|| iconName != _trayIconName
|| muted != _trayIconMuted
|| counterSlice != _trayIconCount) {
if (_trayIconImageBack.isNull()
|| _trayIconImageBack.width() != _trayIconSize
|| iconThemeName != _trayIconThemeName
|| iconName != _trayIconName) {
_trayIconImageBack = Core::App().logo();
const auto iconSizes = {
16,
22,
24,
32,
48,
64
};
_trayIconImageBack = QIcon::fromTheme(
iconName,
QIcon::fromTheme(
kTrayIconName.utf16(),
QIcon(QPixmap::fromImage(_trayIconImageBack)))
).pixmap(_trayIconSize, _trayIconSize).toImage();
for (const auto iconSize : iconSizes) {
auto &currentImageBack = TrayIconImageBack[iconSize];
const auto desiredSize = QSize(iconSize, iconSize);
auto w = _trayIconImageBack.width(),
h = _trayIconImageBack.height();
if (currentImageBack.isNull()
|| iconThemeName != TrayIconThemeName
|| iconName != TrayIconName) {
if (!iconName.isEmpty()) {
if(systemIcon.isNull()) {
systemIcon = QIcon::fromTheme(iconName);
}
if (w != _trayIconSize || h != _trayIconSize) {
_trayIconImageBack = _trayIconImageBack.scaled(
_trayIconSize,
_trayIconSize,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
if (systemIcon.actualSize(desiredSize) == desiredSize) {
currentImageBack = systemIcon
.pixmap(desiredSize)
.toImage();
} else {
const auto availableSizes = systemIcon
.availableSizes();
const auto biggestSize = ranges::max_element(
availableSizes,
std::less<>(),
&QSize::width);
currentImageBack = systemIcon
.pixmap(*biggestSize)
.toImage();
}
} else {
currentImageBack = Core::App().logo();
}
if (currentImageBack.size() != desiredSize) {
currentImageBack = currentImageBack.scaled(
desiredSize,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
}
_trayIconImageBack = _trayIconImageBack.convertToFormat(
QImage::Format_ARGB32);
}
auto iconImage = currentImageBack;
TrayIconMuted = muted;
TrayIconCount = counterSlice;
TrayIconThemeName = iconThemeName;
TrayIconName = iconName;
_trayIconImage = _trayIconImageBack;
_trayIconMuted = muted;
_trayIconCount = counterSlice;
_trayIconThemeName = iconThemeName;
_trayIconName = iconName;
if (!qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())
&& counter > 0) {
QPainter p(&iconImage);
int32 layerSize = -16;
if (counter > 0) {
QPainter p(&_trayIconImage);
int32 layerSize = -16;
if (iconSize >= 48) {
layerSize = -32;
} else if (iconSize >= 36) {
layerSize = -24;
} else if (iconSize >= 32) {
layerSize = -20;
}
if (_trayIconSize >= 48) {
layerSize = -32;
} else if (_trayIconSize >= 36) {
layerSize = -24;
} else if (_trayIconSize >= 32) {
layerSize = -20;
auto &bg = muted
? st::trayCounterBgMute
: st::trayCounterBg;
auto &fg = st::trayCounterFg;
auto layer = App::wnd()->iconWithCounter(
layerSize,
counter,
bg,
fg,
false);
p.drawImage(
iconImage.width() - layer.width() - 1,
iconImage.height() - layer.height() - 1,
layer);
}
auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg);
auto &fg = st::trayCounterFg;
auto layer = App::wnd()->iconWithCounter(
layerSize,
counter,
bg,
fg,
false);
p.drawImage(
_trayIconImage.width() - layer.width() - 1,
_trayIconImage.height() - layer.height() - 1,
layer);
result.addPixmap(App::pixmapFromImageInPlace(
std::move(iconImage)));
}
TrayIcon = result;
}
return _trayIconImage;
return TrayIcon;
}
bool IsAppIndicator() {
#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION
static const auto AppIndicator = false;
#else // TDESKTOP_DISABLE_DBUS_INTEGRATION
static const auto AppIndicator = QDBusInterface(
qsl("com.canonical.indicator.application"),
qsl("/com/canonical/indicator/application/service"),
qsl("com.canonical.indicator.application.service")).isValid()
|| QDBusInterface(
qsl("org.ayatana.indicator.application"),
qsl("/org/ayatana/indicator/application/service"),
qsl("org.ayatana.indicator.application.service")).isValid();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
return AppIndicator;
}
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
static bool NeedTrayIconFile() {
// Hack for indicator-application, which doesn't handle icons sent across D-Bus:
// save the icon to a temp file and set the icon name to that filename.
static const auto TrayIconFileNeeded = [&] {
auto necessary = false;
const auto session = QDBusConnection::sessionBus();
const auto pid = session.interface()
->servicePid(kSNIWatcherService.utf16()).value();
const auto processName = ProcessNameByPID(QString::number(pid));
necessary = processName.endsWith(
qsl("indicator-application-service"));
if (!necessary) {
// Accessing to process name might be not allowed if the application
// is confined, thus we can just rely on the current desktop in use
necessary = DesktopEnvironment::IsUnity()
|| DesktopEnvironment::IsMATE();
}
return necessary;
}();
static const auto TrayIconFileNeeded = IsAppIndicator();
return TrayIconFileNeeded;
}
@ -176,12 +234,14 @@ static inline QString TrayIconFileTemplate() {
}
std::unique_ptr<QTemporaryFile> TrayIconFile(
const QPixmap &icon, QObject *parent) {
const QIcon &icon,
int size,
QObject *parent) {
auto ret = std::make_unique<QTemporaryFile>(
TrayIconFileTemplate(),
parent);
ret->open();
icon.save(ret.get());
icon.pixmap(size).save(ret.get());
ret->close();
return ret;
}
@ -207,6 +267,18 @@ bool IsSNIAvailable() {
return SNIAvailable;
}
bool UseUnityCounter() {
#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION
static const auto UnityCounter = false;
#else // TDESKTOP_DISABLE_DBUS_INTEGRATION
static const auto UnityCounter = QDBusInterface(
"com.canonical.Unity",
"/").isValid();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
return UnityCounter;
}
quint32 djbStringHash(QString string) {
quint32 hash = 5381;
QByteArray chars = string.toLatin1();
@ -247,24 +319,25 @@ void MainWindow::psTrayMenuUpdated() {
}
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
void MainWindow::setSNITrayIcon(
const QIcon &icon, const QPixmap &iconPixmap) {
if (!NeedTrayIconFile()) {
_sniTrayIcon->setIconByPixmap(icon);
_sniTrayIcon->setToolTipIconByPixmap(icon);
}
void MainWindow::setSNITrayIcon(const QIcon &icon) {
const auto iconName = GetTrayIconName();
if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())) {
const auto iconName = GetTrayIconName();
if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())
&& !iconName.isEmpty()) {
_sniTrayIcon->setIconByName(iconName);
_sniTrayIcon->setToolTipIconByName(iconName);
} else if (NeedTrayIconFile()) {
_trayIconFile = TrayIconFile(iconPixmap, this);
_trayIconFile = TrayIconFile(icon, 22, this);
_trayToolTipIconFile = TrayIconFile(icon, 48, this);
if (_trayIconFile) {
_sniTrayIcon->setIconByName(_trayIconFile->fileName());
_sniTrayIcon->setToolTipIconByName(_trayIconFile->fileName());
_sniTrayIcon->setToolTipIconByName(
_trayToolTipIconFile->fileName());
}
} else {
_sniTrayIcon->setIconByPixmap(icon);
_sniTrayIcon->setToolTipIconByPixmap(icon);
}
}
@ -291,9 +364,6 @@ void MainWindow::attachToSNITrayIcon() {
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
void MainWindow::psSetupTrayIcon() {
const auto iconPixmap = QPixmap::fromImage(TrayIconImageGen());
const auto icon = QIcon(iconPixmap);
if (IsSNIAvailable()) {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
LOG(("Using SNI tray icon."));
@ -302,8 +372,8 @@ void MainWindow::psSetupTrayIcon() {
QCoreApplication::applicationName(),
this);
_sniTrayIcon->setTitle(QCoreApplication::applicationName());
setSNITrayIcon(icon, iconPixmap);
_sniTrayIcon->setTitle(AppName.utf16());
setSNITrayIcon(TrayIconGen());
attachToSNITrayIcon();
}
@ -319,7 +389,7 @@ void MainWindow::psSetupTrayIcon() {
if (!trayIcon) {
trayIcon = new QSystemTrayIcon(this);
trayIcon->setIcon(icon);
trayIcon->setIcon(TrayIconGen());
attachToTrayIcon(trayIcon);
}
@ -362,8 +432,9 @@ void MainWindow::updateIconCounters() {
updateWindowIcon();
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
if (UseUnityCount) {
if (UseUnityCounter()) {
const auto counter = Core::App().unreadBadge();
const auto launcherUrl = "application://" + GetLauncherFilename();
QVariantMap dbusUnityProperties;
if (counter > 0) {
// Gnome requires that count is a 64bit integer
@ -371,32 +442,30 @@ void MainWindow::updateIconCounters() {
"count",
(qint64) ((counter > 9999)
? 9999
: (counter)));
: counter));
dbusUnityProperties.insert("count-visible", true);
} else {
dbusUnityProperties.insert("count-visible", false);
}
QDBusMessage signal = QDBusMessage::createSignal(
UnityCountDBusPath,
"/com/canonical/unity/launcherentry/"
+ QString::number(djbStringHash(launcherUrl)),
"com.canonical.Unity.LauncherEntry",
"Update");
signal << "application://" + UnityCountDesktopFile;
signal << launcherUrl;
signal << dbusUnityProperties;
QDBusConnection::sessionBus().send(signal);
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
const auto iconPixmap = QPixmap::fromImage(TrayIconImageGen());
const auto icon = QIcon(iconPixmap);
if (IsSNIAvailable()) {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
if (_sniTrayIcon) {
setSNITrayIcon(icon, iconPixmap);
setSNITrayIcon(TrayIconGen());
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
} else if (trayIcon) {
trayIcon->setIcon(icon);
trayIcon->setIcon(TrayIconGen());
}
}
@ -406,71 +475,20 @@ void MainWindow::LibsLoaded() {
qDBusRegisterMetaType<IconPixmap>();
qDBusRegisterMetaType<IconPixmapList>();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
if (!IsSNIAvailable()) {
_trayIconSize = 22;
}
}
void MainWindow::psFirstShow() {
void MainWindow::initTrayMenuHook() {
const auto trayAvailable = IsSNIAvailable()
|| QSystemTrayIcon::isSystemTrayAvailable();
LOG(("System tray available: %1").arg(Logs::b(trayAvailable)));
cSetSupportTray(trayAvailable);
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
if (QDBusInterface("com.canonical.Unity", "/").isValid()) {
const std::vector<QString> possibleDesktopFiles = {
GetLauncherFilename(),
"Kotatogram.desktop"
};
for (auto it = possibleDesktopFiles.begin();
it != possibleDesktopFiles.end(); it++) {
if (!QStandardPaths::locate(
QStandardPaths::ApplicationsLocation, *it).isEmpty()) {
UnityCountDesktopFile = *it;
LOG(("Found Unity Launcher entry %1!")
.arg(UnityCountDesktopFile));
UseUnityCount = true;
break;
}
}
if (!UseUnityCount) {
LOG(("Could not get Unity Launcher entry!"));
}
UnityCountDBusPath = "/com/canonical/unity/launcherentry/"
+ QString::number(
djbStringHash("application://" + UnityCountDesktopFile));
if (UseUnityCounter()) {
LOG(("Using Unity launcher counter."));
} else {
LOG(("Not using Unity Launcher count."));
LOG(("Not using Unity launcher counter."));
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
show();
if (cWindowPos().maximized) {
DEBUG_LOG(("Window Pos: First show, setting maximized."));
setWindowState(Qt::WindowMaximized);
}
if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized())
|| cStartInTray()) {
// If I call hide() synchronously here after show() then on Ubuntu 14.04
// it will show a window frame with transparent window body, without content.
// And to be able to "Show from tray" one more hide() will be required.
crl::on_main(this, [=] {
setWindowState(Qt::WindowMinimized);
if (Global::WorkMode().value() == dbiwmTrayOnly
|| Global::WorkMode().value() == dbiwmWindowAndTray) {
hide();
} else {
show();
}
});
}
setPositionInited();
}
MainWindow::~MainWindow() {

View file

@ -24,8 +24,6 @@ class MainWindow : public Window::MainWindow {
public:
explicit MainWindow(not_null<Window::Controller*> controller);
void psFirstShow();
virtual QImage iconWithCounter(
int size,
int count,
@ -43,6 +41,7 @@ public slots:
protected:
void unreadCounterChangedHook() override;
void initTrayMenuHook() override;
bool hasTrayIcon() const override;
void workmodeUpdated(DBIWorkMode mode) override;
@ -69,8 +68,9 @@ private:
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
StatusNotifierItem *_sniTrayIcon = nullptr;
std::unique_ptr<QTemporaryFile> _trayIconFile = nullptr;
std::unique_ptr<QTemporaryFile> _trayToolTipIconFile = nullptr;
void setSNITrayIcon(const QIcon &icon, const QPixmap &iconPixmap);
void setSNITrayIcon(const QIcon &icon);
void attachToSNITrayIcon();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION

View file

@ -88,8 +88,12 @@ QVersionNumber ParseSpecificationVersion(
NotificationData::NotificationData(
const std::shared_ptr<QDBusInterface> &notificationInterface,
const base::weak_ptr<Manager> &manager,
const QString &title, const QString &subtitle,
const QString &msg, PeerId peerId, MsgId msgId)
const QString &title,
const QString &subtitle,
const QString &msg,
PeerId peerId,
MsgId msgId,
bool hideReplyButton)
: _notificationInterface(notificationInterface)
, _manager(manager)
, _title(title)
@ -118,9 +122,9 @@ NotificationData::NotificationData(
kInterface.utf16(),
qsl("ActionInvoked"),
this,
SLOT(notificationClicked(uint)));
SLOT(notificationClicked(uint,QString)));
if (capabilities.contains(qsl("inline-reply"))) {
if (capabilities.contains(qsl("inline-reply")) && !hideReplyButton) {
_actions << qsl("inline-reply")
<< tr::lng_notification_reply(tr::now);
@ -169,12 +173,14 @@ NotificationData::NotificationData(
SLOT(notificationClosed(uint)));
}
bool NotificationData::show() {
bool NotificationData::show(bool hideNameAndPhoto) {
const QDBusReply<uint> notifyReply = _notificationInterface->call(
qsl("Notify"),
AppName.utf16(),
uint(0),
QString(),
hideNameAndPhoto
? qsl("telegram")
: QString(),
_title,
_body,
_actions,
@ -261,13 +267,19 @@ void NotificationData::notificationClosed(uint id) {
}
}
void NotificationData::notificationClicked(uint id) {
if (id == _notificationId) {
const auto manager = _manager;
crl::on_main(manager, [=] {
manager->notificationActivated(_peerId, _msgId);
});
void NotificationData::notificationClicked(uint id, const QString &actionId) {
if (id != _notificationId) {
return;
}
if (actionId != qsl("default") && actionId != qsl("mail-reply-sender")) {
return;
}
const auto manager = _manager;
crl::on_main(manager, [=] {
manager->notificationActivated(_peerId, _msgId);
});
}
void NotificationData::notificationReplied(uint id, const QString &text) {
@ -279,7 +291,8 @@ void NotificationData::notificationReplied(uint id, const QString &text) {
}
}
QDBusArgument &operator<<(QDBusArgument &argument,
QDBusArgument &operator<<(
QDBusArgument &argument,
const NotificationData::ImageData &imageData) {
argument.beginStructure();
argument << imageData.width
@ -293,7 +306,8 @@ QDBusArgument &operator<<(QDBusArgument &argument,
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument,
const QDBusArgument &operator>>(
const QDBusArgument &argument,
NotificationData::ImageData &imageData) {
argument.beginStructure();
argument >> imageData.width
@ -374,12 +388,13 @@ void Manager::Private::showNotification(
subtitle,
msg,
peer->id,
msgId);
msgId,
hideReplyButton);
const auto key = hideNameAndPhoto
? InMemoryKey()
: peer->userpicUniqueKey();
notification->setImage(_cachedUserpics.get(key, peer));
if (!hideNameAndPhoto) {
const auto key = peer->userpicUniqueKey();
notification->setImage(_cachedUserpics.get(key, peer));
}
auto i = _notifications.find(peer->id);
if (i != _notifications.cend()) {
@ -395,7 +410,7 @@ void Manager::Private::showNotification(
i = _notifications.insert(peer->id, QMap<MsgId, Notification>());
}
_notifications[peer->id].insert(msgId, notification);
if (!notification->show()) {
if (!notification->show(hideNameAndPhoto)) {
i = _notifications.find(peer->id);
if (i != _notifications.cend()) {
i->remove(msgId);

View file

@ -38,15 +38,19 @@ public:
NotificationData(
const std::shared_ptr<QDBusInterface> &notificationInterface,
const base::weak_ptr<Manager> &manager,
const QString &title, const QString &subtitle,
const QString &msg, PeerId peerId, MsgId msgId);
const QString &title,
const QString &subtitle,
const QString &msg,
PeerId peerId,
MsgId msgId,
bool hideReplyButton);
NotificationData(const NotificationData &other) = delete;
NotificationData &operator=(const NotificationData &other) = delete;
NotificationData(NotificationData &&other) = delete;
NotificationData &operator=(NotificationData &&other) = delete;
bool show();
bool show(bool hideNameAndPhoto);
bool close();
void setImage(const QString &imagePath);
@ -72,16 +76,18 @@ private:
private slots:
void notificationClosed(uint id);
void notificationClicked(uint id);
void notificationClicked(uint id, const QString &actionId);
void notificationReplied(uint id, const QString &text);
};
using Notification = std::shared_ptr<NotificationData>;
QDBusArgument &operator<<(QDBusArgument &argument,
QDBusArgument &operator<<(
QDBusArgument &argument,
const NotificationData::ImageData &imageData);
const QDBusArgument &operator>>(const QDBusArgument &argument,
const QDBusArgument &operator>>(
const QDBusArgument &argument,
NotificationData::ImageData &imageData);
class Manager

View file

@ -43,8 +43,7 @@ using Platform::File::internal::EscapeShell;
namespace {
constexpr auto kDesktopFile = ":/misc/kotatogramdesktop.desktop"_cs;
bool XDGDesktopPortalPresent = false;
constexpr auto kSnapLauncherDir = "/var/lib/snapd/desktop/applications/"_cs;
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
void SandboxAutostart(bool autostart) {
@ -125,19 +124,25 @@ bool GenerateDesktopFile(const QString &targetPath, const QString &args) {
DEBUG_LOG(("App Info: placing .desktop file to %1").arg(targetPath));
if (!QDir(targetPath).exists()) QDir().mkpath(targetPath);
const auto sourceFile = [&] {
if (InSnap()) {
return kSnapLauncherDir.utf16() + GetLauncherFilename();
} else {
return kDesktopFile.utf16();
}
}();
const auto targetFile = targetPath + GetLauncherFilename();
QString fileText;
QFile source(kDesktopFile.utf16());
QFile source(sourceFile);
if (source.open(QIODevice::ReadOnly)) {
QTextStream s(&source);
fileText = s.readAll();
source.close();
} else {
LOG(("App Error: Could not open '%1' for read")
.arg(kDesktopFile.utf16()));
LOG(("App Error: Could not open '%1' for read").arg(sourceFile));
return false;
}
@ -196,9 +201,22 @@ bool InSnap() {
}
bool IsXDGDesktopPortalPresent() {
#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION
static const auto XDGDesktopPortalPresent = false;
#else // TDESKTOP_DISABLE_DBUS_INTEGRATION
static const auto XDGDesktopPortalPresent = QDBusInterface(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop").isValid();
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
return XDGDesktopPortalPresent;
}
bool UseXDGDesktopPortal() {
static const auto UsePortal = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL")
&& IsXDGDesktopPortalPresent();
return UsePortal;
}
QString ProcessNameByPID(const QString &pid) {
constexpr auto kMaxPath = 1024;
char result[kMaxPath] = { 0 };
@ -264,24 +282,34 @@ QString SingleInstanceLocalServerName(const QString &hash) {
QString GetLauncherBasename() {
static const auto LauncherBasename = [&] {
QString launcherBasename;
if (InSnap()) {
launcherBasename = qsl("%1_%2")
.arg(QString::fromLatin1(qgetenv("SNAP_NAME")))
.arg(qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME)));
const auto snapNameKey =
qEnvironmentVariableIsSet("SNAP_INSTANCE_NAME")
? "SNAP_INSTANCE_NAME"
: "SNAP_NAME";
LOG(("SNAP Environment detected, "
"launcher filename is %1.desktop")
.arg(launcherBasename));
} else {
launcherBasename =
qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME));
return qsl("%1_%2")
.arg(QString::fromLatin1(qgetenv(snapNameKey)))
.arg(cExeName());
}
return launcherBasename;
const auto possibleBasenames = std::vector<QString>{
qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME)),
qsl("Telegram")
};
for (const auto &it : possibleBasenames) {
if (!QStandardPaths::locate(
QStandardPaths::ApplicationsLocation,
it + qsl(".desktop")).isEmpty()) {
return it;
}
}
return possibleBasenames[0];
}();
LOG(("Launcher filename is %1.desktop").arg(LauncherBasename));
return LauncherBasename;
}
@ -406,20 +434,21 @@ namespace Platform {
void start() {
FallbackFontConfig();
#if !defined(TDESKTOP_DISABLE_DBUS_INTEGRATION) && defined(TDESKTOP_FORCE_GTK_FILE_DIALOG)
#ifdef TDESKTOP_FORCE_GTK_FILE_DIALOG
LOG(("Checking for XDG Desktop Portal..."));
XDGDesktopPortalPresent = QDBusInterface(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop").isValid();
// this can give us a chance to use a proper file dialog for current session
if(XDGDesktopPortalPresent) {
if(IsXDGDesktopPortalPresent()) {
LOG(("XDG Desktop Portal is present!"));
qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal");
if(UseXDGDesktopPortal()) {
LOG(("Usage of XDG Desktop Portal is enabled."));
qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal");
} else {
LOG(("Usage of XDG Desktop Portal is disabled."));
}
} else {
LOG(("XDG Desktop Portal is not present :("));
}
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION && TDESKTOP_FORCE_GTK_FILE_DIALOG
#endif // TDESKTOP_FORCE_GTK_FILE_DIALOG
}
void finish() {
@ -539,10 +568,19 @@ void psAutoStart(bool start, bool silent) {
SandboxAutostart(start);
#endif
} else {
const auto autostart =
QStandardPaths::writableLocation(
QStandardPaths::GenericConfigLocation)
+ qsl("/autostart/");
const auto autostart = [&] {
if (InSnap()) {
QDir realHomeDir(home);
realHomeDir.cd(qsl("../../.."));
return realHomeDir
.absoluteFilePath(qsl(".config/autostart/"));
} else {
return QStandardPaths::writableLocation(
QStandardPaths::GenericConfigLocation)
+ qsl("/autostart/");
}
}();
if (start) {
GenerateDesktopFile(autostart, qsl("-autostart"));

View file

@ -24,6 +24,7 @@ bool InSandbox();
bool InSnap();
bool IsXDGDesktopPortalPresent();
bool UseXDGDesktopPortal();
QString ProcessNameByPID(const QString &pid);
QString CurrentExecutablePath(int argc, char *argv[]);

View file

@ -23,8 +23,6 @@ class MainWindow : public Window::MainWindow {
public:
explicit MainWindow(not_null<Window::Controller*> controller);
void psFirstShow();
bool psFilterNativeEvent(void *event);
virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0;
@ -87,14 +85,15 @@ protected:
QTimer psUpdatedPositionTimer;
void initShadows() override;
void closeWithoutDestroy() override;
void createGlobalMenu() override;
private:
friend class Private;
void initTouchBar();
void hideAndDeactivate();
void createGlobalMenu();
void updateTitleCounter();
void updateIconCounters();

View file

@ -548,8 +548,15 @@ void MainWindow::psShowTrayMenu() {
}
void MainWindow::psTrayMenuUpdated() {
if (trayIcon && trayIconMenu && trayIcon->contextMenu() != trayIconMenu) {
trayIcon->setContextMenu(trayIconMenu);
// On macOS just remove trayIcon menu if the window is not active.
// So we will activate the window on click instead of showing the menu.
if (isActive()) {
if (trayIcon && trayIconMenu
&& trayIcon->contextMenu() != trayIconMenu) {
trayIcon->setContextMenu(trayIconMenu);
}
} else if (trayIcon) {
trayIcon->setContextMenu(nullptr);
}
}
@ -656,31 +663,8 @@ void MainWindow::updateIconCounters() {
}
}
void MainWindow::psFirstShow() {
bool showShadows = true;
show();
void MainWindow::initShadows() {
_private->enableShadow(winId());
if (cWindowPos().maximized) {
DEBUG_LOG(("Window Pos: First show, setting maximized."));
setWindowState(Qt::WindowMaximized);
}
if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) || cStartInTray()) {
setWindowState(Qt::WindowMinimized);
if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) {
hide();
} else {
show();
}
showShadows = false;
} else {
show();
}
setPositionInited();
createGlobalMenu();
}
void MainWindow::createGlobalMenu() {

View file

@ -808,41 +808,15 @@ void MainWindow::initHook() {
psInitSysMenu();
}
Q_DECLARE_METATYPE(QMargins);
void MainWindow::psFirstShow() {
void MainWindow::initShadows() {
_psShadowWindows.init(this, st::windowShadowFg->c);
_shadowsWorking = true;
psUpdateMargins();
shadowsUpdate(ShadowsChange::Hidden);
bool showShadows = true;
}
show();
if (cWindowPos().maximized) {
DEBUG_LOG(("Window Pos: First show, setting maximized."));
setWindowState(Qt::WindowMaximized);
}
if (cStartInTray()
|| (cLaunchMode() == LaunchModeAutoStart
&& cStartMinimized()
&& !Core::App().passcodeLocked())) {
DEBUG_LOG(("Window Pos: First show, setting minimized after."));
setWindowState(windowState() | Qt::WindowMinimized);
if (Global::WorkMode().value() == dbiwmTrayOnly
|| Global::WorkMode().value() == dbiwmWindowAndTray) {
hide();
} else {
show();
}
showShadows = false;
} else {
show();
}
setPositionInited();
if (showShadows) {
void MainWindow::firstShadowsUpdate() {
if (!(windowState() & Qt::WindowMinimized) && !isHidden()) {
shadowsUpdate(ShadowsChange::Moved | ShadowsChange::Resized | ShadowsChange::Shown);
}
}
@ -900,6 +874,7 @@ void MainWindow::updateSystemMenu(Qt::WindowState state) {
}
}
Q_DECLARE_METATYPE(QMargins);
void MainWindow::psUpdateMargins() {
if (!ps_hWnd || _inUpdateMargins) return;

View file

@ -28,7 +28,6 @@ public:
HWND psHwnd() const;
HMENU psMenu() const;
void psFirstShow();
void psInitSysMenu();
void updateSystemMenu(Qt::WindowState state);
void psUpdateMargins();
@ -77,6 +76,8 @@ protected:
int32 screenNameChecksum(const QString &name) const override;
void unreadCounterChangedHook() override;
void initShadows() override;
void firstShadowsUpdate() override;
void stateChangedHook(Qt::WindowState state) override;
bool hasTrayIcon() const override {

View file

@ -20,9 +20,22 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
#elif defined Q_OS_LINUX // Q_OS_WIN | Q_OS_MAC
Q_IMPORT_PLUGIN(ShmServerBufferPlugin)
Q_IMPORT_PLUGIN(DmaBufServerBufferPlugin)
Q_IMPORT_PLUGIN(DrmEglServerBufferPlugin)
Q_IMPORT_PLUGIN(QWaylandEglClientBufferPlugin)
Q_IMPORT_PLUGIN(QWaylandIviShellIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandWlShellIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandXdgShellV5IntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandXdgShellV6IntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandXdgShellIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandBradientDecorationPlugin)
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin)
Q_IMPORT_PLUGIN(QWaylandEglPlatformIntegrationPlugin)
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
Q_IMPORT_PLUGIN(QComposePlatformInputContextPlugin)
Q_IMPORT_PLUGIN(QSvgIconPlugin)
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
Q_IMPORT_PLUGIN(QConnmanEnginePlugin)
Q_IMPORT_PLUGIN(QNetworkManagerEnginePlugin)

View file

@ -216,3 +216,7 @@ settingsForwardPrivacyTooltipPadding: margins(8px, 6px, 8px, 6px);
settingsAccentColorSize: 24px;
settingsAccentColorSkip: 4px;
settingsAccentColorLine: 3px;
dictionariesSectionButton: SettingsButton(settingsUpdateToggle) {
font: font(14px semibold);
}

Some files were not shown because too many files have changed in this diff Show more