Compare commits

..

70 Commits

Author SHA1 Message Date
John Preston
1704cb345a Beta version 2.5.4: Fix build on Linux. 2021-01-07 23:17:54 +04:00
John Preston
0b85d0f185 Beta version 2.5.4: Fix build on macOS. 2021-01-07 22:02:22 +04:00
John Preston
348dfefbaa Beta version 2.5.4: Update lib_ui. 2021-01-07 20:07:06 +04:00
John Preston
7508980f62 Beta version 2.5.4.
- Implement new audio module code for calls and voice chats.
- Allow retracting votes from polls in comments to channel posts.
- Show small voice chat button for empty voice chats.
- Fix media viewer updating when screen resolution is changed.
2021-01-07 20:02:21 +04:00
John Preston
e11efe483e Add ability to choose calls audio backend. 2021-01-07 19:27:11 +04:00
John Preston
b23e4fa491 Use OpenAL recording backend for calls on Windows. 2021-01-05 21:15:19 +04:00
John Preston
b6b7f5706f Show small voice chat button for empty voice chats. 2021-01-05 21:15:19 +04:00
John Preston
613bf98283 Fix media viewer controls geometry updating. 2021-01-05 21:15:19 +04:00
23rd
0bdb38753b Added items for polls to context menu in sections.
Fixed #10055.
2021-01-05 21:15:19 +04:00
John Preston
d557e0f2b7 Don't set geometry to OverlayWidget (except macOS). 2021-01-05 21:14:59 +04:00
Ilya Fedin
e81f4e8545 Add updateControls to resizeEvent in media viewer 2021-01-05 18:45:41 +04:00
Ilya Fedin
3b7d5d3c80 Eliminate ifndefs in notifications_manager_linux 2021-01-05 18:16:26 +04:00
Ilya Fedin
0c37990ccd Fix tg_owt cache in windows & macos actions 2021-01-05 18:13:13 +04:00
Ilya Fedin
0fbea454bc Format unity counter setting 2021-01-05 11:43:24 +04:00
Ilya Fedin
d4d688d494 Merge two ifndef blocks in main_window_linux 2021-01-05 11:43:24 +04:00
Ilya Fedin
b3892f49fa Fix kSNIWatcherService/kSNIWatcherInterface misusage
Even though they're the same, there should be interface specified
2021-01-05 11:43:24 +04:00
Ilya Fedin
daa3a2f62f React to resizeEvent in media viewer 2021-01-04 17:33:37 +04:00
Ilya Fedin
5affb168a2 Fix callback function name in open with dialog 2021-01-04 17:08:49 +04:00
Ilya Fedin
99af2a7058 Check for xdg-decoration protocol support on Wayland 2021-01-04 17:08:49 +04:00
Ilya Fedin
b9acea9cef Move GSDMediaKeys initialization to SetWatchingMediaKeys 2021-01-04 11:55:10 +04:00
Ilya Fedin
8fb6ece796 Revert "Use xcb to set transient parent for gtk file dialog"
This reverts commit cd3b989e70.
2021-01-04 11:54:17 +04:00
Ilya Fedin
15a9842b9f Make open with dialog modal on Linux 2021-01-04 11:54:17 +04:00
GitHub Action
b28da30038 Update copyright year to 2021. 2021-01-04 11:24:32 +04:00
GitHub Action
8ce0bd5575 Update User-Agent for DNS to Chrome 87.0.4280.88. 2021-01-04 11:24:11 +04:00
Ilya Fedin
5d68d224e5 Use more --depth=1 in Dockerfile 2021-01-04 11:22:51 +04:00
John Preston
373635a765 Beta version 2.5.3.
- Allow using mouse buttons in Push-to-Talk shortcut.
- Fix blurred thumbnails in Shared Links section.
2020-12-30 17:53:37 +04:00
John Preston
0ecd4d3b40 Close PiP when opening a loading video.
Fixes #9926.
2020-12-30 17:12:40 +04:00
John Preston
3fd62d51aa Hide bot command start button when editing message.
Fixes #9941.
2020-12-30 16:27:32 +04:00
John Preston
818624e051 Don't allow kicking yourself from legacy group. 2020-12-30 16:14:13 +04:00
John Preston
a6eb241ec1 Fix blurred thumbnails in Shared Links section. 2020-12-30 16:13:07 +04:00
John Preston
1d7fb6c4ce Better count voice chat participants count. 2020-12-30 15:53:01 +04:00
John Preston
2af63ec48f Correctly show legacy groups with no admins. 2020-12-30 13:28:35 +04:00
John Preston
0709bc6d70 Fix links in voice chat admin log events. 2020-12-30 13:16:00 +04:00
John Preston
3a34881488 Highlight album part that had a reply clicked. 2020-12-30 12:56:44 +04:00
Ilya Fedin
39f9147790 Check for dbus menu exporter instead of menu path 2020-12-30 11:50:01 +04:00
Ilya Fedin
19a5dcbffc Make OpenAL debugging easier 2020-12-30 11:49:30 +04:00
John Preston
e864aa2ff2 Create audio device module uniformely. 2020-12-30 11:02:18 +04:00
23rd
fe85a8256a Added Github Action that updates copyright year. 2020-12-30 10:59:49 +04:00
23rd
f24b0c6237 Fixed hiding cancel button in state of listen to recorded voice data. 2020-12-30 10:59:49 +04:00
23rd
3940d57c3d Disabled message editing while voice recording. 2020-12-30 10:59:49 +04:00
Ilya Fedin
8da33113a2 Use DeviceModelPretty/SystemVersionPretty directly
This allows using methods that require a running QGuiApplication instance to detect system
2020-12-29 12:36:47 +04:00
Ilya Fedin
f66cfb5684 Use new IsSupportedByWM XCB API from lib_base 2020-12-29 12:29:11 +04:00
John Preston
d648d294ca Fix layout in intro Settings. 2020-12-28 18:29:09 +04:00
John Preston
0c8033414e Allow using mouse buttons in global shortcuts. 2020-12-28 17:06:08 +04:00
Stepan Skryabin
28f29b51c0 Update supported operating systems 2020-12-28 17:01:27 +04:00
Ilya Fedin
8142e83395 Fix connection to QSystemTrayIcon::messageClicked in main_window_win 2020-12-28 17:00:05 +04:00
Ilya Fedin
e247be7e33 Operate with QString instead of QDBusObjectPath 2020-12-28 17:00:05 +04:00
Ilya Fedin
e594b75f4c Use more forward declarations in main_window_linux 2020-12-28 17:00:05 +04:00
Ilya Fedin
28f857f763 Add support for G-S-D's media-keys extension
This fixes media keys handling on (but not limited to, probably):
* GNOME
* Cinnamon
* MATE
* Budgie
* Pantheon (elementaryOS)
* Unity
2020-12-28 17:00:05 +04:00
John Preston
486424af4f Beta version 2.5.2: Update cmake_helpers. 2020-12-25 18:57:21 +04:00
John Preston
f3614d6402 Beta version 2.5.2: Add in-app changelog. 2020-12-25 18:30:41 +04:00
John Preston
71151f6bf6 Beta version 2.5.2.
- Fix possible crash in video calls.
- Fix possible crash in connecting to voice chats.
- Use different audio module code on Windows in calls.
2020-12-25 16:45:18 +04:00
John Preston
2c0ef9c4e9 Improve connecting animation in voice chats. 2020-12-25 16:37:46 +04:00
John Preston
930e971881 Use more modern audio backend in calls on Windows. 2020-12-25 16:11:51 +04:00
John Preston
a576025d4f Always show invited at the end of voice chat. 2020-12-25 15:44:17 +04:00
John Preston
bcd2560e8f Reuse the code for userpics in Calls::TopBar. 2020-12-25 14:10:08 +04:00
John Preston
aede42b0b6 Update submodules. 2020-12-25 14:10:08 +04:00
Ilya Fedin
56728a066e Fix blurry tray icon with svg themes
QIcon::actualSize doesn't work as expected with svg themes, get actual pixmap and check its size instead.
2020-12-24 22:46:09 +03:00
John Preston
1951b7a8a1 Fix possible infinite recursion in video calls. 2020-12-24 14:38:46 +04:00
John Preston
0dc0f588c4 Don't offer sending .pdf-s as photos. 2020-12-24 13:52:38 +04:00
John Preston
7d22c631ca Fix voice chat members context menu. 2020-12-24 13:30:05 +04:00
John Preston
cf5cc3646a Fix multi-pin bar render after theme switch. 2020-12-24 07:59:34 +04:00
Ilya Fedin
375820d5cf glib was missed in stage-packages in snap 2020-12-24 07:48:00 +04:00
Ilya Fedin
3955543699 Remove QtSvg from snap in telegram part
Since it was needed only for lxqt-qtplugin
2020-12-24 07:48:00 +04:00
Ilya Fedin
c03da00e37 Fix getting version tag in snap action 2020-12-24 07:48:00 +04:00
Ilya Fedin
edcd462fb9 Use ibus portal in snap
It's supported since snapd 2.46 that was released in August
2020-12-24 07:48:00 +04:00
Ilya Fedin
e99558abeb Remove linux LastUserInputTime dependency since it's only in lib_base 2020-12-24 07:47:13 +04:00
Ilya Fedin
feff514a07 Update openal in snap to 1.21.0
And remove unneeded dependencies
2020-12-23 20:31:01 +04:00
John Preston
b1b25b0df9 Version 2.5.1.
- Fix crash in voice calls.
2020-12-23 15:01:31 +04:00
John Preston
dfee8238c6 Fix crash in legacy groups speaking typings handling. 2020-12-23 14:45:37 +04:00
108 changed files with 2019 additions and 1193 deletions

View 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"

View File

@@ -102,6 +102,8 @@ jobs:
cd Libraries/macos
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
- name: Patches.
run: |
echo "Find necessary commit from doc."
@@ -475,7 +477,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/tg_owt
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
- name: WebRTC.
if: steps.cache-webrtc.outputs.cache-hit != 'true'
run: |

View File

@@ -51,6 +51,7 @@ jobs:
- name: Clone.
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- name: First set up.

View File

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

View File

@@ -103,6 +103,7 @@ jobs:
- name: Generate cache key.
shell: bash
run: |
curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
echo $MANUAL_CACHING >> CACHE_KEY.txt
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/win.yml
@@ -359,7 +360,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/tg_owt
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
- name: WebRTC.
if: steps.cache-webrtc.outputs.cache-hit != 'true'
run: |

2
LEGAL
View File

@@ -1,7 +1,7 @@
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
Copyright (c) 2014-2020 The Telegram Desktop Authors.
Copyright (c) 2014-2021 The Telegram Desktop Authors.
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

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

View File

@@ -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
)
@@ -85,6 +84,13 @@ if (LINUX)
)
endif()
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_kwayland
)
endif()
if (DESKTOP_APP_USE_PACKAGED
AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
@@ -813,6 +819,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 +1110,19 @@ 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
platform/linux/notifications_manager_linux.cpp
)
nice_target_sources(Telegram ${src_loc}
PRIVATE
platform/linux/notifications_manager_linux_dummy.cpp
)
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)

View File

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

View File

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

View File

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

View File

@@ -928,6 +928,9 @@ void Updates::handleSendActionUpdate(
const auto isSpeakingInCall = (action.type()
== mtpc_speakingInGroupCallAction);
if (isSpeakingInCall) {
if (!peer->isChat() && !peer->isChannel()) {
return;
}
const auto call = peer->groupCall();
const auto now = crl::now();
if (call) {
@@ -943,8 +946,8 @@ void Updates::handleSendActionUpdate(
: (channel->flags() & MTPDchannel::Flag::f_call_active);
if (active) {
_pendingSpeakingCallMembers.emplace(
channel).first->second[userId] = now;
session().api().requestFullPeer(channel);
peer).first->second[userId] = now;
session().api().requestFullPeer(peer);
}
}
}

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_panel.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "facades.h"
@@ -368,10 +369,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()) {
@@ -779,6 +780,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
sendSignalingData(bytes);
});
},
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
};
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "base/global_shortcuts.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h"
#include <tgcalls/group/GroupInstanceImpl.h>
@@ -581,6 +582,8 @@ void GroupCall::createAndStartController() {
},
.initialInputDeviceId = _audioInputId.toStdString(),
.initialOutputDeviceId = _audioOutputId.toStdString(),
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
};
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");

View File

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

View File

@@ -480,6 +480,7 @@ void GroupCallSettingsBox(
// Means we finished showing the box.
crl::on_main(box, [=] {
state->micTester = std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId());
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
});

View File

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

View File

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

View File

@@ -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,30 @@ 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"
},
{
2005004,
"- Implement new audio module code for calls and voice chats.\n"
"- Allow retracting votes from polls in comments to channel posts.\n"
"- Show small voice chat button for empty voice chats.\n"
"- Fix media viewer updating when screen resolution is changed.\n"
},
};
};

View File

