Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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,12 +82,7 @@ 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
|
||||
@@ -130,7 +125,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)
|
||||
@@ -428,6 +423,8 @@ PRIVATE
|
||||
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
|
||||
|
||||
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 |
@@ -1007,6 +1007,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";
|
||||
@@ -2030,6 +2031,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.";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.7.9.0" />
|
||||
Version="2.8.2.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,9,0
|
||||
PRODUCTVERSION 2,7,9,0
|
||||
FILEVERSION 2,8,2,0
|
||||
PRODUCTVERSION 2,8,2,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.9.0"
|
||||
VALUE "FileVersion", "2.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.9.0"
|
||||
VALUE "ProductVersion", "2.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,9,0
|
||||
PRODUCTVERSION 2,7,9,0
|
||||
FILEVERSION 2,8,2,0
|
||||
PRODUCTVERSION 2,8,2,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.9.0"
|
||||
VALUE "FileVersion", "2.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.9.0"
|
||||
VALUE "ProductVersion", "2.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -45,6 +45,6 @@ void AutoLockBox::durationChanged(int seconds) {
|
||||
Core::App().settings().setAutoLock(seconds);
|
||||
Core::App().saveSettingsDelayed();
|
||||
|
||||
Core::App().checkAutoLock();
|
||||
Core::App().checkAutoLock(crl::now());
|
||||
closeBox();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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 {
|
||||
@@ -1251,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 }};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -1064,43 +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();
|
||||
std::make_unique<VideoTrack>(
|
||||
paused,
|
||||
_requireARGB32,
|
||||
endpoint.peer)).first;
|
||||
const auto track = &i->second->track;
|
||||
|
||||
track->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto &activeTrack = _activeVideoTracks[endpoint];
|
||||
const auto activeTrack = _activeVideoTracks[endpoint].get();
|
||||
const auto size = track->frameSize();
|
||||
if (size.isEmpty()) {
|
||||
track->markFrameShown();
|
||||
} else if (!activeTrack.shown) {
|
||||
activeTrack.shown = true;
|
||||
} else if (!activeTrack->shown) {
|
||||
activeTrack->shown = true;
|
||||
markTrackShown(endpoint, true);
|
||||
}
|
||||
activeTrack.trackSize = size;
|
||||
}, i->second.lifetime);
|
||||
activeTrack->trackSize = size;
|
||||
}, i->second->lifetime);
|
||||
|
||||
const auto size = track->frameSize();
|
||||
i->second.trackSize = size;
|
||||
i->second->trackSize = size;
|
||||
if (!size.isEmpty() || paused) {
|
||||
i->second.shown = true;
|
||||
i->second->shown = true;
|
||||
shown = true;
|
||||
} else {
|
||||
track->stateValue(
|
||||
) | rpl::filter([=](Webrtc::VideoState state) {
|
||||
return (state == Webrtc::VideoState::Paused)
|
||||
&& !_activeVideoTracks[endpoint].shown;
|
||||
&& !_activeVideoTracks[endpoint]->shown;
|
||||
}) | rpl::start_with_next([=] {
|
||||
_activeVideoTracks[endpoint].shown = true;
|
||||
_activeVideoTracks[endpoint]->shown = true;
|
||||
markTrackShown(endpoint, true);
|
||||
}, i->second.lifetime);
|
||||
}, i->second->lifetime);
|
||||
}
|
||||
addVideoOutput(i->first.id, { track->sink() });
|
||||
} else {
|
||||
@@ -1144,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);
|
||||
}
|
||||
@@ -1954,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);
|
||||
}
|
||||
|
||||
@@ -2007,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());
|
||||
}
|
||||
@@ -2402,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) {
|
||||
@@ -2414,24 +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
|
||||
&& endpoint.type != VideoEndpointType::Screen)
|
||||
? 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));
|
||||
}
|
||||
|
||||
@@ -2911,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,
|
||||
@@ -321,27 +332,8 @@ public:
|
||||
-> rpl::producer<VideoEndpoint> {
|
||||
return _videoEndpointLarge.value();
|
||||
}
|
||||
|
||||
struct VideoTrack {
|
||||
std::unique_ptr<Webrtc::VideoTrack> track;
|
||||
rpl::variable<QSize> trackSize;
|
||||
PeerData *peer = nullptr;
|
||||
rpl::lifetime lifetime;
|
||||
Group::VideoQuality quality = Group::VideoQuality();
|
||||
bool shown = false;
|
||||
|
||||
[[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
|
||||
@@ -625,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
|
||||
|
||||
@@ -34,7 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#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"
|
||||
|
||||
@@ -1236,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* {
|
||||
@@ -1262,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()) {
|
||||
@@ -1442,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) {
|
||||
@@ -1451,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,
|
||||
@@ -1490,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* {
|
||||
|
||||
@@ -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,8 +931,8 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
|
||||
) | rpl::map(_1 == endpoint && _2);
|
||||
viewport->add(
|
||||
endpoint,
|
||||
VideoTileTrack{ track.track.get(), row },
|
||||
track.trackSize.value(),
|
||||
VideoTileTrack{ GroupCall::TrackPointer(track), row },
|
||||
GroupCall::TrackSizeValue(track),
|
||||
std::move(pinned));
|
||||
};
|
||||
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
|
||||
@@ -908,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) {
|
||||
@@ -980,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(
|
||||
@@ -988,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();
|
||||
}
|
||||
@@ -1041,7 +1138,7 @@ void Panel::refreshTopButton() {
|
||||
chooseJoinAs();
|
||||
});
|
||||
updateControlsGeometry();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
} else {
|
||||
_menuToggle.destroy();
|
||||
_joinAsToggle.destroy();
|
||||
@@ -1295,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
|
||||
}
|
||||
@@ -1307,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 {
|
||||
@@ -1352,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
|
||||
@@ -1373,6 +1477,7 @@ bool Panel::updateMode() {
|
||||
updateButtonsStyles();
|
||||
refreshControlsBackground();
|
||||
updateControlsGeometry();
|
||||
showStickedTooltip();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1557,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);
|
||||
@@ -1569,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()) {
|
||||
@@ -1595,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);
|
||||
@@ -1645,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) {
|
||||
@@ -1832,6 +2043,7 @@ void Panel::updateButtonsGeometry() {
|
||||
width,
|
||||
st::groupCallMembersBottomSkip);
|
||||
}
|
||||
updateTooltipGeometry();
|
||||
}
|
||||
|
||||
bool Panel::videoButtonInNarrowMode() const {
|
||||
@@ -1999,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;
|
||||
|
||||
};
|
||||
|
||||
@@ -493,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)) {
|
||||
@@ -763,7 +763,12 @@ void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
||||
const auto kMedium = style::ConvertScale(540);
|
||||
const auto kSmall = style::ConvertScale(240);
|
||||
const auto &endpoint = tile->endpoint();
|
||||
const auto quality = (min >= kMedium)
|
||||
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
|
||||
|
||||
@@ -439,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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -82,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;
|
||||
}
|
||||
|
||||
@@ -106,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) {
|
||||
@@ -249,7 +249,7 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
||||
}) | rpl::start_with_next([=](bool pinned) {
|
||||
_pinned = pinned;
|
||||
updateTopControlsSize();
|
||||
if (_shown) {
|
||||
if (!_hidden) {
|
||||
updateTopControlsPosition();
|
||||
_update();
|
||||
}
|
||||
|
||||
@@ -45,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;
|
||||
@@ -115,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;
|
||||
|
||||
@@ -744,7 +744,7 @@ FieldAutocomplete::Inner::Inner(
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
_isOneColumn = controller->adaptive().isOneColumn();
|
||||
update();
|
||||
|
||||
@@ -849,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,19 +876,21 @@ bool Application::someSessionExists() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::checkAutoLock() {
|
||||
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();
|
||||
@@ -910,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 {
|
||||
|
||||
@@ -245,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();
|
||||
|
||||
|
||||
@@ -127,6 +127,14 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- 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 {
|
||||
@@ -94,23 +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
|
||||
+ Serialize::bytearraySize(proxy);
|
||||
+ 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);
|
||||
@@ -200,8 +213,9 @@ QByteArray Settings::serialize() const {
|
||||
stream
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
|
||||
<< _workMode.current()
|
||||
<< proxy;
|
||||
<< qint32(_workMode.current())
|
||||
<< proxy
|
||||
<< qint32(_hiddenGroupCallTooltips.value());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -281,6 +295,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 groupCallNoiseSuppression = _groupCallNoiseSuppression ? 1 : 0;
|
||||
qint32 workMode = static_cast<qint32>(_workMode.current());
|
||||
QByteArray proxy;
|
||||
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -421,6 +436,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> proxy;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> hiddenGroupCallTooltips;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -540,6 +558,16 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
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 {
|
||||
@@ -795,6 +823,7 @@ void Settings::resetOnLastLogout() {
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#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;
|
||||
@@ -27,6 +28,10 @@ namespace Webrtc {
|
||||
enum class Backend;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Calls::Group {
|
||||
enum class StickedTooltip;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct WindowPosition {
|
||||
@@ -90,6 +95,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideValue() const {
|
||||
return _adaptiveForWide.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideChanges() const {
|
||||
return _adaptiveForWide.changes();
|
||||
}
|
||||
void setAdaptiveForWide(bool value) {
|
||||
_adaptiveForWide = value;
|
||||
}
|
||||
@@ -574,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) {
|
||||
@@ -671,6 +686,7 @@ private:
|
||||
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
|
||||
|
||||
@@ -12,7 +12,6 @@ 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"
|
||||
@@ -334,7 +333,6 @@ int Launcher::exec() {
|
||||
|
||||
// Must be started before Sandbox is created.
|
||||
Platform::start();
|
||||
Ui::DisableCustomScaling();
|
||||
|
||||
auto result = executeApplication();
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ 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 "app.h"
|
||||
|
||||
@@ -312,6 +313,7 @@ void Sandbox::singleInstanceChecked() {
|
||||
Logs::multipleInstances();
|
||||
}
|
||||
|
||||
Ui::DisableCustomScaling();
|
||||
refreshGlobalProxy();
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
new NotStartedWindow();
|
||||
|
||||
@@ -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 = 2007009;
|
||||
constexpr auto AppVersionStr = "2.7.9";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 2008002;
|
||||
constexpr auto AppVersionStr = "2.8.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -76,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(
|
||||
@@ -309,104 +256,6 @@ QString DocumentFileNameForSave(
|
||||
dir);
|
||||
}
|
||||
|
||||
DocumentClickHandler::DocumentClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context)
|
||||
: FileClickHandler(&document->session(), context)
|
||||
, _document(document) {
|
||||
}
|
||||
|
||||
DocumentOpenClickHandler::DocumentOpenClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> &&callback)
|
||||
: DocumentClickHandler(document)
|
||||
, _handler(std::move(callback)) {
|
||||
}
|
||||
|
||||
void DocumentOpenClickHandler::onClickImpl() const {
|
||||
if (_handler) {
|
||||
_handler();
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
@@ -327,104 +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:
|
||||
DocumentOpenClickHandler(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> &&callback);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
Fn<void()> _handler;
|
||||
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#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"
|
||||
@@ -208,7 +209,7 @@ base::binary_guard ReadImageAsync(
|
||||
}
|
||||
|
||||
void ResolveDocument(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item) {
|
||||
if (!document->date) {
|
||||
@@ -221,7 +222,7 @@ void ResolveDocument(
|
||||
&& document->isVideoFile()
|
||||
&& !document->filepath().isEmpty()) {
|
||||
File::Launch(document->location(false).fname);
|
||||
} else {
|
||||
} else if (controller) {
|
||||
controller->openDocument(document, msgId, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ base::binary_guard ReadImageAsync(
|
||||
FnMut<void(QImage&&)> done);
|
||||
|
||||
void ResolveDocument(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item);
|
||||
|
||||
|
||||
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,52 +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) {
|
||||
}
|
||||
|
||||
PhotoOpenClickHandler::PhotoOpenClickHandler(
|
||||
not_null<PhotoData*> photo,
|
||||
Fn<void()> &&callback)
|
||||
: PhotoClickHandler(photo)
|
||||
, _handler(std::move(callback)) {
|
||||
}
|
||||
|
||||
void PhotoOpenClickHandler::onClickImpl() const {
|
||||
if (_handler) {
|
||||
_handler();
|
||||
}
|
||||
}
|
||||
|
||||
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,53 +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:
|
||||
PhotoOpenClickHandler(not_null<PhotoData*> photo, Fn<void()> &&callback);
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
Fn<void()> _handler;
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -193,15 +193,6 @@ struct GameData;
|
||||
struct PollData;
|
||||
|
||||
class AudioMsgId;
|
||||
class PhotoClickHandler;
|
||||
class PhotoOpenClickHandler;
|
||||
class PhotoSaveClickHandler;
|
||||
class PhotoCancelClickHandler;
|
||||
class DocumentClickHandler;
|
||||
class DocumentSaveClickHandler;
|
||||
class DocumentCancelClickHandler;
|
||||
class DocumentWrappedClickHandler;
|
||||
class VoiceSeekClickHandler;
|
||||
|
||||
using PhotoId = uint64;
|
||||
using VideoId = uint64;
|
||||
@@ -360,33 +351,3 @@ inline bool operator!=(
|
||||
const MessageCursor &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
class FileClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
FileClickHandler(
|
||||
not_null<Main::Session*> session,
|
||||
FullMsgId context)
|
||||
: _session(session)
|
||||
, _context(context) {
|
||||
}
|
||||
|
||||
[[nodiscard]] Main::Session &session() const {
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void setMessageId(FullMsgId context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[[nodiscard]] FullMsgId context() const {
|
||||
return _context;
|
||||
}
|
||||
|
||||
protected:
|
||||
HistoryItem *getActionItem() const;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
FullMsgId _context;
|
||||
|
||||
};
|
||||
|
||||
@@ -2138,7 +2138,7 @@ void InnerWidget::peerSearchReceived(
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"user %1 was not loaded in InnerWidget::peopleReceived()"
|
||||
).arg(peer->id.value));
|
||||
).arg(peerFromMTP(mtpPeer).value));
|
||||
}
|
||||
}
|
||||
for (const auto &mtpPeer : result) {
|
||||
@@ -2153,7 +2153,7 @@ void InnerWidget::peerSearchReceived(
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"user %1 was not loaded in InnerWidget::peopleReceived()"
|
||||
).arg(peer->id.value));
|
||||
).arg(peerFromMTP(mtpPeer).value));
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
|
||||
@@ -266,7 +266,7 @@ Widget::Widget(
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateForwardBar();
|
||||
}, lifetime());
|
||||
@@ -1571,7 +1571,7 @@ void Widget::updateControlsGeometry() {
|
||||
auto smallLayoutWidth = (st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x());
|
||||
auto smallLayoutRatio = (width() < st::columnMinimalWidthLeft) ? (st::columnMinimalWidthLeft - width()) / float64(st::columnMinimalWidthLeft - smallLayoutWidth) : 0.;
|
||||
auto filterLeft = (controller()->filtersWidth() ? st::dialogsFilterSkip : st::dialogsFilterPadding.x() + _mainMenuToggle->width()) + st::dialogsFilterPadding.x();
|
||||
auto filterRight = (_lockUnlock->isVisible() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x();
|
||||
auto filterRight = (session().domain().local().hasLocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x();
|
||||
auto filterWidth = qMax(width(), st::columnMinimalWidthLeft) - filterLeft - filterRight;
|
||||
auto filterAreaHeight = st::topBarHeight;
|
||||
_searchControls->setGeometry(0, filterAreaTop, width(), filterAreaHeight);
|
||||
|
||||
@@ -153,11 +153,15 @@ PanelController::~PanelController() {
|
||||
if (_saveSettingsTimer.isActive()) {
|
||||
saveSettings();
|
||||
}
|
||||
_panel->destroyLayer();
|
||||
if (_panel) {
|
||||
_panel->destroyLayer();
|
||||
}
|
||||
}
|
||||
|
||||
void PanelController::activatePanel() {
|
||||
_panel->showAndActivate();
|
||||
if (_panel) {
|
||||
_panel->showAndActivate();
|
||||
}
|
||||
}
|
||||
|
||||
void PanelController::createPanel() {
|
||||
|
||||
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -615,6 +616,12 @@ void InnerWidget::elementOpenDocument(
|
||||
_controller->openDocument(document, context, showInMediaView);
|
||||
}
|
||||
|
||||
void InnerWidget::elementCancelUpload(const FullMsgId &context) {
|
||||
if (const auto item = session().data().message(context)) {
|
||||
_controller->cancelUploadLayer(item);
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) {
|
||||
|
||||
@@ -115,6 +115,7 @@ public:
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context,
|
||||
bool showInMediaView = false) override;
|
||||
void elementCancelUpload(const FullMsgId &context) override;
|
||||
void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) override;
|
||||
|
||||
@@ -298,10 +298,7 @@ Widget::Widget(
|
||||
|
||||
_fixedBarShadow->raise();
|
||||
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -2580,6 +2581,12 @@ void HistoryInner::elementOpenDocument(
|
||||
_controller->openDocument(document, context, showInMediaView);
|
||||
}
|
||||
|
||||
void HistoryInner::elementCancelUpload(const FullMsgId &context) {
|
||||
if (const auto item = session().data().message(context)) {
|
||||
_controller->cancelUploadLayer(item);
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryInner::elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) {
|
||||
@@ -3234,7 +3241,7 @@ void HistoryInner::deleteItem(FullMsgId itemId) {
|
||||
void HistoryInner::deleteItem(not_null<HistoryItem*> item) {
|
||||
if (auto message = item->toHistoryMessage()) {
|
||||
if (message->uploading()) {
|
||||
_controller->content()->cancelUploadLayer(item);
|
||||
_controller->cancelUploadLayer(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3476,6 +3483,11 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||
showInMediaView);
|
||||
}
|
||||
}
|
||||
void elementCancelUpload(const FullMsgId &context) override {
|
||||
if (Instance) {
|
||||
Instance->elementCancelUpload(context);
|
||||
}
|
||||
}
|
||||
void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) override {
|
||||
|
||||
@@ -95,6 +95,7 @@ public:
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context,
|
||||
bool showInMediaView = false);
|
||||
void elementCancelUpload(const FullMsgId &context);
|
||||
void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback);
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "facades.h"
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
struct WebPageData;
|
||||
class VoiceSeekClickHandler;
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
|
||||
@@ -413,7 +413,7 @@ HistoryWidget::HistoryWidget(
|
||||
Window::ActivateWindow(controller);
|
||||
});
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_history) {
|
||||
_history->forceFullResize();
|
||||
|
||||
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_groups.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "core/file_utilities.h"
|
||||
@@ -727,7 +728,7 @@ bool AddDeleteMessageAction(
|
||||
}
|
||||
if (const auto message = item->toHistoryMessage()) {
|
||||
if (message->uploading()) {
|
||||
controller->content()->cancelUploadLayer(item);
|
||||
controller->cancelUploadLayer(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ void SimpleElementDelegate::elementOpenDocument(
|
||||
bool showInMediaView) {
|
||||
}
|
||||
|
||||
void SimpleElementDelegate::elementCancelUpload(const FullMsgId &context) {
|
||||
}
|
||||
|
||||
void SimpleElementDelegate::elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) {
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context,
|
||||
bool showInMediaView = false) = 0;
|
||||
virtual void elementCancelUpload(const FullMsgId &context) = 0;
|
||||
virtual void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) = 0;
|
||||
@@ -111,6 +112,7 @@ public:
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context,
|
||||
bool showInMediaView = false) override;
|
||||
void elementCancelUpload(const FullMsgId &context) override;
|
||||
void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) override;
|
||||
|
||||
@@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
@@ -1313,6 +1314,12 @@ void ListWidget::elementOpenDocument(
|
||||
_controller->openDocument(document, context, showInMediaView);
|
||||
}
|
||||
|
||||
void ListWidget::elementCancelUpload(const FullMsgId &context) {
|
||||
if (const auto item = session().data().message(context)) {
|
||||
_controller->cancelUploadLayer(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) {
|
||||
|
||||
@@ -241,6 +241,7 @@ public:
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context,
|
||||
bool showInMediaView = false) override;
|
||||
void elementCancelUpload(const FullMsgId &context) override;
|
||||
void elementShowTooltip(
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> hiddenCallback) override;
|
||||
|
||||
@@ -128,10 +128,7 @@ PinnedWidget::PinnedWidget(
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBarShadow->raise();
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -193,10 +193,7 @@ RepliesWidget::RepliesWidget(
|
||||
_rootView->raise();
|
||||
_topBarShadow->raise();
|
||||
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -126,10 +126,7 @@ ScheduledWidget::ScheduledWidget(
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBarShadow->raise();
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
controller->adaptive().changed()
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -117,7 +117,7 @@ TopBarWidget::TopBarWidget(
|
||||
_search->setForceRippled(searchInActiveChat, animated);
|
||||
}, lifetime());
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateAdaptiveLayout();
|
||||
}, lifetime());
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
@@ -217,10 +218,15 @@ void Document::createComponents(bool caption) {
|
||||
_realParent->fullId());
|
||||
thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>(
|
||||
_data,
|
||||
crl::guard(this, [=](FullMsgId id) {
|
||||
_parent->delegate()->elementCancelUpload(id);
|
||||
}),
|
||||
_realParent->fullId());
|
||||
}
|
||||
if (const auto voice = Get<HistoryDocumentVoice>()) {
|
||||
voice->_seekl = std::make_shared<VoiceSeekClickHandler>(_data, [] {});
|
||||
voice->_seekl = std::make_shared<VoiceSeekClickHandler>(
|
||||
_data,
|
||||
[](FullMsgId) {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_session.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
bool File::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
|
||||
return p == _openl || p == _savel || p == _cancell;
|
||||
}
|
||||
bool File::dragItemByHandler(const ClickHandlerPtr &p) const {
|
||||
return p == _openl || p == _savel || p == _cancell;
|
||||
}
|
||||
|
||||
void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
if (p == _savel || p == _cancell) {
|
||||
if (active && !dataLoaded()) {
|
||||
@@ -40,7 +48,7 @@ void File::clickHandlerPressedChanged(
|
||||
}
|
||||
|
||||
void File::setLinks(
|
||||
ClickHandlerPtr &&openl,
|
||||
FileClickHandlerPtr &&openl,
|
||||
FileClickHandlerPtr &&savel,
|
||||
FileClickHandlerPtr &&cancell) {
|
||||
_openl = std::move(openl);
|
||||
@@ -50,6 +58,7 @@ void File::setLinks(
|
||||
|
||||
void File::refreshParentId(not_null<HistoryItem*> realParent) {
|
||||
const auto contextId = realParent->fullId();
|
||||
_openl->setMessageId(contextId);
|
||||
_savel->setMessageId(contextId);
|
||||
_cancell->setMessageId(contextId);
|
||||
}
|
||||
@@ -106,11 +115,17 @@ void File::setDocumentLinks(
|
||||
setLinks(
|
||||
std::make_shared<DocumentOpenClickHandler>(
|
||||
document,
|
||||
crl::guard(this, [=] {
|
||||
_parent->delegate()->elementOpenDocument(document, context);
|
||||
})),
|
||||
crl::guard(this, [=](FullMsgId id) {
|
||||
_parent->delegate()->elementOpenDocument(document, id);
|
||||
}),
|
||||
context),
|
||||
std::make_shared<DocumentSaveClickHandler>(document, context),
|
||||
std::make_shared<DocumentCancelClickHandler>(document, context));
|
||||
std::make_shared<DocumentCancelClickHandler>(
|
||||
document,
|
||||
crl::guard(this, [=](FullMsgId id) {
|
||||
_parent->delegate()->elementCancelUpload(id);
|
||||
}),
|
||||
context));
|
||||
}
|
||||
|
||||
File::~File() = default;
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
class FileClickHandler;
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class File : public Media, public base::has_weak_ptr {
|
||||
@@ -22,19 +24,17 @@ public:
|
||||
, _realParent(realParent) {
|
||||
}
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return p == _openl || p == _savel || p == _cancell;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return p == _openl || p == _savel || p == _cancell;
|
||||
}
|
||||
[[nodiscard]] bool toggleSelectionByHandlerClick(
|
||||
const ClickHandlerPtr &p) const override;
|
||||
[[nodiscard]] bool dragItemByHandler(
|
||||
const ClickHandlerPtr &p) const override;
|
||||
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
void refreshParentId(not_null<HistoryItem*> realParent) override;
|
||||
|
||||
bool allowsFastShare() const override {
|
||||
[[nodiscard]] bool allowsFastShare() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -44,11 +44,10 @@ protected:
|
||||
using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>;
|
||||
|
||||
not_null<HistoryItem*> _realParent;
|
||||
ClickHandlerPtr _openl;
|
||||
FileClickHandlerPtr _savel, _cancell;
|
||||
FileClickHandlerPtr _openl, _savel, _cancell;
|
||||
|
||||
void setLinks(
|
||||
ClickHandlerPtr &&openl,
|
||||
FileClickHandlerPtr &&openl,
|
||||
FileClickHandlerPtr &&savel,
|
||||
FileClickHandlerPtr &&cancell);
|
||||
void setDocumentLinks(
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "layout.h" // FullSelection
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_auto_download.h"
|
||||
#include "core/application.h"
|
||||
@@ -86,11 +87,17 @@ Photo::~Photo() {
|
||||
|
||||
void Photo::create(FullMsgId contextId, PeerData *chat) {
|
||||
setLinks(
|
||||
std::make_shared<PhotoOpenClickHandler>(_data, crl::guard(this, [=] {
|
||||
showPhoto();
|
||||
})),
|
||||
std::make_shared<PhotoOpenClickHandler>(
|
||||
_data,
|
||||
crl::guard(this, [=](FullMsgId id) { showPhoto(id); }),
|
||||
contextId),
|
||||
std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat),
|
||||
std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat));
|
||||
std::make_shared<PhotoCancelClickHandler>(
|
||||
_data,
|
||||
crl::guard(this, [=](FullMsgId id) {
|
||||
_parent->delegate()->elementCancelUpload(id);
|
||||
}),
|
||||
contextId));
|
||||
if ((_dataMedia = _data->activeMediaView())) {
|
||||
dataMediaCreated();
|
||||
} else if (_data->inlineThumbnailBytes().isEmpty()
|
||||
@@ -773,7 +780,7 @@ void Photo::playAnimation(bool autoplay) {
|
||||
if (_streamed && autoplay) {
|
||||
return;
|
||||
} else if (_streamed && videoAutoplayEnabled()) {
|
||||
showPhoto();
|
||||
showPhoto(_parent->data()->fullId());
|
||||
return;
|
||||
}
|
||||
if (_streamed) {
|
||||
@@ -846,10 +853,8 @@ void Photo::parentTextUpdated() {
|
||||
history()->owner().requestViewResize(_parent);
|
||||
}
|
||||
|
||||
void Photo::showPhoto() {
|
||||
_parent->delegate()->elementOpenPhoto(
|
||||
_data,
|
||||
_parent->data()->fullId());
|
||||
void Photo::showPhoto(FullMsgId id) {
|
||||
_parent->delegate()->elementOpenPhoto(_data, id);
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
||||
@@ -102,7 +102,7 @@ protected:
|
||||
private:
|
||||
struct Streamed;
|
||||
|
||||
void showPhoto();
|
||||
void showPhoto(FullMsgId id);
|
||||
|
||||
void create(FullMsgId contextId, PeerData *chat = nullptr);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.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 "lottie/lottie_single_player.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
@@ -286,11 +287,10 @@ void Sticker::refreshLink() {
|
||||
// .webp image and we allow to open it in media viewer.
|
||||
_link = std::make_shared<DocumentOpenClickHandler>(
|
||||
_data,
|
||||
crl::guard(this, [=] {
|
||||
_parent->delegate()->elementOpenDocument(
|
||||
_data,
|
||||
_parent->data()->fullId());
|
||||
}));
|
||||
crl::guard(this, [=](FullMsgId id) {
|
||||
_parent->delegate()->elementOpenDocument(_data, id);
|
||||
}),
|
||||
_parent->data()->fullId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "ui/text/format_values.h"
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document_media.h"
|
||||
@@ -342,9 +343,9 @@ Media::View::OpenRequest Result::openRequest() {
|
||||
|
||||
void Result::cancelFile() {
|
||||
if (_document) {
|
||||
DocumentCancelClickHandler(_document).onClick({});
|
||||
DocumentCancelClickHandler(_document, nullptr).onClick({});
|
||||
} else if (_photo) {
|
||||
PhotoCancelClickHandler(_photo).onClick({});
|
||||
PhotoCancelClickHandler(_photo, nullptr).onClick({});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/focus_persister.h"
|
||||
@@ -68,7 +68,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "mainwindow.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
@@ -381,7 +380,7 @@ MainWidget::MainWidget(
|
||||
}
|
||||
});
|
||||
|
||||
_controller->adaptive().changed(
|
||||
_controller->adaptive().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
handleAdaptiveLayoutUpdate();
|
||||
}, lifetime());
|
||||
@@ -750,36 +749,6 @@ void MainWidget::showSendPathsLayer() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::cancelUploadLayer(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
session().uploader().pause(itemId);
|
||||
const auto stopUpload = [=] {
|
||||
Ui::hideLayer();
|
||||
auto &data = session().data();
|
||||
if (const auto item = data.message(itemId)) {
|
||||
if (!item->isEditingMedia()) {
|
||||
const auto history = item->history();
|
||||
item->destroy();
|
||||
history->requestChatListMessage();
|
||||
} else {
|
||||
item->returnSavedMedia();
|
||||
session().uploader().cancel(item->fullId());
|
||||
}
|
||||
data.sendHistoryChangeNotifications();
|
||||
}
|
||||
session().uploader().unpause();
|
||||
};
|
||||
const auto continueUpload = [=] {
|
||||
session().uploader().unpause();
|
||||
};
|
||||
Ui::show(Box<ConfirmBox>(
|
||||
tr::lng_selected_cancel_sure_this(tr::now),
|
||||
tr::lng_selected_upload_stop(tr::now),
|
||||
tr::lng_continue(tr::now),
|
||||
stopUpload,
|
||||
continueUpload));
|
||||
}
|
||||
|
||||
void MainWidget::deletePhotoLayer(PhotoData *photo) {
|
||||
if (!photo) return;
|
||||
Ui::show(Box<ConfirmBox>(tr::lng_delete_photo_sure(tr::now), tr::lng_box_delete(tr::now), crl::guard(this, [=] {
|
||||
@@ -1454,6 +1423,9 @@ void MainWidget::showChooseReportMessages(
|
||||
peer->id,
|
||||
SectionShow::Way::Forward,
|
||||
ShowForChooseMessagesMsgId);
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_report_please_select_messages(tr::now) },
|
||||
});
|
||||
}
|
||||
|
||||
void MainWidget::clearChooseReportMessages() {
|
||||
|
||||
@@ -160,7 +160,6 @@ public:
|
||||
|
||||
void showForwardLayer(MessageIdsList &&items);
|
||||
void showSendPathsLayer();
|
||||
void cancelUploadLayer(not_null<HistoryItem*> item);
|
||||
void shareUrlLayer(const QString &url, const QString &text);
|
||||
void inlineSwitchLayer(const QString &botAndQuery);
|
||||
void hiderLayer(base::unique_qptr<Window::HistoryHider> h);
|
||||
|
||||
@@ -945,7 +945,7 @@ void MainWindow::sendPaths() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateIsActiveHook() {
|
||||
void MainWindow::activeChangedHook() {
|
||||
if (const auto controller = sessionController()) {
|
||||
controller->session().updates().updateOnline();
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ protected:
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
|
||||
void initHook() override;
|
||||
void updateIsActiveHook() override;
|
||||
void activeChangedHook() override;
|
||||
void clearWidgetsHook() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "media/audio/media_audio_capture.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ffmpeg/ffmpeg_utility.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "base/concurrent_timer.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
namespace Media {
|
||||
namespace Streaming {
|
||||
@@ -288,6 +289,7 @@ void VideoTrackObject::readFrames() {
|
||||
}
|
||||
}, [&](Shared::PrepareNextCheck delay) {
|
||||
Expects(delay == kTimeUnknown || delay > 0);
|
||||
|
||||
if (delay != kTimeUnknown) {
|
||||
queueReadFrames(delay);
|
||||
}
|
||||
@@ -303,7 +305,8 @@ auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
|
||||
-> ReadEnoughState {
|
||||
const auto dropStaleFrames = !_options.waitForMarkAsShown;
|
||||
const auto state = _shared->prepareState(trackTime, dropStaleFrames);
|
||||
return v::match(state, [&](Shared::PrepareFrame frame) -> ReadEnoughState {
|
||||
return v::match(state, [&](Shared::PrepareFrame frame)
|
||||
-> ReadEnoughState {
|
||||
while (true) {
|
||||
const auto result = readFrame(frame);
|
||||
if (result != FrameResult::Done) {
|
||||
@@ -314,6 +317,8 @@ auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
|
||||
}
|
||||
}
|
||||
}, [&](Shared::PrepareNextCheck delay) -> ReadEnoughState {
|
||||
Expects(delay == kTimeUnknown || delay > 0); // Debugging crash.
|
||||
|
||||
return delay;
|
||||
}, [&](v::null_t) -> ReadEnoughState {
|
||||
return FrameResult::Done;
|
||||
@@ -752,6 +757,16 @@ auto VideoTrack::Shared::prepareState(
|
||||
next->displayed = kDisplaySkipped;
|
||||
return next;
|
||||
} else {
|
||||
if (frame->position - trackTime + 1 <= 0) { // Debugging crash.
|
||||
CrashReports::SetAnnotation(
|
||||
"DelayValues",
|
||||
(QString::number(frame->position)
|
||||
+ " + 1 <= "
|
||||
+ QString::number(trackTime)));
|
||||
}
|
||||
Assert(frame->position >= trackTime);
|
||||
Assert(frame->position - trackTime + 1 > 0);
|
||||
|
||||
return PrepareNextCheck(frame->position - trackTime + 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "window/themes/window_theme_preview.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
@@ -1426,12 +1427,15 @@ void OverlayWidget::subscribeToScreenGeometry() {
|
||||
}
|
||||
|
||||
void OverlayWidget::toMessage() {
|
||||
if (!_session || !_controller) {
|
||||
if (!_session) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto item = _session->data().message(_msgid)) {
|
||||
close();
|
||||
_controller->showPeerHistoryAtItem(item);
|
||||
if (const auto window = findWindow()) {
|
||||
window->showPeerHistoryAtItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1555,11 +1559,8 @@ void OverlayWidget::handleDocumentClick() {
|
||||
if (_document->loading()) {
|
||||
saveCancel();
|
||||
} else {
|
||||
if (!_controller) {
|
||||
return;
|
||||
}
|
||||
Data::ResolveDocument(
|
||||
_controller,
|
||||
findWindow(),
|
||||
_document,
|
||||
_document->owner().message(_msgid));
|
||||
if (_document->loading() && !_radial.animating()) {
|
||||
@@ -2250,10 +2251,6 @@ void OverlayWidget::activate() {
|
||||
}
|
||||
|
||||
void OverlayWidget::show(OpenRequest request) {
|
||||
if (!request.controller()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto document = request.document();
|
||||
const auto photo = request.photo();
|
||||
const auto contextItem = request.item();
|
||||
@@ -2263,8 +2260,6 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
return;
|
||||
}
|
||||
setSession(&photo->session());
|
||||
_controller = request.controller();
|
||||
Assert(_session == (&_controller->session()));
|
||||
|
||||
if (contextPeer) {
|
||||
setContext(contextPeer);
|
||||
@@ -2275,9 +2270,7 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
}
|
||||
|
||||
clearControlsState();
|
||||
if (contextPeer) {
|
||||
_firstOpenedPeerPhoto = true;
|
||||
}
|
||||
_firstOpenedPeerPhoto = (contextPeer != nullptr);
|
||||
assignMediaPointer(photo);
|
||||
|
||||
displayPhoto(photo, contextPeer ? nullptr : contextItem);
|
||||
@@ -2285,8 +2278,6 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
activateControls();
|
||||
} else if (document) {
|
||||
setSession(&document->session());
|
||||
_controller = request.controller();
|
||||
Assert(_session == (&_controller->session()));
|
||||
|
||||
if (contextItem) {
|
||||
setContext(contextItem);
|
||||
@@ -2303,12 +2294,15 @@ void OverlayWidget::show(OpenRequest request) {
|
||||
request.cloudTheme()
|
||||
? *request.cloudTheme()
|
||||
: Data::CloudTheme(),
|
||||
false);
|
||||
request.continueStreaming());
|
||||
if (!isHidden()) {
|
||||
preloadData(0);
|
||||
activateControls();
|
||||
}
|
||||
}
|
||||
if (const auto controller = request.controller()) {
|
||||
_window = base::make_weak(&controller->window());
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
||||
@@ -3098,14 +3092,13 @@ float64 OverlayWidget::playbackControlsCurrentSpeed() {
|
||||
void OverlayWidget::switchToPip() {
|
||||
Expects(_streamed != nullptr);
|
||||
Expects(_document != nullptr);
|
||||
Expects(_controller != nullptr);
|
||||
|
||||
const auto document = _document;
|
||||
const auto msgId = _msgid;
|
||||
const auto closeAndContinue = [=] {
|
||||
_showAsPip = false;
|
||||
show(OpenRequest(
|
||||
_controller,
|
||||
findWindow(),
|
||||
document,
|
||||
document->owner().message(msgId),
|
||||
true));
|
||||
@@ -4539,6 +4532,36 @@ void OverlayWidget::applyHideWindowWorkaround() {
|
||||
}
|
||||
}
|
||||
|
||||
Window::SessionController *OverlayWidget::findWindow() const {
|
||||
if (!_session) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto window = _window.get();
|
||||
if (window) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
if (&controller->session() == _session) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto &active = _session->windows();
|
||||
if (!active.empty()) {
|
||||
return active.front();
|
||||
} else if (window) {
|
||||
Window::SessionController *controllerPtr = nullptr;
|
||||
window->invokeForSessionController(
|
||||
&_session->account(),
|
||||
[&](not_null<Window::SessionController*> newController) {
|
||||
controllerPtr = newController;
|
||||
});
|
||||
return controllerPtr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// #TODO unite and check
|
||||
void OverlayWidget::clearBeforeHide() {
|
||||
_sharedMedia = nullptr;
|
||||
|
||||
@@ -411,11 +411,13 @@ private:
|
||||
|
||||
void applyHideWindowWorkaround();
|
||||
|
||||
Window::SessionController *findWindow() const;
|
||||
|
||||
bool _opengl = false;
|
||||
const std::unique_ptr<Ui::RpWidgetWrap> _surface;
|
||||
const not_null<QWidget*> _widget;
|
||||
|
||||
Window::SessionController *_controller = nullptr;
|
||||
base::weak_ptr<Window::Controller> _window;
|
||||
Main::Session *_session = nullptr;
|
||||
rpl::lifetime _sessionLifetime;
|
||||
PhotoData *_photo = nullptr;
|
||||
|
||||
@@ -1009,6 +1009,9 @@ void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
|
||||
}
|
||||
|
||||
void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
|
||||
Expects(1 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
const auto weak = Ui::MakeWeak(_panel.widget());
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (weak) {
|
||||
@@ -1019,10 +1022,22 @@ void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
|
||||
return;
|
||||
}
|
||||
seekUpdate(position);
|
||||
|
||||
Assert(2 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
volumeControllerUpdate(position);
|
||||
|
||||
Assert(3 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
const auto pressed = base::take(_pressed);
|
||||
if (pressed && *pressed == OverState::Playback) {
|
||||
_panel.setDragDisabled(false);
|
||||
|
||||
Assert(4 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
seekFinish(_playbackProgress->value());
|
||||
} else if (pressed && *pressed == OverState::VolumeController) {
|
||||
_panel.setDragDisabled(false);
|
||||
@@ -1083,6 +1098,9 @@ void Pip::seekProgress(float64 value) {
|
||||
}
|
||||
|
||||
void Pip::seekFinish(float64 value) {
|
||||
Expects(5 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
if (!_lastDurationMs) {
|
||||
return;
|
||||
}
|
||||
@@ -1556,15 +1574,30 @@ void Pip::playbackPauseResume() {
|
||||
}
|
||||
|
||||
void Pip::restartAtSeekPosition(crl::time position) {
|
||||
Expects(6 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
if (!_instance.info().video.cover.isNull()) {
|
||||
_preparedCoverStorage = QImage();
|
||||
_preparedCoverState = ThumbState::Empty;
|
||||
_instance.saveFrameToCover();
|
||||
}
|
||||
|
||||
Assert(7 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
auto options = Streaming::PlaybackOptions();
|
||||
options.position = position;
|
||||
options.audioId = _instance.player().prepareLegacyState().id;
|
||||
|
||||
Assert(8 && _delegate->pipPlaybackSpeed() >= 0.5
|
||||
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
|
||||
|
||||
options.speed = _delegate->pipPlaybackSpeed();
|
||||
|
||||
Assert(9 && options.speed >= 0.5
|
||||
&& options.speed <= 2.); // Debugging strange crash.
|
||||
|
||||
_instance.play(options);
|
||||
if (_startPaused) {
|
||||
_instance.pause();
|
||||
|
||||
@@ -189,9 +189,7 @@ ConnectionPointer AbstractConnection::Create(
|
||||
}
|
||||
|
||||
uint32 AbstractConnection::extendedNotSecurePadding() const {
|
||||
return requiresExtendedPadding()
|
||||
? uint32(openssl::RandomValue<uchar>() & 0x3F)
|
||||
: 0;
|
||||
return uint32(openssl::RandomValue<uchar>() & 0x3F);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
@@ -92,9 +92,6 @@ public:
|
||||
[[nodiscard]] virtual bool needHttpWait() {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool requiresExtendedPadding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual int32 debugState() const = 0;
|
||||
|
||||
|
||||
@@ -198,12 +198,6 @@ void ResolvingConnection::sendData(mtpBuffer &&buffer) {
|
||||
_child->sendData(std::move(buffer));
|
||||
}
|
||||
|
||||
bool ResolvingConnection::requiresExtendedPadding() const {
|
||||
Expects(_child != nullptr);
|
||||
|
||||
return _child->requiresExtendedPadding();
|
||||
}
|
||||
|
||||
void ResolvingConnection::disconnectFromServer() {
|
||||
_address = QString();
|
||||
_port = 0;
|
||||
|
||||
@@ -35,7 +35,6 @@ public:
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) override;
|
||||
bool isConnected() const override;
|
||||
bool requiresExtendedPadding() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ public:
|
||||
virtual uint32 id() const = 0;
|
||||
virtual bool supportsArbitraryLength() const = 0;
|
||||
|
||||
virtual bool requiresExtendedPadding() const = 0;
|
||||
virtual void prepareKey(bytes::span key, bytes::const_span source) = 0;
|
||||
virtual bytes::span finalizePacket(mtpBuffer &buffer) = 0;
|
||||
|
||||
@@ -58,7 +57,6 @@ public:
|
||||
uint32 id() const override;
|
||||
bool supportsArbitraryLength() const override;
|
||||
|
||||
bool requiresExtendedPadding() const override;
|
||||
void prepareKey(bytes::span key, bytes::const_span source) override;
|
||||
bytes::span finalizePacket(mtpBuffer &buffer) override;
|
||||
|
||||
@@ -75,10 +73,6 @@ bool TcpConnection::Protocol::Version0::supportsArbitraryLength() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TcpConnection::Protocol::Version0::requiresExtendedPadding() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void TcpConnection::Protocol::Version0::prepareKey(
|
||||
bytes::span key,
|
||||
bytes::const_span source) {
|
||||
@@ -142,7 +136,6 @@ class TcpConnection::Protocol::Version1 : public Version0 {
|
||||
public:
|
||||
explicit Version1(bytes::vector &&secret);
|
||||
|
||||
bool requiresExtendedPadding() const override;
|
||||
void prepareKey(bytes::span key, bytes::const_span source) override;
|
||||
|
||||
private:
|
||||
@@ -154,10 +147,6 @@ TcpConnection::Protocol::Version1::Version1(bytes::vector &&secret)
|
||||
: _secret(std::move(secret)) {
|
||||
}
|
||||
|
||||
bool TcpConnection::Protocol::Version1::requiresExtendedPadding() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TcpConnection::Protocol::Version1::prepareKey(
|
||||
bytes::span key,
|
||||
bytes::const_span source) {
|
||||
@@ -425,12 +414,6 @@ void TcpConnection::socketDisconnected() {
|
||||
}
|
||||
}
|
||||
|
||||
bool TcpConnection::requiresExtendedPadding() const {
|
||||
Expects(_protocol != nullptr);
|
||||
|
||||
return _protocol->requiresExtendedPadding();
|
||||
}
|
||||
|
||||
void TcpConnection::sendData(mtpBuffer &&buffer) {
|
||||
Expects(buffer.size() > 2);
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ public:
|
||||
bool protocolForFiles) override;
|
||||
void timedOut() override;
|
||||
bool isConnected() const override;
|
||||
bool requiresExtendedPadding() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace {
|
||||
auto serialized = SerializedRequest::Serialize(data);
|
||||
serialized.setMsgId(realMsgId);
|
||||
serialized.setSeqNo(0);
|
||||
serialized.addPadding(false, true);
|
||||
serialized.addPadding(true);
|
||||
|
||||
constexpr auto kMsgIdPosition = SerializedRequest::kMessageIdPosition;
|
||||
constexpr auto kMinMessageSize = 5;
|
||||
|
||||
@@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
uint32 CountPaddingPrimesCount(uint32 requestSize, bool extended, bool old) {
|
||||
if (old) {
|
||||
uint32 CountPaddingPrimesCount(
|
||||
uint32 requestSize,
|
||||
bool forAuthKeyInner) {
|
||||
if (forAuthKeyInner) {
|
||||
return ((8 + requestSize) & 0x03)
|
||||
? (4 - ((8 + requestSize) & 0x03))
|
||||
: 0;
|
||||
@@ -27,12 +29,8 @@ uint32 CountPaddingPrimesCount(uint32 requestSize, bool extended, bool old) {
|
||||
result += 4;
|
||||
}
|
||||
|
||||
if (extended) {
|
||||
// Some more random padding.
|
||||
result += ((openssl::RandomValue<uchar>() & 0x0F) << 2);
|
||||
}
|
||||
|
||||
return result;
|
||||
// Some more random padding.
|
||||
return result + ((openssl::RandomValue<uchar>() & 0x0F) << 2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -100,12 +98,14 @@ uint32 SerializedRequest::getSeqNo() const {
|
||||
return uint32((*_data)[kSeqNoPosition]);
|
||||
}
|
||||
|
||||
void SerializedRequest::addPadding(bool extended, bool old) {
|
||||
void SerializedRequest::addPadding(bool forAuthKeyInner) {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
const auto requestSize = (tl::count_length(*this) >> 2);
|
||||
const auto padding = CountPaddingPrimesCount(requestSize, extended, old);
|
||||
const auto padding = CountPaddingPrimesCount(
|
||||
requestSize,
|
||||
forAuthKeyInner);
|
||||
const auto fullSize = kMessageBodyPosition + requestSize + padding;
|
||||
if (uint32(_data->size()) != fullSize) {
|
||||
_data->resize(fullSize);
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
void setSeqNo(uint32 seqNo);
|
||||
[[nodiscard]] uint32 getSeqNo() const;
|
||||
|
||||
void addPadding(bool extended, bool old);
|
||||
void addPadding(bool forAuthKeyInner);
|
||||
[[nodiscard]] uint32 messageSize() const;
|
||||
|
||||
[[nodiscard]] bool needAck() const;
|
||||
|
||||
@@ -1263,17 +1263,16 @@ void SessionPrivate::handleReceived() {
|
||||
return restart();
|
||||
}
|
||||
|
||||
constexpr auto kMinPaddingSize = 12U;
|
||||
constexpr auto kMaxPaddingSize = 1024U;
|
||||
|
||||
auto encryptedInts = ints + kExternalHeaderIntsCount;
|
||||
auto encryptedIntsCount = (intsCount - kExternalHeaderIntsCount) & ~0x03U;
|
||||
auto encryptedBytesCount = encryptedIntsCount * kIntSize;
|
||||
auto decryptedBuffer = QByteArray(encryptedBytesCount, Qt::Uninitialized);
|
||||
auto msgKey = *(MTPint128*)(ints + 2);
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
aesIgeDecrypt_oldmtp(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _encryptionKey, msgKey);
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
aesIgeDecrypt(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _encryptionKey, msgKey);
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
|
||||
auto decryptedInts = reinterpret_cast<const mtpPrime*>(decryptedBuffer.constData());
|
||||
auto serverSalt = *(uint64*)&decryptedInts[0];
|
||||
@@ -1283,31 +1282,10 @@ void SessionPrivate::handleReceived() {
|
||||
auto needAck = ((seqNo & 0x01) != 0);
|
||||
auto messageLength = *(uint32*)&decryptedInts[7];
|
||||
auto fullDataLength = kEncryptedHeaderIntsCount * kIntSize + messageLength; // Without padding.
|
||||
auto badMessageLength = (messageLength > kMaxMessageLength);
|
||||
|
||||
// Can underflow, but it is an unsigned type, so we just check the range later.
|
||||
auto paddingSize = static_cast<uint32>(encryptedBytesCount) - static_cast<uint32>(fullDataLength);
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
constexpr auto kMinPaddingSize_oldmtp = 0U;
|
||||
constexpr auto kMaxPaddingSize_oldmtp = 15U;
|
||||
badMessageLength |= (/*paddingSize < kMinPaddingSize_oldmtp || */paddingSize > kMaxPaddingSize_oldmtp);
|
||||
|
||||
auto hashedDataLength = badMessageLength ? encryptedBytesCount : fullDataLength;
|
||||
auto sha1ForMsgKeyCheck = hashSha1(decryptedInts, hashedDataLength);
|
||||
|
||||
constexpr auto kMsgKeyShift_oldmtp = 4U;
|
||||
if (ConstTimeIsDifferent(&msgKey, sha1ForMsgKeyCheck.data() + kMsgKeyShift_oldmtp, sizeof(msgKey))) {
|
||||
LOG(("TCP Error: bad SHA1 hash after aesDecrypt in message."));
|
||||
TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str()));
|
||||
|
||||
return restart();
|
||||
}
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
constexpr auto kMinPaddingSize = 12U;
|
||||
constexpr auto kMaxPaddingSize = 1024U;
|
||||
badMessageLength |= (paddingSize < kMinPaddingSize || paddingSize > kMaxPaddingSize);
|
||||
|
||||
std::array<uchar, 32> sha256Buffer = { { 0 } };
|
||||
|
||||
SHA256_CTX msgKeyLargeContext;
|
||||
@@ -1323,9 +1301,11 @@ void SessionPrivate::handleReceived() {
|
||||
|
||||
return restart();
|
||||
}
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
|
||||
if (badMessageLength || (messageLength & 0x03)) {
|
||||
if ((messageLength > kMaxMessageLength)
|
||||
|| (messageLength & 0x03)
|
||||
|| (paddingSize < kMinPaddingSize)
|
||||
|| (paddingSize > kMaxPaddingSize)) {
|
||||
LOG(("TCP Error: bad msg_len received %1, data size: %2").arg(messageLength).arg(encryptedBytesCount));
|
||||
TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str()));
|
||||
|
||||
@@ -2616,12 +2596,7 @@ void SessionPrivate::destroyTemporaryKey() {
|
||||
bool SessionPrivate::sendSecureRequest(
|
||||
SerializedRequest &&request,
|
||||
bool needAnyResponse) {
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
const auto oldPadding = true;
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
const auto oldPadding = false;
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
request.addPadding(_connection->requiresExtendedPadding(), oldPadding);
|
||||
request.addPadding(false);
|
||||
|
||||
uint32 fullSize = request->size();
|
||||
if (fullSize < 9) {
|
||||
@@ -2643,27 +2618,6 @@ bool SessionPrivate::sendSecureRequest(
|
||||
).arg(getProtocolDcId()
|
||||
).arg(_encryptionKey->keyId()));
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
uint32 padding = fullSize - 4 - messageSize;
|
||||
|
||||
uchar encryptedSHA[20];
|
||||
MTPint128 &msgKey(*(MTPint128*)(encryptedSHA + 4));
|
||||
hashSha1(
|
||||
request->constData(),
|
||||
(fullSize - padding) * sizeof(mtpPrime),
|
||||
encryptedSHA);
|
||||
|
||||
auto packet = _connection->prepareSecurePacket(_keyId, msgKey, fullSize);
|
||||
const auto prefix = packet.size();
|
||||
packet.resize(prefix + fullSize);
|
||||
|
||||
aesIgeEncrypt_oldmtp(
|
||||
request->constData(),
|
||||
&packet[prefix],
|
||||
fullSize * sizeof(mtpPrime),
|
||||
_encryptionKey,
|
||||
msgKey);
|
||||
#else // TDESKTOP_MTPROTO_OLD
|
||||
uchar encryptedSHA256[32];
|
||||
MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8));
|
||||
|
||||
@@ -2683,7 +2637,6 @@ bool SessionPrivate::sendSecureRequest(
|
||||
fullSize * sizeof(mtpPrime),
|
||||
_encryptionKey,
|
||||
msgKey);
|
||||
#endif // TDESKTOP_MTPROTO_OLD
|
||||
|
||||
DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5]));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user