Compare commits

..

7 Commits

Author SHA1 Message Date
John Preston
eeecc42c25 Version 2.7.
- 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.
2021-03-19 14:15:26 +04:00
John Preston
e22ecafc1d Add confirmation on create / anonymous admin join. 2021-03-19 14:10:02 +04:00
John Preston
ba41da7b28 Fix discarded group call handle. 2021-03-19 00:57:16 +04:00
John Preston
9cfbccf9e7 Beta version 2.6.8.
- Fix connecting and getting allowed to speak on voice chats.
- MPRIS support on Linux.
2021-03-18 22:56:42 +04:00
John Preston
2b6f50e114 Fix joining / unmuting. 2021-03-18 22:56:42 +04:00
Ilya Fedin
d2f57b72c3 Add mpris permission for snap 2021-03-18 21:55:06 +03:00
Ilya Fedin
85ac983a27 Add MPRIS support 2021-03-18 21:55:06 +03:00
19 changed files with 694 additions and 85 deletions

View File

@@ -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

View File

@@ -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";

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.6.7.0" />
Version="2.7.0.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,6,7,0
PRODUCTVERSION 2,6,7,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.7.0"
VALUE "FileVersion", "2.7.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.6.7.0"
VALUE "ProductVersion", "2.7.0.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,6,7,0
PRODUCTVERSION 2,6,7,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.7.0"
VALUE "FileVersion", "2.7.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.6.7.0"
VALUE "ProductVersion", "2.7.0.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -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() {
@@ -257,31 +288,27 @@ void ChooseJoinAsProcess::start(
info.possibleJoinAs = std::move(list);
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
&& (info.possibleJoinAs.front() == self)
&& (!peer->isChannel()
|| !peer->asChannel()->amAnonymous()
|| (peer->isBroadcast() && !peer->canWrite()));
&& (info.possibleJoinAs.front() == self);
// We already joined this voice chat, just rejoin with the same.
const auto byAlreadyUsed = selectedId
&& (info.joinAs->id == selectedId);
&& (info.joinAs->id == selectedId)
&& (peer->groupCall() != nullptr);
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
if (context != Context::JoinWithConfirm) {
const auto confirmation = CreateOrJoinConfirmation(
peer,
context,
byAlreadyUsed);
if (confirmation.text.isEmpty()) {
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),
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([=] {

View File

@@ -471,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
@@ -721,7 +722,8 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDupdateGroupCall &data) {
data.vcall().match([&](const MTPDgroupCall &data) {
handlePossibleCreateOrJoinResponse(data);
}, [](const MTPDgroupCallDiscarded &data) {
}, [&](const MTPDgroupCallDiscarded &data) {
handlePossibleDiscarded(data);
});
}
@@ -732,9 +734,7 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
}
return;
} else if (_id != data.vid().v
|| _accessHash != data.vaccess_hash().v
|| !_instance) {
} else if (_id != data.vid().v || !_instance) {
return;
}
const auto streamDcId = MTP::BareDcId(
@@ -816,6 +816,14 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
});
}
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
@@ -870,10 +878,7 @@ void GroupCall::handleUpdate(const MTPUpdate &update) {
void GroupCall::handleUpdate(const MTPDupdateGroupCall &data) {
data.vcall().match([](const MTPDgroupCall &) {
}, [&](const MTPDgroupCallDiscarded &data) {
if (data.vid().v == _id) {
_mySsrc = 0;
hangup();
}
handlePossibleDiscarded(data);
});
}
@@ -923,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()) {
@@ -1121,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();
}

View File

@@ -228,6 +228,7 @@ private:
};
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);
@@ -309,6 +310,7 @@ private:
uint64 _id = 0;
uint64 _accessHash = 0;
uint32 _mySsrc = 0;
base::flat_set<uint32> _mySsrcs;
mtpRequestId _createRequestId = 0;
mtpRequestId _updateMuteRequestId = 0;

View File

@@ -141,6 +141,12 @@ std::map<int, const char*> BetaLogs() {
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"
},
};
};