@@ -13,12 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_create_adm.h"
#include "facades.h"
namespace Core {
Settings::Settings()
: _sendSubmitWay(Ui::InputSubmitSettings::Enter)
: _callAudioBackend(Webrtc::Backend::OpenAL)
, _sendSubmitWay(Ui::InputSubmitSettings::Enter)
, _floatPlayerColumn(Window::Column::Second)
, _floatPlayerCorner(RectPart::TopRight)
, _dialogsWidthRatio(DefaultDialogsWidthRatio()) {
@@ -112,7 +114,8 @@ QByteArray Settings::serialize() const {
<< qint32(_ipRevealWarning ? 1 : 0)
<< qint32(_groupCallPushToTalk ? 1 : 0)
<< _groupCallPushToTalkShortcut
<< qint64(_groupCallPushToTalkDelay);
<< qint64(_groupCallPushToTalkDelay)
<< qint32(_callAudioBackend);
}
return result;
}
@@ -183,6 +186,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 groupCallPushToTalk = _groupCallPushToTalk ? 1 : 0;
QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut;
qint64 groupCallPushToTalkDelay = _groupCallPushToTalkDelay;
qint32 callAudioBackend = static_cast<qint32>(_callAudioBackend);
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -275,6 +279,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
>> groupCallPushToTalkShortcut
>> groupCallPushToTalkDelay;
}
if (!stream.atEnd()) {
stream >> callAudioBackend;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -369,6 +376,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_groupCallPushToTalk = (groupCallPushToTalk == 1);
_groupCallPushToTalkShortcut = groupCallPushToTalkShortcut;
_groupCallPushToTalkDelay = groupCallPushToTalkDelay;
auto uncheckedBackend = static_cast<Webrtc::Backend>(callAudioBackend);
switch (uncheckedBackend) {
case Webrtc::Backend::OpenAL:
case Webrtc::Backend::ADM:
case Webrtc::Backend::ADM2: _callAudioBackend = uncheckedBackend; break;
}
}
bool Settings::chatWide() const {

View File

@@ -21,6 +21,10 @@ namespace Window {
enum class Column;
} // namespace Window
namespace Webrtc {
enum class Backend;
} // namespace Webrtc
namespace Core {
class Settings final {
@@ -217,6 +221,12 @@ public:
void setCallAudioDuckingEnabled(bool value) {
_callAudioDuckingEnabled = value;
}
[[nodiscard]] Webrtc::Backend callAudioBackend() const {
return _callAudioBackend;
}
void setCallAudioBackend(Webrtc::Backend backend) {
_callAudioBackend = backend;
}
[[nodiscard]] bool groupCallPushToTalk() const {
return _groupCallPushToTalk;
}
@@ -531,13 +541,14 @@ private:
int _callOutputVolume = 100;
int _callInputVolume = 100;
bool _callAudioDuckingEnabled = true;
Webrtc::Backend _callAudioBackend = Webrtc::Backend();
bool _groupCallPushToTalk = false;
QByteArray _groupCallPushToTalkShortcut;
crl::time _groupCallPushToTalkDelay = 20;
Window::Theme::AccentColors _themesAccentColors;
bool _lastSeenWarningSeen = false;
Ui::SendFilesWay _sendFilesWay;
Ui::InputSubmitSettings _sendSubmitWay;
Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay();
Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings();
base::flat_map<QString, QString> _soundOverrides;
bool _exeLaunchWarning = true;
bool _ipRevealWarning = true;
@@ -553,8 +564,8 @@ private:
rpl::variable<bool> _autoDownloadDictionaries = true;
rpl::variable<bool> _mainMenuAccountsShown = true;
bool _tabbedSelectorSectionEnabled = false; // per-window
Window::Column _floatPlayerColumn; // per-window
RectPart _floatPlayerCorner; // per-window
Window::Column _floatPlayerColumn = Window::Column(); // per-window
RectPart _floatPlayerCorner = RectPart(); // per-window
bool _thirdSectionInfoEnabled = true; // per-window
rpl::event_stream<bool> _thirdSectionInfoEnabledValue; // per-window
int _thirdSectionExtendedBy = -1; // per-window

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2005000;
constexpr auto AppVersionStr = "2.5";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 2005004;
constexpr auto AppVersionStr = "2.5.4";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

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

View File

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

View File

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

View File

@@ -1751,18 +1751,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
if (const auto media = item->media()) {
if (const auto poll = media->poll()) {
if (!poll->closed()) {
if (poll->voted() && !poll->quiz()) {
_menu->addAction(tr::lng_polls_retract(tr::now), [=] {
session->api().sendPollVotes(itemId, {});
});
}
if (item->canStopPoll()) {
_menu->addAction(tr::lng_polls_stop(tr::now), [=] {
HistoryView::StopPoll(session, itemId);
});
}
}
HistoryView::AddPollActions(
_menu,
poll,
item,
HistoryView::Context::History);
} else if (const auto contact = media->sharedContact()) {
const auto phone = contact->phoneNumber;
_menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] {
@@ -2520,9 +2513,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 +3414,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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -868,6 +868,9 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
const auto document = linkDocument
? linkDocument->document().get()
: nullptr;
const auto poll = item
? (item->media() ? item->media()->poll() : nullptr)
: nullptr;
const auto hasSelection = !request.selectedItems.empty()
|| !request.selectedText.empty();
@@ -897,6 +900,8 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
// });
// AddToggleGroupingAction(result, linkPeer->peer());
// }
} else if (poll) {
AddPollActions(result, poll, item, list->elementContext());
} else if (!request.overSelection && view && !hasSelection) {
const auto owner = &view->data()->history()->owner();
const auto media = view->media();
@@ -979,4 +984,30 @@ void StopPoll(not_null<Main::Session*> session, FullMsgId itemId) {
stop));
}
void AddPollActions(
not_null<Ui::PopupMenu*> menu,
not_null<PollData*> poll,
not_null<HistoryItem*> item,
Context context) {
if ((context != Context::History)
&& (context != Context::Replies)
&& (context != Context::Pinned)) {
return;
}
if (poll->closed()) {
return;
}
const auto itemId = item->fullId();
if (poll->voted() && !poll->quiz()) {
menu->addAction(tr::lng_polls_retract(tr::now), [=] {
poll->session().api().sendPollVotes(itemId, {});
});
}
if (item->canStopPoll()) {
menu->addAction(tr::lng_polls_stop(tr::now), [=] {
StopPoll(&poll->session(), itemId);
});
}
}
} // namespace HistoryView

View File

@@ -49,5 +49,10 @@ void CopyPostLink(
FullMsgId itemId,
Context context);
void StopPoll(not_null<Main::Session*> session, FullMsgId itemId);
void AddPollActions(
not_null<Ui::PopupMenu*> menu,
not_null<PollData*> poll,
not_null<HistoryItem*> item,
Context context);
} // namespace

View File

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

View File

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

View File

@@ -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,18 +309,19 @@ 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) {
state->current.count = count;
state->current.shown = (count > 0);
consumer.put_next_copy(state->current);
}, lifetime);
@@ -345,12 +349,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();
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -525,12 +525,36 @@ void TopBarWidget::setActiveChat(
_activeChat = activeChat;
return;
}
const auto peerChanged = (_activeChat.key.history()
!= activeChat.key.history());
_activeChat = activeChat;
_sendAction = sendAction;
_titlePeerText.clear();
_back->clearState();
update();
if (peerChanged) {
_activeChatLifetime.destroy();
if (const auto history = _activeChat.key.history()) {
session().changes().peerFlagsValue(
history->peer,
Data::PeerUpdate::Flag::GroupCall
) | rpl::map([=] {
return history->peer->groupCall();
}) | rpl::distinct_until_changed(
) | rpl::map([](Data::GroupCall *call) {
return call ? call->fullCountValue() : rpl::single(-1);
}) | rpl::flatten_latest(
) | rpl::map([](int count) {
return (count == 0);
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=] {
updateControlsVisibility();
updateControlsGeometry();
}, _activeChatLifetime);
}
}
updateUnreadBadge();
refreshInfoButton();
if (_menu) {
@@ -722,7 +746,12 @@ void TopBarWidget::updateControlsVisibility() {
_call->setVisible(historyMode && callsEnabled);
const auto groupCallsEnabled = [&] {
if (const auto peer = _activeChat.key.peer()) {
return peer->canManageGroupCall();
if (peer->canManageGroupCall()) {
return true;
} else if (const auto call = peer->groupCall()) {
return (call->fullCount() == 0);
}
return false;
}
return false;
}();

View File

@@ -130,6 +130,7 @@ private:
const not_null<Window::SessionController*> _controller;
ActiveChat _activeChat;
QString _customTitleText;
rpl::lifetime _activeChatLifetime;
int _selectedCount = 0;
bool _canDelete = false;

View File

@@ -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());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -765,7 +765,9 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
: nullptr;
if (animation) {
animation->percent.update(progress, anim::linear);
animation->filling.update(progress, anim::linear);
animation->filling.update(
progress,
showVotes() ? anim::easeOutCirc : anim::linear);
animation->opacity.update(progress, anim::linear);
}
const auto height = paintAnswer(

View File

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

View File

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

View File

@@ -360,6 +360,7 @@ OverlayWidget::OverlayWidget()
setWindowFlags(Qt::FramelessWindowHint);
}
updateGeometry();
updateControlsGeometry();
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setMouseTracking(true);
@@ -446,17 +447,16 @@ void OverlayWidget::moveToScreen() {
: nullptr;
const auto activeWindowScreen = widgetScreen(window);
const auto myScreen = widgetScreen(this);
// Wayland doesn't support positioning, but Qt emits screenChanged anyway
// and geometry of the widget become broken
if (activeWindowScreen
&& myScreen != activeWindowScreen
&& !Platform::IsWayland()) {
if (activeWindowScreen && myScreen != activeWindowScreen) {
windowHandle()->setScreen(activeWindowScreen);
}
updateGeometry();
}
void OverlayWidget::updateGeometry() {
if (!Platform::IsMac()) {
return;
}
const auto screen = windowHandle() && windowHandle()->screen()
? windowHandle()->screen()
: QApplication::primaryScreen();
@@ -465,7 +465,13 @@ void OverlayWidget::updateGeometry() {
return;
}
setGeometry(available);
}
void OverlayWidget::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
void OverlayWidget::updateControlsGeometry() {
auto navSkip = 2 * st::mediaviewControlMargin + st::mediaviewControlSize;
_closeNav = myrtlrect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, st::mediaviewControlMargin, st::mediaviewControlSize, st::mediaviewControlSize);
_closeNavIcon = style::centerrect(_closeNav, st::mediaviewClose);
@@ -478,6 +484,7 @@ void OverlayWidget::updateGeometry() {
_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
resizeContentByScreenSize();
updateControls();
update();
}
@@ -2443,7 +2450,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;
}

View File

@@ -55,10 +55,14 @@ class Pip;
#define USE_OPENGL_OVERLAY_WIDGET
#endif // Q_OS_MAC && !OS_MAC_OLD
struct OverlayParentTraits : Ui::RpWidgetDefaultTraits {
static constexpr bool kSetZeroGeometry = false;
};
#ifdef USE_OPENGL_OVERLAY_WIDGET
using OverlayParent = Ui::RpWidgetWrap<QOpenGLWidget>;
using OverlayParent = Ui::RpWidgetWrap<QOpenGLWidget, OverlayParentTraits>;
#else // USE_OPENGL_OVERLAY_WIDGET
using OverlayParent = Ui::RpWidget;
using OverlayParent = Ui::RpWidgetWrap<QWidget, OverlayParentTraits>;
#endif // USE_OPENGL_OVERLAY_WIDGET
class OverlayWidget final
@@ -166,6 +170,7 @@ private:
};
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void wheelEvent(QWheelEvent *e) override;
@@ -270,6 +275,7 @@ private:
void updateDocSize();
void updateControls();
void updateActions();
void updateControlsGeometry();
void resizeCenteredControls();
void resizeContentByScreenSize();

