Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
373635a765 | ||
|
|
0ecd4d3b40 | ||
|
|
3fd62d51aa | ||
|
|
818624e051 | ||
|
|
a6eb241ec1 | ||
|
|
1d7fb6c4ce | ||
|
|
2af63ec48f | ||
|
|
0709bc6d70 | ||
|
|
3a34881488 | ||
|
|
39f9147790 | ||
|
|
19a5dcbffc | ||
|
|
e864aa2ff2 | ||
|
|
fe85a8256a | ||
|
|
f24b0c6237 | ||
|
|
3940d57c3d | ||
|
|
8da33113a2 | ||
|
|
f66cfb5684 | ||
|
|
d648d294ca | ||
|
|
0c8033414e | ||
|
|
28f29b51c0 | ||
|
|
8142e83395 | ||
|
|
e247be7e33 | ||
|
|
e594b75f4c | ||
|
|
28f857f763 | ||
|
|
486424af4f | ||
|
|
f3614d6402 | ||
|
|
71151f6bf6 | ||
|
|
2c0ef9c4e9 | ||
|
|
930e971881 | ||
|
|
a576025d4f | ||
|
|
bcd2560e8f | ||
|
|
aede42b0b6 | ||
|
|
56728a066e | ||
|
|
1951b7a8a1 | ||
|
|
0dc0f588c4 | ||
|
|
7d22c631ca | ||
|
|
cf5cc3646a | ||
|
|
375820d5cf | ||
|
|
3955543699 | ||
|
|
c03da00e37 | ||
|
|
edcd462fb9 | ||
|
|
e99558abeb | ||
|
|
feff514a07 |
16
.github/workflows/copyright_year_updater.yml
vendored
Normal file
16
.github/workflows/copyright_year_updater.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Copyright year updater.
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: ["Restart copyright_year_updater workflow."]
|
||||
schedule:
|
||||
# At 03:00 on January 1.
|
||||
- cron: "0 3 1 1 *"
|
||||
|
||||
jobs:
|
||||
Copyright-year:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: desktop-app/action_code_updater@master
|
||||
with:
|
||||
type: "license-year"
|
||||
1
.github/workflows/snap.yml
vendored
1
.github/workflows/snap.yml
vendored
@@ -51,6 +51,7 @@ jobs:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: First set up.
|
||||
|
||||
144
.github/workflows/user_agent_updater.yml
vendored
144
.github/workflows/user_agent_updater.yml
vendored
@@ -5,152 +5,14 @@ on:
|
||||
types: ["Restart user_agent_updater workflow."]
|
||||
schedule:
|
||||
# At 00:00 on day-of-month 1.
|
||||
- cron: '0 0 1 * *'
|
||||
- cron: "0 0 1 * *"
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
User-agent:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
codeFile: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp"
|
||||
headBranchPrefix: "chrome_"
|
||||
baseBranch: "dev"
|
||||
isPull: "0"
|
||||
|
||||
steps:
|
||||
- name: Set env.
|
||||
if: startsWith(github.event_name, 'pull_request')
|
||||
run: |
|
||||
echo "isPull=1" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up git.
|
||||
run: |
|
||||
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
|
||||
if [ -z "${token}" ]; then
|
||||
echo "Token is unset. Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
|
||||
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "GitHub Action"
|
||||
|
||||
git remote set-url origin $url
|
||||
|
||||
- name: Delete branch.
|
||||
env:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
if: |
|
||||
env.isPull == '1'
|
||||
&& github.event.action == 'closed'
|
||||
&& startsWith(env.ref, env.headBranchPrefix)
|
||||
run: |
|
||||
git push origin --delete $ref
|
||||
|
||||
- name: Write a new version of Google Chrome to the user-agent for DNS.
|
||||
if: env.isPull == '0'
|
||||
shell: python
|
||||
run: |
|
||||
import subprocess, os, re;
|
||||
|
||||
regExpVersion = "[0-9]+.[0-9]+.[0-9]+.[0-9]+";
|
||||
chrome = "Chrome/";
|
||||
|
||||
def newVersion():
|
||||
output = subprocess.check_output(["google-chrome", "--version"]);
|
||||
version = re.search(regExpVersion, output);
|
||||
if not version:
|
||||
print("Can't find a Chrome version.");
|
||||
exit();
|
||||
return version.group(0);
|
||||
|
||||
newChromeVersion = newVersion();
|
||||
print(newChromeVersion);
|
||||
|
||||
def setEnv(value):
|
||||
open(os.environ['GITHUB_ENV'], "a").write(value);
|
||||
|
||||
def writeUserAgent():
|
||||
p = os.environ['codeFile'];
|
||||
w = open(p, "r");
|
||||
content = w.read();
|
||||
w.close();
|
||||
|
||||
regExpChrome = chrome + regExpVersion;
|
||||
|
||||
version = re.search(regExpChrome, content);
|
||||
if not version:
|
||||
print("Can't find an user-agent in the code.");
|
||||
exit();
|
||||
content = re.sub(regExpChrome, chrome + newChromeVersion, content);
|
||||
|
||||
w = open(p, "w");
|
||||
w.write(content);
|
||||
|
||||
setEnv("ChromeVersion=" + newChromeVersion);
|
||||
|
||||
writeUserAgent();
|
||||
|
||||
- name: Push to a new branch.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
run: |
|
||||
git diff > git_diff.txt
|
||||
if [[ ! -s git_diff.txt ]]; then
|
||||
echo "Nothing to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git checkout -b $headBranchPrefix$ChromeVersion
|
||||
git add $codeFile
|
||||
git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion."
|
||||
|
||||
git push origin $headBranchPrefix$ChromeVersion
|
||||
echo "Done!"
|
||||
|
||||
- name: Close previous pull requests.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
uses: actions/github-script@0.4.0
|
||||
- uses: desktop-app/action_code_updater@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const common = {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
};
|
||||
|
||||
github.pulls.list(common).then(response => {
|
||||
response.data.forEach((item, _) => {
|
||||
if (item.head.ref.startsWith(process.env.headBranchPrefix)) {
|
||||
console.log(`Close ${item.title} #${item.number}.`);
|
||||
github.pulls.update({
|
||||
pull_number: item.number,
|
||||
state: "closed",
|
||||
...common
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
- name: Create a new pull request.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
uses: actions/github-script@0.4.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const version = process.env.ChromeVersion;
|
||||
const title = `Update User-Agent for DNS to Chrome ${version}.`;
|
||||
|
||||
github.pulls.create({
|
||||
title: title,
|
||||
body: "",
|
||||
head: `${process.env.headBranchPrefix}${version}`,
|
||||
base: process.env.baseBranch,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
type: "user-agent"
|
||||
|
||||
@@ -17,13 +17,17 @@ The latest version is available for
|
||||
|
||||
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
|
||||
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
|
||||
* [OS X 10.10 and 10.11](https://telegram.org/dl/desktop/osx)
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) ([32 bit](https://telegram.org/dl/desktop/linux32))
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
|
||||
* [Snap](https://snapcraft.io/telegram-desktop)
|
||||
* [Flatpak](https://flathub.org/apps/details/org.telegram.desktop)
|
||||
|
||||
## Old system versions
|
||||
|
||||
Version **2.4.4** was the last that supports older systems
|
||||
|
||||
* [OS X 10.10 and 10.11](https://updates.tdesktop.com/tosx/tsetup-osx.2.4.4.dmg)
|
||||
* [Linux static build for 32 bit](https://updates.tdesktop.com/tlinux32/tsetup32.2.4.4.tar.xz)
|
||||
|
||||
Version **1.8.15** was the last that supports older systems
|
||||
|
||||
* [Windows XP and Vista](https://updates.tdesktop.com/tsetup/tsetup.1.8.15.exe) ([portable](https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip))
|
||||
|
||||
@@ -72,7 +72,6 @@ PRIVATE
|
||||
if (LINUX)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_xcb_screensaver
|
||||
desktop-app::external_xcb
|
||||
desktop-app::external_glib
|
||||
)
|
||||
@@ -813,6 +812,8 @@ PRIVATE
|
||||
platform/linux/linux_desktop_environment.h
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_libs.cpp
|
||||
platform/linux/linux_libs.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
@@ -1102,6 +1103,13 @@ if (NOT LINUX)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX AND 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
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.5.1.0" />
|
||||
Version="2.5.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,5,1,0
|
||||
PRODUCTVERSION 2,5,1,0
|
||||
FILEVERSION 2,5,3,0
|
||||
PRODUCTVERSION 2,5,3,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.5.1.0"
|
||||
VALUE "FileVersion", "2.5.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.5.1.0"
|
||||
VALUE "ProductVersion", "2.5.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,5,1,0
|
||||
PRODUCTVERSION 2,5,1,0
|
||||
FILEVERSION 2,5,3,0
|
||||
PRODUCTVERSION 2,5,3,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.5.1.0"
|
||||
VALUE "FileVersion", "2.5.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.5.1.0"
|
||||
VALUE "ProductVersion", "2.5.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -1229,37 +1229,56 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
||||
void PeerListContent::showRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
|
||||
showRowMenu(findRowIndex(row), QCursor::pos(), std::move(destroyed));
|
||||
}
|
||||
|
||||
bool PeerListContent::showRowMenu(
|
||||
RowIndex index,
|
||||
QPoint globalPos,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
|
||||
if (_contextMenu) {
|
||||
_contextMenu->deleteLater();
|
||||
_contextMenu->setDestroyedCallback(nullptr);
|
||||
_contextMenu = nullptr;
|
||||
}
|
||||
setContexted(Selected());
|
||||
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
|
||||
setContexted(_selected);
|
||||
if (_pressButton != Qt::LeftButton) {
|
||||
mousePressReleased(_pressButton);
|
||||
}
|
||||
|
||||
if (const auto row = getRow(_contexted.index)) {
|
||||
_contextMenu = _controller->rowContextMenu(this, row);
|
||||
if (_contextMenu) {
|
||||
_contextMenu->setDestroyedCallback(crl::guard(
|
||||
this,
|
||||
[this] {
|
||||
setContexted(Selected());
|
||||
handleMouseMove(QCursor::pos());
|
||||
}));
|
||||
_contextMenu->popup(e->globalPos());
|
||||
e->accept();
|
||||
} else {
|
||||
const auto row = getRow(index);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_contextMenu = _controller->rowContextMenu(this, row);
|
||||
const auto raw = _contextMenu.get();
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setContexted({ index, false });
|
||||
raw->setDestroyedCallback(crl::guard(
|
||||
this,
|
||||
[=] {
|
||||
setContexted(Selected());
|
||||
}
|
||||
} else {
|
||||
setContexted(Selected());
|
||||
handleMouseMove(QCursor::pos());
|
||||
if (destroyed) {
|
||||
destroyed(raw);
|
||||
}
|
||||
}));
|
||||
raw->popup(globalPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
if (showRowMenu(_selected.index, e->globalPos())) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -300,6 +300,9 @@ public:
|
||||
peerListFinishSelectedRowsBunch();
|
||||
}
|
||||
|
||||
virtual void peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) = 0;
|
||||
virtual int peerListSelectedRowsCount() = 0;
|
||||
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
|
||||
virtual void peerListRestoreState(
|
||||
@@ -557,6 +560,10 @@ public:
|
||||
std::unique_ptr<PeerListState> saveState() const;
|
||||
void restoreState(std::unique_ptr<PeerListState> state);
|
||||
|
||||
void showRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
|
||||
|
||||
auto scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
@@ -640,6 +647,11 @@ private:
|
||||
RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex());
|
||||
QRect getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const;
|
||||
|
||||
bool showRowMenu(
|
||||
RowIndex index,
|
||||
QPoint globalPos,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
|
||||
|
||||
crl::time paintRow(Painter &p, crl::time ms, RowIndex index);
|
||||
|
||||
void addRowEntry(not_null<PeerListRow*> row);
|
||||
@@ -823,6 +835,11 @@ public:
|
||||
std::unique_ptr<PeerListState> state) override {
|
||||
_content->restoreState(std::move(state));
|
||||
}
|
||||
void peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) override {
|
||||
_content->showRowMenu(row, std::move(destroyed));
|
||||
}
|
||||
|
||||
protected:
|
||||
not_null<PeerListContent*> content() const {
|
||||
|
||||
@@ -358,7 +358,7 @@ bool ParticipantsAdditionalData::canRemoveUser(
|
||||
if (canRestrictUser(user)) {
|
||||
return true;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->invitedByMe.contains(user);
|
||||
return !user->isSelf() && chat->invitedByMe.contains(user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1212,6 +1212,9 @@ void ParticipantsBoxController::rebuildChatAdmins(
|
||||
return true;
|
||||
}();
|
||||
if (same) {
|
||||
if (!_allLoaded && !delegate()->peerListFullRowsCount()) {
|
||||
chatListReady();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using "ui/basic.style";
|
||||
|
||||
using "ui/widgets/widgets.style";
|
||||
using "ui/layers/layers.style";
|
||||
using "ui/chat/chat.style"; // GroupCallUserpics
|
||||
using "window/window.style";
|
||||
|
||||
CallSignalBars {
|
||||
@@ -605,9 +606,12 @@ groupCallButtonSkip: 43px;
|
||||
groupCallButtonBottomSkip: 145px;
|
||||
groupCallMuteBottomSkip: 160px;
|
||||
|
||||
groupCallTopBarUserpicSize: 28px;
|
||||
groupCallTopBarUserpicShift: 8px;
|
||||
groupCallTopBarUserpicStroke: 2px;
|
||||
groupCallTopBarUserpics: GroupCallUserpics {
|
||||
size: 28px;
|
||||
shift: 8px;
|
||||
stroke: 2px;
|
||||
align: align(left);
|
||||
}
|
||||
groupCallTopBarJoin: RoundButton(defaultActiveButton) {
|
||||
width: -26px;
|
||||
height: 26px;
|
||||
|
||||
@@ -368,10 +368,10 @@ void Call::setupOutgoingVideo() {
|
||||
_errors.fire({ ErrorType::NoCamera });
|
||||
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||
} else if (_state.current() != State::Established
|
||||
&& state != started
|
||||
&& !_videoCapture) {
|
||||
&& (state != Webrtc::VideoState::Inactive)
|
||||
&& (started == Webrtc::VideoState::Inactive)) {
|
||||
_errors.fire({ ErrorType::NotStartedCall });
|
||||
_videoOutgoing->setState(started);
|
||||
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||
} else if (state != Webrtc::VideoState::Inactive
|
||||
&& _instance
|
||||
&& !_instance->supportsVideo()) {
|
||||
|
||||
@@ -243,6 +243,9 @@ private:
|
||||
void prepareRows(not_null<Data::GroupCall*> real);
|
||||
//void repaintByTimer();
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row);
|
||||
void setupListChangeViewers(not_null<GroupCall*> call);
|
||||
void subscribeToChanges(not_null<Data::GroupCall*> real);
|
||||
void updateRow(
|
||||
@@ -270,6 +273,8 @@ private:
|
||||
rpl::event_stream<MuteRequest> _toggleMuteRequests;
|
||||
rpl::event_stream<not_null<UserData*>> _kickMemberRequests;
|
||||
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;
|
||||
@@ -638,10 +643,7 @@ MembersController::MembersController(
|
||||
}
|
||||
|
||||
MembersController::~MembersController() {
|
||||
if (_menu) {
|
||||
_menu->setDestroyedCallback(nullptr);
|
||||
_menu = nullptr;
|
||||
}
|
||||
base::take(_menu);
|
||||
}
|
||||
|
||||
void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||
@@ -682,9 +684,12 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_realCallRawValue = real;
|
||||
_realId = real->id();
|
||||
|
||||
_fullCount = real->fullCountValue(
|
||||
) | rpl::map([](int value) {
|
||||
return std::max(value, 1);
|
||||
_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);
|
||||
});
|
||||
|
||||
real->participantsSliceAdded(
|
||||
@@ -741,18 +746,44 @@ void MembersController::appendInvitedUsers() {
|
||||
void MembersController::updateRow(
|
||||
const std::optional<Data::GroupCall::Participant> &was,
|
||||
const Data::GroupCall::Participant &now) {
|
||||
auto countChange = 0;
|
||||
if (const auto row = findRow(now.user)) {
|
||||
if (now.speaking && (!was || !was->speaking)) {
|
||||
checkSpeakingRowPosition(row);
|
||||
}
|
||||
if (row->state() == Row::State::Invited) {
|
||||
countChange = 1;
|
||||
}
|
||||
updateRow(row, &now);
|
||||
} else if (auto row = createRow(now)) {
|
||||
if (row->speaking()) {
|
||||
delegate()->peerListPrependRow(std::move(row));
|
||||
} else {
|
||||
static constexpr auto kInvited = Row::State::Invited;
|
||||
const auto reorder = [&] {
|
||||
const auto count = delegate()->peerListFullRowsCount();
|
||||
if (!count) {
|
||||
return false;
|
||||
}
|
||||
const auto row = delegate()->peerListRowAt(count - 1).get();
|
||||
return (static_cast<Row*>(row)->state() == kInvited);
|
||||
}();
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (reorder) {
|
||||
delegate()->peerListPartitionRows([](const PeerListRow &row) {
|
||||
return static_cast<const Row&>(row).state() != kInvited;
|
||||
});
|
||||
}
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
countChange = 1;
|
||||
}
|
||||
if (countChange) {
|
||||
const auto fullCountMin = _fullCountMin.current() + countChange;
|
||||
if (_fullCountMax.current() < fullCountMin) {
|
||||
_fullCountMax = fullCountMin;
|
||||
}
|
||||
_fullCountMin = fullCountMin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,10 +896,11 @@ void MembersController::prepare() {
|
||||
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
||||
|
||||
const auto call = _call.get();
|
||||
if (const auto real = _peer->groupCall();
|
||||
real && call && real->id() == call->id()) {
|
||||
if (const auto real = _peer->groupCall()
|
||||
; real && call && real->id() == call->id()) {
|
||||
prepareRows(real);
|
||||
} else if (auto row = createSelfRow()) {
|
||||
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
@@ -884,6 +916,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
auto foundSelf = 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);
|
||||
@@ -898,6 +931,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
not_null{ user },
|
||||
&Data::GroupCall::Participant::user);
|
||||
if (contains) {
|
||||
++fullCountMin;
|
||||
++i;
|
||||
} else {
|
||||
changed = true;
|
||||
@@ -913,18 +947,29 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
&Data::GroupCall::Participant::user);
|
||||
auto row = (i != end(participants)) ? createRow(*i) : createSelfRow();
|
||||
if (row) {
|
||||
if (row->state() != Row::State::Invited) {
|
||||
++fullCountMin;
|
||||
}
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,29 +1047,20 @@ auto MembersController::kickMemberRequests() const
|
||||
}
|
||||
|
||||
void MembersController::rowClicked(not_null<PeerListRow*> row) {
|
||||
if (_menu) {
|
||||
_menu->setDestroyedCallback(nullptr);
|
||||
_menu->deleteLater();
|
||||
_menu = nullptr;
|
||||
}
|
||||
_menu = rowContextMenu(_menuParent, row);
|
||||
if (const auto raw = _menu.get()) {
|
||||
raw->setDestroyedCallback([=] {
|
||||
if (_menu && _menu.get() != raw) {
|
||||
return;
|
||||
}
|
||||
auto saved = base::take(_menu);
|
||||
for (const auto peer : base::take(_menuCheckRowsAfterHidden)) {
|
||||
if (const auto row = findRow(peer->asUser())) {
|
||||
if (row->speaking()) {
|
||||
checkSpeakingRowPosition(row);
|
||||
}
|
||||
delegate()->peerListShowRowMenu(row, [=](not_null<Ui::PopupMenu*> menu) {
|
||||
if (!_menu || _menu.get() != menu) {
|
||||
return;
|
||||
}
|
||||
auto saved = base::take(_menu);
|
||||
for (const auto peer : base::take(_menuCheckRowsAfterHidden)) {
|
||||
if (const auto row = findRow(peer->asUser())) {
|
||||
if (row->speaking()) {
|
||||
checkSpeakingRowPosition(row);
|
||||
}
|
||||
}
|
||||
_menu = std::move(saved);
|
||||
});
|
||||
raw->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
_menu = std::move(saved);
|
||||
});
|
||||
}
|
||||
|
||||
void MembersController::rowActionClicked(
|
||||
@@ -1035,6 +1071,23 @@ void MembersController::rowActionClicked(
|
||||
base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
auto result = createRowContextMenu(parent, row);
|
||||
|
||||
if (result) {
|
||||
// First clear _menu value, so that we don't check row positions yet.
|
||||
base::take(_menu);
|
||||
|
||||
// Here unique_qptr is used like a shared pointer, where
|
||||
// not the last destroyed pointer destroys the object, but the first.
|
||||
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
Expects(row->peer()->isUser());
|
||||
|
||||
if (row->peer()->isSelf()) {
|
||||
@@ -1125,7 +1178,9 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
|
||||
_kickMemberRequests.fire_copy(user);
|
||||
});
|
||||
|
||||
if (_peer->canManageGroupCall() && (!admin || mute)) {
|
||||
if ((muteState != Row::State::Invited)
|
||||
&& _peer->canManageGroupCall()
|
||||
&& (!admin || mute)) {
|
||||
result->addAction(
|
||||
(mute
|
||||
? tr::lng_group_call_context_mute(tr::now)
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/paint/blobs_linear.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/chat/group_call_userpics.h" // Ui::GroupCallUser.
|
||||
#include "ui/chat/group_call_bar.h" // Ui::GroupCallBarContent.
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_chat.h" // style::GroupCallUserpics
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Calls {
|
||||
@@ -165,7 +167,7 @@ void DebugInfoBox::updateText() {
|
||||
} // namespace
|
||||
|
||||
struct TopBar::User {
|
||||
Ui::GroupCallBarContent::User data;
|
||||
Ui::GroupCallUser data;
|
||||
};
|
||||
|
||||
class Mute final : public Ui::IconButton {
|
||||
@@ -238,6 +240,12 @@ TopBar::TopBar(
|
||||
: RpWidget(parent)
|
||||
, _call(call)
|
||||
, _groupCall(groupCall)
|
||||
, _userpics(call
|
||||
? nullptr
|
||||
: std::make_unique<Ui::GroupCallUserpics>(
|
||||
st::groupCallTopBarUserpics,
|
||||
rpl::single(true),
|
||||
[=] { updateUserpics(); }))
|
||||
, _durationLabel(_call
|
||||
? object_ptr<Ui::LabelSimple>(this, st::callBarLabel)
|
||||
: object_ptr<Ui::LabelSimple>(nullptr))
|
||||
@@ -551,35 +559,31 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||
) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||
return HistoryView::GroupCallTracker::ContentByCall(
|
||||
real,
|
||||
HistoryView::UserpicsInRowStyle{
|
||||
.size = st::groupCallTopBarUserpicSize,
|
||||
.shift = st::groupCallTopBarUserpicShift,
|
||||
.stroke = st::groupCallTopBarUserpicStroke,
|
||||
});
|
||||
st::groupCallTopBarUserpics.size);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
||||
if (_users.size() != content.users.size()) {
|
||||
return true;
|
||||
}
|
||||
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
||||
if (_users[i].data.userpicKey != content.users[i].userpicKey
|
||||
|| _users[i].data.id != content.users[i].id) {
|
||||
if (_users[i].userpicKey != content.users[i].userpicKey
|
||||
|| _users[i].id != content.users[i].id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
||||
const auto sizeChanged = (_users.size() != content.users.size());
|
||||
_users = ranges::view::all(
|
||||
content.users
|
||||
) | ranges::view::transform([](const auto &user) {
|
||||
return User{ user };
|
||||
}) | ranges::to_vector;
|
||||
generateUserpicsInRow();
|
||||
if (sizeChanged) {
|
||||
updateControlsGeometry();
|
||||
_users = content.users;
|
||||
for (auto &user : _users) {
|
||||
user.speaking = false;
|
||||
}
|
||||
update();
|
||||
_userpics->update(_users, !isHidden());
|
||||
}, lifetime());
|
||||
|
||||
_userpics->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
_userpicsWidth = width;
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
|
||||
call->peer()->session().changes().peerUpdates(
|
||||
@@ -591,41 +595,10 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||
}) | rpl::start_with_next([=] {
|
||||
updateInfoLabels();
|
||||
}, lifetime());
|
||||
|
||||
}
|
||||
|
||||
void TopBar::generateUserpicsInRow() {
|
||||
const auto count = int(_users.size());
|
||||
if (!count) {
|
||||
_userpics = QImage();
|
||||
return;
|
||||
}
|
||||
const auto limit = std::min(count, kMaxUsersInBar);
|
||||
const auto single = st::groupCallTopBarUserpicSize;
|
||||
const auto shift = st::groupCallTopBarUserpicShift;
|
||||
const auto width = single + (limit - 1) * (single - shift);
|
||||
if (_userpics.width() != width * cIntRetinaFactor()) {
|
||||
_userpics = QImage(
|
||||
QSize(width, single) * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
_userpics.fill(Qt::transparent);
|
||||
_userpics.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
auto q = Painter(&_userpics);
|
||||
auto hq = PainterHighQualityEnabler(q);
|
||||
auto pen = QPen(Qt::transparent);
|
||||
pen.setWidth(st::groupCallTopBarUserpicStroke);
|
||||
auto x = (count - 1) * (single - shift);
|
||||
for (auto i = count; i != 0;) {
|
||||
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
q.drawImage(x, 0, _users[--i].data.userpic);
|
||||
q.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
q.setBrush(Qt::NoBrush);
|
||||
q.setPen(pen);
|
||||
q.drawEllipse(x, 0, single, single);
|
||||
x -= single - shift;
|
||||
}
|
||||
void TopBar::updateUserpics() {
|
||||
update(_mute->width(), 0, _userpics->maxWidth(), height());
|
||||
}
|
||||
|
||||
void TopBar::updateInfoLabels() {
|
||||
@@ -688,8 +661,13 @@ void TopBar::updateControlsGeometry() {
|
||||
_durationLabel->moveToLeft(left, st::callBarLabelTop);
|
||||
left += _durationLabel->width() + st::callBarSkip;
|
||||
}
|
||||
if (!_userpics.isNull()) {
|
||||
left += _userpics.width() / _userpics.devicePixelRatio();
|
||||
if (_userpicsWidth) {
|
||||
const auto single = st::groupCallTopBarUserpics.size;
|
||||
const auto skip = anim::interpolate(
|
||||
0,
|
||||
st::callBarSkip,
|
||||
std::min(_userpicsWidth, single) / float64(single));
|
||||
left += _userpicsWidth + skip;
|
||||
}
|
||||
if (_signalBars) {
|
||||
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
|
||||
@@ -741,11 +719,10 @@ void TopBar::paintEvent(QPaintEvent *e) {
|
||||
: (_muted ? st::callBarBgMuted : st::callBarBg);
|
||||
p.fillRect(e->rect(), std::move(brush));
|
||||
|
||||
if (!_userpics.isNull()) {
|
||||
const auto imageSize = _userpics.size()
|
||||
/ _userpics.devicePixelRatio();
|
||||
const auto top = (height() - imageSize.height()) / 2;
|
||||
p.drawImage(_mute->width(), top, _userpics);
|
||||
if (_userpicsWidth) {
|
||||
const auto size = st::groupCallTopBarUserpics.size;
|
||||
const auto top = (height() - size) / 2;
|
||||
_userpics->paint(p, _mute->width(), top, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ class IconButton;
|
||||
class AbstractButton;
|
||||
class LabelSimple;
|
||||
class FlatLabel;
|
||||
struct GroupCallUser;
|
||||
class GroupCallUserpics;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
@@ -66,14 +68,15 @@ private:
|
||||
void setMuted(bool mute);
|
||||
|
||||
void subscribeToMembersChanges(not_null<GroupCall*> call);
|
||||
void generateUserpicsInRow();
|
||||
void updateUserpics();
|
||||
|
||||
const base::weak_ptr<Call> _call;
|
||||
const base::weak_ptr<GroupCall> _groupCall;
|
||||
|
||||
bool _muted = false;
|
||||
std::vector<User> _users;
|
||||
QImage _userpics;
|
||||
std::vector<Ui::GroupCallUser> _users;
|
||||
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
||||
int _userpicsWidth = 0;
|
||||
object_ptr<Ui::LabelSimple> _durationLabel;
|
||||
object_ptr<SignalBars> _signalBars;
|
||||
object_ptr<Ui::FlatLabel> _fullInfoLabel;
|
||||
|
||||
@@ -62,7 +62,7 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix sticker pack opening.\n"
|
||||
|
||||
"- Fix group status display.\n"
|
||||
|
||||
|
||||
"- Fix group members display.\n"
|
||||
},
|
||||
{
|
||||
@@ -75,6 +75,20 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Fix freeze on secondary screen disconnect.\n"
|
||||
},
|
||||
{
|
||||
2005002,
|
||||
"- Fix possible crash in video calls.\n"
|
||||
|
||||
"- Fix possible crash in connecting to voice chats.\n"
|
||||
|
||||
"- Use different audio module code on Windows in calls.\n"
|
||||
},
|
||||
{
|
||||
2005003,
|
||||
"- Allow using mouse buttons in Push-to-Talk shortcut.\n"
|
||||
|
||||
"- Fix blurred thumbnails in Shared Links section.\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -270,14 +270,10 @@ std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
|
||||
Launcher::Launcher(
|
||||
int argc,
|
||||
char *argv[],
|
||||
const QString &deviceModel,
|
||||
const QString &systemVersion)
|
||||
char *argv[])
|
||||
: _argc(argc)
|
||||
, _argv(argv)
|
||||
, _baseIntegration(_argc, _argv)
|
||||
, _deviceModel(deviceModel)
|
||||
, _systemVersion(systemVersion) {
|
||||
, _baseIntegration(_argc, _argv) {
|
||||
base::Integration::Set(&_baseIntegration);
|
||||
}
|
||||
|
||||
@@ -328,6 +324,23 @@ int Launcher::exec() {
|
||||
// Must be started before Platform is started.
|
||||
Logs::start(this);
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
const auto openalLogPath = QDir::toNativeSeparators(
|
||||
cWorkingDir() + qsl("DebugLogs/last_openal_log.txt"));
|
||||
|
||||
qputenv("ALSOFT_LOGLEVEL", "3");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_wputenv_s(
|
||||
L"ALSOFT_LOGFILE",
|
||||
openalLogPath.toStdWString().c_str());
|
||||
#else // Q_OS_WIN
|
||||
qputenv(
|
||||
"ALSOFT_LOGFILE",
|
||||
QFile::encodeName(openalLogPath));
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
|
||||
// Must be started before Sandbox is created.
|
||||
Platform::start();
|
||||
Ui::DisableCustomScaling();
|
||||
@@ -419,14 +432,6 @@ void Launcher::prepareSettings() {
|
||||
processArguments();
|
||||
}
|
||||
|
||||
QString Launcher::deviceModel() const {
|
||||
return _deviceModel;
|
||||
}
|
||||
|
||||
QString Launcher::systemVersion() const {
|
||||
return _systemVersion;
|
||||
}
|
||||
|
||||
uint64 Launcher::installationTag() const {
|
||||
return InstallationTag;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ class Launcher {
|
||||
public:
|
||||
Launcher(
|
||||
int argc,
|
||||
char *argv[],
|
||||
const QString &deviceModel,
|
||||
const QString &systemVersion);
|
||||
char *argv[]);
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
@@ -26,8 +24,6 @@ public:
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const;
|
||||
|
||||
QString deviceModel() const;
|
||||
QString systemVersion() const;
|
||||
uint64 installationTag() const;
|
||||
|
||||
bool checkPortableVersionFolder();
|
||||
@@ -67,9 +63,6 @@ private:
|
||||
QStringList _arguments;
|
||||
BaseIntegration _baseIntegration;
|
||||
|
||||
const QString _deviceModel;
|
||||
const QString _systemVersion;
|
||||
|
||||
bool _customWorkingDir = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -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 = 2005001;
|
||||
constexpr auto AppVersionStr = "2.5.1";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 2005003;
|
||||
constexpr auto AppVersionStr = "2.5.3";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -556,7 +556,7 @@ bool InnerWidget::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time InnerWidget::elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
return crl::time(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
|
||||
@@ -838,7 +838,7 @@ void GenerateItems(
|
||||
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto user = history->owner().user(data.vuser_id().v);
|
||||
const auto userLink = user->createOpenLink();
|
||||
const auto userLinkText = textcmdLink(1, user->name);
|
||||
const auto userLinkText = textcmdLink(2, user->name);
|
||||
auto text = tr::lng_admin_log_muted_participant(
|
||||
tr::now,
|
||||
lt_from,
|
||||
@@ -862,7 +862,7 @@ void GenerateItems(
|
||||
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto user = history->owner().user(data.vuser_id().v);
|
||||
const auto userLink = user->createOpenLink();
|
||||
const auto userLinkText = textcmdLink(1, user->name);
|
||||
const auto userLinkText = textcmdLink(2, user->name);
|
||||
auto text = tr::lng_admin_log_unmuted_participant(
|
||||
tr::now,
|
||||
lt_from,
|
||||
|
||||
@@ -2520,9 +2520,9 @@ void HistoryInner::elementStartStickerLoop(
|
||||
_animatedStickersPlayed.emplace(view->data());
|
||||
}
|
||||
|
||||
crl::time HistoryInner::elementHighlightTime(not_null<const Element*> view) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(
|
||||
view->data());
|
||||
crl::time HistoryInner::elementHighlightTime(
|
||||
not_null<const HistoryItem*> item) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(item);
|
||||
if (fullAnimMs > 0) {
|
||||
const auto now = crl::now();
|
||||
if (fullAnimMs < now) {
|
||||
@@ -3421,8 +3421,8 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||
return (App::hoveredItem() == view);
|
||||
}
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> view) override {
|
||||
return Instance ? Instance->elementHighlightTime(view) : 0;
|
||||
not_null<const HistoryItem*> item) override {
|
||||
return Instance ? Instance->elementHighlightTime(item) : 0;
|
||||
}
|
||||
bool elementInSelectionMode() override {
|
||||
return Instance ? Instance->inSelectionMode() : false;
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
int till) const;
|
||||
void elementStartStickerLoop(not_null<const Element*> view);
|
||||
[[nodiscard]] crl::time elementHighlightTime(
|
||||
not_null<const Element*> view);
|
||||
not_null<const HistoryItem*> item);
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context);
|
||||
|
||||
@@ -1045,11 +1045,6 @@ void HistoryWidget::scrollToAnimationCallback(
|
||||
|
||||
void HistoryWidget::enqueueMessageHighlight(
|
||||
not_null<HistoryView::Element*> view) {
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = group->items.front()->mainView()) {
|
||||
view = leader;
|
||||
}
|
||||
}
|
||||
auto enqueueMessageId = [this](MsgId universalId) {
|
||||
if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
|
||||
highlightMessage(universalId);
|
||||
@@ -1096,7 +1091,7 @@ void HistoryWidget::checkNextHighlight() {
|
||||
|
||||
void HistoryWidget::updateHighlightedMessage() {
|
||||
const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId);
|
||||
const auto view = item ? item->mainView() : nullptr;
|
||||
auto view = item ? item->mainView() : nullptr;
|
||||
if (!view) {
|
||||
return stopMessageHighlight();
|
||||
}
|
||||
@@ -1105,6 +1100,11 @@ void HistoryWidget::updateHighlightedMessage() {
|
||||
return stopMessageHighlight();
|
||||
}
|
||||
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = group->items.front()->mainView()) {
|
||||
view = leader;
|
||||
}
|
||||
}
|
||||
session().data().requestViewRepaint(view);
|
||||
}
|
||||
|
||||
@@ -1661,6 +1661,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
_editMsgId = 0;
|
||||
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
|
||||
}
|
||||
updateCmdStartShown();
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry();
|
||||
refreshTopBarActiveChat();
|
||||
@@ -2228,11 +2229,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||
_botCommandStart->hide();
|
||||
} else {
|
||||
_botKeyboardShow->hide();
|
||||
if (_cmdStartShown) {
|
||||
_botCommandStart->show();
|
||||
} else {
|
||||
_botCommandStart->hide();
|
||||
}
|
||||
_botCommandStart->setVisible(_cmdStartShown);
|
||||
}
|
||||
}
|
||||
_attachToggle->show();
|
||||
@@ -3738,7 +3735,7 @@ void HistoryWidget::updateSendButtonType() {
|
||||
bool HistoryWidget::updateCmdStartShown() {
|
||||
bool cmdStartShown = false;
|
||||
if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->isBot()))) {
|
||||
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) {
|
||||
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) {
|
||||
if (!HasSendText(_field)) {
|
||||
cmdStartShown = true;
|
||||
}
|
||||
@@ -4081,8 +4078,8 @@ void HistoryWidget::checkFieldAutocomplete() {
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '/'
|
||||
&& _peer->isUser()
|
||||
&& !_peer->asUser()->isBot()) {
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|
||||
|| _editMsgId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4845,7 +4842,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
|
||||
_tabbedSelectorToggle->show();
|
||||
_botKeyboardHide->hide();
|
||||
_botKeyboardShow->hide();
|
||||
_botCommandStart->show();
|
||||
_botCommandStart->setVisible(!_editMsgId);
|
||||
}
|
||||
_field->setMaxHeight(computeMaxFieldHeight());
|
||||
_kbShown = false;
|
||||
@@ -5652,7 +5649,7 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
||||
}
|
||||
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (_voiceRecordBar->isListenState()) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1814,7 +1814,7 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
|
||||
Expects(_history != nullptr);
|
||||
Expects(draftKeyCurrent() != Data::DraftKey::None());
|
||||
|
||||
if (_voiceRecordBar && _voiceRecordBar->isListenState()) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -924,12 +924,9 @@ CancelButton::CancelButton(not_null<Ui::RpWidget*> parent, int height)
|
||||
|
||||
void CancelButton::init() {
|
||||
_showProgress.value(
|
||||
) | rpl::start_with_next([=](float64 progress) {
|
||||
const auto hasProgress = (progress > 0.);
|
||||
if (isHidden() == !hasProgress) {
|
||||
setVisible(hasProgress);
|
||||
}
|
||||
update();
|
||||
) | rpl::map(rpl::mappers::_1 > 0.) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool hasProgress) {
|
||||
setVisible(hasProgress);
|
||||
}, lifetime());
|
||||
|
||||
paintRequest(
|
||||
@@ -960,6 +957,7 @@ QPoint CancelButton::prepareRippleStartPosition() const {
|
||||
|
||||
void CancelButton::requestPaintProgress(float64 progress) {
|
||||
_showProgress = progress;
|
||||
update();
|
||||
}
|
||||
|
||||
VoiceRecordBar::VoiceRecordBar(
|
||||
|
||||
@@ -79,7 +79,7 @@ bool SimpleElementDelegate::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time SimpleElementDelegate::elementHighlightTime(
|
||||
not_null<const Element*> element) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
return crl::time(0);
|
||||
}
|
||||
|
||||
@@ -280,29 +280,44 @@ void Element::refreshDataIdHook() {
|
||||
void Element::paintHighlight(
|
||||
Painter &p,
|
||||
int geometryHeight) const {
|
||||
const auto animms = delegate()->elementHighlightTime(this);
|
||||
if (!animms
|
||||
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto top = marginTop();
|
||||
const auto bottom = marginBottom();
|
||||
const auto fill = qMin(top, bottom);
|
||||
const auto skiptop = top - fill;
|
||||
const auto fillheight = fill + geometryHeight + fill;
|
||||
|
||||
const auto dt = (animms > st::activeFadeInDuration)
|
||||
paintCustomHighlight(p, skiptop, fillheight, data());
|
||||
}
|
||||
|
||||
float64 Element::highlightOpacity(not_null<const HistoryItem*> item) const {
|
||||
const auto animms = delegate()->elementHighlightTime(item);
|
||||
if (!animms
|
||||
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
return (animms > st::activeFadeInDuration)
|
||||
? (1. - (animms - st::activeFadeInDuration)
|
||||
/ float64(st::activeFadeOutDuration))
|
||||
: (animms / float64(st::activeFadeInDuration));
|
||||
}
|
||||
|
||||
void Element::paintCustomHighlight(
|
||||
Painter &p,
|
||||
int y,
|
||||
int height,
|
||||
not_null<const HistoryItem*> item) const {
|
||||
const auto opacity = highlightOpacity(item);
|
||||
if (opacity == 0.) {
|
||||
return;
|
||||
}
|
||||
const auto o = p.opacity();
|
||||
p.setOpacity(o * dt);
|
||||
p.setOpacity(o * opacity);
|
||||
p.fillRect(
|
||||
0,
|
||||
skiptop,
|
||||
y,
|
||||
width(),
|
||||
fillheight,
|
||||
height,
|
||||
st::defaultTextPalette.selectOverlay);
|
||||
p.setOpacity(o);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
Element *replacing = nullptr) = 0;
|
||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||
virtual crl::time elementHighlightTime(
|
||||
not_null<const Element*> element) = 0;
|
||||
not_null<const HistoryItem*> item) = 0;
|
||||
virtual bool elementInSelectionMode() = 0;
|
||||
virtual bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
Element *replacing = nullptr) override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
@@ -301,6 +301,13 @@ public:
|
||||
virtual void unloadHeavyPart();
|
||||
void checkHeavyPart();
|
||||
|
||||
void paintCustomHighlight(
|
||||
Painter &p,
|
||||
int y,
|
||||
int height,
|
||||
not_null<const HistoryItem*> item) const;
|
||||
float64 highlightOpacity(not_null<const HistoryItem*> item) const;
|
||||
|
||||
// Legacy blocks structure.
|
||||
HistoryBlock *block();
|
||||
const HistoryBlock *block() const;
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_group_call.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
#include "ui/chat/group_call_userpics.h"
|
||||
#include "ui/painter.h"
|
||||
#include "calls/calls_group_call.h"
|
||||
#include "calls/calls_instance.h"
|
||||
@@ -24,7 +25,7 @@ namespace HistoryView {
|
||||
void GenerateUserpicsInRow(
|
||||
QImage &result,
|
||||
const std::vector<UserpicInRow> &list,
|
||||
const UserpicsInRowStyle &st,
|
||||
const style::GroupCallUserpics &st,
|
||||
int maxElements) {
|
||||
const auto count = int(list.size());
|
||||
if (!count) {
|
||||
@@ -67,7 +68,7 @@ GroupCallTracker::GroupCallTracker(not_null<PeerData*> peer)
|
||||
|
||||
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
not_null<Data::GroupCall*> call,
|
||||
const UserpicsInRowStyle &st) {
|
||||
int userpicSize) {
|
||||
struct State {
|
||||
std::vector<UserpicInRow> userpics;
|
||||
Ui::GroupCallBarContent current;
|
||||
@@ -130,7 +131,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
static const auto RegenerateUserpics = [](
|
||||
not_null<State*> state,
|
||||
not_null<Data::GroupCall*> call,
|
||||
const UserpicsInRowStyle &st,
|
||||
int userpicSize,
|
||||
bool force = false) {
|
||||
const auto result = FillMissingUserpics(state, call) || force;
|
||||
if (!result) {
|
||||
@@ -141,7 +142,9 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
state->someUserpicsNotLoaded = false;
|
||||
for (auto &userpic : state->userpics) {
|
||||
userpic.peer->loadUserpic();
|
||||
const auto pic = userpic.peer->genUserpic(userpic.view, st.size);
|
||||
const auto pic = userpic.peer->genUserpic(
|
||||
userpic.view,
|
||||
userpicSize);
|
||||
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
|
||||
state->current.users.push_back({
|
||||
.userpic = pic.toImage(),
|
||||
@@ -161,7 +164,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
not_null<State*> state,
|
||||
not_null<Data::GroupCall*> call,
|
||||
not_null<UserData*> user,
|
||||
const UserpicsInRowStyle &st) {
|
||||
int userpicSize) {
|
||||
const auto i = ranges::find(
|
||||
state->userpics,
|
||||
user,
|
||||
@@ -170,7 +173,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
return false;
|
||||
}
|
||||
state->userpics.erase(i);
|
||||
RegenerateUserpics(state, call, st, true);
|
||||
RegenerateUserpics(state, call, userpicSize, true);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -178,7 +181,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
not_null<State*> state,
|
||||
not_null<Data::GroupCall*> call,
|
||||
not_null<UserData*> user,
|
||||
const UserpicsInRowStyle &st) {
|
||||
int userpicSize) {
|
||||
Expects(state->userpics.size() <= kLimit);
|
||||
|
||||
const auto &participants = call->participants();
|
||||
@@ -237,7 +240,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
}
|
||||
Assert(state->userpics.size() <= kLimit);
|
||||
}
|
||||
RegenerateUserpics(state, call, st, true);
|
||||
RegenerateUserpics(state, call, userpicSize, true);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -262,12 +265,12 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
|
||||
const auto user = update.now ? update.now->user : update.was->user;
|
||||
if (!update.now) {
|
||||
if (RemoveUserpic(state, call, user, st)) {
|
||||
if (RemoveUserpic(state, call, user, userpicSize)) {
|
||||
pushNext();
|
||||
}
|
||||
} else if (update.now->speaking
|
||||
&& (!update.was || !update.was->speaking)) {
|
||||
if (CheckPushToFront(state, call, user, st)) {
|
||||
if (CheckPushToFront(state, call, user, userpicSize)) {
|
||||
pushNext();
|
||||
}
|
||||
} else {
|
||||
@@ -287,7 +290,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
updateSpeakingState = false;
|
||||
}
|
||||
}
|
||||
if (RegenerateUserpics(state, call, st)
|
||||
if (RegenerateUserpics(state, call, userpicSize)
|
||||
|| updateSpeakingState) {
|
||||
pushNext();
|
||||
}
|
||||
@@ -296,7 +299,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
|
||||
call->participantsSliceAdded(
|
||||
) | rpl::filter([=] {
|
||||
return RegenerateUserpics(state, call, st);
|
||||
return RegenerateUserpics(state, call, userpicSize);
|
||||
}) | rpl::start_with_next(pushNext, lifetime);
|
||||
|
||||
call->peer()->session().downloaderTaskFinished(
|
||||
@@ -306,14 +309,14 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
RegenerateUserpics(state, call, st, true);
|
||||
RegenerateUserpics(state, call, userpicSize, true);
|
||||
pushNext();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
RegenerateUserpics(state, call, st);
|
||||
RegenerateUserpics(state, call, userpicSize);
|
||||
|
||||
call->fullCountValue(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
@@ -345,12 +348,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
||||
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
||||
call->reload();
|
||||
}
|
||||
const auto st = UserpicsInRowStyle{
|
||||
.size = st::historyGroupCallUserpicSize,
|
||||
.shift = st::historyGroupCallUserpicShift,
|
||||
.stroke = st::historyGroupCallUserpicStroke,
|
||||
};
|
||||
return ContentByCall(call, st);
|
||||
return ContentByCall(call, st::historyGroupCallUserpics.size);
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ class GroupCall;
|
||||
class CloudImageView;
|
||||
} // namespace Data
|
||||
|
||||
namespace style {
|
||||
struct GroupCallUserpics;
|
||||
} // namespace style
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
struct UserpicInRow {
|
||||
@@ -27,16 +31,10 @@ struct UserpicInRow {
|
||||
mutable InMemoryKey uniqueKey;
|
||||
};
|
||||
|
||||
struct UserpicsInRowStyle {
|
||||
int size = 0;
|
||||
int shift = 0;
|
||||
int stroke = 0;
|
||||
};
|
||||
|
||||
void GenerateUserpicsInRow(
|
||||
QImage &result,
|
||||
const std::vector<UserpicInRow> &list,
|
||||
const UserpicsInRowStyle &st,
|
||||
const style::GroupCallUserpics &st,
|
||||
int maxElements = 0);
|
||||
|
||||
class GroupCallTracker final {
|
||||
@@ -48,7 +46,7 @@ public:
|
||||
|
||||
[[nodiscard]] static rpl::producer<Ui::GroupCallBarContent> ContentByCall(
|
||||
not_null<Data::GroupCall*> call,
|
||||
const UserpicsInRowStyle &st);
|
||||
int userpicSize);
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
@@ -482,7 +482,7 @@ void ListWidget::highlightMessage(FullMsgId itemId) {
|
||||
_highlightedMessageId = itemId;
|
||||
_highlightTimer.callEach(AnimationTimerDelta);
|
||||
|
||||
repaintItem(view);
|
||||
repaintHighlightedItem(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,10 +496,24 @@ void ListWidget::showAroundPosition(
|
||||
refreshViewer();
|
||||
}
|
||||
|
||||
void ListWidget::repaintHighlightedItem(not_null<const Element*> view) {
|
||||
if (view->isHiddenByGroup()) {
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = viewForItem(group->items.front())) {
|
||||
if (!leader->isHiddenByGroup()) {
|
||||
repaintItem(leader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repaintItem(view);
|
||||
}
|
||||
|
||||
void ListWidget::updateHighlightedMessage() {
|
||||
if (const auto item = session().data().message(_highlightedMessageId)) {
|
||||
if (const auto view = viewForItem(item)) {
|
||||
repaintItem(view);
|
||||
repaintHighlightedItem(view);
|
||||
auto duration = st::activeFadeInDuration + st::activeFadeOutDuration;
|
||||
if (crl::now() - _highlightStart <= duration) {
|
||||
return;
|
||||
@@ -1244,8 +1258,8 @@ bool ListWidget::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time ListWidget::elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) {
|
||||
if (element->data()->fullId() == _highlightedMessageId) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
if (item->fullId() == _highlightedMessageId) {
|
||||
if (_highlightTimer.isActive()) {
|
||||
return crl::now() - _highlightStart;
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
Element *replacing = nullptr) override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
@@ -340,6 +340,7 @@ private:
|
||||
int itemTop(not_null<const Element*> view) const;
|
||||
void repaintItem(FullMsgId itemId);
|
||||
void repaintItem(const Element *view);
|
||||
void repaintHighlightedItem(not_null<const Element*> view);
|
||||
void resizeItem(not_null<Element*> view);
|
||||
void refreshItem(not_null<const Element*> view);
|
||||
void itemRemoved(not_null<const HistoryItem*> item);
|
||||
|
||||
@@ -570,7 +570,40 @@ void Message::draw(
|
||||
return;
|
||||
}
|
||||
|
||||
paintHighlight(p, g.height());
|
||||
auto entry = logEntryOriginal();
|
||||
auto mediaDisplayed = media && media->isDisplayed();
|
||||
|
||||
// Entry page is always a bubble bottom.
|
||||
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
|
||||
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
|
||||
|
||||
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
|
||||
? media->getBubbleSelectionIntervals(selection)
|
||||
: std::vector<BubbleSelectionInterval>();
|
||||
auto localMediaTop = 0;
|
||||
const auto customHighlight = mediaDisplayed && media->customHighlight();
|
||||
if (!mediaSelectionIntervals.empty() || customHighlight) {
|
||||
auto localMediaBottom = g.top() + g.height();
|
||||
if (data()->repliesAreComments() || data()->externalReply()) {
|
||||
localMediaBottom -= st::historyCommentsButtonHeight;
|
||||
}
|
||||
if (!mediaOnBottom) {
|
||||
localMediaBottom -= st::msgPadding.bottom();
|
||||
}
|
||||
if (entry) {
|
||||
localMediaBottom -= entry->height();
|
||||
}
|
||||
localMediaTop = localMediaBottom - media->height();
|
||||
for (auto &[top, height] : mediaSelectionIntervals) {
|
||||
top += localMediaTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (customHighlight) {
|
||||
media->drawHighlight(p, localMediaTop);
|
||||
} else {
|
||||
paintHighlight(p, g.height());
|
||||
}
|
||||
|
||||
const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll();
|
||||
if (roll) {
|
||||
@@ -602,34 +635,6 @@ void Message::draw(
|
||||
fromNameUpdated(g.width());
|
||||
}
|
||||
|
||||
auto entry = logEntryOriginal();
|
||||
auto mediaDisplayed = media && media->isDisplayed();
|
||||
|
||||
// Entry page is always a bubble bottom.
|
||||
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
|
||||
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
|
||||
|
||||
|
||||
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
|
||||
? media->getBubbleSelectionIntervals(selection)
|
||||
: std::vector<BubbleSelectionInterval>();
|
||||
if (!mediaSelectionIntervals.empty()) {
|
||||
auto localMediaBottom = g.top() + g.height();
|
||||
if (data()->repliesAreComments() || data()->externalReply()) {
|
||||
localMediaBottom -= st::historyCommentsButtonHeight;
|
||||
}
|
||||
if (!mediaOnBottom) {
|
||||
localMediaBottom -= st::msgPadding.bottom();
|
||||
}
|
||||
if (entry) {
|
||||
localMediaBottom -= entry->height();
|
||||
}
|
||||
const auto localMediaTop = localMediaBottom - media->height();
|
||||
for (auto &[top, height] : mediaSelectionIntervals) {
|
||||
top += localMediaTop;
|
||||
}
|
||||
}
|
||||
|
||||
auto skipTail = isAttachedToNext()
|
||||
|| (media && media->skipBubbleTail())
|
||||
|| (keyboard != nullptr)
|
||||
@@ -795,8 +800,8 @@ void Message::paintCommentsButton(
|
||||
auto &list = _comments->userpics;
|
||||
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
|
||||
const auto count = std::min(int(views->recentRepliers.size()), limit);
|
||||
const auto single = st::historyCommentsUserpicSize;
|
||||
const auto shift = st::historyCommentsUserpicOverlap;
|
||||
const auto single = st::historyCommentsUserpics.size;
|
||||
const auto shift = st::historyCommentsUserpics.shift;
|
||||
const auto regenerate = [&] {
|
||||
if (list.size() != count) {
|
||||
return true;
|
||||
@@ -828,12 +833,11 @@ void Message::paintCommentsButton(
|
||||
while (list.size() > count) {
|
||||
list.pop_back();
|
||||
}
|
||||
const auto st = UserpicsInRowStyle{
|
||||
.size = single,
|
||||
.shift = shift,
|
||||
.stroke = st::historyCommentsUserpicStroke,
|
||||
};
|
||||
GenerateUserpicsInRow(_comments->cachedUserpics, list, st, limit);
|
||||
GenerateUserpicsInRow(
|
||||
_comments->cachedUserpics,
|
||||
list,
|
||||
st::historyCommentsUserpics,
|
||||
limit);
|
||||
}
|
||||
p.drawImage(
|
||||
left,
|
||||
@@ -2135,8 +2139,8 @@ int Message::minWidthForMedia() const {
|
||||
const auto views = data()->Get<HistoryMessageViews>();
|
||||
if (data()->repliesAreComments() && !views->replies.text.isEmpty()) {
|
||||
const auto limit = HistoryMessageViews::kMaxRecentRepliers;
|
||||
const auto single = st::historyCommentsUserpicSize;
|
||||
const auto shift = st::historyCommentsUserpicOverlap;
|
||||
const auto single = st::historyCommentsUserpics.size;
|
||||
const auto shift = st::historyCommentsUserpics.shift;
|
||||
const auto added = single
|
||||
+ (limit - 1) * (single - shift)
|
||||
+ st::historyCommentsSkipLeft
|
||||
|
||||
@@ -951,6 +951,7 @@ void Document::drawGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
p.translate(geometry.topLeft());
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const override;
|
||||
TextState getStateGrouped(
|
||||
|
||||
@@ -901,6 +901,7 @@ void Gif::drawGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
ensureDataMediaCreated();
|
||||
@@ -989,8 +990,16 @@ void Gif::drawGrouped(
|
||||
p.drawPixmap(geometry, *cache);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
const auto overlayOpacity = selected
|
||||
? (1. - highlightOpacity)
|
||||
: highlightOpacity;
|
||||
if (overlayOpacity > 0.) {
|
||||
p.setOpacity(overlayOpacity);
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
if (!selected) {
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
if (radial
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const override;
|
||||
TextState getStateGrouped(
|
||||
|
||||
@@ -84,6 +84,8 @@ public:
|
||||
}
|
||||
virtual void refreshParentId(not_null<HistoryItem*> realParent) {
|
||||
}
|
||||
virtual void drawHighlight(Painter &p, int top) const {
|
||||
}
|
||||
virtual void draw(
|
||||
Painter &p,
|
||||
const QRect &r,
|
||||
@@ -177,6 +179,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
Unexpected("Grouping method call.");
|
||||
@@ -274,6 +277,9 @@ public:
|
||||
const QRect &bubble,
|
||||
crl::time ms) const {
|
||||
}
|
||||
[[nodiscard]] virtual bool customHighlight() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool hasHeavyPart() const {
|
||||
return false;
|
||||
|
||||
@@ -268,6 +268,18 @@ QMargins GroupedMedia::groupedPadding() const {
|
||||
(normal.bottom() - grouped.bottom()) + addToBottom);
|
||||
}
|
||||
|
||||
void GroupedMedia::drawHighlight(Painter &p, int top) const {
|
||||
if (_mode != Mode::Column) {
|
||||
return;
|
||||
}
|
||||
const auto skip = top + groupedPadding().top();
|
||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
||||
const auto &part = _parts[i];
|
||||
const auto rect = part.geometry.translated(0, skip);
|
||||
_parent->paintCustomHighlight(p, rect.y(), rect.height(), part.item);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupedMedia::draw(
|
||||
Painter &p,
|
||||
const QRect &clip,
|
||||
@@ -290,6 +302,9 @@ void GroupedMedia::draw(
|
||||
if (textSelection) {
|
||||
selection = part.content->skipSelection(selection);
|
||||
}
|
||||
const auto highlightOpacity = (_mode == Mode::Grid)
|
||||
? _parent->highlightOpacity(part.item)
|
||||
: 0.;
|
||||
part.content->drawGrouped(
|
||||
p,
|
||||
clip,
|
||||
@@ -298,6 +313,7 @@ void GroupedMedia::draw(
|
||||
part.geometry.translated(0, groupPadding.top()),
|
||||
part.sides,
|
||||
cornersFromSides(part.sides),
|
||||
highlightOpacity,
|
||||
&part.cacheKey,
|
||||
&part.cache);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
|
||||
void refreshParentId(not_null<HistoryItem*> realParent) override;
|
||||
|
||||
void drawHighlight(Painter &p, int top) const override;
|
||||
void draw(
|
||||
Painter &p,
|
||||
const QRect &clip,
|
||||
@@ -87,6 +88,9 @@ public:
|
||||
bool allowsFastShare() const override {
|
||||
return true;
|
||||
}
|
||||
bool customHighlight() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopAnimation() override;
|
||||
void checkAnimation() override;
|
||||
|
||||
@@ -485,6 +485,7 @@ void Photo::drawGrouped(
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const {
|
||||
ensureDataMediaCreated();
|
||||
@@ -509,9 +510,18 @@ void Photo::drawGrouped(
|
||||
// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
}
|
||||
p.drawPixmap(geometry.topLeft(), *cache);
|
||||
if (selected) {
|
||||
|
||||
const auto overlayOpacity = selected
|
||||
? (1. - highlightOpacity)
|
||||
: highlightOpacity;
|
||||
if (overlayOpacity > 0.) {
|
||||
p.setOpacity(overlayOpacity);
|
||||
const auto roundRadius = ImageRoundRadius::Large;
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
if (!selected) {
|
||||
Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
const auto displayState = radial
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
const QRect &geometry,
|
||||
RectParts sides,
|
||||
RectParts corners,
|
||||
float64 highlightOpacity,
|
||||
not_null<uint64*> cacheKey,
|
||||
not_null<QPixmap*> cache) const override;
|
||||
TextState getStateGrouped(
|
||||
|
||||
@@ -247,7 +247,7 @@ rpl::producer<int> AdminsCountValue(not_null<PeerData*> peer) {
|
||||
) | rpl::map([=] {
|
||||
return chat->participants.empty()
|
||||
? 0
|
||||
: int(chat->admins.size() + 1); // + creator
|
||||
: int(chat->admins.size() + (chat->creator ? 1 : 0));
|
||||
});
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
|
||||
@@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "main/main_account.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "core/application.h"
|
||||
#include "core/launcher.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_domain.h" // Storage::StartResult.
|
||||
@@ -386,8 +386,8 @@ void Account::startMtp(std::unique_ptr<MTP::Config> config) {
|
||||
|
||||
auto fields = base::take(_mtpFields);
|
||||
fields.config = std::move(config);
|
||||
fields.deviceModel = Core::App().launcher()->deviceModel();
|
||||
fields.systemVersion = Core::App().launcher()->systemVersion();
|
||||
fields.deviceModel = Platform::DeviceModelPretty();
|
||||
fields.systemVersion = Platform::SystemVersionPretty();
|
||||
_mtp = std::make_unique<MTP::Instance>(
|
||||
MTP::Instance::Mode::Normal,
|
||||
std::move(fields));
|
||||
@@ -534,8 +534,8 @@ void Account::destroyMtpKeys(MTP::AuthKeysList &&keys) {
|
||||
destroyFields.mainDcId = MTP::Instance::Fields::kNoneMainDc;
|
||||
destroyFields.config = std::make_unique<MTP::Config>(_mtp->config());
|
||||
destroyFields.keys = std::move(keys);
|
||||
destroyFields.deviceModel = Core::App().launcher()->deviceModel();
|
||||
destroyFields.systemVersion = Core::App().launcher()->systemVersion();
|
||||
destroyFields.deviceModel = Platform::DeviceModelPretty();
|
||||
destroyFields.systemVersion = Platform::SystemVersionPretty();
|
||||
_mtpForKeysDestroy = std::make_unique<MTP::Instance>(
|
||||
MTP::Instance::Mode::KeysDestroyer,
|
||||
std::move(destroyFields));
|
||||
|
||||
@@ -2443,7 +2443,9 @@ bool OverlayWidget::initStreaming(bool continueStreaming) {
|
||||
void OverlayWidget::startStreamingPlayer() {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
if (_streamed->instance.player().playing()) {
|
||||
if (!_streamed->instance.player().paused()
|
||||
&& !_streamed->instance.player().finished()
|
||||
&& !_streamed->instance.player().failed()) {
|
||||
if (!_streamed->withSound) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1479,9 +1479,8 @@ Link::Link(
|
||||
int32 tw = 0, th = 0;
|
||||
if (_page && _page->photo) {
|
||||
const auto photo = _page->photo;
|
||||
if (photo->inlineThumbnailBytes().isEmpty()
|
||||
&& (photo->hasExact(Data::PhotoSize::Small)
|
||||
|| photo->hasExact(Data::PhotoSize::Thumbnail))) {
|
||||
if (photo->hasExact(Data::PhotoSize::Small)
|
||||
|| photo->hasExact(Data::PhotoSize::Thumbnail)) {
|
||||
photo->load(Data::PhotoSize::Small, parent->fullId());
|
||||
}
|
||||
tw = style::ConvertScale(photo->width());
|
||||
@@ -1623,7 +1622,7 @@ void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const P
|
||||
}
|
||||
|
||||
void Link::validateThumbnail() {
|
||||
if (!_thumbnail.isNull()) {
|
||||
if (!_thumbnail.isNull() && !_thumbnailBlurred) {
|
||||
return;
|
||||
}
|
||||
if (_page && _page->photo) {
|
||||
@@ -1631,12 +1630,16 @@ void Link::validateThumbnail() {
|
||||
ensurePhotoMediaCreated();
|
||||
if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
|
||||
_thumbnail = thumbnail->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto large = _photoMedia->image(PhotoSize::Large)) {
|
||||
_thumbnail = large->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
_thumbnail = small->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
_thumbnail = blurred->pixBlurredSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, ImageRoundRadius::Small);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -1644,14 +1647,20 @@ void Link::validateThumbnail() {
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
} else if (_page && _page->document && _page->document->hasThumbnail()) {
|
||||
ensureDocumentMediaCreated();
|
||||
const auto roundRadius = _page->document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (const auto thumbnail = _documentMedia->thumbnail()) {
|
||||
auto roundRadius = _page->document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
_thumbnail = thumbnail->pixSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius);
|
||||
_documentMedia = nullptr;
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
_thumbnailBlurred = false;
|
||||
} else if (const auto blurred = _documentMedia->thumbnailInline()) {
|
||||
_thumbnail = blurred->pixBlurredSingle(_pixw, _pixh, st::linksPhotoSize, st::linksPhotoSize, roundRadius);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
_documentMedia = nullptr;
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
} else {
|
||||
const auto size = QSize(st::linksPhotoSize, st::linksPhotoSize);
|
||||
_thumbnail = QPixmap(size * cIntRetinaFactor());
|
||||
@@ -1683,6 +1692,7 @@ void Link::validateThumbnail() {
|
||||
_letter,
|
||||
style::al_center);
|
||||
}
|
||||
_thumbnailBlurred = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -368,6 +368,7 @@ private:
|
||||
int _pixh = 0;
|
||||
Ui::Text::String _text = { st::msgMinWidth };
|
||||
QPixmap _thumbnail;
|
||||
bool _thumbnailBlurred = true;
|
||||
|
||||
struct LinkEntry {
|
||||
LinkEntry() : width(0) {
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/launcher_linux.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
@@ -46,7 +45,7 @@ private:
|
||||
} // namespace
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
|
||||
: Core::Launcher(argc, argv) {
|
||||
}
|
||||
|
||||
void Launcher::initHook() {
|
||||
|
||||
182
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp
Normal file
182
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
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_gsd_media_keys.h"
|
||||
|
||||
#include "core/sandbox.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
|
||||
#include <QtDBus/QDBusConnection>
|
||||
#include <QtDBus/QDBusConnectionInterface>
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
#include <gio/gio.h>
|
||||
#define signals public
|
||||
} // extern "C"
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDBusTimeout = 30000;
|
||||
constexpr auto kService = "org.gnome.SettingsDaemon.MediaKeys"_cs;
|
||||
constexpr auto kOldService = "org.gnome.SettingsDaemon"_cs;
|
||||
constexpr auto kMATEService = "org.mate.SettingsDaemon"_cs;
|
||||
constexpr auto kObjectPath = "/org/gnome/SettingsDaemon/MediaKeys"_cs;
|
||||
constexpr auto kMATEObjectPath = "/org/mate/SettingsDaemon/MediaKeys"_cs;
|
||||
constexpr auto kInterface = kService;
|
||||
constexpr auto kMATEInterface = "org.mate.SettingsDaemon.MediaKeys"_cs;
|
||||
|
||||
void KeyPressed(
|
||||
GDBusConnection *connection,
|
||||
const gchar *sender_name,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *signal_name,
|
||||
GVariant *parameters,
|
||||
gpointer user_data) {
|
||||
gchar *appUtf8;
|
||||
gchar *keyUtf8;
|
||||
g_variant_get(parameters, "(ss)", &appUtf8, &keyUtf8);
|
||||
const auto app = QString::fromUtf8(appUtf8);
|
||||
const auto key = QString::fromUtf8(keyUtf8);
|
||||
g_free(keyUtf8);
|
||||
g_free(appUtf8);
|
||||
|
||||
if (app != QCoreApplication::applicationName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
if (key == qstr("Play")) {
|
||||
Media::Player::instance()->playPause();
|
||||
} else if (key == qstr("Stop")) {
|
||||
Media::Player::instance()->stop();
|
||||
} else if (key == qstr("Next")) {
|
||||
Media::Player::instance()->next();
|
||||
} else if (key == qstr("Previous")) {
|
||||
Media::Player::instance()->previous();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GSDMediaKeys::GSDMediaKeys() {
|
||||
GError *error = nullptr;
|
||||
const auto interface = QDBusConnection::sessionBus().interface();
|
||||
|
||||
if (!interface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interface->isServiceRegistered(kService.utf16())) {
|
||||
_service = kService.utf16();
|
||||
_objectPath = kObjectPath.utf16();
|
||||
_interface = kInterface.utf16();
|
||||
} else if (interface->isServiceRegistered(kOldService.utf16())) {
|
||||
_service = kOldService.utf16();
|
||||
_objectPath = kObjectPath.utf16();
|
||||
_interface = kInterface.utf16();
|
||||
} else if (interface->isServiceRegistered(kMATEService.utf16())) {
|
||||
_service = kMATEService.utf16();
|
||||
_objectPath = kMATEObjectPath.utf16();
|
||||
_interface = kMATEInterface.utf16();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
_dbusConnection = g_bus_get_sync(
|
||||
G_BUS_TYPE_SESSION,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
LOG(("GSD Media Keys Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
auto reply = g_dbus_connection_call_sync(
|
||||
_dbusConnection,
|
||||
_service.toUtf8(),
|
||||
_objectPath.toUtf8(),
|
||||
_interface.toUtf8(),
|
||||
"GrabMediaPlayerKeys",
|
||||
g_variant_new(
|
||||
"(su)",
|
||||
QCoreApplication::applicationName().toUtf8().constData(),
|
||||
0),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
kDBusTimeout,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (!error) {
|
||||
_grabbed = true;
|
||||
g_variant_unref(reply);
|
||||
} else {
|
||||
LOG(("GSD Media Keys Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
}
|
||||
|
||||
_signalId = g_dbus_connection_signal_subscribe(
|
||||
_dbusConnection,
|
||||
_service.toUtf8(),
|
||||
_interface.toUtf8(),
|
||||
"MediaPlayerKeyPressed",
|
||||
_objectPath.toUtf8(),
|
||||
nullptr,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||
KeyPressed,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
GSDMediaKeys::~GSDMediaKeys() {
|
||||
GError *error = nullptr;
|
||||
|
||||
if (_signalId != 0) {
|
||||
g_dbus_connection_signal_unsubscribe(
|
||||
_dbusConnection,
|
||||
_signalId);
|
||||
}
|
||||
|
||||
if (_grabbed) {
|
||||
auto reply = g_dbus_connection_call_sync(
|
||||
_dbusConnection,
|
||||
_service.toUtf8(),
|
||||
_objectPath.toUtf8(),
|
||||
_interface.toUtf8(),
|
||||
"ReleaseMediaPlayerKeys",
|
||||
g_variant_new(
|
||||
"(s)",
|
||||
QCoreApplication::applicationName().toUtf8().constData()),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
kDBusTimeout,
|
||||
nullptr,
|
||||
&error);
|
||||
|
||||
if (!error) {
|
||||
_grabbed = false;
|
||||
g_variant_unref(reply);
|
||||
} else {
|
||||
LOG(("GSD Media Keys Error: %1").arg(error->message));
|
||||
g_error_free(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (_dbusConnection) {
|
||||
g_object_unref(_dbusConnection);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
36
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.h
Normal file
36
Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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
|
||||
|
||||
typedef struct _GDBusConnection GDBusConnection;
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
class GSDMediaKeys {
|
||||
public:
|
||||
GSDMediaKeys();
|
||||
|
||||
GSDMediaKeys(const GSDMediaKeys &other) = delete;
|
||||
GSDMediaKeys &operator=(const GSDMediaKeys &other) = delete;
|
||||
GSDMediaKeys(GSDMediaKeys &&other) = delete;
|
||||
GSDMediaKeys &operator=(GSDMediaKeys &&other) = delete;
|
||||
|
||||
~GSDMediaKeys();
|
||||
|
||||
private:
|
||||
GDBusConnection *_dbusConnection = nullptr;
|
||||
QString _service;
|
||||
QString _objectPath;
|
||||
QString _interface;
|
||||
uint _signalId = 0;
|
||||
bool _grabbed = false;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
@@ -23,9 +23,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/linux/base_xcb_utilities_linux.h"
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "platform/linux/linux_gsd_media_keys.h"
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "base/call_delayed.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
@@ -34,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include <QtCore/QTemporaryFile>
|
||||
#include <QtDBus/QDBusInterface>
|
||||
#include <QtDBus/QDBusConnection>
|
||||
#include <QtDBus/QDBusConnectionInterface>
|
||||
@@ -41,9 +48,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtDBus/QDBusMessage>
|
||||
#include <QtDBus/QDBusReply>
|
||||
#include <QtDBus/QDBusError>
|
||||
#include <QtDBus/QDBusObjectPath>
|
||||
#include <QtDBus/QDBusMetaType>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <statusnotifieritem.h>
|
||||
#include <dbusmenuexporter.h>
|
||||
|
||||
extern "C" {
|
||||
#undef signals
|
||||
@@ -58,6 +67,7 @@ namespace {
|
||||
constexpr auto kPanelTrayIconName = "telegram-panel"_cs;
|
||||
constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs;
|
||||
constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs;
|
||||
|
||||
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
|
||||
constexpr auto kTrayIconFilename = "tdesktop-trayicon-XXXXXX.png"_cs;
|
||||
|
||||
@@ -69,6 +79,8 @@ constexpr auto kAppMenuService = "com.canonical.AppMenu.Registrar"_cs;
|
||||
constexpr auto kAppMenuObjectPath = "/com/canonical/AppMenu/Registrar"_cs;
|
||||
constexpr auto kAppMenuInterface = kAppMenuService;
|
||||
|
||||
constexpr auto kMainMenuObjectPath = "/MenuBar"_cs;
|
||||
|
||||
bool TrayIconMuted = true;
|
||||
int32 TrayIconCount = 0;
|
||||
base::flat_map<int, QImage> TrayIconImageBack;
|
||||
@@ -209,6 +221,10 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
48,
|
||||
};
|
||||
|
||||
static const auto dprSize = [](const QImage &image) {
|
||||
return image.size() / image.devicePixelRatio();
|
||||
};
|
||||
|
||||
for (const auto iconSize : iconSizes) {
|
||||
auto ¤tImageBack = TrayIconImageBack[iconSize];
|
||||
const auto desiredSize = QSize(iconSize, iconSize);
|
||||
@@ -221,11 +237,17 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
systemIcon = QIcon::fromTheme(iconName);
|
||||
}
|
||||
|
||||
if (systemIcon.actualSize(desiredSize) == desiredSize) {
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(desiredSize)
|
||||
.toImage();
|
||||
} else {
|
||||
// We can't use QIcon::actualSize here
|
||||
// since it works incorrectly with svg icon themes
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(desiredSize)
|
||||
.toImage();
|
||||
|
||||
const auto firstAttemptSize = dprSize(currentImageBack);
|
||||
|
||||
// if current icon theme is not a svg one, Qt can return
|
||||
// a pixmap that less in size even if there are a bigger one
|
||||
if (firstAttemptSize.width() < desiredSize.width()) {
|
||||
const auto availableSizes = systemIcon.availableSizes();
|
||||
|
||||
const auto biggestSize = ranges::max_element(
|
||||
@@ -233,18 +255,17 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
std::less<>(),
|
||||
&QSize::width);
|
||||
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(*biggestSize)
|
||||
.toImage();
|
||||
if ((*biggestSize).width() > firstAttemptSize.width()) {
|
||||
currentImageBack = systemIcon
|
||||
.pixmap(*biggestSize)
|
||||
.toImage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentImageBack = Core::App().logo();
|
||||
}
|
||||
|
||||
const auto currentImageBackSize = currentImageBack.size()
|
||||
/ currentImageBack.devicePixelRatio();
|
||||
|
||||
if (currentImageBackSize != desiredSize) {
|
||||
if (dprSize(currentImageBack) != desiredSize) {
|
||||
currentImageBack = currentImageBack.scaled(
|
||||
desiredSize * currentImageBack.devicePixelRatio(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
@@ -331,17 +352,33 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
|
||||
static const auto templateName = AppRuntimeDirectory()
|
||||
+ kTrayIconFilename.utf16();
|
||||
|
||||
static const auto dprSize = [](const QPixmap &pixmap) {
|
||||
return pixmap.size() / pixmap.devicePixelRatio();
|
||||
};
|
||||
|
||||
static const auto desiredSize = QSize(22, 22);
|
||||
|
||||
static const auto scalePixmap = [=](const QPixmap &pixmap) {
|
||||
if (dprSize(pixmap) != desiredSize) {
|
||||
return pixmap.scaled(
|
||||
desiredSize * pixmap.devicePixelRatio(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
} else {
|
||||
return pixmap;
|
||||
}
|
||||
};
|
||||
|
||||
auto ret = std::make_unique<QTemporaryFile>(
|
||||
templateName,
|
||||
parent);
|
||||
|
||||
ret->open();
|
||||
|
||||
if (icon.actualSize(desiredSize) == desiredSize) {
|
||||
icon.pixmap(desiredSize).save(ret.get());
|
||||
} else {
|
||||
const auto firstAttempt = icon.pixmap(desiredSize);
|
||||
const auto firstAttemptSize = dprSize(firstAttempt);
|
||||
|
||||
if (firstAttemptSize.width() < desiredSize.width()) {
|
||||
const auto availableSizes = icon.availableSizes();
|
||||
|
||||
const auto biggestSize = ranges::max_element(
|
||||
@@ -349,14 +386,13 @@ std::unique_ptr<QTemporaryFile> TrayIconFile(
|
||||
std::less<>(),
|
||||
&QSize::width);
|
||||
|
||||
const auto iconPixmap = icon.pixmap(*biggestSize);
|
||||
|
||||
iconPixmap
|
||||
.scaled(
|
||||
desiredSize * iconPixmap.devicePixelRatio(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
.save(ret.get());
|
||||
if ((*biggestSize).width() > firstAttemptSize.width()) {
|
||||
scalePixmap(icon.pixmap(*biggestSize)).save(ret.get());
|
||||
} else {
|
||||
scalePixmap(firstAttempt).save(ret.get());
|
||||
}
|
||||
} else {
|
||||
scalePixmap(firstAttempt).save(ret.get());
|
||||
}
|
||||
|
||||
ret->close();
|
||||
@@ -419,7 +455,7 @@ bool IsAppMenuSupported() {
|
||||
return interface->isServiceRegistered(kAppMenuService.utf16());
|
||||
}
|
||||
|
||||
void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) {
|
||||
void RegisterAppMenu(uint winId, const QString &menuPath) {
|
||||
auto message = QDBusMessage::createMethodCall(
|
||||
kAppMenuService.utf16(),
|
||||
kAppMenuObjectPath.utf16(),
|
||||
@@ -428,7 +464,7 @@ void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) {
|
||||
|
||||
message.setArguments({
|
||||
winId,
|
||||
QVariant::fromValue(menuPath)
|
||||
QVariant::fromValue(QDBusObjectPath(menuPath))
|
||||
});
|
||||
|
||||
QDBusConnection::sessionBus().send(message);
|
||||
@@ -548,6 +584,17 @@ void MainWindow::initHook() {
|
||||
} else {
|
||||
LOG(("Not using Unity launcher counter."));
|
||||
}
|
||||
|
||||
Media::Player::instance()->updatedNotifier(
|
||||
) | rpl::start_with_next([=](const Media::Player::TrackState &state) {
|
||||
if (!Media::Player::IsStoppedOrStopping(state.state)) {
|
||||
if (!_gsdMediaKeys) {
|
||||
_gsdMediaKeys = std::make_unique<internal::GSDMediaKeys>();
|
||||
}
|
||||
} else if (_gsdMediaKeys) {
|
||||
_gsdMediaKeys = nullptr;
|
||||
}
|
||||
}, lifetime());
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
updateWaylandDecorationColors();
|
||||
@@ -716,8 +763,8 @@ void MainWindow::handleAppMenuOwnerChanged(
|
||||
LOG(("Not using D-Bus global menu."));
|
||||
}
|
||||
|
||||
if (_appMenuSupported && !_mainMenuPath.path().isEmpty()) {
|
||||
RegisterAppMenu(winId(), _mainMenuPath);
|
||||
if (_appMenuSupported && _mainMenuExporter) {
|
||||
RegisterAppMenu(winId(), kMainMenuObjectPath.utf16());
|
||||
} else {
|
||||
UnregisterAppMenu(winId());
|
||||
}
|
||||
@@ -1032,14 +1079,12 @@ void MainWindow::createGlobalMenu() {
|
||||
|
||||
about->setMenuRole(QAction::AboutQtRole);
|
||||
|
||||
_mainMenuPath.setPath(qsl("/MenuBar"));
|
||||
|
||||
_mainMenuExporter = new DBusMenuExporter(
|
||||
_mainMenuPath.path(),
|
||||
kMainMenuObjectPath.utf16(),
|
||||
psMainMenu);
|
||||
|
||||
if (_appMenuSupported) {
|
||||
RegisterAppMenu(winId(), _mainMenuPath);
|
||||
RegisterAppMenu(winId(), kMainMenuObjectPath.utf16());
|
||||
}
|
||||
|
||||
updateGlobalMenu();
|
||||
@@ -1171,9 +1216,9 @@ void MainWindow::handleVisibleChangedHook(bool visible) {
|
||||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (_appMenuSupported && !_mainMenuPath.path().isEmpty()) {
|
||||
if (_appMenuSupported && _mainMenuExporter) {
|
||||
if (visible) {
|
||||
RegisterAppMenu(winId(), _mainMenuPath);
|
||||
RegisterAppMenu(winId(), kMainMenuObjectPath.utf16());
|
||||
} else {
|
||||
UnregisterAppMenu(winId());
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_main_window.h"
|
||||
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "statusnotifieritem.h"
|
||||
#include <QtCore/QTemporaryFile>
|
||||
#include <QtDBus/QDBusObjectPath>
|
||||
#include <dbusmenuexporter.h>
|
||||
class QTemporaryFile;
|
||||
class DBusMenuExporter;
|
||||
class StatusNotifierItem;
|
||||
|
||||
typedef void* gpointer;
|
||||
typedef char gchar;
|
||||
@@ -24,6 +25,11 @@ typedef struct _GDBusProxy GDBusProxy;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
namespace Platform {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
namespace internal {
|
||||
class GSDMediaKeys;
|
||||
} // namespace internal
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
class MainWindow : public Window::MainWindow {
|
||||
public:
|
||||
@@ -82,11 +88,12 @@ private:
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
StatusNotifierItem *_sniTrayIcon = nullptr;
|
||||
GDBusProxy *_sniDBusProxy = nullptr;
|
||||
std::unique_ptr<QTemporaryFile> _trayIconFile = nullptr;
|
||||
std::unique_ptr<QTemporaryFile> _trayIconFile;
|
||||
|
||||
bool _appMenuSupported = false;
|
||||
DBusMenuExporter *_mainMenuExporter = nullptr;
|
||||
QDBusObjectPath _mainMenuPath;
|
||||
|
||||
std::unique_ptr<internal::GSDMediaKeys> _gsdMediaKeys;
|
||||
|
||||
QMenu *psMainMenu = nullptr;
|
||||
QAction *psLogout = nullptr;
|
||||
|
||||
@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/screensaver.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -452,25 +451,6 @@ bool ShowXCBWindowMenu(QWindow *window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCBFrameExtentsSupported() {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto frameExtentsAtom = base::Platform::XCB::GetAtom(
|
||||
connection,
|
||||
kXCBFrameExtentsAtomName.utf16());
|
||||
|
||||
if (!frameExtentsAtom.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ranges::contains(
|
||||
base::Platform::XCB::GetWMSupported(connection),
|
||||
*frameExtentsAtom);
|
||||
}
|
||||
|
||||
bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
if (!connection) {
|
||||
@@ -527,25 +507,6 @@ bool UnsetXCBFrameExtents(QWindow *window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCBSkipTaskbarSupported() {
|
||||
const auto connection = base::Platform::XCB::GetConnectionFromQt();
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto skipTaskbarAtom = base::Platform::XCB::GetAtom(
|
||||
connection,
|
||||
"_NET_WM_STATE_SKIP_TASKBAR");
|
||||
|
||||
if (!skipTaskbarAtom.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ranges::contains(
|
||||
base::Platform::XCB::GetWMSupported(connection),
|
||||
*skipTaskbarAtom);
|
||||
}
|
||||
|
||||
Window::Control GtkKeywordToWindowControl(const QString &keyword) {
|
||||
if (keyword == qstr("minimize")) {
|
||||
return Window::Control::Minimize;
|
||||
@@ -805,7 +766,8 @@ bool TrayIconSupported() {
|
||||
}
|
||||
|
||||
bool SkipTaskbarSupported() {
|
||||
return !IsWayland() && XCBSkipTaskbarSupported();
|
||||
return !IsWayland()
|
||||
&& base::Platform::XCB::IsSupportedByWM("_NET_WM_STATE_SKIP_TASKBAR");
|
||||
}
|
||||
|
||||
bool StartSystemMove(QWindow *window) {
|
||||
@@ -849,11 +811,8 @@ bool UnsetWindowExtents(QWindow *window) {
|
||||
}
|
||||
|
||||
bool WindowsNeedShadow() {
|
||||
if (!IsWayland() && XCBFrameExtentsSupported()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return !IsWayland()
|
||||
&& base::Platform::XCB::IsSupportedByWM(kXCBFrameExtentsAtomName.utf16());
|
||||
}
|
||||
|
||||
Window::ControlsLayout WindowControlsLayout() {
|
||||
|
||||
@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "base/platform/mac/base_utilities_mac.h"
|
||||
|
||||
@@ -20,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Platform {
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
|
||||
: Core::Launcher(argc, argv) {
|
||||
}
|
||||
|
||||
void Launcher::initHook() {
|
||||
|
||||
@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_h.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
@@ -18,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Platform {
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) {
|
||||
: Core::Launcher(argc, argv) {
|
||||
}
|
||||
|
||||
std::optional<QStringList> Launcher::readArgumentsHook(
|
||||
|
||||
@@ -217,7 +217,11 @@ void MainWindow::psSetupTrayIcon() {
|
||||
auto icon = QIcon(App::pixmapFromImageInPlace(Core::App().logoNoMargin()));
|
||||
|
||||
trayIcon->setIcon(icon);
|
||||
connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showFromTray()));
|
||||
connect(
|
||||
trayIcon,
|
||||
&QSystemTrayIcon::messageClicked,
|
||||
this,
|
||||
[=] { App::wnd()->showFromTray(); });
|
||||
attachToTrayIcon(trayIcon);
|
||||
}
|
||||
updateIconCounters();
|
||||
|
||||
@@ -73,10 +73,18 @@ object_ptr<Ui::RpWidget> CreateIntroSettings(
|
||||
SetupUpdate(result);
|
||||
AddSkip(result);
|
||||
}
|
||||
AddDivider(result);
|
||||
AddSkip(result);
|
||||
SetupSystemIntegrationContent(result);
|
||||
AddSkip(result);
|
||||
{
|
||||
auto wrap = object_ptr<Ui::VerticalLayout>(result);
|
||||
SetupSystemIntegrationContent(wrap.data());
|
||||
if (wrap->count() > 0) {
|
||||
AddDivider(result);
|
||||
AddSkip(result);
|
||||
result->add(object_ptr<Ui::OverrideMargins>(
|
||||
result,
|
||||
std::move(wrap)));
|
||||
AddSkip(result);
|
||||
}
|
||||
}
|
||||
AddDivider(result);
|
||||
AddSkip(result);
|
||||
SetupInterfaceScale(result, false);
|
||||
|
||||
@@ -38,7 +38,9 @@ bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
|
||||
bool ValidPhotoForAlbum(
|
||||
const PreparedFileInformation::Image &image,
|
||||
const QString &mime) {
|
||||
if (image.animated || Core::IsMimeSticker(mime)) {
|
||||
if (image.animated
|
||||
|| Core::IsMimeSticker(mime)
|
||||
|| (mime == u"application/pdf"_q)) {
|
||||
return false;
|
||||
}
|
||||
const auto width = image.data.width();
|
||||
|
||||
@@ -18,6 +18,13 @@ MessageBar {
|
||||
duration: int;
|
||||
}
|
||||
|
||||
GroupCallUserpics {
|
||||
size: pixels;
|
||||
shift: pixels;
|
||||
stroke: pixels;
|
||||
align: align;
|
||||
}
|
||||
|
||||
defaultMessageBar: MessageBar {
|
||||
title: semiboldTextStyle;
|
||||
titleFg: windowActiveTextFg;
|
||||
@@ -739,10 +746,13 @@ historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSel
|
||||
historyCommentsButtonHeight: 40px;
|
||||
historyCommentsSkipLeft: 9px;
|
||||
historyCommentsSkipText: 10px;
|
||||
historyCommentsUserpicSize: 25px;
|
||||
historyCommentsUserpicStroke: 2px;
|
||||
historyCommentsUserpicOverlap: 6px;
|
||||
historyCommentsSkipRight: 8px;
|
||||
historyCommentsUserpics: GroupCallUserpics {
|
||||
size: 25px;
|
||||
shift: 6px;
|
||||
stroke: 2px;
|
||||
align: align(left);
|
||||
}
|
||||
|
||||
boxAttachEmoji: IconButton(historyAttachEmoji) {
|
||||
width: 30px;
|
||||
@@ -798,9 +808,12 @@ historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLin
|
||||
|
||||
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
||||
|
||||
historyGroupCallUserpicSize: 32px;
|
||||
historyGroupCallUserpicShift: 12px;
|
||||
historyGroupCallUserpicStroke: 4px;
|
||||
historyGroupCallUserpics: GroupCallUserpics {
|
||||
size: 32px;
|
||||
shift: 12px;
|
||||
stroke: 4px;
|
||||
align: align(top);
|
||||
}
|
||||
historyGroupCallBlobMinRadius: 23px;
|
||||
historyGroupCallBlobMaxRadius: 25px;
|
||||
|
||||
|
||||
@@ -7,12 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
|
||||
#include "ui/chat/message_bar.h"
|
||||
#include "ui/chat/group_call_userpics.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/paint/blobs.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
||||
@@ -21,71 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDuration = 160;
|
||||
constexpr auto kMaxUserpics = 4;
|
||||
constexpr auto kWideScale = 5;
|
||||
|
||||
constexpr auto kBlobsEnterDuration = crl::time(250);
|
||||
constexpr auto kLevelDuration = 100. + 500. * 0.23;
|
||||
constexpr auto kBlobScale = 0.605;
|
||||
constexpr auto kMinorBlobFactor = 0.9f;
|
||||
constexpr auto kUserpicMinScale = 0.8;
|
||||
constexpr auto kMaxLevel = 1.;
|
||||
constexpr auto kSendRandomLevelInterval = crl::time(100);
|
||||
|
||||
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||
return { {
|
||||
{
|
||||
.segmentsCount = 6,
|
||||
.minScale = kBlobScale * kMinorBlobFactor,
|
||||
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
|
||||
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
|
||||
.speedScale = 1.,
|
||||
.alpha = .5,
|
||||
},
|
||||
{
|
||||
.segmentsCount = 8,
|
||||
.minScale = kBlobScale,
|
||||
.minRadius = (float)st::historyGroupCallBlobMinRadius,
|
||||
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
|
||||
.speedScale = 1.,
|
||||
.alpha = .2,
|
||||
},
|
||||
} };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct GroupCallBar::BlobsAnimation {
|
||||
BlobsAnimation(
|
||||
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
||||
float levelDuration,
|
||||
float maxLevel)
|
||||
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
|
||||
}
|
||||
|
||||
Ui::Paint::Blobs blobs;
|
||||
crl::time lastTime = 0;
|
||||
crl::time lastSpeakingUpdateTime = 0;
|
||||
float64 enter = 0.;
|
||||
};
|
||||
|
||||
struct GroupCallBar::Userpic {
|
||||
User data;
|
||||
std::pair<uint64, uint64> cacheKey;
|
||||
crl::time speakingStarted = 0;
|
||||
QImage cache;
|
||||
Animations::Simple leftAnimation;
|
||||
Animations::Simple shownAnimation;
|
||||
std::unique_ptr<BlobsAnimation> blobsAnimation;
|
||||
int left = 0;
|
||||
bool positionInited = false;
|
||||
bool topMost = false;
|
||||
bool hiding = false;
|
||||
bool cacheMasked = false;
|
||||
};
|
||||
|
||||
GroupCallBar::GroupCallBar(
|
||||
not_null<QWidget*> parent,
|
||||
@@ -98,16 +31,13 @@ GroupCallBar::GroupCallBar(
|
||||
tr::lng_group_call_join(),
|
||||
st::groupCallTopBarJoin))
|
||||
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget()))
|
||||
, _randomSpeakingTimer([=] { sendRandomLevels(); }) {
|
||||
, _userpics(std::make_unique<GroupCallUserpics>(
|
||||
st::historyGroupCallUserpics,
|
||||
std::move(hideBlobs),
|
||||
[=] { updateUserpics(); })) {
|
||||
_wrap.hide(anim::type::instant);
|
||||
_shadow->hide();
|
||||
|
||||
const auto limit = kMaxUserpics;
|
||||
const auto single = st::historyGroupCallUserpicSize;
|
||||
const auto shift = st::historyGroupCallUserpicShift;
|
||||
// + 1 * single for the blobs.
|
||||
_maxUserpicsWidth = 2 * single + (limit - 1) * (single - shift);
|
||||
|
||||
_wrap.entity()->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
|
||||
@@ -122,7 +52,7 @@ GroupCallBar::GroupCallBar(
|
||||
copy
|
||||
) | rpl::start_with_next([=](GroupCallBarContent &&content) {
|
||||
_content = content;
|
||||
updateUserpicsFromContent();
|
||||
_userpics->update(_content.users, !_wrap.isHidden());
|
||||
_inner->update();
|
||||
}, lifetime());
|
||||
|
||||
@@ -140,53 +70,10 @@ GroupCallBar::GroupCallBar(
|
||||
_wrap.toggle(false, anim::type::normal);
|
||||
}, lifetime());
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (auto &userpic : _userpics) {
|
||||
userpic.cache = QImage();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_speakingAnimation.init([=](crl::time now) {
|
||||
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
|
||||
&& (now - last >= kBlobsEnterDuration)) {
|
||||
_speakingAnimation.stop();
|
||||
}
|
||||
for (auto &userpic : _userpics) {
|
||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||
blobs->blobs.updateLevel(now - blobs->lastTime);
|
||||
blobs->lastTime = now;
|
||||
}
|
||||
}
|
||||
updateUserpics();
|
||||
});
|
||||
|
||||
rpl::combine(
|
||||
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
|
||||
std::move(hideBlobs)
|
||||
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
|
||||
const auto hide = animDisabled || deactivated;
|
||||
|
||||
if (!(hide && _speakingAnimationHideLastTime)) {
|
||||
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
|
||||
}
|
||||
_skipLevelUpdate = hide;
|
||||
for (auto &userpic : _userpics) {
|
||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||
blobs->blobs.setLevel(0.);
|
||||
}
|
||||
}
|
||||
if (!hide && !_speakingAnimation.animating()) {
|
||||
_speakingAnimation.start();
|
||||
}
|
||||
_skipLevelUpdate = hide;
|
||||
}, lifetime());
|
||||
|
||||
setupInner();
|
||||
}
|
||||
|
||||
GroupCallBar::~GroupCallBar() {
|
||||
}
|
||||
GroupCallBar::~GroupCallBar() = default;
|
||||
|
||||
void GroupCallBar::setupInner() {
|
||||
_inner->resize(0, st::historyReplyHeight);
|
||||
@@ -252,142 +139,11 @@ void GroupCallBar::paint(Painter &p) {
|
||||
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
|
||||
: tr::lng_group_call_no_members(tr::now)));
|
||||
|
||||
const auto size = st::historyGroupCallUserpics.size;
|
||||
// Skip shadow of the bar above.
|
||||
paintUserpics(p);
|
||||
}
|
||||
|
||||
void GroupCallBar::paintUserpics(Painter &p) {
|
||||
const auto top = (st::historyReplyHeight
|
||||
- st::lineWidth
|
||||
- st::historyGroupCallUserpicSize) / 2 + st::lineWidth;
|
||||
const auto middle = _inner->width() / 2;
|
||||
const auto size = st::historyGroupCallUserpicSize;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto &minScale = kUserpicMinScale;
|
||||
for (auto &userpic : ranges::view::reverse(_userpics)) {
|
||||
const auto shown = userpic.shownAnimation.value(
|
||||
userpic.hiding ? 0. : 1.);
|
||||
if (shown == 0.) {
|
||||
continue;
|
||||
}
|
||||
validateUserpicCache(userpic);
|
||||
p.setOpacity(shown);
|
||||
const auto left = middle + userpic.leftAnimation.value(userpic.left);
|
||||
const auto blobs = userpic.blobsAnimation.get();
|
||||
const auto shownScale = 0.5 + shown / 2.;
|
||||
const auto scale = shownScale * (!blobs
|
||||
? 1.
|
||||
: (minScale
|
||||
+ (1. - minScale) * (_speakingAnimationHideLastTime
|
||||
? (1. - blobs->blobs.currentLevel())
|
||||
: blobs->blobs.currentLevel())));
|
||||
if (blobs) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
const auto shift = QPointF(left + size / 2., top + size / 2.);
|
||||
p.translate(shift);
|
||||
blobs->blobs.paint(p, st::windowActiveTextFg);
|
||||
p.translate(-shift);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
if (std::abs(scale - 1.) < 0.001) {
|
||||
const auto skip = ((kWideScale - 1) / 2) * size * factor;
|
||||
p.drawImage(
|
||||
QRect(left, top, size, size),
|
||||
userpic.cache,
|
||||
QRect(skip, skip, size * factor, size * factor));
|
||||
} else {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
auto target = QRect(
|
||||
left + (1 - kWideScale) / 2 * size,
|
||||
top + (1 - kWideScale) / 2 * size,
|
||||
kWideScale * size,
|
||||
kWideScale * size);
|
||||
auto shrink = anim::interpolate(
|
||||
(1 - kWideScale) / 2 * size,
|
||||
0,
|
||||
scale);
|
||||
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
||||
p.drawImage(target.marginsAdded(margins), userpic.cache);
|
||||
}
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
|
||||
const auto hidden = [](const Userpic &userpic) {
|
||||
return userpic.hiding && !userpic.shownAnimation.animating();
|
||||
};
|
||||
_userpics.erase(ranges::remove_if(_userpics, hidden), end(_userpics));
|
||||
}
|
||||
|
||||
bool GroupCallBar::needUserpicCacheRefresh(Userpic &userpic) {
|
||||
if (userpic.cache.isNull()) {
|
||||
return true;
|
||||
} else if (userpic.hiding) {
|
||||
return false;
|
||||
} else if (userpic.cacheKey != userpic.data.userpicKey) {
|
||||
return true;
|
||||
}
|
||||
const auto shouldBeMasked = !userpic.topMost;
|
||||
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
|
||||
return true;
|
||||
}
|
||||
return !userpic.leftAnimation.animating();
|
||||
}
|
||||
|
||||
void GroupCallBar::ensureBlobsAnimation(Userpic &userpic) {
|
||||
if (userpic.blobsAnimation) {
|
||||
return;
|
||||
}
|
||||
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
|
||||
Blobs() | ranges::to_vector,
|
||||
kLevelDuration,
|
||||
kMaxLevel);
|
||||
userpic.blobsAnimation->lastTime = crl::now();
|
||||
}
|
||||
|
||||
void GroupCallBar::sendRandomLevels() {
|
||||
if (_skipLevelUpdate) {
|
||||
return;
|
||||
}
|
||||
for (auto &userpic : _userpics) {
|
||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
|
||||
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallBar::validateUserpicCache(Userpic &userpic) {
|
||||
if (!needUserpicCacheRefresh(userpic)) {
|
||||
return;
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::historyGroupCallUserpicSize;
|
||||
const auto shift = st::historyGroupCallUserpicShift;
|
||||
const auto full = QSize(size, size) * kWideScale * factor;
|
||||
if (userpic.cache.isNull()) {
|
||||
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||
userpic.cache.setDevicePixelRatio(factor);
|
||||
}
|
||||
userpic.cacheKey = userpic.data.userpicKey;
|
||||
userpic.cacheMasked = !userpic.topMost;
|
||||
userpic.cache.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&userpic.cache);
|
||||
const auto skip = (kWideScale - 1) / 2 * size;
|
||||
p.drawImage(skip, skip, userpic.data.userpic);
|
||||
|
||||
if (userpic.cacheMasked) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto pen = QPen(Qt::transparent);
|
||||
pen.setWidth(st::historyGroupCallUserpicStroke);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setBrush(Qt::transparent);
|
||||
p.setPen(pen);
|
||||
p.drawEllipse(skip - size + shift, skip, size, size);
|
||||
}
|
||||
}
|
||||
const auto top = (st::historyReplyHeight - st::lineWidth - size) / 2
|
||||
+ st::lineWidth;
|
||||
_userpics->paint(p, _inner->width() / 2, top, size);
|
||||
}
|
||||
|
||||
void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) {
|
||||
@@ -413,127 +169,14 @@ void GroupCallBar::updateShadowGeometry(QRect wrapGeometry) {
|
||||
: regular);
|
||||
}
|
||||
|
||||
void GroupCallBar::updateUserpicsFromContent() {
|
||||
const auto idFromUserpic = [](const Userpic &userpic) {
|
||||
return userpic.data.id;
|
||||
};
|
||||
|
||||
// Use "topMost" as "willBeHidden" flag.
|
||||
for (auto &userpic : _userpics) {
|
||||
userpic.topMost = true;
|
||||
}
|
||||
for (const auto &user : _content.users) {
|
||||
const auto i = ranges::find(_userpics, user.id, idFromUserpic);
|
||||
if (i == end(_userpics)) {
|
||||
_userpics.push_back(Userpic{ user });
|
||||
toggleUserpic(_userpics.back(), true);
|
||||
continue;
|
||||
}
|
||||
i->topMost = false;
|
||||
|
||||
if (i->hiding) {
|
||||
toggleUserpic(*i, true);
|
||||
}
|
||||
i->data = user;
|
||||
|
||||
// Put this one after the last we are not hiding.
|
||||
for (auto j = end(_userpics) - 1; j != i; --j) {
|
||||
if (!j->topMost) {
|
||||
ranges::rotate(i, i + 1, j + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
|
||||
// Set correct real values of "topMost" flag.
|
||||
const auto userpicsBegin = begin(_userpics);
|
||||
const auto userpicsEnd = end(_userpics);
|
||||
auto markedTopMost = userpicsEnd;
|
||||
auto hasBlobs = false;
|
||||
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
|
||||
auto &userpic = *i;
|
||||
if (userpic.data.speaking) {
|
||||
ensureBlobsAnimation(userpic);
|
||||
hasBlobs = true;
|
||||
} else {
|
||||
userpic.blobsAnimation = nullptr;
|
||||
}
|
||||
if (userpic.topMost) {
|
||||
toggleUserpic(userpic, false);
|
||||
userpic.topMost = false;
|
||||
} else if (markedTopMost == userpicsEnd) {
|
||||
userpic.topMost = true;
|
||||
markedTopMost = i;
|
||||
}
|
||||
}
|
||||
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
|
||||
// Bring the topMost userpic to the very beginning, above all hiding.
|
||||
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
|
||||
}
|
||||
updateUserpicsPositions();
|
||||
|
||||
if (!hasBlobs) {
|
||||
_randomSpeakingTimer.cancel();
|
||||
_speakingAnimation.stop();
|
||||
} else if (!_randomSpeakingTimer.isActive()) {
|
||||
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
|
||||
_speakingAnimation.start();
|
||||
}
|
||||
|
||||
if (_wrap.isHidden()) {
|
||||
for (auto &userpic : _userpics) {
|
||||
userpic.shownAnimation.stop();
|
||||
userpic.leftAnimation.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallBar::toggleUserpic(Userpic &userpic, bool shown) {
|
||||
userpic.hiding = !shown;
|
||||
userpic.shownAnimation.start(
|
||||
[=] { updateUserpics(); },
|
||||
shown ? 0. : 1.,
|
||||
shown ? 1. : 0.,
|
||||
kDuration);
|
||||
}
|
||||
|
||||
void GroupCallBar::updateUserpicsPositions() {
|
||||
const auto shownCount = ranges::count(_userpics, false, &Userpic::hiding);
|
||||
if (!shownCount) {
|
||||
return;
|
||||
}
|
||||
const auto single = st::historyGroupCallUserpicSize;
|
||||
const auto shift = st::historyGroupCallUserpicShift;
|
||||
// + 1 * single for the blobs.
|
||||
const auto fullWidth = single + (shownCount - 1) * (single - shift);
|
||||
auto left = (-fullWidth / 2);
|
||||
for (auto &userpic : _userpics) {
|
||||
if (userpic.hiding) {
|
||||
continue;
|
||||
}
|
||||
if (!userpic.positionInited) {
|
||||
userpic.positionInited = true;
|
||||
userpic.left = left;
|
||||
} else if (userpic.left != left) {
|
||||
userpic.leftAnimation.start(
|
||||
[=] { updateUserpics(); },
|
||||
userpic.left,
|
||||
left,
|
||||
kDuration);
|
||||
userpic.left = left;
|
||||
}
|
||||
left += (single - shift);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallBar::updateUserpics() {
|
||||
const auto widget = _wrap.entity();
|
||||
const auto middle = widget->width() / 2;
|
||||
_wrap.entity()->update(
|
||||
(middle - _maxUserpicsWidth / 2),
|
||||
const auto width = _userpics->maxWidth();
|
||||
widget->update(
|
||||
(middle - width / 2),
|
||||
0,
|
||||
_maxUserpicsWidth,
|
||||
width,
|
||||
widget->height());
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
@@ -18,17 +17,13 @@ namespace Ui {
|
||||
|
||||
class PlainShadow;
|
||||
class RoundButton;
|
||||
struct GroupCallUser;
|
||||
class GroupCallUserpics;
|
||||
|
||||
struct GroupCallBarContent {
|
||||
struct User {
|
||||
QImage userpic;
|
||||
std::pair<uint64, uint64> userpicKey = {};
|
||||
int32 id = 0;
|
||||
bool speaking = false;
|
||||
};
|
||||
int count = 0;
|
||||
bool shown = false;
|
||||
std::vector<User> users;
|
||||
std::vector<GroupCallUser> users;
|
||||
};
|
||||
|
||||
class GroupCallBar final {
|
||||
@@ -58,24 +53,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
using User = GroupCallBarContent::User;
|
||||
struct BlobsAnimation;
|
||||
struct Userpic;
|
||||
using User = GroupCallUser;
|
||||
|
||||
void updateShadowGeometry(QRect wrapGeometry);
|
||||
void updateControlsGeometry(QRect wrapGeometry);
|
||||
void updateUserpicsFromContent();
|
||||
void updateUserpics();
|
||||
void setupInner();
|
||||
void paint(Painter &p);
|
||||
void paintUserpics(Painter &p);
|
||||
|
||||
void toggleUserpic(Userpic &userpic, bool shown);
|
||||
void updateUserpics();
|
||||
void updateUserpicsPositions();
|
||||
void validateUserpicCache(Userpic &userpic);
|
||||
[[nodiscard]] bool needUserpicCacheRefresh(Userpic &userpic);
|
||||
void ensureBlobsAnimation(Userpic &userpic);
|
||||
void sendRandomLevels();
|
||||
|
||||
SlideWrap<> _wrap;
|
||||
not_null<RpWidget*> _inner;
|
||||
@@ -83,17 +67,11 @@ private:
|
||||
std::unique_ptr<PlainShadow> _shadow;
|
||||
rpl::event_stream<> _barClicks;
|
||||
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
||||
std::vector<Userpic> _userpics;
|
||||
base::Timer _randomSpeakingTimer;
|
||||
Ui::Animations::Basic _speakingAnimation;
|
||||
int _maxUserpicsWidth = 0;
|
||||
bool _shouldBeShown = false;
|
||||
bool _forceHidden = false;
|
||||
|
||||
bool _skipLevelUpdate = false;
|
||||
crl::time _speakingAnimationHideLastTime = 0;
|
||||
|
||||
GroupCallBarContent _content;
|
||||
std::unique_ptr<GroupCallUserpics> _userpics;
|
||||
|
||||
};
|
||||
|
||||
|
||||
416
Telegram/SourceFiles/ui/chat/group_call_userpics.cpp
Normal file
416
Telegram/SourceFiles/ui/chat/group_call_userpics.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
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 "ui/chat/group_call_userpics.h"
|
||||
|
||||
#include "ui/paint/blobs.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDuration = 160;
|
||||
constexpr auto kMaxUserpics = 4;
|
||||
constexpr auto kWideScale = 5;
|
||||
|
||||
constexpr auto kBlobsEnterDuration = crl::time(250);
|
||||
constexpr auto kLevelDuration = 100. + 500. * 0.23;
|
||||
constexpr auto kBlobScale = 0.605;
|
||||
constexpr auto kMinorBlobFactor = 0.9f;
|
||||
constexpr auto kUserpicMinScale = 0.8;
|
||||
constexpr auto kMaxLevel = 1.;
|
||||
constexpr auto kSendRandomLevelInterval = crl::time(100);
|
||||
|
||||
auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||
return { {
|
||||
{
|
||||
.segmentsCount = 6,
|
||||
.minScale = kBlobScale * kMinorBlobFactor,
|
||||
.minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
|
||||
.maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
|
||||
.speedScale = 1.,
|
||||
.alpha = .5,
|
||||
},
|
||||
{
|
||||
.segmentsCount = 8,
|
||||
.minScale = kBlobScale,
|
||||
.minRadius = (float)st::historyGroupCallBlobMinRadius,
|
||||
.maxRadius = (float)st::historyGroupCallBlobMaxRadius,
|
||||
.speedScale = 1.,
|
||||
.alpha = .2,
|
||||
},
|
||||
} };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct GroupCallUserpics::BlobsAnimation {
|
||||
BlobsAnimation(
|
||||
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
||||
float levelDuration,
|
||||
float maxLevel)
|
||||
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
|
||||
}
|
||||
|
||||
Ui::Paint::Blobs blobs;
|
||||
crl::time lastTime = 0;
|
||||
crl::time lastSpeakingUpdateTime = 0;
|
||||
float64 enter = 0.;
|
||||
};
|
||||
|
||||
struct GroupCallUserpics::Userpic {
|
||||
User data;
|
||||
std::pair<uint64, uint64> cacheKey;
|
||||
crl::time speakingStarted = 0;
|
||||
QImage cache;
|
||||
Animations::Simple leftAnimation;
|
||||
Animations::Simple shownAnimation;
|
||||
std::unique_ptr<BlobsAnimation> blobsAnimation;
|
||||
int left = 0;
|
||||
bool positionInited = false;
|
||||
bool topMost = false;
|
||||
bool hiding = false;
|
||||
bool cacheMasked = false;
|
||||
};
|
||||
|
||||
GroupCallUserpics::GroupCallUserpics(
|
||||
const style::GroupCallUserpics &st,
|
||||
rpl::producer<bool> &&hideBlobs,
|
||||
Fn<void()> repaint)
|
||||
: _st(st)
|
||||
, _randomSpeakingTimer([=] { sendRandomLevels(); })
|
||||
, _repaint(std::move(repaint)) {
|
||||
const auto limit = kMaxUserpics;
|
||||
const auto single = _st.size;
|
||||
const auto shift = _st.shift;
|
||||
// + 1 * single for the blobs.
|
||||
_maxWidth = 2 * single + (limit - 1) * (single - shift);
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (auto &userpic : _list) {
|
||||
userpic.cache = QImage();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_speakingAnimation.init([=](crl::time now) {
|
||||
if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
|
||||
&& (now - last >= kBlobsEnterDuration)) {
|
||||
_speakingAnimation.stop();
|
||||
}
|
||||
for (auto &userpic : _list) {
|
||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||
blobs->blobs.updateLevel(now - blobs->lastTime);
|
||||
blobs->lastTime = now;
|
||||
}
|
||||
}
|
||||
if (const auto onstack = _repaint) {
|
||||
onstack();
|
||||
}
|
||||
});
|
||||
|
||||
rpl::combine(
|
||||
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
|
||||
std::move(hideBlobs)
|
||||
) | rpl::start_with_next([=](bool animDisabled, bool deactivated) {
|
||||
const auto hide = animDisabled || deactivated;
|
||||
|
||||
if (!(hide && _speakingAnimationHideLastTime)) {
|
||||
_speakingAnimationHideLastTime = hide ? crl::now() : 0;
|
||||
}
|
||||
_skipLevelUpdate = hide;
|
||||
for (auto &userpic : _list) {
|
||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||
blobs->blobs.setLevel(0.);
|
||||
}
|
||||
}
|
||||
if (!hide && !_speakingAnimation.animating()) {
|
||||
_speakingAnimation.start();
|
||||
}
|
||||
_skipLevelUpdate = hide;
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
GroupCallUserpics::~GroupCallUserpics() = default;
|
||||
|
||||
void GroupCallUserpics::paint(Painter &p, int x, int y, int size) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto &minScale = kUserpicMinScale;
|
||||
for (auto &userpic : ranges::view::reverse(_list)) {
|
||||
const auto shown = userpic.shownAnimation.value(
|
||||
userpic.hiding ? 0. : 1.);
|
||||
if (shown == 0.) {
|
||||
continue;
|
||||
}
|
||||
validateCache(userpic);
|
||||
p.setOpacity(shown);
|
||||
const auto left = x + userpic.leftAnimation.value(userpic.left);
|
||||
const auto blobs = userpic.blobsAnimation.get();
|
||||
const auto shownScale = 0.5 + shown / 2.;
|
||||
const auto scale = shownScale * (!blobs
|
||||
? 1.
|
||||
: (minScale
|
||||
+ (1. - minScale) * (_speakingAnimationHideLastTime
|
||||
? (1. - blobs->blobs.currentLevel())
|
||||
: blobs->blobs.currentLevel())));
|
||||
if (blobs) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
const auto shift = QPointF(left + size / 2., y + size / 2.);
|
||||
p.translate(shift);
|
||||
blobs->blobs.paint(p, st::windowActiveTextFg);
|
||||
p.translate(-shift);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
if (std::abs(scale - 1.) < 0.001) {
|
||||
const auto skip = ((kWideScale - 1) / 2) * size * factor;
|
||||
p.drawImage(
|
||||
QRect(left, y, size, size),
|
||||
userpic.cache,
|
||||
QRect(skip, skip, size * factor, size * factor));
|
||||
} else {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
auto target = QRect(
|
||||
left + (1 - kWideScale) / 2 * size,
|
||||
y + (1 - kWideScale) / 2 * size,
|
||||
kWideScale * size,
|
||||
kWideScale * size);
|
||||
auto shrink = anim::interpolate(
|
||||
(1 - kWideScale) / 2 * size,
|
||||
0,
|
||||
scale);
|
||||
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
||||
p.drawImage(target.marginsAdded(margins), userpic.cache);
|
||||
}
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
|
||||
const auto hidden = [](const Userpic &userpic) {
|
||||
return userpic.hiding && !userpic.shownAnimation.animating();
|
||||
};
|
||||
_list.erase(ranges::remove_if(_list, hidden), end(_list));
|
||||
}
|
||||
|
||||
int GroupCallUserpics::maxWidth() const {
|
||||
return _maxWidth;
|
||||
}
|
||||
|
||||
rpl::producer<int> GroupCallUserpics::widthValue() const {
|
||||
return _width.value();
|
||||
}
|
||||
|
||||
bool GroupCallUserpics::needCacheRefresh(Userpic &userpic) {
|
||||
if (userpic.cache.isNull()) {
|
||||
return true;
|
||||
} else if (userpic.hiding) {
|
||||
return false;
|
||||
} else if (userpic.cacheKey != userpic.data.userpicKey) {
|
||||
return true;
|
||||
}
|
||||
const auto shouldBeMasked = !userpic.topMost;
|
||||
if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
|
||||
return true;
|
||||
}
|
||||
return !userpic.leftAnimation.animating();
|
||||
}
|
||||
|
||||
void GroupCallUserpics::ensureBlobsAnimation(Userpic &userpic) {
|
||||
if (userpic.blobsAnimation) {
|
||||
return;
|
||||
}
|
||||
userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
|
||||
Blobs() | ranges::to_vector,
|
||||
kLevelDuration,
|
||||
kMaxLevel);
|
||||
userpic.blobsAnimation->lastTime = crl::now();
|
||||
}
|
||||
|
||||
void GroupCallUserpics::sendRandomLevels() {
|
||||
if (_skipLevelUpdate) {
|
||||
return;
|
||||
}
|
||||
for (auto &userpic : _list) {
|
||||
if (const auto blobs = userpic.blobsAnimation.get()) {
|
||||
const auto value = 30 + (openssl::RandomValue<uint32>() % 70);
|
||||
userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallUserpics::validateCache(Userpic &userpic) {
|
||||
if (!needCacheRefresh(userpic)) {
|
||||
return;
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = _st.size;
|
||||
const auto shift = _st.shift;
|
||||
const auto full = QSize(size, size) * kWideScale * factor;
|
||||
if (userpic.cache.isNull()) {
|
||||
userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||
userpic.cache.setDevicePixelRatio(factor);
|
||||
}
|
||||
userpic.cacheKey = userpic.data.userpicKey;
|
||||
userpic.cacheMasked = !userpic.topMost;
|
||||
userpic.cache.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&userpic.cache);
|
||||
const auto skip = (kWideScale - 1) / 2 * size;
|
||||
p.drawImage(skip, skip, userpic.data.userpic);
|
||||
|
||||
if (userpic.cacheMasked) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto pen = QPen(Qt::transparent);
|
||||
pen.setWidth(_st.stroke);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.setBrush(Qt::transparent);
|
||||
p.setPen(pen);
|
||||
p.drawEllipse(skip - size + shift, skip, size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallUserpics::update(
|
||||
const std::vector<GroupCallUser> &users,
|
||||
bool visible) {
|
||||
const auto idFromUserpic = [](const Userpic &userpic) {
|
||||
return userpic.data.id;
|
||||
};
|
||||
|
||||
// Use "topMost" as "willBeHidden" flag.
|
||||
for (auto &userpic : _list) {
|
||||
userpic.topMost = true;
|
||||
}
|
||||
for (const auto &user : users) {
|
||||
const auto i = ranges::find(_list, user.id, idFromUserpic);
|
||||
if (i == end(_list)) {
|
||||
_list.push_back(Userpic{ user });
|
||||
toggle(_list.back(), true);
|
||||
continue;
|
||||
}
|
||||
i->topMost = false;
|
||||
|
||||
if (i->hiding) {
|
||||
toggle(*i, true);
|
||||
}
|
||||
i->data = user;
|
||||
|
||||
// Put this one after the last we are not hiding.
|
||||
for (auto j = end(_list) - 1; j != i; --j) {
|
||||
if (!j->topMost) {
|
||||
ranges::rotate(i, i + 1, j + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the ones that "willBeHidden" (currently having "topMost" flag).
|
||||
// Set correct real values of "topMost" flag.
|
||||
const auto userpicsBegin = begin(_list);
|
||||
const auto userpicsEnd = end(_list);
|
||||
auto markedTopMost = userpicsEnd;
|
||||
auto hasBlobs = false;
|
||||
for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
|
||||
auto &userpic = *i;
|
||||
if (userpic.data.speaking) {
|
||||
ensureBlobsAnimation(userpic);
|
||||
hasBlobs = true;
|
||||
} else {
|
||||
userpic.blobsAnimation = nullptr;
|
||||
}
|
||||
if (userpic.topMost) {
|
||||
toggle(userpic, false);
|
||||
userpic.topMost = false;
|
||||
} else if (markedTopMost == userpicsEnd) {
|
||||
userpic.topMost = true;
|
||||
markedTopMost = i;
|
||||
}
|
||||
}
|
||||
if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
|
||||
// Bring the topMost userpic to the very beginning, above all hiding.
|
||||
std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
|
||||
}
|
||||
updatePositions();
|
||||
|
||||
if (!hasBlobs) {
|
||||
_randomSpeakingTimer.cancel();
|
||||
_speakingAnimation.stop();
|
||||
} else if (!_randomSpeakingTimer.isActive()) {
|
||||
_randomSpeakingTimer.callEach(kSendRandomLevelInterval);
|
||||
_speakingAnimation.start();
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
for (auto &userpic : _list) {
|
||||
userpic.shownAnimation.stop();
|
||||
userpic.leftAnimation.stop();
|
||||
}
|
||||
}
|
||||
recountAndRepaint();
|
||||
}
|
||||
|
||||
void GroupCallUserpics::toggle(Userpic &userpic, bool shown) {
|
||||
userpic.hiding = !shown;
|
||||
userpic.shownAnimation.start(
|
||||
[=] { recountAndRepaint(); },
|
||||
shown ? 0. : 1.,
|
||||
shown ? 1. : 0.,
|
||||
kDuration);
|
||||
}
|
||||
|
||||
void GroupCallUserpics::updatePositions() {
|
||||
const auto shownCount = ranges::count(_list, false, &Userpic::hiding);
|
||||
if (!shownCount) {
|
||||
return;
|
||||
}
|
||||
const auto single = _st.size;
|
||||
const auto shift = _st.shift;
|
||||
// + 1 * single for the blobs.
|
||||
const auto fullWidth = single + (shownCount - 1) * (single - shift);
|
||||
auto left = (_st.align & Qt::AlignLeft)
|
||||
? 0
|
||||
: (_st.align & Qt::AlignHCenter)
|
||||
? (-fullWidth / 2)
|
||||
: -fullWidth;
|
||||
for (auto &userpic : _list) {
|
||||
if (userpic.hiding) {
|
||||
continue;
|
||||
}
|
||||
if (!userpic.positionInited) {
|
||||
userpic.positionInited = true;
|
||||
userpic.left = left;
|
||||
} else if (userpic.left != left) {
|
||||
userpic.leftAnimation.start(
|
||||
_repaint,
|
||||
userpic.left,
|
||||
left,
|
||||
kDuration);
|
||||
userpic.left = left;
|
||||
}
|
||||
left += (single - shift);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCallUserpics::recountAndRepaint() {
|
||||
auto width = 0;
|
||||
auto maxShown = 0.;
|
||||
for (const auto &userpic : _list) {
|
||||
const auto shown = userpic.shownAnimation.value(
|
||||
userpic.hiding ? 0. : 1.);
|
||||
if (shown > maxShown) {
|
||||
maxShown = shown;
|
||||
}
|
||||
width += anim::interpolate(0, _st.size - _st.shift, shown);
|
||||
}
|
||||
_width = width + anim::interpolate(0, _st.shift, maxShown);
|
||||
if (_repaint) {
|
||||
_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
73
Telegram/SourceFiles/ui/chat/group_call_userpics.h
Normal file
73
Telegram/SourceFiles/ui/chat/group_call_userpics.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace style {
|
||||
struct GroupCallUserpics;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
struct GroupCallUser {
|
||||
QImage userpic;
|
||||
std::pair<uint64, uint64> userpicKey = {};
|
||||
int32 id = 0;
|
||||
bool speaking = false;
|
||||
};
|
||||
|
||||
class GroupCallUserpics final {
|
||||
public:
|
||||
GroupCallUserpics(
|
||||
const style::GroupCallUserpics &st,
|
||||
rpl::producer<bool> &&hideBlobs,
|
||||
Fn<void()> repaint);
|
||||
~GroupCallUserpics();
|
||||
|
||||
void update(
|
||||
const std::vector<GroupCallUser> &users,
|
||||
bool visible);
|
||||
void paint(Painter &p, int x, int y, int size);
|
||||
|
||||
[[nodiscard]] int maxWidth() const;
|
||||
[[nodiscard]] rpl::producer<int> widthValue() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
using User = GroupCallUser;
|
||||
struct BlobsAnimation;
|
||||
struct Userpic;
|
||||
|
||||
void toggle(Userpic &userpic, bool shown);
|
||||
void updatePositions();
|
||||
void validateCache(Userpic &userpic);
|
||||
[[nodiscard]] bool needCacheRefresh(Userpic &userpic);
|
||||
void ensureBlobsAnimation(Userpic &userpic);
|
||||
void sendRandomLevels();
|
||||
void recountAndRepaint();
|
||||
|
||||
const style::GroupCallUserpics &_st;
|
||||
std::vector<Userpic> _list;
|
||||
base::Timer _randomSpeakingTimer;
|
||||
Fn<void()> _repaint;
|
||||
Ui::Animations::Basic _speakingAnimation;
|
||||
int _maxWidth = 0;
|
||||
bool _skipLevelUpdate = false;
|
||||
crl::time _speakingAnimationHideLastTime = 0;
|
||||
|
||||
rpl::variable<int> _width;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
@@ -45,6 +45,11 @@ MessageBar::MessageBar(not_null<QWidget*> parent, const style::MessageBar &st)
|
||||
: _st(st)
|
||||
, _widget(parent) {
|
||||
setup();
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_topBarGradient = _bottomBarGradient = QPixmap();
|
||||
}, _widget.lifetime());
|
||||
}
|
||||
|
||||
void MessageBar::setup() {
|
||||
|
||||
2
Telegram/ThirdParty/tgcalls
vendored
2
Telegram/ThirdParty/tgcalls
vendored
Submodule Telegram/ThirdParty/tgcalls updated: 178983f723...6e8b3f7e5f
@@ -1,7 +1,7 @@
|
||||
AppVersion 2005001
|
||||
AppVersion 2005003
|
||||
AppVersionStrMajor 2.5
|
||||
AppVersionStrSmall 2.5.1
|
||||
AppVersionStr 2.5.1
|
||||
BetaChannel 0
|
||||
AppVersionStrSmall 2.5.3
|
||||
AppVersionStr 2.5.3
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 2.5.1
|
||||
AppVersionOriginal 2.5.3.beta
|
||||
|
||||
@@ -28,6 +28,8 @@ PRIVATE
|
||||
if (NOT DESKTOP_APP_DISABLE_WEBRTC_INTEGRATION)
|
||||
nice_target_sources(lib_tgcalls ${tgcalls_loc}
|
||||
PRIVATE
|
||||
AudioDeviceHelper.cpp
|
||||
AudioDeviceHelper.h
|
||||
CodecSelectHelper.cpp
|
||||
CodecSelectHelper.h
|
||||
CryptoHelper.cpp
|
||||
|
||||
@@ -80,6 +80,8 @@ PRIVATE
|
||||
ui/chat/attach/attach_single_media_preview.h
|
||||
ui/chat/group_call_bar.cpp
|
||||
ui/chat/group_call_bar.h
|
||||
ui/chat/group_call_userpics.cpp
|
||||
ui/chat/group_call_userpics.h
|
||||
ui/chat/message_bar.cpp
|
||||
ui/chat/message_bar.h
|
||||
ui/chat/pinned_bar.cpp
|
||||
|
||||
Submodule Telegram/lib_base updated: 81df0d0b78...dc89f34be5
Submodule Telegram/lib_ui updated: 1e2799245c...b5fb343d6c
Submodule Telegram/lib_webrtc updated: 4bc51d6f6d...d9e8307af6
@@ -1,3 +1,14 @@
|
||||
2.5.3 beta (30.12.20)
|
||||
|
||||
- Allow using mouse buttons in Push-to-Talk shortcut.
|
||||
- Fix blurred thumbnails in Shared Links section.
|
||||
|
||||
2.5.2 beta (25.12.20)
|
||||
|
||||
- Fix possible crash in video calls.
|
||||
- Fix possible crash in connecting to voice chats.
|
||||
- Use different audio module code on Windows in calls.
|
||||
|
||||
2.5.1 (23.12.20)
|
||||
|
||||
- Fix crash in voice calls.
|
||||
|
||||
2
cmake
2
cmake
Submodule cmake updated: a81345a28d...2208358765
@@ -20,6 +20,8 @@ apps:
|
||||
environment:
|
||||
# Tell glib to use portals on file associations handling.
|
||||
GTK_USE_PORTAL: 1
|
||||
# Use sandboxed ibus api
|
||||
IBUS_USE_PORTAL: 1
|
||||
plugs:
|
||||
- alsa
|
||||
- audio-playback
|
||||
@@ -75,7 +77,6 @@ parts:
|
||||
- liblzma-dev
|
||||
- libopus-dev
|
||||
- libpulse-dev
|
||||
- libqt5svg5-dev
|
||||
- libqt5waylandclient5-dev
|
||||
- libssl-dev
|
||||
- libxcb1-dev
|
||||
@@ -87,11 +88,11 @@ parts:
|
||||
- qt5-image-formats-plugins
|
||||
- qtwayland5
|
||||
- libasound2
|
||||
- libglib2.0-0
|
||||
- libgtk-3-0
|
||||
- liblzma5
|
||||
- libopus0
|
||||
- libpulse0
|
||||
- libqt5svg5
|
||||
- libqt5waylandclient5
|
||||
- libssl1.1
|
||||
- libxcb1
|
||||
@@ -174,16 +175,6 @@ parts:
|
||||
after:
|
||||
- mozjpeg
|
||||
|
||||
# Qt checks that ibus-daemon binary is present, otherwise doesn't work
|
||||
ibus:
|
||||
plugin: nil
|
||||
stage-packages:
|
||||
- ibus
|
||||
stage:
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2
|
||||
after:
|
||||
- mozjpeg
|
||||
|
||||
ffmpeg:
|
||||
plugin: nil
|
||||
build-packages:
|
||||
@@ -223,18 +214,14 @@ parts:
|
||||
openal:
|
||||
source: https://github.com/kcat/openal-soft.git
|
||||
source-depth: 1
|
||||
source-tag: openal-soft-1.20.1
|
||||
source-tag: openal-soft-1.21.0
|
||||
plugin: cmake
|
||||
build-packages:
|
||||
- libasound2-dev
|
||||
- libpulse-dev
|
||||
- libsndio-dev
|
||||
- portaudio19-dev
|
||||
stage-packages:
|
||||
- libasound2
|
||||
- libpulse0
|
||||
- libportaudio2
|
||||
- libsndio7.0
|
||||
cmake-parameters:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
|
||||
Reference in New Issue
Block a user