Add push-to-talk with global shortcut on Windows.
This commit is contained in:
parent
f4dfd738ec
commit
d41e93fb1c
|
@ -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";
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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(), [=] {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue