Compare commits
188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f09b91ebb5 | ||
|
|
57b147e0c8 | ||
|
|
551ea7d879 | ||
|
|
d3c9bb0bc6 | ||
|
|
c711b1f1df | ||
|
|
af7ea90246 | ||
|
|
348cf4829c | ||
|
|
4753a57091 | ||
|
|
1a93f4fa4c | ||
|
|
118fd187e3 | ||
|
|
baa47bde7f | ||
|
|
18b48df9ce | ||
|
|
e71fc60d22 | ||
|
|
148af59615 | ||
|
|
5b2db4112f | ||
|
|
7cedc1f7a5 | ||
|
|
6cea7d4a52 | ||
|
|
bd93aed393 | ||
|
|
348666de6d | ||
|
|
47e32bebe4 | ||
|
|
0b21c04489 | ||
|
|
85f013ebdb | ||
|
|
832cc6ac69 | ||
|
|
30ce049f51 | ||
|
|
d42fb6d1b9 | ||
|
|
cade53aa0a | ||
|
|
2fdcda7536 | ||
|
|
7e6439e4f8 | ||
|
|
f07ee7f590 | ||
|
|
02db4e01fa | ||
|
|
8d75078a42 | ||
|
|
0e25ef7524 | ||
|
|
8608d8aa4d | ||
|
|
c1a7332a5e | ||
|
|
c3fb392906 | ||
|
|
a59bfdb2f8 | ||
|
|
79f96480c2 | ||
|
|
60cbd96d91 | ||
|
|
ee0400f1ac | ||
|
|
b8a3746558 | ||
|
|
14f25fc997 | ||
|
|
27da6ee9eb | ||
|
|
48d482006a | ||
|
|
9afee2620a | ||
|
|
baca3047d4 | ||
|
|
43a5265e0c | ||
|
|
5519bb3523 | ||
|
|
ff213d1386 | ||
|
|
55b3f99653 | ||
|
|
8a6ff3f414 | ||
|
|
feb8624d05 | ||
|
|
a2c33545d4 | ||
|
|
7decf68122 | ||
|
|
6b62ec97c6 | ||
|
|
ea3dab4a06 | ||
|
|
5c8f08fc92 | ||
|
|
00a0b2c8b6 | ||
|
|
007218cc13 | ||
|
|
8afe495a4f | ||
|
|
257f2086d1 | ||
|
|
f011c84ce8 | ||
|
|
8b839f46b2 | ||
|
|
c1067d8fe1 | ||
|
|
bb76818cc8 | ||
|
|
5dcc219f1c | ||
|
|
4ff9e90153 | ||
|
|
28fe98af80 | ||
|
|
5eba65aaa0 | ||
|
|
d1e3e7d240 | ||
|
|
90ff8ecd0f | ||
|
|
468e75a572 | ||
|
|
ae3e5487d7 | ||
|
|
03147a5426 | ||
|
|
14a2b10989 | ||
|
|
b29f8aa1e6 | ||
|
|
f9bb932cd8 | ||
|
|
a38cbbf7e8 | ||
|
|
d5bb1717e0 | ||
|
|
520ff8f2ce | ||
|
|
b3848f6a84 | ||
|
|
518f387e0c | ||
|
|
635f76a312 | ||
|
|
ff14ac68ee | ||
|
|
948c5d50cb | ||
|
|
6b520ecc05 | ||
|
|
bb474686eb | ||
|
|
659ddae9a8 | ||
|
|
b70276912e | ||
|
|
858c575782 | ||
|
|
62fe14d592 | ||
|
|
6ad037e556 | ||
|
|
a55b41faa1 | ||
|
|
a26d769304 | ||
|
|
e1120d1cb5 | ||
|
|
8897f9e46a | ||
|
|
7a588be54f | ||
|
|
1cb1f1cbc1 | ||
|
|
5827d6ffdb | ||
|
|
766bc90921 | ||
|
|
eb228eb744 | ||
|
|
7c02d67665 | ||
|
|
23c54896e5 | ||
|
|
460baa54d8 | ||
|
|
6c56fad180 | ||
|
|
3fd772ce17 | ||
|
|
8834ec8bf2 | ||
|
|
003fb52fb9 | ||
|
|
ec234cdc43 | ||
|
|
55e494f55a | ||
|
|
0b4605a656 | ||
|
|
18a86e500b | ||
|
|
e19af1257c | ||
|
|
e0159e15b2 | ||
|
|
b6e77537e2 | ||
|
|
beaa4190eb | ||
|
|
7924979dfb | ||
|
|
570ed5691d | ||
|
|
c25779b844 | ||
|
|
7304f2b695 | ||
|
|
b4bff939b1 | ||
|
|
1f816c249b | ||
|
|
8591d58798 | ||
|
|
9290cd3a16 | ||
|
|
dc0aaec4a4 | ||
|
|
eefa7263b5 | ||
|
|
7885be4a94 | ||
|
|
583c3d3429 | ||
|
|
6d0d399250 | ||
|
|
0e89c93993 | ||
|
|
b422ec025e | ||
|
|
c8535acad8 | ||
|
|
e2a97e2ae9 | ||
|
|
77a019325d | ||
|
|
115dc460ac | ||
|
|
3df1a73cf5 | ||
|
|
5cf69366d1 | ||
|
|
8c2b1168af | ||
|
|
a425024f21 | ||
|
|
e85026ec46 | ||
|
|
1c6e2eae04 | ||
|
|
4db5624beb | ||
|
|
6d08542afa | ||
|
|
707b36dc12 | ||
|
|
da3e140069 | ||
|
|
5334372671 | ||
|
|
16db8468fa | ||
|
|
2ed3543b53 | ||
|
|
5b4d442799 | ||
|
|
9669a8a44a | ||
|
|
80fe2f57e9 | ||
|
|
824fbc21e8 | ||
|
|
bf7f117323 | ||
|
|
9b488f03a1 | ||
|
|
295a863d69 | ||
|
|
0d814066d6 | ||
|
|
1af8e89eb9 | ||
|
|
7cf79e1f8a | ||
|
|
019fd83c8a | ||
|
|
65779ec37e | ||
|
|
2d90a06078 | ||
|
|
d2c8780c0f | ||
|
|
54dd63d61a | ||
|
|
7852c82eab | ||
|
|
77c8bf8176 | ||
|
|
daa14466e5 | ||
|
|
aad38c2809 | ||
|
|
2c50d3d87b | ||
|
|
0fe7c07007 | ||
|
|
c22d200c17 | ||
|
|
9e6afa0d4e | ||
|
|
3340b2dc03 | ||
|
|
386fae952b | ||
|
|
a164cb9480 | ||
|
|
bc9b288617 | ||
|
|
5c7229f875 | ||
|
|
658d5a1322 | ||
|
|
52e841ec29 | ||
|
|
df28da4d97 | ||
|
|
8dac6896d6 | ||
|
|
f18e157e46 | ||
|
|
b2bf8244dd | ||
|
|
4e0355d09f | ||
|
|
7059336ff0 | ||
|
|
94f10ce72e | ||
|
|
68be54288c | ||
|
|
10636d931f | ||
|
|
3b1aa55d21 | ||
|
|
37f59095f4 |
3
.github/workflows/linux.yml
vendored
3
.github/workflows/linux.yml
vendored
@@ -93,6 +93,9 @@ jobs:
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||
if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" ]; then
|
||||
DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_GTK_INTEGRATION=ON"
|
||||
fi
|
||||
echo Define from matrix: $DEFINE
|
||||
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
|
||||
else
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -76,9 +76,6 @@
|
||||
[submodule "Telegram/ThirdParty/hime"]
|
||||
path = Telegram/ThirdParty/hime
|
||||
url = https://github.com/hime-ime/hime.git
|
||||
[submodule "Telegram/ThirdParty/qt5ct"]
|
||||
path = Telegram/ThirdParty/qt5ct
|
||||
url = https://github.com/desktop-app/qt5ct.git
|
||||
[submodule "Telegram/ThirdParty/fcitx5-qt"]
|
||||
path = Telegram/ThirdParty/fcitx5-qt
|
||||
url = https://github.com/fcitx/fcitx5-qt.git
|
||||
@@ -91,9 +88,6 @@
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
[submodule "Telegram/ThirdParty/mallocng"]
|
||||
path = Telegram/ThirdParty/mallocng
|
||||
url = https://github.com/desktop-app/mallocng.git
|
||||
[submodule "Telegram/lib_waylandshells"]
|
||||
path = Telegram/lib_waylandshells
|
||||
url = https://github.com/desktop-app/lib_waylandshells.git
|
||||
|
||||
@@ -31,6 +31,7 @@ include(cmake/target_link_static_libraries.cmake)
|
||||
include(cmake/target_link_frameworks.cmake)
|
||||
include(cmake/init_target.cmake)
|
||||
include(cmake/generate_target.cmake)
|
||||
include(cmake/nuget.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
|
||||
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
|
||||
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
|
||||
* Hunspell ([GPL](https://github.com/hunspell/hunspell/blob/master/COPYING))
|
||||
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
|
||||
|
||||
## Build instructions
|
||||
|
||||
|
||||
@@ -82,24 +82,17 @@ PRIVATE
|
||||
desktop-app::external_xxhash
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::lib_webview_winrt
|
||||
)
|
||||
elseif (LINUX)
|
||||
if (LINUX)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
desktop-app::external_mallocng
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -119,6 +112,7 @@ elseif (LINUX)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
target_link_libraries(Telegram PRIVATE rt)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY)
|
||||
@@ -130,7 +124,7 @@ elseif (LINUX)
|
||||
target_link_libraries(Telegram PRIVATE PkgConfig::X11)
|
||||
endif()
|
||||
else()
|
||||
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0)
|
||||
pkg_check_modules(GTK REQUIRED gtk+-3.0)
|
||||
target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS})
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
|
||||
@@ -373,6 +367,8 @@ PRIVATE
|
||||
core/core_cloud_password.h
|
||||
core/core_settings.cpp
|
||||
core/core_settings.h
|
||||
core/core_settings_proxy.cpp
|
||||
core/core_settings_proxy.h
|
||||
core/crash_report_window.cpp
|
||||
core/crash_report_window.h
|
||||
core/crash_reports.cpp
|
||||
@@ -398,6 +394,7 @@ PRIVATE
|
||||
data/stickers/data_stickers_set.h
|
||||
data/stickers/data_stickers.cpp
|
||||
data/stickers/data_stickers.h
|
||||
data/data_abstract_sparse_ids.h
|
||||
data/data_abstract_structure.cpp
|
||||
data/data_abstract_structure.h
|
||||
data/data_auto_download.cpp
|
||||
@@ -420,10 +417,14 @@ PRIVATE
|
||||
data/data_document.h
|
||||
data/data_document_media.cpp
|
||||
data/data_document_media.h
|
||||
data/data_document_resolver.cpp
|
||||
data/data_document_resolver.h
|
||||
data/data_drafts.cpp
|
||||
data/data_drafts.h
|
||||
data/data_folder.cpp
|
||||
data/data_folder.h
|
||||
data/data_file_click_handler.cpp
|
||||
data/data_file_click_handler.h
|
||||
data/data_file_origin.cpp
|
||||
data/data_file_origin.h
|
||||
data/data_flags.h
|
||||
@@ -803,6 +804,7 @@ PRIVATE
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_open_common.h
|
||||
mtproto/config_loader.cpp
|
||||
mtproto/config_loader.h
|
||||
mtproto/connection_abstract.cpp
|
||||
@@ -861,8 +863,6 @@ PRIVATE
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_dummy.cpp
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
@@ -1086,6 +1086,8 @@ PRIVATE
|
||||
window/section_memento.h
|
||||
window/section_widget.cpp
|
||||
window/section_widget.h
|
||||
window/window_adaptive.cpp
|
||||
window/window_adaptive.h
|
||||
window/window_connecting_widget.cpp
|
||||
window/window_connecting_widget.h
|
||||
window/window_controller.cpp
|
||||
@@ -1192,8 +1194,6 @@ if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
@@ -1358,6 +1358,32 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32)
|
||||
target_link_options(Telegram
|
||||
PRIVATE
|
||||
/DELAYLOAD:secur32.dll
|
||||
/DELAYLOAD:winmm.dll
|
||||
/DELAYLOAD:ws2_32.dll
|
||||
/DELAYLOAD:user32.dll
|
||||
/DELAYLOAD:gdi32.dll
|
||||
/DELAYLOAD:advapi32.dll
|
||||
/DELAYLOAD:shell32.dll
|
||||
/DELAYLOAD:ole32.dll
|
||||
/DELAYLOAD:oleaut32.dll
|
||||
/DELAYLOAD:shlwapi.dll
|
||||
/DELAYLOAD:iphlpapi.dll
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
|
||||
add_executable(Updater WIN32)
|
||||
init_target(Updater)
|
||||
@@ -1374,8 +1400,26 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
|
||||
|
||||
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
if (WIN32)
|
||||
get_filename_component(lib_base_loc lib_base REALPATH)
|
||||
nice_target_sources(Updater ${lib_base_loc}
|
||||
PRIVATE
|
||||
base/platform/win/base_windows_safe_library.cpp
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater
|
||||
PRIVATE
|
||||
/DELAYLOAD:user32.dll
|
||||
/DELAYLOAD:advapi32.dll
|
||||
/DELAYLOAD:shell32.dll
|
||||
/DELAYLOAD:ole32.dll
|
||||
/DELAYLOAD:shlwapi.dll
|
||||
)
|
||||
else()
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
|
||||
BIN
Telegram/Resources/icons/calls/video_tooltip.png
Normal file
BIN
Telegram/Resources/icons/calls/video_tooltip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 360 B |
BIN
Telegram/Resources/icons/calls/video_tooltip@2x.png
Normal file
BIN
Telegram/Resources/icons/calls/video_tooltip@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 622 B |
BIN
Telegram/Resources/icons/calls/video_tooltip@3x.png
Normal file
BIN
Telegram/Resources/icons/calls/video_tooltip@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 967 B |
@@ -452,6 +452,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_performance" = "Performance";
|
||||
"lng_settings_enable_animations" = "Enable animations";
|
||||
"lng_settings_enable_opengl" = "Enable OpenGL rendering for media";
|
||||
"lng_settings_angle_backend" = "ANGLE graphics backend";
|
||||
"lng_settings_angle_backend_auto" = "Auto";
|
||||
"lng_settings_angle_backend_d3d9" = "Direct3D 9";
|
||||
"lng_settings_angle_backend_d3d11" = "Direct3D 11";
|
||||
"lng_settings_angle_backend_d3d11on12" = "D3D11on12";
|
||||
"lng_settings_angle_backend_opengl" = "OpenGL";
|
||||
"lng_settings_angle_backend_disabled" = "Disabled";
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
@@ -497,6 +504,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
|
||||
"lng_background_text2" = "I can't even take you seriously right now.";
|
||||
"lng_background_bad_link" = "This background link appears to be invalid.";
|
||||
"lng_background_gradient_unsupported" = "Telegram Desktop doesn't support gradient backgrounds yet.";
|
||||
"lng_background_apply" = "Apply";
|
||||
"lng_background_share" = "Share";
|
||||
"lng_background_link_copied" = "Link copied to clipboard";
|
||||
@@ -1006,6 +1014,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_report_group_title" = "Report group";
|
||||
"lng_report_bot_title" = "Report bot";
|
||||
"lng_report_message_title" = "Report message";
|
||||
"lng_report_please_select_messages" = "Please select messages to report.";
|
||||
"lng_report_select_messages" = "Select messages";
|
||||
"lng_report_messages_none" = "Select Messages";
|
||||
"lng_report_messages_count#one" = "Report {count} Message";
|
||||
@@ -2029,6 +2038,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_chat_no_camera" = "You can't turn on video in this chat.";
|
||||
"lng_group_call_chat_no_screen" = "You can't share your screen in this chat.";
|
||||
"lng_group_call_failed_screen" = "An error occured. Screencast has stopped.";
|
||||
"lng_group_call_failed_camera" = "Could not enable camera. Perhaps another app is using the camera already. Try closing other apps.";
|
||||
"lng_group_call_tooltip_screen" = "Share screen";
|
||||
"lng_group_call_tooltip_camera" = "Your camera is off. Click here to enable camera.";
|
||||
"lng_group_call_tooltip_microphone" = "You are on mute. Click here to speak.";
|
||||
@@ -2057,6 +2067,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_ptt_delay_s" = "{amount}s";
|
||||
"lng_group_call_ptt_delay" = "Push to Talk release delay: {delay}";
|
||||
"lng_group_call_share" = "Share Invite Link";
|
||||
"lng_group_call_noise_suppression" = "Enable Noise Suppression";
|
||||
"lng_group_call_limit#one" = "Video is only available\nfor the first {count} member";
|
||||
"lng_group_call_limit#other" = "Video is only available\nfor the first {count} members";
|
||||
"lng_group_call_video_paused" = "Video is paused";
|
||||
"lng_group_call_share_speaker" = "Users with this link can speak";
|
||||
"lng_group_call_copy_speaker_link" = "Copy Speaker Link";
|
||||
"lng_group_call_copy_listener_link" = "Copy Listener Link";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.7.8.0" />
|
||||
Version="2.8.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,8,0
|
||||
PRODUCTVERSION 2,7,8,0
|
||||
FILEVERSION 2,8,5,0
|
||||
PRODUCTVERSION 2,8,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.7.8.0"
|
||||
VALUE "FileVersion", "2.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.8.0"
|
||||
VALUE "ProductVersion", "2.8.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,8,0
|
||||
PRODUCTVERSION 2,7,8,0
|
||||
FILEVERSION 2,8,5,0
|
||||
PRODUCTVERSION 2,8,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.7.8.0"
|
||||
VALUE "FileVersion", "2.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.8.0"
|
||||
VALUE "ProductVersion", "2.8.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "updater.h"
|
||||
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
|
||||
@@ -329,6 +331,8 @@ void updateRegistry() {
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) {
|
||||
base::Platform::InitDynamicLibraries();
|
||||
|
||||
openLog();
|
||||
|
||||
_oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter);
|
||||
|
||||
@@ -36,10 +36,12 @@ void AttachedStickers::request(
|
||||
return;
|
||||
}
|
||||
if (result.v.isEmpty()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
strongController->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
return;
|
||||
} else if (result.v.size() > 1) {
|
||||
Ui::show(Box<StickersBox>(strongController, result));
|
||||
strongController->show(
|
||||
Box<StickersBox>(strongController, result));
|
||||
return;
|
||||
}
|
||||
// Single attached sticker pack.
|
||||
@@ -52,12 +54,15 @@ void AttachedStickers::request(
|
||||
const auto setId = (setData->vid().v && setData->vaccess_hash().v)
|
||||
? MTP_inputStickerSetID(setData->vid(), setData->vaccess_hash())
|
||||
: MTP_inputStickerSetShortName(setData->vshort_name());
|
||||
Ui::show(
|
||||
strongController->show(
|
||||
Box<StickerSetBox>(strongController, setId),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
if (const auto strongController = weak.get()) {
|
||||
strongController->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,11 @@ void CheckChatInvite(
|
||||
session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
|
||||
Core::App().hideMediaView();
|
||||
result.match([=](const MTPDchatInvite &data) {
|
||||
const auto box = Ui::show(Box<ConfirmInviteBox>(
|
||||
const auto strongController = weak.get();
|
||||
if (!strongController) {
|
||||
return;
|
||||
}
|
||||
const auto box = strongController->show(Box<ConfirmInviteBox>(
|
||||
session,
|
||||
data,
|
||||
invitePeekChannel,
|
||||
@@ -80,7 +84,10 @@ void CheckChatInvite(
|
||||
return;
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->show(
|
||||
Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -862,8 +862,8 @@ int32 Updates::pts() const {
|
||||
return _ptsWaiter.current();
|
||||
}
|
||||
|
||||
void Updates::updateOnline() {
|
||||
updateOnline(false);
|
||||
void Updates::updateOnline(crl::time lastNonIdleTime) {
|
||||
updateOnline(lastNonIdleTime, false);
|
||||
}
|
||||
|
||||
bool Updates::isIdle() const {
|
||||
@@ -874,15 +874,20 @@ rpl::producer<bool> Updates::isIdleValue() const {
|
||||
return _isIdle.value();
|
||||
}
|
||||
|
||||
void Updates::updateOnline(bool gotOtherOffline) {
|
||||
crl::on_main(&session(), [] { Core::App().checkAutoLock(); });
|
||||
void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
|
||||
if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = Core::App().lastNonIdleTime();
|
||||
}
|
||||
crl::on_main(&session(), [=] {
|
||||
Core::App().checkAutoLock(lastNonIdleTime);
|
||||
});
|
||||
|
||||
const auto &config = _session->serverConfig();
|
||||
bool isOnline = Core::App().hasActiveWindow(&session());
|
||||
int updateIn = config.onlineUpdatePeriod;
|
||||
Assert(updateIn >= 0);
|
||||
if (isOnline) {
|
||||
const auto idle = crl::now() - Core::App().lastNonIdleTime();
|
||||
const auto idle = crl::now() - lastNonIdleTime;
|
||||
if (idle >= config.offlineIdleTimeout) {
|
||||
isOnline = false;
|
||||
if (!isIdle()) {
|
||||
@@ -933,10 +938,13 @@ void Updates::updateOnline(bool gotOtherOffline) {
|
||||
_onlineTimer.callOnce(updateIn);
|
||||
}
|
||||
|
||||
void Updates::checkIdleFinish() {
|
||||
if (crl::now() - Core::App().lastNonIdleTime()
|
||||
void Updates::checkIdleFinish(crl::time lastNonIdleTime) {
|
||||
if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = Core::App().lastNonIdleTime();
|
||||
}
|
||||
if (crl::now() - lastNonIdleTime
|
||||
< _session->serverConfig().offlineIdleTimeout) {
|
||||
updateOnline();
|
||||
updateOnline(lastNonIdleTime);
|
||||
_idleFinishTimer.cancel();
|
||||
_isIdle = false;
|
||||
} else {
|
||||
@@ -957,9 +965,10 @@ bool Updates::isQuitPrevent() {
|
||||
return false;
|
||||
}
|
||||
LOG(("Api::Updates prevents quit, sending offline status..."));
|
||||
updateOnline();
|
||||
updateOnline(crl::now());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Updates::handleSendActionUpdate(
|
||||
PeerId peerId,
|
||||
MsgId rootId,
|
||||
@@ -1750,7 +1759,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
if (UserId(d.vuser_id()) == session().userId()) {
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline
|
||||
|| d.vstatus().type() == mtpc_userStatusEmpty) {
|
||||
updateOnline(true);
|
||||
updateOnline(Core::App().lastNonIdleTime(), true);
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline) {
|
||||
cSetOtherOnline(
|
||||
d.vstatus().c_userStatusOffline().vwas_online().v);
|
||||
|
||||
@@ -38,10 +38,10 @@ public:
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline();
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
[[nodiscard]] bool isIdle() const;
|
||||
[[nodiscard]] rpl::producer<bool> isIdleValue() const;
|
||||
void checkIdleFinish();
|
||||
void checkIdleFinish(crl::time lastNonIdleTime = 0);
|
||||
bool lastWasOnline() const;
|
||||
crl::time lastSetOnline() const;
|
||||
bool isQuitPrevent();
|
||||
@@ -87,7 +87,7 @@ private:
|
||||
MsgRange range,
|
||||
const MTPupdates_ChannelDifference &result);
|
||||
|
||||
void updateOnline(bool gotOtherOffline);
|
||||
void updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline);
|
||||
void sendPing();
|
||||
void getDifferenceByPts();
|
||||
void getDifferenceAfterFail();
|
||||
|
||||
@@ -212,6 +212,11 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
}, _session->lifetime());
|
||||
|
||||
setupSupportMode();
|
||||
|
||||
Core::App().settings().proxy().connectionTypeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopPromotion();
|
||||
}, _session->lifetime());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,10 +266,10 @@ void ApiWrap::refreshTopPromotion() {
|
||||
return;
|
||||
}
|
||||
const auto key = [&]() -> std::pair<QString, uint32> {
|
||||
if (Global::ProxySettings() != MTP::ProxyData::Settings::Enabled) {
|
||||
if (!Core::App().settings().proxy().isEnabled()) {
|
||||
return {};
|
||||
}
|
||||
const auto &proxy = Global::SelectedProxy();
|
||||
const auto &proxy = Core::App().settings().proxy().selected();
|
||||
if (proxy.type != MTP::ProxyData::Type::Mtproto) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
@@ -45,8 +44,7 @@ void AutoLockBox::prepare() {
|
||||
void AutoLockBox::durationChanged(int seconds) {
|
||||
Core::App().settings().setAutoLock(seconds);
|
||||
Core::App().saveSettingsDelayed();
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
|
||||
Core::App().checkAutoLock();
|
||||
Core::App().checkAutoLock(crl::now());
|
||||
closeBox();
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ void BackgroundBox::prepare() {
|
||||
|
||||
_inner->chooseEvents(
|
||||
) | rpl::start_with_next([=](const Data::WallPaper &paper) {
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<BackgroundPreviewBox>(_controller, paper),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}, _inner->lifetime());
|
||||
@@ -176,7 +176,7 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) {
|
||||
paper.mtpSettings()
|
||||
)).send();
|
||||
};
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<ConfirmBox>(
|
||||
tr::lng_background_sure_delete(tr::now),
|
||||
tr::lng_selected_delete(tr::now),
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
@@ -769,23 +770,25 @@ bool BackgroundPreviewBox::Start(
|
||||
const QString &slug,
|
||||
const QMap<QString, QString> ¶ms) {
|
||||
if (const auto paper = Data::WallPaper::FromColorSlug(slug)) {
|
||||
Ui::show(Box<BackgroundPreviewBox>(
|
||||
controller->show(Box<BackgroundPreviewBox>(
|
||||
controller,
|
||||
paper->withUrlParams(params)));
|
||||
return true;
|
||||
}
|
||||
if (!IsValidWallPaperSlug(slug)) {
|
||||
Ui::show(Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
controller->show(
|
||||
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
return false;
|
||||
}
|
||||
controller->session().api().requestWallPaper(slug, crl::guard(controller, [=](
|
||||
const Data::WallPaper &result) {
|
||||
Ui::show(Box<BackgroundPreviewBox>(
|
||||
controller->show(Box<BackgroundPreviewBox>(
|
||||
controller,
|
||||
result.withUrlParams(params)));
|
||||
}), [](const MTP::Error &error) {
|
||||
Ui::show(Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
});
|
||||
}), crl::guard(controller, [=](const MTP::Error &error) {
|
||||
controller->show(
|
||||
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "main/main_account.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
@@ -27,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -186,7 +186,10 @@ class ProxiesBox : public Ui::BoxContent {
|
||||
public:
|
||||
using View = ProxiesBoxController::ItemView;
|
||||
|
||||
ProxiesBox(QWidget*, not_null<ProxiesBoxController*> controller);
|
||||
ProxiesBox(
|
||||
QWidget*,
|
||||
not_null<ProxiesBoxController*> controller,
|
||||
Core::SettingsProxy &settings);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@@ -201,6 +204,7 @@ private:
|
||||
void refreshProxyForCalls();
|
||||
|
||||
not_null<ProxiesBoxController*> _controller;
|
||||
Core::SettingsProxy &_settings;
|
||||
QPointer<Ui::Checkbox> _tryIPv6;
|
||||
std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings;
|
||||
QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls;
|
||||
@@ -566,8 +570,10 @@ void ProxyRow::showMenu() {
|
||||
|
||||
ProxiesBox::ProxiesBox(
|
||||
QWidget*,
|
||||
not_null<ProxiesBoxController*> controller)
|
||||
not_null<ProxiesBoxController*> controller,
|
||||
Core::SettingsProxy &settings)
|
||||
: _controller(controller)
|
||||
, _settings(settings)
|
||||
, _initialWrap(this) {
|
||||
_controller->views(
|
||||
) | rpl::start_with_next([=](View &&view) {
|
||||
@@ -591,11 +597,11 @@ void ProxiesBox::setupContent() {
|
||||
object_ptr<Ui::Checkbox>(
|
||||
inner,
|
||||
tr::lng_connection_try_ipv6(tr::now),
|
||||
Global::TryIPv6()),
|
||||
_settings.tryIPv6()),
|
||||
st::proxyTryIPv6Padding);
|
||||
_proxySettings
|
||||
= std::make_shared<Ui::RadioenumGroup<ProxyData::Settings>>(
|
||||
Global::ProxySettings());
|
||||
_settings.settings());
|
||||
inner->add(
|
||||
object_ptr<Ui::Radioenum<ProxyData::Settings>>(
|
||||
inner,
|
||||
@@ -623,7 +629,7 @@ void ProxiesBox::setupContent() {
|
||||
object_ptr<Ui::Checkbox>(
|
||||
inner,
|
||||
tr::lng_proxy_use_for_calls(tr::now),
|
||||
Global::UseProxyForCalls()),
|
||||
_settings.useProxyForCalls()),
|
||||
style::margins(
|
||||
0,
|
||||
st::proxyUsePadding.top(),
|
||||
@@ -652,7 +658,7 @@ void ProxiesBox::setupContent() {
|
||||
|
||||
_proxySettings->setChangedCallback([=](ProxyData::Settings value) {
|
||||
if (!_controller->setProxySettings(value)) {
|
||||
_proxySettings->setValue(Global::ProxySettings());
|
||||
_proxySettings->setValue(_settings.settings());
|
||||
addNewProxy();
|
||||
}
|
||||
refreshProxyForCalls();
|
||||
@@ -1051,20 +1057,22 @@ void ProxyBox::addLabel(
|
||||
|
||||
ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
|
||||
: _account(account)
|
||||
, _settings(Core::App().settings().proxy())
|
||||
, _saveTimer([] { Local::writeSettings(); }) {
|
||||
_list = ranges::views::all(
|
||||
Global::ProxiesList()
|
||||
_settings.list()
|
||||
) | ranges::views::transform([&](const ProxyData &proxy) {
|
||||
return Item{ ++_idCounter, proxy };
|
||||
}) | ranges::to_vector;
|
||||
|
||||
subscribe(Global::RefConnectionTypeChanged(), [=] {
|
||||
_proxySettingsChanges.fire_copy(Global::ProxySettings());
|
||||
const auto i = findByProxy(Global::SelectedProxy());
|
||||
_settings.connectionTypeChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
_proxySettingsChanges.fire_copy(_settings.settings());
|
||||
const auto i = findByProxy(_settings.selected());
|
||||
if (i != end(_list)) {
|
||||
updateView(*i);
|
||||
}
|
||||
});
|
||||
}, _lifetime);
|
||||
|
||||
for (auto &item : _list) {
|
||||
refreshChecker(item);
|
||||
@@ -1112,7 +1120,7 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now)
|
||||
: QString());
|
||||
auto callback = [=](Fn<void()> &&close) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = Core::App().settings().proxy().list();
|
||||
if (!ranges::contains(proxies, proxy)) {
|
||||
proxies.push_back(proxy);
|
||||
}
|
||||
@@ -1139,7 +1147,7 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
auto ProxiesBoxController::proxySettingsValue() const
|
||||
-> rpl::producer<ProxyData::Settings> {
|
||||
return _proxySettingsChanges.events_starting_with_copy(
|
||||
Global::ProxySettings()
|
||||
_settings.settings()
|
||||
) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
@@ -1150,6 +1158,7 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
: Variants::Tcp;
|
||||
const auto mtproto = &_account->mtp();
|
||||
const auto dcId = mtproto->mainDcId();
|
||||
const auto forFiles = false;
|
||||
|
||||
item.state = ItemState::Checking;
|
||||
const auto setup = [&](Checker &checker, const bytes::vector &secret) {
|
||||
@@ -1168,7 +1177,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
item.data.host,
|
||||
item.data.port,
|
||||
secret,
|
||||
dcId);
|
||||
dcId,
|
||||
forFiles);
|
||||
item.checkerv6 = nullptr;
|
||||
} else {
|
||||
const auto options = mtproto->dcOptions().lookup(
|
||||
@@ -1180,7 +1190,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
Variants::Address address) {
|
||||
const auto &list = options.data[address][type];
|
||||
if (list.empty()
|
||||
|| (address == Variants::IPv6 && !Global::TryIPv6())) {
|
||||
|| ((address == Variants::IPv6)
|
||||
&& !Core::App().settings().proxy().tryIPv6())) {
|
||||
checker = nullptr;
|
||||
return;
|
||||
}
|
||||
@@ -1190,7 +1201,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
QString::fromStdString(endpoint.ip),
|
||||
endpoint.port,
|
||||
endpoint.secret,
|
||||
dcId);
|
||||
dcId,
|
||||
forFiles);
|
||||
};
|
||||
connect(item.checker, Variants::IPv4);
|
||||
connect(item.checkerv6, Variants::IPv6);
|
||||
@@ -1241,7 +1253,7 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::CreateOwningBox(
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ProxiesBoxController::create() {
|
||||
auto result = Box<ProxiesBox>(this);
|
||||
auto result = Box<ProxiesBox>(this, _settings);
|
||||
for (const auto &item : _list) {
|
||||
updateView(item);
|
||||
}
|
||||
@@ -1279,14 +1291,13 @@ void ProxiesBoxController::shareItem(int id) {
|
||||
|
||||
void ProxiesBoxController::applyItem(int id) {
|
||||
auto item = findById(id);
|
||||
if ((Global::ProxySettings() == ProxyData::Settings::Enabled)
|
||||
&& Global::SelectedProxy() == item->data) {
|
||||
if (_settings.isEnabled() && (_settings.selected() == item->data)) {
|
||||
return;
|
||||
} else if (item->deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto j = findByProxy(Global::SelectedProxy());
|
||||
auto j = findByProxy(_settings.selected());
|
||||
|
||||
Core::App().setCurrentProxy(
|
||||
item->data,
|
||||
@@ -1304,12 +1315,13 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
item->deleted = deleted;
|
||||
|
||||
if (deleted) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
proxies.erase(ranges::remove(proxies, item->data), end(proxies));
|
||||
|
||||
if (item->data == Global::SelectedProxy()) {
|
||||
_lastSelectedProxy = base::take(Global::RefSelectedProxy());
|
||||
if (Global::ProxySettings() == ProxyData::Settings::Enabled) {
|
||||
if (item->data == _settings.selected()) {
|
||||
_lastSelectedProxy = _settings.selected();
|
||||
_settings.setSelected(MTP::ProxyData());
|
||||
if (_settings.isEnabled()) {
|
||||
_lastSelectedProxyUsed = true;
|
||||
Core::App().setCurrentProxy(
|
||||
ProxyData(),
|
||||
@@ -1320,7 +1332,7 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
if (ranges::find(proxies, item->data) == end(proxies)) {
|
||||
auto insertBefore = item + 1;
|
||||
while (insertBefore != end(_list) && insertBefore->deleted) {
|
||||
@@ -1332,15 +1344,15 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
proxies.insert(insertBeforeIt, item->data);
|
||||
}
|
||||
|
||||
if (!Global::SelectedProxy() && _lastSelectedProxy == item->data) {
|
||||
Assert(Global::ProxySettings() != ProxyData::Settings::Enabled);
|
||||
if (!_settings.selected() && _lastSelectedProxy == item->data) {
|
||||
Assert(!_settings.isEnabled());
|
||||
|
||||
if (base::take(_lastSelectedProxyUsed)) {
|
||||
Core::App().setCurrentProxy(
|
||||
base::take(_lastSelectedProxy),
|
||||
ProxyData::Settings::Enabled);
|
||||
} else {
|
||||
Global::SetSelectedProxy(base::take(_lastSelectedProxy));
|
||||
_settings.setSelected(base::take(_lastSelectedProxy));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1368,7 +1380,7 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::editItemBox(int id) {
|
||||
void ProxiesBoxController::replaceItemWith(
|
||||
std::vector<Item>::iterator which,
|
||||
std::vector<Item>::iterator with) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
proxies.erase(ranges::remove(proxies, which->data), end(proxies));
|
||||
|
||||
_views.fire({ which->id });
|
||||
@@ -1388,7 +1400,7 @@ void ProxiesBoxController::replaceItemValue(
|
||||
restoreItem(which->id);
|
||||
}
|
||||
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
const auto i = ranges::find(proxies, which->data);
|
||||
Assert(i != end(proxies));
|
||||
*i = proxy;
|
||||
@@ -1419,7 +1431,7 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::addNewItemBox() {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
proxies.push_back(proxy);
|
||||
|
||||
_list.push_back({ ++_idCounter, proxy });
|
||||
@@ -1428,43 +1440,42 @@ void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
|
||||
}
|
||||
|
||||
bool ProxiesBoxController::setProxySettings(ProxyData::Settings value) {
|
||||
if (Global::ProxySettings() == value) {
|
||||
if (_settings.settings() == value) {
|
||||
return true;
|
||||
} else if (value == ProxyData::Settings::Enabled) {
|
||||
if (Global::ProxiesList().empty()) {
|
||||
if (_settings.list().empty()) {
|
||||
return false;
|
||||
} else if (!Global::SelectedProxy()) {
|
||||
Global::SetSelectedProxy(Global::ProxiesList().back());
|
||||
auto j = findByProxy(Global::SelectedProxy());
|
||||
} else if (!_settings.selected()) {
|
||||
_settings.setSelected(_settings.list().back());
|
||||
auto j = findByProxy(_settings.selected());
|
||||
if (j != end(_list)) {
|
||||
updateView(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
Core::App().setCurrentProxy(Global::SelectedProxy(), value);
|
||||
Core::App().setCurrentProxy(_settings.selected(), value);
|
||||
saveDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setProxyForCalls(bool enabled) {
|
||||
if (Global::UseProxyForCalls() == enabled) {
|
||||
if (_settings.useProxyForCalls() == enabled) {
|
||||
return;
|
||||
}
|
||||
Global::SetUseProxyForCalls(enabled);
|
||||
if ((Global::ProxySettings() == ProxyData::Settings::Enabled)
|
||||
&& Global::SelectedProxy().supportsCalls()) {
|
||||
Global::RefConnectionTypeChanged().notify();
|
||||
_settings.setUseProxyForCalls(enabled);
|
||||
if (_settings.isEnabled() && _settings.selected().supportsCalls()) {
|
||||
_settings.connectionTypeChangesNotify();
|
||||
}
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setTryIPv6(bool enabled) {
|
||||
if (Global::TryIPv6() == enabled) {
|
||||
if (Core::App().settings().proxy().tryIPv6() == enabled) {
|
||||
return;
|
||||
}
|
||||
Global::SetTryIPv6(enabled);
|
||||
Core::App().settings().proxy().setTryIPv6(enabled);
|
||||
_account->mtp().restart();
|
||||
Global::RefConnectionTypeChanged().notify();
|
||||
_settings.connectionTypeChangesNotify();
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
@@ -1477,7 +1488,7 @@ auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::updateView(const Item &item) {
|
||||
const auto selected = (Global::SelectedProxy() == item.data);
|
||||
const auto selected = (_settings.selected() == item.data);
|
||||
const auto deleted = item.deleted;
|
||||
const auto type = [&] {
|
||||
switch (item.data.type) {
|
||||
@@ -1491,8 +1502,7 @@ void ProxiesBoxController::updateView(const Item &item) {
|
||||
Unexpected("Proxy type in ProxiesBoxController::updateView.");
|
||||
}();
|
||||
const auto state = [&] {
|
||||
if (!selected
|
||||
|| (Global::ProxySettings() != ProxyData::Settings::Enabled)) {
|
||||
if (!selected || !_settings.isEnabled()) {
|
||||
return item.state;
|
||||
} else if (_account->mtp().dcstate() == MTP::ConnectedState) {
|
||||
return ItemState::Online;
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace Main {
|
||||
class Account;
|
||||
} // namespace Main
|
||||
|
||||
class ProxiesBoxController : public base::Subscriber {
|
||||
class ProxiesBoxController {
|
||||
public:
|
||||
using ProxyData = MTP::ProxyData;
|
||||
using Type = ProxyData::Type;
|
||||
@@ -110,6 +111,7 @@ private:
|
||||
void addNewItem(const ProxyData &proxy);
|
||||
|
||||
const not_null<Main::Account*> _account;
|
||||
Core::SettingsProxy &_settings;
|
||||
int _idCounter = 0;
|
||||
std::vector<Item> _list;
|
||||
rpl::event_stream<ItemView> _views;
|
||||
@@ -119,4 +121,6 @@ private:
|
||||
ProxyData _lastSelectedProxy;
|
||||
bool _lastSelectedProxyUsed = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -1085,7 +1085,7 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
send({ .silent = true });
|
||||
};
|
||||
const auto sendScheduled = [=] {
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
SendMenu::Type::Scheduled,
|
||||
|
||||
@@ -27,8 +27,8 @@ public:
|
||||
return _y;
|
||||
}
|
||||
|
||||
base::Observable<void> &changed() {
|
||||
return _changed;
|
||||
rpl::producer<> changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
void setHSB(HSB hsb);
|
||||
void setRGB(int red, int green, int blue);
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
float64 _y = 0.;
|
||||
|
||||
bool _choosing = false;
|
||||
base::Observable<void> _changed;
|
||||
rpl::event_stream<> _changed;
|
||||
|
||||
};
|
||||
|
||||
@@ -234,7 +234,7 @@ void EditColorBox::Picker::updateCurrentPoint(QPoint localPosition) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
update();
|
||||
_changed.notify();
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,8 +284,8 @@ public:
|
||||
};
|
||||
Slider(QWidget *parent, Direction direction, Type type, QColor color);
|
||||
|
||||
base::Observable<void> &changed() {
|
||||
return _changed;
|
||||
rpl::producer<> changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
float64 value() const {
|
||||
return _value;
|
||||
@@ -335,7 +335,7 @@ private:
|
||||
QBrush _transparent;
|
||||
|
||||
bool _choosing = false;
|
||||
base::Observable<void> _changed;
|
||||
rpl::event_stream<> _changed;
|
||||
|
||||
};
|
||||
|
||||
@@ -540,7 +540,7 @@ void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {
|
||||
if (_value != value) {
|
||||
_value = value;
|
||||
update();
|
||||
_changed.notify();
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,16 +826,14 @@ void EditColorBox::prepare() {
|
||||
auto height = st::colorEditSkip + st::colorPickerSize + st::colorEditSkip + st::colorSliderWidth + st::colorEditSkip;
|
||||
setDimensions(st::colorEditWidth, height);
|
||||
|
||||
subscribe(_picker->changed(), [=] { updateFromControls(); });
|
||||
if (_hueSlider) {
|
||||
subscribe(_hueSlider->changed(), [=] { updateFromControls(); });
|
||||
}
|
||||
if (_opacitySlider) {
|
||||
subscribe(_opacitySlider->changed(), [=] { updateFromControls(); });
|
||||
}
|
||||
if (_lightnessSlider) {
|
||||
subscribe(_lightnessSlider->changed(), [=] { updateFromControls(); });
|
||||
}
|
||||
rpl::merge(
|
||||
_picker->changed(),
|
||||
(_hueSlider ? _hueSlider->changed() : rpl::never<>()),
|
||||
(_opacitySlider ? _opacitySlider->changed() : rpl::never<>()),
|
||||
(_lightnessSlider ? _lightnessSlider->changed() : rpl::never<>())
|
||||
) | rpl::start_with_next([=] {
|
||||
updateFromControls();
|
||||
}, lifetime());
|
||||
|
||||
boxClosing() | rpl::start_with_next([=] {
|
||||
if (_cancelCallback) {
|
||||
|
||||
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
class EditColorBox : public Ui::BoxContent, private base::Subscriber {
|
||||
class EditColorBox : public Ui::BoxContent {
|
||||
public:
|
||||
enum class Mode {
|
||||
RGBA,
|
||||
|
||||
@@ -165,7 +165,7 @@ void EditPrivacyBox::editExceptions(
|
||||
}));
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
Ui::show(
|
||||
_window->show(
|
||||
Box<PeerListBox>(std::move(controller), std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -1166,15 +1166,19 @@ base::binary_guard LanguageBox::Show() {
|
||||
if (manager.languageList().empty()) {
|
||||
auto guard = std::make_shared<base::binary_guard>(
|
||||
result.make_guard());
|
||||
auto alive = std::make_shared<std::unique_ptr<base::Subscription>>(
|
||||
std::make_unique<base::Subscription>());
|
||||
**alive = manager.languageListChanged().add_subscription([=] {
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
manager.languageListChanged(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=]() mutable {
|
||||
const auto show = guard->alive();
|
||||
*alive = nullptr;
|
||||
if (lifetime) {
|
||||
base::take(lifetime)->destroy();
|
||||
}
|
||||
if (show) {
|
||||
Ui::show(Box<LanguageBox>());
|
||||
}
|
||||
});
|
||||
}, *lifetime);
|
||||
} else {
|
||||
Ui::show(Box<LanguageBox>());
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "passport/passport_encryption.h"
|
||||
#include "passport/passport_panel_edit_contact.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -119,7 +118,12 @@ PasscodeBox::PasscodeBox(
|
||||
, _turningOff(turningOff)
|
||||
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
|
||||
, _newPasscode(this, st::defaultInputField, Global::LocalPasscode() ? tr::lng_passcode_enter_new() : tr::lng_passcode_enter_first())
|
||||
, _newPasscode(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
session->domain().local().hasLocalPasscode()
|
||||
? tr::lng_passcode_enter_new()
|
||||
: tr::lng_passcode_enter_first())
|
||||
, _reenterPasscode(this, st::defaultInputField, tr::lng_passcode_confirm_new())
|
||||
, _passwordHint(this, st::defaultInputField, tr::lng_cloud_password_hint())
|
||||
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
|
||||
@@ -164,7 +168,9 @@ rpl::producer<> PasscodeBox::clearUnconfirmedPassword() const {
|
||||
}
|
||||
|
||||
bool PasscodeBox::currentlyHave() const {
|
||||
return _cloudPwd ? (!!_cloudFields.curRequest) : Global::LocalPasscode();
|
||||
return _cloudPwd
|
||||
? (!!_cloudFields.curRequest)
|
||||
: _session->domain().local().hasLocalPasscode();
|
||||
}
|
||||
|
||||
bool PasscodeBox::onlyCheckCurrent() const {
|
||||
@@ -520,7 +526,7 @@ void PasscodeBox::save(bool force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Core::App().domain().local().checkPasscode(old.toUtf8())) {
|
||||
if (_session->domain().local().checkPasscode(old.toUtf8())) {
|
||||
cSetPasscodeBadTries(0);
|
||||
if (_turningOff) pwd = conf = QString();
|
||||
} else {
|
||||
@@ -588,7 +594,7 @@ void PasscodeBox::save(bool force) {
|
||||
closeReplacedBy();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
cSetPasscodeBadTries(0);
|
||||
Core::App().domain().local().setPasscode(pwd.toUtf8());
|
||||
_session->domain().local().setPasscode(pwd.toUtf8());
|
||||
Core::App().localPasscodeChanged();
|
||||
if (weak) {
|
||||
closeBox();
|
||||
|
||||
@@ -201,8 +201,10 @@ void SaveSlowmodeSeconds(
|
||||
void ShowEditPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto box = Ui::show(
|
||||
Box<EditPeerPermissionsBox>(navigation, peer),
|
||||
auto content = Box<EditPeerPermissionsBox>(navigation, peer);
|
||||
const auto box = QPointer<EditPeerPermissionsBox>(content.data());
|
||||
navigation->parentController()->show(
|
||||
std::move(content),
|
||||
Ui::LayerOption::KeepOther);
|
||||
const auto saving = box->lifetime().make_state<int>(0);
|
||||
const auto save = [=](
|
||||
@@ -244,8 +246,10 @@ void ShowEditPermissions(
|
||||
void ShowEditInviteLinks(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto box = Ui::show(
|
||||
Box<EditPeerPermissionsBox>(navigation, peer),
|
||||
auto content = Box<EditPeerPermissionsBox>(navigation, peer);
|
||||
const auto box = QPointer<EditPeerPermissionsBox>(content.data());
|
||||
navigation->parentController()->show(
|
||||
std::move(content),
|
||||
Ui::LayerOption::KeepOther);
|
||||
const auto saving = box->lifetime().make_state<int>(0);
|
||||
const auto save = [=](
|
||||
@@ -612,7 +616,7 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
|
||||
tr::lng_group_stickers_add(tr::now),
|
||||
st::editPeerInviteLinkButton)
|
||||
)->addClickHandler([=] {
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<StickersBox>(_navigation->parentController(), channel),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
@@ -649,7 +653,7 @@ void Controller::showEditPeerTypeBox(
|
||||
_usernameSavedValue = publicLink;
|
||||
refreshHistoryVisibility();
|
||||
});
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<EditPeerTypeBox>(
|
||||
_peer,
|
||||
_channelHasLocationOriginalValue,
|
||||
@@ -681,7 +685,7 @@ void Controller::showEditLinkedChatBox() {
|
||||
|| channel->canEditPreHistoryHidden()));
|
||||
|
||||
if (const auto chat = *_linkedChatSavedValue) {
|
||||
*box = Ui::show(
|
||||
*box = _navigation->parentController()->show(
|
||||
EditLinkedChatBox(
|
||||
_navigation,
|
||||
channel,
|
||||
@@ -709,7 +713,7 @@ void Controller::showEditLinkedChatBox() {
|
||||
for (const auto &item : list) {
|
||||
chats.emplace_back(_peer->owner().processChat(item));
|
||||
}
|
||||
*box = Ui::show(
|
||||
*box = _navigation->parentController()->show(
|
||||
EditLinkedChatBox(
|
||||
_navigation,
|
||||
channel,
|
||||
@@ -858,7 +862,7 @@ void Controller::fillHistoryVisibilityButton() {
|
||||
_historyVisibilitySavedValue = checked;
|
||||
});
|
||||
const auto buttonCallback = [=] {
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<EditPeerHistoryVisibilityBox>(
|
||||
_peer,
|
||||
boxCallback,
|
||||
@@ -1023,9 +1027,15 @@ void Controller::fillManageSection() {
|
||||
wrap->entity(),
|
||||
tr::lng_manage_peer_invite_links(),
|
||||
rpl::duplicate(count) | ToPositiveNumberString(),
|
||||
[=] { Ui::show(
|
||||
Box(ManageInviteLinksBox, _peer, _peer->session().user(), 0, 0),
|
||||
Ui::LayerOption::KeepOther);
|
||||
[=] {
|
||||
_navigation->parentController()->show(
|
||||
Box(
|
||||
ManageInviteLinksBox,
|
||||
_peer,
|
||||
_peer->session().user(),
|
||||
0,
|
||||
0),
|
||||
Ui::LayerOption::KeepOther);
|
||||
},
|
||||
st::infoIconInviteLinks);
|
||||
|
||||
@@ -1520,7 +1530,7 @@ void Controller::deleteWithConfirmation() {
|
||||
const auto deleteCallback = crl::guard(this, [=] {
|
||||
deleteChannel();
|
||||
});
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<ConfirmBox>(
|
||||
text,
|
||||
tr::lng_box_delete(tr::now),
|
||||
|
||||
@@ -133,8 +133,8 @@ QImage QrExact(const Qr::Data &data, int pixel, QColor color) {
|
||||
skip,
|
||||
skip,
|
||||
Intro::details::TelegramLogoImage().scaled(
|
||||
logoSize,
|
||||
logoSize,
|
||||
logoSize * cIntRetinaFactor(),
|
||||
logoSize * cIntRetinaFactor(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
@@ -1002,7 +1002,7 @@ void SendFilesBox::sendScheduled() {
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: _sendMenuType;
|
||||
const auto callback = [=](Api::SendOptions options) { send(options); };
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
HistoryView::PrepareScheduleBox(this, type, callback),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -63,12 +63,14 @@ public:
|
||||
bool loaded() const;
|
||||
bool notInstalled() const;
|
||||
bool official() const;
|
||||
rpl::producer<TextWithEntities> title() const;
|
||||
QString shortName() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> title() const;
|
||||
[[nodiscard]] QString shortName() const;
|
||||
|
||||
void install();
|
||||
rpl::producer<uint64> setInstalled() const;
|
||||
rpl::producer<> updateControls() const;
|
||||
[[nodiscard]] rpl::producer<uint64> setInstalled() const;
|
||||
[[nodiscard]] rpl::producer<> updateControls() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Error> errors() const;
|
||||
|
||||
~Inner();
|
||||
|
||||
@@ -137,6 +139,7 @@ private:
|
||||
|
||||
rpl::event_stream<uint64> _setInstalled;
|
||||
rpl::event_stream<> _updateControls;
|
||||
rpl::event_stream<Error> _errors;
|
||||
|
||||
};
|
||||
|
||||
@@ -153,7 +156,7 @@ QPointer<Ui::BoxContent> StickerSetBox::Show(
|
||||
not_null<DocumentData*> document) {
|
||||
if (const auto sticker = document->sticker()) {
|
||||
if (sticker->set.type() != mtpc_inputStickerSetEmpty) {
|
||||
return Ui::show(
|
||||
return controller->show(
|
||||
Box<StickerSetBox>(controller, sticker->set),
|
||||
Ui::LayerOption::KeepOther).data();
|
||||
}
|
||||
@@ -186,6 +189,11 @@ void StickerSetBox::prepare() {
|
||||
_controller->session().api().stickerSetInstalled(setId);
|
||||
closeBox();
|
||||
}, lifetime());
|
||||
|
||||
_inner->errors(
|
||||
) | rpl::start_with_next([=](Error error) {
|
||||
handleError(error);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickerSetBox::addStickers() {
|
||||
@@ -198,6 +206,20 @@ void StickerSetBox::copyStickersLink() {
|
||||
QGuiApplication::clipboard()->setText(url);
|
||||
}
|
||||
|
||||
void StickerSetBox::handleError(Error error) {
|
||||
const auto guard = gsl::finally(crl::guard(this, [=] {
|
||||
closeBox();
|
||||
}));
|
||||
|
||||
switch (error) {
|
||||
case Error::NotFound:
|
||||
_controller->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
break;
|
||||
default: Unexpected("Error in StickerSetBox::handleError.");
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::archiveStickers() {
|
||||
const auto weak = base::make_weak(_controller.get());
|
||||
const auto setId = _set.c_inputStickerSetID().vid().v;
|
||||
@@ -211,7 +233,7 @@ void StickerSetBox::archiveStickers() {
|
||||
}
|
||||
if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
|
||||
Ui::Toast::Show(tr::lng_stickers_has_been_archived(tr::now));
|
||||
|
||||
|
||||
const auto &session = controller->session();
|
||||
auto &order = session.data().stickers().setsOrderRef();
|
||||
const auto index = order.indexOf(setId);
|
||||
@@ -219,10 +241,10 @@ void StickerSetBox::archiveStickers() {
|
||||
return;
|
||||
}
|
||||
order.removeAt(index);
|
||||
|
||||
|
||||
session.local().writeInstalledStickers();
|
||||
session.local().writeArchivedStickers();
|
||||
|
||||
|
||||
session.data().stickers().notifyUpdated();
|
||||
}
|
||||
}).fail([](const MTP::Error &error) {
|
||||
@@ -324,7 +346,7 @@ StickerSetBox::Inner::Inner(
|
||||
gotSet(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_loaded = true;
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
_errors.fire(Error::NotFound);
|
||||
}).send();
|
||||
|
||||
_controller->session().api().updateStickers();
|
||||
@@ -419,7 +441,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
});
|
||||
|
||||
if (_pack.isEmpty()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
_errors.fire(Error::NotFound);
|
||||
return;
|
||||
} else {
|
||||
int32 rows = _pack.size() / kStickersPanelPerRow + ((_pack.size() % kStickersPanelPerRow) ? 1 : 0);
|
||||
@@ -439,6 +461,10 @@ rpl::producer<> StickerSetBox::Inner::updateControls() const {
|
||||
return _updateControls.events();
|
||||
}
|
||||
|
||||
rpl::producer<StickerSetBox::Error> StickerSetBox::Inner::errors() const {
|
||||
return _errors.events();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::installDone(
|
||||
const MTPmessages_StickerSetInstallResult &result) {
|
||||
auto &sets = _controller->session().data().stickers().setsRef();
|
||||
@@ -795,7 +821,7 @@ QString StickerSetBox::Inner::shortName() const {
|
||||
|
||||
void StickerSetBox::Inner::install() {
|
||||
if (isMasksSet()) {
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<InformBox>(tr::lng_stickers_masks_pack(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
@@ -808,7 +834,7 @@ void StickerSetBox::Inner::install() {
|
||||
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
|
||||
installDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
_errors.fire(Error::NotFound);
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,16 @@ protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
enum class Error {
|
||||
NotFound,
|
||||
};
|
||||
|
||||
void updateTitleAndButtons();
|
||||
void updateButtons();
|
||||
void addStickers();
|
||||
void copyStickersLink();
|
||||
void archiveStickers();
|
||||
void handleError(Error error);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTPInputStickerSet _set;
|
||||
|
||||
@@ -68,9 +68,7 @@ private:
|
||||
};
|
||||
|
||||
// This class is hold in header because it requires Qt preprocessing.
|
||||
class StickersBox::Inner
|
||||
: public Ui::RpWidget
|
||||
, private base::Subscriber {
|
||||
class StickersBox::Inner : public Ui::RpWidget {
|
||||
public:
|
||||
using Section = StickersBox::Section;
|
||||
|
||||
@@ -85,7 +83,9 @@ public:
|
||||
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
base::Observable<int> scrollToY;
|
||||
rpl::producer<int> scrollsToY() const {
|
||||
return _scrollsToY.events();
|
||||
}
|
||||
void setInnerFocus();
|
||||
|
||||
void saveGroupSet();
|
||||
@@ -276,6 +276,8 @@ private:
|
||||
int _above = -1;
|
||||
rpl::event_stream<int> _draggingScrollDelta;
|
||||
|
||||
rpl::event_stream<int> _scrollsToY;
|
||||
|
||||
int _minHeight = 0;
|
||||
|
||||
int _scrollbar = 0;
|
||||
@@ -387,9 +389,10 @@ StickersBox::StickersBox(
|
||||
, _section(Section::Installed)
|
||||
, _installed(0, this, controller, megagroup)
|
||||
, _megagroupSet(megagroup) {
|
||||
subscribe(_installed.widget()->scrollToY, [=](int y) {
|
||||
_installed.widget()->scrollsToY(
|
||||
) | rpl::start_with_next([=](int y) {
|
||||
onScrollToY(y);
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
StickersBox::StickersBox(
|
||||
@@ -1589,7 +1592,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}();
|
||||
const auto showSetByRow = [&](const Row &row) {
|
||||
setSelected(SelectedRow());
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<StickerSetBox>(_controller, row.set->mtpInput()),
|
||||
Ui::LayerOption::KeepOther);
|
||||
};
|
||||
@@ -1898,7 +1901,7 @@ void StickersBox::Inner::rebuild() {
|
||||
void StickersBox::Inner::setMegagroupSelectedSet(const MTPInputStickerSet &set) {
|
||||
_megagroupSetInput = set;
|
||||
rebuild();
|
||||
scrollToY.notify(0, true);
|
||||
_scrollsToY.fire(0);
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
|
||||
@@ -490,6 +490,7 @@ groupCallMenu: Menu(defaultMenu) {
|
||||
itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
|
||||
|
||||
separatorFg: groupCallMenuBgOver;
|
||||
separatorPadding: margins(0px, 4px, 0px, 4px);
|
||||
|
||||
arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }};
|
||||
|
||||
@@ -513,6 +514,16 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) {
|
||||
menu: groupCallMenu;
|
||||
animation: groupCallPanelAnimation;
|
||||
}
|
||||
groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) {
|
||||
scrollPadding: margins(0px, 3px, 0px, 8px);
|
||||
menu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
}
|
||||
}
|
||||
groupCallPopupVolumeMenu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
itemBgOver: groupCallMenuBg;
|
||||
}
|
||||
|
||||
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
|
||||
groupCallRecordingTimerFont: font(12px);
|
||||
@@ -862,7 +873,7 @@ groupCallButtonSkip: 40px;
|
||||
groupCallButtonSkipSmall: 5px;
|
||||
groupCallButtonBottomSkip: 113px;
|
||||
groupCallButtonBottomSkipSmall: 95px;
|
||||
groupCallButtonBottomSkipWide: 122px;
|
||||
groupCallButtonBottomSkipWide: 108px;
|
||||
groupCallControlsBackMargin: margins(10px, 0px, 10px, 0px);
|
||||
groupCallControlsBackRadius: 12px;
|
||||
groupCallMuteBottomSkip: 116px;
|
||||
@@ -1071,13 +1082,17 @@ groupCallMuteCrossLine: CrossLineAnimation {
|
||||
|
||||
groupCallMenuSpeakerArcsSkip: 1px;
|
||||
groupCallMenuVolumeSkip: 5px;
|
||||
groupCallMenuVolumePadding: margins(17px, 6px, 17px, 5px);
|
||||
groupCallMenuVolumeMargin: margins(55px, 0px, 15px, 0px);
|
||||
groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
|
||||
activeFg: groupCallMembersFg;
|
||||
inactiveFg: groupCallMemberInactiveIcon;
|
||||
inactiveFg: groupCallMembersBgOver;
|
||||
activeFgOver: groupCallMembersFg;
|
||||
inactiveFgOver: groupCallMemberInactiveIcon;
|
||||
activeFgDisabled: groupCallMemberInactiveIcon;
|
||||
receivedTillFg: groupCallMemberInactiveIcon;
|
||||
inactiveFgOver: groupCallMembersBgOver;
|
||||
activeFgDisabled: groupCallMembersBgOver;
|
||||
receivedTillFg: groupCallMembersBgOver;
|
||||
width: 7px;
|
||||
seekSize: size(7px, 7px);
|
||||
}
|
||||
|
||||
groupCallSpeakerArcsAnimation: ArcsAnimation {
|
||||
@@ -1229,6 +1244,9 @@ groupCallVideoTile: GroupCallVideoTile {
|
||||
|
||||
groupCallVideoSmallSkip: 4px;
|
||||
groupCallVideoLargeSkip: 6px;
|
||||
groupCallVideoPlaceholderHeight: 212px;
|
||||
groupCallVideoPlaceholderIconTop: 50px;
|
||||
groupCallVideoPlaceholderTextTop: 120px;
|
||||
|
||||
groupCallTooltip: Tooltip(defaultTooltip) {
|
||||
textBg: groupCallMembersBg;
|
||||
@@ -1248,5 +1266,16 @@ groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
|
||||
linkFontOver: font(11px underline);
|
||||
}
|
||||
}
|
||||
groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) {
|
||||
padding: margins(10px, 1px, 6px, 3px);
|
||||
}
|
||||
groupCallStickedTooltipClose: IconButton(defaultIconButton) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
iconPosition: point(4px, 3px);
|
||||
icon: icon {{ "calls/video_tooltip", importantTooltipFg }};
|
||||
iconOver: icon {{ "calls/video_tooltip", importantTooltipFg }};
|
||||
ripple: emptyRippleAnimation;
|
||||
}
|
||||
groupCallNiceTooltipTop: 4px;
|
||||
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};
|
||||
|
||||
@@ -357,7 +357,7 @@ base::unique_qptr<Ui::PopupMenu> BoxController::rowContextMenu(
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||
result->addAction(tr::lng_context_delete_selected(tr::now), [=] {
|
||||
Ui::show(
|
||||
_window->show(
|
||||
Box<DeleteMessagesBox>(session, base::duplicate(ids)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "facades.h"
|
||||
|
||||
#include <tgcalls/Instance.h>
|
||||
#include <tgcalls/VideoCaptureInterface.h>
|
||||
@@ -805,16 +804,19 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
AppendServer(descriptor.rtcServers, connection);
|
||||
}
|
||||
|
||||
if (Global::UseProxyForCalls()
|
||||
&& (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)) {
|
||||
const auto &selected = Global::SelectedProxy();
|
||||
if (selected.supportsCalls() && !selected.host.isEmpty()) {
|
||||
Assert(selected.type == MTP::ProxyData::Type::Socks5);
|
||||
descriptor.proxy = std::make_unique<tgcalls::Proxy>();
|
||||
descriptor.proxy->host = selected.host.toStdString();
|
||||
descriptor.proxy->port = selected.port;
|
||||
descriptor.proxy->login = selected.user.toStdString();
|
||||
descriptor.proxy->password = selected.password.toStdString();
|
||||
{
|
||||
auto &settingsProxy = Core::App().settings().proxy();
|
||||
using ProxyData = MTP::ProxyData;
|
||||
if (settingsProxy.useProxyForCalls() && settingsProxy.isEnabled()) {
|
||||
const auto &selected = settingsProxy.selected();
|
||||
if (selected.supportsCalls() && !selected.host.isEmpty()) {
|
||||
Assert(selected.type == ProxyData::Type::Socks5);
|
||||
descriptor.proxy = std::make_unique<tgcalls::Proxy>();
|
||||
descriptor.proxy->host = selected.host.toStdString();
|
||||
descriptor.proxy->port = selected.port;
|
||||
descriptor.proxy->login = selected.user.toStdString();
|
||||
descriptor.proxy->password = selected.password.toStdString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "window/main_window.h"
|
||||
#include "media/view/media_view_pip.h" // Utilities for frame rotation.
|
||||
#include "app.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "styles/style_calls.h"
|
||||
@@ -51,16 +50,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace Calls {
|
||||
|
||||
Panel::Panel(not_null<Call*> call)
|
||||
: _call(call)
|
||||
, _user(call->user())
|
||||
, _window(createWindow())
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
_window->body(),
|
||||
widget(),
|
||||
st::callTitle,
|
||||
[=](bool maximized) { toggleFullScreen(maximized); }))
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -86,46 +85,27 @@ Panel::Panel(not_null<Call*> call)
|
||||
|
||||
Panel::~Panel() = default;
|
||||
|
||||
std::unique_ptr<Ui::Window> Panel::createWindow() {
|
||||
auto result = std::make_unique<Ui::Window>();
|
||||
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: Platform::IsWindows()
|
||||
? capabilities.supported
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (Incoming)").arg(Logs::b(use)));
|
||||
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
|
||||
|
||||
if (use) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We have to create a new window, if OpenGL initialization failed.
|
||||
return std::make_unique<Ui::Window>();
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return _window->isActiveWindow()
|
||||
&& _window->isVisible()
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
if (window()->isHidden()) {
|
||||
window()->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
const auto state = window()->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
window()->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
window()->setFocus();
|
||||
}
|
||||
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::replaceCall(not_null<Call*> call) {
|
||||
@@ -134,26 +114,26 @@ void Panel::replaceCall(not_null<Call*> call) {
|
||||
}
|
||||
|
||||
void Panel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
window()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
window()->setAttribute(Qt::WA_NoSystemBackground);
|
||||
window()->setWindowIcon(
|
||||
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitle(u" "_q);
|
||||
_window->setTitleStyle(st::callTitle);
|
||||
window()->setTitle(u" "_q);
|
||||
window()->setTitleStyle(st::callTitle);
|
||||
|
||||
_window->events(
|
||||
window()->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close) {
|
||||
handleClose();
|
||||
} else if (e->type() == QEvent::KeyPress) {
|
||||
if ((static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape)
|
||||
&& _window->isFullScreen()) {
|
||||
_window->showNormal();
|
||||
&& window()->isFullScreen()) {
|
||||
window()->showNormal();
|
||||
}
|
||||
}
|
||||
}, _window->lifetime());
|
||||
}, window()->lifetime());
|
||||
|
||||
_window->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
window()->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
if (!widget()->rect().contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
@@ -179,28 +159,31 @@ void Panel::initWindow() {
|
||||
: (Flag::Move | Flag::FullScreen);
|
||||
});
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows we replace snap-to-top maximizing with fullscreen.
|
||||
//
|
||||
// We have to switch first to showNormal, so that showFullScreen
|
||||
// will remember correct normal window geometry and next showNormal
|
||||
// will show it instead of a moving maximized window.
|
||||
//
|
||||
// We have to do it in InvokeQueued, otherwise it still captures
|
||||
// the maximized window geometry and saves it.
|
||||
//
|
||||
// I couldn't find a less glitchy way to do that *sigh*.
|
||||
const auto object = _window->windowHandle();
|
||||
const auto signal = &QWindow::windowStateChanged;
|
||||
QObject::connect(object, signal, [=](Qt::WindowState state) {
|
||||
if (state == Qt::WindowMaximized) {
|
||||
InvokeQueued(object, [=] {
|
||||
_window->showNormal();
|
||||
_window->showFullScreen();
|
||||
});
|
||||
}
|
||||
});
|
||||
#endif // Q_OS_WIN
|
||||
// Don't do that, it looks awful :(
|
||||
//#ifdef Q_OS_WIN
|
||||
// // On Windows we replace snap-to-top maximizing with fullscreen.
|
||||
// //
|
||||
// // We have to switch first to showNormal, so that showFullScreen
|
||||
// // will remember correct normal window geometry and next showNormal
|
||||
// // will show it instead of a moving maximized window.
|
||||
// //
|
||||
// // We have to do it in InvokeQueued, otherwise it still captures
|
||||
// // the maximized window geometry and saves it.
|
||||
// //
|
||||
// // I couldn't find a less glitchy way to do that *sigh*.
|
||||
// const auto object = window()->windowHandle();
|
||||
// const auto signal = &QWindow::windowStateChanged;
|
||||
// QObject::connect(object, signal, [=](Qt::WindowState state) {
|
||||
// if (state == Qt::WindowMaximized) {
|
||||
// InvokeQueued(object, [=] {
|
||||
// window()->showNormal();
|
||||
// InvokeQueued(object, [=] {
|
||||
// window()->showFullScreen();
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
//#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
void Panel::initWidget() {
|
||||
@@ -339,7 +322,7 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_incoming = std::make_unique<Incoming>(
|
||||
widget(),
|
||||
_call->videoIncoming(),
|
||||
_backend);
|
||||
_window.backend());
|
||||
_incoming->widget()->hide();
|
||||
|
||||
_call->mutedValue(
|
||||
@@ -519,16 +502,20 @@ void Panel::showControls() {
|
||||
}
|
||||
|
||||
void Panel::closeBeforeDestroy() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
reinitWithCall(nullptr);
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::lifetime() {
|
||||
return window()->lifetime();
|
||||
}
|
||||
|
||||
void Panel::initGeometry() {
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto initRect = QRect(0, 0, st::callWidth, st::callHeight);
|
||||
_window->setGeometry(initRect.translated(center - initRect.center()));
|
||||
_window->setMinimumSize({ st::callWidthMin, st::callHeightMin });
|
||||
_window->show();
|
||||
window()->setGeometry(initRect.translated(center - initRect.center()));
|
||||
window()->setMinimumSize({ st::callWidthMin, st::callHeightMin });
|
||||
window()->show();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
@@ -546,9 +533,9 @@ void Panel::refreshOutgoingPreviewInBody(State state) {
|
||||
|
||||
void Panel::toggleFullScreen(bool fullscreen) {
|
||||
if (fullscreen) {
|
||||
_window->showFullScreen();
|
||||
window()->showFullScreen();
|
||||
} else {
|
||||
_window->showNormal();
|
||||
window()->showNormal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,8 +711,12 @@ void Panel::handleClose() {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<Ui::Window*> Panel::window() const {
|
||||
return _window.window();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> Panel::widget() const {
|
||||
return _window->body();
|
||||
return _window.widget();
|
||||
}
|
||||
|
||||
void Panel::stateChanged(State state) {
|
||||
@@ -741,7 +732,7 @@ void Panel::stateChanged(State state) {
|
||||
auto toggleButton = [&](auto &&button, bool visible) {
|
||||
button->toggle(
|
||||
visible,
|
||||
_window->isHidden()
|
||||
window()->isHidden()
|
||||
? anim::type::instant
|
||||
: anim::type::normal);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/object_ptr.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/gl/gl_window.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
@@ -60,6 +61,8 @@ public:
|
||||
void replaceCall(not_null<Call*> call);
|
||||
void closeBeforeDestroy();
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
class Incoming;
|
||||
using State = Call::State;
|
||||
@@ -70,7 +73,7 @@ private:
|
||||
Redial,
|
||||
};
|
||||
|
||||
std::unique_ptr<Ui::Window> createWindow();
|
||||
[[nodiscard]] not_null<Ui::Window*> window() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
|
||||
void paint(QRect clip);
|
||||
@@ -106,8 +109,7 @@ private:
|
||||
Call *_call = nullptr;
|
||||
not_null<UserData*> _user;
|
||||
|
||||
Ui::GL::Backend _backend = Ui::GL::Backend();
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
Ui::GL::Window _window;
|
||||
std::unique_ptr<Incoming> _incoming;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
|
||||
@@ -223,6 +223,7 @@ void Panel::Incoming::RendererGL::paint(
|
||||
Assert(data.format == Webrtc::FrameFormat::YUV420);
|
||||
Assert(!data.yuv420->size.isEmpty());
|
||||
const auto yuv = data.yuv420;
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(f, 1);
|
||||
@@ -230,8 +231,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
@@ -243,8 +244,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -255,8 +256,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
|
||||
@@ -49,6 +49,8 @@ constexpr auto kUpdateSendActionEach = crl::time(500);
|
||||
constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
|
||||
constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000);
|
||||
constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000);
|
||||
constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums.
|
||||
constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums.
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Webrtc::MediaDevices> CreateMediaDevices() {
|
||||
const auto &settings = Core::App().settings();
|
||||
@@ -187,6 +189,28 @@ struct GroupCall::SinkPointer {
|
||||
std::weak_ptr<Webrtc::SinkInterface> data;
|
||||
};
|
||||
|
||||
struct GroupCall::VideoTrack {
|
||||
VideoTrack(bool paused, bool requireARGB32, not_null<PeerData*> peer);
|
||||
|
||||
Webrtc::VideoTrack track;
|
||||
rpl::variable<QSize> trackSize;
|
||||
not_null<PeerData*> peer;
|
||||
rpl::lifetime lifetime;
|
||||
Group::VideoQuality quality = Group::VideoQuality();
|
||||
bool shown = false;
|
||||
};
|
||||
|
||||
GroupCall::VideoTrack::VideoTrack(
|
||||
bool paused,
|
||||
bool requireARGB32,
|
||||
not_null<PeerData*> peer)
|
||||
: track((paused
|
||||
? Webrtc::VideoState::Paused
|
||||
: Webrtc::VideoState::Active),
|
||||
requireARGB32)
|
||||
, peer(peer) {
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGroupCallAdmin(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PeerData*> participantPeer) {
|
||||
@@ -451,6 +475,21 @@ void GroupCall::MediaChannelDescriptionsTask::cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<PeerData*> GroupCall::TrackPeer(
|
||||
const std::unique_ptr<VideoTrack> &track) {
|
||||
return track->peer;
|
||||
}
|
||||
|
||||
not_null<Webrtc::VideoTrack*> GroupCall::TrackPointer(
|
||||
const std::unique_ptr<VideoTrack> &track) {
|
||||
return &track->track;
|
||||
}
|
||||
|
||||
rpl::producer<QSize> GroupCall::TrackSizeValue(
|
||||
const std::unique_ptr<VideoTrack> &track) {
|
||||
return track->trackSize.value();
|
||||
}
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
Group::JoinInfo info,
|
||||
@@ -821,6 +860,9 @@ void GroupCall::setState(State state) {
|
||||
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
|
||||
call->setInCall();
|
||||
}
|
||||
if (!videoIsWorking()) {
|
||||
refreshHasNotShownVideo();
|
||||
}
|
||||
}
|
||||
|
||||
if (false
|
||||
@@ -1043,6 +1085,9 @@ void GroupCall::markEndpointActive(
|
||||
bool paused) {
|
||||
if (!endpoint) {
|
||||
return;
|
||||
} else if (active && !videoIsWorking()) {
|
||||
refreshHasNotShownVideo();
|
||||
return;
|
||||
}
|
||||
const auto i = _activeVideoTracks.find(endpoint);
|
||||
const auto changed = active
|
||||
@@ -1058,35 +1103,39 @@ void GroupCall::markEndpointActive(
|
||||
if (active) {
|
||||
const auto i = _activeVideoTracks.emplace(
|
||||
endpoint,
|
||||
VideoTrack{
|
||||
.track = std::make_unique<Webrtc::VideoTrack>(
|
||||
(paused
|
||||
? Webrtc::VideoState::Paused
|
||||
: Webrtc::VideoState::Active),
|
||||
_requireARGB32),
|
||||
.peer = endpoint.peer,
|
||||
}).first;
|
||||
const auto track = i->second.track.get();
|
||||
if (!track->frameSize().isEmpty()
|
||||
|| track->state() == Webrtc::VideoState::Paused) {
|
||||
std::make_unique<VideoTrack>(
|
||||
paused,
|
||||
_requireARGB32,
|
||||
endpoint.peer)).first;
|
||||
const auto track = &i->second->track;
|
||||
|
||||
track->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto activeTrack = _activeVideoTracks[endpoint].get();
|
||||
const auto size = track->frameSize();
|
||||
if (size.isEmpty()) {
|
||||
track->markFrameShown();
|
||||
} else if (!activeTrack->shown) {
|
||||
activeTrack->shown = true;
|
||||
markTrackShown(endpoint, true);
|
||||
}
|
||||
activeTrack->trackSize = size;
|
||||
}, i->second->lifetime);
|
||||
|
||||
const auto size = track->frameSize();
|
||||
i->second->trackSize = size;
|
||||
if (!size.isEmpty() || paused) {
|
||||
i->second->shown = true;
|
||||
shown = true;
|
||||
} else {
|
||||
auto hasFrame = track->renderNextFrame() | rpl::map([=] {
|
||||
return !track->frameSize().isEmpty();
|
||||
});
|
||||
auto isPaused = track->stateValue(
|
||||
) | rpl::map([=](Webrtc::VideoState state) {
|
||||
return (state == Webrtc::VideoState::Paused);
|
||||
});
|
||||
rpl::merge(
|
||||
std::move(hasFrame),
|
||||
std::move(isPaused)
|
||||
) | rpl::filter([=](bool shouldShow) {
|
||||
return shouldShow;
|
||||
track->stateValue(
|
||||
) | rpl::filter([=](Webrtc::VideoState state) {
|
||||
return (state == Webrtc::VideoState::Paused)
|
||||
&& !_activeVideoTracks[endpoint]->shown;
|
||||
}) | rpl::start_with_next([=] {
|
||||
_activeVideoTracks[endpoint].shownTrackingLifetime.destroy();
|
||||
_activeVideoTracks[endpoint]->shown = true;
|
||||
markTrackShown(endpoint, true);
|
||||
}, i->second.shownTrackingLifetime);
|
||||
}, i->second->lifetime);
|
||||
}
|
||||
addVideoOutput(i->first.id, { track->sink() });
|
||||
} else {
|
||||
@@ -1109,10 +1158,11 @@ void GroupCall::markTrackShown(const VideoEndpoint &endpoint, bool shown) {
|
||||
const auto changed = shown
|
||||
? _shownVideoTracks.emplace(endpoint).second
|
||||
: _shownVideoTracks.remove(endpoint);
|
||||
if (changed) {
|
||||
_videoStreamShownUpdates.fire_copy({ endpoint, shown });
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
if (shown && changed && endpoint.type == VideoEndpointType::Screen) {
|
||||
_videoStreamShownUpdates.fire_copy({ endpoint, shown });
|
||||
if (shown && endpoint.type == VideoEndpointType::Screen) {
|
||||
crl::on_main(this, [=] {
|
||||
if (_shownVideoTracks.contains(endpoint)) {
|
||||
pinVideoEndpoint(endpoint);
|
||||
@@ -1129,7 +1179,7 @@ void GroupCall::markTrackPaused(const VideoEndpoint &endpoint, bool paused) {
|
||||
const auto i = _activeVideoTracks.find(endpoint);
|
||||
Assert(i != end(_activeVideoTracks));
|
||||
|
||||
i->second.track->setState(paused
|
||||
i->second->track.setState(paused
|
||||
? Webrtc::VideoState::Paused
|
||||
: Webrtc::VideoState::Active);
|
||||
}
|
||||
@@ -1606,6 +1656,12 @@ void GroupCall::toggleScheduleStartSubscribed(bool subscribed) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::setNoiseSuppression(bool enabled) {
|
||||
if (_instance) {
|
||||
_instance->setIsNoiseSuppressionEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::addVideoOutput(
|
||||
const std::string &endpoint,
|
||||
not_null<Webrtc::VideoTrack*> track) {
|
||||
@@ -1614,13 +1670,19 @@ void GroupCall::addVideoOutput(
|
||||
|
||||
void GroupCall::setMuted(MuteState mute) {
|
||||
const auto set = [=] {
|
||||
const auto wasMuted = (muted() == MuteState::Muted)
|
||||
|| (muted() == MuteState::PushToTalk);
|
||||
const auto wasRaiseHand = (muted() == MuteState::RaisedHand);
|
||||
const auto was = muted();
|
||||
const auto wasSpeaking = (was == MuteState::Active)
|
||||
|| (was == MuteState::PushToTalk);
|
||||
const auto wasMuted = (was == MuteState::Muted)
|
||||
|| (was == MuteState::PushToTalk);
|
||||
const auto wasRaiseHand = (was == MuteState::RaisedHand);
|
||||
_muted = mute;
|
||||
const auto nowMuted = (muted() == MuteState::Muted)
|
||||
|| (muted() == MuteState::PushToTalk);
|
||||
const auto nowRaiseHand = (muted() == MuteState::RaisedHand);
|
||||
const auto now = muted();
|
||||
const auto nowSpeaking = (now == MuteState::Active)
|
||||
|| (now == MuteState::PushToTalk);
|
||||
const auto nowMuted = (now == MuteState::Muted)
|
||||
|| (now == MuteState::PushToTalk);
|
||||
const auto nowRaiseHand = (now == MuteState::RaisedHand);
|
||||
if (wasMuted != nowMuted || wasRaiseHand != nowRaiseHand) {
|
||||
applyMeInCallLocally();
|
||||
}
|
||||
@@ -1628,6 +1690,14 @@ void GroupCall::setMuted(MuteState mute) {
|
||||
toggleVideo(false);
|
||||
toggleScreenSharing(std::nullopt);
|
||||
}
|
||||
if (wasSpeaking && !nowSpeaking && _joinState.ssrc) {
|
||||
_levelUpdates.fire(LevelUpdate{
|
||||
.ssrc = _joinState.ssrc,
|
||||
.value = 0.f,
|
||||
.voice = false,
|
||||
.me = true,
|
||||
});
|
||||
}
|
||||
};
|
||||
if (mute == MuteState::Active || mute == MuteState::PushToTalk) {
|
||||
_delegate->groupCallRequestPermissionsOrFail(crl::guard(this, set));
|
||||
@@ -1919,6 +1989,10 @@ bool GroupCall::emitShareCameraError() {
|
||||
|
||||
void GroupCall::emitShareCameraError(Error error) {
|
||||
_cameraState = Webrtc::VideoState::Inactive;
|
||||
if (error == Error::CameraFailed
|
||||
&& Webrtc::GetVideoInputList().empty()) {
|
||||
error = Error::NoCamera;
|
||||
}
|
||||
_errors.fire_copy(error);
|
||||
}
|
||||
|
||||
@@ -1972,8 +2046,14 @@ void GroupCall::setupOutgoingVideo() {
|
||||
_cameraCapture = _delegate->groupCallGetVideoCapture(
|
||||
_cameraInputId);
|
||||
if (!_cameraCapture) {
|
||||
return emitShareCameraError(Error::NoCamera);
|
||||
return emitShareCameraError(Error::CameraFailed);
|
||||
}
|
||||
const auto weak = base::make_weak(this);
|
||||
_cameraCapture->setOnFatalError([=] {
|
||||
crl::on_main(weak, [=] {
|
||||
emitShareCameraError(Error::CameraFailed);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_cameraCapture->switchToDevice(_cameraInputId.toStdString());
|
||||
}
|
||||
@@ -2157,6 +2237,8 @@ bool GroupCall::tryCreateController() {
|
||||
return result;
|
||||
},
|
||||
.videoContentType = tgcalls::VideoContentType::Generic,
|
||||
.initialEnableNoiseSuppression
|
||||
= settings.groupCallNoiseSuppression(),
|
||||
.requestMediaChannelDescriptions = [=, call = base::make_weak(this)](
|
||||
const std::vector<uint32_t> &ssrcs,
|
||||
std::function<void(
|
||||
@@ -2203,10 +2285,8 @@ bool GroupCall::tryCreateScreencast() {
|
||||
if (_screenInstance) {
|
||||
return false;
|
||||
}
|
||||
//const auto &settings = Core::App().settings();
|
||||
|
||||
const auto weak = base::make_weak(&_screenInstanceGuard);
|
||||
//const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
|
||||
tgcalls::GroupInstanceDescriptor descriptor = {
|
||||
.threads = tgcalls::StaticThreads::getThreads(),
|
||||
.config = tgcalls::GroupConfig{
|
||||
@@ -2219,20 +2299,6 @@ bool GroupCall::tryCreateScreencast() {
|
||||
.videoCapture = _screenCapture,
|
||||
.videoContentType = tgcalls::VideoContentType::Screencast,
|
||||
};
|
||||
// if (Logs::DebugEnabled()) {
|
||||
// auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
// auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt");
|
||||
// auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
//#ifdef Q_OS_WIN
|
||||
// descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
//#else // Q_OS_WIN
|
||||
// const auto callLogUtf = QFile::encodeName(callLogNative);
|
||||
// descriptor.config.logPath.data.resize(callLogUtf.size());
|
||||
// ranges::copy(callLogUtf, descriptor.config.logPath.data.begin());
|
||||
//#endif // Q_OS_WIN
|
||||
// QFile(callLogPath).remove();
|
||||
// QDir().mkpath(callLogFolder);
|
||||
// }
|
||||
|
||||
LOG(("Call Info: Creating group screen instance"));
|
||||
_screenInstance = std::make_unique<tgcalls::GroupInstanceCustomImpl>(
|
||||
@@ -2381,6 +2447,9 @@ void GroupCall::updateRequestedVideoChannels() {
|
||||
channels.reserve(_activeVideoTracks.size());
|
||||
const auto &camera = cameraSharingEndpoint();
|
||||
const auto &screen = screenSharingEndpoint();
|
||||
auto mediums = 0;
|
||||
auto fullcameras = 0;
|
||||
auto fullscreencasts = 0;
|
||||
for (const auto &[endpoint, video] : _activeVideoTracks) {
|
||||
const auto &endpointId = endpoint.id;
|
||||
if (endpointId == camera || endpointId == screen) {
|
||||
@@ -2393,23 +2462,74 @@ void GroupCall::updateRequestedVideoChannels() {
|
||||
if (!params) {
|
||||
continue;
|
||||
}
|
||||
const auto min = (video->quality == Group::VideoQuality::Full
|
||||
&& endpoint.type == VideoEndpointType::Screen)
|
||||
? Quality::Full
|
||||
: Quality::Thumbnail;
|
||||
const auto max = (video->quality == Group::VideoQuality::Full)
|
||||
? Quality::Full
|
||||
: (video->quality == Group::VideoQuality::Medium
|
||||
&& endpoint.type != VideoEndpointType::Screen)
|
||||
? Quality::Medium
|
||||
: Quality::Thumbnail;
|
||||
if (max == Quality::Full) {
|
||||
if (endpoint.type == VideoEndpointType::Screen) {
|
||||
++fullscreencasts;
|
||||
} else {
|
||||
++fullcameras;
|
||||
}
|
||||
} else if (max == Quality::Medium) {
|
||||
++mediums;
|
||||
}
|
||||
channels.push_back({
|
||||
.audioSsrc = participant->ssrc,
|
||||
.endpointId = endpointId,
|
||||
.ssrcGroups = (params->camera.endpointId == endpointId
|
||||
? params->camera.ssrcGroups
|
||||
: params->screen.ssrcGroups),
|
||||
.minQuality = ((video.quality == Group::VideoQuality::Full
|
||||
&& endpoint.type == VideoEndpointType::Screen)
|
||||
? Quality::Full
|
||||
: Quality::Thumbnail),
|
||||
.maxQuality = (video.quality == Group::VideoQuality::Full
|
||||
? Quality::Full
|
||||
: video.quality == Group::VideoQuality::Medium
|
||||
? Quality::Medium
|
||||
: Quality::Thumbnail),
|
||||
.minQuality = min,
|
||||
.maxQuality = max,
|
||||
});
|
||||
}
|
||||
|
||||
// We limit `count(Full) * kFullAsMediumsCount + count(medium)`.
|
||||
//
|
||||
// Try to preserve all qualities; If not
|
||||
// Try to preserve all screencasts as Full and cameras as Medium; If not
|
||||
// Try to preserve all screencasts as Full; If not
|
||||
// Try to preserve all cameras as Medium;
|
||||
const auto mediumsCount = mediums
|
||||
+ (fullcameras + fullscreencasts) * kFullAsMediumsCount;
|
||||
const auto downgradeSome = (mediumsCount > kMaxMediumQualities);
|
||||
const auto downgradeAll = (fullscreencasts * kFullAsMediumsCount)
|
||||
> kMaxMediumQualities;
|
||||
if (downgradeSome) {
|
||||
for (auto &channel : channels) {
|
||||
if (channel.maxQuality == Quality::Full) {
|
||||
const auto camera = (channel.minQuality != Quality::Full);
|
||||
if (camera) {
|
||||
channel.maxQuality = Quality::Medium;
|
||||
} else if (downgradeAll) {
|
||||
channel.maxQuality
|
||||
= channel.minQuality
|
||||
= Quality::Thumbnail;
|
||||
--fullscreencasts;
|
||||
}
|
||||
}
|
||||
}
|
||||
mediums += fullcameras;
|
||||
fullcameras = 0;
|
||||
if (downgradeAll) {
|
||||
fullscreencasts = 0;
|
||||
}
|
||||
}
|
||||
if (mediums > kMaxMediumQualities) {
|
||||
for (auto &channel : channels) {
|
||||
if (channel.maxQuality == Quality::Medium) {
|
||||
channel.maxQuality = Quality::Thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
_instance->setRequestedVideoChannels(std::move(channels));
|
||||
}
|
||||
|
||||
@@ -2425,21 +2545,37 @@ void GroupCall::updateRequestedVideoChannelsDelayed() {
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::refreshHasNotShownVideo() {
|
||||
if (!_joinState.ssrc || hasNotShownVideo()) {
|
||||
return;
|
||||
}
|
||||
const auto real = lookupReal();
|
||||
Assert(real != nullptr);
|
||||
|
||||
const auto hasVideo = [&](const Data::GroupCallParticipant &data) {
|
||||
return (data.peer != _joinAs)
|
||||
&& (!GetCameraEndpoint(data.videoParams).empty()
|
||||
|| !GetScreenEndpoint(data.videoParams).empty());
|
||||
};
|
||||
_hasNotShownVideo = _joinState.ssrc
|
||||
&& ranges::any_of(real->participants(), hasVideo);
|
||||
}
|
||||
|
||||
void GroupCall::fillActiveVideoEndpoints() {
|
||||
const auto real = lookupReal();
|
||||
Assert(real != nullptr);
|
||||
|
||||
if (const auto participant = real->participantByPeer(_joinAs)) {
|
||||
_videoIsWorking = participant->videoJoined;
|
||||
const auto me = real->participantByPeer(_joinAs);
|
||||
if (me && me->videoJoined) {
|
||||
_videoIsWorking = true;
|
||||
_hasNotShownVideo = false;
|
||||
} else {
|
||||
refreshHasNotShownVideo();
|
||||
_videoIsWorking = false;
|
||||
}
|
||||
if (!videoIsWorking()) {
|
||||
toggleVideo(false);
|
||||
toggleScreenSharing(std::nullopt);
|
||||
}
|
||||
|
||||
const auto &participants = real->participants();
|
||||
const auto &large = _videoEndpointLarge.current();
|
||||
auto largeFound = false;
|
||||
auto endpoints = _activeVideoTracks | ranges::views::transform([](
|
||||
@@ -2463,7 +2599,7 @@ void GroupCall::fillActiveVideoEndpoints() {
|
||||
};
|
||||
using Type = VideoEndpointType;
|
||||
if (_videoIsWorking.current()) {
|
||||
for (const auto &participant : participants) {
|
||||
for (const auto &participant : real->participants()) {
|
||||
const auto camera = GetCameraEndpoint(participant.videoParams);
|
||||
if (camera != _cameraEndpoint
|
||||
&& camera != _screenEndpoint
|
||||
@@ -2479,7 +2615,6 @@ void GroupCall::fillActiveVideoEndpoints() {
|
||||
feedOne({ Type::Screen, participant.peer, screen }, paused);
|
||||
}
|
||||
}
|
||||
const auto pausedState = Webrtc::VideoState::Paused;
|
||||
feedOne(
|
||||
{ Type::Camera, _joinAs, cameraSharingEndpoint() },
|
||||
isCameraPaused());
|
||||
@@ -2530,6 +2665,11 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
|
||||
auto check = false;
|
||||
auto checkNow = false;
|
||||
const auto now = crl::now();
|
||||
const auto meMuted = [&] {
|
||||
const auto state = muted();
|
||||
return (state != MuteState::Active)
|
||||
&& (state != MuteState::PushToTalk);
|
||||
};
|
||||
for (const auto &[ssrcOrZero, value] : data.updates) {
|
||||
const auto ssrc = ssrcOrZero ? ssrcOrZero : _joinState.ssrc;
|
||||
if (!ssrc) {
|
||||
@@ -2538,11 +2678,12 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
|
||||
const auto level = value.level;
|
||||
const auto voice = value.voice;
|
||||
const auto me = (ssrc == _joinState.ssrc);
|
||||
const auto ignore = me && meMuted();
|
||||
_levelUpdates.fire(LevelUpdate{
|
||||
.ssrc = ssrc,
|
||||
.value = level,
|
||||
.voice = voice,
|
||||
.me = me
|
||||
.value = ignore ? 0.f : level,
|
||||
.voice = (!ignore && voice),
|
||||
.me = me,
|
||||
});
|
||||
if (level <= kSpeakLevelThreshold) {
|
||||
continue;
|
||||
@@ -2868,10 +3009,10 @@ void GroupCall::requestVideoQuality(
|
||||
return;
|
||||
}
|
||||
const auto i = _activeVideoTracks.find(endpoint);
|
||||
if (i == end(_activeVideoTracks) || i->second.quality == quality) {
|
||||
if (i == end(_activeVideoTracks) || i->second->quality == quality) {
|
||||
return;
|
||||
}
|
||||
i->second.quality = quality;
|
||||
i->second->quality = quality;
|
||||
updateRequestedVideoChannelsDelayed();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ struct VideoEndpoint {
|
||||
std::string id;
|
||||
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
Expects(id.empty() || peer != nullptr);
|
||||
|
||||
return id.empty();
|
||||
}
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
@@ -194,6 +196,15 @@ public:
|
||||
|
||||
using GlobalShortcutManager = base::GlobalShortcutManager;
|
||||
|
||||
struct VideoTrack;
|
||||
|
||||
[[nodiscard]] static not_null<PeerData*> TrackPeer(
|
||||
const std::unique_ptr<VideoTrack> &track);
|
||||
[[nodiscard]] static not_null<Webrtc::VideoTrack*> TrackPointer(
|
||||
const std::unique_ptr<VideoTrack> &track);
|
||||
[[nodiscard]] static rpl::producer<QSize> TrackSizeValue(
|
||||
const std::unique_ptr<VideoTrack> &track);
|
||||
|
||||
GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
Group::JoinInfo info,
|
||||
@@ -235,6 +246,7 @@ public:
|
||||
}
|
||||
void startScheduledNow();
|
||||
void toggleScheduleStartSubscribed(bool subscribed);
|
||||
void setNoiseSuppression(bool enabled);
|
||||
|
||||
bool emitShareScreenError();
|
||||
bool emitShareCameraError();
|
||||
@@ -320,25 +332,8 @@ public:
|
||||
-> rpl::producer<VideoEndpoint> {
|
||||
return _videoEndpointLarge.value();
|
||||
}
|
||||
|
||||
struct VideoTrack {
|
||||
std::unique_ptr<Webrtc::VideoTrack> track;
|
||||
PeerData *peer = nullptr;
|
||||
rpl::lifetime shownTrackingLifetime;
|
||||
Group::VideoQuality quality = Group::VideoQuality();
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return (track != nullptr);
|
||||
}
|
||||
[[nodiscard]] bool operator==(const VideoTrack &other) const {
|
||||
return (track == other.track) && (peer == other.peer);
|
||||
}
|
||||
[[nodiscard]] bool operator!=(const VideoTrack &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
[[nodiscard]] auto activeVideoTracks() const
|
||||
-> const base::flat_map<VideoEndpoint, VideoTrack> & {
|
||||
-> const base::flat_map<VideoEndpoint, std::unique_ptr<VideoTrack>> & {
|
||||
return _activeVideoTracks;
|
||||
}
|
||||
[[nodiscard]] auto shownVideoTracks() const
|
||||
@@ -365,6 +360,12 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> videoIsWorkingValue() const {
|
||||
return _videoIsWorking.value();
|
||||
}
|
||||
[[nodiscard]] bool hasNotShownVideo() const {
|
||||
return _hasNotShownVideo.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> hasNotShownVideoValue() const {
|
||||
return _hasNotShownVideo.value();
|
||||
}
|
||||
|
||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||
void setCurrentVideoDevice(const QString &deviceId);
|
||||
@@ -513,6 +514,7 @@ private:
|
||||
void updateRequestedVideoChannels();
|
||||
void updateRequestedVideoChannelsDelayed();
|
||||
void fillActiveVideoEndpoints();
|
||||
void refreshHasNotShownVideo();
|
||||
|
||||
void editParticipant(
|
||||
not_null<PeerData*> participantPeer,
|
||||
@@ -569,6 +571,7 @@ private:
|
||||
rpl::variable<MuteState> _muted = MuteState::Muted;
|
||||
rpl::variable<bool> _canManage = false;
|
||||
rpl::variable<bool> _videoIsWorking = false;
|
||||
rpl::variable<bool> _hasNotShownVideo = false;
|
||||
bool _initialMuteStateSent = false;
|
||||
bool _acceptFields = false;
|
||||
|
||||
@@ -614,7 +617,9 @@ private:
|
||||
rpl::event_stream<VideoStateToggle> _videoStreamActiveUpdates;
|
||||
rpl::event_stream<VideoStateToggle> _videoStreamPausedUpdates;
|
||||
rpl::event_stream<VideoStateToggle> _videoStreamShownUpdates;
|
||||
base::flat_map<VideoEndpoint, VideoTrack> _activeVideoTracks;
|
||||
base::flat_map<
|
||||
VideoEndpoint,
|
||||
std::unique_ptr<VideoTrack>> _activeVideoTracks;
|
||||
base::flat_set<VideoEndpoint> _shownVideoTracks;
|
||||
rpl::variable<VideoEndpoint> _videoEndpointLarge;
|
||||
rpl::variable<bool> _videoEndpointPinned = false;
|
||||
|
||||
@@ -61,6 +61,7 @@ enum class VideoQuality {
|
||||
|
||||
enum class Error {
|
||||
NoCamera,
|
||||
CameraFailed,
|
||||
ScreenFailed,
|
||||
MutedNoCamera,
|
||||
MutedNoScreen,
|
||||
@@ -68,4 +69,13 @@ enum class Error {
|
||||
DisabledNoScreen,
|
||||
};
|
||||
|
||||
enum class StickedTooltip {
|
||||
Camera = 0x01,
|
||||
Microphone = 0x02,
|
||||
};
|
||||
constexpr inline bool is_flag_type(StickedTooltip) {
|
||||
return true;
|
||||
}
|
||||
using StickedTooltips = base::flags<StickedTooltip>;
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -29,10 +29,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h" // Core::App().domain, .activeWindow.
|
||||
#include "main/main_domain.h" // Core::App().domain().activate.
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h" // account().appConfig().
|
||||
#include "main/main_app_config.h" // appConfig().get<double>().
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
|
||||
#include "window/window_controller.h" // Controller::sessionController.
|
||||
#include "window/window_session_controller.h"
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
@@ -41,9 +42,110 @@ namespace {
|
||||
|
||||
constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
|
||||
constexpr auto kShadowMaxAlpha = 74;
|
||||
constexpr auto kUserpicSizeForBlur = 40;
|
||||
constexpr auto kUserpicBlurRadius = 8;
|
||||
|
||||
using Row = MembersRow;
|
||||
|
||||
void SetupVideoPlaceholder(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
not_null<PeerData*> chat) {
|
||||
struct State {
|
||||
QImage blurred;
|
||||
QImage rounded;
|
||||
InMemoryKey key = {};
|
||||
std::shared_ptr<Data::CloudImageView> view;
|
||||
qint64 blurredCacheKey = 0;
|
||||
};
|
||||
const auto state = widget->lifetime().make_state<State>();
|
||||
const auto refreshBlurred = [=] {
|
||||
const auto key = chat->userpicUniqueKey(state->view);
|
||||
if (state->key == key && !state->blurred.isNull()) {
|
||||
return;
|
||||
}
|
||||
constexpr auto size = kUserpicSizeForBlur;
|
||||
state->key = key;
|
||||
state->blurred = QImage(
|
||||
QSize(size, size),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
{
|
||||
auto p = Painter(&state->blurred);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
chat->paintUserpicSquare(p, state->view, 0, 0, size);
|
||||
}
|
||||
state->blurred = Images::BlurLargeImage(
|
||||
std::move(state->blurred),
|
||||
kUserpicBlurRadius);
|
||||
widget->update();
|
||||
};
|
||||
const auto refreshRounded = [=](QSize size) {
|
||||
refreshBlurred();
|
||||
const auto key = state->blurred.cacheKey();
|
||||
if (state->rounded.size() == size && state->blurredCacheKey == key) {
|
||||
return;
|
||||
}
|
||||
state->blurredCacheKey = key;
|
||||
state->rounded = Images::prepare(
|
||||
state->blurred,
|
||||
size.width(),
|
||||
size.width(), // Square
|
||||
Images::Option::Smooth,
|
||||
size.width(),
|
||||
size.height());
|
||||
{
|
||||
auto p = QPainter(&state->rounded);
|
||||
p.fillRect(
|
||||
0,
|
||||
0,
|
||||
size.width(),
|
||||
size.height(),
|
||||
QColor(0, 0, 0, Viewport::kShadowMaxAlpha));
|
||||
}
|
||||
state->rounded = Images::prepare(
|
||||
std::move(state->rounded),
|
||||
size.width(),
|
||||
size.height(),
|
||||
(Images::Option::RoundedLarge | Images::Option::RoundedAll),
|
||||
size.width(),
|
||||
size.height());
|
||||
};
|
||||
chat->loadUserpic();
|
||||
refreshBlurred();
|
||||
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto size = QSize(
|
||||
widget->width(),
|
||||
widget->height() - st::groupCallVideoSmallSkip);
|
||||
refreshRounded(size * cIntRetinaFactor());
|
||||
|
||||
auto p = QPainter(widget);
|
||||
const auto inner = QRect(QPoint(), size);
|
||||
p.drawImage(inner, state->rounded);
|
||||
st::groupCallPaused.paint(
|
||||
p,
|
||||
(size.width() - st::groupCallPaused.width()) / 2,
|
||||
st::groupCallVideoPlaceholderIconTop,
|
||||
size.width());
|
||||
|
||||
const auto skip = st::groupCallVideoLargeSkip;
|
||||
const auto limit = chat->session().account().appConfig().get<double>(
|
||||
"groupcall_video_participants_max",
|
||||
30.);
|
||||
p.setPen(st::groupCallVideoTextFg);
|
||||
const auto text = QRect(
|
||||
skip,
|
||||
st::groupCallVideoPlaceholderTextTop,
|
||||
(size.width() - 2 * skip),
|
||||
size.height() - st::groupCallVideoPlaceholderTextTop);
|
||||
p.setFont(st::semiboldFont);
|
||||
p.drawText(
|
||||
text,
|
||||
tr::lng_group_call_limit(tr::now, lt_count, int(limit)),
|
||||
style::al_top);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Members::Controller final
|
||||
@@ -953,9 +1055,7 @@ void Members::Controller::rowPaintIcon(
|
||||
: _coloredCrossLine;
|
||||
const auto color = video
|
||||
? std::nullopt
|
||||
: std::make_optional(narrow
|
||||
? st::groupCallMemberNotJoinedStatus->c
|
||||
: st::groupCallMemberMutedIcon->c);
|
||||
: std::make_optional(st::groupCallMemberMutedIcon->c);
|
||||
line.paint(
|
||||
p,
|
||||
left,
|
||||
@@ -987,9 +1087,7 @@ void Members::Controller::rowPaintIcon(
|
||||
state.speaking);
|
||||
const auto iconColor = anim::color(
|
||||
activeInactiveColor,
|
||||
(narrow
|
||||
? st::groupCallMemberNotJoinedStatus
|
||||
: st::groupCallMemberMutedIcon),
|
||||
st::groupCallMemberMutedIcon,
|
||||
state.muted);
|
||||
const auto color = video
|
||||
? std::nullopt
|
||||
@@ -1137,12 +1235,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto participantPeer = row->peer();
|
||||
const auto real = static_cast<Row*>(row.get());
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::groupCallPopupMenu);
|
||||
|
||||
const auto muteState = real->state();
|
||||
const auto muted = (muteState == Row::State::Muted)
|
||||
|| (muteState == Row::State::RaisedHand);
|
||||
const auto addVolumeItem = !muted || isMe(participantPeer);
|
||||
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
|
||||
const auto session = &_peer->session();
|
||||
const auto getCurrentWindow = [=]() -> Window::SessionController* {
|
||||
@@ -1163,6 +1259,12 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
}
|
||||
return getCurrentWindow();
|
||||
};
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
(addVolumeItem
|
||||
? st::groupCallPopupMenuWithVolume
|
||||
: st::groupCallPopupMenu));
|
||||
const auto weakMenu = Ui::MakeWeak(result.get());
|
||||
const auto performOnMainWindow = [=](auto callback) {
|
||||
if (const auto window = getWindow()) {
|
||||
@@ -1343,7 +1445,8 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
|
||||
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
|
||||
|
||||
if (!muted || _call->joinAs() == participantPeer) {
|
||||
const auto addVolumeItem = !muted || isMe(participantPeer);
|
||||
if (addVolumeItem) {
|
||||
auto otherParticipantStateValue
|
||||
= _call->otherParticipantStateValue(
|
||||
) | rpl::filter([=](const Group::ParticipantState &data) {
|
||||
@@ -1352,7 +1455,7 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
|
||||
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
|
||||
menu->menu(),
|
||||
st::groupCallPopupMenu.menu,
|
||||
st::groupCallPopupVolumeMenu,
|
||||
otherParticipantStateValue,
|
||||
row->volume(),
|
||||
Group::kMaxVolume,
|
||||
@@ -1391,7 +1494,15 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
}
|
||||
}, volumeItem->lifetime());
|
||||
|
||||
if (!menu->empty()) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
menu->addAction(std::move(volumeItem));
|
||||
|
||||
if (!isMe(participantPeer)) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
};
|
||||
|
||||
const auto muteAction = [&]() -> QAction* {
|
||||
@@ -1471,6 +1582,7 @@ Members::Members(
|
||||
, _layout(_scroll->setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
||||
, _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
|
||||
, _videoPlaceholder(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
|
||||
, _viewport(
|
||||
std::make_unique<Viewport>(
|
||||
_videoWrap.get(),
|
||||
@@ -1708,11 +1820,27 @@ void Members::trackViewportGeometry() {
|
||||
_scroll->scrollTopValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
|
||||
|
||||
_viewport->fullHeightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
_videoWrap->resize(_videoWrap->width(), height);
|
||||
move();
|
||||
resize();
|
||||
rpl::combine(
|
||||
_layout->widthValue(),
|
||||
_call->hasNotShownVideoValue()
|
||||
) | rpl::start_with_next([=](int width, bool has) {
|
||||
const auto height = has ? st::groupCallVideoPlaceholderHeight : 0;
|
||||
_videoPlaceholder->setGeometry(0, 0, width, height);
|
||||
}, _videoPlaceholder->lifetime());
|
||||
|
||||
SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer());
|
||||
|
||||
rpl::combine(
|
||||
_videoPlaceholder->heightValue(),
|
||||
_viewport->fullHeightValue()
|
||||
) | rpl::start_with_next([=](int placeholder, int viewport) {
|
||||
_videoWrap->resize(
|
||||
_videoWrap->width(),
|
||||
std::max(placeholder, viewport));
|
||||
if (viewport > 0) {
|
||||
move();
|
||||
resize();
|
||||
}
|
||||
}, _viewport->lifetime());
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ private:
|
||||
std::unique_ptr<Controller> _listController;
|
||||
not_null<Ui::VerticalLayout*> _layout;
|
||||
const not_null<Ui::RpWidget*> _videoWrap;
|
||||
const std::unique_ptr<Ui::RpWidget> _videoPlaceholder;
|
||||
std::unique_ptr<Viewport> _viewport;
|
||||
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
|
||||
RpWidget *_topSkip = nullptr;
|
||||
|
||||
@@ -20,13 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/controls/call_mute_button.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/window.h"
|
||||
#include "ui/widgets/call_button.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "ui/widgets/window.h"
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
#include "ui/layers/layer_manager.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
@@ -47,13 +46,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "app.h"
|
||||
#include "apiwrap.h" // api().kickParticipant.
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource.
|
||||
#include "webrtc/webrtc_audio_input_tester.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
@@ -71,6 +70,10 @@ constexpr auto kRecordingOpacity = 0.6;
|
||||
constexpr auto kStartNoConfirmation = TimeId(10);
|
||||
constexpr auto kControlsBackgroundOpacity = 0.8;
|
||||
constexpr auto kOverrideActiveColorBgAlpha = 172;
|
||||
constexpr auto kMicrophoneTooltipAfterLoudCount = 3;
|
||||
constexpr auto kDropLoudAfterQuietCount = 5;
|
||||
constexpr auto kMicrophoneTooltipLevelThreshold = 0.2;
|
||||
constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -84,17 +87,60 @@ struct Panel::ControlsBackgroundNarrow {
|
||||
Ui::RpWidget blocker;
|
||||
};
|
||||
|
||||
class Panel::MicLevelTester final {
|
||||
public:
|
||||
explicit MicLevelTester(Fn<void()> show);
|
||||
|
||||
[[nodiscard]] bool showTooltip() const;
|
||||
|
||||
private:
|
||||
void check();
|
||||
|
||||
Fn<void()> _show;
|
||||
base::Timer _timer;
|
||||
Webrtc::AudioInputTester _tester;
|
||||
int _loudCount = 0;
|
||||
int _quietCount = 0;
|
||||
|
||||
};
|
||||
|
||||
Panel::MicLevelTester::MicLevelTester(Fn<void()> show)
|
||||
: _show(std::move(show))
|
||||
, _timer([=] { check(); })
|
||||
, _tester(
|
||||
Core::App().settings().callAudioBackend(),
|
||||
Core::App().settings().callInputDeviceId()) {
|
||||
_timer.callEach(kMicrophoneTooltipCheckInterval);
|
||||
}
|
||||
|
||||
bool Panel::MicLevelTester::showTooltip() const {
|
||||
return (_loudCount >= kMicrophoneTooltipAfterLoudCount);
|
||||
}
|
||||
|
||||
void Panel::MicLevelTester::check() {
|
||||
const auto level = _tester.getAndResetLevel();
|
||||
if (level >= kMicrophoneTooltipLevelThreshold) {
|
||||
_quietCount = 0;
|
||||
if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) {
|
||||
_show();
|
||||
}
|
||||
} else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) {
|
||||
_quietCount = 0;
|
||||
_loudCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Panel::Panel(not_null<GroupCall*> call)
|
||||
: _call(call)
|
||||
, _peer(call->peer())
|
||||
, _window(createWindow())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(_window->body()))
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
_window->body(),
|
||||
widget(),
|
||||
st::groupCallTitle))
|
||||
#endif // !Q_OS_MAC
|
||||
, _viewport(std::make_unique<Viewport>(widget(), PanelMode::Wide, _backend))
|
||||
, _viewport(
|
||||
std::make_unique<Viewport>(widget(), PanelMode::Wide, _window.backend()))
|
||||
, _mute(std::make_unique<Ui::CallMuteButton>(
|
||||
widget(),
|
||||
st::callMuteButton,
|
||||
@@ -112,6 +158,8 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
: Ui::CallMuteButtonType::ScheduledSilent),
|
||||
}))
|
||||
, _hangup(widget(), st::groupCallHangup)
|
||||
, _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips()
|
||||
& ~StickedTooltip::Microphone) // Always show tooltip about mic.
|
||||
, _toasts(std::make_unique<Toasts>(this)) {
|
||||
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
|
||||
_layerBg->setHideByBackgroundClick(true);
|
||||
@@ -123,7 +171,7 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
|
||||
SubscribeToMigration(
|
||||
_peer,
|
||||
_window->lifetime(),
|
||||
lifetime(),
|
||||
[=](not_null<ChannelData*> channel) { migrate(channel); });
|
||||
setupRealCallViewers();
|
||||
|
||||
@@ -139,30 +187,11 @@ Panel::~Panel() {
|
||||
_viewport = nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Window> Panel::createWindow() {
|
||||
auto result = std::make_unique<Ui::Window>();
|
||||
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: Platform::IsWindows()
|
||||
? capabilities.supported
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (Calls::Group::Viewport)").arg(Logs::b(use)));
|
||||
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
|
||||
|
||||
if (use) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We have to create a new window, if OpenGL initialization failed.
|
||||
return std::make_unique<Ui::Window>();
|
||||
}
|
||||
|
||||
void Panel::setupRealCallViewers() {
|
||||
_call->real(
|
||||
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
||||
subscribeToChanges(real);
|
||||
}, _window->lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<GroupCall*> Panel::call() const {
|
||||
@@ -170,9 +199,9 @@ not_null<GroupCall*> Panel::call() const {
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return _window->isActiveWindow()
|
||||
&& _window->isVisible()
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::showToast(TextWithEntities &&text, crl::time duration) {
|
||||
@@ -187,24 +216,24 @@ void Panel::showToast(TextWithEntities &&text, crl::time duration) {
|
||||
}
|
||||
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::close() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
if (window()->isHidden()) {
|
||||
window()->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
const auto state = window()->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
window()->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
window()->setFocus();
|
||||
}
|
||||
|
||||
void Panel::migrate(not_null<ChannelData*> channel) {
|
||||
@@ -219,12 +248,12 @@ void Panel::subscribeToPeerChanges() {
|
||||
Info::Profile::NameValue(
|
||||
_peer
|
||||
) | rpl::start_with_next([=](const TextWithEntities &name) {
|
||||
_window->setTitle(name.text);
|
||||
window()->setTitle(name.text);
|
||||
}, _peerLifetime);
|
||||
}
|
||||
|
||||
QWidget *Panel::chooseSourceParent() {
|
||||
return _window.get();
|
||||
return window().get();
|
||||
}
|
||||
|
||||
QString Panel::chooseSourceActiveDeviceId() {
|
||||
@@ -232,7 +261,7 @@ QString Panel::chooseSourceActiveDeviceId() {
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
|
||||
return _window->lifetime();
|
||||
return lifetime();
|
||||
}
|
||||
|
||||
void Panel::chooseSourceAccepted(const QString &deviceId) {
|
||||
@@ -244,15 +273,15 @@ void Panel::chooseSourceStop() {
|
||||
}
|
||||
|
||||
void Panel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
window()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
window()->setAttribute(Qt::WA_NoSystemBackground);
|
||||
window()->setWindowIcon(
|
||||
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitleStyle(st::groupCallTitle);
|
||||
window()->setTitleStyle(st::groupCallTitle);
|
||||
|
||||
subscribeToPeerChanges();
|
||||
|
||||
base::install_event_filter(_window.get(), [=](not_null<QEvent*> e) {
|
||||
base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close && handleClose()) {
|
||||
e->ignore();
|
||||
return base::EventFilterResult::Cancel;
|
||||
@@ -267,7 +296,7 @@ void Panel::initWindow() {
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
_window->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
window()->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
const auto titleRect = QRect(
|
||||
0,
|
||||
@@ -286,7 +315,7 @@ void Panel::initWindow() {
|
||||
_call->hasVideoWithFramesValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateMode();
|
||||
}, _window->lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void Panel::initWidget() {
|
||||
@@ -295,7 +324,7 @@ void Panel::initWidget() {
|
||||
widget()->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
paint(clip);
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
widget()->sizeValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
|
||||
@@ -306,7 +335,7 @@ void Panel::initWidget() {
|
||||
// title geometry depends on _controls->geometry,
|
||||
// which is not updated here yet.
|
||||
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void Panel::endCall() {
|
||||
@@ -380,7 +409,7 @@ void Panel::initControls() {
|
||||
_call->canManageValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopButton();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
_hangup->setClickedCallback([=] { endCall(); });
|
||||
|
||||
@@ -418,7 +447,9 @@ void Panel::initControls() {
|
||||
}
|
||||
|
||||
_call->stateValue(
|
||||
) | rpl::filter([](State state) {
|
||||
) | rpl::before_next([=] {
|
||||
showStickedTooltip();
|
||||
}) | rpl::filter([](State state) {
|
||||
return (state == State::HangingUp)
|
||||
|| (state == State::Ended)
|
||||
|| (state == State::FailedHangingUp)
|
||||
@@ -499,18 +530,22 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
|
||||
&st::groupCallVideoActiveSmall);
|
||||
_video->show();
|
||||
_video->setClickedCallback([=] {
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Camera,
|
||||
StickedTooltipHide::Activated);
|
||||
_call->toggleVideo(!_call->isSharingCamera());
|
||||
});
|
||||
_video->setColorOverrides(
|
||||
toggleableOverrides(_call->isSharingCameraValue()));
|
||||
_call->isSharingCameraValue(
|
||||
) | rpl::start_with_next([=](bool sharing) {
|
||||
if (sharing) {
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Camera,
|
||||
StickedTooltipHide::Activated);
|
||||
}
|
||||
_video->setProgress(sharing ? 1. : 0.);
|
||||
}, _video->lifetime());
|
||||
_call->mutedValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateButtonsGeometry();
|
||||
}, _video->lifetime());
|
||||
}
|
||||
if (!_screenShare) {
|
||||
_screenShare.create(widget(), st::groupCallScreenShareSmall);
|
||||
@@ -536,6 +571,46 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
|
||||
updateButtonsGeometry();
|
||||
}
|
||||
|
||||
void Panel::hideStickedTooltip(StickedTooltipHide hide) {
|
||||
if (!_stickedTooltipClose || !_niceTooltipControl) {
|
||||
return;
|
||||
}
|
||||
if (_niceTooltipControl.data() == _video.data()) {
|
||||
hideStickedTooltip(StickedTooltip::Camera, hide);
|
||||
} else if (_niceTooltipControl.data() == _mute->outer().get()) {
|
||||
hideStickedTooltip(StickedTooltip::Microphone, hide);
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::hideStickedTooltip(
|
||||
StickedTooltip type,
|
||||
StickedTooltipHide hide) {
|
||||
if (hide != StickedTooltipHide::Unavailable) {
|
||||
_stickedTooltipsShown |= type;
|
||||
if (hide == StickedTooltipHide::Discarded) {
|
||||
Core::App().settings().setHiddenGroupCallTooltip(type);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}
|
||||
}
|
||||
const auto control = (type == StickedTooltip::Camera)
|
||||
? _video.data()
|
||||
: (type == StickedTooltip::Microphone)
|
||||
? _mute->outer().get()
|
||||
: nullptr;
|
||||
if (_niceTooltipControl.data() == control) {
|
||||
hideNiceTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::hideNiceTooltip() {
|
||||
if (!_niceTooltip) {
|
||||
return;
|
||||
}
|
||||
_stickedTooltipClose = nullptr;
|
||||
_niceTooltip.release()->toggleAnimated(false);
|
||||
_niceTooltipControl = nullptr;
|
||||
}
|
||||
|
||||
void Panel::initShareAction() {
|
||||
const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
|
||||
_layerBg->showBox(std::move(next));
|
||||
@@ -552,7 +627,7 @@ void Panel::initShareAction() {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
widget()->lifetime().add(std::move(shareLinkLifetime));
|
||||
lifetime().add(std::move(shareLinkLifetime));
|
||||
}
|
||||
|
||||
void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
|
||||
@@ -628,7 +703,7 @@ void Panel::setupScheduledLabels(rpl::producer<TimeId> date) {
|
||||
) | rpl::map([=](TimeId date) {
|
||||
_countdownData = std::make_shared<Ui::GroupCallScheduledLeft>(date);
|
||||
return rpl::empty_value();
|
||||
}) | rpl::start_spawning(widget()->lifetime());
|
||||
}) | rpl::start_spawning(lifetime());
|
||||
|
||||
_countdown = Ui::CreateGradientLabel(widget(), rpl::duplicate(
|
||||
countdownCreated
|
||||
@@ -696,7 +771,7 @@ void Panel::setupMembers() {
|
||||
_countdown.destroy();
|
||||
_startsWhen.destroy();
|
||||
|
||||
_members.create(widget(), _call, mode(), _backend);
|
||||
_members.create(widget(), _call, mode(), _window.backend());
|
||||
|
||||
setupVideo(_viewport.get());
|
||||
setupVideo(_members->viewport());
|
||||
@@ -753,40 +828,40 @@ void Panel::setupMembers() {
|
||||
}
|
||||
|
||||
void Panel::enlargeVideo() {
|
||||
_lastSmallGeometry = _window->geometry();
|
||||
_lastSmallGeometry = window()->geometry();
|
||||
|
||||
const auto available = _window->screen()->availableGeometry();
|
||||
const auto available = window()->screen()->availableGeometry();
|
||||
const auto width = std::max(
|
||||
_window->width(),
|
||||
window()->width(),
|
||||
std::max(
|
||||
std::min(available.width(), st::groupCallWideModeSize.width()),
|
||||
st::groupCallWideModeWidthMin));
|
||||
const auto height = std::max(
|
||||
_window->height(),
|
||||
window()->height(),
|
||||
std::min(available.height(), st::groupCallWideModeSize.height()));
|
||||
auto geometry = QRect(_window->pos(), QSize(width, height));
|
||||
auto geometry = QRect(window()->pos(), QSize(width, height));
|
||||
if (geometry.x() < available.x()) {
|
||||
geometry.setX(std::min(available.x(), _window->x()));
|
||||
geometry.moveLeft(std::min(available.x(), window()->x()));
|
||||
}
|
||||
if (geometry.x() + geometry.width()
|
||||
> available.x() + available.width()) {
|
||||
geometry.setX(std::max(
|
||||
geometry.moveLeft(std::max(
|
||||
available.x() + available.width(),
|
||||
_window->x() + _window->width()) - geometry.width());
|
||||
window()->x() + window()->width()) - geometry.width());
|
||||
}
|
||||
if (geometry.y() < available.y()) {
|
||||
geometry.setY(std::min(available.y(), _window->y()));
|
||||
geometry.moveTop(std::min(available.y(), window()->y()));
|
||||
}
|
||||
if (geometry.y() + geometry.height() > available.y() + available.height()) {
|
||||
geometry.setY(std::max(
|
||||
geometry.moveTop(std::max(
|
||||
available.y() + available.height(),
|
||||
_window->y() + _window->height()) - geometry.height());
|
||||
window()->y() + window()->height()) - geometry.height());
|
||||
}
|
||||
if (_lastLargeMaximized) {
|
||||
_window->setWindowState(
|
||||
_window->windowState() | Qt::WindowMaximized);
|
||||
window()->setWindowState(
|
||||
window()->windowState() | Qt::WindowMaximized);
|
||||
} else {
|
||||
_window->setGeometry((_lastLargeGeometry
|
||||
window()->setGeometry((_lastLargeGeometry
|
||||
&& available.intersects(*_lastLargeGeometry))
|
||||
? *_lastLargeGeometry
|
||||
: geometry);
|
||||
@@ -794,23 +869,23 @@ void Panel::enlargeVideo() {
|
||||
}
|
||||
|
||||
void Panel::minimizeVideo() {
|
||||
if (_window->windowState() & Qt::WindowMaximized) {
|
||||
if (window()->windowState() & Qt::WindowMaximized) {
|
||||
_lastLargeMaximized = true;
|
||||
_window->setWindowState(
|
||||
_window->windowState() & ~Qt::WindowMaximized);
|
||||
window()->setWindowState(
|
||||
window()->windowState() & ~Qt::WindowMaximized);
|
||||
} else {
|
||||
_lastLargeMaximized = false;
|
||||
_lastLargeGeometry = _window->geometry();
|
||||
_lastLargeGeometry = window()->geometry();
|
||||
}
|
||||
const auto available = _window->screen()->availableGeometry();
|
||||
const auto available = window()->screen()->availableGeometry();
|
||||
const auto width = st::groupCallWidth;
|
||||
const auto height = st::groupCallHeight;
|
||||
auto geometry = QRect(
|
||||
_window->x() + (_window->width() - width) / 2,
|
||||
_window->y() + (_window->height() - height) / 2,
|
||||
window()->x() + (window()->width() - width) / 2,
|
||||
window()->y() + (window()->height() - height) / 2,
|
||||
width,
|
||||
height);
|
||||
_window->setGeometry((_lastSmallGeometry
|
||||
window()->setGeometry((_lastSmallGeometry
|
||||
&& available.intersects(*_lastSmallGeometry))
|
||||
? *_lastSmallGeometry
|
||||
: geometry);
|
||||
@@ -838,14 +913,17 @@ void Panel::raiseControls() {
|
||||
}
|
||||
}
|
||||
_mute->raise();
|
||||
if (_niceTooltip) {
|
||||
_niceTooltip->raise();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::setupVideo(not_null<Viewport*> viewport) {
|
||||
const auto setupTile = [=](
|
||||
const VideoEndpoint &endpoint,
|
||||
const GroupCall::VideoTrack &track) {
|
||||
const std::unique_ptr<GroupCall::VideoTrack> &track) {
|
||||
using namespace rpl::mappers;
|
||||
const auto row = _members->lookupRow(track.peer);
|
||||
const auto row = _members->lookupRow(GroupCall::TrackPeer(track));
|
||||
Assert(row != nullptr);
|
||||
auto pinned = rpl::combine(
|
||||
_call->videoEndpointLargeValue(),
|
||||
@@ -853,7 +931,8 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
|
||||
) | rpl::map(_1 == endpoint && _2);
|
||||
viewport->add(
|
||||
endpoint,
|
||||
VideoTileTrack{ track.track.get(), row },
|
||||
VideoTileTrack{ GroupCall::TrackPointer(track), row },
|
||||
GroupCall::TrackSizeValue(track),
|
||||
std::move(pinned));
|
||||
};
|
||||
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
|
||||
@@ -907,18 +986,24 @@ void Panel::toggleWideControls(bool shown) {
|
||||
}
|
||||
_showWideControls = shown;
|
||||
crl::on_main(widget(), [=] {
|
||||
if (_wideControlsShown == _showWideControls) {
|
||||
return;
|
||||
}
|
||||
_wideControlsShown = _showWideControls;
|
||||
_wideControlsAnimation.start(
|
||||
[=] { updateButtonsGeometry(); },
|
||||
_wideControlsShown ? 0. : 1.,
|
||||
_wideControlsShown ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
updateWideControlsVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::updateWideControlsVisibility() {
|
||||
const auto shown = _showWideControls
|
||||
|| (_stickedTooltipClose != nullptr);
|
||||
if (_wideControlsShown == shown) {
|
||||
return;
|
||||
}
|
||||
_wideControlsShown = shown;
|
||||
_wideControlsAnimation.start(
|
||||
[=] { updateButtonsGeometry(); },
|
||||
_wideControlsShown ? 0. : 1.,
|
||||
_wideControlsShown ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
const auto validateRecordingMark = [=](bool recording) {
|
||||
if (!recording && _recordingMark) {
|
||||
@@ -979,7 +1064,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
: tr::lng_group_call_recording_stopped)(
|
||||
tr::now,
|
||||
Ui::Text::RichLangValue));
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
validateRecordingMark(real->recordStartDate() != 0);
|
||||
|
||||
rpl::combine(
|
||||
@@ -987,14 +1072,27 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_call->isSharingCameraValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshVideoButtons();
|
||||
}, widget()->lifetime());
|
||||
showStickedTooltip();
|
||||
}, lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_call->videoIsWorkingValue(),
|
||||
_call->isSharingScreenValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopButton();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
_call->mutedValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next([=](MuteState state) {
|
||||
updateButtonsGeometry();
|
||||
if (state == MuteState::Active
|
||||
|| state == MuteState::PushToTalk) {
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Microphone,
|
||||
StickedTooltipHide::Activated);
|
||||
}
|
||||
showStickedTooltip();
|
||||
}, lifetime());
|
||||
|
||||
updateControlsGeometry();
|
||||
}
|
||||
@@ -1040,7 +1138,7 @@ void Panel::refreshTopButton() {
|
||||
chooseJoinAs();
|
||||
});
|
||||
updateControlsGeometry();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
} else {
|
||||
_menuToggle.destroy();
|
||||
_joinAsToggle.destroy();
|
||||
@@ -1294,7 +1392,7 @@ void Panel::initLayout() {
|
||||
) | rpl::start_with_next([=] {
|
||||
// _menuToggle geometry depends on _controls arrangement.
|
||||
crl::on_main(widget(), [=] { updateControlsGeometry(); });
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
@@ -1306,16 +1404,20 @@ void Panel::showControls() {
|
||||
}
|
||||
|
||||
void Panel::closeBeforeDestroy() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
_callLifetime.destroy();
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::lifetime() {
|
||||
return window()->lifetime();
|
||||
}
|
||||
|
||||
void Panel::initGeometry() {
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
|
||||
_window->setGeometry(rect.translated(center - rect.center()));
|
||||
_window->setMinimumSize(rect.size());
|
||||
_window->show();
|
||||
window()->setGeometry(rect.translated(center - rect.center()));
|
||||
window()->setMinimumSize(rect.size());
|
||||
window()->show();
|
||||
}
|
||||
|
||||
QRect Panel::computeTitleRect() const {
|
||||
@@ -1351,7 +1453,10 @@ bool Panel::updateMode() {
|
||||
_call->showVideoEndpointLarge({});
|
||||
}
|
||||
refreshVideoButtons(wide);
|
||||
_niceTooltip.destroy();
|
||||
if (!_stickedTooltipClose
|
||||
|| _niceTooltipControl.data() != _mute->outer().get()) {
|
||||
_niceTooltip.destroy();
|
||||
}
|
||||
_mode = mode;
|
||||
if (_title) {
|
||||
_title->setTextColorOverride(wide
|
||||
@@ -1372,6 +1477,7 @@ bool Panel::updateMode() {
|
||||
updateButtonsStyles();
|
||||
refreshControlsBackground();
|
||||
updateControlsGeometry();
|
||||
showStickedTooltip();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1556,8 +1662,12 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
|
||||
}
|
||||
|
||||
void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
|
||||
if (_niceTooltip) {
|
||||
_niceTooltip.release()->toggleAnimated(false);
|
||||
if (_stickedTooltipClose) {
|
||||
if (!over) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
hideNiceTooltip();
|
||||
}
|
||||
if (over) {
|
||||
Ui::Integration::Instance().registerLeaveSubscription(control);
|
||||
@@ -1568,7 +1678,52 @@ void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
|
||||
toggleWideControls(over);
|
||||
}
|
||||
|
||||
void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
|
||||
void Panel::showStickedTooltip() {
|
||||
static const auto kHasCamera = !Webrtc::GetVideoInputList().empty();
|
||||
const auto callReady = (_call->state() == State::Joined
|
||||
|| _call->state() == State::Connecting);
|
||||
if (!(_stickedTooltipsShown & StickedTooltip::Camera)
|
||||
&& callReady
|
||||
&& (_mode.current() == PanelMode::Wide)
|
||||
&& _video
|
||||
&& _call->videoIsWorking()
|
||||
&& !_call->mutedByAdmin()
|
||||
&& kHasCamera) { // Don't recount this every time for now.
|
||||
showNiceTooltip(_video, NiceTooltipType::Sticked);
|
||||
return;
|
||||
}
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Camera,
|
||||
StickedTooltipHide::Unavailable);
|
||||
|
||||
if (!(_stickedTooltipsShown & StickedTooltip::Microphone)
|
||||
&& callReady
|
||||
&& _mute
|
||||
&& !_call->mutedByAdmin()) {
|
||||
if (_stickedTooltipClose) {
|
||||
// Showing already.
|
||||
return;
|
||||
} else if (!_micLevelTester) {
|
||||
// Check if there is incoming sound.
|
||||
_micLevelTester = std::make_unique<MicLevelTester>([=] {
|
||||
showStickedTooltip();
|
||||
});
|
||||
}
|
||||
if (_micLevelTester->showTooltip()) {
|
||||
_micLevelTester = nullptr;
|
||||
showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_micLevelTester = nullptr;
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Microphone,
|
||||
StickedTooltipHide::Unavailable);
|
||||
}
|
||||
|
||||
void Panel::showNiceTooltip(
|
||||
not_null<Ui::RpWidget*> control,
|
||||
NiceTooltipType type) {
|
||||
auto text = [&]() -> rpl::producer<QString> {
|
||||
if (control == _screenShare.data()) {
|
||||
if (_call->mutedByAdmin()) {
|
||||
@@ -1594,40 +1749,98 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
|
||||
}
|
||||
return rpl::producer<QString>();
|
||||
}();
|
||||
if (!text
|
||||
|| _wideControlsAnimation.animating()
|
||||
|| !_wideControlsShown) {
|
||||
if (!text || _stickedTooltipClose) {
|
||||
return;
|
||||
} else if (_wideControlsAnimation.animating() || !_wideControlsShown) {
|
||||
if (type == NiceTooltipType::Normal) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto inner = [&]() -> Ui::RpWidget* {
|
||||
const auto normal = (type == NiceTooltipType::Normal);
|
||||
auto container = normal
|
||||
? nullptr
|
||||
: Ui::CreateChild<Ui::RpWidget>(widget().get());
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
(normal ? widget().get() : container),
|
||||
std::move(text),
|
||||
st::groupCallNiceTooltipLabel);
|
||||
if (normal) {
|
||||
return label;
|
||||
}
|
||||
const auto button = Ui::CreateChild<Ui::IconButton>(
|
||||
container,
|
||||
st::groupCallStickedTooltipClose);
|
||||
rpl::combine(
|
||||
label->sizeValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](QSize text, QSize close) {
|
||||
const auto height = std::max(text.height(), close.height());
|
||||
container->resize(text.width() + close.width(), height);
|
||||
label->move(0, (height - text.height()) / 2);
|
||||
button->move(text.width(), (height - close.height()) / 2);
|
||||
}, container->lifetime());
|
||||
button->setClickedCallback([=] {
|
||||
hideStickedTooltip(StickedTooltipHide::Discarded);
|
||||
});
|
||||
_stickedTooltipClose = button;
|
||||
updateWideControlsVisibility();
|
||||
return container;
|
||||
}();
|
||||
_niceTooltip.create(
|
||||
widget().get(),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
widget().get(),
|
||||
std::move(text),
|
||||
st::groupCallNiceTooltipLabel),
|
||||
st::groupCallNiceTooltip);
|
||||
object_ptr<Ui::RpWidget>::fromRaw(inner),
|
||||
(type == NiceTooltipType::Sticked
|
||||
? st::groupCallStickedTooltip
|
||||
: st::groupCallNiceTooltip));
|
||||
const auto tooltip = _niceTooltip.data();
|
||||
const auto weak = QPointer<QWidget>(tooltip);
|
||||
const auto destroy = [=] {
|
||||
delete weak.data();
|
||||
};
|
||||
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (type != NiceTooltipType::Sticked) {
|
||||
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
tooltip->setHiddenCallback(destroy);
|
||||
base::qt_signal_producer(
|
||||
control.get(),
|
||||
&QObject::destroyed
|
||||
) | rpl::start_with_next(destroy, tooltip->lifetime());
|
||||
|
||||
const auto geometry = control->geometry();
|
||||
_niceTooltipControl = control;
|
||||
updateTooltipGeometry();
|
||||
tooltip->toggleAnimated(true);
|
||||
}
|
||||
|
||||
void Panel::updateTooltipGeometry() {
|
||||
if (!_niceTooltip) {
|
||||
return;
|
||||
} else if (!_niceTooltipControl) {
|
||||
hideNiceTooltip();
|
||||
return;
|
||||
}
|
||||
const auto geometry = _niceTooltipControl->geometry();
|
||||
const auto weak = QPointer<QWidget>(_niceTooltip);
|
||||
const auto countPosition = [=](QSize size) {
|
||||
const auto strong = weak.data();
|
||||
if (!strong) {
|
||||
return QPoint();
|
||||
}
|
||||
const auto wide = (_mode.current() == PanelMode::Wide);
|
||||
const auto top = geometry.y()
|
||||
- st::groupCallNiceTooltipTop
|
||||
- (wide ? st::groupCallNiceTooltipTop : 0)
|
||||
- size.height();
|
||||
const auto middle = geometry.center().x();
|
||||
if (!strong) {
|
||||
return QPoint();
|
||||
} else if (!wide) {
|
||||
return QPoint(
|
||||
std::max(
|
||||
std::min(
|
||||
middle - size.width() / 2,
|
||||
(widget()->width()
|
||||
- st::groupCallMembersMargin.right()
|
||||
- size.width())),
|
||||
st::groupCallMembersMargin.left()),
|
||||
top);
|
||||
}
|
||||
const auto back = _controlsBackgroundWide.data();
|
||||
if (size.width() >= _viewport->widget()->width()) {
|
||||
return QPoint(_viewport->widget()->x(), top);
|
||||
@@ -1644,8 +1857,7 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
|
||||
return QPoint(middle - size.width() / 2, top);
|
||||
}
|
||||
};
|
||||
tooltip->pointAt(geometry, RectPart::Top, countPosition);
|
||||
tooltip->toggleAnimated(true);
|
||||
_niceTooltip->pointAt(geometry, RectPart::Top, countPosition);
|
||||
}
|
||||
|
||||
void Panel::trackControls(bool track) {
|
||||
@@ -1831,6 +2043,7 @@ void Panel::updateButtonsGeometry() {
|
||||
width,
|
||||
st::groupCallMembersBottomSkip);
|
||||
}
|
||||
updateTooltipGeometry();
|
||||
}
|
||||
|
||||
bool Panel::videoButtonInNarrowMode() const {
|
||||
@@ -1998,14 +2211,18 @@ void Panel::paint(QRect clip) {
|
||||
|
||||
bool Panel::handleClose() {
|
||||
if (_call) {
|
||||
_window->hide();
|
||||
window()->hide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
not_null<Ui::Window*> Panel::window() const {
|
||||
return _window.window();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> Panel::widget() const {
|
||||
return _window->body();
|
||||
return _window.widget();
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -11,9 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/gl/gl_window.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
@@ -37,14 +39,10 @@ template <typename Widget>
|
||||
class FadeWrap;
|
||||
template <typename Widget>
|
||||
class PaddingWrap;
|
||||
class Window;
|
||||
class ScrollArea;
|
||||
class GenericBox;
|
||||
class LayerManager;
|
||||
class GroupCallScheduledLeft;
|
||||
namespace GL {
|
||||
enum class Backend;
|
||||
} // namespace GL
|
||||
namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
@@ -64,6 +62,7 @@ class Toasts;
|
||||
class Members;
|
||||
class Viewport;
|
||||
enum class PanelMode;
|
||||
enum class StickedTooltip;
|
||||
|
||||
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
|
||||
public:
|
||||
@@ -80,11 +79,24 @@ public:
|
||||
void showAndActivate();
|
||||
void closeBeforeDestroy();
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
using State = GroupCall::State;
|
||||
struct ControlsBackgroundNarrow;
|
||||
|
||||
std::unique_ptr<Ui::Window> createWindow();
|
||||
enum class NiceTooltipType {
|
||||
Normal,
|
||||
Sticked,
|
||||
};
|
||||
enum class StickedTooltipHide {
|
||||
Unavailable,
|
||||
Activated,
|
||||
Discarded,
|
||||
};
|
||||
class MicLevelTester;
|
||||
|
||||
[[nodiscard]] not_null<Ui::Window*> window() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
|
||||
[[nodiscard]] PanelMode mode() const;
|
||||
@@ -111,11 +123,18 @@ private:
|
||||
|
||||
void trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime);
|
||||
void trackControlOver(not_null<Ui::RpWidget*> control, bool over);
|
||||
void showNiceTooltip(not_null<Ui::RpWidget*> control);
|
||||
void showNiceTooltip(
|
||||
not_null<Ui::RpWidget*> control,
|
||||
NiceTooltipType type = NiceTooltipType::Normal);
|
||||
void showStickedTooltip();
|
||||
void hideStickedTooltip(StickedTooltipHide hide);
|
||||
void hideStickedTooltip(StickedTooltip type, StickedTooltipHide hide);
|
||||
void hideNiceTooltip();
|
||||
|
||||
bool updateMode();
|
||||
void updateControlsGeometry();
|
||||
void updateButtonsGeometry();
|
||||
void updateTooltipGeometry();
|
||||
void updateButtonsStyles();
|
||||
void updateMembersGeometry();
|
||||
void refreshControlsBackground();
|
||||
@@ -127,6 +146,7 @@ private:
|
||||
std::optional<bool> overrideWideMode = std::nullopt);
|
||||
void refreshTopButton();
|
||||
void toggleWideControls(bool shown);
|
||||
void updateWideControlsVisibility();
|
||||
[[nodiscard]] bool videoButtonInNarrowMode() const;
|
||||
|
||||
void endCall();
|
||||
@@ -156,8 +176,7 @@ private:
|
||||
const not_null<GroupCall*> _call;
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
Ui::GL::Backend _backend = Ui::GL::Backend();
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
Ui::GL::Window _window;
|
||||
const std::unique_ptr<Ui::LayerManager> _layerBg;
|
||||
rpl::variable<PanelMode> _mode;
|
||||
|
||||
@@ -202,11 +221,16 @@ private:
|
||||
std::unique_ptr<Ui::CallMuteButton> _mute;
|
||||
object_ptr<Ui::CallButton> _hangup;
|
||||
object_ptr<Ui::ImportantTooltip> _niceTooltip = { nullptr };
|
||||
QPointer<Ui::IconButton> _stickedTooltipClose;
|
||||
QPointer<Ui::RpWidget> _niceTooltipControl;
|
||||
StickedTooltips _stickedTooltipsShown;
|
||||
Fn<void()> _callShareLinkCallback;
|
||||
|
||||
const std::unique_ptr<Toasts> _toasts;
|
||||
base::weak_ptr<Ui::Toast::Instance> _lastToast;
|
||||
|
||||
std::unique_ptr<MicLevelTester> _micLevelTester;
|
||||
|
||||
rpl::lifetime _peerLifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -306,6 +306,20 @@ void SettingsBox(
|
||||
//AddDivider(layout);
|
||||
//AddSkip(layout);
|
||||
|
||||
AddButton(
|
||||
layout,
|
||||
tr::lng_group_call_noise_suppression(),
|
||||
st::groupCallSettingsButton
|
||||
)->toggleOn(rpl::single(
|
||||
settings.groupCallNoiseSuppression()
|
||||
))->toggledChanges(
|
||||
) | rpl::start_with_next([=](bool enabled) {
|
||||
Core::App().settings().setGroupCallNoiseSuppression(enabled);
|
||||
call->setNoiseSuppression(enabled);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, layout->lifetime());
|
||||
|
||||
|
||||
using GlobalShortcut = base::GlobalShortcut;
|
||||
struct PushToTalkState {
|
||||
rpl::variable<QString> recordText = tr::lng_group_call_ptt_shortcut();
|
||||
@@ -479,8 +493,10 @@ void SettingsBox(
|
||||
tr::now,
|
||||
lt_delay,
|
||||
FormatDelay(delay)));
|
||||
Core::App().settings().setGroupCallPushToTalkDelay(delay);
|
||||
applyAndSave();
|
||||
if (Core::App().settings().groupCallPushToTalkDelay() != delay) {
|
||||
Core::App().settings().setGroupCallPushToTalkDelay(delay);
|
||||
applyAndSave();
|
||||
}
|
||||
};
|
||||
callback(value);
|
||||
const auto slider = pushToTalkInner->add(
|
||||
|
||||
@@ -105,7 +105,9 @@ void Toasts::setupPinnedVideo() {
|
||||
? _call->videoEndpointLargeValue()
|
||||
: rpl::single(_call->videoEndpointLarge());
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
) | rpl::filter([=] {
|
||||
return (_call->shownVideoTracks().size() > 1);
|
||||
}) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
const auto pinned = _call->videoEndpointPinned();
|
||||
const auto peer = endpoint.peer;
|
||||
if (!peer) {
|
||||
@@ -155,6 +157,8 @@ void Toasts::setupError() {
|
||||
const auto key = [&] {
|
||||
switch (error) {
|
||||
case Error::NoCamera: return tr::lng_call_error_no_camera;
|
||||
case Error::CameraFailed:
|
||||
return tr::lng_group_call_failed_camera;
|
||||
case Error::ScreenFailed:
|
||||
return tr::lng_group_call_failed_screen;
|
||||
case Error::MutedNoCamera:
|
||||
|
||||
@@ -191,7 +191,7 @@ void Viewport::updateSelected(QPoint position) {
|
||||
return;
|
||||
}
|
||||
for (const auto &tile : _tiles) {
|
||||
const auto geometry = tile->shown()
|
||||
const auto geometry = tile->visible()
|
||||
? tile->geometry()
|
||||
: QRect();
|
||||
if (geometry.contains(position)) {
|
||||
@@ -225,10 +225,12 @@ void Viewport::setControlsShown(float64 shown) {
|
||||
void Viewport::add(
|
||||
const VideoEndpoint &endpoint,
|
||||
VideoTileTrack track,
|
||||
rpl::producer<QSize> trackSize,
|
||||
rpl::producer<bool> pinned) {
|
||||
_tiles.push_back(std::make_unique<VideoTile>(
|
||||
endpoint,
|
||||
track,
|
||||
std::move(trackSize),
|
||||
std::move(pinned),
|
||||
[=] { widget()->update(); }));
|
||||
|
||||
@@ -711,11 +713,13 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
|
||||
const auto layoutNext = [&](not_null<VideoTile*> tile) {
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
const auto shown = !size.isEmpty() && _large && tile != _large;
|
||||
const auto height = shown
|
||||
? st::groupCallNarrowVideoHeight
|
||||
: 0;
|
||||
setTileGeometry(tile, { 0, y + top, outerWidth, height });
|
||||
top += height ? (height + st::groupCallVideoSmallSkip) : 0;
|
||||
const auto height = st::groupCallNarrowVideoHeight;
|
||||
if (!shown) {
|
||||
tile->hide();
|
||||
} else {
|
||||
setTileGeometry(tile, { 0, y + top, outerWidth, height });
|
||||
top += height + st::groupCallVideoSmallSkip;
|
||||
}
|
||||
};
|
||||
const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
|
||||
const auto reorderNeeded = [&] {
|
||||
@@ -758,14 +762,20 @@ void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
||||
const auto min = std::min(geometry.width(), geometry.height());
|
||||
const auto kMedium = style::ConvertScale(540);
|
||||
const auto kSmall = style::ConvertScale(240);
|
||||
const auto quality = (min >= kMedium)
|
||||
const auto &endpoint = tile->endpoint();
|
||||
const auto forceThumbnailQuality = !wide()
|
||||
&& (ranges::count(_tiles, false, &VideoTile::hidden) > 1);
|
||||
const auto forceFullQuality = wide() && (tile.get() == _large);
|
||||
const auto quality = forceThumbnailQuality
|
||||
? VideoQuality::Thumbnail
|
||||
: (forceFullQuality || min >= kMedium)
|
||||
? VideoQuality::Full
|
||||
: (min >= kSmall)
|
||||
? VideoQuality::Medium
|
||||
: VideoQuality::Thumbnail;
|
||||
if (tile->updateRequestedQuality(quality)) {
|
||||
_qualityRequests.fire(VideoQualityRequest{
|
||||
.endpoint = tile->endpoint(),
|
||||
.endpoint = endpoint,
|
||||
.quality = quality,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ enum class VideoQuality;
|
||||
struct VideoTileTrack {
|
||||
Webrtc::VideoTrack *track = nullptr;
|
||||
MembersRow *row = nullptr;
|
||||
rpl::variable<QSize> trackSize;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return track != nullptr;
|
||||
@@ -77,6 +78,7 @@ public:
|
||||
void add(
|
||||
const VideoEndpoint &endpoint,
|
||||
VideoTileTrack track,
|
||||
rpl::producer<QSize> trackSize,
|
||||
rpl::producer<bool> pinned);
|
||||
void remove(const VideoEndpoint &endpoint);
|
||||
void showLarge(const VideoEndpoint &endpoint);
|
||||
@@ -91,6 +93,8 @@ public:
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
static constexpr auto kShadowMaxAlpha = 80;
|
||||
|
||||
private:
|
||||
struct Textures;
|
||||
class VideoTile;
|
||||
@@ -132,8 +136,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto kShadowMaxAlpha = 80;
|
||||
|
||||
void setup();
|
||||
[[nodiscard]] bool wide() const;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "calls/group/calls_group_members_row.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/gl/gl_shader.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "styles/style_calls.h"
|
||||
@@ -32,7 +33,7 @@ constexpr auto kBlurOpacity = 0.65;
|
||||
constexpr auto kDitherNoiseAmount = 0.002;
|
||||
constexpr auto kMinCameraVisiblePart = 0.75;
|
||||
|
||||
constexpr auto kQuads = 8;
|
||||
constexpr auto kQuads = 9;
|
||||
constexpr auto kQuadVertices = kQuads * 4;
|
||||
constexpr auto kQuadValues = kQuadVertices * 4;
|
||||
constexpr auto kValues = kQuadValues + 8; // Blur texture coordinates.
|
||||
@@ -197,7 +198,7 @@ float insideTexture() {
|
||||
vec2 fromTextureCenter = abs(v_texcoord - textureHalf);
|
||||
vec2 fromTextureEdge = max(fromTextureCenter, textureHalf) - textureHalf;
|
||||
float outsideCheck = dot(fromTextureEdge, fromTextureEdge);
|
||||
return step(outsideCheck, 0);
|
||||
return step(outsideCheck, 0.);
|
||||
}
|
||||
|
||||
vec4 background() {
|
||||
@@ -320,7 +321,6 @@ Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
|
||||
void Viewport::RendererGL::init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
_factor = widget->devicePixelRatio();
|
||||
_frameBuffer.emplace();
|
||||
_frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_frameBuffer->create();
|
||||
@@ -427,7 +427,11 @@ void Viewport::RendererGL::setDefaultViewport(QOpenGLFunctions &f) {
|
||||
void Viewport::RendererGL::paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
_factor = widget->devicePixelRatio();
|
||||
const auto factor = widget->devicePixelRatio();
|
||||
if (_factor != factor) {
|
||||
_factor = factor;
|
||||
_buttons.invalidate();
|
||||
}
|
||||
_viewport = widget->size();
|
||||
|
||||
const auto defaultFramebufferObject = widget->defaultFramebufferObject();
|
||||
@@ -435,7 +439,7 @@ void Viewport::RendererGL::paint(
|
||||
validateDatas();
|
||||
auto index = 0;
|
||||
for (const auto &tile : _owner->_tiles) {
|
||||
if (!tile->shown()) {
|
||||
if (!tile->visible()) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
@@ -551,16 +555,50 @@ void Viewport::RendererGL::paintTile(
|
||||
texCoords.end());
|
||||
}
|
||||
|
||||
// Paused.
|
||||
const auto &pauseIcon = st::groupCallPaused;
|
||||
const auto pausedIcon = _buttons.texturedRect(
|
||||
const auto nameTop = y + (height
|
||||
- st.namePosition.y()
|
||||
- st::semiboldFont->height);
|
||||
|
||||
// Paused icon and text.
|
||||
const auto middle = (st::groupCallVideoPlaceholderHeight
|
||||
- st::groupCallPaused.height()) / 2;
|
||||
const auto pausedSpace = (nameTop - y)
|
||||
- st::groupCallPaused.height()
|
||||
- st::semiboldFont->height;
|
||||
const auto pauseIconSkip = middle - st::groupCallVideoPlaceholderIconTop;
|
||||
const auto pauseTextSkip = st::groupCallVideoPlaceholderTextTop
|
||||
- st::groupCallVideoPlaceholderIconTop;
|
||||
const auto pauseIconTop = !_owner->wide()
|
||||
? (y + (height - st::groupCallPaused.height()) / 2)
|
||||
: (pausedSpace < 3 * st::semiboldFont->height)
|
||||
? (pausedSpace / 3)
|
||||
: std::min(
|
||||
y + (height / 2) - pauseIconSkip,
|
||||
(nameTop
|
||||
- st::semiboldFont->height * 3
|
||||
- st::groupCallPaused.height()));
|
||||
const auto pauseTextTop = (pausedSpace < 3 * st::semiboldFont->height)
|
||||
? (nameTop - (pausedSpace / 3) - st::semiboldFont->height)
|
||||
: std::min(
|
||||
pauseIconTop + pauseTextSkip,
|
||||
nameTop - st::semiboldFont->height * 2);
|
||||
|
||||
const auto pauseIcon = _buttons.texturedRect(
|
||||
QRect(
|
||||
geometry.x() + (geometry.width() - pauseIcon.width()) / 2,
|
||||
geometry.y() + (geometry.height() - pauseIcon.height()) / 2,
|
||||
pauseIcon.width(),
|
||||
pauseIcon.height()),
|
||||
x + (width - st::groupCallPaused.width()) / 2,
|
||||
pauseIconTop,
|
||||
st::groupCallPaused.width(),
|
||||
st::groupCallPaused.height()),
|
||||
_paused);
|
||||
const auto pausedRect = transformRect(pausedIcon.geometry);
|
||||
const auto pauseRect = transformRect(pauseIcon.geometry);
|
||||
|
||||
const auto pausedPosition = QPoint(
|
||||
x + (width - (_pausedTextRect.width() / cIntRetinaFactor())) / 2,
|
||||
pauseTextTop);
|
||||
const auto pausedText = _names.texturedRect(
|
||||
QRect(pausedPosition, _pausedTextRect.size() / cIntRetinaFactor()),
|
||||
_pausedTextRect);
|
||||
const auto pausedRect = transformRect(pausedText.geometry);
|
||||
|
||||
// Pin.
|
||||
const auto pin = _buttons.texturedRect(
|
||||
@@ -594,10 +632,7 @@ void Viewport::RendererGL::paintTile(
|
||||
// Name.
|
||||
const auto namePosition = QPoint(
|
||||
x + st.namePosition.x(),
|
||||
y + (height
|
||||
- st.namePosition.y()
|
||||
- st::semiboldFont->height
|
||||
+ nameShift));
|
||||
nameTop + nameShift);
|
||||
const auto name = _names.texturedRect(
|
||||
QRect(namePosition, tileData.nameRect.size() / cIntRetinaFactor()),
|
||||
tileData.nameRect,
|
||||
@@ -703,17 +738,30 @@ void Viewport::RendererGL::paintTile(
|
||||
name.texture.left(), name.texture.top(),
|
||||
|
||||
// Paused icon.
|
||||
pauseRect.left(), pauseRect.top(),
|
||||
pauseIcon.texture.left(), pauseIcon.texture.bottom(),
|
||||
|
||||
pauseRect.right(), pauseRect.top(),
|
||||
pauseIcon.texture.right(), pauseIcon.texture.bottom(),
|
||||
|
||||
pauseRect.right(), pauseRect.bottom(),
|
||||
pauseIcon.texture.right(), pauseIcon.texture.top(),
|
||||
|
||||
pauseRect.left(), pauseRect.bottom(),
|
||||
pauseIcon.texture.left(), pauseIcon.texture.top(),
|
||||
|
||||
// Paused text.
|
||||
pausedRect.left(), pausedRect.top(),
|
||||
pausedIcon.texture.left(), pausedIcon.texture.bottom(),
|
||||
pausedText.texture.left(), pausedText.texture.bottom(),
|
||||
|
||||
pausedRect.right(), pausedRect.top(),
|
||||
pausedIcon.texture.right(), pausedIcon.texture.bottom(),
|
||||
pausedText.texture.right(), pausedText.texture.bottom(),
|
||||
|
||||
pausedRect.right(), pausedRect.bottom(),
|
||||
pausedIcon.texture.right(), pausedIcon.texture.top(),
|
||||
pausedText.texture.right(), pausedText.texture.top(),
|
||||
|
||||
pausedRect.left(), pausedRect.bottom(),
|
||||
pausedIcon.texture.left(), pausedIcon.texture.top(),
|
||||
pausedText.texture.left(), pausedText.texture.top(),
|
||||
};
|
||||
|
||||
_frameBuffer->bind();
|
||||
@@ -784,7 +832,9 @@ void Viewport::RendererGL::paintTile(
|
||||
|
||||
const auto pinVisible = _owner->wide()
|
||||
&& (pin.geometry.bottom() > y);
|
||||
if (nameShift == fullNameShift && !pinVisible && paused == 0.) {
|
||||
const auto nameVisible = (nameShift != fullNameShift);
|
||||
const auto pausedVisible = (paused > 0.);
|
||||
if (!nameVisible && !pinVisible && !pausedVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -801,31 +851,40 @@ void Viewport::RendererGL::paintTile(
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_buttons.bind(f);
|
||||
|
||||
if (paused > 0) {
|
||||
// Paused icon.
|
||||
if (pausedVisible) {
|
||||
_imageProgram->setUniformValue("g_opacity", GLfloat(paused));
|
||||
FillTexturedRectangle(f, &*_imageProgram, 30);
|
||||
}
|
||||
_imageProgram->setUniformValue("g_opacity", GLfloat(1.f));
|
||||
|
||||
// Pin.
|
||||
if (pinVisible) {
|
||||
FillTexturedRectangle(f, &*_imageProgram, 14);
|
||||
FillTexturedRectangle(f, &*_imageProgram, 18);
|
||||
}
|
||||
|
||||
if (nameShift == fullNameShift) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mute.
|
||||
if (!muteRect.empty()) {
|
||||
if (nameVisible && !muteRect.empty()) {
|
||||
FillTexturedRectangle(f, &*_imageProgram, 22);
|
||||
}
|
||||
|
||||
if (!nameVisible && !pausedVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
_names.bind(f);
|
||||
|
||||
// Name.
|
||||
if (!nameRect.empty()) {
|
||||
_names.bind(f);
|
||||
if (nameVisible && !nameRect.empty()) {
|
||||
FillTexturedRectangle(f, &*_imageProgram, 26);
|
||||
}
|
||||
|
||||
// Paused text.
|
||||
if (pausedVisible && _owner->wide()) {
|
||||
_imageProgram->setUniformValue("g_opacity", GLfloat(paused));
|
||||
FillTexturedRectangle(f, &*_imageProgram, 34);
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::RendererGL::prepareObjects(
|
||||
@@ -929,6 +988,7 @@ void Viewport::RendererGL::bindFrame(
|
||||
program.argb32->setUniformValue("s_texture", GLint(0));
|
||||
} else {
|
||||
const auto yuv = data.yuv420;
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
program.yuv420->bind();
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
tileData.textures.bind(f, 0);
|
||||
@@ -936,8 +996,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
tileData.textureSize,
|
||||
yuv->y.stride,
|
||||
@@ -950,8 +1010,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
tileData.textureChromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -962,8 +1022,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
tileData.textureChromaSize,
|
||||
yuv->v.stride,
|
||||
@@ -1054,7 +1114,6 @@ void Viewport::RendererGL::ensureButtonsImage() {
|
||||
if (_buttons) {
|
||||
return;
|
||||
}
|
||||
const auto factor = cIntRetinaFactor();
|
||||
const auto pinOnSize = VideoTile::PinInnerSize(true);
|
||||
const auto pinOffSize = VideoTile::PinInnerSize(false);
|
||||
const auto backSize = VideoTile::BackInnerSize();
|
||||
@@ -1074,18 +1133,18 @@ void Viewport::RendererGL::ensureButtonsImage() {
|
||||
+ backSize.height()
|
||||
+ muteSize.height()
|
||||
+ pausedSize.height()));
|
||||
const auto imageSize = fullSize * factor;
|
||||
const auto imageSize = fullSize * _factor;
|
||||
auto image = _buttons.takeImage();
|
||||
if (image.size() != imageSize) {
|
||||
image = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
image.fill(Qt::transparent);
|
||||
image.setDevicePixelRatio(cRetinaFactor());
|
||||
image.setDevicePixelRatio(_factor);
|
||||
{
|
||||
auto p = Painter(&image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
_pinOn = QRect(QPoint(), pinOnSize * factor);
|
||||
_pinOn = QRect(QPoint(), pinOnSize * _factor);
|
||||
VideoTile::PaintPinButton(
|
||||
p,
|
||||
true,
|
||||
@@ -1096,7 +1155,9 @@ void Viewport::RendererGL::ensureButtonsImage() {
|
||||
&_pinIcon);
|
||||
|
||||
const auto pinOffTop = pinOnSize.height();
|
||||
_pinOff = QRect(QPoint(0, pinOffTop) * factor, pinOffSize * factor);
|
||||
_pinOff = QRect(
|
||||
QPoint(0, pinOffTop) * _factor,
|
||||
pinOffSize * _factor);
|
||||
VideoTile::PaintPinButton(
|
||||
p,
|
||||
false,
|
||||
@@ -1107,7 +1168,7 @@ void Viewport::RendererGL::ensureButtonsImage() {
|
||||
&_pinIcon);
|
||||
|
||||
const auto backTop = pinOffTop + pinOffSize.height();
|
||||
_back = QRect(QPoint(0, backTop) * factor, backSize * factor);
|
||||
_back = QRect(QPoint(0, backTop) * _factor, backSize * _factor);
|
||||
VideoTile::PaintBackButton(
|
||||
p,
|
||||
0,
|
||||
@@ -1116,16 +1177,18 @@ void Viewport::RendererGL::ensureButtonsImage() {
|
||||
&_pinBackground);
|
||||
|
||||
const auto muteTop = backTop + backSize.height();
|
||||
_muteOn = QRect(QPoint(0, muteTop) * factor, muteSize * factor);
|
||||
_muteOn = QRect(QPoint(0, muteTop) * _factor, muteSize * _factor);
|
||||
_muteIcon.paint(p, { 0, muteTop }, 1.);
|
||||
|
||||
_muteOff = QRect(
|
||||
QPoint(muteSize.width(), muteTop) * factor,
|
||||
muteSize * factor);
|
||||
QPoint(muteSize.width(), muteTop) * _factor,
|
||||
muteSize * _factor);
|
||||
_muteIcon.paint(p, { muteSize.width(), muteTop }, 0.);
|
||||
|
||||
const auto pausedTop = muteTop + muteSize.height();
|
||||
_paused = QRect(QPoint(0, pausedTop) * factor, pausedSize * factor);
|
||||
_paused = QRect(
|
||||
QPoint(0, pausedTop) * _factor,
|
||||
pausedSize * _factor);
|
||||
st::groupCallPaused.paint(p, 0, pausedTop, fullSize.width());
|
||||
}
|
||||
_buttons.setImage(std::move(image));
|
||||
@@ -1137,12 +1200,15 @@ void Viewport::RendererGL::validateDatas() {
|
||||
const auto count = int(tiles.size());
|
||||
const auto factor = cIntRetinaFactor();
|
||||
const auto nameHeight = st::semiboldFont->height * factor;
|
||||
const auto pausedText = tr::lng_group_call_video_paused(tr::now);
|
||||
const auto pausedBottom = nameHeight;
|
||||
const auto pausedWidth = st::semiboldFont->width(pausedText) * factor;
|
||||
struct Request {
|
||||
int index = 0;
|
||||
bool updating = false;
|
||||
};
|
||||
auto requests = std::vector<Request>();
|
||||
auto available = _names.image().width();
|
||||
auto available = std::max(_names.image().width(), pausedWidth);
|
||||
for (auto &data : _tileData) {
|
||||
data.stale = true;
|
||||
}
|
||||
@@ -1174,7 +1240,7 @@ void Viewport::RendererGL::validateDatas() {
|
||||
if (peer != j->peer
|
||||
|| peer->nameVersion != j->nameVersion
|
||||
|| width != j->nameRect.width()) {
|
||||
const auto nameTop = index * nameHeight;
|
||||
const auto nameTop = pausedBottom + index * nameHeight;
|
||||
j->nameRect = QRect(0, nameTop, width, nameHeight);
|
||||
requests.push_back({ .index = i, .updating = true });
|
||||
}
|
||||
@@ -1219,16 +1285,19 @@ void Viewport::RendererGL::validateDatas() {
|
||||
.pause = paused,
|
||||
});
|
||||
}
|
||||
const auto nameTop = pausedBottom + index * nameHeight;
|
||||
_tileData[index].nameVersion = peer->nameVersion;
|
||||
_tileData[index].nameRect = QRect(
|
||||
0,
|
||||
index * nameHeight,
|
||||
nameTop,
|
||||
nameWidth(i),
|
||||
nameHeight);
|
||||
_tileDataIndices[i] = index;
|
||||
}
|
||||
auto image = _names.takeImage();
|
||||
const auto imageSize = QSize(available, _tileData.size() * nameHeight);
|
||||
const auto imageSize = QSize(
|
||||
available,
|
||||
pausedBottom + _tileData.size() * nameHeight);
|
||||
const auto allocate = (image.size() != imageSize);
|
||||
auto paintToImage = allocate
|
||||
? QImage(imageSize, QImage::Format_ARGB32_Premultiplied)
|
||||
@@ -1239,6 +1308,7 @@ void Viewport::RendererGL::validateDatas() {
|
||||
}
|
||||
{
|
||||
auto p = Painter(&paintToImage);
|
||||
p.setPen(st::groupCallVideoTextFg);
|
||||
if (!image.isNull()) {
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.drawImage(0, 0, image);
|
||||
@@ -1259,8 +1329,11 @@ void Viewport::RendererGL::validateDatas() {
|
||||
Qt::transparent);
|
||||
}
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
} else if (allocate) {
|
||||
p.setFont(st::semiboldFont);
|
||||
p.drawText(0, st::semiboldFont->ascent, pausedText);
|
||||
_pausedTextRect = QRect(0, 0, pausedWidth, nameHeight);
|
||||
}
|
||||
p.setPen(st::groupCallVideoTextFg);
|
||||
for (const auto &request : requests) {
|
||||
const auto i = request.index;
|
||||
const auto index = _tileDataIndices[i];
|
||||
@@ -1296,16 +1369,17 @@ void Viewport::RendererGL::validateNoiseTexture(
|
||||
if (_noiseTexture.created()) {
|
||||
return;
|
||||
}
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
_noiseTexture.ensureCreated(f, GL_NEAREST, GL_REPEAT);
|
||||
_noiseTexture.bind(f, 0);
|
||||
f.glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RED,
|
||||
format,
|
||||
kNoiseTextureSize,
|
||||
kNoiseTextureSize,
|
||||
0,
|
||||
GL_RED,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ private:
|
||||
QRect _paused;
|
||||
|
||||
Ui::GL::Image _names;
|
||||
QRect _pausedTextRect;
|
||||
std::vector<TileData> _tileData;
|
||||
std::vector<int> _tileDataIndices;
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ void Viewport::RendererSW::paintFallback(
|
||||
tileData.stale = true;
|
||||
}
|
||||
for (const auto &tile : _owner->_tiles) {
|
||||
if (!tile->shown()) {
|
||||
if (!tile->visible()) {
|
||||
continue;
|
||||
}
|
||||
paintTile(p, tile.get(), bounding, bg);
|
||||
@@ -232,9 +232,48 @@ void Viewport::RendererSW::paintTileControls(
|
||||
&_pinBackground);
|
||||
}
|
||||
|
||||
const auto &st = st::groupCallVideoTile;
|
||||
const auto nameTop = y + (height
|
||||
- st.namePosition.y()
|
||||
- st::semiboldFont->height);
|
||||
|
||||
if (_pausedFrame) {
|
||||
p.fillRect(x, y, width, height, QColor(0, 0, 0, kShadowMaxAlpha));
|
||||
st::groupCallPaused.paintInCenter(p, { x, y, width, height });
|
||||
|
||||
const auto middle = (st::groupCallVideoPlaceholderHeight
|
||||
- st::groupCallPaused.height()) / 2;
|
||||
const auto pausedSpace = (nameTop - y)
|
||||
- st::groupCallPaused.height()
|
||||
- st::semiboldFont->height;
|
||||
const auto pauseIconSkip = middle - st::groupCallVideoPlaceholderIconTop;
|
||||
const auto pauseTextSkip = st::groupCallVideoPlaceholderTextTop
|
||||
- st::groupCallVideoPlaceholderIconTop;
|
||||
const auto pauseIconTop = !_owner->wide()
|
||||
? (y + (height - st::groupCallPaused.height()) / 2)
|
||||
: (pausedSpace < 3 * st::semiboldFont->height)
|
||||
? (pausedSpace / 3)
|
||||
: std::min(
|
||||
y + (height / 2) - pauseIconSkip,
|
||||
(nameTop
|
||||
- st::semiboldFont->height * 3
|
||||
- st::groupCallPaused.height()));
|
||||
const auto pauseTextTop = (pausedSpace < 3 * st::semiboldFont->height)
|
||||
? (nameTop - (pausedSpace / 3) - st::semiboldFont->height)
|
||||
: std::min(
|
||||
pauseIconTop + pauseTextSkip,
|
||||
nameTop - st::semiboldFont->height * 2);
|
||||
|
||||
st::groupCallPaused.paint(
|
||||
p,
|
||||
x + (width - st::groupCallPaused.width()) / 2,
|
||||
pauseIconTop,
|
||||
width);
|
||||
if (_owner->wide()) {
|
||||
p.drawText(
|
||||
QRect(x, pauseTextTop, width, y + height - pauseTextTop),
|
||||
tr::lng_group_call_video_paused(tr::now),
|
||||
style::al_top);
|
||||
}
|
||||
}
|
||||
|
||||
const auto shown = _owner->_controlsShownRatio;
|
||||
@@ -242,7 +281,6 @@ void Viewport::RendererSW::paintTileControls(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &st = st::groupCallVideoTile;
|
||||
const auto fullShift = st.namePosition.y() + st::normalFont->height;
|
||||
const auto shift = anim::interpolate(fullShift, 0, shown);
|
||||
|
||||
@@ -291,11 +329,12 @@ void Viewport::RendererSW::paintTileControls(
|
||||
- st.iconPosition.x() - icon.width()
|
||||
- st.namePosition.x();
|
||||
const auto nameLeft = x + st.namePosition.x();
|
||||
const auto nameTop = y + (height
|
||||
- st.namePosition.y()
|
||||
- st::semiboldFont->height
|
||||
+ shift);
|
||||
row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width);
|
||||
row->name().drawLeftElided(
|
||||
p,
|
||||
nameLeft,
|
||||
nameTop + shift,
|
||||
hasWidth,
|
||||
width);
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -25,11 +25,13 @@ constexpr auto kPausedVideoSize = 90;
|
||||
Viewport::VideoTile::VideoTile(
|
||||
const VideoEndpoint &endpoint,
|
||||
VideoTileTrack track,
|
||||
rpl::producer<QSize> trackSize,
|
||||
rpl::producer<bool> pinned,
|
||||
Fn<void()> update)
|
||||
: _endpoint(endpoint)
|
||||
, _update(std::move(update))
|
||||
, _track(track) {
|
||||
, _track(track)
|
||||
, _trackSize(std::move(trackSize)) {
|
||||
Expects(track.track != nullptr);
|
||||
Expects(track.row != nullptr);
|
||||
|
||||
@@ -80,14 +82,14 @@ bool Viewport::VideoTile::screencast() const {
|
||||
void Viewport::VideoTile::setGeometry(
|
||||
QRect geometry,
|
||||
TileAnimation animation) {
|
||||
_shown = true;
|
||||
_hidden = false;
|
||||
_geometry = geometry;
|
||||
_animation = animation;
|
||||
updateTopControlsPosition();
|
||||
}
|
||||
|
||||
void Viewport::VideoTile::hide() {
|
||||
_shown = false;
|
||||
_hidden = true;
|
||||
_quality = std::nullopt;
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ void Viewport::VideoTile::toggleTopControlsShown(bool shown) {
|
||||
}
|
||||
|
||||
bool Viewport::VideoTile::updateRequestedQuality(VideoQuality quality) {
|
||||
if (!_shown) {
|
||||
if (_hidden) {
|
||||
_quality = std::nullopt;
|
||||
return false;
|
||||
} else if (_quality && *_quality == quality) {
|
||||
@@ -247,26 +249,14 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
||||
}) | rpl::start_with_next([=](bool pinned) {
|
||||
_pinned = pinned;
|
||||
updateTopControlsSize();
|
||||
if (_shown) {
|
||||
if (!_hidden) {
|
||||
updateTopControlsPosition();
|
||||
_update();
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
_track.track->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto size = _track.track->frameSize();
|
||||
if (size.isEmpty()) {
|
||||
_track.track->markFrameShown();
|
||||
} else {
|
||||
_trackSize = size;
|
||||
}
|
||||
_update();
|
||||
}, _lifetime);
|
||||
|
||||
if (const auto size = _track.track->frameSize(); !size.isEmpty()) {
|
||||
_trackSize = size;
|
||||
}
|
||||
) | rpl::start_with_next(_update, _lifetime);
|
||||
|
||||
updateTopControlsSize();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public:
|
||||
VideoTile(
|
||||
const VideoEndpoint &endpoint,
|
||||
VideoTileTrack track,
|
||||
rpl::producer<QSize> trackSize,
|
||||
rpl::producer<bool> pinned,
|
||||
Fn<void()> update);
|
||||
|
||||
@@ -44,8 +45,11 @@ public:
|
||||
[[nodiscard]] bool pinned() const {
|
||||
return _pinned;
|
||||
}
|
||||
[[nodiscard]] bool shown() const {
|
||||
return _shown && !_geometry.isEmpty();
|
||||
[[nodiscard]] bool hidden() const {
|
||||
return _hidden;
|
||||
}
|
||||
[[nodiscard]] bool visible() const {
|
||||
return !_hidden && !_geometry.isEmpty();
|
||||
}
|
||||
[[nodiscard]] QRect pinOuter() const;
|
||||
[[nodiscard]] QRect pinInner() const;
|
||||
@@ -114,7 +118,7 @@ private:
|
||||
Ui::Animations::Simple _topControlsShownAnimation;
|
||||
bool _topControlsShown = false;
|
||||
bool _pinned = false;
|
||||
bool _shown = false;
|
||||
bool _hidden = true;
|
||||
std::optional<VideoQuality> _quality;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -30,16 +30,12 @@ constexpr auto kVolumeStickedValues =
|
||||
{ 25. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 50. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 75. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 100. / kMaxVolumePercent, 5. / kMaxVolumePercent },
|
||||
{ 100. / kMaxVolumePercent, 10. / kMaxVolumePercent },
|
||||
{ 125. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 150. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
}};
|
||||
|
||||
QString VolumeString(int volumePercent) {
|
||||
return u"%1%"_q.arg(volumePercent);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MenuVolumeItem::MenuVolumeItem(
|
||||
@@ -75,20 +71,21 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
const auto geometry = QRect(QPoint(), size);
|
||||
_itemRect = geometry - _st.itemPadding;
|
||||
_itemRect = geometry - st::groupCallMenuVolumePadding;
|
||||
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
|
||||
_arcPosition = _speakerRect.center()
|
||||
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
|
||||
_volumeRect = QRect(
|
||||
_arcPosition.x()
|
||||
+ st::groupCallMenuVolumeSkip
|
||||
+ _arcs->finishedWidth(),
|
||||
const auto sliderLeft = _arcPosition.x()
|
||||
+ st::groupCallMenuVolumeSkip
|
||||
+ _arcs->maxWidth()
|
||||
+ st::groupCallMenuVolumeSkip;
|
||||
_slider->setGeometry(
|
||||
st::groupCallMenuVolumeMargin.left(),
|
||||
_speakerRect.y(),
|
||||
_st.itemStyle.font->width(VolumeString(kMaxVolumePercent)),
|
||||
(geometry.width()
|
||||
- st::groupCallMenuVolumeMargin.left()
|
||||
- st::groupCallMenuVolumeMargin.right()),
|
||||
_speakerRect.height());
|
||||
|
||||
_slider->setGeometry(_itemRect
|
||||
- style::margins(0, contentHeight() / 2, 0, 0));
|
||||
}, lifetime());
|
||||
|
||||
setCloudVolume(startVolume);
|
||||
@@ -110,15 +107,12 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
unmuteColor(),
|
||||
muteColor(),
|
||||
muteProgress);
|
||||
p.setPen(mutePen);
|
||||
p.setFont(_st.itemStyle.font);
|
||||
p.drawText(_volumeRect, VolumeString(volume), style::al_left);
|
||||
|
||||
_crossLineMute->paint(
|
||||
p,
|
||||
_speakerRect.topLeft(),
|
||||
muteProgress,
|
||||
(!muteProgress) ? std::nullopt : std::optional<QColor>(mutePen));
|
||||
(muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt);
|
||||
|
||||
{
|
||||
p.translate(_arcPosition);
|
||||
@@ -133,7 +127,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
_toggleMuteLocallyRequests.fire_copy(newMuted);
|
||||
|
||||
_crossLineAnimation.start(
|
||||
[=] { update(_speakerRect.united(_volumeRect)); },
|
||||
[=] { update(_speakerRect); },
|
||||
_localMuted ? 0. : 1.,
|
||||
_localMuted ? 1. : 0.,
|
||||
st::callPanelDuration);
|
||||
@@ -141,8 +135,8 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
if (value > 0) {
|
||||
_changeVolumeLocallyRequests.fire(value * _maxVolume);
|
||||
}
|
||||
update(_volumeRect);
|
||||
_arcs->setValue(value);
|
||||
updateSliderColor(value);
|
||||
});
|
||||
|
||||
const auto returnVolume = [=] {
|
||||
@@ -169,6 +163,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
if (!_cloudMuted && !muted) {
|
||||
_changeVolumeRequests.fire_copy(newVolume);
|
||||
}
|
||||
updateSliderColor(value);
|
||||
});
|
||||
|
||||
std::move(
|
||||
@@ -209,30 +204,15 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
}
|
||||
|
||||
void MenuVolumeItem::initArcsAnimation() {
|
||||
const auto volumeLeftWas = lifetime().make_state<int>(0);
|
||||
const auto lastTime = lifetime().make_state<int>(0);
|
||||
_arcsAnimation.init([=](crl::time now) {
|
||||
_arcs->update(now);
|
||||
update(_speakerRect);
|
||||
|
||||
const auto wasRect = _volumeRect;
|
||||
_volumeRect.moveLeft(anim::interpolate(
|
||||
*volumeLeftWas,
|
||||
_arcPosition.x()
|
||||
+ st::groupCallMenuVolumeSkip
|
||||
+ _arcs->finishedWidth(),
|
||||
std::clamp(
|
||||
(now - (*lastTime))
|
||||
/ float64(st::groupCallSpeakerArcsAnimation.duration),
|
||||
0.,
|
||||
1.)));
|
||||
update(_speakerRect.united(wasRect.united(_volumeRect)));
|
||||
});
|
||||
|
||||
_arcs->startUpdateRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!_arcsAnimation.animating()) {
|
||||
*volumeLeftWas = _volumeRect.left();
|
||||
*lastTime = crl::now();
|
||||
_arcsAnimation.start();
|
||||
}
|
||||
@@ -269,8 +249,30 @@ void MenuVolumeItem::setCloudVolume(int volume) {
|
||||
}
|
||||
|
||||
void MenuVolumeItem::setSliderVolume(int volume) {
|
||||
_slider->setValue(float64(volume) / _maxVolume);
|
||||
update(_volumeRect);
|
||||
const auto value = float64(volume) / _maxVolume;
|
||||
_slider->setValue(value);
|
||||
updateSliderColor(value);
|
||||
}
|
||||
|
||||
void MenuVolumeItem::updateSliderColor(float64 value) {
|
||||
value = std::clamp(value, 0., 1.);
|
||||
const auto color = [](int rgb) {
|
||||
return QColor(
|
||||
int((rgb & 0xFF0000) >> 16),
|
||||
int((rgb & 0x00FF00) >> 8),
|
||||
int(rgb & 0x0000FF));
|
||||
};
|
||||
const auto colors = std::array<QColor, 4>{ {
|
||||
color(0xF66464),
|
||||
color(0xD0B738),
|
||||
color(0x24CD80),
|
||||
color(0x3BBCEC),
|
||||
} };
|
||||
_slider->setActiveFgOverride((value < 0.25)
|
||||
? anim::color(colors[0], colors[1], value / 0.25)
|
||||
: (value < 0.5)
|
||||
? anim::color(colors[1], colors[2], (value - 0.25) / 0.25)
|
||||
: anim::color(colors[2], colors[3], (value - 0.5) / 0.5));
|
||||
}
|
||||
|
||||
not_null<QAction*> MenuVolumeItem::action() const {
|
||||
@@ -282,9 +284,9 @@ bool MenuVolumeItem::isEnabled() const {
|
||||
}
|
||||
|
||||
int MenuVolumeItem::contentHeight() const {
|
||||
return _st.itemPadding.top()
|
||||
+ _st.itemPadding.bottom()
|
||||
+ _stCross.icon.height() * 2;
|
||||
return st::groupCallMenuVolumePadding.top()
|
||||
+ st::groupCallMenuVolumePadding.bottom()
|
||||
+ _stCross.icon.height();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
|
||||
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
|
||||
void setCloudVolume(int volume);
|
||||
void setSliderVolume(int volume);
|
||||
void updateSliderColor(float64 value);
|
||||
|
||||
QColor unmuteColor() const;
|
||||
QColor muteColor() const;
|
||||
@@ -64,7 +65,6 @@ private:
|
||||
|
||||
QRect _itemRect;
|
||||
QRect _speakerRect;
|
||||
QRect _volumeRect;
|
||||
QPoint _arcPosition;
|
||||
|
||||
const base::unique_qptr<Ui::MediaSlider> _slider;
|
||||
|
||||
@@ -529,8 +529,7 @@ void EmojiKeywords::apiChanged(ApiWrap *api) {
|
||||
_api = api;
|
||||
if (_api) {
|
||||
crl::on_main(&_api->session(), crl::guard(&_guard, [=] {
|
||||
base::ObservableViewer(
|
||||
Lang::CurrentCloudManager().firstLanguageSuggestion()
|
||||
Lang::CurrentCloudManager().firstLanguageSuggestion(
|
||||
) | rpl::filter([=] {
|
||||
// Refresh with the suggested language if we already were asked.
|
||||
return !_data.empty();
|
||||
|
||||
@@ -34,8 +34,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -123,6 +123,8 @@ private:
|
||||
|
||||
bool _previewShown = false;
|
||||
|
||||
bool _isOneColumn = false;
|
||||
|
||||
Fn<SendMenu::Type()> _sendMenuType;
|
||||
|
||||
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
|
||||
@@ -741,6 +743,12 @@ FieldAutocomplete::Inner::Inner(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
_isOneColumn = controller->adaptive().isOneColumn();
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
@@ -937,9 +945,9 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
|
||||
p.fillRect(_isOneColumn ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (_isOneColumn ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
|
||||
}
|
||||
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
|
||||
p.fillRect(_isOneColumn ? 0 : st::lineWidth, _parent->innerTop(), width() - (_isOneColumn ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) {
|
||||
|
||||
@@ -188,12 +188,13 @@ GifsListWidget::GifsListWidget(
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
subscribe(controller->gifPauseLevelChanged(), [=] {
|
||||
controller->gifPauseLevelChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs)) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<TabbedSelector::FileChosen> GifsListWidget::fileChosen() const {
|
||||
|
||||
@@ -46,8 +46,7 @@ void AddGifAction(
|
||||
|
||||
class GifsListWidget
|
||||
: public TabbedSelector::Inner
|
||||
, public InlineBots::Layout::Context
|
||||
, private base::Subscriber {
|
||||
, public InlineBots::Layout::Context {
|
||||
public:
|
||||
using InlineChosen = TabbedSelector::InlineChosen;
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ Fn<bool(
|
||||
return Ui::InputField::IsValidMarkdownLink(link)
|
||||
&& !TextUtilities::IsMentionLink(link);
|
||||
}
|
||||
Ui::show(Box<EditLinkBox>(controller, text, link, [=](
|
||||
controller->show(Box<EditLinkBox>(controller, text, link, [=](
|
||||
const QString &text,
|
||||
const QString &link) {
|
||||
if (const auto strong = weak.data()) {
|
||||
@@ -321,7 +321,9 @@ void InitSpellchecker(
|
||||
Core::App().settings().spellcheckerEnabledValue(),
|
||||
Spellchecker::SpellingHighlighter::CustomContextMenuItem{
|
||||
tr::lng_settings_manage_dictionaries(tr::now),
|
||||
[=] { Ui::show(Box<Ui::ManageDictionariesBox>(controller)); }
|
||||
[=] {
|
||||
controller->show(Box<Ui::ManageDictionariesBox>(controller));
|
||||
}
|
||||
});
|
||||
field->setExtendedContextMenu(s->contextMenuCreated());
|
||||
#endif // TDESKTOP_DISABLE_SPELLCHECK
|
||||
|
||||
@@ -534,7 +534,7 @@ void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) {
|
||||
updateSelected();
|
||||
|
||||
if (_iconOver == SpecialOver::Settings) {
|
||||
Ui::show(Box<StickersBox>(
|
||||
_pan->controller()->show(Box<StickersBox>(
|
||||
_pan->controller(),
|
||||
(hasOnlyFeaturedSets()
|
||||
? StickersBox::Section::Featured
|
||||
@@ -909,7 +909,7 @@ StickersListWidget::StickersListWidget(
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
_settings->addClickHandler([=] {
|
||||
Ui::show(
|
||||
controller->show(
|
||||
Box<StickersBox>(controller, StickersBox::Section::Installed));
|
||||
});
|
||||
|
||||
@@ -2185,7 +2185,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
removeSet(sets[button->section].id);
|
||||
}
|
||||
} else if (std::get_if<OverGroupAdd>(&pressed)) {
|
||||
Ui::show(Box<StickersBox>(controller(), _megagroupSet));
|
||||
controller()->show(Box<StickersBox>(controller(), _megagroupSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3005,7 +3005,7 @@ void StickersListWidget::displaySet(uint64 setId) {
|
||||
if (setId == Data::Stickers::MegagroupSetId) {
|
||||
if (_megagroupSet->canEditStickers()) {
|
||||
_displayingSet = true;
|
||||
checkHideWithBox(Ui::show(
|
||||
checkHideWithBox(controller()->show(
|
||||
Box<StickersBox>(controller(), _megagroupSet),
|
||||
Ui::LayerOption::KeepOther).data());
|
||||
return;
|
||||
@@ -3019,7 +3019,7 @@ void StickersListWidget::displaySet(uint64 setId) {
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
_displayingSet = true;
|
||||
checkHideWithBox(Ui::show(
|
||||
checkHideWithBox(controller()->show(
|
||||
Box<StickerSetBox>(controller(), it->second->mtpInput()),
|
||||
Ui::LayerOption::KeepOther).data());
|
||||
}
|
||||
@@ -3083,7 +3083,7 @@ void StickersListWidget::removeMegagroupSet(bool locally) {
|
||||
return;
|
||||
}
|
||||
_removingSetId = Data::Stickers::MegagroupSetId;
|
||||
Ui::show(Box<ConfirmBox>(tr::lng_stickers_remove_group_set(tr::now), crl::guard(this, [this, group = _megagroupSet] {
|
||||
controller()->show(Box<ConfirmBox>(tr::lng_stickers_remove_group_set(tr::now), crl::guard(this, [this, group = _megagroupSet] {
|
||||
Expects(group->mgInfo != nullptr);
|
||||
|
||||
if (group->mgInfo->stickerSet.type() != mtpc_inputStickerSetEmpty) {
|
||||
@@ -3105,7 +3105,7 @@ void StickersListWidget::removeSet(uint64 setId) {
|
||||
const auto set = it->second.get();
|
||||
_removingSetId = set->id;
|
||||
auto text = tr::lng_stickers_remove_pack(tr::now, lt_sticker_pack, set->title);
|
||||
Ui::show(Box<ConfirmBox>(text, tr::lng_stickers_remove_pack_confirm(tr::now), crl::guard(this, [=] {
|
||||
controller()->show(Box<ConfirmBox>(text, tr::lng_stickers_remove_pack_confirm(tr::now), crl::guard(this, [=] {
|
||||
Ui::hideLayer();
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto it = sets.find(_removingSetId);
|
||||
|
||||
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/view/media_view_overlay_widget.h"
|
||||
#include "media/view/media_view_open_common.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
@@ -57,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_lock_widgets.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/emoji_config.h"
|
||||
@@ -75,7 +77,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/confirm_phone_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
@@ -177,7 +178,6 @@ Application::~Application() {
|
||||
Media::Player::finish(_audio.get());
|
||||
style::stopManager();
|
||||
|
||||
Global::finish();
|
||||
ThirdParty::finish();
|
||||
|
||||
Instance = nullptr;
|
||||
@@ -187,8 +187,7 @@ void Application::run() {
|
||||
style::internal::StartFonts();
|
||||
|
||||
ThirdParty::start();
|
||||
Global::start();
|
||||
refreshGlobalProxy(); // Depends on Global::start().
|
||||
refreshGlobalProxy(); // Depends on Core::IsAppLaunched().
|
||||
|
||||
// Depends on OpenSSL on macOS, so on ThirdParty::start().
|
||||
// Depends on notifications settings.
|
||||
@@ -283,31 +282,37 @@ void Application::run() {
|
||||
LOG(("Shortcuts Error: %1").arg(error));
|
||||
}
|
||||
|
||||
if (!Platform::IsMac()
|
||||
&& Ui::Integration::Instance().openglLastCheckFailed()) {
|
||||
if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) {
|
||||
showOpenGLCrashNotification();
|
||||
}
|
||||
|
||||
_window->openInMediaViewRequests(
|
||||
) | rpl::start_with_next([=](Media::View::OpenRequest &&request) {
|
||||
if (_mediaView) {
|
||||
_mediaView->show(std::move(request));
|
||||
}
|
||||
}, _window->lifetime());
|
||||
}
|
||||
|
||||
void Application::showOpenGLCrashNotification() {
|
||||
const auto enable = [=] {
|
||||
Ui::GL::ForceDisable(false);
|
||||
Ui::Integration::Instance().openglCheckFinish();
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(false);
|
||||
Local::writeSettings();
|
||||
App::restart();
|
||||
};
|
||||
const auto keepDisabled = [=] {
|
||||
Ui::GL::ForceDisable(true);
|
||||
Ui::Integration::Instance().openglCheckFinish();
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(true);
|
||||
Local::writeSettings();
|
||||
};
|
||||
_window->show(Box<ConfirmBox>(
|
||||
"Last time OpenGL crashed on initialization. "
|
||||
"Perhaps it is a problem with your graphics card driver.\n\n"
|
||||
"Right now OpenGL was disabled. You can try to enable it back "
|
||||
"or keep it disabled, if it continues crashing.",
|
||||
"There may be a problem with your graphics drivers and OpenGL. "
|
||||
"Try updating your drivers.\n\n"
|
||||
"OpenGL has been disabled. You can try to enable it again "
|
||||
"or keep it disabled if crashes continue.",
|
||||
"Enable",
|
||||
"Keep Disabled",
|
||||
enable,
|
||||
@@ -321,8 +326,6 @@ void Application::startDomain() {
|
||||
startSettingsAndBackground();
|
||||
}
|
||||
if (state != Storage::StartResult::Success) {
|
||||
Global::SetLocalPasscode(true);
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
lockByPasscode();
|
||||
DEBUG_LOG(("Application Info: passcode needed..."));
|
||||
}
|
||||
@@ -387,49 +390,6 @@ bool Application::hideMediaView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::showPhoto(not_null<const PhotoOpenClickHandler*> link) {
|
||||
const auto photo = link->photo();
|
||||
const auto peer = link->peer();
|
||||
const auto item = photo->owner().message(link->context());
|
||||
return (!item && peer)
|
||||
? showPhoto(photo, peer)
|
||||
: showPhoto(photo, item);
|
||||
}
|
||||
|
||||
void Application::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
_mediaView->showPhoto(photo, item);
|
||||
}
|
||||
|
||||
void Application::showPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
not_null<PeerData*> peer) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
_mediaView->showPhoto(photo, peer);
|
||||
}
|
||||
|
||||
void Application::showDocument(not_null<DocumentData*> document, HistoryItem *item) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
if (cUseExternalVideoPlayer()
|
||||
&& document->isVideoFile()
|
||||
&& !document->filepath().isEmpty()) {
|
||||
File::Launch(document->location(false).fname);
|
||||
} else {
|
||||
_mediaView->showDocument(document, item);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::showTheme(
|
||||
not_null<DocumentData*> document,
|
||||
const Data::CloudTheme &cloud) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
_mediaView->showTheme(document, cloud);
|
||||
}
|
||||
|
||||
PeerData *Application::ui_getPeerForMouseAction() {
|
||||
if (_mediaView && !_mediaView->isHidden()) {
|
||||
return _mediaView->ui_getPeerForMouseAction();
|
||||
@@ -525,17 +485,17 @@ void Application::setCurrentProxy(
|
||||
const MTP::ProxyData &proxy,
|
||||
MTP::ProxyData::Settings settings) {
|
||||
const auto current = [&] {
|
||||
return (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)
|
||||
? Global::SelectedProxy()
|
||||
return _settings.proxy().isEnabled()
|
||||
? _settings.proxy().selected()
|
||||
: MTP::ProxyData();
|
||||
};
|
||||
const auto was = current();
|
||||
Global::SetSelectedProxy(proxy);
|
||||
Global::SetProxySettings(settings);
|
||||
_settings.proxy().setSelected(proxy);
|
||||
_settings.proxy().setSettings(settings);
|
||||
const auto now = current();
|
||||
refreshGlobalProxy();
|
||||
_proxyChanges.fire({ was, now });
|
||||
Global::RefConnectionTypeChanged().notify();
|
||||
_settings.proxy().connectionTypeChangesNotify();
|
||||
}
|
||||
|
||||
auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
|
||||
@@ -543,11 +503,10 @@ auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
|
||||
}
|
||||
|
||||
void Application::badMtprotoConfigurationError() {
|
||||
if (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled
|
||||
&& !_badProxyDisableBox) {
|
||||
if (_settings.proxy().isEnabled() && !_badProxyDisableBox) {
|
||||
const auto disableCallback = [=] {
|
||||
setCurrentProxy(
|
||||
Global::SelectedProxy(),
|
||||
_settings.proxy().selected(),
|
||||
MTP::ProxyData::Settings::System);
|
||||
};
|
||||
_badProxyDisableBox = Ui::show(Box<InformBox>(
|
||||
@@ -592,6 +551,14 @@ void Application::startEmojiImageLoader() {
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Application::setScreenIsLocked(bool locked) {
|
||||
_screenIsLocked = locked;
|
||||
}
|
||||
|
||||
bool Application::screenIsLocked() const {
|
||||
return _screenIsLocked;
|
||||
}
|
||||
|
||||
void Application::setDefaultFloatPlayerDelegate(
|
||||
not_null<Media::Player::FloatDelegate*> delegate) {
|
||||
Expects(!_defaultFloatPlayerDelegate == !_floatPlayers);
|
||||
@@ -882,7 +849,7 @@ bool Application::passcodeLocked() const {
|
||||
void Application::updateNonIdle() {
|
||||
_lastNonIdleTime = crl::now();
|
||||
if (const auto session = maybeActiveSession()) {
|
||||
session->updates().checkIdleFinish();
|
||||
session->updates().checkIdleFinish(_lastNonIdleTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,19 +876,21 @@ bool Application::someSessionExists() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::checkAutoLock() {
|
||||
if (!Global::LocalPasscode()
|
||||
void Application::checkAutoLock(crl::time lastNonIdleTime) {
|
||||
if (!_domain->local().hasLocalPasscode()
|
||||
|| passcodeLocked()
|
||||
|| !someSessionExists()) {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
return;
|
||||
} else if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = this->lastNonIdleTime();
|
||||
}
|
||||
|
||||
checkLocalTime();
|
||||
const auto now = crl::now();
|
||||
const auto shouldLockInMs = _settings.autoLock() * 1000LL;
|
||||
const auto checkTimeMs = now - lastNonIdleTime();
|
||||
const auto checkTimeMs = now - lastNonIdleTime;
|
||||
if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
@@ -943,7 +912,7 @@ void Application::checkAutoLockIn(crl::time time) {
|
||||
void Application::localPasscodeChanged() {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
checkAutoLock();
|
||||
checkAutoLock(crl::now());
|
||||
}
|
||||
|
||||
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
@@ -1146,7 +1115,7 @@ void Application::startShortcuts() {
|
||||
return true;
|
||||
});
|
||||
request->check(Command::Lock) && request->handle([=] {
|
||||
if (!passcodeLocked() && Global::LocalPasscode()) {
|
||||
if (!passcodeLocked() && _domain->local().hasLocalPasscode()) {
|
||||
lockByPasscode();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_settings.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
#include "base/observer.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class MainWindow;
|
||||
@@ -104,7 +103,7 @@ namespace Core {
|
||||
class Launcher;
|
||||
struct LocalUrlHandler;
|
||||
|
||||
class Application final : public QObject, private base::Subscriber {
|
||||
class Application final : public QObject {
|
||||
public:
|
||||
struct ProxyChange {
|
||||
MTP::ProxyData was;
|
||||
@@ -144,13 +143,6 @@ public:
|
||||
// Media view interface.
|
||||
void checkMediaViewActivation();
|
||||
bool hideMediaView();
|
||||
void showPhoto(not_null<const PhotoOpenClickHandler*> link);
|
||||
void showPhoto(not_null<PhotoData*> photo, HistoryItem *item);
|
||||
void showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> item);
|
||||
void showDocument(not_null<DocumentData*> document, HistoryItem *item);
|
||||
void showTheme(
|
||||
not_null<DocumentData*> document,
|
||||
const Data::CloudTheme &cloud);
|
||||
[[nodiscard]] PeerData *ui_getPeerForMouseAction();
|
||||
|
||||
[[nodiscard]] QPoint getPointForCallPanelCenter() const;
|
||||
@@ -253,7 +245,7 @@ public:
|
||||
rpl::producer<bool> passcodeLockChanges() const;
|
||||
rpl::producer<bool> passcodeLockValue() const;
|
||||
|
||||
void checkAutoLock();
|
||||
void checkAutoLock(crl::time lastNonIdleTime = 0);
|
||||
void checkAutoLockIn(crl::time time);
|
||||
void localPasscodeChanged();
|
||||
|
||||
@@ -281,6 +273,10 @@ public:
|
||||
|
||||
void call_handleObservables();
|
||||
|
||||
// Global runtime variables.
|
||||
void setScreenIsLocked(bool locked);
|
||||
bool screenIsLocked() const;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
@@ -351,7 +347,6 @@ private:
|
||||
const std::unique_ptr<Lang::CloudManager> _langCloudManager;
|
||||
const std::unique_ptr<ChatHelpers::EmojiKeywords> _emojiKeywords;
|
||||
std::unique_ptr<Lang::Translator> _translator;
|
||||
base::Observable<void> _passcodedChanged;
|
||||
QPointer<Ui::BoxContent> _badProxyDisableBox;
|
||||
|
||||
std::unique_ptr<Media::Player::FloatController> _floatPlayers;
|
||||
@@ -362,6 +357,7 @@ private:
|
||||
const QImage _logoNoMargin;
|
||||
|
||||
rpl::variable<bool> _passcodeLock;
|
||||
bool _screenIsLocked = false;
|
||||
|
||||
crl::time _shouldLockAt = 0;
|
||||
base::Timer _autoLockTimer;
|
||||
|
||||
@@ -21,60 +21,6 @@ namespace {
|
||||
|
||||
std::map<int, const char*> BetaLogs() {
|
||||
return {
|
||||
{
|
||||
2004006,
|
||||
"- Fix image compression option when sending files with drag-n-drop.\n"
|
||||
|
||||
"- Fix caption text selection in media albums.\n"
|
||||
|
||||
"- Fix drafts display in personal chats in the chats list.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
},
|
||||
{
|
||||
2004008,
|
||||
"- Upgrade several third party libraries to latest versions.\n"
|
||||
},
|
||||
{
|
||||
2004010,
|
||||
"- Use inline bots and sticker by emoji suggestions in channel comments.\n"
|
||||
|
||||
"- Lock voice message recording, listen to your voice message before sending.\n"
|
||||
},
|
||||
{
|
||||
2004011,
|
||||
"- Improve locked voice message recording.\n"
|
||||
|
||||
"- Fix main window closing to tray on Windows.\n"
|
||||
|
||||
"- Fix crash in bot command sending.\n"
|
||||
|
||||
"- Fix adding additional photos when sending an album to a group with enabled slow mode.\n"
|
||||
},
|
||||
{
|
||||
2004012,
|
||||
"- Voice chats in groups. (alpha version)\n"
|
||||
},
|
||||
{
|
||||
2004014,
|
||||
"- Create voice chats in legacy groups.\n"
|
||||
|
||||
"- Fix sticker pack opening.\n"
|
||||
|
||||
"- Fix group status display.\n"
|
||||
|
||||
"- Fix group members display.\n"
|
||||
},
|
||||
{
|
||||
2004015,
|
||||
"- Improve design of voice chats.\n"
|
||||
|
||||
"- Fix sending of voice messages as replies.\n"
|
||||
|
||||
"- Fix 'Open With' menu position in macOS.\n"
|
||||
|
||||
"- Fix freeze on secondary screen disconnect.\n"
|
||||
},
|
||||
{
|
||||
2005002,
|
||||
"- Fix possible crash in video calls.\n"
|
||||
@@ -173,6 +119,22 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Several crash fixes.\n"
|
||||
},
|
||||
{
|
||||
2007009,
|
||||
"- Added \"Enable noise suppression\" option to group calls Settings.\n"
|
||||
|
||||
"- Fix media viewer with Retina + Non-Retina dual monitor setup on macOS.\n"
|
||||
|
||||
"- Several bug and crash fixes.\n"
|
||||
},
|
||||
{
|
||||
2007010,
|
||||
"- Added ability to mix together bold, italic and other formatting.\n"
|
||||
|
||||
"- Fix voice chats and video calls OpenGL with some drivers on Windows.\n"
|
||||
|
||||
"- Several bug fixes.\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "facades.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -77,6 +78,7 @@ Settings::Settings()
|
||||
QByteArray Settings::serialize() const {
|
||||
const auto themesAccentColors = _themesAccentColors.serialize();
|
||||
const auto windowPosition = Serialize(_windowPosition);
|
||||
const auto proxy = _proxy.serialize();
|
||||
|
||||
auto recentEmojiPreloadGenerated = std::vector<RecentEmojiId>();
|
||||
if (_recentEmojiPreload.empty()) {
|
||||
@@ -93,22 +95,35 @@ QByteArray Settings::serialize() const {
|
||||
+ sizeof(qint32) * 5
|
||||
+ Serialize::stringSize(_downloadPath.current())
|
||||
+ Serialize::bytearraySize(_downloadPathBookmark)
|
||||
+ sizeof(qint32) * 12
|
||||
+ sizeof(qint32) * 9
|
||||
+ Serialize::stringSize(_callOutputDeviceId)
|
||||
+ Serialize::stringSize(_callInputDeviceId)
|
||||
+ Serialize::stringSize(_callVideoInputDeviceId)
|
||||
+ sizeof(qint32) * 5;
|
||||
for (const auto &[key, value] : _soundOverrides) {
|
||||
size += Serialize::stringSize(key) + Serialize::stringSize(value);
|
||||
}
|
||||
size += sizeof(qint32) * 13
|
||||
+ Serialize::bytearraySize(_videoPipGeometry)
|
||||
+ sizeof(qint32)
|
||||
+ (_dictionariesEnabled.current().size() * sizeof(quint64))
|
||||
+ sizeof(qint32) * 12
|
||||
+ Serialize::stringSize(_callVideoInputDeviceId)
|
||||
+ sizeof(qint32) * 2
|
||||
+ Serialize::bytearraySize(_groupCallPushToTalkShortcut)
|
||||
+ sizeof(qint64)
|
||||
+ sizeof(qint32) * 2
|
||||
+ Serialize::bytearraySize(windowPosition)
|
||||
+ sizeof(qint32);
|
||||
for (const auto &[id, rating] : recentEmojiPreloadData) {
|
||||
size += Serialize::stringSize(id) + sizeof(quint16);
|
||||
}
|
||||
size += sizeof(qint32);
|
||||
for (const auto &[id, variant] : _emojiVariants) {
|
||||
size += Serialize::stringSize(id) + sizeof(quint8);
|
||||
}
|
||||
size += Serialize::bytearraySize(_videoPipGeometry);
|
||||
size += Serialize::bytearraySize(windowPosition);
|
||||
size += sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(proxy)
|
||||
+ sizeof(qint32);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -117,7 +132,7 @@ QByteArray Settings::serialize() const {
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream
|
||||
<< themesAccentColors
|
||||
<< qint32(_adaptiveForWide ? 1 : 0)
|
||||
<< qint32(_adaptiveForWide.current() ? 1 : 0)
|
||||
<< qint32(_moderateModeEnabled ? 1 : 0)
|
||||
<< qint32(qRound(_songVolume.current() * 1e6))
|
||||
<< qint32(qRound(_videoVolume.current() * 1e6))
|
||||
@@ -195,7 +210,12 @@ QByteArray Settings::serialize() const {
|
||||
for (const auto &[id, variant] : _emojiVariants) {
|
||||
stream << id << quint8(variant);
|
||||
}
|
||||
stream << qint32(_disableOpenGL ? 1 : 0);
|
||||
stream
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
|
||||
<< qint32(_workMode.current())
|
||||
<< proxy
|
||||
<< qint32(_hiddenGroupCallTooltips.value());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -209,7 +229,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
QByteArray themesAccentColors;
|
||||
qint32 adaptiveForWide = _adaptiveForWide ? 1 : 0;
|
||||
qint32 adaptiveForWide = _adaptiveForWide.current() ? 1 : 0;
|
||||
qint32 moderateModeEnabled = _moderateModeEnabled ? 1 : 0;
|
||||
qint32 songVolume = qint32(qRound(_songVolume.current() * 1e6));
|
||||
qint32 videoVolume = qint32(qRound(_videoVolume.current() * 1e6));
|
||||
@@ -272,6 +292,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
std::vector<RecentEmojiId> recentEmojiPreload;
|
||||
base::flat_map<QString, uint8> emojiVariants;
|
||||
qint32 disableOpenGL = _disableOpenGL ? 1 : 0;
|
||||
qint32 groupCallNoiseSuppression = _groupCallNoiseSuppression ? 1 : 0;
|
||||
qint32 workMode = static_cast<qint32>(_workMode.current());
|
||||
QByteArray proxy;
|
||||
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -403,12 +427,26 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> disableOpenGL;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> groupCallNoiseSuppression;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> workMode;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> proxy;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> hiddenGroupCallTooltips;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
return;
|
||||
} else if (!_themesAccentColors.setFromSerialized(themesAccentColors)) {
|
||||
return;
|
||||
} else if (!_proxy.setFromSerialized(proxy)) {
|
||||
return;
|
||||
}
|
||||
_adaptiveForWide = (adaptiveForWide == 1);
|
||||
_moderateModeEnabled = (moderateModeEnabled == 1);
|
||||
@@ -421,11 +459,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_soundNotify = (soundNotify == 1);
|
||||
_desktopNotify = (desktopNotify == 1);
|
||||
_flashBounceNotify = (flashBounceNotify == 1);
|
||||
const auto uncheckedNotifyView = static_cast<DBINotifyView>(notifyView);
|
||||
const auto uncheckedNotifyView = static_cast<NotifyView>(notifyView);
|
||||
switch (uncheckedNotifyView) {
|
||||
case dbinvShowNothing:
|
||||
case dbinvShowName:
|
||||
case dbinvShowPreview: _notifyView = uncheckedNotifyView; break;
|
||||
case NotifyView::ShowNothing:
|
||||
case NotifyView::ShowName:
|
||||
case NotifyView::ShowPreview: _notifyView = uncheckedNotifyView; break;
|
||||
}
|
||||
switch (nativeNotifications) {
|
||||
case 0: _nativeNotifications = std::nullopt; break;
|
||||
@@ -511,13 +549,25 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_disableOpenGL = (disableOpenGL == 1);
|
||||
if (!Platform::IsMac()) {
|
||||
Ui::GL::ForceDisable(_disableOpenGL
|
||||
|| Ui::Integration::Instance().openglLastCheckFailed());
|
||||
|| Ui::GL::LastCrashCheckFailed());
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::chatWide() const {
|
||||
return _adaptiveForWide
|
||||
&& (Global::AdaptiveChatLayout() == Adaptive::ChatLayout::Wide);
|
||||
_groupCallNoiseSuppression = (groupCallNoiseSuppression == 1);
|
||||
const auto uncheckedWorkMode = static_cast<WorkMode>(workMode);
|
||||
switch (uncheckedWorkMode) {
|
||||
case WorkMode::WindowAndTray:
|
||||
case WorkMode::TrayOnly:
|
||||
case WorkMode::WindowOnly: _workMode = uncheckedWorkMode; break;
|
||||
}
|
||||
_hiddenGroupCallTooltips = [&] {
|
||||
using Tooltip = Calls::Group::StickedTooltip;
|
||||
return Tooltip(0)
|
||||
| ((hiddenGroupCallTooltips & int(Tooltip::Camera))
|
||||
? Tooltip::Camera
|
||||
: Tooltip(0))
|
||||
| ((hiddenGroupCallTooltips & int(Tooltip::Microphone))
|
||||
? Tooltip::Microphone
|
||||
: Tooltip(0));
|
||||
}();
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -719,7 +769,7 @@ void Settings::resetOnLastLogout() {
|
||||
_soundNotify = true;
|
||||
_desktopNotify = true;
|
||||
_flashBounceNotify = true;
|
||||
_notifyView = dbinvShowPreview;
|
||||
_notifyView = NotifyView::ShowPreview;
|
||||
//_nativeNotifications = std::nullopt;
|
||||
//_notificationsCount = 3;
|
||||
//_notificationsCorner = ScreenCorner::BottomRight;
|
||||
@@ -741,6 +791,8 @@ void Settings::resetOnLastLogout() {
|
||||
_groupCallPushToTalkShortcut = QByteArray();
|
||||
_groupCallPushToTalkDelay = 20;
|
||||
|
||||
_groupCallNoiseSuppression = true;
|
||||
|
||||
//_themesAccentColors = Window::Theme::AccentColors();
|
||||
|
||||
_lastSeenWarningSeen = false;
|
||||
@@ -771,10 +823,13 @@ void Settings::resetOnLastLogout() {
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
_emojiVariants.clear();
|
||||
|
||||
_workMode = WorkMode::WindowAndTray;
|
||||
}
|
||||
|
||||
bool Settings::ThirdColumnByDefault() {
|
||||
|
||||
@@ -7,9 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/core_settings_proxy.h"
|
||||
#include "window/themes/window_themes_embedded.h"
|
||||
#include "ui/chat/attach/attach_send_files_way.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "base/flags.h"
|
||||
#include "emoji.h"
|
||||
|
||||
enum class RectPart;
|
||||
@@ -26,6 +28,10 @@ namespace Webrtc {
|
||||
enum class Backend;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Calls::Group {
|
||||
enum class StickedTooltip;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct WindowPosition {
|
||||
@@ -48,6 +54,16 @@ public:
|
||||
BottomRight = 2,
|
||||
BottomLeft = 3,
|
||||
};
|
||||
enum class NotifyView {
|
||||
ShowPreview = 0,
|
||||
ShowName = 1,
|
||||
ShowNothing = 2,
|
||||
};
|
||||
enum class WorkMode {
|
||||
WindowAndTray = 0,
|
||||
TrayOnly = 1,
|
||||
WindowOnly = 2,
|
||||
};
|
||||
|
||||
static constexpr auto kDefaultVolume = 0.9;
|
||||
|
||||
@@ -57,6 +73,10 @@ public:
|
||||
return _saveDelayed.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] SettingsProxy &proxy() {
|
||||
return _proxy;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool IsLeftCorner(ScreenCorner corner) {
|
||||
return (corner == ScreenCorner::TopLeft)
|
||||
|| (corner == ScreenCorner::BottomLeft);
|
||||
@@ -69,9 +89,14 @@ public:
|
||||
[[nodiscard]] QByteArray serialize() const;
|
||||
void addFromSerialized(const QByteArray &serialized);
|
||||
|
||||
[[nodiscard]] bool chatWide() const;
|
||||
[[nodiscard]] bool adaptiveForWide() const {
|
||||
return _adaptiveForWide;
|
||||
return _adaptiveForWide.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideValue() const {
|
||||
return _adaptiveForWide.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideChanges() const {
|
||||
return _adaptiveForWide.changes();
|
||||
}
|
||||
void setAdaptiveForWide(bool value) {
|
||||
_adaptiveForWide = value;
|
||||
@@ -145,10 +170,10 @@ public:
|
||||
void setFlashBounceNotify(bool value) {
|
||||
_flashBounceNotify = value;
|
||||
}
|
||||
[[nodiscard]] DBINotifyView notifyView() const {
|
||||
[[nodiscard]] NotifyView notifyView() const {
|
||||
return _notifyView;
|
||||
}
|
||||
void setNotifyView(DBINotifyView value) {
|
||||
void setNotifyView(NotifyView value) {
|
||||
_notifyView = value;
|
||||
}
|
||||
[[nodiscard]] bool nativeNotifications() const {
|
||||
@@ -265,6 +290,12 @@ public:
|
||||
void setGroupCallPushToTalkDelay(crl::time delay) {
|
||||
_groupCallPushToTalkDelay = delay;
|
||||
}
|
||||
[[nodiscard]] bool groupCallNoiseSuppression() const {
|
||||
return _groupCallNoiseSuppression;
|
||||
}
|
||||
void setGroupCallNoiseSuppression(bool value) {
|
||||
_groupCallNoiseSuppression = value;
|
||||
}
|
||||
[[nodiscard]] Window::Theme::AccentColors &themesAccentColors() {
|
||||
return _themesAccentColors;
|
||||
}
|
||||
@@ -513,6 +544,18 @@ public:
|
||||
void setWindowPosition(const WindowPosition &position) {
|
||||
_windowPosition = position;
|
||||
}
|
||||
void setWorkMode(WorkMode value) {
|
||||
_workMode = value;
|
||||
}
|
||||
[[nodiscard]] WorkMode workMode() const {
|
||||
return _workMode.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<WorkMode> workModeValue() const {
|
||||
return _workMode.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<WorkMode> workModeChanges() const {
|
||||
return _workMode.changes();
|
||||
}
|
||||
|
||||
struct RecentEmoji {
|
||||
EmojiPtr emoji = nullptr;
|
||||
@@ -539,6 +582,13 @@ public:
|
||||
_disableOpenGL = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] base::flags<Calls::Group::StickedTooltip> hiddenGroupCallTooltips() const {
|
||||
return _hiddenGroupCallTooltips;
|
||||
}
|
||||
void setHiddenGroupCallTooltip(Calls::Group::StickedTooltip value) {
|
||||
_hiddenGroupCallTooltips |= value;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
@@ -567,7 +617,9 @@ private:
|
||||
ushort rating = 0;
|
||||
};
|
||||
|
||||
bool _adaptiveForWide = true;
|
||||
SettingsProxy _proxy;
|
||||
|
||||
rpl::variable<bool> _adaptiveForWide = true;
|
||||
bool _moderateModeEnabled = false;
|
||||
rpl::variable<float64> _songVolume = kDefaultVolume;
|
||||
rpl::variable<float64> _videoVolume = kDefaultVolume;
|
||||
@@ -578,7 +630,7 @@ private:
|
||||
bool _soundNotify = true;
|
||||
bool _desktopNotify = true;
|
||||
bool _flashBounceNotify = true;
|
||||
DBINotifyView _notifyView = dbinvShowPreview;
|
||||
NotifyView _notifyView = NotifyView::ShowPreview;
|
||||
std::optional<bool> _nativeNotifications;
|
||||
int _notificationsCount = 3;
|
||||
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
|
||||
@@ -594,6 +646,7 @@ private:
|
||||
bool _callAudioDuckingEnabled = true;
|
||||
bool _disableCalls = false;
|
||||
bool _groupCallPushToTalk = false;
|
||||
bool _groupCallNoiseSuppression = true;
|
||||
QByteArray _groupCallPushToTalkShortcut;
|
||||
crl::time _groupCallPushToTalkDelay = 20;
|
||||
Window::Theme::AccentColors _themesAccentColors;
|
||||
@@ -632,6 +685,8 @@ private:
|
||||
rpl::variable<bool> _systemDarkModeEnabled = false;
|
||||
WindowPosition _windowPosition; // per-window
|
||||
bool _disableOpenGL = false;
|
||||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
238
Telegram/SourceFiles/core/core_settings_proxy.cpp
Normal file
238
Telegram/SourceFiles/core/core_settings_proxy.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "core/core_settings_proxy.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "storage/serialize_common.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] qint32 ProxySettingsToInt(MTP::ProxyData::Settings settings) {
|
||||
switch(settings) {
|
||||
case MTP::ProxyData::Settings::System: return 0;
|
||||
case MTP::ProxyData::Settings::Enabled: return 1;
|
||||
case MTP::ProxyData::Settings::Disabled: return 2;
|
||||
}
|
||||
Unexpected("Bad type in ProxySettingsToInt");
|
||||
}
|
||||
|
||||
[[nodiscard]] MTP::ProxyData::Settings IntToProxySettings(qint32 value) {
|
||||
switch(value) {
|
||||
case 0: return MTP::ProxyData::Settings::System;
|
||||
case 1: return MTP::ProxyData::Settings::Enabled;
|
||||
case 2: return MTP::ProxyData::Settings::Disabled;
|
||||
}
|
||||
Unexpected("Bad type in IntToProxySettings");
|
||||
}
|
||||
|
||||
[[nodiscard]] MTP::ProxyData DeserializeProxyData(const QByteArray &data) {
|
||||
QDataStream stream(data);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
qint32 proxyType, port;
|
||||
MTP::ProxyData proxy;
|
||||
stream
|
||||
>> proxyType
|
||||
>> proxy.host
|
||||
>> port
|
||||
>> proxy.user
|
||||
>> proxy.password;
|
||||
proxy.port = port;
|
||||
proxy.type = [&] {
|
||||
switch(proxyType) {
|
||||
case 0: return MTP::ProxyData::Type::None;
|
||||
case 1: return MTP::ProxyData::Type::Socks5;
|
||||
case 2: return MTP::ProxyData::Type::Http;
|
||||
case 3: return MTP::ProxyData::Type::Mtproto;
|
||||
}
|
||||
Unexpected("Bad type in DeserializeProxyData");
|
||||
}();
|
||||
return proxy;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray SerializeProxyData(const MTP::ProxyData &proxy) {
|
||||
auto result = QByteArray();
|
||||
const auto size = 1 * sizeof(qint32)
|
||||
+ Serialize::stringSize(proxy.host)
|
||||
+ 1 * sizeof(qint32)
|
||||
+ Serialize::stringSize(proxy.user)
|
||||
+ Serialize::stringSize(proxy.password);
|
||||
|
||||
result.reserve(size);
|
||||
{
|
||||
const auto proxyType = [&] {
|
||||
switch(proxy.type) {
|
||||
case MTP::ProxyData::Type::None: return 0;
|
||||
case MTP::ProxyData::Type::Socks5: return 1;
|
||||
case MTP::ProxyData::Type::Http: return 2;
|
||||
case MTP::ProxyData::Type::Mtproto: return 3;
|
||||
}
|
||||
Unexpected("Bad type in SerializeProxyData");
|
||||
}();
|
||||
|
||||
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream
|
||||
<< qint32(proxyType)
|
||||
<< proxy.host
|
||||
<< qint32(proxy.port)
|
||||
<< proxy.user
|
||||
<< proxy.password;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsProxy::SettingsProxy()
|
||||
: _tryIPv6(!Platform::IsWindows()) {
|
||||
}
|
||||
|
||||
QByteArray SettingsProxy::serialize() const {
|
||||
auto result = QByteArray();
|
||||
auto stream = QDataStream(&result, QIODevice::WriteOnly);
|
||||
|
||||
const auto serializedSelected = SerializeProxyData(_selected);
|
||||
const auto serializedList = ranges::views::all(
|
||||
_list
|
||||
) | ranges::views::transform(SerializeProxyData) | ranges::to_vector;
|
||||
|
||||
const auto size = 3 * sizeof(qint32)
|
||||
+ Serialize::bytearraySize(serializedSelected)
|
||||
+ 1 * sizeof(qint32)
|
||||
+ ranges::accumulate(
|
||||
serializedList,
|
||||
0,
|
||||
ranges::plus(),
|
||||
&Serialize::bytearraySize);
|
||||
result.reserve(size);
|
||||
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream
|
||||
<< qint32(_tryIPv6 ? 1 : 0)
|
||||
<< qint32(_useProxyForCalls ? 1 : 0)
|
||||
<< ProxySettingsToInt(_settings)
|
||||
<< serializedSelected
|
||||
<< qint32(_list.size());
|
||||
for (const auto &i : serializedList) {
|
||||
stream << i;
|
||||
}
|
||||
|
||||
stream.device()->close();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SettingsProxy::setFromSerialized(const QByteArray &serialized) {
|
||||
if (serialized.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto stream = QDataStream(serialized);
|
||||
|
||||
auto tryIPv6 = qint32(_tryIPv6 ? 1 : 0);
|
||||
auto useProxyForCalls = qint32(_useProxyForCalls ? 1 : 0);
|
||||
auto settings = ProxySettingsToInt(_settings);
|
||||
auto listCount = qint32(_list.size());
|
||||
auto selectedProxy = QByteArray();
|
||||
|
||||
if (!stream.atEnd()) {
|
||||
stream
|
||||
>> tryIPv6
|
||||
>> useProxyForCalls
|
||||
>> settings
|
||||
>> selectedProxy
|
||||
>> listCount;
|
||||
if (stream.status() == QDataStream::Ok) {
|
||||
for (auto i = 0; i != listCount; ++i) {
|
||||
QByteArray data;
|
||||
stream >> data;
|
||||
_list.push_back(DeserializeProxyData(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::SettingsProxy::setFromSerialized()"));
|
||||
return false;
|
||||
}
|
||||
|
||||
_tryIPv6 = (tryIPv6 == 1);
|
||||
_useProxyForCalls = (useProxyForCalls == 1);
|
||||
_settings = IntToProxySettings(settings);
|
||||
_selected = DeserializeProxyData(selectedProxy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsProxy::isEnabled() const {
|
||||
return _settings == MTP::ProxyData::Settings::Enabled;
|
||||
}
|
||||
|
||||
bool SettingsProxy::isSystem() const {
|
||||
return _settings == MTP::ProxyData::Settings::System;
|
||||
}
|
||||
|
||||
bool SettingsProxy::isDisabled() const {
|
||||
return _settings == MTP::ProxyData::Settings::Disabled;
|
||||
}
|
||||
|
||||
bool SettingsProxy::tryIPv6() const {
|
||||
return _tryIPv6;
|
||||
}
|
||||
|
||||
void SettingsProxy::setTryIPv6(bool value) {
|
||||
_tryIPv6 = value;
|
||||
}
|
||||
|
||||
bool SettingsProxy::useProxyForCalls() const {
|
||||
return _useProxyForCalls;
|
||||
}
|
||||
|
||||
void SettingsProxy::setUseProxyForCalls(bool value) {
|
||||
_useProxyForCalls = value;
|
||||
}
|
||||
|
||||
MTP::ProxyData::Settings SettingsProxy::settings() const {
|
||||
return _settings;
|
||||
}
|
||||
|
||||
void SettingsProxy::setSettings(MTP::ProxyData::Settings value) {
|
||||
_settings = value;
|
||||
}
|
||||
|
||||
MTP::ProxyData SettingsProxy::selected() const {
|
||||
return _selected;
|
||||
}
|
||||
|
||||
void SettingsProxy::setSelected(MTP::ProxyData value) {
|
||||
_selected = value;
|
||||
}
|
||||
|
||||
const std::vector<MTP::ProxyData> &SettingsProxy::list() const {
|
||||
return _list;
|
||||
}
|
||||
|
||||
std::vector<MTP::ProxyData> &SettingsProxy::list() {
|
||||
return _list;
|
||||
}
|
||||
|
||||
rpl::producer<> SettingsProxy::connectionTypeValue() const {
|
||||
return _connectionTypeChanges.events_starting_with({});
|
||||
}
|
||||
|
||||
rpl::producer<> SettingsProxy::connectionTypeChanges() const {
|
||||
return _connectionTypeChanges.events();
|
||||
}
|
||||
|
||||
void SettingsProxy::connectionTypeChangesNotify() {
|
||||
_connectionTypeChanges.fire({});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
56
Telegram/SourceFiles/core/core_settings_proxy.h
Normal file
56
Telegram/SourceFiles/core/core_settings_proxy.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class SettingsProxy final {
|
||||
public:
|
||||
SettingsProxy();
|
||||
|
||||
[[nodiscard]] bool isEnabled() const;
|
||||
[[nodiscard]] bool isSystem() const;
|
||||
[[nodiscard]] bool isDisabled() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<> connectionTypeChanges() const;
|
||||
[[nodiscard]] rpl::producer<> connectionTypeValue() const;
|
||||
void connectionTypeChangesNotify();
|
||||
|
||||
[[nodiscard]] bool tryIPv6() const;
|
||||
void setTryIPv6(bool value);
|
||||
|
||||
[[nodiscard]] bool useProxyForCalls() const;
|
||||
void setUseProxyForCalls(bool value);
|
||||
|
||||
[[nodiscard]] MTP::ProxyData::Settings settings() const;
|
||||
void setSettings(MTP::ProxyData::Settings value);
|
||||
|
||||
[[nodiscard]] MTP::ProxyData selected() const;
|
||||
void setSelected(MTP::ProxyData value);
|
||||
|
||||
[[nodiscard]] const std::vector<MTP::ProxyData> &list() const;
|
||||
[[nodiscard]] std::vector<MTP::ProxyData> &list();
|
||||
|
||||
[[nodiscard]] QByteArray serialize() const;
|
||||
bool setFromSerialized(const QByteArray &serialized);
|
||||
|
||||
private:
|
||||
bool _tryIPv6 = false;
|
||||
bool _useProxyForCalls = false;
|
||||
MTP::ProxyData::Settings _settings = MTP::ProxyData::Settings::System;
|
||||
MTP::ProxyData _selected;
|
||||
std::vector<MTP::ProxyData> _list;
|
||||
|
||||
rpl::event_stream<> _connectionTypeChanges;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/launcher.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "window/main_window.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/zlib_help.h"
|
||||
@@ -103,6 +104,8 @@ PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(paren
|
||||
p.setColor(QPalette::Text, QColor(0, 0, 0));
|
||||
setPalette(p);
|
||||
|
||||
setStyleSheet("QLineEdit { background-color: white; }");
|
||||
|
||||
QLineEdit::setTextMargins(0, 0, 0, 0);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
if (password) {
|
||||
@@ -182,7 +185,7 @@ NotStartedWindow::NotStartedWindow()
|
||||
|
||||
_log.setPlainText(Logs::full());
|
||||
|
||||
connect(&_close, SIGNAL(clicked()), this, SLOT(close()));
|
||||
connect(&_close, &QPushButton::clicked, [=] { close(); });
|
||||
_close.setText(qsl("CLOSE"));
|
||||
|
||||
QRect scr(QApplication::primaryScreen()->availableGeometry());
|
||||
@@ -227,7 +230,6 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
const QByteArray &crashdump,
|
||||
Fn<void()> launch)
|
||||
: _dumpraw(crashdump)
|
||||
, _port(kDefaultProxyPort)
|
||||
, _label(this)
|
||||
, _pleaseSendReport(this)
|
||||
, _yourReportName(this)
|
||||
@@ -252,7 +254,11 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
, _launch(std::move(launch)) {
|
||||
excludeReportUsername();
|
||||
|
||||
if (!cInstallBetaVersion() && !cAlphaVersion()) { // currently accept crash reports only from testers
|
||||
if (!cInstallBetaVersion() && !cAlphaVersion()) {
|
||||
// Currently accept crash reports only from testers.
|
||||
_sendingState = SendingNoReport;
|
||||
} else if (Core::OpenGLLastCheckFailed()) {
|
||||
// Nothing we can do right now with graphics driver crashes in GL.
|
||||
_sendingState = SendingNoReport;
|
||||
}
|
||||
if (_sendingState != SendingNoReport) {
|
||||
@@ -312,7 +318,10 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
}
|
||||
|
||||
_networkSettings.setText(qsl("NETWORK SETTINGS"));
|
||||
connect(&_networkSettings, SIGNAL(clicked()), this, SLOT(onNetworkSettings()));
|
||||
connect(
|
||||
&_networkSettings,
|
||||
&QPushButton::clicked,
|
||||
[=] { networkSettings(); });
|
||||
|
||||
if (_sendingState == SendingNoReport) {
|
||||
_label.setText(qsl("Last time Telegram Desktop was not closed properly."));
|
||||
@@ -322,24 +331,53 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
|
||||
if (_updaterData) {
|
||||
_updaterData->check.setText(qsl("TRY AGAIN"));
|
||||
connect(&_updaterData->check, SIGNAL(clicked()), this, SLOT(onUpdateRetry()));
|
||||
connect(
|
||||
&_updaterData->check,
|
||||
&QPushButton::clicked,
|
||||
[=] { updateRetry(); });
|
||||
_updaterData->skip.setText(qsl("SKIP"));
|
||||
connect(&_updaterData->skip, SIGNAL(clicked()), this, SLOT(onUpdateSkip()));
|
||||
connect(
|
||||
&_updaterData->skip,
|
||||
&QPushButton::clicked,
|
||||
[=] { updateSkip(); });
|
||||
|
||||
Core::UpdateChecker checker;
|
||||
using Progress = Core::UpdateChecker::Progress;
|
||||
checker.checking(
|
||||
) | rpl::start_with_next([=] { onUpdateChecking(); }, _lifetime);
|
||||
) | rpl::start_with_next([=] {
|
||||
Assert(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingCheck);
|
||||
}, _lifetime);
|
||||
|
||||
checker.isLatest(
|
||||
) | rpl::start_with_next([=] { onUpdateLatest(); }, _lifetime);
|
||||
) | rpl::start_with_next([=] {
|
||||
Assert(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingLatest);
|
||||
}, _lifetime);
|
||||
|
||||
checker.progress(
|
||||
) | rpl::start_with_next([=](const Progress &result) {
|
||||
onUpdateDownloading(result.already, result.size);
|
||||
Assert(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingDownload);
|
||||
setDownloadProgress(result.already, result.size);
|
||||
}, _lifetime);
|
||||
|
||||
checker.failed(
|
||||
) | rpl::start_with_next([=] { onUpdateFailed(); }, _lifetime);
|
||||
) | rpl::start_with_next([=] {
|
||||
Assert(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingFail);
|
||||
}, _lifetime);
|
||||
|
||||
checker.ready(
|
||||
) | rpl::start_with_next([=] { onUpdateReady(); }, _lifetime);
|
||||
) | rpl::start_with_next([=] {
|
||||
Assert(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingReady);
|
||||
}, _lifetime);
|
||||
|
||||
switch (checker.state()) {
|
||||
case Core::UpdateChecker::State::Download:
|
||||
@@ -373,19 +411,24 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
_report.setPlainText(_reportTextNoUsername);
|
||||
|
||||
_showReport.setText(qsl("VIEW REPORT"));
|
||||
connect(&_showReport, SIGNAL(clicked()), this, SLOT(onViewReport()));
|
||||
connect(&_showReport, &QPushButton::clicked, [=] {
|
||||
_reportShown = !_reportShown;
|
||||
updateControls();
|
||||
});
|
||||
_saveReport.setText(qsl("SAVE TO FILE"));
|
||||
connect(&_saveReport, SIGNAL(clicked()), this, SLOT(onSaveReport()));
|
||||
connect(&_saveReport, &QPushButton::clicked, [=] { saveReport(); });
|
||||
_getApp.setText(qsl("GET THE LATEST OFFICIAL VERSION OF TELEGRAM DESKTOP"));
|
||||
connect(&_getApp, SIGNAL(clicked()), this, SLOT(onGetApp()));
|
||||
connect(&_getApp, &QPushButton::clicked, [=] {
|
||||
QDesktopServices::openUrl(qsl("https://desktop.telegram.org"));
|
||||
});
|
||||
|
||||
_send.setText(qsl("SEND CRASH REPORT"));
|
||||
connect(&_send, SIGNAL(clicked()), this, SLOT(onSendReport()));
|
||||
connect(&_send, &QPushButton::clicked, [=] { sendReport(); });
|
||||
|
||||
_sendSkip.setText(qsl("SKIP"));
|
||||
connect(&_sendSkip, SIGNAL(clicked()), this, SLOT(onContinue()));
|
||||
connect(&_sendSkip, &QPushButton::clicked, [=] { processContinue(); });
|
||||
_continue.setText(qsl("CONTINUE"));
|
||||
connect(&_continue, SIGNAL(clicked()), this, SLOT(onContinue()));
|
||||
connect(&_continue, &QPushButton::clicked, [=] { processContinue(); });
|
||||
|
||||
QRect scr(QApplication::primaryScreen()->availableGeometry());
|
||||
move(scr.x() + (scr.width() / 6), scr.y() + (scr.height() / 6));
|
||||
@@ -393,12 +436,7 @@ LastCrashedWindow::LastCrashedWindow(
|
||||
show();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onViewReport() {
|
||||
_reportShown = !_reportShown;
|
||||
updateControls();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onSaveReport() {
|
||||
void LastCrashedWindow::saveReport() {
|
||||
QString to = QFileDialog::getSaveFileName(0, qsl("Telegram Crash Report"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + qsl("/report.telegramcrash"), qsl("Telegram crash report (*.telegramcrash)"));
|
||||
if (!to.isEmpty()) {
|
||||
QFile file(to);
|
||||
@@ -420,10 +458,6 @@ QByteArray LastCrashedWindow::getCrashReportRaw() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onGetApp() {
|
||||
QDesktopServices::openUrl(qsl("https://desktop.telegram.org"));
|
||||
}
|
||||
|
||||
void LastCrashedWindow::excludeReportUsername() {
|
||||
QString prefix = qstr("Username:");
|
||||
QStringList lines = _reportText.split('\n');
|
||||
@@ -467,7 +501,7 @@ void LastCrashedWindow::addReportFieldPart(const QLatin1String &name, const QLat
|
||||
}
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onSendReport() {
|
||||
void LastCrashedWindow::sendReport() {
|
||||
if (_checkReply) {
|
||||
_checkReply->deleteLater();
|
||||
_checkReply = nullptr;
|
||||
@@ -484,8 +518,14 @@ void LastCrashedWindow::onSendReport() {
|
||||
QString::number(minidumpFileName().isEmpty() ? 0 : 1),
|
||||
CrashReports::PlatformString())));
|
||||
|
||||
connect(_checkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onSendingError(QNetworkReply::NetworkError)));
|
||||
connect(_checkReply, SIGNAL(finished()), this, SLOT(onCheckingFinished()));
|
||||
connect(
|
||||
_checkReply,
|
||||
&QNetworkReply::errorOccurred,
|
||||
[=](QNetworkReply::NetworkError code) { sendingError(code); });
|
||||
connect(
|
||||
_checkReply,
|
||||
&QNetworkReply::finished,
|
||||
[=] { checkingFinished(); });
|
||||
|
||||
_pleaseSendReport.setText(qsl("Sending crash report..."));
|
||||
_sendingState = SendingProgress;
|
||||
@@ -502,7 +542,7 @@ QString LastCrashedWindow::minidumpFileName() {
|
||||
return QString();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onCheckingFinished() {
|
||||
void LastCrashedWindow::checkingFinished() {
|
||||
if (!_checkReply || _sendReply) return;
|
||||
|
||||
QByteArray result = _checkReply->readAll().trimmed();
|
||||
@@ -574,9 +614,18 @@ void LastCrashedWindow::onCheckingFinished() {
|
||||
_sendReply = _sendManager.post(QNetworkRequest(qsl("https://tdesktop.com/crash.php?act=report")), multipart);
|
||||
multipart->setParent(_sendReply);
|
||||
|
||||
connect(_sendReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onSendingError(QNetworkReply::NetworkError)));
|
||||
connect(_sendReply, SIGNAL(finished()), this, SLOT(onSendingFinished()));
|
||||
connect(_sendReply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onSendingProgress(qint64,qint64)));
|
||||
connect(
|
||||
_sendReply,
|
||||
&QNetworkReply::errorOccurred,
|
||||
[=](QNetworkReply::NetworkError code) { sendingError(code); });
|
||||
connect(
|
||||
_sendReply,
|
||||
&QNetworkReply::finished,
|
||||
[=] { sendingFinished(); });
|
||||
connect(
|
||||
_sendReply,
|
||||
&QNetworkReply::uploadProgress,
|
||||
[=](qint64 sent, qint64 total) { sendingProgress(sent, total); });
|
||||
|
||||
updateControls();
|
||||
}
|
||||
@@ -804,41 +853,23 @@ void LastCrashedWindow::updateControls() {
|
||||
}
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onNetworkSettings() {
|
||||
void LastCrashedWindow::networkSettings() {
|
||||
const auto &proxy = Core::Sandbox::Instance().sandboxProxy();
|
||||
const auto box = new NetworkSettingsWindow(
|
||||
this,
|
||||
proxy.host,
|
||||
proxy.port ? proxy.port : 80,
|
||||
proxy.port ? proxy.port : kDefaultProxyPort,
|
||||
proxy.user,
|
||||
proxy.password);
|
||||
connect(
|
||||
box,
|
||||
SIGNAL(saved(QString,quint32,QString,QString)),
|
||||
this,
|
||||
SLOT(onNetworkSettingsSaved(QString,quint32,QString,QString)));
|
||||
box->saveRequests(
|
||||
) | rpl::start_with_next([=](MTP::ProxyData &&data) {
|
||||
Assert(data.host.isEmpty() || data.port != 0);
|
||||
_proxyChanges.fire(std::move(data));
|
||||
proxyUpdated();
|
||||
}, _lifetime);
|
||||
box->show();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onNetworkSettingsSaved(
|
||||
QString host,
|
||||
quint32 port,
|
||||
QString username,
|
||||
QString password) {
|
||||
Expects(host.isEmpty() || port != 0);
|
||||
|
||||
auto proxy = MTP::ProxyData();
|
||||
proxy.type = host.isEmpty()
|
||||
? MTP::ProxyData::Type::None
|
||||
: MTP::ProxyData::Type::Http;
|
||||
proxy.host = host;
|
||||
proxy.port = port;
|
||||
proxy.user = username;
|
||||
proxy.password = password;
|
||||
_proxyChanges.fire(std::move(proxy));
|
||||
proxyUpdated();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::proxyUpdated() {
|
||||
if (_updaterData
|
||||
&& ((_updaterData->state == UpdatingCheck)
|
||||
@@ -851,7 +882,7 @@ void LastCrashedWindow::proxyUpdated() {
|
||||
checker.start();
|
||||
} else if (_sendingState == SendingFail
|
||||
|| _sendingState == SendingProgress) {
|
||||
onSendReport();
|
||||
sendReport();
|
||||
}
|
||||
activate();
|
||||
}
|
||||
@@ -869,7 +900,7 @@ void LastCrashedWindow::setUpdatingState(UpdatingState state, bool force) {
|
||||
case UpdatingLatest:
|
||||
_updating.setText(qsl("Latest version is installed."));
|
||||
if (_sendingState == SendingNoReport) {
|
||||
QTimer::singleShot(0, this, SLOT(onContinue()));
|
||||
InvokeQueued(this, [=] { processContinue(); });
|
||||
} else {
|
||||
_sendingState = SendingNone;
|
||||
}
|
||||
@@ -909,7 +940,7 @@ void LastCrashedWindow::setDownloadProgress(qint64 ready, qint64 total) {
|
||||
}
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateRetry() {
|
||||
void LastCrashedWindow::updateRetry() {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
cSetLastUpdateCheck(0);
|
||||
@@ -917,11 +948,11 @@ void LastCrashedWindow::onUpdateRetry() {
|
||||
checker.start();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateSkip() {
|
||||
void LastCrashedWindow::updateSkip() {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
if (_sendingState == SendingNoReport) {
|
||||
onContinue();
|
||||
processContinue();
|
||||
} else {
|
||||
if (_updaterData->state == UpdatingCheck
|
||||
|| _updaterData->state == UpdatingDownload) {
|
||||
@@ -934,42 +965,11 @@ void LastCrashedWindow::onUpdateSkip() {
|
||||
}
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateChecking() {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingCheck);
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateLatest() {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingLatest);
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateDownloading(qint64 ready, qint64 total) {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingDownload);
|
||||
setDownloadProgress(ready, total);
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateReady() {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingReady);
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onUpdateFailed() {
|
||||
Expects(_updaterData != nullptr);
|
||||
|
||||
setUpdatingState(UpdatingFail);
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onContinue() {
|
||||
void LastCrashedWindow::processContinue() {
|
||||
close();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onSendingError(QNetworkReply::NetworkError e) {
|
||||
void LastCrashedWindow::sendingError(QNetworkReply::NetworkError e) {
|
||||
LOG(("Crash report sending error: %1").arg(e));
|
||||
|
||||
_pleaseSendReport.setText(qsl("Sending crash report failed :("));
|
||||
@@ -985,7 +985,7 @@ void LastCrashedWindow::onSendingError(QNetworkReply::NetworkError e) {
|
||||
updateControls();
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onSendingFinished() {
|
||||
void LastCrashedWindow::sendingFinished() {
|
||||
if (_sendReply) {
|
||||
QByteArray result = _sendReply->readAll();
|
||||
LOG(("Crash report sending done, result: %1").arg(QString::fromUtf8(result)));
|
||||
@@ -1000,7 +1000,7 @@ void LastCrashedWindow::onSendingFinished() {
|
||||
}
|
||||
}
|
||||
|
||||
void LastCrashedWindow::onSendingProgress(qint64 uploaded, qint64 total) {
|
||||
void LastCrashedWindow::sendingProgress(qint64 uploaded, qint64 total) {
|
||||
if (_sendingState != SendingProgress && _sendingState != SendingUploading) return;
|
||||
_sendingState = SendingUploading;
|
||||
|
||||
@@ -1098,9 +1098,9 @@ NetworkSettingsWindow::NetworkSettingsWindow(QWidget *parent, QString host, quin
|
||||
_passwordLabel.setText(qsl("Password"));
|
||||
|
||||
_save.setText(qsl("SAVE"));
|
||||
connect(&_save, SIGNAL(clicked()), this, SLOT(onSave()));
|
||||
connect(&_save, &QPushButton::clicked, [=] { save(); });
|
||||
_cancel.setText(qsl("CANCEL"));
|
||||
connect(&_cancel, SIGNAL(clicked()), this, SLOT(close()));
|
||||
connect(&_cancel, &QPushButton::clicked, [=] { close(); });
|
||||
|
||||
_hostInput.setText(host);
|
||||
_portInput.setText(QString::number(port));
|
||||
@@ -1131,7 +1131,7 @@ void NetworkSettingsWindow::resizeEvent(QResizeEvent *e) {
|
||||
_cancel.move(_save.x() - padding - _cancel.width(), _save.y());
|
||||
}
|
||||
|
||||
void NetworkSettingsWindow::onSave() {
|
||||
void NetworkSettingsWindow::save() {
|
||||
QString host = _hostInput.text().trimmed(), port = _portInput.text().trimmed(), username = _usernameInput.text().trimmed(), password = _passwordInput.text().trimmed();
|
||||
if (!port.isEmpty() && !port.toUInt()) {
|
||||
_portInput.setFocus();
|
||||
@@ -1140,7 +1140,15 @@ void NetworkSettingsWindow::onSave() {
|
||||
_portInput.setFocus();
|
||||
return;
|
||||
}
|
||||
saved(host, port.toUInt(), username, password);
|
||||
_saveRequests.fire({
|
||||
.type = host.isEmpty()
|
||||
? MTP::ProxyData::Type::None
|
||||
: MTP::ProxyData::Type::Http,
|
||||
.host = host,
|
||||
.port = port.toUInt(),
|
||||
.user = username,
|
||||
.password = password,
|
||||
});
|
||||
close();
|
||||
}
|
||||
|
||||
@@ -1148,6 +1156,10 @@ void NetworkSettingsWindow::closeEvent(QCloseEvent *e) {
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
rpl::producer<MTP::ProxyData> NetworkSettingsWindow::saveRequests() const {
|
||||
return _saveRequests.events();
|
||||
}
|
||||
|
||||
void NetworkSettingsWindow::updateControls() {
|
||||
_hostInput.updateGeometry();
|
||||
_hostInput.resize(_hostInput.sizeHint());
|
||||
|
||||
@@ -92,7 +92,6 @@ private:
|
||||
};
|
||||
|
||||
class LastCrashedWindow : public PreLaunchWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LastCrashedWindow(
|
||||
@@ -106,29 +105,19 @@ public:
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void onViewReport();
|
||||
void onSaveReport();
|
||||
void onSendReport();
|
||||
void onGetApp();
|
||||
void saveReport();
|
||||
void sendReport();
|
||||
|
||||
void onNetworkSettings();
|
||||
void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password);
|
||||
void onContinue();
|
||||
void networkSettings();
|
||||
void processContinue();
|
||||
|
||||
void onCheckingFinished();
|
||||
void onSendingError(QNetworkReply::NetworkError e);
|
||||
void onSendingFinished();
|
||||
void onSendingProgress(qint64 uploaded, qint64 total);
|
||||
void checkingFinished();
|
||||
void sendingError(QNetworkReply::NetworkError e);
|
||||
void sendingFinished();
|
||||
void sendingProgress(qint64 uploaded, qint64 total);
|
||||
|
||||
void onUpdateRetry();
|
||||
void onUpdateSkip();
|
||||
|
||||
void onUpdateChecking();
|
||||
void onUpdateLatest();
|
||||
void onUpdateDownloading(qint64 ready, qint64 total);
|
||||
void onUpdateReady();
|
||||
void onUpdateFailed();
|
||||
void updateRetry();
|
||||
void updateSkip();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
@@ -146,9 +135,6 @@ private:
|
||||
|
||||
QByteArray _dumpraw;
|
||||
|
||||
QString _host, _username, _password;
|
||||
quint32 _port;
|
||||
|
||||
PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
|
||||
PreLaunchLog _report;
|
||||
PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
|
||||
@@ -175,8 +161,6 @@ private:
|
||||
SendingState _sendingState;
|
||||
|
||||
PreLaunchLabel _updating;
|
||||
qint64 _sendingProgress = 0;
|
||||
qint64 _sendingTotal = 0;
|
||||
|
||||
QNetworkAccessManager _sendManager;
|
||||
QNetworkReply *_checkReply = nullptr;
|
||||
@@ -209,16 +193,12 @@ private:
|
||||
};
|
||||
|
||||
class NetworkSettingsWindow : public PreLaunchWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password);
|
||||
|
||||
Q_SIGNALS:
|
||||
void saved(QString host, quint32 port, QString username, QString password);
|
||||
|
||||
public Q_SLOTS:
|
||||
void onSave();
|
||||
[[nodiscard]] rpl::producer<MTP::ProxyData> saveRequests() const;
|
||||
void save();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e);
|
||||
@@ -233,4 +213,6 @@ private:
|
||||
|
||||
QWidget *_parent;
|
||||
|
||||
rpl::event_stream<MTP::ProxyData> _saveRequests;
|
||||
|
||||
};
|
||||
|
||||
@@ -12,13 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "ui/main_queue_processor.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "base/concurrent_timer.h"
|
||||
|
||||
//#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
@@ -100,6 +99,9 @@ void ComputeDebugMode() {
|
||||
if (cDebugMode()) {
|
||||
Logs::SetDebugEnabled(true);
|
||||
}
|
||||
if (Logs::DebugEnabled()) {
|
||||
QLoggingCategory::setFilterRules("qt.qpa.gl.debug=true");
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeExternalUpdater() {
|
||||
@@ -331,7 +333,6 @@ int Launcher::exec() {
|
||||
|
||||
// Must be started before Sandbox is created.
|
||||
Platform::start();
|
||||
Ui::DisableCustomScaling();
|
||||
|
||||
auto result = executeApplication();
|
||||
|
||||
@@ -527,7 +528,6 @@ void Launcher::processArguments() {
|
||||
int Launcher::executeApplication() {
|
||||
FilteredCommandLineArguments arguments(_argc, _argv);
|
||||
Sandbox sandbox(this, arguments.count(), arguments.values());
|
||||
//QLoggingCategory::setFilterRules("qt.qpa.gl.debug=true");
|
||||
Ui::MainQueueProcessor processor;
|
||||
base::ConcurrentTimerEnvironment environment;
|
||||
return sandbox.start();
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
int exec();
|
||||
virtual int exec();
|
||||
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const;
|
||||
|
||||
@@ -68,7 +68,7 @@ bool ShowStickerSet(
|
||||
return false;
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Box<StickerSetBox>(
|
||||
controller->show(Box<StickerSetBox>(
|
||||
controller,
|
||||
MTP_inputStickerSetShortName(MTP_string(match->captured(1)))));
|
||||
return true;
|
||||
@@ -84,6 +84,7 @@ bool ShowTheme(
|
||||
const auto fromMessageId = context.value<ClickHandlerContext>().itemId;
|
||||
Core::App().hideMediaView();
|
||||
controller->session().data().cloudThemes().resolve(
|
||||
&controller->window(),
|
||||
match->captured(1),
|
||||
fromMessageId);
|
||||
return true;
|
||||
@@ -231,9 +232,15 @@ bool ShowWallPaper(
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
if (!params.value("gradient").isEmpty()) {
|
||||
Ui::show(Box<InformBox>(
|
||||
tr::lng_background_gradient_unsupported(tr::now)));
|
||||
return false;
|
||||
}
|
||||
const auto color = params.value("color");
|
||||
return BackgroundPreviewBox::Start(
|
||||
controller,
|
||||
params.value(qsl("slug")),
|
||||
(color.isEmpty() ? params.value(qsl("slug")) : color),
|
||||
params);
|
||||
}
|
||||
|
||||
@@ -362,7 +369,7 @@ bool ResolveSettings(
|
||||
}
|
||||
if (section == qstr("devices")) {
|
||||
controller->session().api().authorizations().reload();
|
||||
Ui::show(Box<SessionsBox>(&controller->session()));
|
||||
controller->show(Box<SessionsBox>(&controller->session()));
|
||||
return true;
|
||||
} else if (section == qstr("language")) {
|
||||
ShowLanguagesBox();
|
||||
@@ -395,12 +402,12 @@ bool HandleUnknown(
|
||||
Core::UpdateApplication();
|
||||
close();
|
||||
};
|
||||
Ui::show(Box<ConfirmBox>(
|
||||
controller->show(Box<ConfirmBox>(
|
||||
text,
|
||||
tr::lng_menu_update(tr::now),
|
||||
callback));
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(text));
|
||||
controller->show(Box<InformBox>(text));
|
||||
}
|
||||
});
|
||||
controller->session().api().requestDeepLinkInfo(request, callback);
|
||||
@@ -431,9 +438,7 @@ bool OpenMediaTimestamp(
|
||||
documentId,
|
||||
time * crl::time(1000));
|
||||
if (document->isVideoFile()) {
|
||||
Core::App().showDocument(
|
||||
document,
|
||||
session->data().message(itemId));
|
||||
controller->openDocument(document, itemId, true);
|
||||
} else if (document->isSong() || document->isVoiceMessage()) {
|
||||
Media::Player::instance()->play({ document, itemId });
|
||||
}
|
||||
@@ -578,9 +583,16 @@ QString TryConvertUrlToLocal(QString url) {
|
||||
return qsl("tg://socks?") + socksMatch->captured(1);
|
||||
} else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), query, matchOptions)) {
|
||||
return qsl("tg://proxy?") + proxyMatch->captured(1);
|
||||
} else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-]+)(\\?(.+)?)?$"), query, matchOptions)) {
|
||||
} else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-\\~]+)(\\?(.+)?)?$"), query, matchOptions)) {
|
||||
const auto params = bgMatch->captured(3);
|
||||
return qsl("tg://bg?slug=") + bgMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params);
|
||||
const auto bg = bgMatch->captured(1);
|
||||
const auto type = regex_match(qsl("^[a-fA-F0-9]{6}^"), bg)
|
||||
? "color"
|
||||
: (regex_match(qsl("^[a-fA-F0-9]{6}\\-[a-fA-F0-9]{6}$"), bg)
|
||||
|| regex_match(qsl("^[a-fA-F0-9]{6}(\\~[a-fA-F0-9]{6}){1,3}$"), bg))
|
||||
? "gradient"
|
||||
: "slug";
|
||||
return qsl("tg://bg?") + type + '=' + bg + (params.isEmpty() ? QString() : '&' + params);
|
||||
} else if (auto postMatch = regex_match(qsl("^c/(\\-?\\d+)/(\\d+)(/?\\?|/?$)"), query, matchOptions)) {
|
||||
auto params = query.mid(postMatch->captured(0).size()).toString();
|
||||
return qsl("tg://privatepost?channel=%1&post=%2").arg(postMatch->captured(1), postMatch->captured(2)) + (params.isEmpty() ? QString() : '&' + params);
|
||||
|
||||
@@ -25,8 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtGui/QSessionManager>
|
||||
@@ -313,6 +313,7 @@ void Sandbox::singleInstanceChecked() {
|
||||
Logs::multipleInstances();
|
||||
}
|
||||
|
||||
Ui::DisableCustomScaling();
|
||||
refreshGlobalProxy();
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
new NotStartedWindow();
|
||||
@@ -450,17 +451,17 @@ void Sandbox::checkForQuit() {
|
||||
}
|
||||
|
||||
void Sandbox::refreshGlobalProxy() {
|
||||
const auto proxy = !Global::started()
|
||||
const auto proxy = !Core::IsAppLaunched()
|
||||
? _sandboxProxy
|
||||
: (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)
|
||||
? Global::SelectedProxy()
|
||||
: Core::App().settings().proxy().isEnabled()
|
||||
? Core::App().settings().proxy().selected()
|
||||
: MTP::ProxyData();
|
||||
if (proxy.type == MTP::ProxyData::Type::Socks5
|
||||
|| proxy.type == MTP::ProxyData::Type::Http) {
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
|
||||
} else if (!Global::started()
|
||||
|| Global::ProxySettings() == MTP::ProxyData::Settings::System) {
|
||||
} else if (!Core::IsAppLaunched()
|
||||
|| Core::App().settings().proxy().isSystem()) {
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
} else {
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
@@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/parse_helper.h"
|
||||
#include "facades.h"
|
||||
|
||||
#include <QtWidgets/QShortcut>
|
||||
#include <QtCore/QJsonDocument>
|
||||
@@ -553,8 +552,6 @@ rpl::producer<not_null<Request*>> Requests() {
|
||||
}
|
||||
|
||||
void Start() {
|
||||
Assert(Global::started());
|
||||
|
||||
Data.fill();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "mainwindow.h"
|
||||
#include "facades.h" // Global::ScreenIsLocked.
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
@@ -89,6 +88,10 @@ const auto kBadPrefix = u"http://"_q;
|
||||
return cWorkingDir() + "tdata/opengl_crash_check";
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ANGLEBackendFilePath() {
|
||||
return cWorkingDir() + "tdata/angle_backend";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void UiIntegration::postponeCall(FnMut<void()> &&callable) {
|
||||
@@ -107,20 +110,12 @@ QString UiIntegration::emojiCacheFolder() {
|
||||
return cWorkingDir() + "tdata/emoji";
|
||||
}
|
||||
|
||||
void UiIntegration::openglCheckStart() {
|
||||
auto f = QFile(OpenGLCheckFilePath());
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.write("1", 1);
|
||||
f.close();
|
||||
}
|
||||
QString UiIntegration::openglCheckFilePath() {
|
||||
return OpenGLCheckFilePath();
|
||||
}
|
||||
|
||||
void UiIntegration::openglCheckFinish() {
|
||||
QFile::remove(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
bool UiIntegration::openglLastCheckFailed() {
|
||||
return QFile::exists(OpenGLCheckFilePath());
|
||||
QString UiIntegration::angleBackendFilePath() {
|
||||
return ANGLEBackendFilePath();
|
||||
}
|
||||
|
||||
void UiIntegration::textActionsUpdated() {
|
||||
@@ -134,7 +129,7 @@ void UiIntegration::activationFromTopPanel() {
|
||||
}
|
||||
|
||||
bool UiIntegration::screenIsLocked() {
|
||||
return Global::ScreenIsLocked();
|
||||
return Core::App().screenIsLocked();
|
||||
}
|
||||
|
||||
QString UiIntegration::timeFormat() {
|
||||
@@ -320,4 +315,8 @@ QString UiIntegration::phraseFormattingMonospace() {
|
||||
return tr::lng_menu_formatting_monospace(tr::now);
|
||||
}
|
||||
|
||||
bool OpenGLLastCheckFailed() {
|
||||
return QFile::exists(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -37,10 +37,8 @@ public:
|
||||
void unregisterLeaveSubscription(not_null<QWidget*> widget) override;
|
||||
|
||||
QString emojiCacheFolder() override;
|
||||
|
||||
void openglCheckStart() override;
|
||||
void openglCheckFinish() override;
|
||||
bool openglLastCheckFailed() override;
|
||||
QString openglCheckFilePath() override;
|
||||
QString angleBackendFilePath() override;
|
||||
|
||||
void textActionsUpdated() override;
|
||||
void activationFromTopPanel() override;
|
||||
@@ -75,6 +73,6 @@ public:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool OpenglLastCheckFailed();
|
||||
[[nodiscard]] bool OpenGLLastCheckFailed();
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -121,18 +121,6 @@ void memset_rand(void *data, uint32 len);
|
||||
QString translitRusEng(const QString &rus);
|
||||
QString rusKeyboardLayoutSwitch(const QString &from);
|
||||
|
||||
enum DBINotifyView {
|
||||
dbinvShowPreview = 0,
|
||||
dbinvShowName = 1,
|
||||
dbinvShowNothing = 2,
|
||||
};
|
||||
|
||||
enum DBIWorkMode {
|
||||
dbiwmWindowAndTray = 0,
|
||||
dbiwmTrayOnly = 1,
|
||||
dbiwmWindowOnly = 2,
|
||||
};
|
||||
|
||||
static const int MatrixRowShift = 40000;
|
||||
|
||||
inline int rowscount(int fullCount, int countPerRow) {
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 2007008;
|
||||
constexpr auto AppVersionStr = "2.7.8";
|
||||
constexpr auto AppVersion = 2008005;
|
||||
constexpr auto AppVersionStr = "2.8.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
78
Telegram/SourceFiles/data/data_abstract_sparse_ids.h
Normal file
78
Telegram/SourceFiles/data/data_abstract_sparse_ids.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
template <typename IdsContainer>
|
||||
class AbstractSparseIds {
|
||||
public:
|
||||
using Id = typename IdsContainer::value_type;
|
||||
|
||||
AbstractSparseIds() = default;
|
||||
AbstractSparseIds(
|
||||
const IdsContainer &ids,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _ids(ids)
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> fullCount() const {
|
||||
return _fullCount;
|
||||
}
|
||||
std::optional<int> skippedBefore() const {
|
||||
return _skippedBefore;
|
||||
}
|
||||
std::optional<int> skippedAfter() const {
|
||||
return _skippedAfter;
|
||||
}
|
||||
std::optional<int> indexOf(Id id) const {
|
||||
const auto it = ranges::find(_ids, id);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
int size() const {
|
||||
return _ids.size();
|
||||
}
|
||||
Id operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
std::optional<int> distance(Id a, Id b) const {
|
||||
if (const auto i = indexOf(a)) {
|
||||
if (const auto j = indexOf(b)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<Id> nearest(Id id) const {
|
||||
if (const auto it = ranges::lower_bound(_ids, id); it != _ids.end()) {
|
||||
return *it;
|
||||
} else if (_ids.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return _ids.back();
|
||||
}
|
||||
void reverse() {
|
||||
ranges::reverse(_ids);
|
||||
std::swap(_skippedBefore, _skippedAfter);
|
||||
}
|
||||
|
||||
private:
|
||||
IdsContainer _ids;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
};
|
||||
@@ -17,11 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document_media.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "core/application.h" // Core::App().showTheme.
|
||||
#include "media/view/media_view_open_common.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "apiwrap.h"
|
||||
#include "app.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
@@ -140,6 +138,7 @@ void CloudThemes::applyUpdate(const MTPTheme &theme) {
|
||||
}
|
||||
|
||||
void CloudThemes::resolve(
|
||||
not_null<Window::Controller*> controller,
|
||||
const QString &slug,
|
||||
const FullMsgId &clickFromMessageId) {
|
||||
_session->api().request(_resolveRequestId).cancel();
|
||||
@@ -148,31 +147,35 @@ void CloudThemes::resolve(
|
||||
MTP_inputThemeSlug(MTP_string(slug)),
|
||||
MTP_long(0)
|
||||
)).done([=](const MTPTheme &result) {
|
||||
showPreview(result);
|
||||
showPreview(controller, result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == qstr("THEME_FORMAT_INVALID")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
controller->show(Box<InformBox>(
|
||||
tr::lng_theme_no_desktop(tr::now)));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void CloudThemes::showPreview(const MTPTheme &data) {
|
||||
void CloudThemes::showPreview(
|
||||
not_null<Window::Controller*> controller,
|
||||
const MTPTheme &data) {
|
||||
data.match([&](const MTPDtheme &data) {
|
||||
showPreview(CloudTheme::Parse(_session, data));
|
||||
showPreview(controller, CloudTheme::Parse(_session, data));
|
||||
});
|
||||
}
|
||||
|
||||
void CloudThemes::showPreview(const CloudTheme &cloud) {
|
||||
void CloudThemes::showPreview(
|
||||
not_null<Window::Controller*> controller,
|
||||
const CloudTheme &cloud) {
|
||||
if (const auto documentId = cloud.documentId) {
|
||||
previewFromDocument(cloud);
|
||||
previewFromDocument(controller, cloud);
|
||||
} else if (cloud.createdBy == _session->userId()) {
|
||||
Ui::show(Box(
|
||||
controller->show(Box(
|
||||
Window::Theme::CreateForExistingBox,
|
||||
&App::wnd()->controller(),
|
||||
controller,
|
||||
cloud));
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(
|
||||
controller->show(Box<InformBox>(
|
||||
tr::lng_theme_no_desktop(tr::now)));
|
||||
}
|
||||
}
|
||||
@@ -193,12 +196,19 @@ void CloudThemes::applyFromDocument(const CloudTheme &cloud) {
|
||||
});
|
||||
}
|
||||
|
||||
void CloudThemes::previewFromDocument(const CloudTheme &cloud) {
|
||||
void CloudThemes::previewFromDocument(
|
||||
not_null<Window::Controller*> controller,
|
||||
const CloudTheme &cloud) {
|
||||
const auto sessionController = controller->sessionController();
|
||||
if (!sessionController) {
|
||||
return;
|
||||
}
|
||||
const auto document = _session->data().document(cloud.documentId);
|
||||
loadDocumentAndInvoke(_previewFrom, cloud, document, [=](
|
||||
std::shared_ptr<Data::DocumentMedia> media) {
|
||||
const auto document = media->owner();
|
||||
Core::App().showTheme(document, cloud);
|
||||
using Open = Media::View::OpenRequest;
|
||||
controller->openInMediaView(Open(sessionController, document, cloud));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
@@ -47,9 +51,16 @@ public:
|
||||
|
||||
void applyUpdate(const MTPTheme &theme);
|
||||
|
||||
void resolve(const QString &slug, const FullMsgId &clickFromMessageId);
|
||||
void showPreview(const MTPTheme &data);
|
||||
void showPreview(const CloudTheme &cloud);
|
||||
void resolve(
|
||||
not_null<Window::Controller*> controller,
|
||||
const QString &slug,
|
||||
const FullMsgId &clickFromMessageId);
|
||||
void showPreview(
|
||||
not_null<Window::Controller*> controller,
|
||||
const MTPTheme &data);
|
||||
void showPreview(
|
||||
not_null<Window::Controller*> controller,
|
||||
const CloudTheme &cloud);
|
||||
void applyFromDocument(const CloudTheme &cloud);
|
||||
|
||||
private:
|
||||
@@ -69,7 +80,9 @@ private:
|
||||
[[nodiscard]] bool needReload() const;
|
||||
void scheduleReload();
|
||||
void reloadCurrent();
|
||||
void previewFromDocument(const CloudTheme &cloud);
|
||||
void previewFromDocument(
|
||||
not_null<Window::Controller*> controller,
|
||||
const CloudTheme &cloud);
|
||||
void loadDocumentAndInvoke(
|
||||
LoadingDocument &value,
|
||||
const CloudTheme &cloud,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_document.h"
|
||||
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_document_media.h"
|
||||
@@ -75,59 +76,6 @@ QString JoinStringList(const QStringList &list, const QString &separator) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void LaunchWithWarning(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &name,
|
||||
HistoryItem *item) {
|
||||
const auto isExecutable = Data::IsExecutableName(name);
|
||||
const auto isIpReveal = Data::IsIpRevealingName(name);
|
||||
auto &app = Core::App();
|
||||
const auto warn = [&] {
|
||||
if (item && item->history()->peer->isVerified()) {
|
||||
return false;
|
||||
}
|
||||
return (isExecutable && app.settings().exeLaunchWarning())
|
||||
|| (isIpReveal && app.settings().ipRevealWarning());
|
||||
}();
|
||||
const auto extension = '.' + Data::FileExtension(name);
|
||||
if (Platform::IsWindows() && extension == u"."_q) {
|
||||
// If you launch a file without extension, like "test", in case
|
||||
// there is an executable file with the same name in this folder,
|
||||
// like "test.bat", the executable file will be launched.
|
||||
//
|
||||
// Now we always force an Open With dialog box for such files.
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeShowOpenWith(name);
|
||||
});
|
||||
return;
|
||||
} else if (!warn) {
|
||||
File::Launch(name);
|
||||
return;
|
||||
}
|
||||
const auto callback = [=, &app](bool checked) {
|
||||
if (checked) {
|
||||
if (isExecutable) {
|
||||
app.settings().setExeLaunchWarning(false);
|
||||
} else if (isIpReveal) {
|
||||
app.settings().setIpRevealWarning(false);
|
||||
}
|
||||
app.saveSettingsDelayed();
|
||||
}
|
||||
File::Launch(name);
|
||||
};
|
||||
auto text = isExecutable
|
||||
? tr::lng_launch_exe_warning(
|
||||
lt_extension,
|
||||
rpl::single(Ui::Text::Bold(extension)),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_launch_svg_warning(Ui::Text::WithEntities);
|
||||
Ui::show(Box<ConfirmDontWarnBox>(
|
||||
std::move(text),
|
||||
tr::lng_launch_exe_dont_ask(tr::now),
|
||||
(isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(),
|
||||
callback));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString FileNameUnsafe(
|
||||
@@ -308,160 +256,6 @@ QString DocumentFileNameForSave(
|
||||
dir);
|
||||
}
|
||||
|
||||
DocumentClickHandler::DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context)
|
||||
: FileClickHandler(&document->session(), context)
|
||||
, _document(document) {
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::Open(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data,
|
||||
HistoryItem *context) {
|
||||
if (!data->date) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto media = data->createMediaView();
|
||||
const auto openImageInApp = [&] {
|
||||
if (data->size >= App::kImageSizeLimit) {
|
||||
return false;
|
||||
}
|
||||
const auto &location = data->location(true);
|
||||
if (!location.isEmpty() && location.accessEnable()) {
|
||||
const auto guard = gsl::finally([&] {
|
||||
location.accessDisable();
|
||||
});
|
||||
const auto path = location.name();
|
||||
if (Core::MimeTypeForFile(path).name().startsWith("image/")
|
||||
&& QImageReader(path).canRead()) {
|
||||
Core::App().showDocument(data, context);
|
||||
return true;
|
||||
}
|
||||
} else if (data->mimeString().startsWith("image/")
|
||||
&& !media->bytes().isEmpty()) {
|
||||
auto bytes = media->bytes();
|
||||
auto buffer = QBuffer(&bytes);
|
||||
if (QImageReader(&buffer).canRead()) {
|
||||
Core::App().showDocument(data, context);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto &location = data->location(true);
|
||||
if (data->isTheme() && media->loaded(true)) {
|
||||
Core::App().showDocument(data, context);
|
||||
location.accessDisable();
|
||||
} else if (media->canBePlayed()) {
|
||||
if (data->isAudioFile()
|
||||
|| data->isVoiceMessage()
|
||||
|| data->isVideoMessage()) {
|
||||
const auto msgId = context ? context->fullId() : FullMsgId();
|
||||
Media::Player::instance()->playPause({ data, msgId });
|
||||
} else if (context
|
||||
&& data->isAnimation()
|
||||
&& HistoryView::Gif::CanPlayInline(data)) {
|
||||
data->owner().requestAnimationPlayInline(context);
|
||||
} else {
|
||||
Core::App().showDocument(data, context);
|
||||
}
|
||||
} else {
|
||||
data->saveFromDataSilent();
|
||||
if (!openImageInApp()) {
|
||||
if (!data->filepath(true).isEmpty()) {
|
||||
LaunchWithWarning(&data->session(), location.name(), context);
|
||||
} else if (data->status == FileReady
|
||||
|| data->status == FileDownloadFailed) {
|
||||
DocumentSaveClickHandler::Save(origin, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::onClickImpl() const {
|
||||
Open(context(), document(), getActionItem());
|
||||
}
|
||||
|
||||
void DocumentSaveClickHandler::Save(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data,
|
||||
Mode mode) {
|
||||
if (!data->date) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto savename = QString();
|
||||
if (mode != Mode::ToCacheOrFile || !data->saveToCache()) {
|
||||
if (mode != Mode::ToNewFile && data->saveFromData()) {
|
||||
return;
|
||||
}
|
||||
const auto filepath = data->filepath(true);
|
||||
const auto fileinfo = QFileInfo(
|
||||
);
|
||||
const auto filedir = filepath.isEmpty()
|
||||
? QDir()
|
||||
: fileinfo.dir();
|
||||
const auto filename = filepath.isEmpty()
|
||||
? QString()
|
||||
: fileinfo.fileName();
|
||||
savename = DocumentFileNameForSave(
|
||||
data,
|
||||
(mode == Mode::ToNewFile),
|
||||
filename,
|
||||
filedir);
|
||||
if (savename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
data->save(origin, savename);
|
||||
}
|
||||
|
||||
void DocumentSaveClickHandler::onClickImpl() const {
|
||||
Save(context(), document());
|
||||
}
|
||||
|
||||
void DocumentCancelClickHandler::onClickImpl() const {
|
||||
const auto data = document();
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else if (data->uploading()) {
|
||||
if (const auto item = data->owner().message(context())) {
|
||||
if (const auto m = App::main()) { // multi good
|
||||
if (&m->session() == &data->session()) {
|
||||
m->cancelUploadLayer(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentOpenWithClickHandler::Open(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data) {
|
||||
if (!data->date) {
|
||||
return;
|
||||
}
|
||||
|
||||
data->saveFromDataSilent();
|
||||
const auto path = data->filepath(true);
|
||||
if (!path.isEmpty()) {
|
||||
File::OpenWith(path, QCursor::pos());
|
||||
} else {
|
||||
DocumentSaveClickHandler::Save(
|
||||
origin,
|
||||
data,
|
||||
DocumentSaveClickHandler::Mode::ToFile);
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentOpenWithClickHandler::onClickImpl() const {
|
||||
Open(context(), document());
|
||||
}
|
||||
|
||||
Data::FileOrigin StickerData::setOrigin() const {
|
||||
return set.match([&](const MTPDinputStickerSetID &data) {
|
||||
return Data::FileOrigin(
|
||||
@@ -1634,126 +1428,3 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) {
|
||||
session().local().writeFileLocation(mediaKey(), _location);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
QString FileExtension(const QString &filepath) {
|
||||
const auto reversed = ranges::views::reverse(filepath);
|
||||
const auto last = ranges::find_first_of(reversed, ".\\/");
|
||||
if (last == reversed.end() || *last != '.') {
|
||||
return QString();
|
||||
}
|
||||
return QString(last.base(), last - reversed.begin());
|
||||
}
|
||||
|
||||
bool IsValidMediaFile(const QString &filepath) {
|
||||
static const auto kExtensions = [] {
|
||||
const auto list = qsl("\
|
||||
16svx 2sf 3g2 3gp 8svx aac aaf aif aifc aiff amr amv ape asf ast au aup \
|
||||
avchd avi brstm bwf cam cdda cust dat divx drc dsh dsf dts dtshd dtsma \
|
||||
dvr-ms dwd evo f4a f4b f4p f4v fla flac flr flv gif gifv gsf gsm gym iff \
|
||||
ifo it jam la ly m1v m2p m2ts m2v m4a m4p m4v mcf mid mk3d mka mks mkv mng \
|
||||
mov mp1 mp2 mp3 mp4 minipsf mod mpc mpe mpeg mpg mpv mscz mt2 mus mxf mxl \
|
||||
niff nsf nsv off ofr ofs ogg ogv opus ots pac ps psf psf2 psflib ptb qsf \
|
||||
qt ra raw rka rm rmj rmvb roq s3m shn sib sid smi smp sol spc spx ssf svi \
|
||||
swa swf tak ts tta txm usf vgm vob voc vox vqf wav webm wma wmv wrap wtv \
|
||||
wv xm xml ym yuv").split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
|
||||
return ranges::binary_search(
|
||||
kExtensions,
|
||||
FileExtension(filepath).toLower());
|
||||
}
|
||||
|
||||
bool IsExecutableName(const QString &filepath) {
|
||||
static const auto kExtensions = [] {
|
||||
const auto joined =
|
||||
#ifdef Q_OS_MAC
|
||||
qsl("\
|
||||
applescript action app bin command csh osx workflow terminal url caction \
|
||||
mpkg pkg scpt scptd xhtm webarchive");
|
||||
#elif defined Q_OS_UNIX // Q_OS_MAC
|
||||
qsl("bin csh deb desktop ksh out pet pkg pup rpm run sh shar \
|
||||
slp zsh");
|
||||
#else // Q_OS_MAC || Q_OS_UNIX
|
||||
qsl("\
|
||||
ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \
|
||||
chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \
|
||||
grp hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse key ksh \
|
||||
lnk local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda \
|
||||
mdb mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml \
|
||||
msh2xml mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps \
|
||||
php-s pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 \
|
||||
psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \
|
||||
sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \
|
||||
vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \
|
||||
vtx website ws wsc wsf wsh xbap xll xnk xs");
|
||||
#endif // !Q_OS_MAC && !Q_OS_UNIX
|
||||
const auto list = joined.split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
|
||||
return ranges::binary_search(
|
||||
kExtensions,
|
||||
FileExtension(filepath).toLower());
|
||||
}
|
||||
|
||||
bool IsIpRevealingName(const QString &filepath) {
|
||||
static const auto kExtensions = [] {
|
||||
const auto joined = u"htm html svg"_q;
|
||||
const auto list = joined.split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
static const auto kMimeTypes = [] {
|
||||
const auto joined = u"text/html image/svg+xml"_q;
|
||||
const auto list = joined.split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
|
||||
return ranges::binary_search(
|
||||
kExtensions,
|
||||
FileExtension(filepath).toLower()
|
||||
) || ranges::binary_search(
|
||||
kMimeTypes,
|
||||
QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name()
|
||||
);
|
||||
}
|
||||
|
||||
base::binary_guard ReadImageAsync(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
FnMut<QImage(QImage)> postprocess,
|
||||
FnMut<void(QImage&&)> done) {
|
||||
auto result = base::binary_guard();
|
||||
crl::async([
|
||||
bytes = media->bytes(),
|
||||
path = media->owner()->filepath(),
|
||||
postprocess = std::move(postprocess),
|
||||
guard = result.make_guard(),
|
||||
callback = std::move(done)
|
||||
]() mutable {
|
||||
auto format = QByteArray();
|
||||
if (bytes.isEmpty()) {
|
||||
QFile f(path);
|
||||
if (f.size() <= App::kImageSizeLimit
|
||||
&& f.open(QIODevice::ReadOnly)) {
|
||||
bytes = f.readAll();
|
||||
}
|
||||
}
|
||||
auto image = bytes.isEmpty()
|
||||
? QImage()
|
||||
: App::readImage(bytes, &format, false, nullptr);
|
||||
if (postprocess) {
|
||||
image = postprocess(std::move(image));
|
||||
}
|
||||
crl::on_main(std::move(guard), [
|
||||
image = std::move(image),
|
||||
callback = std::move(callback)
|
||||
]() mutable {
|
||||
callback(std::move(image));
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -327,103 +327,6 @@ private:
|
||||
VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
|
||||
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
|
||||
|
||||
class DocumentClickHandler : public FileClickHandler {
|
||||
public:
|
||||
DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> document() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<DocumentData*> _document;
|
||||
|
||||
};
|
||||
|
||||
class DocumentSaveClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
enum class Mode {
|
||||
ToCacheOrFile,
|
||||
ToFile,
|
||||
ToNewFile,
|
||||
};
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void Save(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document,
|
||||
Mode mode = Mode::ToCacheOrFile);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class DocumentOpenClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void Open(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *context);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class DocumentCancelClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class DocumentOpenWithClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void Open(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class VoiceSeekClickHandler : public DocumentOpenClickHandler {
|
||||
public:
|
||||
using DocumentOpenClickHandler::DocumentOpenClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class DocumentWrappedClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
DocumentWrappedClickHandler(
|
||||
ClickHandlerPtr wrapped,
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context = FullMsgId())
|
||||
: DocumentClickHandler(document, context)
|
||||
, _wrapped(wrapped) {
|
||||
}
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override {
|
||||
_wrapped->onClick({ Qt::LeftButton });
|
||||
}
|
||||
|
||||
private:
|
||||
ClickHandlerPtr _wrapped;
|
||||
|
||||
};
|
||||
|
||||
QString FileNameForSave(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &title,
|
||||
@@ -438,16 +341,3 @@ QString DocumentFileNameForSave(
|
||||
bool forceSavingAs = false,
|
||||
const QString &already = QString(),
|
||||
const QDir &dir = QDir());
|
||||
|
||||
namespace Data {
|
||||
|
||||
[[nodiscard]] QString FileExtension(const QString &filepath);
|
||||
[[nodiscard]] bool IsValidMediaFile(const QString &filepath);
|
||||
[[nodiscard]] bool IsExecutableName(const QString &filepath);
|
||||
[[nodiscard]] bool IsIpRevealingName(const QString &filepath);
|
||||
base::binary_guard ReadImageAsync(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
FnMut<QImage(QImage)> postprocess,
|
||||
FnMut<void(QImage&&)> done);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document_media.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_file_origin.h"
|
||||
|
||||
288
Telegram/SourceFiles/data/data_document_resolver.cpp
Normal file
288
Telegram/SourceFiles/data/data_document_resolver.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_document_resolver.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "facades.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/media/history_view_gif.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QMimeType>
|
||||
#include <QtCore/QMimeDatabase>
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
void LaunchWithWarning(
|
||||
// not_null<Window::Controller*> controller,
|
||||
const QString &name,
|
||||
HistoryItem *item) {
|
||||
const auto isExecutable = Data::IsExecutableName(name);
|
||||
const auto isIpReveal = Data::IsIpRevealingName(name);
|
||||
auto &app = Core::App();
|
||||
const auto warn = [&] {
|
||||
if (item && item->history()->peer->isVerified()) {
|
||||
return false;
|
||||
}
|
||||
return (isExecutable && app.settings().exeLaunchWarning())
|
||||
|| (isIpReveal && app.settings().ipRevealWarning());
|
||||
}();
|
||||
const auto extension = '.' + Data::FileExtension(name);
|
||||
if (Platform::IsWindows() && extension == u"."_q) {
|
||||
// If you launch a file without extension, like "test", in case
|
||||
// there is an executable file with the same name in this folder,
|
||||
// like "test.bat", the executable file will be launched.
|
||||
//
|
||||
// Now we always force an Open With dialog box for such files.
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeShowOpenWith(name);
|
||||
});
|
||||
return;
|
||||
} else if (!warn) {
|
||||
File::Launch(name);
|
||||
return;
|
||||
}
|
||||
const auto callback = [=, &app](bool checked) {
|
||||
if (checked) {
|
||||
if (isExecutable) {
|
||||
app.settings().setExeLaunchWarning(false);
|
||||
} else if (isIpReveal) {
|
||||
app.settings().setIpRevealWarning(false);
|
||||
}
|
||||
app.saveSettingsDelayed();
|
||||
}
|
||||
File::Launch(name);
|
||||
};
|
||||
auto text = isExecutable
|
||||
? tr::lng_launch_exe_warning(
|
||||
lt_extension,
|
||||
rpl::single(Ui::Text::Bold(extension)),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_launch_svg_warning(Ui::Text::WithEntities);
|
||||
Ui::show(Box<ConfirmDontWarnBox>(
|
||||
std::move(text),
|
||||
tr::lng_launch_exe_dont_ask(tr::now),
|
||||
(isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(),
|
||||
callback));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString FileExtension(const QString &filepath) {
|
||||
const auto reversed = ranges::views::reverse(filepath);
|
||||
const auto last = ranges::find_first_of(reversed, ".\\/");
|
||||
if (last == reversed.end() || *last != '.') {
|
||||
return QString();
|
||||
}
|
||||
return QString(last.base(), last - reversed.begin());
|
||||
}
|
||||
|
||||
// bool IsValidMediaFile(const QString &filepath) {
|
||||
// static const auto kExtensions = [] {
|
||||
// const auto list = qsl("\
|
||||
// 16svx 2sf 3g2 3gp 8svx aac aaf aif aifc aiff amr amv ape asf ast au aup \
|
||||
// avchd avi brstm bwf cam cdda cust dat divx drc dsh dsf dts dtshd dtsma \
|
||||
// dvr-ms dwd evo f4a f4b f4p f4v fla flac flr flv gif gifv gsf gsm gym iff \
|
||||
// ifo it jam la ly m1v m2p m2ts m2v m4a m4p m4v mcf mid mk3d mka mks mkv mng \
|
||||
// mov mp1 mp2 mp3 mp4 minipsf mod mpc mpe mpeg mpg mpv mscz mt2 mus mxf mxl \
|
||||
// niff nsf nsv off ofr ofs ogg ogv opus ots pac ps psf psf2 psflib ptb qsf \
|
||||
// qt ra raw rka rm rmj rmvb roq s3m shn sib sid smi smp sol spc spx ssf svi \
|
||||
// swa swf tak ts tta txm usf vgm vob voc vox vqf wav webm wma wmv wrap wtv \
|
||||
// wv xm xml ym yuv").split(' ');
|
||||
// return base::flat_set<QString>(list.begin(), list.end());
|
||||
// }();
|
||||
|
||||
// return ranges::binary_search(
|
||||
// kExtensions,
|
||||
// FileExtension(filepath).toLower());
|
||||
// }
|
||||
|
||||
bool IsExecutableName(const QString &filepath) {
|
||||
static const auto kExtensions = [] {
|
||||
const auto joined =
|
||||
#ifdef Q_OS_MAC
|
||||
qsl("\
|
||||
applescript action app bin command csh osx workflow terminal url caction \
|
||||
mpkg pkg scpt scptd xhtm webarchive");
|
||||
#elif defined Q_OS_UNIX // Q_OS_MAC
|
||||
qsl("bin csh deb desktop ksh out pet pkg pup rpm run sh shar \
|
||||
slp zsh");
|
||||
#else // Q_OS_MAC || Q_OS_UNIX
|
||||
qsl("\
|
||||
ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \
|
||||
chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \
|
||||
grp hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse key ksh \
|
||||
lnk local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda \
|
||||
mdb mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml \
|
||||
msh2xml mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps \
|
||||
php-s pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 \
|
||||
psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \
|
||||
sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \
|
||||
vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \
|
||||
vtx website ws wsc wsf wsh xbap xll xnk xs");
|
||||
#endif // !Q_OS_MAC && !Q_OS_UNIX
|
||||
const auto list = joined.split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
|
||||
return ranges::binary_search(
|
||||
kExtensions,
|
||||
FileExtension(filepath).toLower());
|
||||
}
|
||||
|
||||
bool IsIpRevealingName(const QString &filepath) {
|
||||
static const auto kExtensions = [] {
|
||||
const auto joined = u"htm html svg"_q;
|
||||
const auto list = joined.split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
static const auto kMimeTypes = [] {
|
||||
const auto joined = u"text/html image/svg+xml"_q;
|
||||
const auto list = joined.split(' ');
|
||||
return base::flat_set<QString>(list.begin(), list.end());
|
||||
}();
|
||||
|
||||
return ranges::binary_search(
|
||||
kExtensions,
|
||||
FileExtension(filepath).toLower()
|
||||
) || ranges::binary_search(
|
||||
kMimeTypes,
|
||||
QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name()
|
||||
);
|
||||
}
|
||||
|
||||
base::binary_guard ReadImageAsync(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
FnMut<QImage(QImage)> postprocess,
|
||||
FnMut<void(QImage&&)> done) {
|
||||
auto result = base::binary_guard();
|
||||
crl::async([
|
||||
bytes = media->bytes(),
|
||||
path = media->owner()->filepath(),
|
||||
postprocess = std::move(postprocess),
|
||||
guard = result.make_guard(),
|
||||
callback = std::move(done)
|
||||
]() mutable {
|
||||
auto format = QByteArray();
|
||||
if (bytes.isEmpty()) {
|
||||
QFile f(path);
|
||||
if (f.size() <= App::kImageSizeLimit
|
||||
&& f.open(QIODevice::ReadOnly)) {
|
||||
bytes = f.readAll();
|
||||
}
|
||||
}
|
||||
auto image = bytes.isEmpty()
|
||||
? QImage()
|
||||
: App::readImage(bytes, &format, false, nullptr);
|
||||
if (postprocess) {
|
||||
image = postprocess(std::move(image));
|
||||
}
|
||||
crl::on_main(std::move(guard), [
|
||||
image = std::move(image),
|
||||
callback = std::move(callback)
|
||||
]() mutable {
|
||||
callback(std::move(image));
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void ResolveDocument(
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item) {
|
||||
if (!document->date) {
|
||||
return;
|
||||
}
|
||||
const auto msgId = item ? item->fullId() : FullMsgId();
|
||||
|
||||
const auto showDocument = [&] {
|
||||
if (cUseExternalVideoPlayer()
|
||||
&& document->isVideoFile()
|
||||
&& !document->filepath().isEmpty()) {
|
||||
File::Launch(document->location(false).fname);
|
||||
} else if (controller) {
|
||||
controller->openDocument(document, msgId, true);
|
||||
}
|
||||
};
|
||||
|
||||
const auto media = document->createMediaView();
|
||||
const auto openImageInApp = [&] {
|
||||
if (document->size >= App::kImageSizeLimit) {
|
||||
return false;
|
||||
}
|
||||
const auto &location = document->location(true);
|
||||
if (!location.isEmpty() && location.accessEnable()) {
|
||||
const auto guard = gsl::finally([&] {
|
||||
location.accessDisable();
|
||||
});
|
||||
const auto path = location.name();
|
||||
if (Core::MimeTypeForFile(path).name().startsWith("image/")
|
||||
&& QImageReader(path).canRead()) {
|
||||
showDocument();
|
||||
return true;
|
||||
}
|
||||
} else if (document->mimeString().startsWith("image/")
|
||||
&& !media->bytes().isEmpty()) {
|
||||
auto bytes = media->bytes();
|
||||
auto buffer = QBuffer(&bytes);
|
||||
if (QImageReader(&buffer).canRead()) {
|
||||
showDocument();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto &location = document->location(true);
|
||||
if (document->isTheme() && media->loaded(true)) {
|
||||
showDocument();
|
||||
location.accessDisable();
|
||||
} else if (media->canBePlayed()) {
|
||||
if (document->isAudioFile()
|
||||
|| document->isVoiceMessage()
|
||||
|| document->isVideoMessage()) {
|
||||
::Media::Player::instance()->playPause({ document, msgId });
|
||||
} else if (item
|
||||
&& document->isAnimation()
|
||||
&& HistoryView::Gif::CanPlayInline(document)) {
|
||||
document->owner().requestAnimationPlayInline(item);
|
||||
} else {
|
||||
showDocument();
|
||||
}
|
||||
} else {
|
||||
document->saveFromDataSilent();
|
||||
if (!openImageInApp()) {
|
||||
if (!document->filepath(true).isEmpty()) {
|
||||
LaunchWithWarning(location.name(), item);
|
||||
} else if (document->status == FileReady
|
||||
|| document->status == FileDownloadFailed) {
|
||||
DocumentSaveClickHandler::Save(
|
||||
item ? item->fullId() : Data::FileOrigin(),
|
||||
document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
37
Telegram/SourceFiles/data/data_document_resolver.h
Normal file
37
Telegram/SourceFiles/data/data_document_resolver.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/binary_guard.h"
|
||||
|
||||
class DocumentData;
|
||||
class HistoryItem;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
|
||||
[[nodiscard]] QString FileExtension(const QString &filepath);
|
||||
// [[nodiscard]] bool IsValidMediaFile(const QString &filepath);
|
||||
[[nodiscard]] bool IsExecutableName(const QString &filepath);
|
||||
[[nodiscard]] bool IsIpRevealingName(const QString &filepath);
|
||||
base::binary_guard ReadImageAsync(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
FnMut<QImage(QImage)> postprocess,
|
||||
FnMut<void(QImage&&)> done);
|
||||
|
||||
void ResolveDocument(
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item);
|
||||
|
||||
} // namespace Data
|
||||
199
Telegram/SourceFiles/data/data_file_click_handler.cpp
Normal file
199
Telegram/SourceFiles/data/data_file_click_handler.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_file_click_handler.h"
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_photo.h"
|
||||
|
||||
FileClickHandler::FileClickHandler(FullMsgId context)
|
||||
: _context(context) {
|
||||
}
|
||||
|
||||
void FileClickHandler::setMessageId(FullMsgId context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
FullMsgId FileClickHandler::context() const {
|
||||
return _context;
|
||||
}
|
||||
|
||||
not_null<DocumentData*> DocumentClickHandler::document() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
DocumentWrappedClickHandler::DocumentWrappedClickHandler(
|
||||
ClickHandlerPtr wrapped,
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context)
|
||||
: DocumentClickHandler(document, context)
|
||||
, _wrapped(wrapped) {
|
||||
}
|
||||
|
||||
void DocumentWrappedClickHandler::onClickImpl() const {
|
||||
_wrapped->onClick({ Qt::LeftButton });
|
||||
}
|
||||
|
||||
DocumentClickHandler::DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context)
|
||||
: FileClickHandler(context)
|
||||
, _document(document) {
|
||||
}
|
||||
|
||||
DocumentOpenClickHandler::DocumentOpenClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context)
|
||||
: DocumentClickHandler(document, context)
|
||||
, _handler(std::move(callback)) {
|
||||
Expects(_handler != nullptr);
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::onClickImpl() const {
|
||||
_handler(context());
|
||||
}
|
||||
|
||||
void DocumentSaveClickHandler::Save(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data,
|
||||
Mode mode) {
|
||||
if (!data->date) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto savename = QString();
|
||||
if (mode != Mode::ToCacheOrFile || !data->saveToCache()) {
|
||||
if (mode != Mode::ToNewFile && data->saveFromData()) {
|
||||
return;
|
||||
}
|
||||
const auto filepath = data->filepath(true);
|
||||
const auto fileinfo = QFileInfo(
|
||||
);
|
||||
const auto filedir = filepath.isEmpty()
|
||||
? QDir()
|
||||
: fileinfo.dir();
|
||||
const auto filename = filepath.isEmpty()
|
||||
? QString()
|
||||
: fileinfo.fileName();
|
||||
savename = DocumentFileNameForSave(
|
||||
data,
|
||||
(mode == Mode::ToNewFile),
|
||||
filename,
|
||||
filedir);
|
||||
if (savename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
data->save(origin, savename);
|
||||
}
|
||||
|
||||
void DocumentSaveClickHandler::onClickImpl() const {
|
||||
Save(context(), document());
|
||||
}
|
||||
|
||||
DocumentCancelClickHandler::DocumentCancelClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context)
|
||||
: DocumentClickHandler(document, context)
|
||||
, _handler(std::move(callback)) {
|
||||
}
|
||||
|
||||
void DocumentCancelClickHandler::onClickImpl() const {
|
||||
const auto data = document();
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else if (data->uploading() && _handler) {
|
||||
_handler(context());
|
||||
} else {
|
||||
data->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentOpenWithClickHandler::Open(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> data) {
|
||||
if (!data->date) {
|
||||
return;
|
||||
}
|
||||
|
||||
data->saveFromDataSilent();
|
||||
const auto path = data->filepath(true);
|
||||
if (!path.isEmpty()) {
|
||||
File::OpenWith(path, QCursor::pos());
|
||||
} else {
|
||||
DocumentSaveClickHandler::Save(
|
||||
origin,
|
||||
data,
|
||||
DocumentSaveClickHandler::Mode::ToFile);
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentOpenWithClickHandler::onClickImpl() const {
|
||||
Open(context(), document());
|
||||
}
|
||||
|
||||
PhotoClickHandler::PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context,
|
||||
PeerData *peer)
|
||||
: FileClickHandler(context)
|
||||
, _photo(photo)
|
||||
, _peer(peer) {
|
||||
}
|
||||
|
||||
not_null<PhotoData*> PhotoClickHandler::photo() const {
|
||||
return _photo;
|
||||
}
|
||||
|
||||
PeerData *PhotoClickHandler::peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
PhotoOpenClickHandler::PhotoOpenClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context)
|
||||
: PhotoClickHandler(photo, context)
|
||||
, _handler(std::move(callback)) {
|
||||
Expects(_handler != nullptr);
|
||||
}
|
||||
|
||||
void PhotoOpenClickHandler::onClickImpl() const {
|
||||
_handler(context());
|
||||
}
|
||||
|
||||
void PhotoSaveClickHandler::onClickImpl() const {
|
||||
const auto data = photo();
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else {
|
||||
data->clearFailed(Data::PhotoSize::Large);
|
||||
data->load(context());
|
||||
}
|
||||
}
|
||||
|
||||
PhotoCancelClickHandler::PhotoCancelClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context)
|
||||
: PhotoClickHandler(photo, context)
|
||||
, _handler(std::move(callback)) {
|
||||
}
|
||||
|
||||
void PhotoCancelClickHandler::onClickImpl() const {
|
||||
const auto data = photo();
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else if (data->uploading() && _handler) {
|
||||
_handler(context());
|
||||
} else {
|
||||
data->cancel();
|
||||
}
|
||||
}
|
||||
181
Telegram/SourceFiles/data/data_file_click_handler.h
Normal file
181
Telegram/SourceFiles/data/data_file_click_handler.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_file_origin.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
|
||||
class DocumentData;
|
||||
class HistoryItem;
|
||||
class PhotoData;
|
||||
|
||||
class FileClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
FileClickHandler(FullMsgId context);
|
||||
|
||||
void setMessageId(FullMsgId context);
|
||||
|
||||
[[nodiscard]] FullMsgId context() const;
|
||||
|
||||
private:
|
||||
FullMsgId _context;
|
||||
|
||||
};
|
||||
|
||||
class DocumentClickHandler : public FileClickHandler {
|
||||
public:
|
||||
DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> document() const;
|
||||
|
||||
private:
|
||||
const not_null<DocumentData*> _document;
|
||||
|
||||
};
|
||||
|
||||
class DocumentSaveClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
enum class Mode {
|
||||
ToCacheOrFile,
|
||||
ToFile,
|
||||
ToNewFile,
|
||||
};
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void Save(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document,
|
||||
Mode mode = Mode::ToCacheOrFile);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class DocumentOpenClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
DocumentOpenClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
const Fn<void(FullMsgId)> _handler;
|
||||
|
||||
};
|
||||
|
||||
class DocumentCancelClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
DocumentCancelClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
const Fn<void(FullMsgId)> _handler;
|
||||
|
||||
};
|
||||
|
||||
class DocumentOpenWithClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
using DocumentClickHandler::DocumentClickHandler;
|
||||
static void Open(
|
||||
Data::FileOrigin origin,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class VoiceSeekClickHandler : public DocumentOpenClickHandler {
|
||||
public:
|
||||
using DocumentOpenClickHandler::DocumentOpenClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class DocumentWrappedClickHandler : public DocumentClickHandler {
|
||||
public:
|
||||
DocumentWrappedClickHandler(
|
||||
ClickHandlerPtr wrapped,
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
ClickHandlerPtr _wrapped;
|
||||
|
||||
};
|
||||
|
||||
class PhotoClickHandler : public FileClickHandler {
|
||||
public:
|
||||
PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context = FullMsgId(),
|
||||
PeerData *peer = nullptr);
|
||||
|
||||
[[nodiscard]] not_null<PhotoData*> photo() const;
|
||||
[[nodiscard]] PeerData *peer() const;
|
||||
|
||||
private:
|
||||
const not_null<PhotoData*> _photo;
|
||||
PeerData * const _peer = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class PhotoOpenClickHandler : public PhotoClickHandler {
|
||||
public:
|
||||
PhotoOpenClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
const Fn<void(FullMsgId)> _handler;
|
||||
|
||||
};
|
||||
|
||||
class PhotoSaveClickHandler : public PhotoClickHandler {
|
||||
public:
|
||||
using PhotoClickHandler::PhotoClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class PhotoCancelClickHandler : public PhotoClickHandler {
|
||||
public:
|
||||
PhotoCancelClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
Fn<void(FullMsgId)> &&callback,
|
||||
FullMsgId context = FullMsgId());
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
const Fn<void(FullMsgId)> _handler;
|
||||
|
||||
};
|
||||
@@ -468,43 +468,3 @@ auto PhotoData::createStreamingLoader(
|
||||
origin)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
PhotoClickHandler::PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context,
|
||||
PeerData *peer)
|
||||
: FileClickHandler(&photo->session(), context)
|
||||
, _photo(photo)
|
||||
, _peer(peer) {
|
||||
}
|
||||
|
||||
void PhotoOpenClickHandler::onClickImpl() const {
|
||||
Core::App().showPhoto(this);
|
||||
}
|
||||
|
||||
void PhotoSaveClickHandler::onClickImpl() const {
|
||||
const auto data = photo();
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else {
|
||||
data->clearFailed(PhotoSize::Large);
|
||||
data->load(context());
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoCancelClickHandler::onClickImpl() const {
|
||||
const auto data = photo();
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else if (data->uploading()) {
|
||||
if (const auto item = data->owner().message(context())) {
|
||||
if (const auto m = App::main()) { // multi good
|
||||
if (&m->session() == &data->session()) {
|
||||
m->cancelUploadLayer(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,50 +175,3 @@ private:
|
||||
not_null<Data::Session*> _owner;
|
||||
|
||||
};
|
||||
|
||||
class PhotoClickHandler : public FileClickHandler {
|
||||
public:
|
||||
PhotoClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context = FullMsgId(),
|
||||
PeerData *peer = nullptr);
|
||||
|
||||
[[nodiscard]] not_null<PhotoData*> photo() const {
|
||||
return _photo;
|
||||
}
|
||||
[[nodiscard]] PeerData *peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<PhotoData*> _photo;
|
||||
PeerData * const _peer = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class PhotoOpenClickHandler : public PhotoClickHandler {
|
||||
public:
|
||||
using PhotoClickHandler::PhotoClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class PhotoSaveClickHandler : public PhotoClickHandler {
|
||||
public:
|
||||
using PhotoClickHandler::PhotoClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class PhotoCancelClickHandler : public PhotoClickHandler {
|
||||
public:
|
||||
using PhotoClickHandler::PhotoClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
@@ -16,8 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_session.h"
|
||||
#include "info/info_memento.h"
|
||||
@@ -30,6 +32,51 @@ namespace {
|
||||
|
||||
using Type = Storage::SharedMediaType;
|
||||
|
||||
bool IsItemGoodForType(const not_null<HistoryItem*> item, Type type) {
|
||||
const auto media = item->media();
|
||||
if (!media || media->webpage()) {
|
||||
return false;
|
||||
}
|
||||
const auto photo = media->photo();
|
||||
const auto photoType = (type == Type::Photo);
|
||||
const auto photoVideoType = (type == Type::PhotoVideo);
|
||||
if ((photoType || photoVideoType) && photo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto document = media->document();
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
const auto voiceType = (type == Type::VoiceFile);
|
||||
const auto voiceDoc = document->isVoiceMessage();
|
||||
|
||||
const auto roundType = (type == Type::RoundFile);
|
||||
const auto roundDoc = document->isVideoMessage();
|
||||
|
||||
const auto audioType = (type == Type::MusicFile);
|
||||
const auto audioDoc = document->isAudioFile();
|
||||
|
||||
const auto gifType = (type == Type::GIF);
|
||||
const auto gifDoc = document->isGifv();
|
||||
|
||||
const auto videoType = (type == Type::Video);
|
||||
const auto videoDoc = document->isVideoFile();
|
||||
|
||||
const auto voiceRoundType = (type == Type::RoundVoiceFile);
|
||||
const auto fileType = (type == Type::File);
|
||||
|
||||
return (audioType && audioDoc)
|
||||
|| (voiceType && voiceDoc)
|
||||
|| (roundType && roundDoc)
|
||||
|| (voiceRoundType && (roundDoc || voiceDoc))
|
||||
|| (gifType && gifDoc)
|
||||
|| ((videoType || photoVideoType) && videoDoc)
|
||||
|| (fileType && (document->isTheme()
|
||||
|| document->isImage()
|
||||
|| !document->canBeStreamed()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<Storage::SharedMediaType> SharedMediaOverviewType(
|
||||
@@ -153,6 +200,55 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(!IsServerMsgId(key.mergedKey.universalId));
|
||||
Expects((key.mergedKey.universalId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
|
||||
const auto history = session->data().history(key.mergedKey.peerId);
|
||||
|
||||
return rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
session->data().scheduledMessages().updates(history)
|
||||
) | rpl::map([=] {
|
||||
const auto list = session->data().scheduledMessages().list(history);
|
||||
|
||||
auto items = ranges::views::all(
|
||||
list.ids
|
||||
) | ranges::views::transform([=](const FullMsgId &fullId) {
|
||||
return session->data().message(fullId);
|
||||
}) | ranges::views::filter([=](HistoryItem *item) {
|
||||
return item
|
||||
? IsItemGoodForType(item, key.type)
|
||||
: false;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
ranges::sort(items, ranges::less(), &HistoryItem::position);
|
||||
|
||||
auto finishMsgIds = ranges::views::all(
|
||||
items
|
||||
) | ranges::views::transform([=](not_null<HistoryItem*> item) {
|
||||
return item->fullId().msg;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto fullCount = finishMsgIds.size();
|
||||
|
||||
auto unsorted = SparseUnsortedIdsSlice(
|
||||
std::move(finishMsgIds),
|
||||
fullCount,
|
||||
list.skippedBefore,
|
||||
list.skippedAfter);
|
||||
return SparseIdsMergedSlice(
|
||||
key.mergedKey,
|
||||
std::move(unsorted));
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
@@ -374,12 +470,29 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return [=](auto consumer) {
|
||||
auto viewerKey = SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type);
|
||||
|
||||
if (std::get_if<not_null<PhotoData*>>(&key.universalId)) {
|
||||
return SharedMediaMergedViewer(
|
||||
session,
|
||||
SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type),
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
|
||||
consumer.put_next(SharedMediaWithLastSlice(
|
||||
session,
|
||||
key,
|
||||
std::move(update),
|
||||
std::nullopt));
|
||||
});
|
||||
}
|
||||
|
||||
if (key.scheduled) {
|
||||
return SharedScheduledMediaViewer(
|
||||
session,
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
|
||||
@@ -393,9 +506,7 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
|
||||
return rpl::combine(
|
||||
SharedMediaMergedViewer(
|
||||
session,
|
||||
SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type),
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter),
|
||||
SharedMediaMergedViewer(
|
||||
|
||||
@@ -50,6 +50,12 @@ struct SharedMediaMergedKey {
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
@@ -71,11 +77,13 @@ public:
|
||||
PeerId peerId,
|
||||
PeerId migratedPeerId,
|
||||
Type type,
|
||||
UniversalMsgId universalId)
|
||||
UniversalMsgId universalId,
|
||||
bool scheduled = false)
|
||||
: peerId(peerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, type(type)
|
||||
, universalId(universalId) {
|
||||
, universalId(universalId)
|
||||
, scheduled(scheduled) {
|
||||
Expects(v::is<MessageId>(universalId) || type == Type::ChatPhoto);
|
||||
}
|
||||
|
||||
@@ -93,6 +101,7 @@ public:
|
||||
PeerId migratedPeerId = 0;
|
||||
Type type = Type::kCount;
|
||||
UniversalMsgId universalId;
|
||||
bool scheduled = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -10,53 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <rpl/combine.h>
|
||||
#include "storage/storage_sparse_ids_list.h"
|
||||
|
||||
SparseIdsSlice::SparseIdsSlice(
|
||||
const base::flat_set<MsgId> &ids,
|
||||
MsgRange range,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _ids(ids)
|
||||
, _range(range)
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsSlice::indexOf(MsgId msgId) const {
|
||||
auto it = _ids.find(msgId);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MsgId SparseIdsSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsSlice::distance(
|
||||
MsgId a,
|
||||
MsgId b) const {
|
||||
if (auto i = indexOf(a)) {
|
||||
if (auto j = indexOf(b)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<MsgId> SparseIdsSlice::nearest(MsgId msgId) const {
|
||||
if (auto it = ranges::lower_bound(_ids, msgId); it != _ids.end()) {
|
||||
return *it;
|
||||
} else if (_ids.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return _ids.back();
|
||||
}
|
||||
|
||||
SparseIdsMergedSlice::SparseIdsMergedSlice(Key key)
|
||||
: SparseIdsMergedSlice(
|
||||
key,
|
||||
@@ -73,33 +26,48 @@ SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||
, _migrated(std::move(migrated)) {
|
||||
}
|
||||
|
||||
SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||
Key key,
|
||||
SparseUnsortedIdsSlice scheduled)
|
||||
: _key(key)
|
||||
, _scheduled(std::move(scheduled)) {
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::fullCount() const {
|
||||
return Add(
|
||||
_part.fullCount(),
|
||||
_migrated ? _migrated->fullCount() : 0);
|
||||
return _scheduled
|
||||
? _scheduled->fullCount()
|
||||
: Add(
|
||||
_part.fullCount(),
|
||||
_migrated ? _migrated->fullCount() : 0);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
|
||||
return Add(
|
||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||
_migrated
|
||||
? (isolatedInPart()
|
||||
? _migrated->fullCount()
|
||||
: _migrated->skippedBefore())
|
||||
: 0
|
||||
);
|
||||
return _scheduled
|
||||
? _scheduled->skippedBefore()
|
||||
: Add(
|
||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||
_migrated
|
||||
? (isolatedInPart()
|
||||
? _migrated->fullCount()
|
||||
: _migrated->skippedBefore())
|
||||
: 0
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
|
||||
return Add(
|
||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||
);
|
||||
return _scheduled
|
||||
? _scheduled->skippedAfter()
|
||||
: Add(
|
||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||
FullMsgId fullId) const {
|
||||
return isFromPart(fullId)
|
||||
return _scheduled
|
||||
? _scheduled->indexOf(fullId.msg)
|
||||
: isFromPart(fullId)
|
||||
? (_part.indexOf(fullId.msg) | func::add(migratedSize()))
|
||||
: isolatedInPart()
|
||||
? std::nullopt
|
||||
@@ -109,14 +77,20 @@ std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||
}
|
||||
|
||||
int SparseIdsMergedSlice::size() const {
|
||||
return (isolatedInPart() ? 0 : migratedSize())
|
||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||
return _scheduled
|
||||
? _scheduled->size()
|
||||
: (isolatedInPart() ? 0 : migratedSize())
|
||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||
}
|
||||
|
||||
FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
if (auto size = migratedSize()) {
|
||||
if (_scheduled) {
|
||||
return ComputeId(_key.peerId, (*_scheduled)[index]);
|
||||
}
|
||||
|
||||
if (const auto size = migratedSize()) {
|
||||
if (index < size) {
|
||||
return ComputeId(_key.migratedPeerId, (*_migrated)[index]);
|
||||
}
|
||||
@@ -128,8 +102,8 @@ FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||
std::optional<int> SparseIdsMergedSlice::distance(
|
||||
const Key &a,
|
||||
const Key &b) const {
|
||||
if (auto i = indexOf(ComputeId(a))) {
|
||||
if (auto j = indexOf(ComputeId(b))) {
|
||||
if (const auto i = indexOf(ComputeId(a))) {
|
||||
if (const auto j = indexOf(ComputeId(b))) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
@@ -138,10 +112,15 @@ std::optional<int> SparseIdsMergedSlice::distance(
|
||||
|
||||
auto SparseIdsMergedSlice::nearest(
|
||||
UniversalMsgId id) const -> std::optional<FullMsgId> {
|
||||
auto convertFromPartNearest = [&](MsgId result) {
|
||||
if (_scheduled) {
|
||||
if (const auto nearestId = _scheduled->nearest(id)) {
|
||||
return ComputeId(_key.peerId, *nearestId);
|
||||
}
|
||||
}
|
||||
const auto convertFromPartNearest = [&](MsgId result) {
|
||||
return ComputeId(_key.peerId, result);
|
||||
};
|
||||
auto convertFromMigratedNearest = [&](MsgId result) {
|
||||
const auto convertFromMigratedNearest = [&](MsgId result) {
|
||||
return ComputeId(_key.migratedPeerId, result);
|
||||
};
|
||||
if (IsServerMsgId(id)) {
|
||||
@@ -245,7 +224,6 @@ bool SparseIdsSliceBuilder::removeOne(MsgId messageId) {
|
||||
|
||||
bool SparseIdsSliceBuilder::removeAll() {
|
||||
_ids = {};
|
||||
_range = { 0, ServerMaxMsgId };
|
||||
_fullCount = 0;
|
||||
_skippedBefore = 0;
|
||||
_skippedAfter = 0;
|
||||
@@ -254,9 +232,6 @@ bool SparseIdsSliceBuilder::removeAll() {
|
||||
|
||||
bool SparseIdsSliceBuilder::invalidateBottom() {
|
||||
_fullCount = _skippedAfter = std::nullopt;
|
||||
if (_range.till == ServerMaxMsgId) {
|
||||
_range.till = _ids.empty() ? _range.from : _ids.back();
|
||||
}
|
||||
checkInsufficient();
|
||||
return true;
|
||||
}
|
||||
@@ -389,7 +364,6 @@ void SparseIdsSliceBuilder::requestMessagesCount() {
|
||||
SparseIdsSlice SparseIdsSliceBuilder::snapshot() const {
|
||||
return SparseIdsSlice(
|
||||
_ids,
|
||||
_range,
|
||||
_fullCount,
|
||||
_skippedBefore,
|
||||
_skippedAfter);
|
||||
@@ -440,4 +414,4 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
std::move(migrated)));
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_abstract_sparse_ids.h"
|
||||
#include "data/data_messages.h"
|
||||
|
||||
namespace Storage {
|
||||
@@ -14,36 +15,15 @@ struct SparseIdsListResult;
|
||||
struct SparseIdsSliceUpdate;
|
||||
} // namespace Storage
|
||||
|
||||
class SparseIdsSlice {
|
||||
class SparseIdsSlice final : public AbstractSparseIds<base::flat_set<MsgId>> {
|
||||
public:
|
||||
using Key = MsgId;
|
||||
|
||||
SparseIdsSlice() = default;
|
||||
SparseIdsSlice(
|
||||
const base::flat_set<MsgId> &ids,
|
||||
MsgRange range,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter);
|
||||
|
||||
std::optional<int> fullCount() const { return _fullCount; }
|
||||
std::optional<int> skippedBefore() const { return _skippedBefore; }
|
||||
std::optional<int> skippedAfter() const { return _skippedAfter; }
|
||||
std::optional<int> indexOf(MsgId msgId) const;
|
||||
int size() const { return _ids.size(); }
|
||||
MsgId operator[](int index) const;
|
||||
std::optional<int> distance(MsgId a, MsgId b) const;
|
||||
std::optional<MsgId> nearest(MsgId msgId) const;
|
||||
|
||||
private:
|
||||
base::flat_set<MsgId> _ids;
|
||||
MsgRange _range;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
using AbstractSparseIds<base::flat_set<MsgId>>::AbstractSparseIds;
|
||||
|
||||
};
|
||||
|
||||
using SparseUnsortedIdsSlice = AbstractSparseIds<std::vector<MsgId>>;
|
||||
|
||||
class SparseIdsMergedSlice {
|
||||
public:
|
||||
using UniversalMsgId = MsgId;
|
||||
@@ -51,9 +31,11 @@ public:
|
||||
Key(
|
||||
PeerId peerId,
|
||||
PeerId migratedPeerId,
|
||||
UniversalMsgId universalId)
|
||||
UniversalMsgId universalId,
|
||||
bool scheduled = false)
|
||||
: peerId(peerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, scheduled(scheduled)
|
||||
, migratedPeerId(scheduled ? 0 : migratedPeerId)
|
||||
, universalId(universalId) {
|
||||
}
|
||||
|
||||
@@ -67,6 +49,7 @@ public:
|
||||
}
|
||||
|
||||
PeerId peerId = 0;
|
||||
bool scheduled = false;
|
||||
PeerId migratedPeerId = 0;
|
||||
UniversalMsgId universalId = 0;
|
||||
|
||||
@@ -77,6 +60,9 @@ public:
|
||||
Key key,
|
||||
SparseIdsSlice part,
|
||||
std::optional<SparseIdsSlice> migrated);
|
||||
SparseIdsMergedSlice(
|
||||
Key key,
|
||||
SparseUnsortedIdsSlice scheduled);
|
||||
|
||||
std::optional<int> fullCount() const;
|
||||
std::optional<int> skippedBefore() const;
|
||||
@@ -155,6 +141,7 @@ private:
|
||||
Key _key;
|
||||
SparseIdsSlice _part;
|
||||
std::optional<SparseIdsSlice> _migrated;
|
||||
std::optional<SparseUnsortedIdsSlice> _scheduled;
|
||||
|
||||
};
|
||||
|
||||
@@ -205,7 +192,6 @@ private:
|
||||
|
||||
Key _key;
|
||||
base::flat_set<MsgId> _ids;
|
||||
MsgRange _range;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
@@ -133,10 +133,6 @@ void MessageCursor::applyTo(not_null<Ui::InputField*> field) {
|
||||
field->scrollTo(scroll);
|
||||
}
|
||||
|
||||
HistoryItem *FileClickHandler::getActionItem() const {
|
||||
return _session->data().message(context());
|
||||
}
|
||||
|
||||
PeerId PeerFromMessage(const MTPmessage &message) {
|
||||
return message.match([](const MTPDmessageEmpty &) {
|
||||
return PeerId(0);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user