diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 9a5e9584f8..138cc0e611 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1251,7 +1251,7 @@ groupCallTooltip: Tooltip(defaultTooltip) { } groupCallNiceTooltip: ImportantTooltip(defaultImportantTooltip) { bg: importantTooltipBg; - padding: margins(10px, 1px, 10px, 3px); + padding: margins(10px, 3px, 10px, 5px); radius: 4px; arrow: 4px; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index d4c0c0f353..dd9a947ef1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -68,4 +68,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; + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 7bd3589b54..23ca8847f8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #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 +72,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,6 +89,49 @@ struct Panel::ControlsBackgroundNarrow { Ui::RpWidget blocker; }; +class Panel::MicLevelTester final { +public: + explicit MicLevelTester(Fn show); + + [[nodiscard]] bool showTooltip() const; + +private: + void check(); + + Fn _show; + base::Timer _timer; + Webrtc::AudioInputTester _tester; + int _loudCount = 0; + int _quietCount = 0; + +}; + +Panel::MicLevelTester::MicLevelTester(Fn 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 call) : _call(call) , _peer(call->peer()) @@ -112,6 +160,7 @@ Panel::Panel(not_null call) : Ui::CallMuteButtonType::ScheduledSilent), })) , _hangup(widget(), st::groupCallHangup) +, _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips()) , _toasts(std::make_unique(this)) { _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); _layerBg->setHideByBackgroundClick(true); @@ -418,7 +467,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) @@ -515,11 +566,6 @@ void Panel::refreshVideoButtons(std::optional overrideWideMode) { } _video->setProgress(sharing ? 1. : 0.); }, _video->lifetime()); - _call->mutedValue( - ) | rpl::start_with_next([=] { - updateButtonsGeometry(); - showStickedTooltip(); - }, _video->lifetime()); } if (!_screenShare) { _screenShare.create(widget(), st::groupCallScreenShareSmall); @@ -562,7 +608,8 @@ void Panel::hideStickedTooltip( if (hide != StickedTooltipHide::Unavailable) { _stickedTooltipsShown |= type; if (hide == StickedTooltipHide::Discarded) { - // #TODO calls save to settings. + Core::App().settings().setHiddenGroupCallTooltip(type); + Core::App().saveSettingsDelayed(); } } const auto control = (type == StickedTooltip::Camera) @@ -886,6 +933,9 @@ void Panel::raiseControls() { } } _mute->raise(); + if (_niceTooltip) { + _niceTooltip->raise(); + } } void Panel::setupVideo(not_null viewport) { @@ -1052,6 +1102,18 @@ void Panel::subscribeToChanges(not_null real) { refreshTopButton(); }, widget()->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(); + }, widget()->lifetime()); + updateControlsGeometry(); } @@ -1407,7 +1469,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 @@ -1631,7 +1696,10 @@ void Panel::trackControlOver(not_null control, bool over) { 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() @@ -1645,13 +1713,25 @@ void Panel::showStickedTooltip() { StickedTooltipHide::Unavailable); if (!(_stickedTooltipsShown & StickedTooltip::Microphone) - && (_mode.current() == PanelMode::Wide) + && callReady && _mute - && !_call->mutedByAdmin() - && false) { // Check if there is incoming sound. - showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked); + && !_call->mutedByAdmin()) { + if (_stickedTooltipClose) { + // Showing already. + return; + } else if (!_micLevelTester) { + // Check if there is incoming sound. + _micLevelTester = std::make_unique([=] { + showStickedTooltip(); + }); + } + if (_micLevelTester->showTooltip()) { + _micLevelTester = nullptr; + showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked); + } return; } + _micLevelTester = nullptr; hideStickedTooltip( StickedTooltip::Microphone, StickedTooltipHide::Unavailable); @@ -1685,11 +1765,12 @@ void Panel::showNiceTooltip( } return rpl::producer(); }(); - if (!text - || _wideControlsAnimation.animating() - || !_wideControlsShown - || _stickedTooltipClose) { + 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); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 4e2a7cc49b..ddc053bfba 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -11,6 +11,7 @@ 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" @@ -64,6 +65,7 @@ class Toasts; class Members; class Viewport; enum class PanelMode; +enum class StickedTooltip; class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate { public: @@ -88,19 +90,12 @@ private: Normal, Sticked, }; - enum class StickedTooltip { - Camera = 0x01, - Microphone = 0x02, - }; - friend constexpr inline bool is_flag_type(StickedTooltip) { - return true; - }; - using StickedTooltips = base::flags; enum class StickedTooltipHide { Unavailable, Activated, Discarded, }; + class MicLevelTester; std::unique_ptr createWindow(); [[nodiscard]] not_null widget() const; @@ -236,6 +231,8 @@ private: const std::unique_ptr _toasts; base::weak_ptr _lastToast; + std::unique_ptr _micLevelTester; + rpl::lifetime _peerLifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 2253df03df..66087e3095 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -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( diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index e5a1d2ef79..f17c2c6a66 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -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(_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(); diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index de6707e236..1eedd75923 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -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 { @@ -574,6 +579,13 @@ public: _disableOpenGL = value; } + [[nodiscard]] base::flags 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 +683,7 @@ private: WindowPosition _windowPosition; // per-window bool _disableOpenGL = false; rpl::variable _workMode = WorkMode::WindowAndTray; + base::flags _hiddenGroupCallTooltips; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window