diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7586a3241..349b8b481 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -71,7 +71,7 @@ jobs: libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ 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 \ + libvorbis-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 libegl1-mesa-dev \ @@ -323,7 +323,7 @@ jobs: run: | cd $LibrariesPath - git clone -b openal-soft-1.19.1 --depth=1 $GIT/kcat/openal-soft.git + git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git cd openal-soft/build cmake -D LIBTYPE:STRING=STATIC .. make -j$(nproc) @@ -342,14 +342,15 @@ jobs: run: | cd $LibrariesPath + opensslDir=openssl_${OPENSSL_VER} git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \ - $GIT/openssl/openssl openssl_${OPENSSL_VER} - cd openssl_${OPENSSL_VER} + $GIT/openssl/openssl $opensslDir + cd $opensslDir ./config --prefix=$LibrariesPath/openssl-cache make -j$(nproc) - sudo make install + sudo make install_sw cd .. - rm -rf openssl_${OPENSSL_VER} + rm -rf $opensslDir - name: OpenSSL install. run: | cd $LibrariesPath diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 53beba810..896e1061c 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -26,11 +26,11 @@ jobs: GIT: "https://github.com" PREFIX: "/usr/local/macos" MACOSX_DEPLOYMENT_TARGET: "10.12" - XZ: "xz-5.0.5" + XZ: "xz-5.2.4" QT: "5_12_5" OPENSSL_VER: "1_1_1" QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.5" - LIBICONV_VER: "libiconv-1.15" + LIBICONV_VER: "libiconv-1.16" UPLOAD_ARTIFACT: "false" ONLY_CACHE: "false" MANUAL_CACHING: "2" diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index ec73a14d8..1dbe5b461 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -47,6 +47,10 @@ 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" '/^ ffmpeg:/' snap/snapcraft.yaml > FFMPEG_CACHE_KEY.txt + md5cache=$(md5sum FFMPEG_CACHE_KEY.txt | cut -c -32) + echo ::set-env name=FFMPEG_CACHE_KEY::$md5cache + - name: CMake cache. id: cache-cmake uses: actions/cache@v1 @@ -58,6 +62,17 @@ jobs: if: steps.cache-cmake.outputs.cache-hit != 'true' run: snapcraft build --destructive-mode cmake + - name: FFmpeg cache. + id: cache-ffmpeg + uses: actions/cache@v1 + with: + path: parts/ffmpeg + key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-${{ env.FFMPEG_CACHE_KEY }} + + - name: FFmpeg build. + if: steps.cache-ffmpeg.outputs.cache-hit != 'true' + run: snapcraft build --destructive-mode ffmpeg + - name: Kotatogram Desktop snap build. if: env.ONLY_CACHE == 'false' run: snapcraft --destructive-mode @@ -80,5 +95,5 @@ jobs: - name: Remove unneeded directories for cache. run: | - rm -rf parts/cmake/{build,src,ubuntu} - rm -rf parts/cmake/state/{stage,prime} + rm -rf parts/{cmake,ffmpeg}/{build,src,ubuntu} + rm -rf parts/{cmake,ffmpeg}/state/{stage,prime} diff --git a/CMakeLists.txt b/CMakeLists.txt index d427ad059..645d886a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,9 @@ project(Telegram ) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Telegram) +get_filename_component(third_party_loc "Telegram/ThirdParty" REALPATH) +get_filename_component(submodules_loc "Telegram" REALPATH) + include(cmake/variables.cmake) include(cmake/nice_target_sources.cmake) include(cmake/target_link_static_libraries.cmake) diff --git a/README.md b/README.md index 4e2f58edc..67b281f1a 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,4 @@ Full list of features will rewritten later, for now you can use one of `control- [flatpak]: https://flathub.org/apps/details/io.github.kotatogram [changelog]: https://github.com/kotatogram/kotatogram-desktop/blob/dev/kotatogram_changes.txt [preview_image]: https://github.com/kotatogram/kotatogram-desktop/blob/dev/docs/assets/ktg_preview.png "Preview of Kotatogram Desktop" -[preview_image_url]: https://github.com/kotatogram/kotatogram-desktop/blob/dev/docs/assets/ktg_preview.png \ No newline at end of file +[preview_image_url]: https://github.com/kotatogram/kotatogram-desktop/blob/dev/docs/assets/ktg_preview.png diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4ae453987..2a6da282f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -94,6 +94,7 @@ PRIVATE desktop-app::external_lz4 desktop-app::external_rlottie desktop-app::external_zlib + desktop-app::external_minizip desktop-app::external_qt desktop-app::external_qr_code_generator desktop-app::external_crash_reports @@ -1219,14 +1220,15 @@ endif() if (LINUX AND DESKTOP_APP_USE_PACKAGED) include(GNUInstallDirs) + configure_file("../lib/xdg/kotatogramdesktop.appdata.xml.in" "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.appdata.xml" @ONLY) install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}") - install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "kotatogram.png") - install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "kotatogram.png") - install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "kotatogram.png") - install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "kotatogram.png") - install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "kotatogram.png") - install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "kotatogram.png") - install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "kotatogram.png") + install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png") + install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png") + install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png") + install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png") + install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png") + install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png") + install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png") install(FILES "../lib/xdg/kotatogramdesktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.desktop") - install(FILES "../lib/xdg/kotatogramdesktop.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.appdata.xml") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.appdata.xml") endif() diff --git a/Telegram/Patches/ffmpeg.diff b/Telegram/Patches/ffmpeg.diff new file mode 100644 index 000000000..b5ab619fe --- /dev/null +++ b/Telegram/Patches/ffmpeg.diff @@ -0,0 +1,225 @@ +diff --git a/libavcodec/aarch64/Makefile b/libavcodec/aarch64/Makefile +index 00f93bf59f..52da7036f3 100644 +--- a/libavcodec/aarch64/Makefile ++++ b/libavcodec/aarch64/Makefile +@@ -6,6 +6,7 @@ OBJS-$(CONFIG_H264DSP) += aarch64/h264dsp_init_aarch64.o + OBJS-$(CONFIG_H264PRED) += aarch64/h264pred_init.o + OBJS-$(CONFIG_H264QPEL) += aarch64/h264qpel_init_aarch64.o + OBJS-$(CONFIG_HPELDSP) += aarch64/hpeldsp_init_aarch64.o ++OBJS-$(CONFIG_IDCTDSP) += aarch64/idctdsp_init_aarch64.o + OBJS-$(CONFIG_MPEGAUDIODSP) += aarch64/mpegaudiodsp_init.o + OBJS-$(CONFIG_NEON_CLOBBER_TEST) += aarch64/neontest.o + OBJS-$(CONFIG_VIDEODSP) += aarch64/videodsp_init.o +@@ -21,6 +22,7 @@ OBJS-$(CONFIG_VC1DSP) += aarch64/vc1dsp_init_aarch64.o + OBJS-$(CONFIG_VORBIS_DECODER) += aarch64/vorbisdsp_init.o + OBJS-$(CONFIG_VP9_DECODER) += aarch64/vp9dsp_init_10bpp_aarch64.o \ + aarch64/vp9dsp_init_12bpp_aarch64.o \ ++ aarch64/vp9mc_aarch64.o \ + aarch64/vp9dsp_init_aarch64.o + + # ARMv8 optimizations +@@ -41,8 +43,7 @@ NEON-OBJS-$(CONFIG_H264PRED) += aarch64/h264pred_neon.o + NEON-OBJS-$(CONFIG_H264QPEL) += aarch64/h264qpel_neon.o \ + aarch64/hpeldsp_neon.o + NEON-OBJS-$(CONFIG_HPELDSP) += aarch64/hpeldsp_neon.o +-NEON-OBJS-$(CONFIG_IDCTDSP) += aarch64/idctdsp_init_aarch64.o \ +- aarch64/simple_idct_neon.o ++NEON-OBJS-$(CONFIG_IDCTDSP) += aarch64/simple_idct_neon.o + NEON-OBJS-$(CONFIG_MDCT) += aarch64/mdct_neon.o + NEON-OBJS-$(CONFIG_MPEGAUDIODSP) += aarch64/mpegaudiodsp_neon.o + NEON-OBJS-$(CONFIG_VP8DSP) += aarch64/vp8dsp_neon.o +diff --git a/libavcodec/aarch64/idctdsp_init_aarch64.c b/libavcodec/aarch64/idctdsp_init_aarch64.c +index 0406e60830..742a3372e3 100644 +--- a/libavcodec/aarch64/idctdsp_init_aarch64.c ++++ b/libavcodec/aarch64/idctdsp_init_aarch64.c +@@ -21,6 +21,8 @@ + */ + + #include "libavutil/attributes.h" ++#include "libavutil/cpu.h" ++#include "libavutil/arm/cpu.h" + #include "libavcodec/avcodec.h" + #include "libavcodec/idctdsp.h" + #include "idct.h" +@@ -28,7 +30,9 @@ + av_cold void ff_idctdsp_init_aarch64(IDCTDSPContext *c, AVCodecContext *avctx, + unsigned high_bit_depth) + { +- if (!avctx->lowres && !high_bit_depth) { ++ int cpu_flags = av_get_cpu_flags(); ++ ++ if (have_neon(cpu_flags) && !avctx->lowres && !high_bit_depth) { + if (avctx->idct_algo == FF_IDCT_AUTO || + avctx->idct_algo == FF_IDCT_SIMPLEAUTO || + avctx->idct_algo == FF_IDCT_SIMPLENEON) { +diff --git a/libavcodec/aarch64/vp9mc_16bpp_neon.S b/libavcodec/aarch64/vp9mc_16bpp_neon.S +index cac6428709..53b372c262 100644 +--- a/libavcodec/aarch64/vp9mc_16bpp_neon.S ++++ b/libavcodec/aarch64/vp9mc_16bpp_neon.S +@@ -25,31 +25,6 @@ + // const uint8_t *ref, ptrdiff_t ref_stride, + // int h, int mx, int my); + +-function ff_vp9_copy128_aarch64, export=1 +-1: +- ldp x5, x6, [x2] +- ldp x7, x8, [x2, #16] +- stp x5, x6, [x0] +- ldp x9, x10, [x2, #32] +- stp x7, x8, [x0, #16] +- subs w4, w4, #1 +- ldp x11, x12, [x2, #48] +- stp x9, x10, [x0, #32] +- stp x11, x12, [x0, #48] +- ldp x5, x6, [x2, #64] +- ldp x7, x8, [x2, #80] +- stp x5, x6, [x0, #64] +- ldp x9, x10, [x2, #96] +- stp x7, x8, [x0, #80] +- ldp x11, x12, [x2, #112] +- stp x9, x10, [x0, #96] +- stp x11, x12, [x0, #112] +- add x2, x2, x3 +- add x0, x0, x1 +- b.ne 1b +- ret +-endfunc +- + function ff_vp9_avg64_16_neon, export=1 + mov x5, x0 + sub x1, x1, #64 +diff --git a/libavcodec/aarch64/vp9mc_aarch64.S b/libavcodec/aarch64/vp9mc_aarch64.S +new file mode 100644 +index 0000000000..f17a8cf04a +--- /dev/null ++++ b/libavcodec/aarch64/vp9mc_aarch64.S +@@ -0,0 +1,81 @@ ++/* ++ * Copyright (c) 2016 Google Inc. ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "libavutil/aarch64/asm.S" ++ ++// All public functions in this file have the following signature: ++// typedef void (*vp9_mc_func)(uint8_t *dst, ptrdiff_t dst_stride, ++// const uint8_t *ref, ptrdiff_t ref_stride, ++// int h, int mx, int my); ++ ++function ff_vp9_copy128_aarch64, export=1 ++1: ++ ldp x5, x6, [x2] ++ ldp x7, x8, [x2, #16] ++ stp x5, x6, [x0] ++ ldp x9, x10, [x2, #32] ++ stp x7, x8, [x0, #16] ++ subs w4, w4, #1 ++ ldp x11, x12, [x2, #48] ++ stp x9, x10, [x0, #32] ++ stp x11, x12, [x0, #48] ++ ldp x5, x6, [x2, #64] ++ ldp x7, x8, [x2, #80] ++ stp x5, x6, [x0, #64] ++ ldp x9, x10, [x2, #96] ++ stp x7, x8, [x0, #80] ++ ldp x11, x12, [x2, #112] ++ stp x9, x10, [x0, #96] ++ stp x11, x12, [x0, #112] ++ add x2, x2, x3 ++ add x0, x0, x1 ++ b.ne 1b ++ ret ++endfunc ++ ++function ff_vp9_copy64_aarch64, export=1 ++1: ++ ldp x5, x6, [x2] ++ ldp x7, x8, [x2, #16] ++ stp x5, x6, [x0] ++ ldp x9, x10, [x2, #32] ++ stp x7, x8, [x0, #16] ++ subs w4, w4, #1 ++ ldp x11, x12, [x2, #48] ++ stp x9, x10, [x0, #32] ++ stp x11, x12, [x0, #48] ++ add x2, x2, x3 ++ add x0, x0, x1 ++ b.ne 1b ++ ret ++endfunc ++ ++function ff_vp9_copy32_aarch64, export=1 ++1: ++ ldp x5, x6, [x2] ++ ldp x7, x8, [x2, #16] ++ stp x5, x6, [x0] ++ subs w4, w4, #1 ++ stp x7, x8, [x0, #16] ++ add x2, x2, x3 ++ add x0, x0, x1 ++ b.ne 1b ++ ret ++endfunc +diff --git a/libavcodec/aarch64/vp9mc_neon.S b/libavcodec/aarch64/vp9mc_neon.S +index f67624ca04..abf2bae9db 100644 +--- a/libavcodec/aarch64/vp9mc_neon.S ++++ b/libavcodec/aarch64/vp9mc_neon.S +@@ -25,23 +25,6 @@ + // const uint8_t *ref, ptrdiff_t ref_stride, + // int h, int mx, int my); + +-function ff_vp9_copy64_aarch64, export=1 +-1: +- ldp x5, x6, [x2] +- ldp x7, x8, [x2, #16] +- stp x5, x6, [x0] +- ldp x9, x10, [x2, #32] +- stp x7, x8, [x0, #16] +- subs w4, w4, #1 +- ldp x11, x12, [x2, #48] +- stp x9, x10, [x0, #32] +- stp x11, x12, [x0, #48] +- add x2, x2, x3 +- add x0, x0, x1 +- b.ne 1b +- ret +-endfunc +- + function ff_vp9_avg64_neon, export=1 + mov x5, x0 + 1: +@@ -64,19 +47,6 @@ function ff_vp9_avg64_neon, export=1 + ret + endfunc + +-function ff_vp9_copy32_aarch64, export=1 +-1: +- ldp x5, x6, [x2] +- ldp x7, x8, [x2, #16] +- stp x5, x6, [x0] +- subs w4, w4, #1 +- stp x7, x8, [x0, #16] +- add x2, x2, x3 +- add x0, x0, x1 +- b.ne 1b +- ret +-endfunc +- + function ff_vp9_avg32_neon, export=1 + 1: + ld1 {v2.16b, v3.16b}, [x2], x3 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index afc2e7528..67046818f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2239,6 +2239,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_linux_menu_undo" = "Undo"; "lng_linux_menu_redo" = "Redo"; +"lng_linux_menu_tools" = "Tools"; +"lng_linux_menu_help" = "Help"; "lng_linux_no_audio_prefs" = "You don't have any audio configuration applications installed."; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 474eae12b..00a57955b 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.9.20.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index a0feaa15b..5dbea9760 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1503,7 +1503,8 @@ void ApiWrap::applyLastParticipantsList( channel->mgInfo->lastAdmins.clear(); channel->mgInfo->lastRestricted.clear(); channel->mgInfo->lastParticipants.clear(); - channel->mgInfo->lastParticipantsStatus = MegagroupInfo::LastParticipantsUpToDate; + channel->mgInfo->lastParticipantsStatus = MegagroupInfo::LastParticipantsUpToDate + | MegagroupInfo::LastParticipantsOnceReceived; auto botStatus = channel->mgInfo->botStatus; const auto emptyAdminRights = MTP_chatAdminRights(MTP_flags(0)); diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 2a5d03d52..9e9e4603d 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -206,8 +206,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { if (_chat) { maxListSize += (_chat->participants.empty() ? _chat->lastAuthors.size() : _chat->participants.size()); } else if (_channel && _channel->isMegagroup()) { - if (_channel->mgInfo->lastParticipants.empty() || _channel->lastParticipantsCountOutdated()) { - } else { + if (!_channel->lastParticipantsRequestNeeded()) { maxListSize += _channel->mgInfo->lastParticipants.size(); } } @@ -274,7 +273,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } } else if (_channel && _channel->isMegagroup()) { QMultiMap ordered; - if (_channel->mgInfo->lastParticipants.empty() || _channel->lastParticipantsCountOutdated()) { + if (_channel->lastParticipantsRequestNeeded()) { Auth().api().requestLastParticipants(_channel); } else { mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); diff --git a/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp index ee4b0d7c6..881a495dc 100644 --- a/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp @@ -133,27 +133,22 @@ void DownloadDictionaryInBackground( 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 (DictionaryExists(id)) { + auto dicts = session->settings().dictionariesEnabled(); + if (!ranges::contains(dicts, id)) { + dicts.push_back(id); + session->settings().setDictionariesEnabled(std::move(dicts)); + session->saveSettingsDelayed(); } } - if (copyCounter >= copyLangs.size()) { + if (counter >= langs.size()) { return; } - DownloadDictionaryInBackground(copySession, copyCounter, copyLangs); + DownloadDictionaryInBackground(session, counter, langs); }; if (DictionaryExists(id)) { destroyer(); @@ -194,20 +189,21 @@ DictLoader::DictLoader( } void DictLoader::unpack(const QString &path) { - Expects(_destroyCallback); crl::async([=] { const auto success = Spellchecker::UnpackDictionary(path, id()); if (success) { QFile(path).remove(); + destroy(); + return; } - crl::on_main(success ? _destroyCallback : [=] { fail(); }); + crl::on_main([=] { fail(); }); }); } void DictLoader::destroy() { Expects(_destroyCallback); - _destroyCallback(); + crl::on_main(_destroyCallback); } void DictLoader::fail() { diff --git a/Telegram/SourceFiles/chat_helpers/spellchecker_common.h b/Telegram/SourceFiles/chat_helpers/spellchecker_common.h index 203c4e047..f806ceb08 100644 --- a/Telegram/SourceFiles/chat_helpers/spellchecker_common.h +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.h @@ -60,6 +60,7 @@ private: void unpack(const QString &path) override; void fail() override; + // Be sure to always call it in the main thread. Fn _destroyCallback; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 783dd333f..e0bfcf8b7 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -18,33 +18,6 @@ namespace { std::map BetaLogs() { return { - { - 1008005, - "\xE2\x80\xA2 Create new themes based on your color " - "and wallpaper choices.\n" - - "\xE2\x80\xA2 Share your themes with other users via links.\n" - - "\xE2\x80\xA2 Update your theme for all its users " - "when you change something.\n" - }, - { - 1009000, - "\xE2\x80\xA2 System spell checker on Windows 8+ and macOS 10.12+.\n" - }, - { - 1009002, - "\xE2\x80\xA2 Videos in chats start playing automatically.\n" - - "\xE2\x80\xA2 Resume playback from where you left off " - "when watching long videos.\n" - - "\xE2\x80\xA2 Control videos, GIFs and round video messages " - "automatic playback in " - "Settings > Advanced > Automatic media download.\n" - - "\xE2\x80\xA2 Spell checker on Linux using Enchant.\n" - }, { 1009010, "\xE2\x80\xA2 Switch to the Picture-in-Picture mode " @@ -63,12 +36,19 @@ std::map BetaLogs() { "\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." + }, + { + 1009020, + "\xE2\x80\xA2 Fix crash in shared links search.\n" + + "\xE2\x80\xA2 Fix blurred thumbnails in albums with video files.\n" + + "\xE2\x80\xA2 Fix a possible crash in animated stickers rendering." } }; }; diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index 551f190ad..3d20b5837 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -247,6 +247,7 @@ void Launcher::init() { prepareSettings(); QApplication::setApplicationName(qsl("KotatogramDesktop")); + QApplication::setApplicationDisplayName(AppName.utf16()); #if defined(Q_OS_LINUX) && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) QApplication::setDesktopFileName(Platform::GetLauncherFilename()); diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index bd43415c7..46c967b4f 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -274,7 +274,7 @@ void Sandbox::socketError(QLocalSocket::LocalSocketError e) { psCheckLocalSocket(_localServerName); if (!_localServer.listen(_localServerName)) { - LOG(("Failed to start listening to %1 server, error %2").arg(_localServerName).arg(int(_localServer.serverError()))); + LOG(("Failed to start listening to %1 server: %2").arg(_localServerName).arg(_localServer.errorString())); return App::quit(); } #endif // !Q_OS_WINRT diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 2eae51e78..08a7956f0 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -341,6 +341,20 @@ bool ChannelData::isGroupAdmin(not_null user) const { return false; } +bool ChannelData::lastParticipantsRequestNeeded() const { + if (!mgInfo) { + return false; + } else if (mgInfo->lastParticipantsCount == membersCount()) { + mgInfo->lastParticipantsStatus + &= ~MegagroupInfo::LastParticipantsCountOutdated; + } + return mgInfo->lastParticipants.empty() + || !(mgInfo->lastParticipantsStatus + & MegagroupInfo::LastParticipantsOnceReceived) + || (mgInfo->lastParticipantsStatus + & MegagroupInfo::LastParticipantsCountOutdated); +} + QString ChannelData::adminRank(not_null user) const { if (!isGroupAdmin(user)) { return QString(); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 6f71e6e7d..7e1995ed4 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -73,6 +73,7 @@ public: enum LastParticipantsStatus { LastParticipantsUpToDate = 0x00, + LastParticipantsOnceReceived = 0x01, LastParticipantsCountOutdated = 0x02, }; mutable int lastParticipantsStatus = LastParticipantsUpToDate; @@ -219,21 +220,8 @@ public: void markForbidden(); [[nodiscard]] bool isGroupAdmin(not_null user) const; + [[nodiscard]] bool lastParticipantsRequestNeeded() const; [[nodiscard]] QString adminRank(not_null user) const; - - [[nodiscard]] bool lastParticipantsCountOutdated() const { - if (!mgInfo - || !(mgInfo->lastParticipantsStatus - & MegagroupInfo::LastParticipantsCountOutdated)) { - return false; - } - if (mgInfo->lastParticipantsCount == membersCount()) { - mgInfo->lastParticipantsStatus - &= ~MegagroupInfo::LastParticipantsCountOutdated; - return false; - } - return true; - } [[nodiscard]] bool isMegagroup() const { return flags() & MTPDchannel::Flag::f_megagroup; } diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 56b6d5ff6..9603ba960 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -42,7 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -constexpr auto kMemoryForCache = 32 * 1024 * 1024; +// Updated Mar 3, 2020: Increase the size of the memory cache for media, to prevent items still being displayed from being unloaded. +constexpr auto kMemoryForCache = 128 * 1024 * 1024; // was 32, updated to 128 const auto kAnimatedStickerDimensions = QSize(512, 512); using FilePathResolve = DocumentData::FilePathResolve; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ea0921999..6396fd98b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5171,6 +5171,8 @@ int HistoryWidget::countInitialScrollTop() { } void HistoryWidget::createUnreadBarIfBelowVisibleArea(int withScrollTop) { + Expects(_history != nullptr); + if (_history->unreadBar()) { return; } @@ -5292,7 +5294,7 @@ void HistoryWidget::updateHistoryGeometry( newScrollTop = countInitialScrollTop(); _historyInited = true; _scrollToAnimation.stop(); - } else if (wasAtBottom && !loadedDown) { + } else if (wasAtBottom && !loadedDown && !_history->unreadBar()) { newScrollTop = countAutomaticScrollTop(); } else { newScrollTop = std::min( diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 0b0f7458d..f07fe091d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -280,6 +280,16 @@ bool Element::isUnderCursor() const { return _delegate->elementUnderCursor(this); } +bool Element::isLastAndSelfMessage() const { + if (!hasOutLayout()) { + return false; + } + if (const auto last = data()->_history->lastMessage()) { + return last == data(); + } + return false; +} + void Element::setPendingResize() { _flags |= Flag::NeedsResize; if (_context == Context::History) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index ced627482..b1226419f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -158,6 +158,8 @@ public: bool pendingResize() const; bool isUnderCursor() const; + bool isLastAndSelfMessage() const; + bool isAttachedToPrevious() const; bool isAttachedToNext() const; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index b9678e3cf..f0741d4a0 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -896,7 +896,7 @@ void TopBarWidget::updateOnlineDisplay() { } } else if (const auto channel = _activeChat.peer()->asChannel()) { if (channel->isMegagroup() && channel->membersCount() > 0 && channel->membersCount() <= Global::ChatSizeMax()) { - if (channel->mgInfo->lastParticipants.empty() || channel->lastParticipantsCountOutdated()) { + if (channel->lastParticipantsRequestNeeded()) { session().api().requestLastParticipants(channel); } const auto self = session().user(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index f063d9f92..2159b0700 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1180,7 +1180,7 @@ void Gif::validateGroupedCache( const auto height = geometry.height(); const auto options = Option::Smooth | Option::RoundedLarge - | (blur ? Option(0) : Option::Blurred) + | (blur ? Option::Blurred : Option(0)) | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) @@ -1494,7 +1494,8 @@ bool Gif::dataLoaded() const { bool Gif::needInfoDisplay() const { return _parent->data()->isSending() || _data->uploading() - || _parent->isUnderCursor(); + || _parent->isUnderCursor() + || _parent->isLastAndSelfMessage(); } bool Gif::needCornerStatusDisplay() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 166ab327e..a754e985d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -464,7 +464,9 @@ bool GroupedMedia::computeNeedBubble() const { } bool GroupedMedia::needInfoDisplay() const { - return (_parent->data()->id < 0 || _parent->isUnderCursor()); + return (_parent->data()->id < 0 + || _parent->isUnderCursor() + || _parent->isLastAndSelfMessage()); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index dab25d4b1..5e246181a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -435,6 +435,7 @@ bool UnwrappedMedia::needInfoDisplay() const { return (_parent->data()->id < 0) || (_parent->isUnderCursor()) || (_parent->displayRightAction()) + || (_parent->isLastAndSelfMessage()) || (_parent->hasOutLayout() && !Adaptive::ChatWide() && _content->alwaysShowOutTimestamp()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 6ff29e3ab..938b2d74c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -523,7 +523,9 @@ bool Photo::dataLoaded() const { } bool Photo::needInfoDisplay() const { - return (_parent->data()->id < 0 || _parent->isUnderCursor()); + return (_parent->data()->id < 0 + || _parent->isUnderCursor() + || _parent->isLastAndSelfMessage()); } void Photo::validateGroupedCache( diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index c1234c4da..2ff60a1be 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -82,7 +82,7 @@ bool PlaybackErrorHappened() { void EnumeratePlaybackDevices() { auto deviceNames = QStringList(); - auto devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + auto devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); Assert(devices != nullptr); while (*devices != 0) { auto deviceName8Bit = QByteArray(devices); @@ -92,7 +92,7 @@ void EnumeratePlaybackDevices() { } LOG(("Audio Playback Devices: %1").arg(deviceNames.join(';'))); - if (auto device = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER)) { + if (auto device = alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER)) { LOG(("Audio Playback Default Device: %1").arg(QString::fromLocal8Bit(device))); } else { LOG(("Audio Playback Default Device: (null)")); diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index c3b0dbd68..06b4ae259 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -62,16 +62,12 @@ Instance::Instance() : _inner(new Inner(&_thread)) { void Instance::check() { _available = false; - if (auto defaultDevice = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) { - if (auto device = alcCaptureOpenDevice(defaultDevice, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5)) { - auto error = ErrorHappened(device); - alcCaptureCloseDevice(device); - _available = !error; - } else { - LOG(("Audio Error: Could not open capture device!")); - } + if (auto device = alcCaptureOpenDevice(nullptr, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5)) { + auto error = ErrorHappened(device); + alcCaptureCloseDevice(device); + _available = !error; } else { - LOG(("Audio Error: No capture device found!")); + LOG(("Audio Error: Could not open capture device!")); } } @@ -177,9 +173,7 @@ void Instance::Inner::onInit() { void Instance::Inner::onStart() { // Start OpenAL Capture - const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); - DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName)); - d->device = alcCaptureOpenDevice(dName, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5); + d->device = alcCaptureOpenDevice(nullptr, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5); if (!d->device) { LOG(("Audio Error: capture device not present!")); emit error(); diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp index 2ec424f6c..088fc015a 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp @@ -7,10 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/details/mtproto_dump_to_text.h" -#include "base/zlib_help.h" #include "scheme-dump_to_text.h" #include "scheme.h" +#include "zlib.h" + namespace MTP::details { bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons) { diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 513b22df3..ea1c081e0 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -13,41 +13,60 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_desktop_environment.h" #include "platform/platform_notifications_manager.h" #include "history/history.h" +#include "history/history_widget.h" +#include "history/history_inner_widget.h" +#include "main/main_account.h" // Account::sessionChanges. #include "mainwindow.h" #include "core/application.h" #include "core/sandbox.h" +#include "boxes/peer_list_controllers.h" +#include "boxes/about_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" +#include "window/window_session_controller.h" +#include "ui/widgets/input_fields.h" #include "facades.h" #include "app.h" #include +#include #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -#include +#include +#include +#include +#include +#include +#include +#include +#include #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION namespace Platform { namespace { constexpr auto kDisableTrayCounter = "TDESKTOP_DISABLE_TRAY_COUNTER"_cs; -constexpr auto kTrayIconName = "kotatogram"_cs; +constexpr auto kForcePanelIcon = "TDESKTOP_FORCE_PANEL_ICON"_cs; constexpr auto kPanelTrayIconName = "kotatogram-panel"_cs; constexpr auto kMutePanelTrayIconName = "kotatogram-mute-panel"_cs; constexpr auto kAttentionPanelTrayIconName = "kotatogram-attention-panel"_cs; constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs; +constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs; constexpr auto kTrayIconFilename = "kdesktop-trayicon-XXXXXX.png"_cs; +constexpr auto kAppMenuService = "com.canonical.AppMenu.Registrar"_cs; +constexpr auto kAppMenuObjectPath = "/com/canonical/AppMenu/Registrar"_cs; +constexpr auto kAppMenuInterface = kAppMenuService; + bool TrayIconMuted = true; int32 TrayIconCount = 0; base::flat_map TrayIconImageBack; QIcon TrayIcon; QString TrayIconThemeName, TrayIconName; -QString GetPanelIconName() { - const auto counter = Core::App().unreadBadge(); - const auto muted = Core::App().unreadBadgeMuted(); +bool SNIAvailable = false; +QString GetPanelIconName(int counter, bool muted) { return (counter > 0) ? (muted ? kMutePanelTrayIconName.utf16() @@ -55,220 +74,232 @@ QString GetPanelIconName() { : kPanelTrayIconName.utf16(); } -QString GetTrayIconName() { - const auto panelIconName = GetPanelIconName(); +QString GetTrayIconName(int counter, bool muted) { + const auto iconName = GetIconName(); + const auto panelIconName = GetPanelIconName(counter, muted); 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(); + } else if (QIcon::hasThemeIcon(iconName)) { + return iconName; } 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) +int GetCounterSlice(int counter) { + return (counter >= 1000) ? (1000 + (counter % 100)) : counter; +} - if (TrayIcon.isNull() +bool IsIconRegenerationNeeded( + int counter, + bool muted, + const QString &iconThemeName = QIcon::themeName()) { + const auto iconName = GetTrayIconName(counter, muted); + const auto counterSlice = GetCounterSlice(counter); + + return TrayIcon.isNull() || iconThemeName != TrayIconThemeName || iconName != TrayIconName || muted != TrayIconMuted - || counterSlice != TrayIconCount) { - QIcon result; - QIcon systemIcon; - - const auto iconSizes = { - 16, - 22, - 24, - 32, - 48, - 64 - }; - - for (const auto iconSize : iconSizes) { - auto ¤tImageBack = TrayIconImageBack[iconSize]; - const auto desiredSize = QSize(iconSize, iconSize); - - if (currentImageBack.isNull() - || iconThemeName != TrayIconThemeName - || iconName != TrayIconName) { - if (!iconName.isEmpty()) { - if(systemIcon.isNull()) { - systemIcon = QIcon::fromTheme(iconName); - } - - 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 { - QString iconFilename(cWorkingDir() + "tdata/icon.png"); - currentImageBack = App::readImage(iconFilename, nullptr, false); - if (currentImageBack.isNull()) { - currentImageBack = Core::App().logo(); - } - } - - if (currentImageBack.size() != desiredSize) { - currentImageBack = currentImageBack.scaled( - desiredSize, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } - } - - auto iconImage = currentImageBack; - TrayIconMuted = muted; - TrayIconCount = counterSlice; - TrayIconThemeName = iconThemeName; - TrayIconName = iconName; - - if (!qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) - && counter > 0) { - QPainter p(&iconImage); - int32 layerSize = -16; - - if (iconSize >= 48) { - layerSize = -32; - } else if (iconSize >= 36) { - layerSize = -24; - } else if (iconSize >= 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); - } - - result.addPixmap(App::pixmapFromImageInPlace( - std::move(iconImage))); - } - - TrayIcon = result; - } - - return TrayIcon; + || counterSlice != TrayIconCount; } -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 +void UpdateIconRegenerationNeeded( + const QIcon &icon, + int counter, + bool muted, + const QString &iconThemeName) { + const auto iconName = GetTrayIconName(counter, muted); + const auto counterSlice = GetCounterSlice(counter); - return AppIndicator; + TrayIcon = icon; + TrayIconMuted = muted; + TrayIconCount = counterSlice; + TrayIconThemeName = iconThemeName; + TrayIconName = iconName; +} + +QIcon TrayIconGen(int counter, bool muted) { + const auto iconThemeName = QIcon::themeName(); + + if (!IsIconRegenerationNeeded(counter, muted, iconThemeName)) { + return TrayIcon; + } + + const auto iconName = GetTrayIconName(counter, muted); + + if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) + && !iconName.isEmpty()) { + const auto result = QIcon::fromTheme(iconName); + UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName); + return result; + } + + QIcon result; + QIcon systemIcon; + + const auto iconSizes = { + 16, + 22, + 24, + 32, + 48, + 64 + }; + + for (const auto iconSize : iconSizes) { + auto ¤tImageBack = TrayIconImageBack[iconSize]; + const auto desiredSize = QSize(iconSize, iconSize); + + if (currentImageBack.isNull() + || iconThemeName != TrayIconThemeName + || iconName != TrayIconName) { + if (!iconName.isEmpty()) { + if(systemIcon.isNull()) { + systemIcon = QIcon::fromTheme(iconName); + } + + 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 { + QString iconFilename(cWorkingDir() + "tdata/icon.png"); + currentImageBack = App::readImage(iconFilename, nullptr, false); + if (currentImageBack.isNull()) { + currentImageBack = Core::App().logo(); + } + } + + if (currentImageBack.size() != desiredSize) { + currentImageBack = currentImageBack.scaled( + desiredSize, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } + } + + auto iconImage = currentImageBack; + + if (!qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) + && counter > 0) { + QPainter p(&iconImage); + int32 layerSize = -16; + + if (iconSize >= 48) { + layerSize = -32; + } else if (iconSize >= 36) { + layerSize = -24; + } else if (iconSize >= 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); + } + + result.addPixmap(App::pixmapFromImageInPlace( + std::move(iconImage))); + } + + UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName); + + return result; } #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -static bool NeedTrayIconFile() { +bool IsIndicatorApplication() { // 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 = IsAppIndicator(); - return TrayIconFileNeeded; -} + static const auto IndicatorApplication = [&] { + const auto interface = QDBusConnection::sessionBus().interface(); -static inline QString TrayIconFileTemplate() { - static const auto TempFileTemplate = AppRuntimeDirectory() - + kTrayIconFilename.utf16(); - return TempFileTemplate; + const auto ubuntuIndicator = interface->isServiceRegistered( + qsl("com.canonical.indicator.application")); + + const auto ayatanaIndicator = interface->isServiceRegistered( + qsl("org.ayatana.indicator.application")); + + return ubuntuIndicator || ayatanaIndicator; + }(); + + return IndicatorApplication; } std::unique_ptr TrayIconFile( const QIcon &icon, int size, QObject *parent) { + static const auto templateName = AppRuntimeDirectory() + + kTrayIconFilename.utf16(); + auto ret = std::make_unique( - TrayIconFileTemplate(), + templateName, parent); + ret->open(); icon.pixmap(size).save(ret.get()); ret->close(); + return ret; } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION bool IsSNIAvailable() { - static const auto SNIAvailable = [&] { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - QDBusInterface systrayHost( - kSNIWatcherService.utf16(), - qsl("/StatusNotifierWatcher"), - kSNIWatcherService.utf16()); + auto message = QDBusMessage::createMethodCall( + kSNIWatcherService.utf16(), + qsl("/StatusNotifierWatcher"), + kPropertiesInterface.utf16(), + qsl("Get")); - return systrayHost.isValid() - && systrayHost - .property("IsStatusNotifierHostRegistered") - .toBool(); + message.setArguments({ + kSNIWatcherService.utf16(), + qsl("IsStatusNotifierHostRegistered") + }); + + const QDBusReply reply = QDBusConnection::sessionBus().call( + message); + + if (reply.isValid()) { + return reply.value().toBool(); + } else if (reply.error().type() != QDBusError::ServiceUnknown) { + LOG(("SNI Error: %1").arg(reply.error().message())); + } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - return false; - }(); - - return SNIAvailable; + return false; } bool UseUnityCounter() { @@ -292,12 +323,113 @@ quint32 djbStringHash(QString string) { return hash; } +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION +bool AppMenuSupported() { + static const auto Available = QDBusInterface( + kAppMenuService.utf16(), + kAppMenuObjectPath.utf16(), + kAppMenuInterface.utf16()).isValid(); + + return Available; +} + +void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) { + auto message = QDBusMessage::createMethodCall( + kAppMenuService.utf16(), + kAppMenuObjectPath.utf16(), + kAppMenuInterface.utf16(), + qsl("RegisterWindow")); + + message.setArguments({ + winId, + QVariant::fromValue(menuPath) + }); + + QDBusConnection::sessionBus().send(message); +} + +void UnregisterAppMenu(uint winId) { + auto message = QDBusMessage::createMethodCall( + kAppMenuService.utf16(), + kAppMenuObjectPath.utf16(), + kAppMenuInterface.utf16(), + qsl("UnregisterWindow")); + + message.setArguments({ + winId + }); + + QDBusConnection::sessionBus().send(message); +} + +void SendKeySequence( + Qt::Key key, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) { + const auto focused = QApplication::focusWidget(); + if (qobject_cast(focused) + || qobject_cast(focused) + || qobject_cast(focused)) { + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyPress, key, modifiers)); + + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyRelease, key, modifiers)); + } +} + +void ForceDisabled(QAction *action, bool disabled) { + if (action->isEnabled()) { + if (disabled) action->setDisabled(true); + } else if (!disabled) { + action->setDisabled(false); + } +} +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + } // namespace MainWindow::MainWindow(not_null controller) : Window::MainWindow(controller) { } +void MainWindow::initHook() { + SNIAvailable = IsSNIAvailable(); + + const auto trayAvailable = SNIAvailable + || QSystemTrayIcon::isSystemTrayAvailable(); + + LOG(("System tray available: %1").arg(Logs::b(trayAvailable))); + cSetSupportTray(trayAvailable); + +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + auto sniWatcher = new QDBusServiceWatcher( + kSNIWatcherService.utf16(), + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForOwnerChange, + this); + + connect( + sniWatcher, + &QDBusServiceWatcher::serviceOwnerChanged, + this, + &MainWindow::onSNIOwnerChanged); + + connect( + windowHandle(), + &QWindow::visibleChanged, + this, + &MainWindow::onVisibleChanged); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + if (UseUnityCounter()) { + LOG(("Using Unity launcher counter.")); + } else { + LOG(("Not using Unity launcher counter.")); + } +} + bool MainWindow::hasTrayIcon() const { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION return trayIcon || _sniTrayIcon; @@ -307,39 +439,48 @@ bool MainWindow::hasTrayIcon() const { } void MainWindow::psShowTrayMenu() { - if (!IsSNIAvailable()) { - _trayIconMenuXEmbed->popup(QCursor::pos()); - } + _trayIconMenuXEmbed->popup(QCursor::pos()); } void MainWindow::psTrayMenuUpdated() { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (IsSNIAvailable()) { - if (_sniTrayIcon && trayIconMenu) { - _sniTrayIcon->setContextMenu(trayIconMenu); - } + if (_sniTrayIcon && trayIconMenu) { + _sniTrayIcon->setContextMenu(trayIconMenu); } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -void MainWindow::setSNITrayIcon(const QIcon &icon) { - const auto iconName = GetTrayIconName(); +void MainWindow::setSNITrayIcon(int counter, bool muted, bool firstShow) { + const auto iconName = GetTrayIconName(counter, muted); if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) - && !iconName.isEmpty()) { + && (!iconName.isEmpty() + || qEnvironmentVariableIsSet(kForcePanelIcon.utf8()))) { + if (_sniTrayIcon->iconName() == iconName) { + return; + } + _sniTrayIcon->setIconByName(iconName); _sniTrayIcon->setToolTipIconByName(iconName); - } else if (NeedTrayIconFile()) { + } else if (IsIndicatorApplication()) { + if(!IsIconRegenerationNeeded(counter, muted) && !firstShow) { + return; + } + + const auto icon = TrayIconGen(counter, muted); _trayIconFile = TrayIconFile(icon, 22, this); - _trayToolTipIconFile = TrayIconFile(icon, 48, this); if (_trayIconFile) { + // indicator-application doesn't support tooltips _sniTrayIcon->setIconByName(_trayIconFile->fileName()); - _sniTrayIcon->setToolTipIconByName( - _trayToolTipIconFile->fileName()); } } else { + if(!IsIconRegenerationNeeded(counter, muted) && !firstShow) { + return; + } + + const auto icon = TrayIconGen(counter, muted); _sniTrayIcon->setIconByPixmap(icon); _sniTrayIcon->setToolTipIconByPixmap(icon); } @@ -365,10 +506,51 @@ void MainWindow::attachToSNITrayIcon() { }); updateTrayMenu(); } + +void MainWindow::onSNIOwnerChanged( + const QString &service, + const QString &oldOwner, + const QString &newOwner) { + if (oldOwner.isEmpty() && !newOwner.isEmpty()) { + LOG(("Switching to SNI tray icon...")); + } else if (!oldOwner.isEmpty() && newOwner.isEmpty()) { + LOG(("Switching to Qt tray icon...")); + } else { + return; + } + + if (_sniTrayIcon) { + _sniTrayIcon->setContextMenu(0); + _sniTrayIcon->deleteLater(); + } + _sniTrayIcon = nullptr; + + if (trayIcon) { + trayIcon->setContextMenu(0); + trayIcon->deleteLater(); + } + trayIcon = nullptr; + + SNIAvailable = IsSNIAvailable(); + + const auto trayAvailable = SNIAvailable + || QSystemTrayIcon::isSystemTrayAvailable(); + + cSetSupportTray(trayAvailable); + + if(cSupportTray()) { + psSetupTrayIcon(); + } else { + LOG(("System tray is not available.")); + } +} #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION void MainWindow::psSetupTrayIcon() { - if (IsSNIAvailable()) { + const auto counter = Core::App().unreadBadge(); + const auto muted = Core::App().unreadBadgeMuted(); + + if (SNIAvailable) { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION LOG(("Using SNI tray icon.")); if (!_sniTrayIcon) { @@ -377,7 +559,7 @@ void MainWindow::psSetupTrayIcon() { this); _sniTrayIcon->setTitle(AppName.utf16()); - setSNITrayIcon(TrayIconGen()); + setSNITrayIcon(counter, muted, true); attachToSNITrayIcon(); } @@ -385,15 +567,9 @@ void MainWindow::psSetupTrayIcon() { #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } else { LOG(("Using Qt tray icon.")); - - if (!_trayIconMenuXEmbed) { - _trayIconMenuXEmbed = new Ui::PopupMenu(nullptr, trayIconMenu); - _trayIconMenuXEmbed->deleteOnHide(false); - } - if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); - trayIcon->setIcon(TrayIconGen()); + trayIcon->setIcon(TrayIconGen(counter, muted)); attachToTrayIcon(trayIcon); } @@ -407,21 +583,19 @@ void MainWindow::workmodeUpdated(DBIWorkMode mode) { if (!cSupportTray()) return; if (mode == dbiwmWindowOnly) { - if (IsSNIAvailable()) { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (_sniTrayIcon) { - _sniTrayIcon->setContextMenu(0); - _sniTrayIcon->deleteLater(); - } - _sniTrayIcon = 0; -#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - } else { - if (trayIcon) { - trayIcon->setContextMenu(0); - trayIcon->deleteLater(); - } - trayIcon = 0; + if (_sniTrayIcon) { + _sniTrayIcon->setContextMenu(0); + _sniTrayIcon->deleteLater(); } + _sniTrayIcon = nullptr; +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + if (trayIcon) { + trayIcon->setContextMenu(0); + trayIcon->deleteLater(); + } + trayIcon = nullptr; } else { psSetupTrayIcon(); } @@ -433,11 +607,13 @@ void MainWindow::unreadCounterChangedHook() { } void MainWindow::updateIconCounters() { + const auto counter = Core::App().unreadBadge(); + const auto muted = Core::App().unreadBadgeMuted(); + updateWindowIcon(); #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION if (UseUnityCounter()) { - const auto counter = Core::App().unreadBadge(); const auto launcherUrl = "application://" + GetLauncherFilename(); QVariantMap dbusUnityProperties; if (counter > 0) { @@ -460,16 +636,14 @@ void MainWindow::updateIconCounters() { signal << dbusUnityProperties; QDBusConnection::sessionBus().send(signal); } + + if (_sniTrayIcon) { + setSNITrayIcon(counter, muted); + } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - if (IsSNIAvailable()) { -#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (_sniTrayIcon) { - setSNITrayIcon(TrayIconGen()); - } -#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - } else if (trayIcon) { - trayIcon->setIcon(TrayIconGen()); + if (trayIcon && IsIconRegenerationNeeded(counter, muted)) { + trayIcon->setIcon(TrayIconGen(counter, muted)); } } @@ -482,22 +656,341 @@ void MainWindow::LibsLoaded() { } void MainWindow::initTrayMenuHook() { - const auto trayAvailable = IsSNIAvailable() - || QSystemTrayIcon::isSystemTrayAvailable(); + _trayIconMenuXEmbed = new Ui::PopupMenu(nullptr, trayIconMenu); + _trayIconMenuXEmbed->deleteOnHide(false); +} - LOG(("System tray available: %1").arg(Logs::b(trayAvailable))); - cSetSupportTray(trayAvailable); +#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (UseUnityCounter()) { - LOG(("Using Unity launcher counter.")); - } else { - LOG(("Not using Unity launcher counter.")); +void MainWindow::createGlobalMenu() { +} + +void MainWindow::updateGlobalMenuHook() { +} + +#else // TDESKTOP_DISABLE_DBUS_INTEGRATION + +void MainWindow::createGlobalMenu() { + if (!AppMenuSupported()) return; + + psMainMenu = new QMenu(this); + + auto file = psMainMenu->addMenu(tr::lng_mac_menu_file(tr::now)); + + psLogout = file->addAction( + tr::lng_mac_menu_logout(tr::now), + App::wnd(), + SLOT(onLogout())); + + auto quit = file->addAction( + tr::lng_mac_menu_quit_telegram(tr::now, lt_telegram, qsl("Telegram")), + App::wnd(), + SLOT(quitFromTray()), + QKeySequence::Quit); + + quit->setMenuRole(QAction::QuitRole); + + auto edit = psMainMenu->addMenu(tr::lng_mac_menu_edit(tr::now)); + + psUndo = edit->addAction( + tr::lng_linux_menu_undo(tr::now), + this, + SLOT(psLinuxUndo()), + QKeySequence::Undo); + + psRedo = edit->addAction( + tr::lng_linux_menu_redo(tr::now), + this, + SLOT(psLinuxRedo()), + QKeySequence::Redo); + + edit->addSeparator(); + + psCut = edit->addAction( + tr::lng_mac_menu_cut(tr::now), + this, + SLOT(psLinuxCut()), + QKeySequence::Cut); + psCopy = edit->addAction( + tr::lng_mac_menu_copy(tr::now), + this, + SLOT(psLinuxCopy()), + QKeySequence::Copy); + + psPaste = edit->addAction( + tr::lng_mac_menu_paste(tr::now), + this, + SLOT(psLinuxPaste()), + QKeySequence::Paste); + + psDelete = edit->addAction( + tr::lng_mac_menu_delete(tr::now), + this, + SLOT(psLinuxDelete()), + QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); + + edit->addSeparator(); + + psBold = edit->addAction( + tr::lng_menu_formatting_bold(tr::now), + this, + SLOT(psLinuxBold()), + QKeySequence::Bold); + + psItalic = edit->addAction( + tr::lng_menu_formatting_italic(tr::now), + this, + SLOT(psLinuxItalic()), + QKeySequence::Italic); + + psUnderline = edit->addAction( + tr::lng_menu_formatting_underline(tr::now), + this, + SLOT(psLinuxUnderline()), + QKeySequence::Underline); + + psStrikeOut = edit->addAction( + tr::lng_menu_formatting_strike_out(tr::now), + this, + SLOT(psLinuxStrikeOut()), + Ui::kStrikeOutSequence); + + psMonospace = edit->addAction( + tr::lng_menu_formatting_monospace(tr::now), + this, + SLOT(psLinuxMonospace()), + Ui::kMonospaceSequence); + + psClearFormat = edit->addAction( + tr::lng_menu_formatting_clear(tr::now), + this, + SLOT(psLinuxClearFormat()), + Ui::kClearFormatSequence); + + edit->addSeparator(); + + psSelectAll = edit->addAction( + tr::lng_mac_menu_select_all(tr::now), + this, SLOT(psLinuxSelectAll()), + QKeySequence::SelectAll); + + edit->addSeparator(); + + auto prefs = edit->addAction( + tr::lng_mac_menu_preferences(tr::now), + App::wnd(), + SLOT(showSettings()), + QKeySequence(Qt::ControlModifier | Qt::Key_Comma)); + + prefs->setMenuRole(QAction::PreferencesRole); + + auto tools = psMainMenu->addMenu(tr::lng_linux_menu_tools(tr::now)); + + psContacts = tools->addAction( + tr::lng_mac_menu_contacts(tr::now), + crl::guard(this, [=] { + if (isHidden()) { + App::wnd()->showFromTray(); + } + + if (!account().sessionExists()) { + return; + } + + Ui::show( + Box(std::make_unique( + sessionController()), + [](not_null box) { + box->addButton(tr::lng_close(), [box] { + box->closeBox(); + }); + + box->addLeftButton(tr::lng_profile_add_contact(), [] { + App::wnd()->onShowAddContact(); + }); + })); + })); + + psAddContact = tools->addAction( + tr::lng_mac_menu_add_contact(tr::now), + App::wnd(), + SLOT(onShowAddContact())); + + tools->addSeparator(); + + psNewGroup = tools->addAction( + tr::lng_mac_menu_new_group(tr::now), + App::wnd(), + SLOT(onShowNewGroup())); + + psNewChannel = tools->addAction( + tr::lng_mac_menu_new_channel(tr::now), + App::wnd(), + SLOT(onShowNewChannel())); + + auto help = psMainMenu->addMenu(tr::lng_linux_menu_help(tr::now)); + + auto about = help->addAction( + tr::lng_mac_menu_about_telegram( + tr::now, + lt_telegram, + qsl("Telegram")), + [] { + if (App::wnd() && App::wnd()->isHidden()) { + App::wnd()->showFromTray(); + } + + Ui::show(Box()); + }); + + about->setMenuRole(QAction::AboutQtRole); + + _mainMenuPath.setPath(qsl("/MenuBar")); + + _mainMenuExporter = new DBusMenuExporter( + _mainMenuPath.path(), + psMainMenu); + + RegisterAppMenu(winId(), _mainMenuPath); + + updateGlobalMenu(); +} + +void MainWindow::psLinuxUndo() { + SendKeySequence(Qt::Key_Z, Qt::ControlModifier); +} + +void MainWindow::psLinuxRedo() { + SendKeySequence(Qt::Key_Z, Qt::ControlModifier | Qt::ShiftModifier); +} + +void MainWindow::psLinuxCut() { + SendKeySequence(Qt::Key_X, Qt::ControlModifier); +} + +void MainWindow::psLinuxCopy() { + SendKeySequence(Qt::Key_C, Qt::ControlModifier); +} + +void MainWindow::psLinuxPaste() { + SendKeySequence(Qt::Key_V, Qt::ControlModifier); +} + +void MainWindow::psLinuxDelete() { + SendKeySequence(Qt::Key_Delete); +} + +void MainWindow::psLinuxSelectAll() { + SendKeySequence(Qt::Key_A, Qt::ControlModifier); +} + +void MainWindow::psLinuxBold() { + SendKeySequence(Qt::Key_B, Qt::ControlModifier); +} + +void MainWindow::psLinuxItalic() { + SendKeySequence(Qt::Key_I, Qt::ControlModifier); +} + +void MainWindow::psLinuxUnderline() { + SendKeySequence(Qt::Key_U, Qt::ControlModifier); +} + +void MainWindow::psLinuxStrikeOut() { + SendKeySequence(Qt::Key_X, Qt::ControlModifier | Qt::ShiftModifier); +} + +void MainWindow::psLinuxMonospace() { + SendKeySequence(Qt::Key_M, Qt::ControlModifier | Qt::ShiftModifier); +} + +void MainWindow::psLinuxClearFormat() { + SendKeySequence(Qt::Key_N, Qt::ControlModifier | Qt::ShiftModifier); +} + +void MainWindow::updateGlobalMenuHook() { + if (!AppMenuSupported() || !App::wnd() || !positionInited()) return; + + const auto focused = QApplication::focusWidget(); + auto canUndo = false; + auto canRedo = false; + auto canCut = false; + auto canCopy = false; + auto canPaste = false; + auto canDelete = false; + auto canSelectAll = false; + const auto clipboardHasText = QGuiApplication::clipboard() + ->ownsClipboard(); + auto markdownEnabled = false; + if (const auto edit = qobject_cast(focused)) { + canCut = canCopy = canDelete = edit->hasSelectedText(); + canSelectAll = !edit->text().isEmpty(); + canUndo = edit->isUndoAvailable(); + canRedo = edit->isRedoAvailable(); + canPaste = clipboardHasText; + } else if (const auto edit = qobject_cast(focused)) { + canCut = canCopy = canDelete = edit->textCursor().hasSelection(); + canSelectAll = !edit->document()->isEmpty(); + canUndo = edit->document()->isUndoAvailable(); + canRedo = edit->document()->isRedoAvailable(); + canPaste = clipboardHasText; + if (canCopy) { + if (const auto inputField = qobject_cast( + focused->parentWidget())) { + markdownEnabled = inputField->isMarkdownEnabled(); + } + } + } else if (const auto list = qobject_cast(focused)) { + canCopy = list->canCopySelected(); + canDelete = list->canDeleteSelected(); + } + App::wnd()->updateIsActive(0); + const auto logged = account().sessionExists(); + const auto locked = Core::App().locked(); + const auto inactive = !logged || locked; + const auto support = logged && account().session().supportMode(); + ForceDisabled(psLogout, !logged && !locked); + ForceDisabled(psUndo, !canUndo); + ForceDisabled(psRedo, !canRedo); + ForceDisabled(psCut, !canCut); + ForceDisabled(psCopy, !canCopy); + ForceDisabled(psPaste, !canPaste); + ForceDisabled(psDelete, !canDelete); + ForceDisabled(psSelectAll, !canSelectAll); + ForceDisabled(psContacts, inactive || support); + ForceDisabled(psAddContact, inactive); + ForceDisabled(psNewGroup, inactive || support); + ForceDisabled(psNewChannel, inactive || support); + + ForceDisabled(psBold, !markdownEnabled); + ForceDisabled(psItalic, !markdownEnabled); + ForceDisabled(psUnderline, !markdownEnabled); + ForceDisabled(psStrikeOut, !markdownEnabled); + ForceDisabled(psMonospace, !markdownEnabled); + ForceDisabled(psClearFormat, !markdownEnabled); +} + +void MainWindow::onVisibleChanged(bool visible) { + if (AppMenuSupported() && !_mainMenuPath.path().isEmpty()) { + if (visible) { + RegisterAppMenu(winId(), _mainMenuPath); + } else { + UnregisterAppMenu(winId()); + } } } +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + MainWindow::~MainWindow() { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION delete _sniTrayIcon; + + if (AppMenuSupported()) { + UnregisterAppMenu(winId()); + delete _mainMenuExporter; + delete psMainMenu; + } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION delete _trayIconMenuXEmbed; diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index f6eadc5d1..8602c5380 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include "statusnotifieritem.h" #include +#include +#include #endif namespace Platform { @@ -38,13 +40,41 @@ public: public slots: void psShowTrayMenu(); +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + void onSNIOwnerChanged( + const QString &service, + const QString &oldOwner, + const QString &newOwner); + + void psLinuxUndo(); + void psLinuxRedo(); + void psLinuxCut(); + void psLinuxCopy(); + void psLinuxPaste(); + void psLinuxDelete(); + void psLinuxSelectAll(); + + void psLinuxBold(); + void psLinuxItalic(); + void psLinuxUnderline(); + void psLinuxStrikeOut(); + void psLinuxMonospace(); + void psLinuxClearFormat(); + + void onVisibleChanged(bool visible); + +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + protected: + void initHook() override; void unreadCounterChangedHook() override; + void updateGlobalMenuHook() override; void initTrayMenuHook() override; bool hasTrayIcon() const override; void workmodeUpdated(DBIWorkMode mode) override; + void createGlobalMenu() override; QSystemTrayIcon *trayIcon = nullptr; QMenu *trayIconMenu = nullptr; @@ -68,9 +98,32 @@ private: #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION StatusNotifierItem *_sniTrayIcon = nullptr; std::unique_ptr _trayIconFile = nullptr; - std::unique_ptr _trayToolTipIconFile = nullptr; - void setSNITrayIcon(const QIcon &icon); + DBusMenuExporter *_mainMenuExporter = nullptr; + QDBusObjectPath _mainMenuPath; + + QMenu *psMainMenu = nullptr; + QAction *psLogout = nullptr; + QAction *psUndo = nullptr; + QAction *psRedo = nullptr; + QAction *psCut = nullptr; + QAction *psCopy = nullptr; + QAction *psPaste = nullptr; + QAction *psDelete = nullptr; + QAction *psSelectAll = nullptr; + QAction *psContacts = nullptr; + QAction *psAddContact = nullptr; + QAction *psNewGroup = nullptr; + QAction *psNewChannel = nullptr; + + QAction *psBold = nullptr; + QAction *psItalic = nullptr; + QAction *psUnderline = nullptr; + QAction *psStrikeOut = nullptr; + QAction *psMonospace = nullptr; + QAction *psClearFormat = nullptr; + + void setSNITrayIcon(int counter, bool muted, bool firstShow = false); void attachToSNITrayIcon(); #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index b52191ae2..d6ef3efbc 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -7,18 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/notifications_manager_linux.h" +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include "platform/linux/specific_linux.h" #include "history/history.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "facades.h" -#include -#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include +#include #include +#include #include -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION +#include namespace Platform { namespace Notifications { @@ -38,50 +40,101 @@ namespace { constexpr auto kService = "org.freedesktop.Notifications"_cs; constexpr auto kObjectPath = "/org/freedesktop/Notifications"_cs; constexpr auto kInterface = kService; +constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs; -std::vector GetServerInformation( - const std::shared_ptr ¬ificationInterface) { +bool InhibitedNotSupported = false; + +std::vector ComputeServerInformation() { std::vector serverInformation; - const auto serverInformationReply = notificationInterface - ->call(qsl("GetServerInformation")); - if (serverInformationReply.type() == QDBusMessage::ReplyMessage) { - for (const auto &arg : serverInformationReply.arguments()) { - if (static_cast(arg.type()) - == QMetaType::QString) { - serverInformation.push_back(arg.toString()); - } else { - LOG(("Native notification error: " - "all elements in GetServerInformation " - "should be strings")); - } - } - } else if (serverInformationReply.type() == QDBusMessage::ErrorMessage) { - LOG(("Native notification error: %1") - .arg(serverInformationReply.errorMessage())); + const auto message = QDBusMessage::createMethodCall( + kService.utf16(), + kObjectPath.utf16(), + kInterface.utf16(), + qsl("GetServerInformation")); + + const auto reply = QDBusConnection::sessionBus().call(message); + + if (reply.type() == QDBusMessage::ReplyMessage) { + ranges::transform( + reply.arguments(), + ranges::back_inserter(serverInformation), + &QVariant::toString + ); + } else if (reply.type() == QDBusMessage::ErrorMessage) { + LOG(("Native notification error: %1").arg(reply.errorMessage())); } else { LOG(("Native notification error: " - "error while getting information about notification daemon")); + "invalid reply from GetServerInformation")); } return serverInformation; } -QStringList GetCapabilities( - const std::shared_ptr ¬ificationInterface) { - const QDBusReply capabilitiesReply = notificationInterface - ->call(qsl("GetCapabilities")); +std::vector GetServerInformation() { + static const auto ServerInformation = ComputeServerInformation(); + return ServerInformation; +} - if (capabilitiesReply.isValid()) { - return capabilitiesReply.value(); +QStringList ComputeCapabilities() { + const auto message = QDBusMessage::createMethodCall( + kService.utf16(), + kObjectPath.utf16(), + kInterface.utf16(), + qsl("GetCapabilities")); + + const QDBusReply reply = QDBusConnection::sessionBus().call( + message); + + if (reply.isValid()) { + return reply.value(); } else { - LOG(("Native notification error: %1") - .arg(capabilitiesReply.error().message())); + LOG(("Native notification error: %1").arg(reply.error().message())); } return {}; } +QStringList GetCapabilities() { + static const auto Capabilities = ComputeCapabilities(); + return Capabilities; +} + +bool Inhibited() { + auto message = QDBusMessage::createMethodCall( + kService.utf16(), + kObjectPath.utf16(), + kPropertiesInterface.utf16(), + qsl("Get")); + + message.setArguments({ + qsl("org.freedesktop.Notifications"), + qsl("Inhibited") + }); + + const QDBusReply reply = QDBusConnection::sessionBus().call( + message); + + constexpr auto notSupportedErrors = { + QDBusError::ServiceUnknown, + QDBusError::InvalidArgs, + }; + + if (reply.isValid()) { + return reply.value().toBool(); + } else if (ranges::contains(notSupportedErrors, reply.error().type())) { + InhibitedNotSupported = true; + } else { + if (reply.error().type() == QDBusError::AccessDenied) { + InhibitedNotSupported = true; + } + + LOG(("Native notification error: %1").arg(reply.error().message())); + } + + return false; +} + QVersionNumber ParseSpecificationVersion( const std::vector &serverInformation) { if (serverInformation.size() >= 4) { @@ -94,10 +147,31 @@ QVersionNumber ParseSpecificationVersion( return QVersionNumber(); } +QString GetImageKey(const QVersionNumber &specificationVersion) { + if (!specificationVersion.isNull()) { + const auto majorVersion = specificationVersion.majorVersion(); + const auto minorVersion = specificationVersion.minorVersion(); + + if ((majorVersion == 1 && minorVersion >= 2) || majorVersion > 1) { + return qsl("image-data"); + } else if (majorVersion == 1 && minorVersion == 1) { + return qsl("image_data"); + } else if ((majorVersion == 1 && minorVersion < 1) + || majorVersion < 1) { + return qsl("icon_data"); + } else { + LOG(("Native notification error: unknown specification version")); + } + } else { + LOG(("Native notification error: specification version is null")); + } + + return QString(); +} + } NotificationData::NotificationData( - const std::shared_ptr ¬ificationInterface, const base::weak_ptr &manager, const QString &title, const QString &subtitle, @@ -105,12 +179,14 @@ NotificationData::NotificationData( PeerId peerId, MsgId msgId, bool hideReplyButton) -: _notificationInterface(notificationInterface) +: _dbusConnection(QDBusConnection::sessionBus()) , _manager(manager) , _title(title) +, _imageKey(GetImageKey(ParseSpecificationVersion( + GetServerInformation()))) , _peerId(peerId) , _msgId(msgId) { - const auto capabilities = GetCapabilities(_notificationInterface); + const auto capabilities = GetCapabilities(); if (capabilities.contains(qsl("body-markup"))) { _body = subtitle.isEmpty() @@ -127,19 +203,16 @@ NotificationData::NotificationData( if (capabilities.contains(qsl("actions"))) { _actions << qsl("default") << QString(); - _notificationInterface->connection().connect( - kService.utf16(), - kObjectPath.utf16(), - kInterface.utf16(), - qsl("ActionInvoked"), - this, - SLOT(notificationClicked(uint,QString))); + _actions + << qsl("mail-mark-read") + << tr::lng_context_mark_read(tr::now); if (capabilities.contains(qsl("inline-reply")) && !hideReplyButton) { - _actions << qsl("inline-reply") + _actions + << qsl("inline-reply") << tr::lng_notification_reply(tr::now); - _notificationInterface->connection().connect( + _dbusConnection.connect( kService.utf16(), kObjectPath.utf16(), kInterface.utf16(), @@ -148,9 +221,18 @@ NotificationData::NotificationData( SLOT(notificationReplied(uint,QString))); } else { // icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html - _actions << qsl("mail-reply-sender") + _actions + << qsl("mail-reply-sender") << tr::lng_notification_reply(tr::now); } + + _dbusConnection.connect( + kService.utf16(), + kObjectPath.utf16(), + kInterface.utf16(), + qsl("ActionInvoked"), + this, + SLOT(actionInvoked(uint,QString))); } if (capabilities.contains(qsl("action-icons"))) { @@ -172,10 +254,9 @@ NotificationData::NotificationData( } _hints["category"] = qsl("im.received"); - _hints["desktop-entry"] = GetLauncherBasename(); - _notificationInterface->connection().connect( + _dbusConnection.connect( kService.utf16(), kObjectPath.utf16(), kInterface.utf16(), @@ -184,65 +265,56 @@ NotificationData::NotificationData( SLOT(notificationClosed(uint))); } -bool NotificationData::show(bool hideNameAndPhoto) { - const QDBusReply notifyReply = _notificationInterface->call( - qsl("Notify"), +bool NotificationData::show() { + const auto iconName = _imageKey.isEmpty() || !_hints.contains(_imageKey) + ? GetIconName() + : QString(); + + auto message = QDBusMessage::createMethodCall( + kService.utf16(), + kObjectPath.utf16(), + kInterface.utf16(), + qsl("Notify")); + + message.setArguments({ AppName.utf16(), uint(0), - hideNameAndPhoto - ? qsl("telegram") - : QString(), + iconName, _title, _body, _actions, _hints, - -1); + -1 + }); - if (notifyReply.isValid()) { - _notificationId = notifyReply.value(); + const QDBusReply reply = _dbusConnection.call( + message); + + if (reply.isValid()) { + _notificationId = reply.value(); } else { - LOG(("Native notification error: %1") - .arg(notifyReply.error().message())); + LOG(("Native notification error: %1").arg(reply.error().message())); } - return notifyReply.isValid(); + return reply.isValid(); } -bool NotificationData::close() { - const QDBusReply closeReply = _notificationInterface - ->call(qsl("CloseNotification"), _notificationId); +void NotificationData::close() { + auto message = QDBusMessage::createMethodCall( + kService.utf16(), + kObjectPath.utf16(), + kInterface.utf16(), + qsl("CloseNotification")); - if (!closeReply.isValid()) { - LOG(("Native notification error: %1") - .arg(closeReply.error().message())); - } + message.setArguments({ + _notificationId + }); - return closeReply.isValid(); + _dbusConnection.send(message); } void NotificationData::setImage(const QString &imagePath) { - const auto specificationVersion = ParseSpecificationVersion( - GetServerInformation(_notificationInterface)); - - QString imageKey; - - if (!specificationVersion.isNull()) { - const auto majorVersion = specificationVersion.majorVersion(); - const auto minorVersion = specificationVersion.minorVersion(); - - if ((majorVersion == 1 && minorVersion >= 2) || majorVersion > 1) { - imageKey = qsl("image-data"); - } else if (majorVersion == 1 && minorVersion == 1) { - imageKey = qsl("image_data"); - } else if ((majorVersion == 1 && minorVersion < 1) - || majorVersion < 1) { - imageKey = qsl("icon_data"); - } else { - LOG(("Native notification error: unknown specification version")); - return; - } - } else { - LOG(("Native notification error: specification version is null")); + if (_imageKey.isEmpty()) { return; } @@ -253,20 +325,21 @@ void NotificationData::setImage(const QString &imagePath) { (const char*)image.constBits(), #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) image.byteCount()); -#else +#else // Qt < 5.10.0 image.sizeInBytes()); -#endif +#endif // Qt >= 5.10.0 - ImageData imageData; - imageData.width = image.width(); - imageData.height = image.height(); - imageData.rowStride = image.bytesPerLine(); - imageData.hasAlpha = true; - imageData.bitsPerSample = 8; - imageData.channels = 4; - imageData.data = imageBytes; + const auto imageData = ImageData{ + image.width(), + image.height(), + image.bytesPerLine(), + true, + 8, + 4, + imageBytes + }; - _hints[imageKey] = QVariant::fromValue(imageData); + _hints[_imageKey] = QVariant::fromValue(imageData); } void NotificationData::notificationClosed(uint id) { @@ -278,19 +351,23 @@ void NotificationData::notificationClosed(uint id) { } } -void NotificationData::notificationClicked(uint id, const QString &actionId) { +void NotificationData::actionInvoked(uint id, const QString &actionName) { if (id != _notificationId) { return; } - if (actionId != qsl("default") && actionId != qsl("mail-reply-sender")) { - return; + if (actionName == qsl("default") + || actionName == qsl("mail-reply-sender")) { + const auto manager = _manager; + crl::on_main(manager, [=] { + manager->notificationActivated(_peerId, _msgId); + }); + } else if (actionName == qsl("mail-mark-read")) { + const auto manager = _manager; + crl::on_main(manager, [=] { + manager->notificationReplied(_peerId, _msgId, {}); + }); } - - const auto manager = _manager; - crl::on_main(manager, [=] { - manager->notificationActivated(_peerId, _msgId); - }); } void NotificationData::notificationReplied(uint id, const QString &text) { @@ -306,7 +383,8 @@ QDBusArgument &operator<<( QDBusArgument &argument, const NotificationData::ImageData &imageData) { argument.beginStructure(); - argument << imageData.width + argument + << imageData.width << imageData.height << imageData.rowStride << imageData.hasAlpha @@ -321,7 +399,8 @@ const QDBusArgument &operator>>( const QDBusArgument &argument, NotificationData::ImageData &imageData) { argument.beginStructure(); - argument >> imageData.width + argument + >> imageData.width >> imageData.height >> imageData.rowStride >> imageData.hasAlpha @@ -331,20 +410,39 @@ const QDBusArgument &operator>>( argument.endStructure(); return argument; } -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + +bool SkipAudio() { +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + if (Supported() + && GetCapabilities().contains(qsl("inhibitions")) + && !InhibitedNotSupported) { + return Inhibited(); + } +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + return false; +} + +bool SkipToast() { +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + if (Supported() + && GetCapabilities().contains(qsl("inhibitions")) + && !InhibitedNotSupported) { + return Inhibited(); + } +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + return false; +} bool Supported() { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - static const auto Available = QDBusInterface( - kService.utf16(), - kObjectPath.utf16(), - kInterface.utf16() - ).isValid(); - + static const auto Available = !GetServerInformation().empty(); return Available; -#else +#else // !TDESKTOP_DISABLE_DBUS_INTEGRATION return false; -#endif +#endif // TDESKTOP_DISABLE_DBUS_INTEGRATION } std::unique_ptr Create( @@ -353,34 +451,36 @@ std::unique_ptr Create( if (Global::NativeNotifications() && Supported()) { return std::make_unique(system); } -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION return nullptr; } #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -Manager::Private::Private(Manager *manager, Type type) +Manager::Private::Private(not_null manager, Type type) : _cachedUserpics(type) -, _manager(manager) -, _notificationInterface( - std::make_shared( - kService.utf16(), - kObjectPath.utf16(), - kInterface.utf16())) { +, _manager(manager) { qDBusRegisterMetaType(); - const auto specificationVersion = ParseSpecificationVersion( - GetServerInformation(_notificationInterface)); + const auto serverInformation = GetServerInformation(); + const auto capabilities = GetCapabilities(); - const auto capabilities = GetCapabilities(_notificationInterface); + if (!serverInformation.empty()) { + LOG(("Notification daemon product name: %1") + .arg(serverInformation[0])); + + LOG(("Notification daemon vendor name: %1") + .arg(serverInformation[1])); + + LOG(("Notification daemon version: %1") + .arg(serverInformation[2])); - if (!specificationVersion.isNull()) { LOG(("Notification daemon specification version: %1") - .arg(specificationVersion.toString())); + .arg(serverInformation[3])); } - if (!capabilities.empty()) { - const auto capabilitiesString = capabilities.join(", "); - LOG(("Notification daemon capabilities: %1").arg(capabilitiesString)); + if (!capabilities.isEmpty()) { + LOG(("Notification daemon capabilities: %1") + .arg(capabilities.join(", "))); } } @@ -393,7 +493,6 @@ void Manager::Private::showNotification( bool hideNameAndPhoto, bool hideReplyButton) { auto notification = std::make_shared( - _notificationInterface, _manager, title, subtitle, @@ -421,7 +520,7 @@ void Manager::Private::showNotification( i = _notifications.insert(peer->id, QMap()); } _notifications[peer->id].insert(msgId, notification); - if (!notification->show(hideNameAndPhoto)) { + if (!notification->show()) { i = _notifications.find(peer->id); if (i != _notifications.cend()) { i->remove(msgId); @@ -465,7 +564,7 @@ Manager::Private::~Private() { clearAll(); } -Manager::Manager(Window::Notifications::System *system) +Manager::Manager(not_null system) : NativeManager(system) , _private(std::make_unique(this, Private::Type::Rounded)) { } @@ -501,7 +600,7 @@ void Manager::doClearAllFast() { void Manager::doClearFromHistory(not_null history) { _private->clearFromHistory(history); } -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } // namespace Notifications } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index dcda340bf..1005c0403 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -12,28 +12,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -#include +#include #include -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION namespace Platform { namespace Notifications { -inline bool SkipAudio() { - return false; -} - -inline bool SkipToast() { - return false; -} - #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION class NotificationData : public QObject { Q_OBJECT public: NotificationData( - const std::shared_ptr ¬ificationInterface, const base::weak_ptr &manager, const QString &title, const QString &subtitle, @@ -47,8 +38,8 @@ public: NotificationData(NotificationData &&other) = delete; NotificationData &operator=(NotificationData &&other) = delete; - bool show(bool hideNameAndPhoto); - bool close(); + bool show(); + void close(); void setImage(const QString &imagePath); struct ImageData { @@ -59,13 +50,14 @@ public: }; private: - std::shared_ptr _notificationInterface; + QDBusConnection _dbusConnection; base::weak_ptr _manager; QString _title; QString _body; QStringList _actions; QVariantMap _hints; + QString _imageKey; uint _notificationId; PeerId _peerId; @@ -73,7 +65,7 @@ private: private slots: void notificationClosed(uint id); - void notificationClicked(uint id, const QString &actionId); + void actionInvoked(uint id, const QString &actionName); void notificationReplied(uint id, const QString &text); }; @@ -91,7 +83,7 @@ class Manager : public Window::Notifications::NativeManager , public base::has_weak_ptr { public: - Manager(Window::Notifications::System *system); + Manager(not_null system); void clearNotification(PeerId peerId, MsgId msgId); ~Manager(); @@ -116,7 +108,7 @@ private: class Manager::Private { public: using Type = Window::Notifications::CachedUserpics::Type; - explicit Private(Manager *manager, Type type); + explicit Private(not_null manager, Type type); void showNotification( not_null peer, @@ -138,13 +130,12 @@ private: Window::Notifications::CachedUserpics _cachedUserpics; base::weak_ptr _manager; - std::shared_ptr _notificationInterface; }; -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } // namespace Notifications } // namespace Platform #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION Q_DECLARE_METATYPE(Platform::Notifications::NotificationData::ImageData) -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index c1b80e28a..ed3dde713 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -26,7 +26,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include -#endif +#include +#include +#include +#include +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION #include #include @@ -44,9 +48,10 @@ namespace { constexpr auto kDesktopFile = ":/misc/kotatogramdesktop.desktop"_cs; constexpr auto kSnapLauncherDir = "/var/lib/snapd/desktop/applications/"_cs; +constexpr auto kIconName = "telegram"_cs; #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -void SandboxAutostart(bool autostart) { +void SandboxAutostart(bool autostart, bool silent = false) { QVariantMap options; options["reason"] = tr::lng_settings_auto_start(tr::now); options["autostart"] = autostart; @@ -56,20 +61,29 @@ void SandboxAutostart(bool autostart) { }); options["dbus-activatable"] = false; - const auto requestBackgroundReply = QDBusInterface( + auto message = QDBusMessage::createMethodCall( qsl("org.freedesktop.portal.Desktop"), qsl("/org/freedesktop/portal/desktop"), - qsl("org.freedesktop.portal.Background") - ).call(qsl("RequestBackground"), QString(), options); + qsl("org.freedesktop.portal.Background"), + qsl("RequestBackground")); - if (requestBackgroundReply.type() == QDBusMessage::ErrorMessage) { - LOG(("Flatpak autostart error: %1") - .arg(requestBackgroundReply.errorMessage())); - } else if (requestBackgroundReply.type() != QDBusMessage::ReplyMessage) { - LOG(("Flatpak autostart error: invalid reply")); + message.setArguments({ + QString(), + options + }); + + if (silent) { + QDBusConnection::sessionBus().send(message); + } else { + const QDBusReply reply = QDBusConnection::sessionBus().call( + message); + + if (!reply.isValid()) { + LOG(("Flatpak autostart error: %1").arg(reply.error().message())); + } } } -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION bool RunShellCommand(const QByteArray &command) { auto result = system(command.constData()); @@ -120,7 +134,10 @@ void FallbackFontConfig() { #endif // TDESKTOP_USE_FONT_CONFIG_FALLBACK } -bool GenerateDesktopFile(const QString &targetPath, const QString &args) { +bool GenerateDesktopFile( + const QString &targetPath, + const QString &args, + bool silent = false) { DEBUG_LOG(("App Info: placing .desktop file to %1").arg(targetPath)); if (!QDir(targetPath).exists()) QDir().mkpath(targetPath); @@ -142,7 +159,9 @@ bool GenerateDesktopFile(const QString &targetPath, const QString &args) { fileText = s.readAll(); source.close(); } else { - LOG(("App Error: Could not open '%1' for read").arg(sourceFile)); + if (!silent) { + LOG(("App Error: Could not open '%1' for read").arg(sourceFile)); + } return false; } @@ -150,23 +169,28 @@ bool GenerateDesktopFile(const QString &targetPath, const QString &args) { if (target.open(QIODevice::WriteOnly)) { #ifdef DESKTOP_APP_USE_PACKAGED fileText = fileText.replace( - QRegularExpression(qsl("^Exec=(.*) -- %u$"), + QRegularExpression( + qsl("^Exec=(.*) -- %u$"), QRegularExpression::MultilineOption), qsl("Exec=\\1") + (args.isEmpty() ? QString() : ' ' + args)); -#else +#else // DESKTOP_APP_USE_PACKAGED fileText = fileText.replace( - QRegularExpression(qsl("^TryExec=.*$"), + QRegularExpression( + qsl("^TryExec=.*$"), QRegularExpression::MultilineOption), qsl("TryExec=") - + EscapeShell(QFile::encodeName(cExeDir() + cExeName()))); + + QFile::encodeName(cExeDir() + cExeName()) + .replace('\\', qsl("\\\\"))); fileText = fileText.replace( - QRegularExpression(qsl("^Exec=.*$"), + QRegularExpression( + qsl("^Exec=.*$"), QRegularExpression::MultilineOption), qsl("Exec=") + EscapeShell(QFile::encodeName(cExeDir() + cExeName())) + .replace('\\', qsl("\\\\")) + (args.isEmpty() ? QString() : ' ' + args)); -#endif +#endif // !DESKTOP_APP_USE_PACKAGED target.write(fileText.toUtf8()); target.close(); @@ -175,7 +199,9 @@ bool GenerateDesktopFile(const QString &targetPath, const QString &args) { return true; } else { - LOG(("App Error: Could not open '%1' for write").arg(targetFile)); + if (!silent) { + LOG(("App Error: Could not open '%1' for write").arg(targetFile)); + } return false; } } @@ -189,9 +215,7 @@ void SetApplicationIcon(const QIcon &icon) { } bool InSandbox() { - static const auto Sandbox = QFileInfo::exists( - QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) - + qsl("/flatpak-info")); + static const auto Sandbox = QFileInfo::exists(qsl("/.flatpak-info")); return Sandbox; } @@ -208,12 +232,18 @@ bool IsXDGDesktopPortalPresent() { "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(); + static const auto UsePortal = [&] { + const auto envVar = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL"); + const auto portalPresent = IsXDGDesktopPortalPresent(); + + return envVar && portalPresent; + }(); + return UsePortal; } @@ -309,7 +339,6 @@ QString GetLauncherBasename() { return possibleBasenames[0]; }(); - LOG(("Launcher filename is %1.desktop").arg(LauncherBasename)); return LauncherBasename; } @@ -319,6 +348,55 @@ QString GetLauncherFilename() { return LauncherFilename; } +QString GetIconName() { + static const auto IconName = InSandbox() + ? GetLauncherBasename() + : kIconName.utf16(); + return IconName; +} + +std::optional LastUserInputTime() { + // TODO: a fallback pure-X11 implementation, this one covers only major DEs on X11 and Wayland + // an example: https://stackoverflow.com/q/9049087 +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + static auto NotSupported = false; + + if (NotSupported) { + return std::nullopt; + } + + static const auto message = QDBusMessage::createMethodCall( + qsl("org.freedesktop.ScreenSaver"), + qsl("/org/freedesktop/ScreenSaver"), + qsl("org.freedesktop.ScreenSaver"), + qsl("GetSessionIdleTime")); + + const QDBusReply reply = QDBusConnection::sessionBus().call( + message); + + constexpr auto notSupportedErrors = { + QDBusError::ServiceUnknown, + QDBusError::NotSupported, + }; + + if (reply.isValid()) { + return (crl::now() - static_cast(reply.value())); + } else if (ranges::contains(notSupportedErrors, reply.error().type())) { + NotSupported = true; + } else { + if (reply.error().type() == QDBusError::AccessDenied) { + NotSupported = true; + } + + LOG(("Unable to get last user input time: %1: %2") + .arg(reply.error().name()) + .arg(reply.error().message())); + } +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + return std::nullopt; +} + } // namespace Platform namespace { @@ -395,15 +473,18 @@ QString getHomeDir() { QString psAppDataPath() { // We should not use ~/.TelegramDesktop, since it's a fork. - - // auto home = getHomeDir(); - // if (!home.isEmpty()) { - // auto oldPath = home + qsl(".TelegramDesktop/"); - // auto oldSettingsBase = oldPath + qsl("tdata/settings"); - // if (QFile(oldSettingsBase + '0').exists() || QFile(oldSettingsBase + '1').exists()) { - // return oldPath; - // } - // } + /* + auto home = getHomeDir(); + if (!home.isEmpty()) { + auto oldPath = home + qsl(".TelegramDesktop/"); + auto oldSettingsBase = oldPath + qsl("tdata/settings"); + if (QFile(oldSettingsBase + '0').exists() + || QFile(oldSettingsBase + '1').exists() + || QFile(oldSettingsBase + 's').exists()) { + return oldPath; + } + } + */ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/'; } @@ -432,8 +513,12 @@ int psFixPrevious() { namespace Platform { void start() { + LOG(("Launcher filename: %1").arg(GetLauncherFilename())); FallbackFontConfig(); + qputenv("PULSE_PROP_application.name", AppName.utf8()); + qputenv("PULSE_PROP_application.icon_name", GetIconName().toLatin1()); + #ifdef TDESKTOP_FORCE_GTK_FILE_DIALOG LOG(("Checking for XDG Desktop Portal...")); // this can give us a chance to use a proper file dialog for current session @@ -454,24 +539,29 @@ void start() { void finish() { } -void RegisterCustomScheme() { +void RegisterCustomScheme(bool force) { #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME - auto home = getHomeDir(); - if (home.isEmpty() || cAlphaVersion() || cExeName().isEmpty()) - return; // don't update desktop file for alpha version - if (Core::UpdaterDisabled()) + const auto home = getHomeDir(); + if (home.isEmpty() || cExeName().isEmpty()) + return; + + static const auto disabledByEnv = qEnvironmentVariableIsSet( + "TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION"); + + // don't update desktop file for alpha version or if updater is disabled + if ((cAlphaVersion() || Core::UpdaterDisabled() || disabledByEnv) + && !force) return; const auto applicationsPath = QStandardPaths::writableLocation( QStandardPaths::ApplicationsLocation) + '/'; -#ifndef TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION GenerateDesktopFile(applicationsPath, qsl("-- %u")); const auto icons = QStandardPaths::writableLocation( QStandardPaths::GenericDataLocation) - + qsl("/icons/"); + + qsl("/icons/"); if (!QDir(icons).exists()) QDir().mkpath(icons); @@ -485,10 +575,9 @@ void RegisterCustomScheme() { } if (!iconExists) { if (QFile(qsl(":/gui/art/logo_256.png")).copy(icon)) { - DEBUG_LOG(("App Info: Icon copied to 'tdata'")); + DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon)); } } -#endif // !TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION RunShellCommand("update-desktop-database " + EscapeShell(QFile::encodeName(applicationsPath))); @@ -559,14 +648,14 @@ bool psShowOpenWithMenu(int x, int y, const QString &file) { } void psAutoStart(bool start, bool silent) { - auto home = getHomeDir(); - if (home.isEmpty() || cAlphaVersion() || cExeName().isEmpty()) + const auto home = getHomeDir(); + if (home.isEmpty() || cExeName().isEmpty()) return; if (InSandbox()) { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - SandboxAutostart(start); -#endif + SandboxAutostart(start, silent); +#endif // !DESKTOP_APP_USE_PACKAGED } else { const auto autostart = [&] { if (InSnap()) { @@ -578,12 +667,12 @@ void psAutoStart(bool start, bool silent) { } else { return QStandardPaths::writableLocation( QStandardPaths::GenericConfigLocation) - + qsl("/autostart/"); + + qsl("/autostart/"); } }(); if (start) { - GenerateDesktopFile(autostart, qsl("-autostart")); + GenerateDesktopFile(autostart, qsl("-autostart"), silent); } else { QFile::remove(autostart + GetLauncherFilename()); } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index f878dac71..7f18032cb 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -35,9 +35,7 @@ QString SingleInstanceLocalServerName(const QString &hash); QString GetLauncherBasename(); QString GetLauncherFilename(); -inline std::optional LastUserInputTime() { - return std::nullopt; -} +QString GetIconName(); inline void IgnoreApplicationActivationRightNow() { } diff --git a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm index cf10125b1..279a4e5df 100644 --- a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm +++ b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm @@ -481,6 +481,8 @@ void AppendEmojiPacks(std::vector &to) { @implementation PinnedDialogButton { rpl::lifetime _lifetime; rpl::lifetime _peerChangedLifetime; + base::has_weak_ptr _guard; + bool isWaitingUserpicLoad; } @@ -518,7 +520,7 @@ void AppendEmojiPacks(std::vector &to) { ) | rpl::filter([](const Window::Theme::BackgroundUpdate &update) { return update.paletteChanged(); }) | rpl::start_with_next([=] { - crl::on_main([=] { + crl::on_main(&_guard, [=] { if (_number <= kSavedMessagesId || UseEmptyUserpic(_peer)) { [self updateUserpic]; } else if (_peer diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index c8490da50..8b31672f2 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -136,7 +136,7 @@ void RemoveQuarantine(const QString &path) { removexattr(local.data(), kQuarantineAttribute, 0); } -void RegisterCustomScheme() { +void RegisterCustomScheme(bool force) { #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME OSStatus result = LSSetDefaultHandlerForURLScheme(CFSTR("tg"), (CFStringRef)[[NSBundle mainBundle] bundleIdentifier]); DEBUG_LOG(("App Info: set default handler for 'tg' scheme result: %1").arg(result)); diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index f5650f969..18ebd6c0c 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -28,7 +28,7 @@ enum class SystemSettingsType { void SetWatchingMediaKeys(bool watching); void SetApplicationIcon(const QIcon &icon); -void RegisterCustomScheme(); +void RegisterCustomScheme(bool force = false); PermissionStatus GetPermissionStatus(PermissionType type); void RequestPermission(PermissionType type, Fn resultCallback); void OpenSystemSettingsForPermission(PermissionType type); diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 485ecae2a..1998b4478 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -404,7 +404,7 @@ namespace { namespace Platform { -void RegisterCustomScheme() { +void RegisterCustomScheme(bool force) { if (cExeName().isEmpty()) { return; } diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 7b0149eb1..fe728c61f 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -192,7 +192,7 @@ void GroupMembersWidget::refreshMembers() { fillChatMembers(chat); } else if (const auto megagroup = peer()->asMegagroup()) { auto &megagroupInfo = megagroup->mgInfo; - if (megagroupInfo->lastParticipants.empty() || megagroup->lastParticipantsCountOutdated()) { + if (megagroup->lastParticipantsRequestNeeded()) { Auth().api().requestLastParticipants(megagroup); } fillMegagroupMembers(megagroup); diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index 505146b57..e50471fa8 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -112,10 +112,12 @@ auto GenerateCodes() { } }); }); +#ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME codes.emplace(qsl("registertg"), [](::Main::Session *session) { - Platform::RegisterCustomScheme(); + Platform::RegisterCustomScheme(true); Ui::Toast::Show("Forced custom scheme register."); }); +#endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME codes.emplace(qsl("export"), [](::Main::Session *session) { session->data().startExport(); }); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 2a3039f90..573cc9bef 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -3167,7 +3167,7 @@ void writeFileLocation(MediaKey location, const FileLocation &local) { return; } if (i.value().first != location) { - for (FileLocations::iterator j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first);) { + for (FileLocations::iterator j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first); ++j) { if (j.value() == i.value().second) { _fileLocations.erase(j); break; @@ -3682,7 +3682,21 @@ void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, // Read orders of installed and featured stickers. if (outOrder) { - stickers.stream >> *outOrder; + auto outOrderCount = quint32(); + stickers.stream >> outOrderCount; + if (!_checkStreamStatus(stickers.stream) || outOrderCount > 1000) { + return failed(); + } + outOrder->reserve(outOrderCount); + for (auto i = 0; i != outOrderCount; ++i) { + auto value = uint64(); + stickers.stream >> value; + if (!_checkStreamStatus(stickers.stream)) { + outOrder->clear(); + return failed(); + } + outOrder->push_back(value); + } } if (!_checkStreamStatus(stickers.stream)) { return failed(); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 3064af274..ff6aad0c8 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -117,9 +117,9 @@ QIcon CreateOfficialIcon(Main::Account *account) { QIcon CreateIcon(Main::Account *account) { auto result = CreateOfficialIcon(account); - if (Platform::IsLinux()) { - return QIcon::fromTheme("kotatogram", result); - } +#ifdef Q_OS_LINUX + return QIcon::fromTheme(Platform::GetIconName(), result); +#endif return result; } diff --git a/Telegram/build/version b/Telegram/build/version index 37dc9a595..02fc021df 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 1009019 +AppVersion 1009020 AppVersionStrMajor 1.9 -AppVersionStrSmall 1.9.19 -AppVersionStr 1.9.19 +AppVersionStrSmall 1.9.20 +AppVersionStr 1.9.20 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 1.9.19.beta +AppVersionOriginal 1.9.20.beta diff --git a/Telegram/cmake/telegram_options.cmake b/Telegram/cmake/telegram_options.cmake index 4c18955f4..c2d34c18a 100644 --- a/Telegram/cmake/telegram_options.cmake +++ b/Telegram/cmake/telegram_options.cmake @@ -7,8 +7,6 @@ option(TDESKTOP_FORCE_GTK_FILE_DIALOG "Force using GTK file dialog (Linux only)." OFF) option(TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME "Disable automatic 'tg://' URL scheme handler registration." ${DESKTOP_APP_USE_PACKAGED}) option(TDESKTOP_DISABLE_NETWORK_PROXY "Disable all code for working through Socks5 or MTProxy." OFF) -option(TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION "Disable automatic '.desktop' file generation (Linux only)." ${DESKTOP_APP_USE_PACKAGED}) -option(TDESKTOP_DISABLE_GTK_INTEGRATION "Disable all code for GTK integration (Linux only)." ON) option(TDESKTOP_USE_PACKAGED_TGVOIP "Find libtgvoip using CMake instead of bundled one." ${DESKTOP_APP_USE_PACKAGED}) option(TDESKTOP_API_TEST "Use test API credentials." OFF) set(TDESKTOP_API_ID "0" CACHE STRING "Provide 'api_id' for the Telegram API access.") @@ -43,12 +41,12 @@ if (TDESKTOP_API_ID STREQUAL "0" OR TDESKTOP_API_HASH STREQUAL "") " ") endif() -if (NOT DESKTOP_APP_SPECIAL_TARGET STREQUAL "") +if (NOT DESKTOP_APP_USE_PACKAGED) set(TDESKTOP_FORCE_GTK_FILE_DIALOG ON) endif() -if (TDESKTOP_FORCE_GTK_FILE_DIALOG) - set(TDESKTOP_DISABLE_GTK_INTEGRATION OFF) +if (NOT TDESKTOP_FORCE_GTK_FILE_DIALOG) + set(TDESKTOP_DISABLE_GTK_INTEGRATION ON) endif() if (DESKTOP_APP_DISABLE_SPELLCHECK) @@ -77,10 +75,6 @@ if (TDESKTOP_DISABLE_NETWORK_PROXY) target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_NETWORK_PROXY) endif() -if (TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION) - target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION) -endif() - if (TDESKTOP_DISABLE_GTK_INTEGRATION) target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_GTK_INTEGRATION) endif() diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index 17b6a6d53..0f6c9f4ac 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit 17b6a6d53252b3e3ff02b113e352c152bd697896 +Subproject commit 0f6c9f4acbdc2412a1a941b4ee96b150589b8369 diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index dbb92ddbe..25f44d999 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit dbb92ddbef82988d426ef7a0ffa40a698cdc3fd3 +Subproject commit 25f44d99915101e7a832cb38bd9b2cee47508c94 diff --git a/changelog.txt b/changelog.txt index 990ae6a71..4ac9906fc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +1.9.20 beta (14.03.20) + +- Fix crash in shared links search. +- Fix blurred thumbnails in albums with video files. +- Fix a possible crash in animated stickers rendering. + 1.9.19 beta (25.02.20) - Bug fixes and other minor improvements. diff --git a/cmake b/cmake index 81e27ccc0..9280064cc 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 81e27ccc0e7bf27405569ce98582860dfc9ea9bb +Subproject commit 9280064cc8a5e8f8acc4cdda3a6a905379d3df0d diff --git a/docs/building-cmake.md b/docs/building-cmake.md index af59c2d30..0583ae4f3 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -1,4 +1,4 @@ -## Build instructions for GYP/CMake under Ubuntu 14.04 +## Build instructions for CMake under Ubuntu 14.04 ### Prepare folder @@ -17,7 +17,7 @@ You will need GCC 8 installed. To install them and all the required dependencies libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ 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 \ + libvorbis-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 libegl1-mesa-dev \ @@ -198,7 +198,7 @@ Go to ***BuildPath*** and run git clone git://repo.or.cz/openal-soft.git cd openal-soft - git checkout openal-soft-1.19.1 + git checkout openal-soft-1.20.1 cd build if [ `uname -p` == "i686" ]; then cmake -D LIBTYPE:STRING=STATIC -D ALSOFT_UTILS:BOOL=OFF .. @@ -214,7 +214,7 @@ Go to ***BuildPath*** and run git checkout OpenSSL_1_1_1-stable ./config --prefix=/usr/local/desktop-app/openssl-1.1.1 make $MAKE_THREADS_CNT - sudo make install + sudo make install_sw cd .. git clone https://github.com/xkbcommon/libxkbcommon.git diff --git a/docs/building-msvc.md b/docs/building-msvc.md index e23677577..60b47713f 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -18,7 +18,6 @@ You will require **api_id** and **api_hash** to access the Telegram API servers. ## Install third party software -Strawberry * Download **Strawberry Perl** installer from [http://strawberryperl.com/](http://strawberryperl.com/) and install to ***BuildPath*\\ThirdParty\\Strawberry** * Download **NASM** installer from [http://www.nasm.us](http://www.nasm.us) and install to ***BuildPath*\\ThirdParty\\NASM** * Download **Yasm** executable from [http://yasm.tortall.net/Download.html](http://yasm.tortall.net/Download.html), rename to *yasm.exe* and put to ***BuildPath*\\ThirdParty\\yasm** diff --git a/docs/building-qtcreator.md b/docs/building-qtcreator.md index aa592148f..71fe151e1 100644 --- a/docs/building-qtcreator.md +++ b/docs/building-qtcreator.md @@ -30,7 +30,7 @@ By git – in Terminal go to **/home/user/TBuild** and run Install dev libraries - sudo apt-get install libexif-dev liblzma-dev libz-dev libssl-dev libappindicator-dev libunity-dev libenchant-dev + sudo apt-get install libexif-dev liblzma-dev libz-dev libssl-dev libappindicator-dev libunity-dev #### zlib 1.2.8 diff --git a/docs/building-xcode.md b/docs/building-xcode.md index d4f374265..fba6b3064 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -1,4 +1,4 @@ -## Build instructions for Xcode 10.1 +## Build instructions for Xcode 11 ### Prepare folder @@ -8,18 +8,14 @@ Choose a folder for the future build, for example **/Users/user/TBuild**. It wil You will require **api_id** and **api_hash** to access the Telegram API servers. To learn how to obtain them [click here][api_credentials]. -### Download libraries - -Download [**xz-5.0.5**](http://tukaani.org/xz/xz-5.0.5.tar.gz) and unpack to ***BuildPath*/Libraries/macos/xz-5.0.5** - -Download [**libiconv-1.15**](http://www.gnu.org/software/libiconv/#downloading) and unpack to ***BuildPath*/Libraries/macos/libiconv-1.15** - ### Clone source code and prepare libraries Go to ***BuildPath*** and run MAKE_THREADS_CNT=-j8 MACOSX_DEPLOYMENT_TARGET=10.12 + UNGUARDED="-Werror=unguarded-availability-new" + MIN_VER="-mmacosx-version-min=10.12" ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" brew install automake cmake fdk-aac git lame libass libtool libvorbis libvpx ninja opus sdl shtool texi2html theora wget x264 xvid yasm pkg-config gnu-tar @@ -45,24 +41,29 @@ Go to ***BuildPath*** and run sudo ./setup.py install cd ../.. + mkdir -p Libraries/macos cd Libraries/macos LibrariesPath=`pwd` git clone https://github.com/desktop-app/patches.git cd patches git checkout 4f1cffb - cd ../ + cd .. git clone --branch 0.10.0 https://github.com/ericniebler/range-v3 - cd xz-5.0.5 - CFLAGS="-mmacosx-version-min=10.12" LDFLAGS="-mmacosx-version-min=10.12" ./configure --prefix=/usr/local/macos + xz_ver=5.2.4 + wget https://tukaani.org/xz/xz-$xz_ver.tar.gz + tar -xvzf xz-$xz_ver.tar.gz + rm xz-$xz_ver.tar.gz + cd xz-$xz_ver + CFLAGS="$MIN_VER" LDFLAGS="$MIN_VER" ./configure --prefix=/usr/local/macos make $MAKE_THREADS_CNT sudo make install cd .. git clone https://github.com/desktop-app/zlib.git cd zlib - CFLAGS="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" LDFLAGS="-mmacosx-version-min=10.12" ./configure --prefix=/usr/local/macos + CFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure --prefix=/usr/local/macos make $MAKE_THREADS_CNT sudo make install cd .. @@ -70,7 +71,7 @@ Go to ***BuildPath*** and run git clone https://github.com/openssl/openssl openssl_1_1_1 cd openssl_1_1_1 git checkout OpenSSL_1_1_1-stable - ./Configure --prefix=/usr/local/macos darwin64-x86_64-cc -static -mmacosx-version-min=10.12 + ./Configure --prefix=/usr/local/macos darwin64-x86_64-cc -static $MIN_VER make build_libs $MAKE_THREADS_CNT cd .. @@ -78,13 +79,17 @@ Go to ***BuildPath*** and run cd opus git checkout v1.3 ./autogen.sh - CFLAGS="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" CPPFLAGS="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" LDFLAGS="-mmacosx-version-min=10.12" ./configure --prefix=/usr/local/macos + CFLAGS="$MIN_VER $UNGUARDED" CPPFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure --prefix=/usr/local/macos make $MAKE_THREADS_CNT sudo make install cd .. - cd libiconv-1.15 - CFLAGS="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" CPPFLAGS="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" LDFLAGS="-mmacosx-version-min=10.12" ./configure --enable-static --prefix=/usr/local/macos + libiconv_ver=1.16 + wget https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$libiconv_ver.tar.gz + tar -xvzf libiconv-$libiconv_ver.tar.gz + rm libiconv-$libiconv_ver.tar.gz + cd libiconv-$libiconv_ver + CFLAGS="$MIN_VER $UNGUARDED" CPPFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure --enable-static --prefix=/usr/local/macos make $MAKE_THREADS_CNT sudo make install cd .. @@ -97,9 +102,9 @@ Go to ***BuildPath*** and run PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig ./configure --prefix=/usr/local/macos \ - --extra-cflags="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" \ - --extra-cxxflags="-mmacosx-version-min=10.12 -Werror=unguarded-availability-new" \ - --extra-ldflags="-mmacosx-version-min=10.12" \ + --extra-cflags="$MIN_VER $UNGUARDED" \ + --extra-cxxflags="$MIN_VER $UNGUARDED" \ + --extra-ldflags="$MIN_VER" \ --enable-protocol=file --enable-libopus \ --disable-programs \ --disable-doc \ @@ -205,7 +210,7 @@ Go to ***BuildPath*** and run cd openal-soft git checkout v1.19 cd build - CFLAGS='-Werror=unguarded-availability-new' CPPFLAGS='-Werror=unguarded-availability-new' cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/local/macos -D ALSOFT_EXAMPLES=OFF -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.12 .. + CFLAGS=$UNGUARDED CPPFLAGS=$UNGUARDED cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/local/macos -D ALSOFT_EXAMPLES=OFF -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.12 .. make $MAKE_THREADS_CNT sudo make install cd ../.. @@ -241,7 +246,19 @@ Go to ***BuildPath*** and run git apply ../../patches/qtbase_5_12_5.diff cd .. - ./configure -prefix "/usr/local/desktop-app/Qt-5.12.5" -debug-and-release -force-debug-info -opensource -confirm-license -static -opengl desktop -no-openssl -securetransport -nomake examples -nomake tests -platform macx-clang + ./configure -prefix "/usr/local/desktop-app/Qt-5.12.5" \ + -debug-and-release \ + -force-debug-info \ + -opensource \ + -confirm-license \ + -static \ + -opengl desktop \ + -no-openssl \ + -securetransport \ + -nomake examples \ + -nomake tests \ + -platform macx-clang + make $MAKE_THREADS_CNT sudo make install cd .. diff --git a/lib/xdg/kotatogramdesktop.appdata.xml b/lib/xdg/kotatogramdesktop.appdata.xml.in similarity index 96% rename from lib/xdg/kotatogramdesktop.appdata.xml rename to lib/xdg/kotatogramdesktop.appdata.xml.in index 25c3d4127..53f502004 100644 --- a/lib/xdg/kotatogramdesktop.appdata.xml +++ b/lib/xdg/kotatogramdesktop.appdata.xml.in @@ -20,7 +20,7 @@ https://github.com/kotatogram/kotatogram-desktop/issues - https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/docs/assets/preview.png + https://github.com/kotatogram/kotatogram-desktop/blob/dev/docs/assets/ktg_preview.png @@ -98,6 +98,7 @@ none none + @TDESKTOP_LAUNCHER_BASENAME@.desktop kotatogram-desktop diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2629e4f36..8d9dc3b28 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,6 +6,13 @@ base: core18 grade: stable confinement: strict +architectures: + - build-on: amd64 + - build-on: i386 + - build-on: arm64 + - build-on: armhf + - build-on: ppc64el + apps: kotatogram-desktop: command: bin/desktop-launch kotatogram-desktop @@ -14,9 +21,12 @@ apps: environment: # Use GTK3 cursor theme, icon theme and open/save file dialogs. QT_QPA_PLATFORMTHEME: gtk3 - # Wayland support is still too bad. + # The blocker is https://github.com/ubuntu/snapcraft-desktop-helpers/issues/172 DISABLE_WAYLAND: 1 plugs: + - alsa + - audio-playback + - audio-record - desktop - desktop-legacy - home @@ -44,6 +54,10 @@ plugs: target: $SNAP/data-dir/sounds default-provider: gtk-common-themes +layout: + /usr/share/alsa: + bind: $SNAP/usr/share/alsa + parts: kotatogram: plugin: cmake @@ -56,17 +70,11 @@ parts: - qtbase5-private-dev - libmapbox-variant-dev - libasound2-dev - - libavcodec-dev - - libavformat-dev - - libavutil-dev - - libswscale-dev - - libswresample-dev - libdbusmenu-qt5-dev - libhunspell-dev - liblz4-dev - liblzma-dev - libminizip-dev - - libopenal-dev - libopus-dev - libpulse-dev - libssl-dev @@ -74,17 +82,11 @@ parts: stage-packages: - qt5-image-formats-plugins - libasound2 - - libavcodec57 - - libavformat57 - - libavutil55 - - libswscale4 - - libswresample2 - libdbusmenu-qt5-2 - libhunspell-1.6-0 - liblz4-1 - liblzma5 - libminizip1 - - libopenal1 - libopus0 - libpulse0 - libssl1.1 @@ -119,9 +121,16 @@ parts: after: - cmake - desktop-qt5 + - ffmpeg + - openal - range-v3 - xxhash + patches: + plugin: dump + source: Telegram/Patches + prime: [-./*] + desktop-qt5: source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source-subdir: qt @@ -176,6 +185,138 @@ parts: - libtinfo5 prime: [-./*] + ffmpeg: + source: https://github.com/FFmpeg/FFmpeg.git + source-depth: 1 + source-branch: release/4.2 + plugin: autotools + build-packages: + - libtool + - pkg-config + - texi2html + - yasm + - libass-dev + - libfreetype6-dev + - libgpac-dev + - liblzma-dev + - libopus-dev + - libsdl1.2-dev + - libtheora-dev + - libva-dev + - libvdpau-dev + - libvorbis-dev + - libxcb1-dev + - libxcb-shm0-dev + - libxcb-xfixes0-dev + - zlib1g-dev + stage-packages: + - freeglut3 + - libass9 + - libfreetype6 + - libgpac4 + - liblzma5 + - libopus0 + - libslang2 + - libsdl1.2debian + - libtheora0 + - libva2 + - libva-drm2 + - libvdpau1 + - libvorbis0a + - libxcb1 + - libxcb-shm0 + - libxcb-xfixes0 + - zlib1g + configflags: + - --prefix=/usr + - --disable-debug + - --disable-programs + - --disable-doc + - --disable-everything + - --disable-neon + - --enable-gpl + - --enable-version3 + - --enable-libopus + - --enable-decoder=aac + - --enable-decoder=aac_latm + - --enable-decoder=aasc + - --enable-decoder=flac + - --enable-decoder=gif + - --enable-decoder=h264 + - --enable-decoder=h264_vdpau + - --enable-decoder=mp1 + - --enable-decoder=mp1float + - --enable-decoder=mp2 + - --enable-decoder=mp2float + - --enable-decoder=mp3 + - --enable-decoder=mp3adu + - --enable-decoder=mp3adufloat + - --enable-decoder=mp3float + - --enable-decoder=mp3on4 + - --enable-decoder=mp3on4float + - --enable-decoder=mpeg4 + - --enable-decoder=mpeg4_vdpau + - --enable-decoder=msmpeg4v2 + - --enable-decoder=msmpeg4v3 + - --enable-decoder=opus + - --enable-decoder=vorbis + - --enable-decoder=wavpack + - --enable-decoder=wmalossless + - --enable-decoder=wmapro + - --enable-decoder=wmav1 + - --enable-decoder=wmav2 + - --enable-decoder=wmavoice + - --enable-encoder=libopus + - --enable-hwaccel=h264_vaapi + - --enable-hwaccel=h264_vdpau + - --enable-hwaccel=mpeg4_vaapi + - --enable-hwaccel=mpeg4_vdpau + - --enable-parser=aac + - --enable-parser=aac_latm + - --enable-parser=flac + - --enable-parser=h264 + - --enable-parser=mpeg4video + - --enable-parser=mpegaudio + - --enable-parser=opus + - --enable-parser=vorbis + - --enable-demuxer=aac + - --enable-demuxer=flac + - --enable-demuxer=gif + - --enable-demuxer=h264 + - --enable-demuxer=mov + - --enable-demuxer=mp3 + - --enable-demuxer=ogg + - --enable-demuxer=wav + - --enable-muxer=ogg + - --enable-muxer=opus + override-pull: | + snapcraftctl pull + patch -p1 < "$SNAPCRAFT_STAGE/ffmpeg.diff" + after: + - patches + + openal: + source: https://github.com/kcat/openal-soft.git + source-depth: 1 + source-tag: openal-soft-1.20.1 + plugin: cmake + build-packages: + - libasound2-dev + - libpulse-dev + - libsndio-dev + - portaudio19-dev + stage-packages: + - libasound2 + - libpulse0 + - libportaudio2 + - libsndio6.1 + configflags: + - -DCMAKE_BUILD_TYPE=Release + - -DALSOFT_EXAMPLES=OFF + - -DALSOFT_TESTS=OFF + - -DALSOFT_UTILS=OFF + - -DALSOFT_CONFIG=OFF + range-v3: source: https://github.com/ericniebler/range-v3.git source-depth: 1