View File

@@ -65,7 +65,7 @@ QByteArray DnsUserAgent() {
static const auto kResult = QByteArray(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/86.0.4240.198 Safari/537.36");
"Chrome/87.0.4280.88 Safari/537.36");
return kResult;
}

View File

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

View File

@@ -368,6 +368,7 @@ private:
int _pixh = 0;
Ui::Text::String _text = { st::msgMinWidth };
QPixmap _thumbnail;
bool _thumbnailBlurred = true;
struct LinkEntry {
LinkEntry() : width(0) {

View File

@@ -45,45 +45,95 @@ bool ShowOpenWithSupported() {
&& (Libs::gtk_app_chooser_dialog_new != nullptr)
&& (Libs::gtk_app_chooser_get_app_info != nullptr)
&& (Libs::gtk_app_chooser_get_type != nullptr)
&& (Libs::gtk_widget_get_type != nullptr)
&& (Libs::gtk_widget_get_window != nullptr)
&& (Libs::gtk_widget_realize != nullptr)
&& (Libs::gtk_widget_show != nullptr)
&& (Libs::gtk_widget_destroy != nullptr);
}
void HandleAppChooserResponse(
GtkDialog *dialog,
int responseId,
GFile *file) {
class OpenWithDialog : public QWindow {
public:
OpenWithDialog(const QString &filepath);
~OpenWithDialog();
bool exec();
private:
static void handleResponse(OpenWithDialog *dialog, int responseId);
GFile *_gfileInstance = nullptr;
GtkWidget *_gtkWidget = nullptr;
QEventLoop _loop;
std::optional<bool> _result = std::nullopt;
};
OpenWithDialog::OpenWithDialog(const QString &filepath)
: _gfileInstance(g_file_new_for_path(filepath.toUtf8()))
, _gtkWidget(Libs::gtk_app_chooser_dialog_new(
nullptr,
GTK_DIALOG_MODAL,
_gfileInstance)) {
g_signal_connect_swapped(
_gtkWidget,
"response",
G_CALLBACK(handleResponse),
this);
}
OpenWithDialog::~OpenWithDialog() {
Libs::gtk_widget_destroy(_gtkWidget);
g_object_unref(_gfileInstance);
}
bool OpenWithDialog::exec() {
Libs::gtk_widget_realize(_gtkWidget);
if (const auto activeWindow = Core::App().activeWindow()) {
Platform::internal::XSetTransientForHint(
Libs::gtk_widget_get_window(_gtkWidget),
activeWindow->widget().get()->windowHandle()->winId());
}
QGuiApplicationPrivate::showModalWindow(this);
Libs::gtk_widget_show(_gtkWidget);
if (!_result.has_value()) {
_loop.exec();
}
QGuiApplicationPrivate::hideModalWindow(this);
return *_result;
}
void OpenWithDialog::handleResponse(OpenWithDialog *dialog, int responseId) {
GAppInfo *chosenAppInfo = nullptr;
dialog->_result = true;
switch (responseId) {
case GTK_RESPONSE_OK:
chosenAppInfo = Libs::gtk_app_chooser_get_app_info(
Libs::gtk_app_chooser_cast(dialog));
Libs::gtk_app_chooser_cast(dialog->_gtkWidget));
if (chosenAppInfo) {
GList *uris = nullptr;
uris = g_list_prepend(uris, g_file_get_uri(file));
uris = g_list_prepend(uris, g_file_get_uri(dialog->_gfileInstance));
g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr);
g_list_free(uris);
g_object_unref(chosenAppInfo);
}
g_object_unref(file);
Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog));
break;
case GTK_RESPONSE_CANCEL:
case GTK_RESPONSE_DELETE_EVENT:
g_object_unref(file);
Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog));
break;
default:
dialog->_result = false;
break;
}
dialog->_loop.quit();
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
@@ -141,30 +191,7 @@ bool UnsafeShowOpenWith(const QString &filepath) {
}
const auto absolutePath = QFileInfo(filepath).absoluteFilePath();
auto gfileInstance = g_file_new_for_path(absolutePath.toUtf8());
auto appChooserDialog = Libs::gtk_app_chooser_dialog_new(
nullptr,
GTK_DIALOG_MODAL,
gfileInstance);
g_signal_connect(
appChooserDialog,
"response",
G_CALLBACK(HandleAppChooserResponse),
gfileInstance);
Libs::gtk_widget_realize(appChooserDialog);
if (const auto activeWindow = Core::App().activeWindow()) {
Platform::internal::XSetTransientForHint(
Libs::gtk_widget_get_window(appChooserDialog),
activeWindow->widget().get()->windowHandle()->winId());
}
Libs::gtk_widget_show(appChooserDialog);
return true;
return OpenWithDialog(absolutePath).exec();
#else // !TDESKTOP_DISABLE_GTK_INTEGRATION
return false;
#endif // TDESKTOP_DISABLE_GTK_INTEGRATION

View File

