Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeecc42c25 | ||
|
|
e22ecafc1d | ||
|
|
ba41da7b28 | ||
|
|
9cfbccf9e7 | ||
|
|
2b6f50e114 | ||
|
|
d2f57b72c3 | ||
|
|
85ac983a27 | ||
|
|
ac397e6e19 | ||
|
|
38e15c9bdb | ||
|
|
00d65fa978 | ||
|
|
3fea9cca08 | ||
|
|
b390e0766b | ||
|
|
2f75e6bbe2 | ||
|
|
decbbb9a73 | ||
|
|
b4b80822c8 | ||
|
|
bc82cdc3b3 | ||
|
|
ebc67d25f0 | ||
|
|
348b4d54ba | ||
|
|
6f86ce595b | ||
|
|
8c53a3c19e | ||
|
|
67623072d6 | ||
|
|
1291f1c80d | ||
|
|
0684db9bd8 | ||
|
|
db7b61a77b | ||
|
|
d392633b90 | ||
|
|
76e08af26a | ||
|
|
b23f16e6e4 | ||
|
|
23156d523c | ||
|
|
04b0e2e9e6 | ||
|
|
ace5740125 | ||
|
|
bc67b79023 | ||
|
|
528c98af67 | ||
|
|
311a2f2753 | ||
|
|
3defb06783 | ||
|
|
5708b5e849 |
3
.github/workflows/win.yml
vendored
3
.github/workflows/win.yml
vendored
@@ -128,7 +128,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Find any version of Python 2."
|
||||
p=`ls /c/hostedtoolcache/windows/python | grep 2 | tail -1`
|
||||
p=`ls /c/hostedtoolcache/windows/python | grep "^2" | tail -1`
|
||||
if [ -z "$p" ]; then
|
||||
echo "Python 2 is not found."
|
||||
exit 1
|
||||
@@ -409,6 +409,7 @@ jobs:
|
||||
-D TDESKTOP_API_TEST=ON ^
|
||||
-D DESKTOP_APP_USE_PACKAGED=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
-D DESKTOP_APP_NO_PDB=ON ^
|
||||
%TDESKTOP_BUILD_DEFINE% ^
|
||||
-DCMAKE_SYSTEM_VERSION=%SDK%
|
||||
|
||||
|
||||
@@ -821,6 +821,8 @@ PRIVATE
|
||||
platform/linux/linux_gtk_integration.h
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.h
|
||||
platform/linux/linux_mpris_support.cpp
|
||||
platform/linux/linux_mpris_support.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
@@ -1113,6 +1115,8 @@ if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_mpris_support.cpp
|
||||
platform/linux/linux_mpris_support.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_xdp_file_dialog.cpp
|
||||
|
||||
@@ -1939,6 +1939,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_leave_sure" = "Are you sure you want to leave this voice chat?";
|
||||
"lng_group_call_leave_to_other_sure" = "Do you want to leave your active voice chat and join a voice chat in this group?";
|
||||
"lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?";
|
||||
"lng_group_call_create_sure_channel" = "Are you sure you want to start a voice chat in this channel as your personal account?";
|
||||
"lng_group_call_join_sure_personal" = "Are you sure you want to join this voice chat as your personal account?";
|
||||
"lng_group_call_also_end" = "End voice chat";
|
||||
"lng_group_call_settings_title" = "Settings";
|
||||
"lng_group_call_invite" = "Invite Member";
|
||||
|
||||
@@ -1203,7 +1203,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
|
||||
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
|
||||
|
||||
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
|
||||
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
|
||||
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
|
||||
|
||||
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
|
||||
|
||||
@@ -1572,7 +1572,7 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> =
|
||||
channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
|
||||
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
|
||||
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true for_groupcall:flags.2?true = messages.Chats;
|
||||
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
|
||||
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
|
||||
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
|
||||
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.6.3.0" />
|
||||
Version="2.7.0.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,6,3,0
|
||||
PRODUCTVERSION 2,6,3,0
|
||||
FILEVERSION 2,7,0,0
|
||||
PRODUCTVERSION 2,7,0,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.6.3.0"
|
||||
VALUE "FileVersion", "2.7.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.3.0"
|
||||
VALUE "ProductVersion", "2.7.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,6,3,0
|
||||
PRODUCTVERSION 2,6,3,0
|
||||
FILEVERSION 2,7,0,0
|
||||
PRODUCTVERSION 2,7,0,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.6.3.0"
|
||||
VALUE "FileVersion", "2.7.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.3.0"
|
||||
VALUE "ProductVersion", "2.7.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -65,7 +65,10 @@ void SendProgressManager::update(
|
||||
SendProgressType type,
|
||||
int progress) {
|
||||
const auto peer = history->peer;
|
||||
if (peer->isSelf() || (peer->isChannel() && !peer->isMegagroup())) {
|
||||
if (peer->isSelf()
|
||||
|| (peer->isChannel()
|
||||
&& !peer->isMegagroup()
|
||||
&& type != SendProgressType::Speaking)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -277,12 +277,32 @@ void Updates::checkLastUpdate(bool afterSleep) {
|
||||
|
||||
void Updates::feedUpdateVector(
|
||||
const MTPVector<MTPUpdate> &updates,
|
||||
bool skipMessageIds) {
|
||||
for (const auto &update : updates.v) {
|
||||
if (skipMessageIds && update.type() == mtpc_updateMessageID) {
|
||||
SkipUpdatePolicy policy) {
|
||||
auto list = updates.v;
|
||||
const auto hasGroupCallParticipantUpdates = ranges::contains(
|
||||
list,
|
||||
mtpc_updateGroupCallParticipants,
|
||||
&MTPUpdate::type);
|
||||
if (hasGroupCallParticipantUpdates) {
|
||||
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
|
||||
if (entry.type() == mtpc_updateGroupCallParticipants) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
|
||||
return;
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
const auto type = entry.type();
|
||||
if ((policy == SkipUpdatePolicy::SkipMessageIds
|
||||
&& type == mtpc_updateMessageID)
|
||||
|| (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants
|
||||
&& type != mtpc_updateGroupCallParticipants)) {
|
||||
continue;
|
||||
}
|
||||
feedUpdate(update);
|
||||
feedUpdate(entry);
|
||||
}
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
}
|
||||
@@ -392,7 +412,9 @@ void Updates::feedChannelDifference(
|
||||
session().data().processMessages(
|
||||
data.vnew_messages(),
|
||||
NewMessageType::Unread);
|
||||
feedUpdateVector(data.vother_updates(), true);
|
||||
feedUpdateVector(
|
||||
data.vother_updates(),
|
||||
SkipUpdatePolicy::SkipMessageIds);
|
||||
_handlingChannelDifference = false;
|
||||
}
|
||||
|
||||
@@ -553,7 +575,7 @@ void Updates::feedDifference(
|
||||
session().data().processChats(chats);
|
||||
feedMessageIds(other);
|
||||
session().data().processMessages(msgs, NewMessageType::Unread);
|
||||
feedUpdateVector(other, true);
|
||||
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
|
||||
}
|
||||
|
||||
void Updates::differenceFail(const MTP::Error &error) {
|
||||
@@ -810,9 +832,32 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
|
||||
if (!requestingDifference()
|
||||
|| HasForceLogoutNotification(updates)) {
|
||||
applyUpdates(updates);
|
||||
} else {
|
||||
applyGroupCallParticipantUpdates(updates);
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
session().data().processChats(data.vchats());
|
||||
feedUpdateVector(
|
||||
data.vupdates(),
|
||||
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
|
||||
}, [&](const MTPDupdatesCombined &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
session().data().processChats(data.vchats());
|
||||
feedUpdateVector(
|
||||
data.vupdates(),
|
||||
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
|
||||
}, [&](const MTPDupdateShort &data) {
|
||||
if (data.vupdate().type() == mtpc_updateGroupCallParticipants) {
|
||||
feedUpdate(data.vupdate());
|
||||
}
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
int32 Updates::pts() const {
|
||||
return _ptsWaiter.current();
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ private:
|
||||
AfterFail,
|
||||
};
|
||||
|
||||
enum class SkipUpdatePolicy {
|
||||
SkipNone,
|
||||
SkipMessageIds,
|
||||
SkipExceptGroupCallParticipants,
|
||||
};
|
||||
|
||||
struct ActiveChatTracker {
|
||||
PeerData *peer = nullptr;
|
||||
rpl::lifetime lifetime;
|
||||
@@ -113,12 +119,14 @@ private:
|
||||
void mtpNewSessionCreated();
|
||||
void feedUpdateVector(
|
||||
const MTPVector<MTPUpdate> &updates,
|
||||
bool skipMessageIds = false);
|
||||
SkipUpdatePolicy policy = SkipUpdatePolicy::SkipNone);
|
||||
// Doesn't call sendHistoryChangeNotifications itself.
|
||||
void feedMessageIds(const MTPVector<MTPUpdate> &updates);
|
||||
// Doesn't call sendHistoryChangeNotifications itself.
|
||||
void feedUpdate(const MTPUpdate &update);
|
||||
|
||||
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
|
||||
|
||||
bool whenGetDiffChanged(
|
||||
ChannelData *channel,
|
||||
int32 ms,
|
||||
|
||||
@@ -163,6 +163,37 @@ void ChooseJoinAsBox(
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities CreateOrJoinConfirmation(
|
||||
not_null<PeerData*> peer,
|
||||
ChooseJoinAsProcess::Context context,
|
||||
bool joinAsAlreadyUsed) {
|
||||
const auto existing = peer->groupCall();
|
||||
if (!existing) {
|
||||
return { peer->isBroadcast()
|
||||
? tr::lng_group_call_create_sure_channel(tr::now)
|
||||
: tr::lng_group_call_create_sure(tr::now) };
|
||||
}
|
||||
const auto channel = peer->asChannel();
|
||||
const auto anonymouseAdmin = channel
|
||||
&& ((channel->isMegagroup() && channel->amAnonymous())
|
||||
|| (channel->isBroadcast()
|
||||
&& (channel->amCreator()
|
||||
|| channel->hasAdminRights())));
|
||||
if (anonymouseAdmin && !joinAsAlreadyUsed) {
|
||||
return { tr::lng_group_call_join_sure_personal(tr::now) };
|
||||
} else if (context != ChooseJoinAsProcess::Context::JoinWithConfirm) {
|
||||
return {};
|
||||
}
|
||||
const auto name = !existing->title().isEmpty()
|
||||
? existing->title()
|
||||
: peer->name;
|
||||
return tr::lng_group_call_join_confirm(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChooseJoinAsProcess::~ChooseJoinAsProcess() {
|
||||
@@ -240,38 +271,6 @@ void ChooseJoinAsProcess::start(
|
||||
if (list.empty()) {
|
||||
_request->showToast(Lang::Hard::ServerError());
|
||||
return;
|
||||
} else if (!changingJoinAsFrom
|
||||
&& list.size() == 1
|
||||
&& list.front() == self
|
||||
&& (!peer->isChannel()
|
||||
|| !peer->asChannel()->amAnonymous()
|
||||
|| (peer->isBroadcast() && !peer->canWrite()))) {
|
||||
info.possibleJoinAs = std::move(list);
|
||||
if (context != Context::JoinWithConfirm
|
||||
|| (selectedId && self->id == selectedId)) {
|
||||
finish(info);
|
||||
return;
|
||||
}
|
||||
const auto real = peer->groupCall();
|
||||
const auto name = (real && !real->title().isEmpty())
|
||||
? real->title()
|
||||
: peer->name;
|
||||
auto box = Box<::ConfirmBox>(
|
||||
tr::lng_group_call_join_confirm(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities),
|
||||
tr::lng_group_call_join(tr::now),
|
||||
crl::guard(&_request->guard, [=] { finish(info); }));
|
||||
box->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_request = nullptr;
|
||||
}, _request->lifetime);
|
||||
|
||||
_request->box = box.data();
|
||||
_request->showBox(std::move(box));
|
||||
return;
|
||||
}
|
||||
info.joinAs = [&]() -> not_null<PeerData*> {
|
||||
const auto loaded = selectedId
|
||||
@@ -288,11 +287,36 @@ void ChooseJoinAsProcess::start(
|
||||
}();
|
||||
info.possibleJoinAs = std::move(list);
|
||||
|
||||
if (!changingJoinAsFrom
|
||||
&& selectedId
|
||||
&& info.joinAs->id == selectedId) {
|
||||
// We already joined this voice chat, just rejoin with the same.
|
||||
finish(info);
|
||||
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
|
||||
&& (info.possibleJoinAs.front() == self);
|
||||
|
||||
// We already joined this voice chat, just rejoin with the same.
|
||||
const auto byAlreadyUsed = selectedId
|
||||
&& (info.joinAs->id == selectedId)
|
||||
&& (peer->groupCall() != nullptr);
|
||||
|
||||
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
|
||||
const auto confirmation = CreateOrJoinConfirmation(
|
||||
peer,
|
||||
context,
|
||||
byAlreadyUsed);
|
||||
if (confirmation.text.isEmpty()) {
|
||||
finish(info);
|
||||
return;
|
||||
}
|
||||
auto box = Box<::ConfirmBox>(
|
||||
confirmation,
|
||||
(peer->groupCall()
|
||||
? tr::lng_group_call_join(tr::now)
|
||||
: tr::lng_create_group_create(tr::now)),
|
||||
crl::guard(&_request->guard, [=] { finish(info); }));
|
||||
box->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_request = nullptr;
|
||||
}, _request->lifetime);
|
||||
|
||||
_request->box = box.data();
|
||||
_request->showBox(std::move(box));
|
||||
return;
|
||||
}
|
||||
auto box = Box(
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_group_common.h"
|
||||
#include "main/main_session.h"
|
||||
#include "api/api_send_progress.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lang/lang_hardcoded.h"
|
||||
@@ -272,11 +273,6 @@ void GroupCall::setState(State state) {
|
||||
|
||||
if (state == State::Joined) {
|
||||
stopConnectingSound();
|
||||
if (!_hadJoinedState) {
|
||||
_hadJoinedState = true;
|
||||
applyGlobalShortcutChanges();
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
|
||||
}
|
||||
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
|
||||
call->setInCall();
|
||||
}
|
||||
@@ -392,8 +388,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||
|
||||
addParticipantsToInstance();
|
||||
|
||||
_peer->session().updates().addActiveChat(
|
||||
_peerStream.events_starting_with_copy(_peer));
|
||||
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
|
||||
_peer = group;
|
||||
_peerStream.fire_copy(group);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -472,6 +471,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
MTP_dataJSON(MTP_bytes(json))
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
_mySsrc = ssrc;
|
||||
_mySsrcs.emplace(ssrc);
|
||||
setState((_instanceState.current()
|
||||
== InstanceState::Disconnected)
|
||||
? State::Connecting
|
||||
@@ -479,6 +479,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
applyMeInCallLocally();
|
||||
maybeSendMutedUpdate(wasMuteState);
|
||||
_peer->session().api().applyUpdates(updates);
|
||||
checkFirstTimeJoined();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
LOG(("Call Error: Could not join, error: %1").arg(type));
|
||||
@@ -547,7 +548,7 @@ void GroupCall::applyMeInCallLocally() {
|
||||
| Flag::f_volume_by_admin // Self volume can only be set by admin.
|
||||
| ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0))
|
||||
| (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0));
|
||||
call->applyUpdateChecked(
|
||||
call->applyLocalUpdate(
|
||||
MTP_updateGroupCallParticipants(
|
||||
inputCall(),
|
||||
MTP_vector<MTPGroupCallParticipant>(
|
||||
@@ -592,7 +593,7 @@ void GroupCall::applyParticipantLocally(
|
||||
| (participant->raisedHandRating
|
||||
? Flag::f_raise_hand_rating
|
||||
: Flag(0));
|
||||
_peer->groupCall()->applyUpdateChecked(
|
||||
_peer->groupCall()->applyLocalUpdate(
|
||||
MTP_updateGroupCallParticipants(
|
||||
inputCall(),
|
||||
MTP_vector<MTPGroupCallParticipant>(
|
||||
@@ -717,101 +718,112 @@ void GroupCall::setMutedAndUpdate(MuteState mute) {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::handleUpdate(const MTPGroupCall &call) {
|
||||
return call.match([&](const MTPDgroupCall &data) {
|
||||
if (_acceptFields) {
|
||||
if (!_instance && !_id) {
|
||||
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
|
||||
}
|
||||
void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||
const MTPDupdateGroupCall &data) {
|
||||
data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
handlePossibleCreateOrJoinResponse(data);
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
handlePossibleDiscarded(data);
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||
const MTPDgroupCall &data) {
|
||||
if (_acceptFields) {
|
||||
if (!_instance && !_id) {
|
||||
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
|
||||
}
|
||||
return;
|
||||
} else if (_id != data.vid().v || !_instance) {
|
||||
return;
|
||||
}
|
||||
const auto streamDcId = MTP::BareDcId(
|
||||
data.vstream_dc_id().value_or_empty());
|
||||
const auto params = data.vparams();
|
||||
if (!params) {
|
||||
return;
|
||||
}
|
||||
params->match([&](const MTPDdataJSON &data) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(
|
||||
data.vdata().v,
|
||||
&error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("API Error: "
|
||||
"Failed to parse group call params, error: %1."
|
||||
).arg(error.errorString()));
|
||||
return;
|
||||
} else if (_id != data.vid().v
|
||||
|| _accessHash != data.vaccess_hash().v
|
||||
|| !_instance) {
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("API Error: "
|
||||
"Not an object received in group call params."));
|
||||
return;
|
||||
}
|
||||
const auto streamDcId = MTP::BareDcId(
|
||||
data.vstream_dc_id().value_or_empty());
|
||||
if (const auto params = data.vparams()) {
|
||||
params->match([&](const MTPDdataJSON &data) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(
|
||||
data.vdata().v,
|
||||
&error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("API Error: "
|
||||
"Failed to parse group call params, error: %1."
|
||||
).arg(error.errorString()));
|
||||
return;
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("API Error: "
|
||||
"Not an object received in group call params."));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto guard = gsl::finally([&] {
|
||||
addParticipantsToInstance();
|
||||
});
|
||||
const auto guard = gsl::finally([&] {
|
||||
addParticipantsToInstance();
|
||||
});
|
||||
|
||||
if (document.object().value("stream").toBool()) {
|
||||
if (!streamDcId) {
|
||||
LOG(("Api Error: Empty stream_dc_id in groupCall."));
|
||||
}
|
||||
_broadcastDcId = streamDcId
|
||||
? streamDcId
|
||||
: _peer->session().mtp().mainDcId();
|
||||
setInstanceMode(InstanceMode::Stream);
|
||||
return;
|
||||
}
|
||||
if (document.object().value("stream").toBool()) {
|
||||
if (!streamDcId) {
|
||||
LOG(("Api Error: Empty stream_dc_id in groupCall."));
|
||||
}
|
||||
_broadcastDcId = streamDcId
|
||||
? streamDcId
|
||||
: _peer->session().mtp().mainDcId();
|
||||
setInstanceMode(InstanceMode::Stream);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto readString = [](
|
||||
const QJsonObject &object,
|
||||
const char *key) {
|
||||
return object.value(key).toString().toStdString();
|
||||
};
|
||||
const auto root = document.object().value("transport").toObject();
|
||||
auto payload = tgcalls::GroupJoinResponsePayload();
|
||||
payload.ufrag = readString(root, "ufrag");
|
||||
payload.pwd = readString(root, "pwd");
|
||||
const auto prints = root.value("fingerprints").toArray();
|
||||
const auto candidates = root.value("candidates").toArray();
|
||||
for (const auto &print : prints) {
|
||||
const auto object = print.toObject();
|
||||
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
|
||||
.hash = readString(object, "hash"),
|
||||
.setup = readString(object, "setup"),
|
||||
.fingerprint = readString(object, "fingerprint"),
|
||||
});
|
||||
}
|
||||
for (const auto &candidate : candidates) {
|
||||
const auto object = candidate.toObject();
|
||||
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
|
||||
.port = readString(object, "port"),
|
||||
.protocol = readString(object, "protocol"),
|
||||
.network = readString(object, "network"),
|
||||
.generation = readString(object, "generation"),
|
||||
.id = readString(object, "id"),
|
||||
.component = readString(object, "component"),
|
||||
.foundation = readString(object, "foundation"),
|
||||
.priority = readString(object, "priority"),
|
||||
.ip = readString(object, "ip"),
|
||||
.type = readString(object, "type"),
|
||||
.tcpType = readString(object, "tcpType"),
|
||||
.relAddr = readString(object, "relAddr"),
|
||||
.relPort = readString(object, "relPort"),
|
||||
});
|
||||
}
|
||||
setInstanceMode(InstanceMode::Rtc);
|
||||
_instance->setJoinResponsePayload(payload, {});
|
||||
const auto readString = [](
|
||||
const QJsonObject &object,
|
||||
const char *key) {
|
||||
return object.value(key).toString().toStdString();
|
||||
};
|
||||
const auto root = document.object().value("transport").toObject();
|
||||
auto payload = tgcalls::GroupJoinResponsePayload();
|
||||
payload.ufrag = readString(root, "ufrag");
|
||||
payload.pwd = readString(root, "pwd");
|
||||
const auto prints = root.value("fingerprints").toArray();
|
||||
const auto candidates = root.value("candidates").toArray();
|
||||
for (const auto &print : prints) {
|
||||
const auto object = print.toObject();
|
||||
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
|
||||
.hash = readString(object, "hash"),
|
||||
.setup = readString(object, "setup"),
|
||||
.fingerprint = readString(object, "fingerprint"),
|
||||
});
|
||||
}
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
if (data.vid().v == _id) {
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
for (const auto &candidate : candidates) {
|
||||
const auto object = candidate.toObject();
|
||||
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
|
||||
.port = readString(object, "port"),
|
||||
.protocol = readString(object, "protocol"),
|
||||
.network = readString(object, "network"),
|
||||
.generation = readString(object, "generation"),
|
||||
.id = readString(object, "id"),
|
||||
.component = readString(object, "component"),
|
||||
.foundation = readString(object, "foundation"),
|
||||
.priority = readString(object, "priority"),
|
||||
.ip = readString(object, "ip"),
|
||||
.type = readString(object, "type"),
|
||||
.tcpType = readString(object, "tcpType"),
|
||||
.relAddr = readString(object, "relAddr"),
|
||||
.relPort = readString(object, "relPort"),
|
||||
});
|
||||
}
|
||||
setInstanceMode(InstanceMode::Rtc);
|
||||
_instance->setJoinResponsePayload(payload, {});
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) {
|
||||
if (data.vid().v == _id) {
|
||||
LOG(("Call Info: Hangup after groupCallDiscarded."));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::addParticipantsToInstance() {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real
|
||||
@@ -853,7 +865,30 @@ void GroupCall::addPreparedParticipantsDelayed() {
|
||||
crl::on_main(this, [=] { addPreparedParticipants(); });
|
||||
}
|
||||
|
||||
void GroupCall::handleUpdate(const MTPUpdate &update) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
handleUpdate(data);
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
handleUpdate(data);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in Instance::applyGroupCallUpdateChecked.");
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::handleUpdate(const MTPDupdateGroupCall &data) {
|
||||
data.vcall().match([](const MTPDgroupCall &) {
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
handlePossibleDiscarded(data);
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
|
||||
const auto callId = data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
if (_id != callId) {
|
||||
return;
|
||||
}
|
||||
const auto state = _state.current();
|
||||
if (state != State::Joined && state != State::Connecting) {
|
||||
return;
|
||||
@@ -893,18 +928,27 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
|
||||
if (data.is_left()) {
|
||||
if (data.vsource().v == _mySsrc) {
|
||||
// I was removed from the call, rejoin.
|
||||
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
|
||||
LOG(("Call Info: "
|
||||
"Rejoin after got 'left' with my ssrc."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
}
|
||||
return;
|
||||
} else if (data.vsource().v != _mySsrc) {
|
||||
// I joined from another device, hangup.
|
||||
LOG(("Call Info: Hangup after '!left' with ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
if (!_mySsrcs.contains(data.vsource().v)) {
|
||||
// I joined from another device, hangup.
|
||||
LOG(("Call Info: "
|
||||
"Hangup after '!left' with ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
} else {
|
||||
LOG(("Call Info: "
|
||||
"Some old 'self' with '!left' and ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.is_muted() && !data.is_can_self_unmute()) {
|
||||
@@ -1091,7 +1135,8 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
|
||||
});
|
||||
});
|
||||
}).fail([=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (error.type() == u"GROUPCALL_JOIN_MISSING"_q) {
|
||||
if (error.type() == u"GROUPCALL_JOIN_MISSING"_q
|
||||
|| error.type() == u"GROUPCALL_FORBIDDEN"_q) {
|
||||
for (const auto &[task, part] : _broadcastParts) {
|
||||
_api.request(part.requestId).cancel();
|
||||
}
|
||||
@@ -1312,9 +1357,24 @@ void GroupCall::setInstanceConnected(
|
||||
if (nowCanSpeak) {
|
||||
notifyAboutAllowedToSpeak();
|
||||
}
|
||||
if (!_hadJoinedState && state() == State::Joined) {
|
||||
checkFirstTimeJoined();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::checkFirstTimeJoined() {
|
||||
if (_hadJoinedState || state() != State::Joined) {
|
||||
return;
|
||||
}
|
||||
_hadJoinedState = true;
|
||||
applyGlobalShortcutChanges();
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
|
||||
}
|
||||
|
||||
void GroupCall::notifyAboutAllowedToSpeak() {
|
||||
if (!_hadJoinedState) {
|
||||
return;
|
||||
}
|
||||
_delegate->groupCallPlaySound(
|
||||
Delegate::GroupCallSound::AllowedToSpeak);
|
||||
_allowedToSpeakNotifications.fire({});
|
||||
|
||||
@@ -116,8 +116,8 @@ public:
|
||||
void rejoinAs(Group::JoinInfo info);
|
||||
void rejoinWithHash(const QString &hash);
|
||||
void join(const MTPInputGroupCall &inputCall);
|
||||
void handleUpdate(const MTPGroupCall &call);
|
||||
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
||||
void handleUpdate(const MTPUpdate &update);
|
||||
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
|
||||
void changeTitle(const QString &title);
|
||||
void toggleRecording(bool enabled, const QString &title);
|
||||
[[nodiscard]] bool recordingStoppedByMe() const {
|
||||
@@ -227,6 +227,10 @@ private:
|
||||
RaiseHand,
|
||||
};
|
||||
|
||||
void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data);
|
||||
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
|
||||
void handleUpdate(const MTPDupdateGroupCall &data);
|
||||
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
||||
void handleRequestError(const MTP::Error &error);
|
||||
void handleControllerError(const QString &error);
|
||||
void ensureControllerCreated();
|
||||
@@ -250,6 +254,7 @@ private:
|
||||
|
||||
void checkGlobalShortcutAvailability();
|
||||
void checkJoined();
|
||||
void checkFirstTimeJoined();
|
||||
void notifyAboutAllowedToSpeak();
|
||||
|
||||
void playConnectingSound();
|
||||
@@ -276,6 +281,7 @@ private:
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
not_null<PeerData*> _peer; // Can change in legacy group migration.
|
||||
rpl::event_stream<PeerData*> _peerStream;
|
||||
not_null<History*> _history; // Can change in legacy group migration.
|
||||
MTP::Sender _api;
|
||||
rpl::variable<State> _state = State::Creating;
|
||||
@@ -304,6 +310,7 @@ private:
|
||||
uint64 _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint32 _mySsrc = 0;
|
||||
base::flat_set<uint32> _mySsrcs;
|
||||
mtpRequestId _createRequestId = 0;
|
||||
mtpRequestId _updateMuteRequestId = 0;
|
||||
|
||||
|
||||
@@ -354,8 +354,6 @@ private:
|
||||
rpl::event_stream<VolumeRequest> _changeVolumeRequests;
|
||||
rpl::event_stream<not_null<PeerData*>> _kickParticipantRequests;
|
||||
rpl::variable<int> _fullCount = 1;
|
||||
rpl::variable<int> _fullCountMin = 0;
|
||||
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
|
||||
|
||||
not_null<QWidget*> _menuParent;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
@@ -954,19 +952,11 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||
delegate()->peerListRefreshRows();
|
||||
});
|
||||
if (const auto row = findRow(event.wasJoinAs)) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
if (const auto min = _fullCountMin.current()) {
|
||||
_fullCountMin = min - 1;
|
||||
}
|
||||
}
|
||||
removeRow(row);
|
||||
}
|
||||
if (findRow(event.nowJoinAs)) {
|
||||
return;
|
||||
} else if (auto row = createRowForMe()) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
_fullCountMin = _fullCountMin.current() + 1;
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}, _lifetime);
|
||||
@@ -976,13 +966,7 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_realCallRawValue = real;
|
||||
_realId = real->id();
|
||||
|
||||
_fullCount = rpl::combine(
|
||||
real->fullCountValue(),
|
||||
_fullCountMin.value(),
|
||||
_fullCountMax.value()
|
||||
) | rpl::map([](int value, int min, int max) {
|
||||
return std::max(std::clamp(value, min, max), 1);
|
||||
});
|
||||
_fullCount = real->fullCountValue();
|
||||
|
||||
real->participantsSliceAdded(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1003,9 +987,6 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
if (isMe(participantPeer)) {
|
||||
updateRow(row, nullptr);
|
||||
} else {
|
||||
if (const auto min = _fullCountMin.current()) {
|
||||
_fullCountMin = min - 1;
|
||||
}
|
||||
removeRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
@@ -1043,37 +1024,41 @@ void MembersController::appendInvitedUsers() {
|
||||
void MembersController::updateRow(
|
||||
const std::optional<Data::GroupCall::Participant> &was,
|
||||
const Data::GroupCall::Participant &now) {
|
||||
auto reorderIfInvitedBeforeIndex = 0;
|
||||
auto countChange = 0;
|
||||
auto reorderIfInvitedBefore = 0;
|
||||
auto checkPosition = (Row*)nullptr;
|
||||
auto addedToBottom = (Row*)nullptr;
|
||||
if (const auto row = findRow(now.peer)) {
|
||||
if (row->state() == Row::State::Invited) {
|
||||
reorderIfInvitedBeforeIndex = row->absoluteIndex();
|
||||
countChange = 1;
|
||||
reorderIfInvitedBefore = row->absoluteIndex();
|
||||
}
|
||||
updateRow(row, &now);
|
||||
if ((now.speaking && (!was || !was->speaking))
|
||||
|| (now.raisedHandRating != (was ? was->raisedHandRating : 0))
|
||||
|| (!now.canSelfUnmute && was && was->canSelfUnmute)) {
|
||||
checkRowPosition(row);
|
||||
checkPosition = row;
|
||||
}
|
||||
} else if (auto row = createRow(now)) {
|
||||
if (row->speaking()) {
|
||||
delegate()->peerListPrependRow(std::move(row));
|
||||
} else {
|
||||
reorderIfInvitedBeforeIndex = delegate()->peerListFullRowsCount();
|
||||
reorderIfInvitedBefore = delegate()->peerListFullRowsCount();
|
||||
if (now.raisedHandRating != 0) {
|
||||
checkPosition = row.get();
|
||||
} else {
|
||||
addedToBottom = row.get();
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
countChange = 1;
|
||||
}
|
||||
static constexpr auto kInvited = Row::State::Invited;
|
||||
const auto reorder = [&] {
|
||||
const auto count = reorderIfInvitedBeforeIndex;
|
||||
const auto count = reorderIfInvitedBefore;
|
||||
if (count <= 0) {
|
||||
return false;
|
||||
}
|
||||
const auto row = delegate()->peerListRowAt(
|
||||
reorderIfInvitedBeforeIndex - 1).get();
|
||||
reorderIfInvitedBefore - 1).get();
|
||||
return (static_cast<Row*>(row)->state() == kInvited);
|
||||
}();
|
||||
if (reorder) {
|
||||
@@ -1081,12 +1066,25 @@ void MembersController::updateRow(
|
||||
return static_cast<const Row&>(row).state() != kInvited;
|
||||
});
|
||||
}
|
||||
if (countChange) {
|
||||
const auto fullCountMin = _fullCountMin.current() + countChange;
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
if (checkPosition) {
|
||||
checkRowPosition(checkPosition);
|
||||
} else if (addedToBottom) {
|
||||
const auto real = resolvedRealCall();
|
||||
if (real && real->joinedToTop()) {
|
||||
const auto proj = [&](const PeerListRow &other) {
|
||||
const auto &real = static_cast<const Row&>(other);
|
||||
return real.speaking()
|
||||
? 2
|
||||
: (&real == addedToBottom)
|
||||
? 1
|
||||
: 0;
|
||||
};
|
||||
delegate()->peerListSortRows([&](
|
||||
const PeerListRow &a,
|
||||
const PeerListRow &b) {
|
||||
return proj(a) > proj(b);
|
||||
});
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1188,7 +1186,7 @@ void MembersController::checkRowPosition(not_null<Row*> row) {
|
||||
// All force muted at the bottom, but 'row' still above others.
|
||||
? (&real == row.get() ? 1ULL : 0ULL)
|
||||
// All not force-muted lie between raised hands and speaking.
|
||||
: (std::numeric_limits<uint64>::max() - 2);
|
||||
: (kTop - 2);
|
||||
};
|
||||
const auto projForOther = [&](const PeerListRow &other) {
|
||||
const auto &real = static_cast<const Row&>(other);
|
||||
@@ -1243,11 +1241,6 @@ void MembersController::updateRow(
|
||||
_soundingAnimation.stop();
|
||||
}
|
||||
|
||||
if (!participant && wasInChat) {
|
||||
if (const auto min = _fullCountMin.current()) {
|
||||
_fullCountMin = min - 1;
|
||||
}
|
||||
}
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
|
||||
@@ -1293,7 +1286,6 @@ void MembersController::prepare() {
|
||||
; real && call && real->id() == call->id()) {
|
||||
prepareRows(real);
|
||||
} else if (auto row = createRowForMe()) {
|
||||
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
@@ -1314,7 +1306,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
auto foundMe = false;
|
||||
auto changed = false;
|
||||
const auto &participants = real->participants();
|
||||
auto fullCountMin = 0;
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto row = delegate()->peerListRowAt(i);
|
||||
@@ -1329,7 +1320,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
participantPeer,
|
||||
&Data::GroupCall::Participant::peer);
|
||||
if (contains) {
|
||||
++fullCountMin;
|
||||
++i;
|
||||
} else {
|
||||
changed = true;
|
||||
@@ -1348,9 +1338,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
? createRow(*i)
|
||||
: createRowForMe();
|
||||
if (row) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
++fullCountMin;
|
||||
}
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
@@ -1358,20 +1345,12 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
}
|
||||
for (const auto &participant : participants) {
|
||||
if (auto row = createRow(participant)) {
|
||||
++fullCountMin;
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
delegate()->peerListRefreshRows();
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
if (real->participantsLoaded()) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1426,8 +1405,10 @@ void MembersController::scheduleRaisedHandStatusRemove() {
|
||||
static_cast<Row*>(row)->clearRaisedHandStatus();
|
||||
}
|
||||
i = _raisedHandStatusRemoveAt.erase(i);
|
||||
} else if (!waiting || waiting > (i->second - now)) {
|
||||
waiting = i->second - now;
|
||||
} else {
|
||||
if (!waiting || waiting > (i->second - now)) {
|
||||
waiting = i->second - now;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
@@ -1616,7 +1597,11 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||
}
|
||||
} else {
|
||||
result->addAction(
|
||||
tr::lng_context_view_profile(tr::now),
|
||||
(participantPeer->isUser()
|
||||
? tr::lng_context_view_profile(tr::now)
|
||||
: participantPeer->isBroadcast()
|
||||
? tr::lng_context_view_channel(tr::now)
|
||||
: tr::lng_context_view_group(tr::now)),
|
||||
showProfile);
|
||||
if (participantPeer->isUser()) {
|
||||
result->addAction(
|
||||
|
||||
@@ -476,7 +476,7 @@ void Panel::initControls() {
|
||||
}
|
||||
});
|
||||
|
||||
_settings->setText(tr::lng_menu_settings());
|
||||
_settings->setText(tr::lng_group_call_settings());
|
||||
_hangup->setText(tr::lng_group_call_leave());
|
||||
|
||||
_members->desiredHeightValue(
|
||||
@@ -1132,7 +1132,9 @@ void Panel::refreshTitle() {
|
||||
widget(),
|
||||
tr::lng_group_call_members(
|
||||
lt_count_decimal,
|
||||
_members->fullCountValue() | tr::to_count()),
|
||||
_members->fullCountValue() | rpl::map([](int value) {
|
||||
return (value > 0) ? float64(value) : 1.;
|
||||
})),
|
||||
st::groupCallSubtitleLabel);
|
||||
_subtitle->show();
|
||||
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
@@ -323,9 +323,9 @@ void Instance::handleUpdate(
|
||||
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
|
||||
handleSignalingData(session, data);
|
||||
}, [&](const MTPDupdateGroupCall &data) {
|
||||
handleGroupCallUpdate(session, data.vcall());
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
handleGroupCallUpdate(session, data);
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Update type in Calls::Instance::handleUpdate.");
|
||||
});
|
||||
@@ -410,31 +410,38 @@ void Instance::handleCallUpdate(
|
||||
|
||||
void Instance::handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPGroupCall &call) {
|
||||
const auto callId = call.match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
const MTPUpdate &update) {
|
||||
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
|
||||
return data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
}, [](const MTPDupdateGroupCallParticipants &data) {
|
||||
return data.vcall().match([&](const MTPDinputGroupCall &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
}, [](const auto &) -> uint64 {
|
||||
Unexpected("Type in Instance::handleGroupCallUpdate.");
|
||||
});
|
||||
if (const auto existing = session->data().groupCall(callId)) {
|
||||
existing->applyUpdate(call);
|
||||
existing->enqueueUpdate(update);
|
||||
} else {
|
||||
applyGroupCallUpdateChecked(session, update);
|
||||
}
|
||||
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
_currentGroupCall->handleUpdate(call);
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::handleGroupCallUpdate(
|
||||
void Instance::applyGroupCallUpdateChecked(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
const auto callId = update.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
if (const auto existing = session->data().groupCall(callId)) {
|
||||
existing->applyUpdate(update);
|
||||
}
|
||||
const MTPUpdate &update) {
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)
|
||||
&& (_currentGroupCall->id() == callId)) {
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
_currentGroupCall->handleUpdate(update);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ public:
|
||||
void handleUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
|
||||
// Called by Data::GroupCall when it is appropriate by the 'version'.
|
||||
void applyGroupCallUpdateChecked(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
|
||||
void showInfoPanel(not_null<Call*> call);
|
||||
void showInfoPanel(not_null<GroupCall*> call);
|
||||
[[nodiscard]] Call *currentCall() const;
|
||||
@@ -130,10 +136,7 @@ private:
|
||||
const MTPDupdatePhoneCallSignalingData &data);
|
||||
void handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPGroupCall &call);
|
||||
void handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
const MTPUpdate &update);
|
||||
|
||||
DhConfig _dhConfig;
|
||||
|
||||
|
||||
@@ -127,6 +127,26 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Fix voice chat admin menu on macOS.\n"
|
||||
},
|
||||
{
|
||||
2006004,
|
||||
"- Fix freeze in voice chats.\n"
|
||||
|
||||
"- Make default interface scale 110% on macOS Retina screens.\n"
|
||||
},
|
||||
{
|
||||
2006005,
|
||||
"- Improvements and fixes in new voice chat features.\n"
|
||||
},
|
||||
{
|
||||
2006007,
|
||||
"- Improve voice chat participants list updating.\n"
|
||||
},
|
||||
{
|
||||
2006008,
|
||||
"- Fix connecting and getting allowed to speak on voice chats.\n"
|
||||
|
||||
"- MPRIS support on Linux.\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ void Sandbox::setupScreenScale() {
|
||||
}
|
||||
style::SetDevicePixelRatio(int(ratio));
|
||||
if (Platform::IsMac() && ratio == 2.) {
|
||||
cSetScreenScale(120); // 120% for Retina screens by default.
|
||||
cSetScreenScale(110); // 110% for Retina screens by default.
|
||||
} else {
|
||||
cSetScreenScale(style::kScaleDefault);
|
||||
}
|
||||
|
||||
@@ -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 = 2006003;
|
||||
constexpr auto AppVersionStr = "2.6.3";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 2007000;
|
||||
constexpr auto AppVersionStr = "2.7";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -52,7 +52,8 @@ auto ChatData::defaultAdminRights(not_null<UserData*> user) -> AdminRights {
|
||||
const auto isCreator = (creator == user->bareId())
|
||||
|| (user->isSelf() && amCreator());
|
||||
using Flag = AdminRight;
|
||||
return Flag::f_change_info
|
||||
return Flag::f_other
|
||||
| Flag::f_change_info
|
||||
| Flag::f_delete_messages
|
||||
| Flag::f_ban_users
|
||||
| Flag::f_invite_users
|
||||
|
||||
@@ -22,9 +22,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestPerPage = 30;
|
||||
constexpr auto kRequestPerPage = 50;
|
||||
constexpr auto kSpeakingAfterActive = crl::time(6000);
|
||||
constexpr auto kActiveAfterJoined = crl::time(1000);
|
||||
constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) {
|
||||
return call.match([&](const MTPDphone_groupCall &data) {
|
||||
return qs(data.vparticipants_next_offset());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -35,6 +42,7 @@ GroupCall::GroupCall(
|
||||
: _id(id)
|
||||
, _accessHash(accessHash)
|
||||
, _peer(peer)
|
||||
, _reloadByQueuedUpdatesTimer([=] { reload(); })
|
||||
, _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) {
|
||||
}
|
||||
|
||||
@@ -48,6 +56,10 @@ uint64 GroupCall::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
bool GroupCall::loaded() const {
|
||||
return _version > 0;
|
||||
}
|
||||
|
||||
not_null<PeerData*> GroupCall::peer() const {
|
||||
return _peer;
|
||||
}
|
||||
@@ -69,21 +81,24 @@ auto GroupCall::participants() const
|
||||
}
|
||||
|
||||
void GroupCall::requestParticipants() {
|
||||
if (_participantsRequestId || _reloadRequestId) {
|
||||
return;
|
||||
} else if (_participants.size() >= _fullCount.current() && _allReceived) {
|
||||
return;
|
||||
} else if (_allReceived) {
|
||||
reload();
|
||||
return;
|
||||
if (!_savedFull) {
|
||||
if (_participantsRequestId || _reloadRequestId) {
|
||||
return;
|
||||
} else if (_allParticipantsLoaded) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
|
||||
input(),
|
||||
MTP_vector<MTPInputPeer>(), // ids
|
||||
MTP_vector<MTPint>(), // ssrcs
|
||||
MTP_string(_nextOffset),
|
||||
MTP_string(_savedFull
|
||||
? ExtractNextOffset(*_savedFull)
|
||||
: _nextOffset),
|
||||
MTP_int(kRequestPerPage)
|
||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||
_participantsRequestId = 0;
|
||||
processSavedFullCall();
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
_nextOffset = qs(data.vnext_offset());
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
@@ -91,26 +106,40 @@ void GroupCall::requestParticipants() {
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
_fullCount = data.vcount().v;
|
||||
if (!_allReceived
|
||||
&& (data.vparticipants().v.size() < kRequestPerPage)) {
|
||||
_allReceived = true;
|
||||
}
|
||||
if (_allReceived) {
|
||||
_fullCount = _participants.size();
|
||||
setServerParticipantsCount(data.vcount().v);
|
||||
if (data.vparticipants().v.isEmpty()) {
|
||||
_allParticipantsLoaded = true;
|
||||
}
|
||||
finishParticipantsSliceRequest();
|
||||
});
|
||||
_participantsSliceAdded.fire({});
|
||||
_participantsRequestId = 0;
|
||||
changePeerEmptyCallFlag();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_fullCount = _participants.size();
|
||||
_allReceived = true;
|
||||
_participantsRequestId = 0;
|
||||
changePeerEmptyCallFlag();
|
||||
processSavedFullCall();
|
||||
setServerParticipantsCount(_participants.size());
|
||||
_allParticipantsLoaded = true;
|
||||
finishParticipantsSliceRequest();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::processSavedFullCall() {
|
||||
if (!_savedFull) {
|
||||
return;
|
||||
}
|
||||
_reloadRequestId = 0;
|
||||
processFullCallFields(*base::take(_savedFull));
|
||||
}
|
||||
|
||||
void GroupCall::finishParticipantsSliceRequest() {
|
||||
computeParticipantsCount();
|
||||
processQueuedUpdates();
|
||||
_participantsSliceAdded.fire({});
|
||||
}
|
||||
|
||||
void GroupCall::setServerParticipantsCount(int count) {
|
||||
_serverParticipantsCount = count;
|
||||
changePeerEmptyCallFlag();
|
||||
}
|
||||
|
||||
void GroupCall::changePeerEmptyCallFlag() {
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
@@ -118,7 +147,7 @@ void GroupCall::changePeerEmptyCallFlag() {
|
||||
constexpr auto channelFlag = MTPDchannel::Flag::f_call_not_empty;
|
||||
if (_peer->groupCall() != this) {
|
||||
return;
|
||||
} else if (fullCount() > 0) {
|
||||
} else if (_serverParticipantsCount > 0) {
|
||||
if (chat && !(chat->flags() & chatFlag)) {
|
||||
chat->addFlags(chatFlag);
|
||||
chat->session().changes().peerUpdated(
|
||||
@@ -152,7 +181,7 @@ rpl::producer<int> GroupCall::fullCountValue() const {
|
||||
}
|
||||
|
||||
bool GroupCall::participantsLoaded() const {
|
||||
return _allReceived;
|
||||
return _allParticipantsLoaded;
|
||||
}
|
||||
|
||||
PeerData *GroupCall::participantPeerBySsrc(uint32 ssrc) const {
|
||||
@@ -169,82 +198,269 @@ auto GroupCall::participantUpdated() const
|
||||
return _participantUpdates.events();
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPGroupCall &update) {
|
||||
applyCall(update, false);
|
||||
void GroupCall::enqueueUpdate(const MTPUpdate &update) {
|
||||
update.match([&](const MTPDupdateGroupCall &updateData) {
|
||||
updateData.vcall().match([&](const MTPDgroupCall &data) {
|
||||
const auto version = data.vversion().v;
|
||||
if (!_applyingQueuedUpdates
|
||||
&& (!_version || _version == version)) {
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Apply updateGroupCall %1 -> %2"
|
||||
).arg(_version
|
||||
).arg(version));
|
||||
applyEnqueuedUpdate(update);
|
||||
} else if (!_version || _version <= version) {
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Queue updateGroupCall %1 -> %2"
|
||||
).arg(_version
|
||||
).arg(version));
|
||||
const auto type = QueuedType::Call;
|
||||
_queuedUpdates.emplace(std::pair{ version, type }, update);
|
||||
}
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
discard(data);
|
||||
});
|
||||
}, [&](const MTPDupdateGroupCallParticipants &updateData) {
|
||||
const auto version = updateData.vversion().v;
|
||||
const auto proj = [](const MTPGroupCallParticipant &data) {
|
||||
return data.match([&](const MTPDgroupCallParticipant &data) {
|
||||
return data.is_versioned();
|
||||
});
|
||||
};
|
||||
const auto increment = ranges::contains(
|
||||
updateData.vparticipants().v,
|
||||
true,
|
||||
proj);
|
||||
const auto required = increment ? (version - 1) : version;
|
||||
if (!_applyingQueuedUpdates && (_version == required)) {
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Apply updateGroupCallParticipant %1 (%2)"
|
||||
).arg(_version
|
||||
).arg(Logs::b(increment)));
|
||||
applyEnqueuedUpdate(update);
|
||||
} else if (_version <= required) {
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Queue updateGroupCallParticipant %1 -> %2 (%3)"
|
||||
).arg(_version
|
||||
).arg(version
|
||||
).arg(Logs::b(increment)));
|
||||
const auto type = increment
|
||||
? QueuedType::VersionedParticipant
|
||||
: QueuedType::Participant;
|
||||
_queuedUpdates.emplace(std::pair{ version, type }, update);
|
||||
}
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in GroupCall::enqueueUpdate.");
|
||||
});
|
||||
processQueuedUpdates();
|
||||
}
|
||||
|
||||
void GroupCall::applyCall(const MTPGroupCall &call, bool force) {
|
||||
call.match([&](const MTPDgroupCall &data) {
|
||||
if (!_version) {
|
||||
_version = data.vversion().v;
|
||||
}
|
||||
const auto title = qs(data.vtitle().value_or_empty());
|
||||
const auto recordDate = data.vrecord_start_date().value_or_empty();
|
||||
const auto changed = (_joinMuted != data.is_join_muted())
|
||||
|| (_fullCount.current() != data.vparticipants_count().v)
|
||||
|| (_canChangeJoinMuted != data.is_can_change_join_muted())
|
||||
|| (_title.current() != title)
|
||||
|| (_recordStartDate.current() != recordDate);
|
||||
if (!force && !changed) {
|
||||
return;
|
||||
} else if (!force && _version > data.vversion().v) {
|
||||
reload();
|
||||
return;
|
||||
}
|
||||
_joinMuted = data.is_join_muted();
|
||||
_canChangeJoinMuted = data.is_can_change_join_muted();
|
||||
_fullCount = data.vparticipants_count().v;
|
||||
_title = title;
|
||||
_recordStartDate = recordDate;
|
||||
changePeerEmptyCallFlag();
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
const auto id = _id;
|
||||
const auto peer = _peer;
|
||||
crl::on_main(&peer->session(), [=] {
|
||||
if (peer->groupCall() && peer->groupCall()->id() == id) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->clearGroupCall();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->clearGroupCall();
|
||||
}
|
||||
void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
|
||||
const auto id = _id;
|
||||
const auto peer = _peer;
|
||||
crl::on_main(&peer->session(), [=] {
|
||||
if (peer->groupCall() && peer->groupCall()->id() == id) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->clearGroupCall();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->clearGroupCall();
|
||||
}
|
||||
}
|
||||
});
|
||||
Core::App().calls().applyGroupCallUpdateChecked(
|
||||
&peer->session(),
|
||||
MTP_updateGroupCall(
|
||||
MTP_int(peer->bareId()),
|
||||
MTP_groupCallDiscarded(
|
||||
data.vid(),
|
||||
data.vaccess_hash(),
|
||||
data.vduration())));
|
||||
}
|
||||
|
||||
void GroupCall::processFullCallUsersChats(const MTPphone_GroupCall &call) {
|
||||
call.match([&](const MTPDphone_groupCall &data) {
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_peer->owner().processChats(data.vchats());
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) {
|
||||
call.match([&](const MTPDphone_groupCall &data) {
|
||||
const auto &participants = data.vparticipants().v;
|
||||
const auto nextOffset = qs(data.vparticipants_next_offset());
|
||||
data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
_participants.clear();
|
||||
_speakingByActiveFinishes.clear();
|
||||
_participantPeerBySsrc.clear();
|
||||
_allParticipantsLoaded = false;
|
||||
|
||||
applyParticipantsSlice(
|
||||
participants,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
_nextOffset = nextOffset;
|
||||
|
||||
applyCallFields(data);
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
discard(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
|
||||
call.match([&](const MTPDphone_groupCall &data) {
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_peer->owner().processChats(data.vchats());
|
||||
_participants.clear();
|
||||
_speakingByActiveFinishes.clear();
|
||||
_participantPeerBySsrc.clear();
|
||||
processFullCallUsersChats(call);
|
||||
processFullCallFields(call);
|
||||
finishParticipantsSliceRequest();
|
||||
}
|
||||
|
||||
void GroupCall::applyCallFields(const MTPDgroupCall &data) {
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Set from groupCall %1 -> %2"
|
||||
).arg(_version
|
||||
).arg(data.vversion().v));
|
||||
_version = data.vversion().v;
|
||||
if (!_version) {
|
||||
LOG(("API Error: Got zero version in groupCall."));
|
||||
_version = 1;
|
||||
}
|
||||
_joinMuted = data.is_join_muted();
|
||||
_canChangeJoinMuted = data.is_can_change_join_muted();
|
||||
_joinedToTop = !data.is_join_date_asc();
|
||||
setServerParticipantsCount(data.vparticipants_count().v);
|
||||
changePeerEmptyCallFlag();
|
||||
_title = qs(data.vtitle().value_or_empty());
|
||||
_recordStartDate = data.vrecord_start_date().value_or_empty();
|
||||
_allParticipantsLoaded
|
||||
= (_serverParticipantsCount == _participants.size());
|
||||
}
|
||||
|
||||
void GroupCall::applyLocalUpdate(
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
applyParticipantsSlice(
|
||||
update.vparticipants().v,
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}
|
||||
|
||||
void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) {
|
||||
Expects(!_applyingQueuedUpdates);
|
||||
|
||||
_applyingQueuedUpdates = true;
|
||||
const auto guard = gsl::finally([&] { _applyingQueuedUpdates = false; });
|
||||
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
applyCallFields(data);
|
||||
computeParticipantsCount();
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
discard(data);
|
||||
});
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Set from updateGroupCallParticipants %1 -> %2"
|
||||
).arg(_version
|
||||
).arg(data.vversion().v));
|
||||
_version = data.vversion().v;
|
||||
if (!_version) {
|
||||
LOG(("API Error: "
|
||||
"Got zero version in updateGroupCallParticipants."));
|
||||
_version = 1;
|
||||
}
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
applyCall(data.vcall(), true);
|
||||
_allReceived = (_fullCount.current() == _participants.size());
|
||||
_participantsSliceAdded.fire({});
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in GroupCall::applyEnqueuedUpdate.");
|
||||
});
|
||||
Core::App().calls().applyGroupCallUpdateChecked(
|
||||
&_peer->session(),
|
||||
update);
|
||||
}
|
||||
|
||||
void GroupCall::processQueuedUpdates() {
|
||||
if (!_version || _applyingQueuedUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = _queuedUpdates.size();
|
||||
while (!_queuedUpdates.empty()) {
|
||||
const auto &entry = _queuedUpdates.front();
|
||||
const auto version = entry.first.first;
|
||||
const auto type = entry.first.second;
|
||||
const auto incremented = (type == QueuedType::VersionedParticipant);
|
||||
if ((version < _version)
|
||||
|| (version == _version && incremented)) {
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
} else if (version == _version
|
||||
|| (version == _version + 1 && incremented)) {
|
||||
const auto update = entry.second;
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
applyEnqueuedUpdate(update);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_queuedUpdates.empty()) {
|
||||
_reloadByQueuedUpdatesTimer.cancel();
|
||||
} else if (_queuedUpdates.size() != size
|
||||
|| !_reloadByQueuedUpdatesTimer.isActive()) {
|
||||
_reloadByQueuedUpdatesTimer.callOnce(kWaitForUpdatesTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::computeParticipantsCount() {
|
||||
_fullCount = _allParticipantsLoaded
|
||||
? int(_participants.size())
|
||||
: std::max(int(_participants.size()), _serverParticipantsCount);
|
||||
}
|
||||
|
||||
void GroupCall::reload() {
|
||||
if (_reloadRequestId) {
|
||||
if (_reloadRequestId || _applyingQueuedUpdates) {
|
||||
return;
|
||||
} else if (_participantsRequestId) {
|
||||
api().request(_participantsRequestId).cancel();
|
||||
_participantsRequestId = 0;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("Group Call Participants: "
|
||||
"Reloading with queued: %1"
|
||||
).arg(_queuedUpdates.size()));
|
||||
|
||||
while (!_queuedUpdates.empty()) {
|
||||
const auto &entry = _queuedUpdates.front();
|
||||
const auto update = entry.second;
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
applyEnqueuedUpdate(update);
|
||||
}
|
||||
_reloadByQueuedUpdatesTimer.cancel();
|
||||
|
||||
_reloadRequestId = api().request(
|
||||
MTPphone_GetGroupCall(input())
|
||||
).done([=](const MTPphone_GroupCall &result) {
|
||||
processFullCall(result);
|
||||
if (requestParticipantsAfterReload(result)) {
|
||||
_savedFull = result;
|
||||
processFullCallUsersChats(result);
|
||||
requestParticipants();
|
||||
return;
|
||||
}
|
||||
_reloadRequestId = 0;
|
||||
processFullCall(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_reloadRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool GroupCall::requestParticipantsAfterReload(
|
||||
const MTPphone_GroupCall &call) const {
|
||||
return call.match([&](const MTPDphone_groupCall &data) {
|
||||
const auto received = data.vparticipants().v.size();
|
||||
const auto size = data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
return data.vparticipants_count().v;
|
||||
}, [](const auto &) {
|
||||
return 0;
|
||||
});
|
||||
return (received < size) && (received < _participants.size());
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::applyParticipantsSlice(
|
||||
const QVector<MTPGroupCallParticipant> &list,
|
||||
ApplySliceSource sliceSource) {
|
||||
@@ -252,7 +468,6 @@ void GroupCall::applyParticipantsSlice(
|
||||
const auto now = base::unixtime::now();
|
||||
const auto speakingAfterActive = TimeId(kSpeakingAfterActive / 1000);
|
||||
|
||||
auto changedCount = _fullCount.current();
|
||||
for (const auto &participant : list) {
|
||||
participant.match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto participantPeerId = peerFromMTP(data.vpeer());
|
||||
@@ -274,8 +489,8 @@ void GroupCall::applyParticipantsSlice(
|
||||
_participantUpdates.fire(std::move(update));
|
||||
}
|
||||
}
|
||||
if (changedCount > _participants.size()) {
|
||||
--changedCount;
|
||||
if (_serverParticipantsCount > 0) {
|
||||
--_serverParticipantsCount;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -338,7 +553,7 @@ void GroupCall::applyParticipantsSlice(
|
||||
*i = value;
|
||||
}
|
||||
if (data.is_just_joined()) {
|
||||
++changedCount;
|
||||
++_serverParticipantsCount;
|
||||
}
|
||||
if (sliceSource != ApplySliceSource::SliceLoaded) {
|
||||
_participantUpdates.fire({
|
||||
@@ -349,8 +564,8 @@ void GroupCall::applyParticipantsSlice(
|
||||
});
|
||||
}
|
||||
if (sliceSource == ApplySliceSource::UpdateReceived) {
|
||||
_fullCount = changedCount;
|
||||
changePeerEmptyCallFlag();
|
||||
computeParticipantsCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,6 +757,7 @@ void GroupCall::requestUnknownParticipants() {
|
||||
).done([=](const MTPphone_GroupParticipants &result) {
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_peer->owner().processChats(data.vchats());
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::UnknownLoaded);
|
||||
@@ -605,41 +821,6 @@ bool GroupCall::inCall() const {
|
||||
&& (current->state() == Calls::GroupCall::State::Joined);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
|
||||
const auto version = update.vversion().v;
|
||||
const auto applyUpdate = [&] {
|
||||
if (version < _version) {
|
||||
return false;
|
||||
}
|
||||
auto versionShouldIncrement = false;
|
||||
for (const auto &participant : update.vparticipants().v) {
|
||||
const auto versioned = participant.match([&](
|
||||
const MTPDgroupCallParticipant &data) {
|
||||
return data.is_versioned();
|
||||
});
|
||||
if (versioned) {
|
||||
versionShouldIncrement = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return versionShouldIncrement
|
||||
? (version == _version + 1)
|
||||
: (version == _version);
|
||||
}();
|
||||
if (!applyUpdate) {
|
||||
return;
|
||||
}
|
||||
_version = version;
|
||||
applyUpdateChecked(update);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdateChecked(
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
applyParticipantsSlice(
|
||||
update.vparticipants().v,
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}
|
||||
|
||||
void GroupCall::setJoinMutedLocally(bool muted) {
|
||||
_joinMuted = muted;
|
||||
}
|
||||
@@ -652,6 +833,10 @@ bool GroupCall::canChangeJoinMuted() const {
|
||||
return _canChangeJoinMuted;
|
||||
}
|
||||
|
||||
bool GroupCall::joinedToTop() const {
|
||||
return _joinedToTop;
|
||||
}
|
||||
|
||||
ApiWrap &GroupCall::api() const {
|
||||
return _peer->session().api();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
~GroupCall();
|
||||
|
||||
[[nodiscard]] uint64 id() const;
|
||||
[[nodiscard]] bool loaded() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
[[nodiscard]] MTPInputGroupCall input() const;
|
||||
[[nodiscard]] QString title() const {
|
||||
@@ -82,10 +83,10 @@ public:
|
||||
[[nodiscard]] rpl::producer<> participantsSliceAdded();
|
||||
[[nodiscard]] rpl::producer<ParticipantUpdate> participantUpdated() const;
|
||||
|
||||
void applyUpdate(const MTPGroupCall &update);
|
||||
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
|
||||
void applyUpdateChecked(
|
||||
void enqueueUpdate(const MTPUpdate &update);
|
||||
void applyLocalUpdate(
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
|
||||
void applyLastSpoke(uint32 ssrc, LastSpokeTimes when, crl::time now);
|
||||
void applyActiveUpdate(
|
||||
PeerId participantPeerId,
|
||||
@@ -104,6 +105,7 @@ public:
|
||||
void setJoinMutedLocally(bool muted);
|
||||
[[nodiscard]] bool joinMuted() const;
|
||||
[[nodiscard]] bool canChangeJoinMuted() const;
|
||||
[[nodiscard]] bool joinedToTop() const;
|
||||
|
||||
private:
|
||||
enum class ApplySliceSource {
|
||||
@@ -111,16 +113,32 @@ private:
|
||||
UnknownLoaded,
|
||||
UpdateReceived,
|
||||
};
|
||||
enum class QueuedType : uint8 {
|
||||
VersionedParticipant,
|
||||
Participant,
|
||||
Call,
|
||||
};
|
||||
[[nodiscard]] ApiWrap &api() const;
|
||||
|
||||
void discard(const MTPDgroupCallDiscarded &data);
|
||||
[[nodiscard]] bool inCall() const;
|
||||
void applyCall(const MTPGroupCall &call, bool force);
|
||||
void applyParticipantsSlice(
|
||||
const QVector<MTPGroupCallParticipant> &list,
|
||||
ApplySliceSource sliceSource);
|
||||
void requestUnknownParticipants();
|
||||
void changePeerEmptyCallFlag();
|
||||
void checkFinishSpeakingByActive();
|
||||
void applyCallFields(const MTPDgroupCall &data);
|
||||
void applyEnqueuedUpdate(const MTPUpdate &update);
|
||||
void setServerParticipantsCount(int count);
|
||||
void computeParticipantsCount();
|
||||
void processQueuedUpdates();
|
||||
void processFullCallUsersChats(const MTPphone_GroupCall &call);
|
||||
void processFullCallFields(const MTPphone_GroupCall &call);
|
||||
[[nodiscard]] bool requestParticipantsAfterReload(
|
||||
const MTPphone_GroupCall &call) const;
|
||||
void processSavedFullCall();
|
||||
void finishParticipantsSliceRequest();
|
||||
|
||||
const uint64 _id = 0;
|
||||
const uint64 _accessHash = 0;
|
||||
@@ -131,11 +149,18 @@ private:
|
||||
mtpRequestId _reloadRequestId = 0;
|
||||
rpl::variable<QString> _title;
|
||||
|
||||
base::flat_multi_map<
|
||||
std::pair<int, QueuedType>,
|
||||
MTPUpdate> _queuedUpdates;
|
||||
base::Timer _reloadByQueuedUpdatesTimer;
|
||||
std::optional<MTPphone_GroupCall> _savedFull;
|
||||
|
||||
std::vector<Participant> _participants;
|
||||
base::flat_map<uint32, not_null<PeerData*>> _participantPeerBySsrc;
|
||||
base::flat_map<not_null<PeerData*>, crl::time> _speakingByActiveFinishes;
|
||||
base::Timer _speakingByActiveFinishTimer;
|
||||
QString _nextOffset;
|
||||
int _serverParticipantsCount = 0;
|
||||
rpl::variable<int> _fullCount = 0;
|
||||
rpl::variable<TimeId> _recordStartDate = 0;
|
||||
|
||||
@@ -148,7 +173,9 @@ private:
|
||||
|
||||
bool _joinMuted = false;
|
||||
bool _canChangeJoinMuted = true;
|
||||
bool _allReceived = false;
|
||||
bool _allParticipantsLoaded = false;
|
||||
bool _joinedToTop = false;
|
||||
bool _applyingQueuedUpdates = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -535,7 +535,10 @@ void ValidateSlice(
|
||||
).arg(from
|
||||
).arg(index
|
||||
).arg(till) + strings.join(","));
|
||||
Unexpected("Bad slice in GroupThumbs.");
|
||||
if (Logs::DebugEnabled()) {
|
||||
Unexpected("Bad slice in GroupThumbs.");
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
keys.emplace(key);
|
||||
}
|
||||
@@ -555,9 +558,7 @@ void GroupThumbs::fillItems(
|
||||
const auto current = (index - from);
|
||||
const auto old = base::take(_items);
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
ValidateSlice(slice, _context, from, index, till);
|
||||
}
|
||||
//ValidateSlice(slice, _context, from, index, till);
|
||||
|
||||
markCacheStale();
|
||||
_items.reserve(till - from);
|
||||
@@ -586,17 +587,21 @@ void GroupThumbs::animateAliveItems(int current) {
|
||||
}
|
||||
|
||||
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
|
||||
Expects(_cache.size() >= _items.size());
|
||||
//Expects(_cache.size() >= _items.size());
|
||||
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
if (_cache.size() >= _items.size()) {
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
}
|
||||
animatePreviouslyAlive(old);
|
||||
markRestAsDying();
|
||||
}
|
||||
|
||||
void GroupThumbs::markRestAsDying() {
|
||||
Expects(_cache.size() >= _items.size());
|
||||
//Expects(_cache.size() >= _items.size());
|
||||
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
if (_cache.size() >= _items.size()) {
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
}
|
||||
for (const auto &cacheItem : _cache) {
|
||||
const auto &thumb = cacheItem.second;
|
||||
const auto state = thumb->state();
|
||||
|
||||
@@ -39,7 +39,7 @@ void UnsafeOpenUrl(const QString &url) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProcess::execute(qsl("xdg-open"), { url });
|
||||
QProcess::startDetached(qsl("xdg-open"), { url });
|
||||
}
|
||||
|
||||
void UnsafeOpenEmailLink(const QString &email) {
|
||||
@@ -83,7 +83,7 @@ void UnsafeLaunch(const QString &filepath) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProcess::execute(qsl("xdg-open"), { qUrlPath.toEncoded() });
|
||||
QProcess::startDetached(qsl("xdg-open"), { qUrlPath.toEncoded() });
|
||||
}
|
||||
|
||||
} // namespace File
|
||||
|
||||
461
Telegram/SourceFiles/platform/linux/linux_mpris_support.cpp
Normal file
461
Telegram/SourceFiles/platform/linux/linux_mpris_support.cpp
Normal file
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
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 "platform/linux/linux_mpris_support.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "data/data_document.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "mainwindow.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
constexpr auto kService = "org.mpris.MediaPlayer2.tdesktop"_cs;
|
||||
constexpr auto kObjectPath = "/org/mpris/MediaPlayer2"_cs;
|
||||
constexpr auto kFakeTrackPath = "/org/telegram/desktop/track/0"_cs;
|
||||
constexpr auto kInterface = "org.mpris.MediaPlayer2"_cs;
|
||||
constexpr auto kPlayerInterface = "org.mpris.MediaPlayer2.Player"_cs;
|
||||
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
|
||||
constexpr auto kSongType = AudioMsgId::Type::Song;
|
||||
|
||||
constexpr auto kIntrospectionXML = R"INTROSPECTION(<node>
|
||||
<interface name='org.mpris.MediaPlayer2'>
|
||||
<method name='Raise'/>
|
||||
<method name='Quit'/>
|
||||
<property name='CanQuit' type='b' access='read'/>
|
||||
<property name='CanRaise' type='b' access='read'/>
|
||||
<property name='HasTrackList' type='b' access='read'/>
|
||||
<property name='Identity' type='s' access='read'/>
|
||||
<property name='DesktopEntry' type='s' access='read'/>
|
||||
<property name='SupportedUriSchemes' type='as' access='read'/>
|
||||
<property name='SupportedMimeTypes' type='as' access='read'/>
|
||||
<property name='Fullscreen' type='b' access='readwrite'/>
|
||||
<property name='CanSetFullscreen' type='b' access='read'/>
|
||||
</interface>
|
||||
</node>)INTROSPECTION"_cs;
|
||||
|
||||
constexpr auto kPlayerIntrospectionXML = R"INTROSPECTION(<node>
|
||||
<interface name='org.mpris.MediaPlayer2.Player'>
|
||||
<method name='Next'/>
|
||||
<method name='Previous'/>
|
||||
<method name='Pause'/>
|
||||
<method name='PlayPause'/>
|
||||
<method name='Stop'/>
|
||||
<method name='Play'/>
|
||||
<method name='Seek'>
|
||||
<arg direction='in' name='Offset' type='x'/>
|
||||
</method>
|
||||
<method name='SetPosition'>
|
||||
<arg direction='in' name='TrackId' type='o'/>
|
||||
<arg direction='in' name='Position' type='x'/>
|
||||
</method>
|
||||
<method name='OpenUri'>
|
||||
<arg direction='in' name='Uri' type='s'/>
|
||||
</method>
|
||||
<signal name='Seeked'>
|
||||
<arg name='Position' type='x'/>
|
||||
</signal>
|
||||
<property name='PlaybackStatus' type='s' access='read'/>
|
||||
<property name='Rate' type='d' access='readwrite'/>
|
||||
<property name='Metadata' type='a{sv}' access='read'>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
||||
</property>
|
||||
<property name='Volume' type='d' access='readwrite'/>
|
||||
<property name='Position' type='x' access='read'/>
|
||||
<property name='MinimumRate' type='d' access='read'/>
|
||||
<property name='MaximumRate' type='d' access='read'/>
|
||||
<property name='CanGoNext' type='b' access='read'/>
|
||||
<property name='CanGoPrevious' type='b' access='read'/>
|
||||
<property name='CanPlay' type='b' access='read'/>
|
||||
<property name='CanPause' type='b' access='read'/>
|
||||
<property name='CanSeek' type='b' access='read'/>
|
||||
<property name='CanControl' type='b' access='read'/>
|
||||
</interface>
|
||||
</node>)INTROSPECTION"_cs;
|
||||
|
||||
auto CreateMetadata(const Media::Player::TrackState &state) {
|
||||
std::map<Glib::ustring, Glib::VariantBase> result;
|
||||
|
||||
if (!Media::Player::IsStoppedOrStopping(state.state)) {
|
||||
result["mpris:trackid"] = Glib::wrap(g_variant_new_object_path(
|
||||
kFakeTrackPath.utf8().constData()));
|
||||
result["mpris:length"] = Glib::Variant<long>::create(
|
||||
state.length * 1000);
|
||||
|
||||
const auto audioData = state.id.audio();
|
||||
if (audioData) {
|
||||
result["xesam:title"] = Glib::Variant<
|
||||
Glib::ustring
|
||||
>::create(audioData->filename().toStdString());
|
||||
|
||||
if (audioData->isSong()) {
|
||||
const auto songData = audioData->song();
|
||||
if (!songData->performer.isEmpty()) {
|
||||
result["xesam:artist"] = Glib::Variant<
|
||||
std::vector<Glib::ustring>
|
||||
>::create({ songData->performer.toStdString() });
|
||||
}
|
||||
if (!songData->performer.isEmpty()) {
|
||||
result["xesam:title"] = Glib::Variant<
|
||||
Glib::ustring
|
||||
>::create(songData->title.toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto PlaybackStatus(Media::Player::State state) {
|
||||
return (state == Media::Player::State::Playing)
|
||||
? "Playing"
|
||||
: Media::Player::IsPausedOrPausing(state)
|
||||
? "Paused"
|
||||
: "Stopped";
|
||||
}
|
||||
|
||||
void HandleMethodCall(
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &method_name,
|
||||
const Glib::VariantContainerBase ¶meters,
|
||||
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
try {
|
||||
auto parametersCopy = parameters;
|
||||
|
||||
if (method_name == "Quit") {
|
||||
App::quit();
|
||||
} else if (method_name == "Raise") {
|
||||
App::wnd()->showFromTray();
|
||||
} else if (method_name == "Next") {
|
||||
Media::Player::instance()->next();
|
||||
} else if (method_name == "Pause") {
|
||||
Media::Player::instance()->pause();
|
||||
} else if (method_name == "Play") {
|
||||
Media::Player::instance()->play();
|
||||
} else if (method_name == "PlayPause") {
|
||||
Media::Player::instance()->playPause();
|
||||
} else if (method_name == "Previous") {
|
||||
Media::Player::instance()->previous();
|
||||
} else if (method_name == "Seek") {
|
||||
const auto offset = base::Platform::GlibVariantCast<long>(
|
||||
parametersCopy.get_child(0));
|
||||
|
||||
const auto state = Media::Player::instance()->getState(
|
||||
kSongType);
|
||||
|
||||
Media::Player::instance()->finishSeeking(
|
||||
kSongType,
|
||||
float64(state.position * 1000 + offset)
|
||||
/ (state.length * 1000));
|
||||
} else if (method_name == "SetPosition") {
|
||||
const auto position = base::Platform::GlibVariantCast<long>(
|
||||
parametersCopy.get_child(1));
|
||||
|
||||
const auto state = Media::Player::instance()->getState(
|
||||
kSongType);
|
||||
|
||||
Media::Player::instance()->finishSeeking(
|
||||
kSongType,
|
||||
float64(position) / (state.length * 1000));
|
||||
} else if (method_name == "Stop") {
|
||||
Media::Player::instance()->stop();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
invocation->return_value({});
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HandleGetProperty(
|
||||
Glib::VariantBase &property,
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &property_name) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
if (property_name == "CanQuit") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "CanRaise") {
|
||||
property = Glib::Variant<bool>::create(!IsWayland());
|
||||
} else if (property_name == "CanSetFullscreen") {
|
||||
property = Glib::Variant<bool>::create(false);
|
||||
} else if (property_name == "DesktopEntry") {
|
||||
property = Glib::Variant<Glib::ustring>::create(
|
||||
QGuiApplication::desktopFileName().chopped(8).toStdString());
|
||||
} else if (property_name == "Fullscreen") {
|
||||
property = Glib::Variant<bool>::create(false);
|
||||
} else if (property_name == "HasTrackList") {
|
||||
property = Glib::Variant<bool>::create(false);
|
||||
} else if (property_name == "Identity") {
|
||||
property = Glib::Variant<Glib::ustring>::create(
|
||||
std::string(AppName));
|
||||
} else if (property_name == "SupportedMimeTypes") {
|
||||
property = Glib::Variant<std::vector<Glib::ustring>>::create({});
|
||||
} else if (property_name == "SupportedUriSchemes") {
|
||||
property = Glib::Variant<std::vector<Glib::ustring>>::create({});
|
||||
} else if (property_name == "CanControl") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "CanGoNext") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "CanGoPrevious") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "CanPause") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "CanPlay") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "CanSeek") {
|
||||
property = Glib::Variant<bool>::create(true);
|
||||
} else if (property_name == "MaximumRate") {
|
||||
property = Glib::Variant<float64>::create(1.0);
|
||||
} else if (property_name == "Metadata") {
|
||||
const auto state = Media::Player::instance()->getState(
|
||||
kSongType);
|
||||
|
||||
property = base::Platform::MakeGlibVariant(
|
||||
CreateMetadata(state));
|
||||
} else if (property_name == "MinimumRate") {
|
||||
property = Glib::Variant<float64>::create(1.0);
|
||||
} else if (property_name == "PlaybackStatus") {
|
||||
const auto state = Media::Player::instance()->getState(
|
||||
kSongType);
|
||||
|
||||
property = Glib::Variant<Glib::ustring>::create(
|
||||
PlaybackStatus(state.state));
|
||||
} else if (property_name == "Position") {
|
||||
const auto state = Media::Player::instance()->getState(
|
||||
kSongType);
|
||||
|
||||
property = Glib::Variant<long>::create(state.position * 1000);
|
||||
} else if (property_name == "Rate") {
|
||||
property = Glib::Variant<float64>::create(1.0);
|
||||
} else if (property_name == "Volume") {
|
||||
property = Glib::Variant<float64>::create(
|
||||
Core::App().settings().songVolume());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool HandleSetProperty(
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &property_name,
|
||||
const Glib::VariantBase &value) {
|
||||
try {
|
||||
if (property_name == "Fullscreen") {
|
||||
} else if (property_name == "Rate") {
|
||||
} else if (property_name == "Volume") {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
Core::App().settings().setSongVolume(
|
||||
base::Platform::GlibVariantCast<float64>(value));
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const Gio::DBus::InterfaceVTable InterfaceVTable(
|
||||
sigc::ptr_fun(&HandleMethodCall),
|
||||
sigc::ptr_fun(&HandleGetProperty),
|
||||
sigc::ptr_fun(&HandleSetProperty));
|
||||
|
||||
void PlayerPropertyChanged(
|
||||
const Glib::ustring &name,
|
||||
const Glib::VariantBase &value) {
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
connection->emit_signal(
|
||||
std::string(kObjectPath),
|
||||
std::string(kPropertiesInterface),
|
||||
"PropertiesChanged",
|
||||
{},
|
||||
base::Platform::MakeGlibVariant(std::tuple{
|
||||
Glib::ustring(std::string(kPlayerInterface)),
|
||||
std::map<Glib::ustring, Glib::VariantBase>{
|
||||
{ name, value },
|
||||
},
|
||||
std::vector<Glib::ustring>{},
|
||||
}));
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void Seeked(long position) {
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
connection->emit_signal(
|
||||
std::string(kObjectPath),
|
||||
std::string(kPlayerInterface),
|
||||
"Seeked",
|
||||
{},
|
||||
base::Platform::MakeGlibVariant(std::tuple{
|
||||
position,
|
||||
}));
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MPRISSupport::Private {
|
||||
public:
|
||||
void updateTrackState(const Media::Player::TrackState &state);
|
||||
|
||||
Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
|
||||
Glib::RefPtr<Gio::DBus::NodeInfo> introspectionData;
|
||||
Glib::RefPtr<Gio::DBus::NodeInfo> playerIntrospectionData;
|
||||
|
||||
uint ownId = 0;
|
||||
uint registerId = 0;
|
||||
uint playerRegisterId = 0;
|
||||
|
||||
std::map<Glib::ustring, Glib::VariantBase> metadata;
|
||||
Glib::ustring playbackStatus;
|
||||
long position = 0;
|
||||
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
void MPRISSupport::Private::updateTrackState(
|
||||
const Media::Player::TrackState &state) {
|
||||
if (state.id.type() != kSongType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto currentMetadata = CreateMetadata(state);
|
||||
const auto currentPosition = state.position * 1000;
|
||||
const auto currentPlaybackStatus = PlaybackStatus(state.state);
|
||||
|
||||
if (!ranges::equal(currentMetadata, metadata, [&](
|
||||
const auto &item1,
|
||||
const auto &item2) {
|
||||
return item1.first == item2.first
|
||||
&& item1.second.equal(item2.second);
|
||||
})) {
|
||||
metadata = currentMetadata;
|
||||
PlayerPropertyChanged(
|
||||
"Metadata",
|
||||
Glib::Variant<
|
||||
std::map<Glib::ustring, Glib::VariantBase>
|
||||
>::create(metadata));
|
||||
}
|
||||
|
||||
if (currentPlaybackStatus != playbackStatus) {
|
||||
playbackStatus = currentPlaybackStatus;
|
||||
PlayerPropertyChanged(
|
||||
"PlaybackStatus",
|
||||
Glib::Variant<Glib::ustring>::create(playbackStatus));
|
||||
}
|
||||
|
||||
if (currentPosition != position) {
|
||||
const auto positionDifference = position - currentPosition;
|
||||
if (positionDifference > 1000000 || positionDifference < -1000000) {
|
||||
Seeked(currentPosition);
|
||||
}
|
||||
|
||||
position = currentPosition;
|
||||
}
|
||||
}
|
||||
|
||||
MPRISSupport::MPRISSupport()
|
||||
: _private(std::make_unique<Private>()) {
|
||||
try {
|
||||
_private->introspectionData = Gio::DBus::NodeInfo::create_for_xml(
|
||||
std::string(kIntrospectionXML));
|
||||
|
||||
_private->playerIntrospectionData = Gio::DBus::NodeInfo::create_for_xml(
|
||||
std::string(kPlayerIntrospectionXML));
|
||||
|
||||
_private->ownId = Gio::DBus::own_name(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION,
|
||||
std::string(kService));
|
||||
|
||||
_private->dbusConnection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
_private->registerId = _private->dbusConnection->register_object(
|
||||
std::string(kObjectPath),
|
||||
_private->introspectionData->lookup_interface(),
|
||||
InterfaceVTable);
|
||||
|
||||
_private->playerRegisterId = _private->dbusConnection->register_object(
|
||||
std::string(kObjectPath),
|
||||
_private->playerIntrospectionData->lookup_interface(),
|
||||
InterfaceVTable);
|
||||
|
||||
_private->updateTrackState(
|
||||
Media::Player::instance()->getState(kSongType));
|
||||
|
||||
Media::Player::instance()->updatedNotifier(
|
||||
) | rpl::start_with_next([=](
|
||||
const Media::Player::TrackState &state) {
|
||||
_private->updateTrackState(state);
|
||||
}, _private->lifetime);
|
||||
|
||||
Core::App().settings().songVolumeChanges(
|
||||
) | rpl::start_with_next([=](float64 volume) {
|
||||
PlayerPropertyChanged(
|
||||
"Volume",
|
||||
Glib::Variant<float64>::create(volume));
|
||||
}, _private->lifetime);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
MPRISSupport::~MPRISSupport() {
|
||||
if (_private->dbusConnection) {
|
||||
if (_private->playerRegisterId) {
|
||||
_private->dbusConnection->unregister_object(
|
||||
_private->playerRegisterId);
|
||||
}
|
||||
|
||||
if (_private->registerId) {
|
||||
_private->dbusConnection->unregister_object(
|
||||
_private->registerId);
|
||||
}
|
||||
}
|
||||
|
||||
if (_private->ownId) {
|
||||
Gio::DBus::unown_name(_private->ownId);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
24
Telegram/SourceFiles/platform/linux/linux_mpris_support.h
Normal file
24
Telegram/SourceFiles/platform/linux/linux_mpris_support.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
class MPRISSupport {
|
||||
public:
|
||||
MPRISSupport();
|
||||
~MPRISSupport();
|
||||
|
||||
private:
|
||||
class Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "base/platform/linux/base_linux_dbus_utilities.h"
|
||||
#include "platform/linux/linux_notification_service_watcher.h"
|
||||
#include "platform/linux/linux_mpris_support.h"
|
||||
#include "platform/linux/linux_gsd_media_keys.h"
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
@@ -347,12 +348,25 @@ void SetGtkScaleFactor() {
|
||||
|
||||
void SetWatchingMediaKeys(bool watching) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
static std::unique_ptr<internal::GSDMediaKeys> Instance;
|
||||
static std::unique_ptr<internal::MPRISSupport> MPRISInstance;
|
||||
static std::unique_ptr<internal::GSDMediaKeys> GSDInstance;
|
||||
|
||||
if (watching && !Instance) {
|
||||
Instance = std::make_unique<internal::GSDMediaKeys>();
|
||||
} else if (!watching && Instance) {
|
||||
Instance = nullptr;
|
||||
if (watching) {
|
||||
if (!MPRISInstance) {
|
||||
MPRISInstance = std::make_unique<internal::MPRISSupport>();
|
||||
}
|
||||
|
||||
if (!GSDInstance) {
|
||||
GSDInstance = std::make_unique<internal::GSDMediaKeys>();
|
||||
}
|
||||
} else {
|
||||
if (MPRISInstance) {
|
||||
MPRISInstance = nullptr;
|
||||
}
|
||||
|
||||
if (GSDInstance) {
|
||||
GSDInstance = nullptr;
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
@@ -593,6 +607,9 @@ void start() {
|
||||
Glib::init();
|
||||
Gio::init();
|
||||
|
||||
Glib::set_prgname(cExeName().toStdString());
|
||||
Glib::set_application_name(std::string(AppName));
|
||||
|
||||
if (const auto integration = BaseGtkIntegration::Instance()) {
|
||||
integration->prepareEnvironment();
|
||||
} else {
|
||||
|
||||
@@ -190,27 +190,37 @@ void SessionNavigation::showPeerByLinkResolved(
|
||||
MTPchannels_GetFullChannel(peer->asChannel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
_session->api().processFullPeer(peer, result);
|
||||
if (const auto call = peer->groupCall()) {
|
||||
const auto id = call->id();
|
||||
_resolveRequestId = _session->api().request(
|
||||
MTPphone_GetGroupCall(call->input())
|
||||
).done([=](const MTPphone_GroupCall &result) {
|
||||
if (const auto now = peer->groupCall()
|
||||
; now && now->id() == id) {
|
||||
now->processFullCall(result);
|
||||
parentController()->startOrJoinGroupCall(
|
||||
peer,
|
||||
hash,
|
||||
SessionController::GroupCallJoinConfirm::Always);
|
||||
} else {
|
||||
bad();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
bad();
|
||||
}).send();
|
||||
} else {
|
||||
const auto call = peer->groupCall();
|
||||
if (!call) {
|
||||
bad();
|
||||
return;
|
||||
}
|
||||
const auto join = [=] {
|
||||
parentController()->startOrJoinGroupCall(
|
||||
peer,
|
||||
hash,
|
||||
SessionController::GroupCallJoinConfirm::Always);
|
||||
};
|
||||
if (call->loaded()) {
|
||||
join();
|
||||
return;
|
||||
}
|
||||
const auto id = call->id();
|
||||
_resolveRequestId = _session->api().request(
|
||||
MTPphone_GetGroupCall(call->input())
|
||||
).done([=](const MTPphone_GroupCall &result) {
|
||||
if (const auto now = peer->groupCall()
|
||||
; now && now->id() == id) {
|
||||
if (!now->loaded()) {
|
||||
now->processFullCall(result);
|
||||
}
|
||||
join();
|
||||
} else {
|
||||
bad();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
bad();
|
||||
}).send();
|
||||
}).send();
|
||||
return;
|
||||
}
|
||||
|
||||
2
Telegram/ThirdParty/tgcalls
vendored
2
Telegram/ThirdParty/tgcalls
vendored
Submodule Telegram/ThirdParty/tgcalls updated: e80667a343...6549849146
@@ -138,10 +138,17 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
|
||||
|
||||
echo "Copying from docker result folder."
|
||||
cp "$ReleasePath/root/$BinaryName" "$ReleasePath/$BinaryName"
|
||||
cp "$ReleasePath/root/$BinaryName.sym" "$ReleasePath/$BinaryName.sym"
|
||||
cp "$ReleasePath/root/Updater" "$ReleasePath/Updater"
|
||||
cp "$ReleasePath/root/Packer" "$ReleasePath/Packer"
|
||||
|
||||
echo "Dumping debug symbols.."
|
||||
"$ReleasePath/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
|
||||
echo "Done!"
|
||||
|
||||
echo "Stripping the executable.."
|
||||
strip -s "$ReleasePath/$BinaryName"
|
||||
echo "Done!"
|
||||
|
||||
echo "Preparing version $AppVersionStrFull, executing Packer.."
|
||||
cd "$ReleasePath"
|
||||
"./Packer" -path "$BinaryName" -path Updater -version $VersionForPacker $AlphaBetaParam
|
||||
|
||||
@@ -16,7 +16,7 @@ if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then
|
||||
fi
|
||||
|
||||
Run () {
|
||||
scl enable devtoolset-8 -- "$@"
|
||||
scl enable devtoolset-9 -- "$@"
|
||||
}
|
||||
|
||||
HomePath="$FullScriptPath/../.."
|
||||
@@ -86,17 +86,8 @@ if [ "$BadCount" != "0" ]; then
|
||||
Error "Bad GCC usages found: $BadCount"
|
||||
fi
|
||||
|
||||
echo "Dumping debug symbols.."
|
||||
/dump_syms "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
|
||||
echo "Done!"
|
||||
|
||||
echo "Stripping the executable.."
|
||||
strip -s "$ReleasePath/$BinaryName"
|
||||
echo "Done!"
|
||||
|
||||
rm -rf "$ReleasePath/root"
|
||||
mkdir "$ReleasePath/root"
|
||||
mv "$ReleasePath/$BinaryName" "$ReleasePath/root/"
|
||||
mv "$ReleasePath/$BinaryName.sym" "$ReleasePath/root/"
|
||||
mv "$ReleasePath/Updater" "$ReleasePath/root/"
|
||||
mv "$ReleasePath/Packer" "$ReleasePath/root/"
|
||||
|
||||
@@ -19,10 +19,10 @@ RUN yum -y install git cmake3 meson ninja-build autoconf automake libtool \
|
||||
freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \
|
||||
pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \
|
||||
webkitgtk4-devel pkgconfig bison yasm file which xorg-x11-util-macros \
|
||||
devtoolset-8-make devtoolset-8-gcc devtoolset-8-gcc-c++ \
|
||||
devtoolset-8-binutils
|
||||
devtoolset-9-make devtoolset-9-gcc devtoolset-9-gcc-c++ \
|
||||
devtoolset-9-binutils
|
||||
|
||||
SHELL [ "scl", "enable", "devtoolset-8", "--", "bash", "-c" ]
|
||||
SHELL [ "scl", "enable", "devtoolset-9", "--", "bash", "-c" ]
|
||||
RUN ln -s cmake3 /usr/bin/cmake
|
||||
|
||||
ENV LibrariesPath /usr/src/Libraries
|
||||
@@ -402,6 +402,7 @@ RUN make -j$(nproc)
|
||||
RUN make DESTDIR="$LibrariesPath/ffmpeg-cache" install
|
||||
|
||||
FROM builder AS openal
|
||||
ADD https://api.github.com/repos/telegramdesktop/openal-soft/git/refs/heads/fix_pulse_default openal-soft-version.json
|
||||
RUN git clone -b fix_pulse_default --depth=1 $GIT/telegramdesktop/openal-soft.git
|
||||
|
||||
WORKDIR openal-soft
|
||||
@@ -597,7 +598,7 @@ RUN git checkout bc8fb886
|
||||
RUN git clone https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss
|
||||
|
||||
WORKDIR src/third_party/lss
|
||||
RUN git checkout a91633d1
|
||||
RUN git checkout 8048ece
|
||||
WORKDIR ${LibrariesPath}
|
||||
|
||||
ENV BreakpadCache ${LibrariesPath}/breakpad-cache
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd Telegram
|
||||
scl enable devtoolset-8 -- ./configure.sh "$@"
|
||||
scl enable devtoolset-9 -- ./configure.sh "$@"
|
||||
|
||||
if [ -n "$DEBUG" ]; then
|
||||
scl enable devtoolset-8 -- cmake3 --build ../out/Debug -j$(nproc)
|
||||
scl enable devtoolset-9 -- cmake3 --build ../out/Debug -j$(nproc)
|
||||
else
|
||||
scl enable devtoolset-8 -- cmake3 --build ../out/Release -j$(nproc)
|
||||
scl enable devtoolset-9 -- cmake3 --build ../out/Release -j$(nproc)
|
||||
fi
|
||||
|
||||
@@ -15,7 +15,7 @@ fi
|
||||
|
||||
Command="$1"
|
||||
if [ "$Command" == "" ]; then
|
||||
Command="scl enable devtoolset-8 -- bash"
|
||||
Command="scl enable devtoolset-9 -- bash"
|
||||
fi
|
||||
|
||||
docker run -it --rm --cpus=8 --memory=22g -v $HOME/Telegram/DesktopPrivate:/usr/src/DesktopPrivate -v $HOME/Telegram/tdesktop:/usr/src/tdesktop tdesktop:centos_env $Command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 2006003
|
||||
AppVersionStrMajor 2.6
|
||||
AppVersionStrSmall 2.6.3
|
||||
AppVersionStr 2.6.3
|
||||
BetaChannel 1
|
||||
AppVersion 2007000
|
||||
AppVersionStrMajor 2.7
|
||||
AppVersionStrSmall 2.7
|
||||
AppVersionStr 2.7.0
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 2.6.3.beta
|
||||
AppVersionOriginal 2.7
|
||||
|
||||
Submodule Telegram/codegen updated: 04645b6d0f...a4904e076b
Submodule Telegram/lib_base updated: 8f26ce5351...5a4f00cbf9
Submodule Telegram/lib_spellcheck updated: 8c7f1154fa...5ae6877747
Submodule Telegram/lib_ui updated: f4d28a3001...0adf0383d8
Submodule Telegram/lib_webrtc updated: 60d5c43daf...01258d99f0
@@ -1,3 +1,37 @@
|
||||
2.7 (19.03.21)
|
||||
|
||||
- Start limitless Voice Chats in Groups and Channels.
|
||||
- Host discussions that can be listened to by millions of people simultaneously.
|
||||
- Record voice chats to share or publish in Channels later.
|
||||
- See that a chat is being recorded from the red dot next to its title.
|
||||
- See user bio texts right from the list of participants.
|
||||
- Raise your hand to show admins you want to speak.
|
||||
- Create separate Voice Chat Invite Links for listeners or speakers.
|
||||
- Change the title of your Voice Chat to give people an idea of the current topic.
|
||||
- Join Voice Chats as one of your Channels to hide your personal account.
|
||||
|
||||
2.6.8 beta (18.03.21)
|
||||
|
||||
- Fix connecting and getting allowed to speak on voice chats.
|
||||
- MPRIS support on Linux.
|
||||
|
||||
2.6.7 beta (18.03.21)
|
||||
|
||||
- Improve voice chat participants list updating.
|
||||
|
||||
2.6.6 beta (18.03.21)
|
||||
|
||||
- Fix joining popular voice chats.
|
||||
|
||||
2.6.5 beta (17.03.21)
|
||||
|
||||
- Improvements and fixes in new voice chat features.
|
||||
|
||||
2.6.4 beta (16.03.21)
|
||||
|
||||
- Fix freeze in voice chats.
|
||||
- Make default interface scale 110% on macOS Retina screens.
|
||||
|
||||
2.6.3 beta (16.03.21)
|
||||
|
||||
- Fix audio device selection in voice chats.
|
||||
|
||||
2
cmake
2
cmake
Submodule cmake updated: 25f0733a60...bd9c097fea
@@ -39,6 +39,8 @@ apps:
|
||||
- unity7
|
||||
- wayland
|
||||
- x11
|
||||
slots:
|
||||
- tdesktop-mpris
|
||||
|
||||
plugs:
|
||||
# Support for common GTK themes
|
||||
@@ -57,6 +59,11 @@ plugs:
|
||||
target: $SNAP/data-dir/sounds
|
||||
default-provider: gtk-common-themes
|
||||
|
||||
slots:
|
||||
tdesktop-mpris:
|
||||
interface: mpris
|
||||
name: tdesktop
|
||||
|
||||
layout:
|
||||
/usr/share/alsa:
|
||||
bind: $SNAP/usr/share/alsa
|
||||
|
||||
Reference in New Issue
Block a user