Add push-to-talk with global shortcut on Windows.

This commit is contained in:
John Preston 2020-12-03 21:17:15 +03:00
parent f4dfd738ec
commit d41e93fb1c
12 changed files with 217 additions and 21 deletions

View File

@ -1836,6 +1836,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_new_muted" = "Mute new members"; "lng_group_call_new_muted" = "Mute new members";
"lng_group_call_speakers" = "Speakers"; "lng_group_call_speakers" = "Speakers";
"lng_group_call_microphone" = "Microphone"; "lng_group_call_microphone" = "Microphone";
"lng_group_call_push_to_talk" = "Push to Talk";
"lng_group_call_ptt_shortcut" = "Edit Shortcut";
"lng_group_call_ptt_recording" = "Stop Recording";
"lng_group_call_share" = "Share Invite Link"; "lng_group_call_share" = "Share Invite Link";
"lng_group_call_end" = "End Voice Chat"; "lng_group_call_end" = "End Voice Chat";
"lng_group_call_join" = "Join"; "lng_group_call_join" = "Join";

View File

@ -641,20 +641,22 @@ groupCallCheckbox: Checkbox(defaultBoxCheckbox) {
rippleBgActive: groupCallMembersBgRipple; rippleBgActive: groupCallMembersBgRipple;
} }
groupCallSettingsButton: SettingsButton { groupCallSettingsToggle: Toggle(defaultToggle) {
toggledBg: groupCallMembersBg;
toggledFg: groupCallActiveFg;
untoggledBg: groupCallMembersBg;
untoggledFg: groupCallMemberNotJoinedStatus;
}
groupCallSettingsButton: SettingsButton(defaultSettingsButton) {
textFg: groupCallMembersFg; textFg: groupCallMembersFg;
textFgOver: groupCallMembersFg; textFgOver: groupCallMembersFg;
textBg: groupCallMembersBg; textBg: groupCallMembersBg;
textBgOver: groupCallMembersBgOver; textBgOver: groupCallMembersBgOver;
font: boxTextFont;
rightLabel: FlatLabel(defaultSettingsRightLabel) { rightLabel: FlatLabel(defaultSettingsRightLabel) {
textFg: groupCallActiveFg; textFg: groupCallActiveFg;
} }
toggle: groupCallSettingsToggle;
height: 20px; toggleOver: groupCallSettingsToggle;
padding: margins(22px, 10px, 22px, 8px);
ripple: groupCallRipple; ripple: groupCallRipple;
} }
groupCallSettingsAttentionButton: SettingsButton(groupCallSettingsButton) { groupCallSettingsAttentionButton: SettingsButton(groupCallSettingsButton) {

View File

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "base/platform/base_platform_global_shortcuts.h"
#include <tgcalls/group/GroupInstanceImpl.h> #include <tgcalls/group/GroupInstanceImpl.h>
@ -92,6 +93,11 @@ void GroupCall::setState(State state) {
} }
_state = state; _state = state;
if (_state.current() == State::Joined && !_pushToTalkStarted) {
_pushToTalkStarted = true;
applyGlobalShortcutChanges();
}
if (false if (false
|| state == State::Ended || state == State::Ended
|| state == State::Failed) { || state == State::Failed) {
@ -706,6 +712,57 @@ std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
return result; return result;
} }
auto GroupCall::ensureGlobalShortcutManager()
-> std::shared_ptr<GlobalShortcutManager> {
if (!_shortcutManager) {
_shortcutManager = base::Platform::CreateGlobalShortcutManager();
}
return _shortcutManager;
}
void GroupCall::applyGlobalShortcutChanges() {
auto &settings = Core::App().settings();
if (!settings.groupCallPushToTalk()) {
_shortcutManager = nullptr;
_pushToTalk = nullptr;
return;
} else if (settings.groupCallPushToTalkShortcut().isEmpty()) {
settings.setGroupCallPushToTalk(false);
Core::App().saveSettingsDelayed();
_shortcutManager = nullptr;
_pushToTalk = nullptr;
return;
}
ensureGlobalShortcutManager();
if (!_shortcutManager) {
settings.setGroupCallPushToTalk(false);
Core::App().saveSettingsDelayed();
_pushToTalk = nullptr;
return;
}
const auto shortcut = _shortcutManager->shortcutFromSerialized(
settings.groupCallPushToTalkShortcut());
if (!shortcut) {
settings.setGroupCallPushToTalkShortcut(QByteArray());
settings.setGroupCallPushToTalk(false);
Core::App().saveSettingsDelayed();
_shortcutManager = nullptr;
_pushToTalk = nullptr;
return;
}
if (_pushToTalk) {
if (shortcut->serialize() == _pushToTalk->serialize()) {
return;
}
_shortcutManager->stopWatching(_pushToTalk);
}
_pushToTalk = shortcut;
_shortcutManager->startWatching(_pushToTalk, [=](bool pressed) {
if (_muted.current() != MuteState::ForceMuted) {
setMuted(pressed ? MuteState::Active : MuteState::Muted);
}
});
}
//void GroupCall::setAudioVolume(bool input, float level) { //void GroupCall::setAudioVolume(bool input, float level) {
// if (_instance) { // if (_instance) {
// if (input) { // if (input) {

View File

@ -19,6 +19,13 @@ namespace tgcalls {
class GroupInstanceImpl; class GroupInstanceImpl;
} // namespace tgcalls } // namespace tgcalls
namespace base {
namespace Platform {
class GlobalShortcutManager;
class GlobalShortcutValue;
} // namespace Platform
} // namespace base
namespace Calls { namespace Calls {
enum class MuteState { enum class MuteState {
@ -44,6 +51,8 @@ public:
}; };
using GlobalShortcutManager = base::Platform::GlobalShortcutManager;
GroupCall( GroupCall(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
@ -102,11 +111,16 @@ public:
std::variant<int, not_null<UserData*>> inviteUsers( std::variant<int, not_null<UserData*>> inviteUsers(
const std::vector<not_null<UserData*>> &users); const std::vector<not_null<UserData*>> &users);
std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager();
void applyGlobalShortcutChanges();
[[nodiscard]] rpl::lifetime &lifetime() { [[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime; return _lifetime;
} }
private: private:
using GlobalShortcutValue = base::Platform::GlobalShortcutValue;
enum class FinishType { enum class FinishType {
None, None,
Ended, Ended,
@ -160,6 +174,10 @@ private:
crl::time _lastSendProgressUpdate = 0; crl::time _lastSendProgressUpdate = 0;
std::shared_ptr<GlobalShortcutManager> _shortcutManager;
std::shared_ptr<GlobalShortcutValue> _pushToTalk;
bool _pushToTalkStarted = false;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View File

@ -10,10 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_call.h" #include "calls/calls_group_call.h"
#include "calls/calls_group_panel.h" // LeaveGroupCallBox. #include "calls/calls_group_panel.h" // LeaveGroupCallBox.
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/level_meter.h" #include "ui/widgets/level_meter.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "base/platform/base_platform_global_shortcuts.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "core/application.h" #include "core/application.h"
@ -90,12 +92,10 @@ void GroupCallSettingsBox(
AddSkip(layout); AddSkip(layout);
} }
const auto muteJoined = addCheck const auto muteJoined = addCheck
? box->addRow(object_ptr<Ui::Checkbox>( ? AddButton(
box.get(), layout,
tr::lng_group_call_new_muted(), tr::lng_group_call_new_muted(),
joinMuted, st::groupCallSettingsButton)->toggleOn(rpl::single(joinMuted))
st::groupCallCheckbox,
st::groupCallCheck))
: nullptr; : nullptr;
if (addCheck) { if (addCheck) {
AddSkip(layout); AddSkip(layout);
@ -155,6 +155,97 @@ void GroupCallSettingsBox(
}); });
AddSkip(layout); AddSkip(layout);
//AddDivider(layout);
//AddSkip(layout);
using namespace base::Platform;
struct PushToTalkState {
rpl::variable<QString> recordText = tr::lng_group_call_ptt_shortcut();
rpl::variable<QString> shortcutText;
GlobalShortcut shortcut;
bool recording = false;
};
const auto manager = call->ensureGlobalShortcutManager();
if (manager) {
const auto state = box->lifetime().make_state<PushToTalkState>();
state->shortcut = manager->shortcutFromSerialized(
settings.groupCallPushToTalkShortcut());
state->shortcutText = state->shortcut
? state->shortcut->toDisplayString()
: QString();
const auto pushToTalk = AddButton(
layout,
tr::lng_group_call_push_to_talk(),
st::groupCallSettingsButton
)->toggleOn(rpl::single(settings.groupCallPushToTalk()));
const auto recordingWrap = layout->add(
object_ptr<Ui::SlideWrap<Button>>(
layout,
object_ptr<Button>(
layout,
state->recordText.value(),
st::groupCallSettingsButton)));
const auto recording = recordingWrap->entity();
CreateRightLabel(
recording,
state->shortcutText.value(),
st::groupCallSettingsButton,
state->recordText.value());
const auto startRecording = [=] {
state->recording = true;
state->recordText = tr::lng_group_call_ptt_recording();
manager->startRecording([=](GlobalShortcut shortcut) {
state->shortcutText = shortcut->toDisplayString();
}, [=](GlobalShortcut shortcut) {
state->recording = false;
state->shortcut = shortcut;
state->shortcutText = shortcut
? shortcut->toDisplayString()
: QString();
state->recordText = tr::lng_group_call_ptt_shortcut();
Core::App().settings().setGroupCallPushToTalkShortcut(shortcut
? shortcut->serialize()
: QByteArray());
Core::App().saveSettingsDelayed();
});
};
const auto stopRecording = [=] {
state->recording = false;
state->recordText = tr::lng_group_call_ptt_shortcut();
state->shortcutText = state->shortcut
? state->shortcut->toDisplayString()
: QString();
manager->stopRecording();
};
recording->addClickHandler([=] {
if (state->recording) {
stopRecording();
} else {
startRecording();
}
});
recordingWrap->toggle(
settings.groupCallPushToTalk(),
anim::type::instant);
pushToTalk->toggledChanges(
) | rpl::start_with_next([=](bool toggled) {
if (!toggled) {
stopRecording();
}
Core::App().settings().setGroupCallPushToTalk(toggled);
Core::App().saveSettingsDelayed();
recordingWrap->toggle(toggled, anim::type::normal);
}, pushToTalk->lifetime());
box->boxClosing(
) | rpl::start_with_next([=] {
call->applyGlobalShortcutChanges();
}, box->lifetime());
}
AddSkip(layout);
//AddDivider(layout);
//AddSkip(layout);
const auto lookupLink = [=] { const auto lookupLink = [=] {
return channel->hasUsername() return channel->hasUsername()
@ -226,8 +317,8 @@ void GroupCallSettingsBox(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (canChangeJoinMuted if (canChangeJoinMuted
&& muteJoined && muteJoined
&& muteJoined->checked() != joinMuted) { && muteJoined->toggled() != joinMuted) {
SaveCallJoinMuted(channel, id, muteJoined->checked()); SaveCallJoinMuted(channel, id, muteJoined->toggled());
} }
}, box->lifetime()); }, box->lifetime());
box->addButton(tr::lng_box_done(), [=] { box->addButton(tr::lng_box_done(), [=] {

View File

@ -109,7 +109,9 @@ QByteArray Settings::serialize() const {
<< qint32(_nativeWindowFrame.current() ? 1 : 0) << qint32(_nativeWindowFrame.current() ? 1 : 0)
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0) << qint32(_systemDarkModeEnabled.current() ? 1 : 0)
<< _callVideoInputDeviceId << _callVideoInputDeviceId
<< qint32(_ipRevealWarning ? 1 : 0); << qint32(_ipRevealWarning ? 1 : 0)
<< qint32(_groupCallPushToTalk ? 1 : 0)
<< _groupCallPushToTalkShortcut;
} }
return result; return result;
} }
@ -177,6 +179,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 nativeWindowFrame = _nativeWindowFrame.current() ? 1 : 0; qint32 nativeWindowFrame = _nativeWindowFrame.current() ? 1 : 0;
qint32 systemDarkModeEnabled = _systemDarkModeEnabled.current() ? 1 : 0; qint32 systemDarkModeEnabled = _systemDarkModeEnabled.current() ? 1 : 0;
qint32 ipRevealWarning = _ipRevealWarning ? 1 : 0; qint32 ipRevealWarning = _ipRevealWarning ? 1 : 0;
qint32 groupCallPushToTalk = _groupCallPushToTalk ? 1 : 0;
QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut;
stream >> themesAccentColors; stream >> themesAccentColors;
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -263,6 +267,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> ipRevealWarning; stream >> ipRevealWarning;
} }
if (!stream.atEnd()) {
stream
>> groupCallPushToTalk
>> groupCallPushToTalkShortcut;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()")); "Bad data for Core::Settings::constructFromSerialized()"));
@ -354,6 +363,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_notifyFromAll = (notifyFromAll == 1); _notifyFromAll = (notifyFromAll == 1);
_nativeWindowFrame = (nativeWindowFrame == 1); _nativeWindowFrame = (nativeWindowFrame == 1);
_systemDarkModeEnabled = (systemDarkModeEnabled == 1); _systemDarkModeEnabled = (systemDarkModeEnabled == 1);
_groupCallPushToTalk = (groupCallPushToTalk == 1);
_groupCallPushToTalkShortcut = groupCallPushToTalkShortcut;
} }
bool Settings::chatWide() const { bool Settings::chatWide() const {

View File

@ -217,6 +217,18 @@ public:
void setCallAudioDuckingEnabled(bool value) { void setCallAudioDuckingEnabled(bool value) {
_callAudioDuckingEnabled = value; _callAudioDuckingEnabled = value;
} }
[[nodiscard]] bool groupCallPushToTalk() const {
return _groupCallPushToTalk;
}
void setGroupCallPushToTalk(bool value) {
_groupCallPushToTalk = value;
}
[[nodiscard]] QByteArray groupCallPushToTalkShortcut() const {
return _groupCallPushToTalkShortcut;
}
void setGroupCallPushToTalkShortcut(const QByteArray &serialized) {
_groupCallPushToTalkShortcut = serialized;
}
[[nodiscard]] Window::Theme::AccentColors &themesAccentColors() { [[nodiscard]] Window::Theme::AccentColors &themesAccentColors() {
return _themesAccentColors; return _themesAccentColors;
} }
@ -513,6 +525,8 @@ private:
int _callOutputVolume = 100; int _callOutputVolume = 100;
int _callInputVolume = 100; int _callInputVolume = 100;
bool _callAudioDuckingEnabled = true; bool _callAudioDuckingEnabled = true;
bool _groupCallPushToTalk = false;
QByteArray _groupCallPushToTalkShortcut;
Window::Theme::AccentColors _themesAccentColors; Window::Theme::AccentColors _themesAccentColors;
bool _lastSeenWarningSeen = false; bool _lastSeenWarningSeen = false;
Ui::SendFilesWay _sendFilesWay; Ui::SendFilesWay _sendFilesWay;

@ -1 +1 @@
Subproject commit 3562a43685fdac00c277292ce2c83d92132cc319 Subproject commit 7d1df24be11eb6b3e6382735b73418b4a282bad4

@ -1 +1 @@
Subproject commit 8aede3acc9d386484d2774316e9d1a3d0c265dd5 Subproject commit 1b540b38ed78e9a3cba93e9ba4ce4525ab692277

@ -1 +1 @@
Subproject commit 6fc06b5f9645005143f09f72e1c052a28d5f26ed Subproject commit cbe51722b73cfa9ff27bd59294b08aa5ee33c936

@ -1 +1 @@
Subproject commit ab4ad89c4c709b2ec0f8296451d49c99d2ae4372 Subproject commit 5f44304a305f9d02823e2d7ded9aadd59463e5a5

@ -1 +1 @@
Subproject commit 52d52cad4e554dac1907224372d51fd40b9da92f Subproject commit 0ed2a6cc048e30ee8bacf7212f3f12f4f7ae2b5a