@@ -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() {

View File

@@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/linux/linux_gdk_helper.h"
#include "platform/linux/linux_libs.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_xcb_utilities_linux.h"
extern "C" {
#undef signals
@@ -33,6 +31,9 @@ GtkLoaded gdk_helper_loaded = GtkLoaded::GtkNone;
#define GdkDrawable GdkWindow
// Gtk 2
using f_gdk_x11_drawable_get_xdisplay = Display*(*)(GdkDrawable*);
f_gdk_x11_drawable_get_xdisplay gdk_x11_drawable_get_xdisplay = nullptr;
using f_gdk_x11_drawable_get_xid = XID(*)(GdkDrawable*);
f_gdk_x11_drawable_get_xid gdk_x11_drawable_get_xid = nullptr;
@@ -46,6 +47,12 @@ inline bool gdk_is_x11_window_check(Object *obj) {
return Libs::g_type_cit_helper(obj, gdk_x11_window_get_type());
}
using f_gdk_window_get_display = GdkDisplay*(*)(GdkWindow *window);
f_gdk_window_get_display gdk_window_get_display = nullptr;
using f_gdk_x11_display_get_xdisplay = Display*(*)(GdkDisplay *display);
f_gdk_x11_display_get_xdisplay gdk_x11_display_get_xdisplay = nullptr;
using f_gdk_x11_window_get_xid = Window(*)(GdkWindow *window);
f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr;
@@ -53,6 +60,7 @@ bool GdkHelperLoadGtk2(QLibrary &lib) {
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
return false;
#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false;
if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false;
return true;
#endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY
@@ -60,6 +68,8 @@ bool GdkHelperLoadGtk2(QLibrary &lib) {
bool GdkHelperLoadGtk3(QLibrary &lib) {
if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false;
if (!LOAD_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false;
if (!LOAD_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false;
if (!LOAD_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
return true;
}
@@ -79,28 +89,14 @@ bool GdkHelperLoaded() {
void XSetTransientForHint(GdkWindow *window, quintptr winId) {
if (gdk_helper_loaded == GtkLoaded::Gtk2) {
if (!IsWayland()) {
xcb_change_property(
base::Platform::XCB::GetConnectionFromQt(),
XCB_PROP_MODE_REPLACE,
gdk_x11_drawable_get_xid(window),
XCB_ATOM_WM_TRANSIENT_FOR,
XCB_ATOM_WINDOW,
32,
1,
&winId);
}
::XSetTransientForHint(gdk_x11_drawable_get_xdisplay(window),
gdk_x11_drawable_get_xid(window),
winId);
} else if (gdk_helper_loaded == GtkLoaded::Gtk3) {
if (!IsWayland() && gdk_is_x11_window_check(window)) {
xcb_change_property(
base::Platform::XCB::GetConnectionFromQt(),
XCB_PROP_MODE_REPLACE,
gdk_x11_window_get_xid(window),
XCB_ATOM_WM_TRANSIENT_FOR,
XCB_ATOM_WINDOW,
32,
1,
&winId);
if (gdk_is_x11_window_check(window)) {
::XSetTransientForHint(gdk_x11_display_get_xdisplay(gdk_window_get_display(window)),
gdk_x11_window_get_xid(window),
winId);
}
}
}

View 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

View 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

View File

@@ -74,7 +74,6 @@ bool setupGtkBase(QLibrary &lib_gtk) {
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_get_type", gtk_widget_get_type)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_contents", gtk_clipboard_wait_for_contents)) return false;
@@ -236,7 +235,6 @@ f_gtk_widget_get_window gtk_widget_get_window = nullptr;
f_gtk_widget_realize gtk_widget_realize = nullptr;
f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete = nullptr;
f_gtk_widget_destroy gtk_widget_destroy = nullptr;
f_gtk_widget_get_type gtk_widget_get_type = nullptr;
f_gtk_clipboard_get gtk_clipboard_get = nullptr;
f_gtk_clipboard_store gtk_clipboard_store = nullptr;
f_gtk_clipboard_wait_for_contents gtk_clipboard_wait_for_contents = nullptr;

View File

@@ -235,14 +235,6 @@ inline GtkWindow *gtk_window_cast(Object *obj) {
return g_type_cic_helper<GtkWindow, Object>(obj, gtk_window_get_type());
}
typedef GType (*f_gtk_widget_get_type)(void) G_GNUC_CONST;
extern f_gtk_widget_get_type gtk_widget_get_type;
template <typename Object>
inline GtkWidget *gtk_widget_cast(Object *obj) {
return g_type_cic_helper<GtkWidget, Object>(obj, gtk_widget_get_type());
}
typedef GType (*f_gtk_app_chooser_get_type)(void) G_GNUC_CONST;
extern f_gtk_app_chooser_get_type gtk_app_chooser_get_type;

View File

@@ -15,11 +15,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <private/qwaylandwindow_p.h>
#include <private/qwaylandshellsurface_p.h>
#include <connection_thread.h>
#include <registry.h>
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) && !defined DESKTOP_APP_QT_PATCHED
#include <wayland-client.h>
#endif // Qt < 5.13 && !DESKTOP_APP_QT_PATCHED
using QtWaylandClient::QWaylandWindow;
using namespace KWayland::Client;
namespace Platform {
namespace internal {
@@ -51,15 +55,75 @@ enum wl_shell_surface_resize WlResizeFromEdges(Qt::Edges edges) {
} // namespace
WaylandIntegration::WaylandIntegration() {
class WaylandIntegration::Private : public QObject {
public:
Private();
[[nodiscard]] Registry &registry() {
return _registry;
}
[[nodiscard]] QEventLoop &interfacesLoop() {
return _interfacesLoop;
}
[[nodiscard]] bool interfacesAnnounced() const {
return _interfacesAnnounced;
}
private:
ConnectionThread _connection;
Registry _registry;
QEventLoop _interfacesLoop;
bool _interfacesAnnounced = false;
};
WaylandIntegration::Private::Private() {
connect(&_connection, &ConnectionThread::connected, [=] {
LOG(("Successfully connected to Wayland server at socket: %1")
.arg(_connection.socketName()));
_registry.create(&_connection);
_registry.setup();
});
connect(
&_connection,
&ConnectionThread::connectionDied,
&_registry,
&Registry::destroy);
connect(&_registry, &Registry::interfacesAnnounced, [=] {
_interfacesAnnounced = true;
_interfacesLoop.quit();
});
_connection.initConnection();
}
WaylandIntegration::WaylandIntegration()
: _private(std::make_unique<Private>()) {
}
WaylandIntegration::~WaylandIntegration() = default;
WaylandIntegration *WaylandIntegration::Instance() {
if (!IsWayland()) return nullptr;
static WaylandIntegration instance;
return &instance;
}
void WaylandIntegration::waitForInterfaceAnnounce() {
if (!_private->interfacesAnnounced()) {
_private->interfacesLoop().exec();
}
}
bool WaylandIntegration::supportsXdgDecoration() {
return _private->registry().hasInterface(
Registry::Interface::XdgDecorationUnstableV1);
}
bool WaylandIntegration::startMove(QWindow *window) {
// There are startSystemMove on Qt 5.15
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) && !defined DESKTOP_APP_QT_PATCHED

View File

@@ -15,12 +15,18 @@ namespace internal {
class WaylandIntegration {
public:
static WaylandIntegration *Instance();
void waitForInterfaceAnnounce();
bool supportsXdgDecoration();
bool startMove(QWindow *window);
bool startResize(QWindow *window, Qt::Edges edges);
bool showWindowMenu(QWindow *window);
private:
WaylandIntegration();
~WaylandIntegration();
class Private;
const std::unique_ptr<Private> _private;
};
} // namespace internal

View File

@@ -12,15 +12,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform {
namespace internal {
class WaylandIntegration::Private {
};
WaylandIntegration::WaylandIntegration() {
}
WaylandIntegration::~WaylandIntegration() = default;
WaylandIntegration *WaylandIntegration::Instance() {
if (!IsWayland()) return nullptr;
static WaylandIntegration instance;
return &instance;
}
void WaylandIntegration::waitForInterfaceAnnounce() {
}
bool WaylandIntegration::supportsXdgDecoration() {
return false;
}
bool WaylandIntegration::startMove(QWindow *window) {
return false;
}

View File

@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_xcb_utilities_linux.h"
#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 +35,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 +43,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 +62,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 +74,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 +216,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 &currentImageBack = TrayIconImageBack[iconSize];
const auto desiredSize = QSize(iconSize, iconSize);
@@ -221,11 +232,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 +250,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 +347,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 +381,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();
@@ -371,10 +402,8 @@ bool UseUnityCounter() {
return Result;
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
bool IsSNIAvailable() {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
auto message = QDBusMessage::createMethodCall(
kSNIWatcherService.utf16(),
kSNIWatcherObjectPath.utf16(),
@@ -382,7 +411,7 @@ bool IsSNIAvailable() {
qsl("Get"));
message.setArguments({
kSNIWatcherService.utf16(),
kSNIWatcherInterface.utf16(),
qsl("IsStatusNotifierHostRegistered")
});
@@ -394,7 +423,6 @@ bool IsSNIAvailable() {
} else if (reply.error().type() != QDBusError::ServiceUnknown) {
LOG(("SNI Error: %1").arg(reply.error().message()));
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
return false;
}
@@ -408,7 +436,6 @@ quint32 djbStringHash(QString string) {
return hash;
}
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
bool IsAppMenuSupported() {
const auto interface = QDBusConnection::sessionBus().interface();
@@ -419,7 +446,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 +455,7 @@ void RegisterAppMenu(uint winId, const QDBusObjectPath &menuPath) {
message.setArguments({
winId,
QVariant::fromValue(menuPath)
QVariant::fromValue(QDBusObjectPath(menuPath))
});
QDBusConnection::sessionBus().send(message);
@@ -481,10 +508,9 @@ MainWindow::MainWindow(not_null<Window::Controller*> controller)
}
void MainWindow::initHook() {
_sniAvailable = IsSNIAvailable();
LOG(("System tray available: %1").arg(Logs::b(trayAvailable())));
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
_sniAvailable = IsSNIAvailable();
_sniDBusProxy = g_dbus_proxy_new_for_bus_sync(
G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
@@ -550,8 +576,9 @@ void MainWindow::initHook() {
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
updateWaylandDecorationColors();
LOG(("System tray available: %1").arg(Logs::b(trayAvailable())));
updateWaylandDecorationColors();
style::PaletteChanged(
) | rpl::start_with_next([=] {
updateWaylandDecorationColors();
@@ -716,8 +743,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());
}
@@ -795,25 +822,28 @@ void MainWindow::updateIconCounters() {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (UseUnityCounter()) {
const auto launcherUrl = "application://" + GetLauncherFilename();
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(counter, 9999);
QVariantMap dbusUnityProperties;
if (counter > 0) {
// Gnome requires that count is a 64bit integer
dbusUnityProperties.insert(
"count",
(qint64) ((counter > 9999)
? 9999
: counter));
dbusUnityProperties.insert("count-visible", true);
if (counterSlice > 0) {
dbusUnityProperties["count"] = counterSlice;
dbusUnityProperties["count-visible"] = true;
} else {
dbusUnityProperties.insert("count-visible", false);
dbusUnityProperties["count-visible"] = false;
}
QDBusMessage signal = QDBusMessage::createSignal(
auto signal = QDBusMessage::createSignal(
"/com/canonical/unity/launcherentry/"
+ QString::number(djbStringHash(launcherUrl)),
"com.canonical.Unity.LauncherEntry",
"Update");
signal << launcherUrl;
signal << dbusUnityProperties;
signal.setArguments({
launcherUrl,
dbusUnityProperties
});
QDBusConnection::sessionBus().send(signal);
}
@@ -1032,14 +1062,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 +1199,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());
}

View File

@@ -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;
@@ -82,11 +83,10 @@ 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;
QMenu *psMainMenu = nullptr;
QAction *psLogout = nullptr;

View File

@@ -17,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "lang/lang_keys.h"
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include <QtCore/QVersionNumber>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusMessage>
@@ -29,12 +28,9 @@ extern "C" {
#include <gio/gio.h>
#define signals public
} // extern "C"
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
namespace Platform {
namespace Notifications {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
namespace {
constexpr auto kDBusTimeout = 30000;
@@ -615,16 +611,13 @@ void NotificationData::notificationReplied(uint id, const QString &text) {
}
} // namespace
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
bool SkipAudio() {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (Supported()
&& GetCapabilities().contains(qsl("inhibitions"))
&& !InhibitedNotSupported) {
return Inhibited();
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
return false;
}
@@ -638,18 +631,12 @@ bool SkipFlashBounce() {
}
bool Supported() {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
return NotificationsSupported;
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
return false;
}
std::unique_ptr<Window::Notifications::Manager> Create(
Window::Notifications::System *system) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
GetSupported();
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if ((Core::App().settings().nativeNotifications() && Supported())
|| IsWayland()) {
@@ -659,27 +646,6 @@ std::unique_ptr<Window::Notifications::Manager> Create(
return nullptr;
}
#ifdef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
class Manager::Private {
public:
using Type = Window::Notifications::CachedUserpics::Type;
explicit Private(not_null<Manager*> manager, Type type) {}
void showNotification(
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {}
void clearAll() {}
void clearFromHistory(not_null<History*> history) {}
void clearFromSession(not_null<Main::Session*> session) {}
void clearNotification(NotificationId id) {}
};
#else // DESKTOP_APP_DISABLE_DBUS_INTEGRATION
class Manager::Private {
public:
using Type = Window::Notifications::CachedUserpics::Type;
@@ -866,7 +832,6 @@ void Manager::Private::clearNotification(NotificationId id) {
Manager::Private::~Private() {
clearAll();
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
Manager::Manager(not_null<Window::Notifications::System*> system)
: NativeManager(system)

View File

@@ -0,0 +1,40 @@
/*
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/notifications_manager_linux.h"
namespace Platform {
namespace Notifications {
bool SkipAudio() {
return false;
}
bool SkipToast() {
return false;
}
bool SkipFlashBounce() {
return false;
}
bool Supported() {
return false;
}
std::unique_ptr<Window::Notifications::Manager> Create(
Window::Notifications::System *system) {
if (IsWayland()) {
return std::make_unique<Window::Notifications::DummyManager>(system);
}
return nullptr;
}
} // namespace Notifications
} // namespace Platform

View File

@@ -24,6 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "core/application.h"
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include "platform/linux/linux_gsd_media_keys.h"
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <QtCore/QStandardPaths>
@@ -42,7 +46,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 +455,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 +511,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;
@@ -560,6 +525,18 @@ Window::Control GtkKeywordToWindowControl(const QString &keyword) {
} // namespace
void SetWatchingMediaKeys(bool watching) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
static std::unique_ptr<internal::GSDMediaKeys> Instance;
if (watching && !Instance) {
Instance = std::make_unique<internal::GSDMediaKeys>();
} else if (!watching && Instance) {
Instance = nullptr;
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
}
void SetApplicationIcon(const QIcon &icon) {
QApplication::setWindowIcon(icon);
}
@@ -805,7 +782,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 +827,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() {
@@ -1251,6 +1226,11 @@ void start() {
Libs::start();
MainWindow::LibsLoaded();
// wait for interface announce to know if native window frame is supported
if (const auto waylandIntegration = WaylandIntegration::Instance()) {
waylandIntegration->waitForInterfaceAnnounce();
}
}
void finish() {

View File

@@ -17,9 +17,6 @@ class LocationPoint;
namespace Platform {
inline void SetWatchingMediaKeys(bool watching) {
}
bool InFlatpak();
bool InSnap();
bool IsStaticBinary();

View File

@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "platform/linux/window_title_linux.h"
#include "platform/linux/linux_desktop_environment.h"
#include "platform/linux/linux_wayland_integration.h"
#include "base/platform/base_platform_info.h"
namespace Platform {
@@ -24,9 +24,10 @@ bool SystemMoveResizeSupported() {
} // namespace
bool AllowNativeWindowFrameToggle() {
const auto waylandIntegration = internal::WaylandIntegration::Instance();
return SystemMoveResizeSupported()
// https://gitlab.gnome.org/GNOME/mutter/-/issues/217
&& !(DesktopEnvironment::IsGnome() && IsWayland());
&& (!waylandIntegration
|| waylandIntegration->supportsXdgDecoration());
}
object_ptr<Window::TitleWidget> CreateTitleWidget(QWidget *parent) {

View File

@@ -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() {

View File

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

View File

@@ -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();

View File

@@ -30,11 +30,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_audio_input_tester.h"
#include "webrtc/webrtc_create_adm.h" // Webrtc::Backend.
#include "tgcalls/VideoCaptureInterface.h"
#include "facades.h"
#include "app.h" // App::restart().
#include "styles/style_layers.h"
namespace Settings {
namespace {
using namespace Webrtc;
} // namespace
Calls::Calls(
QWidget *parent,
@@ -58,7 +65,7 @@ void Calls::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto &settings = Core::App().settings();
const auto cameras = Webrtc::GetVideoInputList();
const auto cameras = GetVideoInputList();
if (!cameras.empty()) {
const auto hasCall = (Core::App().calls().currentCall() != nullptr);
@@ -66,16 +73,16 @@ void Calls::setupContent() {
const auto capturer = capturerOwner.get();
content->lifetime().add([owner = std::move(capturerOwner)]{});
const auto track = content->lifetime().make_state<Webrtc::VideoTrack>(
const auto track = content->lifetime().make_state<VideoTrack>(
(hasCall
? Webrtc::VideoState::Inactive
: Webrtc::VideoState::Active));
? VideoState::Inactive
: VideoState::Active));
const auto currentCameraName = [&] {
const auto i = ranges::find(
cameras,
settings.callVideoInputDeviceId(),
&Webrtc::VideoInput::id);
&VideoInput::id);
return (i != end(cameras))
? i->name
: tr::lng_settings_call_device_default(tr::now);
@@ -93,15 +100,15 @@ void Calls::setupContent() {
),
st::settingsButton
)->addClickHandler([=] {
const auto &devices = Webrtc::GetVideoInputList();
const auto &devices = GetVideoInputList();
const auto options = ranges::view::concat(
ranges::view::single(tr::lng_settings_call_device_default(tr::now)),
devices | ranges::view::transform(&Webrtc::VideoInput::name)
devices | ranges::view::transform(&VideoInput::name)
) | ranges::to_vector;
const auto i = ranges::find(
devices,
Core::App().settings().callVideoInputDeviceId(),
&Webrtc::VideoInput::id);
&VideoInput::id);
const auto currentOption = (i != end(devices))
? int(i - begin(devices) + 1)
: 0;
@@ -159,11 +166,11 @@ void Calls::setupContent() {
Core::App().calls().currentCallValue(
) | rpl::start_with_next([=](::Calls::Call *value) {
if (value) {
track->setState(Webrtc::VideoState::Inactive);
track->setState(VideoState::Inactive);
bubbleWrap->resize(bubbleWrap->width(), 0);
} else {
capturer->setPreferredAspectRatio(0.);
track->setState(Webrtc::VideoState::Active);
track->setState(VideoState::Active);
capturer->setOutput(track->sink());
}
}, content->lifetime());
@@ -252,6 +259,22 @@ void Calls::setupContent() {
// }, content->lifetime());
//#endif // Q_OS_MAC && !OS_MAC_STORE
const auto backend = [&]() -> QString {
using namespace Webrtc;
switch (settings.callAudioBackend()) {
case Backend::OpenAL: return "OpenAL";
case Backend::ADM: return "WebRTC ADM";
case Backend::ADM2: return "WebRTC ADM2";
}
Unexpected("Value in backend.");
}();
AddButton(
content,
rpl::single("Call audio backend: " + backend),
st::settingsButton
)->addClickHandler([] {
Ui::show(ChooseAudioBackendBox());
});
AddButton(
content,
tr::lng_settings_call_open_system_prefs(),
@@ -300,27 +323,30 @@ void Calls::requestPermissionAndStartTestingMicrophone() {
void Calls::startTestingMicrophone() {
_levelUpdateTimer.callEach(kMicTestUpdateInterval);
_micTester = std::make_unique<Webrtc::AudioInputTester>(
_micTester = std::make_unique<AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId());
}
QString CurrentAudioOutputName() {
const auto list = Webrtc::GetAudioOutputList();
const auto &settings = Core::App().settings();
const auto list = GetAudioOutputList(settings.callAudioBackend());
const auto i = ranges::find(
list,
Core::App().settings().callOutputDeviceId(),
&Webrtc::AudioOutput::id);
settings.callOutputDeviceId(),
&AudioOutput::id);
return (i != end(list))
? i->name
: tr::lng_settings_call_device_default(tr::now);
}
QString CurrentAudioInputName() {
const auto list = Webrtc::GetAudioInputList();
const auto &settings = Core::App().settings();
const auto list = GetAudioInputList(settings.callAudioBackend());
const auto i = ranges::find(
list,
Core::App().settings().callInputDeviceId(),
&Webrtc::AudioInput::id);
settings.callInputDeviceId(),
&AudioInput::id);
return (i != end(list))
? i->name
: tr::lng_settings_call_device_default(tr::now);
@@ -330,21 +356,22 @@ object_ptr<SingleChoiceBox> ChooseAudioOutputBox(
Fn<void(QString id, QString name)> chosen,
const style::Checkbox *st,
const style::Radio *radioSt) {
const auto &devices = Webrtc::GetAudioOutputList();
const auto &settings = Core::App().settings();
const auto list = GetAudioOutputList(settings.callAudioBackend());
const auto options = ranges::view::concat(
ranges::view::single(tr::lng_settings_call_device_default(tr::now)),
devices | ranges::view::transform(&Webrtc::AudioOutput::name)
list | ranges::view::transform(&AudioOutput::name)
) | ranges::to_vector;
const auto i = ranges::find(
devices,
Core::App().settings().callOutputDeviceId(),
&Webrtc::AudioOutput::id);
const auto currentOption = (i != end(devices))
? int(i - begin(devices) + 1)
list,
settings.callOutputDeviceId(),
&AudioOutput::id);
const auto currentOption = (i != end(list))
? int(i - begin(list) + 1)
: 0;
const auto save = [=](int option) {
const auto deviceId = option
? devices[option - 1].id
? list[option - 1].id
: "default";
Core::App().calls().setCurrentAudioDevice(false, deviceId);
chosen(deviceId, options[option]);
@@ -362,21 +389,22 @@ object_ptr<SingleChoiceBox> ChooseAudioInputBox(
Fn<void(QString id, QString name)> chosen,
const style::Checkbox *st,
const style::Radio *radioSt) {
const auto devices = Webrtc::GetAudioInputList();
const auto &settings = Core::App().settings();
const auto list = GetAudioInputList(settings.callAudioBackend());
const auto options = ranges::view::concat(
ranges::view::single(tr::lng_settings_call_device_default(tr::now)),
devices | ranges::view::transform(&Webrtc::AudioInput::name)
list | ranges::view::transform(&AudioInput::name)
) | ranges::to_vector;
const auto i = ranges::find(
devices,
list,
Core::App().settings().callInputDeviceId(),
&Webrtc::AudioInput::id);
const auto currentOption = (i != end(devices))
? int(i - begin(devices) + 1)
&AudioInput::id);
const auto currentOption = (i != end(list))
? int(i - begin(list) + 1)
: 0;
const auto save = [=](int option) {
const auto deviceId = option
? devices[option - 1].id
? list[option - 1].id
: "default";
Core::App().calls().setCurrentAudioDevice(true, deviceId);
chosen(deviceId, options[option]);
@@ -390,5 +418,33 @@ object_ptr<SingleChoiceBox> ChooseAudioInputBox(
radioSt);
}
object_ptr<SingleChoiceBox> ChooseAudioBackendBox(
const style::Checkbox *st,
const style::Radio *radioSt) {
const auto &settings = Core::App().settings();
const auto list = GetAudioInputList(settings.callAudioBackend());
const auto options = std::vector<QString>{
"OpenAL",
"Webrtc ADM",
#ifdef Q_OS_WIN
"Webrtc ADM2",
#endif // Q_OS_WIN
};
const auto currentOption = static_cast<int>(settings.callAudioBackend());
const auto save = [=](int option) {
Core::App().settings().setCallAudioBackend(
static_cast<Webrtc::Backend>(option));
Core::App().saveSettings();
App::restart();
};
return Box<SingleChoiceBox>(
rpl::single<QString>("Calls audio backend"),
options,
currentOption,
save,
st,
radioSt);
}
} // namespace Settings

View File

@@ -69,6 +69,9 @@ inline constexpr auto kMicTestAnimationDuration = crl::time(200);
Fn<void(QString id, QString name)> chosen,
const style::Checkbox *st = nullptr,
const style::Radio *radioSt = nullptr);
[[nodiscard]] object_ptr<SingleChoiceBox> ChooseAudioBackendBox(
const style::Checkbox *st = nullptr,
const style::Radio *radioSt = nullptr);
} // namespace Settings

View File

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

View File

@@ -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();

View File

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

View File

@@ -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());
}

View File

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

View 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

View 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

View File

@@ -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() {

View File

@@ -248,6 +248,30 @@ protected:
};
class DummyManager : public NativeManager {
public:
using NativeManager::NativeManager;
protected:
void doShowNativeNotification(
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) override {
}
void doClearAllFast() override {
}
void doClearFromHistory(not_null<History*> history) override {
}
void doClearFromSession(not_null<Main::Session*> session) override {
}
};
QString WrapFromScheduled(const QString &text);
} // namespace Notifications

View File

@@ -1,12 +1,14 @@
FROM centos:7 AS builder
ENV GIT https://github.com
ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig
ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig:/usr/local/share/pkgconfig
ENV QT 5_15_2
ENV QT_TAG v5.15.2
ENV QT_PREFIX /usr/local/desktop-app/Qt-5.15.2
ENV OPENSSL_VER 1_1_1
ENV OPENSSL_PREFIX /usr/local/desktop-app/openssl-1.1.1
ENV PATH ${PATH}:${QT_PREFIX}/bin
ENV Qt5_DIR ${QT_PREFIX}
RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
RUN yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm
@@ -27,8 +29,20 @@ ENV LibrariesPath /usr/src/Libraries
WORKDIR $LibrariesPath
FROM builder AS patches
RUN git clone $GIT/desktop-app/patches.git
RUN cd patches && git checkout 6afd91a
ADD https://api.github.com/repos/desktop-app/patches/git/refs/heads/master patches-version.json
RUN git clone --depth=1 $GIT/desktop-app/patches.git
FROM builder AS extra-cmake-modules
RUN git clone -b v5.77.0 --depth=1 $GIT/KDE/extra-cmake-modules.git
WORKDIR extra-cmake-modules
RUN cmake3 -B build . -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF
RUN cmake3 --build build -j$(nproc)
RUN DESTDIR="$LibrariesPath/extra-cmake-modules-cache" cmake3 --install build
WORKDIR ..
RUN rm -rf extra-cmake-modules
FROM builder AS libffi
RUN git clone -b v3.3 --depth=1 $GIT/libffi/libffi.git
@@ -107,20 +121,18 @@ RUN rm -rf libxcb
FROM builder AS xcb-wm
RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-wm.git
RUN git clone -b 0.4.1 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-wm.git
WORKDIR libxcb-wm
RUN git checkout 0.4.1
RUN ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-wm-cache" install
FROM builder AS xcb-util
RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-util.git
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-util.git
WORKDIR libxcb-util
RUN git checkout 0.4.0
RUN ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-util-cache" install
@@ -128,30 +140,27 @@ RUN make DESTDIR="$LibrariesPath/xcb-util-cache" install
FROM builder AS xcb-image
COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache /
RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-image.git
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-image.git
WORKDIR libxcb-image
RUN git checkout 0.4.0
RUN ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-image-cache" install
FROM builder AS xcb-keysyms
RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms.git
RUN git clone -b 0.4.0 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms.git
WORKDIR libxcb-keysyms
RUN git checkout 0.4.0
RUN ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-keysyms-cache" install
FROM builder AS xcb-render-util
RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util.git
RUN git clone -b 0.3.9 --depth=1 --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util.git
WORKDIR libxcb-render-util
RUN git checkout 0.3.9
RUN ./autogen.sh --enable-static
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/xcb-render-util-cache" install
@@ -208,6 +217,32 @@ RUN make DESTDIR="$LibrariesPath/wayland-cache" install
WORKDIR ..
RUN rm -rf wayland
FROM builder AS wayland-protocols
COPY --from=wayland ${LibrariesPath}/wayland-cache /
RUN git clone -b 1.18 --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols.git
WORKDIR wayland-protocols
RUN ./autogen.sh
RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/wayland-protocols-cache" install
WORKDIR ..
RUN rm -rf wayland-protocols
FROM builder AS plasma-wayland-protocols
COPY --from=extra-cmake-modules ${LibrariesPath}/extra-cmake-modules-cache /
RUN git clone -b v1.1.1 --depth=1 $GIT/KDE/plasma-wayland-protocols.git
WORKDIR plasma-wayland-protocols
RUN cmake3 -B build . -DCMAKE_BUILD_TYPE=Release
RUN cmake3 --build build -j$(nproc)
RUN DESTDIR="$LibrariesPath/plasma-wayland-protocols-cache" cmake3 --install build
WORKDIR ..
RUN rm -rf plasma-wayland-protocols
FROM builder AS libva
COPY --from=libffi ${LibrariesPath}/libffi-cache /
@@ -457,6 +492,34 @@ RUN make INSTALL_ROOT="$LibrariesPath/qt-cache" install
WORKDIR ..
RUN rm -rf qt_${QT}
FROM builder AS kwayland
COPY --from=extra-cmake-modules ${LibrariesPath}/extra-cmake-modules-cache /
COPY --from=libffi ${LibrariesPath}/libffi-cache /
COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache /
COPY --from=xcb ${LibrariesPath}/xcb-cache /
COPY --from=xcb-wm ${LibrariesPath}/xcb-wm-cache /
COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache /
COPY --from=xcb-image ${LibrariesPath}/xcb-image-cache /
COPY --from=xcb-keysyms ${LibrariesPath}/xcb-keysyms-cache /
COPY --from=xcb-render-util ${LibrariesPath}/xcb-render-util-cache /
COPY --from=wayland ${LibrariesPath}/wayland-cache /
COPY --from=wayland-protocols ${LibrariesPath}/wayland-protocols-cache /
COPY --from=plasma-wayland-protocols ${LibrariesPath}/plasma-wayland-protocols-cache /
COPY --from=openssl ${LibrariesPath}/openssl-cache /
COPY --from=xkbcommon ${LibrariesPath}/xkbcommon-cache /
COPY --from=qt ${LibrariesPath}/qt-cache /
RUN git clone -b v5.77.0 --depth=1 $GIT/KDE/kwayland.git
WORKDIR kwayland
RUN cmake3 -B build . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF
RUN cmake3 --build build -j$(nproc)
RUN DESTDIR="$LibrariesPath/kwayland-cache" cmake3 --install build
WORKDIR ..
RUN rm -rf kwayland
FROM patches AS breakpad
RUN git clone https://chromium.googlesource.com/breakpad/breakpad.git
@@ -482,7 +545,7 @@ RUN make DESTDIR="$BreakpadCache" install
WORKDIR src
RUN rm -rf testing
RUN git clone $GIT/google/googletest.git testing
RUN git clone --depth=1 $GIT/google/googletest.git testing
WORKDIR tools
RUN sed -i 's/minidump_upload.m/minidump_upload.cc/' linux/tools_linux.gypi
@@ -503,10 +566,10 @@ COPY --from=opus ${LibrariesPath}/opus-cache /
COPY --from=ffmpeg ${LibrariesPath}/ffmpeg-cache /
COPY --from=openssl ${LibrariesPath}/openssl-cache /
RUN git clone --recursive $GIT/desktop-app/tg_owt.git
ADD https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master tg_owt-version.json
RUN git clone --depth=1 --recursive $GIT/desktop-app/tg_owt.git
WORKDIR tg_owt
RUN git checkout 75ac669
RUN cmake3 -B out/Release . \
-DCMAKE_BUILD_TYPE=Release \
@@ -552,6 +615,7 @@ COPY --from=openal ${LibrariesPath}/openal-cache /
COPY --from=openssl ${LibrariesPath}/openssl-cache /
COPY --from=xkbcommon ${LibrariesPath}/xkbcommon-cache /
COPY --from=qt ${LibrariesPath}/qt-cache /
COPY --from=kwayland ${LibrariesPath}/kwayland-cache /
COPY --from=breakpad ${LibrariesPath}/breakpad breakpad
COPY --from=breakpad ${LibrariesPath}/breakpad-cache /
COPY --from=webrtc ${LibrariesPath}/tg_owt tg_owt

View File

@@ -1,7 +1,7 @@
AppVersion 2005000
AppVersion 2005004
AppVersionStrMajor 2.5
AppVersionStrSmall 2.5
AppVersionStr 2.5.0
BetaChannel 0
AppVersionStrSmall 2.5.4
AppVersionStr 2.5.4
BetaChannel 1
AlphaVersion 0
AppVersionOriginal 2.5
AppVersionOriginal 2.5.4.beta

View File

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

Some files were not shown because too many files have changed in this diff Show More