View File

@@ -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 = 2006007;
constexpr auto AppVersionStr = "2.6.7";
constexpr auto AppBetaVersion = true;
constexpr auto AppVersion = 2007000;
constexpr auto AppVersionStr = "2.7";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -202,21 +202,23 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
update.match([&](const MTPDupdateGroupCall &updateData) {
updateData.vcall().match([&](const MTPDgroupCall &data) {
const auto version = data.vversion().v;
if (!_version || _version == version) {
if (!_applyingQueuedUpdates
&& (!_version || _version == version)) {
DEBUG_LOG(("Group Call Participants: "
"Apply updateGroupCall %1 -> %2"
).arg(_version
).arg(version));
applyUpdate(update);
} else if (_version < version) {
applyEnqueuedUpdate(update);
} else if (!_version || _version <= version) {
DEBUG_LOG(("Group Call Participants: "
"Queue updateGroupCall %1 -> %2"
).arg(_version
).arg(version));
_queuedUpdates.emplace(std::pair{ version, false }, update);
const auto type = QueuedType::Call;
_queuedUpdates.emplace(std::pair{ version, type }, update);
}
}, [&](const MTPDgroupCallDiscarded &data) {
applyUpdate(update);
discard(data);
});
}, [&](const MTPDupdateGroupCallParticipants &updateData) {
const auto version = updateData.vversion().v;
@@ -230,19 +232,22 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
true,
proj);
const auto required = increment ? (version - 1) : version;
if (_version == required) {
if (!_applyingQueuedUpdates && (_version == required)) {
DEBUG_LOG(("Group Call Participants: "
"Apply updateGroupCallParticipant %1 (%2)"
).arg(_version
).arg(Logs::b(increment)));
applyUpdate(update);
} else if (_version < required) {
applyEnqueuedUpdate(update);
} else if (_version <= required) {
DEBUG_LOG(("Group Call Participants: "
"Queue updateGroupCallParticipant %1 -> %2 (%3)"
).arg(_version
).arg(version
).arg(Logs::b(increment)));
_queuedUpdates.emplace(std::pair{ version, increment }, update);
const auto type = increment
? QueuedType::VersionedParticipant
: QueuedType::Participant;
_queuedUpdates.emplace(std::pair{ version, type }, update);
}
}, [](const auto &) {
Unexpected("Type in GroupCall::enqueueUpdate.");
@@ -250,7 +255,7 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
processQueuedUpdates();
}
void GroupCall::discard() {
void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
const auto id = _id;
const auto peer = _peer;
crl::on_main(&peer->session(), [=] {
@@ -262,6 +267,14 @@ void GroupCall::discard() {
}
}
});
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) {
@@ -288,7 +301,7 @@ void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) {
applyCallFields(data);
}, [&](const MTPDgroupCallDiscarded &data) {
discard();
discard(data);
});
});
}
@@ -327,14 +340,18 @@ void GroupCall::applyLocalUpdate(
ApplySliceSource::UpdateReceived);
}
void GroupCall::applyUpdate(const MTPUpdate &update) {
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();
processQueuedUpdates();
}, [&](const MTPDgroupCallDiscarded &data) {
discard();
discard(data);
});
}, [&](const MTPDupdateGroupCallParticipants &data) {
DEBUG_LOG(("Group Call Participants: "
@@ -351,7 +368,7 @@ void GroupCall::applyUpdate(const MTPUpdate &update) {
data.vparticipants().v,
ApplySliceSource::UpdateReceived);
}, [](const auto &) {
Unexpected("Type in GroupCall::processQueuedUpdates.");
Unexpected("Type in GroupCall::applyEnqueuedUpdate.");
});
Core::App().calls().applyGroupCallUpdateChecked(
&_peer->session(),
@@ -359,7 +376,7 @@ void GroupCall::applyUpdate(const MTPUpdate &update) {
}
void GroupCall::processQueuedUpdates() {
if (!_version) {
if (!_version || _applyingQueuedUpdates) {
return;
}
@@ -367,15 +384,16 @@ void GroupCall::processQueuedUpdates() {
while (!_queuedUpdates.empty()) {
const auto &entry = _queuedUpdates.front();
const auto version = entry.first.first;
const auto versionIncremented = entry.first.second;
const auto type = entry.first.second;
const auto incremented = (type == QueuedType::VersionedParticipant);
if ((version < _version)
|| (version == _version && versionIncremented)) {
|| (version == _version && incremented)) {
_queuedUpdates.erase(_queuedUpdates.begin());
} else if (version == _version
|| (version == _version + 1 && versionIncremented)) {
|| (version == _version + 1 && incremented)) {
const auto update = entry.second;
_queuedUpdates.erase(_queuedUpdates.begin());
applyUpdate(update);
applyEnqueuedUpdate(update);
} else {
break;
}
@@ -395,7 +413,7 @@ void GroupCall::computeParticipantsCount() {
}
void GroupCall::reload() {
if (_reloadRequestId) {
if (_reloadRequestId || _applyingQueuedUpdates) {
return;
} else if (_participantsRequestId) {
api().request(_participantsRequestId).cancel();
@@ -410,7 +428,7 @@ void GroupCall::reload() {
const auto &entry = _queuedUpdates.front();
const auto update = entry.second;
_queuedUpdates.erase(_queuedUpdates.begin());
applyUpdate(update);
applyEnqueuedUpdate(update);
}
_reloadByQueuedUpdatesTimer.cancel();

View File

@@ -113,9 +113,14 @@ private:
UnknownLoaded,
UpdateReceived,
};
enum class QueuedType : uint8 {
VersionedParticipant,
Participant,
Call,
};
[[nodiscard]] ApiWrap &api() const;
void discard();
void discard(const MTPDgroupCallDiscarded &data);
[[nodiscard]] bool inCall() const;
void applyParticipantsSlice(
const QVector<MTPGroupCallParticipant> &list,
@@ -124,7 +129,7 @@ private:
void changePeerEmptyCallFlag();
void checkFinishSpeakingByActive();
void applyCallFields(const MTPDgroupCall &data);
void applyUpdate(const MTPUpdate &update);
void applyEnqueuedUpdate(const MTPUpdate &update);
void setServerParticipantsCount(int count);
void computeParticipantsCount();
void processQueuedUpdates();
@@ -144,7 +149,9 @@ private:
mtpRequestId _reloadRequestId = 0;
rpl::variable<QString> _title;
base::flat_multi_map<std::pair<int,bool>, MTPUpdate> _queuedUpdates;
base::flat_multi_map<
std::pair<int, QueuedType>,
MTPUpdate> _queuedUpdates;
base::Timer _reloadByQueuedUpdatesTimer;
std::optional<MTPphone_GroupCall> _savedFull;
@@ -168,6 +175,7 @@ private:
bool _canChangeJoinMuted = true;
bool _allParticipantsLoaded = false;
bool _joinedToTop = false;
bool _applyingQueuedUpdates = false;
};

View File

@@ -558,7 +558,7 @@ void GroupThumbs::fillItems(
const auto current = (index - from);
const auto old = base::take(_items);
ValidateSlice(slice, _context, from, index, till);
//ValidateSlice(slice, _context, from, index, till);
markCacheStale();
_items.reserve(till - from);
@@ -587,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();

View 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 &parameters,
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

View 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

View File

@@ -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
}

View File

@@ -1,7 +1,7 @@
AppVersion 2006007
AppVersionStrMajor 2.6
AppVersionStrSmall 2.6.7
AppVersionStr 2.6.7
BetaChannel 1
AppVersion 2007000
AppVersionStrMajor 2.7
AppVersionStrSmall 2.7
AppVersionStr 2.7.0
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 2.6.7.beta
AppVersionOriginal 2.7

View File

@@ -1,3 +1,20 @@
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.

View File

@@ -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